+1  A: 

To get the normal vector, calculate the cross product between P2 - P1 and P3 - P1.

To get the angle, use the dot product between the normal and the lightPoint. Remember that dot(a, b) = |a||b| * cos(theta), so since you can calculate the length of both, you can get theta (the angle between them).

Matias Valdenegro
Thanks for the answer, I'm just having issues turning this into code :)
Dead account
+4  A: 

OK, here goes...

You have points A, B, and C, each of which has coordinates x, y, and z. You want the length of the normal, as Matias said, so that you can calculate the angle that a vector between your point and the origin of the normal makes with the normal itself. It may help you to realize that your image is misleading for the purposes of our calculations; the normal (blue line) should be emanating from one of the vertices of the triangle. To turn your point into a vector it has to go somewhere, and you only know the points of the vertices.

Anyway, first step is to turn your Point3Ds into Vector3Ds. This is accomplished simply by taking the difference between each of the origin and destination points' coordinates. Use one point as the origin for both vectors, and the other two points as the destination of each. So if A is your origin, subtract A from B, then A from C. You now have a vector that describes the magnitude of the movement in X, Y, and Z axes to go from Point A to point B, and likewise from A to C. It is worth noting that a theoretical vector has no start point of its own; to get to point B, you have to start at A and apply the vector.

The System.Windows.Media.Media3D namespace has a Vector3D struct you can use, and handily enough, the Point3D in the same namespace has a Subtract() function that returns a Vector3D:

Vector3D vectorA = pointB.Subtract(pointA);
Vector3D vectorB = pointB.Subtract(pointA);

Now, the normal is the cross product of the two vectors. Use the following formula:

v1 x v2 = [ y1*z2 - y2*z1 , z1*x2 - z2*x1 , x1*y2 - x2*y1 ]

This is based on matrix math you don't strictly have to know to implement it. The three terms in the matrix are the X, Y, and Z of the normal vector. Luckily enough, if you use the Media3D namespace, the Vector3D structure has a CrossProduct() method that will do this for you:

Vector3D vectorNormal = Vector3D.CrossProduct(vectorA, vectorB);

Now, you need a third vector, between LightPoint and A:

Vector3D vectorLight = PointA.Subtract(LightPoint);

This is the direction that light will travel to get to PointA from your source.

Now, to find the angle between them, you compute the dot product of these two and the length of these two:

|v| = sqrt(x^2 + y^2 + z^2)

v1 * v2 = x1*x2 + y1*y2 + z1*z2

Or, if you're using Media3D, Vector3D has a Length property and a DotProduct static method:

double lengthLight = vectorLight.Length;
double lengthNormal = vectorNormal.Length;
double dotProduct = Vector3D.DotProduct(vectorNormal, vectorLight);

Finally, the formula Matias mentioned:

v1 * v2 = |v1||v2|cos(theta)

rearranging and substituting variable names:

double theta = arccos(dotProduct/(lengthNormal*lengthLight))

Or, if you were smart enough to use Media3D objects, forget all the length and dot product stuff:

double theta = Vector3D.AngleBetween(vectorNormal, vectorLight);

Theta is now the angle in degrees. Multiply this by the quantity 2(pi)/360 to get radians if you need it that way.

The moral of the story is, use what the framework gives you unless you have a good reason to do otherwise; using the Media3D namespace, all the vector algebra goes away and you can find the answer in 5 easy-to-read lines [I edited this, adding the code I used -- Ian]:

Vector3D vectorA = Point3D.Subtract(pointB, pointA);
Vector3D vectorB = Point3D.Subtract(pointC, pointB);
Vector3D vectorNormal = Vector3D.CrossProduct(vectorA, vectorB);
Vector3D vectorLight = Point3D.Subtract(pointA, LightPoint);

double lengthLight = light.Length;
double lengthNormal = norm.Length;
double dotProduct = Vector3D.DotProduct(norm, light);
double theta = Math.Acos(dotProduct / (lengthNormal * lengthLight));

// Convert to intensity between 0..255 range for 'Color.FromArgb(... 
//int intensity = (120 + (Math.Cos(theta) * 90));
KeithS
+1 Thanks for a great answer! System.Windows.Media.Media3D is Framework 4... looks like I'm upgrading :)
Dead account
It's in 3.5 as well. You may have to set the app up as a WPF application, I'm not sure.
KeithS
A: 

Only tangentially related, but most graphics systems also allow for surface normal interpolation, where each vertex has an associated normal. The effective normal at a given U,V then is interpolated from the vertex normals. This allows for smoother shading and much better representation of 'curvature' in lighting without having to rely on a very large number of triangles. Many systems do similar tricks with 'bump mapping' to introduce perturbations in the normal and get a sense of texture without modeling the individual small facets.

Now you can compute the angle between the interpolated normal and the light vector, as others have already described.

One other point of interest is to consider whether you have an ambient light source. If you can pretend that the point light is infinitely far away (effectively true for a source like direct sunlight on a small surface like the Earth), you can save all the trouble of calculating vector differences and simply assume the incoming angle of the light is constant. Now you have a single constant light vector for your dot product.

Dan Bryant