assertSame([1.0, 2.0, 3.0], ThreeMfTransform::apply(ThreeMfTransform::identity(), [1.0, 2.0, 3.0])); } public function test_parses_twelve_space_separated_floats(): void { $matrix = ThreeMfTransform::parse('1 0 0 0 1 0 0 0 1 10 20 30'); $this->assertSame([10.0, 20.0, 33.0], ThreeMfTransform::apply($matrix, [0.0, 0.0, 3.0])); } public function test_rejects_a_transform_without_exactly_twelve_values(): void { $this->expectException(RuntimeException::class); ThreeMfTransform::parse('1 0 0 0 1 0 0 0 1'); } public function test_rejects_a_non_numeric_transform_value(): void { $this->expectException(RuntimeException::class); ThreeMfTransform::parse('1 0 0 0 1 0 0 0 1 x 0 0'); } public function test_rejects_a_non_finite_transform_value(): void { $this->expectException(RuntimeException::class); ThreeMfTransform::parse('1 0 0 0 1 0 0 0 1 NAN 0 0'); } public function test_translation_moves_a_point(): void { $translate = ThreeMfTransform::parse('1 0 0 0 1 0 0 0 1 5 6 7'); $this->assertSame([6.0, 8.0, 10.0], ThreeMfTransform::apply($translate, [1.0, 2.0, 3.0])); } public function test_scale_multiplies_each_axis(): void { $scale = ThreeMfTransform::parse('2 0 0 0 3 0 0 0 4 0 0 0'); $this->assertSame([2.0, 6.0, 12.0], ThreeMfTransform::apply($scale, [1.0, 2.0, 3.0])); } public function test_compose_applies_inner_transform_before_outer(): void { $translateThenScale = ThreeMfTransform::compose( outer: ThreeMfTransform::parse('2 0 0 0 2 0 0 0 2 0 0 0'), inner: ThreeMfTransform::parse('1 0 0 0 1 0 0 0 1 10 0 0'), ); // Expected: apply(outer, apply(inner, p)) = apply(outer, [11,0,0]) = [22,0,0] $this->assertSame([22.0, 0.0, 0.0], ThreeMfTransform::apply($translateThenScale, [1.0, 0.0, 0.0])); } public function test_compose_matches_applying_each_transform_in_turn_for_an_arbitrary_rotation_like_matrix(): void { $outer = ThreeMfTransform::parse('0 1 0 -1 0 0 0 0 1 3 4 5'); $inner = ThreeMfTransform::parse('1 0 0 0 1 0 0 0 1 1 2 3'); $point = [7.0, -2.0, 0.5]; $composed = ThreeMfTransform::compose($outer, $inner); $expected = ThreeMfTransform::apply($outer, ThreeMfTransform::apply($inner, $point)); $this->assertSame($expected, ThreeMfTransform::apply($composed, $point)); } }