Oscillator the script
Now that I'm starting to finish the metaballs script (just need to add a GUI, change the handles spheres of reasonable size add metacube/metacylider/metacone), I'm starting to think of a new script : an oscillator machine.
Here's the thought on that: create part creates a box handle and a magnet a bit far away from it on the Y direction. Magnet is parented to the box, the idea is the user parents and orients the handle in a body part, say an ear, then the user adds the Affected Body Part to the magnet, and sets the magnet zone or weight map as he sees sit. For long rabbit ears that means two such objects, one for each ear, on aligned so that their Y direction is on the long ear's long axis.
Then I dynamically rotate the magnet on X and Z directions, and translate the magnet on the Y direction, per dampened spring (= oscillator) equations. The equations dampen per 6 values: FreqX, FreqY, FreqZ, CyclesX, CyclesY, CyclesZ. Say FreqX=3 and CyclesX=5... that means that once perturbed, the magnet will bounce around on X rotation every 3 frames 5 times, for a total of 15 frames...like... boing-oing-oing-oing-oing... until it comes back to 0.
The perturbation is changes in world coordinates of its handler. So if the handler changes X/Y/Z, there's a deltaX/Y/Z from previous frame, and that causes perturbations in the magnet rotXm/tranYm/rotZm (the coordinates are not the same).
That way if the rabbit say moves forward, there will be an impulse in the magnets Zm direction. If the rabbit rotates the head, there are impulses in both Xm and Zm directions, making the magnet rotate on dampened cycles. If the rabbit jumps, then there is an impulse on the Ym direction, and the ears stretch/contract on dampened cycles.
Then add a parameter Hold to stop the equations contributions. That way the user can set Hold=1.0 to stop the ears from bouncing, and can set the magnet rotXm/tranYm/rotZm manually, like for someone holding the rabbit's ears. Once the user sets Hold back to 0.0, the non-zero rotXM/tranYm/rotZm now are impulses, and the ears will bounce back to their zero position.
May add another parameter Zero to force the magnet back to initial positions. That might come in handy.
The whole thing is just a set of 3 differential equations on rotXm/tranYm/rotZm based on a dampened oscillator thingie. I don't think it's much trouble at all.
Watcha you guys think?
Oh, and run the simulation for the entire range of the animation, as the user can disable it easily via the Hold parameter.
Hmm.. gotta add parameters StrengthX/Y/Z too, to make the thing more or less rigid.
Can't find the difference equations in the net, so let's see the math; in one dimension:
x''(n) + kx'(n) + wx(n) = 0
where w = frequency, k = dampening. So a free oscillation will really need 2 previous frames. That becomes in differences:
x"(n) = (x'(n)-x'(n-1))/h = (x(n)-2x(n-1)+x(n-2))/h^2
x'(n) = (x(n)-x(n-1))/h
Where h = animation fps; so
(x(n)-2x(n-1)+x(n-2))+kh(x(n)-x(n-1))/h+w.h2.x(n) = 0
(1+k+w.h2)x(n) - (2+k).x(n-1) + x(n-2) = 0
x(n) = A.x(n-1) + B.x(n-2)
A = (2+k)/(1+k+w.h2)
B = -1 / (1+k+w.h2)
So these guys should behave as
if zero = 1 then x(n) = 0
if zero = 0 and hold = 1 then x(n) = xcurr(n)
if zero = 0 and hold = 0 then x(n) = A.x(n-1) + B.x(n-2)
We can put that in a linear way to let a bit more control... I think... say
(1-zero)( holdxcurr(n) + (1-hold)*(A.x(n-1) + B.x(n-2) ) )
This is a proof of concept script; the A and B values are just completely guessed
scene = poser.Scene() a1 = scene.Actor('box_1') a2 = scene.Actor('box_2') p1 = a1.Parameter('yTran') p2 = a2.Parameter('yTran') d_minus1 = 0.0 d = 0.0 scene.SetFrame(0) y2_plus1 = p2.Value() for nFrame in xrange(0,100): scene.SetFrame(nFrame) # move y2 based on forces from last frame y2 = y2_plus1 p2.SetValue( y2 ) # now calculate the forces at this frame y1 = p1.Value() d = y2 - y1 - 0.15 d_plus1 = 0.5*d - 0.8*d_minus1 d_minus1 = d y2_plus1 = y1+d_plus1+0.15 scene.SetFrame(0)
This is the result for translations:
The oscillator for angles is just a slight variation; once again the parameters are just guesses:
# Rotation Dynamics # Oscillator is r2(t) - 0.9*( r1(t)-r1(t-1) ), goes to 0 scene.SetFrame(0) rotZ1_minus1 = r1.Value() rotZ2_minus1 = 0 rotZ2_plus1 = 0 for nFrame in xrange(0,100): scene.SetFrame(nFrame) # rotate r2 based on impulse from last frame rotZ1 = r1.Value() rotZ2 = rotZ2_plus1 - 0.8 * (rotZ1-rotZ1_minus1) r2.SetValue( rotZ2 ) # now calculate impulse on this frame print nFrame,'rotZ1 = ',rotZ1,' rotZ1_minus1 = ',rotZ1_minus1 rotZ2_plus1 = 0.5*rotZ2 - 0.8*rotZ2_minus1 rotZ2_minus1 = rotZ2 rotZ1_minus1 = rotZ1 scene.SetFrame(0)
Proof of concept is this guy:
This is a final proof of concept, with a wobbly bridge. The setup is box_1 to generate the impulses, and the controlled oscillator is the magnet; the magnet has a weight map on the bridge:
The box has zrotate on constant interp, so any change in value generates an impulse to the oscillator. Linear and split interpolations work too, but are more points to key, and I'm lazy. This has just 7 impulses via box_1.
Result is this.... NOTE: anyone with polluted mind will remember, this is a BRIDGE, it even has bricks for people to remember that... it's not, err.. something else... or something other else.. or another something else, so whatever you thought, this is not... this is a wobbly bridge! (I could have demo'd with long hair, but it's late and I'm sleepy):
Hmm... I should have added some trees and flowers, and maybe an oxen cart passing over it to make sure people recogize it as a wobbly bridge, and not put their imagination to work...
I should have added some fish underneath, and a duck or two... but I have no ducks in my arsenal!
Hmmm.... I actually can make this script quite generic. Forget the assumptions about parenting and distances and whatnots. I can just get impulses from deltas in X/Y/Z and rotX/Y/Z from a control prop.
Every prop has a X/Y/Z and a rotX/Y/Z. So I can just ignore everything about parenting and whatnots, and just make the script search for pairs of objects called
Make the targets X/Y/Z and rotX/Y/Z on Oscillator_n to be zero (the user can add whatever offset they want by parenting Oscillator_n to something). So whenever the world X/Y/Z or rot X/Y/Z changes in Control_n, that's an impulse and will get the associated values in Oscillator_n to wobble down to zero. Due to the nature of the differences equation, whatever constant values are in Control_n, the oscillator will go down to zero.
The advantage of this method is that the same object can be impacted by multiple oscillators, say one for slow oscillations, one for fast oscillations.
Also I can allow the user to create its own Control_n by renaming some prop or body part to be Control_n (and then default some values for K and W), or use a script that create Control_n cubes with parameters K and W for finer control.
That way it should be pretty generic. So, 3 scripts (or 3 buttons in a script):
(a) Add Control and Oscillator
(b) Run Oscillators
(c) Clear Oscillators
This is looking nice indeed. :)
Darn it... if Poser allowed a parameter with a string value instead of just numbers, I could allow the user to indicate which parameter on which prop they want to plug in the oscillator. This would make this really generic, as then a parameter like "SwingBack" for the hair could also be controlled by an oscillator.
But, alas, no such luck, so chances are the oscillators are always going to be magnets (unless one wants to go in complicated linking of dependent parameters, like back "SwingBack" to be dependent on the xRotation of some off-screen object. But I find creating these dependencies difficult, as there's no linear dependency that just says value0 at input0, value1 at input1 and that's it; the training thing for dependent parameters is too much hassle for me).
Hmmm.... one thing one could do is this... forget about rotations. Script "Add control" creates a big plane named Controllers, then a child small short box inside named Control_n. It's a child so that the user can move the controls out of the way.
The small short box works as a slide. Slide X left or right, it will cause an impulse on the target. The short box has the K and W parameters, and also Amplitude.
The link to the oscillator is done inside Add control. It presents a dropdown of all actors; once selected an actor, it presents another dropdown with parameters in that actor. Once that's selected, it creates a parameter "Oscillator_n" in the actor, then calls the function
AddValueOperation(kValueOpTypeCodeDELTAADD, pointer to Oscillator_n parameter)
in the target parameter. That makes Oscillator_n to be the master for whatever parameter the user wants. That allows oscillation of pretty much anything, from position to rotations to scales to morphs. Just slide the control and voila the target parameter oscillates adding around whatever value the user has in that parameter.
Actually don't need to create the Oscillator_n in the actor, with polution to its timeline. I understand that a parameter can be a master for parameters in other actors... so Oscillator_n can be a parameter in the Control_n box...I think...
Grrr.. have to redo my math on this.. I took a shortcut and ended up confusing the values; the right equation with no shortcuts is
x''(n) + 2.k.w0.x'(n) + w0^2.x(n) = 0
x"(n) = (x'(n)-x'(n-1))/h = (x(n)-2x(n-1)+x(n-2))/h^2
x'(n) = (x(n)-x(n-1))/h
h = 1/frame rate
[x(n)-2x(n-1)+x(n-2)]/h^2 + 2.k.w0[x(n)-x(n-1]/h + w0^2.x(n) = 0
[x(n)-2x(n-1)+x(n-2)] + 2.k.w0.h.[x(n)-x(n-1]/h + h^2.w0^2.x(n) = 0
[x(n+1)-2x(n)+x(n-1)] + 2.k.w0.h.[x(n+1)-x(n)] + h^2.w0^2.x(n+1) = 0
(1+2.k.w0.h+h^2.w0^2)x(n+1) = (2+2.k.w0.h)x(n) + (-1)x(n-1)
x(n+1) = (A.x(n)-B.x(n-1))/C
A = 2+2.k.w0.h
B = 1
C = 1+2.k.w0.h+2.wo^2)
Oh well... good old Euler method diverges for that guy... Trying again before change to say Runge-Kutta.
x' = u
u' = - 2kwu - w2x
x(n+1) = x(n) + h.u(n)
u(n+1) = u(n) - h.( 2.k.w.u(n) + w2.x(n))
Actually I think that's good enough for a toy. Nobody is trying to send a man to space with this. That second form is better behaved, although it can diverge too. The input is an impulse in the handler on frame 1 with change in X of 0.07. As Poser coordinates are tiny, I added a "Force" parameter that multiplies that input, so I set it to 10 for x10, making the input 0.7.
At 12 fps, a value of w = 20 yields oscillations with a period of about 3.5 frames; that's extremely tight for numerical methods, as a sinusoid in 3.5 frames is pretty much a seesaw, but it behaves itself. With K=0.9 it yields some 10 oscillations or so, so that's good enough.
At w=8 it yields a nice sinusoid with period of about 8 frames; with longer period the curve is much better behaved. But one gets to adjust the K for the desired period, as the thing diverges easily, thanks to old Euler method from 1700s.
So I'm leaving this as is. If I have time in the future I'll modify that to use Runge-Kutta, but I haven't looked at that in 30 years or so, so I'm not extremely inspired to do that now.
Hmm... I'm going to add a special case for very fast cycles, which diverge the equation. I'm going to add an exception for w=-1. That will just make
x(n+1) = -K*x(n)
That way one can make very fast dampened triangular oscillations with period = 2. That's pretty useful at 12 fps. Could even think of a case with period = 4...how to write that.. hmm...
x(n+2) = -K*x(n)
as a difference equation.. hmm...
x(n+1) = Ax(n) + Bu(n)
u(n+1) = Cx(n) + Du(n)
x(n+2) = Ax(n+1)+Bu(n+1) = Ax(n+1)+BCx(n)+BDu(n) = -Kx(n)
A = 0, D = 0, BC = -K
x(n+1) = u(n)
u(n+1) = -Kx(n)
hmm... wonder if that will work...
Demo of Oscillator v0.01:
In this Demo Andy doesn't have a single keyframe. Poinger The Oscillator script created 2 handlers (pink and green boxes) that are linked to Andy's head side-to-side (pink one), and Andy's forearm side-to-side (green one).
Whenever the handlers move in either X or Y direction that generates a dampened impulse that adds to whatever values Andy has in in these parameters, so one can still pose Andy as before with no interference.
The pink handler is set with Force = 200, K = 0.9, W = -1 (that's a forced triangular wave oscillator with period = 2; try W=-2 too). The green handler is set with Force = 300, K=0.6, W=12 (that's a sinusoidal with period of about 6 frames.
W is frequency; higher W means faster oscillations; don't set it too high or you'll break the simulator (can't oscillate at more fps than the frame rate). K is dampening; K=1.0 or >1.0 means critical dampened (it doesn't oscillate, it just comes back to the original position with an impule), K = 0.8 to 0.99 means lotsa dampening; K = 0.2 to 0.5 means very little dampening; K = 0.0 to 0.2 will tend to break the simulator.
If you try to simulate and the thing flies away, it means you've set either a too large W or too small K. If the simulation gives nothing, the force may be too small for you to see the effect, trying increasing the force. Trial and error will get the initial adjustments to whatever parameter this is controlling.
This controls ANY parameter in any figure or actor. It can be position, rotation, morph, lighting, etc...