@aics/vole-core 3.12.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. package/LICENSE.txt +26 -0
  2. package/README.md +119 -0
  3. package/es/Atlas2DSlice.js +224 -0
  4. package/es/Channel.js +264 -0
  5. package/es/FileSaver.js +31 -0
  6. package/es/FusedChannelData.js +192 -0
  7. package/es/Histogram.js +250 -0
  8. package/es/ImageInfo.js +127 -0
  9. package/es/Light.js +74 -0
  10. package/es/Lut.js +500 -0
  11. package/es/MarchingCubes.js +507 -0
  12. package/es/MeshVolume.js +334 -0
  13. package/es/NaiveSurfaceNets.js +251 -0
  14. package/es/PathTracedVolume.js +482 -0
  15. package/es/RayMarchedAtlasVolume.js +250 -0
  16. package/es/RenderToBuffer.js +31 -0
  17. package/es/ThreeJsPanel.js +633 -0
  18. package/es/Timing.js +28 -0
  19. package/es/TrackballControls.js +538 -0
  20. package/es/View3d.js +848 -0
  21. package/es/Volume.js +352 -0
  22. package/es/VolumeCache.js +161 -0
  23. package/es/VolumeDims.js +16 -0
  24. package/es/VolumeDrawable.js +702 -0
  25. package/es/VolumeMaker.js +101 -0
  26. package/es/VolumeRenderImpl.js +1 -0
  27. package/es/VolumeRenderSettings.js +203 -0
  28. package/es/constants/basicShaders.js +29 -0
  29. package/es/constants/colors.js +59 -0
  30. package/es/constants/denoiseShader.js +43 -0
  31. package/es/constants/lights.js +42 -0
  32. package/es/constants/materials.js +85 -0
  33. package/es/constants/pathtraceOutputShader.js +13 -0
  34. package/es/constants/scaleBarSVG.js +21 -0
  35. package/es/constants/time.js +34 -0
  36. package/es/constants/volumePTshader.js +153 -0
  37. package/es/constants/volumeRayMarchShader.js +123 -0
  38. package/es/constants/volumeSliceShader.js +115 -0
  39. package/es/index.js +21 -0
  40. package/es/loaders/IVolumeLoader.js +131 -0
  41. package/es/loaders/JsonImageInfoLoader.js +255 -0
  42. package/es/loaders/OmeZarrLoader.js +495 -0
  43. package/es/loaders/OpenCellLoader.js +65 -0
  44. package/es/loaders/RawArrayLoader.js +89 -0
  45. package/es/loaders/TiffLoader.js +219 -0
  46. package/es/loaders/VolumeLoadError.js +44 -0
  47. package/es/loaders/VolumeLoaderUtils.js +221 -0
  48. package/es/loaders/index.js +40 -0
  49. package/es/loaders/zarr_utils/ChunkPrefetchIterator.js +143 -0
  50. package/es/loaders/zarr_utils/WrappedStore.js +51 -0
  51. package/es/loaders/zarr_utils/types.js +24 -0
  52. package/es/loaders/zarr_utils/utils.js +225 -0
  53. package/es/loaders/zarr_utils/validation.js +49 -0
  54. package/es/test/ChunkPrefetchIterator.test.js +208 -0
  55. package/es/test/RequestQueue.test.js +442 -0
  56. package/es/test/SubscribableRequestQueue.test.js +244 -0
  57. package/es/test/VolumeCache.test.js +118 -0
  58. package/es/test/VolumeRenderSettings.test.js +71 -0
  59. package/es/test/lut.test.js +671 -0
  60. package/es/test/num_utils.test.js +140 -0
  61. package/es/test/volume.test.js +98 -0
  62. package/es/test/zarr_utils.test.js +358 -0
  63. package/es/types/Atlas2DSlice.d.ts +41 -0
  64. package/es/types/Channel.d.ts +44 -0
  65. package/es/types/FileSaver.d.ts +6 -0
  66. package/es/types/FusedChannelData.d.ts +26 -0
  67. package/es/types/Histogram.d.ts +57 -0
  68. package/es/types/ImageInfo.d.ts +87 -0
  69. package/es/types/Light.d.ts +27 -0
  70. package/es/types/Lut.d.ts +67 -0
  71. package/es/types/MarchingCubes.d.ts +53 -0
  72. package/es/types/MeshVolume.d.ts +40 -0
  73. package/es/types/NaiveSurfaceNets.d.ts +11 -0
  74. package/es/types/PathTracedVolume.d.ts +65 -0
  75. package/es/types/RayMarchedAtlasVolume.d.ts +41 -0
  76. package/es/types/RenderToBuffer.d.ts +17 -0
  77. package/es/types/ThreeJsPanel.d.ts +107 -0
  78. package/es/types/Timing.d.ts +11 -0
  79. package/es/types/TrackballControls.d.ts +51 -0
  80. package/es/types/View3d.d.ts +357 -0
  81. package/es/types/Volume.d.ts +152 -0
  82. package/es/types/VolumeCache.d.ts +43 -0
  83. package/es/types/VolumeDims.d.ts +28 -0
  84. package/es/types/VolumeDrawable.d.ts +108 -0
  85. package/es/types/VolumeMaker.d.ts +49 -0
  86. package/es/types/VolumeRenderImpl.d.ts +22 -0
  87. package/es/types/VolumeRenderSettings.d.ts +98 -0
  88. package/es/types/constants/basicShaders.d.ts +4 -0
  89. package/es/types/constants/colors.d.ts +2 -0
  90. package/es/types/constants/denoiseShader.d.ts +40 -0
  91. package/es/types/constants/lights.d.ts +38 -0
  92. package/es/types/constants/materials.d.ts +20 -0
  93. package/es/types/constants/pathtraceOutputShader.d.ts +11 -0
  94. package/es/types/constants/scaleBarSVG.d.ts +2 -0
  95. package/es/types/constants/time.d.ts +19 -0
  96. package/es/types/constants/volumePTshader.d.ts +137 -0
  97. package/es/types/constants/volumeRayMarchShader.d.ts +117 -0
  98. package/es/types/constants/volumeSliceShader.d.ts +109 -0
  99. package/es/types/glsl.d.js +0 -0
  100. package/es/types/index.d.ts +28 -0
  101. package/es/types/loaders/IVolumeLoader.d.ts +113 -0
  102. package/es/types/loaders/JsonImageInfoLoader.d.ts +80 -0
  103. package/es/types/loaders/OmeZarrLoader.d.ts +87 -0
  104. package/es/types/loaders/OpenCellLoader.d.ts +9 -0
  105. package/es/types/loaders/RawArrayLoader.d.ts +33 -0
  106. package/es/types/loaders/TiffLoader.d.ts +45 -0
  107. package/es/types/loaders/VolumeLoadError.d.ts +18 -0
  108. package/es/types/loaders/VolumeLoaderUtils.d.ts +38 -0
  109. package/es/types/loaders/index.d.ts +22 -0
  110. package/es/types/loaders/zarr_utils/ChunkPrefetchIterator.d.ts +22 -0
  111. package/es/types/loaders/zarr_utils/WrappedStore.d.ts +24 -0
  112. package/es/types/loaders/zarr_utils/types.d.ts +94 -0
  113. package/es/types/loaders/zarr_utils/utils.d.ts +23 -0
  114. package/es/types/loaders/zarr_utils/validation.d.ts +7 -0
  115. package/es/types/test/ChunkPrefetchIterator.test.d.ts +1 -0
  116. package/es/types/test/RequestQueue.test.d.ts +1 -0
  117. package/es/types/test/SubscribableRequestQueue.test.d.ts +1 -0
  118. package/es/types/test/VolumeCache.test.d.ts +1 -0
  119. package/es/types/test/VolumeRenderSettings.test.d.ts +1 -0
  120. package/es/types/test/lut.test.d.ts +1 -0
  121. package/es/types/test/num_utils.test.d.ts +1 -0
  122. package/es/types/test/volume.test.d.ts +1 -0
  123. package/es/types/test/zarr_utils.test.d.ts +1 -0
  124. package/es/types/types.d.ts +115 -0
  125. package/es/types/utils/RequestQueue.d.ts +112 -0
  126. package/es/types/utils/SubscribableRequestQueue.d.ts +52 -0
  127. package/es/types/utils/num_utils.d.ts +43 -0
  128. package/es/types/workers/VolumeLoaderContext.d.ts +106 -0
  129. package/es/types/workers/types.d.ts +101 -0
  130. package/es/types/workers/util.d.ts +3 -0
  131. package/es/types.js +75 -0
  132. package/es/typings.d.js +0 -0
  133. package/es/utils/RequestQueue.js +267 -0
  134. package/es/utils/SubscribableRequestQueue.js +187 -0
  135. package/es/utils/num_utils.js +231 -0
  136. package/es/workers/FetchTiffWorker.js +153 -0
  137. package/es/workers/VolumeLoadWorker.js +129 -0
  138. package/es/workers/VolumeLoaderContext.js +271 -0
  139. package/es/workers/types.js +41 -0
  140. package/es/workers/util.js +8 -0
  141. package/package.json +83 -0
@@ -0,0 +1,334 @@
1
+ import { Object3D, Vector3, Color, Mesh, Group, Material, MeshPhongMaterial, Plane, DoubleSide } from "three";
2
+ import { STLExporter } from "three/examples/jsm/exporters/STLExporter.js";
3
+ import { GLTFExporter } from "three/examples/jsm/exporters/GLTFExporter.js";
4
+ import { defaultMaterialSettings } from "./constants/materials.js";
5
+ import FileSaver from "./FileSaver.js";
6
+ import NaiveSurfaceNets from "./NaiveSurfaceNets.js";
7
+ import MarchingCubes from "./MarchingCubes.js";
8
+ import { MESH_LAYER } from "./ThreeJsPanel.js";
9
+
10
+ /**
11
+ * this cutoff is chosen to have a small buffer of values before the object is treated
12
+ * as transparent for gpu blending and depth testing.
13
+ */
14
+ const ALPHA_THRESHOLD = 0.95;
15
+ export default class MeshVolume {
16
+ constructor(volume) {
17
+ // need?
18
+ this.volume = volume;
19
+ this.meshRoot = new Object3D(); //create an empty container
20
+ this.meshRoot.name = "Mesh Surface Group";
21
+
22
+ // handle transform ordering for giving the meshroot a rotation about a pivot point
23
+ this.meshPivot = new Group();
24
+ this.meshPivot.name = "MeshContainerNode";
25
+ this.meshPivot.add(this.meshRoot);
26
+ this.meshrep = [];
27
+ this.channelColors = [];
28
+ this.channelOpacities = [];
29
+ this.scale = new Vector3(1, 1, 1);
30
+ this.bounds = {
31
+ bmin: new Vector3(-0.5, -0.5, -0.5),
32
+ bmax: new Vector3(0.5, 0.5, 0.5)
33
+ };
34
+ }
35
+ cleanup() {
36
+ for (let i = 0; i < this.volume.imageInfo.numChannels; ++i) {
37
+ this.destroyIsosurface(i);
38
+ }
39
+ }
40
+ setVisible(isVisible) {
41
+ this.meshRoot.visible = isVisible;
42
+ }
43
+ doRender() {
44
+ // no op
45
+ }
46
+ get3dObject() {
47
+ return this.meshPivot;
48
+ }
49
+ setScale(scale, position = new Vector3(0, 0, 0)) {
50
+ this.scale = scale;
51
+ this.meshRoot.scale.copy(scale).multiplyScalar(0.5);
52
+ this.meshRoot.position.copy(position);
53
+ }
54
+ setFlipAxes(flipX, flipY, flipZ) {
55
+ this.meshRoot.scale.copy(new Vector3(0.5 * this.scale.x * flipX, 0.5 * this.scale.y * flipY, 0.5 * this.scale.z * flipZ));
56
+ }
57
+ setTranslation(vec3xyz) {
58
+ this.meshPivot.position.copy(vec3xyz);
59
+ }
60
+ setRotation(eulerXYZ) {
61
+ this.meshPivot.rotation.copy(eulerXYZ);
62
+ this.updateClipFromBounds();
63
+ }
64
+ setResolution(_x, _y) {
65
+ // no op
66
+ }
67
+ setOrthoThickness(_value) {
68
+ // no op
69
+ }
70
+ setAxisClip(axis, minval, maxval, _isOrthoAxis) {
71
+ this.bounds.bmax[axis] = maxval;
72
+ this.bounds.bmin[axis] = minval;
73
+ this.updateClipFromBounds();
74
+ }
75
+
76
+ //////////////////////////////
77
+
78
+ updateMeshColors(channelColors) {
79
+ // stash values here for later changes
80
+ this.channelColors = channelColors;
81
+
82
+ // update existing meshes
83
+ for (let i = 0; i < this.volume.imageInfo.numChannels; ++i) {
84
+ const meshrep = this.meshrep[i];
85
+ if (meshrep) {
86
+ const rgb = channelColors[i];
87
+ const c = rgb[0] << 16 | rgb[1] << 8 | rgb[2];
88
+ meshrep.traverse(function (child) {
89
+ if (child instanceof Mesh) {
90
+ child.material.color = new Color(c);
91
+ }
92
+ });
93
+ }
94
+ }
95
+ }
96
+ createMaterialForChannel(rgb, alpha, _transp) {
97
+ const col = rgb[0] << 16 | rgb[1] << 8 | rgb[2];
98
+ const material = new MeshPhongMaterial({
99
+ color: new Color(col),
100
+ shininess: defaultMaterialSettings.shininess,
101
+ specular: new Color(defaultMaterialSettings.specularColor),
102
+ opacity: alpha,
103
+ transparent: alpha < ALPHA_THRESHOLD,
104
+ side: DoubleSide
105
+ });
106
+ return material;
107
+ }
108
+ createMeshForChannel(channelIndex, colorrgb, isovalue, alpha, transp) {
109
+ // note that if isovalue out of range, this will return an empty array.
110
+ const geometries = this.generateIsosurfaceGeometry(channelIndex, isovalue);
111
+ const material = this.createMaterialForChannel(colorrgb, alpha, transp);
112
+ const theObject = new Group();
113
+ theObject.name = "Channel" + channelIndex;
114
+ theObject.userData = {
115
+ isovalue: isovalue
116
+ };
117
+ // proper scaling will be done in parent object
118
+ for (let i = 0; i < geometries.length; ++i) {
119
+ const mesh = new Mesh(geometries[i], material);
120
+ mesh.layers.set(MESH_LAYER);
121
+ theObject.add(mesh);
122
+ }
123
+ return theObject;
124
+ }
125
+ updateIsovalue(channel, value) {
126
+ const meshrep = this.meshrep[channel];
127
+ if (!meshrep) {
128
+ return;
129
+ }
130
+ if (meshrep.userData.isovalue === value) {
131
+ return;
132
+ }
133
+
134
+ // find the current isosurface opacity and color.
135
+ const opacity = this.channelOpacities[channel];
136
+ const color = this.channelColors[channel];
137
+ this.destroyIsosurface(channel);
138
+ const newmeshrep = this.createMeshForChannel(channel, color, value, opacity, false);
139
+ this.meshrep[channel] = newmeshrep;
140
+ this.meshRoot.add(newmeshrep);
141
+ }
142
+ getIsovalue(channel) {
143
+ const meshrep = this.meshrep[channel];
144
+ return meshrep?.userData.isovalue;
145
+ }
146
+ getOpacity(channel) {
147
+ const meshrep = this.meshrep[channel];
148
+ let opacity = undefined;
149
+ meshrep?.traverse(obj => {
150
+ if (obj instanceof Mesh) {
151
+ opacity = obj.material.opacity;
152
+ }
153
+ });
154
+ return opacity;
155
+ }
156
+ updateClipRegion(xmin, xmax, ymin, ymax, zmin, zmax) {
157
+ // incoming values expected to be between 0 and 1.
158
+ // I shift them here to be between -0.5 and 0.5
159
+ this.bounds = {
160
+ bmin: new Vector3(xmin - 0.5, ymin - 0.5, zmin - 0.5),
161
+ bmax: new Vector3(xmax - 0.5, ymax - 0.5, zmax - 0.5)
162
+ };
163
+ this.updateClipFromBounds();
164
+ }
165
+ updateClipFromBounds() {
166
+ const xmin = this.bounds.bmin.x;
167
+ const ymin = this.bounds.bmin.y;
168
+ const zmin = this.bounds.bmin.z;
169
+ const xmax = this.bounds.bmax.x;
170
+ const ymax = this.bounds.bmax.y;
171
+ const zmax = this.bounds.bmax.z;
172
+ const euler = this.meshPivot.rotation;
173
+ for (let channel = 0; channel < this.meshrep.length; ++channel) {
174
+ const meshrep = this.meshrep[channel];
175
+ if (!meshrep) {
176
+ continue;
177
+ }
178
+ const planes = [];
179
+ // up to 6 planes.
180
+ if (xmin > -0.5) {
181
+ planes.push(new Plane(new Vector3(1, 0, 0).applyEuler(euler), this.meshRoot.position.x + -xmin * this.scale.x));
182
+ }
183
+ if (ymin > -0.5) {
184
+ planes.push(new Plane(new Vector3(0, 1, 0).applyEuler(euler), this.meshRoot.position.y + -ymin * this.scale.y));
185
+ }
186
+ if (zmin > -0.5) {
187
+ planes.push(new Plane(new Vector3(0, 0, 1).applyEuler(euler), this.meshRoot.position.z + -zmin * this.scale.z));
188
+ }
189
+ if (xmax < 0.5) {
190
+ planes.push(new Plane(new Vector3(-1, 0, 0).applyEuler(euler), this.meshRoot.position.x + xmax * this.scale.x));
191
+ }
192
+ if (ymax < 0.5) {
193
+ planes.push(new Plane(new Vector3(0, -1, 0).applyEuler(euler), this.meshRoot.position.y + ymax * this.scale.y));
194
+ }
195
+ if (zmax < 0.5) {
196
+ planes.push(new Plane(new Vector3(0, 0, -1).applyEuler(euler), this.meshRoot.position.z + zmax * this.scale.z));
197
+ }
198
+ meshrep.traverse(function (child) {
199
+ if (child instanceof Mesh) {
200
+ child.material.clippingPlanes = planes;
201
+ }
202
+ });
203
+ }
204
+ }
205
+ updateOpacity(channel, value) {
206
+ this.channelOpacities[channel] = value;
207
+ const meshrep = this.meshrep[channel];
208
+ if (!meshrep) {
209
+ return;
210
+ }
211
+ meshrep.traverse(function (child) {
212
+ if (child instanceof Mesh) {
213
+ child.material.opacity = value;
214
+ child.material.transparent = value < ALPHA_THRESHOLD;
215
+ //child.material.depthWrite = !child.material.transparent;
216
+ }
217
+ });
218
+ }
219
+ hasIsosurface(channel) {
220
+ return !!this.meshrep[channel];
221
+ }
222
+ createIsosurface(channel, color,
223
+ // 127 is half of the intensity range 0..255
224
+ value = 127,
225
+ // 1.0 indicates full opacity, non-transparent
226
+ alpha = 1.0, transp = alpha < ALPHA_THRESHOLD) {
227
+ if (!this.meshrep[channel]) {
228
+ const meshrep = this.createMeshForChannel(channel, color, value, alpha, transp);
229
+ this.meshrep[channel] = meshrep;
230
+ this.channelOpacities[channel] = alpha;
231
+ this.channelColors[channel] = color;
232
+ // note we are not removing any prior mesh reps for this channel
233
+ this.meshRoot.add(meshrep);
234
+ }
235
+ }
236
+ destroyIsosurface(channel) {
237
+ const meshrep = this.meshrep[channel];
238
+ if (meshrep) {
239
+ this.meshRoot.remove(meshrep);
240
+ meshrep.traverse(function (child) {
241
+ if (child instanceof Mesh) {
242
+ child.material.dispose();
243
+ child.geometry.dispose();
244
+ }
245
+ });
246
+ this.meshrep[channel] = null;
247
+ }
248
+ }
249
+ saveChannelIsosurface(channelIndex, type, namePrefix) {
250
+ const meshrep = this.meshrep[channelIndex];
251
+ if (!meshrep) {
252
+ return;
253
+ }
254
+ if (type === "STL") {
255
+ this.exportSTL(meshrep, namePrefix + "_" + this.volume.channelNames[channelIndex]);
256
+ } else if (type === "GLTF") {
257
+ // temporarily set other meshreps to invisible
258
+ const prevviz = [];
259
+ for (let i = 0; i < this.meshrep.length; ++i) {
260
+ const meshrepi = this.meshrep[i];
261
+ if (meshrepi) {
262
+ prevviz[i] = meshrepi.visible;
263
+ meshrepi.visible = i === channelIndex;
264
+ }
265
+ }
266
+ this.exportGLTF(this.meshRoot, namePrefix + "_" + this.volume.channelNames[channelIndex]);
267
+ for (let i = 0; i < this.meshrep.length; ++i) {
268
+ const meshrepi = this.meshrep[i];
269
+ if (meshrepi) {
270
+ meshrepi.visible = prevviz[i];
271
+ }
272
+ }
273
+ }
274
+ }
275
+ exportSTL(input, fname) {
276
+ const ex = new STLExporter();
277
+ const output = ex.parse(input, {
278
+ binary: true
279
+ });
280
+ // STLExporter's typing shows that it returns string
281
+ // but this is not the case when binary=true.
282
+ FileSaver.saveBinary(output.buffer, fname + ".stl");
283
+ }
284
+
285
+ // takes a scene or object or array of scenes or objects or both!
286
+ exportGLTF(input, fname) {
287
+ const gltfExporter = new GLTFExporter();
288
+ const options = {
289
+ // transforms as translate rotate scale?
290
+ trs: false,
291
+ onlyVisible: true,
292
+ truncateDrawRange: true,
293
+ binary: true,
294
+ forceIndices: false,
295
+ forcePowerOfTwoTextures: true
296
+ };
297
+ gltfExporter.parse(input, function (result) {
298
+ if (result instanceof ArrayBuffer) {
299
+ FileSaver.saveArrayBuffer(result, fname + ".glb");
300
+ } else {
301
+ const output = JSON.stringify(result, null, 2);
302
+ FileSaver.saveString(output, fname + ".gltf");
303
+ }
304
+ }, function (error) {
305
+ console.error(error);
306
+ }, options);
307
+ }
308
+ generateIsosurfaceGeometry(channelIndex, isovalue) {
309
+ if (!this.volume) {
310
+ return [];
311
+ }
312
+ const volumeData = this.volume.channels[channelIndex].volumeData;
313
+ const marchingcubes = true;
314
+ const regionSizeArr = this.volume.imageInfo.subregionSize.toArray();
315
+ if (marchingcubes) {
316
+ const effect = new MarchingCubes(regionSizeArr, new Material(), false, false, true, volumeData);
317
+ effect.position.copy(this.meshRoot.position);
318
+ effect.scale.set(0.5 * this.scale.x, 0.5 * this.scale.y, 0.5 * this.scale.z);
319
+ effect.isovalue = isovalue;
320
+ const geometries = effect.generateGeometry();
321
+ // TODO: weld vertices and recompute normals if MarchingCubes results in excessive coincident verts
322
+ // for (var i = 0; i < geometries.length; ++i) {
323
+ // var g = new THREE.Geometry().fromBufferGeometry(geometries[i]);
324
+ // g.mergeVertices();
325
+ // geometries[i] = new THREE.BufferGeometry().fromGeometry(g);
326
+ // geometries[i].computeVertexNormals();
327
+ // }
328
+ return geometries || [];
329
+ } else {
330
+ const result = NaiveSurfaceNets.surfaceNets(volumeData, regionSizeArr, isovalue);
331
+ return NaiveSurfaceNets.constructTHREEGeometry(result);
332
+ }
333
+ }
334
+ }
@@ -0,0 +1,251 @@
1
+ // The MIT License (MIT)
2
+ //
3
+ // Copyright (c) 2012-2013 Mikola Lysenko
4
+ //
5
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ // of this software and associated documentation files (the "Software"), to deal
7
+ // in the Software without restriction, including without limitation the rights
8
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ // copies of the Software, and to permit persons to whom the Software is
10
+ // furnished to do so, subject to the following conditions:
11
+ //
12
+ // The above copyright notice and this permission notice shall be included in
13
+ // all copies or substantial portions of the Software.
14
+ //
15
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ // THE SOFTWARE.
22
+
23
+ /**
24
+ * SurfaceNets in JavaScript
25
+ *
26
+ * Written by Mikola Lysenko (C) 2012
27
+ *
28
+ * MIT License
29
+ *
30
+ * Based on: S.F. Gibson, "Constrained Elastic Surface Nets". (1998) MERL Tech Report.
31
+ */
32
+
33
+ // MODIFIED 2018 BY DANIELT@ALLENINSTITUTE.ORG TO ACCEPT AN ISOVALUE AND RESCALE VERTEX POSITIONS
34
+ import { BufferAttribute, BufferGeometry } from "three";
35
+ var SurfaceNets = function () {
36
+ "use strict";
37
+
38
+ //Precompute edge table, like Paul Bourke does.
39
+ // This saves a bit of time when computing the centroid of each boundary cell
40
+ var cube_edges = new Int32Array(24),
41
+ edge_table = new Int32Array(256);
42
+ (function () {
43
+ //Initialize the cube_edges table
44
+ // This is just the vertex number of each cube
45
+ var k = 0;
46
+ for (var i = 0; i < 8; ++i) {
47
+ for (var j = 1; j <= 4; j <<= 1) {
48
+ var p = i ^ j;
49
+ if (i <= p) {
50
+ cube_edges[k++] = i;
51
+ cube_edges[k++] = p;
52
+ }
53
+ }
54
+ }
55
+
56
+ //Initialize the intersection table.
57
+ // This is a 2^(cube configuration) -> 2^(edge configuration) map
58
+ // There is one entry for each possible cube configuration, and the output is a 12-bit vector enumerating all edges crossing the 0-level.
59
+ for (var i = 0; i < 256; ++i) {
60
+ var em = 0;
61
+ for (var j = 0; j < 24; j += 2) {
62
+ var a = !!(i & 1 << cube_edges[j]),
63
+ b = !!(i & 1 << cube_edges[j + 1]);
64
+ em |= a !== b ? 1 << (j >> 1) : 0;
65
+ }
66
+ edge_table[i] = em;
67
+ }
68
+ })();
69
+
70
+ //Internal buffer, this may get resized at run time
71
+ var buffer = new Int32Array(4096);
72
+ return function (data, dims, isovalue) {
73
+ var vertices = [],
74
+ faces = [],
75
+ n = 0,
76
+ x = new Int32Array(3),
77
+ R = new Int32Array([1, dims[0] + 1, (dims[0] + 1) * (dims[1] + 1)]),
78
+ grid = new Float32Array(8),
79
+ buf_no = 1;
80
+
81
+ //Resize buffer if necessary
82
+ if (R[2] * 2 > buffer.length) {
83
+ buffer = new Int32Array(R[2] * 2);
84
+ }
85
+
86
+ //March over the voxel grid
87
+ for (x[2] = 0; x[2] < dims[2] - 1; ++x[2], n += dims[0], buf_no ^= 1, R[2] = -R[2]) {
88
+ //m is the pointer into the buffer we are going to use.
89
+ //This is slightly obtuse because javascript does not have good support for packed data structures, so we must use typed arrays :(
90
+ //The contents of the buffer will be the indices of the vertices on the previous x/y slice of the volume
91
+ var m = 1 + (dims[0] + 1) * (1 + buf_no * (dims[1] + 1));
92
+ for (x[1] = 0; x[1] < dims[1] - 1; ++x[1], ++n, m += 2) for (x[0] = 0; x[0] < dims[0] - 1; ++x[0], ++n, ++m) {
93
+ //Read in 8 field values around this vertex and store them in an array
94
+ //Also calculate 8-bit mask, like in marching cubes, so we can speed up sign checks later
95
+ var mask = 0,
96
+ g = 0,
97
+ idx = n;
98
+ for (var k = 0; k < 2; ++k, idx += dims[0] * (dims[1] - 2)) for (var j = 0; j < 2; ++j, idx += dims[0] - 2) for (var i = 0; i < 2; ++i, ++g, ++idx) {
99
+ var p = data[idx] - isovalue;
100
+ grid[g] = p;
101
+ mask |= p < 0 ? 1 << g : 0;
102
+ }
103
+
104
+ //Check for early termination if cell does not intersect boundary
105
+ if (mask === 0 || mask === 0xff) {
106
+ continue;
107
+ }
108
+
109
+ //Sum up edge intersections
110
+ var edge_mask = edge_table[mask],
111
+ v = [0.0, 0.0, 0.0],
112
+ e_count = 0;
113
+
114
+ //For every edge of the cube...
115
+ for (var i = 0; i < 12; ++i) {
116
+ //Use edge mask to check if it is crossed
117
+ if (!(edge_mask & 1 << i)) {
118
+ continue;
119
+ }
120
+
121
+ //If it did, increment number of edge crossings
122
+ ++e_count;
123
+
124
+ //Now find the point of intersection
125
+ var e0 = cube_edges[i << 1],
126
+ //Unpack vertices
127
+ e1 = cube_edges[(i << 1) + 1],
128
+ g0 = grid[e0],
129
+ //Unpack grid values
130
+ g1 = grid[e1],
131
+ t = g0 - g1; //Compute point of intersection
132
+ if (Math.abs(t) > 1e-6) {
133
+ t = g0 / t;
134
+ } else {
135
+ continue;
136
+ }
137
+
138
+ //Interpolate vertices and add up intersections (this can be done without multiplying)
139
+ for (var j = 0, k = 1; j < 3; ++j, k <<= 1) {
140
+ var a = e0 & k,
141
+ b = e1 & k;
142
+ if (a !== b) {
143
+ v[j] += a ? 1.0 - t : t;
144
+ } else {
145
+ v[j] += a ? 1.0 : 0;
146
+ }
147
+ }
148
+ }
149
+
150
+ //Now we just average the edge intersections and add them to coordinate
151
+ var s = 1.0 / e_count;
152
+ for (var i = 0; i < 3; ++i) {
153
+ v[i] = x[i] + s * v[i];
154
+ }
155
+
156
+ //Add vertex to buffer, store pointer to vertex index in buffer
157
+ buffer[m] = vertices.length;
158
+ vertices.push([2.0 * v[0] / dims[0] - 1.0, 2.0 * v[1] / dims[1] - 1.0, 2.0 * v[2] / dims[2] - 1.0]);
159
+
160
+ //Now we need to add faces together, to do this we just loop over 3 basis components
161
+ for (var i = 0; i < 3; ++i) {
162
+ //The first three entries of the edge_mask count the crossings along the edge
163
+ if (!(edge_mask & 1 << i)) {
164
+ continue;
165
+ }
166
+
167
+ // i = axes we are point along. iu, iv = orthogonal axes
168
+ var iu = (i + 1) % 3,
169
+ iv = (i + 2) % 3;
170
+
171
+ //If we are on a boundary, skip it
172
+ if (x[iu] === 0 || x[iv] === 0) {
173
+ continue;
174
+ }
175
+
176
+ //Otherwise, look up adjacent edges in buffer
177
+ var du = R[iu],
178
+ dv = R[iv];
179
+
180
+ //Remember to flip orientation depending on the sign of the corner.
181
+ if (mask & 1) {
182
+ faces.push([buffer[m], buffer[m - du], buffer[m - du - dv], buffer[m - dv]]);
183
+ } else {
184
+ faces.push([buffer[m], buffer[m - dv], buffer[m - du - dv], buffer[m - du]]);
185
+ }
186
+ }
187
+ }
188
+ }
189
+
190
+ //All done! Return the result
191
+ return {
192
+ vertices: vertices,
193
+ faces: faces
194
+ };
195
+ };
196
+ }();
197
+
198
+ // returns array of indexed BufferGeometry
199
+ function ConstructTHREEGeometry(surfaceNetResult) {
200
+ var result = surfaceNetResult;
201
+ var varray = new Float32Array(result.vertices.length * 3);
202
+ for (var i = 0; i < result.vertices.length; ++i) {
203
+ var v = result.vertices[i];
204
+ varray[i * 3 + 0] = v[0];
205
+ varray[i * 3 + 1] = v[1];
206
+ varray[i * 3 + 2] = v[2];
207
+ }
208
+
209
+ // count triangles; split quad faces
210
+ let n_triangles = 0;
211
+ for (var i = 0; i < result.faces.length; ++i) {
212
+ var f = result.faces[i];
213
+ if (f.length === 3) {
214
+ n_triangles += 1;
215
+ } else if (f.length === 4) {
216
+ n_triangles += 2;
217
+ } else {
218
+ //Polygon needs to be subdivided...
219
+ console.log("unhandled poly with " + f.length + " vertices");
220
+ }
221
+ }
222
+ var inds = new Uint32Array(n_triangles * 3);
223
+ let j = 0;
224
+ for (var i = 0; i < result.faces.length; ++i) {
225
+ var f = result.faces[i];
226
+ // note what appears to be inverted winding order.
227
+ // I believe this is related to isosurface < or > testing but have not checked.
228
+ if (f.length === 3) {
229
+ inds[j++] = f[2];
230
+ inds[j++] = f[1];
231
+ inds[j++] = f[0];
232
+ } else if (f.length === 4) {
233
+ // two triangles: (2,1,0) and (3,2,0)
234
+ inds[j++] = f[2];
235
+ inds[j++] = f[1];
236
+ inds[j++] = f[0];
237
+ inds[j++] = f[3];
238
+ inds[j++] = f[2];
239
+ inds[j++] = f[0];
240
+ }
241
+ }
242
+ var geo = new BufferGeometry();
243
+ geo.setAttribute("position", new BufferAttribute(varray, 3, false));
244
+ geo.setIndex(new BufferAttribute(inds, 1));
245
+ geo.computeVertexNormals();
246
+ return [geo];
247
+ }
248
+ export default {
249
+ surfaceNets: SurfaceNets,
250
+ constructTHREEGeometry: ConstructTHREEGeometry
251
+ };