The project has involved a few key challenges:
- Understanding the Moho tool and its file structure.
- Creating a custom dump format (via Lua) to export only the critical data I need.
- Building the shape rendering object in Rust.
Progress: Reverse-Engineering Binding Formulas
By analyzing FBX exports, I've been able to determine the formulas Moho uses for its different bone binding methods. I'm sharing them here in case they're useful to others:
Flexi-Binding
This method computes the raw vertex weight (W) based on its distance (d) from a bone.
Code: Select all
W = K / (1 + (d / c)^4)
Where:
K = bone.length
c = 0.177828 * bone.strength
This appears to use the exact same formula as flexi-binding, but only ever considers the weights from the two nearest bones. This was a surprise to me since smooth joint binding is often discussed as a completely different mechanism, whereas if my findings are correct it's just a ui accelerator for flexibind to subset of 2 bones.
Flexi-Binding (Subset)
As expected, this uses the same formula but only considers bones included in the specified subset.
Region Binding
This uses a completely different formula, which appears to be a "Smoothstep" function.
Code: Select all
W = 1 - t^2 * (3 - 2t)
Where:
t = distance / maxD
maxD = 0.5 * bone.strength * bone.length
And, the weight is clamped to 1 if t is 0, and 0 if greater than 1.
This investigation has led to a few specific problems where I'm hoping someone might have some insight.
FBX Point Coordinate Discrepancy
While all the bone transforms in the FBX export match Moho's values perfectly, the coordinates for shape vertices do not. After accounting for the 100:1 scaling the FBX exporter applies, the ranges are still different. For example, points in a test scene range from -84 to 70 on the x-axis in Moho, but the same points range from -112 to 97 in the FBX file. This is a problem because my weight calculations depend on these vertex positions. Has anyone encountered this coordinate scaling/offset issue with FBX exports before? Maybe the point positions I'm exporting in my dump format are missing another transform, but there is no layer transform on the vector or bone layer (my test scene is simply a bone layer and a child vector layer).
LayerParentBone() Function Behavior
The Lua scripting documentation states that the Layer:LayerParentBone() function should return -2 for flexi-binding or -3 for flexi-binding to a subset. However, in practice, I'm only ever seeing it return -1 (no binding) or a specific bone index. I can work around this by checking IsIncludedInFlexiBoneSubset for each bone, but I'm concerned the function isn't behaving as documented. Is this a known issue or perhaps a legacy feature that has changed in recent Moho versions? Looking at other scripts it seems like this behavior is a known thing because several scripts check IsIncludedInFlexiBoneSubset for all the bones for any negative value of LayerParentBone().
Distinguishing Region vs. Flexi-Binding
I have not found a way through the scripting API to determine if a layer is using region binding or flexi-binding. Shape points return -2 (flexi-binding) when the bone layer is set to region binding (and as mentioned the Layer.LayerParentBone returns -1 whether region binding or flexi-binding. This distinction is critical, as they use different weighting formulas. My current workaround idea is to require an artist to annotate region-bound layers (e.g., by suffixing the layer name with _region), but I'd love to find a more direct method. Is there an API call I'm missing to reliably detect region binding?
Addendum
The code for this project is not currently publicly available. But my intention is to release it all in a public github repository, including the shape renderer for Vello, once it is on firm ground. If anyone is interested in the lua dump script I'm happy to put a copy of it into a gist somewhere (it does not include the weight calculation -- that occurs in the conversion to my shape format).
Some features of moho won't be supported due to not being a good fit for a run-time engine. E.g., the outline mode for a layer requires a boolean operation (union of all the shapes) which is fairly cpu intensive. So although it could be implemented, it's best to stay away from. Some things like warp layers will also not be implemented, at least right away, because it is one more proprietary calculation that would have to be reverse engineerred.
Smart bones? Hell yeah they will be implemented. Very important feature and not hard.
IK? Dynamics? Yes and no. I'm not going to duplicate that functionality in the shape class. Instead the expectation is that they will be baked into the animation. This does mean that my shape format is not exporting the explicit keyframes from moho but instead exporting frame by frame animation.
Particle layers? Not initially, but probably eventually.
Image layers? This would be possible, but I just have no interest. So unless something changes, probably not.
Probably should have included this earlier, but what is Rust? Vello?
Rust is a programming language. Vello (https://github.com/linebender/vello) is a 2d vector graphics library written in Rust which renders high quality vector graphics on the gpu.