@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
|
@@ -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
|
+
}
|