3d_viewer.py 41 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318
  1. #!/usr/bin/env python
  2. # -*- coding: UTF-8 -*-
  3. """ This program loads a model with PyASSIMP, and display it.
  4. Based on:
  5. - pygame code from http://3dengine.org/Spectator_%28PyOpenGL%29
  6. - http://www.lighthouse3d.com/tutorials
  7. - http://www.songho.ca/opengl/gl_transform.html
  8. - http://code.activestate.com/recipes/325391/
  9. - ASSIMP's C++ SimpleOpenGL viewer
  10. Authors: Séverin Lemaignan, 2012-2016
  11. """
  12. import sys
  13. import logging
  14. logger = logging.getLogger("pyassimp")
  15. gllogger = logging.getLogger("OpenGL")
  16. gllogger.setLevel(logging.WARNING)
  17. logging.basicConfig(level=logging.INFO)
  18. import OpenGL
  19. OpenGL.ERROR_CHECKING = False
  20. OpenGL.ERROR_LOGGING = False
  21. # OpenGL.ERROR_ON_COPY = True
  22. # OpenGL.FULL_LOGGING = True
  23. from OpenGL.GL import *
  24. from OpenGL.arrays import vbo
  25. from OpenGL.GL import shaders
  26. import pygame
  27. import pygame.font
  28. import pygame.image
  29. import math, random
  30. from numpy import linalg
  31. import pyassimp
  32. from pyassimp.postprocess import *
  33. from pyassimp.helper import *
  34. import transformations
  35. ROTATION_180_X = numpy.array([[1, 0, 0, 0], [0, -1, 0, 0], [0, 0, -1, 0], [0, 0, 0, 1]], dtype=numpy.float32)
  36. # rendering mode
  37. BASE = "BASE"
  38. COLORS = "COLORS"
  39. SILHOUETTE = "SILHOUETTE"
  40. HELPERS = "HELPERS"
  41. # Entities type
  42. ENTITY = "entity"
  43. CAMERA = "camera"
  44. MESH = "mesh"
  45. FLAT_VERTEX_SHADER_120 = """
  46. #version 120
  47. uniform mat4 u_viewProjectionMatrix;
  48. uniform mat4 u_modelMatrix;
  49. uniform vec4 u_materialDiffuse;
  50. attribute vec3 a_vertex;
  51. varying vec4 v_color;
  52. void main(void)
  53. {
  54. v_color = u_materialDiffuse;
  55. gl_Position = u_viewProjectionMatrix * u_modelMatrix * vec4(a_vertex, 1.0);
  56. }
  57. """
  58. FLAT_VERTEX_SHADER_130 = """
  59. #version 130
  60. uniform mat4 u_viewProjectionMatrix;
  61. uniform mat4 u_modelMatrix;
  62. uniform vec4 u_materialDiffuse;
  63. in vec3 a_vertex;
  64. out vec4 v_color;
  65. void main(void)
  66. {
  67. v_color = u_materialDiffuse;
  68. gl_Position = u_viewProjectionMatrix * u_modelMatrix * vec4(a_vertex, 1.0);
  69. }
  70. """
  71. BASIC_VERTEX_SHADER_120 = """
  72. #version 120
  73. uniform mat4 u_viewProjectionMatrix;
  74. uniform mat4 u_modelMatrix;
  75. uniform mat3 u_normalMatrix;
  76. uniform vec3 u_lightPos;
  77. uniform vec4 u_materialDiffuse;
  78. attribute vec3 a_vertex;
  79. attribute vec3 a_normal;
  80. varying vec4 v_color;
  81. void main(void)
  82. {
  83. // Now the normal is in world space, as we pass the light in world space.
  84. vec3 normal = u_normalMatrix * a_normal;
  85. float dist = distance(a_vertex, u_lightPos);
  86. // go to https://www.desmos.com/calculator/nmnaud1hrw to play with the parameters
  87. // att is not used for now
  88. float att=1.0/(1.0+0.8*dist*dist);
  89. vec3 surf2light = normalize(u_lightPos - a_vertex);
  90. vec3 norm = normalize(normal);
  91. float dcont=max(0.0,dot(norm,surf2light));
  92. float ambient = 0.3;
  93. float intensity = dcont + 0.3 + ambient;
  94. v_color = u_materialDiffuse * intensity;
  95. gl_Position = u_viewProjectionMatrix * u_modelMatrix * vec4(a_vertex, 1.0);
  96. }
  97. """
  98. BASIC_VERTEX_SHADER_130 = """
  99. #version 130
  100. uniform mat4 u_viewProjectionMatrix;
  101. uniform mat4 u_modelMatrix;
  102. uniform mat3 u_normalMatrix;
  103. uniform vec3 u_lightPos;
  104. uniform vec4 u_materialDiffuse;
  105. in vec3 a_vertex;
  106. in vec3 a_normal;
  107. out vec4 v_color;
  108. void main(void)
  109. {
  110. // Now the normal is in world space, as we pass the light in world space.
  111. vec3 normal = u_normalMatrix * a_normal;
  112. float dist = distance(a_vertex, u_lightPos);
  113. // go to https://www.desmos.com/calculator/nmnaud1hrw to play with the parameters
  114. // att is not used for now
  115. float att=1.0/(1.0+0.8*dist*dist);
  116. vec3 surf2light = normalize(u_lightPos - a_vertex);
  117. vec3 norm = normalize(normal);
  118. float dcont=max(0.0,dot(norm,surf2light));
  119. float ambient = 0.3;
  120. float intensity = dcont + 0.3 + ambient;
  121. v_color = u_materialDiffuse * intensity;
  122. gl_Position = u_viewProjectionMatrix * u_modelMatrix * vec4(a_vertex, 1.0);
  123. }
  124. """
  125. BASIC_FRAGMENT_SHADER_120 = """
  126. #version 120
  127. varying vec4 v_color;
  128. void main() {
  129. gl_FragColor = v_color;
  130. }
  131. """
  132. BASIC_FRAGMENT_SHADER_130 = """
  133. #version 130
  134. in vec4 v_color;
  135. void main() {
  136. gl_FragColor = v_color;
  137. }
  138. """
  139. GOOCH_VERTEX_SHADER_120 = """
  140. #version 120
  141. // attributes
  142. attribute vec3 a_vertex; // xyz - position
  143. attribute vec3 a_normal; // xyz - normal
  144. // uniforms
  145. uniform mat4 u_modelMatrix;
  146. uniform mat4 u_viewProjectionMatrix;
  147. uniform mat3 u_normalMatrix;
  148. uniform vec3 u_lightPos;
  149. uniform vec3 u_camPos;
  150. // output data from vertex to fragment shader
  151. varying vec3 o_normal;
  152. varying vec3 o_lightVector;
  153. ///////////////////////////////////////////////////////////////////
  154. void main(void)
  155. {
  156. // transform position and normal to world space
  157. vec4 positionWorld = u_modelMatrix * vec4(a_vertex, 1.0);
  158. vec3 normalWorld = u_normalMatrix * a_normal;
  159. // calculate and pass vectors required for lighting
  160. o_lightVector = u_lightPos - positionWorld.xyz;
  161. o_normal = normalWorld;
  162. // project world space position to the screen and output it
  163. gl_Position = u_viewProjectionMatrix * positionWorld;
  164. }
  165. """
  166. GOOCH_VERTEX_SHADER_130 = """
  167. #version 130
  168. // attributes
  169. in vec3 a_vertex; // xyz - position
  170. in vec3 a_normal; // xyz - normal
  171. // uniforms
  172. uniform mat4 u_modelMatrix;
  173. uniform mat4 u_viewProjectionMatrix;
  174. uniform mat3 u_normalMatrix;
  175. uniform vec3 u_lightPos;
  176. uniform vec3 u_camPos;
  177. // output data from vertex to fragment shader
  178. out vec3 o_normal;
  179. out vec3 o_lightVector;
  180. ///////////////////////////////////////////////////////////////////
  181. void main(void)
  182. {
  183. // transform position and normal to world space
  184. vec4 positionWorld = u_modelMatrix * vec4(a_vertex, 1.0);
  185. vec3 normalWorld = u_normalMatrix * a_normal;
  186. // calculate and pass vectors required for lighting
  187. o_lightVector = u_lightPos - positionWorld.xyz;
  188. o_normal = normalWorld;
  189. // project world space position to the screen and output it
  190. gl_Position = u_viewProjectionMatrix * positionWorld;
  191. }
  192. """
  193. GOOCH_FRAGMENT_SHADER_120 = """
  194. #version 120
  195. // data from vertex shader
  196. varying vec3 o_normal;
  197. varying vec3 o_lightVector;
  198. // diffuse color of the object
  199. uniform vec4 u_materialDiffuse;
  200. // cool color of gooch shading
  201. uniform vec3 u_coolColor;
  202. // warm color of gooch shading
  203. uniform vec3 u_warmColor;
  204. // how much to take from object color in final cool color
  205. uniform float u_alpha;
  206. // how much to take from object color in final warm color
  207. uniform float u_beta;
  208. ///////////////////////////////////////////////////////////
  209. void main(void)
  210. {
  211. // normlize vectors for lighting
  212. vec3 normalVector = normalize(o_normal);
  213. vec3 lightVector = normalize(o_lightVector);
  214. // intensity of diffuse lighting [-1, 1]
  215. float diffuseLighting = dot(lightVector, normalVector);
  216. // map intensity of lighting from range [-1; 1] to [0, 1]
  217. float interpolationValue = (1.0 + diffuseLighting)/2;
  218. //////////////////////////////////////////////////////////////////
  219. // cool color mixed with color of the object
  220. vec3 coolColorMod = u_coolColor + vec3(u_materialDiffuse) * u_alpha;
  221. // warm color mixed with color of the object
  222. vec3 warmColorMod = u_warmColor + vec3(u_materialDiffuse) * u_beta;
  223. // interpolation of cool and warm colors according
  224. // to lighting intensity. The lower the light intensity,
  225. // the larger part of the cool color is used
  226. vec3 colorOut = mix(coolColorMod, warmColorMod, interpolationValue);
  227. //////////////////////////////////////////////////////////////////
  228. // save color
  229. gl_FragColor.rgb = colorOut;
  230. gl_FragColor.a = 1;
  231. }
  232. """
  233. GOOCH_FRAGMENT_SHADER_130 = """
  234. #version 130
  235. // data from vertex shader
  236. in vec3 o_normal;
  237. in vec3 o_lightVector;
  238. // diffuse color of the object
  239. uniform vec4 u_materialDiffuse;
  240. // cool color of gooch shading
  241. uniform vec3 u_coolColor;
  242. // warm color of gooch shading
  243. uniform vec3 u_warmColor;
  244. // how much to take from object color in final cool color
  245. uniform float u_alpha;
  246. // how much to take from object color in final warm color
  247. uniform float u_beta;
  248. // output to framebuffer
  249. out vec4 resultingColor;
  250. ///////////////////////////////////////////////////////////
  251. void main(void)
  252. {
  253. // normlize vectors for lighting
  254. vec3 normalVector = normalize(o_normal);
  255. vec3 lightVector = normalize(o_lightVector);
  256. // intensity of diffuse lighting [-1, 1]
  257. float diffuseLighting = dot(lightVector, normalVector);
  258. // map intensity of lighting from range [-1; 1] to [0, 1]
  259. float interpolationValue = (1.0 + diffuseLighting)/2;
  260. //////////////////////////////////////////////////////////////////
  261. // cool color mixed with color of the object
  262. vec3 coolColorMod = u_coolColor + vec3(u_materialDiffuse) * u_alpha;
  263. // warm color mixed with color of the object
  264. vec3 warmColorMod = u_warmColor + vec3(u_materialDiffuse) * u_beta;
  265. // interpolation of cool and warm colors according
  266. // to lighting intensity. The lower the light intensity,
  267. // the larger part of the cool color is used
  268. vec3 colorOut = mix(coolColorMod, warmColorMod, interpolationValue);
  269. //////////////////////////////////////////////////////////////////
  270. // save color
  271. resultingColor.rgb = colorOut;
  272. resultingColor.a = 1;
  273. }
  274. """
  275. SILHOUETTE_VERTEX_SHADER_120 = """
  276. #version 120
  277. attribute vec3 a_vertex; // xyz - position
  278. attribute vec3 a_normal; // xyz - normal
  279. uniform mat4 u_modelMatrix;
  280. uniform mat4 u_viewProjectionMatrix;
  281. uniform mat4 u_modelViewMatrix;
  282. uniform vec4 u_materialDiffuse;
  283. uniform float u_bordersize; // width of the border
  284. varying vec4 v_color;
  285. void main(void){
  286. v_color = u_materialDiffuse;
  287. float distToCamera = -(u_modelViewMatrix * vec4(a_vertex, 1.0)).z;
  288. vec4 tPos = vec4(a_vertex + a_normal * u_bordersize * distToCamera, 1.0);
  289. gl_Position = u_viewProjectionMatrix * u_modelMatrix * tPos;
  290. }
  291. """
  292. SILHOUETTE_VERTEX_SHADER_130 = """
  293. #version 130
  294. in vec3 a_vertex; // xyz - position
  295. in vec3 a_normal; // xyz - normal
  296. uniform mat4 u_modelMatrix;
  297. uniform mat4 u_viewProjectionMatrix;
  298. uniform mat4 u_modelViewMatrix;
  299. uniform vec4 u_materialDiffuse;
  300. uniform float u_bordersize; // width of the border
  301. out vec4 v_color;
  302. void main(void){
  303. v_color = u_materialDiffuse;
  304. float distToCamera = -(u_modelViewMatrix * vec4(a_vertex, 1.0)).z;
  305. vec4 tPos = vec4(a_vertex + a_normal * u_bordersize * distToCamera, 1.0);
  306. gl_Position = u_viewProjectionMatrix * u_modelMatrix * tPos;
  307. }
  308. """
  309. DEFAULT_CLIP_PLANE_NEAR = 0.001
  310. DEFAULT_CLIP_PLANE_FAR = 1000.0
  311. def get_world_transform(scene, node):
  312. if node == scene.rootnode:
  313. return numpy.identity(4, dtype=numpy.float32)
  314. parents = reversed(_get_parent_chain(scene, node, []))
  315. parent_transform = reduce(numpy.dot, [p.transformation for p in parents])
  316. return numpy.dot(parent_transform, node.transformation)
  317. def _get_parent_chain(scene, node, parents):
  318. parent = node.parent
  319. parents.append(parent)
  320. if parent == scene.rootnode:
  321. return parents
  322. return _get_parent_chain(scene, parent, parents)
  323. class DefaultCamera:
  324. def __init__(self, w, h, fov):
  325. self.name = "default camera"
  326. self.type = CAMERA
  327. self.clipplanenear = DEFAULT_CLIP_PLANE_NEAR
  328. self.clipplanefar = DEFAULT_CLIP_PLANE_FAR
  329. self.aspect = w / h
  330. self.horizontalfov = fov * math.pi / 180
  331. self.transformation = numpy.array([[0.68, -0.32, 0.65, 7.48],
  332. [0.73, 0.31, -0.61, -6.51],
  333. [-0.01, 0.89, 0.44, 5.34],
  334. [0., 0., 0., 1.]], dtype=numpy.float32)
  335. self.transformation = numpy.dot(self.transformation, ROTATION_180_X)
  336. def __str__(self):
  337. return self.name
  338. class PyAssimp3DViewer:
  339. base_name = "PyASSIMP 3D viewer"
  340. def __init__(self, model, w=1024, h=768):
  341. self.w = w
  342. self.h = h
  343. pygame.init()
  344. pygame.display.set_caption(self.base_name)
  345. pygame.display.set_mode((w, h), pygame.OPENGL | pygame.DOUBLEBUF)
  346. glClearColor(0.18, 0.18, 0.18, 1.0)
  347. shader_compilation_succeeded = False
  348. try:
  349. self.set_shaders_v130()
  350. self.prepare_shaders()
  351. except RuntimeError as e:
  352. sys.stderr.write("%s\n" % e.message)
  353. sys.stdout.write("Could not compile shaders in version 1.30, trying version 1.20\n")
  354. if not shader_compilation_succeeded:
  355. self.set_shaders_v120()
  356. self.prepare_shaders()
  357. self.scene = None
  358. self.meshes = {} # stores the OpenGL vertex/faces/normals buffers pointers
  359. self.node2colorid = {} # stores a color ID for each node. Useful for mouse picking and visibility checking
  360. self.colorid2node = {} # reverse dict of node2colorid
  361. self.currently_selected = None
  362. self.moving = False
  363. self.moving_situation = None
  364. self.default_camera = DefaultCamera(self.w, self.h, fov=70)
  365. self.cameras = [self.default_camera]
  366. self.current_cam_index = 0
  367. self.current_cam = self.default_camera
  368. self.set_camera_projection()
  369. self.load_model(model)
  370. # user interactions
  371. self.focal_point = [0, 0, 0]
  372. self.is_rotating = False
  373. self.is_panning = False
  374. self.is_zooming = False
  375. def set_shaders_v120(self):
  376. self.BASIC_VERTEX_SHADER = BASIC_VERTEX_SHADER_120
  377. self.FLAT_VERTEX_SHADER = FLAT_VERTEX_SHADER_120
  378. self.SILHOUETTE_VERTEX_SHADER = SILHOUETTE_VERTEX_SHADER_120
  379. self.GOOCH_VERTEX_SHADER = GOOCH_VERTEX_SHADER_120
  380. self.BASIC_FRAGMENT_SHADER = BASIC_FRAGMENT_SHADER_120
  381. self.GOOCH_FRAGMENT_SHADER = GOOCH_FRAGMENT_SHADER_120
  382. def set_shaders_v130(self):
  383. self.BASIC_VERTEX_SHADER = BASIC_VERTEX_SHADER_130
  384. self.FLAT_VERTEX_SHADER = FLAT_VERTEX_SHADER_130
  385. self.SILHOUETTE_VERTEX_SHADER = SILHOUETTE_VERTEX_SHADER_130
  386. self.GOOCH_VERTEX_SHADER = GOOCH_VERTEX_SHADER_130
  387. self.BASIC_FRAGMENT_SHADER = BASIC_FRAGMENT_SHADER_130
  388. self.GOOCH_FRAGMENT_SHADER = GOOCH_FRAGMENT_SHADER_130
  389. def prepare_shaders(self):
  390. ### Base shader
  391. vertex = shaders.compileShader(self.BASIC_VERTEX_SHADER, GL_VERTEX_SHADER)
  392. fragment = shaders.compileShader(self.BASIC_FRAGMENT_SHADER, GL_FRAGMENT_SHADER)
  393. self.shader = shaders.compileProgram(vertex, fragment)
  394. self.set_shader_accessors(('u_modelMatrix',
  395. 'u_viewProjectionMatrix',
  396. 'u_normalMatrix',
  397. 'u_lightPos',
  398. 'u_materialDiffuse'),
  399. ('a_vertex',
  400. 'a_normal'), self.shader)
  401. ### Flat shader
  402. flatvertex = shaders.compileShader(self.FLAT_VERTEX_SHADER, GL_VERTEX_SHADER)
  403. self.flatshader = shaders.compileProgram(flatvertex, fragment)
  404. self.set_shader_accessors(('u_modelMatrix',
  405. 'u_viewProjectionMatrix',
  406. 'u_materialDiffuse',),
  407. ('a_vertex',), self.flatshader)
  408. ### Silhouette shader
  409. silh_vertex = shaders.compileShader(self.SILHOUETTE_VERTEX_SHADER, GL_VERTEX_SHADER)
  410. self.silhouette_shader = shaders.compileProgram(silh_vertex, fragment)
  411. self.set_shader_accessors(('u_modelMatrix',
  412. 'u_viewProjectionMatrix',
  413. 'u_modelViewMatrix',
  414. 'u_materialDiffuse',
  415. 'u_bordersize' # width of the silhouette
  416. ),
  417. ('a_vertex',
  418. 'a_normal'), self.silhouette_shader)
  419. ### Gooch shader
  420. gooch_vertex = shaders.compileShader(self.GOOCH_VERTEX_SHADER, GL_VERTEX_SHADER)
  421. gooch_fragment = shaders.compileShader(self.GOOCH_FRAGMENT_SHADER, GL_FRAGMENT_SHADER)
  422. self.gooch_shader = shaders.compileProgram(gooch_vertex, gooch_fragment)
  423. self.set_shader_accessors(('u_modelMatrix',
  424. 'u_viewProjectionMatrix',
  425. 'u_normalMatrix',
  426. 'u_lightPos',
  427. 'u_materialDiffuse',
  428. 'u_coolColor',
  429. 'u_warmColor',
  430. 'u_alpha',
  431. 'u_beta'
  432. ),
  433. ('a_vertex',
  434. 'a_normal'), self.gooch_shader)
  435. @staticmethod
  436. def set_shader_accessors(uniforms, attributes, shader):
  437. # add accessors to the shaders uniforms and attributes
  438. for uniform in uniforms:
  439. location = glGetUniformLocation(shader, uniform)
  440. if location in (None, -1):
  441. raise RuntimeError('No uniform: %s (maybe it is not used '
  442. 'anymore and has been optimized out by'
  443. ' the shader compiler)' % uniform)
  444. setattr(shader, uniform, location)
  445. for attribute in attributes:
  446. location = glGetAttribLocation(shader, attribute)
  447. if location in (None, -1):
  448. raise RuntimeError('No attribute: %s' % attribute)
  449. setattr(shader, attribute, location)
  450. @staticmethod
  451. def prepare_gl_buffers(mesh):
  452. mesh.gl = {}
  453. # Fill the buffer for vertex and normals positions
  454. v = numpy.array(mesh.vertices, 'f')
  455. n = numpy.array(mesh.normals, 'f')
  456. mesh.gl["vbo"] = vbo.VBO(numpy.hstack((v, n)))
  457. # Fill the buffer for vertex positions
  458. mesh.gl["faces"] = glGenBuffers(1)
  459. glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh.gl["faces"])
  460. glBufferData(GL_ELEMENT_ARRAY_BUFFER,
  461. numpy.array(mesh.faces, dtype=numpy.int32),
  462. GL_STATIC_DRAW)
  463. mesh.gl["nbfaces"] = len(mesh.faces)
  464. # Unbind buffers
  465. glBindBuffer(GL_ARRAY_BUFFER, 0)
  466. glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)
  467. @staticmethod
  468. def get_rgb_from_colorid(colorid):
  469. r = (colorid >> 0) & 0xff
  470. g = (colorid >> 8) & 0xff
  471. b = (colorid >> 16) & 0xff
  472. return r, g, b
  473. def get_color_id(self):
  474. id = random.randint(0, 256 * 256 * 256)
  475. if id not in self.colorid2node:
  476. return id
  477. else:
  478. return self.get_color_id()
  479. def glize(self, scene, node):
  480. logger.info("Loading node <%s>" % node)
  481. node.selected = True if self.currently_selected and self.currently_selected == node else False
  482. node.transformation = node.transformation.astype(numpy.float32)
  483. if node.meshes:
  484. node.type = MESH
  485. colorid = self.get_color_id()
  486. self.colorid2node[colorid] = node
  487. self.node2colorid[node.name] = colorid
  488. elif node.name in [c.name for c in scene.cameras]:
  489. # retrieve the ASSIMP camera object
  490. [cam] = [c for c in scene.cameras if c.name == node.name]
  491. node.type = CAMERA
  492. logger.info("Added camera <%s>" % node.name)
  493. logger.info("Camera position: %.3f, %.3f, %.3f" % tuple(node.transformation[:, 3][:3].tolist()))
  494. self.cameras.append(node)
  495. node.clipplanenear = cam.clipplanenear
  496. node.clipplanefar = cam.clipplanefar
  497. if numpy.allclose(cam.lookat, [0, 0, -1]) and numpy.allclose(cam.up, [0, 1, 0]): # Cameras in .blend files
  498. # Rotate by 180deg around X to have Z pointing forward
  499. node.transformation = numpy.dot(node.transformation, ROTATION_180_X)
  500. else:
  501. raise RuntimeError(
  502. "I do not know how to normalize this camera orientation: lookat=%s, up=%s" % (cam.lookat, cam.up))
  503. if cam.aspect == 0.0:
  504. logger.warning("Camera aspect not set. Setting to default 4:3")
  505. node.aspect = 1.333
  506. else:
  507. node.aspect = cam.aspect
  508. node.horizontalfov = cam.horizontalfov
  509. else:
  510. node.type = ENTITY
  511. for child in node.children:
  512. self.glize(scene, child)
  513. def load_model(self, path, postprocess=aiProcessPreset_TargetRealtime_MaxQuality):
  514. logger.info("Loading model:" + path + "...")
  515. if postprocess:
  516. self.scene = pyassimp.load(path, processing=postprocess)
  517. else:
  518. self.scene = pyassimp.load(path)
  519. logger.info("Done.")
  520. scene = self.scene
  521. # log some statistics
  522. logger.info(" meshes: %d" % len(scene.meshes))
  523. logger.info(" total faces: %d" % sum([len(mesh.faces) for mesh in scene.meshes]))
  524. logger.info(" materials: %d" % len(scene.materials))
  525. self.bb_min, self.bb_max = get_bounding_box(self.scene)
  526. logger.info(" bounding box:" + str(self.bb_min) + " - " + str(self.bb_max))
  527. self.scene_center = [(a + b) / 2. for a, b in zip(self.bb_min, self.bb_max)]
  528. for index, mesh in enumerate(scene.meshes):
  529. self.prepare_gl_buffers(mesh)
  530. self.glize(scene, scene.rootnode)
  531. # Finally release the model
  532. pyassimp.release(scene)
  533. logger.info("Ready for 3D rendering!")
  534. def cycle_cameras(self):
  535. self.current_cam_index = (self.current_cam_index + 1) % len(self.cameras)
  536. self.current_cam = self.cameras[self.current_cam_index]
  537. self.set_camera_projection(self.current_cam)
  538. logger.info("Switched to camera <%s>" % self.current_cam)
  539. def set_overlay_projection(self):
  540. glViewport(0, 0, self.w, self.h)
  541. glMatrixMode(GL_PROJECTION)
  542. glLoadIdentity()
  543. glOrtho(0.0, self.w - 1.0, 0.0, self.h - 1.0, -1.0, 1.0)
  544. glMatrixMode(GL_MODELVIEW)
  545. glLoadIdentity()
  546. def set_camera_projection(self, camera=None):
  547. if not camera:
  548. camera = self.current_cam
  549. znear = camera.clipplanenear or DEFAULT_CLIP_PLANE_NEAR
  550. zfar = camera.clipplanefar or DEFAULT_CLIP_PLANE_FAR
  551. aspect = camera.aspect
  552. fov = camera.horizontalfov
  553. glMatrixMode(GL_PROJECTION)
  554. glLoadIdentity()
  555. # Compute gl frustrum
  556. tangent = math.tan(fov / 2.)
  557. h = znear * tangent
  558. w = h * aspect
  559. # params: left, right, bottom, top, near, far
  560. glFrustum(-w, w, -h, h, znear, zfar)
  561. # equivalent to:
  562. # gluPerspective(fov * 180/math.pi, aspect, znear, zfar)
  563. self.projection_matrix = glGetFloatv(GL_PROJECTION_MATRIX).transpose()
  564. glMatrixMode(GL_MODELVIEW)
  565. glLoadIdentity()
  566. def render_colors(self):
  567. glEnable(GL_DEPTH_TEST)
  568. glDepthFunc(GL_LEQUAL)
  569. glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
  570. glEnable(GL_CULL_FACE)
  571. glUseProgram(self.flatshader)
  572. glUniformMatrix4fv(self.flatshader.u_viewProjectionMatrix, 1, GL_TRUE,
  573. numpy.dot(self.projection_matrix, self.view_matrix))
  574. self.recursive_render(self.scene.rootnode, self.flatshader, mode=COLORS)
  575. glUseProgram(0)
  576. def get_hovered_node(self, mousex, mousey):
  577. """
  578. Attention: The performances of this method relies heavily on the size of the display!
  579. """
  580. # mouse out of the window?
  581. if mousex < 0 or mousex >= self.w or mousey < 0 or mousey >= self.h:
  582. return None
  583. self.render_colors()
  584. # Capture image from the OpenGL buffer
  585. buf = (GLubyte * (3 * self.w * self.h))(0)
  586. glReadPixels(0, 0, self.w, self.h, GL_RGB, GL_UNSIGNED_BYTE, buf)
  587. # Reinterpret the RGB pixel buffer as a 1-D array of 24bits colors
  588. a = numpy.ndarray(len(buf), numpy.dtype('>u1'), buf)
  589. colors = numpy.zeros(len(buf) / 3, numpy.dtype('<u4'))
  590. for i in range(3):
  591. colors.view(dtype='>u1')[i::4] = a.view(dtype='>u1')[i::3]
  592. colorid = colors[mousex + mousey * self.w]
  593. glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
  594. if colorid in self.colorid2node:
  595. return self.colorid2node[colorid]
  596. def render(self, wireframe=False, twosided=False):
  597. glEnable(GL_DEPTH_TEST)
  598. glDepthFunc(GL_LEQUAL)
  599. glPolygonMode(GL_FRONT_AND_BACK, GL_LINE if wireframe else GL_FILL)
  600. glDisable(GL_CULL_FACE) if twosided else glEnable(GL_CULL_FACE)
  601. self.render_grid()
  602. self.recursive_render(self.scene.rootnode, None, mode=HELPERS)
  603. ### First, the silhouette
  604. if False:
  605. shader = self.silhouette_shader
  606. # glDepthMask(GL_FALSE)
  607. glCullFace(GL_FRONT) # cull front faces
  608. glUseProgram(shader)
  609. glUniform1f(shader.u_bordersize, 0.01)
  610. glUniformMatrix4fv(shader.u_viewProjectionMatrix, 1, GL_TRUE,
  611. numpy.dot(self.projection_matrix, self.view_matrix))
  612. self.recursive_render(self.scene.rootnode, shader, mode=SILHOUETTE)
  613. glUseProgram(0)
  614. ### Then, inner shading
  615. # glDepthMask(GL_TRUE)
  616. glCullFace(GL_BACK)
  617. use_gooch = False
  618. if use_gooch:
  619. shader = self.gooch_shader
  620. glUseProgram(shader)
  621. glUniform3f(shader.u_lightPos, -.5, -.5, .5)
  622. ##### GOOCH specific
  623. glUniform3f(shader.u_coolColor, 159.0 / 255, 148.0 / 255, 255.0 / 255)
  624. glUniform3f(shader.u_warmColor, 255.0 / 255, 75.0 / 255, 75.0 / 255)
  625. glUniform1f(shader.u_alpha, .25)
  626. glUniform1f(shader.u_beta, .25)
  627. #########
  628. else:
  629. shader = self.shader
  630. glUseProgram(shader)
  631. glUniform3f(shader.u_lightPos, -.5, -.5, .5)
  632. glUniformMatrix4fv(shader.u_viewProjectionMatrix, 1, GL_TRUE,
  633. numpy.dot(self.projection_matrix, self.view_matrix))
  634. self.recursive_render(self.scene.rootnode, shader)
  635. glUseProgram(0)
  636. def render_axis(self,
  637. transformation=numpy.identity(4, dtype=numpy.float32),
  638. label=None,
  639. size=0.2,
  640. selected=False):
  641. m = transformation.transpose() # OpenGL row major
  642. glPushMatrix()
  643. glMultMatrixf(m)
  644. glLineWidth(3 if selected else 1)
  645. size = 2 * size if selected else size
  646. glBegin(GL_LINES)
  647. # draw line for x axis
  648. glColor3f(1.0, 0.0, 0.0)
  649. glVertex3f(0.0, 0.0, 0.0)
  650. glVertex3f(size, 0.0, 0.0)
  651. # draw line for y axis
  652. glColor3f(0.0, 1.0, 0.0)
  653. glVertex3f(0.0, 0.0, 0.0)
  654. glVertex3f(0.0, size, 0.0)
  655. # draw line for Z axis
  656. glColor3f(0.0, 0.0, 1.0)
  657. glVertex3f(0.0, 0.0, 0.0)
  658. glVertex3f(0.0, 0.0, size)
  659. glEnd()
  660. if label:
  661. self.showtext(label)
  662. glPopMatrix()
  663. @staticmethod
  664. def render_camera(camera, transformation):
  665. m = transformation.transpose() # OpenGL row major
  666. aspect = camera.aspect
  667. u = 0.1 # unit size (in m)
  668. l = 3 * u # length of the camera cone
  669. f = 3 * u # aperture of the camera cone
  670. glPushMatrix()
  671. glMultMatrixf(m)
  672. glLineWidth(2)
  673. glBegin(GL_LINE_STRIP)
  674. glColor3f(.2, .2, .2)
  675. glVertex3f(u, u, -u)
  676. glVertex3f(u, -u, -u)
  677. glVertex3f(-u, -u, -u)
  678. glVertex3f(-u, u, -u)
  679. glVertex3f(u, u, -u)
  680. glVertex3f(u, u, 0.0)
  681. glVertex3f(u, -u, 0.0)
  682. glVertex3f(-u, -u, 0.0)
  683. glVertex3f(-u, u, 0.0)
  684. glVertex3f(u, u, 0.0)
  685. glVertex3f(f * aspect, f, l)
  686. glVertex3f(f * aspect, -f, l)
  687. glVertex3f(-f * aspect, -f, l)
  688. glVertex3f(-f * aspect, f, l)
  689. glVertex3f(f * aspect, f, l)
  690. glEnd()
  691. glBegin(GL_LINE_STRIP)
  692. glVertex3f(u, -u, -u)
  693. glVertex3f(u, -u, 0.0)
  694. glVertex3f(f * aspect, -f, l)
  695. glEnd()
  696. glBegin(GL_LINE_STRIP)
  697. glVertex3f(-u, -u, -u)
  698. glVertex3f(-u, -u, 0.0)
  699. glVertex3f(-f * aspect, -f, l)
  700. glEnd()
  701. glBegin(GL_LINE_STRIP)
  702. glVertex3f(-u, u, -u)
  703. glVertex3f(-u, u, 0.0)
  704. glVertex3f(-f * aspect, f, l)
  705. glEnd()
  706. glPopMatrix()
  707. @staticmethod
  708. def render_grid():
  709. glLineWidth(1)
  710. glColor3f(0.5, 0.5, 0.5)
  711. glBegin(GL_LINES)
  712. for i in range(-10, 11):
  713. glVertex3f(i, -10.0, 0.0)
  714. glVertex3f(i, 10.0, 0.0)
  715. for i in range(-10, 11):
  716. glVertex3f(-10.0, i, 0.0)
  717. glVertex3f(10.0, i, 0.0)
  718. glEnd()
  719. def recursive_render(self, node, shader, mode=BASE, with_normals=True):
  720. """ Main recursive rendering method.
  721. """
  722. normals = with_normals
  723. if mode == COLORS:
  724. normals = False
  725. if not hasattr(node, "selected"):
  726. node.selected = False
  727. m = get_world_transform(self.scene, node)
  728. # HELPERS mode
  729. ###
  730. if mode == HELPERS:
  731. # if node.type == ENTITY:
  732. self.render_axis(m,
  733. label=node.name if node != self.scene.rootnode else None,
  734. selected=node.selected if hasattr(node, "selected") else False)
  735. if node.type == CAMERA:
  736. self.render_camera(node, m)
  737. for child in node.children:
  738. self.recursive_render(child, shader, mode)
  739. return
  740. # Mesh rendering modes
  741. ###
  742. if node.type == MESH:
  743. for mesh in node.meshes:
  744. stride = 24 # 6 * 4 bytes
  745. if node.selected and mode == SILHOUETTE:
  746. glUniform4f(shader.u_materialDiffuse, 1.0, 0.0, 0.0, 1.0)
  747. glUniformMatrix4fv(shader.u_modelViewMatrix, 1, GL_TRUE,
  748. numpy.dot(self.view_matrix, m))
  749. else:
  750. if mode == COLORS:
  751. colorid = self.node2colorid[node.name]
  752. r, g, b = self.get_rgb_from_colorid(colorid)
  753. glUniform4f(shader.u_materialDiffuse, r / 255.0, g / 255.0, b / 255.0, 1.0)
  754. elif mode == SILHOUETTE:
  755. glUniform4f(shader.u_materialDiffuse, .0, .0, .0, 1.0)
  756. else:
  757. if node.selected:
  758. diffuse = (1.0, 0.0, 0.0, 1.0) # selected nodes in red
  759. else:
  760. diffuse = mesh.material.properties["diffuse"]
  761. if len(diffuse) == 3: # RGB instead of expected RGBA
  762. diffuse.append(1.0)
  763. glUniform4f(shader.u_materialDiffuse, *diffuse)
  764. # if ambient:
  765. # glUniform4f( shader.Material_ambient, *mat["ambient"] )
  766. if mode == BASE: # not in COLORS or SILHOUETTE
  767. normal_matrix = linalg.inv(numpy.dot(self.view_matrix, m)[0:3, 0:3]).transpose()
  768. glUniformMatrix3fv(shader.u_normalMatrix, 1, GL_TRUE, normal_matrix)
  769. glUniformMatrix4fv(shader.u_modelMatrix, 1, GL_TRUE, m)
  770. vbo = mesh.gl["vbo"]
  771. vbo.bind()
  772. glEnableVertexAttribArray(shader.a_vertex)
  773. if normals:
  774. glEnableVertexAttribArray(shader.a_normal)
  775. glVertexAttribPointer(
  776. shader.a_vertex,
  777. 3, GL_FLOAT, False, stride, vbo
  778. )
  779. if normals:
  780. glVertexAttribPointer(
  781. shader.a_normal,
  782. 3, GL_FLOAT, False, stride, vbo + 12
  783. )
  784. glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh.gl["faces"])
  785. glDrawElements(GL_TRIANGLES, mesh.gl["nbfaces"] * 3, GL_UNSIGNED_INT, None)
  786. vbo.unbind()
  787. glDisableVertexAttribArray(shader.a_vertex)
  788. if normals:
  789. glDisableVertexAttribArray(shader.a_normal)
  790. glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)
  791. for child in node.children:
  792. self.recursive_render(child, shader, mode)
  793. def switch_to_overlay(self):
  794. glPushMatrix()
  795. self.set_overlay_projection()
  796. def switch_from_overlay(self):
  797. self.set_camera_projection()
  798. glPopMatrix()
  799. def select_node(self, node):
  800. self.currently_selected = node
  801. self.update_node_select(self.scene.rootnode)
  802. def update_node_select(self, node):
  803. if node is self.currently_selected:
  804. node.selected = True
  805. else:
  806. node.selected = False
  807. for child in node.children:
  808. self.update_node_select(child)
  809. def loop(self):
  810. pygame.display.flip()
  811. if not self.process_events():
  812. return False # ESC has been pressed
  813. glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
  814. return True
  815. def process_events(self):
  816. LEFT_BUTTON = 1
  817. MIDDLE_BUTTON = 2
  818. RIGHT_BUTTON = 3
  819. WHEEL_UP = 4
  820. WHEEL_DOWN = 5
  821. dx, dy = pygame.mouse.get_rel()
  822. mousex, mousey = pygame.mouse.get_pos()
  823. zooming_one_shot = False
  824. ok = True
  825. for evt in pygame.event.get():
  826. if evt.type == pygame.MOUSEBUTTONDOWN and evt.button == LEFT_BUTTON:
  827. hovered = self.get_hovered_node(mousex, self.h - mousey)
  828. if hovered:
  829. if self.currently_selected and self.currently_selected == hovered:
  830. self.select_node(None)
  831. else:
  832. logger.info("Node %s selected" % hovered)
  833. self.select_node(hovered)
  834. else:
  835. self.is_rotating = True
  836. if evt.type == pygame.MOUSEBUTTONUP and evt.button == LEFT_BUTTON:
  837. self.is_rotating = False
  838. if evt.type == pygame.MOUSEBUTTONDOWN and evt.button == MIDDLE_BUTTON:
  839. self.is_panning = True
  840. if evt.type == pygame.MOUSEBUTTONUP and evt.button == MIDDLE_BUTTON:
  841. self.is_panning = False
  842. if evt.type == pygame.MOUSEBUTTONDOWN and evt.button == RIGHT_BUTTON:
  843. self.is_zooming = True
  844. if evt.type == pygame.MOUSEBUTTONUP and evt.button == RIGHT_BUTTON:
  845. self.is_zooming = False
  846. if evt.type == pygame.MOUSEBUTTONDOWN and evt.button in [WHEEL_UP, WHEEL_DOWN]:
  847. zooming_one_shot = True
  848. self.is_zooming = True
  849. dy = -10 if evt.button == WHEEL_UP else 10
  850. if evt.type == pygame.KEYDOWN:
  851. ok = (ok and self.process_keystroke(evt.key, evt.mod))
  852. self.controls_3d(dx, dy, zooming_one_shot)
  853. return ok
  854. def process_keystroke(self, key, mod):
  855. # process arrow keys if an object is selected
  856. if self.currently_selected:
  857. up = 0
  858. strafe = 0
  859. if key == pygame.K_UP:
  860. up = 1
  861. if key == pygame.K_DOWN:
  862. up = -1
  863. if key == pygame.K_LEFT:
  864. strafe = -1
  865. if key == pygame.K_RIGHT:
  866. strafe = 1
  867. self.move_selected_node(up, strafe)
  868. if key == pygame.K_f:
  869. pygame.display.toggle_fullscreen()
  870. if key == pygame.K_TAB:
  871. self.cycle_cameras()
  872. if key in [pygame.K_ESCAPE, pygame.K_q]:
  873. return False
  874. return True
  875. def controls_3d(self, dx, dy, zooming_one_shot=False):
  876. CAMERA_TRANSLATION_FACTOR = 0.01
  877. CAMERA_ROTATION_FACTOR = 0.01
  878. if not (self.is_rotating or self.is_panning or self.is_zooming):
  879. return
  880. current_pos = self.current_cam.transformation[:3, 3].copy()
  881. distance = numpy.linalg.norm(self.focal_point - current_pos)
  882. if self.is_rotating:
  883. """ Orbiting the camera is implemented the following way:
  884. - the rotation is split into a rotation around the *world* Z axis
  885. (controlled by the horizontal mouse motion along X) and a
  886. rotation around the *X* axis of the camera (pitch) *shifted to
  887. the focal origin* (the world origin for now). This is controlled
  888. by the vertical motion of the mouse (Y axis).
  889. - as a result, the resulting transformation of the camera in the
  890. world frame C' is:
  891. C' = (T · Rx · T⁻¹ · (Rz · C)⁻¹)⁻¹
  892. where:
  893. - C is the original camera transformation in the world frame,
  894. - Rz is the rotation along the Z axis (in the world frame)
  895. - T is the translation camera -> world (ie, the inverse of the
  896. translation part of C
  897. - Rx is the rotation around X in the (translated) camera frame
  898. """
  899. rotation_camera_x = dy * CAMERA_ROTATION_FACTOR
  900. rotation_world_z = dx * CAMERA_ROTATION_FACTOR
  901. world_z_rotation = transformations.euler_matrix(0, 0, rotation_world_z)
  902. cam_x_rotation = transformations.euler_matrix(rotation_camera_x, 0, 0)
  903. after_world_z_rotation = numpy.dot(world_z_rotation, self.current_cam.transformation)
  904. inverse_transformation = transformations.inverse_matrix(after_world_z_rotation)
  905. translation = transformations.translation_matrix(
  906. transformations.decompose_matrix(inverse_transformation)[3])
  907. inverse_translation = transformations.inverse_matrix(translation)
  908. new_inverse = numpy.dot(inverse_translation, inverse_transformation)
  909. new_inverse = numpy.dot(cam_x_rotation, new_inverse)
  910. new_inverse = numpy.dot(translation, new_inverse)
  911. self.current_cam.transformation = transformations.inverse_matrix(new_inverse).astype(numpy.float32)
  912. if self.is_panning:
  913. tx = -dx * CAMERA_TRANSLATION_FACTOR * distance
  914. ty = dy * CAMERA_TRANSLATION_FACTOR * distance
  915. cam_transform = transformations.translation_matrix((tx, ty, 0)).astype(numpy.float32)
  916. self.current_cam.transformation = numpy.dot(self.current_cam.transformation, cam_transform)
  917. if self.is_zooming:
  918. tz = dy * CAMERA_TRANSLATION_FACTOR * distance
  919. cam_transform = transformations.translation_matrix((0, 0, tz)).astype(numpy.float32)
  920. self.current_cam.transformation = numpy.dot(self.current_cam.transformation, cam_transform)
  921. if zooming_one_shot:
  922. self.is_zooming = False
  923. self.update_view_camera()
  924. def update_view_camera(self):
  925. self.view_matrix = linalg.inv(self.current_cam.transformation)
  926. # Rotate by 180deg around X to have Z pointing backward (OpenGL convention)
  927. self.view_matrix = numpy.dot(ROTATION_180_X, self.view_matrix)
  928. glMatrixMode(GL_MODELVIEW)
  929. glLoadIdentity()
  930. glMultMatrixf(self.view_matrix.transpose())
  931. def move_selected_node(self, up, strafe):
  932. self.currently_selected.transformation[0][3] += strafe
  933. self.currently_selected.transformation[2][3] += up
  934. @staticmethod
  935. def showtext(text, x=0, y=0, z=0, size=20):
  936. # TODO: alpha blending does not work...
  937. # glEnable(GL_BLEND)
  938. # glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
  939. font = pygame.font.Font(None, size)
  940. text_surface = font.render(text, True, (10, 10, 10, 255),
  941. (255 * 0.18, 255 * 0.18, 255 * 0.18, 0))
  942. text_data = pygame.image.tostring(text_surface, "RGBA", True)
  943. glRasterPos3d(x, y, z)
  944. glDrawPixels(text_surface.get_width(),
  945. text_surface.get_height(),
  946. GL_RGBA, GL_UNSIGNED_BYTE,
  947. text_data)
  948. # glDisable(GL_BLEND)
  949. def main(model, width, height):
  950. app = PyAssimp3DViewer(model, w=width, h=height)
  951. clock = pygame.time.Clock()
  952. while app.loop():
  953. app.update_view_camera()
  954. ## Main rendering
  955. app.render()
  956. ## GUI text display
  957. app.switch_to_overlay()
  958. app.showtext("Active camera: %s" % str(app.current_cam), 10, app.h - 30)
  959. if app.currently_selected:
  960. app.showtext("Selected node: %s" % app.currently_selected, 10, app.h - 50)
  961. pos = app.h - 70
  962. app.showtext("(%sm, %sm, %sm)" % (app.currently_selected.transformation[0, 3],
  963. app.currently_selected.transformation[1, 3],
  964. app.currently_selected.transformation[2, 3]), 30, pos)
  965. app.switch_from_overlay()
  966. # Make sure we do not go over 30fps
  967. clock.tick(30)
  968. logger.info("Quitting! Bye bye!")
  969. #########################################################################
  970. #########################################################################
  971. if __name__ == '__main__':
  972. if not len(sys.argv) > 1:
  973. print("Usage: " + __file__ + " <model>")
  974. sys.exit(2)
  975. main(model=sys.argv[1], width=1024, height=768)