4:01 PM 10/5/2015:
OK, so, we can throw balls around, and figure out their positions and velocities. This is sufficient to make audio examples, but it doesn't give us collision detection or boundaries or anything more interesting than balls in space.
And I'm guessing ode is not designed for simulating planetary motion, so really we're just tossing balls into oblivion and watching them fall. Forever.
There are some useful bits from the first tutorial I didn't mention. Specifically, there are some methods of the mass object worth examining:
| setBox(density, lx, ly, lz)
|
| Set the mass parameters to represent a box of the given
| dimensions and density, with the center of mass at (0,0,0)
| relative to the body. The side lengths of the box along the x,
| y and z axes are lx, ly and lz.
I can only hope that the position variable is the center for every type of object. Not because I can guess where the center of mass would be on, say, a cone, but because then I don't have to ask.
| setBoxTotal(...)
| setBoxTotal(total_mass, lx, ly, lz)
|
| Set the mass parameters to represent a box of the given
| dimensions and mass, with the center of mass at (0,0,0)
| relative to the body. The side lengths of the box along the x,
| y and z axes are lx, ly and lz.
^ same thing, but you're using mass instead of density.
| setCapsule(...)
| setCapsule(density, direction, radius, length)
|
| Set the mass parameters to represent a capsule of the given parameters
| and density, with the center of mass at (0,0,0) relative to the body.
| The radius of the cylinder (and the spherical cap) is radius. The length
| of the cylinder (not counting the spherical cap) is length. The
| cylinder's long axis is oriented along the body's x, y or z axis
| according to the value of direction (1=x, 2=y, 3=z). The first function
| accepts the density of the object, the second accepts its total mass.
For reference, that means, from tip to tip, a capsule is length+2*radius.
That these are axis-aligned is a little perplexing. DO they rotate? Will python complain if I try to store (radius, length, axis) in a toople for purposes of the generator function?
| setCylinder(density, direction, r, h)
|
| Set the mass parameters to represent a flat-ended cylinder of
| the given parameters and density, with the center of mass at
| (0,0,0) relative to the body. The radius of the cylinder is r.
| The length of the cylinder is h. The cylinder's long axis is
| oriented along the body's x, y or z axis according to the value
| of direction (1=x, 2=y, 3=z).
Well, this is exactly the same as setCapsule, right?
Let's go ahead and update the new_body function, shall we?
This requires 3 separate sections: adding the type constants, adjusting the density catching (although maybe using setXTotal would have been a better workaround?), and adding the M.setX() bits.
Oh, dear; it seems I neglected to do something about the case where mass is an illegal value, too.
So, ou.py currently looks like this:
import ode, math
"""
Utility functions for common ode operations.
"""
# Shape constants. We'll add to these later.
SPHERE, BOX, CYLINDER, CAPSULE=(1, 2, 3, 4)
# We use shape constants to avoid writing functions for every single object type.
def new_body(world, type, position, size, density=-1, mass=-1,) :
if(density<0 and mass<0) : density=1.0
elif(density<0) :
if(type==SPHERE) : density=mass/((4/3)*(size**3)*math.pi)
elif type==BOX : density=mass/(size[0]*size[1]*size[2])
elif type==CYLINDER : density=mass/(size[1]*size[1]*math.pi*size[2])
elif type==CAPSULE : density=mass/( (size[1]*size[1]*math.pi*size[2]) + ((4.0/3.0)*(size[1]**3)*math.pi))
# adjust the density to fit the mass and volume.
body = ode.Body(world)
M = ode.Mass()
if(type==SPHERE) : M.setSphere(density, size)
elif type==BOX : M.setBox(density, size[0], size[1], size[2])
elif type==CAPSULE : M.setCapsule(density, size[0], size[1], size[2])
elif type==CYLINDER : M.setCylinder(density, size[0], size[1], size[2])
if(mass>0) : M.mass = mass # Wait, isn't this kinda redundant? Why did the original example have both of these? I'm confused!
body.setMass(M)
body.setPosition( position )
return body
# Shape generator.
Although, I think remembering the numbers for axes might be a bit annoying, so let's add this after the shape constants:
XAXIS, YAXIS, ZAXIS=(1, 2, 3)
Back to the tutorial...
We get this nice table of materials and their densities, which will probably be great for referencing later:
Material
Density (kg/m^3)
Balsa wood
120
Brick
2000
Copper
8900
Cork
250
Diamond
3300
Glass
2500
Gold
19300
Iron
7900
Lead
11300
Styrofoam
100
I think water is ~100 on this scale, although that seems kinda odd to me as I seem to remember it being 1.0, but if it was 1.0 styrofoam wouldn't float, so, meh.
I don't have any idea if ode has boyancy or other fluid dynamics simulations, but at this point, I don't think figuring out how to deal with swimming and floating are quite so urgent.
Before we move on to tutorial 2, tutorial 1 has this useful bit of advice:
If you have to simulate more complex objects you basically have two options:
Approximate the complex object by composing several simple ones. In this case you can add the individual masses (you can actually use the + operator which will call the ODE dMassAdd() function internally).
Calculate the properties yourself and set them with Mass.setParameters(). If you're representing your objects as a polyhedron, then you might find the following paper helpful: Brian Mirtich Fast and Accurate Computation of Polyhedral Mass Properties,journal of graphics tools, volume 1, number 2, 1996
I especially point you to the ability to add masses with the plus operator! I think that's awesome; don't you think that's awesome?
Of course, I have no idea how that handles things like distribution and density in a complex object. I mean, if we try to make a capsule by adding two spheres to a cylinder, what is the resulting mass?
... Wait, we can test that!
>>> import ode
>>> can=ode.Mass()
>>> can.setCylinder(2500.0, 1, 0.05, 1.0)
>>> ball=ode.Mass()
>>> ball.setSphere(2500.0, 0.05)
>>> ball.translate((0.5,0,0))
>>> m=can+ball
>>> m.mass
22.252946853637695
>>> can.mass
22.252946853637695
>>> ball.mass
1.3089970350265503
(Hint: I made liberal use of help(ode.Mass), or rather, help(m) when writing this. I just didn't copy the parts where I screwed up and had to check.)
Somehow, the mass of the ball is not being considered at all in m. Let's see what happens if I try it the other way around?
>>> m=ball+can
>>> m.mass
23.56194305419922
Oh, my. That is troubling.
Wait; we didn't see what happens in the space where they overlap. Eh, that's not too hard:
(If I were doing this in Microsoft Word, I'd probably set a macro for those BB tags.)
So, I can only hope that the distribution of the mass is not quite perfect. But, eh, what does it matter, anyway?
I mean, I checked, and those 4 are the only basic shapes in the mass class. You can design more complex shapes, if you understand the 3x3 matrix that it uses internally. But I don't, so there.
Ah, wait! Disaster!
I decided to check the weird communitive property violation up there, and this happened:
>>> m=can+ball
>>> m.mass
22.252946853637695
>>> can.mass
22.252946853637695
>>> ball.mass
1.3089970350265503
>>> m=ball+can
>>> m.mass
23.56194305419922
>>> m.mass-ball.mass
0.0
>>> (can+ball).mass-(ball+can).mass
-23.56194305419922
>>> (can+ball).mass-(ball+can).mass
-69.37682342529297
>>> (can+ball).mass-(ball+can).mass
-184.56854248046875
>>> (can+ball).mass-(ball+can).mass
-484.32879638671875
>>> (can+ball).mass-(ball+can).mass
-1268.41796875
It seems that those additions are permanently altering the original masses. Oops.
OK, be careful when adding masses. Got it.
看過來!
"If you want utopia but reality gives you Lovecraft, you don't give up, you carve your utopia out of the corpses of dead gods."
MaxAngor wrote:
George... Don't do that.