3d_viewer_py3.py 41 KB

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