1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318 |
- #!/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 as e:
- sys.stderr.write("%s\n" % e.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)
|