new terrain plugin
This commit is contained in:
29
demo/src/CameraManager.gd
Normal file
29
demo/src/CameraManager.gd
Normal file
@@ -0,0 +1,29 @@
|
||||
extends Node3D
|
||||
|
||||
const CAMERA_MAX_PITCH: float = deg_to_rad(70)
|
||||
const CAMERA_MIN_PITCH: float = deg_to_rad(-89.9)
|
||||
const CAMERA_RATIO: float = .625
|
||||
|
||||
@export var mouse_sensitivity: float = .002
|
||||
@export var mouse_y_inversion: float = -1.0
|
||||
|
||||
@onready var _camera_yaw: Node3D = self
|
||||
@onready var _camera_pitch: Node3D = %Arm
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
|
||||
|
||||
|
||||
func _input(p_event: InputEvent) -> void:
|
||||
if p_event is InputEventMouseMotion and Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
|
||||
rotate_camera(p_event.relative)
|
||||
get_viewport().set_input_as_handled()
|
||||
return
|
||||
|
||||
|
||||
func rotate_camera(p_relative:Vector2) -> void:
|
||||
_camera_yaw.rotation.y -= p_relative.x * mouse_sensitivity
|
||||
_camera_yaw.orthonormalize()
|
||||
_camera_pitch.rotation.x += p_relative.y * mouse_sensitivity * CAMERA_RATIO * mouse_y_inversion
|
||||
_camera_pitch.rotation.x = clamp(_camera_pitch.rotation.x, CAMERA_MIN_PITCH, CAMERA_MAX_PITCH)
|
||||
1
demo/src/CameraManager.gd.uid
Normal file
1
demo/src/CameraManager.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://b62ppvc03a6b1
|
||||
22
demo/src/CaveEntrance.gd
Normal file
22
demo/src/CaveEntrance.gd
Normal file
@@ -0,0 +1,22 @@
|
||||
extends Area3D
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
body_entered.connect(_on_body_entered)
|
||||
body_exited.connect(_on_body_exited)
|
||||
|
||||
|
||||
func _on_body_entered(body: Node3D) -> void:
|
||||
if body.name == "Player":
|
||||
var env: WorldEnvironment = get_node_or_null("../../Environment/WorldEnvironment")
|
||||
if env:
|
||||
var tween: Tween = get_tree().create_tween()
|
||||
tween.tween_property(env.environment, "ambient_light_energy", .1, .33)
|
||||
|
||||
|
||||
func _on_body_exited(body: Node3D) -> void:
|
||||
if body.name == "Player":
|
||||
var env: WorldEnvironment = get_node_or_null("../../Environment/WorldEnvironment")
|
||||
if env:
|
||||
var tween: Tween = get_tree().create_tween()
|
||||
tween.tween_property(env.environment, "ambient_light_energy", 1., .33)
|
||||
1
demo/src/CaveEntrance.gd.uid
Normal file
1
demo/src/CaveEntrance.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://c444j1ucmv5ti
|
||||
133
demo/src/CodeGenerated.gd
Normal file
133
demo/src/CodeGenerated.gd
Normal file
@@ -0,0 +1,133 @@
|
||||
extends Node
|
||||
|
||||
var terrain: Terrain3D
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
$UI.player = $Player
|
||||
|
||||
if has_node("RunThisSceneLabel3D"):
|
||||
$RunThisSceneLabel3D.queue_free()
|
||||
|
||||
terrain = await create_terrain()
|
||||
|
||||
# Enable runtime navigation baking using the terrain
|
||||
# Enable `Debug/Visible Navigation` if you wish to see it
|
||||
$RuntimeNavigationBaker.terrain = terrain
|
||||
$RuntimeNavigationBaker.enabled = true
|
||||
|
||||
|
||||
func create_terrain() -> Terrain3D:
|
||||
# Create textures
|
||||
var green_gr := Gradient.new()
|
||||
green_gr.set_color(0, Color.from_hsv(100./360., .35, .3))
|
||||
green_gr.set_color(1, Color.from_hsv(120./360., .4, .37))
|
||||
var green_ta: Terrain3DTextureAsset = await create_texture_asset("Grass", green_gr, 1024)
|
||||
green_ta.uv_scale = 0.1
|
||||
green_ta.detiling_rotation = 0.1
|
||||
|
||||
var brown_gr := Gradient.new()
|
||||
brown_gr.set_color(0, Color.from_hsv(30./360., .4, .3))
|
||||
brown_gr.set_color(1, Color.from_hsv(30./360., .4, .4))
|
||||
var brown_ta: Terrain3DTextureAsset = await create_texture_asset("Dirt", brown_gr, 1024)
|
||||
brown_ta.uv_scale = 0.03
|
||||
green_ta.detiling_rotation = 0.1
|
||||
|
||||
var grass_ma: Terrain3DMeshAsset = create_mesh_asset("Grass", Color.from_hsv(120./360., .4, .37))
|
||||
|
||||
# Create a terrain
|
||||
var terrain := Terrain3D.new()
|
||||
terrain.name = "Terrain3D"
|
||||
add_child(terrain, true)
|
||||
|
||||
# Set material and assets
|
||||
terrain.material.world_background = Terrain3DMaterial.NONE
|
||||
terrain.material.auto_shader = true
|
||||
terrain.material.set_shader_param("auto_slope", 10)
|
||||
terrain.material.set_shader_param("blend_sharpness", .975)
|
||||
terrain.assets = Terrain3DAssets.new()
|
||||
terrain.assets.set_texture(0, green_ta)
|
||||
terrain.assets.set_texture(1, brown_ta)
|
||||
terrain.assets.set_mesh_asset(0, grass_ma)
|
||||
|
||||
# Generate height map w/ 32-bit noise and import it with scale
|
||||
var noise := FastNoiseLite.new()
|
||||
noise.frequency = 0.0005
|
||||
var img: Image = Image.create_empty(2048, 2048, false, Image.FORMAT_RF)
|
||||
for x in img.get_width():
|
||||
for y in img.get_height():
|
||||
img.set_pixel(x, y, Color(noise.get_noise_2d(x, y), 0., 0., 1.))
|
||||
terrain.region_size = 1024
|
||||
terrain.data.import_images([img, null, null], Vector3(-1024, 0, -1024), 0.0, 150.0)
|
||||
|
||||
# Instance foliage
|
||||
var xforms: Array[Transform3D]
|
||||
var width: int = 100
|
||||
var step: int = 2
|
||||
for x in range(0, width, step):
|
||||
for z in range(0, width, step):
|
||||
var pos := Vector3(x, 0, z) - Vector3(width, 0, width) * .5
|
||||
pos.y = terrain.data.get_height(pos)
|
||||
xforms.push_back(Transform3D(Basis(), pos))
|
||||
terrain.instancer.add_transforms(0, xforms)
|
||||
|
||||
# Enable the next line and `Debug/Visible Collision Shapes` to see collision
|
||||
#terrain.collision.mode = Terrain3DCollision.DYNAMIC_EDITOR
|
||||
|
||||
return terrain
|
||||
|
||||
|
||||
func create_texture_asset(asset_name: String, gradient: Gradient, texture_size: int = 512) -> Terrain3DTextureAsset:
|
||||
# Create noise map
|
||||
var fnl := FastNoiseLite.new()
|
||||
fnl.frequency = 0.004
|
||||
|
||||
# Create albedo noise texture
|
||||
var alb_noise_tex := NoiseTexture2D.new()
|
||||
alb_noise_tex.width = texture_size
|
||||
alb_noise_tex.height = texture_size
|
||||
alb_noise_tex.seamless = true
|
||||
alb_noise_tex.noise = fnl
|
||||
alb_noise_tex.color_ramp = gradient
|
||||
await alb_noise_tex.changed
|
||||
var alb_noise_img: Image = alb_noise_tex.get_image()
|
||||
|
||||
# Create albedo + height texture
|
||||
for x in alb_noise_img.get_width():
|
||||
for y in alb_noise_img.get_height():
|
||||
var clr: Color = alb_noise_img.get_pixel(x, y)
|
||||
clr.a = clr.v # Noise as height
|
||||
alb_noise_img.set_pixel(x, y, clr)
|
||||
alb_noise_img.generate_mipmaps()
|
||||
var albedo := ImageTexture.create_from_image(alb_noise_img)
|
||||
|
||||
# Create normal + rough texture
|
||||
var nrm_noise_tex := NoiseTexture2D.new()
|
||||
nrm_noise_tex.width = texture_size
|
||||
nrm_noise_tex.height = texture_size
|
||||
nrm_noise_tex.as_normal_map = true
|
||||
nrm_noise_tex.seamless = true
|
||||
nrm_noise_tex.noise = fnl
|
||||
await nrm_noise_tex.changed
|
||||
var nrm_noise_img = nrm_noise_tex.get_image()
|
||||
for x in nrm_noise_img.get_width():
|
||||
for y in nrm_noise_img.get_height():
|
||||
var normal_rgh: Color = nrm_noise_img.get_pixel(x, y)
|
||||
normal_rgh.a = 0.8 # Roughness
|
||||
nrm_noise_img.set_pixel(x, y, normal_rgh)
|
||||
nrm_noise_img.generate_mipmaps()
|
||||
var normal := ImageTexture.create_from_image(nrm_noise_img)
|
||||
|
||||
var ta := Terrain3DTextureAsset.new()
|
||||
ta.name = asset_name
|
||||
ta.albedo_texture = albedo
|
||||
ta.normal_texture = normal
|
||||
return ta
|
||||
|
||||
|
||||
func create_mesh_asset(asset_name: String, color: Color) -> Terrain3DMeshAsset:
|
||||
var ma := Terrain3DMeshAsset.new()
|
||||
ma.name = asset_name
|
||||
ma.generated_type = Terrain3DMeshAsset.TYPE_TEXTURE_CARD
|
||||
ma.material_override.albedo_color = color
|
||||
return ma
|
||||
1
demo/src/CodeGenerated.gd.uid
Normal file
1
demo/src/CodeGenerated.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dakis6gu8b7nm
|
||||
23
demo/src/DemoScene.gd
Normal file
23
demo/src/DemoScene.gd
Normal file
@@ -0,0 +1,23 @@
|
||||
@tool
|
||||
extends Node
|
||||
|
||||
@onready var terrain: Terrain3D = find_child("Terrain3D")
|
||||
|
||||
|
||||
func _ready():
|
||||
if not Engine.is_editor_hint() and has_node("UI"):
|
||||
$UI.player = $Player
|
||||
|
||||
# Load Sky3D into the demo environment if enabled
|
||||
if Engine.is_editor_hint() and has_node("Environment") and \
|
||||
Engine.get_singleton(&"EditorInterface").is_plugin_enabled("sky_3d"):
|
||||
$Environment.queue_free()
|
||||
var sky3d = load("res://addons/sky_3d/src/Sky3D.gd").new()
|
||||
sky3d.name = "Sky3D"
|
||||
add_child(sky3d, true)
|
||||
move_child(sky3d, 1)
|
||||
sky3d.owner = self
|
||||
sky3d.current_time = 10
|
||||
sky3d.enable_editor_time = false
|
||||
|
||||
|
||||
1
demo/src/DemoScene.gd.uid
Normal file
1
demo/src/DemoScene.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://chstoagn42gbr
|
||||
58
demo/src/Enemy.gd
Normal file
58
demo/src/Enemy.gd
Normal file
@@ -0,0 +1,58 @@
|
||||
extends CharacterBody3D
|
||||
|
||||
const RETARGET_COOLDOWN: float = 1.0
|
||||
|
||||
@export var MOVE_SPEED: float = 50.0
|
||||
@export var target: Node3D
|
||||
|
||||
@onready var nav_agent: NavigationAgent3D = $NavigationAgent3D
|
||||
|
||||
var _retarget_timer: float = 1.0
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
nav_agent.velocity_computed.connect(_on_velocity_computed)
|
||||
|
||||
|
||||
func _process(p_delta: float) -> void:
|
||||
_retarget_timer += p_delta
|
||||
if _retarget_timer > RETARGET_COOLDOWN and target:
|
||||
# Don't reset the target position every frame. It triggers an A* search, which is expensive.
|
||||
_retarget_timer = 0.0
|
||||
nav_agent.set_target_position(target.global_position)
|
||||
|
||||
|
||||
func is_on_nav_mesh() -> bool:
|
||||
var closest_point := NavigationServer3D.map_get_closest_point(nav_agent.get_navigation_map(), global_position)
|
||||
return global_position.distance_squared_to(closest_point) < nav_agent.path_max_distance ** 2
|
||||
|
||||
|
||||
func _physics_process(p_delta: float) -> void:
|
||||
if nav_agent.is_navigation_finished():
|
||||
velocity.x = 0.0
|
||||
velocity.z = 0.0
|
||||
else:
|
||||
var next_path_position: Vector3 = nav_agent.get_next_path_position()
|
||||
var current_agent_position: Vector3 = global_position
|
||||
var velocity_xz := (next_path_position - current_agent_position).normalized() * MOVE_SPEED
|
||||
velocity.x = velocity_xz.x
|
||||
velocity.z = velocity_xz.z
|
||||
|
||||
velocity.y -= 40 * p_delta
|
||||
|
||||
if nav_agent.avoidance_enabled:
|
||||
nav_agent.set_velocity(velocity)
|
||||
else:
|
||||
_on_velocity_computed(velocity)
|
||||
|
||||
# Ensure enemy doesn't fall through terrain when collision absent
|
||||
if get_parent().terrain:
|
||||
var height: float = get_parent().terrain.data.get_height(global_position)
|
||||
if not is_nan(height):
|
||||
global_position.y = maxf(global_position.y, height)
|
||||
|
||||
|
||||
func _on_velocity_computed(p_safe_velocity: Vector3) -> void:
|
||||
velocity.x = p_safe_velocity.x
|
||||
velocity.z = p_safe_velocity.z
|
||||
move_and_slide()
|
||||
1
demo/src/Enemy.gd.uid
Normal file
1
demo/src/Enemy.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://6j2rrp5f1gjs
|
||||
81
demo/src/Player.gd
Normal file
81
demo/src/Player.gd
Normal file
@@ -0,0 +1,81 @@
|
||||
extends CharacterBody3D
|
||||
|
||||
@export var MOVE_SPEED: float = 50.0
|
||||
@export var JUMP_SPEED: float = 2.0
|
||||
@export var first_person: bool = false :
|
||||
set(p_value):
|
||||
first_person = p_value
|
||||
if first_person:
|
||||
var tween: Tween = create_tween()
|
||||
tween.tween_property($CameraManager/Arm, "spring_length", 0.0, .33)
|
||||
tween.tween_callback($Body.set_visible.bind(false))
|
||||
else:
|
||||
$Body.visible = true
|
||||
create_tween().tween_property($CameraManager/Arm, "spring_length", 6.0, .33)
|
||||
|
||||
@export var gravity_enabled: bool = true :
|
||||
set(p_value):
|
||||
gravity_enabled = p_value
|
||||
if not gravity_enabled:
|
||||
velocity.y = 0
|
||||
|
||||
@export var collision_enabled: bool = true :
|
||||
set(p_value):
|
||||
collision_enabled = p_value
|
||||
$CollisionShapeBody.disabled = ! collision_enabled
|
||||
$CollisionShapeRay.disabled = ! collision_enabled
|
||||
|
||||
|
||||
func _physics_process(p_delta) -> void:
|
||||
var direction: Vector3 = get_camera_relative_input()
|
||||
var h_veloc: Vector2 = Vector2(direction.x, direction.z).normalized() * MOVE_SPEED
|
||||
if Input.is_key_pressed(KEY_SHIFT):
|
||||
h_veloc *= 2
|
||||
velocity.x = h_veloc.x
|
||||
velocity.z = h_veloc.y
|
||||
if gravity_enabled:
|
||||
velocity.y -= 40 * p_delta
|
||||
move_and_slide()
|
||||
|
||||
|
||||
# Returns the input vector relative to the camera. Forward is always the direction the camera is facing
|
||||
func get_camera_relative_input() -> Vector3:
|
||||
var input_dir: Vector3 = Vector3.ZERO
|
||||
if Input.is_key_pressed(KEY_A): # Left
|
||||
input_dir -= %Camera3D.global_transform.basis.x
|
||||
if Input.is_key_pressed(KEY_D): # Right
|
||||
input_dir += %Camera3D.global_transform.basis.x
|
||||
if Input.is_key_pressed(KEY_W): # Forward
|
||||
input_dir -= %Camera3D.global_transform.basis.z
|
||||
if Input.is_key_pressed(KEY_S): # Backward
|
||||
input_dir += %Camera3D.global_transform.basis.z
|
||||
if Input.is_key_pressed(KEY_E) or Input.is_key_pressed(KEY_SPACE): # Up
|
||||
velocity.y += JUMP_SPEED + MOVE_SPEED*.016
|
||||
if Input.is_key_pressed(KEY_Q): # Down
|
||||
velocity.y -= JUMP_SPEED + MOVE_SPEED*.016
|
||||
if Input.is_key_pressed(KEY_KP_ADD) or Input.is_key_pressed(KEY_EQUAL):
|
||||
MOVE_SPEED = clamp(MOVE_SPEED + .5, 5, 9999)
|
||||
if Input.is_key_pressed(KEY_KP_SUBTRACT) or Input.is_key_pressed(KEY_MINUS):
|
||||
MOVE_SPEED = clamp(MOVE_SPEED - .5, 5, 9999)
|
||||
return input_dir
|
||||
|
||||
|
||||
func _input(p_event: InputEvent) -> void:
|
||||
if p_event is InputEventMouseButton and p_event.pressed:
|
||||
if p_event.button_index == MOUSE_BUTTON_WHEEL_UP:
|
||||
MOVE_SPEED = clamp(MOVE_SPEED + 5, 5, 9999)
|
||||
elif p_event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
|
||||
MOVE_SPEED = clamp(MOVE_SPEED - 5, 5, 9999)
|
||||
|
||||
elif p_event is InputEventKey:
|
||||
if p_event.pressed:
|
||||
if p_event.keycode == KEY_V:
|
||||
first_person = ! first_person
|
||||
elif p_event.keycode == KEY_G:
|
||||
gravity_enabled = ! gravity_enabled
|
||||
elif p_event.keycode == KEY_C:
|
||||
collision_enabled = ! collision_enabled
|
||||
|
||||
# Else if up/down released
|
||||
elif p_event.keycode in [ KEY_Q, KEY_E, KEY_SPACE ]:
|
||||
velocity.y = 0
|
||||
1
demo/src/Player.gd.uid
Normal file
1
demo/src/Player.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dajlr3n5wjwmb
|
||||
151
demo/src/RuntimeNavigationBaker.gd
Normal file
151
demo/src/RuntimeNavigationBaker.gd
Normal file
@@ -0,0 +1,151 @@
|
||||
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()))
|
||||
1
demo/src/RuntimeNavigationBaker.gd.uid
Normal file
1
demo/src/RuntimeNavigationBaker.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://brh8x1wnycrl5
|
||||
64
demo/src/UI.gd
Normal file
64
demo/src/UI.gd
Normal file
@@ -0,0 +1,64 @@
|
||||
extends Control
|
||||
|
||||
|
||||
var player: Node
|
||||
var visible_mode: int = 1
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
RenderingServer.set_debug_generate_wireframes(true)
|
||||
|
||||
|
||||
func _process(p_delta) -> void:
|
||||
$Label.text = "FPS: %d\n" % Engine.get_frames_per_second()
|
||||
if(visible_mode == 1):
|
||||
$Label.text += "Move Speed: %.1f\n" % player.MOVE_SPEED if player else ""
|
||||
$Label.text += "Position: %.1v\n" % player.global_position if player else ""
|
||||
$Label.text += """
|
||||
Player
|
||||
Move: WASDEQ,Space,Mouse
|
||||
Move speed: Wheel,+/-,Shift
|
||||
Camera View: V
|
||||
Gravity toggle: G
|
||||
Collision toggle: C
|
||||
|
||||
Window
|
||||
Quit: F8
|
||||
UI toggle: F9
|
||||
Render mode: F10
|
||||
Full screen: F11
|
||||
Mouse toggle: Escape / F12
|
||||
"""
|
||||
|
||||
|
||||
func _unhandled_key_input(p_event: InputEvent) -> void:
|
||||
if p_event is InputEventKey and p_event.pressed:
|
||||
match p_event.keycode:
|
||||
KEY_F8:
|
||||
get_tree().quit()
|
||||
KEY_F9:
|
||||
visible_mode = (visible_mode + 1 ) % 3
|
||||
$Label/Panel.visible = (visible_mode == 1)
|
||||
visible = visible_mode > 0
|
||||
KEY_F10:
|
||||
var vp = get_viewport()
|
||||
vp.debug_draw = (vp.debug_draw + 1 ) % 6
|
||||
get_viewport().set_input_as_handled()
|
||||
KEY_F11:
|
||||
toggle_fullscreen()
|
||||
get_viewport().set_input_as_handled()
|
||||
KEY_ESCAPE, KEY_F12:
|
||||
if Input.get_mouse_mode() == Input.MOUSE_MODE_VISIBLE:
|
||||
Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
|
||||
else:
|
||||
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
|
||||
get_viewport().set_input_as_handled()
|
||||
|
||||
|
||||
func toggle_fullscreen() -> void:
|
||||
if DisplayServer.window_get_mode() == DisplayServer.WINDOW_MODE_EXCLUSIVE_FULLSCREEN or \
|
||||
DisplayServer.window_get_mode() == DisplayServer.WINDOW_MODE_FULLSCREEN:
|
||||
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED)
|
||||
DisplayServer.window_set_size(Vector2(1280, 720))
|
||||
else:
|
||||
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_EXCLUSIVE_FULLSCREEN)
|
||||
1
demo/src/UI.gd.uid
Normal file
1
demo/src/UI.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dne6na1m4xku8
|
||||
Reference in New Issue
Block a user