202 lines
6.8 KiB
Python
202 lines
6.8 KiB
Python
|
|
# meshlayer_test.py - Unit tests for MeshLayer terrain system
|
||
|
|
# Tests HeightMap to 3D mesh conversion via Viewport3D
|
||
|
|
|
||
|
|
import mcrfpy
|
||
|
|
import sys
|
||
|
|
|
||
|
|
def test_viewport3d_layer_creation():
|
||
|
|
"""Test that layers can be created and managed"""
|
||
|
|
print("Testing Viewport3D layer creation...")
|
||
|
|
|
||
|
|
viewport = mcrfpy.Viewport3D(pos=(0, 0), size=(320, 240))
|
||
|
|
|
||
|
|
# Initial layer count should be 0
|
||
|
|
assert viewport.layer_count() == 0, f"Expected 0 layers, got {viewport.layer_count()}"
|
||
|
|
|
||
|
|
# Add a layer
|
||
|
|
layer_info = viewport.add_layer("test_layer", z_index=5)
|
||
|
|
assert layer_info is not None, "add_layer returned None"
|
||
|
|
assert layer_info["name"] == "test_layer", f"Layer name mismatch: {layer_info['name']}"
|
||
|
|
assert layer_info["z_index"] == 5, f"Z-index mismatch: {layer_info['z_index']}"
|
||
|
|
|
||
|
|
# Layer count should be 1
|
||
|
|
assert viewport.layer_count() == 1, f"Expected 1 layer, got {viewport.layer_count()}"
|
||
|
|
|
||
|
|
# Get the layer
|
||
|
|
retrieved = viewport.get_layer("test_layer")
|
||
|
|
assert retrieved is not None, "get_layer returned None"
|
||
|
|
assert retrieved["name"] == "test_layer"
|
||
|
|
|
||
|
|
# Get non-existent layer
|
||
|
|
missing = viewport.get_layer("nonexistent")
|
||
|
|
assert missing is None, "Expected None for missing layer"
|
||
|
|
|
||
|
|
# Remove the layer
|
||
|
|
removed = viewport.remove_layer("test_layer")
|
||
|
|
assert removed == True, "remove_layer should return True"
|
||
|
|
assert viewport.layer_count() == 0, "Layer count should be 0 after removal"
|
||
|
|
|
||
|
|
# Remove non-existent layer
|
||
|
|
removed_again = viewport.remove_layer("test_layer")
|
||
|
|
assert removed_again == False, "remove_layer should return False for missing layer"
|
||
|
|
|
||
|
|
print(" PASS: Layer creation and management")
|
||
|
|
|
||
|
|
def test_terrain_from_heightmap():
|
||
|
|
"""Test building terrain mesh from HeightMap"""
|
||
|
|
print("Testing terrain mesh from HeightMap...")
|
||
|
|
|
||
|
|
viewport = mcrfpy.Viewport3D(pos=(0, 0), size=(320, 240))
|
||
|
|
|
||
|
|
# Create a small heightmap
|
||
|
|
hm = mcrfpy.HeightMap((10, 10))
|
||
|
|
hm.fill(0.5) # Flat terrain at 0.5 height
|
||
|
|
|
||
|
|
# Build terrain
|
||
|
|
vertex_count = viewport.build_terrain(
|
||
|
|
layer_name="terrain",
|
||
|
|
heightmap=hm,
|
||
|
|
y_scale=2.0,
|
||
|
|
cell_size=1.0
|
||
|
|
)
|
||
|
|
|
||
|
|
# Expected vertices: (10-1) x (10-1) quads x 2 triangles x 3 vertices = 9 * 9 * 6 = 486
|
||
|
|
expected_verts = 9 * 9 * 6
|
||
|
|
assert vertex_count == expected_verts, f"Expected {expected_verts} vertices, got {vertex_count}"
|
||
|
|
|
||
|
|
# Verify layer exists
|
||
|
|
layer = viewport.get_layer("terrain")
|
||
|
|
assert layer is not None, "Terrain layer not found"
|
||
|
|
assert layer["vertex_count"] == expected_verts
|
||
|
|
|
||
|
|
print(f" PASS: Built terrain with {vertex_count} vertices")
|
||
|
|
|
||
|
|
def test_heightmap_terrain_generation():
|
||
|
|
"""Test that HeightMap generation methods work with terrain"""
|
||
|
|
print("Testing HeightMap generation methods...")
|
||
|
|
|
||
|
|
viewport = mcrfpy.Viewport3D(pos=(0, 0), size=(320, 240))
|
||
|
|
|
||
|
|
# Test midpoint displacement
|
||
|
|
hm = mcrfpy.HeightMap((17, 17)) # Power of 2 + 1 for midpoint displacement
|
||
|
|
hm.mid_point_displacement(0.5, seed=123)
|
||
|
|
hm.normalize(0.0, 1.0)
|
||
|
|
|
||
|
|
min_h, max_h = hm.min_max()
|
||
|
|
assert min_h >= 0.0, f"Min height should be >= 0, got {min_h}"
|
||
|
|
assert max_h <= 1.0, f"Max height should be <= 1, got {max_h}"
|
||
|
|
|
||
|
|
vertex_count = viewport.build_terrain("terrain", hm, y_scale=5.0, cell_size=1.0)
|
||
|
|
assert vertex_count > 0, "Should have vertices"
|
||
|
|
|
||
|
|
print(f" PASS: Midpoint displacement terrain with {vertex_count} vertices")
|
||
|
|
|
||
|
|
def test_orbit_camera():
|
||
|
|
"""Test camera orbit helper"""
|
||
|
|
print("Testing camera orbit...")
|
||
|
|
|
||
|
|
viewport = mcrfpy.Viewport3D(pos=(0, 0), size=(320, 240))
|
||
|
|
|
||
|
|
# Test orbit at different angles
|
||
|
|
import math
|
||
|
|
|
||
|
|
viewport.orbit_camera(angle=0, distance=10, height=5)
|
||
|
|
pos = viewport.camera_pos
|
||
|
|
assert abs(pos[0] - 10.0) < 0.01, f"X should be 10 at angle=0, got {pos[0]}"
|
||
|
|
assert abs(pos[1] - 5.0) < 0.01, f"Y (height) should be 5, got {pos[1]}"
|
||
|
|
assert abs(pos[2]) < 0.01, f"Z should be 0 at angle=0, got {pos[2]}"
|
||
|
|
|
||
|
|
viewport.orbit_camera(angle=math.pi/2, distance=10, height=5)
|
||
|
|
pos = viewport.camera_pos
|
||
|
|
assert abs(pos[0]) < 0.01, f"X should be 0 at angle=pi/2, got {pos[0]}"
|
||
|
|
assert abs(pos[2] - 10.0) < 0.01, f"Z should be 10 at angle=pi/2, got {pos[2]}"
|
||
|
|
|
||
|
|
print(" PASS: Camera orbit positioning")
|
||
|
|
|
||
|
|
def test_large_terrain():
|
||
|
|
"""Test larger terrain (performance check)"""
|
||
|
|
print("Testing larger terrain mesh...")
|
||
|
|
|
||
|
|
viewport = mcrfpy.Viewport3D(pos=(0, 0), size=(320, 240))
|
||
|
|
|
||
|
|
# 80x45 is mentioned in the milestone doc
|
||
|
|
hm = mcrfpy.HeightMap((80, 45))
|
||
|
|
hm.mid_point_displacement(0.5, seed=999)
|
||
|
|
hm.normalize(0.0, 1.0)
|
||
|
|
|
||
|
|
vertex_count = viewport.build_terrain("large_terrain", hm, y_scale=4.0, cell_size=1.0)
|
||
|
|
|
||
|
|
# Expected: 79 * 44 * 6 = 20,856 vertices
|
||
|
|
expected = 79 * 44 * 6
|
||
|
|
assert vertex_count == expected, f"Expected {expected} vertices, got {vertex_count}"
|
||
|
|
|
||
|
|
print(f" PASS: Large terrain ({80}x{45} heightmap) with {vertex_count} vertices")
|
||
|
|
|
||
|
|
def test_terrain_color_map():
|
||
|
|
"""Test applying RGB color maps to terrain"""
|
||
|
|
print("Testing terrain color map...")
|
||
|
|
|
||
|
|
viewport = mcrfpy.Viewport3D(pos=(0, 0), size=(320, 240))
|
||
|
|
|
||
|
|
# Create small terrain
|
||
|
|
hm = mcrfpy.HeightMap((10, 10))
|
||
|
|
hm.fill(0.5)
|
||
|
|
viewport.build_terrain("colored_terrain", hm, y_scale=2.0, cell_size=1.0)
|
||
|
|
|
||
|
|
# Create RGB color maps
|
||
|
|
r_map = mcrfpy.HeightMap((10, 10))
|
||
|
|
g_map = mcrfpy.HeightMap((10, 10))
|
||
|
|
b_map = mcrfpy.HeightMap((10, 10))
|
||
|
|
|
||
|
|
# Fill with test colors (red terrain)
|
||
|
|
r_map.fill(1.0)
|
||
|
|
g_map.fill(0.0)
|
||
|
|
b_map.fill(0.0)
|
||
|
|
|
||
|
|
# Apply colors - should not raise
|
||
|
|
viewport.apply_terrain_colors("colored_terrain", r_map, g_map, b_map)
|
||
|
|
|
||
|
|
# Test with mismatched dimensions (should fail silently or raise)
|
||
|
|
wrong_size = mcrfpy.HeightMap((5, 5))
|
||
|
|
wrong_size.fill(0.5)
|
||
|
|
# This should not crash, just do nothing due to dimension mismatch
|
||
|
|
viewport.apply_terrain_colors("colored_terrain", wrong_size, wrong_size, wrong_size)
|
||
|
|
|
||
|
|
# Test with non-existent layer
|
||
|
|
try:
|
||
|
|
viewport.apply_terrain_colors("nonexistent", r_map, g_map, b_map)
|
||
|
|
assert False, "Should have raised ValueError for non-existent layer"
|
||
|
|
except ValueError:
|
||
|
|
pass # Expected
|
||
|
|
|
||
|
|
print(" PASS: Terrain color map application")
|
||
|
|
|
||
|
|
def run_all_tests():
|
||
|
|
"""Run all unit tests"""
|
||
|
|
print("=" * 60)
|
||
|
|
print("MeshLayer Unit Tests")
|
||
|
|
print("=" * 60)
|
||
|
|
|
||
|
|
try:
|
||
|
|
test_viewport3d_layer_creation()
|
||
|
|
test_terrain_from_heightmap()
|
||
|
|
test_heightmap_terrain_generation()
|
||
|
|
test_orbit_camera()
|
||
|
|
test_large_terrain()
|
||
|
|
test_terrain_color_map()
|
||
|
|
|
||
|
|
print("=" * 60)
|
||
|
|
print("ALL TESTS PASSED")
|
||
|
|
print("=" * 60)
|
||
|
|
sys.exit(0)
|
||
|
|
except AssertionError as e:
|
||
|
|
print(f"FAIL: {e}")
|
||
|
|
sys.exit(1)
|
||
|
|
except Exception as e:
|
||
|
|
print(f"ERROR: {e}")
|
||
|
|
import traceback
|
||
|
|
traceback.print_exc()
|
||
|
|
sys.exit(1)
|
||
|
|
|
||
|
|
# Run tests
|
||
|
|
run_all_tests()
|