Introduction
I had a hard time coding 3D lighting of a diamond-like object built out of (triangular) facets. Not that the code was giving me headaches, but my math skills are quite rusty ;-) The results of my findings are in this tutorial. I'll show you how to code the lighting of an object by a pinpoint light source moving over a sphere.
No stress, I'll help you recall the trigonometry basics and illustrate the steps along the way. I however will assume some knowledge of flash, like making MCs, functions, the attachMovie scripting (+linkage), etc.
First play around with the my result of the adventure in math to come ;-)
PS. The light source can be set to manual: it'll then follow your mouse instead of following the path of a deltoid. Use the menu.
Next chapter I'll explain how to visualize the point you'd want your light to be most fierce, then we'll go over the math. After that I'll discuss chunks of code and we'll end with the whole script.
Pseudo 3D
As you can see we are working on a top view graphic. To show more clearly what we are trying to accomplish, here's an object at different angles:
My trick to faking 3D is to re-calculate the angles of all facets of the object into 2 angles: z-axis and xy-axis. The farther the light moves away from these coordinates projected on the sphere, the dimmer the lighting gets. That's where the trigonometrics come in handy. There alse is a way of doing this more accurate by using matrixes for every coordinate, but my math knowledge on that is even more rusty!. For this tutorial skipping the matrixes will also do just fine.
In-depth: The Math
We'll have to calculate a number of things:
- the angle of the facets on the z-axis
- the angle of the facets on the xy-axis
- the angle of the light source on the z-axis - the light source has a fixed distance: the radius of the sphere
- the angle of the light source on the xy-axis
- the whiteness of each facet - depending on both angle (50%) and distance (50%) of the light source
- the path the light source will follow in 'automatic' mode
sin(a) = B / C | cos(a) = A / C | tan(a) = B / A |
sin(b) = 1 (deg) | cos(b) = 0 (deg) | tan(b) = endless |
sin(c) = A / C | cos(c) = B / C | tan(c) = A / B |
And this pythagoras thingy?
C = sqrt(A*A + B*B) |
Important! - Flash works in radians! The formula for calculating degrees from radians is:
radian = math.PI/180 * degree |
Calculating the facet angles
To save me some serious paper filling calculations I've built in an angle detector (it's the lower left button). Just drag point1 and point2 parallel with the desired angle ;-)
The front and side view can be accesed with the top right button.
The use of atan is discussed in the next topic. The formula code is on the angle detection line mc:
onClipEvent (enterFrame) {
//place the line between mcs point1 and point2
//notice the width and height of this line are 100
this._x = _root.angledetect.point1._x;
this._y = _root.angledetect.point1._y;
this._xscale = (_root.angledetect.point2._x-_root.angledetect.point1._x);
this._yscale = (_root.angledetect.point2._y-_root.angledetect.point1._y);
// Math.atan for angles
xa= this._width;
ya= this._height;
_parent.angle=(Math.atan(ya/xa))/(Math.PI/180);
// -------------------------------
// make less decimals and calculate angle2
_parent.angle=(int(_parent.angle*100))/100
_parent.point1.angle= _parent.angle
_parent.point2.angle= 90 - _parent.angle
// -------------------------------
}
The light source angle on the xy-axis
We'll use Math.atan to calculate this angle: we know A and B (distance base to light source on x and on y):
xt= xm - xb;
yt= ym - yb;
xb and yb are the base coordinates, xm and ym the coordinates of the light source.
_root.DgrMouseX=int(Math.atan(yt/xt) / _root.convrad);
This function gives a flash angle, further on we'll recalculate this angle to an angle between 0 and 360 degrees.
The light source angle on the z axis
The fixed distance of the light source lets us calculate the angle it has. We know that side (C) will be the radius of the sphere, in this case 180. We calculate A (DeltaX) by using pythagoras on xt and yt.
_root.DeltaX=Math.sqrt(xt*xt+yt*yt);
Then we use the Math.acos on A and C to calculate the z-angle:
_root.DgrMouseZ=(Math.acos(_root.DeltaX/180))/_root.convrad);
Flash and angles
Flash treats degrees a little different than we're used to, so we'll re-calculate them to something more handable:
if(xt <0> 0){
_root.DgrMouseX += 270;
} else if(xt <0>= 0 && yt <= 0){ _root.DgrMouseX += 90; } else if(xt>= 0 && yt>= 0){
_root.DgrMouseX += 90;
}
This'll return any angle to one between 0 and 360 degrees counting from 0 at the top clockwise to 360.
On to the whiteness!
Each facet calculates it's own whiteness. The whiteness is done by setting the _alpha of a white facet MC over the background of the object.
Each facet has 2 variables: FacetX (its xy-angle) and FacetZ (its z-angle).
We'll use the Math.cos function so that one side of the object is lit (cos(x)=1) and the other side is not (cos(x)=-1).
function SinMouseX(AngleX) {
return Math.cos(Math.PI/180 * (_root.DgrMouseX - AngleX));
}
This function sets the most fierce point at FacetX and the less fierce point at (FacetX - 180) degrees.
Then we set the _alpha. The ratio is 50% so z-angle and xy-angle have an equally strong influence:
function AlphaMouseX(Z,X) {
return ((_root.DgrMouseZ/Z) * _root.ratio + X * (100 - _root.ratio));
}
The light source
The light source is symbolized by xm and ym. These coordintates either represent the coordinates of the mouse, or are put on the path of a function. For this path I chose a deltoid. I found this function on this page written by Paul Bourke in 1990.
I made a button to switch between 'manual' and 'automatic', the if statements handles that. The variable i represents theta needed to make steps in the deltoid function.
The _root.light mc is a white circle representing the light source.
// making the light move -----
i++;
if (i>= 360) { i=i - 360 }
// must be in radians (not degrees)
theta=(_root.convrad) * i;
// -------------------------------
// path of lightsource -----------
if (_root.lightsource == "manual") {
xm = _root._xmouse;
ym = _root._ymouse;
} else if (_root.lightsource == "automatic"){
// you can replace this function for any other you like better
xm = xb + (2 * a * Math.cos(theta) + a * Math.cos(2 * theta));
ym = yb + (2 * a * Math.sin(theta) - a * Math.sin(2 * theta));
}
// -------------------------------
The Code
Most of the code hangs on the light source:
onClipEvent(load) { this._x=414.0; this._y=198.0; // a * 3 is radius of circle ----- a = 59; i = 0; _root.ratio=50; _root.convrad=Math.PI/180 // x base & y base (in stead of 0,0) xb = 198.0; yb = 198.0; function SinMouseX(AngleX) { return Math.cos(Math.PI/180 * (_root.DgrMouseX - AngleX)); } function AlphaMouseX(Z,X) { return ((_root.DgrMouseZ/Z) * _root.ratio + X * (100 - _root.ratio)); } } onClipEvent(enterFrame) { // making the light move ----- i++; if (i>= 360) { i=i - 360 } // must be in radians (not degrees) theta=(_root.convrad) * i; // ------------------------------- // path of lightsource ----------- if (_root.lightsource == "manual") { xm = _root._xmouse; ym = _root._ymouse; } else if (_root.lightsource == "automatic"){ xm = xb + (2 * a * Math.cos(theta) + a * Math.cos(2 * theta)); ym = yb + (2 * a * Math.sin(theta) - a * Math.sin(2 * theta)); } // ------------------------------- // pythagoras for angle mouse & x,ybase xt= xm - xb; yt= ym - yb; _root.DeltaX=Math.sqrt(xt * xt + yt * yt); // ------------------------------- // z is angle upwards: x,ybase -> light = radius = 180 _root.DgrMouseZ=int(Math.acos(_root.DeltaX/180) / _root.convrad); _root.DgrMouseX=int(Math.atan(yt/xt) / _root.convrad); // ------------------------------- // recompute flash-angles to normal-angles if(xt <0> 0){ _root.DgrMouseX += 270; } else if(xt <0>= 0 && yt <= 0){ _root.DgrMouseX += 90; } else if(xt>= 0 && yt>= 0){ _root.DgrMouseX += 90; } // ------------------------------- }This is the code on the facets:
onClipEvent(load) { // angle of this facet ----------- FacetZ=45; FacetX=63.75; } onClipEvent(enterFrame) { XTemp=_root.light.SinMouseX(FacetX); // set alpha this._alpha = _root.light.AlphaMouseX(FacetZ,XTemp); }The End
Well that's it really. I started out with one object and ended with the diamond. Meanwhile I thought up some nice things you could do with this code and how I could expand further. But I think I won't develop this further for now, I got what I needed, and if people'd like to make complete 3D engines with rotating objects or lights that have spherical forms instead of a pinpointed one, be my guest ;-)
Anywayz. Have fun!Download the files used in this tutorial. Download (99 kb)