@aics/vole-core 4.3.1 → 4.4.0
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/es/BaseDrawableMeshObject.js +72 -0
- package/es/Line3d.js +5 -49
- package/es/VectorArrows3d.js +325 -0
- package/es/View3d.js +35 -10
- package/es/VolumeDrawable.js +20 -20
- package/es/index.js +2 -1
- package/es/loaders/zarr_utils/utils.js +2 -1
- package/es/types/BaseDrawableMeshObject.d.ts +35 -0
- package/es/types/Line3d.d.ts +3 -17
- package/es/types/VectorArrows3d.d.ts +92 -0
- package/es/types/View3d.d.ts +21 -2
- package/es/types/VolumeDrawable.d.ts +11 -13
- package/es/types/index.d.ts +2 -1
- package/es/types/types.d.ts +4 -0
- package/package.json +2 -1
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { Group, Mesh, Vector3 } from "three";
|
|
2
|
+
/**
|
|
3
|
+
* Abstract base class for drawable 3D mesh objects.
|
|
4
|
+
*
|
|
5
|
+
* Provides default implementations for some methods in the IDrawableObject
|
|
6
|
+
* interface, including handling for visibility, transformations, and cleanup.
|
|
7
|
+
*
|
|
8
|
+
* As a default, subclasses should call `this.addChildMesh(mesh)` to register
|
|
9
|
+
* any meshes that should be managed by the base class.
|
|
10
|
+
*/
|
|
11
|
+
export default class BaseDrawableMeshObject {
|
|
12
|
+
/**
|
|
13
|
+
* Pivot group that all child meshes are parented to. Transformations are
|
|
14
|
+
* applied to this group.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
constructor() {
|
|
18
|
+
this.meshPivot = new Group();
|
|
19
|
+
this.scale = new Vector3(1, 1, 1);
|
|
20
|
+
this.flipAxes = new Vector3(1, 1, 1);
|
|
21
|
+
}
|
|
22
|
+
addChildMesh(mesh) {
|
|
23
|
+
this.meshPivot.add(mesh);
|
|
24
|
+
}
|
|
25
|
+
removeChildMesh(mesh) {
|
|
26
|
+
this.meshPivot.remove(mesh);
|
|
27
|
+
}
|
|
28
|
+
cleanup() {
|
|
29
|
+
this.meshPivot.traverse(obj => {
|
|
30
|
+
if (obj instanceof Mesh) {
|
|
31
|
+
obj.geometry.dispose();
|
|
32
|
+
obj.material.dispose();
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
this.meshPivot.clear();
|
|
36
|
+
}
|
|
37
|
+
doRender() {
|
|
38
|
+
// no op
|
|
39
|
+
}
|
|
40
|
+
setVisible(visible) {
|
|
41
|
+
this.meshPivot.visible = visible;
|
|
42
|
+
}
|
|
43
|
+
get3dObject() {
|
|
44
|
+
return this.meshPivot;
|
|
45
|
+
}
|
|
46
|
+
setTranslation(translation) {
|
|
47
|
+
this.meshPivot.position.copy(translation);
|
|
48
|
+
}
|
|
49
|
+
setScale(scale) {
|
|
50
|
+
this.scale.copy(scale);
|
|
51
|
+
this.meshPivot.scale.copy(scale).multiply(this.flipAxes);
|
|
52
|
+
}
|
|
53
|
+
setRotation(eulerXYZ) {
|
|
54
|
+
this.meshPivot.rotation.copy(eulerXYZ);
|
|
55
|
+
}
|
|
56
|
+
setFlipAxes(flipX, flipY, flipZ) {
|
|
57
|
+
this.flipAxes.set(flipX, flipY, flipZ);
|
|
58
|
+
this.meshPivot.scale.copy(this.scale).multiply(this.flipAxes);
|
|
59
|
+
}
|
|
60
|
+
setOrthoThickness(_thickness) {
|
|
61
|
+
// no op
|
|
62
|
+
}
|
|
63
|
+
setResolution(_x, _y) {
|
|
64
|
+
// no op
|
|
65
|
+
}
|
|
66
|
+
setAxisClip(_axis, _minval, _maxval, _isOrthoAxis) {
|
|
67
|
+
// no op
|
|
68
|
+
}
|
|
69
|
+
updateClipRegion(_xmin, _xmax, _ymin, _ymax, _zmin, _zmax) {
|
|
70
|
+
// no op
|
|
71
|
+
}
|
|
72
|
+
}
|
package/es/Line3d.js
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
import { Group, Vector3 } from "three";
|
|
2
1
|
import { LineMaterial } from "three/addons/lines/LineMaterial.js";
|
|
3
2
|
import { LineSegments2 } from "three/addons/lines/LineSegments2.js";
|
|
4
3
|
import { LineSegmentsGeometry } from "three/addons/lines/LineSegmentsGeometry.js";
|
|
5
4
|
import { MESH_NO_PICK_OCCLUSION_LAYER, OVERLAY_LAYER } from "./ThreeJsPanel.js";
|
|
5
|
+
import BaseDrawableMeshObject from "./BaseDrawableMeshObject.js";
|
|
6
6
|
const DEFAULT_VERTEX_BUFFER_SIZE = 1020;
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Simple wrapper for a 3D line segments object, with controls for vertex data,
|
|
10
10
|
* color, width, and segments visible.
|
|
11
11
|
*/
|
|
12
|
-
export default class Line3d {
|
|
12
|
+
export default class Line3d extends BaseDrawableMeshObject {
|
|
13
13
|
constructor() {
|
|
14
|
+
super();
|
|
14
15
|
this.bufferSize = DEFAULT_VERTEX_BUFFER_SIZE;
|
|
15
16
|
const geometry = new LineSegmentsGeometry();
|
|
16
17
|
geometry.setPositions(new Float32Array(this.bufferSize));
|
|
@@ -27,55 +28,10 @@ export default class Line3d {
|
|
|
27
28
|
// artifacts can occur where contours are drawn around lines. This layer
|
|
28
29
|
// (MESH_NO_PICK_OCCLUSION_LAYER) does not occlude/interact with the pick
|
|
29
30
|
// buffer but still writes depth information for the volume.
|
|
31
|
+
this.meshPivot.layers.set(MESH_NO_PICK_OCCLUSION_LAYER);
|
|
30
32
|
this.lineMesh.layers.set(MESH_NO_PICK_OCCLUSION_LAYER);
|
|
31
33
|
this.lineMesh.frustumCulled = false;
|
|
32
|
-
this.
|
|
33
|
-
this.meshPivot.add(this.lineMesh);
|
|
34
|
-
this.meshPivot.layers.set(MESH_NO_PICK_OCCLUSION_LAYER);
|
|
35
|
-
this.scale = new Vector3(1, 1, 1);
|
|
36
|
-
this.flipAxes = new Vector3(1, 1, 1);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// IDrawableObject interface methods
|
|
40
|
-
|
|
41
|
-
cleanup() {
|
|
42
|
-
this.lineMesh.geometry.dispose();
|
|
43
|
-
this.lineMesh.material.dispose();
|
|
44
|
-
}
|
|
45
|
-
setVisible(visible) {
|
|
46
|
-
this.lineMesh.visible = visible;
|
|
47
|
-
}
|
|
48
|
-
doRender() {
|
|
49
|
-
// no op
|
|
50
|
-
}
|
|
51
|
-
get3dObject() {
|
|
52
|
-
return this.meshPivot;
|
|
53
|
-
}
|
|
54
|
-
setTranslation(translation) {
|
|
55
|
-
this.meshPivot.position.copy(translation);
|
|
56
|
-
}
|
|
57
|
-
setScale(scale) {
|
|
58
|
-
this.scale.copy(scale);
|
|
59
|
-
this.meshPivot.scale.copy(scale).multiply(this.flipAxes);
|
|
60
|
-
}
|
|
61
|
-
setRotation(eulerXYZ) {
|
|
62
|
-
this.meshPivot.rotation.copy(eulerXYZ);
|
|
63
|
-
}
|
|
64
|
-
setFlipAxes(flipX, flipY, flipZ) {
|
|
65
|
-
this.flipAxes.set(flipX, flipY, flipZ);
|
|
66
|
-
this.meshPivot.scale.copy(this.scale).multiply(this.flipAxes);
|
|
67
|
-
}
|
|
68
|
-
setOrthoThickness(_thickness) {
|
|
69
|
-
// no op
|
|
70
|
-
}
|
|
71
|
-
setResolution(_x, _y) {
|
|
72
|
-
// no op
|
|
73
|
-
}
|
|
74
|
-
setAxisClip(_axis, _minval, _maxval, _isOrthoAxis) {
|
|
75
|
-
// no op
|
|
76
|
-
}
|
|
77
|
-
updateClipRegion(_xmin, _xmax, _ymin, _ymax, _zmin, _zmax) {
|
|
78
|
-
// no op
|
|
34
|
+
this.addChildMesh(this.lineMesh);
|
|
79
35
|
}
|
|
80
36
|
|
|
81
37
|
// Line-specific functions
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import { InstancedMesh, CylinderGeometry, ConeGeometry, Object3D, Vector3, MeshBasicMaterial, Color, DynamicDrawUsage, Matrix4 } from "three";
|
|
2
|
+
import BaseDrawableMeshObject from "./BaseDrawableMeshObject";
|
|
3
|
+
import { MESH_NO_PICK_OCCLUSION_LAYER } from "./ThreeJsPanel";
|
|
4
|
+
|
|
5
|
+
// Unscaled arrowhead dimensions.
|
|
6
|
+
const SHAFT_BASE_RADIUS = 0.5;
|
|
7
|
+
const HEAD_BASE_RADIUS = 1.5;
|
|
8
|
+
const HEAD_BASE_HEIGHT = 4;
|
|
9
|
+
|
|
10
|
+
/** Default arrow shaft thickness, in world units. */
|
|
11
|
+
const DEFAULT_DIAMETER = 0.002;
|
|
12
|
+
const DEFAULT_INSTANCE_COUNT = 256;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A drawable vector arrow field, which uses instanced meshes for performance.
|
|
16
|
+
*/
|
|
17
|
+
export default class VectorArrows3d extends BaseDrawableMeshObject {
|
|
18
|
+
/**
|
|
19
|
+
* Scale of this object in world coordinates, when unscaled. Used to
|
|
20
|
+
* compensate for parent transforms in order to keep arrow meshes from being
|
|
21
|
+
* distorted.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
// Temporary calculation objects. Optimization taken from three.js examples.
|
|
25
|
+
|
|
26
|
+
constructor() {
|
|
27
|
+
super();
|
|
28
|
+
this.worldScale = new Vector3(1, 1, 1);
|
|
29
|
+
this.meshPivot.layers.set(MESH_NO_PICK_OCCLUSION_LAYER);
|
|
30
|
+
this.maxInstanceCount = DEFAULT_INSTANCE_COUNT;
|
|
31
|
+
const {
|
|
32
|
+
headMesh: headMesh,
|
|
33
|
+
shaftMesh: shaftMesh
|
|
34
|
+
} = this.initInstancedMeshes(DEFAULT_INSTANCE_COUNT);
|
|
35
|
+
this.headInstancedMesh = headMesh;
|
|
36
|
+
this.shaftInstancedMesh = shaftMesh;
|
|
37
|
+
this.headInstancedMesh.count = 0;
|
|
38
|
+
this.shaftInstancedMesh.count = 0;
|
|
39
|
+
this.positions = null;
|
|
40
|
+
this.deltas = null;
|
|
41
|
+
this.colors = null;
|
|
42
|
+
this.diameter = new Float32Array([DEFAULT_DIAMETER]);
|
|
43
|
+
this.tempDst = new Vector3();
|
|
44
|
+
this.tempScale = new Vector3();
|
|
45
|
+
this.tempMatrix = new Object3D();
|
|
46
|
+
this.onParentTransformUpdated();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Returns (unscaled) buffer geometry for the head and shaft parts of the
|
|
51
|
+
* arrow.
|
|
52
|
+
* @returns
|
|
53
|
+
* - `head`: BufferGeometry for the arrowhead, a cone pointing along the +Z
|
|
54
|
+
* axis, with the pivot at the tip of the cone. Height and radius are based
|
|
55
|
+
* on constant values (`HEAD_BASE_HEIGHT` and `HEAD_BASE_RADIUS`).
|
|
56
|
+
* - `shaft`: BufferGeometry for the cylindrical arrow shaft. The cylinder
|
|
57
|
+
* points along the +Z axis, with the pivot at the base of the cylinder.
|
|
58
|
+
* Height is 1 and diameter is 1.
|
|
59
|
+
*
|
|
60
|
+
* ```txt
|
|
61
|
+
* ^ +Z axis ^
|
|
62
|
+
* _____ x
|
|
63
|
+
* | | / \
|
|
64
|
+
* | | / \
|
|
65
|
+
* --x-- /-----\
|
|
66
|
+
*
|
|
67
|
+
* x = pivot (0,0,0)
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
static generateGeometry() {
|
|
71
|
+
// TODO: Currently the shape of the arrow head is fixed. Allow configuring
|
|
72
|
+
// this in the future?
|
|
73
|
+
const cylinderGeometry = new CylinderGeometry(SHAFT_BASE_RADIUS, SHAFT_BASE_RADIUS, 2 * SHAFT_BASE_RADIUS,
|
|
74
|
+
// height
|
|
75
|
+
8,
|
|
76
|
+
// radial segments
|
|
77
|
+
1,
|
|
78
|
+
// height segments
|
|
79
|
+
false // capped ends
|
|
80
|
+
);
|
|
81
|
+
const coneRadius = HEAD_BASE_RADIUS;
|
|
82
|
+
const coneHeight = HEAD_BASE_HEIGHT;
|
|
83
|
+
const coneGeometry = new ConeGeometry(coneRadius, coneHeight, 12);
|
|
84
|
+
|
|
85
|
+
// Rotate both to point along +Z axis
|
|
86
|
+
const rotateToPositiveZ = new Matrix4().makeRotationX(Math.PI / 2);
|
|
87
|
+
// Change cone pivot to be at the tip.
|
|
88
|
+
const coneTranslation = new Matrix4().makeTranslation(0, 0, -coneHeight / 2);
|
|
89
|
+
coneGeometry.applyMatrix4(coneTranslation.multiply(rotateToPositiveZ));
|
|
90
|
+
// Change cylinder pivot to be at the base.
|
|
91
|
+
const cylinderTranslation = new Matrix4().makeTranslation(0, 0, 0.5);
|
|
92
|
+
cylinderGeometry.applyMatrix4(cylinderTranslation.multiply(rotateToPositiveZ));
|
|
93
|
+
return {
|
|
94
|
+
head: coneGeometry,
|
|
95
|
+
shaft: cylinderGeometry
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Create new instanced meshes with the specified instance count, and adds
|
|
101
|
+
* them to the mesh pivot and internal meshes array for future cleanup.
|
|
102
|
+
*
|
|
103
|
+
* If calling outside of the constructor, be sure to call `cleanup()` first.
|
|
104
|
+
*/
|
|
105
|
+
initInstancedMeshes(instanceCount) {
|
|
106
|
+
this.cleanup();
|
|
107
|
+
this.meshPivot.clear();
|
|
108
|
+
const basicMaterial = new MeshBasicMaterial({
|
|
109
|
+
color: "#fff"
|
|
110
|
+
});
|
|
111
|
+
const {
|
|
112
|
+
head: headGeometry,
|
|
113
|
+
shaft: shaftGeometry
|
|
114
|
+
} = VectorArrows3d.generateGeometry();
|
|
115
|
+
const headMesh = new InstancedMesh(headGeometry, basicMaterial, instanceCount);
|
|
116
|
+
const shaftMesh = new InstancedMesh(shaftGeometry, basicMaterial, instanceCount);
|
|
117
|
+
headMesh.layers.set(MESH_NO_PICK_OCCLUSION_LAYER);
|
|
118
|
+
shaftMesh.layers.set(MESH_NO_PICK_OCCLUSION_LAYER);
|
|
119
|
+
headMesh.frustumCulled = false;
|
|
120
|
+
shaftMesh.frustumCulled = false;
|
|
121
|
+
headMesh.instanceMatrix.setUsage(DynamicDrawUsage);
|
|
122
|
+
shaftMesh.instanceMatrix.setUsage(DynamicDrawUsage);
|
|
123
|
+
this.addChildMesh(headMesh);
|
|
124
|
+
this.addChildMesh(shaftMesh);
|
|
125
|
+
return {
|
|
126
|
+
headMesh,
|
|
127
|
+
shaftMesh
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
increaseInstanceCountMax(instanceCount) {
|
|
131
|
+
// Max instance count is set when instanced meshes are created. If we need
|
|
132
|
+
// to increase the max, we need to recreate the instanced meshes.
|
|
133
|
+
let newInstanceCount = this.maxInstanceCount;
|
|
134
|
+
while (newInstanceCount < instanceCount) {
|
|
135
|
+
newInstanceCount *= 2;
|
|
136
|
+
}
|
|
137
|
+
// Delete existing meshes
|
|
138
|
+
this.cleanup();
|
|
139
|
+
const {
|
|
140
|
+
headMesh,
|
|
141
|
+
shaftMesh
|
|
142
|
+
} = this.initInstancedMeshes(newInstanceCount);
|
|
143
|
+
this.headInstancedMesh = headMesh;
|
|
144
|
+
this.shaftInstancedMesh = shaftMesh;
|
|
145
|
+
this.maxInstanceCount = newInstanceCount;
|
|
146
|
+
}
|
|
147
|
+
setScale(scale) {
|
|
148
|
+
if (scale !== this.scale) {
|
|
149
|
+
this.onParentTransformUpdated();
|
|
150
|
+
this.scale.copy(scale);
|
|
151
|
+
if (this.positions && this.deltas) {
|
|
152
|
+
// Update arrows
|
|
153
|
+
this.setArrowData(this.positions, this.deltas);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Called when scaling of parent transforms has been updated or whenever
|
|
160
|
+
* vector data is updated.
|
|
161
|
+
*/
|
|
162
|
+
onParentTransformUpdated() {
|
|
163
|
+
// Measure world scale by temporarily resetting mesh pivot scale
|
|
164
|
+
this.meshPivot.scale.set(1, 1, 1);
|
|
165
|
+
let newWorldScale = new Vector3();
|
|
166
|
+
newWorldScale = this.meshPivot.getWorldScale(newWorldScale);
|
|
167
|
+
|
|
168
|
+
// Scale is inverted on mesh pivot to cancel out parent transforms (though
|
|
169
|
+
// translation and rotation are still affected by any parent transforms).
|
|
170
|
+
// This allows arrows meshes to be scaled 1:1 with world space, regardless
|
|
171
|
+
// of parent transforms, and prevents distortion or skewing of the mesh.
|
|
172
|
+
// Parent scaling is applied to arrow positions and deltas (see
|
|
173
|
+
// `updateAllArrowTransforms`), rather than the meshes themselves.
|
|
174
|
+
const invertScale = new Vector3(1, 1, 1).divide(newWorldScale);
|
|
175
|
+
this.meshPivot.scale.copy(invertScale);
|
|
176
|
+
if (!newWorldScale.equals(this.worldScale)) {
|
|
177
|
+
this.worldScale.copy(newWorldScale);
|
|
178
|
+
if (this.positions && this.deltas) {
|
|
179
|
+
this.setArrowData(this.positions, this.deltas);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
updateSingleArrowTransform(index, src, delta, diameter) {
|
|
184
|
+
// Update the arrow shaft
|
|
185
|
+
const headHeight = HEAD_BASE_HEIGHT * diameter;
|
|
186
|
+
const length = delta.length();
|
|
187
|
+
const shaftHeight = Math.max(length - headHeight, 0);
|
|
188
|
+
if (shaftHeight < 1e-6) {
|
|
189
|
+
// If the shaft height is too small, scale to 0.
|
|
190
|
+
this.tempScale.set(0, 0, 0);
|
|
191
|
+
} else {
|
|
192
|
+
this.tempScale.set(diameter, diameter, shaftHeight);
|
|
193
|
+
}
|
|
194
|
+
this.tempMatrix.scale.copy(this.tempScale);
|
|
195
|
+
this.tempMatrix.position.copy(src);
|
|
196
|
+
this.tempDst.copy(src).add(delta);
|
|
197
|
+
this.tempMatrix.lookAt(this.tempDst);
|
|
198
|
+
this.tempMatrix.updateMatrix();
|
|
199
|
+
this.shaftInstancedMesh.setMatrixAt(index, this.tempMatrix.matrix);
|
|
200
|
+
if (length < headHeight) {
|
|
201
|
+
// If head is longer than the total length, shrink the head to match
|
|
202
|
+
// length. TODO: Is it okay to do this automatically?
|
|
203
|
+
const newDiameter = length / HEAD_BASE_HEIGHT;
|
|
204
|
+
this.tempScale.set(newDiameter, newDiameter, newDiameter);
|
|
205
|
+
} else {
|
|
206
|
+
this.tempScale.set(diameter, diameter, diameter);
|
|
207
|
+
}
|
|
208
|
+
this.tempMatrix.scale.copy(this.tempScale);
|
|
209
|
+
this.tempMatrix.position.copy(this.tempDst);
|
|
210
|
+
this.tempDst.add(delta);
|
|
211
|
+
this.tempMatrix.lookAt(this.tempDst);
|
|
212
|
+
this.tempMatrix.updateMatrix();
|
|
213
|
+
this.headInstancedMesh.setMatrixAt(index, this.tempMatrix.matrix);
|
|
214
|
+
}
|
|
215
|
+
updateAllArrowTransforms() {
|
|
216
|
+
if (!this.positions || !this.deltas) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
const count = this.positions.length / 3;
|
|
220
|
+
const combinedScale = new Vector3().copy(this.scale).multiply(this.flipAxes).multiply(this.worldScale);
|
|
221
|
+
const tempSrc = new Vector3();
|
|
222
|
+
const tempDelta = new Vector3();
|
|
223
|
+
let tempDiameter;
|
|
224
|
+
for (let i = 0; i < count; i++) {
|
|
225
|
+
// Points and deltas scaled to volume space.
|
|
226
|
+
tempSrc.fromArray(this.positions, i * 3).multiply(combinedScale);
|
|
227
|
+
tempDelta.fromArray(this.deltas, i * 3).multiply(combinedScale);
|
|
228
|
+
tempDiameter = this.diameter[i % this.diameter.length] ?? DEFAULT_DIAMETER;
|
|
229
|
+
this.updateSingleArrowTransform(i, tempSrc, tempDelta, tempDiameter);
|
|
230
|
+
}
|
|
231
|
+
this.headInstancedMesh.instanceMatrix.needsUpdate = true;
|
|
232
|
+
this.shaftInstancedMesh.instanceMatrix.needsUpdate = true;
|
|
233
|
+
}
|
|
234
|
+
applyColors() {
|
|
235
|
+
if (!this.colors) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
const colorCount = Math.round(this.colors.length / 3);
|
|
239
|
+
const color = new Color();
|
|
240
|
+
for (let i = 0; i < this.headInstancedMesh.count; i++) {
|
|
241
|
+
// Wrap colors if there are fewer colors than arrows
|
|
242
|
+
const colorIndex = i % colorCount;
|
|
243
|
+
color.fromArray(this.colors, colorIndex * 3);
|
|
244
|
+
this.headInstancedMesh.setColorAt(i, color);
|
|
245
|
+
this.shaftInstancedMesh.setColorAt(i, color);
|
|
246
|
+
}
|
|
247
|
+
if (this.headInstancedMesh.instanceColor) {
|
|
248
|
+
this.headInstancedMesh.instanceColor.needsUpdate = true;
|
|
249
|
+
}
|
|
250
|
+
if (this.shaftInstancedMesh.instanceColor) {
|
|
251
|
+
this.shaftInstancedMesh.instanceColor.needsUpdate = true;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Sets the colors for the arrows as either a single Color or an array of RGB values.
|
|
257
|
+
* If there are more arrows than colors, colors will be repeated in order.
|
|
258
|
+
* @param colors Color object or numeric array of RGB values in the [0, 1] range.
|
|
259
|
+
* @throws {Error} If colors array length is not a multiple of 3.
|
|
260
|
+
*/
|
|
261
|
+
setColors(colors) {
|
|
262
|
+
if (colors instanceof Color) {
|
|
263
|
+
this.colors = new Float32Array(3);
|
|
264
|
+
colors.toArray(this.colors);
|
|
265
|
+
} else {
|
|
266
|
+
if (colors.length % 3 !== 0) {
|
|
267
|
+
throw new Error("VectorArrows.setColors: colors array length must be a multiple of 3.");
|
|
268
|
+
}
|
|
269
|
+
this.colors = new Float32Array(colors);
|
|
270
|
+
}
|
|
271
|
+
this.applyColors();
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Sets all arrows to a uniform diameter (default is `0.002`). To set
|
|
276
|
+
* per-arrow diameter, pass an array of values into `setArrowData` instead.
|
|
277
|
+
* @param diameter Diameter value to set for all arrows.
|
|
278
|
+
*/
|
|
279
|
+
setDiameter(diameter) {
|
|
280
|
+
this.diameter = new Float32Array([diameter]);
|
|
281
|
+
this.updateAllArrowTransforms();
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Sets the per-arrow data. The number of rendered arrows is equal to
|
|
286
|
+
* `positions.length / 3`.
|
|
287
|
+
* @param positions Float32Array, where every three values is the XYZ position
|
|
288
|
+
* of the base of an arrow.
|
|
289
|
+
* @param deltas Float32Array, where every three values is the XYZ delta
|
|
290
|
+
* vector for each arrow.
|
|
291
|
+
* @param diameters Optional Float32Array of diameter thickness values for
|
|
292
|
+
* each arrow's shaft. If provided, overrides a single diameter value set by
|
|
293
|
+
* `setDiameter`. If fewer diameter values are provided than arrows, the
|
|
294
|
+
* values will be repeated in order.
|
|
295
|
+
* @throws {Error} If positions and deltas arrays have different lengths or if
|
|
296
|
+
* their length is not a multiple of 3.
|
|
297
|
+
*/
|
|
298
|
+
setArrowData(positions, deltas, diameters) {
|
|
299
|
+
if (positions.length !== deltas.length) {
|
|
300
|
+
throw new Error("VectorArrows.setArrowData: positions and deltas arrays must have the same length");
|
|
301
|
+
}
|
|
302
|
+
if (positions.length % 3 !== 0) {
|
|
303
|
+
throw new Error("VectorArrows.setArrowData: positions and deltas arrays length must be a multiple of 3");
|
|
304
|
+
}
|
|
305
|
+
this.positions = positions;
|
|
306
|
+
this.deltas = deltas;
|
|
307
|
+
if (diameters) {
|
|
308
|
+
this.diameter = diameters;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Update instance count, add more instances as needed.
|
|
312
|
+
const count = positions.length / 3;
|
|
313
|
+
const didInstanceCountIncrease = this.headInstancedMesh.count < count;
|
|
314
|
+
if (this.maxInstanceCount < count) {
|
|
315
|
+
this.increaseInstanceCountMax(count);
|
|
316
|
+
}
|
|
317
|
+
this.headInstancedMesh.count = count;
|
|
318
|
+
this.shaftInstancedMesh.count = count;
|
|
319
|
+
this.updateAllArrowTransforms();
|
|
320
|
+
if (didInstanceCountIncrease) {
|
|
321
|
+
// Apply colors to new arrows as needed
|
|
322
|
+
this.applyColors();
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
package/es/View3d.js
CHANGED
|
@@ -852,29 +852,54 @@ export class View3d {
|
|
|
852
852
|
* be in the normalized coordinate space of the Volume, where the origin
|
|
853
853
|
* (0,0,0) is at the center of the Volume and the extent is from -0.5 to 0.5
|
|
854
854
|
* in each axis.
|
|
855
|
+
* @deprecated Will be removed in the next major release. Use `addDrawableObject` instead.
|
|
855
856
|
*/
|
|
856
857
|
addLineObject(line) {
|
|
857
|
-
|
|
858
|
-
this.image.addLineObject(line);
|
|
859
|
-
this.redraw();
|
|
860
|
-
}
|
|
858
|
+
return this.addDrawableObject(line);
|
|
861
859
|
}
|
|
862
860
|
|
|
863
|
-
/**
|
|
861
|
+
/**
|
|
862
|
+
* Returns whether a Line3d object exists as a child of the volume.
|
|
863
|
+
* @deprecated Will be removed in the next major release. Use `hasDrawableObject` instead.
|
|
864
|
+
*/
|
|
864
865
|
hasLineObject(line) {
|
|
865
|
-
|
|
866
|
-
return this.image.hasLineObject(line);
|
|
867
|
-
}
|
|
868
|
-
return false;
|
|
866
|
+
return this.hasDrawableObject(line);
|
|
869
867
|
}
|
|
870
868
|
|
|
871
869
|
/**
|
|
872
870
|
* Removes a Line3d object from the Volume, if it exists. Note that the
|
|
873
871
|
* object's resources are not freed automatically (e.g. via `line.cleanup()`).
|
|
872
|
+
* @deprecated Will be removed in the next major release. Use `removeDrawableObject` instead.
|
|
874
873
|
*/
|
|
875
874
|
removeLineObject(line) {
|
|
875
|
+
return this.removeDrawableObject(line);
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
/**
|
|
879
|
+
* Adds a drawable object as a child of the Volume, if it does not already
|
|
880
|
+
* exist. Objects will be in the normalized coordinate space of the Volume,
|
|
881
|
+
* where the origin (0,0,0) is at the center of the Volume and the extent is
|
|
882
|
+
* from -0.5 to 0.5 in each axis.
|
|
883
|
+
*/
|
|
884
|
+
addDrawableObject(object) {
|
|
885
|
+
if (this.image) {
|
|
886
|
+
this.image.addDrawableObject(object);
|
|
887
|
+
this.redraw();
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
/** Returns whether a drawable object exists as a child of the volume. */
|
|
892
|
+
hasDrawableObject(object) {
|
|
893
|
+
return this.image ? this.image.hasDrawableObject(object) : false;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
/**
|
|
897
|
+
* Removes a drawable object from the Volume, if it exists. Note that the
|
|
898
|
+
* object's resources are not freed automatically (e.g. via `object.cleanup()`).
|
|
899
|
+
*/
|
|
900
|
+
removeDrawableObject(object) {
|
|
876
901
|
if (this.image) {
|
|
877
|
-
this.image.
|
|
902
|
+
this.image.removeDrawableObject(object);
|
|
878
903
|
this.redraw();
|
|
879
904
|
}
|
|
880
905
|
}
|
package/es/VolumeDrawable.js
CHANGED
|
@@ -240,6 +240,8 @@ export default class VolumeDrawable {
|
|
|
240
240
|
const scale = normPhysicalSize.clone().multiply(normRegionSize).multiply(this.settings.scale);
|
|
241
241
|
this.childObjectsGroup.scale.copy(scale);
|
|
242
242
|
this.childObjectsGroup.position.copy(this.volume.getContentCenter().multiply(this.settings.scale));
|
|
243
|
+
this.childObjects.forEach(obj => obj.onParentTransformUpdated?.());
|
|
244
|
+
|
|
243
245
|
// TODO only `RayMarchedAtlasVolume` handles scale properly. Get the others on board too!
|
|
244
246
|
this.volumeRendering.updateVolumeDimensions();
|
|
245
247
|
this.volumeRendering.updateSettings(this.settings, SettingsFlags.TRANSFORM);
|
|
@@ -773,33 +775,31 @@ export default class VolumeDrawable {
|
|
|
773
775
|
}
|
|
774
776
|
|
|
775
777
|
/**
|
|
776
|
-
* Adds a
|
|
777
|
-
* exist.
|
|
778
|
-
*
|
|
779
|
-
*
|
|
778
|
+
* Adds a drawable object as a child of the Volume, if it does not already
|
|
779
|
+
* exist. Objects will be in the normalized coordinate space of the Volume,
|
|
780
|
+
* where the origin (0,0,0) is at the center of the Volume and the extent is
|
|
781
|
+
* from -0.5 to 0.5 in each axis.
|
|
780
782
|
*/
|
|
781
|
-
|
|
782
|
-
if (!this.childObjects.has(
|
|
783
|
-
this.
|
|
784
|
-
this.
|
|
785
|
-
|
|
786
|
-
line.setFlipAxes(this.settings.flipAxes.x, this.settings.flipAxes.y, this.settings.flipAxes.z);
|
|
783
|
+
addDrawableObject(object) {
|
|
784
|
+
if (!this.childObjects.has(object)) {
|
|
785
|
+
this.childObjectsGroup.add(object.get3dObject());
|
|
786
|
+
this.childObjects.add(object);
|
|
787
|
+
this.updateScale();
|
|
787
788
|
}
|
|
788
789
|
}
|
|
789
790
|
|
|
790
|
-
/** Returns whether a
|
|
791
|
-
|
|
792
|
-
return this.childObjects.has(
|
|
791
|
+
/** Returns whether a drawable object exists as a child of the volume. */
|
|
792
|
+
hasDrawableObject(object) {
|
|
793
|
+
return this.childObjects.has(object);
|
|
793
794
|
}
|
|
794
795
|
|
|
795
|
-
/**
|
|
796
|
-
*
|
|
797
|
-
* object's resources are not freed automatically (e.g. via `line.cleanup()`).
|
|
796
|
+
/** Removes a drawable object from the Volume, if it exists. Note that the
|
|
797
|
+
* object's resources are not freed automatically (e.g. via `object.cleanup()`).
|
|
798
798
|
*/
|
|
799
|
-
|
|
800
|
-
if (this.childObjects.has(
|
|
801
|
-
this.childObjects.delete(
|
|
802
|
-
this.childObjectsGroup.remove(
|
|
799
|
+
removeDrawableObject(object) {
|
|
800
|
+
if (this.childObjects.has(object)) {
|
|
801
|
+
this.childObjects.delete(object);
|
|
802
|
+
this.childObjectsGroup.remove(object.get3dObject());
|
|
803
803
|
}
|
|
804
804
|
}
|
|
805
805
|
setupGui(pane) {
|
package/es/index.js
CHANGED
|
@@ -19,4 +19,5 @@ import VolumeLoaderContext from "./workers/VolumeLoaderContext.js";
|
|
|
19
19
|
import { VolumeLoadError, VolumeLoadErrorType } from "./loaders/VolumeLoadError.js";
|
|
20
20
|
import { Light, AREA_LIGHT, SKY_LIGHT } from "./Light.js";
|
|
21
21
|
import Line3d from "./Line3d.js";
|
|
22
|
-
|
|
22
|
+
import VectorArrows3d from "./VectorArrows3d.js";
|
|
23
|
+
export { Histogram, Lut, Line3d, VectorArrows3d, remapControlPoints, View3d, Volume, VolumeDrawable, LoadSpec, VolumeMaker, VolumeCache, RequestQueue, SubscribableRequestQueue, PrefetchDirection, OMEZarrLoader, JsonImageInfoLoader, RawArrayLoader, TiffLoader, VolumeLoaderContext, VolumeLoadError, VolumeLoadErrorType, VolumeFileFormat, createVolumeLoader, Channel, Light, ViewportCorner, AREA_LIGHT, RENDERMODE_PATHTRACE, RENDERMODE_RAYMARCH, SKY_LIGHT };
|
|
@@ -78,6 +78,7 @@ export function remapAxesToTCZYX(axes) {
|
|
|
78
78
|
export function orderByDimension(valsTCZYX, orderTCZYX) {
|
|
79
79
|
const specLen = getDimensionCount(orderTCZYX);
|
|
80
80
|
const result = Array(specLen);
|
|
81
|
+
let curIdx = 0;
|
|
81
82
|
orderTCZYX.forEach((val, idx) => {
|
|
82
83
|
if (val >= 0) {
|
|
83
84
|
if (val >= specLen) {
|
|
@@ -85,7 +86,7 @@ export function orderByDimension(valsTCZYX, orderTCZYX) {
|
|
|
85
86
|
type: VolumeLoadErrorType.INVALID_METADATA
|
|
86
87
|
});
|
|
87
88
|
}
|
|
88
|
-
result[
|
|
89
|
+
result[curIdx++] = valsTCZYX[idx];
|
|
89
90
|
}
|
|
90
91
|
});
|
|
91
92
|
return result;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Euler, Group, Mesh, Vector3 } from "three";
|
|
2
|
+
import { IDrawableObject } from "./types";
|
|
3
|
+
/**
|
|
4
|
+
* Abstract base class for drawable 3D mesh objects.
|
|
5
|
+
*
|
|
6
|
+
* Provides default implementations for some methods in the IDrawableObject
|
|
7
|
+
* interface, including handling for visibility, transformations, and cleanup.
|
|
8
|
+
*
|
|
9
|
+
* As a default, subclasses should call `this.addChildMesh(mesh)` to register
|
|
10
|
+
* any meshes that should be managed by the base class.
|
|
11
|
+
*/
|
|
12
|
+
export default abstract class BaseDrawableMeshObject implements IDrawableObject {
|
|
13
|
+
/**
|
|
14
|
+
* Pivot group that all child meshes are parented to. Transformations are
|
|
15
|
+
* applied to this group.
|
|
16
|
+
*/
|
|
17
|
+
protected readonly meshPivot: Group;
|
|
18
|
+
protected scale: Vector3;
|
|
19
|
+
protected flipAxes: Vector3;
|
|
20
|
+
constructor();
|
|
21
|
+
protected addChildMesh(mesh: Mesh): void;
|
|
22
|
+
protected removeChildMesh(mesh: Mesh): void;
|
|
23
|
+
cleanup(): void;
|
|
24
|
+
doRender(): void;
|
|
25
|
+
setVisible(visible: boolean): void;
|
|
26
|
+
get3dObject(): Group;
|
|
27
|
+
setTranslation(translation: Vector3): void;
|
|
28
|
+
setScale(scale: Vector3): void;
|
|
29
|
+
setRotation(eulerXYZ: Euler): void;
|
|
30
|
+
setFlipAxes(flipX: number, flipY: number, flipZ: number): void;
|
|
31
|
+
setOrthoThickness(_thickness: number): void;
|
|
32
|
+
setResolution(_x: number, _y: number): void;
|
|
33
|
+
setAxisClip(_axis: "x" | "y" | "z", _minval: number, _maxval: number, _isOrthoAxis: boolean): void;
|
|
34
|
+
updateClipRegion(_xmin: number, _xmax: number, _ymin: number, _ymax: number, _zmin: number, _zmax: number): void;
|
|
35
|
+
}
|
package/es/types/Line3d.d.ts
CHANGED
|
@@ -1,28 +1,14 @@
|
|
|
1
|
-
import { Color
|
|
1
|
+
import { Color } from "three";
|
|
2
2
|
import { IDrawableObject } from "./types.js";
|
|
3
|
+
import BaseDrawableMeshObject from "./BaseDrawableMeshObject.js";
|
|
3
4
|
/**
|
|
4
5
|
* Simple wrapper for a 3D line segments object, with controls for vertex data,
|
|
5
6
|
* color, width, and segments visible.
|
|
6
7
|
*/
|
|
7
|
-
export default class Line3d implements IDrawableObject {
|
|
8
|
-
private meshPivot;
|
|
9
|
-
private scale;
|
|
10
|
-
private flipAxes;
|
|
8
|
+
export default class Line3d extends BaseDrawableMeshObject implements IDrawableObject {
|
|
11
9
|
private lineMesh;
|
|
12
10
|
private bufferSize;
|
|
13
11
|
constructor();
|
|
14
|
-
cleanup(): void;
|
|
15
|
-
setVisible(visible: boolean): void;
|
|
16
|
-
doRender(): void;
|
|
17
|
-
get3dObject(): Group;
|
|
18
|
-
setTranslation(translation: Vector3): void;
|
|
19
|
-
setScale(scale: Vector3): void;
|
|
20
|
-
setRotation(eulerXYZ: Euler): void;
|
|
21
|
-
setFlipAxes(flipX: number, flipY: number, flipZ: number): void;
|
|
22
|
-
setOrthoThickness(_thickness: number): void;
|
|
23
|
-
setResolution(_x: number, _y: number): void;
|
|
24
|
-
setAxisClip(_axis: "x" | "y" | "z", _minval: number, _maxval: number, _isOrthoAxis: boolean): void;
|
|
25
|
-
updateClipRegion(_xmin: number, _xmax: number, _ymin: number, _ymax: number, _zmin: number, _zmax: number): void;
|
|
26
12
|
/**
|
|
27
13
|
* Sets the color of the line material.
|
|
28
14
|
* @param color Base line color.
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { Vector3, Color } from "three";
|
|
2
|
+
import { IDrawableObject } from "./types";
|
|
3
|
+
import BaseDrawableMeshObject from "./BaseDrawableMeshObject";
|
|
4
|
+
/**
|
|
5
|
+
* A drawable vector arrow field, which uses instanced meshes for performance.
|
|
6
|
+
*/
|
|
7
|
+
export default class VectorArrows3d extends BaseDrawableMeshObject implements IDrawableObject {
|
|
8
|
+
/**
|
|
9
|
+
* Scale of this object in world coordinates, when unscaled. Used to
|
|
10
|
+
* compensate for parent transforms in order to keep arrow meshes from being
|
|
11
|
+
* distorted.
|
|
12
|
+
*/
|
|
13
|
+
protected worldScale: Vector3;
|
|
14
|
+
private headInstancedMesh;
|
|
15
|
+
private shaftInstancedMesh;
|
|
16
|
+
private maxInstanceCount;
|
|
17
|
+
private positions;
|
|
18
|
+
private deltas;
|
|
19
|
+
private colors;
|
|
20
|
+
private diameter;
|
|
21
|
+
private tempDst;
|
|
22
|
+
private tempScale;
|
|
23
|
+
private tempMatrix;
|
|
24
|
+
constructor();
|
|
25
|
+
/**
|
|
26
|
+
* Returns (unscaled) buffer geometry for the head and shaft parts of the
|
|
27
|
+
* arrow.
|
|
28
|
+
* @returns
|
|
29
|
+
* - `head`: BufferGeometry for the arrowhead, a cone pointing along the +Z
|
|
30
|
+
* axis, with the pivot at the tip of the cone. Height and radius are based
|
|
31
|
+
* on constant values (`HEAD_BASE_HEIGHT` and `HEAD_BASE_RADIUS`).
|
|
32
|
+
* - `shaft`: BufferGeometry for the cylindrical arrow shaft. The cylinder
|
|
33
|
+
* points along the +Z axis, with the pivot at the base of the cylinder.
|
|
34
|
+
* Height is 1 and diameter is 1.
|
|
35
|
+
*
|
|
36
|
+
* ```txt
|
|
37
|
+
* ^ +Z axis ^
|
|
38
|
+
* _____ x
|
|
39
|
+
* | | / \
|
|
40
|
+
* | | / \
|
|
41
|
+
* --x-- /-----\
|
|
42
|
+
*
|
|
43
|
+
* x = pivot (0,0,0)
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
private static generateGeometry;
|
|
47
|
+
/**
|
|
48
|
+
* Create new instanced meshes with the specified instance count, and adds
|
|
49
|
+
* them to the mesh pivot and internal meshes array for future cleanup.
|
|
50
|
+
*
|
|
51
|
+
* If calling outside of the constructor, be sure to call `cleanup()` first.
|
|
52
|
+
*/
|
|
53
|
+
private initInstancedMeshes;
|
|
54
|
+
private increaseInstanceCountMax;
|
|
55
|
+
setScale(scale: Vector3): void;
|
|
56
|
+
/**
|
|
57
|
+
* Called when scaling of parent transforms has been updated or whenever
|
|
58
|
+
* vector data is updated.
|
|
59
|
+
*/
|
|
60
|
+
onParentTransformUpdated(): void;
|
|
61
|
+
private updateSingleArrowTransform;
|
|
62
|
+
private updateAllArrowTransforms;
|
|
63
|
+
private applyColors;
|
|
64
|
+
/**
|
|
65
|
+
* Sets the colors for the arrows as either a single Color or an array of RGB values.
|
|
66
|
+
* If there are more arrows than colors, colors will be repeated in order.
|
|
67
|
+
* @param colors Color object or numeric array of RGB values in the [0, 1] range.
|
|
68
|
+
* @throws {Error} If colors array length is not a multiple of 3.
|
|
69
|
+
*/
|
|
70
|
+
setColors(colors: Float32Array | Color): void;
|
|
71
|
+
/**
|
|
72
|
+
* Sets all arrows to a uniform diameter (default is `0.002`). To set
|
|
73
|
+
* per-arrow diameter, pass an array of values into `setArrowData` instead.
|
|
74
|
+
* @param diameter Diameter value to set for all arrows.
|
|
75
|
+
*/
|
|
76
|
+
setDiameter(diameter: number): void;
|
|
77
|
+
/**
|
|
78
|
+
* Sets the per-arrow data. The number of rendered arrows is equal to
|
|
79
|
+
* `positions.length / 3`.
|
|
80
|
+
* @param positions Float32Array, where every three values is the XYZ position
|
|
81
|
+
* of the base of an arrow.
|
|
82
|
+
* @param deltas Float32Array, where every three values is the XYZ delta
|
|
83
|
+
* vector for each arrow.
|
|
84
|
+
* @param diameters Optional Float32Array of diameter thickness values for
|
|
85
|
+
* each arrow's shaft. If provided, overrides a single diameter value set by
|
|
86
|
+
* `setDiameter`. If fewer diameter values are provided than arrows, the
|
|
87
|
+
* values will be repeated in order.
|
|
88
|
+
* @throws {Error} If positions and deltas arrays have different lengths or if
|
|
89
|
+
* their length is not a multiple of 3.
|
|
90
|
+
*/
|
|
91
|
+
setArrowData(positions: Float32Array, deltas: Float32Array, diameters?: Float32Array): void;
|
|
92
|
+
}
|
package/es/types/View3d.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { CameraState } from "./ThreeJsPanel.js";
|
|
|
3
3
|
import VolumeDrawable from "./VolumeDrawable.js";
|
|
4
4
|
import { Light } from "./Light.js";
|
|
5
5
|
import Volume from "./Volume.js";
|
|
6
|
-
import { type ColorizeFeature, type VolumeChannelDisplayOptions, type VolumeDisplayOptions, ViewportCorner, RenderMode } from "./types.js";
|
|
6
|
+
import { type ColorizeFeature, type VolumeChannelDisplayOptions, type VolumeDisplayOptions, ViewportCorner, RenderMode, IDrawableObject } from "./types.js";
|
|
7
7
|
import { PerChannelCallback } from "./loaders/IVolumeLoader.js";
|
|
8
8
|
import Line3d from "./Line3d.js";
|
|
9
9
|
export declare const RENDERMODE_RAYMARCH = RenderMode.RAYMARCH;
|
|
@@ -389,15 +389,34 @@ export declare class View3d {
|
|
|
389
389
|
* be in the normalized coordinate space of the Volume, where the origin
|
|
390
390
|
* (0,0,0) is at the center of the Volume and the extent is from -0.5 to 0.5
|
|
391
391
|
* in each axis.
|
|
392
|
+
* @deprecated Will be removed in the next major release. Use `addDrawableObject` instead.
|
|
392
393
|
*/
|
|
393
394
|
addLineObject(line: Line3d): void;
|
|
394
|
-
/**
|
|
395
|
+
/**
|
|
396
|
+
* Returns whether a Line3d object exists as a child of the volume.
|
|
397
|
+
* @deprecated Will be removed in the next major release. Use `hasDrawableObject` instead.
|
|
398
|
+
*/
|
|
395
399
|
hasLineObject(line: Line3d): boolean;
|
|
396
400
|
/**
|
|
397
401
|
* Removes a Line3d object from the Volume, if it exists. Note that the
|
|
398
402
|
* object's resources are not freed automatically (e.g. via `line.cleanup()`).
|
|
403
|
+
* @deprecated Will be removed in the next major release. Use `removeDrawableObject` instead.
|
|
399
404
|
*/
|
|
400
405
|
removeLineObject(line: Line3d): void;
|
|
406
|
+
/**
|
|
407
|
+
* Adds a drawable object as a child of the Volume, if it does not already
|
|
408
|
+
* exist. Objects will be in the normalized coordinate space of the Volume,
|
|
409
|
+
* where the origin (0,0,0) is at the center of the Volume and the extent is
|
|
410
|
+
* from -0.5 to 0.5 in each axis.
|
|
411
|
+
*/
|
|
412
|
+
addDrawableObject(object: IDrawableObject): void;
|
|
413
|
+
/** Returns whether a drawable object exists as a child of the volume. */
|
|
414
|
+
hasDrawableObject(object: IDrawableObject): boolean;
|
|
415
|
+
/**
|
|
416
|
+
* Removes a drawable object from the Volume, if it exists. Note that the
|
|
417
|
+
* object's resources are not freed automatically (e.g. via `object.cleanup()`).
|
|
418
|
+
*/
|
|
419
|
+
removeDrawableObject(object: IDrawableObject): void;
|
|
401
420
|
/**
|
|
402
421
|
* @description Enable or disable picking on a volume. If enabled, the channelIndex is used to determine which channel to pick.
|
|
403
422
|
* @param volume the image to enable picking on
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { Vector3, Object3D, Euler, DepthTexture, OrthographicCamera, PerspectiveCamera, WebGLRenderer, WebGLRenderTarget, Texture } from "three";
|
|
2
2
|
import { Pane } from "tweakpane";
|
|
3
3
|
import Volume from "./Volume.js";
|
|
4
|
-
import type { VolumeDisplayOptions, VolumeChannelDisplayOptions, ColorizeFeature } from "./types.js";
|
|
4
|
+
import type { VolumeDisplayOptions, VolumeChannelDisplayOptions, ColorizeFeature, IDrawableObject } from "./types.js";
|
|
5
5
|
import { RenderMode } from "./types.js";
|
|
6
6
|
import { Light } from "./Light.js";
|
|
7
7
|
import Channel from "./Channel.js";
|
|
8
8
|
import { Axis } from "./VolumeRenderSettings.js";
|
|
9
|
-
import Line3d from "./Line3d.js";
|
|
10
9
|
type ColorArray = [number, number, number];
|
|
11
10
|
type ColorObject = {
|
|
12
11
|
r: number;
|
|
@@ -120,19 +119,18 @@ export default class VolumeDrawable {
|
|
|
120
119
|
setRotation(eulerXYZ: Euler): void;
|
|
121
120
|
setScale(xyz: Vector3): void;
|
|
122
121
|
/**
|
|
123
|
-
* Adds a
|
|
124
|
-
* exist.
|
|
125
|
-
*
|
|
126
|
-
*
|
|
122
|
+
* Adds a drawable object as a child of the Volume, if it does not already
|
|
123
|
+
* exist. Objects will be in the normalized coordinate space of the Volume,
|
|
124
|
+
* where the origin (0,0,0) is at the center of the Volume and the extent is
|
|
125
|
+
* from -0.5 to 0.5 in each axis.
|
|
127
126
|
*/
|
|
128
|
-
|
|
129
|
-
/** Returns whether a
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
*
|
|
133
|
-
* object's resources are not freed automatically (e.g. via `line.cleanup()`).
|
|
127
|
+
addDrawableObject(object: IDrawableObject): void;
|
|
128
|
+
/** Returns whether a drawable object exists as a child of the volume. */
|
|
129
|
+
hasDrawableObject(object: IDrawableObject): boolean;
|
|
130
|
+
/** Removes a drawable object from the Volume, if it exists. Note that the
|
|
131
|
+
* object's resources are not freed automatically (e.g. via `object.cleanup()`).
|
|
134
132
|
*/
|
|
135
|
-
|
|
133
|
+
removeDrawableObject(object: IDrawableObject): void;
|
|
136
134
|
setupGui(pane: Pane): void;
|
|
137
135
|
setZSlice(slice: number): boolean;
|
|
138
136
|
get showBoundingBox(): boolean;
|
package/es/types/index.d.ts
CHANGED
|
@@ -20,10 +20,11 @@ import { VolumeLoadError, VolumeLoadErrorType } from "./loaders/VolumeLoadError.
|
|
|
20
20
|
import { type CameraState } from "./ThreeJsPanel.js";
|
|
21
21
|
import { Light, AREA_LIGHT, SKY_LIGHT } from "./Light.js";
|
|
22
22
|
import Line3d from "./Line3d.js";
|
|
23
|
+
import VectorArrows3d from "./VectorArrows3d.js";
|
|
23
24
|
export type { ImageInfo } from "./ImageInfo.js";
|
|
24
25
|
export type { ControlPoint } from "./Lut.js";
|
|
25
26
|
export type { CreateLoaderOptions } from "./loaders/index.js";
|
|
26
27
|
export type { IVolumeLoader, PerChannelCallback, ThreadableVolumeLoader } from "./loaders/IVolumeLoader.js";
|
|
27
28
|
export type { ZarrLoaderFetchOptions } from "./loaders/OmeZarrLoader.js";
|
|
28
29
|
export type { WorkerLoader } from "./workers/VolumeLoaderContext.js";
|
|
29
|
-
export { Histogram, Lut, Line3d, remapControlPoints, View3d, Volume, VolumeDrawable, LoadSpec, VolumeMaker, VolumeCache, RequestQueue, SubscribableRequestQueue, PrefetchDirection, OMEZarrLoader, JsonImageInfoLoader, RawArrayLoader, type RawArrayData, type RawArrayInfo, type RawArrayLoaderOptions, TiffLoader, VolumeLoaderContext, VolumeLoadError, VolumeLoadErrorType, VolumeFileFormat, createVolumeLoader, Channel, Light, ViewportCorner, AREA_LIGHT, RENDERMODE_PATHTRACE, RENDERMODE_RAYMARCH, SKY_LIGHT, type CameraState, type ColorizeFeature, type NumberType, };
|
|
30
|
+
export { Histogram, Lut, Line3d, VectorArrows3d, remapControlPoints, View3d, Volume, VolumeDrawable, LoadSpec, VolumeMaker, VolumeCache, RequestQueue, SubscribableRequestQueue, PrefetchDirection, OMEZarrLoader, JsonImageInfoLoader, RawArrayLoader, type RawArrayData, type RawArrayInfo, type RawArrayLoaderOptions, TiffLoader, VolumeLoaderContext, VolumeLoadError, VolumeLoadErrorType, VolumeFileFormat, createVolumeLoader, Channel, Light, ViewportCorner, AREA_LIGHT, RENDERMODE_PATHTRACE, RENDERMODE_RAYMARCH, SKY_LIGHT, type CameraState, type ColorizeFeature, type NumberType, };
|
package/es/types/types.d.ts
CHANGED
|
@@ -80,6 +80,10 @@ export interface IDrawableObject {
|
|
|
80
80
|
get3dObject(): Group;
|
|
81
81
|
setTranslation(translation: Vector3): void;
|
|
82
82
|
setScale(scale: Vector3): void;
|
|
83
|
+
/**
|
|
84
|
+
* Optional. Should be called when parent transforms are updated.
|
|
85
|
+
*/
|
|
86
|
+
onParentTransformUpdated?(): void;
|
|
83
87
|
setRotation(eulerXYZ: Euler): void;
|
|
84
88
|
setFlipAxes(flipX: number, flipY: number, flipZ: number): void;
|
|
85
89
|
setOrthoThickness(thickness: number): void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aics/vole-core",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.4.0",
|
|
4
4
|
"description": "volume renderer for 3d, 4d, or 5d imaging data with OME-Zarr support",
|
|
5
5
|
"main": "es/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"build": "npm run transpileES && npm run build-types",
|
|
18
18
|
"build-types": "tsc -p tsconfig.types.json",
|
|
19
19
|
"build-demo": "vite build public/ --config vite.config.ts --outDir ./demo",
|
|
20
|
+
"checks": "npm run lint & npm run typeCheck & npx vitest run",
|
|
20
21
|
"clean": "rimraf es/",
|
|
21
22
|
"format": "prettier --write src/**/*.ts",
|
|
22
23
|
"gh-build": "vite build public/ --config vite.config.ts --outDir ./vole-core",
|