Module: duim-geometry-internals Synopsis: DUIM geometry Author: Scott McKay, Andy Armstrong Copyright: Original Code is Copyright (c) 1995-2004 Functional Objects, Inc. All rights reserved. License: Functional Objects Library Public License Version 1.0 Dual-license: GNU Lesser General Public License Warranty: Distributed WITHOUT WARRANTY OF ANY KIND /// Transformation protocol define protocol <> () function transform-components (transform :: ) => (mxx :: , mxy :: , myx :: , myy :: , tx :: , ty :: ); function transform-coordinate-sequence (transform :: , coordinates :: , #key copy?) => (coordinates :: ); // Constructors function make-transform (mxx :: , mxy :: , myx :: , myy :: , tx :: , ty :: ) => (transform :: ); function make-translation-transform (tx :: , ty :: ) => (transform :: ); function make-scaling-transform (mx :: , my :: , #key origin-x, origin-y) => (transform :: ); function make-scaling-transform* (mx :: , my :: , #key origin) => (transform :: ); function make-rotation-transform (angle :: , #key origin-x, origin-y) => (transform :: ); function make-rotation-transform* (angle :: , #key origin) => (transform :: ); function make-reflection-transform (x1 :: , y1 :: , x2 :: , y2 :: ) => (transform :: ); function make-reflection-transform* (point-1 :: , point-2 :: ) => (transform :: ); // Predicates function transform-equal (transform1 :: , transform2 :: ) => (true? :: ); function identity-transform? (transform :: ) => (true? :: ); function translation-transform? (transform :: ) => (true? :: ); function invertible-transform? (transform :: ) => (true? :: ); function reflection-transform? (transform :: ) => (true? :: ); function rigid-transform? (transform :: ) => (true? :: ); function even-scaling-transform? (transform :: ) => (true? :: ); function scaling-transform? (transform :: ) => (true? :: ); function rectilinear-transform? (transform :: ) => (true? :: ); // Composition function compose-transforms (transform1 :: , transform2 :: ) => (transform :: ); function compose-translation-with-transform (transform :: , tx :: , ty :: ) => (transform :: ); function compose-transform-with-translation (transform :: , tx :: , ty :: ) => (transform :: ); function compose-scaling-with-transform (transform :: , mx :: , my :: , #key origin) => (transform :: ); function compose-transform-with-scaling (transform :: , mx :: , my :: , #key origin) => (transform :: ); function compose-rotation-with-transform (transform :: , angle :: , #key origin) => (transform :: ); function compose-transform-with-rotation (transform :: , angle :: , #key origin) => (transform :: ); function invert-transform (transform :: ) => (transform :: ); // Simple transformations function transform-position (transform :: , x :: , y :: ) => (x :: , y :: ); function untransform-position (transform :: , x :: , y :: ) => (x :: , y :: ); function transform-distance (transform :: , dx :: , dy :: ) => (dx :: , dy :: ); function untransform-distance (transform :: , dx :: , dy :: ) => (dx :: , dy :: ); function transform-angles (transform :: , start-angle :: , end-angle :: ) => (start-angle :: , end-angle :: ); function untransform-angles (transform :: , start-angle :: , end-angle :: ) => (start-angle :: , end-angle :: ); // Boxes, by definition, have integer coordinates function transform-box (transform :: , x1 :: , y1 :: , x2 :: , y2 :: ) => (left :: , top :: , right :: , bottom :: ); function untransform-box (transform :: , x1 :: , y1 :: , x2 :: , y2 :: ) => (left :: , top :: , right :: , bottom :: ); end protocol <>; /// The identity transform define sealed class () end class ; define sealed domain make (singleton()); define sealed domain initialize (); define sealed inline method transform-components (transform :: ) => (mxx :: , mxy :: , myx :: , myy :: , tx :: , ty :: ); values(1, 0, 0, 1, 0, 0) end method transform-components; define constant $identity-transform :: = make(); /// Translation transformations define open abstract class () sealed slot %inverse :: false-or() = #f; end class ; /// Float translation transformations define sealed class () sealed slot %tx :: , required-init-keyword: tx:; sealed slot %ty :: , required-init-keyword: ty:; end class ; define sealed domain make (singleton()); define sealed domain initialize (); define sealed inline method transform-components (transform :: ) => (mxx :: , mxy :: , myx :: , myy :: , tx :: , ty :: ); values(1.0, 0.0, 0.0, 1.0, transform.%tx, transform.%ty) end method transform-components; /// Integer translation transformations define sealed class () sealed slot %tx :: , required-init-keyword: tx:; sealed slot %ty :: , required-init-keyword: ty:; end class ; define sealed domain make (singleton()); define sealed domain initialize (); define sealed inline method transform-components (transform :: ) => (mxx :: , mxy :: , myx :: , myy :: , tx :: , ty :: ); values(1, 0, 0, 1, transform.%tx, transform.%ty) end method transform-components; /// Conditions define open abstract class () end class ; define sealed class () sealed constant slot %transform, required-init-keyword: transform:; end class ; define method condition-to-string (condition :: ) => (string :: ) format-to-string("The transformation %= is singular", condition.%transform) end method condition-to-string; /// Constructors define sealed method make-translation-transform (tx :: , ty :: ) => (transform :: ) case zero?(tx) & zero?(ty) => $identity-transform; integral?(tx) & integral?(ty) => make(, tx: truncate(tx), ty: truncate(ty)); otherwise => make(, tx: as(, tx), ty: as(, ty)) end end method make-translation-transform; define sealed inline method make (class == , #key tx = 0, ty = 0) => (transform :: ) make-translation-transform(tx, ty) end method make; /// Predicates define method \= (tr1 :: , tr2 :: ) => (true? :: ) tr1 == tr2 | transform-equal(tr1, tr2) end method \=; define sealed method transform-equal (tr1 :: , tr2 :: ) => (true? :: ) #t end method transform-equal; define sealed method transform-equal (tr1 :: , tr2 :: ) => (true? :: ) // NB: the translations can be integers or single floats here tr1.%tx = tr2.%tx & tr1.%ty = tr2.%ty end method transform-equal; define method transform-equal (tr1 :: , tr2 :: ) => (true? :: ) let (mxx1, mxy1, myx1, myy1, tx1, ty1) = transform-components(tr1); let (mxx2, mxy2, myx2, myy2, tx2, ty2) = transform-components(tr2); mxx1 = mxx2 & mxy1 = mxy2 & myx1 = myx2 & myy1 = myy2 & tx1 = tx2 & ty1 = ty2 end method transform-equal; // Identity transform? define sealed method identity-transform? (transform :: ) => (true? :: ) #t end method identity-transform?; define sealed method identity-transform? (transform :: ) => (true? :: ) #f end method identity-transform?; // Translation transform? define sealed method translation-transform? (transform :: ) => (true? :: ) #t end method translation-transform?; define sealed method translation-transform? (transform :: ) => (true? :: ) #t end method translation-transform?; // Invertible transform? define sealed method invertible-transform? (transform :: ) => (true? :: ) #t end method invertible-transform?; define sealed method invertible-transform? (transform :: ) => (true? :: ) #t end method invertible-transform?; // Reflection transform? define sealed method reflection-transform? (transform :: ) => (true? :: ) #f end method reflection-transform?; define sealed method reflection-transform? (transform :: ) => (true? :: ) #f end method reflection-transform?; // Rigid transform? define sealed method rigid-transform? (transform :: ) => (true? :: ) #t end method rigid-transform?; define sealed method rigid-transform? (transform :: ) => (true? :: ) #t end method rigid-transform?; // Even scaling transform? define sealed method even-scaling-transform? (transform :: ) => (true? :: ) #t end method even-scaling-transform?; define sealed method even-scaling-transform? (transform :: ) => (true? :: ) #t end method even-scaling-transform?; // Scaling transform? define sealed method scaling-transform? (transform :: ) => (true? :: ) #t end method scaling-transform?; define sealed method scaling-transform? (transform :: ) => (true? :: ) #t end method scaling-transform?; // Rectilinear transform? define sealed method rectilinear-transform? (transform :: ) => (true? :: ) #t end method rectilinear-transform?; define sealed method rectilinear-transform? (transform :: ) => (true? :: ) #t end method rectilinear-transform?; /// Inversion define sealed method invert-transform (transform :: ) => (transform :: ) transform end method invert-transform; define sealed method invert-transform (transform :: ) => (transform :: ) transform.%inverse | begin // NB: the translations can be integers or single floats here, so this // call might make either a normal or an integer translation let inverse :: = make-translation-transform(-transform.%tx, -transform.%ty); inverse.%inverse := transform; transform.%inverse := inverse; inverse end end method invert-transform; /// Composition operators define method compose-transforms (tr1 :: , tr2 :: ) => (transform :: ) tr2 end method compose-transforms; define method compose-transforms (tr1 :: , tr2 :: ) => (transform :: ) tr1 end method compose-transforms; define method compose-transforms (tr1 :: , tr2 :: ) => (transform :: ) $identity-transform end method compose-transforms; define method compose-transforms (tr1 :: , tr2 :: ) => (transform :: ) // NB: the translations can be integers or single floats here let tx = tr1.%tx + tr2.%tx; let ty = tr1.%ty + tr2.%ty; make-translation-transform(tx, ty) end method compose-transforms; define method compose-transforms (tr1 :: , tr2 :: ) => (transform :: ) let tx = tr1.%tx + tr2.%tx; let ty = tr1.%ty + tr2.%ty; if (zero?(tx) & zero?(ty)) $identity-transform else make(, tx: tx, ty: ty) end end method compose-transforms; /// Translation composition operators define sealed method compose-translation-with-transform (transform :: , tx :: , ty :: ) => (transform :: ) make-translation-transform(tx, ty) end method compose-translation-with-transform; define sealed method compose-translation-with-transform (transform :: , tx :: , ty :: ) => (transform :: ) let tx = as(, tx); let ty = as(, ty); if (tx = 0.0 & ty = 0.0) transform else let tx = tx + transform.%tx; let ty = ty + transform.%ty; if (tx = 0.0 & ty = 0.0) $identity-transform else make-translation-transform(tx, ty) end end end method compose-translation-with-transform; define sealed method compose-translation-with-transform (transform :: , tx :: , ty :: ) => (transform :: ) make-translation-transform(transform.%tx + tx, transform.%ty + ty) end method compose-translation-with-transform; define method compose-transform-with-translation (transform :: , tx :: , ty :: ) => (transform :: ) compose-transforms(make-translation-transform(tx, ty), transform) end method compose-transform-with-translation; /// Transforming and untransforming of "spread" points define sealed inline method transform-position (transform :: , x :: , y :: ) => (x :: , y :: ) values(x, y) end method transform-position; define sealed inline method transform-position (transform :: , x :: , y :: ) => (x :: , y :: ) values(x + transform.%tx, y + transform.%ty) end method transform-position; define sealed inline method transform-position (transform :: , x :: , y :: ) => (x :: , y :: ) values(x + transform.%tx, y + transform.%ty) end method transform-position; define sealed inline method transform-position (transform :: , x :: , y :: ) => (x :: , y :: ) values(x + transform.%tx, y + transform.%ty) end method transform-position; define sealed inline method untransform-position (transform :: , x :: , y :: ) => (x :: , y :: ) values(x, y) end method untransform-position; define sealed inline method untransform-position (transform :: , x :: , y :: ) => (x :: , y :: ) values(x - transform.%tx, y - transform.%ty) end method untransform-position; define sealed inline method untransform-position (transform :: , x :: , y :: ) => (x :: , y :: ) values(x - transform.%tx, y - transform.%ty) end method untransform-position; define sealed inline method untransform-position (transform :: , x :: , y :: ) => (x :: , y :: ) values(x - transform.%tx, y - transform.%ty) end method untransform-position; /// Transforming and untransforming of distances define sealed inline method transform-distance (transform :: , dx :: , dy :: ) => (x :: , y :: ) values(dx, dy) end method transform-distance; define sealed inline method transform-distance (transform :: , dx :: , dy :: ) => (dx :: , dy :: ) values(dx, dy) end method transform-distance; define sealed inline method transform-distance (transform :: , dx :: , dy :: ) => (dx :: , dy :: ) values(dx, dy) end method transform-distance; define sealed inline method untransform-distance (transform :: , dx :: , dy :: ) => (dx :: , dy :: ) values(dx, dy) end method untransform-distance; define sealed inline method untransform-distance (transform :: , dx :: , dy :: ) => (dx :: , dy :: ) values(dx, dy) end method untransform-distance; define sealed inline method untransform-distance (transform :: , dx :: , dy :: ) => (dx :: , dy :: ) values(dx, dy) end method untransform-distance; /// Transforming and untransforming of angles define sealed inline method transform-angles (transform :: , start-angle :: , end-angle :: ) => (start-angle :: , end-angle :: ) values(start-angle, end-angle) end method transform-angles; define sealed inline method transform-angles (transform :: , start-angle :: , end-angle :: ) => (start-angle :: , end-angle :: ) values(start-angle, end-angle) end method transform-angles; define sealed inline method untransform-angles (transform :: , start-angle :: , end-angle :: ) => (start-angle :: , end-angle :: ) values(start-angle, end-angle) end method untransform-angles; define sealed inline method untransform-angles (transform :: , start-angle :: , end-angle :: ) => (start-angle :: , end-angle :: ) values(start-angle, end-angle) end method untransform-angles; /// Transforming and untransforming of "spread" rectangles define sealed method transform-box (transform :: , x1 :: , y1 :: , x2 :: , y2 :: ) => (left :: , top :: , right :: , bottom :: ) values(min(x1, x2), min(y1, y2), max(x1, x2), max(y1, y2)) end method transform-box; define sealed method transform-box (transform :: , x1 :: , y1 :: , x2 :: , y2 :: ) => (left :: , top :: , right :: , bottom :: ) let nx1 = x1 + transform.%tx; let ny1 = y1 + transform.%ty; let nx2 = x2 + transform.%tx; let ny2 = y2 + transform.%ty; fix-box(min(nx1, nx2), min(ny1, ny2), max(nx1, nx2), max(ny1, ny2)) end method transform-box; define sealed method transform-box (transform :: , x1 :: , y1 :: , x2 :: , y2 :: ) => (left :: , top :: , right :: , bottom :: ) let nx1 = x1 + transform.%tx; let ny1 = y1 + transform.%ty; let nx2 = x2 + transform.%tx; let ny2 = y2 + transform.%ty; values(min(nx1, nx2), min(ny1, ny2), max(nx1, nx2), max(ny1, ny2)) end method transform-box; define sealed method untransform-box (transform :: , x1 :: , y1 :: , x2 :: , y2 :: ) => (left :: , top :: , right :: , bottom :: ) values(min(x1, x2), min(y1, y2), max(x1, x2), max(y1, y2)) end method untransform-box; define sealed method untransform-box (transform :: , x1 :: , y1 :: , x2 :: , y2 :: ) => (left :: , top :: , right :: , bottom :: ) let nx1 = x1 - transform.%tx; let ny1 = y1 - transform.%ty; let nx2 = x2 - transform.%tx; let ny2 = y2 - transform.%ty; fix-box(min(nx1, nx2), min(ny1, ny2), max(nx1, nx2), max(ny1, ny2)) end method untransform-box; define sealed method untransform-box (transform :: , x1 :: , y1 :: , x2 :: , y2 :: ) => (left :: , top :: , right :: , bottom :: ) let nx1 = x1 - transform.%tx; let ny1 = y1 - transform.%ty; let nx2 = x2 - transform.%tx; let ny2 = y2 - transform.%ty; values(min(nx1, nx2), min(ny1, ny2), max(nx1, nx2), max(ny1, ny2)) end method untransform-box; // Transforms all of the coordinate pairs in the sequence. This returns // the original sequence if the transformation is the identity and COPY? // is false, otherwise it returns a new vector containing the result. define method transform-coordinate-sequence (transform :: , coordinates :: , #key copy?) => (coordinates :: ) let length :: = size(coordinates); assert(even?(length), "Coordinate sequences must have an even number of x/y pairs"); if (transform == $identity-transform) if (copy?) copy-sequence(coordinates) else coordinates end else let result = make(, size: length); transform-coordinates-into!(transform, coordinates, result) end end method transform-coordinate-sequence; define method transform-coordinate-sequence (transform :: , coordinates :: , #key copy?) => (coordinates :: ) ignore(copy?); let length :: = size(coordinates); assert(even?(length), "Coordinate sequences must have an even number of x/y pairs"); let result = as(, coordinates); if (transform == $identity-transform) result else transform-coordinates-into!(transform, result, result) end end method transform-coordinate-sequence; define method transform-coordinates-into! (transform :: , coordinates :: , result :: ) => (result :: ) // Inline 'do-coordinates' for speed... let ncoords :: = size(coordinates); without-bounds-checks for (i :: = 0 then i + 2, until: i = ncoords) let x = coordinates[i]; let y = coordinates[i + 1]; transform-coordinates!(transform, x, y); result[i] := x; result[i + 1] := y end end; result end method transform-coordinates-into!; /// Mutable integer translation transforms define generic make-translation-transform-into! (tx :: , ty :: , into :: ) => (into :: ); define generic compose-transform-into! (transform :: , into :: ) => (into :: ); define generic compose-translation-into! (x :: , y :: , into :: ) => (into :: ); define sealed class () end class ; define sealed domain make (singleton()); define sealed domain initialize (); define method make-translation-transform-into! (tx :: , ty :: , into :: ) => (into :: ) if (integral?(tx) & integral?(ty)) make(, tx: truncate(tx), ty: truncate(ty)) else make-translation-transform(tx, ty) end end method make-translation-transform-into!; define sealed method make-translation-transform-into! (tx :: , ty :: , into :: ) => (into :: ) into.%tx := tx; into.%ty := ty; into.%inverse := #f; into end method make-translation-transform-into!; define method compose-transform-into! (transform :: , into :: ) => (into :: ) compose-transforms(transform, into) end method compose-transform-into!; define method compose-transform-into! (transform :: , into :: ) => (into :: ) into end method compose-transform-into!; define sealed method compose-transform-into! (transform :: , into :: ) => (into :: ) into.%tx := into.%tx + transform.%tx; into.%ty := into.%ty + transform.%ty; into.%inverse := #f; into end method compose-transform-into!; define method compose-translation-into! (tx :: , ty :: , into :: ) => (into :: ) compose-translation-with-transform(into, tx, ty) end method compose-translation-into!; define sealed method compose-translation-into! (tx :: , ty :: , into :: ) => (into :: ) if (integral?(tx) & integral?(ty)) into.%tx := into.%tx + truncate(tx); into.%ty := into.%ty + truncate(ty); into.%inverse := #f; into else compose-translation-with-transform(into, tx, ty) end end method compose-translation-into!;