rotate object to point

so say I have a agent I want to attach an object to, and I don’t want the object to teleport or ghost into position,

I was thinking that the object will float to the agent, and once in a radius rotate itself around the agent inside this radius until it was lined up with the attachment point and then float in and attach it would be a neat effect.

I have the float into radius code, but am having a bit of trouble thinking of how to rotate the object around the agent.


import bge
from mathutils import Vector


def main():


    cont = bge.logic.getCurrentController()
    own = cont.owner
    #Target object
    object = own.scene.objects['Target']
    #Desired attachment point in local space
    local = [2,-2,2]
    
    #convert local space to world space
    targetPos1 = object.worldPosition+(object.worldOrientation*Vector(local))
    
 
   #get vector to point
    v2 = own.getVectTo(targetPos1)
    


    
    if v2[0]>5:
        #float to radius
        own.applyForce(v2[1]*15*v2[0],0)
        own.applyForce([0,0,own.mass*9.8],0)
        own.worldLinearVelocity*=.9
    else:
        #hover at radius for now
        own.worldLinearVelocity*=.005
        own.worldPosition = targetPos1+v2[1]*-5
        own.applyForce((0,0,9.8*own.mass),0)
        
            
        
                
    
    
main()



any ideas?
perhaps rotate on 1 axis at a time?
starting with local Z to the target agent?

Attachments

FloatToRadius.blend (417 KB)

Transformation matrices are amazing:


difference = obj1.worldTransform.inverted() * obj2.worldTransform

Where difference is a 4x4 matrix representing the transformations that will need to be made to obj1 to move it to the location of obj2.
From there you can decompose it into a translation and a rotation:


diff_linear = difference.translation
diff_angular = difference.to_3x3().to_euler()

From there you can feed it into whatever control system you like: applying forces, lerping, anything.

That is only the start. With the help of transformation matrices, rotating around arbitary points in space is easy enough, as are many other things.
Just don’t try put numbers in them yourself. The 3x3 portion has to be homogeneous to prevent scaling.

You have to multiply in the right order too, or you get unexpected results.
I usually just try several different combinations until I find one that works. :stuck_out_tongue:

well this is what I ended up with

import bgefrom mathutils import Vector


def main():


    cont = bge.logic.getCurrentController()
    own = cont.owner
    if not own.parent:
        Target = own.scene.objects['Target']
        local = [5,5,0]
        r = Vector(local).magnitude
        
        own['Desired']=str(local)
        targetPos1 = Target.worldPosition+(Target.worldOrientation*Vector(local))
        
        
        if 'p' not in Target:
            Target['p']= own.scene.addObject('point',Target,0)
            Target['p'].worldPosition=targetPos1
            Target['p'].setParent(Target,0,1)
            
        
        
        v2 = own.getVectTo(Target)
        
        l2 = own.worldPosition - Target.worldPosition


        if v2[0]>r:
            #float to radius
            own.applyForce(v2[1]*15*v2[0],0)
            own.applyForce([0,0,own.mass*9.8],0)
            own.worldLinearVelocity*=.9
            
        else:
            
            diff  = l2 - Vector(local)
            own['Current'] =str(diff)
            rot = Target.worldOrientation.to_euler()
            vex = l2.copy()
            
            #hover at radius for now
            own.worldLinearVelocity*=.005
            own.worldPosition = Target.worldPosition+v2[1]*-r
            own.applyForce((0,0,9.8*own.mass),0)
            
            if diff.magnitude>.5:
                if diff.x>0:
                    if diff.y>0:
                        add = own.scene.addObject('point',Target,0)
                        rot.z-=.005
                        add.worldOrientation = rot
                        own.worldPosition = add.worldPosition +own.worldOrientation*Vector(vex)
                    else:
                        add = own.scene.addObject('point',Target,0)
                        rot.z+=.005
                        add.worldOrientation = rot
                        own.worldPosition = add.worldPosition +add.worldOrientation*Vector(vex)
                else:
                    if diff.y>0:
                        add = own.scene.addObject('point',Target,0)
                        rot.z-=.005
                        add.worldOrientation = rot
                        own.worldPosition = add.worldPosition +add.worldOrientation*Vector(vex)
                    else:
                        add = own.scene.addObject('point',Target,0)
                        rot.z+=.005
                        add.worldOrientation = rot
                        own.worldPosition = add.worldPosition +add.worldOrientation*Vector(vex)   
            else:
                own.worldPosition = targetPos1
                own.setParent(Target)               
                own.state =1 
                own['On']=False  
                        
        
        
    
    
    
    
    
            
        
                
    
    
main()



it almost works, but if you rotate the ‘Target’ it produces strange results

any idea what is wrong?

Attachments

FloatToRadius_And_Rotate_to_Place.blend (423 KB)

Not sure what you wanted exactly, but you could try setting a variable to check the initial position of the cube in some way, and then using that to ‘stop’ the circling.

Here’s a start: FloatToRadius_And_Rotate_to_Place.blend (482 KB)

Of course you may never exactly reach the initial position again, but you can check for a margin of error.

update- removed much complexity * 2


import bgefrom mathutils import Vector


def main():


    cont = bge.logic.getCurrentController()
    own = cont.owner
    if not own.parent:
        Target = own.scene.objects['Target']
        local = [5,5,0]
        r = Vector(local).magnitude
        
        own['Desired']=str(local)
        targetPos1 = Target.worldPosition+(Target.worldOrientation*Vector(local))
        
        
        if 'p' not in Target:
            Target['p']= own.scene.addObject('point',Target,0)
            Target['p'].worldPosition=targetPos1
            Target['p'].setParent(Target,0,1)
            
        
        
        v2 = own.getVectTo(Target)
        
        l2 = own.worldPosition - Target.worldPosition


        if v2[0]>r:
            #float to radius
            own.applyForce(v2[1]*15*v2[0],0)
            own.applyForce([0,0,own.mass*9.8],0)
            own.worldLinearVelocity*=.9
            
        else:
            
            diff  = l2 - Vector(local)
            own['Current'] =str(diff)
            rot = Target.worldOrientation.to_euler()
            vex = l2.copy()
            
            #hover at radius for now
            own.worldLinearVelocity*=.005
            own.worldPosition = Target.worldPosition+v2[1]*-r
            own.applyForce((0,0,9.8*own.mass),0)
            
            if diff.magnitude>.5:
                if diff.x>0:
                    if diff.y>0:
                        
                        rot.z-=.005
                        b = rot.to_matrix()
                        
                        own.worldPosition = Target.worldPosition +b*Vector(vex)
                    else:
                        rot.z+=.005
                        b = rot.to_matrix()
                        
                        own.worldPosition = Target.worldPosition +b*Vector(vex)
                else:        
                    if diff.y>0:
                        
                        rot.z-=.005
                        b = rot.to_matrix()
                        
                        own.worldPosition = Target.worldPosition +b*Vector(vex)
                    else:
                        rot.z+=.005
                        b = rot.to_matrix()
                        
                        own.worldPosition = Target.worldPosition +b*Vector(vex) 
            else:
                own.worldPosition = targetPos1
                own.setParent(Target)               
                own.state =1 
                own['On']=False  
                        
        
        
    
    
    
    
    
            
        
                
    
    
main()



Attachments

FloatToRadius_And_Rotate_to_Place_2.blend (423 KB)

ok, used angle2_signed and it’s almost working, but there are some issues I could use help with

sometimes it seems the angle value is inverted potentially?



import bge
from mathutils import Vector


def main():


    cont = bge.logic.getCurrentController()
    own = cont.owner
    
        
    
    Target = own.scene.objects['Target']
    
    if 'Helper' not in own:
        own['Helper'] = own.scene.addObject('Helper',Target,0)
    
    local = eval(own['local'])
    
    r = Vector(local).magnitude
    
    own['Desired']=str(local)
    targetPos1 = Target.worldPosition+(Target.worldOrientation*Vector(local))
    
    
    if 'p' not in own:
        own['p']= own.scene.addObject('point',Target,0)
        own['p'].worldPosition=targetPos1
        own['p'].setParent(Target,0,1)
        
    
    
    v2 = own.getVectTo(Target)
    
    l2 = own.worldPosition - Target.worldPosition
    D = l2.copy()
    D = D.normalized()
    D*=-r
    l2 = Target.worldOrientation.inverted()*l2
    if v2[0]>r:
        #float to radius
        own.applyForce(v2[1]*15*v2[0],0)
        own.applyForce([0,0,own.mass*9.8],0)
        own.worldLinearVelocity*=.5
        
    else:
        own.applyForce([0,0,own.mass*9.8],0)
        own.worldLinearVelocity*=0
        if 'rotMe' not in own:
            v2t = Target.getVectTo(targetPos1)
            v2o = Target.getVectTo(own)
            
            v2dt = Vector([v2t[1][0],v2t[1][1]])
            v2do = Vector([v2o[1][0],v2t[1][1]])
            
            a2d = v2dt.angle_signed(v2do, 5)
            
            print(a2d)
            own['rotMe']=a2d
            own['Helper'].alignAxisToVect([0,0,1],2,1)
            own['Helper'].alignAxisToVect(v2o[1],0,1)
            own.setParent(own['Helper'])
            
        else:
            if abs(own['rotMe'])>.005:
                if own['rotMe']>0:
                    own['Helper'].applyRotation((0,0,-.005),1)
                    own['rotMe']-=.005
                else:
                    own['Helper'].applyRotation((0,0,.005),1)
                    own['rotMe']+=.005
            else:
                own.worldPosition=targetPos1
                own.setParent(Target)        
                        
            
                    
                
                
        
        
    
    
    
    
    
            
        
                
    
    
main()



Attachments

FloatToRadius_And_Rotate_to_Place_4.blend (457 KB)

If you can avoid using euler angles, do so. Instead use either quaternions or a 3x3 orientation matrix. Euler angles suffer from gimbal lock and singularities - and are unclear about what reference frame they work in.

More specifically in this case: an angle between two vectors has no inherent direction. It is simply an angular difference. So instead, try representing the angular difference as a rotation axis. You can do this using the cross product between two vectors (either the axis’ of the objects you want to align if you want it to rotate on the spot or vectors between positions of the spot you want to rotate it around). This can then be fed straight into applyRotation.

Quick note: things like


Target.worldPosition+(Target.worldOrientation*Vector(local))

Can be replaced with:


Target.worldTransform * Vector(local)

because transformation matrices are amazing.

how does one get the angle between quats?

can you pop up an edited example?

edit: this works but I still want to know more about quat math :stuck_out_tongue:

Attachments

FloatToRadius_And_Rotate_to_Place_5.blend (457 KB)

ok now I have


import bge
from mathutils import Vector


def main():


    cont = bge.logic.getCurrentController()
    own = cont.owner
    
        
    
    Target = own.scene.objects['Target']
    
    if 'Helper' not in own:
        own['Helper'] = own.scene.addObject('Helper',Target,0)
        own['Helper2'] = own.scene.addObject('Helper',Target,0)
    local = eval(own['local'])
    
    r = Vector(local).magnitude
    
    own['Desired']=str(local)
    targetPos1 = Target.worldPosition+(Target.worldOrientation*Vector(local))
    
    
    if 'p' not in own:
        own['p']= own.scene.addObject('point',Target,0)
        own['p'].worldPosition=targetPos1
        own['p'].setParent(Target,0,1)
        
    
    
    v2 = own.getVectTo(Target)
    
    
    v2t = Target.getVectTo(targetPos1)
    
    if v2[0]>r:
        #float to radius
        own.applyForce(v2[1]*15*v2[0],0)
        own.applyForce([0,0,own.mass*9.8],0)
        own.worldLinearVelocity*=.5
        
    else:
        own.applyForce([0,0,own.mass*9.8],0)
        own.worldLinearVelocity*=0
        if 'rotMe' not in own:
            
            v2o = Target.getVectTo(own)
            own['Helper'].alignAxisToVect([0,0,1],2,1)
            own['Helper'].alignAxisToVect(v2t[1],0,1)
            own['Helper2'].alignAxisToVect([0,0,1],2,1)
            own['Helper2'].alignAxisToVect(v2o[1],0,1)
            
            v2t = own['Helper'].worldOrientation.to_quaternion()
            v20 = own['Helper2'].worldOrientation.to_quaternion()
            
            
            diff = v2t.rotation_difference(v20)
            
            print(diff)
            own['rotMe']=diff
            own['Helper'].alignAxisToVect([0,0,1],2,1)
            own['Helper'].alignAxisToVect(v2o[1],0,1)
            own.setParent(own['Helper'])
            
        else:
            #use rotMe to rotate into place? 
            pass 
                
                        
            
                    
                
                
        
        
    
    
    
    
    
            
        
                
    
    
main()




what do I do with the difference between the quats? (how do I rotate the object using it?)

edit: attached file

Attachments

FloatToRadius_And_Rotate_to_Place_6_a.blend (457 KB)


...
else:
    Target.worldOrientation = own['rotMe']
...

Is this what you want?

EDIT: Nevermind, disregard the above.

EDIT 2:
Oh, interesting. Do you already have the ‘rotate to desired point’ mechanism in place?


...
else:
    ori = Target.worldOrientation.to_quaternion().copy()
    ori.w += 0.1            
    Target.worldOrientation = ori
...

Hope this was what you were looking for. The ‘w’ value defines how much to rotate the quaternion by.

got it
I needed .slerp(other, rate)

but rotating each axis over x frames would be cool,

https://docs.blender.org/api/blender_python_api_current/mathutils.html?highlight=track#mathutils.Vector.to_track_quat
There’s a built in “track to” function. Use .to_3d() to make it compatible with a world orientation matrix.
Then you can lerp them.