Skip to content

Commit 6dcebdb

Browse files
committed
Added .impulse_signed and .impulse_vector on constraints
1 parent a75f274 commit 6dcebdb

File tree

4 files changed

+334
-45
lines changed

4 files changed

+334
-45
lines changed

pymunk/constraints.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,10 @@ def distance(self) -> float:
357357
def distance(self, distance: float) -> None:
358358
lib.cpPinJointSetDist(self._constraint, distance)
359359

360+
@property
361+
def impulse_signed(self) -> float:
362+
return lib.cpPinJointGetImpulse(self._constraint)
363+
360364

361365
class SlideJoint(Constraint):
362366
"""SlideJoint is like a PinJoint, but have a minimum and maximum distance.
@@ -428,6 +432,14 @@ def max(self) -> float:
428432
def max(self, max: float) -> None:
429433
lib.cpSlideJointSetMax(self._constraint, max)
430434

435+
@property
436+
def impulse_signed(self) -> float:
437+
"""Currently not useful, will always be -joint.impulse
438+
439+
TODO: fix?
440+
"""
441+
return lib.cpSlideJointGetImpulse(self._constraint)
442+
431443

432444
class PivotJoint(Constraint):
433445
"""PivotJoint allow two objects to pivot about a single point.
@@ -496,6 +508,11 @@ def anchor_b(self, anchor: tuple[float, float]) -> None:
496508
assert len(anchor) == 2
497509
lib.cpPivotJointSetAnchorB(self._constraint, anchor)
498510

511+
@property
512+
def impulse_vector(self) -> Vec2d:
513+
v = lib.cpPivotJointGetImpulse(self._constraint)
514+
return Vec2d(v.x, v.y)
515+
499516

500517
class GrooveJoint(Constraint):
501518
"""GrooveJoint is similar to a PivotJoint, but with a linear slide.
@@ -560,6 +577,11 @@ def groove_b(self, groove: tuple[float, float]) -> None:
560577
assert len(groove) == 2
561578
lib.cpGrooveJointSetGrooveB(self._constraint, groove)
562579

580+
@property
581+
def impulse_vector(self) -> Vec2d:
582+
v = lib.cpGrooveJointGetImpulse(self._constraint)
583+
return Vec2d(v.x, v.y)
584+
563585

564586
class DampedSpring(Constraint):
565587
"""DampedSpring is a damped spring.
@@ -821,6 +843,14 @@ def max(self) -> float:
821843
def max(self, max: float) -> None:
822844
lib.cpRotaryLimitJointSetMax(self._constraint, max)
823845

846+
@property
847+
def impulse_signed(self) -> float:
848+
"""
849+
TODO: Investigate if/when this can be negative
850+
"""
851+
return lib.cpRotaryLimitJointGetImpulse(self._constraint)
852+
853+
824854

825855
class RatchetJoint(Constraint):
826856
"""RatchetJoint is a rotary ratchet, it works like a socket wrench."""
@@ -860,6 +890,10 @@ def ratchet(self) -> float:
860890
def ratchet(self, ratchet: float) -> None:
861891
lib.cpRatchetJointSetRatchet(self._constraint, ratchet)
862892

893+
@property
894+
def impulse_signed(self) -> float:
895+
return lib.cpRatchetJointGetImpulse(self._constraint)
896+
863897

864898
class GearJoint(Constraint):
865899
"""GearJoint keeps the angular velocity ratio of a pair of bodies constant."""
@@ -892,6 +926,9 @@ def ratio(self) -> float:
892926
def ratio(self, ratio: float) -> None:
893927
lib.cpGearJointSetRatio(self._constraint, ratio)
894928

929+
@property
930+
def impulse_signed(self) -> float:
931+
return lib.cpGearJointGetImpulse(self._constraint)
895932

896933
class SimpleMotor(Constraint):
897934
"""SimpleMotor keeps the relative angular velocity constant."""
@@ -916,3 +953,7 @@ def rate(self) -> float:
916953
@rate.setter
917954
def rate(self, rate: float) -> None:
918955
lib.cpSimpleMotorSetRate(self._constraint, rate)
956+
957+
@property
958+
def impulse_signed(self) -> float:
959+
return lib.cpSimpleMotorGetImpulse(self._constraint)

pymunk/tests/test_constraint.py

Lines changed: 161 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,13 +223,31 @@ def testAnchor(self) -> None:
223223
self.assertEqual(j.anchor_a, (5, 6))
224224
self.assertEqual(j.anchor_b, (7, 8))
225225

226-
def testDistane(self) -> None:
226+
def testDistance(self) -> None:
227227
a, b = p.Body(10, 10), p.Body(20, 20)
228228
j = PinJoint(a, b, (0, 0), (10, 0))
229229
self.assertEqual(j.distance, 10)
230230
j.distance = 20
231231
self.assertEqual(j.distance, 20)
232232

233+
def testImpulse(self) -> None:
234+
a, b = p.Body(10, 10), p.Body(10, 10)
235+
a.position = 0, 10
236+
j = PinJoint(a, b, (0, 0), (0, 0))
237+
s = p.Space()
238+
s.add(a, b, j)
239+
s.step(0.1)
240+
self.assertEqual(j.impulse_signed, 0)
241+
self.assertEqual(j.impulse, 0)
242+
a.position = 0, 0
243+
s.step(0.1)
244+
self.assertAlmostEqual(j.impulse_signed, 2343, 0)
245+
self.assertAlmostEqual(j.impulse, 2343,0)
246+
a.position = 0, 20
247+
s.step(0.1)
248+
self.assertAlmostEqual(j.impulse_signed, -234, 0)
249+
self.assertAlmostEqual(j.impulse, 234, 0)
250+
233251
def testPickle(self) -> None:
234252
a, b = p.Body(10, 10), p.Body(20, 20)
235253
j = PinJoint(a, b, (1, 2), (3, 4))
@@ -269,6 +287,26 @@ def testMax(self) -> None:
269287
j.max = 2
270288
self.assertEqual(j.max, 2)
271289

290+
def testImpulse(self) -> None:
291+
a, b = p.Body(10, 10), p.Body(10, 10)
292+
a.position = 0, 20
293+
j = SlideJoint(a, b, (0, 0), (0, 0), 20, 30)
294+
s = p.Space()
295+
s.add(a, b, j)
296+
s.step(0.1)
297+
self.assertEqual(j.impulse_signed, 0)
298+
self.assertEqual(j.impulse, 0)
299+
a.position = 0, 40
300+
s.step(0.1)
301+
self.assertAlmostEqual(j.impulse_signed, -234, 0)
302+
self.assertAlmostEqual(j.impulse, 234,0)
303+
# TODO: FIX ME when SlideJoint jnAcc is fixed.
304+
# a.position = 0,0
305+
# s.step(0.1)
306+
# self.assertAlmostEqual(j.impulse_signed, 234, 0)
307+
# self.assertAlmostEqual(j.impulse, 234, 0)
308+
309+
272310
def testPickle(self) -> None:
273311
a, b = p.Body(10, 10), p.Body(20, 20)
274312
j = SlideJoint(a, b, (1, 2), (3, 4), 5, 6)
@@ -302,6 +340,20 @@ def testAnchorByAnchor(self) -> None:
302340
self.assertEqual(j.anchor_a, (5, 6))
303341
self.assertEqual(j.anchor_b, (7, 8))
304342

343+
def testImpulse(self) -> None:
344+
a, b = p.Body(10, 10), p.Body(10, 10)
345+
a.position = 0, 10
346+
j = PivotJoint(a, b, (10,0))
347+
s = p.Space()
348+
s.add(a, b, j)
349+
s.step(0.1)
350+
almostEqualVector(self, j.impulse_vector, p.Vec2d(0,0), 0)
351+
self.assertEqual(j.impulse, 0)
352+
a.position = 0, 0
353+
s.step(0.1)
354+
almostEqualVector(self, j.impulse_vector, p.Vec2d(4,-5), 0)
355+
self.assertAlmostEqual(j.impulse, 6,0)
356+
305357
def testPickle(self) -> None:
306358
a, b = p.Body(10, 10), p.Body(20, 20)
307359
j = PivotJoint(a, b, (1, 2), (3, 4))
@@ -333,6 +385,22 @@ def testGroove(self) -> None:
333385
self.assertEqual(j.groove_a, (5, 6))
334386
self.assertEqual(j.groove_b, (7, 8))
335387

388+
def testImpulse(self) -> None:
389+
a, b = p.Body(10, 10), p.Body(10, 10)
390+
a.position = 0, 2.5
391+
b.position = 0, 2.5
392+
j = GrooveJoint(a, b, (0,0), (10, 0), (0,0))
393+
s = p.Space()
394+
s.add(a, b, j)
395+
almostEqualVector(self, j.impulse_vector, p.Vec2d(0,0))
396+
self.assertAlmostEqual(j.impulse, 0)
397+
398+
a.position = 0, 0
399+
s.step(0.1)
400+
almostEqualVector(self, j.impulse_vector, p.Vec2d(0, -59), 0)
401+
self.assertAlmostEqual(j.impulse, 59,0)
402+
403+
336404
def testPickle(self) -> None:
337405
a, b = p.Body(10, 10), p.Body(20, 20)
338406
j = GrooveJoint(a, b, (1, 2), (3, 4), (5, 6))
@@ -403,6 +471,20 @@ def f(spring: p.DampedSpring, dist: float) -> float:
403471
s.step(1)
404472
self.assertAlmostEqual(j.impulse, -100.15)
405473

474+
def testImpulse(self) -> None:
475+
a, b = p.Body(10, 10), p.Body(10, 10)
476+
a.position = 0, 0
477+
b.position = 0, 10
478+
j = DampedSpring(a, b, (0,0), (0,0), 10, 9, 0.01)
479+
s = p.Space()
480+
s.add(a, b, j)
481+
s.step(0.1)
482+
self.assertAlmostEqual(j.impulse, 0)
483+
b.position = 0,25
484+
s.step(0.1)
485+
self.assertAlmostEqual(j.impulse, -13.497300269982)
486+
487+
406488
def testPickle(self) -> None:
407489
a, b = p.Body(10, 10), p.Body(20, 20)
408490
j = DampedSpring(a, b, (1, 2), (3, 4), 5, 6, 7)
@@ -464,6 +546,18 @@ def f(spring: p.DampedRotarySpring, relative_angle: float) -> float:
464546
j.torque_func = DampedRotarySpring.spring_torque
465547
s.step(1)
466548
self.assertAlmostEqual(j.impulse, -21.5)
549+
550+
def testImpulse(self) -> None:
551+
a, b = p.Body(10, 10), p.Body(10, 10)
552+
a.angle = 1
553+
j = DampedRotarySpring(a, b, 1, 9, 0.01)
554+
s = p.Space()
555+
s.add(a, b, j)
556+
s.step(0.1)
557+
self.assertAlmostEqual(j.impulse, 0)
558+
a.angle = 2
559+
s.step(0.1)
560+
self.assertAlmostEqual(j.impulse, 0.89982001)
467561

468562
def testPickle(self) -> None:
469563
a, b = p.Body(10, 10), p.Body(20, 20)
@@ -494,6 +588,19 @@ def testMax(self) -> None:
494588
j.max = 2
495589
self.assertEqual(j.max, 2)
496590

591+
def testImpulse(self) -> None:
592+
a, b = p.Body(10, 10), p.Body(10, 10)
593+
j = RotaryLimitJoint(a, b, 0, 1)
594+
s = p.Space()
595+
s.add(a, b, j)
596+
s.step(0.1)
597+
self.assertAlmostEqual(j.impulse, 0)
598+
self.assertAlmostEqual(j.impulse_signed, 0)
599+
a.angle = 2
600+
s.step(0.1)
601+
self.assertAlmostEqual(j.impulse, 46.855908447)
602+
self.assertAlmostEqual(j.impulse_signed, 46.855908447)
603+
497604
def testPickle(self) -> None:
498605
a, b = p.Body(10, 10), p.Body(20, 20)
499606
j = RotaryLimitJoint(a, b, 1, 2)
@@ -529,6 +636,19 @@ def testRatchet(self) -> None:
529636
j.ratchet = 2
530637
self.assertEqual(j.ratchet, 2)
531638

639+
def testImpulse(self) -> None:
640+
a, b = p.Body(10, 10), p.Body(10, 10)
641+
j = RatchetJoint(a, b, 0.1, 0.1)
642+
s = p.Space()
643+
s.add(a, b, j)
644+
s.step(0.1)
645+
self.assertAlmostEqual(j.impulse, 0)
646+
self.assertAlmostEqual(j.impulse_signed, 0)
647+
a.angle = 2
648+
s.step(0.1)
649+
self.assertAlmostEqual(j.impulse, 46.855908447)
650+
self.assertAlmostEqual(j.impulse_signed, 46.855908447)
651+
532652
def testPickle(self) -> None:
533653
a, b = p.Body(10, 10), p.Body(20, 20)
534654
j = RatchetJoint(a, b, 1, 2)
@@ -557,6 +677,24 @@ def testRatio(self) -> None:
557677
j.ratio = 2
558678
self.assertEqual(j.ratio, 2)
559679

680+
def testImpulse(self) -> None:
681+
a, b = p.Body(10, 10), p.Body(10, 10)
682+
b.angle = 0.03
683+
j = GearJoint(a, b, 0.1, 3)
684+
s = p.Space()
685+
s.add(a, b, j)
686+
self.assertAlmostEqual(j.impulse, 0)
687+
self.assertAlmostEqual(j.impulse_signed, 0)
688+
a.angle = 2
689+
s.step(0.1)
690+
self.assertAlmostEqual(j.impulse, 28.25411279)
691+
self.assertAlmostEqual(j.impulse_signed, 28.25411279)
692+
a.angular_velocity = -0.1
693+
s.step(0.1)
694+
self.assertAlmostEqual(j.impulse, 9.5300055)
695+
self.assertAlmostEqual(j.impulse_signed, -9.5300055)
696+
697+
560698
def testPickle(self) -> None:
561699
a, b = p.Body(10, 10), p.Body(20, 20)
562700
j = GearJoint(a, b, 1, 2)
@@ -570,6 +708,7 @@ def testPickle(self) -> None:
570708
self.assertEqual(j.b.mass, j2.b.mass)
571709

572710

711+
573712
class UnitTestSimleMotor(unittest.TestCase):
574713
def testSimpleMotor(self) -> None:
575714
a, b = p.Body(10, 10), p.Body(20, 20)
@@ -578,6 +717,23 @@ def testSimpleMotor(self) -> None:
578717
j.rate = 0.4
579718
self.assertEqual(j.rate, 0.4)
580719

720+
def testImpulse(self) -> None:
721+
a, b = p.Body(10, 10), p.Body(10, 10)
722+
a.angular_velocity = .3
723+
j = SimpleMotor(a, b, .3)
724+
s = p.Space()
725+
s.add(a, b, j)
726+
self.assertAlmostEqual(j.impulse, 0)
727+
self.assertAlmostEqual(j.impulse_signed, 0)
728+
a.angular_velocity = 1
729+
s.step(0.1)
730+
self.assertAlmostEqual(j.impulse, 3.5)
731+
self.assertAlmostEqual(j.impulse_signed, 3.5)
732+
a.angular_velocity = -0.1
733+
s.step(0.1)
734+
self.assertAlmostEqual(j.impulse, 3.75)
735+
self.assertAlmostEqual(j.impulse_signed, -3.75)
736+
581737
def testPickle(self) -> None:
582738
a, b = p.Body(10, 10), p.Body(20, 20)
583739
j = SimpleMotor(a, b, 1)
@@ -597,3 +753,7 @@ def pre_solve(c: Constraint, s: p.Space) -> None:
597753

598754
def post_solve(c: Constraint, s: p.Space) -> None:
599755
pass
756+
757+
def almostEqualVector(self, first:p.Vec2d, second:p.Vec2d, places:int=7) -> None:
758+
self.assertAlmostEqual(first.x, second.x, places)
759+
self.assertAlmostEqual(first.y, second.y, places)

0 commit comments

Comments
 (0)