Updating LineSegs

Hello everyone.

For an application I am creating, I need to update the origin and direction of multiple LineSegs in real time.

To do this, every single frame, I call reset() on my LineSegs(), draw my new lines, remove the old node and then create() and parent my new node.

Here’s that procedure in some “pseudo-code” demonstration:

# At "__init__". 
self.line_segs = LineSegs()


# In the task: 
# During the start:

# Imagine that there is some loop there that draws the lines.

# Before returning task.again
self.line_segs_node = self.line_segs.create()
self.line_segs_node_path = NodePath(self.line_segs_node)

While this approach is not bad, it starts affecting performance after 200 lines. I was wondering if there is a more efficient approach to this, perhaps updating the origin and direction of the lines without having to remove anything.

Thanks in advance,

Hmm… Perhaps a CompassEffect might help? Depending on what, specifically, you want the lines to point towards, that might provide the updating of their orientations. The origins might be controlled by either making them children of relevant nodes, or having CompassEffect control position as well as orientation.

Basic information on CompassEffects (including at least one caveat) should be available in the manual.

More detailed information should be available in the API.

The compass effect idea does sound interesting, but I do not think it can be used for what I am trying to accomplish. I have several moving objects, and I need to draw lines between them and a source point (acting as the origin), but the source point isn’t actually a singular fixed point but more like a circle with its center at that origin, and lines are drawn from a point in the circumference to the moving targets.

However after more testing, it seems that the bottleneck in my application is not drawing all these lines but a collision traverser that carries collision checks for them. Looking at my CPU usage, it is fairly low (~30%) and I am using the multi-threaded Panda model as in:

loadPrcFileData("", "threading-model /Draw")

However as expected, the ~30% CPU usage does in fact fully occupy one of my cores. Therefore while totally irrelevant to the initial question posted, is there a way to make more use out of my other CPU cores when traversing for collisions?

Hmm… If you’re not doing so already, I suppose that you could try Bullet, to see whether it performs better than the built-in collision-system. Otherwise, perhaps you could stagger collision-checks: perform fifty this frame, fifty the next, and so on. It would mean having some of your data be out-of-date, but only by a few frames.

[edit] I’m afraid that I don’t know how to better utilise your cores, offhand; perhaps someone else could weigh in on that!

I haven’t tried Bullet, but I do not think it will result in a significant improvement. Essentially if I am not mistaken Bullet has no configuration method to make collision traversing run among multiple cores. If I am also not mistaken Panda’s collision system is well optimized for ray to box collision testing which is what I am using. Essentially, since every “From” object " is tested for collisions with all other objects in the world, it should be possible to carry these tests for different “From” objects in parallel.

Perhaps (unless I haven’t found it already?) this doesn’t exist because most games may have few “From” objects… But if they don’t, which could be likely in projects of higher complexity or unusual needs like mine, traversing in parallel will be a useful feature to have… I would try to perhaps modify the collision system myself to meet that need, but my experience with C++ is rather limited.

If anyone with more experience is willing to give this idea a review (maybe even implement it onto the engine as an optional config value?), or let me know of anything wrong with any of the assumptions I am making with this post, I will greatly appreciate it!

As for updating lines, you don’t have to use LineSegs, but you could directly manipulate a low-level GeomVertexData structure using a GeomVertexWriter (or a memoryview). If you are careful to do anything slow (like anything that requires a reallocation), you could update the position of existing vertices quite fast.

Yes, that is a great idea! From some tests I’ve carried, such approach seems to scale better than using LineSegs, however I quite like LineSegs because I can have it anti-aliased without implementing multi-sampling for the entire scene. But this will certainly be an option to look into if LineSegs scale to be an issue.

Actually, I looked at the LineSegs source code (which is by the way just a thin wrapper around the GeomVertex* interfaces) and it looks like you can move existing vertices using segs.setVertex(n, (x, y, z)), without having to call reset or reparent the node. This should be pretty fast.

That’s great, but would I be able to use this to change both the origin and direction? Also depending on the test I carry, I then hide some of the lines. Since I am creating my LineSegs node every frame, I simply do not draw such lines that fail the test. Is there a way to do this using setVertex or any of the other methods?

You set the individual vertices, so you change the direction by moving the second vertex of each line segment to the position of the first vertex plus the direction.

To hide a line, you could set both vertices to the same point.

That’s good to know, I will certainly try it! Thanks a lot.

An update to setting the vertices, I’ve tried it and it works great! The color of individual vertices can also change using segs.setVertexColor(n (r,g,b,a)). For anyone in the future trying to use this, it is also useful to note that you may need to set the bounding volume to OmniBoundingVolume.

Hmm, fair enough, then. (I don’t know offhand whether the above is true, but I’ll take it as given for the moment.)

In that case, what about my suggestion of staggering physics updates? Of updating the first X objects this frame, the next X objects next frame, etc.? If your frame-rate is sufficiently high, and you don’t require accuracy on the scale of a single frame, that might provide an improvement in performance.

That does sound interesting, although there is one thing to consider. Right now I am assigning my CollisionRays to cTrav, which I have no control over how often it updates (but by default at least it updates every frame). If for example I wanted to check for collisions once every two frames, would that mean creating my own traverser and using a task that calls traverse() in some alternating format? Such that it traverses every half of the objects per frame?

EDIT: Now that I am thinking about it, that seems very clever! I can check for collisions less often but update the visuals every frame, making it pretty much identical but with better performance.

Using multiple traversers seems like the obvious way to me, indeed. (It’s probably possible to do it with just cTrav–but that sounds convoluted to me, and I’m not confident of the performance.)

Maybe something like this: (Modified as called for to your specific project.)

def addCollider(collider, handler):
    self.colliderAdditionCounter = (self.colliderAdditionCounter + 1) % self.numTraversers
    self.traverserList[self.colliderAdditionCounter].addCollider(collider, handler)

def update(self, task):
    self.colliderUpdateCounter = (self.colliderUpdateCounter + 1) % self.numTraversers
    # etc...

(I’m using two separate counters there, but that may be superfluous.)

Thanks a lot for the idea, I will definitely try this.

Not a problem–it’s my pleasure, and I hope that it helps! :slight_smile:

Also, is there a performance disadvantage with creating multiple traversers instead of using the same traverser for every “From” object? I am asking this because if I create a unique traverser for every “From” object in my application I can then have some priority list which would mean doing a lot less collision checking.

Hmm… That I don’t know, I’m afraid. Intuitively, I would expect there to be overhead for having a traverser–but I wouldn’t rely on that intuition. I’d say to either wait for someone more knowledgeable on the matter to respond, or to experiment and see what results you get!

Experiments it is then! I will publish the outcome here.