| 1 | # PySoy's scenes.Scene class |
|---|
| 2 | # |
|---|
| 3 | # Copyright (C) 2006,2007,2008 PySoy Group |
|---|
| 4 | # |
|---|
| 5 | # This program is free software; you can redistribute it and/or modify |
|---|
| 6 | # it under the terms of the GNU Affero General Public License as published |
|---|
| 7 | # by the Free Software Foundation, either version 3 of the License, or |
|---|
| 8 | # (at your option) any later version. |
|---|
| 9 | # |
|---|
| 10 | # This program is distributed in the hope that it will be useful, |
|---|
| 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|---|
| 13 | # GNU Affero General Public License for more details. |
|---|
| 14 | # |
|---|
| 15 | # You should have received a copy of the GNU Affero General Public License |
|---|
| 16 | # along with this program; if not, see http://www.gnu.org/licenses |
|---|
| 17 | # |
|---|
| 18 | # $Id$ |
|---|
| 19 | DEF GeomScene = 1 |
|---|
| 20 | DEF GeomBody = 2 |
|---|
| 21 | DEF GeomField = 4 |
|---|
| 22 | DEF GeomLight = 8 |
|---|
| 23 | DEF GeomGhost = 16 |
|---|
| 24 | cdef class Scene (soy._internals.Loopable) : |
|---|
| 25 | '''PySoy Scene |
|---|
| 26 | |
|---|
| 27 | Scenes are containers in physics space for bodies, joints, and shapes. |
|---|
| 28 | Objects in different worlds cannot interact, for example, bodies in two |
|---|
| 29 | different worlds cannot collide. Worlds also apply gravity to bodies. |
|---|
| 30 | ''' |
|---|
| 31 | |
|---|
| 32 | ############################################################################ |
|---|
| 33 | # |
|---|
| 34 | # Python functions |
|---|
| 35 | # |
|---|
| 36 | |
|---|
| 37 | def __cinit__(self, *args, **kw) : |
|---|
| 38 | from soy.colors import gray |
|---|
| 39 | self._worldID = ode.dWorldCreate() |
|---|
| 40 | self._spaceID = ode.dSimpleSpaceCreate(NULL) |
|---|
| 41 | self._contactGroup = ode.dJointGroupCreate(0) |
|---|
| 42 | self._bodies = soy._internals.Children() |
|---|
| 43 | self._fields = soy._internals.Children() |
|---|
| 44 | self._joints = soy._internals.Children() |
|---|
| 45 | self._lights = soy._internals.Children() |
|---|
| 46 | self._ambient = gray |
|---|
| 47 | self._stepSize = 0.01 |
|---|
| 48 | self._friction = 0 |
|---|
| 49 | self._prevTime = _time() |
|---|
| 50 | self._stepMutex = py.PyThread_allocate_lock() |
|---|
| 51 | # a list cor keeping track of the fields in the scene. |
|---|
| 52 | self._giveFields = soy._internals.PointerSet() |
|---|
| 53 | # |
|---|
| 54 | self._callFields = soy._internals.PointerSet() |
|---|
| 55 | _scenes._append(<void*> self) |
|---|
| 56 | |
|---|
| 57 | |
|---|
| 58 | def __dealloc__(self) : |
|---|
| 59 | _scenes._remove(<void*> self) |
|---|
| 60 | if self._worldID != NULL : |
|---|
| 61 | ode.dWorldDestroy(self._worldID) |
|---|
| 62 | if self._spaceID != NULL : |
|---|
| 63 | ode.dSpaceDestroy(self._spaceID) |
|---|
| 64 | |
|---|
| 65 | |
|---|
| 66 | def __repr__(self) : |
|---|
| 67 | report = [] |
|---|
| 68 | |
|---|
| 69 | if self._bodies._current == 1 : |
|---|
| 70 | report.append('1 body') |
|---|
| 71 | else: |
|---|
| 72 | report.append('%d bodies' % self._bodies._current) |
|---|
| 73 | |
|---|
| 74 | if self._lights._current == 1 : |
|---|
| 75 | report.append('1 light') |
|---|
| 76 | else : |
|---|
| 77 | report.append('%d lights' % self._lights._current) |
|---|
| 78 | |
|---|
| 79 | return '<Scene with %s>' % ', '.join(report) |
|---|
| 80 | |
|---|
| 81 | ############################################################################ |
|---|
| 82 | # |
|---|
| 83 | # C functions |
|---|
| 84 | # |
|---|
| 85 | |
|---|
| 86 | cdef int _loop(self) nogil : |
|---|
| 87 | cdef int _i, _steps |
|---|
| 88 | self._time = _time() |
|---|
| 89 | self._callFields._empty() |
|---|
| 90 | self._giveFields._empty() |
|---|
| 91 | # |
|---|
| 92 | self._fields._iterStart() |
|---|
| 93 | for _i from 0 <= _i < self._fields._current : |
|---|
| 94 | # Make sure every field is in givefields & _give each one |
|---|
| 95 | if not self._giveFields._has_key(self._fields._list[_i]) : |
|---|
| 96 | (<soy.fields.Field> self._fields._list[_i])._give(0) |
|---|
| 97 | self._giveFields._insert(self._fields._list[_i]) |
|---|
| 98 | # |
|---|
| 99 | for _i from 0 <= _i < self._fields._current : |
|---|
| 100 | # Apply fields; add incompletly applied fields to the list |
|---|
| 101 | if not (<soy.fields.Field> self._fields._list[_i])._apply() : |
|---|
| 102 | pass |
|---|
| 103 | #self._callFields._insert(self._fields.list[_i]) |
|---|
| 104 | # |
|---|
| 105 | # Apply any outstanding fields |
|---|
| 106 | self._callFields._foreach(_runField, NULL) |
|---|
| 107 | self._fields._iterDone() |
|---|
| 108 | # |
|---|
| 109 | self._stepLock() |
|---|
| 110 | _steps = self._steps() |
|---|
| 111 | for _i from 0 <= _i < _steps : |
|---|
| 112 | self._time = _time() |
|---|
| 113 | self._giveFields._foreach(_prerunField, NULL) |
|---|
| 114 | self._callFields._empty() |
|---|
| 115 | ode.dSpaceCollide(self._spaceID, <void*> self, |
|---|
| 116 | <void(*)(void*, ode.dGeomID, ode.dGeomID) nogil> |
|---|
| 117 | Scene._callback) |
|---|
| 118 | ode.dWorldQuickStep(self._worldID, self._stepSize) |
|---|
| 119 | ode.dJointGroupEmpty(self._contactGroup) |
|---|
| 120 | if _i != 0 : |
|---|
| 121 | self._callFields._foreach(_runField, NULL) |
|---|
| 122 | self._stepUnLock() |
|---|
| 123 | return 1 |
|---|
| 124 | |
|---|
| 125 | |
|---|
| 126 | cdef void _render(self) : |
|---|
| 127 | cdef int _i |
|---|
| 128 | # |
|---|
| 129 | # Setup scene-level rendering |
|---|
| 130 | gl.glClear(gl.GL_DEPTH_BUFFER_BIT) |
|---|
| 131 | gl.glEnable(gl.GL_DEPTH_TEST) |
|---|
| 132 | gl.glEnable(gl.GL_LIGHTING) |
|---|
| 133 | gl.glLightModelfv(gl.GL_LIGHT_MODEL_AMBIENT, self._ambient._rgba) |
|---|
| 134 | # |
|---|
| 135 | # Turn on each light, keep it's iteration locked against mod until done |
|---|
| 136 | self._lights._iterStart() |
|---|
| 137 | for _i from 0 <= _i < self._lights._current : |
|---|
| 138 | # This is a quick hack (gl.GL_LIGHT0 + _i) |
|---|
| 139 | (<soy.bodies.Light> self._lights._list[_i])._on(gl.GL_LIGHT0 + _i) |
|---|
| 140 | # |
|---|
| 141 | # Iterate over bodies |
|---|
| 142 | self._bodies._iterStart() |
|---|
| 143 | for _i from 0 <= _i < self._bodies._current : |
|---|
| 144 | if (<soy.bodies.Body> self._bodies._list[_i])._model is not None : |
|---|
| 145 | (<soy.models.Model> |
|---|
| 146 | (<soy.bodies.Body> self._bodies._list[_i])._model)._render( |
|---|
| 147 | (<soy.bodies.Body> self._bodies._list[_i])) |
|---|
| 148 | self._bodies._iterDone() |
|---|
| 149 | # |
|---|
| 150 | # Turn off all lights and finish iteration loop |
|---|
| 151 | for _i from 0 <= _i < self._lights._current : |
|---|
| 152 | # This is a quick hack (gl.GL_LIGHT0 + _i) |
|---|
| 153 | (<soy.bodies.Light> self._lights._list[_i])._off(gl.GL_LIGHT0 + _i) |
|---|
| 154 | self._lights._iterDone() |
|---|
| 155 | gl.glDisable(gl.GL_LIGHTING) |
|---|
| 156 | gl.glDisable(gl.GL_DEPTH_TEST) |
|---|
| 157 | |
|---|
| 158 | |
|---|
| 159 | cdef int _steps(self) nogil : |
|---|
| 160 | cdef int _step |
|---|
| 161 | cdef double _lapsTime |
|---|
| 162 | _lapsTime = _time() - self._prevTime |
|---|
| 163 | if _lapsTime < ode.dEpsilon : |
|---|
| 164 | _lapsTime = ode.dEpsilon |
|---|
| 165 | _step = math.lround(_lapsTime / self._stepSize) |
|---|
| 166 | if _step > 12 : |
|---|
| 167 | _step = 12 |
|---|
| 168 | self._prevTime = self._prevTime + (_step * self._stepSize) |
|---|
| 169 | return _step |
|---|
| 170 | |
|---|
| 171 | |
|---|
| 172 | cdef void _stepLock(self) nogil : |
|---|
| 173 | py.PyThread_acquire_lock(self._stepMutex,1) |
|---|
| 174 | |
|---|
| 175 | |
|---|
| 176 | cdef int _stepTryLock(self) nogil : |
|---|
| 177 | return py.PyThread_acquire_lock(self._stepMutex,0) |
|---|
| 178 | |
|---|
| 179 | |
|---|
| 180 | cdef void _stepUnLock(self) nogil : |
|---|
| 181 | py.PyThread_release_lock(self._stepMutex) |
|---|
| 182 | |
|---|
| 183 | |
|---|
| 184 | cdef void _callback(self, ode.dGeomID _geomA, ode.dGeomID _geomB) nogil : |
|---|
| 185 | cdef int _contactGeoms, _f, _i |
|---|
| 186 | cdef ode.dJointID _joint |
|---|
| 187 | cdef ode.dContact _contact |
|---|
| 188 | cdef ode.dContactGeom _contactGeom[2] |
|---|
| 189 | cdef ode.dGeomID _geomIDs[2] |
|---|
| 190 | cdef ode.dBodyID _bodyIDs[2] |
|---|
| 191 | |
|---|
| 192 | # |
|---|
| 193 | # Start by generating up to four contacts between 2 bodies |
|---|
| 194 | _contactGeoms = ode.dCollide(_geomA, _geomB, 2, |
|---|
| 195 | _contactGeom, sizeof(_contactGeom[0])) |
|---|
| 196 | # |
|---|
| 197 | # Don't waste time when there's no collision |
|---|
| 198 | if _contactGeoms == 0 : |
|---|
| 199 | return |
|---|
| 200 | # |
|---|
| 201 | # This makes case-testing much simpler in the code |
|---|
| 202 | _geomIDs[0] = _geomA |
|---|
| 203 | _geomIDs[1] = _geomB |
|---|
| 204 | # |
|---|
| 205 | # Scene Check |
|---|
| 206 | for _i from 0 <= _i < 2 : |
|---|
| 207 | if ode.dGeomGetCategoryBits(_geomIDs[_i]) == GeomScene : |
|---|
| 208 | _bodyIDs[_i] = NULL |
|---|
| 209 | #stdio.printf('Scene Collision %d\n', _i) |
|---|
| 210 | else : |
|---|
| 211 | _bodyIDs[_i] = ode.dGeomGetBody(_geomIDs[_i]) |
|---|
| 212 | #stdio.printf('Body Collision %d %d\n', _i, _bodyIDs[_i]) |
|---|
| 213 | # |
|---|
| 214 | # Ghost Check |
|---|
| 215 | for _i from 0 <= _i < 2 : |
|---|
| 216 | if ode.dGeomGetCategoryBits(_geomIDs[_i]) == GeomGhost : |
|---|
| 217 | #stdio.printf('Ghost Collision\n') |
|---|
| 218 | _bodyIDs[(_i + 1) % 2] = NULL |
|---|
| 219 | # |
|---|
| 220 | # If we ended up with a null-null collision, just return now |
|---|
| 221 | if _bodyIDs[0] == NULL and _bodyIDs[1] == NULL : |
|---|
| 222 | #stdio.printf('Both bodies null, aborting\n') |
|---|
| 223 | return |
|---|
| 224 | # |
|---|
| 225 | # Add each joint to the bodies we just determined |
|---|
| 226 | for _i from 0 <= _i < _contactGeoms : |
|---|
| 227 | _contact.geom = _contactGeom[_i] |
|---|
| 228 | _contact.surface.mode = ode.dContactBounce |
|---|
| 229 | _contact.surface.mu = self._friction |
|---|
| 230 | _contact.surface.bounce = 0.8 |
|---|
| 231 | _contact.surface.bounce_vel = 0 |
|---|
| 232 | #stdio.printf('Creating joint\n') |
|---|
| 233 | _joint = ode.dJointCreateContact(self._worldID, self._contactGroup, |
|---|
| 234 | &_contact) |
|---|
| 235 | #stdio.printf('Attaching joint\n') |
|---|
| 236 | ode.dJointAttach(_joint, _bodyIDs[0], _bodyIDs[1]) |
|---|
| 237 | #stdio.printf('Processed contact joint %d\n', _i) |
|---|
| 238 | return |
|---|
| 239 | |
|---|
| 240 | ''' |
|---|
| 241 | # This is the old code, mostly for fields. It needs to be merged into |
|---|
| 242 | # the above but for now we're just testing Scene and Ghosts |
|---|
| 243 | if _c > 0 : |
|---|
| 244 | # Get the Body objects |
|---|
| 245 | _bo1 = <soy.bodies.Body> ode.dBodyGetData(_b1) |
|---|
| 246 | _bo2 = <soy.bodies.Body> ode.dBodyGetData(_b2) |
|---|
| 247 | _f = 0 |
|---|
| 248 | # activate any fields present... |
|---|
| 249 | if isinstance(_bo1, soy.fields.Field) : |
|---|
| 250 | _f = 1 |
|---|
| 251 | if not (<soy.fields.Field> _bo1)._exert(_bo2) : |
|---|
| 252 | self._callFields._insert(<void*> _bo1) |
|---|
| 253 | if isinstance(_bo2, soy.fields.Field) : |
|---|
| 254 | _f = 1 |
|---|
| 255 | if not (<soy.fields.Field> _bo2)._exert(_bo1) : |
|---|
| 256 | self._callFields._insert(<void*> _bo2) |
|---|
| 257 | # if neither shape is a field |
|---|
| 258 | if _f == 0 : |
|---|
| 259 | _ct.geom = _cg |
|---|
| 260 | # surface parameters should have an exposed interface on soy.shapes.Shape |
|---|
| 261 | # this code should pull parameters from there. |
|---|
| 262 | _ct.surface.mode = ode.dContactBounce |
|---|
| 263 | _ct.surface.bounce = 0.8 |
|---|
| 264 | _ct.surface.bounce_vel = 0 |
|---|
| 265 | _j = ode.dJointCreateContact(self._worldID, self._contactGroup, &_ct) |
|---|
| 266 | ode.dJointAttach(_j, _b1, _b2) |
|---|
| 267 | ''' |
|---|
| 268 | |
|---|
| 269 | ############################################################################ |
|---|
| 270 | # |
|---|
| 271 | # Properties |
|---|
| 272 | # |
|---|
| 273 | |
|---|
| 274 | property gravity : |
|---|
| 275 | '''Scene gravity |
|---|
| 276 | |
|---|
| 277 | This is a scene-wide pseudo-force drawing all bodies in a single |
|---|
| 278 | direction. This should not be confused with a monopole force or other |
|---|
| 279 | forces which are often used for gravity in larger scenes. |
|---|
| 280 | |
|---|
| 281 | Takes a (x,y,z) tuple of numbers, defaults to (0.0, 0.0, 0.0) |
|---|
| 282 | ''' |
|---|
| 283 | def __get__(self) : |
|---|
| 284 | cdef ode.dVector3 grav |
|---|
| 285 | ode.dWorldGetGravity(self._worldID, grav) |
|---|
| 286 | return (grav[0], grav[1], grav[2]) |
|---|
| 287 | def __set__(self, value) : |
|---|
| 288 | ode.dWorldSetGravity(self._worldID, value[0], value[1], value[2]) |
|---|
| 289 | |
|---|
| 290 | |
|---|
| 291 | property stepsize : |
|---|
| 292 | '''Step Size |
|---|
| 293 | |
|---|
| 294 | This is the fraction of time each "step" calculates for. Larger steps |
|---|
| 295 | trade accuracy for speed. |
|---|
| 296 | |
|---|
| 297 | Takes a float, defaults to .01 |
|---|
| 298 | ''' |
|---|
| 299 | def __get__(self) : |
|---|
| 300 | return self._stepSize |
|---|
| 301 | def __set__(self, value) : |
|---|
| 302 | self._stepLock() |
|---|
| 303 | self._stepSize = value |
|---|
| 304 | self._stepUnLock() |
|---|
| 305 | |
|---|
| 306 | |
|---|
| 307 | property ambient : |
|---|
| 308 | '''Scene's ambient light |
|---|
| 309 | |
|---|
| 310 | This is the ambient light for the scene. When no light is used this is |
|---|
| 311 | the only light available in the scene - use it well. |
|---|
| 312 | |
|---|
| 313 | Defaults to soy.colors.gray |
|---|
| 314 | ''' |
|---|
| 315 | def __get__(self) : |
|---|
| 316 | return self._ambient |
|---|
| 317 | def __set__(self, soy.colors.Color value) : |
|---|
| 318 | self._ambient = value |
|---|
| 319 | |
|---|
| 320 | |
|---|
| 321 | property friction : |
|---|
| 322 | '''Scene's Friction |
|---|
| 323 | |
|---|
| 324 | This is the amount of friction given to ode's contact joint. |
|---|
| 325 | |
|---|
| 326 | Defaults to 0 ( Really Slippery ) |
|---|
| 327 | ''' |
|---|
| 328 | def __get__(self) : |
|---|
| 329 | return self._friction |
|---|
| 330 | def __set__(self, value) : |
|---|
| 331 | if isinstance(value, int): |
|---|
| 332 | self._friction = value |
|---|
| 333 | else: |
|---|
| 334 | raise TypeError("Friction must be an int of some sort") |
|---|