build: baked lighting pass on kit_demo test map
- Restructured kit_demo.tscn from linear demo to enclosed 5.12x5.12 room with walls, floor tiles, pillar, beam, doorway, window - Added WorldEnvironment with ProceduralSky for ambient lighting - Added DirectionalLight3D (sun key light, 45° angle, shadows enabled) - Added OmniLight3D (warm interior fill light) - Added ReflectionProbe for interior specular reflections (box projection, room-sized extents) - Added LightmapGI with balanced quality settings (bounces=3, texel_scale=1.0, max_texture_size=2048, denoiser=true) - Added tool script (kit_demo.gd) that prints bake status in editor - Added bake_lighting.gd CLI bake script (requires GPU-enabled instance) - Updated project.godot with reflection atlas and shadow map quality settings Headless baking note: Godot's standard editor build requires a GPU/display for LightmapGI.bake(). Open the scene in the Godot editor and click 'Bake Lightmap' on the LightmapGI node to generate the baked lightmap data.
This commit is contained in:
@@ -0,0 +1,330 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Generate Godot 4 .tscn modular kit scenes using CSG nodes.
|
||||
All dimensions in Godot meters, grid base = 2.56m (256cm).
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
SCENES_DIR = os.path.expanduser("/home/oplabs/tactical-shooter/client/assets/scenes/modular")
|
||||
MAT_BASE = "res://assets/materials"
|
||||
|
||||
# Grid constants
|
||||
U = 2.56 # 1 modular unit (256cm)
|
||||
W = 0.16 # wall thickness (16cm)
|
||||
F = 0.08 # floor thickness (8cm)
|
||||
|
||||
# Material name shortcuts
|
||||
MAT_WALL = "wall_concrete_01"
|
||||
MAT_WALL2 = "wall_concrete_02"
|
||||
MAT_FLOOR = "floor_concrete_01"
|
||||
MAT_FLOOR_TILE = "floor_tile_01"
|
||||
MAT_METAL = "metal_structural_01"
|
||||
MAT_BLUE = "accent_team_blue"
|
||||
MAT_RED = "accent_team_red"
|
||||
|
||||
|
||||
def tsnode(name, type_name, parent=".", props=None):
|
||||
"""Build a [node] entry for .tscn format."""
|
||||
lines = [f'[node name="{name}" type="{type_name}" parent="{parent}"]']
|
||||
if props:
|
||||
for k, v in props.items():
|
||||
lines.append(f"{k} = {v}")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
# ── Wall pieces ──────────────────────────────────────────────────────────
|
||||
|
||||
def build_wall_straight():
|
||||
"""Straight wall — 2.56m x 2.56m x 0.16m."""
|
||||
lines = [
|
||||
'[gd_scene load_steps=2 format=3]',
|
||||
f'[ext_resource type="Material" path="{MAT_BASE}/{MAT_WALL}.tres" id="1"]',
|
||||
'',
|
||||
tsnode("WallStraight01", "CSGBox3D", ".", {
|
||||
"size": f"Vector3({U}, {U}, {W})",
|
||||
"material": 'ExtResource("1")',
|
||||
"use_collision": "true",
|
||||
}),
|
||||
]
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
def build_wall_corner():
|
||||
"""L-corner — two CSG boxes in a CSGCombiner3D."""
|
||||
lines = [
|
||||
'[gd_scene load_steps=2 format=3]',
|
||||
f'[ext_resource type="Material" path="{MAT_BASE}/{MAT_WALL}.tres" id="1"]',
|
||||
'',
|
||||
tsnode("WallCorner01", "CSGCombiner3D", ".", {"use_collision": "false"}),
|
||||
tsnode("WallA", "CSGBox3D", "WallCorner01", {
|
||||
"operation": "0",
|
||||
"size": f"Vector3({U}, {U}, {W})",
|
||||
"material": 'ExtResource("1")',
|
||||
"use_collision": "true",
|
||||
"transform": f"Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, {W/2})",
|
||||
}),
|
||||
tsnode("WallB", "CSGBox3D", "WallCorner01", {
|
||||
"operation": "0",
|
||||
"size": f"Vector3({W}, {U}, {U})",
|
||||
"material": 'ExtResource("1")',
|
||||
"use_collision": "true",
|
||||
"transform": f"Transform3D(0, 0, -1, 0, 1, 0, 1, 0, 0, {W/2}, 0, 0)",
|
||||
}),
|
||||
]
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
def build_wall_doorway():
|
||||
"""Wall with door opening (0.80m x 2.00m cutout)."""
|
||||
dw, dh = 0.80, 2.00
|
||||
off_y = dh/2 - U/2 # bottom-aligned
|
||||
lines = [
|
||||
'[gd_scene load_steps=2 format=3]',
|
||||
f'[ext_resource type="Material" path="{MAT_BASE}/{MAT_WALL}.tres" id="1"]',
|
||||
'',
|
||||
tsnode("WallDoorway01", "CSGCombiner3D", ".", {"use_collision": "false"}),
|
||||
tsnode("MainWall", "CSGBox3D", "WallDoorway01", {
|
||||
"operation": "0",
|
||||
"size": f"Vector3({U}, {U}, {W})",
|
||||
"material": 'ExtResource("1")',
|
||||
"use_collision": "true",
|
||||
}),
|
||||
tsnode("DoorCutout", "CSGBox3D", "WallDoorway01", {
|
||||
"operation": "1",
|
||||
"size": f"Vector3({dw}, {dh}, {W + 0.04})",
|
||||
"use_collision": "true",
|
||||
"transform": f"Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, {off_y}, 0)",
|
||||
}),
|
||||
]
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
def build_wall_window():
|
||||
"""Wall with window opening (1.20m x 0.80m cutout, centered slightly above mid)."""
|
||||
ww, wh = 1.20, 0.80
|
||||
off_y = 0.30
|
||||
lines = [
|
||||
'[gd_scene load_steps=2 format=3]',
|
||||
f'[ext_resource type="Material" path="{MAT_BASE}/{MAT_WALL}.tres" id="1"]',
|
||||
'',
|
||||
tsnode("WallWindow01", "CSGCombiner3D", ".", {"use_collision": "false"}),
|
||||
tsnode("MainWall", "CSGBox3D", "WallWindow01", {
|
||||
"operation": "0",
|
||||
"size": f"Vector3({U}, {U}, {W})",
|
||||
"material": 'ExtResource("1")',
|
||||
"use_collision": "true",
|
||||
}),
|
||||
tsnode("WindowCutout", "CSGBox3D", "WallWindow01", {
|
||||
"operation": "1",
|
||||
"size": f"Vector3({ww}, {wh}, {W + 0.04})",
|
||||
"use_collision": "true",
|
||||
"transform": f"Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, {off_y}, 0)",
|
||||
}),
|
||||
]
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
def build_wall_endcap():
|
||||
"""Narrow edge cap — 0.32m x 2.56m x 0.16m."""
|
||||
lines = [
|
||||
'[gd_scene load_steps=2 format=3]',
|
||||
f'[ext_resource type="Material" path="{MAT_BASE}/{MAT_WALL}.tres" id="1"]',
|
||||
'',
|
||||
tsnode("WallEndcap01", "CSGBox3D", ".", {
|
||||
"size": "Vector3(0.32, 2.56, 0.16)",
|
||||
"material": 'ExtResource("1")',
|
||||
"use_collision": "true",
|
||||
}),
|
||||
]
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
# ── Floor pieces ─────────────────────────────────────────────────────────
|
||||
|
||||
def build_floor_tile():
|
||||
"""2.56m x 0.08m x 2.56m floor slab — industrial concrete."""
|
||||
lines = [
|
||||
f'[gd_scene load_steps=2 format=3]',
|
||||
f'[ext_resource type="Material" path="{MAT_BASE}/{MAT_FLOOR}.tres" id="1"]',
|
||||
'',
|
||||
tsnode("FloorTile01", "CSGBox3D", ".", {
|
||||
"size": f"Vector3({U}, {F}, {U})",
|
||||
"material": 'ExtResource("1")',
|
||||
"use_collision": "true",
|
||||
}),
|
||||
]
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
def build_floor_tile_ceramic():
|
||||
"""Floor slab with ceramic tile material."""
|
||||
lines = [
|
||||
f'[gd_scene load_steps=2 format=3]',
|
||||
f'[ext_resource type="Material" path="{MAT_BASE}/{MAT_FLOOR_TILE}.tres" id="1"]',
|
||||
'',
|
||||
tsnode("FloorTileCeramic01", "CSGBox3D", ".", {
|
||||
"size": f"Vector3({U}, {F}, {U})",
|
||||
"material": 'ExtResource("1")',
|
||||
"use_collision": "true",
|
||||
}),
|
||||
]
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
# ── Structural ───────────────────────────────────────────────────────────
|
||||
|
||||
def build_pillar():
|
||||
"""Square pillar — 0.64m x 2.56m x 0.64m."""
|
||||
lines = [
|
||||
f'[gd_scene load_steps=2 format=3]',
|
||||
f'[ext_resource type="Material" path="{MAT_BASE}/{MAT_WALL2}.tres" id="1"]',
|
||||
'',
|
||||
tsnode("Pillar01", "CSGBox3D", ".", {
|
||||
"size": "Vector3(0.64, 2.56, 0.64)",
|
||||
"material": 'ExtResource("1")',
|
||||
"use_collision": "true",
|
||||
}),
|
||||
]
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
def build_beam():
|
||||
"""Ceiling beam — 3.84m x 0.32m x 0.32m metal."""
|
||||
lines = [
|
||||
f'[gd_scene load_steps=2 format=3]',
|
||||
f'[ext_resource type="Material" path="{MAT_BASE}/{MAT_METAL}.tres" id="1"]',
|
||||
'',
|
||||
tsnode("Beam01", "CSGBox3D", ".", {
|
||||
"size": f"Vector3({U * 1.5}, 0.32, 0.32)",
|
||||
"material": 'ExtResource("1")',
|
||||
"use_collision": "true",
|
||||
}),
|
||||
]
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
# ── Accent panels ────────────────────────────────────────────────────────
|
||||
|
||||
def build_accent_panel(name, mat_name, color_label):
|
||||
"""A thin accent panel with team color material."""
|
||||
lines = [
|
||||
f'[gd_scene load_steps=2 format=3]',
|
||||
f'[ext_resource type="Material" path="{MAT_BASE}/{mat_name}.tres" id="1"]',
|
||||
'',
|
||||
tsnode(f"{color_label}Panel01", "CSGBox3D", ".", {
|
||||
"size": f"Vector3(1.28, 0.64, {W * 0.5})",
|
||||
"material": 'ExtResource("1")',
|
||||
"use_collision": "true",
|
||||
}),
|
||||
]
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
# ── Kit demo scene ───────────────────────────────────────────────────────
|
||||
|
||||
def build_kit_demo():
|
||||
"""Showcase scene — places every modular piece on a 3m-spaced strip."""
|
||||
# Each entry: (scene_name, ext_material_name, label_for_node)
|
||||
entries = [
|
||||
("wall_straight_01", MAT_WALL, "Wall01"),
|
||||
("wall_corner_01", MAT_WALL, "Corner01"),
|
||||
("wall_doorway_01", MAT_WALL, "Doorway01"),
|
||||
("wall_window_01", MAT_WALL, "Window01"),
|
||||
("wall_endcap_01", MAT_WALL, "Endcap01"),
|
||||
("floor_tile_01", MAT_FLOOR, "Floor01"),
|
||||
("floor_tile_ceramic_01", MAT_FLOOR_TILE, "FloorCeramic01"),
|
||||
("pillar_01", MAT_WALL2, "Pillar01"),
|
||||
("beam_01", MAT_METAL, "Beam01"),
|
||||
("accent_blue_panel", MAT_BLUE, "BluePanel01"),
|
||||
("accent_red_panel", MAT_RED, "RedPanel01"),
|
||||
]
|
||||
|
||||
# All unique materials used
|
||||
all_mats = sorted(set(e[1] for e in entries))
|
||||
mat_scene_map = {} # mat_name -> {scene_name -> ext_id}
|
||||
scene_map = {} # scene_name -> ext_id
|
||||
|
||||
ext_id_counter = 1
|
||||
lines = []
|
||||
|
||||
# First pass: assign ext ids to scenes (PackedScene) and materials
|
||||
scene_ext_ids = {}
|
||||
mat_ext_ids = {}
|
||||
for scene_name, mat_name, _ in entries:
|
||||
if scene_name not in scene_ext_ids:
|
||||
scene_ext_ids[scene_name] = ext_id_counter
|
||||
ext_id_counter += 1
|
||||
if mat_name not in mat_ext_ids:
|
||||
mat_ext_ids[mat_name] = ext_id_counter
|
||||
ext_id_counter += 1
|
||||
|
||||
total_ext = len(scene_ext_ids) + len(mat_ext_ids)
|
||||
load_steps = total_ext + 1
|
||||
|
||||
lines = [f'[gd_scene load_steps={load_steps} format=3]']
|
||||
|
||||
# Ext resources: scenes
|
||||
for scene_name, eid in scene_ext_ids.items():
|
||||
lines.append(
|
||||
f'[ext_resource type="PackedScene" '
|
||||
f'path="res://assets/scenes/modular/{scene_name}.tscn" id="{eid}"]'
|
||||
)
|
||||
|
||||
# Ext resources: materials (for any inline nodes)
|
||||
for mat_name, eid in mat_ext_ids.items():
|
||||
lines.append(
|
||||
f'[ext_resource type="Material" '
|
||||
f'path="{MAT_BASE}/{mat_name}.tres" id="{eid}"]'
|
||||
)
|
||||
|
||||
lines.append("")
|
||||
lines.append('[node name="KitDemo" type="Node3D"]')
|
||||
lines.append("")
|
||||
|
||||
x = -5.0
|
||||
for scene_name, mat_name, label in entries:
|
||||
scene_eid = scene_ext_ids[scene_name]
|
||||
lines.append(
|
||||
f'[node name="{label}" type="Node3D" parent="."]'
|
||||
)
|
||||
lines.append(f'instance = ExtResource("{scene_eid}")')
|
||||
lines.append(f'transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, {x:.1f}, 0, 0)')
|
||||
lines.append("")
|
||||
x += 3.0
|
||||
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
# ── Main ─────────────────────────────────────────────────────────────────
|
||||
|
||||
SCENE_BUILDERS = {
|
||||
"wall_straight_01": build_wall_straight,
|
||||
"wall_corner_01": build_wall_corner,
|
||||
"wall_doorway_01": build_wall_doorway,
|
||||
"wall_window_01": build_wall_window,
|
||||
"wall_endcap_01": build_wall_endcap,
|
||||
"floor_tile_01": build_floor_tile,
|
||||
"floor_tile_ceramic_01": build_floor_tile_ceramic,
|
||||
"pillar_01": build_pillar,
|
||||
"beam_01": build_beam,
|
||||
"accent_blue_panel": lambda: build_accent_panel("accent_blue_panel", MAT_BLUE, "Blue"),
|
||||
"accent_red_panel": lambda: build_accent_panel("accent_red_panel", MAT_RED, "Red"),
|
||||
"kit_demo": build_kit_demo,
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
os.makedirs(SCENES_DIR, exist_ok=True)
|
||||
print("Generating modular .tscn scenes...")
|
||||
for name, builder in SCENE_BUILDERS.items():
|
||||
content = builder()
|
||||
path = os.path.join(SCENES_DIR, f"{name}.tscn")
|
||||
with open(path, "w") as f:
|
||||
f.write(content)
|
||||
print(f" Created {name}.tscn ({len(content)} bytes)")
|
||||
print(f"\nDone — {len(SCENE_BUILDERS)} .tscn files")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user