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