ValueOperation Codes -What exactly is the maths?



  • @3dcheapskate everything except valueOpKey and PYTHONCALLBACK are well defined in terms of interpolation. DELTAADD, MINUS and PLUS are linearly interpolated.

    TIMES, DIVIDEBY and DIVIDEINTO are simply multiplication by a factor, division by a factor, and division of a factor by the parameter value. Divisions can, of course have undefined asymptotes (Can't divide by zero).

    KEY has limitations in its interpolation. With only two key, value pairs defined, it is linearly interpolated, with the output value locked to which ever endpoint value is closest if the input is outside the specified key range.

    At 3 key value pairs, KEY is probably a quadratic interpolation. Beyond that, the interpolation is unspecified and undocumented. I once tried using the NumPy package to interpolate splines based on the key, value pairs, but I did not get exactly the same values (using the NumPy.Interp1d() method). I had been attempting to calculate inverse functions for the valueOperations prior to the implementation of the parameter.UnaffectedValue() method in Poser's Python API, so that I could determine actual parameter dial values, rather than the value resulting from valueOperations or conforming, prior to saving a pose, so that reapplying the pose would not double parameters which had valueOperations applied to them.

    Currently, there is absolutely no reason I can fathom why you would ever need or want (other than curiosity) to use MINUS or PLUS. DELTAADD does both of their functions perfectly well.

    PYTHONCALLBACK is absolutely impenetrable, once it's been set up. A python script will be called to update the parameter value, whenever parameters normally get updated, though the conditions under which the python script may change the value can be modified. The only possible operation in this case (all of the other valOp types can have their parameters and factors or key-values inspected and changed) is to clear the callback so it ceases to update the parameter. If the callback was set up by some third-party add-on, you can't even re-instate it if you need to temporarily disable updates.

    There is one more, major complication to the maths involved: Parameter limits. Regardless of what a DELTAADD, etc valOp says the output value should be, if the parameter has limits applied, the value will be constrained to those limits (another disastrous possibility if one is trying to calculate an inverse function: discontinuities). The point at which limits are applied is also undocumented, but appears to be at the end of all of the valueOperations applied to a parameter, rather than after each one (this could possibly change the calculated result!).



  • Kuroyume's CR2 FileSpec only mentions valueOpDeltaAdd*, and here's whatit says about the deltAddDelta value:

    deltaAddDelta defines the modifier for this dial. Here, I'll quote directly from Kattman's "CR2 Autopsy" to avoid causing conflicting information:
    "A further explanation of this is required so you know why this value is not equal to 1.000 like all other variations on this. In order to actually figure out the value to add to the deltaAddDelta parameter we need to do a little algebriac math. Do a little addition to find the full range, for example if the min is 0 and the max is 12 then the range is 12, if it is -2 and 12 then the range is 14. Lets say the Parent range is 12 and the Child range is 2, build the equation and solve for x using this formula (x beng the unknown deltaAddDelta): 1/Parent Range = x/Child Range. This would give us 1/12 = x/2. To solve for x we have to multiply both sides by 2 giving us 2
    (1/12) = x. solving this equation we get the deltaAddDelta value of 0.167 (always round to the nearest thousandth). To simplify this we could have simply stated: Child Range * (1/Parent Range) = deltaAddDelta."*

    I think I prefer my version ;o)

    *I think the other codes were only added at Poser 8/9?



  • @3dcheapskate it's a terrible shame that document has not been kept up to date. It was a terrific source of information. Nowadays, we get new keywords added to Poser's Python API with just about every release.

    That description is all about how to do calculations to find out what the delta needs to be set to to achieve the desired outputs.



  • @anomalaus Thank you !
    Now that's the sort of stuff that should be in the manual - especially the "...PYTHONCALLBACK is absolutely impenetrable, once it's been set up" ;o)

    If I manually edit the CR2 I'm guessing that I'd need to change valueOpDeltaAdd to e.g. valueOpDividInto, and deltaAddDelta to... errr...



  • This picture is mainly for my own benefit. I added several pairs of dummy parameters named <valueOp>Master and '<valueOp>Slave' to an actor and then added the appropriate valueOp lines to each slave parameter in the CR2.
    0_1549455071420_valueops.jpg

    So do you think we could use several dummy parameters chained together to do a calculation such as...
    4xNx(180-N)/(40500-Nx(180-N))
    ...



  • It appears that we can.
    Here's that sine approximation done with six parameters and seven valueOps (the names of the parameters were to help me when I was manually adding the valueOps):
    0_1549461076157_sine.jpg

    And here's the relevant bit in the CR2:

    
    		valueParm A (angle in degrees)
    			{
    			name A (angle in degrees)
    			initValue 0
    			hidden 0
    			enabled 1
    			forceLimits 0
    			min -100000
    			max 100000
    			trackingScale 0.004
    			keys
    				{
    				static  0
    				k  0  0
    				}
    			interpStyleLocked 0
    			}
    		valueParm 180 - A
    			{
    			name 180 - A
    			initValue 0
    			hidden 0
    			enabled 1
    			forceLimits 0
    			min -100000
    			max 100000
    			trackingScale 0.004
    			keys
    				{
    				static  0
    				k  0  180
    				}
    			interpStyleLocked 0
    			valueOpMinus
    				Figure 13
    				bone_8:13
    				A (angle in degrees)
    			}
    		valueParm A x 180-A
    			{
    			name A x 180-A
    			initValue 0
    			hidden 0
    			enabled 1
    			forceLimits 0
    			min -100000
    			max 100000
    			trackingScale 0.004
    			keys
    				{
    				static  0
    				k  0  1
    				}
    			interpStyleLocked 0
    			valueOpTimes
    				Figure 13
    				bone_8:13
    				180 - A
    			valueOpTimes
    				Figure 13
    				bone_8:13
    				A (angle in degrees)
    			}
    		valueParm 4 x A(180-A)
    			{
    			name 4 x A(180-A)
    			initValue 0
    			hidden 0
    			enabled 1
    			forceLimits 0
    			min -100000
    			max 100000
    			trackingScale 0.004
    			keys
    				{
    				static  0
    				k  0  4
    				}
    			interpStyleLocked 0
    			valueOpTimes
    				Figure 13
    				bone_8:13
    				A x 180-A
    			}
    		valueParm 40500 - A(180-A)
    			{
    			name 40500 - A(180-A)
    			initValue 0
    			hidden 0
    			enabled 1
    			forceLimits 0
    			min -100000
    			max 100000
    			trackingScale 0.004
    			keys
    				{
    				static  0
    				k  0  40500
    				}
    			interpStyleLocked 0
    			valueOpMinus
    				Figure 13
    				bone_8:13
    				A x 180-A
    			}
    		valueParm 4A(180-A) / 40500-A(180-A)
    			{
    			name 4A(180-A) / 40500-A(180-A)
    			initValue 0
    			hidden 0
    			enabled 1
    			forceLimits 0
    			min -100000
    			max 100000
    			trackingScale 0.004
    			keys
    				{
    				static  0
    				k  0  1
    				}
    			interpStyleLocked 0
    			valueOpTimes
    				Figure 13
    				bone_8:13
    				4 x A(180-A)
    			valueOpDivideBy
    				Figure 13
    				bone_8:13
    				40500 - A(180-A)
    			}
    

    The value of 'A (angle in degrees)' is the ONLY dial you twiddle - the others are predefined constants.



  • @3dcheapskate excellent! I'd done sine and cosine as valOpKEY over 720 degrees in 10 degree steps, but an approximation like yours would eliminate the huge key, value tables and get rid of the locked output when the key is out of the two revolutions range. Can you give an estimate of what level of accuracy that approximation delivers? Poser's rotation dials are only sensitive down to 0.01 degrees. You can't enter smaller increments of angle than that. I assume that such limits were a tradeoff for calculation speed, but it's really hard to get accurate pointing for something more that a few metres away in the scene, without finer angular resolution.

    There are other things it's sometimes* fun to try to do with valOps, but I was never successful at performing modulo arithmetic without giving up in disgust. I've always felt it should be possible, but usually resorted to a Python callback.

    Python callbacks, provided you are the one who installs and maintains them, are very useful for doing things like extracting Euler angles from an actor's rotation matrices (provided you remember to account for the scale hierarchy of the actor!) That way I can have a magnet simulate an actor affected by gravity, which gives more control (via manipulation of the zone and weightmaps) than simply using PointAt on an actor targeting a Nadir prop.



  • @anomalaus The equation was the first one that came up when I Googled sine approximation - Bhaskara I's sine approximation formula. A spreadsheet of 1 degree steps comparing spreadsheet sine function with the approximation, gives a difference of between 0.0001 and 0.0016 with an average of about 0.00140. The larger errors are in four clusters around 12, 55, 130, and 168 degrees.



  • @3dcheapskate thanks, and just complement the angle for cosine, eh? "You stupid angle!" returns NaN, of course ;-)

    I remember reading one of @bagginsbill 's Rendo threads where he'd come up with a trig approximation that was better (consistently more accurate) than one of the famous ones, but since he wasn't famous, nobody else knew about it. I think it related to polar coordinates, maybe?


  • Poser Ambassadors

    Yep and I also found a superior Fresnel approximation than Shlick's. Computers are wonderful things and they will tirelessly search for a needle in an infinite haystack. Being a programmer is fun.



  • But from a practical viewpoint - infinite haystacks are not! :-)