How to correct surface normal for translated object

by Todd McIntosh   Last Updated October 19, 2019 17:15 PM

**Edit - I'm willing to pay a $15USD bounty via Paypal for the accepted answer to this question.

I have a script which generates random points on the surface of a sphere and then places cone objects at each point.

Using the emitter.closest_point_on_mesh call I can get the surface normal from the sphere at a spot very close to the generated point and then apply that normal to the rotation of the cone object, thereby pointing the cone out from the surface of the sphere at an angle that very closely approximates the normal from the sphere surface.

However this only works when the sphere is at the world origin point. When I move the sphere, it no longer works. The cones no longer point away from the sphere surface at the normal direction.

I've tried different techniques involved multiplying the sphere's matrix_world value by the normal, but I can't seem to find the right combo to make it work.

Any help would be GREATLY appreciated!

import bpy
from mathutils.bvhtree import BVHTree
import mathutils
import math
import bpy_extras
from bpy_extras import mesh_utils
import timeit
from random import random
from math import radians
from mathutils import Vector
import bmesh

scn = bpy.context.scene
objs = bpy.data.objects


def DeletePreviousParticles():

    for o in bpy.data.objects:
        if o.name.startswith("particle"):
            objs.remove(o, True)
    #select sphere to update window
    sphere = bpy.data.objects['Sphere']
    bpy.context.scene.objects.active = sphere



def PlaceParticle(emitter,src_obj, point):

    dupCount = 0
    maxObjectDimension = 2


    new_obj = src_obj.copy()
    new_obj.data = src_obj.data.copy()
    scn.objects.link(new_obj)
    new_obj.animation_data_clear()

    new_obj.location[0]=point[0]
    new_obj.location[1]=point[1]
    new_obj.location[2]=point[2]


    new_obj.name = "particle_" + str(dupCount)
    dupCount+=1


    #calculate normal from surface of emitter 
    partLoc = new_obj.location
    surfaceNormal = emitter.closest_point_on_mesh( partLoc )[1]
    print('surfaceNormal' + str(surfaceNormal))

    #the following line does not work when emitter object is not at world origin point
    new_obj.rotation_euler = surfaceNormal.to_track_quat('Z', 'Y').to_euler() 





def Main():

    emitter = bpy.data.objects['Sphere']
    src_obj = bpy.data.objects['sourceParticle']

    print("starting------------------------------------------------------------")

    DeletePreviousParticles()

    me = emitter.data
    me.calc_tessface() # recalculate tessfaces
    tessfaces_select = me.tessfaces #[f for f in me.tessfaces if f.select]
    pointList = bpy_extras.mesh_utils.face_random_points(1, tessfaces_select)

    totalPoints = 20
    pointCounter = 0

    for p in pointList:

            if(pointCounter<totalPoints):

                mat1 = emitter.matrix_world
                adjPoints = mat1 * p

                PlaceParticle(emitter, src_obj, adjPoints)

                pointCounter +=1
            else:
                break    

    print("done")



Main()

enter image description here

enter image description here

Here's the source file:

Tags : python normals


Related Questions


Updated April 03, 2015 21:23 PM

Updated April 11, 2015 20:06 PM

Updated August 13, 2016 08:06 AM

Updated September 06, 2019 01:15 AM

Updated March 20, 2017 19:15 PM