Hello, nice to start coding with panda3d!
I set up a GeoMipTerrain and want to place an object onto the terrain by clicking at a random position on the screen/terrain. I read that one should use terrain.getElevation(x,y). So far so good. But how to I get x,y if the collision ray does not work?
Best, Werner
Hmm… If you don’t mind using the in-development 1.11 version, I gather that there’s a collision-shape for heightfields there–although I don’t know how complete or stable that feature is, offhand.
Otherwise… perhaps you could access the depth-buffer and use the relevant lens-based methods to convert that into position? Or maybe have a single-pixel offscreen camera that has your heightmap render its x- and y- coordinates as colours (scaled to fit the range (0 … 1), which you could then access as called for?
Thanks for the suggestions. I will try them out tomorrow.
I’m pretty sure you’re being offered this route to nowhere. And if you need to move a character or get a normal vector, and so on…
from direct.showbase.ShowBase import ShowBase
from panda3d.core import (PNMImage, GeoMipTerrain, StackedPerlinNoise2, CollisionTraverser,
CollisionHandlerQueue, CollisionNode, NodePath, BitMask32,
CollisionRay)
class TestGeoMipTerrain(ShowBase):
def __init__(self):
ShowBase.__init__(self)
perlin = StackedPerlinNoise2(0.4, 0.5)
noise = PNMImage(1025, 1025)
noise.perlin_noise_fill(perlin)
self.terrain = GeoMipTerrain("DynamicTerrain")
self.terrain.set_heightfield(noise)
self.terrain.set_block_size(32)
self.terrain.set_near(40)
self.terrain.set_far(100)
self.terrain.set_focal_point(base.camera)
root = self.terrain.get_root()
root.reparent_to(render)
root.set_sz(100)
self.terrain.generate()
self.terrain.get_root().set_collide_mask(BitMask32.bit(1))
self.coll_trav = CollisionTraverser()
self.coll_trav.show_collisions(render)
self.coll_queue = CollisionHandlerQueue()
self.coll_ray = CollisionRay()
coll_ray_node = CollisionNode('Ray')
coll_ray_node.addSolid(self.coll_ray)
coll_ray_node.set_from_collide_mask(BitMask32.bit(1))
coll_ray_node.set_into_collide_mask(BitMask32.all_off())
node_path_ray = NodePath(coll_ray_node)
node_path_ray.reparent_to(base.camera)
self.coll_trav.add_collider(node_path_ray, self.coll_queue)
base.accept('mouse1', self.pick)
taskMgr.add(self.terrain_update, "update")
def pick(self):
if base.mouseWatcherNode.has_mouse():
mpos = base.mouseWatcherNode.get_mouse()
self.coll_ray.set_from_lens(base.camNode, mpos.get_x(), mpos.get_y())
self.coll_trav.traverse(render)
self.coll_queue.sort_entries()
if (self.coll_queue.get_num_entries() > 0):
info = self.coll_queue.get_entry(0)
print (info.get_surface_point(render))
def terrain_update(self, task):
self.terrain.update()
return task.cont
game = TestGeoMipTerrain()
game.run()
You can certainly do that–but do so with caution, as visible geometry isn’t set up for efficient collision. (Which is why I avoided suggesting it.)
In some programs, it may perform “well enough”, while in others it may be a performance issue.
This old thread might be worth looking at.
I think the concept of visible geometry is not interpreted correctly. The visible geometry is just a set of data. From this data (the points of the triangles), the solid is created and added to the collision node.
The problem is that the geometry that is being rendered and the geometry from which the collision geometry is being created are not necessarily the same thing. This misconception is common in the Panda3D engine.
Because the artist has to create geometry for visualization and geometry for collision. These are different concepts, but by some miracle in this community it is mixed into a general concept.
Thank you for the assortment of quick answers. In my game the performance of this check does not matter (at least currently), since I want to use it to place the next goal onto the terrain towards which the NPCs shall walk. The code from serega works out-of-the-box, many thanks for that! I am currently merging it with my existing code that detects objects. In case performance will become an issue I will dive deeper into this subject.
Again: thanks a lot for your help!
Looking ahead, I think for optimization, you need to get a chunk of 2d coordinates, then go through the geom node and extract the mesh data, then manually create collision triangles and add them to the collision node.
Judging by the GeoMipTerrain API 1, 2, this is provided for and it is the most correct strategy.
The most difficult thing is to set up the conditions for landscape regeneration correctly, since this is the most demanding part, given that graphics cards do a good job with triangles, it will be better to use statistics. But it depends on the meaning of the gameplay and so on.
Just to clarify – say you have a set of realistic stairs with their “visible geometry” showing every step. Obviously, just producing a trimesh directly from that visible data would be much less performant than placing a single rectangle/tri on the slope of the visible stairs for the collision check routine.
But I don’t think anybody has suggested that isn’t the case. From the discussions I’ve seen here, we’re usually pretty straightforward about trimeshes from visual geometry being for convenience, not performance.
This is not always explicitly stated. Thaumaturge explicitly states that the visible geometry is not designed for effective collision. At the same time, he does not disclose the meaning of his statement.
It is worth noting that the deep meaning also lies in the fact that in the case of geometry you will use triangles. At the same time, panda can use polygons for collisions, but this is difficult when you need to use data obtained from geometry. And it’s almost impossible in the context of such, except when you have an exporter that allows you to transfer 4 points so that you can generate a collision polygon.
In the case of GeoMipTerrain, you are already limited to triangles and there is no other way.
Ah, fair enough! In that case, colliding with visible geometry may well be a good choice for you! ![]()
Sure, but how that data is handled internally can affect its performance for a given task–and the requirements of collision are not the same as those of visual rendering.
No, I think that that’s well-known–and certainly isn’t what I’m referring to.
(It certainly doesn’t help, of course–but is again a different matter.)
I mean, we’ve had this argument, you and I, multiple times before. I don’t really want to get too far into it yet again.
Simply put, the internal structures used for visible geometry are often not the same as those used for collision geometry. The former are less efficient than the latter for collision.
As such, colliding with visible geometry is–in generally–less efficient than colliding with dedicated collision geometry (even when comparing visible geometry with matching trimesh collision geometry).
See the following manual page for more: (The link should go directly to the section on “Colliding with Visible Geometry”)
I still don’t understand what the meaning of this is in the context of GeoMipTerrain.
Do you propose to create collision polygons or?
Well, as I said above, there are other approaches to getting the coordinate at which the user clicked. For example, one can use a single-pixel camera and a shader that renders worldspace coordinates, or work backwards from the depth-buffer.
Plus, there’s apparently a dedicated heightfield-based collision-shape coming in (I gather) 1.11. That should make the whole thing a lot easier when it comes–or if one is willing to use an in-development version of the engine.
This idea is questionable, because in the end you will get a coordinate from the world that has nothing to do with the NodePath itself. To get a link to the NodePath, you need to come up with something again.
However, this has the potential if you render the blocks of the terrain in the second camera with the setting of a personal color. That is, implement a color id buffer, store references to the NodePath in a dictionary with a color key. However… changes are needed to the GeoMipTerrain API.
I doubt that productivity will increase from this, you not deprived of the opportunity to go through .get_root() for all GeomNode and create a CollisionNode based on their triangles.
Sure, but if all you want is to click on terrain, you likely don’t need a link to the NodePath–just the location of the click.
This could certainly be done, indeed!
But I don’t think that it requires changes to GeoMipTerrain–just a shader applied to it that renders the appropriate colour-data, and perhaps a dictionary in code that associates colour-IDs with NodePaths.
Sure, but it’s a bunch of extra work on the side of the developers–and there may be developers who aren’t comfortable with constructing their own geometry in that way.
It would be a convenience at the least, I think: a quick way to get good collision geometry for a heightfield.