Skip to content

Commit c1d222a

Browse files
committed
Made it required to have mass > 0 on dynamic bodies when stepping #268
1 parent 72bbe3b commit c1d222a

File tree

4 files changed

+29
-15
lines changed

4 files changed

+29
-15
lines changed

CHANGELOG.rst

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Changelog
66
New feature: Vec2d supports bool to test if zero. (bool(Vec2d(2,3) == True) Note this is a breaking change.
77
Added Vec2d.length_squared, and depreacted Vec2d.get_length_sqrd()
88
Added Vec2d.get_distance_squared(), and deprecated Vec2d.get_dist_sqrd()
9+
A dynamic body must have non-zero mass when calling Space.step (either from Body.mass, or by setting mass or density on a Shape attached to the Body). Its not valid to set mass to 0 on a dynamic body attached to a space.
910
1011
Added default do_nothing and always_collide callback functions to the CollisionHandler, so that its clear how to reset and align with other callbacks.
1112
If in old code you did handler.begin = None, you should now instead to handler.begin = CollisionHandler.always_collide etc.

pymunk/body.py

+14-12
Original file line numberDiff line numberDiff line change
@@ -254,11 +254,18 @@ def __repr__(self) -> str:
254254

255255
@property
256256
def mass(self) -> float:
257-
"""Mass of the body."""
257+
"""Mass of the body.
258+
259+
Note that dynamic bodies must have mass > 0 if they are attached to a
260+
Space.
261+
"""
258262
return lib.cpBodyGetMass(self._body)
259263

260264
@mass.setter
261265
def mass(self, mass: float) -> None:
266+
assert (
267+
self._space is None or mass > 0
268+
), "Dynamic bodies must have mass > 0 if they are attached to a Space."
262269
lib.cpBodySetMass(self._body, mass)
263270

264271
@property
@@ -347,9 +354,9 @@ def angle(self) -> float:
347354
"""Rotation of the body in radians.
348355
349356
When changing the rotation you may also want to call
350-
:py:func:`Space.reindex_shapes_for_body` to update the collision
351-
detection information for the attached shapes if plan to make any
352-
queries against the space. A body rotates around its center of gravity,
357+
:py:func:`Space.reindex_shapes_for_body` to update the collision
358+
detection information for the attached shapes if plan to make any
359+
queries against the space. A body rotates around its center of gravity,
353360
not its position.
354361
355362
.. Note::
@@ -393,7 +400,7 @@ def rotation_vector(self) -> Vec2d:
393400
def space(self) -> Optional["Space"]:
394401
"""Get the :py:class:`Space` that the body has been added to (or
395402
None)."""
396-
assert hasattr(self, "_space"), (
403+
assert hasattr(self, "_space"), ( # TODO: When can this happen?
397404
"_space not set. This can mean there's a direct or indirect"
398405
" circular reference between the Body and the Space. Circular"
399406
" references are not supported when using pickle or copy and"
@@ -482,12 +489,7 @@ def _set_position_func(self, func: _PositionFunc) -> None:
482489
@property
483490
def kinetic_energy(self) -> float:
484491
"""Get the kinetic energy of a body."""
485-
# todo: use ffi method
486-
# return lib._cpBodyKineticEnergy(self._body)
487-
488-
vsq: float = self.velocity.dot(self.velocity)
489-
wsq: float = self.angular_velocity * self.angular_velocity
490-
return (vsq * self.mass if vsq else 0.0) + (wsq * self.moment if wsq else 0.0)
492+
return lib.cpBodyKineticEnergy(self._body)
491493

492494
@staticmethod
493495
def update_velocity(
@@ -598,7 +600,7 @@ def is_sleeping(self) -> bool:
598600

599601
@property
600602
def body_type(self) -> _BodyType:
601-
"""The type of a body (:py:const:`Body.DYNAMIC`,
603+
"""The type of a body (:py:const:`Body.DYNAMIC`,
602604
:py:const:`Body.KINEMATIC` or :py:const:`Body.STATIC`).
603605
604606
When changing an body to a dynamic body, the mass and moment of

pymunk/space.py

+11
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ def spacefree(cp_space: ffi.CData) -> None:
143143

144144
self._add_later: Set[_AddableObjects] = set()
145145
self._remove_later: Set[_AddableObjects] = set()
146+
self._bodies_to_check: Set[Body] = set()
146147

147148
def _get_self(self) -> "Space":
148149
return self
@@ -409,6 +410,7 @@ def _add_body(self, body: "Body") -> None:
409410

410411
body._space = weakref.proxy(self)
411412
self._bodies[body] = None
413+
self._bodies_to_check.add(body)
412414
cp.cpSpaceAddBody(self._space, body._body)
413415

414416
def _add_constraint(self, constraint: "Constraint") -> None:
@@ -437,6 +439,8 @@ def _remove_body(self, body: "Body") -> None:
437439
"""Removes a body from the space"""
438440
assert body in self._bodies, "body not in space, already removed?"
439441
body._space = None
442+
if body in self._bodies_to_check:
443+
self._bodies_to_check.remove(body)
440444
# During GC at program exit sometimes the shape might already be removed. Then skip this step.
441445
if cp.cpSpaceContainsBody(self._space, body._body):
442446
cp.cpSpaceRemoveBody(self._space, body._body)
@@ -546,6 +550,13 @@ def step(self, dt: float) -> None:
546550
547551
:param dt: Time step length
548552
"""
553+
554+
for b in self._bodies_to_check:
555+
assert b.body_type != Body.DYNAMIC or (
556+
b.mass > 0 and b.mass < math.inf
557+
), f"Dynamic bodies must have a mass > 0 and < inf. {b} has mass {b.mass}."
558+
self._bodies_to_check.clear()
559+
549560
try:
550561
self._locked = True
551562
if self.threaded:

pymunk/tests/test_common.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -134,14 +134,14 @@ def remove_first(arbiter: p.Arbiter, space: p.Space, data: Any) -> None:
134134
def testX(self) -> None:
135135
space = p.Space()
136136

137-
b1 = p.Body()
137+
b1 = p.Body(1)
138138
c1 = p.Circle(b1, 10)
139139
c1.collision_type = 2
140140

141-
b2 = p.Body()
141+
b2 = p.Body(1)
142142
c2 = p.Circle(b2, 10)
143143

144-
b3 = p.Body()
144+
b3 = p.Body(1)
145145
c3 = p.Circle(b3, 10)
146146

147147
# b1.position = 0, 0

0 commit comments

Comments
 (0)