#ifndef _LCR_MAP #define _LCR_MAP #include #include "constants.h" #include "settings.h" /** The map (track) module for Licar. Map coordinates/size: - map size is 64x64x64 blocks - [0,0,0] is is bottom-left-front-most - x goes right, y goes up, z goes forward - coordinate number is a single number obtained as x + 64 * y + 64 * 64 * z The TEXT format serves for editing maps in human readable format, it more or less corresponds to the binary storage format (below) with some exceptions. It has the following structure: - Magic number string: "LM;". - Until next ';': name. - Until next ';': description. - Until next ';': tags. Currently this may contain the following: - digit N: set the map environment to N. - Then a series of block strings follows. Blocks may be separated by characters that aren't '#' (comments may be added this way). Block format is following: #BXYZMT where: - B is block type - X, Y and Z are block coordinates, each one a single character. The following are characters signifying numbers 0 to 63: 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$@ - M is block material ('0' to '3'). - T is an optinal transform string (for more detail see the binary format) consisting from 0 to 3 characters, which may be: - '|': flip horizontally - '-': flip vertically - 'L': rotate 90 degrees - 'I': rotate 180 degrees - 'J': rotate 270 degrees Here the block order matters, latter blocks rewrite previously placed ones. The internal BINARY map format is made from the text string. It can't contain special blocks and blocks are ordered by their coordinate number (for fast lookup). The format consists of blocks, each of format: - 1 byte type: says the type of block, is the same value as B in the text format. - 3 bytes: A, B, C, such that: - A, B and lowest 2 bits of C form the block coordinate number (A being the lowest part etc.) - bits C2 and C3 say the block material - highest 4 bits of C (C4, C5, C6, C7) say the block's transform: - first if C4 is set, the block is flipped in the X direction - then the block is rotated around vertical axis by 0, 90, 180 or 270 degrees if C5C6 is 00, 01, 10 or 11. - last if C7 is set, the block is flipped vertically */ #define LCR_BLOCK_TRANSFORM_FLIP_H 0x10 #define LCR_BLOCK_TRANSFORM_ROT_90 0x20 #define LCR_BLOCK_TRANSFORM_ROT_180 0x40 #define LCR_BLOCK_TRANSFORM_ROT_270 0x60 #define LCR_BLOCK_TRANSFORM_FLIP_V 0x80 #define LCR_BLOCK_XYZ_TO_COORD(x,y,z) // ??? #define LCR_MAP_MAGIC_NUMBER1 'L' #define LCR_MAP_MAGIC_NUMBER2 'M' #define LCR_MAP_SEPARATOR ';' #define LCR_MAP_MAGIC_NUMBER LCR_MAP_MAGIC_NUMBER1, LCR_MAP_MAGIC_NUMBER2 #define LCR_MAP_TERMINATOR 0xff #define LCR_MAP_BLOCK(t,x,y,z,m,r) t,(uint8_t) (x | (y << 6)), \ (uint8_t) ((y >> 2) | (z << 4)), \ (uint8_t) ((z >> 4) | (m << 2) | (r)) #define LCR_BLOCK_SIZE 4 ///< size of map block, in bytes #define LCR_BLOCK_MATERIAL_CONCRETE 0x00 #define LCR_BLOCK_MATERIAL_GRASS 0x01 #define LCR_BLOCK_MATERIAL_DIRT 0x02 #define LCR_BLOCK_MATERIAL_ICE 0x03 // normal blocks: #define LCR_BLOCK_FULL '=' ///< completely filled block #define LCR_BLOCK_BOTTOM '-' ///< filled bottom half #define LCR_BLOCK_LEFT ';' ///< filled left half #define LCR_BLOCK_BOTTOM_LEFT ',' ///< filled bottom left quarter #define LCR_BLOCK_BOTTOM_LEFT_FRONT '.' ///< filled bottom left front eigth #define LCR_BLOCK_RAMP '^' ///< plain ramp #define LCR_BLOCK_RAMP_34 '/' ///< plain ramp, 3/4 size #define LCR_BLOCK_RAMP_12 '<' ///< plain ramp, 1/2 size #define LCR_BLOCK_RAMP_14 '_' ///< plain ramp, 1/4 size #define LCR_BLOCK_RAMP_CORNER 'v' ///< corner of ramp #define LCR_BLOCK_RAMP_CURVED_PLAT ']' ///< curved ramp with top platgform #define LCR_BLOCK_RAMP_CURVED ')' ///< curv. ramp without top platf. #define LCR_BLOCK_RAMP_CURVED_WALL '}' ///< curved ramp plus small wall #define LCR_BLOCK_RAMP_STEEP '|' ///< extremely steep ramp #define LCR_BLOCK_CORNER 'A' ///< diagonal corner #define LCR_BLOCK_CORNER_12 '\\' ///< diagonal corner (1/2 wide) #define LCR_BLOCK_HILL '(' ///< curved "hill" #define LCR_BLOCK_FULL_ACCEL '>' #define LCR_BLOCK_FULL_FAN 'o' #define LCR_BLOCK_BUMP '~' ///< small bump on the road #define LCR_BLOCK_CORNER_CONVEX 'n' #define LCR_BLOCK_CORNER_CONCAVE 'l' #define LCR_BLOCK_CHECKPOINT_0 '+' ///< checkpoint, not taken #define LCR_BLOCK_CHECKPOINT_1 '\'' ///< checkpoint, taken #define LCR_BLOCK_FINISH '!' ///< finish // special blocks: #define LCR_BLOCK_NONE 'x' ///< no block, e.g to make holes #define LCR_BLOCK_CUBOID_FILL 'f' /**< makes a cuboid from the previously specified block, the size is given by block coords */ #define LCR_BLOCK_CUBOID_HOLLOW 'h' /**< same as cuboid special block, but makes a hollow one */ #define LCR_BLOCK_START '*' ///< specifies start block position /* TODO: - ramp corner??? - curved corner? - curved out corner? - curved "hill" - bumpy road - bigger structures like a loop, sloped road etc? */ #define LCR_MAP_BLOCK_CACHE_SIZE (8 * 2) /// do not change /** Cache for accelerating LCR_mapGetBlockAtFast, consists of 8 2-item records, the first record item stores block coord number, the second one stores the value returned by LCR_mapGetBlockAtFast (-1 is 0xffffffff). The record index depends on the block coords: lowest bit is x % 2, middle bit y % 2, highest one z % 2. */ uint32_t _LCR_mapBlockCache[LCR_MAP_BLOCK_CACHE_SIZE]; struct { uint16_t blockCount; uint8_t blocks[LCR_SETTING_MAP_MAX_BLOCKS * LCR_BLOCK_SIZE]; uint8_t startPos[4]; ///< Initial position and rotation. uint8_t environment; uint8_t checkpointCount; // TODO: name, desc? possibly as a single '\n' separated string? } LCR_currentMap; void LCR_makeMapBlock(uint8_t type, uint8_t x, uint8_t y, uint8_t z, uint8_t material, uint8_t transform, uint8_t block[LCR_BLOCK_SIZE]) { block[0] = type; block[1] = x | (y << 6); block[2] = (y >> 2) | (z << 4); block[3] = (z >> 4) | (material << 2) | transform; } void LCR_mapBlockGetCoords(const uint8_t block[LCR_BLOCK_SIZE], uint8_t *x, uint8_t *y, uint8_t *z) { *x = block[1] & 0x3f; *y = (block[1] >> 6) | ((block[2] & 0x0f) << 2); *z = (block[2] >> 4) | ((block[3] & 0x03) << 4); } uint8_t LCR_mapBlockOppositeTransform(uint8_t transform) { if (!(transform & LCR_BLOCK_TRANSFORM_FLIP_H)) { if ((transform & 0x60) == LCR_BLOCK_TRANSFORM_ROT_90) return ((transform & (~0x60)) | LCR_BLOCK_TRANSFORM_ROT_270); if ((transform & 0x60) == LCR_BLOCK_TRANSFORM_ROT_270) return ((transform & (~0x60)) | LCR_BLOCK_TRANSFORM_ROT_90); } return transform; } uint8_t LCR_mapBlockGetTransform(const uint8_t block[LCR_BLOCK_SIZE]) { return block[3] & 0xf0; } uint8_t LCR_mapBlockGetMaterial(const uint8_t block[LCR_BLOCK_SIZE]) { return (block[3] >> 2) & 0x03; } uint32_t LCR_mapBlockGetCoordNumber(const uint8_t block[LCR_BLOCK_SIZE]) { return block[1] | (((uint32_t) block[2]) << 8) | ((((uint32_t) block[3]) & 0x3) << 16); } uint32_t LCR_mapBlockCoordsToCoordNumber(uint8_t x, uint8_t y, uint8_t z) { uint8_t b[LCR_BLOCK_SIZE]; LCR_makeMapBlock(0,x,y,z,0,0,b); return LCR_mapBlockGetCoordNumber(b); } /** Gets dimensions of a non-curved ramp. */ void LCR_rampGetDimensions(uint8_t rampType, uint8_t *height4ths, uint8_t *length6ths) { *height4ths = (rampType == LCR_BLOCK_RAMP_14) + (rampType == LCR_BLOCK_RAMP || rampType == LCR_BLOCK_RAMP_STEEP) * 4 + (rampType == LCR_BLOCK_RAMP_12 || rampType == LCR_BLOCK_RAMP_34) * 2 + (rampType == LCR_BLOCK_RAMP_34); *length6ths = rampType != LCR_BLOCK_RAMP_STEEP ? 6 : 1; } uint8_t *LCR_getMapBlockAtCoordNumber(uint32_t coord) { // binary search the block: uint16_t a = 0, b = LCR_currentMap.blockCount - 1; while (b >= a) { uint16_t mid = (a + b) / 2; uint8_t *block = LCR_currentMap.blocks + mid * LCR_BLOCK_SIZE; uint32_t coord2 = LCR_mapBlockGetCoordNumber(block); if (coord2 == coord) return block; else if (coord2 > coord) b = mid - 1; else a = mid + 1; } return 0; } /** Adds given block to current map, including possibly deleting a block by adding LCR_BLOCK_NONE. The function handles sorting the block to the right position. Returns pointer to the block on success, else 0. */ uint8_t *_LCR_mapAddBlock(const uint8_t block[LCR_BLOCK_SIZE]) { LCR_LOG2("adding map block"); if (LCR_currentMap.blockCount >= LCR_SETTING_MAP_MAX_BLOCKS) { LCR_LOG0("couldn't add block"); return 0; } uint32_t coord = LCR_mapBlockGetCoordNumber(block); uint16_t insertAt = 0; while (insertAt < LCR_currentMap.blockCount && coord > LCR_mapBlockGetCoordNumber(LCR_currentMap.blocks + insertAt * LCR_BLOCK_SIZE)) insertAt++; uint8_t *result = LCR_currentMap.blocks + insertAt * LCR_BLOCK_SIZE; if (block[0] == LCR_BLOCK_NONE) { if (insertAt < LCR_currentMap.blockCount && coord == LCR_mapBlockGetCoordNumber(LCR_currentMap.blocks + insertAt * LCR_BLOCK_SIZE)) { // shift all left (remove the block): for (uint16_t i = insertAt * LCR_BLOCK_SIZE; i < LCR_currentMap.blockCount * LCR_BLOCK_SIZE - 1; ++i) LCR_currentMap.blocks[i] = LCR_currentMap.blocks[i + 1]; LCR_currentMap.blockCount--; } return result; } if (insertAt == LCR_currentMap.blockCount || coord != LCR_mapBlockGetCoordNumber(LCR_currentMap.blocks + insertAt * LCR_BLOCK_SIZE)) { // shift from here to the right, make room for the new block LCR_currentMap.blockCount++; for (int16_t i = ((int16_t) LCR_currentMap.blockCount) - 1; i > insertAt; i--) for (uint8_t j = 0; j < LCR_BLOCK_SIZE; ++j) LCR_currentMap.blocks[i * LCR_BLOCK_SIZE + j] = LCR_currentMap.blocks[(i - 1) * LCR_BLOCK_SIZE + j]; } insertAt *= LCR_BLOCK_SIZE; for (uint8_t j = 0; j < LCR_BLOCK_SIZE; ++j) LCR_currentMap.blocks[insertAt + j] = block[j]; return result; } /** Resets the map to start a run (including e.g. unmarking checkpoints etc.). */ void LCR_mapReset(void) { LCR_LOG0("resetting map"); for (int i = 0; i < LCR_currentMap.blockCount; ++i) if (LCR_currentMap.blocks[i * LCR_BLOCK_SIZE] == LCR_BLOCK_CHECKPOINT_1) LCR_currentMap.blocks[i * LCR_BLOCK_SIZE] = LCR_BLOCK_CHECKPOINT_0; } int _LCR_mapCharToCoord(char c) { if (c >= '0' && c <= '9') return c - '0'; if (c >= 'a' && c <= 'z') return c - 'a' + 10; if (c >= 'A' && c <= 'Z') return c - 'A' + 36; if (c == '@') return 62; if (c == '&') return 63; return -1; } uint8_t LCR_mapLoadFromStr(const char *mapStr) { LCR_LOG0("loading map string"); const uint8_t *prevBlock = 0; for (int i = 0; i < 4; ++i) LCR_currentMap.startPos[i] = 0; LCR_currentMap.checkpointCount = 0; LCR_currentMap.blockCount = 0; LCR_currentMap.environment = 0; if (mapStr[0] != LCR_MAP_MAGIC_NUMBER1 || mapStr[1] != LCR_MAP_MAGIC_NUMBER2 || mapStr[2] != LCR_MAP_SEPARATOR) { LCR_LOG0("bad magic number"); return 0; } mapStr += 3; while (*mapStr != LCR_MAP_SEPARATOR) // read map name { if (mapStr[0] == 0) return 0; // TODO mapStr++; } mapStr++; while (*mapStr != LCR_MAP_SEPARATOR) // read map description { if (mapStr[0] == 0) return 0; // TODO mapStr++; } mapStr++; while (*mapStr != LCR_MAP_SEPARATOR) // read map tags { if (mapStr[0] >= '0' && mapStr[0] <= '9') LCR_currentMap.environment = mapStr[0] - '0'; else if (mapStr[0] == 0) return 0; // TODO mapStr++; } mapStr++; while (*mapStr) { if (*mapStr == '#') { mapStr++; uint8_t block = *mapStr; uint8_t trans = 0; uint8_t mat = 0; int coords[3]; for (int i = 0; i < 3; ++i) { mapStr++; coords[i] = _LCR_mapCharToCoord(*mapStr); if (coords[i] < 0) { LCR_LOG0("bad coord"); return 0; } } mapStr++; if (*mapStr >= '0' && *mapStr <= '3') mat = *mapStr - '0'; else { LCR_LOG0("bad material"); return 0; } mapStr++; while (1) { if (*mapStr == '|') trans |= LCR_BLOCK_TRANSFORM_FLIP_H; else if (*mapStr == '-') trans |= LCR_BLOCK_TRANSFORM_FLIP_V; else if (*mapStr == 'L') trans |= LCR_BLOCK_TRANSFORM_ROT_90; else if (*mapStr == 'I') trans |= LCR_BLOCK_TRANSFORM_ROT_180; else if (*mapStr == 'J') trans |= LCR_BLOCK_TRANSFORM_ROT_270; else break; mapStr++; } switch (block) { case LCR_BLOCK_CUBOID_FILL: case LCR_BLOCK_CUBOID_HOLLOW: { uint8_t x, y, z, mat, transform; uint8_t tmpBlock[LCR_BLOCK_SIZE]; if (prevBlock == 0 || LCR_currentMap.blockCount == 0) { LCR_LOG0("no previous block"); return 0; } mat = LCR_mapBlockGetMaterial(prevBlock); transform = LCR_mapBlockGetTransform(prevBlock); LCR_mapBlockGetCoords(prevBlock,&x,&y,&z); for (uint8_t k = 0; k < coords[2]; ++k) for (uint8_t j = 0; j < coords[1]; ++j) for (uint8_t i = 0; i < coords[0]; ++i) if ((block == LCR_BLOCK_CUBOID_FILL || k == 0 || k == coords[2] - 1 || j == 0 || j == coords[1] - 1 || i == 0 || i == coords[0] - 1) && (x + i < LCR_MAP_SIZE_BLOCKS && y + j < LCR_MAP_SIZE_BLOCKS && z + k < LCR_MAP_SIZE_BLOCKS)) { LCR_makeMapBlock(prevBlock[0],x + i,y + j,z + k,mat,transform, tmpBlock); prevBlock = _LCR_mapAddBlock(tmpBlock); if (!prevBlock) return 0; } break; } case LCR_BLOCK_START: LCR_currentMap.startPos[0] = coords[0]; LCR_currentMap.startPos[1] = coords[1]; LCR_currentMap.startPos[2] = coords[2]; LCR_currentMap.startPos[3] = trans & 0x60; break; case LCR_BLOCK_CHECKPOINT_0: LCR_currentMap.checkpointCount++; // fall through default: // normal block { uint8_t tmpBlock[LCR_BLOCK_SIZE]; LCR_makeMapBlock(block,coords[0],coords[1],coords[2],mat,trans, tmpBlock); prevBlock = _LCR_mapAddBlock(tmpBlock); if (prevBlock == 0) return 0; break; } // TODO: check for invalid blocks? } } if (*mapStr != '#') mapStr++; } LCR_LOG2("clearing map block cache") for (int i = 0; i < LCR_MAP_BLOCK_CACHE_SIZE; ++i) _LCR_mapBlockCache[i] = 0xffffffff; LCR_LOG2("map loaded") LCR_mapReset(); return 1; } /** Same as LCR_mapGetBlockAt, but allows to specify start and end block of the of the search to make it faster. */ int LCR_mapGetBlockAtFast(uint8_t x, uint8_t y, uint8_t z, int start, int end) { // binary search (the blocks are sorted) uint32_t n = LCR_mapBlockCoordsToCoordNumber(x,y,z); uint8_t cacheIndex = 2 * ((x % 2) | ((y % 2) << 1) | ((z % 2) << 2)); if (_LCR_mapBlockCache[cacheIndex] == n) return (_LCR_mapBlockCache[cacheIndex + 1] != 0xffffffff) ? ((int) _LCR_mapBlockCache[cacheIndex + 1]) : -1; _LCR_mapBlockCache[cacheIndex] = n; while (start <= end) { int m = (start + end) / 2; uint32_t n2 = LCR_mapBlockGetCoordNumber( LCR_currentMap.blocks + m * LCR_BLOCK_SIZE); if (n2 < n) start = m + 1; else if (n2 > n) end = m - 1; else { _LCR_mapBlockCache[cacheIndex + 1] = m; return m; } } _LCR_mapBlockCache[cacheIndex + 1] = 0xffffffff; return -1; } /** Gets an index to a map block of the currently loaded map at given coordinates. If there is no block at given coordinates, -1 is returned. */ int LCR_mapGetBlockAt(uint8_t x, uint8_t y, uint8_t z) { if (LCR_currentMap.blockCount == 0) return -1; return LCR_mapGetBlockAtFast(x,y,z,0,LCR_currentMap.blockCount - 1); } uint8_t _LCR_encodeMapBlockCoords(uint8_t x, uint8_t y, uint8_t z) { return (5 * 7) * z + 7 * y + x; } void _LCR_decodeMapBlockCoords(uint8_t byte, uint8_t *x, uint8_t *y, uint8_t *z) { *x = (byte % 7); *y = ((byte / 7) % 5); *z = (byte / 35); } #define LCR_BLOCK_SHAPE_COORD_MAX 12 /** Decodes XYZ coordinates encoded in a byte returned by LCR_mapGetBlockShape. Each coordinate will be in range 0 to 12 (including both). This unusual range is intentional as it for example has an exact mid value. */ void LCR_decodeMapBlockCoords(uint8_t byte, uint8_t *x, uint8_t *y, uint8_t *z) { _LCR_decodeMapBlockCoords(byte,x,y,z); *x *= 2; *y *= 3; *z *= 2; } void _LCR_addBlockShapeByte(uint8_t *bytes, uint8_t *byteCount, int x, int y, int z) { if (*byteCount >= LCR_MAP_BLOCK_SHAPE_MAX_BYTES) return; bytes[*byteCount] = _LCR_encodeMapBlockCoords(x,y,z); *byteCount += 1; } /** Macro that transforms coordinates according to block transformation. */ #define LCR_TRANSFORM_COORDS(trans,cx,cy,cz,maxXZ,maxY)\ if (trans & LCR_BLOCK_TRANSFORM_FLIP_H) cx = maxXZ - cx;\ if (trans & 0x20) { /* for both 90 and 270 */ \ cx ^= cz; cz ^= cx; cx ^= cz; /* swap */ \ cx = maxXZ - cx; } \ if (trans & 0x40) { /* for both 180 and 270 */ \ cx = maxXZ - cx; \ cz = maxXZ - cz; } \ if (trans & LCR_BLOCK_TRANSFORM_FLIP_V) \ cy = maxY - cy; /** Gets a shape of given map block type as a 3D model composed of triangles. The model is returned as an array of byte triplets (triangles), with each byte representing one coordinate. These coordinates can be decoded with LCR_decodeMapBlockCoords function. */ void LCR_mapGetBlockShape(uint8_t blockType, uint8_t transform, uint8_t bytes[LCR_MAP_BLOCK_SHAPE_MAX_BYTES], uint8_t *byteCount) { /* The coordinate format is following: byte B specifies coordinates X (0 to 6) = B % 7, Y (vertical, 0 to 4) = (B / 7) % 5, Z (0 to 6) = B % 35. Helper side view grid: 4 . . . . . . . ^ 3 . . . . . . . | y 2 . . . . . . . 1 . . . . . . . 0 . . . . . . . 0 1 2 3 4 5 6 -> x/z */ *byteCount = 0; #define ADD(a,b,c) _LCR_addBlockShapeByte(bytes,byteCount,a,b,c); switch (blockType) { case LCR_BLOCK_CHECKPOINT_0: case LCR_BLOCK_CHECKPOINT_1: case LCR_BLOCK_FINISH: ADD(3,0,3) ADD(0,2,6) ADD(6,2,6) ADD(3,0,3) ADD(0,2,0) ADD(0,2,6) ADD(3,0,3) ADD(6,2,0) ADD(0,2,0) ADD(3,0,3) ADD(6,2,6) ADD(6,2,0) ADD(3,4,3) ADD(0,2,6) ADD(0,2,0) ADD(3,4,3) ADD(0,2,0) ADD(6,2,0) ADD(3,4,3) ADD(6,2,0) ADD(6,2,6) ADD(3,4,3) ADD(6,2,6) ADD(0,2,6) break; case LCR_BLOCK_FULL: case LCR_BLOCK_BOTTOM: case LCR_BLOCK_LEFT: case LCR_BLOCK_BOTTOM_LEFT: case LCR_BLOCK_BOTTOM_LEFT_FRONT: case LCR_BLOCK_FULL_ACCEL: case LCR_BLOCK_FULL_FAN: { uint8_t xRight = 6, yTop = 4, zBack = 6 >> (blockType == LCR_BLOCK_BOTTOM_LEFT_FRONT); if (blockType == LCR_BLOCK_BOTTOM || blockType == LCR_BLOCK_BOTTOM_LEFT || blockType == LCR_BLOCK_BOTTOM_LEFT_FRONT) yTop /= 2; if (blockType == LCR_BLOCK_LEFT || blockType == LCR_BLOCK_BOTTOM_LEFT || blockType == LCR_BLOCK_BOTTOM_LEFT_FRONT) xRight /= 2; ADD(0,0,0) ADD(xRight,0,0) ADD(xRight,yTop,0) // front ADD(0,0,0) ADD(xRight,yTop,0) ADD(0,yTop,0) ADD(xRight,0,0) ADD(xRight,0,zBack) ADD(xRight,yTop,zBack) // right ADD(xRight,0,0) ADD(xRight,yTop,zBack) ADD(xRight,yTop,0) ADD(0,0,0) ADD(0,yTop,0) ADD(0,yTop,zBack) // left ADD(0,0,0) ADD(0,yTop,zBack) ADD(0,0,zBack) ADD(0,0,zBack) ADD(0,yTop,zBack) ADD(xRight,yTop,zBack) // back ADD(0,0,zBack) ADD(xRight,yTop,zBack) ADD(xRight,0,zBack) ADD(0,yTop,0) ADD(xRight,yTop,0) ADD(xRight,yTop,zBack) // top ADD(0,yTop,0) ADD(xRight,yTop,zBack) ADD(0,yTop,zBack) ADD(0,0,0) ADD(xRight,0,zBack) ADD(xRight,0,0) // bottom ADD(0,0,0) ADD(0,0,zBack) ADD(xRight,0,zBack) break; } case LCR_BLOCK_RAMP_CURVED_PLAT: ADD(0,0,6) ADD(0,4,5) ADD(0,4,6) // left ADD(6,0,6) ADD(6,4,6) ADD(6,4,5) // right ADD(0,4,5) ADD(6,4,5) ADD(0,4,6) // top ADD(0,4,6) ADD(6,4,5) ADD(6,4,6) // fall through case LCR_BLOCK_RAMP_CURVED: { uint8_t plusZ = blockType == LCR_BLOCK_RAMP_CURVED; ADD(0,0,0) ADD(6,0,0) ADD(0,1,3 + plusZ) // ramp ADD(0,1,3 + plusZ) ADD(6,0,0) ADD(6,1,3 + plusZ) ADD(0,1,3 + plusZ) ADD(6,1,3 + plusZ) ADD(0,2,4 + plusZ) // ramp ADD(0,2,4 + plusZ) ADD(6,1,3 + plusZ) ADD(6,2,4 + plusZ) ADD(0,2,4 + plusZ) ADD(6,2,4 + plusZ) ADD(0,4,5 + plusZ) // ramp ADD(0,4,5 + plusZ) ADD(6,2,4 + plusZ) ADD(6,4,5 + plusZ) ADD(0,0,0) ADD(0,1,3 + plusZ) ADD(0,0,6) // left ADD(0,0,6) ADD(0,1,3 + plusZ) ADD(0,2,4 + plusZ) ADD(0,0,6) ADD(0,2,4 + plusZ) ADD(0,4,5 + plusZ) ADD(6,0,0) ADD(6,0,6) ADD(6,1,3 + plusZ) // right ADD(6,0,6) ADD(6,2,4 + plusZ) ADD(6,1,3 + plusZ) ADD(6,0,6) ADD(6,4,5 + plusZ) ADD(6,2,4 + plusZ) ADD(0,0,6) ADD(0,4,6) ADD(6,0,6) // back ADD(6,0,6) ADD(0,4,6) ADD(6,4,6) ADD(0,0,0) ADD(6,0,6) ADD(6,0,0) // bottom ADD(0,0,0) ADD(0,0,6) ADD(6,0,6) break; } case LCR_BLOCK_RAMP_CURVED_WALL: ADD(0,0,0) ADD(5,0,0) ADD(0,1,3) // ramp ADD(0,1,3) ADD(5,0,0) ADD(5,1,3) ADD(0,1,3) ADD(5,1,3) ADD(0,2,4) // ramp ADD(0,2,4) ADD(5,1,3) ADD(5,2,4) ADD(0,2,4) ADD(5,2,4) ADD(0,4,5) // ramp ADD(0,4,5) ADD(5,2,4) ADD(5,4,5) ADD(0,4,5) ADD(5,4,5) ADD(0,4,6) // top ADD(0,4,6) ADD(5,4,5) ADD(6,4,6) ADD(5,4,5) ADD(6,4,0) ADD(6,4,6) // top ADD(5,4,5) ADD(5,4,0) ADD(6,4,0) ADD(5,4,0) ADD(5,4,5) ADD(5,2,4) // inner side ADD(5,4,0) ADD(5,2,4) ADD(5,1,3) ADD(5,4,0) ADD(5,1,3) ADD(5,0,0) ADD(5,4,0) ADD(5,0,0) ADD(6,4,0) // front ADD(6,4,0) ADD(5,0,0) ADD(6,0,0) ADD(6,4,0) ADD(6,0,0) ADD(6,4,6) // right ADD(6,4,6) ADD(6,0,0) ADD(6,0,6) ADD(0,0,6) ADD(0,4,5) ADD(0,4,6) // left ADD(0,0,6) ADD(0,2,4) ADD(0,4,5) ADD(0,0,6) ADD(0,1,3) ADD(0,2,4) ADD(0,0,6) ADD(0,0,0) ADD(0,1,3) ADD(0,0,6) ADD(0,4,6) ADD(6,0,6) // back ADD(6,0,6) ADD(0,4,6) ADD(6,4,6) ADD(0,0,0) ADD(6,0,6) ADD(6,0,0) // bottom ADD(0,0,0) ADD(0,0,6) ADD(6,0,6) break; case LCR_BLOCK_RAMP: case LCR_BLOCK_RAMP_12: case LCR_BLOCK_RAMP_14: case LCR_BLOCK_RAMP_34: case LCR_BLOCK_RAMP_STEEP: { uint8_t front, top; LCR_rampGetDimensions(blockType,&top,&front); front = 6 - front; ADD(0,0,front) ADD(0,top,6) ADD(0,0,6) // side ADD(6,0,front) ADD(6,0,6) ADD(6,top,6) // side ADD(0,0,front) ADD(6,0,front) ADD(0,top,6) // top ADD(6,0,front) ADD(6,top,6) ADD(0,top,6) // top ADD(0,0,6) ADD(6,top,6) ADD(6,0,6) // back ADD(0,0,6) ADD(0,top,6) ADD(6,top,6) // back ADD(0,0,front) ADD(0,0,6) ADD(6,0,6) // bottom ADD(0,0,front) ADD(6,0,6) ADD(6,0,front) // bottom break; } case LCR_BLOCK_CORNER: case LCR_BLOCK_CORNER_12: { uint8_t right = blockType == LCR_BLOCK_CORNER ? 6 : 3; ADD(0,0,0) ADD(right,0,6) ADD(right,4,6) // front/right ADD(0,0,0) ADD(right,4,6) ADD(0,4,0) // front/right ADD(0,0,0) ADD(0,4,6) ADD(0,0,6) // left ADD(0,0,0) ADD(0,4,0) ADD(0,4,6) // left ADD(right,0,6) ADD(0,0,6) ADD(0,4,6) // back ADD(0,4,6) ADD(right,4,6) ADD(right,0,6) // back ADD(0,4,0) ADD(right,4,6) ADD(0,4,6) // top ADD(0,0,6) ADD(right,0,6) ADD(0,0,0) // bottom break; } case LCR_BLOCK_CORNER_CONVEX: case LCR_BLOCK_CORNER_CONCAVE: { uint8_t mx = blockType == LCR_BLOCK_CORNER_CONVEX ? 4 : 2, mz = blockType == LCR_BLOCK_CORNER_CONVEX ? 2 : 4; ADD(0,0,0) ADD(0,4,6) ADD(0,0,6) // left ADD(0,0,0) ADD(0,4,0) ADD(0,4,6) // left ADD(6,0,6) ADD(0,0,6) ADD(0,4,6) // back ADD(0,4,6) ADD(6,4,6) ADD(6,0,6) // back ADD(0,0,0) ADD(mx,4,mz) ADD(0,4,0) // right ADD(mx,0,mz) ADD(mx,4,mz) ADD(0,0,0) ADD(6,4,6) ADD(mx,4,mz) ADD(6,0,6) ADD(6,0,6) ADD(mx,4,mz) ADD(mx,0,mz) ADD(0,4,0) ADD(mx,4,mz) ADD(0,4,6) // top ADD(0,4,6) ADD(mx,4,mz) ADD(6,4,6) ADD(0,0,0) ADD(0,0,6) ADD(mx,0,mz) // bottom ADD(0,0,6) ADD(6,0,6) ADD(mx,0,mz) break; } case LCR_BLOCK_BUMP: ADD(3,0,0) ADD(6,0,3) ADD(3,1,3) // top ADD(6,0,3) ADD(3,0,6) ADD(3,1,3) ADD(3,0,6) ADD(0,0,3) ADD(3,1,3) ADD(0,0,3) ADD(3,0,0) ADD(3,1,3) ADD(3,0,0) ADD(3,0,6) ADD(6,0,3) // bottom ADD(3,0,6) ADD(3,0,0) ADD(0,0,3) // bottom break; case LCR_BLOCK_RAMP_CORNER: ADD(6,0,6) ADD(0,0,0) ADD(6,4,0) // diagonal ADD(6,0,6) ADD(6,4,0) ADD(6,0,0) // right ADD(0,0,0) ADD(6,0,0) ADD(6,4,0) // front ADD(0,0,0) ADD(6,0,6) ADD(6,0,0) // bottom break; case LCR_BLOCK_HILL: ADD(0,0,0) ADD(6,0,0) ADD(0,2,1) // front ADD(6,0,0) ADD(6,2,1) ADD(0,2,1) ADD(0,2,1) ADD(6,2,1) ADD(0,3,2) // front 2 ADD(6,2,1) ADD(6,3,2) ADD(0,3,2) ADD(0,3,2) ADD(6,3,2) ADD(0,4,4) // front 3 ADD(6,3,2) ADD(6,4,4) ADD(0,4,4) ADD(0,4,4) ADD(6,4,4) ADD(0,4,6) // top ADD(6,4,4) ADD(6,4,6) ADD(0,4,6) ADD(0,0,0) ADD(0,0,6) ADD(6,0,0) // bottom ADD(6,0,0) ADD(0,0,6) ADD(6,0,6) ADD(0,0,6) ADD(0,4,6) ADD(6,4,6) // back ADD(0,0,6) ADD(6,4,6) ADD(6,0,6) ADD(0,0,0) ADD(0,2,1) ADD(0,0,6) // left ADD(0,2,1) ADD(0,3,2) ADD(0,0,6) ADD(0,3,2) ADD(0,4,4) ADD(0,0,6) ADD(0,4,4) ADD(0,4,6) ADD(0,0,6) ADD(6,0,0) ADD(6,0,6) ADD(6,2,1) // right ADD(6,2,1) ADD(6,0,6) ADD(6,3,2) ADD(6,3,2) ADD(6,0,6) ADD(6,4,4) ADD(6,4,4) ADD(6,0,6) ADD(6,4,6) break; default: break; } if (transform) { for (int i = 0; i < *byteCount; ++i) { uint8_t x, y, z, tmp; _LCR_decodeMapBlockCoords(bytes[i],&x,&y,&z); LCR_TRANSFORM_COORDS(transform,x,y,z,6,4) bytes[i] = _LCR_encodeMapBlockCoords(x,y,z); } if (((transform & LCR_BLOCK_TRANSFORM_FLIP_H) == 0) != ((transform & LCR_BLOCK_TRANSFORM_FLIP_V) == 0)) // flip triangles for (int i = 0; i < *byteCount; i += 3) { uint8_t tmp = bytes[i]; bytes[i] = bytes[i + 1]; bytes[i + 1] = tmp; } } #undef ADD } #endif // guard