-
Notifications
You must be signed in to change notification settings - Fork 63
Improve "difference mesh" generation, triangulation, and refactor code #111
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
ansonl
wants to merge
64
commits into
ChHarding:main
Choose a base branch
from
ansonl:topbot
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
…to this mega change since I changed a lot.
…le in this commit so it's easier to just pull in the changes.
…d raster add/minus/scaling in grid.__init__(). Removed tile_info.user_offset because it was redundant to minus the minimum height of the raster then add user_offset (which is the minimum height of raster - user defined min_elev).
…zip_file_name takes precedence for ZIP and folder name
… on the dilation mask if needed
…his fixes an issue where the top=notNaN and bottom=NaN in later raster processing step leads to the bottom being forced to base in that location.
nirabo
added a commit
to nirabo/TouchTerrain_for_CAGEO
that referenced
this pull request
Oct 14, 2025
Testing and validation complete: - All tests passing (7 passed, 20 skipped) - Dependencies verified - Pre-commit formatting applied - Integration validated Deferred 138 linting errors in PR ChHarding#111 core files to post-integration cleanup. Ready for pull request creation. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
93 tasks
Find the intersection polygon between each raster cell and the clipping polygon. Sort all individual edges of intersection polygons into buckets stored in RasterVariants based on if the edge lies on a cardinal direction edge of the cell quad. Marks all interior edges as needing walls created. Add shapely_utils with functions for flattening shapely geometries
…ke_wall" Created python test case for the inner most wall determination function
…k at horizontal and vertical neighbor cell and were looking at diagonal neighbor by applying direction YX at same time. Worked on test case for intersection function
…2 cell structure and clipping poly
…de if direction is out of range which happens when the cell is on the very edge of the raster.
…ll creation. Moved quad and vertex into their own files for import because my new functions in shapely_utils.py need to import quad but they can't import existing quad from grid_tesselate due to a circular reference since the new functions are called from grid_tesselate. Simplified the code for marking walls and handle case where cell1 or cell2 are disjoint or contained properly in boundary.
…. Changed wall marking to mark walls on the Polygon side of a match instead of cell 1 so that we know which vertex order to create the eventual wall edge for outward normal direction.
…erties for surfacePolygons explicitly as None so that the property exists to check for existence
… the cell is outside the clipping boundary. The top_hint is used in raster_preparation and affects the top.original raster so it's cells that are out of bounds need to match the top.original cell boundary when they are out of bounds.
…ll_for_walls to handle case where cell1 has no buckets when cell1 is outside the boundary (or otherwise NaN from source) or cell1 is contained properly
…tion borders of the vertex that is deleted because if an entire tri is removed due to having the same Z, there is no longer those 2 borders touching it and there is no new diagonal "wall/border" because the the diagonal edge of the top and bottom have the same Z
…ce mode bottom raster interpolation values match the corresponding "top" raster of the normal mode
…dundancy in environment setup
…irements.txt. Remove requirements.txt from active use by renaming it. Update pyproject.toml metadata.
…n the polygon_parent value
…ge but cell 1 N/S wall was being set
…t imprecision and change -0 to +0
…on. RasterVariants polygon_intersection_geometry now only represents disjoint and intersection (partial+complete) cases. Wall marking handles partial/complete intersection case by checking dilated elevation raster to see if we are at the edge of the generated mesh to see if we need a wall.
…raster_preparation relies on a clipped raster and wall marking relies on dilated raster
This occured because we stopped differentiating between partial intersection and full enclosure (contains_properly) for cells with the existence of polygon_intersection_geometry. We can't differentiate between partial and full enclosure that way anymore so we create a new 2d array under RasterVariants to store if a cell is fully contained by the clipping polygon. This way create_cell() can use that new array to check if a cell is fully contained and use the "quad" for the top/bot of the generated mesh instead of the surface polygon which is now present for all cells that intersect the clipping polygon in some way.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
I have refined Chris' difference mesh generation code, removed some redundant steps, and added some new steps needed to get more seamless meshes. I have also refactored the code and class organization a bit to play better with code editor autocomplete such as VSCode intellisense.
This is a draft of my changes to Touch Terrain. I'll edit this PR and add my changes for a little while until it is ready to merge.Difference Mesh
Tweaked Chris' code to get the "difference mesh" to generate at the correct height with the right walls. The difference mesh is the mesh made from the subtraction of a "top" and "bottom" DEM. The walls are created at the right locations by dilating twice.
Two modes are referred to in the code:
Normal Mode
Generates a mesh for the passed in raster with bottom surface flat along the base.
top_elevation_hintshould be passed when generating a raster that will be the "bottom" raster of a related Difference Mesh.Difference Mesh Mode
Generates a mesh that is sandwiched between two rasters.
Config values related to Difference Mesh mode are:
Mesh Limitations
TouchTerrain's current mesh generation is limited to creating connected volumes with quads as the "top" and "bottom" surfaces of the volume. Real vertical quads connecting a top and bottom quad as "walls" are only allowed at the true X/Y borders of a connected area. This means that a vertical quad (wall) is not generated to connect vertices of 2 quads that are both on the "top" or "bottom" surface.
↓ This limitation leads to mesh generations where 2 volumes share edges. Because an edge should only have 2 faces, this is technically non-manifold.

The 2 volumes are normally closed on their own and the only connection between them are shared edges with no shared volume going through them so this should not cause issues for most usages. It's easy to tell where one volume begins and ends by eye. Hopefully our mesh processing software is smart enough.
The mesh size reduction method in #99 still works despite these technically non-manifold edges because Blender correctly recognizes these edges as part of the "outside edge border".
In the future, we should look into adding a way for top/bottom quad of a
cellto connect to the quad along the same surface of anothercellby a vertical quad.cell's bottomquadcenter value is at the base and it could connect to bordering bottom quads' edges by a vertical quad to avoid the non-manifold edges seen in the animation above where the 2 width gap straddling the highlighted edges that should be along the base is forced to have a "transition" distance of at least 1 cell to get cells that are completely along the base (or actually gapped like in the picture).Make the difference mesh manifold at edges
Remove zero height volumes that occur near the edges of the difference mesh (where the top and bottom mesh have the same Z coordinates) so that the mesh is manifold. This cleanup is in
cell.remove_zero_height_volumes()The removal in NW and SE directions works best when splitting edge rotation is set to favor less steep split edges. Thus
remove_zero_height_volumes()is only active and needed whensplit_rotation = 1↓ Before with zero volume shapes


↓ After removing zero volume shapes
Quad Triangulation Splitting Edge Rotation
Add feature to rotate the splitting edge when triangulating
quads so that the edge orientation can favor less/more steep split edges + flat borders. This occurs inquad.get_triangles()The original behavior of constant edge orientation from NW-SE is still the default behavior.
↓ Before


↓ After
Fixed W/E X coordinate flipped bug
The W/E locations for X coordinate in
createCells()was flipped and noticed in a previous code comment. This made the assignment of the cell's quad's corner vertices confusing because we were flipping the W/E back again during the vertex creation.9297859
This is fixed in the linked commit of this PR so that the X coordinates assigned for W/E now reflect the right W or E directions.
Code refactor
Partially updated the code to PEP standard. Such as
UpperCaseClassNamefor class names andlower_case_underscorefor variable names. It makes Touch Terrain easier to read and debug in code editors with Python linting such as VSCode.Python type hints (Requires Python 3.10+)
Added Python type hints to make code readability and debugging easier. Types have been assigned to some of the variables but more type hints need to be added in the future to make the code "pass" compile time type checking.
Hovering over type hinted variables now tells you what it represents and what it is used for.
Python docstrings
Added new and converted existing docstrings to be formatted correctly.
Numpy
Some files imported
numpyasnpand some had justnumpyso I changed the references in TouchTerrainEarthEngine.py to leavenumpyasnumpyto be consistent.Touch Terrain Config
Configuration is defined as a class
TouchTerrainConfiginstead of adict[str, Any]. Config values centrally managed in a single location inuser_config.pyas class attributes allow type hinting and hover to view documentation on that value.The class attribute also makes it less likely to make a typo with IDE autocomplete versus manually typing a text key for dictionary.
Touch Terrain Tile Info
tile_infois is defined as a classTouchTerrainTileInfoinstead of adict[str, Any]. Similar benefits as the transition toTouchTerrainConfig.TouchTerrainTileInfois defined intile_info.py.TouchTerrainConfigis now stored in tile infos underTouchTerrainTileInfo.configso that the config values can be accessed in a single point of truth instead of copying each config value into a dictionary.Multiple Raster Version Management with
RasterVariantsLots of raster operations done in Touch Terrain affect some or all of the rasters for a single DEM. Keeping track of multiple variants of a DEM with variables like
top,top_nan,top_dilatedis easy to forget making a change to a single variant.Raster variants for the same DEM in various stages of processing are kept in
RasterVariantswhich each variant stored as an attirbute such asoriginal,nan_close, anddilated. There is also a newedge_interpolationwhich uses the DEM from an optionalimportedDEM_interpconfig option to interpolate the top vertex values for thebottom_thru_basecase.RasterVariantssupports+,-,*operators so all raster variants stored asnumpy.ndarraycan be modified at once as ifRasterVariantswas a singlenumpy.ndarray. In the example below, all bottom DEM raster variants' arrays are increased by a constant value.ProcessingTileProcessingTilecontains attributes about the processing tile that were previously passed passed as individual parameters. Now it is easier to pass data along for tile logic by adding it to this new class instead of creating new parameters.Other Changes/Notes
DEM_nameconfig for locally imported DEM andconfig_path(automatically set in standalone mode)zip_file_nameorDEM_nameorconfig_path(checked in that order) is now used as the exported ZIP filename.DEM_nameorconfig_path(checked in that order) is now used as the mesh filename.If only one tile is generated, the exported filename does not include the
_tile_1_1at the end of the filename.tileScaleconfig optionThe map scale can now be directly specified with
tileScaleconfig option. Specified tile scale takes precedence overtilewidth.Other PRs
Incorporated #109, #106
Test environment
I used conda and virtual environment for testing and running TouchTerrain. For anyone else working with a local directory install of TouchTerrain in a similar environment, I recommend cloning the repo into its own folder.
I keep the data files in a separate directory at the top level named like
touchterrain-devso the the repo code folder and the data folder are in the same top level directory.To run TouchTerrain with from
TouchTerrain_standalone.pyin the code folder but use the data folder as the working directory, you can reference the python file in the code folder while in data folder. Likepython ../TOUCHTERRAIN_FOR_CAGEO/TouchTerrain_standalone.pyOr open VSCode in the code folder (workspace folder) and use a VSCode launch configuration setup to debug in the data folder (cwd):
I have added a VSCode
launch.jsonto the repo that I use. It has some left over test cases right now and I will clean up the file and add some test configs in the future. Mylaunch.jsonis setup to run with the JSON config files and DEMs in the../touchterrain-dev(data) folder relative to the repo (code) folder as described above.