Audit prop budgets and add a prop-gallery art check
- audit_glb decodes geometry: 400 tris / 600 verts budget, degenerate triangles, UV range, ground contact, one material; newspaper box parts moved to sit on y=0 - capture_prop_gallery.gd renders the six props on a studio backdrop; new prop_gallery catalogue entry + AI mockup (compare 10 good, judge match 0.90) - headless test that instances share the imported atlas material - README documents the gallery capture and budget; TODO props pipeline section moved to DONE
This commit is contained in:
1 parent
bb40a98537
commit
2a4e0d1998
12 files changed
+310
-22
No files matched your search
@@ -0,0 +1,104 @@
|
||||
extends SceneTree
|
||||
## Dev helper: render every generated prop side by side on a plain backdrop and
|
||||
## save user://screenshots/10-prop-gallery.png. The image feeds
|
||||
## `tools/screen_designs.py compare` so a prop regression is visible.
|
||||
##
|
||||
## Usage: godot --path . --script res://src/test/capture_prop_gallery.gd
|
||||
|
||||
const OUT_DIR := "user://screenshots"
|
||||
const MODEL_DIR := "res://assets/models/generated/"
|
||||
const PROPS := ["bollard", "bench", "trash_bin", "planter_box",
|
||||
"newspaper_box", "fire_hydrant"]
|
||||
const SPACING := 1.9
|
||||
|
||||
var frames := 0
|
||||
|
||||
func _initialize() -> void:
|
||||
root.size = Vector2i(1280, 720)
|
||||
_build_stage()
|
||||
|
||||
func _build_stage() -> void:
|
||||
var stage := Node3D.new()
|
||||
stage.name = "PropGallery"
|
||||
root.add_child(stage)
|
||||
|
||||
var environment := Environment.new()
|
||||
environment.background_mode = Environment.BG_COLOR
|
||||
environment.background_color = Color(0.52, 0.54, 0.58)
|
||||
environment.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR
|
||||
environment.ambient_light_color = Color(0.78, 0.80, 0.84)
|
||||
environment.ambient_light_energy = 0.8
|
||||
var world_env := WorldEnvironment.new()
|
||||
world_env.environment = environment
|
||||
stage.add_child(world_env)
|
||||
|
||||
# Floor and a plain backdrop wall: a studio line-up with no street context.
|
||||
var floor_mesh := PlaneMesh.new()
|
||||
floor_mesh.size = Vector2(14, 8)
|
||||
var floor := MeshInstance3D.new()
|
||||
floor.mesh = floor_mesh
|
||||
floor.material_override = _flat(Color(0.50, 0.51, 0.54), 0.9)
|
||||
stage.add_child(floor)
|
||||
|
||||
var backdrop_mesh := BoxMesh.new()
|
||||
backdrop_mesh.size = Vector3(14, 6, 0.1)
|
||||
var backdrop := MeshInstance3D.new()
|
||||
backdrop.mesh = backdrop_mesh
|
||||
backdrop.material_override = _flat(Color(0.55, 0.57, 0.61), 0.95)
|
||||
backdrop.position = Vector3(0, 3.0, -3.4)
|
||||
stage.add_child(backdrop)
|
||||
|
||||
# Raised back row so both rows read in a single shot.
|
||||
var platform_mesh := BoxMesh.new()
|
||||
platform_mesh.size = Vector3(6.8, 0.8, 1.9)
|
||||
var platform := MeshInstance3D.new()
|
||||
platform.mesh = platform_mesh
|
||||
platform.material_override = _flat(Color(0.38, 0.39, 0.42), 0.85)
|
||||
platform.position = Vector3(0, 0.4, -2.1)
|
||||
stage.add_child(platform)
|
||||
|
||||
var light := DirectionalLight3D.new()
|
||||
light.rotation_degrees = Vector3(-48, -35, 0)
|
||||
light.light_energy = 0.85
|
||||
light.shadow_enabled = true
|
||||
stage.add_child(light)
|
||||
|
||||
var camera := Camera3D.new()
|
||||
camera.position = Vector3(0, 2.1, 6.2)
|
||||
camera.fov = 50.0
|
||||
camera.look_at_from_position(camera.position, Vector3(0, 0.75, -1.0))
|
||||
camera.current = true
|
||||
stage.add_child(camera)
|
||||
|
||||
# 3 x 2 grid: three props on the front row, three on the back row.
|
||||
var columns := [-1.95, 0.0, 1.95]
|
||||
for index in PROPS.size():
|
||||
var prop_name: String = PROPS[index]
|
||||
var packed := ResourceLoader.load(MODEL_DIR + prop_name + ".glb") as PackedScene
|
||||
if packed == null:
|
||||
continue
|
||||
var model := packed.instantiate() as Node3D
|
||||
var col: int = index % 3
|
||||
var row: int = index / 3
|
||||
model.position = Vector3(columns[col], 0.8 if row == 1 else 0.0, -2.1 * row)
|
||||
model.rotation_degrees.y = 14.0
|
||||
stage.add_child(model)
|
||||
|
||||
func _flat(color: Color, roughness: float) -> StandardMaterial3D:
|
||||
var material := StandardMaterial3D.new()
|
||||
material.albedo_color = color
|
||||
material.roughness = roughness
|
||||
return material
|
||||
|
||||
func _process(_delta: float) -> bool:
|
||||
frames += 1
|
||||
if frames < 20:
|
||||
return false
|
||||
var dir := DirAccess.open("user://")
|
||||
if dir == null or not dir.dir_exists("screenshots"):
|
||||
dir.make_dir_recursive("screenshots")
|
||||
var image := root.get_texture().get_image()
|
||||
var err := image.save_png(OUT_DIR.path_join("10-prop-gallery.png"))
|
||||
print("10-prop-gallery.png -> err=%d (%dx%d)" % [err, image.get_width(), image.get_height()])
|
||||
quit(0)
|
||||
return true
|
||||
@@ -68,6 +68,34 @@ func test_prop_scenes_share_one_atlas_material() -> bool:
|
||||
instance.free()
|
||||
return TestUtils.check(problems.is_empty(), "one atlas material per prop (%s)" % str(problems))
|
||||
|
||||
func test_prop_instances_share_the_imported_material() -> bool:
|
||||
## Instancing a prop must not duplicate its atlas material; every copy uses
|
||||
## the same resource, so many props stay cheap to draw.
|
||||
var problems: Array[String] = []
|
||||
for prop in WorldBuilder.MODEL_PROPS:
|
||||
var packed := ResourceLoader.load(MODEL_DIR + str(prop) + ".glb") as PackedScene
|
||||
if packed == null:
|
||||
problems.append("%s: no scene" % prop)
|
||||
continue
|
||||
var first := packed.instantiate()
|
||||
var second := packed.instantiate()
|
||||
var a := _first_material(first)
|
||||
var b := _first_material(second)
|
||||
if a == null or b == null:
|
||||
problems.append("%s: missing material" % prop)
|
||||
elif a.get_instance_id() != b.get_instance_id():
|
||||
problems.append("%s: material is duplicated per instance" % prop)
|
||||
first.free()
|
||||
second.free()
|
||||
return TestUtils.check(problems.is_empty(), "prop instances share materials (%s)" % str(problems))
|
||||
|
||||
func _first_material(instance: Node) -> Material:
|
||||
for node in instance.find_children("*", "MeshInstance3D", true, false):
|
||||
var mesh: Mesh = (node as MeshInstance3D).mesh
|
||||
if mesh != null and mesh.get_surface_count() > 0:
|
||||
return mesh.surface_get_material(0)
|
||||
return null
|
||||
|
||||
func test_world_places_generated_props() -> bool:
|
||||
var root := Node3D.new()
|
||||
var builder := WorldBuilder.new(root)
|
||||
|
||||
Reference in new issue
Block a user