wolfhece 2.0.14__py3-none-any.whl → 2.0.16__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- wolfhece/PyDraw.py +9 -9
- wolfhece/Results2DGPU.py +5 -1
- wolfhece/opengl/__init__.py +0 -0
- wolfhece/opengl/gl_utils.py +1544 -0
- wolfhece/opengl/py3d.py +1665 -0
- wolfhece/opengl/tile_packer.py +352 -0
- wolfhece/shaders/quad_frag_shader.glsl +6 -2
- wolfhece/shaders/quad_geom_shader.glsl +8 -3
- wolfhece/shaders/quadpos_frag_shader.glsl +12 -0
- wolfhece/shaders/quadpos_geom_shader.glsl +76 -0
- wolfhece/shaders/simple_fragment_shader.glsl +4 -1
- wolfhece/wolf_array.py +9 -5
- wolfhece/wolfresults_2D.py +14 -12
- {wolfhece-2.0.14.dist-info → wolfhece-2.0.16.dist-info}/METADATA +3 -1
- {wolfhece-2.0.14.dist-info → wolfhece-2.0.16.dist-info}/RECORD +18 -12
- {wolfhece-2.0.14.dist-info → wolfhece-2.0.16.dist-info}/WHEEL +0 -0
- {wolfhece-2.0.14.dist-info → wolfhece-2.0.16.dist-info}/entry_points.txt +0 -0
- {wolfhece-2.0.14.dist-info → wolfhece-2.0.16.dist-info}/top_level.txt +0 -0
wolfhece/opengl/py3d.py
ADDED
@@ -0,0 +1,1665 @@
|
|
1
|
+
import wx
|
2
|
+
from wx import glcanvas
|
3
|
+
from OpenGL.GL import *
|
4
|
+
from OpenGL.GLU import *
|
5
|
+
from OpenGL.GLUT import *
|
6
|
+
from pathlib import Path
|
7
|
+
import logging
|
8
|
+
from enum import Enum
|
9
|
+
|
10
|
+
import numpy as np
|
11
|
+
from glm import lookAt, perspective, mat4, vec3, vec4, rotate, cross, dot, ortho, mat4x4, transpose, inverse, normalize, translate
|
12
|
+
|
13
|
+
from ..textpillow import Font_Priority, Text_Image, Text_Infos
|
14
|
+
from ..wolf_texture import Text_Image_Texture
|
15
|
+
|
16
|
+
def print_program_resource_names(program, verbose=False):
|
17
|
+
""" Print the active resources in the program """
|
18
|
+
|
19
|
+
glUseProgram(program)
|
20
|
+
|
21
|
+
# Get the number of active resources
|
22
|
+
num_active_resources = glGetProgramiv(program, GL_ACTIVE_ATTRIBUTES)
|
23
|
+
num_active_uniforms = glGetProgramiv(program, GL_ACTIVE_UNIFORMS)
|
24
|
+
|
25
|
+
if verbose:
|
26
|
+
print(f"Number of active resources: {num_active_resources}")
|
27
|
+
print(f"Number of active uniforms: {num_active_uniforms}")
|
28
|
+
|
29
|
+
logging.info(f"Number of active resources: {num_active_resources}")
|
30
|
+
logging.info(f"Number of active uniforms: {num_active_uniforms}")
|
31
|
+
|
32
|
+
ret={}
|
33
|
+
ret['attributes'] = []
|
34
|
+
ret['uniforms'] = []
|
35
|
+
|
36
|
+
# Iterate through each resource and print its name
|
37
|
+
for i in range(num_active_resources):
|
38
|
+
resource_name = glGetProgramResourceName(program, GL_PROGRAM_INPUT, i, 256) # 256 is the maximum name length
|
39
|
+
|
40
|
+
ressource = ''
|
41
|
+
for i in range(resource_name[0]):
|
42
|
+
ressource += chr(resource_name[1][i])
|
43
|
+
|
44
|
+
if verbose:
|
45
|
+
print(f"Resource {i}: {ressource}")
|
46
|
+
logging.info(f"Resource {i}: {ressource}")
|
47
|
+
|
48
|
+
ret['attributes'].append(ressource)
|
49
|
+
|
50
|
+
|
51
|
+
for i in range(num_active_uniforms):
|
52
|
+
resource_name = glGetProgramResourceName(program, GL_UNIFORM, i, 256)
|
53
|
+
|
54
|
+
ressource = ''
|
55
|
+
for i in range(resource_name[0]):
|
56
|
+
ressource += chr(resource_name[1][i])
|
57
|
+
|
58
|
+
if verbose:
|
59
|
+
print(f"Uniform {i}: {ressource}")
|
60
|
+
logging.info(f"Uniform {i}: {ressource}")
|
61
|
+
|
62
|
+
ret['uniforms'].append(ressource)
|
63
|
+
|
64
|
+
return ret
|
65
|
+
|
66
|
+
|
67
|
+
class TypeOfView(Enum):
|
68
|
+
""" Type of view """
|
69
|
+
PERSPECTIVE = 0
|
70
|
+
ORTHOGRAPHIC = 1
|
71
|
+
ORTHOGRAPHIC_2D = 2
|
72
|
+
class Cache_WolfArray_plot3D():
|
73
|
+
"""
|
74
|
+
Cache for the WolfArray_plot3D class
|
75
|
+
|
76
|
+
A cache is created for each canvas. The cache is responsible for the OpenGL resources associated to context.
|
77
|
+
"""
|
78
|
+
|
79
|
+
def __init__(self,
|
80
|
+
parent:"WolfArray_plot3D",
|
81
|
+
context:glcanvas.GLContext,
|
82
|
+
canvas:"CanvasOGL",
|
83
|
+
idx:int = 0) -> None:
|
84
|
+
|
85
|
+
self.parent = parent
|
86
|
+
self.idx = idx
|
87
|
+
|
88
|
+
self._vao = None
|
89
|
+
self._vbo = None
|
90
|
+
self._colorID = None
|
91
|
+
self._textureID = None
|
92
|
+
self._program = None
|
93
|
+
self._sunintensity = 1.
|
94
|
+
self._sunposition = vec3(0., 0., 1000.)
|
95
|
+
|
96
|
+
self._mvpLoc = None
|
97
|
+
self._dxloc = None
|
98
|
+
self._dyloc = None
|
99
|
+
self._origxloc = None
|
100
|
+
self._origyloc = None
|
101
|
+
self._widthloc = None
|
102
|
+
self._heightloc = None
|
103
|
+
self._zscaleloc = None
|
104
|
+
self._ztextureloc = None
|
105
|
+
self._palloc = None
|
106
|
+
self._colorValuesLoc = None
|
107
|
+
self._sunpositionLoc = None
|
108
|
+
self._sunintensityLoc = None
|
109
|
+
self._palettesizeLoc = None
|
110
|
+
self.idxloc = None
|
111
|
+
|
112
|
+
self.canvas = canvas
|
113
|
+
self.context = context
|
114
|
+
|
115
|
+
self._ztexture = None
|
116
|
+
self._color_palette = None
|
117
|
+
self._color_values = np.zeros(256, dtype=np.float32)
|
118
|
+
|
119
|
+
self.params = None
|
120
|
+
|
121
|
+
@property
|
122
|
+
def sunposition(self):
|
123
|
+
|
124
|
+
return self._sunposition
|
125
|
+
|
126
|
+
@sunposition.setter
|
127
|
+
def sunposition(self, sunposition:vec3):
|
128
|
+
|
129
|
+
self._sunposition = sunposition
|
130
|
+
|
131
|
+
glUseProgram(self._program)
|
132
|
+
glUniform3f(self._sunpositionLoc, sunposition.x, sunposition.y, sunposition.z)
|
133
|
+
glUseProgram(0)
|
134
|
+
|
135
|
+
@property
|
136
|
+
def sunintensity(self):
|
137
|
+
return self._sunintensity
|
138
|
+
|
139
|
+
@sunintensity.setter
|
140
|
+
def sunintensity(self, sunintensity:float):
|
141
|
+
self._sunintensity = sunintensity
|
142
|
+
|
143
|
+
glUseProgram(self._program)
|
144
|
+
glUniform1f(self._sunintensityLoc, sunintensity)
|
145
|
+
glUseProgram(0)
|
146
|
+
|
147
|
+
def initialize_color_palette(self):
|
148
|
+
""" Initialize the color palette """
|
149
|
+
|
150
|
+
if self._color_palette is None:
|
151
|
+
self._color_palette = np.array([0., 0., 0., 1., 1., 1.], dtype=np.float32)
|
152
|
+
|
153
|
+
if self._color_values is None:
|
154
|
+
self._color_values[:2] = np.array([self._ztexture.min(), self._ztexture.max()], dtype=np.float32)
|
155
|
+
|
156
|
+
self._colorID = glGenTextures(1)
|
157
|
+
glBindTexture(GL_TEXTURE_1D, self._colorID)
|
158
|
+
|
159
|
+
glTexImage1D(GL_TEXTURE_1D, 0, GL_RGB, len(self._color_palette)//3 , 0, GL_RGB, GL_FLOAT, self._color_palette.data)
|
160
|
+
|
161
|
+
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
|
162
|
+
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
|
163
|
+
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
|
164
|
+
|
165
|
+
|
166
|
+
def __del__(self):
|
167
|
+
""" release the resources """
|
168
|
+
if self._vao is not None:
|
169
|
+
glDeleteVertexArrays(1, [self._vao])
|
170
|
+
if self._vbo is not None:
|
171
|
+
glDeleteBuffers(1, [self._vbo])
|
172
|
+
if self._colorID is not None:
|
173
|
+
glDeleteTextures(1, [self._colorID])
|
174
|
+
if self._textureID is not None:
|
175
|
+
glDeleteTextures(1, [self._textureID])
|
176
|
+
if self._program is not None:
|
177
|
+
glDeleteProgram(self._program)
|
178
|
+
if self._framebuffer is not None:
|
179
|
+
glDeleteFramebuffers(1, [self._framebuffer])
|
180
|
+
if self._textureout is not None:
|
181
|
+
glDeleteTextures(1, [self._textureout])
|
182
|
+
|
183
|
+
def init_GL(self, quad_centers:np.ndarray, ztexture:np.ndarray):
|
184
|
+
|
185
|
+
self.init_shader()
|
186
|
+
self.loc_uniforms()
|
187
|
+
self.update_ztexture(ztexture)
|
188
|
+
self.initialize_color_palette()
|
189
|
+
self.set_uniforms()
|
190
|
+
|
191
|
+
self.update_quads(quad_centers)
|
192
|
+
|
193
|
+
def init_shader(self):
|
194
|
+
# Compile The Program and shaders
|
195
|
+
|
196
|
+
_vertex_shader = glCreateShader(GL_VERTEX_SHADER)
|
197
|
+
with open(Path(__file__).parent.parent / "shaders/simple_vertex_shader_wo_mvp.glsl") as file:
|
198
|
+
VERTEX_SHADER = file.read()
|
199
|
+
glShaderSource(_vertex_shader, VERTEX_SHADER)
|
200
|
+
glCompileShader(_vertex_shader)
|
201
|
+
|
202
|
+
if glGetShaderiv(_vertex_shader, GL_COMPILE_STATUS, None) == GL_FALSE:
|
203
|
+
info_log = glGetShaderInfoLog(_vertex_shader)
|
204
|
+
print(info_log)
|
205
|
+
glDeleteProgram(_vertex_shader)
|
206
|
+
raise Exception("Can't compile shader on GPU")
|
207
|
+
|
208
|
+
_fragment_shader = glCreateShader(GL_FRAGMENT_SHADER)
|
209
|
+
with open(Path(__file__).parent.parent / "shaders/quad_frag_shader.glsl") as file:
|
210
|
+
FRAGMENT_SHADER = file.read()
|
211
|
+
glShaderSource(_fragment_shader, FRAGMENT_SHADER)
|
212
|
+
glCompileShader(_fragment_shader)
|
213
|
+
|
214
|
+
if glGetShaderiv(_fragment_shader, GL_COMPILE_STATUS, None) == GL_FALSE:
|
215
|
+
info_log = glGetShaderInfoLog(_fragment_shader)
|
216
|
+
print(info_log)
|
217
|
+
glDeleteProgram(_fragment_shader)
|
218
|
+
raise Exception("Can't compile shader on GPU")
|
219
|
+
|
220
|
+
_geometry_shader = glCreateShader(GL_GEOMETRY_SHADER)
|
221
|
+
with open(Path(__file__).parent.parent / "shaders/quad_geom_shader.glsl") as file:
|
222
|
+
GEOMETRY_SHADER = file.read()
|
223
|
+
glShaderSource(_geometry_shader, GEOMETRY_SHADER)
|
224
|
+
glCompileShader(_geometry_shader)
|
225
|
+
|
226
|
+
if glGetShaderiv(_geometry_shader, GL_COMPILE_STATUS, None) == GL_FALSE:
|
227
|
+
info_log = glGetShaderInfoLog(_geometry_shader)
|
228
|
+
print(info_log)
|
229
|
+
glDeleteProgram(_geometry_shader)
|
230
|
+
raise Exception("Can't compile shader on GPU")
|
231
|
+
|
232
|
+
self._program = glCreateProgram()
|
233
|
+
glAttachShader(self._program, _vertex_shader)
|
234
|
+
glAttachShader(self._program, _fragment_shader)
|
235
|
+
glAttachShader(self._program, _geometry_shader)
|
236
|
+
glLinkProgram(self._program)
|
237
|
+
|
238
|
+
# Check if the program is compiled
|
239
|
+
if glGetProgramiv(self._program, GL_LINK_STATUS) == GL_FALSE:
|
240
|
+
info_log = glGetProgramInfoLog(self._program)
|
241
|
+
print(info_log)
|
242
|
+
raise Exception("Can't link shader program")
|
243
|
+
|
244
|
+
glDeleteShader(_vertex_shader)
|
245
|
+
glDeleteShader(_fragment_shader)
|
246
|
+
glDeleteShader(_geometry_shader)
|
247
|
+
|
248
|
+
self.params = print_program_resource_names (self._program)
|
249
|
+
|
250
|
+
def loc_uniforms(self, which_prog = 0):
|
251
|
+
""" Initialize the uniforms """
|
252
|
+
|
253
|
+
program = self._program
|
254
|
+
|
255
|
+
glUseProgram(program)
|
256
|
+
self._mvpLoc = glGetUniformLocation(program, "mvp")
|
257
|
+
|
258
|
+
self._dxloc = glGetUniformLocation(program, "dx")
|
259
|
+
self._dyloc = glGetUniformLocation(program, "dy")
|
260
|
+
|
261
|
+
self._widthloc = glGetUniformLocation(program, "width")
|
262
|
+
self._heightloc = glGetUniformLocation(program, "height")
|
263
|
+
|
264
|
+
self._origxloc = glGetUniformLocation(program, "origx")
|
265
|
+
self._origyloc = glGetUniformLocation(program, "origy")
|
266
|
+
self._zscaleloc = glGetUniformLocation(program, "zScale")
|
267
|
+
self._ztextureloc = glGetUniformLocation(program, "zText")
|
268
|
+
|
269
|
+
self._palloc = glGetUniformLocation(program, "colorPalette")
|
270
|
+
self._colorValuesLoc = glGetUniformLocation(program, "colorValues")
|
271
|
+
|
272
|
+
self._sunpositionLoc = glGetUniformLocation(program, "sunPosition")
|
273
|
+
self._sunintensityLoc = glGetUniformLocation(program, "sunIntensity")
|
274
|
+
|
275
|
+
self._palettesizeLoc = glGetUniformLocation(program, "paletteSize")
|
276
|
+
|
277
|
+
self._idxloc = glGetUniformLocation(program, "idx")
|
278
|
+
|
279
|
+
glUseProgram(0)
|
280
|
+
|
281
|
+
def set_uniforms(self, which_prog = 0):
|
282
|
+
""" Set the uniforms """
|
283
|
+
|
284
|
+
if which_prog == 0:
|
285
|
+
glUseProgram(self._program)
|
286
|
+
else:
|
287
|
+
glUseProgram(self._program_pos)
|
288
|
+
|
289
|
+
glUniform1f(self._dxloc, self.parent.dx)
|
290
|
+
glUniform1f(self._dyloc, self.parent.dy)
|
291
|
+
|
292
|
+
glUniform1i(self._idxloc, self.idx)
|
293
|
+
|
294
|
+
glUniform1f(self._widthloc, self.canvas.width)
|
295
|
+
glUniform1f(self._heightloc, self.canvas.height)
|
296
|
+
|
297
|
+
glUniform1f(self._origxloc, self.parent.origx)
|
298
|
+
glUniform1f(self._origyloc, self.parent.origy)
|
299
|
+
glUniform1f(self._zscaleloc, self.parent.zscale)
|
300
|
+
|
301
|
+
glBindTexture(GL_TEXTURE_RECTANGLE, self._textureID)
|
302
|
+
|
303
|
+
if self._ztexture.flags.f_contiguous:
|
304
|
+
w, h = self._ztexture.shape
|
305
|
+
else:
|
306
|
+
h, w = self._ztexture.shape
|
307
|
+
|
308
|
+
glTexImage2D(GL_TEXTURE_RECTANGLE, 0, GL_R32F, w, h, 0, GL_RED, GL_FLOAT, self._ztexture.data)
|
309
|
+
|
310
|
+
glBindTexture(GL_TEXTURE_RECTANGLE, 0)
|
311
|
+
|
312
|
+
glBindTexture(GL_TEXTURE_1D, self._colorID)
|
313
|
+
glTexImage1D(GL_TEXTURE_1D, 0, GL_RGB, len(self._color_palette)//3 , 0, GL_RGB, GL_FLOAT, self._color_palette.data)
|
314
|
+
glBindTexture(GL_TEXTURE_1D, 0)
|
315
|
+
|
316
|
+
glUniform1i(self._palettesizeLoc, len(self._color_palette)//3)
|
317
|
+
|
318
|
+
glUniform1fv(self._colorValuesLoc, 256, self._color_values)
|
319
|
+
|
320
|
+
glUniform1i(self._palloc, 1)
|
321
|
+
|
322
|
+
glUseProgram(0)
|
323
|
+
|
324
|
+
def update_quads(self, data:np.ndarray):
|
325
|
+
""" Update the buffer with new data """
|
326
|
+
|
327
|
+
if self._vbo is not None:
|
328
|
+
glDeleteBuffers(1, [self._vbo])
|
329
|
+
self._vbo = None
|
330
|
+
|
331
|
+
if self._vao is not None:
|
332
|
+
glDeleteVertexArrays(1, [self._vao])
|
333
|
+
self._vao = None
|
334
|
+
|
335
|
+
if self._vao is None:
|
336
|
+
self._vao = glGenVertexArrays(1)
|
337
|
+
if self._vbo is None:
|
338
|
+
self._vbo = glGenBuffers(1)
|
339
|
+
|
340
|
+
self.quad_centers = data
|
341
|
+
|
342
|
+
glBindVertexArray(self._vao)
|
343
|
+
glEnableVertexAttribArray(0)
|
344
|
+
|
345
|
+
glBindBuffer(GL_ARRAY_BUFFER, self._vbo)
|
346
|
+
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, None)
|
347
|
+
|
348
|
+
glBufferData(GL_ARRAY_BUFFER, data.nbytes, data, GL_STATIC_DRAW)
|
349
|
+
|
350
|
+
glBindVertexArray(0)
|
351
|
+
glBindBuffer(GL_ARRAY_BUFFER, 0)
|
352
|
+
|
353
|
+
def update_mvp(self, mvp:mat4x4):
|
354
|
+
""" Update the model view projection matrix """
|
355
|
+
glUseProgram(self._program)
|
356
|
+
glUniformMatrix4fv(self._mvpLoc, 1, GL_FALSE, mvp)
|
357
|
+
|
358
|
+
glUniform1f(self._widthloc, self.canvas.width)
|
359
|
+
glUniform1f(self._heightloc, self.canvas.height)
|
360
|
+
|
361
|
+
glUseProgram(0)
|
362
|
+
|
363
|
+
def update_ztexture(self, ztexture:np.ndarray):
|
364
|
+
""" Update the ztexture """
|
365
|
+
|
366
|
+
self._ztexture = ztexture
|
367
|
+
|
368
|
+
if self._textureID is not None:
|
369
|
+
glDeleteTextures(1, [self._textureID])
|
370
|
+
|
371
|
+
self._textureID = glGenTextures(1)
|
372
|
+
|
373
|
+
glUseProgram(self._program)
|
374
|
+
glBindTexture(GL_TEXTURE_RECTANGLE, self._textureID)
|
375
|
+
glTexImage2D(GL_TEXTURE_RECTANGLE, 0, GL_R32F, ztexture.shape[1], ztexture.shape[0], 0, GL_RED, GL_FLOAT, ztexture)
|
376
|
+
glBindTexture(GL_TEXTURE_RECTANGLE, 0)
|
377
|
+
glUseProgram(0)
|
378
|
+
|
379
|
+
def update_palette(self, color_palette:np.ndarray, color_values:np.ndarray):
|
380
|
+
""" Update the color palette """
|
381
|
+
|
382
|
+
self._color_palette = color_palette
|
383
|
+
|
384
|
+
self._color_values[:len(color_values)] = color_values
|
385
|
+
|
386
|
+
glUseProgram(self._program)
|
387
|
+
|
388
|
+
if self._colorID is not None:
|
389
|
+
glDeleteTextures(1, [self._colorID])
|
390
|
+
|
391
|
+
self.initialize_color_palette()
|
392
|
+
|
393
|
+
glUniform1fv(self._colorValuesLoc, 256, color_values)
|
394
|
+
glUniform1i(self._palettesizeLoc, len(self._color_palette)//3)
|
395
|
+
|
396
|
+
glUseProgram(0)
|
397
|
+
|
398
|
+
def Draw(self):
|
399
|
+
|
400
|
+
glUseProgram(self._program)
|
401
|
+
|
402
|
+
glBindVertexArray(self._vao)
|
403
|
+
|
404
|
+
glActiveTexture(GL_TEXTURE0)
|
405
|
+
glBindTexture(GL_TEXTURE_RECTANGLE, self._textureID)
|
406
|
+
|
407
|
+
glActiveTexture(GL_TEXTURE1)
|
408
|
+
glBindTexture(GL_TEXTURE_1D, self._colorID)
|
409
|
+
|
410
|
+
glDrawArrays(GL_POINTS, 0, len(self.quad_centers)//2)
|
411
|
+
|
412
|
+
glBindVertexArray(0)
|
413
|
+
glBindBuffer(GL_ARRAY_BUFFER, 0)
|
414
|
+
|
415
|
+
glBindTexture(GL_TEXTURE_RECTANGLE, 0)
|
416
|
+
glBindTexture(GL_TEXTURE_1D, 0)
|
417
|
+
|
418
|
+
glUseProgram(0)
|
419
|
+
|
420
|
+
class WolfArray_plot3D():
|
421
|
+
"""
|
422
|
+
Class to plot data in 3D viewer
|
423
|
+
|
424
|
+
reference for texture 2D : https://registry.khronos.org/OpenGL-Refpages/gl4/html/glTexImage2D.xhtml
|
425
|
+
|
426
|
+
OPENGL
|
427
|
+
------
|
428
|
+
The first element corresponds to the lower left corner of the texture image.
|
429
|
+
Subsequent elements progress left-to-right through the remaining texels in the lowest row of the texture image,
|
430
|
+
and then in successively higher rows of the texture image.
|
431
|
+
The final element corresponds to the upper right corner of the texture image.
|
432
|
+
|
433
|
+
void glTexImage2D( GLenum target,
|
434
|
+
GLint level,
|
435
|
+
GLint internalformat,
|
436
|
+
GLsizei width,
|
437
|
+
GLsizei height,
|
438
|
+
GLint border,
|
439
|
+
GLenum format,
|
440
|
+
GLenum type,
|
441
|
+
const void * data);
|
442
|
+
|
443
|
+
NUMPY
|
444
|
+
-----
|
445
|
+
shape[0] is the number of rows and shape[1] is the number of columns.
|
446
|
+
|
447
|
+
The "data" buffer is row-major order or column-major order, depending on the value of the order parameter.
|
448
|
+
- row-major order : C order
|
449
|
+
- column-major order : 'F' order (Fortran)
|
450
|
+
|
451
|
+
So, in row-major order :
|
452
|
+
- OpenGL Texture width = shape[1]
|
453
|
+
- OpenGL Texture height = shape[0]
|
454
|
+
and in column-major order :
|
455
|
+
- OpenGL Texture width = shape[0]
|
456
|
+
- OpenGL Texture height = shape[1]
|
457
|
+
|
458
|
+
** ++ IMPORTANT **
|
459
|
+
|
460
|
+
We assume that if data is row-major order, the indexing [i,j] is (y, x) and if data is column-major order, the indexing is (x, y)
|
461
|
+
|
462
|
+
Example :
|
463
|
+
- array[m,n] in row-major order is the element at the coordinate (n * dx + dx/2. + origx, m * dy + dy/2. + origy)
|
464
|
+
- array[m,n] in column-major order is the element at the coordinate (m * dx + dx/2. + origx, n * dy + dy/2. + origy)
|
465
|
+
|
466
|
+
So:
|
467
|
+
- the data buffer is the same as it is contiguous in memory. We ** don't need to transpose ** the data buffer.
|
468
|
+
- Transposition is done by changing the indexing convention.
|
469
|
+
- "texture" calls in shaders is the same for both row-major and column-major order.
|
470
|
+
|
471
|
+
** -- IMPORTANT **
|
472
|
+
"""
|
473
|
+
|
474
|
+
def __init__(self,
|
475
|
+
quad_centers:np.ndarray,
|
476
|
+
dx:float = 1., dy:float = 1.,
|
477
|
+
origx:float = 0., origy:float = 0.,
|
478
|
+
zscale:float = 0.0,
|
479
|
+
ztexture:np.ndarray = None,
|
480
|
+
color_palette:np.ndarray = None,
|
481
|
+
color_values:np.ndarray = None) -> None:
|
482
|
+
""" Constructor
|
483
|
+
|
484
|
+
:param quad_centers: The centers of the quads in WORLD space
|
485
|
+
:param dx: The width of the quads in WORLD space
|
486
|
+
:param dy: The height of the quads in WORLD space
|
487
|
+
:param origx: The x origin of the mesh in WORLD space
|
488
|
+
:param origy: The y origin of the mesh in WORLD space
|
489
|
+
:param zscale: The z scale applied to the ztexture
|
490
|
+
:param ztexture: The value used as Z coordinate in shaders in WORLD space
|
491
|
+
:param color_palette: The color palette used to plot the quads
|
492
|
+
:param color_values: The color values used to plot the quads
|
493
|
+
|
494
|
+
|
495
|
+
Position
|
496
|
+
--------
|
497
|
+
Indexing is done in the shader with the following formula :
|
498
|
+
i = (x - origx) / dx
|
499
|
+
j = (y - origy) / dy
|
500
|
+
|
501
|
+
If the data is row-major order, the indexing is (y, x) and if the data is column-major order, the indexing is (x, y).
|
502
|
+
The data buffer in memory must be the same.
|
503
|
+
The user must take care of the indexing convention and is responsible for the correct indexing.
|
504
|
+
|
505
|
+
Palette
|
506
|
+
-------
|
507
|
+
The palette is a 1D texture. The color is interpolated between the colors in the palette.
|
508
|
+
color_palette = [r1, g1, b1, r2, g2, b2, r3, g3, b3, ...] - 3 floats per color RGB (float32)
|
509
|
+
color_values = [v1, v2, v3, ...] - 1 float per value (float32) - max 256 values
|
510
|
+
|
511
|
+
|
512
|
+
"""
|
513
|
+
|
514
|
+
assert color_palette.dtype == np.float32, "Color palette must be np.float32"
|
515
|
+
assert color_values.dtype == np.float32, "Color values must be np.float32"
|
516
|
+
assert ztexture.dtype == np.float32, "Z texture must be np.float32"
|
517
|
+
assert quad_centers.dtype == np.float32, "Quad centers must be np.float32"
|
518
|
+
|
519
|
+
|
520
|
+
self.parents:dict["CanvasOGL", Cache_WolfArray_plot3D] = None
|
521
|
+
self.active_parent:"CanvasOGL" = None
|
522
|
+
|
523
|
+
self.dx = dx
|
524
|
+
self.dy = dy
|
525
|
+
self.origx = origx
|
526
|
+
self.origy = origy
|
527
|
+
self.zscale = zscale
|
528
|
+
self.ztexture = ztexture
|
529
|
+
self.quad_centers = quad_centers
|
530
|
+
|
531
|
+
# palette is shared between all the caches
|
532
|
+
self.color_palette = color_palette
|
533
|
+
self.color_values = color_values
|
534
|
+
|
535
|
+
@property
|
536
|
+
def boundingbox(self):
|
537
|
+
""" Return the bounding box of the quads """
|
538
|
+
quads = self.quad_centers.reshape(-1, 2)
|
539
|
+
return quads[:,0].min(), quads[:,0].max(), quads[:,1].min(), quads[:,1].max()
|
540
|
+
|
541
|
+
@property
|
542
|
+
def cache(self) -> Cache_WolfArray_plot3D:
|
543
|
+
if self.active_parent is None:
|
544
|
+
return None
|
545
|
+
|
546
|
+
return self.parents[self.active_parent]
|
547
|
+
|
548
|
+
def __del__(self):
|
549
|
+
""" Destructor """
|
550
|
+
for parent in self.parents:
|
551
|
+
self.remove_parent(parent)
|
552
|
+
|
553
|
+
def remove_parent(self, parent:"CanvasOGL"):
|
554
|
+
""" Remove the parent from the object """
|
555
|
+
if parent in self.parents:
|
556
|
+
del self.parents[parent]
|
557
|
+
|
558
|
+
def add_parent(self, parent:"CanvasOGL", idx:int=0):
|
559
|
+
""" Add the parent to the object """
|
560
|
+
if self.parents is None:
|
561
|
+
self.parents = {}
|
562
|
+
|
563
|
+
cache = self.parents[parent] = Cache_WolfArray_plot3D(self, parent.context, parent, idx)
|
564
|
+
self.active_parent = parent
|
565
|
+
|
566
|
+
cache.init_GL(self.quad_centers, self.ztexture)
|
567
|
+
|
568
|
+
cache.update_palette(self.color_palette, self.color_values)
|
569
|
+
cache.update_mvp(parent.mvp)
|
570
|
+
cache.sunposition = parent.sunposition
|
571
|
+
cache.sunintensity = parent.sunintensity
|
572
|
+
|
573
|
+
|
574
|
+
def update_mvp(self, mvp:mat4x4):
|
575
|
+
""" Update the model view projection matrix """
|
576
|
+
if self.cache is None:
|
577
|
+
return
|
578
|
+
|
579
|
+
self.cache.update_mvp(mvp)
|
580
|
+
|
581
|
+
def update_ztexture(self, ztexture:np.ndarray):
|
582
|
+
""" Update the ztexture """
|
583
|
+
|
584
|
+
for parent in self.parents:
|
585
|
+
self.parents[parent].update_ztexture(ztexture)
|
586
|
+
|
587
|
+
def update_palette(self, color_palette:np.ndarray, color_values:np.ndarray):
|
588
|
+
""" Update the color palette """
|
589
|
+
|
590
|
+
for parent in self.parents:
|
591
|
+
self.parents[parent].update_palette(color_palette, color_values)
|
592
|
+
|
593
|
+
@property
|
594
|
+
def sunposition(self):
|
595
|
+
|
596
|
+
if self.cache is None:
|
597
|
+
return None
|
598
|
+
|
599
|
+
return self.cache._sunposition
|
600
|
+
|
601
|
+
@sunposition.setter
|
602
|
+
def sunposition(self, sunposition:vec3):
|
603
|
+
|
604
|
+
if self.cache is None:
|
605
|
+
return
|
606
|
+
|
607
|
+
self.cache._sunposition = sunposition
|
608
|
+
|
609
|
+
@property
|
610
|
+
def sunintensity(self):
|
611
|
+
if self.cache is None:
|
612
|
+
return None
|
613
|
+
|
614
|
+
return self.cache._sunintensity
|
615
|
+
|
616
|
+
@sunintensity.setter
|
617
|
+
def sunintensity(self, sunintensity:float):
|
618
|
+
if self.cache is None:
|
619
|
+
return
|
620
|
+
|
621
|
+
self.cache._sunintensity = sunintensity
|
622
|
+
|
623
|
+
def Draw(self):
|
624
|
+
|
625
|
+
if self.cache is None:
|
626
|
+
return
|
627
|
+
|
628
|
+
self.cache.Draw()
|
629
|
+
|
630
|
+
class CanvasOGL(glcanvas.GLCanvas):
|
631
|
+
|
632
|
+
def __init__(self, parent):
|
633
|
+
super(CanvasOGL, self).__init__(parent, -1, size=(640, 480))
|
634
|
+
|
635
|
+
self.parent = parent
|
636
|
+
|
637
|
+
self.arrays:dict[str, WolfArray_plot3D] = {}
|
638
|
+
|
639
|
+
self.background = (0.1, 0.1, 0.1, 1)
|
640
|
+
|
641
|
+
self.init = False
|
642
|
+
|
643
|
+
self.persp_or_ortho = TypeOfView.PERSPECTIVE
|
644
|
+
|
645
|
+
self.context = glcanvas.GLContext(self)
|
646
|
+
self.SetCurrent(self.context)
|
647
|
+
|
648
|
+
self.width, self.height = self.GetClientSize()
|
649
|
+
self.ratio_woverh = self.width / self.height
|
650
|
+
|
651
|
+
self.width_view = 40
|
652
|
+
self.height_view = self.width_view / self.ratio_woverh
|
653
|
+
|
654
|
+
self.eye = vec3(0., 0., 20.)
|
655
|
+
self.center = vec3(0., 0., 0.)
|
656
|
+
self.up = vec3(0., 1., 0.)
|
657
|
+
|
658
|
+
self.left_view = self.center.x - self.width_view / 2.
|
659
|
+
self.right_view = self.center.x + self.width_view / 2.
|
660
|
+
self.bottom_view = self.center.y - self.height_view / 2.
|
661
|
+
self.top_view = self.center.y + self.height_view / 2.
|
662
|
+
|
663
|
+
self.fov = 45
|
664
|
+
self.near = 0.1
|
665
|
+
self.far = 10000
|
666
|
+
|
667
|
+
self._sunintensity = 1.
|
668
|
+
self._sunposition = vec3(1000., 1000., 1000.)
|
669
|
+
|
670
|
+
self.grid = False
|
671
|
+
self.drawposition = False
|
672
|
+
|
673
|
+
self.x_plane = False
|
674
|
+
self.y_plane = False
|
675
|
+
self.z_plane = False
|
676
|
+
self.xy_plane = False
|
677
|
+
self.yz_plane = False
|
678
|
+
self.xz_plane = False
|
679
|
+
|
680
|
+
self.update_view()
|
681
|
+
|
682
|
+
self.translation_speed = .1
|
683
|
+
self.rotation_speed = 0.1
|
684
|
+
self.zoom_speed = 0.05
|
685
|
+
|
686
|
+
# list of moves
|
687
|
+
self._moves:list[mat4x4] = []
|
688
|
+
|
689
|
+
self._program_gizmo = self.init_gizmo_shader()
|
690
|
+
|
691
|
+
# Local variables for mouse events
|
692
|
+
self.mouseLeftDown = False
|
693
|
+
self.mouseRightDown = False
|
694
|
+
self.mouseLeftUp = False
|
695
|
+
self.mouseWheelClick = False
|
696
|
+
self.mouseWheel = 0
|
697
|
+
self.deltaWheel = 0
|
698
|
+
self.mousePos = wx.Point(0, 0)
|
699
|
+
self.mouseStartPos = wx.Point(0, 0)
|
700
|
+
self.oldeye = self.eye
|
701
|
+
|
702
|
+
self._framebuffer = None
|
703
|
+
self._textureout = None
|
704
|
+
self._posout = None
|
705
|
+
|
706
|
+
self.textTodraw = []
|
707
|
+
|
708
|
+
self.Bind(wx.EVT_PAINT, self.OnPaint)
|
709
|
+
self.Bind(wx.EVT_SIZE, self.OnSize)
|
710
|
+
self.Bind(wx.EVT_ERASE_BACKGROUND, lambda event: None)
|
711
|
+
self.Bind(wx.EVT_MOUSEWHEEL, self.OnWheel)
|
712
|
+
self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
|
713
|
+
self.Bind(wx.EVT_RIGHT_DOWN, self.OnRightDown)
|
714
|
+
self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
|
715
|
+
self.Bind(wx.EVT_RIGHT_UP, self.OnRightUp)
|
716
|
+
self.Bind(wx.EVT_MOTION, self.OnMouseMove)
|
717
|
+
self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
|
718
|
+
self.Bind(wx.EVT_KEY_UP, self.OnKeyUp)
|
719
|
+
self.SetFocus()
|
720
|
+
|
721
|
+
def force_view(self, x:float, y:float, z:float):
|
722
|
+
""" Force the view to a specific position """
|
723
|
+
self.center= vec3(x, y, z)
|
724
|
+
self.eye = vec3(x, y, z+50.)
|
725
|
+
self.update_view()
|
726
|
+
self.Refresh()
|
727
|
+
|
728
|
+
def init_gizmo_shader(self):
|
729
|
+
|
730
|
+
self._program_gizmo = glCreateProgram()
|
731
|
+
|
732
|
+
# Compile The Program and shaders
|
733
|
+
vertex_shader = glCreateShader(GL_VERTEX_SHADER)
|
734
|
+
with open(Path(__file__).parent.parent / "shaders/simple_vertex_shader_mvp.glsl") as file:
|
735
|
+
VERTEX_SHADER = file.read()
|
736
|
+
glShaderSource(vertex_shader, VERTEX_SHADER)
|
737
|
+
glCompileShader(vertex_shader)
|
738
|
+
|
739
|
+
if glGetShaderiv(vertex_shader, GL_COMPILE_STATUS, None) == GL_FALSE:
|
740
|
+
info_log = glGetShaderInfoLog(vertex_shader)
|
741
|
+
print(info_log)
|
742
|
+
glDeleteProgram(vertex_shader)
|
743
|
+
raise Exception("Can't compile shader on GPU")
|
744
|
+
|
745
|
+
fragment_shader = glCreateShader(GL_FRAGMENT_SHADER)
|
746
|
+
with open(Path(__file__).parent.parent / "shaders/simple_fragment_shader.glsl") as file:
|
747
|
+
FRAGMENT_SHADER = file.read()
|
748
|
+
glShaderSource(fragment_shader, FRAGMENT_SHADER)
|
749
|
+
glCompileShader(fragment_shader)
|
750
|
+
|
751
|
+
if glGetShaderiv(fragment_shader, GL_COMPILE_STATUS, None) == GL_FALSE:
|
752
|
+
info_log = glGetShaderInfoLog(fragment_shader)
|
753
|
+
print(info_log)
|
754
|
+
glDeleteProgram(fragment_shader)
|
755
|
+
raise Exception("Can't compile shader on GPU")
|
756
|
+
|
757
|
+
glAttachShader(self._program_gizmo, vertex_shader)
|
758
|
+
glAttachShader(self._program_gizmo, fragment_shader)
|
759
|
+
glLinkProgram(self._program_gizmo)
|
760
|
+
|
761
|
+
# Check if the program is compiled
|
762
|
+
if glGetProgramiv(self._program_gizmo, GL_LINK_STATUS) == GL_FALSE:
|
763
|
+
info_log = glGetProgramInfoLog(self._program_gizmo)
|
764
|
+
print(info_log)
|
765
|
+
raise Exception("Can't link shader program")
|
766
|
+
|
767
|
+
glDeleteShader(vertex_shader)
|
768
|
+
glDeleteShader(fragment_shader)
|
769
|
+
|
770
|
+
return self._program_gizmo
|
771
|
+
|
772
|
+
def draw_gizmo(self):
|
773
|
+
|
774
|
+
glUseProgram(self._program_gizmo)
|
775
|
+
|
776
|
+
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
|
777
|
+
|
778
|
+
_mvpLoc = glGetUniformLocation(self._program_gizmo, "mvp")
|
779
|
+
|
780
|
+
glUniformMatrix4fv(_mvpLoc, 1, GL_FALSE, self.mvp)
|
781
|
+
|
782
|
+
self.draw_plane(0., 0., 0., 0., 1., 0., 0., 0., 1., 0., 1., 1., 1., 0., 0.) # Red
|
783
|
+
|
784
|
+
self.draw_plane(0., 0., 0., 1., 0., 0., 0., 0., 1., 1., 0., 1., 0., 1., 0.) # Green
|
785
|
+
|
786
|
+
self.draw_plane(0., 0., 0., 1., 0., 0., 0., 1., 0., 1., 1., 0., 0., 0., 1.) # Blue
|
787
|
+
|
788
|
+
glUseProgram(0)
|
789
|
+
|
790
|
+
def draw_plane(self, a, b, c, d, e, f, g, h, i, j, k, l, colr, colg, colb):
|
791
|
+
|
792
|
+
# Create the vertices of the plane
|
793
|
+
vertices = np.array([a, b, c, d, e, f, g, h, i, j, k, l], dtype=np.float32)
|
794
|
+
colors = np.array([colr, colg, colb, colr, colg, colb, colr, colg, colb, colr, colg, colb], dtype=np.float32)
|
795
|
+
|
796
|
+
# Create the VAO and VBO
|
797
|
+
vao = glGenVertexArrays(1)
|
798
|
+
vbo = glGenBuffers(1)
|
799
|
+
|
800
|
+
glBindVertexArray(vao)
|
801
|
+
glEnableVertexAttribArray(0)
|
802
|
+
|
803
|
+
glBindBuffer(GL_ARRAY_BUFFER, vbo)
|
804
|
+
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, None)
|
805
|
+
|
806
|
+
glBufferData(GL_ARRAY_BUFFER, vertices.nbytes, vertices, GL_STATIC_DRAW)
|
807
|
+
|
808
|
+
glEnableVertexAttribArray(1)
|
809
|
+
|
810
|
+
vbo2 = glGenBuffers(1)
|
811
|
+
glBindBuffer(GL_ARRAY_BUFFER, vbo2)
|
812
|
+
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, None)
|
813
|
+
|
814
|
+
glBufferData(GL_ARRAY_BUFFER, colors.nbytes, colors, GL_STATIC_DRAW)
|
815
|
+
|
816
|
+
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)
|
817
|
+
|
818
|
+
glBindVertexArray(0)
|
819
|
+
glBindBuffer(GL_ARRAY_BUFFER, 0)
|
820
|
+
|
821
|
+
glDeleteVertexArrays(1, [vao])
|
822
|
+
glDeleteBuffers(2, [vbo, vbo2])
|
823
|
+
|
824
|
+
def add_array(self, name:str, array:WolfArray_plot3D):
|
825
|
+
""" Add an array to the canvas """
|
826
|
+
self.SetCurrent(self.context)
|
827
|
+
|
828
|
+
self.arrays[name] = array
|
829
|
+
|
830
|
+
array.add_parent(self, len(self.arrays))
|
831
|
+
|
832
|
+
@property
|
833
|
+
def sunposition(self):
|
834
|
+
return self._sunposition
|
835
|
+
|
836
|
+
@sunposition.setter
|
837
|
+
def sunposition(self, sunposition:vec3):
|
838
|
+
self._sunposition = sunposition
|
839
|
+
for curarray in self.arrays.values():
|
840
|
+
curarray.sunposition = sunposition
|
841
|
+
|
842
|
+
@property
|
843
|
+
def sunintensity(self):
|
844
|
+
return self._sunintensity
|
845
|
+
|
846
|
+
@sunintensity.setter
|
847
|
+
def sunintensity(self, sunintensity:float):
|
848
|
+
self._sunintensity = sunintensity
|
849
|
+
for curarray in self.arrays.values():
|
850
|
+
curarray.sunintensity = sunintensity
|
851
|
+
|
852
|
+
@property
|
853
|
+
def mvp(self):
|
854
|
+
""" Return the model view projection matrix as np.array in column major order """
|
855
|
+
mvp = self.mvp_glm
|
856
|
+
mvp = transpose(mvp)
|
857
|
+
|
858
|
+
return np.asarray(mvp, np.float32, order='F')
|
859
|
+
|
860
|
+
@property
|
861
|
+
def moves_matrix4x4(self):
|
862
|
+
""" Return the list of moves as a list of glm.mat4 """
|
863
|
+
move4x4 = mat4x4(1.)
|
864
|
+
for cur in self._moves:
|
865
|
+
move4x4 = cur * move4x4
|
866
|
+
|
867
|
+
return move4x4
|
868
|
+
|
869
|
+
@property
|
870
|
+
def mvp_glm(self):
|
871
|
+
""" Return the model view projection matrix as glm.mat4 """
|
872
|
+
self.SetCurrent(self.context)
|
873
|
+
|
874
|
+
if self.persp_or_ortho == TypeOfView.ORTHOGRAPHIC:
|
875
|
+
# orthographic projection
|
876
|
+
self.proj = ortho(self.left_view, self.right_view, self.bottom_view, self.top_view, -99999, 99999)
|
877
|
+
# self.mv = lookAt(self.eye, self.center, self.up)
|
878
|
+
self.mv = lookAt(vec3(self.center.x, self.center.y, self.center.z+20.), self.center, (0., 1., 0.))
|
879
|
+
self.mv = self.moves_matrix4x4 * self.mv
|
880
|
+
|
881
|
+
elif self.persp_or_ortho == TypeOfView.ORTHOGRAPHIC_2D:
|
882
|
+
# orthographic projection 2D
|
883
|
+
self.proj = ortho(self.left_view, self.right_view, self.bottom_view, self.top_view, -99999, 99999)
|
884
|
+
self.mv = mat4x4(1.)
|
885
|
+
|
886
|
+
else:
|
887
|
+
# perspective projection
|
888
|
+
self.proj = perspective(self.fov, self.ratio_woverh, self.near, self.far)
|
889
|
+
self.mv = lookAt(self.eye, self.center, self.up)
|
890
|
+
|
891
|
+
mvp = self.proj * self.mv
|
892
|
+
|
893
|
+
return mvp
|
894
|
+
|
895
|
+
@property
|
896
|
+
def right(self):
|
897
|
+
"""Return the right vector of the camera"""
|
898
|
+
right = self._cross(self._normalize(self._direction()), self.up)
|
899
|
+
return self._normalize(right)
|
900
|
+
|
901
|
+
@property
|
902
|
+
def distance(self):
|
903
|
+
"""Return the distance between the eye and the center of the view"""
|
904
|
+
return np.linalg.norm(self.center - self.eye)
|
905
|
+
|
906
|
+
@property
|
907
|
+
def ppi(self):
|
908
|
+
"""Return the pixels per inch of the view"""
|
909
|
+
return self.width / self.width_view
|
910
|
+
|
911
|
+
def ray_pick(self, x, y):
|
912
|
+
""" Get the ray direction from the camera to the mouse position """
|
913
|
+
|
914
|
+
# Get the viewport and projection matrix
|
915
|
+
viewport = glGetIntegerv(GL_VIEWPORT)
|
916
|
+
projection_matrix = self.mvp_glm
|
917
|
+
|
918
|
+
# Normalize the coordinates
|
919
|
+
normalized_x = 2.0 * x / self.width - 1.0
|
920
|
+
normalized_y = 1.0 - 2.0 * y / self.height
|
921
|
+
|
922
|
+
# Create ray direction in view space
|
923
|
+
ray_dir_view = vec3(normalized_x, normalized_y, -1.0)
|
924
|
+
|
925
|
+
# Get the inverse of the projection matrix
|
926
|
+
inv_projection_matrix = inverse(projection_matrix)
|
927
|
+
|
928
|
+
# Transform ray direction to world space
|
929
|
+
ray_dir_world = vec3(inv_projection_matrix * vec4(ray_dir_view, 0.0))
|
930
|
+
|
931
|
+
# Normalize the ray direction
|
932
|
+
ray_dir_world = normalize(ray_dir_world)
|
933
|
+
|
934
|
+
return ray_dir_world
|
935
|
+
|
936
|
+
def intersect_ray_plane(self, ray_direction, plane_point):
|
937
|
+
""" Calculer l'intersection entre un rayon et un plan horizontal """
|
938
|
+
|
939
|
+
# Calculer la distance entre le rayon et le plan
|
940
|
+
numerator = dot(plane_point - self.eye, vec3(0.,0.,1.))
|
941
|
+
denominator = dot(ray_direction, vec3(0.,0.,1.))
|
942
|
+
|
943
|
+
# Vérifier si le rayon est parallèle au plan
|
944
|
+
if abs(denominator) < 1e-6:
|
945
|
+
return None, False # Rayon parallèle au plan, pas d'intersection
|
946
|
+
|
947
|
+
# Calculer la distance le long du rayon jusqu'au point d'intersection
|
948
|
+
t = numerator / denominator
|
949
|
+
|
950
|
+
# Vérifier si le point d'intersection est derrière le rayon d'origine
|
951
|
+
if t < 0:
|
952
|
+
return None, False # Point d'intersection derrière le rayon d'origine
|
953
|
+
|
954
|
+
# Calculer les coordonnées du point d'intersection
|
955
|
+
intersection_point = self.eye + t * ray_direction
|
956
|
+
|
957
|
+
return intersection_point, True
|
958
|
+
|
959
|
+
def intersect_ray_quad(self, ray_direction, quad_lowerleft, quad_upperright):
|
960
|
+
"""Calculer l'intersection entre un rayon et un quad"""
|
961
|
+
|
962
|
+
inter, ok = self.intersect_ray_plane(ray_direction, quad_lowerleft)
|
963
|
+
|
964
|
+
if ok:
|
965
|
+
if inter.x >= quad_lowerleft.x and inter.x <= quad_upperright.x and inter.y >= quad_lowerleft.y and inter.y <= quad_upperright.y:
|
966
|
+
return inter, True
|
967
|
+
else:
|
968
|
+
return None, False
|
969
|
+
else:
|
970
|
+
return None, False
|
971
|
+
|
972
|
+
def update_view(self):
|
973
|
+
|
974
|
+
self.SetCurrent(self.context)
|
975
|
+
|
976
|
+
self.left_view = self.center.x - self.width_view/2
|
977
|
+
self.right_view = self.left_view + self.width_view
|
978
|
+
|
979
|
+
self.bottom_view = self.center.y - self.height_view/2
|
980
|
+
self.top_view = self.bottom_view + self.height_view
|
981
|
+
|
982
|
+
for curarray in self.arrays.values():
|
983
|
+
curarray.update_mvp(self.mvp)
|
984
|
+
|
985
|
+
def _direction(self):
|
986
|
+
return self.center - self.eye
|
987
|
+
|
988
|
+
def _normalize(self, v):
|
989
|
+
return v / np.linalg.norm(v)
|
990
|
+
|
991
|
+
def _cross(self, v1, v2):
|
992
|
+
return cross(v1, v2)
|
993
|
+
|
994
|
+
def _dot(self, v1, v2):
|
995
|
+
return dot(v1, v2)
|
996
|
+
|
997
|
+
def _rotate(self, angle, axis):
|
998
|
+
"""Rotation matrix around axis by angle degrees"""
|
999
|
+
return rotate(angle, axis)
|
1000
|
+
|
1001
|
+
def closer(self, factor=1.):
|
1002
|
+
"""Move the camera closer to the center of the view"""
|
1003
|
+
|
1004
|
+
if self.persp_or_ortho in (TypeOfView.ORTHOGRAPHIC, TypeOfView.ORTHOGRAPHIC_2D):
|
1005
|
+
self.width_view *= 1. - self.zoom_speed * factor
|
1006
|
+
self.height_view = self.width_view / self.ratio_woverh
|
1007
|
+
|
1008
|
+
else:
|
1009
|
+
self.eye = self.center - self._direction() * (1.-self.zoom_speed) * factor
|
1010
|
+
|
1011
|
+
self.update_view()
|
1012
|
+
|
1013
|
+
def further_away(self, factor=1.):
|
1014
|
+
"""Move the camera further away from the center of the view"""
|
1015
|
+
|
1016
|
+
if self.persp_or_ortho in (TypeOfView.ORTHOGRAPHIC, TypeOfView.ORTHOGRAPHIC_2D):
|
1017
|
+
self.width_view /= 1. - self.zoom_speed * factor
|
1018
|
+
self.height_view = self.width_view / self.ratio_woverh
|
1019
|
+
|
1020
|
+
else:
|
1021
|
+
self.eye = self.center - self._direction() * (1.+self.zoom_speed) * factor
|
1022
|
+
|
1023
|
+
self.update_view()
|
1024
|
+
|
1025
|
+
def rotate_up(self, angle):
|
1026
|
+
"""Rotate the camera up by angle degrees"""
|
1027
|
+
|
1028
|
+
direction = self._direction()
|
1029
|
+
direction = self._normalize(direction)
|
1030
|
+
self.up = self._rotate(angle, direction) *self.up
|
1031
|
+
self.up = self._normalize(self.up)
|
1032
|
+
|
1033
|
+
if self.persp_or_ortho == TypeOfView.ORTHOGRAPHIC:
|
1034
|
+
self.add_move_rotation(-angle, self.up, self.center)
|
1035
|
+
|
1036
|
+
def rotate_z_center(self, angle):
|
1037
|
+
""" Rotate the camera around the z axis, passing at center, by angle degrees """
|
1038
|
+
|
1039
|
+
direction = self._direction()
|
1040
|
+
rotation_z = self._rotate(angle, vec3(0.,0.,1.))
|
1041
|
+
self.eye = self.center - rotation_z * direction
|
1042
|
+
self.up = rotation_z * self.up
|
1043
|
+
self.up = self._normalize(self.up)
|
1044
|
+
|
1045
|
+
if self.persp_or_ortho == TypeOfView.ORTHOGRAPHIC:
|
1046
|
+
self.add_move_rotation(-angle, vec3(0.,0.,1.), self.center)
|
1047
|
+
|
1048
|
+
def rotate_x_center(self, angle):
|
1049
|
+
""" Rotate the camera around the x axis, passing at center, by angle degrees """
|
1050
|
+
|
1051
|
+
direction = self._direction()
|
1052
|
+
rotation_x = self._rotate(angle, vec3(1.,0.,0.))
|
1053
|
+
self.eye = self.center - rotation_x * direction
|
1054
|
+
self.up = rotation_x * self.up
|
1055
|
+
self.up = self._normalize(self.up)
|
1056
|
+
|
1057
|
+
if self.persp_or_ortho == TypeOfView.ORTHOGRAPHIC:
|
1058
|
+
self.add_move_rotation(-angle, vec3(1.,0.,0.), self.center)
|
1059
|
+
|
1060
|
+
def rotate_y_center(self, angle):
|
1061
|
+
""" Rotate the camera around the y axis, passing at center, by angle degrees """
|
1062
|
+
|
1063
|
+
direction = self._direction()
|
1064
|
+
rotation_y = self._rotate(angle, vec3(0.,1.,0.))
|
1065
|
+
self.eye = self.center - rotation_y * direction
|
1066
|
+
self.up = rotation_y * self.up
|
1067
|
+
self.up = self._normalize(self.up)
|
1068
|
+
|
1069
|
+
if self.persp_or_ortho == TypeOfView.ORTHOGRAPHIC:
|
1070
|
+
self.add_move_rotation(-angle, vec3(0.,1.,0.), self.center)
|
1071
|
+
|
1072
|
+
def add_move_rotation(self, angle:float, axis:vec3, center:vec3):
|
1073
|
+
""" Add a rotation to the list of moves """
|
1074
|
+
|
1075
|
+
self._moves.append(translate(center) * rotate(angle, axis) * translate(-center))
|
1076
|
+
|
1077
|
+
def add_move_translation(self, translation:vec3):
|
1078
|
+
""" Add a translation to the list of moves """
|
1079
|
+
|
1080
|
+
self._moves.append(translate(translation))
|
1081
|
+
|
1082
|
+
def rotate_right_eye(self, angle):
|
1083
|
+
"""Rotate the camera to the right by angle degrees"""
|
1084
|
+
|
1085
|
+
right = self.right
|
1086
|
+
|
1087
|
+
if self.persp_or_ortho == TypeOfView.ORTHOGRAPHIC:
|
1088
|
+
self.add_move_rotation(-angle, right, self.center)
|
1089
|
+
|
1090
|
+
self.up = self._rotate(angle, right) * self.up
|
1091
|
+
self.up = self._normalize(self.up)
|
1092
|
+
|
1093
|
+
direction = self._cross(self.up, right)
|
1094
|
+
self.center = self.eye + self._normalize(direction) * self.distance
|
1095
|
+
|
1096
|
+
def rotate_right_center(self, angle):
|
1097
|
+
"""Rotate the camera to the right by angle degrees"""
|
1098
|
+
|
1099
|
+
right = self.right
|
1100
|
+
|
1101
|
+
if self.persp_or_ortho == TypeOfView.ORTHOGRAPHIC:
|
1102
|
+
self.add_move_rotation(-angle, right, self.center)
|
1103
|
+
|
1104
|
+
self.up = self._rotate(angle, right) * self.up
|
1105
|
+
self.up = self._normalize(self.up)
|
1106
|
+
|
1107
|
+
direction = self._cross(self.up, right)
|
1108
|
+
self.eye = self.center - self._normalize(direction) * self.distance
|
1109
|
+
|
1110
|
+
def translate(self, amplitude, orient):
|
1111
|
+
"""Translate the camera by amplitude in the direction of the vector orient"""
|
1112
|
+
self.eye += amplitude * orient
|
1113
|
+
self.center += amplitude * orient
|
1114
|
+
self.update_view()
|
1115
|
+
|
1116
|
+
def OnPaint(self, event):
|
1117
|
+
"""Called when the window is exposed."""
|
1118
|
+
dc = wx.PaintDC(self)
|
1119
|
+
self.SetCurrent(self.context)
|
1120
|
+
if not self.init:
|
1121
|
+
self.InitGL()
|
1122
|
+
self.init = True
|
1123
|
+
self.Draw()
|
1124
|
+
|
1125
|
+
def OnSize(self, event):
|
1126
|
+
"""Called when the window is resized"""
|
1127
|
+
self.width, self.height = self.GetClientSize()
|
1128
|
+
|
1129
|
+
glViewport(0, 0, self.width, self.height)
|
1130
|
+
|
1131
|
+
self.ratio_woverh = self.width / self.height
|
1132
|
+
self.height_view = self.width_view / self.ratio_woverh
|
1133
|
+
|
1134
|
+
self.update_view()
|
1135
|
+
self.create_fbo()
|
1136
|
+
self._update_colpos = True
|
1137
|
+
|
1138
|
+
self.Draw()
|
1139
|
+
|
1140
|
+
def OnWheel(self, event:wx.MouseEvent):
|
1141
|
+
""" Called when the mouse wheel is scrolled. """
|
1142
|
+
|
1143
|
+
self.mouseWheelClick = True
|
1144
|
+
self.mouseWheel = event.GetWheelRotation()
|
1145
|
+
self.deltaWheel = event.GetWheelDelta()
|
1146
|
+
|
1147
|
+
ctrldown = event.ControlDown()
|
1148
|
+
|
1149
|
+
if self.mouseWheel > 0:
|
1150
|
+
if ctrldown:
|
1151
|
+
self.closer(self.deltaWheel / 120 * 5)
|
1152
|
+
else:
|
1153
|
+
self.closer(self.deltaWheel / 120)
|
1154
|
+
elif self.mouseWheel < 0:
|
1155
|
+
if ctrldown:
|
1156
|
+
self.further_away(self.deltaWheel / 120 * 5)
|
1157
|
+
else:
|
1158
|
+
self.further_away(self.deltaWheel / 120)
|
1159
|
+
|
1160
|
+
self.Refresh()
|
1161
|
+
|
1162
|
+
def OnLeftDown(self, event:wx.MouseEvent):
|
1163
|
+
""" Called when the left mouse button is pressed."""
|
1164
|
+
self.mouseLeftDown = True
|
1165
|
+
self.mouseStartPos = event.GetPosition()
|
1166
|
+
|
1167
|
+
self.oldeye = self.eye
|
1168
|
+
|
1169
|
+
self.Refresh()
|
1170
|
+
|
1171
|
+
def OnRightDown(self, event:wx.MouseEvent):
|
1172
|
+
""" Called when the right mouse button is pressed."""
|
1173
|
+
self.mouseRightDown = True
|
1174
|
+
mousePos = event.GetPosition()
|
1175
|
+
|
1176
|
+
self.drawposition = not self.drawposition
|
1177
|
+
|
1178
|
+
self.Draw()
|
1179
|
+
|
1180
|
+
locx = mousePos.x
|
1181
|
+
locy = self.height - mousePos.y
|
1182
|
+
|
1183
|
+
logging.info('mouse: {} ; {}'.format(mousePos.x, mousePos.y))
|
1184
|
+
logging.info('indices: {} ; {}'.format(locx, locy))
|
1185
|
+
|
1186
|
+
x,y,idx,alpha = self.picker[locx, locy]
|
1187
|
+
|
1188
|
+
logging.info('coords: {} - {}'.format(x,y))
|
1189
|
+
|
1190
|
+
r,g,b = self.colors[locx, locy]
|
1191
|
+
logging.info('color: {} - {} - {}'.format(r,g,b))
|
1192
|
+
|
1193
|
+
self.drawposition = not self.drawposition
|
1194
|
+
|
1195
|
+
curarray = self.arrays[list(self.arrays.keys())[int(idx)-1]]
|
1196
|
+
i = int(x / curarray.dx)
|
1197
|
+
j = int(y / curarray.dy)
|
1198
|
+
z = curarray.ztexture[i, j]
|
1199
|
+
xyz = self.mvp_glm * vec4(x, y, z, 1.)
|
1200
|
+
|
1201
|
+
self.textTodraw.append(Text_Image_Texture('{z:.3f}', self.parent, Text_Infos(Font_Priority.FONTSIZE, colour=(255,255,255,255)), None, x, y))
|
1202
|
+
|
1203
|
+
self.Refresh()
|
1204
|
+
|
1205
|
+
def OnRightUp(self, event:wx.MouseEvent):
|
1206
|
+
""" Called when the right mouse button is released."""
|
1207
|
+
self.mouseRightDown = False
|
1208
|
+
self.Refresh()
|
1209
|
+
|
1210
|
+
def OnLeftUp(self, event:wx.MouseEvent):
|
1211
|
+
""" Called when the left mouse button is released."""
|
1212
|
+
self.mouseLeftUp = True
|
1213
|
+
self.mouseLeftDown = False
|
1214
|
+
|
1215
|
+
self.Refresh()
|
1216
|
+
|
1217
|
+
def OnMouseMove(self, event:wx.MouseEvent):
|
1218
|
+
""" Called when the mouse is in motion."""
|
1219
|
+
self.mousePos = event.GetPosition()
|
1220
|
+
|
1221
|
+
if self.mouseLeftDown:
|
1222
|
+
self.mouseDelta = (self.mousePos - self.mouseStartPos)
|
1223
|
+
|
1224
|
+
self.translate(-self.mouseDelta.x / self.ppi /2, self.right)
|
1225
|
+
self.translate( self.mouseDelta.y / self.ppi /2, self.up)
|
1226
|
+
|
1227
|
+
self.mouseStartPos = self.mousePos
|
1228
|
+
|
1229
|
+
self.Refresh()
|
1230
|
+
|
1231
|
+
@property
|
1232
|
+
def boundingbox(self):
|
1233
|
+
""" Return the bounding box of the view """
|
1234
|
+
|
1235
|
+
bounds = []
|
1236
|
+
|
1237
|
+
for curarray in self.arrays.values():
|
1238
|
+
bounds.append(curarray.boundingbox)
|
1239
|
+
|
1240
|
+
xmin = min([b[0] for b in bounds])
|
1241
|
+
xmax = max([b[1] for b in bounds])
|
1242
|
+
ymin = min([b[2] for b in bounds])
|
1243
|
+
ymax = max([b[3] for b in bounds])
|
1244
|
+
|
1245
|
+
return xmin, xmax, ymin, ymax
|
1246
|
+
|
1247
|
+
@property
|
1248
|
+
def z_extrema(self):
|
1249
|
+
""" Return the extrema of the ztexture """
|
1250
|
+
zmin = 0
|
1251
|
+
zmax = 0
|
1252
|
+
for curarray in self.arrays.values():
|
1253
|
+
if curarray.ztexture is not None:
|
1254
|
+
zmin = min(zmin, curarray.ztexture.min())
|
1255
|
+
zmax = max(zmax, curarray.ztexture.max())
|
1256
|
+
return zmin, zmax
|
1257
|
+
|
1258
|
+
def autoscale(self):
|
1259
|
+
""" Auto scale the view to fit all the arrays """
|
1260
|
+
xmin, xmax, ymin, ymax = self.boundingbox
|
1261
|
+
zmin, zmax = self.z_extrema
|
1262
|
+
|
1263
|
+
if xmax - xmin > ymax - ymin:
|
1264
|
+
self.width_view = (xmax - xmin)*1.5
|
1265
|
+
self.height_view = self.width_view / self.ratio_woverh
|
1266
|
+
else:
|
1267
|
+
self.height_view = (ymax - ymin)*1.5
|
1268
|
+
self.width_view = self.height_view * self.ratio_woverh
|
1269
|
+
|
1270
|
+
center_x = (xmin + xmax) / 2
|
1271
|
+
center_y = (ymin + ymax) / 2
|
1272
|
+
|
1273
|
+
if self.persp_or_ortho == TypeOfView.ORTHOGRAPHIC:
|
1274
|
+
center_x /= 2.
|
1275
|
+
center_y /= 2.
|
1276
|
+
|
1277
|
+
self.eye = vec3(center_x, center_y, 10000.)
|
1278
|
+
self.center = vec3(center_x, center_y, 0.)
|
1279
|
+
|
1280
|
+
else:
|
1281
|
+
self.eye = vec3(center_x, center_y, zmax + (zmax - zmin) * 2.)
|
1282
|
+
self.center = vec3(center_x, center_y, 0.)
|
1283
|
+
|
1284
|
+
self.up = vec3(0., 1., 0.)
|
1285
|
+
|
1286
|
+
if self.persp_or_ortho == TypeOfView.ORTHOGRAPHIC:
|
1287
|
+
self._moves = []
|
1288
|
+
|
1289
|
+
self.update_view()
|
1290
|
+
|
1291
|
+
def OnKeyDown(self, event):
|
1292
|
+
""" Called when a key is pressed."""
|
1293
|
+
|
1294
|
+
shiftdown = event.ShiftDown()
|
1295
|
+
altdown = event.AltDown()
|
1296
|
+
ctrldown = event.ControlDown()
|
1297
|
+
|
1298
|
+
keycode = event.GetKeyCode()
|
1299
|
+
|
1300
|
+
if ctrldown:
|
1301
|
+
if keycode == wx.WXK_LEFT:
|
1302
|
+
self.rotate_up(self.rotation_speed)
|
1303
|
+
|
1304
|
+
elif keycode == wx.WXK_RIGHT:
|
1305
|
+
self.rotate_up(-self.rotation_speed)
|
1306
|
+
|
1307
|
+
elif keycode == wx.WXK_UP:
|
1308
|
+
self.rotate_right_eye(self.rotation_speed)
|
1309
|
+
|
1310
|
+
elif keycode == wx.WXK_DOWN:
|
1311
|
+
self.rotate_right_eye(-self.rotation_speed)
|
1312
|
+
|
1313
|
+
elif altdown:
|
1314
|
+
if keycode == wx.WXK_LEFT:
|
1315
|
+
self.rotate_z_center(-self.rotation_speed)
|
1316
|
+
|
1317
|
+
elif keycode == wx.WXK_RIGHT:
|
1318
|
+
self.rotate_z_center(self.rotation_speed)
|
1319
|
+
|
1320
|
+
elif keycode == wx.WXK_UP:
|
1321
|
+
self.rotate_right_center(self.rotation_speed)
|
1322
|
+
|
1323
|
+
elif keycode == wx.WXK_DOWN:
|
1324
|
+
self.rotate_right_center(-self.rotation_speed)
|
1325
|
+
|
1326
|
+
else:
|
1327
|
+
|
1328
|
+
if keycode == wx.WXK_LEFT:
|
1329
|
+
if shiftdown:
|
1330
|
+
self.translate(self.translation_speed*10, self.right)
|
1331
|
+
else:
|
1332
|
+
self.translate(self.translation_speed, self.right)
|
1333
|
+
|
1334
|
+
elif keycode == wx.WXK_RIGHT:
|
1335
|
+
if shiftdown:
|
1336
|
+
self.translate(-self.translation_speed*10, self.right)
|
1337
|
+
else:
|
1338
|
+
self.translate(-self.translation_speed, self.right)
|
1339
|
+
|
1340
|
+
elif keycode == wx.WXK_UP:
|
1341
|
+
if shiftdown:
|
1342
|
+
self.translate(self.translation_speed*10, self.up)
|
1343
|
+
else:
|
1344
|
+
self.translate(-self.translation_speed, self.up)
|
1345
|
+
|
1346
|
+
elif keycode == wx.WXK_DOWN:
|
1347
|
+
if shiftdown:
|
1348
|
+
self.translate(-self.translation_speed*10, self.up)
|
1349
|
+
else:
|
1350
|
+
self.translate(self.translation_speed, self.up)
|
1351
|
+
|
1352
|
+
elif keycode == wx.WXK_PAGEUP:
|
1353
|
+
self.translate(-self.translation_speed, self._direction())
|
1354
|
+
|
1355
|
+
elif keycode == wx.WXK_PAGEDOWN:
|
1356
|
+
self.translate(self.translation_speed, self._direction())
|
1357
|
+
|
1358
|
+
elif keycode == ord('G'):
|
1359
|
+
self.grid = not self.grid
|
1360
|
+
|
1361
|
+
elif keycode == wx.WXK_SPACE:
|
1362
|
+
|
1363
|
+
if self.persp_or_ortho == TypeOfView.ORTHOGRAPHIC:
|
1364
|
+
self.persp_or_ortho = TypeOfView.ORTHOGRAPHIC_2D
|
1365
|
+
|
1366
|
+
elif self.persp_or_ortho == TypeOfView.ORTHOGRAPHIC_2D:
|
1367
|
+
self.persp_or_ortho = TypeOfView.PERSPECTIVE
|
1368
|
+
|
1369
|
+
else:
|
1370
|
+
self.persp_or_ortho = TypeOfView.ORTHOGRAPHIC
|
1371
|
+
|
1372
|
+
self.autoscale()
|
1373
|
+
|
1374
|
+
elif keycode == wx.WXK_NUMPAD_ADD or keycode == wx.WXK_ADD or keycode == 61:
|
1375
|
+
self.closer()
|
1376
|
+
|
1377
|
+
elif keycode == wx.WXK_NUMPAD_SUBTRACT or keycode == wx.WXK_SUBTRACT or keycode == 45:
|
1378
|
+
self.further_away()
|
1379
|
+
|
1380
|
+
elif keycode == wx.WXK_NUMPAD0 or keycode == 48 or keycode == wx.WXK_HOME:
|
1381
|
+
|
1382
|
+
self.autoscale()
|
1383
|
+
|
1384
|
+
elif keycode == ord('X'):
|
1385
|
+
self.x_plane = not self.x_plane
|
1386
|
+
self.y_plane = False
|
1387
|
+
self.z_plane = False
|
1388
|
+
self.xy_plane = False
|
1389
|
+
self.yz_plane = False
|
1390
|
+
self.xz_plane = False
|
1391
|
+
|
1392
|
+
elif keycode == ord('Y'):
|
1393
|
+
self.y_plane = not self.y_plane
|
1394
|
+
self.x_plane = False
|
1395
|
+
self.z_plane = False
|
1396
|
+
self.xy_plane = False
|
1397
|
+
self.yz_plane = False
|
1398
|
+
self.xz_plane = False
|
1399
|
+
|
1400
|
+
elif keycode == ord('Z'):
|
1401
|
+
self.z_plane = not self.z_plane
|
1402
|
+
self.x_plane = False
|
1403
|
+
self.y_plane = False
|
1404
|
+
self.xy_plane = False
|
1405
|
+
self.yz_plane = False
|
1406
|
+
self.xz_plane = False
|
1407
|
+
|
1408
|
+
elif keycode == ord('C'):
|
1409
|
+
self.xy_plane = not self.xy_plane
|
1410
|
+
self.x_plane = False
|
1411
|
+
self.y_plane = False
|
1412
|
+
self.z_plane = False
|
1413
|
+
self.yz_plane = False
|
1414
|
+
self.xz_plane = False
|
1415
|
+
|
1416
|
+
elif keycode == ord('V'):
|
1417
|
+
self.yz_plane = not self.yz_plane
|
1418
|
+
self.x_plane = False
|
1419
|
+
self.y_plane = False
|
1420
|
+
self.z_plane = False
|
1421
|
+
self.xy_plane = False
|
1422
|
+
self.xz_plane = False
|
1423
|
+
|
1424
|
+
elif keycode == ord('B'):
|
1425
|
+
self.xz_plane = not self.xz_plane
|
1426
|
+
self.x_plane = False
|
1427
|
+
self.y_plane = False
|
1428
|
+
self.z_plane = False
|
1429
|
+
self.xy_plane = False
|
1430
|
+
self.yz_plane = False
|
1431
|
+
|
1432
|
+
self.update_view()
|
1433
|
+
|
1434
|
+
self.Refresh()
|
1435
|
+
|
1436
|
+
def OnKeyUp(self, event):
|
1437
|
+
self.Refresh()
|
1438
|
+
|
1439
|
+
def InitGL(self):
|
1440
|
+
glClearColor(self.background[0], self.background[1], self.background[2], self.background[3])
|
1441
|
+
glEnable(GL_DEPTH_TEST)
|
1442
|
+
|
1443
|
+
def create_fbo(self):
|
1444
|
+
""" Create a framebuffer object """
|
1445
|
+
|
1446
|
+
if self._framebuffer is not None:
|
1447
|
+
glDeleteFramebuffers(1, [self._framebuffer])
|
1448
|
+
self._framebuffer = None
|
1449
|
+
if self._textureout is not None:
|
1450
|
+
glDeleteTextures(1, [self._textureout])
|
1451
|
+
self._textureout = None
|
1452
|
+
if self._posout is not None:
|
1453
|
+
glDeleteTextures(1, [self._posout])
|
1454
|
+
self._posout = None
|
1455
|
+
|
1456
|
+
self._framebuffer = glGenFramebuffers(1)
|
1457
|
+
glBindFramebuffer(GL_FRAMEBUFFER, self._framebuffer)
|
1458
|
+
|
1459
|
+
self._textureout = glGenTextures(1)
|
1460
|
+
glBindTexture(GL_TEXTURE_2D, self._textureout)
|
1461
|
+
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, self.width, self.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, None)
|
1462
|
+
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, self._textureout, 0)
|
1463
|
+
|
1464
|
+
self._posout = glGenTextures(1)
|
1465
|
+
glBindTexture(GL_TEXTURE_2D, self._posout)
|
1466
|
+
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, self.width, self.height, 0, GL_RGBA, GL_FLOAT, None)
|
1467
|
+
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, self._posout, 0)
|
1468
|
+
|
1469
|
+
glDrawBuffers(2, [GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1])
|
1470
|
+
|
1471
|
+
ret = glCheckFramebufferStatus(GL_FRAMEBUFFER)
|
1472
|
+
if ret != GL_FRAMEBUFFER_COMPLETE:
|
1473
|
+
print(f"Framebuffer is not complete: {ret}")
|
1474
|
+
|
1475
|
+
glBindFramebuffer(GL_FRAMEBUFFER, 0)
|
1476
|
+
|
1477
|
+
def Draw(self):
|
1478
|
+
|
1479
|
+
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
|
1480
|
+
|
1481
|
+
if self.drawposition and self._update_colpos:
|
1482
|
+
glBindFramebuffer(GL_FRAMEBUFFER, self._framebuffer)
|
1483
|
+
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
|
1484
|
+
|
1485
|
+
for curarray in self.arrays.values():
|
1486
|
+
curarray.Draw()
|
1487
|
+
|
1488
|
+
glFinish()
|
1489
|
+
|
1490
|
+
glBindTexture(GL_TEXTURE_2D, self._textureout)
|
1491
|
+
self.colors = np.zeros((self.height, self.width, 3), dtype=np.uint8)
|
1492
|
+
glGetTexImage(GL_TEXTURE_2D, 0, GL_RGB, GL_UNSIGNED_BYTE, self.colors, np.uint8)
|
1493
|
+
self.colors = self.colors.swapaxes(0, 1)
|
1494
|
+
|
1495
|
+
glBindTexture(GL_TEXTURE_2D, self._posout)
|
1496
|
+
self.picker = np.zeros((self.height, self.width, 4), dtype=np.float32)
|
1497
|
+
glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_FLOAT, self.picker, np.float32)
|
1498
|
+
self.picker = self.picker.swapaxes(0, 1)
|
1499
|
+
|
1500
|
+
glBindFramebuffer(GL_FRAMEBUFFER, 0)
|
1501
|
+
|
1502
|
+
else:
|
1503
|
+
|
1504
|
+
glBindFramebuffer(GL_FRAMEBUFFER, 0)
|
1505
|
+
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
|
1506
|
+
|
1507
|
+
if self.grid:
|
1508
|
+
self.draw_gizmo()
|
1509
|
+
|
1510
|
+
for curarray in self.arrays.values():
|
1511
|
+
curarray.Draw()
|
1512
|
+
|
1513
|
+
glMatrixMode(GL_PROJECTION)
|
1514
|
+
glLoadIdentity()
|
1515
|
+
glLoadMatrixf(self.mvp)
|
1516
|
+
|
1517
|
+
glMatrixMode(GL_MODELVIEW)
|
1518
|
+
glLoadIdentity()
|
1519
|
+
|
1520
|
+
for curtext in self.textTodraw:
|
1521
|
+
curtext.paint()
|
1522
|
+
|
1523
|
+
|
1524
|
+
self.SwapBuffers()
|
1525
|
+
|
1526
|
+
def update_palette(self, idx, color_palette, color_values):
|
1527
|
+
""" Update the color palette of the array """
|
1528
|
+
if idx in self.arrays:
|
1529
|
+
self.SetCurrent(self.context)
|
1530
|
+
|
1531
|
+
self.arrays[idx].update_palette(color_palette, color_values)
|
1532
|
+
|
1533
|
+
self.Refresh()
|
1534
|
+
|
1535
|
+
|
1536
|
+
class Wolf_Viewer3D(wx.Frame):
|
1537
|
+
|
1538
|
+
def __init__(self, parent, title):
|
1539
|
+
|
1540
|
+
super(Wolf_Viewer3D, self).__init__(parent, title=title, size=(640, 480))
|
1541
|
+
self.canvas = CanvasOGL(self)
|
1542
|
+
self.Show()
|
1543
|
+
|
1544
|
+
@property
|
1545
|
+
def context(self):
|
1546
|
+
return self.canvas.context
|
1547
|
+
|
1548
|
+
def GetSize(self):
|
1549
|
+
return self.canvas.GetSize()
|
1550
|
+
|
1551
|
+
@property
|
1552
|
+
def xmin(self):
|
1553
|
+
return self.canvas.boundingbox[0]
|
1554
|
+
|
1555
|
+
@property
|
1556
|
+
def xmax(self):
|
1557
|
+
return self.canvas.boundingbox[1]
|
1558
|
+
|
1559
|
+
@property
|
1560
|
+
def ymin(self):
|
1561
|
+
return self.canvas.boundingbox[2]
|
1562
|
+
|
1563
|
+
@property
|
1564
|
+
def ymax(self):
|
1565
|
+
return self.canvas.boundingbox[3]
|
1566
|
+
|
1567
|
+
def add_array(self, name:str, array:WolfArray_plot3D):
|
1568
|
+
""" Add an array to the canvas """
|
1569
|
+
self.canvas.add_array(name, array)
|
1570
|
+
|
1571
|
+
def force_view(self, x, y, z):
|
1572
|
+
""" Force the view to the specified coordinates """
|
1573
|
+
self.canvas.force_view(x, y, z)
|
1574
|
+
|
1575
|
+
def autoscale(self):
|
1576
|
+
""" Auto scale the view to fit all the arrays """
|
1577
|
+
self.canvas.autoscale()
|
1578
|
+
|
1579
|
+
def update_palette(self, idx, color_palette, color_values):
|
1580
|
+
""" Update the color palette of the array """
|
1581
|
+
self.canvas.update_palette(idx, color_palette, color_values)
|
1582
|
+
|
1583
|
+
def main_test():
|
1584
|
+
"""
|
1585
|
+
Test the Wolf_Viewer3D class
|
1586
|
+
|
1587
|
+
2 arrays can be added to the canvas. The first one is in row major order and the second one is in column major order.
|
1588
|
+
The plt must be the same for both arrays.
|
1589
|
+
"""
|
1590
|
+
|
1591
|
+
origx = origy = 1000.
|
1592
|
+
dx = dy = 1.
|
1593
|
+
|
1594
|
+
app = wx.App()
|
1595
|
+
frame1 = Wolf_Viewer3D(None, "3D - row major order")
|
1596
|
+
frame2 = Wolf_Viewer3D(None, "3D - column major order")
|
1597
|
+
|
1598
|
+
# Array in row major order
|
1599
|
+
points = np.asarray([1.5,1.5, 2.5,2.5, 2.5,3.5, 7.5,7.5, 1.5,2.5, 3.5,2.5, 2.5,1.5], dtype=np.float32)
|
1600
|
+
points += origx
|
1601
|
+
zvalues = np.zeros((10, 20), dtype=np.float32, order='C')
|
1602
|
+
|
1603
|
+
zvalues[1,1] = 1.
|
1604
|
+
zvalues[2,2] = 2.
|
1605
|
+
zvalues[7,7] = 3.
|
1606
|
+
zvalues[2,1] = 4.
|
1607
|
+
zvalues[3,2] = 5.
|
1608
|
+
zvalues[2,3] = 6.
|
1609
|
+
|
1610
|
+
colors = np.array([1., 0., 1., 0., 0., 1.], dtype=np.float32)
|
1611
|
+
|
1612
|
+
myarray = WolfArray_plot3D(points,
|
1613
|
+
dx = dx, dy = dy,
|
1614
|
+
origx = origx, origy = origy,
|
1615
|
+
zscale = 1.,
|
1616
|
+
ztexture = zvalues,
|
1617
|
+
color_palette = colors,
|
1618
|
+
color_values = np.array([0., 4.], dtype=np.float32))
|
1619
|
+
|
1620
|
+
frame1.add_array("array", myarray)
|
1621
|
+
|
1622
|
+
# Array in column major order
|
1623
|
+
points2 = np.asarray([1.5,1.5, 2.5,2.5, 2.5,3.5, 7.5,7.5, 1.5,2.5, 3.5,2.5, 2.5,1.5], dtype=np.float32)
|
1624
|
+
points2 += origx
|
1625
|
+
zvalues_2 = np.zeros((20, 10), dtype=np.float32, order='F')
|
1626
|
+
|
1627
|
+
zvalues_2[1,1] = 1.
|
1628
|
+
zvalues_2[2,2] = 2.
|
1629
|
+
zvalues_2[7,7] = 3.
|
1630
|
+
zvalues_2[1,2] = 4.
|
1631
|
+
zvalues_2[2,3] = 5.
|
1632
|
+
zvalues_2[3,2] = 6.
|
1633
|
+
|
1634
|
+
colors2 = np.array([1., 0., 1., 0., 0., 1.], dtype=np.float32)
|
1635
|
+
|
1636
|
+
flat_zvalues = zvalues.flatten(order='K').copy()
|
1637
|
+
flat_zvalues2 = zvalues_2.flatten(order='K').copy()
|
1638
|
+
|
1639
|
+
assert zvalues.tobytes(order='A') == zvalues_2.tobytes(order='A')
|
1640
|
+
|
1641
|
+
assert flat_zvalues.shape == flat_zvalues2.shape
|
1642
|
+
assert len(flat_zvalues) == len(flat_zvalues2)
|
1643
|
+
assert len(flat_zvalues) == 200
|
1644
|
+
assert flat_zvalues[21] == 1.
|
1645
|
+
assert flat_zvalues[42] == 2.
|
1646
|
+
assert flat_zvalues[147] == 3.
|
1647
|
+
assert flat_zvalues[41] == 4.
|
1648
|
+
assert flat_zvalues[62] == 5.
|
1649
|
+
assert flat_zvalues[43] == 6.
|
1650
|
+
assert np.all(flat_zvalues == flat_zvalues2)
|
1651
|
+
|
1652
|
+
myarray2 = WolfArray_plot3D(points2,
|
1653
|
+
dx = dx, dy = dy,
|
1654
|
+
origx = origx, origy = origy,
|
1655
|
+
zscale = 1.,
|
1656
|
+
ztexture = zvalues_2,
|
1657
|
+
color_palette = colors2,
|
1658
|
+
color_values = np.array([0., 4.], dtype=np.float32))
|
1659
|
+
|
1660
|
+
frame2.add_array("array2", myarray2)
|
1661
|
+
|
1662
|
+
app.MainLoop()
|
1663
|
+
|
1664
|
+
if __name__ == "__main__":
|
1665
|
+
main_test()
|