VCETREX C - COMPILER

The 3d stuff can only be compiled with optimiziation flag set to
-O0.

It seems all compiler bugs that are still in the Vectrex C compiler
fall back to one main problem.

GCC cannot cope nicely with the m6809 construct of overlapping
registers. (A, B = D)

The original m6809 core I based this gcc version on circumvented that
problem by ALLWAYS using register D, and never bothering about
register A and B (apart from optimizing for 8bit values D -> B).

Most programs compile OK, but some (like the 3d...) do not
compile OK, than we must take measures (like -O0).

This seems to help most of the time. (MOST! Sometimes it doesn't.)

If you use Vectrex C for prototyping be prepared to look at the
*.s code it generates when looking for errors. :-(
It is still buggy :-(

But once you get used to that... it is great for many tedious things :-)


HOW TO USE THE 3D ROUTINES

a. Vectrex C
Well, the original 3d routines are pretty much (C) optimized now.
Actually you can hardly recognize them as C anymore, since most of
it is assembler.

If you use C to use the routines you don't have much to do.
Take following files and put them into your working C directory:

3D       H
STA      SED

000      H
001      H
010      H
011      H
0N1      H
100      H
101      H
110      H
111      H
11N      H
1N1      H
N01      H
N10      H
N11      H

You than have to include the "3d.h" into your source, which in
turn includes all the other *.h files.

The "sta.sed" file is another C-Compiler fix. Look at the demo7
Makefile on how to use it. You have to 'sed' the "*.S" source
produced by gcc. Using sed it a quick-hack-bugfix.
What it does:

It turns lines like "sta #some_place" to "sta some_place".

I couldn't figure out how to tell the inline assembly to remove that
darn '#' without external help :-(

There are two things left for the programmer to do.

'draw', or 'invent' a 3d Vector list. (-> see: 3D VECTOR LIST)

Figure out what kind of vectors are used by the vector list.
Call an initialize function special for that list and draw it on the
screen.
(-> see: 3D INITIALIZATION)
(-> see: GETTING THE 3D VECTOR ON THE SCREEN)


b. Assembler
Not implemented yet :-(
Shouldn't be to hard to convert the 3d 'C' to assembler, since it
is really assembler.
Once that is done (using Macros till you die...) the
rest should work exactly like in 'C'.
(implementation is left to the reader as an exercize!)


3D VECTOR LIST
Here is the structure ('C') used for a 3D Vector list.

struct Line3d
{
 signed char pattern;
 signed char strength;
 signed char *moveTo;
};

 pattern:   255 -> draw full
          2-254 -> draw pattern
              0 -> move only
              1 -> stop

 strength: scale (scale only supported in 'draw_3ds')

 moveTo:   a pointer to a pre generated 3d Vector
          (sounds complicated but isn't)

The first two entries are well known from ordinary vector lists,
I won't describe them here.
The last one 'moveTo' needs some explaining. Due to the nature
of the 3d Routines, you can use only a couple of different Vectors
for 3d drawing (13 different ones, sort of).
(-> see: WHAT IS POSSIBLE, WHAT ISNT - I AM CHEATING!)

Following is a list of available vectors (x,y,z).

 0. _0_0_0
 1. _1_0_0 _N_0_0
 2. _1_1_0 _N_N_0
 3. _1_0_1 _N_0_N
 4. _1_1_1 _N_N_N
 5. _0_1_0 _0_N_0
 6. _0_1_1 _0_N_N
 7. _0_0_1 _0_0_N
 8. _N_1_0 _1_N_0
 9. _N_0_1 _1_0_N
10. _0_N_1 _0_1_N
11. _N_1_1 _1_N_N
12. _1_N_1 _N_1_N
13. _1_1_N _N_N_1

(The first shouldn't be neccessary, the second row is the inverted first row)

These vectors are 'moveTo' vectors. From the current point you move to
the direction they specify.

e.g.
  _1_0_0  moves from the current location 1 step in x-direction,
          but doesn't change otherwise
  _1_1_0  moves from the current location 1 step in x-direction,
          and 1 step in y-direction (no z-change)
          this than sort of draws a diagonal line (2d)
and so on.

0 stands for coordinate does not change
1 stands for appropriate coordinate does change positive 1
N stands for appropriate coordinate does change negative 1

You can easily see, that not all directions are available :-(
(-> see: WHAT IS POSSIBLE, WHAT ISNT - I AM CHEATING!)

Only in angle steps of 45 degrees. But I hope that will be
sufficient for the usual vector game.

Here an example for a cube:

struct Line3d cube[] =
{
 {255, CUBE_STRENGTH, _1_0_0}, // first draw a square
 {255, CUBE_STRENGTH, _0_1_0},
 {255, CUBE_STRENGTH, _N_0_0},
 {255, CUBE_STRENGTH, _0_N_0},

 {255, CUBE_STRENGTH, _0_0_1}, // go deeper (z)

 {255, CUBE_STRENGTH, _1_0_0}, // draw a second square behing the first
 {255, CUBE_STRENGTH, _0_1_0},
 {255, CUBE_STRENGTH, _N_0_0},
 {255, CUBE_STRENGTH, _0_N_0},

 {  0, CUBE_STRENGTH, _1_0_0}, // and now draw the three other 'sides'
 {255, CUBE_STRENGTH, _0_0_N},
 {  0, CUBE_STRENGTH, _0_1_0},
 {255, CUBE_STRENGTH, _0_0_1},
 {  0, CUBE_STRENGTH, _N_0_0},
 {255, CUBE_STRENGTH, _0_0_N},

 {  1, 0, _0_0_N} // other than first parameter don't matter here
};

Note:
Using the above list generates a cube that is rotated by one
of its corners.
To use the cube's center as rotation point you have to
offset the first point accordingly, like:

struct Line3d cubeM[] =
{
 {0, CUBE_STRENGTH/2, _N_1_N},
 {255, CUBE_STRENGTH, _1_0_0},
 {255, CUBE_STRENGTH, _0_1_0},
 {255, CUBE_STRENGTH, _N_0_0},
 {255, CUBE_STRENGTH, _0_N_0},

 {255, CUBE_STRENGTH, _0_0_1},

 {255, CUBE_STRENGTH, _1_0_0},
 {255, CUBE_STRENGTH, _0_1_0},
 {255, CUBE_STRENGTH, _N_0_0},
 {255, CUBE_STRENGTH, _0_N_0},

 {  0, CUBE_STRENGTH, _1_0_0},
 {255, CUBE_STRENGTH, _0_0_N},

 {  0, CUBE_STRENGTH, _0_1_0},
 {255, CUBE_STRENGTH, _0_0_1},
 {  0, CUBE_STRENGTH, _N_0_0},
 {255, CUBE_STRENGTH, _0_0_N},

 {  1, 0, _0_0_N} // other than first parameter don't matter here
};


3D INITIALIZATION
In order to have some optimized bahaviour of the 3d calculating stuff,
it would be better to only calculated the vectors that:

a. are used
b. haven't been calculated already

If you look at the vector list for the cube you will notice that
there are only 3 different vectors used (and of course the mirrors,
but as you will see they don't count).

The initializing routine takes care of generating the rotated vectors.
It generates vectors YOU specify. And it generates them at the
RAM location the 'moveTo' vectors point to.
Using that sort of indirection in the vector list makes is possible
to calculate the 'moveTo' vectors only once per initialization.
They can even be used by many different 3d figures at the same time
(provided they have the same rotation angles).

All that is left to do is figure out what vectors are used by your
vector list.

Lets go thru that step by step, using the above cube:

These are all vectors:
 {255, CUBE_STRENGTH, _1_0_0},
 {255, CUBE_STRENGTH, _0_1_0},
 {255, CUBE_STRENGTH, _N_0_0},
 {255, CUBE_STRENGTH, _0_N_0},

 {255, CUBE_STRENGTH, _0_0_1},

 {255, CUBE_STRENGTH, _1_0_0},
 {255, CUBE_STRENGTH, _0_1_0},
 {255, CUBE_STRENGTH, _N_0_0},
 {255, CUBE_STRENGTH, _0_N_0},

 {  0, CUBE_STRENGTH, _1_0_0},
 {255, CUBE_STRENGTH, _0_0_N},
 {  0, CUBE_STRENGTH, _0_1_0},
 {255, CUBE_STRENGTH, _0_0_1},
 {  0, CUBE_STRENGTH, _N_0_0},
 {255, CUBE_STRENGTH, _0_0_N},

Now eliminate all doubles:
 {255, CUBE_STRENGTH, _1_0_0},
 {255, CUBE_STRENGTH, _0_1_0},
 {255, CUBE_STRENGTH, _N_0_0},
 {255, CUBE_STRENGTH, _0_N_0},

 {255, CUBE_STRENGTH, _0_0_1},

 {255, CUBE_STRENGTH, _0_0_N},

Now eliminate all invers ones, and mark them as invers 'A'lso:
 {255, CUBE_STRENGTH, _1_0_0}, 'A'
 {255, CUBE_STRENGTH, _0_1_0}, 'A'
 {255, CUBE_STRENGTH, _0_0_1}, 'A'

Wow!
Only three different once left!
These now go as makros into our initialize routine:

init3dCube()
{
 cosx = cosinus3d[angle_x];
 sinx = sinus3d[angle_x];
 cosy = cosinus3d[angle_y];
 siny = sinus3d[angle_y];
 cosz = cosinus3d[angle_z];
 sinz = sinus3d[angle_z];

 // set to init for cube!
 INIT_1_0_0_A;
 INIT_0_1_0_A;
 INIT_0_0_1_A;
}

Done!

 cosx, sinx
 cosy, siny
 cosz, sinz

MUST be defined befor calling the init makros.
If you use the same 'style' as I, you will have your angle information
in angle_x, angle_y, angle_z. Than the first six lines in the
initialization routine should be like above.

Note:
Following are the available pregenerated makros for use in the
initialization:

only positive    only negative      both
INIT_0_0_0       INIT_0_0_0_I       INIT_0_0_0_A
INIT_1_0_0       INIT_1_0_0_I       INIT_1_0_0_A
INIT_1_1_0       INIT_1_1_0_I       INIT_1_1_0_A
INIT_1_0_1       INIT_1_0_1_I       INIT_1_0_1_A
INIT_1_1_1       INIT_1_1_1_I       INIT_1_1_1_A
INIT_0_1_0       INIT_0_1_0_I       INIT_0_1_0_A
INIT_0_1_1       INIT_0_1_1_I       INIT_0_1_1_A
INIT_0_0_1       INIT_0_0_1_I       INIT_0_0_1_A
INIT_N_1_0       INIT_N_1_0_I       INIT_N_1_0_A
INIT_N_0_1       INIT_N_0_1_I       INIT_N_0_1_A
INIT_0_N_1       INIT_0_N_1_I       INIT_0_N_1_A
INIT_N_1_1       INIT_N_1_1_I       INIT_N_1_1_A
INIT_1_N_1       INIT_1_N_1_I       INIT_1_N_1_A
INIT_1_1_N       INIT_1_1_N_I       INIT_1_1_N_A

// following are the (redundant) inverse ones
INIT_N_0_0       INIT_N_0_0_I       INIT_N_0_0_A
INIT_N_N_0       INIT_N_N_0_I       INIT_N_N_0_A
INIT_N_0_N       INIT_N_0_N_I       INIT_N_0_N_A
INIT_N_N_N       INIT_N_N_N_I       INIT_N_N_N_A
INIT_0_N_0       INIT_0_N_0_I       INIT_0_N_0_A
INIT_0_N_N       INIT_0_N_N_I       INIT_0_N_N_A
INIT_0_0_N       INIT_0_0_N_I       INIT_0_0_N_A
INIT_1_N_0       INIT_1_N_0_I       INIT_1_N_0_A
INIT_1_0_N       INIT_1_0_N_I       INIT_1_0_N_A
INIT_0_1_N       INIT_0_1_N_I       INIT_0_1_N_A
INIT_1_N_N       INIT_1_N_N_I       INIT_1_N_N_A
INIT_N_1_N       INIT_N_1_N_I       INIT_N_1_N_A
INIT_N_N_1       INIT_N_N_1_I       INIT_N_N_1_A


GETTING THE 3D VECTOR ON THE SCREEN
There are two functions for that. After initialization you can call them.
They will draw to where the 'cursor' currently is.

a. without using strength information
void draw_3d(struct Line3d *figure)

a. with using strength information
void draw_3ds(struct Line3d *figure)

A 3D Vector output routine will than probably look like:

    ...
    init3dCube();
    zero_beam();
    move_to(40, 0);
    draw_3ds(cube);
    ...

WHAT IS POSSIBLE, WHAT ISN'T - I AM CHEATING!
Well as you will already know - only special vectors are
available, - uniform vectors with lengths of 1.
That means only vectors with 45 degrees difference.

Each direction increases in 4 degree steps!


HOW DOES IT WORK
Well you will probably know by now.
The routines generate vectors.

The generated vectors can be used and shared by vector lists.
The direction of the vector is of no importance - so that
the invers direction is really the same vector (apart from the sign).

That way I only generate at MOST 13 different vectors.
Usually I only generate vectors that I'm told to generate.
Therefor most of the time I calculate only 3-6 different vectors
per rotation.

There are two main optimizations used:

- reuse of the generated vectors
- using uniform vectors

REUSE
The vector list 'drawn' by the programmer points directly with its
'directions' to the ram location where the vectors are calculated to.
There is no need to do any copying. If the programmer rotates
5 different figures while using the same angle - we still only calculate
about 4 - 8 different rotation vectors (13 at the very very most).

UNIFORM VECTORS
Using uniform vectors and specialized makros for each of them helps a lot.
Uniform vectors have always a length of one. I use a lookup table
for the sinus - cosinus information used. We get rid of many multiplications
by using uniform vectors, since often:
 1 * sinx = sinx
-1 * sinx = -sinx
 0 * sinx = 0
Can (obviously) be used to do optimization.
I use makros for each possible constellation of vectors, so I also
get rid of 'if' - clauses for examining the possible optimizations.
I KNOW when one component is 1 or -1 or 0.

The resulting vectors have a length of 0 to 64*sinx + 64*cosx.
This is not optimal, since the sum NEVER reaches 128.
(well I couldn't be bothered)

(the multiplacation is done in assembler - not C)

ROOM FOR IMPROVEMENT
Well apart from being C. I think the routines are pretty well optimized.
But that doesn't mean one cannot draw more cycles out of them.

The usual 'do something in between waiting loops' does certainly apply.
Actually this would be a well worth and easy optimization, since the
calculation for the rotation-vectors, can be put into that 'loop', since
it is just that - calculation!!! (not effecting any internal vectrex
hardware registers)

Or some other clever stuff I didn't think of.

...

In the demo each 'figure' is calculated 'all the time'.
One could change the routines that each 3d object has its own
private RAM location. That way if the figure does not rotate...
you don't have to recalculate everything again.

If you re-calculate your figures e.g. only every second frame...
you could do two times the 3d stuff... or so...

...

Note:
Further for most 'applications' it won't even be neccessary to
rotate for all 3 directions.
One could modify the whole setup to only rotate
x,y axis... than one would be able to again save much time
for calculations...




