Internalraycollison on my terrain mesh always has Point.Y = 0

To clarify for others, I am not using TCastleTerrain but my own terrain that is tiled with TCastleScenes that are index triangle sets.

I got tired of going under the terrain so need to gets its height to constrain movement. To get the elevation I use the code I adapted in main ‘pretty’ project which was then adapted from Wyrdforest. It figures out what tile the requested point is over. Then it casts a ray from high in the sky and checks the collisions until it finds the correct class. Then returns the point.y from that collision node.

But now with my own terrain mesh that node.point.y is always 0. I have set collides and precisecollisions to true.

What am I missing? It always works with my old TCastleTerrain based tiles.

  • Unrelated note:

    The TCastleTransform.InternalRayCollision is internal, i.e. only supposed to be used by CGE routines. It should not be used in general :slight_smile:

    From the outside, we advise to use routines like Viewport.Items.WorldXxx, like WorldRay, WorldRayCast, WorldHeight (the latter deliberately done to query height over a terrain). Or using physics with mesh colliders through Viewport.Items.PhysicsRayCast. See examples/viewport_and_scenes/collisions/ for examples.

    If one really wants to access the protected RayCollision (which is all that InternalRayCollision really does), to perform collision only paying attention to the given scene (with the understanding this will ignore some things in parents, e.g. whether parent Exists), you can define own TCastleTransform descendant that exposes it.

    That being said, I know it’s unrelated note. I can see this is justified in this case, you only want to get height looking at given scene. Our TCastleStickToSurface also uses this trick.

  • 2nd unrelated note:

    with makes this sample harder to read. It’s exactly why we recommend to not use with, see Coding Conventions | Manual | Castle Game Engine . In this case, you have the node variable which you actually use, it seems you don’t really need with collision[i] :slight_smile:

  • 3rd unrelated note:

    Note that your code in MouseWheel actually sets pos := MainCamera.Translation; (on all axes, XYZ). The TerrainHeight method takes pos, but ignores pos.Y. It would be probably cleaner if TerrainHeight method took Pos2: TVector2 as parameter.

I don’t have a “related note” that explains your problem, I’m sorry :slight_smile: I would have to have the full source code and data and experiment.

My hint is to check whether the h := node.Point.Y even executes. Maybe you see zero, because initially h := 0, and some condition along the way makes the line h := node.Point.Y not reached.

I added the node := to see the contents. The debugger didn’t let me see collision[i]… the h:= node.point.y does execute. node.point.y is always zero. I will try using the World stuff… but I don’t want it to have to deal with other objects, just the intersection with the one mesh.

Some of the oddity of my terrain height proc is it was originally the heightaboveterrain so took that Y value. It is all just prototyping to get it to work. Then I will worry about cleanliness and how things should be.

(side note. I should have made the terrain in a server long ago… I spent god knows how long waiting for terrain to generate with each test run… now it only takes long once and ever after is fast (at least for the same seed)).

I tried Viewport1.Items.WorldHeight( pos, TerrainH, tri ); instead of my terrainheight in the mousewheel handler. It is setting terrainh to 1800 which means it is finding the camera. I think I had to use the internalraycollision to avoid finding things like the camera, trees, cars and other non-terrain objects. Maybe I need to put all my tiles in their own scene and then call the WorldHeight on that? But tiles are ultimately going to contain their data, like trees, so that may not work? Also since getting terrainheight will be a very often used call, its performance matters, so we don’t want it having to sort through irrelevant ray collisions. It seems like internalraycollision worked perfectly when the tile IS a TCastleTerrain for this purpose. Here the tile is a class that hold a TCastleScene that is loaded from the indexed triangle set. Somehow that behaves differently.

Even if I change it to use Viewport1.Items.WorldRay( vector3(Pos.X, -100, Pos.Z), vector3(0,1,0)); casting from under the terrain to avoid hitting object that aren’t terrain… It find the terrainmesh, but the node.Point.Y is still zero. I have been unable to get this value from any of the ray methods. I think it is finding the bounding box despite having precisecollisions = true.

I thought the problem is that my terrain mesh didn’t have a mesh collider. My tcastleterrain version adds a body and a tcastlemeshcollider. I have collides and precisioncollisions set to true… but still no luck. node.point.y is always zero.

Using the PhysicsRayCast works now that I have a mesh collider. I don’t know why none of the other raycasts can provide point info from my mesh. But this one, being record based is simpler anyways. I do still have to give it the root items which seems suboptimal. There must be a simpler way to find the intersection of ray with just the one mesh. But I am not blocked anymore.

Now I can’t go underground with the mouse wheel. Looks cool at ground level.

Dang, adding the mesh collider kills (doubles) memory usage. I think I will have to make my own function to go through the mesh at the triangle level and find the intersect point.

Oddly, if PreciseCollisions is set to true on my terrain mesh then it renders with no shading from the light, as you see in the above screen shot. If it is false then I get terrain shading (phong?). This setting has no effect on the physics ray cast and I had set it to try to get the casts to work. So just leave it default.


I feel like I know this valley so well, having spent over a year in it haha.

I can comment on some of the things above, though ultimately I would need a testcase (code + data) to reproduce the problem to really solve it :slight_smile:

Some comments:

  • When not using physics (like PhysicsRayCast), the PreciseCollisions matters, and the existence of TCastleMeshCollider doesn’t matter. See Physics | Manual | Castle Game Engine .

  • When using physics (like PhysicsRayCast), the PreciseCollisions doesn’t matter, and the existence of TCastleMeshCollider matters. Also see Physics | Manual | Castle Game Engine :slight_smile: Yes, memory may grow because of the need to create the structure for physics engine.

  • Yes, rolling “your own” collision algorithm for querying the terrain height, given XZ, is most optimal and advised. If you know the data, you can implement this algorithm in constant time. It’s always going to be much faster than any “general” algorithm dealing with a triangle set, which is what CGE does (with physics or not). And if you have your own algorithm in your own method, you can also call it explicitly yourself.

  • To avoid hitting other objects,

    • when doing collision query using WorldXxx (without physics), you can toggle other objects Exists temporarily to false.
    • when doing collision query using physics (PhysicsRayCast), use physics layers to check only the layers you want. You can put the terrain(s) in a separate layer from everything else. Se “layers” section on Physics | Manual | Castle Game Engine . TODO: argh, I see I didn’t merge this improvement to master yet. So this is not true yet… I will merge it today. Solved, PhysicsRayCast has const CollisionLayers: TPhysicsLayers = AllLayers param.
    • note that collision queries never hit the camera, as the camera doesn’t have any geometry. You don’t need to worry about hitting the camera.

PreciseColisions should really have no effect at all on the rendering. If you experience otherwise, please submit a bug with a testcase.

1 Like

This TODO has been closed now. PhysicsRayCast, in CGE master, takes as parameters const CollisionLayers: TPhysicsLayers = AllLayers. To check only with terrain, you would pass only one layer, the one with terrain.

1 Like

I will try to get a cleaned up trimmed version on my public git. I intended to share the terrain client server code when it was more finalized. It still uses folders from my main project, so I need to tighten that up so you don’t need 3 repos to run.

I made a function that does what I want. It is unoptimized in that it brute force checks all the triangles in the mesh. It could hone in on the one triangle since the triangles in strip order so I could compute which triangle to check and save a lot of misses. But this works for now and is fast enough for mouse wheel and navigation…

function TAbstractMesh.ElevationAtPos( Pos : TVector2;
                                       var Elev : single ) : boolean;
 var IndexedTriangleSetNode : TIndexedTriangleSetNode;
     ix : integer;
     p1, p2, p3 : tvector3;
     intersection : TVector3;
 const zero : single = 0.01;
 begin
   elev := 0;
   result := false;
   IndexedTriangleSetNode := TIndexedTriangleSetNode( rootnode.FindNode( TIndexedTriangleSetNode, false ));
   ix := 0;
   while ix < IndexedTriangleSetNode.FdIndex.Count do
    begin
      p1 := CoordinateNode.FdPoint.Items[IndexedTriangleSetNode.FdIndex.Items[ix]];
      inc( ix );
      p2 := CoordinateNode.FdPoint.Items[IndexedTriangleSetNode.FdIndex.Items[ix]];
      inc( ix );
      p3 := CoordinateNode.FdPoint.Items[IndexedTriangleSetNode.FdIndex.Items[ix]];
      inc( ix );

      result := (abs( p1.x - pos.x )<=zero) and (abs( p1.z -pos.y )<=zero);
      if result then
       begin
         elev := p1.y;
         exit;
       end;
      result := (abs( p2.x - pos.x )<=zero) and (abs( p2.z -pos.y )<=zero);
      if result then
       begin
         elev := p2.y;
         exit;
       end;
      result := (abs( p3.x - pos.x )<=zero) and (abs( p3.z -pos.y )<=zero);
      if result then
       begin
         elev := p3.y;
         exit;
       end;
      result := TryTriangleRayCollision( Intersection,
                                         Triangle3( p1, p2, p3 ),
                                         TrianglePlane( p1,p2,p3),
                                         vector3( pos.x, 100, pos.y ), vector3( 0, -1, 0 ));
      if result then
       begin
         elev := intersection.y;
         exit;
       end
    end;

 end;