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.
Files changed (163) hide show
  1. simview/__init__.py +69 -0
  2. simview/__main__.py +174 -0
  3. simview/launcher.py +109 -0
  4. simview/live.py +204 -0
  5. simview/merge.py +593 -0
  6. simview/model.py +690 -0
  7. simview/py.typed +0 -0
  8. simview/scene.py +603 -0
  9. simview/server.py +739 -0
  10. simview/state.py +190 -0
  11. simview/static/css/controls.css +36 -0
  12. simview/static/js/SimView.js +601 -0
  13. simview/static/js/components/AnimationController.js +654 -0
  14. simview/static/js/components/BatchManager.js +198 -0
  15. simview/static/js/components/InteractionController.js +446 -0
  16. simview/static/js/components/InteractionControls.js +116 -0
  17. simview/static/js/components/Scene.js +166 -0
  18. simview/static/js/components/StateStore.js +188 -0
  19. simview/static/js/config.js +233 -0
  20. simview/static/js/main.js +3 -0
  21. simview/static/js/objects/Body.js +616 -0
  22. simview/static/js/objects/StaticObject.js +181 -0
  23. simview/static/js/objects/Terrain.js +514 -0
  24. simview/static/js/objects/utils.js +378 -0
  25. simview/static/js/ui/AnalysisPanel.js +233 -0
  26. simview/static/js/ui/BatchLegend.js +207 -0
  27. simview/static/js/ui/BodyStateWindow.js +521 -0
  28. simview/static/js/ui/Controls.js +586 -0
  29. simview/static/js/ui/ErrorMetrics.js +664 -0
  30. simview/static/js/ui/Legend.js +87 -0
  31. simview/static/js/ui/PlaybackControls.js +314 -0
  32. simview/static/js/ui/ScalarPlotter.js +575 -0
  33. simview/static/js/utils/blobCodec.js +73 -0
  34. simview/static/js/utils/bodyTransforms.js +151 -0
  35. simview/static/js/utils/csv.js +46 -0
  36. simview/static/js/utils/errorMath.js +69 -0
  37. simview/static/js/utils/injectStyles.js +16 -0
  38. simview/static/js/utils/interpolate.js +65 -0
  39. simview/static/js/utils/liveFollow.js +17 -0
  40. simview/static/js/utils/loadRecordingLibs.js +34 -0
  41. simview/static/js/utils/viewState.js +226 -0
  42. simview/static/lib/chroma-js-3.1.2/index.min.js +8 -0
  43. simview/static/lib/chroma-js-3.1.2/src/Color.js +49 -0
  44. simview/static/lib/chroma-js-3.1.2/src/chroma.js +10 -0
  45. simview/static/lib/chroma-js-3.1.2/src/colors/colorbrewer.js +81 -0
  46. simview/static/lib/chroma-js-3.1.2/src/colors/w3cx11.js +164 -0
  47. simview/static/lib/chroma-js-3.1.2/src/generator/average.js +88 -0
  48. simview/static/lib/chroma-js-3.1.2/src/generator/bezier.js +86 -0
  49. simview/static/lib/chroma-js-3.1.2/src/generator/blend.js +57 -0
  50. simview/static/lib/chroma-js-3.1.2/src/generator/cubehelix.js +87 -0
  51. simview/static/lib/chroma-js-3.1.2/src/generator/mix.js +19 -0
  52. simview/static/lib/chroma-js-3.1.2/src/generator/random.js +12 -0
  53. simview/static/lib/chroma-js-3.1.2/src/generator/scale.js +394 -0
  54. simview/static/lib/chroma-js-3.1.2/src/interpolator/_hsx.js +59 -0
  55. simview/static/lib/chroma-js-3.1.2/src/interpolator/hcg.js +12 -0
  56. simview/static/lib/chroma-js-3.1.2/src/interpolator/hsi.js +12 -0
  57. simview/static/lib/chroma-js-3.1.2/src/interpolator/hsl.js +12 -0
  58. simview/static/lib/chroma-js-3.1.2/src/interpolator/hsv.js +12 -0
  59. simview/static/lib/chroma-js-3.1.2/src/interpolator/index.js +1 -0
  60. simview/static/lib/chroma-js-3.1.2/src/interpolator/lab.js +19 -0
  61. simview/static/lib/chroma-js-3.1.2/src/interpolator/lch.js +13 -0
  62. simview/static/lib/chroma-js-3.1.2/src/interpolator/lrgb.js +19 -0
  63. simview/static/lib/chroma-js-3.1.2/src/interpolator/num.js +15 -0
  64. simview/static/lib/chroma-js-3.1.2/src/interpolator/oklab.js +19 -0
  65. simview/static/lib/chroma-js-3.1.2/src/interpolator/oklch.js +12 -0
  66. simview/static/lib/chroma-js-3.1.2/src/interpolator/rgb.js +18 -0
  67. simview/static/lib/chroma-js-3.1.2/src/io/cmyk/cmyk2rgb.js +16 -0
  68. simview/static/lib/chroma-js-3.1.2/src/io/cmyk/index.js +27 -0
  69. simview/static/lib/chroma-js-3.1.2/src/io/cmyk/rgb2cmyk.js +17 -0
  70. simview/static/lib/chroma-js-3.1.2/src/io/css/css2rgb.js +238 -0
  71. simview/static/lib/chroma-js-3.1.2/src/io/css/hsl2css.js +26 -0
  72. simview/static/lib/chroma-js-3.1.2/src/io/css/index.js +27 -0
  73. simview/static/lib/chroma-js-3.1.2/src/io/css/lab2css.js +24 -0
  74. simview/static/lib/chroma-js-3.1.2/src/io/css/lch2css.js +24 -0
  75. simview/static/lib/chroma-js-3.1.2/src/io/css/oklab2css.js +16 -0
  76. simview/static/lib/chroma-js-3.1.2/src/io/css/oklch2css.js +16 -0
  77. simview/static/lib/chroma-js-3.1.2/src/io/css/rgb2css.js +61 -0
  78. simview/static/lib/chroma-js-3.1.2/src/io/gl/index.js +22 -0
  79. simview/static/lib/chroma-js-3.1.2/src/io/hcg/hcg2rgb.js +55 -0
  80. simview/static/lib/chroma-js-3.1.2/src/io/hcg/index.js +27 -0
  81. simview/static/lib/chroma-js-3.1.2/src/io/hcg/rgb2hcg.js +23 -0
  82. simview/static/lib/chroma-js-3.1.2/src/io/hex/hex2rgb.js +56 -0
  83. simview/static/lib/chroma-js-3.1.2/src/io/hex/index.js +29 -0
  84. simview/static/lib/chroma-js-3.1.2/src/io/hex/rgb2hex.js +29 -0
  85. simview/static/lib/chroma-js-3.1.2/src/io/hsi/hsi2rgb.js +45 -0
  86. simview/static/lib/chroma-js-3.1.2/src/io/hsi/index.js +27 -0
  87. simview/static/lib/chroma-js-3.1.2/src/io/hsi/rgb2hsi.js +31 -0
  88. simview/static/lib/chroma-js-3.1.2/src/io/hsl/hsl2rgb.js +35 -0
  89. simview/static/lib/chroma-js-3.1.2/src/io/hsl/index.js +27 -0
  90. simview/static/lib/chroma-js-3.1.2/src/io/hsl/rgb2hsl.js +45 -0
  91. simview/static/lib/chroma-js-3.1.2/src/io/hsv/hsv2rgb.js +47 -0
  92. simview/static/lib/chroma-js-3.1.2/src/io/hsv/index.js +27 -0
  93. simview/static/lib/chroma-js-3.1.2/src/io/hsv/rgb2hsv.js +32 -0
  94. simview/static/lib/chroma-js-3.1.2/src/io/input.js +4 -0
  95. simview/static/lib/chroma-js-3.1.2/src/io/lab/index.js +28 -0
  96. simview/static/lib/chroma-js-3.1.2/src/io/lab/lab-constants.js +120 -0
  97. simview/static/lib/chroma-js-3.1.2/src/io/lab/lab2rgb.js +101 -0
  98. simview/static/lib/chroma-js-3.1.2/src/io/lab/rgb2lab.js +67 -0
  99. simview/static/lib/chroma-js-3.1.2/src/io/lch/hcl2rgb.js +9 -0
  100. simview/static/lib/chroma-js-3.1.2/src/io/lch/index.js +35 -0
  101. simview/static/lib/chroma-js-3.1.2/src/io/lch/lab2lch.js +12 -0
  102. simview/static/lib/chroma-js-3.1.2/src/io/lch/lch2lab.js +18 -0
  103. simview/static/lib/chroma-js-3.1.2/src/io/lch/lch2rgb.js +13 -0
  104. simview/static/lib/chroma-js-3.1.2/src/io/lch/rgb2lch.js +12 -0
  105. simview/static/lib/chroma-js-3.1.2/src/io/named/index.js +30 -0
  106. simview/static/lib/chroma-js-3.1.2/src/io/num/index.js +32 -0
  107. simview/static/lib/chroma-js-3.1.2/src/io/num/num2rgb.js +13 -0
  108. simview/static/lib/chroma-js-3.1.2/src/io/num/rgb2num.js +8 -0
  109. simview/static/lib/chroma-js-3.1.2/src/io/oklab/index.js +27 -0
  110. simview/static/lib/chroma-js-3.1.2/src/io/oklab/oklab2rgb.js +34 -0
  111. simview/static/lib/chroma-js-3.1.2/src/io/oklab/rgb2oklab.js +37 -0
  112. simview/static/lib/chroma-js-3.1.2/src/io/oklch/index.js +27 -0
  113. simview/static/lib/chroma-js-3.1.2/src/io/oklch/oklch2rgb.js +13 -0
  114. simview/static/lib/chroma-js-3.1.2/src/io/oklch/rgb2oklch.js +12 -0
  115. simview/static/lib/chroma-js-3.1.2/src/io/rgb/index.js +44 -0
  116. simview/static/lib/chroma-js-3.1.2/src/io/temp/index.js +22 -0
  117. simview/static/lib/chroma-js-3.1.2/src/io/temp/rgb2temperature.js +30 -0
  118. simview/static/lib/chroma-js-3.1.2/src/io/temp/temperature2rgb.js +39 -0
  119. simview/static/lib/chroma-js-3.1.2/src/ops/alpha.js +13 -0
  120. simview/static/lib/chroma-js-3.1.2/src/ops/clipped.js +5 -0
  121. simview/static/lib/chroma-js-3.1.2/src/ops/darken.js +17 -0
  122. simview/static/lib/chroma-js-3.1.2/src/ops/get.js +13 -0
  123. simview/static/lib/chroma-js-3.1.2/src/ops/luminance.js +54 -0
  124. simview/static/lib/chroma-js-3.1.2/src/ops/mix.js +10 -0
  125. simview/static/lib/chroma-js-3.1.2/src/ops/premultiply.js +12 -0
  126. simview/static/lib/chroma-js-3.1.2/src/ops/saturate.js +15 -0
  127. simview/static/lib/chroma-js-3.1.2/src/ops/set.js +43 -0
  128. simview/static/lib/chroma-js-3.1.2/src/ops/shade.js +11 -0
  129. simview/static/lib/chroma-js-3.1.2/src/utils/analyze.js +191 -0
  130. simview/static/lib/chroma-js-3.1.2/src/utils/clip_rgb.js +15 -0
  131. simview/static/lib/chroma-js-3.1.2/src/utils/contrast.js +12 -0
  132. simview/static/lib/chroma-js-3.1.2/src/utils/contrastAPCA.js +68 -0
  133. simview/static/lib/chroma-js-3.1.2/src/utils/delta-e.js +62 -0
  134. simview/static/lib/chroma-js-3.1.2/src/utils/distance.js +17 -0
  135. simview/static/lib/chroma-js-3.1.2/src/utils/index.js +27 -0
  136. simview/static/lib/chroma-js-3.1.2/src/utils/last.js +8 -0
  137. simview/static/lib/chroma-js-3.1.2/src/utils/limit.js +5 -0
  138. simview/static/lib/chroma-js-3.1.2/src/utils/multiply-matrices.js +36 -0
  139. simview/static/lib/chroma-js-3.1.2/src/utils/scales.js +15 -0
  140. simview/static/lib/chroma-js-3.1.2/src/utils/type.js +18 -0
  141. simview/static/lib/chroma-js-3.1.2/src/utils/unpack.js +17 -0
  142. simview/static/lib/chroma-js-3.1.2/src/utils/valid.js +11 -0
  143. simview/static/lib/chroma-js-3.1.2/src/version.js +2 -0
  144. simview/static/lib/download.js +132 -0
  145. simview/static/lib/js-colormaps.js +19335 -0
  146. simview/static/lib/tar.js +334 -0
  147. simview/static/lib/three-0.174.0/addons/controls/OrbitControls.js +1556 -0
  148. simview/static/lib/three-0.174.0/addons/libs/lil-gui.module.min.js +8 -0
  149. simview/static/lib/three-0.174.0/three.core.js +48830 -0
  150. simview/static/lib/three-0.174.0/three.module.js +17313 -0
  151. simview/static/lib/uPlot.esm.js +6140 -0
  152. simview/static/lib/uPlot.min.css +2 -0
  153. simview/static/textures/contacts/red-cross0.png +0 -0
  154. simview/static/textures/points/ball0.png +0 -0
  155. simview/static/textures/points/ball1.png +0 -0
  156. simview/templates/index.html +81 -0
  157. simview/utils.py +41 -0
  158. simview-3.2.dist-info/METADATA +552 -0
  159. simview-3.2.dist-info/RECORD +163 -0
  160. simview-3.2.dist-info/WHEEL +5 -0
  161. simview-3.2.dist-info/entry_points.txt +2 -0
  162. simview-3.2.dist-info/licenses/LICENSE +28 -0
  163. 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
+ };
@@ -0,0 +1,3 @@
1
+ import { SimView } from "./SimView.js";
2
+
3
+ SimView.run();