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 | |
34 | typedef void(*PixelFunc)(tic_mem* memory, s32 x, s32 y, u8 color); |
35 | |
36 | static 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 | |
50 | static 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 | |
58 | static inline u8 mapColor(tic_mem* tic, u8 color) |
59 | { |
60 | return tic_tool_peek4(tic->ram->vram.mapping, color & 0xf); |
61 | } |
62 | |
63 | static 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 | |
72 | static 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 | |
78 | static 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 | |
91 | static 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 | |
105 | static 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 | |
118 | static 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 | |
124 | static 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 | |
147 | static 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 | |
203 | static 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 | |
266 | static 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 | |
294 | static 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 | |
332 | static 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 | |
358 | void 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 | |
374 | void 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 | |
381 | static double ZBuffer[TIC80_WIDTH * TIC80_HEIGHT]; |
382 | |
383 | void 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 | |
406 | s32 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 | |
419 | s32 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 | |
432 | void 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 | |
437 | static 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 | |
446 | bool tic_api_fget(tic_mem* memory, s32 index, u8 flag) |
447 | { |
448 | return *getFlag(memory, index, flag) & (1 << flag); |
449 | } |
450 | |
451 | void 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 | |
459 | u8 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 | |
469 | void 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 | |
476 | static struct |
477 | { |
478 | s16 Left[TIC80_HEIGHT]; |
479 | s16 Right[TIC80_HEIGHT]; |
480 | } SidesBuffer; |
481 | |
482 | static 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 | |
488 | static 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 | |
497 | static 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 | |
528 | static void setElliPixel(tic_mem* tic, s32 x, s32 y, u8 color) |
529 | { |
530 | setPixel((tic_core*)tic, x, y, color); |
531 | } |
532 | |
533 | static void setElliSide(tic_mem* tic, s32 x, s32 y, u8 color) |
534 | { |
535 | setSidePixel(x, y); |
536 | } |
537 | |
538 | static 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 | |
557 | void 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 | |
564 | void 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 | |
569 | void 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 | |
576 | void 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 | |
581 | static 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 | |
597 | static 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 | |
609 | typedef union |
610 | { |
611 | struct |
612 | { |
613 | double x, y; |
614 | }; |
615 | |
616 | double d[2]; |
617 | } Vec2; |
618 | |
619 | typedef union |
620 | { |
621 | struct |
622 | { |
623 | double x, y, z; |
624 | }; |
625 | |
626 | double d[3]; |
627 | } Vec3; |
628 | |
629 | typedef struct |
630 | { |
631 | void* data; |
632 | const Vec2* v[3]; |
633 | Vec3 w; |
634 | } ShaderAttr; |
635 | |
636 | typedef tic_color(*PixelShader)(const ShaderAttr* a, s32 pixel); |
637 | |
638 | static 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 | |
643 | static 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 | |
707 | static tic_color triColorShader(const ShaderAttr* a, s32 pixel){return *(u8*)a->data;} |
708 | |
709 | void 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 | |
719 | void 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 | |
730 | typedef struct |
731 | { |
732 | Vec2 _; |
733 | Vec3 d; |
734 | }TexVert; |
735 | |
736 | typedef struct |
737 | { |
738 | tic_tilesheet sheet; |
739 | u8* mapping; |
740 | const u8* map; |
741 | const tic_vram* vram; |
742 | bool depth; |
743 | } TexData; |
744 | |
745 | static 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 | |
777 | static 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 | |
787 | static 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 | |
807 | static 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 | |
820 | static 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 | |
834 | void 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 | |
881 | void 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 | |
886 | void 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 | |
894 | u8 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 | |
902 | void 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 | |