@@ -9,6 +9,20 @@ module test_translate() {
99 assert_equal(translate(val), [[1,0,0,val.x],[0,1,0,val.y],[0,0,1,val.z],[0,0,0,1]]);
1010 assert_equal(translate(val, p=[1,2,3]), [1,2,3]+val);
1111 }
12+ // 2D translation: 2-element vector
13+ assert_equal(translate([4,5]), [[1,0,0,4],[0,1,0,5],[0,0,1,0],[0,0,0,1]]);
14+ assert_equal(translate([4,5], p=[1,2,3]), [5,7,3]);
15+ assert_equal(translate([0,0], p=[1,2,3]), [1,2,3]);
16+ // 2D translation with 2D point
17+ assert_equal(translate([3,4], p=[10,20]), [13,24]);
18+ // Translation with large negative values
19+ assert_equal(translate([-100,-200,-300], p=[1,2,3]), [-99,-198,-297]);
20+ // Single-axis translation
21+ assert_equal(translate([5,0,0], p=[0,0,0]), [5,0,0]);
22+ assert_equal(translate([0,5,0], p=[0,0,0]), [0,5,0]);
23+ assert_equal(translate([0,0,5], p=[0,0,0]), [0,0,5]);
24+ // List of points
25+ assert_equal(translate([1,2,3], p=[[0,0,0],[10,10,10]]), [[1,2,3],[11,12,13]]);
1226 // Verify that module at least doesn't crash.
1327 translate([-5,-5,-5]) translate([0,0,0]) translate([5,5,5]) union(){};
1428}
@@ -33,6 +47,18 @@ module test_move() {
3347 assert_equal(move("centroid", sq), move(-centroid(sq),sq));
3448 assert_equal(move("mean", vals), move(-mean(vals), vals));
3549 assert_equal(move("box", vals), move(-mean(pointlist_bounds(vals)),vals));
50+ // 2D move: 2-element vector
51+ assert_equal(move([4,5]), [[1,0,0,4],[0,1,0,5],[0,0,1,0],[0,0,0,1]]);
52+ assert_equal(move([4,5], p=[1,2,3]), [5,7,3]);
53+ assert_equal(move([0,0], p=[7,8,9]), [7,8,9]);
54+ // 2D point with 2D vector
55+ assert_equal(move([3,4], p=[10,20]), [13,24]);
56+ // List of points
57+ assert_equal(move([1,1,1], p=[[0,0,0],[5,5,5]]), [[1,1,1],[6,6,6]]);
58+ // Large negative values
59+ assert_equal(move([-1000,2000,-3000], p=[1,2,3]), [-999,2002,-2997]);
60+ // Composition: move(a) * move(b) = move(a+b)
61+ assert_approx(move([1,2,3]) * move([4,5,6]), move([5,7,9]));
3662}
3763test_move();
3864'''
@@ -168,6 +194,23 @@ module test_scale() {
168194 assert_equal(scale([2,2], cp=[0.5,0.5], p=square(1)), move([-0.5,-0.5], p=square([2,2])));
169195 assert_equal(scale([2,3,4], p=cb), cube([2,3,4]));
170196 assert_equal(scale([-2,-3,-4], p=cb), [[for (p=cb[0]) v_mul(p,[-2,-3,-4])], [for (f=cb[1]) reverse(f)]]);
197+ // Uniform scale 1 = identity
198+ assert_equal(scale(1), [[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]]);
199+ assert_equal(scale(1, p=[7,8,9]), [7,8,9]);
200+ assert_equal(scale([1,1,1]), [[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]]);
201+ assert_equal(scale([1,1,1], p=[7,8,9]), [7,8,9]);
202+ // Zero scale
203+ assert_equal(scale(0, p=[5,10,15]), [0,0,0]);
204+ assert_equal(scale([0,0,0], p=[5,10,15]), [0,0,0]);
205+ // Uniform negative scale
206+ assert_equal(scale(-1, p=[3,4,5]), [-3,-4,-5]);
207+ // Scale with list of points
208+ assert_equal(scale(2, p=[[1,0,0],[0,1,0]]), [[2,0,0],[0,2,0]]);
209+ assert_equal(scale([2,3,4], p=[[1,1,1],[2,2,2]]), [[2,3,4],[4,6,8]]);
210+ // 2D scale
211+ assert_equal(scale([2,3], p=[[1,1],[2,2]]), [[2,3],[4,6]]);
212+ // Composition: scale(a) * scale(a) = scale(a^2) for uniform scale
213+ assert_approx(scale(3) * scale(3), scale(9));
171214 // Verify that module at least doesn't crash.
172215 scale(-5) scale(5) union(){};
173216}
@@ -251,6 +294,23 @@ module test_mirror() {
251294 // Verify that module at least doesn't crash.
252295 mirror(val) union(){};
253296 }
297+ // Double mirror = identity
298+ assert_approx(mirror([1,0,0]) * mirror([1,0,0]), affine3d_identity(), "double mirror X = identity");
299+ assert_approx(mirror([0,1,0]) * mirror([0,1,0]), affine3d_identity(), "double mirror Y = identity");
300+ assert_approx(mirror([0,0,1]) * mirror([0,0,1]), affine3d_identity(), "double mirror Z = identity");
301+ // Mirror with diagonal vector
302+ assert_approx(mirror([1,1,0]) * mirror([1,1,0]), affine3d_identity(), "double mirror diagonal = identity");
303+ // Mirror with point at origin: stays at origin
304+ assert_approx(mirror([1,0,0], p=[0,0,0]), [0,0,0], "mirror origin stays at origin");
305+ // Mirroring on each axis inverts only that axis component
306+ assert_approx(mirror([1,0,0], p=[5,7,9]), [-5,7,9], "mirror X axis");
307+ assert_approx(mirror([0,1,0], p=[5,7,9]), [5,-7,9], "mirror Y axis");
308+ assert_approx(mirror([0,0,1], p=[5,7,9]), [5,7,-9], "mirror Z axis");
309+ // Mirror with list of points
310+ assert_approx(mirror([1,0,0], p=[[1,2,3],[4,5,6]]), [[-1,2,3],[-4,5,6]], "mirror point list");
311+ // Non-unit vector: should work the same (internally normalized)
312+ assert_approx(mirror([2,0,0], p=[5,7,9]), [-5,7,9], "mirror non-unit vector");
313+ assert_approx(mirror([0,10,0], p=[5,7,9]), [5,-7,9], "mirror scaled vector");
254314}
255315test_mirror();
256316'''
@@ -263,6 +323,18 @@ include <../std.scad>
263323module test_xflip() {
264324 assert_approx(xflip(), [[-1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]]);
265325 assert_approx(xflip(p=[1,2,3]), [-1,2,3]);
326+ // Zero value stays zero
327+ assert_approx(xflip(p=[0,5,10]), [0,5,10]);
328+ // Negative values
329+ assert_approx(xflip(p=[-3,2,1]), [3,2,1]);
330+ // Double flip = identity
331+ assert_approx(xflip() * xflip(), affine3d_identity(), "double xflip = identity");
332+ // Flip with offset
333+ assert_approx(xflip(x=5, p=[5,2,3]), [5,2,3], "xflip at x=5, point on plane");
334+ assert_approx(xflip(x=5, p=[7,2,3]), [3,2,3], "xflip at x=5, point off plane");
335+ assert_approx(xflip(x=5, p=[0,0,0]), [10,0,0], "xflip at x=5, origin");
336+ // List of points
337+ assert_approx(xflip(p=[[1,2,3],[4,5,6]]), [[-1,2,3],[-4,5,6]]);
266338 // Verify that module at least doesn't crash.
267339 xflip() union(){};
268340}
@@ -277,6 +349,18 @@ include <../std.scad>
277349module test_yflip() {
278350 assert_approx(yflip(), [[1,0,0,0],[0,-1,0,0],[0,0,1,0],[0,0,0,1]]);
279351 assert_approx(yflip(p=[1,2,3]), [1,-2,3]);
352+ // Zero value stays zero
353+ assert_approx(yflip(p=[5,0,10]), [5,0,10]);
354+ // Negative values
355+ assert_approx(yflip(p=[1,-3,2]), [1,3,2]);
356+ // Double flip = identity
357+ assert_approx(yflip() * yflip(), affine3d_identity(), "double yflip = identity");
358+ // Flip with offset
359+ assert_approx(yflip(y=5, p=[2,5,3]), [2,5,3], "yflip at y=5, point on plane");
360+ assert_approx(yflip(y=5, p=[2,7,3]), [2,3,3], "yflip at y=5, point off plane");
361+ assert_approx(yflip(y=5, p=[0,0,0]), [0,10,0], "yflip at y=5, origin");
362+ // List of points
363+ assert_approx(yflip(p=[[1,2,3],[4,5,6]]), [[1,-2,3],[4,-5,6]]);
280364 // Verify that module at least doesn't crash.
281365 yflip() union(){};
282366}
@@ -291,6 +375,18 @@ include <../std.scad>
291375module test_zflip() {
292376 assert_approx(zflip(), [[1,0,0,0],[0,1,0,0],[0,0,-1,0],[0,0,0,1]]);
293377 assert_approx(zflip(p=[1,2,3]), [1,2,-3]);
378+ // Zero value stays zero
379+ assert_approx(zflip(p=[5,10,0]), [5,10,0]);
380+ // Negative values
381+ assert_approx(zflip(p=[1,2,-3]), [1,2,3]);
382+ // Double flip = identity
383+ assert_approx(zflip() * zflip(), affine3d_identity(), "double zflip = identity");
384+ // Flip with offset
385+ assert_approx(zflip(z=5, p=[2,3,5]), [2,3,5], "zflip at z=5, point on plane");
386+ assert_approx(zflip(z=5, p=[2,3,7]), [2,3,3], "zflip at z=5, point off plane");
387+ assert_approx(zflip(z=5, p=[0,0,0]), [0,0,10], "zflip at z=5, origin");
388+ // List of points
389+ assert_approx(zflip(p=[[1,2,3],[4,5,6]]), [[1,2,-3],[4,5,-6]]);
294390 // Verify that module at least doesn't crash.
295391 zflip() union(){};
296392}
@@ -483,6 +579,10 @@ include <../std.scad>
483579module test_frame_map() {
484580 assert(approx(frame_map(x=[1,1,0], y=[-1,1,0]), affine3d_zrot(45)));
485581 assert(approx(frame_map(x=[0,1,0], y=[0,0,1]), rot(v=[1,1,1],a=120)));
582+ // Identity: default axes
583+ assert_approx(frame_map(x=[1,0,0], y=[0,1,0]), affine3d_identity(), "frame_map identity");
584+ // Swapping X and Y axes is equivalent to a 90-degree rotation about Z
585+ assert_approx(frame_map(x=[0,1,0], y=[-1,0,0]), affine3d_zrot(90), "frame_map 90 deg Z");
486586}
487587test_frame_map();
488588'''
@@ -496,6 +596,26 @@ module test_skew() {
496596 m = affine3d_skew(sxy=2, sxz=3, syx=4, syz=5, szx=6, szy=7);
497597 assert_approx(skew(sxy=2, sxz=3, syx=4, syz=5, szx=6, szy=7), m);
498598 assert_approx(skew(sxy=2, sxz=3, syx=4, syz=5, szx=6, szy=7, p=[1,2,3]), apply(m,[1,2,3]));
599+ // Zero skew = identity
600+ assert_approx(skew(), affine3d_identity(), "skew with no args = identity");
601+ assert_approx(skew(sxy=0, sxz=0, syx=0, syz=0, szx=0, szy=0), affine3d_identity(), "skew all zeros = identity");
602+ // Zero skew applied to a point leaves it unchanged
603+ assert_approx(skew(p=[5,10,15]), [5,10,15], "skew zero on point = identity");
604+ // Single-axis skew
605+ assert_approx(skew(sxy=1, p=[1,2,3]), apply(affine3d_skew(sxy=1), [1,2,3]), "skew sxy only");
606+ assert_approx(skew(sxz=1, p=[1,2,3]), apply(affine3d_skew(sxz=1), [1,2,3]), "skew sxz only");
607+ assert_approx(skew(syx=1, p=[1,2,3]), apply(affine3d_skew(syx=1), [1,2,3]), "skew syx only");
608+ assert_approx(skew(syz=1, p=[1,2,3]), apply(affine3d_skew(syz=1), [1,2,3]), "skew syz only");
609+ assert_approx(skew(szx=1, p=[1,2,3]), apply(affine3d_skew(szx=1), [1,2,3]), "skew szx only");
610+ assert_approx(skew(szy=1, p=[1,2,3]), apply(affine3d_skew(szy=1), [1,2,3]), "skew szy only");
611+ // Skew with negative values
612+ assert_approx(skew(sxy=-3, p=[1,2,3]), apply(affine3d_skew(sxy=-3), [1,2,3]), "skew negative sxy");
613+ // Skew via angle
614+ assert_approx(skew(axy=45, p=[1,2,3]), skew(sxy=tan(45), p=[1,2,3]), "skew axy=45 == sxy=tan(45)");
615+ assert_approx(skew(ayx=30, p=[1,2,3]), skew(syx=tan(30), p=[1,2,3]), "skew ayx=30 == syx=tan(30)");
616+ // List of points
617+ m2 = affine3d_skew(sxy=2);
618+ assert_approx(skew(sxy=2, p=[[1,0,0],[0,1,0],[0,0,1]]), apply(m2, [[1,0,0],[0,1,0],[0,0,1]]), "skew point list");
499619 // Verify that module at least doesn't crash.
500620 skew(undef,2,3,4,5,6,7) union(){};
501621}
0 commit comments