"""
Wrapper for a loaded mesh / vao with properties
"""
from __future__ import annotations
from typing import Optional
import glm
import moderngl
from moderngl_window.opengl.vao import VAO
from .camera import Camera
from .mesh import Mesh
[docs]
class Node:
"""A generic scene node containing a mesh or camera
and/or a container for other nodes. Nodes and their children
represents the scene tree.
"""
def __init__(
self,
name: Optional[str] = None,
camera: Optional[Camera] = None,
mesh: Optional[Mesh] = None,
matrix: Optional[glm.mat4] = None,
):
"""Create a node.
Keyword Args:
name: Name of the node
camera: Camera to store in the node
mesh: Mesh to store in the node
matrix: The node's matrix
"""
self._name = name
self._camera = camera
self._mesh = mesh
# Local node matrix
self._matrix = matrix
# Global matrix
self._matrix_global = glm.mat4(1.0)
self._children: list["Node"] = []
@property
def name(self) -> Optional[str]:
"""str: Get or set the node name"""
return self._name
@name.setter
def name(self, value: str) -> None:
self._name = value
@property
def mesh(self) -> Optional[Mesh]:
""":py:class:`~moderngl_window.scene.Mesh`: The mesh if present"""
return self._mesh
@mesh.setter
def mesh(self, value: Mesh) -> None:
self._mesh = value
@property
def camera(self) -> Optional[Camera]:
""":py:class:`~moderngl_window.scene.Camera`: The camera if present"""
return self._camera
@camera.setter
def camera(self, value: Camera) -> None:
self._camera = value
@property
def matrix(self) -> Optional[glm.mat4]:
"""glm.mat4x4: Note matrix (local)"""
return self._matrix
@matrix.setter
def matrix(self, value: glm.mat4) -> None:
self._matrix = value
@property
def matrix_global(self) -> Optional[glm.mat4]:
"""glm.matx4: The global node matrix containing transformations from parent nodes"""
return self._matrix_global
@matrix_global.setter
def matrix_global(self, value: glm.mat4) -> None:
self._matrix_global = value
@property
def children(self) -> list["Node"]:
"""list: List of children"""
return self._children
[docs]
def add_child(self, node: "Node") -> None:
"""Add a child to this node
Args:
node (Node): Node to add as a child
"""
self._children.append(node)
[docs]
def draw(
self,
projection_matrix: glm.mat4,
camera_matrix: glm.mat4,
time: float = 0.0,
) -> None:
"""Draw node and children.
Keyword Args:
projection_matrix: projection matrix
camera_matrix: camera_matrix
time: The current time
"""
if self._mesh:
self._mesh.draw(
projection_matrix=projection_matrix,
model_matrix=self._matrix_global,
camera_matrix=camera_matrix,
time=time,
)
for child in self._children:
child.draw(
projection_matrix=projection_matrix,
camera_matrix=camera_matrix,
time=time,
)
[docs]
def draw_bbox(
self,
projection_matrix: Optional[glm.mat4],
camera_matrix: Optional[glm.mat4],
program: moderngl.Program,
vao: VAO,
) -> None:
"""Draw bounding box around the node and children.
Keyword Args:
projection_matrix: projection matrix
camera_matrix: camera_matrix
program (moderngl.Program): The program to render the bbox
vao: The vertex array representing the bounding box
"""
if self._mesh:
assert (
projection_matrix is not None
), "Can not draw bbox, the projection matrix is empty"
assert self._matrix_global is not None, "Can not draw bbox, the global matrix is empty"
assert camera_matrix is not None, "Can not draw bbox, the camera matrix is empty"
self._mesh.draw_bbox(
projection_matrix, self._matrix_global, camera_matrix, program, vao
)
for child in self.children:
child.draw_bbox(projection_matrix, camera_matrix, program, vao)
[docs]
def draw_wireframe(
self,
projection_matrix: Optional[glm.mat4],
camera_matrix: Optional[glm.mat4],
program: moderngl.Program,
) -> None:
"""Render the node as wireframe.
Keyword Args:
projection_matrix (bytes): projection matrix
camera_matrix (bytes): camera_matrix
program (moderngl.Program): The program to render wireframe
"""
if self._mesh:
assert (
projection_matrix is not None
), "Can not draw bbox, the projection matrix is empty"
assert self._matrix_global is not None, "Can not draw bbox, the global matrix is empty"
self._mesh.draw_wireframe(projection_matrix, self._matrix_global, program)
for child in self.children:
child.draw_wireframe(projection_matrix, self._matrix_global, program)
[docs]
def calc_global_bbox(
self, view_matrix: glm.mat4, bbox_min: glm.vec3 | None, bbox_max: glm.vec3 | None
) -> tuple[glm.vec3, glm.vec3]:
"""Recursive calculation of scene bbox.
Keyword Args:
view_matrix (numpy.ndarray): view matrix
bbox_min: min bbox values
bbox_max: max bbox values
"""
if self._matrix is not None:
view_matrix = self._matrix * view_matrix
if self._mesh:
bbox_min, bbox_max = self._mesh.calc_global_bbox(view_matrix, bbox_min, bbox_max)
for child in self._children:
bbox_min, bbox_max = child.calc_global_bbox(view_matrix, bbox_min, bbox_max)
return bbox_min, bbox_max
[docs]
def calc_model_mat(self, parent_matrix: glm.mat4) -> None:
"""Calculate the model matrix related to all parents.
Args:
parent_matrix: Matrix for parent node
"""
if self._matrix is not None:
self._matrix_global = parent_matrix * self._matrix
for child in self._children:
child.calc_model_mat(self._matrix_global)
else:
self._matrix_global = parent_matrix
for child in self._children:
child.calc_model_mat(parent_matrix)
def __repr__(self) -> str:
return "<Node name={}>".format(self.name)