Merge git://github.com/bjowi/assimp
commit
29a7126589
|
@ -1,4 +1,4 @@
|
||||||
Open Asset Import Library (_assimp_)
|
Open Asset Import Library (assimp)
|
||||||
========
|
========
|
||||||
|
|
||||||
|
|
||||||
|
@ -29,8 +29,9 @@ __Note__: this `README` refers to the file structure used by release packages, w
|
||||||
The library provides importers for a lot of file formats, including:
|
The library provides importers for a lot of file formats, including:
|
||||||
|
|
||||||
- 3DS
|
- 3DS
|
||||||
- BLEND
|
- BLEND (Blender 3D)
|
||||||
- DAE (Collada)
|
- DAE (Collada)
|
||||||
|
- FBX
|
||||||
- IFC-STEP
|
- IFC-STEP
|
||||||
- ASE
|
- ASE
|
||||||
- DXF
|
- DXF
|
||||||
|
@ -121,5 +122,3 @@ For the formal details, see the `LICENSE` file.
|
||||||
|
|
||||||
|
|
||||||
------------------------------
|
------------------------------
|
||||||
|
|
||||||
(This repository is a mirror of the SVN repository located [here](https://assimp.svn.sourceforge.net/svnroot/assimp). Thanks to [klickverbot](https://github.com/klickverbot) for setting this up!)
|
|
|
@ -1,10 +1,10 @@
|
||||||
prefix=@CMAKE_INSTALL_PREFIX@
|
prefix=@CMAKE_INSTALL_PREFIX@
|
||||||
exec_prefix=@CMAKE_INSTALL_PREFIX@/@BIN_INSTALL_DIR@
|
exec_prefix=@CMAKE_INSTALL_PREFIX@/@ASSIMP_BIN_INSTALL_DIR@
|
||||||
libdir=@CMAKE_INSTALL_PREFIX@/@LIB_INSTALL_DIR@
|
libdir=@CMAKE_INSTALL_PREFIX@/@ASSIMP_LIB_INSTALL_DIR@
|
||||||
includedir=@CMAKE_INSTALL_PREFIX@/@INCLUDE_INSTALL_DIR@/assimp
|
includedir=@CMAKE_INSTALL_PREFIX@/@ASSIMP_INCLUDE_INSTALL_DIR@/assimp
|
||||||
|
|
||||||
Name: @CMAKE_PROJECT_NAME@
|
Name: @CMAKE_PROJECT_NAME@
|
||||||
Description: Import various well-known 3D model formats in an uniform manner.
|
Description: Import various well-known 3D model formats in an uniform manner.
|
||||||
Version: @PROJECT_VERSION@
|
Version: @PROJECT_VERSION@
|
||||||
Libs: -L${libdir} -lassimp@ASSIMP_LIBRARY_SUFFIX@
|
Libs: -L${libdir} -lassimp@ASSIMP_LIBRARY_SUFFIX@
|
||||||
Cflags: -I${includedir}
|
Cflags: -I${includedir}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
PyAssimp Readme
|
PyAssimp Readme
|
||||||
===============
|
===============
|
||||||
|
|
||||||
-- a simple Python wrapper for Assimp using ctypes to access
|
A simple Python wrapper for Assimp using `ctypes` to access the library.
|
||||||
the library. Requires Python >= 2.6.
|
Requires Python >= 2.6.
|
||||||
|
|
||||||
Python 3 support is mostly here, but not well tested.
|
Python 3 support is mostly here, but not well tested.
|
||||||
|
|
||||||
|
@ -12,10 +12,10 @@ particular, only loading of models is currently supported (no export).
|
||||||
USAGE
|
USAGE
|
||||||
-----
|
-----
|
||||||
|
|
||||||
To get started with pyAssimp, examine the sample.py script in scripts/, which
|
To get started with pyAssimp, examine the `sample.py` script in `scripts/`,
|
||||||
illustrates the basic usage. All Assimp data structures are wrapped using
|
which illustrates the basic usage. All Assimp data structures are wrapped using
|
||||||
ctypes. All the data+length fields in Assimp's data structures (such as
|
ctypes. All the data+length fields in Assimp's data structures (such as
|
||||||
'aiMesh::mNumVertices','aiMesh::mVertices') are replaced by simple python
|
`aiMesh::mNumVertices`, `aiMesh::mVertices`) are replaced by simple python
|
||||||
lists, so you can call len() on them to get their respective size and access
|
lists, so you can call len() on them to get their respective size and access
|
||||||
members using [].
|
members using [].
|
||||||
|
|
||||||
|
@ -57,18 +57,18 @@ release(scene)
|
||||||
INSTALL
|
INSTALL
|
||||||
-------
|
-------
|
||||||
|
|
||||||
Install pyassimp by running:
|
Install `pyassimp` by running:
|
||||||
|
|
||||||
> python setup.py install
|
> python setup.py install
|
||||||
|
|
||||||
PyAssimp requires a assimp dynamic library (DLL on windows,
|
PyAssimp requires a assimp dynamic library (`DLL` on windows,
|
||||||
so on linux :-) in order to work. The default search directories
|
`.so` on linux :-) in order to work. The default search directories
|
||||||
are:
|
are:
|
||||||
|
|
||||||
- the current directory
|
- the current directory
|
||||||
- on linux additionally: /usr/lib and /usr/local/lib
|
- on linux additionally: `/usr/lib` and `/usr/local/lib`
|
||||||
|
|
||||||
To build that library, refer to the Assimp master INSTALL
|
To build that library, refer to the Assimp master INSTALL
|
||||||
instructions. To look in more places, edit ./pyassimp/helper.py.
|
instructions. To look in more places, edit `./pyassimp/helper.py`.
|
||||||
There's an 'additional_dirs' list waiting for your entries.
|
There's an `additional_dirs` list waiting for your entries.
|
||||||
|
|
||||||
|
|
|
@ -346,60 +346,28 @@ def _get_properties(properties, length):
|
||||||
for p in [properties[i] for i in range(length)]:
|
for p in [properties[i] for i in range(length)]:
|
||||||
#the name
|
#the name
|
||||||
p = p.contents
|
p = p.contents
|
||||||
key = str(p.mKey.data)
|
key = str(p.mKey.data).split('.')[1]
|
||||||
|
|
||||||
#the data
|
#the data
|
||||||
from ctypes import POINTER, cast, c_int, c_float, sizeof
|
from ctypes import POINTER, cast, c_int, c_float, sizeof
|
||||||
if p.mType == 1:
|
if p.mType == 1:
|
||||||
arr = cast(p.mData, POINTER(c_float * int(p.mDataLength/sizeof(c_float)) )).contents
|
arr = cast(p.mData, POINTER(c_float * int(p.mDataLength/sizeof(c_float)) )).contents
|
||||||
value = numpy.array([x for x in arr])
|
value = [x for x in arr]
|
||||||
elif p.mType == 3: #string can't be an array
|
elif p.mType == 3: #string can't be an array
|
||||||
value = cast(p.mData, POINTER(structs.String)).contents.data
|
value = cast(p.mData, POINTER(structs.String)).contents.data
|
||||||
elif p.mType == 4:
|
elif p.mType == 4:
|
||||||
arr = cast(p.mData, POINTER(c_int * int(p.mDataLength/sizeof(c_int)) )).contents
|
arr = cast(p.mData, POINTER(c_int * int(p.mDataLength/sizeof(c_int)) )).contents
|
||||||
value = numpy.array([x for x in arr])
|
value = [x for x in arr]
|
||||||
else:
|
else:
|
||||||
value = p.mData[:p.mDataLength]
|
value = p.mData[:p.mDataLength]
|
||||||
|
|
||||||
|
if len(value) == 1:
|
||||||
|
[value] = value
|
||||||
|
|
||||||
result[key] = value
|
result[key] = value
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def aiGetMaterialFloatArray(material, key):
|
|
||||||
AI_SUCCESS = 0
|
|
||||||
from ctypes import byref, pointer, cast, c_float, POINTER, sizeof, c_uint
|
|
||||||
out = structs.Color4D()
|
|
||||||
max = c_uint(sizeof(structs.Color4D))
|
|
||||||
r=_assimp_lib.dll.aiGetMaterialFloatArray(pointer(material),
|
|
||||||
key[0],
|
|
||||||
key[1],
|
|
||||||
key[2],
|
|
||||||
byref(out),
|
|
||||||
byref(max))
|
|
||||||
|
|
||||||
if (r != AI_SUCCESS):
|
|
||||||
raise AssimpError("aiGetMaterialFloatArray failed!")
|
|
||||||
|
|
||||||
out._init()
|
|
||||||
return [out[i] for i in range(max.value)]
|
|
||||||
|
|
||||||
def aiGetMaterialString(material, key):
|
|
||||||
AI_SUCCESS = 0
|
|
||||||
from ctypes import byref, pointer, cast, c_float, POINTER, sizeof, c_uint
|
|
||||||
out = structs.String()
|
|
||||||
r=_assimp_lib.dll.aiGetMaterialString(pointer(material),
|
|
||||||
key[0],
|
|
||||||
key[1],
|
|
||||||
key[2],
|
|
||||||
byref(out))
|
|
||||||
|
|
||||||
if (r != AI_SUCCESS):
|
|
||||||
raise AssimpError("aiGetMaterialString failed!")
|
|
||||||
|
|
||||||
return str(out.data)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def decompose_matrix(matrix):
|
def decompose_matrix(matrix):
|
||||||
if not isinstance(matrix, structs.Matrix4x4):
|
if not isinstance(matrix, structs.Matrix4x4):
|
||||||
raise AssimpError("pyassimp.decompose_matrix failed: Not a Matrix4x4!")
|
raise AssimpError("pyassimp.decompose_matrix failed: Not a Matrix4x4!")
|
||||||
|
|
|
@ -9,6 +9,7 @@ import ctypes
|
||||||
from ctypes import POINTER
|
from ctypes import POINTER
|
||||||
import operator
|
import operator
|
||||||
import numpy
|
import numpy
|
||||||
|
from numpy import linalg
|
||||||
|
|
||||||
import logging;logger = logging.getLogger("pyassimp")
|
import logging;logger = logging.getLogger("pyassimp")
|
||||||
|
|
||||||
|
@ -47,12 +48,14 @@ def transform(vector3, matrix4x4):
|
||||||
def get_bounding_box(scene):
|
def get_bounding_box(scene):
|
||||||
bb_min = [1e10, 1e10, 1e10] # x,y,z
|
bb_min = [1e10, 1e10, 1e10] # x,y,z
|
||||||
bb_max = [-1e10, -1e10, -1e10] # x,y,z
|
bb_max = [-1e10, -1e10, -1e10] # x,y,z
|
||||||
return get_bounding_box_for_node(scene.rootnode, bb_min, bb_max)
|
return get_bounding_box_for_node(scene.rootnode, bb_min, bb_max, linalg.inv(scene.rootnode.transformation))
|
||||||
|
|
||||||
def get_bounding_box_for_node(node, bb_min, bb_max):
|
def get_bounding_box_for_node(node, bb_min, bb_max, transformation):
|
||||||
|
|
||||||
|
transformation = numpy.dot(transformation, node.transformation)
|
||||||
for mesh in node.meshes:
|
for mesh in node.meshes:
|
||||||
for v in mesh.vertices:
|
for v in mesh.vertices:
|
||||||
v = transform(v, node.transformation)
|
v = transform(v, transformation)
|
||||||
bb_min[0] = min(bb_min[0], v[0])
|
bb_min[0] = min(bb_min[0], v[0])
|
||||||
bb_min[1] = min(bb_min[1], v[1])
|
bb_min[1] = min(bb_min[1], v[1])
|
||||||
bb_min[2] = min(bb_min[2], v[2])
|
bb_min[2] = min(bb_min[2], v[2])
|
||||||
|
@ -62,7 +65,7 @@ def get_bounding_box_for_node(node, bb_min, bb_max):
|
||||||
|
|
||||||
|
|
||||||
for child in node.children:
|
for child in node.children:
|
||||||
bb_min, bb_max = get_bounding_box_for_node(child, bb_min, bb_max)
|
bb_min, bb_max = get_bounding_box_for_node(child, bb_min, bb_max, transformation)
|
||||||
|
|
||||||
return bb_min, bb_max
|
return bb_min, bb_max
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,410 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
#-*- coding: UTF-8 -*-
|
||||||
|
|
||||||
|
""" This program loads a model with PyASSIMP, and display it.
|
||||||
|
|
||||||
|
It make a large use of shaders to illustrate a 'modern' OpenGL pipeline.
|
||||||
|
|
||||||
|
Based on:
|
||||||
|
- pygame + mouselook 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
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import logging
|
||||||
|
logger = logging.getLogger("underworlds.3d_viewer")
|
||||||
|
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.error import GLError
|
||||||
|
from OpenGL.GLU import *
|
||||||
|
from OpenGL.GLUT import *
|
||||||
|
from OpenGL.arrays import vbo
|
||||||
|
from OpenGL.GL import shaders
|
||||||
|
|
||||||
|
import pygame
|
||||||
|
|
||||||
|
import math, random
|
||||||
|
import numpy
|
||||||
|
from numpy import linalg
|
||||||
|
|
||||||
|
from pyassimp import core as pyassimp
|
||||||
|
from pyassimp.postprocess import *
|
||||||
|
from pyassimp.helper import *
|
||||||
|
|
||||||
|
class DefaultCamera:
|
||||||
|
def __init__(self, w, h, fov):
|
||||||
|
self.clipplanenear = 0.001
|
||||||
|
self.clipplanefar = 100000.0
|
||||||
|
self.aspect = w/h
|
||||||
|
self.horizontalfov = fov * math.pi/180
|
||||||
|
self.transformation = [[ 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. ]]
|
||||||
|
self.lookat = [0.0,0.0,-1.0]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "Default camera"
|
||||||
|
|
||||||
|
class PyAssimp3DViewer:
|
||||||
|
|
||||||
|
base_name = "PyASSIMP 3D viewer"
|
||||||
|
|
||||||
|
def __init__(self, model, w=1024, h=768, fov=75):
|
||||||
|
|
||||||
|
pygame.init()
|
||||||
|
pygame.display.set_caption(self.base_name)
|
||||||
|
pygame.display.set_mode((w,h), pygame.OPENGL | pygame.DOUBLEBUF)
|
||||||
|
|
||||||
|
self.prepare_shaders()
|
||||||
|
|
||||||
|
self.cameras = [DefaultCamera(w,h,fov)]
|
||||||
|
self.current_cam_index = 0
|
||||||
|
|
||||||
|
self.load_model(model)
|
||||||
|
|
||||||
|
# for FPS computation
|
||||||
|
self.frames = 0
|
||||||
|
self.last_fps_time = glutGet(GLUT_ELAPSED_TIME)
|
||||||
|
|
||||||
|
|
||||||
|
self.cycle_cameras()
|
||||||
|
|
||||||
|
def prepare_shaders(self):
|
||||||
|
|
||||||
|
phong_weightCalc = """
|
||||||
|
float phong_weightCalc(
|
||||||
|
in vec3 light_pos, // light position
|
||||||
|
in vec3 frag_normal // geometry normal
|
||||||
|
) {
|
||||||
|
// returns vec2( ambientMult, diffuseMult )
|
||||||
|
float n_dot_pos = max( 0.0, dot(
|
||||||
|
frag_normal, light_pos
|
||||||
|
));
|
||||||
|
return n_dot_pos;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
vertex = shaders.compileShader( phong_weightCalc +
|
||||||
|
"""
|
||||||
|
uniform vec4 Global_ambient;
|
||||||
|
uniform vec4 Light_ambient;
|
||||||
|
uniform vec4 Light_diffuse;
|
||||||
|
uniform vec3 Light_location;
|
||||||
|
uniform vec4 Material_ambient;
|
||||||
|
uniform vec4 Material_diffuse;
|
||||||
|
attribute vec3 Vertex_position;
|
||||||
|
attribute vec3 Vertex_normal;
|
||||||
|
varying vec4 baseColor;
|
||||||
|
void main() {
|
||||||
|
gl_Position = gl_ModelViewProjectionMatrix * vec4(
|
||||||
|
Vertex_position, 1.0
|
||||||
|
);
|
||||||
|
vec3 EC_Light_location = gl_NormalMatrix * Light_location;
|
||||||
|
float diffuse_weight = phong_weightCalc(
|
||||||
|
normalize(EC_Light_location),
|
||||||
|
normalize(gl_NormalMatrix * Vertex_normal)
|
||||||
|
);
|
||||||
|
baseColor = clamp(
|
||||||
|
(
|
||||||
|
// global component
|
||||||
|
(Global_ambient * Material_ambient)
|
||||||
|
// material's interaction with light's contribution
|
||||||
|
// to the ambient lighting...
|
||||||
|
+ (Light_ambient * Material_ambient)
|
||||||
|
// material's interaction with the direct light from
|
||||||
|
// the light.
|
||||||
|
+ (Light_diffuse * Material_diffuse * diffuse_weight)
|
||||||
|
), 0.0, 1.0);
|
||||||
|
}""", GL_VERTEX_SHADER)
|
||||||
|
|
||||||
|
fragment = shaders.compileShader("""
|
||||||
|
varying vec4 baseColor;
|
||||||
|
void main() {
|
||||||
|
gl_FragColor = baseColor;
|
||||||
|
}
|
||||||
|
""", GL_FRAGMENT_SHADER)
|
||||||
|
|
||||||
|
self.shader = shaders.compileProgram(vertex,fragment)
|
||||||
|
self.set_shader_accessors( (
|
||||||
|
'Global_ambient',
|
||||||
|
'Light_ambient','Light_diffuse','Light_location',
|
||||||
|
'Material_ambient','Material_diffuse',
|
||||||
|
), (
|
||||||
|
'Vertex_position','Vertex_normal',
|
||||||
|
), self.shader)
|
||||||
|
|
||||||
|
def set_shader_accessors(self, uniforms, attributes, shader):
|
||||||
|
# add accessors to the shaders uniforms and attributes
|
||||||
|
for uniform in uniforms:
|
||||||
|
location = glGetUniformLocation( shader, uniform )
|
||||||
|
if location in (None,-1):
|
||||||
|
logger.warning('No uniform: %s'%( uniform ))
|
||||||
|
setattr( shader, uniform, location )
|
||||||
|
|
||||||
|
for attribute in attributes:
|
||||||
|
location = glGetAttribLocation( shader, attribute )
|
||||||
|
if location in (None,-1):
|
||||||
|
logger.warning('No attribute: %s'%( attribute ))
|
||||||
|
setattr( shader, attribute, location )
|
||||||
|
|
||||||
|
|
||||||
|
def prepare_gl_buffers(self, 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,
|
||||||
|
mesh.faces,
|
||||||
|
GL_STATIC_DRAW)
|
||||||
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,0)
|
||||||
|
|
||||||
|
|
||||||
|
def load_model(self, path, postprocess = aiProcessPreset_TargetRealtime_MaxQuality):
|
||||||
|
logger.info("Loading model:" + path + "...")
|
||||||
|
|
||||||
|
if postprocess:
|
||||||
|
self.scene = pyassimp.load(path, 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)
|
||||||
|
|
||||||
|
logger.info("Ready for 3D rendering!")
|
||||||
|
|
||||||
|
def cycle_cameras(self):
|
||||||
|
if not self.cameras:
|
||||||
|
logger.info("No camera in the scene")
|
||||||
|
return None
|
||||||
|
self.current_cam_index = (self.current_cam_index + 1) % len(self.cameras)
|
||||||
|
self.current_cam = self.cameras[self.current_cam_index]
|
||||||
|
self.set_camera(self.current_cam)
|
||||||
|
logger.info("Switched to camera <%s>" % self.current_cam)
|
||||||
|
|
||||||
|
def set_camera_projection(self, camera = None):
|
||||||
|
|
||||||
|
if not camera:
|
||||||
|
camera = self.cameras[self.current_cam_index]
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
|
||||||
|
def set_camera(self, camera):
|
||||||
|
|
||||||
|
self.set_camera_projection(camera)
|
||||||
|
|
||||||
|
glMatrixMode(GL_MODELVIEW)
|
||||||
|
glLoadIdentity()
|
||||||
|
|
||||||
|
cam = transform([0.0, 0.0, 0.0], 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 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)
|
||||||
|
|
||||||
|
shader = self.shader
|
||||||
|
|
||||||
|
glUseProgram(shader)
|
||||||
|
glUniform4f( shader.Global_ambient, .4,.2,.2,.1 )
|
||||||
|
glUniform4f( shader.Light_ambient, .4,.4,.4, 1.0 )
|
||||||
|
glUniform4f( shader.Light_diffuse, 1,1,1,1 )
|
||||||
|
glUniform3f( shader.Light_location, 2,2,10 )
|
||||||
|
|
||||||
|
self.recursive_render(self.scene.rootnode, shader)
|
||||||
|
|
||||||
|
|
||||||
|
glUseProgram( 0 )
|
||||||
|
|
||||||
|
def recursive_render(self, node, shader):
|
||||||
|
""" 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:
|
||||||
|
|
||||||
|
stride = 24 # 6 * 4 bytes
|
||||||
|
|
||||||
|
glUniform4f( shader.Material_diffuse, *mesh.material.properties["diffuse"] )
|
||||||
|
glUniform4f( shader.Material_ambient, *mesh.material.properties["ambient"] )
|
||||||
|
|
||||||
|
vbo = mesh.gl["vbo"]
|
||||||
|
vbo.bind()
|
||||||
|
|
||||||
|
glEnableVertexAttribArray( shader.Vertex_position )
|
||||||
|
glEnableVertexAttribArray( shader.Vertex_normal )
|
||||||
|
|
||||||
|
glVertexAttribPointer(
|
||||||
|
shader.Vertex_position,
|
||||||
|
3, GL_FLOAT,False, stride, vbo
|
||||||
|
)
|
||||||
|
|
||||||
|
glVertexAttribPointer(
|
||||||
|
shader.Vertex_normal,
|
||||||
|
3, GL_FLOAT,False, stride, vbo+12
|
||||||
|
)
|
||||||
|
|
||||||
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh.gl["faces"])
|
||||||
|
glDrawElements(GL_TRIANGLES, len(mesh.faces) * 3, GL_UNSIGNED_INT, None)
|
||||||
|
|
||||||
|
|
||||||
|
vbo.unbind()
|
||||||
|
glDisableVertexAttribArray( shader.Vertex_position )
|
||||||
|
|
||||||
|
glDisableVertexAttribArray( shader.Vertex_normal )
|
||||||
|
|
||||||
|
|
||||||
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)
|
||||||
|
|
||||||
|
for child in node.children:
|
||||||
|
self.recursive_render(child, shader)
|
||||||
|
|
||||||
|
glPopMatrix()
|
||||||
|
|
||||||
|
|
||||||
|
def loop(self):
|
||||||
|
|
||||||
|
pygame.display.flip()
|
||||||
|
pygame.event.pump()
|
||||||
|
self.keys = [k for k, pressed in enumerate(pygame.key.get_pressed()) if pressed]
|
||||||
|
|
||||||
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
|
||||||
|
|
||||||
|
# Compute FPS
|
||||||
|
gl_time = glutGet(GLUT_ELAPSED_TIME)
|
||||||
|
self.frames += 1
|
||||||
|
if gl_time - self.last_fps_time >= 1000:
|
||||||
|
current_fps = self.frames * 1000 / (gl_time - self.last_fps_time)
|
||||||
|
pygame.display.set_caption(self.base_name + " - %.0f fps" % current_fps)
|
||||||
|
self.frames = 0
|
||||||
|
self.last_fps_time = gl_time
|
||||||
|
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def controls_3d(self,
|
||||||
|
mouse_button=1, \
|
||||||
|
up_key=pygame.K_UP, \
|
||||||
|
down_key=pygame.K_DOWN, \
|
||||||
|
left_key=pygame.K_LEFT, \
|
||||||
|
right_key=pygame.K_RIGHT):
|
||||||
|
""" The actual camera setting cycle """
|
||||||
|
mouse_dx,mouse_dy = pygame.mouse.get_rel()
|
||||||
|
if pygame.mouse.get_pressed()[mouse_button]:
|
||||||
|
look_speed = .2
|
||||||
|
buffer = glGetDoublev(GL_MODELVIEW_MATRIX)
|
||||||
|
c = (-1 * numpy.mat(buffer[:3,:3]) * \
|
||||||
|
numpy.mat(buffer[3,:3]).T).reshape(3,1)
|
||||||
|
# c is camera center in absolute coordinates,
|
||||||
|
# we need to move it back to (0,0,0)
|
||||||
|
# before rotating the camera
|
||||||
|
glTranslate(c[0],c[1],c[2])
|
||||||
|
m = buffer.flatten()
|
||||||
|
glRotate(mouse_dx * look_speed, m[1],m[5],m[9])
|
||||||
|
glRotate(mouse_dy * look_speed, m[0],m[4],m[8])
|
||||||
|
|
||||||
|
# compensate roll
|
||||||
|
glRotated(-math.atan2(-m[4],m[5]) * \
|
||||||
|
57.295779513082320876798154814105 ,m[2],m[6],m[10])
|
||||||
|
glTranslate(-c[0],-c[1],-c[2])
|
||||||
|
|
||||||
|
# move forward-back or right-left
|
||||||
|
if up_key in self.keys:
|
||||||
|
fwd = .1
|
||||||
|
elif down_key in self.keys:
|
||||||
|
fwd = -.1
|
||||||
|
else:
|
||||||
|
fwd = 0
|
||||||
|
|
||||||
|
if left_key in self.keys:
|
||||||
|
strafe = .1
|
||||||
|
elif right_key in self.keys:
|
||||||
|
strafe = -.1
|
||||||
|
else:
|
||||||
|
strafe = 0
|
||||||
|
|
||||||
|
if abs(fwd) or abs(strafe):
|
||||||
|
m = glGetDoublev(GL_MODELVIEW_MATRIX).flatten()
|
||||||
|
glTranslate(fwd*m[2],fwd*m[6],fwd*m[10])
|
||||||
|
glTranslate(strafe*m[0],strafe*m[4],strafe*m[8])
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
if not len(sys.argv) > 1:
|
||||||
|
print("Usage: " + __file__ + " <model>")
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
|
app = PyAssimp3DViewer(model = sys.argv[1], w = 1024, h = 768, fov = 75)
|
||||||
|
|
||||||
|
while app.loop():
|
||||||
|
app.render()
|
||||||
|
app.controls_3d(0)
|
||||||
|
if pygame.K_f in app.keys: pygame.display.toggle_fullscreen()
|
||||||
|
if pygame.K_s in app.keys: app.screenshot()
|
||||||
|
if pygame.K_v in app.keys: app.check_visibility()
|
||||||
|
if pygame.K_TAB in app.keys: app.cycle_cameras()
|
||||||
|
if pygame.K_ESCAPE in app.keys:
|
||||||
|
break
|
|
@ -1,14 +1,21 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
#-*- coding: UTF-8 -*-
|
#-*- coding: UTF-8 -*-
|
||||||
|
|
||||||
""" This program demonstrate the use of pyassimp to render
|
""" This program demonstrates the use of pyassimp to load and
|
||||||
objects in OpenGL.
|
render objects with OpenGL.
|
||||||
|
|
||||||
It loads a 3D model with ASSIMP and display it.
|
'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.
|
Materials are supported but textures are currently ignored.
|
||||||
|
|
||||||
Half-working keyboard + mouse navigation is supported.
|
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:
|
This sample is based on several sources, including:
|
||||||
- http://www.lighthouse3d.com/tutorials
|
- http://www.lighthouse3d.com/tutorials
|
||||||
|
@ -21,9 +28,8 @@ import os, sys
|
||||||
from OpenGL.GLUT import *
|
from OpenGL.GLUT import *
|
||||||
from OpenGL.GLU import *
|
from OpenGL.GLU import *
|
||||||
from OpenGL.GL import *
|
from OpenGL.GL import *
|
||||||
from OpenGL.arrays import ArrayDatatype
|
|
||||||
|
|
||||||
import logging;logger = logging.getLogger("assimp_opengl")
|
import logging;logger = logging.getLogger("pyassimp_opengl")
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
import math
|
import math
|
||||||
|
@ -40,20 +46,14 @@ width = 900
|
||||||
|
|
||||||
class GLRenderer():
|
class GLRenderer():
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
||||||
self.scene = None
|
self.scene = None
|
||||||
|
|
||||||
self.drot = 0.0
|
|
||||||
self.dp = 0.0
|
|
||||||
|
|
||||||
self.angle = 0.0
|
|
||||||
self.x = 1.0
|
|
||||||
self.z = 3.0
|
|
||||||
self.lx = 0.0
|
|
||||||
self.lz = 0.0
|
|
||||||
self.using_fixed_cam = False
|
self.using_fixed_cam = False
|
||||||
self.current_cam_index = 0
|
self.current_cam_index = 0
|
||||||
|
|
||||||
self.x_origin = -1 # x position of the mouse when pressing left btn
|
# store the global scene rotation
|
||||||
|
self.angle = 0.
|
||||||
|
|
||||||
# for FPS calculation
|
# for FPS calculation
|
||||||
self.prev_time = 0
|
self.prev_time = 0
|
||||||
|
@ -61,6 +61,10 @@ class GLRenderer():
|
||||||
self.frames = 0
|
self.frames = 0
|
||||||
|
|
||||||
def prepare_gl_buffers(self, mesh):
|
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 = {}
|
mesh.gl = {}
|
||||||
|
|
||||||
|
@ -90,8 +94,7 @@ class GLRenderer():
|
||||||
glBindBuffer(GL_ARRAY_BUFFER,0)
|
glBindBuffer(GL_ARRAY_BUFFER,0)
|
||||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,0)
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,0)
|
||||||
|
|
||||||
|
def load_model(self, path, postprocess = None):
|
||||||
def load_dae(self, path, postprocess = None):
|
|
||||||
logger.info("Loading model:" + path + "...")
|
logger.info("Loading model:" + path + "...")
|
||||||
|
|
||||||
if postprocess:
|
if postprocess:
|
||||||
|
@ -129,9 +132,11 @@ class GLRenderer():
|
||||||
|
|
||||||
if not self.using_fixed_cam:
|
if not self.using_fixed_cam:
|
||||||
glLoadIdentity()
|
glLoadIdentity()
|
||||||
gluLookAt(self.x ,1., self.z, # pos
|
|
||||||
self.x + self.lx - 1.0, 1., self.z + self.lz - 3.0, # look at
|
gluLookAt(0.,0.,3.,
|
||||||
0.,1.,0.) # up vector
|
0.,0.,-5.,
|
||||||
|
0.,1.,0.)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def set_camera(self, camera):
|
def set_camera(self, camera):
|
||||||
|
@ -178,13 +183,13 @@ class GLRenderer():
|
||||||
tmp = max(x_max, y_max)
|
tmp = max(x_max, y_max)
|
||||||
z_max = self.bb_max[2] - self.bb_min[2]
|
z_max = self.bb_max[2] - self.bb_min[2]
|
||||||
tmp = max(z_max, tmp)
|
tmp = max(z_max, tmp)
|
||||||
|
|
||||||
if not restore:
|
if not restore:
|
||||||
tmp = 1. / tmp
|
tmp = 1. / tmp
|
||||||
|
|
||||||
logger.info("Scaling the scene by %.03f" % tmp)
|
logger.info("Scaling the scene by %.03f" % tmp)
|
||||||
glScalef(tmp, tmp, tmp)
|
glScalef(tmp, tmp, tmp)
|
||||||
|
|
||||||
# center the model
|
# center the model
|
||||||
direction = -1 if not restore else 1
|
direction = -1 if not restore else 1
|
||||||
glTranslatef( direction * self.scene_center[0],
|
glTranslatef( direction * self.scene_center[0],
|
||||||
|
@ -192,25 +197,23 @@ class GLRenderer():
|
||||||
direction * self.scene_center[2] )
|
direction * self.scene_center[2] )
|
||||||
|
|
||||||
return x_max, y_max, z_max
|
return x_max, y_max, z_max
|
||||||
|
|
||||||
def apply_material(self, mat):
|
def apply_material(self, mat):
|
||||||
""" Apply an OpenGL, using one OpenGL list per material to cache
|
""" Apply an OpenGL, using one OpenGL display list per material to cache
|
||||||
the operation.
|
the operation.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not hasattr(mat, "gl_mat"): # evaluate once the mat properties, and cache the values in a glDisplayList.
|
if not hasattr(mat, "gl_mat"): # evaluate once the mat properties, and cache the values in a glDisplayList.
|
||||||
|
|
||||||
diffuse = mat.properties.get("$clr.diffuse", numpy.array([0.8, 0.8, 0.8, 1.0]))
|
diffuse = numpy.array(mat.properties.get("diffuse", [0.8, 0.8, 0.8, 1.0]))
|
||||||
specular = mat.properties.get("$clr.specular", numpy.array([0., 0., 0., 1.0]))
|
specular = numpy.array(mat.properties.get("specular", [0., 0., 0., 1.0]))
|
||||||
ambient = mat.properties.get("$clr.ambient", numpy.array([0.2, 0.2, 0.2, 1.0]))
|
ambient = numpy.array(mat.properties.get("ambient", [0.2, 0.2, 0.2, 1.0]))
|
||||||
emissive = mat.properties.get("$clr.emissive", numpy.array([0., 0., 0., 1.0]))
|
emissive = numpy.array(mat.properties.get("emissive", [0., 0., 0., 1.0]))
|
||||||
shininess = min(mat.properties.get("$mat.shininess", 1.0), 128)
|
shininess = min(mat.properties.get("shininess", 1.0), 128)
|
||||||
wireframe = mat.properties.get("$mat.wireframe", 0)
|
wireframe = mat.properties.get("wireframe", 0)
|
||||||
twosided = mat.properties.get("$mat.twosided", 1)
|
twosided = mat.properties.get("twosided", 1)
|
||||||
|
|
||||||
from OpenGL.raw import GL
|
setattr(mat, "gl_mat", glGenLists(1))
|
||||||
setattr(mat, "gl_mat", GL.GLuint(0))
|
|
||||||
mat.gl_mat = glGenLists(1)
|
|
||||||
glNewList(mat.gl_mat, GL_COMPILE)
|
glNewList(mat.gl_mat, GL_COMPILE)
|
||||||
|
|
||||||
glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffuse)
|
glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffuse)
|
||||||
|
@ -239,6 +242,8 @@ class GLRenderer():
|
||||||
self.lz = -math.cos(self.angle)
|
self.lz = -math.cos(self.angle)
|
||||||
self.set_default_camera()
|
self.set_default_camera()
|
||||||
|
|
||||||
|
self.angle = (gl_time - self.prev_time) * 0.1
|
||||||
|
|
||||||
self.prev_time = gl_time
|
self.prev_time = gl_time
|
||||||
|
|
||||||
# Compute FPS
|
# Compute FPS
|
||||||
|
@ -290,9 +295,10 @@ class GLRenderer():
|
||||||
""" GLUT callback to redraw OpenGL surface
|
""" GLUT callback to redraw OpenGL surface
|
||||||
"""
|
"""
|
||||||
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
|
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
|
||||||
|
|
||||||
|
glRotatef(self.angle,0.,1.,0.)
|
||||||
self.recursive_render(self.scene.rootnode)
|
self.recursive_render(self.scene.rootnode)
|
||||||
|
|
||||||
glutSwapBuffers()
|
glutSwapBuffers()
|
||||||
self.do_motion()
|
self.do_motion()
|
||||||
return
|
return
|
||||||
|
@ -307,42 +313,6 @@ class GLRenderer():
|
||||||
if key == 'q':
|
if key == 'q':
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
def onspecialkeypress(self, key, x, y):
|
|
||||||
|
|
||||||
fraction = 0.05
|
|
||||||
|
|
||||||
if key == GLUT_KEY_UP:
|
|
||||||
self.dp = 0.5
|
|
||||||
if key == GLUT_KEY_DOWN:
|
|
||||||
self.dp = -0.5
|
|
||||||
if key == GLUT_KEY_LEFT:
|
|
||||||
self.drot = -0.01
|
|
||||||
if key == GLUT_KEY_RIGHT:
|
|
||||||
self.drot = 0.01
|
|
||||||
|
|
||||||
def onspecialkeyrelease(self, key, x, y):
|
|
||||||
|
|
||||||
if key == GLUT_KEY_UP:
|
|
||||||
self.dp = 0.
|
|
||||||
if key == GLUT_KEY_DOWN:
|
|
||||||
self.dp = 0.
|
|
||||||
if key == GLUT_KEY_LEFT:
|
|
||||||
self.drot = 0.0
|
|
||||||
if key == GLUT_KEY_RIGHT:
|
|
||||||
self.drot = 0.0
|
|
||||||
|
|
||||||
def onclick(self, button, state, x, y):
|
|
||||||
if button == GLUT_LEFT_BUTTON:
|
|
||||||
if state == GLUT_UP:
|
|
||||||
self.drot = 0
|
|
||||||
self.x_origin = -1
|
|
||||||
else: # GLUT_DOWN
|
|
||||||
self.x_origin = x
|
|
||||||
|
|
||||||
def onmousemove(self, x, y):
|
|
||||||
if self.x_origin >= 0:
|
|
||||||
self.drot = (x - self.x_origin) * 0.001
|
|
||||||
|
|
||||||
def render(self, filename=None, fullscreen = False, autofit = True, postprocess = None):
|
def render(self, filename=None, fullscreen = False, autofit = True, postprocess = None):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -364,7 +334,8 @@ class GLRenderer():
|
||||||
print("Fullscreen mode not available!")
|
print("Fullscreen mode not available!")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
self.load_dae(filename, postprocess = postprocess)
|
self.load_model(filename, postprocess = postprocess)
|
||||||
|
|
||||||
|
|
||||||
glClearColor(0.1,0.1,0.1,1.)
|
glClearColor(0.1,0.1,0.1,1.)
|
||||||
#glShadeModel(GL_SMOOTH)
|
#glShadeModel(GL_SMOOTH)
|
||||||
|
@ -374,16 +345,10 @@ class GLRenderer():
|
||||||
glEnable(GL_CULL_FACE)
|
glEnable(GL_CULL_FACE)
|
||||||
glEnable(GL_DEPTH_TEST)
|
glEnable(GL_DEPTH_TEST)
|
||||||
|
|
||||||
#lightZeroPosition = [10.,4.,10.,1.]
|
|
||||||
#lightZeroColor = [0.8,1.0,0.8,1.0] #green tinged
|
|
||||||
#glLightfv(GL_LIGHT0, GL_POSITION, lightZeroPosition)
|
|
||||||
#glLightfv(GL_LIGHT0, GL_DIFFUSE, lightZeroColor)
|
|
||||||
#glLightf(GL_LIGHT0, GL_CONSTANT_ATTENUATION, 0.1)
|
|
||||||
#glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 0.05)
|
|
||||||
glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE)
|
glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE)
|
||||||
glEnable(GL_NORMALIZE)
|
glEnable(GL_NORMALIZE)
|
||||||
glEnable(GL_LIGHT0)
|
glEnable(GL_LIGHT0)
|
||||||
|
|
||||||
glutDisplayFunc(self.display)
|
glutDisplayFunc(self.display)
|
||||||
|
|
||||||
|
|
||||||
|
@ -399,14 +364,8 @@ class GLRenderer():
|
||||||
|
|
||||||
glPushMatrix()
|
glPushMatrix()
|
||||||
|
|
||||||
# Register GLUT callbacks for keyboard and mouse
|
|
||||||
glutKeyboardFunc(self.onkeypress)
|
glutKeyboardFunc(self.onkeypress)
|
||||||
glutSpecialFunc(self.onspecialkeypress)
|
|
||||||
glutIgnoreKeyRepeat(1)
|
glutIgnoreKeyRepeat(1)
|
||||||
glutSpecialUpFunc(self.onspecialkeyrelease)
|
|
||||||
|
|
||||||
glutMouseFunc(self.onclick)
|
|
||||||
glutMotionFunc(self.onmousemove)
|
|
||||||
|
|
||||||
glutMainLoop()
|
glutMainLoop()
|
||||||
|
|
Loading…
Reference in New Issue