"       NAME            3-DClass.cls
        AUTHOR          david@ramsey.cs.laurentian.ca (David A. Templeton)
        FUNCTION a 3-d point & transformation class
        ST-VERSIONS     ST/V
        PREREQUISITES   
        CONFLICTS       
        DISTRIBUTION    world
        VERSION         1.0
        DATE    March 24th, 1992
SUMMARY 3-DClass
        I got the original code for the ST-80 'Three-D-Graphics' and now are 
        in the process of converting the rest of the classes to ST/V Win. I 
        give credit to the original writer.
        
        Any progresses will be posted. This make a nice class for the 
        existence and manipulation of 3-D points.  Very nice original code!  
        
        No problems as far as I know.

                          David Templeton
                            aka: david@ramsey.cs.laurentian.ca
"!

Object subclass: #ThreeDTransformation
  instanceVariableNames: 
    'scale translation rotation '
  classVariableNames: ''
  poolDictionaries: ''    !


!ThreeDTransformation class methods !

identity
        "Answer an instance of me with no scaling (nil) and no translation
         (0@0@0).  All rotations are zero."

        ^self new
                setScale: nil
                translation: (ThreeDPoint x: 0.0 y: 0.0 z: 0.0)
                rotation: #(1.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 1.0)!
   
rotation: aRotation
        "Answer an instance of me with a scale factor of
         nil and a translation offset of (0@0@0).  Rotations
         about the x, y and z axes are given by aRotation."

        ^self new
                setScale: nil
                translation: (ThreeDPoint x: 0.0 y: 0.0 z: 0.0)
                rotation: aRotation!
 
scale: aScale translation: aTranslation
        "Answer an instance of me with a scale factor of
         aScale and a translation offset of aTranslation.  All
         rotations are 0."

        ^self new
                setScale: aScale
                translation: aTranslation
                rotation: #(1.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 1.0)!
  
scale: aScale translation: aTranslation rotation: aRotation
        "Answer an instance of me with a scale factor of
         aScale and a translation offset of aTranslation.  Rotations
         about the x,y and z axes are given by aRotation."

        ^self new
                setScale: aScale
                translation: aTranslation
                rotation: aRotation! !



!ThreeDTransformation methods !

applyInverseTo: anObject
        "Apply the inverse of the receiver to anObject and answer the result."

        | transformedObject |
        transformedObject := anObject translateBy: self inverseTranslation.
        transformedObject := transformedObject rotateBy: self inverseRotation.
        scale == nil ifFalse: [
                transformedObject := transformedObject scaleBy: self inverseScale].
        ^transformedObject!
   
applyTo: anObject
        "Apply the receiver to anObject and answer the result."

        | transformedObject |
        scale == nil
                ifTrue: [transformedObject := anObject]
                ifFalse: [transformedObject := anObject scaleBy: scale].
        transformedObject := transformedObject rotateBy: rotation.
        transformedObject := transformedObject translateBy: translation.
        ^transformedObject!
 
checkScale: aScale
        "Private - Converts aScale to the internal format of a floating-point ThreeDPoint."

        | checkedScale |
        checkedScale := aScale asThreeDPoint.
        ^ThreeDPoint
                x: checkedScale x asFloat
                y: checkedScale y asFloat
                z: checkedScale x asFloat!
  
compose: aTransformation
        "Answer a new ThreeDTransformation that is the
         composition of the receiver and aTransformation.
         The effect of applying the resulting ThreeDTransformation
         to an object is the same as that of first applying
         aTransformation to the object and then applying the
         receiver to its result."

        | aTransformationScale newScale newTranslation rot newRotation |
        aTransformationScale := aTransformation scale.
        scale == nil
                ifTrue:
                        [aTransformation noScale
                                ifTrue: [newScale := nil]
                                ifFalse: [newScale := aTransformationScale].
                        newTranslation := translation + aTransformation translation]
                ifFalse:
                        [aTransformation noScale
                                ifTrue: [newScale := scale]
                                ifFalse: [newScale := scale * aTransformationScale].
                        newTranslation := translation + (scale * aTransformation translation)].
        rot := aTransformation rotation.
        newRotation := Array new: 9.
        newRotation at: 1 put:  ((rot at: 1) * (rotation at: 1)) +
                ((rot at: 2) * (rotation at: 4)) + ((rot at: 3) * (rotation at: 7)).
        newRotation at: 2 put: ((rot at: 1) * (rotation at: 2)) +
                ((rot at: 2) * (rotation at: 5)) + ((rot at: 3) * (rotation at: 8)).
        newRotation at: 3 put: ((rot at: 1) * (rotation at: 3)) +
                ((rot at: 2) * (rotation at: 6)) + ((rot at: 3) * (rotation at: 9)).

        newRotation at: 4 put:  ((rot at: 4) * (rotation at: 1)) +
                ((rot at: 5) * (rotation at: 4)) + ((rot at: 6) * (rotation at: 7)).
        newRotation at: 5 put: ((rot at: 4) * (rotation at: 2)) +
                ((rot at: 5) * (rotation at: 5)) + ((rot at: 6) * (rotation at: 8)).
        newRotation at: 6 put: ((rot at: 4) * (rotation at: 3)) +
                ((rot at: 5) * (rotation at: 6)) + ((rot at: 6) * (rotation at: 9)).

        newRotation at: 7 put:  ((rot at: 7) * (rotation at: 1)) +
                ((rot at: 8) * (rotation at: 4)) + ((rot at: 9) * (rotation at: 7)).
        newRotation at: 8 put: ((rot at: 7) * (rotation at: 2)) +
                ((rot at: 8) * (rotation at: 5)) + ((rot at: 9) * (rotation at: 8)).
        newRotation at: 9 put: ((rot at: 7) * (rotation at: 3)) +
                ((rot at: 8) * (rotation at: 6)) + ((rot at: 9) * (rotation at: 9)).

        ^ThreeDTransformation
                scale: newScale
                translation: newTranslation
                rotation: newRotation!

inverseRotation
        "Private - Answer with an Array representing the inverse of my rotation."

        | invRotation |
        invRotation := Array new: 9.
        invRotation at: 1 put: (rotation at: 1).
        invRotation at: 2 put: (rotation at: 4).
        invRotation at: 3 put: (rotation at: 7).
        invRotation at: 4 put: (rotation at: 2).
        invRotation at: 5 put: (rotation at: 5).
        invRotation at: 6 put: (rotation at: 8).
        invRotation at: 7 put: (rotation at: 3).
        invRotation at: 8 put: (rotation at: 6).
        invRotation at: 9 put: (rotation at: 9).
        ^invRotation!

inverseScale
        "Private - Answer with a ThreeDPoint representing the inverse of my
         scale."

        | newScale |
        newScale := self checkScale: scale.
        ^ThreeDPoint
                x: (1.0 / newScale x)
                y: (1.0 / newScale y)
                z: (1.0 / newScale z)!

inverseTranslation
        "Private - Answer with a ThreeDPoint representing the inverse of my
         translation."

        | trans |
        trans := translation asThreeDPoint.
        ^ThreeDPoint
                x: trans x negated
                y: trans y negated
                z: trans z negated!

noScale
        "Answer true if the identity scale is in effect;  answer false, otherwise."

        ^scale == nil!
  
printOn: aStream
        aStream nextPutAll: self class name, ' scale: '.
        scale printOn: aStream.
        aStream nextPutAll: ' translation: '.
        translation printOn: aStream.
        aStream nextPutAll: ' rotation: '.
        rotation printOn: aStream!
   
rotateBy: rot
        "Answer with a new ThreeDTransformation rotated by rot."

        | newRotation |
        newRotation := Array new: 9.
        newRotation at: 1 put:  ((rot at: 1) * (rotation at: 1)) +
                ((rot at: 2) * (rotation at: 4)) + ((rot at: 3) * (rotation at: 7)).
        newRotation at: 2 put: ((rot at: 1) * (rotation at: 2)) +
                ((rot at: 2) * (rotation at: 5)) + ((rot at: 3) * (rotation at: 8)).
        newRotation at: 3 put: ((rot at: 1) * (rotation at: 3)) +
                ((rot at: 2) * (rotation at: 6)) + ((rot at: 3) * (rotation at: 9)).

        newRotation at: 4 put:  ((rot at: 4) * (rotation at: 1)) +
                ((rot at: 5) * (rotation at: 4)) + ((rot at: 6) * (rotation at: 7)).
        newRotation at: 5 put: ((rot at: 4) * (rotation at: 2)) +
                ((rot at: 5) * (rotation at: 5)) + ((rot at: 6) * (rotation at: 8)).
        newRotation at: 6 put: ((rot at: 4) * (rotation at: 3)) +
                ((rot at: 5) * (rotation at: 6)) + ((rot at: 6) * (rotation at: 9)).

        newRotation at: 7 put:  ((rot at: 7) * (rotation at: 1)) +
                ((rot at: 8) * (rotation at: 4)) + ((rot at: 9) * (rotation at: 7)).
        newRotation at: 8 put: ((rot at: 7) * (rotation at: 2)) +
                ((rot at: 8) * (rotation at: 5)) + ((rot at: 9) * (rotation at: 8)).
        newRotation at: 9 put: ((rot at: 7) * (rotation at: 3)) +
                ((rot at: 8) * (rotation at: 6)) + ((rot at: 9) * (rotation at: 9)).

        ^ThreeDTransformation scale: scale translation: translation rotation: newRotation!
  
rotateXBy: anAngle
        "Answer with a new ThreeDTransformation rotated about
         the x-axis by anAngle."

        | angleCos angleSin newRotation |
        newRotation := rotation deepCopy.
        angleCos := anAngle degreesToRadians cos.
        angleSin := anAngle degreesToRadians sin.
        newRotation at: 4 put: ((rotation at: 4) * angleCos) + ((rotation at: 7) * angleSin).
        newRotation at: 5 put: ((rotation at: 5) * angleCos) + ((rotation at: 8) * angleSin).
        newRotation at: 6 put: ((rotation at: 6) * angleCos) + ((rotation at: 9) * angleSin).
        angleSin := 0 - angleSin.
        newRotation at: 7 put: ((rotation at: 4) * angleSin) + ((rotation at: 7) * angleCos).
        newRotation at: 8 put: ((rotation at: 5) * angleSin) + ((rotation at: 8) * angleCos).
        newRotation at: 9 put: ((rotation at: 6) * angleSin) + ((rotation at: 9) * angleCos).
        ^ThreeDTransformation
                scale: scale
                translation: translation
                rotation: newRotation!
   
rotateYBy: anAngle
        "Answer with a new ThreeDTransformation rotated about
         the y-axis by anAngle."

        | angleCos angleSin newRotation |
        newRotation := rotation deepCopy.
        angleCos := anAngle degreesToRadians cos.
        angleSin := 0 - (anAngle degreesToRadians sin).
        newRotation at: 1 put: ((rotation at: 1) * angleCos) + ((rotation at: 7) * angleSin).
        newRotation at: 2 put: ((rotation at: 2) * angleCos) + ((rotation at: 8) * angleSin).
        newRotation at: 3 put: ((rotation at: 3) * angleCos) + ((rotation at: 9) * angleSin).
        angleSin := 0 - angleSin.
        newRotation at: 7 put: ((rotation at: 1) * angleSin) + ((rotation at: 7) * angleCos).
        newRotation at: 8 put: ((rotation at: 2) * angleSin) + ((rotation at: 8) * angleCos).
        newRotation at: 9 put: ((rotation at: 3) * angleSin) + ((rotation at: 9) * angleCos).
        ^ThreeDTransformation
                scale: scale
                translation: translation
                rotation: newRotation!
 
rotateZBy: anAngle
        "Answer with a new ThreeDTransformation rotated about
         the z-axis by anAngle."

        | angleCos angleSin newRotation |
        newRotation := rotation deepCopy.
        angleCos := anAngle degreesToRadians cos.
        angleSin := anAngle degreesToRadians sin.
        newRotation at: 1 put: ((rotation at: 1) * angleCos) + ((rotation at: 4) * angleSin).
        newRotation at: 2 put: ((rotation at: 2) * angleCos) + ((rotation at: 5) * angleSin).
        newRotation at: 3 put: ((rotation at: 3) * angleCos) + ((rotation at: 6) * angleSin).
        angleSin := 0 - angleSin.
        newRotation at: 4 put: ((rotation at: 1) * angleSin) + ((rotation at: 4) * angleCos).
        newRotation at: 5 put: ((rotation at: 2) * angleSin) + ((rotation at: 5) * angleCos).
        newRotation at: 6 put: ((rotation at: 3) * angleSin) + ((rotation at: 6) * angleCos).
        ^ThreeDTransformation
                scale: scale
                translation: translation
                rotation: newRotation!
   
rotation
        "Answer a copy of the current rotation."

        ^rotation copy!
   
scale
        "Answer a copy of the ThreeDPoint that represents the
         current scale of the receiver."

        scale == nil
                ifTrue: [^ThreeDPoint x: 1 y: 1 z: 1]
                ifFalse: [^scale copy]!
  
scaleBy: aScale
        "Answer a new ThreeDTransformation with the scale and translation of
         the receiver both scaled by aScale.  Rotations are unaffected."

        | checkedScale newScale newTranslation |
        aScale == nil
                ifTrue:
                        [newScale := scale.
                        newTranslation := translation]
                ifFalse:
                        [checkedScale := self checkScale: aScale.
                        scale == nil
                                ifTrue: [newScale := checkedScale]
                                ifFalse: [newScale := scale * checkedScale].
                        newTranslation := checkedScale * translation].
        ^ThreeDTransformation
                scale: newScale
                translation: newTranslation
                rotation: rotation!
 
setScale: aScale translation: aTranslation rotation: aRotation
        "Private - Sets the scale to aScale and the translation to aTranslation.  Sets
         the x,y and z rotations to aRotation."

        scale := aScale.
        translation := aTranslation.
        rotation := aRotation!
   
translateBy: aThreeDPoint
        "Answer a new ThreeDTransformation with the same scale and
         rotations as the receiver and with a translation of the current
         translation plus aThreeDPoint."

        ^ThreeDTransformation
                scale: scale
                translation: translation + aThreeDPoint
                rotation: rotation!
  
translation
        "Answer a copy of the receiver's translation."

        ^translation copy!
   
translation: aValue
        "Set the receiver's translation to aValue."

        translation := aValue! !

Object subclass: #ThreeDPoint
  instanceVariableNames: 
    'x y z '
  classVariableNames: ''
  poolDictionaries: ''  !


!ThreeDPoint class methods !
 
x: xInteger y: yInteger z: zInteger
        "Answer a new instance of me with coordinates xInteger,
         yInteger and zInteger."

        ^self new setX: xInteger setY: yInteger setZ: zInteger! !



!ThreeDPoint methods !
   
* scale
        "Answer a new ThreeDPoint that is the product of the
         receiver and scale (which is a ThreeDPoint or Number)."

        | scaleThreeDPoint |
        scaleThreeDPoint := scale asThreeDPoint.
        ^ThreeDPoint
                x: x * scaleThreeDPoint x
                y: y * scaleThreeDPoint y
                z: z * scaleThreeDPoint z!
   
+ delta
        "Answer a new ThreeDPoint that is the sum of the
         receiver and delta (which is a ThreeDPoint or Number)."

        | deltaThreeDPoint |
        deltaThreeDPoint := delta asThreeDPoint.
        ^ThreeDPoint
                x: x + deltaThreeDPoint x
                y: y + deltaThreeDPoint y
                z: z + deltaThreeDPoint z!
   
- delta
        "Answer a new ThreeDPoint that is the difference of the
         receiver and delta (which is a ThreeDPoint or Number)."

        | deltaThreeDPoint |
        deltaThreeDPoint := delta asThreeDPoint.
        ^ThreeDPoint
                x: x - deltaThreeDPoint x
                y: y - deltaThreeDPoint y
                z: z - deltaThreeDPoint z!

/ scale
        "Answer a new ThreeDPoint that is the quotient of the
         receiver and scale (which is a ThreeDPoint or Number)."

        | scaleThreeDPoint |
        scaleThreeDPoint := scale asThreeDPoint.
        ^ThreeDPoint
                x: x / scaleThreeDPoint x
                y: y / scaleThreeDPoint y
                z: z / scaleThreeDPoint z!
  
// scale
        "Answer a new ThreeDPoint that is the quotient of the
         receiver and scale (which is a ThreeDPoint or Number)."

        | scaleThreeDPoint |
        scaleThreeDPoint := scale asThreeDPoint.
        ^ThreeDPoint
                x: x // scaleThreeDPoint x
                y: y // scaleThreeDPoint y
                z: z // scaleThreeDPoint z!
  
< aPoint

        ^(x < aPoint x and: [y < aPoint y]) and: [z < aPoint z]!

<= aPoint

        ^(x <= aPoint x and: [y <= aPoint y]) and: [z <= aPoint z]!

= aPoint

        self species = aPoint species
                ifTrue: [^(x = aPoint x and: [y = aPoint y]) and: [z = aPoint z]]
                ifFalse: [^false]!

> aPoint

        ^(x > aPoint x and: [y > aPoint y]) and: [z > aPoint z]!

>= aPoint

        ^(x >= aPoint x and: [y >= aPoint y]) and: [z >= aPoint z]!

abs
        "Answer a new ThreeDPoint whose x, y and z are the absolute
         values of the receiver's x, y and z."

        ^ThreeDPoint
                x: x abs
                y: y abs
                z: z abs!
 
angle: aThreeDPoint
        "Answer with the angle (in degrees) between the vectors represented
         by aThreeDPoint and the receiver."

        | dotProduct |
        dotProduct := self dotProduct: aThreeDPoint.
        ^(dotProduct / (self r * aThreeDPoint r)) arcCos radiansToDegrees!
   
asPoint
        "Answer with a Point representing the x and y coordinates of
         the receiver."

        ^x@y!
 
asThreeDPoint
        "Answer the receiver itself."

        ^self!
  
coerce: aNumber

        ^ThreeDPoint
                x: aNumber
                y: aNumber
                z: aNumber!

crossProduct: aThreeDPoint
        "Answer with a ThreeDPoint representing the cross-product
         of the receiver and aThreeDPoint (considered as vectors)."

        ^ThreeDPoint
                x: (aThreeDPoint z * self y) - (aThreeDPoint y * self z)
                y: (aThreeDPoint x * self z) - (aThreeDPoint z * self x)
                z: (aThreeDPoint y * self x) - (aThreeDPoint x * self y)!
   
deepCopy
        "Implemented here for better performance."

        ^ThreeDPoint
                x: x deepCopy
                y: y deepCopy
                z: z deepCopy!
  
dist: aThreeDPoint
        "Answer the distance between aThreeDPoint and the receiver."

        ^(aThreeDPoint - self) r!
   
dotProduct: aThreeDPoint
        "Answer a Number that is the dot product of the receiver and
         the argument, aThreeDPoint.  That is, the two points are
         multipled and the coordinates of the result summed."

        | temp |
        temp := self * aThreeDPoint.
        ^temp x abs + temp y abs + temp z abs!
  
generality
        ^90!

grid: aThreeDPoint
        "Answer a new ThreeDPoint to the nearest rounded grid modules
         specified by aThreeDPoint."

        | newX newY newZ |
        aThreeDPoint x = 0
                ifTrue: [newX := 0]
                ifFalse: [newX := x roundTo: aThreeDPoint x].
        aThreeDPoint y = 0
                ifTrue: [newY := 0]
                ifFalse: [newY := y roundTo: aThreeDPoint y].
        aThreeDPoint z = 0
                ifTrue: [newZ := 0]
                ifFalse: [newZ := z roundTo: aThreeDPoint z].
        ^ThreeDPoint
                x: newX
                y: newY
                z: newZ!
 
hash
        ^((x hash bitShift: 4) bitXor: (y hash bitShift: 2)) bitXor: z hash!
  
hashMappedBy: map
        "My hash is independent of my oop."

        ^ self hash!
  
isPerpendicularTo: aThreeDPoint
        "Answer true if the vectors represented by aThreeDPoint
         and the receiver are perpendicular, otherwise false."

        ^(self dotProduct: aThreeDPoint) = 0!
   
max: aPoint

        ^ThreeDPoint
                x: (x max: aPoint x)
                y: (y max: aPoint y)
                z: (z max: aPoint z)!
  
max: maxPoint min: minPoint

        ^ThreeDPoint
                x: (x max: minPoint x min: maxPoint x)
                y: (y max: minPoint y min: maxPoint y)
                z: (z max: minPoint z min: maxPoint z)!

min: aPoint

        ^ThreeDPoint
                x: (x min: aPoint x)
                y: (y min: aPoint y)
                z: (z min: aPoint z)!
  
moveTo: aThreeDPoint
        "Modify the receiver so that it co-encides with aThreeDPoint."

        self x: aThreeDPoint x.
        self y: aThreeDPoint y.
        self z: aThreeDPoint z!
   
negated
        "Answer a new ThreeDPoint whose x, y and z are the negated
         values of the receiver's x, y and z."

        ^ThreeDPoint
                x: x negated
                y: y negated
                z: z negated!
  
printOn: aStream
        "The receiver prints on aStream in terms of infix notation."

        x printOn: aStream.
        aStream nextPut: $@.
        y printOn: aStream.
        aStream nextPut: $@.
        z printOn: aStream!
 
r
        "Answer the receiver's radius in polar coordinate system."

        ^(self dotProduct: self) sqrt!
 
refPoint
        "Answer with a ThreeDPoint which is the 'reference point'
         used when the object is first added to a compound object."

        ^self!
  
rotateBy: rot
        "Answer with a new ThreeDPoint rotated by rot."

        ^ThreeDPoint
                x: (x * (rot at: 1)) + (y * (rot at: 4)) + (z * (rot at: 7))
                y: (x * (rot at: 2)) + (y * (rot at: 5)) + (z * (rot at: 8))
                z: (x * (rot at: 3)) + (y * (rot at: 6)) + (z * (rot at: 9))!
   
rounded
        "Answer a new ThreeDPoint whose x, y and z are the rounded
         values of the receiver's x, y and z."

        ^ThreeDPoint
                x: x rounded
                y: y rounded
                z: z rounded!
  
scaleBy: factor
        "Answer a new ThreeDPoint scaled by factor (an
         instance of ThreeDPoint or Number)."

        (factor isKindOf: Number)
                ifTrue: [^ThreeDPoint
                                x: factor * x
                                y: factor * y
                                z: factor * z]
                ifFalse: [^ThreeDPoint
                                x: factor x * x
                                y: factor y * y
                                z: factor z * z]!
   
setX: xPoint setY: yPoint setZ: zPoint
        " Private - "
        x := xPoint.
        y := yPoint.
        z := zPoint!
 
shallowCopy
        "Implemented here for better performance."

        ^ThreeDPoint
                x: x
                y: y
                z: z!
  
storeOn: aStream

        aStream nextPut: $(;
        nextPutAll: self species name;
        nextPutAll: ' x: ';
        store: x;
        nextPutAll: ' y: ';
        store: y;
        nextPutAll: ' z: ';
        store: z;
        nextPut: $).!
 
translateBy: delta
        "Answer a new ThreeDPoint translated by delta (an
         instance of ThreeDPoint or Number)."

        (delta isKindOf: Number)
                ifTrue: [^ThreeDPoint
                                x: delta + x
                                y: delta + y
                                z: delta + z]
                ifFalse: [^ThreeDPoint
                                x: delta x + x
                                y: delta y + y
                                z: delta z + z]!

truncated
        "Answer a new ThreeDPoint whose x, y and z are the truncated
         values of the receiver's x, y and z."

        ^ThreeDPoint
                x: x truncated
                y: y truncated
                z: z truncated!

truncateTo: grid
        "Answer a new ThreeDPoint that is the receiver's x, y and z
         truncated to grid x, grid y and grid x."

        (grid isKindOf: ThreeDPoint)
                ifTrue: [
                        ^ThreeDPoint
                                x: (x truncateTo: grid x)
                                y: (y truncateTo: grid y)
                                z: (z truncateTo: grid z)]
                ifFalse: [
                        ^ThreeDPoint
                                x: (x truncateTo: grid)
                                y: (y truncateTo: grid)
                                z: (z truncateTo: grid)]!
  
x
        "Answer the x coordinate."

        ^x!

x: xInteger
        "Set the x coordinate."

        x := xInteger!
  
y
        "Answer the y coordinate."

        ^y!

y: yInteger
        "Set the y coordinate."

        y := yInteger!
  
z
        "Answer the z coordinate."

        ^z!

z: zInteger
        "Set the z coordinate."

        z := zInteger! !
