wolfhece 2.0.13__py3-none-any.whl → 2.0.15__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.
@@ -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()