Big Normal Question

Discussion in 'Scripting' started by marty, Jun 28, 2007.

1. marty

Joined:
Apr 27, 2005
Posts:
1,170
If I cast a ray downward, using something like this:

var hit : RaycastHit;
Physics.Raycast (transform.position, transform.TransformDirection (-Vector3.up), hit);

How can I then calculate the average normal of the triangle hit and its immediate neighbors?

Plus, how can I make it so that closer neighboring trianlges are weighted more heavily in the average, proportionate to how close they are?

Unity Technologies

Joined:
Aug 11, 2006
Posts:
3,355
I can't figure out a way to do exactly what you want. Would it suffice to cast multiple rays and average the normals of their hits?

Unity Technologies

Joined:
Mar 16, 2005
Posts:
5,203
you can use the face index of the raycast and then interpolate between the normals of the 3 vertices based on the barycentric coordinate provided by the raycast hit.

4. seon

Joined:
Jan 10, 2007
Posts:
1,441
that's not even english :wink:

5. marty

Joined:
Apr 27, 2005
Posts:
1,170
I tried to strip down the portion of the "Painted Surface" Procedural demo code that does this, and got the attached. It doesn't seem to work though.

Plus, even if that code worked, it would only give me one average normal for a given face. What I would like is for the average normal to be unique even as I move across the face whose triangle is being hit by the ray, since the neighboring triangles are weighted based on distance to the ray hit point.

The net result that I'm after is being able to very smoothly track a transfrom across a "grainy" (low-poly) surface, via this averaging of the normal data.

Code (csharp):
1.
2. var hit : RaycastHit;
3. ray = Physics.Raycast (transform.position, transform.TransformDirection (-Vector3.up), hit);
4.
5. var filter : MeshFilter = hit.collider.GetComponent(MeshFilter);
6. var mesh = filter.mesh;
7. var position = filter.transform.InverseTransformPoint(hit.point);
8.
10. var vertices = mesh.vertices;
11. var normals = mesh.normals;
13.
14. //  calculate averaged normal of all surrounding vertices
15. var averageNormal = Vector3.zero;
16.
17. for (var i=0;i<vertices.length;i++) {
18.     var sqrMagnitude = (vertices[i] - position).sqrMagnitude;
19.
20.     //  early out if too far away
21.     if (sqrMagnitude > sqrRadius) continue;
22.
23.         var distance = Mathf.Sqrt(sqrMagnitude);
24.         var falloff = Mathf.Clamp01(1.0 - distance / inRadius);
25.         averageNormal += normals[i];
26.         averageNormal += falloff * normals[i];
27. }
28.
29. averageNormal = averageNormal.normalized;
30.
31. transform.rotation = Quaternion.LookRotation (Vector3.Cross (averageNormal, transform.TransformDirection (Vector3.left)), averageNormal);
32.
33. transform.position = hit.point + transform.TransformDirection (Vector3.up * surfaceOffset);
34.
35.

Unity Technologies

Joined:
Mar 16, 2005
Posts:
5,203
Thats exactly what my suggestion would do.
The vertex normals stored in the mesh are smoothed already. That means, if you have a sphere, the vertex normals are exactly the average between all the bordering triangles. Thus when you interpolate like i mentioned above you get a smoothly changing interpolated normal. Exactly what you want.

7. marty

Joined:
Apr 27, 2005
Posts:
1,170
But I would only get one average normal per face, right?

I want to be able to move across a face, casting the ray down, and get a new average normal with each bit of translation across the "surface" of that face.

And, if so, any chance you could pen a bit of the 3D Code Of The Gods you are referring to? Please, please, please?

8. RockHound

Joined:
Apr 14, 2006
Posts:
132
I've been helped a lot in these forums, but I haven't contributed a lot, so I'll take this opportunity.

The following code will smoothly interpolate the normals. The top part of the Update function was taken directly from the triangleIndex documentation of RaycastHit, where the mouse is tracked to provide the raycast point. You can modify that as you need.

The next part performs interpolation of the intersected triangle normals. At the bottom, the variable N is assigned the interpolated normal. I had (a form of) this code lying around in C#, so I had to translate it to JavaScript.

Code (csharp):
1.
2.
3. var P  : Vector3;
4. var P1 : Vector3;
5. var P2 : Vector3;
6. var P3 : Vector3;
7.
8. var N  : Vector3;
9. var N1 : Vector3;
10. var N2 : Vector3;
11. var N3 : Vector3;
12.
13.
14. function Update ()
15. {
16.     // Only if we hit something, do we continue
17.     var hit : RaycastHit;
18.     if (!Physics.Raycast (camera.ScreenPointToRay(Input.mousePosition), hit))
19.         return;
20.
21.     // Just in case, also make sure the collider also has a renderer
22.     // material and texture
23.     var meshCollider = hit.collider as MeshCollider;
24.     if (meshCollider == null || meshCollider.sharedMesh == null)
25.         return;
26.
27.     var mesh : Mesh = meshCollider.sharedMesh;
28.     var vertices = mesh.vertices;
29.     var normals = mesh.normals;
30.     var triangles = mesh.triangles;
31.
32.     // Extract local space vertices that were hit
33.     var P1 = vertices[triangles[hit.triangleIndex * 3 + 0]];
34.     var P2 = vertices[triangles[hit.triangleIndex * 3 + 1]];
35.     var P3 = vertices[triangles[hit.triangleIndex * 3 + 2]];
36.
37.     // Extract normals
38.     var N1 = normals[triangles[hit.triangleIndex * 3 + 0]];
39.     var N2 = normals[triangles[hit.triangleIndex * 3 + 1]];
40.     var N3 = normals[triangles[hit.triangleIndex * 3 + 2]];
41.
42.     // Transform local space vertices to world space
43.     var hitTransform : Transform = hit.collider.transform;
44.     P1 = hitTransform.TransformPoint(P1);
45.     P2 = hitTransform.TransformPoint(P2);
46.     P3 = hitTransform.TransformPoint(P3);
47.     N1 = hitTransform.TransformDirection(N1);
48.     N2 = hitTransform.TransformDirection(N2);
49.     N3 = hitTransform.TransformDirection(N3);
50.     P = hit.point;
51.
52.     // finally, interpolate vertex normals
53.     var Nt = Vector3.Cross(P2-P1, P3-P1);
54.     var Na = Vector3.Cross(P3-P2,  P-P2);
55.     var Nb = Vector3.Cross(P1-P3,  P-P3);
56.     var Nc = Vector3.Cross(P2-P1,  P-P1);
57.
58.     // avoid divide by small
59.     var N_norm_squared = Nt.sqrMagnitude;
60.     if (N_norm_squared < 0.001)
61.         Debug.Log("triangle vertices are too close");
62.
63.     // compute barycentric coords
64.     // taken from Pete Shirley's tiger book (Fundamentals of Computer Graphics, 1st Ed.), p. 46
65.     var one_over = 1.0 / N_norm_squared;
66.     var alpha = Vector3.Dot(Nt, Na) * one_over;
67.     var beta = Vector3.Dot(Nt, Nb) * one_over;
68.     var gamma = Vector3.Dot(Nt, Nc) * one_over;
69.
70.     // Check if barycentric coords are within triangle
71.     // We shouldn't have to perform this check because we assume
72.     //  the triangle index returned by RayCast hit is correct
73.     // --------
74.     //if (alpha < 0 || alpha > 1 ||
75.     //  beta < 0 ||  beta > 1 ||
76.     //  gamma < 0 || gamma > 1)
77.     //  Debug.Log("hit point is outside of triangle");
78.
79.     // interpolate normal
80.     N = N1*alpha + N2*beta + N3*gamma;
81.     Debug.DrawLine(P, P + N, Color.green);
82. }
83.

9. marty

Joined:
Apr 27, 2005
Posts:
1,170
Wow!

That's amazing. And it works like a charm.

You appear to be from the same planet of Supergeniusrobot Coders that Joe hails from, RockHound - great to have you here with us on Earth!

;-)

10. marty

Joined:
Apr 27, 2005
Posts:
1,170
BTW, RH, what did you develop this code for orignally?

11. RockHound

Joined:
Apr 14, 2006
Posts:
132
I used it to do the same type of smoothing for colors that you need for your normals. At the time, we were darkening our character based on the baked vertex lighting of the scene. I yanked the code when 1.6 (I think) introduced the textureCoord field in the RaycastHit struct, and we could simply use a light map.

Which raises the question, maybe the 'normal' field of the RaycastHit struct is already doing smooth (barycentric) interpolation, in which case my contributed code is moot. If not, it could easily do so, since I assume uv's are interpolated similarly.

Joined:
Apr 27, 2005
Posts:
1,170

13. RockHound

Joined:
Apr 14, 2006
Posts:
132
Hi Marty,

I just remembered that the above code does not necessarily preserve the length of the interpolated normals. If you need the smoothed normal to have a similar length as the original three normals, you can replace this line (at the bottom of the Update function)

Code (csharp):
1. N = N1*alpha + N2*beta + N3*gamma;
with this
Code (csharp):
1. N = N1*alpha + N2*beta + N3*gamma;
2. N.Normalize();
3. N *= N1.magnitude*alpha + N2.magnitude*beta + N3.magnitude*gamma;
It adds some processing, but you probably won't see any performance hit.

14. marty

Joined:
Apr 27, 2005
Posts:
1,170
Thanks, RH.

A non-normalized normal is okay for orienting a transform, right?

Such as in this code snippet:

Code (csharp):
1. transform.rotation = Quaternion.LookRotation (Vector3.Cross (averagedNormal, transform.TransformDirection (Vector3.left)), averagedNormal);

15. RockHound

Joined:
Apr 14, 2006
Posts:
132
Yea, that works just fine without normalizing.