robolib.utils.raylibutils

raylibutils.py - functions to interact between python and raylib or helpers

 1"""raylibutils.py - functions to interact between python and raylib or helpers"""
 2from __future__ import annotations
 3from typing import Any
 4from dataclasses import dataclass
 5from pathlib import Path
 6
 7import raylib as rl
 8import numpy as np
 9
10from .utils import logger
11
12RlTexture = Any
13RlRenderTexture = Any
14RlModel = Any
15RlVector3 = Any
16RlMesh = Any
17
18def vec3_from(arr: np.ndarray) -> RlVector3:
19    """convers the numpy array to a raylib vector"""
20    return rl.ffi.from_buffer("Vector3 *", np.ascontiguousarray(arr))[0]
21
22@dataclass
23class RlModelWithTexture:
24    """basic raylib model with an optional texture applied to it"""
25    model: RlModel
26    texture: RlTexture | None
27    _bbox: tuple[np.ndarray, np.ndarray] | None = None
28
29    def __post_init__(self):
30        if self.texture is not None:
31            self.model.materials.maps[rl.MATERIAL_MAP_ALBEDO].texture = self.texture
32
33    @staticmethod
34    def from_path(model_path: Path | str, texture_path: Path | str | None = None) -> RlModelWithTexture:
35        """builds a RLModelWithTexture from a path"""
36        model_path = Path(model_path)
37        texture_path = texture_path and Path(texture_path)
38        assert not model_path.is_absolute(), model_path
39        assert texture_path is None or not texture_path.is_absolute(), texture_path
40        assert model_path.exists(), f"Model path: '{model_path}' doesn't exist."
41        logger.info(f"Loading model from '{model_path}'")
42        model = rl.LoadModel(str(model_path).encode())
43        texture = rl.LoadTexture(str(texture_path).encode()) if texture_path is not None else None
44        return RlModelWithTexture(model=model, texture=texture)
45
46    @property
47    def bbox(self) -> tuple[np.ndarray, np.ndarray]:
48        """gets the top left and bottom right 3D points of this mesh. Used for collisions. Cahed once after 1st call"""
49        if self._bbox is None:
50            mins, maxs = np.float32([1<<31, 1<<31, 1<<31]), np.float32([-1<<31, -1<<31, -1<<31])
51            for i in range(self.model.meshCount):
52                mesh: RlMesh = self.model.meshes[i] # of type struct Mesh &
53                buf = rl.ffi.buffer(mesh.vertices, mesh.vertexCount * 3 * 4)
54                vertices = np.frombuffer(buf, "float32").reshape(-1, 3)
55                mins = np.minimum(mins, np.min(vertices, axis=0))
56                maxs = np.maximum(maxs, np.max(vertices, axis=0))
57            self._bbox = mins, maxs
58        return self._bbox
59
60    def __del__(self):
61        if self.model is not None:
62            rl.UnloadModel(self.model)
63        if self.texture is not None:
64            rl.UnloadTexture(self.texture)
65
66def rl_get_device_and_renderer() -> tuple[str, str]:
67    """returns the device and renderer used by raylib for logging"""
68    gl_renderer = 0x1F01
69    gl_get_string = rl.ffi.cast("unsigned char *(*)(unsigned int)", rl.rlGetProcAddress(b"glGetString"))
70    gstr = lambda e: rl.ffi.string(rl.ffi.cast("char*", gl_get_string(e))).decode()
71    renderer = gstr(gl_renderer)
72    software = ("llvmpipe", "softpipe", "swrast", "lavapipe")
73    device = "CPU (software rasterizer)" if any(s in renderer.lower() for s in software) else "GPU"
74    return device, renderer
RlTexture = typing.Any
RlRenderTexture = typing.Any
RlModel = typing.Any
RlVector3 = typing.Any
RlMesh = typing.Any
def vec3_from(arr: numpy.ndarray) -> Any:
19def vec3_from(arr: np.ndarray) -> RlVector3:
20    """convers the numpy array to a raylib vector"""
21    return rl.ffi.from_buffer("Vector3 *", np.ascontiguousarray(arr))[0]

convers the numpy array to a raylib vector

@dataclass
class RlModelWithTexture:
23@dataclass
24class RlModelWithTexture:
25    """basic raylib model with an optional texture applied to it"""
26    model: RlModel
27    texture: RlTexture | None
28    _bbox: tuple[np.ndarray, np.ndarray] | None = None
29
30    def __post_init__(self):
31        if self.texture is not None:
32            self.model.materials.maps[rl.MATERIAL_MAP_ALBEDO].texture = self.texture
33
34    @staticmethod
35    def from_path(model_path: Path | str, texture_path: Path | str | None = None) -> RlModelWithTexture:
36        """builds a RLModelWithTexture from a path"""
37        model_path = Path(model_path)
38        texture_path = texture_path and Path(texture_path)
39        assert not model_path.is_absolute(), model_path
40        assert texture_path is None or not texture_path.is_absolute(), texture_path
41        assert model_path.exists(), f"Model path: '{model_path}' doesn't exist."
42        logger.info(f"Loading model from '{model_path}'")
43        model = rl.LoadModel(str(model_path).encode())
44        texture = rl.LoadTexture(str(texture_path).encode()) if texture_path is not None else None
45        return RlModelWithTexture(model=model, texture=texture)
46
47    @property
48    def bbox(self) -> tuple[np.ndarray, np.ndarray]:
49        """gets the top left and bottom right 3D points of this mesh. Used for collisions. Cahed once after 1st call"""
50        if self._bbox is None:
51            mins, maxs = np.float32([1<<31, 1<<31, 1<<31]), np.float32([-1<<31, -1<<31, -1<<31])
52            for i in range(self.model.meshCount):
53                mesh: RlMesh = self.model.meshes[i] # of type struct Mesh &
54                buf = rl.ffi.buffer(mesh.vertices, mesh.vertexCount * 3 * 4)
55                vertices = np.frombuffer(buf, "float32").reshape(-1, 3)
56                mins = np.minimum(mins, np.min(vertices, axis=0))
57                maxs = np.maximum(maxs, np.max(vertices, axis=0))
58            self._bbox = mins, maxs
59        return self._bbox
60
61    def __del__(self):
62        if self.model is not None:
63            rl.UnloadModel(self.model)
64        if self.texture is not None:
65            rl.UnloadTexture(self.texture)

basic raylib model with an optional texture applied to it

RlModelWithTexture( model: Any, texture: typing.Any | None, _bbox: tuple[numpy.ndarray, numpy.ndarray] | None = None)
model: Any
texture: typing.Any | None
@staticmethod
def from_path( model_path: pathlib.Path | str, texture_path: pathlib.Path | str | None = None) -> RlModelWithTexture:
34    @staticmethod
35    def from_path(model_path: Path | str, texture_path: Path | str | None = None) -> RlModelWithTexture:
36        """builds a RLModelWithTexture from a path"""
37        model_path = Path(model_path)
38        texture_path = texture_path and Path(texture_path)
39        assert not model_path.is_absolute(), model_path
40        assert texture_path is None or not texture_path.is_absolute(), texture_path
41        assert model_path.exists(), f"Model path: '{model_path}' doesn't exist."
42        logger.info(f"Loading model from '{model_path}'")
43        model = rl.LoadModel(str(model_path).encode())
44        texture = rl.LoadTexture(str(texture_path).encode()) if texture_path is not None else None
45        return RlModelWithTexture(model=model, texture=texture)

builds a RLModelWithTexture from a path

bbox: tuple[numpy.ndarray, numpy.ndarray]
47    @property
48    def bbox(self) -> tuple[np.ndarray, np.ndarray]:
49        """gets the top left and bottom right 3D points of this mesh. Used for collisions. Cahed once after 1st call"""
50        if self._bbox is None:
51            mins, maxs = np.float32([1<<31, 1<<31, 1<<31]), np.float32([-1<<31, -1<<31, -1<<31])
52            for i in range(self.model.meshCount):
53                mesh: RlMesh = self.model.meshes[i] # of type struct Mesh &
54                buf = rl.ffi.buffer(mesh.vertices, mesh.vertexCount * 3 * 4)
55                vertices = np.frombuffer(buf, "float32").reshape(-1, 3)
56                mins = np.minimum(mins, np.min(vertices, axis=0))
57                maxs = np.maximum(maxs, np.max(vertices, axis=0))
58            self._bbox = mins, maxs
59        return self._bbox

gets the top left and bottom right 3D points of this mesh. Used for collisions. Cahed once after 1st call

def rl_get_device_and_renderer() -> tuple[str, str]:
67def rl_get_device_and_renderer() -> tuple[str, str]:
68    """returns the device and renderer used by raylib for logging"""
69    gl_renderer = 0x1F01
70    gl_get_string = rl.ffi.cast("unsigned char *(*)(unsigned int)", rl.rlGetProcAddress(b"glGetString"))
71    gstr = lambda e: rl.ffi.string(rl.ffi.cast("char*", gl_get_string(e))).decode()
72    renderer = gstr(gl_renderer)
73    software = ("llvmpipe", "softpipe", "swrast", "lavapipe")
74    device = "CPU (software rasterizer)" if any(s in renderer.lower() for s in software) else "GPU"
75    return device, renderer

returns the device and renderer used by raylib for logging