• Welcome to the Community Forums at HiveWire 3D! Please note that the user name you choose for our forum will be displayed to the public. Our store was closed as January 4, 2021. You can find HiveWire 3D and Lisa's Botanicals products, as well as many of our Contributing Artists, at Renderosity. This thread lists where many are now selling their products. Renderosity is generously putting products which were purchased at HiveWire 3D and are now sold at their store into customer accounts by gifting them. This is not an overnight process so please be patient, if you have already emailed them about this. If you have NOT emailed them, please see the 2nd post in this thread for instructions on what you need to do

The first four segments of a millipede - not sure if this'll go anywhere :)

3dcheapskate

Brilliant
A long time ago, while I was having fun doing trigonometry with valueOps, I was watching the millipedes (not at precisely the same time as I was doing the maths - I'd taken a break and gone outside) and wondered whether the sinusoidal ripple of its legs could be done in the same way with valueOps.

Then I promptly forgot all about it.

Until a couple of weeks ago.

So, anyway, at present I've just created a simple body segment with fixed legs and a simple texture, duplicated that to give me four segments. Imported into Poser as an OBJ, each segment being a separate group so I can add them to the appropriate bone easily. Created a simple four bone skeleton, assigned the segments appropriately, unticked "Bend" for each body part (millipedes don't bend), set limits to +/-9 degrees for x/y/zRotate.

I think my basic idea now is to do the leg movement with morphs, but to control those morphs with trigonometry and valueOps. And then try and get those synchronized with the millipede going for a stroll.

For now here's a very short animated wiggle...


I've attached a zipped runtime with the CR2, OBJ, and texture if anybody wants to play and/or offer suggestions. Everything's in subfolders :3DCheapskate:TEST:Millipede:
 

Attachments

  • Millipede01 test1.zip
    22.5 KB · Views: 22

3dcheapskate

Brilliant
Thanks Janet. :)

To get the basic leg motion that I wanted (for a single pair of legs) I just needed two simple morphs, "Legs Back" and "Legs Up". The former uses values -1.0 to +1.0 to move the leg pair forwards and backwards, while the later uses 0.0 to 1.0 to raise the leg pair and drop them again. I wanted to control them from a single dial, which I called "Leg Cycle", which goes from 0 to 360. The value for "Legs Back" is simply the sine, and the the value for "Legs Up" is 0.5*(1-cosine), each implemented as a valueOpKey in ten degree steps.

I implemented this for just one segment to see if it works as I want - here's a quick video of a single leg cycle (the front is to the right)


And here's a screenshot of the dials in Poser - the only one I touched was the leg cycle one, setting it to 0 at frame 1 and 360 at frame 72

LegCycle.jpg


Next step would be to add the same morphs and valueOps to the other three segments and slave each segments "Leg Cycle" dial to a master dial in the body, but with an appropriate phase shift for each segment...
 

3dcheapskate

Brilliant
Agreed - somebody should probably make some wiggly and/or multi-legged bird food, but that somebody probably isn't me since what I think I'm really doing here is seeing if I can use valueOps maths to make a millipede's legs move realistically, in that sinusoidal rippling manner, from a single controlling dial... Well, if truth be told, probably that and using exactly the same geometry for every segment via geometry swapping. And quite likely some other things I can't quite define. :D

The latest* problem I've run into is that there's no simple way to do a modulo function via valueOps, although I can see a couple of workarounds which should be okay under certain criteria...

*I've run into it before, and I'm well aware of it. Last time I ran into it I got bored and went and did something else instead. This time I have a couple of possible ways forward.
 

3dcheapskate

Brilliant
Making progress...

I now have all four pairs of legs controlled from a single master dial, "Master Leg Cycle Angle" - its range is currently limited to 0-360 to make the modulo stuff simple, but I want some way to set it much bigger to allow multiple leg cycles, e.g. 720 for two cycles.

The "Segment Phase Shift" dial is a value that's added to each leg pair's leg cycle to get the angle for the next pair. Doing this modulo 360 with valueOps is the awkward bit. I've limited this to 0-90 so that the angle added to "Master Leg Cycle Angle" for the fourth pair of legs will be 0-360.

The "Legs# Modulo 360" is the value that's actually applied to a leg pair.

Example: if "Master Leg Cycle Angle"= 337 and "Segment Phase Shift"=10, then "Legs1 Modulo 360"=347, "Legs2 Modulo 360"=357, "Legs3 Modulo 360"=7 and "Legs4 Modulo 360"=17

The screenshot below gives an indication of the rather awkward valueOps maths I did to ensure that adding two values with ranges 0-360 will yield a 0-360 result.
The reason I need 0-360 is that the sine and cosine calclations use valueOpKey lookup tables, currently in 10 degree steps, and I need to duplicate these for each leg pair.

Adding modulo 360, valueOps style.jpg



And here's a 361 frame animation of a single leg cycle - I set the "Master Leg Cycle dial", which defaults to zero, to 360 on frame 361. That's all.


I know that I could probably better do the calculations of the angles for each leg pair with a script... but where's the challenge in that, eh ?! ;)


I just need to tidy up the CR2 a bit (not too much though) and then I'll zip the latest runtime and upload it in the unlikely event that anybody wants a go.
 

3dcheapskate

Brilliant
Found a couple of mistakes in the valueOps (each of the "using a Legs3 value where I should have been using Legs2" type) and finished the tidy up. So zipped runtime attached, containing just the latest CR2 plus the same OBJ and texture map as before. Everythings under :3DCheapskate:TEST:Millipede: subfolders as before. All paths should hopefully be relative, so just copy to any runtime and play.

Here's a 30 (or 36?) frame single cycle animation. I also gave the figure an x translation, judged by eye to make it look about right. So only two parameters needed to be set to get this - the Master Cycle and xTranslate for the final frame.


And a slower, 361 frame version, of the same thing.

 

Attachments

  • Millipede02 test.zip
    28.4 KB · Views: 19

3dcheapskate

Brilliant
I'm quite looking forward to trying this with a longer millipede, say a couple of dozen segments, but I'll want the legs to be able to do several cycles and not just the one cycle I've used in the previous tests.

However, therein lies the biggest problem at present - I need a master dial that can take large values (e.g. 720 for 2 cycles, 3600 for 10 cycles, etc) but there's no easy way to do a modulo 360 using valueOps. I asked over on the Renderosity Poser 11 forum here Creating a new master parameter that works modulo 360 - can it be done ? (renderosity.com) and it doesn't seem as if anybody has a good way to work around that.

Trying to use valueOpKey doesn't seem to work because the spline interpolation it uses doesn't like the angles on a sawtooth waveform. An alternative is that I think I might be able to slightly simplify the method I used in post #6 of this current thread and put separate checks for each 360 degree cycle, i.e. 0-360, 360-720, 720-1080, etc.

So it appears that this project is now on hold until I can get a workable modulo 360 workaround.
 

3dcheapskate

Brilliant
Over on the Renderosity thread mentioned/linked in the previous post adp001 has suggested using a Python callback (one of the valueOp codes that can be used in the CR2 is for a Python callback) to do the modulo 360. This sounds promising, so I'll give it a try. Unfortunately that won't work in DAZ Studio, so it'll be limited to Poser.
 

3dcheapskate

Brilliant
It appears that a Python callback is going to be the best way to do the modulo360. That got me thinking - if I have to write a bit of Python for the modulo 360, why bother with any of the valueOps maths that this thread has been about ? Why not do all the maths in that Python callback ?

Well.. it appears that that won't work due to some weird callback behaviour , which is a shame because I also had an idea for controlling the movement of the millipede in the same callback - basically make the Head the first bone with the following segments in a bone chain, then each frame set the yRotate of the Body to a randomish value of currentYrotate±5°, calculate new x/zTrans based on current position and yRotate, and set the yRotate of segment 1 to the yRotate of the head from the previous frame, segment 2 yRotate = segment 1 yRotate from previous frame, etc...

Anybody have any other ideas for controlling the millipedes movement ? I'm aware that Poser has 'Walk Paths' but that's the full extent of my knowledge on that subject ! :D
 

Janet

Dances with Bees
Contributing Artist
It appears that a Python callback is going to be the best way to do the modulo360. That got me thinking - if I have to write a bit of Python for the modulo 360, why bother with any of the valueOps maths that this thread has been about ? Why not do all the maths in that Python callback ?

Well.. it appears that that won't work due to some weird callback behaviour , which is a shame because I also had an idea for controlling the movement of the millipede in the same callback - basically make the Head the first bone with the following segments in a bone chain, then each frame set the yRotate of the Body to a randomish value of currentYrotate±5°, calculate new x/zTrans based on current position and yRotate, and set the yRotate of segment 1 to the yRotate of the head from the previous frame, segment 2 yRotate = segment 1 yRotate from previous frame, etc...

Anybody have any other ideas for controlling the millipedes movement ? I'm aware that Poser has 'Walk Paths' but that's the full extent of my knowledge on that subject ! :D
keyframe animation?
 

3dcheapskate

Brilliant
That's a logical one because the spline interpolation used by keyframe animation should ensure that changes from frame to frame follow a nice smooth curve.

However, what I really want (and I didn't really know this before) is to move a certain distance each frame in a direction that may vary slightly (say ±2°) from the direction of the previous frame. For movement restricted to a horizontal plane (a good start, and probably as far as I'll ever go) that would mean keyframing both the x and z translations, but I don't see any way of ensuring that they are in step (incoming retroactive pun intended...) - I guess that's what the 'walk path' thing is for ?

Setting yRotate-of-each-segment = yRotate-of-segment-in-front-from-previous-frame, which I believe is what I'll need to do to get the millipede to bend correctly as it moves, is something that I think will have to be done in Python.
 

3dcheapskate

Brilliant
It's going rather fast in this video, but I've now got the individual segments rotating to follow the millipede's path, although the calculations may not be quite correct.

Note: the legs are NOT moving, they're in a fixed position relative to their segment - I haven't yet added the leg movement morphs and valueOp calculations to do that. In fact I think I'll abandon the valueOp maths approach since I can't do the necessary modulo 360 that way. I now plan to control the leg movement from the callbacks.


Here's the latest version of the script (I'll attach the script and millipede.cr2 in the next post for anybody who wants to play):

# ! Callback Test Setup Version 585 (Millipede04)
import math,random

# Version 584
# Added callbacks for the yRotates of each of the four segments of my basic millipede.
# There are debug prints in all the yRotate callbacks to help me check the maths
# There's no error checking so if you try to run this script on anything except my Millipede04.cr2 it'll crash
#
# Next step is probably to try and do the leg cycles in the callbacks too...


#--------------------------------------------------------------------
# Initialize the precalculated arrays
#--------------------------------------------------------------------
def InitYRots():
global ARRAY_SIZE
global yRotArray
global xTranArray
global zTranArray
ARRAY_SIZE=360
yRotArray = range(ARRAY_SIZE)
xTranArray = range(ARRAY_SIZE)
zTranArray = range(ARRAY_SIZE)
for i in range(ARRAY_SIZE):
if i <= 1:
yRotArray = 0
xTranArray = 0
zTranArray = 0
else:
yRotArray = (yRotArray[i-1] + (30 * (random.random()-0.5))) % 360
xTranArray = xTranArray[i-1] + (onestep*math.sin(math.radians(yRotArray)))
zTranArray = zTranArray[i-1] + (onestep*math.cos(math.radians(yRotArray)))
print "Frame"+str(i)+": yR="+str(yRotArray)+", xT="+str(xTranArray)+", zT="+str(zTranArray)

#---------------------------------------------------------------------
# return the next precalculated y rotation
#---------------------------------------------------------------------
def GetYRot(frame):
global yRotArray
global ARRAY_SIZE
if frame >ARRAY_SIZE:
return yRotArray[ARRAY_SIZE-1]
else:
return yRotArray[frame]
#---------------------------------------------------------------------
# return the next precalculated x translation
#---------------------------------------------------------------------
def GetXTran(frame):
global xTranArray
global ARRAY_SIZE
if frame >ARRAY_SIZE:
return xTranArray[ARRAY_SIZE]
else:
return xTranArray[frame]
#---------------------------------------------------------------------
# return the next precalculated z translation
#---------------------------------------------------------------------
def GetZTran(frame):
global zTranArray
global ARRAY_SIZE
if frame >ARRAY_SIZE:
return zTranArray[ARRAY_SIZE]
else:
return zTranArray[frame]


# Callback for the Body "yRotate" parameter
def cheapskatesYRotateCallback(parm,value):
global yRot
global yRotFrame
curframe = poser.Scene().Frame()
if curframe != yRotFrame:
yRot = GetYRot(curframe)
yRotFrame = curframe
print "\n"+str(curframe)+": Body="+str(yRot)
return yRot
# Callback for the Body "xTran" parameter
def cheapskatesXTranCallback(parm,value):
global xPos
global xPosFrame
curframe = poser.Scene().Frame()
if curframe != xPosFrame:
xPos = GetXTran(curframe)
xPosFrame = curframe
return value+xPos
# Callback for the Body "zTran" parameter
def cheapskatesZTranCallback(parm,value):
global zPos
global zPosFrame
curframe = poser.Scene().Frame()
if curframe != zPosFrame:
zPos = GetZTran(curframe)
zPosFrame = curframe
return value+zPos


# Callback for the Segment_01 "yRotate" parameter
def cheapskatesSeg1YRotCallback(parm,value):
global yRotSeg1
global yRotFrameSeg1
curframe = poser.Scene().Frame()
if curframe >= 2:
if curframe != yRotFrameSeg1:
yRotFrameSeg1 = curframe
yRotSeg1 = GetYRot(curframe-1) - GetYRot(curframe)
print str(curframe)+": Seg1="+str(yRotSeg1)
return yRotSeg1
else:
return 0.0
# Callback for the Segment_02 "yRotate" parameter
def cheapskatesSeg2YRotCallback(parm,value):
global yRotSeg2
global yRotFrameSeg2
curframe = poser.Scene().Frame()
if curframe >= 3:
if curframe != yRotFrameSeg2:
yRotFrameSeg2 = curframe
yRotSeg2 = GetYRot(curframe-2) - GetYRot(curframe-1)
print str(curframe)+": Seg2="+str(yRotSeg2)
return yRotSeg2
else:
return 0.0
# Callback for the Segment_03 "yRotate" parameter
def cheapskatesSeg3YRotCallback(parm,value):
global yRotSeg3
global yRotFrameSeg3
curframe = poser.Scene().Frame()
if curframe >= 4:
if curframe != yRotFrameSeg3:
yRotFrameSeg3 = curframe
yRotSeg3 = GetYRot(curframe-3) - GetYRot(curframe-2)
print str(curframe)+": Seg3="+str(yRotSeg3)
return yRotSeg3
else:
return 0.0
# Callback for the Segment_04 "yRotate" parameter
def cheapskatesSeg4YRotCallback(parm,value):
global yRotSeg4
global yRotFrameSeg4
curframe = poser.Scene().Frame()
if curframe >= 5:
if curframe != yRotFrameSeg4:
yRotFrameSeg4 = curframe
yRotSeg4 = GetYRot(curframe-4) - GetYRot(curframe-3)
print str(curframe)+": Seg4="+str(yRotSeg4)
return yRotSeg4
else:
return 0.0

# Need to recalculate yRot,XTran, and zTran each frame based on calculated ('keyed', upper figure on dial) value from previous frame, NOT the dialed ('natural', lower figure on dial)
# This (i.e. using globals) seemed the easiest way to do it, at least for my first attempt
global yRot
global yRotFrame
global xPos
global xPosFrame
global zPos
global zPosFrame
global yRotSeg1
global yRotFrameSeg1
global yRotSeg2
global yRotFrameSeg2
global yRotSeg3
global yRotFrameSeg3
global yRotSeg4
global yRotFrameSeg4

# Arrays to hold precalulated values
global ARRAY_SIZE
global yRotArray
global xTranArray
global zTranArray

# These are just to avoid having to do 'body.Parameter("yRotate")' etc in the callbacks. There are others I could add too.
global parmXTran
global parmZTran
global onestep

# Initialize a few things
fig=poser.Scene().CurrentFigure()
body=fig.ActorByInternalName("BODY")
seg1=fig.ActorByInternalName("seg1")
seg2=fig.ActorByInternalName("seg2")
seg3=fig.ActorByInternalName("seg3")
seg4=fig.ActorByInternalName("seg4")
yRot=0
yRotFrame=0
xPos=0
xPosFrame=0
zPos=0
zPosFrame=0
yRotSeg1=0
yRotFrameSeg1=0
yRotSeg2=0
yRotFrameSeg2=0
yRotSeg3=0
yRotFrameSeg3=0
yRotSeg4=0
yRotFrameSeg4=0
parmYRot=body.Parameter("yRotate")
parmXTran=body.Parameter("xTran")
parmZTran=body.Parameter("zTran")
onestep=0.1

# Initialize the precalculated array
InitYRots()

# Kick off the callbacks
print "Setting up the Body 'yRotate' callback..."
body.Parameter("yRotate").SetUpdateCallback(cheapskatesYRotateCallback)
print "Setting up the Body 'xTran' callback..."
body.Parameter("xTran").SetUpdateCallback(cheapskatesXTranCallback)
print "Setting up the Body 'zTran' callback..."
body.Parameter("zTran").SetUpdateCallback(cheapskatesZTranCallback)
print "Setting up the Segment_01 'yRotate' callback..."
seg1.Parameter("yRotate").SetUpdateCallback(cheapskatesSeg1YRotCallback)
print "Setting up the Segment_02 'yRotate' callback..."
seg2.Parameter("yRotate").SetUpdateCallback(cheapskatesSeg2YRotCallback)
print "Setting up the Segment_03 'yRotate' callback..."
seg3.Parameter("yRotate").SetUpdateCallback(cheapskatesSeg3YRotCallback)
print "Setting up the Segment_04 'yRotate' callback..."
seg4.Parameter("yRotate").SetUpdateCallback(cheapskatesSeg4YRotCallback)
print "...done."
 
Last edited:

3dcheapskate

Brilliant
Ah, but this is only the beginning !
First thing is to slow it down a bit.
Then get the legs working as before, but using the callbacks instead of valueOps.
And then I need more segments...
...lots and lots of them !
:D

(and I mustn't forget about the books)
 

3dcheapskate

Brilliant
And here's the CR2 (Millipede04 EDIT1.cr2) and the script (Millipede04 Callback Setup.py) for Millipede04, should anybody want to play.

Both files are in the same subfolder. (There's also the same texture as for all previous millipedes)

Runtime\libraries\character\3Dcheapskate\TEST\Millipede\

Note: the callbacks ignore the 'natural' values (i.e. the ones set by the user) of yRotate, xTran and zTran so it always starts at the origin facing along +Z. Now that I've got the basics working I'll add that to a list of "more stuff I need to think about"
 

Attachments

  • Millipede04.zip
    20.8 KB · Views: 10
Top