Why do rectangular object without rotation start spinning after colliding with a flat wall in Python Arcade/Pymunk? How to solve this problem?
Let the rectangular object has angular velocity 0, angle 0, center of gravity (0,0) and moves straight. After which the rectangular object collides with a flat wall. I noticed that rectangular objects begin to spin and shift after a collision with walls (static objects) in Arcade. Depending on the ratio of height and width, the turning speed is different. But it always present.
Since I use Python Arcade and Pymunk, I see the following possible solutions to the problem:
hit_handler
via _arbiter._arbiter: pymunk._chipmunk.ffi.CData
. Somehow process the data from CData in Python and figure out how to fix the data that causes the error described above. It would be great if someone could tell me how to do this.velocity
and angular_velocity
from scratch in Python. But this method essentially doubles the number of calculations.It would be great if someone tell another way.
Perhaps this is a bug in the Chipmunk2d library and there is no simple way. I've been struggling with this problem for a very long time. Thank you in advance for any help.
Arcade 2.6.17 Pymunk 6.4.0 Python 3.10.14
This is minimal reproducible example (without Arcade for minimum code):
import pyglet
import pymunk
import pymunk.pyglet_util
from pymunk import Vec2d
class Main(pyglet.window.Window):
def __init__(self):
pyglet.window.Window.__init__(self, vsync=False)
pyglet.clock.schedule_interval(self.update, 1 / 60.0)
self.fps_display = pyglet.window.FPSDisplay(self)
self.create_world()
self.draw_options = pymunk.pyglet_util.DrawOptions()
self.draw_options.flags = self.draw_options.DRAW_SHAPES
def create_world(self):
self.space = pymunk.Space()
self.space.gravity = Vec2d(0.0, -9.0)
self.space.sleep_time_threshold = 0.3
static_lines = [
pymunk.Segment(self.space.static_body, Vec2d(20, 55), Vec2d(600, 55), 1),
pymunk.Segment(self.space.static_body, Vec2d(550, 55), Vec2d(550, 400), 1),
]
for l in static_lines:
l.friction = 0.3
l.elasticity = 1
self.space.add(*static_lines)
size = 20
mass = 1.0
moment = pymunk.moment_for_box(mass, (size//2, size))
body = pymunk.Body(mass, moment)
body.position = Vec2d(300 + 0 * 50, 105 + 5 * (size + 0.1))
shape = pymunk.Poly.create_box(body, (size//2, size))
shape.elasticity = 1
shape.friction = 0.3
self.space.add(body, shape)
def update(self, dt):
self.space.step(dt)
def on_draw(self):
self.clear()
self.fps_display.draw()
self.space.debug_draw(self.draw_options)
def main():
main = Main()
pyglet.app.run()
if __name__ == "__main__":
main()
My solution - change velocity by bounce from arbiter:CData and change angular velocity in special case. So we have next steps:
self.ffibuilder = FFI() with open(r"Path\chipmunk_cdef.h", "r") as f: self.ffibuilder.cdef(f.read())
def pre_handler(_arbiter, _space, _data): _data["a_w"] = _arbiter.shapes[0].body.angular_velocity return True
def post_handler(ffibuilder, _arbiter, _space, _data): arb = ffibuilder.cast("struct cpArbiter *",_arbiter._arbiter) n = (arb.n.x, arb.n.y) distances = [i.distance for i in _arbiter.contact_point_set.points] if distances.count(distances[0]) == len(distances) and abs(_data["a_w"])<=0.1**15: _arbiter.shapes[0].body.velocity = cpvmult(n,abs(arb.contacts[0].bounce)) _arbiter.shapes[0].body.angular_velocity = 0.0
Collision slop and bias also affect. We can set self.space.collision_slop = N
(N max number what you can use, N=2 for me) or set some custom calculations in arb
(post_handler
).
For Python Arcade answer is the same.
Full code:
import pyglet import pymunk import pymunk.pyglet_util from pymunk import Vec2d from cffi import FFI def pre_handler(_arbiter, _space, _data): _data["a_w"] = _arbiter.shapes[0].body.angular_velocity return True def post_handler(ffibuilder, _arbiter, _space, _data): arb = ffibuilder.cast("struct cpArbiter *",_arbiter._arbiter) n = (arb.n.x, arb.n.y) distances = [i.distance for i in _arbiter.contact_point_set.points] if distances.count(distances[0]) == len(distances) and abs(_data["a_w"])<=0.1**15: _arbiter.shapes[0].body.velocity = cpvmult(n,abs(arb.contacts[0].bounce)) _arbiter.shapes[0].body.angular_velocity = 0.0 def cpvmult(t1,number): return t1[0]*number,t1[1]*number class Main(pyglet.window.Window): def __init__(self): pyglet.window.Window.__init__(self, vsync=False) pyglet.clock.schedule_interval(self.update, 1 / 60.0) self.fps_display = pyglet.window.FPSDisplay(self) self.create_world() self.draw_options = pymunk.pyglet_util.DrawOptions() self.draw_options.flags = self.draw_options.DRAW_SHAPES self.ffibuilder = FFI() with open(r"C:\Users\HYPERPC\Downloads\chipmunk_cdef.h", "r") as f: self.ffibuilder.cdef(f.read()) def add_handler(self, shape, pre_handler=None,post_handler=None): def _f1(arbiter, space, data): post_handler(self.ffibuilder, arbiter, space, data) def _f2(arbiter, space, data): return pre_handler(arbiter, space, data) h = self.space.add_wildcard_collision_handler(shape.collision_type) if post_handler: h.post_solve = _f1 if pre_handler: h.pre_solve = _f2 def create_world(self): self.space = pymunk.Space() self.space.gravity = Vec2d(0.0, -9.0) self.space.sleep_time_threshold = 0.3 self.space.collision_slop = 2 static_lines = [ pymunk.Segment(self.space.static_body, Vec2d(20, 55), Vec2d(600, 55), 1), pymunk.Segment(self.space.static_body, Vec2d(550, 55), Vec2d(550, 400), 1), ] for l in static_lines: l.friction = 0.3 l.elasticity = 1 self.space.add(*static_lines) size = 20 mass = 1.0 moment = pymunk.moment_for_box(mass, (size//2, size)) body = pymunk.Body(mass, moment) body.position = Vec2d(300 + 0 * 50, 105 + 5 * (size + 0.1)) shape = pymunk.Poly.create_box(body, (size//2, size)) shape.elasticity = 1 shape.friction = 0.3 self.space.add(body, shape) self.add_handler(shape, pre_handler = pre_handler) self.add_handler(shape, post_handler = post_handler) def update(self, dt): self.space.step(dt) def on_draw(self): self.clear() self.fps_display.draw() self.space.debug_draw(self.draw_options) def main(): main = Main() pyglet.app.run() if __name__ == "__main__": main()