# PySoy's scenes.Scene class # # Copyright (C) 2006,2007,2008 PySoy Group # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program; if not, see http://www.gnu.org/licenses # # $Id$ DEF GeomScene = 1 DEF GeomBody = 2 DEF GeomField = 4 DEF GeomLight = 8 DEF GeomGhost = 16 cdef class Scene (soy._internals.Loopable) : '''PySoy Scene Scenes are containers in physics space for bodies, joints, and shapes. Objects in different worlds cannot interact, for example, bodies in two different worlds cannot collide. Worlds also apply gravity to bodies. ''' ############################################################################ # # Python functions # def __cinit__(self, *args, **kw) : from soy.colors import gray self._worldID = ode.dWorldCreate() self._spaceID = ode.dSimpleSpaceCreate(NULL) self._contactGroup = ode.dJointGroupCreate(0) self._bodies = soy._internals.Children() self._fields = soy._internals.Children() self._joints = soy._internals.Children() self._lights = soy._internals.Children() self._ambient = gray self._stepSize = 0.01 self._friction = 0 self._prevTime = _time() self._stepMutex = py.PyThread_allocate_lock() # a list cor keeping track of the fields in the scene. self._giveFields = soy._internals.PointerSet() # self._callFields = soy._internals.PointerSet() _scenes._append( self) def __dealloc__(self) : _scenes._remove( self) if self._worldID != NULL : ode.dWorldDestroy(self._worldID) if self._spaceID != NULL : ode.dSpaceDestroy(self._spaceID) def __repr__(self) : report = [] if self._bodies._current == 1 : report.append('1 body') else: report.append('%d bodies' % self._bodies._current) if self._lights._current == 1 : report.append('1 light') else : report.append('%d lights' % self._lights._current) return '' % ', '.join(report) ############################################################################ # # C functions # cdef int _loop(self) nogil : cdef int _i, _steps self._time = _time() self._callFields._empty() self._giveFields._empty() # self._fields._iterStart() for _i from 0 <= _i < self._fields._current : # Make sure every field is in givefields & _give each one if not self._giveFields._has_key(self._fields._list[_i]) : ( self._fields._list[_i])._give(0) self._giveFields._insert(self._fields._list[_i]) # for _i from 0 <= _i < self._fields._current : # Apply fields; add incompletly applied fields to the list if not ( self._fields._list[_i])._apply() : pass #self._callFields._insert(self._fields.list[_i]) # # Apply any outstanding fields self._callFields._foreach(_runField, NULL) self._fields._iterDone() # self._stepLock() _steps = self._steps() for _i from 0 <= _i < _steps : self._time = _time() self._giveFields._foreach(_prerunField, NULL) self._callFields._empty() ode.dSpaceCollide(self._spaceID, self, Scene._callback) ode.dWorldQuickStep(self._worldID, self._stepSize) ode.dJointGroupEmpty(self._contactGroup) if _i != 0 : self._callFields._foreach(_runField, NULL) self._stepUnLock() return 1 cdef void _render(self) : cdef int _i # # Setup scene-level rendering gl.glClear(gl.GL_DEPTH_BUFFER_BIT) gl.glEnable(gl.GL_DEPTH_TEST) gl.glEnable(gl.GL_LIGHTING) gl.glLightModelfv(gl.GL_LIGHT_MODEL_AMBIENT, self._ambient._rgba) # # Turn on each light, keep it's iteration locked against mod until done self._lights._iterStart() for _i from 0 <= _i < self._lights._current : # This is a quick hack (gl.GL_LIGHT0 + _i) ( self._lights._list[_i])._on(gl.GL_LIGHT0 + _i) # # Iterate over bodies self._bodies._iterStart() for _i from 0 <= _i < self._bodies._current : if ( self._bodies._list[_i])._model is not None : ( ( self._bodies._list[_i])._model)._render( ( self._bodies._list[_i])) self._bodies._iterDone() # # Turn off all lights and finish iteration loop for _i from 0 <= _i < self._lights._current : # This is a quick hack (gl.GL_LIGHT0 + _i) ( self._lights._list[_i])._off(gl.GL_LIGHT0 + _i) self._lights._iterDone() gl.glDisable(gl.GL_LIGHTING) gl.glDisable(gl.GL_DEPTH_TEST) cdef int _steps(self) nogil : cdef int _step cdef double _lapsTime _lapsTime = _time() - self._prevTime if _lapsTime < ode.dEpsilon : _lapsTime = ode.dEpsilon _step = math.lround(_lapsTime / self._stepSize) if _step > 12 : _step = 12 self._prevTime = self._prevTime + (_step * self._stepSize) return _step cdef void _stepLock(self) nogil : py.PyThread_acquire_lock(self._stepMutex,1) cdef int _stepTryLock(self) nogil : return py.PyThread_acquire_lock(self._stepMutex,0) cdef void _stepUnLock(self) nogil : py.PyThread_release_lock(self._stepMutex) cdef void _callback(self, ode.dGeomID _geomA, ode.dGeomID _geomB) nogil : cdef int _contactGeoms, _f, _i cdef ode.dJointID _joint cdef ode.dContact _contact cdef ode.dContactGeom _contactGeom[2] cdef ode.dGeomID _geomIDs[2] cdef ode.dBodyID _bodyIDs[2] # # Start by generating up to four contacts between 2 bodies _contactGeoms = ode.dCollide(_geomA, _geomB, 2, _contactGeom, sizeof(_contactGeom[0])) # # Don't waste time when there's no collision if _contactGeoms == 0 : return # # This makes case-testing much simpler in the code _geomIDs[0] = _geomA _geomIDs[1] = _geomB # # Scene Check for _i from 0 <= _i < 2 : if ode.dGeomGetCategoryBits(_geomIDs[_i]) == GeomScene : _bodyIDs[_i] = NULL #stdio.printf('Scene Collision %d\n', _i) else : _bodyIDs[_i] = ode.dGeomGetBody(_geomIDs[_i]) #stdio.printf('Body Collision %d %d\n', _i, _bodyIDs[_i]) # # Ghost Check for _i from 0 <= _i < 2 : if ode.dGeomGetCategoryBits(_geomIDs[_i]) == GeomGhost : #stdio.printf('Ghost Collision\n') _bodyIDs[(_i + 1) % 2] = NULL # # If we ended up with a null-null collision, just return now if _bodyIDs[0] == NULL and _bodyIDs[1] == NULL : #stdio.printf('Both bodies null, aborting\n') return # # Add each joint to the bodies we just determined for _i from 0 <= _i < _contactGeoms : _contact.geom = _contactGeom[_i] _contact.surface.mode = ode.dContactBounce _contact.surface.mu = self._friction _contact.surface.bounce = 0.8 _contact.surface.bounce_vel = 0 #stdio.printf('Creating joint\n') _joint = ode.dJointCreateContact(self._worldID, self._contactGroup, &_contact) #stdio.printf('Attaching joint\n') ode.dJointAttach(_joint, _bodyIDs[0], _bodyIDs[1]) #stdio.printf('Processed contact joint %d\n', _i) return ''' # This is the old code, mostly for fields. It needs to be merged into # the above but for now we're just testing Scene and Ghosts if _c > 0 : # Get the Body objects _bo1 = ode.dBodyGetData(_b1) _bo2 = ode.dBodyGetData(_b2) _f = 0 # activate any fields present... if isinstance(_bo1, soy.fields.Field) : _f = 1 if not ( _bo1)._exert(_bo2) : self._callFields._insert( _bo1) if isinstance(_bo2, soy.fields.Field) : _f = 1 if not ( _bo2)._exert(_bo1) : self._callFields._insert( _bo2) # if neither shape is a field if _f == 0 : _ct.geom = _cg # surface parameters should have an exposed interface on soy.shapes.Shape # this code should pull parameters from there. _ct.surface.mode = ode.dContactBounce _ct.surface.bounce = 0.8 _ct.surface.bounce_vel = 0 _j = ode.dJointCreateContact(self._worldID, self._contactGroup, &_ct) ode.dJointAttach(_j, _b1, _b2) ''' ############################################################################ # # Properties # property gravity : '''Scene gravity This is a scene-wide pseudo-force drawing all bodies in a single direction. This should not be confused with a monopole force or other forces which are often used for gravity in larger scenes. Takes a (x,y,z) tuple of numbers, defaults to (0.0, 0.0, 0.0) ''' def __get__(self) : cdef ode.dVector3 grav ode.dWorldGetGravity(self._worldID, grav) return (grav[0], grav[1], grav[2]) def __set__(self, value) : ode.dWorldSetGravity(self._worldID, value[0], value[1], value[2]) property stepsize : '''Step Size This is the fraction of time each "step" calculates for. Larger steps trade accuracy for speed. Takes a float, defaults to .01 ''' def __get__(self) : return self._stepSize def __set__(self, value) : self._stepLock() self._stepSize = value self._stepUnLock() property ambient : '''Scene's ambient light This is the ambient light for the scene. When no light is used this is the only light available in the scene - use it well. Defaults to soy.colors.gray ''' def __get__(self) : return self._ambient def __set__(self, soy.colors.Color value) : self._ambient = value property friction : '''Scene's Friction This is the amount of friction given to ode's contact joint. Defaults to 0 ( Really Slippery ) ''' def __get__(self) : return self._friction def __set__(self, value) : if isinstance(value, int): self._friction = value else: raise TypeError("Friction must be an int of some sort")