McRogueFace/tests/unit/voxel_greedy_meshing_test.py

240 lines
8.1 KiB
Python
Raw Permalink Normal View History

2026-02-06 16:15:07 -05:00
#!/usr/bin/env python3
"""Unit tests for Milestone 13: Greedy Meshing
Tests that greedy meshing produces correct mesh geometry with reduced vertex count.
"""
import mcrfpy
import sys
# Test counters
tests_passed = 0
tests_failed = 0
def test(name, condition):
"""Simple test helper"""
global tests_passed, tests_failed
if condition:
tests_passed += 1
print(f" PASS: {name}")
else:
tests_failed += 1
print(f" FAIL: {name}")
# =============================================================================
# Test greedy meshing property
# =============================================================================
print("\n=== Testing greedy_meshing property ===")
vg = mcrfpy.VoxelGrid((8, 8, 8), cell_size=1.0)
test("Default greedy_meshing is False", vg.greedy_meshing == False)
vg.greedy_meshing = True
test("Can enable greedy_meshing", vg.greedy_meshing == True)
vg.greedy_meshing = False
test("Can disable greedy_meshing", vg.greedy_meshing == False)
# =============================================================================
# Test vertex count reduction
# =============================================================================
print("\n=== Testing vertex count reduction ===")
# Create a solid 4x4x4 cube - this should benefit greatly from greedy meshing
# Non-greedy: 6 faces per voxel for exposed faces = many quads
# Greedy: 6 large quads (one per side)
vg2 = mcrfpy.VoxelGrid((4, 4, 4), cell_size=1.0)
stone = vg2.add_material("stone", (128, 128, 128))
vg2.fill((stone)) # Fill entire grid
# Get vertex count with standard meshing
vg2.greedy_meshing = False
vg2.rebuild_mesh()
standard_vertices = vg2.vertex_count
print(f" Standard meshing: {standard_vertices} vertices")
# Get vertex count with greedy meshing
vg2.greedy_meshing = True
vg2.rebuild_mesh()
greedy_vertices = vg2.vertex_count
print(f" Greedy meshing: {greedy_vertices} vertices")
# For a solid 4x4x4 cube, standard meshing creates:
# Each face of the cube is 4x4 = 16 voxel faces
# 6 cube faces * 16 faces/side * 6 vertices/face = 576 vertices
# Greedy meshing creates:
# 6 cube faces * 1 merged quad * 6 vertices/quad = 36 vertices
test("Greedy meshing reduces vertex count", greedy_vertices < standard_vertices)
test("Solid cube greedy: 36 vertices (6 faces * 6 verts)", greedy_vertices == 36)
# =============================================================================
# Test larger solid block
# =============================================================================
print("\n=== Testing larger solid block ===")
vg3 = mcrfpy.VoxelGrid((16, 16, 16), cell_size=1.0)
stone3 = vg3.add_material("stone", (128, 128, 128))
vg3.fill(stone3)
vg3.greedy_meshing = False
vg3.rebuild_mesh()
standard_verts_large = vg3.vertex_count
print(f" Standard: {standard_verts_large} vertices")
vg3.greedy_meshing = True
vg3.rebuild_mesh()
greedy_verts_large = vg3.vertex_count
print(f" Greedy: {greedy_verts_large} vertices")
# 16x16 faces = 256 quads per side -> 1 quad per side with greedy
# Reduction factor should be significant
reduction_factor = standard_verts_large / greedy_verts_large if greedy_verts_large > 0 else 0
print(f" Reduction factor: {reduction_factor:.1f}x")
test("Large block greedy: still 36 vertices", greedy_verts_large == 36)
test("Significant vertex reduction (>10x)", reduction_factor > 10)
# =============================================================================
# Test checkerboard pattern (worst case for greedy)
# =============================================================================
print("\n=== Testing checkerboard pattern (greedy stress test) ===")
vg4 = mcrfpy.VoxelGrid((4, 4, 4), cell_size=1.0)
stone4 = vg4.add_material("stone", (128, 128, 128))
# Create checkerboard pattern - no adjacent same-material voxels
for z in range(4):
for y in range(4):
for x in range(4):
if (x + y + z) % 2 == 0:
vg4.set(x, y, z, stone4)
vg4.greedy_meshing = False
vg4.rebuild_mesh()
standard_checker = vg4.vertex_count
print(f" Standard: {standard_checker} vertices")
vg4.greedy_meshing = True
vg4.rebuild_mesh()
greedy_checker = vg4.vertex_count
print(f" Greedy: {greedy_checker} vertices")
# In checkerboard, greedy meshing can't merge much, so counts should be similar
test("Checkerboard: greedy meshing works (produces vertices)", greedy_checker > 0)
# Greedy might still reduce a bit due to row merging
test("Checkerboard: greedy <= standard", greedy_checker <= standard_checker)
# =============================================================================
# Test different materials (no cross-material merging)
# =============================================================================
print("\n=== Testing multi-material (no cross-material merging) ===")
vg5 = mcrfpy.VoxelGrid((4, 4, 4), cell_size=1.0)
red = vg5.add_material("red", (255, 0, 0))
blue = vg5.add_material("blue", (0, 0, 255))
# Half red, half blue
vg5.fill_box((0, 0, 0), (1, 3, 3), red)
vg5.fill_box((2, 0, 0), (3, 3, 3), blue)
vg5.greedy_meshing = True
vg5.rebuild_mesh()
multi_material_verts = vg5.vertex_count
print(f" Multi-material greedy: {multi_material_verts} vertices")
# Should have 6 quads per material half = 12 quads = 72 vertices
# But there's a shared face between them that gets culled
# Actually: each 2x4x4 block has 5 exposed faces (not the shared internal face)
# So 5 + 5 = 10 quads = 60 vertices, but may be more due to the contact face
test("Multi-material produces vertices", multi_material_verts > 0)
# =============================================================================
# Test hollow box (interior faces)
# =============================================================================
print("\n=== Testing hollow box ===")
vg6 = mcrfpy.VoxelGrid((8, 8, 8), cell_size=1.0)
stone6 = vg6.add_material("stone", (128, 128, 128))
vg6.fill_box_hollow((0, 0, 0), (7, 7, 7), stone6, thickness=1)
vg6.greedy_meshing = False
vg6.rebuild_mesh()
standard_hollow = vg6.vertex_count
print(f" Standard: {standard_hollow} vertices")
vg6.greedy_meshing = True
vg6.rebuild_mesh()
greedy_hollow = vg6.vertex_count
print(f" Greedy: {greedy_hollow} vertices")
# Hollow box has 6 outer faces and 6 inner faces
# Greedy should merge each face into one quad
# Expected: 12 quads * 6 verts = 72 vertices
test("Hollow box: greedy reduces vertices", greedy_hollow < standard_hollow)
# =============================================================================
# Test floor slab (single layer)
# =============================================================================
print("\n=== Testing floor slab (single layer) ===")
vg7 = mcrfpy.VoxelGrid((10, 1, 10), cell_size=1.0)
floor_mat = vg7.add_material("floor", (100, 80, 60))
vg7.fill(floor_mat)
vg7.greedy_meshing = False
vg7.rebuild_mesh()
standard_floor = vg7.vertex_count
print(f" Standard: {standard_floor} vertices")
vg7.greedy_meshing = True
vg7.rebuild_mesh()
greedy_floor = vg7.vertex_count
print(f" Greedy: {greedy_floor} vertices")
# Floor slab: 10x10 top face + 10x10 bottom face + 4 edge faces (10x1 each)
# Greedy: 6 quads = 36 vertices
test("Floor slab: greedy = 36 vertices", greedy_floor == 36)
# =============================================================================
# Test that mesh is marked dirty when property changes
# =============================================================================
print("\n=== Testing dirty flag behavior ===")
vg8 = mcrfpy.VoxelGrid((4, 4, 4), cell_size=1.0)
stone8 = vg8.add_material("stone", (128, 128, 128))
vg8.fill(stone8)
# Build mesh first
vg8.greedy_meshing = False
vg8.rebuild_mesh()
first_count = vg8.vertex_count
# Change greedy_meshing - mesh should be marked dirty
vg8.greedy_meshing = True
# Rebuild
vg8.rebuild_mesh()
second_count = vg8.vertex_count
test("Changing greedy_meshing affects vertex count", first_count != second_count)
# =============================================================================
# Summary
# =============================================================================
print(f"\n=== Results: {tests_passed} passed, {tests_failed} failed ===")
if tests_failed > 0:
sys.exit(1)
else:
print("All tests passed!")
sys.exit(0)