победа над водопадами, шейдеры m2
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user