| 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 | |