3D / voxel unit tests
This commit is contained in:
parent
e12e80e511
commit
71cd2b9b41
22 changed files with 4705 additions and 0 deletions
240
tests/unit/voxel_greedy_meshing_test.py
Normal file
240
tests/unit/voxel_greedy_meshing_test.py
Normal file
|
|
@ -0,0 +1,240 @@
|
|||
#!/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)
|
||||
Loading…
Add table
Add a link
Reference in a new issue