/* Copyright (c) 2023 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 "dynamictypes/DataSerializer.h" #include "gamemath/Grid.h" #include "tileset/TileMapEditData.h" #include "utilities/IOUtilities.h" #include static DataValueSchema * getSchemaInUse(HashTable * schemasInUse, uint32_t key) { DataValueSchema ** entry = HashTable_get(schemasInUse, HashTable_uint32Key(key)); if (entry != NULL) { return *entry; } return NULL; } static TileMapEditData * TileMapEditData_createNoSchemas(TileMapID identifier, const char * name, Vector2u size, Vector2i origin, unsigned int layerCount, TileMapEditLayer * layers, bool copy) { TileMapEditData * tileMap = malloc(sizeof(*tileMap)); tileMap->identifier = identifier; tileMap->name = strdup_nullSafe(name); tileMap->size = size; tileMap->origin = origin; tileMap->layerCount = layerCount; if (layerCount == 0) { tileMap->layers = NULL; } else { tileMap->layers = malloc(layerCount * sizeof(*tileMap->layers)); for (unsigned int layerIndex = 0; layerIndex < layerCount; layerIndex++) { if (copy) { tileMap->layers[layerIndex] = TileMapEditLayer_copy(&layers[layerIndex]); } else { tileMap->layers[layerIndex] = layers[layerIndex]; } } } tileMap->schemasInUse = NULL; tileMap->metadata = NULL; return tileMap; } TileMapEditData * TileMapEditData_create(TileMapID identifier, const char * name, TilesetEditData * tileset, Vector2u size, Vector2i origin, unsigned int layerCount, TileMapEditLayer * layers, bool copy) { TileMapEditData * tileMap = TileMapEditData_createNoSchemas(identifier, name, size, origin, layerCount, layers, copy); tileMap->schemasInUse = HashTable_create(sizeof(DataValueSchema *)); DataValueSchema * tileMapSchema = TilesetEditData_getMetadataSchema(tileset, tileset->tileMapSchemaID); if (tileMapSchema == NULL) { tileMap->metadata = NULL; } else { tileMapSchema = DataValueSchema_copy(tileMapSchema); tileMap->metadata = DataValueSchema_createBlankValue(tileMapSchema, DATA_TYPE_HASH_TABLE).value.hashTable; } HashTable_set(tileMap->schemasInUse, HashTable_uint32Key(TILE_MAP_SCHEMA_KEY_TILE_MAP), &tileMapSchema); DataValueSchema * tileMapLayerSchema = TilesetEditData_getMetadataSchema(tileset, tileset->tileMapLayerSchemaID); if (tileMapLayerSchema != NULL) { tileMapLayerSchema = DataValueSchema_copy(tileMapLayerSchema); } HashTable_set(tileMap->schemasInUse, HashTable_uint32Key(TILE_MAP_SCHEMA_KEY_TILE_MAP_LAYER), &tileMapLayerSchema); return tileMap; } static bool copyDataValueSchema(HashTable * hashTable, HashTable_key key, void * value, void * context) { DataValueSchema ** schema = value; if (*schema != NULL) { *schema = DataValueSchema_copy(*schema); } return true; } TileMapEditData * TileMapEditData_copy(TileMapEditData * tileMap) { TileMapEditData * copy = TileMapEditData_createNoSchemas(tileMap->identifier, tileMap->name, tileMap->size, tileMap->origin, tileMap->layerCount, tileMap->layers, true); copy->schemasInUse = HashTable_copy(tileMap->schemasInUse); HashTable_foreach(copy->schemasInUse, copyDataValueSchema, NULL); if (tileMap->metadata != NULL) { copy->metadata = hashCopy(tileMap->metadata); } return copy; } static bool disposeDataValueSchema(HashTable * hashTable, HashTable_key key, void * value, void * context) { DataValueSchema * schema = *(DataValueSchema **) value; if (schema != NULL) { DataValueSchema_dispose(schema); } return true; } static void disposeSchemasInUse(HashTable * hashTable) { HashTable_foreach(hashTable, disposeDataValueSchema, NULL); HashTable_dispose(hashTable); } void TileMapEditData_dispose(TileMapEditData * tileMap) { free(tileMap->name); for (unsigned int layerIndex = 0; layerIndex < tileMap->layerCount; layerIndex++) { free(tileMap->layers[layerIndex].name); unsigned int tileCount = tileMap->layers[layerIndex].grid.size.x * tileMap->layers[layerIndex].grid.size.y; for (unsigned int tileIndex = 0; tileIndex < tileCount; tileIndex++) { if (tileMap->layers[layerIndex].grid.tileInstances[tileIndex].metadata != NULL) { hashDispose(tileMap->layers[layerIndex].grid.tileInstances[tileIndex].metadata); } } free(tileMap->layers[layerIndex].grid.tileInstances); if (tileMap->layers[layerIndex].metadata != NULL) { hashDispose(tileMap->layers[layerIndex].metadata); } } free(tileMap->layers); disposeSchemasInUse(tileMap->schemasInUse); if (tileMap->metadata != NULL) { hashDispose(tileMap->metadata); } free(tileMap); } static bool writeSchemaInUse(HashTable * hashTable, HashTable_key key, void * value, void * contextUntyped) { SerializationContext * context = contextUntyped; DataValueSchema ** schema = value; if (*schema != NULL) { call_virtual(beginStructure, context, NULL); call_virtual(writeUInt32, context, "id", key.data.uint32); DataValueSchema_serializeMinimal(*schema, context); call_virtual(endStructure, context); } return true; } void TileMapEditData_serializeMinimal(TileMapEditData * tileMap, compat_type(SerializationContext *) serializationContext) { SerializationContext * context = serializationContext; call_virtual(writeUInt32, context, "id", tileMap->identifier); call_virtual(writeString, context, "name", tileMap->name); call_virtual(writeUInt16, context, "size_x", tileMap->size.x); call_virtual(writeUInt16, context, "size_y", tileMap->size.y); call_virtual(writeInt16, context, "origin_x", tileMap->origin.x); call_virtual(writeInt16, context, "origin_y", tileMap->origin.y); call_virtual(writeUInt16, context, "schema_format_version", DATA_VALUE_SCHEMA_FORMAT_VERSION); call_virtual(beginArray, context, "metadata_schemas"); HashTable_foreach(tileMap->schemasInUse, writeSchemaInUse, context); call_virtual(endArray, context); call_virtual(beginArray, context, "layers"); DataValueSchema * tileMapLayerSchema = getSchemaInUse(tileMap->schemasInUse, TILE_MAP_SCHEMA_KEY_TILE_MAP_LAYER); for (unsigned int layerIndex = 0; layerIndex < tileMap->layerCount; layerIndex++) { call_virtual(beginStructure, context, NULL); call_virtual(writeString, context, "name", tileMap->layers[layerIndex].name); call_virtual(writeInt16, context, "offset_x", tileMap->layers[layerIndex].offset.x); call_virtual(writeInt16, context, "offset_y", tileMap->layers[layerIndex].offset.y); call_virtual(writeUInt16, context, "size_x", tileMap->layers[layerIndex].grid.size.x); call_virtual(writeUInt16, context, "size_y", tileMap->layers[layerIndex].grid.size.y); call_virtual(writeBoolean, context, "visible", tileMap->layers[layerIndex].visible); call_virtual(writeBoolean, context, "write_enabled", tileMap->layers[layerIndex].writeEnabled); call_virtual(writeFloat, context, "opacity", tileMap->layers[layerIndex].opacity); call_virtual(beginArray, context, "tile_instances"); unsigned int tileCount = tileMap->layers[layerIndex].grid.size.x * tileMap->layers[layerIndex].grid.size.y; for (unsigned int tileIndex = 0; tileIndex < tileCount; tileIndex++) { call_virtual(beginStructure, context, NULL); call_virtual(writeUInt32, context, "tile_id", tileMap->layers[layerIndex].grid.tileInstances[tileIndex].tileID); DataValueSchema * tileInstanceSchema = getSchemaInUse(tileMap->schemasInUse, tileMap->layers[layerIndex].grid.tileInstances[tileIndex].tileID); if (tileInstanceSchema != NULL) { DataValue metadataValue = valueCreateHashTable(tileMap->layers[layerIndex].grid.tileInstances[tileIndex].metadata, false, false); DataValue_serializeWithSchema(&metadataValue, tileInstanceSchema, "metadata", context); } call_virtual(endStructure, context); } call_virtual(endArray, context); if (tileMapLayerSchema != NULL) { DataValue metadataValue = valueCreateHashTable(tileMap->layers[layerIndex].metadata, false, false); DataValue_serializeWithSchema(&metadataValue, tileMapLayerSchema, "metadata", context); } call_virtual(endStructure, context); } call_virtual(endArray, context); DataValueSchema * tileMapSchema = getSchemaInUse(tileMap->schemasInUse, TILE_MAP_SCHEMA_KEY_TILE_MAP); if (tileMapSchema != NULL) { DataValue metadataValue = valueCreateHashTable(tileMap->metadata, false, false); DataValue_serializeWithSchema(&metadataValue, tileMapSchema, "metadata", context); } } void TileMapEditData_serialize(TileMapEditData * tileMap, compat_type(SerializationContext *) serializationContext) { serialize_implementation_v2(TileMapEditData, tileMap, TILE_MAP_EDIT_DATA); } TileMapEditData * TileMapEditData_deserializeMinimal(compat_type(DeserializationContext *) deserializationContext, uint16_t formatVersion) { DeserializationContext * context = deserializationContext; if (context->status != SERIALIZATION_ERROR_OK) { return NULL; } if (formatVersion > TILE_MAP_EDIT_DATA_FORMAT_VERSION) { context->status = SERIALIZATION_ERROR_FORMAT_VERSION_TOO_NEW; return NULL; } TileMapID identifier = call_virtual(readUInt32, context, "id"); const char * name = call_virtual(readString, context, "name"); Vector2u size; size.x = call_virtual(readUInt16, context, "size_x"); size.y = call_virtual(readUInt16, context, "size_y"); if (size.x == 0 || size.x > TILE_MAP_DIMENSION_MAX || size.y == 0 || size.y > TILE_MAP_DIMENSION_MAX) { return NULL; } Vector2i origin; origin.x = call_virtual(readInt16, context, "origin_x"); origin.y = call_virtual(readInt16, context, "origin_y"); uint16_t schemaFormatVersion = call_virtual(readUInt16, context, "schema_format_version"); HashTable * schemasInUse = HashTable_create(sizeof(DataValueSchema *)); unsigned int schemaCount = call_virtual(beginArray, context, "metadata_schemas"); for (unsigned int schemaIndex = 0; schemaIndex < schemaCount; schemaIndex++) { call_virtual(beginStructure, context, NULL); uint32_t schemaKey = call_virtual(readUInt32, context, "id"); DataValueSchema * schema = DataValueSchema_deserializeMinimal(context, schemaFormatVersion); if (schema == NULL || HashTable_get(schemasInUse, HashTable_uint32Key(schemaKey)) != NULL) { disposeSchemasInUse(schemasInUse); return NULL; } HashTable_set(schemasInUse, HashTable_uint32Key(schemaKey), &schema); call_virtual(endStructure, context); } call_virtual(endArray, context); unsigned int layerCount = call_virtual(beginArray, context, "layers"); if (layerCount > TILE_MAP_LAYER_COUNT_MAX) { disposeSchemasInUse(schemasInUse); return NULL; } DataHashTable * tileMapMetadata = NULL; DataValueSchema * tileMapLayerSchema = getSchemaInUse(schemasInUse, TILE_MAP_SCHEMA_KEY_TILE_MAP_LAYER); TileMapEditLayer layers[layerCount]; memset(layers, 0, sizeof(layers)); for (unsigned int layerIndex = 0; layerIndex < layerCount; layerIndex++) { call_virtual(beginStructure, context, NULL); layers[layerIndex].name = strdup_nullSafe(call_virtual(readString, context, "name")); layers[layerIndex].offset.x = call_virtual(readInt16, context, "offset_x"); layers[layerIndex].offset.y = call_virtual(readInt16, context, "offset_y"); layers[layerIndex].grid.size.x = call_virtual(readUInt16, context, "size_x"); layers[layerIndex].grid.size.y = call_virtual(readUInt16, context, "size_y"); if (layers[layerIndex].grid.size.x == 0 || layers[layerIndex].grid.size.x > TILE_MAP_DIMENSION_MAX || layers[layerIndex].grid.size.y == 0 || layers[layerIndex].grid.size.y > TILE_MAP_DIMENSION_MAX) { layers[layerIndex].grid.size.x = layers[layerIndex].grid.size.y = 0; goto failure; } layers[layerIndex].visible = call_virtual(readBoolean, context, "visible"); layers[layerIndex].writeEnabled = call_virtual(readBoolean, context, "write_enabled"); layers[layerIndex].opacity = call_virtual(readFloat, context, "opacity"); unsigned int tileCount = call_virtual(beginArray, context, "tile_instances"); if (tileCount != layers[layerIndex].grid.size.x * layers[layerIndex].grid.size.y) { goto failure; } layers[layerIndex].grid.tileInstances = calloc(tileCount, sizeof(*layers[layerIndex].grid.tileInstances)); for (unsigned int tileIndex = 0; tileIndex < tileCount; tileIndex++) { call_virtual(beginStructure, context, NULL); layers[layerIndex].grid.tileInstances[tileIndex].tileID = call_virtual(readUInt32, context, "tile_id"); DataValueSchema * tileInstanceSchema = getSchemaInUse(schemasInUse, layers[layerIndex].grid.tileInstances[tileIndex].tileID); if (tileInstanceSchema != NULL) { DataValue metadataValue = DataValue_deserializeWithSchema(tileInstanceSchema, "metadata", DATA_TYPE_HASH_TABLE, context, false); if (metadataValue.type != DATA_TYPE_HASH_TABLE) { goto failure; } layers[layerIndex].grid.tileInstances[tileIndex].metadata = metadataValue.value.hashTable; } call_virtual(endStructure, context); } call_virtual(endArray, context); if (tileMapLayerSchema != NULL) { DataValue metadataValue = DataValue_deserializeWithSchema(tileMapLayerSchema, "metadata", DATA_TYPE_HASH_TABLE, context, false); if (metadataValue.type != DATA_TYPE_HASH_TABLE) { goto failure; } layers[layerIndex].metadata = metadataValue.value.hashTable; } call_virtual(endStructure, context); } call_virtual(endArray, context); DataValueSchema * tileMapSchema = getSchemaInUse(schemasInUse, TILE_MAP_SCHEMA_KEY_TILE_MAP); if (tileMapSchema != NULL) { DataValue metadataValue = DataValue_deserializeWithSchema(tileMapSchema, "metadata", DATA_TYPE_HASH_TABLE, context, false); if (metadataValue.type != DATA_TYPE_HASH_TABLE) { goto failure; } tileMapMetadata = metadataValue.value.hashTable; } if (context->status != SERIALIZATION_ERROR_OK) { failure: for (unsigned int layerIndex = 0; layerIndex < layerCount; layerIndex++) { free(layers[layerIndex].name); unsigned int tileCount = layers[layerIndex].grid.size.x * layers[layerIndex].grid.size.y; for (unsigned int tileIndex = 0; tileIndex < tileCount; tileIndex++) { if (layers[layerIndex].grid.tileInstances[tileIndex].metadata != NULL) { hashDispose(layers[layerIndex].grid.tileInstances[tileIndex].metadata); } } free(layers[layerIndex].grid.tileInstances); if (layers[layerIndex].metadata != NULL) { hashDispose(layers[layerIndex].metadata); } } if (tileMapMetadata != NULL) { hashDispose(tileMapMetadata); } disposeSchemasInUse(schemasInUse); return NULL; } TileMapEditData * tileMap = TileMapEditData_createNoSchemas(identifier, name, size, origin, layerCount, layers, false); tileMap->schemasInUse = schemasInUse; tileMap->metadata = tileMapMetadata; return tileMap; } TileMapEditData * TileMapEditData_deserialize(compat_type(DeserializationContext *) deserializationContext) { deserialize_implementation_v2(TileMapEditData, TILE_MAP_EDIT_DATA); } void TileMapEditData_addLayer(TileMapEditData * tileMap, TileMapEditLayer layer) { TileMapEditData_insertLayer(tileMap, tileMap->layerCount, layer); } void TileMapEditData_insertLayer(TileMapEditData * tileMap, unsigned int layerIndex, TileMapEditLayer layer) { tileMap->layers = realloc(tileMap->layers, (tileMap->layerCount + 1) * sizeof(*tileMap->layers)); for (unsigned int layerIndex2 = tileMap->layerCount; layerIndex2 > layerIndex; layerIndex2--) { tileMap->layers[layerIndex2] = tileMap->layers[layerIndex2 - 1]; } tileMap->layers[layerIndex] = layer; tileMap->layerCount++; } void TileMapEditData_removeLayerAtIndex(TileMapEditData * tileMap, unsigned int layerIndex) { if (layerIndex < tileMap->layerCount) { free(tileMap->layers[layerIndex].name); unsigned int tileCount = tileMap->layers[layerIndex].grid.size.x * tileMap->layers[layerIndex].grid.size.y; for (unsigned int tileIndex = 0; tileIndex < tileCount; tileIndex++) { if (tileMap->layers[layerIndex].grid.tileInstances[tileIndex].metadata != NULL) { hashDispose(tileMap->layers[layerIndex].grid.tileInstances[tileIndex].metadata); } } free(tileMap->layers[layerIndex].grid.tileInstances); if (tileMap->layers[layerIndex].metadata != NULL) { hashDispose(tileMap->layers[layerIndex].metadata); } tileMap->layerCount--; for (; layerIndex < tileMap->layerCount; layerIndex++) { tileMap->layers[layerIndex] = tileMap->layers[layerIndex + 1]; } } } void TileMapEditData_reorderLayer(TileMapEditData * tileMap, unsigned int fromLayerIndex, unsigned int toLayerIndex) { assert(fromLayerIndex < tileMap->layerCount); assert(toLayerIndex < tileMap->layerCount); TileMapEditLayer swap = tileMap->layers[fromLayerIndex]; int direction = (toLayerIndex > fromLayerIndex) * 2 - 1; for (unsigned int swapIndex = fromLayerIndex; swapIndex != toLayerIndex; swapIndex += direction) { tileMap->layers[swapIndex] = tileMap->layers[swapIndex + direction]; } tileMap->layers[toLayerIndex] = swap; } static const char * getTileMapLayerName(unsigned int index, void * context) { TileMapEditData * tileMap = context; return tileMap->layers[index].name; } TileMapEditLayer TileMapEditData_newLayer(TileMapEditData * tileMap) { TileMapEditLayer layer; layer.name = newDisplayStringWithNumberSuffix("untitled", 32, tileMap->layerCount, getTileMapLayerName, tileMap); layer.offset = VECTOR2i(0, 0); layer.grid.size = tileMap->size; layer.visible = true; layer.writeEnabled = true; layer.opacity = 1.0f; layer.grid.tileInstances = calloc(layer.grid.size.x * layer.grid.size.y, sizeof(*layer.grid.tileInstances)); DataValueSchema * layerSchema = TileMapEditData_getSchemaInUse(tileMap, TILE_MAP_SCHEMA_KEY_TILE_MAP_LAYER); if (layerSchema == NULL) { layer.metadata = NULL; } else { layer.metadata = DataValueSchema_createBlankValue(layerSchema, DATA_TYPE_HASH_TABLE).value.hashTable; } return layer; } TileMapEditLayer TileMapEditLayer_copy(TileMapEditLayer * layer) { TileMapEditLayer copy = *layer; copy.name = strdup_nullSafe(layer->name); unsigned int tileCount = layer->grid.size.x * layer->grid.size.y; copy.grid.tileInstances = memdup(layer->grid.tileInstances, tileCount * sizeof(*layer->grid.tileInstances)); for (unsigned int tileIndex = 0; tileIndex < tileCount; tileIndex++) { copy.grid.tileInstances[tileIndex] = layer->grid.tileInstances[tileIndex]; if (layer->grid.tileInstances[tileIndex].metadata != NULL) { copy.grid.tileInstances[tileIndex].metadata = hashCopy(layer->grid.tileInstances[tileIndex].metadata); } } if (layer->metadata != NULL) { copy.metadata = hashCopy(layer->metadata); } return copy; } DataValueSchema * TileMapEditData_getSchemaInUse(TileMapEditData * tileMap, uint32_t key) { return getSchemaInUse(tileMap->schemasInUse, key); } void TileMapEditData_setTileSchema(TileMapEditData * tileMap, uint32_t key, DataValueSchema * schema) { if (HashTable_get(tileMap->schemasInUse, HashTable_uint32Key(key)) == NULL) { HashTable_set(tileMap->schemasInUse, HashTable_uint32Key(key), &schema); } } TileInstanceGrid TileInstanceGrid_create(unsigned int width, unsigned int height) { TileInstanceGrid tileGrid; tileGrid.size.x = width; tileGrid.size.y = height; tileGrid.tileInstances = calloc(width * height, sizeof(*tileGrid.tileInstances)); return tileGrid; } TileInstanceGrid TileInstanceGrid_copy(TileInstanceGrid * grid) { TileInstanceGrid copy; copy.size = grid->size; unsigned int tileCount = grid->size.x * grid->size.y; copy.tileInstances = malloc(tileCount * sizeof(*copy.tileInstances)); for (unsigned int tileIndex = 0; tileIndex < tileCount; tileIndex++) { copy.tileInstances[tileIndex] = grid->tileInstances[tileIndex]; if (grid->tileInstances[tileIndex].metadata != NULL) { copy.tileInstances[tileIndex].metadata = hashCopy(grid->tileInstances[tileIndex].metadata); } } return copy; } void TileInstanceGrid_dispose(TileInstanceGrid * grid) { unsigned int tileCount = grid->size.x * grid->size.y; for (unsigned int tileIndex = 0; tileIndex < tileCount; tileIndex++) { if (grid->tileInstances[tileIndex].metadata != NULL) { hashDispose(grid->tileInstances[tileIndex].metadata); } } free(grid->tileInstances); } bool TileInstanceGrid_isEqual(TileInstanceGrid * grid1, TileInstanceGrid * grid2) { if (grid1->size.x != grid2->size.x || grid1->size.y != grid2->size.y) { return false; } unsigned int tileCount = grid1->size.x * grid1->size.y; for (unsigned int tileIndex = 0; tileIndex < tileCount; tileIndex++) { if (grid1->tileInstances[tileIndex].tileID != grid2->tileInstances[tileIndex].tileID || !isDataHashTableEqual(grid1->tileInstances[tileIndex].metadata, grid2->tileInstances[tileIndex].metadata)) { return false; } } return true; } void TileInstanceGrid_clear(TileInstanceGrid * grid, TileID tileID) { unsigned int tileCount = grid->size.x * grid->size.y; for (unsigned int tileIndex = 0; tileIndex < tileCount; tileIndex++) { grid->tileInstances[tileIndex].tileID = tileID; if (grid->tileInstances[tileIndex].metadata != NULL) { hashDispose(grid->tileInstances[tileIndex].metadata); grid->tileInstances[tileIndex].metadata = NULL; } } } void TileInstanceGrid_copyTiles(TileInstanceGrid * target, TileInstanceGrid * 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->size.x, source->size.y, target->size.x, target->size.y, &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->size.x + sourceX + columnIndex; unsigned int targetIndex = (targetY + rowIndex) * target->size.x + targetX + columnIndex; target->tileInstances[targetIndex].tileID = source->tileInstances[sourceIndex].tileID; if (target->tileInstances[targetIndex].metadata != NULL) { hashDispose(target->tileInstances[targetIndex].metadata); target->tileInstances[targetIndex].metadata = NULL; } if (source->tileInstances[sourceIndex].metadata != NULL) { target->tileInstances[targetIndex].metadata = hashCopy(source->tileInstances[sourceIndex].metadata); } } } } void TileInstanceGrid_composite(TileInstanceGrid * target, TileInstanceGrid * 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->size.x, source->size.y, target->size.x, target->size.y, &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->size.x + sourceX + columnIndex; unsigned int targetIndex = (targetY + rowIndex) * target->size.x + targetX + columnIndex; if (source->tileInstances[sourceIndex].tileID != TILE_ID_NONE) { target->tileInstances[targetIndex].tileID = source->tileInstances[sourceIndex].tileID; if (target->tileInstances[targetIndex].metadata != NULL) { hashDispose(target->tileInstances[targetIndex].metadata); target->tileInstances[targetIndex].metadata = NULL; } if (source->tileInstances[sourceIndex].metadata != NULL) { target->tileInstances[targetIndex].metadata = hashCopy(source->tileInstances[sourceIndex].metadata); } } } } }