1// MIT License
2
3// Copyright (c) 2020 Vadim Grigoruk @nesbox // grigoruk@gmail.com
4
5// Permission is hereby granted, free of charge, to any person obtaining a copy
6// of this software and associated documentation files (the "Software"), to deal
7// in the Software without restriction, including without limitation the rights
8// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9// copies of the Software, and to permit persons to whom the Software is
10// furnished to do so, subject to the following conditions:
11
12// The above copyright notice and this permission notice shall be included in all
13// copies or substantial portions of the Software.
14
15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21// SOFTWARE.
22
23#include "api.h"
24#include "core.h"
25#include "tilesheet.h"
26
27#include <string.h>
28#include <stdlib.h>
29#include <math.h>
30#include <float.h>
31
32#define TRANSPARENT_COLOR 255
33
34typedef void(*PixelFunc)(tic_mem* memory, s32 x, s32 y, u8 color);
35
36static tic_tilesheet getTileSheetFromSegment(tic_mem* memory, u8 segment)
37{
38 u8* src;
39 switch (segment) {
40 case 0:
41 case 1:
42 src = (u8*)&memory->ram->font; break;
43 default:
44 src = (u8*)&memory->ram->tiles.data; break;
45 }
46
47 return tic_tilesheet_get(segment, src);
48}
49
50static u8* getPalette(tic_mem* tic, u8* colors, u8 count)
51{
52 static u8 mapping[TIC_PALETTE_SIZE];
53 for (s32 i = 0; i < TIC_PALETTE_SIZE; i++) mapping[i] = tic_tool_peek4(tic->ram->vram.mapping, i);
54 for (s32 i = 0; i < count; i++) mapping[colors[i]] = TRANSPARENT_COLOR;
55 return mapping;
56}
57
58static inline u8 mapColor(tic_mem* tic, u8 color)
59{
60 return tic_tool_peek4(tic->ram->vram.mapping, color & 0xf);
61}
62
63static inline void setPixel(tic_core* core, s32 x, s32 y, u8 color)
64{
65 const tic_vram* vram = &core->memory.ram->vram;
66
67 if (x < core->state.clip.l || y < core->state.clip.t || x >= core->state.clip.r || y >= core->state.clip.b) return;
68
69 tic_api_poke4((tic_mem*)core, y * TIC80_WIDTH + x, color);
70}
71
72static inline void setPixelFast(tic_core* core, s32 x, s32 y, u8 color)
73{
74 // does not do any CLIP checking, the caller needs to do that first
75 tic_api_poke4((tic_mem*)core, y * TIC80_WIDTH + x, color);
76}
77
78static u8 getPixel(tic_core* core, s32 x, s32 y)
79{
80 return tic_api_peek4((tic_mem*)core, y * TIC80_WIDTH + x);
81}
82
83#define EARLY_CLIP(x, y, width, height) \
84 ( \
85 (((y)+(height)-1) < core->state.clip.t) \
86 || (((x)+(width)-1) < core->state.clip.l) \
87 || ((y) >= core->state.clip.b) \
88 || ((x) >= core->state.clip.r) \
89 )
90
91static void drawHLine(tic_core* core, s32 x, s32 y, s32 width, u8 color)
92{
93 const tic_vram* vram = &core->memory.ram->vram;
94
95 if (y < core->state.clip.t || core->state.clip.b <= y) return;
96
97 s32 xl = MAX(x, core->state.clip.l);
98 s32 xr = MIN(x + width, core->state.clip.r);
99 s32 start = y * TIC80_WIDTH;
100
101 for(s32 i = start + xl, end = start + xr; i < end; ++i)
102 tic_api_poke4((tic_mem*)core, i, color);
103}
104
105static void drawVLine(tic_core* core, s32 x, s32 y, s32 height, u8 color)
106{
107 const tic_vram* vram = &core->memory.ram->vram;
108
109 if (x < core->state.clip.l || core->state.clip.r <= x) return;
110
111 s32 yl = y < 0 ? 0 : y;
112 s32 yr = y + height >= TIC80_HEIGHT ? TIC80_HEIGHT : y + height;
113
114 for (s32 i = yl; i < yr; ++i)
115 setPixel(core, x, i, color);
116}
117
118static void drawRect(tic_core* core, s32 x, s32 y, s32 width, s32 height, u8 color)
119{
120 for (s32 i = y; i < y + height; ++i)
121 drawHLine(core, x, i, width, color);
122}
123
124static void drawRectBorder(tic_core* core, s32 x, s32 y, s32 width, s32 height, u8 color)
125{
126 drawHLine(core, x, y, width, color);
127 drawHLine(core, x, y + height - 1, width, color);
128
129 drawVLine(core, x, y, height, color);
130 drawVLine(core, x + width - 1, y, height, color);
131}
132
133#define DRAW_TILE_BODY(X, Y) do {\
134 for(s32 py=sy; py < ey; py++, y++) \
135 { \
136 s32 xx = x; \
137 for(s32 px=sx; px < ex; px++, xx++) \
138 { \
139 u8 color = mapping[tic_tilesheet_gettilepix(tile, (X), (Y))];\
140 if(color != TRANSPARENT_COLOR) setPixelFast(core, xx, y, color); \
141 } \
142 } \
143 } while(0)
144
145#define REVERT(X) (TIC_SPRITESIZE - 1 - (X))
146
147static void drawTile(tic_core* core, tic_tileptr* tile, s32 x, s32 y, u8* colors, s32 count, s32 scale, tic_flip flip, tic_rotate rotate)
148{
149 const tic_vram* vram = &core->memory.ram->vram;
150 u8* mapping = getPalette(&core->memory, colors, count);
151
152 rotate &= 3;
153 u32 orientation = flip & 3;
154
155 if (rotate == tic_90_rotate) orientation ^= 1;
156 else if (rotate == tic_180_rotate) orientation ^= 3;
157 else if (rotate == tic_270_rotate) orientation ^= 2;
158 if (rotate == tic_90_rotate || rotate == tic_270_rotate) orientation |= 2;
159
160 if (scale == 1) {
161 // the most common path
162 s32 sx, sy, ex, ey;
163 sx = core->state.clip.l - x; if (sx < 0) sx = 0;
164 sy = core->state.clip.t - y; if (sy < 0) sy = 0;
165 ex = core->state.clip.r - x; if (ex > TIC_SPRITESIZE) ex = TIC_SPRITESIZE;
166 ey = core->state.clip.b - y; if (ey > TIC_SPRITESIZE) ey = TIC_SPRITESIZE;
167 y += sy;
168 x += sx;
169 switch (orientation) {
170 case 4: DRAW_TILE_BODY(py, px); break;
171 case 6: DRAW_TILE_BODY(REVERT(py), px); break;
172 case 5: DRAW_TILE_BODY(py, REVERT(px)); break;
173 case 7: DRAW_TILE_BODY(REVERT(py), REVERT(px)); break;
174 case 0: DRAW_TILE_BODY(px, py); break;
175 case 2: DRAW_TILE_BODY(px, REVERT(py)); break;
176 case 1: DRAW_TILE_BODY(REVERT(px), py); break;
177 case 3: DRAW_TILE_BODY(REVERT(px), REVERT(py)); break;
178 }
179 return;
180 }
181
182 if (EARLY_CLIP(x, y, TIC_SPRITESIZE * scale, TIC_SPRITESIZE * scale)) return;
183
184 for (s32 py = 0; py < TIC_SPRITESIZE; py++, y += scale)
185 {
186 s32 xx = x;
187 for (s32 px = 0; px < TIC_SPRITESIZE; px++, xx += scale)
188 {
189 s32 ix = orientation & 1 ? TIC_SPRITESIZE - px - 1 : px;
190 s32 iy = orientation & 2 ? TIC_SPRITESIZE - py - 1 : py;
191 if (orientation & 4) {
192 s32 tmp = ix; ix = iy; iy = tmp;
193 }
194 u8 color = mapping[tic_tilesheet_gettilepix(tile, ix, iy)];
195 if (color != TRANSPARENT_COLOR) drawRect(core, xx, y, scale, scale, color);
196 }
197 }
198}
199
200#undef DRAW_TILE_BODY
201#undef REVERT
202
203static void drawSprite(tic_core* core, s32 index, s32 x, s32 y, s32 w, s32 h, u8* colors, s32 count, s32 scale, tic_flip flip, tic_rotate rotate)
204{
205 const tic_vram* vram = &core->memory.ram->vram;
206
207 if (index < 0)
208 return;
209
210 rotate &= 3;
211 flip &= 3;
212
213 tic_tilesheet sheet = getTileSheetFromSegment(&core->memory, core->memory.ram->vram.blit.segment);
214 if (w == 1 && h == 1) {
215 tic_tileptr tile = tic_tilesheet_gettile(&sheet, index, false);
216 drawTile(core, &tile, x, y, colors, count, scale, flip, rotate);
217 }
218 else
219 {
220 s32 step = TIC_SPRITESIZE * scale;
221 s32 cols = sheet.segment->sheet_width;
222
223 const tic_flip vert_horz_flip = tic_horz_flip | tic_vert_flip;
224
225 if (EARLY_CLIP(x, y, w * step, h * step)) return;
226
227 for (s32 i = 0; i < w; i++)
228 {
229 for (s32 j = 0; j < h; j++)
230 {
231 s32 mx = i;
232 s32 my = j;
233
234 if (flip == tic_horz_flip || flip == vert_horz_flip) mx = w - 1 - i;
235 if (flip == tic_vert_flip || flip == vert_horz_flip) my = h - 1 - j;
236
237 if (rotate == tic_180_rotate)
238 {
239 mx = w - 1 - mx;
240 my = h - 1 - my;
241 }
242 else if (rotate == tic_90_rotate)
243 {
244 if (flip == tic_no_flip || flip == vert_horz_flip) my = h - 1 - my;
245 else mx = w - 1 - mx;
246 }
247 else if (rotate == tic_270_rotate)
248 {
249 if (flip == tic_no_flip || flip == vert_horz_flip) mx = w - 1 - mx;
250 else my = h - 1 - my;
251 }
252
253 enum { Cols = TIC_SPRITESHEET_SIZE / TIC_SPRITESIZE };
254
255
256 tic_tileptr tile = tic_tilesheet_gettile(&sheet, index + mx + my * cols, false);
257 if (rotate == 0 || rotate == 2)
258 drawTile(core, &tile, x + i * step, y + j * step, colors, count, scale, flip, rotate);
259 else
260 drawTile(core, &tile, x + j * step, y + i * step, colors, count, scale, flip, rotate);
261 }
262 }
263 }
264}
265
266static void drawMap(tic_core* core, const tic_map* src, s32 x, s32 y, s32 width, s32 height, s32 sx, s32 sy, u8* colors, s32 count, s32 scale, RemapFunc remap, void* data)
267{
268 const s32 size = TIC_SPRITESIZE * scale;
269
270 tic_tilesheet sheet = getTileSheetFromSegment(&core->memory, core->memory.ram->vram.blit.segment);
271
272 for (s32 j = y, jj = sy; j < y + height; j++, jj += size)
273 for (s32 i = x, ii = sx; i < x + width; i++, ii += size)
274 {
275 s32 mi = i;
276 s32 mj = j;
277
278 while (mi < 0) mi += TIC_MAP_WIDTH;
279 while (mj < 0) mj += TIC_MAP_HEIGHT;
280 while (mi >= TIC_MAP_WIDTH) mi -= TIC_MAP_WIDTH;
281 while (mj >= TIC_MAP_HEIGHT) mj -= TIC_MAP_HEIGHT;
282
283 s32 index = mi + mj * TIC_MAP_WIDTH;
284 RemapResult retile = { *(src->data + index), tic_no_flip, tic_no_rotate };
285
286 if (remap)
287 remap(data, mi, mj, &retile);
288
289 tic_tileptr tile = tic_tilesheet_gettile(&sheet, retile.index, true);
290 drawTile(core, &tile, ii, jj, colors, count, scale, retile.flip, retile.rotate);
291 }
292}
293
294static s32 drawChar(tic_core* core, tic_tileptr* font_char, s32 x, s32 y, s32 scale, bool fixed, u8* mapping)
295{
296 const tic_vram* vram = &core->memory.ram->vram;
297
298 enum { Size = TIC_SPRITESIZE };
299
300 s32 j = 0, start = 0, end = Size;
301
302 if (!fixed) {
303 for (s32 i = 0; i < Size; i++) {
304 for (j = 0; j < Size; j++)
305 if (mapping[tic_tilesheet_gettilepix(font_char, i, j)] != TRANSPARENT_COLOR) break;
306 if (j < Size) break; else start++;
307 }
308 for (s32 i = Size - 1; i >= start; i--) {
309 for (j = 0; j < Size; j++)
310 if (mapping[tic_tilesheet_gettilepix(font_char, i, j)] != TRANSPARENT_COLOR) break;
311 if (j < Size) break; else end--;
312 }
313 }
314 s32 width = end - start;
315
316 if (EARLY_CLIP(x, y, Size * scale, Size * scale)) return width;
317
318 s32 colStart = start, colStep = 1, rowStart = 0, rowStep = 1;
319
320 for (s32 i = 0, col = colStart, xs = x; i < width; i++, col += colStep, xs += scale)
321 {
322 for (s32 j = 0, row = rowStart, ys = y; j < Size; j++, row += rowStep, ys += scale)
323 {
324 u8 color = tic_tilesheet_gettilepix(font_char, col, row);
325 if (mapping[color] != TRANSPARENT_COLOR)
326 drawRect(core, xs, ys, scale, scale, mapping[color]);
327 }
328 }
329 return width;
330}
331
332static s32 drawText(tic_core* core, tic_tilesheet* font_face, const char* text, s32 x, s32 y, s32 width, s32 height, bool fixed, u8* mapping, s32 scale, bool alt)
333{
334 s32 pos = x;
335 s32 MAX = x;
336 char sym = 0;
337
338 while ((sym = *text++))
339 {
340 if (sym == '\n')
341 {
342 if (pos > MAX)
343 MAX = pos;
344
345 pos = x;
346 y += height * scale;
347 }
348 else {
349 tic_tileptr font_char = tic_tilesheet_gettile(font_face, alt * TIC_FONT_CHARS + sym, true);
350 s32 size = drawChar(core, &font_char, pos, y, scale, fixed, mapping);
351 pos += ((!fixed && size) ? size + 1 : width) * scale;
352 }
353 }
354
355 return pos > MAX ? pos - x : MAX - x;
356}
357
358void tic_api_clip(tic_mem* memory, s32 x, s32 y, s32 width, s32 height)
359{
360 tic_core* core = (tic_core*)memory;
361 tic_vram* vram = &memory->ram->vram;
362
363 core->state.clip.l = x;
364 core->state.clip.t = y;
365 core->state.clip.r = x + width;
366 core->state.clip.b = y + height;
367
368 if (core->state.clip.l < 0) core->state.clip.l = 0;
369 if (core->state.clip.t < 0) core->state.clip.t = 0;
370 if (core->state.clip.r > TIC80_WIDTH) core->state.clip.r = TIC80_WIDTH;
371 if (core->state.clip.b > TIC80_HEIGHT) core->state.clip.b = TIC80_HEIGHT;
372}
373
374void tic_api_rect(tic_mem* memory, s32 x, s32 y, s32 width, s32 height, u8 color)
375{
376 tic_core* core = (tic_core*)memory;
377
378 drawRect(core, x, y, width, height, mapColor(memory, color));
379}
380
381static double ZBuffer[TIC80_WIDTH * TIC80_HEIGHT];
382
383void tic_api_cls(tic_mem* tic, u8 color)
384{
385 tic_core* core = (tic_core*)tic;
386 tic_vram* vram = &tic->ram->vram;
387
388 static const struct ClipRect EmptyClip = { 0, 0, TIC80_WIDTH, TIC80_HEIGHT };
389
390 if (MEMCMP(core->state.clip, EmptyClip))
391 {
392 memset(&vram->screen, (color & 0xf) | (color << TIC_PALETTE_BPP), sizeof(tic_screen));
393 ZEROMEM(ZBuffer);
394 }
395 else
396 {
397 for(s32 y = core->state.clip.t, start = y * TIC80_WIDTH; y < core->state.clip.b; ++y, start += TIC80_WIDTH)
398 for(s32 x = core->state.clip.l, pixel = start + x; x < core->state.clip.r; ++x, ++pixel)
399 {
400 tic_api_poke4(tic, pixel, color);
401 ZBuffer[pixel] = 0;
402 }
403 }
404}
405
406s32 tic_api_font(tic_mem* memory, const char* text, s32 x, s32 y, u8* trans_colors, u8 trans_count, s32 w, s32 h, bool fixed, s32 scale, bool alt)
407{
408 u8* mapping = getPalette(memory, trans_colors, trans_count);
409
410 // Compatibility : flip top and bottom of the spritesheet
411 // to preserve tic_api_font's default target
412 u8 segment = memory->ram->vram.blit.segment >> 1;
413 u8 flipmask = 1; while (segment >>= 1) flipmask <<= 1;
414
415 tic_tilesheet font_face = getTileSheetFromSegment(memory, memory->ram->vram.blit.segment ^ flipmask);
416 return drawText((tic_core*)memory, &font_face, text, x, y, w, h, fixed, mapping, scale, alt);
417}
418
419s32 tic_api_print(tic_mem* memory, const char* text, s32 x, s32 y, u8 color, bool fixed, s32 scale, bool alt)
420{
421 u8 mapping[] = { 255, color };
422 tic_tilesheet font_face = getTileSheetFromSegment(memory, 1);
423
424 const tic_font_data* font = alt ? &memory->ram->font.alt : &memory->ram->font.regular;
425 s32 width = font->width;
426
427 // Compatibility : print uses reduced width for non-fixed space
428 if (!fixed) width -= 2;
429 return drawText((tic_core*)memory, &font_face, text, x, y, width, font->height, fixed, mapping, scale, alt);
430}
431
432void tic_api_spr(tic_mem* memory, s32 index, s32 x, s32 y, s32 w, s32 h, u8* trans_colors, u8 trans_count, s32 scale, tic_flip flip, tic_rotate rotate)
433{
434 drawSprite((tic_core*)memory, index, x, y, w, h, trans_colors, trans_count, scale, flip, rotate);
435}
436
437static inline u8* getFlag(tic_mem* memory, s32 index, u8 flag)
438{
439 static u8 stub = 0;
440 if (index >= TIC_FLAGS || flag >= BITS_IN_BYTE)
441 return &stub;
442
443 return memory->ram->flags.data + index;
444}
445
446bool tic_api_fget(tic_mem* memory, s32 index, u8 flag)
447{
448 return *getFlag(memory, index, flag) & (1 << flag);
449}
450
451void tic_api_fset(tic_mem* memory, s32 index, u8 flag, bool value)
452{
453 if (value)
454 *getFlag(memory, index, flag) |= (1 << flag);
455 else
456 *getFlag(memory, index, flag) &= ~(1 << flag);
457}
458
459u8 tic_api_pix(tic_mem* memory, s32 x, s32 y, u8 color, bool get)
460{
461 tic_core* core = (tic_core*)memory;
462
463 if (get) return getPixel(core, x, y);
464
465 setPixel(core, x, y, mapColor(memory, color));
466 return 0;
467}
468
469void tic_api_rectb(tic_mem* memory, s32 x, s32 y, s32 width, s32 height, u8 color)
470{
471 tic_core* core = (tic_core*)memory;
472
473 drawRectBorder(core, x, y, width, height, mapColor(memory, color));
474}
475
476static struct
477{
478 s16 Left[TIC80_HEIGHT];
479 s16 Right[TIC80_HEIGHT];
480} SidesBuffer;
481
482static void initSidesBuffer()
483{
484 for (s32 i = 0; i < COUNT_OF(SidesBuffer.Left); i++)
485 SidesBuffer.Left[i] = TIC80_WIDTH, SidesBuffer.Right[i] = -1;
486}
487
488static void setSidePixel(s32 x, s32 y)
489{
490 if (y >= 0 && y < TIC80_HEIGHT)
491 {
492 if (x < SidesBuffer.Left[y]) SidesBuffer.Left[y] = x;
493 if (x > SidesBuffer.Right[y]) SidesBuffer.Right[y] = x;
494 }
495}
496
497static void drawEllipse(tic_mem* memory, s32 x0, s32 y0, s32 x1, s32 y1, u8 color, PixelFunc pix)
498{
499 s64 a = abs(x1 - x0), b = abs(y1 - y0), b1 = b & 1; /* values of diameter */
500 s64 dx = 4 * (1 - a) * b * b, dy = 4 * (b1 + 1) * a * a; /* error increment */
501 s64 err = dx + dy + b1 * a * a, e2; /* error of 1.step */
502
503 if (x0 > x1) { x0 = x1; x1 += a; } /* if called with swapped pos32s */
504 if (y0 > y1) y0 = y1; /* .. exchange them */
505 y0 += (b + 1) / 2; y1 = y0 - b1; /* starting pixel */
506 a *= 8 * a; b1 = 8 * b * b;
507
508 do
509 {
510 pix(memory, x1, y0, color); /* I. Quadrant */
511 pix(memory, x0, y0, color); /* II. Quadrant */
512 pix(memory, x0, y1, color); /* III. Quadrant */
513 pix(memory, x1, y1, color); /* IV. Quadrant */
514 e2 = 2 * err;
515 if (e2 <= dy) { y0++; y1--; err += dy += a; } /* y step */
516 if (e2 >= dx || 2 * err > dy) { x0++; x1--; err += dx += b1; } /* x step */
517 } while (x0 <= x1);
518
519 while (y0-y1 < b)
520 { /* too early stop of flat ellipses a=1 */
521 pix(memory, x0 - 1, y0, color); /* -> finish tip of ellipse */
522 pix(memory, x1 + 1, y0++, color);
523 pix(memory, x0 - 1, y1, color);
524 pix(memory, x1 + 1, y1--, color);
525 }
526}
527
528static void setElliPixel(tic_mem* tic, s32 x, s32 y, u8 color)
529{
530 setPixel((tic_core*)tic, x, y, color);
531}
532
533static void setElliSide(tic_mem* tic, s32 x, s32 y, u8 color)
534{
535 setSidePixel(x, y);
536}
537
538static void drawSidesBuffer(tic_mem* memory, s32 y0, s32 y1, u8 color)
539{
540 tic_vram* vram = &memory->ram->vram;
541
542 tic_core* core = (tic_core*)memory;
543 s32 yt = MAX(core->state.clip.t, y0);
544 s32 yb = MIN(core->state.clip.b, y1 + 1);
545 u8 final_color = mapColor(&core->memory, color);
546 for (s32 y = yt; y < yb; y++)
547 {
548 s32 xl = MAX(SidesBuffer.Left[y], core->state.clip.l);
549 s32 xr = MIN(SidesBuffer.Right[y] + 1, core->state.clip.r);
550 s32 start = y * TIC80_WIDTH;
551
552 for(s32 i = start + xl, end = start + xr; i < end; ++i)
553 tic_api_poke4(memory, i, color);
554 }
555}
556
557void tic_api_circ(tic_mem* memory, s32 x, s32 y, s32 r, u8 color)
558{
559 initSidesBuffer();
560 drawEllipse(memory, x - r, y - r, x + r, y + r, 0, setElliSide);
561 drawSidesBuffer(memory, y - r, y + r + 1, color);
562}
563
564void tic_api_circb(tic_mem* memory, s32 x, s32 y, s32 r, u8 color)
565{
566 drawEllipse(memory, x - r, y - r, x + r, y + r, mapColor(memory, color), setElliPixel);
567}
568
569void tic_api_elli(tic_mem* memory, s32 x, s32 y, s32 a, s32 b, u8 color)
570{
571 initSidesBuffer();
572 drawEllipse(memory, x - a, y - b, x + a, y + b, 0, setElliSide);
573 drawSidesBuffer(memory, y - b, y + b + 1, color);
574}
575
576void tic_api_ellib(tic_mem* memory, s32 x, s32 y, s32 a, s32 b, u8 color)
577{
578 drawEllipse(memory, x - a, y - b, x + a, y + b, mapColor(memory, color), setElliPixel);
579}
580
581static inline float initLine(float *x0, float *x1, float *y0, float *y1)
582{
583 if (*y0 > *y1)
584 {
585 SWAP(*x0, *x1, float);
586 SWAP(*y0, *y1, float);
587 }
588
589 float t = (*x1 - *x0) / (*y1 - *y0);
590
591 if(*y0 < 0) *x0 -= *y0 * t, *y0 = 0;
592 if(*y1 > TIC80_WIDTH) *x1 += (TIC80_WIDTH - *y0) * t, *y1 = TIC80_WIDTH;
593
594 return t;
595}
596
597static void drawLine(tic_mem* tic, float x0, float y0, float x1, float y1, u8 color)
598{
599 if(fabs(x0 - x1) < fabs(y0 - y1))
600 for (float t = initLine(&x0, &x1, &y0, &y1); y0 < y1; y0++, x0 += t)
601 setPixel((tic_core*)tic, x0, y0, color);
602 else
603 for (float t = initLine(&y0, &y1, &x0, &x1); x0 < x1; x0++, y0 += t)
604 setPixel((tic_core*)tic, x0, y0, color);
605
606 setPixel((tic_core*)tic, x1, y1, color);
607}
608
609typedef union
610{
611 struct
612 {
613 double x, y;
614 };
615
616 double d[2];
617} Vec2;
618
619typedef union
620{
621 struct
622 {
623 double x, y, z;
624 };
625
626 double d[3];
627} Vec3;
628
629typedef struct
630{
631 void* data;
632 const Vec2* v[3];
633 Vec3 w;
634} ShaderAttr;
635
636typedef tic_color(*PixelShader)(const ShaderAttr* a, s32 pixel);
637
638static inline double edgeFn(const Vec2* a, const Vec2* b, const Vec2* c)
639{
640 return (b->x - a->x) * (c->y - a->y) - (b->y - a->y) * (c->x - a->x);
641}
642
643static void drawTri(tic_mem* tic, const Vec2* v0, const Vec2* v1, const Vec2* v2, PixelShader shader, void* data)
644{
645 ShaderAttr a = {data, v0, v1, v2};
646
647 tic_core* core = (tic_core*)tic;
648 const struct ClipRect* clip = &core->state.clip;
649
650 tic_point min = {floor(MIN3(a.v[0]->x, a.v[1]->x, a.v[2]->x)), floor(MIN3(a.v[0]->y, a.v[1]->y, a.v[2]->y))};
651 tic_point max = {ceil(MAX3(a.v[0]->x, a.v[1]->x, a.v[2]->x)), ceil(MAX3(a.v[0]->y, a.v[1]->y, a.v[2]->y))};
652
653 min.x = MAX(min.x, clip->l);
654 min.y = MAX(min.y, clip->t);
655 max.x = MIN(max.x, clip->r);
656 max.y = MIN(max.y, clip->b);
657
658 if(min.x >= max.x || min.y >= max.y) return;
659
660 double area = edgeFn(a.v[0], a.v[1], a.v[2]);
661 if((s32)floor(area) == 0) return;
662 if(area < 0.0)
663 {
664 SWAP(a.v[1], a.v[2], const Vec2*);
665 area = -area;
666 }
667
668 Vec2 d[3];
669 Vec3 s;
670
671 for(s32 i = 0; i != COUNT_OF(s.d); ++i)
672 {
673 // pixel center
674 const double Center = 0.5 - FLT_EPSILON;
675 Vec2 p = {min.x + Center, min.y + Center};
676
677 s32 c = (i + 1) % 3, n = (i + 2) % 3;
678
679 d[i].x = (a.v[c]->y - a.v[n]->y) / area;
680 d[i].y = (a.v[n]->x - a.v[c]->x) / area;
681 s.d[i] = edgeFn(a.v[c], a.v[n], &p) / area;
682 }
683
684 for(s32 y = min.y, start = min.y * TIC80_WIDTH + min.x; y < max.y; ++y, start += TIC80_WIDTH)
685 {
686 for(s32 i = 0; i != COUNT_OF(a.w.d); ++i)
687 a.w.d[i] = s.d[i];
688
689 for(s32 x = min.x, pixel = start; x < max.x; ++x, ++pixel)
690 {
691 if(a.w.x > -DBL_EPSILON && a.w.y > -DBL_EPSILON && a.w.z > -DBL_EPSILON)
692 {
693 u8 color = shader(&a, pixel);
694 if(color != TRANSPARENT_COLOR)
695 tic_api_poke4(tic, pixel, color);
696 }
697
698 for(s32 i = 0; i != COUNT_OF(a.w.d); ++i)
699 a.w.d[i] += d[i].x;
700 }
701
702 for(s32 i = 0; i != COUNT_OF(s.d); ++i)
703 s.d[i] += d[i].y;
704 }
705}
706
707static tic_color triColorShader(const ShaderAttr* a, s32 pixel){return *(u8*)a->data;}
708
709void tic_api_tri(tic_mem* tic, float x1, float y1, float x2, float y2, float x3, float y3, u8 color)
710{
711 color = mapColor(tic, color);
712 drawTri(tic,
713 &(Vec2){x1, y1},
714 &(Vec2){x2, y2},
715 &(Vec2){x3, y3},
716 triColorShader, &color);
717}
718
719void tic_api_trib(tic_mem* tic, float x1, float y1, float x2, float y2, float x3, float y3, u8 color)
720{
721 tic_core* core = (tic_core*)tic;
722
723 u8 finalColor = mapColor(tic, color);
724
725 drawLine(tic, x1, y1, x2, y2, finalColor);
726 drawLine(tic, x2, y2, x3, y3, finalColor);
727 drawLine(tic, x3, y3, x1, y1, finalColor);
728}
729
730typedef struct
731{
732 Vec2 _;
733 Vec3 d;
734}TexVert;
735
736typedef struct
737{
738 tic_tilesheet sheet;
739 u8* mapping;
740 const u8* map;
741 const tic_vram* vram;
742 bool depth;
743} TexData;
744
745static inline bool shaderStart(const ShaderAttr* a, Vec3* vars, s32 pixel)
746{
747 TexData* data = a->data;
748
749 if(data->depth)
750 {
751 vars->z = 0;
752 for(s32 i = 0; i != COUNT_OF(a->v); ++i)
753 {
754 const TexVert* t = (TexVert*)a->v[i];
755 vars->z += a->w.d[i] * t->d.z;
756 }
757
758 if(ZBuffer[pixel] < vars->z);
759 else return false;
760 }
761
762 vars->x = vars->y = 0;
763 for(s32 i = 0; i != COUNT_OF(a->v); ++i)
764 {
765 const TexVert* t = (TexVert*)a->v[i];
766 vars->x += a->w.d[i] * t->d.x;
767 vars->y += a->w.d[i] * t->d.y;
768 }
769
770 if(data->depth)
771 vars->x /= vars->z,
772 vars->y /= vars->z;
773
774 return true;
775}
776
777static inline tic_color shaderEnd(const ShaderAttr* a, const Vec3* vars, s32 pixel, tic_color color)
778{
779 TexData* data = a->data;
780
781 if(data->depth && color != TRANSPARENT_COLOR)
782 ZBuffer[pixel] = vars->z;
783
784 return color;
785}
786
787static tic_color triTexMapShader(const ShaderAttr* a, s32 pixel)
788{
789 TexData* data = a->data;
790
791 Vec3 vars;
792 if(!shaderStart(a, &vars, pixel))
793 return TRANSPARENT_COLOR;
794
795 enum { MapWidth = TIC_MAP_WIDTH * TIC_SPRITESIZE, MapHeight = TIC_MAP_HEIGHT * TIC_SPRITESIZE,
796 WMask = TIC_SPRITESIZE - 1, HMask = TIC_SPRITESIZE - 1 };
797
798 s32 iu = tic_modulo(vars.x, MapWidth);
799 s32 iv = tic_modulo(vars.y, MapHeight);
800
801 u8 idx = data->map[(iv >> 3) * TIC_MAP_WIDTH + (iu >> 3)];
802 tic_tileptr tile = tic_tilesheet_gettile(&data->sheet, idx, true);
803
804 return shaderEnd(a, &vars, pixel, data->mapping[tic_tilesheet_gettilepix(&tile, iu & WMask, iv & HMask)]);
805}
806
807static tic_color triTexTileShader(const ShaderAttr* a, s32 pixel)
808{
809 TexData* data = a->data;
810
811 Vec3 vars;
812 if(!shaderStart(a, &vars, pixel))
813 return TRANSPARENT_COLOR;
814
815 enum { WMask = TIC_SPRITESHEET_SIZE - 1, HMask = TIC_SPRITESHEET_SIZE * TIC_SPRITE_BANKS - 1 };
816
817 return shaderEnd(a, &vars, pixel, data->mapping[tic_tilesheet_getpix(&data->sheet, (s32)vars.x & WMask, (s32)vars.y & HMask)]);
818}
819
820static tic_color triTexVbankShader(const ShaderAttr* a, s32 pixel)
821{
822 TexData* data = a->data;
823
824 Vec3 vars;
825 if(!shaderStart(a, &vars, pixel))
826 return TRANSPARENT_COLOR;
827
828 s32 iu = tic_modulo(vars.x, TIC80_WIDTH);
829 s32 iv = tic_modulo(vars.y, TIC80_HEIGHT);
830
831 return shaderEnd(a, &vars, pixel, data->mapping[tic_tool_peek4(data->vram->data, iv * TIC80_WIDTH + iu)]);
832}
833
834void tic_api_ttri(tic_mem* tic,
835 float x1, float y1,
836 float x2, float y2,
837 float x3, float y3,
838 float u1, float v1,
839 float u2, float v2,
840 float u3, float v3,
841 tic_texture_src texsrc, u8* colors, s32 count,
842 float z1, float z2, float z3, bool depth)
843{
844 TexData texData =
845 {
846 .sheet = getTileSheetFromSegment(tic, tic->ram->vram.blit.segment),
847 .mapping = getPalette(tic, colors, count),
848 .map = tic->ram->map.data,
849 .vram = &((tic_core*)tic)->state.vbank.mem,
850 .depth = depth,
851 };
852
853 TexVert t[] =
854 {
855 {x1, y1, u1, v1, z1},
856 {x2, y2, u2, v2, z2},
857 {x3, y3, u3, v3, z3},
858 };
859
860 if(depth)
861 for(s32 i = 0; i != COUNT_OF(t); ++i)
862 t[i].d.x /= t[i].d.z,
863 t[i].d.y /= t[i].d.z,
864 t[i].d.z = 1.0 / t[i].d.z;
865
866 static const PixelShader Shaders[] =
867 {
868 [tic_tiles_texture] = triTexTileShader,
869 [tic_map_texture] = triTexMapShader,
870 [tic_vbank_texture] = triTexVbankShader,
871 };
872
873 if(texsrc >= 0 && texsrc < COUNT_OF(Shaders))
874 drawTri(tic,
875 (const Vec2*)&t[0],
876 (const Vec2*)&t[1],
877 (const Vec2*)&t[2],
878 Shaders[texsrc], &texData);
879}
880
881void tic_api_map(tic_mem* memory, s32 x, s32 y, s32 width, s32 height, s32 sx, s32 sy, u8* colors, u8 count, s32 scale, RemapFunc remap, void* data)
882{
883 drawMap((tic_core*)memory, &memory->ram->map, x, y, width, height, sx, sy, colors, count, scale, remap, data);
884}
885
886void tic_api_mset(tic_mem* memory, s32 x, s32 y, u8 value)
887{
888 if (x < 0 || x >= TIC_MAP_WIDTH || y < 0 || y >= TIC_MAP_HEIGHT) return;
889
890 tic_map* src = &memory->ram->map;
891 *(src->data + y * TIC_MAP_WIDTH + x) = value;
892}
893
894u8 tic_api_mget(tic_mem* memory, s32 x, s32 y)
895{
896 if (x < 0 || x >= TIC_MAP_WIDTH || y < 0 || y >= TIC_MAP_HEIGHT) return 0;
897
898 const tic_map* src = &memory->ram->map;
899 return *(src->data + y * TIC_MAP_WIDTH + x);
900}
901
902void tic_api_line(tic_mem* memory, float x0, float y0, float x1, float y1, u8 color)
903{
904 drawLine(memory, x0, y0, x1, y1, mapColor(memory, color));
905}
906
907#if defined(BUILD_DEPRECATED)
908#include "draw_dep.c"
909#endif
910