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,116 @@
|
|
|
1
|
+
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
|
|
2
|
+
import { CONTROLS_CONFIG, SELECTION_CONFIG } from "../config.js";
|
|
3
|
+
|
|
4
|
+
export function setupControls(camera, renderer) {
|
|
5
|
+
const controls = new OrbitControls(camera, renderer.domElement);
|
|
6
|
+
controls.listenToKeyEvents(window);
|
|
7
|
+
|
|
8
|
+
// Apply basic configuration
|
|
9
|
+
Object.assign(controls, CONTROLS_CONFIG);
|
|
10
|
+
|
|
11
|
+
// Add custom event handlers
|
|
12
|
+
setupControlEvents(controls);
|
|
13
|
+
|
|
14
|
+
return controls;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function setupControlEvents(controls) {
|
|
18
|
+
let activeSelectionMode = null;
|
|
19
|
+
|
|
20
|
+
// Prevent OrbitControls from interfering during selection
|
|
21
|
+
const keyDownHandler = (event) => {
|
|
22
|
+
const keyPressed = event.key.toLowerCase().replace("control", "ctrl");
|
|
23
|
+
|
|
24
|
+
// Check if the pressed key matches any selection mode
|
|
25
|
+
for (const [mode, config] of Object.entries(SELECTION_CONFIG)) {
|
|
26
|
+
if (keyPressed === config.key) {
|
|
27
|
+
activeSelectionMode = mode;
|
|
28
|
+
controls.enabled = false; // Disable controls
|
|
29
|
+
document.body.style.cursor = "crosshair"; // Change cursor to indicate selection mode
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const keyUpHandler = (event) => {
|
|
36
|
+
const keyReleased = event.key.toLowerCase().replace("control", "ctrl");
|
|
37
|
+
// Check if the released key matches the active selection mode
|
|
38
|
+
for (const [mode, config] of Object.entries(SELECTION_CONFIG)) {
|
|
39
|
+
if (keyReleased === config.key && activeSelectionMode === mode) {
|
|
40
|
+
activeSelectionMode = null;
|
|
41
|
+
controls.enabled = true; // Enable controls
|
|
42
|
+
document.body.style.cursor = "auto"; // Reset cursor
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// Add event listeners for keypress
|
|
49
|
+
window.addEventListener("keydown", keyDownHandler);
|
|
50
|
+
window.addEventListener("keyup", keyUpHandler);
|
|
51
|
+
|
|
52
|
+
// Update cursor during drag
|
|
53
|
+
controls.addEventListener("start", () => {
|
|
54
|
+
if (!activeSelectionMode) {
|
|
55
|
+
document.body.style.cursor = "grabbing";
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
controls.addEventListener("end", () => {
|
|
60
|
+
if (!activeSelectionMode) {
|
|
61
|
+
document.body.style.cursor = "auto";
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Clean up function to remove event listeners
|
|
66
|
+
const originalDispose = controls.dispose.bind(controls);
|
|
67
|
+
controls.dispose = () => {
|
|
68
|
+
window.removeEventListener("keydown", keyDownHandler);
|
|
69
|
+
window.removeEventListener("keyup", keyUpHandler);
|
|
70
|
+
if (typeof originalDispose === "function") {
|
|
71
|
+
originalDispose();
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// Optional: Add more custom event handlers if needed
|
|
76
|
+
controls.addEventListener("change", () => {
|
|
77
|
+
// Handle control changes
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Helper functions for direct control manipulation
|
|
82
|
+
export function zoomToFit(controls, boundingSphere, offset = 1.5) {
|
|
83
|
+
const fov = controls.object.fov * (Math.PI / 180);
|
|
84
|
+
const distance = (boundingSphere.radius / Math.sin(fov / 2)) * offset;
|
|
85
|
+
|
|
86
|
+
controls.object.position.copy(boundingSphere.center);
|
|
87
|
+
controls.object.position.z += distance;
|
|
88
|
+
controls.target.copy(boundingSphere.center);
|
|
89
|
+
controls.update();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function resetControls(controls) {
|
|
93
|
+
controls.reset();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function updateControlsConfig(controls, config) {
|
|
97
|
+
Object.assign(controls, config);
|
|
98
|
+
controls.update();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Optional: Animation functions
|
|
102
|
+
export function enableAutoRotate(controls, speed = 2.0) {
|
|
103
|
+
controls.autoRotate = true;
|
|
104
|
+
controls.autoRotateSpeed = speed;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function disableAutoRotate(controls) {
|
|
108
|
+
controls.autoRotate = false;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Cleanup function
|
|
112
|
+
export function disposeControls(controls) {
|
|
113
|
+
if (controls) {
|
|
114
|
+
controls.dispose();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import * as THREE from "three";
|
|
2
|
+
import { setupControls } from "./InteractionControls.js";
|
|
3
|
+
import {
|
|
4
|
+
SCENE_CONFIG,
|
|
5
|
+
RENDERER_CONFIG,
|
|
6
|
+
CAMERA_CONFIG,
|
|
7
|
+
LIGHTING_CONFIG,
|
|
8
|
+
} from "../config.js";
|
|
9
|
+
|
|
10
|
+
export class Scene {
|
|
11
|
+
constructor(app) {
|
|
12
|
+
this.app = app;
|
|
13
|
+
this.scene = new THREE.Scene();
|
|
14
|
+
this.initScene();
|
|
15
|
+
this.minRenderDelay = 1000 / 60; // 60 FPS
|
|
16
|
+
this.lastRenderTime = Number.NEGATIVE_INFINITY;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
initScene() {
|
|
20
|
+
THREE.Object3D.DEFAULT_UP.set(...SCENE_CONFIG.defaultUp);
|
|
21
|
+
const renderer = this.createRenderer();
|
|
22
|
+
const camera = this.createCamera();
|
|
23
|
+
const controls = setupControls(camera, renderer);
|
|
24
|
+
this.setupLighting(this.scene);
|
|
25
|
+
const light = new THREE.DirectionalLight(0xffffff, 1);
|
|
26
|
+
light.position.set(10, 10, 10);
|
|
27
|
+
this.scene.add(light);
|
|
28
|
+
this.renderer = renderer;
|
|
29
|
+
this.camera = camera;
|
|
30
|
+
this.controls = controls;
|
|
31
|
+
this.setupWindowHandlers();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
setupWindowHandlers() {
|
|
35
|
+
window.addEventListener("resize", () => this.#handleWindowResize());
|
|
36
|
+
window.addEventListener("error", (event) => this.#handleError(event));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
#handleWindowResize() {
|
|
40
|
+
this.camera.aspect = window.innerWidth / window.innerHeight;
|
|
41
|
+
this.camera.updateProjectionMatrix();
|
|
42
|
+
this.renderer.setPixelRatio(window.devicePixelRatio);
|
|
43
|
+
this.renderer.setSize(window.innerWidth, window.innerHeight, false);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
#handleError(event) {
|
|
47
|
+
console.error("Application Error:", {
|
|
48
|
+
message: event.message,
|
|
49
|
+
source: event.filename,
|
|
50
|
+
lineNumber: event.lineno,
|
|
51
|
+
columnNumber: event.colno,
|
|
52
|
+
error: event.error,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
createRenderer() {
|
|
57
|
+
const renderer = new THREE.WebGLRenderer({
|
|
58
|
+
antialias: RENDERER_CONFIG.antialias,
|
|
59
|
+
preserveDrawingBuffer: RENDERER_CONFIG.preserveDrawingBuffer,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
renderer.setPixelRatio(RENDERER_CONFIG.pixelRatio);
|
|
63
|
+
renderer.setSize(window.innerWidth, window.innerHeight);
|
|
64
|
+
renderer.setClearColor(
|
|
65
|
+
RENDERER_CONFIG.clearColor,
|
|
66
|
+
RENDERER_CONFIG.clearAlpha
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
document.body.appendChild(renderer.domElement);
|
|
70
|
+
return renderer;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
createCamera() {
|
|
74
|
+
const camera = new THREE.PerspectiveCamera(
|
|
75
|
+
CAMERA_CONFIG.fov,
|
|
76
|
+
window.innerWidth / window.innerHeight,
|
|
77
|
+
CAMERA_CONFIG.near,
|
|
78
|
+
CAMERA_CONFIG.far
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
camera.position.set(...CAMERA_CONFIG.position);
|
|
82
|
+
camera.up.set(...CAMERA_CONFIG.up);
|
|
83
|
+
|
|
84
|
+
return camera;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
setupLighting(scene) {
|
|
88
|
+
// Ambient light
|
|
89
|
+
const ambientLight = new THREE.AmbientLight(
|
|
90
|
+
LIGHTING_CONFIG.ambient.color,
|
|
91
|
+
LIGHTING_CONFIG.ambient.intensity
|
|
92
|
+
);
|
|
93
|
+
scene.add(ambientLight);
|
|
94
|
+
|
|
95
|
+
// Directional light
|
|
96
|
+
const directionalLight = new THREE.DirectionalLight(
|
|
97
|
+
LIGHTING_CONFIG.directional.color,
|
|
98
|
+
LIGHTING_CONFIG.directional.intensity
|
|
99
|
+
);
|
|
100
|
+
directionalLight.position.set(...LIGHTING_CONFIG.directional.position);
|
|
101
|
+
scene.add(directionalLight);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
addObject3D(object) {
|
|
105
|
+
this.scene.add(object);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
removeObject3D(object) {
|
|
109
|
+
this.scene.remove(object);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
animate(now) {
|
|
113
|
+
if (now - this.lastRenderTime < this.minRenderDelay) return;
|
|
114
|
+
|
|
115
|
+
if (this.app.uiState && this.app.uiState.splitScreen && this.app.batchManager && this.app.batchManager.simBatches >= 2) {
|
|
116
|
+
const batchA = this.app.uiState.splitBatchA !== undefined ? this.app.uiState.splitBatchA : 0;
|
|
117
|
+
const batchB = this.app.uiState.splitBatchB !== undefined ? this.app.uiState.splitBatchB : 1;
|
|
118
|
+
|
|
119
|
+
const activeBatch = this.app.batchManager.currentlyActiveBatch;
|
|
120
|
+
const activeOffset = this.app.batchManager.getBatchOffset(activeBatch);
|
|
121
|
+
const offsetA = this.app.batchManager.getBatchOffset(batchA);
|
|
122
|
+
const offsetB = this.app.batchManager.getBatchOffset(batchB);
|
|
123
|
+
|
|
124
|
+
const deltaA = new THREE.Vector3(offsetA.x - activeOffset.x, offsetA.y - activeOffset.y, offsetA.z - activeOffset.z);
|
|
125
|
+
const deltaB = new THREE.Vector3(offsetB.x - activeOffset.x, offsetB.y - activeOffset.y, offsetB.z - activeOffset.z);
|
|
126
|
+
|
|
127
|
+
const width = window.innerWidth;
|
|
128
|
+
const height = window.innerHeight;
|
|
129
|
+
const aspectFull = width / height;
|
|
130
|
+
const aspectSplit = (width / 2) / height;
|
|
131
|
+
|
|
132
|
+
this.renderer.setScissorTest(true);
|
|
133
|
+
this.camera.aspect = aspectSplit;
|
|
134
|
+
this.camera.updateProjectionMatrix();
|
|
135
|
+
|
|
136
|
+
// Render Left (Batch A)
|
|
137
|
+
this.renderer.setViewport(0, 0, width / 2, height);
|
|
138
|
+
this.renderer.setScissor(0, 0, width / 2, height);
|
|
139
|
+
this.camera.position.add(deltaA);
|
|
140
|
+
this.camera.updateMatrixWorld();
|
|
141
|
+
this.renderer.render(this.scene, this.camera);
|
|
142
|
+
this.camera.position.sub(deltaA);
|
|
143
|
+
|
|
144
|
+
// Render Right (Batch B)
|
|
145
|
+
this.renderer.setViewport(width / 2, 0, width / 2, height);
|
|
146
|
+
this.renderer.setScissor(width / 2, 0, width / 2, height);
|
|
147
|
+
this.camera.position.add(deltaB);
|
|
148
|
+
this.camera.updateMatrixWorld();
|
|
149
|
+
this.renderer.render(this.scene, this.camera);
|
|
150
|
+
this.camera.position.sub(deltaB);
|
|
151
|
+
|
|
152
|
+
// Restore
|
|
153
|
+
this.camera.aspect = aspectFull;
|
|
154
|
+
this.camera.updateProjectionMatrix();
|
|
155
|
+
this.renderer.setScissorTest(false);
|
|
156
|
+
this.renderer.setViewport(0, 0, width, height);
|
|
157
|
+
this.renderer.setScissor(0, 0, width, height);
|
|
158
|
+
} else {
|
|
159
|
+
this.renderer.setScissorTest(false);
|
|
160
|
+
this.renderer.setViewport(0, 0, window.innerWidth, window.innerHeight);
|
|
161
|
+
this.renderer.render(this.scene, this.camera);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
this.lastRenderTime = now;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
// Abstraction over the two wire shapes `/states` can come back as (see
|
|
2
|
+
// server.py::_columnarize_states and README.md "Binary state fields"):
|
|
3
|
+
//
|
|
4
|
+
// - legacy: a plain JSON array of per-frame state objects, exactly as
|
|
5
|
+
// before -- kept working for scenes the server can't safely columnarize.
|
|
6
|
+
// - columnar (wire format v4): `{times, bodies, scalars}` where each body's
|
|
7
|
+
// numeric fields and each scalar are a single whole-trajectory Float32Array
|
|
8
|
+
// (shape (T, B, k) / (T, B), row-major) fetched from a `/blob/...` URL.
|
|
9
|
+
//
|
|
10
|
+
// Consumers (AnimationController, SimView, ScalarPlotter, ...) only ever see
|
|
11
|
+
// this common API, so the rest of the viewer doesn't need to know which wire
|
|
12
|
+
// shape the current scene used.
|
|
13
|
+
|
|
14
|
+
// Per-body numeric fields packed as whole-trajectory (T, B, k) blobs, with
|
|
15
|
+
// their trailing per-batch-row width. Matches STATE_FIELD_WIDTHS in
|
|
16
|
+
// blobCodec.js (the per-frame equivalent) and _STATE_FIELD_WIDTHS in
|
|
17
|
+
// server.py.
|
|
18
|
+
const COLUMNAR_FIELD_WIDTHS = {
|
|
19
|
+
bodyTransform: 7,
|
|
20
|
+
velocity: 3,
|
|
21
|
+
angularVelocity: 3,
|
|
22
|
+
force: 3,
|
|
23
|
+
torque: 3,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
class ColumnarStateStore {
|
|
27
|
+
// `bodies`: [{name, fields: {fieldName: Float32Array}, contacts?}], each
|
|
28
|
+
// fields Float32Array already reshaped to flat (T * B * k) row-major.
|
|
29
|
+
// `scalars`: {scalarName: Float32Array}, each flat (T * B) row-major.
|
|
30
|
+
constructor(times, bodies, scalars, simBatches) {
|
|
31
|
+
this.times = times;
|
|
32
|
+
this._bodies = bodies;
|
|
33
|
+
this._scalars = scalars;
|
|
34
|
+
this.simBatches = simBatches;
|
|
35
|
+
// Single-frame memo: playback only ever touches one frame at a time,
|
|
36
|
+
// so materializing the whole timeline into legacy-shaped objects up
|
|
37
|
+
// front would defeat the point of storing it columnar in the first place.
|
|
38
|
+
this._memoIndex = -1;
|
|
39
|
+
this._memoFrame = null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
get length() {
|
|
43
|
+
return this.times.length;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
timeAt(i) {
|
|
47
|
+
return this.times[i];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
lastTime() {
|
|
51
|
+
return this.times[this.times.length - 1];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Slices one body's field at frame `i` into an array of per-batch rows,
|
|
55
|
+
// e.g. bodyTransform -> [[x,y,z,w,qx,qy,qz], ...] (one row per batch).
|
|
56
|
+
_sliceField(flatArray, width, i) {
|
|
57
|
+
const B = this.simBatches;
|
|
58
|
+
const base = i * B * width;
|
|
59
|
+
const rows = new Array(B);
|
|
60
|
+
for (let b = 0; b < B; b++) {
|
|
61
|
+
const rowBase = base + b * width;
|
|
62
|
+
const row = new Array(width);
|
|
63
|
+
for (let c = 0; c < width; c++) row[c] = flatArray[rowBase + c];
|
|
64
|
+
rows[b] = row;
|
|
65
|
+
}
|
|
66
|
+
return rows;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
getFrame(i) {
|
|
70
|
+
if (i === this._memoIndex) return this._memoFrame;
|
|
71
|
+
|
|
72
|
+
const state = { time: this.times[i], bodies: [] };
|
|
73
|
+
for (const body of this._bodies) {
|
|
74
|
+
const bodyState = { name: body.name };
|
|
75
|
+
for (const field in body.fields) {
|
|
76
|
+
bodyState[field] = this._sliceField(
|
|
77
|
+
body.fields[field],
|
|
78
|
+
COLUMNAR_FIELD_WIDTHS[field],
|
|
79
|
+
i
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
if (body.contacts) {
|
|
83
|
+
bodyState.contacts = body.contacts[i] ?? null;
|
|
84
|
+
}
|
|
85
|
+
state.bodies.push(bodyState);
|
|
86
|
+
}
|
|
87
|
+
for (const name in this._scalars) {
|
|
88
|
+
const flat = this._scalars[name];
|
|
89
|
+
const B = this.simBatches;
|
|
90
|
+
const base = i * B;
|
|
91
|
+
state[name] = Array.from(flat.subarray(base, base + B));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
this._memoIndex = i;
|
|
95
|
+
this._memoFrame = state;
|
|
96
|
+
return state;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Per-batch series for the scalar plotter: [{x: time, y: value}, ...] per batch.
|
|
100
|
+
// `simBatches` is accepted (and ignored) for API parity with
|
|
101
|
+
// LegacyStateStore.getScalarSeries -- the columnar store already knows
|
|
102
|
+
// its own batch count.
|
|
103
|
+
getScalarSeries(name, _simBatches) {
|
|
104
|
+
const flat = this._scalars[name];
|
|
105
|
+
const B = this.simBatches;
|
|
106
|
+
const series = new Array(B).fill().map(() => new Array(this.length));
|
|
107
|
+
for (let i = 0; i < this.length; i++) {
|
|
108
|
+
const base = i * B;
|
|
109
|
+
for (let b = 0; b < B; b++) {
|
|
110
|
+
series[b][i] = { x: this.times[i], y: flat[base + b] };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return series;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
class LegacyStateStore {
|
|
118
|
+
// `statesArray`: the classic array of per-frame state objects (already
|
|
119
|
+
// decoded -- __b64__ fields expanded via decodeStatesChunk).
|
|
120
|
+
constructor(statesArray) {
|
|
121
|
+
this._states = statesArray;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// simBatches isn't known to the store itself in the legacy shape (each
|
|
125
|
+
// state object carries its own per-batch arrays without a fixed count
|
|
126
|
+
// recorded anywhere) -- callers pass it explicitly to getScalarSeries,
|
|
127
|
+
// matching how ScalarPlotter/ErrorMetrics already get it from
|
|
128
|
+
// app.batchManager.simBatches.
|
|
129
|
+
|
|
130
|
+
get length() {
|
|
131
|
+
return this._states.length;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Recomputed on every access rather than cached, since `append()` can grow
|
|
135
|
+
// `_states` after this store is already in use (live streaming) -- plain
|
|
136
|
+
// arrays are cheap enough here that a stale cache isn't worth the risk.
|
|
137
|
+
get times() {
|
|
138
|
+
return this._states.map((s) => s.time);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
timeAt(i) {
|
|
142
|
+
return this._states[i].time;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
lastTime() {
|
|
146
|
+
return this._states[this._states.length - 1].time;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
getFrame(i) {
|
|
150
|
+
return this._states[i];
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
getScalarSeries(name, simBatches) {
|
|
154
|
+
const series = new Array(simBatches).fill().map(() => []);
|
|
155
|
+
for (const state of this._states) {
|
|
156
|
+
const values = state[name];
|
|
157
|
+
if (values === undefined) {
|
|
158
|
+
throw new Error(`Scalar "${name}" not found in state.`);
|
|
159
|
+
}
|
|
160
|
+
for (let b = 0; b < simBatches; b++) {
|
|
161
|
+
if (values[b] === undefined) {
|
|
162
|
+
throw new Error(`Scalar "${name}" not found in state at index ${b}.`);
|
|
163
|
+
}
|
|
164
|
+
series[b].push({ x: state.time, y: values[b] });
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return series;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Grows the store in place with an already-decoded chunk of new frames
|
|
171
|
+
// (live-streaming support -- see SimView.processStatesChunk). Returns the
|
|
172
|
+
// index of the first newly-appended frame.
|
|
173
|
+
append(chunk) {
|
|
174
|
+
const startIndex = this._states.length;
|
|
175
|
+
this._states.push(...chunk);
|
|
176
|
+
return startIndex;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export class StateStore {
|
|
181
|
+
static fromColumnar({ times, bodies, scalars }, simBatches) {
|
|
182
|
+
return new ColumnarStateStore(times, bodies, scalars, simBatches);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
static fromLegacy(statesArray) {
|
|
186
|
+
return new LegacyStateStore(statesArray);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
export const UI_DEFAULT_CONFIG = {
|
|
2
|
+
bodyVisualizationMode: "points",
|
|
3
|
+
axesVisible: false,
|
|
4
|
+
trailsVisible: false,
|
|
5
|
+
smoothInterpolation: true,
|
|
6
|
+
attributeVisible: {
|
|
7
|
+
contacts: false,
|
|
8
|
+
velocity: false,
|
|
9
|
+
angularVelocity: false,
|
|
10
|
+
force: false,
|
|
11
|
+
torque: false,
|
|
12
|
+
},
|
|
13
|
+
terrainVisualizationModes: {
|
|
14
|
+
surface: true,
|
|
15
|
+
wireframe: true,
|
|
16
|
+
normals: false,
|
|
17
|
+
},
|
|
18
|
+
terrainColorMap: "magma", // Default colormap
|
|
19
|
+
terrainColorMode: "height", // height, friction, or stiffness
|
|
20
|
+
terrainProbe: false, // interactive terrain probe on click
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const FREQ_CONFIG = {
|
|
24
|
+
scene: 60, // Scene update frequency in Hz
|
|
25
|
+
scalarPlotter: 20,
|
|
26
|
+
bodyStateWindow: 30,
|
|
27
|
+
playbackControls: 20,
|
|
28
|
+
errorMetrics: 20,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const CONTROLS_CONFIG = {
|
|
32
|
+
minDistance: 1,
|
|
33
|
+
maxDistance: 500,
|
|
34
|
+
enableDamping: false,
|
|
35
|
+
dampingFactor: 0.05,
|
|
36
|
+
screenSpacePanning: true,
|
|
37
|
+
maxPolarAngle: Math.PI,
|
|
38
|
+
enablePan: true,
|
|
39
|
+
panSpeed: 2.0,
|
|
40
|
+
rotateSpeed: 1.5,
|
|
41
|
+
zoomSpeed: 1.2,
|
|
42
|
+
listenToKeyEvents: true,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const SCENE_CONFIG = {
|
|
46
|
+
defaultUp: [0, 0, 1],
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export const RENDERER_CONFIG = {
|
|
50
|
+
antialias: true,
|
|
51
|
+
preserveDrawingBuffer: true,
|
|
52
|
+
pixelRatio: window.devicePixelRatio,
|
|
53
|
+
clearColor: 0x000000,
|
|
54
|
+
clearAlpha: 1.0,
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const CAMERA_CONFIG = {
|
|
58
|
+
fov: 40,
|
|
59
|
+
near: 0.1,
|
|
60
|
+
far: 500,
|
|
61
|
+
position: [-15, -15, 10],
|
|
62
|
+
up: [0, 0, 1],
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export const LIGHTING_CONFIG = {
|
|
66
|
+
ambient: {
|
|
67
|
+
color: 0xffffff,
|
|
68
|
+
intensity: 0.9,
|
|
69
|
+
},
|
|
70
|
+
directional: {
|
|
71
|
+
color: 0xffffff,
|
|
72
|
+
intensity: 0.8,
|
|
73
|
+
position: [10, 10, 10],
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export const GROUND_CONFIG = {
|
|
78
|
+
size: 1000, // Size of the ground plane
|
|
79
|
+
divisions: 100, // Number of grid divisions
|
|
80
|
+
mainColor: 0x444444, // Main ground color
|
|
81
|
+
gridColor: 0x888888, // Grid line color
|
|
82
|
+
position: [0, 0, 0], // Position of the ground plane
|
|
83
|
+
rotation: [0, 0, 0], // Rotation of the ground plane
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export const TERRAIN_CONFIG = {
|
|
87
|
+
skipNormalCells: 10,
|
|
88
|
+
normalLength: 0.2,
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export const BODY_CONFIG = {
|
|
92
|
+
geometry: {
|
|
93
|
+
box: {
|
|
94
|
+
widthSegments: 4,
|
|
95
|
+
heightSegments: 4,
|
|
96
|
+
depthSegments: 4,
|
|
97
|
+
},
|
|
98
|
+
sphere: {
|
|
99
|
+
widthSegments: 16,
|
|
100
|
+
heightSegments: 16,
|
|
101
|
+
},
|
|
102
|
+
cylinder: {
|
|
103
|
+
radialSegments: 32,
|
|
104
|
+
heightSegments: 4,
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
wireframe: {
|
|
108
|
+
color: 0x4080ff,
|
|
109
|
+
},
|
|
110
|
+
points: {
|
|
111
|
+
size: 0.1,
|
|
112
|
+
opacity: 0.7,
|
|
113
|
+
alphaTest: 0.5,
|
|
114
|
+
transparent: false,
|
|
115
|
+
texture: "static/textures/points/ball1.png",
|
|
116
|
+
},
|
|
117
|
+
contactPoints: {
|
|
118
|
+
size: 0.7,
|
|
119
|
+
opacity: 1.0,
|
|
120
|
+
alphaTest: 0.5,
|
|
121
|
+
transparent: false,
|
|
122
|
+
texture: "static/textures/contacts/red-cross0.png",
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
export const SCALAR_PLOTTER_CONFIG = {
|
|
127
|
+
stepsPerYAxis: 5,
|
|
128
|
+
inactiveBatchOpacity: 0.3,
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
export const TRAIL_CONFIG = {
|
|
132
|
+
opacity: 0.6,
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
export const BODY_VECTOR_CONFIG = {
|
|
136
|
+
// Linear velocity
|
|
137
|
+
velocity: {
|
|
138
|
+
color: 0x2ca02c,
|
|
139
|
+
scale: 1.0,
|
|
140
|
+
},
|
|
141
|
+
// Angular velocity
|
|
142
|
+
angularVelocity: {
|
|
143
|
+
color: 0xffff00,
|
|
144
|
+
scale: 1.0,
|
|
145
|
+
},
|
|
146
|
+
// Force
|
|
147
|
+
force: {
|
|
148
|
+
color: 0xff7f0e,
|
|
149
|
+
scale: 1.0,
|
|
150
|
+
},
|
|
151
|
+
// Torque
|
|
152
|
+
torque: {
|
|
153
|
+
color: 0xd62728,
|
|
154
|
+
scale: 1.0,
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
export const POINT_VECTOR_CONFIG = {
|
|
159
|
+
// Contact Normal (n)
|
|
160
|
+
contactNormal: {
|
|
161
|
+
color: 0xff0000,
|
|
162
|
+
scale: 0.5,
|
|
163
|
+
visible: false,
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
export const CONTACT_CONFIG = {
|
|
168
|
+
points: {
|
|
169
|
+
size: 0.5,
|
|
170
|
+
opacity: 1.0,
|
|
171
|
+
texture: "static/textures/contacts/red-cross0.png",
|
|
172
|
+
transparent: false,
|
|
173
|
+
},
|
|
174
|
+
normals: {
|
|
175
|
+
color: 0xff0000,
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
export const SELECTION_CONFIG = {
|
|
180
|
+
BODIES: {
|
|
181
|
+
key: "ctrl",
|
|
182
|
+
set: "selectedBodies",
|
|
183
|
+
objects: "bodies",
|
|
184
|
+
},
|
|
185
|
+
CONTACT_POINTS: {
|
|
186
|
+
key: "Alt",
|
|
187
|
+
set: "selectedContactPoints",
|
|
188
|
+
objects: "contactPoints",
|
|
189
|
+
},
|
|
190
|
+
BATCH: {
|
|
191
|
+
key: "shiftKey",
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
export const BATCH_PALETTE_GENERATION_CONFIG = {
|
|
196
|
+
colors: ["#00429d", "#96ffea", "#ff40e0", "#ffffe0", "#ff005e", "#93003a"],
|
|
197
|
+
correctLightness: true,
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
export const STATIC_OBJECT_CONFIG = {
|
|
201
|
+
geometry: {
|
|
202
|
+
box: {
|
|
203
|
+
widthSegments: 4,
|
|
204
|
+
heightSegments: 4,
|
|
205
|
+
depthSegments: 4,
|
|
206
|
+
},
|
|
207
|
+
sphere: {
|
|
208
|
+
widthSegments: 16,
|
|
209
|
+
heightSegments: 16,
|
|
210
|
+
},
|
|
211
|
+
cylinder: {
|
|
212
|
+
radialSegments: 32,
|
|
213
|
+
heightSegments: 4,
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
wireframe: {
|
|
217
|
+
color: 0x4080ff,
|
|
218
|
+
},
|
|
219
|
+
points: {
|
|
220
|
+
size: 0.2,
|
|
221
|
+
opacity: 0.7,
|
|
222
|
+
alphaTest: 0.5,
|
|
223
|
+
transparent: false,
|
|
224
|
+
texture: "static/textures/points/ball1.png",
|
|
225
|
+
},
|
|
226
|
+
contactPoints: {
|
|
227
|
+
size: 0.7,
|
|
228
|
+
opacity: 1.0,
|
|
229
|
+
alphaTest: 0.5,
|
|
230
|
+
transparent: false,
|
|
231
|
+
texture: "static/textures/contacts/red-cross0.png",
|
|
232
|
+
},
|
|
233
|
+
};
|