summaryrefslogtreecommitdiff
path: root/libs/assimp/port/PyAssimp/scripts/3d_viewer.py
diff options
context:
space:
mode:
authorsanine <sanine.not@pm.me>2022-04-16 11:55:09 -0500
committersanine <sanine.not@pm.me>2022-04-16 11:55:09 -0500
commitdb81b925d776103326128bf629cbdda576a223e7 (patch)
tree58bea8155c686733310009f6bed7363f91fbeb9d /libs/assimp/port/PyAssimp/scripts/3d_viewer.py
parent55860037b14fb3893ba21cf2654c83d349cc1082 (diff)
move 3rd-party librarys into libs/ and add built-in honeysuckle
Diffstat (limited to 'libs/assimp/port/PyAssimp/scripts/3d_viewer.py')
-rwxr-xr-xlibs/assimp/port/PyAssimp/scripts/3d_viewer.py1318
1 files changed, 1318 insertions, 0 deletions
diff --git a/libs/assimp/port/PyAssimp/scripts/3d_viewer.py b/libs/assimp/port/PyAssimp/scripts/3d_viewer.py
new file mode 100755
index 0000000..08a6266
--- /dev/null
+++ b/libs/assimp/port/PyAssimp/scripts/3d_viewer.py
@@ -0,0 +1,1318 @@
+#!/usr/bin/env python
+# -*- coding: UTF-8 -*-
+
+""" This program loads a model with PyASSIMP, and display it.
+
+Based on:
+- pygame code from http://3dengine.org/Spectator_%28PyOpenGL%29
+- http://www.lighthouse3d.com/tutorials
+- http://www.songho.ca/opengl/gl_transform.html
+- http://code.activestate.com/recipes/325391/
+- ASSIMP's C++ SimpleOpenGL viewer
+
+Authors: Séverin Lemaignan, 2012-2016
+"""
+import sys
+import logging
+
+logger = logging.getLogger("pyassimp")
+gllogger = logging.getLogger("OpenGL")
+gllogger.setLevel(logging.WARNING)
+logging.basicConfig(level=logging.INFO)
+
+import OpenGL
+
+OpenGL.ERROR_CHECKING = False
+OpenGL.ERROR_LOGGING = False
+# OpenGL.ERROR_ON_COPY = True
+# OpenGL.FULL_LOGGING = True
+from OpenGL.GL import *
+from OpenGL.arrays import vbo
+from OpenGL.GL import shaders
+
+import pygame
+import pygame.font
+import pygame.image
+
+import math, random
+from numpy import linalg
+
+import pyassimp
+from pyassimp.postprocess import *
+from pyassimp.helper import *
+import transformations
+
+ROTATION_180_X = numpy.array([[1, 0, 0, 0], [0, -1, 0, 0], [0, 0, -1, 0], [0, 0, 0, 1]], dtype=numpy.float32)
+
+# rendering mode
+BASE = "BASE"
+COLORS = "COLORS"
+SILHOUETTE = "SILHOUETTE"
+HELPERS = "HELPERS"
+
+# Entities type
+ENTITY = "entity"
+CAMERA = "camera"
+MESH = "mesh"
+
+FLAT_VERTEX_SHADER_120 = """
+#version 120
+
+uniform mat4 u_viewProjectionMatrix;
+uniform mat4 u_modelMatrix;
+
+uniform vec4 u_materialDiffuse;
+
+attribute vec3 a_vertex;
+
+varying vec4 v_color;
+
+void main(void)
+{
+ v_color = u_materialDiffuse;
+ gl_Position = u_viewProjectionMatrix * u_modelMatrix * vec4(a_vertex, 1.0);
+}
+"""
+
+FLAT_VERTEX_SHADER_130 = """
+#version 130
+
+uniform mat4 u_viewProjectionMatrix;
+uniform mat4 u_modelMatrix;
+
+uniform vec4 u_materialDiffuse;
+
+in vec3 a_vertex;
+
+out vec4 v_color;
+
+void main(void)
+{
+ v_color = u_materialDiffuse;
+ gl_Position = u_viewProjectionMatrix * u_modelMatrix * vec4(a_vertex, 1.0);
+}
+"""
+
+BASIC_VERTEX_SHADER_120 = """
+#version 120
+
+uniform mat4 u_viewProjectionMatrix;
+uniform mat4 u_modelMatrix;
+uniform mat3 u_normalMatrix;
+uniform vec3 u_lightPos;
+
+uniform vec4 u_materialDiffuse;
+
+attribute vec3 a_vertex;
+attribute vec3 a_normal;
+
+varying vec4 v_color;
+
+void main(void)
+{
+ // Now the normal is in world space, as we pass the light in world space.
+ vec3 normal = u_normalMatrix * a_normal;
+
+ float dist = distance(a_vertex, u_lightPos);
+
+ // go to https://www.desmos.com/calculator/nmnaud1hrw to play with the parameters
+ // att is not used for now
+ float att=1.0/(1.0+0.8*dist*dist);
+
+ vec3 surf2light = normalize(u_lightPos - a_vertex);
+ vec3 norm = normalize(normal);
+ float dcont=max(0.0,dot(norm,surf2light));
+
+ float ambient = 0.3;
+ float intensity = dcont + 0.3 + ambient;
+
+ v_color = u_materialDiffuse * intensity;
+
+ gl_Position = u_viewProjectionMatrix * u_modelMatrix * vec4(a_vertex, 1.0);
+}
+"""
+
+BASIC_VERTEX_SHADER_130 = """
+#version 130
+
+uniform mat4 u_viewProjectionMatrix;
+uniform mat4 u_modelMatrix;
+uniform mat3 u_normalMatrix;
+uniform vec3 u_lightPos;
+
+uniform vec4 u_materialDiffuse;
+
+in vec3 a_vertex;
+in vec3 a_normal;
+
+out vec4 v_color;
+
+void main(void)
+{
+ // Now the normal is in world space, as we pass the light in world space.
+ vec3 normal = u_normalMatrix * a_normal;
+
+ float dist = distance(a_vertex, u_lightPos);
+
+ // go to https://www.desmos.com/calculator/nmnaud1hrw to play with the parameters
+ // att is not used for now
+ float att=1.0/(1.0+0.8*dist*dist);
+
+ vec3 surf2light = normalize(u_lightPos - a_vertex);
+ vec3 norm = normalize(normal);
+ float dcont=max(0.0,dot(norm,surf2light));
+
+ float ambient = 0.3;
+ float intensity = dcont + 0.3 + ambient;
+
+ v_color = u_materialDiffuse * intensity;
+
+ gl_Position = u_viewProjectionMatrix * u_modelMatrix * vec4(a_vertex, 1.0);
+}
+"""
+
+BASIC_FRAGMENT_SHADER_120 = """
+#version 120
+
+varying vec4 v_color;
+
+void main() {
+ gl_FragColor = v_color;
+}
+"""
+
+BASIC_FRAGMENT_SHADER_130 = """
+#version 130
+
+in vec4 v_color;
+
+void main() {
+ gl_FragColor = v_color;
+}
+"""
+
+GOOCH_VERTEX_SHADER_120 = """
+#version 120
+
+// attributes
+attribute vec3 a_vertex; // xyz - position
+attribute vec3 a_normal; // xyz - normal
+
+// uniforms
+uniform mat4 u_modelMatrix;
+uniform mat4 u_viewProjectionMatrix;
+uniform mat3 u_normalMatrix;
+uniform vec3 u_lightPos;
+uniform vec3 u_camPos;
+
+// output data from vertex to fragment shader
+varying vec3 o_normal;
+varying vec3 o_lightVector;
+
+///////////////////////////////////////////////////////////////////
+
+void main(void)
+{
+ // transform position and normal to world space
+ vec4 positionWorld = u_modelMatrix * vec4(a_vertex, 1.0);
+ vec3 normalWorld = u_normalMatrix * a_normal;
+
+ // calculate and pass vectors required for lighting
+ o_lightVector = u_lightPos - positionWorld.xyz;
+ o_normal = normalWorld;
+
+ // project world space position to the screen and output it
+ gl_Position = u_viewProjectionMatrix * positionWorld;
+}
+"""
+
+GOOCH_VERTEX_SHADER_130 = """
+#version 130
+
+// attributes
+in vec3 a_vertex; // xyz - position
+in vec3 a_normal; // xyz - normal
+
+// uniforms
+uniform mat4 u_modelMatrix;
+uniform mat4 u_viewProjectionMatrix;
+uniform mat3 u_normalMatrix;
+uniform vec3 u_lightPos;
+uniform vec3 u_camPos;
+
+// output data from vertex to fragment shader
+out vec3 o_normal;
+out vec3 o_lightVector;
+
+///////////////////////////////////////////////////////////////////
+
+void main(void)
+{
+ // transform position and normal to world space
+ vec4 positionWorld = u_modelMatrix * vec4(a_vertex, 1.0);
+ vec3 normalWorld = u_normalMatrix * a_normal;
+
+ // calculate and pass vectors required for lighting
+ o_lightVector = u_lightPos - positionWorld.xyz;
+ o_normal = normalWorld;
+
+ // project world space position to the screen and output it
+ gl_Position = u_viewProjectionMatrix * positionWorld;
+}
+"""
+
+GOOCH_FRAGMENT_SHADER_120 = """
+#version 120
+
+// data from vertex shader
+varying vec3 o_normal;
+varying vec3 o_lightVector;
+
+// diffuse color of the object
+uniform vec4 u_materialDiffuse;
+// cool color of gooch shading
+uniform vec3 u_coolColor;
+// warm color of gooch shading
+uniform vec3 u_warmColor;
+// how much to take from object color in final cool color
+uniform float u_alpha;
+// how much to take from object color in final warm color
+uniform float u_beta;
+
+///////////////////////////////////////////////////////////
+
+void main(void)
+{
+ // normlize vectors for lighting
+ vec3 normalVector = normalize(o_normal);
+ vec3 lightVector = normalize(o_lightVector);
+ // intensity of diffuse lighting [-1, 1]
+ float diffuseLighting = dot(lightVector, normalVector);
+ // map intensity of lighting from range [-1; 1] to [0, 1]
+ float interpolationValue = (1.0 + diffuseLighting)/2;
+
+ //////////////////////////////////////////////////////////////////
+
+ // cool color mixed with color of the object
+ vec3 coolColorMod = u_coolColor + vec3(u_materialDiffuse) * u_alpha;
+ // warm color mixed with color of the object
+ vec3 warmColorMod = u_warmColor + vec3(u_materialDiffuse) * u_beta;
+ // interpolation of cool and warm colors according
+ // to lighting intensity. The lower the light intensity,
+ // the larger part of the cool color is used
+ vec3 colorOut = mix(coolColorMod, warmColorMod, interpolationValue);
+
+ //////////////////////////////////////////////////////////////////
+
+ // save color
+ gl_FragColor.rgb = colorOut;
+ gl_FragColor.a = 1;
+}
+"""
+
+GOOCH_FRAGMENT_SHADER_130 = """
+#version 130
+
+// data from vertex shader
+in vec3 o_normal;
+in vec3 o_lightVector;
+
+// diffuse color of the object
+uniform vec4 u_materialDiffuse;
+// cool color of gooch shading
+uniform vec3 u_coolColor;
+// warm color of gooch shading
+uniform vec3 u_warmColor;
+// how much to take from object color in final cool color
+uniform float u_alpha;
+// how much to take from object color in final warm color
+uniform float u_beta;
+
+// output to framebuffer
+out vec4 resultingColor;
+
+///////////////////////////////////////////////////////////
+
+void main(void)
+{
+ // normlize vectors for lighting
+ vec3 normalVector = normalize(o_normal);
+ vec3 lightVector = normalize(o_lightVector);
+ // intensity of diffuse lighting [-1, 1]
+ float diffuseLighting = dot(lightVector, normalVector);
+ // map intensity of lighting from range [-1; 1] to [0, 1]
+ float interpolationValue = (1.0 + diffuseLighting)/2;
+
+ //////////////////////////////////////////////////////////////////
+
+ // cool color mixed with color of the object
+ vec3 coolColorMod = u_coolColor + vec3(u_materialDiffuse) * u_alpha;
+ // warm color mixed with color of the object
+ vec3 warmColorMod = u_warmColor + vec3(u_materialDiffuse) * u_beta;
+ // interpolation of cool and warm colors according
+ // to lighting intensity. The lower the light intensity,
+ // the larger part of the cool color is used
+ vec3 colorOut = mix(coolColorMod, warmColorMod, interpolationValue);
+
+ //////////////////////////////////////////////////////////////////
+
+ // save color
+ resultingColor.rgb = colorOut;
+ resultingColor.a = 1;
+}
+"""
+
+SILHOUETTE_VERTEX_SHADER_120 = """
+#version 120
+
+attribute vec3 a_vertex; // xyz - position
+attribute vec3 a_normal; // xyz - normal
+
+uniform mat4 u_modelMatrix;
+uniform mat4 u_viewProjectionMatrix;
+uniform mat4 u_modelViewMatrix;
+uniform vec4 u_materialDiffuse;
+uniform float u_bordersize; // width of the border
+
+varying vec4 v_color;
+
+void main(void){
+ v_color = u_materialDiffuse;
+ float distToCamera = -(u_modelViewMatrix * vec4(a_vertex, 1.0)).z;
+ vec4 tPos = vec4(a_vertex + a_normal * u_bordersize * distToCamera, 1.0);
+ gl_Position = u_viewProjectionMatrix * u_modelMatrix * tPos;
+}
+"""
+
+SILHOUETTE_VERTEX_SHADER_130 = """
+#version 130
+
+in vec3 a_vertex; // xyz - position
+in vec3 a_normal; // xyz - normal
+
+uniform mat4 u_modelMatrix;
+uniform mat4 u_viewProjectionMatrix;
+uniform mat4 u_modelViewMatrix;
+uniform vec4 u_materialDiffuse;
+uniform float u_bordersize; // width of the border
+
+out vec4 v_color;
+
+void main(void){
+ v_color = u_materialDiffuse;
+ float distToCamera = -(u_modelViewMatrix * vec4(a_vertex, 1.0)).z;
+ vec4 tPos = vec4(a_vertex + a_normal * u_bordersize * distToCamera, 1.0);
+ gl_Position = u_viewProjectionMatrix * u_modelMatrix * tPos;
+}
+"""
+DEFAULT_CLIP_PLANE_NEAR = 0.001
+DEFAULT_CLIP_PLANE_FAR = 1000.0
+
+
+def get_world_transform(scene, node):
+ if node == scene.rootnode:
+ return numpy.identity(4, dtype=numpy.float32)
+
+ parents = reversed(_get_parent_chain(scene, node, []))
+ parent_transform = reduce(numpy.dot, [p.transformation for p in parents])
+ return numpy.dot(parent_transform, node.transformation)
+
+
+def _get_parent_chain(scene, node, parents):
+ parent = node.parent
+
+ parents.append(parent)
+
+ if parent == scene.rootnode:
+ return parents
+
+ return _get_parent_chain(scene, parent, parents)
+
+
+class DefaultCamera:
+ def __init__(self, w, h, fov):
+ self.name = "default camera"
+ self.type = CAMERA
+ self.clipplanenear = DEFAULT_CLIP_PLANE_NEAR
+ self.clipplanefar = DEFAULT_CLIP_PLANE_FAR
+ self.aspect = w / h
+ self.horizontalfov = fov * math.pi / 180
+ self.transformation = numpy.array([[0.68, -0.32, 0.65, 7.48],
+ [0.73, 0.31, -0.61, -6.51],
+ [-0.01, 0.89, 0.44, 5.34],
+ [0., 0., 0., 1.]], dtype=numpy.float32)
+
+ self.transformation = numpy.dot(self.transformation, ROTATION_180_X)
+
+ def __str__(self):
+ return self.name
+
+
+class PyAssimp3DViewer:
+ base_name = "PyASSIMP 3D viewer"
+
+ def __init__(self, model, w=1024, h=768):
+
+ self.w = w
+ self.h = h
+
+ pygame.init()
+ pygame.display.set_caption(self.base_name)
+ pygame.display.set_mode((w, h), pygame.OPENGL | pygame.DOUBLEBUF)
+
+ glClearColor(0.18, 0.18, 0.18, 1.0)
+
+ shader_compilation_succeeded = False
+ try:
+ self.set_shaders_v130()
+ self.prepare_shaders()
+ except RuntimeError, message:
+ sys.stderr.write("%s\n" % message)
+ sys.stdout.write("Could not compile shaders in version 1.30, trying version 1.20\n")
+
+ if not shader_compilation_succeeded:
+ self.set_shaders_v120()
+ self.prepare_shaders()
+
+ self.scene = None
+ self.meshes = {} # stores the OpenGL vertex/faces/normals buffers pointers
+
+ self.node2colorid = {} # stores a color ID for each node. Useful for mouse picking and visibility checking
+ self.colorid2node = {} # reverse dict of node2colorid
+
+ self.currently_selected = None
+ self.moving = False
+ self.moving_situation = None
+
+ self.default_camera = DefaultCamera(self.w, self.h, fov=70)
+ self.cameras = [self.default_camera]
+
+ self.current_cam_index = 0
+ self.current_cam = self.default_camera
+ self.set_camera_projection()
+
+ self.load_model(model)
+
+ # user interactions
+ self.focal_point = [0, 0, 0]
+ self.is_rotating = False
+ self.is_panning = False
+ self.is_zooming = False
+
+ def set_shaders_v120(self):
+ self.BASIC_VERTEX_SHADER = BASIC_VERTEX_SHADER_120
+ self.FLAT_VERTEX_SHADER = FLAT_VERTEX_SHADER_120
+ self.SILHOUETTE_VERTEX_SHADER = SILHOUETTE_VERTEX_SHADER_120
+ self.GOOCH_VERTEX_SHADER = GOOCH_VERTEX_SHADER_120
+
+ self.BASIC_FRAGMENT_SHADER = BASIC_FRAGMENT_SHADER_120
+ self.GOOCH_FRAGMENT_SHADER = GOOCH_FRAGMENT_SHADER_120
+
+ def set_shaders_v130(self):
+ self.BASIC_VERTEX_SHADER = BASIC_VERTEX_SHADER_130
+ self.FLAT_VERTEX_SHADER = FLAT_VERTEX_SHADER_130
+ self.SILHOUETTE_VERTEX_SHADER = SILHOUETTE_VERTEX_SHADER_130
+ self.GOOCH_VERTEX_SHADER = GOOCH_VERTEX_SHADER_130
+
+ self.BASIC_FRAGMENT_SHADER = BASIC_FRAGMENT_SHADER_130
+ self.GOOCH_FRAGMENT_SHADER = GOOCH_FRAGMENT_SHADER_130
+
+ def prepare_shaders(self):
+
+ ### Base shader
+ vertex = shaders.compileShader(self.BASIC_VERTEX_SHADER, GL_VERTEX_SHADER)
+ fragment = shaders.compileShader(self.BASIC_FRAGMENT_SHADER, GL_FRAGMENT_SHADER)
+
+ self.shader = shaders.compileProgram(vertex, fragment)
+
+ self.set_shader_accessors(('u_modelMatrix',
+ 'u_viewProjectionMatrix',
+ 'u_normalMatrix',
+ 'u_lightPos',
+ 'u_materialDiffuse'),
+ ('a_vertex',
+ 'a_normal'), self.shader)
+
+ ### Flat shader
+ flatvertex = shaders.compileShader(self.FLAT_VERTEX_SHADER, GL_VERTEX_SHADER)
+ self.flatshader = shaders.compileProgram(flatvertex, fragment)
+
+ self.set_shader_accessors(('u_modelMatrix',
+ 'u_viewProjectionMatrix',
+ 'u_materialDiffuse',),
+ ('a_vertex',), self.flatshader)
+
+ ### Silhouette shader
+ silh_vertex = shaders.compileShader(self.SILHOUETTE_VERTEX_SHADER, GL_VERTEX_SHADER)
+ self.silhouette_shader = shaders.compileProgram(silh_vertex, fragment)
+
+ self.set_shader_accessors(('u_modelMatrix',
+ 'u_viewProjectionMatrix',
+ 'u_modelViewMatrix',
+ 'u_materialDiffuse',
+ 'u_bordersize' # width of the silhouette
+ ),
+ ('a_vertex',
+ 'a_normal'), self.silhouette_shader)
+
+ ### Gooch shader
+ gooch_vertex = shaders.compileShader(self.GOOCH_VERTEX_SHADER, GL_VERTEX_SHADER)
+ gooch_fragment = shaders.compileShader(self.GOOCH_FRAGMENT_SHADER, GL_FRAGMENT_SHADER)
+ self.gooch_shader = shaders.compileProgram(gooch_vertex, gooch_fragment)
+
+ self.set_shader_accessors(('u_modelMatrix',
+ 'u_viewProjectionMatrix',
+ 'u_normalMatrix',
+ 'u_lightPos',
+ 'u_materialDiffuse',
+ 'u_coolColor',
+ 'u_warmColor',
+ 'u_alpha',
+ 'u_beta'
+ ),
+ ('a_vertex',
+ 'a_normal'), self.gooch_shader)
+
+ @staticmethod
+ def set_shader_accessors(uniforms, attributes, shader):
+ # add accessors to the shaders uniforms and attributes
+ for uniform in uniforms:
+ location = glGetUniformLocation(shader, uniform)
+ if location in (None, -1):
+ raise RuntimeError('No uniform: %s (maybe it is not used '
+ 'anymore and has been optimized out by'
+ ' the shader compiler)' % uniform)
+ setattr(shader, uniform, location)
+
+ for attribute in attributes:
+ location = glGetAttribLocation(shader, attribute)
+ if location in (None, -1):
+ raise RuntimeError('No attribute: %s' % attribute)
+ setattr(shader, attribute, location)
+
+ @staticmethod
+ def prepare_gl_buffers(mesh):
+
+ mesh.gl = {}
+
+ # Fill the buffer for vertex and normals positions
+ v = numpy.array(mesh.vertices, 'f')
+ n = numpy.array(mesh.normals, 'f')
+
+ mesh.gl["vbo"] = vbo.VBO(numpy.hstack((v, n)))
+
+ # Fill the buffer for vertex positions
+ mesh.gl["faces"] = glGenBuffers(1)
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh.gl["faces"])
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER,
+ numpy.array(mesh.faces, dtype=numpy.int32),
+ GL_STATIC_DRAW)
+
+ mesh.gl["nbfaces"] = len(mesh.faces)
+
+ # Unbind buffers
+ glBindBuffer(GL_ARRAY_BUFFER, 0)
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)
+
+ @staticmethod
+ def get_rgb_from_colorid(colorid):
+ r = (colorid >> 0) & 0xff
+ g = (colorid >> 8) & 0xff
+ b = (colorid >> 16) & 0xff
+
+ return r, g, b
+
+ def get_color_id(self):
+ id = random.randint(0, 256 * 256 * 256)
+ if id not in self.colorid2node:
+ return id
+ else:
+ return self.get_color_id()
+
+ def glize(self, scene, node):
+
+ logger.info("Loading node <%s>" % node)
+ node.selected = True if self.currently_selected and self.currently_selected == node else False
+
+ node.transformation = node.transformation.astype(numpy.float32)
+
+ if node.meshes:
+ node.type = MESH
+ colorid = self.get_color_id()
+ self.colorid2node[colorid] = node
+ self.node2colorid[node.name] = colorid
+
+ elif node.name in [c.name for c in scene.cameras]:
+
+ # retrieve the ASSIMP camera object
+ [cam] = [c for c in scene.cameras if c.name == node.name]
+ node.type = CAMERA
+ logger.info("Added camera <%s>" % node.name)
+ logger.info("Camera position: %.3f, %.3f, %.3f" % tuple(node.transformation[:, 3][:3].tolist()))
+ self.cameras.append(node)
+ node.clipplanenear = cam.clipplanenear
+ node.clipplanefar = cam.clipplanefar
+
+ if numpy.allclose(cam.lookat, [0, 0, -1]) and numpy.allclose(cam.up, [0, 1, 0]): # Cameras in .blend files
+
+ # Rotate by 180deg around X to have Z pointing forward
+ node.transformation = numpy.dot(node.transformation, ROTATION_180_X)
+ else:
+ raise RuntimeError(
+ "I do not know how to normalize this camera orientation: lookat=%s, up=%s" % (cam.lookat, cam.up))
+
+ if cam.aspect == 0.0:
+ logger.warning("Camera aspect not set. Setting to default 4:3")
+ node.aspect = 1.333
+ else:
+ node.aspect = cam.aspect
+
+ node.horizontalfov = cam.horizontalfov
+
+ else:
+ node.type = ENTITY
+
+ for child in node.children:
+ self.glize(scene, child)
+
+ def load_model(self, path, postprocess=aiProcessPreset_TargetRealtime_MaxQuality):
+ logger.info("Loading model:" + path + "...")
+
+ if postprocess:
+ self.scene = pyassimp.load(path, processing=postprocess)
+ else:
+ self.scene = pyassimp.load(path)
+ logger.info("Done.")
+
+ scene = self.scene
+ # log some statistics
+ logger.info(" meshes: %d" % len(scene.meshes))
+ logger.info(" total faces: %d" % sum([len(mesh.faces) for mesh in scene.meshes]))
+ logger.info(" materials: %d" % len(scene.materials))
+ self.bb_min, self.bb_max = get_bounding_box(self.scene)
+ logger.info(" bounding box:" + str(self.bb_min) + " - " + str(self.bb_max))
+
+ self.scene_center = [(a + b) / 2. for a, b in zip(self.bb_min, self.bb_max)]
+
+ for index, mesh in enumerate(scene.meshes):
+ self.prepare_gl_buffers(mesh)
+
+ self.glize(scene, scene.rootnode)
+
+ # Finally release the model
+ pyassimp.release(scene)
+ logger.info("Ready for 3D rendering!")
+
+ def cycle_cameras(self):
+
+ self.current_cam_index = (self.current_cam_index + 1) % len(self.cameras)
+ self.current_cam = self.cameras[self.current_cam_index]
+ self.set_camera_projection(self.current_cam)
+ logger.info("Switched to camera <%s>" % self.current_cam)
+
+ def set_overlay_projection(self):
+ glViewport(0, 0, self.w, self.h)
+ glMatrixMode(GL_PROJECTION)
+ glLoadIdentity()
+ glOrtho(0.0, self.w - 1.0, 0.0, self.h - 1.0, -1.0, 1.0)
+ glMatrixMode(GL_MODELVIEW)
+ glLoadIdentity()
+
+ def set_camera_projection(self, camera=None):
+
+ if not camera:
+ camera = self.current_cam
+
+ znear = camera.clipplanenear or DEFAULT_CLIP_PLANE_NEAR
+ zfar = camera.clipplanefar or DEFAULT_CLIP_PLANE_FAR
+ aspect = camera.aspect
+ fov = camera.horizontalfov
+
+ glMatrixMode(GL_PROJECTION)
+ glLoadIdentity()
+
+ # Compute gl frustrum
+ tangent = math.tan(fov / 2.)
+ h = znear * tangent
+ w = h * aspect
+
+ # params: left, right, bottom, top, near, far
+ glFrustum(-w, w, -h, h, znear, zfar)
+ # equivalent to:
+ # gluPerspective(fov * 180/math.pi, aspect, znear, zfar)
+
+ self.projection_matrix = glGetFloatv(GL_PROJECTION_MATRIX).transpose()
+
+ glMatrixMode(GL_MODELVIEW)
+ glLoadIdentity()
+
+ def render_colors(self):
+
+ glEnable(GL_DEPTH_TEST)
+ glDepthFunc(GL_LEQUAL)
+
+ glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
+ glEnable(GL_CULL_FACE)
+
+ glUseProgram(self.flatshader)
+
+ glUniformMatrix4fv(self.flatshader.u_viewProjectionMatrix, 1, GL_TRUE,
+ numpy.dot(self.projection_matrix, self.view_matrix))
+
+ self.recursive_render(self.scene.rootnode, self.flatshader, mode=COLORS)
+
+ glUseProgram(0)
+
+ def get_hovered_node(self, mousex, mousey):
+ """
+ Attention: The performances of this method relies heavily on the size of the display!
+ """
+
+ # mouse out of the window?
+ if mousex < 0 or mousex >= self.w or mousey < 0 or mousey >= self.h:
+ return None
+
+ self.render_colors()
+ # Capture image from the OpenGL buffer
+ buf = (GLubyte * (3 * self.w * self.h))(0)
+ glReadPixels(0, 0, self.w, self.h, GL_RGB, GL_UNSIGNED_BYTE, buf)
+
+ # Reinterpret the RGB pixel buffer as a 1-D array of 24bits colors
+ a = numpy.ndarray(len(buf), numpy.dtype('>u1'), buf)
+ colors = numpy.zeros(len(buf) / 3, numpy.dtype('<u4'))
+ for i in range(3):
+ colors.view(dtype='>u1')[i::4] = a.view(dtype='>u1')[i::3]
+
+ colorid = colors[mousex + mousey * self.w]
+
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
+
+ if colorid in self.colorid2node:
+ return self.colorid2node[colorid]
+
+ def render(self, wireframe=False, twosided=False):
+
+ glEnable(GL_DEPTH_TEST)
+ glDepthFunc(GL_LEQUAL)
+
+ glPolygonMode(GL_FRONT_AND_BACK, GL_LINE if wireframe else GL_FILL)
+ glDisable(GL_CULL_FACE) if twosided else glEnable(GL_CULL_FACE)
+
+ self.render_grid()
+
+ self.recursive_render(self.scene.rootnode, None, mode=HELPERS)
+
+ ### First, the silhouette
+
+ if False:
+ shader = self.silhouette_shader
+
+ # glDepthMask(GL_FALSE)
+ glCullFace(GL_FRONT) # cull front faces
+
+ glUseProgram(shader)
+ glUniform1f(shader.u_bordersize, 0.01)
+
+ glUniformMatrix4fv(shader.u_viewProjectionMatrix, 1, GL_TRUE,
+ numpy.dot(self.projection_matrix, self.view_matrix))
+
+ self.recursive_render(self.scene.rootnode, shader, mode=SILHOUETTE)
+
+ glUseProgram(0)
+
+ ### Then, inner shading
+ # glDepthMask(GL_TRUE)
+ glCullFace(GL_BACK)
+
+ use_gooch = False
+ if use_gooch:
+ shader = self.gooch_shader
+
+ glUseProgram(shader)
+ glUniform3f(shader.u_lightPos, -.5, -.5, .5)
+
+ ##### GOOCH specific
+ glUniform3f(shader.u_coolColor, 159.0 / 255, 148.0 / 255, 255.0 / 255)
+ glUniform3f(shader.u_warmColor, 255.0 / 255, 75.0 / 255, 75.0 / 255)
+ glUniform1f(shader.u_alpha, .25)
+ glUniform1f(shader.u_beta, .25)
+ #########
+ else:
+ shader = self.shader
+ glUseProgram(shader)
+ glUniform3f(shader.u_lightPos, -.5, -.5, .5)
+
+ glUniformMatrix4fv(shader.u_viewProjectionMatrix, 1, GL_TRUE,
+ numpy.dot(self.projection_matrix, self.view_matrix))
+
+ self.recursive_render(self.scene.rootnode, shader)
+
+ glUseProgram(0)
+
+ def render_axis(self,
+ transformation=numpy.identity(4, dtype=numpy.float32),
+ label=None,
+ size=0.2,
+ selected=False):
+ m = transformation.transpose() # OpenGL row major
+
+ glPushMatrix()
+ glMultMatrixf(m)
+
+ glLineWidth(3 if selected else 1)
+
+ size = 2 * size if selected else size
+
+ glBegin(GL_LINES)
+
+ # draw line for x axis
+ glColor3f(1.0, 0.0, 0.0)
+ glVertex3f(0.0, 0.0, 0.0)
+ glVertex3f(size, 0.0, 0.0)
+
+ # draw line for y axis
+ glColor3f(0.0, 1.0, 0.0)
+ glVertex3f(0.0, 0.0, 0.0)
+ glVertex3f(0.0, size, 0.0)
+
+ # draw line for Z axis
+ glColor3f(0.0, 0.0, 1.0)
+ glVertex3f(0.0, 0.0, 0.0)
+ glVertex3f(0.0, 0.0, size)
+
+ glEnd()
+
+ if label:
+ self.showtext(label)
+
+ glPopMatrix()
+
+ @staticmethod
+ def render_camera(camera, transformation):
+
+ m = transformation.transpose() # OpenGL row major
+
+ aspect = camera.aspect
+
+ u = 0.1 # unit size (in m)
+ l = 3 * u # length of the camera cone
+ f = 3 * u # aperture of the camera cone
+
+ glPushMatrix()
+ glMultMatrixf(m)
+
+ glLineWidth(2)
+ glBegin(GL_LINE_STRIP)
+
+ glColor3f(.2, .2, .2)
+
+ glVertex3f(u, u, -u)
+ glVertex3f(u, -u, -u)
+ glVertex3f(-u, -u, -u)
+ glVertex3f(-u, u, -u)
+ glVertex3f(u, u, -u)
+
+ glVertex3f(u, u, 0.0)
+ glVertex3f(u, -u, 0.0)
+ glVertex3f(-u, -u, 0.0)
+ glVertex3f(-u, u, 0.0)
+ glVertex3f(u, u, 0.0)
+
+ glVertex3f(f * aspect, f, l)
+ glVertex3f(f * aspect, -f, l)
+ glVertex3f(-f * aspect, -f, l)
+ glVertex3f(-f * aspect, f, l)
+ glVertex3f(f * aspect, f, l)
+
+ glEnd()
+
+ glBegin(GL_LINE_STRIP)
+ glVertex3f(u, -u, -u)
+ glVertex3f(u, -u, 0.0)
+ glVertex3f(f * aspect, -f, l)
+ glEnd()
+
+ glBegin(GL_LINE_STRIP)
+ glVertex3f(-u, -u, -u)
+ glVertex3f(-u, -u, 0.0)
+ glVertex3f(-f * aspect, -f, l)
+ glEnd()
+
+ glBegin(GL_LINE_STRIP)
+ glVertex3f(-u, u, -u)
+ glVertex3f(-u, u, 0.0)
+ glVertex3f(-f * aspect, f, l)
+ glEnd()
+
+ glPopMatrix()
+
+ @staticmethod
+ def render_grid():
+
+ glLineWidth(1)
+ glColor3f(0.5, 0.5, 0.5)
+ glBegin(GL_LINES)
+ for i in range(-10, 11):
+ glVertex3f(i, -10.0, 0.0)
+ glVertex3f(i, 10.0, 0.0)
+
+ for i in range(-10, 11):
+ glVertex3f(-10.0, i, 0.0)
+ glVertex3f(10.0, i, 0.0)
+ glEnd()
+
+ def recursive_render(self, node, shader, mode=BASE, with_normals=True):
+ """ Main recursive rendering method.
+ """
+
+ normals = with_normals
+
+ if mode == COLORS:
+ normals = False
+
+
+ if not hasattr(node, "selected"):
+ node.selected = False
+
+ m = get_world_transform(self.scene, node)
+
+ # HELPERS mode
+ ###
+ if mode == HELPERS:
+ # if node.type == ENTITY:
+ self.render_axis(m,
+ label=node.name if node != self.scene.rootnode else None,
+ selected=node.selected if hasattr(node, "selected") else False)
+
+ if node.type == CAMERA:
+ self.render_camera(node, m)
+
+ for child in node.children:
+ self.recursive_render(child, shader, mode)
+
+ return
+
+ # Mesh rendering modes
+ ###
+ if node.type == MESH:
+
+ for mesh in node.meshes:
+
+ stride = 24 # 6 * 4 bytes
+
+ if node.selected and mode == SILHOUETTE:
+ glUniform4f(shader.u_materialDiffuse, 1.0, 0.0, 0.0, 1.0)
+ glUniformMatrix4fv(shader.u_modelViewMatrix, 1, GL_TRUE,
+ numpy.dot(self.view_matrix, m))
+
+ else:
+ if mode == COLORS:
+ colorid = self.node2colorid[node.name]
+ r, g, b = self.get_rgb_from_colorid(colorid)
+ glUniform4f(shader.u_materialDiffuse, r / 255.0, g / 255.0, b / 255.0, 1.0)
+ elif mode == SILHOUETTE:
+ glUniform4f(shader.u_materialDiffuse, .0, .0, .0, 1.0)
+ else:
+ if node.selected:
+ diffuse = (1.0, 0.0, 0.0, 1.0) # selected nodes in red
+ else:
+ diffuse = mesh.material.properties["diffuse"]
+ if len(diffuse) == 3: # RGB instead of expected RGBA
+ diffuse.append(1.0)
+ glUniform4f(shader.u_materialDiffuse, *diffuse)
+ # if ambient:
+ # glUniform4f( shader.Material_ambient, *mat["ambient"] )
+
+ if mode == BASE: # not in COLORS or SILHOUETTE
+ normal_matrix = linalg.inv(numpy.dot(self.view_matrix, m)[0:3, 0:3]).transpose()
+ glUniformMatrix3fv(shader.u_normalMatrix, 1, GL_TRUE, normal_matrix)
+
+ glUniformMatrix4fv(shader.u_modelMatrix, 1, GL_TRUE, m)
+
+ vbo = mesh.gl["vbo"]
+ vbo.bind()
+
+ glEnableVertexAttribArray(shader.a_vertex)
+ if normals:
+ glEnableVertexAttribArray(shader.a_normal)
+
+ glVertexAttribPointer(
+ shader.a_vertex,
+ 3, GL_FLOAT, False, stride, vbo
+ )
+
+ if normals:
+ glVertexAttribPointer(
+ shader.a_normal,
+ 3, GL_FLOAT, False, stride, vbo + 12
+ )
+
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh.gl["faces"])
+ glDrawElements(GL_TRIANGLES, mesh.gl["nbfaces"] * 3, GL_UNSIGNED_INT, None)
+
+ vbo.unbind()
+ glDisableVertexAttribArray(shader.a_vertex)
+
+ if normals:
+ glDisableVertexAttribArray(shader.a_normal)
+
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)
+
+ for child in node.children:
+ self.recursive_render(child, shader, mode)
+
+
+ def switch_to_overlay(self):
+ glPushMatrix()
+ self.set_overlay_projection()
+
+ def switch_from_overlay(self):
+ self.set_camera_projection()
+ glPopMatrix()
+
+ def select_node(self, node):
+ self.currently_selected = node
+ self.update_node_select(self.scene.rootnode)
+
+ def update_node_select(self, node):
+ if node is self.currently_selected:
+ node.selected = True
+ else:
+ node.selected = False
+
+ for child in node.children:
+ self.update_node_select(child)
+
+ def loop(self):
+
+ pygame.display.flip()
+
+ if not self.process_events():
+ return False # ESC has been pressed
+
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
+
+ return True
+
+ def process_events(self):
+
+ LEFT_BUTTON = 1
+ MIDDLE_BUTTON = 2
+ RIGHT_BUTTON = 3
+ WHEEL_UP = 4
+ WHEEL_DOWN = 5
+
+ dx, dy = pygame.mouse.get_rel()
+ mousex, mousey = pygame.mouse.get_pos()
+
+ zooming_one_shot = False
+
+ ok = True
+
+ for evt in pygame.event.get():
+ if evt.type == pygame.MOUSEBUTTONDOWN and evt.button == LEFT_BUTTON:
+ hovered = self.get_hovered_node(mousex, self.h - mousey)
+ if hovered:
+ if self.currently_selected and self.currently_selected == hovered:
+ self.select_node(None)
+ else:
+ logger.info("Node %s selected" % hovered)
+ self.select_node(hovered)
+ else:
+ self.is_rotating = True
+ if evt.type == pygame.MOUSEBUTTONUP and evt.button == LEFT_BUTTON:
+ self.is_rotating = False
+
+ if evt.type == pygame.MOUSEBUTTONDOWN and evt.button == MIDDLE_BUTTON:
+ self.is_panning = True
+ if evt.type == pygame.MOUSEBUTTONUP and evt.button == MIDDLE_BUTTON:
+ self.is_panning = False
+
+ if evt.type == pygame.MOUSEBUTTONDOWN and evt.button == RIGHT_BUTTON:
+ self.is_zooming = True
+ if evt.type == pygame.MOUSEBUTTONUP and evt.button == RIGHT_BUTTON:
+ self.is_zooming = False
+
+ if evt.type == pygame.MOUSEBUTTONDOWN and evt.button in [WHEEL_UP, WHEEL_DOWN]:
+ zooming_one_shot = True
+ self.is_zooming = True
+ dy = -10 if evt.button == WHEEL_UP else 10
+
+ if evt.type == pygame.KEYDOWN:
+ ok = (ok and self.process_keystroke(evt.key, evt.mod))
+
+ self.controls_3d(dx, dy, zooming_one_shot)
+
+ return ok
+
+ def process_keystroke(self, key, mod):
+
+ # process arrow keys if an object is selected
+ if self.currently_selected:
+ up = 0
+ strafe = 0
+
+ if key == pygame.K_UP:
+ up = 1
+ if key == pygame.K_DOWN:
+ up = -1
+ if key == pygame.K_LEFT:
+ strafe = -1
+ if key == pygame.K_RIGHT:
+ strafe = 1
+
+ self.move_selected_node(up, strafe)
+
+ if key == pygame.K_f:
+ pygame.display.toggle_fullscreen()
+
+ if key == pygame.K_TAB:
+ self.cycle_cameras()
+
+ if key in [pygame.K_ESCAPE, pygame.K_q]:
+ return False
+
+ return True
+
+ def controls_3d(self, dx, dy, zooming_one_shot=False):
+
+ CAMERA_TRANSLATION_FACTOR = 0.01
+ CAMERA_ROTATION_FACTOR = 0.01
+
+ if not (self.is_rotating or self.is_panning or self.is_zooming):
+ return
+
+ current_pos = self.current_cam.transformation[:3, 3].copy()
+ distance = numpy.linalg.norm(self.focal_point - current_pos)
+
+ if self.is_rotating:
+ """ Orbiting the camera is implemented the following way:
+
+ - the rotation is split into a rotation around the *world* Z axis
+ (controlled by the horizontal mouse motion along X) and a
+ rotation around the *X* axis of the camera (pitch) *shifted to
+ the focal origin* (the world origin for now). This is controlled
+ by the vertical motion of the mouse (Y axis).
+
+ - as a result, the resulting transformation of the camera in the
+ world frame C' is:
+ C' = (T · Rx · T⁻¹ · (Rz · C)⁻¹)⁻¹
+
+ where:
+ - C is the original camera transformation in the world frame,
+ - Rz is the rotation along the Z axis (in the world frame)
+ - T is the translation camera -> world (ie, the inverse of the
+ translation part of C
+ - Rx is the rotation around X in the (translated) camera frame
+ """
+
+ rotation_camera_x = dy * CAMERA_ROTATION_FACTOR
+ rotation_world_z = dx * CAMERA_ROTATION_FACTOR
+ world_z_rotation = transformations.euler_matrix(0, 0, rotation_world_z)
+ cam_x_rotation = transformations.euler_matrix(rotation_camera_x, 0, 0)
+
+ after_world_z_rotation = numpy.dot(world_z_rotation, self.current_cam.transformation)
+
+ inverse_transformation = transformations.inverse_matrix(after_world_z_rotation)
+
+ translation = transformations.translation_matrix(
+ transformations.decompose_matrix(inverse_transformation)[3])
+ inverse_translation = transformations.inverse_matrix(translation)
+
+ new_inverse = numpy.dot(inverse_translation, inverse_transformation)
+ new_inverse = numpy.dot(cam_x_rotation, new_inverse)
+ new_inverse = numpy.dot(translation, new_inverse)
+
+ self.current_cam.transformation = transformations.inverse_matrix(new_inverse).astype(numpy.float32)
+
+ if self.is_panning:
+ tx = -dx * CAMERA_TRANSLATION_FACTOR * distance
+ ty = dy * CAMERA_TRANSLATION_FACTOR * distance
+ cam_transform = transformations.translation_matrix((tx, ty, 0)).astype(numpy.float32)
+ self.current_cam.transformation = numpy.dot(self.current_cam.transformation, cam_transform)
+
+ if self.is_zooming:
+ tz = dy * CAMERA_TRANSLATION_FACTOR * distance
+ cam_transform = transformations.translation_matrix((0, 0, tz)).astype(numpy.float32)
+ self.current_cam.transformation = numpy.dot(self.current_cam.transformation, cam_transform)
+
+ if zooming_one_shot:
+ self.is_zooming = False
+
+ self.update_view_camera()
+
+ def update_view_camera(self):
+
+ self.view_matrix = linalg.inv(self.current_cam.transformation)
+
+ # Rotate by 180deg around X to have Z pointing backward (OpenGL convention)
+ self.view_matrix = numpy.dot(ROTATION_180_X, self.view_matrix)
+
+ glMatrixMode(GL_MODELVIEW)
+ glLoadIdentity()
+ glMultMatrixf(self.view_matrix.transpose())
+
+ def move_selected_node(self, up, strafe):
+ self.currently_selected.transformation[0][3] += strafe
+ self.currently_selected.transformation[2][3] += up
+
+ @staticmethod
+ def showtext(text, x=0, y=0, z=0, size=20):
+
+ # TODO: alpha blending does not work...
+ # glEnable(GL_BLEND)
+ # glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
+
+ font = pygame.font.Font(None, size)
+ text_surface = font.render(text, True, (10, 10, 10, 255),
+ (255 * 0.18, 255 * 0.18, 255 * 0.18, 0))
+ text_data = pygame.image.tostring(text_surface, "RGBA", True)
+ glRasterPos3d(x, y, z)
+ glDrawPixels(text_surface.get_width(),
+ text_surface.get_height(),
+ GL_RGBA, GL_UNSIGNED_BYTE,
+ text_data)
+
+ # glDisable(GL_BLEND)
+
+
+def main(model, width, height):
+ app = PyAssimp3DViewer(model, w=width, h=height)
+
+ clock = pygame.time.Clock()
+
+ while app.loop():
+
+ app.update_view_camera()
+
+ ## Main rendering
+ app.render()
+
+ ## GUI text display
+ app.switch_to_overlay()
+ app.showtext("Active camera: %s" % str(app.current_cam), 10, app.h - 30)
+ if app.currently_selected:
+ app.showtext("Selected node: %s" % app.currently_selected, 10, app.h - 50)
+ pos = app.h - 70
+
+ app.showtext("(%sm, %sm, %sm)" % (app.currently_selected.transformation[0, 3],
+ app.currently_selected.transformation[1, 3],
+ app.currently_selected.transformation[2, 3]), 30, pos)
+
+ app.switch_from_overlay()
+
+ # Make sure we do not go over 30fps
+ clock.tick(30)
+
+ logger.info("Quitting! Bye bye!")
+
+
+#########################################################################
+#########################################################################
+
+if __name__ == '__main__':
+ if not len(sys.argv) > 1:
+ print("Usage: " + __file__ + " <model>")
+ sys.exit(2)
+
+ main(model=sys.argv[1], width=1024, height=768)