summaryrefslogtreecommitdiff
path: root/libs/assimp/port/PyAssimp/scripts
diff options
context:
space:
mode:
authorsanine <sanine.not@pm.me>2023-02-12 23:53:22 -0600
committersanine <sanine.not@pm.me>2023-02-12 23:53:22 -0600
commitf1fe73d1909a2448a004a88362a1a532d0d4f7c3 (patch)
treeab37ae3837e2f858de2932bcee9f26e69fab3db1 /libs/assimp/port/PyAssimp/scripts
parentf567ea1e2798fd3156a416e61f083ea3e6b95719 (diff)
switch to tinyobj and nanovg from assimp and cairo
Diffstat (limited to 'libs/assimp/port/PyAssimp/scripts')
-rwxr-xr-xlibs/assimp/port/PyAssimp/scripts/3d_viewer.py1318
-rwxr-xr-xlibs/assimp/port/PyAssimp/scripts/3d_viewer_py3.py1316
-rw-r--r--libs/assimp/port/PyAssimp/scripts/README.md13
-rwxr-xr-xlibs/assimp/port/PyAssimp/scripts/fixed_pipeline_3d_viewer.py372
-rwxr-xr-xlibs/assimp/port/PyAssimp/scripts/quicktest.py53
-rwxr-xr-xlibs/assimp/port/PyAssimp/scripts/sample.py89
-rw-r--r--libs/assimp/port/PyAssimp/scripts/transformations.py1705
7 files changed, 0 insertions, 4866 deletions
diff --git a/libs/assimp/port/PyAssimp/scripts/3d_viewer.py b/libs/assimp/port/PyAssimp/scripts/3d_viewer.py
deleted file mode 100755
index 08a6266..0000000
--- a/libs/assimp/port/PyAssimp/scripts/3d_viewer.py
+++ /dev/null
@@ -1,1318 +0,0 @@
-#!/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)
diff --git a/libs/assimp/port/PyAssimp/scripts/3d_viewer_py3.py b/libs/assimp/port/PyAssimp/scripts/3d_viewer_py3.py
deleted file mode 100755
index fcee637..0000000
--- a/libs/assimp/port/PyAssimp/scripts/3d_viewer_py3.py
+++ /dev/null
@@ -1,1316 +0,0 @@
-#!/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
-
-from functools import reduce
-
-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 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):
- """ 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 """
-
- 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:
- 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)
diff --git a/libs/assimp/port/PyAssimp/scripts/README.md b/libs/assimp/port/PyAssimp/scripts/README.md
deleted file mode 100644
index 42caa27..0000000
--- a/libs/assimp/port/PyAssimp/scripts/README.md
+++ /dev/null
@@ -1,13 +0,0 @@
-pyassimp examples
-=================
-
-- `sample.py`: shows how to load a model with pyassimp, and display some statistics.
-- `3d_viewer.py`: an OpenGL 3D viewer that requires shaders
-- `fixed_pipeline_3d_viewer`: an OpenGL 3D viewer using the old fixed-pipeline.
- Only for illustration example. Base new projects on `3d_viewer.py`.
-
-
-Requirements for the 3D viewers:
-
-- `pyopengl` (on Ubuntu/Debian, `sudo apt-get install python-opengl`)
-- `pygame` (on Ubuntu/Debian, `sudo apt-get install python-pygame`)
diff --git a/libs/assimp/port/PyAssimp/scripts/fixed_pipeline_3d_viewer.py b/libs/assimp/port/PyAssimp/scripts/fixed_pipeline_3d_viewer.py
deleted file mode 100755
index c2f6ceb..0000000
--- a/libs/assimp/port/PyAssimp/scripts/fixed_pipeline_3d_viewer.py
+++ /dev/null
@@ -1,372 +0,0 @@
-#!/usr/bin/env python
-#-*- coding: UTF-8 -*-
-
-""" This program demonstrates the use of pyassimp to load and
-render objects with OpenGL.
-
-'c' cycles between cameras (if any available)
-'q' to quit
-
-This example mixes 'old' OpenGL fixed-function pipeline with
-Vertex Buffer Objects.
-
-Materials are supported but textures are currently ignored.
-
-For a more advanced example (with shaders + keyboard/mouse
-controls), check scripts/sdl_viewer.py
-
-Author: Séverin Lemaignan, 2012
-
-This sample is based on several sources, including:
- - http://www.lighthouse3d.com/tutorials
- - http://www.songho.ca/opengl/gl_transform.html
- - http://code.activestate.com/recipes/325391/
- - ASSIMP's C++ SimpleOpenGL viewer
-"""
-
-import sys
-from OpenGL.GLUT import *
-from OpenGL.GLU import *
-from OpenGL.GL import *
-
-import logging
-logger = logging.getLogger("pyassimp_opengl")
-logging.basicConfig(level=logging.INFO)
-
-import math
-import numpy
-
-import pyassimp
-from pyassimp.postprocess import *
-from pyassimp.helper import *
-
-
-name = 'pyassimp OpenGL viewer'
-height = 600
-width = 900
-
-class GLRenderer():
- def __init__(self):
-
- self.scene = None
-
- self.using_fixed_cam = False
- self.current_cam_index = 0
-
- # store the global scene rotation
- self.angle = 0.
-
- # for FPS calculation
- self.prev_time = 0
- self.prev_fps_time = 0
- self.frames = 0
-
- def prepare_gl_buffers(self, mesh):
- """ Creates 3 buffer objets for each mesh,
- to store the vertices, the normals, and the faces
- indices.
- """
-
- mesh.gl = {}
-
- # Fill the buffer for vertex positions
- mesh.gl["vertices"] = glGenBuffers(1)
- glBindBuffer(GL_ARRAY_BUFFER, mesh.gl["vertices"])
- glBufferData(GL_ARRAY_BUFFER,
- mesh.vertices,
- GL_STATIC_DRAW)
-
- # Fill the buffer for normals
- mesh.gl["normals"] = glGenBuffers(1)
- glBindBuffer(GL_ARRAY_BUFFER, mesh.gl["normals"])
- glBufferData(GL_ARRAY_BUFFER,
- mesh.normals,
- GL_STATIC_DRAW)
-
-
- # Fill the buffer for vertex positions
- mesh.gl["triangles"] = glGenBuffers(1)
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh.gl["triangles"])
- glBufferData(GL_ELEMENT_ARRAY_BUFFER,
- mesh.faces,
- GL_STATIC_DRAW)
-
- # Unbind buffers
- glBindBuffer(GL_ARRAY_BUFFER,0)
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,0)
-
- def load_model(self, path, postprocess = None):
- 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)
-
- # Finally release the model
- pyassimp.release(scene)
-
- def cycle_cameras(self):
- self.current_cam_index
- if not self.scene.cameras:
- return None
- self.current_cam_index = (self.current_cam_index + 1) % len(self.scene.cameras)
- cam = self.scene.cameras[self.current_cam_index]
- logger.info("Switched to camera " + str(cam))
- return cam
-
- def set_default_camera(self):
-
- if not self.using_fixed_cam:
- glLoadIdentity()
-
- gluLookAt(0.,0.,3.,
- 0.,0.,-5.,
- 0.,1.,0.)
-
-
-
- def set_camera(self, camera):
-
- if not camera:
- return
-
- self.using_fixed_cam = True
-
- znear = camera.clipplanenear
- zfar = camera.clipplanefar
- 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)
-
- glMatrixMode(GL_MODELVIEW)
- glLoadIdentity()
-
- cam = transform(camera.position, camera.transformation)
- at = transform(camera.lookat, camera.transformation)
- gluLookAt(cam[0], cam[2], -cam[1],
- at[0], at[2], -at[1],
- 0, 1, 0)
-
- def fit_scene(self, restore = False):
- """ Compute a scale factor and a translation to fit and center
- the whole geometry on the screen.
- """
-
- x_max = self.bb_max[0] - self.bb_min[0]
- y_max = self.bb_max[1] - self.bb_min[1]
- tmp = max(x_max, y_max)
- z_max = self.bb_max[2] - self.bb_min[2]
- tmp = max(z_max, tmp)
-
- if not restore:
- tmp = 1. / tmp
-
- logger.info("Scaling the scene by %.03f" % tmp)
- glScalef(tmp, tmp, tmp)
-
- # center the model
- direction = -1 if not restore else 1
- glTranslatef( direction * self.scene_center[0],
- direction * self.scene_center[1],
- direction * self.scene_center[2] )
-
- return x_max, y_max, z_max
-
- def apply_material(self, mat):
- """ Apply an OpenGL, using one OpenGL display list per material to cache
- the operation.
- """
-
- if not hasattr(mat, "gl_mat"): # evaluate once the mat properties, and cache the values in a glDisplayList.
- diffuse = numpy.array(mat.properties.get("diffuse", [0.8, 0.8, 0.8, 1.0]))
- specular = numpy.array(mat.properties.get("specular", [0., 0., 0., 1.0]))
- ambient = numpy.array(mat.properties.get("ambient", [0.2, 0.2, 0.2, 1.0]))
- emissive = numpy.array(mat.properties.get("emissive", [0., 0., 0., 1.0]))
- shininess = min(mat.properties.get("shininess", 1.0), 128)
- wireframe = mat.properties.get("wireframe", 0)
- twosided = mat.properties.get("twosided", 1)
-
- setattr(mat, "gl_mat", glGenLists(1))
- glNewList(mat.gl_mat, GL_COMPILE)
-
- glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffuse)
- glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, specular)
- glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, ambient)
- glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, emissive)
- glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, shininess)
- glPolygonMode(GL_FRONT_AND_BACK, GL_LINE if wireframe else GL_FILL)
- glDisable(GL_CULL_FACE) if twosided else glEnable(GL_CULL_FACE)
-
- glEndList()
-
- glCallList(mat.gl_mat)
-
-
-
- def do_motion(self):
-
- gl_time = glutGet(GLUT_ELAPSED_TIME)
-
- self.angle = (gl_time - self.prev_time) * 0.1
-
- self.prev_time = gl_time
-
- # Compute FPS
- self.frames += 1
- if gl_time - self.prev_fps_time >= 1000:
- current_fps = self.frames * 1000 / (gl_time - self.prev_fps_time)
- logger.info('%.0f fps' % current_fps)
- self.frames = 0
- self.prev_fps_time = gl_time
-
- glutPostRedisplay()
-
- def recursive_render(self, node):
- """ Main recursive rendering method.
- """
-
- # save model matrix and apply node transformation
- glPushMatrix()
- m = node.transformation.transpose() # OpenGL row major
- glMultMatrixf(m)
-
- for mesh in node.meshes:
- self.apply_material(mesh.material)
-
- glBindBuffer(GL_ARRAY_BUFFER, mesh.gl["vertices"])
- glEnableClientState(GL_VERTEX_ARRAY)
- glVertexPointer(3, GL_FLOAT, 0, None)
-
- glBindBuffer(GL_ARRAY_BUFFER, mesh.gl["normals"])
- glEnableClientState(GL_NORMAL_ARRAY)
- glNormalPointer(GL_FLOAT, 0, None)
-
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh.gl["triangles"])
- glDrawElements(GL_TRIANGLES,len(mesh.faces) * 3, GL_UNSIGNED_INT, None)
-
- glDisableClientState(GL_VERTEX_ARRAY)
- glDisableClientState(GL_NORMAL_ARRAY)
-
- glBindBuffer(GL_ARRAY_BUFFER, 0)
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)
-
- for child in node.children:
- self.recursive_render(child)
-
- glPopMatrix()
-
-
- def display(self):
- """ GLUT callback to redraw OpenGL surface
- """
- glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
-
- glRotatef(self.angle,0.,1.,0.)
- self.recursive_render(self.scene.rootnode)
-
- glutSwapBuffers()
- self.do_motion()
- return
-
- ####################################################################
- ## GLUT keyboard and mouse callbacks ##
- ####################################################################
- def onkeypress(self, key, x, y):
- if key == 'c':
- self.fit_scene(restore = True)
- self.set_camera(self.cycle_cameras())
- if key == 'q':
- sys.exit(0)
-
- def render(self, filename=None, fullscreen = False, autofit = True, postprocess = None):
- """
-
- :param autofit: if true, scale the scene to fit the whole geometry
- in the viewport.
- """
-
- # First initialize the openGL context
- glutInit(sys.argv)
- glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH)
- if not fullscreen:
- glutInitWindowSize(width, height)
- glutCreateWindow(name)
- else:
- glutGameModeString("1024x768")
- if glutGameModeGet(GLUT_GAME_MODE_POSSIBLE):
- glutEnterGameMode()
- else:
- print("Fullscreen mode not available!")
- sys.exit(1)
-
- self.load_model(filename, postprocess = postprocess)
-
-
- glClearColor(0.1,0.1,0.1,1.)
- #glShadeModel(GL_SMOOTH)
-
- glEnable(GL_LIGHTING)
-
- glEnable(GL_CULL_FACE)
- glEnable(GL_DEPTH_TEST)
-
- glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE)
- glEnable(GL_NORMALIZE)
- glEnable(GL_LIGHT0)
-
- glutDisplayFunc(self.display)
-
-
- glMatrixMode(GL_PROJECTION)
- glLoadIdentity()
- gluPerspective(35.0, width/float(height) , 0.10, 100.0)
- glMatrixMode(GL_MODELVIEW)
- self.set_default_camera()
-
- if autofit:
- # scale the whole asset to fit into our view frustum·
- self.fit_scene()
-
- glPushMatrix()
-
- glutKeyboardFunc(self.onkeypress)
- glutIgnoreKeyRepeat(1)
-
- glutMainLoop()
-
-
-if __name__ == '__main__':
- if not len(sys.argv) > 1:
- print("Usage: " + __file__ + " <model>")
- sys.exit(0)
-
- glrender = GLRenderer()
- glrender.render(sys.argv[1], fullscreen = False, postprocess = aiProcessPreset_TargetRealtime_MaxQuality)
-
diff --git a/libs/assimp/port/PyAssimp/scripts/quicktest.py b/libs/assimp/port/PyAssimp/scripts/quicktest.py
deleted file mode 100755
index cbeccb4..0000000
--- a/libs/assimp/port/PyAssimp/scripts/quicktest.py
+++ /dev/null
@@ -1,53 +0,0 @@
-#!/usr/bin/env python
-#-*- coding: UTF-8 -*-
-
-"""
-This module uses the sample.py script to load all test models it finds.
-
-Note: this is not an exhaustive test suite, it does not check the
-data structures in detail. It just verifies whether basic
-loading and querying of 3d models using pyassimp works.
-"""
-
-import os
-import sys
-
-# Make the development (ie. GIT repo) version of PyAssimp available for import.
-sys.path.insert(0, '..')
-
-import sample
-from pyassimp import errors
-
-# Paths to model files.
-basepaths = [os.path.join('..', '..', '..', 'test', 'models'),
- os.path.join('..', '..', '..', 'test', 'models-nonbsd')]
-
-# Valid extensions for 3D model files.
-extensions = ['.3ds', '.x', '.lwo', '.obj', '.md5mesh', '.dxf', '.ply', '.stl',
- '.dae', '.md5anim', '.lws', '.irrmesh', '.nff', '.off', '.blend']
-
-
-def run_tests():
- ok, err = 0, 0
- for path in basepaths:
- print("Looking for models in %s..." % path)
- for root, dirs, files in os.walk(path):
- for afile in files:
- base, ext = os.path.splitext(afile)
- if ext in extensions:
- try:
- sample.main(os.path.join(root, afile))
- ok += 1
- except errors.AssimpError as error:
- # Assimp error is fine; this is a controlled case.
- print(error)
- err += 1
- except Exception:
- print("Error encountered while loading <%s>"
- % os.path.join(root, afile))
- print('** Loaded %s models, got controlled errors for %s files'
- % (ok, err))
-
-
-if __name__ == '__main__':
- run_tests()
diff --git a/libs/assimp/port/PyAssimp/scripts/sample.py b/libs/assimp/port/PyAssimp/scripts/sample.py
deleted file mode 100755
index 3cd4b3e..0000000
--- a/libs/assimp/port/PyAssimp/scripts/sample.py
+++ /dev/null
@@ -1,89 +0,0 @@
-#!/usr/bin/env python
-#-*- coding: UTF-8 -*-
-
-"""
-This module demonstrates the functionality of PyAssimp.
-"""
-
-import sys
-import logging
-logging.basicConfig(level=logging.INFO)
-
-import pyassimp
-import pyassimp.postprocess
-
-def recur_node(node,level = 0):
- print(" " + "\t" * level + "- " + str(node))
- for child in node.children:
- recur_node(child, level + 1)
-
-
-def main(filename=None):
-
- scene = pyassimp.load(filename, processing=pyassimp.postprocess.aiProcess_Triangulate)
-
- #the model we load
- print("MODEL:" + filename)
- print
-
- #write some statistics
- print("SCENE:")
- print(" meshes:" + str(len(scene.meshes)))
- print(" materials:" + str(len(scene.materials)))
- print(" textures:" + str(len(scene.textures)))
- print
-
- print("NODES:")
- recur_node(scene.rootnode)
-
- print
- print("MESHES:")
- for index, mesh in enumerate(scene.meshes):
- print(" MESH" + str(index+1))
- print(" material id:" + str(mesh.materialindex+1))
- print(" vertices:" + str(len(mesh.vertices)))
- print(" first 3 verts:\n" + str(mesh.vertices[:3]))
- if mesh.normals.any():
- print(" first 3 normals:\n" + str(mesh.normals[:3]))
- else:
- print(" no normals")
- print(" colors:" + str(len(mesh.colors)))
- tcs = mesh.texturecoords
- if tcs.any():
- for tc_index, tc in enumerate(tcs):
- print(" texture-coords "+ str(tc_index) + ":" + str(len(tcs[tc_index])) + "first3:" + str(tcs[tc_index][:3]))
-
- else:
- print(" no texture coordinates")
- print(" uv-component-count:" + str(len(mesh.numuvcomponents)))
- print(" faces:" + str(len(mesh.faces)) + " -> first:\n" + str(mesh.faces[:3]))
- print(" bones:" + str(len(mesh.bones)) + " -> first:" + str([str(b) for b in mesh.bones[:3]]))
- print
-
- print("MATERIALS:")
- for index, material in enumerate(scene.materials):
- print(" MATERIAL (id:" + str(index+1) + ")")
- for key, value in material.properties.items():
- print(" %s: %s" % (key, value))
- print
-
- print("TEXTURES:")
- for index, texture in enumerate(scene.textures):
- print(" TEXTURE" + str(index+1))
- print(" width:" + str(texture.width))
- print(" height:" + str(texture.height))
- print(" hint:" + str(texture.achformathint))
- print(" data (size):" + str(len(texture.data)))
-
- # Finally release the model
- pyassimp.release(scene)
-
-def usage():
- print("Usage: sample.py <3d model>")
-
-if __name__ == "__main__":
-
- if len(sys.argv) != 2:
- usage()
- else:
- main(sys.argv[1])
diff --git a/libs/assimp/port/PyAssimp/scripts/transformations.py b/libs/assimp/port/PyAssimp/scripts/transformations.py
deleted file mode 100644
index bf0cac9..0000000
--- a/libs/assimp/port/PyAssimp/scripts/transformations.py
+++ /dev/null
@@ -1,1705 +0,0 @@
-# -*- coding: utf-8 -*-
-# transformations.py
-
-# Copyright (c) 2006, Christoph Gohlke
-# Copyright (c) 2006-2009, The Regents of the University of California
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-#
-# * Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-# * Redistributions in binary form must reproduce the above copyright
-# notice, this list of conditions and the following disclaimer in the
-# documentation and/or other materials provided with the distribution.
-# * Neither the name of the copyright holders nor the names of any
-# contributors may be used to endorse or promote products derived
-# from this software without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-# POSSIBILITY OF SUCH DAMAGE.
-
-"""Homogeneous Transformation Matrices and Quaternions.
-
-A library for calculating 4x4 matrices for translating, rotating, reflecting,
-scaling, shearing, projecting, orthogonalizing, and superimposing arrays of
-3D homogeneous coordinates as well as for converting between rotation matrices,
-Euler angles, and quaternions. Also includes an Arcball control object and
-functions to decompose transformation matrices.
-
-:Authors:
- `Christoph Gohlke <http://www.lfd.uci.edu/~gohlke/>`__,
- Laboratory for Fluorescence Dynamics, University of California, Irvine
-
-:Version: 20090418
-
-Requirements
-------------
-
-* `Python 2.6 <http://www.python.org>`__
-* `Numpy 1.3 <http://numpy.scipy.org>`__
-* `transformations.c 20090418 <http://www.lfd.uci.edu/~gohlke/>`__
- (optional implementation of some functions in C)
-
-Notes
------
-
-Matrices (M) can be inverted using numpy.linalg.inv(M), concatenated using
-numpy.dot(M0, M1), or used to transform homogeneous coordinates (v) using
-numpy.dot(M, v) for shape (4, \*) "point of arrays", respectively
-numpy.dot(v, M.T) for shape (\*, 4) "array of points".
-
-Calculations are carried out with numpy.float64 precision.
-
-This Python implementation is not optimized for speed.
-
-Vector, point, quaternion, and matrix function arguments are expected to be
-"array like", i.e. tuple, list, or numpy arrays.
-
-Return types are numpy arrays unless specified otherwise.
-
-Angles are in radians unless specified otherwise.
-
-Quaternions ix+jy+kz+w are represented as [x, y, z, w].
-
-Use the transpose of transformation matrices for OpenGL glMultMatrixd().
-
-A triple of Euler angles can be applied/interpreted in 24 ways, which can
-be specified using a 4 character string or encoded 4-tuple:
-
- *Axes 4-string*: e.g. 'sxyz' or 'ryxy'
-
- - first character : rotations are applied to 's'tatic or 'r'otating frame
- - remaining characters : successive rotation axis 'x', 'y', or 'z'
-
- *Axes 4-tuple*: e.g. (0, 0, 0, 0) or (1, 1, 1, 1)
-
- - inner axis: code of axis ('x':0, 'y':1, 'z':2) of rightmost matrix.
- - parity : even (0) if inner axis 'x' is followed by 'y', 'y' is followed
- by 'z', or 'z' is followed by 'x'. Otherwise odd (1).
- - repetition : first and last axis are same (1) or different (0).
- - frame : rotations are applied to static (0) or rotating (1) frame.
-
-References
-----------
-
-(1) Matrices and transformations. Ronald Goldman.
- In "Graphics Gems I", pp 472-475. Morgan Kaufmann, 1990.
-(2) More matrices and transformations: shear and pseudo-perspective.
- Ronald Goldman. In "Graphics Gems II", pp 320-323. Morgan Kaufmann, 1991.
-(3) Decomposing a matrix into simple transformations. Spencer Thomas.
- In "Graphics Gems II", pp 320-323. Morgan Kaufmann, 1991.
-(4) Recovering the data from the transformation matrix. Ronald Goldman.
- In "Graphics Gems II", pp 324-331. Morgan Kaufmann, 1991.
-(5) Euler angle conversion. Ken Shoemake.
- In "Graphics Gems IV", pp 222-229. Morgan Kaufmann, 1994.
-(6) Arcball rotation control. Ken Shoemake.
- In "Graphics Gems IV", pp 175-192. Morgan Kaufmann, 1994.
-(7) Representing attitude: Euler angles, unit quaternions, and rotation
- vectors. James Diebel. 2006.
-(8) A discussion of the solution for the best rotation to relate two sets
- of vectors. W Kabsch. Acta Cryst. 1978. A34, 827-828.
-(9) Closed-form solution of absolute orientation using unit quaternions.
- BKP Horn. J Opt Soc Am A. 1987. 4(4), 629-642.
-(10) Quaternions. Ken Shoemake.
- http://www.sfu.ca/~jwa3/cmpt461/files/quatut.pdf
-(11) From quaternion to matrix and back. JMP van Waveren. 2005.
- http://www.intel.com/cd/ids/developer/asmo-na/eng/293748.htm
-(12) Uniform random rotations. Ken Shoemake.
- In "Graphics Gems III", pp 124-132. Morgan Kaufmann, 1992.
-
-
-Examples
---------
-
->>> alpha, beta, gamma = 0.123, -1.234, 2.345
->>> origin, xaxis, yaxis, zaxis = (0, 0, 0), (1, 0, 0), (0, 1, 0), (0, 0, 1)
->>> I = identity_matrix()
->>> Rx = rotation_matrix(alpha, xaxis)
->>> Ry = rotation_matrix(beta, yaxis)
->>> Rz = rotation_matrix(gamma, zaxis)
->>> R = concatenate_matrices(Rx, Ry, Rz)
->>> euler = euler_from_matrix(R, 'rxyz')
->>> numpy.allclose([alpha, beta, gamma], euler)
-True
->>> Re = euler_matrix(alpha, beta, gamma, 'rxyz')
->>> is_same_transform(R, Re)
-True
->>> al, be, ga = euler_from_matrix(Re, 'rxyz')
->>> is_same_transform(Re, euler_matrix(al, be, ga, 'rxyz'))
-True
->>> qx = quaternion_about_axis(alpha, xaxis)
->>> qy = quaternion_about_axis(beta, yaxis)
->>> qz = quaternion_about_axis(gamma, zaxis)
->>> q = quaternion_multiply(qx, qy)
->>> q = quaternion_multiply(q, qz)
->>> Rq = quaternion_matrix(q)
->>> is_same_transform(R, Rq)
-True
->>> S = scale_matrix(1.23, origin)
->>> T = translation_matrix((1, 2, 3))
->>> Z = shear_matrix(beta, xaxis, origin, zaxis)
->>> R = random_rotation_matrix(numpy.random.rand(3))
->>> M = concatenate_matrices(T, R, Z, S)
->>> scale, shear, angles, trans, persp = decompose_matrix(M)
->>> numpy.allclose(scale, 1.23)
-True
->>> numpy.allclose(trans, (1, 2, 3))
-True
->>> numpy.allclose(shear, (0, math.tan(beta), 0))
-True
->>> is_same_transform(R, euler_matrix(axes='sxyz', *angles))
-True
->>> M1 = compose_matrix(scale, shear, angles, trans, persp)
->>> is_same_transform(M, M1)
-True
-
-"""
-
-from __future__ import division
-
-import warnings
-import math
-
-import numpy
-
-# Documentation in HTML format can be generated with Epydoc
-__docformat__ = "restructuredtext en"
-
-
-def identity_matrix():
- """Return 4x4 identity/unit matrix.
-
- >>> I = identity_matrix()
- >>> numpy.allclose(I, numpy.dot(I, I))
- True
- >>> numpy.sum(I), numpy.trace(I)
- (4.0, 4.0)
- >>> numpy.allclose(I, numpy.identity(4, dtype=numpy.float64))
- True
-
- """
- return numpy.identity(4, dtype=numpy.float64)
-
-
-def translation_matrix(direction):
- """Return matrix to translate by direction vector.
-
- >>> v = numpy.random.random(3) - 0.5
- >>> numpy.allclose(v, translation_matrix(v)[:3, 3])
- True
-
- """
- M = numpy.identity(4)
- M[:3, 3] = direction[:3]
- return M
-
-
-def translation_from_matrix(matrix):
- """Return translation vector from translation matrix.
-
- >>> v0 = numpy.random.random(3) - 0.5
- >>> v1 = translation_from_matrix(translation_matrix(v0))
- >>> numpy.allclose(v0, v1)
- True
-
- """
- return numpy.array(matrix, copy=False)[:3, 3].copy()
-
-
-def reflection_matrix(point, normal):
- """Return matrix to mirror at plane defined by point and normal vector.
-
- >>> v0 = numpy.random.random(4) - 0.5
- >>> v0[3] = 1.0
- >>> v1 = numpy.random.random(3) - 0.5
- >>> R = reflection_matrix(v0, v1)
- >>> numpy.allclose(2., numpy.trace(R))
- True
- >>> numpy.allclose(v0, numpy.dot(R, v0))
- True
- >>> v2 = v0.copy()
- >>> v2[:3] += v1
- >>> v3 = v0.copy()
- >>> v2[:3] -= v1
- >>> numpy.allclose(v2, numpy.dot(R, v3))
- True
-
- """
- normal = unit_vector(normal[:3])
- M = numpy.identity(4)
- M[:3, :3] -= 2.0 * numpy.outer(normal, normal)
- M[:3, 3] = (2.0 * numpy.dot(point[:3], normal)) * normal
- return M
-
-
-def reflection_from_matrix(matrix):
- """Return mirror plane point and normal vector from reflection matrix.
-
- >>> v0 = numpy.random.random(3) - 0.5
- >>> v1 = numpy.random.random(3) - 0.5
- >>> M0 = reflection_matrix(v0, v1)
- >>> point, normal = reflection_from_matrix(M0)
- >>> M1 = reflection_matrix(point, normal)
- >>> is_same_transform(M0, M1)
- True
-
- """
- M = numpy.array(matrix, dtype=numpy.float64, copy=False)
- # normal: unit eigenvector corresponding to eigenvalue -1
- l, V = numpy.linalg.eig(M[:3, :3])
- i = numpy.where(abs(numpy.real(l) + 1.0) < 1e-8)[0]
- if not len(i):
- raise ValueError("no unit eigenvector corresponding to eigenvalue -1")
- normal = numpy.real(V[:, i[0]]).squeeze()
- # point: any unit eigenvector corresponding to eigenvalue 1
- l, V = numpy.linalg.eig(M)
- i = numpy.where(abs(numpy.real(l) - 1.0) < 1e-8)[0]
- if not len(i):
- raise ValueError("no unit eigenvector corresponding to eigenvalue 1")
- point = numpy.real(V[:, i[-1]]).squeeze()
- point /= point[3]
- return point, normal
-
-
-def rotation_matrix(angle, direction, point=None):
- """Return matrix to rotate about axis defined by point and direction.
-
- >>> angle = (random.random() - 0.5) * (2*math.pi)
- >>> direc = numpy.random.random(3) - 0.5
- >>> point = numpy.random.random(3) - 0.5
- >>> R0 = rotation_matrix(angle, direc, point)
- >>> R1 = rotation_matrix(angle-2*math.pi, direc, point)
- >>> is_same_transform(R0, R1)
- True
- >>> R0 = rotation_matrix(angle, direc, point)
- >>> R1 = rotation_matrix(-angle, -direc, point)
- >>> is_same_transform(R0, R1)
- True
- >>> I = numpy.identity(4, numpy.float64)
- >>> numpy.allclose(I, rotation_matrix(math.pi*2, direc))
- True
- >>> numpy.allclose(2., numpy.trace(rotation_matrix(math.pi/2,
- ... direc, point)))
- True
-
- """
- sina = math.sin(angle)
- cosa = math.cos(angle)
- direction = unit_vector(direction[:3])
- # rotation matrix around unit vector
- R = numpy.array(((cosa, 0.0, 0.0),
- (0.0, cosa, 0.0),
- (0.0, 0.0, cosa)), dtype=numpy.float64)
- R += numpy.outer(direction, direction) * (1.0 - cosa)
- direction *= sina
- R += numpy.array((( 0.0, -direction[2], direction[1]),
- ( direction[2], 0.0, -direction[0]),
- (-direction[1], direction[0], 0.0)),
- dtype=numpy.float64)
- M = numpy.identity(4)
- M[:3, :3] = R
- if point is not None:
- # rotation not around origin
- point = numpy.array(point[:3], dtype=numpy.float64, copy=False)
- M[:3, 3] = point - numpy.dot(R, point)
- return M
-
-
-def rotation_from_matrix(matrix):
- """Return rotation angle and axis from rotation matrix.
-
- >>> angle = (random.random() - 0.5) * (2*math.pi)
- >>> direc = numpy.random.random(3) - 0.5
- >>> point = numpy.random.random(3) - 0.5
- >>> R0 = rotation_matrix(angle, direc, point)
- >>> angle, direc, point = rotation_from_matrix(R0)
- >>> R1 = rotation_matrix(angle, direc, point)
- >>> is_same_transform(R0, R1)
- True
-
- """
- R = numpy.array(matrix, dtype=numpy.float64, copy=False)
- R33 = R[:3, :3]
- # direction: unit eigenvector of R33 corresponding to eigenvalue of 1
- l, W = numpy.linalg.eig(R33.T)
- i = numpy.where(abs(numpy.real(l) - 1.0) < 1e-8)[0]
- if not len(i):
- raise ValueError("no unit eigenvector corresponding to eigenvalue 1")
- direction = numpy.real(W[:, i[-1]]).squeeze()
- # point: unit eigenvector of R33 corresponding to eigenvalue of 1
- l, Q = numpy.linalg.eig(R)
- i = numpy.where(abs(numpy.real(l) - 1.0) < 1e-8)[0]
- if not len(i):
- raise ValueError("no unit eigenvector corresponding to eigenvalue 1")
- point = numpy.real(Q[:, i[-1]]).squeeze()
- point /= point[3]
- # rotation angle depending on direction
- cosa = (numpy.trace(R33) - 1.0) / 2.0
- if abs(direction[2]) > 1e-8:
- sina = (R[1, 0] + (cosa-1.0)*direction[0]*direction[1]) / direction[2]
- elif abs(direction[1]) > 1e-8:
- sina = (R[0, 2] + (cosa-1.0)*direction[0]*direction[2]) / direction[1]
- else:
- sina = (R[2, 1] + (cosa-1.0)*direction[1]*direction[2]) / direction[0]
- angle = math.atan2(sina, cosa)
- return angle, direction, point
-
-
-def scale_matrix(factor, origin=None, direction=None):
- """Return matrix to scale by factor around origin in direction.
-
- Use factor -1 for point symmetry.
-
- >>> v = (numpy.random.rand(4, 5) - 0.5) * 20.0
- >>> v[3] = 1.0
- >>> S = scale_matrix(-1.234)
- >>> numpy.allclose(numpy.dot(S, v)[:3], -1.234*v[:3])
- True
- >>> factor = random.random() * 10 - 5
- >>> origin = numpy.random.random(3) - 0.5
- >>> direct = numpy.random.random(3) - 0.5
- >>> S = scale_matrix(factor, origin)
- >>> S = scale_matrix(factor, origin, direct)
-
- """
- if direction is None:
- # uniform scaling
- M = numpy.array(((factor, 0.0, 0.0, 0.0),
- (0.0, factor, 0.0, 0.0),
- (0.0, 0.0, factor, 0.0),
- (0.0, 0.0, 0.0, 1.0)), dtype=numpy.float64)
- if origin is not None:
- M[:3, 3] = origin[:3]
- M[:3, 3] *= 1.0 - factor
- else:
- # nonuniform scaling
- direction = unit_vector(direction[:3])
- factor = 1.0 - factor
- M = numpy.identity(4)
- M[:3, :3] -= factor * numpy.outer(direction, direction)
- if origin is not None:
- M[:3, 3] = (factor * numpy.dot(origin[:3], direction)) * direction
- return M
-
-
-def scale_from_matrix(matrix):
- """Return scaling factor, origin and direction from scaling matrix.
-
- >>> factor = random.random() * 10 - 5
- >>> origin = numpy.random.random(3) - 0.5
- >>> direct = numpy.random.random(3) - 0.5
- >>> S0 = scale_matrix(factor, origin)
- >>> factor, origin, direction = scale_from_matrix(S0)
- >>> S1 = scale_matrix(factor, origin, direction)
- >>> is_same_transform(S0, S1)
- True
- >>> S0 = scale_matrix(factor, origin, direct)
- >>> factor, origin, direction = scale_from_matrix(S0)
- >>> S1 = scale_matrix(factor, origin, direction)
- >>> is_same_transform(S0, S1)
- True
-
- """
- M = numpy.array(matrix, dtype=numpy.float64, copy=False)
- M33 = M[:3, :3]
- factor = numpy.trace(M33) - 2.0
- try:
- # direction: unit eigenvector corresponding to eigenvalue factor
- l, V = numpy.linalg.eig(M33)
- i = numpy.where(abs(numpy.real(l) - factor) < 1e-8)[0][0]
- direction = numpy.real(V[:, i]).squeeze()
- direction /= vector_norm(direction)
- except IndexError:
- # uniform scaling
- factor = (factor + 2.0) / 3.0
- direction = None
- # origin: any eigenvector corresponding to eigenvalue 1
- l, V = numpy.linalg.eig(M)
- i = numpy.where(abs(numpy.real(l) - 1.0) < 1e-8)[0]
- if not len(i):
- raise ValueError("no eigenvector corresponding to eigenvalue 1")
- origin = numpy.real(V[:, i[-1]]).squeeze()
- origin /= origin[3]
- return factor, origin, direction
-
-
-def projection_matrix(point, normal, direction=None,
- perspective=None, pseudo=False):
- """Return matrix to project onto plane defined by point and normal.
-
- Using either perspective point, projection direction, or none of both.
-
- If pseudo is True, perspective projections will preserve relative depth
- such that Perspective = dot(Orthogonal, PseudoPerspective).
-
- >>> P = projection_matrix((0, 0, 0), (1, 0, 0))
- >>> numpy.allclose(P[1:, 1:], numpy.identity(4)[1:, 1:])
- True
- >>> point = numpy.random.random(3) - 0.5
- >>> normal = numpy.random.random(3) - 0.5
- >>> direct = numpy.random.random(3) - 0.5
- >>> persp = numpy.random.random(3) - 0.5
- >>> P0 = projection_matrix(point, normal)
- >>> P1 = projection_matrix(point, normal, direction=direct)
- >>> P2 = projection_matrix(point, normal, perspective=persp)
- >>> P3 = projection_matrix(point, normal, perspective=persp, pseudo=True)
- >>> is_same_transform(P2, numpy.dot(P0, P3))
- True
- >>> P = projection_matrix((3, 0, 0), (1, 1, 0), (1, 0, 0))
- >>> v0 = (numpy.random.rand(4, 5) - 0.5) * 20.0
- >>> v0[3] = 1.0
- >>> v1 = numpy.dot(P, v0)
- >>> numpy.allclose(v1[1], v0[1])
- True
- >>> numpy.allclose(v1[0], 3.0-v1[1])
- True
-
- """
- M = numpy.identity(4)
- point = numpy.array(point[:3], dtype=numpy.float64, copy=False)
- normal = unit_vector(normal[:3])
- if perspective is not None:
- # perspective projection
- perspective = numpy.array(perspective[:3], dtype=numpy.float64,
- copy=False)
- M[0, 0] = M[1, 1] = M[2, 2] = numpy.dot(perspective-point, normal)
- M[:3, :3] -= numpy.outer(perspective, normal)
- if pseudo:
- # preserve relative depth
- M[:3, :3] -= numpy.outer(normal, normal)
- M[:3, 3] = numpy.dot(point, normal) * (perspective+normal)
- else:
- M[:3, 3] = numpy.dot(point, normal) * perspective
- M[3, :3] = -normal
- M[3, 3] = numpy.dot(perspective, normal)
- elif direction is not None:
- # parallel projection
- direction = numpy.array(direction[:3], dtype=numpy.float64, copy=False)
- scale = numpy.dot(direction, normal)
- M[:3, :3] -= numpy.outer(direction, normal) / scale
- M[:3, 3] = direction * (numpy.dot(point, normal) / scale)
- else:
- # orthogonal projection
- M[:3, :3] -= numpy.outer(normal, normal)
- M[:3, 3] = numpy.dot(point, normal) * normal
- return M
-
-
-def projection_from_matrix(matrix, pseudo=False):
- """Return projection plane and perspective point from projection matrix.
-
- Return values are same as arguments for projection_matrix function:
- point, normal, direction, perspective, and pseudo.
-
- >>> point = numpy.random.random(3) - 0.5
- >>> normal = numpy.random.random(3) - 0.5
- >>> direct = numpy.random.random(3) - 0.5
- >>> persp = numpy.random.random(3) - 0.5
- >>> P0 = projection_matrix(point, normal)
- >>> result = projection_from_matrix(P0)
- >>> P1 = projection_matrix(*result)
- >>> is_same_transform(P0, P1)
- True
- >>> P0 = projection_matrix(point, normal, direct)
- >>> result = projection_from_matrix(P0)
- >>> P1 = projection_matrix(*result)
- >>> is_same_transform(P0, P1)
- True
- >>> P0 = projection_matrix(point, normal, perspective=persp, pseudo=False)
- >>> result = projection_from_matrix(P0, pseudo=False)
- >>> P1 = projection_matrix(*result)
- >>> is_same_transform(P0, P1)
- True
- >>> P0 = projection_matrix(point, normal, perspective=persp, pseudo=True)
- >>> result = projection_from_matrix(P0, pseudo=True)
- >>> P1 = projection_matrix(*result)
- >>> is_same_transform(P0, P1)
- True
-
- """
- M = numpy.array(matrix, dtype=numpy.float64, copy=False)
- M33 = M[:3, :3]
- l, V = numpy.linalg.eig(M)
- i = numpy.where(abs(numpy.real(l) - 1.0) < 1e-8)[0]
- if not pseudo and len(i):
- # point: any eigenvector corresponding to eigenvalue 1
- point = numpy.real(V[:, i[-1]]).squeeze()
- point /= point[3]
- # direction: unit eigenvector corresponding to eigenvalue 0
- l, V = numpy.linalg.eig(M33)
- i = numpy.where(abs(numpy.real(l)) < 1e-8)[0]
- if not len(i):
- raise ValueError("no eigenvector corresponding to eigenvalue 0")
- direction = numpy.real(V[:, i[0]]).squeeze()
- direction /= vector_norm(direction)
- # normal: unit eigenvector of M33.T corresponding to eigenvalue 0
- l, V = numpy.linalg.eig(M33.T)
- i = numpy.where(abs(numpy.real(l)) < 1e-8)[0]
- if len(i):
- # parallel projection
- normal = numpy.real(V[:, i[0]]).squeeze()
- normal /= vector_norm(normal)
- return point, normal, direction, None, False
- else:
- # orthogonal projection, where normal equals direction vector
- return point, direction, None, None, False
- else:
- # perspective projection
- i = numpy.where(abs(numpy.real(l)) > 1e-8)[0]
- if not len(i):
- raise ValueError(
- "no eigenvector not corresponding to eigenvalue 0")
- point = numpy.real(V[:, i[-1]]).squeeze()
- point /= point[3]
- normal = - M[3, :3]
- perspective = M[:3, 3] / numpy.dot(point[:3], normal)
- if pseudo:
- perspective -= normal
- return point, normal, None, perspective, pseudo
-
-
-def clip_matrix(left, right, bottom, top, near, far, perspective=False):
- """Return matrix to obtain normalized device coordinates from frustrum.
-
- The frustrum bounds are axis-aligned along x (left, right),
- y (bottom, top) and z (near, far).
-
- Normalized device coordinates are in range [-1, 1] if coordinates are
- inside the frustrum.
-
- If perspective is True the frustrum is a truncated pyramid with the
- perspective point at origin and direction along z axis, otherwise an
- orthographic canonical view volume (a box).
-
- Homogeneous coordinates transformed by the perspective clip matrix
- need to be dehomogenized (divided by w coordinate).
-
- >>> frustrum = numpy.random.rand(6)
- >>> frustrum[1] += frustrum[0]
- >>> frustrum[3] += frustrum[2]
- >>> frustrum[5] += frustrum[4]
- >>> M = clip_matrix(*frustrum, perspective=False)
- >>> numpy.dot(M, [frustrum[0], frustrum[2], frustrum[4], 1.0])
- array([-1., -1., -1., 1.])
- >>> numpy.dot(M, [frustrum[1], frustrum[3], frustrum[5], 1.0])
- array([ 1., 1., 1., 1.])
- >>> M = clip_matrix(*frustrum, perspective=True)
- >>> v = numpy.dot(M, [frustrum[0], frustrum[2], frustrum[4], 1.0])
- >>> v / v[3]
- array([-1., -1., -1., 1.])
- >>> v = numpy.dot(M, [frustrum[1], frustrum[3], frustrum[4], 1.0])
- >>> v / v[3]
- array([ 1., 1., -1., 1.])
-
- """
- if left >= right or bottom >= top or near >= far:
- raise ValueError("invalid frustrum")
- if perspective:
- if near <= _EPS:
- raise ValueError("invalid frustrum: near <= 0")
- t = 2.0 * near
- M = ((-t/(right-left), 0.0, (right+left)/(right-left), 0.0),
- (0.0, -t/(top-bottom), (top+bottom)/(top-bottom), 0.0),
- (0.0, 0.0, -(far+near)/(far-near), t*far/(far-near)),
- (0.0, 0.0, -1.0, 0.0))
- else:
- M = ((2.0/(right-left), 0.0, 0.0, (right+left)/(left-right)),
- (0.0, 2.0/(top-bottom), 0.0, (top+bottom)/(bottom-top)),
- (0.0, 0.0, 2.0/(far-near), (far+near)/(near-far)),
- (0.0, 0.0, 0.0, 1.0))
- return numpy.array(M, dtype=numpy.float64)
-
-
-def shear_matrix(angle, direction, point, normal):
- """Return matrix to shear by angle along direction vector on shear plane.
-
- The shear plane is defined by a point and normal vector. The direction
- vector must be orthogonal to the plane's normal vector.
-
- A point P is transformed by the shear matrix into P" such that
- the vector P-P" is parallel to the direction vector and its extent is
- given by the angle of P-P'-P", where P' is the orthogonal projection
- of P onto the shear plane.
-
- >>> angle = (random.random() - 0.5) * 4*math.pi
- >>> direct = numpy.random.random(3) - 0.5
- >>> point = numpy.random.random(3) - 0.5
- >>> normal = numpy.cross(direct, numpy.random.random(3))
- >>> S = shear_matrix(angle, direct, point, normal)
- >>> numpy.allclose(1.0, numpy.linalg.det(S))
- True
-
- """
- normal = unit_vector(normal[:3])
- direction = unit_vector(direction[:3])
- if abs(numpy.dot(normal, direction)) > 1e-6:
- raise ValueError("direction and normal vectors are not orthogonal")
- angle = math.tan(angle)
- M = numpy.identity(4)
- M[:3, :3] += angle * numpy.outer(direction, normal)
- M[:3, 3] = -angle * numpy.dot(point[:3], normal) * direction
- return M
-
-
-def shear_from_matrix(matrix):
- """Return shear angle, direction and plane from shear matrix.
-
- >>> angle = (random.random() - 0.5) * 4*math.pi
- >>> direct = numpy.random.random(3) - 0.5
- >>> point = numpy.random.random(3) - 0.5
- >>> normal = numpy.cross(direct, numpy.random.random(3))
- >>> S0 = shear_matrix(angle, direct, point, normal)
- >>> angle, direct, point, normal = shear_from_matrix(S0)
- >>> S1 = shear_matrix(angle, direct, point, normal)
- >>> is_same_transform(S0, S1)
- True
-
- """
- M = numpy.array(matrix, dtype=numpy.float64, copy=False)
- M33 = M[:3, :3]
- # normal: cross independent eigenvectors corresponding to the eigenvalue 1
- l, V = numpy.linalg.eig(M33)
- i = numpy.where(abs(numpy.real(l) - 1.0) < 1e-4)[0]
- if len(i) < 2:
- raise ValueError("No two linear independent eigenvectors found %s" % l)
- V = numpy.real(V[:, i]).squeeze().T
- lenorm = -1.0
- for i0, i1 in ((0, 1), (0, 2), (1, 2)):
- n = numpy.cross(V[i0], V[i1])
- l = vector_norm(n)
- if l > lenorm:
- lenorm = l
- normal = n
- normal /= lenorm
- # direction and angle
- direction = numpy.dot(M33 - numpy.identity(3), normal)
- angle = vector_norm(direction)
- direction /= angle
- angle = math.atan(angle)
- # point: eigenvector corresponding to eigenvalue 1
- l, V = numpy.linalg.eig(M)
- i = numpy.where(abs(numpy.real(l) - 1.0) < 1e-8)[0]
- if not len(i):
- raise ValueError("no eigenvector corresponding to eigenvalue 1")
- point = numpy.real(V[:, i[-1]]).squeeze()
- point /= point[3]
- return angle, direction, point, normal
-
-
-def decompose_matrix(matrix):
- """Return sequence of transformations from transformation matrix.
-
- matrix : array_like
- Non-degenerative homogeneous transformation matrix
-
- Return tuple of:
- scale : vector of 3 scaling factors
- shear : list of shear factors for x-y, x-z, y-z axes
- angles : list of Euler angles about static x, y, z axes
- translate : translation vector along x, y, z axes
- perspective : perspective partition of matrix
-
- Raise ValueError if matrix is of wrong type or degenerative.
-
- >>> T0 = translation_matrix((1, 2, 3))
- >>> scale, shear, angles, trans, persp = decompose_matrix(T0)
- >>> T1 = translation_matrix(trans)
- >>> numpy.allclose(T0, T1)
- True
- >>> S = scale_matrix(0.123)
- >>> scale, shear, angles, trans, persp = decompose_matrix(S)
- >>> scale[0]
- 0.123
- >>> R0 = euler_matrix(1, 2, 3)
- >>> scale, shear, angles, trans, persp = decompose_matrix(R0)
- >>> R1 = euler_matrix(*angles)
- >>> numpy.allclose(R0, R1)
- True
-
- """
- M = numpy.array(matrix, dtype=numpy.float64, copy=True).T
- if abs(M[3, 3]) < _EPS:
- raise ValueError("M[3, 3] is zero")
- M /= M[3, 3]
- P = M.copy()
- P[:, 3] = 0, 0, 0, 1
- if not numpy.linalg.det(P):
- raise ValueError("Matrix is singular")
-
- scale = numpy.zeros((3, ), dtype=numpy.float64)
- shear = [0, 0, 0]
- angles = [0, 0, 0]
-
- if any(abs(M[:3, 3]) > _EPS):
- perspective = numpy.dot(M[:, 3], numpy.linalg.inv(P.T))
- M[:, 3] = 0, 0, 0, 1
- else:
- perspective = numpy.array((0, 0, 0, 1), dtype=numpy.float64)
-
- translate = M[3, :3].copy()
- M[3, :3] = 0
-
- row = M[:3, :3].copy()
- scale[0] = vector_norm(row[0])
- row[0] /= scale[0]
- shear[0] = numpy.dot(row[0], row[1])
- row[1] -= row[0] * shear[0]
- scale[1] = vector_norm(row[1])
- row[1] /= scale[1]
- shear[0] /= scale[1]
- shear[1] = numpy.dot(row[0], row[2])
- row[2] -= row[0] * shear[1]
- shear[2] = numpy.dot(row[1], row[2])
- row[2] -= row[1] * shear[2]
- scale[2] = vector_norm(row[2])
- row[2] /= scale[2]
- shear[1:] /= scale[2]
-
- if numpy.dot(row[0], numpy.cross(row[1], row[2])) < 0:
- scale *= -1
- row *= -1
-
- angles[1] = math.asin(-row[0, 2])
- if math.cos(angles[1]):
- angles[0] = math.atan2(row[1, 2], row[2, 2])
- angles[2] = math.atan2(row[0, 1], row[0, 0])
- else:
- #angles[0] = math.atan2(row[1, 0], row[1, 1])
- angles[0] = math.atan2(-row[2, 1], row[1, 1])
- angles[2] = 0.0
-
- return scale, shear, angles, translate, perspective
-
-
-def compose_matrix(scale=None, shear=None, angles=None, translate=None,
- perspective=None):
- """Return transformation matrix from sequence of transformations.
-
- This is the inverse of the decompose_matrix function.
-
- Sequence of transformations:
- scale : vector of 3 scaling factors
- shear : list of shear factors for x-y, x-z, y-z axes
- angles : list of Euler angles about static x, y, z axes
- translate : translation vector along x, y, z axes
- perspective : perspective partition of matrix
-
- >>> scale = numpy.random.random(3) - 0.5
- >>> shear = numpy.random.random(3) - 0.5
- >>> angles = (numpy.random.random(3) - 0.5) * (2*math.pi)
- >>> trans = numpy.random.random(3) - 0.5
- >>> persp = numpy.random.random(4) - 0.5
- >>> M0 = compose_matrix(scale, shear, angles, trans, persp)
- >>> result = decompose_matrix(M0)
- >>> M1 = compose_matrix(*result)
- >>> is_same_transform(M0, M1)
- True
-
- """
- M = numpy.identity(4)
- if perspective is not None:
- P = numpy.identity(4)
- P[3, :] = perspective[:4]
- M = numpy.dot(M, P)
- if translate is not None:
- T = numpy.identity(4)
- T[:3, 3] = translate[:3]
- M = numpy.dot(M, T)
- if angles is not None:
- R = euler_matrix(angles[0], angles[1], angles[2], 'sxyz')
- M = numpy.dot(M, R)
- if shear is not None:
- Z = numpy.identity(4)
- Z[1, 2] = shear[2]
- Z[0, 2] = shear[1]
- Z[0, 1] = shear[0]
- M = numpy.dot(M, Z)
- if scale is not None:
- S = numpy.identity(4)
- S[0, 0] = scale[0]
- S[1, 1] = scale[1]
- S[2, 2] = scale[2]
- M = numpy.dot(M, S)
- M /= M[3, 3]
- return M
-
-
-def orthogonalization_matrix(lengths, angles):
- """Return orthogonalization matrix for crystallographic cell coordinates.
-
- Angles are expected in degrees.
-
- The de-orthogonalization matrix is the inverse.
-
- >>> O = orthogonalization_matrix((10., 10., 10.), (90., 90., 90.))
- >>> numpy.allclose(O[:3, :3], numpy.identity(3, float) * 10)
- True
- >>> O = orthogonalization_matrix([9.8, 12.0, 15.5], [87.2, 80.7, 69.7])
- >>> numpy.allclose(numpy.sum(O), 43.063229)
- True
-
- """
- a, b, c = lengths
- angles = numpy.radians(angles)
- sina, sinb, _ = numpy.sin(angles)
- cosa, cosb, cosg = numpy.cos(angles)
- co = (cosa * cosb - cosg) / (sina * sinb)
- return numpy.array((
- ( a*sinb*math.sqrt(1.0-co*co), 0.0, 0.0, 0.0),
- (-a*sinb*co, b*sina, 0.0, 0.0),
- ( a*cosb, b*cosa, c, 0.0),
- ( 0.0, 0.0, 0.0, 1.0)),
- dtype=numpy.float64)
-
-
-def superimposition_matrix(v0, v1, scaling=False, usesvd=True):
- """Return matrix to transform given vector set into second vector set.
-
- v0 and v1 are shape (3, \*) or (4, \*) arrays of at least 3 vectors.
-
- If usesvd is True, the weighted sum of squared deviations (RMSD) is
- minimized according to the algorithm by W. Kabsch [8]. Otherwise the
- quaternion based algorithm by B. Horn [9] is used (slower when using
- this Python implementation).
-
- The returned matrix performs rotation, translation and uniform scaling
- (if specified).
-
- >>> v0 = numpy.random.rand(3, 10)
- >>> M = superimposition_matrix(v0, v0)
- >>> numpy.allclose(M, numpy.identity(4))
- True
- >>> R = random_rotation_matrix(numpy.random.random(3))
- >>> v0 = ((1,0,0), (0,1,0), (0,0,1), (1,1,1))
- >>> v1 = numpy.dot(R, v0)
- >>> M = superimposition_matrix(v0, v1)
- >>> numpy.allclose(v1, numpy.dot(M, v0))
- True
- >>> v0 = (numpy.random.rand(4, 100) - 0.5) * 20.0
- >>> v0[3] = 1.0
- >>> v1 = numpy.dot(R, v0)
- >>> M = superimposition_matrix(v0, v1)
- >>> numpy.allclose(v1, numpy.dot(M, v0))
- True
- >>> S = scale_matrix(random.random())
- >>> T = translation_matrix(numpy.random.random(3)-0.5)
- >>> M = concatenate_matrices(T, R, S)
- >>> v1 = numpy.dot(M, v0)
- >>> v0[:3] += numpy.random.normal(0.0, 1e-9, 300).reshape(3, -1)
- >>> M = superimposition_matrix(v0, v1, scaling=True)
- >>> numpy.allclose(v1, numpy.dot(M, v0))
- True
- >>> M = superimposition_matrix(v0, v1, scaling=True, usesvd=False)
- >>> numpy.allclose(v1, numpy.dot(M, v0))
- True
- >>> v = numpy.empty((4, 100, 3), dtype=numpy.float64)
- >>> v[:, :, 0] = v0
- >>> M = superimposition_matrix(v0, v1, scaling=True, usesvd=False)
- >>> numpy.allclose(v1, numpy.dot(M, v[:, :, 0]))
- True
-
- """
- v0 = numpy.array(v0, dtype=numpy.float64, copy=False)[:3]
- v1 = numpy.array(v1, dtype=numpy.float64, copy=False)[:3]
-
- if v0.shape != v1.shape or v0.shape[1] < 3:
- raise ValueError("Vector sets are of wrong shape or type.")
-
- # move centroids to origin
- t0 = numpy.mean(v0, axis=1)
- t1 = numpy.mean(v1, axis=1)
- v0 = v0 - t0.reshape(3, 1)
- v1 = v1 - t1.reshape(3, 1)
-
- if usesvd:
- # Singular Value Decomposition of covariance matrix
- u, s, vh = numpy.linalg.svd(numpy.dot(v1, v0.T))
- # rotation matrix from SVD orthonormal bases
- R = numpy.dot(u, vh)
- if numpy.linalg.det(R) < 0.0:
- # R does not constitute right handed system
- R -= numpy.outer(u[:, 2], vh[2, :]*2.0)
- s[-1] *= -1.0
- # homogeneous transformation matrix
- M = numpy.identity(4)
- M[:3, :3] = R
- else:
- # compute symmetric matrix N
- xx, yy, zz = numpy.sum(v0 * v1, axis=1)
- xy, yz, zx = numpy.sum(v0 * numpy.roll(v1, -1, axis=0), axis=1)
- xz, yx, zy = numpy.sum(v0 * numpy.roll(v1, -2, axis=0), axis=1)
- N = ((xx+yy+zz, yz-zy, zx-xz, xy-yx),
- (yz-zy, xx-yy-zz, xy+yx, zx+xz),
- (zx-xz, xy+yx, -xx+yy-zz, yz+zy),
- (xy-yx, zx+xz, yz+zy, -xx-yy+zz))
- # quaternion: eigenvector corresponding to most positive eigenvalue
- l, V = numpy.linalg.eig(N)
- q = V[:, numpy.argmax(l)]
- q /= vector_norm(q) # unit quaternion
- q = numpy.roll(q, -1) # move w component to end
- # homogeneous transformation matrix
- M = quaternion_matrix(q)
-
- # scale: ratio of rms deviations from centroid
- if scaling:
- v0 *= v0
- v1 *= v1
- M[:3, :3] *= math.sqrt(numpy.sum(v1) / numpy.sum(v0))
-
- # translation
- M[:3, 3] = t1
- T = numpy.identity(4)
- T[:3, 3] = -t0
- M = numpy.dot(M, T)
- return M
-
-
-def euler_matrix(ai, aj, ak, axes='sxyz'):
- """Return homogeneous rotation matrix from Euler angles and axis sequence.
-
- ai, aj, ak : Euler's roll, pitch and yaw angles
- axes : One of 24 axis sequences as string or encoded tuple
-
- >>> R = euler_matrix(1, 2, 3, 'syxz')
- >>> numpy.allclose(numpy.sum(R[0]), -1.34786452)
- True
- >>> R = euler_matrix(1, 2, 3, (0, 1, 0, 1))
- >>> numpy.allclose(numpy.sum(R[0]), -0.383436184)
- True
- >>> ai, aj, ak = (4.0*math.pi) * (numpy.random.random(3) - 0.5)
- >>> for axes in _AXES2TUPLE.keys():
- ... R = euler_matrix(ai, aj, ak, axes)
- >>> for axes in _TUPLE2AXES.keys():
- ... R = euler_matrix(ai, aj, ak, axes)
-
- """
- try:
- firstaxis, parity, repetition, frame = _AXES2TUPLE[axes]
- except (AttributeError, KeyError):
- _ = _TUPLE2AXES[axes]
- firstaxis, parity, repetition, frame = axes
-
- i = firstaxis
- j = _NEXT_AXIS[i+parity]
- k = _NEXT_AXIS[i-parity+1]
-
- if frame:
- ai, ak = ak, ai
- if parity:
- ai, aj, ak = -ai, -aj, -ak
-
- si, sj, sk = math.sin(ai), math.sin(aj), math.sin(ak)
- ci, cj, ck = math.cos(ai), math.cos(aj), math.cos(ak)
- cc, cs = ci*ck, ci*sk
- sc, ss = si*ck, si*sk
-
- M = numpy.identity(4)
- if repetition:
- M[i, i] = cj
- M[i, j] = sj*si
- M[i, k] = sj*ci
- M[j, i] = sj*sk
- M[j, j] = -cj*ss+cc
- M[j, k] = -cj*cs-sc
- M[k, i] = -sj*ck
- M[k, j] = cj*sc+cs
- M[k, k] = cj*cc-ss
- else:
- M[i, i] = cj*ck
- M[i, j] = sj*sc-cs
- M[i, k] = sj*cc+ss
- M[j, i] = cj*sk
- M[j, j] = sj*ss+cc
- M[j, k] = sj*cs-sc
- M[k, i] = -sj
- M[k, j] = cj*si
- M[k, k] = cj*ci
- return M
-
-
-def euler_from_matrix(matrix, axes='sxyz'):
- """Return Euler angles from rotation matrix for specified axis sequence.
-
- axes : One of 24 axis sequences as string or encoded tuple
-
- Note that many Euler angle triplets can describe one matrix.
-
- >>> R0 = euler_matrix(1, 2, 3, 'syxz')
- >>> al, be, ga = euler_from_matrix(R0, 'syxz')
- >>> R1 = euler_matrix(al, be, ga, 'syxz')
- >>> numpy.allclose(R0, R1)
- True
- >>> angles = (4.0*math.pi) * (numpy.random.random(3) - 0.5)
- >>> for axes in _AXES2TUPLE.keys():
- ... R0 = euler_matrix(axes=axes, *angles)
- ... R1 = euler_matrix(axes=axes, *euler_from_matrix(R0, axes))
- ... if not numpy.allclose(R0, R1): print axes, "failed"
-
- """
- try:
- firstaxis, parity, repetition, frame = _AXES2TUPLE[axes.lower()]
- except (AttributeError, KeyError):
- _ = _TUPLE2AXES[axes]
- firstaxis, parity, repetition, frame = axes
-
- i = firstaxis
- j = _NEXT_AXIS[i+parity]
- k = _NEXT_AXIS[i-parity+1]
-
- M = numpy.array(matrix, dtype=numpy.float64, copy=False)[:3, :3]
- if repetition:
- sy = math.sqrt(M[i, j]*M[i, j] + M[i, k]*M[i, k])
- if sy > _EPS:
- ax = math.atan2( M[i, j], M[i, k])
- ay = math.atan2( sy, M[i, i])
- az = math.atan2( M[j, i], -M[k, i])
- else:
- ax = math.atan2(-M[j, k], M[j, j])
- ay = math.atan2( sy, M[i, i])
- az = 0.0
- else:
- cy = math.sqrt(M[i, i]*M[i, i] + M[j, i]*M[j, i])
- if cy > _EPS:
- ax = math.atan2( M[k, j], M[k, k])
- ay = math.atan2(-M[k, i], cy)
- az = math.atan2( M[j, i], M[i, i])
- else:
- ax = math.atan2(-M[j, k], M[j, j])
- ay = math.atan2(-M[k, i], cy)
- az = 0.0
-
- if parity:
- ax, ay, az = -ax, -ay, -az
- if frame:
- ax, az = az, ax
- return ax, ay, az
-
-
-def euler_from_quaternion(quaternion, axes='sxyz'):
- """Return Euler angles from quaternion for specified axis sequence.
-
- >>> angles = euler_from_quaternion([0.06146124, 0, 0, 0.99810947])
- >>> numpy.allclose(angles, [0.123, 0, 0])
- True
-
- """
- return euler_from_matrix(quaternion_matrix(quaternion), axes)
-
-
-def quaternion_from_euler(ai, aj, ak, axes='sxyz'):
- """Return quaternion from Euler angles and axis sequence.
-
- ai, aj, ak : Euler's roll, pitch and yaw angles
- axes : One of 24 axis sequences as string or encoded tuple
-
- >>> q = quaternion_from_euler(1, 2, 3, 'ryxz')
- >>> numpy.allclose(q, [0.310622, -0.718287, 0.444435, 0.435953])
- True
-
- """
- try:
- firstaxis, parity, repetition, frame = _AXES2TUPLE[axes.lower()]
- except (AttributeError, KeyError):
- _ = _TUPLE2AXES[axes]
- firstaxis, parity, repetition, frame = axes
-
- i = firstaxis
- j = _NEXT_AXIS[i+parity]
- k = _NEXT_AXIS[i-parity+1]
-
- if frame:
- ai, ak = ak, ai
- if parity:
- aj = -aj
-
- ai /= 2.0
- aj /= 2.0
- ak /= 2.0
- ci = math.cos(ai)
- si = math.sin(ai)
- cj = math.cos(aj)
- sj = math.sin(aj)
- ck = math.cos(ak)
- sk = math.sin(ak)
- cc = ci*ck
- cs = ci*sk
- sc = si*ck
- ss = si*sk
-
- quaternion = numpy.empty((4, ), dtype=numpy.float64)
- if repetition:
- quaternion[i] = cj*(cs + sc)
- quaternion[j] = sj*(cc + ss)
- quaternion[k] = sj*(cs - sc)
- quaternion[3] = cj*(cc - ss)
- else:
- quaternion[i] = cj*sc - sj*cs
- quaternion[j] = cj*ss + sj*cc
- quaternion[k] = cj*cs - sj*sc
- quaternion[3] = cj*cc + sj*ss
- if parity:
- quaternion[j] *= -1
-
- return quaternion
-
-
-def quaternion_about_axis(angle, axis):
- """Return quaternion for rotation about axis.
-
- >>> q = quaternion_about_axis(0.123, (1, 0, 0))
- >>> numpy.allclose(q, [0.06146124, 0, 0, 0.99810947])
- True
-
- """
- quaternion = numpy.zeros((4, ), dtype=numpy.float64)
- quaternion[:3] = axis[:3]
- qlen = vector_norm(quaternion)
- if qlen > _EPS:
- quaternion *= math.sin(angle/2.0) / qlen
- quaternion[3] = math.cos(angle/2.0)
- return quaternion
-
-
-def quaternion_matrix(quaternion):
- """Return homogeneous rotation matrix from quaternion.
-
- >>> R = quaternion_matrix([0.06146124, 0, 0, 0.99810947])
- >>> numpy.allclose(R, rotation_matrix(0.123, (1, 0, 0)))
- True
-
- """
- q = numpy.array(quaternion[:4], dtype=numpy.float64, copy=True)
- nq = numpy.dot(q, q)
- if nq < _EPS:
- return numpy.identity(4)
- q *= math.sqrt(2.0 / nq)
- q = numpy.outer(q, q)
- return numpy.array((
- (1.0-q[1, 1]-q[2, 2], q[0, 1]-q[2, 3], q[0, 2]+q[1, 3], 0.0),
- ( q[0, 1]+q[2, 3], 1.0-q[0, 0]-q[2, 2], q[1, 2]-q[0, 3], 0.0),
- ( q[0, 2]-q[1, 3], q[1, 2]+q[0, 3], 1.0-q[0, 0]-q[1, 1], 0.0),
- ( 0.0, 0.0, 0.0, 1.0)
- ), dtype=numpy.float64)
-
-
-def quaternion_from_matrix(matrix):
- """Return quaternion from rotation matrix.
-
- >>> R = rotation_matrix(0.123, (1, 2, 3))
- >>> q = quaternion_from_matrix(R)
- >>> numpy.allclose(q, [0.0164262, 0.0328524, 0.0492786, 0.9981095])
- True
-
- """
- q = numpy.empty((4, ), dtype=numpy.float64)
- M = numpy.array(matrix, dtype=numpy.float64, copy=False)[:4, :4]
- t = numpy.trace(M)
- if t > M[3, 3]:
- q[3] = t
- q[2] = M[1, 0] - M[0, 1]
- q[1] = M[0, 2] - M[2, 0]
- q[0] = M[2, 1] - M[1, 2]
- else:
- i, j, k = 0, 1, 2
- if M[1, 1] > M[0, 0]:
- i, j, k = 1, 2, 0
- if M[2, 2] > M[i, i]:
- i, j, k = 2, 0, 1
- t = M[i, i] - (M[j, j] + M[k, k]) + M[3, 3]
- q[i] = t
- q[j] = M[i, j] + M[j, i]
- q[k] = M[k, i] + M[i, k]
- q[3] = M[k, j] - M[j, k]
- q *= 0.5 / math.sqrt(t * M[3, 3])
- return q
-
-
-def quaternion_multiply(quaternion1, quaternion0):
- """Return multiplication of two quaternions.
-
- >>> q = quaternion_multiply([1, -2, 3, 4], [-5, 6, 7, 8])
- >>> numpy.allclose(q, [-44, -14, 48, 28])
- True
-
- """
- x0, y0, z0, w0 = quaternion0
- x1, y1, z1, w1 = quaternion1
- return numpy.array((
- x1*w0 + y1*z0 - z1*y0 + w1*x0,
- -x1*z0 + y1*w0 + z1*x0 + w1*y0,
- x1*y0 - y1*x0 + z1*w0 + w1*z0,
- -x1*x0 - y1*y0 - z1*z0 + w1*w0), dtype=numpy.float64)
-
-
-def quaternion_conjugate(quaternion):
- """Return conjugate of quaternion.
-
- >>> q0 = random_quaternion()
- >>> q1 = quaternion_conjugate(q0)
- >>> q1[3] == q0[3] and all(q1[:3] == -q0[:3])
- True
-
- """
- return numpy.array((-quaternion[0], -quaternion[1],
- -quaternion[2], quaternion[3]), dtype=numpy.float64)
-
-
-def quaternion_inverse(quaternion):
- """Return inverse of quaternion.
-
- >>> q0 = random_quaternion()
- >>> q1 = quaternion_inverse(q0)
- >>> numpy.allclose(quaternion_multiply(q0, q1), [0, 0, 0, 1])
- True
-
- """
- return quaternion_conjugate(quaternion) / numpy.dot(quaternion, quaternion)
-
-
-def quaternion_slerp(quat0, quat1, fraction, spin=0, shortestpath=True):
- """Return spherical linear interpolation between two quaternions.
-
- >>> q0 = random_quaternion()
- >>> q1 = random_quaternion()
- >>> q = quaternion_slerp(q0, q1, 0.0)
- >>> numpy.allclose(q, q0)
- True
- >>> q = quaternion_slerp(q0, q1, 1.0, 1)
- >>> numpy.allclose(q, q1)
- True
- >>> q = quaternion_slerp(q0, q1, 0.5)
- >>> angle = math.acos(numpy.dot(q0, q))
- >>> numpy.allclose(2.0, math.acos(numpy.dot(q0, q1)) / angle) or \
- numpy.allclose(2.0, math.acos(-numpy.dot(q0, q1)) / angle)
- True
-
- """
- q0 = unit_vector(quat0[:4])
- q1 = unit_vector(quat1[:4])
- if fraction == 0.0:
- return q0
- elif fraction == 1.0:
- return q1
- d = numpy.dot(q0, q1)
- if abs(abs(d) - 1.0) < _EPS:
- return q0
- if shortestpath and d < 0.0:
- # invert rotation
- d = -d
- q1 *= -1.0
- angle = math.acos(d) + spin * math.pi
- if abs(angle) < _EPS:
- return q0
- isin = 1.0 / math.sin(angle)
- q0 *= math.sin((1.0 - fraction) * angle) * isin
- q1 *= math.sin(fraction * angle) * isin
- q0 += q1
- return q0
-
-
-def random_quaternion(rand=None):
- """Return uniform random unit quaternion.
-
- rand: array like or None
- Three independent random variables that are uniformly distributed
- between 0 and 1.
-
- >>> q = random_quaternion()
- >>> numpy.allclose(1.0, vector_norm(q))
- True
- >>> q = random_quaternion(numpy.random.random(3))
- >>> q.shape
- (4,)
-
- """
- if rand is None:
- rand = numpy.random.rand(3)
- else:
- assert len(rand) == 3
- r1 = numpy.sqrt(1.0 - rand[0])
- r2 = numpy.sqrt(rand[0])
- pi2 = math.pi * 2.0
- t1 = pi2 * rand[1]
- t2 = pi2 * rand[2]
- return numpy.array((numpy.sin(t1)*r1,
- numpy.cos(t1)*r1,
- numpy.sin(t2)*r2,
- numpy.cos(t2)*r2), dtype=numpy.float64)
-
-
-def random_rotation_matrix(rand=None):
- """Return uniform random rotation matrix.
-
- rnd: array like
- Three independent random variables that are uniformly distributed
- between 0 and 1 for each returned quaternion.
-
- >>> R = random_rotation_matrix()
- >>> numpy.allclose(numpy.dot(R.T, R), numpy.identity(4))
- True
-
- """
- return quaternion_matrix(random_quaternion(rand))
-
-
-class Arcball(object):
- """Virtual Trackball Control.
-
- >>> ball = Arcball()
- >>> ball = Arcball(initial=numpy.identity(4))
- >>> ball.place([320, 320], 320)
- >>> ball.down([500, 250])
- >>> ball.drag([475, 275])
- >>> R = ball.matrix()
- >>> numpy.allclose(numpy.sum(R), 3.90583455)
- True
- >>> ball = Arcball(initial=[0, 0, 0, 1])
- >>> ball.place([320, 320], 320)
- >>> ball.setaxes([1,1,0], [-1, 1, 0])
- >>> ball.setconstrain(True)
- >>> ball.down([400, 200])
- >>> ball.drag([200, 400])
- >>> R = ball.matrix()
- >>> numpy.allclose(numpy.sum(R), 0.2055924)
- True
- >>> ball.next()
-
- """
-
- def __init__(self, initial=None):
- """Initialize virtual trackball control.
-
- initial : quaternion or rotation matrix
-
- """
- self._axis = None
- self._axes = None
- self._radius = 1.0
- self._center = [0.0, 0.0]
- self._vdown = numpy.array([0, 0, 1], dtype=numpy.float64)
- self._constrain = False
-
- if initial is None:
- self._qdown = numpy.array([0, 0, 0, 1], dtype=numpy.float64)
- else:
- initial = numpy.array(initial, dtype=numpy.float64)
- if initial.shape == (4, 4):
- self._qdown = quaternion_from_matrix(initial)
- elif initial.shape == (4, ):
- initial /= vector_norm(initial)
- self._qdown = initial
- else:
- raise ValueError("initial not a quaternion or matrix.")
-
- self._qnow = self._qpre = self._qdown
-
- def place(self, center, radius):
- """Place Arcball, e.g. when window size changes.
-
- center : sequence[2]
- Window coordinates of trackball center.
- radius : float
- Radius of trackball in window coordinates.
-
- """
- self._radius = float(radius)
- self._center[0] = center[0]
- self._center[1] = center[1]
-
- def setaxes(self, *axes):
- """Set axes to constrain rotations."""
- if axes is None:
- self._axes = None
- else:
- self._axes = [unit_vector(axis) for axis in axes]
-
- def setconstrain(self, constrain):
- """Set state of constrain to axis mode."""
- self._constrain = constrain == True
-
- def getconstrain(self):
- """Return state of constrain to axis mode."""
- return self._constrain
-
- def down(self, point):
- """Set initial cursor window coordinates and pick constrain-axis."""
- self._vdown = arcball_map_to_sphere(point, self._center, self._radius)
- self._qdown = self._qpre = self._qnow
-
- if self._constrain and self._axes is not None:
- self._axis = arcball_nearest_axis(self._vdown, self._axes)
- self._vdown = arcball_constrain_to_axis(self._vdown, self._axis)
- else:
- self._axis = None
-
- def drag(self, point):
- """Update current cursor window coordinates."""
- vnow = arcball_map_to_sphere(point, self._center, self._radius)
-
- if self._axis is not None:
- vnow = arcball_constrain_to_axis(vnow, self._axis)
-
- self._qpre = self._qnow
-
- t = numpy.cross(self._vdown, vnow)
- if numpy.dot(t, t) < _EPS:
- self._qnow = self._qdown
- else:
- q = [t[0], t[1], t[2], numpy.dot(self._vdown, vnow)]
- self._qnow = quaternion_multiply(q, self._qdown)
-
- def next(self, acceleration=0.0):
- """Continue rotation in direction of last drag."""
- q = quaternion_slerp(self._qpre, self._qnow, 2.0+acceleration, False)
- self._qpre, self._qnow = self._qnow, q
-
- def matrix(self):
- """Return homogeneous rotation matrix."""
- return quaternion_matrix(self._qnow)
-
-
-def arcball_map_to_sphere(point, center, radius):
- """Return unit sphere coordinates from window coordinates."""
- v = numpy.array(((point[0] - center[0]) / radius,
- (center[1] - point[1]) / radius,
- 0.0), dtype=numpy.float64)
- n = v[0]*v[0] + v[1]*v[1]
- if n > 1.0:
- v /= math.sqrt(n) # position outside of sphere
- else:
- v[2] = math.sqrt(1.0 - n)
- return v
-
-
-def arcball_constrain_to_axis(point, axis):
- """Return sphere point perpendicular to axis."""
- v = numpy.array(point, dtype=numpy.float64, copy=True)
- a = numpy.array(axis, dtype=numpy.float64, copy=True)
- v -= a * numpy.dot(a, v) # on plane
- n = vector_norm(v)
- if n > _EPS:
- if v[2] < 0.0:
- v *= -1.0
- v /= n
- return v
- if a[2] == 1.0:
- return numpy.array([1, 0, 0], dtype=numpy.float64)
- return unit_vector([-a[1], a[0], 0])
-
-
-def arcball_nearest_axis(point, axes):
- """Return axis, which arc is nearest to point."""
- point = numpy.array(point, dtype=numpy.float64, copy=False)
- nearest = None
- mx = -1.0
- for axis in axes:
- t = numpy.dot(arcball_constrain_to_axis(point, axis), point)
- if t > mx:
- nearest = axis
- mx = t
- return nearest
-
-
-# epsilon for testing whether a number is close to zero
-_EPS = numpy.finfo(float).eps * 4.0
-
-# axis sequences for Euler angles
-_NEXT_AXIS = [1, 2, 0, 1]
-
-# map axes strings to/from tuples of inner axis, parity, repetition, frame
-_AXES2TUPLE = {
- 'sxyz': (0, 0, 0, 0), 'sxyx': (0, 0, 1, 0), 'sxzy': (0, 1, 0, 0),
- 'sxzx': (0, 1, 1, 0), 'syzx': (1, 0, 0, 0), 'syzy': (1, 0, 1, 0),
- 'syxz': (1, 1, 0, 0), 'syxy': (1, 1, 1, 0), 'szxy': (2, 0, 0, 0),
- 'szxz': (2, 0, 1, 0), 'szyx': (2, 1, 0, 0), 'szyz': (2, 1, 1, 0),
- 'rzyx': (0, 0, 0, 1), 'rxyx': (0, 0, 1, 1), 'ryzx': (0, 1, 0, 1),
- 'rxzx': (0, 1, 1, 1), 'rxzy': (1, 0, 0, 1), 'ryzy': (1, 0, 1, 1),
- 'rzxy': (1, 1, 0, 1), 'ryxy': (1, 1, 1, 1), 'ryxz': (2, 0, 0, 1),
- 'rzxz': (2, 0, 1, 1), 'rxyz': (2, 1, 0, 1), 'rzyz': (2, 1, 1, 1)}
-
-_TUPLE2AXES = dict((v, k) for k, v in _AXES2TUPLE.items())
-
-# helper functions
-
-def vector_norm(data, axis=None, out=None):
- """Return length, i.e. eucledian norm, of ndarray along axis.
-
- >>> v = numpy.random.random(3)
- >>> n = vector_norm(v)
- >>> numpy.allclose(n, numpy.linalg.norm(v))
- True
- >>> v = numpy.random.rand(6, 5, 3)
- >>> n = vector_norm(v, axis=-1)
- >>> numpy.allclose(n, numpy.sqrt(numpy.sum(v*v, axis=2)))
- True
- >>> n = vector_norm(v, axis=1)
- >>> numpy.allclose(n, numpy.sqrt(numpy.sum(v*v, axis=1)))
- True
- >>> v = numpy.random.rand(5, 4, 3)
- >>> n = numpy.empty((5, 3), dtype=numpy.float64)
- >>> vector_norm(v, axis=1, out=n)
- >>> numpy.allclose(n, numpy.sqrt(numpy.sum(v*v, axis=1)))
- True
- >>> vector_norm([])
- 0.0
- >>> vector_norm([1.0])
- 1.0
-
- """
- data = numpy.array(data, dtype=numpy.float64, copy=True)
- if out is None:
- if data.ndim == 1:
- return math.sqrt(numpy.dot(data, data))
- data *= data
- out = numpy.atleast_1d(numpy.sum(data, axis=axis))
- numpy.sqrt(out, out)
- return out
- else:
- data *= data
- numpy.sum(data, axis=axis, out=out)
- numpy.sqrt(out, out)
-
-
-def unit_vector(data, axis=None, out=None):
- """Return ndarray normalized by length, i.e. eucledian norm, along axis.
-
- >>> v0 = numpy.random.random(3)
- >>> v1 = unit_vector(v0)
- >>> numpy.allclose(v1, v0 / numpy.linalg.norm(v0))
- True
- >>> v0 = numpy.random.rand(5, 4, 3)
- >>> v1 = unit_vector(v0, axis=-1)
- >>> v2 = v0 / numpy.expand_dims(numpy.sqrt(numpy.sum(v0*v0, axis=2)), 2)
- >>> numpy.allclose(v1, v2)
- True
- >>> v1 = unit_vector(v0, axis=1)
- >>> v2 = v0 / numpy.expand_dims(numpy.sqrt(numpy.sum(v0*v0, axis=1)), 1)
- >>> numpy.allclose(v1, v2)
- True
- >>> v1 = numpy.empty((5, 4, 3), dtype=numpy.float64)
- >>> unit_vector(v0, axis=1, out=v1)
- >>> numpy.allclose(v1, v2)
- True
- >>> list(unit_vector([]))
- []
- >>> list(unit_vector([1.0]))
- [1.0]
-
- """
- if out is None:
- data = numpy.array(data, dtype=numpy.float64, copy=True)
- if data.ndim == 1:
- data /= math.sqrt(numpy.dot(data, data))
- return data
- else:
- if out is not data:
- out[:] = numpy.array(data, copy=False)
- data = out
- length = numpy.atleast_1d(numpy.sum(data*data, axis))
- numpy.sqrt(length, length)
- if axis is not None:
- length = numpy.expand_dims(length, axis)
- data /= length
- if out is None:
- return data
-
-
-def random_vector(size):
- """Return array of random doubles in the half-open interval [0.0, 1.0).
-
- >>> v = random_vector(10000)
- >>> numpy.all(v >= 0.0) and numpy.all(v < 1.0)
- True
- >>> v0 = random_vector(10)
- >>> v1 = random_vector(10)
- >>> numpy.any(v0 == v1)
- False
-
- """
- return numpy.random.random(size)
-
-
-def inverse_matrix(matrix):
- """Return inverse of square transformation matrix.
-
- >>> M0 = random_rotation_matrix()
- >>> M1 = inverse_matrix(M0.T)
- >>> numpy.allclose(M1, numpy.linalg.inv(M0.T))
- True
- >>> for size in range(1, 7):
- ... M0 = numpy.random.rand(size, size)
- ... M1 = inverse_matrix(M0)
- ... if not numpy.allclose(M1, numpy.linalg.inv(M0)): print size
-
- """
- return numpy.linalg.inv(matrix)
-
-
-def concatenate_matrices(*matrices):
- """Return concatenation of series of transformation matrices.
-
- >>> M = numpy.random.rand(16).reshape((4, 4)) - 0.5
- >>> numpy.allclose(M, concatenate_matrices(M))
- True
- >>> numpy.allclose(numpy.dot(M, M.T), concatenate_matrices(M, M.T))
- True
-
- """
- M = numpy.identity(4)
- for i in matrices:
- M = numpy.dot(M, i)
- return M
-
-
-def is_same_transform(matrix0, matrix1):
- """Return True if two matrices perform same transformation.
-
- >>> is_same_transform(numpy.identity(4), numpy.identity(4))
- True
- >>> is_same_transform(numpy.identity(4), random_rotation_matrix())
- False
-
- """
- matrix0 = numpy.array(matrix0, dtype=numpy.float64, copy=True)
- matrix0 /= matrix0[3, 3]
- matrix1 = numpy.array(matrix1, dtype=numpy.float64, copy=True)
- matrix1 /= matrix1[3, 3]
- return numpy.allclose(matrix0, matrix1)
-
-
-def _import_module(module_name, warn=True, prefix='_py_', ignore='_'):
- """Try import all public attributes from module into global namespace.
-
- Existing attributes with name clashes are renamed with prefix.
- Attributes starting with underscore are ignored by default.
-
- Return True on successful import.
-
- """
- try:
- module = __import__(module_name)
- except ImportError:
- if warn:
- warnings.warn("Failed to import module " + module_name)
- else:
- for attr in dir(module):
- if ignore and attr.startswith(ignore):
- continue
- if prefix:
- if attr in globals():
- globals()[prefix + attr] = globals()[attr]
- elif warn:
- warnings.warn("No Python implementation of " + attr)
- globals()[attr] = getattr(module, attr)
- return True