
Object variableByteSubclass: #OpenGLVector
  classVariableNames: ''
  poolDictionaries: ''  !

OpenGLVector variableByteSubclass: #OpenGL3DPoint
  classVariableNames: ''
  poolDictionaries: '' !

OpenGLVector variableByteSubclass: #OpenGLColor
  classVariableNames: ''
  poolDictionaries: ''   !

OpenGLVector variableByteSubclass: #OpenGLSinglePrecisionFloatVector
  classVariableNames: ''
  poolDictionaries: ''   !

OpenGLVector variableByteSubclass: #OpenGLTransformation
  classVariableNames: ''
  poolDictionaries: ''    !

!OpenGLVector class methods !

comment
    "
Providing Primitive 3D Objects: Points, Transformations, The ModelView Matrix Stack

    In order to simplify use of the OpenGL primitives, point and transformation objects
were created. Since this particular Smalltalk has only double precision floats, the double
precision variants of the OpenGL primitives were used whenever there was a choice.
However, there are a few odd cases where only single precision is permitted. As a
consequence, a method named asSinglePrecisionFloat was added to the class Float that
converts from double to single precision.

    One problem with the OpenGL primitives (indeed with any DLL that needs objects more
complex than integers) is that they must be passed by address. This implies that they
must, from Smalltalk's perspective, look like byte arrays. Rather than incur the overhead
of constant conversion to byte arrays, 3D points are implemented as variable byte
subclasses. In the future, if more sophisticated 3D facilities are added, it may be better to have
3D points implemented in the more traditional Smalltalk manner. To provide for that possibility,
less convenient names were used in the classes; namely, OpenGL3DPoint and OpenGLTransformation.

    OpenGLVector is the root class of the point and transformation class heirarchy. It
is designed to be able to support vectors of elements of arbitrary size. Note that class methods

    elementClass, elementWidth, vectorWidth

indicate how the subclasses are to treat its components. For class OpenGL3DPoint, for
example, the defaults are Float, 8, and 3. For class OpenGLSinglePrecisionFloatVector,
which was introduced only for temporary conversion to a single precision format required
by special light operations, the defaults are ByteArray, 4, and illegal. We can't obtain
instances of OpenGLVector or OpenGLSinglePrecisionFloatVector without specifying a
size.


    Consider the root class OpenGLVector. It contains byte replacement primitives copied
from ByteArray. What is interesting about these operations is that a float understands
the same messages. To implement the at: and at:put: operations to get and set float
elements, we are effectively writing code like the following:

    | bytes |
    ''To extract the bytes from a float.''
    bytes := (ByteArray new: 8)
        replaceBytesFrom: 1
        to: 8
        with: 19.99 ''a float''
        startingAt: 1

    ''To put bytes back into a float.''
    ^Float new
        replaceBytesFrom: 1
        to: 8
        with: bytes
        startingAt: 1

    All other messages such as do:, first, last, printOn:, x, y, negated simply use the
primitive at: and at:put: messages.

    The OpenGLTransformation maps a two dimensional matrix to a one dimensional
vector via operations at:and: and at:and:put:. But its real contribution is to provide
operations for translating, scaling, and rotating a transformation in addition to an
operation for multiplying a transformation by either a 3D point or another transformation.
All of these operations indicate that 3D points are possible parameters but in fact, any
indexable collection of floats are also allowed because our OpenGLLibrary extensions
all support collections too. The OpenGLLibrary operations make use of a type
conversion method as3DPoint in IndexedCollection.

    To understand how the destructive local transformations work, one must be aware of
the fact that the OpenGL Library provides facilities for manipulating matrices ONLY on
a stack. Conceptually, there are two stacks: a perspective matrix stack IMMEDIATELY
FOLLOWED BY a model view matrix stack. The perspective stack is manipulated via
a special perspective:aspect:front:back: message. The model view matrix stack is
manipulated via stack manipulation operations.

Before an object can be rendered, one matrix must be provided on the perspective
matrix stack and one or more transformation matrices must be provided on the model
view matrix stack. As a minimum, one model view matrix is provided for the camera.
If an object cannot be moved, a matrix for the object is not needed. But it is just as easy
to provide one, in which case the object can be easily positioned. During rendering, each
point in an object is transformed by applying the transformations in the two stacks from
right to left; i.e., first the model view stack matrices (right to left) and then the perspective matrix.

The implication of all this is that it is no mean feat to use the DLL versions of the
translation, scaling, and rotation operations. So, it is important to provide facilities
(with the same names) in the OpenGLTransformation class that hides all the
complexities of the OpenGL stacks. To see how this has been done, look at the
implementation of 'translateBy: a3DPoint'. Because of the requirements
for special pre- and post-processing commands, the common parts have been factored
out in a special method called into:computeMatrix: in the OpenGLDLL. Note that
putModelViewMatrixInto: is more like a byte array copy operation; i.e., copy the matrix
into the bytes occupied by self. It's effectively a poor man's version of the become:
operation.
    "!
  
elementClass
    ^Float!
   
elementWidth
    ^8!
   
fromCollection: aCollection
    | answer |
    answer := self new: aCollection size.
    1 to: aCollection size do: [:index|
        answer at: index put: (aCollection at: index) asFloat].
    ^answer!
  
new
    ^self new: self vectorWidth!
   
new: size
    ^super new: size * self elementWidth!

vectorWidth
    ^self implementedBySubclass! !



!OpenGLVector methods !
  
as3DPoint
    ^OpenGL3DPoint fromCollection: self.!

asOpenGLSinglePrecisionFloatVector
    ^OpenGLSinglePrecisionFloatVector fromCollection: self.!

asOpenGLVector
    ^self!
  
asOrderedCollection
        "Copied from Collection"
    ^(OrderedCollection new: self size)
        addAll: self;
        yourself!

at: index
    ^(self class elementClass basicNew: self class elementWidth)
        replaceBytesFrom: 1
        to: self class elementWidth
        with: self
        startingAt: self class elementWidth * (index - 1) + 1!
   
at: index put: anElement
    | start |
    start := self class elementWidth * (index - 1) + 1.
    self
        replaceBytesFrom: start
        to: start + self class elementWidth - 1
        with: anElement
        startingAt: 1!
   
do: aBlock
    1 to: self size do: [:index |
        aBlock value: (self at: index)]!
 
first
    ^self at: 1!
 
last
    ^self at: self size!
  
printOn: aStream
    | spacer |
    self class printOn: aStream.
    aStream nextPutAll: ' ('.
    spacer := #yourself.
    self do: [:anElement |
        aStream perform: spacer.
        anElement printOn: aStream.
        spacer := #space].
    aStream nextPut: $).!
   
replaceBytesFrom: byteStart to: byteStop with: aByteObject startingAt: byteRepStart
        "Private - Copied from ByteArray."
    <primitive: 80>
    ^self primitiveFailed!

replaceBytesWith: bytes
    self
        replaceBytesFrom: 1
        to: self basicSize
        with: bytes
        startingAt: 1!
 
size
    ^super size / self class elementWidth! !

!OpenGL3DPoint class methods !
   
fromCollection: aCollection
    "Supports only 3D points (but converts 4D points to normalized 3D points)."
    | answer w |
    answer := super fromCollection: aCollection.
    (answer size = 4 and: [(w := answer last) ~= 1.0]) ifTrue: [
        1 to: 4 do: [:index |
            answer at: index put: ((answer at: index) / w)]].
    ^answer.!
 
vectorWidth
    ^3!

x: x y: y z: z
    ^self new x: x; y: y; z: z.! !



!OpenGL3DPoint methods !
  
as3DPoint
    ^self!
   
negated
    | answer |
    answer := self class new.
    1 to: self size do: [:i |
        answer at: i put: (self at: i) negated].
    ^answer!
   
reverseMultiplyWithTransformation: aTransformation
    | vector sum result |
    vector := self asOrderedCollection add: 1.0; yourself.
    result := Array new: 4.
    1 to: 4 do: [:i |
        sum := 0.0.
        1 to: 4 do: [:j |
            sum := sum + ((aTransformation at: i and: j) * (vector at: j))].
        result at: i put: sum].
    ^result as3DPoint.!
   
x
    ^self at: 1!
 
x: x
    self at: 1 put: x asFloat!

y
    ^self at: 2!
 
y: y
    self at: 2 put: y asFloat!

z
    ^self at: 3!
 
z: z
    self at: 3 put: z asFloat! !


!OpenGLColor class methods !
 
red: red green: green blue: blue
    ^self new red: red; green: green; blue: blue; alpha: 1.0!
 
vectorWidth
    ^4! !



!OpenGLColor methods !

alpha
    ^self at: 4!
 
alpha: alpha
    ^self at: 4 put: alpha!
   
blue
    ^self at: 3!
  
blue: blue
    ^self at: 3 put: blue!
  
green
    ^self at: 2!
 
green: green
    ^self at: 2 put: green!
   
red
    ^self at: 1!
   
red: red
    ^self at: 1 put: red! !


!OpenGLSinglePrecisionFloatVector class methods !

elementClass
    ^ByteArray!
   
elementWidth
    ^4!
   
fromCollection: aCollection
    | answer |
    answer := self new: aCollection size.
    1 to: aCollection size do: [:index|
        answer at: index put: (aCollection at: index) asSinglePrecisionFloat].
    ^answer! !



!OpenGLSinglePrecisionFloatVector methods !
  
asOpenGLSinglePrecisionFloatVector
    ^self! !




!OpenGLTransformation class methods !

identity
    ^OpenGLLibrary computeMatrix: [OpenGLLibrary loadIdentityMatrix].!

rotatedBy: angle around: a3DPoint
    ^self identity rotateBy: angle around: a3DPoint!
 
scaledBy: a3DPoint
    ^self identity scaleBy: a3DPoint!
   
translatedBy: a3DPoint
    ^self identity translateBy: a3DPoint.!
  
vectorWidth
    ^16! !



!OpenGLTransformation methods !
  
* vector
    ^((vector isKindOf: Collection) ifTrue: [vector as3DPoint] ifFalse: [vector])
        reverseMultiplyWithTransformation: self!
   
at: row and: column
    "Transformations are in column major form."
    ^self at: (column - 1) * 4 + row!
 
at: row and: column put: aFloat
    "Transformations are in column major form."
    ^self at: (column - 1) * 4 + row put: aFloat!
 
globalRotateBy: angle around: a3DPoint
    ^self replaceBytesWith: (self class identity rotateBy: angle around: a3DPoint) * self!
  
globalTranslateBy: a3DPoint
    ^self replaceBytesWith: (self class identity translatedBy: a3DPoint) * self!
   
reverseMultiplyWithTransformation: aTransformation
    ^OpenGLLibrary computeMatrix: [
        OpenGLLibrary
            loadMatrix: aTransformation;
            multiplyMatrix: self]!

rotateBy: angle around: a3DPoint
    "Answers the modified receiver."
    ^OpenGLLibrary into: self computeMatrix: [
        OpenGLLibrary loadMatrix: self; rotateBy: angle around: a3DPoint].!
 
rotatedBy: angle around: a3DPoint
    "Answers a new transformation (the receiver is unmodified)."
    ^OpenGLLibrary computeMatrix: [
        OpenGLLibrary loadMatrix: self; rotateBy: angle around: a3DPoint].!
   
scaleBy: a3DPoint
    "Answers the modified receiver."
    ^OpenGLLibrary into: self computeMatrix: [
        OpenGLLibrary loadMatrix: self; scaleBy: a3DPoint].!
   
scaledBy: a3DPoint
    "Answers a new transformation (the receiver is unmodified)."
    ^OpenGLLibrary computeMatrix: [
        OpenGLLibrary loadMatrix: self; scaleBy: a3DPoint].!
 
translateBy: a3DPoint
    "Answers the modified receiver."
    ^OpenGLLibrary into: self computeMatrix: [
        OpenGLLibrary loadMatrix: self; translateBy: a3DPoint].!
   
translatedBy: a3DPoint
    "Answers a new transformation (the receiver is unmodified)."
    ^OpenGLLibrary computeMatrix: [
        OpenGLLibrary loadMatrix: self; translateBy: a3DPoint].! !

!IndexedCollection methods !

as3DPoint
    ^OpenGL3DPoint fromCollection: self!

asOpenGLSinglePrecisionFloatVector
    ^OpenGLSinglePrecisionFloatVector fromCollection: self!

asOpenGLVector
    ^OpenGLVector fromCollection: self! !

!Float methods !

asSinglePrecisionFloat
    "Converts a Smalltalk 8 byte float to a external 4 byte float.
    Change the exponent byte from format xxyyyzzz to xxzzz and shift the
    mantissa bits over keeping only the rightmost four bytes.
    Note that Floats are stored in reverse byte order so the high order bytes of byte
    i are shifted into the low order bytes of byte i + 1."
    |  result |
    result := ByteArray new: 4.
    1 to: 4 do: [:index|
        result at: index put:
            (((self at: index + 4) & 16r1F) bitShift: 3) |
            (((self at: index + 3) & 16rE0) bitShift: -5)].
    result at: 4 put: ((result at: 4) & 16r3F) | ((self at: 8) & 16rC0).
    ^result.! !

