Created
March 26, 2026 12:36
-
-
Save maskrosen/061e2a365885f918fde4927a5286a5e5 to your computer and use it in GitHub Desktop.
assetExportUtils.h
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| assetExportUtils - Functions to load and save data for models and animations in raylibs in memory format | |
| Tested with Raylib 5.5 might work with other versions | |
| Copyright (c) 2026 Lingon Studios | |
| Some of the code is derived from Raylib Copyright (c) 2013-2026 Ramon Santamaria (@raysan5) | |
| 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. | |
| */ | |
| #ifndef ASSET_EXPORT_UTILS_H | |
| #define ASSET_EXPORT_UTILS_H | |
| #pragma warning(disable:4996) | |
| #include "raylib.h" | |
| #include "raymath.h" | |
| #define SUPPORT_TRACELOG | |
| #include "utils.h" | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #ifndef PLATFORM_WEB | |
| #include <direct.h> | |
| #endif // !PLATFORM_WEB | |
| #include "external/cgltf.h" // glTF file format loading | |
| #include "structs.h" | |
| #include <stdint.h> | |
| #include <float.h> | |
| typedef int32_t i32; | |
| typedef uint32_t u32; | |
| typedef int64_t i64; | |
| typedef uint64_t u64; | |
| typedef uint8_t u8; | |
| typedef int16_t i16; | |
| typedef uint16_t u16; | |
| typedef float f32; | |
| typedef double f64; | |
| typedef struct MeshDataHeader | |
| { | |
| int vertexCount; // Number of vertices stored in arrays | |
| i32 triangleCount; // Number of triangles stored (indexed or not) | |
| bool verticesNull; | |
| bool texcoordsNull; | |
| bool texcoords2Null; | |
| bool normalsNull; | |
| bool tangentsNull; | |
| bool colorsNull; | |
| bool indicesNull; | |
| bool boneIdsNull; | |
| bool boneWeightsNull; | |
| }MeshDataHeader; | |
| typedef struct ModelDataHeader | |
| { | |
| Matrix transform; // Local transform matrix | |
| i32 meshCount; // Number of meshes | |
| i32 materialCount; // Number of materials | |
| int boneCount; // Number of bones | |
| }ModelDataHeader; | |
| typedef struct ModelAnimationDataHeader | |
| { | |
| int boneCount; | |
| int frameCount; | |
| }ModelAnimationDataHeader; | |
| typedef struct AnimationsHeader | |
| { | |
| int animationCount; | |
| }AnimationsHeader; | |
| typedef struct ByteArray | |
| { | |
| int length; | |
| char* data; | |
| }ByteArray; | |
| typedef struct LevelRenderObjects | |
| { | |
| int meshCount; | |
| int materialCount; | |
| Mesh* meshes; | |
| int* instanceCounts; | |
| Matrix** transforms; | |
| Material* materials; | |
| int* meshMaterial; | |
| BoundingSphere** boundingSpheres; | |
| }LevelRenderObjects; | |
| inline void copyDataToBuffer(u8* buffer, const void* src, i32 sizeInBytes, i32* bufferOffset) | |
| { | |
| memcpy(buffer + *bufferOffset, src, sizeInBytes); | |
| *bufferOffset += sizeInBytes; | |
| } | |
| inline void readDataFromBuffer(u8* buffer, void* dest, i32 sizeInBytes, i32* bufferOffset) | |
| { | |
| memcpy(dest, buffer + *bufferOffset, sizeInBytes); | |
| *bufferOffset += sizeInBytes; | |
| } | |
| inline i32 getMeshSizeInBytes(MeshDataHeader header) | |
| { | |
| int verticesByteSize = 3 * header.vertexCount * sizeof(float); | |
| int texcoordsByteSize = 2 * header.vertexCount * sizeof(float); | |
| int texcoords2ByteSize = 2 * header.vertexCount * sizeof(float); | |
| int normalsByteSize = 3 * header.vertexCount * sizeof(float); | |
| int tangentsByteSize = 4 * header.vertexCount * sizeof(float); | |
| int colorsByteSize = 4 * header.vertexCount * sizeof(u8); | |
| int indicesByteSize = 3 * header.triangleCount * sizeof(u16); | |
| int boneIdsByteSize = 4 * header.vertexCount * sizeof(u8); | |
| int boneWeigthsByteSize = 4 * header.vertexCount * sizeof(float); | |
| int byteSize = sizeof(MeshDataHeader); | |
| if(!header.verticesNull) | |
| { | |
| byteSize += verticesByteSize; | |
| } | |
| if(!header.texcoordsNull) | |
| { | |
| byteSize += texcoordsByteSize; | |
| } | |
| if(!header.texcoords2Null) | |
| { | |
| byteSize += texcoords2ByteSize; | |
| } | |
| if(!header.normalsNull) | |
| { | |
| byteSize += normalsByteSize; | |
| } | |
| if(!header.tangentsNull) | |
| { | |
| byteSize += tangentsByteSize; | |
| } | |
| if(!header.colorsNull) | |
| { | |
| byteSize += colorsByteSize; | |
| } | |
| if(!header.indicesNull) | |
| { | |
| byteSize += indicesByteSize; | |
| } | |
| if(!header.boneIdsNull) | |
| { | |
| byteSize += boneIdsByteSize; | |
| } | |
| if(!header.boneWeightsNull) | |
| { | |
| byteSize += boneWeigthsByteSize; | |
| } | |
| return byteSize; | |
| } | |
| inline ByteArray getMeshBytes(Mesh mesh) | |
| { | |
| ByteArray result; | |
| MeshDataHeader header = {mesh.vertexCount, mesh.triangleCount}; | |
| header.verticesNull = mesh.vertices == NULL; | |
| header.texcoordsNull = mesh.texcoords == NULL; | |
| header.texcoords2Null = mesh.texcoords2 == NULL; | |
| header.normalsNull = mesh.normals == NULL; | |
| header.tangentsNull = mesh.tangents == NULL; | |
| header.colorsNull = mesh.colors == NULL; | |
| header.indicesNull = mesh.indices == NULL; | |
| header.boneIdsNull = mesh.boneIds == NULL; | |
| header.boneWeightsNull = mesh.boneWeights == NULL; | |
| int byteSize = getMeshSizeInBytes(header); | |
| int verticesByteSize = 3 * header.vertexCount * sizeof(float); | |
| int texcoordsByteSize = 2 * header.vertexCount * sizeof(float); | |
| int texcoords2ByteSize = 2 * header.vertexCount * sizeof(float); | |
| int normalsByteSize = 3 * header.vertexCount * sizeof(float); | |
| int tangentsByteSize = 4 * header.vertexCount * sizeof(float); | |
| int colorsByteSize = 4 * header.vertexCount * sizeof(u8); | |
| int indicesByteSize = 3 * header.triangleCount * sizeof(u16); | |
| int boneIdsByteSize = 4 * header.vertexCount * sizeof(u8); | |
| int boneWeigthsByteSize = 4 * header.vertexCount * sizeof(float); | |
| u8* meshData = (u8*)RL_MALLOC(byteSize); | |
| int byteOffset = 0; | |
| //Header | |
| copyDataToBuffer(meshData, &header, sizeof(MeshDataHeader), &byteOffset); | |
| //Vertices | |
| if(!header.verticesNull) | |
| { | |
| copyDataToBuffer(meshData, mesh.vertices, verticesByteSize, &byteOffset); | |
| } | |
| //texcoords | |
| if(!header.texcoordsNull) | |
| { | |
| copyDataToBuffer(meshData, mesh.texcoords, texcoordsByteSize, &byteOffset); | |
| } | |
| //texcoord2 | |
| if(!header.texcoords2Null) | |
| { | |
| copyDataToBuffer(meshData, mesh.texcoords2, texcoords2ByteSize, &byteOffset); | |
| } | |
| //normals | |
| if(!header.normalsNull) | |
| { | |
| copyDataToBuffer(meshData, mesh.normals, normalsByteSize, &byteOffset); | |
| } | |
| //tangents | |
| if(!header.tangentsNull) | |
| { | |
| copyDataToBuffer(meshData, mesh.tangents, tangentsByteSize, &byteOffset); | |
| } | |
| //colors | |
| if(!header.colorsNull) | |
| { | |
| copyDataToBuffer(meshData, mesh.colors, colorsByteSize, &byteOffset); | |
| } | |
| //indices | |
| if(!header.indicesNull) | |
| { | |
| copyDataToBuffer(meshData, mesh.indices, indicesByteSize, &byteOffset); | |
| } | |
| //boneIds | |
| if(!header.boneIdsNull) | |
| { | |
| copyDataToBuffer(meshData, mesh.boneIds, boneIdsByteSize, &byteOffset); | |
| } | |
| //boneWeights | |
| if(!header.boneWeightsNull) | |
| { | |
| copyDataToBuffer(meshData, mesh.boneWeights, boneWeigthsByteSize, &byteOffset); | |
| } | |
| result.data = meshData; | |
| result.length = byteSize; | |
| return result; | |
| } | |
| inline ByteArray getMaterialBytes(Material material) | |
| { | |
| } | |
| inline void createMissingDirectories(const char* filename) | |
| { | |
| #ifndef PLATFORM_WEB | |
| if(!DirectoryExists(GetDirectoryPath(filename))) | |
| { | |
| const char* directoryStatic = GetDirectoryPath(filename); | |
| char* directory = RL_MALLOC(strlen(directoryStatic)); | |
| strcpy(directory, directoryStatic); | |
| createMissingDirectories(directory); | |
| if(_mkdir(GetDirectoryPath(filename)) != 0) | |
| { | |
| TRACELOG(LOG_ERROR, "Could not create directories for path: [%s]", filename); | |
| } | |
| RL_FREE(directory); | |
| } | |
| #endif // !PLATFORM_WEB | |
| } | |
| inline ByteArray getModelAnimationBytes(ModelAnimation modelAnimation) | |
| { | |
| ByteArray result; | |
| ModelAnimationDataHeader header = {0}; | |
| header.boneCount = modelAnimation.boneCount; | |
| header.frameCount = modelAnimation.frameCount; | |
| int byteSize = sizeof(ModelAnimationDataHeader); | |
| byteSize += sizeof(BoneInfo) * header.boneCount; | |
| byteSize += sizeof(Transform) * header.boneCount * header.frameCount; | |
| u8* bytes = RL_MALLOC(byteSize); | |
| int byteOffset = 0; | |
| //Header | |
| copyDataToBuffer(bytes, &header, sizeof(ModelAnimationDataHeader), &byteOffset); | |
| //bones | |
| copyDataToBuffer(bytes, modelAnimation.bones, sizeof(BoneInfo) * header.boneCount, &byteOffset); | |
| for (i32 i = 0; i < header.frameCount; i++) | |
| { | |
| //transforms | |
| copyDataToBuffer(bytes, modelAnimation.framePoses[i], sizeof(Transform) * header.boneCount, &byteOffset); | |
| } | |
| result.data = bytes; | |
| result.length = byteSize; | |
| return result; | |
| } | |
| inline bool exportAnimationsToRawFile(const char* filename, ModelAnimation* animations, i32 animationCount) | |
| { | |
| int byteSize = sizeof(AnimationsHeader); | |
| AnimationsHeader animationsHeader = {animationCount}; | |
| ByteArray* animationsBytes = (ByteArray*)RL_MALLOC(sizeof(ByteArray) * animationCount); | |
| for (i32 i = 0; i < animationCount; i++) | |
| { | |
| animationsBytes[i] = getModelAnimationBytes(animations[i]); | |
| byteSize += animationsBytes[i].length; | |
| } | |
| u8* animationsData = (u8*)RL_MALLOC(byteSize); | |
| int byteOffset = 0; | |
| copyDataToBuffer(animationsData, &animationsHeader, sizeof(AnimationsHeader), &byteOffset); | |
| for (i32 i = 0; i < animationCount; i++) | |
| { | |
| copyDataToBuffer(animationsData, animationsBytes[i].data, animationsBytes[i].length, &byteOffset); | |
| } | |
| createMissingDirectories(filename); | |
| bool success = SaveFileData(filename, animationsData, byteSize); | |
| for (i32 i = 0; i < animationCount; i++) | |
| { | |
| RL_FREE(animationsBytes[i].data); | |
| } | |
| RL_FREE(animationsBytes); | |
| return success; | |
| } | |
| inline i32 getModelAnimationSizeInBytes(ModelAnimationDataHeader header) | |
| { | |
| return header.boneCount * sizeof(BoneInfo) + sizeof(Transform) * header.boneCount * header.frameCount; | |
| } | |
| inline ModelAnimation loadAnimationFromBytes(u8* data, ModelAnimationDataHeader header) | |
| { | |
| ModelAnimation animation = {0}; | |
| animation.boneCount = header.boneCount; | |
| animation.frameCount = header.frameCount; | |
| animation.bones = RL_MALLOC(sizeof(BoneInfo) * header.boneCount); | |
| int byteOffset = 0; | |
| //bones | |
| readDataFromBuffer(data, animation.bones, sizeof(BoneInfo) * header.boneCount, &byteOffset); | |
| //transforms | |
| animation.framePoses = RL_MALLOC((sizeof(Transform*) * animation.frameCount)); | |
| for (i32 i = 0; i < header.frameCount; i++) | |
| { | |
| //transforms | |
| animation.framePoses[i] = RL_MALLOC(sizeof(Transform) * header.boneCount); | |
| readDataFromBuffer(data, animation.framePoses[i], sizeof(Transform) * header.boneCount, &byteOffset); | |
| } | |
| return animation; | |
| } | |
| inline ModelAnimation* loadAnimationsFromRawMemory(u8* data, i32* animationCount) | |
| { | |
| AnimationsHeader* header = (AnimationsHeader*)data; //first part should be header | |
| ModelAnimation* animations = RL_MALLOC((sizeof(ModelAnimation)) * header->animationCount); | |
| int byteOffset = sizeof(AnimationsHeader); | |
| for (i32 i = 0; i < header->animationCount; i++) | |
| { | |
| ModelAnimationDataHeader* animationHeader = (ModelAnimationDataHeader*)(data + byteOffset); //the first part in the data should be the header | |
| int modelAnimationSizeInBytes = getModelAnimationSizeInBytes(*animationHeader); | |
| animations[i] = loadAnimationFromBytes(data + byteOffset + sizeof(ModelAnimationDataHeader), *animationHeader); | |
| byteOffset += modelAnimationSizeInBytes + sizeof(ModelAnimationDataHeader); | |
| } | |
| *animationCount = header->animationCount; | |
| return animations; | |
| } | |
| inline ModelAnimation* loadAnimationsFromRawFile(const char* filename, i32* animationCount) | |
| { | |
| u32 numberOfBytes = 0; | |
| u8* rawData = LoadFileData(filename, &numberOfBytes); | |
| ModelAnimation* animations = loadAnimationsFromRawMemory(rawData, animationCount); | |
| UnloadFileData(rawData); | |
| return animations; | |
| } | |
| inline ByteArray getModelBytes(Model model) | |
| { | |
| int byteSize = sizeof(ModelDataHeader); | |
| ByteArray* meshes = (ByteArray*)RL_MALLOC(sizeof(ByteArray) * model.meshCount); | |
| for (i32 i = 0; i < model.meshCount; i++) | |
| { | |
| meshes[i] = getMeshBytes(model.meshes[i]); | |
| byteSize += meshes[i].length; | |
| } | |
| ModelDataHeader header = {0}; | |
| header.boneCount = model.boneCount; | |
| header.materialCount = model.materialCount; | |
| header.meshCount = model.meshCount; | |
| header.transform = model.transform; | |
| int bonesSizeByte = header.boneCount * sizeof(BoneInfo); | |
| int bindPoseSizeByte = header.boneCount * sizeof(Transform); | |
| byteSize += bonesSizeByte; | |
| byteSize += bindPoseSizeByte; | |
| int byteOffset = 0; | |
| char* modelData = (char*)RL_MALLOC(byteSize); | |
| copyDataToBuffer(modelData, &header, sizeof(ModelDataHeader), &byteOffset); | |
| for (i32 i = 0; i < model.meshCount; i++) | |
| { | |
| copyDataToBuffer(modelData, meshes[i].data, meshes[i].length, &byteOffset); | |
| } | |
| for (i32 i = 0; i < model.meshCount; i++) | |
| { | |
| RL_FREE(meshes[i].data); | |
| } | |
| RL_FREE(meshes); | |
| copyDataToBuffer(modelData, model.bones, bonesSizeByte, &byteOffset); | |
| copyDataToBuffer(modelData, model.bindPose, bindPoseSizeByte, &byteOffset); | |
| ByteArray result = {byteSize, modelData}; | |
| return result; | |
| } | |
| //Does not save materials | |
| inline bool exportStaticModelToRawFile(const char* filename, Model model) | |
| { | |
| ByteArray modelData = getModelBytes(model); | |
| createMissingDirectories(filename); | |
| bool success = SaveFileData(filename, modelData.data, modelData.length); | |
| return success; | |
| } | |
| inline Mesh loadMeshFromBytes(u8* data, MeshDataHeader header) | |
| { | |
| Mesh mesh = {0}; | |
| mesh.vertexCount = header.vertexCount; | |
| mesh.triangleCount = header.triangleCount; | |
| int verticesByteSize = 3 * mesh.vertexCount * sizeof(float); | |
| int texcoordsByteSize = 2 * mesh.vertexCount * sizeof(float); | |
| int texcoords2ByteSize = 2 * mesh.vertexCount * sizeof(float); | |
| int normalsByteSize = 3 * mesh.vertexCount * sizeof(float); | |
| int tangentsByteSize = 4 * mesh.vertexCount * sizeof(float); | |
| int colorsByteSize = 4 * mesh.vertexCount * sizeof(u8); | |
| int indicesByteSize = 3 * mesh.triangleCount * sizeof(u16); | |
| int boneIdsByteSize = 4 * mesh.vertexCount * sizeof(u8); | |
| int boneWeigthsByteSize = 4 * mesh.vertexCount * sizeof(float); | |
| mesh.vertices = RL_CALLOC(mesh.vertexCount*3, sizeof(float)); // Default vertex positions | |
| mesh.normals = RL_CALLOC(mesh.vertexCount*3, sizeof(float)); // Default vertex normals | |
| mesh.texcoords = RL_CALLOC(mesh.vertexCount*2, sizeof(float)); // Default vertex texcoords | |
| mesh.boneIds = RL_CALLOC(mesh.vertexCount*4, sizeof(u8)); // Up-to 4 bones supported! | |
| mesh.boneWeights = RL_CALLOC(mesh.vertexCount*4, sizeof(float)); // Up-to 4 bones supported! | |
| // Animated verted data, what we actually process for rendering | |
| // NOTE: Animated vertex should be re-uploaded to GPU (if not using GPU skinning) | |
| mesh.animVertices = RL_CALLOC(mesh.vertexCount*3, sizeof(float)); | |
| mesh.animNormals = RL_CALLOC(mesh.vertexCount*3, sizeof(float)); | |
| int byteOffset = 0; | |
| //vertices | |
| if(!header.verticesNull) | |
| { | |
| readDataFromBuffer(data, mesh.vertices, verticesByteSize, &byteOffset); | |
| memcpy(mesh.animVertices, mesh.vertices, verticesByteSize); | |
| } | |
| //texcoords | |
| if(!header.texcoordsNull) | |
| { | |
| readDataFromBuffer(data, mesh.texcoords, texcoordsByteSize, &byteOffset); | |
| } | |
| //texcoord2 | |
| if(!header.texcoords2Null) | |
| { | |
| readDataFromBuffer(data, mesh.texcoords2, texcoords2ByteSize, &byteOffset); | |
| } | |
| //normals | |
| if(!header.normalsNull) | |
| { | |
| readDataFromBuffer(data, mesh.normals, normalsByteSize, &byteOffset); | |
| memcpy(mesh.animNormals, mesh.normals, normalsByteSize); | |
| } | |
| //tangents | |
| if(!header.tangentsNull) | |
| { | |
| readDataFromBuffer(data, mesh.tangents, tangentsByteSize, &byteOffset); | |
| } | |
| //colors | |
| if(!header.colorsNull) | |
| { | |
| mesh.colors = RL_MALLOC(colorsByteSize); | |
| readDataFromBuffer(data, mesh.colors, colorsByteSize, &byteOffset); | |
| } | |
| //indices | |
| if(!header.indicesNull) | |
| { | |
| mesh.indices = RL_CALLOC(mesh.triangleCount*3, sizeof(u16)); | |
| readDataFromBuffer(data, mesh.indices, indicesByteSize, &byteOffset); | |
| } | |
| //boneIds | |
| if(!header.boneIdsNull) | |
| { | |
| readDataFromBuffer(data, mesh.boneIds, boneIdsByteSize, &byteOffset); | |
| } | |
| //boneWeights | |
| if(!header.boneWeightsNull) | |
| { | |
| readDataFromBuffer(data, mesh.boneWeights, boneWeigthsByteSize, &byteOffset); | |
| } | |
| return mesh; | |
| } | |
| inline Model loadModelFromRawMemory(u8* data) | |
| { | |
| Model model = { 0 }; | |
| ModelDataHeader* header = (ModelDataHeader*)data; //first part should be header | |
| int byteOffset = sizeof(ModelDataHeader); | |
| model.meshCount = header->meshCount; | |
| model.boneCount = header->boneCount; | |
| model.transform = header->transform; | |
| model.materialCount = 1; | |
| model.materials = (Material *)RL_CALLOC(model.materialCount, sizeof(Material)); | |
| model.materials[0] = LoadMaterialDefault(); | |
| model.meshMaterial = (i32 *)RL_CALLOC(model.meshCount, sizeof(int)); | |
| model.meshes = RL_CALLOC(model.meshCount, sizeof(Mesh)); | |
| model.bones = RL_MALLOC(model.boneCount * sizeof(BoneInfo)); | |
| model.bindPose = RL_MALLOC(model.boneCount * sizeof(Transform)); | |
| for (i32 i = 0; i < model.meshCount; i++) | |
| { | |
| MeshDataHeader* meshHeader = (MeshDataHeader*)(data + byteOffset); //the first part in the data should be the header | |
| int meshSizeInBytes = getMeshSizeInBytes(*meshHeader); | |
| model.meshes[i] = loadMeshFromBytes(data + byteOffset + sizeof(MeshDataHeader), *meshHeader); | |
| byteOffset += meshSizeInBytes; | |
| UploadMesh(&model.meshes[i], false); | |
| } | |
| readDataFromBuffer(data, model.bones, model.boneCount * sizeof(BoneInfo), &byteOffset); | |
| readDataFromBuffer(data, model.bindPose, model.boneCount * sizeof(Transform), &byteOffset); | |
| return model; | |
| } | |
| inline Model loadModelFromRawFile(const char* filename) | |
| { | |
| Model model = { 0 }; | |
| u32 numberOfBytes = 0; | |
| u8* rawData = LoadFileData(filename, &numberOfBytes); | |
| model = loadModelFromRawMemory(rawData); | |
| UnloadFileData(rawData); | |
| return model; | |
| } | |
| inline void StripMeshName(const char* name, char* buffer) | |
| { | |
| strcpy(buffer, name); | |
| int dotIndex = -1; | |
| for (i32 i = 63; i > 0; i--) | |
| { | |
| if(buffer[i] == '.' || buffer[i] == '_') | |
| { | |
| dotIndex = i; | |
| break; | |
| } | |
| } | |
| if(dotIndex != -1) | |
| { | |
| for (i32 i = dotIndex; i < 64; i++) | |
| { | |
| buffer[i] = 0; | |
| } | |
| } | |
| } | |
| // Macro to simplify attributes loading code | |
| #define LOAD_ATTRIBUTE(accesor, numComp, dataType, dstPtr) \ | |
| { \ | |
| int n = 0; \ | |
| dataType *buffer = (dataType *)accesor->buffer_view->buffer->data + accesor->buffer_view->offset/sizeof(dataType) + accesor->offset/sizeof(dataType); \ | |
| for (u32 k = 0; k < accesor->count; k++) \ | |
| {\ | |
| for (i32 l = 0; l < numComp; l++) \ | |
| {\ | |
| dstPtr[numComp*k + l] = buffer[n + l];\ | |
| }\ | |
| n += (int)(accesor->stride/sizeof(dataType));\ | |
| }\ | |
| } | |
| // Calculate angle between two vectors | |
| RMAPI float Vector3AngleOld(Vector3 v1, Vector3 v2) | |
| { | |
| float result = 0.0f; | |
| Vector3 cross = { v1.y * v2.z - v1.z * v2.y, v1.z * v2.x - v1.x * v2.z, v1.x * v2.y - v1.y * v2.x }; | |
| float len = sqrtf(cross.x * cross.x + cross.y * cross.y + cross.z * cross.z); | |
| float dot = (v1.x * v2.x + v1.y * v2.y + v1.z * v2.z); | |
| result = atan2f(len, dot); | |
| return result; | |
| } | |
| inline bool GetVerticesTransform(cgltf_mesh uniqueMesh, cgltf_mesh instancedMesh, Matrix* transform) | |
| { | |
| *transform = MatrixIdentity(); | |
| float* verticesUniqueMesh = NULL; | |
| float* verticesInstancedMesh = NULL; | |
| for (u32 p = 0; p < uniqueMesh.primitives_count; p++) | |
| { | |
| // NOTE: We only support primitives defined by triangles | |
| // Other alternatives: points, lines, line_strip, triangle_strip | |
| if (uniqueMesh.primitives[p].type != cgltf_primitive_type_triangles) continue; | |
| for (u32 j = 0; j < uniqueMesh.primitives[p].attributes_count; j++) | |
| { | |
| // Check the different attributes for every pimitive | |
| if (uniqueMesh.primitives[p].attributes[j].type == cgltf_attribute_type_position) // POSITION | |
| { | |
| cgltf_accessor *attribute = uniqueMesh.primitives[p].attributes[j].data; | |
| // WARNING: SPECS: POSITION accessor MUST have its min and max properties defined. | |
| if ((attribute->component_type == cgltf_component_type_r_32f) && (attribute->type == cgltf_type_vec3)) | |
| { | |
| // Init raylib mesh vertices to copy glTF attribute data | |
| //lro.meshes[meshIndex].vertexCount = (int)attribute->count; | |
| verticesUniqueMesh = RL_MALLOC(attribute->count*3*sizeof(float)); | |
| // Load 3 components of float data type into mesh.vertices | |
| LOAD_ATTRIBUTE(attribute, 3, float, verticesUniqueMesh) | |
| } | |
| } | |
| } | |
| } | |
| for (u32 p = 0; p < instancedMesh.primitives_count; p++) | |
| { | |
| // NOTE: We only support primitives defined by triangles | |
| // Other alternatives: points, lines, line_strip, triangle_strip | |
| if (instancedMesh.primitives[p].type != cgltf_primitive_type_triangles) continue; | |
| for (u32 j = 0; j < instancedMesh.primitives[p].attributes_count; j++) | |
| { | |
| // Check the different attributes for every pimitive | |
| if (instancedMesh.primitives[p].attributes[j].type == cgltf_attribute_type_position) // POSITION | |
| { | |
| cgltf_accessor *attribute = instancedMesh.primitives[p].attributes[j].data; | |
| // WARNING: SPECS: POSITION accessor MUST have its min and max properties defined. | |
| if ((attribute->component_type == cgltf_component_type_r_32f) && (attribute->type == cgltf_type_vec3)) | |
| { | |
| // Init raylib mesh vertices to copy glTF attribute data | |
| //lro.meshes[meshIndex].vertexCount = (int)attribute->count; | |
| verticesInstancedMesh = RL_MALLOC(attribute->count*3*sizeof(float)); | |
| // Load 3 components of float data type into mesh.vertices | |
| LOAD_ATTRIBUTE(attribute, 3, float, verticesInstancedMesh) | |
| } | |
| } | |
| } | |
| } | |
| bool success = true; | |
| if(fabsf(verticesUniqueMesh[0] - verticesInstancedMesh[0]) > 0.01f) | |
| { | |
| //we need to fix the transform | |
| Vector3 uniqueMeshPos = {verticesUniqueMesh[0], verticesUniqueMesh[1], verticesUniqueMesh[2]}; | |
| Vector3 instancedMeshPos = {verticesInstancedMesh[0], verticesInstancedMesh[1], verticesInstancedMesh[2]}; | |
| float scale = Vector3Length(instancedMeshPos) / Vector3Length(uniqueMeshPos); | |
| Vector3 deScaledInstancedMeshPos = Vector3Scale(instancedMeshPos, 1 / scale); | |
| float yRot = Vector3AngleOld(uniqueMeshPos, deScaledInstancedMeshPos); | |
| *transform = MatrixMultiply(*transform, MatrixScale(scale, scale, scale)); | |
| *transform = MatrixMultiply(*transform, MatrixRotateY(yRot)); | |
| Vector3 testPos = Vector3Transform(uniqueMeshPos, *transform); | |
| success = Vector3Distance(instancedMeshPos, testPos) <= 0.01f; | |
| if(!success) | |
| { | |
| TRACELOG(LOG_WARNING, TextFormat("Could not fins correct transform for vertices. Diff was %f", Vector3Distance(instancedMeshPos, testPos))); | |
| } | |
| } | |
| return success; | |
| } | |
| // Load glTF file into model struct, .gltf and .glb supported | |
| inline LevelRenderObjects LoadGLTFWithNames(const char *fileName) | |
| { | |
| LevelRenderObjects lro = {0}; | |
| // glTF file loading | |
| u32 dataSize = 0; | |
| u8 *fileData = LoadFileData(fileName, &dataSize); | |
| if (fileData == NULL) return lro; | |
| // glTF data loading | |
| cgltf_options options = { 0 }; | |
| cgltf_data *data = NULL; | |
| cgltf_result result = cgltf_parse(&options, fileData, dataSize, &data); | |
| const u32 maxNumberOfMeshes = 2048; | |
| const u32 maxNumberOfInstances = 2048; //Important!!! Need to match what we set in RenderLevel in gameUtils.c | |
| char* foundBaseNames = RL_CALLOC(64 * maxNumberOfMeshes, 1); | |
| u32* foundBaseNamesCounts = RL_CALLOC(maxNumberOfMeshes, sizeof(u32)); | |
| cgltf_mesh** uniqueMeshes = RL_CALLOC(maxNumberOfMeshes, sizeof(cgltf_mesh*)); | |
| Matrix* transforms = RL_MALLOC(sizeof(Matrix) * maxNumberOfMeshes * maxNumberOfInstances); | |
| if (result == cgltf_result_success) | |
| { | |
| if (data->file_type == cgltf_file_type_glb) TRACELOG(LOG_INFO, "MODEL: [%s] Model basic data (glb) loaded successfully", fileName); | |
| else if (data->file_type == cgltf_file_type_gltf) TRACELOG(LOG_INFO, "MODEL: [%s] Model basic data (glTF) loaded successfully", fileName); | |
| else TRACELOG(LOG_WARNING, "MODEL: [%s] Model format not recognized", fileName); | |
| TRACELOG(LOG_INFO, " > Meshes count: %i", data->meshes_count); | |
| TRACELOG(LOG_INFO, " > Materials count: %i (+1 default)", data->materials_count); | |
| TRACELOG(LOG_DEBUG, " > Buffers count: %i", data->buffers_count); | |
| TRACELOG(LOG_DEBUG, " > Images count: %i", data->images_count); | |
| TRACELOG(LOG_DEBUG, " > Textures count: %i", data->textures_count); | |
| // Force reading data buffers (fills buffer_view->buffer->data) | |
| // NOTE: If an uri is defined to base64 data or external path, it's automatically loaded -> TODO: Verify this assumption | |
| result = cgltf_load_buffers(&options, data, fileName); | |
| if (result != cgltf_result_success) TRACELOG(LOG_INFO, "MODEL: [%s] Failed to load mesh/material buffers", fileName); | |
| i32 primitivesCount = 0; | |
| // NOTE: We will load every primitive in the glTF as a separate raylib mesh | |
| for (u32 i = 0; i < data->meshes_count; i++) primitivesCount += (int)data->meshes[i].primitives_count; | |
| //Find duplicate meshes | |
| u32 uniqueMeshCount = 0; | |
| char* nameBuffer = RL_MALLOC(64); | |
| for (u32 i = 0, meshIndex = 0; i < data->nodes_count; i++) | |
| { | |
| cgltf_node node = data->nodes[i]; | |
| if(node.mesh == NULL) | |
| { | |
| continue; | |
| } | |
| memset(nameBuffer,0,64); | |
| StripMeshName(node.name, nameBuffer); | |
| bool duplicateMesh = false; | |
| for (u32 j = 0; j < uniqueMeshCount; j++) | |
| { | |
| int nameListIndex = 64 * j; | |
| if(strcmp(nameBuffer, &foundBaseNames[nameListIndex]) == 0) | |
| { | |
| duplicateMesh = true; | |
| Matrix transform = {0}; | |
| TraceLog(LOG_INFO, TextFormat("Trying to fix transform of vertives for mesh %s", &foundBaseNames[nameListIndex])); | |
| if(!GetVerticesTransform(*uniqueMeshes[j], *node.mesh, &transform)) | |
| { | |
| duplicateMesh = false; | |
| break; | |
| } | |
| if(node.has_scale) | |
| { | |
| transform = MatrixMultiply(transform, MatrixScale(node.scale[0], node.scale[1], node.scale[2])); | |
| } | |
| if(node.has_rotation) | |
| { | |
| Quaternion rotation = {node.rotation[0],node.rotation[1],node.rotation[2],node.rotation[3]}; | |
| transform = MatrixMultiply(transform, QuaternionToMatrix(rotation)); | |
| } | |
| if(node.has_translation) | |
| { | |
| transform = MatrixMultiply(transform, MatrixTranslate(node.translation[0], node.translation[1], node.translation[2])); | |
| } | |
| transforms[maxNumberOfInstances * j + foundBaseNamesCounts[j]] = transform; | |
| foundBaseNamesCounts[j]++; | |
| if(foundBaseNamesCounts[j] > maxNumberOfInstances) | |
| { | |
| TRACELOG(LOG_FATAL, "Too many instances of mesh with name %s.", node.name); | |
| } | |
| break; | |
| } | |
| } | |
| if(!duplicateMesh) | |
| { | |
| uniqueMeshes[uniqueMeshCount] = node.mesh; | |
| if(uniqueMeshCount > maxNumberOfMeshes) | |
| { | |
| TRACELOG(LOG_FATAL, "Too many unique meshes in model."); | |
| } | |
| strcpy(&foundBaseNames[uniqueMeshCount * 64], nameBuffer); | |
| Matrix transform = MatrixIdentity(); | |
| if(node.has_scale) | |
| { | |
| transform = MatrixMultiply(transform, MatrixScale(node.scale[0], node.scale[1], node.scale[2])); | |
| } | |
| if(node.has_rotation) | |
| { | |
| Quaternion rotation = {node.rotation[0],node.rotation[1],node.rotation[2],node.rotation[3]}; | |
| transform = MatrixMultiply(transform, QuaternionToMatrix(rotation)); | |
| } | |
| if(node.has_translation) | |
| { | |
| transform = MatrixMultiply(transform, MatrixTranslate(node.translation[0], node.translation[1], node.translation[2])); | |
| } | |
| transforms[maxNumberOfInstances * uniqueMeshCount + foundBaseNamesCounts[uniqueMeshCount]] = transform; | |
| foundBaseNamesCounts[uniqueMeshCount] = 1; | |
| uniqueMeshCount++; | |
| } | |
| } | |
| RL_FREE(nameBuffer); | |
| lro.meshCount = uniqueMeshCount; | |
| lro.meshes = RL_CALLOC(uniqueMeshCount, sizeof(Mesh)); | |
| lro.meshMaterial = RL_MALLOC(sizeof(int) * uniqueMeshCount); | |
| lro.instanceCounts = RL_MALLOC(sizeof(int) * uniqueMeshCount); | |
| memcpy(lro.instanceCounts, foundBaseNamesCounts, sizeof(int) * uniqueMeshCount); | |
| lro.materials = RL_MALLOC(sizeof(Material) * 2); | |
| lro.materials[0] = LoadMaterialDefault();//single | |
| lro.materials[1] = LoadMaterialDefault();//instanced | |
| lro.materialCount = 2; | |
| //TODO if we want to support more than one material we need to add support here | |
| lro.transforms = RL_MALLOC(sizeof(Matrix*) * uniqueMeshCount); | |
| for (u32 i = 0; i < uniqueMeshCount; i++) | |
| { | |
| lro.transforms[i] = RL_MALLOC(sizeof(Matrix) * foundBaseNamesCounts[i]); | |
| int startIndex = i * maxNumberOfInstances; | |
| memcpy(lro.transforms[i], &transforms[startIndex], sizeof(Matrix) * foundBaseNamesCounts[i]); | |
| } | |
| // Load meshes data | |
| //---------------------------------------------------------------------------------------------------- | |
| for (u32 i = 0, meshIndex = 0; i < uniqueMeshCount; i++) | |
| { | |
| // NOTE: meshIndex accumulates primitives | |
| cgltf_mesh mesh = *uniqueMeshes[i]; | |
| for (u32 p = 0; p < mesh.primitives_count; p++) | |
| { | |
| // NOTE: We only support primitives defined by triangles | |
| // Other alternatives: points, lines, line_strip, triangle_strip | |
| if (mesh.primitives[p].type != cgltf_primitive_type_triangles) continue; | |
| // NOTE: Attributes data could be provided in several data formats (8, 8u, 16u, 32...), | |
| // Only some formats for each attribute type are supported, read info at the top of this function! | |
| for (u32 j = 0; j < mesh.primitives[p].attributes_count; j++) | |
| { | |
| // Check the different attributes for every pimitive | |
| if (mesh.primitives[p].attributes[j].type == cgltf_attribute_type_position) // POSITION | |
| { | |
| cgltf_accessor *attribute = mesh.primitives[p].attributes[j].data; | |
| // WARNING: SPECS: POSITION accessor MUST have its min and max properties defined. | |
| if ((attribute->component_type == cgltf_component_type_r_32f) && (attribute->type == cgltf_type_vec3)) | |
| { | |
| // Init raylib mesh vertices to copy glTF attribute data | |
| lro.meshes[meshIndex].vertexCount = (int)attribute->count; | |
| lro.meshes[meshIndex].vertices = RL_MALLOC(attribute->count*3*sizeof(float)); | |
| // Load 3 components of float data type into mesh.vertices | |
| LOAD_ATTRIBUTE(attribute, 3, float, lro.meshes[meshIndex].vertices) | |
| } | |
| else TRACELOG(LOG_WARNING, "MODEL: [%s] Vertices attribute data format not supported, use vec3 float", fileName); | |
| } | |
| else if (mesh.primitives[p].attributes[j].type == cgltf_attribute_type_normal) // NORMAL | |
| { | |
| cgltf_accessor *attribute = mesh.primitives[p].attributes[j].data; | |
| if ((attribute->component_type == cgltf_component_type_r_32f) && (attribute->type == cgltf_type_vec3)) | |
| { | |
| // Init raylib mesh normals to copy glTF attribute data | |
| lro.meshes[meshIndex].normals = RL_MALLOC(attribute->count*3*sizeof(float)); | |
| // Load 3 components of float data type into mesh.normals | |
| LOAD_ATTRIBUTE(attribute, 3, float, lro.meshes[meshIndex].normals) | |
| } | |
| else TRACELOG(LOG_WARNING, "MODEL: [%s] Normal attribute data format not supported, use vec3 float", fileName); | |
| } | |
| //we skip tangents since we are not using those anyways | |
| /*else if (mesh.primitives[p].attributes[j].type == cgltf_attribute_type_tangent) // TANGENT | |
| { | |
| cgltf_accessor *attribute = mesh.primitives[p].attributes[j].data; | |
| if ((attribute->component_type == cgltf_component_type_r_32f) && (attribute->type == cgltf_type_vec4)) | |
| { | |
| // Init raylib mesh tangent to copy glTF attribute data | |
| lro.meshes[meshIndex].tangents = RL_MALLOC(attribute->count*4*sizeof(float)); | |
| // Load 4 components of float data type into mesh.tangents | |
| LOAD_ATTRIBUTE(attribute, 4, float, lro.meshes[meshIndex].tangents) | |
| } | |
| else TRACELOG(LOG_WARNING, "MODEL: [%s] Tangent attribute data format not supported, use vec4 float", fileName); | |
| }*/ | |
| else if (mesh.primitives[p].attributes[j].type == cgltf_attribute_type_texcoord) // TEXCOORD_0 | |
| { | |
| // TODO: Support additional texture coordinates: TEXCOORD_1 -> mesh.texcoords2 | |
| cgltf_accessor *attribute = mesh.primitives[p].attributes[j].data; | |
| if ((attribute->component_type == cgltf_component_type_r_32f) && (attribute->type == cgltf_type_vec2)) | |
| { | |
| // Init raylib mesh texcoords to copy glTF attribute data | |
| lro.meshes[meshIndex].texcoords = RL_MALLOC(attribute->count*2*sizeof(float)); | |
| // Load 3 components of float data type into mesh.texcoords | |
| LOAD_ATTRIBUTE(attribute, 2, float, lro.meshes[meshIndex].texcoords) | |
| } | |
| else TRACELOG(LOG_WARNING, "MODEL: [%s] Texcoords attribute data format not supported, use vec2 float", fileName); | |
| } | |
| else if (mesh.primitives[p].attributes[j].type == cgltf_attribute_type_color) // COLOR_0 | |
| { | |
| cgltf_accessor *attribute = mesh.primitives[p].attributes[j].data; | |
| // WARNING: SPECS: All components of each COLOR_n accessor element MUST be clamped to [0.0, 1.0] range. | |
| if ((attribute->component_type == cgltf_component_type_r_8u) && (attribute->type == cgltf_type_vec4)) | |
| { | |
| // Init raylib mesh color to copy glTF attribute data | |
| lro.meshes[meshIndex].colors = RL_MALLOC(attribute->count*4*sizeof(u8)); | |
| // Load 4 components of u8 data type into mesh.colors | |
| LOAD_ATTRIBUTE(attribute, 4, u8, lro.meshes[meshIndex].colors) | |
| } | |
| else if ((attribute->component_type == cgltf_component_type_r_16u) && (attribute->type == cgltf_type_vec4)) | |
| { | |
| // Init raylib mesh color to copy glTF attribute data | |
| lro.meshes[meshIndex].colors = RL_MALLOC(attribute->count*4*sizeof(u8)); | |
| // Load data into a temp buffer to be converted to raylib data type | |
| u16 *temp = RL_MALLOC(attribute->count*4*sizeof(u16)); | |
| LOAD_ATTRIBUTE(attribute, 4, u16, temp); | |
| // Convert data to raylib color data type (4 bytes) | |
| for (u32 c = 0; c < attribute->count*4; c++) lro.meshes[meshIndex].colors[c] = (u8)(((float)temp[c]/65535.0f)*255.0f); | |
| RL_FREE(temp); | |
| } | |
| else if ((attribute->component_type == cgltf_component_type_r_32f) && (attribute->type == cgltf_type_vec4)) | |
| { | |
| // Init raylib mesh color to copy glTF attribute data | |
| lro.meshes[meshIndex].colors = RL_MALLOC(attribute->count*4*sizeof(u8)); | |
| // Load data into a temp buffer to be converted to raylib data type | |
| float *temp = RL_MALLOC(attribute->count*4*sizeof(float)); | |
| LOAD_ATTRIBUTE(attribute, 4, float, temp); | |
| // Convert data to raylib color data type (4 bytes), we expect the color data normalized | |
| for (u32 c = 0; c < attribute->count*4; c++) lro.meshes[meshIndex].colors[c] = (u8)(temp[c]*255.0f); | |
| RL_FREE(temp); | |
| } | |
| else TRACELOG(LOG_WARNING, "MODEL: [%s] Color attribute data format not supported", fileName); | |
| } | |
| // NOTE: Attributes related to animations are processed separately | |
| } | |
| // Load primitive indices data (if provided) | |
| if (mesh.primitives[p].indices != NULL) | |
| { | |
| cgltf_accessor *attribute = mesh.primitives[p].indices; | |
| lro.meshes[meshIndex].triangleCount = (int)attribute->count/3; | |
| if (attribute->component_type == cgltf_component_type_r_16u) | |
| { | |
| // Init raylib mesh indices to copy glTF attribute data | |
| lro.meshes[meshIndex].indices = RL_MALLOC(attribute->count*sizeof(u16)); | |
| // Load u16 data type into mesh.indices | |
| LOAD_ATTRIBUTE(attribute, 1, u16, lro.meshes[meshIndex].indices) | |
| } | |
| else if (attribute->component_type == cgltf_component_type_r_32u) | |
| { | |
| // Init raylib mesh indices to copy glTF attribute data | |
| lro.meshes[meshIndex].indices = RL_MALLOC(attribute->count*sizeof(u16)); | |
| // Load data into a temp buffer to be converted to raylib data type | |
| u32 *temp = RL_MALLOC(attribute->count*sizeof(u32)); | |
| LOAD_ATTRIBUTE(attribute, 1, u32, temp); | |
| // Convert data to raylib indices data type (u16) | |
| for (u32 d = 0; d < attribute->count; d++) lro.meshes[meshIndex].indices[d] = (u16)temp[d]; | |
| TRACELOG(LOG_WARNING, "MODEL: [%s] Indices data converted from u32 to u16, possible loss of data", fileName); | |
| RL_FREE(temp); | |
| } | |
| else TRACELOG(LOG_WARNING, "MODEL: [%s] Indices data format not supported, use u16", fileName); | |
| } | |
| else lro.meshes[meshIndex].triangleCount = lro.meshes[meshIndex].vertexCount/3; // Unindexed mesh | |
| //NOTE: If we want to support more than 1 material in a level this needs to be updated to support that | |
| for (u32 m = 0; m < uniqueMeshCount; m++) | |
| { | |
| lro.meshMaterial[meshIndex] = 0; | |
| } | |
| meshIndex++; // Move to next mesh | |
| } | |
| } | |
| // Free all cgltf loaded data | |
| cgltf_free(data); | |
| } | |
| else TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to load glTF data", fileName); | |
| RL_FREE(foundBaseNames); | |
| RL_FREE(foundBaseNamesCounts); | |
| RL_FREE(uniqueMeshes); | |
| RL_FREE(transforms); | |
| // WARNING: cgltf requires the file pointer available while reading data | |
| UnloadFileData(fileData); | |
| return lro; | |
| } | |
| inline LevelRenderObjects loadLevelFromRawMemory(u8* data) | |
| { | |
| LevelRenderObjects lro = {0}; | |
| int byteOffset = 0; | |
| readDataFromBuffer(data, &lro.meshCount, sizeof(int), &byteOffset); | |
| readDataFromBuffer(data, &lro.materialCount, sizeof(int), &byteOffset); | |
| lro.meshes = RL_CALLOC(lro.meshCount, sizeof(Mesh)); | |
| lro.instanceCounts = RL_MALLOC(sizeof(int) * lro.meshCount); | |
| for (i32 i = 0; i < lro.meshCount; i++) | |
| { | |
| MeshDataHeader* meshHeader = (MeshDataHeader*)(data + byteOffset); //the first part in the data should be the header | |
| int meshSizeInBytes = getMeshSizeInBytes(*meshHeader); | |
| lro.meshes[i] = loadMeshFromBytes(data + byteOffset + sizeof(MeshDataHeader), *meshHeader); | |
| float vertex = lro.meshes[i].vertices[lro.meshes[i].vertexCount - 1]; | |
| if (lro.meshes[i].tangents != NULL) | |
| { | |
| int a = 5; | |
| } | |
| byteOffset += meshSizeInBytes; | |
| UploadMesh(&lro.meshes[i], false); | |
| } | |
| readDataFromBuffer(data, lro.instanceCounts, lro.meshCount * sizeof(int), &byteOffset); | |
| lro.transforms = RL_MALLOC(sizeof(Matrix*) * lro.meshCount); | |
| for (i32 i = 0; i < lro.meshCount; i++) | |
| { | |
| lro.transforms[i] = RL_MALLOC(lro.instanceCounts[i] * sizeof(Matrix)); | |
| readDataFromBuffer(data, lro.transforms[i], lro.instanceCounts[i] * sizeof(Matrix), &byteOffset); | |
| } | |
| lro.meshMaterial = RL_MALLOC(lro.meshCount * sizeof(int)); | |
| readDataFromBuffer(data, lro.meshMaterial, lro.meshCount * sizeof(int), &byteOffset); | |
| lro.materials = RL_MALLOC(sizeof(Material) * 2); | |
| lro.materials[0] = LoadMaterialDefault();//single | |
| lro.materials[1] = LoadMaterialDefault();//instanced | |
| //TODO if we want to support more than one material we need to add support here | |
| return lro; | |
| } | |
| //Exports a model as a LevelRenderObjects struct to a file | |
| inline bool exportLevelRenderObjectsToFile(const char* saveFilename, const char* modelFilename) | |
| { | |
| if (!(IsFileExtension(modelFilename, ".gltf") || IsFileExtension(modelFilename, ".glb"))) | |
| { | |
| TRACELOG(LOG_WARNING, "Unsupported file format for level, supported files are .gltf or .glb"); | |
| return false; | |
| } | |
| LevelRenderObjects lro = LoadGLTFWithNames(modelFilename); | |
| //Save lro | |
| int byteSize = 8; //meshCount + materialCount | |
| ByteArray* meshes = (ByteArray*)RL_MALLOC(sizeof(ByteArray) * lro.meshCount); | |
| for (i32 i = 0; i < lro.meshCount; i++) | |
| { | |
| meshes[i] = getMeshBytes(lro.meshes[i]); | |
| byteSize += meshes[i].length; | |
| } | |
| byteSize += sizeof(int) * lro.meshCount; //instanceCounts | |
| int transformsBytes = 0; | |
| for (i32 i = 0; i < lro.meshCount; i++) | |
| { | |
| transformsBytes += lro.instanceCounts[i] * sizeof(Matrix); | |
| } | |
| byteSize += transformsBytes; | |
| byteSize += sizeof(int) * lro.meshCount; //instancedMeshMaterial | |
| u8* data = RL_MALLOC(byteSize); | |
| int byteOffset = 0; | |
| copyDataToBuffer(data, &lro.meshCount, sizeof(int), &byteOffset); | |
| copyDataToBuffer(data, &lro.materialCount, sizeof(int), &byteOffset); | |
| for (i32 i = 0; i < lro.meshCount; i++) | |
| { | |
| copyDataToBuffer(data, meshes[i].data, meshes[i].length, &byteOffset); | |
| } | |
| copyDataToBuffer(data, lro.instanceCounts, lro.meshCount * sizeof(int), &byteOffset); | |
| for (i32 i = 0; i < lro.meshCount; i++) | |
| { | |
| copyDataToBuffer(data, lro.transforms[i], lro.instanceCounts[i] * sizeof(Matrix), &byteOffset); | |
| } | |
| //we don't save material since that is created in runtime | |
| copyDataToBuffer(data, lro.meshMaterial, lro.meshCount * sizeof(int), &byteOffset); | |
| //we don't save bounding speheres since that is created in runtime | |
| createMissingDirectories(saveFilename); | |
| bool success = SaveFileData(saveFilename, data, byteSize); | |
| return success; | |
| } | |
| #endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment