
Russell Smith is the creator of ODE (Open Dynamics Engine), a free software for simulation of rigid body dynamics in 3D.
Takashi Yamamiya is working in a kit for the use of ODE inside Squeak-Croquet: ODECo. He , with Bernd Eckardt, have created a nice demo, that was our main source for the exercice of this lesson.
The Windows version of the kit/plugin is available now and we will use it at our lesson.
First of all you need to download:
In the previous lesson you have learned how to install "Squeak-Croquet" packages. Download and install:
ODE+ODECo provides resource for a Croquet object to simulate physics occurences like reactions to the gravity force and to collisons.
The exercice we will do at this lesson is: one of the "legoics" (the dear habitants of Croq City) will kick balls.
You need to download a new legoic:
The Class has something new:
TGroup subclass: #LegoicMODE instanceVariableNames: 'inControl tframe body geom pickDelta ' classVariableNames: '' poolDictionaries: 'OpenGLConstants ' category: 'MyCroquet'
And the methods (similar for any piece having "physics") are a little diffrent of that of the usual "piece":
body ^body.
Each piece to react physically needs to have a virtual "body", a kind of soul... Another method:
body: aBody body := aBody.
A piece will have also a "geom" (from geometry). New basic method:
geom ^geom.
And for a complete basic definition of "geom", another method:
geom: aGeom geom := aGeom.
Four methods about pointer:
handlesPointerDown: pointer ^ true.
Another:
pick: pointer ^true.
The thirth will start the TimeStep loop:
pointerDown: pointer pickDelta := pointer globalTransform orthoNormInverse composeWith: self globalTransform. self startStepping. ^ true.
This, stops the TimeStep loop.:
pointerUp: pointer self stopStepping. pickDelta := nil. ^ true.
The TimeStep loop:
step | pointer | pickDelta ifNil: [^ self]. pointer := CroquetGlobals theTeapotMorph activeCamera avatar pointer. self localTransform: (pointer globalTransform composeWith: pickDelta). body position: self globalPosition. body quaternion: self quaternion.
The usual definition of component:
isComponent ^ true.
And in the "initialize" method we really have something more interesting:
initialize
super initialize.
tframe := (TLoad3DSMax new initializeWithFileName:
(FileDirectory pathFrom:
{FileDirectory default pathName. 'Content'. 'legoicMODE'. 'legoicMODE.ase'})
scale: 0.1 shadeAngle: 90.1 textureMode: GLReplace) frame.
tframe collapse.
tframe boundsDepth: 3.
tframe initBounds.
self addChild: tframe.
tframe objectOwner: self.
body := ODEBody new.
body position: self globalPosition.
body massSphere: 216. "6x6x6"
self body:body.
geom := ODEBox new extent: 6@6@6 .
geom body: self body.
inControl := false.
CODE COMMENTS:
This is all for the Class that defines the "legoic".
The name of the main Class of the exercice will be: "MyODE". It's a subClass of TeapotMorph:
TeapotMorph subclass: #MyODE instanceVariableNames: 'tframe cworld sceneObjects floor world space contacts prevTime rnd legMODE ' classVariableNames: '' poolDictionaries: 'OpenGLConstants ' category: 'MyCroquet'
We have many instanceVariables to be used...
The usual initializeDefaultSpace will be:
initializeDefaultSpace
cworld := TSpace new.
self makeLight: cworld.
floor := self makeFloor: cworld fileName: 'lawn.BMP' .
floor translationX: 0 y: -3 z: 0.
prevTime := Time millisecondClockValue.
world := ODEWorld new.
space := ODESpace new.
world add: (contacts := ODEContactGroup new).
world gravity: #(0 -30 0 ).
sceneObjects := Dictionary new.
rnd : Random new.
space add: (ODEPlane new planeA: 1 b:0 c:0 d:-40).
space add: (ODEPlane new planeA: 0 b:0 c:1 d:-40).
space add: (ODEPlane new planeA:-1 b:0 c:0 d:-40).
space add: (ODEPlane new planeA: 0 b:0 c:-1 d:-40).
space add: (ODEPlane new planeA: 0 b:1 c:0 d: (floor globalPosition y + (floor extent y / 2))).
"Adding the pseudo avatar-kicker"
legMODE := LegoicMODE new.
legMODE translationX: 0 y: -2 z: 0.
cworld addChild: legMODE.
world add: (legMODE body).
space add: (legMODE geom).
sceneObjects at: legMODE body id put: {legMODE. legMODE body. legMODE geom}.
self addCube. "required!!!!"
self transferCam: legMODE.
^ cworld
CODE COMMENTS
You need to add the method: transferCam to the Class. We will not talk about it here. Only use it.:
transferCam: object | trans | object ifNotNil:[ self activeCamera parent = object ifTrue:[ trans := (self activeCamera) globalTransform. (self activeCamera) transferTo: (object parent). (self activeCamera) localTransform: trans.] ifFalse:[ trans := object globalTransform orthoNormInverse composeWith: (self activeCamera) globalTransform. self activeCamera transferTo: object. self activeCamera localTransform: trans. self activeCamera translationX: 0 y: 0 z:16 . self activeCamera rotationAroundY: 0. ] ]
We will use here also a TimeStep loop. We will have two methods:
wantsSteps ^true.
And the TimeStep:
stepAt: currentTime | dur | super step. space ifNil: [^self]. world connect. space connect. dur := ((currentTime asFloat) - (prevTime asFloat)) / 1000. prevTime := currentTime. sceneObjects do:[:id| | each body | each := id at:1. body := id at:2. each isStepping not ifTrue:[ each localTransform: body transform transposed. ] ifFalse:[ body linearVel: each globalPosition - (body position) * 10. body position: each globalPosition. body quaternion: (each quaternion) ] ]. space collideDo: [:geom1 :geom2 | contacts addContact: geom1 with: geom2 bounce: 0.03 bounceVel: 0.2 softCFM: 0.000000001.]. world quickStep: dur. contacts empty.
CODE COMMENTS
And we have an important method that will define how the "legoic" moves in the space. We are using for him the "DOOM paradigma" and you will use the "arrow keys" to control their movements. The method is:
keyStroke: anEvent | kv kc bb | kc := anEvent keyCharacter asLowercase. kv := anEvent keyValue. bb:= legMODE . "The codes for arrow keys are: 28, 29, 30, 31" kv = 30 ifTrue:[ bb translation: bb translation+( bb localTransform column3*-0.5). bb body position: bb globalPosition. ]. kv = 31 ifTrue:[ bb translation: bb translation+( bb localTransform column3*0.5). bb body position:bb globalPosition. ]. kv = 28 ifTrue:[ bb addRotationAroundY:2. bb body quaternion: bb quaternion. ]. kv = 29 ifTrue:[ bb addRotationAroundY:-2. bb body quaternion: bb quaternion. ]. "To create 3 balls, press P" kc = $p ifTrue:[ self addBall2. self addBall2. self addBall2. ]. (#(28 29 30 31) includes: kv) ifTrue: [^self]. "To change the camera legoic/avatar use Esc key" kv = 27 ifTrue:[ ^self transferCam:bb]. super keyStroke: anEvent.
CODE COMMENTS
For the creation of balls the method (also from the ODECo demo) is:
addBall2
| body geom textures ball2 |
textures := #('BasketballColor.jpg' 'TennisBall.jpg' 'Softball.jpg' 'BeachBall.jpg' ).
txtr := TTexture new initializeWithFileName: (textures at: rnd next * textures size truncated + 1).
txtr scale: 14.
ball2 := TSphereODE new.
ball2 translationX: 0 y: 15 z: 0.
ball2 radius: 1.
ball2 texture: txtr.
ball2 segments: 30.
cworld addChild: ball2.
world add: (body := ODEBody new).
body position: ball2 globalPosition.
body massSphere: 1.
space add: (geom := ODESphere new radius: ball2 radius).
geom body: body.
ball2 body: body.
sceneObjects at: body id put: {ball2. body. geom}
CODE COMMENTS
Finally we need to create a Cube (required). The method is:
addCube
| body geom cube1 |
txtr := TTexture new initializeWithFileName: 'checker.bmp'.
txtr scale: 13.
cube1 := TCubeODE new.
cube1 translationX: 20 y: 3 z: 10.
cube1 extent: 4 @ 4 @ 4.
cube1 texture: txtr.
cworld addChild: cube1.
world add: (body := ODEBody new).
body position: cube1 globalPosition.
body massSphere: 5.
space add: (geom := ODEBox new extent: cube1 extent).
geom body: body.
cube1 body: body.
sceneObjects at: body id put: {cube1. body. geom}.
CODE COMMENTS
To run our exercice you need an "script for Workspace". We are not using the Private area - it's only an exercice - but it would be the adequate area.:
oD:= MyODE new. win2D:= SystemWindow new. lf:= LayoutFrame new. lf leftFraction:0. lf rightFraction:1. lf topFraction:0. lf bottomFraction:1. win2D setLabel: 'ODE'. win2D addMorph: oD fullFrame:lf. win2D openInWorldExtent: 500@500. oD requestInitialSpace.
Look some figures of our exercice:
PREVIOUS LESSON NEXT LESSON T. CONTENTS HOMEPAGE
