Create 3D meshes for the three building types #36
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#36
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
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?
Create 3D meshes as code/resources for the Colony Rocket (3x3), Sensor Tower (1x1) and Basic Mine (2x2) building types, and the data model that carries them.
This issue covers the meshes and the building data model. The ghost/cursor behaviour that consumes them is #39; tile highlighting and validity is #37. Both depend on this.
Scope
BuildingFootprintresource (below).BuildingType:mesh: Mesh,footprint: BuildingFootprint,height_levels: int.Meshper building type, plus a footprint for each.BuildingFootprintFootprints are not all square. Rectangles are expected and L-shapes are planned, so an
intside length cannot express the data. Shapes are authored as a small ASCII grid:Authored shapes:
The anchor is authored, not derived
"The centre" is undefined for an L-shape and lands on a tile corner for any even-sided rectangle. Storing cells as offsets from an explicit anchor makes the tile under the cursor be the anchor, so every consumer is
hovered_cell + offsetwith no parity maths anywhere. It also makes rotation a rotation about the cursor tile, which is the behaviour you want.Note the consequence for the 2x2 mine above: with
@in the top-left, the hovered tile is always the mine's top-left corner. That is now an authoring choice visible in the file, not a hidden rule.rotated()must be pureRotation is
Vector2i(x, y) -> Vector2i(-y, x)appliedstepstimes, returning a new array. If footprints become shared.tresfiles, everyBuildingTypereferencing one holds the same ref-counted instance — a rotation that mutated_cellswould turn every building of every type using that shape for the rest of the session. Same hazard class as the shared-preset warning inTerrainGrid.next_shape().No rotation UI in this issue; just the function and its tests.
Parse in the property setter, not
_init()Verified on 4.7.2:
_init()runs before exported values are assigned, so parsing there yields an empty footprint. Property setters do fire when a resource is loaded from disk, soset(value): shape = value; _rebuild()is correct in both the editor and at runtime.Also verified:
@export_multilineserialises to literal newlines in the.tres, so the shape reads as an actual grid in a diff:Validation
A hand-typed grid can be wrong in ways an array cannot, so validate: exactly one
@, at least one occupied tile, ragged rows (pad or reject — decide), and 4-connectivity if disjoint blobs should be forbidden. A GUT test that walks the catalogue and asserts every footprint parses catches typos at commit time rather than at placement.Meshes: leave them centred, offset via
get_aabb()An earlier version of this issue said to author each mesh with its origin at the base, following
TerrainGrid._build_mesh_library(). That does not apply here.TerrainGridcan do it becauseMeshLibraryhasset_item_mesh_transform(); a bareMeshon a resource has no such hook, and the primitives have no origin offset of their own. Verified on 4.7.2:PlaneMeshis the only primitive with an offset. Baking origin-at-base into a box would mean hand-building anArrayMeshwith shifted vertices for every building — a lot of work to avoid one subtraction.So: leave the meshes centred, and let #39 sit them on the ground with
instance.position.y = ground_y - mesh.get_aabb().position.y. The caller never needs to know the building's height, and it keeps working unchanged if a mesh later becomes a hand-builtArrayMeshwhose origin is at its base.Simple boxes with a distinct colour/tint per type are enough for now; no art assets, consistent with how
TerrainGridgenerates its mesh library at runtime.For a non-rectangular footprint, model the mesh to suit its bounding box — #39 centres the ghost on
bounds(), not on the occupied tiles' centre of mass.Why
Meshrather thanPackedSceneDecided 2026-08-24. A single
Meshper type keeps the ghost trivial: oneMeshInstance3Dwith onematerial_override.material_overridedoes not propagate to childMeshInstance3Dnodes, so a multi-partPackedScenebuilding would force the ghost to walk the tree tinting each part.Revisit if and when buildings need per-instance behaviour or animated parts.
Height
height_levels: intonBuildingType, in terrain level units (xlevel_height), matching how the grid already quantises.On
BuildingTyperather than onBuildingFootprintso a shared generic shape (a plain2x2) can serve buildings of different heights. The mesh's real height viaget_aabb().size.yis art and is allowed to disagree withheight_levels, which is simulation — name that distinction in the doc comment, or someone will later "fix" the mismatch.Create 3D meshes for the two building typesto Create 3D meshes for the three building types