/
redgpu
/
raygpu
Обзор
Документация
Войти
/
redgpu
/
raygpu
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/models.c
3 033 строки
135 KB
manuel
Remove need for -I src/internal_include
08 окт 2025, 16:36
08 окт 2025, 16:36
1cb2ab9
Код
Авторство
О чём код?
// begin file src/models.c #include "mathutils.h" #include <stdio.h> #include <raygpu.h> #ifndef RL_CALLOC #define RL_CALLOC calloc #endif #ifndef RL_MALLOC #define RL_MALLOC malloc #endif #ifndef RL_REALLOC #define RL_REALLOC realloc #endif #ifndef RL_FREE #define RL_FREE free #endif #ifndef RL_REALLOC #define RL_REALLOC realloc #endif #define TINYOBJ_LOADER_C_IMPLEMENTATION #include <external/tinyobj_loader_c.h> #include <external/cgltf.h> #define PAR_SHAPES_IMPLEMENTATION #include <external/par_shapes.h> #ifndef MAX_MATERIAL_MAPS #define MAX_MATERIAL_MAPS 12 // Maximum number of maps supported #endif #ifndef MAX_MESH_VERTEX_BUFFERS #define MAX_MESH_VERTEX_BUFFERS 9 // Maximum vertex buffers (VBO) per mesh #endif const float v3_zero [3] = {0,0,0}; const float v3_xunit[3] = {1,0,0}; const float v3_yunit[3] = {0,1,0}; const float v3_zunit[3] = {0,0,1}; const float v3_xunit_negative[3] = {-1,0,0}; const float v3_yunit_negative[3] = {0,-1,0}; const float v3_zunit_negative[3] = {0,0,-1}; cgltf_result LoadFileGLTFCallback(const struct cgltf_memory_options *memoryOptions, const struct cgltf_file_options *fileOptions, const char *path, cgltf_size *size, void **data){ size_t filesize; void* filedata = LoadFileData(path, &filesize); if (filedata == NULL) return cgltf_result_io_error; *size = filesize; *data = filedata; return cgltf_result_success; } // Release file data callback for cgltf static void ReleaseFileGLTFCallback(const struct cgltf_memory_options *memoryOptions, const struct cgltf_file_options *fileOptions, void *data){ UnloadFileData(data); } void UploadMesh(Mesh *mesh, bool dynamic){ if(mesh->colors == NULL){ mesh->colors = (uint8_t*)RL_CALLOC(mesh->vertexCount, sizeof(RGBA8Color)); for(size_t i = 0;i < mesh->vertexCount * 4;i++){ mesh->colors[i] = 255; } } if(mesh->vbos == NULL){ mesh->vbos = (DescribedBuffer**)RL_CALLOC(MAX_MESH_VERTEX_BUFFERS, sizeof(DescribedBuffer*)); mesh->vbos[0] = GenVertexBuffer(mesh->vertices , mesh->vertexCount * sizeof(float ) * 3); mesh->vbos[1] = GenVertexBuffer(mesh->texcoords, mesh->vertexCount * sizeof(float ) * 2); mesh->vbos[2] = GenVertexBuffer(mesh->normals , mesh->vertexCount * sizeof(float ) * 3); mesh->vbos[3] = GenVertexBuffer(mesh->colors , mesh->vertexCount * sizeof(uint8_t) * 4); if(mesh->boneWeights){ mesh->vbos[4] = GenVertexBuffer(mesh->boneWeights, mesh->vertexCount * sizeof(float) * 4); } if(mesh->boneIds){ //TODO: Maybe change this to uint32_t mesh->vbos[5] = GenVertexBuffer(mesh->boneIds, mesh->vertexCount * sizeof(float) * 4); } if(mesh->indices){ mesh->ibo = GenIndexBuffer(mesh->indices, mesh->triangleCount * 3 * sizeof(uint32_t)); } mesh->vao = LoadVertexArray(); VertexAttribPointer(mesh->vao, mesh->vbos[0], 0, RGVertexFormat_Float32x3, sizeof(float) * 0, RGVertexStepMode_Vertex); EnableVertexAttribArray(mesh->vao, 0); VertexAttribPointer(mesh->vao, mesh->vbos[1], 1, RGVertexFormat_Float32x2, sizeof(float) * 0, RGVertexStepMode_Vertex); EnableVertexAttribArray(mesh->vao, 1); VertexAttribPointer(mesh->vao, mesh->vbos[2], 2, RGVertexFormat_Float32x3, sizeof(float) * 0, RGVertexStepMode_Vertex); EnableVertexAttribArray(mesh->vao, 2); VertexAttribPointer(mesh->vao, mesh->vbos[3], 3, RGVertexFormat_Unorm8x4, sizeof(float) * 0, RGVertexStepMode_Vertex); EnableVertexAttribArray(mesh->vao, 3); if(mesh->boneWeights){ VertexAttribPointer(mesh->vao, mesh->vbos[4], 4, RGVertexFormat_Float32x4, sizeof(float) * 0, RGVertexStepMode_Vertex); EnableVertexAttribArray(mesh->vao, 4); } if(mesh->boneIds){ VertexAttribPointer(mesh->vao, mesh->vbos[5], 5, mesh->boneIDFormat, sizeof(float) * 0, RGVertexStepMode_Vertex); EnableVertexAttribArray(mesh->vao, 5); } if(mesh->boneMatrices && mesh->boneCount){ mesh->boneMatrixBuffer = GenStorageBuffer(mesh->boneMatrices, sizeof(Matrix) * mesh->boneCount); } } else{ BufferData(mesh->vbos[0], mesh->vertices , mesh->vertexCount * sizeof(float ) * 3); BufferData(mesh->vbos[1], mesh->texcoords, mesh->vertexCount * sizeof(float ) * 2); BufferData(mesh->vbos[2], mesh->normals , mesh->vertexCount * sizeof(float ) * 3); BufferData(mesh->vbos[3], mesh->colors , mesh->vertexCount * sizeof(uint8_t) * 4); if(mesh->indices){ BufferData(mesh->ibo, mesh->indices, mesh->triangleCount * 3 * sizeof(uint32_t)); } } } DescribedBuffer* trfBuffer = NULL; RGAPI void DrawMeshInstanced(Mesh mesh, Material material, const Matrix* transforms, int instances){ if(trfBuffer && trfBuffer->buffer){ BufferData(trfBuffer, transforms, instances * sizeof(Matrix)); }else{ trfBuffer = GenStorageBuffer(transforms, instances * sizeof(Matrix)); } SetShaderStorageBuffer(GetActiveShader(), GetUniformLocation(GetActiveShader(), RL_DEFAULT_SHADER_UNIFORM_NAME_INSTANCE_TX), trfBuffer); SetTexture(GetUniformLocation(GetActiveShader(), RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE0), material.maps[MATERIAL_MAP_DIFFUSE].texture); BindShaderVertexArray(GetActiveShader(), mesh.vao); if(mesh.ibo){ DrawArraysIndexedInstanced(RL_TRIANGLES, *mesh.ibo, mesh.triangleCount * 3, instances); }else{ DrawArraysInstanced(RL_TRIANGLES, mesh.vertexCount, instances); } //wgpuBufferRelease(trfBuffer.buffer); } RGAPI Model LoadModelFromMesh(Mesh mesh) { Model model = { 0 }; model.transform = MatrixIdentity(); model.meshCount = 1; model.meshes = (Mesh *)RL_CALLOC(model.meshCount, sizeof(Mesh)); model.meshes[0] = mesh; model.materialCount = 1; model.materials = (Material *)RL_CALLOC(model.materialCount, sizeof(Material)); model.materials[0] = LoadMaterialDefault(); model.meshMaterial = (int *)RL_CALLOC(model.meshCount, sizeof(int)); model.meshMaterial[0] = 0; // First material index return model; } RGAPI void DrawModel(Model model, Vector3 position, float scale, Color tint) { Vector3 vScale = { scale, scale, scale }; Vector3 rotationAxis = { 0.0f, 1.0f, 0.0f }; float rotationAngle = 0.0f; DrawModelEx(model, position, rotationAxis, rotationAngle, vScale, tint); } RGAPI void DrawModelEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint) { // Calculate transformation matrix from function parameters // Get transform matrix (rotation -> scale -> translation) Matrix matScale = MatrixScale(scale.x, scale.y, scale.z); Matrix matRotation = MatrixRotate(rotationAxis, rotationAngle); Matrix matTranslation = MatrixTranslate(position.x, position.y, position.z); Matrix matTransform = MatrixMultiplySwap(MatrixMultiplySwap(matScale, matRotation), matTranslation); // Combine model transformation matrix (model.transform) with matrix generated by function parameters (matTransform) model.transform = matTransform; for (int i = 0; i < model.meshCount; i++) { Color color = model.materials[model.meshMaterial[i]].maps[MATERIAL_MAP_DIFFUSE].color; Color colorTint = WHITE; colorTint.r = (unsigned char)(((int)color.r*(int)tint.r)/255); colorTint.g = (unsigned char)(((int)color.g*(int)tint.g)/255); colorTint.b = (unsigned char)(((int)color.b*(int)tint.b)/255); colorTint.a = (unsigned char)(((int)color.a*(int)tint.a)/255); model.materials[model.meshMaterial[i]].maps[MATERIAL_MAP_DIFFUSE].color = colorTint; DrawMesh(model.meshes[i], model.materials[model.meshMaterial[i]], model.transform); model.materials[model.meshMaterial[i]].maps[MATERIAL_MAP_DIFFUSE].color = color; } } RGAPI void DrawMesh(Mesh mesh, Material material, Matrix transform){ SetStorageBufferData(3, &transform, sizeof(Matrix)); SetTexture(GetUniformLocation(GetActiveShader(), RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE0), material.maps[MATERIAL_MAP_DIFFUSE].texture); BindShaderVertexArray(GetActiveShader(), mesh.vao); if(mesh.ibo){ DrawArraysIndexed(RL_TRIANGLES, *mesh.ibo, mesh.triangleCount * 3); }else{ DrawArrays(RL_TRIANGLES, mesh.vertexCount); } } void ProcessMaterialsOBJ(Material *materials, tinyobj_material_t *mats, int materialCount, const char* directory){ // Init model mats for (int m = 0; m < materialCount; m++){ // Init material to default // NOTE: Uses default shader, which only supports MATERIAL_MAP_DIFFUSE materials[m] = LoadMaterialDefault(); if (mats == NULL) continue; // Get default texture, in case no texture is defined // NOTE: rlgl default texture is a 1x1 pixel UNCOMPRESSED_R8G8B8A8 materials[m].maps[MATERIAL_MAP_DIFFUSE].texture = GetDefaultTexture(); if (mats[m].diffuse_texname != NULL) materials[m].maps[MATERIAL_MAP_DIFFUSE].texture = LoadTexture(mats[m].diffuse_texname); //char *diffuse_texname; // map_Kd else materials[m].maps[MATERIAL_MAP_DIFFUSE].color = CLITERAL(Color){ (unsigned char)(mats[m].diffuse[0]*255.0f), (unsigned char)(mats[m].diffuse[1]*255.0f), (unsigned char)(mats[m].diffuse[2]*255.0f), 255 }; //float diffuse[3]; materials[m].maps[MATERIAL_MAP_DIFFUSE].value = 0.0f; if (mats[m].specular_texname != NULL) materials[m].maps[MATERIAL_MAP_SPECULAR].texture = LoadTexture(mats[m].specular_texname); //char *specular_texname; // map_Ks materials[m].maps[MATERIAL_MAP_SPECULAR].color = CLITERAL(Color){ (unsigned char)(mats[m].specular[0]*255.0f), (unsigned char)(mats[m].specular[1]*255.0f), (unsigned char)(mats[m].specular[2]*255.0f), 255 }; //float specular[3]; materials[m].maps[MATERIAL_MAP_SPECULAR].value = 0.0f; if (mats[m].bump_texname != NULL) materials[m].maps[MATERIAL_MAP_NORMAL].texture = LoadTexture(mats[m].bump_texname); //char *bump_texname; // map_bump, bump materials[m].maps[MATERIAL_MAP_NORMAL].color = WHITE; materials[m].maps[MATERIAL_MAP_NORMAL].value = mats[m].shininess; materials[m].maps[MATERIAL_MAP_EMISSION].color = CLITERAL(Color){ (unsigned char)(mats[m].emission[0]*255.0f), (unsigned char)(mats[m].emission[1]*255.0f), (unsigned char)(mats[m].emission[2]*255.0f), 255 }; //float emission[3]; if (mats[m].displacement_texname != NULL) materials[m].maps[MATERIAL_MAP_HEIGHT].texture = LoadTexture(mats[m].displacement_texname); //char *displacement_texname; // disp } } Model LoadOBJ(const char *fileName){ tinyobj_attrib_t objAttributes = {0}; tinyobj_shape_t *objShapes = NULL; unsigned int objShapeCount = 0; tinyobj_material_t *objMaterials = NULL; unsigned int objMaterialCount = 0; Model model = {0}; model.transform = MatrixIdentity(); char *fileText = LoadFileText(fileName); if (fileText == NULL){ TRACELOG(LOG_ERROR, "MODEL: [%s] Unable to read obj file", fileName); return model; } //strcpy(currentDir, GetWorkingDirectory()); // Save current working directory const char *objDir = GetDirectoryPath(fileName); // Switch to OBJ directory for material path correctness //if (CHDIR(workingDir) != 0) TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to change working directory", workingDir); unsigned int dataSize = (unsigned int)strlen(fileText); unsigned int flags = TINYOBJ_FLAG_TRIANGULATE; int ret = tinyobj_parse_obj(&objAttributes, &objShapes, &objShapeCount, &objMaterials, &objMaterialCount, fileText, dataSize, flags); if (ret != TINYOBJ_SUCCESS) { TRACELOG(LOG_ERROR, "MODEL: Unable to read obj data %s", fileName); return model; } UnloadFileText(fileText); unsigned int faceVertIndex = 0; unsigned int nextShape = 1; int lastMaterial = -1; unsigned int meshIndex = 0; // Count meshes unsigned int nextShapeEnd = objAttributes.num_face_num_verts; // See how many verts till the next shape if (objShapeCount > 1) nextShapeEnd = objShapes[nextShape].face_offset; // Walk all the faces for (unsigned int faceId = 0; faceId < objAttributes.num_faces; faceId++) { if (faceId >= nextShapeEnd) { // Try to find the last vert in the next shape nextShape++; if (nextShape < objShapeCount) nextShapeEnd = objShapes[nextShape].face_offset; else nextShapeEnd = objAttributes.num_face_num_verts; // This is actually the total number of face verts in the file, not faces meshIndex++; } else if ((lastMaterial != -1) && (objAttributes.material_ids[faceId] != lastMaterial)) { meshIndex++; // If this is a new material, we need to allocate a new mesh } lastMaterial = objAttributes.material_ids[faceId]; faceVertIndex += objAttributes.face_num_verts[faceId]; } // Allocate the base meshes and materials model.meshCount = meshIndex + 1; model.meshes = (Mesh *)calloc(model.meshCount, sizeof(Mesh)); if (objMaterialCount > 0) { model.materialCount = objMaterialCount; model.materials = (Material *)calloc(objMaterialCount, sizeof(Material)); } else // We must allocate at least one material { model.materialCount = 1; model.materials = (Material *)calloc(1, sizeof(Material)); } model.meshMaterial = (int *)calloc(model.meshCount, sizeof(int)); // See how many verts are in each mesh unsigned int *localMeshVertexCounts = (unsigned int *)calloc(model.meshCount, sizeof(unsigned int)); faceVertIndex = 0; nextShapeEnd = objAttributes.num_face_num_verts; lastMaterial = -1; meshIndex = 0; unsigned int localMeshVertexCount = 0; nextShape = 1; if (objShapeCount > 1) nextShapeEnd = objShapes[nextShape].face_offset; // Walk all the faces for (unsigned int faceId = 0; faceId < objAttributes.num_faces; faceId++) { bool newMesh = false; // Do we need a new mesh? if (faceId >= nextShapeEnd) { // Try to find the last vert in the next shape nextShape++; if (nextShape < objShapeCount) nextShapeEnd = objShapes[nextShape].face_offset; else nextShapeEnd = objAttributes.num_face_num_verts; // this is actually the total number of face verts in the file, not faces newMesh = true; } else if ((lastMaterial != -1) && (objAttributes.material_ids[faceId] != lastMaterial)) { newMesh = true; } lastMaterial = objAttributes.material_ids[faceId]; if (newMesh) { localMeshVertexCounts[meshIndex] = localMeshVertexCount; localMeshVertexCount = 0; meshIndex++; } faceVertIndex += objAttributes.face_num_verts[faceId]; localMeshVertexCount += objAttributes.face_num_verts[faceId]; } localMeshVertexCounts[meshIndex] = localMeshVertexCount; for (int i = 0; i < model.meshCount; i++) { // Allocate the buffers for each mesh unsigned int vertexCount = localMeshVertexCounts[i]; model.meshes[i].vertexCount = vertexCount; model.meshes[i].triangleCount = vertexCount/3; model.meshes[i].vertices = (float *) RL_CALLOC(vertexCount * 3, sizeof(float )); model.meshes[i].normals = (float *) RL_CALLOC(vertexCount * 3, sizeof(float )); model.meshes[i].texcoords = (float *) RL_CALLOC(vertexCount * 2, sizeof(float )); model.meshes[i].colors = (uint8_t *) RL_CALLOC(vertexCount * 4, sizeof(uint8_t)); } free(localMeshVertexCounts); localMeshVertexCounts = NULL; // Fill meshes faceVertIndex = 0; nextShapeEnd = objAttributes.num_face_num_verts; // See how many verts till the next shape nextShape = 1; if (objShapeCount > 1) nextShapeEnd = objShapes[nextShape].face_offset; lastMaterial = -1; meshIndex = 0; localMeshVertexCount = 0; // Walk all the faces for (unsigned int faceId = 0; faceId < objAttributes.num_faces; faceId++) { bool newMesh = false; // Do we need a new mesh? if (faceId >= nextShapeEnd) { // Try to find the last vert in the next shape nextShape++; if (nextShape < objShapeCount) nextShapeEnd = objShapes[nextShape].face_offset; else nextShapeEnd = objAttributes.num_face_num_verts; // This is actually the total number of face verts in the file, not faces newMesh = true; } // If this is a new material, we need to allocate a new mesh if (lastMaterial != -1 && objAttributes.material_ids[faceId] != lastMaterial) newMesh = true; lastMaterial = objAttributes.material_ids[faceId]; if (newMesh) { localMeshVertexCount = 0; meshIndex++; } int matId = 0; if ((lastMaterial >= 0) && (lastMaterial < (int)objMaterialCount)) matId = lastMaterial; model.meshMaterial[meshIndex] = matId; for (int f = 0; f < objAttributes.face_num_verts[faceId]; f++) { int vertIndex = objAttributes.faces[faceVertIndex].v_idx; int normalIndex = objAttributes.faces[faceVertIndex].vn_idx; int texcordIndex = objAttributes.faces[faceVertIndex].vt_idx; for (int i = 0; i < 3; i++) model.meshes[meshIndex].vertices[localMeshVertexCount*3 + i] = objAttributes.vertices[vertIndex*3 + i]; for (int i = 0; i < 3; i++) model.meshes[meshIndex].normals[localMeshVertexCount*3 + i] = objAttributes.normals[normalIndex*3 + i]; for (int i = 0; i < 2; i++) model.meshes[meshIndex].texcoords[localMeshVertexCount*2 + i] = objAttributes.texcoords[texcordIndex*2 + i]; model.meshes[meshIndex].texcoords[localMeshVertexCount*2 + 1] = 1.0f - model.meshes[meshIndex].texcoords[localMeshVertexCount*2 + 1]; for (int i = 0; i < 4; i++) model.meshes[meshIndex].colors[localMeshVertexCount * 4 + i] = (uint8_t)255; faceVertIndex++; localMeshVertexCount++; } } if (objMaterialCount > 0) ProcessMaterialsOBJ(model.materials, objMaterials, objMaterialCount, objDir); else model.materials[0] = LoadMaterialDefault(); // Set default material for the mesh tinyobj_attrib_free(&objAttributes); tinyobj_shapes_free(objShapes, objShapeCount); tinyobj_materials_free(objMaterials, objMaterialCount); for (int i = 0; i < model.meshCount; i++) UploadMesh(model.meshes + i, true); // Restore current working directory //if (CHDIR(currentDir) != 0) //{ // TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to change working directory", currentDir); //} return model; } static Image LoadImageFromCgltfImage(cgltf_image *cgltfImage, const char *texPath) { Image image = {0}; if (cgltfImage == NULL) return image; if (cgltfImage->uri != NULL) // Check if image data is provided as an uri (base64 or path) { if ((strlen(cgltfImage->uri) > 5) && (cgltfImage->uri[0] == 'd') && (cgltfImage->uri[1] == 'a') && (cgltfImage->uri[2] == 't') && (cgltfImage->uri[3] == 'a') && (cgltfImage->uri[4] == ':')) // Check if image is provided as base64 text data { // Data URI Format: data:<mediatype>;base64,<data> // Find the comma int i = 0; while ((cgltfImage->uri[i] != ',') && (cgltfImage->uri[i] != 0)) i++; if (cgltfImage->uri[i] == 0) TRACELOG(LOG_WARNING, "IMAGE: glTF data URI is not a valid image"); else { int base64Size = (int)strlen(cgltfImage->uri + i + 1); while (cgltfImage->uri[i + base64Size] == '=') base64Size--; // Ignore optional paddings int numberOfEncodedBits = base64Size*6 - (base64Size*6) % 8 ; // Encoded bits minus extra bits, so it becomes a multiple of 8 bits int outSize = numberOfEncodedBits/8 ; // Actual encoded bytes void *data = NULL; cgltf_options options = {0}; options.file.read = LoadFileGLTFCallback; options.file.release = ReleaseFileGLTFCallback; cgltf_result result = cgltf_load_buffer_base64(&options, outSize, cgltfImage->uri + i + 1, &data); if (result == cgltf_result_success) { image = LoadImageFromMemory(".png", (unsigned char *)data, outSize); RL_FREE(data); } } } else // Check if image is provided as image path { image = LoadImage(TextFormat("%s/%s", texPath, cgltfImage->uri)); } } else if (cgltfImage->buffer_view != NULL && cgltfImage->buffer_view->buffer->data != NULL) // Check if image is provided as data buffer { unsigned char *data = (unsigned char*) RL_MALLOC(cgltfImage->buffer_view->size); int offset = (int)cgltfImage->buffer_view->offset; int stride = (int)cgltfImage->buffer_view->stride? (int)cgltfImage->buffer_view->stride : 1; // Copy buffer data to memory for loading for (unsigned int i = 0; i < cgltfImage->buffer_view->size; i++) { data[i] = ((unsigned char *)cgltfImage->buffer_view->buffer->data)[offset]; offset += stride; } // Check mime_type for image: (cgltfImage->mime_type == "image/png") // NOTE: Detected that some models define mime_type as "image\\/png" if ((strcmp(cgltfImage->mime_type, "image\\/png") == 0) || (strcmp(cgltfImage->mime_type, "image/png") == 0)) image = LoadImageFromMemory(".png", data, (int)cgltfImage->buffer_view->size); else if ((strcmp(cgltfImage->mime_type, "image\\/jpeg") == 0) || (strcmp(cgltfImage->mime_type, "image/jpeg") == 0)) image = LoadImageFromMemory(".jpg", data, (int)cgltfImage->buffer_view->size); else TRACELOG(LOG_WARNING, "MODEL: glTF image data MIME type not recognized", TextFormat("%s/%s", texPath, cgltfImage->uri)); RL_FREE(data); } return image; } Model LoadGLTFFromMemory(const void* fileData, size_t size){ Model ret = {0}; cgltf_options options = {0}; cgltf_data* data = NULL; cgltf_result result = cgltf_parse(&options, fileData, size, &data); if (result == cgltf_result_success){ /* TODO make awesome stuff */ cgltf_free(data); } else{ TRACELOG(LOG_WARNING, "GLTF could not be parsed"); } return ret; } static BoneInfo *LoadBoneInfoGLTF(cgltf_skin skin, int *boneCount) { *boneCount = (int)skin.joints_count; BoneInfo *bones = (BoneInfo*)RL_MALLOC(skin.joints_count*sizeof(BoneInfo)); for (unsigned int i = 0; i < skin.joints_count; i++) { cgltf_node node = *skin.joints[i]; if (node.name != NULL) { strncpy(bones[i].name, node.name, sizeof(bones[i].name)); bones[i].name[sizeof(bones[i].name) - 1] = '\0'; } // Find parent bone index int parentIndex = -1; for (unsigned int j = 0; j < skin.joints_count; j++) { if (skin.joints[j] == node.parent) { parentIndex = (int)j; break; } } bones[i].parent = parentIndex; } return bones; } void UnloadMaterial(Material mat){ RL_FREE(mat.maps); } Material LoadMaterialDefault(void){ Material material = {0}; material.maps = (MaterialMap*) RL_CALLOC(MAX_MATERIAL_MAPS, sizeof(MaterialMap)); // Using default pipeline material.shader = DefaultShader(); // Using default texture (1x1 pixel, WGPuTexture, 1 mipmap) material.maps[MATERIAL_MAP_DIFFUSE].texture = GetDefaultTexture(); //material.maps[MATERIAL_MAP_NORMAL].texture; // NOTE: By default, not set //material.maps[MATERIAL_MAP_SPECULAR].texture; // NOTE: By default, not set material.maps[MATERIAL_MAP_DIFFUSE].color = WHITE; // Diffuse color material.maps[MATERIAL_MAP_SPECULAR].color = WHITE; // Specular color return material; } static bool GetPoseAtTimeGLTF(cgltf_interpolation_type interpolationType, cgltf_accessor *input, cgltf_accessor *output, float time, void *data) { if (interpolationType >= cgltf_interpolation_type_max_enum) return false; // Input and output should have the same count float tstart = 0.0f; float tend = 0.0f; int keyframe = 0; // Defaults to first pose for (int i = 0; i < (int)input->count - 1; i++) { cgltf_bool r1 = cgltf_accessor_read_float(input, i, &tstart, 1); if (!r1) return false; cgltf_bool r2 = cgltf_accessor_read_float(input, i + 1, &tend, 1); if (!r2) return false; if ((tstart <= time) && (time < tend)) { keyframe = i; break; } } // Constant animation, no need to interpolate if (FloatEquals(tend, tstart)) return true; float duration = fmaxf((tend - tstart), EPSILON); float t = (time - tstart)/duration; t = (t < 0.0f)? 0.0f : t; t = (t > 1.0f)? 1.0f : t; if (output->component_type != cgltf_component_type_r_32f) return false; if (output->type == cgltf_type_vec3) { switch (interpolationType) { case cgltf_interpolation_type_step: { float tmp[3] = { 0.0f }; cgltf_accessor_read_float(output, keyframe, tmp, 3); Vector3 v1 = {tmp[0], tmp[1], tmp[2]}; Vector3 *r = (Vector3*)data; *r = v1; } break; case cgltf_interpolation_type_linear: { float tmp[3] = { 0.0f }; cgltf_accessor_read_float(output, keyframe, tmp, 3); Vector3 v1 = {tmp[0], tmp[1], tmp[2]}; cgltf_accessor_read_float(output, keyframe+1, tmp, 3); Vector3 v2 = {tmp[0], tmp[1], tmp[2]}; Vector3 *r = (Vector3*)data; *r = Vector3Lerp(v1, v2, t); } break; case cgltf_interpolation_type_cubic_spline: { float tmp[3] = { 0.0f }; cgltf_accessor_read_float(output, 3*keyframe+1, tmp, 3); Vector3 v1 = {tmp[0], tmp[1], tmp[2]}; cgltf_accessor_read_float(output, 3*keyframe+2, tmp, 3); Vector3 tangent1 = {tmp[0], tmp[1], tmp[2]}; cgltf_accessor_read_float(output, 3*(keyframe+1)+1, tmp, 3); Vector3 v2 = {tmp[0], tmp[1], tmp[2]}; cgltf_accessor_read_float(output, 3*(keyframe+1), tmp, 3); Vector3 tangent2 = {tmp[0], tmp[1], tmp[2]}; Vector3 *r = (Vector3*)data; *r = Vector3CubicHermite(v1, tangent1, v2, tangent2, t); } break; default: break; } } else if (output->type == cgltf_type_vec4) { // Only v4 is for rotations, so we know it's a quaternion switch (interpolationType) { case cgltf_interpolation_type_step: { float tmp[4] = { 0.0f }; cgltf_accessor_read_float(output, keyframe, tmp, 4); Vector4 v1 = {tmp[0], tmp[1], tmp[2], tmp[3]}; Vector4 *r = (Vector4*)data; *r = v1; } break; case cgltf_interpolation_type_linear: { float tmp[4] = { 0.0f }; cgltf_accessor_read_float(output, keyframe, tmp, 4); Vector4 v1 = {tmp[0], tmp[1], tmp[2], tmp[3]}; cgltf_accessor_read_float(output, keyframe+1, tmp, 4); Vector4 v2 = {tmp[0], tmp[1], tmp[2], tmp[3]}; Vector4 *r = (Vector4*)data; *r = QuaternionSlerp(v1, v2, t); } break; case cgltf_interpolation_type_cubic_spline: { float tmp[4] = { 0.0f }; cgltf_accessor_read_float(output, 3*keyframe+1, tmp, 4); Vector4 v1 = {tmp[0], tmp[1], tmp[2], tmp[3]}; cgltf_accessor_read_float(output, 3*keyframe+2, tmp, 4); Vector4 outTangent1 = {tmp[0], tmp[1], tmp[2], 0.0f}; cgltf_accessor_read_float(output, 3*(keyframe+1)+1, tmp, 4); Vector4 v2 = {tmp[0], tmp[1], tmp[2], tmp[3]}; cgltf_accessor_read_float(output, 3*(keyframe+1), tmp, 4); Vector4 inTangent2 = {tmp[0], tmp[1], tmp[2], 0.0f}; Vector4 *r = (Vector4*)data; v1 = QuaternionNormalize(v1); v2 = QuaternionNormalize(v2); if (Vector4DotProduct(v1, v2) < 0.0f) { v2 = Vector4Negate(v2); } outTangent1 = Vector4Scale(outTangent1, duration); inTangent2 = Vector4Scale(inTangent2, duration); *r = QuaternionCubicHermiteSpline(v1, outTangent1, v2, inTangent2, t); } break; default: break; } } return true; } static void BuildPoseFromParentJoints(BoneInfo *bones, int boneCount, Transform *transforms) { for (int i = 0; i < boneCount; i++) { if (bones[i].parent >= 0) { if (bones[i].parent > i) { TRACELOG(LOG_WARNING, "Assumes bones are toplogically sorted, but bone %d has parent %d. Skipping.", i, bones[i].parent); continue; } transforms[i].rotation = QuaternionMultiply(transforms[bones[i].parent].rotation, transforms[i].rotation); transforms[i].translation = Vector3RotateByQuaternion(transforms[i].translation, transforms[bones[i].parent].rotation); transforms[i].translation = Vector3Add(transforms[i].translation, transforms[bones[i].parent].translation); transforms[i].scale = Vector3Multiply(transforms[i].scale, transforms[bones[i].parent].scale); } } } #define GLTF_ANIMDELAY 17 ModelAnimation *LoadModelAnimationsGLTF(const char *fileName, int *animCount){ // glTF file loading size_t dataSize = 0; unsigned char *fileData = (unsigned char*)LoadFileData(fileName, &dataSize); ModelAnimation *animations = NULL; // glTF data loading cgltf_options options = {0}; options.file.read = LoadFileGLTFCallback; options.file.release = ReleaseFileGLTFCallback; cgltf_data *data = NULL; cgltf_result result = cgltf_parse(&options, fileData, dataSize, &data); if (result != cgltf_result_success) { TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to load glTF data", fileName); *animCount = 0; return NULL; } result = cgltf_load_buffers(&options, data, fileName); if (result != cgltf_result_success) TRACELOG(LOG_INFO, "MODEL: [%s] Failed to load animation buffers", fileName); if (result == cgltf_result_success) { if (data->skins_count > 0) { cgltf_skin skin = data->skins[0]; *animCount = (int)data->animations_count; animations = (ModelAnimation*)RL_MALLOC(data->animations_count * sizeof(ModelAnimation)); for (unsigned int i = 0; i < data->animations_count; i++) { animations[i].bones = LoadBoneInfoGLTF(skin, &animations[i].boneCount); cgltf_animation animData = data->animations[i]; struct Channels { cgltf_animation_channel *translate; cgltf_animation_channel *rotate; cgltf_animation_channel *scale; cgltf_interpolation_type interpolationType; }; struct Channels *boneChannels = (struct Channels *)RL_CALLOC(animations[i].boneCount, sizeof(struct Channels)); float animDuration = 0.0f; for (unsigned int j = 0; j < animData.channels_count; j++) { cgltf_animation_channel channel = animData.channels[j]; int boneIndex = -1; for (unsigned int k = 0; k < skin.joints_count; k++) { if (animData.channels[j].target_node == skin.joints[k]) { boneIndex = k; break; } } if (boneIndex == -1) { // Animation channel for a node not in the armature continue; } boneChannels[boneIndex].interpolationType = animData.channels[j].sampler->interpolation; if (animData.channels[j].sampler->interpolation != cgltf_interpolation_type_max_enum) { if (channel.target_path == cgltf_animation_path_type_translation) { boneChannels[boneIndex].translate = &animData.channels[j]; } else if (channel.target_path == cgltf_animation_path_type_rotation) { boneChannels[boneIndex].rotate = &animData.channels[j]; } else if (channel.target_path == cgltf_animation_path_type_scale) { boneChannels[boneIndex].scale = &animData.channels[j]; } else { TRACELOG(LOG_WARNING, "MODEL: [%s] Unsupported target_path on channel %d's sampler for animation %d. Skipping.", fileName, j, i); } } else TRACELOG(LOG_WARNING, "MODEL: [%s] Invalid interpolation curve encountered for GLTF animation.", fileName); float t = 0.0f; cgltf_bool r = cgltf_accessor_read_float(channel.sampler->input, channel.sampler->input->count - 1, &t, 1); if (!r) { TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to load input time", fileName); continue; } animDuration = (t > animDuration)? t : animDuration; } if (animData.name != NULL) { strncpy(animations[i].name, animData.name, sizeof(animations[i].name)); animations[i].name[sizeof(animations[i].name) - 1] = '\0'; } animations[i].frameCount = (int)(animDuration*1000.0f/GLTF_ANIMDELAY) + 1; animations[i].framePoses = (Transform**)RL_MALLOC(animations[i].frameCount*sizeof(Transform *)); for (int j = 0; j < animations[i].frameCount; j++) { animations[i].framePoses[j] = (Transform*)RL_MALLOC(animations[i].boneCount*sizeof(Transform)); float time = ((float) j*GLTF_ANIMDELAY)/1000.0f; for (int k = 0; k < animations[i].boneCount; k++) { Vector3 translation = {skin.joints[k]->translation[0], skin.joints[k]->translation[1], skin.joints[k]->translation[2]}; Quaternion rotation = {skin.joints[k]->rotation[0], skin.joints[k]->rotation[1], skin.joints[k]->rotation[2], skin.joints[k]->rotation[3]}; Vector3 scale = {skin.joints[k]->scale[0], skin.joints[k]->scale[1], skin.joints[k]->scale[2]}; if (boneChannels[k].translate) { if (!GetPoseAtTimeGLTF(boneChannels[k].interpolationType, boneChannels[k].translate->sampler->input, boneChannels[k].translate->sampler->output, time, &translation)) { TRACELOG(LOG_INFO, "MODEL: [%s] Failed to load translate pose data for bone %s", fileName, animations[i].bones[k].name); } } if (boneChannels[k].rotate) { if (!GetPoseAtTimeGLTF(boneChannels[k].interpolationType, boneChannels[k].rotate->sampler->input, boneChannels[k].rotate->sampler->output, time, &rotation)) { TRACELOG(LOG_INFO, "MODEL: [%s] Failed to load rotate pose data for bone %s", fileName, animations[i].bones[k].name); } } if (boneChannels[k].scale) { if (!GetPoseAtTimeGLTF(boneChannels[k].interpolationType, boneChannels[k].scale->sampler->input, boneChannels[k].scale->sampler->output, time, &scale)) { TRACELOG(LOG_INFO, "MODEL: [%s] Failed to load scale pose data for bone %s", fileName, animations[i].bones[k].name); } } animations[i].framePoses[j][k] = CLITERAL(Transform){ .translation = translation, .rotation = rotation, .scale = scale }; } BuildPoseFromParentJoints(animations[i].bones, animations[i].boneCount, animations[i].framePoses[j]); } TRACELOG(LOG_INFO, "MODEL: [%s] Loaded animation: %s (%d frames, %fs)", fileName, (animData.name != NULL)? animData.name : "NULL", animations[i].frameCount, animDuration); RL_FREE(boneChannels); } } if (data->skins_count > 1) { TRACELOG(LOG_WARNING, "MODEL: [%s] expected exactly one skin to load animation data from, but found %i", fileName, data->skins_count); } cgltf_free(data); } UnloadFileData(fileData); return animations; } // helper static inline size_t AccessorStride(const cgltf_accessor* a) { size_t comp_sz = (a->component_type == cgltf_component_type_r_8u || a->component_type == cgltf_component_type_r_8) ? 1 : (a->component_type == cgltf_component_type_r_16u || a->component_type == cgltf_component_type_r_16) ? 2 : (a->component_type == cgltf_component_type_r_32u || a->component_type == cgltf_component_type_r_32f)? 4 : 0; size_t num_comp = (a->type == cgltf_type_scalar)?1:(a->type==cgltf_type_vec2)?2:(a->type==cgltf_type_vec3)?3:(a->type==cgltf_type_vec4)?4: (a->type==cgltf_type_mat2)?4:(a->type==cgltf_type_mat3)?9:(a->type==cgltf_type_mat4)?16:0; size_t tight = comp_sz * num_comp; size_t bv_stride = a->buffer_view && a->buffer_view->stride ? a->buffer_view->stride : 0; return bv_stride ? bv_stride : tight; } Model LoadGLTF(const char *fileName) { /********************************************************************************************* Function implemented by Wilhem Barbier(@wbrbr), with modifications by Tyler Bezera(@gamerfiend) Transform handling implemented by Paul Melis (@paulmelis). Reviewed by Ramon Santamaria (@raysan5) FEATURES: - Supports .gltf and .glb files - Supports embedded (base64) or external textures - Supports PBR metallic/roughness flow, loads material textures, values and colors PBR specular/glossiness flow and extended texture flows not supported - Supports multiple meshes per model (every primitives is loaded as a separate mesh) - Supports basic animations - Transforms, including parent-child relations, are applied on the mesh data, but the hierarchy is not kept (as it can't be represented). - Mesh instances in the glTF file (i.e. same mesh linked from multiple nodes) are turned into separate raylib Meshes. RESTRICTIONS: - Only triangle meshes supported - Vertex attribute types and formats supported: > Vertices (position): vec3: float > Normals: vec3: float > Texcoords: vec2: float > Colors: vec4: u8, u16, f32 (normalized) > Indices: u16, u32 (truncated to u16) - Scenes defined in the glTF file are ignored. All nodes in the file are used. ***********************************************************************************************/ // Macro to simplify attributes loading code #define LOAD_ATTRIBUTE(accesor, numComp, srcType, dstPtr) LOAD_ATTRIBUTE_CAST(accesor, numComp, srcType, dstPtr, srcType) #define LOAD_ATTRIBUTE_CAST(accesor, numComp, srcType, dstPtr, dstType) \ { \ int n = 0; \ srcType *buffer = (srcType *)accesor->buffer_view->buffer->data + accesor->buffer_view->offset/sizeof(srcType) + accesor->offset/sizeof(srcType); \ for (unsigned int k = 0; k < accesor->count; k++) \ {\ for (int l = 0; l < numComp; l++) \ {\ dstPtr[numComp*k + l] = (dstType)buffer[n + l];\ }\ n += (int)(AccessorStride(accesor)/sizeof(srcType));\ }\ } Model model = {0}; // glTF file loading size_t dataSize = 0; unsigned char *fileData = (unsigned char*)LoadFileData(fileName, &dataSize); if (fileData == NULL) return model; // glTF data loading cgltf_options options = {0}; options.file.read = NULL;//LoadFileGLTFCallback; options.file.release = NULL;//ReleaseFileGLTFCallback; cgltf_data *data = NULL; cgltf_result result = cgltf_parse(&options, fileData, dataSize, &data); 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 result = cgltf_load_buffers(&options, data, fileName); if (result != cgltf_result_success) TRACELOG(LOG_INFO, "MODEL: [%s] Failed to load mesh/material buffers", fileName); int primitivesCount = 0; // NOTE: We will load every primitive in the glTF as a separate raylib Mesh. // Determine total number of meshes needed from the node hierarchy. for (unsigned int i = 0; i < data->nodes_count; i++) { cgltf_node *node = &(data->nodes[i]); cgltf_mesh *mesh = node->mesh; if (!mesh) continue; for (unsigned int p = 0; p < mesh->primitives_count; p++) { if (mesh->primitives[p].type == cgltf_primitive_type_triangles) primitivesCount++; } } TRACELOG(LOG_DEBUG, " > Primitives (triangles only) count based on hierarchy : %i", primitivesCount); // Load our model data: meshes and materials model.meshCount = primitivesCount; model.meshes = (Mesh*)RL_CALLOC(model.meshCount, sizeof(Mesh)); // NOTE: We keep an extra slot for default material, in case some mesh requires it model.materialCount = (int)data->materials_count + 1; model.materials = (Material*)RL_CALLOC(model.materialCount, sizeof(Material)); model.materials[0] = LoadMaterialDefault(); // Load default material (index: 0) // Load mesh-material indices, by default all meshes are mapped to material index: 0 model.meshMaterial = (int*)RL_CALLOC(model.meshCount, sizeof(int)); // Load materials data //---------------------------------------------------------------------------------------------------- for (unsigned int i = 0, j = 1; i < data->materials_count; i++, j++) { model.materials[j] = LoadMaterialDefault(); const char *texPath = GetDirectoryPath(fileName); // Check glTF material flow: PBR metallic/roughness flow // NOTE: Alternatively, materials can follow PBR specular/glossiness flow if (data->materials[i].has_pbr_metallic_roughness) { // Load base color texture (albedo) if (data->materials[i].pbr_metallic_roughness.base_color_texture.texture) { Image imAlbedo = LoadImageFromCgltfImage(data->materials[i].pbr_metallic_roughness.base_color_texture.texture->image, texPath); if (imAlbedo.data != NULL) { model.materials[j].maps[MATERIAL_MAP_ALBEDO].texture = LoadTextureFromImage(imAlbedo); UnloadImage(imAlbedo); } } // Load base color factor (tint) model.materials[j].maps[MATERIAL_MAP_ALBEDO].color.r = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[0]*255); model.materials[j].maps[MATERIAL_MAP_ALBEDO].color.g = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[1]*255); model.materials[j].maps[MATERIAL_MAP_ALBEDO].color.b = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[2]*255); model.materials[j].maps[MATERIAL_MAP_ALBEDO].color.a = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[3]*255); // Load metallic/roughness texture if (data->materials[i].pbr_metallic_roughness.metallic_roughness_texture.texture) { Image imMetallicRoughness = LoadImageFromCgltfImage(data->materials[i].pbr_metallic_roughness.metallic_roughness_texture.texture->image, texPath); if (imMetallicRoughness.data != NULL) { model.materials[j].maps[MATERIAL_MAP_ROUGHNESS].texture = LoadTextureFromImage(imMetallicRoughness); UnloadImage(imMetallicRoughness); } // Load metallic/roughness material properties float roughness = data->materials[i].pbr_metallic_roughness.roughness_factor; model.materials[j].maps[MATERIAL_MAP_ROUGHNESS].value = roughness; float metallic = data->materials[i].pbr_metallic_roughness.metallic_factor; model.materials[j].maps[MATERIAL_MAP_METALNESS].value = metallic; } // Load normal texture if (data->materials[i].normal_texture.texture) { Image imNormal = LoadImageFromCgltfImage(data->materials[i].normal_texture.texture->image, texPath); if (imNormal.data != NULL) { model.materials[j].maps[MATERIAL_MAP_NORMAL].texture = LoadTextureFromImage(imNormal); UnloadImage(imNormal); } } // Load ambient occlusion texture if (data->materials[i].occlusion_texture.texture) { Image imOcclusion = LoadImageFromCgltfImage(data->materials[i].occlusion_texture.texture->image, texPath); if (imOcclusion.data != NULL) { model.materials[j].maps[MATERIAL_MAP_OCCLUSION].texture = LoadTextureFromImage(imOcclusion); UnloadImage(imOcclusion); } } // Load emissive texture if (data->materials[i].emissive_texture.texture) { Image imEmissive = LoadImageFromCgltfImage(data->materials[i].emissive_texture.texture->image, texPath); if (imEmissive.data != NULL) { model.materials[j].maps[MATERIAL_MAP_EMISSION].texture = LoadTextureFromImage(imEmissive); UnloadImage(imEmissive); } // Load emissive color factor model.materials[j].maps[MATERIAL_MAP_EMISSION].color.r = (unsigned char)(data->materials[i].emissive_factor[0]*255); model.materials[j].maps[MATERIAL_MAP_EMISSION].color.g = (unsigned char)(data->materials[i].emissive_factor[1]*255); model.materials[j].maps[MATERIAL_MAP_EMISSION].color.b = (unsigned char)(data->materials[i].emissive_factor[2]*255); model.materials[j].maps[MATERIAL_MAP_EMISSION].color.a = 255; } } // Other possible materials not supported by raylib pipeline: // has_clearcoat, has_transmission, has_volume, has_ior, has specular, has_sheen } // Visit each node in the hierarchy and process any mesh linked from it. // Each primitive within a glTF node becomes a Raylib Mesh. // The local-to-world transform of each node is used to transform the // points/normals/tangents of the created Mesh(es). // Any glTF mesh linked from more than one Node (i.e. instancing) // is turned into multiple Mesh's, as each Node will have its own // transform applied. // Note: the code below disregards the scenes defined in the file, all nodes are used. //---------------------------------------------------------------------------------------------------- int meshIndex = 0; for (unsigned int i = 0; i < data->nodes_count; i++) { cgltf_node *node = &(data->nodes[i]); cgltf_mesh *mesh = node->mesh; if (!mesh) continue; cgltf_float worldTransform[16]; cgltf_node_transform_world(node, worldTransform); Matrix worldMatrix = { worldTransform[0], worldTransform[4], worldTransform[8], worldTransform[12], worldTransform[1], worldTransform[5], worldTransform[9], worldTransform[13], worldTransform[2], worldTransform[6], worldTransform[10], worldTransform[14], worldTransform[3], worldTransform[7], worldTransform[11], worldTransform[15] }; //{ // worldMatrix.m3 = 0; // worldMatrix.m7 = 0; // worldMatrix.m11 = 0; // worldMatrix.m12 = 0; // worldMatrix.m13 = 0; // worldMatrix.m14 = 0; //} //char dbgFN[32]; //snprintf(dbgFN, 32, "debug%d.txt", i); //FILE* doutput = fopen(dbgFN, "w"); //printf("%f, %f, %f, %f\n", worldMatrix.m0, worldMatrix.m4, worldMatrix.m8, worldMatrix.m12); //printf("%f, %f, %f, %f\n", worldMatrix.m1, worldMatrix.m5, worldMatrix.m9, worldMatrix.m13); //printf("%f, %f, %f, %f\n", worldMatrix.m2, worldMatrix.m6, worldMatrix.m10, worldMatrix.m14); //printf("%f, %f, %f, %f\n\n", worldMatrix.m3, worldMatrix.m7, worldMatrix.m11, worldMatrix.m15); //worldMatrix = MatrixTranspose(worldMatrix); Matrix worldMatrixNormals = MatrixTranspose(MatrixInvert(worldMatrix)); //worldMatrixNormals.m12 = 0; //worldMatrixNormals.m13 = 0; //worldMatrixNormals.m14 = 0; //if(worldMatrixNormals.m12 != 0 || worldMatrixNormals.m13 != 0 || worldMatrixNormals.m14 != 0){ // abort(); //} for (unsigned int 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 (unsigned int j = 0; j < mesh->primitives[p].attributes_count; j++) { // Check the different attributes for every primitive if (mesh->primitives[p].attributes[j].type == cgltf_attribute_type_position) // POSITION, vec3, float { cgltf_accessor *attribute = mesh->primitives[p].attributes[j].data; // WARNING: SPECS: POSITION accessor MUST have its min and max properties defined if ((attribute->type == cgltf_type_vec3) && (attribute->component_type == cgltf_component_type_r_32f)) { // Init raylib mesh vertices to copy glTF attribute data model.meshes[meshIndex].vertexCount = (int)attribute->count; model.meshes[meshIndex].vertices = (float*)RL_MALLOC(attribute->count*3*sizeof(float)); // Load 3 components of float data type into mesh.vertices //cgltf_accessor_unpack_floats(attribute, model.meshes[meshIndex].vertices, attribute->count); LOAD_ATTRIBUTE(attribute, 3, float, model.meshes[meshIndex].vertices) // Transform the vertices float *vertices = model.meshes[meshIndex].vertices; for (unsigned int k = 0; k < attribute->count; k++) { // Vector3 vt = worldMatrix * CLITERAL(Vector3){ vertices[3*k], vertices[3*k+1], vertices[3*k+2] }; // Vector3 vt = { vertices[3*(size_t)k], vertices[3*(size_t)k+1], vertices[3*(size_t)k+2]}; Vector3 vt = Vector3Transform((Vector3){ vertices[3*k], vertices[3*k+1], vertices[3*k+2] }, worldMatrix); // fprintf(doutput, "%f, %f, %f\n", vt.x, vt.y, vt.z); vertices[3*(size_t)k] = vt.x; vertices[3*(size_t)k+1] = vt.y; vertices[3*(size_t)k+2] = vt.z; } } 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, vec3, float { cgltf_accessor *attribute = mesh->primitives[p].attributes[j].data; if ((attribute->type == cgltf_type_vec3) && (attribute->component_type == cgltf_component_type_r_32f)) { // Init raylib mesh normals to copy glTF attribute data model.meshes[meshIndex].normals = (float*)RL_MALLOC(attribute->count*3*sizeof(float)); // Load 3 components of float data type into mesh.normals LOAD_ATTRIBUTE(attribute, 3, float, model.meshes[meshIndex].normals) // Transform the normals float *normals = model.meshes[meshIndex].normals; for (unsigned int k = 0; k < attribute->count; k++) { //Vector3 nt = worldMatrixNormals * CLITERAL(Vector3){ normals[3*k], normals[3*k+1], normals[3*k+2] }; Vector3 nt = Vector3Transform(CLITERAL(Vector3){ normals[3*k], normals[3*k+1], normals[3*k+2] }, worldMatrixNormals); normals[3*k] = nt.x; normals[3*k+1] = nt.y; normals[3*k+2] = nt.z; } } else TRACELOG(LOG_WARNING, "MODEL: [%s] Normal attribute data format not supported, use vec3 float", fileName); } else if (mesh->primitives[p].attributes[j].type == cgltf_attribute_type_tangent) // TANGENT, vec3, float { cgltf_accessor *attribute = mesh->primitives[p].attributes[j].data; if ((attribute->type == cgltf_type_vec4) && (attribute->component_type == cgltf_component_type_r_32f)) { // Init raylib mesh tangent to copy glTF attribute data model.meshes[meshIndex].tangents = (float*)RL_MALLOC(attribute->count*4*sizeof(float)); // Load 4 components of float data type into mesh.tangents LOAD_ATTRIBUTE(attribute, 4, float, model.meshes[meshIndex].tangents) // Transform the tangents float *tangents = model.meshes[meshIndex].tangents; for (unsigned int k = 0; k < attribute->count; k++) { //Vector3 tt = worldMatrix * CLITERAL(Vector3){tangents[3*k], tangents[3*k+1], tangents[3*k+2]}; Vector3 tt = Vector3Transform(CLITERAL(Vector3){tangents[3*k], tangents[3*k+1], tangents[3*k+2]}, worldMatrix); tangents[3*k] = tt.x; tangents[3*k+1] = tt.y; tangents[3*k+2] = tt.z; } } 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_n, vec2, float/u8n/u16n { // Support up to 2 texture coordinates attributes float *texcoordPtr = NULL; cgltf_accessor *attribute = mesh->primitives[p].attributes[j].data; if (attribute->type == cgltf_type_vec2) { if (attribute->component_type == cgltf_component_type_r_32f) // vec2, float { // Init raylib mesh texcoords to copy glTF attribute data texcoordPtr = (float *)RL_MALLOC(attribute->count*2*sizeof(float)); // Load 3 components of float data type into mesh.texcoords LOAD_ATTRIBUTE(attribute, 2, float, texcoordPtr) } else if (attribute->component_type == cgltf_component_type_r_8u) // vec2, u8n { // Init raylib mesh texcoords to copy glTF attribute data texcoordPtr = (float *)RL_MALLOC(attribute->count*2*sizeof(float)); // Load data into a temp buffer to be converted to raylib data type unsigned char *temp = (unsigned char *)RL_MALLOC(attribute->count*2*sizeof(unsigned char)); LOAD_ATTRIBUTE(attribute, 2, unsigned char, temp) // Convert data to raylib texcoord data type (float) for (unsigned int t = 0; t < attribute->count*2; t++) texcoordPtr[t] = (float)temp[t]/255.0f; RL_FREE(temp); } else if (attribute->component_type == cgltf_component_type_r_16u) // vec2, u16n { // Init raylib mesh texcoords to copy glTF attribute data texcoordPtr = (float *)RL_MALLOC(attribute->count*2*sizeof(float)); // Load data into a temp buffer to be converted to raylib data type unsigned short *temp = (unsigned short *)RL_MALLOC(attribute->count*2*sizeof(unsigned short)); LOAD_ATTRIBUTE(attribute, 2, unsigned short, temp) // Convert data to raylib texcoord data type (float) for (unsigned int t = 0; t < attribute->count*2; t++) texcoordPtr[t] = (float)temp[t]/65535.0f; RL_FREE(temp); } else TRACELOG(LOG_WARNING, "MODEL: [%s] Texcoords attribute data format not supported", fileName); } else TRACELOG(LOG_WARNING, "MODEL: [%s] Texcoords attribute data format not supported, use vec2 float", fileName); int index = mesh->primitives[p].attributes[j].index; if (index == 0) model.meshes[meshIndex].texcoords = texcoordPtr; else if (index == 1) model.meshes[meshIndex].texcoords2 = texcoordPtr; else { TRACELOG(LOG_WARNING, "MODEL: [%s] No more than 2 texture coordinates attributes supported", fileName); if (texcoordPtr != NULL) RL_FREE(texcoordPtr); } } else if (mesh->primitives[p].attributes[j].type == cgltf_attribute_type_color) // COLOR_n, vec3/vec4, float/u8n/u16n { 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->type == cgltf_type_vec3) // RGB { if (attribute->component_type == cgltf_component_type_r_8u) { // Init raylib mesh color to copy glTF attribute data model.meshes[meshIndex].colors = (uint8_t*)RL_MALLOC(attribute->count * 4 * sizeof(uint8_t)); // Load data into a temp buffer to be converted to raylib data type unsigned char *temp = (unsigned char*)RL_MALLOC(attribute->count*3*sizeof(unsigned char)); LOAD_ATTRIBUTE(attribute, 3, unsigned char, temp) // Convert data to raylib color data type (4 bytes) for (unsigned int c = 0, k = 0; c < (attribute->count*4 - 3); c += 4, k += 3) { model.meshes[meshIndex].colors[c] = temp[k] / 255.0f; model.meshes[meshIndex].colors[c + 1] = temp[k + 1] / 255.0f; model.meshes[meshIndex].colors[c + 2] = temp[k + 2] / 255.0f; model.meshes[meshIndex].colors[c + 3] = 1.0f; } RL_FREE(temp); } else if (attribute->component_type == cgltf_component_type_r_16u) { // Init raylib mesh color to copy glTF attribute data model.meshes[meshIndex].colors = (uint8_t*)RL_MALLOC(attribute->count * 4 * sizeof(uint8_t)); // Load data into a temp buffer to be converted to raylib data type unsigned short *temp = (unsigned short *)RL_MALLOC(attribute->count*3*sizeof(unsigned short)); LOAD_ATTRIBUTE(attribute, 3, unsigned short, temp) // Convert data to raylib color data type (4 bytes) for (unsigned int c = 0, k = 0; c < (attribute->count*4 - 3); c += 4, k += 3) { model.meshes[meshIndex].colors[c] = (unsigned char)(((float)temp[k] / 255)); model.meshes[meshIndex].colors[c + 1] = (unsigned char)(((float)temp[k + 1] / 255)); model.meshes[meshIndex].colors[c + 2] = (unsigned char)(((float)temp[k + 2] / 255)); model.meshes[meshIndex].colors[c + 3] = (uint8_t)(255); } RL_FREE(temp); } else if (attribute->component_type == cgltf_component_type_r_32f) { // Init raylib mesh color to copy glTF attribute data model.meshes[meshIndex].colors = (uint8_t*)RL_MALLOC(attribute->count*4*sizeof(uint8_t)); // Load data into a temp buffer to be converted to raylib data type float *temp = (float *)RL_MALLOC(attribute->count*3*sizeof(float)); LOAD_ATTRIBUTE(attribute, 3, float, temp) // Convert data to raylib color data type (4 bytes) for (unsigned int c = 0, k = 0; c < (attribute->count*4 - 3); c += 4, k += 3) { model.meshes[meshIndex].colors[c] = (unsigned char)(temp[k] * 255.0f); model.meshes[meshIndex].colors[c + 1] = (unsigned char)(temp[k + 1] * 255.0f); model.meshes[meshIndex].colors[c + 2] = (unsigned char)(temp[k + 2] * 255.0f); model.meshes[meshIndex].colors[c + 3] = (uint8_t)(255); } RL_FREE(temp); } else TRACELOG(LOG_WARNING, "MODEL: [%s] Color attribute data format not supported", fileName); } else if (attribute->type == cgltf_type_vec4) // RGBA { if (attribute->component_type == cgltf_component_type_r_8u) { // Init raylib mesh color to copy glTF attribute data model.meshes[meshIndex].colors = (uint8_t*)RL_MALLOC(attribute->count*4*sizeof(uint8_t)); unsigned char *temp = (unsigned char*)RL_MALLOC(attribute->count*4*sizeof(unsigned char)); LOAD_ATTRIBUTE(attribute, 4, unsigned char, temp) for (unsigned int c = 0; c < attribute->count*4; c++) model.meshes[meshIndex].colors[c] = temp[c]; RL_FREE(temp); } else if (attribute->component_type == cgltf_component_type_r_16u) { // Init raylib mesh color to copy glTF attribute data model.meshes[meshIndex].colors = (uint8_t*)RL_MALLOC(attribute->count*4*sizeof(uint8_t)); // Load data into a temp buffer to be converted to raylib data type uint16_t *temp = (uint16_t*)RL_MALLOC(attribute->count*4*sizeof(uint16_t)); LOAD_ATTRIBUTE(attribute, 4, uint16_t, temp) // Convert data to raylib color data type (4 bytes) for (unsigned int c = 0; c < attribute->count*4; c++) model.meshes[meshIndex].colors[c] = (((uint8_t)(temp[c] / 255))); RL_FREE(temp); } else if (attribute->component_type == cgltf_component_type_r_32f) { // Init raylib mesh color to copy glTF attribute data model.meshes[meshIndex].colors = (uint8_t*)RL_MALLOC(attribute->count * 4 * sizeof(uint8_t)); // Load data into a temp buffer to be converted to raylib data type float *temp = (float*)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 (unsigned int c = 0; c < attribute->count*4; c++) model.meshes[meshIndex].colors[c] = (uint8_t)(temp[c] * 255); RL_FREE(temp); } else TRACELOG(LOG_WARNING, "MODEL: [%s] Color attribute data format not supported", fileName); } 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) && (mesh->primitives[p].indices->buffer_view != NULL)) { cgltf_accessor *attribute = mesh->primitives[p].indices; model.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 model.meshes[meshIndex].indices = (uint32_t*)RL_MALLOC(attribute->count*sizeof(uint32_t)); // Load unsigned short data type into mesh.indices LOAD_ATTRIBUTE(attribute, 1, unsigned short, model.meshes[meshIndex].indices) } else if (attribute->component_type == cgltf_component_type_r_8u) { // Init raylib mesh indices to copy glTF attribute data model.meshes[meshIndex].indices = (uint32_t*)RL_MALLOC(attribute->count*sizeof(uint32_t)); LOAD_ATTRIBUTE_CAST(attribute, 1, unsigned char, model.meshes[meshIndex].indices, uint32_t) } else if (attribute->component_type == cgltf_component_type_r_32u) { // Init raylib mesh indices to copy glTF attribute data model.meshes[meshIndex].indices = (uint32_t*)RL_MALLOC(attribute->count*sizeof(uint32_t)); LOAD_ATTRIBUTE_CAST(attribute, 1, unsigned int, model.meshes[meshIndex].indices, uint32_t) TRACELOG(LOG_WARNING, "MODEL: [%s] Indices data converted from u32 to u16, possible loss of data", fileName); } else { TRACELOG(LOG_WARNING, "MODEL: [%s] Indices data format not supported, use u16", fileName); } } else model.meshes[meshIndex].triangleCount = model.meshes[meshIndex].vertexCount/3; // Unindexed mesh // Assign to the primitive mesh the corresponding material index // NOTE: If no material defined, mesh uses the already assigned default material (index: 0) for (unsigned int m = 0; m < data->materials_count; m++) { // The primitive actually keeps the pointer to the corresponding material, // raylib instead assigns to the mesh the by its index, as loaded in model.materials array // To get the index, we check if material pointers match, and we assign the corresponding index, // skipping index 0, the default material if (&data->materials[m] == mesh->primitives[p].material) { model.meshMaterial[meshIndex] = m + 1; break; } } meshIndex++; // Move to next mesh } // fclose(doutput); } // Load glTF meshes animation data // REF: https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#skins // REF: https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#skinned-mesh-attributes // // LIMITATIONS: // - Only supports 1 armature per file, and skips loading it if there are multiple armatures // - Only supports linear interpolation (default method in Blender when checked "Always Sample Animations" when exporting a GLTF file) // - Only supports translation/rotation/scale animation channel.path, weights not considered (i.e. morph targets) //---------------------------------------------------------------------------------------------------- if (data->skins_count > 0) { cgltf_skin skin = data->skins[0]; model.bones = LoadBoneInfoGLTF(skin, &model.boneCount); model.bindPose = (Transform*)RL_MALLOC(model.boneCount*sizeof(Transform)); for (int i = 0; i < model.boneCount; i++) { cgltf_node *node = skin.joints[i]; cgltf_float worldTransform[16]; cgltf_node_transform_world(node, worldTransform); Matrix worldMatrix = { worldTransform[0], worldTransform[4], worldTransform[8], worldTransform[12], worldTransform[1], worldTransform[5], worldTransform[9], worldTransform[13], worldTransform[2], worldTransform[6], worldTransform[10], worldTransform[14], worldTransform[3], worldTransform[7], worldTransform[11], worldTransform[15] }; worldMatrix = MatrixTranspose(worldMatrix); MatrixDecompose(worldMatrix, &(model.bindPose[i].translation), &(model.bindPose[i].rotation), &(model.bindPose[i].scale)); } } if (data->skins_count > 1) { TRACELOG(LOG_WARNING, "MODEL: [%s] can only load one skin (armature) per model, but gltf skins_count == %i", fileName, data->skins_count); } meshIndex = 0; for (unsigned int i = 0; i < data->nodes_count; i++) { cgltf_node *node = &(data->nodes[i]); cgltf_mesh *mesh = node->mesh; if (!mesh) continue; for (unsigned int p = 0; p < mesh->primitives_count; p++) { // NOTE: We only support primitives defined by triangles if (mesh->primitives[p].type != cgltf_primitive_type_triangles) continue; for (unsigned int j = 0; j < mesh->primitives[p].attributes_count; j++) { // NOTE: JOINTS_1 + WEIGHT_1 will be used for +4 joints influencing a vertex -> Not supported by raylib if (mesh->primitives[p].attributes[j].type == cgltf_attribute_type_joints) // JOINTS_n (vec4: 4 bones max per vertex / u8, u16) { cgltf_accessor *attribute = mesh->primitives[p].attributes[j].data; // NOTE: JOINTS_n can only be vec4 and u8/u16 // SPECS: https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#meshes-overview // WARNING: raylib only supports model.meshes[].boneIds as u8 (unsigned char), // if data is provided in any other format, it is converted to supported format but // it could imply data loss (a warning message is issued in that case) if (attribute->type == cgltf_type_vec4) { if (attribute->component_type == cgltf_component_type_r_8u) { // Init raylib mesh boneIds to copy glTF attribute data model.meshes[meshIndex].boneIds = (unsigned char*)RL_CALLOC(model.meshes[meshIndex].vertexCount*4, sizeof(unsigned char)); // Load attribute: vec4, u8 (unsigned char) LOAD_ATTRIBUTE(attribute, 4, unsigned char, model.meshes[meshIndex].boneIds) model.meshes[meshIndex].boneIDFormat = RGVertexFormat_Uint8x4; } else if (attribute->component_type == cgltf_component_type_r_16u) { // Init raylib mesh boneIds to copy glTF attribute data //model.meshes[meshIndex].boneIds = (unsigned char*)RL_CALLOC(model.meshes[meshIndex].vertexCount*4, sizeof(unsigned char)); // Load data into a temp buffer to be converted to raylib data type model.meshes[meshIndex].boneIds = (unsigned char*)RL_CALLOC(model.meshes[meshIndex].vertexCount*4, sizeof(unsigned short)); LOAD_ATTRIBUTE(attribute, 4, unsigned short, model.meshes[meshIndex].boneIds) model.meshes[meshIndex].boneIDFormat = RGVertexFormat_Uint16x4; } else TRACELOG(LOG_WARNING, "MODEL: [%s] Joint attribute data format not supported", fileName); } else TRACELOG(LOG_WARNING, "MODEL: [%s] Joint attribute data format not supported", fileName); } else if (mesh->primitives[p].attributes[j].type == cgltf_attribute_type_weights) // WEIGHTS_n (vec4, u8n/u16n/f32) { cgltf_accessor *attribute = mesh->primitives[p].attributes[j].data; if (attribute->type == cgltf_type_vec4) { // TODO: Support component types: u8, u16? if (attribute->component_type == cgltf_component_type_r_8u) { // Init raylib mesh bone weight to copy glTF attribute data model.meshes[meshIndex].boneWeights = (float*)RL_CALLOC(model.meshes[meshIndex].vertexCount*4, sizeof(float)); // Load data into a temp buffer to be converted to raylib data type unsigned char *temp = (unsigned char*)RL_MALLOC(attribute->count*4*sizeof(unsigned char)); LOAD_ATTRIBUTE(attribute, 4, unsigned char, temp) // Convert data to raylib bone weight data type (4 bytes) for (unsigned int b = 0; b < attribute->count*4; b++) model.meshes[meshIndex].boneWeights[b] = (float)temp[b]/255.0f; RL_FREE(temp); } else if (attribute->component_type == cgltf_component_type_r_16u) { // Init raylib mesh bone weight to copy glTF attribute data model.meshes[meshIndex].boneWeights = (float*)RL_CALLOC(model.meshes[meshIndex].vertexCount*4, sizeof(float)); // Load data into a temp buffer to be converted to raylib data type unsigned short *temp = (unsigned short*)RL_MALLOC(attribute->count*4*sizeof(unsigned short)); LOAD_ATTRIBUTE(attribute, 4, unsigned short, temp) // Convert data to raylib bone weight data type for (unsigned int b = 0; b < attribute->count*4; b++) model.meshes[meshIndex].boneWeights[b] = (float)temp[b]/65535.0f; RL_FREE(temp); } else if (attribute->component_type == cgltf_component_type_r_32f) { // Init raylib mesh bone weight to copy glTF attribute data model.meshes[meshIndex].boneWeights = (float*)RL_CALLOC(model.meshes[meshIndex].vertexCount*4, sizeof(float)); // Load 4 components of float data type into mesh.boneWeights // for cgltf_attribute_type_weights we have: // - data.meshes[0] (256 vertices) // - 256 values, provided as cgltf_type_vec4 of float (4 byte per joint, stride 16) LOAD_ATTRIBUTE(attribute, 4, float, model.meshes[meshIndex].boneWeights) } else TRACELOG(LOG_WARNING, "MODEL: [%s] Joint weight attribute data format not supported, use vec4 float", fileName); } else TRACELOG(LOG_WARNING, "MODEL: [%s] Joint weight attribute data format not supported, use vec4 float", fileName); } } // Animated vertex data model.meshes[meshIndex].animVertices = (float*)RL_CALLOC(model.meshes[meshIndex].vertexCount*3, sizeof(float)); memcpy(model.meshes[meshIndex].animVertices, model.meshes[meshIndex].vertices, model.meshes[meshIndex].vertexCount*3*sizeof(float)); model.meshes[meshIndex].animNormals = (float*)RL_CALLOC(model.meshes[meshIndex].vertexCount*3, sizeof(float)); if (model.meshes[meshIndex].normals != NULL) { memcpy(model.meshes[meshIndex].animNormals, model.meshes[meshIndex].normals, model.meshes[meshIndex].vertexCount*3*sizeof(float)); } // Bone Transform Matrices model.meshes[meshIndex].boneCount = model.boneCount; model.meshes[meshIndex].boneMatrices = (Matrix*)RL_CALLOC(model.meshes[meshIndex].boneCount, sizeof(Matrix)); for (int j = 0; j < model.meshes[meshIndex].boneCount; j++) { model.meshes[meshIndex].boneMatrices[j] = MatrixIdentity(); } 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); // WARNING: cgltf requires the file pointer available while reading data UnloadFileData(fileData); return model; } // Update model animated bones transform matrices for a given frame // NOTE: Updated data is not uploaded to GPU but kept at model.meshes[i].boneMatrices[boneId], // to be uploaded to shader at drawing, in case GPU skinning is enabled void UpdateModelAnimationBones(Model model, ModelAnimation anim, int frame) { if ((anim.frameCount > 0) && (anim.bones != NULL) && (anim.framePoses != NULL)) { if (frame >= anim.frameCount) frame = frame%anim.frameCount; // Get first mesh which have bones int firstMeshWithBones = -1; for (int i = 0; i < model.meshCount; i++) { if (model.meshes[i].boneMatrices) { if (firstMeshWithBones == -1) { firstMeshWithBones = i; break; } } } // Update all bones and boneMatrices of first mesh with bones. for (int boneId = 0; boneId < anim.boneCount; boneId++) { Vector3 inTranslation = model.bindPose[boneId].translation; Quaternion inRotation = model.bindPose[boneId].rotation; Vector3 inScale = model.bindPose[boneId].scale; Vector3 outTranslation = anim.framePoses[frame][boneId].translation; Quaternion outRotation = anim.framePoses[frame][boneId].rotation; Vector3 outScale = anim.framePoses[frame][boneId].scale; Quaternion invRotation = QuaternionInvert(inRotation); Vector3 invTranslation = Vector3RotateByQuaternion(Vector3Negate(inTranslation), invRotation); Vector3 invScale = Vector3Divide(CLITERAL(Vector3){ 1.0f, 1.0f, 1.0f }, inScale); Vector3 boneTranslation = Vector3Add(Vector3RotateByQuaternion( Vector3Multiply(outScale, invTranslation), outRotation), outTranslation); Quaternion boneRotation = QuaternionMultiply(outRotation, invRotation); Vector3 boneScale = Vector3Multiply(outScale, invScale); Matrix boneMatrix = (MatrixMultiplySwap(MatrixMultiplySwap( QuaternionToMatrix(boneRotation), MatrixTranslate(boneTranslation.x, boneTranslation.y, boneTranslation.z)), MatrixScale(boneScale.x, boneScale.y, boneScale.z))); model.meshes[firstMeshWithBones].boneMatrices[boneId] = boneMatrix; } // Update remaining meshes with bones // NOTE: Using deep copy because shallow copy results in double free with 'UnloadModel()' if (firstMeshWithBones != -1) { for (int i = firstMeshWithBones + 1; i < model.meshCount; i++) { if (model.meshes[i].boneMatrices) { memcpy(model.meshes[i].boneMatrices, model.meshes[firstMeshWithBones].boneMatrices, model.meshes[i].boneCount*sizeof(model.meshes[i].boneMatrices[0])); } } } } } // at least 2x speed up vs the old method // Update model animated vertex data (positions and normals) for a given frame // NOTE: Updated data is uploaded to GPU void UpdateModelAnimation(Model model, ModelAnimation anim, int frame) { UpdateModelAnimationBones(model,anim,frame); for (int m = 0; m < model.meshCount; m++) { Mesh mesh = model.meshes[m]; Vector3 animVertex = {0}; Vector3 animNormal = {0}; int boneId = 0; int boneCounter = 0; float boneWeight = 0.0; bool updated = false; // Flag to check when anim vertex information is updated const int vValues = mesh.vertexCount*3; // Skip if missing bone data, causes segfault without on some models if ((mesh.boneWeights == NULL) || (mesh.boneIds == NULL)) continue; for (int vCounter = 0; vCounter < vValues; vCounter += 3) { mesh.animVertices[vCounter] = 0; mesh.animVertices[vCounter + 1] = 0; mesh.animVertices[vCounter + 2] = 0; if (mesh.animNormals != NULL) { mesh.animNormals[vCounter] = 0; mesh.animNormals[vCounter + 1] = 0; mesh.animNormals[vCounter + 2] = 0; } // Iterates over 4 bones per vertex for (int j = 0; j < 4; j++, boneCounter++) { boneWeight = mesh.boneWeights[boneCounter]; boneId = mesh.boneIds[boneCounter]; // Early stop when no transformation will be applied if (boneWeight == 0.0f) continue; animVertex = CLITERAL(Vector3){ mesh.vertices[vCounter], mesh.vertices[vCounter + 1], mesh.vertices[vCounter + 2] }; animVertex = Vector3Transform(animVertex,model.meshes[m].boneMatrices[boneId]); mesh.animVertices[vCounter] += animVertex.x*boneWeight; mesh.animVertices[vCounter+1] += animVertex.y*boneWeight; mesh.animVertices[vCounter+2] += animVertex.z*boneWeight; updated = true; // Normals processing // NOTE: We use meshes.baseNormals (default normal) to calculate meshes.normals (animated normals) if ((mesh.normals != NULL) && (mesh.animNormals != NULL )) { animNormal = CLITERAL(Vector3){ mesh.normals[vCounter], mesh.normals[vCounter + 1], mesh.normals[vCounter + 2] }; animNormal = Vector3Transform(animNormal, MatrixTranspose(MatrixInvert(model.meshes[m].boneMatrices[boneId]))); mesh.animNormals[vCounter] += animNormal.x*boneWeight; mesh.animNormals[vCounter + 1] += animNormal.y*boneWeight; mesh.animNormals[vCounter + 2] += animNormal.z*boneWeight; } } } if (updated) { BufferData((mesh.vbos[0]), mesh.animVertices, mesh.vertexCount * 3 * sizeof(float)); // Update vertex position if (mesh.normals != NULL) BufferData((mesh.vbos[2]), mesh.animNormals, mesh.vertexCount * 3 * sizeof(float)); // Update vertex normals } } } // Load model animations from file ModelAnimation *LoadModelAnimations(const char *fileName, int *animCount){ ModelAnimation *animations = NULL; #if defined(SUPPORT_FILEFORMAT_IQM) if (IsFileExtension(fileName, ".iqm")) animations = LoadModelAnimationsIQM(fileName, animCount); #endif #if defined(SUPPORT_FILEFORMAT_M3D) if (IsFileExtension(fileName, ".m3d")) animations = LoadModelAnimationsM3D(fileName, animCount); #endif #if defined(SUPPORT_FILEFORMAT_GLTF) #endif if (IsFileExtension(fileName, ".gltf;.glb")) animations = LoadModelAnimationsGLTF(fileName, animCount); return animations; } Model LoadModel(const char *fileName){ Model model = {0}; if (IsFileExtension(fileName, ".obj")) model = LoadOBJ(fileName); else if (IsFileExtension(fileName, ".glb")) model = LoadGLTF(fileName); return model; } // // // ========== MESH GENERATION ========== // // Mesh GenMeshPoly(int sides, float radius) { Mesh mesh = {0}; if (sides < 3) return mesh; // Security check int vertexCount = sides*3; // Vertices definition Vector3 *vertices = (Vector3 *)RL_MALLOC(vertexCount*sizeof(Vector3)); float d = 0.0f, dStep = 360.0f/sides; for (int v = 0; v < vertexCount - 2; v += 3) { vertices[v] = CLITERAL(Vector3){ 0.0f, 0.0f, 0.0f }; vertices[v + 1] = CLITERAL(Vector3){ sinf(DEG2RAD*d)*radius, 0.0f, cosf(DEG2RAD*d)*radius }; vertices[v + 2] = CLITERAL(Vector3){ sinf(DEG2RAD*(d+dStep))*radius, 0.0f, cosf(DEG2RAD*(d+dStep))*radius }; d += dStep; } // Normals definition Vector3 *normals = (Vector3 *)RL_MALLOC(vertexCount*sizeof(Vector3)); for (int n = 0; n < vertexCount; n++) normals[n] = CLITERAL(Vector3){ 0.0f, 1.0f, 0.0f }; // Vector3.up; // TexCoords definition Vector2 *texcoords = (Vector2 *)RL_MALLOC(vertexCount*sizeof(Vector2)); for (int n = 0; n < vertexCount; n++) texcoords[n] = CLITERAL(Vector2){ 0.0f, 0.0f }; mesh.vertexCount = vertexCount; mesh.triangleCount = sides; mesh.vertices = (float *)RL_MALLOC(mesh.vertexCount*3*sizeof(float)); mesh.texcoords = (float *)RL_MALLOC(mesh.vertexCount*2*sizeof(float)); mesh.normals = (float *)RL_MALLOC(mesh.vertexCount*3*sizeof(float)); // Mesh vertices position array for (int i = 0; i < mesh.vertexCount; i++) { mesh.vertices[3*i] = vertices[i].x; mesh.vertices[3*i + 1] = vertices[i].y; mesh.vertices[3*i + 2] = vertices[i].z; } // Mesh texcoords array for (int i = 0; i < mesh.vertexCount; i++) { mesh.texcoords[2*i] = texcoords[i].x; mesh.texcoords[2*i + 1] = texcoords[i].y; } // Mesh normals array for (int i = 0; i < mesh.vertexCount; i++) { mesh.normals[3*i] = normals[i].x; mesh.normals[3*i + 1] = normals[i].y; mesh.normals[3*i + 2] = normals[i].z; } RL_FREE(vertices); RL_FREE(normals); RL_FREE(texcoords); // Upload vertex data to GPU (static mesh) // NOTE: mesh.vboId array is allocated inside UploadMesh() UploadMesh(&mesh, false); return mesh; } // Generate plane mesh (with subdivisions) Mesh GenMeshPlane(float width, float length, int resX, int resZ) { Mesh mesh = {0}; #define CUSTOM_MESH_GEN_PLANE #if defined(CUSTOM_MESH_GEN_PLANE) resX++; resZ++; // Vertices definition int vertexCount = resX*resZ; // vertices get reused for the faces Vector3 *vertices = (Vector3 *)RL_MALLOC(vertexCount*sizeof(Vector3)); for (int z = 0; z < resZ; z++) { // [-length/2, length/2] float zPos = ((float)z/(resZ - 1) - 0.5f)*length; for (int x = 0; x < resX; x++) { // [-width/2, width/2] float xPos = ((float)x/(resX - 1) - 0.5f)*width; vertices[x + z*resX] = CLITERAL(Vector3){ xPos, 0.0f, zPos }; } } // Normals definition Vector3 *normals = (Vector3 *)RL_MALLOC(vertexCount*sizeof(Vector3)); for (int n = 0; n < vertexCount; n++) normals[n] = CLITERAL(Vector3){ 0.0f, 1.0f, 0.0f }; // Vector3.up; // TexCoords definition Vector2 *texcoords = (Vector2 *)RL_MALLOC(vertexCount*sizeof(Vector2)); for (int v = 0; v < resZ; v++) { for (int u = 0; u < resX; u++) { texcoords[u + v*resX] = CLITERAL(Vector2){ (float)u/(resX - 1), (float)v/(resZ - 1) }; } } // Triangles definition (indices) int numFaces = (resX - 1)*(resZ - 1); int *triangles = (int *)RL_MALLOC(numFaces*6*sizeof(int)); int t = 0; for (int face = 0; face < numFaces; face++) { // Retrieve lower left corner from face ind int i = face + face/(resX - 1); triangles[t++] = i + resX; triangles[t++] = i + 1; triangles[t++] = i; triangles[t++] = i + resX; triangles[t++] = i + resX + 1; triangles[t++] = i + 1; } mesh.vertexCount = vertexCount; mesh.triangleCount = numFaces*2; mesh.vertices = (float *)RL_MALLOC(mesh.vertexCount * 3 * sizeof(float)); mesh.texcoords = (float *)RL_MALLOC(mesh.vertexCount * 2 * sizeof(float)); mesh.normals = (float *)RL_MALLOC(mesh.vertexCount * 3 * sizeof(float)); mesh.indices = (uint32_t*) RL_MALLOC(mesh.triangleCount * 3 * sizeof(uint32_t)); // Mesh vertices position array for (int i = 0; i < mesh.vertexCount; i++) { mesh.vertices[3*i] = vertices[i].x; mesh.vertices[3*i + 1] = vertices[i].y; mesh.vertices[3*i + 2] = vertices[i].z; } // Mesh texcoords array for (int i = 0; i < mesh.vertexCount; i++) { mesh.texcoords[2*i] = texcoords[i].x; mesh.texcoords[2*i + 1] = texcoords[i].y; } // Mesh normals array for (int i = 0; i < mesh.vertexCount; i++) { mesh.normals[3*i] = normals[i].x; mesh.normals[3*i + 1] = normals[i].y; mesh.normals[3*i + 2] = normals[i].z; } // Mesh indices array initialization for (int i = 0; i < mesh.triangleCount*3; i++) mesh.indices[i] = triangles[i]; RL_FREE(vertices); RL_FREE(normals); RL_FREE(texcoords); RL_FREE(triangles); #else // Use par_shapes library to generate plane mesh par_shapes_mesh *plane = par_shapes_create_plane(resX, resZ); // No normals/texcoords generated!!! par_shapes_scale(plane, width, length, 1.0f); par_shapes_rotate(plane, -M_PIPI/2.0f,v3_xunit); par_shapes_translate(plane, -width/2, 0.0f, length/2); mesh.vertices = (float *)RL_MALLOC(plane->ntriangles*3*3*sizeof(float)); mesh.texcoords = (float *)RL_MALLOC(plane->ntriangles*3*2*sizeof(float)); mesh.normals = (float *)RL_MALLOC(plane->ntriangles*3*3*sizeof(float)); mesh.vertexCount = plane->ntriangles*3; mesh.triangleCount = plane->ntriangles; for (int k = 0; k < mesh.vertexCount; k++) { mesh.vertices[k*3] = plane->points[plane->triangles[k]*3]; mesh.vertices[k*3 + 1] = plane->points[plane->triangles[k]*3 + 1]; mesh.vertices[k*3 + 2] = plane->points[plane->triangles[k]*3 + 2]; mesh.normals[k*3] = plane->normals[plane->triangles[k]*3]; mesh.normals[k*3 + 1] = plane->normals[plane->triangles[k]*3 + 1]; mesh.normals[k*3 + 2] = plane->normals[plane->triangles[k]*3 + 2]; mesh.texcoords[k*2] = plane->tcoords[plane->triangles[k]*2]; mesh.texcoords[k*2 + 1] = plane->tcoords[plane->triangles[k]*2 + 1]; } par_shapes_free_mesh(plane); #endif // Upload vertex data to GPU (static mesh) UploadMesh(&mesh, false); return mesh; } // Generated cuboid mesh Mesh GenMeshCube(float width, float height, float length) { Mesh mesh = {0}; #define CUSTOM_MESH_GEN_CUBE #if defined(CUSTOM_MESH_GEN_CUBE) float vertices[] = { -width/2, -height/2, length/2, width/2, -height/2, length/2, width/2, height/2, length/2, -width/2, height/2, length/2, -width/2, -height/2, -length/2, -width/2, height/2, -length/2, width/2, height/2, -length/2, width/2, -height/2, -length/2, -width/2, height/2, -length/2, -width/2, height/2, length/2, width/2, height/2, length/2, width/2, height/2, -length/2, -width/2, -height/2, -length/2, width/2, -height/2, -length/2, width/2, -height/2, length/2, -width/2, -height/2, length/2, width/2, -height/2, -length/2, width/2, height/2, -length/2, width/2, height/2, length/2, width/2, -height/2, length/2, -width/2, -height/2, -length/2, -width/2, -height/2, length/2, -width/2, height/2, length/2, -width/2, height/2, -length/2 }; float texcoords[] = { 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f }; float normals[] = { 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,-1.0f, 0.0f, 0.0f,-1.0f, 0.0f, 0.0f,-1.0f, 0.0f, 0.0f,-1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,-1.0f, 0.0f, 0.0f,-1.0f, 0.0f, 0.0f,-1.0f, 0.0f, 0.0f,-1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f }; mesh.vertices = (float *)RL_MALLOC(24*3*sizeof(float)); memcpy(mesh.vertices, vertices, 24*3*sizeof(float)); mesh.texcoords = (float *)RL_MALLOC(24*2*sizeof(float)); memcpy(mesh.texcoords, texcoords, 24*2*sizeof(float)); mesh.normals = (float *)RL_MALLOC(24*3*sizeof(float)); memcpy(mesh.normals, normals, 24*3*sizeof(float)); mesh.indices = (uint32_t *)RL_MALLOC(36*sizeof(uint32_t)); int k = 0; // Indices can be initialized right now for (int i = 0; i < 36; i += 6) { mesh.indices[i] = 4*k; mesh.indices[i + 1] = 4*k + 1; mesh.indices[i + 2] = 4*k + 2; mesh.indices[i + 3] = 4*k; mesh.indices[i + 4] = 4*k + 2; mesh.indices[i + 5] = 4*k + 3; k++; } mesh.vertexCount = 24; mesh.triangleCount = 12; #else // Use par_shapes library to generate cube mesh /* // Platonic solids: par_shapes_mesh *par_shapes_create_tetrahedron(); // 4 sides polyhedron (pyramid) par_shapes_mesh *par_shapes_create_cube(); // 6 sides polyhedron (cube) par_shapes_mesh *par_shapes_create_octahedron(); // 8 sides polyhedron (diamond) par_shapes_mesh *par_shapes_create_dodecahedron(); // 12 sides polyhedron par_shapes_mesh *par_shapes_create_icosahedron(); // 20 sides polyhedron */ // Platonic solid generation: cube (6 sides) // NOTE: No normals/texcoords generated by default par_shapes_mesh *cube = par_shapes_create_cube(); cube->tcoords = PAR_MALLOC(float, 2*cube->npoints); for (int i = 0; i < 2*cube->npoints; i++) cube->tcoords[i] = 0.0f; par_shapes_scale(cube, width, height, length); par_shapes_translate(cube, -width/2, 0.0f, -length/2); par_shapes_compute_normals(cube); mesh.vertices = (float *)RL_MALLOC(cube->ntriangles*3*3*sizeof(float)); mesh.texcoords = (float *)RL_MALLOC(cube->ntriangles*3*2*sizeof(float)); mesh.normals = (float *)RL_MALLOC(cube->ntriangles*3*3*sizeof(float)); mesh.vertexCount = cube->ntriangles*3; mesh.triangleCount = cube->ntriangles; for (int k = 0; k < mesh.vertexCount; k++) { mesh.vertices[k*3] = cube->points[cube->triangles[k]*3]; mesh.vertices[k*3 + 1] = cube->points[cube->triangles[k]*3 + 1]; mesh.vertices[k*3 + 2] = cube->points[cube->triangles[k]*3 + 2]; mesh.normals[k*3] = cube->normals[cube->triangles[k]*3]; mesh.normals[k*3 + 1] = cube->normals[cube->triangles[k]*3 + 1]; mesh.normals[k*3 + 2] = cube->normals[cube->triangles[k]*3 + 2]; mesh.texcoords[k*2] = cube->tcoords[cube->triangles[k]*2]; mesh.texcoords[k*2 + 1] = cube->tcoords[cube->triangles[k]*2 + 1]; } par_shapes_free_mesh(cube); #endif // Upload vertex data to GPU (static mesh) UploadMesh(&mesh, false); return mesh; } // Generate sphere mesh (standard sphere) Mesh GenMeshSphere(float radius, int rings, int slices) { Mesh mesh = {0}; if ((rings >= 3) && (slices >= 3)) { par_shapes_set_epsilon_degenerate_sphere(0.0); par_shapes_mesh *sphere = par_shapes_create_parametric_sphere(slices, rings); par_shapes_scale(sphere, radius, radius, radius); // NOTE: Soft normals are computed internally mesh.vertices = (float *)RL_MALLOC(sphere->ntriangles*3*3*sizeof(float)); mesh.texcoords = (float *)RL_MALLOC(sphere->ntriangles*3*2*sizeof(float)); mesh.normals = (float *)RL_MALLOC(sphere->ntriangles*3*3*sizeof(float)); mesh.vertexCount = sphere->ntriangles*3; mesh.triangleCount = sphere->ntriangles; for (int k = 0; k < mesh.vertexCount; k++) { mesh.vertices[k*3] = sphere->points[sphere->triangles[k]*3]; mesh.vertices[k*3 + 1] = sphere->points[sphere->triangles[k]*3 + 1]; mesh.vertices[k*3 + 2] = sphere->points[sphere->triangles[k]*3 + 2]; mesh.normals[k*3] = sphere->normals[sphere->triangles[k]*3]; mesh.normals[k*3 + 1] = sphere->normals[sphere->triangles[k]*3 + 1]; mesh.normals[k*3 + 2] = sphere->normals[sphere->triangles[k]*3 + 2]; mesh.texcoords[k*2] = sphere->tcoords[sphere->triangles[k]*2]; mesh.texcoords[k*2 + 1] = sphere->tcoords[sphere->triangles[k]*2 + 1]; } par_shapes_free_mesh(sphere); // Upload vertex data to GPU (static mesh) UploadMesh(&mesh, false); } else TRACELOG(LOG_WARNING, "MESH: Failed to generate mesh: sphere"); return mesh; } // Generate hemisphere mesh (half sphere, no bottom cap) Mesh GenMeshHemiSphere(float radius, int rings, int slices) { Mesh mesh = {0}; if ((rings >= 3) && (slices >= 3)) { if (radius < 0.0f) radius = 0.0f; par_shapes_mesh *sphere = par_shapes_create_hemisphere(slices, rings); par_shapes_scale(sphere, radius, radius, radius); // NOTE: Soft normals are computed internally mesh.vertices = (float *)RL_MALLOC(sphere->ntriangles*3*3*sizeof(float)); mesh.texcoords = (float *)RL_MALLOC(sphere->ntriangles*3*2*sizeof(float)); mesh.normals = (float *)RL_MALLOC(sphere->ntriangles*3*3*sizeof(float)); mesh.vertexCount = sphere->ntriangles*3; mesh.triangleCount = sphere->ntriangles; for (int k = 0; k < mesh.vertexCount; k++) { mesh.vertices[k*3] = sphere->points[sphere->triangles[k]*3]; mesh.vertices[k*3 + 1] = sphere->points[sphere->triangles[k]*3 + 1]; mesh.vertices[k*3 + 2] = sphere->points[sphere->triangles[k]*3 + 2]; mesh.normals[k*3] = sphere->normals[sphere->triangles[k]*3]; mesh.normals[k*3 + 1] = sphere->normals[sphere->triangles[k]*3 + 1]; mesh.normals[k*3 + 2] = sphere->normals[sphere->triangles[k]*3 + 2]; mesh.texcoords[k*2] = sphere->tcoords[sphere->triangles[k]*2]; mesh.texcoords[k*2 + 1] = sphere->tcoords[sphere->triangles[k]*2 + 1]; } par_shapes_free_mesh(sphere); // Upload vertex data to GPU (static mesh) UploadMesh(&mesh, false); } else TRACELOG(LOG_WARNING, "MESH: Failed to generate mesh: hemisphere"); return mesh; } // Generate cylinder mesh Mesh GenMeshCylinder(float radius, float height, int slices) { Mesh mesh = {0}; if (slices >= 3) { // Instance a cylinder that sits on the Z=0 plane using the given tessellation // levels across the UV domain. Think of "slices" like a number of pizza // slices, and "stacks" like a number of stacked rings // Height and radius are both 1.0, but they can easily be changed with par_shapes_scale par_shapes_mesh *cylinder = par_shapes_create_cylinder(slices, 8); par_shapes_scale(cylinder, radius, radius, height); par_shapes_rotate(cylinder, -M_PI/2.0f, v3_xunit); // Generate an orientable disk shape (top cap) par_shapes_mesh *capTop = par_shapes_create_disk(radius, slices, v3_zero, v3_zunit); capTop->tcoords = PAR_MALLOC(float, 2*capTop->npoints); for (int i = 0; i < 2*capTop->npoints; i++) capTop->tcoords[i] = 0.0f; par_shapes_rotate(capTop, -M_PI/2.0f,v3_xunit); par_shapes_rotate(capTop, 90*DEG2RAD,v3_yunit); par_shapes_translate(capTop, 0, height, 0); // Generate an orientable disk shape (bottom cap) par_shapes_mesh *capBottom = par_shapes_create_disk(radius, slices, v3_zero, v3_zunit_negative); capBottom->tcoords = PAR_MALLOC(float, 2*capBottom->npoints); for (int i = 0; i < 2*capBottom->npoints; i++) capBottom->tcoords[i] = 0.95f; par_shapes_rotate(capBottom, M_PI/2.0f,v3_xunit); par_shapes_rotate(capBottom, -90*DEG2RAD,v3_yunit); par_shapes_merge_and_free(cylinder, capTop); par_shapes_merge_and_free(cylinder, capBottom); mesh.vertices = (float *)RL_MALLOC(cylinder->ntriangles*3*3*sizeof(float)); mesh.texcoords = (float *)RL_MALLOC(cylinder->ntriangles*3*2*sizeof(float)); mesh.normals = (float *)RL_MALLOC(cylinder->ntriangles*3*3*sizeof(float)); mesh.vertexCount = cylinder->ntriangles*3; mesh.triangleCount = cylinder->ntriangles; for (int k = 0; k < mesh.vertexCount; k++) { mesh.vertices[k*3] = cylinder->points[cylinder->triangles[k]*3]; mesh.vertices[k*3 + 1] = cylinder->points[cylinder->triangles[k]*3 + 1]; mesh.vertices[k*3 + 2] = cylinder->points[cylinder->triangles[k]*3 + 2]; mesh.normals[k*3] = cylinder->normals[cylinder->triangles[k]*3]; mesh.normals[k*3 + 1] = cylinder->normals[cylinder->triangles[k]*3 + 1]; mesh.normals[k*3 + 2] = cylinder->normals[cylinder->triangles[k]*3 + 2]; mesh.texcoords[k*2] = cylinder->tcoords[cylinder->triangles[k]*2]; mesh.texcoords[k*2 + 1] = cylinder->tcoords[cylinder->triangles[k]*2 + 1]; } par_shapes_free_mesh(cylinder); // Upload vertex data to GPU (static mesh) UploadMesh(&mesh, false); } else TRACELOG(LOG_WARNING, "MESH: Failed to generate mesh: cylinder"); return mesh; } // Generate cone/pyramid mesh Mesh GenMeshCone(float radius, float height, int slices) { Mesh mesh = {0}; if (slices >= 3) { // Instance a cone that sits on the Z=0 plane using the given tessellation // levels across the UV domain. Think of "slices" like a number of pizza // slices, and "stacks" like a number of stacked rings // Height and radius are both 1.0, but they can easily be changed with par_shapes_scale par_shapes_mesh *cone = par_shapes_create_cone(slices, 8); par_shapes_scale(cone, radius, radius, height); par_shapes_rotate(cone, -M_PI/2.0f,v3_xunit); par_shapes_rotate(cone, M_PI/2.0f,v3_yunit); // Generate an orientable disk shape (bottom cap) par_shapes_mesh *capBottom = par_shapes_create_disk(radius, slices, v3_zero, v3_zunit_negative); capBottom->tcoords = PAR_MALLOC(float, 2*capBottom->npoints); for (int i = 0; i < 2*capBottom->npoints; i++) capBottom->tcoords[i] = 0.95f; par_shapes_rotate(capBottom, M_PI/2.0f,v3_xunit); par_shapes_merge_and_free(cone, capBottom); mesh.vertices = (float *)RL_MALLOC(cone->ntriangles*3*3*sizeof(float)); mesh.texcoords = (float *)RL_MALLOC(cone->ntriangles*3*2*sizeof(float)); mesh.normals = (float *)RL_MALLOC(cone->ntriangles*3*3*sizeof(float)); mesh.vertexCount = cone->ntriangles*3; mesh.triangleCount = cone->ntriangles; for (int k = 0; k < mesh.vertexCount; k++) { mesh.vertices[k*3] = cone->points[cone->triangles[k]*3]; mesh.vertices[k*3 + 1] = cone->points[cone->triangles[k]*3 + 1]; mesh.vertices[k*3 + 2] = cone->points[cone->triangles[k]*3 + 2]; mesh.normals[k*3] = cone->normals[cone->triangles[k]*3]; mesh.normals[k*3 + 1] = cone->normals[cone->triangles[k]*3 + 1]; mesh.normals[k*3 + 2] = cone->normals[cone->triangles[k]*3 + 2]; mesh.texcoords[k*2] = cone->tcoords[cone->triangles[k]*2]; mesh.texcoords[k*2 + 1] = cone->tcoords[cone->triangles[k]*2 + 1]; } par_shapes_free_mesh(cone); // Upload vertex data to GPU (static mesh) UploadMesh(&mesh, false); } else TRACELOG(LOG_WARNING, "MESH: Failed to generate mesh: cone"); return mesh; } // Generate torus mesh Mesh GenMeshTorus(float radius, float size, int radSeg, int sides) { Mesh mesh = {0}; if ((sides >= 3) && (radSeg >= 3)) { if (radius > 1.0f) radius = 1.0f; else if (radius < 0.1f) radius = 0.1f; // Create a donut that sits on the Z=0 plane with the specified inner radius // The outer radius can be controlled with par_shapes_scale par_shapes_mesh *torus = par_shapes_create_torus(radSeg, sides, radius); par_shapes_scale(torus, size/2, size/2, size/2); mesh.vertices = (float *)RL_MALLOC(torus->ntriangles*3*3*sizeof(float)); mesh.texcoords = (float *)RL_MALLOC(torus->ntriangles*3*2*sizeof(float)); mesh.normals = (float *)RL_MALLOC(torus->ntriangles*3*3*sizeof(float)); mesh.vertexCount = torus->ntriangles*3; mesh.triangleCount = torus->ntriangles; for (int k = 0; k < mesh.vertexCount; k++) { mesh.vertices[k*3] = torus->points[torus->triangles[k]*3]; mesh.vertices[k*3 + 1] = torus->points[torus->triangles[k]*3 + 1]; mesh.vertices[k*3 + 2] = torus->points[torus->triangles[k]*3 + 2]; mesh.normals[k*3] = torus->normals[torus->triangles[k]*3]; mesh.normals[k*3 + 1] = torus->normals[torus->triangles[k]*3 + 1]; mesh.normals[k*3 + 2] = torus->normals[torus->triangles[k]*3 + 2]; mesh.texcoords[k*2] = torus->tcoords[torus->triangles[k]*2]; mesh.texcoords[k*2 + 1] = torus->tcoords[torus->triangles[k]*2 + 1]; } par_shapes_free_mesh(torus); // Upload vertex data to GPU (static mesh) UploadMesh(&mesh, false); } else TRACELOG(LOG_WARNING, "MESH: Failed to generate mesh: torus"); return mesh; } // Generate trefoil knot mesh Mesh GenMeshKnot(float radius, float size, int radSeg, int sides) { Mesh mesh = {0}; if ((sides >= 3) && (radSeg >= 3)) { if (radius > 3.0f) radius = 3.0f; else if (radius < 0.5f) radius = 0.5f; par_shapes_mesh *knot = par_shapes_create_trefoil_knot(radSeg, sides, radius); par_shapes_scale(knot, size, size, size); mesh.vertices = (float *)RL_MALLOC(knot->ntriangles*3*3*sizeof(float)); mesh.texcoords = (float *)RL_MALLOC(knot->ntriangles*3*2*sizeof(float)); mesh.normals = (float *)RL_MALLOC(knot->ntriangles*3*3*sizeof(float)); mesh.vertexCount = knot->ntriangles*3; mesh.triangleCount = knot->ntriangles; for (int k = 0; k < mesh.vertexCount; k++) { mesh.vertices[k*3] = knot->points[knot->triangles[k]*3]; mesh.vertices[k*3 + 1] = knot->points[knot->triangles[k]*3 + 1]; mesh.vertices[k*3 + 2] = knot->points[knot->triangles[k]*3 + 2]; mesh.normals[k*3] = knot->normals[knot->triangles[k]*3]; mesh.normals[k*3 + 1] = knot->normals[knot->triangles[k]*3 + 1]; mesh.normals[k*3 + 2] = knot->normals[knot->triangles[k]*3 + 2]; mesh.texcoords[k*2] = knot->tcoords[knot->triangles[k]*2]; mesh.texcoords[k*2 + 1] = knot->tcoords[knot->triangles[k]*2 + 1]; } par_shapes_free_mesh(knot); // Upload vertex data to GPU (static mesh) UploadMesh(&mesh, false); } else TRACELOG(LOG_WARNING, "MESH: Failed to generate mesh: knot"); return mesh; } // Generate a mesh from heightmap // NOTE: Vertex data is uploaded to GPU Mesh GenMeshHeightmap(Image heightmap, Vector3 size) { #define GRAY_VALUE(c) ((float)(c.r + c.g + c.b)/3.0f) Mesh mesh = {0}; int mapX = heightmap.width; int mapZ = heightmap.height; Color *pixels = LoadImageColors(heightmap); // NOTE: One vertex per pixel mesh.triangleCount = (mapX - 1)*(mapZ - 1)*2; // One quad every four pixels mesh.vertexCount = mesh.triangleCount*3; mesh.vertices = (float *)RL_MALLOC(mesh.vertexCount*3*sizeof(float)); mesh.normals = (float *)RL_MALLOC(mesh.vertexCount*3*sizeof(float)); mesh.texcoords = (float *)RL_MALLOC(mesh.vertexCount*2*sizeof(float)); mesh.colors = NULL; int vCounter = 0; // Used to count vertices float by float int tcCounter = 0; // Used to count texcoords float by float int nCounter = 0; // Used to count normals float by float Vector3 scaleFactor = { size.x/(mapX - 1), size.y/255.0f, size.z/(mapZ - 1) }; Vector3 vA = {0}; Vector3 vB = {0}; Vector3 vC = {0}; Vector3 vN = {0}; for (int z = 0; z < mapZ-1; z++) { for (int x = 0; x < mapX-1; x++) { // Fill vertices array with data //---------------------------------------------------------- // one triangle - 3 vertex mesh.vertices[vCounter] = (float)x*scaleFactor.x; mesh.vertices[vCounter + 1] = GRAY_VALUE(pixels[x + z*mapX])*scaleFactor.y; mesh.vertices[vCounter + 2] = (float)z*scaleFactor.z; mesh.vertices[vCounter + 3] = (float)x*scaleFactor.x; mesh.vertices[vCounter + 4] = GRAY_VALUE(pixels[x + (z + 1)*mapX])*scaleFactor.y; mesh.vertices[vCounter + 5] = (float)(z + 1)*scaleFactor.z; mesh.vertices[vCounter + 6] = (float)(x + 1)*scaleFactor.x; mesh.vertices[vCounter + 7] = GRAY_VALUE(pixels[(x + 1) + z*mapX])*scaleFactor.y; mesh.vertices[vCounter + 8] = (float)z*scaleFactor.z; // Another triangle - 3 vertex mesh.vertices[vCounter + 9] = mesh.vertices[vCounter + 6]; mesh.vertices[vCounter + 10] = mesh.vertices[vCounter + 7]; mesh.vertices[vCounter + 11] = mesh.vertices[vCounter + 8]; mesh.vertices[vCounter + 12] = mesh.vertices[vCounter + 3]; mesh.vertices[vCounter + 13] = mesh.vertices[vCounter + 4]; mesh.vertices[vCounter + 14] = mesh.vertices[vCounter + 5]; mesh.vertices[vCounter + 15] = (float)(x + 1)*scaleFactor.x; mesh.vertices[vCounter + 16] = GRAY_VALUE(pixels[(x + 1) + (z + 1)*mapX])*scaleFactor.y; mesh.vertices[vCounter + 17] = (float)(z + 1)*scaleFactor.z; vCounter += 18; // 6 vertex, 18 floats // Fill texcoords array with data //-------------------------------------------------------------- mesh.texcoords[tcCounter] = (float)x/(mapX - 1); mesh.texcoords[tcCounter + 1] = (float)z/(mapZ - 1); mesh.texcoords[tcCounter + 2] = (float)x/(mapX - 1); mesh.texcoords[tcCounter + 3] = (float)(z + 1)/(mapZ - 1); mesh.texcoords[tcCounter + 4] = (float)(x + 1)/(mapX - 1); mesh.texcoords[tcCounter + 5] = (float)z/(mapZ - 1); mesh.texcoords[tcCounter + 6] = mesh.texcoords[tcCounter + 4]; mesh.texcoords[tcCounter + 7] = mesh.texcoords[tcCounter + 5]; mesh.texcoords[tcCounter + 8] = mesh.texcoords[tcCounter + 2]; mesh.texcoords[tcCounter + 9] = mesh.texcoords[tcCounter + 3]; mesh.texcoords[tcCounter + 10] = (float)(x + 1)/(mapX - 1); mesh.texcoords[tcCounter + 11] = (float)(z + 1)/(mapZ - 1); tcCounter += 12; // 6 texcoords, 12 floats // Fill normals array with data //-------------------------------------------------------------- for (int i = 0; i < 18; i += 9) { vA.x = mesh.vertices[nCounter + i]; vA.y = mesh.vertices[nCounter + i + 1]; vA.z = mesh.vertices[nCounter + i + 2]; vB.x = mesh.vertices[nCounter + i + 3]; vB.y = mesh.vertices[nCounter + i + 4]; vB.z = mesh.vertices[nCounter + i + 5]; vC.x = mesh.vertices[nCounter + i + 6]; vC.y = mesh.vertices[nCounter + i + 7]; vC.z = mesh.vertices[nCounter + i + 8]; vN = Vector3Normalize(Vector3CrossProduct(Vector3Subtract(vB, vA), Vector3Subtract(vC, vA))); mesh.normals[nCounter + i] = vN.x; mesh.normals[nCounter + i + 1] = vN.y; mesh.normals[nCounter + i + 2] = vN.z; mesh.normals[nCounter + i + 3] = vN.x; mesh.normals[nCounter + i + 4] = vN.y; mesh.normals[nCounter + i + 5] = vN.z; mesh.normals[nCounter + i + 6] = vN.x; mesh.normals[nCounter + i + 7] = vN.y; mesh.normals[nCounter + i + 8] = vN.z; } nCounter += 18; // 6 vertex, 18 floats } } UnloadImageColors(pixels); // Unload pixels color data // Upload vertex data to GPU (static mesh) UploadMesh(&mesh, false); return mesh; } // Generate a cubes mesh from pixel data // NOTE: Vertex data is uploaded to GPU Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize) { #define COLOR_EQUAL(col1, col2) ((col1.r == col2.r)&&(col1.g == col2.g)&&(col1.b == col2.b)&&(col1.a == col2.a)) Mesh mesh = {0}; Color *pixels = LoadImageColors(cubicmap); // NOTE: Max possible number of triangles numCubes*(12 triangles by cube) int maxTriangles = cubicmap.width*cubicmap.height*12; int vCounter = 0; // Used to count vertices int tcCounter = 0; // Used to count texcoords int nCounter = 0; // Used to count normals float w = cubeSize.x; float h = cubeSize.z; float h2 = cubeSize.y; Vector3 *mapVertices = (Vector3 *)RL_MALLOC(maxTriangles*3*sizeof(Vector3)); Vector2 *mapTexcoords = (Vector2 *)RL_MALLOC(maxTriangles*3*sizeof(Vector2)); Vector3 *mapNormals = (Vector3 *)RL_MALLOC(maxTriangles*3*sizeof(Vector3)); // Define the 6 normals of the cube, we will combine them accordingly later... Vector3 n1 = { 1.0f, 0.0f, 0.0f }; Vector3 n2 = { -1.0f, 0.0f, 0.0f }; Vector3 n3 = { 0.0f, 1.0f, 0.0f }; Vector3 n4 = { 0.0f, -1.0f, 0.0f }; Vector3 n5 = { 0.0f, 0.0f, -1.0f }; Vector3 n6 = { 0.0f, 0.0f, 1.0f }; // NOTE: We use texture rectangles to define different textures for top-bottom-front-back-right-left (6) typedef struct RectangleF { float x; float y; float width; float height; } RectangleF; RectangleF rightTexUV = { 0.0f, 0.0f, 0.5f, 0.5f }; RectangleF leftTexUV = { 0.5f, 0.0f, 0.5f, 0.5f }; RectangleF frontTexUV = { 0.0f, 0.0f, 0.5f, 0.5f }; RectangleF backTexUV = { 0.5f, 0.0f, 0.5f, 0.5f }; RectangleF topTexUV = { 0.0f, 0.5f, 0.5f, 0.5f }; RectangleF bottomTexUV = { 0.5f, 0.5f, 0.5f, 0.5f }; for (int z = 0; z < cubicmap.height; ++z) { for (int x = 0; x < cubicmap.width; ++x) { // Define the 8 vertex of the cube, we will combine them accordingly later... Vector3 v1 = { w*(x - 0.5f), h2, h*(z - 0.5f) }; Vector3 v2 = { w*(x - 0.5f), h2, h*(z + 0.5f) }; Vector3 v3 = { w*(x + 0.5f), h2, h*(z + 0.5f) }; Vector3 v4 = { w*(x + 0.5f), h2, h*(z - 0.5f) }; Vector3 v5 = { w*(x + 0.5f), 0, h*(z - 0.5f) }; Vector3 v6 = { w*(x - 0.5f), 0, h*(z - 0.5f) }; Vector3 v7 = { w*(x - 0.5f), 0, h*(z + 0.5f) }; Vector3 v8 = { w*(x + 0.5f), 0, h*(z + 0.5f) }; // We check pixel color to be WHITE -> draw full cube if (COLOR_EQUAL(pixels[z*cubicmap.width + x], WHITE)) { // Define triangles and checking collateral cubes //------------------------------------------------ // Define top triangles (2 tris, 6 vertex --> v1-v2-v3, v1-v3-v4) // WARNING: Not required for a WHITE cubes, created to allow seeing the map from outside mapVertices[vCounter] = v1; mapVertices[vCounter + 1] = v2; mapVertices[vCounter + 2] = v3; mapVertices[vCounter + 3] = v1; mapVertices[vCounter + 4] = v3; mapVertices[vCounter + 5] = v4; vCounter += 6; mapNormals[nCounter] = n3; mapNormals[nCounter + 1] = n3; mapNormals[nCounter + 2] = n3; mapNormals[nCounter + 3] = n3; mapNormals[nCounter + 4] = n3; mapNormals[nCounter + 5] = n3; nCounter += 6; mapTexcoords[tcCounter] = CLITERAL(Vector2){ topTexUV.x, topTexUV.y }; mapTexcoords[tcCounter + 1] = CLITERAL(Vector2){ topTexUV.x, topTexUV.y + topTexUV.height }; mapTexcoords[tcCounter + 2] = CLITERAL(Vector2){ topTexUV.x + topTexUV.width, topTexUV.y + topTexUV.height }; mapTexcoords[tcCounter + 3] = CLITERAL(Vector2){ topTexUV.x, topTexUV.y }; mapTexcoords[tcCounter + 4] = CLITERAL(Vector2){ topTexUV.x + topTexUV.width, topTexUV.y + topTexUV.height }; mapTexcoords[tcCounter + 5] = CLITERAL(Vector2){ topTexUV.x + topTexUV.width, topTexUV.y }; tcCounter += 6; // Define bottom triangles (2 tris, 6 vertex --> v6-v8-v7, v6-v5-v8) mapVertices[vCounter] = v6; mapVertices[vCounter + 1] = v8; mapVertices[vCounter + 2] = v7; mapVertices[vCounter + 3] = v6; mapVertices[vCounter + 4] = v5; mapVertices[vCounter + 5] = v8; vCounter += 6; mapNormals[nCounter] = n4; mapNormals[nCounter + 1] = n4; mapNormals[nCounter + 2] = n4; mapNormals[nCounter + 3] = n4; mapNormals[nCounter + 4] = n4; mapNormals[nCounter + 5] = n4; nCounter += 6; mapTexcoords[tcCounter] = CLITERAL(Vector2){ bottomTexUV.x + bottomTexUV.width, bottomTexUV.y }; mapTexcoords[tcCounter + 1] = CLITERAL(Vector2){ bottomTexUV.x, bottomTexUV.y + bottomTexUV.height }; mapTexcoords[tcCounter + 2] = CLITERAL(Vector2){ bottomTexUV.x + bottomTexUV.width, bottomTexUV.y + bottomTexUV.height }; mapTexcoords[tcCounter + 3] = CLITERAL(Vector2){ bottomTexUV.x + bottomTexUV.width, bottomTexUV.y }; mapTexcoords[tcCounter + 4] = CLITERAL(Vector2){ bottomTexUV.x, bottomTexUV.y }; mapTexcoords[tcCounter + 5] = CLITERAL(Vector2){ bottomTexUV.x, bottomTexUV.y + bottomTexUV.height }; tcCounter += 6; // Checking cube on bottom of current cube if (((z < cubicmap.height - 1) && COLOR_EQUAL(pixels[(z + 1)*cubicmap.width + x], BLACK)) || (z == cubicmap.height - 1)) { // Define front triangles (2 tris, 6 vertex) --> v2 v7 v3, v3 v7 v8 // NOTE: Collateral occluded faces are not generated mapVertices[vCounter] = v2; mapVertices[vCounter + 1] = v7; mapVertices[vCounter + 2] = v3; mapVertices[vCounter + 3] = v3; mapVertices[vCounter + 4] = v7; mapVertices[vCounter + 5] = v8; vCounter += 6; mapNormals[nCounter] = n6; mapNormals[nCounter + 1] = n6; mapNormals[nCounter + 2] = n6; mapNormals[nCounter + 3] = n6; mapNormals[nCounter + 4] = n6; mapNormals[nCounter + 5] = n6; nCounter += 6; mapTexcoords[tcCounter] = CLITERAL(Vector2){ frontTexUV.x, frontTexUV.y }; mapTexcoords[tcCounter + 1] = CLITERAL(Vector2){ frontTexUV.x, frontTexUV.y + frontTexUV.height }; mapTexcoords[tcCounter + 2] = CLITERAL(Vector2){ frontTexUV.x + frontTexUV.width, frontTexUV.y }; mapTexcoords[tcCounter + 3] = CLITERAL(Vector2){ frontTexUV.x + frontTexUV.width, frontTexUV.y }; mapTexcoords[tcCounter + 4] = CLITERAL(Vector2){ frontTexUV.x, frontTexUV.y + frontTexUV.height }; mapTexcoords[tcCounter + 5] = CLITERAL(Vector2){ frontTexUV.x + frontTexUV.width, frontTexUV.y + frontTexUV.height }; tcCounter += 6; } // Checking cube on top of current cube if (((z > 0) && COLOR_EQUAL(pixels[(z - 1)*cubicmap.width + x], BLACK)) || (z == 0)) { // Define back triangles (2 tris, 6 vertex) --> v1 v5 v6, v1 v4 v5 // NOTE: Collateral occluded faces are not generated mapVertices[vCounter] = v1; mapVertices[vCounter + 1] = v5; mapVertices[vCounter + 2] = v6; mapVertices[vCounter + 3] = v1; mapVertices[vCounter + 4] = v4; mapVertices[vCounter + 5] = v5; vCounter += 6; mapNormals[nCounter] = n5; mapNormals[nCounter + 1] = n5; mapNormals[nCounter + 2] = n5; mapNormals[nCounter + 3] = n5; mapNormals[nCounter + 4] = n5; mapNormals[nCounter + 5] = n5; nCounter += 6; mapTexcoords[tcCounter] = CLITERAL(Vector2){ backTexUV.x + backTexUV.width, backTexUV.y }; mapTexcoords[tcCounter + 1] = CLITERAL(Vector2){ backTexUV.x, backTexUV.y + backTexUV.height }; mapTexcoords[tcCounter + 2] = CLITERAL(Vector2){ backTexUV.x + backTexUV.width, backTexUV.y + backTexUV.height }; mapTexcoords[tcCounter + 3] = CLITERAL(Vector2){ backTexUV.x + backTexUV.width, backTexUV.y }; mapTexcoords[tcCounter + 4] = CLITERAL(Vector2){ backTexUV.x, backTexUV.y }; mapTexcoords[tcCounter + 5] = CLITERAL(Vector2){ backTexUV.x, backTexUV.y + backTexUV.height }; tcCounter += 6; } // Checking cube on right of current cube if (((x < cubicmap.width - 1) && COLOR_EQUAL(pixels[z*cubicmap.width + (x + 1)], BLACK)) || (x == cubicmap.width - 1)) { // Define right triangles (2 tris, 6 vertex) --> v3 v8 v4, v4 v8 v5 // NOTE: Collateral occluded faces are not generated mapVertices[vCounter] = v3; mapVertices[vCounter + 1] = v8; mapVertices[vCounter + 2] = v4; mapVertices[vCounter + 3] = v4; mapVertices[vCounter + 4] = v8; mapVertices[vCounter + 5] = v5; vCounter += 6; mapNormals[nCounter] = n1; mapNormals[nCounter + 1] = n1; mapNormals[nCounter + 2] = n1; mapNormals[nCounter + 3] = n1; mapNormals[nCounter + 4] = n1; mapNormals[nCounter + 5] = n1; nCounter += 6; mapTexcoords[tcCounter] = CLITERAL(Vector2){ rightTexUV.x, rightTexUV.y }; mapTexcoords[tcCounter + 1] = CLITERAL(Vector2){ rightTexUV.x, rightTexUV.y + rightTexUV.height }; mapTexcoords[tcCounter + 2] = CLITERAL(Vector2){ rightTexUV.x + rightTexUV.width, rightTexUV.y }; mapTexcoords[tcCounter + 3] = CLITERAL(Vector2){ rightTexUV.x + rightTexUV.width, rightTexUV.y }; mapTexcoords[tcCounter + 4] = CLITERAL(Vector2){ rightTexUV.x, rightTexUV.y + rightTexUV.height }; mapTexcoords[tcCounter + 5] = CLITERAL(Vector2){ rightTexUV.x + rightTexUV.width, rightTexUV.y + rightTexUV.height }; tcCounter += 6; } // Checking cube on left of current cube if (((x > 0) && COLOR_EQUAL(pixels[z*cubicmap.width + (x - 1)], BLACK)) || (x == 0)) { // Define left triangles (2 tris, 6 vertex) --> v1 v7 v2, v1 v6 v7 // NOTE: Collateral occluded faces are not generated mapVertices[vCounter] = v1; mapVertices[vCounter + 1] = v7; mapVertices[vCounter + 2] = v2; mapVertices[vCounter + 3] = v1; mapVertices[vCounter + 4] = v6; mapVertices[vCounter + 5] = v7; vCounter += 6; mapNormals[nCounter] = n2; mapNormals[nCounter + 1] = n2; mapNormals[nCounter + 2] = n2; mapNormals[nCounter + 3] = n2; mapNormals[nCounter + 4] = n2; mapNormals[nCounter + 5] = n2; nCounter += 6; mapTexcoords[tcCounter] = CLITERAL(Vector2){ leftTexUV.x, leftTexUV.y }; mapTexcoords[tcCounter + 1] = CLITERAL(Vector2){ leftTexUV.x + leftTexUV.width, leftTexUV.y + leftTexUV.height }; mapTexcoords[tcCounter + 2] = CLITERAL(Vector2){ leftTexUV.x + leftTexUV.width, leftTexUV.y }; mapTexcoords[tcCounter + 3] = CLITERAL(Vector2){ leftTexUV.x, leftTexUV.y }; mapTexcoords[tcCounter + 4] = CLITERAL(Vector2){ leftTexUV.x, leftTexUV.y + leftTexUV.height }; mapTexcoords[tcCounter + 5] = CLITERAL(Vector2){ leftTexUV.x + leftTexUV.width, leftTexUV.y + leftTexUV.height }; tcCounter += 6; } } // We check pixel color to be BLACK, we will only draw floor and roof else if (COLOR_EQUAL(pixels[z*cubicmap.width + x], BLACK)) { // Define top triangles (2 tris, 6 vertex --> v1-v2-v3, v1-v3-v4) mapVertices[vCounter] = v1; mapVertices[vCounter + 1] = v3; mapVertices[vCounter + 2] = v2; mapVertices[vCounter + 3] = v1; mapVertices[vCounter + 4] = v4; mapVertices[vCounter + 5] = v3; vCounter += 6; mapNormals[nCounter] = n4; mapNormals[nCounter + 1] = n4; mapNormals[nCounter + 2] = n4; mapNormals[nCounter + 3] = n4; mapNormals[nCounter + 4] = n4; mapNormals[nCounter + 5] = n4; nCounter += 6; mapTexcoords[tcCounter] = CLITERAL(Vector2){ topTexUV.x, topTexUV.y }; mapTexcoords[tcCounter + 1] = CLITERAL(Vector2){ topTexUV.x + topTexUV.width, topTexUV.y + topTexUV.height }; mapTexcoords[tcCounter + 2] = CLITERAL(Vector2){ topTexUV.x, topTexUV.y + topTexUV.height }; mapTexcoords[tcCounter + 3] = CLITERAL(Vector2){ topTexUV.x, topTexUV.y }; mapTexcoords[tcCounter + 4] = CLITERAL(Vector2){ topTexUV.x + topTexUV.width, topTexUV.y }; mapTexcoords[tcCounter + 5] = CLITERAL(Vector2){ topTexUV.x + topTexUV.width, topTexUV.y + topTexUV.height }; tcCounter += 6; // Define bottom triangles (2 tris, 6 vertex --> v6-v8-v7, v6-v5-v8) mapVertices[vCounter] = v6; mapVertices[vCounter + 1] = v7; mapVertices[vCounter + 2] = v8; mapVertices[vCounter + 3] = v6; mapVertices[vCounter + 4] = v8; mapVertices[vCounter + 5] = v5; vCounter += 6; mapNormals[nCounter] = n3; mapNormals[nCounter + 1] = n3; mapNormals[nCounter + 2] = n3; mapNormals[nCounter + 3] = n3; mapNormals[nCounter + 4] = n3; mapNormals[nCounter + 5] = n3; nCounter += 6; mapTexcoords[tcCounter] = CLITERAL(Vector2){ bottomTexUV.x + bottomTexUV.width, bottomTexUV.y }; mapTexcoords[tcCounter + 1] = CLITERAL(Vector2){ bottomTexUV.x + bottomTexUV.width, bottomTexUV.y + bottomTexUV.height }; mapTexcoords[tcCounter + 2] = CLITERAL(Vector2){ bottomTexUV.x, bottomTexUV.y + bottomTexUV.height }; mapTexcoords[tcCounter + 3] = CLITERAL(Vector2){ bottomTexUV.x + bottomTexUV.width, bottomTexUV.y }; mapTexcoords[tcCounter + 4] = CLITERAL(Vector2){ bottomTexUV.x, bottomTexUV.y + bottomTexUV.height }; mapTexcoords[tcCounter + 5] = CLITERAL(Vector2){ bottomTexUV.x, bottomTexUV.y }; tcCounter += 6; } } } // Move data from mapVertices temp arrays to vertices float array mesh.vertexCount = vCounter; mesh.triangleCount = vCounter/3; mesh.vertices = (float *)RL_MALLOC(mesh.vertexCount*3*sizeof(float)); mesh.normals = (float *)RL_MALLOC(mesh.vertexCount*3*sizeof(float)); mesh.texcoords = (float *)RL_MALLOC(mesh.vertexCount*2*sizeof(float)); mesh.colors = NULL; int fCounter = 0; // Move vertices data for (int i = 0; i < vCounter; i++) { mesh.vertices[fCounter] = mapVertices[i].x; mesh.vertices[fCounter + 1] = mapVertices[i].y; mesh.vertices[fCounter + 2] = mapVertices[i].z; fCounter += 3; } fCounter = 0; // Move normals data for (int i = 0; i < nCounter; i++) { mesh.normals[fCounter] = mapNormals[i].x; mesh.normals[fCounter + 1] = mapNormals[i].y; mesh.normals[fCounter + 2] = mapNormals[i].z; fCounter += 3; } fCounter = 0; // Move texcoords data for (int i = 0; i < tcCounter; i++) { mesh.texcoords[fCounter] = mapTexcoords[i].x; mesh.texcoords[fCounter + 1] = mapTexcoords[i].y; fCounter += 2; } RL_FREE(mapVertices); RL_FREE(mapNormals); RL_FREE(mapTexcoords); UnloadImageColors(pixels); // Unload pixels color data // Upload vertex data to GPU (static mesh) UploadMesh(&mesh, false); return mesh; } // end file src/models.c