Source code for concepts.simulator.pymunk.world

#! /usr/bin/env python3
# -*- coding: utf-8 -*-
# File   : world.py
# Author : Jiayuan Mao
# Email  : maojiayuan@gmail.com
# Date   : 06/21/2022
#
# This file is part of Project Concepts.
# Distributed under terms of the MIT license.

import pymunk
import random
from typing import Any, Optional, Tuple, Dict

from concepts.algorithm.configuration_space import BoxConfigurationSpace, CollisionFreeProblemSpace
from concepts.math.range import Range

__all__ = ['PymunkWorld', 'PymunkSingleObjectConfigurationSpace', 'PymunkCollisionFreeProblemSpace']


[docs] class PymunkWorld(pymunk.Space): """A wrapper :class:`pymunk.Space` providing a manager for screen size and body labels."""
[docs] def __init__(self, *args, screen_width: Optional[int] = None, screen_height: Optional[int] = None, **kwargs): super().__init__(*args, **kwargs) self.screen_width = screen_width self.screen_height = screen_height self.body2label = dict() self.label2body = dict() self.body_selectable = dict() self.selectable_bodies = list()
[docs] def add_body(self, body, shapes=None, selectable=False, label=None) -> pymunk.Body: """Add a body to the space. Args: body: the body to be added. selectable: whether the body is selectable. label: the label of the body. Returns: The body added. """ if shapes is None: shapes = list(body.shapes) self.add(body, *shapes) self.label2body[label] = body self.body2label[body] = label self.body_selectable[body] = selectable body.label = label body.selectable = selectable if selectable: self.selectable_bodies.append(body) return body
@property def bodies_extra(self): for body in self.bodies: yield body, self.body_selectable[body], self.body2label[body]
[docs] def select_body(self, point: Tuple[float, float]) -> Optional[pymunk.Body]: """Select a body by a point. Args: point: the point to select the body. Returns: The body selected. None if no body is selected. """ import concepts.simulator.pymunk.body_utils as body_utils return body_utils.select_body(self, point, self.selectable_bodies)
[docs] def get_body_by_label(self, name: str) -> pymunk.Body: assert name in self.label2body, f'Body "{name}" is not in the space.' return self.label2body[name]
[docs] def random_body_pos(self, body: Optional[pymunk.Body] = None) -> Tuple[float, float]: import concepts.simulator.pymunk.body_utils as body_utils if body is None: return random.randint(0, self.screen_width), random.randint(0, self.screen_height) else: return body_utils.random_body_pos(self, body)
[docs] def get_body_poses(self) -> Dict[str, Tuple[float, float]]: return {label: tuple(body.position) for label, body in self.label2body.items()}
[docs] def get_body_states(self) -> Dict[str, Dict[str, Any]]: return {label: { 'position': tuple(body.position), 'velocity': tuple(body.velocity), 'angle': body.angle, 'angular_velocity': body.angular_velocity } for label, body in self.label2body.items()}
[docs] def get_collision_free_pspace(self, controlling_object: str, ignore_collision_filter=None, max_diff=2) -> 'PymunkCollisionFreeProblemSpace': return PymunkCollisionFreeProblemSpace( PymunkSingleObjectConfigurationSpace(self, controlling_object, max_diff=max_diff), ignore_collision_filter=ignore_collision_filter )
[docs] class PymunkSingleObjectConfigurationSpace(BoxConfigurationSpace):
[docs] def __init__(self, space: PymunkWorld, controlling_object: str, max_diff: float = 2): super().__init__([Range(0, space.screen_width), Range(space.screen_height // 2 - 200, space.screen_height)], max_diff) self.controlling_object = controlling_object self.pymunk_space = space
[docs] class PymunkCollisionFreeProblemSpace(CollisionFreeProblemSpace):
[docs] def __init__(self, cspace: PymunkSingleObjectConfigurationSpace, ignore_collision_filter=None): super().__init__(cspace) self.ignore_collision_filter = ignore_collision_filter
@property def space(self) -> PymunkWorld: return self.cspace.pymunk_space @property def controlling_object(self) -> str: return self.cspace.controlling_object
[docs] def collide(self, configuration): from .collision import collision_test all_collisions = collision_test(self.space, {self.cspace.controlling_object: configuration}, bodies=[self.space.get_body_by_label(self.cspace.controlling_object)]) if self.ignore_collision_filter is not None: all_collisions = [c for c in all_collisions if not self.ignore_collision_filter(*c)] return len(all_collisions) > 0