3efae76cf3024b60b01800f0e2c0b32700627be6
[gbajam22] / src / xgl.c
1 /*
2 gbajam22 entry for the Gameboy Advance
3 Copyright (C) 2022  John Tsiombikas <nuclear@mutantstargoat.com>
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program.  If not, see <https://www.gnu.org/licenses/>.
17 */
18 #include <string.h>
19 #include <math.h>
20 #include "xgl.h"
21 #include "polyfill.h"
22 #include "debug.h"
23
24 #define MAT_STACK_SIZE  4
25
26 static int vp[4];
27 static int32_t mat[MAT_STACK_SIZE][16];
28 static int mtop;
29 static unsigned int opt;
30 static int32_t ldir[3];
31
32 static void draw_ptlines(int prim, const struct xvertex *varr, int vcount);
33
34
35 void xgl_init(void)
36 {
37         xgl_viewport(0, 0, 240, 160);
38         xgl_load_identity();
39
40         ldir[0] = ldir[1] = 0;
41         ldir[2] = -0x100;
42 }
43
44 void xgl_enable(unsigned int o)
45 {
46         opt |= o;
47 }
48
49 void xgl_disable(unsigned int o)
50 {
51         opt &= ~o;
52 }
53
54 void xgl_viewport(int x, int y, int w, int h)
55 {
56         vp[0] = x;
57         vp[1] = y;
58         vp[2] = w;
59         vp[3] = h;
60 }
61
62 void xgl_push_matrix(void)
63 {
64         int prev;
65
66         if(mtop >= MAT_STACK_SIZE - 1) return;
67
68         prev = mtop++;
69         memcpy(mat[mtop], mat[prev], sizeof mat[0]);
70 }
71
72 void xgl_pop_matrix(void)
73 {
74         if(mtop > 0) mtop--;
75 }
76
77 static int32_t id[] = {
78         0x10000, 0, 0, 0,
79         0, 0x10000, 0, 0,
80         0, 0, 0x10000, 0,
81         0, 0, 0, 0x10000
82 };
83
84 void xgl_load_identity(void)
85 {
86         memcpy(mat[mtop], id, sizeof mat[0]);
87 }
88
89 void xgl_load_matrix(const int32_t *m)
90 {
91         memcpy(mat[mtop], m, sizeof mat[0]);
92 }
93
94 void xgl_get_matrix(int32_t *m)
95 {
96         memcpy(m, mat[mtop], sizeof mat[0]);
97 }
98
99 #define M(i,j)  (((i) << 2) + (j))
100 #define XMUL(a, b)      (((a) >> 8) * ((b) >> 8))
101 void xgl_mult_matrix(const int32_t *m2)
102 {
103         int i, j;
104         int32_t m1[16];
105         int32_t *dest = mat[mtop];
106
107         memcpy(m1, dest, sizeof m1);
108
109         for(i=0; i<4; i++) {
110                 for(j=0; j<4; j++) {
111                         *dest++ = XMUL(m1[M(0, j)], m2[M(i, 0)]) +
112                                 XMUL(m1[M(1, j)], m2[M(i, 1)]) +
113                                 XMUL(m1[M(2, j)], m2[M(i, 2)]) +
114                                 XMUL(m1[M(3, j)], m2[M(i, 3)]);
115                 }
116         }
117 }
118
119 /* XXX TODO XXX */
120 #define XSIN(x)         (int32_t)(sin(x / 65536.0f) * 65536.0f)
121 #define XCOS(x)         (int32_t)(cos(x / 65536.0f) * 65536.0f)
122
123 void xgl_translate(int32_t x, int32_t y, int32_t z)
124 {
125         int32_t m[16] = {0x10000, 0, 0, 0, 0, 0x10000, 0, 0, 0, 0, 0x10000, 0, 0, 0, 0, 0x10000};
126         m[12] = x;
127         m[13] = y;
128         m[14] = z;
129         xgl_mult_matrix(m);
130 }
131
132 void xgl_rotate_x(int32_t angle)
133 {
134         int32_t m[16] = {0x10000, 0, 0, 0, 0, 0x10000, 0, 0, 0, 0, 0x10000, 0, 0, 0, 0, 0x10000};
135         int32_t sa = XSIN(angle);
136         int32_t ca = XCOS(angle);
137         m[5] = ca;
138         m[6] = sa;
139         m[9] = -sa;
140         m[10] = ca;
141         xgl_mult_matrix(m);
142 }
143
144 void xgl_rotate_y(int32_t angle)
145 {
146         int32_t m[16] = {0x10000, 0, 0, 0, 0, 0x10000, 0, 0, 0, 0, 0x10000, 0, 0, 0, 0, 0x10000};
147         int32_t sa = XSIN(angle);
148         int32_t ca = XCOS(angle);
149         m[0] = ca;
150         m[2] = -sa;
151         m[8] = sa;
152         m[10] = ca;
153         xgl_mult_matrix(m);
154 }
155
156 void xgl_rotate_z(int32_t angle)
157 {
158         int32_t m[16] = {0x10000, 0, 0, 0, 0, 0x10000, 0, 0, 0, 0, 0x10000, 0, 0, 0, 0, 0x10000};
159         int32_t sa = XSIN(angle);
160         int32_t ca = XCOS(angle);
161         m[0] = ca;
162         m[1] = sa;
163         m[4] = -sa;
164         m[5] = ca;
165         xgl_mult_matrix(m);
166 }
167
168 void xgl_scale(int32_t x, int32_t y, int32_t z)
169 {
170         int32_t m[16] = {0};
171         m[0] = x;
172         m[5] = y;
173         m[10] = z;
174         m[15] = 0x10000;
175         xgl_mult_matrix(m);
176 }
177
178 static void xform(struct xvertex *out, const struct xvertex *in, const int32_t *m)
179 {
180         out->x = XMUL(m[0], in->x) + XMUL(m[4], in->y) + XMUL(m[8], in->z) + m[12];
181         out->y = XMUL(m[1], in->x) + XMUL(m[5], in->y) + XMUL(m[9], in->z) + m[13];
182         out->z = XMUL(m[2], in->x) + XMUL(m[6], in->y) + XMUL(m[10], in->z) + m[14];
183 }
184
185 static void xform_norm(struct xvertex *out, const struct xvertex *in, const int32_t *m)
186 {
187         out->nx = XMUL(m[0], in->nx) + XMUL(m[4], in->ny) + XMUL(m[8], in->nz);
188         out->ny = XMUL(m[1], in->nx) + XMUL(m[5], in->ny) + XMUL(m[9], in->nz);
189         out->nz = XMUL(m[2], in->nx) + XMUL(m[6], in->ny) + XMUL(m[10], in->nz);
190 }
191
192 /* d = 1.0 / tan(fov/2) */
193 #define PROJ_D  0x20000
194 /* near Z = 0.5 */
195 #define NEAR_Z  0x18000
196
197 void xgl_draw(int prim, const struct xvertex *varr, int vcount)
198 {
199         int i, cidx, clipnum;
200         struct xvertex xv[4], xvclip[8];
201         struct pvertex pv[4];
202         int32_t ndotl;
203
204         if(prim < 3) {
205                 draw_ptlines(prim, varr, vcount);
206                 return;
207         }
208
209         while(vcount >= prim) {
210                 cidx = 0xff;//varr->cidx;
211
212                 xform(xv, varr, mat[mtop]);
213                 xform_norm(xv, varr, mat[mtop]);
214
215                 /*
216                 if(opt & XGL_LIGHTING) {
217                         ndotl = (xv->nx >> 8) * ldir[0] + (xv->ny >> 8) * ldir[1] + (xv->nz >> 8) * ldir[2];
218                         if(ndotl < 0) ndotl = 0;
219                         cidx = 128 + (ndotl >> 9);
220                         if(cidx > 255) cidx = 255;
221                 }
222                 */
223
224                 /* transform the rest of the vertices to view space */
225                 for(i=1; i<prim; i++) {
226                         xform(xv + i, varr + i, mat[mtop]);
227                 }
228
229                 /* clip against near plane */
230                 xgl_clip_near(xvclip, &clipnum, xv, prim);
231
232                 for(i=0; i<clipnum; i++) {
233                         xvclip[i].x = (xvclip[i].x << 1) / (xvclip[i].z >> 8);  /* assume aspect: ~2 */
234                         xvclip[i].y = (xvclip[i].y << 2) / (xvclip[i].z >> 8);  /* the shift is * PROJ_D */
235                         /* transform result is 24.8 */
236                         /* viewport */
237                         pv[i].x = (((xvclip[i].x + 0x100) >> 1) * vp[2]) + (vp[0] << 8);
238                         pv[i].y = (((0x100 - xvclip[i].y) >> 1) * vp[3]) + (vp[1] << 8);
239                 }
240
241                 polyfill_flat(pv, clipnum, cidx);
242 skip_poly:
243                 varr += prim;
244                 vcount -= prim;
245         }
246 }
247
248 void xgl_transform(const struct xvertex *vin, int *x, int *y)
249 {
250         struct xvertex v;
251         xform(&v, vin, mat[mtop]);
252
253         v.x = (v.x << 1) / (v.z >> 8);  /* assume aspect: ~2 */
254         v.y = (v.y << 2) / (v.z >> 8);  /* the shift is * PROJ_D */
255         /* projection result is 24.8 */
256         /* viewport */
257         *x = ((((v.x + 0x100) >> 1) * vp[2]) >> 8) + vp[0];
258         *y = ((((0x100 - v.y) >> 1) * vp[3]) >> 8) + vp[1];
259 }
260
261 static void draw_ptlines(int prim, const struct xvertex *varr, int vcount)
262 {
263         int i;
264         struct xvertex xv[2];
265
266         while(vcount >= prim) {
267                 for(i=0; i<prim; i++) {
268                         xform(xv + i, varr, mat[mtop]);
269
270                         xv[i].x = (xv[i].x << 1) / (xv[i].z >> 8);      /* assume aspect: ~2 */
271                         xv[i].y = (xv[i].y << 2) / (xv[i].z >> 8);      /* the shift is * PROJ_D */
272                         /* projection result is 24.8 */
273                         /* viewport */
274                         xv[i].x = ((((xv[i].x + 0x100) >> 1) * vp[2]) >> 8) + vp[0];
275                         xv[i].y = ((((0x100 - xv[i].y) >> 1) * vp[3]) >> 8) + vp[1];
276                         varr++;
277                 }
278                 vcount -= prim;
279
280                 /* line clipping */
281 #ifndef ALT_LCLIP
282                 clip_line((int*)&xv[0].x, (int*)&xv[0].y, (int*)&xv[1].x, (int*)&xv[1].y, vp[0], vp[1], vp[2] - 1, vp[3] - 1);
283 #endif
284                 draw_line(xv[0].x, xv[0].y, xv[1].x, xv[1].y, 0xff);
285         }
286 }
287
288 void xgl_xyzzy(void)
289 {
290         mat[mtop][12] = mat[mtop][13] = 0;
291 }
292
293 #define ISECT_NEAR(v0, v1)      ((((v0)->z - NEAR_Z) << 8) / ((v0)->z - (v1)->z))
294
295 #define LERP_VATTR(res, v0, v1, t) \
296         do { \
297                 (res)->x = (v0)->x + (((v1)->x - (v0)->x) >> 8) * (t);  \
298                 (res)->y = (v0)->y + (((v1)->y - (v0)->y) >> 8) * (t);  \
299                 (res)->z = (v0)->z + (((v1)->z - (v0)->z) >> 8) * (t);  \
300                 (res)->nx = (v0)->nx + (((v1)->nx - (v0)->nx) >> 8) * (t);      \
301                 (res)->ny = (v0)->ny + (((v1)->ny - (v0)->ny) >> 8) * (t);      \
302                 (res)->nz = (v0)->nz + (((v1)->nz - (v0)->nz) >> 8) * (t);      \
303                 (res)->tx = (v0)->tx + (((v1)->tx - (v0)->tx) >> 8) * (t);      \
304                 (res)->ty = (v0)->ty + (((v1)->ty - (v0)->ty) >> 8) * (t);      \
305                 (res)->lit = (v0)->lit + (((v1)->lit - (v0)->lit) >> 8) * (t); \
306         } while(0)
307
308 static int clip_edge_near(struct xvertex *poly, int *vnumptr, struct xvertex *v0, struct xvertex *v1)
309 {
310         int vnum = *vnumptr;
311         int in0, in1;
312         int32_t t;
313         struct xvertex *vptr;
314
315         in0 = v0->z >= NEAR_Z ? 1 : 0;
316         in1 = v1->z >= NEAR_Z ? 1 : 0;
317
318         if(in0) {
319                 /* start inside */
320                 if(in1) {
321                         /* all inside */
322                         poly[vnum++] = *v1;     /* append v1 */
323                         *vnumptr = vnum;
324                         return 1;
325                 } else {
326                         /* going out */
327                         vptr = poly + vnum;
328                         t = ISECT_NEAR(v0, v1); /* 24:8 */
329                         LERP_VATTR(vptr, v0, v1, t);
330                         ++vnum;         /* append new vertex on the intersection point */
331                 }
332         } else {
333                 /* start outside */
334                 if(in1) {
335                         /* going in */
336                         vptr = poly + vnum;
337                         t = ISECT_NEAR(v0, v1);
338                         LERP_VATTR(vptr, v0, v1, t);
339                         ++vnum;         /* append new vertex ... */
340                         /* then append v1 */
341                         poly[vnum++] = *v1;
342                 } else {
343                         /* all outside */
344                         return -1;
345                 }
346         }
347
348         *vnumptr = vnum;
349         return 0;
350 }
351
352 /* special case near-plane clipper */
353 int xgl_clip_near(struct xvertex *vout, int *voutnum, struct xvertex *vin, int vnum)
354 {
355         int i, nextidx, res;
356         int edges_clipped = 0;
357
358         *voutnum = 0;
359
360         for(i=0; i<vnum; i++) {
361                 nextidx = i + 1;
362                 if(nextidx >= vnum) nextidx = 0;
363                 res = clip_edge_near(vout, voutnum, vin + i, vin + nextidx);
364                 if(res == 0) {
365                         ++edges_clipped;
366                 }
367         }
368
369         if(*voutnum <= 0) return -1;
370         return edges_clipped > 0 ? 0 : 1;
371 }