260 lines
8.2 KiB
Python
260 lines
8.2 KiB
Python
|
|
# billboard_test.py - Unit test for Billboard 3D camera-facing sprites
|
||
|
|
|
||
|
|
import mcrfpy
|
||
|
|
import sys
|
||
|
|
|
||
|
|
def test_billboard_creation():
|
||
|
|
"""Test basic Billboard creation and default properties"""
|
||
|
|
bb = mcrfpy.Billboard()
|
||
|
|
|
||
|
|
# Default sprite index
|
||
|
|
assert bb.sprite_index == 0, f"Expected sprite_index=0, got {bb.sprite_index}"
|
||
|
|
|
||
|
|
# Default position
|
||
|
|
assert bb.pos == (0.0, 0.0, 0.0), f"Expected pos=(0,0,0), got {bb.pos}"
|
||
|
|
|
||
|
|
# Default scale
|
||
|
|
assert bb.scale == 1.0, f"Expected scale=1.0, got {bb.scale}"
|
||
|
|
|
||
|
|
# Default facing mode
|
||
|
|
assert bb.facing == "camera_y", f"Expected facing='camera_y', got {bb.facing}"
|
||
|
|
|
||
|
|
# Default theta/phi (for fixed mode)
|
||
|
|
assert bb.theta == 0.0, f"Expected theta=0.0, got {bb.theta}"
|
||
|
|
assert bb.phi == 0.0, f"Expected phi=0.0, got {bb.phi}"
|
||
|
|
|
||
|
|
# Default opacity and visibility
|
||
|
|
assert bb.opacity == 1.0, f"Expected opacity=1.0, got {bb.opacity}"
|
||
|
|
assert bb.visible == True, f"Expected visible=True, got {bb.visible}"
|
||
|
|
|
||
|
|
print("[PASS] test_billboard_creation")
|
||
|
|
|
||
|
|
def test_billboard_with_kwargs():
|
||
|
|
"""Test Billboard creation with keyword arguments"""
|
||
|
|
bb = mcrfpy.Billboard(
|
||
|
|
sprite_index=5,
|
||
|
|
pos=(10.0, 5.0, -3.0),
|
||
|
|
scale=2.5,
|
||
|
|
facing="camera",
|
||
|
|
opacity=0.8
|
||
|
|
)
|
||
|
|
|
||
|
|
assert bb.sprite_index == 5, f"Expected sprite_index=5, got {bb.sprite_index}"
|
||
|
|
assert bb.pos == (10.0, 5.0, -3.0), f"Expected pos=(10,5,-3), got {bb.pos}"
|
||
|
|
assert bb.scale == 2.5, f"Expected scale=2.5, got {bb.scale}"
|
||
|
|
assert bb.facing == "camera", f"Expected facing='camera', got {bb.facing}"
|
||
|
|
assert abs(bb.opacity - 0.8) < 0.001, f"Expected opacity~=0.8, got {bb.opacity}"
|
||
|
|
|
||
|
|
print("[PASS] test_billboard_with_kwargs")
|
||
|
|
|
||
|
|
def test_billboard_facing_modes():
|
||
|
|
"""Test all Billboard facing modes"""
|
||
|
|
bb = mcrfpy.Billboard()
|
||
|
|
|
||
|
|
# Test camera mode (full rotation to face camera)
|
||
|
|
bb.facing = "camera"
|
||
|
|
assert bb.facing == "camera", f"Expected facing='camera', got {bb.facing}"
|
||
|
|
|
||
|
|
# Test camera_y mode (only Y-axis rotation, stays upright)
|
||
|
|
bb.facing = "camera_y"
|
||
|
|
assert bb.facing == "camera_y", f"Expected facing='camera_y', got {bb.facing}"
|
||
|
|
|
||
|
|
# Test fixed mode (uses theta/phi angles)
|
||
|
|
bb.facing = "fixed"
|
||
|
|
assert bb.facing == "fixed", f"Expected facing='fixed', got {bb.facing}"
|
||
|
|
|
||
|
|
print("[PASS] test_billboard_facing_modes")
|
||
|
|
|
||
|
|
def test_billboard_fixed_rotation():
|
||
|
|
"""Test fixed mode rotation angles (theta/phi)"""
|
||
|
|
bb = mcrfpy.Billboard(facing="fixed")
|
||
|
|
|
||
|
|
# Set theta (horizontal rotation)
|
||
|
|
bb.theta = 1.5708 # ~90 degrees
|
||
|
|
assert abs(bb.theta - 1.5708) < 0.001, f"Expected theta~=1.5708, got {bb.theta}"
|
||
|
|
|
||
|
|
# Set phi (vertical tilt)
|
||
|
|
bb.phi = 0.7854 # ~45 degrees
|
||
|
|
assert abs(bb.phi - 0.7854) < 0.001, f"Expected phi~=0.7854, got {bb.phi}"
|
||
|
|
|
||
|
|
print("[PASS] test_billboard_fixed_rotation")
|
||
|
|
|
||
|
|
def test_billboard_property_modification():
|
||
|
|
"""Test modifying Billboard properties after creation"""
|
||
|
|
bb = mcrfpy.Billboard()
|
||
|
|
|
||
|
|
# Modify position
|
||
|
|
bb.pos = (5.0, 10.0, 15.0)
|
||
|
|
assert bb.pos == (5.0, 10.0, 15.0), f"Expected pos=(5,10,15), got {bb.pos}"
|
||
|
|
|
||
|
|
# Modify sprite index
|
||
|
|
bb.sprite_index = 42
|
||
|
|
assert bb.sprite_index == 42, f"Expected sprite_index=42, got {bb.sprite_index}"
|
||
|
|
|
||
|
|
# Modify scale
|
||
|
|
bb.scale = 0.5
|
||
|
|
assert bb.scale == 0.5, f"Expected scale=0.5, got {bb.scale}"
|
||
|
|
|
||
|
|
# Modify opacity
|
||
|
|
bb.opacity = 0.25
|
||
|
|
assert abs(bb.opacity - 0.25) < 0.001, f"Expected opacity~=0.25, got {bb.opacity}"
|
||
|
|
|
||
|
|
# Modify visibility
|
||
|
|
bb.visible = False
|
||
|
|
assert bb.visible == False, f"Expected visible=False, got {bb.visible}"
|
||
|
|
|
||
|
|
print("[PASS] test_billboard_property_modification")
|
||
|
|
|
||
|
|
def test_billboard_opacity_clamping():
|
||
|
|
"""Test that opacity is clamped to 0-1 range"""
|
||
|
|
bb = mcrfpy.Billboard()
|
||
|
|
|
||
|
|
# Test upper clamp
|
||
|
|
bb.opacity = 2.0
|
||
|
|
assert bb.opacity == 1.0, f"Expected opacity=1.0 after clamping, got {bb.opacity}"
|
||
|
|
|
||
|
|
# Test lower clamp
|
||
|
|
bb.opacity = -0.5
|
||
|
|
assert bb.opacity == 0.0, f"Expected opacity=0.0 after clamping, got {bb.opacity}"
|
||
|
|
|
||
|
|
print("[PASS] test_billboard_opacity_clamping")
|
||
|
|
|
||
|
|
def test_billboard_repr():
|
||
|
|
"""Test Billboard string representation"""
|
||
|
|
bb = mcrfpy.Billboard(pos=(1.0, 2.0, 3.0), sprite_index=7, facing="camera")
|
||
|
|
repr_str = repr(bb)
|
||
|
|
|
||
|
|
# Check that repr contains expected information
|
||
|
|
assert "Billboard" in repr_str, f"Expected 'Billboard' in repr, got {repr_str}"
|
||
|
|
|
||
|
|
print("[PASS] test_billboard_repr")
|
||
|
|
|
||
|
|
def test_billboard_with_texture():
|
||
|
|
"""Test Billboard with texture assignment"""
|
||
|
|
# Use default_texture which is always available
|
||
|
|
tex = mcrfpy.default_texture
|
||
|
|
bb = mcrfpy.Billboard(texture=tex, sprite_index=0)
|
||
|
|
|
||
|
|
# Verify texture is assigned
|
||
|
|
assert bb.texture is not None, "Expected texture to be assigned"
|
||
|
|
assert bb.sprite_index == 0, f"Expected sprite_index=0, got {bb.sprite_index}"
|
||
|
|
|
||
|
|
# Change sprite index
|
||
|
|
bb.sprite_index = 10
|
||
|
|
assert bb.sprite_index == 10, f"Expected sprite_index=10, got {bb.sprite_index}"
|
||
|
|
|
||
|
|
# Test assigning texture via property
|
||
|
|
bb2 = mcrfpy.Billboard()
|
||
|
|
assert bb2.texture is None, "Expected no texture initially"
|
||
|
|
bb2.texture = tex
|
||
|
|
assert bb2.texture is not None, "Expected texture after assignment"
|
||
|
|
|
||
|
|
# Test setting texture to None
|
||
|
|
bb2.texture = None
|
||
|
|
assert bb2.texture is None, "Expected None after clearing texture"
|
||
|
|
|
||
|
|
print("[PASS] test_billboard_with_texture")
|
||
|
|
|
||
|
|
def test_viewport3d_billboard_methods():
|
||
|
|
"""Test Viewport3D billboard management methods"""
|
||
|
|
vp = mcrfpy.Viewport3D()
|
||
|
|
|
||
|
|
# Initial count should be 0
|
||
|
|
assert vp.billboard_count() == 0, f"Expected 0, got {vp.billboard_count()}"
|
||
|
|
|
||
|
|
# Add billboards
|
||
|
|
bb1 = mcrfpy.Billboard(pos=(1, 0, 1), scale=1.0)
|
||
|
|
vp.add_billboard(bb1)
|
||
|
|
assert vp.billboard_count() == 1, f"Expected 1, got {vp.billboard_count()}"
|
||
|
|
|
||
|
|
bb2 = mcrfpy.Billboard(pos=(2, 0, 2), scale=0.5)
|
||
|
|
vp.add_billboard(bb2)
|
||
|
|
assert vp.billboard_count() == 2, f"Expected 2, got {vp.billboard_count()}"
|
||
|
|
|
||
|
|
# Get billboard by index
|
||
|
|
retrieved = vp.get_billboard(0)
|
||
|
|
assert retrieved.pos == (1.0, 0.0, 1.0), f"Expected (1,0,1), got {retrieved.pos}"
|
||
|
|
|
||
|
|
# Modify retrieved billboard
|
||
|
|
retrieved.pos = (5, 1, 5)
|
||
|
|
assert retrieved.pos == (5.0, 1.0, 5.0), f"Expected (5,1,5), got {retrieved.pos}"
|
||
|
|
|
||
|
|
# Clear all billboards
|
||
|
|
vp.clear_billboards()
|
||
|
|
assert vp.billboard_count() == 0, f"Expected 0 after clear, got {vp.billboard_count()}"
|
||
|
|
|
||
|
|
print("[PASS] test_viewport3d_billboard_methods")
|
||
|
|
|
||
|
|
def test_viewport3d_billboard_index_bounds():
|
||
|
|
"""Test get_billboard index bounds checking"""
|
||
|
|
vp = mcrfpy.Viewport3D()
|
||
|
|
|
||
|
|
# Empty viewport - any index should fail
|
||
|
|
try:
|
||
|
|
vp.get_billboard(0)
|
||
|
|
assert False, "Should have raised IndexError"
|
||
|
|
except IndexError:
|
||
|
|
pass
|
||
|
|
|
||
|
|
# Add one billboard
|
||
|
|
bb = mcrfpy.Billboard()
|
||
|
|
vp.add_billboard(bb)
|
||
|
|
|
||
|
|
# Index 0 should work
|
||
|
|
vp.get_billboard(0)
|
||
|
|
|
||
|
|
# Index 1 should fail
|
||
|
|
try:
|
||
|
|
vp.get_billboard(1)
|
||
|
|
assert False, "Should have raised IndexError"
|
||
|
|
except IndexError:
|
||
|
|
pass
|
||
|
|
|
||
|
|
# Negative index should fail
|
||
|
|
try:
|
||
|
|
vp.get_billboard(-1)
|
||
|
|
assert False, "Should have raised IndexError"
|
||
|
|
except IndexError:
|
||
|
|
pass
|
||
|
|
|
||
|
|
print("[PASS] test_viewport3d_billboard_index_bounds")
|
||
|
|
|
||
|
|
def run_all_tests():
|
||
|
|
"""Run all Billboard tests"""
|
||
|
|
tests = [
|
||
|
|
test_billboard_creation,
|
||
|
|
test_billboard_with_kwargs,
|
||
|
|
test_billboard_facing_modes,
|
||
|
|
test_billboard_fixed_rotation,
|
||
|
|
test_billboard_property_modification,
|
||
|
|
test_billboard_opacity_clamping,
|
||
|
|
test_billboard_repr,
|
||
|
|
test_billboard_with_texture,
|
||
|
|
test_viewport3d_billboard_methods,
|
||
|
|
test_viewport3d_billboard_index_bounds,
|
||
|
|
]
|
||
|
|
|
||
|
|
passed = 0
|
||
|
|
failed = 0
|
||
|
|
skipped = 0
|
||
|
|
|
||
|
|
for test in tests:
|
||
|
|
try:
|
||
|
|
test()
|
||
|
|
passed += 1
|
||
|
|
except AssertionError as e:
|
||
|
|
print(f"[FAIL] {test.__name__}: {e}")
|
||
|
|
failed += 1
|
||
|
|
except Exception as e:
|
||
|
|
if "[SKIP]" in str(e):
|
||
|
|
skipped += 1
|
||
|
|
else:
|
||
|
|
print(f"[ERROR] {test.__name__}: {e}")
|
||
|
|
failed += 1
|
||
|
|
|
||
|
|
print(f"\n=== Results: {passed} passed, {failed} failed, {skipped} skipped ===")
|
||
|
|
return failed == 0
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
success = run_all_tests()
|
||
|
|
sys.exit(0 if success else 1)
|