Possible bug realted to LerpQuatInterval

When a LerpQuatInterval is playing on an object it sometimes disappears when it is near the edge of the screen. The code I’m using moves an object between two points and performs a quick rotation to face the proper heading.

	#move object between two points
	def objMove(self, startPos, endPos, what):
		v = endPos-startPos	
		self.interval.pause() #finish caused undesirable teleport to previous endpoint
		del self.interval
		moveInt = LerpPosInterval(what, calcTime(v), endPos, startPos)
		h = calcH(v)
		q = Quat()
		q.setHpr(Point3(h, 0.0, 0.0))
		qInt = LerpQuatInterval(what, 0.2, q, blendType='easeIn')
		self.interval = Parallel(moveInt, qInt)

In the previous code calcH calculates the appropriate heading, and calcTime calculates the time it would take to travel the vector’s length at a predefined speed, both of those functions work correctly and the effect is the one I want.

At first I thought it only happened when the change in heading was 180º, but I noticed it also sometimes happens with other changes that are aproximately only 90º

Anyone have any idea what could be wrong here? Did I make some trivial mistake?

It sounds like the object’s bounding volume is not correctly defined. When this happens, the object might disappear at certain orientations, especially when it is close to the edge of the screen. It’s not related specifically to the use of LerpQuatInterval.

This happens because Panda uses the object’s reported bounding volume to determine if it is visible within the viewing frustum. If no part of the bounding volume intersects the viewing frustum, Panda assumes no part of the object is visible, and doesn’t bother to draw it. This is a very important rendering optimization, but it requires that no part of the object extends beyond its computed bounding volume.

Panda normally computes bounding volumes automatically, and normally it gets it right. But there are two known cases in which the bounding volume might be incorrect. One is when an off-center BillboardEffect has been applied; in this case, the billboarding won’t contribute to the object’s bounding volume–the bounding volume will be computed as if the object doesn’t rotate.

The other, probably more common, case is in the case of an animated character: an Actor. The bounding volume is computed for the Actor in its original pose, and does not update during the animation. If the Actor’s animation moves it substantially from its original position, it can wander out of its bounding volume.

In either of these cases, the workaround is to explicitly set an appropriately large bounding volume on the object. You can do this with something like this:

obj.node().setBounds(BoundingSphere(Point3(x, y, z), radius))

The setFinal() call is necessary to tell Panda that this artificially-imposed bounding volume should also be applied to all children of this node.

If you don’t know exactly how big the bounding sphere should be, and you just want to turn off bounding-volume culling altogether for this object (for instance, because you know it will always be in front of the camera), you can use obj.node().setBounds(OmniBoundingVolume()).

Finally, you can use obj.showBounds() to make an object’s bounding volume visible (except for the infinite OmniBoundingVolume).


Thank you very much for your response. I tried setting the bounding volume manually and that fixed it. Strangely enough, when I used showBounds() before and after doing this, the bounds look exactly the same. So there’s still some strange behaviour there in my opinion, especially since this only happened when the object was rotating which shouldn’t affect a bounding sphere.

Oh, I forgot to mention, the objects I was testing are not of the kinds you describe. I tried it with a simple non-animated sphere and it happened all the same.

Actually, rotating an object can affect a bounding sphere, if the sphere is not centered at the object’s local (0, 0, 0).

But if you’re getting this behavior with a simple static object, something is indeed wrong. Is it something you can easily write a simple one-page program to demonstrate?


Okay, here’s some code to demonstrate the issue:

import direct.directbase.DirectStart
from pandac.PandaModules import *
from direct.showbase.DirectObject import *
from direct.interval.IntervalGlobal import *
from math import acos, degrees

def calcH(v):
	v2 = Vec2(v[0], v[1])
	d = v2.dot(Vec2.unitY())
	# if it's almost normal to the vertical check with the horizontal instead
	if abs(d) < 0.1: 
		d = v2.dot(Vec2.unitX())
		return degrees(acos(d))+90
	return degrees(acos(d))+180
class Test(DirectObject):

	def __init__(self):
		base.camera.setPos(0, -50, 10)
		self.sphere = loader.loadModel("misc/sphere")		
		#without the following two lines the problem occurs
##		self.sphere.node().setBounds(BoundingSphere(Point3(0, 0, 0), 0.1))
##		self.sphere.node().setFinal(1) 
		self.interval = None
		taskMgr.add(self.move, 'move')
		self.i = 0
		n = 19; n2 = n+3
		self.pos = [(-n, 0, 0), (-n2, n, 0), (n2, n, 0), (n, 0, 0)]
	def objMove(self, startPos, endPos, what):
		v = endPos-startPos	
		if self.interval != None:
			self.interval.pause() #finish caused undesirable teleport to previous endpoint
			del self.interval
		moveInt = LerpPosInterval(what, 2, endPos, startPos)
		h = calcH(v)
		q = Quat()
		q.setHpr(Point3(h, 0.0, 0.0))
		qInt = LerpQuatInterval(what, 0.5, q)
		self.interval = Parallel(moveInt, qInt)
	def move(self, task):
		if self.interval == None or not self.interval.isPlaying():
			start = self.sphere.getPos()
			end = Point3(*self.pos[self.i])
			self.objMove(start, end, self.sphere)
			self.i = self.i%len(self.pos)
		return Task.cont

t = Test()

OK, you’re absolutely right: there’s a bug that’s (indirectly) related to the use of LerpQuatInterval. What’s actually happening is that the LerpQuatInterval is storing a non-normalized quaternion in the transform, but the low-level transform code is incorrectly assuming it will always get a normalized quaternion.

I’ll check in a fix. Thanks for finding this! In the meantime, it turns out that the minimum workaround is just to do the setFinal(1) call; changing the node’s bounding volume isn’t necessary.


Glad I could be of service. And thank you for the tip on the workaround.