Browse Source

Add navigation mesh chunks demos (#1099)

Adds 2D and 3D demo project for how to bake navigation meshes for large world chunk systems.
smix8 11 months ago
parent
commit
cbb68060c6

+ 15 - 0
2d/navigation_mesh_chunks/README.md

@@ -0,0 +1,15 @@
+# Navigation Mesh Chunks 2D
+
+Demo that shows how to bake navigation meshes for large world chunk systems.
+
+A mouse cursor left click changes the start position for the debug paths.
+
+Language: GDScript
+
+Renderer: Compatibility
+
+> Note: this demo requires Godot 4.3 or later
+
+## Screenshots
+
+![Screenshot](screenshots/navigation_mesh_chunks.webp)

BIN
2d/navigation_mesh_chunks/icon.webp


+ 156 - 0
2d/navigation_mesh_chunks/navmesh_chhunks_demo_2d.gd

@@ -0,0 +1,156 @@
+extends Node2D
+
+
+static var map_cell_size: float = 1.0
+static var chunk_size: int = 256
+static var cell_size: float = 1.0
+static var agent_radius: float = 10.0
+static var chunk_id_to_region: Dictionary = {}
+
+
+var path_start_position: Vector2
+
+
+func _ready() -> void:
+	NavigationServer2D.set_debug_enabled(true)
+
+	path_start_position = %DebugPaths.global_position
+
+	var map: RID = get_world_2d().navigation_map
+	NavigationServer2D.map_set_cell_size(map, map_cell_size)
+
+	# Disable performance costly edge connection margin feature.
+	# This feature is not needed to merge navigation mesh edges.
+	# If edges are well aligned they will merge just fine by edge key.
+	NavigationServer2D.map_set_use_edge_connections(map, false)
+
+	# Parse the collision shapes below our parse root node.
+	var source_geometry: NavigationMeshSourceGeometryData2D = NavigationMeshSourceGeometryData2D.new()
+	var parse_settings: NavigationPolygon = NavigationPolygon.new()
+	parse_settings.parsed_geometry_type = NavigationPolygon.PARSED_GEOMETRY_STATIC_COLLIDERS
+	NavigationServer2D.parse_source_geometry_data(parse_settings, source_geometry, %ParseRootNode)
+
+	# Add an outline to define the traversable surface that the parsed collision shapes can "cut" into.
+	var traversable_outline: PackedVector2Array = PackedVector2Array([
+		Vector2(0.0, 0.0),
+		Vector2(1920.0, 0.0),
+		Vector2(1920.0, 1080.0),
+		Vector2(0.0, 1080.0),
+	])
+	source_geometry.add_traversable_outline(traversable_outline)
+
+	create_region_chunks(%ChunksContainer, source_geometry, chunk_size * cell_size, agent_radius)
+
+
+static func create_region_chunks(chunks_root_node: Node, p_source_geometry: NavigationMeshSourceGeometryData2D, p_chunk_size: float, p_agent_radius: float) -> void:
+	# We need to know how many chunks are required for the input geometry.
+	# So first get an axis aligned bounding box that covers all vertices.
+	var input_geometry_bounds: Rect2 = calculate_source_geometry_bounds(p_source_geometry)
+
+	# Rasterize bounding box into chunk grid to know range of required chunks.
+	var start_chunk: Vector2 = floor(
+		input_geometry_bounds.position / p_chunk_size
+	)
+	var end_chunk: Vector2 = floor(
+		(input_geometry_bounds.position + input_geometry_bounds.size)
+		/ p_chunk_size
+	)
+
+	for chunk_y in range(start_chunk.y, end_chunk.y + 1):
+		for chunk_x in range(start_chunk.x, end_chunk.x + 1):
+			var chunk_id: Vector2i = Vector2i(chunk_x, chunk_y)
+
+			var chunk_bounding_box: Rect2 = Rect2(
+				Vector2(chunk_x, chunk_y) * p_chunk_size,
+				Vector2(p_chunk_size, p_chunk_size),
+			)
+			# We grow the chunk bounding box to include geometry
+			# from all the neighbor chunks so edges can align.
+			# The border size is the same value as our grow amount so
+			# the final navigation mesh ends up with the intended chunk size.
+			var baking_bounds: Rect2 = chunk_bounding_box.grow(p_chunk_size)
+
+			var chunk_navmesh: NavigationPolygon = NavigationPolygon.new()
+			chunk_navmesh.parsed_geometry_type = NavigationPolygon.PARSED_GEOMETRY_STATIC_COLLIDERS
+			chunk_navmesh.baking_rect = baking_bounds
+			chunk_navmesh.border_size = p_chunk_size
+			chunk_navmesh.agent_radius = p_agent_radius
+			NavigationServer2D.bake_from_source_geometry_data(chunk_navmesh, p_source_geometry)
+
+			# The only reason we reset the baking bounds here is to not render its debug.
+			chunk_navmesh.baking_rect = Rect2()
+
+			# Snap vertex positions to avoid most rasterization issues with float precision.
+			var navmesh_vertices: PackedVector2Array = chunk_navmesh.vertices
+			for i in navmesh_vertices.size():
+				var vertex: Vector2 = navmesh_vertices[i]
+				navmesh_vertices[i] = vertex.snappedf(map_cell_size * 0.1)
+			chunk_navmesh.vertices = navmesh_vertices
+
+			var chunk_region: NavigationRegion2D = NavigationRegion2D.new()
+			chunk_region.navigation_polygon = chunk_navmesh
+			chunks_root_node.add_child(chunk_region)
+
+			chunk_id_to_region[chunk_id] = chunk_region
+
+
+static func calculate_source_geometry_bounds(p_source_geometry: NavigationMeshSourceGeometryData2D) -> Rect2:
+	if p_source_geometry.has_method("get_bounds"):
+		# Godot 4.3 Patch added get_bounds() function that does the same but faster.
+		return p_source_geometry.call("get_bounds")
+
+	var bounds: Rect2 = Rect2()
+	var first_vertex: bool = true
+
+	for traversable_outline: PackedVector2Array in p_source_geometry.get_traversable_outlines():
+		for traversable_point: Vector2 in traversable_outline:
+			if first_vertex:
+				first_vertex = false
+				bounds.position = traversable_point
+			else:
+				bounds = bounds.expand(traversable_point)
+
+	for obstruction_outline: PackedVector2Array in p_source_geometry.get_obstruction_outlines():
+		for obstruction_point: Vector2 in obstruction_outline:
+			if first_vertex:
+				first_vertex = false
+				bounds.position = obstruction_point
+			else:
+				bounds = bounds.expand(obstruction_point)
+
+	for projected_obstruction: Dictionary in p_source_geometry.get_projected_obstructions():
+		var projected_obstruction_vertices: PackedFloat32Array = projected_obstruction["vertices"]
+		for i in projected_obstruction_vertices.size() / 2:
+			var vertex: Vector2 = Vector2(projected_obstruction_vertices[i * 2], projected_obstruction_vertices[i * 2 + 1])
+			if first_vertex:
+				first_vertex = false
+				bounds.position = vertex
+			else:
+				bounds = bounds.expand(vertex)
+
+	return bounds
+
+
+func _process(_delta: float) -> void:
+	var mouse_cursor_position: Vector2 = get_global_mouse_position()
+
+	var map: RID = get_world_2d().navigation_map
+	# Do not query when the map has never synchronized and is empty.
+	if NavigationServer2D.map_get_iteration_id(map) == 0:
+		return
+
+	var closest_point_on_navmesh: Vector2 = NavigationServer2D.map_get_closest_point(
+		map,
+		mouse_cursor_position
+	)
+
+	if Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT):
+		path_start_position = closest_point_on_navmesh
+
+	%DebugPaths.global_position = path_start_position
+
+	%PathDebugCorridorFunnel.target_position = closest_point_on_navmesh
+	%PathDebugEdgeCentered.target_position = closest_point_on_navmesh
+
+	%PathDebugCorridorFunnel.get_next_path_position()
+	%PathDebugEdgeCentered.get_next_path_position()

+ 99 - 0
2d/navigation_mesh_chunks/navmesh_chhunks_demo_2d.tscn

@@ -0,0 +1,99 @@
+[gd_scene load_steps=2 format=3 uid="uid://svfku2i5n033"]
+
+[ext_resource type="Script" path="res://navmesh_chhunks_demo_2d.gd" id="1_d68tl"]
+
+[node name="NavMeshChunksDemo2D" type="Node2D"]
+script = ExtResource("1_d68tl")
+
+[node name="Camera2D" type="Camera2D" parent="."]
+position = Vector2(960, 540)
+zoom = Vector2(0.8, 0.8)
+
+[node name="ParseRootNode" type="Node2D" parent="."]
+unique_name_in_owner = true
+
+[node name="StaticBody2D" type="StaticBody2D" parent="ParseRootNode"]
+
+[node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="ParseRootNode/StaticBody2D"]
+polygon = PackedVector2Array(244, 147, 636, 155, 376, 391, 460, 655, 804, 795, 756, 971, 484, 839, 308, 963, 132, 811, 128, 559)
+
+[node name="CollisionPolygon2D2" type="CollisionPolygon2D" parent="ParseRootNode/StaticBody2D"]
+polygon = PackedVector2Array(1312, 207, 1596, 91, 1832, 175, 1739, 926, 1562, 796, 1508, 935, 1184, 811, 1324, 533, 1499, 599, 1684, 511, 1596, 322)
+
+[node name="CollisionPolygon2D3" type="CollisionPolygon2D" parent="ParseRootNode/StaticBody2D"]
+polygon = PackedVector2Array(661, 339, 943, 397, 1112, 178, 1172, 443, 960, 639, 700, 579)
+
+[node name="DebugPaths" type="Node2D" parent="."]
+unique_name_in_owner = true
+
+[node name="PathDebugCorridorFunnel" type="NavigationAgent2D" parent="DebugPaths"]
+unique_name_in_owner = true
+debug_enabled = true
+debug_use_custom = true
+debug_path_custom_color = Color(1, 0, 1, 1)
+debug_path_custom_point_size = 10.0
+debug_path_custom_line_width = 4.0
+
+[node name="PathDebugEdgeCentered" type="NavigationAgent2D" parent="DebugPaths"]
+unique_name_in_owner = true
+path_postprocessing = 1
+debug_enabled = true
+debug_use_custom = true
+debug_path_custom_color = Color(1, 1, 0, 1)
+debug_path_custom_point_size = 10.0
+debug_path_custom_line_width = 4.0
+
+[node name="ChunksContainer" type="Node2D" parent="."]
+unique_name_in_owner = true
+
+[node name="CanvasLayer" type="CanvasLayer" parent="."]
+
+[node name="PanelContainer" type="PanelContainer" parent="CanvasLayer"]
+offset_right = 40.0
+offset_bottom = 40.0
+
+[node name="MarginContainer" type="MarginContainer" parent="CanvasLayer/PanelContainer"]
+layout_mode = 2
+theme_override_constants/margin_left = 15
+theme_override_constants/margin_top = 15
+theme_override_constants/margin_right = 15
+theme_override_constants/margin_bottom = 15
+
+[node name="VBoxContainer" type="VBoxContainer" parent="CanvasLayer/PanelContainer/MarginContainer"]
+layout_mode = 2
+
+[node name="Label" type="Label" parent="CanvasLayer/PanelContainer/MarginContainer/VBoxContainer"]
+layout_mode = 2
+text = "Use cursor button to set path start position"
+
+[node name="HBoxContainer" type="HBoxContainer" parent="CanvasLayer/PanelContainer/MarginContainer/VBoxContainer"]
+layout_mode = 2
+theme_override_constants/separation = 10
+
+[node name="ColorRect" type="ColorRect" parent="CanvasLayer/PanelContainer/MarginContainer/VBoxContainer/HBoxContainer"]
+custom_minimum_size = Vector2(128, 8)
+layout_mode = 2
+size_flags_vertical = 4
+color = Color(1, 0, 1, 1)
+
+[node name="Label" type="Label" parent="CanvasLayer/PanelContainer/MarginContainer/VBoxContainer/HBoxContainer"]
+layout_mode = 2
+text = "Path  corridor-funnel"
+horizontal_alignment = 1
+vertical_alignment = 1
+
+[node name="HBoxContainer2" type="HBoxContainer" parent="CanvasLayer/PanelContainer/MarginContainer/VBoxContainer"]
+layout_mode = 2
+theme_override_constants/separation = 10
+
+[node name="ColorRect" type="ColorRect" parent="CanvasLayer/PanelContainer/MarginContainer/VBoxContainer/HBoxContainer2"]
+custom_minimum_size = Vector2(128, 8)
+layout_mode = 2
+size_flags_vertical = 4
+color = Color(1, 1, 0, 1)
+
+[node name="Label" type="Label" parent="CanvasLayer/PanelContainer/MarginContainer/VBoxContainer/HBoxContainer2"]
+layout_mode = 2
+text = "Path edge-centered"
+horizontal_alignment = 1
+vertical_alignment = 1

+ 29 - 0
2d/navigation_mesh_chunks/project.godot

@@ -0,0 +1,29 @@
+; Engine configuration file.
+; It's best edited using the editor UI and not directly,
+; since the parameters that go here are not all obvious.
+;
+; Format:
+;   [section] ; section goes between []
+;   param=value ; assign values to parameters
+
+config_version=5
+
+[application]
+
+config/name="Navigation Mesh Chunks 2D"
+config/tags=PackedStringArray("2d", "ai", "demo", "official")
+run/main_scene="res://navmesh_chhunks_demo_2d.tscn"
+config/features=PackedStringArray("4.3", "GL Compatibility")
+config/icon="res://icon.webp"
+
+[display]
+
+window/size/viewport_width=1920
+window/size/viewport_height=1080
+window/stretch/mode="canvas_items"
+window/stretch/aspect="expand"
+
+[rendering]
+
+renderer/rendering_method="gl_compatibility"
+renderer/rendering_method.mobile="gl_compatibility"

+ 0 - 0
2d/navigation_mesh_chunks/screenshots/.gdignore


BIN
2d/navigation_mesh_chunks/screenshots/navigation_mesh_chunks.webp


+ 15 - 0
3d/navigation_mesh_chunks/README.md

@@ -0,0 +1,15 @@
+# Navigation Mesh Chunks 3D
+
+Demo that shows how to bake navigation meshes for large world chunk systems.
+
+A mouse cursor left click changes the start position for the debug paths.
+
+Language: GDScript
+
+Renderer: Compatibility
+
+> Note: this demo requires Godot 4.3 or later
+
+## Screenshots
+
+![Screenshot](screenshots/navigation_mesh_chunks.webp)

BIN
3d/navigation_mesh_chunks/icon.webp


+ 157 - 0
3d/navigation_mesh_chunks/navmesh_chhunks_demo_3d.gd

@@ -0,0 +1,157 @@
+extends Node3D
+
+
+static var map_cell_size: float = 0.25
+static var chunk_size: int = 16
+static var cell_size: float = 0.25
+static var agent_radius: float = 0.5
+static var chunk_id_to_region: Dictionary = {}
+
+
+var path_start_position: Vector3
+
+
+func _ready() -> void:
+	NavigationServer3D.set_debug_enabled(true)
+
+	path_start_position = %DebugPaths.global_position
+
+	var map: RID = get_world_3d().navigation_map
+	NavigationServer3D.map_set_cell_size(map, map_cell_size)
+
+	# Disable performance costly edge connection margin feature.
+	# This feature is not needed to merge navigation mesh edges.
+	# If edges are well aligned they will merge just fine by edge key.
+	NavigationServer3D.map_set_use_edge_connections(map, false)
+
+	# Parse the collision shapes below our parse root node.
+	var source_geometry: NavigationMeshSourceGeometryData3D = NavigationMeshSourceGeometryData3D.new()
+	var parse_settings: NavigationMesh = NavigationMesh.new()
+	parse_settings.geometry_parsed_geometry_type = NavigationMesh.PARSED_GEOMETRY_STATIC_COLLIDERS
+	NavigationServer3D.parse_source_geometry_data(parse_settings, source_geometry, %ParseRootNode)
+
+	create_region_chunks(%ChunksContainer, source_geometry, chunk_size * cell_size, agent_radius)
+
+
+static func create_region_chunks(chunks_root_node: Node, p_source_geometry: NavigationMeshSourceGeometryData3D, p_chunk_size: float, p_agent_radius: float) -> void:
+	# We need to know how many chunks are required for the input geometry.
+	# So first get an axis aligned bounding box that covers all vertices.
+	var input_geometry_bounds: AABB = calculate_source_geometry_bounds(p_source_geometry)
+
+	# Rasterize bounding box into chunk grid to know range of required chunks.
+	var start_chunk: Vector3 = floor(
+		input_geometry_bounds.position / p_chunk_size
+	)
+	var end_chunk: Vector3 = floor(
+		(input_geometry_bounds.position + input_geometry_bounds.size)
+		/ p_chunk_size
+	)
+
+	# NavigationMesh.border_size is limited to the xz-axis.
+	# So we can only bake one chunk for the y-axis and also
+	# need to span the bake bounds over the entire y-axis.
+	# If we dont do this we would create duplicated polygons
+	# and stack them on top of each other causing merge errors.
+	var bounds_min_height: float = start_chunk.y
+	var bounds_max_height: float = end_chunk.y + p_chunk_size
+	var chunk_y: int = 0
+
+	for chunk_z in range(start_chunk.z, end_chunk.z + 1):
+		for chunk_x in range(start_chunk.x, end_chunk.x + 1):
+			var chunk_id: Vector3i = Vector3i(chunk_x, chunk_y, chunk_z)
+
+			var chunk_bounding_box: AABB = AABB(
+				Vector3(chunk_x, bounds_min_height, chunk_z) * p_chunk_size,
+				Vector3(p_chunk_size, bounds_max_height, p_chunk_size),
+			)
+			# We grow the chunk bounding box to include geometry
+			# from all the neighbor chunks so edges can align.
+			# The border size is the same value as our grow amount so
+			# the final navigation mesh ends up with the intended chunk size.
+			var baking_bounds: AABB = chunk_bounding_box.grow(p_chunk_size)
+
+			var chunk_navmesh: NavigationMesh = NavigationMesh.new()
+			chunk_navmesh.geometry_parsed_geometry_type = NavigationMesh.PARSED_GEOMETRY_STATIC_COLLIDERS
+			chunk_navmesh.cell_size = cell_size
+			chunk_navmesh.cell_height = cell_size
+			chunk_navmesh.filter_baking_aabb = baking_bounds
+			chunk_navmesh.border_size = p_chunk_size
+			chunk_navmesh.agent_radius = p_agent_radius
+			NavigationServer3D.bake_from_source_geometry_data(chunk_navmesh, p_source_geometry)
+
+			# The only reason we reset the baking bounds here is to not render its debug.
+			chunk_navmesh.filter_baking_aabb = AABB()
+
+			# Snap vertex positions to avoid most rasterization issues with float precision.
+			var navmesh_vertices: PackedVector3Array = chunk_navmesh.vertices
+			for i in navmesh_vertices.size():
+				var vertex: Vector3 = navmesh_vertices[i]
+				navmesh_vertices[i] = vertex.snappedf(map_cell_size * 0.1)
+			chunk_navmesh.vertices = navmesh_vertices
+
+			var chunk_region: NavigationRegion3D = NavigationRegion3D.new()
+			chunk_region.navigation_mesh = chunk_navmesh
+			chunks_root_node.add_child(chunk_region)
+
+			chunk_id_to_region[chunk_id] = chunk_region
+
+
+static func calculate_source_geometry_bounds(p_source_geometry: NavigationMeshSourceGeometryData3D) -> AABB:
+	if p_source_geometry.has_method("get_bounds"):
+		# Godot 4.3 Patch added get_bounds() function that does the same but faster.
+		return p_source_geometry.call("get_bounds")
+
+	var bounds: AABB = AABB()
+	var first_vertex: bool = true
+
+	var vertices: PackedFloat32Array = p_source_geometry.get_vertices()
+	var vertices_count: int = vertices.size() / 3
+	for i in vertices_count:
+		var vertex: Vector3 = Vector3(vertices[i * 3], vertices[i * 3 + 1], vertices[i * 3 + 2])
+		if first_vertex:
+			first_vertex = false
+			bounds.position = vertex
+		else:
+			bounds = bounds.expand(vertex)
+
+	for projected_obstruction: Dictionary in p_source_geometry.get_projected_obstructions():
+		var projected_obstruction_vertices: PackedFloat32Array = projected_obstruction["vertices"]
+		for i in projected_obstruction_vertices.size() / 3:
+			var vertex: Vector3 = Vector3(projected_obstruction.vertices[i * 3], projected_obstruction.vertices[i * 3 + 1], projected_obstruction.vertices[i * 3 + 2]);
+			if first_vertex:
+				first_vertex = false
+				bounds.position = vertex
+			else:
+				bounds = bounds.expand(vertex)
+
+	return bounds
+
+
+func _process(_delta: float) -> void:
+	var mouse_cursor_position: Vector2 = get_viewport().get_mouse_position()
+
+	var map: RID = get_world_3d().navigation_map
+	# Do not query when the map has never synchronized and is empty.
+	if NavigationServer3D.map_get_iteration_id(map) == 0:
+		return
+
+	var camera: Camera3D = get_viewport().get_camera_3d()
+	var camera_ray_length: float = 1000.0
+	var camera_ray_start: Vector3 = camera.project_ray_origin(mouse_cursor_position)
+	var camera_ray_end: Vector3 = camera_ray_start + camera.project_ray_normal(mouse_cursor_position) * camera_ray_length
+	var closest_point_on_navmesh: Vector3 = NavigationServer3D.map_get_closest_point_to_segment(
+		map,
+		camera_ray_start,
+		camera_ray_end
+	)
+
+	if Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT):
+		path_start_position = closest_point_on_navmesh
+
+	%DebugPaths.global_position = path_start_position
+
+	%PathDebugCorridorFunnel.target_position = closest_point_on_navmesh
+	%PathDebugEdgeCentered.target_position = closest_point_on_navmesh
+
+	%PathDebugCorridorFunnel.get_next_path_position()
+	%PathDebugEdgeCentered.get_next_path_position()

+ 152 - 0
3d/navigation_mesh_chunks/navmesh_chhunks_demo_3d.tscn

@@ -0,0 +1,152 @@
+[gd_scene load_steps=11 format=3 uid="uid://cir4dtbp7i1ky"]
+
+[ext_resource type="Script" path="res://navmesh_chhunks_demo_3d.gd" id="1_027f2"]
+
+[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_p73ky"]
+sky_horizon_color = Color(0.64625, 0.65575, 0.67075, 1)
+ground_horizon_color = Color(0.64625, 0.65575, 0.67075, 1)
+
+[sub_resource type="Sky" id="Sky_k64yg"]
+sky_material = SubResource("ProceduralSkyMaterial_p73ky")
+
+[sub_resource type="Environment" id="Environment_ccmns"]
+background_mode = 2
+sky = SubResource("Sky_k64yg")
+tonemap_mode = 2
+glow_enabled = true
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_8p3iq"]
+albedo_color = Color(0.2, 0.2, 0.2, 1)
+
+[sub_resource type="BoxMesh" id="BoxMesh_yn2x6"]
+size = Vector3(15, 1, 15)
+
+[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_7rw3x"]
+points = PackedVector3Array(-7.5, -0.5, -7.5, -7.5, 0.5, -7.5, 7.5, -0.5, -7.5, -7.5, -0.5, 7.5, -7.5, 0.5, 7.5, 7.5, 0.5, -7.5, 7.5, -0.5, 7.5, 7.5, 0.5, 7.5)
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_3kxje"]
+albedo_color = Color(0.5, 0, 0, 1)
+
+[sub_resource type="BoxMesh" id="BoxMesh_hdqb8"]
+size = Vector3(5, 4, 5)
+
+[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_ydj0h"]
+points = PackedVector3Array(-2.5, -2, -2.5, -2.5, 2, -2.5, 2.5, -2, -2.5, -2.5, -2, 2.5, -2.5, 2, 2.5, 2.5, 2, -2.5, 2.5, -2, 2.5, 2.5, 2, 2.5)
+
+[node name="NavMeshChunksDemo3D" type="Node3D"]
+script = ExtResource("1_027f2")
+
+[node name="Camera3D" type="Camera3D" parent="."]
+transform = Transform3D(0.707107, -0.5, 0.5, 0, 0.707107, 0.707107, -0.707107, -0.5, 0.5, 15, 18, 15)
+current = true
+fov = 40.0
+
+[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
+transform = Transform3D(-0.866024, -0.433016, 0.250001, 0, 0.499998, 0.866026, -0.500003, 0.749999, -0.43301, 0, 0, 0)
+shadow_enabled = true
+
+[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
+environment = SubResource("Environment_ccmns")
+
+[node name="ParseRootNode" type="Node3D" parent="."]
+unique_name_in_owner = true
+
+[node name="Ground" type="MeshInstance3D" parent="ParseRootNode"]
+material_override = SubResource("StandardMaterial3D_8p3iq")
+mesh = SubResource("BoxMesh_yn2x6")
+
+[node name="StaticBody3D" type="StaticBody3D" parent="ParseRootNode/Ground"]
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="ParseRootNode/Ground/StaticBody3D"]
+shape = SubResource("ConvexPolygonShape3D_7rw3x")
+
+[node name="CenterBlock" type="MeshInstance3D" parent="ParseRootNode"]
+material_override = SubResource("StandardMaterial3D_3kxje")
+mesh = SubResource("BoxMesh_hdqb8")
+
+[node name="NavmeshDiscard" type="NavigationObstacle3D" parent="ParseRootNode/CenterBlock"]
+height = 3.0
+vertices = PackedVector3Array(-2.5, 0, -2.5, 2.5, 0, -2.5, 2.5, 0, 2.5, -2.5, 0, 2.5)
+affect_navigation_mesh = true
+avoidance_enabled = false
+
+[node name="StaticBody3D" type="StaticBody3D" parent="ParseRootNode/CenterBlock"]
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="ParseRootNode/CenterBlock/StaticBody3D"]
+shape = SubResource("ConvexPolygonShape3D_ydj0h")
+
+[node name="DebugPaths" type="Node3D" parent="."]
+unique_name_in_owner = true
+
+[node name="PathDebugCorridorFunnel" type="NavigationAgent3D" parent="DebugPaths"]
+unique_name_in_owner = true
+debug_enabled = true
+debug_use_custom = true
+debug_path_custom_color = Color(1, 0, 1, 1)
+debug_path_custom_point_size = 10.0
+
+[node name="PathDebugEdgeCentered" type="NavigationAgent3D" parent="DebugPaths"]
+unique_name_in_owner = true
+path_postprocessing = 1
+debug_enabled = true
+debug_use_custom = true
+debug_path_custom_color = Color(1, 1, 0, 1)
+debug_path_custom_point_size = 10.0
+
+[node name="DebugMousePos" type="Node3D" parent="DebugPaths"]
+unique_name_in_owner = true
+
+[node name="ChunksContainer" type="Node3D" parent="."]
+unique_name_in_owner = true
+
+[node name="CanvasLayer" type="CanvasLayer" parent="."]
+
+[node name="PanelContainer" type="PanelContainer" parent="CanvasLayer"]
+offset_right = 40.0
+offset_bottom = 40.0
+
+[node name="MarginContainer" type="MarginContainer" parent="CanvasLayer/PanelContainer"]
+layout_mode = 2
+theme_override_constants/margin_left = 15
+theme_override_constants/margin_top = 15
+theme_override_constants/margin_right = 15
+theme_override_constants/margin_bottom = 15
+
+[node name="VBoxContainer" type="VBoxContainer" parent="CanvasLayer/PanelContainer/MarginContainer"]
+layout_mode = 2
+
+[node name="Label" type="Label" parent="CanvasLayer/PanelContainer/MarginContainer/VBoxContainer"]
+layout_mode = 2
+text = "Use cursor button to set path start position"
+
+[node name="HBoxContainer" type="HBoxContainer" parent="CanvasLayer/PanelContainer/MarginContainer/VBoxContainer"]
+layout_mode = 2
+theme_override_constants/separation = 10
+
+[node name="ColorRect" type="ColorRect" parent="CanvasLayer/PanelContainer/MarginContainer/VBoxContainer/HBoxContainer"]
+custom_minimum_size = Vector2(128, 8)
+layout_mode = 2
+size_flags_vertical = 4
+color = Color(1, 0, 1, 1)
+
+[node name="Label" type="Label" parent="CanvasLayer/PanelContainer/MarginContainer/VBoxContainer/HBoxContainer"]
+layout_mode = 2
+text = "Path  corridor-funnel"
+horizontal_alignment = 1
+vertical_alignment = 1
+
+[node name="HBoxContainer2" type="HBoxContainer" parent="CanvasLayer/PanelContainer/MarginContainer/VBoxContainer"]
+layout_mode = 2
+theme_override_constants/separation = 10
+
+[node name="ColorRect" type="ColorRect" parent="CanvasLayer/PanelContainer/MarginContainer/VBoxContainer/HBoxContainer2"]
+custom_minimum_size = Vector2(128, 8)
+layout_mode = 2
+size_flags_vertical = 4
+color = Color(1, 1, 0, 1)
+
+[node name="Label" type="Label" parent="CanvasLayer/PanelContainer/MarginContainer/VBoxContainer/HBoxContainer2"]
+layout_mode = 2
+text = "Path edge-centered"
+horizontal_alignment = 1
+vertical_alignment = 1

+ 23 - 0
3d/navigation_mesh_chunks/project.godot

@@ -0,0 +1,23 @@
+; Engine configuration file.
+; It's best edited using the editor UI and not directly,
+; since the parameters that go here are not all obvious.
+;
+; Format:
+;   [section] ; section goes between []
+;   param=value ; assign values to parameters
+
+config_version=5
+
+[application]
+
+config/name="Navigation Mesh Chunks 3D"
+config/tags=PackedStringArray("3d", "ai", "demo", "official")
+run/main_scene="res://navmesh_chhunks_demo_3d.tscn"
+config/features=PackedStringArray("4.3", "GL Compatibility")
+config/icon="res://icon.webp"
+
+[rendering]
+
+renderer/rendering_method="gl_compatibility"
+renderer/rendering_method.mobile="gl_compatibility"
+anti_aliasing/quality/msaa_3d=2

+ 0 - 0
3d/navigation_mesh_chunks/screenshots/.gdignore


BIN
3d/navigation_mesh_chunks/screenshots/navigation_mesh_chunks.webp