diff --git a/assets/map/CMakeLists.txt b/assets/map/CMakeLists.txt index 4f7a2b6..03f4a37 100644 --- a/assets/map/CMakeLists.txt +++ b/assets/map/CMakeLists.txt @@ -3,4 +3,4 @@ # This software is released under the MIT License. # https://opensource.org/licenses/MIT -add_asset(MAP untitled.tmx) \ No newline at end of file +add_asset(MAP map.json) \ No newline at end of file diff --git a/assets/map/map.json b/assets/map/map.json new file mode 100644 index 0000000..25251de --- /dev/null +++ b/assets/map/map.json @@ -0,0 +1,6 @@ +{ + "tiles": [ + 1, 1, 1, + 1, 1, 1 + ] +} \ No newline at end of file diff --git a/assets/map/untitled.tmx b/assets/map/untitled.tmx deleted file mode 100644 index 9f69427..0000000 --- a/assets/map/untitled.tmx +++ /dev/null @@ -1,28 +0,0 @@ - - - - - -2,3,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -9,10,11,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -16,17,18,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 - - - diff --git a/src/asset/assettype.h b/src/asset/assettype.h index d1fd812..e9de5e6 100644 --- a/src/asset/assettype.h +++ b/src/asset/assettype.h @@ -9,6 +9,7 @@ #include "type/assetpaletteimage.h" #include "type/assetalphaimage.h" #include "type/assetlanguage.h" +#include "type/assetmap.h" #include typedef enum { @@ -17,6 +18,7 @@ typedef enum { ASSET_TYPE_PALETTE_IMAGE, ASSET_TYPE_ALPHA_IMAGE, ASSET_TYPE_LANGUAGE, + ASSET_TYPE_MAP, ASSET_TYPE_COUNT, } assettype_t; @@ -64,5 +66,12 @@ static const assettypedef_t ASSET_TYPE_DEFINITIONS[ASSET_TYPE_COUNT] = { .header = "DLF", .loadStrategy = ASSET_LOAD_STRAT_CUSTOM, .custom = assetLanguageHandler + }, + + [ASSET_TYPE_MAP] = { + .header = "DMF", + .loadStrategy = ASSET_LOAD_STRAT_ENTIRE, + .dataSize = sizeof(assetmap_t), + .entire = assetMapLoad } }; \ No newline at end of file diff --git a/src/asset/type/CMakeLists.txt b/src/asset/type/CMakeLists.txt index 1b37565..fdf5db0 100644 --- a/src/asset/type/CMakeLists.txt +++ b/src/asset/type/CMakeLists.txt @@ -9,4 +9,5 @@ target_sources(${DUSK_TARGET_NAME} assetalphaimage.c assetpaletteimage.c assetlanguage.c + assetmap.c ) \ No newline at end of file diff --git a/src/asset/type/assetmap.c b/src/asset/type/assetmap.c new file mode 100644 index 0000000..b4d7063 --- /dev/null +++ b/src/asset/type/assetmap.c @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "asset/asset.h" +#include "assert/assert.h" +#include "util/memory.h" + +errorret_t assetMapLoad(void *data, void *output) { + assertNotNull(data, "Data cannot be NULL"); + assertNotNull(output, "Output cannot be NULL"); + + assetmap_t *mapData = (assetmap_t *)data; + assetmapmodel_t *mesh = (assetmapmodel_t *)output; + + memoryCopy( + mesh, + &mapData->models[0], + sizeof(assetmapmodel_t) + ); + + meshInit( + &mesh->mesh, + MESH_PRIMITIVE_TRIANGLES, + mesh->vertexCount, + mesh->vertices + ); + + errorOk(); +} \ No newline at end of file diff --git a/src/asset/type/assetmap.h b/src/asset/type/assetmap.h new file mode 100644 index 0000000..4603c2d --- /dev/null +++ b/src/asset/type/assetmap.h @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "error/error.h" +#include "rpg/world/map.h" +#include "display/mesh/mesh.h" + +typedef struct { + uint32_t vertexCount; + meshvertex_t vertices[36]; + mesh_t mesh; +} assetmapmodel_t; + +#pragma pack(push, 1) +typedef struct { + uint32_t tileCount; + uint8_t modelCount; + tile_t tiles[CHUNK_TILE_COUNT]; + assetmapmodel_t models[1]; +} assetmap_t; +#pragma pack(pop) + +/** + * Loads a map asset from the given data pointer into the output map structure. + * + * @param data Pointer to the raw assetmap_t data. + * @param output Pointer to the map_t to load the map into. + * @return An error code. + */ +errorret_t assetMapLoad(void *data, void *output); \ No newline at end of file diff --git a/src/rpg/entity/entity.c b/src/rpg/entity/entity.c index be46056..7db871e 100644 --- a/src/rpg/entity/entity.c +++ b/src/rpg/entity/entity.c @@ -81,9 +81,11 @@ void entityWalk(entity_t *entity, const entitydir_t direction) { // Check one level down for walkable tile (stairs down) worldpos_t belowPos = newPos; belowPos.z -= 1; - tile = mapGetTile(belowPos); - - if(tile != TILE_NULL) newPos.z -= 1; + tile_t belowTile = mapGetTile(belowPos); + + if(belowTile == TILE_STAIRS_UP) { + tile = TILE_STAIRS_DOWN;// Mark current as stairs down + } } // Tile walkable? @@ -98,11 +100,19 @@ void entityWalk(entity_t *entity, const entitydir_t direction) { return;// Blocked } while(++other, other < &ENTITIES[ENTITY_COUNT]); + entity->lastPosition = entity->position; entity->position = newPos; entity->animation = ENTITY_ANIM_WALK; entity->animTime = ENTITY_ANIM_WALK_DURATION;// TODO: Running vs walking // We are comitting, we can run effects here. + if(tile == TILE_STAIRS_DOWN) { + // Moving down a level + entity->position.z -= 1; + } else if(tile == TILE_STAIRS_UP) { + // Moving up a level + entity->position.z += 1; + } } entity_t * entityGetAt(const worldpos_t position) { diff --git a/src/rpg/entity/entity.h b/src/rpg/entity/entity.h index 8290422..0ae2184 100644 --- a/src/rpg/entity/entity.h +++ b/src/rpg/entity/entity.h @@ -23,6 +23,7 @@ typedef struct entity_s { // Movement entitydir_t direction; worldpos_t position; + worldpos_t lastPosition; entityanim_t animation; float_t animTime; diff --git a/src/rpg/world/map.c b/src/rpg/world/map.c index 125c139..92e3980 100644 --- a/src/rpg/world/map.c +++ b/src/rpg/world/map.c @@ -128,12 +128,25 @@ void mapChunkLoad(chunk_t* chunk) { memoryZero(chunk->tiles, sizeof(tile_t) * CHUNK_TILE_COUNT); // 3x3 test walkable area - for(int y = 0; y <= 3; y++) { - for(int x = 0; x <= 3; x++) { + chunktileindex_t x, y, z; + + z = 0; + for(y = 0; y <= 3; y++) { + for(x = 0; x <= 3; x++) { chunktileindex_t tileIndex = (y * CHUNK_WIDTH) + x; chunk->tiles[tileIndex] = TILE_WALKABLE; } } + + x = 3, y = 3; + chunk->tiles[(z * CHUNK_WIDTH * CHUNK_HEIGHT) + (y * CHUNK_WIDTH) + x] = TILE_STAIRS_UP; + + // x = 3, y = 2, z = 1; + // chunk->tiles[(z * CHUNK_WIDTH * CHUNK_HEIGHT) + (y * CHUNK_WIDTH) + x] = TILE_WALKABLE; + // x = 2, y = 2, z = 1; + // chunk->tiles[(z * CHUNK_WIDTH * CHUNK_HEIGHT) + (y * CHUNK_WIDTH) + x] = TILE_WALKABLE; + // x = 4, y = 2, z = 1; + // chunk->tiles[(z * CHUNK_WIDTH * CHUNK_HEIGHT) + (y * CHUNK_WIDTH) + x] = TILE_WALKABLE; } chunkindex_t mapGetChunkIndexAt(const chunkpos_t position) { diff --git a/src/rpg/world/tile.c b/src/rpg/world/tile.c index 09fe756..3e1cac1 100644 --- a/src/rpg/world/tile.c +++ b/src/rpg/world/tile.c @@ -8,5 +8,9 @@ #include "tile.h" bool_t tileIsWalkable(const tile_t tile) { - return tile == TILE_WALKABLE; + return ( + tile == TILE_WALKABLE || + tile == TILE_STAIRS_UP || + tile == TILE_STAIRS_DOWN + ); } \ No newline at end of file diff --git a/src/rpg/world/tile.h b/src/rpg/world/tile.h index bbabcf9..367ddfc 100644 --- a/src/rpg/world/tile.h +++ b/src/rpg/world/tile.h @@ -13,6 +13,8 @@ typedef uint8_t tile_t; #define TILE_NULL 0 #define TILE_WALKABLE 1 +#define TILE_STAIRS_UP 2 +#define TILE_STAIRS_DOWN 3 /** * Returns whether or not the given tile is walkable. diff --git a/src/rpg/world/worldpos.h b/src/rpg/world/worldpos.h index 401f92d..540eb2b 100644 --- a/src/rpg/world/worldpos.h +++ b/src/rpg/world/worldpos.h @@ -21,7 +21,7 @@ typedef int16_t worldunit_t; typedef int16_t chunkunit_t; typedef int16_t chunkindex_t; -typedef uint8_t chunktileindex_t; +typedef uint32_t chunktileindex_t; typedef int32_t worldunits_t; typedef int32_t chunkunits_t; diff --git a/src/scene/scene/scenemap.c b/src/scene/scene/scenemap.c index f71cc71..530da68 100644 --- a/src/scene/scene/scenemap.c +++ b/src/scene/scene/scenemap.c @@ -9,6 +9,7 @@ #include "scene/scenedata.h" #include "display/spritebatch.h" #include "assert/assert.h" +#include "asset/asset.h" #include "rpg/entity/entity.h" #include "rpg/world/map.h" #include "display/screen.h" @@ -16,6 +17,7 @@ #include "util/memory.h" #define TILE_SIZE 16 +assetmapmodel_t mesh; errorret_t sceneMapInit(scenedata_t *data) { // Init the camera. @@ -34,6 +36,8 @@ errorret_t sceneMapInit(scenedata_t *data) { ); data->sceneMap.camera.lookatPixelPerfect.pixelsPerUnit = 1.0f; + + errorChain(assetLoad("map/map.dmf", &mesh)); errorOk(); } @@ -61,15 +65,18 @@ void sceneMapEntityGetPosition(const entity_t *entity, vec3 outPosition) { float_t animPercentage = entity->animTime / ENTITY_ANIM_WALK_DURATION; // Get facing rel, we know we moved from the inverse direction. - worldunits_t x, y; - entityDirGetRelative(entity->direction, &x, &y); - x = -x, y = -y; // Add tile size times percentage to posMin/max vec3 offset = { - x * TILE_SIZE * animPercentage, - y * TILE_SIZE * animPercentage, - 0.0f + ( + (float_t)entity->position.x - (float_t)entity->lastPosition.x + ) * TILE_SIZE * -animPercentage, + ( + (float_t)entity->position.y - (float_t)entity->lastPosition.y + ) * TILE_SIZE * -animPercentage, + ( + (float_t)entity->position.z - (float_t)entity->lastPosition.z + ) * TILE_SIZE * -animPercentage }; glm_vec3_add(outPosition, offset, outPosition); break; @@ -107,7 +114,10 @@ void sceneMapRender(scenedata_t *data) { cameraPushMatrix(&data->sceneMap.camera); // Render map probably. - sceneMapRenderMap(); + // sceneMapRenderMap(); + + textureBind(NULL); + meshDraw(&mesh.mesh, -1, -1); // Render ents entity_t *ent = ENTITIES; @@ -125,11 +135,10 @@ void sceneMapRenderEntity(entity_t *entity) { if(entity->type == ENTITY_TYPE_NULL) return; - vec3 posCenter, posMin, posMax; - vec3 halfSize = { TILE_SIZE / 2.0f, TILE_SIZE / 2.0f, TILE_SIZE / 2.0f }; - sceneMapEntityGetPosition(entity, posCenter); - glm_vec3_sub(posCenter, halfSize, posMin); - glm_vec3_add(posCenter, halfSize, posMax); + vec3 posMin, posMax; + vec3 size = { TILE_SIZE, TILE_SIZE, TILE_SIZE }; + sceneMapEntityGetPosition(entity, posMin); + glm_vec3_add(posMin, size, posMax); // TEST: Change color depending on dir. color_t testColor; @@ -167,11 +176,6 @@ void sceneMapRenderMap() { min[1] = chunk->position.y * CHUNK_HEIGHT * TILE_SIZE; min[2] = chunk->position.z * CHUNK_DEPTH * TILE_SIZE; - // center tile - min[0] -= TILE_SIZE / 2.0f; - min[1] -= TILE_SIZE / 2.0f; - min[2] -= TILE_SIZE / 2.0f; - max[0] = min[0] + (CHUNK_WIDTH * TILE_SIZE); max[1] = min[1] + (CHUNK_HEIGHT * TILE_SIZE); max[2] = min[2]; @@ -195,4 +199,5 @@ void sceneMapRenderMap() { } void sceneMapDispose(scenedata_t *data) { + meshDispose(&mesh.mesh); } diff --git a/src/ui/uidebug.c b/src/ui/uidebug.c index 76f061e..b54af32 100644 --- a/src/ui/uidebug.c +++ b/src/ui/uidebug.c @@ -18,7 +18,7 @@ bool_t UI_DEBUG_DRAW = true; void uiDebugRender(const tileset_t *tileset, texture_t *texture) { if(!UI_DEBUG_DRAW) return; - char_t buffer[96]; + char_t buffer[128]; color_t color; int32_t w, h, hOffset = 0; @@ -64,6 +64,7 @@ void uiDebugRender(const tileset_t *tileset, texture_t *texture) { ); hOffset += h; + // Player position entity_t *player = NULL; for(uint8_t i = 0; i < ENTITY_COUNT; i++) { @@ -77,9 +78,10 @@ void uiDebugRender(const tileset_t *tileset, texture_t *texture) { snprintf( buffer, sizeof(buffer), - "%d,%d/%d/%d", + "%d,%d,%d/%d/%d", player->position.x, player->position.y, + player->position.z, (int32_t)player->direction, (int32_t)player->animation ); @@ -87,7 +89,7 @@ void uiDebugRender(const tileset_t *tileset, texture_t *texture) { uiTextMeasure(buffer, tileset, &w, &h); uiTextDraw( SCREEN.width - w, hOffset, - buffer, COLOR_WHITE, tileset, texture + buffer, COLOR_GREEN, tileset, texture ); hOffset += h; diff --git a/tools/assetstool/processmap.py b/tools/assetstool/processmap.py index 64b9f7d..25b8f45 100644 --- a/tools/assetstool/processmap.py +++ b/tools/assetstool/processmap.py @@ -1,143 +1,124 @@ +import struct import sys import os +import json from args import args -from xml.etree import ElementTree as ET -from processtileset import processTileset from assetcache import assetCache, assetGetCache from assethelpers import getAssetRelativePath +CHUNK_WIDTH = 16 +CHUNK_HEIGHT = 16 +CHUNK_DEPTH = 32 +CHUNK_TILE_COUNT = CHUNK_WIDTH * CHUNK_HEIGHT * CHUNK_DEPTH +TILE_SIZE = 16.0 + +def createQuadForTile(model, tileIndex, x=0, y=0, z=0): + # Only append vertices if z == 0 + if z != 0: + return + # Determine color for checkerboard pattern + color = (255,255,255) if (x + y) % 2 == 0 else (0,0,0) + # Use TILE_SIZE for positions + px = x * TILE_SIZE + py = y * TILE_SIZE + pz = z * TILE_SIZE + quad_vertices = [ + {'position': (px, py, pz), 'color': color, 'uv': (0,0)}, # 0,0 + {'position': (px + TILE_SIZE, py, pz), 'color': color, 'uv': (1,0)}, # 1,0 + {'position': (px + TILE_SIZE, py + TILE_SIZE, pz), 'color': color, 'uv': (1,1)}, # 1,1 + {'position': (px, py, pz), 'color': color, 'uv': (0,0)}, # 0,0 (repeat) + {'position': (px + TILE_SIZE, py + TILE_SIZE, pz), 'color': color, 'uv': (1,1)}, # 1,1 (repeat) + {'position': (px, py + TILE_SIZE, pz), 'color': color, 'uv': (0,1)} # 0,1 + ] + base = len(model['vertices']) + quad_indices = [base, base+1, base+2, base+3, base+4, base+5] + model['vertices'].extend(quad_vertices) + model['indices'].extend(quad_indices) + model['vertexCount'] = len(model['vertices']) + model['indexCount'] = len(model['indices']) + def processMap(asset): cache = assetGetCache(asset['path']) if cache is not None: return cache - - # Load the TMX file - tree = ET.parse(asset['path']) - root = tree.getroot() - - # Root needs to be "map" element. - if root.tag != 'map': - print(f"Error: TMX file {asset['path']} does not have a root element") - sys.exit(1) - - # Root needs to be orientation="orthogonal" - if 'orientation' not in root.attrib or root.attrib['orientation'] != 'orthogonal': - print(f"Error: TMX file {asset['path']} does not have orientation='orthogonal'") - sys.exit(1) - # Extract width, height, tilewidth, tileheight attributes - if 'width' not in root.attrib or 'height' not in root.attrib or 'tilewidth' not in root.attrib or 'tileheight' not in root.attrib: - print(f"Error: TMX file {asset['path']} is missing required attributes (width, height, tilewidth, tileheight)") - sys.exit(1) - - mapWidth = int(root.attrib['width']) - mapHeight = int(root.attrib['height']) - tileWidth = int(root.attrib['tilewidth']) - tileHeight = int(root.attrib['tileheight']) - - # Find all tileset elements - tilesets = [] - for tilesetElement in root.findall('tileset'): - # Tileset must have a source attribute - if 'source' not in tilesetElement.attrib: - print(f"Error: element in {asset['path']} is missing a source attribute") - sys.exit(1) - # Must have a firstgid attribute - if 'firstgid' not in tilesetElement.attrib: - print(f"Error: element in {asset['path']} is missing a firstgid attribute") - sys.exit(1) - - firstGid = int(tilesetElement.attrib['firstgid']) - source = tilesetElement.attrib['source'] - - # Get source path relative to the tmx file's working directory. - # Needs normalizing also since ".." is often used. - source = os.path.normpath(os.path.join(os.path.dirname(asset['path']), source)) - tileset = processTileset({ 'path': source, 'type': 'tileset', 'options': {} }) - - tilesets.append({ - 'firstGid': firstGid, - 'source': source, - 'tileset': tileset - }) - - # Sort tilesets by firstGid, highest first - tilesets.sort(key=lambda x: x['firstGid'], reverse=True) - - # Layer types - # objectLayers = [] # Not implemented - tileLayers = [] - for layerElement in root.findall('layer'): - # Assume tile layer for now - # Must have id, name, width, height attributes - if 'id' not in layerElement.attrib or 'name' not in layerElement.attrib or 'width' not in layerElement.attrib or 'height' not in layerElement.attrib: - print(f"Error: element in {asset['path']} is missing required attributes (id, name, width, height)") - sys.exit(1) - - id = int(layerElement.attrib['id']) - name = layerElement.attrib['name'] - width = int(layerElement.attrib['width']) - height = int(layerElement.attrib['height']) - - # Need exactly one data element - dataElements = layerElement.findall('data') - if len(dataElements) != 1: - print(f"Error: element in {asset['path']} must have exactly one child element") - sys.exit(1) - - # Get text, remove whitespace, split by comman and convert to int - dataElement = dataElements[0] - if dataElement.attrib.get('encoding', '') != 'csv': - print(f"Error: element in {asset['path']} must have encoding='csv'") - sys.exit(1) - - dataText = dataElement.text.strip() - data = [int(gid) for gid in dataText.split(',') if gid.strip().isdigit()] - - # Should be exactly width * height entries - if len(data) != width * height: - print(f"Error: element in {asset['path']} has {len(data)} entries but expected {width * height} (width * height)") - sys.exit(1) - - tileLayers.append({ - 'id': id, - 'name': name, - 'width': width, - 'height': height, - 'data': data, - }) - - # Now we have our layers all parsed out. - data = bytearray() - data += b'DRM' # Dusk RPG Map - data += mapWidth.to_bytes(4, 'little') # Map width in tiles - data += mapHeight.to_bytes(4, 'little') # Map height in tiles - data += len(tilesets).to_bytes(4, 'little') # Number of tilesets - data += len(tileLayers).to_bytes(4, 'little') # Number of layers + # Read input file as JSON + with open(asset['path'], 'r') as f: + inData = json.load(f) - # For each layer... - for layer in tileLayers: - for gid in layer['data']: - data += gid.to_bytes(4, 'little') # Tileset index + tileIndexes = inData['tiles'] - # For each tileset - for tileset in tilesets: - data += tileset['firstGid'].to_bytes(4, 'little') # First GID - data += tileset['tileset']['tilesetIndex'].to_bytes(4, 'little') # Tileset index + # Create output object 'map' with default tile indexes and models array + map = { + 'tiles': [0] * CHUNK_TILE_COUNT, + 'models': [] + } + # Create a simple 3D model object + model = { + 'vertices': [], + 'indices': [], + 'vertexCount': 0, + 'indexCount': 0 + } + + # Append the model to map.models + map['models'].append(model) + + for i, tile in enumerate(tileIndexes): + # Calculate x, y, z from i + x = i % CHUNK_WIDTH + y = (i // CHUNK_WIDTH) % CHUNK_HEIGHT + z = i // (CHUNK_WIDTH * CHUNK_HEIGHT) + createQuadForTile(model, tile, x, y, z) + + # Generate binary buffer for efficient output + buffer = bytearray() + buffer.extend(b'DMF')# Header + buffer.extend(len(map['tiles']).to_bytes(4, 'little')) # Number of tiles + buffer.extend(len(map['models']).to_bytes(1, 'little')) # Number of models + + # Buffer tile data as array of uint8_t + for tileIndex in map['tiles']: + buffer.append(tileIndex.to_bytes(1, 'little')[0]) + + # For each model + for model in map['models']: + # Write vertex count and index count + buffer.extend(model['vertexCount'].to_bytes(4, 'little')) + # buffer.extend(model['indexCount'].to_bytes(4, 'little')) + # For each vertex + for vertex in model['vertices']: + # This is not tightly packed in memory. + # R G B A U V X Y Z + # Color is 4 bytes (RGBA) + # Rest is floats + r, g, b = vertex['color'] + a = 255 + buffer.extend(r.to_bytes(1, 'little')) + buffer.extend(g.to_bytes(1, 'little')) + buffer.extend(b.to_bytes(1, 'little')) + buffer.extend(a.to_bytes(1, 'little')) + u, v = vertex['uv'] + buffer.extend(bytearray(struct.pack('