q3dviewer 1.2.2__py3-none-any.whl → 1.2.4__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.
- q3dviewer/__init__.py +1 -10
- q3dviewer/custom_items/__init__.py +2 -0
- q3dviewer/custom_items/mesh_item.py +275 -213
- q3dviewer/custom_items/static_mesh_item.py +330 -0
- q3dviewer/glwidget.py +26 -1
- q3dviewer/shaders/cloud_vert.glsl +1 -1
- q3dviewer/shaders/mesh_geom.glsl +83 -0
- q3dviewer/shaders/mesh_vert.glsl +28 -56
- q3dviewer/shaders/triangle_mesh_geom.glsl +66 -0
- q3dviewer/shaders/triangle_mesh_vert.glsl +31 -0
- q3dviewer/tools/cloud_viewer.py +67 -57
- q3dviewer/tools/film_maker.py +7 -8
- q3dviewer/utils/cloud_io.py +46 -19
- q3dviewer/utils/helpers.py +12 -0
- {q3dviewer-1.2.2.dist-info → q3dviewer-1.2.4.dist-info}/METADATA +24 -12
- {q3dviewer-1.2.2.dist-info → q3dviewer-1.2.4.dist-info}/RECORD +20 -16
- {q3dviewer-1.2.2.dist-info → q3dviewer-1.2.4.dist-info}/LICENSE +0 -0
- {q3dviewer-1.2.2.dist-info → q3dviewer-1.2.4.dist-info}/WHEEL +0 -0
- {q3dviewer-1.2.2.dist-info → q3dviewer-1.2.4.dist-info}/entry_points.txt +0 -0
- {q3dviewer-1.2.2.dist-info → q3dviewer-1.2.4.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Copyright 2024 Panasonic Advanced Technology Development Co.,Ltd. (Liu Yang)
|
|
5
|
+
Distributed under MIT license. See LICENSE for more information.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
from q3dviewer.base_item import BaseItem
|
|
10
|
+
from OpenGL.GL import *
|
|
11
|
+
from OpenGL.GL import shaders
|
|
12
|
+
from q3dviewer.Qt.QtWidgets import QLabel, QCheckBox, QDoubleSpinBox, QSlider, QHBoxLayout, QLineEdit
|
|
13
|
+
|
|
14
|
+
import os
|
|
15
|
+
from q3dviewer.utils import set_uniform, text_to_rgba
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class StaticMeshItem(BaseItem):
|
|
19
|
+
"""
|
|
20
|
+
A OpenGL mesh item for rendering static 3D triangular meshes.
|
|
21
|
+
Optimized for static geometry with triangle-only rendering.
|
|
22
|
+
Data format: Nx9 numpy array (3 vertices per triangle, no good flag needed)
|
|
23
|
+
|
|
24
|
+
Attributes:
|
|
25
|
+
color (str or tuple): Accepts any valid matplotlib color (e.g., 'red', '#FF4500', (1.0, 0.5, 0.0)).
|
|
26
|
+
wireframe (bool): If True, renders the mesh in wireframe mode.
|
|
27
|
+
"""
|
|
28
|
+
def __init__(self, color='lightblue', wireframe=False):
|
|
29
|
+
super(StaticMeshItem, self).__init__()
|
|
30
|
+
self.wireframe = wireframe
|
|
31
|
+
self.color = color
|
|
32
|
+
self.flat_rgb = text_to_rgba(color, flat=True)
|
|
33
|
+
|
|
34
|
+
# Static mesh buffer: N x 9 numpy array
|
|
35
|
+
# Each row: [v0.x, v0.y, v0.z, v1.x, v1.y, v1.z, v2.x, v2.y, v2.z]
|
|
36
|
+
self.vertices = None
|
|
37
|
+
self.num_triangles = 0
|
|
38
|
+
|
|
39
|
+
# OpenGL objects
|
|
40
|
+
self.vao = None
|
|
41
|
+
self.vbo = None
|
|
42
|
+
self.program = None
|
|
43
|
+
|
|
44
|
+
# Fixed rendering parameters
|
|
45
|
+
self.enable_lighting = True
|
|
46
|
+
self.line_width = 1.0
|
|
47
|
+
self.light_color = [1.0, 1.0, 1.0]
|
|
48
|
+
self.ambient_strength = 0.1
|
|
49
|
+
self.diffuse_strength = 1.2
|
|
50
|
+
self.specular_strength = 0.1
|
|
51
|
+
self.shininess = 32.0
|
|
52
|
+
self.alpha = 1.0
|
|
53
|
+
|
|
54
|
+
# Settings flag
|
|
55
|
+
self.need_update_setting = True
|
|
56
|
+
self.need_update_buffer = True
|
|
57
|
+
self.path = os.path.dirname(__file__)
|
|
58
|
+
|
|
59
|
+
def add_setting(self, layout):
|
|
60
|
+
"""Add UI controls for mesh visualization"""
|
|
61
|
+
# Wireframe toggle
|
|
62
|
+
self.wireframe_box = QCheckBox("Wireframe Mode")
|
|
63
|
+
self.wireframe_box.setChecked(self.wireframe)
|
|
64
|
+
self.wireframe_box.toggled.connect(self.update_wireframe)
|
|
65
|
+
layout.addWidget(self.wireframe_box)
|
|
66
|
+
|
|
67
|
+
# Enable lighting toggle
|
|
68
|
+
self.lighting_box = QCheckBox("Enable Lighting")
|
|
69
|
+
self.lighting_box.setChecked(self.enable_lighting)
|
|
70
|
+
self.lighting_box.toggled.connect(self.update_enable_lighting)
|
|
71
|
+
layout.addWidget(self.lighting_box)
|
|
72
|
+
|
|
73
|
+
# Color setting
|
|
74
|
+
label_rgb = QLabel("Color:")
|
|
75
|
+
label_rgb.setToolTip("Use hex color, i.e. #FF4500, or named color, i.e. 'red'")
|
|
76
|
+
layout.addWidget(label_rgb)
|
|
77
|
+
self.edit_rgb = QLineEdit()
|
|
78
|
+
self.edit_rgb.setToolTip("Use hex color, i.e. #FF4500, or named color, i.e. 'red'")
|
|
79
|
+
self.edit_rgb.setText(self.color)
|
|
80
|
+
self.edit_rgb.textChanged.connect(self._on_color)
|
|
81
|
+
layout.addWidget(self.edit_rgb)
|
|
82
|
+
|
|
83
|
+
# Material property controls for Phong lighting
|
|
84
|
+
if self.enable_lighting:
|
|
85
|
+
# Ambient strength control
|
|
86
|
+
ambient_layout = QHBoxLayout()
|
|
87
|
+
ambient_label = QLabel("Ambient Strength:")
|
|
88
|
+
ambient_layout.addWidget(ambient_label)
|
|
89
|
+
self.ambient_slider = QSlider()
|
|
90
|
+
self.ambient_slider.setOrientation(1) # Qt.Horizontal
|
|
91
|
+
self.ambient_slider.setRange(0, 100)
|
|
92
|
+
self.ambient_slider.setValue(int(self.ambient_strength * 100))
|
|
93
|
+
self.ambient_slider.valueChanged.connect(lambda v: self.update_ambient_strength(v / 100.0))
|
|
94
|
+
ambient_layout.addWidget(self.ambient_slider)
|
|
95
|
+
layout.addLayout(ambient_layout)
|
|
96
|
+
|
|
97
|
+
# Diffuse strength control
|
|
98
|
+
diffuse_layout = QHBoxLayout()
|
|
99
|
+
diffuse_label = QLabel("Diffuse Strength:")
|
|
100
|
+
diffuse_layout.addWidget(diffuse_label)
|
|
101
|
+
self.diffuse_slider = QSlider()
|
|
102
|
+
self.diffuse_slider.setOrientation(1)
|
|
103
|
+
self.diffuse_slider.setRange(0, 200)
|
|
104
|
+
self.diffuse_slider.setValue(int(self.diffuse_strength * 100))
|
|
105
|
+
self.diffuse_slider.valueChanged.connect(lambda v: self.update_diffuse_strength(v / 100.0))
|
|
106
|
+
diffuse_layout.addWidget(self.diffuse_slider)
|
|
107
|
+
layout.addLayout(diffuse_layout)
|
|
108
|
+
|
|
109
|
+
# Specular strength control
|
|
110
|
+
specular_layout = QHBoxLayout()
|
|
111
|
+
specular_label = QLabel("Specular Strength:")
|
|
112
|
+
specular_layout.addWidget(specular_label)
|
|
113
|
+
self.specular_slider = QSlider()
|
|
114
|
+
self.specular_slider.setOrientation(1)
|
|
115
|
+
self.specular_slider.setRange(0, 100)
|
|
116
|
+
self.specular_slider.setValue(int(self.specular_strength * 100))
|
|
117
|
+
self.specular_slider.valueChanged.connect(lambda v: self.update_specular_strength(v / 100.0))
|
|
118
|
+
specular_layout.addWidget(self.specular_slider)
|
|
119
|
+
layout.addLayout(specular_layout)
|
|
120
|
+
|
|
121
|
+
# Shininess control
|
|
122
|
+
shininess_layout = QHBoxLayout()
|
|
123
|
+
shininess_label = QLabel("Shininess:")
|
|
124
|
+
shininess_layout.addWidget(shininess_label)
|
|
125
|
+
self.shininess_slider = QSlider()
|
|
126
|
+
self.shininess_slider.setOrientation(1)
|
|
127
|
+
self.shininess_slider.setRange(1, 256)
|
|
128
|
+
self.shininess_slider.setValue(int(self.shininess))
|
|
129
|
+
self.shininess_slider.valueChanged.connect(lambda v: self.update_shininess(float(v)))
|
|
130
|
+
shininess_layout.addWidget(self.shininess_slider)
|
|
131
|
+
layout.addLayout(shininess_layout)
|
|
132
|
+
|
|
133
|
+
def _on_color(self, color):
|
|
134
|
+
try:
|
|
135
|
+
self.color = color
|
|
136
|
+
self.flat_rgb = text_to_rgba(color, flat=True)
|
|
137
|
+
self.need_update_setting = True
|
|
138
|
+
except ValueError:
|
|
139
|
+
pass
|
|
140
|
+
|
|
141
|
+
def update_wireframe(self, value):
|
|
142
|
+
self.wireframe = value
|
|
143
|
+
|
|
144
|
+
def update_enable_lighting(self, value):
|
|
145
|
+
self.enable_lighting = value
|
|
146
|
+
self.need_update_setting = True
|
|
147
|
+
|
|
148
|
+
def update_line_width(self, value):
|
|
149
|
+
self.line_width = value
|
|
150
|
+
self.need_update_setting = True
|
|
151
|
+
|
|
152
|
+
def update_ambient_strength(self, value):
|
|
153
|
+
self.ambient_strength = value
|
|
154
|
+
self.need_update_setting = True
|
|
155
|
+
|
|
156
|
+
def update_diffuse_strength(self, value):
|
|
157
|
+
self.diffuse_strength = value
|
|
158
|
+
self.need_update_setting = True
|
|
159
|
+
|
|
160
|
+
def update_specular_strength(self, value):
|
|
161
|
+
self.specular_strength = value
|
|
162
|
+
self.need_update_setting = True
|
|
163
|
+
|
|
164
|
+
def update_shininess(self, value):
|
|
165
|
+
self.shininess = value
|
|
166
|
+
self.need_update_setting = True
|
|
167
|
+
|
|
168
|
+
def update_alpha(self, value):
|
|
169
|
+
"""Update mesh alpha (opacity)"""
|
|
170
|
+
self.alpha = float(value)
|
|
171
|
+
self.need_update_setting = True
|
|
172
|
+
|
|
173
|
+
def set_data(self, data):
|
|
174
|
+
"""
|
|
175
|
+
Set complete mesh data at once.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
data: Nx9 numpy array where N is the number of triangles
|
|
179
|
+
Each row: [v0.x, v0.y, v0.z, v1.x, v1.y, v1.z, v2.x, v2.y, v2.z]
|
|
180
|
+
"""
|
|
181
|
+
print("Setting static mesh data with {} triangles".format(len(data)))
|
|
182
|
+
if not isinstance(data, np.ndarray):
|
|
183
|
+
raise ValueError("Data must be a numpy array")
|
|
184
|
+
|
|
185
|
+
# Check shape
|
|
186
|
+
if data.ndim != 2 or data.shape[1] != 9:
|
|
187
|
+
# Try to reshape if it's Nx3 (vertex list)
|
|
188
|
+
if data.ndim == 2 and data.shape[1] == 3 and data.shape[0] % 3 == 0:
|
|
189
|
+
data = data.reshape(-1, 9)
|
|
190
|
+
else:
|
|
191
|
+
raise ValueError(f"Invalid data shape {data.shape}. Expected Nx9 or (N*3)x3")
|
|
192
|
+
|
|
193
|
+
self.vertices = data.astype(np.float32)
|
|
194
|
+
self.num_triangles = len(self.vertices)
|
|
195
|
+
self.need_update_buffer = True
|
|
196
|
+
|
|
197
|
+
def clear_mesh(self):
|
|
198
|
+
"""Clear all mesh data"""
|
|
199
|
+
self.vertices = None
|
|
200
|
+
self.num_triangles = 0
|
|
201
|
+
self.need_update_buffer = True
|
|
202
|
+
|
|
203
|
+
def initialize_gl(self):
|
|
204
|
+
"""OpenGL initialization - load triangle shader"""
|
|
205
|
+
frag_shader = open(self.path + '/../shaders/mesh_frag.glsl', 'r').read()
|
|
206
|
+
|
|
207
|
+
try:
|
|
208
|
+
vert_shader = open(self.path + '/../shaders/triangle_mesh_vert.glsl', 'r').read()
|
|
209
|
+
geom_shader = open(self.path + '/../shaders/triangle_mesh_geom.glsl', 'r').read()
|
|
210
|
+
self.program = shaders.compileProgram(
|
|
211
|
+
shaders.compileShader(vert_shader, GL_VERTEX_SHADER),
|
|
212
|
+
shaders.compileShader(geom_shader, GL_GEOMETRY_SHADER),
|
|
213
|
+
shaders.compileShader(frag_shader, GL_FRAGMENT_SHADER),
|
|
214
|
+
)
|
|
215
|
+
except Exception as e:
|
|
216
|
+
print(f"Error compiling static mesh shader: {e}")
|
|
217
|
+
raise
|
|
218
|
+
|
|
219
|
+
def update_render_buffer(self):
|
|
220
|
+
"""
|
|
221
|
+
Update GPU buffer with triangle data.
|
|
222
|
+
Each triangle: 9 floats (3 vertices x 3 coordinates)
|
|
223
|
+
"""
|
|
224
|
+
if self.num_triangles == 0 or self.vertices is None:
|
|
225
|
+
return
|
|
226
|
+
|
|
227
|
+
# Initialize buffers on first call
|
|
228
|
+
if self.vao is None:
|
|
229
|
+
self.vao = glGenVertexArrays(1)
|
|
230
|
+
self.vbo = glGenBuffers(1)
|
|
231
|
+
|
|
232
|
+
if not self.need_update_buffer:
|
|
233
|
+
return
|
|
234
|
+
|
|
235
|
+
glBindVertexArray(self.vao)
|
|
236
|
+
glBindBuffer(GL_ARRAY_BUFFER, self.vbo)
|
|
237
|
+
|
|
238
|
+
# Upload all triangle data
|
|
239
|
+
glBufferData(GL_ARRAY_BUFFER,
|
|
240
|
+
self.vertices.nbytes,
|
|
241
|
+
self.vertices,
|
|
242
|
+
GL_STATIC_DRAW)
|
|
243
|
+
|
|
244
|
+
# Setup vertex attributes
|
|
245
|
+
# Triangle: [v0.x, v0.y, v0.z, v1.x, v1.y, v1.z, v2.x, v2.y, v2.z]
|
|
246
|
+
# 9 floats = 36 bytes stride
|
|
247
|
+
stride = 36
|
|
248
|
+
|
|
249
|
+
# v0 (location 1) - vec3
|
|
250
|
+
glEnableVertexAttribArray(1)
|
|
251
|
+
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, stride, ctypes.c_void_p(0))
|
|
252
|
+
glVertexAttribDivisor(1, 1)
|
|
253
|
+
|
|
254
|
+
# v1 (location 2) - vec3
|
|
255
|
+
glEnableVertexAttribArray(2)
|
|
256
|
+
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, stride, ctypes.c_void_p(12))
|
|
257
|
+
glVertexAttribDivisor(2, 1)
|
|
258
|
+
|
|
259
|
+
# v2 (location 3) - vec3
|
|
260
|
+
glEnableVertexAttribArray(3)
|
|
261
|
+
glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, stride, ctypes.c_void_p(24))
|
|
262
|
+
glVertexAttribDivisor(3, 1)
|
|
263
|
+
|
|
264
|
+
glBindVertexArray(0)
|
|
265
|
+
glBindBuffer(GL_ARRAY_BUFFER, 0)
|
|
266
|
+
self.need_update_buffer = False
|
|
267
|
+
|
|
268
|
+
def update_setting(self):
|
|
269
|
+
"""Set rendering parameters"""
|
|
270
|
+
if not self.need_update_setting:
|
|
271
|
+
return
|
|
272
|
+
|
|
273
|
+
set_uniform(self.program, int(self.enable_lighting), 'if_light')
|
|
274
|
+
set_uniform(self.program, 1, 'two_sided')
|
|
275
|
+
set_uniform(self.program, np.array(self.light_color, dtype=np.float32), 'light_color')
|
|
276
|
+
set_uniform(self.program, float(self.ambient_strength), 'ambient_strength')
|
|
277
|
+
set_uniform(self.program, float(self.diffuse_strength), 'diffuse_strength')
|
|
278
|
+
set_uniform(self.program, float(self.specular_strength), 'specular_strength')
|
|
279
|
+
set_uniform(self.program, float(self.shininess), 'shininess')
|
|
280
|
+
set_uniform(self.program, float(self.alpha), 'alpha')
|
|
281
|
+
set_uniform(self.program, int(self.flat_rgb), 'flat_rgb')
|
|
282
|
+
self.need_update_setting = False
|
|
283
|
+
|
|
284
|
+
def paint(self):
|
|
285
|
+
"""
|
|
286
|
+
Render the static mesh using instanced rendering with geometry shader.
|
|
287
|
+
Each triangle instance is rendered as a point, geometry shader generates 1 triangle.
|
|
288
|
+
"""
|
|
289
|
+
if self.num_triangles == 0 or self.vertices is None:
|
|
290
|
+
return
|
|
291
|
+
|
|
292
|
+
glUseProgram(self.program)
|
|
293
|
+
|
|
294
|
+
self.update_render_buffer()
|
|
295
|
+
self.update_setting()
|
|
296
|
+
|
|
297
|
+
view_matrix = self.glwidget().view_matrix
|
|
298
|
+
set_uniform(self.program, view_matrix, 'view')
|
|
299
|
+
project_matrix = self.glwidget().projection_matrix
|
|
300
|
+
set_uniform(self.program, project_matrix, 'projection')
|
|
301
|
+
view_pos = self.glwidget().center
|
|
302
|
+
set_uniform(self.program, np.array(view_pos), 'view_pos')
|
|
303
|
+
|
|
304
|
+
# Enable blending and depth testing
|
|
305
|
+
glEnable(GL_BLEND)
|
|
306
|
+
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
|
|
307
|
+
glEnable(GL_DEPTH_TEST)
|
|
308
|
+
glDisable(GL_CULL_FACE) # two-sided rendering
|
|
309
|
+
|
|
310
|
+
# Set line width
|
|
311
|
+
glLineWidth(self.line_width)
|
|
312
|
+
|
|
313
|
+
# Bind VAO
|
|
314
|
+
glBindVertexArray(self.vao)
|
|
315
|
+
|
|
316
|
+
if self.wireframe:
|
|
317
|
+
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
|
|
318
|
+
else:
|
|
319
|
+
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
|
|
320
|
+
|
|
321
|
+
# Draw using instanced rendering
|
|
322
|
+
# Input: POINTS (one per triangle instance)
|
|
323
|
+
# Geometry shader generates 1 triangle (3 vertices) per point
|
|
324
|
+
glDrawArraysInstanced(GL_POINTS, 0, 1, self.num_triangles)
|
|
325
|
+
|
|
326
|
+
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
|
|
327
|
+
glBindVertexArray(0)
|
|
328
|
+
glDisable(GL_DEPTH_TEST)
|
|
329
|
+
glDisable(GL_BLEND)
|
|
330
|
+
glUseProgram(0)
|
q3dviewer/glwidget.py
CHANGED
|
@@ -55,6 +55,7 @@ class GLWidget(BaseGLWidget):
|
|
|
55
55
|
self.followable_item_name = None
|
|
56
56
|
self.setting_window = SettingWindow()
|
|
57
57
|
self.enable_show_center = True
|
|
58
|
+
self.old_center = None
|
|
58
59
|
super(GLWidget, self).__init__()
|
|
59
60
|
|
|
60
61
|
def keyPressEvent(self, ev: QKeyEvent):
|
|
@@ -63,14 +64,38 @@ class GLWidget(BaseGLWidget):
|
|
|
63
64
|
self.open_setting_window()
|
|
64
65
|
else:
|
|
65
66
|
super().keyPressEvent(ev)
|
|
67
|
+
if ev.key() == QtCore.Qt.Key_F: # reset follow
|
|
68
|
+
if self.followable_item_name is None:
|
|
69
|
+
self.initial_followable()
|
|
70
|
+
|
|
71
|
+
if self.followed_name != 'none':
|
|
72
|
+
self.followed_name = 'none'
|
|
73
|
+
print("Reset follow.")
|
|
74
|
+
elif len(self.followable_item_name) > 1:
|
|
75
|
+
self.followed_name = self.followable_item_name[1]
|
|
76
|
+
print("Set follow to ", self.followed_name)
|
|
77
|
+
else:
|
|
78
|
+
pass # do nothing
|
|
66
79
|
|
|
67
80
|
def on_followable_selection(self, index):
|
|
68
81
|
self.followed_name = self.followable_item_name[index]
|
|
69
82
|
|
|
83
|
+
def mouseDoubleClickEvent(self, event):
|
|
84
|
+
"""Double click to set center."""
|
|
85
|
+
p = self.get_point(event.x(), event.y())
|
|
86
|
+
if p is not None:
|
|
87
|
+
self.set_center(p)
|
|
88
|
+
super().mouseDoubleClickEvent(event)
|
|
89
|
+
|
|
70
90
|
def update(self):
|
|
71
91
|
if self.followed_name != 'none':
|
|
72
92
|
new_center = self.named_items[self.followed_name].T[:3, 3]
|
|
73
|
-
self.
|
|
93
|
+
if self.old_center is None:
|
|
94
|
+
self.old_center = self.center
|
|
95
|
+
return
|
|
96
|
+
delta = new_center - self.old_center
|
|
97
|
+
self.set_center(self.center + delta)
|
|
98
|
+
self.old_center = new_center
|
|
74
99
|
super().update()
|
|
75
100
|
|
|
76
101
|
def add_setting(self, layout):
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#version 430 core
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
Copyright 2024 Panasonic Advanced Technology Development Co.,Ltd. (Liu Yang)
|
|
5
|
+
Distributed under MIT license. See LICENSE for more information.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
layout(points) in;
|
|
9
|
+
layout(triangle_strip, max_vertices = 6) out;
|
|
10
|
+
|
|
11
|
+
// Input from vertex shader
|
|
12
|
+
in VS_OUT {
|
|
13
|
+
vec3 v0, v1, v2, v3;
|
|
14
|
+
float good;
|
|
15
|
+
} gs_in[];
|
|
16
|
+
|
|
17
|
+
// Uniforms
|
|
18
|
+
uniform mat4 view;
|
|
19
|
+
uniform mat4 projection;
|
|
20
|
+
|
|
21
|
+
uniform int flat_rgb;
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
// Output to fragment shader
|
|
25
|
+
out vec3 FragPos;
|
|
26
|
+
out vec3 Normal;
|
|
27
|
+
out vec3 objectColor;
|
|
28
|
+
|
|
29
|
+
// Calculate normal from three vertices
|
|
30
|
+
vec3 calculateNormal(vec3 a, vec3 b, vec3 c) {
|
|
31
|
+
vec3 edge1 = b - a;
|
|
32
|
+
vec3 edge2 = c - a;
|
|
33
|
+
return normalize(cross(edge1, edge2));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
void emitVertex(vec3 pos, vec3 normal, vec3 color) {
|
|
37
|
+
FragPos = pos;
|
|
38
|
+
Normal = normal;
|
|
39
|
+
objectColor = color;
|
|
40
|
+
gl_Position = projection * view * vec4(pos, 1.0);
|
|
41
|
+
EmitVertex();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
void main()
|
|
45
|
+
{
|
|
46
|
+
// Discard if face is not good
|
|
47
|
+
if (gs_in[0].good != 1.0) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
vec3 v0 = gs_in[0].v0;
|
|
52
|
+
vec3 v1 = gs_in[0].v1;
|
|
53
|
+
vec3 v2 = gs_in[0].v2;
|
|
54
|
+
vec3 v3 = gs_in[0].v3;
|
|
55
|
+
|
|
56
|
+
float eps = 0.0001;
|
|
57
|
+
|
|
58
|
+
// Use default light blue color
|
|
59
|
+
vec3 color = vec3(
|
|
60
|
+
float((uint(flat_rgb) & uint(0x00FF0000)) >> 16)/255.,
|
|
61
|
+
float((uint(flat_rgb) & uint(0x0000FF00)) >> 8)/255.,
|
|
62
|
+
float( uint(flat_rgb) & uint(0x000000FF))/255.
|
|
63
|
+
);
|
|
64
|
+
// Triangle 1: (v0, v1, v2)
|
|
65
|
+
vec3 normal1 = calculateNormal(v0, v1, v2);
|
|
66
|
+
// Skip degenerate triangles
|
|
67
|
+
if (length(normal1) > eps) {
|
|
68
|
+
emitVertex(v0, normal1, color);
|
|
69
|
+
emitVertex(v1, normal1, color);
|
|
70
|
+
emitVertex(v2, normal1, color);
|
|
71
|
+
EndPrimitive();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Triangle 2: (v0, v1, v3)
|
|
75
|
+
vec3 normal2 = calculateNormal(v0, v1, v3);
|
|
76
|
+
// Skip degenerate triangles
|
|
77
|
+
if (length(normal2) > eps) {
|
|
78
|
+
emitVertex(v0, normal2, color);
|
|
79
|
+
emitVertex(v1, normal2, color);
|
|
80
|
+
emitVertex(v3, normal2, color);
|
|
81
|
+
EndPrimitive();
|
|
82
|
+
}
|
|
83
|
+
}
|
q3dviewer/shaders/mesh_vert.glsl
CHANGED
|
@@ -1,65 +1,37 @@
|
|
|
1
|
-
#version
|
|
2
|
-
layout (location = 0) in vec3 aPos;
|
|
3
|
-
layout (location = 1) in vec3 aNormal;
|
|
4
|
-
layout (location = 2) in uint aColor;
|
|
1
|
+
#version 430 core
|
|
5
2
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
/*
|
|
4
|
+
Copyright 2024 Panasonic Advanced Technology Development Co.,Ltd. (Liu Yang)
|
|
5
|
+
Distributed under MIT license. See LICENSE for more information.
|
|
6
|
+
*/
|
|
9
7
|
|
|
8
|
+
// Face attributes (per-instance) - vertex positions embedded in each face
|
|
9
|
+
layout(location = 1) in vec3 v0;
|
|
10
|
+
layout(location = 2) in vec3 v1;
|
|
11
|
+
layout(location = 3) in vec3 v2;
|
|
12
|
+
layout(location = 4) in vec3 v3;
|
|
13
|
+
layout(location = 5) in float good;
|
|
14
|
+
|
|
15
|
+
// Uniforms
|
|
10
16
|
uniform mat4 view;
|
|
11
17
|
uniform mat4 projection;
|
|
12
|
-
uniform int flat_rgb;
|
|
13
|
-
uniform int color_mode; // 0: FLAT, 1: Intensity, 2: RGB
|
|
14
|
-
uniform float vmin;
|
|
15
|
-
uniform float vmax;
|
|
16
|
-
|
|
17
|
-
vec3 getRainbowColor(uint value_raw) {
|
|
18
|
-
float range = vmax - vmin;
|
|
19
|
-
float value = 1.0 - (float(value_raw) - vmin) / range;
|
|
20
|
-
value = clamp(value, 0.0, 1.0);
|
|
21
|
-
float hue = value * 5.0 + 1.0;
|
|
22
|
-
int i = int(floor(hue));
|
|
23
|
-
float f = hue - float(i);
|
|
24
|
-
if (mod(i, 2) == 0) f = 1.0 - f;
|
|
25
|
-
float n = 1.0 - f;
|
|
26
18
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
else color = vec3(1.0, n, 0.0);
|
|
33
|
-
return color;
|
|
34
|
-
}
|
|
19
|
+
// Outputs to fragment shader (via geometry shader)
|
|
20
|
+
out VS_OUT {
|
|
21
|
+
vec3 v0, v1, v2, v3;
|
|
22
|
+
float good;
|
|
23
|
+
} vs_out;
|
|
35
24
|
|
|
36
25
|
void main()
|
|
37
26
|
{
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
27
|
+
// Pass vertex positions directly (no SSBO lookup needed)
|
|
28
|
+
vs_out.v0 = v0;
|
|
29
|
+
vs_out.v1 = v1;
|
|
30
|
+
vs_out.v2 = v2;
|
|
31
|
+
vs_out.v3 = v3;
|
|
32
|
+
vs_out.good = good;
|
|
42
33
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
c.x = float((uint(flat_rgb) & uint(0x00FF0000)) >> 16)/255.;
|
|
48
|
-
}
|
|
49
|
-
else if (color_mode == 1) {
|
|
50
|
-
// Intensity: use intensity channel (bits 24-31) for rainbow color
|
|
51
|
-
uint intensity = aColor >> 24;
|
|
52
|
-
c = getRainbowColor(intensity);
|
|
53
|
-
}
|
|
54
|
-
else if (color_mode == 2) {
|
|
55
|
-
// RGB: use RGB channels (bits 0-23)
|
|
56
|
-
c.z = float(aColor & uint(0x000000FF))/255.;
|
|
57
|
-
c.y = float((aColor & uint(0x0000FF00)) >> 8)/255.;
|
|
58
|
-
c.x = float((aColor & uint(0x00FF0000)) >> 16)/255.;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
objectColor = c;
|
|
62
|
-
|
|
63
|
-
// Final vertex position (apply view/projection)
|
|
64
|
-
gl_Position = projection * view * vec4(aPos, 1.0);
|
|
65
|
-
}
|
|
34
|
+
// Output dummy point (geometry shader will generate triangles)
|
|
35
|
+
// Note: This position is not used; geometry shader generates actual triangles
|
|
36
|
+
gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
|
|
37
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#version 430 core
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
Copyright 2024 Panasonic Advanced Technology Development Co.,Ltd. (Liu Yang)
|
|
5
|
+
Distributed under MIT license. See LICENSE for more information.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
layout(points) in;
|
|
9
|
+
layout(triangle_strip, max_vertices = 3) out;
|
|
10
|
+
|
|
11
|
+
// Input from vertex shader
|
|
12
|
+
in VS_OUT {
|
|
13
|
+
vec3 v0, v1, v2;
|
|
14
|
+
} gs_in[];
|
|
15
|
+
|
|
16
|
+
// Uniforms
|
|
17
|
+
uniform mat4 view;
|
|
18
|
+
uniform mat4 projection;
|
|
19
|
+
|
|
20
|
+
uniform int flat_rgb;
|
|
21
|
+
|
|
22
|
+
// Output to fragment shader
|
|
23
|
+
out vec3 FragPos;
|
|
24
|
+
out vec3 Normal;
|
|
25
|
+
out vec3 objectColor;
|
|
26
|
+
|
|
27
|
+
// Calculate normal from three vertices
|
|
28
|
+
vec3 calculateNormal(vec3 a, vec3 b, vec3 c) {
|
|
29
|
+
vec3 edge1 = b - a;
|
|
30
|
+
vec3 edge2 = c - a;
|
|
31
|
+
return normalize(cross(edge1, edge2));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
void emitVertex(vec3 pos, vec3 normal, vec3 color) {
|
|
35
|
+
FragPos = pos;
|
|
36
|
+
Normal = normal;
|
|
37
|
+
objectColor = color;
|
|
38
|
+
gl_Position = projection * view * vec4(pos, 1.0);
|
|
39
|
+
EmitVertex();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
void main()
|
|
43
|
+
{
|
|
44
|
+
vec3 v0 = gs_in[0].v0;
|
|
45
|
+
vec3 v1 = gs_in[0].v1;
|
|
46
|
+
vec3 v2 = gs_in[0].v2;
|
|
47
|
+
|
|
48
|
+
float eps = 0.0001;
|
|
49
|
+
|
|
50
|
+
// Use color from uniform
|
|
51
|
+
vec3 color = vec3(
|
|
52
|
+
float((uint(flat_rgb) & uint(0x00FF0000)) >> 16)/255.,
|
|
53
|
+
float((uint(flat_rgb) & uint(0x0000FF00)) >> 8)/255.,
|
|
54
|
+
float( uint(flat_rgb) & uint(0x000000FF))/255.
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
// Triangle: (v0, v1, v2)
|
|
58
|
+
vec3 normal = calculateNormal(v0, v1, v2);
|
|
59
|
+
// Skip degenerate triangles
|
|
60
|
+
if (length(normal) > eps) {
|
|
61
|
+
emitVertex(v0, normal, color);
|
|
62
|
+
emitVertex(v1, normal, color);
|
|
63
|
+
emitVertex(v2, normal, color);
|
|
64
|
+
EndPrimitive();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#version 430 core
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
Copyright 2024 Panasonic Advanced Technology Development Co.,Ltd. (Liu Yang)
|
|
5
|
+
Distributed under MIT license. See LICENSE for more information.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Triangle face attributes (per-instance) - 3 vertex positions
|
|
9
|
+
layout(location = 1) in vec3 v0;
|
|
10
|
+
layout(location = 2) in vec3 v1;
|
|
11
|
+
layout(location = 3) in vec3 v2;
|
|
12
|
+
|
|
13
|
+
// Uniforms
|
|
14
|
+
uniform mat4 view;
|
|
15
|
+
uniform mat4 projection;
|
|
16
|
+
|
|
17
|
+
// Outputs to fragment shader (via geometry shader)
|
|
18
|
+
out VS_OUT {
|
|
19
|
+
vec3 v0, v1, v2;
|
|
20
|
+
} vs_out;
|
|
21
|
+
|
|
22
|
+
void main()
|
|
23
|
+
{
|
|
24
|
+
// Pass vertex positions directly (no SSBO lookup needed)
|
|
25
|
+
vs_out.v0 = v0;
|
|
26
|
+
vs_out.v1 = v1;
|
|
27
|
+
vs_out.v2 = v2;
|
|
28
|
+
|
|
29
|
+
// Output dummy point (geometry shader will generate triangles)
|
|
30
|
+
gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
|
|
31
|
+
}
|