zincjs 2.4.1-beta.3 → 2.4.1-beta.5
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 +86 -70
- package/build/zinc.js +5 -5
- package/build/zinc.js.map +1 -1
- package/package.json +1 -1
- package/src/controls.js +71 -49
- package/src/loaders/GLTFToZincJSLoader.js +4 -1
- package/src/loaders/JSONLoader.js +2 -2
- package/src/loaders/primitivesLoader.js +60 -17
- package/src/minimap.js +48 -13
- package/src/primitives/geometry.js +2 -0
- package/src/primitives/glyph.js +11 -1
- package/src/primitives/glyphset.js +33 -7
- package/src/primitives/label.js +39 -24
- package/src/primitives/lines.js +2 -0
- package/src/primitives/lines2.js +76 -24
- package/src/primitives/lod.js +18 -4
- package/src/primitives/marker.js +6 -1
- package/src/primitives/markerCluster.js +5 -1
- package/src/primitives/pointset.js +33 -28
- package/src/primitives/texturePrimitive.js +12 -0
- package/src/primitives/textureSlides.js +24 -3
- package/src/primitives/textureVolume.js +2 -0
- package/src/primitives/tubeLines.js +4 -0
- package/src/primitives/zincObject.js +80 -10
- package/src/region.js +92 -37
- package/src/renderRequests.js +20 -0
- package/src/renderer.js +81 -9
- package/src/scene.js +133 -23
- package/src/sceneExporter.js +4 -0
- package/src/sceneLoader.js +114 -18
- package/src/textSprite.js +211 -0
- package/src/texture/texture.js +10 -0
- package/src/texture/textureArray.js +9 -0
- package/src/utilities.js +147 -47
package/src/sceneLoader.js
CHANGED
|
@@ -27,7 +27,28 @@ const SceneLoader = function (sceneIn) {
|
|
|
27
27
|
this.progressMap = {};
|
|
28
28
|
let viewLoaded = false;
|
|
29
29
|
let errorDownload = false;
|
|
30
|
+
let generation = 0;
|
|
30
31
|
const primitivesLoader = new PrimitivesLoader();
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Cancel all pending downloads, objects from the cancelled downloads
|
|
35
|
+
* will be discarded instead of being added into the scene once they
|
|
36
|
+
* arrive.
|
|
37
|
+
*/
|
|
38
|
+
this.cancelPendingLoads = () => {
|
|
39
|
+
generation++;
|
|
40
|
+
this.toBeDownloaded = 0;
|
|
41
|
+
this.progressMap = {};
|
|
42
|
+
viewLoaded = false;
|
|
43
|
+
errorDownload = false;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
//Return a function which returns true if the loads requested before
|
|
47
|
+
//this call have been cancelled.
|
|
48
|
+
const createStaleCheck = () => {
|
|
49
|
+
const requestGeneration = generation;
|
|
50
|
+
return () => requestGeneration !== generation;
|
|
51
|
+
}
|
|
31
52
|
/**
|
|
32
53
|
* This function returns a three component array, which contains
|
|
33
54
|
* [totalsize, totalLoaded and errorDownload] of all the downloads happening
|
|
@@ -62,7 +83,9 @@ const SceneLoader = function (sceneIn) {
|
|
|
62
83
|
}
|
|
63
84
|
|
|
64
85
|
this.onError = finishCallback => {
|
|
86
|
+
const isStale = createStaleCheck();
|
|
65
87
|
return xhr => {
|
|
88
|
+
if (isStale()) return;
|
|
66
89
|
this.toBeDownloaded = this.toBeDownloaded - 1;
|
|
67
90
|
errorDownload = true;
|
|
68
91
|
console.error(`There is an issue with external resource ${xhr?.responseURL ? ": " + xhr?.responseURL: ""}.`);
|
|
@@ -116,6 +139,7 @@ const SceneLoader = function (sceneIn) {
|
|
|
116
139
|
this.loadViewURL = (url, finishCallback) => {
|
|
117
140
|
this.toBeDownloaded += 1;
|
|
118
141
|
const requestURL = url;
|
|
142
|
+
const isStale = createStaleCheck();
|
|
119
143
|
fetch(requestURL)
|
|
120
144
|
.then((response) => {
|
|
121
145
|
if (!response.ok) {
|
|
@@ -124,6 +148,7 @@ const SceneLoader = function (sceneIn) {
|
|
|
124
148
|
return response.json();
|
|
125
149
|
})
|
|
126
150
|
.then((viewData) => {
|
|
151
|
+
if (isStale()) return;
|
|
127
152
|
scene.setupMultipleViews("default", { "default" : viewData });
|
|
128
153
|
scene.resetView();
|
|
129
154
|
viewLoaded = true;
|
|
@@ -176,6 +201,7 @@ const SceneLoader = function (sceneIn) {
|
|
|
176
201
|
*/
|
|
177
202
|
this.loadFromViewURL = (targetRegion, jsonFilePrefix, finishCallback) => {
|
|
178
203
|
const requestURL = jsonFilePrefix + "_view.json";
|
|
204
|
+
const isStale = createStaleCheck();
|
|
179
205
|
fetch(requestURL)
|
|
180
206
|
.then((response) => {
|
|
181
207
|
if (!response.ok) {
|
|
@@ -184,6 +210,7 @@ const SceneLoader = function (sceneIn) {
|
|
|
184
210
|
return response.json();
|
|
185
211
|
})
|
|
186
212
|
.then((viewData) => {
|
|
213
|
+
if (isStale()) return;
|
|
187
214
|
scene.loadView(viewData);
|
|
188
215
|
const urls = [];
|
|
189
216
|
const filename_prefix = jsonFilePrefix + "_";
|
|
@@ -209,7 +236,12 @@ const SceneLoader = function (sceneIn) {
|
|
|
209
236
|
//Internal loader for a regular zinc geometry.
|
|
210
237
|
const linesloader = (region, localTimeEnabled, localMorphColour, groupName,
|
|
211
238
|
anatomicalId, renderOrder, lod, tubeLines, finishCallback) => {
|
|
239
|
+
const isStale = createStaleCheck();
|
|
212
240
|
return (geometry, materials) => {
|
|
241
|
+
if (isStale()) {
|
|
242
|
+
geometry.dispose();
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
213
245
|
const newLines = tubeLines ? new TubeLines() : new Lines();
|
|
214
246
|
let material = undefined;
|
|
215
247
|
if (materials && materials[0]) {
|
|
@@ -276,7 +308,9 @@ const SceneLoader = function (sceneIn) {
|
|
|
276
308
|
}
|
|
277
309
|
|
|
278
310
|
const glyphsetloader = (region, glyphurl, groupName, finishCallback, options) => {
|
|
311
|
+
const isStale = createStaleCheck();
|
|
279
312
|
return (data) => {
|
|
313
|
+
if (isStale()) return;
|
|
280
314
|
let glyphsetData = data;
|
|
281
315
|
if (typeof glyphsetData === 'string' || glyphsetData instanceof String) {
|
|
282
316
|
glyphsetData = JSON.parse(data);
|
|
@@ -292,6 +326,7 @@ const SceneLoader = function (sceneIn) {
|
|
|
292
326
|
newGlyphset.setDuration(scene.getDuration());
|
|
293
327
|
newGlyphset.groupName = groupName;
|
|
294
328
|
let myCallback = () => {
|
|
329
|
+
if (isStale()) return;
|
|
295
330
|
--this.toBeDownloaded;
|
|
296
331
|
if (finishCallback != undefined && (typeof finishCallback == 'function'))
|
|
297
332
|
finishCallback(newGlyphset);
|
|
@@ -358,7 +393,12 @@ const SceneLoader = function (sceneIn) {
|
|
|
358
393
|
|
|
359
394
|
//Internal loader for zinc pointset.
|
|
360
395
|
const pointsetloader = (region, localTimeEnabled, localMorphColour, groupName, anatomicalId, renderOrder, finishCallback) => {
|
|
396
|
+
const isStale = createStaleCheck();
|
|
361
397
|
return (geometry, materials) => {
|
|
398
|
+
if (isStale()) {
|
|
399
|
+
geometry.dispose();
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
362
402
|
const newPointset = new Pointset();
|
|
363
403
|
let material = new THREE.PointsMaterial(
|
|
364
404
|
{
|
|
@@ -408,7 +448,8 @@ const SceneLoader = function (sceneIn) {
|
|
|
408
448
|
const loader = new STLLoader();
|
|
409
449
|
loader.crossOrigin = "Anonymous";
|
|
410
450
|
loader.load(url, meshloader(region, colour, opacity, false,
|
|
411
|
-
false, groupName, undefined, undefined,
|
|
451
|
+
false, groupName, undefined, undefined, {}, finishCallback),
|
|
452
|
+
this.onProgress(url), this.onError(finishCallback));
|
|
412
453
|
}
|
|
413
454
|
|
|
414
455
|
/**
|
|
@@ -426,8 +467,9 @@ const SceneLoader = function (sceneIn) {
|
|
|
426
467
|
const opacity = Zinc.defaultOpacity;
|
|
427
468
|
const loader = new OBJLoader();
|
|
428
469
|
loader.crossOrigin = "Anonymous";
|
|
429
|
-
loader.load(url,
|
|
430
|
-
false, groupName, undefined, undefined,
|
|
470
|
+
loader.load(url, objloader(region, colour, opacity, false,
|
|
471
|
+
false, groupName, undefined, undefined, {}, finishCallback),
|
|
472
|
+
this.onProgress(url), this.onError(finishCallback));
|
|
431
473
|
}
|
|
432
474
|
|
|
433
475
|
/**
|
|
@@ -458,17 +500,14 @@ const SceneLoader = function (sceneIn) {
|
|
|
458
500
|
if (morphColour != undefined)
|
|
459
501
|
localMorphColour = morphColour ? true : false;
|
|
460
502
|
let loader = primitivesLoader;
|
|
461
|
-
if (fileFormat
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
options.loaderOptions);
|
|
470
|
-
return;
|
|
471
|
-
}
|
|
503
|
+
if (fileFormat == "STL" || fileFormat == "OBJ") {
|
|
504
|
+
const typedLoader = fileFormat == "STL" ? meshloader : objloader;
|
|
505
|
+
loader = fileFormat == "STL" ? new STLLoader() : new OBJLoader();
|
|
506
|
+
loader.crossOrigin = "Anonymous";
|
|
507
|
+
loader.load(url, typedLoader(region, colour, opacity, localTimeEnabled,
|
|
508
|
+
localMorphColour, groupName, anatomicalId, renderOrder, options ? options : {},
|
|
509
|
+
finishCallback), this.onProgress(url), this.onError(finishCallback));
|
|
510
|
+
return;
|
|
472
511
|
}
|
|
473
512
|
if (isInline) {
|
|
474
513
|
const object = primitivesLoader.parse( url );
|
|
@@ -554,7 +593,12 @@ const SceneLoader = function (sceneIn) {
|
|
|
554
593
|
}
|
|
555
594
|
if (newTexture) {
|
|
556
595
|
newTexture.groupName = groupName;
|
|
596
|
+
const isStale = createStaleCheck();
|
|
557
597
|
let myCallback = () => {
|
|
598
|
+
if (isStale()) {
|
|
599
|
+
newTexture.dispose();
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
558
602
|
//Add zincObject after it has sort out all the required download
|
|
559
603
|
region.addZincObject(newTexture);
|
|
560
604
|
--this.toBeDownloaded;
|
|
@@ -582,6 +626,7 @@ const SceneLoader = function (sceneIn) {
|
|
|
582
626
|
loadTexture(region, undefined, url, groupName, finishCallback, options);
|
|
583
627
|
} else {
|
|
584
628
|
const requestURL = url;
|
|
629
|
+
const isStale = createStaleCheck();
|
|
585
630
|
fetch(requestURL)
|
|
586
631
|
.then((response) => {
|
|
587
632
|
if (!response.ok) {
|
|
@@ -591,6 +636,7 @@ const SceneLoader = function (sceneIn) {
|
|
|
591
636
|
return response.json().then((textureData) => ({ textureData, referenceURL }));
|
|
592
637
|
})
|
|
593
638
|
.then(({textureData, referenceURL}) => {
|
|
639
|
+
if (isStale()) return;
|
|
594
640
|
loadTexture(region, referenceURL, textureData, groupName, finishCallback, options);
|
|
595
641
|
})
|
|
596
642
|
.catch((error) => {
|
|
@@ -662,14 +708,19 @@ const SceneLoader = function (sceneIn) {
|
|
|
662
708
|
options,
|
|
663
709
|
finishCallback
|
|
664
710
|
) => {
|
|
711
|
+
const isStale = createStaleCheck();
|
|
665
712
|
return (geometry, materials) => {
|
|
713
|
+
if (isStale()) {
|
|
714
|
+
geometry.dispose();
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
666
717
|
let material = undefined;
|
|
667
718
|
if (materials && materials[0]) {
|
|
668
719
|
material = materials[0];
|
|
669
720
|
}
|
|
670
721
|
const zincGeometry = addZincGeometry(region, geometry, colour, opacity,
|
|
671
722
|
localTimeEnabled, localMorphColour, undefined, material, groupName, renderOrder, anatomicalId);
|
|
672
|
-
if (options
|
|
723
|
+
if (options?.lod?.levels) {
|
|
673
724
|
for (const [key, value] of Object.entries(options.lod.levels)) {
|
|
674
725
|
zincGeometry.addLOD(primitivesLoader, key, value.URL, value.Index, options.lod.preload);
|
|
675
726
|
}
|
|
@@ -682,6 +733,48 @@ const SceneLoader = function (sceneIn) {
|
|
|
682
733
|
};
|
|
683
734
|
}
|
|
684
735
|
|
|
736
|
+
//Internal loader for OBJ files, OBJLoader returns a group and each mesh
|
|
737
|
+
//in it is added as a separate zinc geometry.
|
|
738
|
+
const objloader = (
|
|
739
|
+
region,
|
|
740
|
+
colour,
|
|
741
|
+
opacity,
|
|
742
|
+
localTimeEnabled,
|
|
743
|
+
localMorphColour,
|
|
744
|
+
groupName,
|
|
745
|
+
anatomicalId,
|
|
746
|
+
renderOrder,
|
|
747
|
+
options,
|
|
748
|
+
finishCallback
|
|
749
|
+
) => {
|
|
750
|
+
const isStale = createStaleCheck();
|
|
751
|
+
const onMeshLoaded = meshloader(region, colour, opacity, localTimeEnabled,
|
|
752
|
+
localMorphColour, groupName, anatomicalId, renderOrder, options, finishCallback);
|
|
753
|
+
return (group) => {
|
|
754
|
+
const meshes = [];
|
|
755
|
+
group.traverse((child) => {
|
|
756
|
+
if (child.isMesh && child.geometry) {
|
|
757
|
+
meshes.push(child);
|
|
758
|
+
}
|
|
759
|
+
if (child.material) {
|
|
760
|
+
const materials = Array.isArray(child.material) ? child.material : [child.material];
|
|
761
|
+
materials.forEach((material) => material.dispose());
|
|
762
|
+
}
|
|
763
|
+
});
|
|
764
|
+
if (isStale()) {
|
|
765
|
+
meshes.forEach((mesh) => mesh.geometry.dispose());
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
if (meshes.length === 0) {
|
|
769
|
+
--this.toBeDownloaded;
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
//meshloader decrements the counter once per mesh
|
|
773
|
+
this.toBeDownloaded += meshes.length - 1;
|
|
774
|
+
meshes.forEach((mesh) => onMeshLoaded(mesh.geometry));
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
|
|
685
778
|
//Turn ISO 8601 duration string into an array.
|
|
686
779
|
const parseDuration = (durationString) => {
|
|
687
780
|
const regex = /P(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)W)?(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?)?$/;
|
|
@@ -831,7 +924,8 @@ const SceneLoader = function (sceneIn) {
|
|
|
831
924
|
*/
|
|
832
925
|
this.loadGLTF = (region, url, finishCallback, allCompletedCallback, options) => {
|
|
833
926
|
const loader = new GLTFToZincJSLoader();
|
|
834
|
-
loader.load(scene, region, url, finishCallback, allCompletedCallback, options
|
|
927
|
+
loader.load(scene, region, url, finishCallback, allCompletedCallback, options,
|
|
928
|
+
createStaleCheck());
|
|
835
929
|
}
|
|
836
930
|
|
|
837
931
|
let loadRegions = (currentRegion, referenceURL, regions, callback) => {
|
|
@@ -962,9 +1056,9 @@ const SceneLoader = function (sceneIn) {
|
|
|
962
1056
|
// view file does not receive callback
|
|
963
1057
|
let callback = new metaFinishCallback(numberOfObjects, finishCallback, allCompletedCallback);
|
|
964
1058
|
// Prioritise the view file and settings before loading anything else
|
|
965
|
-
for (let i = 0; i <
|
|
1059
|
+
for (let i = 0; i < filteredMetada.length; i++)
|
|
966
1060
|
readViewAndSettingsItem(referenceURL, filteredMetada[i], callback);
|
|
967
|
-
for (let i = 0; i <
|
|
1061
|
+
for (let i = 0; i < filteredMetada.length; i++) {
|
|
968
1062
|
readVersionOneRegionPath(targetRegion, referenceURL, filteredMetada[i], i, callback);
|
|
969
1063
|
}
|
|
970
1064
|
}
|
|
@@ -993,6 +1087,7 @@ const SceneLoader = function (sceneIn) {
|
|
|
993
1087
|
*/
|
|
994
1088
|
this.loadMetadataURL = (targetRegion, url, finishCallback, allCompletedCallback, options) => {
|
|
995
1089
|
const requestURL = url;
|
|
1090
|
+
const isStale = createStaleCheck();
|
|
996
1091
|
fetch(requestURL)
|
|
997
1092
|
.then((response) => {
|
|
998
1093
|
if (!response.ok) {
|
|
@@ -1002,6 +1097,7 @@ const SceneLoader = function (sceneIn) {
|
|
|
1002
1097
|
return response.json().then((metadata) => ({ metadata, referenceURL }));
|
|
1003
1098
|
})
|
|
1004
1099
|
.then(({metadata, referenceURL}) => {
|
|
1100
|
+
if (isStale()) return;
|
|
1005
1101
|
scene.resetMetadata();
|
|
1006
1102
|
scene.resetDuration();
|
|
1007
1103
|
viewLoaded = false;
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import * as THREE from 'three/webgpu';
|
|
2
|
+
import SpriteTextModule from 'three-spritetext';
|
|
3
|
+
import { requestRenderAll } from './renderRequests';
|
|
4
|
+
const SpriteText = SpriteTextModule.default || SpriteTextModule;
|
|
5
|
+
|
|
6
|
+
/*
|
|
7
|
+
* Text sprites (labels, marker numbers) are drawn on a canvas by
|
|
8
|
+
* three-spritetext. To keep the text sharp the canvas resolution is chosen
|
|
9
|
+
* from the size of the text on screen instead of a fixed font size, and it
|
|
10
|
+
* is redrawn once web fonts have finished loading.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
//Canvas pixels per screen pixel, a little oversampling smooths the edges
|
|
14
|
+
const OVERSAMPLE = 1.5;
|
|
15
|
+
const MIN_FONT_SIZE = 16;
|
|
16
|
+
const MAX_FONT_SIZE = 256;
|
|
17
|
+
//Only redraw when the screen scale has changed by more than this ratio
|
|
18
|
+
const RESCALE_THRESHOLD = 0.1;
|
|
19
|
+
|
|
20
|
+
/*
|
|
21
|
+
* Screen pixels per unit of sprite height. Text sprites do not use size
|
|
22
|
+
* attenuation, their height is in view units at a distance of one, so this
|
|
23
|
+
* is viewportHeight * pixelRatio / (2 * tan(fov / 2)). Default to a 1000
|
|
24
|
+
* pixels viewport with the 40 degrees field of view used by the scenes.
|
|
25
|
+
*/
|
|
26
|
+
let pixelsPerUnit = 1000 / (2 * Math.tan(20 * Math.PI / 180));
|
|
27
|
+
|
|
28
|
+
//Weak references to every live text sprite, so they can be redrawn
|
|
29
|
+
const sprites = new Set();
|
|
30
|
+
let fontListenerAdded = false;
|
|
31
|
+
|
|
32
|
+
const fontSizeForHeight = (textHeight) => {
|
|
33
|
+
const size = Math.round(textHeight * pixelsPerUnit * OVERSAMPLE);
|
|
34
|
+
return Math.min(Math.max(size, MIN_FONT_SIZE), MAX_FONT_SIZE);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/*
|
|
38
|
+
* The canvas matches the on screen size so mipmaps are not required,
|
|
39
|
+
* they blur the text when it is shrunk.
|
|
40
|
+
*/
|
|
41
|
+
const applyTextureSettings = (sprite) => {
|
|
42
|
+
const texture = sprite.material?.map;
|
|
43
|
+
if (texture) {
|
|
44
|
+
texture.generateMipmaps = false;
|
|
45
|
+
texture.minFilter = THREE.LinearFilter;
|
|
46
|
+
texture.magFilter = THREE.LinearFilter;
|
|
47
|
+
texture.anisotropy = 1;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/*
|
|
52
|
+
* SpriteText redraws its canvas and creates a new texture in every setter,
|
|
53
|
+
* set all font properties before a single redraw instead.
|
|
54
|
+
*/
|
|
55
|
+
const setFont = (sprite, fontFace, fontSize, fontWeight) => {
|
|
56
|
+
if ('_fontFace' in sprite && typeof sprite._genCanvas === 'function') {
|
|
57
|
+
sprite._fontFace = fontFace;
|
|
58
|
+
sprite._fontSize = fontSize;
|
|
59
|
+
sprite._fontWeight = fontWeight;
|
|
60
|
+
sprite._genCanvas();
|
|
61
|
+
} else {
|
|
62
|
+
sprite.fontFace = fontFace;
|
|
63
|
+
sprite.fontSize = fontSize;
|
|
64
|
+
sprite.fontWeight = fontWeight;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/*
|
|
69
|
+
* Redraw the sprite at the resolution required for its current height.
|
|
70
|
+
*/
|
|
71
|
+
const refreshTextSprite = (sprite, force = false) => {
|
|
72
|
+
const fontSize = fontSizeForHeight(sprite.textHeight);
|
|
73
|
+
if (force || fontSize !== sprite.fontSize) {
|
|
74
|
+
setFont(sprite, sprite.fontFace, fontSize, sprite.fontWeight);
|
|
75
|
+
applyTextureSettings(sprite);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const forEachSprite = (callback) => {
|
|
80
|
+
sprites.forEach((ref) => {
|
|
81
|
+
const sprite = ref.deref();
|
|
82
|
+
if (sprite) {
|
|
83
|
+
callback(sprite);
|
|
84
|
+
} else {
|
|
85
|
+
sprites.delete(ref);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const redrawAll = () => {
|
|
91
|
+
forEachSprite((sprite) => refreshTextSprite(sprite, true));
|
|
92
|
+
requestRenderAll();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/*
|
|
96
|
+
* The canvas is drawn with a fallback font if a web font has not been
|
|
97
|
+
* loaded yet, redraw once it is available.
|
|
98
|
+
*/
|
|
99
|
+
const waitForFont = (sprite) => {
|
|
100
|
+
if (typeof document === 'undefined' || !document.fonts) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (!fontListenerAdded && document.fonts.addEventListener) {
|
|
104
|
+
//Catch fonts from stylesheets added after the sprites are created
|
|
105
|
+
document.fonts.addEventListener('loadingdone', redrawAll);
|
|
106
|
+
fontListenerAdded = true;
|
|
107
|
+
}
|
|
108
|
+
const font = `${sprite.fontWeight} ${sprite.fontSize}px ${sprite.fontFace}`;
|
|
109
|
+
try {
|
|
110
|
+
if (!document.fonts.check(font)) {
|
|
111
|
+
document.fonts.load(font).then(() => {
|
|
112
|
+
refreshTextSprite(sprite, true);
|
|
113
|
+
requestRenderAll();
|
|
114
|
+
}).catch(() => {});
|
|
115
|
+
}
|
|
116
|
+
} catch {
|
|
117
|
+
//Invalid font string, keep the fallback font
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const registerTextSprite = (sprite) => {
|
|
122
|
+
sprites.add(new WeakRef(sprite));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Stop tracking a text sprite, call this when it is disposed.
|
|
127
|
+
*/
|
|
128
|
+
const releaseTextSprite = (sprite) => {
|
|
129
|
+
sprites.forEach((ref) => {
|
|
130
|
+
const trackedSprite = ref.deref();
|
|
131
|
+
if (!trackedSprite || trackedSprite === sprite) {
|
|
132
|
+
sprites.delete(ref);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Create a text sprite rendered with a node material, the canvas resolution
|
|
139
|
+
* is chosen from the size of the text on screen.
|
|
140
|
+
*
|
|
141
|
+
* @param {String} text - Text to display.
|
|
142
|
+
* @param {Number} textHeight - Height of a line of text in view units.
|
|
143
|
+
* @param {String} colour - CSS colour of the text.
|
|
144
|
+
* @param {String} fontFace - Font family.
|
|
145
|
+
* @param {Number|String} fontWeight - Font weight.
|
|
146
|
+
* @return {SpriteText}
|
|
147
|
+
*/
|
|
148
|
+
const createTextSprite = (text, textHeight, colour, fontFace, fontWeight) => {
|
|
149
|
+
const sprite = colour ? new SpriteText(text, textHeight, colour) :
|
|
150
|
+
new SpriteText(text, textHeight);
|
|
151
|
+
setFont(sprite, fontFace, fontSizeForHeight(textHeight), fontWeight);
|
|
152
|
+
//Replace the SpriteMaterial created by SpriteText with a node material
|
|
153
|
+
//using the same canvas texture.
|
|
154
|
+
const originalMaterial = sprite.material;
|
|
155
|
+
sprite.material = new THREE.SpriteNodeMaterial({
|
|
156
|
+
map: originalMaterial.map,
|
|
157
|
+
});
|
|
158
|
+
originalMaterial.map = null;
|
|
159
|
+
originalMaterial.dispose();
|
|
160
|
+
sprite.material.sizeAttenuation = false;
|
|
161
|
+
applyTextureSettings(sprite);
|
|
162
|
+
registerTextSprite(sprite);
|
|
163
|
+
waitForFont(sprite);
|
|
164
|
+
return sprite;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Set the height of a text sprite and redraw it once at the matching
|
|
169
|
+
* resolution.
|
|
170
|
+
*/
|
|
171
|
+
const setTextSpriteHeight = (sprite, textHeight) => {
|
|
172
|
+
if ('_textHeight' in sprite) {
|
|
173
|
+
sprite._textHeight = textHeight;
|
|
174
|
+
refreshTextSprite(sprite, true);
|
|
175
|
+
} else {
|
|
176
|
+
sprite.textHeight = textHeight;
|
|
177
|
+
refreshTextSprite(sprite);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Update the number of screen pixels per unit of text height, e.g. when the
|
|
183
|
+
* viewport is resized. Text sprites are redrawn if it has changed
|
|
184
|
+
* significantly.
|
|
185
|
+
*
|
|
186
|
+
* @param {Number} value - viewportHeight * pixelRatio / (2 * tan(fov / 2))
|
|
187
|
+
*/
|
|
188
|
+
const setTextPixelsPerUnit = (value) => {
|
|
189
|
+
if (!(value > 0) || !isFinite(value)) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
if (Math.abs(value / pixelsPerUnit - 1) > RESCALE_THRESHOLD) {
|
|
193
|
+
pixelsPerUnit = value;
|
|
194
|
+
forEachSprite((sprite) => refreshTextSprite(sprite));
|
|
195
|
+
requestRenderAll();
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const getTextPixelsPerUnit = () => pixelsPerUnit;
|
|
200
|
+
|
|
201
|
+
export {
|
|
202
|
+
applyTextureSettings as applyTextSpriteTextureSettings,
|
|
203
|
+
createTextSprite,
|
|
204
|
+
fontSizeForHeight,
|
|
205
|
+
getTextPixelsPerUnit,
|
|
206
|
+
refreshTextSprite,
|
|
207
|
+
registerTextSprite,
|
|
208
|
+
releaseTextSprite,
|
|
209
|
+
setTextPixelsPerUnit,
|
|
210
|
+
setTextSpriteHeight,
|
|
211
|
+
};
|
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 };
|