Voxel functionality extension
This commit is contained in:
parent
3e6b6a5847
commit
992ea781cb
14 changed files with 3045 additions and 17 deletions
263
tests/demo/screens/voxel_core_demo.py
Normal file
263
tests/demo/screens/voxel_core_demo.py
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
"""VoxelGrid Core Demo (Milestone 9)
|
||||
|
||||
Demonstrates the VoxelGrid data structure without rendering.
|
||||
This is a "console demo" that creates VoxelGrids, defines materials,
|
||||
places voxel patterns, and displays statistics.
|
||||
|
||||
Note: Visual rendering comes in Milestone 10 (VoxelMeshing).
|
||||
"""
|
||||
import mcrfpy
|
||||
from mcrfpy import Color
|
||||
|
||||
def format_bytes(bytes_val):
|
||||
"""Format bytes as human-readable string"""
|
||||
if bytes_val < 1024:
|
||||
return f"{bytes_val} B"
|
||||
elif bytes_val < 1024 * 1024:
|
||||
return f"{bytes_val / 1024:.1f} KB"
|
||||
else:
|
||||
return f"{bytes_val / (1024 * 1024):.1f} MB"
|
||||
|
||||
def print_header(title):
|
||||
"""Print a formatted header"""
|
||||
print("\n" + "=" * 60)
|
||||
print(f" {title}")
|
||||
print("=" * 60)
|
||||
|
||||
def print_grid_stats(vg, name="VoxelGrid"):
|
||||
"""Print statistics for a VoxelGrid"""
|
||||
print(f"\n {name}:")
|
||||
print(f" Dimensions: {vg.width} x {vg.height} x {vg.depth}")
|
||||
print(f" Total voxels: {vg.width * vg.height * vg.depth:,}")
|
||||
print(f" Cell size: {vg.cell_size} units")
|
||||
print(f" Materials: {vg.material_count}")
|
||||
print(f" Non-air voxels: {vg.count_non_air():,}")
|
||||
print(f" Memory estimate: {format_bytes(vg.width * vg.height * vg.depth)}")
|
||||
print(f" Offset: {vg.offset}")
|
||||
print(f" Rotation: {vg.rotation} deg")
|
||||
|
||||
def demo_basic_creation():
|
||||
"""Demonstrate basic VoxelGrid creation"""
|
||||
print_header("1. Basic VoxelGrid Creation")
|
||||
|
||||
# Create various sizes
|
||||
small = mcrfpy.VoxelGrid(size=(8, 4, 8))
|
||||
medium = mcrfpy.VoxelGrid(size=(16, 8, 16), cell_size=1.0)
|
||||
large = mcrfpy.VoxelGrid(size=(32, 16, 32), cell_size=0.5)
|
||||
|
||||
print_grid_stats(small, "Small (8x4x8)")
|
||||
print_grid_stats(medium, "Medium (16x8x16)")
|
||||
print_grid_stats(large, "Large (32x16x32, 0.5 cell size)")
|
||||
|
||||
def demo_material_palette():
|
||||
"""Demonstrate material palette system"""
|
||||
print_header("2. Material Palette System")
|
||||
|
||||
vg = mcrfpy.VoxelGrid(size=(16, 8, 16))
|
||||
|
||||
# Define a palette of building materials
|
||||
materials = {}
|
||||
materials['stone'] = vg.add_material("stone", color=Color(128, 128, 128))
|
||||
materials['brick'] = vg.add_material("brick", color=Color(165, 42, 42))
|
||||
materials['wood'] = vg.add_material("wood", color=Color(139, 90, 43))
|
||||
materials['glass'] = vg.add_material("glass",
|
||||
color=Color(200, 220, 255, 128),
|
||||
transparent=True,
|
||||
path_cost=1.0)
|
||||
materials['metal'] = vg.add_material("metal",
|
||||
color=Color(180, 180, 190),
|
||||
path_cost=0.8)
|
||||
materials['grass'] = vg.add_material("grass", color=Color(60, 150, 60))
|
||||
|
||||
print(f"\n Defined {vg.material_count} materials:")
|
||||
print(f" ID 0: air (implicit, always transparent)")
|
||||
|
||||
for name, mat_id in materials.items():
|
||||
mat = vg.get_material(mat_id)
|
||||
c = mat['color']
|
||||
props = []
|
||||
if mat['transparent']:
|
||||
props.append("transparent")
|
||||
if mat['path_cost'] != 1.0:
|
||||
props.append(f"cost={mat['path_cost']}")
|
||||
props_str = f" ({', '.join(props)})" if props else ""
|
||||
print(f" ID {mat_id}: {name} RGB({c.r},{c.g},{c.b},{c.a}){props_str}")
|
||||
|
||||
return vg, materials
|
||||
|
||||
def demo_voxel_placement():
|
||||
"""Demonstrate voxel placement patterns"""
|
||||
print_header("3. Voxel Placement Patterns")
|
||||
|
||||
vg, materials = demo_material_palette()
|
||||
stone = materials['stone']
|
||||
brick = materials['brick']
|
||||
wood = materials['wood']
|
||||
|
||||
# Pattern 1: Solid cube
|
||||
print("\n Pattern: Solid 4x4x4 cube at origin")
|
||||
for z in range(4):
|
||||
for y in range(4):
|
||||
for x in range(4):
|
||||
vg.set(x, y, z, stone)
|
||||
print(f" Placed {vg.count_material(stone)} stone voxels")
|
||||
|
||||
# Pattern 2: Checkerboard floor
|
||||
print("\n Pattern: Checkerboard floor at y=0, x=6-14, z=0-8")
|
||||
for z in range(8):
|
||||
for x in range(6, 14):
|
||||
mat = stone if (x + z) % 2 == 0 else brick
|
||||
vg.set(x, 0, z, mat)
|
||||
print(f" Stone: {vg.count_material(stone)}, Brick: {vg.count_material(brick)}")
|
||||
|
||||
# Pattern 3: Hollow cube (walls only)
|
||||
print("\n Pattern: Hollow cube frame 4x4x4 at x=10, z=10")
|
||||
for x in range(4):
|
||||
for y in range(4):
|
||||
for z in range(4):
|
||||
# Only place on edges
|
||||
on_edge_x = (x == 0 or x == 3)
|
||||
on_edge_y = (y == 0 or y == 3)
|
||||
on_edge_z = (z == 0 or z == 3)
|
||||
if sum([on_edge_x, on_edge_y, on_edge_z]) >= 2:
|
||||
vg.set(10 + x, y, 10 + z, wood)
|
||||
print(f" Wood voxels: {vg.count_material(wood)}")
|
||||
|
||||
print_grid_stats(vg, "After patterns")
|
||||
|
||||
# Material breakdown
|
||||
print("\n Material breakdown:")
|
||||
print(f" Air: {vg.count_material(0):,} ({100 * vg.count_material(0) / (16*8*16):.1f}%)")
|
||||
print(f" Stone: {vg.count_material(stone):,}")
|
||||
print(f" Brick: {vg.count_material(brick):,}")
|
||||
print(f" Wood: {vg.count_material(wood):,}")
|
||||
|
||||
def demo_bulk_operations():
|
||||
"""Demonstrate bulk fill and clear operations"""
|
||||
print_header("4. Bulk Operations")
|
||||
|
||||
vg = mcrfpy.VoxelGrid(size=(32, 8, 32))
|
||||
total = 32 * 8 * 32
|
||||
|
||||
stone = vg.add_material("stone", color=Color(128, 128, 128))
|
||||
|
||||
print(f"\n Grid: 32x8x32 = {total:,} voxels")
|
||||
|
||||
# Fill
|
||||
vg.fill(stone)
|
||||
print(f" After fill(stone): {vg.count_non_air():,} non-air")
|
||||
|
||||
# Clear
|
||||
vg.clear()
|
||||
print(f" After clear(): {vg.count_non_air():,} non-air")
|
||||
|
||||
def demo_transforms():
|
||||
"""Demonstrate transform properties"""
|
||||
print_header("5. Transform Properties")
|
||||
|
||||
vg = mcrfpy.VoxelGrid(size=(8, 8, 8))
|
||||
|
||||
print(f"\n Default state:")
|
||||
print(f" Offset: {vg.offset}")
|
||||
print(f" Rotation: {vg.rotation} deg")
|
||||
|
||||
# Position for a building
|
||||
vg.offset = (100.0, 0.0, 50.0)
|
||||
vg.rotation = 45.0
|
||||
|
||||
print(f"\n After positioning:")
|
||||
print(f" Offset: {vg.offset}")
|
||||
print(f" Rotation: {vg.rotation} deg")
|
||||
|
||||
# Multiple buildings with different transforms
|
||||
print("\n Example: Village layout with 3 buildings")
|
||||
buildings = []
|
||||
positions = [(0, 0, 0), (20, 0, 0), (10, 0, 15)]
|
||||
rotations = [0, 90, 45]
|
||||
|
||||
for i, (pos, rot) in enumerate(zip(positions, rotations)):
|
||||
b = mcrfpy.VoxelGrid(size=(8, 6, 8))
|
||||
b.offset = pos
|
||||
b.rotation = rot
|
||||
buildings.append(b)
|
||||
print(f" Building {i+1}: offset={pos}, rotation={rot} deg")
|
||||
|
||||
def demo_edge_cases():
|
||||
"""Test edge cases and limits"""
|
||||
print_header("6. Edge Cases and Limits")
|
||||
|
||||
# Maximum practical size
|
||||
print("\n Testing large grid (64x64x64)...")
|
||||
large = mcrfpy.VoxelGrid(size=(64, 64, 64))
|
||||
mat = large.add_material("test", color=Color(128, 128, 128))
|
||||
large.fill(mat)
|
||||
print(f" Created and filled: {large.count_non_air():,} voxels")
|
||||
large.clear()
|
||||
print(f" Cleared: {large.count_non_air()} voxels")
|
||||
|
||||
# Bounds checking
|
||||
print("\n Bounds checking (should not crash):")
|
||||
small = mcrfpy.VoxelGrid(size=(4, 4, 4))
|
||||
test_mat = small.add_material("test", color=Color(255, 0, 0))
|
||||
small.set(-1, 0, 0, test_mat)
|
||||
small.set(100, 0, 0, test_mat)
|
||||
print(f" Out-of-bounds get(-1,0,0): {small.get(-1, 0, 0)} (expected 0)")
|
||||
print(f" Out-of-bounds get(100,0,0): {small.get(100, 0, 0)} (expected 0)")
|
||||
|
||||
# Material palette capacity
|
||||
print("\n Material palette capacity test:")
|
||||
full_vg = mcrfpy.VoxelGrid(size=(4, 4, 4))
|
||||
for i in range(255):
|
||||
full_vg.add_material(f"mat_{i}", color=Color(i, i, i))
|
||||
print(f" Added 255 materials: count = {full_vg.material_count}")
|
||||
|
||||
try:
|
||||
full_vg.add_material("overflow", color=Color(255, 255, 255))
|
||||
print(" ERROR: Should have raised exception!")
|
||||
except RuntimeError as e:
|
||||
print(f" 256th material correctly rejected: {e}")
|
||||
|
||||
def demo_memory_usage():
|
||||
"""Show memory usage for various grid sizes"""
|
||||
print_header("7. Memory Usage Estimates")
|
||||
|
||||
sizes = [
|
||||
(8, 8, 8),
|
||||
(16, 8, 16),
|
||||
(32, 16, 32),
|
||||
(64, 32, 64),
|
||||
(80, 16, 45), # Example dungeon size
|
||||
]
|
||||
|
||||
print("\n Size Voxels Memory")
|
||||
print(" " + "-" * 40)
|
||||
|
||||
for w, h, d in sizes:
|
||||
voxels = w * h * d
|
||||
memory = voxels # 1 byte per voxel
|
||||
print(f" {w:3}x{h:3}x{d:3} {voxels:>10,} {format_bytes(memory):>10}")
|
||||
|
||||
def main():
|
||||
"""Run all demos"""
|
||||
print("\n" + "=" * 60)
|
||||
print(" VOXELGRID CORE DEMO (Milestone 9)")
|
||||
print(" Dense 3D Voxel Array with Material Palette")
|
||||
print("=" * 60)
|
||||
|
||||
demo_basic_creation()
|
||||
demo_material_palette()
|
||||
demo_voxel_placement()
|
||||
demo_bulk_operations()
|
||||
demo_transforms()
|
||||
demo_edge_cases()
|
||||
demo_memory_usage()
|
||||
|
||||
print_header("Demo Complete!")
|
||||
print("\n Next milestone (10): Voxel Mesh Generation")
|
||||
print(" The VoxelGrid data will be converted to renderable 3D meshes.")
|
||||
print()
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
main()
|
||||
sys.exit(0)
|
||||
273
tests/demo/screens/voxel_dungeon_demo.py
Normal file
273
tests/demo/screens/voxel_dungeon_demo.py
Normal file
|
|
@ -0,0 +1,273 @@
|
|||
# voxel_dungeon_demo.py - Procedural dungeon demonstrating bulk voxel operations
|
||||
# Milestone 11: Bulk Operations and Building Primitives
|
||||
|
||||
import mcrfpy
|
||||
import sys
|
||||
import math
|
||||
import random
|
||||
|
||||
# Create demo scene
|
||||
scene = mcrfpy.Scene("voxel_dungeon_demo")
|
||||
|
||||
# Dark background
|
||||
bg = mcrfpy.Frame(pos=(0, 0), size=(1024, 768), fill_color=mcrfpy.Color(20, 20, 30))
|
||||
scene.children.append(bg)
|
||||
|
||||
# Title
|
||||
title = mcrfpy.Caption(text="Voxel Dungeon Demo - Bulk Operations (Milestone 11)", pos=(20, 10))
|
||||
title.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
scene.children.append(title)
|
||||
|
||||
# Create the 3D viewport
|
||||
viewport = mcrfpy.Viewport3D(
|
||||
pos=(50, 60),
|
||||
size=(620, 520),
|
||||
render_resolution=(400, 320),
|
||||
fov=60.0,
|
||||
camera_pos=(40.0, 30.0, 40.0),
|
||||
camera_target=(16.0, 4.0, 16.0),
|
||||
bg_color=mcrfpy.Color(30, 30, 40) # Dark atmosphere
|
||||
)
|
||||
scene.children.append(viewport)
|
||||
|
||||
# Global voxel grid reference
|
||||
voxels = None
|
||||
seed = 42
|
||||
|
||||
def generate_dungeon(dungeon_seed=42):
|
||||
"""Generate a procedural dungeon showcasing all bulk operations"""
|
||||
global voxels, seed
|
||||
seed = dungeon_seed
|
||||
random.seed(seed)
|
||||
|
||||
# Create voxel grid for dungeon
|
||||
print(f"Generating dungeon (seed={seed})...")
|
||||
voxels = mcrfpy.VoxelGrid(size=(32, 12, 32), cell_size=1.0)
|
||||
|
||||
# Define materials
|
||||
STONE_WALL = voxels.add_material("stone_wall", color=mcrfpy.Color(80, 80, 90))
|
||||
STONE_FLOOR = voxels.add_material("stone_floor", color=mcrfpy.Color(100, 95, 90))
|
||||
MOSS = voxels.add_material("moss", color=mcrfpy.Color(40, 80, 40))
|
||||
WATER = voxels.add_material("water", color=mcrfpy.Color(40, 80, 160, 180), transparent=True)
|
||||
PILLAR = voxels.add_material("pillar", color=mcrfpy.Color(120, 110, 100))
|
||||
GOLD = voxels.add_material("gold", color=mcrfpy.Color(255, 215, 0))
|
||||
|
||||
print(f"Defined {voxels.material_count} materials")
|
||||
|
||||
# 1. Main room using fill_box_hollow
|
||||
print("Building main room with fill_box_hollow...")
|
||||
voxels.fill_box_hollow((2, 0, 2), (29, 10, 29), STONE_WALL, thickness=1)
|
||||
|
||||
# 2. Floor with slight variation using fill_box
|
||||
voxels.fill_box((3, 0, 3), (28, 0, 28), STONE_FLOOR)
|
||||
|
||||
# 3. Spherical alcoves carved into walls using fill_sphere
|
||||
print("Carving alcoves with fill_sphere...")
|
||||
alcove_positions = [
|
||||
(2, 5, 16), # West wall
|
||||
(29, 5, 16), # East wall
|
||||
(16, 5, 2), # North wall
|
||||
(16, 5, 29), # South wall
|
||||
]
|
||||
for pos in alcove_positions:
|
||||
voxels.fill_sphere(pos, 3, 0) # Carve out (air)
|
||||
|
||||
# 4. Small decorative spheres (gold orbs in alcoves)
|
||||
print("Adding gold orbs in alcoves...")
|
||||
for i, pos in enumerate(alcove_positions):
|
||||
# Offset inward so orb is visible
|
||||
ox, oy, oz = pos
|
||||
if ox < 10:
|
||||
ox += 2
|
||||
elif ox > 20:
|
||||
ox -= 2
|
||||
if oz < 10:
|
||||
oz += 2
|
||||
elif oz > 20:
|
||||
oz -= 2
|
||||
voxels.fill_sphere((ox, oy - 1, oz), 1, GOLD)
|
||||
|
||||
# 5. Support pillars using fill_cylinder
|
||||
print("Building pillars with fill_cylinder...")
|
||||
pillar_positions = [
|
||||
(8, 1, 8), (8, 1, 24),
|
||||
(24, 1, 8), (24, 1, 24),
|
||||
(16, 1, 8), (16, 1, 24),
|
||||
(8, 1, 16), (24, 1, 16),
|
||||
]
|
||||
for px, py, pz in pillar_positions:
|
||||
voxels.fill_cylinder((px, py, pz), 1, 9, PILLAR)
|
||||
|
||||
# 6. Moss patches using fill_noise
|
||||
print("Adding moss patches with fill_noise...")
|
||||
voxels.fill_noise((3, 1, 3), (28, 1, 28), MOSS, threshold=0.65, scale=0.15, seed=seed)
|
||||
|
||||
# 7. Central water pool
|
||||
print("Creating water pool...")
|
||||
voxels.fill_box((12, 0, 12), (20, 0, 20), 0) # Carve depression
|
||||
voxels.fill_box((12, 0, 12), (20, 0, 20), WATER)
|
||||
|
||||
# 8. Copy a pillar as prefab and paste variations
|
||||
print("Creating prefab from pillar and pasting copies...")
|
||||
pillar_prefab = voxels.copy_region((8, 1, 8), (9, 9, 9))
|
||||
print(f" Pillar prefab: {pillar_prefab.size}")
|
||||
|
||||
# Paste smaller pillars at corners (offset from main room)
|
||||
corner_positions = [(4, 1, 4), (4, 1, 27), (27, 1, 4), (27, 1, 27)]
|
||||
for cx, cy, cz in corner_positions:
|
||||
voxels.paste_region(pillar_prefab, (cx, cy, cz), skip_air=True)
|
||||
|
||||
# Build mesh
|
||||
voxels.rebuild_mesh()
|
||||
|
||||
print(f"\nDungeon generated:")
|
||||
print(f" Non-air voxels: {voxels.count_non_air()}")
|
||||
print(f" Vertices: {voxels.vertex_count}")
|
||||
print(f" Faces: {voxels.vertex_count // 6}")
|
||||
|
||||
# Add to viewport
|
||||
# First remove old layer if exists
|
||||
if viewport.voxel_layer_count() > 0:
|
||||
pass # Can't easily remove, so we regenerate the whole viewport
|
||||
viewport.add_voxel_layer(voxels, z_index=0)
|
||||
|
||||
return voxels
|
||||
|
||||
# Generate initial dungeon
|
||||
generate_dungeon(42)
|
||||
|
||||
# Create info panel
|
||||
info_frame = mcrfpy.Frame(pos=(690, 60), size=(300, 280), fill_color=mcrfpy.Color(40, 40, 60, 220))
|
||||
scene.children.append(info_frame)
|
||||
|
||||
info_title = mcrfpy.Caption(text="Dungeon Stats", pos=(700, 70))
|
||||
info_title.fill_color = mcrfpy.Color(255, 255, 100)
|
||||
scene.children.append(info_title)
|
||||
|
||||
def update_stats():
|
||||
global stats_caption
|
||||
stats_text = f"""Grid: {voxels.width}x{voxels.height}x{voxels.depth}
|
||||
Total cells: {voxels.width * voxels.height * voxels.depth}
|
||||
Non-air: {voxels.count_non_air()}
|
||||
Materials: {voxels.material_count}
|
||||
|
||||
Mesh Stats:
|
||||
Vertices: {voxels.vertex_count}
|
||||
Faces: {voxels.vertex_count // 6}
|
||||
|
||||
Seed: {seed}
|
||||
|
||||
Operations Used:
|
||||
- fill_box_hollow (walls)
|
||||
- fill_sphere (alcoves)
|
||||
- fill_cylinder (pillars)
|
||||
- fill_noise (moss)
|
||||
- copy/paste (prefabs)"""
|
||||
stats_caption.text = stats_text
|
||||
|
||||
stats_caption = mcrfpy.Caption(text="", pos=(700, 100))
|
||||
stats_caption.fill_color = mcrfpy.Color(200, 200, 200)
|
||||
scene.children.append(stats_caption)
|
||||
update_stats()
|
||||
|
||||
# Controls panel
|
||||
controls_frame = mcrfpy.Frame(pos=(690, 360), size=(300, 180), fill_color=mcrfpy.Color(40, 40, 60, 220))
|
||||
scene.children.append(controls_frame)
|
||||
|
||||
controls_title = mcrfpy.Caption(text="Controls", pos=(700, 370))
|
||||
controls_title.fill_color = mcrfpy.Color(255, 255, 100)
|
||||
scene.children.append(controls_title)
|
||||
|
||||
controls_text = """R - Regenerate dungeon (new seed)
|
||||
1-4 - Camera presets
|
||||
+/- - Zoom in/out
|
||||
SPACE - Reset camera
|
||||
ESC - Exit demo"""
|
||||
|
||||
controls = mcrfpy.Caption(text=controls_text, pos=(700, 400))
|
||||
controls.fill_color = mcrfpy.Color(200, 200, 200)
|
||||
scene.children.append(controls)
|
||||
|
||||
# Camera animation state
|
||||
rotation_enabled = False
|
||||
camera_distance = 50.0
|
||||
camera_angle = 45.0 # degrees
|
||||
camera_height = 30.0
|
||||
|
||||
camera_presets = [
|
||||
(40.0, 30.0, 40.0, 16.0, 4.0, 16.0), # Default diagonal
|
||||
(16.0, 30.0, 50.0, 16.0, 4.0, 16.0), # Front view
|
||||
(50.0, 30.0, 16.0, 16.0, 4.0, 16.0), # Side view
|
||||
(16.0, 50.0, 16.0, 16.0, 4.0, 16.0), # Top-down
|
||||
]
|
||||
|
||||
def rotate_camera(timer_name, runtime):
|
||||
"""Timer callback for camera rotation"""
|
||||
global camera_angle, rotation_enabled
|
||||
if rotation_enabled:
|
||||
camera_angle += 0.5
|
||||
if camera_angle >= 360.0:
|
||||
camera_angle = 0.0
|
||||
rad = camera_angle * math.pi / 180.0
|
||||
x = 16.0 + camera_distance * math.cos(rad)
|
||||
z = 16.0 + camera_distance * math.sin(rad)
|
||||
viewport.camera_pos = (x, camera_height, z)
|
||||
|
||||
# Set up rotation timer
|
||||
timer = mcrfpy.Timer("rotate_cam", rotate_camera, 33)
|
||||
|
||||
def handle_key(key, action):
|
||||
"""Keyboard handler"""
|
||||
global rotation_enabled, seed, camera_distance, camera_height
|
||||
if action != mcrfpy.InputState.PRESSED:
|
||||
return
|
||||
|
||||
if key == mcrfpy.Key.R:
|
||||
seed = random.randint(1, 99999)
|
||||
generate_dungeon(seed)
|
||||
update_stats()
|
||||
print(f"Regenerated dungeon with seed {seed}")
|
||||
elif key == mcrfpy.Key.NUM_1:
|
||||
viewport.camera_pos = camera_presets[0][:3]
|
||||
viewport.camera_target = camera_presets[0][3:]
|
||||
rotation_enabled = False
|
||||
elif key == mcrfpy.Key.NUM_2:
|
||||
viewport.camera_pos = camera_presets[1][:3]
|
||||
viewport.camera_target = camera_presets[1][3:]
|
||||
rotation_enabled = False
|
||||
elif key == mcrfpy.Key.NUM_3:
|
||||
viewport.camera_pos = camera_presets[2][:3]
|
||||
viewport.camera_target = camera_presets[2][3:]
|
||||
rotation_enabled = False
|
||||
elif key == mcrfpy.Key.NUM_4:
|
||||
viewport.camera_pos = camera_presets[3][:3]
|
||||
viewport.camera_target = camera_presets[3][3:]
|
||||
rotation_enabled = False
|
||||
elif key == mcrfpy.Key.SPACE:
|
||||
rotation_enabled = not rotation_enabled
|
||||
print(f"Camera rotation: {'ON' if rotation_enabled else 'OFF'}")
|
||||
elif key == mcrfpy.Key.EQUALS or key == mcrfpy.Key.ADD:
|
||||
camera_distance = max(20.0, camera_distance - 5.0)
|
||||
camera_height = max(15.0, camera_height - 2.0)
|
||||
elif key == mcrfpy.Key.DASH or key == mcrfpy.Key.SUBTRACT:
|
||||
camera_distance = min(80.0, camera_distance + 5.0)
|
||||
camera_height = min(50.0, camera_height + 2.0)
|
||||
elif key == mcrfpy.Key.ESCAPE:
|
||||
print("Exiting demo...")
|
||||
sys.exit(0)
|
||||
|
||||
scene.on_key = handle_key
|
||||
|
||||
# Activate the scene
|
||||
mcrfpy.current_scene = scene
|
||||
print("\nVoxel Dungeon Demo ready!")
|
||||
print("Press SPACE to toggle camera rotation, R to regenerate")
|
||||
|
||||
# Main entry point for --exec mode
|
||||
if __name__ == "__main__":
|
||||
print("\n=== Voxel Dungeon Demo Summary ===")
|
||||
print(f"Grid size: {voxels.width}x{voxels.height}x{voxels.depth}")
|
||||
print(f"Non-air voxels: {voxels.count_non_air()}")
|
||||
print(f"Generated vertices: {voxels.vertex_count}")
|
||||
print(f"Rendered faces: {voxels.vertex_count // 6}")
|
||||
print("===================================\n")
|
||||
250
tests/demo/screens/voxel_navigation_demo.py
Normal file
250
tests/demo/screens/voxel_navigation_demo.py
Normal file
|
|
@ -0,0 +1,250 @@
|
|||
#!/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)
|
||||
314
tests/demo/screens/voxel_serialization_demo.py
Normal file
314
tests/demo/screens/voxel_serialization_demo.py
Normal file
|
|
@ -0,0 +1,314 @@
|
|||
"""Voxel Serialization Demo - Milestone 14
|
||||
|
||||
Demonstrates save/load functionality for VoxelGrid, including:
|
||||
- Saving to file with .mcvg format
|
||||
- Loading from file
|
||||
- Serialization to bytes (for network/custom storage)
|
||||
- RLE compression effectiveness
|
||||
"""
|
||||
|
||||
import mcrfpy
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
def create_demo_scene():
|
||||
"""Create a scene demonstrating voxel serialization."""
|
||||
scene = mcrfpy.Scene("voxel_serialization_demo")
|
||||
ui = scene.children
|
||||
|
||||
# Dark background
|
||||
bg = mcrfpy.Frame(pos=(0, 0), size=(1024, 768), fill_color=(20, 20, 30))
|
||||
ui.append(bg)
|
||||
|
||||
# Title
|
||||
title = mcrfpy.Caption(text="Milestone 14: VoxelGrid Serialization",
|
||||
pos=(30, 20))
|
||||
title.font_size = 28
|
||||
title.fill_color = (255, 220, 100)
|
||||
ui.append(title)
|
||||
|
||||
# Create demo VoxelGrid with interesting structure
|
||||
grid = mcrfpy.VoxelGrid((16, 16, 16), cell_size=1.0)
|
||||
|
||||
# Add materials
|
||||
stone = grid.add_material("stone", (100, 100, 110))
|
||||
wood = grid.add_material("wood", (139, 90, 43))
|
||||
glass = grid.add_material("glass", (180, 200, 220, 100), transparent=True)
|
||||
gold = grid.add_material("gold", (255, 215, 0))
|
||||
|
||||
# Build a small structure
|
||||
grid.fill_box((0, 0, 0), (15, 0, 15), stone) # Floor
|
||||
grid.fill_box((0, 1, 0), (0, 4, 15), stone) # Wall 1
|
||||
grid.fill_box((15, 1, 0), (15, 4, 15), stone) # Wall 2
|
||||
grid.fill_box((0, 1, 0), (15, 4, 0), stone) # Wall 3
|
||||
grid.fill_box((0, 1, 15), (15, 4, 15), stone) # Wall 4
|
||||
|
||||
# Windows (clear some wall, add glass)
|
||||
grid.fill_box((6, 2, 0), (10, 3, 0), 0) # Clear for window
|
||||
grid.fill_box((6, 2, 0), (10, 3, 0), glass) # Add glass
|
||||
|
||||
# Pillars
|
||||
grid.fill_box((4, 1, 4), (4, 3, 4), wood)
|
||||
grid.fill_box((12, 1, 4), (12, 3, 4), wood)
|
||||
grid.fill_box((4, 1, 12), (4, 3, 12), wood)
|
||||
grid.fill_box((12, 1, 12), (12, 3, 12), wood)
|
||||
|
||||
# Gold decorations
|
||||
grid.set(8, 1, 8, gold)
|
||||
grid.set(7, 1, 8, gold)
|
||||
grid.set(9, 1, 8, gold)
|
||||
grid.set(8, 1, 7, gold)
|
||||
grid.set(8, 1, 9, gold)
|
||||
|
||||
# Get original stats
|
||||
original_voxels = grid.count_non_air()
|
||||
original_materials = grid.material_count
|
||||
|
||||
# === Test save/load to file ===
|
||||
with tempfile.NamedTemporaryFile(suffix='.mcvg', delete=False) as f:
|
||||
temp_path = f.name
|
||||
|
||||
save_success = grid.save(temp_path)
|
||||
file_size = os.path.getsize(temp_path) if save_success else 0
|
||||
|
||||
# Load into new grid
|
||||
loaded_grid = mcrfpy.VoxelGrid((1, 1, 1))
|
||||
load_success = loaded_grid.load(temp_path)
|
||||
os.unlink(temp_path) # Clean up
|
||||
|
||||
loaded_voxels = loaded_grid.count_non_air() if load_success else 0
|
||||
loaded_materials = loaded_grid.material_count if load_success else 0
|
||||
|
||||
# === Test to_bytes/from_bytes ===
|
||||
data_bytes = grid.to_bytes()
|
||||
bytes_size = len(data_bytes)
|
||||
|
||||
bytes_grid = mcrfpy.VoxelGrid((1, 1, 1))
|
||||
bytes_success = bytes_grid.from_bytes(data_bytes)
|
||||
bytes_voxels = bytes_grid.count_non_air() if bytes_success else 0
|
||||
|
||||
# === Calculate compression ===
|
||||
raw_size = 16 * 16 * 16 # Uncompressed voxel data
|
||||
compression_ratio = raw_size / bytes_size if bytes_size > 0 else 0
|
||||
|
||||
# Display information
|
||||
y_pos = 80
|
||||
|
||||
# Original Grid Info
|
||||
info1 = mcrfpy.Caption(text="Original VoxelGrid:",
|
||||
pos=(30, y_pos))
|
||||
info1.font_size = 20
|
||||
info1.fill_color = (100, 200, 255)
|
||||
ui.append(info1)
|
||||
y_pos += 30
|
||||
|
||||
for line in [
|
||||
f" Dimensions: 16x16x16 = 4096 voxels",
|
||||
f" Non-air voxels: {original_voxels}",
|
||||
f" Materials defined: {original_materials}",
|
||||
f" Structure: Walled room with pillars, windows, gold decor"
|
||||
]:
|
||||
cap = mcrfpy.Caption(text=line, pos=(30, y_pos))
|
||||
cap.font_size = 16
|
||||
cap.fill_color = (200, 200, 210)
|
||||
ui.append(cap)
|
||||
y_pos += 22
|
||||
|
||||
y_pos += 20
|
||||
|
||||
# File Save/Load Results
|
||||
info2 = mcrfpy.Caption(text="File Serialization (.mcvg):",
|
||||
pos=(30, y_pos))
|
||||
info2.font_size = 20
|
||||
info2.fill_color = (100, 255, 150)
|
||||
ui.append(info2)
|
||||
y_pos += 30
|
||||
|
||||
save_status = "SUCCESS" if save_success else "FAILED"
|
||||
load_status = "SUCCESS" if load_success else "FAILED"
|
||||
match_status = "MATCH" if loaded_voxels == original_voxels else "MISMATCH"
|
||||
|
||||
for line in [
|
||||
f" Save to file: {save_status}",
|
||||
f" File size: {file_size} bytes",
|
||||
f" Load from file: {load_status}",
|
||||
f" Loaded voxels: {loaded_voxels} ({match_status})",
|
||||
f" Loaded materials: {loaded_materials}"
|
||||
]:
|
||||
color = (150, 255, 150) if "SUCCESS" in line or "MATCH" in line else (200, 200, 210)
|
||||
if "FAILED" in line or "MISMATCH" in line:
|
||||
color = (255, 100, 100)
|
||||
cap = mcrfpy.Caption(text=line, pos=(30, y_pos))
|
||||
cap.font_size = 16
|
||||
cap.fill_color = color
|
||||
ui.append(cap)
|
||||
y_pos += 22
|
||||
|
||||
y_pos += 20
|
||||
|
||||
# Bytes Serialization Results
|
||||
info3 = mcrfpy.Caption(text="Memory Serialization (to_bytes/from_bytes):",
|
||||
pos=(30, y_pos))
|
||||
info3.font_size = 20
|
||||
info3.fill_color = (255, 200, 100)
|
||||
ui.append(info3)
|
||||
y_pos += 30
|
||||
|
||||
bytes_status = "SUCCESS" if bytes_success else "FAILED"
|
||||
bytes_match = "MATCH" if bytes_voxels == original_voxels else "MISMATCH"
|
||||
|
||||
for line in [
|
||||
f" Serialized size: {bytes_size} bytes",
|
||||
f" Raw voxel data: {raw_size} bytes",
|
||||
f" Compression ratio: {compression_ratio:.1f}x",
|
||||
f" from_bytes(): {bytes_status}",
|
||||
f" Restored voxels: {bytes_voxels} ({bytes_match})"
|
||||
]:
|
||||
color = (200, 200, 210)
|
||||
if "SUCCESS" in line or "MATCH" in line:
|
||||
color = (150, 255, 150)
|
||||
cap = mcrfpy.Caption(text=line, pos=(30, y_pos))
|
||||
cap.font_size = 16
|
||||
cap.fill_color = color
|
||||
ui.append(cap)
|
||||
y_pos += 22
|
||||
|
||||
y_pos += 20
|
||||
|
||||
# RLE Compression Demo
|
||||
info4 = mcrfpy.Caption(text="RLE Compression Effectiveness:",
|
||||
pos=(30, y_pos))
|
||||
info4.font_size = 20
|
||||
info4.fill_color = (200, 150, 255)
|
||||
ui.append(info4)
|
||||
y_pos += 30
|
||||
|
||||
# Create uniform grid for compression test
|
||||
uniform_grid = mcrfpy.VoxelGrid((32, 32, 32))
|
||||
uniform_mat = uniform_grid.add_material("solid", (128, 128, 128))
|
||||
uniform_grid.fill(uniform_mat)
|
||||
uniform_bytes = uniform_grid.to_bytes()
|
||||
uniform_raw = 32 * 32 * 32
|
||||
uniform_ratio = uniform_raw / len(uniform_bytes)
|
||||
|
||||
for line in [
|
||||
f" Uniform 32x32x32 filled grid:",
|
||||
f" Raw: {uniform_raw} bytes",
|
||||
f" Compressed: {len(uniform_bytes)} bytes",
|
||||
f" Compression: {uniform_ratio:.0f}x",
|
||||
f" ",
|
||||
f" RLE excels at runs of identical values."
|
||||
]:
|
||||
cap = mcrfpy.Caption(text=line, pos=(30, y_pos))
|
||||
cap.font_size = 16
|
||||
cap.fill_color = (200, 180, 220)
|
||||
ui.append(cap)
|
||||
y_pos += 22
|
||||
|
||||
y_pos += 30
|
||||
|
||||
# File Format Info
|
||||
info5 = mcrfpy.Caption(text="File Format (.mcvg):",
|
||||
pos=(30, y_pos))
|
||||
info5.font_size = 20
|
||||
info5.fill_color = (255, 150, 200)
|
||||
ui.append(info5)
|
||||
y_pos += 30
|
||||
|
||||
for line in [
|
||||
" Header: Magic 'MCVG' + version + dimensions + cell_size",
|
||||
" Materials: name, color (RGBA), sprite_index, transparent, path_cost",
|
||||
" Voxel data: RLE-encoded material IDs",
|
||||
" ",
|
||||
" Note: Transform (offset, rotation) is runtime state, not serialized"
|
||||
]:
|
||||
cap = mcrfpy.Caption(text=line, pos=(30, y_pos))
|
||||
cap.font_size = 14
|
||||
cap.fill_color = (200, 180, 200)
|
||||
ui.append(cap)
|
||||
y_pos += 20
|
||||
|
||||
# API Reference on right side
|
||||
y_ref = 80
|
||||
x_ref = 550
|
||||
|
||||
api_title = mcrfpy.Caption(text="Python API:", pos=(x_ref, y_ref))
|
||||
api_title.font_size = 20
|
||||
api_title.fill_color = (150, 200, 255)
|
||||
ui.append(api_title)
|
||||
y_ref += 35
|
||||
|
||||
for line in [
|
||||
"# Save to file",
|
||||
"success = grid.save('world.mcvg')",
|
||||
"",
|
||||
"# Load from file",
|
||||
"grid = VoxelGrid((1,1,1))",
|
||||
"success = grid.load('world.mcvg')",
|
||||
"",
|
||||
"# Save to bytes",
|
||||
"data = grid.to_bytes()",
|
||||
"",
|
||||
"# Load from bytes",
|
||||
"success = grid.from_bytes(data)",
|
||||
"",
|
||||
"# Network example:",
|
||||
"# send_to_server(grid.to_bytes())",
|
||||
"# data = recv_from_server()",
|
||||
"# grid.from_bytes(data)"
|
||||
]:
|
||||
cap = mcrfpy.Caption(text=line, pos=(x_ref, y_ref))
|
||||
cap.font_size = 14
|
||||
if line.startswith("#"):
|
||||
cap.fill_color = (100, 150, 100)
|
||||
elif "=" in line or "(" in line:
|
||||
cap.fill_color = (255, 220, 150)
|
||||
else:
|
||||
cap.fill_color = (180, 180, 180)
|
||||
ui.append(cap)
|
||||
y_ref += 18
|
||||
|
||||
return scene
|
||||
|
||||
|
||||
# Run demonstration
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
# Create and activate the scene
|
||||
scene = create_demo_scene()
|
||||
mcrfpy.current_scene = scene
|
||||
|
||||
# When run directly, print summary and exit for headless testing
|
||||
print("\n=== Voxel Serialization Demo (Milestone 14) ===\n")
|
||||
|
||||
# Run a quick verification
|
||||
grid = mcrfpy.VoxelGrid((8, 8, 8))
|
||||
mat = grid.add_material("test", (100, 100, 100))
|
||||
grid.fill_box((0, 0, 0), (7, 0, 7), mat)
|
||||
|
||||
print(f"Created 8x8x8 grid with {grid.count_non_air()} non-air voxels")
|
||||
|
||||
# Test to_bytes
|
||||
data = grid.to_bytes()
|
||||
print(f"Serialized to {len(data)} bytes")
|
||||
|
||||
# Test from_bytes
|
||||
grid2 = mcrfpy.VoxelGrid((1, 1, 1))
|
||||
success = grid2.from_bytes(data)
|
||||
print(f"from_bytes(): {'SUCCESS' if success else 'FAILED'}")
|
||||
print(f"Restored size: {grid2.size}")
|
||||
print(f"Restored voxels: {grid2.count_non_air()}")
|
||||
|
||||
# Compression test
|
||||
big_grid = mcrfpy.VoxelGrid((32, 32, 32))
|
||||
big_mat = big_grid.add_material("solid", (128, 128, 128))
|
||||
big_grid.fill(big_mat)
|
||||
big_data = big_grid.to_bytes()
|
||||
raw_size = 32 * 32 * 32
|
||||
print(f"\nCompression test (32x32x32 uniform):")
|
||||
print(f" Raw: {raw_size} bytes")
|
||||
print(f" Compressed: {len(big_data)} bytes")
|
||||
print(f" Ratio: {raw_size / len(big_data):.0f}x")
|
||||
|
||||
print("\n=== Demo complete ===")
|
||||
sys.exit(0)
|
||||
Loading…
Add table
Add a link
Reference in a new issue