zincjs 2.4.1-beta.2 → 2.4.1-beta.4
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.
- package/README.md +80 -74
- package/build/zinc.js +3 -3
- package/build/zinc.js.map +1 -1
- package/package.json +1 -1
- package/src/loaders/GLTFToZincJSLoader.js +60 -3
- package/src/loaders/JSONLoader.js +2 -2
- package/src/loaders/niftiReader.js +2 -2
- package/src/loaders/primitivesLoader.js +1 -1
- package/src/primitives/geometry.js +1 -1
- package/src/primitives/glyph.js +18 -4
- package/src/primitives/glyphset.js +468 -38
- package/src/primitives/lines.js +1 -1
- package/src/primitives/lod.js +7 -0
- package/src/primitives/pointset.js +13 -22
- package/src/primitives/texturePrimitive.js +12 -0
- package/src/primitives/textureSlides.js +8 -1
- package/src/primitives/zincObject.js +4 -0
- package/src/region.js +16 -2
- package/src/renderer.js +25 -6
- package/src/scene.js +27 -3
- package/src/sceneExporter.js +94 -12
- package/src/sceneLoader.js +58 -1
- package/src/texture/texture.js +10 -0
- package/src/texture/textureArray.js +9 -0
- package/src/tsl/glyphRenderMaterial.js +53 -0
- package/src/tsl/glyphTransform.js +306 -0
- package/src/{tls → tsl}/morphColorMaterial.js +4 -14
- package/src/{tls → tsl}/textureSlides.js +4 -1
- package/vite.config.js +1 -0
- package/src/three/GLTFExporter.js +0 -2501
- /package/src/{tls → tsl}/pointsMaterial.js +0 -0
package/src/primitives/lod.js
CHANGED
|
@@ -26,6 +26,8 @@ const LOD = function (parent) {
|
|
|
26
26
|
this._loader = undefined;
|
|
27
27
|
//The owning Zinc Object
|
|
28
28
|
this._parent = parent;
|
|
29
|
+
//Levels loaded after dispose are discarded
|
|
30
|
+
this._disposed = false;
|
|
29
31
|
|
|
30
32
|
/*
|
|
31
33
|
* Add a level of LOD at the specified distance
|
|
@@ -182,6 +184,7 @@ const LOD = function (parent) {
|
|
|
182
184
|
}
|
|
183
185
|
|
|
184
186
|
this.dispose = () => {
|
|
187
|
+
this._disposed = true;
|
|
185
188
|
this.levels.forEach((level) => {
|
|
186
189
|
if (level.morph && level.morph.geometry) {
|
|
187
190
|
level.morph.geometry.dispose();
|
|
@@ -212,6 +215,10 @@ const LOD = function (parent) {
|
|
|
212
215
|
*/
|
|
213
216
|
this.lodLoader = function (distance) {
|
|
214
217
|
return (geometryIn) => {
|
|
218
|
+
if (this._disposed) {
|
|
219
|
+
geometryIn.dispose();
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
215
222
|
const material = this._material;
|
|
216
223
|
const options = {
|
|
217
224
|
localTimeEnabled: this._parent.timeEnabled,
|
|
@@ -3,7 +3,7 @@ import { Label } from './label';
|
|
|
3
3
|
import { InstancedPoints } from '../three/InstancedPoints';
|
|
4
4
|
import { ZincObject } from './zincObject';
|
|
5
5
|
import { getCircularTexture, toBufferGeometry } from '../utilities';
|
|
6
|
-
import { createInstancedPointsMaterial } from '../
|
|
6
|
+
import { createInstancedPointsMaterial } from '../tsl/pointsMaterial';
|
|
7
7
|
|
|
8
8
|
//Maximum number of points a programmatically built (addPoints) Pointset can
|
|
9
9
|
//hold - the InstancedMesh backing it is allocated once with this capacity.
|
|
@@ -26,28 +26,26 @@ const createPointQuadGeometry = (capacity) => {
|
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
28
|
* Extract the per time step, flat [x0, y0, z0, x1, y1, z1, ...] position
|
|
29
|
-
* arrays from a (possibly time varying) BufferGeometry.
|
|
30
|
-
|
|
29
|
+
* arrays from a (possibly time varying) BufferGeometry.
|
|
30
|
+
|
|
31
31
|
*/
|
|
32
32
|
const extractPositionFrames = (geometry, localTimeEnabled) => {
|
|
33
33
|
const positionAttribute = geometry.getAttribute('position');
|
|
34
|
-
const frames = [Float32Array.from(positionAttribute.array)];
|
|
35
34
|
const morphPositions = geometry.morphAttributes.position;
|
|
36
35
|
if (localTimeEnabled && morphPositions && morphPositions.length > 0) {
|
|
37
36
|
const relative = geometry.morphTargetsRelative;
|
|
38
|
-
morphPositions.
|
|
39
|
-
if (relative) {
|
|
40
|
-
|
|
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));
|
|
37
|
+
return morphPositions.map((attribute) => {
|
|
38
|
+
if (!relative) {
|
|
39
|
+
return Float32Array.from(attribute.array);
|
|
47
40
|
}
|
|
41
|
+
const frame = new Float32Array(positionAttribute.array.length);
|
|
42
|
+
for (let i = 0; i < frame.length; i++) {
|
|
43
|
+
frame[i] = positionAttribute.array[i] + attribute.array[i];
|
|
44
|
+
}
|
|
45
|
+
return frame;
|
|
48
46
|
});
|
|
49
47
|
}
|
|
50
|
-
return
|
|
48
|
+
return [Float32Array.from(positionAttribute.array)];
|
|
51
49
|
}
|
|
52
50
|
|
|
53
51
|
/**
|
|
@@ -59,14 +57,7 @@ const extractPositionFrames = (geometry, localTimeEnabled) => {
|
|
|
59
57
|
const extractColorFrames = (geometry, localMorphColour) => {
|
|
60
58
|
const morphColors = geometry.morphAttributes.color;
|
|
61
59
|
if (localMorphColour && morphColors && morphColors.length > 0) {
|
|
62
|
-
|
|
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;
|
|
60
|
+
return morphColors.map((attribute) => Float32Array.from(attribute.array));
|
|
70
61
|
}
|
|
71
62
|
return undefined;
|
|
72
63
|
}
|
|
@@ -70,4 +70,16 @@ const TexturePrimitive = function (textureIn) {
|
|
|
70
70
|
|
|
71
71
|
TexturePrimitive.prototype = Object.create(ZincObject.prototype);
|
|
72
72
|
TexturePrimitive.prototype.constructor = TexturePrimitive;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Free the textures and the other allocated memory.
|
|
76
|
+
*/
|
|
77
|
+
TexturePrimitive.prototype.dispose = function() {
|
|
78
|
+
const textures = new Set(this.textureList);
|
|
79
|
+
if (this.texture) textures.add(this.texture);
|
|
80
|
+
textures.forEach(texture => texture.dispose());
|
|
81
|
+
this.texture = undefined;
|
|
82
|
+
this.textureList = [];
|
|
83
|
+
ZincObject.prototype.dispose.call(this);
|
|
84
|
+
}
|
|
73
85
|
export { TexturePrimitive };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as THREE from 'three/webgpu';
|
|
2
2
|
//import * as shader from '../shaders/textureSlide.js';
|
|
3
|
-
import { createWebGPUMaterial } from '../
|
|
3
|
+
import { createWebGPUMaterial } from '../tsl/textureSlides.js';
|
|
4
4
|
import { TexturePrimitive } from './texturePrimitive';
|
|
5
5
|
|
|
6
6
|
|
|
@@ -18,6 +18,8 @@ const cloneData3DTexture = (sourceTex) => {
|
|
|
18
18
|
targetTex.wrapS = sourceTex.wrapS;
|
|
19
19
|
targetTex.wrapT = sourceTex.wrapT;
|
|
20
20
|
targetTex.wrapR = sourceTex.wrapR;
|
|
21
|
+
targetTex.colorSpace = sourceTex.colorSpace;
|
|
22
|
+
targetTex.anisotropy = sourceTex.anisotropy;
|
|
21
23
|
targetTex.needsUpdate = true;
|
|
22
24
|
return targetTex;
|
|
23
25
|
}
|
|
@@ -275,6 +277,11 @@ const TextureSlides = function (textureIn) {
|
|
|
275
277
|
if (slide.material)
|
|
276
278
|
slide.material.dispose();
|
|
277
279
|
});
|
|
280
|
+
if (maskTexture) {
|
|
281
|
+
maskTexture.dispose();
|
|
282
|
+
maskTexture = undefined;
|
|
283
|
+
maskEnabled = false;
|
|
284
|
+
}
|
|
278
285
|
TexturePrimitive.prototype.dispose.call(this);
|
|
279
286
|
this.boundingBoxUpdateRequired = true;
|
|
280
287
|
}
|
|
@@ -565,6 +565,10 @@ ZincObject.prototype.getBoundingBox = function() {
|
|
|
565
565
|
ZincObject.prototype.dispose = function() {
|
|
566
566
|
//multilayyers
|
|
567
567
|
this._lod.dispose();
|
|
568
|
+
if (this.marker) {
|
|
569
|
+
this.marker.dispose();
|
|
570
|
+
this.marker = undefined;
|
|
571
|
+
}
|
|
568
572
|
this.animationGroup = undefined;
|
|
569
573
|
this.mixer = undefined;
|
|
570
574
|
this.morph = undefined;
|
package/src/region.js
CHANGED
|
@@ -125,6 +125,15 @@ let Region = function (parentIn, sceneIn) {
|
|
|
125
125
|
return parent;
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
+
/**
|
|
129
|
+
* Get the {@link Scene} this region belongs to.
|
|
130
|
+
*
|
|
131
|
+
* @return {Scene}
|
|
132
|
+
*/
|
|
133
|
+
this.getScene = () => {
|
|
134
|
+
return scene;
|
|
135
|
+
}
|
|
136
|
+
|
|
128
137
|
/**
|
|
129
138
|
* Get the array of each hierarachy from the root region to this region.
|
|
130
139
|
*
|
|
@@ -436,13 +445,18 @@ let Region = function (parentIn, sceneIn) {
|
|
|
436
445
|
*/
|
|
437
446
|
this.clear = transverse => {
|
|
438
447
|
if (transverse) {
|
|
439
|
-
children.forEach(childRegion =>
|
|
448
|
+
children.forEach(childRegion => {
|
|
449
|
+
childRegion.clear(transverse);
|
|
450
|
+
group.remove(childRegion.getGroup());
|
|
451
|
+
});
|
|
440
452
|
}
|
|
441
453
|
zincObjects.forEach(zincObject => {
|
|
442
454
|
group.remove(zincObject.getGroup());
|
|
443
455
|
zincObject.dispose();
|
|
444
456
|
});
|
|
445
|
-
|
|
457
|
+
if (transverse) {
|
|
458
|
+
children = [];
|
|
459
|
+
}
|
|
446
460
|
zincObjects = [];
|
|
447
461
|
}
|
|
448
462
|
|
package/src/renderer.js
CHANGED
|
@@ -133,14 +133,20 @@ const Renderer = function (containerIn) {
|
|
|
133
133
|
}
|
|
134
134
|
//WebGPURenderer otherwise requests a device with the default
|
|
135
135
|
//maxTextureArrayLayers (256 on many adapters), which is too low for
|
|
136
|
-
//some currnetly sypported scaffold
|
|
137
|
-
//on
|
|
136
|
+
//some currnetly sypported scaffold, and the default
|
|
137
|
+
//maxStorageBuffersPerShaderStage (8 on many adapters), which is too
|
|
138
|
+
//low for compute passes like glyphset.js's GPU glyph-transform (11
|
|
139
|
+
//storage buffers). We will request the highest possible on the device
|
|
140
|
+
//for both.
|
|
138
141
|
if (parameters.device === undefined && parameters.requiredLimits === undefined &&
|
|
139
142
|
typeof navigator !== 'undefined' && navigator.gpu) {
|
|
140
143
|
try {
|
|
141
144
|
const adapter = await navigator.gpu.requestAdapter({ powerPreference: parameters.powerPreference });
|
|
142
145
|
if (adapter) {
|
|
143
|
-
parameters.requiredLimits = {
|
|
146
|
+
parameters.requiredLimits = {
|
|
147
|
+
maxTextureArrayLayers: adapter.limits.maxTextureArrayLayers,
|
|
148
|
+
maxStorageBuffersPerShaderStage: adapter.limits.maxStorageBuffersPerShaderStage,
|
|
149
|
+
};
|
|
144
150
|
}
|
|
145
151
|
} catch (err) {
|
|
146
152
|
//Fall through and let WebGPURenderer's own adapter/device request
|
|
@@ -719,10 +725,23 @@ const Renderer = function (containerIn) {
|
|
|
719
725
|
}
|
|
720
726
|
}
|
|
721
727
|
|
|
728
|
+
/**
|
|
729
|
+
* Check if the renderer is running on the WebGL 2 fallback backend.
|
|
730
|
+
* WebGPURenderer never uses WebGL 1, so this is false when WebGPU is used.
|
|
731
|
+
*
|
|
732
|
+
* @return {Boolean}
|
|
733
|
+
*/
|
|
722
734
|
this.isWebGL2 = () => {
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
735
|
+
return renderer?.backend?.isWebGLBackend === true;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/**
|
|
739
|
+
* Check if the renderer is running on the WebGPU backend.
|
|
740
|
+
*
|
|
741
|
+
* @return {Boolean}
|
|
742
|
+
*/
|
|
743
|
+
this.isWebGPU = () => {
|
|
744
|
+
return renderer?.backend?.isWebGPUBackend === true;
|
|
726
745
|
}
|
|
727
746
|
};
|
|
728
747
|
|
package/src/scene.js
CHANGED
|
@@ -9,7 +9,7 @@ import { SceneExporter } from './sceneExporter';
|
|
|
9
9
|
import { SceneLoader } from './sceneLoader';
|
|
10
10
|
import { Viewport } from './controls';
|
|
11
11
|
import { createPointQuadGeometry } from './primitives/pointset';
|
|
12
|
-
import { createInstancedPointsMaterial } from './
|
|
12
|
+
import { createInstancedPointsMaterial } from './tsl/pointsMaterial';
|
|
13
13
|
import {
|
|
14
14
|
createBufferGeometry,
|
|
15
15
|
createNewSpriteText,
|
|
@@ -988,8 +988,32 @@ const Scene = function (containerIn, rendererIn) {
|
|
|
988
988
|
return undefined;
|
|
989
989
|
}
|
|
990
990
|
|
|
991
|
+
/**
|
|
992
|
+
* Check if the renderer is running on the WebGL 2 fallback backend.
|
|
993
|
+
*
|
|
994
|
+
* @return {Boolean}
|
|
995
|
+
*/
|
|
991
996
|
this.isWebGL2 = () => {
|
|
992
|
-
return rendererIn
|
|
997
|
+
return rendererIn?.backend?.isWebGLBackend === true;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
/**
|
|
1001
|
+
* Check if the renderer is running on the WebGPU backend.
|
|
1002
|
+
*
|
|
1003
|
+
* @return {Boolean}
|
|
1004
|
+
*/
|
|
1005
|
+
this.isWebGPU = () => {
|
|
1006
|
+
return rendererIn?.backend?.isWebGPUBackend === true;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
/**
|
|
1010
|
+
* Get the THREE.WebGPURenderer this scene is being drawn with, for code
|
|
1011
|
+
* that needs to dispatch its own GPU work (e.g. a TSL compute pass).
|
|
1012
|
+
*
|
|
1013
|
+
* @return {THREE.WebGPURenderer}
|
|
1014
|
+
*/
|
|
1015
|
+
this.getRenderer = () => {
|
|
1016
|
+
return rendererIn;
|
|
993
1017
|
}
|
|
994
1018
|
|
|
995
1019
|
/**
|
|
@@ -1001,7 +1025,7 @@ const Scene = function (containerIn, rendererIn) {
|
|
|
1001
1025
|
rootRegion.clear(true);
|
|
1002
1026
|
this.clearZincObjectAddedCallbacks();
|
|
1003
1027
|
this.clearZincObjectRemovedCallbacks();
|
|
1004
|
-
sceneLoader.
|
|
1028
|
+
sceneLoader.cancelPendingLoads();
|
|
1005
1029
|
if (zincCameraControls) {
|
|
1006
1030
|
zincCameraControls.calculateMaxAllowedDistance(this);
|
|
1007
1031
|
}
|
package/src/sceneExporter.js
CHANGED
|
@@ -1,4 +1,49 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { BufferAttribute } from 'three/webgpu';
|
|
2
|
+
import { GLTFExporter } from 'three/examples/jsm/exporters/GLTFExporter';
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
* GLTFExporter only writes POSITION and NORMAL morph targets. glTF allows
|
|
6
|
+
* COLOR_n morph targets as well (GLTFLoader reads them), so add them to the
|
|
7
|
+
* targets the exporter has already written. Like other glTF morph targets
|
|
8
|
+
* they are stored relative to the base attribute.
|
|
9
|
+
*/
|
|
10
|
+
class GLTFMorphColourPlugin {
|
|
11
|
+
constructor(writer) {
|
|
12
|
+
this.writer = writer;
|
|
13
|
+
this.name = 'ZINC_morph_colour';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
writeMesh(mesh, meshDef) {
|
|
17
|
+
const geometry = mesh.geometry;
|
|
18
|
+
const morphColours = geometry && geometry.morphAttributes ?
|
|
19
|
+
geometry.morphAttributes.color : undefined;
|
|
20
|
+
const baseColour = geometry ? geometry.attributes.color : undefined;
|
|
21
|
+
const primitive = meshDef.primitives ? meshDef.primitives[0] : undefined;
|
|
22
|
+
if (!morphColours || !baseColour || !primitive || !primitive.targets)
|
|
23
|
+
return;
|
|
24
|
+
// All primitives of a mesh share the same targets array.
|
|
25
|
+
const targets = primitive.targets;
|
|
26
|
+
const itemSize = baseColour.itemSize;
|
|
27
|
+
const count = baseColour.count;
|
|
28
|
+
for (let i = 0; i < targets.length && i < morphColours.length; i++) {
|
|
29
|
+
const attribute = morphColours[i];
|
|
30
|
+
if (attribute.itemSize !== itemSize || attribute.count < count) {
|
|
31
|
+
console.warn('GLTFExporter: Colour morph target does not match the base colour, skipped.');
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const array = new Float32Array(count * itemSize);
|
|
35
|
+
for (let j = 0; j < count; j++) {
|
|
36
|
+
for (let a = 0; a < itemSize; a++) {
|
|
37
|
+
const value = attribute.getComponent(j, a);
|
|
38
|
+
array[j * itemSize + a] = geometry.morphTargetsRelative ?
|
|
39
|
+
value : value - baseColour.getComponent(j, a);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
targets[i].COLOR_0 = this.writer.processAccessor(
|
|
43
|
+
new BufferAttribute(array, itemSize), geometry);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
2
47
|
|
|
3
48
|
/**
|
|
4
49
|
* Provides an object which uses for exporting the scene
|
|
@@ -10,22 +55,59 @@ import { GLTFExporter } from './three/GLTFExporter';
|
|
|
10
55
|
const SceneExporter = function (sceneIn) {
|
|
11
56
|
const scene = sceneIn;
|
|
12
57
|
|
|
13
|
-
|
|
58
|
+
// Matches the exporter's onlyVisible pruning - animations targeting a
|
|
59
|
+
// hidden node would otherwise produce channels with no target node.
|
|
60
|
+
const isVisible = (object) => {
|
|
61
|
+
for (let current = object; current; current = current.parent) {
|
|
62
|
+
if (current.visible === false) return false;
|
|
63
|
+
}
|
|
64
|
+
return true;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
this.exportGLTF = async (binary) => {
|
|
14
68
|
const rootRegion = scene.getRootRegion();
|
|
15
69
|
const zincObjects = rootRegion.getAllObjects(true);
|
|
16
70
|
const animations = [];
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
71
|
+
const glyphsetsToRestore = [];
|
|
72
|
+
for (const zincObject of zincObjects) {
|
|
73
|
+
if (zincObject.animationClip && zincObject.animationClip[0]) {
|
|
74
|
+
const mesh = zincObject.getMorph();
|
|
75
|
+
if (mesh && isVisible(mesh)) {
|
|
76
|
+
// The exporter resolves tracks against the scene root, but morph
|
|
77
|
+
// clips target '' (the mesh itself), so retarget them by uuid.
|
|
78
|
+
const clip = zincObject.animationClip[0].clone();
|
|
79
|
+
clip.tracks.forEach(track => { track.name = mesh.uuid + track.name; });
|
|
80
|
+
animations.push(clip);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// Glyphsets with GPU-compute-driven colour intentionally keep
|
|
84
|
+
// instanceColor null during normal rendering (see glyphset.js's
|
|
85
|
+
// prepareColorForExport() for why) - populate it just for this
|
|
86
|
+
// export, then undo it again afterwards.
|
|
87
|
+
if (zincObject.isGlyphset && zincObject.prepareColorForExport) {
|
|
88
|
+
await zincObject.prepareColorForExport();
|
|
89
|
+
glyphsetsToRestore.push(zincObject);
|
|
20
90
|
}
|
|
21
|
-
}
|
|
91
|
+
}
|
|
22
92
|
const exporter = new GLTFExporter();
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
93
|
+
exporter.register(writer => new GLTFMorphColourPlugin(writer));
|
|
94
|
+
const options = { binary, animations, onlyVisible: true };
|
|
95
|
+
try {
|
|
96
|
+
return await new Promise((resolve, reject) => {
|
|
97
|
+
exporter.parse(
|
|
98
|
+
scene.getThreeJSScene(),
|
|
99
|
+
function ( gltf ) {
|
|
100
|
+
resolve(gltf);
|
|
101
|
+
},
|
|
102
|
+
function ( error ) {
|
|
103
|
+
console.error("Unable to export GLTF.", error);
|
|
104
|
+
reject(error);
|
|
105
|
+
},
|
|
106
|
+
options );
|
|
107
|
+
});
|
|
108
|
+
} finally {
|
|
109
|
+
glyphsetsToRestore.forEach(zincObject => zincObject.clearColorExportState());
|
|
110
|
+
}
|
|
29
111
|
}
|
|
30
112
|
}
|
|
31
113
|
|
package/src/sceneLoader.js
CHANGED
|
@@ -27,7 +27,30 @@ const SceneLoader = function (sceneIn) {
|
|
|
27
27
|
this.progressMap = {};
|
|
28
28
|
let viewLoaded = false;
|
|
29
29
|
let errorDownload = false;
|
|
30
|
+
//Incremented when pending loads are cancelled, loads requested
|
|
31
|
+
//before the increment are discarded once they complete.
|
|
32
|
+
let generation = 0;
|
|
30
33
|
const primitivesLoader = new PrimitivesLoader();
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Cancel all pending downloads, objects from the cancelled downloads
|
|
37
|
+
* will be discarded instead of being added into the scene once they
|
|
38
|
+
* arrive.
|
|
39
|
+
*/
|
|
40
|
+
this.cancelPendingLoads = () => {
|
|
41
|
+
generation++;
|
|
42
|
+
this.toBeDownloaded = 0;
|
|
43
|
+
this.progressMap = {};
|
|
44
|
+
viewLoaded = false;
|
|
45
|
+
errorDownload = false;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
//Return a function which returns true if the loads requested before
|
|
49
|
+
//this call have been cancelled.
|
|
50
|
+
const createStaleCheck = () => {
|
|
51
|
+
const requestGeneration = generation;
|
|
52
|
+
return () => requestGeneration !== generation;
|
|
53
|
+
}
|
|
31
54
|
/**
|
|
32
55
|
* This function returns a three component array, which contains
|
|
33
56
|
* [totalsize, totalLoaded and errorDownload] of all the downloads happening
|
|
@@ -62,7 +85,9 @@ const SceneLoader = function (sceneIn) {
|
|
|
62
85
|
}
|
|
63
86
|
|
|
64
87
|
this.onError = finishCallback => {
|
|
88
|
+
const isStale = createStaleCheck();
|
|
65
89
|
return xhr => {
|
|
90
|
+
if (isStale()) return;
|
|
66
91
|
this.toBeDownloaded = this.toBeDownloaded - 1;
|
|
67
92
|
errorDownload = true;
|
|
68
93
|
console.error(`There is an issue with external resource ${xhr?.responseURL ? ": " + xhr?.responseURL: ""}.`);
|
|
@@ -116,6 +141,7 @@ const SceneLoader = function (sceneIn) {
|
|
|
116
141
|
this.loadViewURL = (url, finishCallback) => {
|
|
117
142
|
this.toBeDownloaded += 1;
|
|
118
143
|
const requestURL = url;
|
|
144
|
+
const isStale = createStaleCheck();
|
|
119
145
|
fetch(requestURL)
|
|
120
146
|
.then((response) => {
|
|
121
147
|
if (!response.ok) {
|
|
@@ -124,6 +150,7 @@ const SceneLoader = function (sceneIn) {
|
|
|
124
150
|
return response.json();
|
|
125
151
|
})
|
|
126
152
|
.then((viewData) => {
|
|
153
|
+
if (isStale()) return;
|
|
127
154
|
scene.setupMultipleViews("default", { "default" : viewData });
|
|
128
155
|
scene.resetView();
|
|
129
156
|
viewLoaded = true;
|
|
@@ -176,6 +203,7 @@ const SceneLoader = function (sceneIn) {
|
|
|
176
203
|
*/
|
|
177
204
|
this.loadFromViewURL = (targetRegion, jsonFilePrefix, finishCallback) => {
|
|
178
205
|
const requestURL = jsonFilePrefix + "_view.json";
|
|
206
|
+
const isStale = createStaleCheck();
|
|
179
207
|
fetch(requestURL)
|
|
180
208
|
.then((response) => {
|
|
181
209
|
if (!response.ok) {
|
|
@@ -184,6 +212,7 @@ const SceneLoader = function (sceneIn) {
|
|
|
184
212
|
return response.json();
|
|
185
213
|
})
|
|
186
214
|
.then((viewData) => {
|
|
215
|
+
if (isStale()) return;
|
|
187
216
|
scene.loadView(viewData);
|
|
188
217
|
const urls = [];
|
|
189
218
|
const filename_prefix = jsonFilePrefix + "_";
|
|
@@ -209,7 +238,12 @@ const SceneLoader = function (sceneIn) {
|
|
|
209
238
|
//Internal loader for a regular zinc geometry.
|
|
210
239
|
const linesloader = (region, localTimeEnabled, localMorphColour, groupName,
|
|
211
240
|
anatomicalId, renderOrder, lod, tubeLines, finishCallback) => {
|
|
241
|
+
const isStale = createStaleCheck();
|
|
212
242
|
return (geometry, materials) => {
|
|
243
|
+
if (isStale()) {
|
|
244
|
+
geometry.dispose();
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
213
247
|
const newLines = tubeLines ? new TubeLines() : new Lines();
|
|
214
248
|
let material = undefined;
|
|
215
249
|
if (materials && materials[0]) {
|
|
@@ -276,7 +310,9 @@ const SceneLoader = function (sceneIn) {
|
|
|
276
310
|
}
|
|
277
311
|
|
|
278
312
|
const glyphsetloader = (region, glyphurl, groupName, finishCallback, options) => {
|
|
313
|
+
const isStale = createStaleCheck();
|
|
279
314
|
return (data) => {
|
|
315
|
+
if (isStale()) return;
|
|
280
316
|
let glyphsetData = data;
|
|
281
317
|
if (typeof glyphsetData === 'string' || glyphsetData instanceof String) {
|
|
282
318
|
glyphsetData = JSON.parse(data);
|
|
@@ -292,6 +328,7 @@ const SceneLoader = function (sceneIn) {
|
|
|
292
328
|
newGlyphset.setDuration(scene.getDuration());
|
|
293
329
|
newGlyphset.groupName = groupName;
|
|
294
330
|
let myCallback = () => {
|
|
331
|
+
if (isStale()) return;
|
|
295
332
|
--this.toBeDownloaded;
|
|
296
333
|
if (finishCallback != undefined && (typeof finishCallback == 'function'))
|
|
297
334
|
finishCallback(newGlyphset);
|
|
@@ -358,7 +395,12 @@ const SceneLoader = function (sceneIn) {
|
|
|
358
395
|
|
|
359
396
|
//Internal loader for zinc pointset.
|
|
360
397
|
const pointsetloader = (region, localTimeEnabled, localMorphColour, groupName, anatomicalId, renderOrder, finishCallback) => {
|
|
398
|
+
const isStale = createStaleCheck();
|
|
361
399
|
return (geometry, materials) => {
|
|
400
|
+
if (isStale()) {
|
|
401
|
+
geometry.dispose();
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
362
404
|
const newPointset = new Pointset();
|
|
363
405
|
let material = new THREE.PointsMaterial(
|
|
364
406
|
{
|
|
@@ -554,7 +596,12 @@ const SceneLoader = function (sceneIn) {
|
|
|
554
596
|
}
|
|
555
597
|
if (newTexture) {
|
|
556
598
|
newTexture.groupName = groupName;
|
|
599
|
+
const isStale = createStaleCheck();
|
|
557
600
|
let myCallback = () => {
|
|
601
|
+
if (isStale()) {
|
|
602
|
+
newTexture.dispose();
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
558
605
|
//Add zincObject after it has sort out all the required download
|
|
559
606
|
region.addZincObject(newTexture);
|
|
560
607
|
--this.toBeDownloaded;
|
|
@@ -582,6 +629,7 @@ const SceneLoader = function (sceneIn) {
|
|
|
582
629
|
loadTexture(region, undefined, url, groupName, finishCallback, options);
|
|
583
630
|
} else {
|
|
584
631
|
const requestURL = url;
|
|
632
|
+
const isStale = createStaleCheck();
|
|
585
633
|
fetch(requestURL)
|
|
586
634
|
.then((response) => {
|
|
587
635
|
if (!response.ok) {
|
|
@@ -591,6 +639,7 @@ const SceneLoader = function (sceneIn) {
|
|
|
591
639
|
return response.json().then((textureData) => ({ textureData, referenceURL }));
|
|
592
640
|
})
|
|
593
641
|
.then(({textureData, referenceURL}) => {
|
|
642
|
+
if (isStale()) return;
|
|
594
643
|
loadTexture(region, referenceURL, textureData, groupName, finishCallback, options);
|
|
595
644
|
})
|
|
596
645
|
.catch((error) => {
|
|
@@ -662,7 +711,12 @@ const SceneLoader = function (sceneIn) {
|
|
|
662
711
|
options,
|
|
663
712
|
finishCallback
|
|
664
713
|
) => {
|
|
714
|
+
const isStale = createStaleCheck();
|
|
665
715
|
return (geometry, materials) => {
|
|
716
|
+
if (isStale()) {
|
|
717
|
+
geometry.dispose();
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
666
720
|
let material = undefined;
|
|
667
721
|
if (materials && materials[0]) {
|
|
668
722
|
material = materials[0];
|
|
@@ -831,7 +885,8 @@ const SceneLoader = function (sceneIn) {
|
|
|
831
885
|
*/
|
|
832
886
|
this.loadGLTF = (region, url, finishCallback, allCompletedCallback, options) => {
|
|
833
887
|
const loader = new GLTFToZincJSLoader();
|
|
834
|
-
loader.load(scene, region, url, finishCallback, allCompletedCallback, options
|
|
888
|
+
loader.load(scene, region, url, finishCallback, allCompletedCallback, options,
|
|
889
|
+
createStaleCheck());
|
|
835
890
|
}
|
|
836
891
|
|
|
837
892
|
let loadRegions = (currentRegion, referenceURL, regions, callback) => {
|
|
@@ -993,6 +1048,7 @@ const SceneLoader = function (sceneIn) {
|
|
|
993
1048
|
*/
|
|
994
1049
|
this.loadMetadataURL = (targetRegion, url, finishCallback, allCompletedCallback, options) => {
|
|
995
1050
|
const requestURL = url;
|
|
1051
|
+
const isStale = createStaleCheck();
|
|
996
1052
|
fetch(requestURL)
|
|
997
1053
|
.then((response) => {
|
|
998
1054
|
if (!response.ok) {
|
|
@@ -1002,6 +1058,7 @@ const SceneLoader = function (sceneIn) {
|
|
|
1002
1058
|
return response.json().then((metadata) => ({ metadata, referenceURL }));
|
|
1003
1059
|
})
|
|
1004
1060
|
.then(({metadata, referenceURL}) => {
|
|
1061
|
+
if (isStale()) return;
|
|
1005
1062
|
scene.resetMetadata();
|
|
1006
1063
|
scene.resetDuration();
|
|
1007
1064
|
viewLoaded = false;
|
package/src/texture/texture.js
CHANGED
|
@@ -70,6 +70,16 @@ Texture.prototype.loadFromImages = async function (srcArrays) {
|
|
|
70
70
|
return
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
/**
|
|
74
|
+
* Free the memory allocated for this texture.
|
|
75
|
+
*/
|
|
76
|
+
Texture.prototype.dispose = function () {
|
|
77
|
+
if (this.impl) {
|
|
78
|
+
this.impl.dispose();
|
|
79
|
+
this.impl = undefined;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
73
83
|
/**
|
|
74
84
|
* Return true if the texture is ready for consumption.
|
|
75
85
|
*
|
|
@@ -121,4 +121,13 @@ const TextureArray = function () {
|
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
TextureArray.prototype = Object.create(Texture.prototype);
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Free the memory allocated for this texture array.
|
|
127
|
+
*/
|
|
128
|
+
TextureArray.prototype.dispose = function () {
|
|
129
|
+
Texture.prototype.dispose.call(this);
|
|
130
|
+
this.imageData = undefined;
|
|
131
|
+
}
|
|
132
|
+
|
|
124
133
|
export { TextureArray };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import * as THREE from 'three/webgpu';
|
|
2
|
+
import { Fn, instanceIndex, positionGeometry, materialColor, materialOpacity, vec4 } from 'three/tsl';
|
|
3
|
+
import { NODE_MATERIAL_CLASSES } from './morphColorMaterial';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Builds a NodeMaterial that renders each glyph instance's transform and
|
|
7
|
+
* colour read directly from a glyphCompute's (see ../tsl/glyphTransform.js)
|
|
8
|
+
* GPU output buffers, via a custom positionNode/colorNode - i.e. no
|
|
9
|
+
* instanceMatrix/instanceColor CPU sync is involved in producing what gets
|
|
10
|
+
* drawn. This is what lets Glyphset render every frame of an animation
|
|
11
|
+
* straight from the compute pass's output with zero GPU->CPU readback.
|
|
12
|
+
*
|
|
13
|
+
* Per NodeMaterial.setupPosition(), setting `positionNode` fully replaces
|
|
14
|
+
* (not composes with) the automatic per-instance instanceMatrix transform a
|
|
15
|
+
* stock THREE.InstancedMesh would otherwise apply - so instanceMatrix is
|
|
16
|
+
* simply irrelevant to rendering once this is set, no double-transform risk.
|
|
17
|
+
* (instanceMatrix is still kept in sync separately, when paused, purely so
|
|
18
|
+
* Glyphset's existing bounding-box/raycast code - which does need a
|
|
19
|
+
* CPU-side transform to read - keeps working unmodified.)
|
|
20
|
+
*
|
|
21
|
+
* @param {THREE.Material} sourceMaterial - the material JSONLoader produced
|
|
22
|
+
* for this glyph geometry; converted to its NodeMaterial equivalent.
|
|
23
|
+
* @param {Object} glyphCompute - return value of createGlyphTransformCompute().
|
|
24
|
+
* @returns {THREE.NodeMaterial}
|
|
25
|
+
*/
|
|
26
|
+
function createGlyphInstancedMaterial(sourceMaterial, glyphCompute) {
|
|
27
|
+
let material = sourceMaterial;
|
|
28
|
+
if (!material || !material.isNodeMaterial) {
|
|
29
|
+
const NodeMaterialClass = (sourceMaterial && NODE_MATERIAL_CLASSES[sourceMaterial.type]) || THREE.MeshPhongNodeMaterial;
|
|
30
|
+
material = new NodeMaterialClass();
|
|
31
|
+
if (sourceMaterial) material.copy(sourceMaterial);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const { position, axis1, axis2, axis3, color } = glyphCompute.outputs;
|
|
35
|
+
|
|
36
|
+
material.positionNode = Fn(() => {
|
|
37
|
+
const idx = instanceIndex;
|
|
38
|
+
return position.element(idx)
|
|
39
|
+
.add(axis1.element(idx).mul(positionGeometry.x))
|
|
40
|
+
.add(axis2.element(idx).mul(positionGeometry.y))
|
|
41
|
+
.add(axis3.element(idx).mul(positionGeometry.z));
|
|
42
|
+
})();
|
|
43
|
+
|
|
44
|
+
if (color) {
|
|
45
|
+
material.colorNode = Fn(() => {
|
|
46
|
+
return vec4(color.element(instanceIndex).mul(materialColor), materialOpacity);
|
|
47
|
+
})();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return material;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export { createGlyphInstancedMaterial };
|