The “dream” of all vectrex players and programmers was allways to be able to do a vector game, where all objects are displayed with correct 3d vectors.
Common believe is, that in realtime this is not possible for the pure old brute. And since as of now not one game actually exists it is probably true – or VERY difficult (demos do exist!).
Some time ago I did a set of routines, that look like 3d and are reasonably fast in displaying I used some “tricks” to achieve the fast results.
I never came around doing a full game with these routines, but I am fairly certain, that one could!
Here another image of “demo” I did for the Vectrex “C” package.
You can grab the whole package: 3D.zip
Here the original readme(s):
3D for assembler. Only #################################################### AS09 Assembler for M6809/H6309 [1.19]. Copyright 1994-97, Frank A. Vorstenbosch #################################################### supported, since I make heavy use of the macro functionality that assembler supplies. I strongly recomend using that Assembler for vectrex development anyway, since it IS good. This Assembler port was done in about 2h. I don't promise it will work allright. I only tested it with the CUBEM. Strangely enough the assembler port needs another vector to keep the cube in center than the C version. This MIGHT just be a bug somewhere, but I don't know if it is in assembly or in C. If there are errors in the assembler port they will most probably be in the ###.i files. The macro definitions for the uniform vectors. If some vector does not come out as expected, take a look at these definitions and compare them with their C counterpart. They nearly look the same, so if there is a bug you will probably discover it by a closer look. It was really a boring job to convert those 14 files, so there might just be that I made a few mistakes by accidently falling asleep while doing it :-) For comments on how to use it, read the "3ddemo.txt". A small assembler demo is provided, see there how to use it from assembler. There are 3 main files for the assembler version. The file "3D_VAR.I" declares all needed variables, and should go to the BSS section (including). The file "3D_MAKRO.I" includes all macros that are there for 3d stuff. Should be at the beginning of your code section. But may probably be placed anywhere it fits. The file "3D_PRG.I" has the two custom vector drawing functions. Include it somewhere in your code section. It also holds the cosinus and sinus tables, needed for angle computations. Thats it for now! Malban
And the readme for the Vectrex C-Compiler edition:
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...