#!/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)