1

How to turn off collisions for some objects and then again turn it on using pymunk lib in python?

Let me show you the example, based on the code below. I want all red balls to go through first border of lines and stop on the lower border. Blue balls should still collide with upper border.

What needs to be changed in the code?

import pygame
from pygame.locals import *
from pygame.color import *
import pymunk as pm
from pymunk import Vec2d
import math, sys, random

def to_pygame(p):
    """Small hack to convert pymunk to pygame coordinates"""
    return int(p.x), int(-p.y+600)

pygame.init()
screen = pygame.display.set_mode((600, 600))
clock = pygame.time.Clock()
running = True

### Physics stuff
space = pm.Space()
space.gravity = (0.0, -900.0)

## Balls
balls = []

### walls
static_body = pm.Body()
static_lines = [pm.Segment(static_body, (111.0, 280.0), (407.0, 246.0), 0.0),
                pm.Segment(static_body, (407.0, 246.0), (407.0, 343.0), 0.0),
                pm.Segment(static_body, (111.0, 420.0), (407.0, 386.0), 0.0),
                pm.Segment(static_body, (407.0, 386.0), (407.0, 493.0), 0.0)]  
for line in static_lines:
    line.elasticity = 0.95
space.add(static_lines)

ticks_to_next_ball = 10

while running:
    for event in pygame.event.get():
        if event.type == QUIT:
            running = False
        elif event.type == KEYDOWN and event.key == K_ESCAPE:
            running = False

    ticks_to_next_ball -= 1
    if ticks_to_next_ball <= 0:
        ticks_to_next_ball = 100
        mass = 10
        radius = random.randint(10,40)
        inertia = pm.moment_for_circle(mass, 0, radius, (0,0))
        body = pm.Body(mass, inertia)
        x = random.randint(115,350)
        body.position = x, 600
        shape = pm.Circle(body, radius, (0,0))
        shape.elasticity = 0.95
        space.add(body, shape)
        balls.append(shape)

    ### Clear screen
    screen.fill(THECOLORS["white"])

    ### Draw stuff
    balls_to_remove = []
    for ball in balls:
        if ball.body.position.y < 200: balls_to_remove.append(ball)

        p = to_pygame(ball.body.position)
        if ball.radius > 25:
            color = THECOLORS["blue"]
        else:
            color = THECOLORS["red"]
        pygame.draw.circle(screen, color, p, int(ball.radius), 2)

    for ball in balls_to_remove:
        space.remove(ball, ball.body)
        balls.remove(ball)

    for line in static_lines:
        body = line.body
        pv1 = body.position + line.a.rotated(body.angle)
        pv2 = body.position + line.b.rotated(body.angle)
        p1 = to_pygame(pv1)
        p2 = to_pygame(pv2)
        pygame.draw.lines(screen, THECOLORS["lightgray"], False, [p1,p2])

    ### Update physics
    dt = 1.0/60.0
    for x in range(1):
        space.step(dt)

    ### Flip screen
    pygame.display.flip()
    clock.tick(50)
    pygame.display.set_caption("fps: " + str(clock.get_fps()))
Stanyko
  • 95
  • 3
  • 15

2 Answers2

4

Chipmunk has a few options filtering collisions: http://chipmunk-physics.net/release/ChipmunkLatest-Docs/#cpShape-Filtering

It sounds like you just need to use a layers bitmask though.

ex:

# This layer bit is for balls colliding with other balls
# I'm only guessing that you want this though.
ball_layer = 1
# This layer bit is for things that collide with red balls only.
red_ball_layer = 2
# This layer bit is for things that collide with blue balls only.
blue_ball_layer = 4

# Bitwise OR the layer bits together
red_ball_shape.layers = ball_layer | red_ball_layer
blue_ball_shape.layers = ball_layer | blue_ball_layer

# Lower border should collide with red only
upper_border_shape.layers = red_ball_layer

#Upper border with blue balls only
lower_border_shape.layers = blue_ball_layer

I've never actually used Pymunk personally, but I'm guessing that it exposes the Chipmunk layers property simply as .layers

slembcke
  • 1,359
  • 7
  • 7
1

In Pymunk you can use the ShapeFilter class to set the categories (layers) with which an object can collide. I put the upper and lower lines into the categories 1 and 2 and then set the masks of the balls so that they ignore these layers. You need to understand how bitmasking works.

Here's the complete example based on the code in the original question (press left and right mouse button to spawn the balls).

import sys
import pygame as pg
from pygame.color import THECOLORS
import pymunk as pm


def to_pygame(p):
    """Small hack to convert pymunk to pygame coordinates"""
    return int(p[0]), int(-p[1]+600)


pg.init()
screen = pg.display.set_mode((600, 600))
clock = pg.time.Clock()

space = pm.Space()
space.gravity = (0.0, -900.0)

# Walls
static_body = space.static_body
static_lines = [
    pm.Segment(static_body, (111.0, 280.0), (407.0, 246.0), 0.0),
    pm.Segment(static_body, (407.0, 246.0), (407.0, 343.0), 0.0),
    pm.Segment(static_body, (111.0, 420.0), (407.0, 386.0), 0.0),
    pm.Segment(static_body, (407.0, 386.0), (407.0, 493.0), 0.0),
    ]
for idx, line in enumerate(static_lines):
    line.elasticity = 0.95
    if idx < 2:  # Lower lines.
        # The lower lines are in category 2, in binary 0b10.
        line.filter = pm.ShapeFilter(categories=2)
    else:  # Upper lines.
        # The upper lines are in category 1, in binary 0b1.
        line.filter = pm.ShapeFilter(categories=1)
space.add(static_lines)

balls = []
running = True

while running:
    for event in pg.event.get():
        if event.type == pg.QUIT:
            running = False
        elif event.type == pg.KEYDOWN and event.key == pg.K_ESCAPE:
            running = False
        if event.type == pg.MOUSEBUTTONDOWN:
            radius = 15 if event.button == 1 else 30
            mass = 10
            inertia = pm.moment_for_circle(mass, 0, radius, (0,0))
            body = pm.Body(mass, inertia)
            body.position = to_pygame(event.pos)
            shape = pm.Circle(body, radius, (0,0))
            shape.elasticity = 0.95
            if shape.radius > 25:
                # bin(pm.ShapeFilter.ALL_MASKS ^ 1) is '0b11111111111111111111111111111110'
                # That means all categories are checked for collisions except
                # bit 1 (the upper lines) which are ignored.
                shape.filter = pm.ShapeFilter(mask=pm.ShapeFilter.ALL_MASKS ^ 1)
            else:
                # Ignores category bin(2), '0b11111111111111111111111111111101'
                # All categories are checked for collisions except bit 2 (the lower lines).
                shape.filter = pm.ShapeFilter(mask=pm.ShapeFilter.ALL_MASKS ^ 2)

            space.add(body, shape)
            balls.append(shape)

    screen.fill(THECOLORS["white"])

    balls_to_remove = []
    for ball in balls:
        if ball.body.position.y < 100:
            balls_to_remove.append(ball)

        p = to_pygame(ball.body.position)
        if ball.radius > 25:
            color = THECOLORS["red"]
        else:
            color = THECOLORS["blue"]
        pg.draw.circle(screen, color, p, int(ball.radius), 2)

    for ball in balls_to_remove:
        space.remove(ball, ball.body)
        balls.remove(ball)

    for line in static_lines:
        body = line.body
        pv1 = body.position + line.a.rotated(body.angle)
        pv2 = body.position + line.b.rotated(body.angle)
        p1 = to_pygame(pv1)
        p2 = to_pygame(pv2)
        pg.draw.lines(screen, THECOLORS["gray29"], False, [p1, p2])

    # Update physics.
    dt = 1.0/60.0
    for x in range(1):
        space.step(dt)

    pg.display.flip()
    clock.tick(50)


pg.quit()
sys.exit()
skrx
  • 19,980
  • 5
  • 34
  • 48
  • I've just had to experiment a bit (with the bits :P) to figure out how collision filtering works in Pymunk and couldn't find other examples, so I post this if others have the same problems. – skrx Apr 11 '17 at 13:26
  • Why all mask ^with exclusive or? – Meric Ozcan Jun 12 '18 at 13:10
  • Could you please check my code? I edited, I need some urgent support if you can help me :) – Meric Ozcan Jun 12 '18 at 13:27
  • If you pass `pymunk.ShapeFilter.ALL_MASKS` as the mask attribute, then all bits of the mask are on and the shape will collide with all categories. If you want to exclude one or several categories, you've got to turn these bits off and that can be done with the bitwise XOR which toggles bits (see the [wikipedia article](https://en.wikipedia.org/wiki/Mask_(computing)#Toggling_bit_values)). – skrx Jun 12 '18 at 13:31
  • I want to collide with one category and ignore all other categories. Please check my code under unit function. My code can compile. I used XOR and NOT operands. To collide one ignore all others. However this is might not be good thing to do. I know your code from previous question. But this is a new problem and it is bit unique @skrx – Meric Ozcan Jun 12 '18 at 13:35