Is it possible to find the direction a tri is facing?


(Peter Rabbit) #1

I’m writing some code and I only want it to operate on tri-s facing up. So if they have an angle off the Z axis of more than 90 degrees then they’re ignored. Is there any easy way to check this?


(Secrop) #2

It’s called the Face Normal…

Normal = Normalize(cross(vert1-vert0, vert2-vert0))
where vert0, vert1 and vert2 are the positions of the triangle vertices.

If in the end, the Z component of the Normal is positive, then the triangle is facing up… more exactly, since the Normal is normalized, the Z component corresponds to the cosine of the angle between the Normal and the Z axis; 1 means the normal and the Z axis are the same, 0 means they are perpendicular, and -1 means the normal is pointing to -Z.


(Peter Rabbit) #3

Thanks! That’s really helpful. Does that return a vector? I’ve never used those before.


(Secrop) #4

yes, the cross product between two vectors (in this case two edges of the triangle,) returns a vector perpendicular to both vectors with a length equal to the product of the lenght of both vectors and the sine of the angle between them. (but since we are normalizing the cross product, the final lenght is just equal to 1)


(Peter Rabbit) #5

Hi again, sorry, it took me longer than I thought to get to this part of my code.

I realise this:

Normal = Normalize(cross(vert1-vert0, vert2-vert0))

Is probably pseudocode, I’m having trouble translating it into actual code. I’m not very used to python, or any weakly typed languages, so I’m not sure how much I have to specify and how much I don’t.

Here’s what I have so far in the relevant code block:

    #Extract verts
    v1 = obj.vertices[obj.polygons[x].vertices[0]]
    v2 = obj.vertices[obj.polygons[x].vertices[1]]
    v3 = obj.vertices[obj.polygons[x].vertices[2]]
    
    print ("Polygon no." + str(x))
    print ("V1 = " + str(v1.co))
    print ("V2 = " + str(v2.co))
    print ("V3 = " + str(v3.co))
    print ("")
    
    #Create normals
    ???
    
    #Find face normal
    faceNormal.cross(v2-v1, v3-v1)
    
    #Normalise result
    faceNormal.normalize()
    
    #Check if it faces up
    if faceNormal.z > 0:
        Print ("Polygon faces up!")
    else:
        Print ("Polygon is not facing up :(")

Am I supposed to instantiate two new normals using the obj’s edges, then cross them? Or do you literally just feed the verts into the cross function? If the latter, do I have to instantiate faceNormal as a normal first?

Thanks in advance for any help. I’m way out of my depth.


(RLGUY) #6

Hey, hope I’m able to help with this.

The Blender object stores face normals already calculated so you do not need to involve the cross product if you do not want to.

Here is an example script that loops through the active object’s polygons and calculates whether the polygon is pointing ‘up’.


import bpy

obj = bpy.context.scene.objects.active

for p in obj.data.polygons:
    # This operation converts the object's local model normal to the world normal (How it is rotated in the 3D viewport)
    normal = obj.matrix_world * p.normal

    print("The normal of this polygon is:", normal)
    if normal.z > 0:
        print("This polygon faces up!")
    else:
        print("This polygon is not facing up")

And if you do want to compute the normal using the vertices, you can use the ‘mathutils’ library for vector operations like cross and normalize:


import bpy
import mathutils


obj = bpy.context.scene.objects.active


for x,p in enumerate(obj.data.polygons):
    #Extract verts
    v1 = obj.matrix_world * obj.data.vertices[p.vertices[0]].co
    v2 = obj.matrix_world * obj.data.vertices[p.vertices[1]].co
    v3 = obj.matrix_world * obj.data.vertices[p.vertices[2]].co


    print("-------")
    print("Polygon no. " + str(x))
    print("V1 = " + str(v1))
    print("V2 = " + str(v2))
    print("V3 = " + str(v3))
    print("")


    #Find face normal
    face_normal = mathutils.Vector.cross(v2-v1, v3-v1)


    #Normalise result
    mathutils.Vector.normalize(face_normal)
    print("Face Normal: " + str(face_normal))


    #Check if it faces up
    if face_normal.z > 0:
        print ("Polygon faces up!")
    else:
        print ("Polygon is not facing up ")


(Peter Rabbit) #7

Thanks so much for your reply. I’ve managed to get the face normal using your code, and it’s almost working, but for some reason it’s reporting horizontal polygons as being upward facing (z > 0), even though the Z coord of their normal is 0 or -0.

Here’s the code (the stuff about the vertices is just for debugging, I’ll be removing it later):

    #Keeps track of loop for debugging. Deleteme later
    x=1
    
    #Loop through faces, determining which ones face up
    for p in obj.data.polygons:
        #Deleteme
        v1 = obj.data.vertices[p.vertices[0]]
        v2 = obj.data.vertices[p.vertices[1]]
        v3 = obj.data.vertices[p.vertices[2]]
        
        #Deleteme
        print ("Polygon no." + str(x))
        print ("V1 = " + str(v1.co))
        print ("V2 = " + str(v2.co))
        print ("V3 = " + str(v3.co))
        
        #Deleteme
        x += 1
        
        # This operation converts the object's local model normal to the world normal (How it is rotated in the 3D viewport)
        normal = obj.matrix_world * p.normal
        
        #Determines global orientation of polygon
        print("Normal = ", str(normal))
        if normal.z > 0:
            print("This polygon faces up!")
        else:
            print("This polygon is not facing up")
        
        #Deleteme
        print ("")

And the output when applied to a standard cube which has been converted to tris (another part of my code does this):

Beginning enforce draft
Faces converted to tris

Polygon no.1
V1 = <Vector (1.0000, -1.0000, -1.0000)>
V2 = <Vector (-1.0000, -1.0000, -1.0000)>
V3 = <Vector (-1.0000, 1.0000, -1.0000)>
Normal =  <Vector (0.0000, 0.0000, -1.0000)>
This polygon is not facing up


Polygon no.2
V1 = <Vector (-1.0000, 1.0000, 1.0000)>
V2 = <Vector (-1.0000, -1.0000, 1.0000)>
V3 = <Vector (1.0000, -1.0000, 1.0000)>
Normal =  <Vector (0.0000, 0.0000, 1.0000)>
This polygon faces up!


Polygon no.3
V1 = <Vector (1.0000, 1.0000, 1.0000)>
V2 = <Vector (1.0000, -1.0000, 1.0000)>
V3 = <Vector (1.0000, -1.0000, -1.0000)>
Normal =  <Vector (1.0000, -0.0000, 0.0000)>
This polygon faces up!


Polygon no.4
V1 = <Vector (1.0000, -1.0000, 1.0000)>
V2 = <Vector (-1.0000, -1.0000, 1.0000)>
V3 = <Vector (-1.0000, -1.0000, -1.0000)>
Normal =  <Vector (-0.0000, -1.0000, 0.0000)>
This polygon faces up!


Polygon no.5
V1 = <Vector (-1.0000, -1.0000, -1.0000)>
V2 = <Vector (-1.0000, -1.0000, 1.0000)>
V3 = <Vector (-1.0000, 1.0000, 1.0000)>
Normal =  <Vector (-1.0000, 0.0000, -0.0000)>
This polygon is not facing up


Polygon no.6
V1 = <Vector (1.0000, 1.0000, -1.0000)>
V2 = <Vector (-1.0000, 1.0000, -1.0000)>
V3 = <Vector (-1.0000, 1.0000, 1.0000)>
Normal =  <Vector (0.0000, 1.0000, 0.0000)>
This polygon faces up!


Polygon no.7
V1 = <Vector (1.0000, 1.0000, -1.0000)>
V2 = <Vector (1.0000, -1.0000, -1.0000)>
V3 = <Vector (-1.0000, 1.0000, -1.0000)>
Normal =  <Vector (0.0000, 0.0000, -1.0000)>
This polygon is not facing up


Polygon no.8
V1 = <Vector (1.0000, 1.0000, 1.0000)>
V2 = <Vector (-1.0000, 1.0000, 1.0000)>
V3 = <Vector (1.0000, -1.0000, 1.0000)>
Normal =  <Vector (0.0000, 0.0000, 1.0000)>
This polygon faces up!


Polygon no.9
V1 = <Vector (1.0000, 1.0000, -1.0000)>
V2 = <Vector (1.0000, 1.0000, 1.0000)>
V3 = <Vector (1.0000, -1.0000, -1.0000)>
Normal =  <Vector (1.0000, 0.0000, -0.0000)>
This polygon is not facing up


Polygon no.10
V1 = <Vector (1.0000, -1.0000, -1.0000)>
V2 = <Vector (1.0000, -1.0000, 1.0000)>
V3 = <Vector (-1.0000, -1.0000, -1.0000)>
Normal =  <Vector (-0.0000, -1.0000, -0.0000)>
This polygon is not facing up


Polygon no.11
V1 = <Vector (-1.0000, 1.0000, -1.0000)>
V2 = <Vector (-1.0000, -1.0000, -1.0000)>
V3 = <Vector (-1.0000, 1.0000, 1.0000)>
Normal =  <Vector (-1.0000, 0.0000, -0.0000)>
This polygon is not facing up


Polygon no.12
V1 = <Vector (1.0000, 1.0000, 1.0000)>
V2 = <Vector (1.0000, 1.0000, -1.0000)>
V3 = <Vector (-1.0000, 1.0000, 1.0000)>
Normal =  <Vector (0.0000, 1.0000, 0.0000)>
This polygon faces up!

As you can see on polygon number 3, the z coord of the normal is 0, but it still detects it as being greater than zero. What’s going on here?


(RLGUY) #8

The script reporting the triangle as up for a z coord of ‘0.0’ could be due to a precision issue with how computers do math. Computers aren’t able to make calculations with 100% precision, so the z coordinate might have actually been calculated to something like 0.00000000003213 instead of an exact 0, which is what may be causing the triangle to be reported as ‘up’.

You could change the condition to normal.z > some_very_small_number to prevent horizontal triangles from being reported:


// Here 1e-6 is the same as 10^-6 = 0.000001
if normal.z > 1e-6:
    print("This polygon faces up!")
else:
    print("This polygon is not facing up")


(Peter Rabbit) #9

Hmmm, so it could be a double, but it’s being written to console as a float? Sounds weird, but I suppose that would work.

Edit: Yep, that works, thanks!


(emu) #10

I have just a brief code review.

There is much easier way to do this:

face_normal = mathutils.geometry.normal(v1, v2, v3)

The method outputs a normalized result and it works with any number of vertices, not only triangles. You can just say:

mathutils.geometry.normal(v.co for v in polygon.vertices)

Obviously, you need to import mathutils.geometry in the beginning of the script.

Another note: if you really want to calculate the cross product, just use (v2 - v1).cross(v3 - v1).


(Peter Rabbit) #11

Thanks, in the end I went with RL guy’s solution which seemed simpler.