McRogueFace/tests/unit/heightmap_combination_test.py

310 lines
8.9 KiB
Python

"""Unit tests for HeightMap combination operations (Issue #194)
Tests:
- add(other) - cell-by-cell addition
- subtract(other) - cell-by-cell subtraction
- multiply(other) - cell-by-cell multiplication (masking)
- lerp(other, t) - linear interpolation
- copy_from(other) - copy values
- max(other) - cell-by-cell maximum
- min(other) - cell-by-cell minimum
- Dimension mismatch handling (operates on overlapping region)
- Method chaining
"""
import mcrfpy
import sys
def test_add():
"""Test add() operation"""
h1 = mcrfpy.HeightMap((10, 10), fill=1.0)
h2 = mcrfpy.HeightMap((10, 10), fill=2.0)
result = h1.add(h2)
# Should return self for chaining
assert result is h1, "add() should return self"
# Check values
for x in range(10):
for y in range(10):
assert h1.get((x, y)) == 3.0, f"Expected 3.0 at ({x},{y}), got {h1.get((x, y))}"
print(" PASS: add()")
def test_subtract():
"""Test subtract() operation"""
h1 = mcrfpy.HeightMap((10, 10), fill=5.0)
h2 = mcrfpy.HeightMap((10, 10), fill=2.0)
result = h1.subtract(h2)
assert result is h1, "subtract() should return self"
for x in range(10):
for y in range(10):
assert h1.get((x, y)) == 3.0, f"Expected 3.0, got {h1.get((x, y))}"
print(" PASS: subtract()")
def test_multiply():
"""Test multiply() operation"""
h1 = mcrfpy.HeightMap((10, 10), fill=3.0)
h2 = mcrfpy.HeightMap((10, 10), fill=2.0)
result = h1.multiply(h2)
assert result is h1, "multiply() should return self"
for x in range(10):
for y in range(10):
assert h1.get((x, y)) == 6.0, f"Expected 6.0, got {h1.get((x, y))}"
print(" PASS: multiply()")
def test_multiply_masking():
"""Test multiply() for masking (0/1 values)"""
h1 = mcrfpy.HeightMap((10, 10), fill=5.0)
# Create mask: 1.0 in center, 0.0 outside
mask = mcrfpy.HeightMap((10, 10), fill=0.0)
for x in range(3, 7):
for y in range(3, 7):
# Need to use the underlying heightmap directly
pass # We'll fill differently
# Actually fill the mask using a different approach
mask = mcrfpy.HeightMap((10, 10), fill=0.0)
# Fill with 0, then add 1 to center region
center = mcrfpy.HeightMap((10, 10), fill=0.0)
center.add_hill((5, 5), 0.1, 1.0) # Small hill at center
center.threshold_binary((0.5, 2.0), value=1.0) # Make binary
# Just test basic masking with simple uniform values
h1 = mcrfpy.HeightMap((10, 10), fill=5.0)
mask = mcrfpy.HeightMap((10, 10), fill=0.5)
h1.multiply(mask)
# All values should be 5.0 * 0.5 = 2.5
for x in range(10):
for y in range(10):
assert abs(h1.get((x, y)) - 2.5) < 0.001, f"Expected 2.5, got {h1.get((x, y))}"
print(" PASS: multiply() for masking")
def test_lerp():
"""Test lerp() operation"""
h1 = mcrfpy.HeightMap((10, 10), fill=0.0)
h2 = mcrfpy.HeightMap((10, 10), fill=10.0)
# t=0.5 should give midpoint
result = h1.lerp(h2, 0.5)
assert result is h1, "lerp() should return self"
for x in range(10):
for y in range(10):
assert abs(h1.get((x, y)) - 5.0) < 0.001, f"Expected 5.0, got {h1.get((x, y))}"
print(" PASS: lerp() at t=0.5")
def test_lerp_extremes():
"""Test lerp() at t=0 and t=1"""
h1 = mcrfpy.HeightMap((10, 10), fill=0.0)
h2 = mcrfpy.HeightMap((10, 10), fill=10.0)
# t=0 should keep h1 values
h1.lerp(h2, 0.0)
assert abs(h1.get((5, 5)) - 0.0) < 0.001, f"t=0: Expected 0.0, got {h1.get((5, 5))}"
# Reset and test t=1
h1.fill(0.0)
h1.lerp(h2, 1.0)
assert abs(h1.get((5, 5)) - 10.0) < 0.001, f"t=1: Expected 10.0, got {h1.get((5, 5))}"
print(" PASS: lerp() at extremes")
def test_copy_from():
"""Test copy_from() operation"""
h1 = mcrfpy.HeightMap((10, 10), fill=0.0)
h2 = mcrfpy.HeightMap((10, 10), fill=7.5)
result = h1.copy_from(h2)
assert result is h1, "copy_from() should return self"
for x in range(10):
for y in range(10):
assert h1.get((x, y)) == 7.5, f"Expected 7.5, got {h1.get((x, y))}"
print(" PASS: copy_from()")
def test_max():
"""Test max() operation"""
h1 = mcrfpy.HeightMap((10, 10), fill=3.0)
h2 = mcrfpy.HeightMap((10, 10), fill=5.0)
result = h1.max(h2)
assert result is h1, "max() should return self"
for x in range(10):
for y in range(10):
assert h1.get((x, y)) == 5.0, f"Expected 5.0, got {h1.get((x, y))}"
print(" PASS: max()")
def test_max_varying():
"""Test max() with varying values"""
h1 = mcrfpy.HeightMap((10, 10), fill=0.0)
h2 = mcrfpy.HeightMap((10, 10), fill=0.0)
# h1 has values 0-4 in left half, h2 has values 5-9 in right half
h1.fill(3.0) # All 3
h2.fill(7.0) # All 7
# Modify h1 to have some higher values
h1.add_constant(5.0) # Now h1 is 8.0
h1.max(h2)
# Result should be 8.0 everywhere (h1 was 8, h2 was 7)
for x in range(10):
for y in range(10):
assert h1.get((x, y)) == 8.0, f"Expected 8.0, got {h1.get((x, y))}"
print(" PASS: max() with varying values")
def test_min():
"""Test min() operation"""
h1 = mcrfpy.HeightMap((10, 10), fill=8.0)
h2 = mcrfpy.HeightMap((10, 10), fill=5.0)
result = h1.min(h2)
assert result is h1, "min() should return self"
for x in range(10):
for y in range(10):
assert h1.get((x, y)) == 5.0, f"Expected 5.0, got {h1.get((x, y))}"
print(" PASS: min()")
def test_dimension_mismatch_allowed():
"""Test that dimension mismatch works (operates on overlapping region)"""
# Smaller dest, larger source - uses smaller size
h1 = mcrfpy.HeightMap((10, 10), fill=5.0)
h2 = mcrfpy.HeightMap((20, 20), fill=3.0)
h1.add(h2)
# All cells in h1 should be 5.0 + 3.0 = 8.0
for x in range(10):
for y in range(10):
assert h1.get((x, y)) == 8.0, f"Expected 8.0 at ({x},{y}), got {h1.get((x, y))}"
# Test the reverse: larger dest, smaller source
h3 = mcrfpy.HeightMap((20, 20), fill=10.0)
h4 = mcrfpy.HeightMap((5, 5), fill=2.0)
h3.add(h4)
# Only the 5x5 region should be affected
for x in range(20):
for y in range(20):
expected = 12.0 if (x < 5 and y < 5) else 10.0
assert h3.get((x, y)) == expected, f"Expected {expected} at ({x},{y}), got {h3.get((x, y))}"
print(" PASS: Dimension mismatch handling (overlapping region)")
def test_type_error():
"""Test that non-HeightMap argument raises TypeError"""
h1 = mcrfpy.HeightMap((10, 10), fill=0.0)
ops = [
('add', lambda: h1.add(5.0)),
('subtract', lambda: h1.subtract("invalid")),
('multiply', lambda: h1.multiply([1, 2, 3])),
('copy_from', lambda: h1.copy_from(None)),
('max', lambda: h1.max({})),
('min', lambda: h1.min(42)),
]
for name, op in ops:
try:
op()
print(f" FAIL: {name}() should raise TypeError for non-HeightMap")
sys.exit(1)
except TypeError:
pass
print(" PASS: Type error handling")
def test_method_chaining():
"""Test method chaining with combination operations"""
h1 = mcrfpy.HeightMap((10, 10), fill=1.0)
h2 = mcrfpy.HeightMap((10, 10), fill=2.0)
h3 = mcrfpy.HeightMap((10, 10), fill=3.0)
# Chain multiple operations
result = h1.add(h2).add(h3).scale(0.5)
# 1.0 + 2.0 + 3.0 = 6.0, then * 0.5 = 3.0
for x in range(10):
for y in range(10):
assert abs(h1.get((x, y)) - 3.0) < 0.001, f"Expected 3.0, got {h1.get((x, y))}"
print(" PASS: Method chaining")
def test_self_operation():
"""Test operations with self (h.add(h))"""
h1 = mcrfpy.HeightMap((10, 10), fill=5.0)
# Adding to self should double values
h1.add(h1)
for x in range(10):
for y in range(10):
assert h1.get((x, y)) == 10.0, f"Expected 10.0, got {h1.get((x, y))}"
# Multiplying self by self should square
h1.fill(3.0)
h1.multiply(h1)
for x in range(10):
for y in range(10):
assert h1.get((x, y)) == 9.0, f"Expected 9.0, got {h1.get((x, y))}"
print(" PASS: Self operations")
def run_tests():
"""Run all HeightMap combination tests"""
print("Testing HeightMap combination operations (Issue #194)...")
test_add()
test_subtract()
test_multiply()
test_multiply_masking()
test_lerp()
test_lerp_extremes()
test_copy_from()
test_max()
test_max_varying()
test_min()
test_dimension_mismatch_allowed()
test_type_error()
test_method_chaining()
test_self_operation()
print("All HeightMap combination tests PASSED!")
return True
if __name__ == "__main__":
try:
success = run_tests()
sys.exit(0 if success else 1)
except Exception as e:
print(f"FAIL: Unexpected exception: {e}")
import traceback
traceback.print_exc()
sys.exit(1)