первые наработки по рендеру
This commit is contained in:
@@ -19,10 +19,9 @@ update_interval = 0.1
|
||||
max_concurrent_tile_tasks = 4
|
||||
chunk_ops_per_tick = 64
|
||||
cached_tile_mesh_limit = 48
|
||||
use_baked_tile_cache = false
|
||||
editor_follow_view_camera = false
|
||||
enable_water = true
|
||||
terrain_cast_shadows = true
|
||||
m2_cast_shadows = null
|
||||
|
||||
[node name="Camera3D" type="Camera3D" parent="." unique_id=502573687]
|
||||
transform = Transform3D(1, 0, 0, 0, 0.707107, 0.707107, 0, -0.707107, 0.707107, 0, 300, 300)
|
||||
@@ -36,6 +35,9 @@ fast_mult = 8.0
|
||||
transform = Transform3D(0.866025, -0.353553, 0.353553, 0, 0.707107, 0.707107, -0.5, -0.612372, 0.612372, 0, 0, 0)
|
||||
light_energy = 1.5
|
||||
shadow_enabled = true
|
||||
directional_shadow_mode = 1
|
||||
directional_shadow_fade_start = 0.85
|
||||
directional_shadow_max_distance = 2200.0
|
||||
|
||||
[node name="WorldEnvironment" type="WorldEnvironment" parent="." unique_id=12906896]
|
||||
environment = SubResource("Environment_1")
|
||||
|
||||
@@ -10,6 +10,7 @@ const REQUIRED_BAKED_TILE_FORMAT_VERSION := 4
|
||||
|
||||
const TILE_SIZE := 533.33333
|
||||
const CHUNK_SIZE := TILE_SIZE / 16.0
|
||||
const M2_RIGHT_YAW_OFFSET := 0.0
|
||||
|
||||
@export var extracted_dir: String = "res://data/extracted"
|
||||
@export var map_name: String = "Azeroth"
|
||||
@@ -51,6 +52,14 @@ const CHUNK_SIZE := TILE_SIZE / 16.0
|
||||
@export var enable_m2_assets: bool = true
|
||||
@export var enable_m2_placeholders: bool = false
|
||||
@export var m2_placeholder_only_trees: bool = true
|
||||
## Dynamic doodad render radius in ADT tiles. Negative keeps all loaded M2s active.
|
||||
@export var m2_tile_radius: int = 2
|
||||
## Dynamic WMO render radius in ADT tiles. Negative keeps all loaded WMOs active.
|
||||
@export var wmo_tile_radius: int = 4
|
||||
## Godot visibility range applied to instantiated M2 MultiMesh nodes. <= 0 disables it.
|
||||
@export var m2_visibility_range: float = TILE_SIZE * 3.0
|
||||
## Godot visibility range applied to instantiated WMO trees. <= 0 disables it.
|
||||
@export var wmo_visibility_range: float = TILE_SIZE * 5.0
|
||||
@export var m2_cache_dir: String = "res://data/cache/m2_glb"
|
||||
@export var wmo_cache_dir: String = "res://data/cache/wmo_tscn"
|
||||
@export var terrain_cast_shadows: bool = false
|
||||
@@ -61,6 +70,7 @@ const CHUNK_SIZE := TILE_SIZE / 16.0
|
||||
var _builder
|
||||
var _map_dir := ""
|
||||
var _abs_map_dir := ""
|
||||
var _wdt_info: Dictionary = {}
|
||||
var _available_tiles: Dictionary = {}
|
||||
var _tile_states: Dictionary = {}
|
||||
var _tile_load_queue: Array = []
|
||||
@@ -91,6 +101,8 @@ var _wmo_prototype_cache: Dictionary = {}
|
||||
var _wmo_missing_cache: Dictionary = {}
|
||||
var _m2_scene_cache: Dictionary = {}
|
||||
var _m2_missing_cache: Dictionary = {}
|
||||
var _world_wmo_root: Node3D
|
||||
var _wmo_registry: Dictionary = {}
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
@@ -110,10 +122,11 @@ func _ready() -> void:
|
||||
_terrain_root.name = "Terrain"
|
||||
add_child(_terrain_root)
|
||||
_reset_terrain_root_children()
|
||||
_ensure_world_wmo_root()
|
||||
_set_editor_owner_recursive(_terrain_root)
|
||||
|
||||
_scan_available_tiles()
|
||||
if _available_tiles.is_empty():
|
||||
if _available_tiles.is_empty() and not _wdt_has_global_wmo():
|
||||
push_error("No ADT tiles found in %s" % _map_dir)
|
||||
return
|
||||
if _can_use_baked_tile_cache():
|
||||
@@ -121,6 +134,9 @@ func _ready() -> void:
|
||||
if not DirAccess.dir_exists_absolute(baked_dir):
|
||||
push_warning("Baked terrain cache not found: %s. Runtime will fall back to raw ADT builds." % baked_dir)
|
||||
|
||||
if enable_wmo:
|
||||
_place_global_wmo()
|
||||
|
||||
if Engine.is_editor_hint():
|
||||
_tick_editor_streaming()
|
||||
elif auto_position_camera:
|
||||
@@ -185,6 +201,64 @@ func _tick_editor_streaming() -> void:
|
||||
|
||||
|
||||
func _scan_available_tiles() -> void:
|
||||
_available_tiles.clear()
|
||||
_wdt_info = {}
|
||||
|
||||
if _load_tiles_from_wdt():
|
||||
print("StreamingWorld: loaded %d ADT tiles from WDT for %s" % [_available_tiles.size(), map_name])
|
||||
return
|
||||
|
||||
push_warning("StreamingWorld: falling back to directory scan for %s (no WDT)" % map_name)
|
||||
_load_tiles_from_directory()
|
||||
print("StreamingWorld: found %d ADT tiles by directory scan for %s" % [_available_tiles.size(), map_name])
|
||||
|
||||
|
||||
func _load_tiles_from_wdt() -> bool:
|
||||
if not ClassDB.class_exists("WDTLoader"):
|
||||
return false
|
||||
|
||||
var wdt_path_abs := _abs_map_dir.path_join("%s.wdt" % map_name)
|
||||
if not FileAccess.file_exists(wdt_path_abs):
|
||||
return false
|
||||
|
||||
var loader = ClassDB.instantiate("WDTLoader")
|
||||
if loader == null:
|
||||
return false
|
||||
|
||||
var data: Dictionary = loader.call("load_wdt", wdt_path_abs)
|
||||
if data.is_empty():
|
||||
push_warning("StreamingWorld: WDTLoader returned empty data for %s" % wdt_path_abs)
|
||||
return false
|
||||
|
||||
var tiles: Array = data.get("tiles", [])
|
||||
for tile_variant in tiles:
|
||||
if not (tile_variant is Dictionary):
|
||||
continue
|
||||
var entry: Dictionary = tile_variant
|
||||
var tx: int = int(entry.get("x", -1))
|
||||
var ty: int = int(entry.get("y", -1))
|
||||
if tx < 0 or tx > 63 or ty < 0 or ty > 63:
|
||||
continue
|
||||
|
||||
var adt_file := "%s_%d_%d.adt" % [map_name, tx, ty]
|
||||
var adt_abs := _abs_map_dir.path_join(adt_file)
|
||||
# WDT flags the tile as present, but the ADT may still be missing in
|
||||
# a partial extraction. Skip so downstream code never chases a ghost.
|
||||
if not FileAccess.file_exists(adt_abs):
|
||||
continue
|
||||
|
||||
var key := _tile_key(tx, ty)
|
||||
_available_tiles[key] = adt_abs
|
||||
_tile_min.x = min(_tile_min.x, tx)
|
||||
_tile_min.y = min(_tile_min.y, ty)
|
||||
_tile_max.x = max(_tile_max.x, tx)
|
||||
_tile_max.y = max(_tile_max.y, ty)
|
||||
|
||||
_wdt_info = data
|
||||
return not _available_tiles.is_empty()
|
||||
|
||||
|
||||
func _load_tiles_from_directory() -> void:
|
||||
var dir := DirAccess.open(_map_dir)
|
||||
if dir == null:
|
||||
push_error("Cannot open map directory: %s" % _map_dir)
|
||||
@@ -210,8 +284,6 @@ func _scan_available_tiles() -> void:
|
||||
_tile_max.x = max(_tile_max.x, tx)
|
||||
_tile_max.y = max(_tile_max.y, ty)
|
||||
|
||||
print("StreamingWorld: found %d ADT tiles for %s" % [_available_tiles.size(), map_name])
|
||||
|
||||
|
||||
func _refresh_streaming_targets(force: bool) -> void:
|
||||
var camera := _get_stream_camera()
|
||||
@@ -312,6 +384,7 @@ func _apply_streaming_target(wanted_tiles: Dictionary, focus_pos: Vector3) -> vo
|
||||
else:
|
||||
state["desired_lods"] = {}
|
||||
state["desired_tile_lod"] = -1
|
||||
state = _sync_detail_assets(state, focus_pos)
|
||||
_tile_states[key] = state
|
||||
|
||||
_rebuild_chunk_queues()
|
||||
@@ -592,25 +665,12 @@ func _finalize_loaded_tile(request: Dictionary, data: Dictionary) -> void:
|
||||
if water_root:
|
||||
tile_root.add_child(water_root)
|
||||
_set_editor_owner_recursive(water_root)
|
||||
if enable_m2_assets:
|
||||
_build_tile_m2_assets(
|
||||
tile_root,
|
||||
tile_origin,
|
||||
data.get("m2_names", PackedStringArray()),
|
||||
data.get("m2_placements", []))
|
||||
if enable_m2_placeholders:
|
||||
_build_tile_m2_placeholders(
|
||||
tile_root,
|
||||
tile_origin,
|
||||
data.get("m2_names", PackedStringArray()),
|
||||
data.get("m2_placements", []))
|
||||
if enable_wmo:
|
||||
_build_tile_wmos(
|
||||
tile_root,
|
||||
tile_origin,
|
||||
data.get("wmo_names", PackedStringArray()),
|
||||
data.get("wmo_placements", []))
|
||||
|
||||
var state := {
|
||||
"key": key,
|
||||
"tx": request["tx"],
|
||||
@@ -629,11 +689,18 @@ func _finalize_loaded_tile(request: Dictionary, data: Dictionary) -> void:
|
||||
"desired_tile_lod": -1,
|
||||
"wanted": true,
|
||||
"root": tile_root,
|
||||
"m2_names": data.get("m2_names", PackedStringArray()),
|
||||
"m2_placements": data.get("m2_placements", []),
|
||||
"m2_built": false,
|
||||
"wmo_names": data.get("wmo_names", PackedStringArray()),
|
||||
"wmo_placements": data.get("wmo_placements", []),
|
||||
"wmo_refs": [],
|
||||
}
|
||||
|
||||
# Use _last_focus_pos — already set correctly for both editor and game.
|
||||
state["desired_lods"] = _compute_desired_lods(state, _last_focus_pos)
|
||||
state["desired_tile_lod"] = -1 if not state["desired_lods"].is_empty() else _compute_desired_tile_lod(state, _last_focus_pos)
|
||||
state = _sync_detail_assets(state, _last_focus_pos)
|
||||
|
||||
_tile_states[key] = state
|
||||
_rebuild_chunk_queues()
|
||||
@@ -663,27 +730,14 @@ func _finalize_loaded_baked_tile(request: Dictionary, baked_tile: Resource) -> v
|
||||
|
||||
var tile_origin: Vector3 = baked_tile.get("tile_origin")
|
||||
tile_root.position = tile_origin
|
||||
if enable_m2_assets:
|
||||
var baked_m2_names_value: Variant = baked_tile.get("m2_names")
|
||||
var baked_m2_placements_value: Variant = baked_tile.get("m2_placements")
|
||||
var baked_m2_names: PackedStringArray = baked_m2_names_value if baked_m2_names_value is PackedStringArray else PackedStringArray()
|
||||
var baked_m2_placements: Array = baked_m2_placements_value if baked_m2_placements_value is Array else []
|
||||
_build_tile_m2_assets(
|
||||
tile_root,
|
||||
tile_origin,
|
||||
baked_m2_names,
|
||||
baked_m2_placements)
|
||||
if enable_wmo:
|
||||
var baked_wmo_names_value: Variant = baked_tile.get("wmo_names")
|
||||
var baked_wmo_placements_value: Variant = baked_tile.get("wmo_placements")
|
||||
var baked_wmo_names: PackedStringArray = baked_wmo_names_value if baked_wmo_names_value is PackedStringArray else PackedStringArray()
|
||||
var baked_wmo_placements: Array = baked_wmo_placements_value if baked_wmo_placements_value is Array else []
|
||||
_build_tile_wmos(
|
||||
tile_root,
|
||||
tile_origin,
|
||||
baked_wmo_names,
|
||||
baked_wmo_placements)
|
||||
|
||||
var baked_m2_names_value: Variant = baked_tile.get("m2_names")
|
||||
var baked_m2_placements_value: Variant = baked_tile.get("m2_placements")
|
||||
var baked_m2_names: PackedStringArray = baked_m2_names_value if baked_m2_names_value is PackedStringArray else PackedStringArray()
|
||||
var baked_m2_placements: Array = baked_m2_placements_value if baked_m2_placements_value is Array else []
|
||||
var baked_wmo_names_value: Variant = baked_tile.get("wmo_names")
|
||||
var baked_wmo_placements_value: Variant = baked_tile.get("wmo_placements")
|
||||
var baked_wmo_names: PackedStringArray = baked_wmo_names_value if baked_wmo_names_value is PackedStringArray else PackedStringArray()
|
||||
var baked_wmo_placements: Array = baked_wmo_placements_value if baked_wmo_placements_value is Array else []
|
||||
var state := {
|
||||
"key": key,
|
||||
"tx": request["tx"],
|
||||
@@ -702,9 +756,16 @@ func _finalize_loaded_baked_tile(request: Dictionary, baked_tile: Resource) -> v
|
||||
"desired_tile_lod": -1,
|
||||
"wanted": true,
|
||||
"root": tile_root,
|
||||
"m2_names": baked_m2_names,
|
||||
"m2_placements": baked_m2_placements,
|
||||
"m2_built": false,
|
||||
"wmo_names": baked_wmo_names,
|
||||
"wmo_placements": baked_wmo_placements,
|
||||
"wmo_refs": [],
|
||||
}
|
||||
|
||||
state["desired_tile_lod"] = _compute_desired_tile_lod(state, _last_focus_pos)
|
||||
state = _sync_detail_assets(state, _last_focus_pos)
|
||||
_tile_states[key] = state
|
||||
_rebuild_chunk_queues()
|
||||
if debug_streaming:
|
||||
@@ -910,6 +971,7 @@ func _release_tile(key: String) -> void:
|
||||
return
|
||||
|
||||
var state: Dictionary = _tile_states[key]
|
||||
_unregister_tile_wmos(state)
|
||||
var tile_lod_rid: RID = state.get("tile_lod_rid", RID())
|
||||
if tile_lod_rid.is_valid():
|
||||
_free_render_instance(tile_lod_rid)
|
||||
@@ -949,6 +1011,10 @@ func _clear_streamed_world() -> void:
|
||||
|
||||
_tile_states.clear()
|
||||
_clear_tile_mesh_cache()
|
||||
_clear_wmo_registry()
|
||||
_ensure_world_wmo_root()
|
||||
if enable_wmo:
|
||||
_place_global_wmo()
|
||||
|
||||
|
||||
func _reset_terrain_root_children() -> void:
|
||||
@@ -1121,16 +1187,110 @@ func _tile_mesh_cache_key(tile_key: String, lod: int) -> String:
|
||||
return "%s|%d" % [tile_key, lod]
|
||||
|
||||
|
||||
func _build_tile_wmos(tile_root: Node3D, tile_origin: Vector3, wmo_names: PackedStringArray, wmo_placements: Array) -> void:
|
||||
if wmo_names.is_empty() or wmo_placements.is_empty():
|
||||
func _ensure_world_wmo_root() -> void:
|
||||
if _world_wmo_root != null and is_instance_valid(_world_wmo_root):
|
||||
return
|
||||
_world_wmo_root = Node3D.new()
|
||||
_world_wmo_root.name = "WMOs"
|
||||
_terrain_root.add_child(_world_wmo_root)
|
||||
_set_editor_owner_recursive(_world_wmo_root)
|
||||
|
||||
|
||||
func _wdt_has_global_wmo() -> bool:
|
||||
return bool(_wdt_info.get("has_global_wmo", false)) and _wdt_info.has("global_wmo_name")
|
||||
|
||||
|
||||
func _place_global_wmo() -> void:
|
||||
if not _wdt_has_global_wmo():
|
||||
return
|
||||
if _wmo_registry.has("global"):
|
||||
return
|
||||
|
||||
var wmo_root := Node3D.new()
|
||||
wmo_root.name = "WMOs"
|
||||
tile_root.add_child(wmo_root)
|
||||
_set_editor_owner_recursive(wmo_root)
|
||||
_ensure_world_wmo_root()
|
||||
|
||||
for placement_variant in wmo_placements:
|
||||
var rel_path := String(_wdt_info.get("global_wmo_name", "")).replace("\\", "/")
|
||||
if rel_path.is_empty():
|
||||
return
|
||||
|
||||
var placement: Dictionary = _wdt_info.get("global_wmo_placement", {})
|
||||
if placement.is_empty():
|
||||
placement = {
|
||||
"pos": Vector3.ZERO,
|
||||
"rot": Vector3.ZERO,
|
||||
"scale": 1.0,
|
||||
}
|
||||
|
||||
var instance := _instantiate_wmo_world(rel_path, placement)
|
||||
if instance == null:
|
||||
push_warning("StreamingWorld: failed to load global WMO: %s" % rel_path)
|
||||
return
|
||||
|
||||
_world_wmo_root.add_child(instance)
|
||||
_apply_visibility_range_recursive(instance, wmo_visibility_range)
|
||||
_set_editor_owner_recursive(instance)
|
||||
_wmo_registry["global"] = {
|
||||
"node": instance,
|
||||
"refs": {"__global__": true},
|
||||
}
|
||||
|
||||
|
||||
func _sync_detail_assets(state: Dictionary, focus_pos: Vector3) -> Dictionary:
|
||||
if not bool(state.get("wanted", false)):
|
||||
_disable_tile_detail_assets(state)
|
||||
return state
|
||||
|
||||
var tile_root: Node = state.get("root", null)
|
||||
if tile_root == null or not is_instance_valid(tile_root):
|
||||
return state
|
||||
|
||||
if enable_wmo and _tile_within_detail_radius(state, focus_pos, wmo_tile_radius):
|
||||
var refs: Array = state.get("wmo_refs", [])
|
||||
if refs.is_empty():
|
||||
_register_tile_wmos(
|
||||
state,
|
||||
state.get("wmo_names", PackedStringArray()),
|
||||
state.get("wmo_placements", []))
|
||||
else:
|
||||
_unregister_tile_wmos(state)
|
||||
|
||||
if enable_m2_assets and _tile_within_detail_radius(state, focus_pos, m2_tile_radius):
|
||||
if not bool(state.get("m2_built", false)):
|
||||
state["m2_built"] = _build_tile_m2_assets(
|
||||
tile_root as Node3D,
|
||||
state.get("origin", Vector3.ZERO),
|
||||
state.get("m2_names", PackedStringArray()),
|
||||
state.get("m2_placements", []))
|
||||
else:
|
||||
_remove_tile_m2_assets(state)
|
||||
state["m2_built"] = false
|
||||
|
||||
return state
|
||||
|
||||
|
||||
func _disable_tile_detail_assets(state: Dictionary) -> void:
|
||||
_unregister_tile_wmos(state)
|
||||
_remove_tile_m2_assets(state)
|
||||
state["m2_built"] = false
|
||||
|
||||
|
||||
func _tile_within_detail_radius(state: Dictionary, focus_pos: Vector3, radius_tiles: int) -> bool:
|
||||
if radius_tiles < 0:
|
||||
return true
|
||||
var radius_sq := (float(radius_tiles) * TILE_SIZE) ** 2
|
||||
return _tile_dist_sq(state, focus_pos) <= radius_sq
|
||||
|
||||
|
||||
func _register_tile_wmos(state: Dictionary, wmo_names: PackedStringArray, wmo_placements: Array) -> void:
|
||||
var refs: Array = state.get("wmo_refs", [])
|
||||
if wmo_names.is_empty() or wmo_placements.is_empty():
|
||||
state["wmo_refs"] = refs
|
||||
return
|
||||
|
||||
_ensure_world_wmo_root()
|
||||
var tile_key: String = state["key"]
|
||||
|
||||
for i in wmo_placements.size():
|
||||
var placement_variant = wmo_placements[i]
|
||||
if not (placement_variant is Dictionary):
|
||||
continue
|
||||
var placement: Dictionary = placement_variant
|
||||
@@ -1138,17 +1298,83 @@ func _build_tile_wmos(tile_root: Node3D, tile_origin: Vector3, wmo_names: Packed
|
||||
if name_id < 0 or name_id >= wmo_names.size():
|
||||
continue
|
||||
|
||||
var unique_key := _wmo_unique_key(placement, tile_key, i)
|
||||
|
||||
if _wmo_registry.has(unique_key):
|
||||
var entry: Dictionary = _wmo_registry[unique_key]
|
||||
var entry_refs: Dictionary = entry["refs"]
|
||||
entry_refs[tile_key] = true
|
||||
refs.append(unique_key)
|
||||
continue
|
||||
|
||||
var rel_path: String = str(wmo_names[name_id]).replace("\\", "/")
|
||||
var instance := _instantiate_wmo(rel_path, placement, tile_origin)
|
||||
var instance := _instantiate_wmo_world(rel_path, placement)
|
||||
if instance == null:
|
||||
continue
|
||||
wmo_root.add_child(instance)
|
||||
|
||||
_world_wmo_root.add_child(instance)
|
||||
_apply_visibility_range_recursive(instance, wmo_visibility_range)
|
||||
_set_editor_owner_recursive(instance)
|
||||
_wmo_registry[unique_key] = {
|
||||
"node": instance,
|
||||
"refs": {tile_key: true},
|
||||
}
|
||||
refs.append(unique_key)
|
||||
|
||||
state["wmo_refs"] = refs
|
||||
|
||||
|
||||
func _build_tile_m2_assets(tile_root: Node3D, tile_origin: Vector3, m2_names: PackedStringArray, m2_placements: Array) -> void:
|
||||
if m2_names.is_empty() or m2_placements.is_empty():
|
||||
func _unregister_tile_wmos(state: Dictionary) -> void:
|
||||
var refs: Array = state.get("wmo_refs", [])
|
||||
if refs.is_empty():
|
||||
return
|
||||
var tile_key: String = state["key"]
|
||||
|
||||
for unique_key in refs:
|
||||
if not _wmo_registry.has(unique_key):
|
||||
continue
|
||||
var entry: Dictionary = _wmo_registry[unique_key]
|
||||
var entry_refs: Dictionary = entry["refs"]
|
||||
entry_refs.erase(tile_key)
|
||||
if entry_refs.is_empty():
|
||||
var node: Node = entry.get("node", null)
|
||||
if node:
|
||||
node.queue_free()
|
||||
_wmo_registry.erase(unique_key)
|
||||
|
||||
state["wmo_refs"] = []
|
||||
|
||||
|
||||
func _wmo_unique_key(placement: Dictionary, tile_key: String, idx: int) -> String:
|
||||
var uid: int = int(placement.get("unique_id", -1))
|
||||
# uniqueId is always set in vanilla/3.3.5a MODF data, but older baked caches
|
||||
# predating the ADTLoader unique_id field will miss it. Fall back to a
|
||||
# per-tile synthetic key — no cross-tile dedup, but also no collisions.
|
||||
if uid > 0:
|
||||
return "uid:%d" % uid
|
||||
return "tile:%s:%d" % [tile_key, idx]
|
||||
|
||||
|
||||
func _clear_wmo_registry() -> void:
|
||||
for entry_variant in _wmo_registry.values():
|
||||
if not (entry_variant is Dictionary):
|
||||
continue
|
||||
var entry: Dictionary = entry_variant
|
||||
var node: Node = entry.get("node", null)
|
||||
if node and is_instance_valid(node):
|
||||
node.queue_free()
|
||||
_wmo_registry.clear()
|
||||
if _world_wmo_root and is_instance_valid(_world_wmo_root):
|
||||
_world_wmo_root.queue_free()
|
||||
_world_wmo_root = null
|
||||
|
||||
|
||||
func _build_tile_m2_assets(tile_root: Node3D, tile_origin: Vector3, m2_names: PackedStringArray, m2_placements: Array) -> bool:
|
||||
if m2_names.is_empty() or m2_placements.is_empty():
|
||||
return false
|
||||
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()
|
||||
|
||||
var groups: Dictionary = {}
|
||||
for placement_variant in m2_placements:
|
||||
@@ -1169,14 +1395,15 @@ func _build_tile_m2_assets(tile_root: Node3D, tile_origin: Vector3, m2_names: Pa
|
||||
var pos: Vector3 = placement.get("pos", Vector3.ZERO) - tile_origin
|
||||
var rot: Vector3 = placement.get("rot", Vector3.ZERO)
|
||||
var scale_value: float = float(placement.get("scale", 1.0))
|
||||
var xform := Transform3D(Basis.from_euler(rot).scaled(Vector3.ONE * maxf(scale_value, 0.0001)), pos)
|
||||
var basis := Basis.from_euler(rot) * Basis(Vector3.UP, M2_RIGHT_YAW_OFFSET)
|
||||
var xform := Transform3D(basis.scaled(Vector3.ONE * maxf(scale_value, 0.0001)), pos)
|
||||
|
||||
if not groups.has(normalized):
|
||||
groups[normalized] = []
|
||||
(groups[normalized] as Array).append(xform)
|
||||
|
||||
if groups.is_empty():
|
||||
return
|
||||
return false
|
||||
|
||||
var m2_root := Node3D.new()
|
||||
m2_root.name = "M2s"
|
||||
@@ -1200,15 +1427,40 @@ func _build_tile_m2_assets(tile_root: Node3D, tile_origin: Vector3, m2_names: Pa
|
||||
mmi.name = rel_path.get_file().get_basename()
|
||||
mmi.multimesh = mm
|
||||
mmi.cast_shadow = (
|
||||
GeometryInstance3D.SHADOW_CASTING_SETTING_ON
|
||||
if m2_cast_shadows
|
||||
else GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
|
||||
GeometryInstance3D.SHADOW_CASTING_SETTING_ON
|
||||
if m2_cast_shadows
|
||||
else GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
|
||||
)
|
||||
if m2_visibility_range > 0.0:
|
||||
mmi.visibility_range_end = m2_visibility_range
|
||||
mmi.visibility_range_end_margin = CHUNK_SIZE
|
||||
m2_root.add_child(mmi)
|
||||
_set_editor_owner_recursive(mmi)
|
||||
|
||||
if m2_root.get_child_count() == 0:
|
||||
m2_root.queue_free()
|
||||
return false
|
||||
return true
|
||||
|
||||
|
||||
func _remove_tile_m2_assets(state: Dictionary) -> void:
|
||||
var tile_root: Node = state.get("root", null)
|
||||
if tile_root == null or not is_instance_valid(tile_root):
|
||||
return
|
||||
var m2_root := tile_root.get_node_or_null("M2s")
|
||||
if m2_root:
|
||||
m2_root.queue_free()
|
||||
|
||||
|
||||
func _apply_visibility_range_recursive(node: Node, range_end: float) -> void:
|
||||
if range_end <= 0.0:
|
||||
return
|
||||
if node is GeometryInstance3D:
|
||||
var geometry := node as GeometryInstance3D
|
||||
geometry.visibility_range_end = range_end
|
||||
geometry.visibility_range_end_margin = CHUNK_SIZE
|
||||
for child in node.get_children():
|
||||
_apply_visibility_range_recursive(child, range_end)
|
||||
|
||||
|
||||
func _get_or_load_m2_mesh(rel_path: String) -> Mesh:
|
||||
@@ -1317,23 +1569,23 @@ func _instantiate_m2(rel_path: String, placement: Dictionary, tile_origin: Vecto
|
||||
|
||||
node.name = rel_path.get_file().get_basename()
|
||||
node.position = placement.get("pos", Vector3.ZERO) - tile_origin
|
||||
node.rotation = placement.get("rot", Vector3.ZERO)
|
||||
node.rotation = placement.get("rot", Vector3.ZERO) + Vector3(0.0, M2_RIGHT_YAW_OFFSET, 0.0)
|
||||
var scale_value: float = float(placement.get("scale", 1.0))
|
||||
node.scale = Vector3.ONE * scale_value
|
||||
return node
|
||||
|
||||
|
||||
func _instantiate_wmo(rel_path: String, placement: Dictionary, tile_origin: Vector3) -> Node3D:
|
||||
func _instantiate_wmo_world(rel_path: String, placement: Dictionary) -> Node3D:
|
||||
var prototype := _get_or_load_wmo_prototype(rel_path)
|
||||
if prototype == null:
|
||||
return null
|
||||
|
||||
var instance := prototype.duplicate()
|
||||
instance.name = rel_path.get_file().get_basename()
|
||||
instance.position = placement.get("pos", Vector3.ZERO) - tile_origin
|
||||
var rot: Vector3 = placement.get("rot", Vector3.ZERO)
|
||||
rot.y += PI
|
||||
instance.rotation = rot
|
||||
# WMOs are parented to _terrain_root, whose position absorbs the editor
|
||||
# offset — use world-space placement.pos directly (no tile_origin subtraction).
|
||||
instance.position = placement.get("pos", Vector3.ZERO)
|
||||
instance.rotation = placement.get("rot", Vector3.ZERO)
|
||||
var scale_value: float = float(placement.get("scale", 1.0))
|
||||
instance.scale = Vector3.ONE * scale_value
|
||||
return instance
|
||||
|
||||
Reference in New Issue
Block a user