3
$\begingroup$

In a recent answer I employed Object.ray_cast which returns the location and normal of a ray hitting a face. The normal is needed to calculate the rays reflection, ray.reflect(norm). The normal returned for any hit on a face is the face's normal, not as I was hoping the normal for that location on the face.

In image camera is projecting a grid of rays onto sphere and reflecting them back to create the plane.

enter image description here

The result of using face normal can be clearly seen in reflected mesh. Mimics the rings / loops of the sphere.

To get around this, as an estimate a barycentric interpolation using the triangle constructed from the centre of the face, and the vertices of the outer edge that the hit location falls within. Using the normals at these three points, the location of hit on face can be plugged into mathutils.geometry.barycentric_transform(...) to estimate the normal at the point of hit.

def calc_normal(mesh, face_index, point):
    face = mesh.polygons[face_index]
    o = face.center
    p = point - o
    # find the edge

    for ek in face.edge_keys:
        v0, v1 = (mesh.vertices[i] for i in ek)
        if (v0.co - o).angle(p) <= (v0.co - o).angle(v1.co - o):
            break

    return barycentric_transform(point, o, v0.co, v1.co,
                                face.normal, v0.normal, v1.normal)



def obj_raycast(obj, ray_origin, ray_target, matrix=None):
    # returns global coords
    if matrix is None:
        matrix = obj.matrix_world

    # get the ray relative to the object
    matrix_inv = matrix.inverted()
    ray_origin_obj = matrix_inv * ray_origin
    ray_target_obj = matrix_inv * ray_target
    ray_direction_obj = ray_target_obj - ray_origin_obj

    # cast the ray
    success, location, normal, face_index = obj.ray_cast(ray_origin_obj, ray_direction_obj)

    if success:
        n = calc_normal(obj.data, face_index, location)
        return matrix * location, (matrix * (location + n) - matrix * location).normalized(), face_index
    else:
        return None, None, None

enter image description here Which produces this "ok" result.

Are there other ways to calculate / estimate the surface normal at any given point. Perhaps using BVHTree Utilities ray_cast

$\endgroup$
  • 1
    $\begingroup$ Isn't the normal of the vertice in the center of the face inaccurate (for the sphere anyways)? So we have 2 perfect normals, which are the vertices at the corners of the face and the one in the middle. Shouldn't we first calculate the right normal for the center of the face, instead of using the normal of the face? I'm just not sure how that translates to other objects $\endgroup$ – WhatAMesh Feb 21 at 15:33
  • $\begingroup$ Haven't tested, was under the impression the face normal was the value for the "imaginary" vertex in the middle of the face. (as displayed with show normals) Could quickly test on sphere if face.calc_center() - o is || to face.normal where o is the geometric origin of the sphere. $\endgroup$ – batFINGER Feb 21 at 15:42
  • $\begingroup$ In the code to answer I added an empty as arrow as a quick way to display vectors, or use the mathviz addon to test. $\endgroup$ – batFINGER Feb 21 at 15:47
  • $\begingroup$ It is not parallel. The normal (blue) of the face misses (0,0,0) when sphere center is (0,0,0): imgur.com/a/aFVmmfy. The white line is a self-constructed edge showing a line from (0,0,0) to center of face (which is hopefully x,y,z when selecting the face^^) $\endgroup$ – WhatAMesh Feb 21 at 16:02
  • $\begingroup$ wow that much.. smooth or flat shading? $\endgroup$ – batFINGER Feb 21 at 16:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.