KICKABLE OR ANCHORED ODE PIECES - PART 1

We said that we have 3 types of pieces:

Considering that a "piece" is an object having a 3D representation, we have some other types of pieces in Croquet:

We have talked about these objects at the "Basic Tutorial for Beginners.

Until now we have used ODE pieces that are balls or cubes, models created by code. At this lesson we will see how to use the "traditional" .ASE files you have downloaded, for the creation of ODE pieces.

To kick balls is very funny but we need to have pieces (walls, by example) where the "false avatar" (can be a car) will collide against then and go back, not kick. So: we like to say that we have two types of ODE pieces: kickable and anchored - because they do not move from their positions.

Using the same .ASE file (and textures) we can create a "normal piece" or an "ODE piece" or a piece that is "both". It's what we will do here, now.

My sugestion is to use the name of the folder adding or not "ODE" to the name of the Class of the piece (by example: Wall12x7x1 or Wall12x7x1ODE).

For simplification we will not use ODEPlanes and "gravity" in the ODE world/space.

Imagine that we are going to make some ODE pieces for a new version of "Croq city". The first will be: WallDoor12x7x1ODE. The Class definition is:

TGroup subclass: #WallDoor12x7x1ODE
	instanceVariableNames: 'inControl tframe body geom pickDelta open door'
	classVariableNames: ''
	poolDictionaries: 'OpenGLConstants '
	category: 'MyCroquet'

It will have the methods: body, body:,geom, geom:, handlesPointerDown, isComponent, pick, pointerDown, pointerUp, step , that are the same for every ODE piece - like you can see in previous lesson.

And it needs, also, to have the methods of a "normal piece" : handlesKeyboard and keyDown.

The keyDown method is very important here because it will have the code for the interactive animation (open/close the door). It will also define the ultimate position of the wall:

keyDown: pointer

| c |
c := pointer event2D keyCharacter.
"To move X+" 
c = $w ifTrue:[ 
 tframe translation:  tframe translation+( tframe localTransform column1*0.5).			 
 inControl := true.
].
"To move X-" 
c = $s ifTrue:[  
  tframe translation:  tframe translation-( tframe localTransform column1*0.5).	
 inControl := true.
].
"To turn left"
c = $a ifTrue:[  
  tframe addRotationAroundY:-90.
 inControl := true.
].
"To turn right"
c = $d ifTrue:[  
   tframe addRotationAroundY: 90.			 
 inControl :=true.
].
"To go up:Y+"
c = $z ifTrue:[ 
 tframe translation:  tframe translation+( tframe localTransform column2*0.5).		 
 inControl := true.
].
"To go down (oops!):Y-"
c = $x ifTrue:[  
 tframe translation:  tframe translation-( tframe localTransform column2*0.5).		 
inControl := true.
].
"To move Z+"
c = $c ifTrue:[  
  tframe translation: tframe translation+( tframe localTransform column3*0.5).		 
 inControl:= true.
].
"To move Z-"
c = $v ifTrue:[ "Z-" 
  tframe translation:  tframe translation-( tframe localTransform column3*0.5).		 
 inControl := true.
].

"To open/cloce door"
c = $p ifTrue:[
  door := (tframe find:[:frm | frm objectName = 'door']) at: 1. 
  open=false ifTrue:[
   door translation: door translation+(door localTransform column1*5).
   open:=true.
  ]
  ifFalse:[	 
  door translation: door translation-(door localTransform column1*5).
  open:=false.
  ].
  inControl := true.
].

c = $l ifTrue:[ "Localization" 
 Transcript show: tframe globalPosition.	 
 inControl := true.
]. 

The "initialize" method is the same we have seeing, for the "normal piece" in previous lesson. :

initialize

super initialize.
open:=false.

tframe := (TLoad3DSMax new initializeWithFileName: 
	   (FileDirectory pathFrom:
           {FileDirectory default pathName.  'Content'.   'wallDoor12x7x1'. 'wallDoor12x7x1.ase'}) 
	   scale: 0.1 shadeAngle: 90.1 textureMode: GLReplace) frame.
tframe collapse.
tframe boundsDepth: 3.
tframe initBounds.
self addChild: tframe.
tframe objectOwner: self.
"Finding door"
door := (tframe find:[:frm | frm objectName = 'door']) at: 1. 
inControl := false. 

So, until now, nothing very different from the "normal piece".

But each "ODE piece" has an "add-method" in the Class Private!

Here, we will need to create the method: addWallDoor12x7x1a.

But, before, we need to change the definition of the Class Private to have some new instance variables:

TeapotMorph subclass: #Private
 instanceVariableNames: 'tframe cworld sceneObjects floor world space contacts prevTime rnd wD12x7x1a wD12x7x1abody ahead bC '
 classVariableNames: ''
 poolDictionaries: 'OpenGLConstants '
 category: 'MyCroquet'

The specific instance variables added for our new piece are: wD12x7x1a and wD12x7x1abody. This means that: if you create another piece you will need to add 2 variables more.

The "add-method" will be:

addWallDoor12x7x1a

| geom |
wD12x7x1a := WallDoor12x7x1ODE new.
cworld addChild: wD12x7x1a.
world add: (wD12x7x1abody := ODEBody new).
wD12x7x1abody  position:10.0@0.5@-20.0.
wD12x7x1abody rotationAroundZ:90. "really is aroundY"
wD12x7x1abody  massSphere: 80. 
space add: (geom := ODEBox new extent: 12@7@1).
geom body: wD12x7x1abody .
sceneObjects at: wD12x7x1abody  id put: {wD12x7x1a . wD12x7x1abody . geom} 

CODE COMMENTS

The Private Class needs to have some other methods for an ODE piece.

The addCube, wantsSteps, transferCam methods are the same of that previous lesson.

The initializeDefaultSpace method is:

initializeDefaultSpace
	 
| base |
	
cworld := TSpace new.
"Make a light"
self makeLight: cworld.
"The floor of Croq city"	 
base := BasePlateA70x3x70 new.
base translationX: 0 y: -6.3 z: 0.

cworld addChild: base.
prevTime := Time millisecondClockValue.
world := ODEWorld new.
space := ODESpace new.
world add: (contacts := ODEContactGroup new).
sceneObjects := Dictionary new.
rnd := Random new.
	
"The false-avatar or car"
bC := BaseCar10x2x8ODE  new.
bC translationX: 0 y: -2  z: 0.
bC addRotationAroundY:180.	 
cworld addChild: bC.
world add: ( bC body).
space add: (bC geom).
sceneObjects at: bC body id put: {bC. bC body.  bC geom}.
   
self addCube.  "required"
self transferCam: bC.
^ cworld 

CODE COMMENTS

The method keyStroke:

keyStroke: anEvent

| kv kc |
kc := anEvent keyCharacter asLowercase.
kv := anEvent keyValue.

kv = 30 ifTrue:[ 
 ahead:=true.
 bC   translation: bC translation+( bC localTransform column3*-0.5).
 bC body position: bC globalPosition.  
]. 
 
kv = 31 ifTrue:[ 
 ahead:=false.
 bC   translation: bC translation+( bC localTransform column3*0.5).   
 bC body position:bC globalPosition. 
]. 

kv = 28 ifTrue:[ 
 bC addRotationAroundY:2.
 bC body quaternion: bC quaternion.
].    
  
kv = 29 ifTrue:[ 
 bC addRotationAroundY:-2.
 bC body quaternion: bC quaternion.
].

kv = 5 ifTrue:[ "Pressing Insert"
 self addWall12x7x1a.
].

(#(28 29 30 31) includes: kv) ifTrue: [^self]. 
kv = 27 ifTrue:[ "Pressing Esc"
 ^self transferCam:bC
].
	
super keyStroke: anEvent.

CODE COMMENTS

The "big trick" for ODE pieces is at this method defining a TimeStep loop:

stepAt: currentTime

| dur  nCollision |
super  step.
  
wD12x7x1a ifNotNil:[
  wD12x7x1abody  position:10.0@0.50@-20.0.
  wD12x7x1abody rotationAroundZ:90. 
].	
 
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 |  
   nCollision := contacts
   addContact: geom1
   with: geom2
   bounce: 0.1 
   bounceVel: 0.1
   softCFM: 0.1.

  nCollision > 0 ifTrue:[
    wD12x7x1a ifNotNil:[  
     (ahead=true) ifTrue:[
       bC  translation:bC translation+( bC localTransform column3*5). 
     ]
     ifFalse:[
      bC  translation: bC translation-( bC localTransform column3*5). ]	 
     ]
    ].
   ].

world quickStep: dur.  
contacts empty.

CODE COMMENTS

VERY IMPORTANT: When you move the piece using WASDZXCV keys, the "body" does not moves together. You need to find the ultimate position, change the code and reload the piece. OR THE CAR WILL NOT COLLIDE OK - because it collides against the "body".

Look some figures of our exercice:




PREVIOUS LESSON NEXT LESSON
T. CONTENTS HOMEPAGE

DISCLAIMER: This material can be translated for any language, and reproduced total or partially by anybody using any type of media. But, please, don't put your name like author. And let me know if it was useful (americo@dmu.com).The use of any code, 3D model or technique published is free and doesn't need to have any reference about this source.The author is not responsible for any damage that the material can cause to your professional or sexual life.