/* Copyright (c) 2021 Alex Diener This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. Alex Diener alex@ludobloom.com */ #include "gamemath/Grid.h" #include "tileset/TileMap.h" #include #ifdef WIN32 #include #endif #include #include TileMap * TileMap_create(unsigned int width, unsigned int height) { TileMap * tileMap = malloc(sizeof(*tileMap)); tileMap->width = width; tileMap->height = height; tileMap->tiles = calloc(sizeof(*tileMap->tiles), width * height); return tileMap; } TileMap * TileMap_createWithTiles(unsigned int width, unsigned int height, TileID * tiles) { TileMap * tileMap = TileMap_create(width, height); memcpy(tileMap->tiles, tiles, sizeof(*tiles) * width * height); return tileMap; } TileMap * TileMap_loadString(const char * string, unsigned int forceWidth, unsigned int forceHeight, TileMapStringKeyEntry * key, unsigned int entryCount) { unsigned int length = strlen(string); if (forceWidth == 0) { unsigned int lineCharIndex = 0; for (unsigned int charIndex = 0; charIndex < length; charIndex++) { if (string[charIndex] == '\n') { lineCharIndex = 0; continue; } lineCharIndex++; if (lineCharIndex > forceWidth) { forceWidth = lineCharIndex; } } } if (forceHeight == 0) { forceHeight = !!length; for (unsigned int charIndex = 0; charIndex < length; charIndex++) { if (string[charIndex] == '\n') { forceHeight++; } } } TileMap * tileMap = TileMap_create(forceWidth, forceHeight); unsigned int lineIndex = 0, lineCharIndex = 0; for (unsigned int charIndex = 0; charIndex < length; charIndex++) { if (string[charIndex] == '\n') { lineIndex++; if (lineIndex >= forceHeight) { break; } lineCharIndex = 0; continue; } if (lineCharIndex >= forceWidth) { continue; } TileID tileID = 0; for (unsigned int entryIndex = 0; entryIndex < entryCount; entryIndex++) { if (string[charIndex] == key[entryIndex].character) { tileID = key[entryIndex].tileID; break; } } tileMap->tiles[lineIndex * forceWidth + lineCharIndex] = tileID; lineCharIndex++; } return tileMap; } TileMap * TileMap_loadRGB(const uint8_t * pixels, unsigned int width, unsigned int height, TileMapColorKeyEntry * key, unsigned int entryCount) { TileMap * tileMap = TileMap_create(width, height); for (unsigned int rowIndex = 0; rowIndex < height; rowIndex++) { for (unsigned int columnIndex = 0; columnIndex < width; columnIndex++) { TileID tileID = 0; for (unsigned int entryIndex = 0; entryIndex < entryCount; entryIndex++) { if (pixels[(rowIndex * width + columnIndex) * 3 + 0] == key[entryIndex].red && pixels[(rowIndex * width + columnIndex) * 3 + 1] == key[entryIndex].green && pixels[(rowIndex * width + columnIndex) * 3 + 2] == key[entryIndex].blue) { tileID = key[entryIndex].tileID; break; } } tileMap->tiles[rowIndex * width + columnIndex] = tileID; } } return tileMap; } bool TileMap_isEqual(TileMap * self, TileMap * tileMap) { if (self->width != tileMap->width || self->height != tileMap->height) { return false; } return !memcmp(self->tiles, tileMap->tiles, self->width * self->height * sizeof(self->tiles[0])); } TileMap * TileMap_copy(TileMap * tileMap) { TileMap * copy = TileMap_create(tileMap->width, tileMap->height); memcpy(copy->tiles, tileMap->tiles, tileMap->width * tileMap->height * sizeof(tileMap->tiles[0])); return copy; } void TileMap_dispose(TileMap * tileMap) { free(tileMap->tiles); free(tileMap); } void TileMap_writeString(TileMap * tileMap, char * outString, TileMapStringKeyEntry * key, unsigned int entryCount, char unknownTileCharacter) { unsigned int width = tileMap->width, height = tileMap->height; for (unsigned int rowIndex = 0; rowIndex < height; rowIndex++) { for (unsigned int columnIndex = 0; columnIndex < width; columnIndex++) { char character = unknownTileCharacter; for (unsigned int entryIndex = 0; entryIndex < entryCount; entryIndex++) { if (tileMap->tiles[rowIndex * width + columnIndex] == key[entryIndex].tileID) { character = key[entryIndex].character; break; } } outString[rowIndex * (width + 1) + columnIndex] = character; } outString[rowIndex * (width + 1) + width] = '\n'; } outString[height * (width + 1) - 1] = 0; } void TileMap_writeRGB(TileMap * tileMap, uint8_t * outPixels, TileMapColorKeyEntry * key, unsigned int entryCount, uint8_t unknownTileRed, uint8_t unknownTileGreen, uint8_t unknownTileBlue) { unsigned int width = tileMap->width, height = tileMap->height; for (unsigned int rowIndex = 0; rowIndex < height; rowIndex++) { for (unsigned int columnIndex = 0; columnIndex < width; columnIndex++) { uint8_t rgb[3] = {unknownTileRed, unknownTileGreen, unknownTileBlue}; for (unsigned int entryIndex = 0; entryIndex < entryCount; entryIndex++) { if (tileMap->tiles[rowIndex * width + columnIndex] == key[entryIndex].tileID) { rgb[0] = key[entryIndex].red; rgb[1] = key[entryIndex].green; rgb[2] = key[entryIndex].blue; break; } } outPixels[(rowIndex * width + columnIndex) * 3 + 0] = rgb[0]; outPixels[(rowIndex * width + columnIndex) * 3 + 1] = rgb[1]; outPixels[(rowIndex * width + columnIndex) * 3 + 2] = rgb[2]; } } } void TileMap_calculateAdjacency4(TileMap * tileMap, TileAdjacency4 * outAdjacency, TileAdjacencyKeyEntry * key, unsigned int entryCount) { unsigned int width = tileMap->width, height = tileMap->height; TileID * tiles = tileMap->tiles; for (unsigned int rowIndex = 0; rowIndex < height; rowIndex++) { for (unsigned int columnIndex = 0; columnIndex < width; columnIndex++) { TileAdjacency4 currentTileAdjacency = {0, 0, 0, 0}; for (unsigned int entryIndex = 0; entryIndex < entryCount; entryIndex++) { if (tiles[rowIndex * width + columnIndex] == key[entryIndex].originTileID) { if ((columnIndex == 0 && key[entryIndex].assignAtEdges) || (columnIndex > 0 && tiles[rowIndex * width + columnIndex - 1] == key[entryIndex].adjacentTileID)) { currentTileAdjacency.left = key[entryIndex].value; } if ((columnIndex == width - 1 && key[entryIndex].assignAtEdges) || (columnIndex < width - 1 && tiles[rowIndex * width + columnIndex + 1] == key[entryIndex].adjacentTileID)) { currentTileAdjacency.right = key[entryIndex].value; } if ((rowIndex == 0 && key[entryIndex].assignAtEdges) || (rowIndex > 0 && tiles[(rowIndex - 1) * width + columnIndex] == key[entryIndex].adjacentTileID)) { currentTileAdjacency.top = key[entryIndex].value; } if ((rowIndex == height - 1 && key[entryIndex].assignAtEdges) || (rowIndex < height - 1 && tiles[(rowIndex + 1) * width + columnIndex] == key[entryIndex].adjacentTileID)) { currentTileAdjacency.bottom = key[entryIndex].value; } } } outAdjacency[rowIndex * width + columnIndex] = currentTileAdjacency; } } } void TileMap_calculateAdjacency8(TileMap * tileMap, TileAdjacency8 * outAdjacency, TileAdjacencyKeyEntry * key, unsigned int entryCount) { unsigned int width = tileMap->width, height = tileMap->height; TileID * tiles = tileMap->tiles; for (unsigned int rowIndex = 0; rowIndex < height; rowIndex++) { for (unsigned int columnIndex = 0; columnIndex < width; columnIndex++) { TileAdjacency8 currentTileAdjacency = {0, 0, 0, 0, 0, 0, 0, 0}; for (unsigned int entryIndex = 0; entryIndex < entryCount; entryIndex++) { if (tiles[rowIndex * width + columnIndex] == key[entryIndex].originTileID) { if (((rowIndex == 0 || columnIndex == 0) && key[entryIndex].assignAtEdges) || (rowIndex > 0 && columnIndex > 0 && tiles[(rowIndex - 1) * width + columnIndex - 1] == key[entryIndex].adjacentTileID)) { currentTileAdjacency.topLeft = key[entryIndex].value; } if ((rowIndex == 0 && key[entryIndex].assignAtEdges) || (rowIndex > 0 && tiles[(rowIndex - 1) * width + columnIndex] == key[entryIndex].adjacentTileID)) { currentTileAdjacency.top = key[entryIndex].value; } if (((rowIndex == 0 || columnIndex == width - 1) && key[entryIndex].assignAtEdges) || (rowIndex > 0 && columnIndex < width - 1 && tiles[(rowIndex - 1) * width + columnIndex + 1] == key[entryIndex].adjacentTileID)) { currentTileAdjacency.topRight = key[entryIndex].value; } if ((columnIndex == 0 && key[entryIndex].assignAtEdges) || (columnIndex > 0 && tiles[rowIndex * width + columnIndex - 1] == key[entryIndex].adjacentTileID)) { currentTileAdjacency.left = key[entryIndex].value; } if ((columnIndex == width - 1 && key[entryIndex].assignAtEdges) || (columnIndex < width - 1 && tiles[rowIndex * width + columnIndex + 1] == key[entryIndex].adjacentTileID)) { currentTileAdjacency.right = key[entryIndex].value; } if (((rowIndex == height - 1 || columnIndex == 0) && key[entryIndex].assignAtEdges) || (rowIndex < height - 1 && columnIndex > 0 && tiles[(rowIndex + 1) * width + columnIndex - 1] == key[entryIndex].adjacentTileID)) { currentTileAdjacency.bottomLeft = key[entryIndex].value; } if ((rowIndex == height - 1 && key[entryIndex].assignAtEdges) || (rowIndex < height - 1 && tiles[(rowIndex + 1) * width + columnIndex] == key[entryIndex].adjacentTileID)) { currentTileAdjacency.bottom = key[entryIndex].value; } if (((rowIndex == height - 1 || columnIndex == width - 1) && key[entryIndex].assignAtEdges) || (rowIndex < height - 1 && columnIndex < width - 1 && tiles[(rowIndex + 1) * width + columnIndex + 1] == key[entryIndex].adjacentTileID)) { currentTileAdjacency.bottomRight = key[entryIndex].value; } } } outAdjacency[rowIndex * width + columnIndex] = currentTileAdjacency; } } } static TileID evaluateAutoTileRules(AutoTileRuleset * ruleset, TileID baseTileID, TileID adjacentTiles[ADJACENT_DIRECTION_COUNT]) { for (unsigned int ruleIndex = 0; ruleIndex < ruleset->ruleCount; ruleIndex++) { if (baseTileID == ruleset->rules[ruleIndex].baseTileID) { bool pass = true; for (unsigned int directionIndex = 0; directionIndex < ADJACENT_DIRECTION_COUNT; directionIndex++) { TileID rulesetTile = ruleset->rules[ruleIndex].adjacentDirections[directionIndex]; if (rulesetTile == TILE_ID_INVALID) { continue; } TileID adjacentTile = adjacentTiles[directionIndex]; if ((ruleset->rules[ruleIndex].options & AUTO_TILE_OPTION_EDGE_IS_MATCH) && adjacentTile == TILE_ID_INVALID) { if (ruleset->rules[ruleIndex].options & AUTO_TILE_OPTION_NEGATIVE) { pass = false; break; } continue; } if ((ruleset->rules[ruleIndex].options & AUTO_TILE_OPTION_ZERO_IS_MATCH) && adjacentTile == TILE_ID_NONE) { if (ruleset->rules[ruleIndex].options & AUTO_TILE_OPTION_NEGATIVE) { pass = false; break; } continue; } if ((rulesetTile == adjacentTile) != !(ruleset->rules[ruleIndex].options & AUTO_TILE_OPTION_NEGATIVE)) { pass = false; break; } } if (pass) { return ruleset->rules[ruleIndex].adjustedTileID; } } } return baseTileID; } static TileID TileMap_autoTileSingle_internal(unsigned int width, unsigned int height, TileID * tiles, unsigned int columnIndex, unsigned int rowIndex, AutoTileRuleset * ruleset) { static const int adjacencyOffsets[ADJACENT_DIRECTION_COUNT][2] = { {-1, -1}, {0, -1}, {1, -1}, {-1, 0}, {1, 0}, {-1, 1}, {0, 1}, {1, 1} }; TileID baseTileID = tiles[rowIndex * width + columnIndex]; TileID adjacentTiles[ADJACENT_DIRECTION_COUNT]; for (unsigned int directionIndex = 0; directionIndex < ADJACENT_DIRECTION_COUNT; directionIndex++) { if (rowIndex + adjacencyOffsets[directionIndex][1] >= height || columnIndex + adjacencyOffsets[directionIndex][0] >= width) { adjacentTiles[directionIndex] = TILE_ID_INVALID; } else { adjacentTiles[directionIndex] = tiles[(rowIndex + adjacencyOffsets[directionIndex][1]) * width + columnIndex + adjacencyOffsets[directionIndex][0]]; } } return evaluateAutoTileRules(ruleset, baseTileID, adjacentTiles); } void TileMap_applyAutoTile(TileMap * toTileMap, TileMap * fromTileMap, AutoTileRuleset * ruleset) { assert(toTileMap->width == fromTileMap->width && toTileMap->height == fromTileMap->height); unsigned int width = toTileMap->width, height = toTileMap->height; TileID * fromTiles, * toTiles = toTileMap->tiles; if (toTileMap == fromTileMap) { fromTiles = alloca(sizeof(*fromTiles) * fromTileMap->width * fromTileMap->height); memcpy(fromTiles, fromTileMap->tiles, sizeof(*fromTiles) * fromTileMap->width * fromTileMap->height); } else { fromTiles = fromTileMap->tiles; } for (unsigned int rowIndex = 0; rowIndex < height; rowIndex++) { for (unsigned int columnIndex = 0; columnIndex < width; columnIndex++) { toTiles[rowIndex * width + columnIndex] = TileMap_autoTileSingle_internal(width, height, fromTiles, columnIndex, rowIndex, ruleset); } } } TileID TileMap_autoTileSingle(TileMap * tileMap, unsigned int columnIndex, unsigned int rowIndex, AutoTileRuleset * ruleset) { return TileMap_autoTileSingle_internal(tileMap->width, tileMap->height, tileMap->tiles, columnIndex, rowIndex, ruleset); } void TileMap_clear(TileMap * tileMap, TileID tileID) { unsigned int tileCount = tileMap->width * tileMap->height; for (unsigned int tileIndex = 0; tileIndex < tileCount; tileIndex++) { tileMap->tiles[tileIndex] = tileID; } } void TileMap_copyTiles(TileMap * target, TileMap * source, int sourceX, int sourceY, int targetX, int targetY, unsigned int width, unsigned int height) { copyGridCells(target->tiles, source->tiles, source->width, source->height, target->width, target->height, sizeof(TileID), sourceX, sourceY, targetX, targetY, width, height); } void TileMap_composite(TileMap * target, TileMap * source, int sourceX, int sourceY, int targetX, int targetY, unsigned int width, unsigned int height) { unsigned int startX = 0, startY = 0; clampGridCopyRegion(sourceX, sourceY, targetX, targetY, source->width, source->height, target->width, target->height, &width, &height, &startX, &startY); for (unsigned int rowIndex = startY; rowIndex < height; rowIndex++) { for (unsigned int columnIndex = startX; columnIndex < width; columnIndex++) { unsigned int sourceIndex = (sourceY + rowIndex) * source->width + sourceX + columnIndex; unsigned int targetIndex = (targetY + rowIndex) * target->width + targetX + columnIndex; if (source->tiles[sourceIndex] != 0) { target->tiles[targetIndex] = source->tiles[sourceIndex]; } } } } void TileMap_flipHorizontal(TileMap * tileMap) { flipGridHorizontal(tileMap->tiles, tileMap->width, tileMap->height, sizeof(TileID)); } void TileMap_flipVertical(TileMap * tileMap) { flipGridVertical(tileMap->tiles, tileMap->width, tileMap->height, sizeof(TileID)); } void TileMap_rotate180(TileMap * tileMap) { rotateGrid180(tileMap->tiles, tileMap->width, tileMap->height, sizeof(TileID)); } void TileMap_rotate90CW(TileMap * tileMap) { rotateGrid90CW(tileMap->tiles, tileMap->width, tileMap->height, sizeof(TileID)); unsigned int width = tileMap->width; tileMap->width = tileMap->height; tileMap->height = width; } void TileMap_rotate90CCW(TileMap * tileMap) { rotateGrid90CCW(tileMap->tiles, tileMap->width, tileMap->height, sizeof(TileID)); unsigned int width = tileMap->width; tileMap->width = tileMap->height; tileMap->height = width; }