250 lines
9 KiB
Python
250 lines
9 KiB
Python
|
|
#!/usr/bin/env python3
|
||
|
|
"""Visual Demo: Milestone 12 - VoxelGrid Navigation Projection
|
||
|
|
|
||
|
|
Demonstrates projection of 3D voxel terrain to 2D navigation grid for pathfinding.
|
||
|
|
Shows:
|
||
|
|
1. Voxel dungeon with multiple levels
|
||
|
|
2. Navigation grid projection (walkable/unwalkable areas)
|
||
|
|
3. A* pathfinding through the projected terrain
|
||
|
|
4. FOV computation from voxel transparency
|
||
|
|
"""
|
||
|
|
|
||
|
|
import mcrfpy
|
||
|
|
import sys
|
||
|
|
import math
|
||
|
|
|
||
|
|
def create_demo_scene():
|
||
|
|
"""Create the navigation projection demo scene"""
|
||
|
|
|
||
|
|
scene = mcrfpy.Scene("voxel_nav_demo")
|
||
|
|
|
||
|
|
# =========================================================================
|
||
|
|
# Create a small dungeon-style voxel grid
|
||
|
|
# =========================================================================
|
||
|
|
|
||
|
|
vg = mcrfpy.VoxelGrid((16, 8, 16), cell_size=1.0)
|
||
|
|
|
||
|
|
# Add materials
|
||
|
|
floor_mat = vg.add_material("floor", (100, 80, 60)) # Brown floor
|
||
|
|
wall_mat = vg.add_material("wall", (80, 80, 90), transparent=False) # Gray walls
|
||
|
|
pillar_mat = vg.add_material("pillar", (60, 60, 70), transparent=False) # Dark pillars
|
||
|
|
glass_mat = vg.add_material("glass", (150, 200, 255), transparent=True) # Transparent glass
|
||
|
|
water_mat = vg.add_material("water", (50, 100, 200), transparent=True, path_cost=3.0) # Slow water
|
||
|
|
|
||
|
|
# Create floor
|
||
|
|
vg.fill_box((0, 0, 0), (15, 0, 15), floor_mat)
|
||
|
|
|
||
|
|
# Create outer walls
|
||
|
|
vg.fill_box((0, 1, 0), (15, 4, 0), wall_mat) # North wall
|
||
|
|
vg.fill_box((0, 1, 15), (15, 4, 15), wall_mat) # South wall
|
||
|
|
vg.fill_box((0, 1, 0), (0, 4, 15), wall_mat) # West wall
|
||
|
|
vg.fill_box((15, 1, 0), (15, 4, 15), wall_mat) # East wall
|
||
|
|
|
||
|
|
# Interior walls creating rooms
|
||
|
|
vg.fill_box((5, 1, 0), (5, 4, 10), wall_mat) # Vertical wall
|
||
|
|
vg.fill_box((10, 1, 5), (15, 4, 5), wall_mat) # Horizontal wall
|
||
|
|
|
||
|
|
# Doorways (carve holes)
|
||
|
|
vg.fill_box((5, 1, 3), (5, 2, 4), 0) # Door in vertical wall
|
||
|
|
vg.fill_box((12, 1, 5), (13, 2, 5), 0) # Door in horizontal wall
|
||
|
|
|
||
|
|
# Central pillars
|
||
|
|
vg.fill_box((8, 1, 8), (8, 4, 8), pillar_mat)
|
||
|
|
vg.fill_box((8, 1, 12), (8, 4, 12), pillar_mat)
|
||
|
|
|
||
|
|
# Water pool in one corner (slow movement)
|
||
|
|
vg.fill_box((1, 0, 11), (3, 0, 14), water_mat)
|
||
|
|
|
||
|
|
# Glass window
|
||
|
|
vg.fill_box((10, 2, 5), (11, 3, 5), glass_mat)
|
||
|
|
|
||
|
|
# Raised platform in one area (height variation)
|
||
|
|
vg.fill_box((12, 1, 8), (14, 1, 13), floor_mat) # Platform at y=1
|
||
|
|
|
||
|
|
# =========================================================================
|
||
|
|
# Create Viewport3D with navigation grid
|
||
|
|
# =========================================================================
|
||
|
|
|
||
|
|
viewport = mcrfpy.Viewport3D(pos=(10, 10), size=(600, 400))
|
||
|
|
viewport.set_grid_size(16, 16)
|
||
|
|
viewport.cell_size = 1.0
|
||
|
|
|
||
|
|
# Configure camera for top-down view
|
||
|
|
viewport.camera_pos = (8, 15, 20)
|
||
|
|
viewport.camera_target = (8, 0, 8)
|
||
|
|
|
||
|
|
# Add voxel layer
|
||
|
|
viewport.add_voxel_layer(vg, z_index=0)
|
||
|
|
|
||
|
|
# Project voxels to navigation grid with headroom=2 (entity needs 2 voxels height)
|
||
|
|
viewport.project_voxel_to_nav(vg, headroom=2)
|
||
|
|
|
||
|
|
# =========================================================================
|
||
|
|
# Info panel
|
||
|
|
# =========================================================================
|
||
|
|
|
||
|
|
info_frame = mcrfpy.Frame(pos=(620, 10), size=(250, 400))
|
||
|
|
info_frame.fill_color = mcrfpy.Color(30, 30, 40, 220)
|
||
|
|
info_frame.outline_color = mcrfpy.Color(100, 100, 120)
|
||
|
|
info_frame.outline = 2.0
|
||
|
|
|
||
|
|
title = mcrfpy.Caption(text="Nav Projection Demo", pos=(10, 10))
|
||
|
|
title.fill_color = mcrfpy.Color(255, 255, 100)
|
||
|
|
|
||
|
|
desc = mcrfpy.Caption(text="Voxels projected to\n2D nav grid", pos=(10, 35))
|
||
|
|
desc.fill_color = mcrfpy.Color(200, 200, 200)
|
||
|
|
|
||
|
|
info1 = mcrfpy.Caption(text="Grid: 16x16 cells", pos=(10, 75))
|
||
|
|
info1.fill_color = mcrfpy.Color(150, 200, 255)
|
||
|
|
|
||
|
|
info2 = mcrfpy.Caption(text="Headroom: 2 voxels", pos=(10, 95))
|
||
|
|
info2.fill_color = mcrfpy.Color(150, 200, 255)
|
||
|
|
|
||
|
|
# Count walkable cells
|
||
|
|
walkable_count = 0
|
||
|
|
for x in range(16):
|
||
|
|
for z in range(16):
|
||
|
|
cell = viewport.at(x, z)
|
||
|
|
if cell.walkable:
|
||
|
|
walkable_count += 1
|
||
|
|
|
||
|
|
info3 = mcrfpy.Caption(text=f"Walkable: {walkable_count}/256", pos=(10, 115))
|
||
|
|
info3.fill_color = mcrfpy.Color(100, 255, 100)
|
||
|
|
|
||
|
|
# Find path example
|
||
|
|
path = viewport.find_path((1, 1), (13, 13))
|
||
|
|
info4 = mcrfpy.Caption(text=f"Path length: {len(path)}", pos=(10, 135))
|
||
|
|
info4.fill_color = mcrfpy.Color(255, 200, 100)
|
||
|
|
|
||
|
|
# FOV example
|
||
|
|
fov = viewport.compute_fov((8, 8), 10)
|
||
|
|
info5 = mcrfpy.Caption(text=f"FOV cells: {len(fov)}", pos=(10, 155))
|
||
|
|
info5.fill_color = mcrfpy.Color(200, 150, 255)
|
||
|
|
|
||
|
|
# Legend
|
||
|
|
legend_title = mcrfpy.Caption(text="Materials:", pos=(10, 185))
|
||
|
|
legend_title.fill_color = mcrfpy.Color(255, 255, 255)
|
||
|
|
|
||
|
|
leg1 = mcrfpy.Caption(text=" Floor (walkable)", pos=(10, 205))
|
||
|
|
leg1.fill_color = mcrfpy.Color(100, 80, 60)
|
||
|
|
|
||
|
|
leg2 = mcrfpy.Caption(text=" Wall (blocking)", pos=(10, 225))
|
||
|
|
leg2.fill_color = mcrfpy.Color(80, 80, 90)
|
||
|
|
|
||
|
|
leg3 = mcrfpy.Caption(text=" Water (slow)", pos=(10, 245))
|
||
|
|
leg3.fill_color = mcrfpy.Color(50, 100, 200)
|
||
|
|
|
||
|
|
leg4 = mcrfpy.Caption(text=" Glass (see-through)", pos=(10, 265))
|
||
|
|
leg4.fill_color = mcrfpy.Color(150, 200, 255)
|
||
|
|
|
||
|
|
controls = mcrfpy.Caption(text="[Space] Recompute FOV\n[P] Show path\n[Q] Quit", pos=(10, 300))
|
||
|
|
controls.fill_color = mcrfpy.Color(150, 150, 150)
|
||
|
|
|
||
|
|
info_frame.children.extend([
|
||
|
|
title, desc, info1, info2, info3, info4, info5,
|
||
|
|
legend_title, leg1, leg2, leg3, leg4, controls
|
||
|
|
])
|
||
|
|
|
||
|
|
# =========================================================================
|
||
|
|
# Status bar
|
||
|
|
# =========================================================================
|
||
|
|
|
||
|
|
status_frame = mcrfpy.Frame(pos=(10, 420), size=(860, 50))
|
||
|
|
status_frame.fill_color = mcrfpy.Color(20, 20, 30, 220)
|
||
|
|
status_frame.outline_color = mcrfpy.Color(80, 80, 100)
|
||
|
|
status_frame.outline = 1.0
|
||
|
|
|
||
|
|
status_text = mcrfpy.Caption(
|
||
|
|
text="Milestone 12: VoxelGrid Navigation Projection - Project 3D voxels to 2D pathfinding grid",
|
||
|
|
pos=(10, 15)
|
||
|
|
)
|
||
|
|
status_text.fill_color = mcrfpy.Color(180, 180, 200)
|
||
|
|
status_frame.children.append(status_text)
|
||
|
|
|
||
|
|
# =========================================================================
|
||
|
|
# Add elements to scene
|
||
|
|
# =========================================================================
|
||
|
|
|
||
|
|
scene.children.extend([viewport, info_frame, status_frame])
|
||
|
|
|
||
|
|
# Store references for interaction (using module-level globals)
|
||
|
|
global demo_viewport, demo_voxelgrid, demo_path, demo_fov_origin
|
||
|
|
demo_viewport = viewport
|
||
|
|
demo_voxelgrid = vg
|
||
|
|
demo_path = path
|
||
|
|
demo_fov_origin = (8, 8)
|
||
|
|
|
||
|
|
# =========================================================================
|
||
|
|
# Keyboard handler
|
||
|
|
# =========================================================================
|
||
|
|
|
||
|
|
def on_key(key, state):
|
||
|
|
global demo_fov_origin
|
||
|
|
if state != mcrfpy.InputState.PRESSED:
|
||
|
|
return
|
||
|
|
|
||
|
|
if key == mcrfpy.Key.Q or key == mcrfpy.Key.ESCAPE:
|
||
|
|
# Exit
|
||
|
|
sys.exit(0)
|
||
|
|
elif key == mcrfpy.Key.SPACE:
|
||
|
|
# Recompute FOV from different origin
|
||
|
|
ox, oz = demo_fov_origin
|
||
|
|
ox = (ox + 3) % 14 + 1
|
||
|
|
oz = (oz + 5) % 14 + 1
|
||
|
|
demo_fov_origin = (ox, oz)
|
||
|
|
fov = demo_viewport.compute_fov((ox, oz), 8)
|
||
|
|
info5.text = f"FOV from ({ox},{oz}): {len(fov)}"
|
||
|
|
elif key == mcrfpy.Key.P:
|
||
|
|
# Show path info
|
||
|
|
print(f"Path from (1,1) to (13,13): {len(demo_path)} steps")
|
||
|
|
for i, (px, pz) in enumerate(demo_path[:10]):
|
||
|
|
cell = demo_viewport.at(px, pz)
|
||
|
|
print(f" Step {i}: ({px},{pz}) h={cell.height:.1f} cost={cell.cost:.1f}")
|
||
|
|
if len(demo_path) > 10:
|
||
|
|
print(f" ... and {len(demo_path) - 10} more steps")
|
||
|
|
|
||
|
|
scene.on_key = on_key
|
||
|
|
|
||
|
|
return scene
|
||
|
|
|
||
|
|
def main():
|
||
|
|
"""Main entry point"""
|
||
|
|
print("=== Milestone 12: VoxelGrid Navigation Projection Demo ===")
|
||
|
|
print()
|
||
|
|
print("This demo shows how 3D voxel terrain is projected to a 2D")
|
||
|
|
print("navigation grid for pathfinding and FOV calculations.")
|
||
|
|
print()
|
||
|
|
print("The projection scans each column from top to bottom, finding")
|
||
|
|
print("the topmost walkable floor with adequate headroom.")
|
||
|
|
print()
|
||
|
|
|
||
|
|
scene = create_demo_scene()
|
||
|
|
mcrfpy.current_scene = scene
|
||
|
|
|
||
|
|
# Print nav grid summary
|
||
|
|
grid_w, grid_d = demo_viewport.grid_size
|
||
|
|
print("Navigation grid summary:")
|
||
|
|
print(f" Grid size: {grid_w}x{grid_d}")
|
||
|
|
|
||
|
|
# Count by walkability and transparency
|
||
|
|
walkable = 0
|
||
|
|
blocking = 0
|
||
|
|
transparent = 0
|
||
|
|
for x in range(grid_w):
|
||
|
|
for z in range(grid_d):
|
||
|
|
cell = demo_viewport.at(x, z)
|
||
|
|
if cell.walkable:
|
||
|
|
walkable += 1
|
||
|
|
else:
|
||
|
|
blocking += 1
|
||
|
|
if cell.transparent:
|
||
|
|
transparent += 1
|
||
|
|
|
||
|
|
print(f" Walkable cells: {walkable}")
|
||
|
|
print(f" Blocking cells: {blocking}")
|
||
|
|
print(f" Transparent cells: {transparent}")
|
||
|
|
print()
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
main()
|
||
|
|
sys.exit(0)
|