победа над водопадами, шейдеры m2

This commit is contained in:
2026-07-07 10:45:43 +04:00
parent 8738c2495d
commit 44614a79e4
38 changed files with 4820 additions and 192 deletions
@@ -61,6 +61,8 @@ max_concurrent_tile_tasks = 1
tile_lod_remove_ops_per_tick = 1
m2_build_groups_per_tick = 1
m2_multimesh_batch_size = 64
m2_animated_denylist_patterns = PackedStringArray("gryphonroost")
m2_animated_allowlist_patterns = PackedStringArray("creature/fish/", "creature/eagle/", "world/critter/")
wmo_render_group_ops_per_tick = 16
cached_tile_mesh_limit = 48
terrain_quality_mesh_cache_limit = 48
@@ -85,13 +87,14 @@ m2_tile_radius = 3
wmo_tile_radius = 5
m2_visibility_range = 1600.0
wmo_visibility_range = 3600.0
debug_streaming = true
hitch_profiler_enabled = true
[node name="ThirdPersonPlayer" type="CharacterBody3D" parent="." unique_id=502573687]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 22666, 80, 15200)
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 16800, 80, 26400)
script = ExtResource("2_player")
spawn_tile_x = 31
spawn_tile_y = 31
spawn_tile_y = 49
[node name="CollisionShape3D" type="CollisionShape3D" parent="ThirdPersonPlayer" unique_id=1297880621]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.05, 0)
+245
View File
@@ -0,0 +1,245 @@
extends Node
class_name M2NativeAnimator
var mesh_instance_path: NodePath = NodePath("../Mesh")
var mesh_instance: MeshInstance3D
var mesh: ArrayMesh
var bones: Array = []
var surfaces: Array = []
var animation_length: float = 0.0
var playback_speed: float = 1.0
var _time: float = 0.0
var _materials: Array[Material] = []
var _unique_mesh_ready := false
var _prepared := false
func setup(target_mesh_instance: MeshInstance3D, bone_data: Array, surface_data: Array, length_seconds: float) -> void:
mesh_instance = target_mesh_instance
if mesh_instance != null:
mesh_instance_path = get_path_to(mesh_instance)
mesh = mesh_instance.mesh as ArrayMesh
bones = bone_data
surfaces = surface_data
animation_length = maxf(length_seconds, 0.0)
_capture_materials()
_make_mesh_unique()
_rebuild_mesh(0.0)
set_process(mesh != null and not bones.is_empty() and not surfaces.is_empty() and animation_length > 0.0)
func _ready() -> void:
prepare_runtime()
func _process(delta: float) -> void:
if mesh == null or bones.is_empty() or surfaces.is_empty() or animation_length <= 0.0:
return
_time = fmod(_time + delta * playback_speed, animation_length)
_rebuild_mesh(_time)
func set_phase(phase: float) -> void:
if animation_length <= 0.0:
_time = 0.0
else:
_time = fposmod(animation_length * phase, animation_length)
_rebuild_mesh(_time)
func prepare_runtime() -> bool:
_resolve_mesh_instance()
_capture_materials()
_unique_mesh_ready = false
_make_mesh_unique()
_rebuild_mesh(_time)
_prepared = mesh != null and not bones.is_empty() and not surfaces.is_empty() and animation_length > 0.0
set_process(_prepared)
return _prepared
func runtime_debug_state() -> Dictionary:
return {
"prepared": _prepared,
"processing": is_processing(),
"has_mesh": mesh != null,
"bones": bones.size(),
"surfaces": surfaces.size(),
"length": animation_length,
}
func _resolve_mesh_instance() -> void:
var resolved := get_node_or_null(mesh_instance_path)
if resolved is MeshInstance3D:
mesh_instance = resolved as MeshInstance3D
if mesh_instance != null:
mesh = mesh_instance.mesh as ArrayMesh
func _make_mesh_unique() -> void:
if _unique_mesh_ready or mesh_instance == null or mesh_instance.mesh == null:
return
var duplicated := mesh_instance.mesh.duplicate(true) as ArrayMesh
if duplicated == null:
return
mesh_instance.mesh = duplicated
mesh = duplicated
_unique_mesh_ready = true
func _capture_materials() -> void:
if mesh == null:
return
if not _materials.is_empty() and _materials.size() == mesh.get_surface_count():
return
_materials.clear()
for surface_index in mesh.get_surface_count():
_materials.append(mesh.surface_get_material(surface_index))
func _rebuild_mesh(time: float) -> void:
if mesh == null:
return
var bone_matrices := _build_bone_matrices(time)
if bone_matrices.is_empty():
return
mesh.clear_surfaces()
for surface_index in surfaces.size():
var source = surfaces[surface_index]
if not (source is Dictionary):
continue
var surface: Dictionary = source
var base_vertices: PackedVector3Array = surface.get("vertices", PackedVector3Array())
var base_normals: PackedVector3Array = surface.get("normals", PackedVector3Array())
var uvs: PackedVector2Array = surface.get("uvs", PackedVector2Array())
var uvs2: PackedVector2Array = surface.get("uvs2", PackedVector2Array())
var indices: PackedInt32Array = surface.get("indices", PackedInt32Array())
var bone_indices: PackedInt32Array = surface.get("bones", PackedInt32Array())
var weights: PackedFloat32Array = surface.get("weights", PackedFloat32Array())
if base_vertices.is_empty() or indices.is_empty():
continue
var vertices := PackedVector3Array()
var normals := PackedVector3Array()
vertices.resize(base_vertices.size())
if base_normals.size() == base_vertices.size():
normals.resize(base_normals.size())
var can_skin := bone_indices.size() == base_vertices.size() * 4 and weights.size() == base_vertices.size() * 4
for vertex_index in base_vertices.size():
if can_skin:
var skinned_pos := Vector3.ZERO
var skinned_nrm := Vector3.ZERO
var total_weight := 0.0
for influence in 4:
var weight := weights[vertex_index * 4 + influence]
if weight <= 0.0:
continue
var bone_index := bone_indices[vertex_index * 4 + influence]
if bone_index < 0 or bone_index >= bone_matrices.size():
continue
var transform: Transform3D = bone_matrices[bone_index]
skinned_pos += transform * base_vertices[vertex_index] * weight
if normals.size() == base_normals.size():
skinned_nrm += (transform.basis * base_normals[vertex_index]) * weight
total_weight += weight
if total_weight > 0.0:
vertices[vertex_index] = skinned_pos / total_weight
if normals.size() == base_normals.size():
normals[vertex_index] = (skinned_nrm / total_weight).normalized()
else:
vertices[vertex_index] = base_vertices[vertex_index]
if normals.size() == base_normals.size():
normals[vertex_index] = base_normals[vertex_index]
else:
vertices[vertex_index] = base_vertices[vertex_index]
if normals.size() == base_normals.size():
normals[vertex_index] = base_normals[vertex_index]
var arrays := []
arrays.resize(Mesh.ARRAY_MAX)
arrays[Mesh.ARRAY_VERTEX] = vertices
if normals.size() == vertices.size():
arrays[Mesh.ARRAY_NORMAL] = normals
if uvs.size() == vertices.size():
arrays[Mesh.ARRAY_TEX_UV] = uvs
if uvs2.size() == vertices.size():
arrays[Mesh.ARRAY_TEX_UV2] = uvs2
arrays[Mesh.ARRAY_INDEX] = indices
mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays)
if surface_index < _materials.size() and _materials[surface_index] != null:
mesh.surface_set_material(mesh.get_surface_count() - 1, _materials[surface_index])
func _build_bone_matrices(time: float) -> Array[Transform3D]:
var result: Array[Transform3D] = []
result.resize(bones.size())
for i in range(bones.size()):
var bone: Dictionary = bones[i]
var pivot: Vector3 = bone.get("pivot", Vector3.ZERO)
var translation := _sample_vec3_track(bone.get("translation", {}), time, Vector3.ZERO)
var rotation := _sample_quat_track(bone.get("rotation", {}), time, Quaternion.IDENTITY)
var scale := _sample_vec3_track(bone.get("scale", {}), time, Vector3.ONE)
var local := Transform3D(Basis.IDENTITY, pivot + translation)
local = local * Transform3D(Basis(rotation).scaled(scale), Vector3.ZERO)
local = local * Transform3D(Basis.IDENTITY, -pivot)
var parent := int(bone.get("parent", -1))
if parent >= 0 and parent < i:
result[i] = result[parent] * local
else:
result[i] = local
return result
func _sample_vec3_track(track: Variant, time: float, fallback: Vector3) -> Vector3:
if not (track is Dictionary):
return fallback
var times: PackedFloat32Array = track.get("times", PackedFloat32Array())
var values: PackedVector3Array = track.get("values", PackedVector3Array())
var index := _track_index(times, time)
if index < 0 or values.is_empty():
return fallback
if index >= values.size() - 1 or index >= times.size() - 1:
return values[mini(index, values.size() - 1)]
var t0 := times[index]
var t1 := times[index + 1]
var alpha := 0.0 if is_equal_approx(t0, t1) else clampf((time - t0) / (t1 - t0), 0.0, 1.0)
return values[index].lerp(values[index + 1], alpha)
func _sample_quat_track(track: Variant, time: float, fallback: Quaternion) -> Quaternion:
if not (track is Dictionary):
return fallback
var times: PackedFloat32Array = track.get("times", PackedFloat32Array())
var raw_values: PackedVector4Array = track.get("values", PackedVector4Array())
var index := _track_index(times, time)
if index < 0 or raw_values.is_empty():
return fallback
var q0 := _quat_from_vec4(raw_values[mini(index, raw_values.size() - 1)])
if index >= raw_values.size() - 1 or index >= times.size() - 1:
return q0.normalized()
var q1 := _quat_from_vec4(raw_values[index + 1])
var t0 := times[index]
var t1 := times[index + 1]
var alpha := 0.0 if is_equal_approx(t0, t1) else clampf((time - t0) / (t1 - t0), 0.0, 1.0)
return q0.slerp(q1, alpha).normalized()
func _track_index(times: PackedFloat32Array, time: float) -> int:
if times.is_empty():
return -1
if times.size() == 1 or time <= times[0]:
return 0
for i in range(times.size() - 1):
if time >= times[i] and time < times[i + 1]:
return i
return times.size() - 1
func _quat_from_vec4(v: Vector4) -> Quaternion:
return Quaternion(v.x, v.y, v.z, v.w).normalized()
@@ -0,0 +1 @@
uid://b8pxshlr85g2t
File diff suppressed because it is too large Load Diff