zincjs 2.4.0 → 2.4.1-beta.0

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 (55) hide show
  1. package/build/zinc.js +3 -486
  2. package/build/zinc.js.map +1 -1
  3. package/package.json +3 -4
  4. package/src/controls.js +1 -1
  5. package/src/geometryCSG.js +1 -1
  6. package/src/glyphsetCSG.js +1 -1
  7. package/src/loaders/GLTFToZincJSLoader.js +1 -1
  8. package/src/loaders/JSONLoader.js +204 -377
  9. package/src/loaders/OBJLoader.js +1 -1
  10. package/src/loaders/STLLoader.js +1 -1
  11. package/src/loaders/niftiReader.js +6 -4
  12. package/src/loaders/primitivesLoader.js +7 -9
  13. package/src/minimap.js +1 -1
  14. package/src/primitives/geometry.js +11 -29
  15. package/src/primitives/glyph.js +1 -1
  16. package/src/primitives/glyphset.js +2 -4
  17. package/src/primitives/label.js +1 -1
  18. package/src/primitives/lines.js +12 -5
  19. package/src/primitives/lines2.js +4 -8
  20. package/src/primitives/lod.js +9 -3
  21. package/src/primitives/marker.js +1 -1
  22. package/src/primitives/markerCluster.js +1 -1
  23. package/src/primitives/pointset.js +368 -52
  24. package/src/primitives/textureSlides.js +59 -16
  25. package/src/primitives/textureVolume.js +1 -1
  26. package/src/primitives/tubeLines.js +20 -3
  27. package/src/primitives/zincObject.js +13 -1
  28. package/src/region.js +1 -1
  29. package/src/renderer.js +17 -13
  30. package/src/scene.js +20 -8
  31. package/src/sceneLoader.js +1 -1
  32. package/src/shaders/rgbVolumeRender.js +1 -1
  33. package/src/shaders/textureSlide.js +1 -1
  34. package/src/texture/texture.js +1 -1
  35. package/src/texture/textureArray.js +9 -2
  36. package/src/three/GLTFExporter.js +55 -2
  37. package/src/three/InstancedPoints.js +73 -0
  38. package/src/three/Loader.js +1 -1
  39. package/src/three/line/Line.js +1 -1
  40. package/src/three/line/LineSegments.js +1 -1
  41. package/src/three/line/LineSegments2.js +24 -39
  42. package/src/three/line/LineSegmentsGeometry.js +1 -1
  43. package/src/tls/morphColorMaterial.js +65 -0
  44. package/src/tls/pointsMaterial.js +79 -0
  45. package/src/tls/textureSlides.js +126 -0
  46. package/src/utilities.js +47 -70
  47. package/src/videoHandler.js +1 -2
  48. package/src/workers/geometryCSG.worker.js +1 -1
  49. package/src/workers/geometryCSGInternal.js +1 -1
  50. package/src/zinc.js +1 -1
  51. package/vite.config.js +3 -1
  52. package/src/primitives/augmentShader.js +0 -45
  53. package/src/three/Geometry.js +0 -2176
  54. package/src/three/Points.js +0 -224
  55. package/src/three/line/LineMaterial.js +0 -726
@@ -1,15 +1,97 @@
1
- import * as THREE from 'three';
1
+ import * as THREE from 'three/webgpu';
2
2
  import { Label } from './label';
3
- import { Points } from '../three/Points';
3
+ import { InstancedPoints } from '../three/InstancedPoints';
4
4
  import { ZincObject } from './zincObject';
5
5
  import { getCircularTexture, toBufferGeometry } from '../utilities';
6
- import { augmentMorphColor } from './augmentShader';
6
+ import { createInstancedPointsMaterial } from '../tls/pointsMaterial';
7
+
8
+ //Maximum number of points a programmatically built (addPoints) Pointset can
9
+ //hold - the InstancedMesh backing it is allocated once with this capacity.
10
+ const DEFAULT_CAPACITY = 500;
11
+
12
+ /**
13
+ * Build the shared quad geometry every point instance is drawn with plus
14
+ * the per instance `instancePosition` attribute used to place it - the
15
+ * actual on screen billboard offset and constant pixel sizing is computed
16
+ * in {@link createInstancedPointsMaterial}.
17
+ */
18
+ const createPointQuadGeometry = (capacity) => {
19
+ const geometry = new THREE.PlaneGeometry(1, 1);
20
+ const instancePosition = new THREE.InstancedBufferAttribute(
21
+ new Float32Array(capacity * 3), 3);
22
+ instancePosition.setUsage(THREE.DynamicDrawUsage);
23
+ geometry.setAttribute('instancePosition', instancePosition);
24
+ return geometry;
25
+ }
26
+
27
+ /**
28
+ * Extract the per time step, flat [x0, y0, z0, x1, y1, z1, ...] position
29
+ * arrays from a (possibly time varying) BufferGeometry. Index 0 is always
30
+ * the base frame, subsequent entries come from `morphAttributes.position`.
31
+ */
32
+ const extractPositionFrames = (geometry, localTimeEnabled) => {
33
+ const positionAttribute = geometry.getAttribute('position');
34
+ const frames = [Float32Array.from(positionAttribute.array)];
35
+ const morphPositions = geometry.morphAttributes.position;
36
+ if (localTimeEnabled && morphPositions && morphPositions.length > 0) {
37
+ const relative = geometry.morphTargetsRelative;
38
+ morphPositions.forEach((attribute) => {
39
+ if (relative) {
40
+ const frame = new Float32Array(positionAttribute.array.length);
41
+ for (let i = 0; i < frame.length; i++) {
42
+ frame[i] = positionAttribute.array[i] + attribute.array[i];
43
+ }
44
+ frames.push(frame);
45
+ } else {
46
+ frames.push(Float32Array.from(attribute.array));
47
+ }
48
+ });
49
+ }
50
+ return frames;
51
+ }
52
+
53
+ /**
54
+ * Extract the per time step, flat [r0, g0, b0, r1, g1, b1, ...] colour
55
+ * arrays from a time varying BufferGeometry. Returns undefined unless
56
+ * localMorphColour is requested and the geometry actually carries morph
57
+ * colour targets, mirroring the legacy vertexColors/onBeforeCompile gating.
58
+ */
59
+ const extractColorFrames = (geometry, localMorphColour) => {
60
+ const morphColors = geometry.morphAttributes.color;
61
+ if (localMorphColour && morphColors && morphColors.length > 0) {
62
+ const colorAttribute = geometry.getAttribute('color');
63
+ const frames = [];
64
+ frames.push(colorAttribute ? Float32Array.from(colorAttribute.array) :
65
+ new Float32Array(morphColors[0].array.length));
66
+ morphColors.forEach((attribute) => {
67
+ frames.push(Float32Array.from(attribute.array));
68
+ });
69
+ return frames;
70
+ }
71
+ return undefined;
72
+ }
73
+
74
+ /**
75
+ * Extract the plain, non-morph per-vertex 'color' attribute (if any) so a
76
+ * Pointset still shows static vertex colours from the file when there is
77
+ * no colour animation to blend between.
78
+ */
79
+ const extractStaticColorFrame = (geometry) => {
80
+ const colorAttribute = geometry.getAttribute('color');
81
+ return colorAttribute ? Float32Array.from(colorAttribute.array) : undefined;
82
+ }
7
83
 
8
84
  /**
9
85
  * Provides an object which stores points and provides method which controls its position.
10
86
  * This is created when a valid json file containing point is read into a {@link Zinc.Scene}
11
87
  * object.
12
88
  *
89
+ * Points are rendered as an instanced mesh - every point is one instance of a
90
+ * small quad billboarded towards the camera and kept at a constant size in
91
+ * pixels on the GPU. Time varying vertex colour and morph vertex positions
92
+ * are supported by blending between per time step position/colour arrays on
93
+ * the CPU and writing the result into the instance attributes every frame.
94
+ *
13
95
  * @class
14
96
  * @author Alan Wu
15
97
  * @return {Pointset}
@@ -22,30 +104,131 @@ const Pointset = function () {
22
104
  let labelDepthTest = false;
23
105
  let fontWeight = 500;
24
106
  let labelVisibility = true;
107
+ let positionFrames = [];
108
+ let colorFrames = undefined;
109
+ let morphVertices = false;
110
+ let morphColours = false;
111
+ const _tempColor = new THREE.Color();
25
112
 
26
113
  /**
27
- * Create the pointsets using geometry and material.
114
+ * Blend positionFrames/colorFrames based on the current inbuildTime and
115
+ * write the result into the instancePosition/instanceColor attributes.
116
+ */
117
+ const updateMorphPointset = () => {
118
+ const mesh = this.getMorph();
119
+ if (!mesh || positionFrames.length === 0) {
120
+ return;
121
+ }
122
+ const activeCount = mesh.count;
123
+ const totalSteps = positionFrames.length;
124
+ let bottomFrame = 0;
125
+ let topFrame = 0;
126
+ let proportion = 1;
127
+ if (totalSteps > 1) {
128
+ const currentTime = this.inbuildTime / this.duration * (totalSteps - 1);
129
+ bottomFrame = Math.floor(currentTime);
130
+ topFrame = Math.ceil(currentTime);
131
+ proportion = 1 - (currentTime - bottomFrame);
132
+ }
133
+ if (morphVertices) {
134
+ const bottomPositions = positionFrames[bottomFrame];
135
+ const topPositions = positionFrames[topFrame];
136
+ const positions = mesh.pointPositions;
137
+ const length = activeCount * 3;
138
+ for (let i = 0; i < length; i++) {
139
+ positions[i] = proportion * bottomPositions[i] + (1 - proportion) * topPositions[i];
140
+ }
141
+ const instancePosition = mesh.geometry.getAttribute('instancePosition');
142
+ instancePosition.array.set(positions.subarray(0, length));
143
+ instancePosition.needsUpdate = true;
144
+ this.boundingBoxUpdateRequired = true;
145
+ }
146
+ if (morphColours && colorFrames) {
147
+ const bottomColors = colorFrames[Math.min(bottomFrame, colorFrames.length - 1)];
148
+ const topColors = colorFrames[Math.min(topFrame, colorFrames.length - 1)];
149
+ for (let i = 0; i < activeCount; i++) {
150
+ const index = i * 3;
151
+ _tempColor.setRGB(
152
+ proportion * bottomColors[index] + (1 - proportion) * topColors[index],
153
+ proportion * bottomColors[index + 1] + (1 - proportion) * topColors[index + 1],
154
+ proportion * bottomColors[index + 2] + (1 - proportion) * topColors[index + 2],
155
+ );
156
+ mesh.setColorAt(i, _tempColor);
157
+ }
158
+ mesh.instanceColor.needsUpdate = true;
159
+ }
160
+ }
161
+
162
+ /**
163
+ * Create the pointset using geometry and material.
28
164
  *
29
165
  * @param {THREE.Geomtry} geometryIn - Geometry of points to be rendered.
30
- * @param {THREE.Material} materialIn - Material to be set for the lines.
166
+ * @param {THREE.Material} materialIn - Material to be set for the points,
167
+ * only its colour/opacity/alphaTest are used.
31
168
  * @param {Object} options - Provide various options
32
- * @param {Boolean} options.localTimeEnabled - A flag to indicate either the lines is
33
- * time dependent.
169
+ * @param {Boolean} options.localTimeEnabled - A flag to indicate either the points'
170
+ * positions are time dependent.
34
171
  * @param {Boolean} options.localMorphColour - A flag to indicate either the colour is
35
172
  * time dependent.
36
173
  */
37
174
  this.createMesh = (geometryIn, materialIn, options) => {
38
- if (geometryIn && materialIn) {
39
- let geometry = toBufferGeometry(geometryIn, options);
40
- console.log(geometry, options)
41
- if (options.localMorphColour && geometry.morphAttributes[ "color" ]) {
42
- materialIn.vertexColors = true;
43
- materialIn.onBeforeCompile = augmentMorphColor;
175
+ if (!geometryIn || !materialIn) {
176
+ return;
177
+ }
178
+ const geometry = toBufferGeometry(geometryIn, options);
179
+ positionFrames = extractPositionFrames(geometry, options.localTimeEnabled);
180
+ colorFrames = extractColorFrames(geometry, options.localMorphColour);
181
+ morphVertices = positionFrames.length > 1;
182
+ morphColours = !!colorFrames;
183
+ this.timeEnabled = morphVertices;
184
+ this.morphColour = morphColours;
185
+
186
+ const numberOfPoints = positionFrames[0].length / 3;
187
+ const quadGeometry = createPointQuadGeometry(numberOfPoints);
188
+ const material = createInstancedPointsMaterial(getCircularTexture());
189
+ if (materialIn.color) {
190
+ material.color.copy(materialIn.color);
191
+ }
192
+ if (materialIn.opacity !== undefined) {
193
+ material.opacity = materialIn.opacity;
194
+ }
195
+ material.transparent = !!materialIn.transparent;
196
+ material.alphaTest = materialIn.alphaTest !== undefined ? materialIn.alphaTest : 0.5;
197
+
198
+ const mesh = new InstancedPoints(quadGeometry, material, numberOfPoints);
199
+ mesh.count = numberOfPoints;
200
+ mesh.pointPositions = new Float32Array(numberOfPoints * 3);
201
+ mesh.pointPositions.set(positionFrames[0]);
202
+ mesh.userData = this;
203
+ this.drawRange = numberOfPoints;
204
+
205
+ const instancePosition = quadGeometry.getAttribute('instancePosition');
206
+ instancePosition.array.set(positionFrames[0]);
207
+ instancePosition.needsUpdate = true;
208
+
209
+ const initialColorFrame = colorFrames ? colorFrames[0] : extractStaticColorFrame(geometry);
210
+ if (initialColorFrame) {
211
+ for (let i = 0; i < numberOfPoints; i++) {
212
+ _tempColor.setRGB(initialColorFrame[i * 3], initialColorFrame[i * 3 + 1], initialColorFrame[i * 3 + 2]);
213
+ mesh.setColorAt(i, _tempColor);
44
214
  }
45
- const texture = getCircularTexture();
46
- materialIn.map = texture;
47
- let point = new Points(geometry, materialIn);
48
- this.setMesh(point, options.localTimeEnabled, options.localMorphColour);
215
+ }
216
+
217
+ this.setMorph(mesh);
218
+ geometry.dispose();
219
+ this.boundingBoxUpdateRequired = true;
220
+ }
221
+
222
+ /**
223
+ * Adapt a plain THREE.Points object (e.g. one parsed from a GLTF file)
224
+ * into this Pointset's instanced mesh representation.
225
+ */
226
+ this.setMesh = (mesh, localTimeEnabled, localMorphColour) => {
227
+ this.createMesh(mesh.geometry, mesh.material,
228
+ { localTimeEnabled, localMorphColour });
229
+ const newMesh = this.getMorph();
230
+ if (newMesh) {
231
+ newMesh.name = mesh.name;
49
232
  }
50
233
  }
51
234
 
@@ -88,31 +271,45 @@ const Pointset = function () {
88
271
  */
89
272
  this.addPoints = (coords, labels, colour) => {
90
273
  if (coords && coords.length > 0) {
91
- let current = this.drawRange;
92
- if (current === -1) {
93
- current = 0;
94
- }
95
- const geometry = this.addVertices(coords);
96
274
  let mesh = this.getMorph();
275
+ let current = this.drawRange;
97
276
  if (!mesh) {
98
- let material = new THREE.PointsMaterial({ alphaTest: 0.5, size: 10,
99
- color: colour, sizeAttenuation: false });
100
- const options = { localTimeEnabled: false, localMorphColour: false};
101
- geometry.colorsNeedUpdate = true;
102
- this.createMesh(geometry, material, options);
277
+ current = 0;
278
+ this.drawRange = 0;
279
+ const quadGeometry = createPointQuadGeometry(DEFAULT_CAPACITY);
280
+ const material = createInstancedPointsMaterial(getCircularTexture());
281
+ material.color.set(colour);
282
+ material.alphaTest = 0.5;
283
+ mesh = new InstancedPoints(quadGeometry, material, DEFAULT_CAPACITY);
284
+ mesh.pointPositions = new Float32Array(DEFAULT_CAPACITY * 3);
285
+ mesh.count = 0;
286
+ mesh.userData = this;
287
+ this.setMorph(mesh);
103
288
  }
289
+ const instancePosition = mesh.geometry.getAttribute('instancePosition');
290
+ coords.forEach(coord => {
291
+ if (this.drawRange < DEFAULT_CAPACITY) {
292
+ instancePosition.setXYZ(this.drawRange, coord[0], coord[1], coord[2]);
293
+ mesh.pointPositions[this.drawRange * 3] = coord[0];
294
+ mesh.pointPositions[this.drawRange * 3 + 1] = coord[1];
295
+ mesh.pointPositions[this.drawRange * 3 + 2] = coord[2];
296
+ this.drawRange++;
297
+ }
298
+ });
299
+ instancePosition.needsUpdate = true;
300
+ mesh.count = this.drawRange;
104
301
 
105
302
  let end = current + coords.length;
106
303
  let index = 0;
107
304
  if ((Array.isArray(labels) && labels.length === coords.length) ||
108
305
  (typeof labels === "string")) {
109
- const size = labelSets.length;
110
306
  for (current; current + index < end;) {
111
307
  const labelText = typeof labels === "string" ? labels : labels[index];
112
308
  addLabel(current + index, coords[index], labelText, this._lod._material.color);
113
309
  index++;
114
310
  }
115
311
  }
312
+ this.boundingBoxUpdateRequired = true;
116
313
  if (this.region) this.region.pickableUpdateRequired = true;
117
314
  }
118
315
  }
@@ -206,14 +403,15 @@ const Pointset = function () {
206
403
  }
207
404
  }
208
405
  /**
209
- * Set the size of the points.
406
+ * Set the size of the points in pixels.
210
407
  *
211
408
  * @param {Number} size - size to be set.
212
409
  */
213
410
  this.setSize = size => {
214
411
  if (this.morph && this.morph.material) {
215
412
  this.morph.material.size = size;
216
- this.morph.material.needsUpdate = true;
413
+ this.morph.material.userData.uniforms.pointSize.value = size;
414
+ this.morph.pointSize = size;
217
415
  }
218
416
  }
219
417
 
@@ -226,7 +424,7 @@ const Pointset = function () {
226
424
  this.setSizeAttenuation = flag => {
227
425
  if (this.morph && this.morph.material) {
228
426
  this.morph.material.sizeAttenuation = flag;
229
- this.morph.material.needsUpdate = true;
427
+ this.morph.material.userData.uniforms.sizeAttenuation.value = flag ? 1 : 0;
230
428
  }
231
429
  }
232
430
 
@@ -234,12 +432,12 @@ const Pointset = function () {
234
432
  * Get vertices at index
235
433
  */
236
434
  this.getVerticesByIndex = function(index) {
237
- if (index >= 0 && this.drawRange > index) {
238
- const positionAttribute = this.getMorph().geometry.getAttribute( 'position' );
435
+ const mesh = this.getMorph();
436
+ if (mesh && index >= 0 && this.drawRange > index) {
239
437
  return [
240
- positionAttribute.getX(index),
241
- positionAttribute.getY(index),
242
- positionAttribute.getZ(index)
438
+ mesh.pointPositions[index * 3],
439
+ mesh.pointPositions[index * 3 + 1],
440
+ mesh.pointPositions[index * 3 + 2],
243
441
  ];
244
442
  }
245
443
  return undefined;
@@ -250,24 +448,32 @@ const Pointset = function () {
250
448
  */
251
449
  this.editVertices = function(coords, i) {
252
450
  if (coords && coords.length) {
253
- let mesh = this.getMorph();
451
+ const mesh = this.getMorph();
254
452
  const maxIndex = i + coords.length - 1;
255
453
  if (!mesh || 0 > i || maxIndex >= this.drawRange) {
256
454
  return;
257
455
  } else {
258
- const positionAttribute = mesh.geometry.getAttribute( 'position' );
456
+ const instancePosition = mesh.geometry.getAttribute('instancePosition');
259
457
  let index = i;
260
458
  coords.forEach(coord => {
261
459
  const label = labelSets[index];
262
460
  if (label) {
263
461
  label.setPosition(coord[0], coord[1], coord[2]);
264
462
  }
265
- positionAttribute.setXYZ(index++, coord[0], coord[1], coord[2]);
266
-
463
+ instancePosition.setXYZ(index, coord[0], coord[1], coord[2]);
464
+ mesh.pointPositions[index * 3] = coord[0];
465
+ mesh.pointPositions[index * 3 + 1] = coord[1];
466
+ mesh.pointPositions[index * 3 + 2] = coord[2];
467
+ //Keep the base frame consistent for any pointset that also
468
+ //happens to be time varying.
469
+ if (positionFrames[0]) {
470
+ positionFrames[0][index * 3] = coord[0];
471
+ positionFrames[0][index * 3 + 1] = coord[1];
472
+ positionFrames[0][index * 3 + 2] = coord[2];
473
+ }
474
+ index++;
267
475
  });
268
- positionAttribute.needsUpdate = true;
269
- mesh.geometry.computeBoundingBox();
270
- mesh.geometry.computeBoundingSphere();
476
+ instancePosition.needsUpdate = true;
271
477
  this.boundingBoxUpdateRequired = true;
272
478
  }
273
479
  }
@@ -277,10 +483,23 @@ const Pointset = function () {
277
483
  * Delete a vertex in index.
278
484
  */
279
485
  this.deleteVertices = function(index) {
280
- let removed = Pointset.prototype.deleteVertices.call(this, index);
281
- if (removed) {
282
- removeLabel(index);
486
+ const mesh = this.getMorph();
487
+ if (!mesh || 0 > index || index >= this.drawRange) {
488
+ return this.drawRange;
489
+ }
490
+ const instancePosition = mesh.geometry.getAttribute('instancePosition');
491
+ const end = this.drawRange - 1;
492
+ instancePosition.array.copyWithin(index * 3, (index + 1) * 3, this.drawRange * 3);
493
+ mesh.pointPositions.copyWithin(index * 3, (index + 1) * 3, this.drawRange * 3);
494
+ if (mesh.instanceColor) {
495
+ mesh.instanceColor.array.copyWithin(index * 3, (index + 1) * 3, this.drawRange * 3);
496
+ mesh.instanceColor.needsUpdate = true;
283
497
  }
498
+ instancePosition.needsUpdate = true;
499
+ this.drawRange = end;
500
+ mesh.count = this.drawRange;
501
+ removeLabel(index);
502
+ this.boundingBoxUpdateRequired = true;
284
503
  return this.drawRange;
285
504
  }
286
505
 
@@ -299,21 +518,118 @@ const Pointset = function () {
299
518
  })
300
519
  }
301
520
 
521
+ /**
522
+ * Get the index of the closest vertex to centroid.
523
+ */
524
+ this.getClosestVertexIndex = function() {
525
+ let closestIndex = -1;
526
+ const mesh = this.getMorph();
527
+ if (mesh && this.drawRange > 0) {
528
+ this._b1.setFromArray(mesh.pointPositions.subarray(0, this.drawRange * 3));
529
+ this._b1.getCenter(this._v1);
530
+ let distance = -1;
531
+ for (let i = 0; i < this.drawRange; i++) {
532
+ this._v2.fromArray(mesh.pointPositions, i * 3);
533
+ const currentDistance = this._v1.distanceTo(this._v2);
534
+ if (distance === -1 || currentDistance < distance) {
535
+ distance = currentDistance;
536
+ closestIndex = i;
537
+ }
538
+ }
539
+ }
540
+ return closestIndex;
541
+ }
542
+
543
+ /**
544
+ * Get the closest vertex to centroid.
545
+ */
546
+ this.getClosestVertex = function(applyMatrixWorld) {
547
+ const mesh = this.getMorph();
548
+ if (this.closestVertexIndex === -1) {
549
+ this.closestVertexIndex = this.getClosestVertexIndex();
550
+ }
551
+ const position = new THREE.Vector3();
552
+ if (mesh && this.closestVertexIndex >= 0) {
553
+ position.fromArray(mesh.pointPositions, this.closestVertexIndex * 3);
554
+ return applyMatrixWorld ? position.applyMatrix4(mesh.matrixWorld) : position;
555
+ }
556
+ this.getBoundingBox();
557
+ position.copy(this.center);
558
+ return position;
559
+ }
560
+
561
+ /**
562
+ * Get the bounding box for the whole set of points.
563
+ *
564
+ * @return {THREE.Box3}
565
+ */
566
+ this.getBoundingBox = function() {
567
+ const mesh = this.getMorph();
568
+ if (this.visible && mesh && mesh.visible && this.drawRange > 0) {
569
+ if (this.boundingBoxUpdateRequired) {
570
+ this._b1.setFromArray(mesh.pointPositions.subarray(0, this.drawRange * 3));
571
+ this.cachedBoundingBox.copy(this._b1);
572
+ mesh.updateWorldMatrix(true, true);
573
+ this.cachedBoundingBox.applyMatrix4(mesh.matrixWorld);
574
+ this.cachedBoundingBox.getCenter(this.center);
575
+ this.radius = this.center.distanceTo(this.cachedBoundingBox.max);
576
+ this.boundingBoxUpdateRequired = false;
577
+ }
578
+ return this.cachedBoundingBox;
579
+ }
580
+ return undefined;
581
+ }
302
582
 
303
583
  /**
304
- * Turn size attenuation on/off based on the flag.
305
- *
306
- * @param {Boolean} flag - Determin either size attenuation
307
- * should be on or off.
584
+ * Check if the pointset is time varying.
585
+ *
586
+ * @return {Boolean}
587
+ */
588
+ this.isTimeVarying = () => {
589
+ return morphVertices || morphColours;
590
+ }
591
+
592
+ /**
593
+ * Set the local time of this pointset.
594
+ *
595
+ * @param {Number} time - Can be any value between 0 to duration.
596
+ */
597
+ this.setMorphTime = time => {
598
+ if (time > this.duration) {
599
+ this.inbuildTime = this.duration;
600
+ } else if (0 > time) {
601
+ this.inbuildTime = 0;
602
+ } else {
603
+ this.inbuildTime = time;
604
+ }
605
+ if (morphVertices || morphColours) {
606
+ updateMorphPointset();
607
+ }
608
+ }
609
+
610
+ /**
611
+ * Update the geometry and colours depending on the internal time and
612
+ * refresh the marker/LOD state for the render.
308
613
  */
309
614
  this.render = (delta, playAnimation, cameraControls, options) => {
310
615
  if (this.morph && cameraControls) {
311
616
  this.morph.sizePerPixel = cameraControls.pixelHeight;
312
617
  }
313
- Pointset.prototype.render.call(this, delta, playAnimation, cameraControls, options);
618
+ if (this.visible) {
619
+ this._lod.update(cameraControls, this.center);
620
+ }
621
+ if (playAnimation === true && (morphVertices || morphColours)) {
622
+ let targetTime = this.inbuildTime + delta;
623
+ if (targetTime > this.duration) {
624
+ targetTime = targetTime - this.duration;
625
+ }
626
+ this.inbuildTime = targetTime;
627
+ updateMorphPointset();
628
+ }
629
+ this.updateMarker(playAnimation, options);
314
630
  }
315
631
 
316
632
  }
317
633
 
318
634
  Pointset.prototype = Object.create(ZincObject.prototype);
319
- export { Pointset };
635
+ export { Pointset, createPointQuadGeometry };
@@ -1,7 +1,27 @@
1
- import * as THREE from 'three';
2
- import * as shader from '../shaders/textureSlide.js';
1
+ import * as THREE from 'three/webgpu';
2
+ //import * as shader from '../shaders/textureSlide.js';
3
+ import { createWebGPUMaterial } from '../tls/textureSlides.js';
3
4
  import { TexturePrimitive } from './texturePrimitive';
4
5
 
6
+
7
+ const cloneData3DTexture = (sourceTex) => {
8
+ const image = sourceTex.image;
9
+ const width = image.width;
10
+ const height = image.height;
11
+ const depth = image.depth;
12
+ const clonedData = image.data;
13
+ const targetTex = new THREE.Data3DTexture(clonedData, width, height, depth);
14
+ targetTex.format = sourceTex.format;
15
+ targetTex.type = sourceTex.type;
16
+ targetTex.minFilter = sourceTex.minFilter;
17
+ targetTex.magFilter = sourceTex.magFilter;
18
+ targetTex.wrapS = sourceTex.wrapS;
19
+ targetTex.wrapT = sourceTex.wrapT;
20
+ targetTex.wrapR = sourceTex.wrapR;
21
+ targetTex.needsUpdate = true;
22
+ return targetTex;
23
+ }
24
+
5
25
  /**
6
26
  * Provides a class which create a texture stacks in a block
7
27
  * with shaders allowing slices of texture to be displayed.
@@ -63,7 +83,7 @@ const TextureSlides = function (textureIn) {
63
83
  */
64
84
  const setUniformSlideSettingsOfMesh = (mesh, settings) => {
65
85
  const material = mesh.material;
66
- const uniforms = material.uniforms;
86
+ const uniforms = material.userData.uniforms;
67
87
  mesh.rotation.x = 0;
68
88
  mesh.rotation.y = 0;
69
89
  mesh.rotation.z = 0;
@@ -92,7 +112,6 @@ const TextureSlides = function (textureIn) {
92
112
  default:
93
113
  break;
94
114
  }
95
- material.needsUpdate = true;
96
115
  this.boundingBoxUpdateRequired = true;
97
116
  }
98
117
 
@@ -122,6 +141,7 @@ const TextureSlides = function (textureIn) {
122
141
  if (settings && settings.direction && settings.value !== undefined) {
123
142
  const geometry = new THREE.PlaneGeometry(1, 1);
124
143
  geometry.translate(0.5, 0.5, 0);
144
+ /*
125
145
  const uniforms = shader.getUniforms();
126
146
  uniforms.brightness.value = brightness;
127
147
  uniforms.contrast.value = contrast;
@@ -142,8 +162,23 @@ const TextureSlides = function (textureIn) {
142
162
  side: THREE.DoubleSide,
143
163
  transparent: false
144
164
  };
145
- const material = this.texture.getMaterial(options);
146
- material.needsUpdate = true;
165
+
166
+ //const material = this.texture.getMaterial(options);
167
+ */
168
+ const material = createWebGPUMaterial();
169
+ material.userData.uniforms.brightness.value = brightness;
170
+ material.userData.uniforms.contrast.value = contrast;
171
+ material.userData.uniforms.diffuse0.value = cloneData3DTexture(this.texture.impl);
172
+ material.userData.uniforms.diffuse1.value = cloneData3DTexture(this.texture.impl);
173
+ material.userData.uniforms.discardAlpha.value = discardAlpha;
174
+ material.userData.uniforms.depth.value = this.texture.size.depth;
175
+ material.userData.uniforms.flipY.value = flipY;
176
+ material.userData.uniforms.flipZ.value = flipZ;
177
+ if (maskTexture) {
178
+ material.userData.uniforms.mask.value = maskTexture;
179
+ }
180
+ material.userData.uniforms.maskEnabled.value = maskEnabled;
181
+ material.userData.uniforms.nChannels.value = nChannels;
147
182
  const mesh = new THREE.Mesh(geometry, material);
148
183
  mesh.name = this.groupName;
149
184
  mesh.userData = this;
@@ -288,7 +323,7 @@ const TextureSlides = function (textureIn) {
288
323
  this.cachedBoundingBox.makeEmpty();
289
324
  const vector = new THREE.Vector3(0, 0, 0);
290
325
  this.morph.children.forEach(slide => {
291
- expandBoxWithSettings(this.cachedBoundingBox, slide.material.uniforms,
326
+ expandBoxWithSettings(this.cachedBoundingBox, slide.material.userData.uniforms,
292
327
  vector);
293
328
  });
294
329
  this.morph.updateMatrixWorld (true, true);
@@ -368,10 +403,9 @@ const TextureSlides = function (textureIn) {
368
403
  this.setUniformsValue = (name, val) => {
369
404
  this.morph.children.forEach((mesh) => {
370
405
  const material = mesh.material;
371
- if (material.type === "ShaderMaterial") {
372
- const uniforms = material.uniforms;
406
+ if (material.userData.uniforms) {
407
+ const uniforms = material.userData.uniforms;
373
408
  uniforms[name].value = val;
374
- material.needsUpdate = true;
375
409
  }
376
410
  });
377
411
  }
@@ -421,7 +455,15 @@ const TextureSlides = function (textureIn) {
421
455
  this.setMask = (maskTextureIn) => {
422
456
  maskTexture = maskTextureIn;
423
457
  maskEnabled = maskTexture ? true : false;
424
- this.setUniformsValue("mask", maskTexture);
458
+ this.morph.children.forEach((mesh) => {
459
+ const material = mesh.material;
460
+ if (material.userData.uniforms) {
461
+ const uniforms = material.userData.uniforms;
462
+ if (maskTexture) {
463
+ uniforms.mask.value = maskTexture;
464
+ }
465
+ }
466
+ });
425
467
  this.setUniformsValue("maskEnabled", maskEnabled);
426
468
  }
427
469
 
@@ -441,16 +483,17 @@ const TextureSlides = function (textureIn) {
441
483
  const ratio = iTime - t0;
442
484
  this.morph.children.forEach((mesh) => {
443
485
  const material = mesh.material;
444
- if (material.type === "ShaderMaterial") {
445
- const uniforms = material.uniforms;
486
+ if (material.userData.uniforms) {
487
+ const uniforms = material.userData.uniforms;
446
488
  if (lt0 !== t0) {
447
- uniforms.diffuse0.value = this.textureList[t0].impl;
489
+ uniforms.diffuse0.value.image.data = this.textureList[t0].imageData;
490
+ uniforms.diffuse0.value.needsUpdate = true;
448
491
  }
449
492
  if (lt1 !== t1) {
450
- uniforms.diffuse1.value = this.textureList[t1].impl;
493
+ uniforms.diffuse1.value.image.data = this.textureList[t1].imageData;
494
+ uniforms.diffuse1.value.needsUpdate = true;
451
495
  }
452
496
  uniforms.time.value = ratio;
453
- material.needsUpdate = true;
454
497
  }
455
498
  });
456
499
  lt0 = t0;
@@ -1,4 +1,4 @@
1
- import * as THREE from 'three';
1
+ import * as THREE from 'three/webgpu';
2
2
  import * as shader from '../shaders/volumeRender.js';
3
3
  import { TexturePrimitive } from './texturePrimitive.js';
4
4