Calculate Terrain Height

Hi,

I try to get the height for a point in TCastleTerrainImage.

With TCastleTerrainNoise it works:
tnh:=TerrainNoise1.Height(vector2(SceneDSTree.Translation.X,SceneDSTree.Translation.Z),vector2(SceneDSTree.Translation.X,SceneDSTree.Translation.Z));

With TCastleTerrainImage the result is always 0 at every point (x,z) I try.

Maybe someone can help.

Regards
Didi

Hm, it has to work:) That is: the engine internally also uses TCastleTerrainImage.Height to make terrain with data from TCastleTerrainImage work. I just opened in editor examples/terrain/data/additional_terrain_tests.castle-user-interface that confirms data from image works.

Some possible hints:

  • Are you sure you set TCastleTerrainImage.Image before? :slight_smile:

  • What are your TCastleTerrainImage.MinLevel, TCastleTerrainImage.MaxLevel?

  • The function TCastleTerrainImage.Height(const Coord, TexCoord: TVector2): Single; call uses only the 2nd argument, TexCoord. It is assumed to be in 0…1 range if you want to query the whole image. Your line of code possibly uses too big coordinates, try as 2nd argument to pass something like

    Vector2(
      SceneDSTree.Translation.X / TerrainSizeX, 
      SceneDSTree.Translation.Z / TerrainSizeZ
    )
    

See TCastleTerrainImage.Height implementation, it is rather simple :slight_smile:

function TCastleTerrainImage.Height(const Coord, TexCoord: TVector2): Single;
var
  PX, PY: Integer;
  Intensity: Single;
begin
  if FImage <> nil then
  begin
    PX := Floor(TexCoord.X * FImage.Width );
    PY := Floor(TexCoord.Y * FImage.Height);
    ClampVar(PX, 0, FImage.Width  - 1);
    ClampVar(PY, 0, FImage.Height - 1);
    Intensity := MapRangeTo01(FImage.PixelPtr(PX, PY)^, 0, High(Byte));
  end else
    Intensity := 0.5;
  Result := Lerp(Intensity, MinLevel, MaxLevel);
end;

If this doesn’t help, please submit a complete testcase to reproduce the problem.

1 Like

OK, it works. The values has to be between 0 and 1. So the values of the translation has to be converted.

In my case:

tx := (SceneAvatar.Translation.X + Terrain6.Size.X / 2) / Terrain6.Size.X;
tz := (SceneAvatar.Translation.Z + Terrain6.Size.Y / 2) / Terrain6.Size.Y;
th := TerrainImage6.Height(vector2(tx,tz),vector2(tx,tz));
LabelFps.Caption := 'FPS: ’ + Container.Fps.ToString + ’ Height: ’ + th.ToString;

I can see now that the height value (th) is not 100% correct.
Maybe the height value represents the pixel color and the height for the avatar or the terrain is interpolated between the different pixels in the heightmap.

regards
Didi

addition:

this is my solution (I had to switch the z-axis and include the Terrain.Translation).

tx:=(SceneAvatar.Translation.X - Terrain6.Translation.X + (Terrain6.Size.X / 2)) / Terrain6.Size.X;
tz:=((-1*(SceneAvatar.Translation.Z - Terrain6.Translation.Z)) + Terrain6.Size.Y / 2) / Terrain6.Size.Y;
th:=TerrainImage6.Height(vector2(tx,tz),vector2(tx,tz));

Now I’m able to create grass (TCastleTransform) on the terrain.

Thank you for your great support
Didi

2 Likes

Note that there is Castle Game Engine: CastleBehaviors: Class TCastleStickToSurface behavior just for this specific usecase. You can check here for more info:

that’s right, I tried TCastleStickToSurface, but I couldn’t get this to work.

For the grass I use this:

for i := 1 to 50 do
begin
GrassTransforms[I] := TCastleTransform.Create(FreeAtStop);
GrassTransforms[I].Translation := CalcTransformPos(40);
GrassTransforms[I].Add(SceneGrass);
MainViewport.Items.Add(GrassTransforms[I]);
end;

With the function CalcTransformPos I’m now able to set the height of terrain.
Do you think it works with TCastleStickToSurface?

Cool image with the grass!

I do hope to extend our terrain tools in the future to have such things (terrain modification, grass and trees planting on terrain etc.). Good that in the meantime, it is doable already :slight_smile:

Indeed. Heights returned by TCastleTerrainImage.Height (or in general by any TCastleTerrainData.Height) are not final. They are used for the points on the mesh generated by TCastleTerrain, and between them we place triangles and so effectively introduce linear interpolation between them.

I can see now that I should fix this. TCastleTerrainImage.Height should actually already interpolate the results (that is, perform bilinear filtering), otherwise if you use mesh with a big density – you will start to see pixels. Maybe it deserves a property at TCastleTerrainImage, like BilinearFiltering: Boolean. I added this to my TODO.

TCastleStickToSurface is indeed another approach to this. It uses a completely different way to query the mesh: it just uses ray collision, so it really doesn’t do anything specific to a terrain. It’s a bit less efficient, but it is more general solution. You can see at TCastleStickToSurface.Update ( castle-engine/src/transform/castlebehaviors_sticktosurface.inc at master · castle-engine/castle-engine · GitHub ) implementation how it’s doing that.