/* SPDX-License-Identifier: MIT */ /* * kontile.c * * Copyright (c) 2026 KonAki * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include "kontile.h" #include static int kon_floorDiv_(int a, int b) { int q = a / b; return (a % b != 0 && (a < 0) != (b < 0)) ? q - 1 : q; } uint16_t kon_tilemapAt(const kon_tilemap_t *map, int column, int row) { if (!map || !map->tiles) return 0; if (column < 0 || row < 0 || column >= map->columns || row >= map->rows) return 0; return map->tiles[(size_t)row * (size_t)map->columns + (size_t)column]; } void kon_tilemapSet(kon_tilemap_t *map, int column, int row, uint16_t tile) { if (!map || !map->tiles) return; if (column < 0 || row < 0 || column >= map->columns || row >= map->rows) return; map->tiles[(size_t)row * (size_t)map->columns + (size_t)column] = tile; } int kon_tilemapPixelToTile(const kon_tilemap_t *map, int x, int y, int *column, int *row) { if (!map || map->tileWidth <= 0 || map->tileHeight <= 0) return 0; int c = kon_floorDiv_(x, map->tileWidth); int r = kon_floorDiv_(y, map->tileHeight); if (column) *column = c; if (row) *row = r; return c >= 0 && r >= 0 && c < map->columns && r < map->rows; } int kon_tilemapBoxHits(const kon_tilemap_t *map, int x, int y, int width, int height, const uint8_t *solid, int solidCount) { if (!map || !solid || solidCount <= 0 || map->tileWidth <= 0 || map->tileHeight <= 0 || width <= 0 || height <= 0) return 0; int firstColumn = kon_floorDiv_(x, map->tileWidth); int firstRow = kon_floorDiv_(y, map->tileHeight); int lastColumn = kon_floorDiv_(x + width - 1, map->tileWidth); int lastRow = kon_floorDiv_(y + height - 1, map->tileHeight); for (int row = firstRow; row <= lastRow; row++) { for (int column = firstColumn; column <= lastColumn; column++) { int tile = kon_tilemapAt(map, column, row); if (tile < solidCount && solid[tile]) return 1; } } return 0; } void kon_drawTilemap(kon_framebuffer_t *fb, const kon_tilemap_t *map, int cameraX, int cameraY) { if (!fb || !map || !map->sheet || !map->tiles) return; if (map->tileWidth <= 0 || map->tileHeight <= 0) return; int sheetColumns = map->sheetColumns > 0 ? map->sheetColumns : map->sheet->width / map->tileWidth; if (sheetColumns <= 0) return; int firstColumn = kon_floorDiv_(cameraX, map->tileWidth); int firstRow = kon_floorDiv_(cameraY, map->tileHeight); int lastColumn = kon_floorDiv_(cameraX + fb->width - 1, map->tileWidth); int lastRow = kon_floorDiv_(cameraY + fb->height - 1, map->tileHeight); if (firstColumn < 0) firstColumn = 0; if (firstRow < 0) firstRow = 0; if (lastColumn > map->columns - 1) lastColumn = map->columns - 1; if (lastRow > map->rows - 1) lastRow = map->rows - 1; for (int row = firstRow; row <= lastRow; row++) { for (int column = firstColumn; column <= lastColumn; column++) { int tile = map->tiles[(size_t)row * (size_t)map->columns + (size_t)column]; if (tile == 0) continue; tile--; kon_rect_t src = KON_RECT((tile % sheetColumns) * map->tileWidth, (tile / sheetColumns) * map->tileHeight, map->tileWidth, map->tileHeight); kon_drawImageRegion(fb, column * map->tileWidth - cameraX, row * map->tileHeight - cameraY, map->sheet, src); } } } /*** moving ***/ static int kon_tileSolid_(const kon_tilemap_t *map, int column, int row, const uint8_t *solid, int solidCount) { uint16_t id = kon_tilemapAt(map, column, row); return id < solidCount && solid[id]; } /* tiles a box covers, an edge exactly on a tile border does not touch the next tile */ static void kon_tileSpan_(const kon_tilemap_t *map, kon_box2_t box, int *c0, int *r0, int *c1, int *r1) { *c0 = (int)floorf(box.x / (float)map->tileWidth); *r0 = (int)floorf(box.y / (float)map->tileHeight); *c1 = (int)ceilf((box.x + box.w) / (float)map->tileWidth) - 1; *r1 = (int)ceilf((box.y + box.h) / (float)map->tileHeight) - 1; } /* moves along x or y by at most one tile at a time. returns 1 if it ran into a tile */ static int kon_tileStep_(const kon_tilemap_t *map, kon_box2_t *box, int vertical, float amount, const uint8_t *solid, int solidCount) { float tile = (float)(vertical ? map->tileHeight : map->tileWidth); int c0, r0, c1, r1, c, r; while (amount != 0.0f) { float step = amount > tile ? tile : (amount < -tile ? -tile : amount); kon_box2_t next = *box; if (vertical) next.y += step; else next.x += step; amount -= step; kon_tileSpan_(map, next, &c0, &r0, &c1, &r1); /* the line of tiles the box is moving into */ { int line = vertical ? (step > 0.0f ? r1 : r0) : (step > 0.0f ? c1 : c0); int hit = 0, b0, br0, b1, br1; if (vertical) { for (c = c0; c <= c1 && !hit; c++) hit = kon_tileSolid_(map, c, line, solid, solidCount); } else { for (r = r0; r <= r1 && !hit; r++) hit = kon_tileSolid_(map, line, r, solid, solidCount); } /* a line the box was already in means it started inside a tile, let it move */ kon_tileSpan_(map, *box, &b0, &br0, &b1, &br1); if (hit && (vertical ? (line >= br0 && line <= br1) : (line >= b0 && line <= b1))) hit = 0; if (hit) { if (vertical) next.y = step > 0.0f ? (float)(line * map->tileHeight) - next.h : (float)((line + 1) * map->tileHeight); else next.x = step > 0.0f ? (float)(line * map->tileWidth) - next.w : (float)((line + 1) * map->tileWidth); *box = next; return 1; } } *box = next; } return 0; } int kon_tilemapMove(const kon_tilemap_t *map, kon_box2_t *box, float dx, float dy, const uint8_t *solid, int solidCount) { int flags = 0; if (!map || !box || !solid || solidCount <= 0 || map->tileWidth <= 0 || map->tileHeight <= 0 || box->w <= 0.0f || box->h <= 0.0f) return 0; if (kon_tileStep_(map, box, 0, dx, solid, solidCount)) flags |= dx > 0.0f ? KON_HIT_RIGHT : KON_HIT_LEFT; if (kon_tileStep_(map, box, 1, dy, solid, solidCount)) flags |= dy > 0.0f ? KON_HIT_DOWN : KON_HIT_UP; return flags; }