/* Copyright (c) 2018 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 "3dmodelio/TextureAtlasData.h" #include "utilities/IOUtilities.h" #include #include #include #define stemobject_implementation TextureAtlasData stemobject_vtable_begin(); stemobject_vtable_entry(dispose); stemobject_vtable_end(); TextureAtlasData * TextureAtlasData_create(const char * textureName, unsigned int entryCount, struct TextureAtlasData_entry * entries) { stemobject_create_implementation(init, textureName, entryCount, entries) } bool TextureAtlasData_init(TextureAtlasData * self, const char * textureName, unsigned int entryCount, struct TextureAtlasData_entry * entries) { call_super(init, self); self->textureName = strdup_nullSafe(textureName); self->entryCount = entryCount; self->entries = malloc(entryCount * sizeof(*self->entries)); for (unsigned int entryIndex = 0; entryIndex < entryCount; entryIndex++) { self->entries[entryIndex].name = strdup(entries[entryIndex].name); self->entries[entryIndex].bounds = entries[entryIndex].bounds; } return true; } TextureAtlasData * TextureAtlasData_copy(TextureAtlasData * self) { return TextureAtlasData_create(self->textureName, self->entryCount, self->entries); } void TextureAtlasData_dispose(TextureAtlasData * self) { for (unsigned int entryIndex = 0; entryIndex < self->entryCount; entryIndex++) { free(self->entries[entryIndex].name); } free(self->entries); free(self->textureName); call_super(dispose, self); } TextureAtlasData * TextureAtlasData_deserialize(compat_type(DeserializationContext *) deserializationContext) { stemobject_deserialize_implementation(loadSerializedData) } bool TextureAtlasData_loadSerializedData(TextureAtlasData * self, compat_type(DeserializationContext *) deserializationContext) { DeserializationContext * context = deserializationContext; context->vtable->beginStructure(context, TEXTUREATLASDATA_FORMAT_TYPE); const char * formatType = context->vtable->readString(context, "format_type"); if (context->status != SERIALIZATION_ERROR_OK || strcmp(formatType, TEXTUREATLASDATA_FORMAT_TYPE)) { return false; } uint16_t formatVersion = context->vtable->readUInt16(context, "format_version"); if (context->status != SERIALIZATION_ERROR_OK || formatVersion > TEXTUREATLASDATA_FORMAT_VERSION) { return false; } if (formatVersion < 4) { context->vtable->readStringNullable(context, "name"); } const char * textureNameString = context->vtable->readStringNullable(context, "texture_name"); if (formatVersion < 4) { context->vtable->readBoolean(context, "magnify_nearest"); if (formatVersion == 3) { context->vtable->readBoolean(context, "minify_nearest"); } } unsigned int entryCount = context->vtable->beginDictionary(context, "entries"); struct TextureAtlasData_entry * entries = NULL; if (entryCount > 0) { entries = malloc(entryCount * sizeof(*entries)); const char * xMinKey = "xmin", * xMaxKey = "xmax", * yMinKey = "ymin", * yMaxKey = "ymax"; if (formatVersion < 4) { xMinKey = "left"; xMaxKey = "right"; yMinKey = "top"; yMaxKey = "bottom"; } for (unsigned int entryIndex = 0; entryIndex < entryCount; entryIndex++) { const char * entryName = context->vtable->readNextDictionaryKey(context); entries[entryIndex].name = (char *) entryName; context->vtable->beginStructure(context, entryName); entries[entryIndex].bounds.xMin = context->vtable->readFloat(context, xMinKey); entries[entryIndex].bounds.xMax = context->vtable->readFloat(context, xMaxKey); entries[entryIndex].bounds.yMax = context->vtable->readFloat(context, yMaxKey); entries[entryIndex].bounds.yMin = context->vtable->readFloat(context, yMinKey); context->vtable->endStructure(context); } } context->vtable->endDictionary(context); context->vtable->endStructure(context); if (context->status != SERIALIZATION_ERROR_OK) { free(entries); return false; } call_super(init, self); self->textureName = strdup_nullSafe(textureNameString); self->entryCount = entryCount; for (unsigned int entryIndex = 0; entryIndex < entryCount; entryIndex++) { entries[entryIndex].name = strdup(entries[entryIndex].name); } self->entries = entries; return true; } void TextureAtlasData_serialize(TextureAtlasData * self, compat_type(SerializationContext *) serializationContext) { SerializationContext * context = serializationContext; context->vtable->beginStructure(context, TEXTUREATLASDATA_FORMAT_TYPE); context->vtable->writeString(context, "format_type", TEXTUREATLASDATA_FORMAT_TYPE); context->vtable->writeUInt16(context, "format_version", TEXTUREATLASDATA_FORMAT_VERSION); context->vtable->writeStringNullable(context, "texture_name", self->textureName); context->vtable->beginDictionary(context, "entries"); for (unsigned int entryIndex = 0; entryIndex < self->entryCount; entryIndex++) { context->vtable->beginStructure(context, self->entries[entryIndex].name); context->vtable->writeFloat(context, "xmin", self->entries[entryIndex].bounds.xMin); context->vtable->writeFloat(context, "xmax", self->entries[entryIndex].bounds.xMax); context->vtable->writeFloat(context, "ymax", self->entries[entryIndex].bounds.yMax); context->vtable->writeFloat(context, "ymin", self->entries[entryIndex].bounds.yMin); context->vtable->endStructure(context); } context->vtable->endDictionary(context); context->vtable->endStructure(context); } TextureAtlas * TextureAtlasData_createTextureAtlas(TextureAtlasData * self, unsigned int imageWidth, unsigned int imageHeight) { TextureAtlas * textureAtlas = TextureAtlas_create(imageWidth, imageHeight); TextureAtlasData_setTextureAtlasEntries(self, textureAtlas, imageWidth, imageHeight); return textureAtlas; } void TextureAtlasData_setTextureAtlasEntries(TextureAtlasData * self, TextureAtlas * textureAtlas, unsigned int imageWidth, unsigned int imageHeight) { for (unsigned int entryIndex = 0; entryIndex < self->entryCount; entryIndex++) { TextureAtlas_setEntry(textureAtlas, HashTable_stringKey(self->entries[entryIndex].name), TextureAtlasData_convertToTextureAtlasCoodinates(self->entries[entryIndex].bounds, imageWidth, imageHeight)); } } Rect4f TextureAtlasData_convertToTextureAtlasCoodinates(Rect4f bounds, unsigned int imageWidth, unsigned int imageHeight) { Rect4f result = bounds; result.xMin /= imageWidth; result.xMax /= imageWidth; result.yMin = (imageHeight - bounds.yMax) / imageHeight; result.yMax = (imageHeight - bounds.yMin) / imageHeight; return result; } void TextureAtlasData_setEntry(TextureAtlasData * self, const char * entryName, Rect4f entryBounds) { struct TextureAtlasData_entry * entry = TextureAtlasData_getEntryWithName(self, entryName); if (entry != NULL) { entry->bounds = entryBounds; } else { self->entries = realloc(self->entries, (self->entryCount + 1) * sizeof(*self->entries)); self->entries[self->entryCount].name = strdup(entryName); self->entries[self->entryCount].bounds = entryBounds; self->entryCount++; } } void TextureAtlasData_removeEntry(TextureAtlasData * self, const char * entryName) { for (unsigned int entryIndex = 0; entryIndex < self->entryCount; entryIndex++) { if (!strcmp(self->entries[entryIndex].name, entryName)) { free(self->entries[entryIndex].name); self->entryCount--; for (; entryIndex < self->entryCount; entryIndex++) { self->entries[entryIndex] = self->entries[entryIndex + 1]; } break; } } } struct TextureAtlasData_entry * TextureAtlasData_getEntryWithName(TextureAtlasData * self, const char * entryName) { for (unsigned int entryIndex = 0; entryIndex < self->entryCount; entryIndex++) { if (!strcmp(self->entries[entryIndex].name, entryName)) { return &self->entries[entryIndex]; } } return NULL; }