Source code for miniworldmaker.tokens.physics.token_physics

import pymunk as pymunk_engine
import pymunk.pygame_util
import sys
import math
from typing import Union
from miniworldmaker.tokens import token as token


[docs]class TokenPhysics: """The PhysicsProperty class describes all properties necessary to physically simulate an object using the pymunk engine. If you use a physics board, you can access token-physics properties with ``token_name.physics.property`` Examples: .. code-block:: python from miniworldmaker import * board = PhysicsBoard((800, 600)) a = Circle() a.position = (75, 200) a.color = (255,0,0) a.physics.simulation = "simulated" a.direction = 180 a.physics.shape_type = "circle" a.impulse(45, 1500) board.run() """ def __init__(self, token, board): self.started: bool = False self._body_type = pymunk.Body.DYNAMIC self.board = board self.token: token.Token = token self.simulation: str = "simulated" self._gravity: bool = False self._stable: bool = False self._can_move: bool = True self._density: float = 10 self._friction: float = 10 self._velocity_x: float = 0 self._velocity_y: float = 0 self._elasticity: float = 0.5 self._shape_type: str = "rect" self._correct_angle: float = 90 self._body: Union[pymunk_engine.Body, None] = None self._shape: Union[pymunk_engine.Shape, None] = None self.dirty: int = 1 self.has_physics: bool = False self._update_from_physics: bool = False # is position/direction updated from physics_engine? self.size = (1, 1) # scale factor for physics box model self.joints = [] def join(self, other: "token.Token"): """joins two tokens at their center points """ if not hasattr(other, "physics"): raise TypeError("Other token has no attribute physics") my_body = self._body other_body = other.physics._body pj = pymunk.PinJoint(my_body, other_body, (0, 0), (0, 0)) self.board.space.add(pj) return self.token.position_manager.get_position(), other.position
[docs] def join(self, other: "token.Token"): """joins two tokens at their center points """ if not hasattr(other, "physics"): raise TypeError("Other token has no attribute physics") my_body = self._body other_body = other.physics._body pj = pymunk.PinJoint(my_body, other_body, (0, 0), (0, 0)) self.joints.append(pj) self.board.space.add(pj) return self.token.position_manager.get_position(), other.position
[docs] def remove_join(self, other: "token.Token"): """Remove a joint between two tokens. Removes a joint between two tokens, if a joint exists. Examples: Add and remove a joint on key_down: .. code-block:: python import random from miniworldmaker import * board = PhysicsBoard((400, 200)) connected = False line = None anchor = Rectangle() anchor.size = (20,20) anchor.center = (100, 20) anchor.physics.simulation = "manual" other_side = Line((250,100),(500,200)) def add_line(obj1, obj2): l = Line(obj1.center, obj2.center) l.physics.simulation = None @l.register def act(self): self.start_position = obj1.center self.end_position = obj2.center return l c = Circle() @c.register def on_key_down(self, key): global connected global line if not connected: print("not connected") self.physics.join(anchor) line = add_line(self, anchor) connected = True else: print("connected") self.physics.remove_join(anchor) line.remove() board.run() .. raw:: html <video loop autoplay muted width=400> <source src="../_static/jointsremove1.webm" type="video/webm"> <source src="../_static/jointsremove1.mp4" type="video/mp4"> Your browser does not support the video tag. </video> """ for join in self.joints: if other.physics._body == join.b: self.board.space.remove(join)
def _start(self): """Starts the physics-simulation Called in board-connector """ if self.started == False: self.started = True self._setup_physics_model() # After on_setup def _get_pymunk_shape(self): # Sets the shape-type_update_from_physics if self.shape_type.lower() == "rect": shape = pymunk.Poly.create_box(self._body, (self.size[0] * self.token.width, self.size[1] * self.token.height), 2 # small radius ) elif self.shape_type.lower() == "circle": shape = pymunk.Circle(self._body, self.size[0] * self.token.width / 2, (0, 0), ) elif self.shape_type.lower() == "line": shift_x = self.token.size[0] / 2 + self.token.position_manager.get_position()[0] shift_y = self.token.size[1] / 2 + self.token.position_manager.get_position()[1] start = pymunk.pygame_util.from_pygame( (self.token.start_position[0] - shift_x, self.token.start_position[1] - shift_y), self.token.board.image) end = pymunk.pygame_util.from_pygame( (self.token.end_position[0] - shift_x, self.token.end_position[1] - shift_y), self.token.board.image) shape = pymunk.Segment(self._body, start, end, self.token.border) else: raise AttributeError("No shape set!") return shape def _setup_physics_model(self): if self.dirty and self.token.position_manager.get_position(): # if token is on board # create body self.has_physics = False self._body = pymunk_engine.Body(body_type=self.body_type) self._set_pymunk_position() self._set_pymunk_direction() self._body.size = (self.size[0] * self.token.width, self.size[1] * self.token.height) # disable velocity for tokens if token has no gravity if self.simulation == "static": self._body.velocity_func = lambda body, gravity, damping, dt: None else: self._body.velocity = self.velocity_x, self._velocity_y # Adds object to space if self._simulation != None: self._shape = self._get_pymunk_shape() self._shape.density = self.density self._shape.friction = self.friction self._shape.elasticity = self.elasticity self._shape.token = self.token self._shape.collision_type = hash( self.token.__class__.__name__) % ((sys.maxsize + 1) * 2) self.board.space.add(self._body, self._shape) if self.simulation == "static": self.board.space.reindex_static() self.dirty = 1 self.has_physics = True def _set_pymunk_position(self): pymunk_position = self.token.center[0], self.token.center[1] self._body.position = pymunk.pygame_util.from_pygame( pymunk_position, self.token.board.image) def _set_pymunk_direction(self): self._body.angle = self.token.position_manager.get_pymunk_direction_from_miniworldmaker() def _set_mwm_token_position(self): if self._body: self.token.center = pymunk.pygame_util.from_pygame( self._body.position, self.token.board.image) self.dirty = 0 def _set_mwm_token_direction(self): self.token.position_manager.set_mwm_direction_from_pymunk() self.dirty = 0
[docs] def reload(self): """Removes token from space and reloads physics_model :return: """ if self.started: self.dirty = 1 # Remove shape and body from space self._remove_from_space() # Set new properties and reset to space self._setup_physics_model() else: self.dirty = 1
def _remove_from_space(self): if self._body: for shape in list(self._body.shapes): self.board.space.remove(shape) if self._body in self.board.space.bodies: self.board.space.remove(self._body)
[docs] def remove(self): """Removes an object from physics-space """ self._remove_from_space()
@property def simulation(self): """Sets simulation type for token (static, manual, simulated or None) Sets simulation type for token: * simulated: Token is fully simulated by physics engine. * manual: Token is not affected by gravity. * static: Token is not moved by physics engine, but tokens can collide with token. * None: Token is not moved by physics engine and other tokens can't collige with token. """ return self._simulation @simulation.setter def simulation(self, value: Union[str, None]): # Sets the body type: # dynamic: Influenced by physic (e.g. actors) # static: not influenced by physics (e.g. plattforms) # kinematic: e.g. moving plattforms self._simulation = value if value == None: self._is_rotatable = False self._gravity = False self._can_move = False self._stable = True elif value.lower() == "static": self._is_rotatable = False self._gravity = False self._can_move = False self._stable = True self.density = 0 elif value.lower() == "manual": self._is_rotatable = True self._gravity = False self._can_move = True self._stable = True elif value.lower() == "simulated": self._is_rotatable = False self._gravity = True self._can_move = True self._stable = False self.dirty = 1 self.reload() @property def body_type(self): """Returns body type of token Must not be used from outside - Use property simulation instead. """ if self.simulation is None or self.simulation == "static": return pymunk.Body.STATIC elif self.simulation == "manual": return pymunk.Body.KINEMATIC else: return pymunk.Body.DYNAMIC @property def size(self): """Sets size of physics_object in relation to object * 1: Physics object size equals token size * < 1: Physics object is smaller than token * > 1: Physics object is larger than token. .. warning:: Token is re-added to physics space after this operation - Velocity and impulses are lost. """ return self._size @size.setter def size(self, value: tuple): self._size = value self.dirty = 1 self.reload() @property def shape_type(self): """Sets shape type of object: Shape Types: * "rect": Rectangle * "circle": Circle (Planned for future relases: autogeometry) .. warning:: Token is re-added to physics space after this operation - Velocity and impulses are lost. Examples: Demonstrate different shape types: .. code-block:: python from miniworldmaker import * board = PhysicsBoard(600,300) Line((0,100),(100,150)) t = Token((0,50)) t.physics.shape_type = "rect" Line((200,100),(300,150)) t = Token((200,50)) t.physics.shape_type = "circle" board.run() .. raw:: html <video loop autoplay muted width=400> <source src="../_static/shape_types.webm" type="video/webm"> <source src="../_static/shape_types.mp4" type="video/mp4"> Your browser does not support the video tag. </video> """ return self._shape_type @shape_type.setter def shape_type(self, value: str): self._shape_type = value self.dirty = 1 self.reload() @property def friction(self): """Sets friction of token .. warning:: Token is re-added to physics space after this operation - Velocity and impulses are lost. """ return self._friction @friction.setter def friction(self, value: float): self._friction = value self.dirty = 1 self.reload() @property def elasticity(self): """Sets elasticity of token .. warning:: Token is re-added to physics space after this operation - Velocity and impulses are lost. """ return self._elasticity @elasticity.setter def elasticity(self, value: float): self._elasticity = value self.dirty = 1 self.reload() @property def density(self): """Sets density of token .. warning:: Token is re-added to physics space after this operation - Velocity and impulses are lost. """ return self._density @density.setter def density(self, value: float): self._density = value self.dirty = 1 self.reload() def _simulation_preprocess_token(self): """ Updates the physics model in every frame Returns: """ if (self._body and not self._body.body_type == pymunk_engine.Body.STATIC) and self.dirty: self._set_pymunk_position() self._set_pymunk_direction() self.board.space.reindex_shapes_for_body(self._body) self.dirty = 0 def _set_update_mode(self): self._update_from_physics = True def _unset_update_mode(self): self._update_from_physics = False def _is_in_update_mode(self): return self._update_from_physics def _simulation_postprocess_token(self): """ Reloads physics model from pygame data Returns: """ if not math.isnan(self._body.position[0]): self._set_mwm_token_position() self._set_mwm_token_direction() if self._body and not self._body.body_type == pymunk_engine.Body.STATIC: self.velocity_x = self._body.velocity[0] self.velocity_y = self._body.velocity[1] if self.board.debug: options = pymunk.pygame_util.DrawOptions(self.token.board.image) options.collision_point_color = (255, 20, 30, 40) self.board.space.debug_draw(options) @property def velocity_x(self): """Sets velocity in x-direction :return: """ return self._velocity_x @velocity_x.setter def velocity_x(self, value: float): self._velocity_x = value if self._body: self._body.velocity = value, self._body.velocity[1] @property def velocity_y(self): """Sets velocity in y-direction :return: """ return self._velocity_y @velocity_y.setter def velocity_y(self, value: float): self._velocity_y = value if self._body: self._body.velocity = self._body.velocity[0], value @property def is_rotatable(self): """defines, if token will be rotated by physics-engine? """ return self._is_rotatable @is_rotatable.setter def is_rotatable(self, value: bool): self._is_rotatable = value
[docs] def impulse_in_direction(self, direction: float, power: float): """ Adds an impulse in token-direction Examples: .. code-block:: python from miniworldmaker import * board = PhysicsBoard(300, 200) rect = Rectangle((280,120), 20, 80) rect.physics.simulation = "manual" ball = Circle((50,50),20) @rect.register def act(self): rect.x -= 1 if rect.x == 0: rect.x = 280 @ball.register def on_key_down(self, key): self.physics.impulse_in_direction(0, 5000) board.run() Args: power: The power-value of the impulse. direction: pymunk direction """ impulse = pymunk.Vec2d(1, 0) impulse = impulse.rotated_degrees( 360 - self.token.position_manager.dir_to_unit_circle(direction - self.token.direction)) impulse = power * 1000 * impulse.normalized() self._body.apply_impulse_at_local_point(impulse)
[docs] def force_in_direction(self, direction: float, power: float): """ Adds an force in token-direction Args: power: The power-value of the force. direction: pymunk direction """ force = pymunk.Vec2d(1, 0) force = force.rotated_degrees( 360 - self.token.position_manager.dir_to_unit_circle(direction - self.token.direction)) force = power * 10000 * force.normalized() self._body.apply_force_at_local_point(force, (0, 0))