This question is about pymunk, but I'm aware that there are a lot more Chimpunk users out there; so if your answer involves C/Chipmunk code, it's okay. While I don't know how to write C code, I can usually figure out what's going on if I read someone else's.
The Setup
I'm simulating a top-down sliding object game (think curling or shuffleboard). I have made a minimal example of the relevant part of the code (at the end of the question), but the heart of it is:
What I'm hoping to achieve
When the stones collide, the lower stone should come to a sudden stop (or maybe bounce back a little - I'm not sweating that detail quite yet), while all or a majority of its energy is transferred to the upper stone, which would start moving upscreen.
What I'm getting instead
When the stones collide, the lower stone does not stop, and starts pushing the upper stone upscreen. It's as if the lower had more mass than the upper, but they're created with the same function so they should be the identical.
I've uploaded a .gif to imgur which illustrates this, if it helps: https://i.sstatic.net/A6B1N.jpg
It's a lower framerate than when actually running the script, but it still illustrates what's going on.
What I've tried
Reading through the pygame documentation, to try to identify all the body and shape properties that might be relevant, I've tried adjusting all of the following in various combinations:
Tweaking any/all of these made noticeable and expected changes in the behavior of the stones, but none changed the fundamental problem of one stone pushing the other when they collide.
I have read about using pymunk.CollisionHandler(), but haven't tried using it yet. From the documentation, I get the sense that this is mainly intended for adding additional effects to collisions, not for modifying the basic physics of what happens at a collision in the first place. But I may have misunderstood and am open to any suggestion.
I have looked at several of the pymunk demos. Most notably, a demo called newtons_cradle.py exhibits the behavior I want. It's a simulation of one of those gadgets with five balls suspended in a row; when the user pulls one ball on the end back, it hits the rest of the row and the energy is transferred to the ball on the opposite side. newtons_crade.py only has two major differences from my code:
Regrettably, using gravity isn't an option in my top-down setup. So the problem might be my use of apply_impulse/apply_force, but I can't see any way to modify the way these are used (I've already tried various combinations of power and mass, as well adjusting the settings of the constraints).
Even pointing me in the right direction - i.e. some advice on what else I might read up on, what else I might try modifying - would be greatly appreciated. I can't be the first person to try this in pymunk/chipmunk, but I haven't been able to find an example. At least not on the pymunk side; if there's a good example in C/Chipmunk I could study, that'd be useful too.
Thank you all for your time.
Minimal Example Code
It's not necessary to study the code to understand the question, but I've posted it here just in case it's helpful. Although stripped down to show just heart of the code, it is a full script and can be run. It's in Python 3.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
import pygame
from pygame.locals import *
import pymunk
import pymunk.pygame_util
def add_and_tether_stone(space,sx,sy):
"""Creates a stone and its corresponding shape, and tethers it with constraints that simulate ground friction and govern spin."""
#body
mass = stone_mass
radius = stone_radius
moment = pymunk.moment_for_circle(mass, 0, radius)
body = pymunk.Body(mass, moment)
body.position = sx,sy
#shape
shape = pymunk.Circle(body, radius)
shape.friction = stone_friction
shape.elisticity = stone_elisticity
space.add(body, shape)
#constraints
fpiv = pymunk.constraint.PivotJoint(space.static_body,body,(0.0,0.0),(0.0,0.0))
fpiv.max_force = ground_friction
fpiv.max_bias = 0.0
fmot = pymunk.constraint.SimpleMotor(space.static_body,body,0)
fmot.max_force = 5000000 #arbitry 'very high' value clamps down on the high rotation imparted by apply_impulse or apply_force
space.add(fpiv)
space.add(fmot)
return body,shape,fpiv,fmot
def launch_stone(body,power):
"""Launches a stone in the manner of a player taking a shot."""
body.apply_impulse_at_world_point((0,power),(0,0)) #force(x,y),offset(x,y)
def main():
global ground_friction,stone_mass,stone_radius,stone_friction,stone_elisticity
running = True
#PyGame setup
pygame.init()
screen = pygame.display.set_mode((500,500))
clock = pygame.time.Clock()
sheet = pygame.Surface((500,500))
sheetcolor = (0,0,0)
sheet.fill(sheetcolor)
sheet = sheet.convert()
sheetblit = (0,0)
screen.blit(sheet,sheetblit)
#PyMunk setup
space = pymunk.Space() #space.damping defaults to 1.0, and space.gravity defaults to (0.0, 0.0).
draw_options = pymunk.pygame_util.DrawOptions(sheet) #used only for the pygame_util debug draw mode
#Constants to Tweak
stone_mass = 1.4
stone_radius = 20
power = 340 #in a full implementation, this would vary with player input
ground_friction = 4.5
stone_friction = 2.0
stone_elisticity = 1.0
#Setup for the minimal example: add two stones and launch one at the other.
stone_a = add_and_tether_stone(space,40,260)
stone_b = add_and_tether_stone(space,40,21)
launch_stone(stone_b[0],power)
while running:
for event in pygame.event.get(): #listen for controls (all the controls except 'esc' have been removed for the minimal example)
if event.type == KEYDOWN and event.key == K_ESCAPE:
running = False
#Draw, update physics, and advance
sheet.fill(sheetcolor)
space.debug_draw(draw_options) #from pymunk.pygame-util (handy!)
screen.blit(sheet,sheetblit)
space.step(1/50.0)
pygame.display.flip()
clock.tick(50)
if __name__ == '__main__':
sys.exit(main())
Thank you all again for your time.
Maybe I missed something in your code (I only have command line python here so I cant run your script), but I cant recreate your problem.
Here is a short code I tried which seems to work as you want:
import pymunk
s = pymunk.Space()
b1 = pymunk.Body(1,10)
b1.position = 0,0
b2 = pymunk.Body(1,10)
b2.position = 0,10
c1 = pymunk.Circle(b1, 1)
c1.elasticity = 1.0
c2 = pymunk.Circle(b2, 1)
c2.elasticity = 1.0
j1 = pymunk.constraint.PivotJoint(s.static_body, b1, (0,0),(0,0))
j1.max_force = 4.5
j1.max_bias = 0
j2 = pymunk.constraint.PivotJoint(s.static_body, b2, (0,0),(0,0))
j2.max_force = 4.5
j2.max_bias = 0
j3 = pymunk.constraint.SimpleMotor(s.static_body,b1,0)
j3.max_force = 5000000
j4 = pymunk.constraint.SimpleMotor(s.static_body,b2,0)
j4.max_force = 5000000
s.add(b1,b2,c1,c2,j1,j2,j3,j4)
b1.apply_impulse_at_world_point((0,30),(0,0))
for x in range(25):
s.step(0.02)
print(b1.position, b2.position)
This prints out this on my screen (so b1 stopped and all the movement is transferred to b2):
Vec2d(0.0, 0.6) Vec2d(0.0, 10.0)
Vec2d(0.0, 1.1982) Vec2d(0.0, 10.0)
Vec2d(0.0, 1.7946) Vec2d(0.0, 10.0)
Vec2d(0.0, 2.3891999999999998) Vec2d(0.0, 10.0)
Vec2d(0.0, 2.9819999999999998) Vec2d(0.0, 10.0)
Vec2d(0.0, 3.573) Vec2d(0.0, 10.0)
Vec2d(0.0, 4.1622) Vec2d(0.0, 10.0)
Vec2d(0.0, 4.7496) Vec2d(0.0, 10.0)
Vec2d(0.0, 5.3352) Vec2d(0.0, 10.0)
Vec2d(0.0, 5.9190000000000005) Vec2d(0.0, 10.0)
Vec2d(0.0, 6.501) Vec2d(0.0, 10.0)
Vec2d(0.0, 7.081200000000001) Vec2d(0.0, 10.0)
Vec2d(0.0, 7.659600000000001) Vec2d(0.0, 10.0)
Vec2d(0.0, 8.2362) Vec2d(0.0, 10.0)
Vec2d(0.0, 8.228112001309862) Vec2d(0.0, 10.584682725252637)
Vec2d(0.0, 8.228112001309862) Vec2d(0.0, 11.159477451815137)
Vec2d(0.0, 8.228112001309862) Vec2d(0.0, 11.732472178377638)
Vec2d(0.0, 8.228112001309862) Vec2d(0.0, 12.303666904940137)
Vec2d(0.0, 8.228112001309862) Vec2d(0.0, 12.873061631502637)
Vec2d(0.0, 8.228112001309862) Vec2d(0.0, 13.440656358065137)