Actor image stuttering when followed by camera

[EDIT] The original main question asked in this post has been answered. I’ve replaced it with two new questions, which can be found in the latest post.

Hi, I made a little test program where the camera follows an actor or possibly a 2D actor (just a sprite printed on a billboard card) around while the actor moves along a set interval. The camera moves smoothly, but the actor is a little glitchy visually. I was wondering if anyone could shed light on if or how it’s possible to fix it.

[SOLVED] Basically, once in a while (say, about every 3 seconds) the image of the moving actor kind of stutters left or right a little, like it’s being drawn in a slightly incorrect place for a split moment before it corrects. This happens even when I’m using a 3D model, although it’s more noticeable with a 2D one. Is there any particular reason I’m getting that graphical glitch? I’m using an i5-2500k CPU with a solid, recent model of video card, so presumably it’s not an issue of processing power.

Also, when I was using a specific test image (it’s Google’s first hit for Mario), the top of Mario’s mustache against the rest of his body gave an excellent example of that awkward, “swimming pixels” redrawing effect you see where a 3D object is moving away against a backdrop without anti-aliasing… except that it’s happening here on a 2D sprite. I’m assuming this has to do with the yresolution=600 in LoadImageAsPlane, but I can’t find a better way to scale down the texture, and in fact I can’t figure out why it starts out so big to begin with.

Sample program:

import sys, os
from direct.showbase.ShowBase import ShowBase
from direct.task import Task
from direct.actor.Actor import Actor
from direct.interval.IntervalGlobal import Sequence
from panda3d.core import Point3, NodePath, CardMaker, TransparencyAttrib, Texture, Vec4, Filename

GFX_PATH = Filename.fromOsSpecific(os.path.dirname(sys.argv[0]) + '/')

def LoadImageAsPlane(filepath, yresolution = 600):
	"""Currently expects to be given a pre-padded sprite that has power-of-two dimensions, although the Mario sprite mentioned works fine. Copied from existing Panda3D snippet by preusser."""
	tex = loader.loadTexture(filepath)
	tex.setBorderColor(Vec4(0,0,0,0))
	tex.setWrapU(Texture.WMBorderColor)
	tex.setWrapV(Texture.WMBorderColor)
	cm = CardMaker(str(filepath) + ' card')
	cm.setFrame(-tex.getOrigFileXSize(), tex.getOrigFileXSize(), -tex.getOrigFileYSize(), tex.getOrigFileYSize())
	card = NodePath(cm.generate())
	card.setTexture(tex)
	card.setScale(card.getScale()/ yresolution)
	card.flattenLight()
	return card

class MyApp(ShowBase):
	def __init__(self):
		ShowBase.__init__(self)
		
		base.disableMouse()
		
		self.environ = self.loader.loadModel("models/environment")
		self.environ.reparentTo(self.render)
		self.environ.setScale(0.25, 0.25, 0.25)
		self.environ.setPos(-8, 42, 0)
		
		self.camera.setPos(14, 12, 8)
		
		self.taskMgr.add(self.LookAtActorTask, 'LookAtActorTask')
		
		# Comment out one or the other to test.
		#self.Use3dActor()
		self.Use2dActor()
		
		self.MakeActorMove()
	
	def Use3dActor(self):
		self.actor = Actor('panda-model')
		self.actorZ = 3
		#self.actor.setPos(0, 0, self.actorZ)
		self.actor.setScale(.01, .01, .01)
		self.actor.reparentTo(self.render)
	
	def Use2dActor(self):
		self.actor = LoadImageAsPlane(GFX_PATH + 'sprite.png')
		self.actor.setTransparency(TransparencyAttrib.MAlpha)
		self.actor.setBillboardAxis()
		#self.actor.setBillboardPointWorld()
		self.actorZ = 3.6
		#self.actor.setPos(0, 0, self.actorZ)
		self.actor.reparentTo(self.render)
	
	def MakeActorMove(self):
		actorPosInterval1 = self.actor.posInterval(6,
												   Point3(0, -8, self.actorZ),
												   startPos=Point3(0, 8, self.actorZ))
		actorPosInterval2 = self.actor.posInterval(6,
												   Point3(0, 8, self.actorZ),
												   startPos=Point3(0, -8, self.actorZ))
		
		self.actorPace = Sequence(actorPosInterval1, actorPosInterval2, name='ActorPace')
		self.actorPace.loop()
	
	def LookAtActorTask(self, task):
		self.camera.lookAt(self.actor)
		return Task.cont

app = MyApp()
app.run()

I remember having an issue like this a long time ago. I think it has to do with your camera task and the collision/character movement task running in wrong order. Use the “sort” keyword in your task constructors. Make the camera task run after the movement/collision tasks.
panda3d.org/manual/index.php/Tasks

Thanks a lot, this seems to solve my main issue. I put the new code below for anyone else who stumbles on this problem. I have two other questions, which maybe should go in a new topic, but I’ll try them here first:

(1) When the camera is following an actor, it seems like Panda3D skips a frame once in a while, period. It’s noticeable with the environmental scenery, so it’s not an actor-related issue. It isn’t too noticeable and it only happens once in a while, but is there anything I can do to get a less-skippy frame rate when I have an actor-following camera?

(2) I’m assuming that the “swimming pixels” glitch with Mario’s mustache that I mentioned earlier is happening because for some reason Panda3D creates the texture at a massive size and then scales it down (to a 600th the size, in fact; see LoadImageAsPlane) once each frame, resulting in small frame-by-frame differences, particularly noticeable in high-contrast areas like the mustache. Or maybe there’s another explanation? In any case, I don’t know why the .png texture is being loaded at such a large size, but is there a way to scale down the texture before applying it to the card? I’m assuming that would be one way to solve this.

[i]For anyone who has this problem and wants to know the change I made:
I set the task manager call in init to this:

self.taskMgr.add(self.LookAtActorTask, 'LookAtActorTask', sort=2)

and changed the contents of two of the functions as follows:[/i]

	def MakeActorMove(self):
		actorPosInterval1 = self.actor.posInterval(6,
												   Point3(0, -8, self.actorZ),
												   startPos=Point3(0, 8, self.actorZ))
		actorPosInterval2 = self.actor.posInterval(6,
												   Point3(0, 8, self.actorZ),
												   startPos=Point3(0, -8, self.actorZ))
		
		self.actorPace = Sequence(actorPosInterval1, actorPosInterval2, name='ActorPace')
		self.actorPace.start()
		self.actorPace.finish()
		self.taskMgr.add(self.MoveActorTask, 'MoveActorTask', sort=1)
	
	def MoveActorTask(self, task):
		self.actorPace.stepPlay()
		return Task.cont

I couldn’t get Sequence.setupPlay(0.0, -1.0, 1.0, False) to set up the interval properly, but I call it indirectly by calling Sequence.start() and immediately Sequence.finish(). Maybe there’s a better way.

Sure, use framerate-independant movement

NodePath.setPos(pos * globalClock.getDt())

Unlikely. flattenLight() bakes the scale.

A picture would help.
Could be z-fighting going on.

(1) Ah, good point about frame-independent movement. Do you know if there’s a way to make that work with the interval system? Although I guess I could use some basic trigonometry with my existing Task system to move between two 3D points by variable distances per frame by frame using getDt for timing.

Although to be honest, even when I have the camera spinning around in space using this:

from math import pi, sin, cos
	def SpinCameraTask(self, task):
		angleDegrees = task.time * 12.0
		angleRadians = angleDegrees * (pi / 180.0)
		self.camera.setPos(20 * sin(angleRadians), -20.0 * cos(angleRadians), 3)
		self.camera.setHpr(angleDegrees, 0, 0)

Even at a steady 60.0 FPS, the camera spinning around in the default “models/environment” environment, with the actor removed, looks like it’s skipping or mis-timing frames once in a while. It just looks like the camera jerks a little once in a while, momentarily. I’m not really sure how to deal with this since the FPS is indeed stable, and it’s not even actor-related.

(2) I uploaded a video, but Youtube lowers the quality so much that it’s a bit hard to tell that aside from the mustache the sprite looks basically solid, and the black pixels at the top of the mustache are wavering wildly as the card moves along. (A single picture wouldn’t help since it’s the slight differences in how the sprite appears from frame to frame that make it look glitchy.)

Intervals take seconds as time parameter, not frames, so I don’t think there should be a problem.

I’m not sure, could be that the task panda created for the billboard to face the camera is in wrong order to your camera positioning task.

Looks like aliasing to me.
panda3d.org/manual/index.php/Antialiasing

I moved the code that calls the interval into a frame-based task, though, and called it using stepPlay() rather than letting it behave naturally, in order to solve my original stuttering problem (see my 2nd post for the code). If I let the interval do its natural thing, the actor seems to stutter.

Sorry, let me rephrase: I actually meant that it feels like Panda3D is skipping a frame once in a while even with a camera spinning around an actor-less, billboard-less environment. I’m not sure why it looks jumpy given that Panda3D claims it has a constant 60.0 FPS.

Yeah, enabling multisampling helps; I guess I just didn’t expect it on a flat card.

Don’t know why you’re seeing that.

Do you have v-sync enabled?
In my own experience the fps meter is never 100% accurate.
Maybe try running pstats profiler and see what is shown there instead.

From the video it seems like one of those “vegetation style” billboards which only rotates in the Z axis, not the rest. So it has some angle from the camera’s view, not looking perfectly at the camera, and then aliasing is expected.