/* I'm trying out a different programming paradigm that new programmers may find easier to use. This is based on the model that was used in the 60's and 70's for vector graphic terminals such as you might find on a PDP computer. In this model of Vectrex graphics, there are no direct drawing commands - instead, you create objects off-screen and when the object is built you make it appear using a 'Post' command. You move the object by altering it's x,y coordinates. On the Vectrex the data structure is designed so that maximum amounts of data are held in ROM, with only variable parameters such as x,y or intensity being in RAM. By use of pointers to common data, you could even have multiple instantiations of an object in ROM, with each one having a different (y,x) position, and you could animate the image by switching through these multiple copies (Unpost-ing the object at one y,x and Posting a clone of the object which has a different Y,X embedded) so that the only RAM requirement is the 2 byte pointer for the Post-ed object. Similarly changing pointers to the vector data would allow you to animate a sprite very simply. There are two kinds of objects, movable ones and fixed ones. They are essentially the same except that the initial x,y co-ordinates are in RAM for movable objects and ROM for fixed ones. A movable object will need at least 4 bytes in RAM - x, y, and the pointer to the object. Objects will eventually be able to instantiate other objects, at relative positions to the start of the object, and at specified scales (need to decide if fixed absolute, or relative to parent object) Objects can be Post()-ed or Unpost()-ed. If movable, the Post()-ing call takes (y,x). Objects (icons) are primarily DrawVLp lists. To draw arbitrary simple lines, (which you should do as seldom as possible) they have to be put into one of these lists in RAM. There can be other kinds of objects including (raster) String objects. Objects have intensities and can flash. The word Object is synonymous with the word Sprite. Almost. This is a first draft, it will be improved later as we test on more examples and fine-tune the procedure call interface to make writing code in this style easy. Graham Toal, 12 May 2021 */ // TO DO: add: // turn(A, angle) (remove autorotate?) // 'point (A, * in direction of */ B)' // cursor following, // compare_distance(A, /*to*/ B, C, /*to*/ D) // touching(A, B) // auto_bounce(A, XL, YB, XR, YT) #ifdef LINUX // This is currently only here to allow a quick syntax check on my linux web host. // Otherwise just ignore. static void Wait_Recal(void) { } static void Reset0Ref(void) { } static void Moveto_d(int y, int x) { } static void Draw_VLp(int *lines) { } static void Draw_Pat_VL(unsigned char *list) { } static void Rot_VL_Mode(int angle, void *original, void *rotated) { } static void Intensity_a(unsigned int bright) { } static void Intensity_7F(void) { } static void Intensity_3F(void) { } static inline void InternalSetScale(unsigned int AbsScale) { } #define IN_ROM #else #include <vectrex.h> static inline void InternalSetScale(unsigned int AbsScale) { VIA_t1_cnt_lo = AbsScale; } // This doesn't seem to be working at the moment... trying to minimise RAM usage... #define IN_ROM // __attribute__ ((section(".text"))) #endif #ifndef NULL #define NULL 0 #endif // Only a subset implemented so far... these other primitives are suggestions for // the sort of features which might be added. typedef enum PRIMITIVE { FixedVL, MovableVL, RotatableVL, SynchedList, CallBack, RasterString, VectorString, END_LIST } PRIMITIVE; typedef struct FixedVLp { int y,x; int *data; } FixedVLp; typedef struct SynchedListp { signed char *data; } SynchedListp; typedef struct VLp { unsigned int bright; int points; const int *data; } VLp; typedef struct CallBackp { // add optional parameters here? int (*callback) (void); } CallBackp; typedef struct CallBack_Object { PRIMITIVE type; // == CallBack CallBackp *data; } CallBack_Object; typedef struct Fixed_VLp_Object { PRIMITIVE type; // == FixedVL FixedVLp *data; } Fixed_VLp_Object; typedef struct Synched_Listp_Object { PRIMITIVE type; // == SynchedList int y, x; unsigned int move_scale, draw_scale; SynchedListp *data; } Synched_Listp_Object; typedef struct Movable_VLp_Object { PRIMITIVE type; // == MovableVL int y, x; unsigned int AbsScale; VLp *data; } Movable_VLp_Object; typedef struct Rotatable_VLp_Object { PRIMITIVE type; // == RotatableVL int y, x; unsigned int angle; int deltaangle; unsigned int AbsScale; VLp *data; } Rotatable_VLp_Object; typedef struct Object { PRIMITIVE type; } Object; #define MAX_OBJECTS 32 int OBJSTART = 0; typedef struct Universe { Object *each[MAX_OBJECTS]; } Universe; Universe U; int Post(Object *sprite, int y, int x, unsigned int scale) { int i; for (i = 0; i < MAX_OBJECTS; i++, OBJSTART++) { if (OBJSTART == MAX_OBJECTS) OBJSTART = 0; if (U.each[OBJSTART] == NULL) break; } // fail if i == MAX_OBJECTS U.each[OBJSTART] = sprite; switch (sprite->type) { case MovableVL: { Movable_VLp_Object *O = (Movable_VLp_Object *)sprite; O->y = y; O->x = x; O->AbsScale = scale; break; } case SynchedList: { Synched_Listp_Object *O = (Synched_Listp_Object *)sprite; O->y = y; O->x = x; break; } case RotatableVL: { Rotatable_VLp_Object *O = (Rotatable_VLp_Object *)sprite; O->y = y; O->x = x; O->AbsScale = scale; break; } case CallBack: { //CallBack_Object *O = (CallBack_Object *)sprite; break; } default: break; } return OBJSTART++; } void Unpost(int icon_num) { if ((icon_num < 0) || (icon_num > MAX_OBJECTS)) return; U.each[icon_num] = NULL; } void Move(int icon_num, int y, int x) { if ((icon_num < 0) || (icon_num > MAX_OBJECTS)) return; Movable_VLp_Object *O = (Movable_VLp_Object *)U.each[icon_num]; O->x = x; O->y = y; } void Direction(int icon_num, unsigned int angle) { // rotation angles count clockwise starting at vertical=0, in range 0:255 if ((icon_num < 0) || (icon_num > MAX_OBJECTS)) return; if (U.each[icon_num] == NULL) return; if (U.each[icon_num]->type != RotatableVL) return; Rotatable_VLp_Object *P = (Rotatable_VLp_Object *)U.each[icon_num]; P->angle = angle; } void InitUniverse(void) { int i; for (i = 0; i < MAX_OBJECTS; i++) { U.each[i] = NULL; } } static inline void Rotate_VLp(unsigned int angle, const void *original, void *rotated) { // rotation angles count clockwise starting at vertical=0, in range 0:255 Rot_VL_Mode(255U-angle, (void *)original, rotated); } // this is quite a "C" optimized version of print_sync // (-O3 and no_frame_pointer) #define ZERO_DELAY 5 void draw_synced_list_c( const signed char *u, signed int y, signed int x, unsigned int scaleMove, unsigned int scaleList ) { #ifndef LINUX do { // resync / startsync dp_VIA_shift_reg = 0; // all output is BLANK // move to zero dp_VIA_cntl = (unsigned int)0xcc; // zero the integrators dp_VIA_port_a = 0; // reset integrator offset dp_VIA_port_b = (int)0b10000010; dp_VIA_t1_cnt_lo = scaleMove; // delay, till beam is at zero // volatile - otherwise delay loop does not work with -O for (volatile signed int b=ZERO_DELAY; b>0; b--); dp_VIA_port_b= (int)0b10000011; // move to "location" dp_VIA_port_a = y; // y pos to dac dp_VIA_cntl = (unsigned int)0xce; // disable zero, disable all blank dp_VIA_port_b = 0; // mux enable, dac to -> integrator y (and x) dp_VIA_port_b = 1; // mux disable, dac only to x dp_VIA_port_a = x; // dac -> x dp_VIA_t1_cnt_hi=0; // start timer // this can be done before the wait loop // since it only fills the latch, not the actual timer! dp_VIA_t1_cnt_lo = scaleList; u+=3; // moveing test for yx== 0 into the move delay if ((*(u-2)!=0) || (*(u-1)!=0)) { while ((dp_VIA_int_flags & 0x40) == 0); // wait till timer finishes // internal moveTo dp_VIA_port_a = *(u-2); // y pos to dac dp_VIA_cntl = (unsigned int)0xce; // disable zero, disable all blank dp_VIA_port_b = 0; // mux enable, dac to -> integrator y (and x) dp_VIA_port_b = 1; // mux disable, dac only to x dp_VIA_port_a = *(u-1); // dac -> x dp_VIA_t1_cnt_hi=0; // start timer while ((dp_VIA_int_flags & 0x40) == 0); // wait till timer finishes } else { while ((dp_VIA_int_flags & 0x40) == 0); // wait till timer finishes } while (1) { if (*u<0) // draw line { // draw a vector dp_VIA_port_a = *(1+u); // y pos to dac dp_VIA_port_b = 0; // mux enable, dac to -> integrator y (and x) dp_VIA_port_b=1; // mux disable, dac only to x dp_VIA_port_a = *(2+u); // dac -> x dp_VIA_t1_cnt_hi=0; // start timer dp_VIA_shift_reg = (unsigned int)0xff; // draw complete line if (scaleList>10) { while ((dp_VIA_int_flags & 0x40) == 0); // wait till timer finishes } dp_VIA_shift_reg = 0; // all output is BLANK } else if (*u == 0) // moveTo { if ((*(u+1)!=0) || (*(u+2)!=0)) { // internal moveTo dp_VIA_port_a = *(1+u); // y pos to dac dp_VIA_cntl = (unsigned int)0xce; // disable zero, disable all blank dp_VIA_port_b = 0; // mux enable, dac to -> integrator y (and x) dp_VIA_port_b =1; // mux disable, dac only to x dp_VIA_port_a = *(2+u); // dac -> x dp_VIA_t1_cnt_hi=0; // start timer while ((dp_VIA_int_flags & 0x40) == 0); // wait till timer finishes } } else { break; } u+=3; } } while (*u != 2); #endif } void UpdateDisplay(void) { int i; Wait_Recal(); //draw_objects(&world) for (i = 0; i < MAX_OBJECTS; i++) { Object *O = (Object *)U.each[i]; if (O) { switch (O->type) { case FixedVL: { Fixed_VLp_Object *P = (Fixed_VLp_Object *)O; FixedVLp *VLp = P->data; Moveto_d(VLp->y, VLp->x); Draw_VLp(VLp->data); } break; case MovableVL: { Movable_VLp_Object *P = (Movable_VLp_Object *)O; Reset0Ref(); InternalSetScale(127); Moveto_d(P->y, P->x); InternalSetScale(P->AbsScale); Intensity_a(P->data->bright); Draw_VLp((int *)P->data->data); } break; case SynchedList: { Synched_Listp_Object *P = (Synched_Listp_Object *)O; Reset0Ref(); InternalSetScale(127); Moveto_d(P->y, P->x); Intensity_a(0x7f); draw_synced_list_c( P->data->data, P->y, P->x, P->move_scale, P->draw_scale ); } break; case RotatableVL: { Rotatable_VLp_Object *P = (Rotatable_VLp_Object *)O; Reset0Ref(); InternalSetScale(127); Moveto_d(P->y, P->x); InternalSetScale(P->AbsScale); Intensity_a(P->data->bright); if (P->angle == 0) { Draw_VLp((int *)P->data->data); } else { char TEMP[P->data->points*3+1]; // apply rotation here... Rotate_VLp((unsigned int)(P->angle>>2), (unsigned int *)P->data->data, (unsigned int *)TEMP); Draw_VLp((int *)TEMP); /* a rotate&draw without extra storage would be nice... */ } // rotation angles count clockwise starting at vertical=0, in range 0:255 // If deltaange is non-0, object autorotates. full circle = 0:255 P->angle += (unsigned int)(P->deltaangle*4); } break; case CallBack: { CallBack_Object *P = (CallBack_Object *)O; P->data->callback(); break; } case RasterString: break; case VectorString: break; case END_LIST: break; } } } } static inline int abs(int x) { if (x >= 0) return x; return -x; } static inline int sgn(int x) { if (x < 0) return -1; if (x > 0) return 1; return x; } static unsigned int dotmask = 0xC3; static unsigned char patList[2]; static void drawline_patterned(int y, int x, unsigned char pat) { patList[0]=(unsigned char)y; patList[1]=(unsigned char)x; *(volatile unsigned char *)0xC829 = pat; *(volatile unsigned char *)0xC823 =0; Draw_Pat_VL(patList); } #define DRAWING_SCALE 0x80 //#define CROSSHAIR_SCALE 0x40 #define BRIGHT TRUE #define normal FALSE static int last_intensity = -128; static void drawline(unsigned int x1, unsigned int y1, unsigned int x2, unsigned int y2, int bright, int dashed) { long ax1, ay1, dx, dy; if (bright) { if (last_intensity != BRIGHT) Intensity_7F(); } else { if (last_intensity != normal) Intensity_3F(); } last_intensity = bright; Reset0Ref(); InternalSetScale(DRAWING_SCALE); ax1 = ((long)x1-128L); ay1 = ((long)y1-128L); dx = ((long)x2-(long)x1); dy = ((long)y2-(long)y1); Moveto_d((int)ay1,(int)ax1); // id dx is 255, it splits into 127 and 128 - which causes a problem because +128 is not possible if (dy == 255 || dx == 255) { // 255-unit long lines are split in three, all others are split in two. drawline_patterned((int)(dy/3L), (int)(dx/3L), (dashed ? dotmask : 0xFF)); dy -= dy/3L; dx -= dx/3L; } if (dy < -128L || dy > 127L || dx < -128L || dx > 127L) { drawline_patterned((int)(dy>>1L), (int)(dx>>1L), (dashed ? dotmask : 0xFF)); // don't care which way it rounds (>>1 or /2) because this picks up the odd bit: dy -= dy>>1L; dx -= dx>>1L; } drawline_patterned((int)dy, (int)dx, (dashed ? dotmask : 0xFF)); }