ERC Keyed support?

Issue #1442 new
Miki3d created an issue

I know keyed morphs doesn’t work on diffeomorphi now, but I wonder if there’s a plan to support them in the future, since I’m making a genitalia that uses them.

Thanks ^_^

Comments (33)

  1. Maneki

    What do you mean by “keyed”? Morph controller with keyed % work. For example the Michael 9 controller is keyed to use head and body at the same time.

  2. Maneki

    I think they are talking about something else. You can “key” one morph slider differently so it doesn’t get activated linearly. Let’s say you need a corrective morph only at 10% strength for the first 50% of the slider and going from 10% to 100% between a 50% to 100% value of the slider. Currently morphs only get activated linearly I think. I hope I was able to explain it correctly. If you still don’t understand I’ll try to visualize it.

  3. Miki3d reporter

    Yea I mean in a non linear way like Maneki said, like in the example I posted that morph has value 0 when the controller is 0, value 0.1 when the controller is 1 and 1 when the controller is 1.

  4. Alessandro Padovani

    I guess you mean the opposite: morph value is 1 when controller is from 0.1 to 1. I’m not sure this makes sense in blender, unless to import daz poses for that particular asset.

  5. Thomas Larsson repo owner

    When I return to this old issue I think I understand what it is about. Non-linear drivers are not supported in general, but in some cases they are approximated by a linear ramp, This is used in some JCMs, e.g. thigh forward.

    Let A be the lThighBend X rotation. The thigh JCMs values are

    pJCMThighFwd_57_L:
    0 if A < 0
    A/57 if 0 < A < 57 degrees
    1 if A > 57 degrees
    
    pJCMThighFwd_115_L:
    0 if A < 57 degrees
    (A-57)/58 if 57 < A < 115 degrees
    1 if A > 115 degrees
    

    The actual drivers look like this:

    pJCMThighFwd_57_L:
    clamp(M*((1 if A< -0.995 else -1.005*A if A< 0  else 0)),0,1)
    
    pJCMThighFwd_115_L:
    clamp((1 if A< -2.007 else -0.988*A-0.983 if A< -0.995  else 0),0,1)
    

    where

    A = lThighBend X rotation, in radians
    M = JCMs On
    

  6. Alessandro Padovani

    Shouldn’t the expressions be much easier ? I mean clamp already takes care of the boundaries so there’s no need to double check them.

    For example:

    pJCMThighFwd_57_L: j*clamp(x/57,0,1)
    pJCMThighFwd_115_L: j*clamp((x-57)/(115-57),0,1)
    

    where

    j = JCMs On
    x = lThighBend x rotation, in degrees
    

    Or j could mute/umnute the drivers instead of being evaluated in the expression, that would be a good optimization.

    note. I understand this may be hard to do as a general purpose conversion, but we could do it as custom conversion for known figures. I mean, if we know that G8 has boundary checks for all expressions then we can ignore them and use clamp. Same for JCMs On, we can use it as mute/unmute for known figures.

    note. Also I see you use the (fin) drivers for jcms. That’s not necessary since the daz user can’t modify jcms there’s no sliders for them, thus there’s no way for jcms to drive other jcms. You could use the jcm driver directly in the shapekey. Unless I miss something.

    update. Just tried it and seems to work fine, I only use clamp and drive directly the shapekeys without (fin). Much simpler.

    note. In general, you could use (fin) only when needed, that is, only for drivers driven by other drivers. That would simplify the setup linking most drivers directly to shapekeys, apart those having relations. Then I understand this requires to scan the drivers list first, or after if implemented as “pruning“.

  7. Thomas Larsson repo owner

    In the last commit, only the relevant part is kept and the clamp function does the clamping (in Blender 2.93 and later, clamp did not exist in drivers before). Another optimization is that JCMs On is ignored. It doesn’t make much sense, since only the 57 morph has it but not the 115 one (seems like a bug in the daz asset), and it eliminates the risk that the multiplier is accidentally set to zero which has happened in the past. Other properties that are also ignored are [BaseFlexions, body_basejointcorrectives, body_ctrl_FlexionAutoStrength, facs_ctrl_FACSDetailStrength].

    Remaining multipliers are still inside the clamp function, which may cause a problem for custom morphs.

    As for eliminating the fin drivers, it is a job for the Optimize Drivers tool. I thought that it already did so, but apparently not.

  8. Alessandro Padovani

    Ok, as for “optimize drivers“, iirc it does complex optimizations and it’s not fully reliable. So, at least for jcms, we could use drivers on the shapekeys since we know they don’t need (fin). Again unless I miss something, I know very little of drivers and rigging.

  9. Thomas Larsson repo owner

    The user can add other morphs later that may use the fin value, so it cannot be removed until we know that the last morph has been loaded, i.e. in the optimization step. In particular, the jcms for clothes are driven by the same formula as the body mesh, and we don’t want to evaluate it twice. The transfer tool drives the clothes shapekeys with the body shapekey value rather than with the fin value for this very reason.

    However, the user can still import clothes jcms as custom morphs, and then the same formula is imported again. OTOH, perhaps we can live with that, since it is a rather contrived case.

  10. Thomas Larsson repo owner

    The optimization for jcms and flexions has been introduced in the last commit. However, it can go wrong if you import custom jcms to clothes with the import custom morphs tool, so it is controlled by a global option that is disabled by default. Everything seems to be fine if we use the intended workflow (import morphs to body and transfer to clothes).

  11. Alessandro Padovani

    Commit 0315d44 seems to work fine, it’s nice to see simple drivers there.

    note. Since for transferred drivers we reference shapekey datablocks among objetcs, it would be helpful for easy of reading to name them. I mean rather than having Key, Key.002 etc. we could have G8F.Key, Shorts.Key etc. taking the names from the objects.

    note. Again I’d suggest to remove the “Mesh“ suffix from objects and add a “RIG“ suffix to armatures instead, because this way we don’t rename the DAZ figures and also have shorter names for references. Consider that a single merged blender rig includes multiple DAZ figures that are all renamed with the “Mesh“ suffix otherwise. Then this is minor.

    p.s. Also we already add “MHX“ and “RIGIFY“ suffixes when we keep the daz rig, so adding a “RIG“ suffix for the daz rig would just be consistent. We could also add a “IK“ suffix for the simple ik rig. I mean when we convert the daz rig to MHX we change the “RIG” suffix to “MHX“ etc. unless we keep the daz rig. This way the armature name also identifies the type of rig.

  12. Thomas Larsson repo owner

    Name of the shapekey block has been renamed.

    The thing you work with in animation is the rig and not the meshes, with few exceptions. It is then natural that the name of the rig is the same as the figure in DS. However, the “ Mesh” ending is now removed from submeshes when you merge rigs, since the original subrigs are gone. The main mesh retains the ending.

    How the rigs are named when you have separate control and deform rigs I don’t really care about, since I don’t use that myself.

  13. Alessandro Padovani

    Commit 4d1ee19 works fine, thank you for the fix.

    As for the original request by Miki, I understand a keyed ERC with linear interpolation could be implemented as below. But I have no idea where to find an asset to test, or how to create a keyed ERC in daz studio. May be @Maneki can provide some examples.

    As I understand it, lerp() should be available as a fast blender function for simple expressions, together with clamp(). Never used it so please check.

    https://docs.blender.org/manual/en/latest/animation/drivers/drivers_panel.html

    # daz keyed ERC with linear interpolation
    
    Key 0:v0
    Key a1:v1
    Key a2:v2
    Key a3:v3
    
    driver = v0 + (v1-v0)*A if A < a1 else
             v1 + (v2-v1)*(A-a1) if A < a2 else
             v2 + (v3-v2)*(A-a2) if A < a3
    
    driver = lerp(v0,v1,A) if A < a1 else 
             lerp(v1,v2,A-a1) if A < a2 else
             lerp(v2,v3,A-a2) if A < a3
    

  14. schema

    Importing some animations, the issue of not supporting TCB might be more severe than assumed.

    Pretty much all body morphs that target bone rotations use TCB in one way or the other. Not sure why DAZ has done it that way, since they could have easily have done it with linear functions, but DAZ is gonna DAZ I guess.

    The main problem is that this severely messes up any animation that uses body morphs as the main way of animation, especially the spine/arm/leg ones. Due to this, some animations look very neck-breaking unfortunately.

    I’ve looked through the code, and the ‘cheatSplineTCB’ does indeed only consider the lower part of a 3 point TCB morph.

    It’s somewhat fixable for my own purpose by manually adding a (if a < 0) condition to the expressions, but for an actual fix, implementing the method of Allesandro should cover all bases, though it could be simplified into two conditions with a single multiplications for most drivers using 0 as their rest point.

    e.g. for CTRLNeckHeadBend I have:

    +0.4712*a+0.326*b if a < 0 else +0.76359*a+0.326*b 
    

    which represents

    -1, -27°
    0, 0°
    0.8, 35°

    like the keys define in the original morph file.

    Above shows the same frame of the animation in DAZ and in Blender. Unfortunately, it also messes with the positions of the hands, as the spine bending is slightly different, too.

  15. Alessandro Padovani

    Yes, the (0,0) key can be simplified, if a1=v1=0 the term becomes "v2*A if A<a2". I'm not sure that the "spline TCB" is the same as "linear ERC keyed" as pointed by Miki, that sounds more like a TCB interpolation to me, so it would be approximated but better than nothing.

    Could you provide any example for "linear ERC keyed" ?

    note. Looking at the drivers docs, I see there's smoothstep() that we can probably use to better approximate TCB. The equations would become as below. I didn't try anything of this so it has to be verified. Again if a1=v1=0 the term becomes "v2*smoothstep(A) if A<a2".

    https://docs.blender.org/manual/en/latest/animation/drivers/drivers_panel.html
    https://www.youtube.com/watch?v=YJB1QnEmlTs

    # daz spline TCB
    
    Key 0:v0
    Key a1:v1
    Key a2:v2
    Key a3:v3
    
    driver = lerp(v0,v1,smoothstep(A)) if A < a1 else 
             lerp(v1,v2,smoothstep(A-a1)) if A < a2 else
             lerp(v2,v3,smoothstep(A-a2)) if A < a3
    

    note. I’m not sure how smoothstep() affects performances, though it is provided as a fast blender function for simple expressions. I mean if the drivers become too slow then we may go with the linear approximation.

  16. schema

    I agree, smoothstep would probably be most accurate, though I think TCB at 0,0,0 does slightly overshoot the target compared to smoothstep.

    I wonder if it would make sense to implement some sort of native driver system as a plugin. That way, several optimizations could be made, and also importing would be a lot easier. I’m running my own TCB interpolation on all jcm in less than a millisecond in unity. But of course, this would be quite the undertaking.

    And the downside would of course be that a custom system somewhat strays away from the functionality blender drivers already provide. However, I’m not sure drivers weren’t meant to drive bones with this complexity on this scale.

    In general, I’d say that accuracy is more important that performance for now. Maybe multiple implementation could be done, giving the option to use a slow, but accurate driver representation, or a fast approximate.

  17. schema

    I’m not 100% sure, but I think there is a small miscalculation in the formula above.

    For each section, A only interpolated between the 0 and it’s next section. However, I think it has to project to the full range of 0,1 for each section.

    As an example, pCTRLKneesUpLeft has 4 keys:

    0, 0 -> 0.2, 40 -> 0.5, 110 -> 1, 150
    

    with the formula above, the first section would look like:

    lerp(0, 40, a) if a < 0.2 else ...
    

    The problem is that this lerp will only ever go to 0.2, so evaluate to 8 at its max, before the if-branch comes in, which would instantly set the evaluation to 40.

    So I think that to correct this, 'a' has to be normalized to cover the full range for each branch:

    lerp(0, 40, a / 0.2) if a < 0.2 else
    lerp(0, 40, (a-0.2) / (0.5-0.2) if a < 0.2 else ...
    

    So the general formula for a 4 point TCB would become:

    driver = lerp(v0,v1,smoothstep(A / a1)) if A < a1 else 
             lerp(v1,v2,smoothstep((A-a1) / (a2-a1)) if A < a2 else
             lerp(v2,v3,smoothstep((A-a2) / a3)
    

    I haven’t tested this, so I’m not sure if it is correct.

  18. schema

    I’ve made a test with pCTRLKneesUpLeft using the function:

    lerp(0, 0.698, a / 0.2) if a < 0.2 else lerp(0.698, 1.91, (a-0.2) / (0.5 - 0.2)) if a < 0.5 else lerp(1.91, 2.705, (a-0.5) / 0.5)
    

    and it works pretty well. The difference is also visible:

    The left knee uses the lerp function above. The right one uses the original (+2.705*a) approximation (linear from 0° to 150°)

    Fortunately, there are much less morphs of the body morphs driven by TCB keyframes than I thought. I’ll go through them and check which ones exactly.

  19. schema

    I tried around with smoothStep a bit, but it behaves worse than a lerp as it only really smoothes the individual sections, which makes the bending more rapidly speed up and down. Maybe I used the wrong parameters though.

    Here is the expression if anyone else wants to try it:

    lerp(0, 0.698, smoothstep(0, 0.2, a / 0.2)) if a < 0.2 else lerp(0.698, 1.91, smoothstep(0, 0.3, (a-0.2) / (0.5 - 0.2))) if a < 0.5 else lerp(1.91, 2.705, smoothstep(0, 0.5, (a-0.5) / 0.5))
    

    I’ll go ahead with without smoothstep for now

  20. Alessandro Padovani

    I didn’t test my expressions in any way and never used lerp() in blender myself, it’s just an idea for Thomas to check if he wants to go that way. What you report for A sounds odd to me, it should work without normalizing, but Thomas can verify so thank you for reporting that.

  21. schema

    if you don’t normalize you aren’t at the keyframe value at the right point because you never reach the endpoint of the interpolation.

    TCB keys:

    0, 0
    0.2, 40
    0.5, 110
    1, 150

    Without normalizing:

    #Using: lerp(v0,v1,A)
    0.0 => lerp(0,40,0.0) => 0
    
    0.1 => lerp(0,40,0.1) => 4
    
    0.1999 => lerp(0,40,0.1999) => ~7.9
    
    #Using: lerp(v1,v2,A-a1) 
    0.2 => lerp(40, 110, 0) == lerp(0, 70, 0)+40 == 0 + 40 => 40
    
    0.499 => lerp(40, 110, 0.2999) == lerp(0, 70, 0.2999)+40 == ~20.99 + 40 => ~60.99
    

    … it jumps from ~8 to 40. it does the same on the next transition, too

    With normalizing:

    #using: lerp(v0,v1, A / a1)
    0.0 => lerp(0,40,0.0) => 0
    
    0.1 => lerp(0,40, 0.5) => 20
    
    0.1999 => lerp(0,40, ~0.999) => ~39.9
    
    #using: lerp(v1,v2, (A-a1) / (a2-a1))
    0.2 => lerp(40, 110, 0) => 40
    
    0.4999 => lerp(40, 110, ~0.999) => ~109.999
    

  22. Alessandro Padovani

    Uh .. yes you’re right of course, we have to normalize A for lerp(v1,v2,A) to go from v1 to v2. My mistake. Same for spline TCB.

    # daz keyed ERC with linear interpolation
    
    Key 0:v0
    Key a1:v1
    Key a2:v2
    Key a3:v3
    
    driver = lerp(v0,v1,A/a1) if A < a1 else 
             lerp(v1,v2,(A-a1)/(a2-a1)) if A < a2 else
             lerp(v2,v3,(A-a2)/(a3-a2)) if A < a3
    
    if a1=v1=0 the expression is simplified to v2*A/a2 if A < a2
    
    # daz spline TCB
    
    Key 0:v0
    Key a1:v1
    Key a2:v2
    Key a3:v3
    
    driver = lerp(v0,v1,smoothstep(A/a1)) if A < a1 else 
             lerp(v1,v2,smoothstep((A-a1)/(a2-a1))) if A < a2 else
             lerp(v2,v3,smoothstep((A-a2)/(a3-a2))) if A < a3
    
    if a1=v1=0 the expression is simplified to v2*smoothstep(A/a2) if A < a2
    

  23. Thomas Larsson repo owner

    Unfortunately, fixing this is not straightforward, since there are assumptions built into the code that has to be changed. I will have a look, but dont know how long it will take.

  24. Alessandro Padovani

    Well, body morphs are not compatible with ik anyway, so makes little sense to import them in blender. I mean if this is too complex to fix and risks to break anything it may be not worth it. We can always bake the figure in daz studio before exporting if we need to import a pose or animation with body morphs, there’s edit > figure > bake to transforms.

    # bake body morphs to bones
    
    edit > figure > bake to transforms
    

  25. Thomas Larsson repo owner

    No, I’m pretty sure that it can be fixed, at least so the spline is replaced by a collection of linear ramps. That is already implemented for jcms but not for body morphs. But to do it for body morphs the code has to redesigned, which will take some time.

  26. schema

    Thanks for the tip with baking morphs to transforms, Alessandro. This would have been exactly what I needed to make my life easier.

    Unfortunately, as with most features in DAZ, it doesn’t really work as it should. I tried it on a few animations that use body morphs, and it’s just randomly putting wrong bone rotations on some limbs making the animations not match with the original.

    But I’ve now manually replaced the expressions. Luckily, only like 3-4 of the body morphs really need it (shins and spine/neck. I didn’t check the fingers and toes), and I only need it once in A-Pose to be able to load any pose preset.

  27. Alessandro Padovani

    I’m not using bake to transforms personally but last time I tried it worked quite fine, may be I got lucky. Be aware that daz studio has this “types“ menu in the timeline where you have to specify what kind of keyframes you want to record. I suspect bake to transforms needs at least TRSOA with “Node Recurse“ as Keys set.

  28. schema

    Ah yes, thanks, it set to all types for me, but I keep forgetting that those can reset on load even if the UI shows them set (sometimes, switching them off and on make keyframes magically appear). I’ll try it again.

  29. Log in to comment