Building footprints, data model and generated placeholder meshes (#36) #42
No reviewers
Labels
No labels
assets
bug
duplicate
enhancement
help wanted
invalid
question
testing
ui
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
liamjd/UntitledColonyBuilder!42
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "36-building-meshes"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Delivers the building data model and the three placeholder meshes.
Closes #36
What's here
BuildingFootprint— the tiles a building occupies, authored as an ASCII grid andstored as offsets from an explicit
@anchor, so every consumer ishovered_cell + offsetand rotation is a rotation about the cursor tile. Parsing happens in the
shapesetter,not
_init(), because exported values are assigned after_init()runs but setters dofire on load from disk.
Validation rejects a shape with no anchor, more than one anchor, no occupied tiles, or
tiles that are not 4-connected (flood fill over the parsed cells, so a C-shape joined
only through its base stays legal while corner-touching tiles do not).
cells()androtated()both return copies — a shared.tresmeans everyBuildingTypereferencingone holds the same ref-counted instance, so a mutating rotation would turn every building
of every type using that shape for the rest of the session.
BuildingTypegainsmesh,footprint,height_levels, plusplaceholder_shapeand
placeholder_colorfor the generated-mesh era.BuildingMeshes.build(type, tile_size, level_height)— generates aPrimitiveMeshsized from the footprint's bounds and
height_levels, tinted viaPrimitiveMesh.material.An authored
type.meshshort-circuits the whole thing and is returned untouched, which isthe migration path to Blender models later.
Resources: six footprints (1x1, 1x2, 2x2, 3x3, L, C) and three building types wired to
them, all three in the catalogue.
Decisions worth recording
Generated, not authored, meshes.
tile_sizeis an@exportonTerrainGrid, not aconstant — an authored
BoxMeshof size 6.0 would bake a silent coupling totile_size = 2.0into a file that never mentions terrain. Generation derives it. This alsofollows
TerrainGrid._build_mesh_library(), and leaves the door open to building anon-rectangular footprint from per-cell boxes, which an authored primitive cannot do.
Meshes stay centred on their origin. A bare
Meshhas no equivalent ofMeshLibrary.set_item_mesh_transform()and onlyPlaneMeshhas acenter_offset, so#39 sits a building on the ground with
instance.position.y = ground_y - mesh.get_aabb().position.y. That works for a centredplaceholder and stays correct for an authored mesh whose origin really is at its base.
Two-layer material.
PrimitiveMesh.materialcarries the building's real colour, aproperty of the type, shared by every instance.
MeshInstance3D.material_overrideis leftfree for #39's ghost tint. Neither knows the other exists, and the material is deliberately
opaque so real buildings stay in the opaque pass — a transparent building would be depth
sorted per object, which is how you get sorting pops against a terraced GridMap on yaw.
Verification
80/80 GUT tests across 5 scripts, no orphans.
test_building_footprint.gd(39) — parsing, ragged rows, case, bounds, rotation purityand non-aliasing, round-trip from disk, all four validation rules, and a walk over every
authored
.tres.test_building_meshes.gd(25) — sizing per shape, scaling againsttile_sizeandlevel_heightrather than hardcoded numbers, the grid-Y vs world-Z axis trap (using anon-square 1x2 footprint, the only shape that can distinguish a swap), cylinders never
overhanging a non-square footprint, the centred-origin contract, material colour /
matteness / opacity, both failure paths, and a walk over the real catalogue asserting
every shipped type is exactly
height_levels * level_heighttall.Height was the recurring defect — wrong twice, in two different ways, both times passing
a hand-picked example. It is now pinned by an invariant (a cylinder and a box of the same
height_levelsmust be the same height) rather than by a number.Visual check:
tools/screenshot_buildings.gddrops one of each type onto real plainsterrain using the placement maths #39 will own, and saves a PNG. Run headed —
gl_compatibilityrenders nothing headless, so no unit test in here says anything abouthow this looks. Confirms all three sit exactly on their tile tops and that the three
silhouettes are distinguishable at gameplay zoom.
Known gaps and follow-ups
callers, and the cache key depends on how #39 and the real placer actually call in.
it is the one building that most needs to be findable at a glance. Worth a more saturated
colour, but it is one field on a
.tresand not worth blocking on.build()does not yet document that its return may be shared. The authored pathreturns
type.meshby reference today, so callers must already treat the result asread-only. Worth stating on the function before #39 starts tinting anything.
will clip or float; that is #37's validity rules, not this PR.
cell_to_world()computes
(cell.x + 0.5) * tile_size,world_to_cell()floorslocal.x / tile_size,and
_configure_grid()setscell_center_x = true. Those three do not obviously agree.Nothing in this PR depends on it, but #39 will.
Unblocks #37 and #39.