@aics/vole-core 3.12.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.
Files changed (141) hide show
  1. package/LICENSE.txt +26 -0
  2. package/README.md +119 -0
  3. package/es/Atlas2DSlice.js +224 -0
  4. package/es/Channel.js +264 -0
  5. package/es/FileSaver.js +31 -0
  6. package/es/FusedChannelData.js +192 -0
  7. package/es/Histogram.js +250 -0
  8. package/es/ImageInfo.js +127 -0
  9. package/es/Light.js +74 -0
  10. package/es/Lut.js +500 -0
  11. package/es/MarchingCubes.js +507 -0
  12. package/es/MeshVolume.js +334 -0
  13. package/es/NaiveSurfaceNets.js +251 -0
  14. package/es/PathTracedVolume.js +482 -0
  15. package/es/RayMarchedAtlasVolume.js +250 -0
  16. package/es/RenderToBuffer.js +31 -0
  17. package/es/ThreeJsPanel.js +633 -0
  18. package/es/Timing.js +28 -0
  19. package/es/TrackballControls.js +538 -0
  20. package/es/View3d.js +848 -0
  21. package/es/Volume.js +352 -0
  22. package/es/VolumeCache.js +161 -0
  23. package/es/VolumeDims.js +16 -0
  24. package/es/VolumeDrawable.js +702 -0
  25. package/es/VolumeMaker.js +101 -0
  26. package/es/VolumeRenderImpl.js +1 -0
  27. package/es/VolumeRenderSettings.js +203 -0
  28. package/es/constants/basicShaders.js +29 -0
  29. package/es/constants/colors.js +59 -0
  30. package/es/constants/denoiseShader.js +43 -0
  31. package/es/constants/lights.js +42 -0
  32. package/es/constants/materials.js +85 -0
  33. package/es/constants/pathtraceOutputShader.js +13 -0
  34. package/es/constants/scaleBarSVG.js +21 -0
  35. package/es/constants/time.js +34 -0
  36. package/es/constants/volumePTshader.js +153 -0
  37. package/es/constants/volumeRayMarchShader.js +123 -0
  38. package/es/constants/volumeSliceShader.js +115 -0
  39. package/es/index.js +21 -0
  40. package/es/loaders/IVolumeLoader.js +131 -0
  41. package/es/loaders/JsonImageInfoLoader.js +255 -0
  42. package/es/loaders/OmeZarrLoader.js +495 -0
  43. package/es/loaders/OpenCellLoader.js +65 -0
  44. package/es/loaders/RawArrayLoader.js +89 -0
  45. package/es/loaders/TiffLoader.js +219 -0
  46. package/es/loaders/VolumeLoadError.js +44 -0
  47. package/es/loaders/VolumeLoaderUtils.js +221 -0
  48. package/es/loaders/index.js +40 -0
  49. package/es/loaders/zarr_utils/ChunkPrefetchIterator.js +143 -0
  50. package/es/loaders/zarr_utils/WrappedStore.js +51 -0
  51. package/es/loaders/zarr_utils/types.js +24 -0
  52. package/es/loaders/zarr_utils/utils.js +225 -0
  53. package/es/loaders/zarr_utils/validation.js +49 -0
  54. package/es/test/ChunkPrefetchIterator.test.js +208 -0
  55. package/es/test/RequestQueue.test.js +442 -0
  56. package/es/test/SubscribableRequestQueue.test.js +244 -0
  57. package/es/test/VolumeCache.test.js +118 -0
  58. package/es/test/VolumeRenderSettings.test.js +71 -0
  59. package/es/test/lut.test.js +671 -0
  60. package/es/test/num_utils.test.js +140 -0
  61. package/es/test/volume.test.js +98 -0
  62. package/es/test/zarr_utils.test.js +358 -0
  63. package/es/types/Atlas2DSlice.d.ts +41 -0
  64. package/es/types/Channel.d.ts +44 -0
  65. package/es/types/FileSaver.d.ts +6 -0
  66. package/es/types/FusedChannelData.d.ts +26 -0
  67. package/es/types/Histogram.d.ts +57 -0
  68. package/es/types/ImageInfo.d.ts +87 -0
  69. package/es/types/Light.d.ts +27 -0
  70. package/es/types/Lut.d.ts +67 -0
  71. package/es/types/MarchingCubes.d.ts +53 -0
  72. package/es/types/MeshVolume.d.ts +40 -0
  73. package/es/types/NaiveSurfaceNets.d.ts +11 -0
  74. package/es/types/PathTracedVolume.d.ts +65 -0
  75. package/es/types/RayMarchedAtlasVolume.d.ts +41 -0
  76. package/es/types/RenderToBuffer.d.ts +17 -0
  77. package/es/types/ThreeJsPanel.d.ts +107 -0
  78. package/es/types/Timing.d.ts +11 -0
  79. package/es/types/TrackballControls.d.ts +51 -0
  80. package/es/types/View3d.d.ts +357 -0
  81. package/es/types/Volume.d.ts +152 -0
  82. package/es/types/VolumeCache.d.ts +43 -0
  83. package/es/types/VolumeDims.d.ts +28 -0
  84. package/es/types/VolumeDrawable.d.ts +108 -0
  85. package/es/types/VolumeMaker.d.ts +49 -0
  86. package/es/types/VolumeRenderImpl.d.ts +22 -0
  87. package/es/types/VolumeRenderSettings.d.ts +98 -0
  88. package/es/types/constants/basicShaders.d.ts +4 -0
  89. package/es/types/constants/colors.d.ts +2 -0
  90. package/es/types/constants/denoiseShader.d.ts +40 -0
  91. package/es/types/constants/lights.d.ts +38 -0
  92. package/es/types/constants/materials.d.ts +20 -0
  93. package/es/types/constants/pathtraceOutputShader.d.ts +11 -0
  94. package/es/types/constants/scaleBarSVG.d.ts +2 -0
  95. package/es/types/constants/time.d.ts +19 -0
  96. package/es/types/constants/volumePTshader.d.ts +137 -0
  97. package/es/types/constants/volumeRayMarchShader.d.ts +117 -0
  98. package/es/types/constants/volumeSliceShader.d.ts +109 -0
  99. package/es/types/glsl.d.js +0 -0
  100. package/es/types/index.d.ts +28 -0
  101. package/es/types/loaders/IVolumeLoader.d.ts +113 -0
  102. package/es/types/loaders/JsonImageInfoLoader.d.ts +80 -0
  103. package/es/types/loaders/OmeZarrLoader.d.ts +87 -0
  104. package/es/types/loaders/OpenCellLoader.d.ts +9 -0
  105. package/es/types/loaders/RawArrayLoader.d.ts +33 -0
  106. package/es/types/loaders/TiffLoader.d.ts +45 -0
  107. package/es/types/loaders/VolumeLoadError.d.ts +18 -0
  108. package/es/types/loaders/VolumeLoaderUtils.d.ts +38 -0
  109. package/es/types/loaders/index.d.ts +22 -0
  110. package/es/types/loaders/zarr_utils/ChunkPrefetchIterator.d.ts +22 -0
  111. package/es/types/loaders/zarr_utils/WrappedStore.d.ts +24 -0
  112. package/es/types/loaders/zarr_utils/types.d.ts +94 -0
  113. package/es/types/loaders/zarr_utils/utils.d.ts +23 -0
  114. package/es/types/loaders/zarr_utils/validation.d.ts +7 -0
  115. package/es/types/test/ChunkPrefetchIterator.test.d.ts +1 -0
  116. package/es/types/test/RequestQueue.test.d.ts +1 -0
  117. package/es/types/test/SubscribableRequestQueue.test.d.ts +1 -0
  118. package/es/types/test/VolumeCache.test.d.ts +1 -0
  119. package/es/types/test/VolumeRenderSettings.test.d.ts +1 -0
  120. package/es/types/test/lut.test.d.ts +1 -0
  121. package/es/types/test/num_utils.test.d.ts +1 -0
  122. package/es/types/test/volume.test.d.ts +1 -0
  123. package/es/types/test/zarr_utils.test.d.ts +1 -0
  124. package/es/types/types.d.ts +115 -0
  125. package/es/types/utils/RequestQueue.d.ts +112 -0
  126. package/es/types/utils/SubscribableRequestQueue.d.ts +52 -0
  127. package/es/types/utils/num_utils.d.ts +43 -0
  128. package/es/types/workers/VolumeLoaderContext.d.ts +106 -0
  129. package/es/types/workers/types.d.ts +101 -0
  130. package/es/types/workers/util.d.ts +3 -0
  131. package/es/types.js +75 -0
  132. package/es/typings.d.js +0 -0
  133. package/es/utils/RequestQueue.js +267 -0
  134. package/es/utils/SubscribableRequestQueue.js +187 -0
  135. package/es/utils/num_utils.js +231 -0
  136. package/es/workers/FetchTiffWorker.js +153 -0
  137. package/es/workers/VolumeLoadWorker.js +129 -0
  138. package/es/workers/VolumeLoaderContext.js +271 -0
  139. package/es/workers/types.js +41 -0
  140. package/es/workers/util.js +8 -0
  141. package/package.json +83 -0
@@ -0,0 +1,633 @@
1
+ import { AxesHelper, Vector3, Object3D, Mesh, BoxGeometry, MeshBasicMaterial, OrthographicCamera, PerspectiveCamera, NormalBlending, WebGLRenderer, Scene, DepthTexture, WebGLRenderTarget, NearestFilter, UnsignedByteType, RGBAFormat } from "three";
2
+ import TrackballControls from "./TrackballControls.js";
3
+ import Timing from "./Timing.js";
4
+ import scaleBarSVG from "./constants/scaleBarSVG.js";
5
+ import { isOrthographicCamera, isPerspectiveCamera, isTop, isRight } from "./types.js";
6
+ import { constrainToAxis, formatNumber, getTimestamp } from "./utils/num_utils.js";
7
+ import { Axis } from "./VolumeRenderSettings.js";
8
+ import RenderToBuffer from "./RenderToBuffer.js";
9
+ import { copyImageFragShader } from "./constants/basicShaders.js";
10
+ export const VOLUME_LAYER = 0;
11
+ export const MESH_LAYER = 1;
12
+ const DEFAULT_PERSPECTIVE_CAMERA_DISTANCE = 5.0;
13
+ const DEFAULT_PERSPECTIVE_CAMERA_NEAR = 0.1;
14
+ const DEFAULT_PERSPECTIVE_CAMERA_FAR = 20.0;
15
+ const DEFAULT_ORTHO_SCALE = 0.5;
16
+ export class ThreeJsPanel {
17
+ constructor(parentElement, _useWebGL2) {
18
+ this.containerdiv = document.createElement("div");
19
+ this.containerdiv.style.position = "relative";
20
+ this.canvas = document.createElement("canvas");
21
+ this.containerdiv.appendChild(this.canvas);
22
+ this.canvas.style.backgroundColor = "black";
23
+ if (parentElement) {
24
+ this.canvas.height = parentElement.offsetHeight;
25
+ this.canvas.width = parentElement.offsetWidth;
26
+ parentElement.appendChild(this.containerdiv);
27
+ }
28
+ this.scene = new Scene();
29
+ this.meshRenderTarget = new WebGLRenderTarget(this.canvas.width, this.canvas.height, {
30
+ minFilter: NearestFilter,
31
+ magFilter: NearestFilter,
32
+ format: RGBAFormat,
33
+ type: UnsignedByteType,
34
+ depthBuffer: true
35
+ });
36
+ this.meshRenderToBuffer = new RenderToBuffer(copyImageFragShader, {
37
+ image: {
38
+ value: this.meshRenderTarget.texture
39
+ }
40
+ });
41
+ this.meshRenderTarget.depthTexture = new DepthTexture(this.canvas.width, this.canvas.height);
42
+ this.scaleBarContainerElement = document.createElement("div");
43
+ this.orthoScaleBarElement = document.createElement("div");
44
+ this.showOrthoScaleBar = true;
45
+ this.perspectiveScaleBarElement = document.createElement("div");
46
+ this.showPerspectiveScaleBar = false;
47
+ this.timestepIndicatorElement = document.createElement("div");
48
+ this.showTimestepIndicator = false;
49
+ this.animateFuncs = [];
50
+
51
+ // are we in a constant render loop or not?
52
+ this.inRenderLoop = false;
53
+ // if we're not in a constant render loop, have we queued any single redraws?
54
+ this.requestedRender = 0;
55
+
56
+ // if webgl 2 is available, let's just use it anyway.
57
+ // we are ignoring the useWebGL2 flag
58
+ this.hasWebGL2 = false;
59
+ const context = this.canvas.getContext("webgl2");
60
+ if (context) {
61
+ this.hasWebGL2 = true;
62
+ this.renderer = new WebGLRenderer({
63
+ context: context,
64
+ canvas: this.canvas,
65
+ preserveDrawingBuffer: true,
66
+ alpha: true,
67
+ premultipliedAlpha: false
68
+ });
69
+ //this.renderer.autoClear = false;
70
+ // set pixel ratio to 0.25 or 0.5 to render at lower res.
71
+ this.renderer.setPixelRatio(window.devicePixelRatio);
72
+ this.renderer.state.setBlending(NormalBlending);
73
+ //required by WebGL 2.0 for rendering to FLOAT textures
74
+ this.renderer.getContext().getExtension("EXT_color_buffer_float");
75
+ } else {
76
+ // TODO Deprecate this code path.
77
+ console.warn("WebGL 2.0 not available. Some functionality may be limited. Please use a browser that supports WebGL 2.0.");
78
+ this.renderer = new WebGLRenderer({
79
+ canvas: this.canvas,
80
+ preserveDrawingBuffer: true,
81
+ alpha: true,
82
+ premultipliedAlpha: false
83
+ });
84
+ this.renderer.setPixelRatio(window.devicePixelRatio);
85
+ this.renderer.state.setBlending(NormalBlending);
86
+ }
87
+ this.renderer.localClippingEnabled = true;
88
+ if (parentElement) {
89
+ this.renderer.setSize(parentElement.offsetWidth, parentElement.offsetHeight);
90
+ this.meshRenderTarget.setSize(parentElement.offsetWidth, parentElement.offsetHeight);
91
+ }
92
+ this.timer = new Timing();
93
+ const scale = DEFAULT_ORTHO_SCALE;
94
+ const aspect = this.getWidth() / this.getHeight();
95
+ this.fov = 20;
96
+ this.perspectiveCamera = new PerspectiveCamera(this.fov, aspect, DEFAULT_PERSPECTIVE_CAMERA_NEAR, DEFAULT_PERSPECTIVE_CAMERA_FAR);
97
+ this.resetPerspectiveCamera();
98
+ this.perspectiveControls = new TrackballControls(this.perspectiveCamera, this.canvas);
99
+ this.perspectiveControls.rotateSpeed = 4.0 / window.devicePixelRatio;
100
+ this.perspectiveControls.autoRotate = false;
101
+ this.perspectiveControls.staticMoving = true;
102
+ this.perspectiveControls.length = 10;
103
+ this.perspectiveControls.enabled = true; //turn off mouse moments by setting to false
104
+
105
+ this.orthographicCameraX = new OrthographicCamera(-scale * aspect, scale * aspect, scale, -scale, 0.001, 20);
106
+ this.resetOrthographicCameraX();
107
+ this.orthoControlsX = new TrackballControls(this.orthographicCameraX, this.canvas);
108
+ this.orthoControlsX.noRotate = true;
109
+ this.orthoControlsX.scale = scale;
110
+ this.orthoControlsX.scale0 = scale;
111
+ this.orthoControlsX.aspect = aspect;
112
+ this.orthoControlsX.staticMoving = true;
113
+ this.orthoControlsX.enabled = false;
114
+ this.orthoControlsX.panSpeed = this.canvas.clientWidth * 0.5;
115
+ this.orthographicCameraY = new OrthographicCamera(-scale * aspect, scale * aspect, scale, -scale, 0.001, 20);
116
+ this.resetOrthographicCameraY();
117
+ this.orthoControlsY = new TrackballControls(this.orthographicCameraY, this.canvas);
118
+ this.orthoControlsY.noRotate = true;
119
+ this.orthoControlsY.scale = scale;
120
+ this.orthoControlsY.scale0 = scale;
121
+ this.orthoControlsY.aspect = aspect;
122
+ this.orthoControlsY.staticMoving = true;
123
+ this.orthoControlsY.enabled = false;
124
+ this.orthoControlsY.panSpeed = this.canvas.clientWidth * 0.5;
125
+ this.orthographicCameraZ = new OrthographicCamera(-scale * aspect, scale * aspect, scale, -scale, 0.001, 20);
126
+ this.resetOrthographicCameraZ();
127
+ this.orthoControlsZ = new TrackballControls(this.orthographicCameraZ, this.canvas);
128
+ this.orthoControlsZ.noRotate = true;
129
+ this.orthoControlsZ.scale = scale;
130
+ this.orthoControlsZ.scale0 = scale;
131
+ this.orthoControlsZ.aspect = aspect;
132
+ this.orthoControlsZ.staticMoving = true;
133
+ this.orthoControlsZ.enabled = false;
134
+ this.orthoControlsZ.panSpeed = this.canvas.clientWidth * 0.5;
135
+ this.camera = this.perspectiveCamera;
136
+ this.controls = this.perspectiveControls;
137
+ this.viewMode = Axis.NONE;
138
+ this.axisCamera = new OrthographicCamera();
139
+ this.axisHelperScene = new Scene();
140
+ this.axisHelperObject = new Object3D();
141
+ this.axisHelperObject.name = "axisHelperParentObject";
142
+ this.showAxis = false;
143
+ // size of axes in px.
144
+ this.axisScale = 50.0;
145
+ // offset from bottom left corner in px.
146
+ this.axisOffset = [66.0, 66.0];
147
+ this.setupAxisHelper();
148
+ this.setupIndicatorElements();
149
+ }
150
+ updateCameraFocus(fov, _focalDistance, _apertureSize) {
151
+ this.perspectiveCamera.fov = fov;
152
+ this.fov = fov;
153
+ this.perspectiveCamera.updateProjectionMatrix();
154
+ }
155
+ resetPerspectiveCamera() {
156
+ this.perspectiveCamera.position.x = 0.0;
157
+ this.perspectiveCamera.position.y = 0.0;
158
+ this.perspectiveCamera.position.z = DEFAULT_PERSPECTIVE_CAMERA_DISTANCE;
159
+ this.perspectiveCamera.up.x = 0.0;
160
+ this.perspectiveCamera.up.y = 1.0;
161
+ this.perspectiveCamera.up.z = 0.0;
162
+ }
163
+ resetOrthographicCameraX() {
164
+ this.orthographicCameraX.position.x = 2.0;
165
+ this.orthographicCameraX.position.y = 0.0;
166
+ this.orthographicCameraX.position.z = 0.0;
167
+ this.orthographicCameraX.up.x = 0.0;
168
+ this.orthographicCameraX.up.y = 0.0;
169
+ this.orthographicCameraX.up.z = 1.0;
170
+ this.orthographicCameraX.lookAt(new Vector3(0, 0, 0));
171
+ }
172
+ resetOrthographicCameraY() {
173
+ this.orthographicCameraY.position.x = 0.0;
174
+ this.orthographicCameraY.position.y = 2.0;
175
+ this.orthographicCameraY.position.z = 0.0;
176
+ this.orthographicCameraY.up.x = 0.0;
177
+ this.orthographicCameraY.up.y = 0.0;
178
+ this.orthographicCameraY.up.z = 1.0;
179
+ this.orthographicCameraY.lookAt(new Vector3(0, 0, 0));
180
+ }
181
+ resetOrthographicCameraZ() {
182
+ this.orthographicCameraZ.position.x = 0.0;
183
+ this.orthographicCameraZ.position.y = 0.0;
184
+ this.orthographicCameraZ.position.z = 2.0;
185
+ this.orthographicCameraZ.up.x = 0.0;
186
+ this.orthographicCameraZ.up.y = 1.0;
187
+ this.orthographicCameraZ.up.z = 0.0;
188
+ this.orthographicCameraZ.lookAt(new Vector3(0, 0, 0));
189
+ }
190
+ requestCapture(dataurlcallback) {
191
+ this.dataurlcallback = dataurlcallback;
192
+ this.redraw();
193
+ }
194
+ isVR() {
195
+ return this.renderer.xr.enabled;
196
+ }
197
+ resetToPerspectiveCamera() {
198
+ const aspect = this.getWidth() / this.getHeight();
199
+ this.perspectiveCamera = new PerspectiveCamera(this.fov, aspect, DEFAULT_PERSPECTIVE_CAMERA_NEAR, DEFAULT_PERSPECTIVE_CAMERA_FAR);
200
+ this.resetPerspectiveCamera();
201
+ this.switchViewMode("3D");
202
+ this.controls.object = this.perspectiveCamera;
203
+ this.controls.enabled = true;
204
+ this.controls.reset();
205
+ }
206
+ resetCamera() {
207
+ if (this.camera === this.perspectiveCamera) {
208
+ this.resetPerspectiveCamera();
209
+ } else if (this.camera === this.orthographicCameraX) {
210
+ this.resetOrthographicCameraX();
211
+ } else if (this.camera === this.orthographicCameraY) {
212
+ this.resetOrthographicCameraY();
213
+ } else if (this.camera === this.orthographicCameraZ) {
214
+ this.resetOrthographicCameraZ();
215
+ }
216
+ this.controls.reset();
217
+ }
218
+ setupAxisHelper() {
219
+ // set up axis widget.
220
+
221
+ const axisCubeMaterial = new MeshBasicMaterial({
222
+ color: 0xaeacad
223
+ });
224
+ const axisCube = new BoxGeometry(this.axisScale / 5, this.axisScale / 5, this.axisScale / 5);
225
+ const axisCubeMesh = new Mesh(axisCube, axisCubeMaterial);
226
+ this.axisHelperObject.add(axisCubeMesh);
227
+ const axisHelper = new AxesHelper(this.axisScale);
228
+ this.axisHelperObject.add(axisHelper);
229
+ this.axisHelperScene.add(this.axisHelperObject);
230
+ this.axisCamera = new OrthographicCamera(0, this.getWidth(), this.getHeight(), 0, 0.001, this.axisScale * 4.0);
231
+ this.axisCamera.position.z = 1.0;
232
+ this.axisCamera.up.x = 0.0;
233
+ this.axisCamera.up.y = 1.0;
234
+ this.axisCamera.up.z = 0.0;
235
+ this.axisCamera.lookAt(new Vector3(0, 0, 0));
236
+ this.axisCamera.position.set(-this.axisOffset[0], -this.axisOffset[1], this.axisScale * 2.0);
237
+ }
238
+ setAxisPosition(marginX, marginY, corner) {
239
+ // Offset is relative to center of object, not corner of possible extent
240
+ // at offsets lower than BASE_MARGIN, axes may extend off screen
241
+ const BASE_MARGIN = 50;
242
+ this.axisOffset = [marginX + BASE_MARGIN, marginY + BASE_MARGIN];
243
+ if (isTop(corner)) {
244
+ this.axisOffset[1] = this.getHeight() - this.axisOffset[1];
245
+ }
246
+ if (isRight(corner)) {
247
+ this.axisOffset[0] = this.getWidth() - this.axisOffset[0];
248
+ }
249
+ this.axisCamera.position.set(-this.axisOffset[0], -this.axisOffset[1], this.axisScale * 2.0);
250
+ }
251
+ orthoScreenPixelsToPhysicalUnits(pixels, physicalUnitsPerWorldUnit) {
252
+ const worldUnitsPerPixel = 1 / (this.camera.zoom * this.getHeight());
253
+ // Multiply by devicePixelRatio to convert from scaled CSS pixels to physical pixels
254
+ // (to account for high dpi monitors, e.g.). We didn't do this to height above because
255
+ // that value comes from three, which works in physical pixels.
256
+ return pixels * window.devicePixelRatio * worldUnitsPerPixel * physicalUnitsPerWorldUnit;
257
+ }
258
+ setupIndicatorElements() {
259
+ const scaleBarContainerStyle = {
260
+ fontFamily: "-apple-system, 'Segoe UI', 'Helvetica Neue', Helvetica, Arial, sans-serif",
261
+ position: "absolute",
262
+ right: "169px",
263
+ bottom: "20px"
264
+ };
265
+ Object.assign(this.scaleBarContainerElement.style, scaleBarContainerStyle);
266
+ this.containerdiv.appendChild(this.scaleBarContainerElement);
267
+
268
+ // Orthographic scale bar
269
+ const orthoScaleBarStyle = {
270
+ border: "1px solid white",
271
+ borderTop: "none",
272
+ height: "10px",
273
+ display: "none",
274
+ color: "white",
275
+ mixBlendMode: "difference",
276
+ fontSize: "14px",
277
+ textAlign: "right",
278
+ lineHeight: "0",
279
+ boxSizing: "border-box",
280
+ paddingRight: "10px",
281
+ // TODO: Adjust based on width of timestamp
282
+ marginRight: "40px"
283
+ };
284
+ Object.assign(this.orthoScaleBarElement.style, orthoScaleBarStyle);
285
+ this.scaleBarContainerElement.appendChild(this.orthoScaleBarElement);
286
+
287
+ // Perspective scale bar
288
+ const perspectiveScaleBarStyle = {
289
+ width: "75px",
290
+ textAlign: "center",
291
+ display: "none",
292
+ color: "white"
293
+ };
294
+ Object.assign(this.perspectiveScaleBarElement.style, perspectiveScaleBarStyle);
295
+ this.scaleBarContainerElement.appendChild(this.perspectiveScaleBarElement);
296
+ const labeldiv = document.createElement("div");
297
+ const svgdiv = document.createElement("div");
298
+ svgdiv.style.color = "rgb(255, 255, 0)";
299
+ svgdiv.innerHTML = scaleBarSVG;
300
+ this.perspectiveScaleBarElement.appendChild(labeldiv);
301
+ this.perspectiveScaleBarElement.appendChild(svgdiv);
302
+
303
+ // Time step indicator
304
+ const timestepIndicatorStyle = {
305
+ fontFamily: "-apple-system, 'Segoe UI', 'Helvetica Neue', Helvetica, Arial, sans-serif",
306
+ position: "absolute",
307
+ right: "20px",
308
+ bottom: "20px",
309
+ color: "white",
310
+ mixBlendMode: "difference",
311
+ display: "none",
312
+ lineHeight: "0.75"
313
+ };
314
+ Object.assign(this.timestepIndicatorElement.style, timestepIndicatorStyle);
315
+ this.containerdiv.appendChild(this.timestepIndicatorElement);
316
+ }
317
+ updateOrthoScaleBar(scale, unit) {
318
+ // We want to find the largest round number of physical units that keeps the scale bar within this width on screen
319
+ const SCALE_BAR_MAX_WIDTH = 150;
320
+ // Convert max width to volume physical units
321
+ const physicalMaxWidth = this.orthoScreenPixelsToPhysicalUnits(SCALE_BAR_MAX_WIDTH, scale);
322
+ // Round off all but the most significant digit of physicalMaxWidth
323
+ const digits = Math.floor(Math.log10(physicalMaxWidth));
324
+ const div10 = 10 ** digits;
325
+ const scaleValue = Math.floor(physicalMaxWidth / div10) * div10;
326
+ const scaleStr = formatNumber(scaleValue);
327
+ this.orthoScaleBarElement.innerHTML = `${scaleStr}${unit || ""}`;
328
+ this.orthoScaleBarElement.style.width = `${SCALE_BAR_MAX_WIDTH * (scaleValue / physicalMaxWidth)}px`;
329
+ }
330
+ updatePerspectiveScaleBar(length, unit) {
331
+ const labeldiv = this.perspectiveScaleBarElement.firstChild;
332
+ labeldiv.innerHTML = `${formatNumber(length)}${unit || ""}`;
333
+ }
334
+ updateTimestepIndicator(progress, total, unit) {
335
+ this.timestepIndicatorElement.innerHTML = getTimestamp(progress, total, unit);
336
+ }
337
+ setPerspectiveScaleBarColor(color) {
338
+ // set the font color of the SVG container. only paths with `stroke="currentColor"` will react to this.
339
+ const svgdiv = this.perspectiveScaleBarElement.lastChild;
340
+ svgdiv.style.color = `rgb(${color[0] * 255}, ${color[1] * 255}, ${color[2] * 255})`;
341
+ }
342
+ updateScaleBarVisibility() {
343
+ const isOrtho = isOrthographicCamera(this.camera);
344
+ const orthoVisible = isOrtho && this.showOrthoScaleBar;
345
+ const perspectiveVisible = !isOrtho && this.showPerspectiveScaleBar;
346
+ this.orthoScaleBarElement.style.display = orthoVisible ? "" : "none";
347
+ this.perspectiveScaleBarElement.style.display = perspectiveVisible ? "" : "none";
348
+ }
349
+ setShowOrthoScaleBar(visible) {
350
+ this.showOrthoScaleBar = visible;
351
+ this.updateScaleBarVisibility();
352
+ }
353
+ setShowPerspectiveScaleBar(visible) {
354
+ this.showPerspectiveScaleBar = visible;
355
+ this.updateScaleBarVisibility();
356
+ }
357
+ setShowTimestepIndicator(visible) {
358
+ this.showTimestepIndicator = visible;
359
+ this.timestepIndicatorElement.style.display = visible ? "" : "none";
360
+ }
361
+ setIndicatorPosition(timestep, marginX, marginY, corner) {
362
+ const {
363
+ style
364
+ } = timestep ? this.timestepIndicatorElement : this.scaleBarContainerElement;
365
+ style.removeProperty("top");
366
+ style.removeProperty("bottom");
367
+ style.removeProperty("left");
368
+ style.removeProperty("right");
369
+ const xProp = isRight(corner) ? "right" : "left";
370
+ const yProp = isTop(corner) ? "top" : "bottom";
371
+ Object.assign(style, {
372
+ [xProp]: marginX + "px",
373
+ [yProp]: marginY + "px"
374
+ });
375
+ }
376
+ setAutoRotate(rotate) {
377
+ this.controls.autoRotate = rotate;
378
+ }
379
+ getAutoRotate() {
380
+ return this.controls.autoRotate;
381
+ }
382
+ replaceCamera(newCam) {
383
+ this.camera = newCam;
384
+ }
385
+ replaceControls(newControls) {
386
+ if (this.controls === newControls) {
387
+ return;
388
+ }
389
+ // disable the old, install the new.
390
+ this.controls.enabled = false;
391
+
392
+ // detach old control change handlers
393
+ this.removeControlHandlers();
394
+ this.controls = newControls;
395
+ this.controls.enabled = true;
396
+
397
+ // re-install existing control change handlers on new controls
398
+ if (this.controlStartHandler) {
399
+ this.controls.addEventListener("start", this.controlStartHandler);
400
+ }
401
+ if (this.controlChangeHandler) {
402
+ this.controls.addEventListener("change", this.controlChangeHandler);
403
+ }
404
+ if (this.controlEndHandler) {
405
+ this.controls.addEventListener("end", this.controlEndHandler);
406
+ }
407
+ this.controls.update();
408
+ }
409
+ switchViewMode(mode) {
410
+ mode = mode.toUpperCase();
411
+ switch (mode) {
412
+ case "YZ":
413
+ case "X":
414
+ this.replaceCamera(this.orthographicCameraX);
415
+ this.replaceControls(this.orthoControlsX);
416
+ this.axisHelperObject.rotation.set(0, Math.PI * 0.5, 0);
417
+ this.viewMode = Axis.X;
418
+ break;
419
+ case "XZ":
420
+ case "Y":
421
+ this.replaceCamera(this.orthographicCameraY);
422
+ this.replaceControls(this.orthoControlsY);
423
+ this.axisHelperObject.rotation.set(Math.PI * 0.5, 0, 0);
424
+ this.viewMode = Axis.Y;
425
+ break;
426
+ case "XY":
427
+ case "Z":
428
+ this.replaceCamera(this.orthographicCameraZ);
429
+ this.replaceControls(this.orthoControlsZ);
430
+ this.axisHelperObject.rotation.set(0, 0, 0);
431
+ this.viewMode = Axis.Z;
432
+ break;
433
+ default:
434
+ this.replaceCamera(this.perspectiveCamera);
435
+ this.replaceControls(this.perspectiveControls);
436
+ this.axisHelperObject.rotation.setFromRotationMatrix(this.camera.matrixWorldInverse);
437
+ this.viewMode = Axis.NONE;
438
+ break;
439
+ }
440
+ this.updateScaleBarVisibility();
441
+ }
442
+ getMeshDepthTexture() {
443
+ return this.meshRenderTarget.depthTexture;
444
+ }
445
+ resize(comp, w, h, _ow, _oh, _eOpts) {
446
+ w = w || this.containerdiv.parentElement?.offsetWidth || this.containerdiv.offsetWidth;
447
+ h = h || this.containerdiv.parentElement?.offsetHeight || this.containerdiv.offsetHeight;
448
+ this.containerdiv.style.width = "" + w + "px";
449
+ this.containerdiv.style.height = "" + h + "px";
450
+ const aspect = w / h;
451
+ this.perspectiveControls.aspect = aspect;
452
+ this.orthoControlsZ.aspect = aspect;
453
+ this.orthoControlsZ.panSpeed = w * 0.5;
454
+ this.orthoControlsY.aspect = aspect;
455
+ this.orthoControlsY.panSpeed = w * 0.5;
456
+ this.orthoControlsX.aspect = aspect;
457
+ this.orthoControlsX.panSpeed = w * 0.5;
458
+ if (isOrthographicCamera(this.camera)) {
459
+ this.camera.left = -DEFAULT_ORTHO_SCALE * aspect;
460
+ this.camera.right = DEFAULT_ORTHO_SCALE * aspect;
461
+ this.camera.updateProjectionMatrix();
462
+ } else {
463
+ this.camera.aspect = aspect;
464
+ this.camera.updateProjectionMatrix();
465
+ }
466
+ this.axisCamera.left = 0;
467
+ this.axisCamera.right = w;
468
+ this.axisCamera.top = h;
469
+ this.axisCamera.bottom = 0;
470
+ this.axisCamera.updateProjectionMatrix();
471
+ if (this.renderer.getPixelRatio() !== window.devicePixelRatio) {
472
+ this.renderer.setPixelRatio(window.devicePixelRatio);
473
+ }
474
+ this.renderer.setSize(w, h);
475
+ this.meshRenderTarget.setSize(w, h);
476
+ this.perspectiveControls.handleResize();
477
+ this.orthoControlsZ.handleResize();
478
+ this.orthoControlsY.handleResize();
479
+ this.orthoControlsX.handleResize();
480
+ }
481
+ setClearColor(color, alpha) {
482
+ this.renderer.setClearColor(color, alpha);
483
+ }
484
+ getWidth() {
485
+ return this.renderer.getContext().canvas.width;
486
+ }
487
+ getHeight() {
488
+ return this.renderer.getContext().canvas.height;
489
+ }
490
+ getCameraState() {
491
+ return {
492
+ position: this.camera.position.toArray(),
493
+ up: this.camera.up.toArray(),
494
+ target: this.controls.target.toArray(),
495
+ orthoScale: isOrthographicCamera(this.camera) ? this.controls.scale : undefined,
496
+ fov: isPerspectiveCamera(this.camera) ? this.camera.fov : undefined
497
+ };
498
+ }
499
+
500
+ /**
501
+ * Updates the camera's state, including the position, up vector, target position,
502
+ * scaling, and FOV. If values are missing from `state`, they will be left unchanged.
503
+ *
504
+ * @param state Partial `CameraState` object.
505
+ *
506
+ * If an OrthographicCamera is used, the camera's position will be constrained to match
507
+ * the `target` position along the current view mode.
508
+ */
509
+ setCameraState(state) {
510
+ const currentState = this.getCameraState();
511
+ // Fill in any missing properties with current state
512
+ const newState = {
513
+ ...currentState,
514
+ ...state
515
+ };
516
+ this.camera.up.fromArray(newState.up).normalize();
517
+ this.controls.target.fromArray(newState.target);
518
+ const constrainedPosition = constrainToAxis(newState.position, newState.target, this.viewMode);
519
+ this.camera.position.fromArray(constrainedPosition);
520
+
521
+ // Update fields by camera type
522
+ if (isOrthographicCamera(this.camera)) {
523
+ const scale = newState.orthoScale || DEFAULT_ORTHO_SCALE;
524
+ this.controls.scale = scale;
525
+ this.camera.zoom = 0.5 / scale;
526
+ } else {
527
+ this.camera.fov = newState.fov || this.fov;
528
+ }
529
+ this.controls.update();
530
+ this.camera.updateProjectionMatrix();
531
+ }
532
+ render() {
533
+ // update the axis helper in case the view was rotated
534
+ if (!isOrthographicCamera(this.camera)) {
535
+ this.axisHelperObject.rotation.setFromRotationMatrix(this.camera.matrixWorldInverse);
536
+ }
537
+
538
+ // do whatever we have to do before the main render of this.scene
539
+ for (let i = 0; i < this.animateFuncs.length; i++) {
540
+ if (this.animateFuncs[i]) {
541
+ this.animateFuncs[i](this.renderer, this.camera, this.meshRenderTarget.depthTexture);
542
+ }
543
+ }
544
+
545
+ // RENDERING
546
+ // Step 1: Render meshes, e.g. isosurfaces, separately to a render target. (Meshes are all on
547
+ // layer 1.) This is necessary to access the depth buffer.
548
+ this.camera.layers.set(MESH_LAYER);
549
+ this.renderer.setRenderTarget(this.meshRenderTarget);
550
+ this.renderer.render(this.scene, this.camera);
551
+
552
+ // Step 2: Render the mesh render target out to the screen.
553
+ this.meshRenderToBuffer.material.uniforms.image.value = this.meshRenderTarget.texture;
554
+ this.meshRenderToBuffer.render(this.renderer);
555
+
556
+ // Step 3: Render volumes, which can now depth test against the meshes.
557
+ this.camera.layers.set(VOLUME_LAYER);
558
+ this.renderer.setRenderTarget(null);
559
+ this.renderer.autoClear = false;
560
+ this.renderer.render(this.scene, this.camera);
561
+ this.renderer.autoClear = true;
562
+
563
+ // overlay
564
+ if (this.showAxis) {
565
+ this.renderer.autoClear = false;
566
+ this.renderer.render(this.axisHelperScene, this.axisCamera);
567
+ this.renderer.autoClear = true;
568
+ }
569
+ if (this.dataurlcallback) {
570
+ this.dataurlcallback(this.canvas.toDataURL());
571
+ this.dataurlcallback = undefined;
572
+ }
573
+ }
574
+ redraw() {
575
+ // if we are not in a render loop already
576
+ if (!this.inRenderLoop) {
577
+ // if there is currently a queued redraw, cancel it and replace it with a new one.
578
+ if (this.requestedRender) {
579
+ cancelAnimationFrame(this.requestedRender);
580
+ }
581
+ this.timer.begin();
582
+ this.requestedRender = requestAnimationFrame(this.onAnimationLoop.bind(this));
583
+ }
584
+ }
585
+ onAnimationLoop() {
586
+ // delta is in seconds
587
+ this.timer.update();
588
+ const delta = this.timer.lastFrameMs / 1000.0;
589
+ this.controls.update(delta);
590
+ this.render();
591
+ }
592
+ startRenderLoop() {
593
+ this.inRenderLoop = true;
594
+ // reset the timer so that the time delta won't go back to the last time we were animating.
595
+ this.timer.begin();
596
+ this.renderer.setAnimationLoop(this.onAnimationLoop.bind(this));
597
+ }
598
+ stopRenderLoop() {
599
+ this.renderer.setAnimationLoop(null);
600
+ this.inRenderLoop = false;
601
+ if (this.requestedRender) {
602
+ cancelAnimationFrame(this.requestedRender);
603
+ this.requestedRender = 0;
604
+ }
605
+ this.timer.end();
606
+ }
607
+ removeControlHandlers() {
608
+ if (this.controlStartHandler) {
609
+ this.controls.removeEventListener("start", this.controlStartHandler);
610
+ }
611
+ if (this.controlChangeHandler) {
612
+ this.controls.removeEventListener("change", this.controlChangeHandler);
613
+ }
614
+ if (this.controlEndHandler) {
615
+ this.controls.removeEventListener("end", this.controlEndHandler);
616
+ }
617
+ }
618
+ setControlHandlers(onstart, onchange, onend) {
619
+ this.removeControlHandlers();
620
+ if (onstart) {
621
+ this.controlStartHandler = onstart;
622
+ this.controls.addEventListener("start", this.controlStartHandler);
623
+ }
624
+ if (onchange) {
625
+ this.controlChangeHandler = onchange;
626
+ this.controls.addEventListener("change", this.controlChangeHandler);
627
+ }
628
+ if (onend) {
629
+ this.controlEndHandler = onend;
630
+ this.controls.addEventListener("end", this.controlEndHandler);
631
+ }
632
+ }
633
+ }
package/es/Timing.js ADDED
@@ -0,0 +1,28 @@
1
+ export default class Timing {
2
+ constructor() {
3
+ this.beginTime = (performance || Date).now();
4
+ this.prevTime = this.beginTime;
5
+ this.frames = 0;
6
+ this.lastFrameMs = 0;
7
+ this.lastFPS = 0;
8
+ }
9
+ begin() {
10
+ this.beginTime = (performance || Date).now();
11
+ }
12
+ end() {
13
+ this.frames++;
14
+ const time = (performance || Date).now();
15
+ this.lastFrameMs = time - this.beginTime;
16
+
17
+ // wait at least a second's worth of frames, to update FPS.
18
+ if (time >= this.prevTime + 1000) {
19
+ this.lastFPS = this.frames * 1000 / (time - this.prevTime);
20
+ this.prevTime = time;
21
+ this.frames = 0;
22
+ }
23
+ return time;
24
+ }
25
+ update() {
26
+ this.beginTime = this.end();
27
+ }
28
+ }