REVIEWED: Support M3D file loading #2648
This commit is contained in:
parent
ae745e4fa8
commit
f66b1a3136
@ -466,6 +466,7 @@ MODELS = \
|
|||||||
models/models_loading \
|
models/models_loading \
|
||||||
models/models_loading_vox \
|
models/models_loading_vox \
|
||||||
models/models_loading_gltf \
|
models/models_loading_gltf \
|
||||||
|
models/models_loading_m3d \
|
||||||
models/models_orthographic_projection \
|
models/models_orthographic_projection \
|
||||||
models/models_rlgl_solar_system \
|
models/models_rlgl_solar_system \
|
||||||
models/models_skybox \
|
models/models_skybox \
|
||||||
|
@ -12,6 +12,8 @@
|
|||||||
* raylib can load .iqm animations.
|
* raylib can load .iqm animations.
|
||||||
* - VOX > Binary file format. MagikaVoxel mesh format:
|
* - VOX > Binary file format. MagikaVoxel mesh format:
|
||||||
* https://github.com/ephtracy/voxel-model/blob/master/MagicaVoxel-file-format-vox.txt
|
* https://github.com/ephtracy/voxel-model/blob/master/MagicaVoxel-file-format-vox.txt
|
||||||
|
* - M3D > Binary file format. Model 3D format:
|
||||||
|
* https://bztsrc.gitlab.io/model3d
|
||||||
*
|
*
|
||||||
* Example originally created with raylib 2.0, last time updated with raylib 4.2
|
* Example originally created with raylib 2.0, last time updated with raylib 4.2
|
||||||
*
|
*
|
||||||
@ -80,7 +82,8 @@ int main(void)
|
|||||||
IsFileExtension(droppedFiles.paths[0], ".gltf") ||
|
IsFileExtension(droppedFiles.paths[0], ".gltf") ||
|
||||||
IsFileExtension(droppedFiles.paths[0], ".glb") ||
|
IsFileExtension(droppedFiles.paths[0], ".glb") ||
|
||||||
IsFileExtension(droppedFiles.paths[0], ".vox") ||
|
IsFileExtension(droppedFiles.paths[0], ".vox") ||
|
||||||
IsFileExtension(droppedFiles.paths[0], ".iqm")) // Model file formats supported
|
IsFileExtension(droppedFiles.paths[0], ".iqm") ||
|
||||||
|
IsFileExtension(droppedFiles.paths[0], ".m3d")) // Model file formats supported
|
||||||
{
|
{
|
||||||
UnloadModel(model); // Unload previous model
|
UnloadModel(model); // Unload previous model
|
||||||
model = LoadModel(droppedFiles.paths[0]); // Load new model
|
model = LoadModel(droppedFiles.paths[0]); // Load new model
|
||||||
|
127
examples/models/models_loading_m3d.c
Normal file
127
examples/models/models_loading_m3d.c
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
/*******************************************************************************************
|
||||||
|
*
|
||||||
|
* raylib [models] example - Load M3D model (with optional animations) and play them
|
||||||
|
*
|
||||||
|
* Example static mesh Suzanne from Blender
|
||||||
|
* Example animated seagull model from Scorched 3D, licensed GPLv2
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019-2022 Culacant (@culacant) and Ramon Santamaria (@raysan5)
|
||||||
|
* Copyright (c) 2022 bzt (@bztsrc)
|
||||||
|
*
|
||||||
|
********************************************************************************************
|
||||||
|
*
|
||||||
|
* NOTE: To export a model from blender, just use https://gitlab.com/bztsrc/model3d/-/tree/master/blender
|
||||||
|
* and make sure to add "(action)" markers to the timeline if you want multiple animations.
|
||||||
|
*
|
||||||
|
********************************************************************************************/
|
||||||
|
|
||||||
|
#include "raylib.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------------
|
||||||
|
// Program main entry point
|
||||||
|
//------------------------------------------------------------------------------------
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
char *model_fn = argc > 1 ? argv[1] : "resources/models/m3d/suzanne.m3d";
|
||||||
|
|
||||||
|
// Initialization
|
||||||
|
//--------------------------------------------------------------------------------------
|
||||||
|
const int screenWidth = 800;
|
||||||
|
const int screenHeight = 450;
|
||||||
|
|
||||||
|
InitWindow(screenWidth, screenHeight, "raylib [models] example - M3D model");
|
||||||
|
|
||||||
|
// Define the camera to look into our 3d world
|
||||||
|
Camera camera = { 0 };
|
||||||
|
camera.position = (Vector3){ 10.0f, 10.0f, 10.0f }; // Camera position
|
||||||
|
camera.target = (Vector3){ 0.0f, 0.0f, 0.0f }; // Camera looking at point
|
||||||
|
camera.up = (Vector3){ 0.0f, 1.0f, 0.0f }; // Camera up vector (rotation towards target)
|
||||||
|
camera.fovy = 45.0f; // Camera field-of-view Y
|
||||||
|
camera.projection = CAMERA_PERSPECTIVE; // Camera mode type
|
||||||
|
Vector3 position = { 0.0f, 0.0f, 0.0f }; // Set model position
|
||||||
|
|
||||||
|
// Load model
|
||||||
|
Model model = LoadModel(model_fn); // Load the animated model mesh and basic data
|
||||||
|
|
||||||
|
// Load animation data
|
||||||
|
unsigned int animsCount = 0;
|
||||||
|
ModelAnimation *anims = LoadModelAnimations(model_fn, &animsCount);
|
||||||
|
int animFrameCounter = 0, animId = 0;
|
||||||
|
|
||||||
|
SetCameraMode(camera, CAMERA_FREE); // Set free camera mode
|
||||||
|
|
||||||
|
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Main game loop
|
||||||
|
while (!WindowShouldClose()) // Detect window close button or ESC key
|
||||||
|
{
|
||||||
|
// Update
|
||||||
|
//----------------------------------------------------------------------------------
|
||||||
|
UpdateCamera(&camera);
|
||||||
|
|
||||||
|
// Play animation when spacebar is held down
|
||||||
|
if (animsCount)
|
||||||
|
{
|
||||||
|
if (IsKeyDown(KEY_SPACE))
|
||||||
|
{
|
||||||
|
animFrameCounter++;
|
||||||
|
UpdateModelAnimation(model, anims[animId], animFrameCounter);
|
||||||
|
if (animFrameCounter >= anims[animId].frameCount) animFrameCounter = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select animation on mouse click
|
||||||
|
if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT))
|
||||||
|
{
|
||||||
|
animFrameCounter = 0;
|
||||||
|
animId++;
|
||||||
|
if (animId >= animsCount) animId = 0;
|
||||||
|
UpdateModelAnimation(model, anims[animId], 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//----------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Draw
|
||||||
|
//----------------------------------------------------------------------------------
|
||||||
|
BeginDrawing();
|
||||||
|
|
||||||
|
ClearBackground(RAYWHITE);
|
||||||
|
|
||||||
|
BeginMode3D(camera);
|
||||||
|
|
||||||
|
DrawModel(model, position, 1.0f, WHITE); // Draw 3d model with texture
|
||||||
|
if(anims)
|
||||||
|
for (int i = 0; i < model.boneCount; i++)
|
||||||
|
{
|
||||||
|
DrawCube(anims[animId].framePoses[animFrameCounter][i].translation, 0.2f, 0.2f, 0.2f, RED);
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawGrid(10, 1.0f); // Draw a grid
|
||||||
|
|
||||||
|
EndMode3D();
|
||||||
|
|
||||||
|
DrawText("PRESS SPACE to PLAY MODEL ANIMATION", 10, GetScreenHeight() - 30, 10, MAROON);
|
||||||
|
DrawText("MOUSE LEFT BUTTON to CYCLE THROUGH ANIMATIONS", 10, GetScreenHeight() - 20, 10, DARKGRAY);
|
||||||
|
DrawText("(c) Suzanne 3D model by blender", screenWidth - 200, screenHeight - 20, 10, GRAY);
|
||||||
|
|
||||||
|
EndDrawing();
|
||||||
|
//----------------------------------------------------------------------------------
|
||||||
|
}
|
||||||
|
|
||||||
|
// De-Initialization
|
||||||
|
//--------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Unload model animations data
|
||||||
|
for (unsigned int i = 0; i < animsCount; i++) UnloadModelAnimation(anims[i]);
|
||||||
|
RL_FREE(anims);
|
||||||
|
|
||||||
|
UnloadModel(model); // Unload model
|
||||||
|
|
||||||
|
CloseWindow(); // Close window and OpenGL context
|
||||||
|
//--------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
23
src/external/m3d.h
vendored
23
src/external/m3d.h
vendored
@ -1865,6 +1865,9 @@ static void *_m3dstbi__png_load(_m3dstbi__context *s, int *x, int *y, int *comp,
|
|||||||
#define stbi__png_load _m3dstbi__png_load
|
#define stbi__png_load _m3dstbi__png_load
|
||||||
#define stbi_zlib_decode_malloc_guesssize_headerflag _m3dstbi_zlib_decode_malloc_guesssize_headerflag
|
#define stbi_zlib_decode_malloc_guesssize_headerflag _m3dstbi_zlib_decode_malloc_guesssize_headerflag
|
||||||
#endif
|
#endif
|
||||||
|
#if !defined(M3D_NOIMPORTER) && defined(STBI_INCLUDE_STB_IMAGE_H) && !defined(STB_IMAGE_IMPLEMENTATION)
|
||||||
|
#error "stb_image.h included without STB_IMAGE_IMPLEMENTATION. Sorry, we need some stuff defined inside the ifguard for proper integration"
|
||||||
|
#endif
|
||||||
|
|
||||||
#if defined(M3D_EXPORTER) && !defined(INCLUDE_STB_IMAGE_WRITE_H)
|
#if defined(M3D_EXPORTER) && !defined(INCLUDE_STB_IMAGE_WRITE_H)
|
||||||
/* zlib_compressor from
|
/* zlib_compressor from
|
||||||
@ -2165,11 +2168,9 @@ M3D_INDEX _m3d_gettx(m3d_t *model, m3dread_t readfilecb, m3dfree_t freecb, char
|
|||||||
unsigned int i, len = 0;
|
unsigned int i, len = 0;
|
||||||
unsigned char *buff = NULL;
|
unsigned char *buff = NULL;
|
||||||
char *fn2;
|
char *fn2;
|
||||||
#ifdef STBI__PNG_TYPE
|
|
||||||
unsigned int w, h;
|
unsigned int w, h;
|
||||||
stbi__context s;
|
stbi__context s;
|
||||||
stbi__result_info ri;
|
stbi__result_info ri;
|
||||||
#endif
|
|
||||||
|
|
||||||
/* do we have loaded this texture already? */
|
/* do we have loaded this texture already? */
|
||||||
for(i = 0; i < model->numtexture; i++)
|
for(i = 0; i < model->numtexture; i++)
|
||||||
@ -2212,7 +2213,6 @@ M3D_INDEX _m3d_gettx(m3d_t *model, m3dread_t readfilecb, m3dfree_t freecb, char
|
|||||||
model->texture[i].w = model->texture[i].h = 0; model->texture[i].d = NULL;
|
model->texture[i].w = model->texture[i].h = 0; model->texture[i].d = NULL;
|
||||||
if(buff) {
|
if(buff) {
|
||||||
if(buff[0] == 0x89 && buff[1] == 'P' && buff[2] == 'N' && buff[3] == 'G') {
|
if(buff[0] == 0x89 && buff[1] == 'P' && buff[2] == 'N' && buff[3] == 'G') {
|
||||||
#ifdef STBI__PNG_TYPE
|
|
||||||
s.read_from_callbacks = 0;
|
s.read_from_callbacks = 0;
|
||||||
s.img_buffer = s.img_buffer_original = (unsigned char *) buff;
|
s.img_buffer = s.img_buffer_original = (unsigned char *) buff;
|
||||||
s.img_buffer_end = s.img_buffer_original_end = (unsigned char *) buff+len;
|
s.img_buffer_end = s.img_buffer_original_end = (unsigned char *) buff+len;
|
||||||
@ -2223,7 +2223,6 @@ M3D_INDEX _m3d_gettx(m3d_t *model, m3dread_t readfilecb, m3dfree_t freecb, char
|
|||||||
model->texture[i].w = w;
|
model->texture[i].w = w;
|
||||||
model->texture[i].h = h;
|
model->texture[i].h = h;
|
||||||
model->texture[i].f = (uint8_t)len;
|
model->texture[i].f = (uint8_t)len;
|
||||||
#endif
|
|
||||||
} else {
|
} else {
|
||||||
#ifdef M3D_TX_INTERP
|
#ifdef M3D_TX_INTERP
|
||||||
if((model->errcode = M3D_TX_INTERP(fn, buff, len, &model->texture[i])) != M3D_SUCCESS) {
|
if((model->errcode = M3D_TX_INTERP(fn, buff, len, &model->texture[i])) != M3D_SUCCESS) {
|
||||||
@ -3225,7 +3224,7 @@ asciiend:
|
|||||||
|
|
||||||
/* parse header */
|
/* parse header */
|
||||||
data += sizeof(m3dhdr_t);
|
data += sizeof(m3dhdr_t);
|
||||||
M3D_LOG(data);
|
M3D_LOG((char*)data);
|
||||||
model->name = (char*)data;
|
model->name = (char*)data;
|
||||||
for(; data < end && *data; data++) {}; data++;
|
for(; data < end && *data; data++) {}; data++;
|
||||||
model->license = (char*)data;
|
model->license = (char*)data;
|
||||||
@ -3264,12 +3263,12 @@ asciiend:
|
|||||||
}
|
}
|
||||||
if((sizeof(M3D_INDEX) == 2 && (model->vi_s > 2 || model->si_s > 2 || model->ci_s > 2 || model->ti_s > 2 ||
|
if((sizeof(M3D_INDEX) == 2 && (model->vi_s > 2 || model->si_s > 2 || model->ci_s > 2 || model->ti_s > 2 ||
|
||||||
model->bi_s > 2 || model->sk_s > 2 || model->fc_s > 2 || model->hi_s > 2 || model->fi_s > 2)) ||
|
model->bi_s > 2 || model->sk_s > 2 || model->fc_s > 2 || model->hi_s > 2 || model->fi_s > 2)) ||
|
||||||
(sizeof(M3D_VOXEL) == 2 && model->vp_s > 2)) {
|
(sizeof(M3D_VOXEL) < (size_t)model->vp_s && model->vp_s != 8)) {
|
||||||
M3D_LOG("32 bit indices not supported, unable to load model");
|
M3D_LOG("32 bit indices not supported, unable to load model");
|
||||||
M3D_FREE(model);
|
M3D_FREE(model);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
if(model->vi_s > 4 || model->si_s > 4) {
|
if(model->vi_s > 4 || model->si_s > 4 || model->vp_s == 4) {
|
||||||
M3D_LOG("Invalid index size, unable to load model");
|
M3D_LOG("Invalid index size, unable to load model");
|
||||||
M3D_FREE(model);
|
M3D_FREE(model);
|
||||||
return NULL;
|
return NULL;
|
||||||
@ -3346,12 +3345,12 @@ memerr: M3D_LOG("Out of memory");
|
|||||||
for(i = 0, data += sizeof(m3dchunk_t); data < chunk; i++) {
|
for(i = 0, data += sizeof(m3dchunk_t); data < chunk; i++) {
|
||||||
switch(model->vc_s) {
|
switch(model->vc_s) {
|
||||||
case 1:
|
case 1:
|
||||||
model->tmap[i].u = (M3D_FLOAT)(data[0]) / (M3D_FLOAT)255.0;
|
model->tmap[i].u = (M3D_FLOAT)((uint8_t)data[0]) / (M3D_FLOAT)255.0;
|
||||||
model->tmap[i].v = (M3D_FLOAT)(data[1]) / (M3D_FLOAT)255.0;
|
model->tmap[i].v = (M3D_FLOAT)((uint8_t)data[1]) / (M3D_FLOAT)255.0;
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
model->tmap[i].u = (M3D_FLOAT)(*((int16_t*)(data+0))) / (M3D_FLOAT)65535.0;
|
model->tmap[i].u = (M3D_FLOAT)(*((uint16_t*)(data+0))) / (M3D_FLOAT)65535.0;
|
||||||
model->tmap[i].v = (M3D_FLOAT)(*((int16_t*)(data+2))) / (M3D_FLOAT)65535.0;
|
model->tmap[i].v = (M3D_FLOAT)(*((uint16_t*)(data+2))) / (M3D_FLOAT)65535.0;
|
||||||
break;
|
break;
|
||||||
case 4:
|
case 4:
|
||||||
model->tmap[i].u = (M3D_FLOAT)(*((float*)(data+0)));
|
model->tmap[i].u = (M3D_FLOAT)(*((float*)(data+0)));
|
||||||
@ -5219,7 +5218,7 @@ memerr: if(vrtxidx) M3D_FREE(vrtxidx);
|
|||||||
if(model->preview.data && model->preview.length) {
|
if(model->preview.data && model->preview.length) {
|
||||||
sl = _m3d_safestr(sn, 0);
|
sl = _m3d_safestr(sn, 0);
|
||||||
if(sl) {
|
if(sl) {
|
||||||
ptr -= (uintptr_t)out; len = (unsigned int)((uintptr_t)ptr + (uintptr_t)20);
|
ptr -= (uintptr_t)out; len = (unsigned int)((uintptr_t)ptr + (uintptr_t)20 + strlen(sl));
|
||||||
out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uintptr_t)out;
|
out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uintptr_t)out;
|
||||||
if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; }
|
if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; }
|
||||||
ptr += sprintf(ptr, "Preview\r\n%s.png\r\n\r\n", sl);
|
ptr += sprintf(ptr, "Preview\r\n%s.png\r\n\r\n", sl);
|
||||||
|
205
src/rmodels.c
205
src/rmodels.c
@ -92,10 +92,6 @@
|
|||||||
#define M3D_REALLOC RL_REALLOC
|
#define M3D_REALLOC RL_REALLOC
|
||||||
#define M3D_FREE RL_FREE
|
#define M3D_FREE RL_FREE
|
||||||
|
|
||||||
// Let the M3D loader know about stb_image is used in this project,
|
|
||||||
// to allow it to use on textures loading
|
|
||||||
#include "external/stb_image.h"
|
|
||||||
|
|
||||||
#define M3D_IMPLEMENTATION
|
#define M3D_IMPLEMENTATION
|
||||||
#include "external/m3d.h" // Model3D file format loading
|
#include "external/m3d.h" // Model3D file format loading
|
||||||
#endif
|
#endif
|
||||||
@ -157,6 +153,7 @@ static Model LoadVOX(const char *filename); // Load VOX mesh data
|
|||||||
#endif
|
#endif
|
||||||
#if defined(SUPPORT_FILEFORMAT_M3D)
|
#if defined(SUPPORT_FILEFORMAT_M3D)
|
||||||
static Model LoadM3D(const char *filename); // Load M3D mesh data
|
static Model LoadM3D(const char *filename); // Load M3D mesh data
|
||||||
|
static ModelAnimation *LoadModelAnimationsM3D(const char *fileName, unsigned int *animCount); // Load M3D animation data
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
//----------------------------------------------------------------------------------
|
//----------------------------------------------------------------------------------
|
||||||
@ -1862,6 +1859,9 @@ ModelAnimation *LoadModelAnimations(const char *fileName, unsigned int *animCoun
|
|||||||
#if defined(SUPPORT_FILEFORMAT_IQM)
|
#if defined(SUPPORT_FILEFORMAT_IQM)
|
||||||
if (IsFileExtension(fileName, ".iqm")) animations = LoadModelAnimationsIQM(fileName, animCount);
|
if (IsFileExtension(fileName, ".iqm")) animations = LoadModelAnimationsIQM(fileName, animCount);
|
||||||
#endif
|
#endif
|
||||||
|
#if defined(SUPPORT_FILEFORMAT_M3D)
|
||||||
|
if (IsFileExtension(fileName, ".m3d")) animations = LoadModelAnimationsM3D(fileName, animCount);
|
||||||
|
#endif
|
||||||
#if defined(SUPPORT_FILEFORMAT_GLTF)
|
#if defined(SUPPORT_FILEFORMAT_GLTF)
|
||||||
//if (IsFileExtension(fileName, ".gltf;.glb")) animations = LoadModelAnimationGLTF(fileName, animCount);
|
//if (IsFileExtension(fileName, ".gltf;.glb")) animations = LoadModelAnimationGLTF(fileName, animCount);
|
||||||
#endif
|
#endif
|
||||||
@ -5135,19 +5135,29 @@ static Model LoadM3D(const char *fileName)
|
|||||||
m3dp_t *prop = NULL;
|
m3dp_t *prop = NULL;
|
||||||
unsigned int bytesRead = 0;
|
unsigned int bytesRead = 0;
|
||||||
unsigned char *fileData = LoadFileData(fileName, &bytesRead);
|
unsigned char *fileData = LoadFileData(fileName, &bytesRead);
|
||||||
int i, j, k, l, mi = -2;
|
int i, j, k, l, n, mi = -2;
|
||||||
|
|
||||||
if (fileData != NULL)
|
if (fileData != NULL)
|
||||||
{
|
{
|
||||||
m3d = m3d_load(fileData, m3d_loaderhook, m3d_freehook, NULL);
|
m3d = m3d_load(fileData, m3d_loaderhook, m3d_freehook, NULL);
|
||||||
|
|
||||||
if (!m3d || (m3d->errcode != M3D_SUCCESS))
|
if (!m3d || M3D_ERR_ISFATAL(m3d->errcode))
|
||||||
{
|
{
|
||||||
TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to load M3D data", fileName);
|
TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to load M3D data, error code %d", fileName, m3d ? m3d->errcode : -2);
|
||||||
|
if (m3d) m3d_free(m3d);
|
||||||
|
UnloadFileData(fileData);
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
else TRACELOG(LOG_INFO, "MODEL: [%s] M3D data loaded successfully: %i faces/%i materials", fileName, m3d->numface, m3d->nummaterial);
|
else TRACELOG(LOG_INFO, "MODEL: [%s] M3D data loaded successfully: %i faces/%i materials", fileName, m3d->numface, m3d->nummaterial);
|
||||||
|
|
||||||
|
// no face? this is probably just a material library
|
||||||
|
if (!m3d->numface)
|
||||||
|
{
|
||||||
|
m3d_free(m3d);
|
||||||
|
UnloadFileData(fileData);
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
if (m3d->nummaterial > 0)
|
if (m3d->nummaterial > 0)
|
||||||
{
|
{
|
||||||
model.meshCount = model.materialCount = m3d->nummaterial;
|
model.meshCount = model.materialCount = m3d->nummaterial;
|
||||||
@ -5161,17 +5171,25 @@ static Model LoadM3D(const char *fileName)
|
|||||||
|
|
||||||
model.meshes = (Mesh *)RL_CALLOC(model.meshCount, sizeof(Mesh));
|
model.meshes = (Mesh *)RL_CALLOC(model.meshCount, sizeof(Mesh));
|
||||||
model.meshMaterial = (int *)RL_CALLOC(model.meshCount, sizeof(int));
|
model.meshMaterial = (int *)RL_CALLOC(model.meshCount, sizeof(int));
|
||||||
model.materials = (Material *)RL_CALLOC(model.meshCount + 1, sizeof(Material));
|
model.materials = (Material *)RL_CALLOC(model.materialCount + 1, sizeof(Material));
|
||||||
|
|
||||||
// Map no material to index 0 with default shader, everything else materialid + 1
|
// Map no material to index 0 with default shader, everything else materialid + 1
|
||||||
model.materials[0] = LoadMaterialDefault();
|
model.materials[0] = LoadMaterialDefault();
|
||||||
model.materials[0].maps[MATERIAL_MAP_DIFFUSE].texture = (Texture2D){ rlGetTextureIdDefault(), 1, 1, 1, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 };
|
|
||||||
|
|
||||||
for (i = l = 0, k = -1; i < m3d->numface; i++, l++)
|
for (i = l = 0, k = -1; i < m3d->numface; i++, l++)
|
||||||
{
|
{
|
||||||
// Materials are grouped together
|
// Materials are grouped together
|
||||||
if (mi != m3d->face[i].materialid)
|
if (mi != m3d->face[i].materialid)
|
||||||
{
|
{
|
||||||
|
// there should be only one material switch per material kind, but be bulletproof for unoptimal model files
|
||||||
|
if (k + 1 >= model.meshCount)
|
||||||
|
{
|
||||||
|
model.meshCount++;
|
||||||
|
model.meshes = (Mesh *)RL_REALLOC(model.meshes, model.meshCount*sizeof(Mesh));
|
||||||
|
memset(&model.meshes[model.meshCount - 1], 0, sizeof(Mesh));
|
||||||
|
model.meshMaterial = (int *)RL_REALLOC(model.meshMaterial, model.meshCount*sizeof(int));
|
||||||
|
}
|
||||||
|
|
||||||
k++;
|
k++;
|
||||||
mi = m3d->face[i].materialid;
|
mi = m3d->face[i].materialid;
|
||||||
|
|
||||||
@ -5182,6 +5200,19 @@ static Model LoadM3D(const char *fileName)
|
|||||||
model.meshes[k].vertices = (float *)RL_CALLOC(model.meshes[k].vertexCount*3, sizeof(float));
|
model.meshes[k].vertices = (float *)RL_CALLOC(model.meshes[k].vertexCount*3, sizeof(float));
|
||||||
model.meshes[k].texcoords = (float *)RL_CALLOC(model.meshes[k].vertexCount*2, sizeof(float));
|
model.meshes[k].texcoords = (float *)RL_CALLOC(model.meshes[k].vertexCount*2, sizeof(float));
|
||||||
model.meshes[k].normals = (float *)RL_CALLOC(model.meshes[k].vertexCount*3, sizeof(float));
|
model.meshes[k].normals = (float *)RL_CALLOC(model.meshes[k].vertexCount*3, sizeof(float));
|
||||||
|
// without material, we rely on vertex colors
|
||||||
|
if (mi == M3D_UNDEF && model.meshes[k].colors == NULL)
|
||||||
|
{
|
||||||
|
model.meshes[k].colors = RL_CALLOC(model.meshes[k].vertexCount*4, sizeof(unsigned char));
|
||||||
|
for (j = 0; j < model.meshes[k].vertexCount*4; j += 4) memcpy(&model.meshes[k].colors[j], &WHITE, 4);
|
||||||
|
}
|
||||||
|
if (m3d->numbone && m3d->numskin)
|
||||||
|
{
|
||||||
|
model.meshes[k].boneIds = (unsigned char *)RL_CALLOC(model.meshes[k].vertexCount*4, sizeof(unsigned char));
|
||||||
|
model.meshes[k].boneWeights = (float *)RL_CALLOC(model.meshes[k].vertexCount*4, sizeof(float));
|
||||||
|
model.meshes[k].animVertices = (float *)RL_CALLOC(model.meshes[k].vertexCount*3, sizeof(float));
|
||||||
|
model.meshes[k].animNormals = (float *)RL_CALLOC(model.meshes[k].vertexCount*3, sizeof(float));
|
||||||
|
}
|
||||||
model.meshMaterial[k] = mi + 1;
|
model.meshMaterial[k] = mi + 1;
|
||||||
l = 0;
|
l = 0;
|
||||||
}
|
}
|
||||||
@ -5197,14 +5228,25 @@ static Model LoadM3D(const char *fileName)
|
|||||||
model.meshes[k].vertices[l * 9 + 7] = m3d->vertex[m3d->face[i].vertex[2]].y*m3d->scale;
|
model.meshes[k].vertices[l * 9 + 7] = m3d->vertex[m3d->face[i].vertex[2]].y*m3d->scale;
|
||||||
model.meshes[k].vertices[l * 9 + 8] = m3d->vertex[m3d->face[i].vertex[2]].z*m3d->scale;
|
model.meshes[k].vertices[l * 9 + 8] = m3d->vertex[m3d->face[i].vertex[2]].z*m3d->scale;
|
||||||
|
|
||||||
|
if (mi == M3D_UNDEF)
|
||||||
|
{
|
||||||
|
// without vertex color (full transparency), we use the default color
|
||||||
|
if (m3d->vertex[m3d->face[i].vertex[0]].color & 0xFF000000)
|
||||||
|
memcpy(&model.meshes[k].colors[l * 12 + 0], &m3d->vertex[m3d->face[i].vertex[0]].color, 4);
|
||||||
|
if (m3d->vertex[m3d->face[i].vertex[1]].color & 0xFF000000)
|
||||||
|
memcpy(&model.meshes[k].colors[l * 12 + 4], &m3d->vertex[m3d->face[i].vertex[1]].color, 4);
|
||||||
|
if (m3d->vertex[m3d->face[i].vertex[2]].color & 0xFF000000)
|
||||||
|
memcpy(&model.meshes[k].colors[l * 12 + 8], &m3d->vertex[m3d->face[i].vertex[2]].color, 4);
|
||||||
|
}
|
||||||
|
|
||||||
if (m3d->face[i].texcoord[0] != M3D_UNDEF)
|
if (m3d->face[i].texcoord[0] != M3D_UNDEF)
|
||||||
{
|
{
|
||||||
model.meshes[k].texcoords[l * 6 + 0] = m3d->tmap[m3d->face[i].texcoord[0]].u;
|
model.meshes[k].texcoords[l * 6 + 0] = m3d->tmap[m3d->face[i].texcoord[0]].u;
|
||||||
model.meshes[k].texcoords[l * 6 + 1] = m3d->tmap[m3d->face[i].texcoord[0]].v;
|
model.meshes[k].texcoords[l * 6 + 1] = 1.0 - m3d->tmap[m3d->face[i].texcoord[0]].v;
|
||||||
model.meshes[k].texcoords[l * 6 + 2] = m3d->tmap[m3d->face[i].texcoord[1]].u;
|
model.meshes[k].texcoords[l * 6 + 2] = m3d->tmap[m3d->face[i].texcoord[1]].u;
|
||||||
model.meshes[k].texcoords[l * 6 + 3] = m3d->tmap[m3d->face[i].texcoord[1]].v;
|
model.meshes[k].texcoords[l * 6 + 3] = 1.0 - m3d->tmap[m3d->face[i].texcoord[1]].v;
|
||||||
model.meshes[k].texcoords[l * 6 + 4] = m3d->tmap[m3d->face[i].texcoord[2]].u;
|
model.meshes[k].texcoords[l * 6 + 4] = m3d->tmap[m3d->face[i].texcoord[2]].u;
|
||||||
model.meshes[k].texcoords[l * 6 + 5] = m3d->tmap[m3d->face[i].texcoord[2]].v;
|
model.meshes[k].texcoords[l * 6 + 5] = 1.0 - m3d->tmap[m3d->face[i].texcoord[2]].v;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m3d->face[i].normal[0] != M3D_UNDEF)
|
if (m3d->face[i].normal[0] != M3D_UNDEF)
|
||||||
@ -5219,12 +5261,28 @@ static Model LoadM3D(const char *fileName)
|
|||||||
model.meshes[k].normals[l * 9 + 7] = m3d->vertex[m3d->face[i].normal[2]].y;
|
model.meshes[k].normals[l * 9 + 7] = m3d->vertex[m3d->face[i].normal[2]].y;
|
||||||
model.meshes[k].normals[l * 9 + 8] = m3d->vertex[m3d->face[i].normal[2]].z;
|
model.meshes[k].normals[l * 9 + 8] = m3d->vertex[m3d->face[i].normal[2]].z;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add skin (vertex / bone weight pairs)
|
||||||
|
if (m3d->numbone && m3d->numskin) {
|
||||||
|
for (n = 0; n < 3; n++) {
|
||||||
|
int skinid = m3d->vertex[m3d->face[i].vertex[n]].skinid;
|
||||||
|
// check if there's a skin for this mesh, should be, just failsafe
|
||||||
|
if (skinid != M3D_UNDEF && skinid < m3d->numskin)
|
||||||
|
{
|
||||||
|
for (j = 0; j < 4; j++)
|
||||||
|
{
|
||||||
|
model.meshes[k].boneIds[l * 12 + n * 4 + j] = m3d->skin[skinid].boneid[j];
|
||||||
|
model.meshes[k].boneWeights[l * 12 + n * 4 + j] = m3d->skin[skinid].weight[j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load materials
|
||||||
for (i = 0; i < m3d->nummaterial; i++)
|
for (i = 0; i < m3d->nummaterial; i++)
|
||||||
{
|
{
|
||||||
model.materials[i + 1] = LoadMaterialDefault();
|
model.materials[i + 1] = LoadMaterialDefault();
|
||||||
model.materials[i + 1].maps[MATERIAL_MAP_DIFFUSE].texture = (Texture2D){ rlGetTextureIdDefault(), 1, 1, 1, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 };
|
|
||||||
|
|
||||||
for (j = 0; j < m3d->material[i].numprop; j++)
|
for (j = 0; j < m3d->material[i].numprop; j++)
|
||||||
{
|
{
|
||||||
@ -5240,7 +5298,6 @@ static Model LoadM3D(const char *fileName)
|
|||||||
case m3dp_Ks:
|
case m3dp_Ks:
|
||||||
{
|
{
|
||||||
memcpy(&model.materials[i + 1].maps[MATERIAL_MAP_SPECULAR].color, &prop->value.color, 4);
|
memcpy(&model.materials[i + 1].maps[MATERIAL_MAP_SPECULAR].color, &prop->value.color, 4);
|
||||||
model.materials[i + 1].maps[MATERIAL_MAP_SPECULAR].value = 0.0f;
|
|
||||||
} break;
|
} break;
|
||||||
case m3dp_Ns:
|
case m3dp_Ns:
|
||||||
{
|
{
|
||||||
@ -5283,6 +5340,8 @@ static Model LoadM3D(const char *fileName)
|
|||||||
case m3dp_map_Ks: model.materials[i + 1].maps[MATERIAL_MAP_SPECULAR].texture = LoadTextureFromImage(image); break;
|
case m3dp_map_Ks: model.materials[i + 1].maps[MATERIAL_MAP_SPECULAR].texture = LoadTextureFromImage(image); break;
|
||||||
case m3dp_map_Ke: model.materials[i + 1].maps[MATERIAL_MAP_EMISSION].texture = LoadTextureFromImage(image); break;
|
case m3dp_map_Ke: model.materials[i + 1].maps[MATERIAL_MAP_EMISSION].texture = LoadTextureFromImage(image); break;
|
||||||
case m3dp_map_Km: model.materials[i + 1].maps[MATERIAL_MAP_NORMAL].texture = LoadTextureFromImage(image); break;
|
case m3dp_map_Km: model.materials[i + 1].maps[MATERIAL_MAP_NORMAL].texture = LoadTextureFromImage(image); break;
|
||||||
|
case m3dp_map_Ka: model.materials[i + 1].maps[MATERIAL_MAP_OCCLUSION].texture = LoadTextureFromImage(image); break;
|
||||||
|
case m3dp_map_Pm: model.materials[i + 1].maps[MATERIAL_MAP_ROUGHNESS].texture = LoadTextureFromImage(image); break;
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -5291,12 +5350,130 @@ static Model LoadM3D(const char *fileName)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load bones
|
||||||
|
if(m3d->numbone)
|
||||||
|
{
|
||||||
|
model.boneCount = m3d->numbone;
|
||||||
|
model.bones = RL_MALLOC(m3d->numbone*sizeof(BoneInfo));
|
||||||
|
model.bindPose = RL_MALLOC(m3d->numbone*sizeof(Transform));
|
||||||
|
for (i = 0; i < m3d->numbone; i++)
|
||||||
|
{
|
||||||
|
model.bones[i].parent = m3d->bone[i].parent;
|
||||||
|
strncpy(model.bones[i].name, m3d->bone[i].name, sizeof(model.bones[i].name));
|
||||||
|
model.bindPose[i].translation.x = m3d->vertex[m3d->bone[i].pos].x;
|
||||||
|
model.bindPose[i].translation.y = m3d->vertex[m3d->bone[i].pos].y;
|
||||||
|
model.bindPose[i].translation.z = m3d->vertex[m3d->bone[i].pos].z;
|
||||||
|
model.bindPose[i].rotation.x = m3d->vertex[m3d->bone[i].ori].x;
|
||||||
|
model.bindPose[i].rotation.y = m3d->vertex[m3d->bone[i].ori].y;
|
||||||
|
model.bindPose[i].rotation.z = m3d->vertex[m3d->bone[i].ori].z;
|
||||||
|
model.bindPose[i].rotation.w = m3d->vertex[m3d->bone[i].ori].w;
|
||||||
|
// TODO: if the orientation quaternion not normalized, then that's encoding scaling
|
||||||
|
model.bindPose[i].rotation = QuaternionNormalize(model.bindPose[i].rotation);
|
||||||
|
model.bindPose[i].scale.x = model.bindPose[i].scale.y = model.bindPose[i].scale.z = 1.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load bone-pose default mesh into animation vertices. These will be updated when UpdateModelAnimation gets
|
||||||
|
// called, but not before, however DrawMesh uses these if they exists (so not good if they are left empty).
|
||||||
|
if (m3d->numbone && m3d->numskin)
|
||||||
|
{
|
||||||
|
for(i = 0; i < model.meshCount; i++)
|
||||||
|
{
|
||||||
|
memcpy(model.meshes[i].animVertices, model.meshes[i].vertices, model.meshes[i].vertexCount*3*sizeof(float));
|
||||||
|
memcpy(model.meshes[i].animNormals, model.meshes[i].normals, model.meshes[i].vertexCount*3*sizeof(float));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
m3d_free(m3d);
|
m3d_free(m3d);
|
||||||
UnloadFileData(fileData);
|
UnloadFileData(fileData);
|
||||||
}
|
}
|
||||||
|
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load M3D animation data
|
||||||
|
#define M3D_ANIMDELAY 17 // that's roughly ~1000 msec / 60 FPS (16.666666* msec)
|
||||||
|
static ModelAnimation *LoadModelAnimationsM3D(const char *fileName, unsigned int *animCount)
|
||||||
|
{
|
||||||
|
m3d_t *m3d = NULL;
|
||||||
|
unsigned int bytesRead = 0;
|
||||||
|
unsigned char *fileData = LoadFileData(fileName, &bytesRead);
|
||||||
|
ModelAnimation *animations = NULL;
|
||||||
|
int i, j;
|
||||||
|
|
||||||
|
*animCount = 0;
|
||||||
|
|
||||||
|
if (fileData != NULL)
|
||||||
|
{
|
||||||
|
m3d = m3d_load(fileData, m3d_loaderhook, m3d_freehook, NULL);
|
||||||
|
|
||||||
|
if (!m3d || M3D_ERR_ISFATAL(m3d->errcode))
|
||||||
|
{
|
||||||
|
TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to load M3D data, error code %d", fileName, m3d ? m3d->errcode : -2);
|
||||||
|
UnloadFileData(fileData);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
else TRACELOG(LOG_INFO, "MODEL: [%s] M3D data loaded successfully: %i animations, %i bones, %i skins", fileName,
|
||||||
|
m3d->numaction, m3d->numbone, m3d->numskin);
|
||||||
|
|
||||||
|
// no animation or bone+skin?
|
||||||
|
if (!m3d->numaction || !m3d->numbone || !m3d->numskin)
|
||||||
|
{
|
||||||
|
m3d_free(m3d);
|
||||||
|
UnloadFileData(fileData);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
animations = RL_MALLOC(m3d->numaction*sizeof(ModelAnimation));
|
||||||
|
*animCount = m3d->numaction;
|
||||||
|
|
||||||
|
for (unsigned int a = 0; a < m3d->numaction; a++)
|
||||||
|
{
|
||||||
|
animations[a].frameCount = m3d->action[a].durationmsec / M3D_ANIMDELAY;
|
||||||
|
animations[a].boneCount = m3d->numbone;
|
||||||
|
animations[a].bones = RL_MALLOC(m3d->numbone*sizeof(BoneInfo));
|
||||||
|
animations[a].framePoses = RL_MALLOC(animations[a].frameCount*sizeof(Transform *));
|
||||||
|
// strncpy(animations[a].name, m3d->action[a].name, sizeof(animations[a].name));
|
||||||
|
TRACELOG(LOG_INFO, "MODEL: [%s] animation #%i: %i msec, %i frames", fileName, a, m3d->action[a].durationmsec, animations[a].frameCount);
|
||||||
|
|
||||||
|
for (i = 0; i < m3d->numbone; i++)
|
||||||
|
{
|
||||||
|
animations[a].bones[i].parent = m3d->bone[i].parent;
|
||||||
|
strncpy(animations[a].bones[i].name, m3d->bone[i].name, sizeof(animations[a].bones[i].name));
|
||||||
|
}
|
||||||
|
|
||||||
|
// M3D stores frames at arbitrary intervals with sparse skeletons. We need full skeletons at
|
||||||
|
// regular intervals, so let the M3D SDK do the heavy lifting and calculate interpolated bones
|
||||||
|
for (i = 0; i < animations[a].frameCount; i++)
|
||||||
|
{
|
||||||
|
animations[a].framePoses[i] = RL_MALLOC(m3d->numbone*sizeof(Transform));
|
||||||
|
|
||||||
|
m3db_t *pose = m3d_pose(m3d, a, i * M3D_ANIMDELAY);
|
||||||
|
if (pose != NULL)
|
||||||
|
{
|
||||||
|
for (j = 0; j < m3d->numbone; j++)
|
||||||
|
{
|
||||||
|
animations[a].framePoses[i][j].translation.x = m3d->vertex[pose[j].pos].x;
|
||||||
|
animations[a].framePoses[i][j].translation.y = m3d->vertex[pose[j].pos].y;
|
||||||
|
animations[a].framePoses[i][j].translation.z = m3d->vertex[pose[j].pos].z;
|
||||||
|
animations[a].framePoses[i][j].rotation.x = m3d->vertex[pose[j].ori].x;
|
||||||
|
animations[a].framePoses[i][j].rotation.y = m3d->vertex[pose[j].ori].y;
|
||||||
|
animations[a].framePoses[i][j].rotation.z = m3d->vertex[pose[j].ori].z;
|
||||||
|
animations[a].framePoses[i][j].rotation.w = m3d->vertex[pose[j].ori].w;
|
||||||
|
animations[a].framePoses[i][j].rotation = QuaternionNormalize(animations[a].framePoses[i][j].rotation);
|
||||||
|
animations[a].framePoses[i][j].scale.x = animations[a].framePoses[i][j].scale.y = animations[a].framePoses[i][j].scale.z = 1.0f;
|
||||||
|
}
|
||||||
|
RL_FREE(pose);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m3d_free(m3d);
|
||||||
|
UnloadFileData(fileData);
|
||||||
|
}
|
||||||
|
|
||||||
|
return animations;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif // SUPPORT_MODULE_RMODELS
|
#endif // SUPPORT_MODULE_RMODELS
|
||||||
|
Loading…
Reference in New Issue
Block a user