Source code for miniworldmaker.boards.physics_board

import sys

import pymunk as pymunk_engine

import miniworldmaker.boards.board as board
import miniworldmaker.boards.token_connectors.physics_board_connector as physics_board_connector
import miniworldmaker.tools.token_class_inspection as token_class_inspection
import miniworldmaker.tools.token_inspection as token_inspection


[docs]class PhysicsBoard(board.Board): """ A PhysicsBoard is a playing field on which objects follow physical laws. The PhysicsBoard itself defines some values with which the physics engine can be influenced, e.g. the gravity in the world. All tokens on a PhysicsBoard have an attribute ``token.physics``, with which you can change the physical properties of the object. """ def __init__( self, columns: int = 40, rows: int = 40, ): super().__init__(columns, rows) self.gravity_x: float = 0 self.gravity_y: float = 900 self.debug: bool = False self._accuracy = 1 self.space = pymunk_engine.Space() self.space.gravity = self.gravity_x, self.gravity_y self.space.iterations = 35 self.space.damping = 0.9 self.space.collision_persistence = 10 self.physics_tokens = list() self.touching_methods = set() # filled in token_manager self.separate_methods = set() # filled in token_manager @property def accuracy(self): """Sets number of physics-steps performed in each frame. Default: 1 """ return self._accuracy @accuracy.setter def accuracy(self, value: int): self._accuracy = value def _pymunk_register_collision_manager(self, token, other_class, event, method): """Adds pymunk collision handler, which is evaluated by pymunk engine. The event (begin, end) and the method (on_touching...) are added as data to the handler Args: token: The token other_class: The class which should be detected by collision handler event: The pymunk-event (begin or separate) method: The method, e.g. on_touching_token or on_separation_from_token. Last part is a class name :meta private: """ space = self.space token_id = hash(token.__class__.__name__) % ((sys.maxsize + 1) * 2) other_id = hash(other_class.__name__) % ((sys.maxsize + 1) * 2) handler = space.add_collision_handler(token_id, other_id) handler.data["method"] = getattr(token, method.__name__) handler.data["type"] = event if event == "begin": handler.begin = self.pymunk_touching_collision_listener if event == "separate": handler.separate = self.pymunk_separation_collision_listener @staticmethod def _get_token_connector_class(): return physics_board_connector.PhysicsBoardConnector def get_physics_collision_methods_for_token(self, token): """Gets all collision methods for token :meta private: """ return [ getattr(token, method_name) for method_name in dir(token) if hasattr(token, method_name) and callable(getattr(token, method_name)) and method_name.startswith("on_touching_") or method_name.startswith("on_separation_from_") ] def register_all_physics_collision_managers_for_token(self, token): """Registers on__touching and on_seperation-Methods to token. If new_class is set, only methods with new class (e.g. on_touching_new_class are set) :meta private: """ collision_methods = self.get_physics_collision_methods_for_token(token) for method in collision_methods: if method.__name__.startswith("on_touching_"): self.register_touching_method(method) elif method.__name__.startswith("on_separation_from_"): self.register_separate_method(method) def _register_physics_listener_method(self, method, event, other_cls): """Registers a physics listener method. (on touching or on_seperation.) Called from register_touching_method and register_separate_method :meta private: """ token_class_inspect = token_class_inspection.TokenClassInspection(self) all_token_classes = token_class_inspect.get_all_token_classes() if other_cls not in all_token_classes: return False else: subclasses_of_other_token = token_class_inspection.TokenClassInspection(other_cls).get_subclasses_for_cls() for other_subcls in subclasses_of_other_token: # If you register a Collision with a Token, collissns with subclasses of the token # are also registered self._pymunk_register_collision_manager(method.__self__, other_subcls, event, method) return True def register_touching_method(self, method): """ Registers on_touching_[class] method :meta private: """ event = "begin" other_cls_name = method.__name__[len("on_touching_"):].lower() other_cls = token_class_inspection.TokenClassInspection(self).find_token_class_by_classname(other_cls_name) if self._register_physics_listener_method(method, event, other_cls): self.touching_methods.add(method) def register_separate_method(self, method): """ Registers on_separation_from_[class] method :meta private: """ event = "separate" other_cls_name = method.__name__[len("on_separation_from_"):].lower() other_cls = token_class_inspection.TokenClassInspection(self).find_token_class_by_classname(other_cls_name) if self._register_physics_listener_method(method, event, other_cls): self.separate_methods.add(method)
[docs] def remove_token_from_board(self, token): """Removes token from board and removes pymunk body and shapes. """ super().remove_token_from_board(token) self.physics_tokens.remove(token)
def act_all(self): """Handles acting of tokens - Calls the physics-simulation in each frame. :meta private: """ super().act_all() self.simulate_all_physics_tokens() def simulate_all_physics_tokens(self): """Iterates over all tokens and process physics-simulation Processes phyisics-simulation in three steps * Convert miniworldmaker-position/direction to pymunk position/direction * Simulate a step in physics-engine * Convert pymunk position/direction to miniworldmaker position/direction :meta private: """ if len(self.physics_tokens) > 0: # pre-process [token.physics._set_update_mode() for token in self.physics_tokens] [token.physics._simulation_preprocess_token() for token in self.physics_tokens] # simulate steps = self.accuracy for _ in range(steps): # if self.physics.space is not None: - can be removed self.space.step(1 / (60 * steps)) # post-process [token.physics._simulation_postprocess_token() for token in self.physics_tokens] [token.physics._unset_update_mode() for token in self.physics_tokens] @property def gravity(self): """ Defines gravity in physics board. Default gravity: x=0, y=500 :return: """ return self.gravity_x, self.gravity_y @gravity.setter def gravity(self, value: tuple): self.gravity_x = value[0] self.gravity_y = value[1] self.space.gravity = self.gravity_x, self.gravity_y @property def damping(self): """ Amount of simple damping to apply to the space. A value of 0.9 means that each body will lose 10% of its velocity per second. Defaults to 1. """ return self.gravity_x, self.gravity_y @damping.setter def damping(self, value: tuple): self._damping = value self.space.damping = self._damping def pymunk_touching_collision_listener(self, arbiter, space, data): """Handles collisions - Handled by pymunk engine :meta private: """ # Translate pymunk variables to miniworldmaker variables token = arbiter.shapes[0].token other = arbiter.shapes[1].token collision = dict() # get touching token_manager for token for method in self.touching_methods: method_other_cls_name = method.__name__[len("on_touching_"):].lower() method_other_cls = token_class_inspection.TokenClassInspection(self).find_token_class_by_classname( method_other_cls_name ) # is other an instance of method_other_cls if isinstance(other, method_other_cls): token_inspection.TokenInspection(token).get_and_call_method(method.__name__, [other, collision]) return True def pymunk_separation_collision_listener(self, arbiter, space, data): """Handles collisions - Handled by pymunk engine :meta private: """ # Translate pymunk variables to miniworldmaker variables token = arbiter.shapes[0].token other = arbiter.shapes[1].token collision = dict() # get separation token_manager for token for method in self.separate_methods: method_other_cls_name = method.__name__[len("on_separation_from_"):].lower() method_other_cls = token_class_inspection.TokenClassInspection(self).find_token_class_by_classname( method_other_cls_name ) # is other an instance of method_other_cls if isinstance(other, method_other_cls): token_inspection.TokenInspection(token).get_and_call_method(method.__name__, [other, collision]) return True