robosim.systems
systems.py - Generic systems in robosim. Should only use entities and components and low level utilities. Import level: 3
1""" 2systems.py - Generic systems in robosim. Should only use entities and components and low level utilities. 3Import level: 3 4""" 5import numpy as np 6from microecs import World 7 8from robosim.camera import Camera 9from robosim.utils import Point3D 10from robosim.physics import motion_level1, motion_level2, make_grid_3d, check_collision, integrate_velocity_into_pose 11from robosim.components import (HasMotionInput, HasPose, HasCollision, HasVelocity6DoF, HasAcceleration6DoF, 12 HasPhysicsLevel1, HasPhysicsLevel2, HasModel, HasFPV) 13 14class FPVCameraSystem: 15 """FPV camera system. Basically just sets the position of the camera at each frame""" 16 def __call__(self, world: World): 17 qr = world.query(HasPose, HasFPV) 18 cam: Camera 19 for cam, pose in zip(map(lambda x: x.item(), qr.fpv_camera), qr.pose): 20 cam.set_position(position=pose[0:3, 3], up=pose[0:3, 1], target=(pose[0:3, 3] + pose[0:3, 2])) 21 22class PhysicsSystem: 23 """ 24 A orchestration physics system that runs each subtick. It does 4 steps atomically: 25 - candidate motion: move robots based on their physics 'level' (+other forces) 26 - detect collisions and other similar physical events on the candidate new pose 27 - update the candidate position based on the collisions 28 - commit the new physics state 29 """ 30 31 def _motion_from_inputs(self, world: World, dt: float, physics_levels: list[str] | None = None): 32 physics_levels = physics_levels or ["level1", "level2"] 33 for physics_level in physics_levels: 34 if physics_level == "level1": 35 qr = world.query(HasPhysicsLevel1, HasMotionInput, HasVelocity6DoF) 36 else: # level 2 37 qr = world.query(HasPhysicsLevel2, HasMotionInput, HasVelocity6DoF, HasAcceleration6DoF) 38 39 if len(qr) == 0: 40 continue 41 42 if physics_level == "level1": 43 vel = motion_level1(motion_input=qr.motion_input, max_velocities=qr.max_velocities) 44 qr.candidate_velocity[:] = vel 45 else: # level 2 46 vel, acc = motion_level2(motion_input=qr.motion_input, dt=dt, current_velocity=qr.velocity, 47 max_accelerations=qr.max_accelerations, drag_coefficient=qr.drag_coefficient) 48 qr.candidate_velocity[:] = vel 49 qr.candidate_acceleration[:] = acc 50 51 def _integrate_velocity_into_pose(self, world: World, dt: float): 52 qr = world.query(HasPose, HasVelocity6DoF) 53 qr.candidate_pose[:] = integrate_velocity_into_pose(qr.pose, qr.candidate_velocity, dt) 54 55 def _detect_collisions(self, world: World, cell_size: Point3D): 56 qr = world.query(HasModel, HasCollision, HasPose) 57 58 # Step 1) Update the static collider model using the future pose 59 qr.collider_radii[:] = qr.model_radius * qr.scale 60 qr.collider_bbox[:] = qr.candidate_pose[:, 0:3, 3][:, None] + qr.model_bbox * qr.scale[..., None] 61 62 entities = [world.get_entity(eid) for eid in qr.entity_ids] 63 grid = make_grid_3d(entities, cell_size) 64 65 qr.is_colliding[:] = False 66 for cell_entities in grid.values(): 67 for i, e1 in enumerate(cell_entities): 68 for e2 in cell_entities[i+1:]: 69 if check_collision(e1, e2): 70 e1.is_colliding = e2.is_colliding = True 71 72 def _update_motion_from_collisions(self, world: World): 73 qr = world.query(HasPose, HasCollision, HasVelocity6DoF) 74 # NOTE: bounce back by negating velocity. We should use some sort of impulse addition + normals 75 # here to figure out the direction of the velocity. 76 qr.candidate_pose[:] = np.where(qr.is_colliding[..., None], qr.pose, qr.candidate_pose) 77 qr.candidate_velocity[:] = np.where(qr.is_colliding, -qr.candidate_velocity, qr.candidate_velocity) 78 79 def _commit(self, world: World): 80 qr = world.query(HasPose, HasVelocity6DoF) 81 qr.pose[:] = qr.candidate_pose 82 qr.velocity[:] = qr.candidate_velocity 83 qr = world.query(HasAcceleration6DoF) 84 qr.acceleration[:] = qr.candidate_acceleration 85 86 def __call__(self, world: World, dt: float, cell_size: Point3D, physics_levels: list[str] | None = None): 87 self._motion_from_inputs(world, dt, physics_levels) 88 self._integrate_velocity_into_pose(world, dt) 89 self._detect_collisions(world, cell_size) 90 self._update_motion_from_collisions(world) 91 self._commit(world)
class
FPVCameraSystem:
15class FPVCameraSystem: 16 """FPV camera system. Basically just sets the position of the camera at each frame""" 17 def __call__(self, world: World): 18 qr = world.query(HasPose, HasFPV) 19 cam: Camera 20 for cam, pose in zip(map(lambda x: x.item(), qr.fpv_camera), qr.pose): 21 cam.set_position(position=pose[0:3, 3], up=pose[0:3, 1], target=(pose[0:3, 3] + pose[0:3, 2]))
FPV camera system. Basically just sets the position of the camera at each frame
class
PhysicsSystem:
23class PhysicsSystem: 24 """ 25 A orchestration physics system that runs each subtick. It does 4 steps atomically: 26 - candidate motion: move robots based on their physics 'level' (+other forces) 27 - detect collisions and other similar physical events on the candidate new pose 28 - update the candidate position based on the collisions 29 - commit the new physics state 30 """ 31 32 def _motion_from_inputs(self, world: World, dt: float, physics_levels: list[str] | None = None): 33 physics_levels = physics_levels or ["level1", "level2"] 34 for physics_level in physics_levels: 35 if physics_level == "level1": 36 qr = world.query(HasPhysicsLevel1, HasMotionInput, HasVelocity6DoF) 37 else: # level 2 38 qr = world.query(HasPhysicsLevel2, HasMotionInput, HasVelocity6DoF, HasAcceleration6DoF) 39 40 if len(qr) == 0: 41 continue 42 43 if physics_level == "level1": 44 vel = motion_level1(motion_input=qr.motion_input, max_velocities=qr.max_velocities) 45 qr.candidate_velocity[:] = vel 46 else: # level 2 47 vel, acc = motion_level2(motion_input=qr.motion_input, dt=dt, current_velocity=qr.velocity, 48 max_accelerations=qr.max_accelerations, drag_coefficient=qr.drag_coefficient) 49 qr.candidate_velocity[:] = vel 50 qr.candidate_acceleration[:] = acc 51 52 def _integrate_velocity_into_pose(self, world: World, dt: float): 53 qr = world.query(HasPose, HasVelocity6DoF) 54 qr.candidate_pose[:] = integrate_velocity_into_pose(qr.pose, qr.candidate_velocity, dt) 55 56 def _detect_collisions(self, world: World, cell_size: Point3D): 57 qr = world.query(HasModel, HasCollision, HasPose) 58 59 # Step 1) Update the static collider model using the future pose 60 qr.collider_radii[:] = qr.model_radius * qr.scale 61 qr.collider_bbox[:] = qr.candidate_pose[:, 0:3, 3][:, None] + qr.model_bbox * qr.scale[..., None] 62 63 entities = [world.get_entity(eid) for eid in qr.entity_ids] 64 grid = make_grid_3d(entities, cell_size) 65 66 qr.is_colliding[:] = False 67 for cell_entities in grid.values(): 68 for i, e1 in enumerate(cell_entities): 69 for e2 in cell_entities[i+1:]: 70 if check_collision(e1, e2): 71 e1.is_colliding = e2.is_colliding = True 72 73 def _update_motion_from_collisions(self, world: World): 74 qr = world.query(HasPose, HasCollision, HasVelocity6DoF) 75 # NOTE: bounce back by negating velocity. We should use some sort of impulse addition + normals 76 # here to figure out the direction of the velocity. 77 qr.candidate_pose[:] = np.where(qr.is_colliding[..., None], qr.pose, qr.candidate_pose) 78 qr.candidate_velocity[:] = np.where(qr.is_colliding, -qr.candidate_velocity, qr.candidate_velocity) 79 80 def _commit(self, world: World): 81 qr = world.query(HasPose, HasVelocity6DoF) 82 qr.pose[:] = qr.candidate_pose 83 qr.velocity[:] = qr.candidate_velocity 84 qr = world.query(HasAcceleration6DoF) 85 qr.acceleration[:] = qr.candidate_acceleration 86 87 def __call__(self, world: World, dt: float, cell_size: Point3D, physics_levels: list[str] | None = None): 88 self._motion_from_inputs(world, dt, physics_levels) 89 self._integrate_velocity_into_pose(world, dt) 90 self._detect_collisions(world, cell_size) 91 self._update_motion_from_collisions(world) 92 self._commit(world)
A orchestration physics system that runs each subtick. It does 4 steps atomically:
- candidate motion: move robots based on their physics 'level' (+other forces)
- detect collisions and other similar physical events on the candidate new pose
- update the candidate position based on the collisions
- commit the new physics state