Skip to content

Instantly share code, notes, and snippets.

@maskrosen
Created March 26, 2026 12:36
Show Gist options
  • Select an option

  • Save maskrosen/061e2a365885f918fde4927a5286a5e5 to your computer and use it in GitHub Desktop.

Select an option

Save maskrosen/061e2a365885f918fde4927a5286a5e5 to your computer and use it in GitHub Desktop.
assetExportUtils.h
/**
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