#!/usr/bin/env python3 """Unit tests for mcrfpy.HeightMap query methods (#196) Tests the HeightMap query methods: get, get_interpolated, get_slope, get_normal, min_max, count_in_range """ import sys import math import mcrfpy def test_get_basic(): """get() returns correct value at position""" hmap = mcrfpy.HeightMap((10, 10), fill=0.5) value = hmap.get((5, 5)) assert abs(value - 0.5) < 0.001, f"Expected 0.5, got {value}" print("PASS: test_get_basic") def test_get_corners(): """get() works at all corners""" hmap = mcrfpy.HeightMap((10, 10), fill=0.25) # All corners should have the fill value assert abs(hmap.get((0, 0)) - 0.25) < 0.001 assert abs(hmap.get((9, 0)) - 0.25) < 0.001 assert abs(hmap.get((0, 9)) - 0.25) < 0.001 assert abs(hmap.get((9, 9)) - 0.25) < 0.001 print("PASS: test_get_corners") def test_get_out_of_bounds(): """get() raises IndexError for out-of-bounds position""" hmap = mcrfpy.HeightMap((10, 10)) # Test various out-of-bounds positions for pos in [(-1, 0), (0, -1), (10, 0), (0, 10), (10, 10)]: try: hmap.get(pos) print(f"FAIL: test_get_out_of_bounds - should have raised IndexError for {pos}") sys.exit(1) except IndexError: pass print("PASS: test_get_out_of_bounds") def test_get_invalid_type(): """get() raises TypeError for invalid position""" hmap = mcrfpy.HeightMap((10, 10)) try: hmap.get([5, 5]) # list instead of tuple print("FAIL: test_get_invalid_type - should have raised TypeError") sys.exit(1) except TypeError: pass print("PASS: test_get_invalid_type") def test_get_interpolated_basic(): """get_interpolated() returns value at float position""" hmap = mcrfpy.HeightMap((10, 10), fill=0.5) value = hmap.get_interpolated((5.5, 5.5)) # With uniform fill, interpolation should return same value assert abs(value - 0.5) < 0.001, f"Expected ~0.5, got {value}" print("PASS: test_get_interpolated_basic") def test_get_interpolated_at_integers(): """get_interpolated() matches get() at integer positions""" hmap = mcrfpy.HeightMap((10, 10), fill=0.75) int_value = hmap.get((3, 4)) interp_value = hmap.get_interpolated((3.0, 4.0)) assert abs(int_value - interp_value) < 0.001, f"Values differ: {int_value} vs {interp_value}" print("PASS: test_get_interpolated_at_integers") def test_get_slope_flat(): """get_slope() returns 0 for flat terrain""" hmap = mcrfpy.HeightMap((10, 10), fill=0.5) slope = hmap.get_slope((5, 5)) # Flat terrain should have slope near 0 assert abs(slope) < 0.01, f"Expected ~0 for flat terrain, got {slope}" print("PASS: test_get_slope_flat") def test_get_slope_out_of_bounds(): """get_slope() raises IndexError for out-of-bounds position""" hmap = mcrfpy.HeightMap((10, 10)) try: hmap.get_slope((10, 5)) print("FAIL: test_get_slope_out_of_bounds - should have raised IndexError") sys.exit(1) except IndexError: pass print("PASS: test_get_slope_out_of_bounds") def test_get_normal_flat(): """get_normal() returns up vector for flat terrain""" hmap = mcrfpy.HeightMap((10, 10), fill=0.5) nx, ny, nz = hmap.get_normal((5.0, 5.0)) # Flat terrain should have normal pointing up (0, 0, 1) assert abs(nx) < 0.01, f"Expected nx~0, got {nx}" assert abs(ny) < 0.01, f"Expected ny~0, got {ny}" assert abs(nz - 1.0) < 0.01, f"Expected nz~1, got {nz}" print("PASS: test_get_normal_flat") def test_get_normal_with_water_level(): """get_normal() accepts water_level parameter""" hmap = mcrfpy.HeightMap((10, 10), fill=0.5) nx, ny, nz = hmap.get_normal((5.0, 5.0), water_level=0.3) # Should still return valid normal assert isinstance(nx, float) assert isinstance(ny, float) assert isinstance(nz, float) print("PASS: test_get_normal_with_water_level") def test_min_max_uniform(): """min_max() returns correct values for uniform heightmap""" hmap = mcrfpy.HeightMap((10, 10), fill=0.5) min_val, max_val = hmap.min_max() assert abs(min_val - 0.5) < 0.001, f"Expected min=0.5, got {min_val}" assert abs(max_val - 0.5) < 0.001, f"Expected max=0.5, got {max_val}" print("PASS: test_min_max_uniform") def test_min_max_after_operations(): """min_max() updates after operations""" hmap = mcrfpy.HeightMap((10, 10)) hmap.fill(0.0).add_constant(0.5).scale(2.0) min_val, max_val = hmap.min_max() expected = 1.0 # 0.0 + 0.5 * 2.0 assert abs(min_val - expected) < 0.001, f"Expected min={expected}, got {min_val}" assert abs(max_val - expected) < 0.001, f"Expected max={expected}, got {max_val}" print("PASS: test_min_max_after_operations") def test_count_in_range_all(): """count_in_range() returns all cells for uniform map in range""" hmap = mcrfpy.HeightMap((10, 10), fill=0.5) count = hmap.count_in_range((0.0, 1.0)) assert count == 100, f"Expected 100 cells, got {count}" print("PASS: test_count_in_range_all") def test_count_in_range_none(): """count_in_range() returns 0 when no cells in range""" hmap = mcrfpy.HeightMap((10, 10), fill=0.5) count = hmap.count_in_range((0.0, 0.4)) assert count == 0, f"Expected 0 cells, got {count}" print("PASS: test_count_in_range_none") def test_count_in_range_exact(): """count_in_range() with exact bounds""" hmap = mcrfpy.HeightMap((10, 10), fill=0.5) count = hmap.count_in_range((0.5, 0.5)) # Should count all cells since fill value is exactly 0.5 assert count == 100, f"Expected 100 cells at exact value, got {count}" print("PASS: test_count_in_range_exact") def test_count_in_range_invalid(): """count_in_range() raises TypeError for invalid range""" hmap = mcrfpy.HeightMap((10, 10)) try: hmap.count_in_range([0.0, 1.0]) # list instead of tuple print("FAIL: test_count_in_range_invalid - should have raised TypeError") sys.exit(1) except TypeError: pass print("PASS: test_count_in_range_invalid") def run_all_tests(): """Run all tests""" print("Running HeightMap query method tests...") print() test_get_basic() test_get_corners() test_get_out_of_bounds() test_get_invalid_type() test_get_interpolated_basic() test_get_interpolated_at_integers() test_get_slope_flat() test_get_slope_out_of_bounds() test_get_normal_flat() test_get_normal_with_water_level() test_min_max_uniform() test_min_max_after_operations() test_count_in_range_all() test_count_in_range_none() test_count_in_range_exact() test_count_in_range_invalid() print() print("All HeightMap query method tests PASSED!") # Run tests directly run_all_tests() sys.exit(0)