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

builds a RLModelWithTexture from a path

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

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

def vec3_from(arr: numpy.ndarray) -> Any:
65def vec3_from(arr: np.ndarray) -> RlVector3:
66    """convers the numpy array to a raylib vector"""
67    return rl.ffi.from_buffer("Vector3 *", np.ascontiguousarray(arr))[0]

convers the numpy array to a raylib vector

def rl_vec3_to_np(vec: Any) -> numpy.ndarray:
69def rl_vec3_to_np(vec: RlVector3) -> np.ndarray:
70    """returns a np array from a raylib vec3"""
71    return np.array([vec.x, vec.y, vec.z], "float32")

returns a np array from a raylib vec3

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

returns the device and renderer used by raylib for logging