#include "map.h" #include "../core/logger.h" #include "../memory/memory.h" #include "../math/vec2i.h" #include #include #include BzTileMap BZ_TILEMAP_INVALID = {.isValid = false}; void bzTileLayerSkipRender(BzTileMap *map, BzTileLayer *layer) { BZ_UNUSED(map); BZ_UNUSED(layer); } void bzTileObjectGroupSkipRender(BzTileMap *map, BzTileObjectGroup *objectGroup) { BZ_UNUSED(map); BZ_UNUSED(objectGroup); } BzTileLayerRenderFunc BZ_TILE_LAYER_SKIP_RENDER = bzTileLayerSkipRender; BzTileObjectGroupRenderFunc BZ_TILE_OBJECTS_SKIP_RENDER = bzTileObjectGroupSkipRender; bool bzTileLayerClear(BzTileMap *map, BzTileLayer *layer) { BZ_UNUSED(map); BZ_UNUSED(layer); return true; } bool bzTileObjectsClear(BzTileMap *map, BzTileObjectGroup *objectGroup) { BZ_UNUSED(map); BZ_UNUSED(objectGroup); return true; } BzTileLayerFunc BZ_TILE_LAYER_CLEAR = bzTileLayerClear; BzTileObjectsFunc BZ_TILE_OBJECTS_CLEAR = bzTileObjectsClear; BzTile bzTileLayerGetTile(BzTileLayer *layer, i32 x, i32 y) { return layer->data[layer->width * y + x]; } void bzTileLayerSetTile(BzTileLayer *layer, BzTile tile, i32 x, i32 y, i32 w, i32 h) { for (i32 yIdx = y; yIdx < y + h; yIdx++) { for (i32 xIdx = x; xIdx < x + w; xIdx++) { layer->data[yIdx * layer->width + xIdx] = tile; } } } BzTileset *bzTileLayerGetTileset(BzTileMap *map, BzTileLayer *layer) { i32 idx = layer->tilesetIdx; if (idx < 0 || idx >= map->tilesetCount) return NULL; return &map->tilesets[idx]; } BzTileset *bzTileObjectGroupGetTileset(BzTileMap *map, BzTileObjectGroup *objectGroup) { i32 idx = objectGroup->tilesetIdx; if (idx < 0 || idx >= map->tilesetCount) return NULL; return &map->tilesets[idx]; } static void handleTileLayer(BzTileLayer *layer, cute_tiled_layer_t *cuteLayer) { layer->id = cuteLayer->id; layer->dataCount = cuteLayer->data_count; layer->data = NULL; if (layer->dataCount > 0) { layer->data = bzAlloc(layer->dataCount * sizeof(*layer->data)); layer->minData = layer->maxData = (BzTile) cuteLayer->data[0]; for (i32 i = 0; i < layer->dataCount; i++) { layer->data[i] = (BzTile) cuteLayer->data[i]; if (layer->data[i] < layer->minData) layer->minData = layer->data[i]; else if (layer->data[i] > layer->maxData) layer->maxData = layer->data[i]; } } layer->width = cuteLayer->width; layer->height = cuteLayer->height; layer->offsetX = cuteLayer->offsetx; layer->offsetY = cuteLayer->offsety; layer->opacity = cuteLayer->opacity; } // tileset.c BzTileShape bzCuteObjectToTileShape(cute_tiled_object_t *object); static void handleTileObjectLayer(BzTileObjectGroup *layer, cute_tiled_layer_t *cuteLayer, BzStringHashFunc hashFunc) { // Count objects layer->objectCount = 0; cute_tiled_object_t *object = cuteLayer->objects; while (object) { layer->objectCount++; object = object->next; } layer->objects = bzAlloc(layer->objectCount * sizeof(*layer->objects)); object = cuteLayer->objects; for (i32 i = 0; i < layer->objectCount; i++) { layer->objects[i].id = hashFunc(object->name.ptr); layer->objects[i].gid = object->gid; if (layer->minGID > object->gid) layer->minGID = object->gid; else if (layer->maxGID < object->gid) layer->maxGID = object->gid; layer->objects[i].shape = bzCuteObjectToTileShape(object); object = object->next; } } static void updateCollisionMap(BzTileMap *map, i32 startX, i32 startY, i32 endX, i32 endY) { BZ_ASSERT(map->collisionMap); BZ_ASSERT(startX >= 0 && endX <= map->width && startY >= 0 && endY <= map->height); for (i32 y = startY; y < endY; y++) { for (i32 x = startX; x < endX; x++) { map->collisionMap[y * map->width + x] = false; } } // Top-most layer takes priority for (i32 i = map->layerCount - 1; i >= 0; i--) { BzTileLayer *layer = map->layers + i; if (!layer->desc.applyColliders) continue; if (!layer->data) continue; if (layer->tilesetIdx == -1) continue; BzTileset *tileset = map->tilesets + layer->tilesetIdx; for (i32 y = startY; y < endY; y++) { for (i32 x = startX; x < endX; x++) { i32 idx = y * map->width + x; if (map->collisionMap[idx]) continue; BzTile tile = bzTileLayerGetTile(layer, x, y); BzTileID tileID = bzTilesetGetTileID(tileset, tile); BzTileShape tilesetShape = bzTilesetGetTileCollider(tileset, tileID); if (tilesetShape.type == BZ_TILE_SHAPE_NONE || tilesetShape.type == BZ_TILE_SHAPE_POINT) continue; map->collisionMap[idx] = true; } } } } static void createColliders(BzTileMap *map) { map->collisionMap = bzAlloc(map->width * map->height * sizeof(*map->collisionMap)); updateCollisionMap(map, 0, 0, map->width, map->height); } BzTileMap bzTileMapCreate(const BzTileMapDesc *desc) { BzTileMap map = {.backgroundColor=BLACK}; // Auto detect tileset count. for (i32 i = 0; i < BZ_MAP_MAX_TILESETS; i++) { if (!desc->tilesets[i].isValid) break; map.tilesetCount++; } for (i32 i = 0; i < map.tilesetCount; i++) { map.tilesets[i] = desc->tilesets[i]; } cute_tiled_map_t *cuteMap = cute_tiled_load_map_from_file(desc->path, NULL); map.backgroundColor = GetColor(cuteMap->backgroundcolor); map.width = cuteMap->width; map.height = cuteMap->height; map.tileWidth = cuteMap->tilewidth; map.tileHeight = cuteMap->tileheight; cute_tiled_layer_t *cuteLayer = cuteMap->layers; while (cuteLayer) { BZ_ASSERT(map.layerCount < BZ_MAP_MAX_LAYERS); BZ_ASSERT(map.objectGroupCount < BZ_MAP_MAX_LAYERS); // Find slot i32 slot = -1; for (i32 i = 0; i < BZ_MAP_MAX_LAYERS; i++) { const BzTileLayerDesc *layerDesc = desc->layers + i; const BzTileObjectsDesc *objectsDesc = desc->objectGroups + i; if ((layerDesc->name && strcmp(layerDesc->name, cuteLayer->name.ptr) == 0) || (objectsDesc->name && strcmp(objectsDesc->name, cuteLayer->name.ptr) == 0)) { slot = i; break; } else if (!layerDesc->name && !objectsDesc->name) { break; } } if (slot == -1) { bzLogWarning("Dangling layer: %s", cuteLayer->name.ptr); cuteLayer = cuteLayer->next; continue; } if (cuteLayer->data) { BzTileLayer *layer = map.layers + slot; layer->desc = desc->layers[slot]; handleTileLayer(layer, cuteLayer); layer->hasOwnership = true; map.layerCount++; } else { BzTileObjectGroup *objectGroup = map.objectGroups + slot; objectGroup->desc = desc->objectGroups[slot]; if (!objectGroup->desc.hashFunc) objectGroup->desc.hashFunc = bzStringDefaultHash; handleTileObjectLayer(objectGroup, cuteLayer, objectGroup->desc.hashFunc); map.objectGroupCount++; } cuteLayer = cuteLayer->next; } cute_tiled_tileset_t *cuteTileset = cuteMap->tilesets; int tilesetCount = 0; for (; cuteTileset; cuteTileset = cuteTileset->next) tilesetCount++; BZ_ASSERT(tilesetCount == map.tilesetCount); cuteTileset = cuteMap->tilesets; for (i32 i = 0; i < map.tilesetCount; i++) { BzTileset *tileset = map.tilesets + i; tileset->startID = cuteTileset->firstgid; cuteTileset = cuteTileset->next; } // Assign tilesets to layers for (i32 i = 0; i < BZ_MAP_MAX_LAYERS; i++) { BzTileLayer *layer = map.layers + i; layer->tilesetIdx = -1; BzTileObjectGroup *objects = map.objectGroups + i; objects->tilesetIdx = -1; bool hasLayer = i < map.layerCount; bool hasObjects = i < map.objectGroupCount; for (i32 j = map.tilesetCount - 1; j >= 0; j--) { BzTileset *tileset = map.tilesets + j; if (hasLayer && tileset->startID >= layer->minData && tileset->startID <= layer->maxData) { layer->tilesetIdx = j; hasLayer = false; } if (hasObjects && tileset->startID >= objects->minGID && tileset->startID <= objects->maxGID) { objects->tilesetIdx = j; hasObjects = false; } if (!hasLayer && !hasObjects) break; } } cute_tiled_free_map(cuteMap); if (desc->generateCollisionMap) createColliders(&map); map.isValid = true; return map; } void bzTileMapDestroy(BzTileMap *map) { for (i32 i = 0; i < map->layerCount; i++) { BzTileLayer *layer = map->layers + i; if (layer->hasOwnership && layer->data) { bzFree(layer->data); layer->data = NULL; layer->dataCount = 0; } } for (i32 i = 0; i < map->objectGroupCount; i++) { BzTileObjectGroup *objectLayer = map->objectGroups + i; if (objectLayer->hasOwnership && objectLayer->objects) { bzFree(objectLayer->objects); objectLayer->objects = NULL; objectLayer->objectCount = 0; } } bzFree(map->collisionMap); *map = BZ_TILEMAP_INVALID; } Vec2i bzTileMapPosToTile(BzTileMap *map, Vector2 pos) { return (Vec2i) { .x = (i32) (pos.x / (f32) map->tileWidth), .y = (i32) (pos.y / (f32) map->tileHeight) }; } void bzTileMapOverrideLayer(BzTileMap *map, i32 slotID, BzTileLayerFunc func) { BZ_ASSERT(slotID >= 0 && slotID < map->layerCount); BzTileLayer *layer = map->layers + slotID; i32 dataCount = layer->dataCount; BzTile *data = layer->data; if (func(map, layer)) { if (!layer->hasOwnership) return; bzFree(layer->data); if (layer->data == data) { layer->data = NULL; layer->dataCount = 0; } layer->hasOwnership = false; } else { BZ_ASSERT(data == layer->data && dataCount == layer->dataCount); } } void bzTileMapOverrideObjectGroup(BzTileMap *map, i32 slotID, BzTileObjectsFunc func) { BZ_ASSERT(slotID >= 0 && slotID < map->objectGroupCount); BzTileObjectGroup *objectGroup = map->objectGroups + slotID; BzTileObject *objects = objectGroup->objects; i32 objectCount = objectGroup->objectCount; if (func(map, objectGroup)) { bzFree(objectGroup->objects); if (objectGroup->objects == objects) { objectGroup->objects = NULL; objectGroup->objectCount = 0; } objectGroup->hasOwnership = false; } else { BZ_ASSERT(objects == objectGroup->objects && objectCount == objectGroup->objectCount); } } static void drawLayer(BzTileLayer *layer, BzTileset *tileset) { if (!tileset) return; if (layer->minData == layer->maxData) return; Vector2 drawPos = {layer->offsetX, layer->offsetY}; for (i32 y = 0; y < layer->height; y++) { for (i32 x = 0; x < layer->width; x++) { BzTile tile = bzTileLayerGetTile(layer, x, y); BzTileID tileID = bzTilesetGetTileID(tileset, tile); if (tileID != -1) { Rectangle rec = bzTilesetGetTileRegion(tileset, tileID); DrawTextureRec(tileset->tiles, rec, drawPos, WHITE); } drawPos.x += (float) tileset->tileWidth; } drawPos.x = layer->offsetX; drawPos.y += (float) tileset->tileHeight; } } static void drawObjectLayer(BzTileObjectGroup *objectLayer, BzTileset *tileset) { Color color = ORANGE; for (int i = 0; i < objectLayer->objectCount; i++) { BzTileObject object = objectLayer->objects[i]; BzTileShape shape = object.shape; switch (shape.type) { case BZ_TILE_SHAPE_NONE: break; case BZ_TILE_SHAPE_POINT: DrawCircle(shape.x, shape.y, 2.0f, color); break; case BZ_TILE_SHAPE_RECT: if (object.gid) { Rectangle rec = bzTilesetGetTileRegion(tileset, object.gid); Rectangle dest = {shape.x, shape.y - shape.sizeY, shape.sizeX, shape.sizeY}; DrawTexturePro(tileset->tiles, rec, dest, (Vector2) {0.0f, 0.0f}, 0.0f, WHITE); } else { DrawRectangle(shape.x, shape.y, shape.sizeX, shape.sizeY, color); } break; case BZ_TILE_SHAPE_ELLIPSE: DrawEllipse(shape.x, shape.y, shape.sizeX, shape.sizeY, color); break; } } } BzTileLayer *bzTileMapGetLayer(BzTileMap *map, i32 slotID) { BZ_ASSERT(slotID >= 0 && slotID < map->layerCount); return &map->layers[slotID]; } BzTileObjectGroup *bzTileMapGetObjects(BzTileMap *map, i32 slotID) { BZ_ASSERT(slotID >= 0 && slotID < map->objectGroupCount); return &map->objectGroups[slotID]; } static f32 absf32(f32 x) { return x > 0 ? x : -x; } f32 bzTileMapRayCast(BzTileMap *map, Vector2 from, Vector2 to, f32 maxDst, Vector2 *outIntersection) { // Digital Differential Analyzer (DDA) Vector2 scale = {1.0f / map->tileWidth, 1.0f / map->tileHeight}; from = Vector2Multiply(from, scale); to = Vector2Multiply(to, scale); Vector2 dir = Vector2Subtract(to, from); dir = Vector2Normalize(dir); //Vector2 stepSize = { // sqrtf(1 + (dir.y / dir.x) * (dir.y / dir.x)), // sqrtf(1 + (dir.x / dir.y) * (dir.x / dir.y)) //}; Vector2 stepSize = {absf32(1.0f / dir.x), absf32(1.0f / dir.y)}; i32 cellX = (i32) from.x; i32 cellY = (i32) from.y; if (bzTileMapHasCollision(map, cellX, cellY)) { return 0.0f; } Vector2 rayLength = Vector2Zero(); i32 stepX, stepY; if (dir.x < 0) { stepX = -1; rayLength.x = (from.x - (f32) (cellX)) * stepSize.x; } else { stepX = 1; rayLength.x = ((f32) cellX + 1.0f - from.x) * stepSize.x; } if (dir.y < 0) { stepY = -1; rayLength.y = (from.y - (f32) cellY) * stepSize.y; } else { stepY = 1; rayLength.y = ((f32) cellY + 1 - from.y) * stepSize.y; } f32 distance = 0.0f; while (distance < maxDst) { if (rayLength.x < rayLength.y) { cellX += stepX; distance = rayLength.x * map->tileWidth; rayLength.x += stepSize.x; } else { cellY += stepY; distance = rayLength.y * map->tileHeight; rayLength.y += stepSize.y; } if (cellX >= 0 && cellX < map->width && cellY >= 0 && cellY < map->height && map->collisionMap[cellY * map->width + cellX]) { break; } } if (outIntersection) { Vector2 intersection = from; intersection = Vector2Add(intersection, Vector2Scale(dir, distance)); intersection.x *= map->tileWidth; intersection.y *= map->tileHeight; *outIntersection = intersection; } return distance; } bool bzTileMapCanRayCastLine(BzTileMap *map, Vector2 from, Vector2 to) { f32 dst = Vector2Distance(from, to); f32 rayDst = bzTileMapRayCast(map, from, to, dst, NULL); return rayDst >= dst; } void bzTileMapDraw(BzTileMap *map) { for (i32 i = 0; i < map->layerCount; i++) { BzTileLayer *layer = map->layers + i; if (!layer->data) continue; BzTileset *tileset = NULL; if (layer->tilesetIdx != -1) { tileset = map->tilesets + layer->tilesetIdx; } if (layer->desc.renderer) layer->desc.renderer(map, layer); else drawLayer(map->layers + i, tileset); } for (i32 i = 0; i < map->objectGroupCount; i++) { BzTileObjectGroup *objectLayer = map->objectGroups + i; BzTileset *tileset = NULL; if (objectLayer->tilesetIdx != -1) { tileset = map->tilesets + objectLayer->tilesetIdx; } if (!objectLayer->objects) continue; drawObjectLayer(objectLayer, tileset); } } void bzTileMapDrawCollisions(BzTileMap *map) { if (!map->collisionMap) return; Color color = RED; color.a = 150; i32 sizeX = map->tileWidth; i32 sizeY = map->tileHeight; for (i32 y = 0; y < map->height; y++) { for (i32 x = 0; x < map->width; x++) { i32 idx = y * map->width + x; i32 posX = x * sizeX; i32 posY = y * sizeY; if (map->collisionMap[idx]) DrawRectangleLines(posX, posY, sizeX, sizeY, color); } } } bool bzTileMapHasCollision(BzTileMap *map, i32 x, i32 y) { if (!map->collisionMap) return false; i32 idx = y * map->width + x; return map->collisionMap[idx]; } void bzTileMapUpdateCollisions(BzTileMap *map, i32 x, i32 y, i32 sizeX, i32 sizeY) { if (!map->collisionMap) return; updateCollisionMap(map, x, y, x + sizeX, y + sizeY); }