I've got a circle, with a box on top:
The circle is a simple motor. I want the box to stay directly over the circle. I've tried different constraints, but most of my attempts cause the box to flop to the side.
My most successful attempt is to set the box's body.moment to pymunk.inf, and pinning the box to the circle. That comes close, but the box still moves from side to side when I'd like it directly over the circle's center. I could manually set it there, but it seems like I should be able to do so with some kind of constraint.
Any ideas? Below is some sample code using Pymunk and Arcade libraries.
import arcade
import pymunk
import math
SCREEN_WIDTH = 1200
SCREEN_HEIGHT = 800
BOX_SIZE = 45
class MyApplication(arcade.Window):
""" Main application class. """
def __init__(self, width, height):
super().__init__(width, height)
arcade.set_background_color(arcade.color.DARK_SLATE_GRAY)
# -- Pymunk space
self.space = pymunk.Space()
self.space.gravity = (0.0, -900.0)
# Create the floor
body = pymunk.Body(body_type=pymunk.Body.STATIC)
self.floor = pymunk.Segment(body, [0, 10], [SCREEN_WIDTH, 10], 0.0)
self.floor.friction = 10
self.space.add(self.floor)
# Create the circle
player_x = 300
player_y = 300
mass = 2
radius = 25
inertia = pymunk.moment_for_circle(mass, 0, radius, (0, 0))
circle_body = pymunk.Body(mass, inertia)
circle_body.position = pymunk.Vec2d(player_x, player_y)
self.circle_shape = pymunk.Circle(circle_body, radius, pymunk.Vec2d(0, 0))
self.circle_shape.friction = 1
self.space.add(circle_body, self.circle_shape)
# Create the box
size = BOX_SIZE
mass = 5
moment = pymunk.moment_for_box(mass, (size, size))
moment = pymunk.inf
body = pymunk.Body(mass, moment)
body.position = pymunk.Vec2d(player_x, player_y + 49)
self.box_shape = pymunk.Poly.create_box(body, (size, size))
self.box_shape.friction = 0.3
self.space.add(body, self.box_shape)
# Create a joint between them
constraint = pymunk.constraint.PinJoint(self.box_shape.body, self.circle_shape.body)
self.space.add(constraint)
# Make the circle rotate
constraint = pymunk.constraint.SimpleMotor(self.circle_shape.body, self.box_shape.body, -3)
self.space.add(constraint)
def on_draw(self):
"""
Render the screen.
"""
arcade.start_render()
# Draw circle
arcade.draw_circle_outline(self.circle_shape.body.position[0],
self.circle_shape.body.position[1],
self.circle_shape.radius,
arcade.color.WHITE,
2)
# Draw box
arcade.draw_rectangle_outline(self.box_shape.body.position[0],
self.box_shape.body.position[1],
BOX_SIZE,
BOX_SIZE,
arcade.color.WHITE, 2,
tilt_angle=math.degrees(self.box_shape.body.angle))
# Draw floor
pv1 = self.floor.body.position + self.floor.a.rotated(self.floor.body.angle)
pv2 = self.floor.body.position + self.floor.b.rotated(self.floor.body.angle)
arcade.draw_line(pv1.x, pv1.y, pv2.x, pv2.y, arcade.color.WHITE, 2)
def animate(self, delta_time):
# Update physics
self.space.step(1 / 80.0)
window = MyApplication(SCREEN_WIDTH, SCREEN_HEIGHT)
arcade.run()
You can use two pin joints instead of one, with spread out anchor points on the box. Sort of how you would make it stable also in real life :)
# Create a joint between them
constraint = pymunk.constraint.PinJoint(self.box_shape.body, self.circle_shape.body, (-20,0))
self.space.add(constraint)
constraint = pymunk.constraint.PinJoint(self.box_shape.body, self.circle_shape.body, (20,0))
self.space.add(constraint)
If its not good enough you can try to experiment with a lower error_bias
value on the constraints, but Im not sure how much it helps. If you need it to be pixel perfect I dont think joints can do it, they can always have some small error. So in that case I think you have to fake it by drawing the upper and lower sprite on the same x value.