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
class Camera(robolib.traits.Serializable):
 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
ptu
camera
manual_speed
manual_speed_fast
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

@staticmethod
def from_dict(state: dict) -> 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.