@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.
- package/LICENSE.txt +26 -0
- package/README.md +119 -0
- package/es/Atlas2DSlice.js +224 -0
- package/es/Channel.js +264 -0
- package/es/FileSaver.js +31 -0
- package/es/FusedChannelData.js +192 -0
- package/es/Histogram.js +250 -0
- package/es/ImageInfo.js +127 -0
- package/es/Light.js +74 -0
- package/es/Lut.js +500 -0
- package/es/MarchingCubes.js +507 -0
- package/es/MeshVolume.js +334 -0
- package/es/NaiveSurfaceNets.js +251 -0
- package/es/PathTracedVolume.js +482 -0
- package/es/RayMarchedAtlasVolume.js +250 -0
- package/es/RenderToBuffer.js +31 -0
- package/es/ThreeJsPanel.js +633 -0
- package/es/Timing.js +28 -0
- package/es/TrackballControls.js +538 -0
- package/es/View3d.js +848 -0
- package/es/Volume.js +352 -0
- package/es/VolumeCache.js +161 -0
- package/es/VolumeDims.js +16 -0
- package/es/VolumeDrawable.js +702 -0
- package/es/VolumeMaker.js +101 -0
- package/es/VolumeRenderImpl.js +1 -0
- package/es/VolumeRenderSettings.js +203 -0
- package/es/constants/basicShaders.js +29 -0
- package/es/constants/colors.js +59 -0
- package/es/constants/denoiseShader.js +43 -0
- package/es/constants/lights.js +42 -0
- package/es/constants/materials.js +85 -0
- package/es/constants/pathtraceOutputShader.js +13 -0
- package/es/constants/scaleBarSVG.js +21 -0
- package/es/constants/time.js +34 -0
- package/es/constants/volumePTshader.js +153 -0
- package/es/constants/volumeRayMarchShader.js +123 -0
- package/es/constants/volumeSliceShader.js +115 -0
- package/es/index.js +21 -0
- package/es/loaders/IVolumeLoader.js +131 -0
- package/es/loaders/JsonImageInfoLoader.js +255 -0
- package/es/loaders/OmeZarrLoader.js +495 -0
- package/es/loaders/OpenCellLoader.js +65 -0
- package/es/loaders/RawArrayLoader.js +89 -0
- package/es/loaders/TiffLoader.js +219 -0
- package/es/loaders/VolumeLoadError.js +44 -0
- package/es/loaders/VolumeLoaderUtils.js +221 -0
- package/es/loaders/index.js +40 -0
- package/es/loaders/zarr_utils/ChunkPrefetchIterator.js +143 -0
- package/es/loaders/zarr_utils/WrappedStore.js +51 -0
- package/es/loaders/zarr_utils/types.js +24 -0
- package/es/loaders/zarr_utils/utils.js +225 -0
- package/es/loaders/zarr_utils/validation.js +49 -0
- package/es/test/ChunkPrefetchIterator.test.js +208 -0
- package/es/test/RequestQueue.test.js +442 -0
- package/es/test/SubscribableRequestQueue.test.js +244 -0
- package/es/test/VolumeCache.test.js +118 -0
- package/es/test/VolumeRenderSettings.test.js +71 -0
- package/es/test/lut.test.js +671 -0
- package/es/test/num_utils.test.js +140 -0
- package/es/test/volume.test.js +98 -0
- package/es/test/zarr_utils.test.js +358 -0
- package/es/types/Atlas2DSlice.d.ts +41 -0
- package/es/types/Channel.d.ts +44 -0
- package/es/types/FileSaver.d.ts +6 -0
- package/es/types/FusedChannelData.d.ts +26 -0
- package/es/types/Histogram.d.ts +57 -0
- package/es/types/ImageInfo.d.ts +87 -0
- package/es/types/Light.d.ts +27 -0
- package/es/types/Lut.d.ts +67 -0
- package/es/types/MarchingCubes.d.ts +53 -0
- package/es/types/MeshVolume.d.ts +40 -0
- package/es/types/NaiveSurfaceNets.d.ts +11 -0
- package/es/types/PathTracedVolume.d.ts +65 -0
- package/es/types/RayMarchedAtlasVolume.d.ts +41 -0
- package/es/types/RenderToBuffer.d.ts +17 -0
- package/es/types/ThreeJsPanel.d.ts +107 -0
- package/es/types/Timing.d.ts +11 -0
- package/es/types/TrackballControls.d.ts +51 -0
- package/es/types/View3d.d.ts +357 -0
- package/es/types/Volume.d.ts +152 -0
- package/es/types/VolumeCache.d.ts +43 -0
- package/es/types/VolumeDims.d.ts +28 -0
- package/es/types/VolumeDrawable.d.ts +108 -0
- package/es/types/VolumeMaker.d.ts +49 -0
- package/es/types/VolumeRenderImpl.d.ts +22 -0
- package/es/types/VolumeRenderSettings.d.ts +98 -0
- package/es/types/constants/basicShaders.d.ts +4 -0
- package/es/types/constants/colors.d.ts +2 -0
- package/es/types/constants/denoiseShader.d.ts +40 -0
- package/es/types/constants/lights.d.ts +38 -0
- package/es/types/constants/materials.d.ts +20 -0
- package/es/types/constants/pathtraceOutputShader.d.ts +11 -0
- package/es/types/constants/scaleBarSVG.d.ts +2 -0
- package/es/types/constants/time.d.ts +19 -0
- package/es/types/constants/volumePTshader.d.ts +137 -0
- package/es/types/constants/volumeRayMarchShader.d.ts +117 -0
- package/es/types/constants/volumeSliceShader.d.ts +109 -0
- package/es/types/glsl.d.js +0 -0
- package/es/types/index.d.ts +28 -0
- package/es/types/loaders/IVolumeLoader.d.ts +113 -0
- package/es/types/loaders/JsonImageInfoLoader.d.ts +80 -0
- package/es/types/loaders/OmeZarrLoader.d.ts +87 -0
- package/es/types/loaders/OpenCellLoader.d.ts +9 -0
- package/es/types/loaders/RawArrayLoader.d.ts +33 -0
- package/es/types/loaders/TiffLoader.d.ts +45 -0
- package/es/types/loaders/VolumeLoadError.d.ts +18 -0
- package/es/types/loaders/VolumeLoaderUtils.d.ts +38 -0
- package/es/types/loaders/index.d.ts +22 -0
- package/es/types/loaders/zarr_utils/ChunkPrefetchIterator.d.ts +22 -0
- package/es/types/loaders/zarr_utils/WrappedStore.d.ts +24 -0
- package/es/types/loaders/zarr_utils/types.d.ts +94 -0
- package/es/types/loaders/zarr_utils/utils.d.ts +23 -0
- package/es/types/loaders/zarr_utils/validation.d.ts +7 -0
- package/es/types/test/ChunkPrefetchIterator.test.d.ts +1 -0
- package/es/types/test/RequestQueue.test.d.ts +1 -0
- package/es/types/test/SubscribableRequestQueue.test.d.ts +1 -0
- package/es/types/test/VolumeCache.test.d.ts +1 -0
- package/es/types/test/VolumeRenderSettings.test.d.ts +1 -0
- package/es/types/test/lut.test.d.ts +1 -0
- package/es/types/test/num_utils.test.d.ts +1 -0
- package/es/types/test/volume.test.d.ts +1 -0
- package/es/types/test/zarr_utils.test.d.ts +1 -0
- package/es/types/types.d.ts +115 -0
- package/es/types/utils/RequestQueue.d.ts +112 -0
- package/es/types/utils/SubscribableRequestQueue.d.ts +52 -0
- package/es/types/utils/num_utils.d.ts +43 -0
- package/es/types/workers/VolumeLoaderContext.d.ts +106 -0
- package/es/types/workers/types.d.ts +101 -0
- package/es/types/workers/util.d.ts +3 -0
- package/es/types.js +75 -0
- package/es/typings.d.js +0 -0
- package/es/utils/RequestQueue.js +267 -0
- package/es/utils/SubscribableRequestQueue.js +187 -0
- package/es/utils/num_utils.js +231 -0
- package/es/workers/FetchTiffWorker.js +153 -0
- package/es/workers/VolumeLoadWorker.js +129 -0
- package/es/workers/VolumeLoaderContext.js +271 -0
- package/es/workers/types.js +41 -0
- package/es/workers/util.js +8 -0
- package/package.json +83 -0
package/es/View3d.js
ADDED
|
@@ -0,0 +1,848 @@
|
|
|
1
|
+
import { AmbientLight, Vector3, Object3D, SpotLight, DirectionalLight, Euler, Scene, Color } from "three";
|
|
2
|
+
import { Pane } from "tweakpane";
|
|
3
|
+
import { MESH_LAYER, ThreeJsPanel } from "./ThreeJsPanel.js";
|
|
4
|
+
import lightSettings from "./constants/lights.js";
|
|
5
|
+
import VolumeDrawable from "./VolumeDrawable.js";
|
|
6
|
+
import { Light, AREA_LIGHT, SKY_LIGHT } from "./Light.js";
|
|
7
|
+
import { isOrthographicCamera, ViewportCorner, RenderMode } from "./types.js";
|
|
8
|
+
import { Axis } from "./VolumeRenderSettings.js";
|
|
9
|
+
// Constants are kept for compatibility reasons.
|
|
10
|
+
export const RENDERMODE_RAYMARCH = RenderMode.RAYMARCH;
|
|
11
|
+
export const RENDERMODE_PATHTRACE = RenderMode.PATHTRACE;
|
|
12
|
+
const allGlobalLoadingOptions = {
|
|
13
|
+
numChunksToPrefetchAhead: 5,
|
|
14
|
+
prefetchAlongNonPlayingAxis: false,
|
|
15
|
+
throttleArrivingChannelData: true
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @class
|
|
20
|
+
*/
|
|
21
|
+
export class View3d {
|
|
22
|
+
// TODO because View3d is basically a top level entrypoint for Vol-E,
|
|
23
|
+
// maybe it should create the VolumeLoaderContext with options passed in.
|
|
24
|
+
// (instead of having the loaderContext created externally)
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @param {Object} options Optional options.
|
|
28
|
+
* @param {boolean} options.useWebGL2 Default true
|
|
29
|
+
* @param {HTMLElement} options.parentElement An optional element to which to append the viewer element on creation.
|
|
30
|
+
* The viewer will attempt to fill this element if provided.
|
|
31
|
+
*/
|
|
32
|
+
constructor(options) {
|
|
33
|
+
const useWebGL2 = options?.useWebGL2 === undefined ? true : options.useWebGL2;
|
|
34
|
+
this.canvas3d = new ThreeJsPanel(options?.parentElement, useWebGL2);
|
|
35
|
+
this.redraw = this.redraw.bind(this);
|
|
36
|
+
this.scene = new Scene();
|
|
37
|
+
this.backgroundColor = new Color(0x000000);
|
|
38
|
+
this.lights = [];
|
|
39
|
+
this.pixelSamplingRate = 0.75;
|
|
40
|
+
this.exposure = 0.5;
|
|
41
|
+
this.volumeRenderMode = RenderMode.RAYMARCH;
|
|
42
|
+
window.addEventListener("resize", () => this.resize(null));
|
|
43
|
+
this.lightContainer = new Object3D();
|
|
44
|
+
this.ambientLight = new AmbientLight();
|
|
45
|
+
this.spotLight = new SpotLight();
|
|
46
|
+
this.reflectedLight = new DirectionalLight();
|
|
47
|
+
this.fillLight = new DirectionalLight();
|
|
48
|
+
this.buildScene();
|
|
49
|
+
this.tweakpane = null;
|
|
50
|
+
window.addEventListener("keydown", this.handleKeydown);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// prerender should be called on every redraw and should be the first thing done.
|
|
54
|
+
preRender() {
|
|
55
|
+
// TODO: if fps just updated and it's too low, do something:
|
|
56
|
+
// if (this.canvas3d.timer.lastFPS < 7 && this.canvas3d.timer.lastFPS > 0 && this.canvas3d.timer.frames === 0) {
|
|
57
|
+
// }
|
|
58
|
+
|
|
59
|
+
const lightContainer = this.scene.getObjectByName("lightContainer");
|
|
60
|
+
if (lightContainer) {
|
|
61
|
+
lightContainer.rotation.setFromRotationMatrix(this.canvas3d.camera.matrixWorld);
|
|
62
|
+
}
|
|
63
|
+
// keep the ortho scale up to date.
|
|
64
|
+
if (this.image && isOrthographicCamera(this.canvas3d.camera)) {
|
|
65
|
+
const {
|
|
66
|
+
top,
|
|
67
|
+
zoom
|
|
68
|
+
} = this.canvas3d.camera;
|
|
69
|
+
this.image.setOrthoScale(Math.abs(top) / zoom);
|
|
70
|
+
this.updateOrthoScaleBar(this.image.volume);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
updateOrthoScaleBar(volume) {
|
|
74
|
+
this.canvas3d.updateOrthoScaleBar(volume.physicalScale, volume.imageInfo.spatialUnit);
|
|
75
|
+
}
|
|
76
|
+
updatePerspectiveScaleBar(volume) {
|
|
77
|
+
this.canvas3d.updatePerspectiveScaleBar(volume.tickMarkPhysicalLength, volume.imageInfo.spatialUnit);
|
|
78
|
+
}
|
|
79
|
+
updateTimestepIndicator(volume) {
|
|
80
|
+
const {
|
|
81
|
+
times,
|
|
82
|
+
timeScale,
|
|
83
|
+
timeUnit
|
|
84
|
+
} = volume.imageInfo;
|
|
85
|
+
const currentTime = volume.loadSpec.time;
|
|
86
|
+
this.canvas3d.updateTimestepIndicator(currentTime * timeScale, times * timeScale, timeUnit);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Capture the contents of this canvas to a data url
|
|
91
|
+
* @param {Object} dataurlcallback function to call when data url is ready; function accepts dataurl as string arg
|
|
92
|
+
*/
|
|
93
|
+
capture(dataurlcallback) {
|
|
94
|
+
return this.canvas3d.requestCapture(dataurlcallback);
|
|
95
|
+
}
|
|
96
|
+
getDOMElement() {
|
|
97
|
+
return this.canvas3d.containerdiv;
|
|
98
|
+
}
|
|
99
|
+
getCameraState() {
|
|
100
|
+
return this.canvas3d.getCameraState();
|
|
101
|
+
}
|
|
102
|
+
setCameraState(transform) {
|
|
103
|
+
this.canvas3d.setCameraState(transform);
|
|
104
|
+
this.redraw();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Force a redraw.
|
|
109
|
+
*/
|
|
110
|
+
redraw() {
|
|
111
|
+
this.canvas3d.redraw();
|
|
112
|
+
}
|
|
113
|
+
unsetImage() {
|
|
114
|
+
if (this.image) {
|
|
115
|
+
this.canvas3d.removeControlHandlers();
|
|
116
|
+
this.canvas3d.animateFuncs = [];
|
|
117
|
+
this.scene.remove(this.image.sceneRoot);
|
|
118
|
+
}
|
|
119
|
+
return this.image;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Add a new volume image to the viewer. (The viewer currently only supports a single image at a time - adding repeatedly, without removing in between, is a potential resource leak)
|
|
124
|
+
* @param {Volume} volume
|
|
125
|
+
* @param {VolumeDisplayOptions} options
|
|
126
|
+
*/
|
|
127
|
+
addVolume(volume, options) {
|
|
128
|
+
volume.addVolumeDataObserver(this);
|
|
129
|
+
options = options || {};
|
|
130
|
+
options.renderMode = this.volumeRenderMode;
|
|
131
|
+
this.setImage(new VolumeDrawable(volume, options));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Apply a set of display options to a given channel of a volume
|
|
136
|
+
* @param {Volume} volume
|
|
137
|
+
* @param {number} channelIndex the channel index
|
|
138
|
+
* @param {VolumeChannelDisplayOptions} options
|
|
139
|
+
*/
|
|
140
|
+
setVolumeChannelOptions(volume, channelIndex, options) {
|
|
141
|
+
this.image?.setChannelOptions(channelIndex, options);
|
|
142
|
+
this.redraw();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Apply a set of display options to the given volume
|
|
147
|
+
* @param {Volume} volume
|
|
148
|
+
* @param {VolumeDisplayOptions} options
|
|
149
|
+
*/
|
|
150
|
+
setVolumeDisplayOptions(volume, options) {
|
|
151
|
+
this.image?.setOptions(options);
|
|
152
|
+
this.redraw();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Remove a volume image from the viewer. This will clean up the View3D's resources for the current volume
|
|
157
|
+
* @param {Volume} volume
|
|
158
|
+
*/
|
|
159
|
+
removeVolume(volume) {
|
|
160
|
+
const oldImage = this.unsetImage();
|
|
161
|
+
if (oldImage) {
|
|
162
|
+
oldImage.cleanup();
|
|
163
|
+
}
|
|
164
|
+
if (volume) {
|
|
165
|
+
// assert oldImage.volume === volume!
|
|
166
|
+
volume.removeVolumeDataObserver(this);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Remove all volume images from the viewer.
|
|
172
|
+
*/
|
|
173
|
+
removeAllVolumes() {
|
|
174
|
+
if (this.image) {
|
|
175
|
+
this.removeVolume(this.image.volume);
|
|
176
|
+
}
|
|
177
|
+
this.image = undefined;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* @param {function} callback a function that will receive the number of render iterations when it changes
|
|
182
|
+
*/
|
|
183
|
+
setRenderUpdateListener(callback) {
|
|
184
|
+
this.renderUpdateListener = callback;
|
|
185
|
+
this.image?.setRenderUpdateListener(callback);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// channels is an array of channel indices for which new data just arrived.
|
|
189
|
+
onVolumeData(volume, channels) {
|
|
190
|
+
this.image?.updateScale();
|
|
191
|
+
this.image?.onChannelLoaded(channels);
|
|
192
|
+
if (volume.isLoaded() && this.tweakpane) {
|
|
193
|
+
this.tweakpane.refresh();
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// do fixups for when the volume has had a new empty channel added.
|
|
198
|
+
onVolumeChannelAdded(volume, newChannelIndex) {
|
|
199
|
+
this.image?.onChannelAdded(newChannelIndex);
|
|
200
|
+
}
|
|
201
|
+
onVolumeLoadError(volume, error) {
|
|
202
|
+
this.loadErrorHandler?.(volume, error);
|
|
203
|
+
}
|
|
204
|
+
setLoadErrorHandler(handler) {
|
|
205
|
+
this.loadErrorHandler = handler;
|
|
206
|
+
}
|
|
207
|
+
setTime(volume, time, onChannelLoaded) {
|
|
208
|
+
const timeClamped = Math.max(0, Math.min(time, volume.imageInfo.times - 1));
|
|
209
|
+
volume.updateRequiredData({
|
|
210
|
+
time: timeClamped
|
|
211
|
+
}, onChannelLoaded);
|
|
212
|
+
this.updateTimestepIndicator(volume);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Nudge the scale level loaded into this volume off the one chosen by the loader.
|
|
217
|
+
* E.g. a bias of `1` will load 1 scale level lower than "ideal."
|
|
218
|
+
*/
|
|
219
|
+
setScaleLevelBias(volume, scaleLevelBias) {
|
|
220
|
+
volume.updateRequiredData({
|
|
221
|
+
scaleLevelBias
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Assign a channel index as a mask channel (will multiply its color against the entire visible volume)
|
|
227
|
+
* @param {Object} volume
|
|
228
|
+
* @param {number} maskChannelIndex
|
|
229
|
+
*/
|
|
230
|
+
setVolumeChannelAsMask(volume, maskChannelIndex) {
|
|
231
|
+
this.image?.setChannelAsMask(maskChannelIndex);
|
|
232
|
+
this.redraw();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Set voxel dimensions - controls volume scaling. For example, the physical measurements of the voxels from a biological data set
|
|
237
|
+
* @param {Object} volume
|
|
238
|
+
* @param {number} values Array of x,y,z floating point values for the physical voxel size scaling
|
|
239
|
+
* @param {string} unit The unit of `values`, if different than previous
|
|
240
|
+
*/
|
|
241
|
+
setVoxelSize(volume, values, unit) {
|
|
242
|
+
if (this.image) {
|
|
243
|
+
this.image.setVoxelSize(new Vector3().fromArray(values));
|
|
244
|
+
if (unit) {
|
|
245
|
+
this.image.volume.setUnitSymbol(unit);
|
|
246
|
+
}
|
|
247
|
+
this.updatePerspectiveScaleBar(this.image.volume);
|
|
248
|
+
}
|
|
249
|
+
this.redraw();
|
|
250
|
+
}
|
|
251
|
+
setRayStepSizes(volume, primary, secondary) {
|
|
252
|
+
this.image?.setRayStepSizes(primary, secondary);
|
|
253
|
+
this.redraw();
|
|
254
|
+
}
|
|
255
|
+
setShowBoundingBox(volume, showBoundingBox) {
|
|
256
|
+
this.image?.setShowBoundingBox(showBoundingBox);
|
|
257
|
+
this.canvas3d.setShowPerspectiveScaleBar(showBoundingBox && this.canvas3d.showOrthoScaleBar && this.volumeRenderMode !== RenderMode.PATHTRACE);
|
|
258
|
+
this.redraw();
|
|
259
|
+
}
|
|
260
|
+
setBoundingBoxColor(volume, color) {
|
|
261
|
+
this.image?.setBoundingBoxColor(color);
|
|
262
|
+
this.canvas3d.setPerspectiveScaleBarColor(color);
|
|
263
|
+
this.redraw();
|
|
264
|
+
}
|
|
265
|
+
setBackgroundColor(color) {
|
|
266
|
+
const c = new Color().fromArray(color);
|
|
267
|
+
this.backgroundColor = c;
|
|
268
|
+
this.canvas3d.setClearColor(c, 1);
|
|
269
|
+
this.redraw();
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Is an isosurface already created for this channel?
|
|
274
|
+
* @param {Object} volume
|
|
275
|
+
* @param {number} channel
|
|
276
|
+
* @return true if there is currently a mesh isosurface for this channel
|
|
277
|
+
*/
|
|
278
|
+
hasIsosurface(volume, channel) {
|
|
279
|
+
return this.image?.hasIsosurface(channel) || false;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Save a channel's isosurface as a triangle mesh to either STL or GLTF2 format. File will be named automatically, using image name and channel name.
|
|
284
|
+
* @param {Object} volume
|
|
285
|
+
* @param {number} channelIndex
|
|
286
|
+
* @param {string} type Either 'GLTF' or 'STL'
|
|
287
|
+
*/
|
|
288
|
+
saveChannelIsosurface(volume, channelIndex, type) {
|
|
289
|
+
this.image?.saveChannelIsosurface(channelIndex, type);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Add a new volume image to the viewer. The viewer currently only supports a single image at a time, and will return any prior existing image.
|
|
293
|
+
setImage(img) {
|
|
294
|
+
const oldImage = this.unsetImage();
|
|
295
|
+
this.image = img;
|
|
296
|
+
this.scene.add(img.sceneRoot);
|
|
297
|
+
|
|
298
|
+
// new image picks up current settings
|
|
299
|
+
this.image.setResolution(this.canvas3d.getWidth(), this.canvas3d.getHeight());
|
|
300
|
+
this.image.setIsOrtho(isOrthographicCamera(this.canvas3d.camera));
|
|
301
|
+
this.image.setBrightness(this.exposure);
|
|
302
|
+
this.canvas3d.setControlHandlers(this.onStartControls.bind(this), this.onChangeControls.bind(this), this.onEndControls.bind(this));
|
|
303
|
+
this.canvas3d.animateFuncs.push(this.preRender.bind(this));
|
|
304
|
+
this.canvas3d.animateFuncs.push(img.onAnimate.bind(img));
|
|
305
|
+
this.updatePerspectiveScaleBar(img.volume);
|
|
306
|
+
this.updateTimestepIndicator(img.volume);
|
|
307
|
+
|
|
308
|
+
// redraw if not already in draw loop
|
|
309
|
+
this.redraw();
|
|
310
|
+
return oldImage;
|
|
311
|
+
}
|
|
312
|
+
onStartControls() {
|
|
313
|
+
if (this.volumeRenderMode !== RenderMode.PATHTRACE) {
|
|
314
|
+
// TODO: VR display requires a running renderloop
|
|
315
|
+
this.canvas3d.startRenderLoop();
|
|
316
|
+
}
|
|
317
|
+
this.image?.onStartControls();
|
|
318
|
+
}
|
|
319
|
+
onChangeControls() {
|
|
320
|
+
this.image?.onChangeControls();
|
|
321
|
+
}
|
|
322
|
+
onEndControls() {
|
|
323
|
+
this.image?.onEndControls();
|
|
324
|
+
// If we are pathtracing or autorotating, then keep rendering. Otherwise stop now.
|
|
325
|
+
if (this.volumeRenderMode !== RenderMode.PATHTRACE && !this.canvas3d.controls.autoRotate) {
|
|
326
|
+
// TODO: VR display requires a running renderloop
|
|
327
|
+
this.canvas3d.stopRenderLoop();
|
|
328
|
+
}
|
|
329
|
+
// force a redraw. This mainly fixes a bug with the way TrackballControls deals with wheel events.
|
|
330
|
+
this.redraw();
|
|
331
|
+
}
|
|
332
|
+
buildScene() {
|
|
333
|
+
this.scene = this.canvas3d.scene;
|
|
334
|
+
|
|
335
|
+
// background color
|
|
336
|
+
this.canvas3d.setClearColor(this.backgroundColor, 1.0);
|
|
337
|
+
this.lights = [new Light(SKY_LIGHT), new Light(AREA_LIGHT)];
|
|
338
|
+
this.lightContainer = new Object3D();
|
|
339
|
+
this.lightContainer.name = "lightContainer";
|
|
340
|
+
this.ambientLight = new AmbientLight(lightSettings.ambientLightSettings.color, lightSettings.ambientLightSettings.intensity);
|
|
341
|
+
this.ambientLight.layers.enable(MESH_LAYER);
|
|
342
|
+
this.lightContainer.add(this.ambientLight);
|
|
343
|
+
|
|
344
|
+
// key light
|
|
345
|
+
this.spotLight = new SpotLight(lightSettings.spotlightSettings.color, lightSettings.spotlightSettings.intensity);
|
|
346
|
+
this.spotLight.position.set(lightSettings.spotlightSettings.position.x, lightSettings.spotlightSettings.position.y, lightSettings.spotlightSettings.position.z);
|
|
347
|
+
this.spotLight.target = new Object3D(); // this.substrate;
|
|
348
|
+
this.spotLight.angle = lightSettings.spotlightSettings.angle;
|
|
349
|
+
this.spotLight.layers.enable(MESH_LAYER);
|
|
350
|
+
this.lightContainer.add(this.spotLight);
|
|
351
|
+
|
|
352
|
+
// reflect light
|
|
353
|
+
this.reflectedLight = new DirectionalLight(lightSettings.reflectedLightSettings.color);
|
|
354
|
+
this.reflectedLight.position.set(lightSettings.reflectedLightSettings.position.x, lightSettings.reflectedLightSettings.position.y, lightSettings.reflectedLightSettings.position.z);
|
|
355
|
+
this.reflectedLight.castShadow = lightSettings.reflectedLightSettings.castShadow;
|
|
356
|
+
this.reflectedLight.intensity = lightSettings.reflectedLightSettings.intensity;
|
|
357
|
+
this.reflectedLight.layers.enable(MESH_LAYER);
|
|
358
|
+
this.lightContainer.add(this.reflectedLight);
|
|
359
|
+
|
|
360
|
+
// fill light
|
|
361
|
+
this.fillLight = new DirectionalLight(lightSettings.fillLightSettings.color);
|
|
362
|
+
this.fillLight.position.set(lightSettings.fillLightSettings.position.x, lightSettings.fillLightSettings.position.y, lightSettings.fillLightSettings.position.z);
|
|
363
|
+
this.fillLight.castShadow = lightSettings.fillLightSettings.castShadow;
|
|
364
|
+
this.fillLight.intensity = lightSettings.fillLightSettings.intensity;
|
|
365
|
+
this.fillLight.layers.enable(MESH_LAYER);
|
|
366
|
+
this.lightContainer.add(this.fillLight);
|
|
367
|
+
this.scene.add(this.lightContainer);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// TODO: Change mode to an enum
|
|
371
|
+
/**
|
|
372
|
+
* Change the camera projection to look along an axis, or to view in a 3d perspective camera.
|
|
373
|
+
* @param {string} mode Mode can be "3D", or "XY" or "Z", or "YZ" or "X", or "XZ" or "Y". 3D is a perspective view, and all the others are orthographic projections
|
|
374
|
+
*/
|
|
375
|
+
setCameraMode(mode) {
|
|
376
|
+
this.canvas3d.switchViewMode(mode);
|
|
377
|
+
this.image?.setViewMode(mode, this.volumeRenderMode);
|
|
378
|
+
this.image?.setIsOrtho(mode !== "3D");
|
|
379
|
+
this.canvas3d.redraw();
|
|
380
|
+
}
|
|
381
|
+
setZSlice(volume, slice) {
|
|
382
|
+
if (this.image?.setZSlice(slice)) {
|
|
383
|
+
this.canvas3d.redraw();
|
|
384
|
+
return true;
|
|
385
|
+
}
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Enable or disable 3d axis display at lower left.
|
|
391
|
+
* @param {boolean} showAxis
|
|
392
|
+
*/
|
|
393
|
+
setShowAxis(showAxis) {
|
|
394
|
+
this.canvas3d.showAxis = showAxis;
|
|
395
|
+
this.canvas3d.redraw();
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Enable or disable scale indicators.
|
|
400
|
+
* @param showScaleBar
|
|
401
|
+
*/
|
|
402
|
+
setShowScaleBar(showScaleBar) {
|
|
403
|
+
this.canvas3d.setShowOrthoScaleBar(showScaleBar);
|
|
404
|
+
this.canvas3d.setShowPerspectiveScaleBar(showScaleBar && !!this.image?.showBoundingBox && this.volumeRenderMode !== RenderMode.PATHTRACE);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Enable or disable time indicator.
|
|
409
|
+
* @param showTimestepIndicator
|
|
410
|
+
*/
|
|
411
|
+
setShowTimestepIndicator(showIndicator) {
|
|
412
|
+
const times = this.image?.volume.imageInfo.times;
|
|
413
|
+
const hasTimes = !!times && times > 1;
|
|
414
|
+
this.canvas3d.setShowTimestepIndicator(showIndicator && hasTimes);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Set the position of the axis indicator, as a corner of the viewport and horizontal and vertical margins from the
|
|
419
|
+
* edge of the viewport.
|
|
420
|
+
* @param {number} marginX
|
|
421
|
+
* @param {number} marginY
|
|
422
|
+
* @param {string} [corner] The corner of the viewport in which the axis appears. Default: `"bottom_left"`.
|
|
423
|
+
* TypeScript users should use the `ViewportCorner` enum. Otherwise, corner is one of: `"top_left"`, `"top_right"`,
|
|
424
|
+
* `"bottom_left"`, `"bottom_right"`.
|
|
425
|
+
*/
|
|
426
|
+
setAxisPosition(marginX, marginY, corner = ViewportCorner.BOTTOM_LEFT) {
|
|
427
|
+
this.canvas3d.setAxisPosition(marginX, marginY, corner);
|
|
428
|
+
if (this.canvas3d.showAxis) {
|
|
429
|
+
this.canvas3d.redraw();
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Set the position of the scale bar, as a corner of the viewport and horizontal and vertical margins from the edge
|
|
435
|
+
* of the viewport.
|
|
436
|
+
* @param {number} marginX
|
|
437
|
+
* @param {number} marginY
|
|
438
|
+
* @param {string} [corner] The corner of the viewport in which the scale bar appears. Default: `"bottom_right"`.
|
|
439
|
+
* TypeScript users should use the `ViewportCorner` enum. Otherwise, corner is one of: `"top_left"`, `"top_right"`,
|
|
440
|
+
* `"bottom_left"`, `"bottom_right"`.
|
|
441
|
+
*/
|
|
442
|
+
setScaleBarPosition(marginX, marginY, corner = ViewportCorner.BOTTOM_RIGHT) {
|
|
443
|
+
this.canvas3d.setIndicatorPosition(false, marginX, marginY, corner);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Set the position of the time step indicator, as a corner of the viewport and horizontal and vertical margins from
|
|
448
|
+
* the edge of the viewport.
|
|
449
|
+
* @param {number} marginX
|
|
450
|
+
* @param {number} marginY
|
|
451
|
+
* @param {string} [corner] The corner of the viewport in which the scale bar appears. Default: `"bottom_right"`.
|
|
452
|
+
* TypeScript users should use the `ViewportCorner` enum. Otherwise, corner is one of: `"top_left"`, `"top_right"`,
|
|
453
|
+
* `"bottom_left"`, `"bottom_right"`.
|
|
454
|
+
*/
|
|
455
|
+
setTimestepIndicatorPosition(marginX, marginY, corner = ViewportCorner.BOTTOM_RIGHT) {
|
|
456
|
+
this.canvas3d.setIndicatorPosition(true, marginX, marginY, corner);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Enable or disable a turntable rotation mode. The display will continuously spin about the vertical screen axis.
|
|
461
|
+
* @param {boolean} autorotate
|
|
462
|
+
*/
|
|
463
|
+
setAutoRotate(autorotate) {
|
|
464
|
+
this.canvas3d.setAutoRotate(autorotate);
|
|
465
|
+
if (autorotate) {
|
|
466
|
+
this.onStartControls();
|
|
467
|
+
} else {
|
|
468
|
+
this.onEndControls();
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Set the unit symbol for the scale bar (e.g. µm)
|
|
474
|
+
* @param {string} unit
|
|
475
|
+
*/
|
|
476
|
+
setScaleUnit(unit) {
|
|
477
|
+
if (this.image) {
|
|
478
|
+
this.image.volume.setUnitSymbol(unit);
|
|
479
|
+
this.updatePerspectiveScaleBar(this.image.volume);
|
|
480
|
+
if (isOrthographicCamera(this.canvas3d.camera)) {
|
|
481
|
+
this.updateOrthoScaleBar(this.image.volume);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Invert axes of volume. -1 to invert, +1 NOT to invert.
|
|
488
|
+
* @param {Object} volume
|
|
489
|
+
* @param {number} flipX x axis sense
|
|
490
|
+
* @param {number} flipY y axis sense
|
|
491
|
+
* @param {number} flipZ z axis sense
|
|
492
|
+
*/
|
|
493
|
+
setFlipVolume(volume, flipX, flipY, flipZ) {
|
|
494
|
+
this.image?.setFlipAxes(flipX, flipY, flipZ);
|
|
495
|
+
this.redraw();
|
|
496
|
+
}
|
|
497
|
+
setInterpolationEnabled(volume, active) {
|
|
498
|
+
this.image?.setInterpolationEnabled(active);
|
|
499
|
+
this.redraw();
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Notify the view that it has been resized. This will automatically be connected to the window when the View3d is created.
|
|
504
|
+
* @param {HTMLElement=} comp Ignored.
|
|
505
|
+
* @param {number=} w Width, or parent element's offsetWidth if not specified.
|
|
506
|
+
* @param {number=} h Height, or parent element's offsetHeight if not specified.
|
|
507
|
+
* @param {number=} ow Ignored.
|
|
508
|
+
* @param {number=} oh Ignored.
|
|
509
|
+
* @param {Object=} eOpts Ignored.
|
|
510
|
+
*/
|
|
511
|
+
resize(comp, w, h, ow, oh, eOpts) {
|
|
512
|
+
this.canvas3d.resize(comp, w, h, ow, oh, eOpts);
|
|
513
|
+
this.image?.setResolution(this.canvas3d.getWidth(), this.canvas3d.getHeight());
|
|
514
|
+
this.redraw();
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Set the volume scattering density
|
|
519
|
+
* @param {Object} volume
|
|
520
|
+
* @param {number} density 0..1 UI slider value
|
|
521
|
+
*/
|
|
522
|
+
updateDensity(volume, density) {
|
|
523
|
+
this.image?.setDensity(density);
|
|
524
|
+
this.redraw();
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* Set the shading method - applies to pathtraced render mode only
|
|
529
|
+
* @param {Object} volume
|
|
530
|
+
* @param {number} isbrdf 0: brdf, 1: isotropic phase function, 2: mixed
|
|
531
|
+
*/
|
|
532
|
+
updateShadingMethod(volume, isbrdf) {
|
|
533
|
+
this.image?.updateShadingMethod(isbrdf);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* Set gamma levels: this affects the transparency and brightness of the single pass ray march volume render
|
|
538
|
+
* @param {Object} volume
|
|
539
|
+
* @param {number} gmin
|
|
540
|
+
* @param {number} glevel
|
|
541
|
+
* @param {number} gmax
|
|
542
|
+
*/
|
|
543
|
+
setGamma(volume, gmin, glevel, gmax) {
|
|
544
|
+
this.image?.setGamma(gmin, glevel, gmax);
|
|
545
|
+
this.redraw();
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* Set max projection on or off - applies to single pass raymarch render mode only
|
|
550
|
+
* @param {Object} volume
|
|
551
|
+
* @param {boolean} isMaxProject true for max project, false for regular volume ray march integration
|
|
552
|
+
*/
|
|
553
|
+
setMaxProjectMode(volume, isMaxProject) {
|
|
554
|
+
this.image?.setMaxProjectMode(isMaxProject);
|
|
555
|
+
this.redraw();
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* Notify the view that the set of active volume channels has been modified.
|
|
560
|
+
* @param {Object} volume
|
|
561
|
+
*/
|
|
562
|
+
updateActiveChannels(_volume) {
|
|
563
|
+
this.image?.fuse();
|
|
564
|
+
this.redraw();
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Notify the view that transfer function lookup table data has been modified.
|
|
569
|
+
* @param {Object} volume
|
|
570
|
+
*/
|
|
571
|
+
updateLuts(_volume) {
|
|
572
|
+
this.image?.updateLuts();
|
|
573
|
+
this.redraw();
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* Notify the view that color and appearance settings have been modified.
|
|
578
|
+
* @param {Object} volume
|
|
579
|
+
*/
|
|
580
|
+
updateMaterial(_volume) {
|
|
581
|
+
this.image?.updateMaterial();
|
|
582
|
+
this.redraw();
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Increase or decrease the overall brightness of the rendered image
|
|
587
|
+
* @param {number} e 0..1
|
|
588
|
+
*/
|
|
589
|
+
updateExposure(e) {
|
|
590
|
+
this.exposure = e;
|
|
591
|
+
this.image?.setBrightness(e);
|
|
592
|
+
this.redraw();
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Set camera focus properties.
|
|
597
|
+
* @param {number} fov Vertical field of view in degrees
|
|
598
|
+
* @param {number} focalDistance view-space units for center of focus
|
|
599
|
+
* @param {number} apertureSize view-space units for radius of camera aperture
|
|
600
|
+
*/
|
|
601
|
+
updateCamera(fov, focalDistance, apertureSize) {
|
|
602
|
+
this.canvas3d.updateCameraFocus(fov, focalDistance, apertureSize);
|
|
603
|
+
this.image?.onCameraChanged(fov, focalDistance, apertureSize);
|
|
604
|
+
this.redraw();
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* Set clipping range (between 0 and 1, relative to bounds) for the current volume.
|
|
609
|
+
* @param {Object} volume
|
|
610
|
+
* @param {number} xmin 0..1, should be less than xmax
|
|
611
|
+
* @param {number} xmax 0..1, should be greater than xmin
|
|
612
|
+
* @param {number} ymin 0..1, should be less than ymax
|
|
613
|
+
* @param {number} ymax 0..1, should be greater than ymin
|
|
614
|
+
* @param {number} zmin 0..1, should be less than zmax
|
|
615
|
+
* @param {number} zmax 0..1, should be greater than zmin
|
|
616
|
+
*/
|
|
617
|
+
updateClipRegion(volume, xmin, xmax, ymin, ymax, zmin, zmax) {
|
|
618
|
+
this.image?.updateClipRegion(xmin, xmax, ymin, ymax, zmin, zmax);
|
|
619
|
+
this.redraw();
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/**
|
|
623
|
+
* Set clipping range (between 0 and 1) for a given axis.
|
|
624
|
+
* Calling this allows the rendering to compensate for changes in thickness in orthographic views that affect how bright the volume is.
|
|
625
|
+
* @param {Object} volume
|
|
626
|
+
* @param {string} axis x, y, or z axis
|
|
627
|
+
* @param {number} minval 0..1, should be less than maxval
|
|
628
|
+
* @param {number} maxval 0..1, should be greater than minval
|
|
629
|
+
* @param {boolean} isOrthoAxis is this an orthographic projection or just a clipping of the range for perspective view
|
|
630
|
+
*/
|
|
631
|
+
setAxisClip(volume, axis, minval, maxval, isOrthoAxis) {
|
|
632
|
+
this.image?.setAxisClip(axis, minval, maxval, isOrthoAxis);
|
|
633
|
+
this.redraw();
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* Update lights
|
|
638
|
+
* @param {Array} state array of Lights
|
|
639
|
+
*/
|
|
640
|
+
updateLights(state) {
|
|
641
|
+
// TODO flesh this out
|
|
642
|
+
this.lights = state;
|
|
643
|
+
this.image?.updateLights(state);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
/**
|
|
647
|
+
* Set a sampling rate to trade performance for quality.
|
|
648
|
+
* @param {number} value (+epsilon..1) 1 is max quality, ~0.1 for lowest quality and highest speed
|
|
649
|
+
*/
|
|
650
|
+
updatePixelSamplingRate(value) {
|
|
651
|
+
if (this.pixelSamplingRate === value) {
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
this.pixelSamplingRate = value;
|
|
655
|
+
this.image?.setPixelSamplingRate(value);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/**
|
|
659
|
+
* Set the opacity of the mask channel
|
|
660
|
+
* @param {Object} volume
|
|
661
|
+
* @param {number} value (0..1) 0 for full transparent, 1 for fully opaque
|
|
662
|
+
*/
|
|
663
|
+
updateMaskAlpha(volume, value) {
|
|
664
|
+
this.image?.setMaskAlpha(value);
|
|
665
|
+
this.redraw();
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
/**
|
|
669
|
+
* Show / hide volume channels
|
|
670
|
+
* @param {Object} volume
|
|
671
|
+
* @param {number} channel
|
|
672
|
+
* @param {boolean} enabled
|
|
673
|
+
*/
|
|
674
|
+
setVolumeChannelEnabled(volume, channel, enabled) {
|
|
675
|
+
this.image?.setChannelOptions(channel, {
|
|
676
|
+
enabled
|
|
677
|
+
});
|
|
678
|
+
this.redraw();
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
/**
|
|
682
|
+
* Set the material for a channel
|
|
683
|
+
* @param {Object} volume
|
|
684
|
+
* @param {number} channelIndex
|
|
685
|
+
* @param {Array.<number>} colorrgb [r,g,b]
|
|
686
|
+
* @param {Array.<number>} specularrgb [r,g,b]
|
|
687
|
+
* @param {Array.<number>} emissivergb [r,g,b]
|
|
688
|
+
* @param {number} glossiness
|
|
689
|
+
*/
|
|
690
|
+
updateChannelMaterial(volume, channelIndex, colorrgb, specularrgb, emissivergb, glossiness) {
|
|
691
|
+
this.image?.updateChannelMaterial(channelIndex, colorrgb, specularrgb, emissivergb, glossiness);
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
/**
|
|
695
|
+
* Set the color for a channel
|
|
696
|
+
* @param {Object} volume
|
|
697
|
+
* @param {number} channelIndex
|
|
698
|
+
* @param {Array.<number>} colorrgb [r,g,b]
|
|
699
|
+
*/
|
|
700
|
+
updateChannelColor(volume, channelIndex, colorrgb) {
|
|
701
|
+
this.image?.updateChannelColor(channelIndex, colorrgb);
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
/**
|
|
705
|
+
* Switch between single pass ray-marched volume rendering and progressive path traced rendering.
|
|
706
|
+
* @param {RenderMode} mode RAYMARCH for single pass ray march, PATHTRACE for progressive path trace
|
|
707
|
+
*/
|
|
708
|
+
setVolumeRenderMode(mode) {
|
|
709
|
+
if (mode === this.volumeRenderMode) {
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
this.volumeRenderMode = mode;
|
|
713
|
+
if (this.image) {
|
|
714
|
+
const viewMode = this.image.getViewMode();
|
|
715
|
+
if (viewMode === Axis.Z) {
|
|
716
|
+
// if the camera view is in single-slice view, then we don't want to change
|
|
717
|
+
// anything but still remember the mode for when we switch back to a volumetric view
|
|
718
|
+
return;
|
|
719
|
+
} else if (mode === RenderMode.PATHTRACE && this.canvas3d.hasWebGL2) {
|
|
720
|
+
this.image.setVolumeRendering(RenderMode.PATHTRACE);
|
|
721
|
+
this.image.updateLights(this.lights);
|
|
722
|
+
// pathtrace is a continuous rendering mode
|
|
723
|
+
this.canvas3d.startRenderLoop();
|
|
724
|
+
} else if (mode === RenderMode.RAYMARCH) {
|
|
725
|
+
this.image.setVolumeRendering(RenderMode.RAYMARCH);
|
|
726
|
+
this.canvas3d.redraw();
|
|
727
|
+
}
|
|
728
|
+
this.updatePixelSamplingRate(this.pixelSamplingRate);
|
|
729
|
+
this.image.setIsOrtho(isOrthographicCamera(this.canvas3d.camera));
|
|
730
|
+
this.image.setResolution(this.canvas3d.getWidth(), this.canvas3d.getHeight());
|
|
731
|
+
this.setAutoRotate(this.canvas3d.controls.autoRotate);
|
|
732
|
+
this.image.setRenderUpdateListener(this.renderUpdateListener);
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
// TODO remove when pathtrace supports a bounding box
|
|
736
|
+
this.canvas3d.setShowPerspectiveScaleBar(this.canvas3d.showOrthoScaleBar && !!this.image?.showBoundingBox && mode !== RenderMode.PATHTRACE);
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
/**
|
|
740
|
+
*
|
|
741
|
+
* @param {Object} volume
|
|
742
|
+
* @param {Array.<number>} xyz
|
|
743
|
+
*/
|
|
744
|
+
setVolumeTranslation(volume, xyz) {
|
|
745
|
+
this.image?.setTranslation(new Vector3().fromArray(xyz));
|
|
746
|
+
this.redraw();
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
/**
|
|
750
|
+
*
|
|
751
|
+
* @param {Object} volume
|
|
752
|
+
* @param {Array.<number>} eulerXYZ
|
|
753
|
+
*/
|
|
754
|
+
setVolumeRotation(volume, eulerXYZ) {
|
|
755
|
+
this.image?.setRotation(new Euler().fromArray(eulerXYZ));
|
|
756
|
+
this.redraw();
|
|
757
|
+
}
|
|
758
|
+
setVolumeScale(volume, xyz) {
|
|
759
|
+
this.image?.setScale(new Vector3().fromArray(xyz));
|
|
760
|
+
this.redraw();
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
/**
|
|
764
|
+
* Reset the camera to its default position
|
|
765
|
+
*/
|
|
766
|
+
resetCamera() {
|
|
767
|
+
this.canvas3d.resetCamera();
|
|
768
|
+
this.image?.onResetCamera();
|
|
769
|
+
this.redraw();
|
|
770
|
+
}
|
|
771
|
+
hasWebGL2() {
|
|
772
|
+
return this.canvas3d.hasWebGL2;
|
|
773
|
+
}
|
|
774
|
+
handleKeydown = event => {
|
|
775
|
+
// control-option-1 (mac) or ctrl-alt-1 (windows)
|
|
776
|
+
if (event.code === "Digit1" && event.altKey && event.ctrlKey) {
|
|
777
|
+
if (this.tweakpane) {
|
|
778
|
+
this.tweakpane.dispose();
|
|
779
|
+
this.tweakpane = null;
|
|
780
|
+
} else {
|
|
781
|
+
this.tweakpane = this.setupGui(this.canvas3d.containerdiv);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
};
|
|
785
|
+
removeEventListeners() {
|
|
786
|
+
window.removeEventListener("keydown", this.handleKeydown);
|
|
787
|
+
}
|
|
788
|
+
setupGui(container) {
|
|
789
|
+
const pane = new Pane({
|
|
790
|
+
title: "Advanced Settings",
|
|
791
|
+
container
|
|
792
|
+
});
|
|
793
|
+
const paneStyle = {
|
|
794
|
+
position: "absolute",
|
|
795
|
+
top: "0",
|
|
796
|
+
right: "0"
|
|
797
|
+
};
|
|
798
|
+
Object.assign(pane.element.style, paneStyle);
|
|
799
|
+
|
|
800
|
+
// LIGHTS
|
|
801
|
+
const lights = pane.addFolder({
|
|
802
|
+
title: "Lights (isosurface)"
|
|
803
|
+
});
|
|
804
|
+
const addFolderForLight = (light, title) => {
|
|
805
|
+
const folder = lights.addFolder({
|
|
806
|
+
title,
|
|
807
|
+
expanded: false
|
|
808
|
+
});
|
|
809
|
+
folder.addInput(light, "color", {
|
|
810
|
+
color: {
|
|
811
|
+
type: "float"
|
|
812
|
+
}
|
|
813
|
+
});
|
|
814
|
+
folder.addInput(light, "intensity", {
|
|
815
|
+
min: 0
|
|
816
|
+
});
|
|
817
|
+
if (!light.isAmbientLight) {
|
|
818
|
+
folder.addInput(light, "position");
|
|
819
|
+
}
|
|
820
|
+
};
|
|
821
|
+
addFolderForLight(this.spotLight, "spot light");
|
|
822
|
+
addFolderForLight(this.ambientLight, "ambient light");
|
|
823
|
+
addFolderForLight(this.reflectedLight, "reflected light");
|
|
824
|
+
addFolderForLight(this.fillLight, "fill light");
|
|
825
|
+
this.image?.setupGui(pane);
|
|
826
|
+
const prefetch = pane.addFolder({
|
|
827
|
+
title: "Prefetch"
|
|
828
|
+
});
|
|
829
|
+
// one number will be used for all axis directions
|
|
830
|
+
prefetch.addInput(allGlobalLoadingOptions, "numChunksToPrefetchAhead").on("change", event => {
|
|
831
|
+
this.loaderContext?.getActiveLoader()?.updateFetchOptions({
|
|
832
|
+
maxPrefetchDistance: [event.value, event.value, event.value, event.value]
|
|
833
|
+
});
|
|
834
|
+
this.image?.volume.updateRequiredData({});
|
|
835
|
+
});
|
|
836
|
+
// should we try to prefetch along Z even if we are only playing along T?
|
|
837
|
+
prefetch.addInput(allGlobalLoadingOptions, "prefetchAlongNonPlayingAxis").on("change", event => {
|
|
838
|
+
this.loaderContext?.getActiveLoader()?.updateFetchOptions({
|
|
839
|
+
onlyPriorityDirections: !event.value
|
|
840
|
+
});
|
|
841
|
+
});
|
|
842
|
+
// when multiple prefetch frames arrive at once, should we slow down how quickly we load them?
|
|
843
|
+
prefetch.addInput(allGlobalLoadingOptions, "throttleArrivingChannelData").on("change", event => {
|
|
844
|
+
this.loaderContext?.setThrottleChannelData(event.value);
|
|
845
|
+
});
|
|
846
|
+
return pane;
|
|
847
|
+
}
|
|
848
|
+
}
|