G1-Karte in Unity (Assets/Luneraria/Scenes/G1_Map.unity), reproduzierbar gebaut aus dem Godot-Weltgenerator (gebacken), den FBX-Modellen aus U2A und der Karte des C#-Kerns. - tools/godot-golden/world_export.gd: backt Gelände (Raster, Farben, Lauf- und Standhöhen), Lanebeläge, Plattform, Brücke, Randsteine, Basen, Landmarken, Vegetation, Adern, Grottentreppen, Aufstellungsbereiche und Licht. Braucht einen echten Renderer (HEADLESS=0); prüft auf Headless-Einheitstransformationen. - LuneWorldBuilder: Meshes (GUID-stabil), Szene, Licht, Himmel, Post-Processing, 16 Türme, 2 Ancients und die Grotte aus LuneMap mit Teammaterialien. - Materialsystem: Luneraria/Surface (Gelände, Kluft-Oberflächen, Fluss, Adern, Kristall, G2-Werte mit Teamvarianten und Vertex-AO) und Luneraria/Pad; LuneMaterialSet.Dress wie Godots dress. - View-Assembly: LuneSpace (Unity = (-x, h, y)), LuneGround, LuneCameraMath, LuneBuildingView (Zustände intakt bis Ruine). - Vergleichsbilder mit den Kameraeinstellungen der Godot-G2-Aufnahmen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
234 lines
12 KiB
GDScript
234 lines
12 KiB
GDScript
extends Node
|
|
## Bakes the procedural G1 world of the Godot reference for the Unity port (U2B): the view
|
|
## LuneWorld (scripts/luneraria/view/lune_world.gd) is built headless for the real map, and
|
|
## every static mesh it creates is written out - terrain grids, lane paving, platform, bridge,
|
|
## curbs, bases, landmarks, vegetation, grotto stairs, veins, deployment areas, water - with its
|
|
## transforms and material description, plus the height a figure stands on (0.5 m grid).
|
|
## The strategic views (towers, Ancients, grotto, tower effects) are skipped: Unity builds them
|
|
## from the FBX assets and the match state. Terrain and vegetation depend on Godot's noise and
|
|
## random generator, so baking keeps them exact. Coordinates stay Godot's (Unity: x -> -x).
|
|
## Run: HEADLESS=0 sh tools/godot-golden/run_godot.sh world_export --out=<dir>
|
|
## (a real rendering driver is needed: the headless dummy renderer returns identity transforms
|
|
## for MultiMesh instances; the tool refuses to write such a bake).
|
|
|
|
const LuneWorld = preload("res://scripts/luneraria/view/lune_world.gd")
|
|
const SKIP_SCRIPTS: Array[String] = [
|
|
"res://scripts/luneraria/view/lune_tower_view.gd", "res://scripts/luneraria/view/lune_ancient_view.gd",
|
|
"res://scripts/luneraria/view/lune_grotto_view.gd", "res://scripts/luneraria/view/lune_tower_fx.gd",
|
|
]
|
|
const STAND_STEP: float = 0.5
|
|
const STAND_EXTENT: float = 60.0
|
|
|
|
var _blob := PackedByteArray()
|
|
var _meshes: Array = []
|
|
var _mesh_ids: Dictionary = {}
|
|
var _materials: Array = []
|
|
var _material_ids: Dictionary = {}
|
|
var _world: Node3D
|
|
|
|
func _ready() -> void:
|
|
var out: String = "/tmp/lune_world"
|
|
for arg: String in OS.get_cmdline_user_args():
|
|
if arg.begins_with("--out="):
|
|
out = arg.substr(6)
|
|
var map := LuneMap.new(LuneBalance.default_balance())
|
|
_world = LuneWorld.new()
|
|
add_child(_world)
|
|
_world.build(map)
|
|
var props: Dictionary = {}
|
|
for key: String in _world._props:
|
|
props[(_world._props[key] as Mesh).get_instance_id()] = key
|
|
var surface_keys: Dictionary = {}
|
|
for key: String in _world._materials:
|
|
surface_keys[(_world._materials[key] as Material).get_instance_id()] = key
|
|
var nodes: Array = []
|
|
_collect(_world, nodes, props, surface_keys)
|
|
for entry: Dictionary in nodes:
|
|
if entry.kind == "multimesh" and (entry.transforms as Array).size() > 1 and entry.transforms[0] == entry.transforms[1]:
|
|
push_error("WORLD_EXPORT: MultiMesh %s has identical instance transforms (headless dummy renderer?)" % entry.name)
|
|
get_tree().quit(1)
|
|
return
|
|
var terrain: LuneTerrain = _world.terrain
|
|
var data: Dictionary = {
|
|
"engine": Engine.get_version_info().string,
|
|
"generator": "scripts/luneraria/view/lune_world.gd, scripts/luneraria/view/lune_terrain.gd",
|
|
"coordinates": "Godot: x right, y up, z towards the viewer; map point (x, y) = (x, h, y)",
|
|
"terrain": _terrain(terrain),
|
|
"stand": _stand(),
|
|
"lights": _lights(),
|
|
"nodes": nodes,
|
|
"meshes": _meshes,
|
|
"materials": _materials,
|
|
"constants": {"plaza_lift": LuneWorld.PLAZA_LIFT, "plaza_radius": LuneWorld.PLAZA_RADIUS, "water_level": LuneTerrain.WATER_LEVEL,
|
|
"base_height": LuneTerrain.BASE_HEIGHT, "arena_height": LuneTerrain.ARENA_HEIGHT, "arena_radius": LuneTerrain.ARENA_RADIUS,
|
|
"platform_height": LuneTerrain.PLATFORM_HEIGHT, "platform_radius": LuneTerrain.PLATFORM_RADIUS},
|
|
}
|
|
DirAccess.make_dir_recursive_absolute(out)
|
|
var json := FileAccess.open(out.path_join("g1_world.json"), FileAccess.WRITE)
|
|
json.store_string(JSON.stringify(data, "\t", false))
|
|
json.close()
|
|
var bin := FileAccess.open(out.path_join("g1_world.bytes"), FileAccess.WRITE)
|
|
bin.store_buffer(_blob)
|
|
bin.close()
|
|
print("WORLD_EXPORT nodes=%d meshes=%d materials=%d bytes=%d -> %s" % [nodes.size(), _meshes.size(), _materials.size(), _blob.size(), out])
|
|
get_tree().quit()
|
|
|
|
func _collect(node: Node, nodes: Array, props: Dictionary, surface_keys: Dictionary) -> void:
|
|
for child: Node in node.get_children():
|
|
var script: Script = child.get_script()
|
|
if script != null and SKIP_SCRIPTS.has(script.resource_path):
|
|
continue
|
|
if child is MeshInstance3D and (child as MeshInstance3D).mesh != null:
|
|
var instance := child as MeshInstance3D
|
|
var entry: Dictionary = _instance_common(instance, surface_keys)
|
|
entry["kind"] = "mesh"
|
|
entry["transforms"] = [_transform(_relative(instance))]
|
|
_mesh_ref(instance.mesh, props, entry)
|
|
nodes.append(entry)
|
|
elif child is MultiMeshInstance3D and (child as MultiMeshInstance3D).multimesh != null:
|
|
var multi := child as MultiMeshInstance3D
|
|
var entry: Dictionary = _instance_common(multi, surface_keys)
|
|
entry["kind"] = "multimesh"
|
|
var base: Transform3D = _relative(multi)
|
|
var transforms: Array = []
|
|
for index: int in range(multi.multimesh.instance_count):
|
|
transforms.append(_transform(base * multi.multimesh.get_instance_transform(index)))
|
|
entry["transforms"] = transforms
|
|
_mesh_ref(multi.multimesh.mesh, props, entry)
|
|
nodes.append(entry)
|
|
_collect(child, nodes, props, surface_keys)
|
|
|
|
func _relative(node: Node3D) -> Transform3D:
|
|
return _world.global_transform.affine_inverse() * node.global_transform
|
|
|
|
func _instance_common(instance: GeometryInstance3D, surface_keys: Dictionary) -> Dictionary:
|
|
var entry: Dictionary = {"name": str(instance.name), "visible": instance.visible,
|
|
"cast_shadow": instance.cast_shadow != GeometryInstance3D.SHADOW_CASTING_SETTING_OFF}
|
|
if instance.material_override != null:
|
|
entry["material"] = _material(instance.material_override, surface_keys)
|
|
var params: Dictionary = {}
|
|
for name: String in ["tint", "seed", "energy", "flicker"]:
|
|
var value: Variant = instance.get_instance_shader_parameter(name)
|
|
if value != null:
|
|
params[name] = _value(value)
|
|
if not params.is_empty():
|
|
entry["instance_params"] = params
|
|
return entry
|
|
|
|
func _mesh_ref(mesh: Mesh, props: Dictionary, entry: Dictionary) -> void:
|
|
if props.has(mesh.get_instance_id()):
|
|
entry["prop"] = props[mesh.get_instance_id()]
|
|
return
|
|
var id: int = mesh.get_instance_id()
|
|
if not _mesh_ids.has(id):
|
|
_mesh_ids[id] = _meshes.size()
|
|
var surfaces: Array = []
|
|
for index: int in range(mesh.get_surface_count()):
|
|
var arrays: Array = mesh.surface_get_arrays(index)
|
|
var surface: Dictionary = {"vertices": _put((arrays[Mesh.ARRAY_VERTEX] as PackedVector3Array).to_byte_array(), (arrays[Mesh.ARRAY_VERTEX] as PackedVector3Array).size())}
|
|
if arrays[Mesh.ARRAY_NORMAL] != null:
|
|
surface["normals"] = _put((arrays[Mesh.ARRAY_NORMAL] as PackedVector3Array).to_byte_array(), (arrays[Mesh.ARRAY_NORMAL] as PackedVector3Array).size())
|
|
if arrays[Mesh.ARRAY_TEX_UV] != null:
|
|
surface["uvs"] = _put((arrays[Mesh.ARRAY_TEX_UV] as PackedVector2Array).to_byte_array(), (arrays[Mesh.ARRAY_TEX_UV] as PackedVector2Array).size())
|
|
if arrays[Mesh.ARRAY_COLOR] != null:
|
|
surface["colors"] = _put((arrays[Mesh.ARRAY_COLOR] as PackedColorArray).to_byte_array(), (arrays[Mesh.ARRAY_COLOR] as PackedColorArray).size())
|
|
if arrays[Mesh.ARRAY_INDEX] != null:
|
|
surface["indices"] = _put((arrays[Mesh.ARRAY_INDEX] as PackedInt32Array).to_byte_array(), (arrays[Mesh.ARRAY_INDEX] as PackedInt32Array).size())
|
|
var material: Material = mesh.surface_get_material(index)
|
|
if material != null:
|
|
surface["material"] = _material(material, {})
|
|
surfaces.append(surface)
|
|
_meshes.append({"type": mesh.get_class(), "surfaces": surfaces})
|
|
entry["mesh"] = _mesh_ids[id]
|
|
|
|
func _put(bytes: PackedByteArray, count: int) -> Array:
|
|
var offset: int = _blob.size()
|
|
_blob.append_array(bytes)
|
|
return [offset, count]
|
|
|
|
func _material(material: Material, surface_keys: Dictionary) -> int:
|
|
var id: int = material.get_instance_id()
|
|
if _material_ids.has(id):
|
|
return _material_ids[id]
|
|
var entry: Dictionary = {"name": material.resource_name}
|
|
if surface_keys.has(id):
|
|
entry["surface_key"] = surface_keys[id]
|
|
if material is ShaderMaterial:
|
|
var shader_material := material as ShaderMaterial
|
|
entry["type"] = "shader"
|
|
entry["shader"] = shader_material.shader.resource_path
|
|
var params: Dictionary = {}
|
|
for uniform: Dictionary in shader_material.shader.get_shader_uniform_list():
|
|
var value: Variant = shader_material.get_shader_parameter(uniform.name)
|
|
if value != null:
|
|
params[uniform.name] = _value(value)
|
|
entry["params"] = params
|
|
elif material is StandardMaterial3D:
|
|
var standard := material as StandardMaterial3D
|
|
entry["type"] = "standard"
|
|
entry["albedo"] = _value(standard.albedo_color)
|
|
entry["roughness"] = standard.roughness
|
|
entry["metallic"] = standard.metallic
|
|
entry["unshaded"] = standard.shading_mode == BaseMaterial3D.SHADING_MODE_UNSHADED
|
|
entry["transparent"] = standard.transparency != BaseMaterial3D.TRANSPARENCY_DISABLED
|
|
entry["vertex_color_albedo"] = standard.vertex_color_use_as_albedo
|
|
if standard.emission_enabled:
|
|
entry["emission"] = _value(standard.emission)
|
|
entry["emission_energy"] = standard.emission_energy_multiplier
|
|
else:
|
|
entry["type"] = material.get_class()
|
|
_material_ids[id] = _materials.size()
|
|
_materials.append(entry)
|
|
return _material_ids[id]
|
|
|
|
func _value(value: Variant) -> Variant:
|
|
if value is Color:
|
|
return [value.r, value.g, value.b, value.a]
|
|
if value is Vector2:
|
|
return [value.x, value.y]
|
|
if value is Vector3:
|
|
return [value.x, value.y, value.z]
|
|
if value is Texture:
|
|
return (value as Texture).resource_path
|
|
return value
|
|
|
|
func _transform(t: Transform3D) -> Array:
|
|
# Column-major basis (x, y, z axes) and origin.
|
|
return [t.basis.x.x, t.basis.x.y, t.basis.x.z, t.basis.y.x, t.basis.y.y, t.basis.y.z,
|
|
t.basis.z.x, t.basis.z.y, t.basis.z.z, t.origin.x, t.origin.y, t.origin.z]
|
|
|
|
func _terrain(terrain: LuneTerrain) -> Dictionary:
|
|
var outer: Dictionary = terrain._grid(LuneTerrain.OUTER_EXTENT, LuneTerrain.OUTER_STEP, LuneTerrain.OUTER_HOLE)
|
|
return {
|
|
"inner": {"extent": LuneTerrain.EXTENT, "step": LuneTerrain.STEP, "size": terrain.size, "hole": 0.0,
|
|
"ground": _put(terrain.ground.to_byte_array(), terrain.ground.size()), "walk": _put(terrain.walk.to_byte_array(), terrain.walk.size()),
|
|
"colors": _put(terrain.colors.to_byte_array(), terrain.colors.size())},
|
|
"outer": {"extent": LuneTerrain.OUTER_EXTENT, "step": LuneTerrain.OUTER_STEP, "size": outer.size, "hole": LuneTerrain.OUTER_HOLE,
|
|
"ground": _put((outer.ground as PackedFloat32Array).to_byte_array(), (outer.ground as PackedFloat32Array).size()),
|
|
"colors": _put((outer.colors as PackedColorArray).to_byte_array(), (outer.colors as PackedColorArray).size())},
|
|
}
|
|
|
|
## Height a figure stands on (terrain walk height plus plaza, dais and grotto lifts).
|
|
func _stand() -> Dictionary:
|
|
var n: int = int(round(STAND_EXTENT * 2.0 / STAND_STEP)) + 1
|
|
var heights := PackedFloat32Array()
|
|
heights.resize(n * n)
|
|
for j: int in range(n):
|
|
for i: int in range(n):
|
|
heights[j * n + i] = _world.height_at(Vector2(-STAND_EXTENT + i * STAND_STEP, -STAND_EXTENT + j * STAND_STEP))
|
|
return {"extent": STAND_EXTENT, "step": STAND_STEP, "size": n, "heights": _put(heights.to_byte_array(), heights.size())}
|
|
|
|
func _lights() -> Dictionary:
|
|
var result: Dictionary = {}
|
|
for child: Node in _world.get_children():
|
|
if child is DirectionalLight3D:
|
|
var light := child as DirectionalLight3D
|
|
result[str(light.name) if light == _world.sun else "fill"] = {"rotation_degrees": _value(light.rotation_degrees),
|
|
"color": _value(light.light_color), "energy": light.light_energy, "shadow": light.shadow_enabled}
|
|
elif child is WorldEnvironment:
|
|
var environment: Environment = (child as WorldEnvironment).environment
|
|
result["environment"] = {"ambient_source": environment.ambient_light_source, "ambient_color": _value(environment.ambient_light_color),
|
|
"ambient_energy": environment.ambient_light_energy, "background_mode": environment.background_mode,
|
|
"background_color": _value(environment.background_color), "tonemap_mode": environment.tonemap_mode,
|
|
"tonemap_exposure": environment.tonemap_exposure, "tonemap_white": environment.tonemap_white}
|
|
return result
|