So, after some mulling, I decided to use delta-values instead of actual values. I’m writing this in case anyone else wants to achieve the same visual effect, which is: rotate a node, from one h/p/r to another h/p/r and to consistently use the longest or shortest route within an interpolation between the two sets of h/p/r without suffering from the changes that come with shifting ranges when using euler angles, e.g at times, whenever getH() is invoked, it might return -160, other times, it returns 200, making it unreliable when doing interpolations.
Firstly, the neutral hpr state of the nodepath needs to be saved; this is the hpr state before any interpolation is done on the nodepath:
neutralHpr=relevantNodePath.getHpr()
Then, whenever the h or p or r is changed, maintain a set of deltas that indicates that change. There should be a set for positive changes and a set for negative changes:
positiveHprDeltas=Point3(0,0,0)
negativeHprDeltas=Point3(0,0,0)
Then, whenever you rotate left or right by say, 90 degrees, you just update the values:
#Turned right by 90:
positiveHprDeltas.x+=90
#For reliable results, always clamp the values between 0 and 359:
if(positiveHprDeltas.x>=360):
positiveHprDeltas.x=0
#Whenever you update the positive, you must updated the negative and vice versa:
negativeHprDeltas.x=360-positiveHprDeltas.x
if(negativeHprDeltas.x>=360):
negativeHprDeltas.x=0
That way, at any given time, we get how many degrees the nodepath has turned from its neutral hpr state.
When it comes to interpolation, a snapshot of these deltas will be needed at different time slots. So if you want to interpolate between only two time slots for example, then you’d need two sets of both positive and negative deltas:
#For demonstration purposes, a dummy object that stores time and delta data:
exampleKeyFrameObject=keyFrameObject()
#store the information inside it:
#get the hpr deltas at the starting point:
exampleKeyFrameObject.frameTimeOne=0.1
positiveHprDOne=Point3(0,0,0)
positiveHprDOne.x=positiveHprDeltas.x
positiveHprDOne.y=positiveHprDeltas.y
positiveHprDOne.z=positiveHprDeltas.z
negativeHprDOne=Point3(0,0,0)
negativeHprDOne.x=negativeHprDeltas.x
negativeHprDOne.y=negativeHprDeltas.y
negativeHprDOne.z=negativeHprDeltas.z
exampleKeyFrameObject.deltaDataOne=[positiveHprDOne,negativeHprDOne]
#Get a snapshot of the data at the next point, where the hpr deltas have changed:
exampleKeyFrameObject.frameTimeTwo=0.8
positiveHprDTwo=Point3(0,0,0)
positiveHprDTwo.x=positiveHprDeltas.x
positiveHprDTwo.y=positiveHprDeltas.y
positiveHprDTwo.z=positiveHprDeltas.z
negativeHprDTwo=Point3(0,0,0)
negativeHprDTwo.x=negativeHprDeltas.x
negativeHprDTwo.y=negativeHprDeltas.y
negativeHprDTwo.z=negativeHprDeltas.z
exampleKeyFrameObject.deltaDataTwo=[positiveHprDTwo,negativeHprDTwo]
So, now that the data is stored, time to move between it:
#First, set the nodePath to its neutral hpr state:
relevantNodePath.setHpr(neutralHpr)
#then, find out how much time has passed since the interpolation started:
timeElapsed=sliderValue-exampleKeyFrameObject.frameTimeOne
#the rest of the calculations:
timeBetweenFrames=exampleKeyFrameObject.frameTimeTwo-exampleKeyFrameObject.frameTimeOne
#We will need to get the difference between the deltas at the different snapshots,
#Then we pick the least difference to use for turning, two sets of differences, positive and negative:
def returnDifference(deltaOne,deltaTwo):
if(deltaTwo>=deltaOne):
diffReturn=deltaTwo-deltaOne
else:
diffReturn=(360-deltaOne)+deltaTwo
return diffReturn
diffHP=returnDifference(exampleKeyFrameObject.deltaDataOne[0].x,exampleKeyFrameObject.deltaDataTwo[0].x)
diffPP=returnDifference(exampleKeyFrameObject.deltaDataOne[0].y,exampleKeyFrameObject.deltaDataTwo[0].y)
diffRP=returnDifference(exampleKeyFrameObject.deltaDataOne[0].z,exampleKeyFrameObject.deltaDataTwo[0].z)
diffHN=returnDifference(exampleKeyFrameObject.deltaDataOne[1].x,exampleKeyFrameObject.deltaDataTwo[1].x)
diffPN=returnDifference(exampleKeyFrameObject.deltaDataOne[1].y,exampleKeyFrameObject.deltaDataTwo[1].y)
diffRN=returnDifference(exampleKeyFrameObject.deltaDataOne[1].z,exampleKeyFrameObject.deltaDataTwo[1].z)
#Now, pick the lesser delta and indicate whether it was the delta from the positive or negative value picked:
if(diffHP<diffHN):
actDH=diffHP
hT=1
else:
actDH=diffHN
hT=-1
if(diffPP<diffPN):
actDP=diffPP
pT=1
else:
actDP=diffPN
pT=-1
if(diffRP<diffRN):
actDR=diffRP
rT=1
else:
actDR=diffRN
rT=-1
#of course, if you want the long way, just flip and pick the greater delta.
#Now, we have our delta to apply, either one that results in a shorter rotation arch or a longer rotation arch, just apply it, based on the current value of the slider:
currentHDist=((timeElapsed*actDH)/timeBetweenFrames)*hT
currentPDist=((timeElapsed*actDP)/timeBetweenFrames)*pT
currentRDist=((timeElapsed*actDR)/timeBetweenFrames)*rT
#Make sure to add the delta at the first keyframe, since it's the starting point of the interpolation:
p1H=exampleKeyFrameObject.deltaDataOne[0].x
p1P=exampleKeyFrameObject.deltaDataOne[0].y
p1R=exampleKeyFrameObject.deltaDataOne[0].z
relevantNodePath.setH(relevantNodePath.getH()+p1H+currentHDist)
relevantNodePath.setP(relevantNodePath.getP()+p1P+currentPDist)
relevantNodePath.setR(relevantNodePath.getR()+p1R+currentRDist)
The slider-value referenced could just be some variable somehow provided to the interpolation method. The slider-value could be increased periodically within a task or sequence that is called at whatever rate you want it to be called. Or it could be attached to a directSlider object and manipulated by the end-user, etc.
In any case, the above is an example of how to use deltas from a neutral state to turn a nodepath and ensure it always takes either the shortest or longest rotation arch during its transform: store a neutral hpr state, maintain a positive and negative delta-variable that are updated appropriately whenever the nodepath’s hpr changes, store the values from the positive and negative delta-variables at whatever point you want involved in an interpolation, making sure to store some corresponding time value for each stored point, then, move between deltas smoothly and apply the current deltas in the manner indicated above to turn either the short way, or the long way.
I don’t know if this long post will help anyone, but I hope it does in some way. If anything I said is unclear, just tell me and I’ll try to clarify what I mean.