simview 3.2__py3-none-any.whl
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.
- simview/__init__.py +69 -0
- simview/__main__.py +174 -0
- simview/launcher.py +109 -0
- simview/live.py +204 -0
- simview/merge.py +593 -0
- simview/model.py +690 -0
- simview/py.typed +0 -0
- simview/scene.py +603 -0
- simview/server.py +739 -0
- simview/state.py +190 -0
- simview/static/css/controls.css +36 -0
- simview/static/js/SimView.js +601 -0
- simview/static/js/components/AnimationController.js +654 -0
- simview/static/js/components/BatchManager.js +198 -0
- simview/static/js/components/InteractionController.js +446 -0
- simview/static/js/components/InteractionControls.js +116 -0
- simview/static/js/components/Scene.js +166 -0
- simview/static/js/components/StateStore.js +188 -0
- simview/static/js/config.js +233 -0
- simview/static/js/main.js +3 -0
- simview/static/js/objects/Body.js +616 -0
- simview/static/js/objects/StaticObject.js +181 -0
- simview/static/js/objects/Terrain.js +514 -0
- simview/static/js/objects/utils.js +378 -0
- simview/static/js/ui/AnalysisPanel.js +233 -0
- simview/static/js/ui/BatchLegend.js +207 -0
- simview/static/js/ui/BodyStateWindow.js +521 -0
- simview/static/js/ui/Controls.js +586 -0
- simview/static/js/ui/ErrorMetrics.js +664 -0
- simview/static/js/ui/Legend.js +87 -0
- simview/static/js/ui/PlaybackControls.js +314 -0
- simview/static/js/ui/ScalarPlotter.js +575 -0
- simview/static/js/utils/blobCodec.js +73 -0
- simview/static/js/utils/bodyTransforms.js +151 -0
- simview/static/js/utils/csv.js +46 -0
- simview/static/js/utils/errorMath.js +69 -0
- simview/static/js/utils/injectStyles.js +16 -0
- simview/static/js/utils/interpolate.js +65 -0
- simview/static/js/utils/liveFollow.js +17 -0
- simview/static/js/utils/loadRecordingLibs.js +34 -0
- simview/static/js/utils/viewState.js +226 -0
- simview/static/lib/chroma-js-3.1.2/index.min.js +8 -0
- simview/static/lib/chroma-js-3.1.2/src/Color.js +49 -0
- simview/static/lib/chroma-js-3.1.2/src/chroma.js +10 -0
- simview/static/lib/chroma-js-3.1.2/src/colors/colorbrewer.js +81 -0
- simview/static/lib/chroma-js-3.1.2/src/colors/w3cx11.js +164 -0
- simview/static/lib/chroma-js-3.1.2/src/generator/average.js +88 -0
- simview/static/lib/chroma-js-3.1.2/src/generator/bezier.js +86 -0
- simview/static/lib/chroma-js-3.1.2/src/generator/blend.js +57 -0
- simview/static/lib/chroma-js-3.1.2/src/generator/cubehelix.js +87 -0
- simview/static/lib/chroma-js-3.1.2/src/generator/mix.js +19 -0
- simview/static/lib/chroma-js-3.1.2/src/generator/random.js +12 -0
- simview/static/lib/chroma-js-3.1.2/src/generator/scale.js +394 -0
- simview/static/lib/chroma-js-3.1.2/src/interpolator/_hsx.js +59 -0
- simview/static/lib/chroma-js-3.1.2/src/interpolator/hcg.js +12 -0
- simview/static/lib/chroma-js-3.1.2/src/interpolator/hsi.js +12 -0
- simview/static/lib/chroma-js-3.1.2/src/interpolator/hsl.js +12 -0
- simview/static/lib/chroma-js-3.1.2/src/interpolator/hsv.js +12 -0
- simview/static/lib/chroma-js-3.1.2/src/interpolator/index.js +1 -0
- simview/static/lib/chroma-js-3.1.2/src/interpolator/lab.js +19 -0
- simview/static/lib/chroma-js-3.1.2/src/interpolator/lch.js +13 -0
- simview/static/lib/chroma-js-3.1.2/src/interpolator/lrgb.js +19 -0
- simview/static/lib/chroma-js-3.1.2/src/interpolator/num.js +15 -0
- simview/static/lib/chroma-js-3.1.2/src/interpolator/oklab.js +19 -0
- simview/static/lib/chroma-js-3.1.2/src/interpolator/oklch.js +12 -0
- simview/static/lib/chroma-js-3.1.2/src/interpolator/rgb.js +18 -0
- simview/static/lib/chroma-js-3.1.2/src/io/cmyk/cmyk2rgb.js +16 -0
- simview/static/lib/chroma-js-3.1.2/src/io/cmyk/index.js +27 -0
- simview/static/lib/chroma-js-3.1.2/src/io/cmyk/rgb2cmyk.js +17 -0
- simview/static/lib/chroma-js-3.1.2/src/io/css/css2rgb.js +238 -0
- simview/static/lib/chroma-js-3.1.2/src/io/css/hsl2css.js +26 -0
- simview/static/lib/chroma-js-3.1.2/src/io/css/index.js +27 -0
- simview/static/lib/chroma-js-3.1.2/src/io/css/lab2css.js +24 -0
- simview/static/lib/chroma-js-3.1.2/src/io/css/lch2css.js +24 -0
- simview/static/lib/chroma-js-3.1.2/src/io/css/oklab2css.js +16 -0
- simview/static/lib/chroma-js-3.1.2/src/io/css/oklch2css.js +16 -0
- simview/static/lib/chroma-js-3.1.2/src/io/css/rgb2css.js +61 -0
- simview/static/lib/chroma-js-3.1.2/src/io/gl/index.js +22 -0
- simview/static/lib/chroma-js-3.1.2/src/io/hcg/hcg2rgb.js +55 -0
- simview/static/lib/chroma-js-3.1.2/src/io/hcg/index.js +27 -0
- simview/static/lib/chroma-js-3.1.2/src/io/hcg/rgb2hcg.js +23 -0
- simview/static/lib/chroma-js-3.1.2/src/io/hex/hex2rgb.js +56 -0
- simview/static/lib/chroma-js-3.1.2/src/io/hex/index.js +29 -0
- simview/static/lib/chroma-js-3.1.2/src/io/hex/rgb2hex.js +29 -0
- simview/static/lib/chroma-js-3.1.2/src/io/hsi/hsi2rgb.js +45 -0
- simview/static/lib/chroma-js-3.1.2/src/io/hsi/index.js +27 -0
- simview/static/lib/chroma-js-3.1.2/src/io/hsi/rgb2hsi.js +31 -0
- simview/static/lib/chroma-js-3.1.2/src/io/hsl/hsl2rgb.js +35 -0
- simview/static/lib/chroma-js-3.1.2/src/io/hsl/index.js +27 -0
- simview/static/lib/chroma-js-3.1.2/src/io/hsl/rgb2hsl.js +45 -0
- simview/static/lib/chroma-js-3.1.2/src/io/hsv/hsv2rgb.js +47 -0
- simview/static/lib/chroma-js-3.1.2/src/io/hsv/index.js +27 -0
- simview/static/lib/chroma-js-3.1.2/src/io/hsv/rgb2hsv.js +32 -0
- simview/static/lib/chroma-js-3.1.2/src/io/input.js +4 -0
- simview/static/lib/chroma-js-3.1.2/src/io/lab/index.js +28 -0
- simview/static/lib/chroma-js-3.1.2/src/io/lab/lab-constants.js +120 -0
- simview/static/lib/chroma-js-3.1.2/src/io/lab/lab2rgb.js +101 -0
- simview/static/lib/chroma-js-3.1.2/src/io/lab/rgb2lab.js +67 -0
- simview/static/lib/chroma-js-3.1.2/src/io/lch/hcl2rgb.js +9 -0
- simview/static/lib/chroma-js-3.1.2/src/io/lch/index.js +35 -0
- simview/static/lib/chroma-js-3.1.2/src/io/lch/lab2lch.js +12 -0
- simview/static/lib/chroma-js-3.1.2/src/io/lch/lch2lab.js +18 -0
- simview/static/lib/chroma-js-3.1.2/src/io/lch/lch2rgb.js +13 -0
- simview/static/lib/chroma-js-3.1.2/src/io/lch/rgb2lch.js +12 -0
- simview/static/lib/chroma-js-3.1.2/src/io/named/index.js +30 -0
- simview/static/lib/chroma-js-3.1.2/src/io/num/index.js +32 -0
- simview/static/lib/chroma-js-3.1.2/src/io/num/num2rgb.js +13 -0
- simview/static/lib/chroma-js-3.1.2/src/io/num/rgb2num.js +8 -0
- simview/static/lib/chroma-js-3.1.2/src/io/oklab/index.js +27 -0
- simview/static/lib/chroma-js-3.1.2/src/io/oklab/oklab2rgb.js +34 -0
- simview/static/lib/chroma-js-3.1.2/src/io/oklab/rgb2oklab.js +37 -0
- simview/static/lib/chroma-js-3.1.2/src/io/oklch/index.js +27 -0
- simview/static/lib/chroma-js-3.1.2/src/io/oklch/oklch2rgb.js +13 -0
- simview/static/lib/chroma-js-3.1.2/src/io/oklch/rgb2oklch.js +12 -0
- simview/static/lib/chroma-js-3.1.2/src/io/rgb/index.js +44 -0
- simview/static/lib/chroma-js-3.1.2/src/io/temp/index.js +22 -0
- simview/static/lib/chroma-js-3.1.2/src/io/temp/rgb2temperature.js +30 -0
- simview/static/lib/chroma-js-3.1.2/src/io/temp/temperature2rgb.js +39 -0
- simview/static/lib/chroma-js-3.1.2/src/ops/alpha.js +13 -0
- simview/static/lib/chroma-js-3.1.2/src/ops/clipped.js +5 -0
- simview/static/lib/chroma-js-3.1.2/src/ops/darken.js +17 -0
- simview/static/lib/chroma-js-3.1.2/src/ops/get.js +13 -0
- simview/static/lib/chroma-js-3.1.2/src/ops/luminance.js +54 -0
- simview/static/lib/chroma-js-3.1.2/src/ops/mix.js +10 -0
- simview/static/lib/chroma-js-3.1.2/src/ops/premultiply.js +12 -0
- simview/static/lib/chroma-js-3.1.2/src/ops/saturate.js +15 -0
- simview/static/lib/chroma-js-3.1.2/src/ops/set.js +43 -0
- simview/static/lib/chroma-js-3.1.2/src/ops/shade.js +11 -0
- simview/static/lib/chroma-js-3.1.2/src/utils/analyze.js +191 -0
- simview/static/lib/chroma-js-3.1.2/src/utils/clip_rgb.js +15 -0
- simview/static/lib/chroma-js-3.1.2/src/utils/contrast.js +12 -0
- simview/static/lib/chroma-js-3.1.2/src/utils/contrastAPCA.js +68 -0
- simview/static/lib/chroma-js-3.1.2/src/utils/delta-e.js +62 -0
- simview/static/lib/chroma-js-3.1.2/src/utils/distance.js +17 -0
- simview/static/lib/chroma-js-3.1.2/src/utils/index.js +27 -0
- simview/static/lib/chroma-js-3.1.2/src/utils/last.js +8 -0
- simview/static/lib/chroma-js-3.1.2/src/utils/limit.js +5 -0
- simview/static/lib/chroma-js-3.1.2/src/utils/multiply-matrices.js +36 -0
- simview/static/lib/chroma-js-3.1.2/src/utils/scales.js +15 -0
- simview/static/lib/chroma-js-3.1.2/src/utils/type.js +18 -0
- simview/static/lib/chroma-js-3.1.2/src/utils/unpack.js +17 -0
- simview/static/lib/chroma-js-3.1.2/src/utils/valid.js +11 -0
- simview/static/lib/chroma-js-3.1.2/src/version.js +2 -0
- simview/static/lib/download.js +132 -0
- simview/static/lib/js-colormaps.js +19335 -0
- simview/static/lib/tar.js +334 -0
- simview/static/lib/three-0.174.0/addons/controls/OrbitControls.js +1556 -0
- simview/static/lib/three-0.174.0/addons/libs/lil-gui.module.min.js +8 -0
- simview/static/lib/three-0.174.0/three.core.js +48830 -0
- simview/static/lib/three-0.174.0/three.module.js +17313 -0
- simview/static/lib/uPlot.esm.js +6140 -0
- simview/static/lib/uPlot.min.css +2 -0
- simview/static/textures/contacts/red-cross0.png +0 -0
- simview/static/textures/points/ball0.png +0 -0
- simview/static/textures/points/ball1.png +0 -0
- simview/templates/index.html +81 -0
- simview/utils.py +41 -0
- simview-3.2.dist-info/METADATA +552 -0
- simview-3.2.dist-info/RECORD +163 -0
- simview-3.2.dist-info/WHEEL +5 -0
- simview-3.2.dist-info/entry_points.txt +2 -0
- simview-3.2.dist-info/licenses/LICENSE +28 -0
- simview-3.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import * as THREE from "three";
|
|
2
|
+
import { STATIC_OBJECT_CONFIG } from "../config.js"; // Assuming this exists
|
|
3
|
+
import {
|
|
4
|
+
createGeometry,
|
|
5
|
+
createMesh,
|
|
6
|
+
createPoints,
|
|
7
|
+
createWireframe,
|
|
8
|
+
} from "./utils.js";
|
|
9
|
+
|
|
10
|
+
export class StaticObject {
|
|
11
|
+
constructor(objectData, app) {
|
|
12
|
+
this.app = app;
|
|
13
|
+
this.name = objectData.name;
|
|
14
|
+
this.isSingleton = objectData.isSingleton || false; // Default to false if not specified
|
|
15
|
+
this.batchSize = app.batchManager.simBatches;
|
|
16
|
+
this.representations = { mesh: [], wireframe: [], points: [] }; // For visualization modes
|
|
17
|
+
this.batchGroups = []; // Store batch groups
|
|
18
|
+
|
|
19
|
+
// Validate input data
|
|
20
|
+
if (this.isSingleton) {
|
|
21
|
+
if (!objectData.shape || !objectData.shape.type) {
|
|
22
|
+
throw new Error(
|
|
23
|
+
"Singleton static object requires a 'shape' definition."
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
this.type = objectData.shape.type;
|
|
27
|
+
} else {
|
|
28
|
+
if (
|
|
29
|
+
!Array.isArray(objectData.shapes) ||
|
|
30
|
+
objectData.shapes.length !== this.batchSize
|
|
31
|
+
) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
`Batched static object requires a 'shapes' array of length ${this.batchSize}.`
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
this.type = objectData.shapes[0].type; // Assume consistent types for simplicity
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Initialize and create visual objects
|
|
40
|
+
this.initializeGroup();
|
|
41
|
+
this.createBatchGroups(objectData);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Initialize the main Three.js group */
|
|
45
|
+
initializeGroup() {
|
|
46
|
+
this.group = new THREE.Group();
|
|
47
|
+
this.group.name = this.name;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
createBatchGroups(objectData) {
|
|
51
|
+
const geometryConfig = STATIC_OBJECT_CONFIG.geometry || {};
|
|
52
|
+
|
|
53
|
+
if (this.isSingleton) {
|
|
54
|
+
const shape = objectData.shape;
|
|
55
|
+
const meshConfig = {
|
|
56
|
+
...STATIC_OBJECT_CONFIG.mesh,
|
|
57
|
+
color: shape.color || STATIC_OBJECT_CONFIG.color || 0xffffff,
|
|
58
|
+
};
|
|
59
|
+
const pointsConfig = {
|
|
60
|
+
...STATIC_OBJECT_CONFIG.points,
|
|
61
|
+
color: shape.color || STATIC_OBJECT_CONFIG.color || 0xffffff,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
if (shape.type === "pointcloud") {
|
|
65
|
+
for (let i = 0; i < this.batchSize; i++) {
|
|
66
|
+
const points = createPoints(shape.points, pointsConfig);
|
|
67
|
+
if (points) {
|
|
68
|
+
points.visible = this.app.uiState.bodyVisualizationMode === "points";
|
|
69
|
+
const offset = this.app.batchManager.getBatchOffset(i);
|
|
70
|
+
points.position.set(offset.x, offset.y, offset.z);
|
|
71
|
+
this.group.add(points);
|
|
72
|
+
this.representations["points"].push(points);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
} else {
|
|
76
|
+
const geometry = createGeometry(shape, geometryConfig);
|
|
77
|
+
const meshMaterial = createMesh(geometry, meshConfig).material;
|
|
78
|
+
const wireframeMaterial = new THREE.MeshBasicMaterial({
|
|
79
|
+
color: STATIC_OBJECT_CONFIG.wireframe?.color || 0x4080ff,
|
|
80
|
+
wireframe: true,
|
|
81
|
+
transparent: true,
|
|
82
|
+
opacity: 0.2
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const instancedMesh = new THREE.InstancedMesh(geometry, meshMaterial, this.batchSize);
|
|
86
|
+
const instancedWireframe = new THREE.InstancedMesh(geometry, wireframeMaterial, this.batchSize);
|
|
87
|
+
|
|
88
|
+
for (let i = 0; i < this.batchSize; i++) {
|
|
89
|
+
const offset = this.app.batchManager.getBatchOffset(i);
|
|
90
|
+
const matrix = new THREE.Matrix4().makeTranslation(offset.x, offset.y, offset.z);
|
|
91
|
+
instancedMesh.setMatrixAt(i, matrix);
|
|
92
|
+
instancedWireframe.setMatrixAt(i, matrix);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
instancedMesh.visible = this.app.uiState.bodyVisualizationMode === "mesh";
|
|
96
|
+
instancedWireframe.visible = this.app.uiState.bodyVisualizationMode === "wireframe";
|
|
97
|
+
|
|
98
|
+
this.group.add(instancedMesh);
|
|
99
|
+
this.group.add(instancedWireframe);
|
|
100
|
+
|
|
101
|
+
this.representations["mesh"] = instancedMesh;
|
|
102
|
+
this.representations["wireframe"] = instancedWireframe;
|
|
103
|
+
}
|
|
104
|
+
} else {
|
|
105
|
+
// Non-singleton: reuse geometry if possible (if all shapes are same type and same dimensions)
|
|
106
|
+
// For now, keep as is but optimize material/geometry creation if they are identical
|
|
107
|
+
for (let i = 0; i < this.batchSize; i++) {
|
|
108
|
+
const batchGroup = new THREE.Group();
|
|
109
|
+
batchGroup.name = `${this.name}_batch_${i}`;
|
|
110
|
+
this.group.add(batchGroup);
|
|
111
|
+
this.batchGroups.push(batchGroup);
|
|
112
|
+
|
|
113
|
+
const shape = objectData.shapes[i];
|
|
114
|
+
const meshConfig = {
|
|
115
|
+
...STATIC_OBJECT_CONFIG.mesh,
|
|
116
|
+
color: shape.color || STATIC_OBJECT_CONFIG.color || 0xffffff,
|
|
117
|
+
};
|
|
118
|
+
const pointsConfig = {
|
|
119
|
+
...STATIC_OBJECT_CONFIG.points,
|
|
120
|
+
color: shape.color || STATIC_OBJECT_CONFIG.color || 0xffffff,
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
if (shape.type === "pointcloud") {
|
|
124
|
+
const points = createPoints(shape.points, pointsConfig);
|
|
125
|
+
if (points) {
|
|
126
|
+
points.visible = this.app.uiState.bodyVisualizationMode === "points";
|
|
127
|
+
batchGroup.add(points);
|
|
128
|
+
this.representations["points"].push(points);
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
const geometry = createGeometry(shape, geometryConfig);
|
|
132
|
+
const mesh = createMesh(geometry, meshConfig);
|
|
133
|
+
mesh.visible = this.app.uiState.bodyVisualizationMode === "mesh";
|
|
134
|
+
batchGroup.add(mesh);
|
|
135
|
+
this.representations["mesh"].push(mesh);
|
|
136
|
+
const wireframe = createWireframe(geometry, STATIC_OBJECT_CONFIG.wireframe);
|
|
137
|
+
wireframe.visible = this.app.uiState.bodyVisualizationMode === "wireframe";
|
|
138
|
+
batchGroup.add(wireframe);
|
|
139
|
+
this.representations["wireframe"].push(wireframe);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const offset = this.app.batchManager.getBatchOffset(i);
|
|
143
|
+
batchGroup.position.set(offset.x, offset.y, offset.z);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Update visualization mode (mesh, wireframe, points) */
|
|
149
|
+
updateVisualizationMode(mode) {
|
|
150
|
+
for (const [type, obj] of Object.entries(this.representations)) {
|
|
151
|
+
if (obj instanceof THREE.InstancedMesh) {
|
|
152
|
+
obj.visible = type === mode;
|
|
153
|
+
} else if (Array.isArray(obj)) {
|
|
154
|
+
obj.forEach((o) => (o.visible = type === mode));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Return the Three.js group for scene integration */
|
|
160
|
+
getObject3D() {
|
|
161
|
+
return this.group;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Clean up resources */
|
|
165
|
+
dispose() {
|
|
166
|
+
if (this.group) {
|
|
167
|
+
if (
|
|
168
|
+
this.app.scene &&
|
|
169
|
+
typeof this.app.scene.removeObject3D === "function"
|
|
170
|
+
) {
|
|
171
|
+
this.app.scene.removeObject3D(this.group);
|
|
172
|
+
} else if (this.group.parent) {
|
|
173
|
+
this.group.parent.remove(this.group);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
this.group = null;
|
|
177
|
+
this.representations = { mesh: [], wireframe: [], points: [] };
|
|
178
|
+
this.batchGroups = [];
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
import * as THREE from "three";
|
|
2
|
+
import { TERRAIN_CONFIG } from "../config.js";
|
|
3
|
+
import { colorMapOptions, evaluate_cmap } from "../../lib/js-colormaps.js";
|
|
4
|
+
|
|
5
|
+
export class Terrain {
|
|
6
|
+
constructor(terrainData, app) {
|
|
7
|
+
this.app = app; // Store reference to app for accessing batch manager
|
|
8
|
+
this.bounds = terrainData.bounds;
|
|
9
|
+
this.dimensions = terrainData.dimensions;
|
|
10
|
+
this.group = null;
|
|
11
|
+
|
|
12
|
+
this.heightData = this.#normalizeScalarField(terrainData.heightData, true);
|
|
13
|
+
this.frictionData = this.#normalizeScalarField(terrainData.frictionData);
|
|
14
|
+
this.stiffnessData = this.#normalizeScalarField(terrainData.stiffnessData);
|
|
15
|
+
this.isSingleton = terrainData.isSingleton;
|
|
16
|
+
|
|
17
|
+
const normals = this.#normalizeNormals(terrainData.normals);
|
|
18
|
+
|
|
19
|
+
this.#createVisualRepresentations(this.heightData, normals);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Reshapes a flat Float32Array of `batchSize` concatenated per-vertex
|
|
23
|
+
// records (`width` floats each) into one subarray view per batch.
|
|
24
|
+
#splitIntoBatches(flatArray, width) {
|
|
25
|
+
const batchSize = this.app.batchManager.simBatches;
|
|
26
|
+
const resolution = this.dimensions.resolutionX * this.dimensions.resolutionY;
|
|
27
|
+
const perBatch = resolution * width;
|
|
28
|
+
const batches = [];
|
|
29
|
+
for (let i = 0; i < batchSize; i++) {
|
|
30
|
+
batches.push(flatArray.subarray(i * perBatch, (i + 1) * perBatch));
|
|
31
|
+
}
|
|
32
|
+
return batches;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Normalizes a per-vertex scalar terrain field (heightData/frictionData/
|
|
36
|
+
// stiffnessData) to an array of one entry per batch. Passes through
|
|
37
|
+
// unchanged if already batched, or if absent (friction/stiffness are
|
|
38
|
+
// optional).
|
|
39
|
+
#normalizeScalarField(data, logNormalization = false) {
|
|
40
|
+
if (data instanceof Float32Array) {
|
|
41
|
+
return this.#splitIntoBatches(data, 1);
|
|
42
|
+
}
|
|
43
|
+
if (Array.isArray(data) && data.length > 0 && typeof data[0] === "number") {
|
|
44
|
+
if (logNormalization) {
|
|
45
|
+
console.debug("Normalizing flat heightData to single batch");
|
|
46
|
+
}
|
|
47
|
+
return [data];
|
|
48
|
+
}
|
|
49
|
+
return data;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Normalizes terrain normals (per-vertex 3-vectors) to an array of one
|
|
53
|
+
// batch of vectors.
|
|
54
|
+
#normalizeNormals(data) {
|
|
55
|
+
if (data instanceof Float32Array) {
|
|
56
|
+
return this.#splitIntoBatches(data, 3);
|
|
57
|
+
}
|
|
58
|
+
if (Array.isArray(data) && data.length > 0) {
|
|
59
|
+
if (typeof data[0] === "number") {
|
|
60
|
+
// Case: Flat array [x, y, z, x, y, z...]
|
|
61
|
+
console.debug("Normalizing flat normals array to single batch of vectors");
|
|
62
|
+
const vectors = [];
|
|
63
|
+
for (let i = 0; i < data.length; i += 3) {
|
|
64
|
+
vectors.push([data[i], data[i + 1], data[i + 2]]);
|
|
65
|
+
}
|
|
66
|
+
return [vectors];
|
|
67
|
+
}
|
|
68
|
+
if (
|
|
69
|
+
Array.isArray(data[0]) &&
|
|
70
|
+
data[0].length > 0 &&
|
|
71
|
+
typeof data[0][0] === "number"
|
|
72
|
+
) {
|
|
73
|
+
// Case: Array of vectors [[x,y,z], ...] -> Wrap in batch
|
|
74
|
+
console.debug("Normalizing list of normal vectors to single batch");
|
|
75
|
+
return [data];
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return data;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
#createMaterials() {
|
|
82
|
+
// Create materials for the surface and wireframe
|
|
83
|
+
const surfaceMaterial = new THREE.MeshPhongMaterial({
|
|
84
|
+
vertexColors: true,
|
|
85
|
+
side: THREE.DoubleSide,
|
|
86
|
+
flatShading: TERRAIN_CONFIG.flatShading || false,
|
|
87
|
+
shininess: TERRAIN_CONFIG.shininess || 10,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const wireframeMaterial = new THREE.MeshBasicMaterial({
|
|
91
|
+
color: 0xffffff,
|
|
92
|
+
wireframe: true,
|
|
93
|
+
opacity: 0.2,
|
|
94
|
+
transparent: true,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
return { surfaceMaterial, wireframeMaterial };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
#createVisualRepresentations(heightData, normals) {
|
|
101
|
+
console.debug(
|
|
102
|
+
`Creating terrain geometry: ${this.dimensions.sizeX}x${this.dimensions.sizeY} m, resolution: ${this.dimensions.resolutionX}x${this.dimensions.resolutionY}`
|
|
103
|
+
);
|
|
104
|
+
const { surfaceMaterial, wireframeMaterial } = this.#createMaterials();
|
|
105
|
+
|
|
106
|
+
let singletonSurfaceGeometry = null;
|
|
107
|
+
let singletonNormals = null;
|
|
108
|
+
|
|
109
|
+
if (this.isSingleton) {
|
|
110
|
+
singletonSurfaceGeometry = this.#createSurfaceGeometryFromHeightData(
|
|
111
|
+
heightData[0],
|
|
112
|
+
0
|
|
113
|
+
);
|
|
114
|
+
singletonNormals = this.#createNormalVectors(
|
|
115
|
+
heightData[0],
|
|
116
|
+
normals[0]
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Create geometry for each batch
|
|
121
|
+
this.group = new THREE.Group();
|
|
122
|
+
for (let i = 0; i < this.app.batchManager.simBatches; i++) {
|
|
123
|
+
const batchGroup = new THREE.Group();
|
|
124
|
+
batchGroup.name = `batch${i}`;
|
|
125
|
+
|
|
126
|
+
const surfaceGeometry = this.isSingleton
|
|
127
|
+
? singletonSurfaceGeometry
|
|
128
|
+
: this.#createSurfaceGeometryFromHeightData(heightData[i], i);
|
|
129
|
+
|
|
130
|
+
const surfaceMesh = new THREE.Mesh(surfaceGeometry, surfaceMaterial);
|
|
131
|
+
surfaceMesh.name = "surface";
|
|
132
|
+
surfaceMesh.receiveShadow = true;
|
|
133
|
+
surfaceMesh.castShadow = true;
|
|
134
|
+
batchGroup.add(surfaceMesh);
|
|
135
|
+
|
|
136
|
+
const wireframeMesh = new THREE.Mesh(surfaceGeometry, wireframeMaterial);
|
|
137
|
+
wireframeMesh.name = "wireframe";
|
|
138
|
+
batchGroup.add(wireframeMesh);
|
|
139
|
+
|
|
140
|
+
const surfaceNormals = this.isSingleton
|
|
141
|
+
? singletonNormals.clone()
|
|
142
|
+
: this.#createNormalVectors(heightData[i], normals[i]);
|
|
143
|
+
|
|
144
|
+
surfaceNormals.name = "normals";
|
|
145
|
+
batchGroup.add(surfaceNormals);
|
|
146
|
+
|
|
147
|
+
for (const [key, value] of Object.entries(
|
|
148
|
+
this.app.uiState.terrainVisualizationModes
|
|
149
|
+
)) {
|
|
150
|
+
const obj = batchGroup.getObjectByName(key);
|
|
151
|
+
if (obj) obj.visible = value;
|
|
152
|
+
}
|
|
153
|
+
// translate by the batch offset
|
|
154
|
+
const batch_offset = this.app.batchManager.getBatchOffset(i);
|
|
155
|
+
batchGroup.position.set(batch_offset.x, batch_offset.y, batch_offset.z);
|
|
156
|
+
this.group.add(batchGroup);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Normalize a value into [0, 1] against an explicit [min, max] range.
|
|
161
|
+
// Falls back to [0, 1] clamping when the range is missing or degenerate.
|
|
162
|
+
#normalizeToRange(value, min, max) {
|
|
163
|
+
if (
|
|
164
|
+
typeof min !== "number" ||
|
|
165
|
+
typeof max !== "number" ||
|
|
166
|
+
max - min === 0
|
|
167
|
+
) {
|
|
168
|
+
return Math.max(0, Math.min(1, value));
|
|
169
|
+
}
|
|
170
|
+
const normalized = (value - min) / (max - min);
|
|
171
|
+
return Math.max(0, Math.min(1, normalized));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
calculateNormalizedHeight(height) {
|
|
175
|
+
var normalizedHeight =
|
|
176
|
+
(height - this.bounds.minZ) / (this.bounds.maxZ - this.bounds.minZ);
|
|
177
|
+
normalizedHeight = Number.isFinite(normalizedHeight) ? normalizedHeight : 0;
|
|
178
|
+
normalizedHeight = Math.max(0, Math.min(1, normalizedHeight));
|
|
179
|
+
return normalizedHeight;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
*
|
|
184
|
+
* @param {array} heightData - Height data for the terrain, a flattened array
|
|
185
|
+
* @returns
|
|
186
|
+
*/
|
|
187
|
+
#createSurfaceGeometryFromHeightData(heightData, batchIndex) {
|
|
188
|
+
const { sizeX, sizeY, resolutionX, resolutionY } = this.dimensions;
|
|
189
|
+
const { minX, minY, maxX, maxY } = this.bounds;
|
|
190
|
+
// Create a plane geometry with the right number of segments
|
|
191
|
+
const geometry = new THREE.PlaneGeometry(
|
|
192
|
+
sizeX,
|
|
193
|
+
sizeY,
|
|
194
|
+
resolutionX - 1,
|
|
195
|
+
resolutionY - 1
|
|
196
|
+
);
|
|
197
|
+
// Center the geometry based on bounds
|
|
198
|
+
const centerX = (minX + maxX) / 2;
|
|
199
|
+
const centerY = (minY + maxY) / 2;
|
|
200
|
+
geometry.translate(centerX, centerY, 0);
|
|
201
|
+
// Get attributes for direct manipulation
|
|
202
|
+
const position = geometry.attributes.position;
|
|
203
|
+
// Create color buffer
|
|
204
|
+
const colorAttribute = new THREE.BufferAttribute(
|
|
205
|
+
new Float32Array(position.count * 3),
|
|
206
|
+
3
|
|
207
|
+
);
|
|
208
|
+
geometry.setAttribute("color", colorAttribute);
|
|
209
|
+
|
|
210
|
+
// Apply height data to geometry
|
|
211
|
+
// NOTE: THREE.js PlaneGeometry vertices are arranged in rows from bottom to top (Y increases)
|
|
212
|
+
const callableColormap = this.getCallableFromColorMapName(
|
|
213
|
+
this.app.uiState.terrainColorMap
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
for (let i = 0; i < position.count; i++) {
|
|
217
|
+
// Convert vertex index to grid coordinates
|
|
218
|
+
const col = i % resolutionX;
|
|
219
|
+
const invertedRow = Math.floor(i / resolutionX);
|
|
220
|
+
const row = resolutionY - invertedRow - 1; // Invert row index
|
|
221
|
+
// Calculate index in the flattened height data array
|
|
222
|
+
const dataIndex = row * resolutionX + col;
|
|
223
|
+
// Set Z coordinate (height)
|
|
224
|
+
position.setZ(i, heightData[dataIndex]);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Apply colors
|
|
228
|
+
this.#updateSurfaceColor(batchIndex, geometry, callableColormap);
|
|
229
|
+
|
|
230
|
+
// Make sure changes are applied
|
|
231
|
+
position.needsUpdate = true;
|
|
232
|
+
return geometry;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
getCallableFromColorMapName(cmapName) {
|
|
236
|
+
let reversed = false;
|
|
237
|
+
// Check if this is a reversed colormap request
|
|
238
|
+
if (cmapName.endsWith("_r")) {
|
|
239
|
+
cmapName = cmapName.substring(0, cmapName.length - 2);
|
|
240
|
+
reversed = true;
|
|
241
|
+
}
|
|
242
|
+
if (colorMapOptions.includes(cmapName))
|
|
243
|
+
return (value) => {
|
|
244
|
+
const [r, g, b] = evaluate_cmap(value, cmapName, reversed);
|
|
245
|
+
return new THREE.Color(r / 255, g / 255, b / 255);
|
|
246
|
+
};
|
|
247
|
+
console.log(
|
|
248
|
+
`Colormap ${cmapName} not found in colorMapOptions. Using default colormap instead.`
|
|
249
|
+
);
|
|
250
|
+
// Fallback
|
|
251
|
+
switch (cmapName) {
|
|
252
|
+
case "grayscale":
|
|
253
|
+
return (value) => new THREE.Color(value, value, value);
|
|
254
|
+
case "heatmap":
|
|
255
|
+
// Simple heatmap: blue->cyan->green->yellow->red
|
|
256
|
+
return (value) => {
|
|
257
|
+
if (value < 0.25) {
|
|
258
|
+
return new THREE.Color(0, value * 4, 1);
|
|
259
|
+
} else if (value < 0.5) {
|
|
260
|
+
return new THREE.Color(0, 1, 1 - (value - 0.25) * 4);
|
|
261
|
+
} else if (value < 0.75) {
|
|
262
|
+
return new THREE.Color((value - 0.5) * 4, 1, 0);
|
|
263
|
+
} else {
|
|
264
|
+
return new THREE.Color(1, 1 - (value - 0.75) * 4, 0);
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
case "terrain":
|
|
268
|
+
// Terrain color map - blues for low areas, greens for middle, browns/whites for high
|
|
269
|
+
return (value) => {
|
|
270
|
+
if (value < 0.2) {
|
|
271
|
+
return new THREE.Color(0.0, 0.2, 0.5 + value); // Deep to shallow water
|
|
272
|
+
} else if (value < 0.4) {
|
|
273
|
+
const t = (value - 0.2) * 5; // 0-1 within this range
|
|
274
|
+
return new THREE.Color(0.2 * t, 0.5 + 0.2 * t, 0.7 - 0.2 * t); // Shore transition
|
|
275
|
+
} else if (value < 0.75) {
|
|
276
|
+
const t = (value - 0.4) / 0.35; // 0-1 within this range
|
|
277
|
+
return new THREE.Color(0.2 + 0.3 * t, 0.7 - 0.2 * t, 0.5 - 0.4 * t); // Green to brown
|
|
278
|
+
} else {
|
|
279
|
+
const t = (value - 0.75) * 4; // 0-1 within this range
|
|
280
|
+
return new THREE.Color(0.5 + 0.5 * t, 0.5 + 0.5 * t, 0.1 + 0.9 * t); // Brown to white (snow)
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
default:
|
|
284
|
+
// Default blue to red gradient
|
|
285
|
+
return (value) => new THREE.Color(value, 0.2, 1 - value);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
*
|
|
291
|
+
* @param {array} heightData - Height data for the terrain, a flattened array
|
|
292
|
+
* @param {array} normals - Normal data for the terrain, an array of 3D vectors
|
|
293
|
+
* @returns {THREE.Group} - Group containing normal vectors
|
|
294
|
+
*/
|
|
295
|
+
#createNormalVectors(heightData, normals) {
|
|
296
|
+
const { sizeX, sizeY, resolutionX, resolutionY } = this.dimensions;
|
|
297
|
+
const { minX, minY } = this.bounds;
|
|
298
|
+
|
|
299
|
+
const normalVectors = new THREE.Group();
|
|
300
|
+
normalVectors.visible =
|
|
301
|
+
this.app.uiState.terrainVisualizationModes.normals || false;
|
|
302
|
+
|
|
303
|
+
// Create a helper arrow for each normal
|
|
304
|
+
const normalLength = TERRAIN_CONFIG.normalLength || 0.5;
|
|
305
|
+
const skipFactor = Math.max(
|
|
306
|
+
1,
|
|
307
|
+
Math.floor(resolutionX / TERRAIN_CONFIG.skipNormalCells)
|
|
308
|
+
); // Adaptive skip factor based on resolution
|
|
309
|
+
|
|
310
|
+
// Sample normals at regular intervals
|
|
311
|
+
for (let row = 0; row < resolutionY; row += skipFactor) {
|
|
312
|
+
for (let col = 0; col < resolutionX; col += skipFactor) {
|
|
313
|
+
const dataIndex = row * resolutionX + col;
|
|
314
|
+
|
|
315
|
+
if (dataIndex < heightData.length) {
|
|
316
|
+
// Calculate real-world coordinates
|
|
317
|
+
const x = minX + col * (sizeX / (resolutionX - 1));
|
|
318
|
+
const y = minY + row * (sizeY / (resolutionY - 1));
|
|
319
|
+
const z = heightData[dataIndex];
|
|
320
|
+
|
|
321
|
+
// Get normal data
|
|
322
|
+
let nx, ny, nz;
|
|
323
|
+
if (normals instanceof Float32Array) {
|
|
324
|
+
nx = normals[dataIndex * 3];
|
|
325
|
+
ny = normals[dataIndex * 3 + 1];
|
|
326
|
+
nz = normals[dataIndex * 3 + 2];
|
|
327
|
+
} else {
|
|
328
|
+
[nx, ny, nz] = normals[dataIndex];
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const origin = new THREE.Vector3(x, y, z);
|
|
332
|
+
const direction = new THREE.Vector3(nx, ny, nz);
|
|
333
|
+
|
|
334
|
+
const arrowHelper = new THREE.ArrowHelper(
|
|
335
|
+
direction.normalize(),
|
|
336
|
+
origin,
|
|
337
|
+
normalLength,
|
|
338
|
+
0xff0000
|
|
339
|
+
);
|
|
340
|
+
|
|
341
|
+
normalVectors.add(arrowHelper);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return normalVectors;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Toggle methods for visualizations - now updated to support batched terrains
|
|
349
|
+
toggleVisualization(type, visible) {
|
|
350
|
+
// Toggle in the original terrain
|
|
351
|
+
for (const batchGroup of this.group.children) {
|
|
352
|
+
const object = batchGroup.getObjectByName(type);
|
|
353
|
+
if (object) {
|
|
354
|
+
object.visible = visible;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
#updateSurfaceColor(batchIndex, geometry, callableColormap) {
|
|
360
|
+
if (!geometry) return;
|
|
361
|
+
const position = geometry.attributes.position;
|
|
362
|
+
const colorAttribute = geometry.attributes.color;
|
|
363
|
+
if (!position || !colorAttribute) return;
|
|
364
|
+
|
|
365
|
+
const { resolutionX, resolutionY } = this.dimensions;
|
|
366
|
+
const mode = this.app.uiState.terrainColorMode || "height";
|
|
367
|
+
|
|
368
|
+
// Update all colors based on the new colormap
|
|
369
|
+
for (let i = 0; i < position.count; i++) {
|
|
370
|
+
let value;
|
|
371
|
+
if (mode === "height") {
|
|
372
|
+
value = this.calculateNormalizedHeight(position.getZ(i));
|
|
373
|
+
} else {
|
|
374
|
+
const col = i % resolutionX;
|
|
375
|
+
const invertedRow = Math.floor(i / resolutionX);
|
|
376
|
+
const row = resolutionY - invertedRow - 1;
|
|
377
|
+
const dataIndex = row * resolutionX + col;
|
|
378
|
+
|
|
379
|
+
if (mode === "friction" && this.frictionData && this.frictionData[batchIndex]) {
|
|
380
|
+
// Normalize against the data range shipped in bounds (falls back to [0, 1]).
|
|
381
|
+
value = this.#normalizeToRange(
|
|
382
|
+
this.frictionData[batchIndex][dataIndex],
|
|
383
|
+
this.bounds.minFriction,
|
|
384
|
+
this.bounds.maxFriction
|
|
385
|
+
);
|
|
386
|
+
} else if (mode === "stiffness" && this.stiffnessData && this.stiffnessData[batchIndex]) {
|
|
387
|
+
// Normalize against the data range shipped in bounds (falls back to [0, 1]).
|
|
388
|
+
value = this.#normalizeToRange(
|
|
389
|
+
this.stiffnessData[batchIndex][dataIndex],
|
|
390
|
+
this.bounds.minStiffness,
|
|
391
|
+
this.bounds.maxStiffness
|
|
392
|
+
);
|
|
393
|
+
} else {
|
|
394
|
+
value = this.calculateNormalizedHeight(position.getZ(i));
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
const color = callableColormap(value);
|
|
398
|
+
colorAttribute.setXYZ(i, color.r, color.g, color.b);
|
|
399
|
+
}
|
|
400
|
+
// Update the buffer
|
|
401
|
+
colorAttribute.needsUpdate = true;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Update terrain colors with current colormap
|
|
405
|
+
setColorMap(colormapName) {
|
|
406
|
+
// Update the main terrain surface
|
|
407
|
+
const callableColormap = this.getCallableFromColorMapName(colormapName);
|
|
408
|
+
const batchesToUpdate = this.isSingleton ? 1 : this.app.batchManager.simBatches;
|
|
409
|
+
|
|
410
|
+
for (let i = 0; i < batchesToUpdate; i++) {
|
|
411
|
+
const batchGroup = this.group.getObjectByName(`batch${i}`);
|
|
412
|
+
const surfaceMesh = batchGroup.getObjectByName("surface");
|
|
413
|
+
const geometry = surfaceMesh.geometry;
|
|
414
|
+
this.#updateSurfaceColor(i, geometry, callableColormap);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Update terrain colors with current mode
|
|
419
|
+
setColorMode(mode) {
|
|
420
|
+
const callableColormap = this.getCallableFromColorMapName(this.app.uiState.terrainColorMap);
|
|
421
|
+
const batchesToUpdate = this.isSingleton ? 1 : this.app.batchManager.simBatches;
|
|
422
|
+
|
|
423
|
+
for (let i = 0; i < batchesToUpdate; i++) {
|
|
424
|
+
const batchGroup = this.group.getObjectByName(`batch${i}`);
|
|
425
|
+
const surfaceMesh = batchGroup.getObjectByName("surface");
|
|
426
|
+
const geometry = surfaceMesh.geometry;
|
|
427
|
+
this.#updateSurfaceColor(i, geometry, callableColormap);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
getAvailableColorModes() {
|
|
432
|
+
const modes = [];
|
|
433
|
+
if (this.heightData && this.heightData.length > 0) {
|
|
434
|
+
modes.push("height");
|
|
435
|
+
}
|
|
436
|
+
if (this.frictionData && this.frictionData.length > 0) {
|
|
437
|
+
modes.push("friction");
|
|
438
|
+
}
|
|
439
|
+
if (this.stiffnessData && this.stiffnessData.length > 0) {
|
|
440
|
+
modes.push("stiffness");
|
|
441
|
+
}
|
|
442
|
+
return modes;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
getPropertiesAt(x, y, batchIndex) {
|
|
446
|
+
const batchOffset = this.app.batchManager.getBatchOffset(batchIndex);
|
|
447
|
+
const localX = x - batchOffset.x;
|
|
448
|
+
const localY = y - batchOffset.y;
|
|
449
|
+
|
|
450
|
+
const { sizeX, sizeY, resolutionX, resolutionY } = this.dimensions;
|
|
451
|
+
const { minX, minY, maxX, maxY } = this.bounds;
|
|
452
|
+
|
|
453
|
+
if (localX < minX || localX > maxX || localY < minY || localY > maxY) {
|
|
454
|
+
return null;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const col = Math.round(((localX - minX) / sizeX) * (resolutionX - 1));
|
|
458
|
+
const row = Math.round(((localY - minY) / sizeY) * (resolutionY - 1));
|
|
459
|
+
|
|
460
|
+
if (col < 0 || col >= resolutionX || row < 0 || row >= resolutionY) return null;
|
|
461
|
+
|
|
462
|
+
const dataIndex = row * resolutionX + col;
|
|
463
|
+
const props = {};
|
|
464
|
+
|
|
465
|
+
const dataBatchIndex = this.isSingleton ? 0 : batchIndex;
|
|
466
|
+
|
|
467
|
+
if (this.heightData && this.heightData[dataBatchIndex]) {
|
|
468
|
+
props.height = this.heightData[dataBatchIndex][dataIndex];
|
|
469
|
+
} else {
|
|
470
|
+
return null;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
if (this.frictionData && this.frictionData[dataBatchIndex]) {
|
|
474
|
+
props.friction = this.frictionData[dataBatchIndex][dataIndex];
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
if (this.stiffnessData && this.stiffnessData[dataBatchIndex]) {
|
|
478
|
+
props.stiffness = this.stiffnessData[dataBatchIndex][dataIndex];
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
return props;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Get THREE.js group containing all visualizations
|
|
485
|
+
getObject3D() {
|
|
486
|
+
return this.group;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Clean up resources when terrain is no longer needed
|
|
490
|
+
dispose() {
|
|
491
|
+
const geometries = new Set();
|
|
492
|
+
const materials = new Set();
|
|
493
|
+
|
|
494
|
+
for (const batchGroup of this.group.children) {
|
|
495
|
+
batchGroup.traverse((child) => {
|
|
496
|
+
if (child.geometry) geometries.add(child.geometry);
|
|
497
|
+
if (child.material) {
|
|
498
|
+
if (Array.isArray(child.material)) {
|
|
499
|
+
child.material.forEach((m) => materials.add(m));
|
|
500
|
+
} else {
|
|
501
|
+
materials.add(child.material);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
});
|
|
505
|
+
if (this.app && this.app.scene) {
|
|
506
|
+
this.app.scene.removeObject3D(batchGroup);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
geometries.forEach((g) => g.dispose());
|
|
511
|
+
materials.forEach((m) => m.dispose());
|
|
512
|
+
this.group = null;
|
|
513
|
+
}
|
|
514
|
+
}
|