powroupi / blender_mmd_tools

mmd_tools is a blender addon for importing Models and Motions of MikuMikuDance.
GNU General Public License v3.0
1.82k stars 277 forks source link

A better way to approximate SDEF interpolation using blender armatures #162

Open Takuyax opened 6 years ago

Takuyax commented 6 years ago

Hi! This issue is mainly intended as a discussion that might lead to some enhancements in mmd tools, or to myself understanding how to do this manually.

My main usage scenario is to use MMD tools as an importer to render models directly in Blender. Models get imported, converted to quads, and I do a bunch of blender-specific manual adjustments (separate mesh objects, creases, lattice deformed spherical eyes, etc.)

To get nice pointy elbows and knees, I manually add a second armature modifier, set it to “Preserve Volume” (which activates quaternion interpolation) and “Multi Modifier”, then add a vertex group “volume”, which controls the influence of the quaternion armature modifier. I then pose the model and weight paint the “volume” vertex group until I get a nice shape.

This manual approach works great, however every time I see that a model has SDEF shape keys, I wonder if it wouldn’t be possible to convert this to something that blender could use, even if that would be a simplified approximation.

For starters, is there any good documentation about how the c, r0 and r1 coordinates work? I suppose c stands for center and r for radius, so it would seem like it’s possible to calculate some basic per-vertex volume data from these. Why are are two radiuses though?

nathanvasil commented 6 years ago

I think this might be a fruitful discussion, although I'm afraid I don't have a lot to offer except gripes. As somebody who's more of an exporter, I'm really grateful to powroupi for implementing storage of SDEF, but on my most recent model, I find that this is one of the issues where I still run it through a PMXE pass to enable SDEF, because I want to preserve quads, Blender materials. sharp edges, edge crease in my main .blend, and because Blender's handling of shapekeyed meshes is so painful. (The other issue where I need to run it through PMXE is material order, but that may be a function just of my workflow; and of course Blender mat view is nothing like MMD's, but I don't think that a fix for that is particularly viable, it's still faster to run through PMXE than to emulate MMD rendering in Cycles.)

However, I'm still surprised that just enabling "preserve volume" on your main armature modifier isn't good enough. My understanding of preserve volume is that it's most similar to MMD's quaternion deformation, functional only in MMM. Sometimes SDEF is just what the doctor ordered, especially for knobby elbow bones....

There have been some SDEF papers, and I believe powroupi linked me one some time earlier, but I didn't really understand it. I imagine that implementing SDEF in Blender would be possible, but probably not the best use of one's limited time on Earth.

powroupi commented 6 years ago

SDEF data was discussed in https://github.com/powroupi/blender_mmd_tools/issues/23#issuecomment-253217520, the reference provided by @nagadomi is broken, fortunately translation is there, and it's easy to search for SDEF papers. :)

nagadomi commented 6 years ago

I was thinking about solving this issue after the previous PR. But I haven't even started it yet. The reference about SDEF by mqdl can be found in Internet Archive. https://web.archive.org/web/20161015160142/http://d.hatena.ne.jp/mqdl/20080525/1211706244

Edit: I found a SDEF code. I have not tried it yet. https://jbbs.shitaraba.net/bbs/read.cgi/music/23040/1285499541/472-474

var bone0 = bones[(int)v.BlendIndices[0]];
var bone1 = bones[(int)v.BlendIndices[1]];
var center = v.SdefC; // Vector3
var weight0 = v.BlendWeight[0]; // float
var weight1 = 1 - weight0; // float
var mat = Matrix.RotationQuaternion(Quaternion.Lerp(Quaternion.Identity, bone1.LocalRotation, weight1) * Quaternion.RotationMatrix(bone0.AbsoluteTransform)); // Matrix
var pos = v.Position; // Vector3

pos = Vector3.TransformCoordinate(pos - center, mat)
+ (center
+ (Vector3.TransformCoordinate(center, bone0.AbsoluteTransform) - center + Vector3.TransformCoordinate(v.SdefR0, bone0.AbsoluteTransform)) * weight0
+ (Vector3.TransformCoordinate(center, bone1.AbsoluteTransform) - center + Vector3.TransformCoordinate(v.SdefR1, bone1.AbsoluteTransform)) * weight1) * 0.5f;
v.Normal = Vector3.TransformNormal(v.Normal, mat);
v.Position = pos;

// WTFPL
Takuyax commented 6 years ago

Thanks for all the replies!

@nathanvasil Last time I exported a model, material/bone order seemed to export correctly. Though I have to admit, I don't like exporting because it always ends with me swearing way too much. Just enabling "preserve volume" can be enough, if a model was specifically weighted for quaternion interpolation. The only model that I have where that applies is one that I made from scratch. For pmx models, the usual behavior is that knees and elbows get too much volume and turn into spheres. So I need to either remake their weights, or use the method with 2 armature modifiers and "volume" vertex group, which is easier and preserves the original weights for eventual re-export. However even then it's difficult to match the original SDEF deformation. Here is my original model, compared to iei's Kagura Aya: bone_demo

@powroupi @nagadomi Thanks for the references! I will have a look at them and try to understand them.

Hogarth-MMD commented 6 years ago

So why are there two radiuses?

Takuyax commented 6 years ago

@Hogarth-MMD According to the code nagadomi posted, there is one radius per bone. Center is transformed by both bones, r0 is only transformed by bone0 and r1 is only transformed by bone1. Then a bunch of arithmetic happens to get the final vertex position. I haven't come up with a good way to do this in Blender yet. Re-making this in python would probably be slow. It might be possible to use the animation nodes add-on for this, since it allows vertex-level mesh manipulation and can do fast matrix transforms. The only problem I'm aware of is that it can't output meshes with UV maps yet in version 2.0. The in-development version 2.1 has that feature but was extremely buggy when I tried it some months ago: https://github.com/JacquesLucke/animation_nodes/tree/v2.1 The developers already teased a new animation nodes successor for Blender 2.8 though, so it might be worth waiting for that.

Hogarth-MMD commented 6 years ago

If I remember correctly, in bmesh, vertices can have custom properties. SDEF drivers on vertex custom properties? The animation nodes add-on uses bpy.app.handlers. I am not really sure how necessary SDEF is. Probably a lot of work would be needed to implement it in Blender.

Hogarth-MMD commented 6 years ago

Can SDEF be created on vertices in PMX editor? Or how is that done?

nathanvasil commented 6 years ago

Can SDEF be created on vertices in PMX editor? Or how is that done?

Yup, that's how it's done. You select one or more vertices and change their weight mode to SDEF (from edit/weights./SDEF.) PMXE figures out the centers from the bone positions. Editing centers can be done on a vert-by-vert basis but it's easier to move the bones and recalculate centers.

Hogarth-MMD commented 6 years ago

Is there a test PMX model which shows how SDEF works in the simplest, clearest way, with the minimum number of vertices? This would be helpful to understand and implement SDEF in Blender.

Takuyax commented 6 years ago

@Hogarth-MMD Regarding the necessity, it would be a feature that adds consistency to how a model is rendered. It's easy to achieve the same by manually changing weights for quaternion interpolation, but when a model is well weighted for SDEF, it's a shame not being able to use it. Another task where it would be helpful is to actually create SDEF weights in Blender and get an accurate preview that matches MMD.

Regarding models with SDEF, Kakomiki's Aria comes to mind. I know that the AB8 161007_2 version has good SDEF weights, despite using extreme radius settings that I cannot understand. The AB8b remake probably uses SDEF too, but I'm not familiar with that model. Both versions are available here: https://onedrive.live.com/?authkey=%21AGzT%5F4WgGZYwEF4&id=BFE73CA818CE632F%21138&cid=BFE73CA818CE632F

nathanvasil commented 6 years ago

@Hogarth-MMD Here, http://www.mediafire.com/file/7z1qm63qb46o8z8/sdef.pmx/file , three cylinders, one at SDEF, one at BDEF, one at SDEF with altered centers. Hope it's the right balance of simple but not too simple.

Hogarth-MMD commented 6 years ago

If I understand correctly from this diagram:

P is a vertex. B is the center point of the overlapped area of 2 bones, or the center point of the overlapped area of 2 vertex groups of 2 bones. C is the rotation center for P, for rotation around the bone's twist axis, and the line between C and P is a vector perpendicular to the bone(s). R0 is a point located opposite from B with C as its center of reflection. R1 is a point located opposite from R0 with B as its center of reflection. 20080525175907

Hogarth-MMD commented 6 years ago

But a pair of bones would not normally be aligned with each other along the same straight line, like you see in this diagram.

Hogarth-MMD commented 6 years ago

I tried to extract the SDEF data from the 3 SDEF shape keys, using the test model of @nathanvasil . In PMX editor, when a vertex has no SDEF, its SDEF values are (0,0,0), but in Blender I did not see any (0,0,0) values in the SDEF shape keys.

Hogarth-MMD commented 6 years ago
import bpy

mmd_sdef_c = []
mmd_sdef_r0 = []
mmd_sdef_r1 = []

shape_keys_names = bpy.context.active_object.data.shape_keys.key_blocks.keys()

if "mmd_sdef_c" in shape_keys_names:
    for v in bpy.context.active_object.data.shape_keys.key_blocks["mmd_sdef_c"].data:
        mmd_sdef_c.append(v.co[:])

if "mmd_sdef_r0" in shape_keys_names:
    for v in bpy.context.active_object.data.shape_keys.key_blocks["mmd_sdef_r0"].data:
        mmd_sdef_r0.append(v.co[:])

if "mmd_sdef_r1" in shape_keys_names:
    for v in bpy.context.active_object.data.shape_keys.key_blocks["mmd_sdef_r1"].data:
        mmd_sdef_r1.append(v.co[:])

print("\n\n")
print("C SDEF data", len(mmd_sdef_c), "vertices")
print("\n")
print(mmd_sdef_c)
print("\n\n")
print("R0 SDEF data", len(mmd_sdef_r0), "vertices")
print("\n")
print(mmd_sdef_r0)
print("\n\n")
print("R1 SDEF data", len(mmd_sdef_r1), "vertices")
print("\n")
print(mmd_sdef_r1)
Hogarth-MMD commented 6 years ago

Above is my python code to extract the SDEF data from the 3 SDEF shape keys, then store this data in 3 python lists and print this data to the Blender system console. Apart from that, I basically have no $%^$%^ idea how to implement SDEF in Blender.

powroupi commented 6 years ago

If you want to use SDEF data, here is a sample code:

import bpy

obj = bpy.context.active_object # select the mesh object of a MMD model before running this script
pose_bones = obj.parent.pose.bones # its parent should be the armature object

key_blocks = obj.data.shape_keys.key_blocks
sdefC = key_blocks.get('mmd_sdef_c')
sdefR0 = key_blocks.get('mmd_sdef_r0')
sdefR1 = key_blocks.get('mmd_sdef_r1')

for v, c, r0, r1 in zip(obj.data.vertices, sdefC.data, sdefR0.data, sdefR1.data):
    if v.co != c.co:
        sdef_c = c.co
        sdef_r0 = r0.co
        sdef_r1 = r1.co
        # now you get sdef data (sdef_c, sdef_r0, sdef_r1) and original vertex (v.co)

Reference: pmx.importer.py#L155-L158 pmx.importer.py#L175-L191

Hogarth-MMD commented 6 years ago

So

 if v.co == c.co:

then the vertex is not an SDEF vertex. Right?

Hogarth-MMD commented 6 years ago

I can't do anything with the code quoted by @nagadomi . I would either need someone to translate it into Blender python, or explain it very clearly in plain English (maybe with images).

nathanvasil commented 6 years ago

But a pair of bones would not normally be aligned with each other along the same straight line, like you see in this diagram.

I think you might have an easier time if you change the way that you think about bones.

A bone is a center of rotation and scaling, and a center is a point, not a line. (Translation doesn't need a center.) The tail doesn't matter-- Blender uses it to establish the local Y axis, but that's arbitrary, and any set of orthogonal, non-zero local basis vectors could be used to determine the same global space transformation just as easily. And of course Blender can use the tail as a pocket of extra information for use with constraints. But those aren't essential qualities for a bone, and they don't have anything to do with the global space, post-constraint, vertex-deforming transformation of the bone. Tails are as unimportant as a bone's name for that.

So a bone isn't really a line, it's a point. Because of that, every pair of bones can be seen as 'aligned', since there exists a straight line between any two points.

Just offering in case that's useful to you.

nagadomi commented 6 years ago

https://gist.github.com/nagadomi/aa39745ae6716b50c2a60288b093d14b This is SDEF test addon under construction. I have succeeded in creating user-defined skinning driver, but I have not implemented SDEF yet. This works with Linear Blend Skinning. vertex driver does not work if the mesh has shapekey. So I implemented it with dynamic shapekey data. It seems that python code is not too slow. I would be grateful if someone implement SDEF.

Hogarth-MMD commented 6 years ago

To start with, I suggest forget about R0 and R1 temporarily. Just get the interpolation of vertex locations along geodesics relative to point C with mathutils slerp (spherical linear interpolation of vectors). That aspect should be just simple Vector mathematics.

Hogarth-MMD commented 6 years ago

From the small amount of available information about SDEF, it seems to me that the location of point C of a vertex is being influenced by the conflicting influences of 2 bones. As these 2 bones rotate or move, how does this change the location of point C?

nagadomi commented 6 years ago

I've implemented SDEF on blender python. You can try it with the following addon. https://gist.github.com/nagadomi/aa39745ae6716b50c2a60288b093d14b This implementation will work fine for simple model, but it is very slow on AB8 :(

In the code I posted above, the preprocessing of R0 and R1 is missing. It is necessary for models with extreme R0/R1 settings like AB8. SDEF in blender python is as follows.

# preprocessing that was missing
rc = r0 * weight0 + r1 * weight1
r0 = c + r0 - rc
r1 = c + r1 - rc
# SDEF
mat0 = bone0.matrix * bone0.bone.matrix_local.inverted()
rot0 = mat0.to_quaternion().normalized()
mat1 = bone1.matrix * bone1.bone.matrix_local.inverted()
rot1 = mat1.to_quaternion().normalized()
c_b0 = mat0 * c
c_b1 = mat1 * c
r0 = mat0 * r0
r1 = mat1 * r1
b = r0 * weight0 + r1 * weight1
loc = ((c + (c_b0 - c ) * weight0 + (c_b1 - c) * weight1) + b) * 0.5
mat = rot0.slerp(rot1, weight1).to_matrix().to_4x4()
pos = (mat * (vertex_co - c)) + loc

Edit: I think that this code is able to speed up with numpy and bone pair grouping. it seems that recent blender python contains numpy.

powroupi commented 6 years ago

@nagadomi Here is a simplified version: (assuming w0 + w1 == 1) :smile:

def mmd_sdef_driver_function(shapekey, obj_name):
    ...
    def calc_skin_mat(pose_bone):
        if pose_bone.name not in memo:
            mat = pose_bone.matrix * pose_bone.bone.matrix_local.inverted()
            v = (mat, mat.to_3x3())
            memo[pose_bone.name] = v
            return v
        else:
            return memo[pose_bone.name]
    for i, w0, w1, bone0, bone1, pos_c, cr0, cr1 in g_mmd_sdef_verts[obj.name]:
        mat0, rot0 = calc_skin_mat(bone0)
        mat1, rot1 = calc_skin_mat(bone1)
        shapekey.data[i].co = rot0.lerp(rot1, w1)*pos_c + mat0*cr0*w0 + mat1*cr1*w1

def mmd_sdef_find_vertices(obj):
    ... ... ... ...
                    vertices.append((
                        i, w0, w1, bones[0]["pose_bone"], bones[1]["pose_bone"],
                        vd[i].co-c, (c+r0)/2, (c+r1)/2))
nagadomi commented 6 years ago

https://gist.github.com/nagadomi/aa39745ae6716b50c2a60288b093d14b I merged powroupi's simplified formula and improved shapekey data update performance, added a skip condition when the bones are not moving. This is my best effort. I tried to loop unroll with numpy, but mathutils.Matrix.lerp(interp_m3_m3m3) is complicated and it is a bottleneck, so I gave up it.

Hogarth-MMD commented 6 years ago

Congratulations @nagadomi !! I think that the SDEF algorithm could be used to create joint-controlled morphs. DAZ\Poser models use joint-controlled morphs for the deformation of joints of DAZ\Poser models. Instead of a dynamically updated shape key, there would be permanent shape keys on the model which have shape key drivers, the shape keys being driven by bone rotations. Joint-controlled morphs would not have any issues with slow speed. I don't really know if this would be better or worse than your current method. I'm just throwing it out as an idea.

Hogarth-MMD commented 6 years ago

In Blender terminology, a "joint-controlled morph" is called a "corrective shape key".

nagadomi commented 6 years ago

@Hogarth-MMD The purpose of SDEF implementation is compatibility with MMD. If we simply want a beautiful deformation, we can easily do it with shapekey driver, stretching bone, blending of linear blend skinning(default armature) and dual quaternion skinning(preserve volume) or corrective smooth modifier. In SDEF, each vertex can have a different C, R0, R1, and two bones rotates arbitrarily. A single shapekey can only deform on a straight line. So I think it is impossible to implement SDEF with permanent shapekey driver.

nagadomi commented 6 years ago

I think that a good approximation method is to calculate the weight for two armature modifiers(linear and quaternion interpolation) that is the most similar deformation to SDEF, mentioned by @takuyax. Now that SDEF is revealed, its weight can be optimized.

powroupi commented 6 years ago

Comparing with np version, non-np version run faster on my PC. :confused:

def mmd_sdef_driver_function(shapekey, obj_name):
    obj = bpy.data.objects[obj_name]
    if mmd_sdef_check_binding(obj):
        pass
    import time
    st = time.time()
    #mmd_sdef_driver_function_0(shapekey, obj_name)
    mmd_sdef_driver_function_np(shapekey, obj_name)
    print(time.time()-st)
    return 1.0

def mmd_sdef_driver_function_0(shapekey, obj_name):
    shapekey_data = shapekey.data
    for bone0, bone1, vid, w0, w1, pos_c, cr0, cr1 in g_mmd_sdef_verts[obj_name].values():
        mat0 = bone0.matrix * bone0.bone.matrix_local.inverted()
        mat1 = bone1.matrix * bone1.bone.matrix_local.inverted()
        rot0 = mat0.to_3x3()
        rot1 = mat1.to_3x3()
        for vi, w0i, w1i, posi, cr0i, cr1i in zip(vid, w0, w1, pos_c, cr0, cr1):
            shapekey_data[vi].co = rot0.lerp(rot1, w1i) * posi + mat0 * cr0i * w0i + mat1 * cr1i * w1i

def mmd_sdef_driver_function_np(shapekey, obj_name):
    shapekey_co = np.zeros(len(shapekey.data) * 3, dtype=np.float32)
    shapekey.data.foreach_get("co", shapekey_co)
    shapekey_co = shapekey_co.reshape(len(shapekey.data), 3)
    for bone0, bone1, vid, w0, w1, pos_c, cr0, cr1 in g_mmd_sdef_verts[obj_name].values():
        mat0 = bone0.matrix * bone0.bone.matrix_local.inverted()
        mat1 = bone1.matrix * bone1.bone.matrix_local.inverted()
        rot0 = mat0.to_3x3()
        rot1 = mat1.to_3x3()
        shapekey_co[vid] = [rot0.lerp(rot1, w1[i]) * pos_c[i] + mat0 * cr0[i] * w0[i] + mat1 * cr1[i] * w1[i] for i in range(len(vid))]
    shapekey.data.foreach_set("co", shapekey_co.reshape(3 * len(shapekey.data)))

Minor changes for non-np version 2:

def mmd_sdef_driver_function_v2(shapekey, obj_name):
    shapekey_data = shapekey.data
    for bone0, bone1, sdef_data in g_mmd_sdef_verts[obj_name].values():
        mat0 = bone0.matrix * bone0.bone.matrix_local.inverted()
        mat1 = bone1.matrix * bone1.bone.matrix_local.inverted()
        rot0 = mat0.to_3x3()
        rot1 = mat1.to_3x3()
        for vid, w0, w1, pos_c, cr0, cr1 in sdef_data:
            shapekey_data[vid].co = rot0.lerp(rot1, w1) * pos_c + mat0 * cr0 * w0 + mat1 * cr1 * w1

def mmd_sdef_find_vertices(obj):
    ... ... ... ...
                    if key not in vertices:
                        vertices[key] = (bones[0]["pose_bone"], bones[1]["pose_bone"], [])
                    vertices[key][2].append((i, w0, w1, vd[i].co-c, (c+r0)/2, (c+r1)/2))

def mmd_sdef_bind(obj):
    ...
        mask = tuple(i[0] for v in g_mmd_sdef_verts[obj.name].values() for i in v[2])
Hogarth-MMD commented 6 years ago

Please forgive me if this is a dumb question, but what are np and non-np?

powroupi commented 6 years ago

what are np and non-np?

@Hogarth-MMD speed up with and without numpy. Function mmd_sdef_driver_function_np uses numpy, function mmd_sdef_driver_function_0 doesn't. My test result is that mmd_sdef_driver_function_0 has better performance on my PC. :smiley:

nagadomi commented 6 years ago

@powroupi The difference is whether to set all shapekey.data at once by bpy_prop_collection.foreach_get / foreach_set, and set only updated values with each attr. In my PC, np version is faster than non-np version (np: 2.6FPS, non-np: 1.7FPS, on AB8(ab8_161007_2)). One reason I can think of is that I am using numpy optimized with OpenBLAS, but probably it does not matter because the code only use memory copy function. foreach_set sets all data without updates, the memory copy speed may be related. :thinking: I am not sure. and my PC is Ubuntu 18.04(Linux).

It is possible to switch functions by benchmark results. :cold_sweat:

nagadomi commented 6 years ago

Oh, perhaps the result will change depending on the percentage of SDEF Vertex/All Vertex.

nagadomi commented 6 years ago

I added the function to select the fastest strategy by benchmark. https://gist.github.com/nagadomi/aa39745ae6716b50c2a60288b093d14b On my PC, probably the bulk update version (np version) is faster on any model.

mmd_sdef benchmark: default 4.2916 vs bulk_update 2.0821 => use `mmd_sdef_driver_bulk`
mmd_sdef benchmark: default 0.1684 vs bulk_update 0.1009 => use `mmd_sdef_driver_bulk`
mmd_sdef benchmark: default 0.0171 vs bulk_update 0.0073 => use `mmd_sdef_driver_bulk`
Hogarth-MMD commented 6 years ago

mmd_sdef benchmark: default 2.6922 vs bulk_update 3.1725 => use mmd_sdef_driver

On my Windows 10 PC, the bulk update version (np version) is slower.

Hogarth-MMD commented 6 years ago

An imported MMD model may have SDEF errors. Previously mmd_tools was only concerned about the import and export of SDEF data, without making any corrections. If an imported model has incorrect or incomplete C or R0 or R1 values, will these now be automatically re-calculated and corrected by the add-on of @nagadomi ?

powroupi commented 6 years ago

Comparing with MMD, I think this version is more correct. :smiley: (simplified version has slightly different result)

    for bone0, bone1, sdef_data, vids in g_mmd_sdef_verts[obj.name].values():
        mat0 = bone0.matrix * bone0.bone.matrix_local.inverted()
        mat1 = bone1.matrix * bone1.bone.matrix_local.inverted()
        rot0 = mat0.to_quaternion()
        rot1 = mat1.to_quaternion()
        if rot1.dot(rot0) < 0:
            rot1 = -rot1
        # s0, s1 = mat0.to_scale(), mat1.to_scale() # for scaling
        for vid, w0, w1, pos_c, cr0, cr1 in sdef_data:
            mat_rot = (rot0*w0 + rot1*w1).normalized().to_matrix()
            # s = s0*w0 + s1*w1
            # mat_rot *= Matrix([[s[0],0,0], [0,s[1],0], [0,0,s[2]]])
            shapekey_data[vid].co = mat_rot * pos_c + mat0 * cr0 * w0 + mat1 * cr1 * w1

Sample file: sdef_test.pmx.zip

nagadomi commented 6 years ago

@powroupi I merged your change and refactored the code. https://gist.github.com/nagadomi/aa39745ae6716b50c2a60288b093d14b How do you think about merging this feature into mmd_tools? If there is no problem, I will send Pull Request. Or if you want to make any changes, please feel free to merge/use this code.

Takuyax commented 6 years ago

Thank you all for the amazing work! Sadly I couldn't contribute, since this is above my current level of python skills. This script does exactly what I had in mind. The results look correct and the way you implemented it, using shape keys and vertex groups, looks nice and clean. Seems fully non-destructive and easily reversible.

Using Aria as a benchmark, I think the speed is good enough when rendering, although I would disable SDEF for posing. One suggestion would be, to have a toggle to disable SDEF without removing the binding. (E.g. only unset the armature modifier's vertex group and pause SDEF updating.) That way you could get higher framerates for posing/animating and easily switch back to SDEF for rendering, without re-binding.

Or the parts that do the heavy math could be compiled using cython. That would require separate compiled versions for Linux/Windows/MacOS, so it should probably be an optional install that can replace the uncompiled SDEF python file for people who want more performance. Would this be feasible?

powroupi commented 6 years ago

How do you think about merging this feature into mmd_tools? If there is no problem, I will send Pull Request.

Just minor issues for future improvement, currently your code is no problem, you can sent PR to me :smile:

  1. a way for mute/unmute SDEF (@Takuyax's suggestion)
  2. option for disabling benchmark, like {default, bulk, auto}, auto for benchmarking
  3. f.driver.use_self = True is only supported for Blender 2.78+.
  4. shape key support (the following code is working but not optimized):
    key_blocks = tuple(k for k in obj.data.shape_keys.key_blocks[1:] if not k.mute and k.name != "mmd_sdef_skinning")
    for bone0, bone1,...
        for vid, w0, w1,...
            offset = sum((k.value*(k.data[vid].co-k.relative_key.data[vid].co) for k in key_blocks), Vector())
            shapekey_data[vid].co = rot*(pos_c+offset) + mat0*cr0*w0 + mat1*cr1*w1 - offset
  5. cpython (@Takuyax's suggestion, I have no idea how to use it :cry: )
powroupi commented 6 years ago

Okay, I've updated SDEF feature, now we can bind/unbind selected (mesh) objects (select MMD model root object to bind/unbind all meshes of the MMD model), we can also mute/unmute SDEF shape key or toggle [SDEF] in [MMD Display] panel. :smile:

Hogarth-MMD commented 6 years ago

Congratulations to @nagadomi and @powroupi for this achievement. What about adding SDEF to a model which does not have SDEF? Does @nagadomi have enough information to implement that feature? Will this feature be added later?

nagadomi commented 6 years ago

I am not familiar with weight painting of SDEF. It seems to be a different paradigm than general weight painting. It seems to setup boxes called anchor and calculate weights, C, R0, R1 from those overlaps. ref: http://ch.nicovideo.jp/dede/blomaga/ar727832 (japanese) The PMX model loses the anchor info, so it needs to be restored from bone pos/length and SDEF parameters, but probably it can not be completely restored if it was fine tuned manually.

Hogarth-MMD commented 6 years ago

According to information in a tutorial, there is a Metasequoia plug-in called Keynote, which is used to add SDEF to PMX models. Are Metasequoia plug-ins programmed in Python? If Metasequoia plug-ins use Python, then, translating code from Keynote to Blender Python should work.

nathanvasil commented 6 years ago

@nagadomi I read the linked blog entry via Google translate. My understanding of MMD's anchor setting is that it is used for altering weights, not SDEF C/R0/R1 values. It's used for editing of BDEF as well. I've never used this functionality because it's not very intuitive, and because I can use Blender to achieve the same sorts of things that PMXE's anchors are used for-- things like locking vertex groups and renormalizing other weights around them. I don't believe the author is using anchors to change SDEF centers, but to change weights to make the model work with the SDEF centers calculated by PMXE.

Google translate gives me, "If this remains the default value (during simple SDEF conversion) The inside is dented and the outside bulges." which isn't quite accurate. Sometimes, default values cause undesirable deformation; usually, they do not. PMXE's transformation from BDEF->SDEF calculates SDEF values automatically on the basis of the locations of the bones involved. When it's not, I move my bones and do it over again. After calculating SDEF, I can move my bones wherever I want them to go without changing the SDEF values. Part of that may be the fact that I use SDEF judiciously, on only parts of my models, and make sure that I have BDEF1 loops at every SDEF/BDEF border.

As a recent example of how PMXE's automatically calculated centers can go wrong, I recently had a model with a (near) T-pose. Humerus twist bone was coincident with the humerus bone (only the axis matters for transformation of an axis-limited bone like this, distance along axis is unimportant.) Automatically calculated SDEF values for the arm twist bones were beautiful. But upon transformation to an A pose (and SDEF recalculation) for better compatibility with existing motions, arm twist bones deformed very poorly. I moved my humerus twist bones to a more standard position, closer to the elbow along the same axis, recalculated SDEF, and the problem went away. (I could have moved them back after calculation, but again, as twist bones, their position along the axis doesn't actually matter.)

Takuyax commented 6 years ago

@nathanvasil Actually the overlap of the anchor shapes changes the volume of the deformation a bit. I don't know which of the values it changes though. I sometimes used it to fix knees. It seems that overlapping two box anchors for thigh+shin, turning the backside really flat and the front really tall, so it looks like a pizza slice from the side, with the shin bone handle (circle) in the narrow end and the tall end fully enveloping the knee, gives pointier knees than the default BDEF->SDEF conversion. So there is definitely something that works different when using this method. I'm not sure if that's easy to follow. I can post a screenshot when I have the windows PC running again.

nathanvasil commented 6 years ago

I would be interested in learning more about PMXE anchors, @Takuyax, and I would appreciate anything you have to share on the subject.