ngsolve-webgpu 0.0.1__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.
Files changed (37) hide show
  1. ngsolve_webgpu/__init__.py +0 -0
  2. ngsolve_webgpu/_version.py +21 -0
  3. ngsolve_webgpu/animate.py +76 -0
  4. ngsolve_webgpu/cf.py +369 -0
  5. ngsolve_webgpu/clipping.py +155 -0
  6. ngsolve_webgpu/geometry.py +376 -0
  7. ngsolve_webgpu/isosurface.py +101 -0
  8. ngsolve_webgpu/jupyter.py +168 -0
  9. ngsolve_webgpu/lic.py +115 -0
  10. ngsolve_webgpu/mesh.py +424 -0
  11. ngsolve_webgpu/shaders/clipping/common.wgsl +120 -0
  12. ngsolve_webgpu/shaders/clipping/compute.wgsl +30 -0
  13. ngsolve_webgpu/shaders/clipping/render.wgsl +37 -0
  14. ngsolve_webgpu/shaders/compute.wgsl +59 -0
  15. ngsolve_webgpu/shaders/elements3d.wgsl +83 -0
  16. ngsolve_webgpu/shaders/eval/common.wgsl +14 -0
  17. ngsolve_webgpu/shaders/eval/seg.wgsl +30 -0
  18. ngsolve_webgpu/shaders/eval/tet.wgsl +61 -0
  19. ngsolve_webgpu/shaders/eval/trig.wgsl +121 -0
  20. ngsolve_webgpu/shaders/eval.wgsl +0 -0
  21. ngsolve_webgpu/shaders/geo_edge.wgsl +55 -0
  22. ngsolve_webgpu/shaders/geo_face.wgsl +47 -0
  23. ngsolve_webgpu/shaders/geo_vertex.wgsl +71 -0
  24. ngsolve_webgpu/shaders/isosurface/compute.wgsl +63 -0
  25. ngsolve_webgpu/shaders/isosurface/negative_clipping.wgsl +13 -0
  26. ngsolve_webgpu/shaders/isosurface/negative_surface.wgsl +15 -0
  27. ngsolve_webgpu/shaders/isosurface/render.wgsl +50 -0
  28. ngsolve_webgpu/shaders/line_integral_convolution.wgsl +100 -0
  29. ngsolve_webgpu/shaders/mesh.wgsl +296 -0
  30. ngsolve_webgpu/shaders/numbers.wgsl +40 -0
  31. ngsolve_webgpu/shaders/shader.wgsl +111 -0
  32. ngsolve_webgpu/shaders/uniforms.wgsl +93 -0
  33. ngsolve_webgpu-0.0.1.dist-info/METADATA +13 -0
  34. ngsolve_webgpu-0.0.1.dist-info/RECORD +37 -0
  35. ngsolve_webgpu-0.0.1.dist-info/WHEEL +5 -0
  36. ngsolve_webgpu-0.0.1.dist-info/licenses/LICENSE +504 -0
  37. ngsolve_webgpu-0.0.1.dist-info/top_level.txt +1 -0
ngsolve_webgpu/lic.py ADDED
@@ -0,0 +1,115 @@
1
+ import typing
2
+
3
+ import js
4
+
5
+ from .gpu import WebGPU
6
+ from .mesh import RenderObject
7
+ from .uniforms import Binding, LineIntegralConvolutionUniforms
8
+ from .utils import ShaderStage, StorageTextureBinding, TextureBinding
9
+
10
+
11
+ class LineIntegralConvolutionRenderObject(RenderObject):
12
+ """Line Integral Convolution (LIC) render object"""
13
+
14
+ texture_input: typing.Any
15
+ texture_output: typing.Any
16
+ uniforms: LineIntegralConvolutionUniforms
17
+ gpu: WebGPU
18
+ _buffers: dict = {}
19
+
20
+ def __init__(self, gpu, width, height):
21
+ self.gpu = gpu
22
+ self.uniforms = LineIntegralConvolutionUniforms(gpu.device)
23
+ self.uniforms.width = width
24
+ self.uniforms.height = height
25
+ self.uniforms.update_buffer()
26
+ self.texture_input = self.device.create_texture(
27
+ {
28
+ "label": "LIC input texture",
29
+ "size": [width, height],
30
+ "format": "rg32float",
31
+ "usage": js.GPUTextureUsage.TEXTURE_BINDING
32
+ | js.GPUTextureUsage.COPY_DST
33
+ | js.GPUTextureUsage.RENDER_ATTACHMENT,
34
+ }
35
+ )
36
+ self.texture_output = self.device.create_texture(
37
+ {
38
+ "label": "LIC output texture",
39
+ "size": [width, height],
40
+ "format": "r32float",
41
+ "usage": js.GPUTextureUsage.STORAGE_BINDING,
42
+ }
43
+ )
44
+
45
+ self._create_pipelines()
46
+
47
+ def _create_evaluate_pipeline(self):
48
+ device = self.device
49
+ layout, self.bind_group = device.create_bind_group(
50
+ self.get_bindings(), "LIC evaluate"
51
+ )
52
+
53
+ self.evaluate_pipeline = device.create_compute_pipeline(
54
+ layout,
55
+ {
56
+ "label": "create_test_mesh",
57
+ "layout": device.create_pipeline_layout(layout, "LIC evaluate"),
58
+ "compute": {
59
+ "module": device.shader_module,
60
+ "entryPoint": "evaluateLineIntegralConvolution",
61
+ },
62
+ },
63
+ )
64
+
65
+ def _create_compute_pipeline(self):
66
+ device = self.device
67
+ layout, self.bind_group = device.create_bind_group(
68
+ self.get_bindings(), "LIC compute"
69
+ )
70
+
71
+ self.compute_pipeline = device.create_compute_pipeline(
72
+ layout,
73
+ {
74
+ "label": "create_test_mesh",
75
+ "layout": device.create_pipeline_layout(layout, "LIC compute"),
76
+ "compute": {
77
+ "module": device.shader_module,
78
+ "entryPoint": "computeLineIntegralConvolution",
79
+ },
80
+ },
81
+ )
82
+
83
+ def render(self, encoder):
84
+ pass_encoder = encoder.beginComputePass()
85
+ pass_encoder.setPipeline(self.compute_pipeline)
86
+ pass_encoder.setBindGroup(0, self.bind_group)
87
+ pass_encoder.dispatchWorkgroups(
88
+ (self.uniforms.width + 15 // 16), (self.uniforms.height + 15) // 16, 1
89
+ )
90
+ pass_encoder.end()
91
+
92
+ @property
93
+ def device(self):
94
+ return self.gpu.device
95
+
96
+ def get_bindings(self):
97
+ return (
98
+ self.uniforms.get_bindings()
99
+ + self.gpu.get_bindings()
100
+ + [
101
+ TextureBinding(
102
+ Binding.LINE_INTEGRAL_CONVOLUTION_INPUT_TEXTURE,
103
+ self.texture_input,
104
+ dim=2,
105
+ visibility=ShaderStage.COMPUTE,
106
+ sample_type="unfilterable-float",
107
+ ),
108
+ StorageTextureBinding(
109
+ Binding.LINE_INTEGRAL_CONVOLUTION_OUTPUT_TEXTURE,
110
+ self.texture_output,
111
+ dim=2,
112
+ access="write-only",
113
+ ),
114
+ ]
115
+ )
ngsolve_webgpu/mesh.py ADDED
@@ -0,0 +1,424 @@
1
+ from enum import Enum
2
+ import netgen.meshing
3
+ import numpy as np
4
+ from webgpu.font import Font
5
+ from webgpu.render_object import (
6
+ RenderObject,
7
+ )
8
+
9
+ # from webgpu.uniforms import Binding
10
+ from webgpu.uniforms import UniformBase, ct
11
+ from webgpu.utils import (
12
+ BufferBinding,
13
+ UniformBinding,
14
+ read_shader_file,
15
+ buffer_from_array,
16
+ uniform_from_array,
17
+ get_device,
18
+ )
19
+ from webgpu.webgpu_api import *
20
+ from webgpu.clipping import Clipping
21
+
22
+
23
+ class Binding:
24
+ """Binding numbers for uniforms in shader code in uniforms.wgsl"""
25
+
26
+ EDGES = 8
27
+ TRIGS = 9
28
+ SEG_FUNCTION_VALUES = 11
29
+ VERTICES = 12
30
+ TRIGS_INDEX = 13
31
+ CURVATURE_VALUES_2D = 14
32
+ CURVATURE_SUBDIVISION = 15
33
+ DEFORMATION_VALUES = 16
34
+ DEFORMATION_SCALE = 17
35
+
36
+ MESH = 20
37
+ EDGE = 21
38
+ SEG = 22
39
+ TRIG = 23
40
+ QUAD = 24
41
+ TET = 25
42
+ PYRAMID = 26
43
+ PRISM = 27
44
+ HEX = 28
45
+
46
+ LINE_INTEGRAL_CONVOLUTION = 40
47
+ LINE_INTEGRAL_CONVOLUTION_INPUT_TEXTURE = 41
48
+ LINE_INTEGRAL_CONVOLUTION_OUTPUT_TEXTURE = 42
49
+
50
+
51
+ class _eltype:
52
+ dim: int
53
+ primitive_topology: PrimitiveTopology
54
+ num_vertices_per_primitive: int
55
+
56
+ def __init__(self, dim, primitive_topology, num_vertices_per_primitive):
57
+ self.dim = dim
58
+ self.primitive_topology = primitive_topology
59
+ self.num_vertices_per_primitive = num_vertices_per_primitive
60
+
61
+
62
+ class ElType(Enum):
63
+ POINT = _eltype(0, PrimitiveTopology.point_list, 1)
64
+ SEG = _eltype(1, PrimitiveTopology.line_list, 2)
65
+ TRIG = _eltype(2, PrimitiveTopology.triangle_list, 3)
66
+ QUAD = _eltype(2, PrimitiveTopology.triangle_list, 2 * 3)
67
+ TET = _eltype(3, PrimitiveTopology.triangle_list, 4 * 3)
68
+ HEX = _eltype(3, PrimitiveTopology.triangle_list, 6 * 2 * 3)
69
+ PRISM = _eltype(3, PrimitiveTopology.triangle_list, 2 * 3 + 3 * 2 * 3)
70
+ PYRAMID = _eltype(3, PrimitiveTopology.triangle_list, 4 + 2 * 3)
71
+
72
+ @staticmethod
73
+ def from_dim_np(dim: int, np: int):
74
+ if dim == 2:
75
+ if np == 3:
76
+ return ElType.TRIG
77
+ if np == 4:
78
+ return ElType.QUAD
79
+ if dim == 3:
80
+ if np == 4:
81
+ return ElType.TET
82
+ if np == 8:
83
+ return ElType.HEX
84
+ if np == 6:
85
+ return ElType.PRISM
86
+ if np == 5:
87
+ return ElType.PYRAMID
88
+ raise ValueError(f"Unsupported element type dim={dim} np={np}")
89
+
90
+
91
+ ElTypes2D = [ElType.TRIG, ElType.QUAD]
92
+ ElTypes3D = [ElType.TET, ElType.HEX, ElType.PRISM, ElType.PYRAMID]
93
+
94
+ class MeshData:
95
+ # only for drawing the mesh, not needed for function values
96
+ num_elements: dict[str | ElType, int]
97
+ elements: dict[str | ElType, np.ndarray]
98
+ gpu_elements: dict[str | ElType, Buffer]
99
+ curvature_subdivision: int
100
+
101
+ mesh: netgen.meshing.Mesh
102
+ curvature_data = None
103
+ deformation_data = None
104
+ _ngs_mesh = None
105
+ _last_mesh_timestamp: int = -1
106
+ _timestamp = -1
107
+
108
+ def __init__(self, mesh):
109
+ self.on_region = False
110
+ self.need_3d = False
111
+ if isinstance(mesh, netgen.meshing.Mesh):
112
+ self.mesh = mesh
113
+ else:
114
+ self._ngs_mesh = mesh
115
+ import ngsolve as ngs
116
+
117
+ if isinstance(mesh, ngs.Region):
118
+ self.on_region = True
119
+ mesh = mesh.mesh
120
+ self.mesh = mesh.ngmesh
121
+ self.num_elements = {}
122
+ self.elements = {}
123
+ self.gpu_elements = {}
124
+ self.curvature_subdivision = 1
125
+ self._deformation_scale = 1
126
+
127
+ @property
128
+ def deformation_scale(self):
129
+ return self._deformation_scale
130
+
131
+ @deformation_scale.setter
132
+ def deformation_scale(self, value):
133
+ self._deformation_scale = value
134
+ if self.gpu_elements and "deformation_scale" in self.gpu_elements:
135
+ get_device().queue.writeBuffer(
136
+ self.gpu_elements["deformation_scale"],
137
+ 0, np.array([self._deformation_scale], dtype=np.float32).tobytes())
138
+
139
+ @property
140
+ def ngs_mesh(self):
141
+ import ngsolve
142
+
143
+ if self._ngs_mesh is None:
144
+ self._ngs_mesh = ngsolve.Mesh(self.mesh)
145
+ return self._ngs_mesh
146
+
147
+ def update(self, timestamp):
148
+ if self._last_mesh_timestamp != self.mesh._timestamp:
149
+ self._create_data()
150
+ if timestamp == self._timestamp:
151
+ return
152
+ if "curvature_2d" in self.gpu_elements:
153
+ self.gpu_elements.pop("curvature_2d")
154
+ if "deformation_2d" in self.gpu_elements:
155
+ self.gpu_elements.pop("deformation_2d")
156
+ if self.curvature_data:
157
+ self.curvature_data.update(self.mesh._timestamp)
158
+ self.elements["curvature_2d"] = self.curvature_data.data_2d
159
+ else:
160
+ self.elements["curvature_2d"] = np.array([0], dtype=np.float32)
161
+
162
+ if self.deformation_data:
163
+ self.deformation_data.update(self.mesh._timestamp)
164
+ self.elements["deformation_2d"] = self.deformation_data.data_2d
165
+ else:
166
+ self.elements["deformation_2d"] = np.array([-1], dtype=np.float32)
167
+ self._timestamp = timestamp
168
+
169
+
170
+ def _create_data(self):
171
+ # TODO: implement other element types than triangles
172
+ # TODO: handle region correctly to draw only part of the mesh
173
+ mesh = self.mesh
174
+ self.num_elements = {eltype: 0 for eltype in ElType}
175
+ self.elements = {}
176
+ self.gpu_elements = {}
177
+
178
+ # Vertices
179
+ nv = len(mesh.Points())
180
+ self.num_elements["vertices"] = nv
181
+ vertices = np.array(mesh.Coordinates(), dtype=np.float32)
182
+ if vertices.shape[1] == 2:
183
+ vertices = np.hstack((vertices, np.zeros((nv, 1), dtype=np.float32)))
184
+
185
+ self.pmin = np.min(vertices, axis=0)
186
+ self.pmax = np.max(vertices, axis=0)
187
+ self.elements["vertices"] = vertices
188
+
189
+ # Trigs TODO: Quads
190
+ trigs = mesh.Elements2D().NumPy()
191
+ if self.on_region:
192
+ region = self.ngs_mesh
193
+ import ngsolve as ngs
194
+
195
+ if region.VB() == ngs.VOL and region.mesh.dim == 3:
196
+ region = region.Boundaries()
197
+ indices = np.flatnonzero(region.Mask()) + 1
198
+ trigs = trigs[np.isin(trigs["index"], indices)]
199
+ self.num_elements[ElType.TRIG] = len(trigs)
200
+ trigs_data = np.zeros((len(trigs), 4), dtype=np.uint32)
201
+ trigs_data[:, :3] = trigs["nodes"][:, :3] - 1
202
+ trigs_data[:, 3] = trigs["index"]
203
+ self.elements[ElType.TRIG] = trigs_data
204
+
205
+ # 3d Elements
206
+ if self.need_3d:
207
+ els = mesh.Elements3D().NumPy()
208
+ for num_pts in (4, 5, 6, 8):
209
+ eltype = ElType.from_dim_np(3, num_pts)
210
+ filtered = els[els["np"] == num_pts]
211
+ nels = len(filtered)
212
+ if nels == 0:
213
+ continue
214
+ u32array = np.empty((nels, num_pts + 2), dtype=np.uint32)
215
+ u32array[:, :num_pts] = filtered["nodes"][:, :num_pts] - 1
216
+ u32array[:, num_pts] = filtered["index"]
217
+ self.elements[eltype] = u32array
218
+ self.num_elements[eltype] = len(filtered)
219
+
220
+ curve_order = mesh.GetCurveOrder()
221
+ if curve_order > 1:
222
+ from .cf import FunctionData
223
+ import ngsolve as ngs
224
+
225
+ cf = ngs.CF((ngs.x, ngs.y, ngs.z))
226
+ self.curvature_data = FunctionData(self, cf, curve_order)
227
+ self.curvature_subdivision = curve_order + 3
228
+
229
+ self._last_mesh_timestamp = mesh._timestamp
230
+
231
+ def get_bounding_box(self):
232
+ return (self.pmin, self.pmax)
233
+
234
+ def get_buffers(self):
235
+ for eltype in self.elements:
236
+ if eltype not in self.gpu_elements:
237
+ self.gpu_elements[eltype] = buffer_from_array(
238
+ self.elements[eltype])
239
+ if "curvature_subdivision" not in self.gpu_elements:
240
+ self.gpu_elements["curvature_subdivision"] = uniform_from_array(
241
+ np.array([self.curvature_subdivision], dtype=np.uint32)
242
+ )
243
+ if "deformation_scale" not in self.gpu_elements:
244
+ self.gpu_elements["deformation_scale"] = uniform_from_array(
245
+ np.array([self.deformation_scale], dtype=np.float32)
246
+ )
247
+
248
+ return self.gpu_elements
249
+
250
+
251
+ class Mesh2dElementsRenderer(RenderObject):
252
+ depthBias: int = 1
253
+ depthBiasSlopeScale: float = 1.0
254
+ vertex_entry_point: str = "vertexTrigP1Indexed"
255
+ fragment_entry_point: str = "fragment2dElement"
256
+ color = (0, 1, 0, 1)
257
+
258
+ def __init__(self, data: MeshData, label="Mesh2dElementsRenderer"):
259
+ super().__init__(label=label)
260
+ self.data = data
261
+ self.clipping = Clipping()
262
+
263
+ def update(self, timestamp: float):
264
+ if self._timestamp == timestamp:
265
+ return
266
+ self.clipping.update(timestamp)
267
+ self.data.update(timestamp)
268
+ self.curvature_subdivision = self.data.curvature_subdivision
269
+ self.n_vertices = 3 * self.curvature_subdivision**2
270
+
271
+ self._buffers = self.data.get_buffers()
272
+ self.n_instances = self.data.num_elements[ElType.TRIG]
273
+ self.color_uniform = buffer_from_array(np.array(self.color, dtype=np.float32))
274
+ self.create_render_pipeline()
275
+
276
+ def get_bounding_box(self):
277
+ return self.data.get_bounding_box()
278
+
279
+ def get_bindings(self):
280
+ bindings = [
281
+ *self.options.get_bindings(),
282
+ *self.clipping.get_bindings(),
283
+ BufferBinding(Binding.VERTICES, self._buffers["vertices"]),
284
+ BufferBinding(Binding.TRIGS_INDEX, self._buffers[ElType.TRIG]),
285
+ BufferBinding(Binding.CURVATURE_VALUES_2D, self._buffers["curvature_2d"]),
286
+ BufferBinding(Binding.DEFORMATION_VALUES, self._buffers["deformation_2d"]),
287
+ UniformBinding(Binding.DEFORMATION_SCALE, self._buffers["deformation_scale"]),
288
+ UniformBinding(
289
+ Binding.CURVATURE_SUBDIVISION, self._buffers["curvature_subdivision"]
290
+ ),
291
+ ]
292
+ if hasattr(self, "color_uniform"):
293
+ bindings.append(BufferBinding(54, self.color_uniform))
294
+ return bindings
295
+
296
+
297
+ def get_shader_code(self):
298
+ shader_code = ""
299
+ shader_code += self.clipping.get_shader_code()
300
+ for file_name in [
301
+ "eval.wgsl",
302
+ "mesh.wgsl",
303
+ "shader.wgsl",
304
+ "uniforms.wgsl",
305
+ ]:
306
+ shader_code += read_shader_file(file_name, __file__)
307
+ # for now as shaders are not seperated well
308
+ import webgpu.colormap
309
+
310
+ shader_code += webgpu.colormap.Colormap().get_shader_code()
311
+ shader_code += self.options.camera.get_shader_code()
312
+ shader_code += self.options.light.get_shader_code()
313
+ return shader_code
314
+
315
+
316
+ class Mesh2dWireframeRenderer(Mesh2dElementsRenderer):
317
+ depthBias: int = 0
318
+ topology: PrimitiveTopology = PrimitiveTopology.line_strip
319
+ color = (0, 0, 0, 1)
320
+ fragment_entry_point: str = "fragmentWireframe2d"
321
+
322
+
323
+ class El3dUniform(UniformBase):
324
+ _binding = Binding.MESH
325
+ _fields_ = [
326
+ ("subdivision", ct.c_uint32),
327
+ ("shrink", ct.c_float),
328
+ ("padding", ct.c_float * 2),
329
+ ]
330
+
331
+ def __init__(self, device, subdivision=0, shrink=1.0):
332
+ super().__init__(device, subdivision=subdivision, shrink=shrink)
333
+
334
+
335
+ class Mesh3dElementsRenderObject(RenderObject):
336
+ n_vertices: int = 3 * 4
337
+
338
+ def __init__(self, data: MeshData):
339
+ super().__init__(label="Mesh3dElementsRenderObject")
340
+ data.need_3d = True
341
+ self.data = data
342
+ self.clipping = Clipping()
343
+
344
+ def get_bounding_box(self) -> tuple[list[float], list[float]] | None:
345
+ return self.data.get_bounding_box()
346
+
347
+ def update(self, timestamp):
348
+ if timestamp == self._timestamp:
349
+ return
350
+ self._timestamp = timestamp
351
+ self.data.update()
352
+ self.uniforms = El3dUniform(self.device)
353
+ self.clipping.options = self.options
354
+ self.clipping.update(timestamp)
355
+ self._buffers = self.data.get_buffers()
356
+ self.uniforms.update_buffer()
357
+ self.n_instances = self.data.num_elements[ElType.TET]
358
+ self.create_render_pipeline()
359
+
360
+ def add_options_to_gui(self, gui):
361
+ def set_shrink(value):
362
+ self.uniforms.shrink = value
363
+ self.uniforms.update_buffer()
364
+
365
+ gui.slider(
366
+ label="Shrink", value=1.0, min=0.0, max=1.0, step=0.01, func=set_shrink
367
+ )
368
+
369
+ def get_bindings(self):
370
+ return [
371
+ *self.clipping.get_bindings(),
372
+ BufferBinding(Binding.VERTICES, self._buffers["vertices"]),
373
+ BufferBinding(Binding.TET, self._buffers[ElType.TET]),
374
+ *self.uniforms.get_bindings(),
375
+ *self.options.get_bindings(),
376
+ ]
377
+
378
+ def get_shader_code(self):
379
+ code = read_shader_file("elements3d.wgsl", __file__)
380
+ code += self.clipping.get_shader_code()
381
+ code += self.options.camera.get_shader_code()
382
+ code += self.options.light.get_shader_code()
383
+ return code
384
+
385
+
386
+ class PointNumbersRenderObject(RenderObject):
387
+ """Render a point numbers of a mesh"""
388
+
389
+ _buffers: dict
390
+
391
+ def __init__(self, data, font_size=20, label=None):
392
+ super().__init__(label=label)
393
+ self.n_digits = 6
394
+ self.data = data
395
+ self.depthBias = -1
396
+ self.vertex_entry_point = "vertexPointNumber"
397
+ self.fragment_entry_point = "fragmentFont"
398
+ self.n_vertices = self.n_digits * 6
399
+ self.font_size = font_size
400
+ self.clipping = Clipping()
401
+
402
+ def update(self, timestamp):
403
+ if timestamp == self._timestamp:
404
+ return
405
+ self._timestamp = timestamp
406
+ self.clipping.update(timestamp)
407
+ self.font = Font(self.canvas, self.font_size)
408
+ self._buffers = self.data.get_buffers()
409
+ self.n_instances = self.data.num_elements["vertices"]
410
+ self.create_render_pipeline()
411
+
412
+ def get_shader_code(self):
413
+ return read_shader_file("numbers.wgsl", __file__)
414
+
415
+ def get_bounding_box(self):
416
+ return self.data.get_bounding_box()
417
+
418
+ def get_bindings(self):
419
+ return [
420
+ *self.clipping.get_bindings(),
421
+ *self.options.get_bindings(),
422
+ *self.font.get_bindings(),
423
+ BufferBinding(Binding.VERTICES, self._buffers["vertices"]),
424
+ ]
@@ -0,0 +1,120 @@
1
+
2
+ @group(0) @binding(12) var<storage> vertices : array<f32>;
3
+ @group(0) @binding(25) var<storage> u_tets : array<Tet>;
4
+
5
+ struct Tet { p: array<u32, 4>, nr: u32, index: u32 };
6
+
7
+ struct SubTrig {
8
+ lam: array<vec3f, 3>,
9
+ id: u32,
10
+ }
11
+
12
+ struct ClipTetResult {
13
+ n: u32,
14
+ trigs: array<SubTrig, 2>,
15
+ }
16
+
17
+ fn get_tet_points(elid: u32) -> array<vec3f, 4>
18
+ {
19
+ var p: array<vec3f, 4>;
20
+ for(var i = 0u; i < 4u; i++) {
21
+ for(var j = 0u; j < 3u; j++) {
22
+ p[i][j] = vertices[u_tets[elid].p[i] * 3u + j];
23
+ }
24
+ }
25
+ return p;
26
+ }
27
+
28
+ fn clipCheckOrientation(trig: SubTrig, values: array<f32, 4>) -> SubTrig {
29
+ var result = trig;
30
+ let n = cross(
31
+ trig.lam[1] - trig.lam[0],
32
+ trig.lam[2] - trig.lam[0]
33
+ );
34
+
35
+ let p = 1.0/3.*(trig.lam[0] + trig.lam[1] + trig.lam[2]) + n;
36
+ let p4 = 1.0 - p.x - p.y - p.z;
37
+ let value = p.x * values[0] + p.y * values[1] + p.z * values[2] + p4 * values[3];
38
+
39
+ if(value<0.0) {
40
+ result.lam[1] = trig.lam[2];
41
+ result.lam[2] = trig.lam[1];
42
+ }
43
+
44
+ return result;
45
+ }
46
+
47
+ // clip tet such that the clip triangle(s) have value 0 everywhere
48
+ fn clipTet(lam: array<vec3f, 4>, values: array<f32, 4>, ei: u32) -> ClipTetResult {
49
+ var trigs = ClipTetResult(0, array<SubTrig, 2>(SubTrig(array<vec3f, 3>(vec3f(0.0), vec3f(0.0), vec3f(0.0)), 0), SubTrig(array<vec3f, 3>(vec3f(0.0), vec3f(0.0), vec3f(0.0)), 0)));
50
+ var p_pos = array<u32, 4>(0u, 0u, 0u, 0u);
51
+ var p_neg = array<u32, 4>(0u, 0u, 0u, 0u);
52
+
53
+ var n_pos: u32 = 0u;
54
+ var n_neg: u32 = 0u;
55
+
56
+ for (var i = 0u; i < 4u; i++) {
57
+ if (values[i] > 0.0) {
58
+ p_pos[n_pos] = i;
59
+ n_pos++;
60
+ } else {
61
+ p_neg[n_neg] = i;
62
+ n_neg++;
63
+ }
64
+ }
65
+
66
+ if n_pos == 4u || n_neg == 4u {
67
+ return trigs;
68
+ }
69
+
70
+ if n_pos == 3u {
71
+ trigs.n = 1u;
72
+ n_pos = 1u;
73
+ n_neg = 3u;
74
+ p_pos[3] = p_neg[0];
75
+ p_neg[0] = p_pos[0];
76
+ p_neg[1] = p_pos[1];
77
+ p_neg[2] = p_pos[2];
78
+ p_pos[0] = p_pos[3];
79
+ }
80
+
81
+ if n_pos == 1u {
82
+ trigs.n = 1u;
83
+ trigs.trigs[0].id = ei;
84
+ for (var i = 0u; i < 3u; i++) {
85
+ let t = values[p_pos[0] ] / (values[p_pos[0] ] - values[p_neg[i] ]);
86
+ let lam_trig = mix(lam[p_pos[0] ], lam[p_neg[i] ], t);
87
+ trigs.trigs[0].lam[i] = lam_trig;
88
+ }
89
+
90
+ trigs.trigs[0] = clipCheckOrientation(trigs.trigs[0], values);
91
+ return trigs;
92
+ }
93
+
94
+ // two points before, two points behind clipping plane
95
+ // -> two triangles
96
+ trigs.n = 2u;
97
+ var pairs = array<vec2u,4>(
98
+ vec2u(p_pos[1], p_neg[0]),
99
+ vec2u(p_pos[0], p_neg[0]),
100
+ vec2u(p_pos[0], p_neg[1]),
101
+ vec2u(p_pos[1], p_neg[1])
102
+ );
103
+ var points: array<vec3f, 4> = array<vec3f, 4>(vec3f(0.0), vec3f(0.0), vec3f(0.0), vec3f(0.0));
104
+ for (var i = 0; i < 4; i++) {
105
+ let p0 = pairs[i].x;
106
+ let p1 = pairs[i].y;
107
+ let t = values[p0 ] / (values[p0] - values[p1]);
108
+ let lam_trig = mix(lam[p0], lam[p1], t);
109
+ points[i] = lam_trig;
110
+ }
111
+ trigs.trigs[0].id = ei;
112
+ trigs.trigs[0].lam = array(points[0], points[1], points[2]);
113
+ trigs.trigs[1].id = ei;
114
+ trigs.trigs[1].lam = array(points[0], points[2], points[3]);
115
+
116
+ trigs.trigs[0] = clipCheckOrientation(trigs.trigs[0], values);
117
+ trigs.trigs[1] = clipCheckOrientation(trigs.trigs[1], values);
118
+ return trigs;
119
+ }
120
+
@@ -0,0 +1,30 @@
1
+
2
+ @group(0) @binding(21) var<storage, read_write> count_trigs: atomic<u32>;
3
+ @group(0) @binding(22) var<uniform> u_ntets: u32;
4
+ @group(0) @binding(23) var<uniform> only_count: u32;
5
+ @group(0) @binding(24) var<storage, read_write> subtrigs: array<SubTrig>;
6
+
7
+ @compute @workgroup_size(256)
8
+ fn main(@builtin(global_invocation_id) id: vec3<u32>) {
9
+ for (var i = id.x; i<u_ntets; i+=256*1024) {
10
+ let p = get_tet_points(i);
11
+ let lam = array(vec3f(1.0, 0.0, 0.0), vec3f(0.0, 1.0, 0.0), vec3f(0.0, 0.0, 1.0), vec3f(0.0, 0.0, 0.0));
12
+
13
+ let f = array(dot(vec4<f32>(p[0], 1.0), u_clipping.plane),
14
+ dot(vec4<f32>(p[1], 1.0), u_clipping.plane),
15
+ dot(vec4<f32>(p[2], 1.0), u_clipping.plane),
16
+ dot(vec4<f32>(p[3], 1.0), u_clipping.plane));
17
+ let cuts = clipTet(lam, f, i);
18
+ if(cuts.n == 0) {
19
+ continue;
20
+ }
21
+ let index = atomicAdd(&count_trigs, cuts.n);
22
+ if(only_count == u32(1)) {
23
+ continue;
24
+ }
25
+ for(var i = 0u; i < cuts.n; i++) {
26
+ subtrigs[index+i] = cuts.trigs[i];
27
+ }
28
+ }
29
+ }
30
+