def test_reflect_angle(initial: Vector, normal: Vector): """Test angle-related properties of Vector.reflect: * initial.reflect(normal) * normal == - initial * normal * normal.angle(initial) == 180 - normal.angle(reflected) """ # Exclude initial vectors that are very small or very close to the surface. assume(not angle_isclose(initial.angle(normal) % 180, 90, epsilon=10)) assume(initial.length > 1e-10) reflected = initial.reflect(normal) assert isclose((initial * normal), -(reflected * normal)) assert angle_isclose(normal.angle(initial), 180 - normal.angle(reflected))
def test_rotation_angle(initial, angle): assume(initial.length > 1e-5) rotated = initial.rotate(angle) note(f"Rotated: {rotated}") measured_angle = initial.angle(rotated) d = measured_angle - angle % 360 note(f"Angle: {measured_angle} = {angle} + {d if d<180 else d-360}") assert angle_isclose(angle, measured_angle)
def test_angle_range(left, right): """Vector.angle produces values in [-180; 180] and is antisymmetric. Antisymmetry means that left.angle(right) == - right.angle(left). """ lr = left.angle(right) rl = right.angle(left) assert -180 < lr <= 180 assert -180 < rl <= 180 assert angle_isclose(lr, -rl)
def test_reflect_prop(initial: Vector2, normal: Vector2): # Exclude cases where the initial vector is very close to the surface assume(not angle_isclose(initial.angle(normal) % 180, 90, epsilon=10)) # Exclude cases where the initial vector is very small assume(initial.length > 1e-10) reflected = initial.reflect(normal) returned = reflected.reflect(normal) note(f"|normal|: {normal.length}, |initial|: {initial.length}") note(f"angle(normal, initial): {normal.angle(initial)}") note(f"angle(normal, reflected): {normal.angle(reflected)}") note(f"initial ^ normal: {initial ^ normal}") note(f"Reflected: {reflected}") assert not any(map(isinf, reflected)) assert initial.isclose(returned) note(f"initial ⋅ normal: {initial * normal}") note(f"reflected ⋅ normal: {reflected * normal}") assert isclose((initial * normal), -(reflected * normal)) assert angle_isclose(normal.angle(initial), 180 - normal.angle(reflected))
def test_normalize_angle(v): """Normalization preserves direction.""" assume(v) assert angle_isclose(v.normalize().angle(v), 0)
def test_close_rotations(input, angle, expected): assert input.rotate(angle).isclose(expected) assert angle_isclose(input.angle(expected), angle)
def test_rotation_angle(initial, angle): """initial.angle( initial.rotate(angle) ) == angle""" assume(initial.length > 1e-5) assert angle_isclose(initial.angle(initial.rotate(angle)), angle)
def test_angle_aligned(x: Vector, scalar: float): """x.angle(scalar * x) is 0 or 180, depending on whether scalar > 0""" assume(scalar != 0) y = scalar * x assert angle_isclose(x.angle(y), 0 if scalar > 0 else 180)
def test_angle_additive(left, middle, right): """left.angle(middle) + middle.angle(right) == left.angle(right)""" lm = left.angle(middle) mr = middle.angle(right) lr = left.angle(right) assert angle_isclose(lm + mr, lr)
def test_angle(left, right, expected): left, right = Vector(left), Vector(right) lr = left.angle(right) rl = right.angle(left) assert angle_isclose(lr, expected) assert angle_isclose(rl, -expected)
def test_scale_aligned(x: Vector, length: float): """Test that x.scale_to(length) is aligned with x.""" assume(length > 0 and x) assert angle_isclose(x.scale_to(length).angle(x), 0)
def test_angle_prop(left, right): lr = left.angle(right) rl = right.angle(left) assert -180 < lr <= 180 assert -180 < rl <= 180 assert angle_isclose(lr, -rl)
def test_multiple_rotations(input, degrees, expected): assert input.rotate(degrees).isclose(expected) assert angle_isclose(input.angle(expected), degrees)