Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to keep one body on top of another using Pymunk

I've got a circle, with a box on top:

Circle with 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()
like image 750
Paul Vincent Craven Avatar asked Oct 18 '22 05:10

Paul Vincent Craven


1 Answers

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.

like image 135
viblo Avatar answered Oct 22 '22 09:10

viblo