Fixed Depth Object

I am trying to make an object not change size regardless of how relatively close the camera is located. I can get this to work with the billboard effect, however I need it to not rotate and it also must be clickable which is a bit of an issue with billboards if I am not mistaken. I tried using the compass effect too but with no success.

I think that you might be able to do this by scaling it (in a task, perhaps) by the distance to the camera, and perhaps a constant that affects its final size.

A quick example program:

Press “w” and “s” to move forwards and backwards. You should see the central panda remain at a fixed size; the pandas at the side are there so that your movement is visible.

from direct.task import Task

from direct.showbase import ShowBase as showBase

class Game(showBase.ShowBase):

    def __init__(self):
        showBase.ShowBase.__init__(self)

        self.accept("escape", self.userExit)

        self.model = loader.loadModel("panda")
        self.model.reparentTo(render)
        self.model.setY(20)

        for i in range(20):
            sideCube = loader.loadModel("panda")
            sideCube.reparentTo(render)
            sideCube.setPos(3, (i - 10)*5, -0.5)
            sideCube.setScale(0.1)

        self.updateTask = taskMgr.add(self.update, "update")

        self.accept("w", self.setKey, extraArgs = ["forwards", True])
        self.accept("w-up", self.setKey, extraArgs = ["forwards", False])
        self.accept("s", self.setKey, extraArgs = ["backwards", True])
        self.accept("s-up", self.setKey, extraArgs = ["backwards", False])

        self.keys = {
            "forwards" : False,
            "backwards" : False
        }

    def setKey(self, key, state):
        self.keys[key] = state

    def update(self, task):

        dt = globalClock.getDt()

        if self.keys["forwards"]:
            self.cam.setY(self.cam, 3.0*dt)
        if self.keys["backwards"]:
            self.cam.setY(self.cam, -3.0*dt)

        diff = self.model.getPos() - self.cam.getPos()
        dist = diff.length()

        self.model.setScale(0.005*dist)

        return Task.cont

app = Game()
app.run()

I see, I was hoping there was a quicker way of doing this, but a task will work just as well.

The manual and API seem to claim that the compass can do such thing, and while I can in fact use it to maintain a fixed rotation and position to the camera, when it comes to scale it does not work. But regardless, I am not even sure if I’d be able to click the object once it is under the compass effect, and if so, it is of no use to me.

Nonetheless, thank you for taking the time to reply and even provide me with an example.

It’s my pleasure; I hope that it helps! :slight_smile:

A compass-effect will, if I’m not much mistaken, cause the object to maintain a fixed scale relative to the camera-node–but that’s not actually the effect that you’re asking for. You’re asking for a fixed size on the screen, which isn’t the same thing.

Roughly-speaking, if you were to print out the camera’s scale (something like “print (base.cam.getScale())” that is the scale that the compass-effect would give to your object. Since the scale of the camera-node doesn’t vary with the objects that it views, and since you (presumably) don’t change the camera-node’s scale yourself, the compass-effect would presumably cause the object to have a static scale, regardless of distance from the camera.

(I might be slightly off in some of the particulars there; for one, I’m not sure of whether the scales of parent-objects affect a compass-effect. But I think that the basic idea is correct.)

Oh, that makes so much sense now, thanks a lot.

Not a problem; it’s my pleasure. :slight_smile:

This is how I combine a billboard effect and a compass effect to make an object’s screen size constant, without affecting its apparent position and orientation relative to the world:

from panda3d.core import *
from direct.showbase.ShowBase import ShowBase

class MyApp(ShowBase):

    def __init__(self):

        ShowBase.__init__(self)

        # Setup environment

        self.scene = self.loader.load_model("environment")
        self.scene.reparent_to(self.render)
        self.scene.set_scale(.25)
        self.scene.set_pos(-8., 42., 0.)

        # Load model and make its screen size constant, using both a billboard
        # and compass effect

        # create a target node to serve as a "real-world proxy" for the model
        target = self.render.attach_new_node("target")
        target.set_pos(5., 7., 3.)
        target.set_hpr(30., 20., 10.)
        # create a pivot node that will be directly affected by the billboard
        # effect, instead of the model, as the latter will already be affected
        # by the compass effect
        pivot = self.cam.attach_new_node("pivot")
        pivot.set_billboard_point_world(target, 2.)
        pivot.node().set_bounds(OmniBoundingVolume())
        pivot.node().set_final(True)
        # load the model and set the compass effect on it
        smiley = self.loader.load_model("smiley")
        smiley.set_scale(.1)
        smiley.reparent_to(pivot)
        smiley.set_compass(target)

app = MyApp()
app.run()

So you need to create two additional nodes to make this work:

  • a target node: this is a kind of “real-world proxy”, whose position and orientation is used for the object by both effects;
  • a pivot node: this is a node attached to the camera, and the object itself has to be parented to it; it is also on this node that the billboard effect needs to be set instead of on the object.

The compass effect is then set on the object.
It is also necessary to give the pivot node very large bounds to prevent the object from being culled, since both effects can easily take the object out of its own bounds.

Now whenever you need to change position or orientation of the object, make those changes on the target node and it should work as expected.

That is very clever, thanks a lot, especially for even providing an example.