SMF - 3D skeletal animation - Now with a custom Blender exporter!

The-any-Key

Member
Just played with it for some hours now, and it's great. The example projects helped a lot. I have started to create some generic scripts:
Code:
// Create 3D model
var skybox=scr_3D_model_create(0,0,0,"skybox");
// Start animation
scr_3D_model_set_animation(skybox,"Test",1000);
I am trying to find some texture UV in the editor. But I will check the material example for some insight.

Is there any plans on animation mix? Ex mix sample1 with sample2 with 30%/70% rate? So you could smoothly go from one animation to another?
 
Last edited:

TheSnidr

Heavy metal viking dentist
GMC Elder
Just played with it for some hours now, and it's great. The example projects helped a lot. I have started to create some generic scripts:
Code:
// Create 3D model
var skybox=scr_3D_model_create(0,0,0,"skybox");
// Start animation
scr_3D_model_set_animation(skybox,"Test",1000);
I am trying to find some texture UV in the editor. But I will check the material example for some insight.

Is there any plans on animation mix? Ex mix sample1 with sample2 with 30%/70% rate? So you could smoothly go from one animation to another?
The Model Tool does not let you edit texture UVs, that has to be done before the model is imported! It only allows for rough UV manipulation like mirroring and rotation.
Animation blending is already implemented! ;) Look up the script called smf_sample_blend in the Animation folder. This script is also used in the Clocktown demo (Demo 6) to interpolate between Link's animations.
 
I've made a little script to combine a sample with parts of another sample, I haven't tried the whole pose system yet but I assume this script is somewhat like
smf_pose_replace_bone() but for a array of bones.
Let me know if you know if there is any point with this kind of script or if it's more efficient to use the pose system (or any other way).

Here is a sample:
https://gyazo.com/c1394ba701d486dfdeab16f6f00df2a8
I use smf_sample_blend() to blend between a idle state and a walk state.
Then I use smf_sample_combine() to replace the bones in the right arm with a sword swing animation.

smf_sample_combine():
Code:
/// @description smf_sample_combine(sampleFrom, boneList, sampleTo)
/// @param sampleFrom
/// @param boneList
/// @param sampleTo
/*
Return sampleTo with a array of bones (boneList) overwriten from sampleFrom
*/

var sampleFrom, boneList, sampleTo, i, j, num, row;

sampleFrom = argument0
boneList = argument1
sampleTo = argument2
num = array_length_1d(boneList)

for(i = 0; i < num; i++) {
    row = boneList[i] * 8
    for(j = 0; j < 8; j++) {
        sampleTo[row + j] = sampleFrom[row + j]
    }
}

return sampleTo
 
I found a tiny bug :)

Code:
FATAL ERROR in
action number 1
of Draw Event
for object oCtrl:

ds_list_find_value argument 1 incorrect type (undefined) expecting a Number (YYGI32)
 at gml_Script_smf_sample_get_bone_orientation (line 12) - S = bindPose[| bone];
############################################################################################
--------------------------------------------------------------------------------------------
stack frame is
gml_Script_smf_sample_get_bone_orientation (line 12)
called from - gml_Object_oCtrl_Draw_0 (line 75) - boneDq = smf_sample_get_bone_orientation(sample, 19)
It occurs (some times, not sure why it's just sometimes) when you blend 2 animations and then try to get a bones orientation.
smf_sample_get_bone_orientation expect the last 2 elements in the samples to give the animationIndex and modelIndex, which it should.
To fix this you need to edit the smf_sample_blend() script to not blend the last 2 elements in the array.

From:
Code:
t1 = 1 - t2;
num = array_length_1d(sample1);
returnSample = -1;
for (i = 0; i < num; i ++)
{
    returnSample[i] = sample1[i] * t1 + sample2[i] * t2;
}
return returnSample;

To:
Code:
t1 = 1 - t2;
num = array_length_1d(sample1) - 2;
returnSample = -1;
for (i = 0; i < num; i ++)
{
    returnSample[i] = sample1[i] * t1 + sample2[i] * t2;
}
returnSample[num] = sample1[num];
num++;
returnSample[num] = sample1[num];
return returnSample;
 
Last edited:

Joe Ellis

Member
In gms 2, if you multiply an array, does it scalar multiply every entry in it? thats an amazing improvement to gms1.4
 
Z

Zeke

Guest
I found a tiny bug :)

Code:
FATAL ERROR in
action number 1
of Draw Event
for object oCtrl:

ds_list_find_value argument 1 incorrect type (undefined) expecting a Number (YYGI32)
 at gml_Script_smf_sample_get_bone_orientation (line 12) - S = bindPose[| bone];
############################################################################################
--------------------------------------------------------------------------------------------
stack frame is
gml_Script_smf_sample_get_bone_orientation (line 12)
called from - gml_Object_oCtrl_Draw_0 (line 75) - boneDq = smf_sample_get_bone_orientation(sample, 19)
It occurs (some times, not sure why it's just sometimes) when you blend 2 animations and then try to get a bones orientation.
smf_sample_get_bone_orientation expect the last 2 elements in the samples to give the animationIndex and modelIndex, which it should.
To fix this you need to edit the smf_sample_blend() script to not blend the last 2 elements in the array.

From:
t1 = 1 - t2;
num = array_length_1d(sample1);
returnSample = -1;
for (i = 0; i < num; i ++)
{
returnSample = sample1 * t1 + sample2 * t2;
}
return returnSample;

To:
t1 = 1 - t2;
num = array_length_1d(sample1) - 2;
returnSample = -1;
for (i = 0; i < num; i ++)
{
returnSample = sample1 * t1 + sample2 * t2;
}
returnSample[num] = sample1[num];
num++;
returnSample[num] = sample1[num];

return returnSample;
Amazing. I was just trying to solve this exact error for the past 2 hours and you already found and posted a solution to it. This solution worked perfectly for me.
 
In gms 2, if you multiply an array, does it scalar multiply every entry in it? thats an amazing improvement to gms1.4
oh no, not that I know of. The code I posted have been altered by posting it as regular text, I should have used the code tag. Apperntly [ i ] (without spaces) will enable cursive text when uses outside the code tag
 

Joe Ellis

Member
oh no, not that I know of. The code I posted have been altered by posting it as regular text, I should have used the code tag. Apperntly [ i ] (without spaces) will enable cursive text when uses outside the code tag
Ohh i get it now, actually thats happened to me before and I never knew why lol, I just kept correcting how it suddenly turns to italics, makes sense now
 

TheSnidr

Heavy metal viking dentist
GMC Elder
I've made a little script to combine a sample with parts of another sample, I haven't tried the whole pose system yet but I assume this script is somewhat like
smf_pose_replace_bone() but for a array of bones.
Let me know if you know if there is any point with this kind of script or if it's more efficient to use the pose system (or any other way).

Here is a sample:
https://gyazo.com/c1394ba701d486dfdeab16f6f00df2a8
I use smf_sample_blend() to blend between a idle state and a walk state.
Then I use smf_sample_combine() to replace the bones in the right arm with a sword swing animation.

smf_sample_combine():
Code:
/// @description smf_sample_combine(sampleFrom, boneList, sampleTo)
/// @param sampleFrom
/// @param boneList
/// @param sampleTo
/*
Return sampleTo with a array of bones (boneList) overwriten from sampleFrom
*/

var sampleFrom, boneList, sampleTo, i, j, num, row;

sampleFrom = argument0
boneList = argument1
sampleTo = argument2
num = array_length_1d(boneList)

for(i = 0; i < num; i++) {
    row = boneList[i] * 8
    for(j = 0; j < 8; j++) {
        sampleTo[row + j] = sampleFrom[row + j]
    }
}

return sampleTo
Very nice! Just a heads-up though, modifying the sample directly could lead to some unwanted glitches, since there's no longer any guarantee that the bones will stay attached after modifying. If you make the body bob up and down while walking, the arm could stay still, effectively detaching from the rest of the body :p
Luckily you can often get away with tiny errors like this, and modifying the sample directly like this is ridiculously much faster than making a pose, modifying that and then creating a sample! But since it technically is the "wrong" way to do it, I won't include it in the library.

There is now a new version available! This one will be backwards compatible with all previous versions of the format. It will also have a limited FBX importer, allowing the importing of a model, rig and skinning info, but I'm still working on getting animation import up and running. So the models will have to be animated in the model tool for the time being!
Also, there's been some optimization of the animations - zero-length bones will no longer be included in the sample! This also includes the root bone, so all animations will have at the very least one bone fewer.
Models are compressed with zlib by default, leading to much smaller file sizes. However, the decompress function doesn't seem to work properly in HTML5, so I've added the option to not compress your buffers.
Ideally, all import scripts should be updated if you already have a project that uses SMF.

Download SMF version 0.9.7

It also features this badass logo made by @Chris Goodwin:
 

TheSnidr

Heavy metal viking dentist
GMC Elder
Thank you for the heads up and the in-depth explanation!
Also glad to see a new version! Do you have a page for this project btw or is this the official page?
I'm still impressed you figured out how to edit the sample though :D Please, keep experimenting like this!
This topic is the only official channel for the SMF system ;)
 
Thanks!
Well, I've actually made a system like this myself before. The differences from this is that it was based on matices in the shader instead of DQs even tho the rotations were stored with a single quaternion.
It initially only supported exports from Blender which I really liked because if you gonna do animations, why not use a very know and powerful tool as Blender?
That, however, also ment that you had to do the skinning and animations in Blender and this is one of the reasons I never finished it.
I couldn't fully figure out how to export the keyframes (they use some modified version of a cubic bezier cruve) of a of the animation so I hade to export each frame which can really be a good thing but is also limiting, which I didn't like.
You also had to animate with quaternions in Blender (that is, enable them before you start animating) or the export wouldn't work.
For me, that didn't matter at all but if someone else was gonna use it, it might be really frustrating.
The problem with that is that exporting from euler angles (the default style) to quaternions will mess up the rotations and make the whole animation look different when you run it.
So after messing with the Blender export, I tried to make my own skinning and animation program (kinda like your tool) and there I got stuck with skinning which I couldn't fix/find a decent solution for so I put the whole project on ice.

But all that doesn't matter anymore since this wonderful tool exists! Which also have a lot more features that I didn't think of.
The only thing I can see that I did different that might be a advantage is that I split up the models and the animations so that you could use one animation for multiple models if they had the exact same amount of bones.
Let's say you have 10 different human-like models with the same skeleton, they could all use the same animations.
That made sense since the animation files became quite large because I had to export every frame. But if you could copy animations between models in your tool, there would be no need to be able to do that.

I also like to add that it's really enjoyable to work with this tool/library, it's easy to use and have a lot of smart functionality. It's also super nice to have the actually source code for the library to be able to fix/add stuff as you like!

Sorry for the long story but I thought you might find it all interesting. :p
 

The-any-Key

Member
When you try set a specific number it's somewhat hard to get it. It jumps from 0.99 to 1.02 sometimes when I want 1.
upload_2018-6-18_7-46-56.png
Tips is to add the mouse wheel to just +-0.01 or enable the arrow to left<>right +-0.01 up<>down +-0.1

Is it possible to make the material emit light?
 
Last edited:
Got a error with the new version of the model tool:
Code:
___________________________________________
############################################################################################
FATAL ERROR in
action number 1
of  Step Event0
for object oRigSystem:

Variable oRigSystem.NodeDQ(100348, -2147483648) not set before reading it.
at gml_Script_rig_bone_get_local_dq
############################################################################################
--------------------------------------------------------------------------------------------
stack frame is
gml_Script_rig_bone_get_local_dq (line -1)
gml_Script_perform_actions
gml_Object_oRigSystem_Step_0
What I did:
1) Loaded a .smf model created in the previous version.
2) Went to the animation tab.
3) Copied a animation.
4) Selected the copied animation and renamed it.
5) Selected "Rotate world X (6)".
6) Picked a bone to rotate.

And right at that moment it broke.


Edit:
Also, I see that you fixed the scaling issue! :D and now I understand what you mean with the font issue you talked about before, some letters like to just jump 1 pixel down.
 

TheSnidr

Heavy metal viking dentist
GMC Elder
@Carl Nylander: I actually had animations stored as separate files, but figured that since they're so closely tied up to the bind pose and the skinning of the model itself, it would be easiest to just combine it all into one file. Assuming their skeletons are the same, you can still use the sample generated from one model on any model with the same skeleton though! I've also made it easy to import animations from one .smf file to another with the "Import animation" button.
Thank you for notifying me about this error! It has been fixed, here's the new download link:
Download version 0.9.71

@The-any-Key: Thanks for the request, I've made it round to the nearest 0.05 so that hitting those sweet, sweet integers is easier :D
 

The-any-Key

Member
those sweet, sweet integers
Now I can breathe again :)

Found a bug in 0.9.71 when add and then remove diffuse texture:
___________________________________________
############################################################################################
FATAL ERROR in
action number 1
of Step Event0
for object oRigSystem:
Push :: Execution Error - Variable Index [0,13] out of range [1,1] - -5.editorTextureIndex(100193,13)
at gml_Script_press_buttons
############################################################################################
--------------------------------------------------------------------------------------------
stack frame is
gml_Script_press_buttons (line -1)
gml_Object_oRigSystem_Step_0

I think there is a problem with the height map too:
 
Last edited:

TheSnidr

Heavy metal viking dentist
GMC Elder
Now I can breathe again :)

Found a bug in 0.9.71 when add and then remove diffuse texture:
___________________________________________
############################################################################################
FATAL ERROR in
action number 1
of Step Event0
for object oRigSystem:
Push :: Execution Error - Variable Index [0,13] out of range [1,1] - -5.editorTextureIndex(100193,13)
at gml_Script_press_buttons
############################################################################################
--------------------------------------------------------------------------------------------
stack frame is
gml_Script_press_buttons (line -1)
gml_Object_oRigSystem_Step_0

I think there is a problem with the height map too:
Ah, keep telling me about errors like these! I never delete textures, so I would've never found this error :p The exported file only includes textures that are actually used, so in a way, it cleans up on its own. But still, it's useful to be able to delete textures.
Here's a new version with only that error fixed:
Download v0.9.72

The heightmap works exactly as intended by the way! :D Your problem is that the normals are rounded, so it attempts to shade the cube as if it's a round object. Press the "Flat normals" button in the lower left, and it should work better.
Keep in mind though, when using height maps, it's often better to use a repeating texture, as nearby textures may bleed over into the current one, like this:

So either a repeating texture, or make sure the edges of the heightmap are all white
 

The-any-Key

Member
Your problem is that the normals are rounded, so it attempts to shade the cube as if it's a round object.
upload_2018-6-20_7-53-39.png
Ahh. Thanks. Got it working. Easy fix with white borders or I could also move the textures so they are more apart from each other on the diffuse texture. And with some noise on the normal map on the edges makes the hard edges go away.

One thing with the model tool:
It's a little annoying when you drag the sliders and happen to move your mouse down a little and start drag the other sliders by mistake. Is it possible to lock it so you only move the slider you started to click on?

On my wish-list for SMF is:
Opacity map (could save a ton of vertexes on models)
 

TheSnidr

Heavy metal viking dentist
GMC Elder
Ahh. Thanks. Got it working. Easy fix with white borders or I could also move the textures so they are more apart from each other on the diffuse texture. And with some noise on the normal map on the edges makes the hard edges go away.

One thing with the model tool:
It's a little annoying when you drag the sliders and happen to move your mouse down a little and start drag the other sliders by mistake. Is it possible to lock it so you only move the slider you started to click on?

On my wish-list for SMF is:
Opacity map (could save a ton of vertexes on models)
Nice one, I've been annoyed by the sliders myself, but not annoyed enough to replace them xD
Luckily it was a simple fix. I just hope I didn't break anything else in the process :p
Download v0.9.73
You can load textures with transparency already though, just load 'em as PNGs
 

The-any-Key

Member
You can load textures with transparency already though, just load 'em as PNGs
Works like a charm. Even if you can see through the box in the transparent areas when you get close. It works as a cheap way to remove flat edges and only cost you 8 vertexes and 6 faces if backside culling is on. You can always create a black box inside the model to cover some and it will still be less compare to all vertexes needed for sculpting this.
upload_2018-6-20_22-4-9.png
 

The-any-Key

Member
I know I am doing something wrong. I try to compare notes with the Demo 6 when drawing Link. But it seems I can not put the finger on what I am doing wrong:
upload_2018-6-23_9-4-41.png
 

The-any-Key

Member
Have you enabled z-buffer reading and writing?
That's the one, got it working. I thought I called smf_3d_enable (that include the z buffer enabling) in my wrapper for SMF but I only did it in the Room change event. Not in the create event. So the first room had it disabled :)
 

The-any-Key

Member
Created a mixer so you can easy mix between multiple animations. Inspired by the Spine animation mixer system:
You set the start mix % and milliseconds each transition take. You specify from and to animation. (I set the transition time to 1000 ms so I could click the buttons so it would have 4-6 animation sets to try mix between)
Code:
        scr_3D_model_animation_mix(test_model,"Idle","Run",0.01,1000);
        scr_3D_model_animation_mix(test_model,"Run","Idle",0.01,1000);
        scr_3D_model_animation_mix(test_model,"Idle","Arms",0.01,1000);
        scr_3D_model_animation_mix(test_model,"Arms","Run",0.01,1000);
Then you can just set the animation and the mixer will handle the transition. Even if you go crazy on the buttons and a transition before has just begun it will mix the new animation set into the last one and the one before that... to give a smooth transition.
Code:
        if keyboard_check_released(ord("R")) scr_3D_model_animation_set(test_model,"Run",500);
        if keyboard_check_released(ord("I")) scr_3D_model_animation_set(test_model,"Idle",1000);
        if keyboard_check_released(ord("A")) scr_3D_model_animation_set(test_model,"Arms",500);
Another wish:
A wish for the Model tool would be that we could drag the timeline in the animation section with the mouse to step play the animation.
 

The-any-Key

Member
Could you include the clock-town in a non exported/optimized/compiled way so we could have a look on it in the model tool. The demo6 is harder to understand if we don't have the model data to compare with in the model tool.
 

TheSnidr

Heavy metal viking dentist
GMC Elder
Very nice suggestions, @The-any-Key! I'm loving all the feedback I get these days :D Will definitely implement both node names and animation stepping. I like how your animation mixing looks, would you mind posting the script for it?
I've actually been kinda waiting for somebody to ask for the Clocktown files ;) About time! Here they are: Download.

@Carl Nylander: Good idea to benchmark them! So it seems like it's around 5-6 times faster to use linearized animations. There really isn't much visible difference!
 

The-any-Key

Member
I like how your animation mixing looks, would you mind posting the script for it?
This should cover what I do to mix between them. In general, You pre-define in what % the mix should start and how long the mix should be with scr_3D_model_animation_mix. When you set an animation with scr_3D_model_animation_set it will check the old animation and look up if there is a mix between the new animation. If there is it will check if there is still a mix going on from a previous animation set. If it is, it will save the current mixing values into the mix list so it can loop all mixes in order and sample each one on top of each other. The sample blend start with the start % and gains until the new animation got 100%. This will clear any old mix because there is no point mix the old ones if there is a mix with 100%. The mixer will keep the last mix sample (even if it's 100%) until the current animation mix is done. When done it will clear the mix list and only sample the current animation.
Code:
/// @description  scr_3D_model_init();
// Init model link
model_3d_model=noone;
model_3d_string_name="";
model_3d_animation=noone;
model_3d_animation_loop_time=1000;
model_3d_animation_time=0;
// Hold all mixes
model_3d_animation_mix_library_array=noone;
model_3d_animation_mix_exists=false;
model_3d_animation_mix_list=ds_list_create();
model_3d_animation_mix_value=1;
model_3d_animation_mix_acc=0;

// model_3d_animation_mix_library
enum aml
{
    from,
    to,
    mix,
    mix_acc,
    life,
    loop_speed,
    animation_time,
    mix_value
}
Code:
/// @description  scr_3D_model_animation_mix(object_3d_id, running_animation_string_name, to_animation_string_name, start_mix_value, under_milliseconds);
/// @param object_3d_id
/// @param running_animation_string_name
/// @param to_animation_string_name
/// @param start_mix_value
/// @param under_milliseconds
var object_3d_id=argument0;
var running_animation_string_name=argument1;
var to_animation_string_name=argument2;
var mix=argument3;
var under_milliseconds=argument4;
with object_3d_id
{
    var from_animation=smf_animation_get_index(model_3d_model,running_animation_string_name);
    var to_animation=smf_animation_get_index(model_3d_model,to_animation_string_name);
    // Check if exists
    if from_animation>-1 and to_animation>-1
    {
        // Set that animation mix exists, so we don't need to run is_array
        model_3d_animation_mix_exists=true;
        // Generate new mix
        var new_mix_array=noone;
        new_mix_array[aml.from]=from_animation;
        new_mix_array[aml.to]=to_animation;
        new_mix_array[aml.mix]=mix; // Mix value to the new animation 0.9 means 90% of the new animation will show
        new_mix_array[aml.mix_acc]=(1-mix)/under_milliseconds; // Increase mix every millisecond to make it 100% the new animation for a smooth fade
        new_mix_array[aml.life]=under_milliseconds;
        new_mix_array[aml.loop_speed]=0; // Hold loop speed
        new_mix_array[aml.animation_time]=0; // Hold current animation time, so we can continue play it in the same speed
        new_mix_array[aml.mix_value]=0; // If we start a 3th animation when we still mix between 1 and 2 (current), we need to save the current mix value so we can continue to mix with the old one
        // Save mix values
        if is_array(model_3d_animation_mix_library_array)
        {
            // Get last index
            var last_index=array_length_1d(model_3d_animation_mix_library_array);
            model_3d_animation_mix_library_array[last_index]=new_mix_array;
        }
        else
        {
            // Save first
            model_3d_animation_mix_library_array[0]=new_mix_array;
        }
    }
    else
    {
        scr_3D_model_debug_message("Animation " + animation_string_name + " not found in " + model_3d_string_name + "!");
    }
}
Code:
/// @description  scr_3D_model_animation_set(object_3d_id, animation_string_name, loop_speed_milliseconds);
/// @param object_3d_id
/// @param animation_string_name
/// @param loop_speed_milliseconds
var object_3d_id=argument0;
var animation_string_name=argument1;
var loop_speed_milliseconds=argument2;
with object_3d_id
{
    var new_animation=smf_animation_get_index(model_3d_model,animation_string_name);
    // Check if exists
    if new_animation>-1
    {
        // Check if mix exists
        if model_3d_animation_mix_exists
        {
            // Loop and check if find this mix
            var al=array_length_1d(model_3d_animation_mix_library_array);
            for (var i = 0; i < al; ++i)
            {
                // Get array
                var mix_array=model_3d_animation_mix_library_array[i];
                // Check if match this mix
                if mix_array[aml.from]=model_3d_animation and mix_array[aml.to]=new_animation
                {
                    // Found mix
                    var mix_clone=scr_3D_tool_clone_array(mix_array);
                    // Add current loop speed and time so we can continue animate the current one and mix in the new one
                    mix_clone[aml.loop_speed]=model_3d_animation_loop_time;
                    mix_clone[aml.animation_time]=model_3d_animation_time;
                    // Save the current mix, if the mix was not done yet
                    // We need to continue mix the last animation with this old animation, with the current mix values
                    mix_clone[aml.mix_value]=model_3d_animation_mix_value
                    // We will mix the old animation with the new current one with these values
                    model_3d_animation_mix_value=mix_clone[aml.mix];
                    model_3d_animation_mix_acc=mix_clone[aml.mix_acc];
                    // Add mix to draw
                    ds_list_add(model_3d_animation_mix_list,mix_clone);
                    break;
                }
            }
        }
        // Set new animation
        model_3d_animation=new_animation;
        model_3d_animation_loop_time=loop_speed_milliseconds;
        // Reset current counter, so the animation start in the beginning
        model_3d_animation_time=0;
    }
    else
    {
        // Reset
        model_3d_animation=noone;
        ds_list_clear(model_3d_animation_mix_list);
        scr_3D_model_debug_message("Animation " + animation_string_name + " not found in " + model_3d_string_name + "!");
    }
}
Code:
/// @description  scr_3D_model_draw();
// Delta milliseconds
var delta_milli=delta_time*0.001;
var sample=noone;

if model_3d_model!=noone
{
        // Check if we currently mix animations, we last mix with the current model_3d_animation
        if ds_list_size(model_3d_animation_mix_list)>0
        {
            // Gain mix, for current animation
            model_3d_animation_mix_value+=delta_milli*model_3d_animation_mix_acc;
            // Check if 1, then there is no point get samples becuase the current animation has taken over
            if model_3d_animation_mix_value>=1
            {
                // Clear all mixes
                ds_list_clear(model_3d_animation_mix_list);
                // Clamp 1
                model_3d_animation_mix_value=1;
            }
            // Loop list and mix all
            var al=ds_list_size(model_3d_animation_mix_list);
            var mix_array=noone;
            var dead_exists=false;
            var acumelated_sample=noone;
            for (var i = 0; i < al; ++i)
            {
                // Reset sample
                sample=noone;
                // Get mix array
                mix_array=model_3d_animation_mix_list[| i];
                // Make sure is array
                if is_array(mix_array)
                {
                    // Remove time from mix life
                    mix_array[aml.life]-=delta_milli;
                    // Continue acc the mix value so we can mix old ones with this
                    mix_array[aml.mix_value]+=delta_milli*mix_array[aml.mix_acc];
                    // If this animation got 1 or above we can reset the old ones
                    if mix_array[aml.mix_value]>=1
                    {
                        // Reset old animations that is before this one
                        // We keep this one until the current or animation after this is done
                        for (var ii = 0; ii < i; ++ii)
                        {
                            // Destroy this mix
                            model_3d_animation_mix_list[| ii]=noone;              
                        }
                        // Clamp
                        mix_array[aml.mix_value]=1;
                    }
                    // Check if still alive
                    if mix_array[aml.life]>0
                    {
                        // Add time to animation
                        mix_array[aml.animation_time]+=delta_milli;
                        // Get animation state
                        sample=smf_sample_create(model_3d_model,mix_array[aml.from],0,mix_array[aml.animation_time]/mix_array[aml.loop_speed]);
                        // Check if got previous sample
                        if is_array(acumelated_sample)
                        {
                            // Mix with the old animation
                            acumelated_sample=smf_sample_blend(acumelated_sample,sample,mix_array[aml.mix_value]);
                        }
                        else
                        {
                            // Add first
                            acumelated_sample=sample;
                        }
                        // Save back new mix array
                        model_3d_animation_mix_list[| i]=mix_array;
                    }
                    else
                    {
                        // Destroy this mix
                        model_3d_animation_mix_list[| i]=noone;
                        dead_exists=true;
                    }
                }
            }
            // Remove dead
            if dead_exists
            {
                scr_3D_tool_list_remove_noone(model_3d_animation_mix_list);
            }
            // Get current animation sample
            // Gain time in milliseconds
            model_3d_animation_time+=delta_milli;
            // Get animation state
            var current_sample=smf_sample_create(model_3d_model,model_3d_animation,0,model_3d_animation_time/model_3d_animation_loop_time);  
            // Mix with current animation
            if is_array(acumelated_sample)
            {
                sample=smf_sample_blend(acumelated_sample,current_sample,model_3d_animation_mix_value);
            }
            else
            {
                // Nothing to mix with, all mixes ended
                sample=current_sample;
            }
        }
        else
        {
            // Gain time in milliseconds
            model_3d_animation_time+=delta_milli;
            // Get animation state
            sample=smf_sample_create(model_3d_model,model_3d_animation,0,model_3d_animation_time/model_3d_animation_loop_time);
            // Loop time (so the sample script is less stressed when high numbers)
            while model_3d_animation_time > model_3d_animation_loop_time{model_3d_animation_time-=model_3d_animation_loop_time;}
            while model_3d_animation_time < 0{model_3d_animation_time+=model_3d_animation_loop_time;}
        }
 
        // Draw
        smf_model_draw(model_3d_model,sample);
}
Code:
/// @description  scr_3D_tool_list_remove_noone(list);
/// @param list
var list=argument0;
// Get a noone pos
var pos=ds_list_find_index(list,noone);
// Check if found noone pos
while pos>-1
{
    // Delete noone pos
    ds_list_delete(list,pos)
    // Find next noone pos
    pos=ds_list_find_index(list,noone);
}
Code:
/// @description  scr_3D_tool_clone_array(array_to_clone);
/// @param array_to_clone
var array_to_clone=argument0;
// Make "change" so it becomes copy
array_to_clone[0]=array_to_clone[0];
return array_to_clone;

Here they are: Download.
:thumbsup:

Wish #4325:
Pan camera in the model tool (top-right view) so we can zoom in specific parts of the model.

#4326:
The model tool show the loaded smf name (if any) in the window title

#4327:
When create collision buffer it ask for the bleed over value. It would be great to select a node with an assigned model and get the radie from that model. It works when you use one collision sphere but not with two. Maybe there is another solution. Maybe a tool that create temporally spheres so you could "measure" it in the model tool. To get the best bleed over value.
 
Last edited:

The-any-Key

Member
"Merge same" produce this error:
___________________________________________
############################################################################################
FATAL ERROR in
action number 1
of Step Event0
for object oRigSystem:
Push :: Execution Error - Variable Index [0,14] out of range [1,3] - -5.editorTextureIndex(100194,14)
at gml_Script_press_buttons
############################################################################################
--------------------------------------------------------------------------------------------
stack frame is
gml_Script_press_buttons (line -1)
gml_Object_oRigSystem_Step_0
 
Last edited:

The-any-Key

Member
EDIT:
My bad. You need to mark the sprites as "separate texture page" when you compile or export.
Wish:
Add that info to the mini guide in the tool on the export page.

ORG:
There seems to be some bugs with the "compiler" in the export section in the model tool. This a image from GMS2 with the compiled version loaded:
upload_2018-6-27_15-4-30.png
When I just load the saved smf file it works fine:
upload_2018-6-27_15-4-49.png

It seems the material from the glass take over and the first material is skipped.

I compiled car, front wheel and back wheel. Car and wheels use the same diffuse map, normal map and specular map. The glass got its own texture and material but is in the car smf file.
You can check the compiled files:
https://drive.google.com/open?id=1TEBJ1btwi5Y07ErGsbFl2f-pfek8HJw3
Compare to the original smf files:
https://drive.google.com/open?id=1mlSi_TeAlpK7O4dGVHajpgU44V_zuw5S
https://drive.google.com/open?id=1JBAWlvV-uWnX83gmYXUW1xlrIf6kcjMh
https://drive.google.com/open?id=1XM1w7Rlo0_7POycEQqmOt5xbfP5zHsbf

I also get an error sometimes when compiling only the car:
EDIT: Check later posts for more specifics.
___________________________________________
############################################################################################
FATAL ERROR in
action number 1
of Step Event0
for object oRigSystem:
DoDiv :2: undefined value
at gml_Script_batch_compile
############################################################################################
--------------------------------------------------------------------------------------------
stack frame is
gml_Script_batch_compile (line -1)
gml_Script_press_buttons
gml_Object_oRigSystem_Step_0

And when I delete the whole batch list, all of them comes back when I restart the Model tool:
upload_2018-6-27_15-23-22.png
 
Last edited:
J

jaydee

Guest
Just starting to play around with this tool, and experiencing a bit of a bug when loading textures:

When I load the texture (*.png) into SMF, it loads blue: https://imgur.com/drYjX2e
The actual texture is skin coloured: https://imgur.com/HBR8apK

I had to use an external program to convert from png to bmp to get it to load correctly. Happens with about 50% of the *.png textures in this particular model!
 

The-any-Key

Member
You can try reproduce the compile error by add this in a batch:
https://drive.google.com/open?id=1JBAWlvV-uWnX83gmYXUW1xlrIf6kcjMh

When you click compile you should get this error:
___________________________________________
############################################################################################
FATAL ERROR in
action number 1
of Step Event0
for object oRigSystem:
DoDiv :2: undefined value
at gml_Script_batch_compile
############################################################################################
--------------------------------------------------------------------------------------------
stack frame is
gml_Script_batch_compile (line -1)
gml_Script_press_buttons
gml_Object_oRigSystem_Step_0

EDIT:
See next post.
 
Last edited:

The-any-Key

Member
Ok. I boiled the error:
___________________________________________
############################################################################################
FATAL ERROR in
action number 1
of Step Event0
for object oRigSystem:
DoDiv :2: undefined value
at gml_Script_batch_compile
############################################################################################
--------------------------------------------------------------------------------------------
stack frame is
gml_Script_batch_compile (line -1)
gml_Script_press_buttons
gml_Object_oRigSystem_Step_0
Down to the problem. When you have a model with a normal map and save to SMF file. Then you load another model and use a normal map for that model and save to SMF file. Then you add both to a batch and then try compile. You get the error. If you remove the normal map from one of the models it works fine.

Conclusion: The exporter can't handle multiple normal maps.
 
Last edited:

The-any-Key

Member
Fount another one:
When I have 3 models with a 256x256, 512x512 and 512x512 textures. And I try compile these into the same texture page. The compiler refuse to put them on a 1024x1024 page. It just saves them as 3 separate textures. I tested to mess with the texture page size/allow resize/commpression and it still wont put them together to one texture page.
 

TheSnidr

Heavy metal viking dentist
GMC Elder
I've fixed a lot of the reported bugs, and I'm currently giving the compiling system a rewrite. I can confirm that there are indeed a few problems with how it's set up today. The next installment will not be as finnicky to use! Hope to get it done some time today. As it is in the version you use, the compiler doesn't add vertex buffers and their textures to the compiling batch if they have normal maps enabled - and if all models have normal maps enabled, the ds_priority storing the models to be compiled is empty, giving an undefined error when trying to read from it! It's a silly bug, but I wouldn't have found it any time soon without your help.
By the way, try to avoid double (and triple!) posting! I can see if you have edited your post.

@jaydee: Could you send me the png file? I haven't experienced that before. Do you know what program has been used to create it originally?
It uses the built-in sprite_add to load .png files, so this must be a bug in GM :p

EDIT:
So, the idea for the new compiling system, is to make use of GM's own texture packing. I've made it so that textures no longer have to be marked with "Separate Texture Page". This means that there's no need to pack multiple models onto the same texture page, and textures don't even have to be sized in powers of two. You can of course still "compile" single models to combine their vertex buffers and textures!
 
Last edited:

The-any-Key

Member
Bug in smf_sample_get_bone_orientation:
Code:
var bindPose, modelIndex, bone, sample, R, S, Q, i;
sample = argument0;
bone = ds_list_find_value(modelIndex[| SMF_model.BindSampleMappingList], argument1);
modelIndex = sample[array_length_1d(sample) - 1];
Code:
var bindPose, modelIndex, bone, sample, R, S, Q, i;
sample = argument0;
modelIndex = sample[array_length_1d(sample) - 1];
bone = ds_list_find_value(modelIndex[| SMF_model.BindSampleMappingList], argument1);
modelIndex need to be one row up. Else modelIndex will give an non existing variable error.

EDIT:
Seems to be a bug when you get a linear sample too: (Or maybe it's just me being dumb again)
When I use this:
Code:
var default_sample=smf_sample_create(model_3d_link_library_my_parent.model_3d_model,0,SMF_play_linear,0);
I will get an array with:
{ { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,16 }, }
This has a lot of zero data and cant be used with smf_sample_get_bone_orientation(default_sample,1). It just give me wrong orientation.

Code:
        var default_pose=smf_pose_create(model_3d_link_library_my_parent.model_3d_model,0,SMF_play_linear,0);
        default_sample=smf_sample_create_from_pose(default_pose);
I get:
{ { 0,0,0,1,0,0,0,0,0,16 }, }
And when used in smf_sample_get_bone_orientation(default_sample,1) I get the correct orientation that I can use to create a matrix with smf_matrix_create_from_dualquat.

EDIT 2:
Created code to link models with each other:

The wheel is animated and is linked to the cars matrix. So if you move the car the wheel follow as it should. Another wheel is linked to the first wheel with a bone index so it follow the turn animation. A third wheel is linked to the second wheel bone and follow that wheel when it moves and animates.
All done with some calls:
Code:
        // Get wheel position in car space
        var position=smf_node_get_position(model_, node_index);
        // Create model in car space
        var test_model=scr_3D_model_create(position[0],position[1],position[2],"FrontWheel");
        // Set scale to invert wheel
        test_model.child_xs=-1;
        test_model.child_ys=1;
        test_model.child_zs=-1;
        // Link this model to the car space (child values is offset from car origin)
        scr_3D_model_link_model(id,test_model,noone);
        // Start animation on wheel
        scr_3D_model_animation_set(test_model,"Turn",1000);
 
        // Test link in link
        // Create model in first wheel bone space
        var test_model2=scr_3D_model_create(1,0,0,"FrontWheel");
        // Set scale to invert wheel
        test_model2.child_xs=-1;
        test_model2.child_ys=-1;
        test_model2.child_zs=1;
        // Link this wheel to the first wheel on bone 1
        scr_3D_model_link_model(test_model,test_model2,1);
        // Start animation on this wheel too
        scr_3D_model_animation_set(test_model2,"Turn",2000);
 
        // Test link in link in link
        // Create wheel in second wheel bone space
        var test_model3=scr_3D_model_create(1,0,0,"FrontWheel");
        // Set scale to invert wheel
        test_model3.child_xs=-1;
        test_model3.child_ys=-1;
        test_model3.child_zs=1;
        // Link this wheel to wheel 2
        scr_3D_model_link_model(test_model2,test_model3,1);
This could be used to run separate animations on each part of a character. Or add a shield that follow your characters hand. And you don't need to worry that the animation sample would separate ex an arm from the body. Because each child matrix is in the parent space. And if a child got children, the children is in the child-parent space, and so on.

EDIT 3:
Wish 5372:
LOD handler so we can import 2 or more models with different LOD levels and setup when each model should be used depending on the distance from the camera. This could be manually done, but it would be great if it was integrated into the engine itself.

EDIT 4:
Wish 5373:
When using normal maps its good practice to be able to set the depth value. When you are close to a model you want a low depth on the normal map so the model look more smooth but with good details. When far away you want a higher value so you can compensate the else blury surface.

EDIT 5:
In the model tool. When assign a model to a node. The node is hidden in the model so it's hard to move it. Tips is to make the model somewhat transparent or draw the node after model without depth.
 
Last edited:

TheSnidr

Heavy metal viking dentist
GMC Elder
Lotsa excellent suggestions goin there, @The-any-Key! I'd like to implement a bunch of them, but I figured I should just release this new version already, since the old one has a few errors and some problems with model comiling!

Download version 0.9.74
New in this version:
  • The "Export" tab has been removed, and the tools that were there have been simplified and moved to the bottom left of the "Edit model" tab.
  • Textures no longer have to be a power of two, and they don't need their own texture pages! So you can use textures as if they were normal sprites. This goes even for repeating textures, but then you have to tesselate the model first. The option to tesselate the model is found in the lower left of the "Edit model" tab.
  • Model compiling has been replaced with the "Combine textures" tool (lower left of Edit Model tab). This tool automatically tesselates your models, puts textures onto texture pages and combines vertex buffers that use the same material and texture page. This may allow for a decent speedup in some cases, since multiple draw calls often lead to slower processing. Keep in mind the triangle count though, which in some cases may greatly increase when the model is tesselated!
  • Triangle count is now shown in the lower right
  • Undo and redo buttons have been added! It's a fairly crude system that stores part of the loaded model whenever anything changes, and since it can result in slowdowns for large models, there's an option to turn it off. Either use the buttons in the lower right, or Ctrl+Z and Ctrl+Y.
  • Textures and nodes are now selected from a scrollable dropdown menu, making it easier to access them.
  • Various bugfixes.
  • The button for drawing bone indices is replaced with a button for drawing node names when using the level editor
EDIT:
Also, for those of you who wish it wasn't such a closed format, who wish SMF didn't handle everything for you in a closed environment - you're perfectly free to look at, modify, deconstruct and tailor the import scripts to your own liking! The-any-key is currently doing just that! Some teams have made their own parsers so that they're able to load SMF files into their own formats, and that is totally fine. The format is segmented in a pretty simple way, and the import script is commented well enough so that you should be able to deconstruct it.
Would you like to only load the vertex buffers from a model? Go ahead, seek to the position of the vertex buffers and read them. Only need to get the collision buffer? The buffer position of the collision buffer is stored in the header, so just skip everything before the collision buffer, read only that, and toss away the rest of the buffer. In fact, all the major parts of the file are indexed in the header.
SMF is not a closed system. It is made with efficieny in mind so that it can handle everything for you with as few lines of code required from your side as possible, but since so much of it is open source and free, you're free to use the format however you like.
 
Last edited:

TheSnidr

Heavy metal viking dentist
GMC Elder
I'm currently working on a particle system for SMF! It's fairly efficient, I've had six million particles from a single emitter running at 100 fps! There's still a lot of work that needs to be done, but it's getting there! Here's a cube-shaped emitter moving back and forth while emitting 60.000 particles a second:


EDIT:
Just made a circular emitter as well! :D
 
Last edited:
I'm currently working on a particle system for SMF! It's fairly efficient, I've had six million particles from a single emitter running at 100 fps! There's still a lot of work that needs to be done, but it's getting there! Here's a cube-shaped emitter moving back and forth while emitting 60.000 particles a second:
EDIT:
Just made a circular emitter as well! :D
How does it work? Are the particles completely dynamic?
 

TheSnidr

Heavy metal viking dentist
GMC Elder
The particles aren't dynamic at all, the vertex shader controls which particles are drawn and how they move throughout their lives. You can't create or move individual particles, but you can tell the shader how many particles to create per unit of time, as well as define the general rules for the particles. Pretty much like the built-in 2D particle system.
Moving emitters is a bit more difficult, and requires sending an array of logged emitter positions to the shader. Emitters that change orientation, like the circle, require a separate draw call for each step it has moved while the particles are still alive.
 
Okay, so let me see if I understand this. You've basically got the particles pooled together into a vertex buffer, right? Divided into a fixed number of different emitter "slots". Or is each emitter drawn separately? The position, colour, and opacity at any time of any particle is totally deterministic.

It sounds rather complicated in the case where emitters change velocity or change orientation.

I've been trying to think of a really efficient way of getting random particles or billboarded sprites (or for that matter, many instances of 3d models) into my games. So far the best thing I could come up with is to pool about 20 or so instances together in a single buffer, with the properties of each instance controlled by positions within a uniform array. However, we have soooo few uniform components to work with. It'd be nice if we could just throw a thousand or ten thousand instances into a buffer, and send a large array to the gpu containing all of their positions and orientations, etc... of course, it'd be even better if we ddin't have to duplicate geometry in the buffer, but could just tell the gpu to draw x copies of our instances.
 
Top