"""
Wrapper for a loaded scene with properties.
"""
from typing import TYPE_CHECKING
import logging
import numpy
from pyrr import matrix44, vector3
import moderngl
import moderngl_window as mglw
from moderngl_window.resources import programs
from moderngl_window.meta import ProgramDescription
from moderngl_window import geometry
from .programs import (
FallbackProgram,
VertexColorProgram,
ColorLightProgram,
MeshProgram,
TextureProgram,
TextureVertexColorProgram,
TextureLightProgram,
)
logger = logging.getLogger(__name__)
if TYPE_CHECKING:
from moderngl_window.scene import Node, Material
class Scene:
"""Generic scene"""
[docs] def __init__(self, name, **kwargs):
"""Create a scene with a name.
Args:
name (str): Unique name or path for the scene
"""
self.name = name
self.root_nodes = []
# References resources in the scene
self.nodes = []
self.materials = []
self.meshes = []
self.cameras = []
self.bbox_min = None # Type: numpy.ndarray
self.bbox_max = None # Type: numpy.ndarray
self.diagonal_size = 1.0
self.bbox_vao = geometry.bbox()
if self.ctx.extra is None:
self.ctx.extra = {}
# Load bbox program and cache in the context
self.bbox_program = self.ctx.extra.get("DEFAULT_BBOX_PROGRAM")
if not self.bbox_program:
self.bbox_program = programs.load(
ProgramDescription(path="scene_default/bbox.glsl"),
)
self.ctx.extra["DEFAULT_BBOX_PROGRAM"] = self.bbox_program
# Load wireframe program and cache in the context
self.wireframe_program = self.ctx.extra.get("DEFAULT_WIREFRAME_PROGRAM")
if not self.wireframe_program:
self.wireframe_program = programs.load(
ProgramDescription(path="scene_default/wireframe.glsl"),
)
self.ctx.extra["DEFAULT_WIREFRAME_PROGRAM"] = self.wireframe_program
self._matrix = matrix44.create_identity(dtype="f4")
@property
def ctx(self) -> moderngl.Context:
"""moderngl.Context: The current context"""
return mglw.ctx()
@property
def matrix(self) -> numpy.ndarray:
"""numpy.ndarray: The current model matrix
This property is settable.
"""
return self._matrix
@matrix.setter
def matrix(self, matrix: numpy.ndarray):
self._matrix = matrix.astype("f4")
for node in self.root_nodes:
node.calc_model_mat(self._matrix)
[docs] def draw(
self,
projection_matrix: numpy.ndarray = None,
camera_matrix: numpy.ndarray = None,
time=0.0,
) -> None:
"""Draw all the nodes in the scene.
Args:
projection_matrix (ndarray): projection matrix (bytes)
camera_matrix (ndarray): camera_matrix (bytes)
time (float): The current time
"""
for node in self.root_nodes:
node.draw(
projection_matrix=projection_matrix.astype("f4"),
camera_matrix=camera_matrix.astype("f4"),
time=time,
)
self.ctx.clear_samplers(0, 4)
[docs] def draw_bbox(
self,
projection_matrix=None,
camera_matrix=None,
children=True,
color=(0.75, 0.75, 0.75),
) -> None:
"""Draw scene and mesh bounding boxes.
Args:
projection_matrix (ndarray): mat4 projection
camera_matrix (ndarray): mat4 camera matrix
children (bool): Will draw bounding boxes for meshes as well
color (tuple): Color of the bounding boxes
"""
projection_matrix = projection_matrix.astype("f4")
camera_matrix = camera_matrix.astype("f4")
# Scene bounding box
self.bbox_program["m_proj"].write(projection_matrix)
self.bbox_program["m_model"].write(self._matrix)
self.bbox_program["m_cam"].write(camera_matrix)
self.bbox_program["bb_min"].write(self.bbox_min)
self.bbox_program["bb_max"].write(self.bbox_max)
self.bbox_program["color"].value = color
self.bbox_vao.render(self.bbox_program)
if not children:
return
# Draw bounding box for children
for node in self.root_nodes:
node.draw_bbox(
projection_matrix, camera_matrix, self.bbox_program, self.bbox_vao
)
[docs] def draw_wireframe(
self, projection_matrix=None, camera_matrix=None, color=(0.75, 0.75, 0.75, 1.0)
):
"""Render the scene in wireframe mode.
Args:
projection_matrix (ndarray): mat4 projection
camera_matrix (ndarray): mat4 camera matrix
children (bool): Will draw bounding boxes for meshes as well
color (tuple): Color of the wireframes
"""
projection_matrix = projection_matrix.astype("f4")
camera_matrix = camera_matrix.astype("f4")
self.wireframe_program["m_proj"].write(projection_matrix)
self.wireframe_program["m_model"].write(self._matrix)
self.wireframe_program["m_cam"].write(camera_matrix)
self.wireframe_program["color"] = color
# Draw bounding box for children
self.ctx.wireframe = True
for node in self.root_nodes:
node.draw_wireframe(
projection_matrix, camera_matrix, self.wireframe_program
)
self.ctx.wireframe = False
[docs] def apply_mesh_programs(self, mesh_programs=None, clear: bool = True) -> None:
"""Applies mesh programs to meshes.
If not mesh programs are passed in we assign default ones.
Args:
mesh_programs (list): List of mesh programs to assign
clear (bool): Clear all assigned mesh programs
"""
global DEFAULT_PROGRAMS
if clear:
for mesh in self.meshes:
mesh.mesh_program = None
if not mesh_programs:
mesh_programs = self.ctx.extra.get("DEFAULT_PROGRAMS")
if not mesh_programs:
mesh_programs = [
TextureLightProgram(),
TextureProgram(),
VertexColorProgram(),
TextureVertexColorProgram(),
ColorLightProgram(),
FallbackProgram(),
]
self.ctx.extra["DEFAULT_PROGRAMS"] = mesh_programs
for mesh in self.meshes:
for mesh_prog in mesh_programs:
instance = mesh_prog.apply(mesh)
if instance is not None:
if isinstance(instance, MeshProgram):
mesh.mesh_program = mesh_prog
break
else:
raise ValueError(
"apply() must return a MeshProgram instance, not {}".format(
type(instance)
)
)
if not mesh.mesh_program:
logger.warning("WARING: No mesh program applied to '%s'", mesh.name)
[docs] def calc_scene_bbox(self) -> None:
"""Calculate scene bbox"""
bbox_min, bbox_max = None, None
for node in self.root_nodes:
bbox_min, bbox_max = node.calc_global_bbox(
matrix44.create_identity(dtype="f4"), bbox_min, bbox_max
)
self.bbox_min = bbox_min
self.bbox_max = bbox_max
self.diagonal_size = vector3.length(self.bbox_max - self.bbox_min)
[docs] def prepare(self) -> None:
"""prepare the scene for rendering.
Calls ``apply_mesh_programs()`` assigning default meshprograms if needed
and sets the model matrix.
"""
self.apply_mesh_programs()
# Recursively calculate model matrices
self.matrix = matrix44.create_identity(dtype="f4")
[docs] def find_node(self, name: str = None) -> "Node":
"""Finds a :py:class:`~moderngl_window.scene.Node`
Keyword Args:
name (str): Case sensitive name
Returns:
A :py:class:`~moderngl_window.scene.Node` or ``None`` if not found.
"""
for node in self.nodes:
if node.name == name:
return node
return None
[docs] def find_material(self, name: str = None) -> "Material":
"""Finds a :py:class:`~moderngl_window.scene.Material`
Keyword Args:
name (str): Case sensitive material name
Returns:
A :py:class:`~moderngl_window.scene.Material` or ``None``
"""
for mat in self.materials:
if mat.name == name:
return mat
return None
[docs] def release(self):
"""Destroys the scene data and vertex buffers"""
self.destroy()
[docs] def destroy(self) -> None:
"""Destroys the scene data and vertex buffers"""
for mesh in self.meshes:
mesh.vao.release()
# if mesh.mesh_program:
# mesh.mesh_program.program.release()
for mat in self.materials:
mat.release()
self.meshes = []
self.root_nodes = []
def __str__(self) -> str:
return "<Scene: {}>".format(self.name)
def __repr__(self) -> str:
return str(self)