1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224
|
"""
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
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
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)
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,
)
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)
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)
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
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)
|