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= ## (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