Welcome, Guest. Please login or register.

Author Topic: I want an asm-float-routine  (Read 3436 times)

Description:

0 Members and 1 Guest are viewing this topic.

Offline Sidewinder

  • Full Member
  • ***
  • Join Date: Mar 2002
  • Posts: 241
    • Show all replies
    • http://www.liquido2.com
Re: I want an asm-float-routine
« on: June 05, 2004, 09:00:22 PM »
@GreatLor

Woah!  Take it easy with the shifting!  I take it that you're doing all the shifting to try to optimize your 2D/3D routine?  Lets see if we can figure out why things are going wrong.

So, we have a 2D point (or vector if you prefer) at <10,10>.  We want to rotate it clockwise 30 degrees (i.e. about the Z axis).  To do this we need to multiply the x component by cos 30 and the y component by sin 30:

x' = x * cos(θ) - y * sin(θ)
y' = y * cos(θ) + x * sin(θ)

Now, the real problem here is that most Amigas don't do floating point math very quickly and sin() and cos() always return a value less than 1 so when converted to an integer they are always 0, hence x and y are always 0!  This is not good!  So how do we get around that?

One way is to use floating point math, but this is both difficult in assembly and slow in practice.  Another way, and what I think you were trying to do, is to use fixed point math.  This method scales all values by some constant so that the sin() and cos() values are not rounded off to zero.  The formulas can be rewritten as:

x' = ((x * cos(θ) * SCALE) - (y * sin(θ) * SCALE)) / SCALE
y' = ((y * cos(θ) * SCALE) + (x * sin(θ) * SCALE)) / SCALE

Using a shift left is a quick way to scale a number by a power of 2.  Shifting 7 bits scales it by 128 and is the same as multiplying the number by 128.  If we choose a good scale factor and scale all of our operations by it then things will come out fine.

So lets look at our vector and try to write a routine in AMOS to rotate it 30 degrees:
Code: [Select]

Degree

Rem never change the originals!
X = 10
Y = 10

Rem Our scale factor
SCALE = 128

Rem Rotate THETA degrees
THETA = 30

Rem Scale the sin() and cos() values
_SIN = Sin(THETA) * SCALE
_COS = Cos(THETA) * SCALE

Rem Compute the new (scaled) values
_X = X * _COS - Y * _SIN
_Y = Y * _COS + X * _SIN

Rem Unscale the coordinates
_X = _X / SCALE
_Y = _Y / SCALE

Rem Here is the new point
Plot _X, _Y

Now, in assembly:
Code: [Select]

           move.l X,d0        X-coord in d0
           move.l Y,d1        Y-coord in d1
           muls.w COS30,d0    X * cos(30)
           muls.w SIN30,d1    Y * sin(30)
           sub.l d1,d0        (X * cos(30)) - (Y * sin(30))
           lsr.l #7,d0        Unscale X
           move.l d0,_X       New x-coord
           move.l X,d0        X-coord in d0
           move.l Y,d1        Y-coord in d1
           muls.w COS30,d1    Y * cos(30)
           muls.w SIN30,d0    X * sin(30)
           add.l d0,d1        (Y * cos(30)) + (X * sin(30))
           lsr.l #7,d1        Unscale Y
           move.l d1,_Y       New y-coord
           rts

_X         ds.l 1    Storage for x-coord
_Y         ds.l 1    Storage for y-coord

X          dc.l 10   Initial x-coord
Y          dc.l 10   Initial y-coord
SIN30      dc.w 64  sin(30)*128
COS30      dc.w 110  cos(30)*128

Notice that since we've scaled the sine and cosine values, we don't need to scale the x and y coordinates.  Doing so will cause problems.  Also, everything must use the same scale factor.  Trying to shift the sines and cosines by 14 bits and the coordinates by 8 bits will not work!

Anyway, I hope this helps.  Let me know how it works for you.
Sidewinder
 

Offline Sidewinder

  • Full Member
  • ***
  • Join Date: Mar 2002
  • Posts: 241
    • Show all replies
    • http://www.liquido2.com
Re: I want an asm-float-routine
« Reply #1 on: June 06, 2004, 07:38:39 PM »
@GreatLor

Also, if you really want to use float routines in assembly the best way to do this is to use the mathffp.library and the mathtrans.library.

The mathffp.library contains the routines to use numbers encoded in Motorola's Fast Floating Point format. These are the basic operations like add, subtract, multiply, divide, as well as functions to compare FFP numbers and convert them into integers, etc.

The mathtrans.library contains more complex operations like trig functions (Sine, Cosine, Tangent) as well as square roots and conversion routines to and from the IEEE format for use with FPUs.

C has a link library called mathlink_lib.lib that contains functions to convert a string into an FFP value, but assembly offers no interface to these functions.  This means that you'll have to convert the numbers into FFP format yourself.  To do that you need to understand how FFP works.

A float can be represented as something like:

-6.7344e-3 or -6.7344 x E^-3

Here the first character is the sign.  In this case it is negative.  The sign is followed by the mantissa (6.7344).  The mantissa is followed by the exponent, which is the number after the "E" or "e".  In this case the exponent is negative.  The exponent tells us how far, and which direction to move the decimal point to arrive at the real value.  Negative exponents mean move left (smaller numbers), positive exponents mean move right (larger numbers).

The above number is equal to the value -0.0067344.

The FFP format is a 32 bits arranged as:

MMMMMMMM MMMMMMMM MMMMMMMM SEEEEEEE

Bits 0 through 6 represent the exponent in excess-64 notation.  Simply take this value and subtract 64 to get the real exponent.  This means that a value of 0 represents an exponent of -64 and a value of 127 represents an exponent of +63.

If our float value is in d0 then
Code: [Select]

    move.b d0,d1    move the lowest byte into d1
    andi.b #$7f,d1  AND d1 with $7f for only the exponent
    sub.b #64,d1    d1.b now holds the exponent value

will place the exponent in d1.

Bit 7 is the sign of the number.  0 is positive and 1 is negative.

Using
Code: [Select]

    btst.b #7,d0    Test if the value is positive

we can test the sign of the value and use the bne or beq instruction to take action if it is positve or negative.

Finally bits 8 through 31 represent the mantissa.  The mantissa is always normalized, which means that bit 31 is always set (unless representing a value of zero).  This allows us to get as much data as possible into the 24 bits of the mantissa because there are no leading zeros.

Using the code
Code: [Select]

    move.l d0,d2   copy ffp into d2
    lsr.l #8,d2    shift right 8 bits

we can extract the mantissa.

So, lets say we want to try to encode the number -6.7344e-3 into FFP format.

First of all, we know the number is negative, so the sign bit (bit 7) will be set:

00000000 00000000 00000000 10000000

we also know that the exponent is -3.  We convert to excess-64 notation by adding 64 so the exponent is 61.  In binary 61 is 111101 so we plug that into the first byte:

00000000 00000000 00000000 10111101

The mantissa is 6.7344.  Ignore the decimal point for now and we get 67,344 which is 10000011100010000.  We plug this in and get:

00000001 00000111 00010000 10111101

But the mantissa must be normalized (i.e. have a 1 in bit 31).  So we shift the 24 bits of the mantissa to the left 7 bits:

10000011 10001000 00000000 10111101

This is -6.7344e-3 encoded in FFP format.  To load it into a register for use with the mathffp.library functions we can convert it into hex:

$838800bd

and use a move.l instruction:
Code: [Select]

    move.l #$838800bd,d0

One more thing.  The number zero is a special case in FFP.  It is represented by a long word of all zeros:
Code: [Select]

    clr.l d0

And that's pretty much it.  Let me know if you have any questions.




Sidewinder
 

Offline Sidewinder

  • Full Member
  • ***
  • Join Date: Mar 2002
  • Posts: 241
    • Show all replies
    • http://www.liquido2.com
Re: I want an asm-float-routine
« Reply #2 on: June 09, 2004, 03:12:03 AM »
@GreatLor

Since Hattig hasn't jumped at the chance to explain matrices I think I'll give it a try.

A matrix is a concept for linear algebra.  It is basically an array of numbers enclosed by square brackets.  It looks something like (please image the "|" characters are connected):
Code: [Select]

[font=Courier]
 
| 1  6  34  2  |
| 23 45 10  87 |
| 12 37 9   56 |
| 0  4  75  42 |
 
[/font]

A matrix is an efficent way to represent a set of linear equations:
Code: [Select]

[font=Courier]
 
  x - 2y +  z =  0
      2y - 8z =  8
-4x + 5y + 9z = -9
 
[/font]

can be represented as:
Code: [Select]

[font=Courier]
 
| 1 -2  1  0 |
| 0  2 -8  8 |
|-4  5  9 -9 |
 
[/font]

When you think about this for a little while it becomes clear that a vector (2D or 3D point) can be represented as a set of linear equations and, thus, also as a matrix:
Code: [Select]

[font=Courier]
 
<10, 5, 7>
 
[/font]

can become
Code: [Select]

[font=Courier]
 
1x + 0y + 0z = 10      x = 10
0x + 1y + 0x = 5   ==  y = 5
0x + 0y + 1z = 7       x = 7
 
[/font]

or simply the matrix
Code: [Select]

[font=Courier]
 
| 10 |
|  5 |
|  7 |
 
[/font]

Matrices can be added and subtracted from eachother by adding or subtracting the value in each position of one matrix with the value in the same position in the other matrix.
Code: [Select]

[font=Courier]
 
| 1  0  2 |     | 3  5  -1 |     | 4  5  1 |
| 3 -1  4 |  +  | 2  7   0 |  =  | 5  6  4 |
|-2  8  7 |     | 4 -8  11 |     | 2  0 18 |
 
| 1  0  2 |     | 3  5  -1 |     |-2 -5  3 |
| 3 -1  4 |  -  | 2  7   0 |  =  | 1 -8  4 |
|-2  8  7 |     | 4 -8  11 |     |-6 16 -4 |
 
[/font]

They can be multiplied too, but it's a bit more tricky.  To do this we multiply each row of the first matrix by each column of the other matrix, element by element.  Add the resulting numbers and then we place the result in the location of of the row number and column number that we used.  Here's a small example:
Code: [Select]

[font=Courier]
 
| 2  5 |   | 4  6 |
| 3  4 | * | 1  2 |
 
[/font]

Take the first row of the first matrix and multiply each number by the first column of the second matrix, sum the result:
Code: [Select]

[font=Courier]
 
r1 * c1 = (2 * 4) + (5 * 1) = 13
 
[/font]

repeat for the other row/column combinations:
Code: [Select]

[font=Courier]
 
r2 * c1 = (3 * 4) + (4 * 1) = 16
r1 * c2 = (2 * 6) + (5 * 2) = 22
r2 * c2 = (3 * 6) + (4 * 2) = 26
 
[/font]

Then we take these results and place them into a matrix using their rows and columns as coordinates:
Code: [Select]

[font=Courier]
 
     c1  c2
r1 | 13  22 |
r2 | 16  25 |
 
[/font]

so
Code: [Select]

[font=Courier]
 
| 2  5 |   | 4  6 |   | 13  22 |
| 3  4 | * | 1  2 | = | 16  25 |
 
[/font]

in 3D it's the same thing:
Code: [Select]

[font=Courier]
 
| 1  0  2 |     | 3  5  -1 |
| 3 -1  4 |  *  | 2  7   0 |  =  ?
|-2  8  7 |     | 4 -8  11 |
 
(1 * 3) + (0 * 2) + (2 * 4) = 11
(3 * 3) + (-1 * 2) + (4 * 4) = 23
(-2 * 3) + (8 * 2) + (7 * 4) = 38
 
(1 * 5) + (0 * 7) + (2 * -8) = -11
(3 * 5) + (-1 * 7) + (4 * -8) = -16
(-2 * 5) + (8 * 7) + (7 * -8) = -10
 
(1 * -1) + (0 * 0) + (2 * 11) = 21
(3 * -1) + (-1 * 0) + (4 * 11) = 41
(-2 * -1) + (8 * 0) + (7 * 11) = 79
 
| 1  0  2 |     | 3  5  -1 |     | 11 -11 21 |
| 3 -1  4 |  *  | 2  7   0 |  =  | 23 -16 41 |
|-2  8  7 |     | 4 -8  11 |     | 38 -10 79 |
 
[/font]

I hope I did all that math correctly.  Anyway, this method can easily be adapted using two nested For loops to cycle through each row and multiply it by each column.

Ok, so that's matrix theory.  In 3D graphics we use 4 x 4 matrices.  This extra dimension is necessary to make the operations work out properly.  But because the matrices are 4 x 4, the vectors also have to be 4 numbers or [x y z 1].

So our point represented as a 3D vector looks like:
Code: [Select]

[font=Courier]
 
[10 5 7 1]
 
[/font]

Now, there is a special matrix called the identity matrix.  It is simply a matrix with a diagonal of 1's:
Code: [Select]

[font=Courier]
 
| 1  0  0  0 |
| 0  1  0  0 |
| 0  0  1  0 |
| 0  0  0  1 |
 
[/font]

This matrix is useful because if you multiply any vector by the identity matrix you get the vector back again.  This may not seem too important now, but it is really quite useful because all other transformation matrices use it as a starting point.

To translate (move) a vector to a new position in 3D space we use the translation matrix:
Code: [Select]

[font=Courier]
 
| 1  0  0  0 |
| 0  1  0  0 |
| 0  0  1  0 |
| dx dy dz 1 |
 
[/font]

If we multiply our vector by this matrix we get the new position of the vector!  Lets try to move our point 2 units right, 7 units down, and 10 units back:
Code: [Select]

[font=Courier]
 
             | 1  0  0  0 |
[10 5 7 1] * | 0  1  0  0 | = [12 -2 17 1]
             | 0  0  1  0 |
             | 2 -7 10  1 |
 
[/font]

It worked!

Now the same thing works for other operations like scaling and rotating.  Just multiply the vector by the correct matrix.

The scaling matrix is:
Code: [Select]

[font=Courier]
 
| sx 0  0  0 |
| 0  sy 0  0 |
| 0  0  sz 0 |
| 0  0  0  1 |
 
[/font]

If each dimension is to be scaled the same amount then this can become:
Code: [Select]

[font=Courier]
 
| s  0  0  0 |
| 0  s  0  0 |
| 0  0  s  0 |
| 0  0  0  1 |
 
[/font]

Where s is the scale factor.

Rotations can be done with matrices also.  To rotate about the X-axis use the matrix:
Code: [Select]

[font=Courier]
 
| 1    0       0     0 |
| 0  cos(T)  sin(T)  0 |
| 0 -sin(T)  cos(T)  0 |
| 0    0       0     1 |
 
[/font]

About the Y-axis it is:
Code: [Select]

[font=Courier]
 
| cos(T)  0 -sin(T)  0 |
|   0     1    0     0 |
| sin(T)  0  cos(T)  0 |
|   0     0    0     1 |
 
[/font]

And about the Z-axis
Code: [Select]

[font=Courier]
 
| cos(T)   sin(T)  0  0 |
| -sin(T)  cos(T)  0  0 |
|    0       0     1  0 |
|    0       0     0  1 |
 
[/font]

You can also multiply these matrices together to come up with a single matrix that does it all!  But the order that you multiply matrices DOES matter, so you generally want to  use the scale matrix first, multiply the rotation matrices next (in order X, Y, Z), and finally multiply the result by the translate matrix.  This will scale first, then rotate, and finally move an object, which is probably what you want.

I should also note that many other matrices can be applied to a vector.  A matrix that applies perspective, a matrix to twist or shear an object, basically anything you can deam of can be done with matrices!  Just do a search on the web for matrix transformations.

I hope that helps you understand how using matrices can help with 3D graphics.  Let me know if I can be of any further assistance.

Sidewinder
 

Offline Sidewinder

  • Full Member
  • ***
  • Join Date: Mar 2002
  • Posts: 241
    • Show all replies
    • http://www.liquido2.com
Re: I want an asm-float-routine
« Reply #3 on: June 09, 2004, 05:43:13 PM »
@GreatLor

I understand where you are coming from.  The jump from trig to linear algebra is a long one.  Matrix operations are not necessary for 3D graphics, but they do offer some speed gains as well as a more unified way of working with 3D concepts as long as you can understand what's going on.  Oh well, hopefully somebody learned something from my little tutorial.   :-)

Sidewinder
 

Offline Sidewinder

  • Full Member
  • ***
  • Join Date: Mar 2002
  • Posts: 241
    • Show all replies
    • http://www.liquido2.com
Re: I want an asm-float-routine
« Reply #4 on: June 10, 2004, 04:36:07 PM »
If you want proofs and such you'll really need to get a book on linear algebra.  I don't think any computer graphics book is going to go that in depth into the math.  Most assume that you know how to do the math.  However, one book that I have found that may be useful to you is "3D Math Primer for Graphics and Game Development".


Sidewinder