4463e524aa
Auto-format all GDScript files using gdformat from gdtoolkit. This is a baseline formatting pass to ensure consistent style: - Normalizes indentation and spacing - Wraps long lines to 100 characters - Removes trailing whitespace - Standardizes blank lines between functions 68 files reformatted, 8 files left unchanged (3rd-party addon files with parse errors excluded).
159 lines
4.5 KiB
GDScript
159 lines
4.5 KiB
GDScript
extends Node
|
|
|
|
signal bake_finished
|
|
|
|
@export var enabled: bool = true:
|
|
set = set_enabled
|
|
@export var enter_cost: float = 0.0:
|
|
set = set_enter_cost
|
|
@export var travel_cost: float = 1.0:
|
|
set = set_travel_cost
|
|
@export_flags_3d_navigation var navigation_layers: int = 1:
|
|
set = set_navigation_layers
|
|
@export var template: NavigationMesh:
|
|
set = set_template
|
|
@export var terrain: Terrain3D
|
|
@export var player: Node3D
|
|
@export var mesh_size := Vector3(256, 512, 256)
|
|
@export var min_rebake_distance: float = 64.0
|
|
@export var bake_cooldown: float = 1.0
|
|
@export_group("Debug")
|
|
@export var log_timing: bool = false
|
|
|
|
var _scene_geometry: NavigationMeshSourceGeometryData3D
|
|
var _current_center := Vector3(INF, INF, INF)
|
|
|
|
var _bake_task_id: int = -1
|
|
var _bake_task_timer: float = 0.0
|
|
var _bake_cooldown_timer: float = 0.0
|
|
var _nav_region: NavigationRegion3D
|
|
|
|
|
|
func _ready():
|
|
_nav_region = NavigationRegion3D.new()
|
|
_nav_region.navigation_layers = navigation_layers
|
|
_nav_region.enabled = enabled
|
|
_nav_region.enter_cost = enter_cost
|
|
_nav_region.travel_cost = travel_cost
|
|
|
|
# Enabling edge connections comes with a performance penalty that causes hitches whenever
|
|
# the nav mesh is updated. The navigation server has to compare each edge, and it does this on
|
|
# the main thread.
|
|
_nav_region.use_edge_connections = false
|
|
|
|
add_child(_nav_region)
|
|
|
|
_update_map_cell_size()
|
|
|
|
# If you're using ProtonScatter, you will want to delay this next call until after all
|
|
# your scatter nodes have finished setting up. Here, we just defer one frame so that nodes
|
|
# after this one in the tree get set up first
|
|
parse_scene.call_deferred()
|
|
|
|
|
|
func set_enabled(p_value: bool) -> void:
|
|
enabled = p_value
|
|
if _nav_region:
|
|
_nav_region.enabled = enabled
|
|
set_process(enabled and template)
|
|
|
|
|
|
func set_enter_cost(p_value: bool) -> void:
|
|
enter_cost = p_value
|
|
if _nav_region:
|
|
_nav_region.enter_cost = enter_cost
|
|
|
|
|
|
func set_travel_cost(p_value: bool) -> void:
|
|
travel_cost = p_value
|
|
if _nav_region:
|
|
_nav_region.travel_cost = travel_cost
|
|
|
|
|
|
func set_navigation_layers(p_value: int) -> void:
|
|
navigation_layers = p_value
|
|
if _nav_region:
|
|
_nav_region.navigation_layers = navigation_layers
|
|
|
|
|
|
func set_template(p_value: NavigationMesh) -> void:
|
|
template = p_value
|
|
set_process(enabled and template)
|
|
_update_map_cell_size()
|
|
|
|
|
|
func parse_scene() -> void:
|
|
_scene_geometry = NavigationMeshSourceGeometryData3D.new()
|
|
NavigationServer3D.parse_source_geometry_data(template, _scene_geometry, self)
|
|
|
|
|
|
func _update_map_cell_size() -> void:
|
|
if get_viewport() and template:
|
|
var map := get_viewport().find_world_3d().navigation_map
|
|
NavigationServer3D.map_set_cell_size(map, template.cell_size)
|
|
NavigationServer3D.map_set_cell_height(map, template.cell_height)
|
|
|
|
|
|
func _process(p_delta: float) -> void:
|
|
if _bake_task_id != -1:
|
|
_bake_task_timer += p_delta
|
|
|
|
if not player or _bake_task_id != -1:
|
|
return
|
|
|
|
if _bake_cooldown_timer > 0.0:
|
|
_bake_cooldown_timer -= p_delta
|
|
return
|
|
|
|
var track_pos := player.global_position
|
|
if player is CharacterBody3D:
|
|
# Center on where the player is likely _going to be_:
|
|
track_pos += player.velocity * bake_cooldown
|
|
|
|
if track_pos.distance_squared_to(_current_center) >= min_rebake_distance * min_rebake_distance:
|
|
_current_center = track_pos
|
|
_rebake(_current_center)
|
|
|
|
|
|
func _rebake(p_center: Vector3) -> void:
|
|
assert(template != null)
|
|
_bake_task_id = WorkerThreadPool.add_task(
|
|
_task_bake.bind(p_center), false, "RuntimeNavigationBaker"
|
|
)
|
|
_bake_task_timer = 0.0
|
|
_bake_cooldown_timer = bake_cooldown
|
|
|
|
|
|
func _task_bake(p_center: Vector3) -> void:
|
|
var nav_mesh: NavigationMesh = template.duplicate()
|
|
nav_mesh.filter_baking_aabb = AABB(-mesh_size * 0.5, mesh_size)
|
|
nav_mesh.filter_baking_aabb_offset = p_center
|
|
var source_geometry: NavigationMeshSourceGeometryData3D
|
|
source_geometry = _scene_geometry.duplicate()
|
|
|
|
if terrain:
|
|
var aabb: AABB = nav_mesh.filter_baking_aabb
|
|
aabb.position += nav_mesh.filter_baking_aabb_offset
|
|
var faces: PackedVector3Array = terrain.generate_nav_mesh_source_geometry(aabb, false)
|
|
source_geometry.add_faces(faces, Transform3D.IDENTITY)
|
|
|
|
if source_geometry.has_data():
|
|
NavigationServer3D.bake_from_source_geometry_data(nav_mesh, source_geometry)
|
|
_bake_finished.call_deferred(nav_mesh)
|
|
else:
|
|
_bake_finished.call_deferred(null)
|
|
|
|
|
|
func _bake_finished(p_nav_mesh: NavigationMesh) -> void:
|
|
if log_timing:
|
|
print("Navigation bake took ", _bake_task_timer, "s")
|
|
|
|
_bake_task_timer = 0.0
|
|
_bake_task_id = -1
|
|
|
|
if p_nav_mesh:
|
|
_nav_region.navigation_mesh = p_nav_mesh
|
|
|
|
bake_finished.emit()
|
|
assert(!NavigationServer3D.region_get_use_edge_connections(_nav_region.get_region_rid()))
|