архитектура и цели для агентов
This commit is contained in:
@@ -12,11 +12,11 @@ const WMO_BUILDER_SCRIPT := preload("res://addons/mpq_extractor/loaders/wmo_buil
|
||||
const M2_BUILDER_SCRIPT := preload("res://addons/mpq_extractor/loaders/m2_builder.gd")
|
||||
const M2_NATIVE_ANIMATED_BUILDER_SCRIPT := preload("res://addons/mpq_extractor/loaders/m2_native_animated_builder.gd")
|
||||
const M2_NATIVE_ANIMATOR_SCRIPT := preload("res://src/scenes/streaming/m2_native_animator.gd")
|
||||
const REQUIRED_BAKED_TILE_FORMAT_VERSION := 4
|
||||
const REQUIRED_BAKED_TILE_FORMAT_VERSION := 5
|
||||
const REQUIRED_SPLAT_TILE_FORMAT_VERSION := 1
|
||||
const REQUIRED_CONTROL_SPLAT_TILE_FORMAT_VERSION := 3
|
||||
const M2_MATERIAL_REFRESH_VERSION := 1
|
||||
const WMO_MATERIAL_REFRESH_VERSION := 3
|
||||
const M2_MATERIAL_REFRESH_VERSION := 2
|
||||
const WMO_MATERIAL_REFRESH_VERSION := 10
|
||||
|
||||
const TILE_SIZE := 533.33333
|
||||
const CHUNK_SIZE := TILE_SIZE / 16.0
|
||||
@@ -183,9 +183,11 @@ var _m2_group_result_mutex := Mutex.new()
|
||||
var _m2_group_result_queue: Array = []
|
||||
var _m2_build_jobs: Dictionary = {}
|
||||
var _m2_build_queue: Array = []
|
||||
var _m2_unique_registry: Dictionary = {}
|
||||
var _m2_mesh_cache: Dictionary = {}
|
||||
var _m2_mesh_load_requests: Dictionary = {}
|
||||
var _m2_mesh_finalize_queue: Array = []
|
||||
var _m2_runtime_rebuild_required_cache: Dictionary = {}
|
||||
var _m2_animation_load_requests: Dictionary = {}
|
||||
var _m2_animation_finalize_queue: Array = []
|
||||
var _wmo_build_jobs: Dictionary = {}
|
||||
@@ -2066,6 +2068,7 @@ func _wait_for_tile_tasks() -> void:
|
||||
ResourceLoader.load_threaded_get(path)
|
||||
_m2_mesh_load_requests.clear()
|
||||
_m2_mesh_finalize_queue.clear()
|
||||
_m2_runtime_rebuild_required_cache.clear()
|
||||
|
||||
for pending in _m2_animation_load_requests.values():
|
||||
var path: String = String(pending.get("path", ""))
|
||||
@@ -2219,6 +2222,8 @@ func _finalize_loaded_tile(request: Dictionary, data: Dictionary) -> void:
|
||||
"m2_names": data.get("m2_names", PackedStringArray()),
|
||||
"m2_placements": data.get("m2_placements", []),
|
||||
"m2_built": false,
|
||||
"m2_unique_keys": [],
|
||||
"m2_skipped_unique_keys": [],
|
||||
"wmo_names": data.get("wmo_names", PackedStringArray()),
|
||||
"wmo_placements": data.get("wmo_placements", []),
|
||||
"wmo_refs": [],
|
||||
@@ -2295,6 +2300,8 @@ func _finalize_loaded_baked_tile(request: Dictionary, baked_tile: Resource) -> v
|
||||
"m2_names": baked_m2_names,
|
||||
"m2_placements": baked_m2_placements,
|
||||
"m2_built": false,
|
||||
"m2_unique_keys": [],
|
||||
"m2_skipped_unique_keys": [],
|
||||
"wmo_names": baked_wmo_names,
|
||||
"wmo_placements": baked_wmo_placements,
|
||||
"wmo_refs": [],
|
||||
@@ -2370,6 +2377,8 @@ func _finalize_loaded_streaming_tile(request: Dictionary, stream_tile: Resource)
|
||||
"m2_names": stream_m2_names,
|
||||
"m2_placements": stream_m2_placements,
|
||||
"m2_built": false,
|
||||
"m2_unique_keys": [],
|
||||
"m2_skipped_unique_keys": [],
|
||||
"wmo_names": stream_wmo_names,
|
||||
"wmo_placements": stream_wmo_placements,
|
||||
"wmo_refs": [],
|
||||
@@ -2658,6 +2667,7 @@ func _release_tile(key: String) -> void:
|
||||
|
||||
var state: Dictionary = _tile_states[key]
|
||||
_unregister_tile_wmos(state)
|
||||
_remove_tile_m2_assets(state)
|
||||
_terrain_upgrade_tasks.erase(key)
|
||||
_terrain_control_splat_cache_tasks.erase(key)
|
||||
_terrain_splat_cache_tasks.erase(key)
|
||||
@@ -2708,8 +2718,10 @@ func _clear_streamed_world() -> void:
|
||||
for key in _m2_build_jobs.keys():
|
||||
_cancel_m2_build_job(String(key))
|
||||
_m2_build_queue.clear()
|
||||
_m2_unique_registry.clear()
|
||||
_m2_mesh_load_requests.clear()
|
||||
_m2_mesh_finalize_queue.clear()
|
||||
_m2_runtime_rebuild_required_cache.clear()
|
||||
_m2_animation_load_requests.clear()
|
||||
_m2_animation_finalize_queue.clear()
|
||||
_wmo_build_jobs.clear()
|
||||
@@ -3192,12 +3204,17 @@ func _process_detail_asset_key(key: String) -> void:
|
||||
|
||||
if enable_m2_assets and _tile_within_detail_radius(state, _last_focus_pos, m2_tile_radius):
|
||||
if not bool(state.get("m2_built", false)):
|
||||
state["m2_built"] = _request_tile_m2_assets(
|
||||
var m2_request := _request_tile_m2_assets(
|
||||
tile_root as Node3D,
|
||||
String(state.get("key", "")),
|
||||
state.get("origin", Vector3.ZERO),
|
||||
state.get("m2_names", PackedStringArray()),
|
||||
state.get("m2_placements", []))
|
||||
state["m2_built"] = bool(m2_request.get("built", false))
|
||||
if m2_request.has("unique_keys"):
|
||||
state["m2_unique_keys"] = m2_request.get("unique_keys", [])
|
||||
if m2_request.has("skipped_unique_keys"):
|
||||
state["m2_skipped_unique_keys"] = m2_request.get("skipped_unique_keys", [])
|
||||
else:
|
||||
_remove_tile_m2_assets(state)
|
||||
state["m2_built"] = false
|
||||
@@ -3854,19 +3871,121 @@ func _request_tile_m2_assets(
|
||||
key: String,
|
||||
tile_origin: Vector3,
|
||||
m2_names: PackedStringArray,
|
||||
m2_placements: Array) -> bool:
|
||||
m2_placements: Array) -> Dictionary:
|
||||
var result := {
|
||||
"built": false,
|
||||
}
|
||||
if key.is_empty() or m2_names.is_empty() or m2_placements.is_empty():
|
||||
return false
|
||||
return result
|
||||
var existing_m2_root := tile_root.get_node_or_null("M2s")
|
||||
if existing_m2_root != null:
|
||||
return not existing_m2_root.is_queued_for_deletion()
|
||||
result["built"] = not existing_m2_root.is_queued_for_deletion()
|
||||
return result
|
||||
if _m2_group_tasks.has(key) or _m2_build_jobs.has(key):
|
||||
return false
|
||||
return result
|
||||
|
||||
var filtered := _reserve_tile_m2_placements(key, m2_placements)
|
||||
var filtered_placements: Array = filtered.get("placements", [])
|
||||
result["unique_keys"] = filtered.get("unique_keys", [])
|
||||
result["skipped_unique_keys"] = filtered.get("skipped_unique_keys", [])
|
||||
if filtered_placements.is_empty():
|
||||
result["built"] = true
|
||||
return result
|
||||
|
||||
var task_id: int = WorkerThreadPool.add_task(
|
||||
_group_tile_m2_task.bind(key, tile_origin, m2_names, m2_placements))
|
||||
_group_tile_m2_task.bind(key, tile_origin, m2_names, filtered_placements))
|
||||
_m2_group_tasks[key] = task_id
|
||||
return false
|
||||
return result
|
||||
|
||||
|
||||
func _reserve_tile_m2_placements(tile_key: String, m2_placements: Array) -> Dictionary:
|
||||
var filtered_placements: Array = []
|
||||
var unique_keys: Array = []
|
||||
var skipped_unique_keys: Array = []
|
||||
for index in m2_placements.size():
|
||||
var placement_variant = m2_placements[index]
|
||||
if not (placement_variant is Dictionary):
|
||||
continue
|
||||
var placement: Dictionary = placement_variant
|
||||
var unique_key := _m2_unique_key(placement)
|
||||
if unique_key.is_empty():
|
||||
filtered_placements.append(placement)
|
||||
continue
|
||||
if _m2_unique_registry.has(unique_key):
|
||||
var entry: Dictionary = _m2_unique_registry[unique_key]
|
||||
var owner_tile := String(entry.get("tile", ""))
|
||||
if owner_tile == tile_key and unique_keys.has(unique_key):
|
||||
continue
|
||||
if owner_tile != tile_key:
|
||||
if not skipped_unique_keys.has(unique_key):
|
||||
skipped_unique_keys.append(unique_key)
|
||||
continue
|
||||
_m2_unique_registry[unique_key] = {"tile": tile_key}
|
||||
if not unique_keys.has(unique_key):
|
||||
unique_keys.append(unique_key)
|
||||
filtered_placements.append(placement)
|
||||
return {
|
||||
"placements": filtered_placements,
|
||||
"unique_keys": unique_keys,
|
||||
"skipped_unique_keys": skipped_unique_keys,
|
||||
}
|
||||
|
||||
|
||||
func _m2_unique_key(placement: Dictionary) -> String:
|
||||
var uid: int = int(placement.get("unique_id", -1))
|
||||
if uid <= 0:
|
||||
return ""
|
||||
return "uid:%d" % uid
|
||||
|
||||
|
||||
func _release_tile_m2_unique_keys(state: Dictionary, notify_candidates: bool = true) -> void:
|
||||
var tile_key := String(state.get("key", ""))
|
||||
var unique_keys: Array = state.get("m2_unique_keys", [])
|
||||
if tile_key.is_empty() or unique_keys.is_empty():
|
||||
state["m2_unique_keys"] = []
|
||||
state["m2_skipped_unique_keys"] = []
|
||||
return
|
||||
|
||||
var released_keys: Array = []
|
||||
for unique_key_variant in unique_keys:
|
||||
var unique_key := String(unique_key_variant)
|
||||
if unique_key.is_empty() or not _m2_unique_registry.has(unique_key):
|
||||
continue
|
||||
var entry: Dictionary = _m2_unique_registry[unique_key]
|
||||
if String(entry.get("tile", "")) != tile_key:
|
||||
continue
|
||||
_m2_unique_registry.erase(unique_key)
|
||||
released_keys.append(unique_key)
|
||||
|
||||
state["m2_unique_keys"] = []
|
||||
state["m2_skipped_unique_keys"] = []
|
||||
if not notify_candidates:
|
||||
return
|
||||
for unique_key in released_keys:
|
||||
_queue_m2_tiles_skipping_unique_key(String(unique_key), tile_key)
|
||||
|
||||
|
||||
func _queue_m2_tiles_skipping_unique_key(unique_key: String, released_tile_key: String) -> void:
|
||||
if unique_key.is_empty():
|
||||
return
|
||||
for tile_key_variant in _tile_states.keys():
|
||||
var tile_key := String(tile_key_variant)
|
||||
if tile_key == released_tile_key:
|
||||
continue
|
||||
var state: Dictionary = _tile_states[tile_key]
|
||||
var skipped_keys: Array = state.get("m2_skipped_unique_keys", [])
|
||||
if not skipped_keys.has(unique_key):
|
||||
continue
|
||||
if not bool(state.get("wanted", false)):
|
||||
continue
|
||||
if not _tile_within_detail_radius(state, _last_focus_pos, m2_tile_radius):
|
||||
continue
|
||||
_cancel_m2_build_job(tile_key)
|
||||
_remove_tile_m2_root(state)
|
||||
state["m2_built"] = false
|
||||
state["m2_skipped_unique_keys"] = []
|
||||
_tile_states[tile_key] = state
|
||||
_enqueue_detail_asset_sync(tile_key)
|
||||
|
||||
|
||||
func _group_tile_m2_task(
|
||||
@@ -4059,6 +4178,9 @@ func _process_m2_build_jobs() -> void:
|
||||
var state: Dictionary = _tile_states[key]
|
||||
if not bool(state.get("wanted", false)) or not _tile_within_detail_radius(state, _last_focus_pos, m2_tile_radius):
|
||||
_cancel_m2_build_job(key)
|
||||
_release_tile_m2_unique_keys(state)
|
||||
state["m2_built"] = false
|
||||
_tile_states[key] = state
|
||||
_m2_build_queue.pop_front()
|
||||
continue
|
||||
|
||||
@@ -4314,6 +4436,11 @@ func _remove_tile_m2_assets(state: Dictionary) -> void:
|
||||
var key := String(state.get("key", ""))
|
||||
if not key.is_empty():
|
||||
_cancel_m2_build_job(key)
|
||||
_release_tile_m2_unique_keys(state)
|
||||
_remove_tile_m2_root(state)
|
||||
|
||||
|
||||
func _remove_tile_m2_root(state: Dictionary) -> void:
|
||||
var tile_root: Node = state.get("root", null)
|
||||
if tile_root == null or not is_instance_valid(tile_root):
|
||||
return
|
||||
@@ -4403,7 +4530,11 @@ func _prepare_m2_mesh_for_runtime(normalized_rel: String, mesh: Mesh) -> Mesh:
|
||||
return null
|
||||
if int(mesh.get_meta("wow_m2_material_refresh_version", 0)) >= M2_MATERIAL_REFRESH_VERSION:
|
||||
return mesh
|
||||
var rebuilt := _rebuild_m2_mesh_from_raw(normalized_rel)
|
||||
var data := _load_m2_raw_data_for_refresh(normalized_rel)
|
||||
if data.is_empty() or not _m2_raw_data_needs_runtime_mesh_rebuild(normalized_rel, data):
|
||||
mesh.set_meta("wow_m2_material_refresh_version", M2_MATERIAL_REFRESH_VERSION)
|
||||
return mesh
|
||||
var rebuilt := _rebuild_m2_mesh_from_data(data)
|
||||
if rebuilt != null:
|
||||
return rebuilt
|
||||
mesh.set_meta("wow_m2_material_refresh_version", M2_MATERIAL_REFRESH_VERSION)
|
||||
@@ -4421,16 +4552,67 @@ func _prepare_m2_node_for_runtime(normalized_rel: String, root: Node3D) -> void:
|
||||
mesh_instance.mesh = prepared
|
||||
|
||||
|
||||
func _rebuild_m2_mesh_from_raw(normalized_rel: String) -> Mesh:
|
||||
func _load_m2_raw_data_for_refresh(normalized_rel: String) -> Dictionary:
|
||||
if normalized_rel.is_empty() or not ClassDB.class_exists("M2Loader"):
|
||||
return null
|
||||
return {}
|
||||
var abs_path := ProjectSettings.globalize_path(extracted_dir.path_join(normalized_rel))
|
||||
if not FileAccess.file_exists(abs_path):
|
||||
return null
|
||||
return {}
|
||||
var loader = ClassDB.instantiate("M2Loader")
|
||||
if loader == null:
|
||||
return null
|
||||
var data: Dictionary = loader.call("load_m2", abs_path)
|
||||
return {}
|
||||
var data: Variant = loader.call("load_m2", abs_path)
|
||||
return data if data is Dictionary else {}
|
||||
|
||||
|
||||
func _m2_raw_data_needs_runtime_mesh_rebuild(normalized_rel: String, data: Dictionary) -> bool:
|
||||
if _m2_runtime_rebuild_required_cache.has(normalized_rel):
|
||||
return bool(_m2_runtime_rebuild_required_cache[normalized_rel])
|
||||
var needs_rebuild := _m2_raw_data_has_billboards(data) or _m2_raw_data_has_uv_rotation(data)
|
||||
_m2_runtime_rebuild_required_cache[normalized_rel] = needs_rebuild
|
||||
return needs_rebuild
|
||||
|
||||
|
||||
func _m2_raw_data_has_billboards(data: Dictionary) -> bool:
|
||||
for batch_variant in data.get("batches", []):
|
||||
if not (batch_variant is Dictionary):
|
||||
continue
|
||||
var batch: Dictionary = batch_variant
|
||||
if bool(batch.get("has_billboard", false)) or int(batch.get("billboard_vertex_count", 0)) > 0:
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
func _m2_raw_data_has_uv_rotation(data: Dictionary) -> bool:
|
||||
var transforms: Array = data.get("texture_transforms", [])
|
||||
if transforms.is_empty():
|
||||
return false
|
||||
var combos: PackedInt32Array = data.get("texture_transform_combos", PackedInt32Array())
|
||||
if combos.is_empty():
|
||||
return false
|
||||
for batch_variant in data.get("batches", []):
|
||||
if not (batch_variant is Dictionary):
|
||||
continue
|
||||
var batch: Dictionary = batch_variant
|
||||
var texture_count := maxi(1, int(batch.get("texture_count", 1)))
|
||||
var base_combo := int(batch.get("texture_transform_combo_index", -1))
|
||||
for stage in mini(texture_count, 4):
|
||||
var combo_index := base_combo + stage
|
||||
if combo_index < 0 or combo_index >= combos.size():
|
||||
continue
|
||||
var transform_index := int(combos[combo_index])
|
||||
if transform_index < 0 or transform_index >= transforms.size():
|
||||
continue
|
||||
var transform: Dictionary = transforms[transform_index] if transforms[transform_index] is Dictionary else {}
|
||||
var rotation: Vector4 = transform.get("rotation", Vector4(1.0, 0.0, 0.0, 1.0))
|
||||
if not rotation.is_equal_approx(Vector4(1.0, 0.0, 0.0, 1.0)):
|
||||
return true
|
||||
if absf(float(transform.get("rotation_speed", 0.0))) > 0.000001:
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
func _rebuild_m2_mesh_from_data(data: Dictionary) -> Mesh:
|
||||
if data.is_empty():
|
||||
return null
|
||||
var prototype: Node3D = M2_BUILDER_SCRIPT.build(data, extracted_dir)
|
||||
|
||||
Reference in New Issue
Block a user