Castle terrain subdivisions?

Trying to figure out discrepencies in my water flow, I am surprised to find that my terrain tiles are 99x99 ‘cells’ instead of 100x100. I have subdivisions set to 100 for my 500x500m area… so I would expect that would make an array of 101x101 vertices and there would be 100x100 rows and columns of 2-triangle cells. So that each cell would be 5x5m. But it would seem that instead I end up with 99 squares across the 500m so that each ‘cell’ in the grid ends up being 5.05x5.05m. My water grid then ends up being misaligned because it treats subdivisions as the number of cells or strips, not the number of vertices.

So is subdivisions really just the vertex count? If so then it seems misnamed.
Please clarify.

Pictured is 3x3 grid of terrain tiles, the center one has wire frame enabled. I had assumed it would be 100x100 cells instead of 99x99. (Good luck counting, but I have verified 3 times)

Sorry for such delay – just got to this unanswered post in my TODO.

The observed behavior is intentional, both terrain-generation algorithms (used depending on Triangulate property of TCastleTerrain) honor it the same. So Subdivisions = (4, 4) means there are 4 * 4 = 16 vertexes, and 3 * 3 = 9 quads rendered.

This matches the xDimension and zDimension values of X3D node ElevationGrid’ ( https://www.web3d.org/documents/specifications/19775-1/V4.0/Part01/components/geometry3D.html#ElevationGrid ).

But I agree this is confusing.

  • It would be more natural if subdivisions dictated rows and columns, as you write.

  • One would expect that e.g. halving subdivisions, from 1024x1024 to 512x512, decreases quad count / 4. But it doesn’t, not exactly, it actually changes the quad count from 1023^2 to 511^2 now. It decreases the vertex count / 4.

  • Comparing with Blender, we are inconsistent. Blender’s grid also shows properties called “subdivisions”, and Blender (4, 4) subdivisions mean 4*4=16 quads, and 5*5 vertexes.

So we’re inconsistent with both intuition and Blender :slight_smile: Added to TODO, I’ll deal with it. I will probably just change the interpretation of Subdivisions, hoping it doesn’t cause much turmoil for actual usage.

I already have scheduled a rework of this terrain triangulation (to decrease draw calls of TCastleTerrain in case of Triangulate=true, and likely get rid of Triangulate property – we should have one, perfect, triangulation algorithm, period). So I’ll deal with subdivisions too then.

1 Like

It isn’t really necessary to do anything for me on this! My wrapper classes sort it out for me so that behavior is consistent between my grids of different resolution. You wouldn’t want to break people who have the projects running on the way it is.

As for triangulation algorithm… I use the one that splits the squares all in the same direction instead of sometimes in the other direction.

I considered “leaving it the way it is” when writing last message, for backward compatibility.

But then I saw that we’re inconsistent with Blender, that has exactly the same property on a grid with different (+1) meaning, and this persuades me that it’s better to change this. Following my own guideline “3.7. Backward compatibility is important; having a consistent (easy to learn) API and useful features is even more important” ( Coding Conventions | Manual | Castle Game Engine ) – it is better to break compat than live with this forever.

I’ll of course announce it when done, along with warning that it changes things.

Why? And what can I do to change your opinion? :slight_smile: My initial preference was actually to keep as “the default one, and only one” the one that splits each quad into 2 triangles smartly – this results in better triangulation, this was done deliberately (in castle-engine/src/scene/x3d/x3dnodes_utils_elevationgrid.inc at 73105cb71eb8287eb3b18370fa82a49bc525bae8 · castle-engine/castle-engine · GitHub we detect whether it’s more optimal to split it this or that way).

Long long time ago I had tests when this resulted in definitely better-looking triangulation.

Because it corresponds to the triangle strips I build for my water layer.

Oh, OK. But water layer is flat. So as long as vertexes match, does it matter how are quads split into 2 triangles for terrain?

To explain my context: I have a few reasons why I want to change terrain mesh:

  • Simplicity. We now maintain 2 algorithms, Triangulate = false / true, and this doubles some work.

  • Efficiency.

    I found that Triangulate = true algorithm (using TIndexedTriangleStripSetNode primitive) is not efficient as it makes a number of triangle strips (one for each row) and thus a number of draw calls. The Triangulate = false algorithm is better because it uses TIndexedFaceSetNode underneath, and makes 1 draw call for entire terrain.

Above gives already 1 reason why Triangulate = false is better IMHO: 1 draw call.

The fact that it smartly splits each quad, along shorter edge, makes it also look better.

Currently, Triangulate = false has also one problem: updating it is not as fast. Because Triangulate = false uses TIndexedFaceSetNode which has some computations when updating. But my plan is to remove this difference by changing TIndexedFaceSetNode into TIndexedTriangleSetNode and add an internal API for “fast updates”.

In effect, we would have

  • something that always looks like current Triangulate = false (divides quad along shortest edge)
  • something that always has 1 draw call (like current Triangulate = false)
  • something that is fast to update (like current Triangulate = true).

Fixing the subdivision interpretation is just a change “by the way” of this :slight_smile:

That said, since you use our terrain intensively, I want to make sure you’re on board with these changes :slight_smile:

My water layer is not flat. Water flows downhill. It matches the terrain. So it is a mesh of indexed triangle strips, like your water demo with ripples you posted in the spring.

Ah!

To be clear, the demo mesh_update uses TIndexedTriangleSetNode geometry. It is a different primitive than terrains, both when

  • Triangulate = true (which uses TIndexedTriangleStripSetNode geometry now)

  • and Triangulate = false (which uses TIndexedFaceSetNode geometry now).

But it will be the same after the rework I describe above, since I want terrains to use TIndexedTriangleSetNode too.

That said, the TIndexedTriangleSetNode setup by mesh_update happens to match the look of Triangulate = false.

And I understand that for dynamic mesh (water updates) you don’t want to split quads in different way, depending on height (as it would imply the need to also change indexes, so a bit more work).

The question in this: Do you need them to match in how quads are triangulated? What visual issues do you have experience if they don’t? Your water is not flat, but still it’s heights do not exactly match the terrain heights, so does it matter if quads are split differently?

I want to judge do I need to implement a solution like TCastleTerrain.TriangulateQuadsConsistently, passed to TElevationNode.TriangulateQuadsConsistently, or not :slight_smile:

The water when at 0 height then just matches the terrain (I push down a tad to prevent z-fighting). So if they are split different that can cause ‘puddles’ to appear when there shouldn’t be. I had hoped maybe to make my own terrain using the same grid as the water, but don’t want to lose the current texture mapping terrain has, and hadn’t succeeded in getting that to work on my mesh. Or I could use the terrain for water layer if I could make it update as fast, but it updates very slowly in comparison. And yes, I rely on the indexes to stay the same so I can update just the heights quickly in the raw data.

You shouldn’t worry about me though. Ideally I would abandon your terrain so terrain deformation would be very fast. But I am not up to that leap yet. I tried to get the texturing from castle terrain to work with mine and failed. CastleTerrain works good enough for now, waiting a half second to rebuild terrain isn’t so bad if it just happens when you place a road (where the water updates every frame for the tile you are on and looking at)

(but I eventually I want it fast enough for realtime modifying terrain with tools, but that can wait)

Thanks! This makes sense.

I also realized that I questioned you needlessly – because our existing solution to edit the terrain by shaders (in PR Base terrain editing tools for Castle Engine Editor by and3md · Pull Request #612 · castle-engine/castle-engine · GitHub ) also obviously requires this, i.e. topology (indexes) cannot change when coordinates (drawn vertexes) change. Because the heights are then determined by a shader, when it’s too late to change indexes.

So, I need to implement the option TCastleTerrain.TriangulateQuadsConsistently anyway. For editing it will have to be true. (And it’s an open question is it really worth to maintain TCastleTerrain.TriangulateQuadsConsistently = false option for perfect rendering.) So you will also be able to just set it to true and get the same topology as now with Triangulate = true. Actually we should just make Triangulate a deprecated alias for TriangulateQuadsConsistently.

As for updating terrain fast: This is something we explore in Base terrain editing tools for Castle Engine Editor by and3md · Pull Request #612 · castle-engine/castle-engine · GitHub :slight_smile: Hopefully the solution there would be useful to you too. Alas, it takes me time to get to review + apply this.

1 Like

@edj

I’ve done the rework of terrain triangulation, as foretold in this thread :slight_smile: I will make a news about it on our news around the weekend, but I also wanted to let here know, so that you’re not surprised by it.

Summary of things that happened:

  • One triangulation method, not two. Using TIndexedTriangleSetNode.

    • Easier to maintain.

    • Fast to render: 1 draw call (unlike previous TIndexedTriangleStripSetNode).

    • Fastest to render also because TIndexedTriangleSetNode maps directly to GPU (unlike TIndexedFaceSetNode that needs triangulation, unlike TElevationGridNode that needs a proxy).

  • Normals calculated fast and good for connecting terrain patches.

    I found that this was broken previously in both Triangulate=true and Triangulate=false modes, although was supposed to work OK in Triangulate=true.

    Now it’s really good (and Triangulate, aka new TriangulationIgnoreHeights, doesn’t matter). Terrain patches will match (regardless of TriangulationIgnoreHeights, terrains will maych even if their TriangulationIgnoreHeights are different).

    Explore additional_noise_offset_test.castle-user-interface in examples/terain/ to test this. The screenshot below shows actually 3 TCastleTerrain instances, but you see no weird lighting at the point where they connect, their normals match perfectly.

  • New TriangulationIgnoreHeights.

    It no longer makes any big change to the algorithm underneath, it just toggles how we split quads, aside from this – everything else is calculated the same, always TIndexedTriangleSetNode.

    Triangulate is now a deprecated alias to TriangulationIgnoreHeights. If you used this to request consistent ordering – then new TriangulationIgnoreHeights is just as good. If you used Triangulate for anything else – then it is probably not needed anymore, new rendering takes the best from previous 2 algorithms.

  • Subdivisions meaning changed.

    They now determine the number of rows in columns in the mesh.
    E.g. subdivision (4,4) means 16 (4 * 4) quads and 25 (5 * 5) vertexes.
    This matches the Blender’s “subdivisions” parameter for grids.

    The meaning is also explicitly documented now, so I won’t have an excuse to break it :slight_smile:

Further TODOs are warranted, first of all to merge my changes with changes in Base terrain editing tools for Castle Engine Editor by and3md · Pull Request #612 · castle-engine/castle-engine · GitHub and finally merge that PR. It shows how to do terrain updates really fast (using shaders).

1 Like

This is great. When I get back to my efforts I look forward to incorporating the new terrain. Faster is always better! Summer has me busy on other fronts as it is so short around here at 8400ft on top of colorado’s terrain.

1 Like

I have been afraid to update to this version because even though it is for me, I have built my terrain and water classes around the prior meaning of subdivisions. But the new changes for movetofront and shadow optimizations were too much to resist and as I feared, my terrain crashes now. I am sure it is the subdivisions meaning change as I get a value out of range. I build the noise to an array in memory and then when the tile builds (at varying resolutions) it reads from that array which can also be modified by digging or whatever. Is it possible and easy to make a setting that allows subdivisions to work the old way? Then I can figure out my problem working the new way more leisurely while working on the things I am now focused on? I don’t want to be a pain. It is a shame my code has become complicated enough that this isn’t an easy/clear change. Once I worked around the size discrepency, I forgot what was involved with that.

Maintaining both meanings of Subdivisions in CGE, and making it switchable, would be a bit confusing and complicating that code. I think in your case, if you want a “temporary permission to delay upgrading to new meaning of Subvisions”, just modify your CGE copy: go to these lines of src/scene/castleterrain.pas: castle-engine/src/scene/castleterrain.pas at 9f9c462c6ea11b0c8b36d0ce07fed9835ff0029f · castle-engine/castle-engine · GitHub

and remove “+ 1” from

  XDimension := Round(Subdivisions.X) + 1;
  ZDimension := Round(Subdivisions.Y) + 1;

to make it

  XDimension := Round(Subdivisions.X);
  ZDimension := Round(Subdivisions.Y);

I think this is it. If you want to have for temporary time old meaning of subdivisions, you can just have these 2 lines modified from the original CGE code.

1 Like

Perfect. I wish my code were as simple.

1 Like

…unfortunately it still crashes. So the problem is not the subdivisions but something to do with the new triangulation behavior. The crash is in my code though so not sure how that would affect anything. Maybe there is something I need to setup differently in the new paradigm? The crash occurs when the tcastleterrain is calling the height virtual function override in my TCastleTerrainData subclass that puts the noise data into an array. The error is a range error because somehow the value it is looking up falls out of the range of the array now when it didn’t before. That is why I assumed subdivisions was the issue. Can you think of how the new build triangulation behavior might affect this?

Recall that my terrain is tiled, so I use the queryoffset you added to the TCastleTerrain for me last year. Does the new triangulation code handle the queryoffset? Maybe that could account for out of range locations?

The Height code takes the Coord and subtracts the query offset and then applies a factor that should result in a x and y values from 0…99 the index into the tile’s height array. But the resulting x, y index is ending up as -2, -2. which crashes because those are dword.
It should never be negative so it seems like QueryOffset is wrong.