feat: bake jajce terrain navigation
This commit is contained in:
@@ -626,14 +626,14 @@ Completed:
|
|||||||
8. Base Terrain3D collision/navigation guardrails: runtime tests now keep
|
8. Base Terrain3D collision/navigation guardrails: runtime tests now keep
|
||||||
Terrain3D collision enabled, keep the old greybox ground out of physics, and
|
Terrain3D collision enabled, keep the old greybox ground out of physics, and
|
||||||
verify required navigation paths track the authored height field.
|
verify required navigation paths track the authored height field.
|
||||||
|
9. Terrain3D-derived playable-loop navigation: `JajceWorld` now uses an
|
||||||
|
external baked navigation resource generated from Terrain3D source geometry,
|
||||||
|
and runtime tests reject partial paths that do not reach their targets.
|
||||||
|
|
||||||
Next:
|
Next:
|
||||||
|
|
||||||
1. Continue replacing the temporary greybox navigation source with baked
|
1. Capture and visually review “Jajce Lookdev 01.”
|
||||||
Terrain3D-derived navigation for the sculpted playable loop.
|
2. Capture “Simulation Garden 01.”
|
||||||
|
|
||||||
2. Capture and visually review “Jajce Lookdev 01.”
|
|
||||||
3. Capture “Simulation Garden 01.”
|
|
||||||
|
|
||||||
Do not start with GIS data, a full city, a large asset pack, or more NPC
|
Do not start with GIS data, a full city, a large asset pack, or more NPC
|
||||||
mechanics. The next proof is a beautiful stage for the systems that already
|
mechanics. The next proof is a beautiful stage for the systems that already
|
||||||
|
|||||||
@@ -674,6 +674,10 @@ Recently completed:
|
|||||||
now assert Terrain3D collision remains enabled, the hidden greybox ground no
|
now assert Terrain3D collision remains enabled, the hidden greybox ground no
|
||||||
longer provides physics collision, and tested navigation routes stay close to
|
longer provides physics collision, and tested navigation routes stay close to
|
||||||
the authored Terrain3D height field.
|
the authored Terrain3D height field.
|
||||||
|
- The temporary greybox navigation source was replaced by a project-owned
|
||||||
|
Terrain3D-derived navigation mesh resource generated by
|
||||||
|
`tools/bake_jajce_navigation.gd`; route tests now also reject partial paths
|
||||||
|
that do not reach their requested targets.
|
||||||
|
|
||||||
This order strengthens the simulation while regularly producing visible
|
This order strengthens the simulation while regularly producing visible
|
||||||
progress suitable for public development updates.
|
progress suitable for public development updates.
|
||||||
|
|||||||
+10
-8
@@ -193,8 +193,8 @@ study, and patrol target typed activity sites. The migration is documented in
|
|||||||
- **Main scene:** `res://main.tscn`
|
- **Main scene:** `res://main.tscn`
|
||||||
- **Terrain:** Terrain3D 1.0.2 is installed and enabled
|
- **Terrain:** Terrain3D 1.0.2 is installed and enabled
|
||||||
- **Jajce runtime:** reusable 512 m Terrain3D seed, greybox landmarks, stable
|
- **Jajce runtime:** reusable 512 m Terrain3D seed, greybox landmarks, stable
|
||||||
ResourceNode placement, lookdev camera, tested runtime navigation, and base
|
ResourceNode placement, lookdev camera, and Terrain3D-derived runtime
|
||||||
Terrain3D collision/navigation guardrails
|
navigation with collision guardrails
|
||||||
- **Terrain compatibility floor:** Godot 4.4 according to the bundled extension
|
- **Terrain compatibility floor:** Godot 4.4 according to the bundled extension
|
||||||
- **Version control:** Git
|
- **Version control:** Git
|
||||||
- **Primary branch:** `main`
|
- **Primary branch:** `main`
|
||||||
@@ -328,10 +328,11 @@ distance with resource `safety_risk`, `comfort_distance`, and
|
|||||||
- warm sky, fog, shadows, and wind-reactive foliage;
|
- warm sky, fog, shadows, and wind-reactive foliage;
|
||||||
- eight ResourceNodes preserving stable food/wood discovery IDs;
|
- eight ResourceNodes preserving stable food/wood discovery IDs;
|
||||||
- typed village pantry storage and typed guard, study, and rest activity sites;
|
- typed village pantry storage and typed guard, study, and rest activity sites;
|
||||||
- a temporary baked navigation loop with 24 tested routes;
|
- a Terrain3D-derived baked navigation mesh covering the current playable loop;
|
||||||
- base collision/navigation guardrails that keep Terrain3D collision enabled,
|
- base collision/navigation guardrails that keep Terrain3D collision enabled,
|
||||||
keep the hidden greybox ground out of runtime physics, and compare required
|
keep the old greybox ground out of runtime physics, require paths to reach
|
||||||
navigation paths against the Terrain3D height field;
|
their targets, and compare required navigation paths against the Terrain3D
|
||||||
|
height field;
|
||||||
- a look-development scene and camera.
|
- a look-development scene and camera.
|
||||||
|
|
||||||
This is a runtime integration proof, not the final terrain composition.
|
This is a runtime integration proof, not the final terrain composition.
|
||||||
@@ -538,9 +539,10 @@ These are expected prototype constraints, not necessarily isolated bugs:
|
|||||||
mutation APIs, but selection, execution, target resolution, and active-world
|
mutation APIs, but selection, execution, target resolution, and active-world
|
||||||
queries now have focused collaborators.
|
queries now have focused collaborators.
|
||||||
- Path failure and interruption emit a `navigation_failed` signal and send the NPC to wander; this is functional but not yet polished.
|
- Path failure and interruption emit a `navigation_failed` signal and send the NPC to wander; this is functional but not yet polished.
|
||||||
- The old greybox navigation source remains temporary; current tests now guard
|
- The old greybox navigation source has been replaced by a project-owned
|
||||||
against collision/nav regressions, but the next terrain base phase is a
|
Terrain3D-derived navigation resource. The current bake is still a first
|
||||||
Terrain3D-derived bake for the sculpted playable loop.
|
playable-loop pass, so future terrain sculpting should rerun the bake tool and
|
||||||
|
recheck reachability rather than hand-editing navigation polygons.
|
||||||
- Terrain3D and the bounded Jajce beauty baseline now run in the main game
|
- Terrain3D and the bounded Jajce beauty baseline now run in the main game
|
||||||
scene; final visual review and character/profession readability remain.
|
scene; final visual review and character/profession readability remain.
|
||||||
- Combat, companions, factions, politics, trade, rumours, quests, persistence,
|
- Combat, companions, factions, politics, trade, rumours, quests, persistence,
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ func _run() -> void:
|
|||||||
var main_scene: Node = load("res://main.tscn").instantiate()
|
var main_scene: Node = load("res://main.tscn").instantiate()
|
||||||
root.add_child(main_scene)
|
root.add_child(main_scene)
|
||||||
await process_frame
|
await process_frame
|
||||||
await physics_frame
|
for _frame in 10:
|
||||||
|
await physics_frame
|
||||||
|
|
||||||
var simulation_manager: Node = main_scene.get_node("SimulationManager")
|
var simulation_manager: Node = main_scene.get_node("SimulationManager")
|
||||||
simulation_manager.set_process(false)
|
simulation_manager.set_process(false)
|
||||||
@@ -27,10 +28,9 @@ func _run() -> void:
|
|||||||
"Runtime Terrain3D should bind to the gameplay camera"
|
"Runtime Terrain3D should bind to the gameplay camera"
|
||||||
)
|
)
|
||||||
_check(terrain.collision_mode != 0, "Runtime Terrain3D collision should be enabled")
|
_check(terrain.collision_mode != 0, "Runtime Terrain3D collision should be enabled")
|
||||||
var greybox_ground := main_scene.get_node("JajceWorld/NavigationRegion3D/GreyboxGround") as StaticBody3D
|
|
||||||
_check(
|
_check(
|
||||||
greybox_ground.collision_layer == 0 and greybox_ground.collision_mask == 0,
|
not main_scene.has_node("JajceWorld/NavigationRegion3D/GreyboxGround"),
|
||||||
"Runtime GreyboxGround should not provide physics collision"
|
"Runtime should not keep the temporary greybox navigation ground"
|
||||||
)
|
)
|
||||||
_check(
|
_check(
|
||||||
(
|
(
|
||||||
@@ -49,6 +49,11 @@ func _run() -> void:
|
|||||||
)
|
)
|
||||||
|
|
||||||
var navigation_map: RID = main_scene.get_world_3d().navigation_map
|
var navigation_map: RID = main_scene.get_world_3d().navigation_map
|
||||||
|
var navigation_region := main_scene.get_node("JajceWorld/NavigationRegion3D") as NavigationRegion3D
|
||||||
|
_check(
|
||||||
|
navigation_region.navigation_mesh.resource_path == "res://world/jajce/JajceNavigationMesh.tres",
|
||||||
|
"Runtime navigation should use the Terrain3D-derived baked mesh resource"
|
||||||
|
)
|
||||||
await _wait_for_navigation_map(navigation_map)
|
await _wait_for_navigation_map(navigation_map)
|
||||||
NavigationServer3D.map_force_update(navigation_map)
|
NavigationServer3D.map_force_update(navigation_map)
|
||||||
|
|
||||||
@@ -82,6 +87,9 @@ func _run() -> void:
|
|||||||
_check_path_tracks_terrain(
|
_check_path_tracks_terrain(
|
||||||
terrain, path, "Runtime navigation path should track Terrain3D height"
|
terrain, path, "Runtime navigation path should track Terrain3D height"
|
||||||
)
|
)
|
||||||
|
_check_path_reaches_destination(
|
||||||
|
path, destination, "Runtime navigation path should reach its requested target"
|
||||||
|
)
|
||||||
|
|
||||||
var village := SimVillage.new()
|
var village := SimVillage.new()
|
||||||
var worker := SimNPC.new(100, "BaselineWorker", SimulationIds.PROFESSION_SCHOLAR, 5.0, 5.0)
|
var worker := SimNPC.new(100, "BaselineWorker", SimulationIds.PROFESSION_SCHOLAR, 5.0, 5.0)
|
||||||
@@ -152,3 +160,17 @@ func _check_path_tracks_terrain(terrain: Terrain3D, path: PackedVector3Array, me
|
|||||||
% [message, point, terrain_height]
|
% [message, point, terrain_height]
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
|
func _check_path_reaches_destination(path: PackedVector3Array, destination: Vector3, message: String) -> void:
|
||||||
|
if path.is_empty():
|
||||||
|
return
|
||||||
|
|
||||||
|
var final_point := path[path.size() - 1]
|
||||||
|
var final_xz := Vector2(final_point.x, final_point.z)
|
||||||
|
var destination_xz := Vector2(destination.x, destination.z)
|
||||||
|
if final_xz.distance_to(destination_xz) > 1.5:
|
||||||
|
failures.append(
|
||||||
|
"%s: final path point %s is too far from %s"
|
||||||
|
% [message, final_point, destination]
|
||||||
|
)
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ func _run() -> void:
|
|||||||
var world: Node3D = load("res://world/jajce/JajceWorld.tscn").instantiate()
|
var world: Node3D = load("res://world/jajce/JajceWorld.tscn").instantiate()
|
||||||
root.add_child(world)
|
root.add_child(world)
|
||||||
await process_frame
|
await process_frame
|
||||||
for frame in 10:
|
for _frame in 10:
|
||||||
await physics_frame
|
await physics_frame
|
||||||
|
|
||||||
var terrain = world.get_node("TerrainRoot/Terrain3D")
|
var terrain = world.get_node("TerrainRoot/Terrain3D")
|
||||||
@@ -27,10 +27,9 @@ func _run() -> void:
|
|||||||
"Terrain3D should use dedicated Jajce data"
|
"Terrain3D should use dedicated Jajce data"
|
||||||
)
|
)
|
||||||
_check(terrain.collision_mode != 0, "Terrain3D collision should be enabled for gameplay")
|
_check(terrain.collision_mode != 0, "Terrain3D collision should be enabled for gameplay")
|
||||||
var greybox_ground := world.get_node("NavigationRegion3D/GreyboxGround") as StaticBody3D
|
|
||||||
_check(
|
_check(
|
||||||
greybox_ground.collision_layer == 0 and greybox_ground.collision_mask == 0,
|
not world.has_node("NavigationRegion3D/GreyboxGround"),
|
||||||
"GreyboxGround should not provide runtime physics collision"
|
"JajceWorld should not keep the temporary greybox navigation ground"
|
||||||
)
|
)
|
||||||
_check(
|
_check(
|
||||||
configured_texture_count == 6,
|
configured_texture_count == 6,
|
||||||
@@ -203,6 +202,10 @@ func _run() -> void:
|
|||||||
navigation_region.navigation_mesh.get_polygon_count() > 0,
|
navigation_region.navigation_mesh.get_polygon_count() > 0,
|
||||||
"Jajce navigation mesh should contain baked polygons"
|
"Jajce navigation mesh should contain baked polygons"
|
||||||
)
|
)
|
||||||
|
_check(
|
||||||
|
navigation_region.navigation_mesh.resource_path == "res://world/jajce/JajceNavigationMesh.tres",
|
||||||
|
"Jajce navigation should use the Terrain3D-derived baked mesh resource"
|
||||||
|
)
|
||||||
await _wait_for_navigation_map(navigation_map)
|
await _wait_for_navigation_map(navigation_map)
|
||||||
NavigationServer3D.map_force_update(navigation_map)
|
NavigationServer3D.map_force_update(navigation_map)
|
||||||
|
|
||||||
@@ -224,6 +227,9 @@ func _run() -> void:
|
|||||||
_check_path_tracks_terrain(
|
_check_path_tracks_terrain(
|
||||||
terrain, path, "Jajce navigation path should track Terrain3D height"
|
terrain, path, "Jajce navigation path should track Terrain3D height"
|
||||||
)
|
)
|
||||||
|
_check_path_reaches_destination(
|
||||||
|
path, destination, "Jajce navigation path should reach its requested target"
|
||||||
|
)
|
||||||
|
|
||||||
var adapter := ActiveWorldAdapter.new()
|
var adapter := ActiveWorldAdapter.new()
|
||||||
adapter.pantry_storage = pantry
|
adapter.pantry_storage = pantry
|
||||||
@@ -330,3 +336,17 @@ func _check_path_tracks_terrain(terrain: Terrain3D, path: PackedVector3Array, me
|
|||||||
% [message, point, terrain_height]
|
% [message, point, terrain_height]
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
|
func _check_path_reaches_destination(path: PackedVector3Array, destination: Vector3, message: String) -> void:
|
||||||
|
if path.is_empty():
|
||||||
|
return
|
||||||
|
|
||||||
|
var final_point := path[path.size() - 1]
|
||||||
|
var final_xz := Vector2(final_point.x, final_point.z)
|
||||||
|
var destination_xz := Vector2(destination.x, destination.z)
|
||||||
|
if final_xz.distance_to(destination_xz) > 1.5:
|
||||||
|
failures.append(
|
||||||
|
"%s: final path point %s is too far from %s"
|
||||||
|
% [message, final_point, destination]
|
||||||
|
)
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
extends SceneTree
|
||||||
|
|
||||||
|
const OUTPUT_PATH := "res://world/jajce/JajceNavigationMesh.tres"
|
||||||
|
const WORLD_SCENE_PATH := "res://world/jajce/JajceWorld.tscn"
|
||||||
|
const TERRAIN_PATH := "TerrainRoot/Terrain3D"
|
||||||
|
|
||||||
|
|
||||||
|
func _initialize() -> void:
|
||||||
|
call_deferred("_run")
|
||||||
|
|
||||||
|
|
||||||
|
func _run() -> void:
|
||||||
|
var world_scene := load(WORLD_SCENE_PATH) as PackedScene
|
||||||
|
if world_scene == null:
|
||||||
|
push_error("Could not load %s" % WORLD_SCENE_PATH)
|
||||||
|
quit(1)
|
||||||
|
return
|
||||||
|
|
||||||
|
var world := world_scene.instantiate() as Node3D
|
||||||
|
root.add_child(world)
|
||||||
|
await process_frame
|
||||||
|
for _frame in 10:
|
||||||
|
await physics_frame
|
||||||
|
|
||||||
|
var terrain := world.get_node(TERRAIN_PATH) as Terrain3D
|
||||||
|
if terrain == null or terrain.data == null:
|
||||||
|
push_error("Jajce Terrain3D data is not available")
|
||||||
|
quit(1)
|
||||||
|
return
|
||||||
|
|
||||||
|
var nav_mesh := _create_navigation_mesh_template()
|
||||||
|
var bake_aabb: AABB = nav_mesh.filter_baking_aabb
|
||||||
|
bake_aabb.position += nav_mesh.filter_baking_aabb_offset
|
||||||
|
|
||||||
|
var terrain_faces: PackedVector3Array = terrain.generate_nav_mesh_source_geometry(bake_aabb, false)
|
||||||
|
if terrain_faces.is_empty():
|
||||||
|
push_error("Terrain3D produced no navigation source faces")
|
||||||
|
quit(1)
|
||||||
|
return
|
||||||
|
|
||||||
|
var source_geometry := NavigationMeshSourceGeometryData3D.new()
|
||||||
|
source_geometry.add_faces(terrain_faces, Transform3D.IDENTITY)
|
||||||
|
NavigationServer3D.bake_from_source_geometry_data(nav_mesh, source_geometry)
|
||||||
|
|
||||||
|
if nav_mesh.get_polygon_count() == 0:
|
||||||
|
push_error("Baked Jajce navigation mesh contains no polygons")
|
||||||
|
quit(1)
|
||||||
|
return
|
||||||
|
|
||||||
|
var error := ResourceSaver.save(nav_mesh, OUTPUT_PATH)
|
||||||
|
if error != OK:
|
||||||
|
push_error("Could not save %s: %s" % [OUTPUT_PATH, error_string(error)])
|
||||||
|
quit(1)
|
||||||
|
return
|
||||||
|
|
||||||
|
print(
|
||||||
|
"[TOOL] Saved %s with %d vertices and %d polygons"
|
||||||
|
% [OUTPUT_PATH, nav_mesh.get_vertices().size(), nav_mesh.get_polygon_count()]
|
||||||
|
)
|
||||||
|
quit(0)
|
||||||
|
|
||||||
|
|
||||||
|
func _create_navigation_mesh_template() -> NavigationMesh:
|
||||||
|
var nav_mesh := NavigationMesh.new()
|
||||||
|
nav_mesh.agent_height = 1.7
|
||||||
|
nav_mesh.agent_radius = 0.4
|
||||||
|
nav_mesh.agent_max_climb = 0.55
|
||||||
|
nav_mesh.agent_max_slope = 35.0
|
||||||
|
nav_mesh.cell_size = 0.25
|
||||||
|
nav_mesh.cell_height = 0.1
|
||||||
|
nav_mesh.filter_baking_aabb = AABB(
|
||||||
|
Vector3(-36.0, -8.0, -36.0), Vector3(72.0, 32.0, 72.0)
|
||||||
|
)
|
||||||
|
return nav_mesh
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,4 +1,4 @@
|
|||||||
[gd_scene load_steps=26 format=3]
|
[gd_scene load_steps=27 format=3]
|
||||||
|
|
||||||
[ext_resource type="Terrain3DAssets" path="res://terrain/jajce/assets.tres" id="1_assets"]
|
[ext_resource type="Terrain3DAssets" path="res://terrain/jajce/assets.tres" id="1_assets"]
|
||||||
[ext_resource type="PackedScene" path="res://world/resource_nodes/ResourceNode.tscn" id="2_resource"]
|
[ext_resource type="PackedScene" path="res://world/resource_nodes/ResourceNode.tscn" id="2_resource"]
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
[ext_resource type="Script" path="res://world/jajce/day_night_cycle.gd" id="13_daynight"]
|
[ext_resource type="Script" path="res://world/jajce/day_night_cycle.gd" id="13_daynight"]
|
||||||
[ext_resource type="PackedScene" path="res://world/storage/StorageNode.tscn" id="14_storage"]
|
[ext_resource type="PackedScene" path="res://world/storage/StorageNode.tscn" id="14_storage"]
|
||||||
[ext_resource type="Script" path="res://world/activity/ActivitySite.gd" id="15_activity"]
|
[ext_resource type="Script" path="res://world/activity/ActivitySite.gd" id="15_activity"]
|
||||||
|
[ext_resource type="NavigationMesh" path="res://world/jajce/JajceNavigationMesh.tres" id="16_nav"]
|
||||||
|
|
||||||
[sub_resource type="Terrain3DMaterial" id="Terrain3DMaterial_jajce"]
|
[sub_resource type="Terrain3DMaterial" id="Terrain3DMaterial_jajce"]
|
||||||
_shader_parameters = {
|
_shader_parameters = {
|
||||||
@@ -118,18 +119,7 @@ size = Vector3(2.8, 0.3, 0.9)
|
|||||||
script = ExtResource("3_world")
|
script = ExtResource("3_world")
|
||||||
|
|
||||||
[node name="NavigationRegion3D" type="NavigationRegion3D" parent="."]
|
[node name="NavigationRegion3D" type="NavigationRegion3D" parent="."]
|
||||||
navigation_mesh = SubResource("NavigationMesh_greybox")
|
navigation_mesh = ExtResource("16_nav")
|
||||||
|
|
||||||
[node name="GreyboxGround" type="StaticBody3D" parent="NavigationRegion3D"]
|
|
||||||
collision_layer = 0
|
|
||||||
collision_mask = 0
|
|
||||||
|
|
||||||
[node name="Mesh" type="MeshInstance3D" parent="NavigationRegion3D/GreyboxGround"]
|
|
||||||
visible = false
|
|
||||||
mesh = SubResource("Mesh_ground")
|
|
||||||
|
|
||||||
[node name="Collision" type="CollisionShape3D" parent="NavigationRegion3D/GreyboxGround"]
|
|
||||||
shape = SubResource("Shape_ground")
|
|
||||||
|
|
||||||
[node name="TerrainRoot" type="Node3D" parent="."]
|
[node name="TerrainRoot" type="Node3D" parent="."]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user