robolib.camera
camera.py - raylib camera wrapper Import level: 2
1""" 2camera.py - raylib camera wrapper 3Import level: 2 4""" 5from __future__ import annotations 6import math 7 8import raylib as rl 9import numpy as np 10 11from robolib.utils import Point3D, vec3_from, make_arr, rl_vec3_to_np 12from robolib.traits import Serializable 13 14WORLD_CAMERA_SPEED = 10 15WORLD_CAMERA_SPEED_FAST = 30 16WORLD_CAMERA_SENSITIVITY = 0.3 17WORLD_CAMERA_SCROLL_SPEED = 0.1 18TOPDOWN_CAMERA_SPEED = 1 19TOPDOWN_CAMERA_ZOOM_SPEED = 1 20TOPDOWN_LOG_CONSTANT = math.log(2, 1.2) # convert from base 2 to base 1.2 for smooth and height-aware scrolling 21 22class Camera(Serializable): 23 """Raylib camera rapper. 24 Parameters: 25 - fovy (float) Camera field of view 26 - projection (int) The type of projection (default: perspective) 27 - ptu (dict) [position, target, up] 28 - label (str) A string denoting the camera label for human interpretation 29 """ 30 def __init__(self, fovy: float = 60, projection: int = rl.CAMERA_PERSPECTIVE, 31 ptu: dict[str, Point3D] | None = None, manual_speed: float | None = None, 32 manual_speed_fast: float | None = None, label: str="Camera"): 33 self.ptu = ptu or {"position": make_arr(15, 12, 15), "target": make_arr(0, 2, 0), "up": make_arr(0, 1, 0)} 34 # Note: should be GC'd automatically. 35 self.camera = rl.ffi.new("Camera3D *", [vec3_from(self.ptu['position']), vec3_from(self.ptu['target']), 36 vec3_from(self.ptu['up']), fovy, projection]) 37 self._key2move = {rl.KEY_W: make_arr(1, 0, 0), rl.KEY_S: make_arr(-1, 0, 0), 38 rl.KEY_D: make_arr(0, 1, 0), rl.KEY_A: make_arr(0, -1, 0)} 39 40 self.manual_speed = manual_speed or WORLD_CAMERA_SPEED 41 self.manual_speed_fast = manual_speed_fast or WORLD_CAMERA_SPEED_FAST 42 self.label = label 43 44 def to_dict(self) -> dict: 45 """returns the dict representation of a raylib camera""" 46 return { 47 "position": (self.camera.position.x, self.camera.position.y, self.camera.position.z), 48 "target": (self.camera.target.x, self.camera.target.y, self.camera.target.z), 49 "up": (self.camera.up.x, self.camera.up.y, self.camera.up.z), 50 "fovy": self.camera.fovy, 51 "projection": ["perspective", "orthographic"][self.camera.projection], 52 "manual_speed": self.manual_speed, 53 "manual_speed_fast": self.manual_speed_fast, 54 "label": self.label, 55 } 56 57 @staticmethod 58 def from_dict(state: dict) -> Camera: 59 """Creates a raylib camera from a dict representation.""" 60 projection = {"perspective": rl.CAMERA_PERSPECTIVE, "orthographic": rl.CAMERA_ORTHOGRAPHIC}[state["projection"]] 61 ptu = {"position": np.float32(state["position"]), "target": np.float32(state["target"]), 62 "up": np.float32(state["up"])} 63 return Camera(ptu=ptu, fovy=state["fovy"], projection=projection, manual_speed=state["manual_speed"], 64 manual_speed_fast=state["manual_speed_fast"], label=state["label"]) 65 66 def set_pose(self, position: Point3D, target: Point3D, up: Point3D): 67 """Sets the camera pose given a position and 2 vectors: target and up""" 68 self.camera[0].position = vec3_from(position) 69 self.camera[0].target = vec3_from(target) 70 self.camera[0].up = vec3_from(up) 71 72 def get_pose(self) -> tuple[Point3D, Point3D, Point3D]: 73 """Gets the camera pose as 3 vectors: position, target and up""" 74 c = self.camera[0] 75 return rl_vec3_to_np(c.position), rl_vec3_to_np(c.target), rl_vec3_to_np(c.up) 76 77 def _world_camera_movement(self): 78 # move in the direction of the center of the viewport (screen). Can use (ctrl+) mouse scroll or WASD. 79 fwd_keys = rl.IsKeyDown(rl.KEY_W) - rl.IsKeyDown(rl.KEY_S) 80 side_keys = rl.IsKeyDown(rl.KEY_D) - rl.IsKeyDown(rl.KEY_A) 81 scroll = np.sign(rl.GetMouseWheelMove()).item() 82 83 if fwd_keys != 0 or side_keys !=0 or scroll != 0: 84 direction = rl_vec3_to_np(self.camera.target) - rl_vec3_to_np(self.camera.position) 85 direction /= np.linalg.norm(direction) 86 right = np.cross(direction, rl_vec3_to_np(self.camera.up)) 87 speed = self.manual_speed_fast if rl.IsKeyDown(rl.KEY_LEFT_SHIFT) else self.manual_speed 88 89 move = (fwd_keys * direction + side_keys * right) * speed * rl.GetFrameTime() # continuous → dt 90 move += scroll * direction * WORLD_CAMERA_SCROLL_SPEED * speed # discrete → NO dt 91 92 self.camera.position = (rl_vec3_to_np(self.camera.position) + move).tolist() 93 self.camera.target = (rl_vec3_to_np(self.camera.target) + move).tolist() 94 95 def manual_movement(self): 96 """Moves the simulator's active camera manually. Only valid for world camera and topdown camera.""" 97 if self.camera[0].projection not in (rl.CAMERA_PERSPECTIVE, rl.CAMERA_ORTHOGRAPHIC): 98 return 99 100 if self.camera[0].projection == rl.CAMERA_PERSPECTIVE: # world camera 101 self._world_camera_movement() 102 else: # topdown camera 103 speed = math.log2(1 + self.camera[0].fovy) / TOPDOWN_LOG_CONSTANT # height-aware topdown movemenmt speed 104 delta = sum(v * rl.IsKeyDown(k) * speed for k, v in self._key2move.items()) 105 106 self.camera[0].position.x += delta[1] # left/right 107 self.camera[0].target.x += delta[1] # left/right 108 self.camera[0].position.z -= delta[0] # up/down 109 self.camera[0].target.z -= delta[0] # up/down 110 self.camera[0].fovy = max(1.0, self.camera[0].fovy - rl.GetMouseWheelMove() * TOPDOWN_CAMERA_ZOOM_SPEED) 111 112 def __repr__(self): 113 return f"[{self.label}] {self.to_dict()}"
WORLD_CAMERA_SPEED =
10
WORLD_CAMERA_SPEED_FAST =
30
WORLD_CAMERA_SENSITIVITY =
0.3
WORLD_CAMERA_SCROLL_SPEED =
0.1
TOPDOWN_CAMERA_SPEED =
1
TOPDOWN_CAMERA_ZOOM_SPEED =
1
TOPDOWN_LOG_CONSTANT =
3.8017840169239308
23class Camera(Serializable): 24 """Raylib camera rapper. 25 Parameters: 26 - fovy (float) Camera field of view 27 - projection (int) The type of projection (default: perspective) 28 - ptu (dict) [position, target, up] 29 - label (str) A string denoting the camera label for human interpretation 30 """ 31 def __init__(self, fovy: float = 60, projection: int = rl.CAMERA_PERSPECTIVE, 32 ptu: dict[str, Point3D] | None = None, manual_speed: float | None = None, 33 manual_speed_fast: float | None = None, label: str="Camera"): 34 self.ptu = ptu or {"position": make_arr(15, 12, 15), "target": make_arr(0, 2, 0), "up": make_arr(0, 1, 0)} 35 # Note: should be GC'd automatically. 36 self.camera = rl.ffi.new("Camera3D *", [vec3_from(self.ptu['position']), vec3_from(self.ptu['target']), 37 vec3_from(self.ptu['up']), fovy, projection]) 38 self._key2move = {rl.KEY_W: make_arr(1, 0, 0), rl.KEY_S: make_arr(-1, 0, 0), 39 rl.KEY_D: make_arr(0, 1, 0), rl.KEY_A: make_arr(0, -1, 0)} 40 41 self.manual_speed = manual_speed or WORLD_CAMERA_SPEED 42 self.manual_speed_fast = manual_speed_fast or WORLD_CAMERA_SPEED_FAST 43 self.label = label 44 45 def to_dict(self) -> dict: 46 """returns the dict representation of a raylib camera""" 47 return { 48 "position": (self.camera.position.x, self.camera.position.y, self.camera.position.z), 49 "target": (self.camera.target.x, self.camera.target.y, self.camera.target.z), 50 "up": (self.camera.up.x, self.camera.up.y, self.camera.up.z), 51 "fovy": self.camera.fovy, 52 "projection": ["perspective", "orthographic"][self.camera.projection], 53 "manual_speed": self.manual_speed, 54 "manual_speed_fast": self.manual_speed_fast, 55 "label": self.label, 56 } 57 58 @staticmethod 59 def from_dict(state: dict) -> Camera: 60 """Creates a raylib camera from a dict representation.""" 61 projection = {"perspective": rl.CAMERA_PERSPECTIVE, "orthographic": rl.CAMERA_ORTHOGRAPHIC}[state["projection"]] 62 ptu = {"position": np.float32(state["position"]), "target": np.float32(state["target"]), 63 "up": np.float32(state["up"])} 64 return Camera(ptu=ptu, fovy=state["fovy"], projection=projection, manual_speed=state["manual_speed"], 65 manual_speed_fast=state["manual_speed_fast"], label=state["label"]) 66 67 def set_pose(self, position: Point3D, target: Point3D, up: Point3D): 68 """Sets the camera pose given a position and 2 vectors: target and up""" 69 self.camera[0].position = vec3_from(position) 70 self.camera[0].target = vec3_from(target) 71 self.camera[0].up = vec3_from(up) 72 73 def get_pose(self) -> tuple[Point3D, Point3D, Point3D]: 74 """Gets the camera pose as 3 vectors: position, target and up""" 75 c = self.camera[0] 76 return rl_vec3_to_np(c.position), rl_vec3_to_np(c.target), rl_vec3_to_np(c.up) 77 78 def _world_camera_movement(self): 79 # move in the direction of the center of the viewport (screen). Can use (ctrl+) mouse scroll or WASD. 80 fwd_keys = rl.IsKeyDown(rl.KEY_W) - rl.IsKeyDown(rl.KEY_S) 81 side_keys = rl.IsKeyDown(rl.KEY_D) - rl.IsKeyDown(rl.KEY_A) 82 scroll = np.sign(rl.GetMouseWheelMove()).item() 83 84 if fwd_keys != 0 or side_keys !=0 or scroll != 0: 85 direction = rl_vec3_to_np(self.camera.target) - rl_vec3_to_np(self.camera.position) 86 direction /= np.linalg.norm(direction) 87 right = np.cross(direction, rl_vec3_to_np(self.camera.up)) 88 speed = self.manual_speed_fast if rl.IsKeyDown(rl.KEY_LEFT_SHIFT) else self.manual_speed 89 90 move = (fwd_keys * direction + side_keys * right) * speed * rl.GetFrameTime() # continuous → dt 91 move += scroll * direction * WORLD_CAMERA_SCROLL_SPEED * speed # discrete → NO dt 92 93 self.camera.position = (rl_vec3_to_np(self.camera.position) + move).tolist() 94 self.camera.target = (rl_vec3_to_np(self.camera.target) + move).tolist() 95 96 def manual_movement(self): 97 """Moves the simulator's active camera manually. Only valid for world camera and topdown camera.""" 98 if self.camera[0].projection not in (rl.CAMERA_PERSPECTIVE, rl.CAMERA_ORTHOGRAPHIC): 99 return 100 101 if self.camera[0].projection == rl.CAMERA_PERSPECTIVE: # world camera 102 self._world_camera_movement() 103 else: # topdown camera 104 speed = math.log2(1 + self.camera[0].fovy) / TOPDOWN_LOG_CONSTANT # height-aware topdown movemenmt speed 105 delta = sum(v * rl.IsKeyDown(k) * speed for k, v in self._key2move.items()) 106 107 self.camera[0].position.x += delta[1] # left/right 108 self.camera[0].target.x += delta[1] # left/right 109 self.camera[0].position.z -= delta[0] # up/down 110 self.camera[0].target.z -= delta[0] # up/down 111 self.camera[0].fovy = max(1.0, self.camera[0].fovy - rl.GetMouseWheelMove() * TOPDOWN_CAMERA_ZOOM_SPEED) 112 113 def __repr__(self): 114 return f"[{self.label}] {self.to_dict()}"
Raylib camera rapper. Parameters:
- fovy (float) Camera field of view
- projection (int) The type of projection (default: perspective)
- ptu (dict) [position, target, up]
- label (str) A string denoting the camera label for human interpretation
Camera( fovy: float = 60, projection: int = 0, ptu: dict[str, numpy.ndarray] | None = None, manual_speed: float | None = None, manual_speed_fast: float | None = None, label: str = 'Camera')
31 def __init__(self, fovy: float = 60, projection: int = rl.CAMERA_PERSPECTIVE, 32 ptu: dict[str, Point3D] | None = None, manual_speed: float | None = None, 33 manual_speed_fast: float | None = None, label: str="Camera"): 34 self.ptu = ptu or {"position": make_arr(15, 12, 15), "target": make_arr(0, 2, 0), "up": make_arr(0, 1, 0)} 35 # Note: should be GC'd automatically. 36 self.camera = rl.ffi.new("Camera3D *", [vec3_from(self.ptu['position']), vec3_from(self.ptu['target']), 37 vec3_from(self.ptu['up']), fovy, projection]) 38 self._key2move = {rl.KEY_W: make_arr(1, 0, 0), rl.KEY_S: make_arr(-1, 0, 0), 39 rl.KEY_D: make_arr(0, 1, 0), rl.KEY_A: make_arr(0, -1, 0)} 40 41 self.manual_speed = manual_speed or WORLD_CAMERA_SPEED 42 self.manual_speed_fast = manual_speed_fast or WORLD_CAMERA_SPEED_FAST 43 self.label = label
def
to_dict(self) -> dict:
45 def to_dict(self) -> dict: 46 """returns the dict representation of a raylib camera""" 47 return { 48 "position": (self.camera.position.x, self.camera.position.y, self.camera.position.z), 49 "target": (self.camera.target.x, self.camera.target.y, self.camera.target.z), 50 "up": (self.camera.up.x, self.camera.up.y, self.camera.up.z), 51 "fovy": self.camera.fovy, 52 "projection": ["perspective", "orthographic"][self.camera.projection], 53 "manual_speed": self.manual_speed, 54 "manual_speed_fast": self.manual_speed_fast, 55 "label": self.label, 56 }
returns the dict representation of a raylib camera
58 @staticmethod 59 def from_dict(state: dict) -> Camera: 60 """Creates a raylib camera from a dict representation.""" 61 projection = {"perspective": rl.CAMERA_PERSPECTIVE, "orthographic": rl.CAMERA_ORTHOGRAPHIC}[state["projection"]] 62 ptu = {"position": np.float32(state["position"]), "target": np.float32(state["target"]), 63 "up": np.float32(state["up"])} 64 return Camera(ptu=ptu, fovy=state["fovy"], projection=projection, manual_speed=state["manual_speed"], 65 manual_speed_fast=state["manual_speed_fast"], label=state["label"])
Creates a raylib camera from a dict representation.
def
set_pose( self, position: numpy.ndarray, target: numpy.ndarray, up: numpy.ndarray):
67 def set_pose(self, position: Point3D, target: Point3D, up: Point3D): 68 """Sets the camera pose given a position and 2 vectors: target and up""" 69 self.camera[0].position = vec3_from(position) 70 self.camera[0].target = vec3_from(target) 71 self.camera[0].up = vec3_from(up)
Sets the camera pose given a position and 2 vectors: target and up
def
get_pose(self) -> tuple[numpy.ndarray, numpy.ndarray, numpy.ndarray]:
73 def get_pose(self) -> tuple[Point3D, Point3D, Point3D]: 74 """Gets the camera pose as 3 vectors: position, target and up""" 75 c = self.camera[0] 76 return rl_vec3_to_np(c.position), rl_vec3_to_np(c.target), rl_vec3_to_np(c.up)
Gets the camera pose as 3 vectors: position, target and up
def
manual_movement(self):
96 def manual_movement(self): 97 """Moves the simulator's active camera manually. Only valid for world camera and topdown camera.""" 98 if self.camera[0].projection not in (rl.CAMERA_PERSPECTIVE, rl.CAMERA_ORTHOGRAPHIC): 99 return 100 101 if self.camera[0].projection == rl.CAMERA_PERSPECTIVE: # world camera 102 self._world_camera_movement() 103 else: # topdown camera 104 speed = math.log2(1 + self.camera[0].fovy) / TOPDOWN_LOG_CONSTANT # height-aware topdown movemenmt speed 105 delta = sum(v * rl.IsKeyDown(k) * speed for k, v in self._key2move.items()) 106 107 self.camera[0].position.x += delta[1] # left/right 108 self.camera[0].target.x += delta[1] # left/right 109 self.camera[0].position.z -= delta[0] # up/down 110 self.camera[0].target.z -= delta[0] # up/down 111 self.camera[0].fovy = max(1.0, self.camera[0].fovy - rl.GetMouseWheelMove() * TOPDOWN_CAMERA_ZOOM_SPEED)
Moves the simulator's active camera manually. Only valid for world camera and topdown camera.