2V2-e.5b: feed native map height and light into game render

This commit is contained in:
shenlei
2026-09-23 22:10:29 +09:00
parent 702fef7b47
commit 5a1df57e69
13 changed files with 678 additions and 76 deletions
+15 -33
View File
@@ -30,7 +30,6 @@ var map_path := "metin2_map_a1"
var _atlas: Array = []
var _terrain_attempted := false
var _last_terrain_focus := Vector2(INF, INF)
var _ground_offset_cm := NAN
var _meshes: Array[MeshInstance3D] = []
# Materials keyed by texture and state, so an unchanged draw keeps its material from frame to frame.
var _materials := {}
@@ -84,6 +83,13 @@ static func multiply(a: PackedFloat32Array, b: PackedFloat32Array) -> PackedFloa
func update_frame() -> void:
var draws: Array = Metin2PythonHost.render3d_draws()
var native_map: String = Metin2PythonHost.current_map_name()
if terrain != null and not native_map.is_empty() and native_map != map_path:
terrain.queue_free()
terrain = null
terrain_report = {}
_terrain_attempted = false
_last_terrain_focus = Vector2(INF, INF)
var shown := 0
var camera_set := false
var light_set := false
@@ -106,9 +112,6 @@ func update_frame() -> void:
if not light_set and draw["light0"]:
_apply_light(draw, view)
light_set = true
elif not light_set and not terrain_report.is_empty():
_apply_map_light(view)
light_set = light.visible
var instance := _mesh_instance(shown)
instance.transform = d3d_transform(multiply(draw["world"], view))
instance.mesh = _build_mesh(draw)
@@ -130,7 +133,8 @@ func _load_terrain(draw: Dictionary) -> void:
if focus.distance_to(_last_terrain_focus) < 1000.0:
return
_last_terrain_focus = focus
map_path = _map_for_position(focus)
var native_map: String = Metin2PythonHost.current_map_name()
map_path = native_map if not native_map.is_empty() else _map_for_position(focus)
terrain = ClassDB.instantiate("Metin2World")
terrain.name = "PackTerrain"
terrain.set("auto_load", false)
@@ -142,7 +146,9 @@ func _load_terrain(draw: Dictionary) -> void:
terrain.call("set_collision_enabled", false)
# A skinned mesh's world matrix can be local to a bone. The inverse view
# locates the camera beside the actor and selects the correct 3x3 map tiles.
terrain.call("set_initial_focus_cm", focus)
# Native RenderGame coordinates are map-local; Metin2World's initial_focus_cm
# expects global AtlasInfo coordinates, so set the local tile directly.
terrain.call("set_focus_tile", Vector2i(int(floor(focus.x / 25600.0)), int(floor(focus.y / 25600.0))))
add_child(terrain)
if not terrain.call("load_map"):
terrain_report = terrain.call("get_load_report")
@@ -159,7 +165,6 @@ func _load_terrain(draw: Dictionary) -> void:
terrain_report = {}
return
_terrain_attempted = true
_ground_offset_cm = NAN
# The map's own character light uses legacy Godot coordinates; the recorded game pass
# needs one light in camera space, derived from the same .msenv values below.
var map_character_light := terrain.get_node_or_null("CharacterLight") as DirectionalLight3D
@@ -190,37 +195,14 @@ func _map_for_position(global_cm: Vector2) -> String:
return map_path
func _place_terrain(view: PackedFloat32Array) -> void:
var base: Vector2 = terrain.call("get_map_base_cm")
var camera_world := d3d_transform(view).affine_inverse().origin
var local_x := (camera_world.x - base.x) / 100.0
var local_y := (-camera_world.y - base.y) / 100.0
var local_x := camera_world.x / 100.0
var local_y := -camera_world.y / 100.0
terrain.call("set_focus_position", local_x, local_y)
# Until the native CMapManager::GetHeight path is ported, the actor enters at
# z=0. Align the map once to its actual sampled starting elevation.
if is_nan(_ground_offset_cm):
_ground_offset_cm = float(terrain.call("sample_height", local_x, local_y)) * 100.0
var from_map := Transform3D(Basis(Vector3(100, 0, 0), Vector3(0, 0, 100),
Vector3(0, -100, 0)), Vector3(base.x, -base.y, -_ground_offset_cm))
Vector3(0, -100, 0)), Vector3.ZERO)
terrain.transform = d3d_transform(view) * from_map
func _apply_map_light(view: PackedFloat32Array) -> void:
if not terrain_report.get("env_ok", false) or not terrain_report.get("character_light_enabled", false):
light.visible = false
return
var direction: Vector3 = d3d_transform(view).basis * terrain_report["character_light_direction"]
if direction.length_squared() < 1e-8:
light.visible = false
return
light.visible = true
light.transform = Transform3D(Basis.looking_at(direction.normalized(),
Vector3.UP if absf(direction.normalized().y) < 0.99 else Vector3.RIGHT), Vector3.ZERO)
light.light_color = terrain_report["character_light_diffuse"]
light.light_energy = 1.0
var world_env := terrain.get_node_or_null("WorldEnv") as WorldEnvironment
if world_env and world_env.environment:
world_env.environment.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR
world_env.environment.ambient_light_color = terrain_report["character_light_ambient"]
func _apply_projection(proj: PackedFloat32Array) -> void:
# D3DXMatrixPerspectiveFovRH: _22 = cot(fovy / 2), _33 = zf / (zn - zf), _43 = zn * zf / (zn - zf).
camera.fov = rad_to_deg(2.0 * atan(1.0 / proj[5]))