Source code for wasabisg.scenegraph

import itertools
import pyglet

from pyglet.graphics import Group
from OpenGL.GL import *
from OpenGL.GLU import *
from euclid import Matrix4, Point3, Vector3

from .renderer import LightingAccumulationRenderer
from .model import Model, Mesh


def v3(a, *args):
    """Helper for constructing a Point vector."""
    if not args:
        return Point3(*a)
    return Point3(a, *args)


class GLStateGroup(Group):
    def __init__(self, enable=[], disable=[], cull_face=None, depth_mask=None, parent=None):
        super(GLStateGroup, self).__init__(parent)
        self.enable = enable
        self.disable = disable
        self.cull_face = cull_face
        if cull_face:
            enable.append(GL_CULL_FACE)
        self.depth_mask = depth_mask

    def set_state(self):
        for e in self.enable:
            glEnable(e)
        for e in self.disable:
            glDisable(e)
        if self.cull_face:
            glCullFace(self.cull_face)
        if self.depth_mask is not None:
            glDepthMask(GL_TRUE if self.depth_mask else GL_FALSE)

    def unset_state(self):
        for e in self.enable:
            glDisable(e)
        for e in self.disable:
            glEnable(e)
        if self.depth_mask is not None:
            glDepthMask(GL_TRUE)


[docs]class ModelNode(object): """Draw a model at a point in space, with a rotation.""" def __init__(self, model, pos=(0, 0, 0), rotation=(0, 0, 1, 0), group=None, transparent=False): self.model_instance = model.get_instance() self.pos = pos self.rotation = rotation self.group = group self.transparent = transparent if group: self.draw = self.draw_with_group else: self.draw = self.draw_inner def update(self, dt): self.model_instance.update(dt) def is_transparent(self): return self.transparent def draw_with_group(self, camera): self.group.set_state_recursive() self.draw_inner(camera) self.group.unset_state_recursive() def draw_inner(self, camera): glPushMatrix() glTranslatef(*self.pos) glRotatef(*self.rotation) self.model_instance.draw() glPopMatrix()
class GroupNode(object): """Group a bunch of other nodes.""" def __init__(self, nodes, pos=(0, 0, 0), rotation=(0, 0, 1, 0), group=None): self.nodes = nodes self.pos = pos self.rotation = rotation self.group = group if group: self.draw = self.draw_with_group else: self.draw = self.draw_inner def update(self, dt): for n in self.nodes: n.update(dt) def is_transparent(self): return False def draw_with_group(self, camera): self.group.set_state_recursive() self.draw_inner(camera) self.group.unset_state_recursive() def draw_inner(self, camera): glPushMatrix() glTranslatef(*self.pos) glRotatef(*self.rotation) for n in self.nodes: n.draw(camera) glPopMatrix() class RayNode(object): """A ray, drawn as a billboard towards the camera.""" ta = (0, 0) tb = (0, 1) tc = (1, 1) td = (1, 0) uvs = list(itertools.chain(ta, tb, tc, td)) def __init__(self, p1, p2, width, transparent=False, group=None): self.p1 = p1 self.p2 = p2 self.width = width self.transparent = transparent self.group = group def update(self, dt): pass def is_transparent(self): return self.transparent def draw(self, camera): p1, p2 = self.p1, self.p2 along = (p2 - p1).normalize() across = along.cross(camera.eye_vector()).normalize() across *= 0.5 * self.width va = p1 - across vb = p2 - across vc = p2 + across vd = p1 + across vs = list(itertools.chain(va, vb, vc, vd)) if self.group: self.group.set_state_recursive() pyglet.graphics.draw(4, GL_QUADS, ('v3f', vs), ('t2f', self.uvs), ) if self.group: self.group.unset_state_recursive()
[docs]class Scene(object): """A collection of scenegraph objects. At present this class does little more than render a list of objects; in future however it may support more sophisticated behaviour. """ def __init__( self, ambient=(0, 0, 0, 1.0), renderer=LightingAccumulationRenderer): self.ambient = ambient self.objects = [] self.models = {} if callable(renderer): self.renderer = renderer() else: self.renderer = renderer def prepare_model(self, model): return self.renderer.prepare_model(model) def prepare_modelnode(self, c): c.model_instance = self.prepare_model(c.model_instance) def prepare_group(self, group): for c in group.nodes: if isinstance(c, GroupNode): self.prepare_group(c) else: self.prepare_modelnode(c)
[docs] def clear(self): """Remove all objects from the scene.""" del self.objects[:]
[docs] def add(self, obj): """Add obj to the scene. obj should be a scenegraph node, but currently adding a Mesh or Model directly is supported as a convenience. """ if isinstance(obj, Mesh): model = Model(meshes=[obj]) model = self.prepare_model(model) obj = ModelNode(model) elif isinstance(obj, Model): model = self.prepare_model(obj) obj = ModelNode(model) elif isinstance(obj, ModelNode): obj.model_instance = self.prepare_model(obj.model_instance) elif isinstance(obj, GroupNode): self.prepare_group(obj) if obj in self.objects: return self.objects.append(obj)
[docs] def remove(self, obj): """Remove obj from the scene.""" try: self.objects.remove(obj) except ValueError: pass
[docs] def update(self, dt): """Update all objects in the scene with the given time step.""" for o in self.objects: o.update(dt)
[docs] def render(self, camera): """Render the scene with the given camera.""" self.renderer.render(self, camera)
[docs]class Camera(object): """The camera class is a view onto a scene. This class offers the ability to set up the projection and modelview matrixes. :param fov: The field of view in the y direction. """ def __init__( self, width=800, height=600, pos=v3((0, 15, 15)), look_at=v3((0, 0, 0)), fov=67.5, near=1.0, far=10000.0): self.viewport = width, height self.aspect = float(width) / height self.fov = fov self.pos = pos self.look_at = look_at self.near = near self.far = far
[docs] def eye_vector(self): """Get the direction in which the camera is looking.""" return self.look_at - self.pos
def set_projection_matrix(self): glMatrixMode(GL_PROJECTION) glLoadIdentity() gluPerspective(self.fov, self.aspect, self.near, self.far) def set_matrix(self): self.set_projection_matrix() glMatrixMode(GL_MODELVIEW) glLoadIdentity() gluLookAt(*itertools.chain( self.pos, self.look_at, (0, 1, 0) )) def get_view_matrix_gl(self): from ctypes import c_double mat = (c_double * 16)() glGetDoublev(GL_MODELVIEW_MATRIX, mat) return Matrix4.new(*mat)
[docs] def get_view_matrix(self): """Get the view matrix as a euclid.Matrix4.""" f = (v3(self.look_at) - v3(self.pos)).normalized() #print "f=", f up = Vector3(0, 1, 0) s = f.cross(up).normalize() #print abs(f), abs(up), abs(s) #print "s=", s u = s.cross(f) m = Matrix4.new(*itertools.chain( s, [0], u, [0], -f, [0], [0, 0, 0, 1] )) #print m xlate = Matrix4.new_translate(*(-self.pos)) #print xlate mat = m.inverse() * xlate #print mat return mat
[docs]class OrthographicCamera(Camera): """An orthographic camera. :param scale: the width of the viewport in world space. """ def __init__( self, width=800, height=600, pos=v3((0, 15, 15)), look_at=v3((0, 0, 0)), scale=20.0, near=1.0, far=10000.0): self.viewport = width, height self.aspect = float(width) / height self.pos = pos self.look_at = look_at self.near = near self.far = far self.scale = scale def bounds(self): hs = 0.5 * self.scale vs = hs / self.aspect l = -hs r = hs b = -vs t = vs return l, r, b, t, self.near, self.far def set_projection_matrix(self): glMatrixMode(GL_PROJECTION) glLoadIdentity() glOrtho(*self.bounds())