Closed bztsrc closed 2 years ago
Hi @bztsrc! Your Model3D format looks great! I like it a lot! Congratulations!
Feel free to send a PR with this amazing addition! :D
Hi @bztsrc! Your Model3D format looks great! I like it a lot! Congratulations!
Thanks!
Feel free to send a PR with this amazing addition! :D
Sadly since the recent changes in github, I cannot create PRs any more (I have no probs on gitlab, so this must be a github configuration issue).
The Goxel2's maintainer likes this format too, and he has chosen it as Goxel2's main format. I have the same PR creation problems there too :-( (BTW, Model3D can store voxel images, but you won't notice that because on load voxels are transparently converted into a face-culled triangle mesh by the SDK).
Truly sorry about sending a diff, but this is the best I can do in lack of PRs.
Cheers, bzt
@bztsrc Ok, no worries, I will implement the changes you provided.
Are you planning to add animations support? It would be great!
Ok, no worries, I will implement the changes you provided.
Thank you very much! It's great if raylib can handle .m3d files out-of-the-box, because I'm getting to like this lib more and more. I've just recently started experimenting with it, but so far it's pretty awesome!
Are you planning to add animations support? It would be great!
Yes! I'm studying the IQM code now on how to fill up the variables with a skeleton based animation. But it's not simple, because IQM uses joints, while M3D is using a bone skeleton, with keyframes stored as incremental bone changes (each bone is technically a local coordinate system relative to its parent bone). If everything else fails, then I'll use m3d_pose
to generate the skeleton for each frame and I'll load those, that will surely work.
Another issue I have to solve is the textures. As I see all the other loaders use the LoadTexture
function which needs a filename as input, but M3D returns decoded width, height, texture pixel data, because it supports inlined and generated textures too. Doesn't seem to be a big problem, but I'll have to figure out how to integrate these, haven't got time to dig deeper what LoadTexture returns.
Cheers, bzt
raylib animation system was designed following most tipical conventions, actually it should be equivalent to a standard bone system, independent of the IQM file format.
First format supported with animations was IQM but actually, glTF animations was also partially supported in the past. Current structutes should be able to accomodate any skeletal bones-based system, as far as I know.
// Transform, vectex transformation data
typedef struct Transform {
Vector3 translation; // Translation
Quaternion rotation; // Rotation
Vector3 scale; // Scale
} Transform;
// Bone, skeletal animation bone
typedef struct BoneInfo {
char name[32]; // Bone name
int parent; // Bone parent
} BoneInfo;
// Model, meshes, materials and animation data
typedef struct Model {
Matrix transform; // Local transform matrix
int meshCount; // Number of meshes
int materialCount; // Number of materials
Mesh *meshes; // Meshes array
Material *materials; // Materials array
int *meshMaterial; // Mesh material number
// Animation data
int boneCount; // Number of bones
BoneInfo *bones; // Bones information (skeleton)
Transform *bindPose; // Bones base transformation (pose)
} Model;
// ModelAnimation
typedef struct ModelAnimation {
int boneCount; // Number of bones
int frameCount; // Number of animation frames
BoneInfo *bones; // Bones information (skeleton)
Transform **framePoses; // Poses array by frame
} ModelAnimation;
BoneInfo
keeps a reference of every bone to its parent bone and the skeleton is just defined as an array of bones. Every bone also defines a base transform that all together define a bindPose
.
ModelAnimation
is just a sequence of poses.
About the textures, raylib also has the Image
structure that exposes width, height and pixel data directly. Texture2D
just contains the OpenGL id reference for the uploaded Image
in GPU. You can just fill the Image
struct manually with retrieved pixel data and then LoadtextureFromImage()
to upload that data to GPU and get the texture id.
Thank you for the information!
raylib animation system was designed following most tipical conventions
Looks like I can load the animations almost as-is. :-) One question remains, M3D files can store multiple animations (so it should return multiple ModelAnimation records). For example, there can be an animation for walking, one for attack, one for jumping etc.)
About the textures, raylib also has the Image structure
Thank you, in the meantime I've figured it out myself, here's an updated version with pretty useful material support. I've modified the examples/models/models_loading.c example a bit, to accept command line arguments, and added some .m3d example models too. It works great, but looks like models with vertex colors only (no material) needs some fixing, they are loaded as black.
And if you don't mind, I've added Model3D attribution and link to raylib.h.
Cheers, bzt
One question remains, M3D files can store multiple animations
What I mean by that, to handle that, this prototype would need to change:
ModelAnimation *LoadModelAnimations(const char *fileName, unsigned int *animCount);
to
ModelAnimation *LoadModelAnimations(const char *fileName, const char *action, unsigned int *animCount);
or
ModelAnimation **LoadModelAnimations(const char *fileName, unsigned int *animCount);
but obviously these would break the raylib API, so I'm not sure how to proceed.
One solution could be to use animCount as input for the action id (and keep it for frame count as output), but that feels extremely hackish. (This is not a problem for IQM only because the mesh and animation are separated, so people can use different animation files. But here you might have multiple animations in a single file. Actions could be referenced by action id or by action name too.)
Any ideas? Maybe adding a new function?
Cheers, bzt
@bztsrc I'm afraid I don't understand the problem, current LoadModelAnimations()
should be able to load all the included animations (walk, run, attack...), why action
parameter is required? what is the action id?
Could it be that actions
are actually the separate animations? As per my understanding, each action
could be loaded into one ModelAnimation
.
why action parameter is required? what is the action id?
The action parameter is the name of the animation, and action id is just the index. For example, a typical game model can have one animation for "walking", one for "attacking", one for "jumping", then you can use the action name to find the action id for that animation.
Could it be that actions are actually the separate animations?
Yes, exactly. The model has a bind pose (stored in the bones chunk), and then several animations (so called actions in M3D parlance), which are just keyframes with only the modified bones. For every animation, the first frame is a difference to the bind pose, and for the other subsequent frames it's just the difference to the previous frame. Simply put, M3D does not store the entire skeleton for each frame. This results in a very small file size, but complicates loading a bit. That's why I have m3d_pose()
, which takes an action index and a frame index and returns the entire skeleton with all the bones, modified for that frame.
As per my understanding, each action could be loaded into one ModelAnimation.
Well yes. But how should the user know which one? LoadModelAnimations()
requires a filename as input, so how should people know which animation is which? (As I have said, for other formats typically only one animation is stored in a separate file, so this isn't an issue because the filename is different).
Maybe adding a simple char name[32]
to the ModelAnimation
struct and letting the people iterate through it would be enough?
Or maybe I'm just overthinking, this isn't an issue at all, because people are using the same animations within a game for all models therefore it's okay to just rely on the action id (which is the index to the array returned by LoadModelAnimations()
)?
Anyway, for now I'll just implement this without action names, and please consider and tell me if you think adding char name[32]
to ModelAnimation
struct sounds reasonable to you.
Thanks for your answer and all the help you provided, bzt
@bztsrc Personally I would keep it simple and just skip the animations naming for now and just use the index to refer to any animation. User is responsible to define the animation index for their models on user code. Actually, same approach is planned for glTF
models that can also contain multiple animations.
Personally I would keep it simple and just skip the animations naming for now and just use the index to refer to any animation. User is responsible to define the animation index for their models on user code.
Ok, understood! I was also assuming that I'm overthinking this.
Good news, here's a modified version! This loads now vertex colors correctly (and fallbacks to a clay-like color if there are no materials not vertex colors either), and I've implemented bones, skin and animations too. I've also added examples/models/models_loading_m3d.c
, and two example models, suzanne.m3d (the well-known blender mascot) and a seagull.m3d (which is an animated seagull converted from Scorch3D's milkshape model).
Unfortunately it isn't finished just yet. It works great and plays most of the animated models I've tested with, but not all. Sometimes bones are off position, like the seagull's wings. And loading inlined textures isn't perfect yet either, have to debug that too. BTW, in what space does UpdateModelAnimation()
expect the bone vertices? I'm providing them in model space, and not bone local space (these only are identical for the root bone, but not for the child bones), that might be the source of the positioning issue?
Anyway I wanted to send this to you, because it is major milestone on adding animated M3D models!
Cheers, bzt
Hi,
Here's the newest version:
Please merge this as soon as possible, because it contains memory leak fixes too.
As a result, this version supports static meshes 100% perfectly.
The one and only issue that remains is with the UpdateModelAnimation
call. I'm now comparing that to my implementation here, trying to figure out what's the difference. So far both seems to expect the mesh in bind-pose in model-space, and they both convert that into bone-local space as a first step. Yet, for some reason the results are different.
Cheers, bzt
@bztsrc Hi! Excuse the late response! Just implemented the latest changes provided. Thanks!
Just note I'm not implementing the argv inputs for the models_loading
example.
Hi! Excuse the late response! Just implemented the latest changes provided. Thanks!
No worries, and I'm the one saying thanks! :-)
Just note I'm not implementing the argv inputs for the models_loading example.
That's okay! I needed that because drag'n'drop is not working for me at all (and I'm more of a CLI guy, I don't have any resource-eating shiny eye-candy heavy file manager installed, just mc). It's perfectly fine if you not merge that part! (But if you don't mind me asking, I'm curious, any particular reason why to refuse CLI arguments? You don't have to use them if you don't want to, and I bet I'm not the only one who prefers them.)
But back on the topic, any idea on the animation issue? Any suggestion where should I look? Right now I'm dumping transformation matrices and comparing them, but it's going pretty slowly. My guess is, the problem originates from using model-space transformations multiple times, hence the "explosion". Or something like that along the line of space conversion, because the bones and the skin looks just fine.
Cheers, bzt
But if you don't mind me asking, I'm curious, any particular reason why to refuse CLI arguments? You don't have to use them if you don't want to, and I bet I'm not the only one who prefers them.
I try to keep examples as simple as possible and avoid the CLI arguments because when compiling examples for web they can't be used. But I like CLI a lot, actually all my tools have powerful CLI.
any idea on the animation issue? Any suggestion where should I look?
I'm afraid I don't know where it could be the issue, maybe on parent transform propagation between bones? Have you tried calculating a bind pose for one specific frame? You can try drawing the bones transformed with lines/boxes to see if they are correctly transformed...
avoid the CLI arguments because when compiling examples for web they can't be used
Ah, I see, make sense.
But I like CLI a lot, actually all my tools have powerful CLI.
:thumbsup:
Have you tried calculating a bind pose for one specific frame?
That's what I'm doing now dumping the transformation matrices and vertices along the way. (I'm doing the same thing in my viewer which works correctly and comparing the two outputs. But it's difficult and going slowly.)
You can try drawing the bones transformed with lines/boxes to see if they are correctly transformed
I'm over that, that was the very first thing I've checked and the bones look okay to me, that's why I'm stuck. (You can still see drawing the boxes for the bones in models_loading_m3d.c because I haven't removed that part.)
Thanks anyway, back to the boring matrix dumping debugging... :-( I hope I'll figure this one last part out soon. bzt
I'd set aside the bank holiday weekend (at least in part!) to make a shader to replay animations (maybe with key frame morphing) but hit a fairly hard brick wall
https://gitlab.com/bztsrc/model3d/-/issues/3
Hopefully this is a quick fix for some change in blender...
Okay, now I'm totally and absolutely confused. In an effort to figure out where the problem is, I've modified the blender exporter like this:
The exporter now saves the mesh and bone positions and quaternions as they are in blender, as-is.
The importer passes them to raylib's renderer as they are, no conversion nor transformation takes place there either (for the records, the only difference would be in how m3d_pose calculates the bone local space to model space transformation matrix, which matrix the LoadModelAnimationsM3D function does not use).
And guess what, this is what I got: As you can see,
How is this even possible? Either all child bones should be misaligned, or neither of them, but how is it possible that just some bones have wrong axis alignment? Makes totally no sense to me.
I'm not sure if this is an issue in blender (do I have to use some "to model space" transformations explicitly?) or in the raylib's renderer (assuming the data is correct). Any help figuring this out would very much appreciated. Here's the model in question (warning, child bones are not parent local in this one!): cesium1.m3d.gz and the original GLTF I imported into blender: CesiumMan.glb.gz (the blender GLTF importer is buggy, it won't load the textures from the glb file, but that's a different story. Concentrate on the geometry for now.)
Cheers, bzt
this is fantastic work, can't wait for a complete art path with bone animation, I have some interesting ideas for it !
Okay, here's an update. This has almost everything fixed, and bones are working properly, displayed consistently in models_loading_m3d.c as well as in m3dview. Now only the skin issue remains, which turns out was responsible for the right leg / left leg alignment problem (still exists, but since the model is properly oriented, now it appears on a different axis).
@raysan5 please update. Minor modification in src/rmodels.c (forgot to handle child bones properly), otherwise mostly changes under the examples directory. I've added a screenshot and two example models (Suzanne from blender and an animated seagull from Scorched3D. Latter being 11k in size, which includes the texture too :-D). Still not the final version, but I think an important step.
this is fantastic work, can't wait for a complete art path with bone animation, I have some interesting ideas for it !
@chriscamacho thank you very much! Hopefully you don't have to wait for much longer, although I have other projects running unfortunately. FYI, I've updated the Blender exporter too, CesiumMan's twisted animated armature is now exported correctly.
Cheers, bzt
@bztsrc Hi! Nice to see the implementation is taking shape! Congratulations on your progress!
I just implemented the latest changes you send!
And... wait for it... (drumroll)... TADAM!
After many unsuccessful hours of debugging, I realized there's nothing wrong with the skinning code, the data was wrong all along! The skin issue was entirely a blender API fault in the exporter.
So, here is a last update, this is the final version! All known bugs have been accounted for and fixed.
Changes:
A
key.That's all! Thank you very much for all the help you gave, it was a pleasure working with you! Once these changes are merged, you can safely close this issue!
Cheers, bzt
ps: next I'm planning to add Scalable Screen Font support to raylib for my own use (I personally dislike ttf big time, I prefer this single-header lib and its much compact font format). Knowing that raylib already has font support, would you be interested in an SSFN patch? No hard feelings if you say no.
@bztsrc AMAZING! Congratulations! This is a great addition to raylib! Animations support was highly demanded by the raylib community, IQM had many issues and glTF animations were not properly supported... Now we have a great file format with animations support! Thank you very much Zoltan! :D
Just reviewed the code and the example and merged it, now I'm officially announcing it to the community!
next I'm planning to add Scalable Screen Font support to raylib for my own use (I personally dislike ttf big time, I prefer this single-header lib and its much compact font format). Knowing that raylib already has font support, would you be interested in an SSFN patch? No hard feelings if you say no.
This is very interesting! raylib currently uses stb_truetype
for TTF fonts rasterization and atlas is generated internally, how does it compare to SSFN??? I'm interested, feel free to open a new issue/discussion!
And... wait for it... (drumroll)... TADAM!
yes, everything works as it should with your model. I tried to take another one and this is what comes out. blender plugin from your master branch
First, thanks for the great work here! I'm sure it took a long time, this is something very nice raylib has been missing.
I noticed for some strange reasons the example seems to be trying to open the files ".png" and "" This doesn't look right to me, placing a breakpoint on LoadFileData shows this:
Thread 1 "models_loading_" hit Breakpoint 1, LoadFileData (fileName=0x55555653fd40 ".png", bytesRead=0x7fffffffdad0) at utils.c:185
185 unsigned char *data = NULL;
This call with that data seems to originate from _m3d_gettx, as this backtrace shows
#1 0x0000555555650f2e in m3d_loaderhook (fn=0x55555653fd40 ".png", len=0x7fffffffdad0) at rmodels.c:5127
#2 0x000055555561ad73 in _m3d_gettx (model=0x555556533170, readfilecb=0x555555650f0b <m3d_loaderhook>, freecb=0x555555650f30 <m3d_freehook>, fn=0x55555656a9d0 "") at external/m3d.h:2196
The same thing is also done for "". I can't see a possible reason for m3d.h to be doing this, maybe it needs some review?
Thread 1 "models_loading_" hit Breakpoint 1, LoadFileData (fileName=0x55555656a9d0 "", bytesRead=0x7fffffffdad0) at utils.c:185
185 unsigned char *data = NULL;
(gdb) bt
#0 LoadFileData (fileName=0x55555656a9d0 "", bytesRead=0x7fffffffdad0) at utils.c:185
#1 0x0000555555650f2e in m3d_loaderhook (fn=0x55555656a9d0 "", len=0x7fffffffdad0) at rmodels.c:5127
#2 0x000055555561adb0 in _m3d_gettx (model=0x555556533170, readfilecb=0x555555650f0b <m3d_loaderhook>, freecb=0x555555650f30 <m3d_freehook>, fn=0x55555656a9d0 "") at external/m3d.h:2200
here's a backtrace for the empty string case too, if it helps.
Thank you!
I tried to take another one and this is what comes out. blender plugin from your master branch
@GuvaCode: Can you please open an issue in my repo and attach the original model file? I'll look into it. Looks like the skeleton is fine, most of the mesh is fine, just one vertex per bone goes off? Interesting, I would definitely need the original model to debug this.
I noticed for some strange reasons the example seems to be trying to open the files ".png" and ""
@Peter0x44: again, please open an issue in my repo and attach the original model file. It looks like your model has no inlined textures, and that's why _m3d_gettx
is trying to load it from a separate file. So far so good, the issue seems to be there's something wrong with getting the texture's name. I would like to take a look at the original model what texture file names is it using. There should be no textures without a name in an M3D file.
This is very interesting! raylib currently uses stb_truetype for TTF fonts rasterization and atlas is generated internally, how does it compare to SSFN??? I'm interested, feel free to open a new issue/discussion!
@raysan5: okay, I try to keep it brief, sorry if it's going to be long. TL;DR font atlas is not really viable for rendering ligatures, combined diacriticals and other tricky nuances of the waste UNICODE codeplane.
The problem with atlas is, that it works for English language which has only 26 letters, but not so much for UNICODE, because there are simply way too many codepoints (1114112 times font width times font height times 4 bytes) and sometimes you can't choose one glyph, you have to compose from multiple, and other times the same character can have multiple glyphs... Not to mention what happens if a certain glyph is missing and you have to look it up from another font file. Therefore SSFN does not use an atlas, instead it has an efficient internal cache (only for the glyphs which was already rendered at least once, and with only 1 byte per pixel), and composes that cache to an RGBA pixel buffer directly. But truth to be told, even without this glyph cache the SSFN renderer is blazing fast. (It took only 0.15 secs to draw the entire feature demo you see in the README (with more than a dozen vector/bitmaps/pixmaps fonts, including loading, decompressing, decoding, and generating dynamic styling for missing glyphs etc. rasterizing every time in lack of a cache) on my 10 years old and slow machine. I imagine on a brand new computer that would easily take no more than 0.075 secs.
So instead of generating an atlas texture, the API I'm planning to implement would draw an UTF-8 string to an existing texture, which then could be used to display the entire text (not just one letter) as many times as you want. (Currently a similar API exists ssfn_text, but that returns a new pixel buffer, because it's a drop-in-replacement for SDL_ttf's TTF_RenderUTF8_Blended()
. I'd prefer drawing strings to existing raylib textures.)
And furthermore, while stb_truetype is a great lib, it can't be used to render pixmap fonts, so people have to implement their own pixel fonts all the time. Also TTF files are huge and ineffective in the first place. For example, an OpenType TTF, FreeSerif.otf is 2047k, exactly the same typeface encoded in SSFN is just 595k. Similar with TrueType TTF, for example UbuntuBold.ttf is 333k, the same typeface in SSFN is just 40k.
Cheers, bzt
@GuvaCode: Can you please open an issue in my repo and attach the original model file? I'll look into it. Looks like the skeleton is fine, most of the mesh is fine, just one vertex per bone goes off? Interesting, I would definitely need the original model to debug this.
Good. I'll do it in a few hours.
@bztsrc the file I am using is simply the one included in the repo, at examples/models/resources/models/m3d/CesiumMan.m3d
. I won't have time today, but I can file an issue in the repo tomorrow.
Hi,
Could you please add Model 3D format to the supported models? It has an stb-style single header SDK written in ANSI C, looks simple to integrate.
I've already started the necessary work, see below (note this is just a minimal implementation, only loads meshes, but does not handle all the M3D features like skeletal animations etc.) Here are the steps I made:
src/external
#define SUPPORT_FILEFORMAT_M3D 1
to src/config.hLoadM3D
: rmodels.c.gzBut the diff is pretty small, here it is in its entirety for easier review:
NOTE: this just loads the mesh and some material properties, but does not provide full support (yet). This is just the first step.
Please let me know what you think.
Thanks, bzt