Extract per-venue geometry specs into WorldBuilder
Add the Hollywood, Joke Shop and TV Studio tables for facade, window, stair, street, nav, guard-spawn and feature data. Unknown ids fall back to the boulevard. Use the specs for the core dimensions and gates, with a headless test for the distinct venue data. Headless 230/230.
This commit is contained in:
1 parent
99f390078d
commit
4cca2af2dd
4 files changed
+198
-62
No files matched your search
@@ -246,6 +246,7 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
|
||||
helmet is lifted 2 cm above the brow. `test_npc_character` guards head/back gear in Godot.
|
||||
Blender's GLB export is byte-nondeterministic for unchanged assets (padding/index order), so do
|
||||
not commit a full no-op rebuild; rebuild only the assets whose geometry changed.
|
||||
- **Venue geometry (v0.6.1):** `WorldBuilder.VENUE_SPECS` is the per-venue table; `WorldBuilder.new(root, id)` / `set_venue()` select it, unknown ids fall back to `hollywood_building`. Dimensions, window/pilaster rows, stairs, street, nav bounds, guard spawn and feature flags are read from the spec. Joke Shop and TV Studio currently use empty `prop_placements`; bespoke alley/studio dressing and the `GameManager` rebuild-on-venue-switch are follow-ups.
|
||||
- **Portrait capture:** `capture_realism.gd` faces the **Rig** toward the camera (using the
|
||||
inverse look direction) and hides the blue `FocusCircle`; the old code used the wrong yaw and
|
||||
left the focus disc over the face. An explicit `--portrait-type=N` now overrides the hooded
|
||||
|
||||
@@ -9,10 +9,11 @@ Today all three venues share the Hollywood `WorldBuilder` geometry, so the Joke
|
||||
concepts are only compared on lighting (noted in `tools/scene_concepts.py` `SHOTS`). They also
|
||||
have no stage in the committed `docs/screenshots/` gallery, only in `capture_realism.gd`.
|
||||
|
||||
- [ ] Extract a geometry spec per venue instead of branching inline: a `scenario.` (or
|
||||
`WorldBuilder`) table of facade width/height, door style, window rows, awning/marquee
|
||||
flags, street furniture set, palette and props, defaulting to the Hollywood values so
|
||||
nothing regresses.
|
||||
- [x] Extract a geometry spec per venue instead of branching inline: `WorldBuilder.VENUE_SPECS`
|
||||
now holds facade/stair/street/nav dimensions, window/pilaster rows, guard spawn, feature
|
||||
flags and prop placements for Hollywood, Joke Shop and TV Studio. Unknown ids fall back to
|
||||
Hollywood; `test_venue_geometry_specs_are_distinct_and_defaulted` checks the narrow Joke Shop
|
||||
facade, disabled skyline/marquee, studio bulb-canopy flag and fallback.
|
||||
- [ ] Joke Shop: narrow brick alley (10–12 m wide), one warm wall lantern as the key light,
|
||||
recessed door with a step, shuttered window, dumpster/AC clutter, no skyline ring — the
|
||||
alley concept has depth, not a horizon.
|
||||
|
||||
+159
-58
@@ -59,18 +59,99 @@ var library_nodes: Array[Node3D] = []
|
||||
## Total triangle budget for the Blender library pieces placed in the world.
|
||||
const LIBRARY_TRIANGLE_BUDGET := 60000
|
||||
|
||||
func _init(root_node: Node3D) -> void:
|
||||
## Per-venue geometry data. The Hollywood values are the fallback: every field
|
||||
## here defaults to the long-standing boulevard layout, so adding a venue cannot
|
||||
## silently regress the original scene. Joke Shop and TV Studio override the
|
||||
## facade/street measurements and feature flags; their bespoke dressing lands in
|
||||
## the following tasks.
|
||||
const VENUE_SPECS := {
|
||||
"hollywood_building": {
|
||||
"display_name": "Hollywood Boulevard Building",
|
||||
"facade_width": 40.0, "facade_height": 18.0, "facade_depth": 4.0,
|
||||
"facade_center_z": -6.0,
|
||||
"window_columns": [-17.0, -13.6, -10.2, -6.8, 6.8, 10.2, 13.6, 17.0],
|
||||
"pilaster_columns": [-15.3, -11.9, -8.5, -5.1, 5.1, 8.5, 11.9, 15.3],
|
||||
"window_rows": [9.2, 12.4],
|
||||
"stair_width": 8.0, "stair_count": 4, "stair_tread": 0.6, "stair_height": 0.16,
|
||||
"sidewalk_width": 30.0, "sidewalk_depth": 8.0,
|
||||
"street_width": 120.0, "street_depth": 26.0,
|
||||
"nav_min": Vector2(-18.0, -0.6), "nav_max": Vector2(18.0, 20.0),
|
||||
"guard_spawn": Vector3(0.0, 0.1, -2.0),
|
||||
"features": {
|
||||
"marquee": true, "skyline": true, "handrail": true, "sedan": true,
|
||||
"wall_lanterns": true, "palms": true, "street_trees": true,
|
||||
"crosswalk": true, "bulb_canopy": false,
|
||||
},
|
||||
},
|
||||
"joke_shop": {
|
||||
"display_name": "The Joke Shop",
|
||||
"facade_width": 12.0, "facade_height": 12.0, "facade_depth": 5.0,
|
||||
"facade_center_z": -6.5,
|
||||
"window_columns": [-3.8, 3.8],
|
||||
"pilaster_columns": [-5.2, 5.2],
|
||||
"window_rows": [8.2],
|
||||
"stair_width": 5.0, "stair_count": 2, "stair_tread": 0.65, "stair_height": 0.16,
|
||||
"sidewalk_width": 22.0, "sidewalk_depth": 6.0,
|
||||
"street_width": 75.0, "street_depth": 18.0,
|
||||
"nav_min": Vector2(-9.0, -0.6), "nav_max": Vector2(9.0, 14.0),
|
||||
"guard_spawn": Vector3(0.0, 0.1, -1.6),
|
||||
"features": {
|
||||
"marquee": false, "skyline": false, "handrail": false, "sedan": false,
|
||||
"wall_lanterns": true, "palms": false, "street_trees": false,
|
||||
"crosswalk": false, "bulb_canopy": false,
|
||||
},
|
||||
"prop_placements": [],
|
||||
},
|
||||
"tv_studio": {
|
||||
"display_name": "Late Night TV Studio",
|
||||
"facade_width": 30.0, "facade_height": 15.0, "facade_depth": 4.0,
|
||||
"facade_center_z": -6.0,
|
||||
"window_columns": [-11.0, -7.2, 7.2, 11.0],
|
||||
"pilaster_columns": [-13.2, -9.0, -5.0, 5.0, 9.0, 13.2],
|
||||
"window_rows": [10.5],
|
||||
"stair_width": 7.0, "stair_count": 3, "stair_tread": 0.6, "stair_height": 0.16,
|
||||
"sidewalk_width": 28.0, "sidewalk_depth": 7.0,
|
||||
"street_width": 100.0, "street_depth": 24.0,
|
||||
"nav_min": Vector2(-14.0, -0.6), "nav_max": Vector2(14.0, 18.0),
|
||||
"guard_spawn": Vector3(0.0, 0.1, -1.8),
|
||||
"features": {
|
||||
"marquee": false, "skyline": true, "handrail": true, "sedan": true,
|
||||
"wall_lanterns": false, "palms": false, "street_trees": true,
|
||||
"crosswalk": true, "bulb_canopy": true,
|
||||
},
|
||||
"prop_placements": [],
|
||||
},
|
||||
}
|
||||
const DEFAULT_VENUE := "hollywood_building"
|
||||
|
||||
var venue_id := DEFAULT_VENUE
|
||||
var spec: Dictionary = VENUE_SPECS[DEFAULT_VENUE]
|
||||
|
||||
func _init(root_node: Node3D, id: String = DEFAULT_VENUE) -> void:
|
||||
root = root_node
|
||||
set_venue(id)
|
||||
|
||||
## Select the geometry spec before `build()`. Unknown ids fall back to the
|
||||
## boulevard so old callers and tests keep working.
|
||||
func set_venue(id: String) -> void:
|
||||
venue_id = id if VENUE_SPECS.has(id) else DEFAULT_VENUE
|
||||
spec = VENUE_SPECS[venue_id]
|
||||
|
||||
func _feature(name: String) -> bool:
|
||||
return bool((spec.get("features", {}) as Dictionary).get(name, false))
|
||||
|
||||
func build() -> void:
|
||||
_build_ground()
|
||||
_build_building()
|
||||
_build_skyline_backdrop()
|
||||
_build_marquee()
|
||||
if _feature("skyline"):
|
||||
_build_skyline_backdrop()
|
||||
if _feature("marquee"):
|
||||
_build_marquee()
|
||||
_build_steps()
|
||||
_build_sidewalk()
|
||||
_build_street()
|
||||
_build_crosswalk()
|
||||
if _feature("crosswalk"):
|
||||
_build_crosswalk()
|
||||
_build_street_furniture()
|
||||
_build_street_detail()
|
||||
_build_library_dressing()
|
||||
@@ -111,41 +192,46 @@ func _build_ground() -> void:
|
||||
func _build_building() -> void:
|
||||
var building := StaticBody3D.new()
|
||||
building.name = "Building"
|
||||
var w := float(spec["facade_width"])
|
||||
var h := float(spec["facade_height"])
|
||||
var d := float(spec["facade_depth"])
|
||||
var cz := float(spec["facade_center_z"])
|
||||
var front := cz + d * 0.5
|
||||
# Main facade (textured brick)
|
||||
_add_box(building, Vector3(40, 18, 4), Vector3(0, 9, -6), Color(0.86, 0.85, 0.83), false, T_BRICK, Vector2(22, 10), "brick")
|
||||
_add_box(building, Vector3(w, h, d), Vector3(0, h * 0.5, cz), Color(0.86, 0.85, 0.83), false, T_BRICK, Vector2(w * 0.55, h * 0.55), "brick")
|
||||
# Stone base course so the facade has a footing instead of meeting the steps dead-on.
|
||||
_add_box(building, Vector3(40, 0.8, 0.5), Vector3(0, 0.4, -3.78), Color(0.52, 0.5, 0.47), false, null, Vector2.ONE, "stone")
|
||||
_add_box(building, Vector3(w, 0.8, 0.5), Vector3(0, 0.4, front + 0.22), Color(0.52, 0.5, 0.47), false, null, Vector2.ONE, "stone")
|
||||
# Facade depth, not a flat wall: a projecting plinth, a string course between
|
||||
# the window bands, a cornice under the roofline and pilasters at the bays.
|
||||
_add_box(building, Vector3(40, 0.34, 0.22), Vector3(0, 0.93, -3.9), Color(0.6, 0.58, 0.55), true, null, Vector2.ONE, "stone")
|
||||
_add_box(building, Vector3(40, 0.28, 0.28), Vector3(0, 7.6, -3.86), Color(0.6, 0.58, 0.55), true, null, Vector2.ONE, "stone")
|
||||
_add_box(building, Vector3(40, 0.5, 0.5), Vector3(0, 17.7, -3.75), Color(0.56, 0.54, 0.51), true, null, Vector2.ONE, "stone")
|
||||
_add_box(building, Vector3(40, 0.22, 0.2), Vector3(0, 10.9, -3.9), Color(0.58, 0.56, 0.53), true, null, Vector2.ONE, "stone")
|
||||
for bay in [-15.3, -11.9, -8.5, -5.1, 5.1, 8.5, 11.9, 15.3]:
|
||||
_add_box(building, Vector3(0.5, 8.4, 0.24), Vector3(bay, 12.6, -3.9),
|
||||
_add_box(building, Vector3(w, 0.34, 0.22), Vector3(0, 0.93, front + 0.1), Color(0.6, 0.58, 0.55), true, null, Vector2.ONE, "stone")
|
||||
_add_box(building, Vector3(w, 0.28, 0.28), Vector3(0, 7.6, front + 0.14), Color(0.6, 0.58, 0.55), true, null, Vector2.ONE, "stone")
|
||||
_add_box(building, Vector3(w, 0.5, 0.5), Vector3(0, h - 0.3, front + 0.25), Color(0.56, 0.54, 0.51), true, null, Vector2.ONE, "stone")
|
||||
_add_box(building, Vector3(w, 0.22, 0.2), Vector3(0, 10.9, front + 0.1), Color(0.58, 0.56, 0.53), true, null, Vector2.ONE, "stone")
|
||||
for bay in spec.get("pilaster_columns", [-15.3, -11.9, -8.5, -5.1, 5.1, 8.5, 11.9, 15.3]):
|
||||
_add_box(building, Vector3(0.5, 8.4, 0.24), Vector3(float(bay), 12.6, front + 0.1),
|
||||
Color(0.8, 0.78, 0.76), true, T_BRICK, Vector2(0.3, 4.7), "brick")
|
||||
# Entrance recess + door recess frame
|
||||
_add_box(building, Vector3(5.4, 6.4, 0.2), Vector3(0, 3.2, -3.9), Color(0.08, 0.08, 0.09), false, null, Vector2.ONE, "stone")
|
||||
_add_box(building, Vector3(5.4, 6.4, 0.2), Vector3(0, 3.2, front + 0.1), Color(0.08, 0.08, 0.09), false, null, Vector2.ONE, "stone")
|
||||
# Door frame columns and lintel, so the entrance is framed rather than painted on.
|
||||
for side in [-1.55, 1.55]:
|
||||
_add_box(building, Vector3(0.28, 5.2, 0.42), Vector3(side, 2.9, -3.86), Color(0.30, 0.28, 0.26), true, null, Vector2.ONE, "stone")
|
||||
_add_box(building, Vector3(3.4, 0.3, 0.42), Vector3(0, 5.55, -3.86), Color(0.30, 0.28, 0.26), true, null, Vector2.ONE, "stone")
|
||||
_add_box(building, Vector3(0.28, 5.2, 0.42), Vector3(side, 2.9, front + 0.14), Color(0.30, 0.28, 0.26), true, null, Vector2.ONE, "stone")
|
||||
_add_box(building, Vector3(3.4, 0.3, 0.42), Vector3(0, 5.55, front + 0.14), Color(0.30, 0.28, 0.26), true, null, Vector2.ONE, "stone")
|
||||
# Door frame and dark door
|
||||
_add_box(building, Vector3(3.0, 5.2, 0.3), Vector3(0, 2.9, -3.98), Color(0.13, 0.12, 0.11), true, null, Vector2.ONE, "painted_metal")
|
||||
_add_box(building, Vector3(3.0, 5.2, 0.3), Vector3(0, 2.9, front + 0.02), Color(0.13, 0.12, 0.11), true, null, Vector2.ONE, "painted_metal")
|
||||
# Door glass (semi-transparent)
|
||||
var glass := _add_box(building, Vector3(1.8, 1.4, 0.34), Vector3(0, 3.8, -3.9), Color(0.6, 0.75, 0.9, 0.55))
|
||||
var glass := _add_box(building, Vector3(1.8, 1.4, 0.34), Vector3(0, 3.8, front + 0.1), Color(0.6, 0.75, 0.9, 0.55))
|
||||
glass.material_override = Materials.glass(Color(0.55, 0.7, 0.88, 0.55))
|
||||
# Door handles: two slim metal bars where the guard reaches for the lock.
|
||||
for side in [-0.42, 0.42]:
|
||||
_add_box(building, Vector3(0.06, 0.5, 0.06), Vector3(side, 2.6, -3.79), Color(0.75, 0.74, 0.70), false, null, Vector2.ONE, "metal")
|
||||
_add_box(building, Vector3(0.06, 0.5, 0.06), Vector3(side, 2.6, front + 0.21), Color(0.75, 0.74, 0.70), false, null, Vector2.ONE, "metal")
|
||||
# Door lock indicator (green = open, red = locked), placed beside the handle
|
||||
door_indicator = _add_box(building, Vector3(0.18, 0.18, 0.24), Vector3(-1.8, 1.7, -3.82), Color(0.1, 0.9, 0.3))
|
||||
door_indicator = _add_box(building, Vector3(0.18, 0.18, 0.24), Vector3(-1.8, 1.7, front + 0.18), Color(0.1, 0.9, 0.3))
|
||||
door_indicator.material_override = _lock_indicator_material(Color(0.1, 0.9, 0.3))
|
||||
# Blender glazed double door in front of the recessed frame.
|
||||
var door_model := _load_prop_scene("double_door")
|
||||
if door_model != null:
|
||||
door_model.name = "LibraryDoubleDoor"
|
||||
door_model.position = Vector3(0, 0.0, -3.84)
|
||||
door_model.position = Vector3(0, 0.0, front + 0.16)
|
||||
building.add_child(door_model)
|
||||
library_nodes.append(door_model)
|
||||
_apply_library_ao(door_model, "double_door")
|
||||
@@ -154,18 +240,21 @@ func _build_building() -> void:
|
||||
_lamp_bulb_mats.append(material)
|
||||
_build_facade_windows(building)
|
||||
var col := BoxShape3D.new()
|
||||
col.size = Vector3(40, 18, 4)
|
||||
col.size = Vector3(w, h, d)
|
||||
var so := building.create_shape_owner(building)
|
||||
building.shape_owner_add_shape(so, col)
|
||||
building.shape_owner_set_transform(so, Transform3D(Basis(), Vector3(0, 9, -6)))
|
||||
building.shape_owner_set_transform(so, Transform3D(Basis(), Vector3(0, h * 0.5, cz)))
|
||||
root.add_child(building)
|
||||
|
||||
## Regular window bands above the entrance. Panes are dark glass by day and
|
||||
## light up after dark, which gives the facade depth at night.
|
||||
func _build_facade_windows(parent: Node3D) -> void:
|
||||
var columns := [-17.0, -13.6, -10.2, -6.8, 6.8, 10.2, 13.6, 17.0]
|
||||
for y in [9.2, 12.4]:
|
||||
for x in columns:
|
||||
var columns: Array = spec.get("window_columns", [-17.0, -13.6, -10.2, -6.8, 6.8, 10.2, 13.6, 17.0])
|
||||
var rows: Array = spec.get("window_rows", [9.2, 12.4])
|
||||
for value in rows:
|
||||
var y := float(value)
|
||||
for x_value in columns:
|
||||
var x := float(x_value)
|
||||
# Recessed reveal, then the pane: gives the window a dark frame edge.
|
||||
_add_box(parent, Vector3(1.96, 2.46, 0.1), Vector3(x, y, -3.99),
|
||||
Color(0.22, 0.21, 0.20), false, null, Vector2.ONE, "stone")
|
||||
@@ -188,12 +277,13 @@ func _build_steps() -> void:
|
||||
# The ground and building provide the actual collision boundaries.
|
||||
var steps := StaticBody3D.new()
|
||||
steps.name = "Steps"
|
||||
var step_w := 8.0
|
||||
var tread := 0.6
|
||||
var height := 0.16
|
||||
for i in 4:
|
||||
var step_w := float(spec.get("stair_width", 8.0))
|
||||
var tread := float(spec.get("stair_tread", 0.6))
|
||||
var height := float(spec.get("stair_height", 0.16))
|
||||
var count := int(spec.get("stair_count", 4))
|
||||
for i in count:
|
||||
var y: float = i * height
|
||||
var z: float = 4.0 * tread - i * tread
|
||||
var z: float = float(count) * tread - i * tread
|
||||
var w: float = step_w - i * 0.35
|
||||
_add_box(steps, Vector3(w, height, tread), Vector3(0, y + height / 2.0, z), Color(0.55, 0.52, 0.5))
|
||||
# Add collision shape to each tread
|
||||
@@ -203,10 +293,11 @@ func _build_steps() -> void:
|
||||
steps.shape_owner_add_shape(owner_id, tread_col)
|
||||
steps.shape_owner_set_transform(owner_id, Transform3D(Basis(), Vector3(0, y + height / 2.0, z)))
|
||||
# Landing at the top with collision
|
||||
var landing_y: float = 4 * height + 0.1
|
||||
_add_box(steps, Vector3(9, 0.2, 3), Vector3(0, landing_y, -1.5), Color(0.5, 0.47, 0.45))
|
||||
var landing_y: float = count * height + 0.1
|
||||
var landing_w := step_w + 1.0
|
||||
_add_box(steps, Vector3(landing_w, 0.2, 3), Vector3(0, landing_y, -1.5), Color(0.5, 0.47, 0.45))
|
||||
var landing_col := BoxShape3D.new()
|
||||
landing_col.size = Vector3(9, 0.2, 3)
|
||||
landing_col.size = Vector3(landing_w, 0.2, 3)
|
||||
var landing_owner := steps.create_shape_owner(steps)
|
||||
steps.shape_owner_add_shape(landing_owner, landing_col)
|
||||
steps.shape_owner_set_transform(landing_owner, Transform3D(Basis(), Vector3(0, landing_y, -1.5)))
|
||||
@@ -215,12 +306,14 @@ func _build_steps() -> void:
|
||||
func _build_sidewalk() -> void:
|
||||
var sidewalk := StaticBody3D.new()
|
||||
sidewalk.name = "Sidewalk"
|
||||
_add_box(sidewalk, Vector3(30, 0.18, 8), Vector3(0, 0.09, 8), Color(0.55, 0.55, 0.54), false, T_CONCRETE, Vector2(9, 2.4), "concrete")
|
||||
var w := float(spec.get("sidewalk_width", 30.0))
|
||||
var d := float(spec.get("sidewalk_depth", 8.0))
|
||||
_add_box(sidewalk, Vector3(w, 0.18, d), Vector3(0, 0.09, d * 0.5), Color(0.55, 0.55, 0.54), false, T_CONCRETE, Vector2(w * 0.3, d * 0.3), "concrete")
|
||||
var col := BoxShape3D.new()
|
||||
col.size = Vector3(30, 0.36, 8)
|
||||
col.size = Vector3(w, 0.36, d)
|
||||
var so := sidewalk.create_shape_owner(sidewalk)
|
||||
sidewalk.shape_owner_add_shape(so, col)
|
||||
sidewalk.shape_owner_set_transform(so, Transform3D(Basis(), Vector3(0, 0, 8)))
|
||||
sidewalk.shape_owner_set_transform(so, Transform3D(Basis(), Vector3(0, 0, d * 0.5)))
|
||||
root.add_child(sidewalk)
|
||||
|
||||
func _build_street() -> void:
|
||||
@@ -230,20 +323,24 @@ func _build_street() -> void:
|
||||
# white looked like more pavement.
|
||||
# The street keeps its own material so night mode can switch to the wet
|
||||
# asphalt map (glossier, with lamp reflections) and back.
|
||||
_street_mat = Materials.asphalt(Color(0.62, 0.63, 0.66), Vector2(30, 7))
|
||||
var street_mesh := _add_box(street, Vector3(120, 0.1, 26), Vector3(0, 0.05, 22),
|
||||
Color(0.62, 0.63, 0.66), false, T_ASPHALT, Vector2(30, 7), "asphalt")
|
||||
var street_w := float(spec.get("street_width", 120.0))
|
||||
var street_d := float(spec.get("street_depth", 26.0))
|
||||
var curb_z := float(spec.get("sidewalk_depth", 8.0)) + 4.1
|
||||
var street_z := curb_z + street_d * 0.5 - 3.1
|
||||
_street_mat = Materials.asphalt(Color(0.62, 0.63, 0.66), Vector2(street_w * 0.25, street_d * 0.27))
|
||||
var street_mesh := _add_box(street, Vector3(street_w, 0.1, street_d), Vector3(0, 0.05, street_z),
|
||||
Color(0.62, 0.63, 0.66), false, T_ASPHALT, Vector2(street_w * 0.25, street_d * 0.27), "asphalt")
|
||||
street_mesh.material_override = _street_mat
|
||||
# Center dashes along the street (x varied so they form a lane line, not a stack).
|
||||
for i in range(-40, 41, 4):
|
||||
_add_box(street, Vector3(0.2, 0.02, 1.2), Vector3(float(i), 0.11, 22), Color(0.85, 0.85, 0.30), false, null, Vector2.ONE, "concrete")
|
||||
for i in range(-int(street_w / 3.0), int(street_w / 3.0) + 1, 4):
|
||||
_add_box(street, Vector3(0.2, 0.02, 1.2), Vector3(float(i), 0.11, street_z), Color(0.85, 0.85, 0.30), false, null, Vector2.ONE, "concrete")
|
||||
# Curb between the sidewalk and the roadway.
|
||||
_add_box(street, Vector3(120, 0.22, 0.5), Vector3(0, 0.11, 12.1), Color(0.62, 0.61, 0.58), false, null, Vector2.ONE, "concrete")
|
||||
_add_box(street, Vector3(street_w, 0.22, 0.5), Vector3(0, 0.11, curb_z), Color(0.62, 0.61, 0.58), false, null, Vector2.ONE, "concrete")
|
||||
var col := BoxShape3D.new()
|
||||
col.size = Vector3(120, 0.2, 26)
|
||||
col.size = Vector3(street_w, 0.2, street_d)
|
||||
var so := street.create_shape_owner(street)
|
||||
street.shape_owner_add_shape(so, col)
|
||||
street.shape_owner_set_transform(so, Transform3D(Basis(), Vector3(0, 0, 22)))
|
||||
street.shape_owner_set_transform(so, Transform3D(Basis(), Vector3(0, 0, street_z)))
|
||||
root.add_child(street)
|
||||
|
||||
func _build_navmesh() -> void:
|
||||
@@ -256,9 +353,11 @@ func _build_navmesh() -> void:
|
||||
mesh.agent_max_climb = 0.8
|
||||
mesh.agent_max_slope = 45.0
|
||||
mesh.cell_size = 0.25
|
||||
var nav_min: Vector2 = spec.get("nav_min", Vector2(-18.0, -0.6))
|
||||
var nav_max: Vector2 = spec.get("nav_max", Vector2(18.0, 20.0))
|
||||
var verts := PackedVector3Array([
|
||||
Vector3(-18, 0.0, -0.6), Vector3(18, 0.0, -0.6),
|
||||
Vector3(18, 0.0, 20.0), Vector3(-18, 0.0, 20.0),
|
||||
Vector3(nav_min.x, 0.0, nav_min.y), Vector3(nav_max.x, 0.0, nav_min.y),
|
||||
Vector3(nav_max.x, 0.0, nav_max.y), Vector3(nav_min.x, 0.0, nav_max.y),
|
||||
])
|
||||
mesh.set_vertices(verts)
|
||||
mesh.add_polygon(PackedInt32Array([0, 1, 2, 3]))
|
||||
@@ -377,7 +476,7 @@ func _build_marquee() -> void:
|
||||
## a parked sedan, and wall lanterns. Kept separate so the placement validator
|
||||
## only sees grounded sidewalk furniture.
|
||||
func _build_library_dressing() -> void:
|
||||
if ResourceLoader.exists(BLENDER_PROP_DIR + "handrail.glb"):
|
||||
if _feature("handrail") and ResourceLoader.exists(BLENDER_PROP_DIR + "handrail.glb"):
|
||||
var rail := _load_prop_scene("handrail")
|
||||
if rail != null:
|
||||
rail.name = "LibraryHandrail"
|
||||
@@ -386,7 +485,7 @@ func _build_library_dressing() -> void:
|
||||
root.add_child(rail)
|
||||
library_nodes.append(rail)
|
||||
_apply_library_ao(rail, "handrail")
|
||||
if ResourceLoader.exists(BLENDER_PROP_DIR + "sedan.glb"):
|
||||
if _feature("sedan") and ResourceLoader.exists(BLENDER_PROP_DIR + "sedan.glb"):
|
||||
var sedan := _add_prop_model(root, "sedan", Vector3(6.0, 0.1, 17.0), 180.0)
|
||||
sedan.name = "ParkedSedan"
|
||||
if not library_nodes.has(sedan):
|
||||
@@ -394,7 +493,7 @@ func _build_library_dressing() -> void:
|
||||
for material in _emissive_materials(sedan):
|
||||
if not _lamp_bulb_mats.has(material):
|
||||
_lamp_bulb_mats.append(material)
|
||||
if ResourceLoader.exists(BLENDER_PROP_DIR + "wall_lantern.glb"):
|
||||
if _feature("wall_lanterns") and ResourceLoader.exists(BLENDER_PROP_DIR + "wall_lantern.glb"):
|
||||
for side in [-4.9, 4.9]:
|
||||
var lantern := _load_prop_scene("wall_lantern")
|
||||
if lantern == null:
|
||||
@@ -440,12 +539,13 @@ func _build_street_furniture() -> void:
|
||||
_collect_emissive(holder)
|
||||
# Blender library street trees replace the cone trees. Flank the walkway and
|
||||
# keep the sight line to the door clear.
|
||||
for i in 6:
|
||||
var side := -1.0 if i % 2 == 0 else 1.0
|
||||
var x := side * (6.5 + float(int(i / 2)) * 3.0)
|
||||
var z := 4.0 + float((i * 7) % 5)
|
||||
_add_prop_model(root, "street_tree", Vector3(x, 0.18, z),
|
||||
float((i * 37) % 360))
|
||||
if _feature("street_trees"):
|
||||
for i in 6:
|
||||
var side := -1.0 if i % 2 == 0 else 1.0
|
||||
var x := side * (6.5 + float(int(i / 2)) * 3.0)
|
||||
var z := 4.0 + float((i * 7) % 5)
|
||||
_add_prop_model(root, "street_tree", Vector3(x, 0.18, z),
|
||||
float((i * 37) % 360))
|
||||
|
||||
## Total triangles across the placed Blender library pieces, for the draw budget
|
||||
## test. Counts indices when present, otherwise the vertex stream.
|
||||
@@ -585,8 +685,9 @@ func _build_street_detail() -> void:
|
||||
var detail := Node3D.new()
|
||||
detail.name = "StreetDetail"
|
||||
root.add_child(detail)
|
||||
for index in PROP_PLACEMENTS.size():
|
||||
var entry: Dictionary = PROP_PLACEMENTS[index]
|
||||
var placements: Array = spec.get("prop_placements", PROP_PLACEMENTS)
|
||||
for index in placements.size():
|
||||
var entry: Dictionary = placements[index]
|
||||
var base_rot := float(entry.get("rot", 0.0))
|
||||
var variant := variant_for(index, base_rot)
|
||||
var holder := _add_prop_model(detail, str(entry["prop"]),
|
||||
@@ -821,7 +922,7 @@ func _build_guard_spawn() -> void:
|
||||
# The guard's head offset is 1.6m, so spawn at y=0.76-1.6=-0.84 to place feet on ground
|
||||
# But we want the guard at eye-level with the sidewalk, so adjust to y=0.1 for feet
|
||||
# Then the head will be at y=1.7 (1.6 offset + 0.1 base)
|
||||
marker.position = Vector3(0, 0.1, -2.0)
|
||||
marker.position = spec.get("guard_spawn", Vector3(0, 0.1, -2.0))
|
||||
root.add_child(marker)
|
||||
|
||||
func _lock_indicator_material(color: Color) -> StandardMaterial3D:
|
||||
|
||||
@@ -19,6 +19,39 @@ func test_navmesh_built() -> bool:
|
||||
root.free()
|
||||
return a and b
|
||||
|
||||
func test_venue_geometry_specs_are_distinct_and_defaulted() -> bool:
|
||||
var hollywood := WorldBuilder.new(Node3D.new())
|
||||
var joke := WorldBuilder.new(Node3D.new(), "joke_shop")
|
||||
var studio := WorldBuilder.new(Node3D.new(), "tv_studio")
|
||||
var unknown := WorldBuilder.new(Node3D.new(), "not_a_venue")
|
||||
var a := TestUtils.check(hollywood.venue_id == "hollywood_building",
|
||||
"the default venue is Hollywood")
|
||||
var b := TestUtils.check(float(hollywood.spec["facade_width"]) > float(joke.spec["facade_width"]),
|
||||
"the Joke Shop alley is narrower than the boulevard")
|
||||
var c := TestUtils.check(not bool(joke.spec["features"]["skyline"])
|
||||
and not bool(joke.spec["features"]["marquee"]),
|
||||
"the Joke Shop disables the skyline and marquee")
|
||||
var d := TestUtils.check(bool(studio.spec["features"]["bulb_canopy"])
|
||||
and not bool(studio.spec["features"]["marquee"]),
|
||||
"the studio uses a bulb canopy instead of the marquee")
|
||||
var e := TestUtils.check(unknown.venue_id == "hollywood_building",
|
||||
"an unknown venue falls back to Hollywood")
|
||||
var root := Node3D.new()
|
||||
var builder := WorldBuilder.new(root, "joke_shop")
|
||||
builder.build()
|
||||
var body := root.find_child("Building", true, false)
|
||||
var facade := body.get_child(0) as MeshInstance3D if body != null else null
|
||||
var f := TestUtils.check(facade != null and (facade.mesh as BoxMesh).size.x < 20.0,
|
||||
"the Joke Shop builds the narrow facade")
|
||||
var g := TestUtils.check(root.find_child("SkylineBackdrop", true, false) == null,
|
||||
"the Joke Shop builds no skyline ring")
|
||||
hollywood.root.free()
|
||||
joke.root.free()
|
||||
studio.root.free()
|
||||
unknown.root.free()
|
||||
root.free()
|
||||
return a and b and c and d and e and f and g
|
||||
|
||||
func test_phase_lighting_config_covered() -> bool:
|
||||
var ok := true
|
||||
for phase in [Rules.Phase.EARLY, Rules.Phase.MIDDAY, Rules.Phase.EVENING, Rules.Phase.LATE]:
|
||||
|
||||
Reference in new issue
Block a user