Compare commits
577
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
020a878915 | ||
|
|
404285419f | ||
|
|
ba0c948ca1 | ||
|
|
429b4681fe | ||
|
|
22d69107ba | ||
|
|
a8647c4d87 | ||
|
|
b1aaa40508 | ||
|
|
3616acc648 | ||
|
|
e82d012907 | ||
|
|
fc545f8440 | ||
|
|
a98ef339de | ||
|
|
9062126913 | ||
|
|
59e14c5bc0 | ||
|
|
15bb793e5e | ||
|
|
79911eb316 | ||
|
|
d696842f5d | ||
|
|
57f2537af3 | ||
|
|
e56eb42507 | ||
|
|
b2a1b23131 | ||
|
|
cada687173 | ||
|
|
eff60e490a | ||
|
|
9db29bcdc6 | ||
|
|
27979854a7 | ||
|
|
3ea8d987a7 | ||
|
|
bd947b49f4 | ||
|
|
54c8d82884 | ||
|
|
bee8552003 | ||
|
|
4e0565eb4b | ||
|
|
44afadb021 | ||
|
|
e436fb56d4 | ||
|
|
c2710b55a7 | ||
|
|
d777f3198b | ||
|
|
1e4ddc2cff | ||
|
|
80fc0058cb | ||
|
|
43f1cdc862 | ||
|
|
0041e9fadb | ||
|
|
cabd3081c7 | ||
|
|
049ed3eed4 | ||
|
|
35c1249631 | ||
|
|
941183d624 | ||
|
|
6af4c208ce | ||
|
|
75a5320756 | ||
|
|
59a74a3677 | ||
|
|
35d28418f3 | ||
|
|
48835a0499 | ||
|
|
93ae1edbb4 | ||
|
|
093df8b38f | ||
|
|
35b6cd9e9e | ||
|
|
d80df61a7e | ||
|
|
d7b8989e43 | ||
|
|
0cc6b96306 | ||
|
|
6469505c41 | ||
|
|
191612282b | ||
|
|
0310592ee3 | ||
|
|
df0158e1ba | ||
|
|
2ef8c08bf2 | ||
|
|
c570f7d938 | ||
|
|
03e8e71bea | ||
|
|
61cbdb30a0 | ||
|
|
e91721fe6a | ||
|
|
70927ea585 | ||
|
|
03a7e730ae | ||
|
|
c31c46caa5 | ||
|
|
06a66a3da8 | ||
|
|
92625bf48b | ||
|
|
b8ba0cae1e | ||
|
|
f39f1ff0b6 | ||
|
|
380b6eaba7 | ||
|
|
f6d73d8bfb | ||
|
|
221e429c0a | ||
|
|
a85aed2e03 | ||
|
|
f102712f0b | ||
|
|
350366bae0 | ||
|
|
c23e86fd39 | ||
|
|
79ce55d197 | ||
|
|
a937400942 | ||
|
|
da60feff90 | ||
|
|
85349f42f9 | ||
|
|
ac69e3b444 | ||
|
|
6acf468b25 | ||
|
|
d264cbce04 | ||
|
|
1f8101e4a5 | ||
|
|
bcacb02a22 | ||
|
|
adcca7aa81 | ||
|
|
097e450a87 | ||
|
|
4e633a662c | ||
|
|
c6c42c29e8 | ||
|
|
ce09d30c1a | ||
|
|
34f13ba59e | ||
|
|
d9ea7c1129 | ||
|
|
a3c67e249c | ||
|
|
294585936f | ||
|
|
380a13688c | ||
|
|
fd8313899b | ||
|
|
05c99efb68 | ||
|
|
ffd6be5606 | ||
|
|
bfe6ef4c77 | ||
|
|
4e04d06191 | ||
|
|
5a78c997ac | ||
|
|
62d5520c7a | ||
|
|
a945d665dd | ||
|
|
d655d6998d | ||
|
|
7025e173a3 | ||
|
|
10fe727561 | ||
|
|
00ee5f163a | ||
|
|
cbcc09afd6 | ||
|
|
e958729e9d | ||
|
|
0fcd037e4f | ||
|
|
11a146011f | ||
|
|
44ebbe7ca2 | ||
|
|
a3299dcfc3 | ||
|
|
2989fba99c | ||
|
|
142481f0d8 | ||
|
|
b2ae29b8d8 | ||
|
|
8eced6c34d | ||
|
|
732cc139fd | ||
|
|
104e54f4dd | ||
|
|
7b021782d3 | ||
|
|
759830b054 | ||
|
|
19c0bd70d0 | ||
|
|
35d99e40d5 | ||
|
|
bfc1f1bbed | ||
|
|
f41428f907 | ||
|
|
3744d79ad3 | ||
|
|
b57c3e94da | ||
|
|
73a7d28065 | ||
|
|
f64fbb1fa6 | ||
|
|
1ff527b264 | ||
|
|
def8c71410 | ||
|
|
7e59176581 | ||
|
|
4cd94b5805 | ||
|
|
1766c0bdb2 | ||
|
|
56bfeaee7b | ||
|
|
892d0085e3 | ||
|
|
e2de8188ab | ||
|
|
9c69d81296 | ||
|
|
2f6e45bc96 | ||
|
|
8a1c578e0e | ||
|
|
9beb534108 | ||
|
|
d1b75efcea | ||
|
|
c9302c78c2 | ||
|
|
d67e86176e | ||
|
|
e72609120f | ||
|
|
177be8869f | ||
|
|
2a5763d3fb | ||
|
|
8d5aeb0533 | ||
|
|
be83283ee0 | ||
|
|
023151c186 | ||
|
|
e6a9846167 | ||
|
|
81fdf6904a | ||
|
|
9c5259dbc9 | ||
|
|
35daf73d5c | ||
|
|
07453142c2 | ||
|
|
f5ee42751d | ||
|
|
e9ffb829c9 | ||
|
|
757d368c86 | ||
|
|
f1cbe0061a | ||
|
|
74da6437e9 | ||
|
|
bd2892140f | ||
|
|
525ab74168 | ||
|
|
c30d77572e | ||
|
|
5179e93f4a | ||
|
|
c06b709fd7 | ||
|
|
856b61be99 | ||
|
|
76694cc869 | ||
|
|
e6df8cb264 | ||
|
|
4b487e367f | ||
|
|
d7d9485724 | ||
|
|
0a0cde7b0e | ||
|
|
9dec37a8a9 | ||
|
|
24b65926fe | ||
|
|
57dacc5a04 | ||
|
|
d9ec8c9cef | ||
|
|
af1e530162 | ||
|
|
e10b96b546 | ||
|
|
a2c3385062 | ||
|
|
7fcfecd063 | ||
|
|
7a3b0b84d0 | ||
|
|
e1c3e7e90a | ||
|
|
14877b6683 | ||
|
|
0848cefc89 | ||
|
|
722e60e5ef | ||
|
|
c4c2392c79 | ||
|
|
30080c51aa | ||
|
|
67e6ae98e2 | ||
|
|
57204b3b0b | ||
|
|
7594c71dd1 | ||
|
|
92aa796c9d | ||
|
|
7c0fd81b2f | ||
|
|
95a1ef5057 | ||
|
|
c8593207d3 | ||
|
|
7aeea4ed81 | ||
|
|
bf76304e00 | ||
|
|
b70dc4298a | ||
|
|
f153d671d3 | ||
|
|
7720594790 | ||
|
|
296cf610cd | ||
|
|
46cef8df17 | ||
|
|
d722b0f82b | ||
|
|
40c850b167 | ||
|
|
6544d9e918 | ||
|
|
67d5d6fa05 | ||
|
|
941fb89616 | ||
|
|
989a3ce28c | ||
|
|
28d5297237 | ||
|
|
b0738438d4 | ||
|
|
67578f4ce1 | ||
|
|
83ef98e9e5 | ||
|
|
316f0d7f85 | ||
|
|
2e7abbc56b | ||
|
|
b445745d27 | ||
|
|
1cc87e1836 | ||
|
|
aed59030b4 | ||
|
|
2e20ca7777 | ||
|
|
ae13ca5946 | ||
|
|
1b6131904a | ||
|
|
7fa2bfe52d | ||
|
|
1696b4742b | ||
|
|
ac1dc7d930 | ||
|
|
a98ad929d9 | ||
|
|
5a9738d6aa | ||
|
|
30227b661f | ||
|
|
4bcc35c1a0 | ||
|
|
a65ae9c285 | ||
|
|
5e97fa293f | ||
|
|
aba06086ac | ||
|
|
424374263e | ||
|
|
906a6ce37b | ||
|
|
1496184167 | ||
|
|
9561a068ab | ||
|
|
9e686b3311 | ||
|
|
936ee479ed | ||
|
|
b82a97fe8b | ||
|
|
90bdc27785 | ||
|
|
0b01bb7775 | ||
|
|
b688347778 | ||
|
|
252cbc8495 | ||
|
|
048716b3b3 | ||
|
|
343ddeac2d | ||
|
|
5983883ffe | ||
|
|
6290c75210 | ||
|
|
3c81c39a1c | ||
|
|
932c5216dc | ||
|
|
f9a76fed0e | ||
|
|
c05db1fbe0 | ||
|
|
385082c8ca | ||
|
|
cb1e1494af | ||
|
|
d943ec853c | ||
|
|
b111406cf8 | ||
|
|
28effdde23 | ||
|
|
2920fc7579 | ||
|
|
147a623df9 | ||
|
|
62c8ed421a | ||
|
|
be079bf18f | ||
|
|
73fbfddf6c | ||
|
|
3ff3b9b587 | ||
|
|
9a956c318a | ||
|
|
209d0bfc26 | ||
|
|
f50e0077c5 | ||
|
|
fb5bf943c9 | ||
|
|
bab0bb2df2 | ||
|
|
e21332b9e1 | ||
|
|
078b8421a6 | ||
|
|
84624ae3ea | ||
|
|
477886d100 | ||
|
|
1d706dd169 | ||
|
|
d1e6e42cf4 | ||
|
|
a0152f371e | ||
|
|
5b07372791 | ||
|
|
b5080671c9 | ||
|
|
15be698097 | ||
|
|
3679e43fe3 | ||
|
|
05590867b7 | ||
|
|
33cef2fdea | ||
|
|
6276bbf324 | ||
|
|
1084984da2 | ||
|
|
669bb64423 | ||
|
|
eb416ff80a | ||
|
|
4fa1669ab7 | ||
|
|
4fa6597b18 | ||
|
|
992c5a8b74 | ||
|
|
7400fe2fd7 | ||
|
|
8877ee947f | ||
|
|
a7caed70c1 | ||
|
|
a55ef475d9 | ||
|
|
34ed8a0f5f | ||
|
|
64860be4ae | ||
|
|
4ac07f7671 | ||
|
|
bcda5d001a | ||
|
|
cbbaea9148 | ||
|
|
3d3c0eced2 | ||
|
|
d50d3bb3d3 | ||
|
|
51bb1369eb | ||
|
|
a3fc13ad63 | ||
|
|
c041a569f6 | ||
|
|
5ebc9fc27c | ||
|
|
99eafaf02d | ||
|
|
03830cd15e | ||
|
|
6e7428a116 | ||
|
|
3e5aedd74d | ||
|
|
687c050c28 | ||
|
|
4e00e804f3 | ||
|
|
456141a3f3 | ||
|
|
7d911b4b1f | ||
|
|
71c8b043c9 | ||
|
|
0752e5c6bf | ||
|
|
1001a31638 | ||
|
|
85035fa07b | ||
|
|
eaad9deda1 | ||
|
|
7217244886 | ||
|
|
c7843a3c7e | ||
|
|
a3138f3d71 | ||
|
|
d01be34cac | ||
|
|
ac4a00c67d | ||
|
|
aee60f12b9 | ||
|
|
321cde70e8 | ||
|
|
986ea69610 | ||
|
|
72a2f37c03 | ||
|
|
4a24275ddc | ||
|
|
17a07355cf | ||
|
|
cd8a1045c4 | ||
|
|
3235ba7b1c | ||
|
|
9e6b5b403a | ||
|
|
9ef115d3b4 | ||
|
|
606cbbfae2 | ||
|
|
0ca7316a24 | ||
|
|
aff331082f | ||
|
|
52da9b75b7 | ||
|
|
be50144ca8 | ||
|
|
81ae4425cb | ||
|
|
8eaa3ab497 | ||
|
|
b7a51e26e6 | ||
|
|
b8fb4ebcba | ||
|
|
e38f2fb1da | ||
|
|
9df9f58ab8 | ||
|
|
aacc6dc43b | ||
|
|
13ed096f80 | ||
|
|
31313c3c96 | ||
|
|
7aa3ce55c0 | ||
|
|
c70ad345ea | ||
|
|
de19229895 | ||
|
|
b0d0bc8d7f | ||
|
|
6b810952a4 | ||
|
|
998775c3af | ||
|
|
438e0ca263 | ||
|
|
3ecbe3fb21 | ||
|
|
b1f9957317 | ||
|
|
8bd632509e | ||
|
|
8e161a197f | ||
|
|
7756310bdf | ||
|
|
b38cd51338 | ||
|
|
40794f13aa | ||
|
|
720487379c | ||
|
|
c9242480c2 | ||
|
|
95ac235122 | ||
|
|
a680debfaf | ||
|
|
029b486c67 | ||
|
|
ae1e41f7fe | ||
|
|
8ef215e98f | ||
|
|
78a3dc2162 | ||
|
|
1b3e0b3406 | ||
|
|
6d0abbc569 | ||
|
|
de12e96297 | ||
|
|
4c763bd8aa | ||
|
|
67bd96868f | ||
|
|
7a72a206f1 | ||
|
|
12c8a2d74c | ||
|
|
3982256f08 | ||
|
|
c1b0f6f8a3 | ||
|
|
55b7821a5c | ||
|
|
b44c4b3265 | ||
|
|
a2cb50d1ad | ||
|
|
8fe6184c52 | ||
|
|
a9a5ca5a26 | ||
|
|
29fee5c0ac | ||
|
|
dc6f9b66da | ||
|
|
dee6d57133 | ||
|
|
8c91d40e2e | ||
|
|
81d7ad1a77 | ||
|
|
e4e5bf6ea1 | ||
|
|
fdcaf57ae7 | ||
|
|
e9bbfb9a41 | ||
|
|
7d1a70d8e0 | ||
|
|
e56fa0c074 | ||
|
|
2ab9e45548 | ||
|
|
091ab2671e | ||
|
|
cc6838ed24 | ||
|
|
8e4891d608 | ||
|
|
2678295ca7 | ||
|
|
0c3855eac5 | ||
|
|
b91d3b913e | ||
|
|
5a06194962 | ||
|
|
f2a4dfe863 | ||
|
|
8e3710b493 | ||
|
|
66f8d7a1fa | ||
|
|
d95a13059c | ||
|
|
6180c3b8b7 | ||
|
|
32c5d0dda5 | ||
|
|
611853d3af | ||
|
|
9db39347bd | ||
|
|
1e1b7fe43d | ||
|
|
82a44478fb | ||
|
|
8b1f109b09 | ||
|
|
c455301764 | ||
|
|
636db089ae | ||
|
|
d8ccc9345a | ||
|
|
1eba7c82ba | ||
|
|
2a97cf28c0 | ||
|
|
37b8930527 | ||
|
|
79eb4b46f7 | ||
|
|
783aa444ac | ||
|
|
bfd95c7fee | ||
|
|
d729abab31 | ||
|
|
1f20e0d7d9 | ||
|
|
6e5124bd9c | ||
|
|
c018f2b074 | ||
|
|
e35f81ac34 | ||
|
|
6db5ac05ef | ||
|
|
a2fcaa7b39 | ||
|
|
65a72d40a8 | ||
|
|
d2964a0ddc | ||
|
|
a63d2fe15b | ||
|
|
2ffbb51a7e | ||
|
|
4a65b52f6d | ||
|
|
1c0181297d | ||
|
|
1f4c960cc4 | ||
|
|
b8a008b50e | ||
|
|
9fc002d86f | ||
|
|
667db00b52 | ||
|
|
67e12a47a4 | ||
|
|
438196cd6d | ||
|
|
fa20079c9e | ||
|
|
b894945344 | ||
|
|
621084fcd6 | ||
|
|
95c6540691 | ||
|
|
2c016b117d | ||
|
|
fa2d66ab0e | ||
|
|
9bbd5845b7 | ||
|
|
32257fc2b4 | ||
|
|
10c306f162 | ||
|
|
091d560ac8 | ||
|
|
001d597a89 | ||
|
|
a44d7627fd | ||
|
|
2ffaf94052 | ||
|
|
f50405d907 | ||
|
|
4db00b53f7 | ||
|
|
e9ff29297b | ||
|
|
360ec39ff3 | ||
|
|
dc55ff16c9 | ||
|
|
453d760be4 | ||
|
|
42041b37cd | ||
|
|
05413e3f5c | ||
|
|
677dbdb776 | ||
|
|
f791b63890 | ||
|
|
36ea7ea3d8 | ||
|
|
a1e2cb523f | ||
|
|
e6d5411bdd | ||
|
|
4c83e8a4e7 | ||
|
|
796b9306f9 | ||
|
|
c6c48fbc38 | ||
|
|
3449b2bc3e | ||
|
|
04a658b218 | ||
|
|
cfe17e49bb | ||
|
|
d8b7ed4526 | ||
|
|
21c7a5507c | ||
|
|
15821a1c98 | ||
|
|
41e347d20a | ||
|
|
8446eb4492 | ||
|
|
f2f044080f | ||
|
|
054feec0d2 | ||
|
|
13c35ff446 | ||
|
|
e97573e221 | ||
|
|
2e5c4ba1c5 | ||
|
|
b037076506 | ||
|
|
cea63f9a1e | ||
|
|
d6decbe05c | ||
|
|
60ba64004b | ||
|
|
951b40c870 | ||
|
|
881e97312b | ||
|
|
c01712c30d | ||
|
|
f57d64bf52 | ||
|
|
3333e9ac0f | ||
|
|
2dfb34ef21 | ||
|
|
75a3e669d9 | ||
|
|
e4fdd9fa20 | ||
|
|
3f549f91f1 | ||
|
|
37c24b5fa8 | ||
|
|
5abd07d2bf | ||
|
|
bc61331604 | ||
|
|
49b0f47fea | ||
|
|
0fb2782f2d | ||
|
|
6ad1803aaa | ||
|
|
8ace0cd102 | ||
|
|
b9ce5f53a0 | ||
|
|
8eecb8ed69 | ||
|
|
a0e9597cb5 | ||
|
|
97a3775acf | ||
|
|
f63ed36913 | ||
|
|
101707d54c | ||
|
|
53fd1e3bfc | ||
|
|
60e79160c9 | ||
|
|
17a0e7a7f5 | ||
|
|
ee7d1dd26c | ||
|
|
6cc40282e0 | ||
|
|
1995dfdf68 | ||
|
|
b7ccca3481 | ||
|
|
359f0d1d39 | ||
|
|
b790f29d59 | ||
|
|
a78c8c83f5 | ||
|
|
6cb14954d4 | ||
|
|
b4e3b3b735 | ||
|
|
01ec12263a | ||
|
|
e1809ac9e1 | ||
|
|
630d1ae819 | ||
|
|
12fba7126d | ||
|
|
fc8c81dbc4 | ||
|
|
a19053dfd4 | ||
|
|
2548bb5740 | ||
|
|
fc5cbdff8a | ||
|
|
c9b1009259 | ||
|
|
6290fcb2ce | ||
|
|
81761067e9 | ||
|
|
7fba39a629 | ||
|
|
230cba4b21 | ||
|
|
e6dd8c2cf1 | ||
|
|
23181c867b | ||
|
|
cfc7b062da | ||
|
|
5a29a536a2 | ||
|
|
082b7057da | ||
|
|
10019a454a | ||
|
|
f1f8e21b78 | ||
|
|
ae2ce055c8 | ||
|
|
a7babf60db | ||
|
|
90450ae320 | ||
|
|
ecd95a22f0 | ||
|
|
9f712bf353 | ||
|
|
ee15160862 | ||
|
|
3a64f54dea | ||
|
|
a0706fd5d8 | ||
|
|
b53d6d5a45 | ||
|
|
ada2a55ee6 | ||
|
|
2b53168149 | ||
|
|
31fc4f6567 | ||
|
|
648d5bced4 | ||
|
|
75d2e5da4b | ||
|
|
57b2208cae | ||
|
|
fecb5885d0 | ||
|
|
71c85e9894 | ||
|
|
ffffee1879 | ||
|
|
d7a2c22faf | ||
|
|
7b7b5d6cad | ||
|
|
36f0c93d2a | ||
|
|
a29b38a0d7 | ||
|
|
eddc09008e | ||
|
|
a4accf0fb6 | ||
|
|
ea811ff370 | ||
|
|
d7749208a7 | ||
|
|
af9319a33b | ||
|
|
0ac6424c46 | ||
|
|
574a494759 | ||
|
|
aa3680948f | ||
|
|
9d51080d2e | ||
|
|
24a47318f2 | ||
|
|
f9ffb7bd1c | ||
|
|
e94c64eebd | ||
|
|
007ff9fff2 | ||
|
|
656f0c524d | ||
|
|
aa2df560b7 | ||
|
|
2bda1986bd | ||
|
|
a281faf923 | ||
|
|
05bdab24a5 | ||
|
|
e6f7648b84 | ||
|
|
19ab973110 | ||
|
|
63dd8c8790 | ||
|
|
27bf7527df | ||
|
|
9225227421 | ||
|
|
8459bb03fb |
@@ -0,0 +1,260 @@
|
||||
---
|
||||
name: mission-core-product-ui
|
||||
description: Design and enforce Mission Core product surfaces, information architecture, product UI, and laboratory presentation. Use for every new or relocated workspace, product root, navigation category, panel, viewer, Control Station surface, LAB catalog/summary, visualization control, fullscreen behavior, product-facing status, or UI refactor in NODEDC_MISSION_CORE, including work that could introduce a new component, page pattern, or task-specific composition.
|
||||
---
|
||||
|
||||
# Mission Core product UI
|
||||
|
||||
## Required context
|
||||
|
||||
Before changing UI:
|
||||
|
||||
1. Read the repository `AGENTS.md`.
|
||||
2. Read `docs/17_PRODUCT_UI_AND_LAB_PRESENTATION_CANON.md`.
|
||||
3. Read `docs/18_APPLICATION_COMPONENT_ARCHITECTURE.md`.
|
||||
4. For a new, relocated, or materially changed non-LAB surface, read
|
||||
`docs/19_PRODUCT_SURFACE_EXTENSION_PROTOCOL.md`.
|
||||
5. For LAB work, also read `docs/15_LABORATORY_RUN_CANON.md`.
|
||||
6. Read the sibling Design Guideline:
|
||||
- `registry/registry.json`
|
||||
- `registry/components.json`
|
||||
- `registry/icons.json`
|
||||
- `docs/COMPONENTS.md`
|
||||
- `docs/GOVERNANCE.md`
|
||||
- the documentation for the affected page pattern or component.
|
||||
|
||||
Do not start implementation before resolving the required controls and icons in
|
||||
the registries.
|
||||
|
||||
## Canonical local endpoint
|
||||
|
||||
- Keep the integrated Mission Core Control Station and API exclusively on
|
||||
`http://127.0.0.1:8000`.
|
||||
- Treat the canonical `8000` process as durable operator state, not as a
|
||||
disposable QA process. Finishing a test, build, browser check, Codex turn, or
|
||||
implementation increment never authorizes stopping it.
|
||||
- At the start of every Control Station task, inspect `8000`. If it is not
|
||||
serving Mission Core, start the canonical integrated service before UI work.
|
||||
At handoff, verify it again and leave it running.
|
||||
- A rebuild may replace the process on `8000`, but the replacement must be the
|
||||
same canonical service and must be listening successfully before handoff.
|
||||
Never leave the operator with a stopped Control Station after a restart.
|
||||
- Never start a second integrated backend on an alternate port to bypass a
|
||||
stale or occupied `8000`. In particular, TCP `8765` is a legacy Foxglove
|
||||
regression port and is not an operator path.
|
||||
- Before browser QA or product handoff, inspect listeners on `8000` and `8765`.
|
||||
If the Mission Core process on `8000` is stale, restart that exact canonical
|
||||
service instead of launching another instance.
|
||||
- Use `5173` only for an explicitly requested Vite development session and
|
||||
`4173` only for an explicitly requested production preview. Both must proxy
|
||||
the API to `8000`; neither changes the canonical integrated endpoint.
|
||||
- Finish with exactly one integrated Mission Core backend on `8000` and no
|
||||
Mission Core backend on `8765`. Open or hand off `8000`, never an alternate
|
||||
backend URL.
|
||||
|
||||
## Local workstation resource gate
|
||||
|
||||
- The local operator machine is a 14-inch 2023 MacBook Pro with only 18 GB of
|
||||
physical memory. Its memory, swap, CPU, GPU, and thermal headroom are part of
|
||||
the product-test safety boundary.
|
||||
- Run memory-intensive work sequentially. Never overlap a full frontend test
|
||||
pass, production build, Docker/Compose build or startup, synthetic replay,
|
||||
and browser visual QA.
|
||||
- Before starting a memory-intensive operation, inspect host memory pressure and
|
||||
the active Docker/process workload. If memory pressure is elevated or swap is
|
||||
growing, stop temporary work and recover headroom before continuing.
|
||||
- Prefer focused tests and an existing valid build. Run the full suite and
|
||||
production build only when they are required acceptance gates, one at a time.
|
||||
- Do not start Docker Desktop, Compose stacks, duplicate application servers,
|
||||
duplicate browser automation sessions, or load/stress tests for convenience.
|
||||
Bounded load work belongs on Worker 006, not on the Mac.
|
||||
- After each test, build, replay, or visual-QA stage, stop temporary watchers,
|
||||
publishers, viewers, workers, and containers before starting the next heavy
|
||||
stage. Preserve only the canonical integrated service on `8000` and other
|
||||
explicitly required durable operator services.
|
||||
- Do not change Docker Desktop CPU or memory limits without explicit product
|
||||
owner approval. A configured Docker VM ceiling is not safe available host
|
||||
memory.
|
||||
|
||||
## Component gate
|
||||
|
||||
- Reuse package exports from `@nodedc/ui-react`, `@nodedc/ui-core`,
|
||||
`@nodedc/tokens`, and `@nodedc/page-patterns`.
|
||||
- Do not create a local substitute for a button, icon button, segmented switch,
|
||||
select, dropdown, glass surface, status, window, focus state, hover state, or
|
||||
page pattern.
|
||||
- Treat an absent generic visual entity as a product decision. Stop, describe
|
||||
the user need and proposed generic API, and obtain explicit approval.
|
||||
- After approval, implement the generic entity in the Design Guideline first,
|
||||
including registry, documentation, interaction states, catalog example, and
|
||||
validation. Consume it from Mission Core only afterward.
|
||||
- Allow Mission Core domain renderers, data adapters, and domain layouts. Build
|
||||
their controls from canonical exports.
|
||||
|
||||
Explicit approval for one entity does not authorize unrelated visual entities.
|
||||
|
||||
## Product surface design
|
||||
|
||||
Keep product grammar stable without forcing one layout:
|
||||
|
||||
- Reuse the canonical shell, controls, states, icons, focus, motion, and
|
||||
responsive behavior.
|
||||
- Never add decorative perimeter borders, outlines, inset rings, or persistent
|
||||
edge strokes to Mission Core cards, panels, topology nodes, telemetry blocks,
|
||||
task stages, or LAB content surfaces. Separate product hierarchy with
|
||||
approved surface tones, spacing, typography, and elevation.
|
||||
- Preserve canonical keyboard focus indicators and accessibility outlines from
|
||||
the Design Guideline. The no-outline rule applies to decoration, never to
|
||||
focus visibility, data-visualization grids, or a necessary structural
|
||||
divider.
|
||||
- Change composition when the operator job, primary entity, lifecycle, or
|
||||
action model changes materially.
|
||||
- Do not treat the LAB template as the default for non-LAB work.
|
||||
- Choose the smallest complete surface: view mode, inspector/panel, floating
|
||||
window, existing section, dedicated workspace, product root, plugin slot, or
|
||||
LAB result.
|
||||
|
||||
Before adding or relocating a workspace or changing primary navigation:
|
||||
|
||||
1. State the operator job, trigger/frequency, primary entity/lifecycle,
|
||||
evidence, authority, actions, and complete state grammar.
|
||||
2. Compare at least two credible placements or compositions.
|
||||
3. Resolve the Design Guideline primitives and page patterns.
|
||||
4. Classify the novelty:
|
||||
- domain content in an admitted composition;
|
||||
- new Mission Core-specific composition;
|
||||
- new shared Design Guideline entity/pattern;
|
||||
- new product root or platform meaning.
|
||||
5. Present the selected placement and rejected alternative with UX reasoning.
|
||||
6. Obtain product-owner agreement for a new/relocated workspace, primary
|
||||
navigation change, new root, or shared visual pattern.
|
||||
7. Implement only after the decision; do not add placeholder product routes or
|
||||
temporary navigation.
|
||||
|
||||
Valid task-specific compositions include scene-first, map-first,
|
||||
timeline-first, list/detail queue, table/catalog, form/editor, graph/topology,
|
||||
dashboard, and immutable LAB review.
|
||||
|
||||
## Application architecture
|
||||
|
||||
- Preserve `core → components/renderers → workspaces → composition/App`.
|
||||
- Core owns contracts, adapters, state machines, and domain hooks. It must not
|
||||
import visual adapters, workspaces, or App.
|
||||
- Reusable components may consume Core and canonical UI exports. They must not
|
||||
import workspaces or own product navigation.
|
||||
- Add every new domain or LAB run as a bounded feature module. Do not append its
|
||||
API adapter, renderer, selectors, or styles to `App.tsx`,
|
||||
`Workspaces.tsx`, or `styles/workspaces.css`.
|
||||
- Keep LAB composition under `workspaces/laboratory` and pass generic host
|
||||
viewers through typed contracts.
|
||||
- Treat `productModel.ts` as the application registry. Do not duplicate root,
|
||||
workspace, icon, or capability lists in the shell.
|
||||
- Do not introduce a runtime ontology merely to organize code. Use the typed
|
||||
registry, executable contracts, and experimental vocabulary until the
|
||||
multi-consumer admission gate in the architecture canon is met.
|
||||
- Never raise an architecture line-count ratchet to fit a new feature. Split
|
||||
the feature and retain the boundary test.
|
||||
|
||||
## Laboratory product contract
|
||||
|
||||
Use the executable template
|
||||
`missioncore.laboratory-report/v1`:
|
||||
|
||||
`selectors → compact summary → evidence → result → optional reusable details`
|
||||
|
||||
For every new bounded `workspaces/laboratory/ENNResult.tsx`:
|
||||
|
||||
1. Pass typed values into `LaboratorySummary`; always supply `question`,
|
||||
`approach`, `principalResult`, and `limitation`.
|
||||
2. Put visual evidence in `LaboratoryEvidence`.
|
||||
3. Pass result metrics and `proved`, `notProved`, and `decision` into
|
||||
`LaboratoryResultSummary`.
|
||||
4. Do not render the `laboratory-summary`, `laboratory-result-summary`,
|
||||
`laboratory-result-metrics`, or `laboratory-result-conclusion` classes in the
|
||||
LAB module.
|
||||
5. Do not change slot order or shared report geometry for one run.
|
||||
|
||||
Treat a missing field as missing product evidence, not as permission to omit
|
||||
the section. An admitted diagnostic viewer may own the result interaction only
|
||||
when the existing canon explicitly identifies that viewer as the result
|
||||
instrument.
|
||||
|
||||
Changing the v1 anatomy requires product-owner agreement, a new template
|
||||
version, migration of every consumer, updated canon, and an updated
|
||||
architecture test. Never drift v1 incrementally from a single LAB.
|
||||
|
||||
The compact summary must cover:
|
||||
|
||||
- decision question and purpose;
|
||||
- immutable source and tested bounds;
|
||||
- pipeline, execution class, models, algorithms, tools, and worker/runtime;
|
||||
- experimental feature or configuration;
|
||||
- principal result and limitation;
|
||||
- retained authority.
|
||||
|
||||
Keep manifests, long hashes, implementation files, validation logs, regressions,
|
||||
rejected approaches, and next-stage planning in the Mission Core Ops report.
|
||||
|
||||
Do not expose roadmap steps, next-gate checklists, internal reason taxonomies,
|
||||
debug controls, placeholder blocks, or implementation scaffolding in product
|
||||
UI.
|
||||
|
||||
## Evidence viewers
|
||||
|
||||
- Use one reusable viewer frame for related representations.
|
||||
- Provide canonical expand/restore through `IconButton` and `Icon`.
|
||||
- Provide mode switching through `SegmentedControl`.
|
||||
- Preserve selected evidence and mode across resize.
|
||||
- Use 3D for spatial shape, range, height, topology, support, and occupied
|
||||
volume.
|
||||
- Use 2D for camera-plane reprojection, bbox/mask agreement, calibration,
|
||||
field-of-view, occlusion, and pixel correspondence.
|
||||
- When both are useful, keep them as modes of the same viewer and share source
|
||||
indices and selected case.
|
||||
- Keep renderer colors semantic and token-derived. Never use them to invent
|
||||
control states or persistent colored outlines.
|
||||
|
||||
## Ops report boundary
|
||||
|
||||
When the task includes reporting a completed LAB or architecture milestone, use
|
||||
the direct `nodedc-ops-agent` tools and write titled structured blocks in this
|
||||
order:
|
||||
|
||||
1. Objective and architecture stage.
|
||||
2. Decision question and hypothesis.
|
||||
3. Immutable source evidence and bounds.
|
||||
4. Method, preprocessing, models, algorithms, tools, and identities.
|
||||
5. Worker/runtime topology and resource policy.
|
||||
6. Experimental implementation.
|
||||
7. Validation and reproduced evidence.
|
||||
8. Results.
|
||||
9. Regressions, rejected approaches, and limitations.
|
||||
10. Decision.
|
||||
11. Next stage and forbidden authority.
|
||||
12. Acceptance checker.
|
||||
|
||||
Do not turn the product UI into a duplicate Ops report.
|
||||
|
||||
## Validation
|
||||
|
||||
Before handoff:
|
||||
|
||||
1. Search the changed product UI for raw local controls, hard-coded product
|
||||
colors, decorative perimeter borders/outlines, temporary copy,
|
||||
gate/checklist UI, and per-LAB page branches. Justify every remaining
|
||||
`border`, `outline`, or inset `box-shadow` as a canonical focus state,
|
||||
data-visualization grid, or necessary structural divider.
|
||||
2. Verify every changed or new bounded `ENNResult.tsx` uses
|
||||
`LaboratorySummary`, `LaboratoryEvidence`, and `LaboratoryResultSummary`
|
||||
without raw canonical report markup.
|
||||
3. For a new surface, verify the product-surface brief names the user job,
|
||||
selected placement, rejected alternative, state grammar, and real acceptance
|
||||
evidence.
|
||||
4. Under the local workstation resource gate, run
|
||||
`test/applicationArchitecture.test.mjs`, then the full frontend typecheck,
|
||||
unit tests, and production build sequentially. Never overlap these jobs.
|
||||
5. After the build/test processes have exited and memory headroom is confirmed,
|
||||
use the in-app browser to verify normal and expanded modes, every new view
|
||||
mode, keyboard Escape, and the canonical control states.
|
||||
6. Verify the default composition answers the primary operator question.
|
||||
7. Leave a real evidence case open for product-owner review.
|
||||
@@ -0,0 +1,4 @@
|
||||
interface:
|
||||
display_name: "Mission Core Product UI"
|
||||
short_description: "Mission Core surfaces and LAB product architecture"
|
||||
default_prompt: "Use $mission-core-product-ui to design or review a Mission Core product surface and its placement."
|
||||
+1
-1
@@ -9,4 +9,4 @@
|
||||
*.xbin binary
|
||||
*.las binary
|
||||
*.lcc binary
|
||||
|
||||
apps/control-station/vendor/rerun-web-viewer-0.34.1/re_viewer_bg.nodedc.wasm filter=lfs diff=lfs merge=lfs -text
|
||||
|
||||
@@ -12,6 +12,7 @@ __pycache__/
|
||||
.coverage
|
||||
htmlcov/
|
||||
dist/
|
||||
apps/control-station/dist-shadow/
|
||||
build/
|
||||
*.egg-info/
|
||||
|
||||
@@ -21,10 +22,14 @@ build/
|
||||
!.env.example
|
||||
config/local.toml
|
||||
private/
|
||||
.runtime/
|
||||
|
||||
# Real laboratory captures and decoded artifacts are sensitive and large.
|
||||
captures/
|
||||
sessions/
|
||||
# The host runtime package shares the domain name but is source, not evidence.
|
||||
!src/k1link/sessions/
|
||||
!src/k1link/sessions/**/*.py
|
||||
artifacts/raw/
|
||||
artifacts/decoded/
|
||||
*.pcap
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
# Repository operating rules
|
||||
|
||||
This repository investigates an owner-controlled XGRIDS/LixelKity K1 as a
|
||||
black-box sensor. Preserve the device, the Mac, the working LAN, and raw
|
||||
evidence.
|
||||
NODEDC MISSION CORE is a vendor-neutral mission-control monorepo. Its first
|
||||
real device plugin investigates an owner-controlled XGRIDS/LixelKity K1 as a
|
||||
black-box sensor. Preserve the device, the host, the working LAN, raw evidence,
|
||||
and the boundary between Mission Core and vendor-specific integration code.
|
||||
|
||||
## Non-negotiable safety boundaries
|
||||
|
||||
@@ -23,6 +24,33 @@ evidence.
|
||||
- Do not install or alter Homebrew/system components unless the user explicitly
|
||||
authorizes that concrete change.
|
||||
|
||||
## Local operator resource envelope
|
||||
|
||||
- The local operator machine is a 14-inch 2023 MacBook Pro with only 18 GB of
|
||||
physical memory. Treat local memory and swap as a hard shared operational
|
||||
limit, not as disposable build capacity.
|
||||
- Run frontend tests, production builds, Docker builds, runtime startup, and
|
||||
browser QA sequentially. Do not launch parallel heavy local jobs.
|
||||
- Do not start Docker Desktop, a Compose stack, a second browser automation
|
||||
session, or a full test/build pass merely for convenience. First prove that
|
||||
the operation is necessary, prefer the narrowest focused check, and keep no
|
||||
more than one memory-intensive validation job active at a time.
|
||||
- Do not run load or stress tests on the Mac. Run bounded synthetic load only on
|
||||
Worker 006, and remove every temporary process after the measurement.
|
||||
- Use only the canonical Mission Core endpoint on port `8000`; do not start
|
||||
duplicate application servers to work around stale state.
|
||||
- Before a memory-intensive local operation, inspect current memory pressure and
|
||||
active Docker workload. If memory pressure is elevated or swap is growing,
|
||||
stop and remove temporary jobs before continuing. Prefer focused tests and
|
||||
existing build artifacts when they are sufficient for acceptance.
|
||||
- After every local test, build, replay, browser-QA, or Docker operation, stop
|
||||
temporary workers, watchers, replay publishers, duplicate viewers, and
|
||||
containers that are not durable operator state. Keep only the canonical
|
||||
Mission Core service on `8000` and explicitly required operator services.
|
||||
- Docker Desktop's configured VM ceiling is not evidence that the host can
|
||||
safely supply that memory. Do not change Docker Desktop CPU or memory limits
|
||||
without explicit owner approval.
|
||||
|
||||
## Evidence rules
|
||||
|
||||
- Reference inputs under `docs/reference/` are immutable; write corrections in
|
||||
@@ -38,3 +66,103 @@ evidence.
|
||||
|
||||
Follow the gates in `docs/01_IMPLEMENTATION_PLAN.md`. Do not build heavy
|
||||
decoders before BLE/Wi-Fi/data-session evidence exists.
|
||||
|
||||
## Docker runtime governance
|
||||
|
||||
- Every new or migrated durable NODE.DC-owned Docker container, Compose project,
|
||||
network, named volume, and first-party image uses the lowercase `ndc-`
|
||||
namespace. The prefix is mandatory for anything that may leave a laboratory
|
||||
workstation; a legacy project/network name may survive only behind a bounded,
|
||||
documented migration barrier.
|
||||
- Do not retag or rename unrelated third-party runtime owned by another product.
|
||||
Express NODE.DC ownership through the managed object name and
|
||||
`com.nodedc.product`, `com.nodedc.stack`, `com.nodedc.role`, and
|
||||
`com.nodedc.managed-by` labels.
|
||||
- One operator-managed contour may be one Compose project while still using
|
||||
separate single-purpose containers. Do not merge a broker, normalizer,
|
||||
database, or application process into a multi-process container merely to
|
||||
make Docker Desktop show one row.
|
||||
- Treat Compose and worker orchestration declarations as the source of truth.
|
||||
A live `docker rename` is allowed only as a bounded migration with exact
|
||||
predecessor IDs, a persisted declaration change, rollback, and post-change
|
||||
health acceptance.
|
||||
- Keep bounded legacy-name fallback only for an explicit migration window.
|
||||
New defaults, UI probes, runbooks, and durable runtime names must use `ndc-`.
|
||||
|
||||
## Product UI governance
|
||||
|
||||
- For every Control Station, LAB, viewer, or product-presentation change, use
|
||||
`.codex/skills/mission-core-product-ui/SKILL.md` and follow
|
||||
`docs/17_PRODUCT_UI_AND_LAB_PRESENTATION_CANON.md` and
|
||||
`docs/18_APPLICATION_COMPONENT_ARCHITECTURE.md`.
|
||||
- The integrated Control Station and API have one canonical local endpoint:
|
||||
`http://127.0.0.1:8000`. Never start a second Mission Core backend on another
|
||||
port to bypass a stale process. TCP `8765` is legacy Foxglove regression
|
||||
only, not an operator path. Restart the canonical `8000` process and finish
|
||||
with no Mission Core backend listening on `8765`.
|
||||
- The canonical `8000` process is durable operator state. A completed test,
|
||||
build, browser QA pass, Codex turn, or implementation increment never
|
||||
authorizes stopping it. Every Control Station task starts by checking and, if
|
||||
necessary, starting the integrated service; every handoff verifies and leaves
|
||||
that exact service running on `8000`. A rebuild may replace it only when the
|
||||
replacement is confirmed listening before handoff.
|
||||
- `NODEDC_DESIGN_GUIDELINE` is the only visual-design source of truth. Before
|
||||
editing UI, read its `registry/registry.json`, `registry/components.json`,
|
||||
`registry/icons.json`, and the relevant component documentation.
|
||||
- Reuse `@nodedc/ui-react`, `@nodedc/ui-core`, tokens, icons, and page patterns.
|
||||
Do not create an application-local visual control, interaction state,
|
||||
geometry, color language, or copy of a design-system component.
|
||||
- If the required visual entity is absent from the Design Guideline, stop and
|
||||
obtain explicit product-owner approval. After approval, add it to the Design
|
||||
Guideline first with registry, documentation, states, and validation; only
|
||||
then consume it here.
|
||||
- Domain renderers may remain Mission Core code when they visualize Mission
|
||||
Core data. Their controls and containing surfaces must still be composed from
|
||||
canonical Design Guideline exports.
|
||||
- Product UI must not expose implementation steps, roadmap gates, internal
|
||||
reason taxonomies, debug controls, placeholder status blocks, or temporary
|
||||
experiment scaffolding. Keep these in Ops, engineering reports, or developer
|
||||
tooling.
|
||||
- Extend one reusable application pattern instead of adding per-LAB layouts.
|
||||
A new LAB supplies data and renderer configuration; it does not invent a new
|
||||
page hierarchy or visual language.
|
||||
- Do not generalize the LAB hierarchy into a universal application template.
|
||||
For a new non-LAB interface, follow
|
||||
`docs/19_PRODUCT_SURFACE_EXTENSION_PROTOCOL.md`: identify the operator job,
|
||||
compare placement/composition alternatives, classify the novelty, and obtain
|
||||
product-owner agreement before adding a workspace, changing primary
|
||||
navigation, or introducing a new product root.
|
||||
- Preserve one product grammar while allowing task-specific composition. A
|
||||
scene, map, timeline, queue, editor, topology, dashboard, and LAB review may
|
||||
have different layouts when their entity, lifecycle, or action model differs.
|
||||
- Preserve the internal dependency direction:
|
||||
`core → components/renderers → workspaces → composition/App`. Core never
|
||||
imports workspaces or visual adapters; reusable components never import
|
||||
workspaces. New domains and LAB runs get their own feature module instead of
|
||||
growing `App.tsx`, `Workspaces.tsx`, or generic CSS buckets.
|
||||
- Treat `productModel.ts`, executable versioned contracts, and the experimental
|
||||
vocabulary as the current local semantic sources. Do not add a parallel
|
||||
runtime ontology unless the admission conditions in
|
||||
`docs/18_APPLICATION_COMPONENT_ARCHITECTURE.md` are met.
|
||||
|
||||
## Laboratory presentation and reporting
|
||||
|
||||
- Use one fixed laboratory presentation contract: selectors, one compact
|
||||
canonical summary, admitted evidence viewer(s), result, and optional reusable
|
||||
technical details.
|
||||
- Keep `missioncore.laboratory-report/v1` executable: bounded `ENNResult.tsx`
|
||||
modules supply all four `LaboratorySummary` brief fields and use
|
||||
`LaboratoryResultSummary` for metrics plus proved/not-proved/decision. They
|
||||
never own canonical summary/result markup. Anatomy changes require a new
|
||||
version and product-owner agreement.
|
||||
- The compact summary must explain the decision question, immutable source,
|
||||
tested method/models/algorithms, experimental mode, principal result,
|
||||
limitations, and retained authority. It is not the engineering report.
|
||||
- The complete engineering report belongs in the Mission Core Ops card as
|
||||
titled structured blocks: objective and architecture stage, source evidence,
|
||||
method/models/algorithms, worker/runtime, implementation, validation,
|
||||
results, regressions and limitations, decision, next stage, and acceptance
|
||||
checker.
|
||||
- Spatial evidence uses 3D by default. Image-space reprojection may be offered
|
||||
as a 2D diagnostic mode inside the same reusable viewer. Primary evidence
|
||||
viewers must provide the canonical expand/restore action.
|
||||
|
||||
@@ -1,16 +1,52 @@
|
||||
# NDC XGRIDS K1 Connector
|
||||
# NODEDC MISSION CORE
|
||||
|
||||
Pre-production research project for connecting an owner-controlled
|
||||
XGRIDS/LixelKity K1 to a Mac without LixelGO, firmware changes, device opening,
|
||||
or speculative writes.
|
||||
NODEDC MISSION CORE is the vendor-neutral control, observation, mission-planning,
|
||||
recording, and integration platform for NODEDC autonomous systems. This
|
||||
repository is the canonical early-stage monorepo: it contains the Mission Core
|
||||
Control Station, the current local control plane, shared contracts as they are
|
||||
extracted, and device plugins.
|
||||
|
||||
Current status: live proof completed on firmware 3.0.2. The Mac provisioned the
|
||||
K1 onto an existing LAN without LixelGO, connected to its MQTT broker, captured
|
||||
the scan-correlated point-cloud and pose streams, and decoded both successfully.
|
||||
The repository also contains a local React control console, an automatic Rerun
|
||||
gRPC bridge for the verified point-cloud and trajectory streams, and a
|
||||
self-hosted Rerun Web Viewer embedded in that console. The former Foxglove
|
||||
bridge remains only as a legacy regression module.
|
||||
The first proven hardware vertical is the XGRIDS/LixelKity K1 plugin. On firmware
|
||||
3.0.2 the host provisions the scanner onto an existing LAN without LixelGO,
|
||||
connects to its MQTT broker, persists each raw frame before preview work, decodes
|
||||
point cloud and pose, and renders the real cloud plus trajectory through an
|
||||
embedded self-hosted Rerun Web Viewer. Native MQTT persistence uses bounded
|
||||
group commit (at most 0.5 seconds, 4 MiB or 32 messages), and camera archives
|
||||
commit complete fMP4 segments before their index rows. These are explicit
|
||||
crash-RPO bounds, not a zero-loss or disk-replication claim. The former
|
||||
Foxglove bridge remains only as a legacy regression module.
|
||||
|
||||
The backend vendor implementation is physically isolated below
|
||||
`src/k1link/device_plugins/xgrids_k1/`; its reviewed manifest and compatibility
|
||||
profile remain below `plugins/xgrids-k1/`. Mission Core host code discovers
|
||||
archive sources, recovery hooks and recording exporters only through the plugin
|
||||
runtime contribution. The verified wire protocol and raw evidence format are
|
||||
unchanged.
|
||||
|
||||
The K1 acquisition now also requires an operator project name. Frontend and
|
||||
backend normalize it with NFKC plus surrounding-whitespace trimming, reject
|
||||
control/surrogate characters and values above 96 Unicode characters, and
|
||||
preserve it as local session/catalog display metadata. It is never used as a
|
||||
filesystem path. The physically accepted interactive control path carries the
|
||||
same validated value in its single canonical START; there is no separate
|
||||
project-name write.
|
||||
|
||||
Plugin SDK v0alpha2 now provides executable, vendor-neutral identity, session,
|
||||
operation, runtime-action, stream, evidence and compatibility contracts. Every
|
||||
backend runtime must now pass a versioned descriptor/handshake against its
|
||||
manifest before actions are admitted. Every action then crosses immutable SDK
|
||||
`RuntimeActionInvocation` and `RuntimeActionResult` validation through a
|
||||
replaceable runtime transport seam. The meanings remain a local experimental
|
||||
vocabulary rather than a mutation of NODE.DC Platform Ontology. The current
|
||||
transport is deliberately in process: it uses an explicitly injected K1
|
||||
normalizer to produce transport-neutral local consumer views; portable SDK
|
||||
stream envelopes, process isolation, durable operations, multi-device routing
|
||||
and the remote Edge split remain later gates.
|
||||
|
||||
The application-control bootstrap reads and correlates live `DeviceInfo` before
|
||||
START. The exact profile requires model `LixelKity K1`, observed platform type
|
||||
`A4`, activated state, firmware `3.0.2`, and direct-LAN topology. The UI selects
|
||||
that profile but does not ask the operator to attest firmware manually.
|
||||
|
||||
The repository now contains one narrowly gated state-changing command:
|
||||
`ble wifi-configure`. It accepts only the reviewed firmware-3 provisioning
|
||||
@@ -18,12 +54,21 @@ profile and requires explicit `--confirm-write`; the Wi-Fi password is collected
|
||||
through a hidden local macOS dialog. MQTT capture and decoding are read-only.
|
||||
Nothing changes router settings, firmware or global Python packages.
|
||||
|
||||
## Available stand
|
||||
## Verified XGRIDS K1 stand
|
||||
|
||||
- one XGRIDS/LixelKity K1;
|
||||
- one Apple Silicon MacBook running macOS;
|
||||
- one ordinary TP-Link Deco/mesh network used by other devices;
|
||||
- no LixelGO, phone, Linux host, dedicated AP, OpenWrt, or vendor SDK.
|
||||
- the proven baseline used no LixelGO, phone, Linux host, dedicated AP,
|
||||
OpenWrt, or vendor SDK.
|
||||
|
||||
An owner-controlled iPhone with LixelGO is now available for a separate,
|
||||
evidence-only observation stage. It does not invalidate the no-phone baseline
|
||||
and is not a runtime dependency. The decision and gated runbook are
|
||||
[`ADR 0005`](docs/adr/0005-owner-controlled-lixelgo-iphone-observation.md) and
|
||||
[`docs/08_LIXELGO_IPHONE_OBSERVATION.md`](docs/08_LIXELGO_IPHONE_OBSERVATION.md).
|
||||
The Mac capture environment is isolated under
|
||||
`plugins/xgrids-k1/lab/iphone-capture/` and does not require full Xcode.
|
||||
|
||||
The ordinary router is sufficient for the first gates. We first observe the
|
||||
existing LAN without changing it. A Guest/IoT SSID is optional and may be
|
||||
@@ -39,10 +84,11 @@ The project has three independent gates:
|
||||
3. Without LixelGO, K1 can be associated with Wi-Fi and a proprietary data
|
||||
session can be opened.
|
||||
|
||||
All three gates are now proven on the tested unit. The external stream is plain
|
||||
All three gates are now proven on the tested unit. The spatial stream is plain
|
||||
MQTT 3.1.1 on TCP 1883. Firmware-3 `lio_pcl` is protobuf wrapped in a raw LZ4
|
||||
block, and `lio_pose` is an uncompressed protobuf. Raw panoramic camera access is
|
||||
still unproven and is not implied by point-cloud success.
|
||||
block, and `lio_pose` is an uncompressed protobuf. Owner-operated LixelGO capture
|
||||
also proved separate left/right RTSP/H.264 camera previews on TCP 8554. This is a
|
||||
compressed preview contract, not proof of full-resolution raw camera access.
|
||||
|
||||
## Local environment
|
||||
|
||||
@@ -51,26 +97,281 @@ This does not install Python packages globally and does not modify neighboring
|
||||
repositories.
|
||||
|
||||
```bash
|
||||
cd /Users/dcconstructions/Downloads/mnt/NODEDC/NDC_xgrids-k1-connector
|
||||
uv sync --group dev
|
||||
cd /Users/dcconstructions/Downloads/mnt/NODEDC/NODEDC_MISSION_CORE
|
||||
uv sync --frozen --group dev
|
||||
uv run k1link doctor
|
||||
uv run pytest
|
||||
```
|
||||
|
||||
## NODE.DC Control Station and visualization adapters
|
||||
## External perception worker
|
||||
|
||||
The browser application is now a universal NODE.DC Control Station rather than
|
||||
a K1-specific Foxglove launcher. Its fixed shell contains six architectural
|
||||
sections — Center, Fleet, Observation, Missions, Data and System — while the K1
|
||||
BLE/Wi-Fi/live workflow remains isolated as the first real device adapter.
|
||||
Mission Core can now package one sealed camera epoch as an immutable,
|
||||
content-addressed `missioncore.compute-job/v1` without changing the source
|
||||
evidence. The first recorded TEST007 job was digest-verified and decoded on a
|
||||
separate Windows RTX 4090 worker, executed through pinned Triton/YOLOX-S and
|
||||
returned a content-addressed `missioncore.compute-result/v1` containing 56
|
||||
session-timestamped generic COCO detections. The worker never connects to K1 and
|
||||
does not own the raw archive.
|
||||
|
||||
Mission Core now validates and projects that optional result into the same saved
|
||||
Rerun recording. Open TEST007 and use **Распознавание** to see the recorded
|
||||
left-camera frames and boxes; **Облако точек** returns to 3D. This remains a
|
||||
recorded camera-only acceptance, not a navigation stack: large-epoch
|
||||
performance, direct worker transport and bounded live fan-out remain open. See
|
||||
[ADR 0014](docs/adr/0014-bounded-external-perception-worker.md)
|
||||
and the [external worker contract](docs/10_EXTERNAL_PERCEPTION_WORKER.md).
|
||||
|
||||
## LiDAR Dataset Gateway
|
||||
|
||||
Mission Core now distinguishes a single native LiDAR scan, a normalized
|
||||
sensor-frame scan and a pose-registered rolling local map. The first Dataset
|
||||
Gateway adapter reads GOOSE/SemanticKITTI XYZI plus point-wise semantic and
|
||||
instance labels without silently accumulating or transforming them. Current
|
||||
K1 `lio_pcl` remains a post-LIO vendor-map increment and is not promoted to a
|
||||
raw scan. Large dataset bytes are admitted only on the Windows/WSL worker under
|
||||
`D:\NDC_MISSIONCORE\datasets`; no archive is downloaded automatically.
|
||||
|
||||
That representation boundary does not reject K1 as the production starting
|
||||
sensor. The passively received real-time `lio_pcl` plus `lio_pose` stream is
|
||||
the primary K1 input for two separate derivatives: a short-TTL local world
|
||||
model and a persistent territory reconstruction. `RAVNOVES00` is the immutable
|
||||
private replay dataset for this work. No K1 firmware change, onboard exporter
|
||||
or new device command is part of the LiDAR roadmap, and the historical
|
||||
`1.27 m` handheld-height experiment is not a runtime constant.
|
||||
|
||||
The first passive replay derivative is now implemented as
|
||||
`missioncore.k1-local-surface/v1`. It binds the immutable `RAVNOVES00` pack,
|
||||
estimates a rolling local surface from map points plus compatible pose, and
|
||||
publishes height, slope, roughness, confidence and conservative observed
|
||||
surface/occupied/unknown evidence. The complete pack contains `4,489` timeline
|
||||
frames, `3,928` available/valid LiDAR frames and `9,207,270` points; there were
|
||||
no stale-pose, insufficient-surface or fit-failed frames. Prior-only
|
||||
next-frame qualification produced `3,927` samples with `0.04065 m` p50 median
|
||||
residual and `205` strict temporal jumps. The p95 residual tail remains too
|
||||
large for planner use. The current complete derivative is the first item in
|
||||
**Парк → Диагностика LiDAR**.
|
||||
|
||||
The earlier content-bound `526`-frame review slice remains available as
|
||||
historical laboratory evidence. Its deterministic triage contains `37`
|
||||
attention frames in `21` episodes and four high-priority frames. The selected
|
||||
frame can switch to the prior-plane residual view; source frame `1254` has
|
||||
`36/98` evaluated cells above the `0.16 m` band and none below it. This
|
||||
localizes a positive residual structure without naming an object or granting
|
||||
safety authority.
|
||||
|
||||
The same estimator now runs behind
|
||||
`missioncore.k1-local-surface-shadow-runtime/v1`, a capacity-two
|
||||
latest-wins queue with a bounded diagnostic result ring. LAB E27 passed a
|
||||
`14.995 s` RAVNOVES00 slice at recorded 1× pace: `143/143` available frames
|
||||
were consumed, maximum queue depth was `1/2`, no frame was replaced, p95
|
||||
processing latency was `14.777 ms`, and state, point classes, step candidates
|
||||
and scalar results matched the immutable replay derivative exactly. This is a
|
||||
recorded-source-paced execution gate only; physical K1 acquisition,
|
||||
free-space, commands, navigation and safety authority remain unavailable.
|
||||
|
||||
The authenticated external-worker seam is now implemented and replay-qualified.
|
||||
It binds decoded map-frame `lio_pcl` and `lio_pose` in either arrival order
|
||||
through
|
||||
`missioncore.k1-local-surface-pose-binder/v1`, then feeds the accepted estimator
|
||||
without blocking camera cadence. Point input and estimator work are
|
||||
bounded separately: the authenticated ingress has LiDAR depth eight, while the
|
||||
freshness-first estimator remains capacity two. LAB E28 replayed the complete
|
||||
recording at 1× through the actual NVIDIA worker: all `4,570` LiDAR and `4,598`
|
||||
pose events arrived with zero gaps, all point frames bound, and `4,206`
|
||||
results were persisted at `9.1945 Hz`. Explicitly superseded work was
|
||||
`364/4,570` (`7.965%`), p95 result age was `89.530 ms`, and all acceptance
|
||||
checks passed. Physical K1 acquisition remains a separate commissioning test;
|
||||
it is not required for replay algorithm development.
|
||||
|
||||
The complete RELLIS-3D v1.1 release is now admitted there and its full
|
||||
`2,413`-frame validation split is available in **Полигон → Датасеты**. The
|
||||
sealed Current/Patchwork++ comparison rejected Patchwork++ for navigation:
|
||||
despite much lower latency and a small Ground-IoU gain, obstacle non-ground
|
||||
recall regressed from `80.46%` to `69.70%`.
|
||||
|
||||
Operator review of the later M4.8R3 LOW-STEP replay rejected its connected-
|
||||
component camera boxes as the product representation for general static
|
||||
obstacles. The replacement evaluation is terrain-relative and sequential:
|
||||
GSeg3D plus Nav2 Ground Consistency first, TRAVEL as the class-free challenger,
|
||||
and nvblox only as a separately measured occupancy/ESDF candidate. See the
|
||||
[traversability and static-obstacle stack decision](docs/23_TRAVERSABILITY_AND_STATIC_OBSTACLE_STACK_DECISION.md).
|
||||
|
||||
See [the Dataset Gateway plan](docs/14_LIDAR_DATASET_GATEWAY.md),
|
||||
[the laboratory run canon](docs/15_LABORATORY_RUN_CANON.md) and
|
||||
[ADR 0021](docs/adr/0021-dataset-gateway-representation-boundary.md).
|
||||
|
||||
## Simulation Polygon
|
||||
|
||||
Mission Core now has a parallel Polygon product branch for reproducible
|
||||
autonomy qualification. Mission Core owns scenarios, qualification-run
|
||||
lifecycle, authority, canonical contracts, provenance and reports; Gazebo, PX4
|
||||
SITL, ROS 2, Nav2 and viewers remain replaceable providers. Simulation, replay,
|
||||
digital twin, HIL and physical shadow keep distinct causal run kinds.
|
||||
|
||||
The provider boundary is now executable through
|
||||
`missioncore.simulation-provider-profile/v1`: worker status declares provider
|
||||
roles/capabilities, clock, ENU/FLU frames and supported control profiles.
|
||||
Gazebo/PX4 stays the accepted regression profile. Unreal is evaluated as a
|
||||
separate high-fidelity provider family; native Unreal, CARLA and Project AirSim
|
||||
must pass ADR 0017 U0 before a production adapter is selected. Unreal runs on a
|
||||
worker, never inside React; the existing browser scene, lifecycle, evidence and
|
||||
safety contracts are retained.
|
||||
|
||||
SIM S0 is accepted on the reviewed D-only worker. Its strict profile fixes
|
||||
loopback-only process isolation, exact provider pins, a 2 ms physics step,
|
||||
1×/2× RTF/resource budgets and disabled real/direct actuator authority. Two
|
||||
accepted-profile stock-rover runs and nine digest-bound evidence claims passed;
|
||||
the target doctor returned `GO`. The doctor remains read-only and a development
|
||||
host without the private evidence pack still returns `INCOMPLETE`:
|
||||
|
||||
```bash
|
||||
uv run missioncore-sim s0 doctor \
|
||||
--evidence /path/to/private/evidence.yaml \
|
||||
--json
|
||||
```
|
||||
|
||||
S1A now adds the first server-owned domain boundary: typed immutable
|
||||
`QualificationRun` identities, legal lifecycle transitions, canonical
|
||||
Ackermann/Differential commands, authority generation and TTL validation, and
|
||||
an append-only crash-safe run/event/command/artifact repository. Active runs
|
||||
recover as failed/interrupted rather than silently resuming, and reset creates
|
||||
a new linked run/episode. This is repository-level lifecycle acceptance only;
|
||||
PX4 command delivery, watchdog/failsafe behavior, navigation behavior and real
|
||||
actuator control remain unaccepted.
|
||||
|
||||
The S1B repository increment adds the transport-neutral
|
||||
`SimulationApplicationService`, an exact-S0/D-only worker admission guard and a
|
||||
POSIX process supervisor. Start is idempotent, a second active owner is
|
||||
rejected, provider processes receive dedicated PGIDs and run-scoped logs, and
|
||||
shutdown follows the S0 dependency order with residue reported as failure.
|
||||
Pause/resume/step/reset/stop are owned by the application service through a
|
||||
worker port.
|
||||
|
||||
Commit `6cb1495` has now passed the real D-only `MissionCore-Sim` S1B lifecycle:
|
||||
the exact immutable source generation started Micro XRCE-DDS Agent and the PX4
|
||||
v1.17.0/Gazebo Harmonic stock Ackermann rover in a fresh loopback-only
|
||||
namespace, admitted the run only after world/startup/DDS-writer health markers,
|
||||
and stopped both owned PGIDs with zero residue. The persisted run reached
|
||||
`completed`, contains eight lifecycle events and zero control commands. This
|
||||
run is now visible through UI-0: a direct internal read-only Control Station
|
||||
surface backed by the same append-only qualification repository. It exposes
|
||||
identity, terminal reason, provider pins, events, command count, artifact
|
||||
metadata and the virtual-only authority boundary. Live pause/resume/step/reset,
|
||||
canonical telemetry, PX4 command mapping, watchdog/failsafe evidence and the
|
||||
accepted command path remain gated.
|
||||
|
||||
S1C commit `b7ccca3` adds the first registered live-worker vertical without
|
||||
collapsing that boundary. An unprivileged worker agent owns the existing
|
||||
application service and providers inside a fresh loopback-only network
|
||||
namespace. Mission Core reaches it through a mode-`0600` Unix socket; React
|
||||
reaches only same-origin Polygon API routes. The live browser panel can
|
||||
start/stop the stock rover and show worker/run/provider status. UI-2 commit
|
||||
`60e7916` adds a worker-gated `Полигон` header root and a browser-native
|
||||
Three.js Ackermann Rover scene with orbit, zoom, pan, follow and reset. The
|
||||
deep-link remains for exact-run review; it is no longer the primary operator
|
||||
entry. Gazebo dynamic-pose ground truth remains explicitly `diagnostic`, not
|
||||
accepted PX4/ROS 2 telemetry.
|
||||
|
||||
The exact D-only UI-2 generation passed 35 target tests and a real UI-driven run
|
||||
`s1c-60e7916-20260724t182006z-2f164e`: both providers reached readiness, the
|
||||
browser received live `VehicleState`, the run reached `completed` after
|
||||
27.74 seconds of Gazebo time, and shutdown left no PX4/Gazebo/XRCE process
|
||||
residue. The run contained zero commands, so this result does not yet prove
|
||||
rover motion, PX4 command delivery, watchdog/failsafe behavior, navigation or
|
||||
safety acceptance.
|
||||
|
||||
S1D exact code generation `49b0f47` closes the first virtual Ackermann command
|
||||
and motion slice. React sends bounded speed/steering intents through the
|
||||
same-origin Mission Core API and private worker socket. The worker journals each
|
||||
accepted command before mapping it to PX4 rover-level Offboard setpoints:
|
||||
`RoverThrottleSetpoint` is calibrated against the stock model's
|
||||
`RO_MAX_THR_SPEED=3.1`, `RoverSteeringSetpoint` carries normalized steering,
|
||||
and PX4 retains arming, Offboard loss handling and control allocation. No
|
||||
`ActuatorMotors` or `ActuatorServos` topic is exposed or published by Mission
|
||||
Core.
|
||||
|
||||
The immutable D-only generation passed 40 focused target tests. Its first cold
|
||||
start completed in one request after 32.08 seconds. Run
|
||||
`s1c-49b0f47-20260724t195259z-62edd2` proved 3.74 m straight motion, a curved
|
||||
turn, reverse motion, 44.6 ms sampled command admission, a 250 ms TTL transition
|
||||
(`ttl_expired_count` 4 -> 5), stable stopped pose and clean provider shutdown
|
||||
with no PX4/Gazebo/XRCE residue. This does not accept calibrated closed-loop
|
||||
speed tracking, Differential Rover, canonical PX4/ROS 2 telemetry,
|
||||
pause/step/reset, heartbeat/offboard/link-loss cases, Nav2, perception or safety
|
||||
behavior. Real actuator authority remains false.
|
||||
|
||||
Start the worker from the exact immutable D source generation:
|
||||
|
||||
```bash
|
||||
sudo simulation/s1/worker-agent.sh <exact-40-character-mission-core-commit>
|
||||
```
|
||||
|
||||
Then configure the Mission Core backend. When the worker status is available,
|
||||
open `Полигон` from the Control Station header:
|
||||
|
||||
```bash
|
||||
export MISSIONCORE_POLYGON_RUNS_ROOT=/mnt/d/NDC_MISSIONCORE/simulation/artifacts/s1/runs
|
||||
export MISSIONCORE_POLYGON_WORKER_SOCKET=/run/missioncore-sim/worker.sock
|
||||
export MISSIONCORE_POLYGON_WORKER_CONTROL=internal-virtual-only
|
||||
export MISSIONCORE_COMMIT=<exact-40-character-mission-core-commit>
|
||||
uv run k1link serve
|
||||
```
|
||||
|
||||
```text
|
||||
http://127.0.0.1:8000/
|
||||
```
|
||||
|
||||
The direct `?workspace=polygon-run&run=<qualification-run-id>` URL remains a
|
||||
compatible evidence deep-link. The archive API remains GET-only. The worker API
|
||||
adds status/live GETs and explicit start, command and stop POSTs; POSTs remain
|
||||
`403` unless the backend has the `internal-virtual-only` gate and an exact
|
||||
commit. Missing configuration, transport drift and corrupt evidence fail
|
||||
closed. The browser receives no Unix socket, D root, artifact bytes, PX4
|
||||
endpoint or direct provider command.
|
||||
|
||||
Do not launch a second integrated backend on another port when `8000` is
|
||||
already occupied. Stop or restart the stale Mission Core process on `8000`.
|
||||
TCP `8765` remains reserved for the legacy Foxglove regression module and is
|
||||
not a Control Station operator path.
|
||||
|
||||
See the [Polygon product/SRS](docs/12_SIMULATION_POLYGON_PRODUCT_AND_SRS.md),
|
||||
[ADR 0015](docs/adr/0015-simulation-polygon-qualification-boundary.md),
|
||||
[ADR 0016](docs/adr/0016-distributed-product-edge-and-worker-topology.md),
|
||||
[ADR 0017](docs/adr/0017-provider-neutral-simulation-and-unreal-evaluation.md)
|
||||
and the
|
||||
[SIM S0 worker runbook](docs/runbooks/SIM_S0_AI_WORKER.md). The live-worker
|
||||
launch and acceptance procedure is in
|
||||
[SIM S1C live-worker runbook](docs/runbooks/SIM_S1C_LIVE_WORKER.md).
|
||||
|
||||
Mission Core remains a distributed browser product rather than a
|
||||
platform-specific desktop executable. React runs in the operator browser;
|
||||
Gateway/API and Control Plane run as long-lived services; Gazebo/PX4/ROS 2/Nav2
|
||||
run headless on a registered simulation worker; and a future field vehicle runs
|
||||
a headless onboard Linux Edge Agent with local-first evidence and
|
||||
store-and-forward synchronization. Native Gazebo GUI is worker-local
|
||||
diagnostics. The accepted live rover scene uses canonical state in Three.js;
|
||||
Rerun remains the recorded spatial-evidence path. Field execution never depends
|
||||
on streaming a simulator desktop.
|
||||
|
||||
## Mission Core Control Station and visualization adapters
|
||||
|
||||
The browser application is the universal Mission Core Control Station rather than
|
||||
a K1-specific Foxglove launcher. Its fixed shell contains Center, Fleet,
|
||||
Observation, Missions, Data and System; a seventh Polygon section is inserted
|
||||
only while an admitted Simulation Worker is available. The K1
|
||||
BLE/Wi-Fi/live workflow remains isolated as the first real device adapter. Its
|
||||
React provisioning, acquisition/replay and diagnostic blocks live beside the
|
||||
plugin manifest under `plugins/xgrids-k1/frontend`; the generic application
|
||||
mounts them through one reviewed composition import.
|
||||
|
||||
Install, type-check, build and serve the complete local application from the
|
||||
repository root:
|
||||
|
||||
```bash
|
||||
uv sync --group dev
|
||||
cd apps/k1-viewer
|
||||
npm install
|
||||
uv sync --frozen --group dev
|
||||
cd apps/control-station
|
||||
npm ci
|
||||
npm run test:unit
|
||||
npm run typecheck
|
||||
npm run build
|
||||
cd ../..
|
||||
@@ -80,45 +381,149 @@ uv run k1link serve
|
||||
Open `http://127.0.0.1:8000`. The static application, REST/WebSocket control
|
||||
plane and credential endpoint bind to loopback only. The current K1 adapter
|
||||
still provides real CoreBluetooth discovery, one operator-triggered reviewed
|
||||
BLE Wi-Fi provisioning write, read-only MQTT live capture, native `.k1mqtt` and
|
||||
reviewed-TSV replay, raw-first evidence storage and measured preview metrics.
|
||||
Physical K1 scanning is still started and stopped by the verified double-click;
|
||||
the connector publishes no modeling command.
|
||||
BLE Wi-Fi provisioning write, a one-shot CoreWLAN K1-AP association path,
|
||||
read-only MQTT live capture, native `.k1mqtt` and
|
||||
reviewed-TSV replay, raw-first evidence storage, device-reported scan
|
||||
time/distance/speed and measured preview metrics. Its frontend contribution now
|
||||
also owns an optional spatial-scene control block, including acquisition phase,
|
||||
telemetry and the stop action. Operator-manual acquisition still finalizes only
|
||||
local reception; plugin-commanded v0.5.0 acquisition uses the separately gated
|
||||
canonical K1 START/STOP dialogue.
|
||||
|
||||
Plugin v0.7.0 supervises the v0.6.0 local connection matrix with separate
|
||||
desired, configured and active modes plus exact DeviceInfo-backed Ready.
|
||||
Bridge remains the default and accepted product path; Direct Connect sends the reviewed station
|
||||
provisioning frame for an already-running controller hotspot. Quick Connect
|
||||
sends one separately reviewed AP-enable frame and can associate a prepared Mac
|
||||
through CoreWLAN. Its device activation and prepared-host association were
|
||||
physically accepted, but credential bootstrap is not portable: a clean host
|
||||
cannot acquire the firmware-defined AP material from the reviewed BLE protocol.
|
||||
Quick Connect therefore remains laboratory functionality, with no automatic
|
||||
firmware download, iPhone extraction or hard-coded fallback. Each mode has a
|
||||
distinct topology attestation and no automatic retry; see
|
||||
[ADR 0013](docs/adr/0013-k1-local-connection-matrix.md).
|
||||
|
||||
The exact recovered `ModelingRequest` start/stop encoder, response correlator
|
||||
and device-status state machine are installed in the operator-gated interactive
|
||||
control path.
|
||||
The plugin also contains the exact ten-request pre-START bootstrap recovered
|
||||
from retained LixelGO traffic. Its first `DeviceInfoRequest` has no device ID;
|
||||
the response supplies live vendor identity, serial, model, activation and
|
||||
version facts for the BLE-selected K1. Static and wire evidence identify OpenAPI
|
||||
as one private application-level value in the observed client, not a manual
|
||||
per-scanner profile. The whole pre-START order and START/STOP payloads are
|
||||
byte-matched offline. A fixed macOS Keychain loader now supplies the exact
|
||||
application authority without env/file/browser fallback, and a five-batch
|
||||
orchestrator passes all retained response barriers. The legacy shadow publisher
|
||||
boundary remains structurally write-disabled and cannot call its sink. Its
|
||||
dormant coordinator can hold authority
|
||||
for at most 300 seconds and expiry/disarm/reprovision/acquisition/shutdown revoke
|
||||
it together with the orchestrator. Plugin v0.5.0 installs a separate
|
||||
physical-acceptance transport reproducing MQTT 3.1.1 `clean_session=false`,
|
||||
keepalive 60, exact
|
||||
response subscriptions, QoS2 completion and the five response-gated batches.
|
||||
It consumes operation keys before publish and poisons unknown outcomes without
|
||||
retry. One background owner services that socket through response-gated
|
||||
connection, workspace, project, START, STOP and device-standby stages. The normal
|
||||
UI collapses only the pre-START controls into one explicit operator intent;
|
||||
navigation cannot emit a command. The first operator-present physical attempt on 2026-07-18 emitted only
|
||||
the first six bootstrap requests, then failed closed while correlating the third
|
||||
response batch; START was not emitted. A second one-shot attempt correlated all
|
||||
ten pre-START requests and the START acknowledgement, but incorrectly collapsed
|
||||
the whole exchange into 365 ms and ended the control lifecycle before the
|
||||
retained post-START reads. K1 stayed in `SCAN_STARTING` for about 24 seconds,
|
||||
reported `ALGORITHM_ERROR` twice, emitted no point/pose data and showed steady
|
||||
red until an operator power cycle restored steady green. The collapsed executor
|
||||
is now disabled. The offline acceptance executor maps connection requests 1–6,
|
||||
workspace-entry request 7, project-prompt reads 8–10, user-confirmed START 11,
|
||||
immediate status read 12 and state-gated reads 13–14 onto one continuously owned
|
||||
MQTT session. No captured operator delay is a protocol timer: the final reads
|
||||
wait for live `SCANNING`, project binding and `init_ready`, while STOP/save waits
|
||||
for live unbound `READY`. Standalone START and STOP
|
||||
are both rejected. The same socket is serviced throughout launch and STOP
|
||||
confirmation, and no captured human delay is replayed as a protocol timer.
|
||||
Plugin v0.5.0 wires this executor to the facade and plugin UI. The full staged
|
||||
START/live/STOP/save cycle is physically accepted on the reviewed unit. START
|
||||
and STOP each retain an explicit operator action; final local sealing now follows
|
||||
the scanner's protocol-reported unbound READY without a redundant second click.
|
||||
|
||||
This locked bootstrap is repeatable in the current workspace, not yet a
|
||||
standalone release install. The frontend consumes sibling `file:` packages from
|
||||
`NODEDC_DESIGN_GUIDELINE`; `package-lock.json` does not pin that checkout's Git
|
||||
revision or content hash. Publishing/vendoring those packages or enforcing an
|
||||
immutable donor revision remains a packaging and CI prerequisite.
|
||||
|
||||
Polygon is implemented as a capability-gated seventh section. When Mission Core
|
||||
confirms an admitted Simulation Worker, its header button opens one direct
|
||||
workspace with live 3D inspection and the server-owned qualification
|
||||
repository. `?workspace=polygon-run[&run=<id>]` remains a compatibility
|
||||
deep-link. Command and safety acceptance remain separate gates.
|
||||
|
||||
The Observation spatial workspace embeds the open-source Rerun Web Viewer
|
||||
inside the NODE.DC shell. It can open a compatible RRD file over HTTP(S) or a
|
||||
Rerun gRPC/proxy source such as `rerun+http://127.0.0.1:9876/proxy`. It does not
|
||||
use an external hosted viewer UI.
|
||||
inside the Mission Core shell. It can open a compatible RRD over same-origin
|
||||
HTTP or a Rerun gRPC/proxy source such as
|
||||
`rerun+http://127.0.0.1:9876/proxy`; no external hosted viewer UI is used.
|
||||
Dynamic point-cloud/camera composition and the live source contract are fixed in
|
||||
[`ADR 0006`](docs/adr/0006-vendor-neutral-observation-sources-and-live-only-timeline.md).
|
||||
The durable session catalog, recorded `session_time` scrubber and versioned
|
||||
workspace-layout profile are fixed in
|
||||
[`ADR 0008`](docs/adr/0008-durable-observation-sessions-and-workspace-layout.md).
|
||||
|
||||
The first K1 live session or replay in a `k1link serve` process creates one local
|
||||
Rerun `RecordingStream`, starts its gRPC/proxy server on TCP 9876 and publishes
|
||||
the resulting URL through control-plane state. Later sessions reset their
|
||||
session-local scene and metrics and reuse that process-wide stream; this avoids
|
||||
restarting the native listener while the embedded browser remains connected.
|
||||
The first K1 live session or adapter file-replay (`.k1mqtt`/reviewed TSV) in a
|
||||
`k1link serve` process creates one local Rerun `RecordingStream`, starts its
|
||||
gRPC/proxy server on TCP 9876 and publishes the resulting URL through
|
||||
control-plane state. Later live/file-replay sessions reset their session-local
|
||||
scene and metrics and reuse that process-wide stream. Saved observation sessions
|
||||
do not reuse this listener: they open a private digest-bound cache-v9 RRD
|
||||
generation over same-origin HTTP through Rerun's native incremental receiver.
|
||||
Unless an operator has entered a manual source, the React application assigns
|
||||
that URL to the embedded viewer. The complete runtime path is K1 MQTT → raw-first evidence
|
||||
capture → bounded latest-wins preview queue → reviewed protobuf/LZ4 decoders →
|
||||
Rerun `Points3D`, `Transform3D` and `LineStrips3D` → embedded Web Viewer.
|
||||
the applicable source to the embedded viewer. A generic host action clears a
|
||||
previous manual/archive selection only after a plugin-owned live or file-replay
|
||||
start succeeds; a failed start leaves the current scene mounted. A fail-closed
|
||||
guard blocks operator source switches for every nonterminal acquisition. The
|
||||
complete visual live runtime path is K1 MQTT → raw-first evidence capture →
|
||||
bounded four-message latest-wins preview queue → explicitly injected K1
|
||||
protobuf/LZ4 normalizer → transport-neutral decoded local views → Rerun
|
||||
`Points3D`, `Transform3D` and `LineStrips3D` → embedded Web Viewer. Rerun does
|
||||
not inspect K1 topics or raw payloads. These local decoded views are not yet the
|
||||
portable Plugin SDK wire envelopes. K1 `ModelingReport` status is consumed by an
|
||||
explicitly injected pre-preview observer, so scan telemetry cannot evict point
|
||||
or pose frames from that four-message queue.
|
||||
|
||||
The default Rerun blueprint shows a 12-second sliding accumulation of real point
|
||||
frames. Product controls are connected for point size, intensity/height/distance
|
||||
or available RGB coloring, Turbo/Viridis/Plasma/grayscale/custom palettes,
|
||||
point and trajectory visibility, and the scene grid. Projection, custom
|
||||
timeline transport and saved layout remain later product work. No synthetic
|
||||
point cloud, trajectory, camera frame or latency value is generated.
|
||||
point and trajectory visibility, and the scene grid. The first disk action saves
|
||||
and restores the versioned spatial layout without mutating sensor evidence.
|
||||
Saved native point/pose sessions are materialized losslessly into private,
|
||||
digest-bound RRD recordings and can be played, paused and scrubbed on a
|
||||
zero-based `session_time` timeline. New captures durably publish a clock origin
|
||||
before camera production, retain a transport-scoped provisional envelope and
|
||||
atomically point their summary to a content-addressed session envelope after all
|
||||
producers stop. That session envelope becomes the real RRD origin/end range;
|
||||
archived `ModelingReport` distance, speed and scan time are logged as scalar
|
||||
time series. No synthetic point cloud, trajectory, camera frame or latency
|
||||
value is generated.
|
||||
|
||||
A powered-device checkpoint passed 80 real MQTT messages through the current
|
||||
Rerun runtime: 38 point-cloud frames, 42 pose frames, 2,775 points in the last
|
||||
cloud and zero decode errors. Raw panoramic camera frames remain absent. Rerun
|
||||
`capture_time` is the Mac receive timestamp, not a proven K1 sensor timestamp or
|
||||
photon-to-screen measurement.
|
||||
cloud and zero decode errors. The later RTSP camera preview is available through
|
||||
the generic floating observation windows and new acquisitions archive its fMP4
|
||||
segments independently of browser delivery. Historical sessions recorded before
|
||||
that archive contract contain no video. A real archived session containing one
|
||||
camera plus point cloud has not yet passed physical shared-timeline playback;
|
||||
the implementation and test contract do not close that hardware gate. Rerun
|
||||
`capture_time` and the camera index use Mac receive/arrival timestamps, not
|
||||
proven K1 sensor timestamps or a photon-to-screen measurement.
|
||||
|
||||
The old Foxglove implementation is retained only in
|
||||
`src/k1link/viewer/foxglove_bridge.py` and its regression tests. The current
|
||||
`src/k1link/device_plugins/xgrids_k1/viewer/foxglove_bridge.py` and its regression tests. The current
|
||||
live/replay runtime does not start it or use TCP 8765. The
|
||||
[live viewer runbook](docs/06_K1_LIVE_VIEWER.md) records the active Rerun path and
|
||||
its timing/security boundaries; the frontend contract is documented in
|
||||
[`apps/k1-viewer/README.md`](apps/k1-viewer/README.md).
|
||||
[`apps/control-station/README.md`](apps/control-station/README.md).
|
||||
The host storage layout, recovery rules, replay API and operator path are in
|
||||
[`docs/09_OBSERVATION_SESSIONS.md`](docs/09_OBSERVATION_SESSIONS.md).
|
||||
|
||||
The FastAPI application and credential endpoint bind to loopback, but the Rerun
|
||||
gRPC server currently binds TCP 9876 on all network interfaces even though its
|
||||
@@ -165,12 +570,36 @@ present.
|
||||
|
||||
- [Technical audit](docs/00_TECHNICAL_AUDIT.md)
|
||||
- [Implementation gates](docs/01_IMPLEMENTATION_PLAN.md)
|
||||
- [Architecture audit execution roadmap](docs/16_ARCHITECTURE_AUDIT_EXECUTION_ROADMAP.md)
|
||||
- [Laboratory run canon](docs/15_LABORATORY_RUN_CANON.md)
|
||||
- [Product UI and laboratory presentation canon](docs/17_PRODUCT_UI_AND_LAB_PRESENTATION_CANON.md)
|
||||
- [Application component architecture](docs/18_APPLICATION_COMPONENT_ARCHITECTURE.md)
|
||||
- [Product surface extension protocol](docs/19_PRODUCT_SURFACE_EXTENSION_PROTOCOL.md)
|
||||
- [First lab runbook](docs/02_FIRST_LAB_RUNBOOK.md)
|
||||
- [Artifact and secret policy](docs/03_ARTIFACT_POLICY.md)
|
||||
- [Reviewed BLE Wi-Fi profile](docs/04_K1_WIFI_PROVISIONING_PROFILE.md)
|
||||
- [Verified MQTT stream profile](docs/05_K1_MQTT_STREAM_PROFILE.md)
|
||||
- [Live console and embedded Rerun runbook](docs/06_K1_LIVE_VIEWER.md)
|
||||
- [Mission Core monorepo and plugin boundary](docs/07_MISSION_CORE_MONOREPO.md)
|
||||
- [Owner-controlled LixelGO/iPhone observation](docs/08_LIXELGO_IPHONE_OBSERVATION.md)
|
||||
- [K1 application-control physical acceptance](docs/lab/003_K1_CONTROL_ACCEPTANCE_20260718.redacted.md)
|
||||
- [Observation sessions, playback and workspace layout](docs/09_OBSERVATION_SESSIONS.md)
|
||||
- [Monorepo architecture decision](docs/adr/0002-mission-core-monorepo.md)
|
||||
- [Device plugin UI and runtime boundary](docs/adr/0003-device-plugin-ui-and-runtime-boundary.md)
|
||||
- [Plugin SDK v0alpha2 and experimental device lifecycle](docs/adr/0004-plugin-sdk-v0alpha2-and-experimental-device-lifecycle.md)
|
||||
- [Owner-controlled LixelGO observation decision](docs/adr/0005-owner-controlled-lixelgo-iphone-observation.md)
|
||||
- [Vendor-neutral live observation sources](docs/adr/0006-vendor-neutral-observation-sources-and-live-only-timeline.md)
|
||||
- [K1 camera preview copy-remux gateway](docs/adr/0007-k1-camera-preview-copy-remux-gateway.md)
|
||||
- [Durable observation sessions and workspace layout](docs/adr/0008-durable-observation-sessions-and-workspace-layout.md)
|
||||
- [Device-plugin observation runtime and K1 extraction](docs/adr/0009-device-plugin-observation-runtime-and-k1-extraction.md)
|
||||
- [Plugin-owned frontend device workflows](docs/adr/0010-plugin-owned-frontend-device-workflows.md)
|
||||
- [Laboratory plugin runtime handshake and transport seam](docs/adr/0011-laboratory-plugin-runtime-handshake-and-transport-seam.md)
|
||||
- [Device-bound K1 command authority](docs/adr/0012-device-bound-k1-command-authority.md)
|
||||
- [Simulation Polygon qualification boundary](docs/adr/0015-simulation-polygon-qualification-boundary.md)
|
||||
- [Distributed product, edge and worker topology](docs/adr/0016-distributed-product-edge-and-worker-topology.md)
|
||||
- [Sensor representation capabilities and free-space admission](docs/adr/0023-sensor-representation-capabilities-and-free-space-admission.md)
|
||||
- [Redacted live lab report](docs/lab/001_K1_LIVE_MQTT_20260715.redacted.md)
|
||||
- [Canonical control and durable archive milestone](docs/lab/004_K1_CANONICAL_CONTROL_ARCHIVE_20260719.redacted.md)
|
||||
- [Session manifest schema](schemas/session-manifest.schema.json)
|
||||
- [Reference input provenance](docs/reference/README.md)
|
||||
|
||||
@@ -186,13 +615,15 @@ capture of traffic to or from the confirmed K1 address, and offline analysis of
|
||||
owned artifacts.
|
||||
|
||||
The reviewed provisioning write requires its named profile and explicit operator
|
||||
confirmation. Application command publishing remains disabled: physical
|
||||
double-click is the verified start/stop mechanism. Any future MQTT publisher,
|
||||
router configuration change or new BLE write requires its own evidence and
|
||||
confirmation. The interactive application-control publisher is enabled only for
|
||||
the exact K1/A4/FW 3.0.2 direct-LAN profile and only inside one operator-opened,
|
||||
response-gated session; physical double-click remains the independent fallback.
|
||||
The legacy shadow publisher remains structurally disabled. Any broader firmware,
|
||||
topology, scanner-family or command surface requires its own evidence and
|
||||
reviewed step. Random writes, fuzzing, brute force, firmware operations,
|
||||
destructive file access and credential guessing remain out of scope.
|
||||
|
||||
Real captures, projects, router metadata, serials, credentials, maps, images,
|
||||
and logs are ignored by normal Git. Redacted manifests and SHA-256 inventories
|
||||
are committed; encrypted artifact storage will be selected only when real data
|
||||
exists.
|
||||
and logs under `.runtime/`, canonical evidence roots and legacy `sessions/` are
|
||||
ignored by normal Git. Redacted manifests and SHA-256 inventories are committed;
|
||||
encrypted/replicated artifact storage remains a deployment decision.
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
# Mission Core applications
|
||||
|
||||
- `control-station/` — the current browser operator application.
|
||||
|
||||
Applications consume Mission Core contracts and capabilities. Device-specific
|
||||
transport and codecs belong under `plugins/`, not inside application routing
|
||||
or navigation.
|
||||
@@ -0,0 +1,356 @@
|
||||
# NODEDC MISSION CORE · Control Station
|
||||
|
||||
Универсальный браузерный пункт управления Mission Core на React 19, TypeScript и
|
||||
Vite. Приложение задаёт общую операторскую оболочку для аппаратов, сенсоров,
|
||||
наблюдения, миссий и записей. XGRIDS/LixelKity K1 является первым реальным
|
||||
device adapter, но структура интерфейса от него не зависит.
|
||||
|
||||
Внутри пространственной рабочей поверхности встроен открытый Rerun Web Viewer.
|
||||
Это self-hosted frontend-компонент из npm-пакета `@rerun-io/web-viewer`, а не
|
||||
переход во внешний облачный интерфейс. Для K1 live и adapter file-replay backend
|
||||
сам создаёт Rerun gRPC/proxy source. Saved observation sessions открывают
|
||||
проверенную digest-bound RRD generation через same-origin HTTP; ручной адрес
|
||||
нужен только для другого совместимого Rerun source.
|
||||
|
||||
## Текущее состояние
|
||||
|
||||
| Контур | Состояние | Что это означает |
|
||||
| --- | --- | --- |
|
||||
| Mission Core fixed shell | Реализован | Header, навигация по разделам, рабочая поверхность, окна и инспекторы работают в одном приложении. |
|
||||
| Device plugin registry | Реализован, v1alpha1 + v1alpha2 | До выбора модели provider остаётся inert и не делает I/O. v1alpha1 сохраняет одну модель; v1alpha2 допускает одну или несколько моделей и требует profile coverage каждой. Custom `device.connection` UI key и backend factory подключаются одним reviewed import в composition root. |
|
||||
| Plugin-owned device UI | Реализован | XGRIDS provisioning, project/acquisition/replay, diagnostics, metrics, optional spatial controls и scoped styles физически находятся в `plugins/xgrids-k1/frontend`; generic Control Station предоставляет frontend SDK, model catalog и host slots. |
|
||||
| Локальный control plane | Реализован | React получает состояние и выполняет операции через FastAPI REST и WebSocket на loopback. |
|
||||
| K1 BLE → Wi-Fi | Реализован | Реальный BLE-поиск всех видимых устройств и одна подтверждённая provisioning-запись выбранному устройству. |
|
||||
| K1 live/replay MQTT | Реализован | Read-only приём, обязательное локальное имя проекта, raw-first сохранение, декодирование облака/позы и device-reported scan time/distance/speed. |
|
||||
| Автоматический MQTT → Rerun | Реализован | Первый live/adapter file-replay поднимает process-wide `RecordingStream` и gRPC/proxy на TCP 9876; следующие такие сессии переиспользуют его. Saved observation replay использует отдельный immutable HTTP RRD path. |
|
||||
| Встроенный Rerun Viewer | Реализован | Self-hosted npm-компонент автоматически открывает текущий gRPC source внутри Control Station; внешний viewer не используется. |
|
||||
| Контролы сцены → Rerun | Реализованы для текущей геометрии | Работают размер и видимость точек, атрибут цвета, палитра, окно накопления, траектория, сетка, host timeline и сохранение/восстановление spatial layout. Проекция и семантические слои ещё не подключены. |
|
||||
| Сохранённые observation sessions | Реализованы для point/pose/device metrics | Три последние сессии, background preparation, capture-clock-bound cache v9, generation-bound RRD, atomic admission, autoplay, play/pause/seek и controlled switching больших записей. |
|
||||
| K1 camera preview и archive | Live реализован; recorded contract реализован | Обе RTSP/H.264 камеры физически приняты в live UI. Новые acquisition-owned fMP4 archives не зависят от browser windows; recorded player подключён, но point-cloud + one-camera archived playback ещё не прошёл physical acceptance. |
|
||||
| Legacy Foxglove module | Только regression | Модуль и тесты сохранены для сравнения декодирования. Текущий live/replay runtime не запускает Foxglove WebSocket и не использует TCP 8765. |
|
||||
| Карты и миссии | Интерфейсный каркас | Реальные map/mission backends и vehicle control ещё не подключены. |
|
||||
|
||||
Приложение не генерирует демонстрационное облако, траекторию, кадры или
|
||||
метрики. Если реальных данных нет, область сцены остаётся пустой, а числовые поля
|
||||
показывают `—`.
|
||||
|
||||
## Архитектура данных
|
||||
|
||||
```text
|
||||
K1 MQTT :1883, read-only
|
||||
└── raw .k1mqtt + metadata + durable clock origin сохраняются первыми
|
||||
├── injected ModelingReport observer -> device scan time/distance/speed
|
||||
└── bounded latest-wins visual preview queue (4 сообщения)
|
||||
└── явно внедрённый K1 protobuf/LZ4 point/pose normalizer
|
||||
└── transport-neutral DecodedPointCloudView / DecodedPoseView
|
||||
└── Rerun Points3D + Transform3D + LineStrips3D
|
||||
└── gRPC/proxy TCP 9876
|
||||
└── rerun_grpc_url в REST/WebSocket state
|
||||
└── встроенный @rerun-io/web-viewer
|
||||
└── пространственная сцена Mission Core
|
||||
|
||||
Mission Core Control Station ←→ REST /api/v1/device-plugins/*
|
||||
+ plugin-scoped WebSocket events
|
||||
FastAPI на 127.0.0.1:8000
|
||||
CoreBluetooth + live/replay runtime
|
||||
|
||||
sealed/recovered observation session
|
||||
└── SQLite catalog + bounded background preparation
|
||||
└── capture-clock-bound atomic RRD cache v9 + recorded-media manifest v2
|
||||
└── generation-bound same-origin HTTP
|
||||
└── aggregate admission
|
||||
└── Rerun native receiver + recorded fMP4 player
|
||||
```
|
||||
|
||||
В момент готовности live/file-replay `RerunBridge` backend публикует адрес вида
|
||||
`rerun+http://127.0.0.1:9876/proxy`. Frontend автоматически назначает его сцене,
|
||||
если оператор не указал ручной source. После остановки приёма URL и встроенный
|
||||
viewer остаются активны, а следующая сессия сбрасывает session-local геометрию,
|
||||
траекторию и метрики и использует тот же listener. Он закрывается вместе с
|
||||
процессом `k1link serve`.
|
||||
|
||||
Поля `foxglove_ws_url` и `foxglove_viewer_url` пока остаются в API как
|
||||
совместимость со старым контрактом, но текущий runtime держит их пустыми.
|
||||
|
||||
## Фиксированная оболочка
|
||||
|
||||
Shell собран из локальных NODE.DC UI packages и сохраняет одну структуру для
|
||||
всех функциональных модулей:
|
||||
|
||||
1. `AppHeader` — марка NODE DC, выбор архитектурного раздела и состояние
|
||||
локального backend.
|
||||
2. `AdminNavigationPanel` — контекст аппарата и список рабочих поверхностей
|
||||
выбранного раздела.
|
||||
3. `LandingStage` — стартовая ситуационная поверхность и быстрые переходы.
|
||||
4. `ApplicationPanel` — единый контейнер активной рабочей поверхности.
|
||||
5. `Window` и `Inspector` — источник, отображение, слои и компоновка без
|
||||
раскрытия внутренних панелей визуального движка.
|
||||
|
||||
Встроенный Rerun Viewer работает как canvas внутри этой оболочки. Его верхняя,
|
||||
blueprint-, selection- и time-панели скрыты, чтобы продуктовые действия жили в
|
||||
Control Station. Размер точек, способ окрашивания и палитра, 12-секундное по
|
||||
умолчанию накопление, видимость облака и траектории и сетка связаны с backend и
|
||||
Rerun Blueprint. Host timeline управляет сохранённым `session_time`, а spatial
|
||||
layout сохраняет display settings, tool windows и source-window geometry через
|
||||
revisioned API. Смена 2D/3D/карты и семантические слои пока остаются
|
||||
интерфейсным контрактом.
|
||||
|
||||
## Архитектурные разделы
|
||||
|
||||
Навигация описана данными в `src/productModel.ts`, а не зашита в разметку каждой
|
||||
страницы.
|
||||
|
||||
| Раздел | Назначение |
|
||||
| --- | --- |
|
||||
| Центр | Живое состояние вычислительных узлов, процессов, сети и подключённых устройств. |
|
||||
| Парк | Аппараты, текущее устройство, сенсоры и конфигурации борта. |
|
||||
| Наблюдение | Только live-пространственная сцена, камеры, карта, объекты, телеметрия и время. |
|
||||
| Миссии | Планировщик, маршруты, сценарии и исполнение. Командный backend отключён. |
|
||||
| Данные | Сохранённые сессии, replay, публичные датасеты, потоки, сущности и экспорт. |
|
||||
| Система | Модули, интеграции, сеть, аудит и настройки платформы. |
|
||||
| Тестировочный контур | Архив лабораторных работ, конфигурации экспериментов и визуальные результаты. |
|
||||
|
||||
Карточки возможностей имеют четыре честных уровня: работает сейчас, готово к
|
||||
источнику, интерфейсный контракт и последующий этап. Каталог не следует читать
|
||||
как утверждение, что для каждой карточки уже существует backend.
|
||||
|
||||
## Установка, проверка, сборка и запуск
|
||||
|
||||
Требуются Node.js 20.19+ либо 22.12+, `uv` и соседний checkout
|
||||
`NODEDC_DESIGN_GUIDELINE`: зависимости `@nodedc/*` подключены к нему через
|
||||
локальные `file:` пути. Python устанавливается только в `.venv` репозитория.
|
||||
|
||||
Канонический путь от корня репозитория:
|
||||
|
||||
```bash
|
||||
cd /Users/dcconstructions/Downloads/mnt/NODEDC/NODEDC_MISSION_CORE
|
||||
uv sync --frozen --group dev
|
||||
|
||||
cd apps/control-station
|
||||
npm ci
|
||||
npm run test:unit
|
||||
npm run typecheck
|
||||
npm run build
|
||||
|
||||
cd ../..
|
||||
uv run k1link serve
|
||||
```
|
||||
|
||||
Открыть `http://127.0.0.1:8000`. Команда `serve` отдаёт собранный `dist/` и
|
||||
локальный API. Она намеренно привязана только к `127.0.0.1`; LAN bind не
|
||||
предусмотрен, потому что endpoint подключения кратковременно принимает пароль
|
||||
Wi-Fi.
|
||||
|
||||
Этот locked bootstrap повторяем в текущем workspace, но он ещё не является
|
||||
standalone release install. Локальные `file:` зависимости берутся из соседнего
|
||||
`NODEDC_DESIGN_GUIDELINE`, а lockfile не фиксирует Git revision или content hash
|
||||
этого checkout. До CI/portable packaging эти пакеты нужно опубликовать,
|
||||
завендорить либо проверять по неизменяемой donor revision.
|
||||
|
||||
Для разработки интерфейса после запуска backend:
|
||||
|
||||
```bash
|
||||
cd apps/control-station
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Для полного live-viewer контура Vite должен открыться на
|
||||
`http://127.0.0.1:5173`: только 5173, production preview 4173 и backend 8000
|
||||
входят в текущий Rerun CORS allowlist. Если Vite сообщает fallback на 5174 или
|
||||
выше, освободите 5173 и перезапустите `npm run dev`; REST/WebSocket proxy на
|
||||
fallback-порту может работать, но прямой browser → Rerun 9876 будет отклонён.
|
||||
Vite проксирует весь `/api` (включая WebSocket) на `http://127.0.0.1:8000`.
|
||||
Другой локальный backend можно указать переменной `VITE_API_TARGET`. Preview
|
||||
production-сборки запускается командой `npm run preview` на
|
||||
`http://127.0.0.1:4173`.
|
||||
|
||||
## Операторский путь для текущего K1 adapter
|
||||
|
||||
1. Запустить `uv run k1link serve` и открыть Mission Core Control Station.
|
||||
2. Выбрать **Парк → Локальное устройство**.
|
||||
3. В каталоге моделей выбрать **XGRIDS LixelKity K1**. Только после этого
|
||||
активируется runtime и монтируется custom UI XGRIDS-плагина.
|
||||
4. Включить K1, дождаться стабильного индикатора, вручную сверить firmware
|
||||
`3.0.2` и direct-LAN топологию, затем подтвердить это в форме. Mission Core
|
||||
не читает firmware с устройства; такое подтверждение остаётся
|
||||
`operator-attested`, а не device-derived evidence.
|
||||
5. Нажать **Показать все BLE-устройства**. Интерфейс показывает полный результат
|
||||
шестисекундного поиска; метка совместимости является подсказкой, выбор делает
|
||||
оператор.
|
||||
6. Ввести SSID и пароль существующей сети и явно запустить подключение. Это одна
|
||||
reviewed provisioning-запись без автоматических повторов.
|
||||
7. Ввести обязательное название проекта. Оно NFKC-нормализуется, trim-ится,
|
||||
проверяется на control/surrogate characters и лимит 96 Unicode characters,
|
||||
сохраняется как display metadata и не используется в filesystem path.
|
||||
8. Запустить локальный live-приём по определённому адресу K1 либо replay
|
||||
локального `.k1mqtt`/проверенного TSV. Физическое сканирование K1 запускается
|
||||
и останавливается подтверждённым двойным нажатием кнопки устройства.
|
||||
9. Backend автоматически поднимет Rerun gRPC на TCP 9876 и опубликует адрес в
|
||||
state. Ручной source вводить не требуется.
|
||||
10. Открыть **Наблюдение → Пространственная сцена**. Plugin-owned spatial block
|
||||
показывает local acquisition phase, stop local reception и полученные от K1
|
||||
scan time/distance/speed; облако, траектория и preview metrics появляются
|
||||
только после реальных сообщений.
|
||||
11. Физически остановить K1, дождаться steady green, затем остановить локальный
|
||||
приём из device workflow или spatial block, чтобы запечатать evidence.
|
||||
12. После нормального stop или recovery открыть **Данные → Сессии и записи**. Дождаться
|
||||
состояния **Готово**, выбрать запись и использовать host timeline. Evidence
|
||||
сохраняется автоматически; disk action сохраняет только workspace layout.
|
||||
|
||||
В проверочном live-сеансе через этот путь прошло 80 реальных MQTT-сообщений:
|
||||
38 кадров `lio_pcl`, 42 кадра `lio_pose`, 2 775 точек в последнем облаке и
|
||||
0 ошибок декодирования.
|
||||
|
||||
## Источники Rerun
|
||||
|
||||
Live/replay runtime автоматически назначает первый URL из примера. В
|
||||
**Наблюдение → Пространственная сцена → Источник** можно оставить ручное поле
|
||||
пустым либо указать другой адрес, который понимает `@rerun-io/web-viewer`
|
||||
зафиксированной в `package.json` версии:
|
||||
|
||||
```text
|
||||
rerun+http://127.0.0.1:9876/proxy
|
||||
http://127.0.0.1:8080/recording.rrd
|
||||
https://example.internal/recording.rrd
|
||||
```
|
||||
|
||||
- `rerun+http://…/proxy` — живой Rerun gRPC source через доступный браузеру
|
||||
proxy;
|
||||
- `http(s)://…/recording.rrd` — RRD-запись по HTTP(S);
|
||||
- версия RRD должна быть совместима с версией Web Viewer;
|
||||
- ручной URL хранится только в состоянии текущей страницы и имеет приоритет над
|
||||
автоматическим source;
|
||||
- nonterminal acquisition fail-closed блокирует manual source apply/reset,
|
||||
saved session switch и persisted replay reattach до завершения текущего
|
||||
приёма; automatic-source action очищает старый source и открывает сцену только
|
||||
после successful start, failed start сохраняет текущую сцену, а guard не
|
||||
останавливает устройство;
|
||||
- ошибка запуска показывается в viewport, без подстановки фиктивных данных.
|
||||
|
||||
После готовности Viewer выбор Rerun entity возвращает `entityPath` и имя view в
|
||||
оболочку. Backend принимает и применяет к Rerun размер точки `0.5–12.0`, режимы
|
||||
цвета `intensity`, `height`, `distance`, `rgb`, `class`, палитры Turbo, Viridis,
|
||||
Plasma, grayscale и custom, накопление `0–120` секунд, а также видимость облака,
|
||||
траектории и сетки. Значение по умолчанию — 12 секунд истории реальных кадров.
|
||||
Для текущего firmware-3 `lio_pcl` доказана только интенсивность из младшего
|
||||
байта `rgbi`; RGB используется лишь когда он действительно присутствует в
|
||||
декодированном формате. Custom/class сейчас означает выбранный сплошной цвет,
|
||||
а не готовую семантическую классификацию.
|
||||
|
||||
Для saved session host timeline вызывает Rerun play/pause/seek только после
|
||||
полного admission. Accumulation и display settings применяются через отдельный
|
||||
маленький blueprint RRD и не заменяют основную запись. Versioned
|
||||
`observation.spatial` layout сохраняется на host с optimistic revision; он не
|
||||
содержит sensor evidence. Переключатель 2D/3D/карты и семантические слои пока не
|
||||
вызывают готовый presentation backend.
|
||||
|
||||
## Контракт локального API
|
||||
|
||||
| Метод | Route | Тело / назначение |
|
||||
| --- | --- | --- |
|
||||
| `GET` | `/api/health` | Проверка локального сервиса. |
|
||||
| `GET` | `/api/v1/device-plugins` | Валидированные manifests установленных device plugins. |
|
||||
| `GET` | `/api/v1/device-models` | Backend-каталог моделей из валидированных manifests; UI-каталог текущей сборки формируется отдельным static composition root. |
|
||||
| `POST` | `/api/v1/device-plugins/{pluginId}/actions/{actionId}` | Namespaced действие через host allowlist; тело `{ "input": { ... } }`. |
|
||||
| `WS` | `/api/v1/device-plugins/{pluginId}/events` | Plugin-scoped snapshots с `pluginId` и монотонным `sequence`. |
|
||||
| `GET` | `/api/v1/observation-sessions` | Последние cataloged sessions и authoritative preparation state. |
|
||||
| `POST` | `/api/v1/observation-sessions/{id}/replay` | Verified replay launch либо HTTP 202 preparation handle. |
|
||||
| `GET` | `/api/v1/observation-sessions/{id}/recording-preparation` | Exact-job polling с `If-Match`. |
|
||||
| `GET` | `/api/v1/observation-sessions/{id}/recording.rrd` | Immutable generation-bound RRD; arbitrary chunked `send_rrd` не используется. |
|
||||
| `GET` | `/api/v1/observation-sessions/{id}/media/{artifact}/...` | Manifest/init/segments записанных камер по opaque identifiers. |
|
||||
| `GET`, `PUT` | `/api/v1/workspace-layouts/observation.spatial` | Versioned host layout с optimistic revision. |
|
||||
|
||||
Frontend XGRIDS-плагина использует только v1alpha namespaced routes. Старые
|
||||
`/api/state`, `/api/events`, `/api/ble/scan`, `/api/connect`, `/api/session/*` и
|
||||
`/api/viewer/settings` сохранены как deprecated compatibility shims внутри
|
||||
XGRIDS backend contribution.
|
||||
|
||||
В v1alpha1/v1alpha2 backend composition загружается из manifests, а frontend plugins
|
||||
статически включаются в сборку через `src/composition/devicePlugins.ts`.
|
||||
Автоматической runtime-сверки двух installed sets пока нет: их соответствие —
|
||||
проверяемое требование сборки до появления подписанных plugin bundles и startup
|
||||
compatibility handshake. Поля manifest `permissions`, `mutating` и
|
||||
`secretFields` пока являются декларативными метаданными; generic host валидирует
|
||||
их форму и action allowlist, но ещё не реализует на их основе RBAC, подтверждения
|
||||
оператора или secret-vault substitution.
|
||||
Точный K1 compatibility profile остаётся неактивным, пока оператор явно не
|
||||
подтвердит firmware `3.0.2` и direct-LAN топологию. Runtime не получает версию
|
||||
firmware с устройства и явно помечает основание как `operator-attested`.
|
||||
Ошибки валидации namespaced action endpoint и deprecated `/api/connect`
|
||||
возвращаются как общий `422` без исходных значений запроса, поэтому отклонённое
|
||||
значение credential-поля не отражается клиенту.
|
||||
|
||||
Legacy `/api/state` и изменяющие состояние ответы могут вернуть snapshot напрямую или
|
||||
как `{ "state": { ... } }`. v1alpha2 snapshot отдельно содержит `device_ref`,
|
||||
`device_session`, `acquisition`, `operations`, compatibility/calibration/sensor
|
||||
facts и старые presentation-поля `phase`, `message`, `devices`,
|
||||
`selected_device_id`, `k1_ip`, `source_mode`, `metrics`, `rerun_grpc_url` и
|
||||
`viewer_settings`. Legacy-поля `foxglove_ws_url`/`foxglove_viewer_url` остаются
|
||||
пустыми. OpenAPI доступен по `/api/docs`.
|
||||
|
||||
## Карта исходников frontend
|
||||
|
||||
| Файл | Ответственность |
|
||||
| --- | --- |
|
||||
| `src/App.tsx` | Fixed shell, выбор разделов и окна source/display/layers/layout. |
|
||||
| `src/productModel.ts` | Архитектурные разделы, рабочие поверхности и уровни готовности. |
|
||||
| `src/core/device-plugins/` | Vendor-neutral manifest parser, registry, lifecycle, plugin host и public frontend SDK surface. |
|
||||
| `src/core/runtime/` | Нормализованное состояние активного устройства, spatial source и fail-closed source-switch guard. |
|
||||
| `src/composition/devicePlugins.ts` | Единственный allowlist импортов конкретных device plugins. |
|
||||
| `src/workspaces/DeviceWorkspace.tsx` | Generic выбор модели и `device.connection` slot. |
|
||||
| `../../plugins/xgrids-k1/frontend/` | Plugin-owned K1 BLE/Wi-Fi, project/acquisition/replay UI, optional spatial controls, API client, runtime mapper и scoped styles. |
|
||||
| `src/workspaces/Workspaces.tsx` | Оперативный обзор, spatial viewport и остальные продуктовые поверхности. |
|
||||
| `src/components/RerunViewport.tsx` | Live/recorded lifecycle WebViewer, native RRD open, atomic admission, playback и selection events. |
|
||||
| `src/components/ObservationSessionSelect.tsx` | Три последние сессии, состояния `Готово` / `Обработка` / `Ошибка`. |
|
||||
| `src/components/RecordedFmp4Player.tsx` | Проверенный generation-bound MSE playback архивных камер. |
|
||||
| `src/core/observation/sessionArchive.ts` | Строгий wire contract catalog/preparation/replay/media API. |
|
||||
| `src/core/observation/recordedSessionAdmission.ts` | Общий RRD/camera gate до публикации recorded workspace. |
|
||||
| `src/core/observation/workspaceLayout.ts` | Versioned spatial layout и optimistic revision. |
|
||||
| `src/sceneSettings.ts` | Типизированный UI-профиль пространственной сцены. |
|
||||
| `src/presentation.ts` | Vendor-neutral подписи lifecycle и форматирование нормализованных метрик. |
|
||||
| `src/styles.css`, `src/styles/*` | Компоновка shell и рабочих поверхностей. |
|
||||
|
||||
## Safety и чувствительные данные
|
||||
|
||||
- Mission Core Control Station и credential endpoint доступны только на loopback.
|
||||
- Исключение: Rerun gRPC/proxy на TCP 9876 сейчас слушает все сетевые интерфейсы,
|
||||
хотя автоматически возвращаемый URL содержит `127.0.0.1`. На нём нет
|
||||
connector-level authentication или TLS. Этот порт допустим только в доверенной
|
||||
лабораторной LAN; его нельзя пробрасывать в публичный Интернет или cellular
|
||||
WAN без аутентифицированного TLS reverse proxy. Listener намеренно остаётся
|
||||
активным между сессиями; для его закрытия нужно остановить `k1link serve`.
|
||||
- Пароль Wi-Fi находится только в React memory, передаётся в JSON POST body,
|
||||
очищается после успешного ответа и не сохраняется в URL/local storage.
|
||||
- Bridge и Direct Connect выполняют только отдельно рассмотренную 99-байтную
|
||||
provisioning-запись. Quick Connect выполняет отдельную рассмотренную
|
||||
100-байтную AP-enable запись и после AP-ready допускает ровно одну системную
|
||||
ассоциацию с заранее материализованным exact-firmware профилем. Пароль не
|
||||
попадает в браузер/API/argv; системный Wi-Fi Keychain, post-write prompt и
|
||||
автоматические повторы запрещены.
|
||||
- MQTT data live/replay остаётся subscribe-only. Команды изолированы в
|
||||
plugin-owned canonical control session и доступны только через отдельные
|
||||
operator-present UI checkpoints.
|
||||
- Exact start/stop codec, response correlator и device-status state machine не
|
||||
владеют transport. Plugin v0.5.0 связывает их отдельным single-owner MQTT
|
||||
контуром без wall-clock переходов, reconnect или automatic retry; после STOP
|
||||
обязательны live READY и подтверждённый постоянный зелёный индикатор.
|
||||
- Live-сессии сначала сохраняют сырые сообщения и camera segments, затем
|
||||
формируют disposable preview. При перегрузке preview может быть отброшен,
|
||||
native evidence сохраняется. MQTT durability имеет bounded group-commit RPO,
|
||||
camera durability — текущий незавершённый fragment RPO; это не zero-loss claim.
|
||||
- Новая session summary hash-bind-ит durable clock origin и атомарно переключает
|
||||
pointer с provisional `mqtt.timeline.json` на content-addressed
|
||||
`mqtt.timeline.session-<sha256>.json`: только owner-sealed `session` scope
|
||||
может рекламировать combined media replay. Cache v9 создаёт реальные
|
||||
origin/end RRD rows; provisional `transport` scope fails closed.
|
||||
- Timeline `capture_time` сохраняет Unix-время приёма сообщения Mac, а окно
|
||||
накопления viewer использует session-local `stream_time`. `capture_time` — не
|
||||
доказанный timestamp сенсора K1 и не photon-to-screen latency.
|
||||
- Панорамный камерный поток в MQTT report topics отсутствует; отдельные
|
||||
left/right RTSP preview доступны через generic camera windows. LiDAR и camera
|
||||
сейчас синхронизированы только по host arrival, не по доказанным sensor clocks.
|
||||
Физический archived playback point cloud + одна выбранная камера ещё не принят.
|
||||
- `.runtime/`, canonical evidence roots и legacy `sessions/` игнорируются Git и
|
||||
могут содержать адреса, изображения, идентификаторы, траекторию и карту
|
||||
помещения. В репозиторий попадают только код, тесты, redacted manifests и
|
||||
безопасная документация.
|
||||
@@ -5,10 +5,10 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Пункт управления NODE.DC — наблюдение, данные и планирование миссий беспилотных аппаратов."
|
||||
content="NODEDC MISSION CORE — наблюдение, данные и планирование миссий беспилотных аппаратов."
|
||||
/>
|
||||
<meta name="theme-color" content="#08090b" />
|
||||
<title>NODE.DC · Пункт управления</title>
|
||||
<title>NODEDC MISSION CORE</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
+136
-11
@@ -1,52 +1,77 @@
|
||||
{
|
||||
"name": "@nodedc/k1-viewer",
|
||||
"name": "@nodedc/mission-core-control-station",
|
||||
"version": "0.1.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@nodedc/k1-viewer",
|
||||
"name": "@nodedc/mission-core-control-station",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@noble/hashes": "^2.2.0",
|
||||
"@nodedc/map-cesium-react": "file:../../../NODEDC_DESIGN_GUIDELINE/packages/map-cesium-react",
|
||||
"@nodedc/page-patterns": "file:../../../NODEDC_DESIGN_GUIDELINE/packages/page-patterns",
|
||||
"@nodedc/tokens": "file:../../../NODEDC_DESIGN_GUIDELINE/packages/tokens",
|
||||
"@nodedc/ui-core": "file:../../../NODEDC_DESIGN_GUIDELINE/packages/ui-core",
|
||||
"@nodedc/ui-react": "file:../../../NODEDC_DESIGN_GUIDELINE/packages/ui-react",
|
||||
"@rerun-io/web-viewer": "0.34.1",
|
||||
"@rerun-io/web-viewer": "0.36.3",
|
||||
"meshoptimizer": "1.1.1",
|
||||
"playcanvas": "2.21.4",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0"
|
||||
"react-dom": "^19.1.0",
|
||||
"three": "0.185.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.0.0",
|
||||
"@types/react": "^19.1.0",
|
||||
"@types/react-dom": "^19.1.0",
|
||||
"@types/three": "0.185.1",
|
||||
"@vitejs/plugin-react": "^4.6.0",
|
||||
"typescript": "^5.8.3",
|
||||
"vite": "^7.0.0",
|
||||
"vite-plugin-wasm": "^3.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"../../../NODEDC_DESIGN_GUIDELINE/packages/map-cesium-react": {
|
||||
"name": "@nodedc/map-cesium-react",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"cesium": "1.143.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^19.1.0",
|
||||
"react": "^19.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=18"
|
||||
}
|
||||
},
|
||||
"../../../NODEDC_DESIGN_GUIDELINE/packages/page-patterns": {
|
||||
"name": "@nodedc/page-patterns",
|
||||
"version": "0.1.0"
|
||||
},
|
||||
"../../../NODEDC_DESIGN_GUIDELINE/packages/tokens": {
|
||||
"name": "@nodedc/tokens",
|
||||
"version": "0.6.0"
|
||||
},
|
||||
"../../../NODEDC_DESIGN_GUIDELINE/packages/ui-core": {
|
||||
"name": "@nodedc/ui-core",
|
||||
"version": "0.6.0",
|
||||
"version": "0.7.0",
|
||||
"dependencies": {
|
||||
"@nodedc/tokens": "0.6.0"
|
||||
}
|
||||
},
|
||||
"../../../NODEDC_DESIGN_GUIDELINE/packages/ui-react": {
|
||||
"name": "@nodedc/ui-react",
|
||||
"version": "0.6.0",
|
||||
"version": "0.7.0",
|
||||
"dependencies": {
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"@nodedc/ui-core": "0.6.0",
|
||||
"@nodedc/ui-core": "0.7.0",
|
||||
"lucide-react": "^0.468.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -342,6 +367,13 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@dimforge/rapier3d-compat": {
|
||||
"version": "0.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@dimforge/rapier3d-compat/-/rapier3d-compat-0.12.0.tgz",
|
||||
"integrity": "sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz",
|
||||
@@ -834,6 +866,26 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@noble/hashes": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.2.0.tgz",
|
||||
"integrity": "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 20.19.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@nodedc/map-cesium-react": {
|
||||
"resolved": "../../../NODEDC_DESIGN_GUIDELINE/packages/map-cesium-react",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@nodedc/page-patterns": {
|
||||
"resolved": "../../../NODEDC_DESIGN_GUIDELINE/packages/page-patterns",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@nodedc/tokens": {
|
||||
"resolved": "../../../NODEDC_DESIGN_GUIDELINE/packages/tokens",
|
||||
"link": true
|
||||
@@ -847,9 +899,9 @@
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@rerun-io/web-viewer": {
|
||||
"version": "0.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@rerun-io/web-viewer/-/web-viewer-0.34.1.tgz",
|
||||
"integrity": "sha512-2Oq9Mw3qOs765XArGq4e/0pAIksPFKlAqkgo+PDsRkPd77/KdnQhb835suRmYQ6tuqlbPeVCFhlNIXT0+TjmTg==",
|
||||
"version": "0.36.3",
|
||||
"resolved": "https://registry.npmjs.org/@rerun-io/web-viewer/-/web-viewer-0.36.3.tgz",
|
||||
"integrity": "sha512-LMGnsxRmY5UwiGras2dZrMnEYkow5Xr4v+1hAUSspXWPPiilMqoz9G77jo8Ps/deAaX81TnOq123DFO8iX/Ulw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@rolldown/pluginutils": {
|
||||
@@ -1209,6 +1261,13 @@
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@tweenjs/tween.js": {
|
||||
"version": "23.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.3.tgz",
|
||||
"integrity": "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/babel__core": {
|
||||
"version": "7.20.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
|
||||
@@ -1291,6 +1350,34 @@
|
||||
"@types/react": "^19.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/stats.js": {
|
||||
"version": "0.17.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.4.tgz",
|
||||
"integrity": "sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/three": {
|
||||
"version": "0.185.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/three/-/three-0.185.1.tgz",
|
||||
"integrity": "sha512-db1xTb+EgYF2didW+eudSvVPtn75zo+fGsY8ShQrJY/B5ZBmC2Fiaykv3aImHAlCNEGuMPkPGXBJGLwzu5mC7A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@dimforge/rapier3d-compat": "~0.12.0",
|
||||
"@tweenjs/tween.js": "~23.1.3",
|
||||
"@types/stats.js": "*",
|
||||
"@types/webxr": ">=0.5.17",
|
||||
"fflate": "~0.8.2",
|
||||
"meshoptimizer": "~1.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/webxr": {
|
||||
"version": "0.5.24",
|
||||
"resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.24.tgz",
|
||||
"integrity": "sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@vitejs/plugin-react": {
|
||||
"version": "4.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
|
||||
@@ -1312,6 +1399,12 @@
|
||||
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@webgpu/types": {
|
||||
"version": "0.1.72",
|
||||
"resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.72.tgz",
|
||||
"integrity": "sha512-0cF7RFM2edNoiIS1ODJp0/Gzv4/xSXhwoR0YCza+OWpJWtn4wmo9DvK91aLlH9+uUnwIriP7ZiC3WitmyhuzBw==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/baseline-browser-mapping": {
|
||||
"version": "2.10.43",
|
||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.43.tgz",
|
||||
@@ -1489,6 +1582,13 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/fflate": {
|
||||
"version": "0.8.3",
|
||||
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.3.tgz",
|
||||
"integrity": "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
@@ -1557,6 +1657,12 @@
|
||||
"yallist": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/meshoptimizer": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-1.1.1.tgz",
|
||||
"integrity": "sha512-oRFNWJRDA/WTrVj7NWvqa5HqE1t9MYDj2VaWirQCzCCrAd2GHrqR/sQezCxiWATPNlKTcRaPRHPJwIRoPBAp5g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
@@ -1613,6 +1719,19 @@
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/playcanvas": {
|
||||
"version": "2.21.4",
|
||||
"resolved": "https://registry.npmjs.org/playcanvas/-/playcanvas-2.21.4.tgz",
|
||||
"integrity": "sha512-L4UGy3z/YT8AaNBEY4ITsZup548UFMabF7loh0YQ0DRwQLdjIW8grmZPoQ1+B83+N6sejRS0/XlXV2Vfb11+fw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/webxr": "^0.5.24",
|
||||
"@webgpu/types": "^0.1.70"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.19",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.19.tgz",
|
||||
@@ -1744,6 +1863,12 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/three": {
|
||||
"version": "0.185.1",
|
||||
"resolved": "https://registry.npmjs.org/three/-/three-0.185.1.tgz",
|
||||
"integrity": "sha512-5aojFCXKwnjBRZvUnt3WFfEcvUJgkN5LlijRFN95hMy8WVkG4I0QNcJE+OuWvuJ0bOdStrbfXn0pkd6/QyiAlg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tinyglobby": {
|
||||
"version": "0.2.17",
|
||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@nodedc/k1-viewer",
|
||||
"name": "@nodedc/mission-core-control-station",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
@@ -7,26 +7,34 @@
|
||||
"dev": "vite",
|
||||
"build": "tsc -b && vite build",
|
||||
"preview": "vite preview",
|
||||
"test:unit": "node --test test/*.test.mjs",
|
||||
"typecheck": "tsc -b --pretty false"
|
||||
},
|
||||
"dependencies": {
|
||||
"@noble/hashes": "^2.2.0",
|
||||
"@nodedc/map-cesium-react": "file:../../../NODEDC_DESIGN_GUIDELINE/packages/map-cesium-react",
|
||||
"@nodedc/page-patterns": "file:../../../NODEDC_DESIGN_GUIDELINE/packages/page-patterns",
|
||||
"@nodedc/tokens": "file:../../../NODEDC_DESIGN_GUIDELINE/packages/tokens",
|
||||
"@nodedc/ui-core": "file:../../../NODEDC_DESIGN_GUIDELINE/packages/ui-core",
|
||||
"@nodedc/ui-react": "file:../../../NODEDC_DESIGN_GUIDELINE/packages/ui-react",
|
||||
"@rerun-io/web-viewer": "0.34.1",
|
||||
"@rerun-io/web-viewer": "0.36.3",
|
||||
"meshoptimizer": "1.1.1",
|
||||
"playcanvas": "2.21.4",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0"
|
||||
"react-dom": "^19.1.0",
|
||||
"three": "0.185.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.0.0",
|
||||
"@types/react": "^19.1.0",
|
||||
"@types/react-dom": "^19.1.0",
|
||||
"@types/three": "0.185.1",
|
||||
"@vitejs/plugin-react": "^4.6.0",
|
||||
"typescript": "^5.8.3",
|
||||
"vite": "^7.0.0",
|
||||
"vite-plugin-wasm": "^3.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 322 B After Width: | Height: | Size: 322 B |
@@ -0,0 +1,9 @@
|
||||
# PlayCanvas Ammo runtime
|
||||
|
||||
- Source: `https://developer.playcanvas.com/assets/modules/ammo/ammo.wasm.js`
|
||||
- Source: `https://developer.playcanvas.com/assets/modules/ammo/ammo.wasm.wasm`
|
||||
- Retrieved: 2026-08-27
|
||||
- `ammo.wasm.js`: 442459 bytes, SHA-256 `9b765569bce6993e781cd95d548c3711b119f355b512b25722e86a27d6ea8857`
|
||||
- `ammo.wasm.wasm`: 747690 bytes, SHA-256 `a73b547ed4289aa174bf3424b14b3cb8addc7fe708cd2636d306b72880d6ad13`
|
||||
|
||||
The control station loads these pinned same-origin assets before constructing the PlayCanvas application so the rigid-body systems bind to the expected Ammo instance.
|
||||
@@ -0,0 +1,921 @@
|
||||
// This is ammo.js, a port of Bullet Physics to JavaScript. zlib licensed.
|
||||
|
||||
var Ammo = (() => {
|
||||
var _scriptDir = typeof document !== 'undefined' && document.currentScript ? document.currentScript.src : undefined;
|
||||
if (typeof __filename !== 'undefined') _scriptDir = _scriptDir || __filename;
|
||||
return (
|
||||
function(Ammo = {}) {
|
||||
|
||||
var b;b||(b=typeof Ammo !== 'undefined' ? Ammo : {});var aa,ba;b.ready=new Promise(function(a,c){aa=a;ba=c});var ca=Object.assign({},b),da="object"==typeof window,ea="function"==typeof importScripts,fa="object"==typeof process&&"object"==typeof process.versions&&"string"==typeof process.versions.node,ha="",ia,ja,ka;
|
||||
if(fa){var fs=require("fs"),la=require("path");ha=ea?la.dirname(ha)+"/":__dirname+"/";ia=(a,c)=>{a=a.startsWith("file://")?new URL(a):la.normalize(a);return fs.readFileSync(a,c?void 0:"utf8")};ka=a=>{a=ia(a,!0);a.buffer||(a=new Uint8Array(a));return a};ja=(a,c,d)=>{a=a.startsWith("file://")?new URL(a):la.normalize(a);fs.readFile(a,function(e,g){e?d(e):c(g.buffer)})};1<process.argv.length&&process.argv[1].replace(/\\/g,"/");process.argv.slice(2);b.inspect=function(){return"[Emscripten Module object]"}}else if(da||
|
||||
ea)ea?ha=self.location.href:"undefined"!=typeof document&&document.currentScript&&(ha=document.currentScript.src),_scriptDir&&(ha=_scriptDir),ha=0!==ha.indexOf("blob:")?ha.substr(0,ha.replace(/[?#].*/,"").lastIndexOf("/")+1):"",ia=a=>{var c=new XMLHttpRequest;c.open("GET",a,!1);c.send(null);return c.responseText},ea&&(ka=a=>{var c=new XMLHttpRequest;c.open("GET",a,!1);c.responseType="arraybuffer";c.send(null);return new Uint8Array(c.response)}),ja=(a,c,d)=>{var e=new XMLHttpRequest;e.open("GET",a,
|
||||
!0);e.responseType="arraybuffer";e.onload=()=>{200==e.status||0==e.status&&e.response?c(e.response):d()};e.onerror=d;e.send(null)};b.print||console.log.bind(console);var ma=b.printErr||console.warn.bind(console);Object.assign(b,ca);ca=null;var na;b.wasmBinary&&(na=b.wasmBinary);var noExitRuntime=b.noExitRuntime||!0;"object"!=typeof WebAssembly&&oa("no native wasm support detected");var pa,qa=!1,ra="undefined"!=typeof TextDecoder?new TextDecoder("utf8"):void 0;
|
||||
function sa(a,c){if(a){var d=ta,e=a+c;for(c=a;d[c]&&!(c>=e);)++c;if(16<c-a&&d.buffer&&ra)a=ra.decode(d.subarray(a,c));else{for(e="";a<c;){var g=d[a++];if(g&128){var n=d[a++]&63;if(192==(g&224))e+=String.fromCharCode((g&31)<<6|n);else{var z=d[a++]&63;g=224==(g&240)?(g&15)<<12|n<<6|z:(g&7)<<18|n<<12|z<<6|d[a++]&63;65536>g?e+=String.fromCharCode(g):(g-=65536,e+=String.fromCharCode(55296|g>>10,56320|g&1023))}}else e+=String.fromCharCode(g)}a=e}}else a="";return a}
|
||||
var ua,ta,va,wa,xa,ya,za=[],Aa=[],Ba=[],Ca=!1;function Ea(){var a=b.preRun.shift();za.unshift(a)}var Fa=0,Ga=null,Ha=null;function oa(a){if(b.onAbort)b.onAbort(a);a="Aborted("+a+")";ma(a);qa=!0;a=new WebAssembly.RuntimeError(a+". Build with -sASSERTIONS for more info.");ba(a);throw a;}function Ia(a){return a.startsWith("data:application/octet-stream;base64,")}var Ja;Ja="ammo.wasm.wasm";if(!Ia(Ja)){var Ka=Ja;Ja=b.locateFile?b.locateFile(Ka,ha):ha+Ka}
|
||||
function La(a){try{if(a==Ja&&na)return new Uint8Array(na);if(ka)return ka(a);throw"both async and sync fetching of the wasm failed";}catch(c){oa(c)}}
|
||||
function Ma(a){if(!na&&(da||ea)){if("function"==typeof fetch&&!a.startsWith("file://"))return fetch(a,{credentials:"same-origin"}).then(function(c){if(!c.ok)throw"failed to load wasm binary file at '"+a+"'";return c.arrayBuffer()}).catch(function(){return La(a)});if(ja)return new Promise(function(c,d){ja(a,function(e){c(new Uint8Array(e))},d)})}return Promise.resolve().then(function(){return La(a)})}
|
||||
function Na(a,c,d){return Ma(a).then(function(e){return WebAssembly.instantiate(e,c)}).then(function(e){return e}).then(d,function(e){ma("failed to asynchronously prepare wasm: "+e);oa(e)})}
|
||||
function Oa(a,c){var d=Ja;return na||"function"!=typeof WebAssembly.instantiateStreaming||Ia(d)||d.startsWith("file://")||fa||"function"!=typeof fetch?Na(d,a,c):fetch(d,{credentials:"same-origin"}).then(function(e){return WebAssembly.instantiateStreaming(e,a).then(c,function(g){ma("wasm streaming compile failed: "+g);ma("falling back to ArrayBuffer instantiation");return Na(d,a,c)})})}
|
||||
var Pa={27338:(a,c,d,e)=>{a=b.getCache(b.DebugDrawer)[a];if(!a.hasOwnProperty("drawLine"))throw"a JSImplementation must implement all functions, you forgot DebugDrawer::drawLine.";a.drawLine(c,d,e)},27558:(a,c,d,e,g,n)=>{a=b.getCache(b.DebugDrawer)[a];if(!a.hasOwnProperty("drawContactPoint"))throw"a JSImplementation must implement all functions, you forgot DebugDrawer::drawContactPoint.";a.drawContactPoint(c,d,e,g,n)},27808:(a,c)=>{a=b.getCache(b.DebugDrawer)[a];if(!a.hasOwnProperty("reportErrorWarning"))throw"a JSImplementation must implement all functions, you forgot DebugDrawer::reportErrorWarning.";
|
||||
a.reportErrorWarning(c)},28052:(a,c,d)=>{a=b.getCache(b.DebugDrawer)[a];if(!a.hasOwnProperty("draw3dText"))throw"a JSImplementation must implement all functions, you forgot DebugDrawer::draw3dText.";a.draw3dText(c,d)},28275:(a,c)=>{a=b.getCache(b.DebugDrawer)[a];if(!a.hasOwnProperty("setDebugMode"))throw"a JSImplementation must implement all functions, you forgot DebugDrawer::setDebugMode.";a.setDebugMode(c)},28501:a=>{a=b.getCache(b.DebugDrawer)[a];if(!a.hasOwnProperty("getDebugMode"))throw"a JSImplementation must implement all functions, you forgot DebugDrawer::getDebugMode.";
|
||||
return a.getDebugMode()},28732:(a,c)=>{a=b.getCache(b.MotionState)[a];if(!a.hasOwnProperty("getWorldTransform"))throw"a JSImplementation must implement all functions, you forgot MotionState::getWorldTransform.";a.getWorldTransform(c)},28973:(a,c)=>{a=b.getCache(b.MotionState)[a];if(!a.hasOwnProperty("setWorldTransform"))throw"a JSImplementation must implement all functions, you forgot MotionState::setWorldTransform.";a.setWorldTransform(c)},29214:(a,c,d,e,g,n,z,T)=>{a=b.getCache(b.ConcreteContactResultCallback)[a];
|
||||
if(!a.hasOwnProperty("addSingleResult"))throw"a JSImplementation must implement all functions, you forgot ConcreteContactResultCallback::addSingleResult.";return a.addSingleResult(c,d,e,g,n,z,T)}};function Qa(a){for(;0<a.length;)a.shift()(b)}var Ra=[];function Sa(a,c,d){Ra.length=0;var e;for(d>>=2;e=ta[c++];)d+=105!=e&d,Ra.push(105==e?va[d]:xa[d++>>1]),++d;return Pa[a].apply(null,Ra)}
|
||||
var Ta=[],Ua=void 0,Va=[],Wa={b:function(){oa("")},f:function(a,c,d){return Sa(a,c,d)},a:function(a,c,d){return Sa(a,c,d)},d:function(){return Date.now()},e:function(a,c,d){ta.copyWithin(a,c,c+d)},c:function(){oa("OOM")}};
|
||||
(function(){function a(d){d=d.exports;b.asm=d;pa=b.asm.g;var e=pa.buffer;b.HEAP8=ua=new Int8Array(e);b.HEAP16=new Int16Array(e);b.HEAP32=va=new Int32Array(e);b.HEAPU8=ta=new Uint8Array(e);b.HEAPU16=new Uint16Array(e);b.HEAPU32=new Uint32Array(e);b.HEAPF32=wa=new Float32Array(e);b.HEAPF64=xa=new Float64Array(e);ya=b.asm.iB;Aa.unshift(b.asm.h);Fa--;b.monitorRunDependencies&&b.monitorRunDependencies(Fa);0==Fa&&(null!==Ga&&(clearInterval(Ga),Ga=null),Ha&&(e=Ha,Ha=null,e()));return d}var c={a:Wa};Fa++;
|
||||
b.monitorRunDependencies&&b.monitorRunDependencies(Fa);if(b.instantiateWasm)try{return b.instantiateWasm(c,a)}catch(d){ma("Module.instantiateWasm callback failed with error: "+d),ba(d)}Oa(c,function(d){a(d.instance)}).catch(ba);return{}})();
|
||||
var Xa=b._emscripten_bind_btCollisionShape_setLocalScaling_1=function(){return(Xa=b._emscripten_bind_btCollisionShape_setLocalScaling_1=b.asm.i).apply(null,arguments)},Ya=b._emscripten_bind_btCollisionShape_getLocalScaling_0=function(){return(Ya=b._emscripten_bind_btCollisionShape_getLocalScaling_0=b.asm.j).apply(null,arguments)},Za=b._emscripten_bind_btCollisionShape_calculateLocalInertia_2=function(){return(Za=b._emscripten_bind_btCollisionShape_calculateLocalInertia_2=b.asm.k).apply(null,arguments)},
|
||||
$a=b._emscripten_bind_btCollisionShape_setMargin_1=function(){return($a=b._emscripten_bind_btCollisionShape_setMargin_1=b.asm.l).apply(null,arguments)},ab=b._emscripten_bind_btCollisionShape_getMargin_0=function(){return(ab=b._emscripten_bind_btCollisionShape_getMargin_0=b.asm.m).apply(null,arguments)},bb=b._emscripten_bind_btCollisionShape___destroy___0=function(){return(bb=b._emscripten_bind_btCollisionShape___destroy___0=b.asm.n).apply(null,arguments)},cb=b._emscripten_bind_btCollisionWorld_getDispatcher_0=
|
||||
function(){return(cb=b._emscripten_bind_btCollisionWorld_getDispatcher_0=b.asm.o).apply(null,arguments)},db=b._emscripten_bind_btCollisionWorld_rayTest_3=function(){return(db=b._emscripten_bind_btCollisionWorld_rayTest_3=b.asm.p).apply(null,arguments)},eb=b._emscripten_bind_btCollisionWorld_getPairCache_0=function(){return(eb=b._emscripten_bind_btCollisionWorld_getPairCache_0=b.asm.q).apply(null,arguments)},fb=b._emscripten_bind_btCollisionWorld_getDispatchInfo_0=function(){return(fb=b._emscripten_bind_btCollisionWorld_getDispatchInfo_0=
|
||||
b.asm.r).apply(null,arguments)},gb=b._emscripten_bind_btCollisionWorld_addCollisionObject_1=function(){return(gb=b._emscripten_bind_btCollisionWorld_addCollisionObject_1=b.asm.s).apply(null,arguments)},hb=b._emscripten_bind_btCollisionWorld_addCollisionObject_2=function(){return(hb=b._emscripten_bind_btCollisionWorld_addCollisionObject_2=b.asm.t).apply(null,arguments)},ib=b._emscripten_bind_btCollisionWorld_addCollisionObject_3=function(){return(ib=b._emscripten_bind_btCollisionWorld_addCollisionObject_3=
|
||||
b.asm.u).apply(null,arguments)},jb=b._emscripten_bind_btCollisionWorld_removeCollisionObject_1=function(){return(jb=b._emscripten_bind_btCollisionWorld_removeCollisionObject_1=b.asm.v).apply(null,arguments)},kb=b._emscripten_bind_btCollisionWorld_getBroadphase_0=function(){return(kb=b._emscripten_bind_btCollisionWorld_getBroadphase_0=b.asm.w).apply(null,arguments)},lb=b._emscripten_bind_btCollisionWorld_convexSweepTest_5=function(){return(lb=b._emscripten_bind_btCollisionWorld_convexSweepTest_5=b.asm.x).apply(null,
|
||||
arguments)},mb=b._emscripten_bind_btCollisionWorld_contactPairTest_3=function(){return(mb=b._emscripten_bind_btCollisionWorld_contactPairTest_3=b.asm.y).apply(null,arguments)},nb=b._emscripten_bind_btCollisionWorld_contactTest_2=function(){return(nb=b._emscripten_bind_btCollisionWorld_contactTest_2=b.asm.z).apply(null,arguments)},ob=b._emscripten_bind_btCollisionWorld_updateSingleAabb_1=function(){return(ob=b._emscripten_bind_btCollisionWorld_updateSingleAabb_1=b.asm.A).apply(null,arguments)},pb=
|
||||
b._emscripten_bind_btCollisionWorld_setDebugDrawer_1=function(){return(pb=b._emscripten_bind_btCollisionWorld_setDebugDrawer_1=b.asm.B).apply(null,arguments)},qb=b._emscripten_bind_btCollisionWorld_getDebugDrawer_0=function(){return(qb=b._emscripten_bind_btCollisionWorld_getDebugDrawer_0=b.asm.C).apply(null,arguments)},rb=b._emscripten_bind_btCollisionWorld_debugDrawWorld_0=function(){return(rb=b._emscripten_bind_btCollisionWorld_debugDrawWorld_0=b.asm.D).apply(null,arguments)},sb=b._emscripten_bind_btCollisionWorld_debugDrawObject_3=
|
||||
function(){return(sb=b._emscripten_bind_btCollisionWorld_debugDrawObject_3=b.asm.E).apply(null,arguments)},tb=b._emscripten_bind_btCollisionWorld___destroy___0=function(){return(tb=b._emscripten_bind_btCollisionWorld___destroy___0=b.asm.F).apply(null,arguments)},ub=b._emscripten_bind_btCollisionObject_setAnisotropicFriction_2=function(){return(ub=b._emscripten_bind_btCollisionObject_setAnisotropicFriction_2=b.asm.G).apply(null,arguments)},vb=b._emscripten_bind_btCollisionObject_getCollisionShape_0=
|
||||
function(){return(vb=b._emscripten_bind_btCollisionObject_getCollisionShape_0=b.asm.H).apply(null,arguments)},wb=b._emscripten_bind_btCollisionObject_setContactProcessingThreshold_1=function(){return(wb=b._emscripten_bind_btCollisionObject_setContactProcessingThreshold_1=b.asm.I).apply(null,arguments)},xb=b._emscripten_bind_btCollisionObject_setActivationState_1=function(){return(xb=b._emscripten_bind_btCollisionObject_setActivationState_1=b.asm.J).apply(null,arguments)},yb=b._emscripten_bind_btCollisionObject_forceActivationState_1=
|
||||
function(){return(yb=b._emscripten_bind_btCollisionObject_forceActivationState_1=b.asm.K).apply(null,arguments)},zb=b._emscripten_bind_btCollisionObject_activate_0=function(){return(zb=b._emscripten_bind_btCollisionObject_activate_0=b.asm.L).apply(null,arguments)},Ab=b._emscripten_bind_btCollisionObject_activate_1=function(){return(Ab=b._emscripten_bind_btCollisionObject_activate_1=b.asm.M).apply(null,arguments)},Bb=b._emscripten_bind_btCollisionObject_isActive_0=function(){return(Bb=b._emscripten_bind_btCollisionObject_isActive_0=
|
||||
b.asm.N).apply(null,arguments)},Cb=b._emscripten_bind_btCollisionObject_isKinematicObject_0=function(){return(Cb=b._emscripten_bind_btCollisionObject_isKinematicObject_0=b.asm.O).apply(null,arguments)},Db=b._emscripten_bind_btCollisionObject_isStaticObject_0=function(){return(Db=b._emscripten_bind_btCollisionObject_isStaticObject_0=b.asm.P).apply(null,arguments)},Eb=b._emscripten_bind_btCollisionObject_isStaticOrKinematicObject_0=function(){return(Eb=b._emscripten_bind_btCollisionObject_isStaticOrKinematicObject_0=
|
||||
b.asm.Q).apply(null,arguments)},Fb=b._emscripten_bind_btCollisionObject_getRestitution_0=function(){return(Fb=b._emscripten_bind_btCollisionObject_getRestitution_0=b.asm.R).apply(null,arguments)},Gb=b._emscripten_bind_btCollisionObject_getFriction_0=function(){return(Gb=b._emscripten_bind_btCollisionObject_getFriction_0=b.asm.S).apply(null,arguments)},Hb=b._emscripten_bind_btCollisionObject_getRollingFriction_0=function(){return(Hb=b._emscripten_bind_btCollisionObject_getRollingFriction_0=b.asm.T).apply(null,
|
||||
arguments)},Ib=b._emscripten_bind_btCollisionObject_setRestitution_1=function(){return(Ib=b._emscripten_bind_btCollisionObject_setRestitution_1=b.asm.U).apply(null,arguments)},Jb=b._emscripten_bind_btCollisionObject_setFriction_1=function(){return(Jb=b._emscripten_bind_btCollisionObject_setFriction_1=b.asm.V).apply(null,arguments)},Kb=b._emscripten_bind_btCollisionObject_setRollingFriction_1=function(){return(Kb=b._emscripten_bind_btCollisionObject_setRollingFriction_1=b.asm.W).apply(null,arguments)},
|
||||
Lb=b._emscripten_bind_btCollisionObject_getWorldTransform_0=function(){return(Lb=b._emscripten_bind_btCollisionObject_getWorldTransform_0=b.asm.X).apply(null,arguments)},Mb=b._emscripten_bind_btCollisionObject_getCollisionFlags_0=function(){return(Mb=b._emscripten_bind_btCollisionObject_getCollisionFlags_0=b.asm.Y).apply(null,arguments)},Nb=b._emscripten_bind_btCollisionObject_setCollisionFlags_1=function(){return(Nb=b._emscripten_bind_btCollisionObject_setCollisionFlags_1=b.asm.Z).apply(null,arguments)},
|
||||
Ob=b._emscripten_bind_btCollisionObject_setWorldTransform_1=function(){return(Ob=b._emscripten_bind_btCollisionObject_setWorldTransform_1=b.asm._).apply(null,arguments)},Pb=b._emscripten_bind_btCollisionObject_setCollisionShape_1=function(){return(Pb=b._emscripten_bind_btCollisionObject_setCollisionShape_1=b.asm.$).apply(null,arguments)},Qb=b._emscripten_bind_btCollisionObject_setCcdMotionThreshold_1=function(){return(Qb=b._emscripten_bind_btCollisionObject_setCcdMotionThreshold_1=b.asm.aa).apply(null,
|
||||
arguments)},Rb=b._emscripten_bind_btCollisionObject_setCcdSweptSphereRadius_1=function(){return(Rb=b._emscripten_bind_btCollisionObject_setCcdSweptSphereRadius_1=b.asm.ba).apply(null,arguments)},Sb=b._emscripten_bind_btCollisionObject_getUserIndex_0=function(){return(Sb=b._emscripten_bind_btCollisionObject_getUserIndex_0=b.asm.ca).apply(null,arguments)},Tb=b._emscripten_bind_btCollisionObject_setUserIndex_1=function(){return(Tb=b._emscripten_bind_btCollisionObject_setUserIndex_1=b.asm.da).apply(null,
|
||||
arguments)},Ub=b._emscripten_bind_btCollisionObject_getUserPointer_0=function(){return(Ub=b._emscripten_bind_btCollisionObject_getUserPointer_0=b.asm.ea).apply(null,arguments)},Vb=b._emscripten_bind_btCollisionObject_setUserPointer_1=function(){return(Vb=b._emscripten_bind_btCollisionObject_setUserPointer_1=b.asm.fa).apply(null,arguments)},Wb=b._emscripten_bind_btCollisionObject_getBroadphaseHandle_0=function(){return(Wb=b._emscripten_bind_btCollisionObject_getBroadphaseHandle_0=b.asm.ga).apply(null,
|
||||
arguments)},Xb=b._emscripten_bind_btCollisionObject___destroy___0=function(){return(Xb=b._emscripten_bind_btCollisionObject___destroy___0=b.asm.ha).apply(null,arguments)},Yb=b._emscripten_bind_btConcaveShape_setLocalScaling_1=function(){return(Yb=b._emscripten_bind_btConcaveShape_setLocalScaling_1=b.asm.ia).apply(null,arguments)},Zb=b._emscripten_bind_btConcaveShape_getLocalScaling_0=function(){return(Zb=b._emscripten_bind_btConcaveShape_getLocalScaling_0=b.asm.ja).apply(null,arguments)},$b=b._emscripten_bind_btConcaveShape_calculateLocalInertia_2=
|
||||
function(){return($b=b._emscripten_bind_btConcaveShape_calculateLocalInertia_2=b.asm.ka).apply(null,arguments)},ac=b._emscripten_bind_btConcaveShape___destroy___0=function(){return(ac=b._emscripten_bind_btConcaveShape___destroy___0=b.asm.la).apply(null,arguments)},bc=b._emscripten_bind_btCollisionAlgorithm___destroy___0=function(){return(bc=b._emscripten_bind_btCollisionAlgorithm___destroy___0=b.asm.ma).apply(null,arguments)},cc=b._emscripten_bind_btTypedConstraint_enableFeedback_1=function(){return(cc=
|
||||
b._emscripten_bind_btTypedConstraint_enableFeedback_1=b.asm.na).apply(null,arguments)},ec=b._emscripten_bind_btTypedConstraint_getBreakingImpulseThreshold_0=function(){return(ec=b._emscripten_bind_btTypedConstraint_getBreakingImpulseThreshold_0=b.asm.oa).apply(null,arguments)},fc=b._emscripten_bind_btTypedConstraint_setBreakingImpulseThreshold_1=function(){return(fc=b._emscripten_bind_btTypedConstraint_setBreakingImpulseThreshold_1=b.asm.pa).apply(null,arguments)},gc=b._emscripten_bind_btTypedConstraint_getParam_2=
|
||||
function(){return(gc=b._emscripten_bind_btTypedConstraint_getParam_2=b.asm.qa).apply(null,arguments)},hc=b._emscripten_bind_btTypedConstraint_setParam_3=function(){return(hc=b._emscripten_bind_btTypedConstraint_setParam_3=b.asm.ra).apply(null,arguments)},ic=b._emscripten_bind_btTypedConstraint___destroy___0=function(){return(ic=b._emscripten_bind_btTypedConstraint___destroy___0=b.asm.sa).apply(null,arguments)},jc=b._emscripten_bind_btDynamicsWorld_addAction_1=function(){return(jc=b._emscripten_bind_btDynamicsWorld_addAction_1=
|
||||
b.asm.ta).apply(null,arguments)},kc=b._emscripten_bind_btDynamicsWorld_removeAction_1=function(){return(kc=b._emscripten_bind_btDynamicsWorld_removeAction_1=b.asm.ua).apply(null,arguments)},lc=b._emscripten_bind_btDynamicsWorld_getSolverInfo_0=function(){return(lc=b._emscripten_bind_btDynamicsWorld_getSolverInfo_0=b.asm.va).apply(null,arguments)},mc=b._emscripten_bind_btDynamicsWorld_setInternalTickCallback_1=function(){return(mc=b._emscripten_bind_btDynamicsWorld_setInternalTickCallback_1=b.asm.wa).apply(null,
|
||||
arguments)},nc=b._emscripten_bind_btDynamicsWorld_setInternalTickCallback_2=function(){return(nc=b._emscripten_bind_btDynamicsWorld_setInternalTickCallback_2=b.asm.xa).apply(null,arguments)},oc=b._emscripten_bind_btDynamicsWorld_setInternalTickCallback_3=function(){return(oc=b._emscripten_bind_btDynamicsWorld_setInternalTickCallback_3=b.asm.ya).apply(null,arguments)},pc=b._emscripten_bind_btDynamicsWorld_getDispatcher_0=function(){return(pc=b._emscripten_bind_btDynamicsWorld_getDispatcher_0=b.asm.za).apply(null,
|
||||
arguments)},qc=b._emscripten_bind_btDynamicsWorld_rayTest_3=function(){return(qc=b._emscripten_bind_btDynamicsWorld_rayTest_3=b.asm.Aa).apply(null,arguments)},rc=b._emscripten_bind_btDynamicsWorld_getPairCache_0=function(){return(rc=b._emscripten_bind_btDynamicsWorld_getPairCache_0=b.asm.Ba).apply(null,arguments)},sc=b._emscripten_bind_btDynamicsWorld_getDispatchInfo_0=function(){return(sc=b._emscripten_bind_btDynamicsWorld_getDispatchInfo_0=b.asm.Ca).apply(null,arguments)},tc=b._emscripten_bind_btDynamicsWorld_addCollisionObject_1=
|
||||
function(){return(tc=b._emscripten_bind_btDynamicsWorld_addCollisionObject_1=b.asm.Da).apply(null,arguments)},uc=b._emscripten_bind_btDynamicsWorld_addCollisionObject_2=function(){return(uc=b._emscripten_bind_btDynamicsWorld_addCollisionObject_2=b.asm.Ea).apply(null,arguments)},vc=b._emscripten_bind_btDynamicsWorld_addCollisionObject_3=function(){return(vc=b._emscripten_bind_btDynamicsWorld_addCollisionObject_3=b.asm.Fa).apply(null,arguments)},wc=b._emscripten_bind_btDynamicsWorld_removeCollisionObject_1=
|
||||
function(){return(wc=b._emscripten_bind_btDynamicsWorld_removeCollisionObject_1=b.asm.Ga).apply(null,arguments)},xc=b._emscripten_bind_btDynamicsWorld_getBroadphase_0=function(){return(xc=b._emscripten_bind_btDynamicsWorld_getBroadphase_0=b.asm.Ha).apply(null,arguments)},yc=b._emscripten_bind_btDynamicsWorld_convexSweepTest_5=function(){return(yc=b._emscripten_bind_btDynamicsWorld_convexSweepTest_5=b.asm.Ia).apply(null,arguments)},zc=b._emscripten_bind_btDynamicsWorld_contactPairTest_3=function(){return(zc=
|
||||
b._emscripten_bind_btDynamicsWorld_contactPairTest_3=b.asm.Ja).apply(null,arguments)},Ac=b._emscripten_bind_btDynamicsWorld_contactTest_2=function(){return(Ac=b._emscripten_bind_btDynamicsWorld_contactTest_2=b.asm.Ka).apply(null,arguments)},Bc=b._emscripten_bind_btDynamicsWorld_updateSingleAabb_1=function(){return(Bc=b._emscripten_bind_btDynamicsWorld_updateSingleAabb_1=b.asm.La).apply(null,arguments)},Cc=b._emscripten_bind_btDynamicsWorld_setDebugDrawer_1=function(){return(Cc=b._emscripten_bind_btDynamicsWorld_setDebugDrawer_1=
|
||||
b.asm.Ma).apply(null,arguments)},Dc=b._emscripten_bind_btDynamicsWorld_getDebugDrawer_0=function(){return(Dc=b._emscripten_bind_btDynamicsWorld_getDebugDrawer_0=b.asm.Na).apply(null,arguments)},Ec=b._emscripten_bind_btDynamicsWorld_debugDrawWorld_0=function(){return(Ec=b._emscripten_bind_btDynamicsWorld_debugDrawWorld_0=b.asm.Oa).apply(null,arguments)},Fc=b._emscripten_bind_btDynamicsWorld_debugDrawObject_3=function(){return(Fc=b._emscripten_bind_btDynamicsWorld_debugDrawObject_3=b.asm.Pa).apply(null,
|
||||
arguments)},Gc=b._emscripten_bind_btDynamicsWorld___destroy___0=function(){return(Gc=b._emscripten_bind_btDynamicsWorld___destroy___0=b.asm.Qa).apply(null,arguments)},Hc=b._emscripten_bind_btIDebugDraw_drawLine_3=function(){return(Hc=b._emscripten_bind_btIDebugDraw_drawLine_3=b.asm.Ra).apply(null,arguments)},Ic=b._emscripten_bind_btIDebugDraw_drawContactPoint_5=function(){return(Ic=b._emscripten_bind_btIDebugDraw_drawContactPoint_5=b.asm.Sa).apply(null,arguments)},Jc=b._emscripten_bind_btIDebugDraw_reportErrorWarning_1=
|
||||
function(){return(Jc=b._emscripten_bind_btIDebugDraw_reportErrorWarning_1=b.asm.Ta).apply(null,arguments)},Kc=b._emscripten_bind_btIDebugDraw_draw3dText_2=function(){return(Kc=b._emscripten_bind_btIDebugDraw_draw3dText_2=b.asm.Ua).apply(null,arguments)},Lc=b._emscripten_bind_btIDebugDraw_setDebugMode_1=function(){return(Lc=b._emscripten_bind_btIDebugDraw_setDebugMode_1=b.asm.Va).apply(null,arguments)},Mc=b._emscripten_bind_btIDebugDraw_getDebugMode_0=function(){return(Mc=b._emscripten_bind_btIDebugDraw_getDebugMode_0=
|
||||
b.asm.Wa).apply(null,arguments)},Nc=b._emscripten_bind_btIDebugDraw___destroy___0=function(){return(Nc=b._emscripten_bind_btIDebugDraw___destroy___0=b.asm.Xa).apply(null,arguments)},Oc=b._emscripten_bind_btVector3_btVector3_0=function(){return(Oc=b._emscripten_bind_btVector3_btVector3_0=b.asm.Ya).apply(null,arguments)},Pc=b._emscripten_bind_btVector3_btVector3_3=function(){return(Pc=b._emscripten_bind_btVector3_btVector3_3=b.asm.Za).apply(null,arguments)},Qc=b._emscripten_bind_btVector3_length_0=
|
||||
function(){return(Qc=b._emscripten_bind_btVector3_length_0=b.asm._a).apply(null,arguments)},Rc=b._emscripten_bind_btVector3_x_0=function(){return(Rc=b._emscripten_bind_btVector3_x_0=b.asm.$a).apply(null,arguments)},Sc=b._emscripten_bind_btVector3_y_0=function(){return(Sc=b._emscripten_bind_btVector3_y_0=b.asm.ab).apply(null,arguments)},Tc=b._emscripten_bind_btVector3_z_0=function(){return(Tc=b._emscripten_bind_btVector3_z_0=b.asm.bb).apply(null,arguments)},Uc=b._emscripten_bind_btVector3_setX_1=function(){return(Uc=
|
||||
b._emscripten_bind_btVector3_setX_1=b.asm.cb).apply(null,arguments)},Vc=b._emscripten_bind_btVector3_setY_1=function(){return(Vc=b._emscripten_bind_btVector3_setY_1=b.asm.db).apply(null,arguments)},Wc=b._emscripten_bind_btVector3_setZ_1=function(){return(Wc=b._emscripten_bind_btVector3_setZ_1=b.asm.eb).apply(null,arguments)},Xc=b._emscripten_bind_btVector3_setValue_3=function(){return(Xc=b._emscripten_bind_btVector3_setValue_3=b.asm.fb).apply(null,arguments)},Yc=b._emscripten_bind_btVector3_normalize_0=
|
||||
function(){return(Yc=b._emscripten_bind_btVector3_normalize_0=b.asm.gb).apply(null,arguments)},Zc=b._emscripten_bind_btVector3_rotate_2=function(){return(Zc=b._emscripten_bind_btVector3_rotate_2=b.asm.hb).apply(null,arguments)},$c=b._emscripten_bind_btVector3_dot_1=function(){return($c=b._emscripten_bind_btVector3_dot_1=b.asm.ib).apply(null,arguments)},ad=b._emscripten_bind_btVector3_op_mul_1=function(){return(ad=b._emscripten_bind_btVector3_op_mul_1=b.asm.jb).apply(null,arguments)},bd=b._emscripten_bind_btVector3_op_add_1=
|
||||
function(){return(bd=b._emscripten_bind_btVector3_op_add_1=b.asm.kb).apply(null,arguments)},cd=b._emscripten_bind_btVector3_op_sub_1=function(){return(cd=b._emscripten_bind_btVector3_op_sub_1=b.asm.lb).apply(null,arguments)},dd=b._emscripten_bind_btVector3___destroy___0=function(){return(dd=b._emscripten_bind_btVector3___destroy___0=b.asm.mb).apply(null,arguments)},ed=b._emscripten_bind_btQuadWord_x_0=function(){return(ed=b._emscripten_bind_btQuadWord_x_0=b.asm.nb).apply(null,arguments)},fd=b._emscripten_bind_btQuadWord_y_0=
|
||||
function(){return(fd=b._emscripten_bind_btQuadWord_y_0=b.asm.ob).apply(null,arguments)},gd=b._emscripten_bind_btQuadWord_z_0=function(){return(gd=b._emscripten_bind_btQuadWord_z_0=b.asm.pb).apply(null,arguments)},hd=b._emscripten_bind_btQuadWord_w_0=function(){return(hd=b._emscripten_bind_btQuadWord_w_0=b.asm.qb).apply(null,arguments)},jd=b._emscripten_bind_btQuadWord_setX_1=function(){return(jd=b._emscripten_bind_btQuadWord_setX_1=b.asm.rb).apply(null,arguments)},kd=b._emscripten_bind_btQuadWord_setY_1=
|
||||
function(){return(kd=b._emscripten_bind_btQuadWord_setY_1=b.asm.sb).apply(null,arguments)},ld=b._emscripten_bind_btQuadWord_setZ_1=function(){return(ld=b._emscripten_bind_btQuadWord_setZ_1=b.asm.tb).apply(null,arguments)},md=b._emscripten_bind_btQuadWord_setW_1=function(){return(md=b._emscripten_bind_btQuadWord_setW_1=b.asm.ub).apply(null,arguments)},nd=b._emscripten_bind_btQuadWord___destroy___0=function(){return(nd=b._emscripten_bind_btQuadWord___destroy___0=b.asm.vb).apply(null,arguments)},od=
|
||||
b._emscripten_bind_btMotionState_getWorldTransform_1=function(){return(od=b._emscripten_bind_btMotionState_getWorldTransform_1=b.asm.wb).apply(null,arguments)},pd=b._emscripten_bind_btMotionState_setWorldTransform_1=function(){return(pd=b._emscripten_bind_btMotionState_setWorldTransform_1=b.asm.xb).apply(null,arguments)},qd=b._emscripten_bind_btMotionState___destroy___0=function(){return(qd=b._emscripten_bind_btMotionState___destroy___0=b.asm.yb).apply(null,arguments)},rd=b._emscripten_bind_RayResultCallback_hasHit_0=
|
||||
function(){return(rd=b._emscripten_bind_RayResultCallback_hasHit_0=b.asm.zb).apply(null,arguments)},sd=b._emscripten_bind_RayResultCallback_get_m_collisionFilterGroup_0=function(){return(sd=b._emscripten_bind_RayResultCallback_get_m_collisionFilterGroup_0=b.asm.Ab).apply(null,arguments)},td=b._emscripten_bind_RayResultCallback_set_m_collisionFilterGroup_1=function(){return(td=b._emscripten_bind_RayResultCallback_set_m_collisionFilterGroup_1=b.asm.Bb).apply(null,arguments)},ud=b._emscripten_bind_RayResultCallback_get_m_collisionFilterMask_0=
|
||||
function(){return(ud=b._emscripten_bind_RayResultCallback_get_m_collisionFilterMask_0=b.asm.Cb).apply(null,arguments)},vd=b._emscripten_bind_RayResultCallback_set_m_collisionFilterMask_1=function(){return(vd=b._emscripten_bind_RayResultCallback_set_m_collisionFilterMask_1=b.asm.Db).apply(null,arguments)},wd=b._emscripten_bind_RayResultCallback_get_m_closestHitFraction_0=function(){return(wd=b._emscripten_bind_RayResultCallback_get_m_closestHitFraction_0=b.asm.Eb).apply(null,arguments)},xd=b._emscripten_bind_RayResultCallback_set_m_closestHitFraction_1=
|
||||
function(){return(xd=b._emscripten_bind_RayResultCallback_set_m_closestHitFraction_1=b.asm.Fb).apply(null,arguments)},yd=b._emscripten_bind_RayResultCallback_get_m_collisionObject_0=function(){return(yd=b._emscripten_bind_RayResultCallback_get_m_collisionObject_0=b.asm.Gb).apply(null,arguments)},zd=b._emscripten_bind_RayResultCallback_set_m_collisionObject_1=function(){return(zd=b._emscripten_bind_RayResultCallback_set_m_collisionObject_1=b.asm.Hb).apply(null,arguments)},Ad=b._emscripten_bind_RayResultCallback_get_m_flags_0=
|
||||
function(){return(Ad=b._emscripten_bind_RayResultCallback_get_m_flags_0=b.asm.Ib).apply(null,arguments)},Bd=b._emscripten_bind_RayResultCallback_set_m_flags_1=function(){return(Bd=b._emscripten_bind_RayResultCallback_set_m_flags_1=b.asm.Jb).apply(null,arguments)},Cd=b._emscripten_bind_RayResultCallback___destroy___0=function(){return(Cd=b._emscripten_bind_RayResultCallback___destroy___0=b.asm.Kb).apply(null,arguments)},Dd=b._emscripten_bind_ContactResultCallback_addSingleResult_7=function(){return(Dd=
|
||||
b._emscripten_bind_ContactResultCallback_addSingleResult_7=b.asm.Lb).apply(null,arguments)},Ed=b._emscripten_bind_ContactResultCallback___destroy___0=function(){return(Ed=b._emscripten_bind_ContactResultCallback___destroy___0=b.asm.Mb).apply(null,arguments)},Fd=b._emscripten_bind_ConvexResultCallback_hasHit_0=function(){return(Fd=b._emscripten_bind_ConvexResultCallback_hasHit_0=b.asm.Nb).apply(null,arguments)},Gd=b._emscripten_bind_ConvexResultCallback_get_m_collisionFilterGroup_0=function(){return(Gd=
|
||||
b._emscripten_bind_ConvexResultCallback_get_m_collisionFilterGroup_0=b.asm.Ob).apply(null,arguments)},Hd=b._emscripten_bind_ConvexResultCallback_set_m_collisionFilterGroup_1=function(){return(Hd=b._emscripten_bind_ConvexResultCallback_set_m_collisionFilterGroup_1=b.asm.Pb).apply(null,arguments)},Id=b._emscripten_bind_ConvexResultCallback_get_m_collisionFilterMask_0=function(){return(Id=b._emscripten_bind_ConvexResultCallback_get_m_collisionFilterMask_0=b.asm.Qb).apply(null,arguments)},Jd=b._emscripten_bind_ConvexResultCallback_set_m_collisionFilterMask_1=
|
||||
function(){return(Jd=b._emscripten_bind_ConvexResultCallback_set_m_collisionFilterMask_1=b.asm.Rb).apply(null,arguments)},Kd=b._emscripten_bind_ConvexResultCallback_get_m_closestHitFraction_0=function(){return(Kd=b._emscripten_bind_ConvexResultCallback_get_m_closestHitFraction_0=b.asm.Sb).apply(null,arguments)},Ld=b._emscripten_bind_ConvexResultCallback_set_m_closestHitFraction_1=function(){return(Ld=b._emscripten_bind_ConvexResultCallback_set_m_closestHitFraction_1=b.asm.Tb).apply(null,arguments)},
|
||||
Md=b._emscripten_bind_ConvexResultCallback___destroy___0=function(){return(Md=b._emscripten_bind_ConvexResultCallback___destroy___0=b.asm.Ub).apply(null,arguments)},Nd=b._emscripten_bind_btConvexShape_setLocalScaling_1=function(){return(Nd=b._emscripten_bind_btConvexShape_setLocalScaling_1=b.asm.Vb).apply(null,arguments)},Od=b._emscripten_bind_btConvexShape_getLocalScaling_0=function(){return(Od=b._emscripten_bind_btConvexShape_getLocalScaling_0=b.asm.Wb).apply(null,arguments)},Pd=b._emscripten_bind_btConvexShape_calculateLocalInertia_2=
|
||||
function(){return(Pd=b._emscripten_bind_btConvexShape_calculateLocalInertia_2=b.asm.Xb).apply(null,arguments)},Qd=b._emscripten_bind_btConvexShape_setMargin_1=function(){return(Qd=b._emscripten_bind_btConvexShape_setMargin_1=b.asm.Yb).apply(null,arguments)},Rd=b._emscripten_bind_btConvexShape_getMargin_0=function(){return(Rd=b._emscripten_bind_btConvexShape_getMargin_0=b.asm.Zb).apply(null,arguments)},Sd=b._emscripten_bind_btConvexShape___destroy___0=function(){return(Sd=b._emscripten_bind_btConvexShape___destroy___0=
|
||||
b.asm._b).apply(null,arguments)},Td=b._emscripten_bind_btCapsuleShape_btCapsuleShape_2=function(){return(Td=b._emscripten_bind_btCapsuleShape_btCapsuleShape_2=b.asm.$b).apply(null,arguments)},Ud=b._emscripten_bind_btCapsuleShape_setMargin_1=function(){return(Ud=b._emscripten_bind_btCapsuleShape_setMargin_1=b.asm.ac).apply(null,arguments)},Vd=b._emscripten_bind_btCapsuleShape_getMargin_0=function(){return(Vd=b._emscripten_bind_btCapsuleShape_getMargin_0=b.asm.bc).apply(null,arguments)},Wd=b._emscripten_bind_btCapsuleShape_getUpAxis_0=
|
||||
function(){return(Wd=b._emscripten_bind_btCapsuleShape_getUpAxis_0=b.asm.cc).apply(null,arguments)},Xd=b._emscripten_bind_btCapsuleShape_getRadius_0=function(){return(Xd=b._emscripten_bind_btCapsuleShape_getRadius_0=b.asm.dc).apply(null,arguments)},Yd=b._emscripten_bind_btCapsuleShape_getHalfHeight_0=function(){return(Yd=b._emscripten_bind_btCapsuleShape_getHalfHeight_0=b.asm.ec).apply(null,arguments)},Zd=b._emscripten_bind_btCapsuleShape_setLocalScaling_1=function(){return(Zd=b._emscripten_bind_btCapsuleShape_setLocalScaling_1=
|
||||
b.asm.fc).apply(null,arguments)},$d=b._emscripten_bind_btCapsuleShape_getLocalScaling_0=function(){return($d=b._emscripten_bind_btCapsuleShape_getLocalScaling_0=b.asm.gc).apply(null,arguments)},ae=b._emscripten_bind_btCapsuleShape_calculateLocalInertia_2=function(){return(ae=b._emscripten_bind_btCapsuleShape_calculateLocalInertia_2=b.asm.hc).apply(null,arguments)},be=b._emscripten_bind_btCapsuleShape___destroy___0=function(){return(be=b._emscripten_bind_btCapsuleShape___destroy___0=b.asm.ic).apply(null,
|
||||
arguments)},ce=b._emscripten_bind_btCylinderShape_btCylinderShape_1=function(){return(ce=b._emscripten_bind_btCylinderShape_btCylinderShape_1=b.asm.jc).apply(null,arguments)},de=b._emscripten_bind_btCylinderShape_setMargin_1=function(){return(de=b._emscripten_bind_btCylinderShape_setMargin_1=b.asm.kc).apply(null,arguments)},ee=b._emscripten_bind_btCylinderShape_getMargin_0=function(){return(ee=b._emscripten_bind_btCylinderShape_getMargin_0=b.asm.lc).apply(null,arguments)},fe=b._emscripten_bind_btCylinderShape_setLocalScaling_1=
|
||||
function(){return(fe=b._emscripten_bind_btCylinderShape_setLocalScaling_1=b.asm.mc).apply(null,arguments)},ge=b._emscripten_bind_btCylinderShape_getLocalScaling_0=function(){return(ge=b._emscripten_bind_btCylinderShape_getLocalScaling_0=b.asm.nc).apply(null,arguments)},he=b._emscripten_bind_btCylinderShape_calculateLocalInertia_2=function(){return(he=b._emscripten_bind_btCylinderShape_calculateLocalInertia_2=b.asm.oc).apply(null,arguments)},ie=b._emscripten_bind_btCylinderShape___destroy___0=function(){return(ie=
|
||||
b._emscripten_bind_btCylinderShape___destroy___0=b.asm.pc).apply(null,arguments)},je=b._emscripten_bind_btConeShape_btConeShape_2=function(){return(je=b._emscripten_bind_btConeShape_btConeShape_2=b.asm.qc).apply(null,arguments)},ke=b._emscripten_bind_btConeShape_setLocalScaling_1=function(){return(ke=b._emscripten_bind_btConeShape_setLocalScaling_1=b.asm.rc).apply(null,arguments)},le=b._emscripten_bind_btConeShape_getLocalScaling_0=function(){return(le=b._emscripten_bind_btConeShape_getLocalScaling_0=
|
||||
b.asm.sc).apply(null,arguments)},me=b._emscripten_bind_btConeShape_calculateLocalInertia_2=function(){return(me=b._emscripten_bind_btConeShape_calculateLocalInertia_2=b.asm.tc).apply(null,arguments)},ne=b._emscripten_bind_btConeShape___destroy___0=function(){return(ne=b._emscripten_bind_btConeShape___destroy___0=b.asm.uc).apply(null,arguments)},oe=b._emscripten_bind_btStridingMeshInterface_setScaling_1=function(){return(oe=b._emscripten_bind_btStridingMeshInterface_setScaling_1=b.asm.vc).apply(null,
|
||||
arguments)},pe=b._emscripten_bind_btStridingMeshInterface___destroy___0=function(){return(pe=b._emscripten_bind_btStridingMeshInterface___destroy___0=b.asm.wc).apply(null,arguments)},qe=b._emscripten_bind_btTriangleMeshShape_setLocalScaling_1=function(){return(qe=b._emscripten_bind_btTriangleMeshShape_setLocalScaling_1=b.asm.xc).apply(null,arguments)},re=b._emscripten_bind_btTriangleMeshShape_getLocalScaling_0=function(){return(re=b._emscripten_bind_btTriangleMeshShape_getLocalScaling_0=b.asm.yc).apply(null,
|
||||
arguments)},se=b._emscripten_bind_btTriangleMeshShape_calculateLocalInertia_2=function(){return(se=b._emscripten_bind_btTriangleMeshShape_calculateLocalInertia_2=b.asm.zc).apply(null,arguments)},te=b._emscripten_bind_btTriangleMeshShape___destroy___0=function(){return(te=b._emscripten_bind_btTriangleMeshShape___destroy___0=b.asm.Ac).apply(null,arguments)},ue=b._emscripten_bind_btPrimitiveManagerBase_is_trimesh_0=function(){return(ue=b._emscripten_bind_btPrimitiveManagerBase_is_trimesh_0=b.asm.Bc).apply(null,
|
||||
arguments)},ve=b._emscripten_bind_btPrimitiveManagerBase_get_primitive_count_0=function(){return(ve=b._emscripten_bind_btPrimitiveManagerBase_get_primitive_count_0=b.asm.Cc).apply(null,arguments)},we=b._emscripten_bind_btPrimitiveManagerBase_get_primitive_box_2=function(){return(we=b._emscripten_bind_btPrimitiveManagerBase_get_primitive_box_2=b.asm.Dc).apply(null,arguments)},xe=b._emscripten_bind_btPrimitiveManagerBase_get_primitive_triangle_2=function(){return(xe=b._emscripten_bind_btPrimitiveManagerBase_get_primitive_triangle_2=
|
||||
b.asm.Ec).apply(null,arguments)},ye=b._emscripten_bind_btPrimitiveManagerBase___destroy___0=function(){return(ye=b._emscripten_bind_btPrimitiveManagerBase___destroy___0=b.asm.Fc).apply(null,arguments)},ze=b._emscripten_bind_btGImpactShapeInterface_updateBound_0=function(){return(ze=b._emscripten_bind_btGImpactShapeInterface_updateBound_0=b.asm.Gc).apply(null,arguments)},Ae=b._emscripten_bind_btGImpactShapeInterface_postUpdate_0=function(){return(Ae=b._emscripten_bind_btGImpactShapeInterface_postUpdate_0=
|
||||
b.asm.Hc).apply(null,arguments)},Be=b._emscripten_bind_btGImpactShapeInterface_getShapeType_0=function(){return(Be=b._emscripten_bind_btGImpactShapeInterface_getShapeType_0=b.asm.Ic).apply(null,arguments)},Ce=b._emscripten_bind_btGImpactShapeInterface_getName_0=function(){return(Ce=b._emscripten_bind_btGImpactShapeInterface_getName_0=b.asm.Jc).apply(null,arguments)},De=b._emscripten_bind_btGImpactShapeInterface_getGImpactShapeType_0=function(){return(De=b._emscripten_bind_btGImpactShapeInterface_getGImpactShapeType_0=
|
||||
b.asm.Kc).apply(null,arguments)},Ee=b._emscripten_bind_btGImpactShapeInterface_getPrimitiveManager_0=function(){return(Ee=b._emscripten_bind_btGImpactShapeInterface_getPrimitiveManager_0=b.asm.Lc).apply(null,arguments)},Fe=b._emscripten_bind_btGImpactShapeInterface_getNumChildShapes_0=function(){return(Fe=b._emscripten_bind_btGImpactShapeInterface_getNumChildShapes_0=b.asm.Mc).apply(null,arguments)},Ge=b._emscripten_bind_btGImpactShapeInterface_childrenHasTransform_0=function(){return(Ge=b._emscripten_bind_btGImpactShapeInterface_childrenHasTransform_0=
|
||||
b.asm.Nc).apply(null,arguments)},He=b._emscripten_bind_btGImpactShapeInterface_needsRetrieveTriangles_0=function(){return(He=b._emscripten_bind_btGImpactShapeInterface_needsRetrieveTriangles_0=b.asm.Oc).apply(null,arguments)},Ie=b._emscripten_bind_btGImpactShapeInterface_needsRetrieveTetrahedrons_0=function(){return(Ie=b._emscripten_bind_btGImpactShapeInterface_needsRetrieveTetrahedrons_0=b.asm.Pc).apply(null,arguments)},Je=b._emscripten_bind_btGImpactShapeInterface_getBulletTriangle_2=function(){return(Je=
|
||||
b._emscripten_bind_btGImpactShapeInterface_getBulletTriangle_2=b.asm.Qc).apply(null,arguments)},Ke=b._emscripten_bind_btGImpactShapeInterface_getBulletTetrahedron_2=function(){return(Ke=b._emscripten_bind_btGImpactShapeInterface_getBulletTetrahedron_2=b.asm.Rc).apply(null,arguments)},Le=b._emscripten_bind_btGImpactShapeInterface_getChildShape_1=function(){return(Le=b._emscripten_bind_btGImpactShapeInterface_getChildShape_1=b.asm.Sc).apply(null,arguments)},Me=b._emscripten_bind_btGImpactShapeInterface_getChildTransform_1=
|
||||
function(){return(Me=b._emscripten_bind_btGImpactShapeInterface_getChildTransform_1=b.asm.Tc).apply(null,arguments)},Ne=b._emscripten_bind_btGImpactShapeInterface_setChildTransform_2=function(){return(Ne=b._emscripten_bind_btGImpactShapeInterface_setChildTransform_2=b.asm.Uc).apply(null,arguments)},Oe=b._emscripten_bind_btGImpactShapeInterface_setLocalScaling_1=function(){return(Oe=b._emscripten_bind_btGImpactShapeInterface_setLocalScaling_1=b.asm.Vc).apply(null,arguments)},Pe=b._emscripten_bind_btGImpactShapeInterface_getLocalScaling_0=
|
||||
function(){return(Pe=b._emscripten_bind_btGImpactShapeInterface_getLocalScaling_0=b.asm.Wc).apply(null,arguments)},Qe=b._emscripten_bind_btGImpactShapeInterface_calculateLocalInertia_2=function(){return(Qe=b._emscripten_bind_btGImpactShapeInterface_calculateLocalInertia_2=b.asm.Xc).apply(null,arguments)},Re=b._emscripten_bind_btGImpactShapeInterface___destroy___0=function(){return(Re=b._emscripten_bind_btGImpactShapeInterface___destroy___0=b.asm.Yc).apply(null,arguments)},Se=b._emscripten_bind_btActivatingCollisionAlgorithm___destroy___0=
|
||||
function(){return(Se=b._emscripten_bind_btActivatingCollisionAlgorithm___destroy___0=b.asm.Zc).apply(null,arguments)},Te=b._emscripten_bind_btDefaultCollisionConfiguration_btDefaultCollisionConfiguration_0=function(){return(Te=b._emscripten_bind_btDefaultCollisionConfiguration_btDefaultCollisionConfiguration_0=b.asm._c).apply(null,arguments)},Ue=b._emscripten_bind_btDefaultCollisionConfiguration_btDefaultCollisionConfiguration_1=function(){return(Ue=b._emscripten_bind_btDefaultCollisionConfiguration_btDefaultCollisionConfiguration_1=
|
||||
b.asm.$c).apply(null,arguments)},Ve=b._emscripten_bind_btDefaultCollisionConfiguration___destroy___0=function(){return(Ve=b._emscripten_bind_btDefaultCollisionConfiguration___destroy___0=b.asm.ad).apply(null,arguments)},We=b._emscripten_bind_btDispatcher_getNumManifolds_0=function(){return(We=b._emscripten_bind_btDispatcher_getNumManifolds_0=b.asm.bd).apply(null,arguments)},Xe=b._emscripten_bind_btDispatcher_getManifoldByIndexInternal_1=function(){return(Xe=b._emscripten_bind_btDispatcher_getManifoldByIndexInternal_1=
|
||||
b.asm.cd).apply(null,arguments)},Ye=b._emscripten_bind_btDispatcher___destroy___0=function(){return(Ye=b._emscripten_bind_btDispatcher___destroy___0=b.asm.dd).apply(null,arguments)},Ze=b._emscripten_bind_btGeneric6DofConstraint_btGeneric6DofConstraint_3=function(){return(Ze=b._emscripten_bind_btGeneric6DofConstraint_btGeneric6DofConstraint_3=b.asm.ed).apply(null,arguments)},$e=b._emscripten_bind_btGeneric6DofConstraint_btGeneric6DofConstraint_5=function(){return($e=b._emscripten_bind_btGeneric6DofConstraint_btGeneric6DofConstraint_5=
|
||||
b.asm.fd).apply(null,arguments)},af=b._emscripten_bind_btGeneric6DofConstraint_setLinearLowerLimit_1=function(){return(af=b._emscripten_bind_btGeneric6DofConstraint_setLinearLowerLimit_1=b.asm.gd).apply(null,arguments)},bf=b._emscripten_bind_btGeneric6DofConstraint_setLinearUpperLimit_1=function(){return(bf=b._emscripten_bind_btGeneric6DofConstraint_setLinearUpperLimit_1=b.asm.hd).apply(null,arguments)},cf=b._emscripten_bind_btGeneric6DofConstraint_setAngularLowerLimit_1=function(){return(cf=b._emscripten_bind_btGeneric6DofConstraint_setAngularLowerLimit_1=
|
||||
b.asm.id).apply(null,arguments)},df=b._emscripten_bind_btGeneric6DofConstraint_setAngularUpperLimit_1=function(){return(df=b._emscripten_bind_btGeneric6DofConstraint_setAngularUpperLimit_1=b.asm.jd).apply(null,arguments)},ef=b._emscripten_bind_btGeneric6DofConstraint_getFrameOffsetA_0=function(){return(ef=b._emscripten_bind_btGeneric6DofConstraint_getFrameOffsetA_0=b.asm.kd).apply(null,arguments)},ff=b._emscripten_bind_btGeneric6DofConstraint_enableFeedback_1=function(){return(ff=b._emscripten_bind_btGeneric6DofConstraint_enableFeedback_1=
|
||||
b.asm.ld).apply(null,arguments)},gf=b._emscripten_bind_btGeneric6DofConstraint_getBreakingImpulseThreshold_0=function(){return(gf=b._emscripten_bind_btGeneric6DofConstraint_getBreakingImpulseThreshold_0=b.asm.md).apply(null,arguments)},hf=b._emscripten_bind_btGeneric6DofConstraint_setBreakingImpulseThreshold_1=function(){return(hf=b._emscripten_bind_btGeneric6DofConstraint_setBreakingImpulseThreshold_1=b.asm.nd).apply(null,arguments)},jf=b._emscripten_bind_btGeneric6DofConstraint_getParam_2=function(){return(jf=
|
||||
b._emscripten_bind_btGeneric6DofConstraint_getParam_2=b.asm.od).apply(null,arguments)},kf=b._emscripten_bind_btGeneric6DofConstraint_setParam_3=function(){return(kf=b._emscripten_bind_btGeneric6DofConstraint_setParam_3=b.asm.pd).apply(null,arguments)},lf=b._emscripten_bind_btGeneric6DofConstraint___destroy___0=function(){return(lf=b._emscripten_bind_btGeneric6DofConstraint___destroy___0=b.asm.qd).apply(null,arguments)},mf=b._emscripten_bind_btDiscreteDynamicsWorld_btDiscreteDynamicsWorld_4=function(){return(mf=
|
||||
b._emscripten_bind_btDiscreteDynamicsWorld_btDiscreteDynamicsWorld_4=b.asm.rd).apply(null,arguments)},nf=b._emscripten_bind_btDiscreteDynamicsWorld_setGravity_1=function(){return(nf=b._emscripten_bind_btDiscreteDynamicsWorld_setGravity_1=b.asm.sd).apply(null,arguments)},of=b._emscripten_bind_btDiscreteDynamicsWorld_getGravity_0=function(){return(of=b._emscripten_bind_btDiscreteDynamicsWorld_getGravity_0=b.asm.td).apply(null,arguments)},pf=b._emscripten_bind_btDiscreteDynamicsWorld_addRigidBody_1=
|
||||
function(){return(pf=b._emscripten_bind_btDiscreteDynamicsWorld_addRigidBody_1=b.asm.ud).apply(null,arguments)},qf=b._emscripten_bind_btDiscreteDynamicsWorld_addRigidBody_3=function(){return(qf=b._emscripten_bind_btDiscreteDynamicsWorld_addRigidBody_3=b.asm.vd).apply(null,arguments)},rf=b._emscripten_bind_btDiscreteDynamicsWorld_removeRigidBody_1=function(){return(rf=b._emscripten_bind_btDiscreteDynamicsWorld_removeRigidBody_1=b.asm.wd).apply(null,arguments)},sf=b._emscripten_bind_btDiscreteDynamicsWorld_addConstraint_1=
|
||||
function(){return(sf=b._emscripten_bind_btDiscreteDynamicsWorld_addConstraint_1=b.asm.xd).apply(null,arguments)},tf=b._emscripten_bind_btDiscreteDynamicsWorld_addConstraint_2=function(){return(tf=b._emscripten_bind_btDiscreteDynamicsWorld_addConstraint_2=b.asm.yd).apply(null,arguments)},uf=b._emscripten_bind_btDiscreteDynamicsWorld_removeConstraint_1=function(){return(uf=b._emscripten_bind_btDiscreteDynamicsWorld_removeConstraint_1=b.asm.zd).apply(null,arguments)},vf=b._emscripten_bind_btDiscreteDynamicsWorld_stepSimulation_1=
|
||||
function(){return(vf=b._emscripten_bind_btDiscreteDynamicsWorld_stepSimulation_1=b.asm.Ad).apply(null,arguments)},wf=b._emscripten_bind_btDiscreteDynamicsWorld_stepSimulation_2=function(){return(wf=b._emscripten_bind_btDiscreteDynamicsWorld_stepSimulation_2=b.asm.Bd).apply(null,arguments)},xf=b._emscripten_bind_btDiscreteDynamicsWorld_stepSimulation_3=function(){return(xf=b._emscripten_bind_btDiscreteDynamicsWorld_stepSimulation_3=b.asm.Cd).apply(null,arguments)},yf=b._emscripten_bind_btDiscreteDynamicsWorld_setContactAddedCallback_1=
|
||||
function(){return(yf=b._emscripten_bind_btDiscreteDynamicsWorld_setContactAddedCallback_1=b.asm.Dd).apply(null,arguments)},zf=b._emscripten_bind_btDiscreteDynamicsWorld_setContactProcessedCallback_1=function(){return(zf=b._emscripten_bind_btDiscreteDynamicsWorld_setContactProcessedCallback_1=b.asm.Ed).apply(null,arguments)},Af=b._emscripten_bind_btDiscreteDynamicsWorld_setContactDestroyedCallback_1=function(){return(Af=b._emscripten_bind_btDiscreteDynamicsWorld_setContactDestroyedCallback_1=b.asm.Fd).apply(null,
|
||||
arguments)},Bf=b._emscripten_bind_btDiscreteDynamicsWorld_getDispatcher_0=function(){return(Bf=b._emscripten_bind_btDiscreteDynamicsWorld_getDispatcher_0=b.asm.Gd).apply(null,arguments)},Cf=b._emscripten_bind_btDiscreteDynamicsWorld_rayTest_3=function(){return(Cf=b._emscripten_bind_btDiscreteDynamicsWorld_rayTest_3=b.asm.Hd).apply(null,arguments)},Df=b._emscripten_bind_btDiscreteDynamicsWorld_getPairCache_0=function(){return(Df=b._emscripten_bind_btDiscreteDynamicsWorld_getPairCache_0=b.asm.Id).apply(null,
|
||||
arguments)},Ef=b._emscripten_bind_btDiscreteDynamicsWorld_getDispatchInfo_0=function(){return(Ef=b._emscripten_bind_btDiscreteDynamicsWorld_getDispatchInfo_0=b.asm.Jd).apply(null,arguments)},Ff=b._emscripten_bind_btDiscreteDynamicsWorld_addCollisionObject_1=function(){return(Ff=b._emscripten_bind_btDiscreteDynamicsWorld_addCollisionObject_1=b.asm.Kd).apply(null,arguments)},Gf=b._emscripten_bind_btDiscreteDynamicsWorld_addCollisionObject_2=function(){return(Gf=b._emscripten_bind_btDiscreteDynamicsWorld_addCollisionObject_2=
|
||||
b.asm.Ld).apply(null,arguments)},Hf=b._emscripten_bind_btDiscreteDynamicsWorld_addCollisionObject_3=function(){return(Hf=b._emscripten_bind_btDiscreteDynamicsWorld_addCollisionObject_3=b.asm.Md).apply(null,arguments)},If=b._emscripten_bind_btDiscreteDynamicsWorld_removeCollisionObject_1=function(){return(If=b._emscripten_bind_btDiscreteDynamicsWorld_removeCollisionObject_1=b.asm.Nd).apply(null,arguments)},Jf=b._emscripten_bind_btDiscreteDynamicsWorld_getBroadphase_0=function(){return(Jf=b._emscripten_bind_btDiscreteDynamicsWorld_getBroadphase_0=
|
||||
b.asm.Od).apply(null,arguments)},Kf=b._emscripten_bind_btDiscreteDynamicsWorld_convexSweepTest_5=function(){return(Kf=b._emscripten_bind_btDiscreteDynamicsWorld_convexSweepTest_5=b.asm.Pd).apply(null,arguments)},Lf=b._emscripten_bind_btDiscreteDynamicsWorld_contactPairTest_3=function(){return(Lf=b._emscripten_bind_btDiscreteDynamicsWorld_contactPairTest_3=b.asm.Qd).apply(null,arguments)},Mf=b._emscripten_bind_btDiscreteDynamicsWorld_contactTest_2=function(){return(Mf=b._emscripten_bind_btDiscreteDynamicsWorld_contactTest_2=
|
||||
b.asm.Rd).apply(null,arguments)},Nf=b._emscripten_bind_btDiscreteDynamicsWorld_updateSingleAabb_1=function(){return(Nf=b._emscripten_bind_btDiscreteDynamicsWorld_updateSingleAabb_1=b.asm.Sd).apply(null,arguments)},Of=b._emscripten_bind_btDiscreteDynamicsWorld_setDebugDrawer_1=function(){return(Of=b._emscripten_bind_btDiscreteDynamicsWorld_setDebugDrawer_1=b.asm.Td).apply(null,arguments)},Pf=b._emscripten_bind_btDiscreteDynamicsWorld_getDebugDrawer_0=function(){return(Pf=b._emscripten_bind_btDiscreteDynamicsWorld_getDebugDrawer_0=
|
||||
b.asm.Ud).apply(null,arguments)},Qf=b._emscripten_bind_btDiscreteDynamicsWorld_debugDrawWorld_0=function(){return(Qf=b._emscripten_bind_btDiscreteDynamicsWorld_debugDrawWorld_0=b.asm.Vd).apply(null,arguments)},Rf=b._emscripten_bind_btDiscreteDynamicsWorld_debugDrawObject_3=function(){return(Rf=b._emscripten_bind_btDiscreteDynamicsWorld_debugDrawObject_3=b.asm.Wd).apply(null,arguments)},Sf=b._emscripten_bind_btDiscreteDynamicsWorld_addAction_1=function(){return(Sf=b._emscripten_bind_btDiscreteDynamicsWorld_addAction_1=
|
||||
b.asm.Xd).apply(null,arguments)},Tf=b._emscripten_bind_btDiscreteDynamicsWorld_removeAction_1=function(){return(Tf=b._emscripten_bind_btDiscreteDynamicsWorld_removeAction_1=b.asm.Yd).apply(null,arguments)},Uf=b._emscripten_bind_btDiscreteDynamicsWorld_getSolverInfo_0=function(){return(Uf=b._emscripten_bind_btDiscreteDynamicsWorld_getSolverInfo_0=b.asm.Zd).apply(null,arguments)},Vf=b._emscripten_bind_btDiscreteDynamicsWorld_setInternalTickCallback_1=function(){return(Vf=b._emscripten_bind_btDiscreteDynamicsWorld_setInternalTickCallback_1=
|
||||
b.asm._d).apply(null,arguments)},Wf=b._emscripten_bind_btDiscreteDynamicsWorld_setInternalTickCallback_2=function(){return(Wf=b._emscripten_bind_btDiscreteDynamicsWorld_setInternalTickCallback_2=b.asm.$d).apply(null,arguments)},Xf=b._emscripten_bind_btDiscreteDynamicsWorld_setInternalTickCallback_3=function(){return(Xf=b._emscripten_bind_btDiscreteDynamicsWorld_setInternalTickCallback_3=b.asm.ae).apply(null,arguments)},Yf=b._emscripten_bind_btDiscreteDynamicsWorld___destroy___0=function(){return(Yf=
|
||||
b._emscripten_bind_btDiscreteDynamicsWorld___destroy___0=b.asm.be).apply(null,arguments)},Zf=b._emscripten_bind_btVehicleRaycaster_castRay_3=function(){return(Zf=b._emscripten_bind_btVehicleRaycaster_castRay_3=b.asm.ce).apply(null,arguments)},$f=b._emscripten_bind_btVehicleRaycaster___destroy___0=function(){return($f=b._emscripten_bind_btVehicleRaycaster___destroy___0=b.asm.de).apply(null,arguments)},ag=b._emscripten_bind_btActionInterface_updateAction_2=function(){return(ag=b._emscripten_bind_btActionInterface_updateAction_2=
|
||||
b.asm.ee).apply(null,arguments)},bg=b._emscripten_bind_btActionInterface___destroy___0=function(){return(bg=b._emscripten_bind_btActionInterface___destroy___0=b.asm.fe).apply(null,arguments)},cg=b._emscripten_bind_btGhostObject_btGhostObject_0=function(){return(cg=b._emscripten_bind_btGhostObject_btGhostObject_0=b.asm.ge).apply(null,arguments)},dg=b._emscripten_bind_btGhostObject_getNumOverlappingObjects_0=function(){return(dg=b._emscripten_bind_btGhostObject_getNumOverlappingObjects_0=b.asm.he).apply(null,
|
||||
arguments)},eg=b._emscripten_bind_btGhostObject_getOverlappingObject_1=function(){return(eg=b._emscripten_bind_btGhostObject_getOverlappingObject_1=b.asm.ie).apply(null,arguments)},fg=b._emscripten_bind_btGhostObject_setAnisotropicFriction_2=function(){return(fg=b._emscripten_bind_btGhostObject_setAnisotropicFriction_2=b.asm.je).apply(null,arguments)},gg=b._emscripten_bind_btGhostObject_getCollisionShape_0=function(){return(gg=b._emscripten_bind_btGhostObject_getCollisionShape_0=b.asm.ke).apply(null,
|
||||
arguments)},hg=b._emscripten_bind_btGhostObject_setContactProcessingThreshold_1=function(){return(hg=b._emscripten_bind_btGhostObject_setContactProcessingThreshold_1=b.asm.le).apply(null,arguments)},ig=b._emscripten_bind_btGhostObject_setActivationState_1=function(){return(ig=b._emscripten_bind_btGhostObject_setActivationState_1=b.asm.me).apply(null,arguments)},jg=b._emscripten_bind_btGhostObject_forceActivationState_1=function(){return(jg=b._emscripten_bind_btGhostObject_forceActivationState_1=b.asm.ne).apply(null,
|
||||
arguments)},kg=b._emscripten_bind_btGhostObject_activate_0=function(){return(kg=b._emscripten_bind_btGhostObject_activate_0=b.asm.oe).apply(null,arguments)},lg=b._emscripten_bind_btGhostObject_activate_1=function(){return(lg=b._emscripten_bind_btGhostObject_activate_1=b.asm.pe).apply(null,arguments)},mg=b._emscripten_bind_btGhostObject_isActive_0=function(){return(mg=b._emscripten_bind_btGhostObject_isActive_0=b.asm.qe).apply(null,arguments)},ng=b._emscripten_bind_btGhostObject_isKinematicObject_0=
|
||||
function(){return(ng=b._emscripten_bind_btGhostObject_isKinematicObject_0=b.asm.re).apply(null,arguments)},og=b._emscripten_bind_btGhostObject_isStaticObject_0=function(){return(og=b._emscripten_bind_btGhostObject_isStaticObject_0=b.asm.se).apply(null,arguments)},pg=b._emscripten_bind_btGhostObject_isStaticOrKinematicObject_0=function(){return(pg=b._emscripten_bind_btGhostObject_isStaticOrKinematicObject_0=b.asm.te).apply(null,arguments)},qg=b._emscripten_bind_btGhostObject_getRestitution_0=function(){return(qg=
|
||||
b._emscripten_bind_btGhostObject_getRestitution_0=b.asm.ue).apply(null,arguments)},rg=b._emscripten_bind_btGhostObject_getFriction_0=function(){return(rg=b._emscripten_bind_btGhostObject_getFriction_0=b.asm.ve).apply(null,arguments)},sg=b._emscripten_bind_btGhostObject_getRollingFriction_0=function(){return(sg=b._emscripten_bind_btGhostObject_getRollingFriction_0=b.asm.we).apply(null,arguments)},tg=b._emscripten_bind_btGhostObject_setRestitution_1=function(){return(tg=b._emscripten_bind_btGhostObject_setRestitution_1=
|
||||
b.asm.xe).apply(null,arguments)},ug=b._emscripten_bind_btGhostObject_setFriction_1=function(){return(ug=b._emscripten_bind_btGhostObject_setFriction_1=b.asm.ye).apply(null,arguments)},vg=b._emscripten_bind_btGhostObject_setRollingFriction_1=function(){return(vg=b._emscripten_bind_btGhostObject_setRollingFriction_1=b.asm.ze).apply(null,arguments)},wg=b._emscripten_bind_btGhostObject_getWorldTransform_0=function(){return(wg=b._emscripten_bind_btGhostObject_getWorldTransform_0=b.asm.Ae).apply(null,arguments)},
|
||||
xg=b._emscripten_bind_btGhostObject_getCollisionFlags_0=function(){return(xg=b._emscripten_bind_btGhostObject_getCollisionFlags_0=b.asm.Be).apply(null,arguments)},yg=b._emscripten_bind_btGhostObject_setCollisionFlags_1=function(){return(yg=b._emscripten_bind_btGhostObject_setCollisionFlags_1=b.asm.Ce).apply(null,arguments)},zg=b._emscripten_bind_btGhostObject_setWorldTransform_1=function(){return(zg=b._emscripten_bind_btGhostObject_setWorldTransform_1=b.asm.De).apply(null,arguments)},Ag=b._emscripten_bind_btGhostObject_setCollisionShape_1=
|
||||
function(){return(Ag=b._emscripten_bind_btGhostObject_setCollisionShape_1=b.asm.Ee).apply(null,arguments)},Bg=b._emscripten_bind_btGhostObject_setCcdMotionThreshold_1=function(){return(Bg=b._emscripten_bind_btGhostObject_setCcdMotionThreshold_1=b.asm.Fe).apply(null,arguments)},Cg=b._emscripten_bind_btGhostObject_setCcdSweptSphereRadius_1=function(){return(Cg=b._emscripten_bind_btGhostObject_setCcdSweptSphereRadius_1=b.asm.Ge).apply(null,arguments)},Dg=b._emscripten_bind_btGhostObject_getUserIndex_0=
|
||||
function(){return(Dg=b._emscripten_bind_btGhostObject_getUserIndex_0=b.asm.He).apply(null,arguments)},Eg=b._emscripten_bind_btGhostObject_setUserIndex_1=function(){return(Eg=b._emscripten_bind_btGhostObject_setUserIndex_1=b.asm.Ie).apply(null,arguments)},Fg=b._emscripten_bind_btGhostObject_getUserPointer_0=function(){return(Fg=b._emscripten_bind_btGhostObject_getUserPointer_0=b.asm.Je).apply(null,arguments)},Gg=b._emscripten_bind_btGhostObject_setUserPointer_1=function(){return(Gg=b._emscripten_bind_btGhostObject_setUserPointer_1=
|
||||
b.asm.Ke).apply(null,arguments)},Hg=b._emscripten_bind_btGhostObject_getBroadphaseHandle_0=function(){return(Hg=b._emscripten_bind_btGhostObject_getBroadphaseHandle_0=b.asm.Le).apply(null,arguments)},Ig=b._emscripten_bind_btGhostObject___destroy___0=function(){return(Ig=b._emscripten_bind_btGhostObject___destroy___0=b.asm.Me).apply(null,arguments)},Jg=b._emscripten_bind_btSoftBodySolver___destroy___0=function(){return(Jg=b._emscripten_bind_btSoftBodySolver___destroy___0=b.asm.Ne).apply(null,arguments)},
|
||||
Kg=b._emscripten_bind_VoidPtr___destroy___0=function(){return(Kg=b._emscripten_bind_VoidPtr___destroy___0=b.asm.Oe).apply(null,arguments)},Lg=b._emscripten_bind_DebugDrawer_DebugDrawer_0=function(){return(Lg=b._emscripten_bind_DebugDrawer_DebugDrawer_0=b.asm.Pe).apply(null,arguments)},Mg=b._emscripten_bind_DebugDrawer_drawLine_3=function(){return(Mg=b._emscripten_bind_DebugDrawer_drawLine_3=b.asm.Qe).apply(null,arguments)},Ng=b._emscripten_bind_DebugDrawer_drawContactPoint_5=function(){return(Ng=
|
||||
b._emscripten_bind_DebugDrawer_drawContactPoint_5=b.asm.Re).apply(null,arguments)},Og=b._emscripten_bind_DebugDrawer_reportErrorWarning_1=function(){return(Og=b._emscripten_bind_DebugDrawer_reportErrorWarning_1=b.asm.Se).apply(null,arguments)},Pg=b._emscripten_bind_DebugDrawer_draw3dText_2=function(){return(Pg=b._emscripten_bind_DebugDrawer_draw3dText_2=b.asm.Te).apply(null,arguments)},Qg=b._emscripten_bind_DebugDrawer_setDebugMode_1=function(){return(Qg=b._emscripten_bind_DebugDrawer_setDebugMode_1=
|
||||
b.asm.Ue).apply(null,arguments)},Rg=b._emscripten_bind_DebugDrawer_getDebugMode_0=function(){return(Rg=b._emscripten_bind_DebugDrawer_getDebugMode_0=b.asm.Ve).apply(null,arguments)},Sg=b._emscripten_bind_DebugDrawer___destroy___0=function(){return(Sg=b._emscripten_bind_DebugDrawer___destroy___0=b.asm.We).apply(null,arguments)},Tg=b._emscripten_bind_btVector4_btVector4_0=function(){return(Tg=b._emscripten_bind_btVector4_btVector4_0=b.asm.Xe).apply(null,arguments)},Ug=b._emscripten_bind_btVector4_btVector4_4=
|
||||
function(){return(Ug=b._emscripten_bind_btVector4_btVector4_4=b.asm.Ye).apply(null,arguments)},Vg=b._emscripten_bind_btVector4_w_0=function(){return(Vg=b._emscripten_bind_btVector4_w_0=b.asm.Ze).apply(null,arguments)},Wg=b._emscripten_bind_btVector4_setValue_4=function(){return(Wg=b._emscripten_bind_btVector4_setValue_4=b.asm._e).apply(null,arguments)},Xg=b._emscripten_bind_btVector4_length_0=function(){return(Xg=b._emscripten_bind_btVector4_length_0=b.asm.$e).apply(null,arguments)},Yg=b._emscripten_bind_btVector4_x_0=
|
||||
function(){return(Yg=b._emscripten_bind_btVector4_x_0=b.asm.af).apply(null,arguments)},Zg=b._emscripten_bind_btVector4_y_0=function(){return(Zg=b._emscripten_bind_btVector4_y_0=b.asm.bf).apply(null,arguments)},$g=b._emscripten_bind_btVector4_z_0=function(){return($g=b._emscripten_bind_btVector4_z_0=b.asm.cf).apply(null,arguments)},ah=b._emscripten_bind_btVector4_setX_1=function(){return(ah=b._emscripten_bind_btVector4_setX_1=b.asm.df).apply(null,arguments)},bh=b._emscripten_bind_btVector4_setY_1=
|
||||
function(){return(bh=b._emscripten_bind_btVector4_setY_1=b.asm.ef).apply(null,arguments)},ch=b._emscripten_bind_btVector4_setZ_1=function(){return(ch=b._emscripten_bind_btVector4_setZ_1=b.asm.ff).apply(null,arguments)},dh=b._emscripten_bind_btVector4_normalize_0=function(){return(dh=b._emscripten_bind_btVector4_normalize_0=b.asm.gf).apply(null,arguments)},eh=b._emscripten_bind_btVector4_rotate_2=function(){return(eh=b._emscripten_bind_btVector4_rotate_2=b.asm.hf).apply(null,arguments)},fh=b._emscripten_bind_btVector4_dot_1=
|
||||
function(){return(fh=b._emscripten_bind_btVector4_dot_1=b.asm.jf).apply(null,arguments)},gh=b._emscripten_bind_btVector4_op_mul_1=function(){return(gh=b._emscripten_bind_btVector4_op_mul_1=b.asm.kf).apply(null,arguments)},hh=b._emscripten_bind_btVector4_op_add_1=function(){return(hh=b._emscripten_bind_btVector4_op_add_1=b.asm.lf).apply(null,arguments)},ih=b._emscripten_bind_btVector4_op_sub_1=function(){return(ih=b._emscripten_bind_btVector4_op_sub_1=b.asm.mf).apply(null,arguments)},jh=b._emscripten_bind_btVector4___destroy___0=
|
||||
function(){return(jh=b._emscripten_bind_btVector4___destroy___0=b.asm.nf).apply(null,arguments)},kh=b._emscripten_bind_btQuaternion_btQuaternion_4=function(){return(kh=b._emscripten_bind_btQuaternion_btQuaternion_4=b.asm.of).apply(null,arguments)},lh=b._emscripten_bind_btQuaternion_setValue_4=function(){return(lh=b._emscripten_bind_btQuaternion_setValue_4=b.asm.pf).apply(null,arguments)},mh=b._emscripten_bind_btQuaternion_setEulerZYX_3=function(){return(mh=b._emscripten_bind_btQuaternion_setEulerZYX_3=
|
||||
b.asm.qf).apply(null,arguments)},nh=b._emscripten_bind_btQuaternion_setRotation_2=function(){return(nh=b._emscripten_bind_btQuaternion_setRotation_2=b.asm.rf).apply(null,arguments)},oh=b._emscripten_bind_btQuaternion_normalize_0=function(){return(oh=b._emscripten_bind_btQuaternion_normalize_0=b.asm.sf).apply(null,arguments)},ph=b._emscripten_bind_btQuaternion_length2_0=function(){return(ph=b._emscripten_bind_btQuaternion_length2_0=b.asm.tf).apply(null,arguments)},qh=b._emscripten_bind_btQuaternion_length_0=
|
||||
function(){return(qh=b._emscripten_bind_btQuaternion_length_0=b.asm.uf).apply(null,arguments)},rh=b._emscripten_bind_btQuaternion_dot_1=function(){return(rh=b._emscripten_bind_btQuaternion_dot_1=b.asm.vf).apply(null,arguments)},sh=b._emscripten_bind_btQuaternion_normalized_0=function(){return(sh=b._emscripten_bind_btQuaternion_normalized_0=b.asm.wf).apply(null,arguments)},th=b._emscripten_bind_btQuaternion_getAxis_0=function(){return(th=b._emscripten_bind_btQuaternion_getAxis_0=b.asm.xf).apply(null,
|
||||
arguments)},uh=b._emscripten_bind_btQuaternion_inverse_0=function(){return(uh=b._emscripten_bind_btQuaternion_inverse_0=b.asm.yf).apply(null,arguments)},vh=b._emscripten_bind_btQuaternion_getAngle_0=function(){return(vh=b._emscripten_bind_btQuaternion_getAngle_0=b.asm.zf).apply(null,arguments)},wh=b._emscripten_bind_btQuaternion_getAngleShortestPath_0=function(){return(wh=b._emscripten_bind_btQuaternion_getAngleShortestPath_0=b.asm.Af).apply(null,arguments)},xh=b._emscripten_bind_btQuaternion_angle_1=
|
||||
function(){return(xh=b._emscripten_bind_btQuaternion_angle_1=b.asm.Bf).apply(null,arguments)},yh=b._emscripten_bind_btQuaternion_angleShortestPath_1=function(){return(yh=b._emscripten_bind_btQuaternion_angleShortestPath_1=b.asm.Cf).apply(null,arguments)},zh=b._emscripten_bind_btQuaternion_op_add_1=function(){return(zh=b._emscripten_bind_btQuaternion_op_add_1=b.asm.Df).apply(null,arguments)},Ah=b._emscripten_bind_btQuaternion_op_sub_1=function(){return(Ah=b._emscripten_bind_btQuaternion_op_sub_1=b.asm.Ef).apply(null,
|
||||
arguments)},Bh=b._emscripten_bind_btQuaternion_op_mul_1=function(){return(Bh=b._emscripten_bind_btQuaternion_op_mul_1=b.asm.Ff).apply(null,arguments)},Ch=b._emscripten_bind_btQuaternion_op_mulq_1=function(){return(Ch=b._emscripten_bind_btQuaternion_op_mulq_1=b.asm.Gf).apply(null,arguments)},Dh=b._emscripten_bind_btQuaternion_op_div_1=function(){return(Dh=b._emscripten_bind_btQuaternion_op_div_1=b.asm.Hf).apply(null,arguments)},Eh=b._emscripten_bind_btQuaternion_x_0=function(){return(Eh=b._emscripten_bind_btQuaternion_x_0=
|
||||
b.asm.If).apply(null,arguments)},Fh=b._emscripten_bind_btQuaternion_y_0=function(){return(Fh=b._emscripten_bind_btQuaternion_y_0=b.asm.Jf).apply(null,arguments)},Gh=b._emscripten_bind_btQuaternion_z_0=function(){return(Gh=b._emscripten_bind_btQuaternion_z_0=b.asm.Kf).apply(null,arguments)},Hh=b._emscripten_bind_btQuaternion_w_0=function(){return(Hh=b._emscripten_bind_btQuaternion_w_0=b.asm.Lf).apply(null,arguments)},Ih=b._emscripten_bind_btQuaternion_setX_1=function(){return(Ih=b._emscripten_bind_btQuaternion_setX_1=
|
||||
b.asm.Mf).apply(null,arguments)},Jh=b._emscripten_bind_btQuaternion_setY_1=function(){return(Jh=b._emscripten_bind_btQuaternion_setY_1=b.asm.Nf).apply(null,arguments)},Kh=b._emscripten_bind_btQuaternion_setZ_1=function(){return(Kh=b._emscripten_bind_btQuaternion_setZ_1=b.asm.Of).apply(null,arguments)},Lh=b._emscripten_bind_btQuaternion_setW_1=function(){return(Lh=b._emscripten_bind_btQuaternion_setW_1=b.asm.Pf).apply(null,arguments)},Mh=b._emscripten_bind_btQuaternion___destroy___0=function(){return(Mh=
|
||||
b._emscripten_bind_btQuaternion___destroy___0=b.asm.Qf).apply(null,arguments)},Nh=b._emscripten_bind_btMatrix3x3_setEulerZYX_3=function(){return(Nh=b._emscripten_bind_btMatrix3x3_setEulerZYX_3=b.asm.Rf).apply(null,arguments)},Oh=b._emscripten_bind_btMatrix3x3_getRotation_1=function(){return(Oh=b._emscripten_bind_btMatrix3x3_getRotation_1=b.asm.Sf).apply(null,arguments)},Ph=b._emscripten_bind_btMatrix3x3_getRow_1=function(){return(Ph=b._emscripten_bind_btMatrix3x3_getRow_1=b.asm.Tf).apply(null,arguments)},
|
||||
Qh=b._emscripten_bind_btMatrix3x3___destroy___0=function(){return(Qh=b._emscripten_bind_btMatrix3x3___destroy___0=b.asm.Uf).apply(null,arguments)},Rh=b._emscripten_bind_btTransform_btTransform_0=function(){return(Rh=b._emscripten_bind_btTransform_btTransform_0=b.asm.Vf).apply(null,arguments)},Sh=b._emscripten_bind_btTransform_btTransform_2=function(){return(Sh=b._emscripten_bind_btTransform_btTransform_2=b.asm.Wf).apply(null,arguments)},Th=b._emscripten_bind_btTransform_setIdentity_0=function(){return(Th=
|
||||
b._emscripten_bind_btTransform_setIdentity_0=b.asm.Xf).apply(null,arguments)},Uh=b._emscripten_bind_btTransform_setOrigin_1=function(){return(Uh=b._emscripten_bind_btTransform_setOrigin_1=b.asm.Yf).apply(null,arguments)},Vh=b._emscripten_bind_btTransform_setRotation_1=function(){return(Vh=b._emscripten_bind_btTransform_setRotation_1=b.asm.Zf).apply(null,arguments)},Wh=b._emscripten_bind_btTransform_getOrigin_0=function(){return(Wh=b._emscripten_bind_btTransform_getOrigin_0=b.asm._f).apply(null,arguments)},
|
||||
Xh=b._emscripten_bind_btTransform_getRotation_0=function(){return(Xh=b._emscripten_bind_btTransform_getRotation_0=b.asm.$f).apply(null,arguments)},Yh=b._emscripten_bind_btTransform_getBasis_0=function(){return(Yh=b._emscripten_bind_btTransform_getBasis_0=b.asm.ag).apply(null,arguments)},Zh=b._emscripten_bind_btTransform_setFromOpenGLMatrix_1=function(){return(Zh=b._emscripten_bind_btTransform_setFromOpenGLMatrix_1=b.asm.bg).apply(null,arguments)},$h=b._emscripten_bind_btTransform_inverse_0=function(){return($h=
|
||||
b._emscripten_bind_btTransform_inverse_0=b.asm.cg).apply(null,arguments)},ai=b._emscripten_bind_btTransform_op_mul_1=function(){return(ai=b._emscripten_bind_btTransform_op_mul_1=b.asm.dg).apply(null,arguments)},bi=b._emscripten_bind_btTransform___destroy___0=function(){return(bi=b._emscripten_bind_btTransform___destroy___0=b.asm.eg).apply(null,arguments)},ci=b._emscripten_bind_MotionState_MotionState_0=function(){return(ci=b._emscripten_bind_MotionState_MotionState_0=b.asm.fg).apply(null,arguments)},
|
||||
di=b._emscripten_bind_MotionState_getWorldTransform_1=function(){return(di=b._emscripten_bind_MotionState_getWorldTransform_1=b.asm.gg).apply(null,arguments)},ei=b._emscripten_bind_MotionState_setWorldTransform_1=function(){return(ei=b._emscripten_bind_MotionState_setWorldTransform_1=b.asm.hg).apply(null,arguments)},fi=b._emscripten_bind_MotionState___destroy___0=function(){return(fi=b._emscripten_bind_MotionState___destroy___0=b.asm.ig).apply(null,arguments)},gi=b._emscripten_bind_btDefaultMotionState_btDefaultMotionState_0=
|
||||
function(){return(gi=b._emscripten_bind_btDefaultMotionState_btDefaultMotionState_0=b.asm.jg).apply(null,arguments)},hi=b._emscripten_bind_btDefaultMotionState_btDefaultMotionState_1=function(){return(hi=b._emscripten_bind_btDefaultMotionState_btDefaultMotionState_1=b.asm.kg).apply(null,arguments)},ii=b._emscripten_bind_btDefaultMotionState_btDefaultMotionState_2=function(){return(ii=b._emscripten_bind_btDefaultMotionState_btDefaultMotionState_2=b.asm.lg).apply(null,arguments)},ji=b._emscripten_bind_btDefaultMotionState_getWorldTransform_1=
|
||||
function(){return(ji=b._emscripten_bind_btDefaultMotionState_getWorldTransform_1=b.asm.mg).apply(null,arguments)},ki=b._emscripten_bind_btDefaultMotionState_setWorldTransform_1=function(){return(ki=b._emscripten_bind_btDefaultMotionState_setWorldTransform_1=b.asm.ng).apply(null,arguments)},li=b._emscripten_bind_btDefaultMotionState_get_m_graphicsWorldTrans_0=function(){return(li=b._emscripten_bind_btDefaultMotionState_get_m_graphicsWorldTrans_0=b.asm.og).apply(null,arguments)},mi=b._emscripten_bind_btDefaultMotionState_set_m_graphicsWorldTrans_1=
|
||||
function(){return(mi=b._emscripten_bind_btDefaultMotionState_set_m_graphicsWorldTrans_1=b.asm.pg).apply(null,arguments)},ni=b._emscripten_bind_btDefaultMotionState___destroy___0=function(){return(ni=b._emscripten_bind_btDefaultMotionState___destroy___0=b.asm.qg).apply(null,arguments)},oi=b._emscripten_bind_btCollisionObjectWrapper_getWorldTransform_0=function(){return(oi=b._emscripten_bind_btCollisionObjectWrapper_getWorldTransform_0=b.asm.rg).apply(null,arguments)},pi=b._emscripten_bind_btCollisionObjectWrapper_getCollisionObject_0=
|
||||
function(){return(pi=b._emscripten_bind_btCollisionObjectWrapper_getCollisionObject_0=b.asm.sg).apply(null,arguments)},qi=b._emscripten_bind_btCollisionObjectWrapper_getCollisionShape_0=function(){return(qi=b._emscripten_bind_btCollisionObjectWrapper_getCollisionShape_0=b.asm.tg).apply(null,arguments)},ri=b._emscripten_bind_ClosestRayResultCallback_ClosestRayResultCallback_2=function(){return(ri=b._emscripten_bind_ClosestRayResultCallback_ClosestRayResultCallback_2=b.asm.ug).apply(null,arguments)},
|
||||
si=b._emscripten_bind_ClosestRayResultCallback_hasHit_0=function(){return(si=b._emscripten_bind_ClosestRayResultCallback_hasHit_0=b.asm.vg).apply(null,arguments)},ti=b._emscripten_bind_ClosestRayResultCallback_get_m_rayFromWorld_0=function(){return(ti=b._emscripten_bind_ClosestRayResultCallback_get_m_rayFromWorld_0=b.asm.wg).apply(null,arguments)},ui=b._emscripten_bind_ClosestRayResultCallback_set_m_rayFromWorld_1=function(){return(ui=b._emscripten_bind_ClosestRayResultCallback_set_m_rayFromWorld_1=
|
||||
b.asm.xg).apply(null,arguments)},vi=b._emscripten_bind_ClosestRayResultCallback_get_m_rayToWorld_0=function(){return(vi=b._emscripten_bind_ClosestRayResultCallback_get_m_rayToWorld_0=b.asm.yg).apply(null,arguments)},wi=b._emscripten_bind_ClosestRayResultCallback_set_m_rayToWorld_1=function(){return(wi=b._emscripten_bind_ClosestRayResultCallback_set_m_rayToWorld_1=b.asm.zg).apply(null,arguments)},xi=b._emscripten_bind_ClosestRayResultCallback_get_m_hitNormalWorld_0=function(){return(xi=b._emscripten_bind_ClosestRayResultCallback_get_m_hitNormalWorld_0=
|
||||
b.asm.Ag).apply(null,arguments)},yi=b._emscripten_bind_ClosestRayResultCallback_set_m_hitNormalWorld_1=function(){return(yi=b._emscripten_bind_ClosestRayResultCallback_set_m_hitNormalWorld_1=b.asm.Bg).apply(null,arguments)},zi=b._emscripten_bind_ClosestRayResultCallback_get_m_hitPointWorld_0=function(){return(zi=b._emscripten_bind_ClosestRayResultCallback_get_m_hitPointWorld_0=b.asm.Cg).apply(null,arguments)},Ai=b._emscripten_bind_ClosestRayResultCallback_set_m_hitPointWorld_1=function(){return(Ai=
|
||||
b._emscripten_bind_ClosestRayResultCallback_set_m_hitPointWorld_1=b.asm.Dg).apply(null,arguments)},Bi=b._emscripten_bind_ClosestRayResultCallback_get_m_collisionFilterGroup_0=function(){return(Bi=b._emscripten_bind_ClosestRayResultCallback_get_m_collisionFilterGroup_0=b.asm.Eg).apply(null,arguments)},Ci=b._emscripten_bind_ClosestRayResultCallback_set_m_collisionFilterGroup_1=function(){return(Ci=b._emscripten_bind_ClosestRayResultCallback_set_m_collisionFilterGroup_1=b.asm.Fg).apply(null,arguments)},
|
||||
Di=b._emscripten_bind_ClosestRayResultCallback_get_m_collisionFilterMask_0=function(){return(Di=b._emscripten_bind_ClosestRayResultCallback_get_m_collisionFilterMask_0=b.asm.Gg).apply(null,arguments)},Ei=b._emscripten_bind_ClosestRayResultCallback_set_m_collisionFilterMask_1=function(){return(Ei=b._emscripten_bind_ClosestRayResultCallback_set_m_collisionFilterMask_1=b.asm.Hg).apply(null,arguments)},Fi=b._emscripten_bind_ClosestRayResultCallback_get_m_closestHitFraction_0=function(){return(Fi=b._emscripten_bind_ClosestRayResultCallback_get_m_closestHitFraction_0=
|
||||
b.asm.Ig).apply(null,arguments)},Gi=b._emscripten_bind_ClosestRayResultCallback_set_m_closestHitFraction_1=function(){return(Gi=b._emscripten_bind_ClosestRayResultCallback_set_m_closestHitFraction_1=b.asm.Jg).apply(null,arguments)},Hi=b._emscripten_bind_ClosestRayResultCallback_get_m_collisionObject_0=function(){return(Hi=b._emscripten_bind_ClosestRayResultCallback_get_m_collisionObject_0=b.asm.Kg).apply(null,arguments)},Ii=b._emscripten_bind_ClosestRayResultCallback_set_m_collisionObject_1=function(){return(Ii=
|
||||
b._emscripten_bind_ClosestRayResultCallback_set_m_collisionObject_1=b.asm.Lg).apply(null,arguments)},Ji=b._emscripten_bind_ClosestRayResultCallback_get_m_flags_0=function(){return(Ji=b._emscripten_bind_ClosestRayResultCallback_get_m_flags_0=b.asm.Mg).apply(null,arguments)},Ki=b._emscripten_bind_ClosestRayResultCallback_set_m_flags_1=function(){return(Ki=b._emscripten_bind_ClosestRayResultCallback_set_m_flags_1=b.asm.Ng).apply(null,arguments)},Li=b._emscripten_bind_ClosestRayResultCallback___destroy___0=
|
||||
function(){return(Li=b._emscripten_bind_ClosestRayResultCallback___destroy___0=b.asm.Og).apply(null,arguments)},Mi=b._emscripten_bind_btConstCollisionObjectArray_size_0=function(){return(Mi=b._emscripten_bind_btConstCollisionObjectArray_size_0=b.asm.Pg).apply(null,arguments)},Ni=b._emscripten_bind_btConstCollisionObjectArray_at_1=function(){return(Ni=b._emscripten_bind_btConstCollisionObjectArray_at_1=b.asm.Qg).apply(null,arguments)},Oi=b._emscripten_bind_btConstCollisionObjectArray___destroy___0=
|
||||
function(){return(Oi=b._emscripten_bind_btConstCollisionObjectArray___destroy___0=b.asm.Rg).apply(null,arguments)},Pi=b._emscripten_bind_btScalarArray_size_0=function(){return(Pi=b._emscripten_bind_btScalarArray_size_0=b.asm.Sg).apply(null,arguments)},Qi=b._emscripten_bind_btScalarArray_at_1=function(){return(Qi=b._emscripten_bind_btScalarArray_at_1=b.asm.Tg).apply(null,arguments)},Ri=b._emscripten_bind_btScalarArray___destroy___0=function(){return(Ri=b._emscripten_bind_btScalarArray___destroy___0=
|
||||
b.asm.Ug).apply(null,arguments)},Si=b._emscripten_bind_AllHitsRayResultCallback_AllHitsRayResultCallback_2=function(){return(Si=b._emscripten_bind_AllHitsRayResultCallback_AllHitsRayResultCallback_2=b.asm.Vg).apply(null,arguments)},Ti=b._emscripten_bind_AllHitsRayResultCallback_hasHit_0=function(){return(Ti=b._emscripten_bind_AllHitsRayResultCallback_hasHit_0=b.asm.Wg).apply(null,arguments)},Ui=b._emscripten_bind_AllHitsRayResultCallback_get_m_collisionObjects_0=function(){return(Ui=b._emscripten_bind_AllHitsRayResultCallback_get_m_collisionObjects_0=
|
||||
b.asm.Xg).apply(null,arguments)},Vi=b._emscripten_bind_AllHitsRayResultCallback_set_m_collisionObjects_1=function(){return(Vi=b._emscripten_bind_AllHitsRayResultCallback_set_m_collisionObjects_1=b.asm.Yg).apply(null,arguments)},Wi=b._emscripten_bind_AllHitsRayResultCallback_get_m_rayFromWorld_0=function(){return(Wi=b._emscripten_bind_AllHitsRayResultCallback_get_m_rayFromWorld_0=b.asm.Zg).apply(null,arguments)},Xi=b._emscripten_bind_AllHitsRayResultCallback_set_m_rayFromWorld_1=function(){return(Xi=
|
||||
b._emscripten_bind_AllHitsRayResultCallback_set_m_rayFromWorld_1=b.asm._g).apply(null,arguments)},Yi=b._emscripten_bind_AllHitsRayResultCallback_get_m_rayToWorld_0=function(){return(Yi=b._emscripten_bind_AllHitsRayResultCallback_get_m_rayToWorld_0=b.asm.$g).apply(null,arguments)},Zi=b._emscripten_bind_AllHitsRayResultCallback_set_m_rayToWorld_1=function(){return(Zi=b._emscripten_bind_AllHitsRayResultCallback_set_m_rayToWorld_1=b.asm.ah).apply(null,arguments)},$i=b._emscripten_bind_AllHitsRayResultCallback_get_m_hitNormalWorld_0=
|
||||
function(){return($i=b._emscripten_bind_AllHitsRayResultCallback_get_m_hitNormalWorld_0=b.asm.bh).apply(null,arguments)},aj=b._emscripten_bind_AllHitsRayResultCallback_set_m_hitNormalWorld_1=function(){return(aj=b._emscripten_bind_AllHitsRayResultCallback_set_m_hitNormalWorld_1=b.asm.ch).apply(null,arguments)},bj=b._emscripten_bind_AllHitsRayResultCallback_get_m_hitPointWorld_0=function(){return(bj=b._emscripten_bind_AllHitsRayResultCallback_get_m_hitPointWorld_0=b.asm.dh).apply(null,arguments)},
|
||||
cj=b._emscripten_bind_AllHitsRayResultCallback_set_m_hitPointWorld_1=function(){return(cj=b._emscripten_bind_AllHitsRayResultCallback_set_m_hitPointWorld_1=b.asm.eh).apply(null,arguments)},dj=b._emscripten_bind_AllHitsRayResultCallback_get_m_hitFractions_0=function(){return(dj=b._emscripten_bind_AllHitsRayResultCallback_get_m_hitFractions_0=b.asm.fh).apply(null,arguments)},ej=b._emscripten_bind_AllHitsRayResultCallback_set_m_hitFractions_1=function(){return(ej=b._emscripten_bind_AllHitsRayResultCallback_set_m_hitFractions_1=
|
||||
b.asm.gh).apply(null,arguments)},fj=b._emscripten_bind_AllHitsRayResultCallback_get_m_collisionFilterGroup_0=function(){return(fj=b._emscripten_bind_AllHitsRayResultCallback_get_m_collisionFilterGroup_0=b.asm.hh).apply(null,arguments)},gj=b._emscripten_bind_AllHitsRayResultCallback_set_m_collisionFilterGroup_1=function(){return(gj=b._emscripten_bind_AllHitsRayResultCallback_set_m_collisionFilterGroup_1=b.asm.ih).apply(null,arguments)},hj=b._emscripten_bind_AllHitsRayResultCallback_get_m_collisionFilterMask_0=
|
||||
function(){return(hj=b._emscripten_bind_AllHitsRayResultCallback_get_m_collisionFilterMask_0=b.asm.jh).apply(null,arguments)},ij=b._emscripten_bind_AllHitsRayResultCallback_set_m_collisionFilterMask_1=function(){return(ij=b._emscripten_bind_AllHitsRayResultCallback_set_m_collisionFilterMask_1=b.asm.kh).apply(null,arguments)},jj=b._emscripten_bind_AllHitsRayResultCallback_get_m_closestHitFraction_0=function(){return(jj=b._emscripten_bind_AllHitsRayResultCallback_get_m_closestHitFraction_0=b.asm.lh).apply(null,
|
||||
arguments)},kj=b._emscripten_bind_AllHitsRayResultCallback_set_m_closestHitFraction_1=function(){return(kj=b._emscripten_bind_AllHitsRayResultCallback_set_m_closestHitFraction_1=b.asm.mh).apply(null,arguments)},lj=b._emscripten_bind_AllHitsRayResultCallback_get_m_collisionObject_0=function(){return(lj=b._emscripten_bind_AllHitsRayResultCallback_get_m_collisionObject_0=b.asm.nh).apply(null,arguments)},mj=b._emscripten_bind_AllHitsRayResultCallback_set_m_collisionObject_1=function(){return(mj=b._emscripten_bind_AllHitsRayResultCallback_set_m_collisionObject_1=
|
||||
b.asm.oh).apply(null,arguments)},nj=b._emscripten_bind_AllHitsRayResultCallback_get_m_flags_0=function(){return(nj=b._emscripten_bind_AllHitsRayResultCallback_get_m_flags_0=b.asm.ph).apply(null,arguments)},oj=b._emscripten_bind_AllHitsRayResultCallback_set_m_flags_1=function(){return(oj=b._emscripten_bind_AllHitsRayResultCallback_set_m_flags_1=b.asm.qh).apply(null,arguments)},pj=b._emscripten_bind_AllHitsRayResultCallback___destroy___0=function(){return(pj=b._emscripten_bind_AllHitsRayResultCallback___destroy___0=
|
||||
b.asm.rh).apply(null,arguments)},qj=b._emscripten_bind_btManifoldPoint_getPositionWorldOnA_0=function(){return(qj=b._emscripten_bind_btManifoldPoint_getPositionWorldOnA_0=b.asm.sh).apply(null,arguments)},rj=b._emscripten_bind_btManifoldPoint_getPositionWorldOnB_0=function(){return(rj=b._emscripten_bind_btManifoldPoint_getPositionWorldOnB_0=b.asm.th).apply(null,arguments)},sj=b._emscripten_bind_btManifoldPoint_getAppliedImpulse_0=function(){return(sj=b._emscripten_bind_btManifoldPoint_getAppliedImpulse_0=
|
||||
b.asm.uh).apply(null,arguments)},tj=b._emscripten_bind_btManifoldPoint_getDistance_0=function(){return(tj=b._emscripten_bind_btManifoldPoint_getDistance_0=b.asm.vh).apply(null,arguments)},uj=b._emscripten_bind_btManifoldPoint_get_m_localPointA_0=function(){return(uj=b._emscripten_bind_btManifoldPoint_get_m_localPointA_0=b.asm.wh).apply(null,arguments)},vj=b._emscripten_bind_btManifoldPoint_set_m_localPointA_1=function(){return(vj=b._emscripten_bind_btManifoldPoint_set_m_localPointA_1=b.asm.xh).apply(null,
|
||||
arguments)},wj=b._emscripten_bind_btManifoldPoint_get_m_localPointB_0=function(){return(wj=b._emscripten_bind_btManifoldPoint_get_m_localPointB_0=b.asm.yh).apply(null,arguments)},xj=b._emscripten_bind_btManifoldPoint_set_m_localPointB_1=function(){return(xj=b._emscripten_bind_btManifoldPoint_set_m_localPointB_1=b.asm.zh).apply(null,arguments)},yj=b._emscripten_bind_btManifoldPoint_get_m_positionWorldOnB_0=function(){return(yj=b._emscripten_bind_btManifoldPoint_get_m_positionWorldOnB_0=b.asm.Ah).apply(null,
|
||||
arguments)},zj=b._emscripten_bind_btManifoldPoint_set_m_positionWorldOnB_1=function(){return(zj=b._emscripten_bind_btManifoldPoint_set_m_positionWorldOnB_1=b.asm.Bh).apply(null,arguments)},Aj=b._emscripten_bind_btManifoldPoint_get_m_positionWorldOnA_0=function(){return(Aj=b._emscripten_bind_btManifoldPoint_get_m_positionWorldOnA_0=b.asm.Ch).apply(null,arguments)},Bj=b._emscripten_bind_btManifoldPoint_set_m_positionWorldOnA_1=function(){return(Bj=b._emscripten_bind_btManifoldPoint_set_m_positionWorldOnA_1=
|
||||
b.asm.Dh).apply(null,arguments)},Cj=b._emscripten_bind_btManifoldPoint_get_m_normalWorldOnB_0=function(){return(Cj=b._emscripten_bind_btManifoldPoint_get_m_normalWorldOnB_0=b.asm.Eh).apply(null,arguments)},Dj=b._emscripten_bind_btManifoldPoint_set_m_normalWorldOnB_1=function(){return(Dj=b._emscripten_bind_btManifoldPoint_set_m_normalWorldOnB_1=b.asm.Fh).apply(null,arguments)},Ej=b._emscripten_bind_btManifoldPoint_get_m_userPersistentData_0=function(){return(Ej=b._emscripten_bind_btManifoldPoint_get_m_userPersistentData_0=
|
||||
b.asm.Gh).apply(null,arguments)},Fj=b._emscripten_bind_btManifoldPoint_set_m_userPersistentData_1=function(){return(Fj=b._emscripten_bind_btManifoldPoint_set_m_userPersistentData_1=b.asm.Hh).apply(null,arguments)},Gj=b._emscripten_bind_btManifoldPoint___destroy___0=function(){return(Gj=b._emscripten_bind_btManifoldPoint___destroy___0=b.asm.Ih).apply(null,arguments)},Hj=b._emscripten_bind_ConcreteContactResultCallback_ConcreteContactResultCallback_0=function(){return(Hj=b._emscripten_bind_ConcreteContactResultCallback_ConcreteContactResultCallback_0=
|
||||
b.asm.Jh).apply(null,arguments)},Ij=b._emscripten_bind_ConcreteContactResultCallback_addSingleResult_7=function(){return(Ij=b._emscripten_bind_ConcreteContactResultCallback_addSingleResult_7=b.asm.Kh).apply(null,arguments)},Jj=b._emscripten_bind_ConcreteContactResultCallback___destroy___0=function(){return(Jj=b._emscripten_bind_ConcreteContactResultCallback___destroy___0=b.asm.Lh).apply(null,arguments)},Kj=b._emscripten_bind_LocalShapeInfo_get_m_shapePart_0=function(){return(Kj=b._emscripten_bind_LocalShapeInfo_get_m_shapePart_0=
|
||||
b.asm.Mh).apply(null,arguments)},Lj=b._emscripten_bind_LocalShapeInfo_set_m_shapePart_1=function(){return(Lj=b._emscripten_bind_LocalShapeInfo_set_m_shapePart_1=b.asm.Nh).apply(null,arguments)},Mj=b._emscripten_bind_LocalShapeInfo_get_m_triangleIndex_0=function(){return(Mj=b._emscripten_bind_LocalShapeInfo_get_m_triangleIndex_0=b.asm.Oh).apply(null,arguments)},Nj=b._emscripten_bind_LocalShapeInfo_set_m_triangleIndex_1=function(){return(Nj=b._emscripten_bind_LocalShapeInfo_set_m_triangleIndex_1=b.asm.Ph).apply(null,
|
||||
arguments)},Oj=b._emscripten_bind_LocalShapeInfo___destroy___0=function(){return(Oj=b._emscripten_bind_LocalShapeInfo___destroy___0=b.asm.Qh).apply(null,arguments)},Pj=b._emscripten_bind_LocalConvexResult_LocalConvexResult_5=function(){return(Pj=b._emscripten_bind_LocalConvexResult_LocalConvexResult_5=b.asm.Rh).apply(null,arguments)},Qj=b._emscripten_bind_LocalConvexResult_get_m_hitCollisionObject_0=function(){return(Qj=b._emscripten_bind_LocalConvexResult_get_m_hitCollisionObject_0=b.asm.Sh).apply(null,
|
||||
arguments)},Rj=b._emscripten_bind_LocalConvexResult_set_m_hitCollisionObject_1=function(){return(Rj=b._emscripten_bind_LocalConvexResult_set_m_hitCollisionObject_1=b.asm.Th).apply(null,arguments)},Sj=b._emscripten_bind_LocalConvexResult_get_m_localShapeInfo_0=function(){return(Sj=b._emscripten_bind_LocalConvexResult_get_m_localShapeInfo_0=b.asm.Uh).apply(null,arguments)},Tj=b._emscripten_bind_LocalConvexResult_set_m_localShapeInfo_1=function(){return(Tj=b._emscripten_bind_LocalConvexResult_set_m_localShapeInfo_1=
|
||||
b.asm.Vh).apply(null,arguments)},Uj=b._emscripten_bind_LocalConvexResult_get_m_hitNormalLocal_0=function(){return(Uj=b._emscripten_bind_LocalConvexResult_get_m_hitNormalLocal_0=b.asm.Wh).apply(null,arguments)},Vj=b._emscripten_bind_LocalConvexResult_set_m_hitNormalLocal_1=function(){return(Vj=b._emscripten_bind_LocalConvexResult_set_m_hitNormalLocal_1=b.asm.Xh).apply(null,arguments)},Wj=b._emscripten_bind_LocalConvexResult_get_m_hitPointLocal_0=function(){return(Wj=b._emscripten_bind_LocalConvexResult_get_m_hitPointLocal_0=
|
||||
b.asm.Yh).apply(null,arguments)},Xj=b._emscripten_bind_LocalConvexResult_set_m_hitPointLocal_1=function(){return(Xj=b._emscripten_bind_LocalConvexResult_set_m_hitPointLocal_1=b.asm.Zh).apply(null,arguments)},Yj=b._emscripten_bind_LocalConvexResult_get_m_hitFraction_0=function(){return(Yj=b._emscripten_bind_LocalConvexResult_get_m_hitFraction_0=b.asm._h).apply(null,arguments)},Zj=b._emscripten_bind_LocalConvexResult_set_m_hitFraction_1=function(){return(Zj=b._emscripten_bind_LocalConvexResult_set_m_hitFraction_1=
|
||||
b.asm.$h).apply(null,arguments)},ak=b._emscripten_bind_LocalConvexResult___destroy___0=function(){return(ak=b._emscripten_bind_LocalConvexResult___destroy___0=b.asm.ai).apply(null,arguments)},bk=b._emscripten_bind_ClosestConvexResultCallback_ClosestConvexResultCallback_2=function(){return(bk=b._emscripten_bind_ClosestConvexResultCallback_ClosestConvexResultCallback_2=b.asm.bi).apply(null,arguments)},ck=b._emscripten_bind_ClosestConvexResultCallback_hasHit_0=function(){return(ck=b._emscripten_bind_ClosestConvexResultCallback_hasHit_0=
|
||||
b.asm.ci).apply(null,arguments)},dk=b._emscripten_bind_ClosestConvexResultCallback_get_m_hitCollisionObject_0=function(){return(dk=b._emscripten_bind_ClosestConvexResultCallback_get_m_hitCollisionObject_0=b.asm.di).apply(null,arguments)},ek=b._emscripten_bind_ClosestConvexResultCallback_set_m_hitCollisionObject_1=function(){return(ek=b._emscripten_bind_ClosestConvexResultCallback_set_m_hitCollisionObject_1=b.asm.ei).apply(null,arguments)},fk=b._emscripten_bind_ClosestConvexResultCallback_get_m_convexFromWorld_0=
|
||||
function(){return(fk=b._emscripten_bind_ClosestConvexResultCallback_get_m_convexFromWorld_0=b.asm.fi).apply(null,arguments)},gk=b._emscripten_bind_ClosestConvexResultCallback_set_m_convexFromWorld_1=function(){return(gk=b._emscripten_bind_ClosestConvexResultCallback_set_m_convexFromWorld_1=b.asm.gi).apply(null,arguments)},hk=b._emscripten_bind_ClosestConvexResultCallback_get_m_convexToWorld_0=function(){return(hk=b._emscripten_bind_ClosestConvexResultCallback_get_m_convexToWorld_0=b.asm.hi).apply(null,
|
||||
arguments)},ik=b._emscripten_bind_ClosestConvexResultCallback_set_m_convexToWorld_1=function(){return(ik=b._emscripten_bind_ClosestConvexResultCallback_set_m_convexToWorld_1=b.asm.ii).apply(null,arguments)},jk=b._emscripten_bind_ClosestConvexResultCallback_get_m_hitNormalWorld_0=function(){return(jk=b._emscripten_bind_ClosestConvexResultCallback_get_m_hitNormalWorld_0=b.asm.ji).apply(null,arguments)},kk=b._emscripten_bind_ClosestConvexResultCallback_set_m_hitNormalWorld_1=function(){return(kk=b._emscripten_bind_ClosestConvexResultCallback_set_m_hitNormalWorld_1=
|
||||
b.asm.ki).apply(null,arguments)},lk=b._emscripten_bind_ClosestConvexResultCallback_get_m_hitPointWorld_0=function(){return(lk=b._emscripten_bind_ClosestConvexResultCallback_get_m_hitPointWorld_0=b.asm.li).apply(null,arguments)},mk=b._emscripten_bind_ClosestConvexResultCallback_set_m_hitPointWorld_1=function(){return(mk=b._emscripten_bind_ClosestConvexResultCallback_set_m_hitPointWorld_1=b.asm.mi).apply(null,arguments)},nk=b._emscripten_bind_ClosestConvexResultCallback_get_m_collisionFilterGroup_0=
|
||||
function(){return(nk=b._emscripten_bind_ClosestConvexResultCallback_get_m_collisionFilterGroup_0=b.asm.ni).apply(null,arguments)},ok=b._emscripten_bind_ClosestConvexResultCallback_set_m_collisionFilterGroup_1=function(){return(ok=b._emscripten_bind_ClosestConvexResultCallback_set_m_collisionFilterGroup_1=b.asm.oi).apply(null,arguments)},pk=b._emscripten_bind_ClosestConvexResultCallback_get_m_collisionFilterMask_0=function(){return(pk=b._emscripten_bind_ClosestConvexResultCallback_get_m_collisionFilterMask_0=
|
||||
b.asm.pi).apply(null,arguments)},qk=b._emscripten_bind_ClosestConvexResultCallback_set_m_collisionFilterMask_1=function(){return(qk=b._emscripten_bind_ClosestConvexResultCallback_set_m_collisionFilterMask_1=b.asm.qi).apply(null,arguments)},rk=b._emscripten_bind_ClosestConvexResultCallback_get_m_closestHitFraction_0=function(){return(rk=b._emscripten_bind_ClosestConvexResultCallback_get_m_closestHitFraction_0=b.asm.ri).apply(null,arguments)},sk=b._emscripten_bind_ClosestConvexResultCallback_set_m_closestHitFraction_1=
|
||||
function(){return(sk=b._emscripten_bind_ClosestConvexResultCallback_set_m_closestHitFraction_1=b.asm.si).apply(null,arguments)},tk=b._emscripten_bind_ClosestConvexResultCallback___destroy___0=function(){return(tk=b._emscripten_bind_ClosestConvexResultCallback___destroy___0=b.asm.ti).apply(null,arguments)},uk=b._emscripten_bind_btConvexTriangleMeshShape_btConvexTriangleMeshShape_1=function(){return(uk=b._emscripten_bind_btConvexTriangleMeshShape_btConvexTriangleMeshShape_1=b.asm.ui).apply(null,arguments)},
|
||||
vk=b._emscripten_bind_btConvexTriangleMeshShape_btConvexTriangleMeshShape_2=function(){return(vk=b._emscripten_bind_btConvexTriangleMeshShape_btConvexTriangleMeshShape_2=b.asm.vi).apply(null,arguments)},wk=b._emscripten_bind_btConvexTriangleMeshShape_setLocalScaling_1=function(){return(wk=b._emscripten_bind_btConvexTriangleMeshShape_setLocalScaling_1=b.asm.wi).apply(null,arguments)},xk=b._emscripten_bind_btConvexTriangleMeshShape_getLocalScaling_0=function(){return(xk=b._emscripten_bind_btConvexTriangleMeshShape_getLocalScaling_0=
|
||||
b.asm.xi).apply(null,arguments)},yk=b._emscripten_bind_btConvexTriangleMeshShape_calculateLocalInertia_2=function(){return(yk=b._emscripten_bind_btConvexTriangleMeshShape_calculateLocalInertia_2=b.asm.yi).apply(null,arguments)},zk=b._emscripten_bind_btConvexTriangleMeshShape_setMargin_1=function(){return(zk=b._emscripten_bind_btConvexTriangleMeshShape_setMargin_1=b.asm.zi).apply(null,arguments)},Ak=b._emscripten_bind_btConvexTriangleMeshShape_getMargin_0=function(){return(Ak=b._emscripten_bind_btConvexTriangleMeshShape_getMargin_0=
|
||||
b.asm.Ai).apply(null,arguments)},Bk=b._emscripten_bind_btConvexTriangleMeshShape___destroy___0=function(){return(Bk=b._emscripten_bind_btConvexTriangleMeshShape___destroy___0=b.asm.Bi).apply(null,arguments)},Ck=b._emscripten_bind_btBoxShape_btBoxShape_1=function(){return(Ck=b._emscripten_bind_btBoxShape_btBoxShape_1=b.asm.Ci).apply(null,arguments)},Dk=b._emscripten_bind_btBoxShape_setMargin_1=function(){return(Dk=b._emscripten_bind_btBoxShape_setMargin_1=b.asm.Di).apply(null,arguments)},Ek=b._emscripten_bind_btBoxShape_getMargin_0=
|
||||
function(){return(Ek=b._emscripten_bind_btBoxShape_getMargin_0=b.asm.Ei).apply(null,arguments)},Fk=b._emscripten_bind_btBoxShape_setLocalScaling_1=function(){return(Fk=b._emscripten_bind_btBoxShape_setLocalScaling_1=b.asm.Fi).apply(null,arguments)},Gk=b._emscripten_bind_btBoxShape_getLocalScaling_0=function(){return(Gk=b._emscripten_bind_btBoxShape_getLocalScaling_0=b.asm.Gi).apply(null,arguments)},Hk=b._emscripten_bind_btBoxShape_calculateLocalInertia_2=function(){return(Hk=b._emscripten_bind_btBoxShape_calculateLocalInertia_2=
|
||||
b.asm.Hi).apply(null,arguments)},Ik=b._emscripten_bind_btBoxShape___destroy___0=function(){return(Ik=b._emscripten_bind_btBoxShape___destroy___0=b.asm.Ii).apply(null,arguments)},Jk=b._emscripten_bind_btCapsuleShapeX_btCapsuleShapeX_2=function(){return(Jk=b._emscripten_bind_btCapsuleShapeX_btCapsuleShapeX_2=b.asm.Ji).apply(null,arguments)},Kk=b._emscripten_bind_btCapsuleShapeX_setMargin_1=function(){return(Kk=b._emscripten_bind_btCapsuleShapeX_setMargin_1=b.asm.Ki).apply(null,arguments)},Lk=b._emscripten_bind_btCapsuleShapeX_getMargin_0=
|
||||
function(){return(Lk=b._emscripten_bind_btCapsuleShapeX_getMargin_0=b.asm.Li).apply(null,arguments)},Mk=b._emscripten_bind_btCapsuleShapeX_getUpAxis_0=function(){return(Mk=b._emscripten_bind_btCapsuleShapeX_getUpAxis_0=b.asm.Mi).apply(null,arguments)},Nk=b._emscripten_bind_btCapsuleShapeX_getRadius_0=function(){return(Nk=b._emscripten_bind_btCapsuleShapeX_getRadius_0=b.asm.Ni).apply(null,arguments)},Ok=b._emscripten_bind_btCapsuleShapeX_getHalfHeight_0=function(){return(Ok=b._emscripten_bind_btCapsuleShapeX_getHalfHeight_0=
|
||||
b.asm.Oi).apply(null,arguments)},Pk=b._emscripten_bind_btCapsuleShapeX_setLocalScaling_1=function(){return(Pk=b._emscripten_bind_btCapsuleShapeX_setLocalScaling_1=b.asm.Pi).apply(null,arguments)},Qk=b._emscripten_bind_btCapsuleShapeX_getLocalScaling_0=function(){return(Qk=b._emscripten_bind_btCapsuleShapeX_getLocalScaling_0=b.asm.Qi).apply(null,arguments)},Rk=b._emscripten_bind_btCapsuleShapeX_calculateLocalInertia_2=function(){return(Rk=b._emscripten_bind_btCapsuleShapeX_calculateLocalInertia_2=
|
||||
b.asm.Ri).apply(null,arguments)},Sk=b._emscripten_bind_btCapsuleShapeX___destroy___0=function(){return(Sk=b._emscripten_bind_btCapsuleShapeX___destroy___0=b.asm.Si).apply(null,arguments)},Tk=b._emscripten_bind_btCapsuleShapeZ_btCapsuleShapeZ_2=function(){return(Tk=b._emscripten_bind_btCapsuleShapeZ_btCapsuleShapeZ_2=b.asm.Ti).apply(null,arguments)},Uk=b._emscripten_bind_btCapsuleShapeZ_setMargin_1=function(){return(Uk=b._emscripten_bind_btCapsuleShapeZ_setMargin_1=b.asm.Ui).apply(null,arguments)},
|
||||
Vk=b._emscripten_bind_btCapsuleShapeZ_getMargin_0=function(){return(Vk=b._emscripten_bind_btCapsuleShapeZ_getMargin_0=b.asm.Vi).apply(null,arguments)},Wk=b._emscripten_bind_btCapsuleShapeZ_getUpAxis_0=function(){return(Wk=b._emscripten_bind_btCapsuleShapeZ_getUpAxis_0=b.asm.Wi).apply(null,arguments)},Xk=b._emscripten_bind_btCapsuleShapeZ_getRadius_0=function(){return(Xk=b._emscripten_bind_btCapsuleShapeZ_getRadius_0=b.asm.Xi).apply(null,arguments)},Yk=b._emscripten_bind_btCapsuleShapeZ_getHalfHeight_0=
|
||||
function(){return(Yk=b._emscripten_bind_btCapsuleShapeZ_getHalfHeight_0=b.asm.Yi).apply(null,arguments)},Zk=b._emscripten_bind_btCapsuleShapeZ_setLocalScaling_1=function(){return(Zk=b._emscripten_bind_btCapsuleShapeZ_setLocalScaling_1=b.asm.Zi).apply(null,arguments)},$k=b._emscripten_bind_btCapsuleShapeZ_getLocalScaling_0=function(){return($k=b._emscripten_bind_btCapsuleShapeZ_getLocalScaling_0=b.asm._i).apply(null,arguments)},al=b._emscripten_bind_btCapsuleShapeZ_calculateLocalInertia_2=function(){return(al=
|
||||
b._emscripten_bind_btCapsuleShapeZ_calculateLocalInertia_2=b.asm.$i).apply(null,arguments)},bl=b._emscripten_bind_btCapsuleShapeZ___destroy___0=function(){return(bl=b._emscripten_bind_btCapsuleShapeZ___destroy___0=b.asm.aj).apply(null,arguments)},cl=b._emscripten_bind_btCylinderShapeX_btCylinderShapeX_1=function(){return(cl=b._emscripten_bind_btCylinderShapeX_btCylinderShapeX_1=b.asm.bj).apply(null,arguments)},dl=b._emscripten_bind_btCylinderShapeX_setMargin_1=function(){return(dl=b._emscripten_bind_btCylinderShapeX_setMargin_1=
|
||||
b.asm.cj).apply(null,arguments)},el=b._emscripten_bind_btCylinderShapeX_getMargin_0=function(){return(el=b._emscripten_bind_btCylinderShapeX_getMargin_0=b.asm.dj).apply(null,arguments)},fl=b._emscripten_bind_btCylinderShapeX_setLocalScaling_1=function(){return(fl=b._emscripten_bind_btCylinderShapeX_setLocalScaling_1=b.asm.ej).apply(null,arguments)},gl=b._emscripten_bind_btCylinderShapeX_getLocalScaling_0=function(){return(gl=b._emscripten_bind_btCylinderShapeX_getLocalScaling_0=b.asm.fj).apply(null,
|
||||
arguments)},hl=b._emscripten_bind_btCylinderShapeX_calculateLocalInertia_2=function(){return(hl=b._emscripten_bind_btCylinderShapeX_calculateLocalInertia_2=b.asm.gj).apply(null,arguments)},il=b._emscripten_bind_btCylinderShapeX___destroy___0=function(){return(il=b._emscripten_bind_btCylinderShapeX___destroy___0=b.asm.hj).apply(null,arguments)},jl=b._emscripten_bind_btCylinderShapeZ_btCylinderShapeZ_1=function(){return(jl=b._emscripten_bind_btCylinderShapeZ_btCylinderShapeZ_1=b.asm.ij).apply(null,
|
||||
arguments)},kl=b._emscripten_bind_btCylinderShapeZ_setMargin_1=function(){return(kl=b._emscripten_bind_btCylinderShapeZ_setMargin_1=b.asm.jj).apply(null,arguments)},ll=b._emscripten_bind_btCylinderShapeZ_getMargin_0=function(){return(ll=b._emscripten_bind_btCylinderShapeZ_getMargin_0=b.asm.kj).apply(null,arguments)},ml=b._emscripten_bind_btCylinderShapeZ_setLocalScaling_1=function(){return(ml=b._emscripten_bind_btCylinderShapeZ_setLocalScaling_1=b.asm.lj).apply(null,arguments)},nl=b._emscripten_bind_btCylinderShapeZ_getLocalScaling_0=
|
||||
function(){return(nl=b._emscripten_bind_btCylinderShapeZ_getLocalScaling_0=b.asm.mj).apply(null,arguments)},ol=b._emscripten_bind_btCylinderShapeZ_calculateLocalInertia_2=function(){return(ol=b._emscripten_bind_btCylinderShapeZ_calculateLocalInertia_2=b.asm.nj).apply(null,arguments)},pl=b._emscripten_bind_btCylinderShapeZ___destroy___0=function(){return(pl=b._emscripten_bind_btCylinderShapeZ___destroy___0=b.asm.oj).apply(null,arguments)},ql=b._emscripten_bind_btSphereShape_btSphereShape_1=function(){return(ql=
|
||||
b._emscripten_bind_btSphereShape_btSphereShape_1=b.asm.pj).apply(null,arguments)},rl=b._emscripten_bind_btSphereShape_setMargin_1=function(){return(rl=b._emscripten_bind_btSphereShape_setMargin_1=b.asm.qj).apply(null,arguments)},sl=b._emscripten_bind_btSphereShape_getMargin_0=function(){return(sl=b._emscripten_bind_btSphereShape_getMargin_0=b.asm.rj).apply(null,arguments)},tl=b._emscripten_bind_btSphereShape_setLocalScaling_1=function(){return(tl=b._emscripten_bind_btSphereShape_setLocalScaling_1=
|
||||
b.asm.sj).apply(null,arguments)},ul=b._emscripten_bind_btSphereShape_getLocalScaling_0=function(){return(ul=b._emscripten_bind_btSphereShape_getLocalScaling_0=b.asm.tj).apply(null,arguments)},vl=b._emscripten_bind_btSphereShape_calculateLocalInertia_2=function(){return(vl=b._emscripten_bind_btSphereShape_calculateLocalInertia_2=b.asm.uj).apply(null,arguments)},wl=b._emscripten_bind_btSphereShape___destroy___0=function(){return(wl=b._emscripten_bind_btSphereShape___destroy___0=b.asm.vj).apply(null,
|
||||
arguments)},xl=b._emscripten_bind_btMultiSphereShape_btMultiSphereShape_3=function(){return(xl=b._emscripten_bind_btMultiSphereShape_btMultiSphereShape_3=b.asm.wj).apply(null,arguments)},yl=b._emscripten_bind_btMultiSphereShape_setLocalScaling_1=function(){return(yl=b._emscripten_bind_btMultiSphereShape_setLocalScaling_1=b.asm.xj).apply(null,arguments)},zl=b._emscripten_bind_btMultiSphereShape_getLocalScaling_0=function(){return(zl=b._emscripten_bind_btMultiSphereShape_getLocalScaling_0=b.asm.yj).apply(null,
|
||||
arguments)},Al=b._emscripten_bind_btMultiSphereShape_calculateLocalInertia_2=function(){return(Al=b._emscripten_bind_btMultiSphereShape_calculateLocalInertia_2=b.asm.zj).apply(null,arguments)},Bl=b._emscripten_bind_btMultiSphereShape___destroy___0=function(){return(Bl=b._emscripten_bind_btMultiSphereShape___destroy___0=b.asm.Aj).apply(null,arguments)},Cl=b._emscripten_bind_btConeShapeX_btConeShapeX_2=function(){return(Cl=b._emscripten_bind_btConeShapeX_btConeShapeX_2=b.asm.Bj).apply(null,arguments)},
|
||||
Dl=b._emscripten_bind_btConeShapeX_setLocalScaling_1=function(){return(Dl=b._emscripten_bind_btConeShapeX_setLocalScaling_1=b.asm.Cj).apply(null,arguments)},El=b._emscripten_bind_btConeShapeX_getLocalScaling_0=function(){return(El=b._emscripten_bind_btConeShapeX_getLocalScaling_0=b.asm.Dj).apply(null,arguments)},Fl=b._emscripten_bind_btConeShapeX_calculateLocalInertia_2=function(){return(Fl=b._emscripten_bind_btConeShapeX_calculateLocalInertia_2=b.asm.Ej).apply(null,arguments)},Gl=b._emscripten_bind_btConeShapeX___destroy___0=
|
||||
function(){return(Gl=b._emscripten_bind_btConeShapeX___destroy___0=b.asm.Fj).apply(null,arguments)},Hl=b._emscripten_bind_btConeShapeZ_btConeShapeZ_2=function(){return(Hl=b._emscripten_bind_btConeShapeZ_btConeShapeZ_2=b.asm.Gj).apply(null,arguments)},Il=b._emscripten_bind_btConeShapeZ_setLocalScaling_1=function(){return(Il=b._emscripten_bind_btConeShapeZ_setLocalScaling_1=b.asm.Hj).apply(null,arguments)},Jl=b._emscripten_bind_btConeShapeZ_getLocalScaling_0=function(){return(Jl=b._emscripten_bind_btConeShapeZ_getLocalScaling_0=
|
||||
b.asm.Ij).apply(null,arguments)},Kl=b._emscripten_bind_btConeShapeZ_calculateLocalInertia_2=function(){return(Kl=b._emscripten_bind_btConeShapeZ_calculateLocalInertia_2=b.asm.Jj).apply(null,arguments)},Ll=b._emscripten_bind_btConeShapeZ___destroy___0=function(){return(Ll=b._emscripten_bind_btConeShapeZ___destroy___0=b.asm.Kj).apply(null,arguments)},Ml=b._emscripten_bind_btIntArray_size_0=function(){return(Ml=b._emscripten_bind_btIntArray_size_0=b.asm.Lj).apply(null,arguments)},Nl=b._emscripten_bind_btIntArray_at_1=
|
||||
function(){return(Nl=b._emscripten_bind_btIntArray_at_1=b.asm.Mj).apply(null,arguments)},Ol=b._emscripten_bind_btIntArray___destroy___0=function(){return(Ol=b._emscripten_bind_btIntArray___destroy___0=b.asm.Nj).apply(null,arguments)},Pl=b._emscripten_bind_btFace_get_m_indices_0=function(){return(Pl=b._emscripten_bind_btFace_get_m_indices_0=b.asm.Oj).apply(null,arguments)},Ql=b._emscripten_bind_btFace_set_m_indices_1=function(){return(Ql=b._emscripten_bind_btFace_set_m_indices_1=b.asm.Pj).apply(null,
|
||||
arguments)},Rl=b._emscripten_bind_btFace_get_m_plane_1=function(){return(Rl=b._emscripten_bind_btFace_get_m_plane_1=b.asm.Qj).apply(null,arguments)},Sl=b._emscripten_bind_btFace_set_m_plane_2=function(){return(Sl=b._emscripten_bind_btFace_set_m_plane_2=b.asm.Rj).apply(null,arguments)},Tl=b._emscripten_bind_btFace___destroy___0=function(){return(Tl=b._emscripten_bind_btFace___destroy___0=b.asm.Sj).apply(null,arguments)},Ul=b._emscripten_bind_btVector3Array_size_0=function(){return(Ul=b._emscripten_bind_btVector3Array_size_0=
|
||||
b.asm.Tj).apply(null,arguments)},Vl=b._emscripten_bind_btVector3Array_at_1=function(){return(Vl=b._emscripten_bind_btVector3Array_at_1=b.asm.Uj).apply(null,arguments)},Wl=b._emscripten_bind_btVector3Array___destroy___0=function(){return(Wl=b._emscripten_bind_btVector3Array___destroy___0=b.asm.Vj).apply(null,arguments)},Xl=b._emscripten_bind_btFaceArray_size_0=function(){return(Xl=b._emscripten_bind_btFaceArray_size_0=b.asm.Wj).apply(null,arguments)},Yl=b._emscripten_bind_btFaceArray_at_1=function(){return(Yl=
|
||||
b._emscripten_bind_btFaceArray_at_1=b.asm.Xj).apply(null,arguments)},Zl=b._emscripten_bind_btFaceArray___destroy___0=function(){return(Zl=b._emscripten_bind_btFaceArray___destroy___0=b.asm.Yj).apply(null,arguments)},$l=b._emscripten_bind_btConvexPolyhedron_get_m_vertices_0=function(){return($l=b._emscripten_bind_btConvexPolyhedron_get_m_vertices_0=b.asm.Zj).apply(null,arguments)},am=b._emscripten_bind_btConvexPolyhedron_set_m_vertices_1=function(){return(am=b._emscripten_bind_btConvexPolyhedron_set_m_vertices_1=
|
||||
b.asm._j).apply(null,arguments)},bm=b._emscripten_bind_btConvexPolyhedron_get_m_faces_0=function(){return(bm=b._emscripten_bind_btConvexPolyhedron_get_m_faces_0=b.asm.$j).apply(null,arguments)},cm=b._emscripten_bind_btConvexPolyhedron_set_m_faces_1=function(){return(cm=b._emscripten_bind_btConvexPolyhedron_set_m_faces_1=b.asm.ak).apply(null,arguments)},dm=b._emscripten_bind_btConvexPolyhedron___destroy___0=function(){return(dm=b._emscripten_bind_btConvexPolyhedron___destroy___0=b.asm.bk).apply(null,
|
||||
arguments)},em=b._emscripten_bind_btConvexHullShape_btConvexHullShape_0=function(){return(em=b._emscripten_bind_btConvexHullShape_btConvexHullShape_0=b.asm.ck).apply(null,arguments)},fm=b._emscripten_bind_btConvexHullShape_btConvexHullShape_1=function(){return(fm=b._emscripten_bind_btConvexHullShape_btConvexHullShape_1=b.asm.dk).apply(null,arguments)},gm=b._emscripten_bind_btConvexHullShape_btConvexHullShape_2=function(){return(gm=b._emscripten_bind_btConvexHullShape_btConvexHullShape_2=b.asm.ek).apply(null,
|
||||
arguments)},hm=b._emscripten_bind_btConvexHullShape_addPoint_1=function(){return(hm=b._emscripten_bind_btConvexHullShape_addPoint_1=b.asm.fk).apply(null,arguments)},im=b._emscripten_bind_btConvexHullShape_addPoint_2=function(){return(im=b._emscripten_bind_btConvexHullShape_addPoint_2=b.asm.gk).apply(null,arguments)},jm=b._emscripten_bind_btConvexHullShape_setMargin_1=function(){return(jm=b._emscripten_bind_btConvexHullShape_setMargin_1=b.asm.hk).apply(null,arguments)},km=b._emscripten_bind_btConvexHullShape_getMargin_0=
|
||||
function(){return(km=b._emscripten_bind_btConvexHullShape_getMargin_0=b.asm.ik).apply(null,arguments)},lm=b._emscripten_bind_btConvexHullShape_getNumVertices_0=function(){return(lm=b._emscripten_bind_btConvexHullShape_getNumVertices_0=b.asm.jk).apply(null,arguments)},mm=b._emscripten_bind_btConvexHullShape_initializePolyhedralFeatures_1=function(){return(mm=b._emscripten_bind_btConvexHullShape_initializePolyhedralFeatures_1=b.asm.kk).apply(null,arguments)},nm=b._emscripten_bind_btConvexHullShape_recalcLocalAabb_0=
|
||||
function(){return(nm=b._emscripten_bind_btConvexHullShape_recalcLocalAabb_0=b.asm.lk).apply(null,arguments)},om=b._emscripten_bind_btConvexHullShape_getConvexPolyhedron_0=function(){return(om=b._emscripten_bind_btConvexHullShape_getConvexPolyhedron_0=b.asm.mk).apply(null,arguments)},pm=b._emscripten_bind_btConvexHullShape_setLocalScaling_1=function(){return(pm=b._emscripten_bind_btConvexHullShape_setLocalScaling_1=b.asm.nk).apply(null,arguments)},qm=b._emscripten_bind_btConvexHullShape_getLocalScaling_0=
|
||||
function(){return(qm=b._emscripten_bind_btConvexHullShape_getLocalScaling_0=b.asm.ok).apply(null,arguments)},rm=b._emscripten_bind_btConvexHullShape_calculateLocalInertia_2=function(){return(rm=b._emscripten_bind_btConvexHullShape_calculateLocalInertia_2=b.asm.pk).apply(null,arguments)},sm=b._emscripten_bind_btConvexHullShape___destroy___0=function(){return(sm=b._emscripten_bind_btConvexHullShape___destroy___0=b.asm.qk).apply(null,arguments)},tm=b._emscripten_bind_btShapeHull_btShapeHull_1=function(){return(tm=
|
||||
b._emscripten_bind_btShapeHull_btShapeHull_1=b.asm.rk).apply(null,arguments)},um=b._emscripten_bind_btShapeHull_buildHull_1=function(){return(um=b._emscripten_bind_btShapeHull_buildHull_1=b.asm.sk).apply(null,arguments)},wm=b._emscripten_bind_btShapeHull_numVertices_0=function(){return(wm=b._emscripten_bind_btShapeHull_numVertices_0=b.asm.tk).apply(null,arguments)},xm=b._emscripten_bind_btShapeHull_getVertexPointer_0=function(){return(xm=b._emscripten_bind_btShapeHull_getVertexPointer_0=b.asm.uk).apply(null,
|
||||
arguments)},ym=b._emscripten_bind_btShapeHull___destroy___0=function(){return(ym=b._emscripten_bind_btShapeHull___destroy___0=b.asm.vk).apply(null,arguments)},zm=b._emscripten_bind_btCompoundShape_btCompoundShape_0=function(){return(zm=b._emscripten_bind_btCompoundShape_btCompoundShape_0=b.asm.wk).apply(null,arguments)},Am=b._emscripten_bind_btCompoundShape_btCompoundShape_1=function(){return(Am=b._emscripten_bind_btCompoundShape_btCompoundShape_1=b.asm.xk).apply(null,arguments)},Bm=b._emscripten_bind_btCompoundShape_addChildShape_2=
|
||||
function(){return(Bm=b._emscripten_bind_btCompoundShape_addChildShape_2=b.asm.yk).apply(null,arguments)},Cm=b._emscripten_bind_btCompoundShape_removeChildShape_1=function(){return(Cm=b._emscripten_bind_btCompoundShape_removeChildShape_1=b.asm.zk).apply(null,arguments)},Dm=b._emscripten_bind_btCompoundShape_removeChildShapeByIndex_1=function(){return(Dm=b._emscripten_bind_btCompoundShape_removeChildShapeByIndex_1=b.asm.Ak).apply(null,arguments)},Em=b._emscripten_bind_btCompoundShape_getNumChildShapes_0=
|
||||
function(){return(Em=b._emscripten_bind_btCompoundShape_getNumChildShapes_0=b.asm.Bk).apply(null,arguments)},Fm=b._emscripten_bind_btCompoundShape_getChildShape_1=function(){return(Fm=b._emscripten_bind_btCompoundShape_getChildShape_1=b.asm.Ck).apply(null,arguments)},Gm=b._emscripten_bind_btCompoundShape_updateChildTransform_2=function(){return(Gm=b._emscripten_bind_btCompoundShape_updateChildTransform_2=b.asm.Dk).apply(null,arguments)},Hm=b._emscripten_bind_btCompoundShape_updateChildTransform_3=
|
||||
function(){return(Hm=b._emscripten_bind_btCompoundShape_updateChildTransform_3=b.asm.Ek).apply(null,arguments)},Im=b._emscripten_bind_btCompoundShape_setMargin_1=function(){return(Im=b._emscripten_bind_btCompoundShape_setMargin_1=b.asm.Fk).apply(null,arguments)},Jm=b._emscripten_bind_btCompoundShape_getMargin_0=function(){return(Jm=b._emscripten_bind_btCompoundShape_getMargin_0=b.asm.Gk).apply(null,arguments)},Km=b._emscripten_bind_btCompoundShape_setLocalScaling_1=function(){return(Km=b._emscripten_bind_btCompoundShape_setLocalScaling_1=
|
||||
b.asm.Hk).apply(null,arguments)},Lm=b._emscripten_bind_btCompoundShape_getLocalScaling_0=function(){return(Lm=b._emscripten_bind_btCompoundShape_getLocalScaling_0=b.asm.Ik).apply(null,arguments)},Mm=b._emscripten_bind_btCompoundShape_calculateLocalInertia_2=function(){return(Mm=b._emscripten_bind_btCompoundShape_calculateLocalInertia_2=b.asm.Jk).apply(null,arguments)},Nm=b._emscripten_bind_btCompoundShape___destroy___0=function(){return(Nm=b._emscripten_bind_btCompoundShape___destroy___0=b.asm.Kk).apply(null,
|
||||
arguments)},Om=b._emscripten_bind_btIndexedMesh_get_m_numTriangles_0=function(){return(Om=b._emscripten_bind_btIndexedMesh_get_m_numTriangles_0=b.asm.Lk).apply(null,arguments)},Pm=b._emscripten_bind_btIndexedMesh_set_m_numTriangles_1=function(){return(Pm=b._emscripten_bind_btIndexedMesh_set_m_numTriangles_1=b.asm.Mk).apply(null,arguments)},Qm=b._emscripten_bind_btIndexedMesh___destroy___0=function(){return(Qm=b._emscripten_bind_btIndexedMesh___destroy___0=b.asm.Nk).apply(null,arguments)},Rm=b._emscripten_bind_btIndexedMeshArray_size_0=
|
||||
function(){return(Rm=b._emscripten_bind_btIndexedMeshArray_size_0=b.asm.Ok).apply(null,arguments)},Sm=b._emscripten_bind_btIndexedMeshArray_at_1=function(){return(Sm=b._emscripten_bind_btIndexedMeshArray_at_1=b.asm.Pk).apply(null,arguments)},Tm=b._emscripten_bind_btIndexedMeshArray___destroy___0=function(){return(Tm=b._emscripten_bind_btIndexedMeshArray___destroy___0=b.asm.Qk).apply(null,arguments)},Um=b._emscripten_bind_btTriangleMesh_btTriangleMesh_0=function(){return(Um=b._emscripten_bind_btTriangleMesh_btTriangleMesh_0=
|
||||
b.asm.Rk).apply(null,arguments)},Vm=b._emscripten_bind_btTriangleMesh_btTriangleMesh_1=function(){return(Vm=b._emscripten_bind_btTriangleMesh_btTriangleMesh_1=b.asm.Sk).apply(null,arguments)},Wm=b._emscripten_bind_btTriangleMesh_btTriangleMesh_2=function(){return(Wm=b._emscripten_bind_btTriangleMesh_btTriangleMesh_2=b.asm.Tk).apply(null,arguments)},Xm=b._emscripten_bind_btTriangleMesh_addTriangle_3=function(){return(Xm=b._emscripten_bind_btTriangleMesh_addTriangle_3=b.asm.Uk).apply(null,arguments)},
|
||||
Ym=b._emscripten_bind_btTriangleMesh_addTriangle_4=function(){return(Ym=b._emscripten_bind_btTriangleMesh_addTriangle_4=b.asm.Vk).apply(null,arguments)},Zm=b._emscripten_bind_btTriangleMesh_findOrAddVertex_2=function(){return(Zm=b._emscripten_bind_btTriangleMesh_findOrAddVertex_2=b.asm.Wk).apply(null,arguments)},$m=b._emscripten_bind_btTriangleMesh_addIndex_1=function(){return($m=b._emscripten_bind_btTriangleMesh_addIndex_1=b.asm.Xk).apply(null,arguments)},an=b._emscripten_bind_btTriangleMesh_getIndexedMeshArray_0=
|
||||
function(){return(an=b._emscripten_bind_btTriangleMesh_getIndexedMeshArray_0=b.asm.Yk).apply(null,arguments)},bn=b._emscripten_bind_btTriangleMesh_setScaling_1=function(){return(bn=b._emscripten_bind_btTriangleMesh_setScaling_1=b.asm.Zk).apply(null,arguments)},cn=b._emscripten_bind_btTriangleMesh___destroy___0=function(){return(cn=b._emscripten_bind_btTriangleMesh___destroy___0=b.asm._k).apply(null,arguments)},dn=b._emscripten_bind_btEmptyShape_btEmptyShape_0=function(){return(dn=b._emscripten_bind_btEmptyShape_btEmptyShape_0=
|
||||
b.asm.$k).apply(null,arguments)},en=b._emscripten_bind_btEmptyShape_setLocalScaling_1=function(){return(en=b._emscripten_bind_btEmptyShape_setLocalScaling_1=b.asm.al).apply(null,arguments)},fn=b._emscripten_bind_btEmptyShape_getLocalScaling_0=function(){return(fn=b._emscripten_bind_btEmptyShape_getLocalScaling_0=b.asm.bl).apply(null,arguments)},gn=b._emscripten_bind_btEmptyShape_calculateLocalInertia_2=function(){return(gn=b._emscripten_bind_btEmptyShape_calculateLocalInertia_2=b.asm.cl).apply(null,
|
||||
arguments)},hn=b._emscripten_bind_btEmptyShape___destroy___0=function(){return(hn=b._emscripten_bind_btEmptyShape___destroy___0=b.asm.dl).apply(null,arguments)},jn=b._emscripten_bind_btStaticPlaneShape_btStaticPlaneShape_2=function(){return(jn=b._emscripten_bind_btStaticPlaneShape_btStaticPlaneShape_2=b.asm.el).apply(null,arguments)},kn=b._emscripten_bind_btStaticPlaneShape_setLocalScaling_1=function(){return(kn=b._emscripten_bind_btStaticPlaneShape_setLocalScaling_1=b.asm.fl).apply(null,arguments)},
|
||||
ln=b._emscripten_bind_btStaticPlaneShape_getLocalScaling_0=function(){return(ln=b._emscripten_bind_btStaticPlaneShape_getLocalScaling_0=b.asm.gl).apply(null,arguments)},mn=b._emscripten_bind_btStaticPlaneShape_calculateLocalInertia_2=function(){return(mn=b._emscripten_bind_btStaticPlaneShape_calculateLocalInertia_2=b.asm.hl).apply(null,arguments)},nn=b._emscripten_bind_btStaticPlaneShape___destroy___0=function(){return(nn=b._emscripten_bind_btStaticPlaneShape___destroy___0=b.asm.il).apply(null,arguments)},
|
||||
on=b._emscripten_bind_btBvhTriangleMeshShape_btBvhTriangleMeshShape_2=function(){return(on=b._emscripten_bind_btBvhTriangleMeshShape_btBvhTriangleMeshShape_2=b.asm.jl).apply(null,arguments)},pn=b._emscripten_bind_btBvhTriangleMeshShape_btBvhTriangleMeshShape_3=function(){return(pn=b._emscripten_bind_btBvhTriangleMeshShape_btBvhTriangleMeshShape_3=b.asm.kl).apply(null,arguments)},qn=b._emscripten_bind_btBvhTriangleMeshShape_setLocalScaling_1=function(){return(qn=b._emscripten_bind_btBvhTriangleMeshShape_setLocalScaling_1=
|
||||
b.asm.ll).apply(null,arguments)},rn=b._emscripten_bind_btBvhTriangleMeshShape_getLocalScaling_0=function(){return(rn=b._emscripten_bind_btBvhTriangleMeshShape_getLocalScaling_0=b.asm.ml).apply(null,arguments)},sn=b._emscripten_bind_btBvhTriangleMeshShape_calculateLocalInertia_2=function(){return(sn=b._emscripten_bind_btBvhTriangleMeshShape_calculateLocalInertia_2=b.asm.nl).apply(null,arguments)},tn=b._emscripten_bind_btBvhTriangleMeshShape___destroy___0=function(){return(tn=b._emscripten_bind_btBvhTriangleMeshShape___destroy___0=
|
||||
b.asm.ol).apply(null,arguments)},un=b._emscripten_bind_btHeightfieldTerrainShape_btHeightfieldTerrainShape_9=function(){return(un=b._emscripten_bind_btHeightfieldTerrainShape_btHeightfieldTerrainShape_9=b.asm.pl).apply(null,arguments)},vn=b._emscripten_bind_btHeightfieldTerrainShape_setMargin_1=function(){return(vn=b._emscripten_bind_btHeightfieldTerrainShape_setMargin_1=b.asm.ql).apply(null,arguments)},wn=b._emscripten_bind_btHeightfieldTerrainShape_getMargin_0=function(){return(wn=b._emscripten_bind_btHeightfieldTerrainShape_getMargin_0=
|
||||
b.asm.rl).apply(null,arguments)},xn=b._emscripten_bind_btHeightfieldTerrainShape_setLocalScaling_1=function(){return(xn=b._emscripten_bind_btHeightfieldTerrainShape_setLocalScaling_1=b.asm.sl).apply(null,arguments)},yn=b._emscripten_bind_btHeightfieldTerrainShape_getLocalScaling_0=function(){return(yn=b._emscripten_bind_btHeightfieldTerrainShape_getLocalScaling_0=b.asm.tl).apply(null,arguments)},zn=b._emscripten_bind_btHeightfieldTerrainShape_calculateLocalInertia_2=function(){return(zn=b._emscripten_bind_btHeightfieldTerrainShape_calculateLocalInertia_2=
|
||||
b.asm.ul).apply(null,arguments)},An=b._emscripten_bind_btHeightfieldTerrainShape___destroy___0=function(){return(An=b._emscripten_bind_btHeightfieldTerrainShape___destroy___0=b.asm.vl).apply(null,arguments)},Bn=b._emscripten_bind_btAABB_btAABB_4=function(){return(Bn=b._emscripten_bind_btAABB_btAABB_4=b.asm.wl).apply(null,arguments)},Cn=b._emscripten_bind_btAABB_invalidate_0=function(){return(Cn=b._emscripten_bind_btAABB_invalidate_0=b.asm.xl).apply(null,arguments)},Dn=b._emscripten_bind_btAABB_increment_margin_1=
|
||||
function(){return(Dn=b._emscripten_bind_btAABB_increment_margin_1=b.asm.yl).apply(null,arguments)},En=b._emscripten_bind_btAABB_copy_with_margin_2=function(){return(En=b._emscripten_bind_btAABB_copy_with_margin_2=b.asm.zl).apply(null,arguments)},Fn=b._emscripten_bind_btAABB___destroy___0=function(){return(Fn=b._emscripten_bind_btAABB___destroy___0=b.asm.Al).apply(null,arguments)},Gn=b._emscripten_bind_btPrimitiveTriangle_btPrimitiveTriangle_0=function(){return(Gn=b._emscripten_bind_btPrimitiveTriangle_btPrimitiveTriangle_0=
|
||||
b.asm.Bl).apply(null,arguments)},Hn=b._emscripten_bind_btPrimitiveTriangle___destroy___0=function(){return(Hn=b._emscripten_bind_btPrimitiveTriangle___destroy___0=b.asm.Cl).apply(null,arguments)},In=b._emscripten_bind_btTriangleShapeEx_btTriangleShapeEx_3=function(){return(In=b._emscripten_bind_btTriangleShapeEx_btTriangleShapeEx_3=b.asm.Dl).apply(null,arguments)},Jn=b._emscripten_bind_btTriangleShapeEx_getAabb_3=function(){return(Jn=b._emscripten_bind_btTriangleShapeEx_getAabb_3=b.asm.El).apply(null,
|
||||
arguments)},Kn=b._emscripten_bind_btTriangleShapeEx_applyTransform_1=function(){return(Kn=b._emscripten_bind_btTriangleShapeEx_applyTransform_1=b.asm.Fl).apply(null,arguments)},Ln=b._emscripten_bind_btTriangleShapeEx_buildTriPlane_1=function(){return(Ln=b._emscripten_bind_btTriangleShapeEx_buildTriPlane_1=b.asm.Gl).apply(null,arguments)},Mn=b._emscripten_bind_btTriangleShapeEx___destroy___0=function(){return(Mn=b._emscripten_bind_btTriangleShapeEx___destroy___0=b.asm.Hl).apply(null,arguments)},Nn=
|
||||
b._emscripten_bind_btTetrahedronShapeEx_btTetrahedronShapeEx_0=function(){return(Nn=b._emscripten_bind_btTetrahedronShapeEx_btTetrahedronShapeEx_0=b.asm.Il).apply(null,arguments)},On=b._emscripten_bind_btTetrahedronShapeEx_setVertices_4=function(){return(On=b._emscripten_bind_btTetrahedronShapeEx_setVertices_4=b.asm.Jl).apply(null,arguments)},Pn=b._emscripten_bind_btTetrahedronShapeEx___destroy___0=function(){return(Pn=b._emscripten_bind_btTetrahedronShapeEx___destroy___0=b.asm.Kl).apply(null,arguments)},
|
||||
Qn=b._emscripten_bind_CompoundPrimitiveManager_get_primitive_count_0=function(){return(Qn=b._emscripten_bind_CompoundPrimitiveManager_get_primitive_count_0=b.asm.Ll).apply(null,arguments)},Rn=b._emscripten_bind_CompoundPrimitiveManager_get_primitive_box_2=function(){return(Rn=b._emscripten_bind_CompoundPrimitiveManager_get_primitive_box_2=b.asm.Ml).apply(null,arguments)},Sn=b._emscripten_bind_CompoundPrimitiveManager_get_primitive_triangle_2=function(){return(Sn=b._emscripten_bind_CompoundPrimitiveManager_get_primitive_triangle_2=
|
||||
b.asm.Nl).apply(null,arguments)},Tn=b._emscripten_bind_CompoundPrimitiveManager_is_trimesh_0=function(){return(Tn=b._emscripten_bind_CompoundPrimitiveManager_is_trimesh_0=b.asm.Ol).apply(null,arguments)},Un=b._emscripten_bind_CompoundPrimitiveManager_get_m_compoundShape_0=function(){return(Un=b._emscripten_bind_CompoundPrimitiveManager_get_m_compoundShape_0=b.asm.Pl).apply(null,arguments)},Vn=b._emscripten_bind_CompoundPrimitiveManager_set_m_compoundShape_1=function(){return(Vn=b._emscripten_bind_CompoundPrimitiveManager_set_m_compoundShape_1=
|
||||
b.asm.Ql).apply(null,arguments)},Wn=b._emscripten_bind_CompoundPrimitiveManager___destroy___0=function(){return(Wn=b._emscripten_bind_CompoundPrimitiveManager___destroy___0=b.asm.Rl).apply(null,arguments)},Xn=b._emscripten_bind_btGImpactCompoundShape_btGImpactCompoundShape_0=function(){return(Xn=b._emscripten_bind_btGImpactCompoundShape_btGImpactCompoundShape_0=b.asm.Sl).apply(null,arguments)},Yn=b._emscripten_bind_btGImpactCompoundShape_btGImpactCompoundShape_1=function(){return(Yn=b._emscripten_bind_btGImpactCompoundShape_btGImpactCompoundShape_1=
|
||||
b.asm.Tl).apply(null,arguments)},Zn=b._emscripten_bind_btGImpactCompoundShape_childrenHasTransform_0=function(){return(Zn=b._emscripten_bind_btGImpactCompoundShape_childrenHasTransform_0=b.asm.Ul).apply(null,arguments)},$n=b._emscripten_bind_btGImpactCompoundShape_getPrimitiveManager_0=function(){return($n=b._emscripten_bind_btGImpactCompoundShape_getPrimitiveManager_0=b.asm.Vl).apply(null,arguments)},ao=b._emscripten_bind_btGImpactCompoundShape_getCompoundPrimitiveManager_0=function(){return(ao=
|
||||
b._emscripten_bind_btGImpactCompoundShape_getCompoundPrimitiveManager_0=b.asm.Wl).apply(null,arguments)},bo=b._emscripten_bind_btGImpactCompoundShape_getNumChildShapes_0=function(){return(bo=b._emscripten_bind_btGImpactCompoundShape_getNumChildShapes_0=b.asm.Xl).apply(null,arguments)},co=b._emscripten_bind_btGImpactCompoundShape_addChildShape_2=function(){return(co=b._emscripten_bind_btGImpactCompoundShape_addChildShape_2=b.asm.Yl).apply(null,arguments)},eo=b._emscripten_bind_btGImpactCompoundShape_getChildShape_1=
|
||||
function(){return(eo=b._emscripten_bind_btGImpactCompoundShape_getChildShape_1=b.asm.Zl).apply(null,arguments)},fo=b._emscripten_bind_btGImpactCompoundShape_getChildAabb_4=function(){return(fo=b._emscripten_bind_btGImpactCompoundShape_getChildAabb_4=b.asm._l).apply(null,arguments)},go=b._emscripten_bind_btGImpactCompoundShape_getChildTransform_1=function(){return(go=b._emscripten_bind_btGImpactCompoundShape_getChildTransform_1=b.asm.$l).apply(null,arguments)},ho=b._emscripten_bind_btGImpactCompoundShape_setChildTransform_2=
|
||||
function(){return(ho=b._emscripten_bind_btGImpactCompoundShape_setChildTransform_2=b.asm.am).apply(null,arguments)},io=b._emscripten_bind_btGImpactCompoundShape_calculateLocalInertia_2=function(){return(io=b._emscripten_bind_btGImpactCompoundShape_calculateLocalInertia_2=b.asm.bm).apply(null,arguments)},jo=b._emscripten_bind_btGImpactCompoundShape_getName_0=function(){return(jo=b._emscripten_bind_btGImpactCompoundShape_getName_0=b.asm.cm).apply(null,arguments)},ko=b._emscripten_bind_btGImpactCompoundShape_getGImpactShapeType_0=
|
||||
function(){return(ko=b._emscripten_bind_btGImpactCompoundShape_getGImpactShapeType_0=b.asm.dm).apply(null,arguments)},lo=b._emscripten_bind_btGImpactCompoundShape_setLocalScaling_1=function(){return(lo=b._emscripten_bind_btGImpactCompoundShape_setLocalScaling_1=b.asm.em).apply(null,arguments)},mo=b._emscripten_bind_btGImpactCompoundShape_getLocalScaling_0=function(){return(mo=b._emscripten_bind_btGImpactCompoundShape_getLocalScaling_0=b.asm.fm).apply(null,arguments)},no=b._emscripten_bind_btGImpactCompoundShape_updateBound_0=
|
||||
function(){return(no=b._emscripten_bind_btGImpactCompoundShape_updateBound_0=b.asm.gm).apply(null,arguments)},oo=b._emscripten_bind_btGImpactCompoundShape_postUpdate_0=function(){return(oo=b._emscripten_bind_btGImpactCompoundShape_postUpdate_0=b.asm.hm).apply(null,arguments)},po=b._emscripten_bind_btGImpactCompoundShape_getShapeType_0=function(){return(po=b._emscripten_bind_btGImpactCompoundShape_getShapeType_0=b.asm.im).apply(null,arguments)},qo=b._emscripten_bind_btGImpactCompoundShape_needsRetrieveTriangles_0=
|
||||
function(){return(qo=b._emscripten_bind_btGImpactCompoundShape_needsRetrieveTriangles_0=b.asm.jm).apply(null,arguments)},ro=b._emscripten_bind_btGImpactCompoundShape_needsRetrieveTetrahedrons_0=function(){return(ro=b._emscripten_bind_btGImpactCompoundShape_needsRetrieveTetrahedrons_0=b.asm.km).apply(null,arguments)},so=b._emscripten_bind_btGImpactCompoundShape_getBulletTriangle_2=function(){return(so=b._emscripten_bind_btGImpactCompoundShape_getBulletTriangle_2=b.asm.lm).apply(null,arguments)},to=
|
||||
b._emscripten_bind_btGImpactCompoundShape_getBulletTetrahedron_2=function(){return(to=b._emscripten_bind_btGImpactCompoundShape_getBulletTetrahedron_2=b.asm.mm).apply(null,arguments)},uo=b._emscripten_bind_btGImpactCompoundShape___destroy___0=function(){return(uo=b._emscripten_bind_btGImpactCompoundShape___destroy___0=b.asm.nm).apply(null,arguments)},vo=b._emscripten_bind_TrimeshPrimitiveManager_TrimeshPrimitiveManager_0=function(){return(vo=b._emscripten_bind_TrimeshPrimitiveManager_TrimeshPrimitiveManager_0=
|
||||
b.asm.om).apply(null,arguments)},wo=b._emscripten_bind_TrimeshPrimitiveManager_TrimeshPrimitiveManager_1=function(){return(wo=b._emscripten_bind_TrimeshPrimitiveManager_TrimeshPrimitiveManager_1=b.asm.pm).apply(null,arguments)},xo=b._emscripten_bind_TrimeshPrimitiveManager_lock_0=function(){return(xo=b._emscripten_bind_TrimeshPrimitiveManager_lock_0=b.asm.qm).apply(null,arguments)},yo=b._emscripten_bind_TrimeshPrimitiveManager_unlock_0=function(){return(yo=b._emscripten_bind_TrimeshPrimitiveManager_unlock_0=
|
||||
b.asm.rm).apply(null,arguments)},zo=b._emscripten_bind_TrimeshPrimitiveManager_is_trimesh_0=function(){return(zo=b._emscripten_bind_TrimeshPrimitiveManager_is_trimesh_0=b.asm.sm).apply(null,arguments)},Ao=b._emscripten_bind_TrimeshPrimitiveManager_get_vertex_count_0=function(){return(Ao=b._emscripten_bind_TrimeshPrimitiveManager_get_vertex_count_0=b.asm.tm).apply(null,arguments)},Bo=b._emscripten_bind_TrimeshPrimitiveManager_get_indices_4=function(){return(Bo=b._emscripten_bind_TrimeshPrimitiveManager_get_indices_4=
|
||||
b.asm.um).apply(null,arguments)},Co=b._emscripten_bind_TrimeshPrimitiveManager_get_vertex_2=function(){return(Co=b._emscripten_bind_TrimeshPrimitiveManager_get_vertex_2=b.asm.vm).apply(null,arguments)},Do=b._emscripten_bind_TrimeshPrimitiveManager_get_bullet_triangle_2=function(){return(Do=b._emscripten_bind_TrimeshPrimitiveManager_get_bullet_triangle_2=b.asm.wm).apply(null,arguments)},Eo=b._emscripten_bind_TrimeshPrimitiveManager_get_m_margin_0=function(){return(Eo=b._emscripten_bind_TrimeshPrimitiveManager_get_m_margin_0=
|
||||
b.asm.xm).apply(null,arguments)},Fo=b._emscripten_bind_TrimeshPrimitiveManager_set_m_margin_1=function(){return(Fo=b._emscripten_bind_TrimeshPrimitiveManager_set_m_margin_1=b.asm.ym).apply(null,arguments)},Go=b._emscripten_bind_TrimeshPrimitiveManager_get_m_meshInterface_0=function(){return(Go=b._emscripten_bind_TrimeshPrimitiveManager_get_m_meshInterface_0=b.asm.zm).apply(null,arguments)},Ho=b._emscripten_bind_TrimeshPrimitiveManager_set_m_meshInterface_1=function(){return(Ho=b._emscripten_bind_TrimeshPrimitiveManager_set_m_meshInterface_1=
|
||||
b.asm.Am).apply(null,arguments)},Io=b._emscripten_bind_TrimeshPrimitiveManager_get_m_part_0=function(){return(Io=b._emscripten_bind_TrimeshPrimitiveManager_get_m_part_0=b.asm.Bm).apply(null,arguments)},Jo=b._emscripten_bind_TrimeshPrimitiveManager_set_m_part_1=function(){return(Jo=b._emscripten_bind_TrimeshPrimitiveManager_set_m_part_1=b.asm.Cm).apply(null,arguments)},Ko=b._emscripten_bind_TrimeshPrimitiveManager_get_m_lock_count_0=function(){return(Ko=b._emscripten_bind_TrimeshPrimitiveManager_get_m_lock_count_0=
|
||||
b.asm.Dm).apply(null,arguments)},Lo=b._emscripten_bind_TrimeshPrimitiveManager_set_m_lock_count_1=function(){return(Lo=b._emscripten_bind_TrimeshPrimitiveManager_set_m_lock_count_1=b.asm.Em).apply(null,arguments)},Mo=b._emscripten_bind_TrimeshPrimitiveManager_get_numverts_0=function(){return(Mo=b._emscripten_bind_TrimeshPrimitiveManager_get_numverts_0=b.asm.Fm).apply(null,arguments)},No=b._emscripten_bind_TrimeshPrimitiveManager_set_numverts_1=function(){return(No=b._emscripten_bind_TrimeshPrimitiveManager_set_numverts_1=
|
||||
b.asm.Gm).apply(null,arguments)},Oo=b._emscripten_bind_TrimeshPrimitiveManager_get_type_0=function(){return(Oo=b._emscripten_bind_TrimeshPrimitiveManager_get_type_0=b.asm.Hm).apply(null,arguments)},Po=b._emscripten_bind_TrimeshPrimitiveManager_set_type_1=function(){return(Po=b._emscripten_bind_TrimeshPrimitiveManager_set_type_1=b.asm.Im).apply(null,arguments)},Qo=b._emscripten_bind_TrimeshPrimitiveManager_get_stride_0=function(){return(Qo=b._emscripten_bind_TrimeshPrimitiveManager_get_stride_0=b.asm.Jm).apply(null,
|
||||
arguments)},Ro=b._emscripten_bind_TrimeshPrimitiveManager_set_stride_1=function(){return(Ro=b._emscripten_bind_TrimeshPrimitiveManager_set_stride_1=b.asm.Km).apply(null,arguments)},So=b._emscripten_bind_TrimeshPrimitiveManager_get_indexstride_0=function(){return(So=b._emscripten_bind_TrimeshPrimitiveManager_get_indexstride_0=b.asm.Lm).apply(null,arguments)},To=b._emscripten_bind_TrimeshPrimitiveManager_set_indexstride_1=function(){return(To=b._emscripten_bind_TrimeshPrimitiveManager_set_indexstride_1=
|
||||
b.asm.Mm).apply(null,arguments)},Uo=b._emscripten_bind_TrimeshPrimitiveManager_get_numfaces_0=function(){return(Uo=b._emscripten_bind_TrimeshPrimitiveManager_get_numfaces_0=b.asm.Nm).apply(null,arguments)},Vo=b._emscripten_bind_TrimeshPrimitiveManager_set_numfaces_1=function(){return(Vo=b._emscripten_bind_TrimeshPrimitiveManager_set_numfaces_1=b.asm.Om).apply(null,arguments)},Wo=b._emscripten_bind_TrimeshPrimitiveManager_get_indicestype_0=function(){return(Wo=b._emscripten_bind_TrimeshPrimitiveManager_get_indicestype_0=
|
||||
b.asm.Pm).apply(null,arguments)},Xo=b._emscripten_bind_TrimeshPrimitiveManager_set_indicestype_1=function(){return(Xo=b._emscripten_bind_TrimeshPrimitiveManager_set_indicestype_1=b.asm.Qm).apply(null,arguments)},Yo=b._emscripten_bind_TrimeshPrimitiveManager___destroy___0=function(){return(Yo=b._emscripten_bind_TrimeshPrimitiveManager___destroy___0=b.asm.Rm).apply(null,arguments)},Zo=b._emscripten_bind_btGImpactMeshShapePart_btGImpactMeshShapePart_2=function(){return(Zo=b._emscripten_bind_btGImpactMeshShapePart_btGImpactMeshShapePart_2=
|
||||
b.asm.Sm).apply(null,arguments)},$o=b._emscripten_bind_btGImpactMeshShapePart_getTrimeshPrimitiveManager_0=function(){return($o=b._emscripten_bind_btGImpactMeshShapePart_getTrimeshPrimitiveManager_0=b.asm.Tm).apply(null,arguments)},ap=b._emscripten_bind_btGImpactMeshShapePart_getVertexCount_0=function(){return(ap=b._emscripten_bind_btGImpactMeshShapePart_getVertexCount_0=b.asm.Um).apply(null,arguments)},bp=b._emscripten_bind_btGImpactMeshShapePart_getVertex_2=function(){return(bp=b._emscripten_bind_btGImpactMeshShapePart_getVertex_2=
|
||||
b.asm.Vm).apply(null,arguments)},cp=b._emscripten_bind_btGImpactMeshShapePart_getPart_0=function(){return(cp=b._emscripten_bind_btGImpactMeshShapePart_getPart_0=b.asm.Wm).apply(null,arguments)},dp=b._emscripten_bind_btGImpactMeshShapePart_setLocalScaling_1=function(){return(dp=b._emscripten_bind_btGImpactMeshShapePart_setLocalScaling_1=b.asm.Xm).apply(null,arguments)},ep=b._emscripten_bind_btGImpactMeshShapePart_getLocalScaling_0=function(){return(ep=b._emscripten_bind_btGImpactMeshShapePart_getLocalScaling_0=
|
||||
b.asm.Ym).apply(null,arguments)},fp=b._emscripten_bind_btGImpactMeshShapePart_updateBound_0=function(){return(fp=b._emscripten_bind_btGImpactMeshShapePart_updateBound_0=b.asm.Zm).apply(null,arguments)},gp=b._emscripten_bind_btGImpactMeshShapePart_postUpdate_0=function(){return(gp=b._emscripten_bind_btGImpactMeshShapePart_postUpdate_0=b.asm._m).apply(null,arguments)},hp=b._emscripten_bind_btGImpactMeshShapePart_getShapeType_0=function(){return(hp=b._emscripten_bind_btGImpactMeshShapePart_getShapeType_0=
|
||||
b.asm.$m).apply(null,arguments)},ip=b._emscripten_bind_btGImpactMeshShapePart_needsRetrieveTriangles_0=function(){return(ip=b._emscripten_bind_btGImpactMeshShapePart_needsRetrieveTriangles_0=b.asm.an).apply(null,arguments)},jp=b._emscripten_bind_btGImpactMeshShapePart_needsRetrieveTetrahedrons_0=function(){return(jp=b._emscripten_bind_btGImpactMeshShapePart_needsRetrieveTetrahedrons_0=b.asm.bn).apply(null,arguments)},kp=b._emscripten_bind_btGImpactMeshShapePart_getBulletTriangle_2=function(){return(kp=
|
||||
b._emscripten_bind_btGImpactMeshShapePart_getBulletTriangle_2=b.asm.cn).apply(null,arguments)},lp=b._emscripten_bind_btGImpactMeshShapePart_getBulletTetrahedron_2=function(){return(lp=b._emscripten_bind_btGImpactMeshShapePart_getBulletTetrahedron_2=b.asm.dn).apply(null,arguments)},mp=b._emscripten_bind_btGImpactMeshShapePart___destroy___0=function(){return(mp=b._emscripten_bind_btGImpactMeshShapePart___destroy___0=b.asm.en).apply(null,arguments)},np=b._emscripten_bind_btGImpactMeshShape_btGImpactMeshShape_1=
|
||||
function(){return(np=b._emscripten_bind_btGImpactMeshShape_btGImpactMeshShape_1=b.asm.fn).apply(null,arguments)},op=b._emscripten_bind_btGImpactMeshShape_getMeshInterface_0=function(){return(op=b._emscripten_bind_btGImpactMeshShape_getMeshInterface_0=b.asm.gn).apply(null,arguments)},pp=b._emscripten_bind_btGImpactMeshShape_getMeshPartCount_0=function(){return(pp=b._emscripten_bind_btGImpactMeshShape_getMeshPartCount_0=b.asm.hn).apply(null,arguments)},qp=b._emscripten_bind_btGImpactMeshShape_getMeshPart_1=
|
||||
function(){return(qp=b._emscripten_bind_btGImpactMeshShape_getMeshPart_1=b.asm.jn).apply(null,arguments)},rp=b._emscripten_bind_btGImpactMeshShape_calculateSerializeBufferSize_0=function(){return(rp=b._emscripten_bind_btGImpactMeshShape_calculateSerializeBufferSize_0=b.asm.kn).apply(null,arguments)},sp=b._emscripten_bind_btGImpactMeshShape_setLocalScaling_1=function(){return(sp=b._emscripten_bind_btGImpactMeshShape_setLocalScaling_1=b.asm.ln).apply(null,arguments)},tp=b._emscripten_bind_btGImpactMeshShape_getLocalScaling_0=
|
||||
function(){return(tp=b._emscripten_bind_btGImpactMeshShape_getLocalScaling_0=b.asm.mn).apply(null,arguments)},up=b._emscripten_bind_btGImpactMeshShape_updateBound_0=function(){return(up=b._emscripten_bind_btGImpactMeshShape_updateBound_0=b.asm.nn).apply(null,arguments)},vp=b._emscripten_bind_btGImpactMeshShape_postUpdate_0=function(){return(vp=b._emscripten_bind_btGImpactMeshShape_postUpdate_0=b.asm.on).apply(null,arguments)},wp=b._emscripten_bind_btGImpactMeshShape_getShapeType_0=function(){return(wp=
|
||||
b._emscripten_bind_btGImpactMeshShape_getShapeType_0=b.asm.pn).apply(null,arguments)},xp=b._emscripten_bind_btGImpactMeshShape_needsRetrieveTriangles_0=function(){return(xp=b._emscripten_bind_btGImpactMeshShape_needsRetrieveTriangles_0=b.asm.qn).apply(null,arguments)},yp=b._emscripten_bind_btGImpactMeshShape_needsRetrieveTetrahedrons_0=function(){return(yp=b._emscripten_bind_btGImpactMeshShape_needsRetrieveTetrahedrons_0=b.asm.rn).apply(null,arguments)},zp=b._emscripten_bind_btGImpactMeshShape_getBulletTriangle_2=
|
||||
function(){return(zp=b._emscripten_bind_btGImpactMeshShape_getBulletTriangle_2=b.asm.sn).apply(null,arguments)},Ap=b._emscripten_bind_btGImpactMeshShape_getBulletTetrahedron_2=function(){return(Ap=b._emscripten_bind_btGImpactMeshShape_getBulletTetrahedron_2=b.asm.tn).apply(null,arguments)},Bp=b._emscripten_bind_btGImpactMeshShape___destroy___0=function(){return(Bp=b._emscripten_bind_btGImpactMeshShape___destroy___0=b.asm.un).apply(null,arguments)},Cp=b._emscripten_bind_btCollisionAlgorithmConstructionInfo_btCollisionAlgorithmConstructionInfo_0=
|
||||
function(){return(Cp=b._emscripten_bind_btCollisionAlgorithmConstructionInfo_btCollisionAlgorithmConstructionInfo_0=b.asm.vn).apply(null,arguments)},Dp=b._emscripten_bind_btCollisionAlgorithmConstructionInfo_btCollisionAlgorithmConstructionInfo_2=function(){return(Dp=b._emscripten_bind_btCollisionAlgorithmConstructionInfo_btCollisionAlgorithmConstructionInfo_2=b.asm.wn).apply(null,arguments)},Ep=b._emscripten_bind_btCollisionAlgorithmConstructionInfo_get_m_dispatcher1_0=function(){return(Ep=b._emscripten_bind_btCollisionAlgorithmConstructionInfo_get_m_dispatcher1_0=
|
||||
b.asm.xn).apply(null,arguments)},Fp=b._emscripten_bind_btCollisionAlgorithmConstructionInfo_set_m_dispatcher1_1=function(){return(Fp=b._emscripten_bind_btCollisionAlgorithmConstructionInfo_set_m_dispatcher1_1=b.asm.yn).apply(null,arguments)},Gp=b._emscripten_bind_btCollisionAlgorithmConstructionInfo_get_m_manifold_0=function(){return(Gp=b._emscripten_bind_btCollisionAlgorithmConstructionInfo_get_m_manifold_0=b.asm.zn).apply(null,arguments)},Hp=b._emscripten_bind_btCollisionAlgorithmConstructionInfo_set_m_manifold_1=
|
||||
function(){return(Hp=b._emscripten_bind_btCollisionAlgorithmConstructionInfo_set_m_manifold_1=b.asm.An).apply(null,arguments)},Ip=b._emscripten_bind_btCollisionAlgorithmConstructionInfo___destroy___0=function(){return(Ip=b._emscripten_bind_btCollisionAlgorithmConstructionInfo___destroy___0=b.asm.Bn).apply(null,arguments)},Jp=b._emscripten_bind_btGImpactCollisionAlgorithm_btGImpactCollisionAlgorithm_3=function(){return(Jp=b._emscripten_bind_btGImpactCollisionAlgorithm_btGImpactCollisionAlgorithm_3=
|
||||
b.asm.Cn).apply(null,arguments)},Kp=b._emscripten_bind_btGImpactCollisionAlgorithm_registerAlgorithm_1=function(){return(Kp=b._emscripten_bind_btGImpactCollisionAlgorithm_registerAlgorithm_1=b.asm.Dn).apply(null,arguments)},Lp=b._emscripten_bind_btGImpactCollisionAlgorithm___destroy___0=function(){return(Lp=b._emscripten_bind_btGImpactCollisionAlgorithm___destroy___0=b.asm.En).apply(null,arguments)},Mp=b._emscripten_bind_btDefaultCollisionConstructionInfo_btDefaultCollisionConstructionInfo_0=function(){return(Mp=
|
||||
b._emscripten_bind_btDefaultCollisionConstructionInfo_btDefaultCollisionConstructionInfo_0=b.asm.Fn).apply(null,arguments)},Np=b._emscripten_bind_btDefaultCollisionConstructionInfo___destroy___0=function(){return(Np=b._emscripten_bind_btDefaultCollisionConstructionInfo___destroy___0=b.asm.Gn).apply(null,arguments)},Op=b._emscripten_bind_btPersistentManifold_btPersistentManifold_0=function(){return(Op=b._emscripten_bind_btPersistentManifold_btPersistentManifold_0=b.asm.Hn).apply(null,arguments)},Pp=
|
||||
b._emscripten_bind_btPersistentManifold_getBody0_0=function(){return(Pp=b._emscripten_bind_btPersistentManifold_getBody0_0=b.asm.In).apply(null,arguments)},Qp=b._emscripten_bind_btPersistentManifold_getBody1_0=function(){return(Qp=b._emscripten_bind_btPersistentManifold_getBody1_0=b.asm.Jn).apply(null,arguments)},Rp=b._emscripten_bind_btPersistentManifold_getNumContacts_0=function(){return(Rp=b._emscripten_bind_btPersistentManifold_getNumContacts_0=b.asm.Kn).apply(null,arguments)},Sp=b._emscripten_bind_btPersistentManifold_getContactPoint_1=
|
||||
function(){return(Sp=b._emscripten_bind_btPersistentManifold_getContactPoint_1=b.asm.Ln).apply(null,arguments)},Tp=b._emscripten_bind_btPersistentManifold___destroy___0=function(){return(Tp=b._emscripten_bind_btPersistentManifold___destroy___0=b.asm.Mn).apply(null,arguments)},Up=b._emscripten_bind_btCollisionDispatcher_btCollisionDispatcher_1=function(){return(Up=b._emscripten_bind_btCollisionDispatcher_btCollisionDispatcher_1=b.asm.Nn).apply(null,arguments)},Vp=b._emscripten_bind_btCollisionDispatcher_getNumManifolds_0=
|
||||
function(){return(Vp=b._emscripten_bind_btCollisionDispatcher_getNumManifolds_0=b.asm.On).apply(null,arguments)},Wp=b._emscripten_bind_btCollisionDispatcher_getManifoldByIndexInternal_1=function(){return(Wp=b._emscripten_bind_btCollisionDispatcher_getManifoldByIndexInternal_1=b.asm.Pn).apply(null,arguments)},Xp=b._emscripten_bind_btCollisionDispatcher___destroy___0=function(){return(Xp=b._emscripten_bind_btCollisionDispatcher___destroy___0=b.asm.Qn).apply(null,arguments)},Yp=b._emscripten_bind_btOverlappingPairCallback___destroy___0=
|
||||
function(){return(Yp=b._emscripten_bind_btOverlappingPairCallback___destroy___0=b.asm.Rn).apply(null,arguments)},Zp=b._emscripten_bind_btOverlappingPairCache_setInternalGhostPairCallback_1=function(){return(Zp=b._emscripten_bind_btOverlappingPairCache_setInternalGhostPairCallback_1=b.asm.Sn).apply(null,arguments)},$p=b._emscripten_bind_btOverlappingPairCache_getNumOverlappingPairs_0=function(){return($p=b._emscripten_bind_btOverlappingPairCache_getNumOverlappingPairs_0=b.asm.Tn).apply(null,arguments)},
|
||||
aq=b._emscripten_bind_btOverlappingPairCache___destroy___0=function(){return(aq=b._emscripten_bind_btOverlappingPairCache___destroy___0=b.asm.Un).apply(null,arguments)},bq=b._emscripten_bind_btAxisSweep3_btAxisSweep3_2=function(){return(bq=b._emscripten_bind_btAxisSweep3_btAxisSweep3_2=b.asm.Vn).apply(null,arguments)},cq=b._emscripten_bind_btAxisSweep3_btAxisSweep3_3=function(){return(cq=b._emscripten_bind_btAxisSweep3_btAxisSweep3_3=b.asm.Wn).apply(null,arguments)},dq=b._emscripten_bind_btAxisSweep3_btAxisSweep3_4=
|
||||
function(){return(dq=b._emscripten_bind_btAxisSweep3_btAxisSweep3_4=b.asm.Xn).apply(null,arguments)},eq=b._emscripten_bind_btAxisSweep3_btAxisSweep3_5=function(){return(eq=b._emscripten_bind_btAxisSweep3_btAxisSweep3_5=b.asm.Yn).apply(null,arguments)},fq=b._emscripten_bind_btAxisSweep3___destroy___0=function(){return(fq=b._emscripten_bind_btAxisSweep3___destroy___0=b.asm.Zn).apply(null,arguments)},gq=b._emscripten_bind_btBroadphaseInterface_getOverlappingPairCache_0=function(){return(gq=b._emscripten_bind_btBroadphaseInterface_getOverlappingPairCache_0=
|
||||
b.asm._n).apply(null,arguments)},hq=b._emscripten_bind_btBroadphaseInterface___destroy___0=function(){return(hq=b._emscripten_bind_btBroadphaseInterface___destroy___0=b.asm.$n).apply(null,arguments)},iq=b._emscripten_bind_btCollisionConfiguration___destroy___0=function(){return(iq=b._emscripten_bind_btCollisionConfiguration___destroy___0=b.asm.ao).apply(null,arguments)},jq=b._emscripten_bind_btDbvtBroadphase_btDbvtBroadphase_0=function(){return(jq=b._emscripten_bind_btDbvtBroadphase_btDbvtBroadphase_0=
|
||||
b.asm.bo).apply(null,arguments)},kq=b._emscripten_bind_btDbvtBroadphase___destroy___0=function(){return(kq=b._emscripten_bind_btDbvtBroadphase___destroy___0=b.asm.co).apply(null,arguments)},lq=b._emscripten_bind_btBroadphaseProxy_get_m_collisionFilterGroup_0=function(){return(lq=b._emscripten_bind_btBroadphaseProxy_get_m_collisionFilterGroup_0=b.asm.eo).apply(null,arguments)},mq=b._emscripten_bind_btBroadphaseProxy_set_m_collisionFilterGroup_1=function(){return(mq=b._emscripten_bind_btBroadphaseProxy_set_m_collisionFilterGroup_1=
|
||||
b.asm.fo).apply(null,arguments)},nq=b._emscripten_bind_btBroadphaseProxy_get_m_collisionFilterMask_0=function(){return(nq=b._emscripten_bind_btBroadphaseProxy_get_m_collisionFilterMask_0=b.asm.go).apply(null,arguments)},oq=b._emscripten_bind_btBroadphaseProxy_set_m_collisionFilterMask_1=function(){return(oq=b._emscripten_bind_btBroadphaseProxy_set_m_collisionFilterMask_1=b.asm.ho).apply(null,arguments)},pq=b._emscripten_bind_btBroadphaseProxy___destroy___0=function(){return(pq=b._emscripten_bind_btBroadphaseProxy___destroy___0=
|
||||
b.asm.io).apply(null,arguments)},qq=b._emscripten_bind_btRigidBodyConstructionInfo_btRigidBodyConstructionInfo_3=function(){return(qq=b._emscripten_bind_btRigidBodyConstructionInfo_btRigidBodyConstructionInfo_3=b.asm.jo).apply(null,arguments)},rq=b._emscripten_bind_btRigidBodyConstructionInfo_btRigidBodyConstructionInfo_4=function(){return(rq=b._emscripten_bind_btRigidBodyConstructionInfo_btRigidBodyConstructionInfo_4=b.asm.ko).apply(null,arguments)},sq=b._emscripten_bind_btRigidBodyConstructionInfo_get_m_linearDamping_0=
|
||||
function(){return(sq=b._emscripten_bind_btRigidBodyConstructionInfo_get_m_linearDamping_0=b.asm.lo).apply(null,arguments)},tq=b._emscripten_bind_btRigidBodyConstructionInfo_set_m_linearDamping_1=function(){return(tq=b._emscripten_bind_btRigidBodyConstructionInfo_set_m_linearDamping_1=b.asm.mo).apply(null,arguments)},uq=b._emscripten_bind_btRigidBodyConstructionInfo_get_m_angularDamping_0=function(){return(uq=b._emscripten_bind_btRigidBodyConstructionInfo_get_m_angularDamping_0=b.asm.no).apply(null,
|
||||
arguments)},vq=b._emscripten_bind_btRigidBodyConstructionInfo_set_m_angularDamping_1=function(){return(vq=b._emscripten_bind_btRigidBodyConstructionInfo_set_m_angularDamping_1=b.asm.oo).apply(null,arguments)},wq=b._emscripten_bind_btRigidBodyConstructionInfo_get_m_friction_0=function(){return(wq=b._emscripten_bind_btRigidBodyConstructionInfo_get_m_friction_0=b.asm.po).apply(null,arguments)},xq=b._emscripten_bind_btRigidBodyConstructionInfo_set_m_friction_1=function(){return(xq=b._emscripten_bind_btRigidBodyConstructionInfo_set_m_friction_1=
|
||||
b.asm.qo).apply(null,arguments)},yq=b._emscripten_bind_btRigidBodyConstructionInfo_get_m_rollingFriction_0=function(){return(yq=b._emscripten_bind_btRigidBodyConstructionInfo_get_m_rollingFriction_0=b.asm.ro).apply(null,arguments)},zq=b._emscripten_bind_btRigidBodyConstructionInfo_set_m_rollingFriction_1=function(){return(zq=b._emscripten_bind_btRigidBodyConstructionInfo_set_m_rollingFriction_1=b.asm.so).apply(null,arguments)},Aq=b._emscripten_bind_btRigidBodyConstructionInfo_get_m_restitution_0=
|
||||
function(){return(Aq=b._emscripten_bind_btRigidBodyConstructionInfo_get_m_restitution_0=b.asm.to).apply(null,arguments)},Bq=b._emscripten_bind_btRigidBodyConstructionInfo_set_m_restitution_1=function(){return(Bq=b._emscripten_bind_btRigidBodyConstructionInfo_set_m_restitution_1=b.asm.uo).apply(null,arguments)},Cq=b._emscripten_bind_btRigidBodyConstructionInfo_get_m_linearSleepingThreshold_0=function(){return(Cq=b._emscripten_bind_btRigidBodyConstructionInfo_get_m_linearSleepingThreshold_0=b.asm.vo).apply(null,
|
||||
arguments)},Dq=b._emscripten_bind_btRigidBodyConstructionInfo_set_m_linearSleepingThreshold_1=function(){return(Dq=b._emscripten_bind_btRigidBodyConstructionInfo_set_m_linearSleepingThreshold_1=b.asm.wo).apply(null,arguments)},Eq=b._emscripten_bind_btRigidBodyConstructionInfo_get_m_angularSleepingThreshold_0=function(){return(Eq=b._emscripten_bind_btRigidBodyConstructionInfo_get_m_angularSleepingThreshold_0=b.asm.xo).apply(null,arguments)},Fq=b._emscripten_bind_btRigidBodyConstructionInfo_set_m_angularSleepingThreshold_1=
|
||||
function(){return(Fq=b._emscripten_bind_btRigidBodyConstructionInfo_set_m_angularSleepingThreshold_1=b.asm.yo).apply(null,arguments)},Gq=b._emscripten_bind_btRigidBodyConstructionInfo_get_m_additionalDamping_0=function(){return(Gq=b._emscripten_bind_btRigidBodyConstructionInfo_get_m_additionalDamping_0=b.asm.zo).apply(null,arguments)},Hq=b._emscripten_bind_btRigidBodyConstructionInfo_set_m_additionalDamping_1=function(){return(Hq=b._emscripten_bind_btRigidBodyConstructionInfo_set_m_additionalDamping_1=
|
||||
b.asm.Ao).apply(null,arguments)},Iq=b._emscripten_bind_btRigidBodyConstructionInfo_get_m_additionalDampingFactor_0=function(){return(Iq=b._emscripten_bind_btRigidBodyConstructionInfo_get_m_additionalDampingFactor_0=b.asm.Bo).apply(null,arguments)},Jq=b._emscripten_bind_btRigidBodyConstructionInfo_set_m_additionalDampingFactor_1=function(){return(Jq=b._emscripten_bind_btRigidBodyConstructionInfo_set_m_additionalDampingFactor_1=b.asm.Co).apply(null,arguments)},Kq=b._emscripten_bind_btRigidBodyConstructionInfo_get_m_additionalLinearDampingThresholdSqr_0=
|
||||
function(){return(Kq=b._emscripten_bind_btRigidBodyConstructionInfo_get_m_additionalLinearDampingThresholdSqr_0=b.asm.Do).apply(null,arguments)},Lq=b._emscripten_bind_btRigidBodyConstructionInfo_set_m_additionalLinearDampingThresholdSqr_1=function(){return(Lq=b._emscripten_bind_btRigidBodyConstructionInfo_set_m_additionalLinearDampingThresholdSqr_1=b.asm.Eo).apply(null,arguments)},Mq=b._emscripten_bind_btRigidBodyConstructionInfo_get_m_additionalAngularDampingThresholdSqr_0=function(){return(Mq=b._emscripten_bind_btRigidBodyConstructionInfo_get_m_additionalAngularDampingThresholdSqr_0=
|
||||
b.asm.Fo).apply(null,arguments)},Nq=b._emscripten_bind_btRigidBodyConstructionInfo_set_m_additionalAngularDampingThresholdSqr_1=function(){return(Nq=b._emscripten_bind_btRigidBodyConstructionInfo_set_m_additionalAngularDampingThresholdSqr_1=b.asm.Go).apply(null,arguments)},Oq=b._emscripten_bind_btRigidBodyConstructionInfo_get_m_additionalAngularDampingFactor_0=function(){return(Oq=b._emscripten_bind_btRigidBodyConstructionInfo_get_m_additionalAngularDampingFactor_0=b.asm.Ho).apply(null,arguments)},
|
||||
Pq=b._emscripten_bind_btRigidBodyConstructionInfo_set_m_additionalAngularDampingFactor_1=function(){return(Pq=b._emscripten_bind_btRigidBodyConstructionInfo_set_m_additionalAngularDampingFactor_1=b.asm.Io).apply(null,arguments)},Qq=b._emscripten_bind_btRigidBodyConstructionInfo___destroy___0=function(){return(Qq=b._emscripten_bind_btRigidBodyConstructionInfo___destroy___0=b.asm.Jo).apply(null,arguments)},Rq=b._emscripten_bind_btRigidBody_btRigidBody_1=function(){return(Rq=b._emscripten_bind_btRigidBody_btRigidBody_1=
|
||||
b.asm.Ko).apply(null,arguments)},Sq=b._emscripten_bind_btRigidBody_getCenterOfMassTransform_0=function(){return(Sq=b._emscripten_bind_btRigidBody_getCenterOfMassTransform_0=b.asm.Lo).apply(null,arguments)},Tq=b._emscripten_bind_btRigidBody_setCenterOfMassTransform_1=function(){return(Tq=b._emscripten_bind_btRigidBody_setCenterOfMassTransform_1=b.asm.Mo).apply(null,arguments)},Uq=b._emscripten_bind_btRigidBody_setSleepingThresholds_2=function(){return(Uq=b._emscripten_bind_btRigidBody_setSleepingThresholds_2=
|
||||
b.asm.No).apply(null,arguments)},Vq=b._emscripten_bind_btRigidBody_getLinearDamping_0=function(){return(Vq=b._emscripten_bind_btRigidBody_getLinearDamping_0=b.asm.Oo).apply(null,arguments)},Wq=b._emscripten_bind_btRigidBody_getAngularDamping_0=function(){return(Wq=b._emscripten_bind_btRigidBody_getAngularDamping_0=b.asm.Po).apply(null,arguments)},Xq=b._emscripten_bind_btRigidBody_setDamping_2=function(){return(Xq=b._emscripten_bind_btRigidBody_setDamping_2=b.asm.Qo).apply(null,arguments)},Yq=b._emscripten_bind_btRigidBody_setMassProps_2=
|
||||
function(){return(Yq=b._emscripten_bind_btRigidBody_setMassProps_2=b.asm.Ro).apply(null,arguments)},Zq=b._emscripten_bind_btRigidBody_getLinearFactor_0=function(){return(Zq=b._emscripten_bind_btRigidBody_getLinearFactor_0=b.asm.So).apply(null,arguments)},$q=b._emscripten_bind_btRigidBody_setLinearFactor_1=function(){return($q=b._emscripten_bind_btRigidBody_setLinearFactor_1=b.asm.To).apply(null,arguments)},ar=b._emscripten_bind_btRigidBody_applyTorque_1=function(){return(ar=b._emscripten_bind_btRigidBody_applyTorque_1=
|
||||
b.asm.Uo).apply(null,arguments)},br=b._emscripten_bind_btRigidBody_applyLocalTorque_1=function(){return(br=b._emscripten_bind_btRigidBody_applyLocalTorque_1=b.asm.Vo).apply(null,arguments)},cr=b._emscripten_bind_btRigidBody_applyForce_2=function(){return(cr=b._emscripten_bind_btRigidBody_applyForce_2=b.asm.Wo).apply(null,arguments)},dr=b._emscripten_bind_btRigidBody_applyCentralForce_1=function(){return(dr=b._emscripten_bind_btRigidBody_applyCentralForce_1=b.asm.Xo).apply(null,arguments)},er=b._emscripten_bind_btRigidBody_applyCentralLocalForce_1=
|
||||
function(){return(er=b._emscripten_bind_btRigidBody_applyCentralLocalForce_1=b.asm.Yo).apply(null,arguments)},fr=b._emscripten_bind_btRigidBody_applyTorqueImpulse_1=function(){return(fr=b._emscripten_bind_btRigidBody_applyTorqueImpulse_1=b.asm.Zo).apply(null,arguments)},gr=b._emscripten_bind_btRigidBody_applyImpulse_2=function(){return(gr=b._emscripten_bind_btRigidBody_applyImpulse_2=b.asm._o).apply(null,arguments)},hr=b._emscripten_bind_btRigidBody_applyCentralImpulse_1=function(){return(hr=b._emscripten_bind_btRigidBody_applyCentralImpulse_1=
|
||||
b.asm.$o).apply(null,arguments)},ir=b._emscripten_bind_btRigidBody_updateInertiaTensor_0=function(){return(ir=b._emscripten_bind_btRigidBody_updateInertiaTensor_0=b.asm.ap).apply(null,arguments)},jr=b._emscripten_bind_btRigidBody_getLinearVelocity_0=function(){return(jr=b._emscripten_bind_btRigidBody_getLinearVelocity_0=b.asm.bp).apply(null,arguments)},kr=b._emscripten_bind_btRigidBody_getAngularVelocity_0=function(){return(kr=b._emscripten_bind_btRigidBody_getAngularVelocity_0=b.asm.cp).apply(null,
|
||||
arguments)},lr=b._emscripten_bind_btRigidBody_setLinearVelocity_1=function(){return(lr=b._emscripten_bind_btRigidBody_setLinearVelocity_1=b.asm.dp).apply(null,arguments)},mr=b._emscripten_bind_btRigidBody_setAngularVelocity_1=function(){return(mr=b._emscripten_bind_btRigidBody_setAngularVelocity_1=b.asm.ep).apply(null,arguments)},nr=b._emscripten_bind_btRigidBody_getMotionState_0=function(){return(nr=b._emscripten_bind_btRigidBody_getMotionState_0=b.asm.fp).apply(null,arguments)},or=b._emscripten_bind_btRigidBody_setMotionState_1=
|
||||
function(){return(or=b._emscripten_bind_btRigidBody_setMotionState_1=b.asm.gp).apply(null,arguments)},pr=b._emscripten_bind_btRigidBody_getAngularFactor_0=function(){return(pr=b._emscripten_bind_btRigidBody_getAngularFactor_0=b.asm.hp).apply(null,arguments)},qr=b._emscripten_bind_btRigidBody_setAngularFactor_1=function(){return(qr=b._emscripten_bind_btRigidBody_setAngularFactor_1=b.asm.ip).apply(null,arguments)},rr=b._emscripten_bind_btRigidBody_upcast_1=function(){return(rr=b._emscripten_bind_btRigidBody_upcast_1=
|
||||
b.asm.jp).apply(null,arguments)},sr=b._emscripten_bind_btRigidBody_getAabb_2=function(){return(sr=b._emscripten_bind_btRigidBody_getAabb_2=b.asm.kp).apply(null,arguments)},tr=b._emscripten_bind_btRigidBody_applyGravity_0=function(){return(tr=b._emscripten_bind_btRigidBody_applyGravity_0=b.asm.lp).apply(null,arguments)},ur=b._emscripten_bind_btRigidBody_getGravity_0=function(){return(ur=b._emscripten_bind_btRigidBody_getGravity_0=b.asm.mp).apply(null,arguments)},vr=b._emscripten_bind_btRigidBody_setGravity_1=
|
||||
function(){return(vr=b._emscripten_bind_btRigidBody_setGravity_1=b.asm.np).apply(null,arguments)},wr=b._emscripten_bind_btRigidBody_getBroadphaseProxy_0=function(){return(wr=b._emscripten_bind_btRigidBody_getBroadphaseProxy_0=b.asm.op).apply(null,arguments)},xr=b._emscripten_bind_btRigidBody_clearForces_0=function(){return(xr=b._emscripten_bind_btRigidBody_clearForces_0=b.asm.pp).apply(null,arguments)},yr=b._emscripten_bind_btRigidBody_setFlags_1=function(){return(yr=b._emscripten_bind_btRigidBody_setFlags_1=
|
||||
b.asm.qp).apply(null,arguments)},zr=b._emscripten_bind_btRigidBody_getFlags_0=function(){return(zr=b._emscripten_bind_btRigidBody_getFlags_0=b.asm.rp).apply(null,arguments)},Ar=b._emscripten_bind_btRigidBody_setAnisotropicFriction_2=function(){return(Ar=b._emscripten_bind_btRigidBody_setAnisotropicFriction_2=b.asm.sp).apply(null,arguments)},Br=b._emscripten_bind_btRigidBody_getCollisionShape_0=function(){return(Br=b._emscripten_bind_btRigidBody_getCollisionShape_0=b.asm.tp).apply(null,arguments)},
|
||||
Cr=b._emscripten_bind_btRigidBody_setContactProcessingThreshold_1=function(){return(Cr=b._emscripten_bind_btRigidBody_setContactProcessingThreshold_1=b.asm.up).apply(null,arguments)},Dr=b._emscripten_bind_btRigidBody_setActivationState_1=function(){return(Dr=b._emscripten_bind_btRigidBody_setActivationState_1=b.asm.vp).apply(null,arguments)},Er=b._emscripten_bind_btRigidBody_forceActivationState_1=function(){return(Er=b._emscripten_bind_btRigidBody_forceActivationState_1=b.asm.wp).apply(null,arguments)},
|
||||
Fr=b._emscripten_bind_btRigidBody_activate_0=function(){return(Fr=b._emscripten_bind_btRigidBody_activate_0=b.asm.xp).apply(null,arguments)},Gr=b._emscripten_bind_btRigidBody_activate_1=function(){return(Gr=b._emscripten_bind_btRigidBody_activate_1=b.asm.yp).apply(null,arguments)},Hr=b._emscripten_bind_btRigidBody_isActive_0=function(){return(Hr=b._emscripten_bind_btRigidBody_isActive_0=b.asm.zp).apply(null,arguments)},Ir=b._emscripten_bind_btRigidBody_isKinematicObject_0=function(){return(Ir=b._emscripten_bind_btRigidBody_isKinematicObject_0=
|
||||
b.asm.Ap).apply(null,arguments)},Jr=b._emscripten_bind_btRigidBody_isStaticObject_0=function(){return(Jr=b._emscripten_bind_btRigidBody_isStaticObject_0=b.asm.Bp).apply(null,arguments)},Kr=b._emscripten_bind_btRigidBody_isStaticOrKinematicObject_0=function(){return(Kr=b._emscripten_bind_btRigidBody_isStaticOrKinematicObject_0=b.asm.Cp).apply(null,arguments)},Lr=b._emscripten_bind_btRigidBody_getRestitution_0=function(){return(Lr=b._emscripten_bind_btRigidBody_getRestitution_0=b.asm.Dp).apply(null,
|
||||
arguments)},Mr=b._emscripten_bind_btRigidBody_getFriction_0=function(){return(Mr=b._emscripten_bind_btRigidBody_getFriction_0=b.asm.Ep).apply(null,arguments)},Nr=b._emscripten_bind_btRigidBody_getRollingFriction_0=function(){return(Nr=b._emscripten_bind_btRigidBody_getRollingFriction_0=b.asm.Fp).apply(null,arguments)},Or=b._emscripten_bind_btRigidBody_setRestitution_1=function(){return(Or=b._emscripten_bind_btRigidBody_setRestitution_1=b.asm.Gp).apply(null,arguments)},Pr=b._emscripten_bind_btRigidBody_setFriction_1=
|
||||
function(){return(Pr=b._emscripten_bind_btRigidBody_setFriction_1=b.asm.Hp).apply(null,arguments)},Qr=b._emscripten_bind_btRigidBody_setRollingFriction_1=function(){return(Qr=b._emscripten_bind_btRigidBody_setRollingFriction_1=b.asm.Ip).apply(null,arguments)},Rr=b._emscripten_bind_btRigidBody_getWorldTransform_0=function(){return(Rr=b._emscripten_bind_btRigidBody_getWorldTransform_0=b.asm.Jp).apply(null,arguments)},Sr=b._emscripten_bind_btRigidBody_getCollisionFlags_0=function(){return(Sr=b._emscripten_bind_btRigidBody_getCollisionFlags_0=
|
||||
b.asm.Kp).apply(null,arguments)},Tr=b._emscripten_bind_btRigidBody_setCollisionFlags_1=function(){return(Tr=b._emscripten_bind_btRigidBody_setCollisionFlags_1=b.asm.Lp).apply(null,arguments)},Ur=b._emscripten_bind_btRigidBody_setWorldTransform_1=function(){return(Ur=b._emscripten_bind_btRigidBody_setWorldTransform_1=b.asm.Mp).apply(null,arguments)},Vr=b._emscripten_bind_btRigidBody_setCollisionShape_1=function(){return(Vr=b._emscripten_bind_btRigidBody_setCollisionShape_1=b.asm.Np).apply(null,arguments)},
|
||||
Wr=b._emscripten_bind_btRigidBody_setCcdMotionThreshold_1=function(){return(Wr=b._emscripten_bind_btRigidBody_setCcdMotionThreshold_1=b.asm.Op).apply(null,arguments)},Xr=b._emscripten_bind_btRigidBody_setCcdSweptSphereRadius_1=function(){return(Xr=b._emscripten_bind_btRigidBody_setCcdSweptSphereRadius_1=b.asm.Pp).apply(null,arguments)},Yr=b._emscripten_bind_btRigidBody_getUserIndex_0=function(){return(Yr=b._emscripten_bind_btRigidBody_getUserIndex_0=b.asm.Qp).apply(null,arguments)},Zr=b._emscripten_bind_btRigidBody_setUserIndex_1=
|
||||
function(){return(Zr=b._emscripten_bind_btRigidBody_setUserIndex_1=b.asm.Rp).apply(null,arguments)},$r=b._emscripten_bind_btRigidBody_getUserPointer_0=function(){return($r=b._emscripten_bind_btRigidBody_getUserPointer_0=b.asm.Sp).apply(null,arguments)},as=b._emscripten_bind_btRigidBody_setUserPointer_1=function(){return(as=b._emscripten_bind_btRigidBody_setUserPointer_1=b.asm.Tp).apply(null,arguments)},bs=b._emscripten_bind_btRigidBody_getBroadphaseHandle_0=function(){return(bs=b._emscripten_bind_btRigidBody_getBroadphaseHandle_0=
|
||||
b.asm.Up).apply(null,arguments)},cs=b._emscripten_bind_btRigidBody___destroy___0=function(){return(cs=b._emscripten_bind_btRigidBody___destroy___0=b.asm.Vp).apply(null,arguments)},ds=b._emscripten_bind_btConstraintSetting_btConstraintSetting_0=function(){return(ds=b._emscripten_bind_btConstraintSetting_btConstraintSetting_0=b.asm.Wp).apply(null,arguments)},es=b._emscripten_bind_btConstraintSetting_get_m_tau_0=function(){return(es=b._emscripten_bind_btConstraintSetting_get_m_tau_0=b.asm.Xp).apply(null,
|
||||
arguments)},gs=b._emscripten_bind_btConstraintSetting_set_m_tau_1=function(){return(gs=b._emscripten_bind_btConstraintSetting_set_m_tau_1=b.asm.Yp).apply(null,arguments)},hs=b._emscripten_bind_btConstraintSetting_get_m_damping_0=function(){return(hs=b._emscripten_bind_btConstraintSetting_get_m_damping_0=b.asm.Zp).apply(null,arguments)},is=b._emscripten_bind_btConstraintSetting_set_m_damping_1=function(){return(is=b._emscripten_bind_btConstraintSetting_set_m_damping_1=b.asm._p).apply(null,arguments)},
|
||||
js=b._emscripten_bind_btConstraintSetting_get_m_impulseClamp_0=function(){return(js=b._emscripten_bind_btConstraintSetting_get_m_impulseClamp_0=b.asm.$p).apply(null,arguments)},ks=b._emscripten_bind_btConstraintSetting_set_m_impulseClamp_1=function(){return(ks=b._emscripten_bind_btConstraintSetting_set_m_impulseClamp_1=b.asm.aq).apply(null,arguments)},ls=b._emscripten_bind_btConstraintSetting___destroy___0=function(){return(ls=b._emscripten_bind_btConstraintSetting___destroy___0=b.asm.bq).apply(null,
|
||||
arguments)},ms=b._emscripten_bind_btPoint2PointConstraint_btPoint2PointConstraint_2=function(){return(ms=b._emscripten_bind_btPoint2PointConstraint_btPoint2PointConstraint_2=b.asm.cq).apply(null,arguments)},ns=b._emscripten_bind_btPoint2PointConstraint_btPoint2PointConstraint_4=function(){return(ns=b._emscripten_bind_btPoint2PointConstraint_btPoint2PointConstraint_4=b.asm.dq).apply(null,arguments)},ps=b._emscripten_bind_btPoint2PointConstraint_setPivotA_1=function(){return(ps=b._emscripten_bind_btPoint2PointConstraint_setPivotA_1=
|
||||
b.asm.eq).apply(null,arguments)},qs=b._emscripten_bind_btPoint2PointConstraint_setPivotB_1=function(){return(qs=b._emscripten_bind_btPoint2PointConstraint_setPivotB_1=b.asm.fq).apply(null,arguments)},rs=b._emscripten_bind_btPoint2PointConstraint_getPivotInA_0=function(){return(rs=b._emscripten_bind_btPoint2PointConstraint_getPivotInA_0=b.asm.gq).apply(null,arguments)},ss=b._emscripten_bind_btPoint2PointConstraint_getPivotInB_0=function(){return(ss=b._emscripten_bind_btPoint2PointConstraint_getPivotInB_0=
|
||||
b.asm.hq).apply(null,arguments)},ts=b._emscripten_bind_btPoint2PointConstraint_enableFeedback_1=function(){return(ts=b._emscripten_bind_btPoint2PointConstraint_enableFeedback_1=b.asm.iq).apply(null,arguments)},us=b._emscripten_bind_btPoint2PointConstraint_getBreakingImpulseThreshold_0=function(){return(us=b._emscripten_bind_btPoint2PointConstraint_getBreakingImpulseThreshold_0=b.asm.jq).apply(null,arguments)},vs=b._emscripten_bind_btPoint2PointConstraint_setBreakingImpulseThreshold_1=function(){return(vs=
|
||||
b._emscripten_bind_btPoint2PointConstraint_setBreakingImpulseThreshold_1=b.asm.kq).apply(null,arguments)},xs=b._emscripten_bind_btPoint2PointConstraint_getParam_2=function(){return(xs=b._emscripten_bind_btPoint2PointConstraint_getParam_2=b.asm.lq).apply(null,arguments)},ys=b._emscripten_bind_btPoint2PointConstraint_setParam_3=function(){return(ys=b._emscripten_bind_btPoint2PointConstraint_setParam_3=b.asm.mq).apply(null,arguments)},zs=b._emscripten_bind_btPoint2PointConstraint_get_m_setting_0=function(){return(zs=
|
||||
b._emscripten_bind_btPoint2PointConstraint_get_m_setting_0=b.asm.nq).apply(null,arguments)},As=b._emscripten_bind_btPoint2PointConstraint_set_m_setting_1=function(){return(As=b._emscripten_bind_btPoint2PointConstraint_set_m_setting_1=b.asm.oq).apply(null,arguments)},Bs=b._emscripten_bind_btPoint2PointConstraint___destroy___0=function(){return(Bs=b._emscripten_bind_btPoint2PointConstraint___destroy___0=b.asm.pq).apply(null,arguments)},Cs=b._emscripten_bind_btGeneric6DofSpringConstraint_btGeneric6DofSpringConstraint_3=
|
||||
function(){return(Cs=b._emscripten_bind_btGeneric6DofSpringConstraint_btGeneric6DofSpringConstraint_3=b.asm.qq).apply(null,arguments)},Ds=b._emscripten_bind_btGeneric6DofSpringConstraint_btGeneric6DofSpringConstraint_5=function(){return(Ds=b._emscripten_bind_btGeneric6DofSpringConstraint_btGeneric6DofSpringConstraint_5=b.asm.rq).apply(null,arguments)},Es=b._emscripten_bind_btGeneric6DofSpringConstraint_enableSpring_2=function(){return(Es=b._emscripten_bind_btGeneric6DofSpringConstraint_enableSpring_2=
|
||||
b.asm.sq).apply(null,arguments)},Fs=b._emscripten_bind_btGeneric6DofSpringConstraint_setStiffness_2=function(){return(Fs=b._emscripten_bind_btGeneric6DofSpringConstraint_setStiffness_2=b.asm.tq).apply(null,arguments)},Gs=b._emscripten_bind_btGeneric6DofSpringConstraint_setDamping_2=function(){return(Gs=b._emscripten_bind_btGeneric6DofSpringConstraint_setDamping_2=b.asm.uq).apply(null,arguments)},Hs=b._emscripten_bind_btGeneric6DofSpringConstraint_setEquilibriumPoint_0=function(){return(Hs=b._emscripten_bind_btGeneric6DofSpringConstraint_setEquilibriumPoint_0=
|
||||
b.asm.vq).apply(null,arguments)},Is=b._emscripten_bind_btGeneric6DofSpringConstraint_setEquilibriumPoint_1=function(){return(Is=b._emscripten_bind_btGeneric6DofSpringConstraint_setEquilibriumPoint_1=b.asm.wq).apply(null,arguments)},Js=b._emscripten_bind_btGeneric6DofSpringConstraint_setEquilibriumPoint_2=function(){return(Js=b._emscripten_bind_btGeneric6DofSpringConstraint_setEquilibriumPoint_2=b.asm.xq).apply(null,arguments)},Ks=b._emscripten_bind_btGeneric6DofSpringConstraint_setLinearLowerLimit_1=
|
||||
function(){return(Ks=b._emscripten_bind_btGeneric6DofSpringConstraint_setLinearLowerLimit_1=b.asm.yq).apply(null,arguments)},Ls=b._emscripten_bind_btGeneric6DofSpringConstraint_setLinearUpperLimit_1=function(){return(Ls=b._emscripten_bind_btGeneric6DofSpringConstraint_setLinearUpperLimit_1=b.asm.zq).apply(null,arguments)},Ms=b._emscripten_bind_btGeneric6DofSpringConstraint_setAngularLowerLimit_1=function(){return(Ms=b._emscripten_bind_btGeneric6DofSpringConstraint_setAngularLowerLimit_1=b.asm.Aq).apply(null,
|
||||
arguments)},Ns=b._emscripten_bind_btGeneric6DofSpringConstraint_setAngularUpperLimit_1=function(){return(Ns=b._emscripten_bind_btGeneric6DofSpringConstraint_setAngularUpperLimit_1=b.asm.Bq).apply(null,arguments)},Os=b._emscripten_bind_btGeneric6DofSpringConstraint_getFrameOffsetA_0=function(){return(Os=b._emscripten_bind_btGeneric6DofSpringConstraint_getFrameOffsetA_0=b.asm.Cq).apply(null,arguments)},Ps=b._emscripten_bind_btGeneric6DofSpringConstraint_enableFeedback_1=function(){return(Ps=b._emscripten_bind_btGeneric6DofSpringConstraint_enableFeedback_1=
|
||||
b.asm.Dq).apply(null,arguments)},Qs=b._emscripten_bind_btGeneric6DofSpringConstraint_getBreakingImpulseThreshold_0=function(){return(Qs=b._emscripten_bind_btGeneric6DofSpringConstraint_getBreakingImpulseThreshold_0=b.asm.Eq).apply(null,arguments)},Rs=b._emscripten_bind_btGeneric6DofSpringConstraint_setBreakingImpulseThreshold_1=function(){return(Rs=b._emscripten_bind_btGeneric6DofSpringConstraint_setBreakingImpulseThreshold_1=b.asm.Fq).apply(null,arguments)},Ss=b._emscripten_bind_btGeneric6DofSpringConstraint_getParam_2=
|
||||
function(){return(Ss=b._emscripten_bind_btGeneric6DofSpringConstraint_getParam_2=b.asm.Gq).apply(null,arguments)},Ts=b._emscripten_bind_btGeneric6DofSpringConstraint_setParam_3=function(){return(Ts=b._emscripten_bind_btGeneric6DofSpringConstraint_setParam_3=b.asm.Hq).apply(null,arguments)},Us=b._emscripten_bind_btGeneric6DofSpringConstraint___destroy___0=function(){return(Us=b._emscripten_bind_btGeneric6DofSpringConstraint___destroy___0=b.asm.Iq).apply(null,arguments)},Vs=b._emscripten_bind_btSequentialImpulseConstraintSolver_btSequentialImpulseConstraintSolver_0=
|
||||
function(){return(Vs=b._emscripten_bind_btSequentialImpulseConstraintSolver_btSequentialImpulseConstraintSolver_0=b.asm.Jq).apply(null,arguments)},Ws=b._emscripten_bind_btSequentialImpulseConstraintSolver___destroy___0=function(){return(Ws=b._emscripten_bind_btSequentialImpulseConstraintSolver___destroy___0=b.asm.Kq).apply(null,arguments)},Xs=b._emscripten_bind_btConeTwistConstraint_btConeTwistConstraint_2=function(){return(Xs=b._emscripten_bind_btConeTwistConstraint_btConeTwistConstraint_2=b.asm.Lq).apply(null,
|
||||
arguments)},Ys=b._emscripten_bind_btConeTwistConstraint_btConeTwistConstraint_4=function(){return(Ys=b._emscripten_bind_btConeTwistConstraint_btConeTwistConstraint_4=b.asm.Mq).apply(null,arguments)},Zs=b._emscripten_bind_btConeTwistConstraint_setLimit_2=function(){return(Zs=b._emscripten_bind_btConeTwistConstraint_setLimit_2=b.asm.Nq).apply(null,arguments)},$s=b._emscripten_bind_btConeTwistConstraint_setAngularOnly_1=function(){return($s=b._emscripten_bind_btConeTwistConstraint_setAngularOnly_1=b.asm.Oq).apply(null,
|
||||
arguments)},at=b._emscripten_bind_btConeTwistConstraint_setDamping_1=function(){return(at=b._emscripten_bind_btConeTwistConstraint_setDamping_1=b.asm.Pq).apply(null,arguments)},bt=b._emscripten_bind_btConeTwistConstraint_enableMotor_1=function(){return(bt=b._emscripten_bind_btConeTwistConstraint_enableMotor_1=b.asm.Qq).apply(null,arguments)},ct=b._emscripten_bind_btConeTwistConstraint_setMaxMotorImpulse_1=function(){return(ct=b._emscripten_bind_btConeTwistConstraint_setMaxMotorImpulse_1=b.asm.Rq).apply(null,
|
||||
arguments)},dt=b._emscripten_bind_btConeTwistConstraint_setMaxMotorImpulseNormalized_1=function(){return(dt=b._emscripten_bind_btConeTwistConstraint_setMaxMotorImpulseNormalized_1=b.asm.Sq).apply(null,arguments)},et=b._emscripten_bind_btConeTwistConstraint_setMotorTarget_1=function(){return(et=b._emscripten_bind_btConeTwistConstraint_setMotorTarget_1=b.asm.Tq).apply(null,arguments)},ft=b._emscripten_bind_btConeTwistConstraint_setMotorTargetInConstraintSpace_1=function(){return(ft=b._emscripten_bind_btConeTwistConstraint_setMotorTargetInConstraintSpace_1=
|
||||
b.asm.Uq).apply(null,arguments)},gt=b._emscripten_bind_btConeTwistConstraint_enableFeedback_1=function(){return(gt=b._emscripten_bind_btConeTwistConstraint_enableFeedback_1=b.asm.Vq).apply(null,arguments)},ht=b._emscripten_bind_btConeTwistConstraint_getBreakingImpulseThreshold_0=function(){return(ht=b._emscripten_bind_btConeTwistConstraint_getBreakingImpulseThreshold_0=b.asm.Wq).apply(null,arguments)},it=b._emscripten_bind_btConeTwistConstraint_setBreakingImpulseThreshold_1=function(){return(it=b._emscripten_bind_btConeTwistConstraint_setBreakingImpulseThreshold_1=
|
||||
b.asm.Xq).apply(null,arguments)},jt=b._emscripten_bind_btConeTwistConstraint_getParam_2=function(){return(jt=b._emscripten_bind_btConeTwistConstraint_getParam_2=b.asm.Yq).apply(null,arguments)},kt=b._emscripten_bind_btConeTwistConstraint_setParam_3=function(){return(kt=b._emscripten_bind_btConeTwistConstraint_setParam_3=b.asm.Zq).apply(null,arguments)},lt=b._emscripten_bind_btConeTwistConstraint___destroy___0=function(){return(lt=b._emscripten_bind_btConeTwistConstraint___destroy___0=b.asm._q).apply(null,
|
||||
arguments)},mt=b._emscripten_bind_btHingeConstraint_btHingeConstraint_2=function(){return(mt=b._emscripten_bind_btHingeConstraint_btHingeConstraint_2=b.asm.$q).apply(null,arguments)},nt=b._emscripten_bind_btHingeConstraint_btHingeConstraint_3=function(){return(nt=b._emscripten_bind_btHingeConstraint_btHingeConstraint_3=b.asm.ar).apply(null,arguments)},ot=b._emscripten_bind_btHingeConstraint_btHingeConstraint_4=function(){return(ot=b._emscripten_bind_btHingeConstraint_btHingeConstraint_4=b.asm.br).apply(null,
|
||||
arguments)},pt=b._emscripten_bind_btHingeConstraint_btHingeConstraint_5=function(){return(pt=b._emscripten_bind_btHingeConstraint_btHingeConstraint_5=b.asm.cr).apply(null,arguments)},qt=b._emscripten_bind_btHingeConstraint_btHingeConstraint_6=function(){return(qt=b._emscripten_bind_btHingeConstraint_btHingeConstraint_6=b.asm.dr).apply(null,arguments)},rt=b._emscripten_bind_btHingeConstraint_btHingeConstraint_7=function(){return(rt=b._emscripten_bind_btHingeConstraint_btHingeConstraint_7=b.asm.er).apply(null,
|
||||
arguments)},st=b._emscripten_bind_btHingeConstraint_getHingeAngle_0=function(){return(st=b._emscripten_bind_btHingeConstraint_getHingeAngle_0=b.asm.fr).apply(null,arguments)},tt=b._emscripten_bind_btHingeConstraint_setLimit_4=function(){return(tt=b._emscripten_bind_btHingeConstraint_setLimit_4=b.asm.gr).apply(null,arguments)},ut=b._emscripten_bind_btHingeConstraint_setLimit_5=function(){return(ut=b._emscripten_bind_btHingeConstraint_setLimit_5=b.asm.hr).apply(null,arguments)},vt=b._emscripten_bind_btHingeConstraint_enableAngularMotor_3=
|
||||
function(){return(vt=b._emscripten_bind_btHingeConstraint_enableAngularMotor_3=b.asm.ir).apply(null,arguments)},wt=b._emscripten_bind_btHingeConstraint_setAngularOnly_1=function(){return(wt=b._emscripten_bind_btHingeConstraint_setAngularOnly_1=b.asm.jr).apply(null,arguments)},xt=b._emscripten_bind_btHingeConstraint_enableMotor_1=function(){return(xt=b._emscripten_bind_btHingeConstraint_enableMotor_1=b.asm.kr).apply(null,arguments)},yt=b._emscripten_bind_btHingeConstraint_setMaxMotorImpulse_1=function(){return(yt=
|
||||
b._emscripten_bind_btHingeConstraint_setMaxMotorImpulse_1=b.asm.lr).apply(null,arguments)},zt=b._emscripten_bind_btHingeConstraint_setMotorTarget_2=function(){return(zt=b._emscripten_bind_btHingeConstraint_setMotorTarget_2=b.asm.mr).apply(null,arguments)},At=b._emscripten_bind_btHingeConstraint_enableFeedback_1=function(){return(At=b._emscripten_bind_btHingeConstraint_enableFeedback_1=b.asm.nr).apply(null,arguments)},Bt=b._emscripten_bind_btHingeConstraint_getBreakingImpulseThreshold_0=function(){return(Bt=
|
||||
b._emscripten_bind_btHingeConstraint_getBreakingImpulseThreshold_0=b.asm.or).apply(null,arguments)},Ct=b._emscripten_bind_btHingeConstraint_setBreakingImpulseThreshold_1=function(){return(Ct=b._emscripten_bind_btHingeConstraint_setBreakingImpulseThreshold_1=b.asm.pr).apply(null,arguments)},Dt=b._emscripten_bind_btHingeConstraint_getParam_2=function(){return(Dt=b._emscripten_bind_btHingeConstraint_getParam_2=b.asm.qr).apply(null,arguments)},Et=b._emscripten_bind_btHingeConstraint_setParam_3=function(){return(Et=
|
||||
b._emscripten_bind_btHingeConstraint_setParam_3=b.asm.rr).apply(null,arguments)},Ft=b._emscripten_bind_btHingeConstraint___destroy___0=function(){return(Ft=b._emscripten_bind_btHingeConstraint___destroy___0=b.asm.sr).apply(null,arguments)},Gt=b._emscripten_bind_btSliderConstraint_btSliderConstraint_3=function(){return(Gt=b._emscripten_bind_btSliderConstraint_btSliderConstraint_3=b.asm.tr).apply(null,arguments)},Ht=b._emscripten_bind_btSliderConstraint_btSliderConstraint_5=function(){return(Ht=b._emscripten_bind_btSliderConstraint_btSliderConstraint_5=
|
||||
b.asm.ur).apply(null,arguments)},It=b._emscripten_bind_btSliderConstraint_getLinearPos_0=function(){return(It=b._emscripten_bind_btSliderConstraint_getLinearPos_0=b.asm.vr).apply(null,arguments)},Jt=b._emscripten_bind_btSliderConstraint_getAngularPos_0=function(){return(Jt=b._emscripten_bind_btSliderConstraint_getAngularPos_0=b.asm.wr).apply(null,arguments)},Kt=b._emscripten_bind_btSliderConstraint_setLowerLinLimit_1=function(){return(Kt=b._emscripten_bind_btSliderConstraint_setLowerLinLimit_1=b.asm.xr).apply(null,
|
||||
arguments)},Lt=b._emscripten_bind_btSliderConstraint_setUpperLinLimit_1=function(){return(Lt=b._emscripten_bind_btSliderConstraint_setUpperLinLimit_1=b.asm.yr).apply(null,arguments)},Mt=b._emscripten_bind_btSliderConstraint_setLowerAngLimit_1=function(){return(Mt=b._emscripten_bind_btSliderConstraint_setLowerAngLimit_1=b.asm.zr).apply(null,arguments)},Nt=b._emscripten_bind_btSliderConstraint_setUpperAngLimit_1=function(){return(Nt=b._emscripten_bind_btSliderConstraint_setUpperAngLimit_1=b.asm.Ar).apply(null,
|
||||
arguments)},Ot=b._emscripten_bind_btSliderConstraint_setPoweredLinMotor_1=function(){return(Ot=b._emscripten_bind_btSliderConstraint_setPoweredLinMotor_1=b.asm.Br).apply(null,arguments)},Pt=b._emscripten_bind_btSliderConstraint_setMaxLinMotorForce_1=function(){return(Pt=b._emscripten_bind_btSliderConstraint_setMaxLinMotorForce_1=b.asm.Cr).apply(null,arguments)},Qt=b._emscripten_bind_btSliderConstraint_setTargetLinMotorVelocity_1=function(){return(Qt=b._emscripten_bind_btSliderConstraint_setTargetLinMotorVelocity_1=
|
||||
b.asm.Dr).apply(null,arguments)},Rt=b._emscripten_bind_btSliderConstraint_enableFeedback_1=function(){return(Rt=b._emscripten_bind_btSliderConstraint_enableFeedback_1=b.asm.Er).apply(null,arguments)},St=b._emscripten_bind_btSliderConstraint_getBreakingImpulseThreshold_0=function(){return(St=b._emscripten_bind_btSliderConstraint_getBreakingImpulseThreshold_0=b.asm.Fr).apply(null,arguments)},Tt=b._emscripten_bind_btSliderConstraint_setBreakingImpulseThreshold_1=function(){return(Tt=b._emscripten_bind_btSliderConstraint_setBreakingImpulseThreshold_1=
|
||||
b.asm.Gr).apply(null,arguments)},Ut=b._emscripten_bind_btSliderConstraint_getParam_2=function(){return(Ut=b._emscripten_bind_btSliderConstraint_getParam_2=b.asm.Hr).apply(null,arguments)},Vt=b._emscripten_bind_btSliderConstraint_setParam_3=function(){return(Vt=b._emscripten_bind_btSliderConstraint_setParam_3=b.asm.Ir).apply(null,arguments)},Wt=b._emscripten_bind_btSliderConstraint___destroy___0=function(){return(Wt=b._emscripten_bind_btSliderConstraint___destroy___0=b.asm.Jr).apply(null,arguments)},
|
||||
Xt=b._emscripten_bind_btFixedConstraint_btFixedConstraint_4=function(){return(Xt=b._emscripten_bind_btFixedConstraint_btFixedConstraint_4=b.asm.Kr).apply(null,arguments)},Yt=b._emscripten_bind_btFixedConstraint_enableFeedback_1=function(){return(Yt=b._emscripten_bind_btFixedConstraint_enableFeedback_1=b.asm.Lr).apply(null,arguments)},Zt=b._emscripten_bind_btFixedConstraint_getBreakingImpulseThreshold_0=function(){return(Zt=b._emscripten_bind_btFixedConstraint_getBreakingImpulseThreshold_0=b.asm.Mr).apply(null,
|
||||
arguments)},$t=b._emscripten_bind_btFixedConstraint_setBreakingImpulseThreshold_1=function(){return($t=b._emscripten_bind_btFixedConstraint_setBreakingImpulseThreshold_1=b.asm.Nr).apply(null,arguments)},au=b._emscripten_bind_btFixedConstraint_getParam_2=function(){return(au=b._emscripten_bind_btFixedConstraint_getParam_2=b.asm.Or).apply(null,arguments)},bu=b._emscripten_bind_btFixedConstraint_setParam_3=function(){return(bu=b._emscripten_bind_btFixedConstraint_setParam_3=b.asm.Pr).apply(null,arguments)},
|
||||
cu=b._emscripten_bind_btFixedConstraint___destroy___0=function(){return(cu=b._emscripten_bind_btFixedConstraint___destroy___0=b.asm.Qr).apply(null,arguments)},du=b._emscripten_bind_btConstraintSolver___destroy___0=function(){return(du=b._emscripten_bind_btConstraintSolver___destroy___0=b.asm.Rr).apply(null,arguments)},eu=b._emscripten_bind_btDispatcherInfo_get_m_timeStep_0=function(){return(eu=b._emscripten_bind_btDispatcherInfo_get_m_timeStep_0=b.asm.Sr).apply(null,arguments)},fu=b._emscripten_bind_btDispatcherInfo_set_m_timeStep_1=
|
||||
function(){return(fu=b._emscripten_bind_btDispatcherInfo_set_m_timeStep_1=b.asm.Tr).apply(null,arguments)},gu=b._emscripten_bind_btDispatcherInfo_get_m_stepCount_0=function(){return(gu=b._emscripten_bind_btDispatcherInfo_get_m_stepCount_0=b.asm.Ur).apply(null,arguments)},hu=b._emscripten_bind_btDispatcherInfo_set_m_stepCount_1=function(){return(hu=b._emscripten_bind_btDispatcherInfo_set_m_stepCount_1=b.asm.Vr).apply(null,arguments)},iu=b._emscripten_bind_btDispatcherInfo_get_m_dispatchFunc_0=function(){return(iu=
|
||||
b._emscripten_bind_btDispatcherInfo_get_m_dispatchFunc_0=b.asm.Wr).apply(null,arguments)},ju=b._emscripten_bind_btDispatcherInfo_set_m_dispatchFunc_1=function(){return(ju=b._emscripten_bind_btDispatcherInfo_set_m_dispatchFunc_1=b.asm.Xr).apply(null,arguments)},ku=b._emscripten_bind_btDispatcherInfo_get_m_timeOfImpact_0=function(){return(ku=b._emscripten_bind_btDispatcherInfo_get_m_timeOfImpact_0=b.asm.Yr).apply(null,arguments)},lu=b._emscripten_bind_btDispatcherInfo_set_m_timeOfImpact_1=function(){return(lu=
|
||||
b._emscripten_bind_btDispatcherInfo_set_m_timeOfImpact_1=b.asm.Zr).apply(null,arguments)},mu=b._emscripten_bind_btDispatcherInfo_get_m_useContinuous_0=function(){return(mu=b._emscripten_bind_btDispatcherInfo_get_m_useContinuous_0=b.asm._r).apply(null,arguments)},nu=b._emscripten_bind_btDispatcherInfo_set_m_useContinuous_1=function(){return(nu=b._emscripten_bind_btDispatcherInfo_set_m_useContinuous_1=b.asm.$r).apply(null,arguments)},ou=b._emscripten_bind_btDispatcherInfo_get_m_enableSatConvex_0=function(){return(ou=
|
||||
b._emscripten_bind_btDispatcherInfo_get_m_enableSatConvex_0=b.asm.as).apply(null,arguments)},pu=b._emscripten_bind_btDispatcherInfo_set_m_enableSatConvex_1=function(){return(pu=b._emscripten_bind_btDispatcherInfo_set_m_enableSatConvex_1=b.asm.bs).apply(null,arguments)},qu=b._emscripten_bind_btDispatcherInfo_get_m_enableSPU_0=function(){return(qu=b._emscripten_bind_btDispatcherInfo_get_m_enableSPU_0=b.asm.cs).apply(null,arguments)},ru=b._emscripten_bind_btDispatcherInfo_set_m_enableSPU_1=function(){return(ru=
|
||||
b._emscripten_bind_btDispatcherInfo_set_m_enableSPU_1=b.asm.ds).apply(null,arguments)},su=b._emscripten_bind_btDispatcherInfo_get_m_useEpa_0=function(){return(su=b._emscripten_bind_btDispatcherInfo_get_m_useEpa_0=b.asm.es).apply(null,arguments)},tu=b._emscripten_bind_btDispatcherInfo_set_m_useEpa_1=function(){return(tu=b._emscripten_bind_btDispatcherInfo_set_m_useEpa_1=b.asm.fs).apply(null,arguments)},uu=b._emscripten_bind_btDispatcherInfo_get_m_allowedCcdPenetration_0=function(){return(uu=b._emscripten_bind_btDispatcherInfo_get_m_allowedCcdPenetration_0=
|
||||
b.asm.gs).apply(null,arguments)},vu=b._emscripten_bind_btDispatcherInfo_set_m_allowedCcdPenetration_1=function(){return(vu=b._emscripten_bind_btDispatcherInfo_set_m_allowedCcdPenetration_1=b.asm.hs).apply(null,arguments)},wu=b._emscripten_bind_btDispatcherInfo_get_m_useConvexConservativeDistanceUtil_0=function(){return(wu=b._emscripten_bind_btDispatcherInfo_get_m_useConvexConservativeDistanceUtil_0=b.asm.is).apply(null,arguments)},xu=b._emscripten_bind_btDispatcherInfo_set_m_useConvexConservativeDistanceUtil_1=
|
||||
function(){return(xu=b._emscripten_bind_btDispatcherInfo_set_m_useConvexConservativeDistanceUtil_1=b.asm.js).apply(null,arguments)},yu=b._emscripten_bind_btDispatcherInfo_get_m_convexConservativeDistanceThreshold_0=function(){return(yu=b._emscripten_bind_btDispatcherInfo_get_m_convexConservativeDistanceThreshold_0=b.asm.ks).apply(null,arguments)},zu=b._emscripten_bind_btDispatcherInfo_set_m_convexConservativeDistanceThreshold_1=function(){return(zu=b._emscripten_bind_btDispatcherInfo_set_m_convexConservativeDistanceThreshold_1=
|
||||
b.asm.ls).apply(null,arguments)},Au=b._emscripten_bind_btDispatcherInfo___destroy___0=function(){return(Au=b._emscripten_bind_btDispatcherInfo___destroy___0=b.asm.ms).apply(null,arguments)},Bu=b._emscripten_bind_btContactSolverInfo_get_m_splitImpulse_0=function(){return(Bu=b._emscripten_bind_btContactSolverInfo_get_m_splitImpulse_0=b.asm.ns).apply(null,arguments)},Cu=b._emscripten_bind_btContactSolverInfo_set_m_splitImpulse_1=function(){return(Cu=b._emscripten_bind_btContactSolverInfo_set_m_splitImpulse_1=
|
||||
b.asm.os).apply(null,arguments)},Du=b._emscripten_bind_btContactSolverInfo_get_m_splitImpulsePenetrationThreshold_0=function(){return(Du=b._emscripten_bind_btContactSolverInfo_get_m_splitImpulsePenetrationThreshold_0=b.asm.ps).apply(null,arguments)},Eu=b._emscripten_bind_btContactSolverInfo_set_m_splitImpulsePenetrationThreshold_1=function(){return(Eu=b._emscripten_bind_btContactSolverInfo_set_m_splitImpulsePenetrationThreshold_1=b.asm.qs).apply(null,arguments)},Fu=b._emscripten_bind_btContactSolverInfo_get_m_numIterations_0=
|
||||
function(){return(Fu=b._emscripten_bind_btContactSolverInfo_get_m_numIterations_0=b.asm.rs).apply(null,arguments)},Gu=b._emscripten_bind_btContactSolverInfo_set_m_numIterations_1=function(){return(Gu=b._emscripten_bind_btContactSolverInfo_set_m_numIterations_1=b.asm.ss).apply(null,arguments)},Hu=b._emscripten_bind_btContactSolverInfo___destroy___0=function(){return(Hu=b._emscripten_bind_btContactSolverInfo___destroy___0=b.asm.ts).apply(null,arguments)},Iu=b._emscripten_bind_btVehicleTuning_btVehicleTuning_0=
|
||||
function(){return(Iu=b._emscripten_bind_btVehicleTuning_btVehicleTuning_0=b.asm.us).apply(null,arguments)},Ju=b._emscripten_bind_btVehicleTuning_get_m_suspensionStiffness_0=function(){return(Ju=b._emscripten_bind_btVehicleTuning_get_m_suspensionStiffness_0=b.asm.vs).apply(null,arguments)},Ku=b._emscripten_bind_btVehicleTuning_set_m_suspensionStiffness_1=function(){return(Ku=b._emscripten_bind_btVehicleTuning_set_m_suspensionStiffness_1=b.asm.ws).apply(null,arguments)},Lu=b._emscripten_bind_btVehicleTuning_get_m_suspensionCompression_0=
|
||||
function(){return(Lu=b._emscripten_bind_btVehicleTuning_get_m_suspensionCompression_0=b.asm.xs).apply(null,arguments)},Mu=b._emscripten_bind_btVehicleTuning_set_m_suspensionCompression_1=function(){return(Mu=b._emscripten_bind_btVehicleTuning_set_m_suspensionCompression_1=b.asm.ys).apply(null,arguments)},Nu=b._emscripten_bind_btVehicleTuning_get_m_suspensionDamping_0=function(){return(Nu=b._emscripten_bind_btVehicleTuning_get_m_suspensionDamping_0=b.asm.zs).apply(null,arguments)},Ou=b._emscripten_bind_btVehicleTuning_set_m_suspensionDamping_1=
|
||||
function(){return(Ou=b._emscripten_bind_btVehicleTuning_set_m_suspensionDamping_1=b.asm.As).apply(null,arguments)},Pu=b._emscripten_bind_btVehicleTuning_get_m_maxSuspensionTravelCm_0=function(){return(Pu=b._emscripten_bind_btVehicleTuning_get_m_maxSuspensionTravelCm_0=b.asm.Bs).apply(null,arguments)},Qu=b._emscripten_bind_btVehicleTuning_set_m_maxSuspensionTravelCm_1=function(){return(Qu=b._emscripten_bind_btVehicleTuning_set_m_maxSuspensionTravelCm_1=b.asm.Cs).apply(null,arguments)},Ru=b._emscripten_bind_btVehicleTuning_get_m_frictionSlip_0=
|
||||
function(){return(Ru=b._emscripten_bind_btVehicleTuning_get_m_frictionSlip_0=b.asm.Ds).apply(null,arguments)},Su=b._emscripten_bind_btVehicleTuning_set_m_frictionSlip_1=function(){return(Su=b._emscripten_bind_btVehicleTuning_set_m_frictionSlip_1=b.asm.Es).apply(null,arguments)},Tu=b._emscripten_bind_btVehicleTuning_get_m_maxSuspensionForce_0=function(){return(Tu=b._emscripten_bind_btVehicleTuning_get_m_maxSuspensionForce_0=b.asm.Fs).apply(null,arguments)},Uu=b._emscripten_bind_btVehicleTuning_set_m_maxSuspensionForce_1=
|
||||
function(){return(Uu=b._emscripten_bind_btVehicleTuning_set_m_maxSuspensionForce_1=b.asm.Gs).apply(null,arguments)},Vu=b._emscripten_bind_btVehicleRaycasterResult_get_m_hitPointInWorld_0=function(){return(Vu=b._emscripten_bind_btVehicleRaycasterResult_get_m_hitPointInWorld_0=b.asm.Hs).apply(null,arguments)},Wu=b._emscripten_bind_btVehicleRaycasterResult_set_m_hitPointInWorld_1=function(){return(Wu=b._emscripten_bind_btVehicleRaycasterResult_set_m_hitPointInWorld_1=b.asm.Is).apply(null,arguments)},
|
||||
Xu=b._emscripten_bind_btVehicleRaycasterResult_get_m_hitNormalInWorld_0=function(){return(Xu=b._emscripten_bind_btVehicleRaycasterResult_get_m_hitNormalInWorld_0=b.asm.Js).apply(null,arguments)},Yu=b._emscripten_bind_btVehicleRaycasterResult_set_m_hitNormalInWorld_1=function(){return(Yu=b._emscripten_bind_btVehicleRaycasterResult_set_m_hitNormalInWorld_1=b.asm.Ks).apply(null,arguments)},Zu=b._emscripten_bind_btVehicleRaycasterResult_get_m_distFraction_0=function(){return(Zu=b._emscripten_bind_btVehicleRaycasterResult_get_m_distFraction_0=
|
||||
b.asm.Ls).apply(null,arguments)},$u=b._emscripten_bind_btVehicleRaycasterResult_set_m_distFraction_1=function(){return($u=b._emscripten_bind_btVehicleRaycasterResult_set_m_distFraction_1=b.asm.Ms).apply(null,arguments)},av=b._emscripten_bind_btVehicleRaycasterResult___destroy___0=function(){return(av=b._emscripten_bind_btVehicleRaycasterResult___destroy___0=b.asm.Ns).apply(null,arguments)},bv=b._emscripten_bind_btDefaultVehicleRaycaster_btDefaultVehicleRaycaster_1=function(){return(bv=b._emscripten_bind_btDefaultVehicleRaycaster_btDefaultVehicleRaycaster_1=
|
||||
b.asm.Os).apply(null,arguments)},cv=b._emscripten_bind_btDefaultVehicleRaycaster_castRay_3=function(){return(cv=b._emscripten_bind_btDefaultVehicleRaycaster_castRay_3=b.asm.Ps).apply(null,arguments)},dv=b._emscripten_bind_btDefaultVehicleRaycaster___destroy___0=function(){return(dv=b._emscripten_bind_btDefaultVehicleRaycaster___destroy___0=b.asm.Qs).apply(null,arguments)},ev=b._emscripten_bind_RaycastInfo_get_m_contactNormalWS_0=function(){return(ev=b._emscripten_bind_RaycastInfo_get_m_contactNormalWS_0=
|
||||
b.asm.Rs).apply(null,arguments)},fv=b._emscripten_bind_RaycastInfo_set_m_contactNormalWS_1=function(){return(fv=b._emscripten_bind_RaycastInfo_set_m_contactNormalWS_1=b.asm.Ss).apply(null,arguments)},gv=b._emscripten_bind_RaycastInfo_get_m_contactPointWS_0=function(){return(gv=b._emscripten_bind_RaycastInfo_get_m_contactPointWS_0=b.asm.Ts).apply(null,arguments)},hv=b._emscripten_bind_RaycastInfo_set_m_contactPointWS_1=function(){return(hv=b._emscripten_bind_RaycastInfo_set_m_contactPointWS_1=b.asm.Us).apply(null,
|
||||
arguments)},iv=b._emscripten_bind_RaycastInfo_get_m_suspensionLength_0=function(){return(iv=b._emscripten_bind_RaycastInfo_get_m_suspensionLength_0=b.asm.Vs).apply(null,arguments)},jv=b._emscripten_bind_RaycastInfo_set_m_suspensionLength_1=function(){return(jv=b._emscripten_bind_RaycastInfo_set_m_suspensionLength_1=b.asm.Ws).apply(null,arguments)},kv=b._emscripten_bind_RaycastInfo_get_m_hardPointWS_0=function(){return(kv=b._emscripten_bind_RaycastInfo_get_m_hardPointWS_0=b.asm.Xs).apply(null,arguments)},
|
||||
lv=b._emscripten_bind_RaycastInfo_set_m_hardPointWS_1=function(){return(lv=b._emscripten_bind_RaycastInfo_set_m_hardPointWS_1=b.asm.Ys).apply(null,arguments)},mv=b._emscripten_bind_RaycastInfo_get_m_wheelDirectionWS_0=function(){return(mv=b._emscripten_bind_RaycastInfo_get_m_wheelDirectionWS_0=b.asm.Zs).apply(null,arguments)},nv=b._emscripten_bind_RaycastInfo_set_m_wheelDirectionWS_1=function(){return(nv=b._emscripten_bind_RaycastInfo_set_m_wheelDirectionWS_1=b.asm._s).apply(null,arguments)},ov=b._emscripten_bind_RaycastInfo_get_m_wheelAxleWS_0=
|
||||
function(){return(ov=b._emscripten_bind_RaycastInfo_get_m_wheelAxleWS_0=b.asm.$s).apply(null,arguments)},pv=b._emscripten_bind_RaycastInfo_set_m_wheelAxleWS_1=function(){return(pv=b._emscripten_bind_RaycastInfo_set_m_wheelAxleWS_1=b.asm.at).apply(null,arguments)},qv=b._emscripten_bind_RaycastInfo_get_m_isInContact_0=function(){return(qv=b._emscripten_bind_RaycastInfo_get_m_isInContact_0=b.asm.bt).apply(null,arguments)},rv=b._emscripten_bind_RaycastInfo_set_m_isInContact_1=function(){return(rv=b._emscripten_bind_RaycastInfo_set_m_isInContact_1=
|
||||
b.asm.ct).apply(null,arguments)},sv=b._emscripten_bind_RaycastInfo_get_m_groundObject_0=function(){return(sv=b._emscripten_bind_RaycastInfo_get_m_groundObject_0=b.asm.dt).apply(null,arguments)},tv=b._emscripten_bind_RaycastInfo_set_m_groundObject_1=function(){return(tv=b._emscripten_bind_RaycastInfo_set_m_groundObject_1=b.asm.et).apply(null,arguments)},uv=b._emscripten_bind_RaycastInfo___destroy___0=function(){return(uv=b._emscripten_bind_RaycastInfo___destroy___0=b.asm.ft).apply(null,arguments)},
|
||||
vv=b._emscripten_bind_btWheelInfoConstructionInfo_get_m_chassisConnectionCS_0=function(){return(vv=b._emscripten_bind_btWheelInfoConstructionInfo_get_m_chassisConnectionCS_0=b.asm.gt).apply(null,arguments)},wv=b._emscripten_bind_btWheelInfoConstructionInfo_set_m_chassisConnectionCS_1=function(){return(wv=b._emscripten_bind_btWheelInfoConstructionInfo_set_m_chassisConnectionCS_1=b.asm.ht).apply(null,arguments)},xv=b._emscripten_bind_btWheelInfoConstructionInfo_get_m_wheelDirectionCS_0=function(){return(xv=
|
||||
b._emscripten_bind_btWheelInfoConstructionInfo_get_m_wheelDirectionCS_0=b.asm.it).apply(null,arguments)},yv=b._emscripten_bind_btWheelInfoConstructionInfo_set_m_wheelDirectionCS_1=function(){return(yv=b._emscripten_bind_btWheelInfoConstructionInfo_set_m_wheelDirectionCS_1=b.asm.jt).apply(null,arguments)},zv=b._emscripten_bind_btWheelInfoConstructionInfo_get_m_wheelAxleCS_0=function(){return(zv=b._emscripten_bind_btWheelInfoConstructionInfo_get_m_wheelAxleCS_0=b.asm.kt).apply(null,arguments)},Av=b._emscripten_bind_btWheelInfoConstructionInfo_set_m_wheelAxleCS_1=
|
||||
function(){return(Av=b._emscripten_bind_btWheelInfoConstructionInfo_set_m_wheelAxleCS_1=b.asm.lt).apply(null,arguments)},Bv=b._emscripten_bind_btWheelInfoConstructionInfo_get_m_suspensionRestLength_0=function(){return(Bv=b._emscripten_bind_btWheelInfoConstructionInfo_get_m_suspensionRestLength_0=b.asm.mt).apply(null,arguments)},Cv=b._emscripten_bind_btWheelInfoConstructionInfo_set_m_suspensionRestLength_1=function(){return(Cv=b._emscripten_bind_btWheelInfoConstructionInfo_set_m_suspensionRestLength_1=
|
||||
b.asm.nt).apply(null,arguments)},Dv=b._emscripten_bind_btWheelInfoConstructionInfo_get_m_maxSuspensionTravelCm_0=function(){return(Dv=b._emscripten_bind_btWheelInfoConstructionInfo_get_m_maxSuspensionTravelCm_0=b.asm.ot).apply(null,arguments)},Ev=b._emscripten_bind_btWheelInfoConstructionInfo_set_m_maxSuspensionTravelCm_1=function(){return(Ev=b._emscripten_bind_btWheelInfoConstructionInfo_set_m_maxSuspensionTravelCm_1=b.asm.pt).apply(null,arguments)},Fv=b._emscripten_bind_btWheelInfoConstructionInfo_get_m_wheelRadius_0=
|
||||
function(){return(Fv=b._emscripten_bind_btWheelInfoConstructionInfo_get_m_wheelRadius_0=b.asm.qt).apply(null,arguments)},Gv=b._emscripten_bind_btWheelInfoConstructionInfo_set_m_wheelRadius_1=function(){return(Gv=b._emscripten_bind_btWheelInfoConstructionInfo_set_m_wheelRadius_1=b.asm.rt).apply(null,arguments)},Hv=b._emscripten_bind_btWheelInfoConstructionInfo_get_m_suspensionStiffness_0=function(){return(Hv=b._emscripten_bind_btWheelInfoConstructionInfo_get_m_suspensionStiffness_0=b.asm.st).apply(null,
|
||||
arguments)},Iv=b._emscripten_bind_btWheelInfoConstructionInfo_set_m_suspensionStiffness_1=function(){return(Iv=b._emscripten_bind_btWheelInfoConstructionInfo_set_m_suspensionStiffness_1=b.asm.tt).apply(null,arguments)},Jv=b._emscripten_bind_btWheelInfoConstructionInfo_get_m_wheelsDampingCompression_0=function(){return(Jv=b._emscripten_bind_btWheelInfoConstructionInfo_get_m_wheelsDampingCompression_0=b.asm.ut).apply(null,arguments)},Kv=b._emscripten_bind_btWheelInfoConstructionInfo_set_m_wheelsDampingCompression_1=
|
||||
function(){return(Kv=b._emscripten_bind_btWheelInfoConstructionInfo_set_m_wheelsDampingCompression_1=b.asm.vt).apply(null,arguments)},Lv=b._emscripten_bind_btWheelInfoConstructionInfo_get_m_wheelsDampingRelaxation_0=function(){return(Lv=b._emscripten_bind_btWheelInfoConstructionInfo_get_m_wheelsDampingRelaxation_0=b.asm.wt).apply(null,arguments)},Mv=b._emscripten_bind_btWheelInfoConstructionInfo_set_m_wheelsDampingRelaxation_1=function(){return(Mv=b._emscripten_bind_btWheelInfoConstructionInfo_set_m_wheelsDampingRelaxation_1=
|
||||
b.asm.xt).apply(null,arguments)},Nv=b._emscripten_bind_btWheelInfoConstructionInfo_get_m_frictionSlip_0=function(){return(Nv=b._emscripten_bind_btWheelInfoConstructionInfo_get_m_frictionSlip_0=b.asm.yt).apply(null,arguments)},Ov=b._emscripten_bind_btWheelInfoConstructionInfo_set_m_frictionSlip_1=function(){return(Ov=b._emscripten_bind_btWheelInfoConstructionInfo_set_m_frictionSlip_1=b.asm.zt).apply(null,arguments)},Pv=b._emscripten_bind_btWheelInfoConstructionInfo_get_m_maxSuspensionForce_0=function(){return(Pv=
|
||||
b._emscripten_bind_btWheelInfoConstructionInfo_get_m_maxSuspensionForce_0=b.asm.At).apply(null,arguments)},Qv=b._emscripten_bind_btWheelInfoConstructionInfo_set_m_maxSuspensionForce_1=function(){return(Qv=b._emscripten_bind_btWheelInfoConstructionInfo_set_m_maxSuspensionForce_1=b.asm.Bt).apply(null,arguments)},Rv=b._emscripten_bind_btWheelInfoConstructionInfo_get_m_bIsFrontWheel_0=function(){return(Rv=b._emscripten_bind_btWheelInfoConstructionInfo_get_m_bIsFrontWheel_0=b.asm.Ct).apply(null,arguments)},
|
||||
Sv=b._emscripten_bind_btWheelInfoConstructionInfo_set_m_bIsFrontWheel_1=function(){return(Sv=b._emscripten_bind_btWheelInfoConstructionInfo_set_m_bIsFrontWheel_1=b.asm.Dt).apply(null,arguments)},Tv=b._emscripten_bind_btWheelInfoConstructionInfo___destroy___0=function(){return(Tv=b._emscripten_bind_btWheelInfoConstructionInfo___destroy___0=b.asm.Et).apply(null,arguments)},Uv=b._emscripten_bind_btWheelInfo_btWheelInfo_1=function(){return(Uv=b._emscripten_bind_btWheelInfo_btWheelInfo_1=b.asm.Ft).apply(null,
|
||||
arguments)},Vv=b._emscripten_bind_btWheelInfo_getSuspensionRestLength_0=function(){return(Vv=b._emscripten_bind_btWheelInfo_getSuspensionRestLength_0=b.asm.Gt).apply(null,arguments)},Wv=b._emscripten_bind_btWheelInfo_updateWheel_2=function(){return(Wv=b._emscripten_bind_btWheelInfo_updateWheel_2=b.asm.Ht).apply(null,arguments)},Xv=b._emscripten_bind_btWheelInfo_get_m_suspensionStiffness_0=function(){return(Xv=b._emscripten_bind_btWheelInfo_get_m_suspensionStiffness_0=b.asm.It).apply(null,arguments)},
|
||||
Yv=b._emscripten_bind_btWheelInfo_set_m_suspensionStiffness_1=function(){return(Yv=b._emscripten_bind_btWheelInfo_set_m_suspensionStiffness_1=b.asm.Jt).apply(null,arguments)},Zv=b._emscripten_bind_btWheelInfo_get_m_frictionSlip_0=function(){return(Zv=b._emscripten_bind_btWheelInfo_get_m_frictionSlip_0=b.asm.Kt).apply(null,arguments)},$v=b._emscripten_bind_btWheelInfo_set_m_frictionSlip_1=function(){return($v=b._emscripten_bind_btWheelInfo_set_m_frictionSlip_1=b.asm.Lt).apply(null,arguments)},aw=b._emscripten_bind_btWheelInfo_get_m_engineForce_0=
|
||||
function(){return(aw=b._emscripten_bind_btWheelInfo_get_m_engineForce_0=b.asm.Mt).apply(null,arguments)},bw=b._emscripten_bind_btWheelInfo_set_m_engineForce_1=function(){return(bw=b._emscripten_bind_btWheelInfo_set_m_engineForce_1=b.asm.Nt).apply(null,arguments)},cw=b._emscripten_bind_btWheelInfo_get_m_rollInfluence_0=function(){return(cw=b._emscripten_bind_btWheelInfo_get_m_rollInfluence_0=b.asm.Ot).apply(null,arguments)},dw=b._emscripten_bind_btWheelInfo_set_m_rollInfluence_1=function(){return(dw=
|
||||
b._emscripten_bind_btWheelInfo_set_m_rollInfluence_1=b.asm.Pt).apply(null,arguments)},ew=b._emscripten_bind_btWheelInfo_get_m_suspensionRestLength1_0=function(){return(ew=b._emscripten_bind_btWheelInfo_get_m_suspensionRestLength1_0=b.asm.Qt).apply(null,arguments)},fw=b._emscripten_bind_btWheelInfo_set_m_suspensionRestLength1_1=function(){return(fw=b._emscripten_bind_btWheelInfo_set_m_suspensionRestLength1_1=b.asm.Rt).apply(null,arguments)},gw=b._emscripten_bind_btWheelInfo_get_m_wheelsRadius_0=function(){return(gw=
|
||||
b._emscripten_bind_btWheelInfo_get_m_wheelsRadius_0=b.asm.St).apply(null,arguments)},hw=b._emscripten_bind_btWheelInfo_set_m_wheelsRadius_1=function(){return(hw=b._emscripten_bind_btWheelInfo_set_m_wheelsRadius_1=b.asm.Tt).apply(null,arguments)},iw=b._emscripten_bind_btWheelInfo_get_m_wheelsDampingCompression_0=function(){return(iw=b._emscripten_bind_btWheelInfo_get_m_wheelsDampingCompression_0=b.asm.Ut).apply(null,arguments)},jw=b._emscripten_bind_btWheelInfo_set_m_wheelsDampingCompression_1=function(){return(jw=
|
||||
b._emscripten_bind_btWheelInfo_set_m_wheelsDampingCompression_1=b.asm.Vt).apply(null,arguments)},kw=b._emscripten_bind_btWheelInfo_get_m_wheelsDampingRelaxation_0=function(){return(kw=b._emscripten_bind_btWheelInfo_get_m_wheelsDampingRelaxation_0=b.asm.Wt).apply(null,arguments)},lw=b._emscripten_bind_btWheelInfo_set_m_wheelsDampingRelaxation_1=function(){return(lw=b._emscripten_bind_btWheelInfo_set_m_wheelsDampingRelaxation_1=b.asm.Xt).apply(null,arguments)},mw=b._emscripten_bind_btWheelInfo_get_m_steering_0=
|
||||
function(){return(mw=b._emscripten_bind_btWheelInfo_get_m_steering_0=b.asm.Yt).apply(null,arguments)},nw=b._emscripten_bind_btWheelInfo_set_m_steering_1=function(){return(nw=b._emscripten_bind_btWheelInfo_set_m_steering_1=b.asm.Zt).apply(null,arguments)},ow=b._emscripten_bind_btWheelInfo_get_m_maxSuspensionForce_0=function(){return(ow=b._emscripten_bind_btWheelInfo_get_m_maxSuspensionForce_0=b.asm._t).apply(null,arguments)},pw=b._emscripten_bind_btWheelInfo_set_m_maxSuspensionForce_1=function(){return(pw=
|
||||
b._emscripten_bind_btWheelInfo_set_m_maxSuspensionForce_1=b.asm.$t).apply(null,arguments)},qw=b._emscripten_bind_btWheelInfo_get_m_maxSuspensionTravelCm_0=function(){return(qw=b._emscripten_bind_btWheelInfo_get_m_maxSuspensionTravelCm_0=b.asm.au).apply(null,arguments)},rw=b._emscripten_bind_btWheelInfo_set_m_maxSuspensionTravelCm_1=function(){return(rw=b._emscripten_bind_btWheelInfo_set_m_maxSuspensionTravelCm_1=b.asm.bu).apply(null,arguments)},sw=b._emscripten_bind_btWheelInfo_get_m_wheelsSuspensionForce_0=
|
||||
function(){return(sw=b._emscripten_bind_btWheelInfo_get_m_wheelsSuspensionForce_0=b.asm.cu).apply(null,arguments)},tw=b._emscripten_bind_btWheelInfo_set_m_wheelsSuspensionForce_1=function(){return(tw=b._emscripten_bind_btWheelInfo_set_m_wheelsSuspensionForce_1=b.asm.du).apply(null,arguments)},uw=b._emscripten_bind_btWheelInfo_get_m_bIsFrontWheel_0=function(){return(uw=b._emscripten_bind_btWheelInfo_get_m_bIsFrontWheel_0=b.asm.eu).apply(null,arguments)},vw=b._emscripten_bind_btWheelInfo_set_m_bIsFrontWheel_1=
|
||||
function(){return(vw=b._emscripten_bind_btWheelInfo_set_m_bIsFrontWheel_1=b.asm.fu).apply(null,arguments)},ww=b._emscripten_bind_btWheelInfo_get_m_raycastInfo_0=function(){return(ww=b._emscripten_bind_btWheelInfo_get_m_raycastInfo_0=b.asm.gu).apply(null,arguments)},xw=b._emscripten_bind_btWheelInfo_set_m_raycastInfo_1=function(){return(xw=b._emscripten_bind_btWheelInfo_set_m_raycastInfo_1=b.asm.hu).apply(null,arguments)},yw=b._emscripten_bind_btWheelInfo_get_m_chassisConnectionPointCS_0=function(){return(yw=
|
||||
b._emscripten_bind_btWheelInfo_get_m_chassisConnectionPointCS_0=b.asm.iu).apply(null,arguments)},zw=b._emscripten_bind_btWheelInfo_set_m_chassisConnectionPointCS_1=function(){return(zw=b._emscripten_bind_btWheelInfo_set_m_chassisConnectionPointCS_1=b.asm.ju).apply(null,arguments)},Aw=b._emscripten_bind_btWheelInfo_get_m_worldTransform_0=function(){return(Aw=b._emscripten_bind_btWheelInfo_get_m_worldTransform_0=b.asm.ku).apply(null,arguments)},Bw=b._emscripten_bind_btWheelInfo_set_m_worldTransform_1=
|
||||
function(){return(Bw=b._emscripten_bind_btWheelInfo_set_m_worldTransform_1=b.asm.lu).apply(null,arguments)},Cw=b._emscripten_bind_btWheelInfo_get_m_wheelDirectionCS_0=function(){return(Cw=b._emscripten_bind_btWheelInfo_get_m_wheelDirectionCS_0=b.asm.mu).apply(null,arguments)},Dw=b._emscripten_bind_btWheelInfo_set_m_wheelDirectionCS_1=function(){return(Dw=b._emscripten_bind_btWheelInfo_set_m_wheelDirectionCS_1=b.asm.nu).apply(null,arguments)},Ew=b._emscripten_bind_btWheelInfo_get_m_wheelAxleCS_0=function(){return(Ew=
|
||||
b._emscripten_bind_btWheelInfo_get_m_wheelAxleCS_0=b.asm.ou).apply(null,arguments)},Fw=b._emscripten_bind_btWheelInfo_set_m_wheelAxleCS_1=function(){return(Fw=b._emscripten_bind_btWheelInfo_set_m_wheelAxleCS_1=b.asm.pu).apply(null,arguments)},Gw=b._emscripten_bind_btWheelInfo_get_m_rotation_0=function(){return(Gw=b._emscripten_bind_btWheelInfo_get_m_rotation_0=b.asm.qu).apply(null,arguments)},Hw=b._emscripten_bind_btWheelInfo_set_m_rotation_1=function(){return(Hw=b._emscripten_bind_btWheelInfo_set_m_rotation_1=
|
||||
b.asm.ru).apply(null,arguments)},Iw=b._emscripten_bind_btWheelInfo_get_m_deltaRotation_0=function(){return(Iw=b._emscripten_bind_btWheelInfo_get_m_deltaRotation_0=b.asm.su).apply(null,arguments)},Jw=b._emscripten_bind_btWheelInfo_set_m_deltaRotation_1=function(){return(Jw=b._emscripten_bind_btWheelInfo_set_m_deltaRotation_1=b.asm.tu).apply(null,arguments)},Kw=b._emscripten_bind_btWheelInfo_get_m_brake_0=function(){return(Kw=b._emscripten_bind_btWheelInfo_get_m_brake_0=b.asm.uu).apply(null,arguments)},
|
||||
Lw=b._emscripten_bind_btWheelInfo_set_m_brake_1=function(){return(Lw=b._emscripten_bind_btWheelInfo_set_m_brake_1=b.asm.vu).apply(null,arguments)},Mw=b._emscripten_bind_btWheelInfo_get_m_clippedInvContactDotSuspension_0=function(){return(Mw=b._emscripten_bind_btWheelInfo_get_m_clippedInvContactDotSuspension_0=b.asm.wu).apply(null,arguments)},Nw=b._emscripten_bind_btWheelInfo_set_m_clippedInvContactDotSuspension_1=function(){return(Nw=b._emscripten_bind_btWheelInfo_set_m_clippedInvContactDotSuspension_1=
|
||||
b.asm.xu).apply(null,arguments)},Ow=b._emscripten_bind_btWheelInfo_get_m_suspensionRelativeVelocity_0=function(){return(Ow=b._emscripten_bind_btWheelInfo_get_m_suspensionRelativeVelocity_0=b.asm.yu).apply(null,arguments)},Pw=b._emscripten_bind_btWheelInfo_set_m_suspensionRelativeVelocity_1=function(){return(Pw=b._emscripten_bind_btWheelInfo_set_m_suspensionRelativeVelocity_1=b.asm.zu).apply(null,arguments)},Qw=b._emscripten_bind_btWheelInfo_get_m_skidInfo_0=function(){return(Qw=b._emscripten_bind_btWheelInfo_get_m_skidInfo_0=
|
||||
b.asm.Au).apply(null,arguments)},Rw=b._emscripten_bind_btWheelInfo_set_m_skidInfo_1=function(){return(Rw=b._emscripten_bind_btWheelInfo_set_m_skidInfo_1=b.asm.Bu).apply(null,arguments)},Sw=b._emscripten_bind_btWheelInfo___destroy___0=function(){return(Sw=b._emscripten_bind_btWheelInfo___destroy___0=b.asm.Cu).apply(null,arguments)},Tw=b._emscripten_bind_btKinematicCharacterController_btKinematicCharacterController_3=function(){return(Tw=b._emscripten_bind_btKinematicCharacterController_btKinematicCharacterController_3=
|
||||
b.asm.Du).apply(null,arguments)},Uw=b._emscripten_bind_btKinematicCharacterController_btKinematicCharacterController_4=function(){return(Uw=b._emscripten_bind_btKinematicCharacterController_btKinematicCharacterController_4=b.asm.Eu).apply(null,arguments)},Vw=b._emscripten_bind_btKinematicCharacterController_setUpAxis_1=function(){return(Vw=b._emscripten_bind_btKinematicCharacterController_setUpAxis_1=b.asm.Fu).apply(null,arguments)},Ww=b._emscripten_bind_btKinematicCharacterController_setWalkDirection_1=
|
||||
function(){return(Ww=b._emscripten_bind_btKinematicCharacterController_setWalkDirection_1=b.asm.Gu).apply(null,arguments)},Xw=b._emscripten_bind_btKinematicCharacterController_setVelocityForTimeInterval_2=function(){return(Xw=b._emscripten_bind_btKinematicCharacterController_setVelocityForTimeInterval_2=b.asm.Hu).apply(null,arguments)},Yw=b._emscripten_bind_btKinematicCharacterController_warp_1=function(){return(Yw=b._emscripten_bind_btKinematicCharacterController_warp_1=b.asm.Iu).apply(null,arguments)},
|
||||
Zw=b._emscripten_bind_btKinematicCharacterController_preStep_1=function(){return(Zw=b._emscripten_bind_btKinematicCharacterController_preStep_1=b.asm.Ju).apply(null,arguments)},$w=b._emscripten_bind_btKinematicCharacterController_playerStep_2=function(){return($w=b._emscripten_bind_btKinematicCharacterController_playerStep_2=b.asm.Ku).apply(null,arguments)},ax=b._emscripten_bind_btKinematicCharacterController_setFallSpeed_1=function(){return(ax=b._emscripten_bind_btKinematicCharacterController_setFallSpeed_1=
|
||||
b.asm.Lu).apply(null,arguments)},bx=b._emscripten_bind_btKinematicCharacterController_setJumpSpeed_1=function(){return(bx=b._emscripten_bind_btKinematicCharacterController_setJumpSpeed_1=b.asm.Mu).apply(null,arguments)},cx=b._emscripten_bind_btKinematicCharacterController_setMaxJumpHeight_1=function(){return(cx=b._emscripten_bind_btKinematicCharacterController_setMaxJumpHeight_1=b.asm.Nu).apply(null,arguments)},dx=b._emscripten_bind_btKinematicCharacterController_canJump_0=function(){return(dx=b._emscripten_bind_btKinematicCharacterController_canJump_0=
|
||||
b.asm.Ou).apply(null,arguments)},ex=b._emscripten_bind_btKinematicCharacterController_jump_0=function(){return(ex=b._emscripten_bind_btKinematicCharacterController_jump_0=b.asm.Pu).apply(null,arguments)},fx=b._emscripten_bind_btKinematicCharacterController_setGravity_1=function(){return(fx=b._emscripten_bind_btKinematicCharacterController_setGravity_1=b.asm.Qu).apply(null,arguments)},gx=b._emscripten_bind_btKinematicCharacterController_getGravity_0=function(){return(gx=b._emscripten_bind_btKinematicCharacterController_getGravity_0=
|
||||
b.asm.Ru).apply(null,arguments)},hx=b._emscripten_bind_btKinematicCharacterController_setMaxSlope_1=function(){return(hx=b._emscripten_bind_btKinematicCharacterController_setMaxSlope_1=b.asm.Su).apply(null,arguments)},ix=b._emscripten_bind_btKinematicCharacterController_getMaxSlope_0=function(){return(ix=b._emscripten_bind_btKinematicCharacterController_getMaxSlope_0=b.asm.Tu).apply(null,arguments)},jx=b._emscripten_bind_btKinematicCharacterController_getGhostObject_0=function(){return(jx=b._emscripten_bind_btKinematicCharacterController_getGhostObject_0=
|
||||
b.asm.Uu).apply(null,arguments)},kx=b._emscripten_bind_btKinematicCharacterController_setUseGhostSweepTest_1=function(){return(kx=b._emscripten_bind_btKinematicCharacterController_setUseGhostSweepTest_1=b.asm.Vu).apply(null,arguments)},lx=b._emscripten_bind_btKinematicCharacterController_onGround_0=function(){return(lx=b._emscripten_bind_btKinematicCharacterController_onGround_0=b.asm.Wu).apply(null,arguments)},mx=b._emscripten_bind_btKinematicCharacterController_setUpInterpolate_1=function(){return(mx=
|
||||
b._emscripten_bind_btKinematicCharacterController_setUpInterpolate_1=b.asm.Xu).apply(null,arguments)},nx=b._emscripten_bind_btKinematicCharacterController_updateAction_2=function(){return(nx=b._emscripten_bind_btKinematicCharacterController_updateAction_2=b.asm.Yu).apply(null,arguments)},ox=b._emscripten_bind_btKinematicCharacterController___destroy___0=function(){return(ox=b._emscripten_bind_btKinematicCharacterController___destroy___0=b.asm.Zu).apply(null,arguments)},px=b._emscripten_bind_btRaycastVehicle_btRaycastVehicle_3=
|
||||
function(){return(px=b._emscripten_bind_btRaycastVehicle_btRaycastVehicle_3=b.asm._u).apply(null,arguments)},qx=b._emscripten_bind_btRaycastVehicle_applyEngineForce_2=function(){return(qx=b._emscripten_bind_btRaycastVehicle_applyEngineForce_2=b.asm.$u).apply(null,arguments)},rx=b._emscripten_bind_btRaycastVehicle_setSteeringValue_2=function(){return(rx=b._emscripten_bind_btRaycastVehicle_setSteeringValue_2=b.asm.av).apply(null,arguments)},sx=b._emscripten_bind_btRaycastVehicle_getWheelTransformWS_1=
|
||||
function(){return(sx=b._emscripten_bind_btRaycastVehicle_getWheelTransformWS_1=b.asm.bv).apply(null,arguments)},tx=b._emscripten_bind_btRaycastVehicle_updateWheelTransform_2=function(){return(tx=b._emscripten_bind_btRaycastVehicle_updateWheelTransform_2=b.asm.cv).apply(null,arguments)},ux=b._emscripten_bind_btRaycastVehicle_addWheel_7=function(){return(ux=b._emscripten_bind_btRaycastVehicle_addWheel_7=b.asm.dv).apply(null,arguments)},vx=b._emscripten_bind_btRaycastVehicle_getNumWheels_0=function(){return(vx=
|
||||
b._emscripten_bind_btRaycastVehicle_getNumWheels_0=b.asm.ev).apply(null,arguments)},wx=b._emscripten_bind_btRaycastVehicle_getRigidBody_0=function(){return(wx=b._emscripten_bind_btRaycastVehicle_getRigidBody_0=b.asm.fv).apply(null,arguments)},xx=b._emscripten_bind_btRaycastVehicle_getWheelInfo_1=function(){return(xx=b._emscripten_bind_btRaycastVehicle_getWheelInfo_1=b.asm.gv).apply(null,arguments)},yx=b._emscripten_bind_btRaycastVehicle_setBrake_2=function(){return(yx=b._emscripten_bind_btRaycastVehicle_setBrake_2=
|
||||
b.asm.hv).apply(null,arguments)},zx=b._emscripten_bind_btRaycastVehicle_setCoordinateSystem_3=function(){return(zx=b._emscripten_bind_btRaycastVehicle_setCoordinateSystem_3=b.asm.iv).apply(null,arguments)},Ax=b._emscripten_bind_btRaycastVehicle_getCurrentSpeedKmHour_0=function(){return(Ax=b._emscripten_bind_btRaycastVehicle_getCurrentSpeedKmHour_0=b.asm.jv).apply(null,arguments)},Bx=b._emscripten_bind_btRaycastVehicle_getChassisWorldTransform_0=function(){return(Bx=b._emscripten_bind_btRaycastVehicle_getChassisWorldTransform_0=
|
||||
b.asm.kv).apply(null,arguments)},Cx=b._emscripten_bind_btRaycastVehicle_rayCast_1=function(){return(Cx=b._emscripten_bind_btRaycastVehicle_rayCast_1=b.asm.lv).apply(null,arguments)},Dx=b._emscripten_bind_btRaycastVehicle_updateVehicle_1=function(){return(Dx=b._emscripten_bind_btRaycastVehicle_updateVehicle_1=b.asm.mv).apply(null,arguments)},Ex=b._emscripten_bind_btRaycastVehicle_resetSuspension_0=function(){return(Ex=b._emscripten_bind_btRaycastVehicle_resetSuspension_0=b.asm.nv).apply(null,arguments)},
|
||||
Fx=b._emscripten_bind_btRaycastVehicle_getSteeringValue_1=function(){return(Fx=b._emscripten_bind_btRaycastVehicle_getSteeringValue_1=b.asm.ov).apply(null,arguments)},Gx=b._emscripten_bind_btRaycastVehicle_updateWheelTransformsWS_1=function(){return(Gx=b._emscripten_bind_btRaycastVehicle_updateWheelTransformsWS_1=b.asm.pv).apply(null,arguments)},Hx=b._emscripten_bind_btRaycastVehicle_updateWheelTransformsWS_2=function(){return(Hx=b._emscripten_bind_btRaycastVehicle_updateWheelTransformsWS_2=b.asm.qv).apply(null,
|
||||
arguments)},Ix=b._emscripten_bind_btRaycastVehicle_setPitchControl_1=function(){return(Ix=b._emscripten_bind_btRaycastVehicle_setPitchControl_1=b.asm.rv).apply(null,arguments)},Jx=b._emscripten_bind_btRaycastVehicle_updateSuspension_1=function(){return(Jx=b._emscripten_bind_btRaycastVehicle_updateSuspension_1=b.asm.sv).apply(null,arguments)},Kx=b._emscripten_bind_btRaycastVehicle_updateFriction_1=function(){return(Kx=b._emscripten_bind_btRaycastVehicle_updateFriction_1=b.asm.tv).apply(null,arguments)},
|
||||
Lx=b._emscripten_bind_btRaycastVehicle_getRightAxis_0=function(){return(Lx=b._emscripten_bind_btRaycastVehicle_getRightAxis_0=b.asm.uv).apply(null,arguments)},Mx=b._emscripten_bind_btRaycastVehicle_getUpAxis_0=function(){return(Mx=b._emscripten_bind_btRaycastVehicle_getUpAxis_0=b.asm.vv).apply(null,arguments)},Nx=b._emscripten_bind_btRaycastVehicle_getForwardAxis_0=function(){return(Nx=b._emscripten_bind_btRaycastVehicle_getForwardAxis_0=b.asm.wv).apply(null,arguments)},Ox=b._emscripten_bind_btRaycastVehicle_getForwardVector_0=
|
||||
function(){return(Ox=b._emscripten_bind_btRaycastVehicle_getForwardVector_0=b.asm.xv).apply(null,arguments)},Px=b._emscripten_bind_btRaycastVehicle_getUserConstraintType_0=function(){return(Px=b._emscripten_bind_btRaycastVehicle_getUserConstraintType_0=b.asm.yv).apply(null,arguments)},Qx=b._emscripten_bind_btRaycastVehicle_setUserConstraintType_1=function(){return(Qx=b._emscripten_bind_btRaycastVehicle_setUserConstraintType_1=b.asm.zv).apply(null,arguments)},Rx=b._emscripten_bind_btRaycastVehicle_setUserConstraintId_1=
|
||||
function(){return(Rx=b._emscripten_bind_btRaycastVehicle_setUserConstraintId_1=b.asm.Av).apply(null,arguments)},Sx=b._emscripten_bind_btRaycastVehicle_getUserConstraintId_0=function(){return(Sx=b._emscripten_bind_btRaycastVehicle_getUserConstraintId_0=b.asm.Bv).apply(null,arguments)},Tx=b._emscripten_bind_btRaycastVehicle_updateAction_2=function(){return(Tx=b._emscripten_bind_btRaycastVehicle_updateAction_2=b.asm.Cv).apply(null,arguments)},Ux=b._emscripten_bind_btRaycastVehicle___destroy___0=function(){return(Ux=
|
||||
b._emscripten_bind_btRaycastVehicle___destroy___0=b.asm.Dv).apply(null,arguments)},Vx=b._emscripten_bind_btPairCachingGhostObject_btPairCachingGhostObject_0=function(){return(Vx=b._emscripten_bind_btPairCachingGhostObject_btPairCachingGhostObject_0=b.asm.Ev).apply(null,arguments)},Wx=b._emscripten_bind_btPairCachingGhostObject_setAnisotropicFriction_2=function(){return(Wx=b._emscripten_bind_btPairCachingGhostObject_setAnisotropicFriction_2=b.asm.Fv).apply(null,arguments)},Xx=b._emscripten_bind_btPairCachingGhostObject_getCollisionShape_0=
|
||||
function(){return(Xx=b._emscripten_bind_btPairCachingGhostObject_getCollisionShape_0=b.asm.Gv).apply(null,arguments)},Yx=b._emscripten_bind_btPairCachingGhostObject_setContactProcessingThreshold_1=function(){return(Yx=b._emscripten_bind_btPairCachingGhostObject_setContactProcessingThreshold_1=b.asm.Hv).apply(null,arguments)},Zx=b._emscripten_bind_btPairCachingGhostObject_setActivationState_1=function(){return(Zx=b._emscripten_bind_btPairCachingGhostObject_setActivationState_1=b.asm.Iv).apply(null,
|
||||
arguments)},$x=b._emscripten_bind_btPairCachingGhostObject_forceActivationState_1=function(){return($x=b._emscripten_bind_btPairCachingGhostObject_forceActivationState_1=b.asm.Jv).apply(null,arguments)},ay=b._emscripten_bind_btPairCachingGhostObject_activate_0=function(){return(ay=b._emscripten_bind_btPairCachingGhostObject_activate_0=b.asm.Kv).apply(null,arguments)},by=b._emscripten_bind_btPairCachingGhostObject_activate_1=function(){return(by=b._emscripten_bind_btPairCachingGhostObject_activate_1=
|
||||
b.asm.Lv).apply(null,arguments)},cy=b._emscripten_bind_btPairCachingGhostObject_isActive_0=function(){return(cy=b._emscripten_bind_btPairCachingGhostObject_isActive_0=b.asm.Mv).apply(null,arguments)},dy=b._emscripten_bind_btPairCachingGhostObject_isKinematicObject_0=function(){return(dy=b._emscripten_bind_btPairCachingGhostObject_isKinematicObject_0=b.asm.Nv).apply(null,arguments)},ey=b._emscripten_bind_btPairCachingGhostObject_isStaticObject_0=function(){return(ey=b._emscripten_bind_btPairCachingGhostObject_isStaticObject_0=
|
||||
b.asm.Ov).apply(null,arguments)},fy=b._emscripten_bind_btPairCachingGhostObject_isStaticOrKinematicObject_0=function(){return(fy=b._emscripten_bind_btPairCachingGhostObject_isStaticOrKinematicObject_0=b.asm.Pv).apply(null,arguments)},gy=b._emscripten_bind_btPairCachingGhostObject_getRestitution_0=function(){return(gy=b._emscripten_bind_btPairCachingGhostObject_getRestitution_0=b.asm.Qv).apply(null,arguments)},hy=b._emscripten_bind_btPairCachingGhostObject_getFriction_0=function(){return(hy=b._emscripten_bind_btPairCachingGhostObject_getFriction_0=
|
||||
b.asm.Rv).apply(null,arguments)},iy=b._emscripten_bind_btPairCachingGhostObject_getRollingFriction_0=function(){return(iy=b._emscripten_bind_btPairCachingGhostObject_getRollingFriction_0=b.asm.Sv).apply(null,arguments)},jy=b._emscripten_bind_btPairCachingGhostObject_setRestitution_1=function(){return(jy=b._emscripten_bind_btPairCachingGhostObject_setRestitution_1=b.asm.Tv).apply(null,arguments)},ky=b._emscripten_bind_btPairCachingGhostObject_setFriction_1=function(){return(ky=b._emscripten_bind_btPairCachingGhostObject_setFriction_1=
|
||||
b.asm.Uv).apply(null,arguments)},ly=b._emscripten_bind_btPairCachingGhostObject_setRollingFriction_1=function(){return(ly=b._emscripten_bind_btPairCachingGhostObject_setRollingFriction_1=b.asm.Vv).apply(null,arguments)},my=b._emscripten_bind_btPairCachingGhostObject_getWorldTransform_0=function(){return(my=b._emscripten_bind_btPairCachingGhostObject_getWorldTransform_0=b.asm.Wv).apply(null,arguments)},ny=b._emscripten_bind_btPairCachingGhostObject_getCollisionFlags_0=function(){return(ny=b._emscripten_bind_btPairCachingGhostObject_getCollisionFlags_0=
|
||||
b.asm.Xv).apply(null,arguments)},oy=b._emscripten_bind_btPairCachingGhostObject_setCollisionFlags_1=function(){return(oy=b._emscripten_bind_btPairCachingGhostObject_setCollisionFlags_1=b.asm.Yv).apply(null,arguments)},py=b._emscripten_bind_btPairCachingGhostObject_setWorldTransform_1=function(){return(py=b._emscripten_bind_btPairCachingGhostObject_setWorldTransform_1=b.asm.Zv).apply(null,arguments)},qy=b._emscripten_bind_btPairCachingGhostObject_setCollisionShape_1=function(){return(qy=b._emscripten_bind_btPairCachingGhostObject_setCollisionShape_1=
|
||||
b.asm._v).apply(null,arguments)},ry=b._emscripten_bind_btPairCachingGhostObject_setCcdMotionThreshold_1=function(){return(ry=b._emscripten_bind_btPairCachingGhostObject_setCcdMotionThreshold_1=b.asm.$v).apply(null,arguments)},sy=b._emscripten_bind_btPairCachingGhostObject_setCcdSweptSphereRadius_1=function(){return(sy=b._emscripten_bind_btPairCachingGhostObject_setCcdSweptSphereRadius_1=b.asm.aw).apply(null,arguments)},ty=b._emscripten_bind_btPairCachingGhostObject_getUserIndex_0=function(){return(ty=
|
||||
b._emscripten_bind_btPairCachingGhostObject_getUserIndex_0=b.asm.bw).apply(null,arguments)},uy=b._emscripten_bind_btPairCachingGhostObject_setUserIndex_1=function(){return(uy=b._emscripten_bind_btPairCachingGhostObject_setUserIndex_1=b.asm.cw).apply(null,arguments)},vy=b._emscripten_bind_btPairCachingGhostObject_getUserPointer_0=function(){return(vy=b._emscripten_bind_btPairCachingGhostObject_getUserPointer_0=b.asm.dw).apply(null,arguments)},wy=b._emscripten_bind_btPairCachingGhostObject_setUserPointer_1=
|
||||
function(){return(wy=b._emscripten_bind_btPairCachingGhostObject_setUserPointer_1=b.asm.ew).apply(null,arguments)},xy=b._emscripten_bind_btPairCachingGhostObject_getBroadphaseHandle_0=function(){return(xy=b._emscripten_bind_btPairCachingGhostObject_getBroadphaseHandle_0=b.asm.fw).apply(null,arguments)},yy=b._emscripten_bind_btPairCachingGhostObject_getNumOverlappingObjects_0=function(){return(yy=b._emscripten_bind_btPairCachingGhostObject_getNumOverlappingObjects_0=b.asm.gw).apply(null,arguments)},
|
||||
zy=b._emscripten_bind_btPairCachingGhostObject_getOverlappingObject_1=function(){return(zy=b._emscripten_bind_btPairCachingGhostObject_getOverlappingObject_1=b.asm.hw).apply(null,arguments)},Ay=b._emscripten_bind_btPairCachingGhostObject___destroy___0=function(){return(Ay=b._emscripten_bind_btPairCachingGhostObject___destroy___0=b.asm.iw).apply(null,arguments)},By=b._emscripten_bind_btGhostPairCallback_btGhostPairCallback_0=function(){return(By=b._emscripten_bind_btGhostPairCallback_btGhostPairCallback_0=
|
||||
b.asm.jw).apply(null,arguments)},Cy=b._emscripten_bind_btGhostPairCallback___destroy___0=function(){return(Cy=b._emscripten_bind_btGhostPairCallback___destroy___0=b.asm.kw).apply(null,arguments)},Dy=b._emscripten_bind_btSoftBodyWorldInfo_btSoftBodyWorldInfo_0=function(){return(Dy=b._emscripten_bind_btSoftBodyWorldInfo_btSoftBodyWorldInfo_0=b.asm.lw).apply(null,arguments)},Ey=b._emscripten_bind_btSoftBodyWorldInfo_get_air_density_0=function(){return(Ey=b._emscripten_bind_btSoftBodyWorldInfo_get_air_density_0=
|
||||
b.asm.mw).apply(null,arguments)},Fy=b._emscripten_bind_btSoftBodyWorldInfo_set_air_density_1=function(){return(Fy=b._emscripten_bind_btSoftBodyWorldInfo_set_air_density_1=b.asm.nw).apply(null,arguments)},Gy=b._emscripten_bind_btSoftBodyWorldInfo_get_water_density_0=function(){return(Gy=b._emscripten_bind_btSoftBodyWorldInfo_get_water_density_0=b.asm.ow).apply(null,arguments)},Hy=b._emscripten_bind_btSoftBodyWorldInfo_set_water_density_1=function(){return(Hy=b._emscripten_bind_btSoftBodyWorldInfo_set_water_density_1=
|
||||
b.asm.pw).apply(null,arguments)},Iy=b._emscripten_bind_btSoftBodyWorldInfo_get_water_offset_0=function(){return(Iy=b._emscripten_bind_btSoftBodyWorldInfo_get_water_offset_0=b.asm.qw).apply(null,arguments)},Jy=b._emscripten_bind_btSoftBodyWorldInfo_set_water_offset_1=function(){return(Jy=b._emscripten_bind_btSoftBodyWorldInfo_set_water_offset_1=b.asm.rw).apply(null,arguments)},Ky=b._emscripten_bind_btSoftBodyWorldInfo_get_m_maxDisplacement_0=function(){return(Ky=b._emscripten_bind_btSoftBodyWorldInfo_get_m_maxDisplacement_0=
|
||||
b.asm.sw).apply(null,arguments)},Ly=b._emscripten_bind_btSoftBodyWorldInfo_set_m_maxDisplacement_1=function(){return(Ly=b._emscripten_bind_btSoftBodyWorldInfo_set_m_maxDisplacement_1=b.asm.tw).apply(null,arguments)},My=b._emscripten_bind_btSoftBodyWorldInfo_get_water_normal_0=function(){return(My=b._emscripten_bind_btSoftBodyWorldInfo_get_water_normal_0=b.asm.uw).apply(null,arguments)},Ny=b._emscripten_bind_btSoftBodyWorldInfo_set_water_normal_1=function(){return(Ny=b._emscripten_bind_btSoftBodyWorldInfo_set_water_normal_1=
|
||||
b.asm.vw).apply(null,arguments)},Oy=b._emscripten_bind_btSoftBodyWorldInfo_get_m_broadphase_0=function(){return(Oy=b._emscripten_bind_btSoftBodyWorldInfo_get_m_broadphase_0=b.asm.ww).apply(null,arguments)},Py=b._emscripten_bind_btSoftBodyWorldInfo_set_m_broadphase_1=function(){return(Py=b._emscripten_bind_btSoftBodyWorldInfo_set_m_broadphase_1=b.asm.xw).apply(null,arguments)},Qy=b._emscripten_bind_btSoftBodyWorldInfo_get_m_dispatcher_0=function(){return(Qy=b._emscripten_bind_btSoftBodyWorldInfo_get_m_dispatcher_0=
|
||||
b.asm.yw).apply(null,arguments)},Ry=b._emscripten_bind_btSoftBodyWorldInfo_set_m_dispatcher_1=function(){return(Ry=b._emscripten_bind_btSoftBodyWorldInfo_set_m_dispatcher_1=b.asm.zw).apply(null,arguments)},Sy=b._emscripten_bind_btSoftBodyWorldInfo_get_m_gravity_0=function(){return(Sy=b._emscripten_bind_btSoftBodyWorldInfo_get_m_gravity_0=b.asm.Aw).apply(null,arguments)},Ty=b._emscripten_bind_btSoftBodyWorldInfo_set_m_gravity_1=function(){return(Ty=b._emscripten_bind_btSoftBodyWorldInfo_set_m_gravity_1=
|
||||
b.asm.Bw).apply(null,arguments)},Uy=b._emscripten_bind_btSoftBodyWorldInfo___destroy___0=function(){return(Uy=b._emscripten_bind_btSoftBodyWorldInfo___destroy___0=b.asm.Cw).apply(null,arguments)},Vy=b._emscripten_bind_Face_get_m_n_1=function(){return(Vy=b._emscripten_bind_Face_get_m_n_1=b.asm.Dw).apply(null,arguments)},Wy=b._emscripten_bind_Face_set_m_n_2=function(){return(Wy=b._emscripten_bind_Face_set_m_n_2=b.asm.Ew).apply(null,arguments)},Xy=b._emscripten_bind_Face_get_m_normal_0=function(){return(Xy=
|
||||
b._emscripten_bind_Face_get_m_normal_0=b.asm.Fw).apply(null,arguments)},Yy=b._emscripten_bind_Face_set_m_normal_1=function(){return(Yy=b._emscripten_bind_Face_set_m_normal_1=b.asm.Gw).apply(null,arguments)},Zy=b._emscripten_bind_Face_get_m_ra_0=function(){return(Zy=b._emscripten_bind_Face_get_m_ra_0=b.asm.Hw).apply(null,arguments)},$y=b._emscripten_bind_Face_set_m_ra_1=function(){return($y=b._emscripten_bind_Face_set_m_ra_1=b.asm.Iw).apply(null,arguments)},az=b._emscripten_bind_Face___destroy___0=
|
||||
function(){return(az=b._emscripten_bind_Face___destroy___0=b.asm.Jw).apply(null,arguments)},bz=b._emscripten_bind_tFaceArray_size_0=function(){return(bz=b._emscripten_bind_tFaceArray_size_0=b.asm.Kw).apply(null,arguments)},cz=b._emscripten_bind_tFaceArray_at_1=function(){return(cz=b._emscripten_bind_tFaceArray_at_1=b.asm.Lw).apply(null,arguments)},dz=b._emscripten_bind_tFaceArray___destroy___0=function(){return(dz=b._emscripten_bind_tFaceArray___destroy___0=b.asm.Mw).apply(null,arguments)},ez=b._emscripten_bind_Node_get_m_x_0=
|
||||
function(){return(ez=b._emscripten_bind_Node_get_m_x_0=b.asm.Nw).apply(null,arguments)},fz=b._emscripten_bind_Node_set_m_x_1=function(){return(fz=b._emscripten_bind_Node_set_m_x_1=b.asm.Ow).apply(null,arguments)},gz=b._emscripten_bind_Node_get_m_q_0=function(){return(gz=b._emscripten_bind_Node_get_m_q_0=b.asm.Pw).apply(null,arguments)},hz=b._emscripten_bind_Node_set_m_q_1=function(){return(hz=b._emscripten_bind_Node_set_m_q_1=b.asm.Qw).apply(null,arguments)},iz=b._emscripten_bind_Node_get_m_v_0=function(){return(iz=
|
||||
b._emscripten_bind_Node_get_m_v_0=b.asm.Rw).apply(null,arguments)},jz=b._emscripten_bind_Node_set_m_v_1=function(){return(jz=b._emscripten_bind_Node_set_m_v_1=b.asm.Sw).apply(null,arguments)},kz=b._emscripten_bind_Node_get_m_f_0=function(){return(kz=b._emscripten_bind_Node_get_m_f_0=b.asm.Tw).apply(null,arguments)},lz=b._emscripten_bind_Node_set_m_f_1=function(){return(lz=b._emscripten_bind_Node_set_m_f_1=b.asm.Uw).apply(null,arguments)},mz=b._emscripten_bind_Node_get_m_n_0=function(){return(mz=b._emscripten_bind_Node_get_m_n_0=
|
||||
b.asm.Vw).apply(null,arguments)},nz=b._emscripten_bind_Node_set_m_n_1=function(){return(nz=b._emscripten_bind_Node_set_m_n_1=b.asm.Ww).apply(null,arguments)},oz=b._emscripten_bind_Node_get_m_im_0=function(){return(oz=b._emscripten_bind_Node_get_m_im_0=b.asm.Xw).apply(null,arguments)},pz=b._emscripten_bind_Node_set_m_im_1=function(){return(pz=b._emscripten_bind_Node_set_m_im_1=b.asm.Yw).apply(null,arguments)},qz=b._emscripten_bind_Node_get_m_area_0=function(){return(qz=b._emscripten_bind_Node_get_m_area_0=
|
||||
b.asm.Zw).apply(null,arguments)},rz=b._emscripten_bind_Node_set_m_area_1=function(){return(rz=b._emscripten_bind_Node_set_m_area_1=b.asm._w).apply(null,arguments)},sz=b._emscripten_bind_Node___destroy___0=function(){return(sz=b._emscripten_bind_Node___destroy___0=b.asm.$w).apply(null,arguments)},tz=b._emscripten_bind_tNodeArray_size_0=function(){return(tz=b._emscripten_bind_tNodeArray_size_0=b.asm.ax).apply(null,arguments)},uz=b._emscripten_bind_tNodeArray_at_1=function(){return(uz=b._emscripten_bind_tNodeArray_at_1=
|
||||
b.asm.bx).apply(null,arguments)},vz=b._emscripten_bind_tNodeArray___destroy___0=function(){return(vz=b._emscripten_bind_tNodeArray___destroy___0=b.asm.cx).apply(null,arguments)},wz=b._emscripten_bind_Material_get_m_kLST_0=function(){return(wz=b._emscripten_bind_Material_get_m_kLST_0=b.asm.dx).apply(null,arguments)},xz=b._emscripten_bind_Material_set_m_kLST_1=function(){return(xz=b._emscripten_bind_Material_set_m_kLST_1=b.asm.ex).apply(null,arguments)},yz=b._emscripten_bind_Material_get_m_kAST_0=function(){return(yz=
|
||||
b._emscripten_bind_Material_get_m_kAST_0=b.asm.fx).apply(null,arguments)},zz=b._emscripten_bind_Material_set_m_kAST_1=function(){return(zz=b._emscripten_bind_Material_set_m_kAST_1=b.asm.gx).apply(null,arguments)},Az=b._emscripten_bind_Material_get_m_kVST_0=function(){return(Az=b._emscripten_bind_Material_get_m_kVST_0=b.asm.hx).apply(null,arguments)},Bz=b._emscripten_bind_Material_set_m_kVST_1=function(){return(Bz=b._emscripten_bind_Material_set_m_kVST_1=b.asm.ix).apply(null,arguments)},Cz=b._emscripten_bind_Material_get_m_flags_0=
|
||||
function(){return(Cz=b._emscripten_bind_Material_get_m_flags_0=b.asm.jx).apply(null,arguments)},Dz=b._emscripten_bind_Material_set_m_flags_1=function(){return(Dz=b._emscripten_bind_Material_set_m_flags_1=b.asm.kx).apply(null,arguments)},Ez=b._emscripten_bind_Material___destroy___0=function(){return(Ez=b._emscripten_bind_Material___destroy___0=b.asm.lx).apply(null,arguments)},Fz=b._emscripten_bind_tMaterialArray_size_0=function(){return(Fz=b._emscripten_bind_tMaterialArray_size_0=b.asm.mx).apply(null,
|
||||
arguments)},Gz=b._emscripten_bind_tMaterialArray_at_1=function(){return(Gz=b._emscripten_bind_tMaterialArray_at_1=b.asm.nx).apply(null,arguments)},Hz=b._emscripten_bind_tMaterialArray___destroy___0=function(){return(Hz=b._emscripten_bind_tMaterialArray___destroy___0=b.asm.ox).apply(null,arguments)},Iz=b._emscripten_bind_Anchor_get_m_node_0=function(){return(Iz=b._emscripten_bind_Anchor_get_m_node_0=b.asm.px).apply(null,arguments)},Jz=b._emscripten_bind_Anchor_set_m_node_1=function(){return(Jz=b._emscripten_bind_Anchor_set_m_node_1=
|
||||
b.asm.qx).apply(null,arguments)},Kz=b._emscripten_bind_Anchor_get_m_local_0=function(){return(Kz=b._emscripten_bind_Anchor_get_m_local_0=b.asm.rx).apply(null,arguments)},Lz=b._emscripten_bind_Anchor_set_m_local_1=function(){return(Lz=b._emscripten_bind_Anchor_set_m_local_1=b.asm.sx).apply(null,arguments)},Mz=b._emscripten_bind_Anchor_get_m_body_0=function(){return(Mz=b._emscripten_bind_Anchor_get_m_body_0=b.asm.tx).apply(null,arguments)},Nz=b._emscripten_bind_Anchor_set_m_body_1=function(){return(Nz=
|
||||
b._emscripten_bind_Anchor_set_m_body_1=b.asm.ux).apply(null,arguments)},Oz=b._emscripten_bind_Anchor_get_m_influence_0=function(){return(Oz=b._emscripten_bind_Anchor_get_m_influence_0=b.asm.vx).apply(null,arguments)},Pz=b._emscripten_bind_Anchor_set_m_influence_1=function(){return(Pz=b._emscripten_bind_Anchor_set_m_influence_1=b.asm.wx).apply(null,arguments)},Qz=b._emscripten_bind_Anchor_get_m_c0_0=function(){return(Qz=b._emscripten_bind_Anchor_get_m_c0_0=b.asm.xx).apply(null,arguments)},Rz=b._emscripten_bind_Anchor_set_m_c0_1=
|
||||
function(){return(Rz=b._emscripten_bind_Anchor_set_m_c0_1=b.asm.yx).apply(null,arguments)},Sz=b._emscripten_bind_Anchor_get_m_c1_0=function(){return(Sz=b._emscripten_bind_Anchor_get_m_c1_0=b.asm.zx).apply(null,arguments)},Tz=b._emscripten_bind_Anchor_set_m_c1_1=function(){return(Tz=b._emscripten_bind_Anchor_set_m_c1_1=b.asm.Ax).apply(null,arguments)},Uz=b._emscripten_bind_Anchor_get_m_c2_0=function(){return(Uz=b._emscripten_bind_Anchor_get_m_c2_0=b.asm.Bx).apply(null,arguments)},Vz=b._emscripten_bind_Anchor_set_m_c2_1=
|
||||
function(){return(Vz=b._emscripten_bind_Anchor_set_m_c2_1=b.asm.Cx).apply(null,arguments)},Wz=b._emscripten_bind_Anchor___destroy___0=function(){return(Wz=b._emscripten_bind_Anchor___destroy___0=b.asm.Dx).apply(null,arguments)},Xz=b._emscripten_bind_tAnchorArray_size_0=function(){return(Xz=b._emscripten_bind_tAnchorArray_size_0=b.asm.Ex).apply(null,arguments)},Yz=b._emscripten_bind_tAnchorArray_at_1=function(){return(Yz=b._emscripten_bind_tAnchorArray_at_1=b.asm.Fx).apply(null,arguments)},Zz=b._emscripten_bind_tAnchorArray_clear_0=
|
||||
function(){return(Zz=b._emscripten_bind_tAnchorArray_clear_0=b.asm.Gx).apply(null,arguments)},$z=b._emscripten_bind_tAnchorArray_push_back_1=function(){return($z=b._emscripten_bind_tAnchorArray_push_back_1=b.asm.Hx).apply(null,arguments)},aA=b._emscripten_bind_tAnchorArray_pop_back_0=function(){return(aA=b._emscripten_bind_tAnchorArray_pop_back_0=b.asm.Ix).apply(null,arguments)},bA=b._emscripten_bind_tAnchorArray___destroy___0=function(){return(bA=b._emscripten_bind_tAnchorArray___destroy___0=b.asm.Jx).apply(null,
|
||||
arguments)},cA=b._emscripten_bind_Config_get_kVCF_0=function(){return(cA=b._emscripten_bind_Config_get_kVCF_0=b.asm.Kx).apply(null,arguments)},dA=b._emscripten_bind_Config_set_kVCF_1=function(){return(dA=b._emscripten_bind_Config_set_kVCF_1=b.asm.Lx).apply(null,arguments)},eA=b._emscripten_bind_Config_get_kDP_0=function(){return(eA=b._emscripten_bind_Config_get_kDP_0=b.asm.Mx).apply(null,arguments)},fA=b._emscripten_bind_Config_set_kDP_1=function(){return(fA=b._emscripten_bind_Config_set_kDP_1=b.asm.Nx).apply(null,
|
||||
arguments)},gA=b._emscripten_bind_Config_get_kDG_0=function(){return(gA=b._emscripten_bind_Config_get_kDG_0=b.asm.Ox).apply(null,arguments)},hA=b._emscripten_bind_Config_set_kDG_1=function(){return(hA=b._emscripten_bind_Config_set_kDG_1=b.asm.Px).apply(null,arguments)},iA=b._emscripten_bind_Config_get_kLF_0=function(){return(iA=b._emscripten_bind_Config_get_kLF_0=b.asm.Qx).apply(null,arguments)},jA=b._emscripten_bind_Config_set_kLF_1=function(){return(jA=b._emscripten_bind_Config_set_kLF_1=b.asm.Rx).apply(null,
|
||||
arguments)},kA=b._emscripten_bind_Config_get_kPR_0=function(){return(kA=b._emscripten_bind_Config_get_kPR_0=b.asm.Sx).apply(null,arguments)},lA=b._emscripten_bind_Config_set_kPR_1=function(){return(lA=b._emscripten_bind_Config_set_kPR_1=b.asm.Tx).apply(null,arguments)},mA=b._emscripten_bind_Config_get_kVC_0=function(){return(mA=b._emscripten_bind_Config_get_kVC_0=b.asm.Ux).apply(null,arguments)},nA=b._emscripten_bind_Config_set_kVC_1=function(){return(nA=b._emscripten_bind_Config_set_kVC_1=b.asm.Vx).apply(null,
|
||||
arguments)},oA=b._emscripten_bind_Config_get_kDF_0=function(){return(oA=b._emscripten_bind_Config_get_kDF_0=b.asm.Wx).apply(null,arguments)},pA=b._emscripten_bind_Config_set_kDF_1=function(){return(pA=b._emscripten_bind_Config_set_kDF_1=b.asm.Xx).apply(null,arguments)},qA=b._emscripten_bind_Config_get_kMT_0=function(){return(qA=b._emscripten_bind_Config_get_kMT_0=b.asm.Yx).apply(null,arguments)},rA=b._emscripten_bind_Config_set_kMT_1=function(){return(rA=b._emscripten_bind_Config_set_kMT_1=b.asm.Zx).apply(null,
|
||||
arguments)},sA=b._emscripten_bind_Config_get_kCHR_0=function(){return(sA=b._emscripten_bind_Config_get_kCHR_0=b.asm._x).apply(null,arguments)},tA=b._emscripten_bind_Config_set_kCHR_1=function(){return(tA=b._emscripten_bind_Config_set_kCHR_1=b.asm.$x).apply(null,arguments)},uA=b._emscripten_bind_Config_get_kKHR_0=function(){return(uA=b._emscripten_bind_Config_get_kKHR_0=b.asm.ay).apply(null,arguments)},vA=b._emscripten_bind_Config_set_kKHR_1=function(){return(vA=b._emscripten_bind_Config_set_kKHR_1=
|
||||
b.asm.by).apply(null,arguments)},wA=b._emscripten_bind_Config_get_kSHR_0=function(){return(wA=b._emscripten_bind_Config_get_kSHR_0=b.asm.cy).apply(null,arguments)},xA=b._emscripten_bind_Config_set_kSHR_1=function(){return(xA=b._emscripten_bind_Config_set_kSHR_1=b.asm.dy).apply(null,arguments)},yA=b._emscripten_bind_Config_get_kAHR_0=function(){return(yA=b._emscripten_bind_Config_get_kAHR_0=b.asm.ey).apply(null,arguments)},zA=b._emscripten_bind_Config_set_kAHR_1=function(){return(zA=b._emscripten_bind_Config_set_kAHR_1=
|
||||
b.asm.fy).apply(null,arguments)},AA=b._emscripten_bind_Config_get_kSRHR_CL_0=function(){return(AA=b._emscripten_bind_Config_get_kSRHR_CL_0=b.asm.gy).apply(null,arguments)},BA=b._emscripten_bind_Config_set_kSRHR_CL_1=function(){return(BA=b._emscripten_bind_Config_set_kSRHR_CL_1=b.asm.hy).apply(null,arguments)},CA=b._emscripten_bind_Config_get_kSKHR_CL_0=function(){return(CA=b._emscripten_bind_Config_get_kSKHR_CL_0=b.asm.iy).apply(null,arguments)},DA=b._emscripten_bind_Config_set_kSKHR_CL_1=function(){return(DA=
|
||||
b._emscripten_bind_Config_set_kSKHR_CL_1=b.asm.jy).apply(null,arguments)},EA=b._emscripten_bind_Config_get_kSSHR_CL_0=function(){return(EA=b._emscripten_bind_Config_get_kSSHR_CL_0=b.asm.ky).apply(null,arguments)},FA=b._emscripten_bind_Config_set_kSSHR_CL_1=function(){return(FA=b._emscripten_bind_Config_set_kSSHR_CL_1=b.asm.ly).apply(null,arguments)},GA=b._emscripten_bind_Config_get_kSR_SPLT_CL_0=function(){return(GA=b._emscripten_bind_Config_get_kSR_SPLT_CL_0=b.asm.my).apply(null,arguments)},HA=b._emscripten_bind_Config_set_kSR_SPLT_CL_1=
|
||||
function(){return(HA=b._emscripten_bind_Config_set_kSR_SPLT_CL_1=b.asm.ny).apply(null,arguments)},IA=b._emscripten_bind_Config_get_kSK_SPLT_CL_0=function(){return(IA=b._emscripten_bind_Config_get_kSK_SPLT_CL_0=b.asm.oy).apply(null,arguments)},JA=b._emscripten_bind_Config_set_kSK_SPLT_CL_1=function(){return(JA=b._emscripten_bind_Config_set_kSK_SPLT_CL_1=b.asm.py).apply(null,arguments)},KA=b._emscripten_bind_Config_get_kSS_SPLT_CL_0=function(){return(KA=b._emscripten_bind_Config_get_kSS_SPLT_CL_0=b.asm.qy).apply(null,
|
||||
arguments)},LA=b._emscripten_bind_Config_set_kSS_SPLT_CL_1=function(){return(LA=b._emscripten_bind_Config_set_kSS_SPLT_CL_1=b.asm.ry).apply(null,arguments)},MA=b._emscripten_bind_Config_get_maxvolume_0=function(){return(MA=b._emscripten_bind_Config_get_maxvolume_0=b.asm.sy).apply(null,arguments)},NA=b._emscripten_bind_Config_set_maxvolume_1=function(){return(NA=b._emscripten_bind_Config_set_maxvolume_1=b.asm.ty).apply(null,arguments)},OA=b._emscripten_bind_Config_get_timescale_0=function(){return(OA=
|
||||
b._emscripten_bind_Config_get_timescale_0=b.asm.uy).apply(null,arguments)},PA=b._emscripten_bind_Config_set_timescale_1=function(){return(PA=b._emscripten_bind_Config_set_timescale_1=b.asm.vy).apply(null,arguments)},QA=b._emscripten_bind_Config_get_viterations_0=function(){return(QA=b._emscripten_bind_Config_get_viterations_0=b.asm.wy).apply(null,arguments)},RA=b._emscripten_bind_Config_set_viterations_1=function(){return(RA=b._emscripten_bind_Config_set_viterations_1=b.asm.xy).apply(null,arguments)},
|
||||
SA=b._emscripten_bind_Config_get_piterations_0=function(){return(SA=b._emscripten_bind_Config_get_piterations_0=b.asm.yy).apply(null,arguments)},TA=b._emscripten_bind_Config_set_piterations_1=function(){return(TA=b._emscripten_bind_Config_set_piterations_1=b.asm.zy).apply(null,arguments)},UA=b._emscripten_bind_Config_get_diterations_0=function(){return(UA=b._emscripten_bind_Config_get_diterations_0=b.asm.Ay).apply(null,arguments)},VA=b._emscripten_bind_Config_set_diterations_1=function(){return(VA=
|
||||
b._emscripten_bind_Config_set_diterations_1=b.asm.By).apply(null,arguments)},WA=b._emscripten_bind_Config_get_citerations_0=function(){return(WA=b._emscripten_bind_Config_get_citerations_0=b.asm.Cy).apply(null,arguments)},XA=b._emscripten_bind_Config_set_citerations_1=function(){return(XA=b._emscripten_bind_Config_set_citerations_1=b.asm.Dy).apply(null,arguments)},YA=b._emscripten_bind_Config_get_collisions_0=function(){return(YA=b._emscripten_bind_Config_get_collisions_0=b.asm.Ey).apply(null,arguments)},
|
||||
ZA=b._emscripten_bind_Config_set_collisions_1=function(){return(ZA=b._emscripten_bind_Config_set_collisions_1=b.asm.Fy).apply(null,arguments)},$A=b._emscripten_bind_Config___destroy___0=function(){return($A=b._emscripten_bind_Config___destroy___0=b.asm.Gy).apply(null,arguments)},aB=b._emscripten_bind_btSoftBody_btSoftBody_4=function(){return(aB=b._emscripten_bind_btSoftBody_btSoftBody_4=b.asm.Hy).apply(null,arguments)},bB=b._emscripten_bind_btSoftBody_checkLink_2=function(){return(bB=b._emscripten_bind_btSoftBody_checkLink_2=
|
||||
b.asm.Iy).apply(null,arguments)},cB=b._emscripten_bind_btSoftBody_checkFace_3=function(){return(cB=b._emscripten_bind_btSoftBody_checkFace_3=b.asm.Jy).apply(null,arguments)},dB=b._emscripten_bind_btSoftBody_appendMaterial_0=function(){return(dB=b._emscripten_bind_btSoftBody_appendMaterial_0=b.asm.Ky).apply(null,arguments)},eB=b._emscripten_bind_btSoftBody_appendNode_2=function(){return(eB=b._emscripten_bind_btSoftBody_appendNode_2=b.asm.Ly).apply(null,arguments)},fB=b._emscripten_bind_btSoftBody_appendLink_4=
|
||||
function(){return(fB=b._emscripten_bind_btSoftBody_appendLink_4=b.asm.My).apply(null,arguments)},gB=b._emscripten_bind_btSoftBody_appendFace_4=function(){return(gB=b._emscripten_bind_btSoftBody_appendFace_4=b.asm.Ny).apply(null,arguments)},hB=b._emscripten_bind_btSoftBody_appendTetra_5=function(){return(hB=b._emscripten_bind_btSoftBody_appendTetra_5=b.asm.Oy).apply(null,arguments)},iB=b._emscripten_bind_btSoftBody_appendAnchor_4=function(){return(iB=b._emscripten_bind_btSoftBody_appendAnchor_4=b.asm.Py).apply(null,
|
||||
arguments)},jB=b._emscripten_bind_btSoftBody_addForce_1=function(){return(jB=b._emscripten_bind_btSoftBody_addForce_1=b.asm.Qy).apply(null,arguments)},kB=b._emscripten_bind_btSoftBody_addForce_2=function(){return(kB=b._emscripten_bind_btSoftBody_addForce_2=b.asm.Ry).apply(null,arguments)},lB=b._emscripten_bind_btSoftBody_addAeroForceToNode_2=function(){return(lB=b._emscripten_bind_btSoftBody_addAeroForceToNode_2=b.asm.Sy).apply(null,arguments)},mB=b._emscripten_bind_btSoftBody_getTotalMass_0=function(){return(mB=
|
||||
b._emscripten_bind_btSoftBody_getTotalMass_0=b.asm.Ty).apply(null,arguments)},nB=b._emscripten_bind_btSoftBody_setTotalMass_2=function(){return(nB=b._emscripten_bind_btSoftBody_setTotalMass_2=b.asm.Uy).apply(null,arguments)},oB=b._emscripten_bind_btSoftBody_setMass_2=function(){return(oB=b._emscripten_bind_btSoftBody_setMass_2=b.asm.Vy).apply(null,arguments)},pB=b._emscripten_bind_btSoftBody_transform_1=function(){return(pB=b._emscripten_bind_btSoftBody_transform_1=b.asm.Wy).apply(null,arguments)},
|
||||
qB=b._emscripten_bind_btSoftBody_translate_1=function(){return(qB=b._emscripten_bind_btSoftBody_translate_1=b.asm.Xy).apply(null,arguments)},rB=b._emscripten_bind_btSoftBody_rotate_1=function(){return(rB=b._emscripten_bind_btSoftBody_rotate_1=b.asm.Yy).apply(null,arguments)},sB=b._emscripten_bind_btSoftBody_scale_1=function(){return(sB=b._emscripten_bind_btSoftBody_scale_1=b.asm.Zy).apply(null,arguments)},tB=b._emscripten_bind_btSoftBody_generateClusters_1=function(){return(tB=b._emscripten_bind_btSoftBody_generateClusters_1=
|
||||
b.asm._y).apply(null,arguments)},uB=b._emscripten_bind_btSoftBody_generateClusters_2=function(){return(uB=b._emscripten_bind_btSoftBody_generateClusters_2=b.asm.$y).apply(null,arguments)},vB=b._emscripten_bind_btSoftBody_generateBendingConstraints_2=function(){return(vB=b._emscripten_bind_btSoftBody_generateBendingConstraints_2=b.asm.az).apply(null,arguments)},wB=b._emscripten_bind_btSoftBody_upcast_1=function(){return(wB=b._emscripten_bind_btSoftBody_upcast_1=b.asm.bz).apply(null,arguments)},xB=
|
||||
b._emscripten_bind_btSoftBody_getRestLengthScale_0=function(){return(xB=b._emscripten_bind_btSoftBody_getRestLengthScale_0=b.asm.cz).apply(null,arguments)},yB=b._emscripten_bind_btSoftBody_setRestLengthScale_1=function(){return(yB=b._emscripten_bind_btSoftBody_setRestLengthScale_1=b.asm.dz).apply(null,arguments)},zB=b._emscripten_bind_btSoftBody_setAnisotropicFriction_2=function(){return(zB=b._emscripten_bind_btSoftBody_setAnisotropicFriction_2=b.asm.ez).apply(null,arguments)},AB=b._emscripten_bind_btSoftBody_getCollisionShape_0=
|
||||
function(){return(AB=b._emscripten_bind_btSoftBody_getCollisionShape_0=b.asm.fz).apply(null,arguments)},BB=b._emscripten_bind_btSoftBody_setContactProcessingThreshold_1=function(){return(BB=b._emscripten_bind_btSoftBody_setContactProcessingThreshold_1=b.asm.gz).apply(null,arguments)},CB=b._emscripten_bind_btSoftBody_setActivationState_1=function(){return(CB=b._emscripten_bind_btSoftBody_setActivationState_1=b.asm.hz).apply(null,arguments)},DB=b._emscripten_bind_btSoftBody_forceActivationState_1=function(){return(DB=
|
||||
b._emscripten_bind_btSoftBody_forceActivationState_1=b.asm.iz).apply(null,arguments)},EB=b._emscripten_bind_btSoftBody_activate_0=function(){return(EB=b._emscripten_bind_btSoftBody_activate_0=b.asm.jz).apply(null,arguments)},FB=b._emscripten_bind_btSoftBody_activate_1=function(){return(FB=b._emscripten_bind_btSoftBody_activate_1=b.asm.kz).apply(null,arguments)},GB=b._emscripten_bind_btSoftBody_isActive_0=function(){return(GB=b._emscripten_bind_btSoftBody_isActive_0=b.asm.lz).apply(null,arguments)},
|
||||
HB=b._emscripten_bind_btSoftBody_isKinematicObject_0=function(){return(HB=b._emscripten_bind_btSoftBody_isKinematicObject_0=b.asm.mz).apply(null,arguments)},IB=b._emscripten_bind_btSoftBody_isStaticObject_0=function(){return(IB=b._emscripten_bind_btSoftBody_isStaticObject_0=b.asm.nz).apply(null,arguments)},JB=b._emscripten_bind_btSoftBody_isStaticOrKinematicObject_0=function(){return(JB=b._emscripten_bind_btSoftBody_isStaticOrKinematicObject_0=b.asm.oz).apply(null,arguments)},KB=b._emscripten_bind_btSoftBody_getRestitution_0=
|
||||
function(){return(KB=b._emscripten_bind_btSoftBody_getRestitution_0=b.asm.pz).apply(null,arguments)},LB=b._emscripten_bind_btSoftBody_getFriction_0=function(){return(LB=b._emscripten_bind_btSoftBody_getFriction_0=b.asm.qz).apply(null,arguments)},MB=b._emscripten_bind_btSoftBody_getRollingFriction_0=function(){return(MB=b._emscripten_bind_btSoftBody_getRollingFriction_0=b.asm.rz).apply(null,arguments)},NB=b._emscripten_bind_btSoftBody_setRestitution_1=function(){return(NB=b._emscripten_bind_btSoftBody_setRestitution_1=
|
||||
b.asm.sz).apply(null,arguments)},OB=b._emscripten_bind_btSoftBody_setFriction_1=function(){return(OB=b._emscripten_bind_btSoftBody_setFriction_1=b.asm.tz).apply(null,arguments)},PB=b._emscripten_bind_btSoftBody_setRollingFriction_1=function(){return(PB=b._emscripten_bind_btSoftBody_setRollingFriction_1=b.asm.uz).apply(null,arguments)},QB=b._emscripten_bind_btSoftBody_getWorldTransform_0=function(){return(QB=b._emscripten_bind_btSoftBody_getWorldTransform_0=b.asm.vz).apply(null,arguments)},RB=b._emscripten_bind_btSoftBody_getCollisionFlags_0=
|
||||
function(){return(RB=b._emscripten_bind_btSoftBody_getCollisionFlags_0=b.asm.wz).apply(null,arguments)},SB=b._emscripten_bind_btSoftBody_setCollisionFlags_1=function(){return(SB=b._emscripten_bind_btSoftBody_setCollisionFlags_1=b.asm.xz).apply(null,arguments)},TB=b._emscripten_bind_btSoftBody_setWorldTransform_1=function(){return(TB=b._emscripten_bind_btSoftBody_setWorldTransform_1=b.asm.yz).apply(null,arguments)},UB=b._emscripten_bind_btSoftBody_setCollisionShape_1=function(){return(UB=b._emscripten_bind_btSoftBody_setCollisionShape_1=
|
||||
b.asm.zz).apply(null,arguments)},VB=b._emscripten_bind_btSoftBody_setCcdMotionThreshold_1=function(){return(VB=b._emscripten_bind_btSoftBody_setCcdMotionThreshold_1=b.asm.Az).apply(null,arguments)},WB=b._emscripten_bind_btSoftBody_setCcdSweptSphereRadius_1=function(){return(WB=b._emscripten_bind_btSoftBody_setCcdSweptSphereRadius_1=b.asm.Bz).apply(null,arguments)},XB=b._emscripten_bind_btSoftBody_getUserIndex_0=function(){return(XB=b._emscripten_bind_btSoftBody_getUserIndex_0=b.asm.Cz).apply(null,
|
||||
arguments)},YB=b._emscripten_bind_btSoftBody_setUserIndex_1=function(){return(YB=b._emscripten_bind_btSoftBody_setUserIndex_1=b.asm.Dz).apply(null,arguments)},ZB=b._emscripten_bind_btSoftBody_getUserPointer_0=function(){return(ZB=b._emscripten_bind_btSoftBody_getUserPointer_0=b.asm.Ez).apply(null,arguments)},$B=b._emscripten_bind_btSoftBody_setUserPointer_1=function(){return($B=b._emscripten_bind_btSoftBody_setUserPointer_1=b.asm.Fz).apply(null,arguments)},aC=b._emscripten_bind_btSoftBody_getBroadphaseHandle_0=
|
||||
function(){return(aC=b._emscripten_bind_btSoftBody_getBroadphaseHandle_0=b.asm.Gz).apply(null,arguments)},bC=b._emscripten_bind_btSoftBody_get_m_cfg_0=function(){return(bC=b._emscripten_bind_btSoftBody_get_m_cfg_0=b.asm.Hz).apply(null,arguments)},cC=b._emscripten_bind_btSoftBody_set_m_cfg_1=function(){return(cC=b._emscripten_bind_btSoftBody_set_m_cfg_1=b.asm.Iz).apply(null,arguments)},dC=b._emscripten_bind_btSoftBody_get_m_nodes_0=function(){return(dC=b._emscripten_bind_btSoftBody_get_m_nodes_0=b.asm.Jz).apply(null,
|
||||
arguments)},eC=b._emscripten_bind_btSoftBody_set_m_nodes_1=function(){return(eC=b._emscripten_bind_btSoftBody_set_m_nodes_1=b.asm.Kz).apply(null,arguments)},fC=b._emscripten_bind_btSoftBody_get_m_faces_0=function(){return(fC=b._emscripten_bind_btSoftBody_get_m_faces_0=b.asm.Lz).apply(null,arguments)},gC=b._emscripten_bind_btSoftBody_set_m_faces_1=function(){return(gC=b._emscripten_bind_btSoftBody_set_m_faces_1=b.asm.Mz).apply(null,arguments)},hC=b._emscripten_bind_btSoftBody_get_m_materials_0=function(){return(hC=
|
||||
b._emscripten_bind_btSoftBody_get_m_materials_0=b.asm.Nz).apply(null,arguments)},iC=b._emscripten_bind_btSoftBody_set_m_materials_1=function(){return(iC=b._emscripten_bind_btSoftBody_set_m_materials_1=b.asm.Oz).apply(null,arguments)},jC=b._emscripten_bind_btSoftBody_get_m_anchors_0=function(){return(jC=b._emscripten_bind_btSoftBody_get_m_anchors_0=b.asm.Pz).apply(null,arguments)},kC=b._emscripten_bind_btSoftBody_set_m_anchors_1=function(){return(kC=b._emscripten_bind_btSoftBody_set_m_anchors_1=b.asm.Qz).apply(null,
|
||||
arguments)},lC=b._emscripten_bind_btSoftBody___destroy___0=function(){return(lC=b._emscripten_bind_btSoftBody___destroy___0=b.asm.Rz).apply(null,arguments)},mC=b._emscripten_bind_btSoftBodyRigidBodyCollisionConfiguration_btSoftBodyRigidBodyCollisionConfiguration_0=function(){return(mC=b._emscripten_bind_btSoftBodyRigidBodyCollisionConfiguration_btSoftBodyRigidBodyCollisionConfiguration_0=b.asm.Sz).apply(null,arguments)},nC=b._emscripten_bind_btSoftBodyRigidBodyCollisionConfiguration_btSoftBodyRigidBodyCollisionConfiguration_1=
|
||||
function(){return(nC=b._emscripten_bind_btSoftBodyRigidBodyCollisionConfiguration_btSoftBodyRigidBodyCollisionConfiguration_1=b.asm.Tz).apply(null,arguments)},oC=b._emscripten_bind_btSoftBodyRigidBodyCollisionConfiguration___destroy___0=function(){return(oC=b._emscripten_bind_btSoftBodyRigidBodyCollisionConfiguration___destroy___0=b.asm.Uz).apply(null,arguments)},pC=b._emscripten_bind_btDefaultSoftBodySolver_btDefaultSoftBodySolver_0=function(){return(pC=b._emscripten_bind_btDefaultSoftBodySolver_btDefaultSoftBodySolver_0=
|
||||
b.asm.Vz).apply(null,arguments)},qC=b._emscripten_bind_btDefaultSoftBodySolver___destroy___0=function(){return(qC=b._emscripten_bind_btDefaultSoftBodySolver___destroy___0=b.asm.Wz).apply(null,arguments)},rC=b._emscripten_bind_btSoftBodyArray_size_0=function(){return(rC=b._emscripten_bind_btSoftBodyArray_size_0=b.asm.Xz).apply(null,arguments)},sC=b._emscripten_bind_btSoftBodyArray_at_1=function(){return(sC=b._emscripten_bind_btSoftBodyArray_at_1=b.asm.Yz).apply(null,arguments)},tC=b._emscripten_bind_btSoftBodyArray___destroy___0=
|
||||
function(){return(tC=b._emscripten_bind_btSoftBodyArray___destroy___0=b.asm.Zz).apply(null,arguments)},uC=b._emscripten_bind_btSoftRigidDynamicsWorld_btSoftRigidDynamicsWorld_5=function(){return(uC=b._emscripten_bind_btSoftRigidDynamicsWorld_btSoftRigidDynamicsWorld_5=b.asm._z).apply(null,arguments)},vC=b._emscripten_bind_btSoftRigidDynamicsWorld_addSoftBody_3=function(){return(vC=b._emscripten_bind_btSoftRigidDynamicsWorld_addSoftBody_3=b.asm.$z).apply(null,arguments)},wC=b._emscripten_bind_btSoftRigidDynamicsWorld_removeSoftBody_1=
|
||||
function(){return(wC=b._emscripten_bind_btSoftRigidDynamicsWorld_removeSoftBody_1=b.asm.aA).apply(null,arguments)},xC=b._emscripten_bind_btSoftRigidDynamicsWorld_removeCollisionObject_1=function(){return(xC=b._emscripten_bind_btSoftRigidDynamicsWorld_removeCollisionObject_1=b.asm.bA).apply(null,arguments)},yC=b._emscripten_bind_btSoftRigidDynamicsWorld_getWorldInfo_0=function(){return(yC=b._emscripten_bind_btSoftRigidDynamicsWorld_getWorldInfo_0=b.asm.cA).apply(null,arguments)},zC=b._emscripten_bind_btSoftRigidDynamicsWorld_getSoftBodyArray_0=
|
||||
function(){return(zC=b._emscripten_bind_btSoftRigidDynamicsWorld_getSoftBodyArray_0=b.asm.dA).apply(null,arguments)},AC=b._emscripten_bind_btSoftRigidDynamicsWorld_getDispatcher_0=function(){return(AC=b._emscripten_bind_btSoftRigidDynamicsWorld_getDispatcher_0=b.asm.eA).apply(null,arguments)},BC=b._emscripten_bind_btSoftRigidDynamicsWorld_rayTest_3=function(){return(BC=b._emscripten_bind_btSoftRigidDynamicsWorld_rayTest_3=b.asm.fA).apply(null,arguments)},CC=b._emscripten_bind_btSoftRigidDynamicsWorld_getPairCache_0=
|
||||
function(){return(CC=b._emscripten_bind_btSoftRigidDynamicsWorld_getPairCache_0=b.asm.gA).apply(null,arguments)},DC=b._emscripten_bind_btSoftRigidDynamicsWorld_getDispatchInfo_0=function(){return(DC=b._emscripten_bind_btSoftRigidDynamicsWorld_getDispatchInfo_0=b.asm.hA).apply(null,arguments)},EC=b._emscripten_bind_btSoftRigidDynamicsWorld_addCollisionObject_1=function(){return(EC=b._emscripten_bind_btSoftRigidDynamicsWorld_addCollisionObject_1=b.asm.iA).apply(null,arguments)},FC=b._emscripten_bind_btSoftRigidDynamicsWorld_addCollisionObject_2=
|
||||
function(){return(FC=b._emscripten_bind_btSoftRigidDynamicsWorld_addCollisionObject_2=b.asm.jA).apply(null,arguments)},GC=b._emscripten_bind_btSoftRigidDynamicsWorld_addCollisionObject_3=function(){return(GC=b._emscripten_bind_btSoftRigidDynamicsWorld_addCollisionObject_3=b.asm.kA).apply(null,arguments)},HC=b._emscripten_bind_btSoftRigidDynamicsWorld_getBroadphase_0=function(){return(HC=b._emscripten_bind_btSoftRigidDynamicsWorld_getBroadphase_0=b.asm.lA).apply(null,arguments)},IC=b._emscripten_bind_btSoftRigidDynamicsWorld_convexSweepTest_5=
|
||||
function(){return(IC=b._emscripten_bind_btSoftRigidDynamicsWorld_convexSweepTest_5=b.asm.mA).apply(null,arguments)},JC=b._emscripten_bind_btSoftRigidDynamicsWorld_contactPairTest_3=function(){return(JC=b._emscripten_bind_btSoftRigidDynamicsWorld_contactPairTest_3=b.asm.nA).apply(null,arguments)},KC=b._emscripten_bind_btSoftRigidDynamicsWorld_contactTest_2=function(){return(KC=b._emscripten_bind_btSoftRigidDynamicsWorld_contactTest_2=b.asm.oA).apply(null,arguments)},LC=b._emscripten_bind_btSoftRigidDynamicsWorld_updateSingleAabb_1=
|
||||
function(){return(LC=b._emscripten_bind_btSoftRigidDynamicsWorld_updateSingleAabb_1=b.asm.pA).apply(null,arguments)},MC=b._emscripten_bind_btSoftRigidDynamicsWorld_setDebugDrawer_1=function(){return(MC=b._emscripten_bind_btSoftRigidDynamicsWorld_setDebugDrawer_1=b.asm.qA).apply(null,arguments)},NC=b._emscripten_bind_btSoftRigidDynamicsWorld_getDebugDrawer_0=function(){return(NC=b._emscripten_bind_btSoftRigidDynamicsWorld_getDebugDrawer_0=b.asm.rA).apply(null,arguments)},OC=b._emscripten_bind_btSoftRigidDynamicsWorld_debugDrawWorld_0=
|
||||
function(){return(OC=b._emscripten_bind_btSoftRigidDynamicsWorld_debugDrawWorld_0=b.asm.sA).apply(null,arguments)},PC=b._emscripten_bind_btSoftRigidDynamicsWorld_debugDrawObject_3=function(){return(PC=b._emscripten_bind_btSoftRigidDynamicsWorld_debugDrawObject_3=b.asm.tA).apply(null,arguments)},QC=b._emscripten_bind_btSoftRigidDynamicsWorld_setGravity_1=function(){return(QC=b._emscripten_bind_btSoftRigidDynamicsWorld_setGravity_1=b.asm.uA).apply(null,arguments)},RC=b._emscripten_bind_btSoftRigidDynamicsWorld_getGravity_0=
|
||||
function(){return(RC=b._emscripten_bind_btSoftRigidDynamicsWorld_getGravity_0=b.asm.vA).apply(null,arguments)},SC=b._emscripten_bind_btSoftRigidDynamicsWorld_addRigidBody_1=function(){return(SC=b._emscripten_bind_btSoftRigidDynamicsWorld_addRigidBody_1=b.asm.wA).apply(null,arguments)},TC=b._emscripten_bind_btSoftRigidDynamicsWorld_addRigidBody_3=function(){return(TC=b._emscripten_bind_btSoftRigidDynamicsWorld_addRigidBody_3=b.asm.xA).apply(null,arguments)},UC=b._emscripten_bind_btSoftRigidDynamicsWorld_removeRigidBody_1=
|
||||
function(){return(UC=b._emscripten_bind_btSoftRigidDynamicsWorld_removeRigidBody_1=b.asm.yA).apply(null,arguments)},VC=b._emscripten_bind_btSoftRigidDynamicsWorld_addConstraint_1=function(){return(VC=b._emscripten_bind_btSoftRigidDynamicsWorld_addConstraint_1=b.asm.zA).apply(null,arguments)},WC=b._emscripten_bind_btSoftRigidDynamicsWorld_addConstraint_2=function(){return(WC=b._emscripten_bind_btSoftRigidDynamicsWorld_addConstraint_2=b.asm.AA).apply(null,arguments)},XC=b._emscripten_bind_btSoftRigidDynamicsWorld_removeConstraint_1=
|
||||
function(){return(XC=b._emscripten_bind_btSoftRigidDynamicsWorld_removeConstraint_1=b.asm.BA).apply(null,arguments)},YC=b._emscripten_bind_btSoftRigidDynamicsWorld_stepSimulation_1=function(){return(YC=b._emscripten_bind_btSoftRigidDynamicsWorld_stepSimulation_1=b.asm.CA).apply(null,arguments)},ZC=b._emscripten_bind_btSoftRigidDynamicsWorld_stepSimulation_2=function(){return(ZC=b._emscripten_bind_btSoftRigidDynamicsWorld_stepSimulation_2=b.asm.DA).apply(null,arguments)},$C=b._emscripten_bind_btSoftRigidDynamicsWorld_stepSimulation_3=
|
||||
function(){return($C=b._emscripten_bind_btSoftRigidDynamicsWorld_stepSimulation_3=b.asm.EA).apply(null,arguments)},aD=b._emscripten_bind_btSoftRigidDynamicsWorld_setContactAddedCallback_1=function(){return(aD=b._emscripten_bind_btSoftRigidDynamicsWorld_setContactAddedCallback_1=b.asm.FA).apply(null,arguments)},bD=b._emscripten_bind_btSoftRigidDynamicsWorld_setContactProcessedCallback_1=function(){return(bD=b._emscripten_bind_btSoftRigidDynamicsWorld_setContactProcessedCallback_1=b.asm.GA).apply(null,
|
||||
arguments)},cD=b._emscripten_bind_btSoftRigidDynamicsWorld_setContactDestroyedCallback_1=function(){return(cD=b._emscripten_bind_btSoftRigidDynamicsWorld_setContactDestroyedCallback_1=b.asm.HA).apply(null,arguments)},dD=b._emscripten_bind_btSoftRigidDynamicsWorld_addAction_1=function(){return(dD=b._emscripten_bind_btSoftRigidDynamicsWorld_addAction_1=b.asm.IA).apply(null,arguments)},eD=b._emscripten_bind_btSoftRigidDynamicsWorld_removeAction_1=function(){return(eD=b._emscripten_bind_btSoftRigidDynamicsWorld_removeAction_1=
|
||||
b.asm.JA).apply(null,arguments)},fD=b._emscripten_bind_btSoftRigidDynamicsWorld_getSolverInfo_0=function(){return(fD=b._emscripten_bind_btSoftRigidDynamicsWorld_getSolverInfo_0=b.asm.KA).apply(null,arguments)},gD=b._emscripten_bind_btSoftRigidDynamicsWorld_setInternalTickCallback_1=function(){return(gD=b._emscripten_bind_btSoftRigidDynamicsWorld_setInternalTickCallback_1=b.asm.LA).apply(null,arguments)},hD=b._emscripten_bind_btSoftRigidDynamicsWorld_setInternalTickCallback_2=function(){return(hD=
|
||||
b._emscripten_bind_btSoftRigidDynamicsWorld_setInternalTickCallback_2=b.asm.MA).apply(null,arguments)},iD=b._emscripten_bind_btSoftRigidDynamicsWorld_setInternalTickCallback_3=function(){return(iD=b._emscripten_bind_btSoftRigidDynamicsWorld_setInternalTickCallback_3=b.asm.NA).apply(null,arguments)},jD=b._emscripten_bind_btSoftRigidDynamicsWorld___destroy___0=function(){return(jD=b._emscripten_bind_btSoftRigidDynamicsWorld___destroy___0=b.asm.OA).apply(null,arguments)},kD=b._emscripten_bind_btSoftBodyHelpers_btSoftBodyHelpers_0=
|
||||
function(){return(kD=b._emscripten_bind_btSoftBodyHelpers_btSoftBodyHelpers_0=b.asm.PA).apply(null,arguments)},lD=b._emscripten_bind_btSoftBodyHelpers_CreateRope_5=function(){return(lD=b._emscripten_bind_btSoftBodyHelpers_CreateRope_5=b.asm.QA).apply(null,arguments)},mD=b._emscripten_bind_btSoftBodyHelpers_CreatePatch_9=function(){return(mD=b._emscripten_bind_btSoftBodyHelpers_CreatePatch_9=b.asm.RA).apply(null,arguments)},nD=b._emscripten_bind_btSoftBodyHelpers_CreatePatchUV_10=function(){return(nD=
|
||||
b._emscripten_bind_btSoftBodyHelpers_CreatePatchUV_10=b.asm.SA).apply(null,arguments)},oD=b._emscripten_bind_btSoftBodyHelpers_CreateEllipsoid_4=function(){return(oD=b._emscripten_bind_btSoftBodyHelpers_CreateEllipsoid_4=b.asm.TA).apply(null,arguments)},pD=b._emscripten_bind_btSoftBodyHelpers_CreateFromTriMesh_5=function(){return(pD=b._emscripten_bind_btSoftBodyHelpers_CreateFromTriMesh_5=b.asm.UA).apply(null,arguments)},qD=b._emscripten_bind_btSoftBodyHelpers_CreateFromConvexHull_4=function(){return(qD=
|
||||
b._emscripten_bind_btSoftBodyHelpers_CreateFromConvexHull_4=b.asm.VA).apply(null,arguments)},rD=b._emscripten_bind_btSoftBodyHelpers___destroy___0=function(){return(rD=b._emscripten_bind_btSoftBodyHelpers___destroy___0=b.asm.WA).apply(null,arguments)},sD=b._emscripten_enum_PHY_ScalarType_PHY_FLOAT=function(){return(sD=b._emscripten_enum_PHY_ScalarType_PHY_FLOAT=b.asm.XA).apply(null,arguments)},tD=b._emscripten_enum_PHY_ScalarType_PHY_DOUBLE=function(){return(tD=b._emscripten_enum_PHY_ScalarType_PHY_DOUBLE=
|
||||
b.asm.YA).apply(null,arguments)},uD=b._emscripten_enum_PHY_ScalarType_PHY_INTEGER=function(){return(uD=b._emscripten_enum_PHY_ScalarType_PHY_INTEGER=b.asm.ZA).apply(null,arguments)},vD=b._emscripten_enum_PHY_ScalarType_PHY_SHORT=function(){return(vD=b._emscripten_enum_PHY_ScalarType_PHY_SHORT=b.asm._A).apply(null,arguments)},wD=b._emscripten_enum_PHY_ScalarType_PHY_FIXEDPOINT88=function(){return(wD=b._emscripten_enum_PHY_ScalarType_PHY_FIXEDPOINT88=b.asm.$A).apply(null,arguments)},xD=b._emscripten_enum_PHY_ScalarType_PHY_UCHAR=
|
||||
function(){return(xD=b._emscripten_enum_PHY_ScalarType_PHY_UCHAR=b.asm.aB).apply(null,arguments)},yD=b._emscripten_enum_eGIMPACT_SHAPE_TYPE_CONST_GIMPACT_COMPOUND_SHAPE=function(){return(yD=b._emscripten_enum_eGIMPACT_SHAPE_TYPE_CONST_GIMPACT_COMPOUND_SHAPE=b.asm.bB).apply(null,arguments)},zD=b._emscripten_enum_eGIMPACT_SHAPE_TYPE_CONST_GIMPACT_TRIMESH_SHAPE_PART=function(){return(zD=b._emscripten_enum_eGIMPACT_SHAPE_TYPE_CONST_GIMPACT_TRIMESH_SHAPE_PART=b.asm.cB).apply(null,arguments)},AD=b._emscripten_enum_eGIMPACT_SHAPE_TYPE_CONST_GIMPACT_TRIMESH_SHAPE=
|
||||
function(){return(AD=b._emscripten_enum_eGIMPACT_SHAPE_TYPE_CONST_GIMPACT_TRIMESH_SHAPE=b.asm.dB).apply(null,arguments)},BD=b._emscripten_enum_btConstraintParams_BT_CONSTRAINT_ERP=function(){return(BD=b._emscripten_enum_btConstraintParams_BT_CONSTRAINT_ERP=b.asm.eB).apply(null,arguments)},CD=b._emscripten_enum_btConstraintParams_BT_CONSTRAINT_STOP_ERP=function(){return(CD=b._emscripten_enum_btConstraintParams_BT_CONSTRAINT_STOP_ERP=b.asm.fB).apply(null,arguments)},DD=b._emscripten_enum_btConstraintParams_BT_CONSTRAINT_CFM=
|
||||
function(){return(DD=b._emscripten_enum_btConstraintParams_BT_CONSTRAINT_CFM=b.asm.gB).apply(null,arguments)},ED=b._emscripten_enum_btConstraintParams_BT_CONSTRAINT_STOP_CFM=function(){return(ED=b._emscripten_enum_btConstraintParams_BT_CONSTRAINT_STOP_CFM=b.asm.hB).apply(null,arguments)};b._malloc=function(){return(b._malloc=b.asm.jB).apply(null,arguments)};b.___start_em_js=27240;b.___stop_em_js=27338;b.UTF8ToString=sa;
|
||||
b.addFunction=function(a,c){if(!Ua){Ua=new WeakMap;var d=ya.length;if(Ua)for(var e=0;e<0+d;e++){var g=e;var n=Ta[g];n||(g>=Ta.length&&(Ta.length=g+1),Ta[g]=n=ya.get(g));(g=n)&&Ua.set(g,e)}}if(d=Ua.get(a)||0)return d;if(Va.length)d=Va.pop();else{try{ya.grow(1)}catch(T){if(!(T instanceof RangeError))throw T;throw"Unable to grow wasm table. Set ALLOW_TABLE_GROWTH.";}d=ya.length-1}try{e=d,ya.set(e,a),Ta[e]=ya.get(e)}catch(T){if(!(T instanceof TypeError))throw T;if("function"==typeof WebAssembly.Function){e=
|
||||
WebAssembly.Function;g={i:"i32",j:"i32",f:"f32",d:"f64",p:"i32"};n={parameters:[],results:"v"==c[0]?[]:[g[c[0]]]};for(var z=1;z<c.length;++z)n.parameters.push(g[c[z]]),"j"===c[z]&&n.parameters.push("i32");c=new e(n,a)}else{e=[1];g=c.slice(0,1);c=c.slice(1);n={i:127,p:127,j:126,f:125,d:124};e.push(96);z=c.length;128>z?e.push(z):e.push(z%128|128,z>>7);for(z=0;z<c.length;++z)e.push(n[c[z]]);"v"==g?e.push(0):e.push(1,n[g]);c=[0,97,115,109,1,0,0,0,1];g=e.length;128>g?c.push(g):c.push(g%128|128,g>>7);c.push.apply(c,
|
||||
e);c.push(2,7,1,1,101,1,102,0,0,7,5,1,1,102,0,0);c=new WebAssembly.Module(new Uint8Array(c));c=(new WebAssembly.Instance(c,{e:{f:a}})).exports.f}e=d;ya.set(e,c);Ta[e]=ya.get(e)}Ua.set(a,d);return d};var FD;Ha=function GD(){FD||HD();FD||(Ha=GD)};
|
||||
function HD(){function a(){if(!FD&&(FD=!0,b.calledRun=!0,!qa)){Ca=!0;Qa(Aa);aa(b);if(b.onRuntimeInitialized)b.onRuntimeInitialized();if(b.postRun)for("function"==typeof b.postRun&&(b.postRun=[b.postRun]);b.postRun.length;){var c=b.postRun.shift();Ba.unshift(c)}Qa(Ba)}}if(!(0<Fa)){if(b.preRun)for("function"==typeof b.preRun&&(b.preRun=[b.preRun]);b.preRun.length;)Ea();Qa(za);0<Fa||(b.setStatus?(b.setStatus("Running..."),setTimeout(function(){setTimeout(function(){b.setStatus("")},1);a()},1)):a())}}
|
||||
if(b.preInit)for("function"==typeof b.preInit&&(b.preInit=[b.preInit]);0<b.preInit.length;)b.preInit.pop()();HD();function f(){}f.prototype=Object.create(f.prototype);f.prototype.constructor=f;f.prototype.lB=f;f.mB={};b.WrapperObject=f;function h(a){return(a||f).mB}b.getCache=h;function k(a,c){var d=h(c),e=d[a];if(e)return e;e=Object.create((c||f).prototype);e.kB=a;return d[a]=e}b.wrapPointer=k;b.castObject=function(a,c){return k(a.kB,c)};b.NULL=k(0);
|
||||
b.destroy=function(a){if(!a.__destroy__)throw"Error: Cannot destroy object. (Did you create it yourself?)";a.__destroy__();delete h(a.lB)[a.kB]};b.compare=function(a,c){return a.kB===c.kB};b.getPointer=function(a){return a.kB};b.getClass=function(a){return a.lB};var ID=0,JD=0,KD=0,LD=[],MD=0;function ND(){if(MD){for(var a=0;a<LD.length;a++)b._free(LD[a]);LD.length=0;b._free(ID);ID=0;JD+=MD;MD=0}ID||(JD+=128,(ID=b._malloc(JD))||oa());KD=0}
|
||||
function OD(a,c){ID||oa();a=a.length*c.BYTES_PER_ELEMENT;a=a+7&-8;KD+a>=JD?(0<a||oa(),MD+=a,c=b._malloc(a),LD.push(c)):(c=ID+KD,KD+=a);return c}function PD(a,c,d){d>>>=0;switch(c.BYTES_PER_ELEMENT){case 2:d>>>=1;break;case 4:d>>>=2;break;case 8:d>>>=3}for(var e=0;e<a.length;e++)c[d+e]=a[e]}
|
||||
function QD(a){if("string"===typeof a){for(var c=0,d=0;d<a.length;++d){var e=a.charCodeAt(d);127>=e?c++:2047>=e?c+=2:55296<=e&&57343>=e?(c+=4,++d):c+=3}c=Array(c+1);e=c.length;d=0;if(0<e){e=d+e-1;for(var g=0;g<a.length;++g){var n=a.charCodeAt(g);if(55296<=n&&57343>=n){var z=a.charCodeAt(++g);n=65536+((n&1023)<<10)|z&1023}if(127>=n){if(d>=e)break;c[d++]=n}else{if(2047>=n){if(d+1>=e)break;c[d++]=192|n>>6}else{if(65535>=n){if(d+2>=e)break;c[d++]=224|n>>12}else{if(d+3>=e)break;c[d++]=240|n>>18;c[d++]=
|
||||
128|n>>12&63}c[d++]=128|n>>6&63}c[d++]=128|n&63}}c[d]=0}a=OD(c,ua);PD(c,ua,a);return a}return a}function RD(a){if("object"===typeof a){var c=OD(a,wa);PD(a,wa,c);return c}return a}function l(){throw"cannot construct a btCollisionShape, no constructor in IDL";}l.prototype=Object.create(f.prototype);l.prototype.constructor=l;l.prototype.lB=l;l.mB={};b.btCollisionShape=l;l.prototype.setLocalScaling=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Xa(c,a)};
|
||||
l.prototype.getLocalScaling=function(){return k(Ya(this.kB),m)};l.prototype.calculateLocalInertia=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);Za(d,a,c)};l.prototype.setMargin=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);$a(c,a)};l.prototype.getMargin=function(){return ab(this.kB)};l.prototype.__destroy__=function(){bb(this.kB)};function SD(){throw"cannot construct a btCollisionWorld, no constructor in IDL";}SD.prototype=Object.create(f.prototype);
|
||||
SD.prototype.constructor=SD;SD.prototype.lB=SD;SD.mB={};b.btCollisionWorld=SD;SD.prototype.getDispatcher=function(){return k(cb(this.kB),TD)};SD.prototype.rayTest=function(a,c,d){var e=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);db(e,a,c,d)};SD.prototype.getPairCache=function(){return k(eb(this.kB),UD)};SD.prototype.getDispatchInfo=function(){return k(fb(this.kB),p)};
|
||||
SD.prototype.addCollisionObject=function(a,c,d){var e=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);void 0===c?gb(e,a):void 0===d?hb(e,a,c):ib(e,a,c,d)};SD.prototype.removeCollisionObject=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);jb(c,a)};SD.prototype.getBroadphase=function(){return k(kb(this.kB),VD)};
|
||||
SD.prototype.convexSweepTest=function(a,c,d,e,g){var n=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);e&&"object"===typeof e&&(e=e.kB);g&&"object"===typeof g&&(g=g.kB);lb(n,a,c,d,e,g)};SD.prototype.contactPairTest=function(a,c,d){var e=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);mb(e,a,c,d)};
|
||||
SD.prototype.contactTest=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);nb(d,a,c)};SD.prototype.updateSingleAabb=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);ob(c,a)};SD.prototype.setDebugDrawer=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);pb(c,a)};SD.prototype.getDebugDrawer=function(){return k(qb(this.kB),WD)};SD.prototype.debugDrawWorld=function(){rb(this.kB)};
|
||||
SD.prototype.debugDrawObject=function(a,c,d){var e=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);sb(e,a,c,d)};SD.prototype.__destroy__=function(){tb(this.kB)};function q(){throw"cannot construct a btCollisionObject, no constructor in IDL";}q.prototype=Object.create(f.prototype);q.prototype.constructor=q;q.prototype.lB=q;q.mB={};b.btCollisionObject=q;
|
||||
q.prototype.setAnisotropicFriction=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);ub(d,a,c)};q.prototype.getCollisionShape=function(){return k(vb(this.kB),l)};q.prototype.setContactProcessingThreshold=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);wb(c,a)};q.prototype.setActivationState=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);xb(c,a)};
|
||||
q.prototype.forceActivationState=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);yb(c,a)};q.prototype.activate=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);void 0===a?zb(c):Ab(c,a)};q.prototype.isActive=function(){return!!Bb(this.kB)};q.prototype.isKinematicObject=function(){return!!Cb(this.kB)};q.prototype.isStaticObject=function(){return!!Db(this.kB)};q.prototype.isStaticOrKinematicObject=function(){return!!Eb(this.kB)};q.prototype.getRestitution=function(){return Fb(this.kB)};
|
||||
q.prototype.getFriction=function(){return Gb(this.kB)};q.prototype.getRollingFriction=function(){return Hb(this.kB)};q.prototype.setRestitution=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Ib(c,a)};q.prototype.setFriction=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Jb(c,a)};q.prototype.setRollingFriction=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Kb(c,a)};q.prototype.getWorldTransform=function(){return k(Lb(this.kB),r)};q.prototype.getCollisionFlags=function(){return Mb(this.kB)};
|
||||
q.prototype.setCollisionFlags=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Nb(c,a)};q.prototype.setWorldTransform=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Ob(c,a)};q.prototype.setCollisionShape=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Pb(c,a)};q.prototype.setCcdMotionThreshold=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Qb(c,a)};q.prototype.setCcdSweptSphereRadius=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Rb(c,a)};
|
||||
q.prototype.getUserIndex=function(){return Sb(this.kB)};q.prototype.setUserIndex=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Tb(c,a)};q.prototype.getUserPointer=function(){return k(Ub(this.kB),XD)};q.prototype.setUserPointer=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Vb(c,a)};q.prototype.getBroadphaseHandle=function(){return k(Wb(this.kB),YD)};q.prototype.__destroy__=function(){Xb(this.kB)};
|
||||
function ZD(){throw"cannot construct a btConcaveShape, no constructor in IDL";}ZD.prototype=Object.create(l.prototype);ZD.prototype.constructor=ZD;ZD.prototype.lB=ZD;ZD.mB={};b.btConcaveShape=ZD;ZD.prototype.setLocalScaling=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Yb(c,a)};ZD.prototype.getLocalScaling=function(){return k(Zb(this.kB),m)};ZD.prototype.calculateLocalInertia=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);$b(d,a,c)};
|
||||
ZD.prototype.__destroy__=function(){ac(this.kB)};function $D(){throw"cannot construct a btCollisionAlgorithm, no constructor in IDL";}$D.prototype=Object.create(f.prototype);$D.prototype.constructor=$D;$D.prototype.lB=$D;$D.mB={};b.btCollisionAlgorithm=$D;$D.prototype.__destroy__=function(){bc(this.kB)};function aE(){throw"cannot construct a btTypedConstraint, no constructor in IDL";}aE.prototype=Object.create(f.prototype);aE.prototype.constructor=aE;aE.prototype.lB=aE;aE.mB={};
|
||||
b.btTypedConstraint=aE;aE.prototype.enableFeedback=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);cc(c,a)};aE.prototype.getBreakingImpulseThreshold=function(){return ec(this.kB)};aE.prototype.setBreakingImpulseThreshold=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);fc(c,a)};aE.prototype.getParam=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);return gc(d,a,c)};
|
||||
aE.prototype.setParam=function(a,c,d){var e=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);hc(e,a,c,d)};aE.prototype.__destroy__=function(){ic(this.kB)};function bE(){throw"cannot construct a btDynamicsWorld, no constructor in IDL";}bE.prototype=Object.create(SD.prototype);bE.prototype.constructor=bE;bE.prototype.lB=bE;bE.mB={};b.btDynamicsWorld=bE;bE.prototype.addAction=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);jc(c,a)};
|
||||
bE.prototype.removeAction=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);kc(c,a)};bE.prototype.getSolverInfo=function(){return k(lc(this.kB),t)};bE.prototype.setInternalTickCallback=function(a,c,d){var e=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);void 0===c?mc(e,a):void 0===d?nc(e,a,c):oc(e,a,c,d)};bE.prototype.getDispatcher=function(){return k(pc(this.kB),TD)};
|
||||
bE.prototype.rayTest=function(a,c,d){var e=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);qc(e,a,c,d)};bE.prototype.getPairCache=function(){return k(rc(this.kB),UD)};bE.prototype.getDispatchInfo=function(){return k(sc(this.kB),p)};
|
||||
bE.prototype.addCollisionObject=function(a,c,d){var e=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);void 0===c?tc(e,a):void 0===d?uc(e,a,c):vc(e,a,c,d)};bE.prototype.removeCollisionObject=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);wc(c,a)};bE.prototype.getBroadphase=function(){return k(xc(this.kB),VD)};
|
||||
bE.prototype.convexSweepTest=function(a,c,d,e,g){var n=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);e&&"object"===typeof e&&(e=e.kB);g&&"object"===typeof g&&(g=g.kB);yc(n,a,c,d,e,g)};bE.prototype.contactPairTest=function(a,c,d){var e=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);zc(e,a,c,d)};
|
||||
bE.prototype.contactTest=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);Ac(d,a,c)};bE.prototype.updateSingleAabb=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Bc(c,a)};bE.prototype.setDebugDrawer=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Cc(c,a)};bE.prototype.getDebugDrawer=function(){return k(Dc(this.kB),WD)};bE.prototype.debugDrawWorld=function(){Ec(this.kB)};
|
||||
bE.prototype.debugDrawObject=function(a,c,d){var e=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);Fc(e,a,c,d)};bE.prototype.__destroy__=function(){Gc(this.kB)};function WD(){throw"cannot construct a btIDebugDraw, no constructor in IDL";}WD.prototype=Object.create(f.prototype);WD.prototype.constructor=WD;WD.prototype.lB=WD;WD.mB={};b.btIDebugDraw=WD;
|
||||
WD.prototype.drawLine=function(a,c,d){var e=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);Hc(e,a,c,d)};WD.prototype.drawContactPoint=function(a,c,d,e,g){var n=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);e&&"object"===typeof e&&(e=e.kB);g&&"object"===typeof g&&(g=g.kB);Ic(n,a,c,d,e,g)};
|
||||
WD.prototype.reportErrorWarning=function(a){var c=this.kB;ND();a=a&&"object"===typeof a?a.kB:QD(a);Jc(c,a)};WD.prototype.draw3dText=function(a,c){var d=this.kB;ND();a&&"object"===typeof a&&(a=a.kB);c=c&&"object"===typeof c?c.kB:QD(c);Kc(d,a,c)};WD.prototype.setDebugMode=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Lc(c,a)};WD.prototype.getDebugMode=function(){return Mc(this.kB)};WD.prototype.__destroy__=function(){Nc(this.kB)};
|
||||
function m(a,c,d){a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);this.kB=void 0===a?Oc():void 0===c?_emscripten_bind_btVector3_btVector3_1(a):void 0===d?_emscripten_bind_btVector3_btVector3_2(a,c):Pc(a,c,d);h(m)[this.kB]=this}m.prototype=Object.create(f.prototype);m.prototype.constructor=m;m.prototype.lB=m;m.mB={};b.btVector3=m;m.prototype.length=m.prototype.length=function(){return Qc(this.kB)};m.prototype.x=m.prototype.x=function(){return Rc(this.kB)};
|
||||
m.prototype.y=m.prototype.y=function(){return Sc(this.kB)};m.prototype.z=m.prototype.z=function(){return Tc(this.kB)};m.prototype.setX=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Uc(c,a)};m.prototype.setY=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Vc(c,a)};m.prototype.setZ=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Wc(c,a)};
|
||||
m.prototype.setValue=function(a,c,d){var e=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);Xc(e,a,c,d)};m.prototype.normalize=m.prototype.normalize=function(){Yc(this.kB)};m.prototype.rotate=m.prototype.rotate=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);return k(Zc(d,a,c),m)};m.prototype.dot=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);return $c(c,a)};
|
||||
m.prototype.op_mul=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);return k(ad(c,a),m)};m.prototype.op_add=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);return k(bd(c,a),m)};m.prototype.op_sub=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);return k(cd(c,a),m)};m.prototype.__destroy__=function(){dd(this.kB)};function cE(){throw"cannot construct a btQuadWord, no constructor in IDL";}cE.prototype=Object.create(f.prototype);cE.prototype.constructor=cE;
|
||||
cE.prototype.lB=cE;cE.mB={};b.btQuadWord=cE;cE.prototype.x=cE.prototype.x=function(){return ed(this.kB)};cE.prototype.y=cE.prototype.y=function(){return fd(this.kB)};cE.prototype.z=cE.prototype.z=function(){return gd(this.kB)};cE.prototype.w=cE.prototype.w=function(){return hd(this.kB)};cE.prototype.setX=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);jd(c,a)};cE.prototype.setY=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);kd(c,a)};
|
||||
cE.prototype.setZ=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);ld(c,a)};cE.prototype.setW=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);md(c,a)};cE.prototype.__destroy__=function(){nd(this.kB)};function dE(){throw"cannot construct a btMotionState, no constructor in IDL";}dE.prototype=Object.create(f.prototype);dE.prototype.constructor=dE;dE.prototype.lB=dE;dE.mB={};b.btMotionState=dE;
|
||||
dE.prototype.getWorldTransform=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);od(c,a)};dE.prototype.setWorldTransform=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);pd(c,a)};dE.prototype.__destroy__=function(){qd(this.kB)};function u(){throw"cannot construct a RayResultCallback, no constructor in IDL";}u.prototype=Object.create(f.prototype);u.prototype.constructor=u;u.prototype.lB=u;u.mB={};b.RayResultCallback=u;u.prototype.hasHit=function(){return!!rd(this.kB)};
|
||||
u.prototype.get_m_collisionFilterGroup=u.prototype.nB=function(){return sd(this.kB)};u.prototype.set_m_collisionFilterGroup=u.prototype.pB=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);td(c,a)};Object.defineProperty(u.prototype,"m_collisionFilterGroup",{get:u.prototype.nB,set:u.prototype.pB});u.prototype.get_m_collisionFilterMask=u.prototype.oB=function(){return ud(this.kB)};
|
||||
u.prototype.set_m_collisionFilterMask=u.prototype.qB=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);vd(c,a)};Object.defineProperty(u.prototype,"m_collisionFilterMask",{get:u.prototype.oB,set:u.prototype.qB});u.prototype.get_m_closestHitFraction=u.prototype.rB=function(){return wd(this.kB)};u.prototype.set_m_closestHitFraction=u.prototype.sB=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);xd(c,a)};Object.defineProperty(u.prototype,"m_closestHitFraction",{get:u.prototype.rB,set:u.prototype.sB});
|
||||
u.prototype.get_m_collisionObject=u.prototype.vB=function(){return k(yd(this.kB),q)};u.prototype.set_m_collisionObject=u.prototype.CB=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);zd(c,a)};Object.defineProperty(u.prototype,"m_collisionObject",{get:u.prototype.vB,set:u.prototype.CB});u.prototype.get_m_flags=u.prototype.tB=function(){return Ad(this.kB)};u.prototype.set_m_flags=u.prototype.uB=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Bd(c,a)};
|
||||
Object.defineProperty(u.prototype,"m_flags",{get:u.prototype.tB,set:u.prototype.uB});u.prototype.__destroy__=function(){Cd(this.kB)};function eE(){throw"cannot construct a ContactResultCallback, no constructor in IDL";}eE.prototype=Object.create(f.prototype);eE.prototype.constructor=eE;eE.prototype.lB=eE;eE.mB={};b.ContactResultCallback=eE;
|
||||
eE.prototype.addSingleResult=function(a,c,d,e,g,n,z){var T=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);e&&"object"===typeof e&&(e=e.kB);g&&"object"===typeof g&&(g=g.kB);n&&"object"===typeof n&&(n=n.kB);z&&"object"===typeof z&&(z=z.kB);return Dd(T,a,c,d,e,g,n,z)};eE.prototype.__destroy__=function(){Ed(this.kB)};function v(){throw"cannot construct a ConvexResultCallback, no constructor in IDL";}v.prototype=Object.create(f.prototype);
|
||||
v.prototype.constructor=v;v.prototype.lB=v;v.mB={};b.ConvexResultCallback=v;v.prototype.hasHit=function(){return!!Fd(this.kB)};v.prototype.get_m_collisionFilterGroup=v.prototype.nB=function(){return Gd(this.kB)};v.prototype.set_m_collisionFilterGroup=v.prototype.pB=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Hd(c,a)};Object.defineProperty(v.prototype,"m_collisionFilterGroup",{get:v.prototype.nB,set:v.prototype.pB});v.prototype.get_m_collisionFilterMask=v.prototype.oB=function(){return Id(this.kB)};
|
||||
v.prototype.set_m_collisionFilterMask=v.prototype.qB=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Jd(c,a)};Object.defineProperty(v.prototype,"m_collisionFilterMask",{get:v.prototype.oB,set:v.prototype.qB});v.prototype.get_m_closestHitFraction=v.prototype.rB=function(){return Kd(this.kB)};v.prototype.set_m_closestHitFraction=v.prototype.sB=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Ld(c,a)};Object.defineProperty(v.prototype,"m_closestHitFraction",{get:v.prototype.rB,set:v.prototype.sB});
|
||||
v.prototype.__destroy__=function(){Md(this.kB)};function fE(){throw"cannot construct a btConvexShape, no constructor in IDL";}fE.prototype=Object.create(l.prototype);fE.prototype.constructor=fE;fE.prototype.lB=fE;fE.mB={};b.btConvexShape=fE;fE.prototype.setLocalScaling=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Nd(c,a)};fE.prototype.getLocalScaling=function(){return k(Od(this.kB),m)};
|
||||
fE.prototype.calculateLocalInertia=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);Pd(d,a,c)};fE.prototype.setMargin=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Qd(c,a)};fE.prototype.getMargin=function(){return Rd(this.kB)};fE.prototype.__destroy__=function(){Sd(this.kB)};function gE(a,c){a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);this.kB=Td(a,c);h(gE)[this.kB]=this}gE.prototype=Object.create(l.prototype);
|
||||
gE.prototype.constructor=gE;gE.prototype.lB=gE;gE.mB={};b.btCapsuleShape=gE;gE.prototype.setMargin=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Ud(c,a)};gE.prototype.getMargin=function(){return Vd(this.kB)};gE.prototype.getUpAxis=function(){return Wd(this.kB)};gE.prototype.getRadius=function(){return Xd(this.kB)};gE.prototype.getHalfHeight=function(){return Yd(this.kB)};gE.prototype.setLocalScaling=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Zd(c,a)};
|
||||
gE.prototype.getLocalScaling=function(){return k($d(this.kB),m)};gE.prototype.calculateLocalInertia=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);ae(d,a,c)};gE.prototype.__destroy__=function(){be(this.kB)};function hE(a){a&&"object"===typeof a&&(a=a.kB);this.kB=ce(a);h(hE)[this.kB]=this}hE.prototype=Object.create(l.prototype);hE.prototype.constructor=hE;hE.prototype.lB=hE;hE.mB={};b.btCylinderShape=hE;
|
||||
hE.prototype.setMargin=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);de(c,a)};hE.prototype.getMargin=function(){return ee(this.kB)};hE.prototype.setLocalScaling=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);fe(c,a)};hE.prototype.getLocalScaling=function(){return k(ge(this.kB),m)};hE.prototype.calculateLocalInertia=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);he(d,a,c)};hE.prototype.__destroy__=function(){ie(this.kB)};
|
||||
function iE(a,c){a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);this.kB=je(a,c);h(iE)[this.kB]=this}iE.prototype=Object.create(l.prototype);iE.prototype.constructor=iE;iE.prototype.lB=iE;iE.mB={};b.btConeShape=iE;iE.prototype.setLocalScaling=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);ke(c,a)};iE.prototype.getLocalScaling=function(){return k(le(this.kB),m)};
|
||||
iE.prototype.calculateLocalInertia=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);me(d,a,c)};iE.prototype.__destroy__=function(){ne(this.kB)};function jE(){throw"cannot construct a btStridingMeshInterface, no constructor in IDL";}jE.prototype=Object.create(f.prototype);jE.prototype.constructor=jE;jE.prototype.lB=jE;jE.mB={};b.btStridingMeshInterface=jE;jE.prototype.setScaling=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);oe(c,a)};
|
||||
jE.prototype.__destroy__=function(){pe(this.kB)};function kE(){throw"cannot construct a btTriangleMeshShape, no constructor in IDL";}kE.prototype=Object.create(ZD.prototype);kE.prototype.constructor=kE;kE.prototype.lB=kE;kE.mB={};b.btTriangleMeshShape=kE;kE.prototype.setLocalScaling=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);qe(c,a)};kE.prototype.getLocalScaling=function(){return k(re(this.kB),m)};
|
||||
kE.prototype.calculateLocalInertia=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);se(d,a,c)};kE.prototype.__destroy__=function(){te(this.kB)};function lE(){throw"cannot construct a btPrimitiveManagerBase, no constructor in IDL";}lE.prototype=Object.create(f.prototype);lE.prototype.constructor=lE;lE.prototype.lB=lE;lE.mB={};b.btPrimitiveManagerBase=lE;lE.prototype.is_trimesh=function(){return!!ue(this.kB)};lE.prototype.get_primitive_count=function(){return ve(this.kB)};
|
||||
lE.prototype.get_primitive_box=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);we(d,a,c)};lE.prototype.get_primitive_triangle=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);xe(d,a,c)};lE.prototype.__destroy__=function(){ye(this.kB)};function w(){throw"cannot construct a btGImpactShapeInterface, no constructor in IDL";}w.prototype=Object.create(ZD.prototype);w.prototype.constructor=w;w.prototype.lB=w;
|
||||
w.mB={};b.btGImpactShapeInterface=w;w.prototype.updateBound=function(){ze(this.kB)};w.prototype.postUpdate=function(){Ae(this.kB)};w.prototype.getShapeType=function(){return Be(this.kB)};w.prototype.getName=function(){return sa(Ce(this.kB))};w.prototype.getGImpactShapeType=function(){return De(this.kB)};w.prototype.getPrimitiveManager=function(){return k(Ee(this.kB),lE)};w.prototype.getNumChildShapes=function(){return Fe(this.kB)};w.prototype.childrenHasTransform=function(){return!!Ge(this.kB)};
|
||||
w.prototype.needsRetrieveTriangles=function(){return!!He(this.kB)};w.prototype.needsRetrieveTetrahedrons=function(){return!!Ie(this.kB)};w.prototype.getBulletTriangle=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);Je(d,a,c)};w.prototype.getBulletTetrahedron=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);Ke(d,a,c)};
|
||||
w.prototype.getChildShape=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);return k(Le(c,a),l)};w.prototype.getChildTransform=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);return k(Me(c,a),r)};w.prototype.setChildTransform=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);Ne(d,a,c)};w.prototype.setLocalScaling=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Oe(c,a)};
|
||||
w.prototype.getLocalScaling=function(){return k(Pe(this.kB),m)};w.prototype.calculateLocalInertia=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);Qe(d,a,c)};w.prototype.__destroy__=function(){Re(this.kB)};function mE(){throw"cannot construct a btActivatingCollisionAlgorithm, no constructor in IDL";}mE.prototype=Object.create($D.prototype);mE.prototype.constructor=mE;mE.prototype.lB=mE;mE.mB={};b.btActivatingCollisionAlgorithm=mE;
|
||||
mE.prototype.__destroy__=function(){Se(this.kB)};function nE(a){a&&"object"===typeof a&&(a=a.kB);this.kB=void 0===a?Te():Ue(a);h(nE)[this.kB]=this}nE.prototype=Object.create(f.prototype);nE.prototype.constructor=nE;nE.prototype.lB=nE;nE.mB={};b.btDefaultCollisionConfiguration=nE;nE.prototype.__destroy__=function(){Ve(this.kB)};function TD(){throw"cannot construct a btDispatcher, no constructor in IDL";}TD.prototype=Object.create(f.prototype);TD.prototype.constructor=TD;TD.prototype.lB=TD;TD.mB={};
|
||||
b.btDispatcher=TD;TD.prototype.getNumManifolds=function(){return We(this.kB)};TD.prototype.getManifoldByIndexInternal=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);return k(Xe(c,a),oE)};TD.prototype.__destroy__=function(){Ye(this.kB)};
|
||||
function pE(a,c,d,e,g){a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);e&&"object"===typeof e&&(e=e.kB);g&&"object"===typeof g&&(g=g.kB);this.kB=void 0===e?Ze(a,c,d):void 0===g?_emscripten_bind_btGeneric6DofConstraint_btGeneric6DofConstraint_4(a,c,d,e):$e(a,c,d,e,g);h(pE)[this.kB]=this}pE.prototype=Object.create(aE.prototype);pE.prototype.constructor=pE;pE.prototype.lB=pE;pE.mB={};b.btGeneric6DofConstraint=pE;
|
||||
pE.prototype.setLinearLowerLimit=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);af(c,a)};pE.prototype.setLinearUpperLimit=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);bf(c,a)};pE.prototype.setAngularLowerLimit=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);cf(c,a)};pE.prototype.setAngularUpperLimit=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);df(c,a)};pE.prototype.getFrameOffsetA=function(){return k(ef(this.kB),r)};
|
||||
pE.prototype.enableFeedback=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);ff(c,a)};pE.prototype.getBreakingImpulseThreshold=function(){return gf(this.kB)};pE.prototype.setBreakingImpulseThreshold=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);hf(c,a)};pE.prototype.getParam=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);return jf(d,a,c)};
|
||||
pE.prototype.setParam=function(a,c,d){var e=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);kf(e,a,c,d)};pE.prototype.__destroy__=function(){lf(this.kB)};function x(a,c,d,e){a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);e&&"object"===typeof e&&(e=e.kB);this.kB=mf(a,c,d,e);h(x)[this.kB]=this}x.prototype=Object.create(bE.prototype);x.prototype.constructor=x;x.prototype.lB=x;x.mB={};
|
||||
b.btDiscreteDynamicsWorld=x;x.prototype.setGravity=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);nf(c,a)};x.prototype.getGravity=function(){return k(of(this.kB),m)};x.prototype.addRigidBody=function(a,c,d){var e=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);void 0===c?pf(e,a):void 0===d?_emscripten_bind_btDiscreteDynamicsWorld_addRigidBody_2(e,a,c):qf(e,a,c,d)};
|
||||
x.prototype.removeRigidBody=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);rf(c,a)};x.prototype.addConstraint=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);void 0===c?sf(d,a):tf(d,a,c)};x.prototype.removeConstraint=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);uf(c,a)};
|
||||
x.prototype.stepSimulation=function(a,c,d){var e=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);return void 0===c?vf(e,a):void 0===d?wf(e,a,c):xf(e,a,c,d)};x.prototype.setContactAddedCallback=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);yf(c,a)};x.prototype.setContactProcessedCallback=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);zf(c,a)};
|
||||
x.prototype.setContactDestroyedCallback=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Af(c,a)};x.prototype.getDispatcher=function(){return k(Bf(this.kB),TD)};x.prototype.rayTest=function(a,c,d){var e=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);Cf(e,a,c,d)};x.prototype.getPairCache=function(){return k(Df(this.kB),UD)};x.prototype.getDispatchInfo=function(){return k(Ef(this.kB),p)};
|
||||
x.prototype.addCollisionObject=function(a,c,d){var e=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);void 0===c?Ff(e,a):void 0===d?Gf(e,a,c):Hf(e,a,c,d)};x.prototype.removeCollisionObject=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);If(c,a)};x.prototype.getBroadphase=function(){return k(Jf(this.kB),VD)};
|
||||
x.prototype.convexSweepTest=function(a,c,d,e,g){var n=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);e&&"object"===typeof e&&(e=e.kB);g&&"object"===typeof g&&(g=g.kB);Kf(n,a,c,d,e,g)};x.prototype.contactPairTest=function(a,c,d){var e=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);Lf(e,a,c,d)};
|
||||
x.prototype.contactTest=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);Mf(d,a,c)};x.prototype.updateSingleAabb=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Nf(c,a)};x.prototype.setDebugDrawer=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Of(c,a)};x.prototype.getDebugDrawer=function(){return k(Pf(this.kB),WD)};x.prototype.debugDrawWorld=function(){Qf(this.kB)};
|
||||
x.prototype.debugDrawObject=function(a,c,d){var e=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);Rf(e,a,c,d)};x.prototype.addAction=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Sf(c,a)};x.prototype.removeAction=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Tf(c,a)};x.prototype.getSolverInfo=function(){return k(Uf(this.kB),t)};
|
||||
x.prototype.setInternalTickCallback=function(a,c,d){var e=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);void 0===c?Vf(e,a):void 0===d?Wf(e,a,c):Xf(e,a,c,d)};x.prototype.__destroy__=function(){Yf(this.kB)};function qE(){throw"cannot construct a btVehicleRaycaster, no constructor in IDL";}qE.prototype=Object.create(f.prototype);qE.prototype.constructor=qE;qE.prototype.lB=qE;qE.mB={};b.btVehicleRaycaster=qE;
|
||||
qE.prototype.castRay=function(a,c,d){var e=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);Zf(e,a,c,d)};qE.prototype.__destroy__=function(){$f(this.kB)};function rE(){throw"cannot construct a btActionInterface, no constructor in IDL";}rE.prototype=Object.create(f.prototype);rE.prototype.constructor=rE;rE.prototype.lB=rE;rE.mB={};b.btActionInterface=rE;
|
||||
rE.prototype.updateAction=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);ag(d,a,c)};rE.prototype.__destroy__=function(){bg(this.kB)};function y(){this.kB=cg();h(y)[this.kB]=this}y.prototype=Object.create(q.prototype);y.prototype.constructor=y;y.prototype.lB=y;y.mB={};b.btGhostObject=y;y.prototype.getNumOverlappingObjects=function(){return dg(this.kB)};
|
||||
y.prototype.getOverlappingObject=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);return k(eg(c,a),q)};y.prototype.setAnisotropicFriction=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);fg(d,a,c)};y.prototype.getCollisionShape=function(){return k(gg(this.kB),l)};y.prototype.setContactProcessingThreshold=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);hg(c,a)};
|
||||
y.prototype.setActivationState=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);ig(c,a)};y.prototype.forceActivationState=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);jg(c,a)};y.prototype.activate=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);void 0===a?kg(c):lg(c,a)};y.prototype.isActive=function(){return!!mg(this.kB)};y.prototype.isKinematicObject=function(){return!!ng(this.kB)};y.prototype.isStaticObject=function(){return!!og(this.kB)};
|
||||
y.prototype.isStaticOrKinematicObject=function(){return!!pg(this.kB)};y.prototype.getRestitution=function(){return qg(this.kB)};y.prototype.getFriction=function(){return rg(this.kB)};y.prototype.getRollingFriction=function(){return sg(this.kB)};y.prototype.setRestitution=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);tg(c,a)};y.prototype.setFriction=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);ug(c,a)};
|
||||
y.prototype.setRollingFriction=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);vg(c,a)};y.prototype.getWorldTransform=function(){return k(wg(this.kB),r)};y.prototype.getCollisionFlags=function(){return xg(this.kB)};y.prototype.setCollisionFlags=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);yg(c,a)};y.prototype.setWorldTransform=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);zg(c,a)};
|
||||
y.prototype.setCollisionShape=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Ag(c,a)};y.prototype.setCcdMotionThreshold=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Bg(c,a)};y.prototype.setCcdSweptSphereRadius=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Cg(c,a)};y.prototype.getUserIndex=function(){return Dg(this.kB)};y.prototype.setUserIndex=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Eg(c,a)};
|
||||
y.prototype.getUserPointer=function(){return k(Fg(this.kB),XD)};y.prototype.setUserPointer=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Gg(c,a)};y.prototype.getBroadphaseHandle=function(){return k(Hg(this.kB),YD)};y.prototype.__destroy__=function(){Ig(this.kB)};function sE(){throw"cannot construct a btSoftBodySolver, no constructor in IDL";}sE.prototype=Object.create(f.prototype);sE.prototype.constructor=sE;sE.prototype.lB=sE;sE.mB={};b.btSoftBodySolver=sE;sE.prototype.__destroy__=function(){Jg(this.kB)};
|
||||
function XD(){throw"cannot construct a VoidPtr, no constructor in IDL";}XD.prototype=Object.create(f.prototype);XD.prototype.constructor=XD;XD.prototype.lB=XD;XD.mB={};b.VoidPtr=XD;XD.prototype.__destroy__=function(){Kg(this.kB)};function tE(){this.kB=Lg();h(tE)[this.kB]=this}tE.prototype=Object.create(WD.prototype);tE.prototype.constructor=tE;tE.prototype.lB=tE;tE.mB={};b.DebugDrawer=tE;
|
||||
tE.prototype.drawLine=function(a,c,d){var e=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);Mg(e,a,c,d)};tE.prototype.drawContactPoint=function(a,c,d,e,g){var n=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);e&&"object"===typeof e&&(e=e.kB);g&&"object"===typeof g&&(g=g.kB);Ng(n,a,c,d,e,g)};
|
||||
tE.prototype.reportErrorWarning=function(a){var c=this.kB;ND();a=a&&"object"===typeof a?a.kB:QD(a);Og(c,a)};tE.prototype.draw3dText=function(a,c){var d=this.kB;ND();a&&"object"===typeof a&&(a=a.kB);c=c&&"object"===typeof c?c.kB:QD(c);Pg(d,a,c)};tE.prototype.setDebugMode=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Qg(c,a)};tE.prototype.getDebugMode=function(){return Rg(this.kB)};tE.prototype.__destroy__=function(){Sg(this.kB)};
|
||||
function A(a,c,d,e){a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);e&&"object"===typeof e&&(e=e.kB);this.kB=void 0===a?Tg():void 0===c?_emscripten_bind_btVector4_btVector4_1(a):void 0===d?_emscripten_bind_btVector4_btVector4_2(a,c):void 0===e?_emscripten_bind_btVector4_btVector4_3(a,c,d):Ug(a,c,d,e);h(A)[this.kB]=this}A.prototype=Object.create(m.prototype);A.prototype.constructor=A;A.prototype.lB=A;A.mB={};b.btVector4=A;
|
||||
A.prototype.w=A.prototype.w=function(){return Vg(this.kB)};A.prototype.setValue=function(a,c,d,e){var g=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);e&&"object"===typeof e&&(e=e.kB);Wg(g,a,c,d,e)};A.prototype.length=A.prototype.length=function(){return Xg(this.kB)};A.prototype.x=A.prototype.x=function(){return Yg(this.kB)};A.prototype.y=A.prototype.y=function(){return Zg(this.kB)};A.prototype.z=A.prototype.z=function(){return $g(this.kB)};
|
||||
A.prototype.setX=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);ah(c,a)};A.prototype.setY=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);bh(c,a)};A.prototype.setZ=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);ch(c,a)};A.prototype.normalize=A.prototype.normalize=function(){dh(this.kB)};A.prototype.rotate=A.prototype.rotate=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);return k(eh(d,a,c),m)};
|
||||
A.prototype.dot=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);return fh(c,a)};A.prototype.op_mul=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);return k(gh(c,a),m)};A.prototype.op_add=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);return k(hh(c,a),m)};A.prototype.op_sub=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);return k(ih(c,a),m)};A.prototype.__destroy__=function(){jh(this.kB)};
|
||||
function B(a,c,d,e){a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);e&&"object"===typeof e&&(e=e.kB);this.kB=kh(a,c,d,e);h(B)[this.kB]=this}B.prototype=Object.create(cE.prototype);B.prototype.constructor=B;B.prototype.lB=B;B.mB={};b.btQuaternion=B;B.prototype.setValue=function(a,c,d,e){var g=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);e&&"object"===typeof e&&(e=e.kB);lh(g,a,c,d,e)};
|
||||
B.prototype.setEulerZYX=function(a,c,d){var e=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);mh(e,a,c,d)};B.prototype.setRotation=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);nh(d,a,c)};B.prototype.normalize=B.prototype.normalize=function(){oh(this.kB)};B.prototype.length2=function(){return ph(this.kB)};B.prototype.length=B.prototype.length=function(){return qh(this.kB)};
|
||||
B.prototype.dot=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);return rh(c,a)};B.prototype.normalized=function(){return k(sh(this.kB),B)};B.prototype.getAxis=function(){return k(th(this.kB),m)};B.prototype.inverse=B.prototype.inverse=function(){return k(uh(this.kB),B)};B.prototype.getAngle=function(){return vh(this.kB)};B.prototype.getAngleShortestPath=function(){return wh(this.kB)};
|
||||
B.prototype.angle=B.prototype.angle=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);return xh(c,a)};B.prototype.angleShortestPath=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);return yh(c,a)};B.prototype.op_add=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);return k(zh(c,a),B)};B.prototype.op_sub=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);return k(Ah(c,a),B)};
|
||||
B.prototype.op_mul=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);return k(Bh(c,a),B)};B.prototype.op_mulq=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);return k(Ch(c,a),B)};B.prototype.op_div=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);return k(Dh(c,a),B)};B.prototype.x=B.prototype.x=function(){return Eh(this.kB)};B.prototype.y=B.prototype.y=function(){return Fh(this.kB)};B.prototype.z=B.prototype.z=function(){return Gh(this.kB)};
|
||||
B.prototype.w=B.prototype.w=function(){return Hh(this.kB)};B.prototype.setX=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Ih(c,a)};B.prototype.setY=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Jh(c,a)};B.prototype.setZ=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Kh(c,a)};B.prototype.setW=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Lh(c,a)};B.prototype.__destroy__=function(){Mh(this.kB)};
|
||||
function uE(){throw"cannot construct a btMatrix3x3, no constructor in IDL";}uE.prototype=Object.create(f.prototype);uE.prototype.constructor=uE;uE.prototype.lB=uE;uE.mB={};b.btMatrix3x3=uE;uE.prototype.setEulerZYX=function(a,c,d){var e=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);Nh(e,a,c,d)};uE.prototype.getRotation=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Oh(c,a)};
|
||||
uE.prototype.getRow=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);return k(Ph(c,a),m)};uE.prototype.__destroy__=function(){Qh(this.kB)};function r(a,c){a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);this.kB=void 0===a?Rh():void 0===c?_emscripten_bind_btTransform_btTransform_1(a):Sh(a,c);h(r)[this.kB]=this}r.prototype=Object.create(f.prototype);r.prototype.constructor=r;r.prototype.lB=r;r.mB={};b.btTransform=r;r.prototype.setIdentity=function(){Th(this.kB)};
|
||||
r.prototype.setOrigin=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Uh(c,a)};r.prototype.setRotation=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Vh(c,a)};r.prototype.getOrigin=function(){return k(Wh(this.kB),m)};r.prototype.getRotation=function(){return k(Xh(this.kB),B)};r.prototype.getBasis=function(){return k(Yh(this.kB),uE)};r.prototype.setFromOpenGLMatrix=function(a){var c=this.kB;ND();"object"==typeof a&&(a=RD(a));Zh(c,a)};
|
||||
r.prototype.inverse=r.prototype.inverse=function(){return k($h(this.kB),r)};r.prototype.op_mul=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);return k(ai(c,a),r)};r.prototype.__destroy__=function(){bi(this.kB)};function vE(){this.kB=ci();h(vE)[this.kB]=this}vE.prototype=Object.create(dE.prototype);vE.prototype.constructor=vE;vE.prototype.lB=vE;vE.mB={};b.MotionState=vE;vE.prototype.getWorldTransform=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);di(c,a)};
|
||||
vE.prototype.setWorldTransform=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);ei(c,a)};vE.prototype.__destroy__=function(){fi(this.kB)};function wE(a,c){a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);this.kB=void 0===a?gi():void 0===c?hi(a):ii(a,c);h(wE)[this.kB]=this}wE.prototype=Object.create(dE.prototype);wE.prototype.constructor=wE;wE.prototype.lB=wE;wE.mB={};b.btDefaultMotionState=wE;
|
||||
wE.prototype.getWorldTransform=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);ji(c,a)};wE.prototype.setWorldTransform=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);ki(c,a)};wE.prototype.get_m_graphicsWorldTrans=wE.prototype.lD=function(){return k(li(this.kB),r)};wE.prototype.set_m_graphicsWorldTrans=wE.prototype.cG=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);mi(c,a)};Object.defineProperty(wE.prototype,"m_graphicsWorldTrans",{get:wE.prototype.lD,set:wE.prototype.cG});
|
||||
wE.prototype.__destroy__=function(){ni(this.kB)};function xE(){throw"cannot construct a btCollisionObjectWrapper, no constructor in IDL";}xE.prototype=Object.create(f.prototype);xE.prototype.constructor=xE;xE.prototype.lB=xE;xE.mB={};b.btCollisionObjectWrapper=xE;xE.prototype.getWorldTransform=function(){return k(oi(this.kB),r)};xE.prototype.getCollisionObject=function(){return k(pi(this.kB),q)};xE.prototype.getCollisionShape=function(){return k(qi(this.kB),l)};
|
||||
function C(a,c){a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);this.kB=ri(a,c);h(C)[this.kB]=this}C.prototype=Object.create(u.prototype);C.prototype.constructor=C;C.prototype.lB=C;C.mB={};b.ClosestRayResultCallback=C;C.prototype.hasHit=function(){return!!si(this.kB)};C.prototype.get_m_rayFromWorld=C.prototype.NB=function(){return k(ti(this.kB),m)};C.prototype.set_m_rayFromWorld=C.prototype.XB=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);ui(c,a)};
|
||||
Object.defineProperty(C.prototype,"m_rayFromWorld",{get:C.prototype.NB,set:C.prototype.XB});C.prototype.get_m_rayToWorld=C.prototype.OB=function(){return k(vi(this.kB),m)};C.prototype.set_m_rayToWorld=C.prototype.YB=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);wi(c,a)};Object.defineProperty(C.prototype,"m_rayToWorld",{get:C.prototype.OB,set:C.prototype.YB});C.prototype.get_m_hitNormalWorld=C.prototype.xB=function(){return k(xi(this.kB),m)};
|
||||
C.prototype.set_m_hitNormalWorld=C.prototype.EB=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);yi(c,a)};Object.defineProperty(C.prototype,"m_hitNormalWorld",{get:C.prototype.xB,set:C.prototype.EB});C.prototype.get_m_hitPointWorld=C.prototype.yB=function(){return k(zi(this.kB),m)};C.prototype.set_m_hitPointWorld=C.prototype.FB=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Ai(c,a)};Object.defineProperty(C.prototype,"m_hitPointWorld",{get:C.prototype.yB,set:C.prototype.FB});
|
||||
C.prototype.get_m_collisionFilterGroup=C.prototype.nB=function(){return Bi(this.kB)};C.prototype.set_m_collisionFilterGroup=C.prototype.pB=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Ci(c,a)};Object.defineProperty(C.prototype,"m_collisionFilterGroup",{get:C.prototype.nB,set:C.prototype.pB});C.prototype.get_m_collisionFilterMask=C.prototype.oB=function(){return Di(this.kB)};
|
||||
C.prototype.set_m_collisionFilterMask=C.prototype.qB=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Ei(c,a)};Object.defineProperty(C.prototype,"m_collisionFilterMask",{get:C.prototype.oB,set:C.prototype.qB});C.prototype.get_m_closestHitFraction=C.prototype.rB=function(){return Fi(this.kB)};C.prototype.set_m_closestHitFraction=C.prototype.sB=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Gi(c,a)};Object.defineProperty(C.prototype,"m_closestHitFraction",{get:C.prototype.rB,set:C.prototype.sB});
|
||||
C.prototype.get_m_collisionObject=C.prototype.vB=function(){return k(Hi(this.kB),q)};C.prototype.set_m_collisionObject=C.prototype.CB=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Ii(c,a)};Object.defineProperty(C.prototype,"m_collisionObject",{get:C.prototype.vB,set:C.prototype.CB});C.prototype.get_m_flags=C.prototype.tB=function(){return Ji(this.kB)};C.prototype.set_m_flags=C.prototype.uB=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Ki(c,a)};
|
||||
Object.defineProperty(C.prototype,"m_flags",{get:C.prototype.tB,set:C.prototype.uB});C.prototype.__destroy__=function(){Li(this.kB)};function yE(){throw"cannot construct a btConstCollisionObjectArray, no constructor in IDL";}yE.prototype=Object.create(f.prototype);yE.prototype.constructor=yE;yE.prototype.lB=yE;yE.mB={};b.btConstCollisionObjectArray=yE;yE.prototype.size=yE.prototype.size=function(){return Mi(this.kB)};
|
||||
yE.prototype.at=yE.prototype.at=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);return k(Ni(c,a),q)};yE.prototype.__destroy__=function(){Oi(this.kB)};function zE(){throw"cannot construct a btScalarArray, no constructor in IDL";}zE.prototype=Object.create(f.prototype);zE.prototype.constructor=zE;zE.prototype.lB=zE;zE.mB={};b.btScalarArray=zE;zE.prototype.size=zE.prototype.size=function(){return Pi(this.kB)};
|
||||
zE.prototype.at=zE.prototype.at=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);return Qi(c,a)};zE.prototype.__destroy__=function(){Ri(this.kB)};function D(a,c){a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);this.kB=Si(a,c);h(D)[this.kB]=this}D.prototype=Object.create(u.prototype);D.prototype.constructor=D;D.prototype.lB=D;D.mB={};b.AllHitsRayResultCallback=D;D.prototype.hasHit=function(){return!!Ti(this.kB)};
|
||||
D.prototype.get_m_collisionObjects=D.prototype.UC=function(){return k(Ui(this.kB),yE)};D.prototype.set_m_collisionObjects=D.prototype.LF=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Vi(c,a)};Object.defineProperty(D.prototype,"m_collisionObjects",{get:D.prototype.UC,set:D.prototype.LF});D.prototype.get_m_rayFromWorld=D.prototype.NB=function(){return k(Wi(this.kB),m)};D.prototype.set_m_rayFromWorld=D.prototype.XB=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Xi(c,a)};
|
||||
Object.defineProperty(D.prototype,"m_rayFromWorld",{get:D.prototype.NB,set:D.prototype.XB});D.prototype.get_m_rayToWorld=D.prototype.OB=function(){return k(Yi(this.kB),m)};D.prototype.set_m_rayToWorld=D.prototype.YB=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Zi(c,a)};Object.defineProperty(D.prototype,"m_rayToWorld",{get:D.prototype.OB,set:D.prototype.YB});D.prototype.get_m_hitNormalWorld=D.prototype.xB=function(){return k($i(this.kB),AE)};
|
||||
D.prototype.set_m_hitNormalWorld=D.prototype.EB=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);aj(c,a)};Object.defineProperty(D.prototype,"m_hitNormalWorld",{get:D.prototype.xB,set:D.prototype.EB});D.prototype.get_m_hitPointWorld=D.prototype.yB=function(){return k(bj(this.kB),AE)};D.prototype.set_m_hitPointWorld=D.prototype.FB=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);cj(c,a)};Object.defineProperty(D.prototype,"m_hitPointWorld",{get:D.prototype.yB,set:D.prototype.FB});
|
||||
D.prototype.get_m_hitFractions=D.prototype.qD=function(){return k(dj(this.kB),zE)};D.prototype.set_m_hitFractions=D.prototype.hG=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);ej(c,a)};Object.defineProperty(D.prototype,"m_hitFractions",{get:D.prototype.qD,set:D.prototype.hG});D.prototype.get_m_collisionFilterGroup=D.prototype.nB=function(){return fj(this.kB)};D.prototype.set_m_collisionFilterGroup=D.prototype.pB=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);gj(c,a)};
|
||||
Object.defineProperty(D.prototype,"m_collisionFilterGroup",{get:D.prototype.nB,set:D.prototype.pB});D.prototype.get_m_collisionFilterMask=D.prototype.oB=function(){return hj(this.kB)};D.prototype.set_m_collisionFilterMask=D.prototype.qB=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);ij(c,a)};Object.defineProperty(D.prototype,"m_collisionFilterMask",{get:D.prototype.oB,set:D.prototype.qB});D.prototype.get_m_closestHitFraction=D.prototype.rB=function(){return jj(this.kB)};
|
||||
D.prototype.set_m_closestHitFraction=D.prototype.sB=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);kj(c,a)};Object.defineProperty(D.prototype,"m_closestHitFraction",{get:D.prototype.rB,set:D.prototype.sB});D.prototype.get_m_collisionObject=D.prototype.vB=function(){return k(lj(this.kB),q)};D.prototype.set_m_collisionObject=D.prototype.CB=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);mj(c,a)};Object.defineProperty(D.prototype,"m_collisionObject",{get:D.prototype.vB,set:D.prototype.CB});
|
||||
D.prototype.get_m_flags=D.prototype.tB=function(){return nj(this.kB)};D.prototype.set_m_flags=D.prototype.uB=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);oj(c,a)};Object.defineProperty(D.prototype,"m_flags",{get:D.prototype.tB,set:D.prototype.uB});D.prototype.__destroy__=function(){pj(this.kB)};function E(){throw"cannot construct a btManifoldPoint, no constructor in IDL";}E.prototype=Object.create(f.prototype);E.prototype.constructor=E;E.prototype.lB=E;E.mB={};b.btManifoldPoint=E;
|
||||
E.prototype.getPositionWorldOnA=function(){return k(qj(this.kB),m)};E.prototype.getPositionWorldOnB=function(){return k(rj(this.kB),m)};E.prototype.getAppliedImpulse=function(){return sj(this.kB)};E.prototype.getDistance=function(){return tj(this.kB)};E.prototype.get_m_localPointA=E.prototype.GD=function(){return k(uj(this.kB),m)};E.prototype.set_m_localPointA=E.prototype.xG=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);vj(c,a)};
|
||||
Object.defineProperty(E.prototype,"m_localPointA",{get:E.prototype.GD,set:E.prototype.xG});E.prototype.get_m_localPointB=E.prototype.HD=function(){return k(wj(this.kB),m)};E.prototype.set_m_localPointB=E.prototype.yG=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);xj(c,a)};Object.defineProperty(E.prototype,"m_localPointB",{get:E.prototype.HD,set:E.prototype.yG});E.prototype.get_m_positionWorldOnB=E.prototype.YD=function(){return k(yj(this.kB),m)};
|
||||
E.prototype.set_m_positionWorldOnB=E.prototype.PG=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);zj(c,a)};Object.defineProperty(E.prototype,"m_positionWorldOnB",{get:E.prototype.YD,set:E.prototype.PG});E.prototype.get_m_positionWorldOnA=E.prototype.XD=function(){return k(Aj(this.kB),m)};E.prototype.set_m_positionWorldOnA=E.prototype.OG=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Bj(c,a)};Object.defineProperty(E.prototype,"m_positionWorldOnA",{get:E.prototype.XD,set:E.prototype.OG});
|
||||
E.prototype.get_m_normalWorldOnB=E.prototype.SD=function(){return k(Cj(this.kB),m)};E.prototype.set_m_normalWorldOnB=E.prototype.JG=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Dj(c,a)};Object.defineProperty(E.prototype,"m_normalWorldOnB",{get:E.prototype.SD,set:E.prototype.JG});E.prototype.get_m_userPersistentData=E.prototype.zE=function(){return Ej(this.kB)};E.prototype.set_m_userPersistentData=E.prototype.rH=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Fj(c,a)};
|
||||
Object.defineProperty(E.prototype,"m_userPersistentData",{get:E.prototype.zE,set:E.prototype.rH});E.prototype.__destroy__=function(){Gj(this.kB)};function BE(){this.kB=Hj();h(BE)[this.kB]=this}BE.prototype=Object.create(eE.prototype);BE.prototype.constructor=BE;BE.prototype.lB=BE;BE.mB={};b.ConcreteContactResultCallback=BE;
|
||||
BE.prototype.addSingleResult=function(a,c,d,e,g,n,z){var T=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);e&&"object"===typeof e&&(e=e.kB);g&&"object"===typeof g&&(g=g.kB);n&&"object"===typeof n&&(n=n.kB);z&&"object"===typeof z&&(z=z.kB);return Ij(T,a,c,d,e,g,n,z)};BE.prototype.__destroy__=function(){Jj(this.kB)};function CE(){throw"cannot construct a LocalShapeInfo, no constructor in IDL";}CE.prototype=Object.create(f.prototype);
|
||||
CE.prototype.constructor=CE;CE.prototype.lB=CE;CE.mB={};b.LocalShapeInfo=CE;CE.prototype.get_m_shapePart=CE.prototype.gE=function(){return Kj(this.kB)};CE.prototype.set_m_shapePart=CE.prototype.ZG=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Lj(c,a)};Object.defineProperty(CE.prototype,"m_shapePart",{get:CE.prototype.gE,set:CE.prototype.ZG});CE.prototype.get_m_triangleIndex=CE.prototype.vE=function(){return Mj(this.kB)};
|
||||
CE.prototype.set_m_triangleIndex=CE.prototype.nH=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Nj(c,a)};Object.defineProperty(CE.prototype,"m_triangleIndex",{get:CE.prototype.vE,set:CE.prototype.nH});CE.prototype.__destroy__=function(){Oj(this.kB)};function F(a,c,d,e,g){a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);e&&"object"===typeof e&&(e=e.kB);g&&"object"===typeof g&&(g=g.kB);this.kB=Pj(a,c,d,e,g);h(F)[this.kB]=this}
|
||||
F.prototype=Object.create(f.prototype);F.prototype.constructor=F;F.prototype.lB=F;F.mB={};b.LocalConvexResult=F;F.prototype.get_m_hitCollisionObject=F.prototype.LB=function(){return k(Qj(this.kB),q)};F.prototype.set_m_hitCollisionObject=F.prototype.VB=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Rj(c,a)};Object.defineProperty(F.prototype,"m_hitCollisionObject",{get:F.prototype.LB,set:F.prototype.VB});F.prototype.get_m_localShapeInfo=F.prototype.ID=function(){return k(Sj(this.kB),CE)};
|
||||
F.prototype.set_m_localShapeInfo=F.prototype.zG=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Tj(c,a)};Object.defineProperty(F.prototype,"m_localShapeInfo",{get:F.prototype.ID,set:F.prototype.zG});F.prototype.get_m_hitNormalLocal=F.prototype.sD=function(){return k(Uj(this.kB),m)};F.prototype.set_m_hitNormalLocal=F.prototype.jG=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Vj(c,a)};Object.defineProperty(F.prototype,"m_hitNormalLocal",{get:F.prototype.sD,set:F.prototype.jG});
|
||||
F.prototype.get_m_hitPointLocal=F.prototype.uD=function(){return k(Wj(this.kB),m)};F.prototype.set_m_hitPointLocal=F.prototype.lG=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Xj(c,a)};Object.defineProperty(F.prototype,"m_hitPointLocal",{get:F.prototype.uD,set:F.prototype.lG});F.prototype.get_m_hitFraction=F.prototype.pD=function(){return Yj(this.kB)};F.prototype.set_m_hitFraction=F.prototype.gG=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Zj(c,a)};
|
||||
Object.defineProperty(F.prototype,"m_hitFraction",{get:F.prototype.pD,set:F.prototype.gG});F.prototype.__destroy__=function(){ak(this.kB)};function G(a,c){a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);this.kB=bk(a,c);h(G)[this.kB]=this}G.prototype=Object.create(v.prototype);G.prototype.constructor=G;G.prototype.lB=G;G.mB={};b.ClosestConvexResultCallback=G;G.prototype.hasHit=function(){return!!ck(this.kB)};
|
||||
G.prototype.get_m_hitCollisionObject=G.prototype.LB=function(){return k(dk(this.kB),q)};G.prototype.set_m_hitCollisionObject=G.prototype.VB=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);ek(c,a)};Object.defineProperty(G.prototype,"m_hitCollisionObject",{get:G.prototype.LB,set:G.prototype.VB});G.prototype.get_m_convexFromWorld=G.prototype.ZC=function(){return k(fk(this.kB),m)};
|
||||
G.prototype.set_m_convexFromWorld=G.prototype.QF=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);gk(c,a)};Object.defineProperty(G.prototype,"m_convexFromWorld",{get:G.prototype.ZC,set:G.prototype.QF});G.prototype.get_m_convexToWorld=G.prototype.$C=function(){return k(hk(this.kB),m)};G.prototype.set_m_convexToWorld=G.prototype.RF=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);ik(c,a)};Object.defineProperty(G.prototype,"m_convexToWorld",{get:G.prototype.$C,set:G.prototype.RF});
|
||||
G.prototype.get_m_hitNormalWorld=G.prototype.xB=function(){return k(jk(this.kB),m)};G.prototype.set_m_hitNormalWorld=G.prototype.EB=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);kk(c,a)};Object.defineProperty(G.prototype,"m_hitNormalWorld",{get:G.prototype.xB,set:G.prototype.EB});G.prototype.get_m_hitPointWorld=G.prototype.yB=function(){return k(lk(this.kB),m)};G.prototype.set_m_hitPointWorld=G.prototype.FB=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);mk(c,a)};
|
||||
Object.defineProperty(G.prototype,"m_hitPointWorld",{get:G.prototype.yB,set:G.prototype.FB});G.prototype.get_m_collisionFilterGroup=G.prototype.nB=function(){return nk(this.kB)};G.prototype.set_m_collisionFilterGroup=G.prototype.pB=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);ok(c,a)};Object.defineProperty(G.prototype,"m_collisionFilterGroup",{get:G.prototype.nB,set:G.prototype.pB});G.prototype.get_m_collisionFilterMask=G.prototype.oB=function(){return pk(this.kB)};
|
||||
G.prototype.set_m_collisionFilterMask=G.prototype.qB=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);qk(c,a)};Object.defineProperty(G.prototype,"m_collisionFilterMask",{get:G.prototype.oB,set:G.prototype.qB});G.prototype.get_m_closestHitFraction=G.prototype.rB=function(){return rk(this.kB)};G.prototype.set_m_closestHitFraction=G.prototype.sB=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);sk(c,a)};Object.defineProperty(G.prototype,"m_closestHitFraction",{get:G.prototype.rB,set:G.prototype.sB});
|
||||
G.prototype.__destroy__=function(){tk(this.kB)};function DE(a,c){a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);this.kB=void 0===c?uk(a):vk(a,c);h(DE)[this.kB]=this}DE.prototype=Object.create(fE.prototype);DE.prototype.constructor=DE;DE.prototype.lB=DE;DE.mB={};b.btConvexTriangleMeshShape=DE;DE.prototype.setLocalScaling=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);wk(c,a)};DE.prototype.getLocalScaling=function(){return k(xk(this.kB),m)};
|
||||
DE.prototype.calculateLocalInertia=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);yk(d,a,c)};DE.prototype.setMargin=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);zk(c,a)};DE.prototype.getMargin=function(){return Ak(this.kB)};DE.prototype.__destroy__=function(){Bk(this.kB)};function EE(a){a&&"object"===typeof a&&(a=a.kB);this.kB=Ck(a);h(EE)[this.kB]=this}EE.prototype=Object.create(l.prototype);EE.prototype.constructor=EE;EE.prototype.lB=EE;
|
||||
EE.mB={};b.btBoxShape=EE;EE.prototype.setMargin=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Dk(c,a)};EE.prototype.getMargin=function(){return Ek(this.kB)};EE.prototype.setLocalScaling=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Fk(c,a)};EE.prototype.getLocalScaling=function(){return k(Gk(this.kB),m)};EE.prototype.calculateLocalInertia=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);Hk(d,a,c)};EE.prototype.__destroy__=function(){Ik(this.kB)};
|
||||
function FE(a,c){a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);this.kB=Jk(a,c);h(FE)[this.kB]=this}FE.prototype=Object.create(gE.prototype);FE.prototype.constructor=FE;FE.prototype.lB=FE;FE.mB={};b.btCapsuleShapeX=FE;FE.prototype.setMargin=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Kk(c,a)};FE.prototype.getMargin=function(){return Lk(this.kB)};FE.prototype.getUpAxis=function(){return Mk(this.kB)};FE.prototype.getRadius=function(){return Nk(this.kB)};
|
||||
FE.prototype.getHalfHeight=function(){return Ok(this.kB)};FE.prototype.setLocalScaling=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Pk(c,a)};FE.prototype.getLocalScaling=function(){return k(Qk(this.kB),m)};FE.prototype.calculateLocalInertia=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);Rk(d,a,c)};FE.prototype.__destroy__=function(){Sk(this.kB)};
|
||||
function GE(a,c){a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);this.kB=Tk(a,c);h(GE)[this.kB]=this}GE.prototype=Object.create(gE.prototype);GE.prototype.constructor=GE;GE.prototype.lB=GE;GE.mB={};b.btCapsuleShapeZ=GE;GE.prototype.setMargin=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Uk(c,a)};GE.prototype.getMargin=function(){return Vk(this.kB)};GE.prototype.getUpAxis=function(){return Wk(this.kB)};GE.prototype.getRadius=function(){return Xk(this.kB)};
|
||||
GE.prototype.getHalfHeight=function(){return Yk(this.kB)};GE.prototype.setLocalScaling=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Zk(c,a)};GE.prototype.getLocalScaling=function(){return k($k(this.kB),m)};GE.prototype.calculateLocalInertia=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);al(d,a,c)};GE.prototype.__destroy__=function(){bl(this.kB)};function HE(a){a&&"object"===typeof a&&(a=a.kB);this.kB=cl(a);h(HE)[this.kB]=this}
|
||||
HE.prototype=Object.create(hE.prototype);HE.prototype.constructor=HE;HE.prototype.lB=HE;HE.mB={};b.btCylinderShapeX=HE;HE.prototype.setMargin=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);dl(c,a)};HE.prototype.getMargin=function(){return el(this.kB)};HE.prototype.setLocalScaling=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);fl(c,a)};HE.prototype.getLocalScaling=function(){return k(gl(this.kB),m)};
|
||||
HE.prototype.calculateLocalInertia=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);hl(d,a,c)};HE.prototype.__destroy__=function(){il(this.kB)};function IE(a){a&&"object"===typeof a&&(a=a.kB);this.kB=jl(a);h(IE)[this.kB]=this}IE.prototype=Object.create(hE.prototype);IE.prototype.constructor=IE;IE.prototype.lB=IE;IE.mB={};b.btCylinderShapeZ=IE;IE.prototype.setMargin=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);kl(c,a)};
|
||||
IE.prototype.getMargin=function(){return ll(this.kB)};IE.prototype.setLocalScaling=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);ml(c,a)};IE.prototype.getLocalScaling=function(){return k(nl(this.kB),m)};IE.prototype.calculateLocalInertia=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);ol(d,a,c)};IE.prototype.__destroy__=function(){pl(this.kB)};function JE(a){a&&"object"===typeof a&&(a=a.kB);this.kB=ql(a);h(JE)[this.kB]=this}JE.prototype=Object.create(l.prototype);
|
||||
JE.prototype.constructor=JE;JE.prototype.lB=JE;JE.mB={};b.btSphereShape=JE;JE.prototype.setMargin=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);rl(c,a)};JE.prototype.getMargin=function(){return sl(this.kB)};JE.prototype.setLocalScaling=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);tl(c,a)};JE.prototype.getLocalScaling=function(){return k(ul(this.kB),m)};
|
||||
JE.prototype.calculateLocalInertia=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);vl(d,a,c)};JE.prototype.__destroy__=function(){wl(this.kB)};function KE(a,c,d){ND();a&&"object"===typeof a&&(a=a.kB);"object"==typeof c&&(c=RD(c));d&&"object"===typeof d&&(d=d.kB);this.kB=xl(a,c,d);h(KE)[this.kB]=this}KE.prototype=Object.create(l.prototype);KE.prototype.constructor=KE;KE.prototype.lB=KE;KE.mB={};b.btMultiSphereShape=KE;
|
||||
KE.prototype.setLocalScaling=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);yl(c,a)};KE.prototype.getLocalScaling=function(){return k(zl(this.kB),m)};KE.prototype.calculateLocalInertia=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);Al(d,a,c)};KE.prototype.__destroy__=function(){Bl(this.kB)};function LE(a,c){a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);this.kB=Cl(a,c);h(LE)[this.kB]=this}LE.prototype=Object.create(iE.prototype);
|
||||
LE.prototype.constructor=LE;LE.prototype.lB=LE;LE.mB={};b.btConeShapeX=LE;LE.prototype.setLocalScaling=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Dl(c,a)};LE.prototype.getLocalScaling=function(){return k(El(this.kB),m)};LE.prototype.calculateLocalInertia=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);Fl(d,a,c)};LE.prototype.__destroy__=function(){Gl(this.kB)};
|
||||
function ME(a,c){a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);this.kB=Hl(a,c);h(ME)[this.kB]=this}ME.prototype=Object.create(iE.prototype);ME.prototype.constructor=ME;ME.prototype.lB=ME;ME.mB={};b.btConeShapeZ=ME;ME.prototype.setLocalScaling=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Il(c,a)};ME.prototype.getLocalScaling=function(){return k(Jl(this.kB),m)};
|
||||
ME.prototype.calculateLocalInertia=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);Kl(d,a,c)};ME.prototype.__destroy__=function(){Ll(this.kB)};function NE(){throw"cannot construct a btIntArray, no constructor in IDL";}NE.prototype=Object.create(f.prototype);NE.prototype.constructor=NE;NE.prototype.lB=NE;NE.mB={};b.btIntArray=NE;NE.prototype.size=NE.prototype.size=function(){return Ml(this.kB)};
|
||||
NE.prototype.at=NE.prototype.at=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);return Nl(c,a)};NE.prototype.__destroy__=function(){Ol(this.kB)};function OE(){throw"cannot construct a btFace, no constructor in IDL";}OE.prototype=Object.create(f.prototype);OE.prototype.constructor=OE;OE.prototype.lB=OE;OE.mB={};b.btFace=OE;OE.prototype.get_m_indices=OE.prototype.xD=function(){return k(Pl(this.kB),NE)};
|
||||
OE.prototype.set_m_indices=OE.prototype.oG=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Ql(c,a)};Object.defineProperty(OE.prototype,"m_indices",{get:OE.prototype.xD,set:OE.prototype.oG});OE.prototype.get_m_plane=OE.prototype.WD=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);return Rl(c,a)};OE.prototype.set_m_plane=OE.prototype.NG=function(a,c){var d=this.kB;ND();a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);Sl(d,a,c)};
|
||||
Object.defineProperty(OE.prototype,"m_plane",{get:OE.prototype.WD,set:OE.prototype.NG});OE.prototype.__destroy__=function(){Tl(this.kB)};function AE(){throw"cannot construct a btVector3Array, no constructor in IDL";}AE.prototype=Object.create(f.prototype);AE.prototype.constructor=AE;AE.prototype.lB=AE;AE.mB={};b.btVector3Array=AE;AE.prototype.size=AE.prototype.size=function(){return Ul(this.kB)};
|
||||
AE.prototype.at=AE.prototype.at=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);return k(Vl(c,a),m)};AE.prototype.__destroy__=function(){Wl(this.kB)};function PE(){throw"cannot construct a btFaceArray, no constructor in IDL";}PE.prototype=Object.create(f.prototype);PE.prototype.constructor=PE;PE.prototype.lB=PE;PE.mB={};b.btFaceArray=PE;PE.prototype.size=PE.prototype.size=function(){return Xl(this.kB)};
|
||||
PE.prototype.at=PE.prototype.at=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);return k(Yl(c,a),OE)};PE.prototype.__destroy__=function(){Zl(this.kB)};function QE(){throw"cannot construct a btConvexPolyhedron, no constructor in IDL";}QE.prototype=Object.create(f.prototype);QE.prototype.constructor=QE;QE.prototype.lB=QE;QE.mB={};b.btConvexPolyhedron=QE;QE.prototype.get_m_vertices=QE.prototype.BE=function(){return k($l(this.kB),AE)};
|
||||
QE.prototype.set_m_vertices=QE.prototype.tH=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);am(c,a)};Object.defineProperty(QE.prototype,"m_vertices",{get:QE.prototype.BE,set:QE.prototype.tH});QE.prototype.get_m_faces=QE.prototype.KB=function(){return k(bm(this.kB),PE)};QE.prototype.set_m_faces=QE.prototype.UB=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);cm(c,a)};Object.defineProperty(QE.prototype,"m_faces",{get:QE.prototype.KB,set:QE.prototype.UB});
|
||||
QE.prototype.__destroy__=function(){dm(this.kB)};function RE(a,c){ND();"object"==typeof a&&(a=RD(a));c&&"object"===typeof c&&(c=c.kB);this.kB=void 0===a?em():void 0===c?fm(a):gm(a,c);h(RE)[this.kB]=this}RE.prototype=Object.create(l.prototype);RE.prototype.constructor=RE;RE.prototype.lB=RE;RE.mB={};b.btConvexHullShape=RE;RE.prototype.addPoint=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);void 0===c?hm(d,a):im(d,a,c)};
|
||||
RE.prototype.setMargin=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);jm(c,a)};RE.prototype.getMargin=function(){return km(this.kB)};RE.prototype.getNumVertices=function(){return lm(this.kB)};RE.prototype.initializePolyhedralFeatures=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);return!!mm(c,a)};RE.prototype.recalcLocalAabb=function(){nm(this.kB)};RE.prototype.getConvexPolyhedron=function(){return k(om(this.kB),QE)};
|
||||
RE.prototype.setLocalScaling=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);pm(c,a)};RE.prototype.getLocalScaling=function(){return k(qm(this.kB),m)};RE.prototype.calculateLocalInertia=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);rm(d,a,c)};RE.prototype.__destroy__=function(){sm(this.kB)};function SE(a){a&&"object"===typeof a&&(a=a.kB);this.kB=tm(a);h(SE)[this.kB]=this}SE.prototype=Object.create(f.prototype);SE.prototype.constructor=SE;
|
||||
SE.prototype.lB=SE;SE.mB={};b.btShapeHull=SE;SE.prototype.buildHull=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);return!!um(c,a)};SE.prototype.numVertices=function(){return wm(this.kB)};SE.prototype.getVertexPointer=function(){return k(xm(this.kB),m)};SE.prototype.__destroy__=function(){ym(this.kB)};function TE(a){a&&"object"===typeof a&&(a=a.kB);this.kB=void 0===a?zm():Am(a);h(TE)[this.kB]=this}TE.prototype=Object.create(l.prototype);TE.prototype.constructor=TE;TE.prototype.lB=TE;
|
||||
TE.mB={};b.btCompoundShape=TE;TE.prototype.addChildShape=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);Bm(d,a,c)};TE.prototype.removeChildShape=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Cm(c,a)};TE.prototype.removeChildShapeByIndex=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Dm(c,a)};TE.prototype.getNumChildShapes=function(){return Em(this.kB)};
|
||||
TE.prototype.getChildShape=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);return k(Fm(c,a),l)};TE.prototype.updateChildTransform=function(a,c,d){var e=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);void 0===d?Gm(e,a,c):Hm(e,a,c,d)};TE.prototype.setMargin=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Im(c,a)};TE.prototype.getMargin=function(){return Jm(this.kB)};
|
||||
TE.prototype.setLocalScaling=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Km(c,a)};TE.prototype.getLocalScaling=function(){return k(Lm(this.kB),m)};TE.prototype.calculateLocalInertia=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);Mm(d,a,c)};TE.prototype.__destroy__=function(){Nm(this.kB)};function UE(){throw"cannot construct a btIndexedMesh, no constructor in IDL";}UE.prototype=Object.create(f.prototype);UE.prototype.constructor=UE;
|
||||
UE.prototype.lB=UE;UE.mB={};b.btIndexedMesh=UE;UE.prototype.get_m_numTriangles=UE.prototype.UD=function(){return Om(this.kB)};UE.prototype.set_m_numTriangles=UE.prototype.LG=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Pm(c,a)};Object.defineProperty(UE.prototype,"m_numTriangles",{get:UE.prototype.UD,set:UE.prototype.LG});UE.prototype.__destroy__=function(){Qm(this.kB)};function VE(){throw"cannot construct a btIndexedMeshArray, no constructor in IDL";}VE.prototype=Object.create(f.prototype);
|
||||
VE.prototype.constructor=VE;VE.prototype.lB=VE;VE.mB={};b.btIndexedMeshArray=VE;VE.prototype.size=VE.prototype.size=function(){return Rm(this.kB)};VE.prototype.at=VE.prototype.at=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);return k(Sm(c,a),UE)};VE.prototype.__destroy__=function(){Tm(this.kB)};function WE(a,c){a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);this.kB=void 0===a?Um():void 0===c?Vm(a):Wm(a,c);h(WE)[this.kB]=this}WE.prototype=Object.create(jE.prototype);
|
||||
WE.prototype.constructor=WE;WE.prototype.lB=WE;WE.mB={};b.btTriangleMesh=WE;WE.prototype.addTriangle=function(a,c,d,e){var g=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);e&&"object"===typeof e&&(e=e.kB);void 0===e?Xm(g,a,c,d):Ym(g,a,c,d,e)};WE.prototype.findOrAddVertex=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);return Zm(d,a,c)};
|
||||
WE.prototype.addIndex=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);$m(c,a)};WE.prototype.getIndexedMeshArray=function(){return k(an(this.kB),VE)};WE.prototype.setScaling=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);bn(c,a)};WE.prototype.__destroy__=function(){cn(this.kB)};function XE(){this.kB=dn();h(XE)[this.kB]=this}XE.prototype=Object.create(ZD.prototype);XE.prototype.constructor=XE;XE.prototype.lB=XE;XE.mB={};b.btEmptyShape=XE;
|
||||
XE.prototype.setLocalScaling=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);en(c,a)};XE.prototype.getLocalScaling=function(){return k(fn(this.kB),m)};XE.prototype.calculateLocalInertia=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);gn(d,a,c)};XE.prototype.__destroy__=function(){hn(this.kB)};function YE(a,c){a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);this.kB=jn(a,c);h(YE)[this.kB]=this}YE.prototype=Object.create(ZD.prototype);
|
||||
YE.prototype.constructor=YE;YE.prototype.lB=YE;YE.mB={};b.btStaticPlaneShape=YE;YE.prototype.setLocalScaling=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);kn(c,a)};YE.prototype.getLocalScaling=function(){return k(ln(this.kB),m)};YE.prototype.calculateLocalInertia=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);mn(d,a,c)};YE.prototype.__destroy__=function(){nn(this.kB)};
|
||||
function ZE(a,c,d){a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);this.kB=void 0===d?on(a,c):pn(a,c,d);h(ZE)[this.kB]=this}ZE.prototype=Object.create(kE.prototype);ZE.prototype.constructor=ZE;ZE.prototype.lB=ZE;ZE.mB={};b.btBvhTriangleMeshShape=ZE;ZE.prototype.setLocalScaling=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);qn(c,a)};ZE.prototype.getLocalScaling=function(){return k(rn(this.kB),m)};
|
||||
ZE.prototype.calculateLocalInertia=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);sn(d,a,c)};ZE.prototype.__destroy__=function(){tn(this.kB)};
|
||||
function $E(a,c,d,e,g,n,z,T,Da){a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);e&&"object"===typeof e&&(e=e.kB);g&&"object"===typeof g&&(g=g.kB);n&&"object"===typeof n&&(n=n.kB);z&&"object"===typeof z&&(z=z.kB);T&&"object"===typeof T&&(T=T.kB);Da&&"object"===typeof Da&&(Da=Da.kB);this.kB=un(a,c,d,e,g,n,z,T,Da);h($E)[this.kB]=this}$E.prototype=Object.create(ZD.prototype);$E.prototype.constructor=$E;$E.prototype.lB=$E;$E.mB={};
|
||||
b.btHeightfieldTerrainShape=$E;$E.prototype.setMargin=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);vn(c,a)};$E.prototype.getMargin=function(){return wn(this.kB)};$E.prototype.setLocalScaling=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);xn(c,a)};$E.prototype.getLocalScaling=function(){return k(yn(this.kB),m)};$E.prototype.calculateLocalInertia=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);zn(d,a,c)};
|
||||
$E.prototype.__destroy__=function(){An(this.kB)};function aF(a,c,d,e){a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);e&&"object"===typeof e&&(e=e.kB);this.kB=Bn(a,c,d,e);h(aF)[this.kB]=this}aF.prototype=Object.create(f.prototype);aF.prototype.constructor=aF;aF.prototype.lB=aF;aF.mB={};b.btAABB=aF;aF.prototype.invalidate=function(){Cn(this.kB)};aF.prototype.increment_margin=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Dn(c,a)};
|
||||
aF.prototype.copy_with_margin=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);En(d,a,c)};aF.prototype.__destroy__=function(){Fn(this.kB)};function bF(){this.kB=Gn();h(bF)[this.kB]=this}bF.prototype=Object.create(f.prototype);bF.prototype.constructor=bF;bF.prototype.lB=bF;bF.mB={};b.btPrimitiveTriangle=bF;bF.prototype.__destroy__=function(){Hn(this.kB)};
|
||||
function cF(a,c,d){a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);this.kB=In(a,c,d);h(cF)[this.kB]=this}cF.prototype=Object.create(f.prototype);cF.prototype.constructor=cF;cF.prototype.lB=cF;cF.mB={};b.btTriangleShapeEx=cF;cF.prototype.getAabb=function(a,c,d){var e=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);Jn(e,a,c,d)};
|
||||
cF.prototype.applyTransform=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Kn(c,a)};cF.prototype.buildTriPlane=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Ln(c,a)};cF.prototype.__destroy__=function(){Mn(this.kB)};function dF(){this.kB=Nn();h(dF)[this.kB]=this}dF.prototype=Object.create(f.prototype);dF.prototype.constructor=dF;dF.prototype.lB=dF;dF.mB={};b.btTetrahedronShapeEx=dF;
|
||||
dF.prototype.setVertices=function(a,c,d,e){var g=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);e&&"object"===typeof e&&(e=e.kB);On(g,a,c,d,e)};dF.prototype.__destroy__=function(){Pn(this.kB)};function eF(){throw"cannot construct a CompoundPrimitiveManager, no constructor in IDL";}eF.prototype=Object.create(lE.prototype);eF.prototype.constructor=eF;eF.prototype.lB=eF;eF.mB={};b.CompoundPrimitiveManager=eF;
|
||||
eF.prototype.get_primitive_count=function(){return Qn(this.kB)};eF.prototype.get_primitive_box=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);Rn(d,a,c)};eF.prototype.get_primitive_triangle=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);Sn(d,a,c)};eF.prototype.is_trimesh=function(){return!!Tn(this.kB)};eF.prototype.get_m_compoundShape=eF.prototype.VC=function(){return k(Un(this.kB),H)};
|
||||
eF.prototype.set_m_compoundShape=eF.prototype.MF=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Vn(c,a)};Object.defineProperty(eF.prototype,"m_compoundShape",{get:eF.prototype.VC,set:eF.prototype.MF});eF.prototype.__destroy__=function(){Wn(this.kB)};function H(a){a&&"object"===typeof a&&(a=a.kB);this.kB=void 0===a?Xn():Yn(a);h(H)[this.kB]=this}H.prototype=Object.create(w.prototype);H.prototype.constructor=H;H.prototype.lB=H;H.mB={};b.btGImpactCompoundShape=H;
|
||||
H.prototype.childrenHasTransform=function(){return!!Zn(this.kB)};H.prototype.getPrimitiveManager=function(){return k($n(this.kB),lE)};H.prototype.getCompoundPrimitiveManager=function(){return k(ao(this.kB),eF)};H.prototype.getNumChildShapes=function(){return bo(this.kB)};H.prototype.addChildShape=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);co(d,a,c)};
|
||||
H.prototype.getChildShape=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);return k(eo(c,a),l)};H.prototype.getChildAabb=function(a,c,d,e){var g=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);e&&"object"===typeof e&&(e=e.kB);fo(g,a,c,d,e)};H.prototype.getChildTransform=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);return k(go(c,a),r)};
|
||||
H.prototype.setChildTransform=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);ho(d,a,c)};H.prototype.calculateLocalInertia=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);io(d,a,c)};H.prototype.getName=function(){return sa(jo(this.kB))};H.prototype.getGImpactShapeType=function(){return ko(this.kB)};H.prototype.setLocalScaling=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);lo(c,a)};
|
||||
H.prototype.getLocalScaling=function(){return k(mo(this.kB),m)};H.prototype.updateBound=function(){no(this.kB)};H.prototype.postUpdate=function(){oo(this.kB)};H.prototype.getShapeType=function(){return po(this.kB)};H.prototype.needsRetrieveTriangles=function(){return!!qo(this.kB)};H.prototype.needsRetrieveTetrahedrons=function(){return!!ro(this.kB)};H.prototype.getBulletTriangle=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);so(d,a,c)};
|
||||
H.prototype.getBulletTetrahedron=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);to(d,a,c)};H.prototype.__destroy__=function(){uo(this.kB)};function I(a){a&&"object"===typeof a&&(a=a.kB);this.kB=void 0===a?vo():wo(a);h(I)[this.kB]=this}I.prototype=Object.create(lE.prototype);I.prototype.constructor=I;I.prototype.lB=I;I.mB={};b.TrimeshPrimitiveManager=I;I.prototype.lock=I.prototype.lock=function(){xo(this.kB)};I.prototype.unlock=I.prototype.unlock=function(){yo(this.kB)};
|
||||
I.prototype.is_trimesh=function(){return!!zo(this.kB)};I.prototype.get_vertex_count=function(){return Ao(this.kB)};I.prototype.get_indices=function(a,c,d,e){var g=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);e&&"object"===typeof e&&(e=e.kB);Bo(g,a,c,d,e)};I.prototype.get_vertex=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);Co(d,a,c)};
|
||||
I.prototype.get_bullet_triangle=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);Do(d,a,c)};I.prototype.get_m_margin=I.prototype.LD=function(){return Eo(this.kB)};I.prototype.set_m_margin=I.prototype.CG=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Fo(c,a)};Object.defineProperty(I.prototype,"m_margin",{get:I.prototype.LD,set:I.prototype.CG});I.prototype.get_m_meshInterface=I.prototype.OD=function(){return k(Go(this.kB),jE)};
|
||||
I.prototype.set_m_meshInterface=I.prototype.FG=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Ho(c,a)};Object.defineProperty(I.prototype,"m_meshInterface",{get:I.prototype.OD,set:I.prototype.FG});I.prototype.get_m_part=I.prototype.VD=function(){return Io(this.kB)};I.prototype.set_m_part=I.prototype.MG=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Jo(c,a)};Object.defineProperty(I.prototype,"m_part",{get:I.prototype.VD,set:I.prototype.MG});
|
||||
I.prototype.get_m_lock_count=I.prototype.JD=function(){return Ko(this.kB)};I.prototype.set_m_lock_count=I.prototype.AG=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Lo(c,a)};Object.defineProperty(I.prototype,"m_lock_count",{get:I.prototype.JD,set:I.prototype.AG});I.prototype.get_numverts=I.prototype.LE=function(){return Mo(this.kB)};I.prototype.set_numverts=I.prototype.DH=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);No(c,a)};
|
||||
Object.defineProperty(I.prototype,"numverts",{get:I.prototype.LE,set:I.prototype.DH});I.prototype.get_type=I.prototype.PE=function(){return Oo(this.kB)};I.prototype.set_type=I.prototype.HH=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Po(c,a)};Object.defineProperty(I.prototype,"type",{get:I.prototype.PE,set:I.prototype.HH});I.prototype.get_stride=I.prototype.NE=function(){return Qo(this.kB)};
|
||||
I.prototype.set_stride=I.prototype.FH=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Ro(c,a)};Object.defineProperty(I.prototype,"stride",{get:I.prototype.NE,set:I.prototype.FH});I.prototype.get_indexstride=I.prototype.gC=function(){return So(this.kB)};I.prototype.set_indexstride=I.prototype.YE=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);To(c,a)};Object.defineProperty(I.prototype,"indexstride",{get:I.prototype.gC,set:I.prototype.YE});
|
||||
I.prototype.get_numfaces=I.prototype.KE=function(){return Uo(this.kB)};I.prototype.set_numfaces=I.prototype.CH=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Vo(c,a)};Object.defineProperty(I.prototype,"numfaces",{get:I.prototype.KE,set:I.prototype.CH});I.prototype.get_indicestype=I.prototype.hC=function(){return Wo(this.kB)};I.prototype.set_indicestype=I.prototype.ZE=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Xo(c,a)};
|
||||
Object.defineProperty(I.prototype,"indicestype",{get:I.prototype.hC,set:I.prototype.ZE});I.prototype.__destroy__=function(){Yo(this.kB)};function fF(a,c){a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);this.kB=Zo(a,c);h(fF)[this.kB]=this}fF.prototype=Object.create(w.prototype);fF.prototype.constructor=fF;fF.prototype.lB=fF;fF.mB={};b.btGImpactMeshShapePart=fF;fF.prototype.getTrimeshPrimitiveManager=function(){return k($o(this.kB),I)};fF.prototype.getVertexCount=function(){return ap(this.kB)};
|
||||
fF.prototype.getVertex=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);bp(d,a,c)};fF.prototype.getPart=function(){return cp(this.kB)};fF.prototype.setLocalScaling=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);dp(c,a)};fF.prototype.getLocalScaling=function(){return k(ep(this.kB),m)};fF.prototype.updateBound=function(){fp(this.kB)};fF.prototype.postUpdate=function(){gp(this.kB)};fF.prototype.getShapeType=function(){return hp(this.kB)};
|
||||
fF.prototype.needsRetrieveTriangles=function(){return!!ip(this.kB)};fF.prototype.needsRetrieveTetrahedrons=function(){return!!jp(this.kB)};fF.prototype.getBulletTriangle=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);kp(d,a,c)};fF.prototype.getBulletTetrahedron=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);lp(d,a,c)};fF.prototype.__destroy__=function(){mp(this.kB)};
|
||||
function gF(a){a&&"object"===typeof a&&(a=a.kB);this.kB=np(a);h(gF)[this.kB]=this}gF.prototype=Object.create(w.prototype);gF.prototype.constructor=gF;gF.prototype.lB=gF;gF.mB={};b.btGImpactMeshShape=gF;gF.prototype.getMeshInterface=function(){return k(op(this.kB),jE)};gF.prototype.getMeshPartCount=function(){return pp(this.kB)};gF.prototype.getMeshPart=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);return k(qp(c,a),fF)};gF.prototype.calculateSerializeBufferSize=function(){return rp(this.kB)};
|
||||
gF.prototype.setLocalScaling=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);sp(c,a)};gF.prototype.getLocalScaling=function(){return k(tp(this.kB),m)};gF.prototype.updateBound=function(){up(this.kB)};gF.prototype.postUpdate=function(){vp(this.kB)};gF.prototype.getShapeType=function(){return wp(this.kB)};gF.prototype.needsRetrieveTriangles=function(){return!!xp(this.kB)};gF.prototype.needsRetrieveTetrahedrons=function(){return!!yp(this.kB)};
|
||||
gF.prototype.getBulletTriangle=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);zp(d,a,c)};gF.prototype.getBulletTetrahedron=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);Ap(d,a,c)};gF.prototype.__destroy__=function(){Bp(this.kB)};
|
||||
function hF(a,c){a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);this.kB=void 0===a?Cp():void 0===c?_emscripten_bind_btCollisionAlgorithmConstructionInfo_btCollisionAlgorithmConstructionInfo_1(a):Dp(a,c);h(hF)[this.kB]=this}hF.prototype=Object.create(f.prototype);hF.prototype.constructor=hF;hF.prototype.lB=hF;hF.mB={};b.btCollisionAlgorithmConstructionInfo=hF;hF.prototype.get_m_dispatcher1=hF.prototype.eD=function(){return k(Ep(this.kB),TD)};
|
||||
hF.prototype.set_m_dispatcher1=hF.prototype.WF=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Fp(c,a)};Object.defineProperty(hF.prototype,"m_dispatcher1",{get:hF.prototype.eD,set:hF.prototype.WF});hF.prototype.get_m_manifold=hF.prototype.KD=function(){return k(Gp(this.kB),oE)};hF.prototype.set_m_manifold=hF.prototype.BG=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Hp(c,a)};Object.defineProperty(hF.prototype,"m_manifold",{get:hF.prototype.KD,set:hF.prototype.BG});
|
||||
hF.prototype.__destroy__=function(){Ip(this.kB)};function iF(a,c,d){a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);this.kB=Jp(a,c,d);h(iF)[this.kB]=this}iF.prototype=Object.create(mE.prototype);iF.prototype.constructor=iF;iF.prototype.lB=iF;iF.mB={};b.btGImpactCollisionAlgorithm=iF;iF.prototype.registerAlgorithm=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Kp(c,a)};iF.prototype.__destroy__=function(){Lp(this.kB)};
|
||||
function jF(){this.kB=Mp();h(jF)[this.kB]=this}jF.prototype=Object.create(f.prototype);jF.prototype.constructor=jF;jF.prototype.lB=jF;jF.mB={};b.btDefaultCollisionConstructionInfo=jF;jF.prototype.__destroy__=function(){Np(this.kB)};function oE(){this.kB=Op();h(oE)[this.kB]=this}oE.prototype=Object.create(f.prototype);oE.prototype.constructor=oE;oE.prototype.lB=oE;oE.mB={};b.btPersistentManifold=oE;oE.prototype.getBody0=function(){return k(Pp(this.kB),q)};
|
||||
oE.prototype.getBody1=function(){return k(Qp(this.kB),q)};oE.prototype.getNumContacts=function(){return Rp(this.kB)};oE.prototype.getContactPoint=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);return k(Sp(c,a),E)};oE.prototype.__destroy__=function(){Tp(this.kB)};function kF(a){a&&"object"===typeof a&&(a=a.kB);this.kB=Up(a);h(kF)[this.kB]=this}kF.prototype=Object.create(TD.prototype);kF.prototype.constructor=kF;kF.prototype.lB=kF;kF.mB={};b.btCollisionDispatcher=kF;
|
||||
kF.prototype.getNumManifolds=function(){return Vp(this.kB)};kF.prototype.getManifoldByIndexInternal=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);return k(Wp(c,a),oE)};kF.prototype.__destroy__=function(){Xp(this.kB)};function lF(){throw"cannot construct a btOverlappingPairCallback, no constructor in IDL";}lF.prototype=Object.create(f.prototype);lF.prototype.constructor=lF;lF.prototype.lB=lF;lF.mB={};b.btOverlappingPairCallback=lF;lF.prototype.__destroy__=function(){Yp(this.kB)};
|
||||
function UD(){throw"cannot construct a btOverlappingPairCache, no constructor in IDL";}UD.prototype=Object.create(f.prototype);UD.prototype.constructor=UD;UD.prototype.lB=UD;UD.mB={};b.btOverlappingPairCache=UD;UD.prototype.setInternalGhostPairCallback=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Zp(c,a)};UD.prototype.getNumOverlappingPairs=function(){return $p(this.kB)};UD.prototype.__destroy__=function(){aq(this.kB)};
|
||||
function mF(a,c,d,e,g){a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);e&&"object"===typeof e&&(e=e.kB);g&&"object"===typeof g&&(g=g.kB);this.kB=void 0===d?bq(a,c):void 0===e?cq(a,c,d):void 0===g?dq(a,c,d,e):eq(a,c,d,e,g);h(mF)[this.kB]=this}mF.prototype=Object.create(f.prototype);mF.prototype.constructor=mF;mF.prototype.lB=mF;mF.mB={};b.btAxisSweep3=mF;mF.prototype.__destroy__=function(){fq(this.kB)};
|
||||
function VD(){throw"cannot construct a btBroadphaseInterface, no constructor in IDL";}VD.prototype=Object.create(f.prototype);VD.prototype.constructor=VD;VD.prototype.lB=VD;VD.mB={};b.btBroadphaseInterface=VD;VD.prototype.getOverlappingPairCache=function(){return k(gq(this.kB),UD)};VD.prototype.__destroy__=function(){hq(this.kB)};function nF(){throw"cannot construct a btCollisionConfiguration, no constructor in IDL";}nF.prototype=Object.create(f.prototype);nF.prototype.constructor=nF;
|
||||
nF.prototype.lB=nF;nF.mB={};b.btCollisionConfiguration=nF;nF.prototype.__destroy__=function(){iq(this.kB)};function oF(){this.kB=jq();h(oF)[this.kB]=this}oF.prototype=Object.create(f.prototype);oF.prototype.constructor=oF;oF.prototype.lB=oF;oF.mB={};b.btDbvtBroadphase=oF;oF.prototype.__destroy__=function(){kq(this.kB)};function YD(){throw"cannot construct a btBroadphaseProxy, no constructor in IDL";}YD.prototype=Object.create(f.prototype);YD.prototype.constructor=YD;YD.prototype.lB=YD;YD.mB={};
|
||||
b.btBroadphaseProxy=YD;YD.prototype.get_m_collisionFilterGroup=YD.prototype.nB=function(){return lq(this.kB)};YD.prototype.set_m_collisionFilterGroup=YD.prototype.pB=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);mq(c,a)};Object.defineProperty(YD.prototype,"m_collisionFilterGroup",{get:YD.prototype.nB,set:YD.prototype.pB});YD.prototype.get_m_collisionFilterMask=YD.prototype.oB=function(){return nq(this.kB)};
|
||||
YD.prototype.set_m_collisionFilterMask=YD.prototype.qB=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);oq(c,a)};Object.defineProperty(YD.prototype,"m_collisionFilterMask",{get:YD.prototype.oB,set:YD.prototype.qB});YD.prototype.__destroy__=function(){pq(this.kB)};function J(a,c,d,e){a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);e&&"object"===typeof e&&(e=e.kB);this.kB=void 0===e?qq(a,c,d):rq(a,c,d,e);h(J)[this.kB]=this}J.prototype=Object.create(f.prototype);
|
||||
J.prototype.constructor=J;J.prototype.lB=J;J.mB={};b.btRigidBodyConstructionInfo=J;J.prototype.get_m_linearDamping=J.prototype.DD=function(){return sq(this.kB)};J.prototype.set_m_linearDamping=J.prototype.uG=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);tq(c,a)};Object.defineProperty(J.prototype,"m_linearDamping",{get:J.prototype.DD,set:J.prototype.uG});J.prototype.get_m_angularDamping=J.prototype.HC=function(){return uq(this.kB)};
|
||||
J.prototype.set_m_angularDamping=J.prototype.yF=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);vq(c,a)};Object.defineProperty(J.prototype,"m_angularDamping",{get:J.prototype.HC,set:J.prototype.yF});J.prototype.get_m_friction=J.prototype.kD=function(){return wq(this.kB)};J.prototype.set_m_friction=J.prototype.bG=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);xq(c,a)};Object.defineProperty(J.prototype,"m_friction",{get:J.prototype.kD,set:J.prototype.bG});
|
||||
J.prototype.get_m_rollingFriction=J.prototype.dE=function(){return yq(this.kB)};J.prototype.set_m_rollingFriction=J.prototype.WG=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);zq(c,a)};Object.defineProperty(J.prototype,"m_rollingFriction",{get:J.prototype.dE,set:J.prototype.WG});J.prototype.get_m_restitution=J.prototype.bE=function(){return Aq(this.kB)};J.prototype.set_m_restitution=J.prototype.UG=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Bq(c,a)};
|
||||
Object.defineProperty(J.prototype,"m_restitution",{get:J.prototype.bE,set:J.prototype.UG});J.prototype.get_m_linearSleepingThreshold=J.prototype.ED=function(){return Cq(this.kB)};J.prototype.set_m_linearSleepingThreshold=J.prototype.vG=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Dq(c,a)};Object.defineProperty(J.prototype,"m_linearSleepingThreshold",{get:J.prototype.ED,set:J.prototype.vG});J.prototype.get_m_angularSleepingThreshold=J.prototype.IC=function(){return Eq(this.kB)};
|
||||
J.prototype.set_m_angularSleepingThreshold=J.prototype.zF=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Fq(c,a)};Object.defineProperty(J.prototype,"m_angularSleepingThreshold",{get:J.prototype.IC,set:J.prototype.zF});J.prototype.get_m_additionalDamping=J.prototype.CC=function(){return!!Gq(this.kB)};J.prototype.set_m_additionalDamping=J.prototype.tF=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Hq(c,a)};
|
||||
Object.defineProperty(J.prototype,"m_additionalDamping",{get:J.prototype.CC,set:J.prototype.tF});J.prototype.get_m_additionalDampingFactor=J.prototype.DC=function(){return Iq(this.kB)};J.prototype.set_m_additionalDampingFactor=J.prototype.uF=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Jq(c,a)};Object.defineProperty(J.prototype,"m_additionalDampingFactor",{get:J.prototype.DC,set:J.prototype.uF});J.prototype.get_m_additionalLinearDampingThresholdSqr=J.prototype.EC=function(){return Kq(this.kB)};
|
||||
J.prototype.set_m_additionalLinearDampingThresholdSqr=J.prototype.vF=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Lq(c,a)};Object.defineProperty(J.prototype,"m_additionalLinearDampingThresholdSqr",{get:J.prototype.EC,set:J.prototype.vF});J.prototype.get_m_additionalAngularDampingThresholdSqr=J.prototype.BC=function(){return Mq(this.kB)};J.prototype.set_m_additionalAngularDampingThresholdSqr=J.prototype.sF=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Nq(c,a)};
|
||||
Object.defineProperty(J.prototype,"m_additionalAngularDampingThresholdSqr",{get:J.prototype.BC,set:J.prototype.sF});J.prototype.get_m_additionalAngularDampingFactor=J.prototype.AC=function(){return Oq(this.kB)};J.prototype.set_m_additionalAngularDampingFactor=J.prototype.rF=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Pq(c,a)};Object.defineProperty(J.prototype,"m_additionalAngularDampingFactor",{get:J.prototype.AC,set:J.prototype.rF});J.prototype.__destroy__=function(){Qq(this.kB)};
|
||||
function K(a){a&&"object"===typeof a&&(a=a.kB);this.kB=Rq(a);h(K)[this.kB]=this}K.prototype=Object.create(q.prototype);K.prototype.constructor=K;K.prototype.lB=K;K.mB={};b.btRigidBody=K;K.prototype.getCenterOfMassTransform=function(){return k(Sq(this.kB),r)};K.prototype.setCenterOfMassTransform=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Tq(c,a)};K.prototype.setSleepingThresholds=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);Uq(d,a,c)};
|
||||
K.prototype.getLinearDamping=function(){return Vq(this.kB)};K.prototype.getAngularDamping=function(){return Wq(this.kB)};K.prototype.setDamping=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);Xq(d,a,c)};K.prototype.setMassProps=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);Yq(d,a,c)};K.prototype.getLinearFactor=function(){return k(Zq(this.kB),m)};
|
||||
K.prototype.setLinearFactor=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);$q(c,a)};K.prototype.applyTorque=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);ar(c,a)};K.prototype.applyLocalTorque=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);br(c,a)};K.prototype.applyForce=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);cr(d,a,c)};
|
||||
K.prototype.applyCentralForce=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);dr(c,a)};K.prototype.applyCentralLocalForce=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);er(c,a)};K.prototype.applyTorqueImpulse=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);fr(c,a)};K.prototype.applyImpulse=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);gr(d,a,c)};
|
||||
K.prototype.applyCentralImpulse=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);hr(c,a)};K.prototype.updateInertiaTensor=function(){ir(this.kB)};K.prototype.getLinearVelocity=function(){return k(jr(this.kB),m)};K.prototype.getAngularVelocity=function(){return k(kr(this.kB),m)};K.prototype.setLinearVelocity=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);lr(c,a)};K.prototype.setAngularVelocity=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);mr(c,a)};
|
||||
K.prototype.getMotionState=function(){return k(nr(this.kB),dE)};K.prototype.setMotionState=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);or(c,a)};K.prototype.getAngularFactor=function(){return k(pr(this.kB),m)};K.prototype.setAngularFactor=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);qr(c,a)};K.prototype.upcast=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);return k(rr(c,a),K)};
|
||||
K.prototype.getAabb=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);sr(d,a,c)};K.prototype.applyGravity=function(){tr(this.kB)};K.prototype.getGravity=function(){return k(ur(this.kB),m)};K.prototype.setGravity=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);vr(c,a)};K.prototype.getBroadphaseProxy=function(){return k(wr(this.kB),YD)};K.prototype.clearForces=function(){xr(this.kB)};
|
||||
K.prototype.setFlags=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);yr(c,a)};K.prototype.getFlags=function(){return zr(this.kB)};K.prototype.setAnisotropicFriction=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);Ar(d,a,c)};K.prototype.getCollisionShape=function(){return k(Br(this.kB),l)};K.prototype.setContactProcessingThreshold=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Cr(c,a)};
|
||||
K.prototype.setActivationState=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Dr(c,a)};K.prototype.forceActivationState=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Er(c,a)};K.prototype.activate=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);void 0===a?Fr(c):Gr(c,a)};K.prototype.isActive=function(){return!!Hr(this.kB)};K.prototype.isKinematicObject=function(){return!!Ir(this.kB)};K.prototype.isStaticObject=function(){return!!Jr(this.kB)};
|
||||
K.prototype.isStaticOrKinematicObject=function(){return!!Kr(this.kB)};K.prototype.getRestitution=function(){return Lr(this.kB)};K.prototype.getFriction=function(){return Mr(this.kB)};K.prototype.getRollingFriction=function(){return Nr(this.kB)};K.prototype.setRestitution=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Or(c,a)};K.prototype.setFriction=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Pr(c,a)};
|
||||
K.prototype.setRollingFriction=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Qr(c,a)};K.prototype.getWorldTransform=function(){return k(Rr(this.kB),r)};K.prototype.getCollisionFlags=function(){return Sr(this.kB)};K.prototype.setCollisionFlags=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Tr(c,a)};K.prototype.setWorldTransform=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Ur(c,a)};
|
||||
K.prototype.setCollisionShape=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Vr(c,a)};K.prototype.setCcdMotionThreshold=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Wr(c,a)};K.prototype.setCcdSweptSphereRadius=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Xr(c,a)};K.prototype.getUserIndex=function(){return Yr(this.kB)};K.prototype.setUserIndex=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Zr(c,a)};
|
||||
K.prototype.getUserPointer=function(){return k($r(this.kB),XD)};K.prototype.setUserPointer=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);as(c,a)};K.prototype.getBroadphaseHandle=function(){return k(bs(this.kB),YD)};K.prototype.__destroy__=function(){cs(this.kB)};function L(){this.kB=ds();h(L)[this.kB]=this}L.prototype=Object.create(f.prototype);L.prototype.constructor=L;L.prototype.lB=L;L.mB={};b.btConstraintSetting=L;L.prototype.get_m_tau=L.prototype.sE=function(){return es(this.kB)};
|
||||
L.prototype.set_m_tau=L.prototype.kH=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);gs(c,a)};Object.defineProperty(L.prototype,"m_tau",{get:L.prototype.sE,set:L.prototype.kH});L.prototype.get_m_damping=L.prototype.aD=function(){return hs(this.kB)};L.prototype.set_m_damping=L.prototype.SF=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);is(c,a)};Object.defineProperty(L.prototype,"m_damping",{get:L.prototype.aD,set:L.prototype.SF});
|
||||
L.prototype.get_m_impulseClamp=L.prototype.wD=function(){return js(this.kB)};L.prototype.set_m_impulseClamp=L.prototype.nG=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);ks(c,a)};Object.defineProperty(L.prototype,"m_impulseClamp",{get:L.prototype.wD,set:L.prototype.nG});L.prototype.__destroy__=function(){ls(this.kB)};
|
||||
function pF(a,c,d,e){a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);e&&"object"===typeof e&&(e=e.kB);this.kB=void 0===d?ms(a,c):void 0===e?_emscripten_bind_btPoint2PointConstraint_btPoint2PointConstraint_3(a,c,d):ns(a,c,d,e);h(pF)[this.kB]=this}pF.prototype=Object.create(aE.prototype);pF.prototype.constructor=pF;pF.prototype.lB=pF;pF.mB={};b.btPoint2PointConstraint=pF;
|
||||
pF.prototype.setPivotA=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);ps(c,a)};pF.prototype.setPivotB=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);qs(c,a)};pF.prototype.getPivotInA=function(){return k(rs(this.kB),m)};pF.prototype.getPivotInB=function(){return k(ss(this.kB),m)};pF.prototype.enableFeedback=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);ts(c,a)};pF.prototype.getBreakingImpulseThreshold=function(){return us(this.kB)};
|
||||
pF.prototype.setBreakingImpulseThreshold=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);vs(c,a)};pF.prototype.getParam=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);return xs(d,a,c)};pF.prototype.setParam=function(a,c,d){var e=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);ys(e,a,c,d)};pF.prototype.get_m_setting=pF.prototype.fE=function(){return k(zs(this.kB),L)};
|
||||
pF.prototype.set_m_setting=pF.prototype.YG=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);As(c,a)};Object.defineProperty(pF.prototype,"m_setting",{get:pF.prototype.fE,set:pF.prototype.YG});pF.prototype.__destroy__=function(){Bs(this.kB)};
|
||||
function qF(a,c,d,e,g){a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);e&&"object"===typeof e&&(e=e.kB);g&&"object"===typeof g&&(g=g.kB);this.kB=void 0===e?Cs(a,c,d):void 0===g?_emscripten_bind_btGeneric6DofSpringConstraint_btGeneric6DofSpringConstraint_4(a,c,d,e):Ds(a,c,d,e,g);h(qF)[this.kB]=this}qF.prototype=Object.create(pE.prototype);qF.prototype.constructor=qF;qF.prototype.lB=qF;qF.mB={};b.btGeneric6DofSpringConstraint=qF;
|
||||
qF.prototype.enableSpring=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);Es(d,a,c)};qF.prototype.setStiffness=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);Fs(d,a,c)};qF.prototype.setDamping=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);Gs(d,a,c)};
|
||||
qF.prototype.setEquilibriumPoint=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);void 0===a?Hs(d):void 0===c?Is(d,a):Js(d,a,c)};qF.prototype.setLinearLowerLimit=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Ks(c,a)};qF.prototype.setLinearUpperLimit=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Ls(c,a)};qF.prototype.setAngularLowerLimit=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Ms(c,a)};
|
||||
qF.prototype.setAngularUpperLimit=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Ns(c,a)};qF.prototype.getFrameOffsetA=function(){return k(Os(this.kB),r)};qF.prototype.enableFeedback=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Ps(c,a)};qF.prototype.getBreakingImpulseThreshold=function(){return Qs(this.kB)};qF.prototype.setBreakingImpulseThreshold=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Rs(c,a)};
|
||||
qF.prototype.getParam=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);return Ss(d,a,c)};qF.prototype.setParam=function(a,c,d){var e=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);Ts(e,a,c,d)};qF.prototype.__destroy__=function(){Us(this.kB)};function rF(){this.kB=Vs();h(rF)[this.kB]=this}rF.prototype=Object.create(f.prototype);rF.prototype.constructor=rF;rF.prototype.lB=rF;rF.mB={};
|
||||
b.btSequentialImpulseConstraintSolver=rF;rF.prototype.__destroy__=function(){Ws(this.kB)};function sF(a,c,d,e){a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);e&&"object"===typeof e&&(e=e.kB);this.kB=void 0===d?Xs(a,c):void 0===e?_emscripten_bind_btConeTwistConstraint_btConeTwistConstraint_3(a,c,d):Ys(a,c,d,e);h(sF)[this.kB]=this}sF.prototype=Object.create(aE.prototype);sF.prototype.constructor=sF;sF.prototype.lB=sF;sF.mB={};
|
||||
b.btConeTwistConstraint=sF;sF.prototype.setLimit=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);Zs(d,a,c)};sF.prototype.setAngularOnly=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);$s(c,a)};sF.prototype.setDamping=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);at(c,a)};sF.prototype.enableMotor=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);bt(c,a)};
|
||||
sF.prototype.setMaxMotorImpulse=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);ct(c,a)};sF.prototype.setMaxMotorImpulseNormalized=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);dt(c,a)};sF.prototype.setMotorTarget=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);et(c,a)};sF.prototype.setMotorTargetInConstraintSpace=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);ft(c,a)};
|
||||
sF.prototype.enableFeedback=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);gt(c,a)};sF.prototype.getBreakingImpulseThreshold=function(){return ht(this.kB)};sF.prototype.setBreakingImpulseThreshold=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);it(c,a)};sF.prototype.getParam=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);return jt(d,a,c)};
|
||||
sF.prototype.setParam=function(a,c,d){var e=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);kt(e,a,c,d)};sF.prototype.__destroy__=function(){lt(this.kB)};
|
||||
function tF(a,c,d,e,g,n,z){a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);e&&"object"===typeof e&&(e=e.kB);g&&"object"===typeof g&&(g=g.kB);n&&"object"===typeof n&&(n=n.kB);z&&"object"===typeof z&&(z=z.kB);this.kB=void 0===d?mt(a,c):void 0===e?nt(a,c,d):void 0===g?ot(a,c,d,e):void 0===n?pt(a,c,d,e,g):void 0===z?qt(a,c,d,e,g,n):rt(a,c,d,e,g,n,z);h(tF)[this.kB]=this}tF.prototype=Object.create(aE.prototype);tF.prototype.constructor=tF;
|
||||
tF.prototype.lB=tF;tF.mB={};b.btHingeConstraint=tF;tF.prototype.getHingeAngle=function(){return st(this.kB)};tF.prototype.setLimit=function(a,c,d,e,g){var n=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);e&&"object"===typeof e&&(e=e.kB);g&&"object"===typeof g&&(g=g.kB);void 0===g?tt(n,a,c,d,e):ut(n,a,c,d,e,g)};
|
||||
tF.prototype.enableAngularMotor=function(a,c,d){var e=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);vt(e,a,c,d)};tF.prototype.setAngularOnly=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);wt(c,a)};tF.prototype.enableMotor=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);xt(c,a)};tF.prototype.setMaxMotorImpulse=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);yt(c,a)};
|
||||
tF.prototype.setMotorTarget=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);zt(d,a,c)};tF.prototype.enableFeedback=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);At(c,a)};tF.prototype.getBreakingImpulseThreshold=function(){return Bt(this.kB)};tF.prototype.setBreakingImpulseThreshold=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Ct(c,a)};
|
||||
tF.prototype.getParam=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);return Dt(d,a,c)};tF.prototype.setParam=function(a,c,d){var e=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);Et(e,a,c,d)};tF.prototype.__destroy__=function(){Ft(this.kB)};
|
||||
function uF(a,c,d,e,g){a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);e&&"object"===typeof e&&(e=e.kB);g&&"object"===typeof g&&(g=g.kB);this.kB=void 0===e?Gt(a,c,d):void 0===g?_emscripten_bind_btSliderConstraint_btSliderConstraint_4(a,c,d,e):Ht(a,c,d,e,g);h(uF)[this.kB]=this}uF.prototype=Object.create(aE.prototype);uF.prototype.constructor=uF;uF.prototype.lB=uF;uF.mB={};b.btSliderConstraint=uF;uF.prototype.getLinearPos=function(){return It(this.kB)};
|
||||
uF.prototype.getAngularPos=function(){return Jt(this.kB)};uF.prototype.setLowerLinLimit=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Kt(c,a)};uF.prototype.setUpperLinLimit=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Lt(c,a)};uF.prototype.setLowerAngLimit=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Mt(c,a)};uF.prototype.setUpperAngLimit=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Nt(c,a)};
|
||||
uF.prototype.setPoweredLinMotor=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Ot(c,a)};uF.prototype.setMaxLinMotorForce=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Pt(c,a)};uF.prototype.setTargetLinMotorVelocity=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Qt(c,a)};uF.prototype.enableFeedback=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Rt(c,a)};uF.prototype.getBreakingImpulseThreshold=function(){return St(this.kB)};
|
||||
uF.prototype.setBreakingImpulseThreshold=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Tt(c,a)};uF.prototype.getParam=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);return Ut(d,a,c)};uF.prototype.setParam=function(a,c,d){var e=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);Vt(e,a,c,d)};uF.prototype.__destroy__=function(){Wt(this.kB)};
|
||||
function vF(a,c,d,e){a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);e&&"object"===typeof e&&(e=e.kB);this.kB=Xt(a,c,d,e);h(vF)[this.kB]=this}vF.prototype=Object.create(aE.prototype);vF.prototype.constructor=vF;vF.prototype.lB=vF;vF.mB={};b.btFixedConstraint=vF;vF.prototype.enableFeedback=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Yt(c,a)};vF.prototype.getBreakingImpulseThreshold=function(){return Zt(this.kB)};
|
||||
vF.prototype.setBreakingImpulseThreshold=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);$t(c,a)};vF.prototype.getParam=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);return au(d,a,c)};vF.prototype.setParam=function(a,c,d){var e=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);bu(e,a,c,d)};vF.prototype.__destroy__=function(){cu(this.kB)};
|
||||
function wF(){throw"cannot construct a btConstraintSolver, no constructor in IDL";}wF.prototype=Object.create(f.prototype);wF.prototype.constructor=wF;wF.prototype.lB=wF;wF.mB={};b.btConstraintSolver=wF;wF.prototype.__destroy__=function(){du(this.kB)};function p(){throw"cannot construct a btDispatcherInfo, no constructor in IDL";}p.prototype=Object.create(f.prototype);p.prototype.constructor=p;p.prototype.lB=p;p.mB={};b.btDispatcherInfo=p;p.prototype.get_m_timeStep=p.prototype.uE=function(){return eu(this.kB)};
|
||||
p.prototype.set_m_timeStep=p.prototype.mH=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);fu(c,a)};Object.defineProperty(p.prototype,"m_timeStep",{get:p.prototype.uE,set:p.prototype.mH});p.prototype.get_m_stepCount=p.prototype.lE=function(){return gu(this.kB)};p.prototype.set_m_stepCount=p.prototype.dH=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);hu(c,a)};Object.defineProperty(p.prototype,"m_stepCount",{get:p.prototype.lE,set:p.prototype.dH});
|
||||
p.prototype.get_m_dispatchFunc=p.prototype.cD=function(){return iu(this.kB)};p.prototype.set_m_dispatchFunc=p.prototype.UF=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);ju(c,a)};Object.defineProperty(p.prototype,"m_dispatchFunc",{get:p.prototype.cD,set:p.prototype.UF});p.prototype.get_m_timeOfImpact=p.prototype.tE=function(){return ku(this.kB)};p.prototype.set_m_timeOfImpact=p.prototype.lH=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);lu(c,a)};
|
||||
Object.defineProperty(p.prototype,"m_timeOfImpact",{get:p.prototype.tE,set:p.prototype.lH});p.prototype.get_m_useContinuous=p.prototype.wE=function(){return!!mu(this.kB)};p.prototype.set_m_useContinuous=p.prototype.oH=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);nu(c,a)};Object.defineProperty(p.prototype,"m_useContinuous",{get:p.prototype.wE,set:p.prototype.oH});p.prototype.get_m_enableSatConvex=p.prototype.hD=function(){return!!ou(this.kB)};
|
||||
p.prototype.set_m_enableSatConvex=p.prototype.ZF=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);pu(c,a)};Object.defineProperty(p.prototype,"m_enableSatConvex",{get:p.prototype.hD,set:p.prototype.ZF});p.prototype.get_m_enableSPU=p.prototype.gD=function(){return!!qu(this.kB)};p.prototype.set_m_enableSPU=p.prototype.YF=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);ru(c,a)};Object.defineProperty(p.prototype,"m_enableSPU",{get:p.prototype.gD,set:p.prototype.YF});
|
||||
p.prototype.get_m_useEpa=p.prototype.yE=function(){return!!su(this.kB)};p.prototype.set_m_useEpa=p.prototype.qH=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);tu(c,a)};Object.defineProperty(p.prototype,"m_useEpa",{get:p.prototype.yE,set:p.prototype.qH});p.prototype.get_m_allowedCcdPenetration=p.prototype.FC=function(){return uu(this.kB)};p.prototype.set_m_allowedCcdPenetration=p.prototype.wF=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);vu(c,a)};
|
||||
Object.defineProperty(p.prototype,"m_allowedCcdPenetration",{get:p.prototype.FC,set:p.prototype.wF});p.prototype.get_m_useConvexConservativeDistanceUtil=p.prototype.xE=function(){return!!wu(this.kB)};p.prototype.set_m_useConvexConservativeDistanceUtil=p.prototype.pH=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);xu(c,a)};Object.defineProperty(p.prototype,"m_useConvexConservativeDistanceUtil",{get:p.prototype.xE,set:p.prototype.pH});
|
||||
p.prototype.get_m_convexConservativeDistanceThreshold=p.prototype.YC=function(){return yu(this.kB)};p.prototype.set_m_convexConservativeDistanceThreshold=p.prototype.PF=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);zu(c,a)};Object.defineProperty(p.prototype,"m_convexConservativeDistanceThreshold",{get:p.prototype.YC,set:p.prototype.PF});p.prototype.__destroy__=function(){Au(this.kB)};function t(){throw"cannot construct a btContactSolverInfo, no constructor in IDL";}t.prototype=Object.create(f.prototype);
|
||||
t.prototype.constructor=t;t.prototype.lB=t;t.mB={};b.btContactSolverInfo=t;t.prototype.get_m_splitImpulse=t.prototype.iE=function(){return!!Bu(this.kB)};t.prototype.set_m_splitImpulse=t.prototype.aH=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Cu(c,a)};Object.defineProperty(t.prototype,"m_splitImpulse",{get:t.prototype.iE,set:t.prototype.aH});t.prototype.get_m_splitImpulsePenetrationThreshold=t.prototype.jE=function(){return Du(this.kB)};
|
||||
t.prototype.set_m_splitImpulsePenetrationThreshold=t.prototype.bH=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Eu(c,a)};Object.defineProperty(t.prototype,"m_splitImpulsePenetrationThreshold",{get:t.prototype.jE,set:t.prototype.bH});t.prototype.get_m_numIterations=t.prototype.TD=function(){return Fu(this.kB)};t.prototype.set_m_numIterations=t.prototype.KG=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Gu(c,a)};
|
||||
Object.defineProperty(t.prototype,"m_numIterations",{get:t.prototype.TD,set:t.prototype.KG});t.prototype.__destroy__=function(){Hu(this.kB)};function M(){this.kB=Iu();h(M)[this.kB]=this}M.prototype=Object.create(f.prototype);M.prototype.constructor=M;M.prototype.lB=M;M.mB={};b.btVehicleTuning=M;M.prototype.get_m_suspensionStiffness=M.prototype.BB=function(){return Ju(this.kB)};M.prototype.set_m_suspensionStiffness=M.prototype.IB=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Ku(c,a)};
|
||||
Object.defineProperty(M.prototype,"m_suspensionStiffness",{get:M.prototype.BB,set:M.prototype.IB});M.prototype.get_m_suspensionCompression=M.prototype.mE=function(){return Lu(this.kB)};M.prototype.set_m_suspensionCompression=M.prototype.eH=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Mu(c,a)};Object.defineProperty(M.prototype,"m_suspensionCompression",{get:M.prototype.mE,set:M.prototype.eH});M.prototype.get_m_suspensionDamping=M.prototype.nE=function(){return Nu(this.kB)};
|
||||
M.prototype.set_m_suspensionDamping=M.prototype.fH=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Ou(c,a)};Object.defineProperty(M.prototype,"m_suspensionDamping",{get:M.prototype.nE,set:M.prototype.fH});M.prototype.get_m_maxSuspensionTravelCm=M.prototype.AB=function(){return Pu(this.kB)};M.prototype.set_m_maxSuspensionTravelCm=M.prototype.HB=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Qu(c,a)};
|
||||
Object.defineProperty(M.prototype,"m_maxSuspensionTravelCm",{get:M.prototype.AB,set:M.prototype.HB});M.prototype.get_m_frictionSlip=M.prototype.wB=function(){return Ru(this.kB)};M.prototype.set_m_frictionSlip=M.prototype.DB=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Su(c,a)};Object.defineProperty(M.prototype,"m_frictionSlip",{get:M.prototype.wB,set:M.prototype.DB});M.prototype.get_m_maxSuspensionForce=M.prototype.zB=function(){return Tu(this.kB)};
|
||||
M.prototype.set_m_maxSuspensionForce=M.prototype.GB=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Uu(c,a)};Object.defineProperty(M.prototype,"m_maxSuspensionForce",{get:M.prototype.zB,set:M.prototype.GB});function xF(){throw"cannot construct a btVehicleRaycasterResult, no constructor in IDL";}xF.prototype=Object.create(f.prototype);xF.prototype.constructor=xF;xF.prototype.lB=xF;xF.mB={};b.btVehicleRaycasterResult=xF;
|
||||
xF.prototype.get_m_hitPointInWorld=xF.prototype.tD=function(){return k(Vu(this.kB),m)};xF.prototype.set_m_hitPointInWorld=xF.prototype.kG=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Wu(c,a)};Object.defineProperty(xF.prototype,"m_hitPointInWorld",{get:xF.prototype.tD,set:xF.prototype.kG});xF.prototype.get_m_hitNormalInWorld=xF.prototype.rD=function(){return k(Xu(this.kB),m)};
|
||||
xF.prototype.set_m_hitNormalInWorld=xF.prototype.iG=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Yu(c,a)};Object.defineProperty(xF.prototype,"m_hitNormalInWorld",{get:xF.prototype.rD,set:xF.prototype.iG});xF.prototype.get_m_distFraction=xF.prototype.fD=function(){return Zu(this.kB)};xF.prototype.set_m_distFraction=xF.prototype.XF=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);$u(c,a)};Object.defineProperty(xF.prototype,"m_distFraction",{get:xF.prototype.fD,set:xF.prototype.XF});
|
||||
xF.prototype.__destroy__=function(){av(this.kB)};function yF(a){a&&"object"===typeof a&&(a=a.kB);this.kB=bv(a);h(yF)[this.kB]=this}yF.prototype=Object.create(qE.prototype);yF.prototype.constructor=yF;yF.prototype.lB=yF;yF.mB={};b.btDefaultVehicleRaycaster=yF;yF.prototype.castRay=function(a,c,d){var e=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);cv(e,a,c,d)};yF.prototype.__destroy__=function(){dv(this.kB)};
|
||||
function N(){throw"cannot construct a RaycastInfo, no constructor in IDL";}N.prototype=Object.create(f.prototype);N.prototype.constructor=N;N.prototype.lB=N;N.mB={};b.RaycastInfo=N;N.prototype.get_m_contactNormalWS=N.prototype.WC=function(){return k(ev(this.kB),m)};N.prototype.set_m_contactNormalWS=N.prototype.NF=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);fv(c,a)};Object.defineProperty(N.prototype,"m_contactNormalWS",{get:N.prototype.WC,set:N.prototype.NF});
|
||||
N.prototype.get_m_contactPointWS=N.prototype.XC=function(){return k(gv(this.kB),m)};N.prototype.set_m_contactPointWS=N.prototype.OF=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);hv(c,a)};Object.defineProperty(N.prototype,"m_contactPointWS",{get:N.prototype.XC,set:N.prototype.OF});N.prototype.get_m_suspensionLength=N.prototype.oE=function(){return iv(this.kB)};N.prototype.set_m_suspensionLength=N.prototype.gH=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);jv(c,a)};
|
||||
Object.defineProperty(N.prototype,"m_suspensionLength",{get:N.prototype.oE,set:N.prototype.gH});N.prototype.get_m_hardPointWS=N.prototype.oD=function(){return k(kv(this.kB),m)};N.prototype.set_m_hardPointWS=N.prototype.fG=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);lv(c,a)};Object.defineProperty(N.prototype,"m_hardPointWS",{get:N.prototype.oD,set:N.prototype.fG});N.prototype.get_m_wheelDirectionWS=N.prototype.DE=function(){return k(mv(this.kB),m)};
|
||||
N.prototype.set_m_wheelDirectionWS=N.prototype.vH=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);nv(c,a)};Object.defineProperty(N.prototype,"m_wheelDirectionWS",{get:N.prototype.DE,set:N.prototype.vH});N.prototype.get_m_wheelAxleWS=N.prototype.CE=function(){return k(ov(this.kB),m)};N.prototype.set_m_wheelAxleWS=N.prototype.uH=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);pv(c,a)};Object.defineProperty(N.prototype,"m_wheelAxleWS",{get:N.prototype.CE,set:N.prototype.uH});
|
||||
N.prototype.get_m_isInContact=N.prototype.zD=function(){return!!qv(this.kB)};N.prototype.set_m_isInContact=N.prototype.qG=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);rv(c,a)};Object.defineProperty(N.prototype,"m_isInContact",{get:N.prototype.zD,set:N.prototype.qG});N.prototype.get_m_groundObject=N.prototype.nD=function(){return sv(this.kB)};N.prototype.set_m_groundObject=N.prototype.eG=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);tv(c,a)};
|
||||
Object.defineProperty(N.prototype,"m_groundObject",{get:N.prototype.nD,set:N.prototype.eG});N.prototype.__destroy__=function(){uv(this.kB)};function O(){throw"cannot construct a btWheelInfoConstructionInfo, no constructor in IDL";}O.prototype=Object.create(f.prototype);O.prototype.constructor=O;O.prototype.lB=O;O.mB={};b.btWheelInfoConstructionInfo=O;O.prototype.get_m_chassisConnectionCS=O.prototype.RC=function(){return k(vv(this.kB),m)};
|
||||
O.prototype.set_m_chassisConnectionCS=O.prototype.IF=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);wv(c,a)};Object.defineProperty(O.prototype,"m_chassisConnectionCS",{get:O.prototype.RC,set:O.prototype.IF});O.prototype.get_m_wheelDirectionCS=O.prototype.QB=function(){return k(xv(this.kB),m)};O.prototype.set_m_wheelDirectionCS=O.prototype.$B=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);yv(c,a)};Object.defineProperty(O.prototype,"m_wheelDirectionCS",{get:O.prototype.QB,set:O.prototype.$B});
|
||||
O.prototype.get_m_wheelAxleCS=O.prototype.PB=function(){return k(zv(this.kB),m)};O.prototype.set_m_wheelAxleCS=O.prototype.ZB=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Av(c,a)};Object.defineProperty(O.prototype,"m_wheelAxleCS",{get:O.prototype.PB,set:O.prototype.ZB});O.prototype.get_m_suspensionRestLength=O.prototype.qE=function(){return Bv(this.kB)};O.prototype.set_m_suspensionRestLength=O.prototype.iH=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Cv(c,a)};
|
||||
Object.defineProperty(O.prototype,"m_suspensionRestLength",{get:O.prototype.qE,set:O.prototype.iH});O.prototype.get_m_maxSuspensionTravelCm=O.prototype.AB=function(){return Dv(this.kB)};O.prototype.set_m_maxSuspensionTravelCm=O.prototype.HB=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Ev(c,a)};Object.defineProperty(O.prototype,"m_maxSuspensionTravelCm",{get:O.prototype.AB,set:O.prototype.HB});O.prototype.get_m_wheelRadius=O.prototype.EE=function(){return Fv(this.kB)};
|
||||
O.prototype.set_m_wheelRadius=O.prototype.wH=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Gv(c,a)};Object.defineProperty(O.prototype,"m_wheelRadius",{get:O.prototype.EE,set:O.prototype.wH});O.prototype.get_m_suspensionStiffness=O.prototype.BB=function(){return Hv(this.kB)};O.prototype.set_m_suspensionStiffness=O.prototype.IB=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Iv(c,a)};Object.defineProperty(O.prototype,"m_suspensionStiffness",{get:O.prototype.BB,set:O.prototype.IB});
|
||||
O.prototype.get_m_wheelsDampingCompression=O.prototype.RB=function(){return Jv(this.kB)};O.prototype.set_m_wheelsDampingCompression=O.prototype.aC=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Kv(c,a)};Object.defineProperty(O.prototype,"m_wheelsDampingCompression",{get:O.prototype.RB,set:O.prototype.aC});O.prototype.get_m_wheelsDampingRelaxation=O.prototype.SB=function(){return Lv(this.kB)};
|
||||
O.prototype.set_m_wheelsDampingRelaxation=O.prototype.bC=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Mv(c,a)};Object.defineProperty(O.prototype,"m_wheelsDampingRelaxation",{get:O.prototype.SB,set:O.prototype.bC});O.prototype.get_m_frictionSlip=O.prototype.wB=function(){return Nv(this.kB)};O.prototype.set_m_frictionSlip=O.prototype.DB=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Ov(c,a)};Object.defineProperty(O.prototype,"m_frictionSlip",{get:O.prototype.wB,set:O.prototype.DB});
|
||||
O.prototype.get_m_maxSuspensionForce=O.prototype.zB=function(){return Pv(this.kB)};O.prototype.set_m_maxSuspensionForce=O.prototype.GB=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Qv(c,a)};Object.defineProperty(O.prototype,"m_maxSuspensionForce",{get:O.prototype.zB,set:O.prototype.GB});O.prototype.get_m_bIsFrontWheel=O.prototype.JB=function(){return!!Rv(this.kB)};O.prototype.set_m_bIsFrontWheel=O.prototype.TB=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Sv(c,a)};
|
||||
Object.defineProperty(O.prototype,"m_bIsFrontWheel",{get:O.prototype.JB,set:O.prototype.TB});O.prototype.__destroy__=function(){Tv(this.kB)};function P(a){a&&"object"===typeof a&&(a=a.kB);this.kB=Uv(a);h(P)[this.kB]=this}P.prototype=Object.create(f.prototype);P.prototype.constructor=P;P.prototype.lB=P;P.mB={};b.btWheelInfo=P;P.prototype.getSuspensionRestLength=function(){return Vv(this.kB)};
|
||||
P.prototype.updateWheel=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);Wv(d,a,c)};P.prototype.get_m_suspensionStiffness=P.prototype.BB=function(){return Xv(this.kB)};P.prototype.set_m_suspensionStiffness=P.prototype.IB=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Yv(c,a)};Object.defineProperty(P.prototype,"m_suspensionStiffness",{get:P.prototype.BB,set:P.prototype.IB});P.prototype.get_m_frictionSlip=P.prototype.wB=function(){return Zv(this.kB)};
|
||||
P.prototype.set_m_frictionSlip=P.prototype.DB=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);$v(c,a)};Object.defineProperty(P.prototype,"m_frictionSlip",{get:P.prototype.wB,set:P.prototype.DB});P.prototype.get_m_engineForce=P.prototype.iD=function(){return aw(this.kB)};P.prototype.set_m_engineForce=P.prototype.$F=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);bw(c,a)};Object.defineProperty(P.prototype,"m_engineForce",{get:P.prototype.iD,set:P.prototype.$F});
|
||||
P.prototype.get_m_rollInfluence=P.prototype.cE=function(){return cw(this.kB)};P.prototype.set_m_rollInfluence=P.prototype.VG=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);dw(c,a)};Object.defineProperty(P.prototype,"m_rollInfluence",{get:P.prototype.cE,set:P.prototype.VG});P.prototype.get_m_suspensionRestLength1=P.prototype.rE=function(){return ew(this.kB)};P.prototype.set_m_suspensionRestLength1=P.prototype.jH=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);fw(c,a)};
|
||||
Object.defineProperty(P.prototype,"m_suspensionRestLength1",{get:P.prototype.rE,set:P.prototype.jH});P.prototype.get_m_wheelsRadius=P.prototype.FE=function(){return gw(this.kB)};P.prototype.set_m_wheelsRadius=P.prototype.xH=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);hw(c,a)};Object.defineProperty(P.prototype,"m_wheelsRadius",{get:P.prototype.FE,set:P.prototype.xH});P.prototype.get_m_wheelsDampingCompression=P.prototype.RB=function(){return iw(this.kB)};
|
||||
P.prototype.set_m_wheelsDampingCompression=P.prototype.aC=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);jw(c,a)};Object.defineProperty(P.prototype,"m_wheelsDampingCompression",{get:P.prototype.RB,set:P.prototype.aC});P.prototype.get_m_wheelsDampingRelaxation=P.prototype.SB=function(){return kw(this.kB)};P.prototype.set_m_wheelsDampingRelaxation=P.prototype.bC=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);lw(c,a)};
|
||||
Object.defineProperty(P.prototype,"m_wheelsDampingRelaxation",{get:P.prototype.SB,set:P.prototype.bC});P.prototype.get_m_steering=P.prototype.kE=function(){return mw(this.kB)};P.prototype.set_m_steering=P.prototype.cH=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);nw(c,a)};Object.defineProperty(P.prototype,"m_steering",{get:P.prototype.kE,set:P.prototype.cH});P.prototype.get_m_maxSuspensionForce=P.prototype.zB=function(){return ow(this.kB)};
|
||||
P.prototype.set_m_maxSuspensionForce=P.prototype.GB=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);pw(c,a)};Object.defineProperty(P.prototype,"m_maxSuspensionForce",{get:P.prototype.zB,set:P.prototype.GB});P.prototype.get_m_maxSuspensionTravelCm=P.prototype.AB=function(){return qw(this.kB)};P.prototype.set_m_maxSuspensionTravelCm=P.prototype.HB=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);rw(c,a)};
|
||||
Object.defineProperty(P.prototype,"m_maxSuspensionTravelCm",{get:P.prototype.AB,set:P.prototype.HB});P.prototype.get_m_wheelsSuspensionForce=P.prototype.GE=function(){return sw(this.kB)};P.prototype.set_m_wheelsSuspensionForce=P.prototype.yH=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);tw(c,a)};Object.defineProperty(P.prototype,"m_wheelsSuspensionForce",{get:P.prototype.GE,set:P.prototype.yH});P.prototype.get_m_bIsFrontWheel=P.prototype.JB=function(){return!!uw(this.kB)};
|
||||
P.prototype.set_m_bIsFrontWheel=P.prototype.TB=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);vw(c,a)};Object.defineProperty(P.prototype,"m_bIsFrontWheel",{get:P.prototype.JB,set:P.prototype.TB});P.prototype.get_m_raycastInfo=P.prototype.aE=function(){return k(ww(this.kB),N)};P.prototype.set_m_raycastInfo=P.prototype.TG=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);xw(c,a)};Object.defineProperty(P.prototype,"m_raycastInfo",{get:P.prototype.aE,set:P.prototype.TG});
|
||||
P.prototype.get_m_chassisConnectionPointCS=P.prototype.SC=function(){return k(yw(this.kB),m)};P.prototype.set_m_chassisConnectionPointCS=P.prototype.JF=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);zw(c,a)};Object.defineProperty(P.prototype,"m_chassisConnectionPointCS",{get:P.prototype.SC,set:P.prototype.JF});P.prototype.get_m_worldTransform=P.prototype.HE=function(){return k(Aw(this.kB),r)};
|
||||
P.prototype.set_m_worldTransform=P.prototype.zH=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Bw(c,a)};Object.defineProperty(P.prototype,"m_worldTransform",{get:P.prototype.HE,set:P.prototype.zH});P.prototype.get_m_wheelDirectionCS=P.prototype.QB=function(){return k(Cw(this.kB),m)};P.prototype.set_m_wheelDirectionCS=P.prototype.$B=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Dw(c,a)};Object.defineProperty(P.prototype,"m_wheelDirectionCS",{get:P.prototype.QB,set:P.prototype.$B});
|
||||
P.prototype.get_m_wheelAxleCS=P.prototype.PB=function(){return k(Ew(this.kB),m)};P.prototype.set_m_wheelAxleCS=P.prototype.ZB=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Fw(c,a)};Object.defineProperty(P.prototype,"m_wheelAxleCS",{get:P.prototype.PB,set:P.prototype.ZB});P.prototype.get_m_rotation=P.prototype.eE=function(){return Gw(this.kB)};P.prototype.set_m_rotation=P.prototype.XG=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Hw(c,a)};
|
||||
Object.defineProperty(P.prototype,"m_rotation",{get:P.prototype.eE,set:P.prototype.XG});P.prototype.get_m_deltaRotation=P.prototype.bD=function(){return Iw(this.kB)};P.prototype.set_m_deltaRotation=P.prototype.TF=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Jw(c,a)};Object.defineProperty(P.prototype,"m_deltaRotation",{get:P.prototype.bD,set:P.prototype.TF});P.prototype.get_m_brake=P.prototype.LC=function(){return Kw(this.kB)};
|
||||
P.prototype.set_m_brake=P.prototype.CF=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Lw(c,a)};Object.defineProperty(P.prototype,"m_brake",{get:P.prototype.LC,set:P.prototype.CF});P.prototype.get_m_clippedInvContactDotSuspension=P.prototype.TC=function(){return Mw(this.kB)};P.prototype.set_m_clippedInvContactDotSuspension=P.prototype.KF=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Nw(c,a)};
|
||||
Object.defineProperty(P.prototype,"m_clippedInvContactDotSuspension",{get:P.prototype.TC,set:P.prototype.KF});P.prototype.get_m_suspensionRelativeVelocity=P.prototype.pE=function(){return Ow(this.kB)};P.prototype.set_m_suspensionRelativeVelocity=P.prototype.hH=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Pw(c,a)};Object.defineProperty(P.prototype,"m_suspensionRelativeVelocity",{get:P.prototype.pE,set:P.prototype.hH});P.prototype.get_m_skidInfo=P.prototype.hE=function(){return Qw(this.kB)};
|
||||
P.prototype.set_m_skidInfo=P.prototype.$G=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Rw(c,a)};Object.defineProperty(P.prototype,"m_skidInfo",{get:P.prototype.hE,set:P.prototype.$G});P.prototype.__destroy__=function(){Sw(this.kB)};function zF(a,c,d,e){a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);e&&"object"===typeof e&&(e=e.kB);this.kB=void 0===e?Tw(a,c,d):Uw(a,c,d,e);h(zF)[this.kB]=this}zF.prototype=Object.create(rE.prototype);
|
||||
zF.prototype.constructor=zF;zF.prototype.lB=zF;zF.mB={};b.btKinematicCharacterController=zF;zF.prototype.setUpAxis=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Vw(c,a)};zF.prototype.setWalkDirection=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Ww(c,a)};zF.prototype.setVelocityForTimeInterval=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);Xw(d,a,c)};
|
||||
zF.prototype.warp=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Yw(c,a)};zF.prototype.preStep=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Zw(c,a)};zF.prototype.playerStep=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);$w(d,a,c)};zF.prototype.setFallSpeed=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);ax(c,a)};zF.prototype.setJumpSpeed=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);bx(c,a)};
|
||||
zF.prototype.setMaxJumpHeight=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);cx(c,a)};zF.prototype.canJump=function(){return!!dx(this.kB)};zF.prototype.jump=function(){ex(this.kB)};zF.prototype.setGravity=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);fx(c,a)};zF.prototype.getGravity=function(){return gx(this.kB)};zF.prototype.setMaxSlope=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);hx(c,a)};zF.prototype.getMaxSlope=function(){return ix(this.kB)};
|
||||
zF.prototype.getGhostObject=function(){return k(jx(this.kB),Q)};zF.prototype.setUseGhostSweepTest=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);kx(c,a)};zF.prototype.onGround=function(){return!!lx(this.kB)};zF.prototype.setUpInterpolate=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);mx(c,a)};zF.prototype.updateAction=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);nx(d,a,c)};zF.prototype.__destroy__=function(){ox(this.kB)};
|
||||
function R(a,c,d){a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);this.kB=px(a,c,d);h(R)[this.kB]=this}R.prototype=Object.create(rE.prototype);R.prototype.constructor=R;R.prototype.lB=R;R.mB={};b.btRaycastVehicle=R;R.prototype.applyEngineForce=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);qx(d,a,c)};
|
||||
R.prototype.setSteeringValue=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);rx(d,a,c)};R.prototype.getWheelTransformWS=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);return k(sx(c,a),r)};R.prototype.updateWheelTransform=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);tx(d,a,c)};
|
||||
R.prototype.addWheel=function(a,c,d,e,g,n,z){var T=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);e&&"object"===typeof e&&(e=e.kB);g&&"object"===typeof g&&(g=g.kB);n&&"object"===typeof n&&(n=n.kB);z&&"object"===typeof z&&(z=z.kB);return k(ux(T,a,c,d,e,g,n,z),P)};R.prototype.getNumWheels=function(){return vx(this.kB)};R.prototype.getRigidBody=function(){return k(wx(this.kB),K)};
|
||||
R.prototype.getWheelInfo=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);return k(xx(c,a),P)};R.prototype.setBrake=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);yx(d,a,c)};R.prototype.setCoordinateSystem=function(a,c,d){var e=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);zx(e,a,c,d)};R.prototype.getCurrentSpeedKmHour=function(){return Ax(this.kB)};
|
||||
R.prototype.getChassisWorldTransform=function(){return k(Bx(this.kB),r)};R.prototype.rayCast=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);return Cx(c,a)};R.prototype.updateVehicle=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Dx(c,a)};R.prototype.resetSuspension=function(){Ex(this.kB)};R.prototype.getSteeringValue=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);return Fx(c,a)};
|
||||
R.prototype.updateWheelTransformsWS=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);void 0===c?Gx(d,a):Hx(d,a,c)};R.prototype.setPitchControl=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Ix(c,a)};R.prototype.updateSuspension=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Jx(c,a)};R.prototype.updateFriction=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Kx(c,a)};R.prototype.getRightAxis=function(){return Lx(this.kB)};
|
||||
R.prototype.getUpAxis=function(){return Mx(this.kB)};R.prototype.getForwardAxis=function(){return Nx(this.kB)};R.prototype.getForwardVector=function(){return k(Ox(this.kB),m)};R.prototype.getUserConstraintType=function(){return Px(this.kB)};R.prototype.setUserConstraintType=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Qx(c,a)};R.prototype.setUserConstraintId=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Rx(c,a)};R.prototype.getUserConstraintId=function(){return Sx(this.kB)};
|
||||
R.prototype.updateAction=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);Tx(d,a,c)};R.prototype.__destroy__=function(){Ux(this.kB)};function Q(){this.kB=Vx();h(Q)[this.kB]=this}Q.prototype=Object.create(y.prototype);Q.prototype.constructor=Q;Q.prototype.lB=Q;Q.mB={};b.btPairCachingGhostObject=Q;Q.prototype.setAnisotropicFriction=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);Wx(d,a,c)};
|
||||
Q.prototype.getCollisionShape=function(){return k(Xx(this.kB),l)};Q.prototype.setContactProcessingThreshold=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Yx(c,a)};Q.prototype.setActivationState=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Zx(c,a)};Q.prototype.forceActivationState=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);$x(c,a)};Q.prototype.activate=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);void 0===a?ay(c):by(c,a)};
|
||||
Q.prototype.isActive=function(){return!!cy(this.kB)};Q.prototype.isKinematicObject=function(){return!!dy(this.kB)};Q.prototype.isStaticObject=function(){return!!ey(this.kB)};Q.prototype.isStaticOrKinematicObject=function(){return!!fy(this.kB)};Q.prototype.getRestitution=function(){return gy(this.kB)};Q.prototype.getFriction=function(){return hy(this.kB)};Q.prototype.getRollingFriction=function(){return iy(this.kB)};
|
||||
Q.prototype.setRestitution=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);jy(c,a)};Q.prototype.setFriction=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);ky(c,a)};Q.prototype.setRollingFriction=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);ly(c,a)};Q.prototype.getWorldTransform=function(){return k(my(this.kB),r)};Q.prototype.getCollisionFlags=function(){return ny(this.kB)};
|
||||
Q.prototype.setCollisionFlags=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);oy(c,a)};Q.prototype.setWorldTransform=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);py(c,a)};Q.prototype.setCollisionShape=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);qy(c,a)};Q.prototype.setCcdMotionThreshold=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);ry(c,a)};Q.prototype.setCcdSweptSphereRadius=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);sy(c,a)};
|
||||
Q.prototype.getUserIndex=function(){return ty(this.kB)};Q.prototype.setUserIndex=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);uy(c,a)};Q.prototype.getUserPointer=function(){return k(vy(this.kB),XD)};Q.prototype.setUserPointer=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);wy(c,a)};Q.prototype.getBroadphaseHandle=function(){return k(xy(this.kB),YD)};Q.prototype.getNumOverlappingObjects=function(){return yy(this.kB)};
|
||||
Q.prototype.getOverlappingObject=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);return k(zy(c,a),q)};Q.prototype.__destroy__=function(){Ay(this.kB)};function AF(){this.kB=By();h(AF)[this.kB]=this}AF.prototype=Object.create(f.prototype);AF.prototype.constructor=AF;AF.prototype.lB=AF;AF.mB={};b.btGhostPairCallback=AF;AF.prototype.__destroy__=function(){Cy(this.kB)};function S(){this.kB=Dy();h(S)[this.kB]=this}S.prototype=Object.create(f.prototype);S.prototype.constructor=S;
|
||||
S.prototype.lB=S;S.mB={};b.btSoftBodyWorldInfo=S;S.prototype.get_air_density=S.prototype.cC=function(){return Ey(this.kB)};S.prototype.set_air_density=S.prototype.UE=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Fy(c,a)};Object.defineProperty(S.prototype,"air_density",{get:S.prototype.cC,set:S.prototype.UE});S.prototype.get_water_density=S.prototype.RE=function(){return Gy(this.kB)};
|
||||
S.prototype.set_water_density=S.prototype.JH=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Hy(c,a)};Object.defineProperty(S.prototype,"water_density",{get:S.prototype.RE,set:S.prototype.JH});S.prototype.get_water_offset=S.prototype.TE=function(){return Iy(this.kB)};S.prototype.set_water_offset=S.prototype.LH=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Jy(c,a)};Object.defineProperty(S.prototype,"water_offset",{get:S.prototype.TE,set:S.prototype.LH});
|
||||
S.prototype.get_m_maxDisplacement=S.prototype.ND=function(){return Ky(this.kB)};S.prototype.set_m_maxDisplacement=S.prototype.EG=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Ly(c,a)};Object.defineProperty(S.prototype,"m_maxDisplacement",{get:S.prototype.ND,set:S.prototype.EG});S.prototype.get_water_normal=S.prototype.SE=function(){return k(My(this.kB),m)};S.prototype.set_water_normal=S.prototype.KH=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Ny(c,a)};
|
||||
Object.defineProperty(S.prototype,"water_normal",{get:S.prototype.SE,set:S.prototype.KH});S.prototype.get_m_broadphase=S.prototype.MC=function(){return k(Oy(this.kB),VD)};S.prototype.set_m_broadphase=S.prototype.DF=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Py(c,a)};Object.defineProperty(S.prototype,"m_broadphase",{get:S.prototype.MC,set:S.prototype.DF});S.prototype.get_m_dispatcher=S.prototype.dD=function(){return k(Qy(this.kB),TD)};
|
||||
S.prototype.set_m_dispatcher=S.prototype.VF=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Ry(c,a)};Object.defineProperty(S.prototype,"m_dispatcher",{get:S.prototype.dD,set:S.prototype.VF});S.prototype.get_m_gravity=S.prototype.mD=function(){return k(Sy(this.kB),m)};S.prototype.set_m_gravity=S.prototype.dG=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Ty(c,a)};Object.defineProperty(S.prototype,"m_gravity",{get:S.prototype.mD,set:S.prototype.dG});S.prototype.__destroy__=function(){Uy(this.kB)};
|
||||
function U(){throw"cannot construct a Face, no constructor in IDL";}U.prototype=Object.create(f.prototype);U.prototype.constructor=U;U.prototype.lB=U;U.mB={};b.Face=U;U.prototype.get_m_n=U.prototype.MB=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);return k(Vy(c,a),Node)};U.prototype.set_m_n=U.prototype.WB=function(a,c){var d=this.kB;ND();a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);Wy(d,a,c)};Object.defineProperty(U.prototype,"m_n",{get:U.prototype.MB,set:U.prototype.WB});
|
||||
U.prototype.get_m_normal=U.prototype.RD=function(){return k(Xy(this.kB),m)};U.prototype.set_m_normal=U.prototype.IG=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Yy(c,a)};Object.defineProperty(U.prototype,"m_normal",{get:U.prototype.RD,set:U.prototype.IG});U.prototype.get_m_ra=U.prototype.$D=function(){return Zy(this.kB)};U.prototype.set_m_ra=U.prototype.SG=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);$y(c,a)};
|
||||
Object.defineProperty(U.prototype,"m_ra",{get:U.prototype.$D,set:U.prototype.SG});U.prototype.__destroy__=function(){az(this.kB)};function BF(){throw"cannot construct a tFaceArray, no constructor in IDL";}BF.prototype=Object.create(f.prototype);BF.prototype.constructor=BF;BF.prototype.lB=BF;BF.mB={};b.tFaceArray=BF;BF.prototype.size=BF.prototype.size=function(){return bz(this.kB)};BF.prototype.at=BF.prototype.at=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);return k(cz(c,a),U)};
|
||||
BF.prototype.__destroy__=function(){dz(this.kB)};function Node(){throw"cannot construct a Node, no constructor in IDL";}Node.prototype=Object.create(f.prototype);Node.prototype.constructor=Node;Node.prototype.lB=Node;Node.mB={};b.Node=Node;Node.prototype.get_m_x=Node.prototype.IE=function(){return k(ez(this.kB),m)};Node.prototype.set_m_x=Node.prototype.AH=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);fz(c,a)};Object.defineProperty(Node.prototype,"m_x",{get:Node.prototype.IE,set:Node.prototype.AH});
|
||||
Node.prototype.get_m_q=Node.prototype.ZD=function(){return k(gz(this.kB),m)};Node.prototype.set_m_q=Node.prototype.QG=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);hz(c,a)};Object.defineProperty(Node.prototype,"m_q",{get:Node.prototype.ZD,set:Node.prototype.QG});Node.prototype.get_m_v=Node.prototype.AE=function(){return k(iz(this.kB),m)};Node.prototype.set_m_v=Node.prototype.sH=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);jz(c,a)};
|
||||
Object.defineProperty(Node.prototype,"m_v",{get:Node.prototype.AE,set:Node.prototype.sH});Node.prototype.get_m_f=Node.prototype.jD=function(){return k(kz(this.kB),m)};Node.prototype.set_m_f=Node.prototype.aG=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);lz(c,a)};Object.defineProperty(Node.prototype,"m_f",{get:Node.prototype.jD,set:Node.prototype.aG});Node.prototype.get_m_n=Node.prototype.MB=function(){return k(mz(this.kB),m)};
|
||||
Node.prototype.set_m_n=Node.prototype.WB=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);nz(c,a)};Object.defineProperty(Node.prototype,"m_n",{get:Node.prototype.MB,set:Node.prototype.WB});Node.prototype.get_m_im=Node.prototype.vD=function(){return oz(this.kB)};Node.prototype.set_m_im=Node.prototype.mG=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);pz(c,a)};Object.defineProperty(Node.prototype,"m_im",{get:Node.prototype.vD,set:Node.prototype.mG});
|
||||
Node.prototype.get_m_area=Node.prototype.JC=function(){return qz(this.kB)};Node.prototype.set_m_area=Node.prototype.AF=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);rz(c,a)};Object.defineProperty(Node.prototype,"m_area",{get:Node.prototype.JC,set:Node.prototype.AF});Node.prototype.__destroy__=function(){sz(this.kB)};function CF(){throw"cannot construct a tNodeArray, no constructor in IDL";}CF.prototype=Object.create(f.prototype);CF.prototype.constructor=CF;CF.prototype.lB=CF;CF.mB={};
|
||||
b.tNodeArray=CF;CF.prototype.size=CF.prototype.size=function(){return tz(this.kB)};CF.prototype.at=CF.prototype.at=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);return k(uz(c,a),Node)};CF.prototype.__destroy__=function(){vz(this.kB)};function V(){throw"cannot construct a Material, no constructor in IDL";}V.prototype=Object.create(f.prototype);V.prototype.constructor=V;V.prototype.lB=V;V.mB={};b.Material=V;V.prototype.get_m_kLST=V.prototype.BD=function(){return wz(this.kB)};
|
||||
V.prototype.set_m_kLST=V.prototype.sG=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);xz(c,a)};Object.defineProperty(V.prototype,"m_kLST",{get:V.prototype.BD,set:V.prototype.sG});V.prototype.get_m_kAST=V.prototype.AD=function(){return yz(this.kB)};V.prototype.set_m_kAST=V.prototype.rG=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);zz(c,a)};Object.defineProperty(V.prototype,"m_kAST",{get:V.prototype.AD,set:V.prototype.rG});V.prototype.get_m_kVST=V.prototype.CD=function(){return Az(this.kB)};
|
||||
V.prototype.set_m_kVST=V.prototype.tG=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Bz(c,a)};Object.defineProperty(V.prototype,"m_kVST",{get:V.prototype.CD,set:V.prototype.tG});V.prototype.get_m_flags=V.prototype.tB=function(){return Cz(this.kB)};V.prototype.set_m_flags=V.prototype.uB=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Dz(c,a)};Object.defineProperty(V.prototype,"m_flags",{get:V.prototype.tB,set:V.prototype.uB});V.prototype.__destroy__=function(){Ez(this.kB)};
|
||||
function DF(){throw"cannot construct a tMaterialArray, no constructor in IDL";}DF.prototype=Object.create(f.prototype);DF.prototype.constructor=DF;DF.prototype.lB=DF;DF.mB={};b.tMaterialArray=DF;DF.prototype.size=DF.prototype.size=function(){return Fz(this.kB)};DF.prototype.at=DF.prototype.at=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);return k(Gz(c,a),V)};DF.prototype.__destroy__=function(){Hz(this.kB)};function W(){throw"cannot construct a Anchor, no constructor in IDL";}
|
||||
W.prototype=Object.create(f.prototype);W.prototype.constructor=W;W.prototype.lB=W;W.mB={};b.Anchor=W;W.prototype.get_m_node=W.prototype.PD=function(){return k(Iz(this.kB),Node)};W.prototype.set_m_node=W.prototype.GG=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Jz(c,a)};Object.defineProperty(W.prototype,"m_node",{get:W.prototype.PD,set:W.prototype.GG});W.prototype.get_m_local=W.prototype.FD=function(){return k(Kz(this.kB),m)};
|
||||
W.prototype.set_m_local=W.prototype.wG=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Lz(c,a)};Object.defineProperty(W.prototype,"m_local",{get:W.prototype.FD,set:W.prototype.wG});W.prototype.get_m_body=W.prototype.KC=function(){return k(Mz(this.kB),K)};W.prototype.set_m_body=W.prototype.BF=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Nz(c,a)};Object.defineProperty(W.prototype,"m_body",{get:W.prototype.KC,set:W.prototype.BF});W.prototype.get_m_influence=W.prototype.yD=function(){return Oz(this.kB)};
|
||||
W.prototype.set_m_influence=W.prototype.pG=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Pz(c,a)};Object.defineProperty(W.prototype,"m_influence",{get:W.prototype.yD,set:W.prototype.pG});W.prototype.get_m_c0=W.prototype.NC=function(){return k(Qz(this.kB),uE)};W.prototype.set_m_c0=W.prototype.EF=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Rz(c,a)};Object.defineProperty(W.prototype,"m_c0",{get:W.prototype.NC,set:W.prototype.EF});
|
||||
W.prototype.get_m_c1=W.prototype.OC=function(){return k(Sz(this.kB),m)};W.prototype.set_m_c1=W.prototype.FF=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Tz(c,a)};Object.defineProperty(W.prototype,"m_c1",{get:W.prototype.OC,set:W.prototype.FF});W.prototype.get_m_c2=W.prototype.PC=function(){return Uz(this.kB)};W.prototype.set_m_c2=W.prototype.GF=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);Vz(c,a)};Object.defineProperty(W.prototype,"m_c2",{get:W.prototype.PC,set:W.prototype.GF});
|
||||
W.prototype.__destroy__=function(){Wz(this.kB)};function EF(){throw"cannot construct a tAnchorArray, no constructor in IDL";}EF.prototype=Object.create(f.prototype);EF.prototype.constructor=EF;EF.prototype.lB=EF;EF.mB={};b.tAnchorArray=EF;EF.prototype.size=EF.prototype.size=function(){return Xz(this.kB)};EF.prototype.at=EF.prototype.at=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);return k(Yz(c,a),W)};EF.prototype.clear=EF.prototype.clear=function(){Zz(this.kB)};
|
||||
EF.prototype.push_back=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);$z(c,a)};EF.prototype.pop_back=function(){aA(this.kB)};EF.prototype.__destroy__=function(){bA(this.kB)};function X(){throw"cannot construct a Config, no constructor in IDL";}X.prototype=Object.create(f.prototype);X.prototype.constructor=X;X.prototype.lB=X;X.mB={};b.Config=X;X.prototype.get_kVCF=X.prototype.zC=function(){return cA(this.kB)};
|
||||
X.prototype.set_kVCF=X.prototype.qF=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);dA(c,a)};Object.defineProperty(X.prototype,"kVCF",{get:X.prototype.zC,set:X.prototype.qF});X.prototype.get_kDP=X.prototype.mC=function(){return eA(this.kB)};X.prototype.set_kDP=X.prototype.dF=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);fA(c,a)};Object.defineProperty(X.prototype,"kDP",{get:X.prototype.mC,set:X.prototype.dF});X.prototype.get_kDG=X.prototype.lC=function(){return gA(this.kB)};
|
||||
X.prototype.set_kDG=X.prototype.cF=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);hA(c,a)};Object.defineProperty(X.prototype,"kDG",{get:X.prototype.lC,set:X.prototype.cF});X.prototype.get_kLF=X.prototype.oC=function(){return iA(this.kB)};X.prototype.set_kLF=X.prototype.fF=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);jA(c,a)};Object.defineProperty(X.prototype,"kLF",{get:X.prototype.oC,set:X.prototype.fF});X.prototype.get_kPR=X.prototype.qC=function(){return kA(this.kB)};
|
||||
X.prototype.set_kPR=X.prototype.hF=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);lA(c,a)};Object.defineProperty(X.prototype,"kPR",{get:X.prototype.qC,set:X.prototype.hF});X.prototype.get_kVC=X.prototype.yC=function(){return mA(this.kB)};X.prototype.set_kVC=X.prototype.pF=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);nA(c,a)};Object.defineProperty(X.prototype,"kVC",{get:X.prototype.yC,set:X.prototype.pF});X.prototype.get_kDF=X.prototype.kC=function(){return oA(this.kB)};
|
||||
X.prototype.set_kDF=X.prototype.bF=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);pA(c,a)};Object.defineProperty(X.prototype,"kDF",{get:X.prototype.kC,set:X.prototype.bF});X.prototype.get_kMT=X.prototype.pC=function(){return qA(this.kB)};X.prototype.set_kMT=X.prototype.gF=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);rA(c,a)};Object.defineProperty(X.prototype,"kMT",{get:X.prototype.pC,set:X.prototype.gF});X.prototype.get_kCHR=X.prototype.jC=function(){return sA(this.kB)};
|
||||
X.prototype.set_kCHR=X.prototype.aF=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);tA(c,a)};Object.defineProperty(X.prototype,"kCHR",{get:X.prototype.jC,set:X.prototype.aF});X.prototype.get_kKHR=X.prototype.nC=function(){return uA(this.kB)};X.prototype.set_kKHR=X.prototype.eF=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);vA(c,a)};Object.defineProperty(X.prototype,"kKHR",{get:X.prototype.nC,set:X.prototype.eF});X.prototype.get_kSHR=X.prototype.rC=function(){return wA(this.kB)};
|
||||
X.prototype.set_kSHR=X.prototype.iF=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);xA(c,a)};Object.defineProperty(X.prototype,"kSHR",{get:X.prototype.rC,set:X.prototype.iF});X.prototype.get_kAHR=X.prototype.iC=function(){return yA(this.kB)};X.prototype.set_kAHR=X.prototype.$E=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);zA(c,a)};Object.defineProperty(X.prototype,"kAHR",{get:X.prototype.iC,set:X.prototype.$E});X.prototype.get_kSRHR_CL=X.prototype.uC=function(){return AA(this.kB)};
|
||||
X.prototype.set_kSRHR_CL=X.prototype.lF=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);BA(c,a)};Object.defineProperty(X.prototype,"kSRHR_CL",{get:X.prototype.uC,set:X.prototype.lF});X.prototype.get_kSKHR_CL=X.prototype.sC=function(){return CA(this.kB)};X.prototype.set_kSKHR_CL=X.prototype.jF=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);DA(c,a)};Object.defineProperty(X.prototype,"kSKHR_CL",{get:X.prototype.sC,set:X.prototype.jF});X.prototype.get_kSSHR_CL=X.prototype.wC=function(){return EA(this.kB)};
|
||||
X.prototype.set_kSSHR_CL=X.prototype.nF=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);FA(c,a)};Object.defineProperty(X.prototype,"kSSHR_CL",{get:X.prototype.wC,set:X.prototype.nF});X.prototype.get_kSR_SPLT_CL=X.prototype.vC=function(){return GA(this.kB)};X.prototype.set_kSR_SPLT_CL=X.prototype.mF=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);HA(c,a)};Object.defineProperty(X.prototype,"kSR_SPLT_CL",{get:X.prototype.vC,set:X.prototype.mF});
|
||||
X.prototype.get_kSK_SPLT_CL=X.prototype.tC=function(){return IA(this.kB)};X.prototype.set_kSK_SPLT_CL=X.prototype.kF=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);JA(c,a)};Object.defineProperty(X.prototype,"kSK_SPLT_CL",{get:X.prototype.tC,set:X.prototype.kF});X.prototype.get_kSS_SPLT_CL=X.prototype.xC=function(){return KA(this.kB)};X.prototype.set_kSS_SPLT_CL=X.prototype.oF=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);LA(c,a)};
|
||||
Object.defineProperty(X.prototype,"kSS_SPLT_CL",{get:X.prototype.xC,set:X.prototype.oF});X.prototype.get_maxvolume=X.prototype.JE=function(){return MA(this.kB)};X.prototype.set_maxvolume=X.prototype.BH=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);NA(c,a)};Object.defineProperty(X.prototype,"maxvolume",{get:X.prototype.JE,set:X.prototype.BH});X.prototype.get_timescale=X.prototype.OE=function(){return OA(this.kB)};
|
||||
X.prototype.set_timescale=X.prototype.GH=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);PA(c,a)};Object.defineProperty(X.prototype,"timescale",{get:X.prototype.OE,set:X.prototype.GH});X.prototype.get_viterations=X.prototype.QE=function(){return QA(this.kB)};X.prototype.set_viterations=X.prototype.IH=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);RA(c,a)};Object.defineProperty(X.prototype,"viterations",{get:X.prototype.QE,set:X.prototype.IH});
|
||||
X.prototype.get_piterations=X.prototype.ME=function(){return SA(this.kB)};X.prototype.set_piterations=X.prototype.EH=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);TA(c,a)};Object.defineProperty(X.prototype,"piterations",{get:X.prototype.ME,set:X.prototype.EH});X.prototype.get_diterations=X.prototype.fC=function(){return UA(this.kB)};X.prototype.set_diterations=X.prototype.XE=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);VA(c,a)};
|
||||
Object.defineProperty(X.prototype,"diterations",{get:X.prototype.fC,set:X.prototype.XE});X.prototype.get_citerations=X.prototype.dC=function(){return WA(this.kB)};X.prototype.set_citerations=X.prototype.VE=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);XA(c,a)};Object.defineProperty(X.prototype,"citerations",{get:X.prototype.dC,set:X.prototype.VE});X.prototype.get_collisions=X.prototype.eC=function(){return YA(this.kB)};
|
||||
X.prototype.set_collisions=X.prototype.WE=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);ZA(c,a)};Object.defineProperty(X.prototype,"collisions",{get:X.prototype.eC,set:X.prototype.WE});X.prototype.__destroy__=function(){$A(this.kB)};function Y(a,c,d,e){ND();a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);"object"==typeof e&&(e=RD(e));this.kB=aB(a,c,d,e);h(Y)[this.kB]=this}Y.prototype=Object.create(q.prototype);
|
||||
Y.prototype.constructor=Y;Y.prototype.lB=Y;Y.mB={};b.btSoftBody=Y;Y.prototype.checkLink=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);return!!bB(d,a,c)};Y.prototype.checkFace=function(a,c,d){var e=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);return!!cB(e,a,c,d)};Y.prototype.appendMaterial=function(){return k(dB(this.kB),V)};
|
||||
Y.prototype.appendNode=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);eB(d,a,c)};Y.prototype.appendLink=function(a,c,d,e){var g=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);e&&"object"===typeof e&&(e=e.kB);fB(g,a,c,d,e)};
|
||||
Y.prototype.appendFace=function(a,c,d,e){var g=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);e&&"object"===typeof e&&(e=e.kB);gB(g,a,c,d,e)};Y.prototype.appendTetra=function(a,c,d,e,g){var n=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);e&&"object"===typeof e&&(e=e.kB);g&&"object"===typeof g&&(g=g.kB);hB(n,a,c,d,e,g)};
|
||||
Y.prototype.appendAnchor=function(a,c,d,e){var g=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);e&&"object"===typeof e&&(e=e.kB);iB(g,a,c,d,e)};Y.prototype.addForce=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);void 0===c?jB(d,a):kB(d,a,c)};Y.prototype.addAeroForceToNode=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);lB(d,a,c)};
|
||||
Y.prototype.getTotalMass=function(){return mB(this.kB)};Y.prototype.setTotalMass=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);nB(d,a,c)};Y.prototype.setMass=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);oB(d,a,c)};Y.prototype.transform=Y.prototype.transform=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);pB(c,a)};
|
||||
Y.prototype.translate=Y.prototype.translate=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);qB(c,a)};Y.prototype.rotate=Y.prototype.rotate=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);rB(c,a)};Y.prototype.scale=Y.prototype.scale=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);sB(c,a)};Y.prototype.generateClusters=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);return void 0===c?tB(d,a):uB(d,a,c)};
|
||||
Y.prototype.generateBendingConstraints=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);return vB(d,a,c)};Y.prototype.upcast=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);return k(wB(c,a),Y)};Y.prototype.getRestLengthScale=function(){return xB(this.kB)};Y.prototype.setRestLengthScale=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);yB(c,a)};
|
||||
Y.prototype.setAnisotropicFriction=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);zB(d,a,c)};Y.prototype.getCollisionShape=function(){return k(AB(this.kB),l)};Y.prototype.setContactProcessingThreshold=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);BB(c,a)};Y.prototype.setActivationState=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);CB(c,a)};
|
||||
Y.prototype.forceActivationState=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);DB(c,a)};Y.prototype.activate=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);void 0===a?EB(c):FB(c,a)};Y.prototype.isActive=function(){return!!GB(this.kB)};Y.prototype.isKinematicObject=function(){return!!HB(this.kB)};Y.prototype.isStaticObject=function(){return!!IB(this.kB)};Y.prototype.isStaticOrKinematicObject=function(){return!!JB(this.kB)};Y.prototype.getRestitution=function(){return KB(this.kB)};
|
||||
Y.prototype.getFriction=function(){return LB(this.kB)};Y.prototype.getRollingFriction=function(){return MB(this.kB)};Y.prototype.setRestitution=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);NB(c,a)};Y.prototype.setFriction=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);OB(c,a)};Y.prototype.setRollingFriction=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);PB(c,a)};Y.prototype.getWorldTransform=function(){return k(QB(this.kB),r)};Y.prototype.getCollisionFlags=function(){return RB(this.kB)};
|
||||
Y.prototype.setCollisionFlags=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);SB(c,a)};Y.prototype.setWorldTransform=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);TB(c,a)};Y.prototype.setCollisionShape=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);UB(c,a)};Y.prototype.setCcdMotionThreshold=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);VB(c,a)};Y.prototype.setCcdSweptSphereRadius=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);WB(c,a)};
|
||||
Y.prototype.getUserIndex=function(){return XB(this.kB)};Y.prototype.setUserIndex=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);YB(c,a)};Y.prototype.getUserPointer=function(){return k(ZB(this.kB),XD)};Y.prototype.setUserPointer=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);$B(c,a)};Y.prototype.getBroadphaseHandle=function(){return k(aC(this.kB),YD)};Y.prototype.get_m_cfg=Y.prototype.QC=function(){return k(bC(this.kB),X)};
|
||||
Y.prototype.set_m_cfg=Y.prototype.HF=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);cC(c,a)};Object.defineProperty(Y.prototype,"m_cfg",{get:Y.prototype.QC,set:Y.prototype.HF});Y.prototype.get_m_nodes=Y.prototype.QD=function(){return k(dC(this.kB),CF)};Y.prototype.set_m_nodes=Y.prototype.HG=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);eC(c,a)};Object.defineProperty(Y.prototype,"m_nodes",{get:Y.prototype.QD,set:Y.prototype.HG});
|
||||
Y.prototype.get_m_faces=Y.prototype.KB=function(){return k(fC(this.kB),BF)};Y.prototype.set_m_faces=Y.prototype.UB=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);gC(c,a)};Object.defineProperty(Y.prototype,"m_faces",{get:Y.prototype.KB,set:Y.prototype.UB});Y.prototype.get_m_materials=Y.prototype.MD=function(){return k(hC(this.kB),DF)};Y.prototype.set_m_materials=Y.prototype.DG=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);iC(c,a)};
|
||||
Object.defineProperty(Y.prototype,"m_materials",{get:Y.prototype.MD,set:Y.prototype.DG});Y.prototype.get_m_anchors=Y.prototype.GC=function(){return k(jC(this.kB),EF)};Y.prototype.set_m_anchors=Y.prototype.xF=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);kC(c,a)};Object.defineProperty(Y.prototype,"m_anchors",{get:Y.prototype.GC,set:Y.prototype.xF});Y.prototype.__destroy__=function(){lC(this.kB)};
|
||||
function FF(a){a&&"object"===typeof a&&(a=a.kB);this.kB=void 0===a?mC():nC(a);h(FF)[this.kB]=this}FF.prototype=Object.create(nE.prototype);FF.prototype.constructor=FF;FF.prototype.lB=FF;FF.mB={};b.btSoftBodyRigidBodyCollisionConfiguration=FF;FF.prototype.__destroy__=function(){oC(this.kB)};function GF(){this.kB=pC();h(GF)[this.kB]=this}GF.prototype=Object.create(sE.prototype);GF.prototype.constructor=GF;GF.prototype.lB=GF;GF.mB={};b.btDefaultSoftBodySolver=GF;GF.prototype.__destroy__=function(){qC(this.kB)};
|
||||
function HF(){throw"cannot construct a btSoftBodyArray, no constructor in IDL";}HF.prototype=Object.create(f.prototype);HF.prototype.constructor=HF;HF.prototype.lB=HF;HF.mB={};b.btSoftBodyArray=HF;HF.prototype.size=HF.prototype.size=function(){return rC(this.kB)};HF.prototype.at=HF.prototype.at=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);return k(sC(c,a),Y)};HF.prototype.__destroy__=function(){tC(this.kB)};
|
||||
function Z(a,c,d,e,g){a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);e&&"object"===typeof e&&(e=e.kB);g&&"object"===typeof g&&(g=g.kB);this.kB=uC(a,c,d,e,g);h(Z)[this.kB]=this}Z.prototype=Object.create(x.prototype);Z.prototype.constructor=Z;Z.prototype.lB=Z;Z.mB={};b.btSoftRigidDynamicsWorld=Z;
|
||||
Z.prototype.addSoftBody=function(a,c,d){var e=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);vC(e,a,c,d)};Z.prototype.removeSoftBody=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);wC(c,a)};Z.prototype.removeCollisionObject=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);xC(c,a)};Z.prototype.getWorldInfo=function(){return k(yC(this.kB),S)};Z.prototype.getSoftBodyArray=function(){return k(zC(this.kB),HF)};
|
||||
Z.prototype.getDispatcher=function(){return k(AC(this.kB),TD)};Z.prototype.rayTest=function(a,c,d){var e=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);BC(e,a,c,d)};Z.prototype.getPairCache=function(){return k(CC(this.kB),UD)};Z.prototype.getDispatchInfo=function(){return k(DC(this.kB),p)};
|
||||
Z.prototype.addCollisionObject=function(a,c,d){var e=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);void 0===c?EC(e,a):void 0===d?FC(e,a,c):GC(e,a,c,d)};Z.prototype.getBroadphase=function(){return k(HC(this.kB),VD)};
|
||||
Z.prototype.convexSweepTest=function(a,c,d,e,g){var n=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);e&&"object"===typeof e&&(e=e.kB);g&&"object"===typeof g&&(g=g.kB);IC(n,a,c,d,e,g)};Z.prototype.contactPairTest=function(a,c,d){var e=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);JC(e,a,c,d)};
|
||||
Z.prototype.contactTest=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);KC(d,a,c)};Z.prototype.updateSingleAabb=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);LC(c,a)};Z.prototype.setDebugDrawer=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);MC(c,a)};Z.prototype.getDebugDrawer=function(){return k(NC(this.kB),WD)};Z.prototype.debugDrawWorld=function(){OC(this.kB)};
|
||||
Z.prototype.debugDrawObject=function(a,c,d){var e=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);PC(e,a,c,d)};Z.prototype.setGravity=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);QC(c,a)};Z.prototype.getGravity=function(){return k(RC(this.kB),m)};
|
||||
Z.prototype.addRigidBody=function(a,c,d){var e=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);void 0===c?SC(e,a):void 0===d?_emscripten_bind_btSoftRigidDynamicsWorld_addRigidBody_2(e,a,c):TC(e,a,c,d)};Z.prototype.removeRigidBody=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);UC(c,a)};
|
||||
Z.prototype.addConstraint=function(a,c){var d=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);void 0===c?VC(d,a):WC(d,a,c)};Z.prototype.removeConstraint=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);XC(c,a)};Z.prototype.stepSimulation=function(a,c,d){var e=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);return void 0===c?YC(e,a):void 0===d?ZC(e,a,c):$C(e,a,c,d)};
|
||||
Z.prototype.setContactAddedCallback=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);aD(c,a)};Z.prototype.setContactProcessedCallback=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);bD(c,a)};Z.prototype.setContactDestroyedCallback=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);cD(c,a)};Z.prototype.addAction=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);dD(c,a)};Z.prototype.removeAction=function(a){var c=this.kB;a&&"object"===typeof a&&(a=a.kB);eD(c,a)};
|
||||
Z.prototype.getSolverInfo=function(){return k(fD(this.kB),t)};Z.prototype.setInternalTickCallback=function(a,c,d){var e=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);void 0===c?gD(e,a):void 0===d?hD(e,a,c):iD(e,a,c,d)};Z.prototype.__destroy__=function(){jD(this.kB)};function IF(){this.kB=kD();h(IF)[this.kB]=this}IF.prototype=Object.create(f.prototype);IF.prototype.constructor=IF;IF.prototype.lB=IF;IF.mB={};b.btSoftBodyHelpers=IF;
|
||||
IF.prototype.CreateRope=function(a,c,d,e,g){var n=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);e&&"object"===typeof e&&(e=e.kB);g&&"object"===typeof g&&(g=g.kB);return k(lD(n,a,c,d,e,g),Y)};
|
||||
IF.prototype.CreatePatch=function(a,c,d,e,g,n,z,T,Da){var dc=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);e&&"object"===typeof e&&(e=e.kB);g&&"object"===typeof g&&(g=g.kB);n&&"object"===typeof n&&(n=n.kB);z&&"object"===typeof z&&(z=z.kB);T&&"object"===typeof T&&(T=T.kB);Da&&"object"===typeof Da&&(Da=Da.kB);return k(mD(dc,a,c,d,e,g,n,z,T,Da),Y)};
|
||||
IF.prototype.CreatePatchUV=function(a,c,d,e,g,n,z,T,Da,dc){var JF=this.kB;ND();a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);e&&"object"===typeof e&&(e=e.kB);g&&"object"===typeof g&&(g=g.kB);n&&"object"===typeof n&&(n=n.kB);z&&"object"===typeof z&&(z=z.kB);T&&"object"===typeof T&&(T=T.kB);Da&&"object"===typeof Da&&(Da=Da.kB);"object"==typeof dc&&(dc=RD(dc));return k(nD(JF,a,c,d,e,g,n,z,T,Da,dc),Y)};
|
||||
IF.prototype.CreateEllipsoid=function(a,c,d,e){var g=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);e&&"object"===typeof e&&(e=e.kB);return k(oD(g,a,c,d,e),Y)};
|
||||
IF.prototype.CreateFromTriMesh=function(a,c,d,e,g){var n=this.kB;ND();a&&"object"===typeof a&&(a=a.kB);"object"==typeof c&&(c=RD(c));if("object"==typeof d&&"object"===typeof d){var z=OD(d,va);PD(d,va,z);d=z}e&&"object"===typeof e&&(e=e.kB);g&&"object"===typeof g&&(g=g.kB);return k(pD(n,a,c,d,e,g),Y)};
|
||||
IF.prototype.CreateFromConvexHull=function(a,c,d,e){var g=this.kB;a&&"object"===typeof a&&(a=a.kB);c&&"object"===typeof c&&(c=c.kB);d&&"object"===typeof d&&(d=d.kB);e&&"object"===typeof e&&(e=e.kB);return k(qD(g,a,c,d,e),Y)};IF.prototype.__destroy__=function(){rD(this.kB)};
|
||||
(function(){function a(){b.PHY_FLOAT=sD();b.PHY_DOUBLE=tD();b.PHY_INTEGER=uD();b.PHY_SHORT=vD();b.PHY_FIXEDPOINT88=wD();b.PHY_UCHAR=xD();b.CONST_GIMPACT_COMPOUND_SHAPE=yD();b.CONST_GIMPACT_TRIMESH_SHAPE_PART=zD();b.CONST_GIMPACT_TRIMESH_SHAPE=AD();b.BT_CONSTRAINT_ERP=BD();b.BT_CONSTRAINT_STOP_ERP=CD();b.BT_CONSTRAINT_CFM=DD();b.BT_CONSTRAINT_STOP_CFM=ED()}Ca?a():Aa.unshift(a)})();b.CONTACT_ADDED_CALLBACK_SIGNATURE="iiiiiiii";b.CONTACT_DESTROYED_CALLBACK_SIGNATURE="ii";
|
||||
b.CONTACT_PROCESSED_CALLBACK_SIGNATURE="iiii";b.INTERNAL_TICK_CALLBACK_SIGNATURE="vif";this.Ammo=b;
|
||||
|
||||
|
||||
return Ammo.ready
|
||||
}
|
||||
|
||||
);
|
||||
})();
|
||||
if (typeof exports === 'object' && typeof module === 'object')
|
||||
module.exports = Ammo;
|
||||
else if (typeof define === 'function' && define['amd'])
|
||||
define([], function() { return Ammo; });
|
||||
else if (typeof exports === 'object')
|
||||
exports["Ammo"] = Ammo;
|
||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
||||
# Draco glTF decoder 1.5.7
|
||||
|
||||
Vendored from Google's immutable versioned Draco decoder distribution for the
|
||||
PlayCanvas `DracoDecoderModule`. Runtime loading remains local to Mission Core.
|
||||
|
||||
- `draco_wasm_wrapper_gltf.js`: `8bb2952d2ba7d67e1414f8df819410cb0434a666be53f671fff75f68843d76f6`
|
||||
- `draco_decoder_gltf.wasm`: `712db3449ae2041d6e8a224c395bda6cedb49e51322fae38b7db9beb8b381889`
|
||||
- Upstream base: `https://www.gstatic.com/draco/versioned/decoders/1.5.7/`
|
||||
BIN
Binary file not shown.
+116
@@ -0,0 +1,116 @@
|
||||
var $jscomp=$jscomp||{};$jscomp.scope={};$jscomp.arrayIteratorImpl=function(h){var n=0;return function(){return n<h.length?{done:!1,value:h[n++]}:{done:!0}}};$jscomp.arrayIterator=function(h){return{next:$jscomp.arrayIteratorImpl(h)}};$jscomp.makeIterator=function(h){var n="undefined"!=typeof Symbol&&Symbol.iterator&&h[Symbol.iterator];return n?n.call(h):$jscomp.arrayIterator(h)};$jscomp.ASSUME_ES5=!1;$jscomp.ASSUME_NO_NATIVE_MAP=!1;$jscomp.ASSUME_NO_NATIVE_SET=!1;$jscomp.SIMPLE_FROUND_POLYFILL=!1;
|
||||
$jscomp.ISOLATE_POLYFILLS=!1;$jscomp.FORCE_POLYFILL_PROMISE=!1;$jscomp.FORCE_POLYFILL_PROMISE_WHEN_NO_UNHANDLED_REJECTION=!1;$jscomp.getGlobal=function(h){h=["object"==typeof globalThis&&globalThis,h,"object"==typeof window&&window,"object"==typeof self&&self,"object"==typeof global&&global];for(var n=0;n<h.length;++n){var k=h[n];if(k&&k.Math==Math)return k}throw Error("Cannot find global object");};$jscomp.global=$jscomp.getGlobal(this);
|
||||
$jscomp.defineProperty=$jscomp.ASSUME_ES5||"function"==typeof Object.defineProperties?Object.defineProperty:function(h,n,k){if(h==Array.prototype||h==Object.prototype)return h;h[n]=k.value;return h};$jscomp.IS_SYMBOL_NATIVE="function"===typeof Symbol&&"symbol"===typeof Symbol("x");$jscomp.TRUST_ES6_POLYFILLS=!$jscomp.ISOLATE_POLYFILLS||$jscomp.IS_SYMBOL_NATIVE;$jscomp.polyfills={};$jscomp.propertyToPolyfillSymbol={};$jscomp.POLYFILL_PREFIX="$jscp$";
|
||||
var $jscomp$lookupPolyfilledValue=function(h,n){var k=$jscomp.propertyToPolyfillSymbol[n];if(null==k)return h[n];k=h[k];return void 0!==k?k:h[n]};$jscomp.polyfill=function(h,n,k,p){n&&($jscomp.ISOLATE_POLYFILLS?$jscomp.polyfillIsolated(h,n,k,p):$jscomp.polyfillUnisolated(h,n,k,p))};
|
||||
$jscomp.polyfillUnisolated=function(h,n,k,p){k=$jscomp.global;h=h.split(".");for(p=0;p<h.length-1;p++){var l=h[p];if(!(l in k))return;k=k[l]}h=h[h.length-1];p=k[h];n=n(p);n!=p&&null!=n&&$jscomp.defineProperty(k,h,{configurable:!0,writable:!0,value:n})};
|
||||
$jscomp.polyfillIsolated=function(h,n,k,p){var l=h.split(".");h=1===l.length;p=l[0];p=!h&&p in $jscomp.polyfills?$jscomp.polyfills:$jscomp.global;for(var y=0;y<l.length-1;y++){var f=l[y];if(!(f in p))return;p=p[f]}l=l[l.length-1];k=$jscomp.IS_SYMBOL_NATIVE&&"es6"===k?p[l]:null;n=n(k);null!=n&&(h?$jscomp.defineProperty($jscomp.polyfills,l,{configurable:!0,writable:!0,value:n}):n!==k&&(void 0===$jscomp.propertyToPolyfillSymbol[l]&&(k=1E9*Math.random()>>>0,$jscomp.propertyToPolyfillSymbol[l]=$jscomp.IS_SYMBOL_NATIVE?
|
||||
$jscomp.global.Symbol(l):$jscomp.POLYFILL_PREFIX+k+"$"+l),$jscomp.defineProperty(p,$jscomp.propertyToPolyfillSymbol[l],{configurable:!0,writable:!0,value:n})))};
|
||||
$jscomp.polyfill("Promise",function(h){function n(){this.batch_=null}function k(f){return f instanceof l?f:new l(function(q,u){q(f)})}if(h&&(!($jscomp.FORCE_POLYFILL_PROMISE||$jscomp.FORCE_POLYFILL_PROMISE_WHEN_NO_UNHANDLED_REJECTION&&"undefined"===typeof $jscomp.global.PromiseRejectionEvent)||!$jscomp.global.Promise||-1===$jscomp.global.Promise.toString().indexOf("[native code]")))return h;n.prototype.asyncExecute=function(f){if(null==this.batch_){this.batch_=[];var q=this;this.asyncExecuteFunction(function(){q.executeBatch_()})}this.batch_.push(f)};
|
||||
var p=$jscomp.global.setTimeout;n.prototype.asyncExecuteFunction=function(f){p(f,0)};n.prototype.executeBatch_=function(){for(;this.batch_&&this.batch_.length;){var f=this.batch_;this.batch_=[];for(var q=0;q<f.length;++q){var u=f[q];f[q]=null;try{u()}catch(A){this.asyncThrow_(A)}}}this.batch_=null};n.prototype.asyncThrow_=function(f){this.asyncExecuteFunction(function(){throw f;})};var l=function(f){this.state_=0;this.result_=void 0;this.onSettledCallbacks_=[];this.isRejectionHandled_=!1;var q=this.createResolveAndReject_();
|
||||
try{f(q.resolve,q.reject)}catch(u){q.reject(u)}};l.prototype.createResolveAndReject_=function(){function f(A){return function(F){u||(u=!0,A.call(q,F))}}var q=this,u=!1;return{resolve:f(this.resolveTo_),reject:f(this.reject_)}};l.prototype.resolveTo_=function(f){if(f===this)this.reject_(new TypeError("A Promise cannot resolve to itself"));else if(f instanceof l)this.settleSameAsPromise_(f);else{a:switch(typeof f){case "object":var q=null!=f;break a;case "function":q=!0;break a;default:q=!1}q?this.resolveToNonPromiseObj_(f):
|
||||
this.fulfill_(f)}};l.prototype.resolveToNonPromiseObj_=function(f){var q=void 0;try{q=f.then}catch(u){this.reject_(u);return}"function"==typeof q?this.settleSameAsThenable_(q,f):this.fulfill_(f)};l.prototype.reject_=function(f){this.settle_(2,f)};l.prototype.fulfill_=function(f){this.settle_(1,f)};l.prototype.settle_=function(f,q){if(0!=this.state_)throw Error("Cannot settle("+f+", "+q+"): Promise already settled in state"+this.state_);this.state_=f;this.result_=q;2===this.state_&&this.scheduleUnhandledRejectionCheck_();
|
||||
this.executeOnSettledCallbacks_()};l.prototype.scheduleUnhandledRejectionCheck_=function(){var f=this;p(function(){if(f.notifyUnhandledRejection_()){var q=$jscomp.global.console;"undefined"!==typeof q&&q.error(f.result_)}},1)};l.prototype.notifyUnhandledRejection_=function(){if(this.isRejectionHandled_)return!1;var f=$jscomp.global.CustomEvent,q=$jscomp.global.Event,u=$jscomp.global.dispatchEvent;if("undefined"===typeof u)return!0;"function"===typeof f?f=new f("unhandledrejection",{cancelable:!0}):
|
||||
"function"===typeof q?f=new q("unhandledrejection",{cancelable:!0}):(f=$jscomp.global.document.createEvent("CustomEvent"),f.initCustomEvent("unhandledrejection",!1,!0,f));f.promise=this;f.reason=this.result_;return u(f)};l.prototype.executeOnSettledCallbacks_=function(){if(null!=this.onSettledCallbacks_){for(var f=0;f<this.onSettledCallbacks_.length;++f)y.asyncExecute(this.onSettledCallbacks_[f]);this.onSettledCallbacks_=null}};var y=new n;l.prototype.settleSameAsPromise_=function(f){var q=this.createResolveAndReject_();
|
||||
f.callWhenSettled_(q.resolve,q.reject)};l.prototype.settleSameAsThenable_=function(f,q){var u=this.createResolveAndReject_();try{f.call(q,u.resolve,u.reject)}catch(A){u.reject(A)}};l.prototype.then=function(f,q){function u(w,B){return"function"==typeof w?function(R){try{A(w(R))}catch(Z){F(Z)}}:B}var A,F,v=new l(function(w,B){A=w;F=B});this.callWhenSettled_(u(f,A),u(q,F));return v};l.prototype.catch=function(f){return this.then(void 0,f)};l.prototype.callWhenSettled_=function(f,q){function u(){switch(A.state_){case 1:f(A.result_);
|
||||
break;case 2:q(A.result_);break;default:throw Error("Unexpected state: "+A.state_);}}var A=this;null==this.onSettledCallbacks_?y.asyncExecute(u):this.onSettledCallbacks_.push(u);this.isRejectionHandled_=!0};l.resolve=k;l.reject=function(f){return new l(function(q,u){u(f)})};l.race=function(f){return new l(function(q,u){for(var A=$jscomp.makeIterator(f),F=A.next();!F.done;F=A.next())k(F.value).callWhenSettled_(q,u)})};l.all=function(f){var q=$jscomp.makeIterator(f),u=q.next();return u.done?k([]):new l(function(A,
|
||||
F){function v(R){return function(Z){w[R]=Z;B--;0==B&&A(w)}}var w=[],B=0;do w.push(void 0),B++,k(u.value).callWhenSettled_(v(w.length-1),F),u=q.next();while(!u.done)})};return l},"es6","es3");$jscomp.owns=function(h,n){return Object.prototype.hasOwnProperty.call(h,n)};$jscomp.assign=$jscomp.TRUST_ES6_POLYFILLS&&"function"==typeof Object.assign?Object.assign:function(h,n){for(var k=1;k<arguments.length;k++){var p=arguments[k];if(p)for(var l in p)$jscomp.owns(p,l)&&(h[l]=p[l])}return h};
|
||||
$jscomp.polyfill("Object.assign",function(h){return h||$jscomp.assign},"es6","es3");$jscomp.checkStringArgs=function(h,n,k){if(null==h)throw new TypeError("The 'this' value for String.prototype."+k+" must not be null or undefined");if(n instanceof RegExp)throw new TypeError("First argument to String.prototype."+k+" must not be a regular expression");return h+""};
|
||||
$jscomp.polyfill("String.prototype.startsWith",function(h){return h?h:function(n,k){var p=$jscomp.checkStringArgs(this,n,"startsWith");n+="";var l=p.length,y=n.length;k=Math.max(0,Math.min(k|0,p.length));for(var f=0;f<y&&k<l;)if(p[k++]!=n[f++])return!1;return f>=y}},"es6","es3");
|
||||
$jscomp.polyfill("Array.prototype.copyWithin",function(h){function n(k){k=Number(k);return Infinity===k||-Infinity===k?k:k|0}return h?h:function(k,p,l){var y=this.length;k=n(k);p=n(p);l=void 0===l?y:n(l);k=0>k?Math.max(y+k,0):Math.min(k,y);p=0>p?Math.max(y+p,0):Math.min(p,y);l=0>l?Math.max(y+l,0):Math.min(l,y);if(k<p)for(;p<l;)p in this?this[k++]=this[p++]:(delete this[k++],p++);else for(l=Math.min(l,y+p-k),k+=l-p;l>p;)--l in this?this[--k]=this[l]:delete this[--k];return this}},"es6","es3");
|
||||
$jscomp.typedArrayCopyWithin=function(h){return h?h:Array.prototype.copyWithin};$jscomp.polyfill("Int8Array.prototype.copyWithin",$jscomp.typedArrayCopyWithin,"es6","es5");$jscomp.polyfill("Uint8Array.prototype.copyWithin",$jscomp.typedArrayCopyWithin,"es6","es5");$jscomp.polyfill("Uint8ClampedArray.prototype.copyWithin",$jscomp.typedArrayCopyWithin,"es6","es5");$jscomp.polyfill("Int16Array.prototype.copyWithin",$jscomp.typedArrayCopyWithin,"es6","es5");
|
||||
$jscomp.polyfill("Uint16Array.prototype.copyWithin",$jscomp.typedArrayCopyWithin,"es6","es5");$jscomp.polyfill("Int32Array.prototype.copyWithin",$jscomp.typedArrayCopyWithin,"es6","es5");$jscomp.polyfill("Uint32Array.prototype.copyWithin",$jscomp.typedArrayCopyWithin,"es6","es5");$jscomp.polyfill("Float32Array.prototype.copyWithin",$jscomp.typedArrayCopyWithin,"es6","es5");$jscomp.polyfill("Float64Array.prototype.copyWithin",$jscomp.typedArrayCopyWithin,"es6","es5");
|
||||
var DracoDecoderModule=function(){var h="undefined"!==typeof document&&document.currentScript?document.currentScript.src:void 0;"undefined"!==typeof __filename&&(h=h||__filename);return function(n){function k(e){return a.locateFile?a.locateFile(e,U):U+e}function p(e,b){if(e){var c=ia;var d=e+b;for(b=e;c[b]&&!(b>=d);)++b;if(16<b-e&&c.buffer&&ra)c=ra.decode(c.subarray(e,b));else{for(d="";e<b;){var g=c[e++];if(g&128){var t=c[e++]&63;if(192==(g&224))d+=String.fromCharCode((g&31)<<6|t);else{var aa=c[e++]&
|
||||
63;g=224==(g&240)?(g&15)<<12|t<<6|aa:(g&7)<<18|t<<12|aa<<6|c[e++]&63;65536>g?d+=String.fromCharCode(g):(g-=65536,d+=String.fromCharCode(55296|g>>10,56320|g&1023))}}else d+=String.fromCharCode(g)}c=d}}else c="";return c}function l(){var e=ja.buffer;a.HEAP8=W=new Int8Array(e);a.HEAP16=new Int16Array(e);a.HEAP32=ca=new Int32Array(e);a.HEAPU8=ia=new Uint8Array(e);a.HEAPU16=new Uint16Array(e);a.HEAPU32=Y=new Uint32Array(e);a.HEAPF32=new Float32Array(e);a.HEAPF64=new Float64Array(e)}function y(e){if(a.onAbort)a.onAbort(e);
|
||||
e="Aborted("+e+")";da(e);sa=!0;e=new WebAssembly.RuntimeError(e+". Build with -sASSERTIONS for more info.");ka(e);throw e;}function f(e){try{if(e==P&&ea)return new Uint8Array(ea);if(ma)return ma(e);throw"both async and sync fetching of the wasm failed";}catch(b){y(b)}}function q(){if(!ea&&(ta||fa)){if("function"==typeof fetch&&!P.startsWith("file://"))return fetch(P,{credentials:"same-origin"}).then(function(e){if(!e.ok)throw"failed to load wasm binary file at '"+P+"'";return e.arrayBuffer()}).catch(function(){return f(P)});
|
||||
if(na)return new Promise(function(e,b){na(P,function(c){e(new Uint8Array(c))},b)})}return Promise.resolve().then(function(){return f(P)})}function u(e){for(;0<e.length;)e.shift()(a)}function A(e){this.excPtr=e;this.ptr=e-24;this.set_type=function(b){Y[this.ptr+4>>2]=b};this.get_type=function(){return Y[this.ptr+4>>2]};this.set_destructor=function(b){Y[this.ptr+8>>2]=b};this.get_destructor=function(){return Y[this.ptr+8>>2]};this.set_refcount=function(b){ca[this.ptr>>2]=b};this.set_caught=function(b){W[this.ptr+
|
||||
12>>0]=b?1:0};this.get_caught=function(){return 0!=W[this.ptr+12>>0]};this.set_rethrown=function(b){W[this.ptr+13>>0]=b?1:0};this.get_rethrown=function(){return 0!=W[this.ptr+13>>0]};this.init=function(b,c){this.set_adjusted_ptr(0);this.set_type(b);this.set_destructor(c);this.set_refcount(0);this.set_caught(!1);this.set_rethrown(!1)};this.add_ref=function(){ca[this.ptr>>2]+=1};this.release_ref=function(){var b=ca[this.ptr>>2];ca[this.ptr>>2]=b-1;return 1===b};this.set_adjusted_ptr=function(b){Y[this.ptr+
|
||||
16>>2]=b};this.get_adjusted_ptr=function(){return Y[this.ptr+16>>2]};this.get_exception_ptr=function(){if(ua(this.get_type()))return Y[this.excPtr>>2];var b=this.get_adjusted_ptr();return 0!==b?b:this.excPtr}}function F(){function e(){if(!la&&(la=!0,a.calledRun=!0,!sa)){va=!0;u(oa);wa(a);if(a.onRuntimeInitialized)a.onRuntimeInitialized();if(a.postRun)for("function"==typeof a.postRun&&(a.postRun=[a.postRun]);a.postRun.length;)xa.unshift(a.postRun.shift());u(xa)}}if(!(0<ba)){if(a.preRun)for("function"==
|
||||
typeof a.preRun&&(a.preRun=[a.preRun]);a.preRun.length;)ya.unshift(a.preRun.shift());u(ya);0<ba||(a.setStatus?(a.setStatus("Running..."),setTimeout(function(){setTimeout(function(){a.setStatus("")},1);e()},1)):e())}}function v(){}function w(e){return(e||v).__cache__}function B(e,b){var c=w(b),d=c[e];if(d)return d;d=Object.create((b||v).prototype);d.ptr=e;return c[e]=d}function R(e){if("string"===typeof e){for(var b=0,c=0;c<e.length;++c){var d=e.charCodeAt(c);127>=d?b++:2047>=d?b+=2:55296<=d&&57343>=
|
||||
d?(b+=4,++c):b+=3}b=Array(b+1);c=0;d=b.length;if(0<d){d=c+d-1;for(var g=0;g<e.length;++g){var t=e.charCodeAt(g);if(55296<=t&&57343>=t){var aa=e.charCodeAt(++g);t=65536+((t&1023)<<10)|aa&1023}if(127>=t){if(c>=d)break;b[c++]=t}else{if(2047>=t){if(c+1>=d)break;b[c++]=192|t>>6}else{if(65535>=t){if(c+2>=d)break;b[c++]=224|t>>12}else{if(c+3>=d)break;b[c++]=240|t>>18;b[c++]=128|t>>12&63}b[c++]=128|t>>6&63}b[c++]=128|t&63}}b[c]=0}e=r.alloc(b,W);r.copy(b,W,e);return e}return e}function Z(e){if("object"===
|
||||
typeof e){var b=r.alloc(e,W);r.copy(e,W,b);return b}return e}function X(){throw"cannot construct a VoidPtr, no constructor in IDL";}function S(){this.ptr=za();w(S)[this.ptr]=this}function Q(){this.ptr=Aa();w(Q)[this.ptr]=this}function V(){this.ptr=Ba();w(V)[this.ptr]=this}function x(){this.ptr=Ca();w(x)[this.ptr]=this}function D(){this.ptr=Da();w(D)[this.ptr]=this}function G(){this.ptr=Ea();w(G)[this.ptr]=this}function H(){this.ptr=Fa();w(H)[this.ptr]=this}function E(){this.ptr=Ga();w(E)[this.ptr]=
|
||||
this}function T(){this.ptr=Ha();w(T)[this.ptr]=this}function C(){throw"cannot construct a Status, no constructor in IDL";}function I(){this.ptr=Ia();w(I)[this.ptr]=this}function J(){this.ptr=Ja();w(J)[this.ptr]=this}function K(){this.ptr=Ka();w(K)[this.ptr]=this}function L(){this.ptr=La();w(L)[this.ptr]=this}function M(){this.ptr=Ma();w(M)[this.ptr]=this}function N(){this.ptr=Na();w(N)[this.ptr]=this}function O(){this.ptr=Oa();w(O)[this.ptr]=this}function z(){this.ptr=Pa();w(z)[this.ptr]=this}function m(){this.ptr=
|
||||
Qa();w(m)[this.ptr]=this}n=void 0===n?{}:n;var a="undefined"!=typeof n?n:{},wa,ka;a.ready=new Promise(function(e,b){wa=e;ka=b});var Ra=!1,Sa=!1;a.onRuntimeInitialized=function(){Ra=!0;if(Sa&&"function"===typeof a.onModuleLoaded)a.onModuleLoaded(a)};a.onModuleParsed=function(){Sa=!0;if(Ra&&"function"===typeof a.onModuleLoaded)a.onModuleLoaded(a)};a.isVersionSupported=function(e){if("string"!==typeof e)return!1;e=e.split(".");return 2>e.length||3<e.length?!1:1==e[0]&&0<=e[1]&&5>=e[1]?!0:0!=e[0]||10<
|
||||
e[1]?!1:!0};var Ta=Object.assign({},a),ta="object"==typeof window,fa="function"==typeof importScripts,Ua="object"==typeof process&&"object"==typeof process.versions&&"string"==typeof process.versions.node,U="";if(Ua){var Va=require("fs"),pa=require("path");U=fa?pa.dirname(U)+"/":__dirname+"/";var Wa=function(e,b){e=e.startsWith("file://")?new URL(e):pa.normalize(e);return Va.readFileSync(e,b?void 0:"utf8")};var ma=function(e){e=Wa(e,!0);e.buffer||(e=new Uint8Array(e));return e};var na=function(e,
|
||||
b,c){e=e.startsWith("file://")?new URL(e):pa.normalize(e);Va.readFile(e,function(d,g){d?c(d):b(g.buffer)})};1<process.argv.length&&process.argv[1].replace(/\\/g,"/");process.argv.slice(2);a.inspect=function(){return"[Emscripten Module object]"}}else if(ta||fa)fa?U=self.location.href:"undefined"!=typeof document&&document.currentScript&&(U=document.currentScript.src),h&&(U=h),U=0!==U.indexOf("blob:")?U.substr(0,U.replace(/[?#].*/,"").lastIndexOf("/")+1):"",Wa=function(e){var b=new XMLHttpRequest;b.open("GET",
|
||||
e,!1);b.send(null);return b.responseText},fa&&(ma=function(e){var b=new XMLHttpRequest;b.open("GET",e,!1);b.responseType="arraybuffer";b.send(null);return new Uint8Array(b.response)}),na=function(e,b,c){var d=new XMLHttpRequest;d.open("GET",e,!0);d.responseType="arraybuffer";d.onload=function(){200==d.status||0==d.status&&d.response?b(d.response):c()};d.onerror=c;d.send(null)};a.print||console.log.bind(console);var da=a.printErr||console.warn.bind(console);Object.assign(a,Ta);Ta=null;var ea;a.wasmBinary&&
|
||||
(ea=a.wasmBinary);"object"!=typeof WebAssembly&&y("no native wasm support detected");var ja,sa=!1,ra="undefined"!=typeof TextDecoder?new TextDecoder("utf8"):void 0,W,ia,ca,Y,ya=[],oa=[],xa=[],va=!1,ba=0,qa=null,ha=null;var P="draco_decoder_gltf.wasm";P.startsWith("data:application/octet-stream;base64,")||(P=k(P));var pd=0,qd={b:function(e,b,c){(new A(e)).init(b,c);pd++;throw e;},a:function(){y("")},d:function(e,b,c){ia.copyWithin(e,b,b+c)},c:function(e){var b=ia.length;e>>>=0;if(2147483648<e)return!1;
|
||||
for(var c=1;4>=c;c*=2){var d=b*(1+.2/c);d=Math.min(d,e+100663296);var g=Math;d=Math.max(e,d);g=g.min.call(g,2147483648,d+(65536-d%65536)%65536);a:{d=ja.buffer;try{ja.grow(g-d.byteLength+65535>>>16);l();var t=1;break a}catch(aa){}t=void 0}if(t)return!0}return!1}};(function(){function e(g,t){a.asm=g.exports;ja=a.asm.e;l();oa.unshift(a.asm.f);ba--;a.monitorRunDependencies&&a.monitorRunDependencies(ba);0==ba&&(null!==qa&&(clearInterval(qa),qa=null),ha&&(g=ha,ha=null,g()))}function b(g){e(g.instance)}
|
||||
function c(g){return q().then(function(t){return WebAssembly.instantiate(t,d)}).then(function(t){return t}).then(g,function(t){da("failed to asynchronously prepare wasm: "+t);y(t)})}var d={a:qd};ba++;a.monitorRunDependencies&&a.monitorRunDependencies(ba);if(a.instantiateWasm)try{return a.instantiateWasm(d,e)}catch(g){da("Module.instantiateWasm callback failed with error: "+g),ka(g)}(function(){return ea||"function"!=typeof WebAssembly.instantiateStreaming||P.startsWith("data:application/octet-stream;base64,")||
|
||||
P.startsWith("file://")||Ua||"function"!=typeof fetch?c(b):fetch(P,{credentials:"same-origin"}).then(function(g){return WebAssembly.instantiateStreaming(g,d).then(b,function(t){da("wasm streaming compile failed: "+t);da("falling back to ArrayBuffer instantiation");return c(b)})})})().catch(ka);return{}})();var Xa=a._emscripten_bind_VoidPtr___destroy___0=function(){return(Xa=a._emscripten_bind_VoidPtr___destroy___0=a.asm.h).apply(null,arguments)},za=a._emscripten_bind_DecoderBuffer_DecoderBuffer_0=
|
||||
function(){return(za=a._emscripten_bind_DecoderBuffer_DecoderBuffer_0=a.asm.i).apply(null,arguments)},Ya=a._emscripten_bind_DecoderBuffer_Init_2=function(){return(Ya=a._emscripten_bind_DecoderBuffer_Init_2=a.asm.j).apply(null,arguments)},Za=a._emscripten_bind_DecoderBuffer___destroy___0=function(){return(Za=a._emscripten_bind_DecoderBuffer___destroy___0=a.asm.k).apply(null,arguments)},Aa=a._emscripten_bind_AttributeTransformData_AttributeTransformData_0=function(){return(Aa=a._emscripten_bind_AttributeTransformData_AttributeTransformData_0=
|
||||
a.asm.l).apply(null,arguments)},$a=a._emscripten_bind_AttributeTransformData_transform_type_0=function(){return($a=a._emscripten_bind_AttributeTransformData_transform_type_0=a.asm.m).apply(null,arguments)},ab=a._emscripten_bind_AttributeTransformData___destroy___0=function(){return(ab=a._emscripten_bind_AttributeTransformData___destroy___0=a.asm.n).apply(null,arguments)},Ba=a._emscripten_bind_GeometryAttribute_GeometryAttribute_0=function(){return(Ba=a._emscripten_bind_GeometryAttribute_GeometryAttribute_0=
|
||||
a.asm.o).apply(null,arguments)},bb=a._emscripten_bind_GeometryAttribute___destroy___0=function(){return(bb=a._emscripten_bind_GeometryAttribute___destroy___0=a.asm.p).apply(null,arguments)},Ca=a._emscripten_bind_PointAttribute_PointAttribute_0=function(){return(Ca=a._emscripten_bind_PointAttribute_PointAttribute_0=a.asm.q).apply(null,arguments)},cb=a._emscripten_bind_PointAttribute_size_0=function(){return(cb=a._emscripten_bind_PointAttribute_size_0=a.asm.r).apply(null,arguments)},db=a._emscripten_bind_PointAttribute_GetAttributeTransformData_0=
|
||||
function(){return(db=a._emscripten_bind_PointAttribute_GetAttributeTransformData_0=a.asm.s).apply(null,arguments)},eb=a._emscripten_bind_PointAttribute_attribute_type_0=function(){return(eb=a._emscripten_bind_PointAttribute_attribute_type_0=a.asm.t).apply(null,arguments)},fb=a._emscripten_bind_PointAttribute_data_type_0=function(){return(fb=a._emscripten_bind_PointAttribute_data_type_0=a.asm.u).apply(null,arguments)},gb=a._emscripten_bind_PointAttribute_num_components_0=function(){return(gb=a._emscripten_bind_PointAttribute_num_components_0=
|
||||
a.asm.v).apply(null,arguments)},hb=a._emscripten_bind_PointAttribute_normalized_0=function(){return(hb=a._emscripten_bind_PointAttribute_normalized_0=a.asm.w).apply(null,arguments)},ib=a._emscripten_bind_PointAttribute_byte_stride_0=function(){return(ib=a._emscripten_bind_PointAttribute_byte_stride_0=a.asm.x).apply(null,arguments)},jb=a._emscripten_bind_PointAttribute_byte_offset_0=function(){return(jb=a._emscripten_bind_PointAttribute_byte_offset_0=a.asm.y).apply(null,arguments)},kb=a._emscripten_bind_PointAttribute_unique_id_0=
|
||||
function(){return(kb=a._emscripten_bind_PointAttribute_unique_id_0=a.asm.z).apply(null,arguments)},lb=a._emscripten_bind_PointAttribute___destroy___0=function(){return(lb=a._emscripten_bind_PointAttribute___destroy___0=a.asm.A).apply(null,arguments)},Da=a._emscripten_bind_AttributeQuantizationTransform_AttributeQuantizationTransform_0=function(){return(Da=a._emscripten_bind_AttributeQuantizationTransform_AttributeQuantizationTransform_0=a.asm.B).apply(null,arguments)},mb=a._emscripten_bind_AttributeQuantizationTransform_InitFromAttribute_1=
|
||||
function(){return(mb=a._emscripten_bind_AttributeQuantizationTransform_InitFromAttribute_1=a.asm.C).apply(null,arguments)},nb=a._emscripten_bind_AttributeQuantizationTransform_quantization_bits_0=function(){return(nb=a._emscripten_bind_AttributeQuantizationTransform_quantization_bits_0=a.asm.D).apply(null,arguments)},ob=a._emscripten_bind_AttributeQuantizationTransform_min_value_1=function(){return(ob=a._emscripten_bind_AttributeQuantizationTransform_min_value_1=a.asm.E).apply(null,arguments)},pb=
|
||||
a._emscripten_bind_AttributeQuantizationTransform_range_0=function(){return(pb=a._emscripten_bind_AttributeQuantizationTransform_range_0=a.asm.F).apply(null,arguments)},qb=a._emscripten_bind_AttributeQuantizationTransform___destroy___0=function(){return(qb=a._emscripten_bind_AttributeQuantizationTransform___destroy___0=a.asm.G).apply(null,arguments)},Ea=a._emscripten_bind_AttributeOctahedronTransform_AttributeOctahedronTransform_0=function(){return(Ea=a._emscripten_bind_AttributeOctahedronTransform_AttributeOctahedronTransform_0=
|
||||
a.asm.H).apply(null,arguments)},rb=a._emscripten_bind_AttributeOctahedronTransform_InitFromAttribute_1=function(){return(rb=a._emscripten_bind_AttributeOctahedronTransform_InitFromAttribute_1=a.asm.I).apply(null,arguments)},sb=a._emscripten_bind_AttributeOctahedronTransform_quantization_bits_0=function(){return(sb=a._emscripten_bind_AttributeOctahedronTransform_quantization_bits_0=a.asm.J).apply(null,arguments)},tb=a._emscripten_bind_AttributeOctahedronTransform___destroy___0=function(){return(tb=
|
||||
a._emscripten_bind_AttributeOctahedronTransform___destroy___0=a.asm.K).apply(null,arguments)},Fa=a._emscripten_bind_PointCloud_PointCloud_0=function(){return(Fa=a._emscripten_bind_PointCloud_PointCloud_0=a.asm.L).apply(null,arguments)},ub=a._emscripten_bind_PointCloud_num_attributes_0=function(){return(ub=a._emscripten_bind_PointCloud_num_attributes_0=a.asm.M).apply(null,arguments)},vb=a._emscripten_bind_PointCloud_num_points_0=function(){return(vb=a._emscripten_bind_PointCloud_num_points_0=a.asm.N).apply(null,
|
||||
arguments)},wb=a._emscripten_bind_PointCloud___destroy___0=function(){return(wb=a._emscripten_bind_PointCloud___destroy___0=a.asm.O).apply(null,arguments)},Ga=a._emscripten_bind_Mesh_Mesh_0=function(){return(Ga=a._emscripten_bind_Mesh_Mesh_0=a.asm.P).apply(null,arguments)},xb=a._emscripten_bind_Mesh_num_faces_0=function(){return(xb=a._emscripten_bind_Mesh_num_faces_0=a.asm.Q).apply(null,arguments)},yb=a._emscripten_bind_Mesh_num_attributes_0=function(){return(yb=a._emscripten_bind_Mesh_num_attributes_0=
|
||||
a.asm.R).apply(null,arguments)},zb=a._emscripten_bind_Mesh_num_points_0=function(){return(zb=a._emscripten_bind_Mesh_num_points_0=a.asm.S).apply(null,arguments)},Ab=a._emscripten_bind_Mesh___destroy___0=function(){return(Ab=a._emscripten_bind_Mesh___destroy___0=a.asm.T).apply(null,arguments)},Ha=a._emscripten_bind_Metadata_Metadata_0=function(){return(Ha=a._emscripten_bind_Metadata_Metadata_0=a.asm.U).apply(null,arguments)},Bb=a._emscripten_bind_Metadata___destroy___0=function(){return(Bb=a._emscripten_bind_Metadata___destroy___0=
|
||||
a.asm.V).apply(null,arguments)},Cb=a._emscripten_bind_Status_code_0=function(){return(Cb=a._emscripten_bind_Status_code_0=a.asm.W).apply(null,arguments)},Db=a._emscripten_bind_Status_ok_0=function(){return(Db=a._emscripten_bind_Status_ok_0=a.asm.X).apply(null,arguments)},Eb=a._emscripten_bind_Status_error_msg_0=function(){return(Eb=a._emscripten_bind_Status_error_msg_0=a.asm.Y).apply(null,arguments)},Fb=a._emscripten_bind_Status___destroy___0=function(){return(Fb=a._emscripten_bind_Status___destroy___0=
|
||||
a.asm.Z).apply(null,arguments)},Ia=a._emscripten_bind_DracoFloat32Array_DracoFloat32Array_0=function(){return(Ia=a._emscripten_bind_DracoFloat32Array_DracoFloat32Array_0=a.asm._).apply(null,arguments)},Gb=a._emscripten_bind_DracoFloat32Array_GetValue_1=function(){return(Gb=a._emscripten_bind_DracoFloat32Array_GetValue_1=a.asm.$).apply(null,arguments)},Hb=a._emscripten_bind_DracoFloat32Array_size_0=function(){return(Hb=a._emscripten_bind_DracoFloat32Array_size_0=a.asm.aa).apply(null,arguments)},Ib=
|
||||
a._emscripten_bind_DracoFloat32Array___destroy___0=function(){return(Ib=a._emscripten_bind_DracoFloat32Array___destroy___0=a.asm.ba).apply(null,arguments)},Ja=a._emscripten_bind_DracoInt8Array_DracoInt8Array_0=function(){return(Ja=a._emscripten_bind_DracoInt8Array_DracoInt8Array_0=a.asm.ca).apply(null,arguments)},Jb=a._emscripten_bind_DracoInt8Array_GetValue_1=function(){return(Jb=a._emscripten_bind_DracoInt8Array_GetValue_1=a.asm.da).apply(null,arguments)},Kb=a._emscripten_bind_DracoInt8Array_size_0=
|
||||
function(){return(Kb=a._emscripten_bind_DracoInt8Array_size_0=a.asm.ea).apply(null,arguments)},Lb=a._emscripten_bind_DracoInt8Array___destroy___0=function(){return(Lb=a._emscripten_bind_DracoInt8Array___destroy___0=a.asm.fa).apply(null,arguments)},Ka=a._emscripten_bind_DracoUInt8Array_DracoUInt8Array_0=function(){return(Ka=a._emscripten_bind_DracoUInt8Array_DracoUInt8Array_0=a.asm.ga).apply(null,arguments)},Mb=a._emscripten_bind_DracoUInt8Array_GetValue_1=function(){return(Mb=a._emscripten_bind_DracoUInt8Array_GetValue_1=
|
||||
a.asm.ha).apply(null,arguments)},Nb=a._emscripten_bind_DracoUInt8Array_size_0=function(){return(Nb=a._emscripten_bind_DracoUInt8Array_size_0=a.asm.ia).apply(null,arguments)},Ob=a._emscripten_bind_DracoUInt8Array___destroy___0=function(){return(Ob=a._emscripten_bind_DracoUInt8Array___destroy___0=a.asm.ja).apply(null,arguments)},La=a._emscripten_bind_DracoInt16Array_DracoInt16Array_0=function(){return(La=a._emscripten_bind_DracoInt16Array_DracoInt16Array_0=a.asm.ka).apply(null,arguments)},Pb=a._emscripten_bind_DracoInt16Array_GetValue_1=
|
||||
function(){return(Pb=a._emscripten_bind_DracoInt16Array_GetValue_1=a.asm.la).apply(null,arguments)},Qb=a._emscripten_bind_DracoInt16Array_size_0=function(){return(Qb=a._emscripten_bind_DracoInt16Array_size_0=a.asm.ma).apply(null,arguments)},Rb=a._emscripten_bind_DracoInt16Array___destroy___0=function(){return(Rb=a._emscripten_bind_DracoInt16Array___destroy___0=a.asm.na).apply(null,arguments)},Ma=a._emscripten_bind_DracoUInt16Array_DracoUInt16Array_0=function(){return(Ma=a._emscripten_bind_DracoUInt16Array_DracoUInt16Array_0=
|
||||
a.asm.oa).apply(null,arguments)},Sb=a._emscripten_bind_DracoUInt16Array_GetValue_1=function(){return(Sb=a._emscripten_bind_DracoUInt16Array_GetValue_1=a.asm.pa).apply(null,arguments)},Tb=a._emscripten_bind_DracoUInt16Array_size_0=function(){return(Tb=a._emscripten_bind_DracoUInt16Array_size_0=a.asm.qa).apply(null,arguments)},Ub=a._emscripten_bind_DracoUInt16Array___destroy___0=function(){return(Ub=a._emscripten_bind_DracoUInt16Array___destroy___0=a.asm.ra).apply(null,arguments)},Na=a._emscripten_bind_DracoInt32Array_DracoInt32Array_0=
|
||||
function(){return(Na=a._emscripten_bind_DracoInt32Array_DracoInt32Array_0=a.asm.sa).apply(null,arguments)},Vb=a._emscripten_bind_DracoInt32Array_GetValue_1=function(){return(Vb=a._emscripten_bind_DracoInt32Array_GetValue_1=a.asm.ta).apply(null,arguments)},Wb=a._emscripten_bind_DracoInt32Array_size_0=function(){return(Wb=a._emscripten_bind_DracoInt32Array_size_0=a.asm.ua).apply(null,arguments)},Xb=a._emscripten_bind_DracoInt32Array___destroy___0=function(){return(Xb=a._emscripten_bind_DracoInt32Array___destroy___0=
|
||||
a.asm.va).apply(null,arguments)},Oa=a._emscripten_bind_DracoUInt32Array_DracoUInt32Array_0=function(){return(Oa=a._emscripten_bind_DracoUInt32Array_DracoUInt32Array_0=a.asm.wa).apply(null,arguments)},Yb=a._emscripten_bind_DracoUInt32Array_GetValue_1=function(){return(Yb=a._emscripten_bind_DracoUInt32Array_GetValue_1=a.asm.xa).apply(null,arguments)},Zb=a._emscripten_bind_DracoUInt32Array_size_0=function(){return(Zb=a._emscripten_bind_DracoUInt32Array_size_0=a.asm.ya).apply(null,arguments)},$b=a._emscripten_bind_DracoUInt32Array___destroy___0=
|
||||
function(){return($b=a._emscripten_bind_DracoUInt32Array___destroy___0=a.asm.za).apply(null,arguments)},Pa=a._emscripten_bind_MetadataQuerier_MetadataQuerier_0=function(){return(Pa=a._emscripten_bind_MetadataQuerier_MetadataQuerier_0=a.asm.Aa).apply(null,arguments)},ac=a._emscripten_bind_MetadataQuerier_HasEntry_2=function(){return(ac=a._emscripten_bind_MetadataQuerier_HasEntry_2=a.asm.Ba).apply(null,arguments)},bc=a._emscripten_bind_MetadataQuerier_GetIntEntry_2=function(){return(bc=a._emscripten_bind_MetadataQuerier_GetIntEntry_2=
|
||||
a.asm.Ca).apply(null,arguments)},cc=a._emscripten_bind_MetadataQuerier_GetIntEntryArray_3=function(){return(cc=a._emscripten_bind_MetadataQuerier_GetIntEntryArray_3=a.asm.Da).apply(null,arguments)},dc=a._emscripten_bind_MetadataQuerier_GetDoubleEntry_2=function(){return(dc=a._emscripten_bind_MetadataQuerier_GetDoubleEntry_2=a.asm.Ea).apply(null,arguments)},ec=a._emscripten_bind_MetadataQuerier_GetStringEntry_2=function(){return(ec=a._emscripten_bind_MetadataQuerier_GetStringEntry_2=a.asm.Fa).apply(null,
|
||||
arguments)},fc=a._emscripten_bind_MetadataQuerier_NumEntries_1=function(){return(fc=a._emscripten_bind_MetadataQuerier_NumEntries_1=a.asm.Ga).apply(null,arguments)},gc=a._emscripten_bind_MetadataQuerier_GetEntryName_2=function(){return(gc=a._emscripten_bind_MetadataQuerier_GetEntryName_2=a.asm.Ha).apply(null,arguments)},hc=a._emscripten_bind_MetadataQuerier___destroy___0=function(){return(hc=a._emscripten_bind_MetadataQuerier___destroy___0=a.asm.Ia).apply(null,arguments)},Qa=a._emscripten_bind_Decoder_Decoder_0=
|
||||
function(){return(Qa=a._emscripten_bind_Decoder_Decoder_0=a.asm.Ja).apply(null,arguments)},ic=a._emscripten_bind_Decoder_DecodeArrayToPointCloud_3=function(){return(ic=a._emscripten_bind_Decoder_DecodeArrayToPointCloud_3=a.asm.Ka).apply(null,arguments)},jc=a._emscripten_bind_Decoder_DecodeArrayToMesh_3=function(){return(jc=a._emscripten_bind_Decoder_DecodeArrayToMesh_3=a.asm.La).apply(null,arguments)},kc=a._emscripten_bind_Decoder_GetAttributeId_2=function(){return(kc=a._emscripten_bind_Decoder_GetAttributeId_2=
|
||||
a.asm.Ma).apply(null,arguments)},lc=a._emscripten_bind_Decoder_GetAttributeIdByName_2=function(){return(lc=a._emscripten_bind_Decoder_GetAttributeIdByName_2=a.asm.Na).apply(null,arguments)},mc=a._emscripten_bind_Decoder_GetAttributeIdByMetadataEntry_3=function(){return(mc=a._emscripten_bind_Decoder_GetAttributeIdByMetadataEntry_3=a.asm.Oa).apply(null,arguments)},nc=a._emscripten_bind_Decoder_GetAttribute_2=function(){return(nc=a._emscripten_bind_Decoder_GetAttribute_2=a.asm.Pa).apply(null,arguments)},
|
||||
oc=a._emscripten_bind_Decoder_GetAttributeByUniqueId_2=function(){return(oc=a._emscripten_bind_Decoder_GetAttributeByUniqueId_2=a.asm.Qa).apply(null,arguments)},pc=a._emscripten_bind_Decoder_GetMetadata_1=function(){return(pc=a._emscripten_bind_Decoder_GetMetadata_1=a.asm.Ra).apply(null,arguments)},qc=a._emscripten_bind_Decoder_GetAttributeMetadata_2=function(){return(qc=a._emscripten_bind_Decoder_GetAttributeMetadata_2=a.asm.Sa).apply(null,arguments)},rc=a._emscripten_bind_Decoder_GetFaceFromMesh_3=
|
||||
function(){return(rc=a._emscripten_bind_Decoder_GetFaceFromMesh_3=a.asm.Ta).apply(null,arguments)},sc=a._emscripten_bind_Decoder_GetTriangleStripsFromMesh_2=function(){return(sc=a._emscripten_bind_Decoder_GetTriangleStripsFromMesh_2=a.asm.Ua).apply(null,arguments)},tc=a._emscripten_bind_Decoder_GetTrianglesUInt16Array_3=function(){return(tc=a._emscripten_bind_Decoder_GetTrianglesUInt16Array_3=a.asm.Va).apply(null,arguments)},uc=a._emscripten_bind_Decoder_GetTrianglesUInt32Array_3=function(){return(uc=
|
||||
a._emscripten_bind_Decoder_GetTrianglesUInt32Array_3=a.asm.Wa).apply(null,arguments)},vc=a._emscripten_bind_Decoder_GetAttributeFloat_3=function(){return(vc=a._emscripten_bind_Decoder_GetAttributeFloat_3=a.asm.Xa).apply(null,arguments)},wc=a._emscripten_bind_Decoder_GetAttributeFloatForAllPoints_3=function(){return(wc=a._emscripten_bind_Decoder_GetAttributeFloatForAllPoints_3=a.asm.Ya).apply(null,arguments)},xc=a._emscripten_bind_Decoder_GetAttributeIntForAllPoints_3=function(){return(xc=a._emscripten_bind_Decoder_GetAttributeIntForAllPoints_3=
|
||||
a.asm.Za).apply(null,arguments)},yc=a._emscripten_bind_Decoder_GetAttributeInt8ForAllPoints_3=function(){return(yc=a._emscripten_bind_Decoder_GetAttributeInt8ForAllPoints_3=a.asm._a).apply(null,arguments)},zc=a._emscripten_bind_Decoder_GetAttributeUInt8ForAllPoints_3=function(){return(zc=a._emscripten_bind_Decoder_GetAttributeUInt8ForAllPoints_3=a.asm.$a).apply(null,arguments)},Ac=a._emscripten_bind_Decoder_GetAttributeInt16ForAllPoints_3=function(){return(Ac=a._emscripten_bind_Decoder_GetAttributeInt16ForAllPoints_3=
|
||||
a.asm.ab).apply(null,arguments)},Bc=a._emscripten_bind_Decoder_GetAttributeUInt16ForAllPoints_3=function(){return(Bc=a._emscripten_bind_Decoder_GetAttributeUInt16ForAllPoints_3=a.asm.bb).apply(null,arguments)},Cc=a._emscripten_bind_Decoder_GetAttributeInt32ForAllPoints_3=function(){return(Cc=a._emscripten_bind_Decoder_GetAttributeInt32ForAllPoints_3=a.asm.cb).apply(null,arguments)},Dc=a._emscripten_bind_Decoder_GetAttributeUInt32ForAllPoints_3=function(){return(Dc=a._emscripten_bind_Decoder_GetAttributeUInt32ForAllPoints_3=
|
||||
a.asm.db).apply(null,arguments)},Ec=a._emscripten_bind_Decoder_GetAttributeDataArrayForAllPoints_5=function(){return(Ec=a._emscripten_bind_Decoder_GetAttributeDataArrayForAllPoints_5=a.asm.eb).apply(null,arguments)},Fc=a._emscripten_bind_Decoder_SkipAttributeTransform_1=function(){return(Fc=a._emscripten_bind_Decoder_SkipAttributeTransform_1=a.asm.fb).apply(null,arguments)},Gc=a._emscripten_bind_Decoder_GetEncodedGeometryType_Deprecated_1=function(){return(Gc=a._emscripten_bind_Decoder_GetEncodedGeometryType_Deprecated_1=
|
||||
a.asm.gb).apply(null,arguments)},Hc=a._emscripten_bind_Decoder_DecodeBufferToPointCloud_2=function(){return(Hc=a._emscripten_bind_Decoder_DecodeBufferToPointCloud_2=a.asm.hb).apply(null,arguments)},Ic=a._emscripten_bind_Decoder_DecodeBufferToMesh_2=function(){return(Ic=a._emscripten_bind_Decoder_DecodeBufferToMesh_2=a.asm.ib).apply(null,arguments)},Jc=a._emscripten_bind_Decoder___destroy___0=function(){return(Jc=a._emscripten_bind_Decoder___destroy___0=a.asm.jb).apply(null,arguments)},Kc=a._emscripten_enum_draco_AttributeTransformType_ATTRIBUTE_INVALID_TRANSFORM=
|
||||
function(){return(Kc=a._emscripten_enum_draco_AttributeTransformType_ATTRIBUTE_INVALID_TRANSFORM=a.asm.kb).apply(null,arguments)},Lc=a._emscripten_enum_draco_AttributeTransformType_ATTRIBUTE_NO_TRANSFORM=function(){return(Lc=a._emscripten_enum_draco_AttributeTransformType_ATTRIBUTE_NO_TRANSFORM=a.asm.lb).apply(null,arguments)},Mc=a._emscripten_enum_draco_AttributeTransformType_ATTRIBUTE_QUANTIZATION_TRANSFORM=function(){return(Mc=a._emscripten_enum_draco_AttributeTransformType_ATTRIBUTE_QUANTIZATION_TRANSFORM=
|
||||
a.asm.mb).apply(null,arguments)},Nc=a._emscripten_enum_draco_AttributeTransformType_ATTRIBUTE_OCTAHEDRON_TRANSFORM=function(){return(Nc=a._emscripten_enum_draco_AttributeTransformType_ATTRIBUTE_OCTAHEDRON_TRANSFORM=a.asm.nb).apply(null,arguments)},Oc=a._emscripten_enum_draco_GeometryAttribute_Type_INVALID=function(){return(Oc=a._emscripten_enum_draco_GeometryAttribute_Type_INVALID=a.asm.ob).apply(null,arguments)},Pc=a._emscripten_enum_draco_GeometryAttribute_Type_POSITION=function(){return(Pc=a._emscripten_enum_draco_GeometryAttribute_Type_POSITION=
|
||||
a.asm.pb).apply(null,arguments)},Qc=a._emscripten_enum_draco_GeometryAttribute_Type_NORMAL=function(){return(Qc=a._emscripten_enum_draco_GeometryAttribute_Type_NORMAL=a.asm.qb).apply(null,arguments)},Rc=a._emscripten_enum_draco_GeometryAttribute_Type_COLOR=function(){return(Rc=a._emscripten_enum_draco_GeometryAttribute_Type_COLOR=a.asm.rb).apply(null,arguments)},Sc=a._emscripten_enum_draco_GeometryAttribute_Type_TEX_COORD=function(){return(Sc=a._emscripten_enum_draco_GeometryAttribute_Type_TEX_COORD=
|
||||
a.asm.sb).apply(null,arguments)},Tc=a._emscripten_enum_draco_GeometryAttribute_Type_GENERIC=function(){return(Tc=a._emscripten_enum_draco_GeometryAttribute_Type_GENERIC=a.asm.tb).apply(null,arguments)},Uc=a._emscripten_enum_draco_EncodedGeometryType_INVALID_GEOMETRY_TYPE=function(){return(Uc=a._emscripten_enum_draco_EncodedGeometryType_INVALID_GEOMETRY_TYPE=a.asm.ub).apply(null,arguments)},Vc=a._emscripten_enum_draco_EncodedGeometryType_POINT_CLOUD=function(){return(Vc=a._emscripten_enum_draco_EncodedGeometryType_POINT_CLOUD=
|
||||
a.asm.vb).apply(null,arguments)},Wc=a._emscripten_enum_draco_EncodedGeometryType_TRIANGULAR_MESH=function(){return(Wc=a._emscripten_enum_draco_EncodedGeometryType_TRIANGULAR_MESH=a.asm.wb).apply(null,arguments)},Xc=a._emscripten_enum_draco_DataType_DT_INVALID=function(){return(Xc=a._emscripten_enum_draco_DataType_DT_INVALID=a.asm.xb).apply(null,arguments)},Yc=a._emscripten_enum_draco_DataType_DT_INT8=function(){return(Yc=a._emscripten_enum_draco_DataType_DT_INT8=a.asm.yb).apply(null,arguments)},Zc=
|
||||
a._emscripten_enum_draco_DataType_DT_UINT8=function(){return(Zc=a._emscripten_enum_draco_DataType_DT_UINT8=a.asm.zb).apply(null,arguments)},$c=a._emscripten_enum_draco_DataType_DT_INT16=function(){return($c=a._emscripten_enum_draco_DataType_DT_INT16=a.asm.Ab).apply(null,arguments)},ad=a._emscripten_enum_draco_DataType_DT_UINT16=function(){return(ad=a._emscripten_enum_draco_DataType_DT_UINT16=a.asm.Bb).apply(null,arguments)},bd=a._emscripten_enum_draco_DataType_DT_INT32=function(){return(bd=a._emscripten_enum_draco_DataType_DT_INT32=
|
||||
a.asm.Cb).apply(null,arguments)},cd=a._emscripten_enum_draco_DataType_DT_UINT32=function(){return(cd=a._emscripten_enum_draco_DataType_DT_UINT32=a.asm.Db).apply(null,arguments)},dd=a._emscripten_enum_draco_DataType_DT_INT64=function(){return(dd=a._emscripten_enum_draco_DataType_DT_INT64=a.asm.Eb).apply(null,arguments)},ed=a._emscripten_enum_draco_DataType_DT_UINT64=function(){return(ed=a._emscripten_enum_draco_DataType_DT_UINT64=a.asm.Fb).apply(null,arguments)},fd=a._emscripten_enum_draco_DataType_DT_FLOAT32=
|
||||
function(){return(fd=a._emscripten_enum_draco_DataType_DT_FLOAT32=a.asm.Gb).apply(null,arguments)},gd=a._emscripten_enum_draco_DataType_DT_FLOAT64=function(){return(gd=a._emscripten_enum_draco_DataType_DT_FLOAT64=a.asm.Hb).apply(null,arguments)},hd=a._emscripten_enum_draco_DataType_DT_BOOL=function(){return(hd=a._emscripten_enum_draco_DataType_DT_BOOL=a.asm.Ib).apply(null,arguments)},id=a._emscripten_enum_draco_DataType_DT_TYPES_COUNT=function(){return(id=a._emscripten_enum_draco_DataType_DT_TYPES_COUNT=
|
||||
a.asm.Jb).apply(null,arguments)},jd=a._emscripten_enum_draco_StatusCode_OK=function(){return(jd=a._emscripten_enum_draco_StatusCode_OK=a.asm.Kb).apply(null,arguments)},kd=a._emscripten_enum_draco_StatusCode_DRACO_ERROR=function(){return(kd=a._emscripten_enum_draco_StatusCode_DRACO_ERROR=a.asm.Lb).apply(null,arguments)},ld=a._emscripten_enum_draco_StatusCode_IO_ERROR=function(){return(ld=a._emscripten_enum_draco_StatusCode_IO_ERROR=a.asm.Mb).apply(null,arguments)},md=a._emscripten_enum_draco_StatusCode_INVALID_PARAMETER=
|
||||
function(){return(md=a._emscripten_enum_draco_StatusCode_INVALID_PARAMETER=a.asm.Nb).apply(null,arguments)},nd=a._emscripten_enum_draco_StatusCode_UNSUPPORTED_VERSION=function(){return(nd=a._emscripten_enum_draco_StatusCode_UNSUPPORTED_VERSION=a.asm.Ob).apply(null,arguments)},od=a._emscripten_enum_draco_StatusCode_UNKNOWN_VERSION=function(){return(od=a._emscripten_enum_draco_StatusCode_UNKNOWN_VERSION=a.asm.Pb).apply(null,arguments)};a._malloc=function(){return(a._malloc=a.asm.Qb).apply(null,arguments)};
|
||||
a._free=function(){return(a._free=a.asm.Rb).apply(null,arguments)};var ua=function(){return(ua=a.asm.Sb).apply(null,arguments)};a.___start_em_js=11660;a.___stop_em_js=11758;var la;ha=function b(){la||F();la||(ha=b)};if(a.preInit)for("function"==typeof a.preInit&&(a.preInit=[a.preInit]);0<a.preInit.length;)a.preInit.pop()();F();v.prototype=Object.create(v.prototype);v.prototype.constructor=v;v.prototype.__class__=v;v.__cache__={};a.WrapperObject=v;a.getCache=w;a.wrapPointer=B;a.castObject=function(b,
|
||||
c){return B(b.ptr,c)};a.NULL=B(0);a.destroy=function(b){if(!b.__destroy__)throw"Error: Cannot destroy object. (Did you create it yourself?)";b.__destroy__();delete w(b.__class__)[b.ptr]};a.compare=function(b,c){return b.ptr===c.ptr};a.getPointer=function(b){return b.ptr};a.getClass=function(b){return b.__class__};var r={buffer:0,size:0,pos:0,temps:[],needed:0,prepare:function(){if(r.needed){for(var b=0;b<r.temps.length;b++)a._free(r.temps[b]);r.temps.length=0;a._free(r.buffer);r.buffer=0;r.size+=
|
||||
r.needed;r.needed=0}r.buffer||(r.size+=128,r.buffer=a._malloc(r.size),r.buffer||y(void 0));r.pos=0},alloc:function(b,c){r.buffer||y(void 0);b=b.length*c.BYTES_PER_ELEMENT;b=b+7&-8;r.pos+b>=r.size?(0<b||y(void 0),r.needed+=b,c=a._malloc(b),r.temps.push(c)):(c=r.buffer+r.pos,r.pos+=b);return c},copy:function(b,c,d){d>>>=0;switch(c.BYTES_PER_ELEMENT){case 2:d>>>=1;break;case 4:d>>>=2;break;case 8:d>>>=3}for(var g=0;g<b.length;g++)c[d+g]=b[g]}};X.prototype=Object.create(v.prototype);X.prototype.constructor=
|
||||
X;X.prototype.__class__=X;X.__cache__={};a.VoidPtr=X;X.prototype.__destroy__=X.prototype.__destroy__=function(){Xa(this.ptr)};S.prototype=Object.create(v.prototype);S.prototype.constructor=S;S.prototype.__class__=S;S.__cache__={};a.DecoderBuffer=S;S.prototype.Init=S.prototype.Init=function(b,c){var d=this.ptr;r.prepare();"object"==typeof b&&(b=Z(b));c&&"object"===typeof c&&(c=c.ptr);Ya(d,b,c)};S.prototype.__destroy__=S.prototype.__destroy__=function(){Za(this.ptr)};Q.prototype=Object.create(v.prototype);
|
||||
Q.prototype.constructor=Q;Q.prototype.__class__=Q;Q.__cache__={};a.AttributeTransformData=Q;Q.prototype.transform_type=Q.prototype.transform_type=function(){return $a(this.ptr)};Q.prototype.__destroy__=Q.prototype.__destroy__=function(){ab(this.ptr)};V.prototype=Object.create(v.prototype);V.prototype.constructor=V;V.prototype.__class__=V;V.__cache__={};a.GeometryAttribute=V;V.prototype.__destroy__=V.prototype.__destroy__=function(){bb(this.ptr)};x.prototype=Object.create(v.prototype);x.prototype.constructor=
|
||||
x;x.prototype.__class__=x;x.__cache__={};a.PointAttribute=x;x.prototype.size=x.prototype.size=function(){return cb(this.ptr)};x.prototype.GetAttributeTransformData=x.prototype.GetAttributeTransformData=function(){return B(db(this.ptr),Q)};x.prototype.attribute_type=x.prototype.attribute_type=function(){return eb(this.ptr)};x.prototype.data_type=x.prototype.data_type=function(){return fb(this.ptr)};x.prototype.num_components=x.prototype.num_components=function(){return gb(this.ptr)};x.prototype.normalized=
|
||||
x.prototype.normalized=function(){return!!hb(this.ptr)};x.prototype.byte_stride=x.prototype.byte_stride=function(){return ib(this.ptr)};x.prototype.byte_offset=x.prototype.byte_offset=function(){return jb(this.ptr)};x.prototype.unique_id=x.prototype.unique_id=function(){return kb(this.ptr)};x.prototype.__destroy__=x.prototype.__destroy__=function(){lb(this.ptr)};D.prototype=Object.create(v.prototype);D.prototype.constructor=D;D.prototype.__class__=D;D.__cache__={};a.AttributeQuantizationTransform=
|
||||
D;D.prototype.InitFromAttribute=D.prototype.InitFromAttribute=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return!!mb(c,b)};D.prototype.quantization_bits=D.prototype.quantization_bits=function(){return nb(this.ptr)};D.prototype.min_value=D.prototype.min_value=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return ob(c,b)};D.prototype.range=D.prototype.range=function(){return pb(this.ptr)};D.prototype.__destroy__=D.prototype.__destroy__=function(){qb(this.ptr)};G.prototype=
|
||||
Object.create(v.prototype);G.prototype.constructor=G;G.prototype.__class__=G;G.__cache__={};a.AttributeOctahedronTransform=G;G.prototype.InitFromAttribute=G.prototype.InitFromAttribute=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return!!rb(c,b)};G.prototype.quantization_bits=G.prototype.quantization_bits=function(){return sb(this.ptr)};G.prototype.__destroy__=G.prototype.__destroy__=function(){tb(this.ptr)};H.prototype=Object.create(v.prototype);H.prototype.constructor=H;H.prototype.__class__=
|
||||
H;H.__cache__={};a.PointCloud=H;H.prototype.num_attributes=H.prototype.num_attributes=function(){return ub(this.ptr)};H.prototype.num_points=H.prototype.num_points=function(){return vb(this.ptr)};H.prototype.__destroy__=H.prototype.__destroy__=function(){wb(this.ptr)};E.prototype=Object.create(v.prototype);E.prototype.constructor=E;E.prototype.__class__=E;E.__cache__={};a.Mesh=E;E.prototype.num_faces=E.prototype.num_faces=function(){return xb(this.ptr)};E.prototype.num_attributes=E.prototype.num_attributes=
|
||||
function(){return yb(this.ptr)};E.prototype.num_points=E.prototype.num_points=function(){return zb(this.ptr)};E.prototype.__destroy__=E.prototype.__destroy__=function(){Ab(this.ptr)};T.prototype=Object.create(v.prototype);T.prototype.constructor=T;T.prototype.__class__=T;T.__cache__={};a.Metadata=T;T.prototype.__destroy__=T.prototype.__destroy__=function(){Bb(this.ptr)};C.prototype=Object.create(v.prototype);C.prototype.constructor=C;C.prototype.__class__=C;C.__cache__={};a.Status=C;C.prototype.code=
|
||||
C.prototype.code=function(){return Cb(this.ptr)};C.prototype.ok=C.prototype.ok=function(){return!!Db(this.ptr)};C.prototype.error_msg=C.prototype.error_msg=function(){return p(Eb(this.ptr))};C.prototype.__destroy__=C.prototype.__destroy__=function(){Fb(this.ptr)};I.prototype=Object.create(v.prototype);I.prototype.constructor=I;I.prototype.__class__=I;I.__cache__={};a.DracoFloat32Array=I;I.prototype.GetValue=I.prototype.GetValue=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return Gb(c,
|
||||
b)};I.prototype.size=I.prototype.size=function(){return Hb(this.ptr)};I.prototype.__destroy__=I.prototype.__destroy__=function(){Ib(this.ptr)};J.prototype=Object.create(v.prototype);J.prototype.constructor=J;J.prototype.__class__=J;J.__cache__={};a.DracoInt8Array=J;J.prototype.GetValue=J.prototype.GetValue=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return Jb(c,b)};J.prototype.size=J.prototype.size=function(){return Kb(this.ptr)};J.prototype.__destroy__=J.prototype.__destroy__=function(){Lb(this.ptr)};
|
||||
K.prototype=Object.create(v.prototype);K.prototype.constructor=K;K.prototype.__class__=K;K.__cache__={};a.DracoUInt8Array=K;K.prototype.GetValue=K.prototype.GetValue=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return Mb(c,b)};K.prototype.size=K.prototype.size=function(){return Nb(this.ptr)};K.prototype.__destroy__=K.prototype.__destroy__=function(){Ob(this.ptr)};L.prototype=Object.create(v.prototype);L.prototype.constructor=L;L.prototype.__class__=L;L.__cache__={};a.DracoInt16Array=
|
||||
L;L.prototype.GetValue=L.prototype.GetValue=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return Pb(c,b)};L.prototype.size=L.prototype.size=function(){return Qb(this.ptr)};L.prototype.__destroy__=L.prototype.__destroy__=function(){Rb(this.ptr)};M.prototype=Object.create(v.prototype);M.prototype.constructor=M;M.prototype.__class__=M;M.__cache__={};a.DracoUInt16Array=M;M.prototype.GetValue=M.prototype.GetValue=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return Sb(c,b)};
|
||||
M.prototype.size=M.prototype.size=function(){return Tb(this.ptr)};M.prototype.__destroy__=M.prototype.__destroy__=function(){Ub(this.ptr)};N.prototype=Object.create(v.prototype);N.prototype.constructor=N;N.prototype.__class__=N;N.__cache__={};a.DracoInt32Array=N;N.prototype.GetValue=N.prototype.GetValue=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return Vb(c,b)};N.prototype.size=N.prototype.size=function(){return Wb(this.ptr)};N.prototype.__destroy__=N.prototype.__destroy__=function(){Xb(this.ptr)};
|
||||
O.prototype=Object.create(v.prototype);O.prototype.constructor=O;O.prototype.__class__=O;O.__cache__={};a.DracoUInt32Array=O;O.prototype.GetValue=O.prototype.GetValue=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return Yb(c,b)};O.prototype.size=O.prototype.size=function(){return Zb(this.ptr)};O.prototype.__destroy__=O.prototype.__destroy__=function(){$b(this.ptr)};z.prototype=Object.create(v.prototype);z.prototype.constructor=z;z.prototype.__class__=z;z.__cache__={};a.MetadataQuerier=
|
||||
z;z.prototype.HasEntry=z.prototype.HasEntry=function(b,c){var d=this.ptr;r.prepare();b&&"object"===typeof b&&(b=b.ptr);c=c&&"object"===typeof c?c.ptr:R(c);return!!ac(d,b,c)};z.prototype.GetIntEntry=z.prototype.GetIntEntry=function(b,c){var d=this.ptr;r.prepare();b&&"object"===typeof b&&(b=b.ptr);c=c&&"object"===typeof c?c.ptr:R(c);return bc(d,b,c)};z.prototype.GetIntEntryArray=z.prototype.GetIntEntryArray=function(b,c,d){var g=this.ptr;r.prepare();b&&"object"===typeof b&&(b=b.ptr);c=c&&"object"===
|
||||
typeof c?c.ptr:R(c);d&&"object"===typeof d&&(d=d.ptr);cc(g,b,c,d)};z.prototype.GetDoubleEntry=z.prototype.GetDoubleEntry=function(b,c){var d=this.ptr;r.prepare();b&&"object"===typeof b&&(b=b.ptr);c=c&&"object"===typeof c?c.ptr:R(c);return dc(d,b,c)};z.prototype.GetStringEntry=z.prototype.GetStringEntry=function(b,c){var d=this.ptr;r.prepare();b&&"object"===typeof b&&(b=b.ptr);c=c&&"object"===typeof c?c.ptr:R(c);return p(ec(d,b,c))};z.prototype.NumEntries=z.prototype.NumEntries=function(b){var c=this.ptr;
|
||||
b&&"object"===typeof b&&(b=b.ptr);return fc(c,b)};z.prototype.GetEntryName=z.prototype.GetEntryName=function(b,c){var d=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);return p(gc(d,b,c))};z.prototype.__destroy__=z.prototype.__destroy__=function(){hc(this.ptr)};m.prototype=Object.create(v.prototype);m.prototype.constructor=m;m.prototype.__class__=m;m.__cache__={};a.Decoder=m;m.prototype.DecodeArrayToPointCloud=m.prototype.DecodeArrayToPointCloud=function(b,c,d){var g=
|
||||
this.ptr;r.prepare();"object"==typeof b&&(b=Z(b));c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return B(ic(g,b,c,d),C)};m.prototype.DecodeArrayToMesh=m.prototype.DecodeArrayToMesh=function(b,c,d){var g=this.ptr;r.prepare();"object"==typeof b&&(b=Z(b));c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return B(jc(g,b,c,d),C)};m.prototype.GetAttributeId=m.prototype.GetAttributeId=function(b,c){var d=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&
|
||||
(c=c.ptr);return kc(d,b,c)};m.prototype.GetAttributeIdByName=m.prototype.GetAttributeIdByName=function(b,c){var d=this.ptr;r.prepare();b&&"object"===typeof b&&(b=b.ptr);c=c&&"object"===typeof c?c.ptr:R(c);return lc(d,b,c)};m.prototype.GetAttributeIdByMetadataEntry=m.prototype.GetAttributeIdByMetadataEntry=function(b,c,d){var g=this.ptr;r.prepare();b&&"object"===typeof b&&(b=b.ptr);c=c&&"object"===typeof c?c.ptr:R(c);d=d&&"object"===typeof d?d.ptr:R(d);return mc(g,b,c,d)};m.prototype.GetAttribute=
|
||||
m.prototype.GetAttribute=function(b,c){var d=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);return B(nc(d,b,c),x)};m.prototype.GetAttributeByUniqueId=m.prototype.GetAttributeByUniqueId=function(b,c){var d=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);return B(oc(d,b,c),x)};m.prototype.GetMetadata=m.prototype.GetMetadata=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return B(pc(c,b),T)};m.prototype.GetAttributeMetadata=m.prototype.GetAttributeMetadata=
|
||||
function(b,c){var d=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);return B(qc(d,b,c),T)};m.prototype.GetFaceFromMesh=m.prototype.GetFaceFromMesh=function(b,c,d){var g=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!rc(g,b,c,d)};m.prototype.GetTriangleStripsFromMesh=m.prototype.GetTriangleStripsFromMesh=function(b,c){var d=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);
|
||||
return sc(d,b,c)};m.prototype.GetTrianglesUInt16Array=m.prototype.GetTrianglesUInt16Array=function(b,c,d){var g=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!tc(g,b,c,d)};m.prototype.GetTrianglesUInt32Array=m.prototype.GetTrianglesUInt32Array=function(b,c,d){var g=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!uc(g,b,c,d)};m.prototype.GetAttributeFloat=m.prototype.GetAttributeFloat=
|
||||
function(b,c,d){var g=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!vc(g,b,c,d)};m.prototype.GetAttributeFloatForAllPoints=m.prototype.GetAttributeFloatForAllPoints=function(b,c,d){var g=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!wc(g,b,c,d)};m.prototype.GetAttributeIntForAllPoints=m.prototype.GetAttributeIntForAllPoints=function(b,c,d){var g=this.ptr;
|
||||
b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!xc(g,b,c,d)};m.prototype.GetAttributeInt8ForAllPoints=m.prototype.GetAttributeInt8ForAllPoints=function(b,c,d){var g=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!yc(g,b,c,d)};m.prototype.GetAttributeUInt8ForAllPoints=m.prototype.GetAttributeUInt8ForAllPoints=function(b,c,d){var g=this.ptr;b&&"object"===typeof b&&(b=
|
||||
b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!zc(g,b,c,d)};m.prototype.GetAttributeInt16ForAllPoints=m.prototype.GetAttributeInt16ForAllPoints=function(b,c,d){var g=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!Ac(g,b,c,d)};m.prototype.GetAttributeUInt16ForAllPoints=m.prototype.GetAttributeUInt16ForAllPoints=function(b,c,d){var g=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&
|
||||
(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!Bc(g,b,c,d)};m.prototype.GetAttributeInt32ForAllPoints=m.prototype.GetAttributeInt32ForAllPoints=function(b,c,d){var g=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);return!!Cc(g,b,c,d)};m.prototype.GetAttributeUInt32ForAllPoints=m.prototype.GetAttributeUInt32ForAllPoints=function(b,c,d){var g=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===
|
||||
typeof d&&(d=d.ptr);return!!Dc(g,b,c,d)};m.prototype.GetAttributeDataArrayForAllPoints=m.prototype.GetAttributeDataArrayForAllPoints=function(b,c,d,g,t){var aa=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);d&&"object"===typeof d&&(d=d.ptr);g&&"object"===typeof g&&(g=g.ptr);t&&"object"===typeof t&&(t=t.ptr);return!!Ec(aa,b,c,d,g,t)};m.prototype.SkipAttributeTransform=m.prototype.SkipAttributeTransform=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);Fc(c,
|
||||
b)};m.prototype.GetEncodedGeometryType_Deprecated=m.prototype.GetEncodedGeometryType_Deprecated=function(b){var c=this.ptr;b&&"object"===typeof b&&(b=b.ptr);return Gc(c,b)};m.prototype.DecodeBufferToPointCloud=m.prototype.DecodeBufferToPointCloud=function(b,c){var d=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===typeof c&&(c=c.ptr);return B(Hc(d,b,c),C)};m.prototype.DecodeBufferToMesh=m.prototype.DecodeBufferToMesh=function(b,c){var d=this.ptr;b&&"object"===typeof b&&(b=b.ptr);c&&"object"===
|
||||
typeof c&&(c=c.ptr);return B(Ic(d,b,c),C)};m.prototype.__destroy__=m.prototype.__destroy__=function(){Jc(this.ptr)};(function(){function b(){a.ATTRIBUTE_INVALID_TRANSFORM=Kc();a.ATTRIBUTE_NO_TRANSFORM=Lc();a.ATTRIBUTE_QUANTIZATION_TRANSFORM=Mc();a.ATTRIBUTE_OCTAHEDRON_TRANSFORM=Nc();a.INVALID=Oc();a.POSITION=Pc();a.NORMAL=Qc();a.COLOR=Rc();a.TEX_COORD=Sc();a.GENERIC=Tc();a.INVALID_GEOMETRY_TYPE=Uc();a.POINT_CLOUD=Vc();a.TRIANGULAR_MESH=Wc();a.DT_INVALID=Xc();a.DT_INT8=Yc();a.DT_UINT8=Zc();a.DT_INT16=
|
||||
$c();a.DT_UINT16=ad();a.DT_INT32=bd();a.DT_UINT32=cd();a.DT_INT64=dd();a.DT_UINT64=ed();a.DT_FLOAT32=fd();a.DT_FLOAT64=gd();a.DT_BOOL=hd();a.DT_TYPES_COUNT=id();a.OK=jd();a.DRACO_ERROR=kd();a.IO_ERROR=ld();a.INVALID_PARAMETER=md();a.UNSUPPORTED_VERSION=nd();a.UNKNOWN_VERSION=od()}va?b():oa.unshift(b)})();if("function"===typeof a.onModuleParsed)a.onModuleParsed();a.Decoder.prototype.GetEncodedGeometryType=function(b){if(b.__class__&&b.__class__===a.DecoderBuffer)return a.Decoder.prototype.GetEncodedGeometryType_Deprecated(b);
|
||||
if(8>b.byteLength)return a.INVALID_GEOMETRY_TYPE;switch(b[7]){case 0:return a.POINT_CLOUD;case 1:return a.TRIANGULAR_MESH;default:return a.INVALID_GEOMETRY_TYPE}};return n.ready}}();"object"===typeof exports&&"object"===typeof module?module.exports=DracoDecoderModule:"function"===typeof define&&define.amd?define([],function(){return DracoDecoderModule}):"object"===typeof exports&&(exports.DracoDecoderModule=DracoDecoderModule);
|
||||
@@ -0,0 +1,16 @@
|
||||
<!doctype html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Синхронизированная запись</title>
|
||||
<style>
|
||||
html, body, #viewer { width: 100%; height: 100%; margin: 0; overflow: hidden; }
|
||||
canvas { display: block; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="viewer"></div>
|
||||
<script type="module" src="/src/components/rerun/isolatedRerunEntry.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,111 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import { copyFileSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { dirname, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const packageRoot = resolve(root, "node_modules/@rerun-io/web-viewer");
|
||||
const manifest = JSON.parse(readFileSync(resolve(packageRoot, "package.json"), "utf8"));
|
||||
const vendorRoot = resolve(root, "vendor/rerun-web-viewer-0.34.1");
|
||||
|
||||
const sha256File = (path) =>
|
||||
createHash("sha256").update(readFileSync(path)).digest("hex");
|
||||
|
||||
if (manifest.version !== "0.34.1") {
|
||||
throw new Error(
|
||||
`Refusing to patch @rerun-io/web-viewer ${manifest.version}; audit the new vendor lifecycle first.`,
|
||||
);
|
||||
}
|
||||
|
||||
const vendorRuntime = [
|
||||
{
|
||||
label: "wasm runtime",
|
||||
packagePath: resolve(packageRoot, "re_viewer_bg.wasm"),
|
||||
vendorPath: resolve(vendorRoot, "re_viewer_bg.nodedc.wasm"),
|
||||
publishedSha256: "3fe7aab8ea6bb0fd03c3ef694932943411ee174029e398c539c390beb0824d35",
|
||||
previousNodedcSha256: "6aca9da47ad561d2046ad0c7c0b8a7f653f023a77132c5c6304789320db512c2",
|
||||
nodedcSha256: "ffe7543d28bb3394f289f6299de43d038767eef83d781c2b7f8f5683308a0469",
|
||||
},
|
||||
{
|
||||
label: "wasm JavaScript glue",
|
||||
packagePath: resolve(packageRoot, "re_viewer.js"),
|
||||
vendorPath: resolve(vendorRoot, "re_viewer.nodedc.js"),
|
||||
publishedSha256: "7c4ba900820137a6ba23e0f7f57d56e3b007db0de3825d5c8bf0d7684b3cc6a6",
|
||||
nodedcSha256: "0f7b76c9f24cbd8437021b5d37499894aeadc586183e422ebc82ef556d7b8339",
|
||||
},
|
||||
];
|
||||
|
||||
for (const runtime of vendorRuntime) {
|
||||
const vendorSha256 = sha256File(runtime.vendorPath);
|
||||
if (vendorSha256 !== runtime.nodedcSha256) {
|
||||
throw new Error(
|
||||
`Refusing corrupt NODE.DC Rerun ${runtime.label}: ${vendorSha256} != ${runtime.nodedcSha256}`,
|
||||
);
|
||||
}
|
||||
|
||||
const installedSha256 = sha256File(runtime.packagePath);
|
||||
if (
|
||||
![
|
||||
runtime.publishedSha256,
|
||||
runtime.previousNodedcSha256,
|
||||
runtime.nodedcSha256,
|
||||
].includes(installedSha256)
|
||||
) {
|
||||
throw new Error(
|
||||
`Refusing to replace unexpected Rerun ${runtime.label}: ${installedSha256}`,
|
||||
);
|
||||
}
|
||||
|
||||
copyFileSync(runtime.vendorPath, runtime.packagePath);
|
||||
if (sha256File(runtime.packagePath) !== runtime.nodedcSha256) {
|
||||
throw new Error(`NODE.DC Rerun ${runtime.label} verification failed after copy`);
|
||||
}
|
||||
}
|
||||
|
||||
const patches = [
|
||||
{
|
||||
label: "compiled watchdog",
|
||||
path: resolve(packageRoot, "index.js"),
|
||||
before: ` function check_for_panic() {\n if (self.#handle?.has_panicked()) {`,
|
||||
after: ` function check_for_panic() {\n if (self.#state !== "ready" || !self.#handle) {\n return;\n }\n if (self.#handle.has_panicked()) {`,
|
||||
},
|
||||
{
|
||||
label: "source watchdog",
|
||||
path: resolve(packageRoot, "index.ts"),
|
||||
before: ` function check_for_panic() {\n if (self.#handle?.has_panicked()) {`,
|
||||
after: ` function check_for_panic() {\n if (self.#state !== "ready" || !self.#handle) {\n return;\n }\n if (self.#handle.has_panicked()) {`,
|
||||
},
|
||||
{
|
||||
label: "compiled singleton global listener",
|
||||
path: resolve(packageRoot, "index.js"),
|
||||
before: `function setupGlobalEventListeners() {\n window.addEventListener("keyup", (e) => {`,
|
||||
after: `let globalEventListenersInstalled = false;\nfunction setupGlobalEventListeners() {\n if (globalEventListenersInstalled) {\n return;\n }\n globalEventListenersInstalled = true;\n window.addEventListener("keyup", (e) => {`,
|
||||
},
|
||||
{
|
||||
label: "source singleton global listener",
|
||||
path: resolve(packageRoot, "index.ts"),
|
||||
before: `function setupGlobalEventListeners() {\n window.addEventListener("keyup", (e) => {`,
|
||||
after: `let globalEventListenersInstalled = false;\nfunction setupGlobalEventListeners() {\n if (globalEventListenersInstalled) {\n return;\n }\n globalEventListenersInstalled = true;\n window.addEventListener("keyup", (e) => {`,
|
||||
},
|
||||
];
|
||||
|
||||
for (const patch of patches) {
|
||||
const source = readFileSync(patch.path, "utf8");
|
||||
if (source.includes(patch.after)) continue;
|
||||
if (!source.includes(patch.before)) {
|
||||
throw new Error(`Vendor ${patch.label} patch no longer matches ${patch.path}.`);
|
||||
}
|
||||
writeFileSync(patch.path, source.replace(patch.before, patch.after));
|
||||
}
|
||||
|
||||
for (const path of [resolve(packageRoot, "index.js"), resolve(packageRoot, "index.ts")]) {
|
||||
const source = readFileSync(path, "utf8");
|
||||
const keyupListenerCount = source.split('window.addEventListener("keyup"').length - 1;
|
||||
if (!source.includes("let globalEventListenersInstalled = false;") || keyupListenerCount !== 1) {
|
||||
throw new Error(`Vendor singleton global listener verification failed for ${path}.`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(
|
||||
"Patched @rerun-io/web-viewer 0.34.1 pointer navigation, camera continuity, watchdog teardown, and singleton global keyup listener.",
|
||||
);
|
||||
@@ -0,0 +1,128 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import { readFileSync, writeFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
|
||||
const [inputArgument, outputArgument] = process.argv.slice(2);
|
||||
if (!inputArgument || !outputArgument) {
|
||||
throw new Error("Usage: node transform-rerun-web-viewer-glue.mjs INPUT OUTPUT");
|
||||
}
|
||||
|
||||
const inputPath = resolve(inputArgument);
|
||||
const outputPath = resolve(outputArgument);
|
||||
const expectedGeneratedSha256 =
|
||||
"cc196a93c5be972c801d46be4dc9934f7f042eb62941f0aa0678f1c8416c6874";
|
||||
|
||||
const sha256 = (value) => createHash("sha256").update(value).digest("hex");
|
||||
let code = readFileSync(inputPath, "utf8");
|
||||
|
||||
if (sha256(code) !== expectedGeneratedSha256) {
|
||||
throw new Error(`Refusing to transform unexpected wasm-bindgen output: ${inputPath}`);
|
||||
}
|
||||
|
||||
// This is the same no-modules-base transformation used by Rerun 0.34.1's
|
||||
// rerun_js/web-viewer/build-wasm.mjs. Each factory call gets isolated closure state.
|
||||
const wrapperStart = "let wasm_bindgen = (function(exports) {";
|
||||
const wrapperEnd = `return Object.assign(__wbg_init, { initSync }, exports);
|
||||
})({ __proto__: null });`;
|
||||
|
||||
if (!code.includes(wrapperStart) || !code.includes(wrapperEnd)) {
|
||||
throw new Error("Rerun wasm-bindgen wrapper markers no longer match");
|
||||
}
|
||||
code = code.replace(wrapperStart, "").replace(wrapperEnd, "");
|
||||
|
||||
code = `
|
||||
export default function() {
|
||||
const exports = { __proto__: null };
|
||||
${code}
|
||||
|
||||
function deinit() {
|
||||
__wbg_init.__wbindgen_wasm_module = null;
|
||||
wasmModule = null;
|
||||
wasm = null;
|
||||
cachedDataViewMemory0 = null;
|
||||
cachedFloat32ArrayMemory0 = null;
|
||||
cachedInt16ArrayMemory0 = null;
|
||||
cachedInt32ArrayMemory0 = null;
|
||||
cachedInt8ArrayMemory0 = null;
|
||||
cachedUint16ArrayMemory0 = null;
|
||||
cachedUint32ArrayMemory0 = null;
|
||||
cachedUint8ArrayMemory0 = null;
|
||||
}
|
||||
|
||||
return Object.assign(__wbg_init, { initSync, deinit }, exports);
|
||||
}
|
||||
`;
|
||||
|
||||
const closureDtorsOriginal = `const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined')
|
||||
? { register: () => {}, unregister: () => {} }
|
||||
: new FinalizationRegistry(state => wasm.__wbindgen_destroy_closure(state.a, state.b));`;
|
||||
const closureDtorsPatch = `const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined')
|
||||
? { register: () => {}, unregister: () => {} }
|
||||
: new FinalizationRegistry(state => {
|
||||
if (wasm) wasm.__wbindgen_destroy_closure(state.a, state.b);
|
||||
});`;
|
||||
|
||||
if (!code.includes(closureDtorsOriginal)) {
|
||||
throw new Error("Rerun CLOSURE_DTORS block no longer matches");
|
||||
}
|
||||
code = code.replace(closureDtorsOriginal, closureDtorsPatch);
|
||||
|
||||
const makeMutClosureOriginal = `function makeMutClosure(arg0, arg1, f) {
|
||||
const state = { a: arg0, b: arg1, cnt: 1 };
|
||||
const real = (...args) => {
|
||||
|
||||
// First up with a closure we increment the internal reference
|
||||
// count. This ensures that the Rust closure environment won't
|
||||
// be deallocated while we're invoking it.
|
||||
state.cnt++;
|
||||
const a = state.a;
|
||||
state.a = 0;
|
||||
try {
|
||||
return f(a, state.b, ...args);
|
||||
} finally {
|
||||
state.a = a;
|
||||
real._wbg_cb_unref();
|
||||
}
|
||||
};
|
||||
real._wbg_cb_unref = () => {
|
||||
if (--state.cnt === 0) {
|
||||
wasm.__wbindgen_destroy_closure(state.a, state.b);
|
||||
state.a = 0;
|
||||
CLOSURE_DTORS.unregister(state);
|
||||
}
|
||||
};
|
||||
CLOSURE_DTORS.register(real, state, state);
|
||||
return real;
|
||||
}`;
|
||||
const makeMutClosurePatch = `function makeMutClosure(arg0, arg1, f) {
|
||||
const state = { a: arg0, b: arg1, cnt: 1 };
|
||||
const real = (...args) => {
|
||||
state.cnt++;
|
||||
const a = state.a;
|
||||
state.a = 0;
|
||||
try {
|
||||
if (!wasm) return;
|
||||
return f(a, state.b, ...args);
|
||||
} finally {
|
||||
state.a = a;
|
||||
real._wbg_cb_unref();
|
||||
}
|
||||
};
|
||||
real._wbg_cb_unref = () => {
|
||||
if (--state.cnt === 0) {
|
||||
if (wasm) wasm.__wbindgen_destroy_closure(state.a, state.b);
|
||||
state.a = 0;
|
||||
CLOSURE_DTORS.unregister(state);
|
||||
}
|
||||
};
|
||||
CLOSURE_DTORS.register(real, state, state);
|
||||
return real;
|
||||
}`;
|
||||
|
||||
if (!code.includes(makeMutClosureOriginal)) {
|
||||
throw new Error("Rerun makeMutClosure block no longer matches");
|
||||
}
|
||||
code = code.replace(makeMutClosureOriginal, makeMutClosurePatch);
|
||||
|
||||
writeFileSync(outputPath, code);
|
||||
console.log(`Transformed Rerun wasm glue: ${sha256(code)} ${outputPath}`);
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,92 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
|
||||
import type {
|
||||
EnvironmentBackground,
|
||||
EnvironmentMediaItem,
|
||||
} from "../core/environment/environmentSettings";
|
||||
|
||||
type ReadyEnvironmentMediaItem = EnvironmentMediaItem & {
|
||||
url: string;
|
||||
mediaKind: "image" | "video";
|
||||
};
|
||||
|
||||
function isReadyMediaItem(
|
||||
item: EnvironmentMediaItem,
|
||||
): item is ReadyEnvironmentMediaItem {
|
||||
return Boolean(item.url && item.mediaKind);
|
||||
}
|
||||
|
||||
export function EnvironmentBackgroundMedia({
|
||||
background,
|
||||
}: {
|
||||
background: EnvironmentBackground;
|
||||
}) {
|
||||
const items = useMemo(
|
||||
() => background.items.filter(isReadyMediaItem),
|
||||
[background.items],
|
||||
);
|
||||
const playlistIdentity = items.map((item) => `${item.id}:${item.url}`).join("|");
|
||||
const [activeIndex, setActiveIndex] = useState(0);
|
||||
const [failedIds, setFailedIds] = useState<Set<string>>(new Set());
|
||||
const playableItems = items.filter((item) => !failedIds.has(item.id));
|
||||
const activeItem = playableItems[activeIndex] ?? playableItems[0] ?? null;
|
||||
|
||||
useEffect(() => {
|
||||
setActiveIndex(0);
|
||||
setFailedIds(new Set());
|
||||
}, [playlistIdentity]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!background.enabled
|
||||
|| !activeItem
|
||||
|| activeItem.mediaKind !== "image"
|
||||
|| playableItems.length < 2
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const timer = window.setTimeout(() => {
|
||||
setActiveIndex((current) => (current + 1) % playableItems.length);
|
||||
}, background.imageDurationSeconds * 1_000);
|
||||
return () => window.clearTimeout(timer);
|
||||
}, [
|
||||
activeItem,
|
||||
background.enabled,
|
||||
background.imageDurationSeconds,
|
||||
playableItems.length,
|
||||
]);
|
||||
|
||||
if (!background.enabled || !activeItem) return null;
|
||||
|
||||
return (
|
||||
<div className="landing-stage__media" aria-hidden="true">
|
||||
{activeItem.mediaKind === "video" ? (
|
||||
<video
|
||||
key={activeItem.url}
|
||||
src={activeItem.url}
|
||||
autoPlay
|
||||
muted
|
||||
loop={playableItems.length === 1}
|
||||
playsInline
|
||||
onEnded={() => setActiveIndex((current) => (
|
||||
(current + 1) % playableItems.length
|
||||
))}
|
||||
onError={() => {
|
||||
setFailedIds((current) => new Set(current).add(activeItem.id));
|
||||
setActiveIndex(0);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<img
|
||||
key={activeItem.url}
|
||||
src={activeItem.url}
|
||||
alt=""
|
||||
onError={() => {
|
||||
setFailedIds((current) => new Set(current).add(activeItem.id));
|
||||
setActiveIndex(0);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import {
|
||||
Icon,
|
||||
IconButton,
|
||||
MediaSourceField,
|
||||
RangeControl,
|
||||
SortableList,
|
||||
} from "@nodedc/ui-react";
|
||||
|
||||
import {
|
||||
appendEnvironmentMediaItem,
|
||||
inferEnvironmentMediaKind,
|
||||
maxEnvironmentMediaItems,
|
||||
removeEnvironmentMediaItem,
|
||||
type EnvironmentBackground,
|
||||
type EnvironmentMediaItem,
|
||||
type EnvironmentSurfaceId,
|
||||
type UploadedEnvironmentMedia,
|
||||
} from "../core/environment/environmentSettings";
|
||||
|
||||
interface EnvironmentMediaPlaylistEditorProps {
|
||||
surfaceId: EnvironmentSurfaceId;
|
||||
background: EnvironmentBackground;
|
||||
disabled: boolean;
|
||||
error: string | null;
|
||||
onChange: (background: EnvironmentBackground) => void;
|
||||
onBusyChange: (busy: boolean) => void;
|
||||
onUpload: (
|
||||
surfaceId: EnvironmentSurfaceId,
|
||||
itemId: string,
|
||||
file: File,
|
||||
) => Promise<UploadedEnvironmentMedia>;
|
||||
}
|
||||
|
||||
const acceptedEnvironmentMedia = [
|
||||
"image/png",
|
||||
"image/jpeg",
|
||||
"image/gif",
|
||||
"image/webp",
|
||||
"image/avif",
|
||||
"video/mp4",
|
||||
"video/webm",
|
||||
"video/quicktime",
|
||||
".png",
|
||||
".jpg",
|
||||
".jpeg",
|
||||
".gif",
|
||||
".webp",
|
||||
".avif",
|
||||
".mp4",
|
||||
".webm",
|
||||
".mov",
|
||||
].join(",");
|
||||
|
||||
function patchItem(
|
||||
background: EnvironmentBackground,
|
||||
itemId: string,
|
||||
patch: Partial<EnvironmentMediaItem>,
|
||||
): EnvironmentBackground {
|
||||
return {
|
||||
...background,
|
||||
items: background.items.map((item) => (
|
||||
item.id === itemId ? { ...item, ...patch } : item
|
||||
)),
|
||||
};
|
||||
}
|
||||
|
||||
export function EnvironmentMediaPlaylistEditor({
|
||||
surfaceId,
|
||||
background,
|
||||
disabled,
|
||||
error,
|
||||
onChange,
|
||||
onBusyChange,
|
||||
onUpload,
|
||||
}: EnvironmentMediaPlaylistEditorProps) {
|
||||
const [uploadingIds, setUploadingIds] = useState<Set<string>>(new Set());
|
||||
const [itemErrors, setItemErrors] = useState<Record<string, string>>({});
|
||||
const backgroundRef = useRef(background);
|
||||
backgroundRef.current = background;
|
||||
const displayedItems = useMemo(
|
||||
() => [...background.items].reverse(),
|
||||
[background.items],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
onBusyChange(uploadingIds.size > 0);
|
||||
}, [onBusyChange, uploadingIds.size]);
|
||||
|
||||
useEffect(() => () => onBusyChange(false), [onBusyChange]);
|
||||
|
||||
const setItemError = (itemId: string, message?: string) => {
|
||||
setItemErrors((current) => {
|
||||
const next = { ...current };
|
||||
if (message) next[itemId] = message;
|
||||
else delete next[itemId];
|
||||
return next;
|
||||
});
|
||||
};
|
||||
|
||||
const uploadFile = async (itemId: string, file?: File) => {
|
||||
if (!file) return;
|
||||
setUploadingIds((current) => new Set(current).add(itemId));
|
||||
setItemError(itemId);
|
||||
try {
|
||||
const uploaded = await onUpload(surfaceId, itemId, file);
|
||||
onChange(patchItem(backgroundRef.current, itemId, {
|
||||
source: "file",
|
||||
url: uploaded.url,
|
||||
mediaKind: uploaded.mediaKind,
|
||||
fileName: uploaded.fileName,
|
||||
}));
|
||||
} catch (reason) {
|
||||
setItemError(
|
||||
itemId,
|
||||
reason instanceof Error
|
||||
? reason.message
|
||||
: "Не удалось загрузить медиаконтент.",
|
||||
);
|
||||
} finally {
|
||||
setUploadingIds((current) => {
|
||||
const next = new Set(current);
|
||||
next.delete(itemId);
|
||||
return next;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="environment-media-playlist">
|
||||
<div className="environment-media-playlist__head">
|
||||
<div>
|
||||
<span>Видео / картинка</span>
|
||||
<p>MP4, WebM, MOV, PNG, JPEG, GIF, WebP или AVIF · до 256 МБ.</p>
|
||||
</div>
|
||||
<IconButton
|
||||
label="Добавить медиаконтент"
|
||||
disabled={disabled || background.items.length >= maxEnvironmentMediaItems}
|
||||
onClick={() => onChange(appendEnvironmentMediaItem(background))}
|
||||
>
|
||||
<Icon name="plus" />
|
||||
</IconButton>
|
||||
</div>
|
||||
|
||||
{displayedItems.length ? (
|
||||
<SortableList
|
||||
items={displayedItems}
|
||||
getId={(item) => item.id}
|
||||
className="environment-media-playlist__items"
|
||||
onReorder={(items) => onChange({
|
||||
...background,
|
||||
items: [...items].reverse(),
|
||||
})}
|
||||
>
|
||||
{(item, { handle }) => {
|
||||
const playbackIndex = background.items.findIndex(
|
||||
(candidate) => candidate.id === item.id,
|
||||
);
|
||||
return (
|
||||
<div className="environment-media-playlist__item">
|
||||
<MediaSourceField
|
||||
label={`Медиаконтент ${String(playbackIndex + 1).padStart(2, "0")}`}
|
||||
kindLabel={item.mediaKind ?? "media"}
|
||||
source={item.source}
|
||||
url={item.url ?? ""}
|
||||
fileName={item.fileName}
|
||||
uploading={uploadingIds.has(item.id)}
|
||||
previewSrc={item.url}
|
||||
previewKind={item.mediaKind}
|
||||
accept={acceptedEnvironmentMedia}
|
||||
path={`${surfaceId}.background.items[${playbackIndex}] → server environment media`}
|
||||
hint="Файл сохраняется в Mission Core data root. URL должен вести прямо на media по HTTP(S)."
|
||||
error={itemErrors[item.id] ?? (
|
||||
playbackIndex === background.items.length - 1 ? error : null
|
||||
)}
|
||||
onSourceChange={(source) => {
|
||||
if (source === item.source) return;
|
||||
setItemError(item.id);
|
||||
onChange(patchItem(background, item.id, {
|
||||
source,
|
||||
url: null,
|
||||
mediaKind: null,
|
||||
fileName: null,
|
||||
}));
|
||||
}}
|
||||
onUrlChange={(url) => {
|
||||
setItemError(item.id);
|
||||
onChange(patchItem(background, item.id, {
|
||||
source: "url",
|
||||
url: url || null,
|
||||
mediaKind: url ? inferEnvironmentMediaKind(url) : null,
|
||||
fileName: null,
|
||||
}));
|
||||
}}
|
||||
onFileChange={(file) => void uploadFile(item.id, file)}
|
||||
/>
|
||||
<div className="environment-media-playlist__item-actions">
|
||||
<IconButton
|
||||
label={`Удалить медиаконтент ${playbackIndex + 1}`}
|
||||
disabled={disabled || uploadingIds.has(item.id)}
|
||||
onClick={() => {
|
||||
setItemError(item.id);
|
||||
onChange(removeEnvironmentMediaItem(background, item.id));
|
||||
}}
|
||||
>
|
||||
<Icon name="trash" />
|
||||
</IconButton>
|
||||
{handle}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</SortableList>
|
||||
) : (
|
||||
<>
|
||||
<p className="environment-media-playlist__empty">
|
||||
Добавьте первый файл или прямую ссылку на медиаконтент.
|
||||
</p>
|
||||
{error ? (
|
||||
<p className="environment-media-playlist__error" role="alert">
|
||||
{error}
|
||||
</p>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="environment-media-playlist__timing">
|
||||
<RangeControl
|
||||
label="Показывать изображение"
|
||||
value={background.imageDurationSeconds}
|
||||
min={1}
|
||||
max={60}
|
||||
step={1}
|
||||
disabled={disabled}
|
||||
formatValue={(value) => `${value} с`}
|
||||
onChange={(imageDurationSeconds) => onChange({
|
||||
...background,
|
||||
imageDurationSeconds,
|
||||
})}
|
||||
/>
|
||||
<span>
|
||||
Новые элементы появляются сверху. Воспроизведение начинается снизу;
|
||||
перетаскивание меняет порядок.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,358 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import {
|
||||
Button,
|
||||
FeatureSettingsWindow,
|
||||
Select,
|
||||
SettingsCard,
|
||||
Switch,
|
||||
TextAreaField,
|
||||
TextField,
|
||||
WindowFooterActions,
|
||||
} from "@nodedc/ui-react";
|
||||
|
||||
import {
|
||||
cloneEnvironmentSettings,
|
||||
type EnvironmentBackground,
|
||||
type EnvironmentPage,
|
||||
type EnvironmentSettings,
|
||||
type EnvironmentSurfaceId,
|
||||
type UploadedEnvironmentMedia,
|
||||
} from "../core/environment/environmentSettings";
|
||||
import { EnvironmentMediaPlaylistEditor } from "./EnvironmentMediaPlaylistEditor";
|
||||
import {
|
||||
roots,
|
||||
workspaces,
|
||||
workspacesForRoot,
|
||||
} from "../productModel";
|
||||
|
||||
interface EnvironmentSettingsWindowProps {
|
||||
open: boolean;
|
||||
settings: EnvironmentSettings;
|
||||
state: "loading" | "ready" | "saving" | "error";
|
||||
error: string | null;
|
||||
onClose: () => void;
|
||||
onSave: (settings: EnvironmentSettings) => Promise<EnvironmentSettings>;
|
||||
onUpload: (
|
||||
surfaceId: EnvironmentSurfaceId,
|
||||
itemId: string,
|
||||
file: File,
|
||||
) => Promise<UploadedEnvironmentMedia>;
|
||||
}
|
||||
|
||||
function patchPage(
|
||||
draft: EnvironmentSettings,
|
||||
surfaceId: EnvironmentSurfaceId,
|
||||
patch: Partial<EnvironmentPage>,
|
||||
): EnvironmentSettings {
|
||||
return {
|
||||
...draft,
|
||||
pages: {
|
||||
...draft.pages,
|
||||
[surfaceId]: {
|
||||
...draft.pages[surfaceId],
|
||||
...patch,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function patchBackground(
|
||||
draft: EnvironmentSettings,
|
||||
surfaceId: EnvironmentSurfaceId,
|
||||
patch: Partial<EnvironmentBackground>,
|
||||
): EnvironmentSettings {
|
||||
const page = draft.pages[surfaceId];
|
||||
return patchPage(draft, surfaceId, {
|
||||
background: {
|
||||
...page.background,
|
||||
...patch,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function EnvironmentSettingsWindow({
|
||||
open,
|
||||
settings,
|
||||
state,
|
||||
error,
|
||||
onClose,
|
||||
onSave,
|
||||
onUpload,
|
||||
}: EnvironmentSettingsWindowProps) {
|
||||
const [draft, setDraft] = useState(() => cloneEnvironmentSettings(settings));
|
||||
const [surfaceId, setSurfaceId] = useState<EnvironmentSurfaceId>("fleet");
|
||||
const [uploading, setUploading] = useState(false);
|
||||
const [localError, setLocalError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
setDraft(cloneEnvironmentSettings(settings));
|
||||
setLocalError(null);
|
||||
}, [open, settings]);
|
||||
|
||||
const selectedPage = draft.pages[surfaceId];
|
||||
const selectedBackground = selectedPage.background;
|
||||
const pageOptions = useMemo(() => [
|
||||
{
|
||||
value: "home" as const,
|
||||
label: draft.pages.home.headerLabel,
|
||||
description: "Главная страница продукта",
|
||||
},
|
||||
...roots.map((root) => ({
|
||||
value: root.id,
|
||||
label: draft.pages[root.id].headerLabel,
|
||||
description: `Стартовая страница раздела «${draft.pages[root.id].title}»`,
|
||||
})),
|
||||
], [draft.pages]);
|
||||
const quickActionWorkspaces = useMemo(
|
||||
() => surfaceId === "home"
|
||||
? workspaces.filter((workspace) => !workspace.internalOnly)
|
||||
: workspacesForRoot(surfaceId),
|
||||
[surfaceId],
|
||||
);
|
||||
const quickActionOptions = useMemo(() => [
|
||||
{
|
||||
value: "none",
|
||||
label: "Не показывать",
|
||||
description: "Кнопка скрыта на стартовом экране",
|
||||
},
|
||||
...quickActionWorkspaces.map((workspace) => ({
|
||||
value: workspace.id,
|
||||
label: workspace.label,
|
||||
description: workspace.description,
|
||||
})),
|
||||
], [quickActionWorkspaces]);
|
||||
const dirty = useMemo(
|
||||
() => JSON.stringify(draft) !== JSON.stringify(settings),
|
||||
[draft, settings],
|
||||
);
|
||||
const busy = state === "saving" || uploading;
|
||||
|
||||
const updatePage = (patch: Partial<EnvironmentPage>) => {
|
||||
setDraft((current) => patchPage(current, surfaceId, patch));
|
||||
};
|
||||
|
||||
const save = async () => {
|
||||
const invalid = Object.entries(draft.pages).find(([, page]) => (
|
||||
!page.headerLabel.trim()
|
||||
|| !page.eyebrow.trim()
|
||||
|| !page.title.trim()
|
||||
|| !page.description.trim()
|
||||
));
|
||||
if (invalid) {
|
||||
setLocalError("Название, надзаголовок, заголовок и описание не могут быть пустыми.");
|
||||
return;
|
||||
}
|
||||
if (
|
||||
selectedPage.primaryWorkspaceId
|
||||
&& selectedPage.primaryWorkspaceId === selectedPage.secondaryWorkspaceId
|
||||
) {
|
||||
setLocalError("Быстрые кнопки должны вести на разные рабочие поверхности.");
|
||||
return;
|
||||
}
|
||||
const invalidMedia = Object.entries(draft.pages).find(([, page]) => (
|
||||
(page.background.enabled && !page.background.items.length)
|
||||
|| page.background.items.some((item) => {
|
||||
if (!item.url || !item.mediaKind) return true;
|
||||
if (item.source !== "url") return false;
|
||||
try {
|
||||
const parsed = new URL(item.url);
|
||||
return !["http:", "https:"].includes(parsed.protocol);
|
||||
} catch {
|
||||
return true;
|
||||
}
|
||||
})
|
||||
));
|
||||
if (invalidMedia) {
|
||||
setSurfaceId(invalidMedia[0] as EnvironmentSurfaceId);
|
||||
setLocalError(
|
||||
"Каждый элемент фона должен содержать загруженный файл или прямой HTTP(S) URL.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
setLocalError(null);
|
||||
try {
|
||||
await onSave({
|
||||
...draft,
|
||||
pages: Object.fromEntries(
|
||||
Object.entries(draft.pages).map(([id, page]) => [id, {
|
||||
...page,
|
||||
headerLabel: page.headerLabel.trim(),
|
||||
eyebrow: page.eyebrow.trim(),
|
||||
title: page.title.trim(),
|
||||
description: page.description.trim(),
|
||||
}]),
|
||||
) as EnvironmentSettings["pages"],
|
||||
});
|
||||
onClose();
|
||||
} catch (reason) {
|
||||
setLocalError(reason instanceof Error
|
||||
? reason.message
|
||||
: "Не удалось сохранить настройки окружения.");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<FeatureSettingsWindow
|
||||
open={open}
|
||||
title="Настройки Mission Core"
|
||||
subtitle="Локальное операторское окружение"
|
||||
identity={{
|
||||
title: "DC",
|
||||
subtitle: "Mission Core",
|
||||
avatarLabel: "DC",
|
||||
}}
|
||||
sections={[
|
||||
{
|
||||
id: "environment",
|
||||
label: "Окружение",
|
||||
group: "MISSION CORE",
|
||||
icon: "settings",
|
||||
},
|
||||
]}
|
||||
activeSection="environment"
|
||||
onSectionChange={() => undefined}
|
||||
onClose={onClose}
|
||||
footer={(
|
||||
<WindowFooterActions>
|
||||
<Button
|
||||
variant="secondary"
|
||||
disabled={!dirty || busy}
|
||||
onClick={() => {
|
||||
setDraft(cloneEnvironmentSettings(settings));
|
||||
setLocalError(null);
|
||||
}}
|
||||
>
|
||||
Сбросить изменения
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
disabled={!dirty || busy}
|
||||
onClick={() => void save()}
|
||||
>
|
||||
{state === "saving" ? "Сохраняем…" : "Сохранить"}
|
||||
</Button>
|
||||
</WindowFooterActions>
|
||||
)}
|
||||
>
|
||||
<div className="environment-settings">
|
||||
<SettingsCard
|
||||
eyebrow="ОКРУЖЕНИЕ"
|
||||
title="Основные элементы управления"
|
||||
description="Выберите страницу и настройте её название в шапке, содержание стартового экрана, подложку и быстрые переходы."
|
||||
actions={(
|
||||
<Switch
|
||||
checked={selectedBackground.enabled}
|
||||
label="Показывать фон"
|
||||
onChange={(enabled) => {
|
||||
if (enabled && !selectedBackground.items.length) {
|
||||
setLocalError("Сначала добавьте медиаконтент.");
|
||||
return;
|
||||
}
|
||||
setLocalError(null);
|
||||
setDraft((current) =>
|
||||
patchBackground(current, surfaceId, { enabled }));
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
>
|
||||
<div className="environment-settings__editor">
|
||||
<div className="environment-settings__surface">
|
||||
<span>Страница</span>
|
||||
<Select
|
||||
label="Выбрать страницу окружения"
|
||||
value={surfaceId}
|
||||
options={pageOptions}
|
||||
variant="split"
|
||||
menuWidth="anchor"
|
||||
onChange={(value) => {
|
||||
setSurfaceId(value);
|
||||
setLocalError(null);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="environment-settings__copy">
|
||||
<TextField
|
||||
label={surfaceId === "home" ? "Название продукта" : "Название в шапке"}
|
||||
value={selectedPage.headerLabel}
|
||||
maxLength={40}
|
||||
onChange={(event) => updatePage({
|
||||
headerLabel: event.currentTarget.value,
|
||||
})}
|
||||
/>
|
||||
<TextField
|
||||
label="Надзаголовок"
|
||||
value={selectedPage.eyebrow}
|
||||
maxLength={80}
|
||||
onChange={(event) => updatePage({
|
||||
eyebrow: event.currentTarget.value,
|
||||
})}
|
||||
/>
|
||||
<TextField
|
||||
label="Основной заголовок"
|
||||
value={selectedPage.title}
|
||||
maxLength={120}
|
||||
onChange={(event) => updatePage({
|
||||
title: event.currentTarget.value,
|
||||
})}
|
||||
/>
|
||||
<TextAreaField
|
||||
label="Описание"
|
||||
value={selectedPage.description}
|
||||
maxLength={500}
|
||||
rows={3}
|
||||
onChange={(event) => updatePage({
|
||||
description: event.currentTarget.value,
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="environment-settings__quick-actions">
|
||||
<div>
|
||||
<span>Кнопка 1</span>
|
||||
<Select
|
||||
label="Выбрать первую быструю кнопку"
|
||||
value={selectedPage.primaryWorkspaceId ?? "none"}
|
||||
options={quickActionOptions}
|
||||
variant="split"
|
||||
menuWidth="anchor"
|
||||
onChange={(value) => updatePage({
|
||||
primaryWorkspaceId: value === "none" ? null : value,
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<span>Кнопка 2</span>
|
||||
<Select
|
||||
label="Выбрать вторую быструю кнопку"
|
||||
value={selectedPage.secondaryWorkspaceId ?? "none"}
|
||||
options={quickActionOptions}
|
||||
variant="split"
|
||||
menuWidth="anchor"
|
||||
onChange={(value) => updatePage({
|
||||
secondaryWorkspaceId: value === "none" ? null : value,
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<EnvironmentMediaPlaylistEditor
|
||||
surfaceId={surfaceId}
|
||||
background={selectedBackground}
|
||||
disabled={busy}
|
||||
error={localError ?? error}
|
||||
onBusyChange={setUploading}
|
||||
onChange={(background) => {
|
||||
setLocalError(null);
|
||||
setDraft((current) =>
|
||||
patchBackground(current, surfaceId, background));
|
||||
}}
|
||||
onUpload={onUpload}
|
||||
/>
|
||||
</div>
|
||||
</SettingsCard>
|
||||
</div>
|
||||
</FeatureSettingsWindow>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
import { useLayoutEffect, useState, type RefObject } from "react";
|
||||
import { WorkspaceWindow } from "@nodedc/ui-react";
|
||||
|
||||
import type { ObservationWindowRect } from "../core/observation/useObservationLayout";
|
||||
import type { ObservationSourceDescriptor } from "../core/runtime/contracts";
|
||||
import { ObservationMedia, observationSourceStatusLabel } from "./ObservationSources";
|
||||
import type { RecordedObservationPlayback } from "./RecordedFmp4Player";
|
||||
import type {
|
||||
RecordedAdmissionPhase,
|
||||
RecordedCameraAdmissionState,
|
||||
} from "../core/observation/recordedSessionAdmission";
|
||||
|
||||
const WINDOW_WIDTH = 336;
|
||||
const WINDOW_HEIGHT = 210;
|
||||
const WINDOW_GAP = 16;
|
||||
const WINDOW_INSET = 18;
|
||||
const TIMELINE_CLEARANCE = 64;
|
||||
|
||||
type ClosestTarget = { closest: (selectors: string) => unknown };
|
||||
|
||||
export function shouldCaptureWorkspacePointer(
|
||||
button: number,
|
||||
target: ClosestTarget | null,
|
||||
): boolean {
|
||||
if (button !== 0 || !target) return false;
|
||||
if (target.closest(".nodedc-workspace-window__resize")) return true;
|
||||
if (!target.closest(".nodedc-workspace-window__head")) return false;
|
||||
return !target.closest("button, input, select, textarea, a");
|
||||
}
|
||||
|
||||
export function initialObservationWindowRect(
|
||||
index: number,
|
||||
count = 1,
|
||||
bounds: { width: number; height: number } = { width: 1280, height: 720 },
|
||||
): ObservationWindowRect {
|
||||
const availableWidth = Math.max(0, bounds.width - WINDOW_INSET * 2);
|
||||
const width = Math.max(1, Math.min(WINDOW_WIDTH, availableWidth || WINDOW_WIDTH));
|
||||
const availableHeight = Math.max(0, bounds.height - WINDOW_INSET - TIMELINE_CLEARANCE);
|
||||
const maxColumns = Math.max(
|
||||
1,
|
||||
Math.floor((availableWidth + WINDOW_GAP) / (width + WINDOW_GAP)),
|
||||
);
|
||||
const columns = Math.max(1, Math.min(Math.max(1, count), maxColumns));
|
||||
const rows = Math.max(1, Math.ceil(Math.max(1, count) / columns));
|
||||
const rowHeight = Math.max(
|
||||
1,
|
||||
(availableHeight - Math.max(0, rows - 1) * WINDOW_GAP) / rows,
|
||||
);
|
||||
const height = Math.max(1, Math.min(WINDOW_HEIGHT, rowHeight));
|
||||
const row = Math.floor(index / columns);
|
||||
const column = index % columns;
|
||||
const rowItemCount = Math.min(columns, Math.max(1, count - row * columns));
|
||||
const rowWidth = rowItemCount * width + Math.max(0, rowItemCount - 1) * WINDOW_GAP;
|
||||
const rowStart = Math.max(0, bounds.width - WINDOW_INSET - rowWidth);
|
||||
|
||||
return {
|
||||
x: rowStart + Math.min(column, rowItemCount - 1) * (width + WINDOW_GAP),
|
||||
y: Math.max(
|
||||
0,
|
||||
bounds.height - TIMELINE_CLEARANCE - height - row * (height + WINDOW_GAP),
|
||||
),
|
||||
width,
|
||||
height,
|
||||
};
|
||||
}
|
||||
|
||||
export function FloatingObservationWindow({
|
||||
source,
|
||||
index,
|
||||
count,
|
||||
boundsRef,
|
||||
rect,
|
||||
maximized,
|
||||
active,
|
||||
hidden = false,
|
||||
onRectChange,
|
||||
onMaximizedChange,
|
||||
onActivate,
|
||||
onClose,
|
||||
playback,
|
||||
prepareRecorded,
|
||||
recordedSessionGate,
|
||||
recordedAdmissionKey,
|
||||
onRecordedAdmissionChange,
|
||||
}: {
|
||||
source: ObservationSourceDescriptor;
|
||||
index: number;
|
||||
count: number;
|
||||
boundsRef: RefObject<HTMLElement | null>;
|
||||
rect?: ObservationWindowRect;
|
||||
maximized: boolean;
|
||||
active: boolean;
|
||||
hidden?: boolean;
|
||||
onRectChange: (rect: ObservationWindowRect) => void;
|
||||
onMaximizedChange: (maximized: boolean) => void;
|
||||
onActivate: () => void;
|
||||
onClose: () => void;
|
||||
playback?: RecordedObservationPlayback | null;
|
||||
prepareRecorded?: boolean;
|
||||
recordedSessionGate?: RecordedAdmissionPhase;
|
||||
recordedAdmissionKey?: string | null;
|
||||
onRecordedAdmissionChange?: (
|
||||
sourceId: string,
|
||||
state: RecordedCameraAdmissionState,
|
||||
) => void;
|
||||
}) {
|
||||
const [bounds, setBounds] = useState<{ width: number; height: number } | null>(null);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const element = boundsRef.current;
|
||||
if (!element) return;
|
||||
|
||||
const measure = () => {
|
||||
const next = { width: element.clientWidth, height: element.clientHeight };
|
||||
setBounds((current) => (
|
||||
current?.width === next.width && current.height === next.height ? current : next
|
||||
));
|
||||
};
|
||||
|
||||
measure();
|
||||
const observer = new ResizeObserver(measure);
|
||||
observer.observe(element);
|
||||
return () => observer.disconnect();
|
||||
}, [boundsRef]);
|
||||
|
||||
// Camera media admission must not wait for the viewport observer. A source
|
||||
// can arrive in the same commit as a workspace transition; mounting it with
|
||||
// the canonical fallback geometry keeps the browser transport alive until
|
||||
// the real bounds are measured immediately afterward.
|
||||
const initialRect = initialObservationWindowRect(index, count, bounds ?? undefined);
|
||||
const windowRect = rect ?? initialRect;
|
||||
|
||||
return (
|
||||
<WorkspaceWindow
|
||||
boundsRef={boundsRef}
|
||||
rect={windowRect}
|
||||
onRectChange={onRectChange}
|
||||
maximized={maximized}
|
||||
onMaximizedChange={onMaximizedChange}
|
||||
onActivate={onActivate}
|
||||
onClose={onClose}
|
||||
title={source.label}
|
||||
subtitle={source.description}
|
||||
status={(
|
||||
<span className="floating-observation-window__status">
|
||||
<i data-availability={source.availability} aria-hidden="true" />
|
||||
{observationSourceStatusLabel(source)}
|
||||
</span>
|
||||
)}
|
||||
footer={(
|
||||
<span className="floating-observation-window__footer">
|
||||
<span>{source.endpointLabel || source.transport}</span>
|
||||
<span>{source.capabilities.timelineMode === "live-only" ? "Эфир без буфера" : "Временная шкала"}</span>
|
||||
</span>
|
||||
)}
|
||||
minWidth={Math.min(280, windowRect.width)}
|
||||
minHeight={Math.min(190, windowRect.height)}
|
||||
resizable={source.capabilities.resizable}
|
||||
active={active}
|
||||
zIndex={maximized ? 15 : active ? 9 : 7}
|
||||
className={`floating-observation-window${hidden ? " floating-observation-window--hidden" : ""}`}
|
||||
onPointerDownCapture={(event) => {
|
||||
if (!shouldCaptureWorkspacePointer(event.button, event.target as HTMLElement)) return;
|
||||
try {
|
||||
event.currentTarget.setPointerCapture(event.pointerId);
|
||||
} catch {
|
||||
// Pointer capture can fail if the browser ended the pointer between
|
||||
// dispatch and capture. The donor's window listeners remain fallback.
|
||||
}
|
||||
}}
|
||||
closeLabel={`Закрыть ${source.label}`}
|
||||
maximizeLabel={`Развернуть ${source.label}`}
|
||||
restoreLabel={`Восстановить ${source.label}`}
|
||||
moveLabel={`Переместить ${source.label}`}
|
||||
resizeLabel={`Изменить размер ${source.label}`}
|
||||
>
|
||||
<ObservationMedia
|
||||
source={source}
|
||||
playback={playback}
|
||||
prepareRecorded={prepareRecorded}
|
||||
recordedSessionGate={recordedSessionGate}
|
||||
recordedAdmissionKey={recordedAdmissionKey}
|
||||
onRecordedAdmissionChange={onRecordedAdmissionChange}
|
||||
/>
|
||||
</WorkspaceWindow>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import { Button, Icon, StatusBadge } from "@nodedc/ui-react";
|
||||
|
||||
import type { BackendStatus, RuntimePhase } from "../core/runtime/contracts";
|
||||
import type { EnvironmentPage } from "../core/environment/environmentSettings";
|
||||
import type { RootDefinition, WorkspaceDefinition } from "../productModel";
|
||||
import { backendLabel, backendTone, phaseLabel, phaseTone } from "../presentation";
|
||||
import { EnvironmentBackgroundMedia } from "./EnvironmentBackgroundMedia";
|
||||
|
||||
export interface LandingStageProps {
|
||||
root: RootDefinition | null;
|
||||
backendStatus: BackendStatus;
|
||||
phase?: RuntimePhase | null;
|
||||
message?: string | null;
|
||||
page: EnvironmentPage;
|
||||
quickActions: readonly WorkspaceDefinition[];
|
||||
onOpenWorkspace: (workspaceId: string) => void;
|
||||
}
|
||||
|
||||
export function LandingStage({
|
||||
root,
|
||||
backendStatus,
|
||||
phase,
|
||||
message,
|
||||
page,
|
||||
quickActions,
|
||||
onOpenWorkspace,
|
||||
}: LandingStageProps) {
|
||||
const { background } = page;
|
||||
const hasMedia = background.enabled && background.items.some(
|
||||
(item) => item.url && item.mediaKind,
|
||||
);
|
||||
return (
|
||||
<section
|
||||
className="landing-stage"
|
||||
data-root={root?.id ?? "home"}
|
||||
data-has-media={hasMedia ? "true" : undefined}
|
||||
>
|
||||
<EnvironmentBackgroundMedia background={background} />
|
||||
<div className="landing-stage__shade" aria-hidden="true" />
|
||||
<div className="landing-stage__copy">
|
||||
<span className="section-eyebrow">{page.eyebrow}</span>
|
||||
<h1>{page.title}</h1>
|
||||
<p>{page.description}</p>
|
||||
{quickActions.length ? (
|
||||
<div className="landing-stage__actions">
|
||||
{quickActions.map((workspace, index) => (
|
||||
<Button
|
||||
key={workspace.id}
|
||||
variant={index === 0 ? "primary" : "secondary"}
|
||||
icon={<Icon name={workspace.icon} />}
|
||||
onClick={() => onOpenWorkspace(workspace.id)}
|
||||
>
|
||||
{workspace.label}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div className="landing-stage__status">
|
||||
<div>
|
||||
<span className="section-eyebrow">ЛОКАЛЬНЫЙ КОНТУР</span>
|
||||
<StatusBadge tone={backendTone(backendStatus)}>{backendLabel(backendStatus)}</StatusBadge>
|
||||
</div>
|
||||
<div>
|
||||
<span className="section-eyebrow">ИСТОЧНИК</span>
|
||||
<StatusBadge tone={phaseTone(phase)}>{phaseLabel(phase)}</StatusBadge>
|
||||
</div>
|
||||
<p>{message || "Выберите архитектурный блок в верхней навигации."}</p>
|
||||
</div>
|
||||
|
||||
<footer className="landing-stage__footer">
|
||||
<span>{root?.accent ?? "НАБЛЮДЕНИЕ · МИССИИ · ДАННЫЕ"}</span>
|
||||
<span>01 / ЛОКАЛЬНЫЙ СТЕНД</span>
|
||||
</footer>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,730 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { Icon } from "@nodedc/ui-react";
|
||||
|
||||
import type { ObservationSourceDelivery } from "../core/runtime/contracts";
|
||||
import {
|
||||
cameraBrowserTransportIdentity,
|
||||
cameraTransportCallbackIsCurrent,
|
||||
initialCameraPlaybackRecoveryState,
|
||||
reduceCameraPlaybackRecovery,
|
||||
type CameraPlaybackRecoveryEvent,
|
||||
} from "../core/observation/liveCameraRecovery";
|
||||
import {
|
||||
createLiveViewerDiagnosticLifecycle,
|
||||
createLiveViewerInstanceId,
|
||||
createLiveViewerLineage,
|
||||
subscribeToLiveViewerBuildFence,
|
||||
type LiveViewerDiagnosticLifecycle,
|
||||
type LiveViewerFailureStage,
|
||||
} from "../core/observation/liveViewerDiagnostics";
|
||||
|
||||
type PlayerStatus = "connecting" | "buffering" | "playing" | "error";
|
||||
|
||||
export interface CameraLeaseRetryBudget {
|
||||
deliveryId: string;
|
||||
count: number;
|
||||
}
|
||||
|
||||
const CAMERA_LEASE_RETRY_DELAYS = [400, 1_000, 2_000, 5_000] as const;
|
||||
export const CAMERA_FIRST_MEDIA_TIMEOUT_MS = 8_000;
|
||||
export const CAMERA_FIRST_PLAYABLE_FRAME_TIMEOUT_MS = 8_000;
|
||||
// The gateway may release an 8 MiB / 64-fragment slow-reader backlog after a
|
||||
// main-thread stall. Keep one bounded append margin above that complete batch;
|
||||
// crossing either limit replaces the MSE epoch instead of dropping fragments.
|
||||
export const CAMERA_PENDING_QUEUE_MAX_BYTES = 12 * 1024 * 1024;
|
||||
export const CAMERA_PENDING_QUEUE_MAX_SEGMENTS = 96;
|
||||
|
||||
export type CameraStartupWatchdogStage = "first-media" | "first-playable-frame";
|
||||
|
||||
export interface CameraStartupWatchdog {
|
||||
armFirstMedia: () => void;
|
||||
markMediaReceived: () => void;
|
||||
markPlaying: () => void;
|
||||
clear: () => void;
|
||||
pendingStage: () => CameraStartupWatchdogStage | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Supervise only the disposable browser transport. One fixed first-media
|
||||
* deadline covers both an MSE that never opens and an open-but-silent socket.
|
||||
* The first non-empty fragment then starts a separate first-playable-frame
|
||||
* deadline. Every subsequent fragment extends that deadline: a browser that is
|
||||
* still receiving the authoritative stream must not destroy its partial MSE
|
||||
* decode solely because the main thread or decoder needed longer to start.
|
||||
*/
|
||||
export function createCameraStartupWatchdog({
|
||||
schedule,
|
||||
cancel,
|
||||
onTimeout,
|
||||
firstMediaTimeoutMs = CAMERA_FIRST_MEDIA_TIMEOUT_MS,
|
||||
firstPlayableFrameTimeoutMs = CAMERA_FIRST_PLAYABLE_FRAME_TIMEOUT_MS,
|
||||
}: {
|
||||
schedule: (callback: () => void, timeoutMs: number) => number;
|
||||
cancel: (handle: number) => void;
|
||||
onTimeout: (stage: CameraStartupWatchdogStage) => void;
|
||||
firstMediaTimeoutMs?: number;
|
||||
firstPlayableFrameTimeoutMs?: number;
|
||||
}): CameraStartupWatchdog {
|
||||
let timer: number | undefined;
|
||||
let stage: CameraStartupWatchdogStage | null = null;
|
||||
let mediaReceived = false;
|
||||
let playing = false;
|
||||
|
||||
const clearTimer = () => {
|
||||
if (timer !== undefined) cancel(timer);
|
||||
timer = undefined;
|
||||
stage = null;
|
||||
};
|
||||
const arm = (nextStage: CameraStartupWatchdogStage, timeoutMs: number) => {
|
||||
clearTimer();
|
||||
stage = nextStage;
|
||||
timer = schedule(() => {
|
||||
if (stage !== nextStage || playing) return;
|
||||
timer = undefined;
|
||||
stage = null;
|
||||
onTimeout(nextStage);
|
||||
}, timeoutMs);
|
||||
};
|
||||
|
||||
return {
|
||||
armFirstMedia() {
|
||||
if (mediaReceived || playing || stage !== null) return;
|
||||
arm("first-media", firstMediaTimeoutMs);
|
||||
},
|
||||
markMediaReceived() {
|
||||
if (playing) return;
|
||||
mediaReceived = true;
|
||||
arm("first-playable-frame", firstPlayableFrameTimeoutMs);
|
||||
},
|
||||
markPlaying() {
|
||||
if (playing) return;
|
||||
playing = true;
|
||||
clearTimer();
|
||||
},
|
||||
clear: clearTimer,
|
||||
pendingStage: () => stage,
|
||||
};
|
||||
}
|
||||
|
||||
export function cameraStartupWatchdogRecoveryMessage(
|
||||
stage: CameraStartupWatchdogStage,
|
||||
): string {
|
||||
return stage === "first-media"
|
||||
? "Камера подключена, но не передаёт медиаданные; восстанавливаем browser-preview."
|
||||
: "Медиаданные поступают, но первый кадр не воспроизводится; пересоздаём decoder.";
|
||||
}
|
||||
|
||||
export function resetCameraLeaseRetryBudget(deliveryId: string): CameraLeaseRetryBudget {
|
||||
return { deliveryId, count: 0 };
|
||||
}
|
||||
|
||||
export function consumeCameraLeaseRetry(
|
||||
current: CameraLeaseRetryBudget,
|
||||
deliveryId: string,
|
||||
): { budget: CameraLeaseRetryBudget; delay: number } {
|
||||
const count = current.deliveryId === deliveryId ? current.count : 0;
|
||||
const delayIndex = Math.min(count, CAMERA_LEASE_RETRY_DELAYS.length - 1);
|
||||
const delay = CAMERA_LEASE_RETRY_DELAYS[delayIndex];
|
||||
return {
|
||||
budget: {
|
||||
deliveryId,
|
||||
count: Math.min(count + 1, CAMERA_LEASE_RETRY_DELAYS.length),
|
||||
},
|
||||
delay,
|
||||
};
|
||||
}
|
||||
|
||||
export function cameraTransportRecoveryIsCurrent(
|
||||
activeAuthorityIdentity: string | null,
|
||||
expectedAuthorityIdentity: string | null,
|
||||
activeEpoch: number,
|
||||
expectedEpoch: number,
|
||||
disposed: boolean,
|
||||
): boolean {
|
||||
return Boolean(
|
||||
expectedAuthorityIdentity
|
||||
&& activeAuthorityIdentity === expectedAuthorityIdentity
|
||||
&& cameraTransportCallbackIsCurrent(activeEpoch, expectedEpoch, disposed),
|
||||
);
|
||||
}
|
||||
|
||||
export function cameraTransportCanOpen(uiBuildStale: boolean): boolean {
|
||||
return !uiBuildStale;
|
||||
}
|
||||
|
||||
export function cameraPendingQueueCanAccept(
|
||||
queuedBytes: number,
|
||||
queuedSegments: number,
|
||||
incomingBytes: number,
|
||||
): boolean {
|
||||
return incomingBytes > 0
|
||||
&& queuedBytes + incomingBytes <= CAMERA_PENDING_QUEUE_MAX_BYTES
|
||||
&& queuedSegments + 1 <= CAMERA_PENDING_QUEUE_MAX_SEGMENTS;
|
||||
}
|
||||
|
||||
export function cameraTransportCloseRecoveryMessage(code: number): string {
|
||||
if (code === 4_008) {
|
||||
return "Browser-reader отстал от эфира; восстанавливаем текущую камеру.";
|
||||
}
|
||||
if (code === 1_008) {
|
||||
return "Camera adapter освобождает прежний browser-reader; переподключаемся.";
|
||||
}
|
||||
if (code === 1_000) {
|
||||
return "Browser-preview завершился; восстанавливаем текущую камеру.";
|
||||
}
|
||||
return "Browser-preview прерван; восстанавливаем текущую камеру.";
|
||||
}
|
||||
|
||||
export function cameraPlaybackRecoveryFailureStage(
|
||||
event: CameraPlaybackRecoveryEvent,
|
||||
): LiveViewerFailureStage {
|
||||
if (event.type === "heartbeat") return "camera-heartbeat-reopen";
|
||||
if (event.type === "network-online") return "camera-network-online-reopen";
|
||||
if (event.type === "page-restore") return "camera-page-restore-reopen";
|
||||
return "camera-visibility-reopen";
|
||||
}
|
||||
|
||||
function websocketUrl(path: string): string {
|
||||
const url = new URL(path, window.location.href);
|
||||
if (url.protocol === "http:") url.protocol = "ws:";
|
||||
if (url.protocol === "https:") url.protocol = "wss:";
|
||||
if (url.protocol !== "ws:" && url.protocol !== "wss:") {
|
||||
throw new Error("Адаптер вернул неподдерживаемый адрес видеопотока.");
|
||||
}
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
export function MseFmp4WebSocketPlayer({
|
||||
delivery,
|
||||
label,
|
||||
recoveryAuthorityIdentity,
|
||||
}: {
|
||||
delivery: ObservationSourceDelivery & { kind: "mse-fmp4-websocket" };
|
||||
label: string;
|
||||
recoveryAuthorityIdentity: string | null;
|
||||
}) {
|
||||
const videoRef = useRef<HTMLVideoElement>(null);
|
||||
const leaseRetryRef = useRef(resetCameraLeaseRetryBudget(delivery.id));
|
||||
const activeAuthorityRef = useRef(recoveryAuthorityIdentity);
|
||||
const recoveryPendingAuthorityRef = useRef<string | null>(null);
|
||||
const transportHealthyRef = useRef(false);
|
||||
const transportEpochRef = useRef(0);
|
||||
const transportAuthorityRef = useRef(recoveryAuthorityIdentity);
|
||||
const activeTransportDisposeRef = useRef<(() => void) | null>(null);
|
||||
const activeDiagnosticRef = useRef<LiveViewerDiagnosticLifecycle | null>(null);
|
||||
const diagnosticInstanceIdRef = useRef<string | null>(null);
|
||||
const diagnosticGenerationRef = useRef(0);
|
||||
const uiBuildStaleRef = useRef(false);
|
||||
const [attempt, setAttempt] = useState(0);
|
||||
const [uiBuildStale, setUiBuildStale] = useState(false);
|
||||
const [status, setStatus] = useState<PlayerStatus>("connecting");
|
||||
const [message, setMessage] = useState("Подключение к локальному видеопотоку");
|
||||
activeAuthorityRef.current = recoveryAuthorityIdentity;
|
||||
const transportIdentity = cameraBrowserTransportIdentity(
|
||||
delivery,
|
||||
recoveryAuthorityIdentity,
|
||||
);
|
||||
|
||||
useEffect(() => subscribeToLiveViewerBuildFence(() => {
|
||||
// Setting this local fence tears down the effect-owned WebSocket and MSE
|
||||
// buffer. It intentionally never invokes a plugin or device command.
|
||||
uiBuildStaleRef.current = true;
|
||||
activeTransportDisposeRef.current?.();
|
||||
setUiBuildStale(true);
|
||||
}), []);
|
||||
|
||||
useEffect(() => {
|
||||
const video = videoRef.current;
|
||||
if (!video) return;
|
||||
if (uiBuildStale || !cameraTransportCanOpen(uiBuildStaleRef.current)) {
|
||||
video.pause();
|
||||
video.removeAttribute("src");
|
||||
video.load();
|
||||
return;
|
||||
}
|
||||
|
||||
if (transportAuthorityRef.current !== recoveryAuthorityIdentity) {
|
||||
transportAuthorityRef.current = recoveryAuthorityIdentity;
|
||||
leaseRetryRef.current = resetCameraLeaseRetryBudget(delivery.id);
|
||||
}
|
||||
|
||||
let disposed = false;
|
||||
const transportEpoch = transportEpochRef.current + 1;
|
||||
transportEpochRef.current = transportEpoch;
|
||||
diagnosticInstanceIdRef.current ??= createLiveViewerInstanceId();
|
||||
diagnosticGenerationRef.current += 1;
|
||||
const diagnosticLifecycle = createLiveViewerDiagnosticLifecycle({
|
||||
lineage: createLiveViewerLineage(
|
||||
diagnosticInstanceIdRef.current,
|
||||
diagnosticGenerationRef.current,
|
||||
),
|
||||
});
|
||||
activeDiagnosticRef.current = diagnosticLifecycle;
|
||||
diagnosticLifecycle.verifyBuild();
|
||||
diagnosticLifecycle.post({
|
||||
eventCode: "live_camera_player_effect_started",
|
||||
streamId: delivery.id,
|
||||
transportEpoch,
|
||||
});
|
||||
if (!recoveryAuthorityIdentity) {
|
||||
diagnosticLifecycle.post({
|
||||
eventCode: "live_camera_playback_authority_missing",
|
||||
streamId: delivery.id,
|
||||
transportEpoch,
|
||||
});
|
||||
}
|
||||
const transportIsCurrent = () => cameraTransportCallbackIsCurrent(
|
||||
transportEpochRef.current,
|
||||
transportEpoch,
|
||||
disposed || uiBuildStaleRef.current,
|
||||
);
|
||||
let socket: WebSocket | null = null;
|
||||
let sourceBuffer: SourceBuffer | null = null;
|
||||
let onBufferUpdateEnd: (() => void) | null = null;
|
||||
let onBufferError: (() => void) | null = null;
|
||||
let objectUrl = "";
|
||||
let retryTimer: number | undefined;
|
||||
let receivedMedia = false;
|
||||
let failed = false;
|
||||
let playingReported = false;
|
||||
let startupWatchdog: CameraStartupWatchdog | null = null;
|
||||
transportHealthyRef.current = false;
|
||||
const recovering = Boolean(
|
||||
activeAuthorityRef.current
|
||||
&& recoveryPendingAuthorityRef.current === activeAuthorityRef.current,
|
||||
);
|
||||
const queue: ArrayBuffer[] = [];
|
||||
let queuedBytes = 0;
|
||||
|
||||
const fail = (copy: string) => {
|
||||
if (!transportIsCurrent() || failed) return;
|
||||
startupWatchdog?.clear();
|
||||
failed = true;
|
||||
transportHealthyRef.current = false;
|
||||
recoveryPendingAuthorityRef.current = null;
|
||||
setStatus("error");
|
||||
setMessage(copy);
|
||||
try {
|
||||
if (socket && socket.readyState < WebSocket.CLOSING) {
|
||||
socket.close(4_000, "Live video buffer reset");
|
||||
}
|
||||
} catch {
|
||||
// The manual reconnect button will create a fresh transport and MSE buffer.
|
||||
}
|
||||
};
|
||||
|
||||
const retryTransport = (
|
||||
copy: string,
|
||||
failureStage: LiveViewerFailureStage,
|
||||
websocketCloseCode?: number,
|
||||
appendFailure?: {
|
||||
cameraAppendErrorName: string;
|
||||
cameraMediaSourceState: "closed" | "open" | "ended";
|
||||
cameraVideoErrorCode: number | null;
|
||||
},
|
||||
) => {
|
||||
if (!transportIsCurrent() || failed) return;
|
||||
if (!cameraTransportRecoveryIsCurrent(
|
||||
activeAuthorityRef.current,
|
||||
recoveryAuthorityIdentity,
|
||||
transportEpochRef.current,
|
||||
transportEpoch,
|
||||
disposed || uiBuildStaleRef.current,
|
||||
)) {
|
||||
fail(copy);
|
||||
return;
|
||||
}
|
||||
startupWatchdog?.clear();
|
||||
failed = true;
|
||||
transportHealthyRef.current = false;
|
||||
const retry = consumeCameraLeaseRetry(leaseRetryRef.current, delivery.id);
|
||||
leaseRetryRef.current = retry.budget;
|
||||
diagnosticLifecycle.post({
|
||||
eventCode: "live_camera_transport_restart_requested",
|
||||
failureStage,
|
||||
streamId: delivery.id,
|
||||
cameraQueueBytes: queuedBytes,
|
||||
cameraQueueSegments: queue.length,
|
||||
cameraRetryCount: retry.budget.count,
|
||||
websocketCloseCode,
|
||||
transportEpoch,
|
||||
...appendFailure,
|
||||
});
|
||||
queue.length = 0;
|
||||
queuedBytes = 0;
|
||||
recoveryPendingAuthorityRef.current = recoveryAuthorityIdentity;
|
||||
setStatus("connecting");
|
||||
setMessage(copy);
|
||||
try {
|
||||
if (socket && socket.readyState < WebSocket.CLOSING) {
|
||||
socket.close(4_001, "Live video transport recovery");
|
||||
}
|
||||
} catch {
|
||||
// The replacement effect still fences and releases this transport.
|
||||
}
|
||||
retryTimer = window.setTimeout(() => {
|
||||
if (cameraTransportRecoveryIsCurrent(
|
||||
activeAuthorityRef.current,
|
||||
recoveryAuthorityIdentity,
|
||||
transportEpochRef.current,
|
||||
transportEpoch,
|
||||
disposed || uiBuildStaleRef.current,
|
||||
)) {
|
||||
setAttempt((value) => value + 1);
|
||||
}
|
||||
}, retry.delay);
|
||||
};
|
||||
|
||||
if (recoveryAuthorityIdentity) {
|
||||
startupWatchdog = createCameraStartupWatchdog({
|
||||
schedule: (callback, timeoutMs) => window.setTimeout(callback, timeoutMs),
|
||||
cancel: (handle) => window.clearTimeout(handle),
|
||||
onTimeout: (stage) => {
|
||||
retryTransport(
|
||||
cameraStartupWatchdogRecoveryMessage(stage),
|
||||
stage === "first-media"
|
||||
? "camera-first-media-timeout"
|
||||
: "camera-first-playable-timeout",
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const onPlaying = () => {
|
||||
if (!transportIsCurrent() || failed) return;
|
||||
startupWatchdog?.markPlaying();
|
||||
transportHealthyRef.current = true;
|
||||
leaseRetryRef.current = resetCameraLeaseRetryBudget(delivery.id);
|
||||
recoveryPendingAuthorityRef.current = null;
|
||||
setStatus("playing");
|
||||
setMessage("");
|
||||
if (!playingReported) {
|
||||
playingReported = true;
|
||||
diagnosticLifecycle.post({
|
||||
eventCode: "live_camera_transport_playing",
|
||||
streamId: delivery.id,
|
||||
cameraQueueBytes: queuedBytes,
|
||||
cameraQueueSegments: queue.length,
|
||||
transportEpoch,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
video.addEventListener("playing", onPlaying);
|
||||
|
||||
const appendNext = () => {
|
||||
if (
|
||||
!transportIsCurrent()
|
||||
|| failed
|
||||
|| !sourceBuffer
|
||||
|| sourceBuffer.updating
|
||||
|| queue.length === 0
|
||||
) return;
|
||||
const chunk = queue.shift();
|
||||
if (!chunk) return;
|
||||
queuedBytes -= chunk.byteLength;
|
||||
try {
|
||||
sourceBuffer.appendBuffer(chunk);
|
||||
} catch (error) {
|
||||
const quotaExceeded = error instanceof DOMException
|
||||
&& error.name === "QuotaExceededError";
|
||||
retryTransport(
|
||||
quotaExceeded
|
||||
? "Live-буфер переполнен; восстанавливаем канал без накопленной задержки."
|
||||
: "Не удалось добавить видеосегмент; восстанавливаем browser-preview.",
|
||||
quotaExceeded ? "camera-append-quota" : "camera-append-error",
|
||||
undefined,
|
||||
{
|
||||
cameraAppendErrorName: error instanceof DOMException
|
||||
? error.name
|
||||
: error instanceof Error
|
||||
? error.name
|
||||
: "UnknownError",
|
||||
cameraMediaSourceState: mediaSource.readyState,
|
||||
cameraVideoErrorCode: video.error?.code ?? null,
|
||||
},
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const enqueue = (chunk: ArrayBuffer) => {
|
||||
if (!transportIsCurrent() || failed || chunk.byteLength === 0) return;
|
||||
startupWatchdog?.markMediaReceived();
|
||||
// Never drop arbitrary fMP4 fragments: the following samples may depend
|
||||
// on them. A bounded reset is safer and keeps live latency deterministic.
|
||||
if (!cameraPendingQueueCanAccept(queuedBytes, queue.length, chunk.byteLength)) {
|
||||
retryTransport(
|
||||
"Видеодекодер отстал от эфира; восстанавливаем live-буфер.",
|
||||
"camera-queue-capacity",
|
||||
);
|
||||
return;
|
||||
}
|
||||
queue.push(chunk);
|
||||
queuedBytes += chunk.byteLength;
|
||||
appendNext();
|
||||
};
|
||||
|
||||
const mediaType = delivery.mediaType?.trim();
|
||||
if (typeof MediaSource === "undefined") {
|
||||
fail("Этот браузер не поддерживает Media Source Extensions.");
|
||||
return;
|
||||
}
|
||||
if (!mediaType || !MediaSource.isTypeSupported(mediaType)) {
|
||||
fail(mediaType
|
||||
? `Браузер не поддерживает ${mediaType}`
|
||||
: "Адаптер не сообщил MIME/codec fMP4-потока.");
|
||||
return;
|
||||
}
|
||||
|
||||
setStatus("connecting");
|
||||
setMessage(recovering
|
||||
? "Восстановление камеры после разрыва браузерного канала"
|
||||
: "Подключение к локальному видеопотоку");
|
||||
const mediaSource = new MediaSource();
|
||||
objectUrl = URL.createObjectURL(mediaSource);
|
||||
|
||||
const onSourceOpen = () => {
|
||||
if (!transportIsCurrent() || failed) return;
|
||||
diagnosticLifecycle.post({
|
||||
eventCode: "live_camera_media_source_open",
|
||||
streamId: delivery.id,
|
||||
transportEpoch,
|
||||
});
|
||||
try {
|
||||
sourceBuffer = mediaSource.addSourceBuffer(mediaType);
|
||||
} catch {
|
||||
fail("Не удалось создать MSE-буфер для указанного кодека.");
|
||||
return;
|
||||
}
|
||||
|
||||
onBufferUpdateEnd = () => {
|
||||
if (!transportIsCurrent() || failed || !sourceBuffer) return;
|
||||
const buffered = sourceBuffer.buffered;
|
||||
if (buffered.length > 0) {
|
||||
const end = buffered.end(buffered.length - 1);
|
||||
const start = buffered.start(0);
|
||||
if (end - video.currentTime > 1) video.currentTime = Math.max(0, end - 0.1);
|
||||
if (!receivedMedia) {
|
||||
receivedMedia = true;
|
||||
setStatus("buffering");
|
||||
setMessage(recovering
|
||||
? "Запуск первого кадра восстановленной камеры"
|
||||
: "Запуск первого декодированного кадра");
|
||||
void video.play().catch(() => undefined);
|
||||
}
|
||||
const removeBefore = end - 3;
|
||||
if (removeBefore > start && !sourceBuffer.updating) {
|
||||
try {
|
||||
sourceBuffer.remove(0, removeBefore);
|
||||
return;
|
||||
} catch {
|
||||
// Continue appending; quota handling performs a clean reset.
|
||||
}
|
||||
}
|
||||
}
|
||||
appendNext();
|
||||
};
|
||||
onBufferError = () => {
|
||||
retryTransport(
|
||||
"MSE сбросил видеосегмент; восстанавливаем decoder.",
|
||||
"camera-source-buffer-error",
|
||||
);
|
||||
};
|
||||
sourceBuffer.addEventListener("updateend", onBufferUpdateEnd);
|
||||
sourceBuffer.addEventListener("error", onBufferError);
|
||||
|
||||
try {
|
||||
socket = new WebSocket(websocketUrl(delivery.url));
|
||||
} catch (error) {
|
||||
fail(error instanceof Error ? error.message : "Некорректный адрес видеопотока.");
|
||||
return;
|
||||
}
|
||||
socket.binaryType = "arraybuffer";
|
||||
socket.addEventListener("open", () => {
|
||||
if (!transportIsCurrent() || failed) return;
|
||||
diagnosticLifecycle.post({
|
||||
eventCode: "live_camera_websocket_open",
|
||||
streamId: delivery.id,
|
||||
transportEpoch,
|
||||
});
|
||||
startupWatchdog?.armFirstMedia();
|
||||
setStatus("buffering");
|
||||
setMessage(recovering
|
||||
? "Ожидание первого кадра после восстановления"
|
||||
: "Ожидание первого видеокадра");
|
||||
});
|
||||
socket.addEventListener("message", (event) => {
|
||||
if (!transportIsCurrent() || failed) return;
|
||||
if (event.data instanceof ArrayBuffer) {
|
||||
enqueue(event.data);
|
||||
} else if (event.data instanceof Blob) {
|
||||
void event.data.arrayBuffer().then(enqueue).catch(() => {
|
||||
retryTransport(
|
||||
"Получен повреждённый видеосегмент; восстанавливаем канал.",
|
||||
"camera-fragment-decode",
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
socket.addEventListener("error", () => {
|
||||
retryTransport(
|
||||
"Связь с локальным video adapter потеряна; переподключаемся.",
|
||||
"camera-websocket-error",
|
||||
);
|
||||
});
|
||||
socket.addEventListener("close", (event) => {
|
||||
if (!transportIsCurrent() || failed) return;
|
||||
retryTransport(
|
||||
cameraTransportCloseRecoveryMessage(event.code),
|
||||
"camera-websocket-close",
|
||||
event.code,
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
// Register before assigning the object URL. `video.src` is the action that
|
||||
// lets the browser open this MediaSource; a fast sequential MSE lease must
|
||||
// not fire `sourceopen` in the gap before its only listener exists.
|
||||
mediaSource.addEventListener("sourceopen", onSourceOpen, { once: true });
|
||||
startupWatchdog?.armFirstMedia();
|
||||
video.src = objectUrl;
|
||||
|
||||
const disposeTransport = () => {
|
||||
if (disposed) return;
|
||||
disposed = true;
|
||||
transportHealthyRef.current = false;
|
||||
startupWatchdog?.clear();
|
||||
if (retryTimer !== undefined) window.clearTimeout(retryTimer);
|
||||
queue.length = 0;
|
||||
socket?.close(1000, "Browser preview transport replaced or hidden");
|
||||
video.removeEventListener("playing", onPlaying);
|
||||
mediaSource.removeEventListener("sourceopen", onSourceOpen);
|
||||
if (sourceBuffer && onBufferUpdateEnd) {
|
||||
sourceBuffer.removeEventListener("updateend", onBufferUpdateEnd);
|
||||
}
|
||||
if (sourceBuffer && onBufferError) {
|
||||
sourceBuffer.removeEventListener("error", onBufferError);
|
||||
}
|
||||
try {
|
||||
if (sourceBuffer?.updating) sourceBuffer.abort();
|
||||
} catch {
|
||||
// The MediaSource can already be closing.
|
||||
}
|
||||
try {
|
||||
if (mediaSource.readyState === "open") mediaSource.endOfStream();
|
||||
} catch {
|
||||
// Cleanup must continue even if the browser has already detached MSE.
|
||||
}
|
||||
video.pause();
|
||||
video.removeAttribute("src");
|
||||
video.load();
|
||||
if (objectUrl) URL.revokeObjectURL(objectUrl);
|
||||
};
|
||||
activeTransportDisposeRef.current = disposeTransport;
|
||||
return () => {
|
||||
if (activeTransportDisposeRef.current === disposeTransport) {
|
||||
activeTransportDisposeRef.current = null;
|
||||
}
|
||||
if (activeDiagnosticRef.current === diagnosticLifecycle) {
|
||||
activeDiagnosticRef.current = null;
|
||||
}
|
||||
diagnosticLifecycle.dispose();
|
||||
disposeTransport();
|
||||
};
|
||||
}, [attempt, recoveryAuthorityIdentity, transportIdentity, uiBuildStale]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!recoveryAuthorityIdentity
|
||||
|| uiBuildStale
|
||||
|| !cameraTransportCanOpen(uiBuildStaleRef.current)
|
||||
) return;
|
||||
|
||||
let recovery = initialCameraPlaybackRecoveryState(
|
||||
recoveryAuthorityIdentity,
|
||||
Date.now(),
|
||||
);
|
||||
const dispatchRecovery = (event: CameraPlaybackRecoveryEvent) => {
|
||||
if (uiBuildStaleRef.current) return;
|
||||
const decision = reduceCameraPlaybackRecovery(recovery, event, {
|
||||
activeAuthorityIdentity: activeAuthorityRef.current,
|
||||
now: Date.now(),
|
||||
documentVisible: document.visibilityState === "visible",
|
||||
networkOnline: navigator.onLine !== false,
|
||||
transportHealthy: transportHealthyRef.current,
|
||||
});
|
||||
recovery = decision.state;
|
||||
if (!decision.reopen) return;
|
||||
activeDiagnosticRef.current?.post({
|
||||
eventCode: "live_camera_transport_restart_requested",
|
||||
failureStage: cameraPlaybackRecoveryFailureStage(event),
|
||||
streamId: delivery.id,
|
||||
transportEpoch: transportEpochRef.current,
|
||||
});
|
||||
leaseRetryRef.current = resetCameraLeaseRetryBudget(delivery.id);
|
||||
recoveryPendingAuthorityRef.current = recoveryAuthorityIdentity;
|
||||
setStatus("connecting");
|
||||
setMessage("Восстановление камеры после разрыва браузерного канала");
|
||||
// The transport effect cleans up the old WebSocket and MSE object before
|
||||
// opening their replacement. No plugin action or device command occurs.
|
||||
setAttempt((value) => value + 1);
|
||||
};
|
||||
const onVisibilityChange = () => {
|
||||
dispatchRecovery({
|
||||
type: document.visibilityState === "hidden"
|
||||
? "document-hidden"
|
||||
: "document-visible",
|
||||
});
|
||||
};
|
||||
const onOnline = () => dispatchRecovery({ type: "network-online" });
|
||||
const onPageShow = (event: PageTransitionEvent) => {
|
||||
dispatchRecovery({ type: "page-restore", persisted: event.persisted });
|
||||
};
|
||||
const heartbeat = window.setInterval(() => {
|
||||
dispatchRecovery({ type: "heartbeat" });
|
||||
}, 1_000);
|
||||
|
||||
document.addEventListener("visibilitychange", onVisibilityChange);
|
||||
window.addEventListener("online", onOnline);
|
||||
window.addEventListener("pageshow", onPageShow);
|
||||
return () => {
|
||||
window.clearInterval(heartbeat);
|
||||
document.removeEventListener("visibilitychange", onVisibilityChange);
|
||||
window.removeEventListener("online", onOnline);
|
||||
window.removeEventListener("pageshow", onPageShow);
|
||||
};
|
||||
}, [delivery.id, recoveryAuthorityIdentity, uiBuildStale]);
|
||||
|
||||
return (
|
||||
<div className="mse-fmp4-player" data-status={status}>
|
||||
<video
|
||||
ref={videoRef}
|
||||
className="observation-media__asset"
|
||||
aria-label={label}
|
||||
autoPlay
|
||||
muted
|
||||
playsInline
|
||||
/>
|
||||
{status !== "playing" ? (
|
||||
<div className="mse-fmp4-player__status" role="status" aria-live="polite">
|
||||
<Icon name={status === "error" ? "alert" : "video"} size={20} />
|
||||
<strong>{status === "error" ? "Канал прерван" : "Подготовка камеры"}</strong>
|
||||
<span>{message}</span>
|
||||
{status === "error" ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
leaseRetryRef.current = resetCameraLeaseRetryBudget(delivery.id);
|
||||
recoveryPendingAuthorityRef.current = null;
|
||||
setAttempt((value) => value + 1);
|
||||
}}
|
||||
>
|
||||
<Icon name="refresh" size={14} />
|
||||
Подключиться повторно
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,484 @@
|
||||
import { useState } from "react";
|
||||
import { ConfirmationModal, Dropdown, Icon } from "@nodedc/ui-react";
|
||||
|
||||
import {
|
||||
type ObservationSessionReplayLaunch,
|
||||
type ObservationSessionSummary,
|
||||
} from "../core/observation/sessionArchive";
|
||||
import { useObservationSessions } from "../core/observation/useObservationSessions";
|
||||
import type {
|
||||
ObservationPreparationPhase,
|
||||
ObservationReplayOutcome,
|
||||
} from "../core/observation/useObservationSessions";
|
||||
import "../styles/observation-sessions.css";
|
||||
|
||||
type SessionVisualState = "ready" | "processing" | "cold" | "error";
|
||||
|
||||
export function observationSessionVisualState(
|
||||
session: ObservationSessionSummary,
|
||||
{ pending = false, failed = false }: { pending?: boolean; failed?: boolean } = {},
|
||||
): SessionVisualState {
|
||||
if (failed) return "error";
|
||||
if (pending) return "processing";
|
||||
if (session.preparation !== null) {
|
||||
if (["queued", "validating", "exporting", "finalizing"].includes(
|
||||
session.preparation.state,
|
||||
)) return "processing";
|
||||
if (session.preparation.state === "ready" && session.replayable) return "ready";
|
||||
return "error";
|
||||
}
|
||||
if (session.status === "recording") return "processing";
|
||||
return session.replayable ? "cold" : "error";
|
||||
}
|
||||
|
||||
export function observationSessionVisualLabel(state: SessionVisualState): string {
|
||||
if (state === "ready") return "Готово";
|
||||
if (state === "processing") return "Обработка";
|
||||
if (state === "cold") return "Подготовить";
|
||||
return "Ошибка";
|
||||
}
|
||||
|
||||
const modalityLabel: Record<string, string> = {
|
||||
"point-cloud": "облако точек",
|
||||
pose: "траектория",
|
||||
trajectory: "траектория",
|
||||
video: "видео",
|
||||
image: "изображения",
|
||||
depth: "глубина",
|
||||
telemetry: "телеметрия",
|
||||
};
|
||||
|
||||
const preparationLabel: Record<ObservationPreparationPhase, string> = {
|
||||
requesting: "Запрашиваем подготовку",
|
||||
queued: "В очереди",
|
||||
validating: "Проверяем запись",
|
||||
exporting: "Готовим операторскую сцену",
|
||||
finalizing: "Завершаем подготовку",
|
||||
failed: "Подготовка не выполнена",
|
||||
cancelled: "Подготовка отменена",
|
||||
};
|
||||
|
||||
function progressCopy(phase: ObservationPreparationPhase): string {
|
||||
// Backend progress values are phase markers and heartbeat revisions, not a
|
||||
// measured fraction of bytes or frames. Presenting 0.5 as "50%" made a
|
||||
// healthy long export look stalled, especially after a browser reload.
|
||||
return preparationLabel[phase];
|
||||
}
|
||||
|
||||
function formatStartedAt(value: string): string {
|
||||
return new Intl.DateTimeFormat("ru-RU", {
|
||||
day: "2-digit",
|
||||
month: "2-digit",
|
||||
year: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
}).format(new Date(value));
|
||||
}
|
||||
|
||||
function formatDuration(seconds: number): string {
|
||||
const totalSeconds = Math.max(0, Math.round(seconds));
|
||||
const hours = Math.floor(totalSeconds / 3_600);
|
||||
const minutes = Math.floor((totalSeconds % 3_600) / 60);
|
||||
const remainingSeconds = totalSeconds % 60;
|
||||
return hours > 0
|
||||
? `${hours}:${String(minutes).padStart(2, "0")}:${String(remainingSeconds).padStart(2, "0")}`
|
||||
: `${minutes}:${String(remainingSeconds).padStart(2, "0")}`;
|
||||
}
|
||||
|
||||
function sessionDescription(session: ObservationSessionSummary): string {
|
||||
const modalities = session.modalities.length
|
||||
? session.modalities.map((value) => modalityLabel[value] ?? value).join(" · ")
|
||||
: "каналы не зафиксированы";
|
||||
return `${formatStartedAt(session.startedAtUtc)} · ${formatDuration(session.durationSeconds)} · ${modalities}`;
|
||||
}
|
||||
|
||||
export interface ObservationSessionReplayCallbacks {
|
||||
onReplayBegin?: (
|
||||
session: ObservationSessionSummary,
|
||||
launch: ObservationSessionReplayLaunch,
|
||||
) => void | Promise<void>;
|
||||
onReplayAccepted?: (
|
||||
session: ObservationSessionSummary,
|
||||
launch: ObservationSessionReplayLaunch,
|
||||
) => void | Promise<void>;
|
||||
onReplaySettled?: (
|
||||
session: ObservationSessionSummary,
|
||||
outcome: ObservationReplayOutcome,
|
||||
) => void | Promise<void>;
|
||||
onDeleteBegin?: (sessionId: string) => void | Promise<void>;
|
||||
}
|
||||
|
||||
export function ObservationSessionSelect({
|
||||
limit = 100,
|
||||
disabled = false,
|
||||
blockedReason = null,
|
||||
onReplayBegin,
|
||||
onReplayAccepted,
|
||||
onReplaySettled,
|
||||
onDeleteBegin,
|
||||
}: ObservationSessionReplayCallbacks & {
|
||||
limit?: number;
|
||||
disabled?: boolean;
|
||||
blockedReason?: string | null;
|
||||
}) {
|
||||
const [deleteTarget, setDeleteTarget] = useState<ObservationSessionSummary | null>(null);
|
||||
const sessions = useObservationSessions({
|
||||
limit,
|
||||
scope: "source",
|
||||
replayEnabled: blockedReason === null,
|
||||
onReplayBegin,
|
||||
onReplayAccepted,
|
||||
onReplaySettled,
|
||||
onDeleteBegin,
|
||||
});
|
||||
const triggerCopy = sessions.replayProgress
|
||||
? progressCopy(sessions.replayProgress.phase)
|
||||
: sessions.state === "loading"
|
||||
? "Загружаем сессии…"
|
||||
: "Сохранённые сессии";
|
||||
const presentedTriggerCopy = blockedReason ?? triggerCopy;
|
||||
|
||||
return <>
|
||||
<Dropdown
|
||||
className="observation-session-select"
|
||||
placement="bottom-end"
|
||||
width={390}
|
||||
offset={10}
|
||||
disabled={disabled}
|
||||
surfaceRole="dialog"
|
||||
surfaceClassName="observation-session-menu"
|
||||
trigger={({ open, toggle, setAnchorRef, setTriggerRef, surfaceId }) => (
|
||||
<button
|
||||
ref={(node) => {
|
||||
setAnchorRef(node);
|
||||
setTriggerRef(node);
|
||||
}}
|
||||
type="button"
|
||||
className="observation-session-select__trigger"
|
||||
data-active={open ? "true" : undefined}
|
||||
aria-label="Открыть сохранённые сессии наблюдения"
|
||||
aria-haspopup="dialog"
|
||||
aria-expanded={open}
|
||||
aria-controls={surfaceId}
|
||||
disabled={disabled}
|
||||
title={blockedReason ?? undefined}
|
||||
onClick={toggle}
|
||||
>
|
||||
<Icon name="database" size={15} />
|
||||
<span>{presentedTriggerCopy}</span>
|
||||
{sessions.state === "ready" ? <small>{sessions.items.length}</small> : null}
|
||||
<Icon name="chevron-down" size={14} />
|
||||
</button>
|
||||
)}
|
||||
>
|
||||
{({ close }) => (
|
||||
<div className="observation-session-menu__content">
|
||||
<header className="observation-session-menu__head">
|
||||
<div>
|
||||
<span className="section-eyebrow">ИСТОРИЯ НАБЛЮДЕНИЯ</span>
|
||||
<strong>Сохранённые сессии</strong>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Обновить каталог сессий"
|
||||
disabled={sessions.state === "loading"}
|
||||
onClick={() => void sessions.refresh()}
|
||||
>
|
||||
<Icon name="refresh" size={14} />
|
||||
</button>
|
||||
</header>
|
||||
|
||||
{sessions.items.length > 0 ? (
|
||||
<div className="observation-session-menu__list">
|
||||
{sessions.items.map((session) => {
|
||||
const pending = sessions.replayingSessionId === session.id;
|
||||
const deleting = sessions.deletingSessionId === session.id;
|
||||
const failed = sessions.failedSessionId === session.id;
|
||||
const visualState = observationSessionVisualState(session, { pending, failed });
|
||||
return (
|
||||
<div
|
||||
key={session.id}
|
||||
className="observation-session-option"
|
||||
data-deleting={deleting ? "true" : undefined}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
className="nodedc-dropdown-option observation-session-option__open"
|
||||
disabled={pending || deleting || !session.replayable}
|
||||
onClick={() => {
|
||||
void sessions.replay(session.id).then((accepted) => {
|
||||
if (accepted) close();
|
||||
});
|
||||
}}
|
||||
>
|
||||
<span className="nodedc-dropdown-option__icon">
|
||||
<i data-session-visual-state={visualState} aria-hidden="true" />
|
||||
</span>
|
||||
<span className="nodedc-dropdown-option__body">
|
||||
<span className="nodedc-dropdown-option__label">
|
||||
{session.lab &&
|
||||
!session.label.startsWith(`${session.lab.labId} ·`) ? (
|
||||
<span className="observation-session-option__lab">
|
||||
{session.lab.labId}
|
||||
</span>
|
||||
) : null}
|
||||
{session.label}
|
||||
</span>
|
||||
<span className="nodedc-dropdown-option__description">
|
||||
{sessionDescription(session)}
|
||||
</span>
|
||||
</span>
|
||||
<span className="observation-session-option__state">
|
||||
{deleting ? "Удаление…" : observationSessionVisualLabel(visualState)}
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="observation-session-option__delete"
|
||||
aria-label={`Удалить сохранённую сессию ${session.label}`}
|
||||
disabled={pending || deleting}
|
||||
onClick={() => setDeleteTarget(session)}
|
||||
>
|
||||
<Icon name="trash" size={15} />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : sessions.state === "loading" ? (
|
||||
<div className="observation-session-menu__empty" role="status">
|
||||
<span className="busy-indicator" aria-hidden="true" />
|
||||
<strong>Читаем каталог</strong>
|
||||
</div>
|
||||
) : (
|
||||
<div className="observation-session-menu__empty">
|
||||
<Icon name={sessions.error ? "alert" : "database"} size={18} />
|
||||
<strong>{sessions.error ? "Каталог недоступен" : "Сессий пока нет"}</strong>
|
||||
<span>{sessions.error ?? "Завершённые записи появятся здесь автоматически."}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{sessions.error && sessions.items.length > 0 ? (
|
||||
<footer className="observation-session-menu__error" role="alert">
|
||||
<Icon name="alert" size={13} />
|
||||
<span>{sessions.error}</span>
|
||||
{sessions.failedSessionId ? (
|
||||
<button type="button" onClick={() => void sessions.retry()}>
|
||||
Повторить
|
||||
</button>
|
||||
) : null}
|
||||
</footer>
|
||||
) : null}
|
||||
|
||||
</div>
|
||||
)}
|
||||
</Dropdown>
|
||||
<ConfirmationModal
|
||||
open={deleteTarget !== null}
|
||||
title="Удалить сохранённую сессию?"
|
||||
description={deleteTarget ? <>
|
||||
<strong>{deleteTarget.label}</strong>
|
||||
{deleteTarget.lab ? (
|
||||
<p>
|
||||
Будут удалены только LAB-запись из каталога и её подготовленный кэш.
|
||||
Исходная запись {deleteTarget.lab.sourceSessionId} и зафиксированный
|
||||
лабораторный результат останутся неизменными.
|
||||
</p>
|
||||
) : (
|
||||
<p>
|
||||
Сессия, исходные данные наблюдения, подготовленная Rerun-запись и
|
||||
видеоматериалы будут удалены с этого сервера без возможности восстановления.
|
||||
</p>
|
||||
)}
|
||||
{sessions.error && sessions.deletingSessionId === null ? (
|
||||
<p className="observation-session-delete-error" role="alert">{sessions.error}</p>
|
||||
) : null}
|
||||
</> : null}
|
||||
confirmLabel="Удалить сессию"
|
||||
pendingLabel="Удаление…"
|
||||
danger
|
||||
onClose={() => {
|
||||
if (sessions.deletingSessionId === null) setDeleteTarget(null);
|
||||
}}
|
||||
onConfirm={async () => {
|
||||
if (!deleteTarget) return;
|
||||
if (await sessions.remove(deleteTarget.id)) setDeleteTarget(null);
|
||||
}}
|
||||
/>
|
||||
</>;
|
||||
}
|
||||
|
||||
export function ObservationSessionArchive({
|
||||
limit = 100,
|
||||
labsOnly = false,
|
||||
disabled = false,
|
||||
blockedReason = null,
|
||||
onReplayBegin,
|
||||
onReplayAccepted,
|
||||
onReplaySettled,
|
||||
onDeleteBegin,
|
||||
}: ObservationSessionReplayCallbacks & {
|
||||
limit?: number;
|
||||
labsOnly?: boolean;
|
||||
disabled?: boolean;
|
||||
blockedReason?: string | null;
|
||||
}) {
|
||||
const [deleteTarget, setDeleteTarget] = useState<ObservationSessionSummary | null>(null);
|
||||
const sessions = useObservationSessions({
|
||||
limit,
|
||||
scope: labsOnly ? "laboratory" : "source",
|
||||
replayEnabled: blockedReason === null,
|
||||
onReplayBegin,
|
||||
onReplayAccepted,
|
||||
onReplaySettled,
|
||||
onDeleteBegin,
|
||||
});
|
||||
const items = sessions.items;
|
||||
|
||||
return <>
|
||||
<section
|
||||
className="observation-session-archive"
|
||||
aria-label={labsOnly ? "Лабораторные записи" : "Сохранённые сессии"}
|
||||
>
|
||||
<header className="observation-session-archive__head">
|
||||
<div>
|
||||
<span className="section-eyebrow">
|
||||
{labsOnly ? "ВОСПРОИЗВОДИМЫЕ ЛАБОРАТОРНЫЕ ЗАПИСИ" : "АРХИВ НАБЛЮДЕНИЯ"}
|
||||
</span>
|
||||
<h2>{labsOnly ? "Записанные лабораторные работы" : "Сохранённые сессии"}</h2>
|
||||
<p>
|
||||
{labsOnly
|
||||
? "Каждая запись связана с LAB-конфигурацией и открывается как самостоятельный визуальный результат."
|
||||
: "Выберите запись: сцена и синхронные каналы откроются ниже, не меняя эфир наблюдения."}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="observation-session-archive__refresh"
|
||||
aria-label="Обновить каталог сессий"
|
||||
disabled={sessions.state === "loading"}
|
||||
onClick={() => void sessions.refresh()}
|
||||
>
|
||||
<Icon name="refresh" size={14} />
|
||||
<span>Обновить</span>
|
||||
</button>
|
||||
</header>
|
||||
|
||||
{items.length > 0 ? (
|
||||
<div className="observation-session-archive__list">
|
||||
{items.map((session) => {
|
||||
const pending = sessions.replayingSessionId === session.id;
|
||||
const deleting = sessions.deletingSessionId === session.id;
|
||||
const failed = sessions.failedSessionId === session.id;
|
||||
const visualState = observationSessionVisualState(session, { pending, failed });
|
||||
return (
|
||||
<article
|
||||
key={session.id}
|
||||
className="observation-session-archive__item"
|
||||
data-deleting={deleting ? "true" : undefined}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
className="observation-session-archive__open"
|
||||
disabled={disabled || pending || deleting || !session.replayable}
|
||||
title={blockedReason ?? undefined}
|
||||
onClick={() => void sessions.replay(session.id)}
|
||||
>
|
||||
<i data-session-visual-state={visualState} aria-hidden="true" />
|
||||
<span>
|
||||
<strong>
|
||||
{session.lab &&
|
||||
!session.label.startsWith(`${session.lab.labId} ·`) ? (
|
||||
<small>{session.lab.labId}</small>
|
||||
) : null}
|
||||
{session.label}
|
||||
</strong>
|
||||
<small>{sessionDescription(session)}</small>
|
||||
</span>
|
||||
<em>
|
||||
{pending && sessions.replayProgress
|
||||
? progressCopy(sessions.replayProgress.phase)
|
||||
: observationSessionVisualLabel(visualState)}
|
||||
</em>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="observation-session-archive__delete"
|
||||
aria-label={`Удалить сохранённую сессию ${session.label}`}
|
||||
disabled={pending || deleting}
|
||||
onClick={() => setDeleteTarget(session)}
|
||||
>
|
||||
<Icon name="trash" size={15} />
|
||||
</button>
|
||||
</article>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : sessions.state === "loading" ? (
|
||||
<div className="observation-session-archive__empty" role="status">
|
||||
<span className="busy-indicator" aria-hidden="true" />
|
||||
<strong>Читаем каталог</strong>
|
||||
</div>
|
||||
) : (
|
||||
<div className="observation-session-archive__empty">
|
||||
<Icon name={sessions.error ? "alert" : "database"} size={18} />
|
||||
<strong>
|
||||
{sessions.error
|
||||
? "Каталог недоступен"
|
||||
: labsOnly
|
||||
? "LAB-записей пока нет"
|
||||
: "Сессий пока нет"}
|
||||
</strong>
|
||||
<span>
|
||||
{sessions.error
|
||||
?? (labsOnly
|
||||
? "Зафиксированные лабораторные записи появятся здесь автоматически."
|
||||
: "Завершённые записи появятся здесь автоматически.")}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{sessions.error && items.length > 0 ? (
|
||||
<footer className="observation-session-menu__error" role="alert">
|
||||
<Icon name="alert" size={13} />
|
||||
<span>{sessions.error}</span>
|
||||
{sessions.failedSessionId ? (
|
||||
<button type="button" onClick={() => void sessions.retry()}>
|
||||
Повторить
|
||||
</button>
|
||||
) : null}
|
||||
</footer>
|
||||
) : null}
|
||||
</section>
|
||||
|
||||
<ConfirmationModal
|
||||
open={deleteTarget !== null}
|
||||
title="Удалить сохранённую сессию?"
|
||||
description={deleteTarget ? <>
|
||||
<strong>{deleteTarget.label}</strong>
|
||||
{deleteTarget.lab ? (
|
||||
<p>
|
||||
Будут удалены только LAB-запись из каталога и её подготовленный кэш.
|
||||
Исходная запись {deleteTarget.lab.sourceSessionId} и зафиксированный
|
||||
лабораторный результат останутся неизменными.
|
||||
</p>
|
||||
) : (
|
||||
<p>
|
||||
Сессия, исходные данные наблюдения, подготовленная Rerun-запись и
|
||||
видеоматериалы будут удалены с этого сервера без возможности восстановления.
|
||||
</p>
|
||||
)}
|
||||
</> : null}
|
||||
confirmLabel="Удалить сессию"
|
||||
pendingLabel="Удаление…"
|
||||
danger
|
||||
onClose={() => {
|
||||
if (sessions.deletingSessionId === null) setDeleteTarget(null);
|
||||
}}
|
||||
onConfirm={async () => {
|
||||
if (!deleteTarget) return;
|
||||
if (await sessions.remove(deleteTarget.id)) setDeleteTarget(null);
|
||||
}}
|
||||
/>
|
||||
</>;
|
||||
}
|
||||
@@ -0,0 +1,263 @@
|
||||
import { Dropdown, Icon, type IconName } from "@nodedc/ui-react";
|
||||
|
||||
import type {
|
||||
ObservationSourceAvailability,
|
||||
ObservationSourceDescriptor,
|
||||
ObservationSourceModality,
|
||||
} from "../core/runtime/contracts";
|
||||
import { MseFmp4WebSocketPlayer } from "./MseFmp4WebSocketPlayer";
|
||||
import {
|
||||
RecordedFmp4Player,
|
||||
type RecordedObservationPlayback,
|
||||
} from "./RecordedFmp4Player";
|
||||
import type {
|
||||
RecordedAdmissionPhase,
|
||||
RecordedCameraAdmissionState,
|
||||
} from "../core/observation/recordedSessionAdmission";
|
||||
import { liveCameraPlaybackAuthorityIdentity } from "../core/observation/liveCameraRecovery";
|
||||
|
||||
const sourceIcon: Record<ObservationSourceModality, IconName> = {
|
||||
"point-cloud": "globe",
|
||||
video: "video",
|
||||
image: "image",
|
||||
depth: "image",
|
||||
};
|
||||
|
||||
const availabilityCopy: Record<ObservationSourceAvailability, string> = {
|
||||
unverified: "Не подтверждён",
|
||||
declared: "Канал объявлен",
|
||||
available: "Доступен",
|
||||
connecting: "Подключение",
|
||||
streaming: "Эфир",
|
||||
degraded: "Нестабильно",
|
||||
unavailable: "Недоступен",
|
||||
error: "Ошибка",
|
||||
};
|
||||
|
||||
export function observationSourceStatusLabel(source: ObservationSourceDescriptor): string {
|
||||
return availabilityCopy[source.availability];
|
||||
}
|
||||
|
||||
export function ObservationMedia({
|
||||
source,
|
||||
playback,
|
||||
prepareRecorded = true,
|
||||
recordedSessionGate = "ready",
|
||||
recordedAdmissionKey = null,
|
||||
onRecordedAdmissionChange,
|
||||
}: {
|
||||
source: ObservationSourceDescriptor;
|
||||
playback?: RecordedObservationPlayback | null;
|
||||
prepareRecorded?: boolean;
|
||||
recordedSessionGate?: RecordedAdmissionPhase;
|
||||
recordedAdmissionKey?: string | null;
|
||||
onRecordedAdmissionChange?: (
|
||||
sourceId: string,
|
||||
state: RecordedCameraAdmissionState,
|
||||
) => void;
|
||||
}) {
|
||||
const sourceSelected = source.activation ? source.activation.selected : true;
|
||||
const deliveryActive = Boolean(source.delivery && sourceSelected);
|
||||
|
||||
if (
|
||||
deliveryActive &&
|
||||
source.delivery?.kind === "mse-fmp4-websocket" &&
|
||||
source.modality === "video"
|
||||
) {
|
||||
return (
|
||||
<MseFmp4WebSocketPlayer
|
||||
delivery={source.delivery}
|
||||
label={source.label}
|
||||
recoveryAuthorityIdentity={liveCameraPlaybackAuthorityIdentity(source)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
deliveryActive &&
|
||||
source.delivery?.kind === "recorded-fmp4-manifest" &&
|
||||
source.modality === "video"
|
||||
) {
|
||||
if (!prepareRecorded) {
|
||||
return (
|
||||
<div className="observation-media__empty" role="status">
|
||||
<span className="busy-indicator" aria-hidden="true" />
|
||||
<strong>Ожидает подготовки</strong>
|
||||
<span>Канал будет проверен последовательно в рамках атомарной сессии.</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<RecordedFmp4Player
|
||||
source={source}
|
||||
playback={playback}
|
||||
prepare
|
||||
sessionGate={recordedSessionGate}
|
||||
admissionKey={recordedAdmissionKey}
|
||||
onAdmissionChange={(state) => onRecordedAdmissionChange?.(source.id, state)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (deliveryActive && source.delivery?.kind === "video-url" && source.modality === "video") {
|
||||
return (
|
||||
<video
|
||||
className="observation-media__asset"
|
||||
src={source.delivery.url}
|
||||
autoPlay
|
||||
muted
|
||||
playsInline
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
deliveryActive &&
|
||||
source.delivery?.kind === "image-url" &&
|
||||
(source.modality === "image" || source.modality === "depth")
|
||||
) {
|
||||
return <img className="observation-media__asset" src={source.delivery.url} alt={source.label} />;
|
||||
}
|
||||
|
||||
if (sourceSelected && source.previewUrl && source.modality === "video") {
|
||||
return (
|
||||
<video
|
||||
className="observation-media__asset"
|
||||
src={source.previewUrl}
|
||||
autoPlay
|
||||
muted
|
||||
playsInline
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
sourceSelected &&
|
||||
source.previewUrl &&
|
||||
(source.modality === "image" || source.modality === "depth")
|
||||
) {
|
||||
return <img className="observation-media__asset" src={source.previewUrl} alt={source.label} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="observation-media__empty">
|
||||
<Icon name={sourceIcon[source.modality]} size={20} />
|
||||
<strong>
|
||||
{source.modality === "video" && source.activation && !source.activation.selected
|
||||
? "Источник отключён"
|
||||
: observationSourceStatusLabel(source)}
|
||||
</strong>
|
||||
<span>
|
||||
{source.modality === "video"
|
||||
? source.activation?.controllable
|
||||
? source.activation.selected
|
||||
? "Повторите подключение — локальный адаптер перезапустит выбранную камеру"
|
||||
: "Канал остановлен оператором; при необходимости его можно открыть снова"
|
||||
: "Канал известен, browser-preview сейчас недоступен"
|
||||
: source.description}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function ObservationSourcePicker({
|
||||
sources,
|
||||
visibleSourceIds,
|
||||
pendingSourceIds,
|
||||
onToggle,
|
||||
}: {
|
||||
sources: readonly ObservationSourceDescriptor[];
|
||||
visibleSourceIds: ReadonlySet<string>;
|
||||
pendingSourceIds?: ReadonlySet<string>;
|
||||
onToggle: (sourceId: string) => void | Promise<boolean>;
|
||||
}) {
|
||||
const visibleCount = sources.filter((source) => visibleSourceIds.has(source.id)).length;
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
className="scene-source-picker"
|
||||
placement="bottom-start"
|
||||
width={340}
|
||||
offset={10}
|
||||
surfaceRole="dialog"
|
||||
surfaceClassName="observation-source-menu"
|
||||
trigger={({ open, toggle, setAnchorRef, setTriggerRef, surfaceId }) => (
|
||||
<button
|
||||
ref={(node) => {
|
||||
setAnchorRef(node);
|
||||
setTriggerRef(node);
|
||||
}}
|
||||
type="button"
|
||||
className="scene-source-picker__trigger"
|
||||
data-active={open || visibleCount > 0 ? "true" : undefined}
|
||||
aria-label="Источники данных сцены"
|
||||
aria-haspopup="dialog"
|
||||
aria-expanded={open}
|
||||
aria-controls={surfaceId}
|
||||
onClick={toggle}
|
||||
>
|
||||
<Icon name="database" size={17} />
|
||||
{sources.length > 0 ? <span>{visibleCount}</span> : null}
|
||||
</button>
|
||||
)}
|
||||
>
|
||||
<header className="observation-source-menu__head">
|
||||
<div>
|
||||
<span className="section-eyebrow">НАБЛЮДЕНИЕ</span>
|
||||
<strong>Источники данных</strong>
|
||||
</div>
|
||||
<small>{sources.length ? `${visibleCount} из ${sources.length}` : "нет источников"}</small>
|
||||
</header>
|
||||
{sources.length ? (
|
||||
<div className="observation-source-menu__list">
|
||||
{sources.map((source) => {
|
||||
const selected = visibleSourceIds.has(source.id);
|
||||
const pending = pendingSourceIds?.has(source.id) ?? false;
|
||||
const canOpen = Boolean(
|
||||
selected ||
|
||||
source.modality === "point-cloud" ||
|
||||
source.previewUrl ||
|
||||
source.delivery ||
|
||||
source.activation?.controllable,
|
||||
);
|
||||
return (
|
||||
<button
|
||||
key={source.id}
|
||||
type="button"
|
||||
className="nodedc-dropdown-option observation-source-option"
|
||||
data-selected={selected ? "true" : undefined}
|
||||
aria-pressed={selected}
|
||||
disabled={pending || !canOpen}
|
||||
onClick={() => void onToggle(source.id)}
|
||||
>
|
||||
<span className="nodedc-dropdown-option__icon">
|
||||
<Icon name={sourceIcon[source.modality]} size={16} />
|
||||
</span>
|
||||
<span className="nodedc-dropdown-option__body">
|
||||
<span className="nodedc-dropdown-option__label">{source.label}</span>
|
||||
<span className="nodedc-dropdown-option__description">
|
||||
<i data-availability={source.availability} aria-hidden="true" />
|
||||
{pending ? "Переключение" : observationSourceStatusLabel(source)} · {source.endpointLabel || source.transport}
|
||||
</span>
|
||||
</span>
|
||||
<span className="nodedc-dropdown-option__check">
|
||||
{selected ? <Icon name="check" size={15} /> : null}
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div className="observation-source-menu__empty">
|
||||
<Icon name="database" size={18} />
|
||||
<strong>Каталог пока пуст</strong>
|
||||
<span>Выберите профиль устройства — его плагин опубликует доступные каналы.</span>
|
||||
</div>
|
||||
)}
|
||||
<footer className="observation-source-menu__foot">
|
||||
Каналы приходят из активного контура или выбранной сохранённой сессии; сцена не знает
|
||||
модель оборудования.
|
||||
</footer>
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
import { Button, Icon, Select } from "@nodedc/ui-react";
|
||||
|
||||
import type { ObservationTimelineMode } from "../core/runtime/contracts";
|
||||
|
||||
export function ObservationTimeline({
|
||||
active,
|
||||
sourceCount,
|
||||
mode = "live-only",
|
||||
seekable = false,
|
||||
synchronization = "host-arrival-best-effort",
|
||||
rangeNs = null,
|
||||
currentNs = null,
|
||||
playing = false,
|
||||
onSeek,
|
||||
onPlayingChange,
|
||||
onJumpToEnd,
|
||||
showJumpToEnd = true,
|
||||
playbackRate,
|
||||
onPlaybackRateChange,
|
||||
accumulationSeconds,
|
||||
onAccumulationChange,
|
||||
onAccumulationCommit,
|
||||
className = "",
|
||||
}: {
|
||||
active: boolean;
|
||||
sourceCount: number;
|
||||
mode?: ObservationTimelineMode;
|
||||
seekable?: boolean;
|
||||
synchronization?: "host-arrival-best-effort" | "shared-clock" | "frame-accurate";
|
||||
rangeNs?: { min: number; max: number } | null;
|
||||
currentNs?: number | null;
|
||||
playing?: boolean;
|
||||
onSeek?: (timeNs: number) => void;
|
||||
onPlayingChange?: (playing: boolean) => void;
|
||||
onJumpToEnd?: () => void;
|
||||
showJumpToEnd?: boolean;
|
||||
playbackRate?: number;
|
||||
onPlaybackRateChange?: (rate: number) => void;
|
||||
accumulationSeconds?: number;
|
||||
onAccumulationChange?: (value: number) => void;
|
||||
onAccumulationCommit?: () => void;
|
||||
className?: string;
|
||||
}) {
|
||||
const playbackRange = normalizeTimelineRange(rangeNs);
|
||||
const buffered = Boolean(
|
||||
seekable &&
|
||||
(mode === "buffered" || mode === "recorded") &&
|
||||
playbackRange,
|
||||
);
|
||||
const elapsedSeconds = playbackRange
|
||||
? timelineOffsetSeconds(playbackRange, currentNs ?? playbackRange.min)
|
||||
: 0;
|
||||
const durationSeconds = playbackRange
|
||||
? Math.max(0, (playbackRange.max - playbackRange.min) / 1_000_000_000)
|
||||
: 0;
|
||||
const synchronizationLabel = {
|
||||
"host-arrival-best-effort": "Синхронизация по приходу",
|
||||
"shared-clock": "Общие часы",
|
||||
"frame-accurate": "Покадровая синхронизация",
|
||||
}[synchronization];
|
||||
const accumulationValue = accumulationSeconds === undefined
|
||||
? null
|
||||
: normalizeAccumulationSeconds(accumulationSeconds);
|
||||
return (
|
||||
<div
|
||||
className={`observation-timeline ${className}`.trim()}
|
||||
data-active={active ? "true" : undefined}
|
||||
data-mode={mode}
|
||||
data-accumulation={accumulationValue !== null ? "true" : undefined}
|
||||
>
|
||||
{accumulationValue !== null ? (
|
||||
<div className="observation-timeline__accumulation">
|
||||
<span>Накопление</span>
|
||||
<input
|
||||
className="observation-timeline__track"
|
||||
type="range"
|
||||
min={0}
|
||||
max={120}
|
||||
step={1}
|
||||
value={accumulationValue}
|
||||
aria-label="Окно накопления облака точек"
|
||||
aria-valuetext={formatAccumulationDuration(accumulationValue)}
|
||||
onChange={(event) =>
|
||||
onAccumulationChange?.(normalizeAccumulationSeconds(Number(event.target.value)))}
|
||||
onPointerUp={onAccumulationCommit}
|
||||
onTouchEnd={onAccumulationCommit}
|
||||
onKeyUp={onAccumulationCommit}
|
||||
onBlur={onAccumulationCommit}
|
||||
/>
|
||||
<code>{formatAccumulationDuration(accumulationValue)}</code>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="observation-timeline__playback">
|
||||
<Button
|
||||
size="compact"
|
||||
variant="ghost"
|
||||
disabled={!buffered || !onSeek}
|
||||
aria-label="Перейти к началу"
|
||||
onClick={() => playbackRange && onSeek?.(playbackRange.min)}
|
||||
>
|
||||
<Icon name="chevron-left" />
|
||||
</Button>
|
||||
<Button
|
||||
className="observation-timeline__transport"
|
||||
size="compact"
|
||||
variant="ghost"
|
||||
disabled={!buffered || !onPlayingChange}
|
||||
aria-label={buffered ? (playing ? "Пауза" : "Воспроизвести") : "Только эфир"}
|
||||
icon={<Icon name={playing ? "stop" : "play"} />}
|
||||
onClick={() => onPlayingChange?.(!playing)}
|
||||
/>
|
||||
{buffered && playbackRate !== undefined && onPlaybackRateChange ? (
|
||||
<Select
|
||||
label="Скорость воспроизведения"
|
||||
value={String(playbackRate)}
|
||||
options={[
|
||||
{ value: "0.5", label: "0,5×" },
|
||||
{ value: "1", label: "1×" },
|
||||
{ value: "2", label: "2×" },
|
||||
]}
|
||||
variant="inline"
|
||||
menuWidth="anchor"
|
||||
onChange={(value) => onPlaybackRateChange(Number(value))}
|
||||
/>
|
||||
) : null}
|
||||
<input
|
||||
className="observation-timeline__track"
|
||||
type="range"
|
||||
min={0}
|
||||
max={Math.max(durationSeconds, 0.001)}
|
||||
step={0.01}
|
||||
value={Math.min(elapsedSeconds, durationSeconds)}
|
||||
disabled={!buffered || !onSeek}
|
||||
aria-label="Позиция воспроизведения"
|
||||
onChange={(event) => {
|
||||
if (!playbackRange) return;
|
||||
onSeek?.(playbackRange.min + Number(event.target.value) * 1_000_000_000);
|
||||
}}
|
||||
/>
|
||||
<div className="observation-timeline__meta">
|
||||
<code>
|
||||
{buffered
|
||||
? `${formatTimelineDuration(elapsedSeconds)} / ${formatTimelineDuration(durationSeconds)}`
|
||||
: active ? "LIVE" : "—:—:—.———"}
|
||||
</code>
|
||||
<small>
|
||||
{buffered ? `${sourceCount} каналов` : `${synchronizationLabel} · буфер не включён`}
|
||||
</small>
|
||||
</div>
|
||||
{showJumpToEnd ? (
|
||||
<button
|
||||
type="button"
|
||||
className="observation-timeline__follow"
|
||||
data-active={!buffered && active ? "true" : undefined}
|
||||
disabled={buffered ? !onJumpToEnd : true}
|
||||
onClick={onJumpToEnd}
|
||||
>
|
||||
{buffered ? "К КОНЦУ" : "ЭФИР"}
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function normalizeAccumulationSeconds(value: number): number {
|
||||
if (!Number.isFinite(value)) return 0;
|
||||
return Math.min(120, Math.max(0, Math.round(value)));
|
||||
}
|
||||
|
||||
export function formatAccumulationDuration(value: number): string {
|
||||
const seconds = normalizeAccumulationSeconds(value);
|
||||
return seconds === 0 ? "Кадр" : `${seconds} с`;
|
||||
}
|
||||
|
||||
export function normalizeTimelineRange(
|
||||
range: { min: number; max: number } | null | undefined,
|
||||
): { min: number; max: number } | null {
|
||||
if (!range || !Number.isFinite(range.min) || !Number.isFinite(range.max)) return null;
|
||||
if (range.max <= range.min) return null;
|
||||
return range;
|
||||
}
|
||||
|
||||
export function timelineOffsetSeconds(
|
||||
range: { min: number; max: number },
|
||||
currentNs: number,
|
||||
): number {
|
||||
const clamped = Math.min(Math.max(currentNs, range.min), range.max);
|
||||
return Math.max(0, (clamped - range.min) / 1_000_000_000);
|
||||
}
|
||||
|
||||
export function formatTimelineDuration(seconds: number): string {
|
||||
if (!Number.isFinite(seconds) || seconds < 0) return "00:00.000";
|
||||
const wholeMinutes = Math.floor(seconds / 60);
|
||||
const remaining = seconds - wholeMinutes * 60;
|
||||
return `${String(wholeMinutes).padStart(2, "0")}:${remaining.toFixed(3).padStart(6, "0")}`;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,33 @@
|
||||
import { CanonicalResultRerunReplay } from "./CanonicalResultRerunReplay";
|
||||
import { resolveAICompositionReplay } from "../../core/laboratory/canonicalLabReplay";
|
||||
import type { ObservationSessionReplayLaunch } from "../../core/observation/sessionArchive";
|
||||
import type { AICompositionRun } from "../../core/observatory/aiComposition";
|
||||
|
||||
function resolveComposition(
|
||||
runId: string,
|
||||
launch: ObservationSessionReplayLaunch,
|
||||
options: { signal: AbortSignal },
|
||||
) {
|
||||
return resolveAICompositionReplay(runId, launch, options);
|
||||
}
|
||||
|
||||
export function AICompositionReplay({ run }: { run: AICompositionRun }) {
|
||||
const semantics = run.moduleIds.includes("ddrnet") || run.moduleIds.includes("eomt");
|
||||
const detections = run.moduleIds.includes("rf-detr")
|
||||
|| run.moduleIds.includes("object-distance");
|
||||
const costmap = run.moduleIds.includes("tgs");
|
||||
return (
|
||||
<CanonicalResultRerunReplay
|
||||
key={run.runId}
|
||||
resultId={run.runId}
|
||||
sessionId={run.sourceSessionId}
|
||||
resolveReplay={resolveComposition}
|
||||
semantics={semantics}
|
||||
detections={detections}
|
||||
costmap={costmap}
|
||||
moduleIds={run.moduleIds}
|
||||
viewerLayers={run.presentation.viewerLayers}
|
||||
label={run.configurationLabel}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
import { useCallback, useEffect, useState, type ReactNode } from "react";
|
||||
import { SegmentedControl, SplitPane, type SplitPaneOrientation } from "@nodedc/ui-react";
|
||||
|
||||
import { LaboratoryEvidenceViewer } from "./LaboratoryEvidenceViewer";
|
||||
|
||||
export const CANONICAL_RECORDED_LAB_REPLAY_CONTRACT =
|
||||
"missioncore.canonical-recorded-lab-replay/v1";
|
||||
|
||||
export interface CanonicalRecordedLabMode<T extends string> {
|
||||
value: T;
|
||||
label: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* The interaction contract from the accepted recorded-LAB instrument.
|
||||
*
|
||||
* Keeping pane selection, collapse semantics, responsive split orientation,
|
||||
* expansion and splitter state here prevents individual experiments from
|
||||
* quietly growing their own replay behaviour. Experiments provide evidence
|
||||
* layers; they do not reimplement the laboratory shell.
|
||||
*/
|
||||
export function useCanonicalRecordedLabReplayState<
|
||||
TMedia extends string,
|
||||
TSpatial extends string,
|
||||
>({
|
||||
initialMediaMode,
|
||||
initialSpatialMode,
|
||||
}: {
|
||||
initialMediaMode: TMedia;
|
||||
initialSpatialMode: TSpatial | null;
|
||||
}) {
|
||||
const [mediaMode, setMediaMode] = useState<TMedia | null>(initialMediaMode);
|
||||
const [spatialMode, setSpatialMode] = useState<TSpatial | null>(initialSpatialMode);
|
||||
const [splitPrimarySize, setSplitPrimarySize] = useState(50);
|
||||
const [splitOrientation, setSplitOrientation] = useState<SplitPaneOrientation>(() => (
|
||||
typeof window !== "undefined" && window.matchMedia("(max-width: 900px)").matches
|
||||
? "horizontal"
|
||||
: "vertical"
|
||||
));
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const query = window.matchMedia("(max-width: 900px)");
|
||||
const update = () => setSplitOrientation(query.matches ? "horizontal" : "vertical");
|
||||
update();
|
||||
query.addEventListener("change", update);
|
||||
return () => query.removeEventListener("change", update);
|
||||
}, []);
|
||||
|
||||
const onMediaModeChange = useCallback((next: TMedia | "none") => {
|
||||
if (next === "none") return;
|
||||
setMediaMode((current) => current === next ? null : next);
|
||||
}, []);
|
||||
const onSpatialModeChange = useCallback((next: TSpatial | "none") => {
|
||||
if (next === "none") return;
|
||||
setSpatialMode((current) => current === next ? null : next);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
mediaMode,
|
||||
spatialMode,
|
||||
splitView: mediaMode !== null && spatialMode !== null,
|
||||
splitPrimarySize,
|
||||
splitOrientation,
|
||||
expanded,
|
||||
onMediaModeChange,
|
||||
onSpatialModeChange,
|
||||
onSplitPrimarySizeChange: setSplitPrimarySize,
|
||||
onExpandedChange: setExpanded,
|
||||
};
|
||||
}
|
||||
|
||||
export function CanonicalRecordedLabReplay<
|
||||
TMedia extends string,
|
||||
TSpatial extends string,
|
||||
>({
|
||||
label,
|
||||
mediaMode,
|
||||
mediaModes,
|
||||
spatialMode,
|
||||
spatialModes,
|
||||
expanded,
|
||||
splitPrimarySize,
|
||||
splitOrientation,
|
||||
mediaAriaLabel,
|
||||
spatialAriaLabel,
|
||||
mediaLayerControls,
|
||||
spatialLayerControls,
|
||||
spatialLeadingControl,
|
||||
spatialTrailingControl,
|
||||
mediaModeControlsVisible = true,
|
||||
paneToolbarsAlwaysVisible = false,
|
||||
mediaMultiLayer = false,
|
||||
mediaContent,
|
||||
spatialContent,
|
||||
unifiedContent,
|
||||
emptyMessage,
|
||||
deckOverlays,
|
||||
actions,
|
||||
overlay,
|
||||
transport,
|
||||
trailingActions,
|
||||
onMediaModeChange,
|
||||
onSpatialModeChange,
|
||||
onExpandedChange,
|
||||
onSplitPrimarySizeChange,
|
||||
}: {
|
||||
label: string;
|
||||
mediaMode: TMedia;
|
||||
mediaModes: readonly CanonicalRecordedLabMode<TMedia>[];
|
||||
spatialMode: TSpatial;
|
||||
spatialModes: readonly CanonicalRecordedLabMode<TSpatial>[];
|
||||
expanded: boolean;
|
||||
splitPrimarySize: number;
|
||||
splitOrientation: SplitPaneOrientation;
|
||||
mediaAriaLabel: string;
|
||||
spatialAriaLabel: string;
|
||||
mediaLayerControls?: ReactNode;
|
||||
spatialLayerControls?: ReactNode;
|
||||
spatialLeadingControl?: ReactNode;
|
||||
spatialTrailingControl?: ReactNode;
|
||||
mediaModeControlsVisible?: boolean;
|
||||
/** Keeps renderer-local controls inside their viewport when only one pane is present. */
|
||||
paneToolbarsAlwaysVisible?: boolean;
|
||||
mediaMultiLayer?: boolean;
|
||||
mediaContent?: ReactNode;
|
||||
spatialContent?: ReactNode;
|
||||
/** One upstream Rerun viewer owns both panes; this controlled split owns their shared geometry. */
|
||||
unifiedContent?: ReactNode;
|
||||
emptyMessage: string;
|
||||
deckOverlays?: ReactNode;
|
||||
actions?: ReactNode;
|
||||
overlay?: ReactNode;
|
||||
transport?: ReactNode;
|
||||
trailingActions?: ReactNode;
|
||||
onMediaModeChange: (mode: TMedia) => void;
|
||||
onSpatialModeChange: (mode: TSpatial) => void;
|
||||
onExpandedChange: (expanded: boolean) => void;
|
||||
onSplitPrimarySizeChange: (size: number) => void;
|
||||
}) {
|
||||
const splitView = mediaMode !== "none" && spatialMode !== "none";
|
||||
const mediaModeControls = (
|
||||
<div className="m4-replay-threat-visual__pane-mode-controls" data-pane-mode="media">
|
||||
<SegmentedControl
|
||||
value={mediaMode}
|
||||
items={[...mediaModes]}
|
||||
label="Видео и камера"
|
||||
size="dense"
|
||||
onChange={onMediaModeChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
const spatialModeControls = (
|
||||
<div className="m4-replay-threat-visual__pane-mode-controls" data-pane-mode="spatial">
|
||||
<SegmentedControl
|
||||
value={spatialMode}
|
||||
items={[...spatialModes]}
|
||||
label="3D и план"
|
||||
size="dense"
|
||||
onChange={onSpatialModeChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
const mediaPane = (
|
||||
<section
|
||||
className="m4-replay-threat-visual__pane"
|
||||
data-pane="media"
|
||||
aria-label={mediaAriaLabel}
|
||||
hidden={mediaMode === "none"}
|
||||
>
|
||||
{splitView || paneToolbarsAlwaysVisible ? (
|
||||
<div
|
||||
className="m4-replay-threat-visual__pane-toolbar"
|
||||
data-pane-toolbar="media"
|
||||
data-multi-semantic={mediaMultiLayer ? "true" : undefined}
|
||||
>
|
||||
{mediaLayerControls}
|
||||
{mediaModeControlsVisible ? mediaModeControls : null}
|
||||
</div>
|
||||
) : null}
|
||||
{mediaContent}
|
||||
</section>
|
||||
);
|
||||
const spatialPane = spatialMode !== "none" ? (
|
||||
<section
|
||||
className="m4-replay-threat-visual__pane"
|
||||
data-pane="spatial"
|
||||
aria-label={spatialAriaLabel}
|
||||
>
|
||||
{splitView || paneToolbarsAlwaysVisible ? (
|
||||
<div
|
||||
className="m4-replay-threat-visual__pane-toolbar"
|
||||
data-pane-toolbar="spatial"
|
||||
>
|
||||
{spatialLeadingControl}
|
||||
<div className="m4-replay-threat-visual__spatial-toolbar-end">
|
||||
{spatialLayerControls}
|
||||
{spatialModeControls}
|
||||
{spatialTrailingControl}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
{spatialContent}
|
||||
</section>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="l3-visual-audit m4-replay-threat-visual"
|
||||
data-contract={CANONICAL_RECORDED_LAB_REPLAY_CONTRACT}
|
||||
>
|
||||
<LaboratoryEvidenceViewer
|
||||
label={label}
|
||||
className="m4-replay-threat-evidence-viewer"
|
||||
mode={mediaMode}
|
||||
modes={[...mediaModes]}
|
||||
secondaryMode={{
|
||||
value: spatialMode,
|
||||
modes: [...spatialModes],
|
||||
label: "3D и план",
|
||||
onChange: onSpatialModeChange,
|
||||
}}
|
||||
expanded={expanded}
|
||||
onModeChange={onMediaModeChange}
|
||||
onExpandedChange={onExpandedChange}
|
||||
modeControlsVisible={!splitView && !paneToolbarsAlwaysVisible}
|
||||
actions={actions}
|
||||
overlay={overlay}
|
||||
transport={transport}
|
||||
trailingActions={trailingActions}
|
||||
>
|
||||
<div
|
||||
className="m4-replay-threat-visual__deck"
|
||||
data-split={splitView ? "true" : undefined}
|
||||
data-empty={mediaMode === "none" && spatialMode === "none" ? "true" : undefined}
|
||||
data-native-rerun={unifiedContent ? "true" : undefined}
|
||||
>
|
||||
{unifiedContent ? (
|
||||
<div className="m4-replay-threat-visual__unified-content">
|
||||
{unifiedContent}
|
||||
</div>
|
||||
) : null}
|
||||
<SplitPane
|
||||
primary={mediaPane}
|
||||
secondary={spatialPane ?? <div />}
|
||||
primarySize={splitView ? splitPrimarySize : mediaMode !== "none" ? 100 : 0}
|
||||
onPrimarySizeChange={onSplitPrimarySizeChange}
|
||||
orientation={splitOrientation}
|
||||
minPrimarySize={splitView ? 24 : 0}
|
||||
minSecondarySize={splitView ? 24 : 0}
|
||||
resizable={splitView}
|
||||
separatorLabel="Изменить размер видео/камеры и 3D/плана"
|
||||
/>
|
||||
{mediaMode === "none" && spatialMode === "none" ? (
|
||||
<div className="l3-visual-audit__state" role="status">
|
||||
{emptyMessage}
|
||||
</div>
|
||||
) : null}
|
||||
{deckOverlays}
|
||||
</div>
|
||||
</LaboratoryEvidenceViewer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,524 @@
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Button,
|
||||
Checker,
|
||||
ControlRow,
|
||||
Icon,
|
||||
IconButton,
|
||||
Inspector,
|
||||
InspectorSelectField,
|
||||
RangeControl,
|
||||
ToastStack,
|
||||
Window,
|
||||
type ToastItem,
|
||||
} from "@nodedc/ui-react";
|
||||
|
||||
import { ObservationTimeline } from "../ObservationTimeline";
|
||||
import {
|
||||
RerunViewport,
|
||||
isRecordedPlaybackPresentationReady,
|
||||
type RerunPlaybackController,
|
||||
type RerunPlaybackState,
|
||||
type RerunViewportStatus,
|
||||
} from "../RerunViewport";
|
||||
import {
|
||||
CanonicalRecordedLabReplay,
|
||||
useCanonicalRecordedLabReplayState,
|
||||
} from "../laboratory/CanonicalRecordedLabReplay";
|
||||
import type { CanonicalLabReplayDescriptor } from "../../core/laboratory/canonicalLabReplay";
|
||||
import type { ObservationSessionReplayLaunch } from "../../core/observation/sessionArchive";
|
||||
import { recordedSessionRerunProfile } from "../../core/observation/viewerProfile";
|
||||
import { resolveObservationSessionReplay } from "../../core/observation/useObservationSessions";
|
||||
import type { AIViewerLayer } from "../../core/observatory/aiComposition";
|
||||
import {
|
||||
fetchLabViewProfile,
|
||||
saveLabViewProfile,
|
||||
} from "../../core/observatory/labViewProfile";
|
||||
import {
|
||||
defaultSceneSettings,
|
||||
type PointColorMode,
|
||||
type PointPalette,
|
||||
type SceneSettings,
|
||||
} from "../../sceneSettings";
|
||||
|
||||
type MediaMode = "camera";
|
||||
type SpatialMode = "3d" | "plan";
|
||||
|
||||
interface CanonicalReplayLaunch {
|
||||
base: ObservationSessionReplayLaunch;
|
||||
replay: CanonicalLabReplayDescriptor;
|
||||
}
|
||||
|
||||
const RERUN_UNIFIED_CAMERA_SHARE_PERCENT = 46;
|
||||
const LAB_SCENE_SETTINGS_STORAGE_PREFIX = "missioncore.observatory.lab-scene-settings.v1:";
|
||||
const LAB_VIEW_PROFILE_ERROR_TOAST = "observatory-lab-view-profile-error";
|
||||
|
||||
const COLOR_OPTIONS: readonly { value: PointColorMode; label: string }[] = [
|
||||
{ value: "intensity", label: "Интенсивность" },
|
||||
{ value: "height", label: "Высота Z" },
|
||||
{ value: "distance", label: "Дистанция" },
|
||||
{ value: "rgb", label: "RGB" },
|
||||
{ value: "class", label: "Класс" },
|
||||
];
|
||||
|
||||
const PALETTE_OPTIONS: readonly { value: PointPalette; label: string }[] = [
|
||||
{ value: "turbo", label: "Turbo" },
|
||||
{ value: "viridis", label: "Viridis" },
|
||||
{ value: "plasma", label: "Plasma" },
|
||||
{ value: "grayscale", label: "Серый" },
|
||||
];
|
||||
|
||||
const LAB_SCENE_DEFAULTS: SceneSettings = {
|
||||
...defaultSceneSettings,
|
||||
pointSize: 3.8,
|
||||
accumulationSeconds: 5,
|
||||
};
|
||||
|
||||
export function normalizeLabSceneSettings(settings: SceneSettings): SceneSettings {
|
||||
const pointSize = Number.isFinite(settings.pointSize) ? settings.pointSize : LAB_SCENE_DEFAULTS.pointSize;
|
||||
const accumulationSeconds = Number.isFinite(settings.accumulationSeconds)
|
||||
? settings.accumulationSeconds : LAB_SCENE_DEFAULTS.accumulationSeconds;
|
||||
return {
|
||||
...settings,
|
||||
pointSize: Math.round(Math.max(0.1, pointSize) * 10) / 10,
|
||||
accumulationSeconds: Math.round(Math.max(0, accumulationSeconds)),
|
||||
};
|
||||
}
|
||||
|
||||
function loadLabSceneSettings(resultId: string): SceneSettings {
|
||||
if (typeof window === "undefined") return LAB_SCENE_DEFAULTS;
|
||||
try {
|
||||
const raw = window.localStorage.getItem(`${LAB_SCENE_SETTINGS_STORAGE_PREFIX}${resultId}`);
|
||||
if (raw === null) return LAB_SCENE_DEFAULTS;
|
||||
const value: unknown = JSON.parse(raw);
|
||||
if (typeof value !== "object" || value === null || Array.isArray(value)) return LAB_SCENE_DEFAULTS;
|
||||
const row = value as Record<string, unknown>;
|
||||
const pointSize = row.pointSize;
|
||||
const accumulationSeconds = row.accumulationSeconds;
|
||||
const colorMode = row.colorMode;
|
||||
const palette = row.palette;
|
||||
const showGrid = row.showGrid;
|
||||
const showLabels = row.showLabels;
|
||||
const showCameraFrustums = row.showCameraFrustums;
|
||||
if (
|
||||
typeof pointSize !== "number" || !Number.isFinite(pointSize) || pointSize < 0.1
|
||||
|| typeof accumulationSeconds !== "number" || !Number.isFinite(accumulationSeconds)
|
||||
|| accumulationSeconds < 0
|
||||
|| !COLOR_OPTIONS.some((option) => option.value === colorMode)
|
||||
|| !PALETTE_OPTIONS.some((option) => option.value === palette)
|
||||
|| typeof showGrid !== "boolean"
|
||||
|| typeof showLabels !== "boolean"
|
||||
|| typeof showCameraFrustums !== "boolean"
|
||||
) return LAB_SCENE_DEFAULTS;
|
||||
return {
|
||||
...LAB_SCENE_DEFAULTS,
|
||||
pointSize,
|
||||
accumulationSeconds,
|
||||
colorMode: colorMode as PointColorMode,
|
||||
palette: palette as PointPalette,
|
||||
showGrid,
|
||||
showLabels,
|
||||
showCameraFrustums,
|
||||
};
|
||||
} catch {
|
||||
return LAB_SCENE_DEFAULTS;
|
||||
}
|
||||
}
|
||||
|
||||
export function CanonicalResultRerunReplay({
|
||||
resultId,
|
||||
sessionId,
|
||||
initialPlaybackStartSeconds,
|
||||
resolveReplay,
|
||||
semantics = false,
|
||||
detections = false,
|
||||
costmap = false,
|
||||
moduleIds = [],
|
||||
viewerLayers = [],
|
||||
label = "Сохранённый результат",
|
||||
}: {
|
||||
resultId: string;
|
||||
sessionId: string;
|
||||
initialPlaybackStartSeconds?: number;
|
||||
resolveReplay: (resultId: string, launch: ObservationSessionReplayLaunch, options: { signal: AbortSignal }) => Promise<CanonicalLabReplayDescriptor>;
|
||||
semantics?: boolean;
|
||||
detections?: boolean;
|
||||
costmap?: boolean;
|
||||
portable?: boolean;
|
||||
moduleIds?: readonly string[];
|
||||
viewerLayers?: readonly AIViewerLayer[];
|
||||
label?: string;
|
||||
}) {
|
||||
const {
|
||||
mediaMode,
|
||||
spatialMode,
|
||||
splitPrimarySize,
|
||||
splitOrientation,
|
||||
expanded,
|
||||
onMediaModeChange,
|
||||
onSpatialModeChange,
|
||||
onSplitPrimarySizeChange,
|
||||
onExpandedChange,
|
||||
} = useCanonicalRecordedLabReplayState<MediaMode, SpatialMode>({
|
||||
initialMediaMode: "camera",
|
||||
initialSpatialMode: "3d",
|
||||
});
|
||||
const layerIds = useMemo(
|
||||
() => new Set(viewerLayers.map((layer) => layer.layerId)),
|
||||
[viewerLayers],
|
||||
);
|
||||
const hasProjectedLayers = viewerLayers.length > 0;
|
||||
const hasDDRNet = layerIds.has("camera.ddrnet")
|
||||
|| (!hasProjectedLayers && semantics && moduleIds.includes("ddrnet"));
|
||||
const hasEoMT = layerIds.has("camera.eomt")
|
||||
|| (!hasProjectedLayers && semantics && moduleIds.includes("eomt"));
|
||||
const hasFallbackSemantics = semantics && !hasDDRNet && !hasEoMT;
|
||||
const hasDetections = layerIds.has("camera.detections") || (!hasProjectedLayers && detections);
|
||||
const hasTgs = layerIds.has("spatial.tgs") || (!hasProjectedLayers && costmap);
|
||||
const legacyUnclassified = !hasProjectedLayers
|
||||
&& moduleIds.length === 0
|
||||
&& !semantics
|
||||
&& !detections
|
||||
&& !costmap;
|
||||
const hasCameraPane = layerIds.has("camera.source")
|
||||
|| (!hasProjectedLayers && (legacyUnclassified || semantics || detections));
|
||||
const hasSourcePoints = layerIds.has("spatial.source-points")
|
||||
|| (!hasProjectedLayers && (legacyUnclassified || costmap || moduleIds.includes("object-distance")));
|
||||
const hasLocalSlam = layerIds.has("spatial.local-slam")
|
||||
|| (!hasProjectedLayers && (legacyUnclassified || costmap || moduleIds.includes("object-distance")));
|
||||
const hasSpatialPane = hasSourcePoints || hasLocalSlam || hasTgs;
|
||||
const splitView = hasCameraPane && hasSpatialPane;
|
||||
|
||||
const [showCamera, setShowCamera] = useState(true);
|
||||
const [showDDRNet, setShowDDRNet] = useState(hasDDRNet || hasFallbackSemantics);
|
||||
const [showEoMT, setShowEoMT] = useState(hasEoMT);
|
||||
const [showDetections, setShowDetections] = useState(hasDetections);
|
||||
const [showSourcePoints, setShowSourcePoints] = useState(hasSourcePoints);
|
||||
const [showLocalSlam, setShowLocalSlam] = useState(hasLocalSlam);
|
||||
const [showTgs, setShowTgs] = useState(hasTgs);
|
||||
const [followTarget, setFollowTarget] = useState(false);
|
||||
const [settingsOpen, setSettingsOpen] = useState(false);
|
||||
const [sceneDraft, setSceneDraft] = useState<SceneSettings>(() => loadLabSceneSettings(resultId));
|
||||
const [blueprintSplitPrimarySize, setBlueprintSplitPrimarySize] = useState(
|
||||
RERUN_UNIFIED_CAMERA_SHARE_PERCENT,
|
||||
);
|
||||
const [toasts, setToasts] = useState<ToastItem[]>([]);
|
||||
const [viewResetGeneration, setViewResetGeneration] = useState<0 | 1>(hasTgs ? 1 : 0);
|
||||
const [playback, setPlayback] = useState<RerunPlaybackState | null>(null);
|
||||
const [playbackController, setPlaybackController] = useState<RerunPlaybackController | null>(null);
|
||||
const [viewerStatus, setViewerStatus] = useState<RerunViewportStatus>("idle");
|
||||
const [launch, setLaunch] = useState<CanonicalReplayLaunch | null>(null);
|
||||
const [launchError, setLaunchError] = useState<string | null>(null);
|
||||
const sceneDraftRef = useRef(sceneDraft);
|
||||
const previousSplitViewRef = useRef(splitView);
|
||||
const trackedLaunchSha256Ref = useRef<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const launchSha256 = launch?.replay.sha256 ?? null;
|
||||
if (trackedLaunchSha256Ref.current !== launchSha256 || (splitView && !previousSplitViewRef.current)) {
|
||||
onSplitPrimarySizeChange(RERUN_UNIFIED_CAMERA_SHARE_PERCENT);
|
||||
setBlueprintSplitPrimarySize(RERUN_UNIFIED_CAMERA_SHARE_PERCENT);
|
||||
}
|
||||
trackedLaunchSha256Ref.current = launchSha256;
|
||||
previousSplitViewRef.current = splitView;
|
||||
}, [launch?.replay.sha256, onSplitPrimarySizeChange, splitView]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!splitView) return;
|
||||
const timer = window.setTimeout(() => setBlueprintSplitPrimarySize(splitPrimarySize), 75);
|
||||
return () => window.clearTimeout(timer);
|
||||
}, [splitPrimarySize, splitView]);
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
window.localStorage.setItem(
|
||||
`${LAB_SCENE_SETTINGS_STORAGE_PREFIX}${resultId}`,
|
||||
JSON.stringify({
|
||||
pointSize: sceneDraft.pointSize,
|
||||
accumulationSeconds: sceneDraft.accumulationSeconds,
|
||||
colorMode: sceneDraft.colorMode,
|
||||
palette: sceneDraft.palette,
|
||||
showGrid: sceneDraft.showGrid,
|
||||
showLabels: sceneDraft.showLabels,
|
||||
showCameraFrustums: sceneDraft.showCameraFrustums,
|
||||
}),
|
||||
);
|
||||
} catch {
|
||||
// The visual remains usable when the browser refuses local persistence.
|
||||
}
|
||||
}, [resultId, sceneDraft]);
|
||||
|
||||
useEffect(() => {
|
||||
const controller = new AbortController();
|
||||
void fetchLabViewProfile(resultId, controller.signal).then((settings) => {
|
||||
if (!controller.signal.aborted && settings) {
|
||||
sceneDraftRef.current = settings;
|
||||
setSceneDraft(settings);
|
||||
}
|
||||
}).catch(() => {
|
||||
// The per-browser copy remains a usable fallback while the local service recovers.
|
||||
});
|
||||
return () => controller.abort();
|
||||
}, [resultId]);
|
||||
|
||||
useEffect(() => {
|
||||
const controller = new AbortController();
|
||||
setLaunch(null);
|
||||
setLaunchError(null);
|
||||
setPlayback(null);
|
||||
setPlaybackController(null);
|
||||
setViewerStatus("idle");
|
||||
void resolveObservationSessionReplay(sessionId, {
|
||||
signal: controller.signal,
|
||||
maximumWaitMs: 30 * 60 * 1000,
|
||||
onUpdate: () => undefined,
|
||||
}).then(async (value) => ({
|
||||
base: value,
|
||||
replay: await resolveReplay(resultId, value, { signal: controller.signal }),
|
||||
})).then((value) => {
|
||||
if (!controller.signal.aborted) setLaunch(value);
|
||||
}).catch((caught: unknown) => {
|
||||
if (!controller.signal.aborted) {
|
||||
setLaunchError(caught instanceof Error ? caught.message : "Сохранённая запись недоступна.");
|
||||
}
|
||||
});
|
||||
return () => controller.abort();
|
||||
}, [resultId, sessionId, resolveReplay]);
|
||||
|
||||
const presentationReady = playbackController !== null
|
||||
&& isRecordedPlaybackPresentationReady(viewerStatus, playback);
|
||||
const presentationState = launchError || viewerStatus === "error"
|
||||
? "error"
|
||||
: presentationReady ? "ready" : "loading";
|
||||
const sceneSettings = useMemo<SceneSettings>(() => ({
|
||||
...sceneDraft,
|
||||
projection: spatialMode === "plan" ? "2d" : "3d",
|
||||
showPoints: spatialMode !== null && (showSourcePoints || showLocalSlam),
|
||||
showTrajectory: spatialMode !== null && showLocalSlam,
|
||||
accumulationSeconds: showLocalSlam ? sceneDraft.accumulationSeconds : 0,
|
||||
}), [sceneDraft, showLocalSlam, showSourcePoints, spatialMode]);
|
||||
const semanticVisible = showDDRNet || showEoMT;
|
||||
const profile = launch ? recordedSessionRerunProfile({
|
||||
sourceUrl: launch.replay.sourceUrl,
|
||||
artifact: {
|
||||
sourceUrl: launch.replay.sourceUrl,
|
||||
viewerSourceUrl: launch.replay.viewerSourceUrl,
|
||||
byteLength: launch.replay.byteLength,
|
||||
sha256: launch.replay.sha256,
|
||||
},
|
||||
blueprintSourceUrl: launch.replay.blueprintSourceUrl,
|
||||
autoplayWhenReady: false,
|
||||
presentationGate: "ready",
|
||||
expectedTimelineStartSeconds: launch.base.timelineStartSeconds,
|
||||
expectedTimelineEndSeconds: launch.base.timelineEndSeconds,
|
||||
initialPlaybackStartSeconds: initialPlaybackStartSeconds
|
||||
?? ((launch.base.mediaSources[0]?.timelineStartSeconds ?? launch.base.timelineStartSeconds)
|
||||
+ (hasTgs ? 0.000001 : 0)),
|
||||
view: hasCameraPane && !hasSpatialPane ? "perception" : "spatial",
|
||||
viewResetGeneration,
|
||||
followTrajectory: followTarget,
|
||||
semanticLayer: showEoMT ? "city" : "vegetation",
|
||||
unifiedPerception: splitView,
|
||||
unifiedCameraShare: blueprintSplitPrimarySize / 100,
|
||||
planView: spatialMode === "plan",
|
||||
perceptionLayers: {
|
||||
enabled: true,
|
||||
cameraImage: showCamera,
|
||||
detections2d: hasDetections && showDetections,
|
||||
segmentation: semantics && semanticVisible,
|
||||
cuboids3d: false,
|
||||
costmap: hasTgs && showTgs,
|
||||
},
|
||||
perceptionRetryGeneration: 0,
|
||||
lockPerceptionCameraInteraction: false,
|
||||
}) : null;
|
||||
|
||||
const toggle = (caption: string, active: boolean, onClick: () => void) => (
|
||||
<Button key={caption} size="dense" shape="pill" variant={active ? "primary" : "secondary"}
|
||||
aria-pressed={active} onClick={onClick}>
|
||||
{caption}
|
||||
</Button>
|
||||
);
|
||||
const openSlamSettings = useCallback(() => {
|
||||
if (expanded) onExpandedChange(false);
|
||||
setSettingsOpen(true);
|
||||
}, [expanded, onExpandedChange]);
|
||||
const patchSceneDraft = useCallback((patch: Partial<SceneSettings>) => {
|
||||
const next = { ...sceneDraftRef.current, ...patch };
|
||||
sceneDraftRef.current = next;
|
||||
setSceneDraft(next);
|
||||
}, []);
|
||||
const closeSlamSettings = useCallback(() => {
|
||||
const next = normalizeLabSceneSettings(sceneDraftRef.current);
|
||||
sceneDraftRef.current = next;
|
||||
setSceneDraft(next);
|
||||
setSettingsOpen(false);
|
||||
void saveLabViewProfile(resultId, next).then((settings) => {
|
||||
sceneDraftRef.current = settings;
|
||||
setSceneDraft(settings);
|
||||
setToasts((current) => current.filter(({ id }) => id !== LAB_VIEW_PROFILE_ERROR_TOAST));
|
||||
}).catch(() => {
|
||||
setToasts((current) => [
|
||||
...current.filter(({ id }) => id !== LAB_VIEW_PROFILE_ERROR_TOAST),
|
||||
{
|
||||
id: LAB_VIEW_PROFILE_ERROR_TOAST,
|
||||
tone: "error",
|
||||
title: "Настройки LAB не сохранились",
|
||||
description: "Изменения остались в текущем просмотре. Откройте настройки и повторите.",
|
||||
},
|
||||
]);
|
||||
});
|
||||
}, [resultId]);
|
||||
const mediaLayerControls = (
|
||||
<div className="m4-replay-threat-visual__pane-layer-controls" role="group" aria-label="Слои камеры">
|
||||
{toggle("КАМЕРА", showCamera, () => setShowCamera((value) => !value))}
|
||||
{hasEoMT ? toggle("EOMT", showEoMT, () => setShowEoMT((value) => !value)) : null}
|
||||
{hasDDRNet || hasFallbackSemantics
|
||||
? toggle("DDRNET", showDDRNet, () => setShowDDRNet((value) => !value)) : null}
|
||||
{hasDetections
|
||||
? toggle("РАМКИ", showDetections, () => setShowDetections((value) => !value)) : null}
|
||||
</div>
|
||||
);
|
||||
const spatialLayerControls = (
|
||||
<div className="m4-replay-threat-visual__pane-layer-controls" role="group" aria-label="Слои облака точек">
|
||||
{hasSourcePoints
|
||||
? toggle("ИСХ. ТОЧКИ", showSourcePoints, () => setShowSourcePoints((value) => !value)) : null}
|
||||
{hasLocalSlam ? (
|
||||
<div className="m4-replay-threat-visual__layer-with-settings">
|
||||
{toggle("ЛОК. SLAM", showLocalSlam, () => setShowLocalSlam((value) => !value))}
|
||||
<IconButton label="Настройки облака SLAM" onClick={openSlamSettings}>
|
||||
<Icon name="settings" size={14} />
|
||||
</IconButton>
|
||||
</div>
|
||||
) : null}
|
||||
{hasTgs ? toggle("TGS", showTgs, () => setShowTgs((value) => !value)) : null}
|
||||
</div>
|
||||
);
|
||||
const followTargetControl = toggle(
|
||||
"СЛЕДОВАТЬ",
|
||||
followTarget,
|
||||
() => setFollowTarget((value) => !value),
|
||||
);
|
||||
const resetSpatialView = (
|
||||
<IconButton label="Сбросить положение видов"
|
||||
onClick={() => setViewResetGeneration((value) => value === 0 ? 1 : 0)}>
|
||||
<Icon name="refresh" size={16} />
|
||||
</IconButton>
|
||||
);
|
||||
|
||||
const transport = presentationReady && playback && playbackController ? (
|
||||
<ObservationTimeline
|
||||
className="m4-replay-threat-visual__timeline canonical-vegetation-rerun-replay__timeline"
|
||||
active
|
||||
sourceCount={3}
|
||||
mode="recorded"
|
||||
seekable
|
||||
synchronization="shared-clock"
|
||||
rangeNs={playback.rangeNs}
|
||||
currentNs={playback.currentNs}
|
||||
playing={playback.playing}
|
||||
onSeek={playbackController.seek}
|
||||
onPlayingChange={playbackController.setPlaying}
|
||||
showJumpToEnd={false}
|
||||
/>
|
||||
) : undefined;
|
||||
|
||||
return (
|
||||
<div className="canonical-vegetation-rerun-replay" data-presentation-state={presentationState}
|
||||
aria-busy={presentationState === "loading"}>
|
||||
<CanonicalRecordedLabReplay
|
||||
label={label}
|
||||
mediaMode={hasCameraPane ? mediaMode ?? "camera" : "none"}
|
||||
mediaModes={[{ value: "camera", label: "КАМЕРА" }]}
|
||||
spatialMode={hasSpatialPane ? spatialMode ?? "3d" : "none"}
|
||||
spatialModes={[{ value: "3d", label: "3D" }, { value: "plan", label: "ПЛАН" }]}
|
||||
expanded={expanded}
|
||||
splitPrimarySize={splitPrimarySize}
|
||||
splitOrientation={splitView ? "vertical" : splitOrientation}
|
||||
mediaAriaLabel="Камера и рассчитанные слои"
|
||||
spatialAriaLabel={spatialMode === "plan" ? "Вид сверху" : "Трёхмерная сцена"}
|
||||
mediaLayerControls={mediaLayerControls}
|
||||
spatialLayerControls={spatialLayerControls}
|
||||
spatialTrailingControl={followTargetControl}
|
||||
mediaModeControlsVisible={false}
|
||||
paneToolbarsAlwaysVisible
|
||||
mediaMultiLayer
|
||||
actions={resetSpatialView}
|
||||
unifiedContent={profile ? (
|
||||
<div className="canonical-vegetation-rerun-replay__viewport-lock"
|
||||
data-split-view={splitView ? "true" : undefined}>
|
||||
<RerunViewport profile={profile} sceneSettings={sceneSettings}
|
||||
onStatusChange={setViewerStatus} onPlaybackChange={setPlayback}
|
||||
onPlaybackControllerChange={setPlaybackController} />
|
||||
</div>
|
||||
) : launchError ? (
|
||||
<div className="l3-visual-audit__state" role="alert">{launchError}</div>
|
||||
) : <div aria-hidden="true" />}
|
||||
emptyMessage="Слои результата недоступны."
|
||||
deckOverlays={presentationState === "loading" ? (
|
||||
<div className="canonical-vegetation-rerun-replay__loading">
|
||||
<ActivityIndicator label="Загружаем синхронизированную запись" />
|
||||
</div>
|
||||
) : undefined}
|
||||
transport={transport}
|
||||
onMediaModeChange={onMediaModeChange}
|
||||
onSpatialModeChange={onSpatialModeChange}
|
||||
onExpandedChange={onExpandedChange}
|
||||
onSplitPrimarySizeChange={onSplitPrimarySizeChange}
|
||||
/>
|
||||
<Window open={settingsOpen} title="Отображение" subtitle="Параметры облака SLAM"
|
||||
placement="end" size="sm" onClose={closeSlamSettings}>
|
||||
<Inspector defaultOpen={["points"]} sections={[
|
||||
{
|
||||
id: "points",
|
||||
label: "Облако точек",
|
||||
description: "Размер и способ окрашивания",
|
||||
content: <>
|
||||
<RangeControl label="Размер точки" min={0.1} max={10} step={0.1}
|
||||
exactValueBounds={{ min: 0.1 }}
|
||||
value={sceneDraft.pointSize} formatValue={(value) => `${value.toFixed(1)} пкс`}
|
||||
onChange={(pointSize) => patchSceneDraft({ pointSize })} />
|
||||
<InspectorSelectField label="Атрибут цвета" value={sceneDraft.colorMode}
|
||||
options={[...COLOR_OPTIONS]}
|
||||
onChange={(colorMode) => patchSceneDraft({ colorMode })} />
|
||||
<InspectorSelectField label="Палитра" value={sceneDraft.palette}
|
||||
options={[...PALETTE_OPTIONS]}
|
||||
onChange={(palette) => patchSceneDraft({ palette })} />
|
||||
</>,
|
||||
},
|
||||
{
|
||||
id: "history",
|
||||
label: "Накопление и время",
|
||||
description: "История облака и траектория",
|
||||
content: <RangeControl label="Накопление" min={0} max={500} step={1}
|
||||
exactValueBounds={{ min: 0 }}
|
||||
value={sceneDraft.accumulationSeconds} formatValue={(value) => `${value} с`}
|
||||
onChange={(accumulationSeconds) => patchSceneDraft({ accumulationSeconds })} />,
|
||||
},
|
||||
{
|
||||
id: "environment",
|
||||
label: "Окружение сцены",
|
||||
description: "Сетка, подписи и камеры",
|
||||
content: <>
|
||||
<ControlRow label="Сетка"><Checker checked={sceneDraft.showGrid} label="Показывать"
|
||||
onChange={(showGrid) => patchSceneDraft({ showGrid })} /></ControlRow>
|
||||
<ControlRow label="Подписи"><Checker checked={sceneDraft.showLabels} label="Показывать"
|
||||
onChange={(showLabels) => patchSceneDraft({ showLabels })} /></ControlRow>
|
||||
<ControlRow label="Камеры"><Checker checked={sceneDraft.showCameraFrustums} label="Показывать"
|
||||
onChange={(showCameraFrustums) => patchSceneDraft({ showCameraFrustums })} /></ControlRow>
|
||||
</>,
|
||||
},
|
||||
]} />
|
||||
</Window>
|
||||
<ToastStack items={toasts}
|
||||
onDismiss={(id) => setToasts((current) => current.filter((item) => item.id !== id))} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { CanonicalResultRerunReplay } from "./CanonicalResultRerunReplay";
|
||||
import { resolveCanonicalLabReplay } from "../../core/laboratory/canonicalLabReplay";
|
||||
import type { VegetationFullRouteReview } from "../../core/laboratory/vegetationShadow";
|
||||
|
||||
export function CanonicalVegetationRerunReplay({ resultId, review }: {
|
||||
resultId: string;
|
||||
review: VegetationFullRouteReview;
|
||||
}) {
|
||||
return <CanonicalResultRerunReplay resultId={resultId} sessionId={review.sessionId}
|
||||
initialPlaybackStartSeconds={review.timelineStartSeconds} semantics
|
||||
resolveReplay={resolveCanonicalLabReplay} label="RAVNOVES004TREE · канонический повтор Rerun" />;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import { StatusBadge } from "@nodedc/ui-react";
|
||||
|
||||
import type { E30EngineeringGeneration } from "../../core/laboratory/e30Engineering";
|
||||
|
||||
export function E30EngineeringGenerationSummary({
|
||||
generation,
|
||||
}: {
|
||||
generation: E30EngineeringGeneration;
|
||||
}) {
|
||||
const summary = generation.summary;
|
||||
const confirmed = summary.verdictDistribution.confirmed ?? 0;
|
||||
const corrected = summary.verdictDistribution.corrected ?? 0;
|
||||
|
||||
return (
|
||||
<section
|
||||
className="e30-engineering-generation"
|
||||
aria-label="Результат A3 engineering generation"
|
||||
>
|
||||
<header>
|
||||
<div>
|
||||
<span className="section-eyebrow">A3 · IMMUTABLE ENGINEERING GENERATION</span>
|
||||
</div>
|
||||
<StatusBadge
|
||||
tone={summary.humanExceptionCount ? "warning" : "accent"}
|
||||
>
|
||||
{summary.reviewedItemCount} / {summary.itemCount}
|
||||
</StatusBadge>
|
||||
</header>
|
||||
|
||||
<dl>
|
||||
<div>
|
||||
<dt>Подтверждено</dt>
|
||||
<dd>{confirmed.toLocaleString("ru-RU")}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Исправлено</dt>
|
||||
<dd>{corrected.toLocaleString("ru-RU")}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Исключения</dt>
|
||||
<dd>{summary.humanExceptionCount.toLocaleString("ru-RU")}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Средняя уверенность</dt>
|
||||
<dd>
|
||||
{(summary.meanConfidence * 100).toLocaleString("ru-RU", {
|
||||
maximumFractionDigits: 1,
|
||||
})}
|
||||
%
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import type { E30ReviewItemDetail } from "../../core/laboratory/e30Review";
|
||||
|
||||
function formatNumber(value: number): string {
|
||||
return value.toLocaleString("ru-RU", { maximumFractionDigits: 3 });
|
||||
}
|
||||
|
||||
function evidenceRange(item: E30ReviewItemDetail): string {
|
||||
const value = item.snapshot.rangeM ?? item.snapshot.nearestRangeM;
|
||||
return value === null ? "недоступна" : `${formatNumber(value)} м`;
|
||||
}
|
||||
|
||||
export function E30EvidenceTelemetry({
|
||||
detail,
|
||||
mode,
|
||||
}: {
|
||||
detail: E30ReviewItemDetail;
|
||||
mode: "camera" | "3d";
|
||||
}) {
|
||||
return (
|
||||
<aside
|
||||
className="e30-evidence-telemetry"
|
||||
aria-label="Параметры доказательства"
|
||||
>
|
||||
<span>
|
||||
{mode === "camera"
|
||||
? "Точный camera frame · LiDAR projection"
|
||||
: "Map frame · Z вверх"}
|
||||
</span>
|
||||
<dl>
|
||||
<div>
|
||||
<dt>Кадр / время</dt>
|
||||
<dd>
|
||||
{formatNumber(detail.sourceFrameIndex)}
|
||||
{" · "}
|
||||
{formatNumber(detail.sessionSeconds)} с
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>LiDAR</dt>
|
||||
<dd>{formatNumber(detail.materialization.projectedPointCount)}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Кандидаты</dt>
|
||||
<dd>{formatNumber(detail.materialization.candidatePointCount)}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Выбрано / отклонено</dt>
|
||||
<dd>
|
||||
{formatNumber(detail.materialization.selectedPointCount)}
|
||||
{" / "}
|
||||
{formatNumber(detail.materialization.rejectedCandidatePointCount)}
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Класс E29</dt>
|
||||
<dd>{detail.snapshot.geometryStatus}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Дальность</dt>
|
||||
<dd>{evidenceRange(detail)}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<div>
|
||||
<span>Точные индексы LiDAR</span>
|
||||
<code>
|
||||
{detail.selected.sourceIndices.length
|
||||
? detail.selected.sourceIndices.slice(0, 18).join(", ")
|
||||
: "нет выбранных точек"}
|
||||
{detail.selected.sourceIndices.length > 18 ? " …" : ""}
|
||||
</code>
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
import {
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useRef,
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
import {
|
||||
Icon,
|
||||
IconButton,
|
||||
SegmentedControl,
|
||||
} from "@nodedc/ui-react";
|
||||
|
||||
export interface LaboratoryEvidenceViewerMode<T extends string> {
|
||||
value: T;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export function LaboratoryEvidenceViewer<
|
||||
T extends string,
|
||||
U extends string = string,
|
||||
>({
|
||||
label,
|
||||
className,
|
||||
mode,
|
||||
modes,
|
||||
expanded,
|
||||
onModeChange,
|
||||
onExpandedChange,
|
||||
actions,
|
||||
secondaryMode,
|
||||
overlay,
|
||||
transport,
|
||||
trailingActions,
|
||||
modeControlsVisible = true,
|
||||
chromeLayout = "overlay",
|
||||
children,
|
||||
}: {
|
||||
label: string;
|
||||
className?: string;
|
||||
mode: T;
|
||||
modes: readonly LaboratoryEvidenceViewerMode<T>[];
|
||||
expanded: boolean;
|
||||
onModeChange: (mode: T) => void;
|
||||
onExpandedChange: (expanded: boolean) => void;
|
||||
actions?: ReactNode;
|
||||
secondaryMode?: {
|
||||
value: U;
|
||||
modes: readonly LaboratoryEvidenceViewerMode<U>[];
|
||||
label: string;
|
||||
onChange: (mode: U) => void;
|
||||
};
|
||||
overlay?: ReactNode;
|
||||
transport?: ReactNode;
|
||||
trailingActions?: ReactNode;
|
||||
modeControlsVisible?: boolean;
|
||||
chromeLayout?: "overlay" | "stacked";
|
||||
children: ReactNode;
|
||||
}) {
|
||||
const expandButtonRef = useRef<HTMLButtonElement | null>(null);
|
||||
const viewerRef = useRef<HTMLDialogElement | null>(null);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const viewer = viewerRef.current;
|
||||
if (!viewer) return;
|
||||
if (viewer.open) viewer.close();
|
||||
if (expanded) {
|
||||
viewer.showModal();
|
||||
} else {
|
||||
viewer.show();
|
||||
}
|
||||
}, [expanded]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!expanded) return;
|
||||
const onKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key !== "Escape") return;
|
||||
event.preventDefault();
|
||||
onExpandedChange(false);
|
||||
window.requestAnimationFrame(() => expandButtonRef.current?.focus());
|
||||
};
|
||||
window.addEventListener("keydown", onKeyDown);
|
||||
return () => window.removeEventListener("keydown", onKeyDown);
|
||||
}, [expanded, onExpandedChange]);
|
||||
|
||||
const controls = (
|
||||
<div className="laboratory-evidence-viewer__controls">
|
||||
{actions}
|
||||
{modeControlsVisible && secondaryMode ? (
|
||||
<SegmentedControl
|
||||
value={secondaryMode.value}
|
||||
items={[...secondaryMode.modes]}
|
||||
label={secondaryMode.label}
|
||||
onChange={secondaryMode.onChange}
|
||||
/>
|
||||
) : null}
|
||||
{modeControlsVisible ? (
|
||||
<SegmentedControl
|
||||
value={mode}
|
||||
items={[...modes]}
|
||||
label={`${label}: режим представления`}
|
||||
onChange={onModeChange}
|
||||
/>
|
||||
) : null}
|
||||
{trailingActions}
|
||||
<IconButton
|
||||
ref={expandButtonRef}
|
||||
label={expanded ? `Свернуть ${label}` : `Развернуть ${label}`}
|
||||
onClick={() => onExpandedChange(!expanded)}
|
||||
>
|
||||
<Icon name={expanded ? "minimize" : "expand"} size={16} />
|
||||
</IconButton>
|
||||
</div>
|
||||
);
|
||||
|
||||
const viewer = (
|
||||
<dialog
|
||||
ref={viewerRef}
|
||||
role="region"
|
||||
className={[
|
||||
"laboratory-evidence-viewer",
|
||||
className,
|
||||
].filter(Boolean).join(" ")}
|
||||
data-expanded={expanded ? "true" : undefined}
|
||||
data-mode-controls={modeControlsVisible ? undefined : "content"}
|
||||
data-chrome-layout={chromeLayout}
|
||||
aria-label={label}
|
||||
>
|
||||
{chromeLayout === "stacked" ? (
|
||||
<>
|
||||
<div className="laboratory-evidence-viewer__header">
|
||||
<div className="laboratory-evidence-viewer__header-context">
|
||||
{overlay}
|
||||
</div>
|
||||
{controls}
|
||||
</div>
|
||||
<div className="laboratory-evidence-viewer__stage">
|
||||
{children}
|
||||
</div>
|
||||
{transport ? (
|
||||
<div className="laboratory-evidence-viewer__transport">
|
||||
{transport}
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="laboratory-evidence-viewer__stage">
|
||||
{children}
|
||||
</div>
|
||||
{overlay}
|
||||
{transport ? (
|
||||
<div className="laboratory-evidence-viewer__transport">
|
||||
{transport}
|
||||
</div>
|
||||
) : null}
|
||||
{controls}
|
||||
</>
|
||||
)}
|
||||
</dialog>
|
||||
);
|
||||
|
||||
return viewer;
|
||||
}
|
||||
@@ -0,0 +1,943 @@
|
||||
import {
|
||||
forwardRef,
|
||||
type CSSProperties,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import * as THREE from "three";
|
||||
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
|
||||
|
||||
import {
|
||||
recordedEvidenceSemanticCssColor,
|
||||
resolveRecordedEvidenceSemanticRgb,
|
||||
type RecordedEvidenceSemanticClass,
|
||||
type RecordedEvidenceSemanticPaletteEntry,
|
||||
} from "./RecordedEvidenceSemanticMaskOverlay";
|
||||
|
||||
export type LaboratoryMetricPoint3 = readonly [number, number, number];
|
||||
export type LaboratoryMetricDecision = "threat" | "not-threat" | "unknown";
|
||||
export type LaboratoryMetricSceneMode = "3d" | "plan";
|
||||
|
||||
export interface LaboratoryMetricObstacleVisual {
|
||||
id: string;
|
||||
decision: LaboratoryMetricDecision;
|
||||
state: "current" | "retained" | "held" | "expired";
|
||||
centroidBodyXyzM: LaboratoryMetricPoint3;
|
||||
cellCentersBodyXyzM: readonly LaboratoryMetricPoint3[];
|
||||
occupancySource?: "baseline" | "mixed" | "additive-low-step";
|
||||
}
|
||||
|
||||
export interface LaboratoryMetricRigVisual {
|
||||
lengthM: number;
|
||||
widthM: number;
|
||||
nominalSensorHeightM: number;
|
||||
}
|
||||
|
||||
export interface LaboratoryMetricCorridorVisual {
|
||||
forwardLengthM: number;
|
||||
rearMarginM: number;
|
||||
halfWidthM: number;
|
||||
}
|
||||
|
||||
export type LaboratoryMetricCellState =
|
||||
| "unobserved"
|
||||
| "ground-support"
|
||||
| "nonground-occupied"
|
||||
| "unknown-rejected";
|
||||
|
||||
export interface LaboratoryMetricCellEvidence {
|
||||
centerBodyXyM: readonly [number, number];
|
||||
zBoundsM: readonly [number | null, number | null];
|
||||
state: LaboratoryMetricCellState;
|
||||
}
|
||||
|
||||
export interface LaboratoryMetricPackedCellEvidence {
|
||||
centersBodyXyM: Float32Array;
|
||||
zBoundsM: Float32Array;
|
||||
stateCodes: Uint8Array;
|
||||
}
|
||||
|
||||
export interface LaboratoryMetricLegendEntry {
|
||||
id: LaboratoryMetricDecision
|
||||
| LaboratoryMetricCellState
|
||||
| "context"
|
||||
| "local-surface"
|
||||
| "rolling"
|
||||
| "low-step";
|
||||
label: string;
|
||||
}
|
||||
|
||||
interface LaboratoryMetricRenderStats {
|
||||
frameMs: number;
|
||||
drawCalls: number;
|
||||
triangles: number;
|
||||
pixelRatio: number;
|
||||
}
|
||||
|
||||
export function laboratoryMetricLegendEntries({
|
||||
pointCloudCount,
|
||||
localSurfaceCount,
|
||||
obstacles,
|
||||
showCurrentIncrement,
|
||||
showLocalSurface,
|
||||
showRollingMap,
|
||||
showLowStep = true,
|
||||
}: {
|
||||
pointCloudCount: number;
|
||||
localSurfaceCount: number;
|
||||
obstacles: readonly LaboratoryMetricObstacleVisual[];
|
||||
showCurrentIncrement: boolean;
|
||||
showLocalSurface: boolean;
|
||||
showRollingMap: boolean;
|
||||
showLowStep?: boolean;
|
||||
}): readonly LaboratoryMetricLegendEntry[] {
|
||||
const visibleObstacles = obstacles.filter((obstacle) => (
|
||||
(showLowStep || obstacle.occupancySource === undefined || obstacle.occupancySource === "baseline")
|
||||
&& (obstacle.state === "current"
|
||||
? showCurrentIncrement
|
||||
: obstacle.state === "retained"
|
||||
? showRollingMap
|
||||
: false)
|
||||
));
|
||||
const decisions = new Set(visibleObstacles.map(({ decision }) => decision));
|
||||
const entries: LaboratoryMetricLegendEntry[] = [];
|
||||
if (decisions.has("threat")) entries.push({ id: "threat", label: "Угроза" });
|
||||
if (decisions.has("not-threat")) entries.push({ id: "not-threat", label: "Вне коридора" });
|
||||
if (decisions.has("unknown")) entries.push({ id: "unknown", label: "Неизвестно" });
|
||||
if (
|
||||
showLowStep
|
||||
&& visibleObstacles.some((obstacle) => (
|
||||
obstacle.occupancySource !== undefined && obstacle.occupancySource !== "baseline"
|
||||
))
|
||||
) {
|
||||
entries.push({ id: "low-step", label: "LOW-STEP · добавлено системой" });
|
||||
}
|
||||
if (showCurrentIncrement && pointCloudCount > 0) {
|
||||
entries.push({ id: "context", label: "Текущий кадр" });
|
||||
}
|
||||
if (showLocalSurface && localSurfaceCount > 0) {
|
||||
entries.push({ id: "local-surface", label: "Локальная SLAM-поверхность" });
|
||||
}
|
||||
if (
|
||||
showRollingMap
|
||||
&& visibleObstacles.some((obstacle) => (
|
||||
obstacle.state === "retained" && obstacle.cellCentersBodyXyzM.length > 0
|
||||
))
|
||||
) {
|
||||
entries.push({ id: "rolling", label: "Занято на накопленной карте" });
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
function tokenColor(
|
||||
host: HTMLElement,
|
||||
token: string,
|
||||
fallback: readonly [number, number, number],
|
||||
): THREE.Color {
|
||||
const value = getComputedStyle(host).getPropertyValue(token).trim();
|
||||
if (value.startsWith("#")) return new THREE.Color(value);
|
||||
const channels = value.match(/[\d.]+/g)?.slice(0, 3).map(Number);
|
||||
const [red, green, blue] = channels?.length === 3 ? channels : fallback;
|
||||
return new THREE.Color(red / 255, green / 255, blue / 255);
|
||||
}
|
||||
|
||||
function disposeRenderable(object: THREE.Object3D): void {
|
||||
const renderable = object as THREE.Object3D & {
|
||||
geometry?: THREE.BufferGeometry;
|
||||
material?: THREE.Material | THREE.Material[];
|
||||
};
|
||||
renderable.geometry?.dispose();
|
||||
const materials = Array.isArray(renderable.material)
|
||||
? renderable.material
|
||||
: renderable.material
|
||||
? [renderable.material]
|
||||
: [];
|
||||
materials.forEach((material) => material.dispose());
|
||||
}
|
||||
|
||||
function clearGroup(group: THREE.Group): void {
|
||||
while (group.children.length) {
|
||||
const child = group.children[0];
|
||||
if (!child) break;
|
||||
group.remove(child);
|
||||
child.traverse(disposeRenderable);
|
||||
}
|
||||
}
|
||||
|
||||
function scenePoint(point: LaboratoryMetricPoint3): LaboratoryMetricPoint3 {
|
||||
return [point[0], point[2], -point[1]];
|
||||
}
|
||||
|
||||
function positions(points: readonly LaboratoryMetricPoint3[]): Float32Array {
|
||||
const result = new Float32Array(points.length * 3);
|
||||
points.forEach((point, index) => {
|
||||
const [x, y, z] = scenePoint(point);
|
||||
const offset = index * 3;
|
||||
result[offset] = x;
|
||||
result[offset + 1] = y;
|
||||
result[offset + 2] = z;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
function updatePointPositions(
|
||||
geometry: THREE.BufferGeometry,
|
||||
points: readonly LaboratoryMetricPoint3[],
|
||||
): void {
|
||||
const requiredValues = points.length * 3;
|
||||
let attribute = geometry.getAttribute("position") as THREE.BufferAttribute | undefined;
|
||||
if (!attribute || attribute.array.length < requiredValues) {
|
||||
let capacity = 3;
|
||||
while (capacity < requiredValues) capacity *= 2;
|
||||
attribute = new THREE.BufferAttribute(new Float32Array(capacity), 3);
|
||||
attribute.setUsage(THREE.DynamicDrawUsage);
|
||||
geometry.setAttribute("position", attribute);
|
||||
}
|
||||
const values = attribute.array as Float32Array;
|
||||
points.forEach((point, index) => {
|
||||
const [x, y, z] = scenePoint(point);
|
||||
const offset = index * 3;
|
||||
values[offset] = x;
|
||||
values[offset + 1] = y;
|
||||
values[offset + 2] = z;
|
||||
});
|
||||
attribute.needsUpdate = true;
|
||||
geometry.setDrawRange(0, points.length);
|
||||
}
|
||||
|
||||
function ensurePointColors(
|
||||
geometry: THREE.BufferGeometry,
|
||||
pointCount: number,
|
||||
): THREE.BufferAttribute {
|
||||
const requiredValues = pointCount * 3;
|
||||
let attribute = geometry.getAttribute("color") as THREE.BufferAttribute | undefined;
|
||||
if (!attribute || attribute.array.length < requiredValues) {
|
||||
let capacity = 3;
|
||||
while (capacity < requiredValues) capacity *= 2;
|
||||
attribute = new THREE.BufferAttribute(new Float32Array(capacity), 3);
|
||||
attribute.setUsage(THREE.DynamicDrawUsage);
|
||||
geometry.setAttribute("color", attribute);
|
||||
}
|
||||
return attribute;
|
||||
}
|
||||
|
||||
function decisionColor(
|
||||
host: HTMLElement,
|
||||
decision: LaboratoryMetricDecision,
|
||||
): THREE.Color {
|
||||
if (decision === "threat") {
|
||||
return tokenColor(host, "--nodedc-danger-rgb", [255, 104, 112]);
|
||||
}
|
||||
if (decision === "not-threat") {
|
||||
return tokenColor(host, "--nodedc-success-rgb", [181, 255, 90]);
|
||||
}
|
||||
return tokenColor(host, "--nodedc-warning-rgb", [255, 197, 92]);
|
||||
}
|
||||
|
||||
function obstacleColor(
|
||||
host: HTMLElement,
|
||||
obstacle: LaboratoryMetricObstacleVisual,
|
||||
): THREE.Color {
|
||||
if (obstacle.occupancySource !== "baseline") {
|
||||
return tokenColor(host, "--nodedc-accent-rgb", [232, 56, 126]);
|
||||
}
|
||||
return decisionColor(host, obstacle.decision);
|
||||
}
|
||||
|
||||
export interface LaboratoryMetricEvidenceSceneHandle {
|
||||
resetView: () => void;
|
||||
}
|
||||
|
||||
export const LaboratoryMetricEvidenceScene = forwardRef<
|
||||
LaboratoryMetricEvidenceSceneHandle,
|
||||
{
|
||||
pointCloudBodyXyzM: readonly LaboratoryMetricPoint3[];
|
||||
localSurfaceBodyXyzM: readonly LaboratoryMetricPoint3[];
|
||||
obstacles: readonly LaboratoryMetricObstacleVisual[];
|
||||
rig: LaboratoryMetricRigVisual;
|
||||
corridor: LaboratoryMetricCorridorVisual;
|
||||
occupiedVoxelSizeM: number;
|
||||
mode: LaboratoryMetricSceneMode;
|
||||
label: string;
|
||||
showCurrentIncrement: boolean;
|
||||
showLocalSurface: boolean;
|
||||
showRollingMap: boolean;
|
||||
showLowStep?: boolean;
|
||||
pointSemanticClassIds?: readonly (number | null)[];
|
||||
semanticClasses?: readonly RecordedEvidenceSemanticClass[];
|
||||
semanticPalette?: readonly RecordedEvidenceSemanticPaletteEntry[];
|
||||
classifiedCells?: readonly LaboratoryMetricCellEvidence[];
|
||||
classifiedPackedCells?: LaboratoryMetricPackedCellEvidence;
|
||||
classifiedCellSizeM?: number;
|
||||
showClassifiedCells?: boolean;
|
||||
}
|
||||
>(function LaboratoryMetricEvidenceScene({
|
||||
pointCloudBodyXyzM,
|
||||
localSurfaceBodyXyzM,
|
||||
obstacles,
|
||||
rig,
|
||||
corridor,
|
||||
occupiedVoxelSizeM,
|
||||
mode,
|
||||
label,
|
||||
showCurrentIncrement,
|
||||
showLocalSurface,
|
||||
showRollingMap,
|
||||
showLowStep = true,
|
||||
pointSemanticClassIds,
|
||||
semanticClasses,
|
||||
semanticPalette,
|
||||
classifiedCells = [],
|
||||
classifiedPackedCells,
|
||||
classifiedCellSizeM = 0.45,
|
||||
showClassifiedCells = true,
|
||||
}, ref) {
|
||||
const hostRef = useRef<HTMLDivElement | null>(null);
|
||||
const sceneRef = useRef<THREE.Scene | null>(null);
|
||||
const cameraRef = useRef<THREE.PerspectiveCamera | null>(null);
|
||||
const controlsRef = useRef<OrbitControls | null>(null);
|
||||
const staticContentRef = useRef<THREE.Group | null>(null);
|
||||
const dynamicContentRef = useRef<THREE.Group | null>(null);
|
||||
const classifiedContentRef = useRef<THREE.Group | null>(null);
|
||||
const localSurfacePointsRef = useRef<THREE.Points | null>(null);
|
||||
const currentIncrementPointsRef = useRef<THREE.Points | null>(null);
|
||||
const requestRenderRef = useRef<() => void>(() => undefined);
|
||||
const corridorForwardLengthRef = useRef(corridor.forwardLengthM);
|
||||
const modeRef = useRef(mode);
|
||||
const classifiedMeshesRef = useRef<ReadonlyMap<LaboratoryMetricCellState, THREE.InstancedMesh>>(
|
||||
new Map(),
|
||||
);
|
||||
const [renderError, setRenderError] = useState<string | null>(null);
|
||||
const [renderStats, setRenderStats] = useState<LaboratoryMetricRenderStats | null>(null);
|
||||
corridorForwardLengthRef.current = corridor.forwardLengthM;
|
||||
modeRef.current = mode;
|
||||
|
||||
useEffect(() => {
|
||||
const host = hostRef.current;
|
||||
if (!host) return;
|
||||
let renderer: THREE.WebGLRenderer;
|
||||
try {
|
||||
renderer = new THREE.WebGLRenderer({
|
||||
antialias: true,
|
||||
alpha: false,
|
||||
powerPreference: "high-performance",
|
||||
});
|
||||
} catch {
|
||||
setRenderError("Браузер не смог создать метрическую 3D-сцену.");
|
||||
return;
|
||||
}
|
||||
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
|
||||
renderer.outputColorSpace = THREE.SRGBColorSpace;
|
||||
renderer.setClearColor(tokenColor(host, "--nodedc-canvas", [5, 5, 6]), 1);
|
||||
renderer.domElement.setAttribute("role", "img");
|
||||
host.prepend(renderer.domElement);
|
||||
|
||||
const scene = new THREE.Scene();
|
||||
const camera = new THREE.PerspectiveCamera(48, 1, 0.01, 300);
|
||||
const controls = new OrbitControls(camera, renderer.domElement);
|
||||
controls.enableDamping = true;
|
||||
controls.dampingFactor = 0.08;
|
||||
controls.enablePan = true;
|
||||
controls.enableZoom = true;
|
||||
controls.screenSpacePanning = true;
|
||||
controls.minDistance = 0.4;
|
||||
controls.maxDistance = 80;
|
||||
const staticContent = new THREE.Group();
|
||||
const dynamicContent = new THREE.Group();
|
||||
const classifiedContent = new THREE.Group();
|
||||
const localSurfacePoints = new THREE.Points(
|
||||
new THREE.BufferGeometry(),
|
||||
new THREE.PointsMaterial({
|
||||
color: tokenColor(host, "--nodedc-accent-rgb", [247, 248, 244]),
|
||||
size: 1.3,
|
||||
sizeAttenuation: false,
|
||||
transparent: true,
|
||||
opacity: 0.42,
|
||||
depthWrite: false,
|
||||
}),
|
||||
);
|
||||
const currentIncrementPoints = new THREE.Points(
|
||||
new THREE.BufferGeometry(),
|
||||
new THREE.PointsMaterial({
|
||||
color: tokenColor(host, "--nodedc-text-muted", [147, 151, 159]),
|
||||
size: 1.55,
|
||||
sizeAttenuation: false,
|
||||
transparent: true,
|
||||
opacity: 0.58,
|
||||
depthWrite: false,
|
||||
}),
|
||||
);
|
||||
localSurfacePoints.visible = false;
|
||||
localSurfacePoints.frustumCulled = false;
|
||||
currentIncrementPoints.visible = false;
|
||||
currentIncrementPoints.frustumCulled = false;
|
||||
scene.add(
|
||||
staticContent,
|
||||
localSurfacePoints,
|
||||
currentIncrementPoints,
|
||||
dynamicContent,
|
||||
classifiedContent,
|
||||
);
|
||||
sceneRef.current = scene;
|
||||
cameraRef.current = camera;
|
||||
controlsRef.current = controls;
|
||||
staticContentRef.current = staticContent;
|
||||
dynamicContentRef.current = dynamicContent;
|
||||
classifiedContentRef.current = classifiedContent;
|
||||
localSurfacePointsRef.current = localSurfacePoints;
|
||||
currentIncrementPointsRef.current = currentIncrementPoints;
|
||||
|
||||
let animationFrame: number | null = null;
|
||||
let lastStatsAt = Number.NEGATIVE_INFINITY;
|
||||
const render = () => {
|
||||
animationFrame = null;
|
||||
// OrbitControls emits another change while damping is still settling, so
|
||||
// this remains smooth during interaction without rendering forever while
|
||||
// the evidence scene is idle.
|
||||
controls.update();
|
||||
const startedAt = performance.now();
|
||||
renderer.render(scene, camera);
|
||||
const completedAt = performance.now();
|
||||
if (completedAt - lastStatsAt >= 1_000) {
|
||||
lastStatsAt = completedAt;
|
||||
setRenderStats({
|
||||
frameMs: completedAt - startedAt,
|
||||
drawCalls: renderer.info.render.calls,
|
||||
triangles: renderer.info.render.triangles,
|
||||
pixelRatio: renderer.getPixelRatio(),
|
||||
});
|
||||
}
|
||||
};
|
||||
const requestRender = () => {
|
||||
if (animationFrame !== null) return;
|
||||
animationFrame = window.requestAnimationFrame(render);
|
||||
};
|
||||
requestRenderRef.current = requestRender;
|
||||
controls.addEventListener("change", requestRender);
|
||||
|
||||
const resize = () => {
|
||||
const width = Math.max(host.clientWidth, 1);
|
||||
const height = Math.max(host.clientHeight, 1);
|
||||
camera.aspect = width / height;
|
||||
camera.updateProjectionMatrix();
|
||||
renderer.setSize(width, height, false);
|
||||
requestRender();
|
||||
};
|
||||
const observer = new ResizeObserver(resize);
|
||||
observer.observe(host);
|
||||
resize();
|
||||
|
||||
requestRender();
|
||||
return () => {
|
||||
if (animationFrame !== null) window.cancelAnimationFrame(animationFrame);
|
||||
observer.disconnect();
|
||||
controls.removeEventListener("change", requestRender);
|
||||
controls.dispose();
|
||||
scene.traverse(disposeRenderable);
|
||||
renderer.dispose();
|
||||
renderer.domElement.remove();
|
||||
sceneRef.current = null;
|
||||
cameraRef.current = null;
|
||||
controlsRef.current = null;
|
||||
staticContentRef.current = null;
|
||||
dynamicContentRef.current = null;
|
||||
classifiedContentRef.current = null;
|
||||
localSurfacePointsRef.current = null;
|
||||
currentIncrementPointsRef.current = null;
|
||||
requestRenderRef.current = () => undefined;
|
||||
classifiedMeshesRef.current = new Map();
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const canvas = hostRef.current?.querySelector("canvas");
|
||||
if (canvas) canvas.setAttribute("aria-label", label);
|
||||
}, [label]);
|
||||
|
||||
useEffect(() => {
|
||||
const points = localSurfacePointsRef.current;
|
||||
if (!points) return;
|
||||
points.visible = showLocalSurface && localSurfaceBodyXyzM.length > 0;
|
||||
if (points.visible) updatePointPositions(points.geometry, localSurfaceBodyXyzM);
|
||||
requestRenderRef.current();
|
||||
}, [localSurfaceBodyXyzM, showLocalSurface]);
|
||||
|
||||
useEffect(() => {
|
||||
const host = hostRef.current;
|
||||
const points = currentIncrementPointsRef.current;
|
||||
if (!host || !points) return;
|
||||
points.visible = showCurrentIncrement && pointCloudBodyXyzM.length > 0;
|
||||
if (!points.visible) {
|
||||
requestRenderRef.current();
|
||||
return;
|
||||
}
|
||||
updatePointPositions(points.geometry, pointCloudBodyXyzM);
|
||||
const material = points.material as THREE.PointsMaterial;
|
||||
const hasAlignedSemanticClasses =
|
||||
pointSemanticClassIds !== undefined
|
||||
&& pointSemanticClassIds.length === pointCloudBodyXyzM.length
|
||||
&& semanticClasses !== undefined
|
||||
&& semanticPalette !== undefined;
|
||||
if (!hasAlignedSemanticClasses) {
|
||||
if (material.vertexColors) {
|
||||
material.vertexColors = false;
|
||||
material.needsUpdate = true;
|
||||
}
|
||||
material.color.copy(tokenColor(host, "--nodedc-text-muted", [147, 151, 159]));
|
||||
requestRenderRef.current();
|
||||
return;
|
||||
}
|
||||
const declaredIds = new Set(semanticClasses.map((item) => item.id));
|
||||
const colorsByClassId = new Map<number, readonly [number, number, number]>();
|
||||
for (const entry of semanticPalette) {
|
||||
if (!declaredIds.has(entry.classId)) continue;
|
||||
const rgb = resolveRecordedEvidenceSemanticRgb(host, entry.color);
|
||||
if (rgb) colorsByClassId.set(entry.classId, rgb);
|
||||
}
|
||||
const context = tokenColor(host, "--nodedc-text-muted", [147, 151, 159]);
|
||||
const colorAttribute = ensurePointColors(points.geometry, pointCloudBodyXyzM.length);
|
||||
const pointColors = colorAttribute.array as Float32Array;
|
||||
pointSemanticClassIds.forEach((classId, index) => {
|
||||
const rgb = classId === null ? undefined : colorsByClassId.get(classId);
|
||||
const offset = index * 3;
|
||||
pointColors[offset] = rgb ? rgb[0] / 255 : context.r;
|
||||
pointColors[offset + 1] = rgb ? rgb[1] / 255 : context.g;
|
||||
pointColors[offset + 2] = rgb ? rgb[2] / 255 : context.b;
|
||||
});
|
||||
colorAttribute.needsUpdate = true;
|
||||
if (!material.vertexColors) {
|
||||
material.vertexColors = true;
|
||||
material.needsUpdate = true;
|
||||
}
|
||||
material.color.setRGB(1, 1, 1);
|
||||
requestRenderRef.current();
|
||||
}, [
|
||||
pointCloudBodyXyzM,
|
||||
pointSemanticClassIds,
|
||||
semanticClasses,
|
||||
semanticPalette,
|
||||
showCurrentIncrement,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
const host = hostRef.current;
|
||||
const content = dynamicContentRef.current;
|
||||
if (!host || !content) return;
|
||||
clearGroup(content);
|
||||
|
||||
for (const obstacle of obstacles) {
|
||||
if (
|
||||
(
|
||||
!showLowStep
|
||||
&& obstacle.occupancySource !== undefined
|
||||
&& obstacle.occupancySource !== "baseline"
|
||||
)
|
||||
||
|
||||
(obstacle.state === "current" && !showCurrentIncrement)
|
||||
|| (obstacle.state === "retained" && !showRollingMap)
|
||||
|| obstacle.state === "held"
|
||||
|| obstacle.state === "expired"
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
const color = obstacleColor(host, obstacle);
|
||||
if (obstacle.state === "retained") {
|
||||
const geometry = new THREE.BoxGeometry(
|
||||
occupiedVoxelSizeM * 0.82,
|
||||
occupiedVoxelSizeM * 0.82,
|
||||
occupiedVoxelSizeM * 0.82,
|
||||
);
|
||||
const material = new THREE.MeshBasicMaterial({
|
||||
color,
|
||||
wireframe: true,
|
||||
transparent: true,
|
||||
opacity: 0.34,
|
||||
depthWrite: false,
|
||||
});
|
||||
const voxels = new THREE.InstancedMesh(
|
||||
geometry,
|
||||
material,
|
||||
obstacle.cellCentersBodyXyzM.length,
|
||||
);
|
||||
const matrix = new THREE.Matrix4();
|
||||
obstacle.cellCentersBodyXyzM.forEach((point, index) => {
|
||||
matrix.makeTranslation(...scenePoint(point));
|
||||
voxels.setMatrixAt(index, matrix);
|
||||
});
|
||||
voxels.instanceMatrix.needsUpdate = true;
|
||||
voxels.userData.evidenceId = obstacle.id;
|
||||
content.add(voxels);
|
||||
} else {
|
||||
const cellsGeometry = new THREE.BufferGeometry();
|
||||
cellsGeometry.setAttribute(
|
||||
"position",
|
||||
new THREE.BufferAttribute(positions(obstacle.cellCentersBodyXyzM), 3),
|
||||
);
|
||||
content.add(new THREE.Points(
|
||||
cellsGeometry,
|
||||
new THREE.PointsMaterial({
|
||||
color,
|
||||
size: 4.4,
|
||||
sizeAttenuation: false,
|
||||
transparent: true,
|
||||
opacity: 0.96,
|
||||
depthWrite: false,
|
||||
}),
|
||||
));
|
||||
const centroid = new THREE.Mesh(
|
||||
new THREE.SphereGeometry(0.065, 12, 8),
|
||||
new THREE.MeshBasicMaterial({ color }),
|
||||
);
|
||||
centroid.position.fromArray(scenePoint(obstacle.centroidBodyXyzM));
|
||||
centroid.userData.evidenceId = obstacle.id;
|
||||
content.add(centroid);
|
||||
}
|
||||
}
|
||||
requestRenderRef.current();
|
||||
}, [
|
||||
obstacles,
|
||||
occupiedVoxelSizeM,
|
||||
showCurrentIncrement,
|
||||
showRollingMap,
|
||||
showLowStep,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
const host = hostRef.current;
|
||||
const content = classifiedContentRef.current;
|
||||
if (!host || !content) return;
|
||||
const packedCellCount = classifiedPackedCells?.stateCodes.length ?? 0;
|
||||
const packedCellsValid = !classifiedPackedCells || (
|
||||
classifiedPackedCells.centersBodyXyM.length === packedCellCount * 2
|
||||
&& classifiedPackedCells.zBoundsM.length === packedCellCount * 2
|
||||
);
|
||||
const cellCount = packedCellsValid && classifiedPackedCells
|
||||
? packedCellCount
|
||||
: classifiedCells.length;
|
||||
content.visible = showClassifiedCells && cellCount > 0;
|
||||
if (!content.visible) {
|
||||
requestRenderRef.current();
|
||||
return;
|
||||
}
|
||||
|
||||
const requiredCapacity = cellCount;
|
||||
const currentMeshes = classifiedMeshesRef.current;
|
||||
const firstMesh = currentMeshes.values().next().value as THREE.InstancedMesh | undefined;
|
||||
const needsAllocation = !firstMesh
|
||||
|| Number(firstMesh.userData.capacity ?? 0) < requiredCapacity
|
||||
|| Number(firstMesh.userData.cellSizeM ?? 0) !== classifiedCellSizeM;
|
||||
if (needsAllocation) {
|
||||
clearGroup(content);
|
||||
const meshes = new Map<LaboratoryMetricCellState, THREE.InstancedMesh>();
|
||||
const states: readonly LaboratoryMetricCellState[] = [
|
||||
"unobserved",
|
||||
"ground-support",
|
||||
"nonground-occupied",
|
||||
"unknown-rejected",
|
||||
];
|
||||
for (const state of states) {
|
||||
const color = state === "ground-support"
|
||||
? tokenColor(host, "--nodedc-success-rgb", [181, 255, 90])
|
||||
: state === "nonground-occupied"
|
||||
? tokenColor(host, "--nodedc-danger-rgb", [255, 104, 112])
|
||||
: state === "unknown-rejected"
|
||||
? tokenColor(host, "--nodedc-warning-rgb", [255, 197, 92])
|
||||
: tokenColor(host, "--nodedc-text-muted", [96, 99, 106]);
|
||||
const mesh = new THREE.InstancedMesh(
|
||||
new THREE.BoxGeometry(classifiedCellSizeM * 0.92, 1, classifiedCellSizeM * 0.92),
|
||||
new THREE.MeshBasicMaterial({
|
||||
color,
|
||||
transparent: true,
|
||||
opacity: state === "unobserved" ? 0.035 : state === "ground-support" ? 0.12 : 0.24,
|
||||
depthWrite: false,
|
||||
}),
|
||||
requiredCapacity,
|
||||
);
|
||||
mesh.count = 0;
|
||||
mesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
|
||||
mesh.frustumCulled = false;
|
||||
mesh.userData.capacity = requiredCapacity;
|
||||
mesh.userData.cellSizeM = classifiedCellSizeM;
|
||||
meshes.set(state, mesh);
|
||||
content.add(mesh);
|
||||
}
|
||||
classifiedMeshesRef.current = meshes;
|
||||
}
|
||||
|
||||
const meshes = classifiedMeshesRef.current;
|
||||
const counts = new Map<LaboratoryMetricCellState, number>();
|
||||
const matrix = new THREE.Matrix4();
|
||||
const position = new THREE.Vector3();
|
||||
const scale = new THREE.Vector3(1, 1, 1);
|
||||
const rotation = new THREE.Quaternion();
|
||||
const setCell = (
|
||||
state: LaboratoryMetricCellState,
|
||||
centerX: number,
|
||||
centerY: number,
|
||||
minimum: number | null,
|
||||
maximum: number | null,
|
||||
) => {
|
||||
const mesh = meshes.get(state);
|
||||
if (!mesh) return;
|
||||
const index = counts.get(state) ?? 0;
|
||||
const height = minimum === null || maximum === null
|
||||
? 0.018
|
||||
: Math.max(0.018, maximum - minimum);
|
||||
const centerZ = minimum === null || maximum === null
|
||||
? -0.012
|
||||
: (minimum + maximum) / 2;
|
||||
const [sceneX, sceneY, sceneZ] = scenePoint([
|
||||
centerX,
|
||||
centerY,
|
||||
centerZ,
|
||||
]);
|
||||
position.set(sceneX, sceneY, sceneZ);
|
||||
scale.set(1, height, 1);
|
||||
matrix.compose(position, rotation, scale);
|
||||
mesh.setMatrixAt(index, matrix);
|
||||
counts.set(state, index + 1);
|
||||
};
|
||||
if (packedCellsValid && classifiedPackedCells) {
|
||||
for (let cellIndex = 0; cellIndex < packedCellCount; cellIndex += 1) {
|
||||
const code = classifiedPackedCells.stateCodes[cellIndex];
|
||||
const state: LaboratoryMetricCellState = code === 1
|
||||
? "ground-support"
|
||||
: code === 2
|
||||
? "nonground-occupied"
|
||||
: code === 3
|
||||
? "unknown-rejected"
|
||||
: code === 0
|
||||
? "unobserved"
|
||||
: "unknown-rejected";
|
||||
const minimum = classifiedPackedCells.zBoundsM[cellIndex * 2]!;
|
||||
const maximum = classifiedPackedCells.zBoundsM[cellIndex * 2 + 1]!;
|
||||
setCell(
|
||||
state,
|
||||
classifiedPackedCells.centersBodyXyM[cellIndex * 2]!,
|
||||
classifiedPackedCells.centersBodyXyM[cellIndex * 2 + 1]!,
|
||||
Number.isFinite(minimum) ? minimum : null,
|
||||
Number.isFinite(maximum) ? maximum : null,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
for (const cell of classifiedCells) {
|
||||
setCell(
|
||||
cell.state,
|
||||
cell.centerBodyXyM[0],
|
||||
cell.centerBodyXyM[1],
|
||||
cell.zBoundsM[0],
|
||||
cell.zBoundsM[1],
|
||||
);
|
||||
}
|
||||
}
|
||||
for (const [state, mesh] of meshes) {
|
||||
mesh.count = counts.get(state) ?? 0;
|
||||
mesh.instanceMatrix.needsUpdate = true;
|
||||
}
|
||||
requestRenderRef.current();
|
||||
}, [classifiedCellSizeM, classifiedCells, classifiedPackedCells, showClassifiedCells]);
|
||||
|
||||
useEffect(() => {
|
||||
const host = hostRef.current;
|
||||
const content = staticContentRef.current;
|
||||
if (!host || !content) return;
|
||||
clearGroup(content);
|
||||
|
||||
const corridorLength = rig.lengthM / 2 + corridor.forwardLengthM + corridor.rearMarginM;
|
||||
const corridorCenterX = (rig.lengthM / 2 + corridor.forwardLengthM - corridor.rearMarginM) / 2;
|
||||
const corridorMesh = new THREE.Mesh(
|
||||
new THREE.PlaneGeometry(corridorLength, corridor.halfWidthM * 2),
|
||||
new THREE.MeshBasicMaterial({
|
||||
color: tokenColor(host, "--nodedc-accent-rgb", [232, 56, 126]),
|
||||
transparent: true,
|
||||
opacity: 0.11,
|
||||
side: THREE.DoubleSide,
|
||||
depthWrite: false,
|
||||
}),
|
||||
);
|
||||
corridorMesh.rotation.x = -Math.PI / 2;
|
||||
corridorMesh.position.set(corridorCenterX, 0.01, 0);
|
||||
content.add(corridorMesh);
|
||||
const corridorOutline = new THREE.LineSegments(
|
||||
new THREE.EdgesGeometry(new THREE.BoxGeometry(corridorLength, 0.01, corridor.halfWidthM * 2)),
|
||||
new THREE.LineBasicMaterial({
|
||||
color: tokenColor(host, "--nodedc-accent-rgb", [232, 56, 126]),
|
||||
transparent: true,
|
||||
opacity: 0.7,
|
||||
}),
|
||||
);
|
||||
corridorOutline.position.set(corridorCenterX, 0.015, 0);
|
||||
content.add(corridorOutline);
|
||||
|
||||
const body = new THREE.LineSegments(
|
||||
new THREE.EdgesGeometry(new THREE.BoxGeometry(rig.lengthM, 0.34, rig.widthM)),
|
||||
new THREE.LineBasicMaterial({
|
||||
color: tokenColor(host, "--nodedc-foreground-rgb", [245, 245, 245]),
|
||||
transparent: true,
|
||||
opacity: 0.86,
|
||||
}),
|
||||
);
|
||||
body.position.y = 0.17;
|
||||
content.add(body);
|
||||
const lidar = new THREE.Mesh(
|
||||
new THREE.CylinderGeometry(0.08, 0.08, 0.08, 20),
|
||||
new THREE.MeshBasicMaterial({
|
||||
color: tokenColor(host, "--nodedc-foreground-rgb", [245, 245, 245]),
|
||||
}),
|
||||
);
|
||||
lidar.position.y = rig.nominalSensorHeightM;
|
||||
content.add(lidar);
|
||||
|
||||
const grid = new THREE.GridHelper(
|
||||
Math.max(20, corridor.forwardLengthM * 2.5),
|
||||
40,
|
||||
tokenColor(host, "--nodedc-text-muted", [96, 99, 106]),
|
||||
tokenColor(host, "--nodedc-glass-outline", [48, 50, 56]),
|
||||
);
|
||||
const gridMaterials = Array.isArray(grid.material) ? grid.material : [grid.material];
|
||||
gridMaterials.forEach((material) => {
|
||||
material.transparent = true;
|
||||
material.opacity = 0.15;
|
||||
material.depthWrite = false;
|
||||
});
|
||||
content.add(grid);
|
||||
requestRenderRef.current();
|
||||
}, [
|
||||
corridor.forwardLengthM,
|
||||
corridor.halfWidthM,
|
||||
corridor.rearMarginM,
|
||||
rig.lengthM,
|
||||
rig.nominalSensorHeightM,
|
||||
rig.widthM,
|
||||
]);
|
||||
|
||||
const resetView = useCallback(() => {
|
||||
const camera = cameraRef.current;
|
||||
const controls = controlsRef.current;
|
||||
if (!camera || !controls) return;
|
||||
const forwardLengthM = corridorForwardLengthRef.current;
|
||||
controls.target.set(forwardLengthM * 0.35, 0.6, 0);
|
||||
if (modeRef.current === "plan") {
|
||||
camera.position.set(forwardLengthM * 0.35, 15, 0.001);
|
||||
camera.up.set(0, 0, -1);
|
||||
} else {
|
||||
camera.position.set(-4.5, 4.8, 8.5);
|
||||
camera.up.set(0, 1, 0);
|
||||
}
|
||||
camera.updateProjectionMatrix();
|
||||
controls.update();
|
||||
requestRenderRef.current();
|
||||
}, []);
|
||||
|
||||
// Playback data and rig object identities must never reset an operator's
|
||||
// orbit. Only a deliberate 3D/PLAN mode transition chooses a default view.
|
||||
useEffect(() => resetView(), [mode, resetView]);
|
||||
useImperativeHandle(ref, () => ({ resetView }), [resetView]);
|
||||
|
||||
const semanticLegendEntries = (() => {
|
||||
if (
|
||||
!showCurrentIncrement
|
||||
|| !pointSemanticClassIds
|
||||
|| pointSemanticClassIds.length !== pointCloudBodyXyzM.length
|
||||
|| !semanticClasses
|
||||
|| !semanticPalette
|
||||
) return [];
|
||||
const presentIds = new Set(pointSemanticClassIds.filter((item): item is number => item !== null));
|
||||
const classesById = new Map(semanticClasses.map((item) => [item.id, item]));
|
||||
return semanticPalette.flatMap((entry) => {
|
||||
const semanticClass = classesById.get(entry.classId);
|
||||
if (!semanticClass || !presentIds.has(entry.classId) || entry.color.kind === "transparent") return [];
|
||||
return [{
|
||||
id: entry.classId,
|
||||
label: semanticClass.label,
|
||||
cssColor: recordedEvidenceSemanticCssColor(entry.color),
|
||||
}];
|
||||
});
|
||||
})();
|
||||
const metricLegendEntries = laboratoryMetricLegendEntries({
|
||||
pointCloudCount: pointSemanticClassIds?.every((item) => item !== null)
|
||||
? 0
|
||||
: pointCloudBodyXyzM.length,
|
||||
localSurfaceCount: localSurfaceBodyXyzM.length,
|
||||
obstacles,
|
||||
showCurrentIncrement,
|
||||
showLocalSurface,
|
||||
showRollingMap,
|
||||
showLowStep,
|
||||
});
|
||||
const classifiedLegendEntries = (() => {
|
||||
const states = new Set<LaboratoryMetricCellState>();
|
||||
if (classifiedPackedCells) {
|
||||
for (const code of classifiedPackedCells.stateCodes) {
|
||||
states.add(code === 1
|
||||
? "ground-support"
|
||||
: code === 2
|
||||
? "nonground-occupied"
|
||||
: code === 3
|
||||
? "unknown-rejected"
|
||||
: code === 0
|
||||
? "unobserved"
|
||||
: "unknown-rejected");
|
||||
}
|
||||
} else {
|
||||
classifiedCells.forEach((cell) => states.add(cell.state));
|
||||
}
|
||||
if (!showClassifiedCells || !states.size) return [];
|
||||
return [
|
||||
states.has("ground-support")
|
||||
? { id: "ground-support" as const, label: "Ground support" }
|
||||
: null,
|
||||
states.has("nonground-occupied")
|
||||
? { id: "nonground-occupied" as const, label: "Non-ground occupied" }
|
||||
: null,
|
||||
states.has("unknown-rejected")
|
||||
? { id: "unknown-rejected" as const, label: "Unknown / rejected" }
|
||||
: null,
|
||||
states.has("unobserved")
|
||||
? { id: "unobserved" as const, label: "Unobserved" }
|
||||
: null,
|
||||
]
|
||||
.filter((entry): entry is NonNullable<typeof entry> => entry !== null)
|
||||
.filter((entry) => !semanticLegendEntries.some(
|
||||
(semanticEntry) => semanticEntry.label === entry.label,
|
||||
));
|
||||
})();
|
||||
|
||||
return (
|
||||
<div className="laboratory-metric-evidence-scene">
|
||||
<div ref={hostRef} className="laboratory-metric-evidence-scene__viewport">
|
||||
{renderError ? <p>{renderError}</p> : null}
|
||||
</div>
|
||||
<div className="laboratory-metric-evidence-scene__legend">
|
||||
{renderStats ? (
|
||||
<span
|
||||
data-decision="performance"
|
||||
title="CPU-время отправки последнего WebGL render pass; это не GPU timer. В покое сцена не перерисовывается."
|
||||
>
|
||||
3D {renderStats.frameMs.toFixed(1)} ms · {renderStats.drawCalls} draw · {renderStats.triangles.toLocaleString("ru-RU")} tri · {(
|
||||
classifiedPackedCells?.stateCodes.length ?? classifiedCells.length
|
||||
).toLocaleString("ru-RU")} cells · {renderStats.pixelRatio.toFixed(1)}×
|
||||
</span>
|
||||
) : null}
|
||||
{metricLegendEntries.map((entry) => (
|
||||
<span key={entry.id} data-decision={entry.id}>{entry.label}</span>
|
||||
))}
|
||||
{classifiedLegendEntries.map((entry) => (
|
||||
<span key={entry.id} data-decision={entry.id}>{entry.label}</span>
|
||||
))}
|
||||
{semanticLegendEntries.map((entry) => (
|
||||
<span
|
||||
key={entry.id}
|
||||
data-decision="semantic"
|
||||
style={{ "--laboratory-metric-legend-color": entry.cssColor } as CSSProperties}
|
||||
>
|
||||
{entry.label}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,366 @@
|
||||
import { useId, useState, type ReactNode } from "react";
|
||||
import { Icon, IconButton, Select, StatusBadge } from "@nodedc/ui-react";
|
||||
|
||||
export interface LaboratoryOption<T extends string> {
|
||||
id: T;
|
||||
label: string;
|
||||
status?: "progress" | "retained" | "failed" | "unreviewed";
|
||||
}
|
||||
|
||||
export type LaboratoryExecutionClass =
|
||||
| "deterministic"
|
||||
| "ai-inference"
|
||||
| "hybrid";
|
||||
export type LaboratoryMethodCompleteness = "complete" | "legacy-partial";
|
||||
export type LaboratoryEvidenceKind = "recorded-replay" | "diagnostic-model";
|
||||
|
||||
export interface LaboratoryMethodComponent {
|
||||
kind: "source" | "tool" | "model" | "algorithm" | "runtime";
|
||||
name: string;
|
||||
version: string;
|
||||
role: string;
|
||||
identitySha256: string | null;
|
||||
}
|
||||
|
||||
export interface LaboratoryMethod {
|
||||
completeness: LaboratoryMethodCompleteness;
|
||||
executionClass: LaboratoryExecutionClass;
|
||||
pipelineId: string;
|
||||
components: readonly LaboratoryMethodComponent[];
|
||||
}
|
||||
|
||||
export interface LaboratoryBrief {
|
||||
question: string;
|
||||
approach: string;
|
||||
principalResult: string;
|
||||
limitation: string;
|
||||
}
|
||||
|
||||
export interface LaboratoryResultMetric {
|
||||
label: string;
|
||||
value: string;
|
||||
hint: string;
|
||||
}
|
||||
|
||||
export interface LaboratoryResultConclusion {
|
||||
proved: string;
|
||||
notProved: string;
|
||||
decision: string;
|
||||
}
|
||||
|
||||
export const LABORATORY_REPORT_TEMPLATE_VERSION =
|
||||
"missioncore.laboratory-report/v1";
|
||||
|
||||
const EXECUTION_LABELS: Record<LaboratoryExecutionClass, string> = {
|
||||
deterministic: "Детерминированный",
|
||||
"ai-inference": "AI Inference",
|
||||
hybrid: "Гибридный",
|
||||
};
|
||||
|
||||
const COMPONENT_LABELS: Record<LaboratoryMethodComponent["kind"], string> = {
|
||||
source: "Источник",
|
||||
tool: "Инструмент",
|
||||
model: "Модель",
|
||||
algorithm: "Алгоритм",
|
||||
runtime: "Runtime",
|
||||
};
|
||||
|
||||
export function LaboratorySelector<T extends string>({
|
||||
eyebrow,
|
||||
title,
|
||||
description,
|
||||
label,
|
||||
value,
|
||||
options,
|
||||
disabled = false,
|
||||
searchable = false,
|
||||
onChange,
|
||||
}: {
|
||||
eyebrow: string;
|
||||
title: string;
|
||||
description: string;
|
||||
label: string;
|
||||
value: T;
|
||||
options: readonly LaboratoryOption<T>[];
|
||||
disabled?: boolean;
|
||||
searchable?: boolean;
|
||||
onChange: (value: T) => void;
|
||||
}) {
|
||||
return (
|
||||
<section className="laboratory-selector">
|
||||
<div>
|
||||
<span className="section-eyebrow">{eyebrow}</span>
|
||||
<h2>{title}</h2>
|
||||
<p>{description}</p>
|
||||
</div>
|
||||
<div className="laboratory-selector__control">
|
||||
<span>{label}</span>
|
||||
<Select
|
||||
label={`Выбрать: ${label}`}
|
||||
value={value}
|
||||
options={options.map((option) => ({
|
||||
value: option.id,
|
||||
label: option.label,
|
||||
icon: option.status ? (
|
||||
<span
|
||||
className="laboratory-status-dot"
|
||||
data-status={option.status}
|
||||
aria-label={option.status === "progress"
|
||||
? "Есть подтверждённый прогресс"
|
||||
: option.status === "retained"
|
||||
? "Сохранено для сравнения"
|
||||
: option.status === "failed"
|
||||
? "Gate не пройден"
|
||||
: "Требует классификации"}
|
||||
>
|
||||
</span>
|
||||
) : undefined,
|
||||
}))}
|
||||
variant="split"
|
||||
menuWidth="anchor"
|
||||
disabled={disabled}
|
||||
searchable={searchable}
|
||||
searchPlaceholder={`Найти: ${label.toLocaleLowerCase("ru-RU")}`}
|
||||
onChange={(next) => onChange(next)}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export function LaboratorySummary({
|
||||
title,
|
||||
description,
|
||||
status,
|
||||
statusTone = "neutral",
|
||||
facts,
|
||||
brief,
|
||||
method,
|
||||
}: {
|
||||
title: string;
|
||||
description: string;
|
||||
status: string;
|
||||
statusTone?: "neutral" | "success" | "warning" | "danger" | "accent";
|
||||
facts: readonly { label: string; value: string }[];
|
||||
brief: LaboratoryBrief;
|
||||
method?: LaboratoryMethod | null;
|
||||
}) {
|
||||
const methodComplete = method?.completeness === "complete";
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const detailsId = useId();
|
||||
return (
|
||||
<section className="laboratory-summary" data-expanded={expanded ? "true" : undefined}>
|
||||
<header>
|
||||
<div className="laboratory-summary__heading">
|
||||
<span className="section-eyebrow">ЛАБОРАТОРНАЯ РАБОТА</span>
|
||||
<h2>{title}</h2>
|
||||
</div>
|
||||
<div className="laboratory-summary__actions">
|
||||
<StatusBadge tone={statusTone}>{status}</StatusBadge>
|
||||
<IconButton
|
||||
label={expanded ? "Свернуть подробности лабораторной работы" : "Раскрыть подробности лабораторной работы"}
|
||||
aria-expanded={expanded}
|
||||
aria-controls={detailsId}
|
||||
onClick={() => setExpanded((current) => !current)}
|
||||
>
|
||||
<span className="laboratory-summary__toggle-glyph" aria-hidden="true">
|
||||
<Icon name="chevron-down" />
|
||||
</span>
|
||||
</IconButton>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div id={detailsId} className="laboratory-summary__details" hidden={!expanded}>
|
||||
<p className="laboratory-summary__description">{description}</p>
|
||||
<dl className="laboratory-summary__facts">
|
||||
{facts.map((fact) => (
|
||||
<div key={fact.label}>
|
||||
<dt>{fact.label}</dt>
|
||||
<dd>{fact.value}</dd>
|
||||
</div>
|
||||
))}
|
||||
</dl>
|
||||
|
||||
<dl className="laboratory-summary__brief">
|
||||
<div>
|
||||
<dt>Задача</dt>
|
||||
<dd>{brief.question}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Как проверяли</dt>
|
||||
<dd>{brief.approach}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Главный результат</dt>
|
||||
<dd>{brief.principalResult}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Ограничение</dt>
|
||||
<dd>{brief.limitation}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
|
||||
{method ? (
|
||||
<div className="laboratory-summary__method">
|
||||
<header>
|
||||
<div>
|
||||
<span className="section-eyebrow">МЕТОД</span>
|
||||
<strong>{method.pipelineId}</strong>
|
||||
</div>
|
||||
<small>
|
||||
{EXECUTION_LABELS[method.executionClass]}
|
||||
{" · "}
|
||||
{methodComplete ? "полная идентичность" : "legacy · частично"}
|
||||
</small>
|
||||
</header>
|
||||
<dl className="laboratory-summary__components">
|
||||
{method.components.map((component, index) => (
|
||||
<div key={`${component.kind}:${component.name}:${index}`}>
|
||||
<dt>{COMPONENT_LABELS[component.kind]}</dt>
|
||||
<dd>
|
||||
<strong>{component.name}</strong>
|
||||
<small>
|
||||
{component.role}
|
||||
{" · "}
|
||||
{component.version}
|
||||
{component.identitySha256
|
||||
? ` · ${component.identitySha256.slice(0, 12)}`
|
||||
: ""}
|
||||
</small>
|
||||
</dd>
|
||||
</div>
|
||||
))}
|
||||
</dl>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export function LaboratoryEvidence({
|
||||
eyebrow,
|
||||
title,
|
||||
kind,
|
||||
resizable = false,
|
||||
children,
|
||||
}: {
|
||||
eyebrow: string;
|
||||
title: string;
|
||||
kind: LaboratoryEvidenceKind;
|
||||
resizable?: boolean;
|
||||
children: ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<section
|
||||
className="lab-result-surface"
|
||||
data-evidence-kind={kind}
|
||||
data-resizable={resizable ? "true" : undefined}
|
||||
>
|
||||
<header>
|
||||
<span className="section-eyebrow">{eyebrow}</span>
|
||||
<strong>{title}</strong>
|
||||
</header>
|
||||
<div className="laboratory-evidence-frame">
|
||||
{children}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export function LaboratoryConclusion({
|
||||
proved,
|
||||
notProved,
|
||||
decision,
|
||||
}: {
|
||||
proved: string;
|
||||
notProved: string;
|
||||
decision: string;
|
||||
}) {
|
||||
return (
|
||||
<dl className="laboratory-result-conclusion">
|
||||
<div>
|
||||
<dt>Доказано</dt>
|
||||
<dd>{proved}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Не доказано</dt>
|
||||
<dd>{notProved}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Решение</dt>
|
||||
<dd>{decision}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
);
|
||||
}
|
||||
|
||||
export function LaboratoryMetricGrid({
|
||||
metrics,
|
||||
}: {
|
||||
metrics: readonly LaboratoryResultMetric[];
|
||||
}) {
|
||||
return (
|
||||
<div className="laboratory-result-metrics">
|
||||
{metrics.map((metric) => (
|
||||
<div key={metric.label}>
|
||||
<span>{metric.label}</span>
|
||||
<strong>{metric.value}</strong>
|
||||
<small>{metric.hint}</small>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function LaboratoryResultSummary({
|
||||
title,
|
||||
status,
|
||||
statusTone = "neutral",
|
||||
metrics,
|
||||
conclusion,
|
||||
}: {
|
||||
title: string;
|
||||
status: string;
|
||||
statusTone?: "neutral" | "success" | "warning" | "danger" | "accent";
|
||||
metrics: readonly LaboratoryResultMetric[];
|
||||
conclusion: LaboratoryResultConclusion;
|
||||
}) {
|
||||
return (
|
||||
<section className="laboratory-result-summary">
|
||||
<header>
|
||||
<div>
|
||||
<span className="section-eyebrow">РЕЗУЛЬТАТ И ВЫВОД</span>
|
||||
<h2>{title}</h2>
|
||||
</div>
|
||||
<StatusBadge tone={statusTone}>{status}</StatusBadge>
|
||||
</header>
|
||||
<LaboratoryMetricGrid metrics={metrics} />
|
||||
<LaboratoryConclusion {...conclusion} />
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export function LaboratoryWorkTemplate({
|
||||
summary,
|
||||
evidence,
|
||||
result = null,
|
||||
details = null,
|
||||
}: {
|
||||
summary: ReactNode;
|
||||
evidence: ReactNode;
|
||||
result?: ReactNode;
|
||||
details?: ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className="laboratory-work-template"
|
||||
data-template-contract={LABORATORY_REPORT_TEMPLATE_VERSION}
|
||||
>
|
||||
{summary}
|
||||
{evidence}
|
||||
{result}
|
||||
{details}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
type ReactNode,
|
||||
type RefObject,
|
||||
} from "react";
|
||||
import { SplitPane } from "@nodedc/ui-react";
|
||||
|
||||
import {
|
||||
RecordedFmp4Player,
|
||||
type RecordedObservationPlayback,
|
||||
} from "../RecordedFmp4Player";
|
||||
import { ObservationTimeline } from "../ObservationTimeline";
|
||||
import type { ObservationSourceDescriptor } from "../../core/runtime/contracts";
|
||||
|
||||
export const LABORATORY_RECORDED_CLIP_VIEWER_CONTRACT =
|
||||
"missioncore.laboratory-recorded-clip-viewer/v1" as const;
|
||||
|
||||
export interface LaboratoryRecordedClipFrame {
|
||||
sequence: number;
|
||||
sourceTimeNs: number;
|
||||
}
|
||||
|
||||
export function nearestLaboratoryRecordedClipFrame(
|
||||
frames: readonly LaboratoryRecordedClipFrame[],
|
||||
sourceTimeNs: number,
|
||||
): LaboratoryRecordedClipFrame | null {
|
||||
if (!frames.length || !Number.isFinite(sourceTimeNs)) return null;
|
||||
let left = 0;
|
||||
let right = frames.length - 1;
|
||||
while (left < right) {
|
||||
const middle = Math.floor((left + right) / 2);
|
||||
if (frames[middle]!.sourceTimeNs < sourceTimeNs) left = middle + 1;
|
||||
else right = middle;
|
||||
}
|
||||
const next = frames[left]!;
|
||||
const previous = frames[Math.max(0, left - 1)]!;
|
||||
return Math.abs(previous.sourceTimeNs - sourceTimeNs)
|
||||
<= Math.abs(next.sourceTimeNs - sourceTimeNs)
|
||||
? previous
|
||||
: next;
|
||||
}
|
||||
|
||||
export function laboratoryRecordedClipEndExclusiveNs(
|
||||
frames: readonly LaboratoryRecordedClipFrame[],
|
||||
): number | null {
|
||||
const last = frames.at(-1);
|
||||
if (!last) return null;
|
||||
const deltas = frames.slice(1).flatMap((frame, index) => {
|
||||
const delta = frame.sourceTimeNs - frames[index]!.sourceTimeNs;
|
||||
return Number.isSafeInteger(delta) && delta > 0 ? [delta] : [];
|
||||
}).sort((left, right) => left - right);
|
||||
const typicalDelta = deltas.length
|
||||
? deltas[Math.floor(deltas.length / 2)]!
|
||||
: 100_000_000;
|
||||
return last.sourceTimeNs + typicalDelta;
|
||||
}
|
||||
|
||||
export function laboratoryRecordedClipClockGate(
|
||||
pendingSequence: number | null,
|
||||
observedSequence: number,
|
||||
): { accept: boolean; pendingSequence: number | null } {
|
||||
if (pendingSequence !== null && pendingSequence !== observedSequence) {
|
||||
return { accept: false, pendingSequence };
|
||||
}
|
||||
return { accept: true, pendingSequence: null };
|
||||
}
|
||||
|
||||
export function LaboratoryRecordedClipPlayer({
|
||||
source,
|
||||
segmentCount,
|
||||
frames,
|
||||
sequence,
|
||||
playing,
|
||||
playbackRate,
|
||||
cameraPresentation,
|
||||
continuousPlayback,
|
||||
sourceCount,
|
||||
cameraRef,
|
||||
cameraOverlay,
|
||||
alternativeScene,
|
||||
onSequenceChange,
|
||||
onPlayingChange,
|
||||
onPlaybackRateChange,
|
||||
}: {
|
||||
source: ObservationSourceDescriptor;
|
||||
segmentCount: number;
|
||||
frames: readonly LaboratoryRecordedClipFrame[];
|
||||
sequence: number;
|
||||
playing: boolean;
|
||||
playbackRate: number;
|
||||
cameraPresentation: "primary" | "companion" | "hidden";
|
||||
continuousPlayback: boolean;
|
||||
sourceCount: number;
|
||||
cameraRef?: RefObject<HTMLDivElement | null>;
|
||||
cameraOverlay?: ReactNode;
|
||||
alternativeScene?: ReactNode;
|
||||
onSequenceChange: (sequence: number) => void;
|
||||
onPlayingChange: (playing: boolean) => void;
|
||||
onPlaybackRateChange: (rate: number) => void;
|
||||
}) {
|
||||
const [companionSpatialSize, setCompanionSpatialSize] = useState(69);
|
||||
const lastEmittedSequenceRef = useRef(sequence);
|
||||
const lastObservedSequenceRef = useRef<number | null>(sequence);
|
||||
const pendingSequenceRef = useRef<number | null>(null);
|
||||
const frame = useMemo(
|
||||
() => frames.find((candidate) => candidate.sequence === sequence) ?? frames[0] ?? null,
|
||||
[frames, sequence],
|
||||
);
|
||||
const endExclusiveNs = useMemo(
|
||||
() => laboratoryRecordedClipEndExclusiveNs(frames),
|
||||
[frames],
|
||||
);
|
||||
const playback = useMemo<RecordedObservationPlayback | null>(() => frame ? ({
|
||||
currentSeconds: frame.sourceTimeNs / 1_000_000_000,
|
||||
playing: continuousPlayback && playing,
|
||||
rate: playbackRate,
|
||||
}) : null, [continuousPlayback, frame, playbackRate, playing]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!continuousPlayback && playing) onPlayingChange(false);
|
||||
}, [continuousPlayback, onPlayingChange, playing]);
|
||||
|
||||
useEffect(() => {
|
||||
if (lastObservedSequenceRef.current !== sequence) {
|
||||
pendingSequenceRef.current = sequence;
|
||||
}
|
||||
lastEmittedSequenceRef.current = sequence;
|
||||
}, [sequence]);
|
||||
|
||||
const emitSequence = useCallback((nextSequence: number) => {
|
||||
if (lastEmittedSequenceRef.current === nextSequence) return;
|
||||
lastEmittedSequenceRef.current = nextSequence;
|
||||
onSequenceChange(nextSequence);
|
||||
}, [onSequenceChange]);
|
||||
|
||||
const requestSequence = useCallback((nextSequence: number) => {
|
||||
pendingSequenceRef.current = nextSequence;
|
||||
emitSequence(nextSequence);
|
||||
}, [emitSequence]);
|
||||
|
||||
const handlePlaybackChange = useCallback((next: RecordedObservationPlayback) => {
|
||||
const sourceTimeNs = Math.round(next.currentSeconds * 1_000_000_000);
|
||||
const first = frames[0];
|
||||
if (!first || endExclusiveNs === null) return;
|
||||
if (sourceTimeNs >= endExclusiveNs) {
|
||||
requestSequence(first.sequence);
|
||||
return;
|
||||
}
|
||||
const nearest = nearestLaboratoryRecordedClipFrame(frames, sourceTimeNs);
|
||||
if (!nearest) return;
|
||||
lastObservedSequenceRef.current = nearest.sequence;
|
||||
const gate = laboratoryRecordedClipClockGate(
|
||||
pendingSequenceRef.current,
|
||||
nearest.sequence,
|
||||
);
|
||||
pendingSequenceRef.current = gate.pendingSequence;
|
||||
if (gate.accept) emitSequence(nearest.sequence);
|
||||
}, [emitSequence, endExclusiveNs, frames, requestSequence]);
|
||||
|
||||
const timelineStart = frames[0]?.sourceTimeNs ?? 0;
|
||||
const timelineEnd = frames.at(-1)?.sourceTimeNs ?? timelineStart + 1;
|
||||
const companionVisible = cameraPresentation === "companion";
|
||||
const spatialSize = companionVisible
|
||||
? companionSpatialSize
|
||||
: cameraPresentation === "primary" ? 0 : 100;
|
||||
const spatialPane = (
|
||||
<div
|
||||
className="laboratory-recorded-clip-player__spatial"
|
||||
aria-hidden={cameraPresentation === "primary"}
|
||||
>
|
||||
{alternativeScene}
|
||||
</div>
|
||||
);
|
||||
const cameraPane = (
|
||||
<div
|
||||
ref={cameraRef}
|
||||
className="laboratory-recorded-clip-player__camera"
|
||||
aria-hidden={cameraPresentation === "hidden"}
|
||||
>
|
||||
{playback ? (
|
||||
<RecordedFmp4Player
|
||||
source={source}
|
||||
playback={playback}
|
||||
interactive={false}
|
||||
prepare
|
||||
segmentSequence={frame?.sequence}
|
||||
segmentCount={segmentCount}
|
||||
admissionKey={`${source.id}:${source.delivery?.id ?? "recorded"}`}
|
||||
onPlaybackChange={handlePlaybackChange}
|
||||
onPlayingRejected={() => onPlayingChange(false)}
|
||||
/>
|
||||
) : null}
|
||||
{cameraPresentation !== "hidden" ? cameraOverlay : null}
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div
|
||||
className="laboratory-recorded-clip-player"
|
||||
data-contract={LABORATORY_RECORDED_CLIP_VIEWER_CONTRACT}
|
||||
data-camera-presentation={cameraPresentation}
|
||||
>
|
||||
<div className="laboratory-recorded-clip-player__stage">
|
||||
<SplitPane
|
||||
className="laboratory-recorded-clip-player__split"
|
||||
primary={spatialPane}
|
||||
secondary={cameraPane}
|
||||
primarySize={spatialSize}
|
||||
onPrimarySizeChange={setCompanionSpatialSize}
|
||||
orientation="vertical"
|
||||
minPrimarySize={companionVisible ? 24 : 0}
|
||||
minSecondarySize={companionVisible ? 24 : 0}
|
||||
resizable={companionVisible}
|
||||
separatorLabel="Изменить размер 3D/ПЛАН и правой камеры"
|
||||
/>
|
||||
</div>
|
||||
<ObservationTimeline
|
||||
className="laboratory-recorded-clip-player__timeline"
|
||||
active
|
||||
sourceCount={sourceCount}
|
||||
mode="recorded"
|
||||
seekable
|
||||
synchronization="frame-accurate"
|
||||
rangeNs={{ min: timelineStart, max: Math.max(timelineEnd, timelineStart + 1) }}
|
||||
currentNs={frame?.sourceTimeNs ?? timelineStart}
|
||||
playing={continuousPlayback && playing}
|
||||
playbackRate={continuousPlayback ? playbackRate : undefined}
|
||||
onPlaybackRateChange={continuousPlayback ? onPlaybackRateChange : undefined}
|
||||
onPlayingChange={continuousPlayback ? onPlayingChange : undefined}
|
||||
onSeek={(timeNs) => {
|
||||
const nearest = nearestLaboratoryRecordedClipFrame(frames, timeNs);
|
||||
if (nearest) requestSequence(nearest.sequence);
|
||||
}}
|
||||
showJumpToEnd={false}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
import {
|
||||
useEffect,
|
||||
useRef,
|
||||
type KeyboardEvent as ReactKeyboardEvent,
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
|
||||
const FOCUSABLE = [
|
||||
"a[href]",
|
||||
"button:not([disabled])",
|
||||
"input:not([disabled])",
|
||||
"select:not([disabled])",
|
||||
"textarea:not([disabled])",
|
||||
"[tabindex]:not([tabindex='-1'])",
|
||||
].join(",");
|
||||
|
||||
function keepFocusInside(
|
||||
event: ReactKeyboardEvent<HTMLElement>,
|
||||
frame: HTMLElement,
|
||||
): void {
|
||||
if (event.key !== "Tab") return;
|
||||
const focusable = Array.from(frame.querySelectorAll<HTMLElement>(FOCUSABLE));
|
||||
if (!focusable.length) {
|
||||
event.preventDefault();
|
||||
frame.focus();
|
||||
return;
|
||||
}
|
||||
const first = focusable[0];
|
||||
const last = focusable[focusable.length - 1];
|
||||
if (event.shiftKey && document.activeElement === first) {
|
||||
event.preventDefault();
|
||||
last.focus();
|
||||
} else if (!event.shiftKey && document.activeElement === last) {
|
||||
event.preventDefault();
|
||||
first.focus();
|
||||
}
|
||||
}
|
||||
|
||||
export function LaboratoryReviewWorkspaceFrame({
|
||||
ariaLabel,
|
||||
toolbar,
|
||||
stage,
|
||||
inspector,
|
||||
overlays,
|
||||
interactionEnabled = true,
|
||||
returnFocusTarget,
|
||||
onClose,
|
||||
}: {
|
||||
ariaLabel: string;
|
||||
toolbar: ReactNode;
|
||||
stage: ReactNode;
|
||||
inspector?: ReactNode;
|
||||
overlays?: ReactNode;
|
||||
interactionEnabled?: boolean;
|
||||
returnFocusTarget?: HTMLElement | null;
|
||||
onClose: () => void;
|
||||
}) {
|
||||
const frameRef = useRef<HTMLElement>(null);
|
||||
const onCloseRef = useRef(onClose);
|
||||
onCloseRef.current = onClose;
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof document === "undefined") return;
|
||||
const previousFocus = document.activeElement instanceof HTMLElement
|
||||
? document.activeElement
|
||||
: null;
|
||||
const previousOverflow = document.body.style.overflow;
|
||||
document.body.style.overflow = "hidden";
|
||||
const animationFrame = window.requestAnimationFrame(() => {
|
||||
const firstFocusable = frameRef.current?.querySelector<HTMLElement>(FOCUSABLE);
|
||||
(firstFocusable ?? frameRef.current)?.focus();
|
||||
});
|
||||
return () => {
|
||||
window.cancelAnimationFrame(animationFrame);
|
||||
document.body.style.overflow = previousOverflow;
|
||||
const target = returnFocusTarget?.isConnected ? returnFocusTarget : previousFocus;
|
||||
window.requestAnimationFrame(() => target?.focus());
|
||||
};
|
||||
}, [returnFocusTarget]);
|
||||
|
||||
if (typeof document === "undefined") return null;
|
||||
|
||||
return createPortal(
|
||||
<section
|
||||
ref={frameRef}
|
||||
className="laboratory-review-workspace"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label={ariaLabel}
|
||||
data-has-inspector={inspector ? "true" : undefined}
|
||||
tabIndex={-1}
|
||||
onKeyDown={(event) => {
|
||||
if (!interactionEnabled) return;
|
||||
if (event.key === "Escape") {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
onCloseRef.current();
|
||||
return;
|
||||
}
|
||||
keepFocusInside(event, event.currentTarget);
|
||||
}}
|
||||
>
|
||||
<header className="laboratory-review-workspace__toolbar">{toolbar}</header>
|
||||
<div className="laboratory-review-workspace__stage">{stage}</div>
|
||||
{inspector ? <footer className="laboratory-review-workspace__inspector">{inspector}</footer> : null}
|
||||
{overlays}
|
||||
</section>,
|
||||
document.body,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import { GlassSurface } from "@nodedc/ui-react";
|
||||
import { CanonicalResultRerunReplay } from "./CanonicalResultRerunReplay";
|
||||
import { resolveCanonicalLabReplay } from "../../core/laboratory/canonicalLabReplay";
|
||||
import type { ObservatoryPortableResultReview } from "../../core/observatory/recordedRun";
|
||||
import type { ObservationSessionReplayLaunch } from "../../core/observation/sessionArchive";
|
||||
|
||||
function resolvePortableTgsReplay(resultId: string, launch: ObservationSessionReplayLaunch,
|
||||
options: { signal: AbortSignal }) {
|
||||
return resolveCanonicalLabReplay(resultId, launch, { ...options, sourceKind: "portable-tgs" });
|
||||
}
|
||||
|
||||
function resolvePortableSemanticReplay(resultId: string, launch: ObservationSessionReplayLaunch,
|
||||
options: { signal: AbortSignal }) {
|
||||
return resolveCanonicalLabReplay(resultId, launch, { ...options, sourceKind: "portable-semantic" });
|
||||
}
|
||||
|
||||
function resolvePortableObjectReplay(resultId: string, launch: ObservationSessionReplayLaunch,
|
||||
options: { signal: AbortSignal }) {
|
||||
return resolveCanonicalLabReplay(resultId, launch, { ...options, sourceKind: "portable-objects" });
|
||||
}
|
||||
|
||||
export function PortableResultReplay({
|
||||
review,
|
||||
label,
|
||||
}: {
|
||||
review: ObservatoryPortableResultReview;
|
||||
label?: string;
|
||||
}) {
|
||||
const schema = review.resultDocument.schema_version;
|
||||
const tgs = schema === "missioncore.recorded-tgs-costmap-review/v2";
|
||||
const module = review.resultDocument.module;
|
||||
const moduleId = module && typeof module === "object" && "module_id" in module
|
||||
? module.module_id : undefined;
|
||||
const semantics = schema === "missioncore.recorded-eomt-ddrnet-review/v2"
|
||||
|| (schema === "missioncore.recorded-ai-layer-review/v1"
|
||||
&& (moduleId === "ddrnet" || moduleId === "eomt"));
|
||||
const objects = schema === "missioncore.recorded-ai-layer-review/v1"
|
||||
&& (moduleId === "rf-detr" || moduleId === "object-distance");
|
||||
if (!tgs && !semantics && !objects) return (
|
||||
<GlassSurface className="observatory-replay-state" padding="lg">
|
||||
<p role="alert">Сохранённый результат этого профиля пока не поддерживает визуальный разбор.</p>
|
||||
</GlassSurface>
|
||||
);
|
||||
return <CanonicalResultRerunReplay key={review.resultId} resultId={review.resultId}
|
||||
sessionId={review.sourceSessionId} portable costmap={tgs} semantics={semantics}
|
||||
detections={objects}
|
||||
moduleIds={typeof moduleId === "string" ? [moduleId] : []}
|
||||
label={label}
|
||||
resolveReplay={tgs ? resolvePortableTgsReplay
|
||||
: objects ? resolvePortableObjectReplay : resolvePortableSemanticReplay} />;
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
export type RecordedEvidenceBoxTone =
|
||||
| "accent"
|
||||
| "danger"
|
||||
| "success"
|
||||
| "warning"
|
||||
| "neutral";
|
||||
|
||||
export interface RecordedEvidenceBox {
|
||||
boxXyxy: readonly [number, number, number, number];
|
||||
label: string;
|
||||
tone: RecordedEvidenceBoxTone;
|
||||
dashed?: boolean;
|
||||
}
|
||||
|
||||
function rgba(
|
||||
host: HTMLElement,
|
||||
token: string,
|
||||
fallback: readonly [number, number, number],
|
||||
alpha = 1,
|
||||
): string {
|
||||
const channels = getComputedStyle(host)
|
||||
.getPropertyValue(token)
|
||||
.trim()
|
||||
.match(/[\d.]+/g)
|
||||
?.slice(0, 3)
|
||||
.map(Number);
|
||||
const [red, green, blue] = channels?.length === 3 ? channels : fallback;
|
||||
return `rgba(${red}, ${green}, ${blue}, ${alpha})`;
|
||||
}
|
||||
|
||||
function toneColor(host: HTMLElement, tone: RecordedEvidenceBoxTone): string {
|
||||
if (tone === "danger") return rgba(host, "--nodedc-danger-rgb", [255, 104, 112]);
|
||||
if (tone === "success") return rgba(host, "--nodedc-success-rgb", [181, 255, 90]);
|
||||
if (tone === "warning") return rgba(host, "--nodedc-warning-rgb", [255, 197, 92]);
|
||||
if (tone === "neutral") return rgba(host, "--nodedc-foreground-rgb", [245, 245, 245], 0.7);
|
||||
return rgba(host, "--nodedc-accent-rgb", [232, 56, 126]);
|
||||
}
|
||||
|
||||
export function RecordedEvidenceBoxOverlay({
|
||||
imageWidth,
|
||||
imageHeight,
|
||||
boxes,
|
||||
ariaLabel,
|
||||
}: {
|
||||
imageWidth: number;
|
||||
imageHeight: number;
|
||||
boxes: readonly RecordedEvidenceBox[];
|
||||
ariaLabel: string;
|
||||
}) {
|
||||
const canvasRef = useRef<HTMLCanvasElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const canvas = canvasRef.current;
|
||||
const host = canvas?.parentElement;
|
||||
if (!host || !canvas) return;
|
||||
const context = canvas.getContext("2d");
|
||||
if (!context) return;
|
||||
|
||||
const render = () => {
|
||||
const width = Math.max(host.clientWidth, 1);
|
||||
const height = Math.max(host.clientHeight, 1);
|
||||
const pixelRatio = Math.min(window.devicePixelRatio, 1.5);
|
||||
canvas.width = Math.round(width * pixelRatio);
|
||||
canvas.height = Math.round(height * pixelRatio);
|
||||
canvas.style.width = `${width}px`;
|
||||
canvas.style.height = `${height}px`;
|
||||
context.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
|
||||
context.clearRect(0, 0, width, height);
|
||||
|
||||
const scale = Math.min(width / imageWidth, height / imageHeight);
|
||||
const drawWidth = imageWidth * scale;
|
||||
const drawHeight = imageHeight * scale;
|
||||
const offsetX = (width - drawWidth) / 2;
|
||||
const offsetY = (height - drawHeight) / 2;
|
||||
for (const item of boxes) {
|
||||
const [left, top, right, bottom] = item.boxXyxy;
|
||||
const x = offsetX + left * scale;
|
||||
const y = offsetY + top * scale;
|
||||
const boxWidth = (right - left) * scale;
|
||||
const boxHeight = (bottom - top) * scale;
|
||||
const stroke = toneColor(host, item.tone);
|
||||
context.strokeStyle = stroke;
|
||||
context.lineWidth = Math.max(1.5, 2 * scale);
|
||||
context.setLineDash(item.dashed ? [5, 4] : []);
|
||||
context.strokeRect(x, y, boxWidth, boxHeight);
|
||||
context.setLineDash([]);
|
||||
|
||||
const fontSize = Math.max(9, 10 * scale);
|
||||
context.font = `650 ${fontSize}px Inter, system-ui, sans-serif`;
|
||||
const labelWidth = Math.min(drawWidth, context.measureText(item.label).width + 8);
|
||||
const labelHeight = fontSize + 6;
|
||||
const labelX = Math.min(offsetX + drawWidth - labelWidth, Math.max(offsetX, x));
|
||||
const labelY = Math.max(offsetY, y - labelHeight);
|
||||
context.fillStyle = rgba(host, "--nodedc-canvas-rgb", [5, 5, 6], 0.9);
|
||||
context.fillRect(labelX, labelY, labelWidth, labelHeight);
|
||||
context.fillStyle = stroke;
|
||||
context.fillText(item.label, labelX + 4, labelY + fontSize + 1, labelWidth - 8);
|
||||
}
|
||||
};
|
||||
|
||||
const observer = new ResizeObserver(render);
|
||||
observer.observe(host);
|
||||
render();
|
||||
return () => observer.disconnect();
|
||||
}, [boxes, imageHeight, imageWidth]);
|
||||
|
||||
return <canvas ref={canvasRef} role="img" aria-label={ariaLabel} />;
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { Icon } from "@nodedc/ui-react";
|
||||
|
||||
import {
|
||||
RecordedEvidenceBoxOverlay,
|
||||
type RecordedEvidenceBox,
|
||||
} from "./RecordedEvidenceBoxOverlay";
|
||||
import {
|
||||
RecordedEvidenceSemanticMaskOverlay,
|
||||
type RecordedEvidenceSemanticOverlay,
|
||||
} from "./RecordedEvidenceSemanticMaskOverlay";
|
||||
import {
|
||||
RecordedEvidencePointCloudOverlay,
|
||||
type RecordedEvidencePointCloudOverlayData,
|
||||
} from "./RecordedEvidencePointCloudOverlay";
|
||||
|
||||
export function RecordedEvidenceImageScene({
|
||||
src,
|
||||
imageWidth,
|
||||
imageHeight,
|
||||
boxes,
|
||||
semanticOverlay,
|
||||
pointCloudOverlay,
|
||||
ariaLabel,
|
||||
}: {
|
||||
src: string;
|
||||
imageWidth: number;
|
||||
imageHeight: number;
|
||||
boxes: readonly RecordedEvidenceBox[];
|
||||
semanticOverlay?: RecordedEvidenceSemanticOverlay;
|
||||
pointCloudOverlay?: RecordedEvidencePointCloudOverlayData;
|
||||
ariaLabel: string;
|
||||
}) {
|
||||
const [state, setState] = useState<"loading" | "ready" | "error">("loading");
|
||||
|
||||
useEffect(() => setState("loading"), [src]);
|
||||
|
||||
return (
|
||||
<div className="recorded-evidence-image-scene">
|
||||
<img
|
||||
src={src}
|
||||
alt=""
|
||||
draggable={false}
|
||||
onLoad={() => setState("ready")}
|
||||
onError={() => setState("error")}
|
||||
/>
|
||||
{state === "ready" ? (
|
||||
<>
|
||||
{semanticOverlay ? (
|
||||
<RecordedEvidenceSemanticMaskOverlay
|
||||
{...semanticOverlay}
|
||||
imageWidth={imageWidth}
|
||||
imageHeight={imageHeight}
|
||||
/>
|
||||
) : null}
|
||||
{pointCloudOverlay ? (
|
||||
<RecordedEvidencePointCloudOverlay
|
||||
imageWidth={imageWidth}
|
||||
imageHeight={imageHeight}
|
||||
overlay={pointCloudOverlay}
|
||||
/>
|
||||
) : null}
|
||||
<RecordedEvidenceBoxOverlay
|
||||
imageWidth={imageWidth}
|
||||
imageHeight={imageHeight}
|
||||
boxes={boxes}
|
||||
ariaLabel={ariaLabel}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<div className="l3-visual-audit__state" role="status">
|
||||
{state === "loading" ? (
|
||||
<span className="busy-indicator" aria-hidden="true" />
|
||||
) : (
|
||||
<Icon name="alert" size={18} />
|
||||
)}
|
||||
<span>
|
||||
{state === "loading"
|
||||
? "Декодируем один точный CAMERA-кадр"
|
||||
: "Точный CAMERA-кадр недоступен."}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
export type RecordedEvidenceProjectedPoint = readonly [number, number, number];
|
||||
|
||||
export interface RecordedEvidencePointCloudOverlayData {
|
||||
pointsXyd: readonly RecordedEvidenceProjectedPoint[];
|
||||
sourcePointCount: number;
|
||||
projectedPointCount: number;
|
||||
projection:
|
||||
| "factory-kb4-exact"
|
||||
| "factory-kb4-causal-registered-accumulation";
|
||||
ariaLabel: string;
|
||||
}
|
||||
|
||||
const DEPTH_BUCKETS = 64;
|
||||
|
||||
function depthBucket(depthM: number): number {
|
||||
const normalized = Math.max(0, Math.min(1, (depthM - 0.5) / 24));
|
||||
return Math.round(normalized * (DEPTH_BUCKETS - 1));
|
||||
}
|
||||
|
||||
function depthColor(bucket: number): string {
|
||||
const hue = 18 + bucket / (DEPTH_BUCKETS - 1) * 190;
|
||||
return `hsla(${hue}, 96%, 62%, 0.86)`;
|
||||
}
|
||||
|
||||
export function RecordedEvidencePointCloudOverlay({
|
||||
imageWidth,
|
||||
imageHeight,
|
||||
overlay,
|
||||
}: {
|
||||
imageWidth: number;
|
||||
imageHeight: number;
|
||||
overlay: RecordedEvidencePointCloudOverlayData;
|
||||
}) {
|
||||
const canvasRef = useRef<HTMLCanvasElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const canvas = canvasRef.current;
|
||||
const host = canvas?.parentElement;
|
||||
if (!canvas || !host) return;
|
||||
const context = canvas.getContext("2d");
|
||||
if (!context) return;
|
||||
|
||||
const render = () => {
|
||||
const width = Math.max(host.clientWidth, 1);
|
||||
const height = Math.max(host.clientHeight, 1);
|
||||
const pixelRatio = Math.min(window.devicePixelRatio, 1.5);
|
||||
canvas.width = Math.round(width * pixelRatio);
|
||||
canvas.height = Math.round(height * pixelRatio);
|
||||
canvas.style.width = `${width}px`;
|
||||
canvas.style.height = `${height}px`;
|
||||
context.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
|
||||
context.clearRect(0, 0, width, height);
|
||||
|
||||
const scale = Math.min(width / imageWidth, height / imageHeight);
|
||||
const offsetX = (width - imageWidth * scale) / 2;
|
||||
const offsetY = (height - imageHeight * scale) / 2;
|
||||
const radius = Math.max(0.8, Math.min(2.2, scale * 1.45));
|
||||
const buckets = Array.from(
|
||||
{ length: DEPTH_BUCKETS },
|
||||
(): [number, number][] => [],
|
||||
);
|
||||
for (const [imageX, imageY, depthM] of overlay.pointsXyd) {
|
||||
const bucket = depthBucket(depthM);
|
||||
buckets[bucket]!.push([
|
||||
offsetX + imageX * scale,
|
||||
offsetY + imageY * scale,
|
||||
]);
|
||||
}
|
||||
for (let bucket = 0; bucket < buckets.length; bucket += 1) {
|
||||
const points = buckets[bucket];
|
||||
if (!points?.length) continue;
|
||||
context.beginPath();
|
||||
for (const [x, y] of points) {
|
||||
context.moveTo(x + radius, y);
|
||||
context.arc(x, y, radius, 0, Math.PI * 2);
|
||||
}
|
||||
context.fillStyle = depthColor(bucket);
|
||||
context.fill();
|
||||
}
|
||||
};
|
||||
|
||||
const observer = new ResizeObserver(render);
|
||||
observer.observe(host);
|
||||
render();
|
||||
return () => observer.disconnect();
|
||||
}, [imageHeight, imageWidth, overlay]);
|
||||
|
||||
return <canvas ref={canvasRef} role="img" aria-label={overlay.ariaLabel} />;
|
||||
}
|
||||
+647
@@ -0,0 +1,647 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
export interface RecordedEvidenceSemanticClass {
|
||||
id: number;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export type RecordedEvidenceSemanticToken =
|
||||
| "--nodedc-accent-rgb"
|
||||
| "--nodedc-danger-rgb"
|
||||
| "--nodedc-foreground-rgb"
|
||||
| "--nodedc-success-rgb"
|
||||
| "--nodedc-text-muted"
|
||||
| "--nodedc-warning-rgb";
|
||||
|
||||
export type RecordedEvidenceSemanticColor =
|
||||
| {
|
||||
kind: "token";
|
||||
token: RecordedEvidenceSemanticToken;
|
||||
}
|
||||
| {
|
||||
kind: "diagnostic";
|
||||
rgb: readonly [number, number, number];
|
||||
}
|
||||
| {
|
||||
kind: "transparent";
|
||||
};
|
||||
|
||||
export interface RecordedEvidenceSemanticPaletteEntry {
|
||||
classId: number;
|
||||
color: RecordedEvidenceSemanticColor;
|
||||
opacity?: number;
|
||||
}
|
||||
|
||||
export interface RecordedEvidenceSemanticOverlay {
|
||||
src: string;
|
||||
prefetchSrcs?: readonly string[];
|
||||
classes: readonly RecordedEvidenceSemanticClass[];
|
||||
palette: readonly RecordedEvidenceSemanticPaletteEntry[];
|
||||
opacity?: number;
|
||||
ariaLabel: string;
|
||||
}
|
||||
|
||||
interface DecodedSemanticMask {
|
||||
key: string;
|
||||
width: number;
|
||||
height: number;
|
||||
source: ImageBitmap | HTMLImageElement;
|
||||
release: () => void;
|
||||
}
|
||||
|
||||
interface PendingSemanticMask {
|
||||
controller: AbortController;
|
||||
subscribers: number;
|
||||
promise: Promise<DecodedSemanticMask>;
|
||||
}
|
||||
|
||||
const MASK_CACHE_LIMIT = 20;
|
||||
const MASK_PREFETCH_LIMIT = 12;
|
||||
const decodedMaskCache = new Map<string, DecodedSemanticMask>();
|
||||
const pendingMaskCache = new Map<string, PendingSemanticMask>();
|
||||
|
||||
const TOKEN_FALLBACKS: Record<RecordedEvidenceSemanticToken, readonly [number, number, number]> = {
|
||||
"--nodedc-accent-rgb": [232, 56, 126],
|
||||
"--nodedc-danger-rgb": [255, 104, 112],
|
||||
"--nodedc-foreground-rgb": [245, 245, 245],
|
||||
"--nodedc-success-rgb": [181, 255, 90],
|
||||
"--nodedc-text-muted": [147, 151, 159],
|
||||
"--nodedc-warning-rgb": [255, 197, 92],
|
||||
};
|
||||
|
||||
function clampChannel(value: number): number {
|
||||
return Math.max(0, Math.min(255, Math.round(value)));
|
||||
}
|
||||
|
||||
function clampOpacity(value: number | undefined, fallback: number): number {
|
||||
return Math.max(0, Math.min(1, Number.isFinite(value) ? Number(value) : fallback));
|
||||
}
|
||||
|
||||
function semanticMaskKey(src: string, width: number, height: number): string {
|
||||
return `${width}x${height}:${src}`;
|
||||
}
|
||||
|
||||
function rememberMask(mask: DecodedSemanticMask): void {
|
||||
const previous = decodedMaskCache.get(mask.key);
|
||||
if (previous && previous !== mask) previous.release();
|
||||
decodedMaskCache.delete(mask.key);
|
||||
decodedMaskCache.set(mask.key, mask);
|
||||
while (decodedMaskCache.size > MASK_CACHE_LIMIT) {
|
||||
const oldest = decodedMaskCache.keys().next().value;
|
||||
if (typeof oldest !== "string") break;
|
||||
const evicted = decodedMaskCache.get(oldest);
|
||||
decodedMaskCache.delete(oldest);
|
||||
evicted?.release();
|
||||
}
|
||||
}
|
||||
|
||||
async function imageSourceFromBlob(
|
||||
blob: Blob,
|
||||
signal: AbortSignal,
|
||||
): Promise<{ source: ImageBitmap | HTMLImageElement; width: number; height: number; release: () => void }> {
|
||||
if (typeof createImageBitmap === "function") {
|
||||
const bitmap = await createImageBitmap(blob, {
|
||||
colorSpaceConversion: "none",
|
||||
premultiplyAlpha: "none",
|
||||
});
|
||||
if (signal.aborted) {
|
||||
bitmap.close();
|
||||
throw new DOMException("Aborted", "AbortError");
|
||||
}
|
||||
return {
|
||||
source: bitmap,
|
||||
width: bitmap.width,
|
||||
height: bitmap.height,
|
||||
release: () => bitmap.close(),
|
||||
};
|
||||
}
|
||||
|
||||
const objectUrl = URL.createObjectURL(blob);
|
||||
const image = new Image();
|
||||
image.decoding = "async";
|
||||
try {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const cleanup = () => {
|
||||
image.removeEventListener("load", onLoad);
|
||||
image.removeEventListener("error", onError);
|
||||
signal.removeEventListener("abort", onAbort);
|
||||
};
|
||||
const onLoad = () => {
|
||||
cleanup();
|
||||
resolve();
|
||||
};
|
||||
const onError = () => {
|
||||
cleanup();
|
||||
reject(new Error("Semantic mask image decode failed"));
|
||||
};
|
||||
const onAbort = () => {
|
||||
cleanup();
|
||||
reject(new DOMException("Aborted", "AbortError"));
|
||||
};
|
||||
image.addEventListener("load", onLoad, { once: true });
|
||||
image.addEventListener("error", onError, { once: true });
|
||||
signal.addEventListener("abort", onAbort, { once: true });
|
||||
image.src = objectUrl;
|
||||
});
|
||||
return {
|
||||
source: image,
|
||||
width: image.naturalWidth,
|
||||
height: image.naturalHeight,
|
||||
release: () => URL.revokeObjectURL(objectUrl),
|
||||
};
|
||||
} catch (error) {
|
||||
URL.revokeObjectURL(objectUrl);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function decodeSemanticMask(
|
||||
key: string,
|
||||
src: string,
|
||||
expectedWidth: number,
|
||||
expectedHeight: number,
|
||||
signal: AbortSignal,
|
||||
): Promise<DecodedSemanticMask> {
|
||||
const response = await fetch(src, { cache: "force-cache", signal });
|
||||
if (!response.ok) throw new Error(`Semantic mask request failed: ${response.status}`);
|
||||
const blob = await response.blob();
|
||||
if (signal.aborted) throw new DOMException("Aborted", "AbortError");
|
||||
const decoded = await imageSourceFromBlob(blob, signal);
|
||||
if (decoded.width !== expectedWidth || decoded.height !== expectedHeight) {
|
||||
decoded.release();
|
||||
throw new Error(
|
||||
`Semantic mask dimensions ${decoded.width}x${decoded.height} do not match ${expectedWidth}x${expectedHeight}`,
|
||||
);
|
||||
}
|
||||
return {
|
||||
key,
|
||||
width: decoded.width,
|
||||
height: decoded.height,
|
||||
source: decoded.source,
|
||||
release: decoded.release,
|
||||
};
|
||||
}
|
||||
|
||||
function subscribeToSemanticMask(
|
||||
src: string,
|
||||
width: number,
|
||||
height: number,
|
||||
): { promise: Promise<DecodedSemanticMask>; release: () => void } {
|
||||
const key = semanticMaskKey(src, width, height);
|
||||
const cached = decodedMaskCache.get(key);
|
||||
if (cached) {
|
||||
decodedMaskCache.delete(key);
|
||||
decodedMaskCache.set(key, cached);
|
||||
return { promise: Promise.resolve(cached), release: () => undefined };
|
||||
}
|
||||
|
||||
let pending = pendingMaskCache.get(key);
|
||||
if (!pending) {
|
||||
const controller = new AbortController();
|
||||
let next: PendingSemanticMask;
|
||||
const promise = decodeSemanticMask(key, src, width, height, controller.signal)
|
||||
.then((mask) => {
|
||||
rememberMask(mask);
|
||||
return mask;
|
||||
})
|
||||
.finally(() => {
|
||||
if (pendingMaskCache.get(key) === next) pendingMaskCache.delete(key);
|
||||
});
|
||||
next = { controller, subscribers: 0, promise };
|
||||
pendingMaskCache.set(key, next);
|
||||
pending = next;
|
||||
}
|
||||
pending.subscribers += 1;
|
||||
let released = false;
|
||||
return {
|
||||
promise: pending.promise,
|
||||
release: () => {
|
||||
if (released) return;
|
||||
released = true;
|
||||
pending!.subscribers -= 1;
|
||||
if (pending!.subscribers > 0 || decodedMaskCache.has(key)) return;
|
||||
pending!.controller.abort();
|
||||
if (pendingMaskCache.get(key) === pending) pendingMaskCache.delete(key);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function recordedEvidenceSemanticCssColor(
|
||||
color: RecordedEvidenceSemanticColor,
|
||||
): string {
|
||||
if (color.kind === "transparent") return "transparent";
|
||||
if (color.kind === "token") return `rgb(var(${color.token}))`;
|
||||
const [red, green, blue] = color.rgb.map(clampChannel);
|
||||
return `rgb(${red} ${green} ${blue})`;
|
||||
}
|
||||
|
||||
export function resolveRecordedEvidenceSemanticRgb(
|
||||
host: HTMLElement,
|
||||
color: RecordedEvidenceSemanticColor,
|
||||
): readonly [number, number, number] | null {
|
||||
if (color.kind === "transparent") return null;
|
||||
if (color.kind === "diagnostic") return color.rgb.map(clampChannel) as [number, number, number];
|
||||
const channels = getComputedStyle(host)
|
||||
.getPropertyValue(color.token)
|
||||
.trim()
|
||||
.match(/[\d.]+/g)
|
||||
?.slice(0, 3)
|
||||
.map(Number);
|
||||
return channels?.length === 3
|
||||
? channels.map(clampChannel) as [number, number, number]
|
||||
: TOKEN_FALLBACKS[color.token];
|
||||
}
|
||||
|
||||
interface SemanticMaskRenderer {
|
||||
clear: (width: number, height: number, pixelRatio: number) => void;
|
||||
render: (
|
||||
mask: DecodedSemanticMask,
|
||||
palette: Uint8Array,
|
||||
width: number,
|
||||
height: number,
|
||||
pixelRatio: number,
|
||||
) => void;
|
||||
dispose: () => void;
|
||||
}
|
||||
|
||||
function compileSemanticShader(
|
||||
gl: WebGLRenderingContext,
|
||||
type: number,
|
||||
source: string,
|
||||
): WebGLShader | null {
|
||||
const shader = gl.createShader(type);
|
||||
if (!shader) return null;
|
||||
gl.shaderSource(shader, source);
|
||||
gl.compileShader(shader);
|
||||
if (gl.getShaderParameter(shader, gl.COMPILE_STATUS)) return shader;
|
||||
gl.deleteShader(shader);
|
||||
return null;
|
||||
}
|
||||
|
||||
function createSemanticMaskRenderer(canvas: HTMLCanvasElement): SemanticMaskRenderer | null {
|
||||
const gl = canvas.getContext("webgl", {
|
||||
alpha: true,
|
||||
antialias: false,
|
||||
depth: false,
|
||||
premultipliedAlpha: false,
|
||||
preserveDrawingBuffer: true,
|
||||
});
|
||||
if (!gl) return null;
|
||||
const vertexShader = compileSemanticShader(gl, gl.VERTEX_SHADER, `
|
||||
attribute vec2 a_position;
|
||||
attribute vec2 a_uv;
|
||||
varying vec2 v_uv;
|
||||
void main() {
|
||||
gl_Position = vec4(a_position, 0.0, 1.0);
|
||||
v_uv = a_uv;
|
||||
}
|
||||
`);
|
||||
const fragmentShader = compileSemanticShader(gl, gl.FRAGMENT_SHADER, `
|
||||
precision mediump float;
|
||||
uniform sampler2D u_mask;
|
||||
uniform sampler2D u_palette;
|
||||
varying vec2 v_uv;
|
||||
void main() {
|
||||
// DOM image sources use a top-left origin while WebGL texture coordinates
|
||||
// use a bottom-left origin. UNPACK_FLIP_Y_WEBGL is ignored for
|
||||
// ImageBitmap uploads, so keep upload semantics stable and flip V here.
|
||||
vec2 mask_uv = vec2(v_uv.x, 1.0 - v_uv.y);
|
||||
float class_id = floor(texture2D(u_mask, mask_uv).r * 255.0 + 0.5);
|
||||
gl_FragColor = texture2D(u_palette, vec2((class_id + 0.5) / 256.0, 0.5));
|
||||
}
|
||||
`);
|
||||
if (!vertexShader || !fragmentShader) {
|
||||
if (vertexShader) gl.deleteShader(vertexShader);
|
||||
if (fragmentShader) gl.deleteShader(fragmentShader);
|
||||
return null;
|
||||
}
|
||||
const program = gl.createProgram();
|
||||
if (!program) {
|
||||
gl.deleteShader(vertexShader);
|
||||
gl.deleteShader(fragmentShader);
|
||||
return null;
|
||||
}
|
||||
gl.attachShader(program, vertexShader);
|
||||
gl.attachShader(program, fragmentShader);
|
||||
gl.linkProgram(program);
|
||||
gl.deleteShader(vertexShader);
|
||||
gl.deleteShader(fragmentShader);
|
||||
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
||||
gl.deleteProgram(program);
|
||||
return null;
|
||||
}
|
||||
const buffer = gl.createBuffer();
|
||||
const maskTexture = gl.createTexture();
|
||||
const paletteTexture = gl.createTexture();
|
||||
if (!buffer || !maskTexture || !paletteTexture) {
|
||||
if (buffer) gl.deleteBuffer(buffer);
|
||||
if (maskTexture) gl.deleteTexture(maskTexture);
|
||||
if (paletteTexture) gl.deleteTexture(paletteTexture);
|
||||
gl.deleteProgram(program);
|
||||
return null;
|
||||
}
|
||||
const positionLocation = gl.getAttribLocation(program, "a_position");
|
||||
const uvLocation = gl.getAttribLocation(program, "a_uv");
|
||||
const maskLocation = gl.getUniformLocation(program, "u_mask");
|
||||
const paletteLocation = gl.getUniformLocation(program, "u_palette");
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
||||
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
|
||||
-1, -1, 0, 0,
|
||||
1, -1, 1, 0,
|
||||
-1, 1, 0, 1,
|
||||
1, 1, 1, 1,
|
||||
]), gl.STATIC_DRAW);
|
||||
|
||||
const resize = (width: number, height: number, pixelRatio: number) => {
|
||||
const pixelWidth = Math.max(1, Math.round(width * pixelRatio));
|
||||
const pixelHeight = Math.max(1, Math.round(height * pixelRatio));
|
||||
if (canvas.width !== pixelWidth) canvas.width = pixelWidth;
|
||||
if (canvas.height !== pixelHeight) canvas.height = pixelHeight;
|
||||
canvas.style.width = `${width}px`;
|
||||
canvas.style.height = `${height}px`;
|
||||
};
|
||||
|
||||
const clear = (width: number, height: number, pixelRatio: number) => {
|
||||
resize(width, height, pixelRatio);
|
||||
gl.viewport(0, 0, canvas.width, canvas.height);
|
||||
gl.clearColor(0, 0, 0, 0);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
};
|
||||
|
||||
return {
|
||||
clear,
|
||||
render: (mask, palette, width, height, pixelRatio) => {
|
||||
if (gl.isContextLost()) throw new Error("Semantic WebGL context lost");
|
||||
clear(width, height, pixelRatio);
|
||||
gl.useProgram(program);
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
||||
gl.enableVertexAttribArray(positionLocation);
|
||||
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 16, 0);
|
||||
gl.enableVertexAttribArray(uvLocation);
|
||||
gl.vertexAttribPointer(uvLocation, 2, gl.FLOAT, false, 16, 8);
|
||||
|
||||
gl.activeTexture(gl.TEXTURE0);
|
||||
gl.bindTexture(gl.TEXTURE_2D, maskTexture);
|
||||
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 0);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, mask.source);
|
||||
gl.uniform1i(maskLocation, 0);
|
||||
|
||||
gl.activeTexture(gl.TEXTURE1);
|
||||
gl.bindTexture(gl.TEXTURE_2D, paletteTexture);
|
||||
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 0);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 256, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, palette);
|
||||
gl.uniform1i(paletteLocation, 1);
|
||||
|
||||
const scale = Math.min(width / mask.width, height / mask.height);
|
||||
const drawWidth = mask.width * scale;
|
||||
const drawHeight = mask.height * scale;
|
||||
const offsetX = (width - drawWidth) / 2;
|
||||
const offsetY = (height - drawHeight) / 2;
|
||||
gl.viewport(
|
||||
Math.round(offsetX * pixelRatio),
|
||||
Math.round(offsetY * pixelRatio),
|
||||
Math.max(1, Math.round(drawWidth * pixelRatio)),
|
||||
Math.max(1, Math.round(drawHeight * pixelRatio)),
|
||||
);
|
||||
gl.enable(gl.BLEND);
|
||||
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
||||
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
||||
},
|
||||
dispose: () => {
|
||||
gl.deleteTexture(maskTexture);
|
||||
gl.deleteTexture(paletteTexture);
|
||||
gl.deleteBuffer(buffer);
|
||||
gl.deleteProgram(program);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function semanticPalettePixels(
|
||||
host: HTMLElement,
|
||||
classes: readonly RecordedEvidenceSemanticClass[],
|
||||
palette: readonly RecordedEvidenceSemanticPaletteEntry[],
|
||||
opacity: number,
|
||||
): Uint8Array {
|
||||
const pixels = new Uint8Array(256 * 4);
|
||||
const declaredClassIds = new Set(
|
||||
classes
|
||||
.map((item) => item.id)
|
||||
.filter((classId) => Number.isInteger(classId) && classId >= 0 && classId <= 255),
|
||||
);
|
||||
for (const entry of palette) {
|
||||
if (!declaredClassIds.has(entry.classId)) continue;
|
||||
const rgb = resolveRecordedEvidenceSemanticRgb(host, entry.color);
|
||||
if (!rgb) continue;
|
||||
const offset = entry.classId * 4;
|
||||
pixels[offset] = rgb[0];
|
||||
pixels[offset + 1] = rgb[1];
|
||||
pixels[offset + 2] = rgb[2];
|
||||
pixels[offset + 3] = Math.round(
|
||||
clampOpacity(entry.opacity, 1) * clampOpacity(opacity, 0.46) * 255,
|
||||
);
|
||||
}
|
||||
return pixels;
|
||||
}
|
||||
|
||||
function renderSemanticMask2d(
|
||||
canvas: HTMLCanvasElement,
|
||||
mask: DecodedSemanticMask,
|
||||
palette: Uint8Array,
|
||||
width: number,
|
||||
height: number,
|
||||
pixelRatio: number,
|
||||
) {
|
||||
canvas.width = Math.max(1, Math.round(width * pixelRatio));
|
||||
canvas.height = Math.max(1, Math.round(height * pixelRatio));
|
||||
canvas.style.width = `${width}px`;
|
||||
canvas.style.height = `${height}px`;
|
||||
const context = canvas.getContext("2d");
|
||||
if (!context) return;
|
||||
context.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
|
||||
context.clearRect(0, 0, width, height);
|
||||
const colorCanvas = document.createElement("canvas");
|
||||
colorCanvas.width = mask.width;
|
||||
colorCanvas.height = mask.height;
|
||||
const colorContext = colorCanvas.getContext("2d", { willReadFrequently: true });
|
||||
if (!colorContext) return;
|
||||
colorContext.drawImage(mask.source, 0, 0);
|
||||
const imageData = colorContext.getImageData(0, 0, mask.width, mask.height);
|
||||
for (let offset = 0; offset < imageData.data.length; offset += 4) {
|
||||
const paletteOffset = (imageData.data[offset] ?? 0) * 4;
|
||||
imageData.data[offset] = palette[paletteOffset] ?? 0;
|
||||
imageData.data[offset + 1] = palette[paletteOffset + 1] ?? 0;
|
||||
imageData.data[offset + 2] = palette[paletteOffset + 2] ?? 0;
|
||||
imageData.data[offset + 3] = palette[paletteOffset + 3] ?? 0;
|
||||
}
|
||||
colorContext.putImageData(imageData, 0, 0);
|
||||
const scale = Math.min(width / mask.width, height / mask.height);
|
||||
const drawWidth = mask.width * scale;
|
||||
const drawHeight = mask.height * scale;
|
||||
context.imageSmoothingEnabled = false;
|
||||
context.drawImage(
|
||||
colorCanvas,
|
||||
(width - drawWidth) / 2,
|
||||
(height - drawHeight) / 2,
|
||||
drawWidth,
|
||||
drawHeight,
|
||||
);
|
||||
}
|
||||
|
||||
export function RecordedEvidenceSemanticMaskOverlay({
|
||||
src,
|
||||
prefetchSrcs = [],
|
||||
imageWidth,
|
||||
imageHeight,
|
||||
classes,
|
||||
palette,
|
||||
opacity = 0.46,
|
||||
ariaLabel,
|
||||
}: RecordedEvidenceSemanticOverlay & {
|
||||
imageWidth: number;
|
||||
imageHeight: number;
|
||||
}) {
|
||||
const canvasRef = useRef<HTMLCanvasElement | null>(null);
|
||||
const rendererRef = useRef<SemanticMaskRenderer | null | undefined>(undefined);
|
||||
const lastReadyMaskRef = useRef<{ series: string; mask: DecodedSemanticMask } | null>(null);
|
||||
const [rendererMode, setRendererMode] = useState<"webgl" | "2d">("webgl");
|
||||
const [mask, setMask] = useState<DecodedSemanticMask | null>(null);
|
||||
const [failure, setFailure] = useState<string | null>(null);
|
||||
const expectedKey = semanticMaskKey(src, imageWidth, imageHeight);
|
||||
const prefetchSignature = prefetchSrcs.join("\n");
|
||||
const series = src.slice(0, Math.max(src.lastIndexOf("/"), 0));
|
||||
if (lastReadyMaskRef.current?.series !== series) lastReadyMaskRef.current = null;
|
||||
const exactMask = failure
|
||||
? null
|
||||
: mask?.key === expectedKey
|
||||
? mask
|
||||
: decodedMaskCache.get(expectedKey) ?? null;
|
||||
if (exactMask) lastReadyMaskRef.current = { series, mask: exactMask };
|
||||
const renderMask = exactMask ?? lastReadyMaskRef.current?.mask ?? null;
|
||||
|
||||
useEffect(() => {
|
||||
setFailure(null);
|
||||
const subscription = subscribeToSemanticMask(src, imageWidth, imageHeight);
|
||||
let current = true;
|
||||
void subscription.promise.then((decoded) => {
|
||||
if (!current || decoded.key !== expectedKey) return;
|
||||
setMask(decoded);
|
||||
}).catch((error: unknown) => {
|
||||
if (!current || (error instanceof DOMException && error.name === "AbortError")) return;
|
||||
setFailure("Semantic mask не прошла проверку или декодирование.");
|
||||
});
|
||||
return () => {
|
||||
current = false;
|
||||
subscription.release();
|
||||
};
|
||||
}, [expectedKey, imageHeight, imageWidth, src]);
|
||||
|
||||
useEffect(() => {
|
||||
const subscriptions = prefetchSignature
|
||||
.split("\n")
|
||||
.filter((candidate) => candidate && candidate !== src)
|
||||
.slice(0, MASK_PREFETCH_LIMIT)
|
||||
.map((candidate) => subscribeToSemanticMask(candidate, imageWidth, imageHeight));
|
||||
for (const subscription of subscriptions) {
|
||||
void subscription.promise
|
||||
.catch(() => undefined)
|
||||
.finally(subscription.release);
|
||||
}
|
||||
}, [imageHeight, imageWidth, prefetchSignature, src]);
|
||||
|
||||
useEffect(() => () => {
|
||||
rendererRef.current?.dispose();
|
||||
rendererRef.current = undefined;
|
||||
}, [rendererMode]);
|
||||
|
||||
useEffect(() => {
|
||||
const canvas = canvasRef.current;
|
||||
if (!canvas || rendererMode !== "webgl") return;
|
||||
const onContextLost = (event: Event) => {
|
||||
event.preventDefault();
|
||||
setRendererMode("2d");
|
||||
};
|
||||
canvas.addEventListener("webglcontextlost", onContextLost);
|
||||
return () => canvas.removeEventListener("webglcontextlost", onContextLost);
|
||||
}, [rendererMode]);
|
||||
|
||||
useEffect(() => {
|
||||
const canvas = canvasRef.current;
|
||||
const host = canvas?.parentElement;
|
||||
if (!canvas || !host) return;
|
||||
const render = () => {
|
||||
const width = Math.max(host.clientWidth, 1);
|
||||
const height = Math.max(host.clientHeight, 1);
|
||||
const pixelRatio = Math.min(window.devicePixelRatio, 1.5);
|
||||
if (rendererMode === "webgl" && rendererRef.current === undefined) {
|
||||
rendererRef.current = createSemanticMaskRenderer(canvas);
|
||||
if (!rendererRef.current) {
|
||||
setRendererMode("2d");
|
||||
return;
|
||||
}
|
||||
}
|
||||
const renderer = rendererMode === "webgl" ? rendererRef.current : null;
|
||||
if (!renderMask) {
|
||||
if (renderer) {
|
||||
renderer.clear(width, height, pixelRatio);
|
||||
} else {
|
||||
canvas.width = Math.max(1, Math.round(width * pixelRatio));
|
||||
canvas.height = Math.max(1, Math.round(height * pixelRatio));
|
||||
canvas.style.width = `${width}px`;
|
||||
canvas.style.height = `${height}px`;
|
||||
}
|
||||
return;
|
||||
}
|
||||
const resolvedPalette = semanticPalettePixels(host, classes, palette, opacity);
|
||||
if (renderer) {
|
||||
try {
|
||||
renderer.render(renderMask, resolvedPalette, width, height, pixelRatio);
|
||||
} catch {
|
||||
setRendererMode("2d");
|
||||
}
|
||||
} else {
|
||||
renderSemanticMask2d(
|
||||
canvas,
|
||||
renderMask,
|
||||
resolvedPalette,
|
||||
width,
|
||||
height,
|
||||
pixelRatio,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const observer = new ResizeObserver(render);
|
||||
observer.observe(host);
|
||||
render();
|
||||
return () => observer.disconnect();
|
||||
}, [classes, expectedKey, imageHeight, imageWidth, opacity, palette, rendererMode, renderMask]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<canvas
|
||||
key={rendererMode}
|
||||
ref={canvasRef}
|
||||
className="recorded-evidence-semantic-mask-overlay"
|
||||
role="img"
|
||||
aria-label={ariaLabel}
|
||||
aria-busy={!failure && !exactMask}
|
||||
data-state={failure ? "error" : exactMask ? "ready" : renderMask ? "stale" : "loading"}
|
||||
data-renderer={rendererMode}
|
||||
style={{ zIndex: 1 }}
|
||||
/>
|
||||
{failure ? (
|
||||
<div className="recorded-evidence-semantic-mask-overlay__error" role="alert">
|
||||
{failure}
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import {
|
||||
RecordedFmp4Player,
|
||||
type RecordedObservationPlayback,
|
||||
} from "../RecordedFmp4Player";
|
||||
import type { ObservationSourceDescriptor } from "../../core/runtime/contracts";
|
||||
import type { RecordedCameraAdmissionState } from "../../core/observation/recordedSessionAdmission";
|
||||
import {
|
||||
RecordedEvidenceBoxOverlay,
|
||||
type RecordedEvidenceBox,
|
||||
type RecordedEvidenceBoxTone,
|
||||
} from "./RecordedEvidenceBoxOverlay";
|
||||
import {
|
||||
RecordedEvidenceSemanticMaskOverlay,
|
||||
type RecordedEvidenceSemanticOverlay,
|
||||
} from "./RecordedEvidenceSemanticMaskOverlay";
|
||||
import {
|
||||
RecordedEvidencePointCloudOverlay,
|
||||
type RecordedEvidencePointCloudOverlayData,
|
||||
} from "./RecordedEvidencePointCloudOverlay";
|
||||
|
||||
export type { RecordedEvidenceBox, RecordedEvidenceBoxTone };
|
||||
|
||||
export function RecordedEvidenceVideoScene({
|
||||
source,
|
||||
playback,
|
||||
imageWidth,
|
||||
imageHeight,
|
||||
boxes,
|
||||
overlaySeconds,
|
||||
semanticOverlay,
|
||||
pointCloudOverlay,
|
||||
ariaLabel,
|
||||
interactive = true,
|
||||
segmentSequence,
|
||||
segmentCount,
|
||||
onPlaybackChange,
|
||||
onPlayingRejected,
|
||||
onAdmissionChange,
|
||||
playbackAuthority = "media",
|
||||
playbackTransport = "segmented",
|
||||
recoverTimestampStalls = false,
|
||||
}: {
|
||||
source: ObservationSourceDescriptor;
|
||||
playback: RecordedObservationPlayback;
|
||||
imageWidth: number;
|
||||
imageHeight: number;
|
||||
boxes: readonly RecordedEvidenceBox[];
|
||||
overlaySeconds?: number;
|
||||
semanticOverlay?: RecordedEvidenceSemanticOverlay;
|
||||
pointCloudOverlay?: RecordedEvidencePointCloudOverlayData;
|
||||
ariaLabel: string;
|
||||
interactive?: boolean;
|
||||
segmentSequence?: number;
|
||||
segmentCount?: number;
|
||||
onPlaybackChange?: (playback: RecordedObservationPlayback) => void;
|
||||
onPlayingRejected?: () => void;
|
||||
onAdmissionChange?: (state: RecordedCameraAdmissionState) => void;
|
||||
playbackAuthority?: "media" | "host";
|
||||
playbackTransport?: "segmented" | "epoch-stream";
|
||||
recoverTimestampStalls?: boolean;
|
||||
}) {
|
||||
const generation = source.delivery?.kind === "recorded-fmp4-manifest"
|
||||
? source.delivery.manifestGenerationSha256
|
||||
: "invalid";
|
||||
const [admissionPhase, setAdmissionPhase] = useState<RecordedCameraAdmissionState["phase"]>(
|
||||
"loading",
|
||||
);
|
||||
const [presentedSeconds, setPresentedSeconds] = useState<number | null>(null);
|
||||
useEffect(() => {
|
||||
setAdmissionPhase("loading");
|
||||
setPresentedSeconds(null);
|
||||
}, [generation, source.id]);
|
||||
const handleAdmissionChange = (next: RecordedCameraAdmissionState) => {
|
||||
setAdmissionPhase(next.phase);
|
||||
onAdmissionChange?.(next);
|
||||
};
|
||||
const sourceReady = admissionPhase === "ready";
|
||||
const overlaysPresented = sourceReady
|
||||
&& presentedSeconds !== null
|
||||
&& Math.abs(presentedSeconds - playback.currentSeconds) <= 0.25
|
||||
&& (overlaySeconds === undefined || Math.abs(presentedSeconds - overlaySeconds) <= 0.25);
|
||||
const handlePlaybackChange = (next: RecordedObservationPlayback) => {
|
||||
setPresentedSeconds(next.currentSeconds);
|
||||
// During a paused operator seek the existing media element can emit its old
|
||||
// timestamp while the requested MSE window is being rebuilt. That stale
|
||||
// callback must not undo the host target before the decoder reaches it.
|
||||
if (!playback.playing && Math.abs(next.currentSeconds - playback.currentSeconds) > 0.35) {
|
||||
return;
|
||||
}
|
||||
onPlaybackChange?.(next);
|
||||
};
|
||||
return (
|
||||
<div className="recorded-evidence-video-scene">
|
||||
<RecordedFmp4Player
|
||||
source={source}
|
||||
playback={playback}
|
||||
interactive={interactive}
|
||||
prepare
|
||||
segmentSequence={segmentSequence}
|
||||
segmentCount={segmentCount}
|
||||
onPlaybackChange={handlePlaybackChange}
|
||||
onPlayingRejected={onPlayingRejected}
|
||||
onAdmissionChange={handleAdmissionChange}
|
||||
playbackAuthority={playbackAuthority}
|
||||
playbackTransport={playbackTransport}
|
||||
recoverTimestampStalls={recoverTimestampStalls}
|
||||
/>
|
||||
{overlaysPresented && semanticOverlay ? (
|
||||
<RecordedEvidenceSemanticMaskOverlay
|
||||
{...semanticOverlay}
|
||||
imageWidth={imageWidth}
|
||||
imageHeight={imageHeight}
|
||||
/>
|
||||
) : null}
|
||||
{overlaysPresented && pointCloudOverlay ? (
|
||||
<RecordedEvidencePointCloudOverlay
|
||||
imageWidth={imageWidth}
|
||||
imageHeight={imageHeight}
|
||||
overlay={pointCloudOverlay}
|
||||
/>
|
||||
) : null}
|
||||
{overlaysPresented ? (
|
||||
<RecordedEvidenceBoxOverlay
|
||||
imageWidth={imageWidth}
|
||||
imageHeight={imageHeight}
|
||||
boxes={boxes}
|
||||
ariaLabel={ariaLabel}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
import {
|
||||
fetchCanonicalRecordedLabSpatialFrame,
|
||||
type CanonicalRecordedLabSpatialFrame,
|
||||
} from "../../core/laboratory/canonicalRecordedLabSpatial";
|
||||
|
||||
const FRAME_CACHE_LIMIT = 12;
|
||||
|
||||
/**
|
||||
* Shared latest-request-wins scheduler for recorded LAB spatial evidence.
|
||||
*
|
||||
* A feature supplies only the sealed session identity and host-clock time.
|
||||
* Cache ownership, identity fencing and stale-response suppression remain in
|
||||
* the canonical instrument instead of being reimplemented per experiment.
|
||||
*/
|
||||
export function useCanonicalRecordedLabSpatialFrame({
|
||||
sessionId,
|
||||
generationSha256,
|
||||
targetTimeNs,
|
||||
}: {
|
||||
sessionId: string;
|
||||
generationSha256: string | null;
|
||||
targetTimeNs: number;
|
||||
}) {
|
||||
const [frame, setFrame] = useState<CanonicalRecordedLabSpatialFrame | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const desiredRef = useRef<number | null>(null);
|
||||
const runningRef = useRef(false);
|
||||
const mountedRef = useRef(true);
|
||||
const identityRef = useRef("");
|
||||
const cacheRef = useRef(new Map<number, CanonicalRecordedLabSpatialFrame>());
|
||||
const pumpRef = useRef<() => void>(() => undefined);
|
||||
const identity = `${sessionId}:${generationSha256 ?? "unavailable"}`;
|
||||
identityRef.current = identity;
|
||||
|
||||
pumpRef.current = () => {
|
||||
if (runningRef.current || desiredRef.current === null || !generationSha256) return;
|
||||
runningRef.current = true;
|
||||
const requestIdentity = identity;
|
||||
let settledTimeNs: number | null = null;
|
||||
void (async () => {
|
||||
while (
|
||||
mountedRef.current
|
||||
&& identityRef.current === requestIdentity
|
||||
&& desiredRef.current !== null
|
||||
) {
|
||||
const requestedTimeNs = desiredRef.current;
|
||||
const cached = cacheRef.current.get(requestedTimeNs);
|
||||
try {
|
||||
const next = cached ?? await fetchCanonicalRecordedLabSpatialFrame(
|
||||
sessionId,
|
||||
generationSha256,
|
||||
requestedTimeNs,
|
||||
);
|
||||
if (!mountedRef.current || identityRef.current !== requestIdentity) break;
|
||||
if (!cached) {
|
||||
cacheRef.current.set(requestedTimeNs, next);
|
||||
while (cacheRef.current.size > FRAME_CACHE_LIMIT) {
|
||||
const oldest = cacheRef.current.keys().next().value as number | undefined;
|
||||
if (oldest === undefined) break;
|
||||
cacheRef.current.delete(oldest);
|
||||
}
|
||||
}
|
||||
setFrame(next);
|
||||
setError(null);
|
||||
} catch (caught: unknown) {
|
||||
if (!mountedRef.current || identityRef.current !== requestIdentity) break;
|
||||
setError(caught instanceof Error
|
||||
? caught.message
|
||||
: "Spatial-слои записанной LAB недоступны.");
|
||||
}
|
||||
settledTimeNs = requestedTimeNs;
|
||||
if (desiredRef.current === requestedTimeNs) break;
|
||||
}
|
||||
})().finally(() => {
|
||||
runningRef.current = false;
|
||||
if (
|
||||
mountedRef.current
|
||||
&& desiredRef.current !== null
|
||||
&& (identityRef.current !== requestIdentity || desiredRef.current !== settledTimeNs)
|
||||
) {
|
||||
pumpRef.current();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
mountedRef.current = true;
|
||||
return () => {
|
||||
mountedRef.current = false;
|
||||
desiredRef.current = null;
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
cacheRef.current.clear();
|
||||
desiredRef.current = null;
|
||||
setFrame(null);
|
||||
setError(null);
|
||||
}, [identity]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!generationSha256) return;
|
||||
desiredRef.current = targetTimeNs;
|
||||
const cached = cacheRef.current.get(targetTimeNs);
|
||||
if (cached) {
|
||||
setFrame(cached);
|
||||
setError(null);
|
||||
return;
|
||||
}
|
||||
pumpRef.current();
|
||||
}, [generationSha256, identity, targetTimeNs]);
|
||||
|
||||
return { frame, error, loading: Boolean(generationSha256) && !frame && !error };
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { useState, type ReactNode } from "react";
|
||||
import { Button, Icon } from "@nodedc/ui-react";
|
||||
|
||||
import type {
|
||||
LaboratoryAnnotationAction,
|
||||
LaboratoryViewAction,
|
||||
} from "../../workspaces/contracts";
|
||||
|
||||
export function useLaboratoryAnnotationHeader(): {
|
||||
control: ReactNode;
|
||||
setAction: (action: LaboratoryAnnotationAction | null) => void;
|
||||
setViewAction: (action: LaboratoryViewAction | null) => void;
|
||||
} {
|
||||
const [action, setAction] = useState<LaboratoryAnnotationAction | null>(null);
|
||||
const [viewAction, setViewAction] = useState<LaboratoryViewAction | null>(null);
|
||||
return {
|
||||
setAction,
|
||||
setViewAction,
|
||||
control: action || viewAction ? (
|
||||
<div className="laboratory-header-tools">
|
||||
{viewAction ? (
|
||||
<Button shape="pill" variant="secondary" onClick={viewAction.onClick}>
|
||||
{viewAction.label}
|
||||
</Button>
|
||||
) : null}
|
||||
{action ? (
|
||||
<Button
|
||||
size="compact"
|
||||
shape="pill"
|
||||
variant="accent"
|
||||
icon={<Icon name="edit" size={16} />}
|
||||
disabled={action.disabled}
|
||||
onClick={action.onClick}
|
||||
>
|
||||
{action.label}
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
) : null,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
|
||||
import type { RecordedObservationPlayback } from "../RecordedFmp4Player";
|
||||
|
||||
export interface RecordedEvidencePlaybackRange {
|
||||
startSeconds: number;
|
||||
endSeconds: number;
|
||||
}
|
||||
|
||||
function validRange(
|
||||
range: RecordedEvidencePlaybackRange | null,
|
||||
): range is RecordedEvidencePlaybackRange {
|
||||
return Boolean(
|
||||
range
|
||||
&& Number.isFinite(range.startSeconds)
|
||||
&& Number.isFinite(range.endSeconds)
|
||||
&& range.endSeconds > range.startSeconds,
|
||||
);
|
||||
}
|
||||
|
||||
export function clampRecordedEvidenceSeconds(
|
||||
seconds: number,
|
||||
range: RecordedEvidencePlaybackRange,
|
||||
): number {
|
||||
return Math.min(range.endSeconds, Math.max(range.startSeconds, seconds));
|
||||
}
|
||||
|
||||
export function advanceRecordedEvidencePlayback(
|
||||
playback: RecordedObservationPlayback,
|
||||
elapsedSeconds: number,
|
||||
range: RecordedEvidencePlaybackRange,
|
||||
): RecordedObservationPlayback {
|
||||
if (!playback.playing || !Number.isFinite(elapsedSeconds) || elapsedSeconds <= 0) {
|
||||
return playback;
|
||||
}
|
||||
const next = playback.currentSeconds + elapsedSeconds * (playback.rate ?? 1);
|
||||
if (next >= range.endSeconds) {
|
||||
return { ...playback, currentSeconds: range.endSeconds, playing: false };
|
||||
}
|
||||
return { ...playback, currentSeconds: clampRecordedEvidenceSeconds(next, range) };
|
||||
}
|
||||
|
||||
export function synchronizeRecordedEvidencePlayback(
|
||||
current: RecordedObservationPlayback,
|
||||
observed: RecordedObservationPlayback,
|
||||
range: RecordedEvidencePlaybackRange,
|
||||
): RecordedObservationPlayback {
|
||||
if (!current.playing) {
|
||||
// A paused transport also covers every explicit operator seek. Native
|
||||
// callbacks from the previously displayed frame remain stale until the
|
||||
// controlled video effect has applied that target, so they cannot move the
|
||||
// shared LAB clock at all while the operator state is paused.
|
||||
return current;
|
||||
}
|
||||
const currentSeconds = clampRecordedEvidenceSeconds(observed.currentSeconds, range);
|
||||
return {
|
||||
...current,
|
||||
currentSeconds,
|
||||
// The transport button owns play/pause intent. A native frame callback may
|
||||
// still report the previous media state immediately after an operator
|
||||
// command, so it must not restart playback. The sealed end is the only
|
||||
// media-clock transition allowed to stop the shared LAB clock.
|
||||
playing: currentSeconds >= range.endSeconds ? false : current.playing,
|
||||
};
|
||||
}
|
||||
|
||||
export function useRecordedEvidencePlayback(
|
||||
range: RecordedEvidencePlaybackRange | null,
|
||||
{ clock = "animation" }: { clock?: "animation" | "external" } = {},
|
||||
) {
|
||||
const [playback, setPlayback] = useState<RecordedObservationPlayback>({
|
||||
currentSeconds: 0,
|
||||
playing: false,
|
||||
rate: 1,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!validRange(range)) return;
|
||||
setPlayback((current) => ({
|
||||
...current,
|
||||
currentSeconds: current.currentSeconds === 0
|
||||
? range.startSeconds
|
||||
: clampRecordedEvidenceSeconds(current.currentSeconds, range),
|
||||
}));
|
||||
}, [range]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!validRange(range) || !playback.playing || clock === "external") return;
|
||||
let animationFrame = 0;
|
||||
let previous = performance.now();
|
||||
const tick = (now: number) => {
|
||||
const elapsed = Math.max(0, now - previous);
|
||||
if (elapsed >= 32) {
|
||||
previous = now;
|
||||
setPlayback((current) => {
|
||||
if (!current.playing) return current;
|
||||
return advanceRecordedEvidencePlayback(current, elapsed / 1_000, range);
|
||||
});
|
||||
}
|
||||
animationFrame = window.requestAnimationFrame(tick);
|
||||
};
|
||||
animationFrame = window.requestAnimationFrame(tick);
|
||||
return () => window.cancelAnimationFrame(animationFrame);
|
||||
}, [clock, playback.playing, range]);
|
||||
|
||||
const seek = useCallback((seconds: number, pause = true) => {
|
||||
if (!validRange(range)) return;
|
||||
setPlayback((current) => ({
|
||||
...current,
|
||||
currentSeconds: clampRecordedEvidenceSeconds(seconds, range),
|
||||
playing: pause ? false : current.playing,
|
||||
}));
|
||||
}, [range]);
|
||||
|
||||
const setPlaying = useCallback((playing: boolean) => {
|
||||
if (!validRange(range)) return;
|
||||
setPlayback((current) => ({
|
||||
...current,
|
||||
currentSeconds: playing && current.currentSeconds >= range.endSeconds
|
||||
? range.startSeconds
|
||||
: current.currentSeconds,
|
||||
playing,
|
||||
}));
|
||||
}, [range]);
|
||||
|
||||
const setRate = useCallback((rate: number) => {
|
||||
if (![0.5, 1, 2].includes(rate)) return;
|
||||
setPlayback((current) => ({ ...current, rate }));
|
||||
}, []);
|
||||
|
||||
const synchronize = useCallback((next: RecordedObservationPlayback) => {
|
||||
if (!validRange(range) || !Number.isFinite(next.currentSeconds)) return;
|
||||
// Animation-clock mode is retained only for non-media diagnostics. A
|
||||
// recorded LAB with video uses the external media clock so spatial and
|
||||
// overlays never advance past the frame the decoder actually presented.
|
||||
if (clock === "animation") return;
|
||||
setPlayback((current) => synchronizeRecordedEvidencePlayback(current, next, range));
|
||||
}, [clock, range]);
|
||||
|
||||
return useMemo(() => ({
|
||||
playback,
|
||||
seek,
|
||||
setPlaying,
|
||||
setRate,
|
||||
synchronize,
|
||||
}), [playback, seek, setPlaying, setRate, synchronize]);
|
||||
}
|
||||
@@ -0,0 +1,381 @@
|
||||
import {
|
||||
Button,
|
||||
Checker,
|
||||
ColorField,
|
||||
ControlRow,
|
||||
InspectorSelectField,
|
||||
RangeControl,
|
||||
StatusBadge,
|
||||
} from "@nodedc/ui-react";
|
||||
|
||||
import type {
|
||||
MapGhostPinLabelMode,
|
||||
MapViewDocument,
|
||||
} from "../../core/map/mapView";
|
||||
|
||||
export function GhostPinSettingsPanel({
|
||||
draft,
|
||||
updateDraft,
|
||||
onGenerate,
|
||||
onClear,
|
||||
}: {
|
||||
draft: MapViewDocument;
|
||||
updateDraft: (
|
||||
update: (current: MapViewDocument) => MapViewDocument,
|
||||
) => void;
|
||||
onGenerate: () => void;
|
||||
onClear: () => void;
|
||||
}) {
|
||||
const configuration = draft.view.ghostPins;
|
||||
const profile = configuration.presentation;
|
||||
return (
|
||||
<div className="mission-map__inspector-controls">
|
||||
<ControlRow label="Контур">
|
||||
<StatusBadge tone="warning">Временная симуляция</StatusBadge>
|
||||
</ControlRow>
|
||||
<RangeControl
|
||||
label="Количество пинов"
|
||||
value={configuration.count}
|
||||
min={1}
|
||||
max={100}
|
||||
step={1}
|
||||
formatValue={(value) => `${value}`}
|
||||
onChange={(count) => updateDraft((current) => {
|
||||
current.view.ghostPins.count = count;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<RangeControl
|
||||
label="Радиус от центра view"
|
||||
value={configuration.radiusMeters}
|
||||
min={100}
|
||||
max={100_000}
|
||||
step={100}
|
||||
formatValue={(value) => value >= 1_000
|
||||
? `${(value / 1_000).toFixed(value % 1_000 === 0 ? 0 : 1)} км`
|
||||
: `${value} м`}
|
||||
onChange={(radiusMeters) => updateDraft((current) => {
|
||||
current.view.ghostPins.radiusMeters = radiusMeters;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<RangeControl
|
||||
label="Максимальная скорость"
|
||||
value={configuration.maximumSpeedMetersPerSecond}
|
||||
min={1}
|
||||
max={100}
|
||||
step={1}
|
||||
formatValue={(value) => `${value} м/с`}
|
||||
onChange={(maximumSpeedMetersPerSecond) => updateDraft((current) => {
|
||||
current.view.ghostPins.maximumSpeedMetersPerSecond =
|
||||
maximumSpeedMetersPerSecond;
|
||||
current.view.ghostPins.positions =
|
||||
current.view.ghostPins.positions.map((pin) => ({
|
||||
...pin,
|
||||
speedMetersPerSecond: Math.min(
|
||||
pin.speedMetersPerSecond,
|
||||
maximumSpeedMetersPerSecond,
|
||||
),
|
||||
}));
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<Checker
|
||||
label="Симуляция движения"
|
||||
checked={configuration.simulationEnabled}
|
||||
onChange={(simulationEnabled) => updateDraft((current) => {
|
||||
current.view.ghostPins.simulationEnabled = simulationEnabled;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<ControlRow label="Позиции">
|
||||
<StatusBadge tone={configuration.positions.length ? "success" : "neutral"}>
|
||||
{configuration.positions.length
|
||||
? `${configuration.positions.length} на карте`
|
||||
: "Не созданы"}
|
||||
</StatusBadge>
|
||||
</ControlRow>
|
||||
<Button variant="secondary" size="compact" onClick={onGenerate}>
|
||||
Разместить в текущем view
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="compact"
|
||||
disabled={configuration.positions.length === 0}
|
||||
onClick={onClear}
|
||||
>
|
||||
Очистить позиции на карте
|
||||
</Button>
|
||||
|
||||
<Checker
|
||||
label="Показывать Ghost Pin"
|
||||
checked={draft.view.layerVisibility.targets}
|
||||
onChange={(checked) => updateDraft((current) => {
|
||||
current.view.layerVisibility.targets = checked;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<ControlRow label="Цвет пина">
|
||||
<ColorField
|
||||
label="Цвет Ghost Pin"
|
||||
value={profile.color}
|
||||
onChange={(color) => updateDraft((current) => {
|
||||
current.view.ghostPins.presentation.color = color;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
</ControlRow>
|
||||
<RangeControl
|
||||
label="Прозрачность пина"
|
||||
value={Math.round(profile.opacity * 100)}
|
||||
min={0}
|
||||
max={100}
|
||||
step={1}
|
||||
formatValue={(value) => `${value}%`}
|
||||
onChange={(value) => updateDraft((current) => {
|
||||
current.view.ghostPins.presentation.opacity = value / 100;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<RangeControl
|
||||
label="Высота таргета"
|
||||
value={profile.stemHeightMeters}
|
||||
min={100}
|
||||
max={10_000}
|
||||
step={50}
|
||||
formatValue={(value) => `${value} м`}
|
||||
onChange={(stemHeightMeters) => updateDraft((current) => {
|
||||
current.view.ghostPins.presentation.stemHeightMeters = stemHeightMeters;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<RangeControl
|
||||
label="Размер головки"
|
||||
value={profile.headSizePx}
|
||||
min={1}
|
||||
max={32}
|
||||
step={1}
|
||||
formatValue={(value) => `${value} px`}
|
||||
onChange={(headSizePx) => updateDraft((current) => {
|
||||
current.view.ghostPins.presentation.headSizePx = headSizePx;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<RangeControl
|
||||
label="Толщина стержня"
|
||||
value={profile.stemWidthPx}
|
||||
min={0.25}
|
||||
max={12}
|
||||
step={0.25}
|
||||
formatValue={(value) => `${value} px`}
|
||||
onChange={(stemWidthPx) => updateDraft((current) => {
|
||||
current.view.ghostPins.presentation.stemWidthPx = stemWidthPx;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<ControlRow label="Цвет обводки">
|
||||
<ColorField
|
||||
label="Цвет обводки головки"
|
||||
value={profile.outlineColor}
|
||||
onChange={(outlineColor) => updateDraft((current) => {
|
||||
current.view.ghostPins.presentation.outlineColor = outlineColor;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
</ControlRow>
|
||||
<RangeControl
|
||||
label="Прозрачность обводки"
|
||||
value={Math.round(profile.outlineOpacity * 100)}
|
||||
min={0}
|
||||
max={100}
|
||||
step={1}
|
||||
formatValue={(value) => `${value}%`}
|
||||
onChange={(value) => updateDraft((current) => {
|
||||
current.view.ghostPins.presentation.outlineOpacity = value / 100;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<RangeControl
|
||||
label="Толщина обводки"
|
||||
value={profile.outlineWidthPx}
|
||||
min={0}
|
||||
max={8}
|
||||
step={0.5}
|
||||
formatValue={(value) => `${value} px`}
|
||||
onChange={(outlineWidthPx) => updateDraft((current) => {
|
||||
current.view.ghostPins.presentation.outlineWidthPx = outlineWidthPx;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<InspectorSelectField<MapGhostPinLabelMode>
|
||||
label="Подпись"
|
||||
value={profile.labelMode}
|
||||
options={[
|
||||
{
|
||||
value: "attributes",
|
||||
label: "Имя",
|
||||
description: "Случайное числовое имя Ghost Pin",
|
||||
},
|
||||
{
|
||||
value: "subject_id",
|
||||
label: "ID",
|
||||
description: "Стабильный синтетический идентификатор",
|
||||
},
|
||||
{
|
||||
value: "none",
|
||||
label: "Нет",
|
||||
description: "Не показывать плашку",
|
||||
},
|
||||
]}
|
||||
onChange={(labelMode) => updateDraft((current) => {
|
||||
current.view.ghostPins.presentation.labelMode = labelMode;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<RangeControl
|
||||
label="Насыщенность шрифта"
|
||||
value={profile.labelFontWeight}
|
||||
min={400}
|
||||
max={700}
|
||||
step={100}
|
||||
formatValue={(value) => `${value}`}
|
||||
onChange={(labelFontWeight) => updateDraft((current) => {
|
||||
current.view.ghostPins.presentation.labelFontWeight = labelFontWeight;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<RangeControl
|
||||
label="Размер подписи"
|
||||
value={profile.labelSizePx}
|
||||
min={8}
|
||||
max={32}
|
||||
step={1}
|
||||
formatValue={(value) => `${value} px`}
|
||||
onChange={(labelSizePx) => updateDraft((current) => {
|
||||
current.view.ghostPins.presentation.labelSizePx = labelSizePx;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<ControlRow label="Цвет подписи">
|
||||
<ColorField
|
||||
label="Цвет текста подписи"
|
||||
value={profile.labelColor}
|
||||
onChange={(labelColor) => updateDraft((current) => {
|
||||
current.view.ghostPins.presentation.labelColor = labelColor;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
</ControlRow>
|
||||
<ControlRow label="Фон плашки">
|
||||
<ColorField
|
||||
label="Цвет фона подписи"
|
||||
value={profile.labelBackgroundColor}
|
||||
onChange={(labelBackgroundColor) => updateDraft((current) => {
|
||||
current.view.ghostPins.presentation.labelBackgroundColor =
|
||||
labelBackgroundColor;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
</ControlRow>
|
||||
<RangeControl
|
||||
label="Прозрачность плашки"
|
||||
value={Math.round(profile.labelBackgroundOpacity * 100)}
|
||||
min={0}
|
||||
max={100}
|
||||
step={1}
|
||||
formatValue={(value) => `${value}%`}
|
||||
onChange={(value) => updateDraft((current) => {
|
||||
current.view.ghostPins.presentation.labelBackgroundOpacity =
|
||||
value / 100;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<RangeControl
|
||||
label="Отступ плашки X"
|
||||
value={profile.labelPaddingX}
|
||||
min={0}
|
||||
max={32}
|
||||
step={1}
|
||||
formatValue={(value) => `${value} px`}
|
||||
onChange={(labelPaddingX) => updateDraft((current) => {
|
||||
current.view.ghostPins.presentation.labelPaddingX = labelPaddingX;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<RangeControl
|
||||
label="Отступ плашки Y"
|
||||
value={profile.labelPaddingY}
|
||||
min={0}
|
||||
max={32}
|
||||
step={1}
|
||||
formatValue={(value) => `${value} px`}
|
||||
onChange={(labelPaddingY) => updateDraft((current) => {
|
||||
current.view.ghostPins.presentation.labelPaddingY = labelPaddingY;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<RangeControl
|
||||
label="Смещение подписи X"
|
||||
value={profile.labelOffsetX}
|
||||
min={-100}
|
||||
max={100}
|
||||
step={1}
|
||||
formatValue={(value) => `${value} px`}
|
||||
onChange={(labelOffsetX) => updateDraft((current) => {
|
||||
current.view.ghostPins.presentation.labelOffsetX = labelOffsetX;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<RangeControl
|
||||
label="Смещение подписи Y"
|
||||
value={profile.labelOffsetY}
|
||||
min={-100}
|
||||
max={100}
|
||||
step={1}
|
||||
formatValue={(value) => `${value} px`}
|
||||
onChange={(labelOffsetY) => updateDraft((current) => {
|
||||
current.view.ghostPins.presentation.labelOffsetY = labelOffsetY;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<RangeControl
|
||||
label="Максимальная длина подписи"
|
||||
value={profile.labelMaxLength}
|
||||
min={1}
|
||||
max={64}
|
||||
step={1}
|
||||
formatValue={(value) => `${value}`}
|
||||
onChange={(labelMaxLength) => updateDraft((current) => {
|
||||
current.view.ghostPins.presentation.labelMaxLength = labelMaxLength;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<RangeControl
|
||||
label="Скрывать подпись выше"
|
||||
value={profile.labelHideCameraHeightMeters}
|
||||
min={1_000}
|
||||
max={500_000}
|
||||
step={1_000}
|
||||
formatValue={(value) => `${Math.round(value / 1_000)} км`}
|
||||
onChange={(labelHideCameraHeightMeters) => updateDraft((current) => {
|
||||
current.view.ghostPins.presentation.labelHideCameraHeightMeters =
|
||||
labelHideCameraHeightMeters;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<RangeControl
|
||||
label="Скрывать таргет выше"
|
||||
value={profile.targetHideCameraHeightMeters}
|
||||
min={1_000}
|
||||
max={500_000}
|
||||
step={1_000}
|
||||
formatValue={(value) => `${Math.round(value / 1_000)} км`}
|
||||
onChange={(targetHideCameraHeightMeters) => updateDraft((current) => {
|
||||
current.view.ghostPins.presentation.targetHideCameraHeightMeters =
|
||||
targetHideCameraHeightMeters;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,708 @@
|
||||
import {
|
||||
Checker,
|
||||
ColorField,
|
||||
ControlRow,
|
||||
Inspector,
|
||||
RangeControl,
|
||||
StatusBadge,
|
||||
} from "@nodedc/ui-react";
|
||||
|
||||
import type {
|
||||
MapInspectorSection,
|
||||
MapViewDocument,
|
||||
} from "../../core/map/mapView";
|
||||
import type { useMapGatewayHealth } from "../../core/map/useMapGatewayHealth";
|
||||
import { GhostPinSettingsPanel } from "./GhostPinSettingsPanel";
|
||||
|
||||
export const editableMapInspectorSections = new Set<MapInspectorSection>([
|
||||
"base-terrain",
|
||||
"atmosphere-light",
|
||||
"buildings",
|
||||
"ghost-pins",
|
||||
"grid-lod",
|
||||
"camera",
|
||||
"tile-cache",
|
||||
]);
|
||||
|
||||
export function MapSettingsInspector({
|
||||
draft,
|
||||
updateDraft,
|
||||
gatewayHealth,
|
||||
onGenerateGhostPins,
|
||||
onClearGhostPins,
|
||||
}: {
|
||||
draft: MapViewDocument;
|
||||
updateDraft: (
|
||||
update: (current: MapViewDocument) => MapViewDocument,
|
||||
) => void;
|
||||
gatewayHealth: ReturnType<typeof useMapGatewayHealth>;
|
||||
onGenerateGhostPins: () => void;
|
||||
onClearGhostPins: () => void;
|
||||
}) {
|
||||
return (
|
||||
<Inspector
|
||||
singleOpen
|
||||
sections={createInspectorSections(
|
||||
draft,
|
||||
updateDraft,
|
||||
gatewayHealth,
|
||||
onGenerateGhostPins,
|
||||
onClearGhostPins,
|
||||
)}
|
||||
openSections={draft.view.inspectorOpenSections.filter(
|
||||
(section) => editableMapInspectorSections.has(section),
|
||||
)}
|
||||
activeId={draft.view.inspectorOpenSections[0]}
|
||||
onOpenSectionsChange={(sections) => {
|
||||
updateDraft((current) => {
|
||||
current.view.inspectorOpenSections = sections.filter(
|
||||
(section): section is MapInspectorSection =>
|
||||
editableMapInspectorSections.has(section as MapInspectorSection),
|
||||
).slice(0, 1);
|
||||
return current;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function createInspectorSections(
|
||||
draft: MapViewDocument,
|
||||
updateDraft: (
|
||||
update: (current: MapViewDocument) => MapViewDocument,
|
||||
) => void,
|
||||
gatewayHealth: ReturnType<typeof useMapGatewayHealth>,
|
||||
onGenerateGhostPins: () => void,
|
||||
onClearGhostPins: () => void,
|
||||
) {
|
||||
const view = draft.view;
|
||||
const settings = view.visualSettings;
|
||||
return [
|
||||
{
|
||||
id: "base-terrain",
|
||||
label: "Подложка и terrain",
|
||||
description: "provider-neutral surface",
|
||||
content: (
|
||||
<div className="mission-map__inspector-controls">
|
||||
<ControlRow label="Подложка">
|
||||
<strong>Cesium World Imagery</strong>
|
||||
</ControlRow>
|
||||
<Checker
|
||||
label="Спутниковая подложка"
|
||||
checked={view.layerVisibility.imagery}
|
||||
onChange={(checked) => updateDraft((current) => {
|
||||
current.view.layerVisibility.imagery = checked;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<Checker
|
||||
label="Terrain"
|
||||
checked={view.layerVisibility.terrain}
|
||||
onChange={(checked) => updateDraft((current) => {
|
||||
current.view.layerVisibility.terrain = checked;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<RangeControl
|
||||
label="Вертикальное преувеличение рельефа"
|
||||
value={settings.terrainExaggeration * 100}
|
||||
min={25}
|
||||
max={300}
|
||||
step={1}
|
||||
formatValue={(value) => `${(value / 100).toFixed(2)}×`}
|
||||
onChange={(value) => updateDraft((current) => {
|
||||
current.view.visualSettings.terrainExaggeration = value / 100;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<Checker
|
||||
label="Монохромная поверхность"
|
||||
checked={settings.monochromeEnabled}
|
||||
onChange={(checked) => updateDraft((current) => {
|
||||
current.view.visualSettings.monochromeEnabled = checked;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<ControlRow label="Цвет монохрома">
|
||||
<ColorField
|
||||
label="Цвет монохромной поверхности"
|
||||
value={settings.monochromeColor}
|
||||
onChange={(value) => updateDraft((current) => {
|
||||
current.view.visualSettings.monochromeColor = value;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
</ControlRow>
|
||||
<RangeControl
|
||||
label="Яркость"
|
||||
value={settings.imageryBrightness}
|
||||
min={0}
|
||||
max={200}
|
||||
formatValue={(value) => `${value}%`}
|
||||
onChange={(value) => updateDraft((current) => {
|
||||
current.view.visualSettings.imageryBrightness = value;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<RangeControl
|
||||
label="Контраст"
|
||||
value={settings.imageryContrast}
|
||||
min={0}
|
||||
max={200}
|
||||
formatValue={(value) => `${value}%`}
|
||||
onChange={(value) => updateDraft((current) => {
|
||||
current.view.visualSettings.imageryContrast = value;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<RangeControl
|
||||
label="Насыщенность"
|
||||
value={settings.imagerySaturation}
|
||||
min={0}
|
||||
max={200}
|
||||
formatValue={(value) => `${value}%`}
|
||||
onChange={(value) => updateDraft((current) => {
|
||||
current.view.visualSettings.imagerySaturation = value;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<RangeControl
|
||||
label="Гамма"
|
||||
value={settings.imageryGamma}
|
||||
min={0}
|
||||
max={300}
|
||||
formatValue={(value) => `${value}%`}
|
||||
onChange={(value) => updateDraft((current) => {
|
||||
current.view.visualSettings.imageryGamma = value;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<RangeControl
|
||||
label="Оттенок"
|
||||
value={settings.imageryHue}
|
||||
min={-180}
|
||||
max={180}
|
||||
formatValue={(value) => `${value}°`}
|
||||
onChange={(value) => updateDraft((current) => {
|
||||
current.view.visualSettings.imageryHue = value;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<RangeControl
|
||||
label="Прозрачность imagery"
|
||||
value={settings.imageryAlpha}
|
||||
min={0}
|
||||
max={100}
|
||||
formatValue={(value) => `${value}%`}
|
||||
onChange={(value) => updateDraft((current) => {
|
||||
current.view.visualSettings.imageryAlpha = value;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<ControlRow label="Цвет планеты">
|
||||
<ColorField
|
||||
label="Цвет terrain без imagery"
|
||||
value={settings.globeColor}
|
||||
onChange={(value) => updateDraft((current) => {
|
||||
current.view.visualSettings.globeColor = value;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
</ControlRow>
|
||||
<ControlRow label="Фон сцены">
|
||||
<ColorField
|
||||
label="Цвет фона сцены"
|
||||
value={settings.backgroundColor}
|
||||
onChange={(value) => updateDraft((current) => {
|
||||
current.view.visualSettings.backgroundColor = value;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
</ControlRow>
|
||||
<RangeControl
|
||||
label="Высота карты"
|
||||
value={view.mapHeight}
|
||||
min={420}
|
||||
max={1080}
|
||||
step={1}
|
||||
formatValue={(value) => `${value} px`}
|
||||
onChange={(value) => updateDraft((current) => {
|
||||
current.view.mapHeight = value;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "atmosphere-light",
|
||||
label: "Атмосфера и освещение",
|
||||
description: "scene / color correction",
|
||||
content: (
|
||||
<div className="mission-map__inspector-controls">
|
||||
<Checker
|
||||
label="Показывать атмосферу"
|
||||
checked={settings.atmosphereEnabled}
|
||||
onChange={(checked) => updateDraft((current) => {
|
||||
current.view.visualSettings.atmosphereEnabled = checked;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<RangeControl
|
||||
label="Атмосфера: оттенок"
|
||||
value={settings.atmosphereHue}
|
||||
min={-100}
|
||||
max={100}
|
||||
formatValue={(value) => `${value}%`}
|
||||
onChange={(value) => updateDraft((current) => {
|
||||
current.view.visualSettings.atmosphereHue = value;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<RangeControl
|
||||
label="Атмосфера: насыщенность"
|
||||
value={settings.atmosphereSaturation}
|
||||
min={-100}
|
||||
max={100}
|
||||
formatValue={(value) => `${value}%`}
|
||||
onChange={(value) => updateDraft((current) => {
|
||||
current.view.visualSettings.atmosphereSaturation = value;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<RangeControl
|
||||
label="Атмосфера: яркость"
|
||||
value={settings.atmosphereBrightness}
|
||||
min={-100}
|
||||
max={100}
|
||||
formatValue={(value) => `${value}%`}
|
||||
onChange={(value) => updateDraft((current) => {
|
||||
current.view.visualSettings.atmosphereBrightness = value;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<Checker
|
||||
label="Туман"
|
||||
checked={settings.fogEnabled}
|
||||
onChange={(checked) => updateDraft((current) => {
|
||||
current.view.visualSettings.fogEnabled = checked;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<RangeControl
|
||||
label="Плотность тумана"
|
||||
value={settings.fogDensity}
|
||||
min={0}
|
||||
max={100}
|
||||
formatValue={(value) => `${(value / 10_000).toFixed(4)}`}
|
||||
onChange={(value) => updateDraft((current) => {
|
||||
current.view.visualSettings.fogDensity = value;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<Checker
|
||||
label="Солнечное освещение"
|
||||
checked={settings.sunEnabled}
|
||||
onChange={(checked) => updateDraft((current) => {
|
||||
current.view.visualSettings.sunEnabled = checked;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<RangeControl
|
||||
label="Час солнца"
|
||||
value={settings.sunHour}
|
||||
min={0}
|
||||
max={24}
|
||||
formatValue={(value) => `${value}:00 UTC`}
|
||||
onChange={(value) => updateDraft((current) => {
|
||||
current.view.visualSettings.sunHour = value;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<RangeControl
|
||||
label="Интенсивность света"
|
||||
value={settings.sunIntensity}
|
||||
min={0}
|
||||
max={200}
|
||||
formatValue={(value) => `${value}%`}
|
||||
onChange={(value) => updateDraft((current) => {
|
||||
current.view.visualSettings.sunIntensity = value;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<Checker
|
||||
label="Тени"
|
||||
checked={settings.shadowsEnabled}
|
||||
onChange={(checked) => updateDraft((current) => {
|
||||
current.view.visualSettings.shadowsEnabled = checked;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "buildings",
|
||||
label: "3D здания",
|
||||
description: "3D Tiles / detail",
|
||||
content: (
|
||||
<div className="mission-map__inspector-controls">
|
||||
<Checker
|
||||
label="Показывать 3D здания"
|
||||
checked={view.layerVisibility.buildings}
|
||||
onChange={(checked) => updateDraft((current) => {
|
||||
current.view.layerVisibility.buildings = checked;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<ControlRow label="Цвет">
|
||||
<ColorField
|
||||
label="Цвет зданий"
|
||||
value={settings.buildingsColor}
|
||||
onChange={(value) => updateDraft((current) => {
|
||||
current.view.visualSettings.buildingsColor = value;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
</ControlRow>
|
||||
<RangeControl
|
||||
label="Прозрачность"
|
||||
value={Math.round(settings.buildingsOpacity * 100)}
|
||||
min={0}
|
||||
max={100}
|
||||
formatValue={(value) => `${value}%`}
|
||||
onChange={(value) => updateDraft((current) => {
|
||||
current.view.visualSettings.buildingsOpacity = value / 100;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<RangeControl
|
||||
label="Детализация"
|
||||
value={settings.buildingsMaximumScreenSpaceError}
|
||||
min={4}
|
||||
max={32}
|
||||
formatValue={(value) => `SSE ${value}`}
|
||||
onChange={(value) => updateDraft((current) => {
|
||||
current.view.visualSettings.buildingsMaximumScreenSpaceError = value;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "ghost-pins",
|
||||
label: "Гост-пин",
|
||||
description: "временная симуляция",
|
||||
content: (
|
||||
<GhostPinSettingsPanel
|
||||
draft={draft}
|
||||
updateDraft={updateDraft}
|
||||
onGenerate={onGenerateGhostPins}
|
||||
onClear={onClearGhostPins}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "grid-lod",
|
||||
label: "Сетка и LOD",
|
||||
description: "first adapter control",
|
||||
content: (
|
||||
<div className="mission-map__inspector-controls">
|
||||
<Checker
|
||||
label="3D-сетка"
|
||||
checked={view.layerVisibility.grid}
|
||||
onChange={(checked) => updateDraft((current) => {
|
||||
current.view.layerVisibility.grid = checked;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<Checker
|
||||
label="LOD по высоте камеры"
|
||||
checked={settings.gridLodEnabled}
|
||||
onChange={(checked) => updateDraft((current) => {
|
||||
current.view.visualSettings.gridLodEnabled = checked;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<RangeControl
|
||||
label="Высота над поверхностью"
|
||||
value={settings.gridHeightMeters}
|
||||
min={0}
|
||||
max={1000}
|
||||
formatValue={(value) => `${value} м`}
|
||||
onChange={(value) => updateDraft((current) => {
|
||||
current.view.visualSettings.gridHeightMeters = value;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<RangeControl
|
||||
label="LOD 1: до высоты"
|
||||
value={settings.gridLod1MaxHeightKm}
|
||||
min={1}
|
||||
max={50}
|
||||
formatValue={(value) => `${value} км`}
|
||||
onChange={(value) => updateDraft((current) => {
|
||||
current.view.visualSettings.gridLod1MaxHeightKm = value;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<RangeControl
|
||||
label="LOD 1: шаг"
|
||||
value={settings.gridLod1StepKm}
|
||||
min={1}
|
||||
max={10}
|
||||
formatValue={(value) => `${value} км`}
|
||||
onChange={(value) => updateDraft((current) => {
|
||||
current.view.visualSettings.gridLod1StepKm = value;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<RangeControl
|
||||
label="LOD 2: до высоты"
|
||||
value={settings.gridLod2MaxHeightKm}
|
||||
min={10}
|
||||
max={200}
|
||||
formatValue={(value) => `${value} км`}
|
||||
onChange={(value) => updateDraft((current) => {
|
||||
current.view.visualSettings.gridLod2MaxHeightKm = value;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<RangeControl
|
||||
label="LOD 2: шаг"
|
||||
value={settings.gridLod2StepKm}
|
||||
min={1}
|
||||
max={25}
|
||||
formatValue={(value) => `${value} км`}
|
||||
onChange={(value) => updateDraft((current) => {
|
||||
current.view.visualSettings.gridLod2StepKm = value;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<RangeControl
|
||||
label="LOD 3: шаг"
|
||||
value={settings.gridLod3StepKm}
|
||||
min={5}
|
||||
max={100}
|
||||
formatValue={(value) => `${value} км`}
|
||||
onChange={(value) => updateDraft((current) => {
|
||||
current.view.visualSettings.gridLod3StepKm = value;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<RangeControl
|
||||
label="Радиус сетки"
|
||||
value={settings.gridRadiusKm}
|
||||
min={5}
|
||||
max={150}
|
||||
formatValue={(value) => `${value} км`}
|
||||
onChange={(value) => updateDraft((current) => {
|
||||
current.view.visualSettings.gridRadiusKm = value;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<ControlRow label="Цвет линий">
|
||||
<ColorField
|
||||
label="Цвет линий сетки"
|
||||
value={settings.gridColor}
|
||||
onChange={(value) => updateDraft((current) => {
|
||||
current.view.visualSettings.gridColor = value;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
</ControlRow>
|
||||
<RangeControl
|
||||
label="Толщина линий"
|
||||
value={settings.gridLineWidth}
|
||||
min={1}
|
||||
max={8}
|
||||
formatValue={(value) => `${value} px`}
|
||||
onChange={(value) => updateDraft((current) => {
|
||||
current.view.visualSettings.gridLineWidth = value;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<RangeControl
|
||||
label="Прозрачность сетки"
|
||||
value={settings.gridOpacity}
|
||||
min={0}
|
||||
max={100}
|
||||
formatValue={(value) => `${value}%`}
|
||||
onChange={(value) => updateDraft((current) => {
|
||||
current.view.visualSettings.gridOpacity = value;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<Checker
|
||||
label="Точки в пересечениях"
|
||||
checked={settings.gridDotsEnabled}
|
||||
onChange={(checked) => updateDraft((current) => {
|
||||
current.view.visualSettings.gridDotsEnabled = checked;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<RangeControl
|
||||
label="Размер точки"
|
||||
value={settings.gridDotsSize}
|
||||
min={2}
|
||||
max={28}
|
||||
formatValue={(value) => `${value} px`}
|
||||
onChange={(value) => updateDraft((current) => {
|
||||
current.view.visualSettings.gridDotsSize = value;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<ControlRow label="Цвет точек">
|
||||
<ColorField
|
||||
label="Цвет точек сетки"
|
||||
value={settings.gridDotsColor}
|
||||
onChange={(value) => updateDraft((current) => {
|
||||
current.view.visualSettings.gridDotsColor = value;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
</ControlRow>
|
||||
<RangeControl
|
||||
label="Прозрачность точек"
|
||||
value={settings.gridDotsOpacity}
|
||||
min={0}
|
||||
max={100}
|
||||
formatValue={(value) => `${value}%`}
|
||||
onChange={(value) => updateDraft((current) => {
|
||||
current.view.visualSettings.gridDotsOpacity = value;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "camera",
|
||||
label: "Анимация камеры",
|
||||
description: "geodesic spiral survey",
|
||||
content: (
|
||||
<div className="mission-map__inspector-controls">
|
||||
<Checker
|
||||
label="Разрешить анимацию камеры"
|
||||
checked={settings.cameraAnimationEnabled}
|
||||
onChange={(checked) => updateDraft((current) => {
|
||||
current.view.visualSettings.cameraAnimationEnabled = checked;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<ControlRow label="Сохранённая позиция">
|
||||
<StatusBadge tone={view.camera ? "success" : "neutral"}>
|
||||
{view.camera ? "Есть" : "Не задана"}
|
||||
</StatusBadge>
|
||||
</ControlRow>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "tile-cache",
|
||||
label: "TileCache",
|
||||
description: "Platform Map Gateway",
|
||||
content: (
|
||||
<div
|
||||
className="mission-map__inspector-controls"
|
||||
data-health-code={gatewayHealth.code ?? undefined}
|
||||
>
|
||||
<Checker
|
||||
label="Использовать общий TileCache"
|
||||
checked={view.cacheIntent.enabled}
|
||||
onChange={(checked) => updateDraft((current) => {
|
||||
current.view.cacheIntent.enabled = checked;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<Checker
|
||||
label="Не перезаписывать существующие тайлы"
|
||||
checked={view.cacheIntent.noOverwrite}
|
||||
disabled={!view.cacheIntent.enabled}
|
||||
onChange={(checked) => updateDraft((current) => {
|
||||
current.view.cacheIntent.noOverwrite = checked;
|
||||
return current;
|
||||
})}
|
||||
/>
|
||||
<CacheHealth health={gatewayHealth} />
|
||||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function CacheHealth({
|
||||
health,
|
||||
}: {
|
||||
health: ReturnType<typeof useMapGatewayHealth>;
|
||||
}) {
|
||||
if (health.state === "loading" || health.state === "idle") {
|
||||
return (
|
||||
<ControlRow label="Состояние">
|
||||
<StatusBadge tone="neutral">Проверяем</StatusBadge>
|
||||
</ControlRow>
|
||||
);
|
||||
}
|
||||
if (!health.snapshot) {
|
||||
return (
|
||||
<ControlRow label="Состояние">
|
||||
<StatusBadge tone="danger">Недоступен</StatusBadge>
|
||||
</ControlRow>
|
||||
);
|
||||
}
|
||||
const { cache } = health.snapshot;
|
||||
return (
|
||||
<dl className="mission-map__cache-health">
|
||||
<div>
|
||||
<dt>Хранилище</dt>
|
||||
<dd>
|
||||
<StatusBadge tone={cache.persistent ? "success" : "warning"}>
|
||||
{cache.persistent ? "Постоянное" : "Временное"}
|
||||
</StatusBadge>
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Объекты</dt>
|
||||
<dd>{cache.entries.toLocaleString("ru-RU")}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Объём</dt>
|
||||
<dd>
|
||||
{formatBytes(cache.bytes)}
|
||||
{cache.maxBytes === null ? "" : ` / ${formatBytes(cache.maxBytes)}`}
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Заполнение</dt>
|
||||
<dd>
|
||||
<StatusBadge
|
||||
tone={
|
||||
cache.atCapacity || health.state === "stale"
|
||||
? "warning"
|
||||
: "success"
|
||||
}
|
||||
>
|
||||
{cache.atCapacity
|
||||
? "Лимит"
|
||||
: health.state === "stale"
|
||||
? "Устарело"
|
||||
: "Норма"}
|
||||
</StatusBadge>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
);
|
||||
}
|
||||
|
||||
function formatBytes(value: number): string {
|
||||
if (value < 1024) return `${value} Б`;
|
||||
const units = ["КБ", "МБ", "ГБ", "ТБ"];
|
||||
let amount = value / 1024;
|
||||
let unit = units[0];
|
||||
for (let index = 1; index < units.length && amount >= 1024; index += 1) {
|
||||
amount /= 1024;
|
||||
unit = units[index];
|
||||
}
|
||||
return `${amount.toFixed(amount >= 10 ? 1 : 2)} ${unit}`;
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Button,
|
||||
FieldFrame,
|
||||
Select,
|
||||
StatusBadge,
|
||||
Window,
|
||||
WindowFooterActions,
|
||||
} from "@nodedc/ui-react";
|
||||
|
||||
import {
|
||||
aiModuleIdForSetup,
|
||||
fetchAIModuleCatalog,
|
||||
fetchAICompositionRuns,
|
||||
fetchAICompositionJobs,
|
||||
saveAIComposition,
|
||||
type AICompositionReceipt,
|
||||
type AICompositionRun,
|
||||
type AIGroupId,
|
||||
type AIModuleCatalog,
|
||||
} from "../../core/observatory/aiComposition";
|
||||
import type { ObservatoryRecordedJob } from "../../core/observatory/recordedJobs";
|
||||
|
||||
const groups: readonly { id: AIGroupId; label: string; description: string }[] = [
|
||||
{ id: "segmentation", label: "Сегментация", description: "Пиксельные маски сцены" },
|
||||
{ id: "detection", label: "Объекты", description: "Рамки и классы объектов" },
|
||||
{ id: "geometry", label: "Облако точек / TGS", description: "Опорная поверхность и карта проходимости" },
|
||||
{ id: "range", label: "Дистанция", description: "Расстояния до объектов" },
|
||||
{ id: "motion", label: "Движение", description: "Треки и динамика объектов" },
|
||||
{ id: "policy", label: "Политика", description: "Теневые правила интерпретации" },
|
||||
];
|
||||
|
||||
export function AIConfigurationWindow({
|
||||
open, sourceSessionId, sourceLabel, onClose, onCalculated,
|
||||
}: {
|
||||
readonly open: boolean;
|
||||
readonly sourceSessionId: string;
|
||||
readonly sourceLabel: string;
|
||||
readonly onClose: () => void;
|
||||
readonly onCalculated: (receipt: AICompositionReceipt) => void;
|
||||
}) {
|
||||
const [catalog, setCatalog] = useState<AIModuleCatalog | null>(null);
|
||||
const [existingJobs, setExistingJobs] = useState<readonly ObservatoryRecordedJob[]>([]);
|
||||
const [existingRuns, setExistingRuns] = useState<readonly AICompositionRun[]>([]);
|
||||
const [selected, setSelected] = useState<Partial<Record<AIGroupId, string>>>({});
|
||||
const [state, setState] = useState<"idle" | "loading" | "ready" | "saving" | "error">("idle");
|
||||
const [message, setMessage] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
const request = new AbortController();
|
||||
setState("loading");
|
||||
setMessage(null);
|
||||
void Promise.all([
|
||||
fetchAIModuleCatalog(request.signal),
|
||||
fetchAICompositionJobs(sourceSessionId, request.signal),
|
||||
fetchAICompositionRuns(sourceSessionId, request.signal),
|
||||
]).then(([value, jobs, runs]) => {
|
||||
if (request.signal.aborted) return;
|
||||
setCatalog(value);
|
||||
setExistingJobs(jobs);
|
||||
setExistingRuns(runs);
|
||||
setSelected({});
|
||||
setState("ready");
|
||||
}).catch((error: unknown) => {
|
||||
if (request.signal.aborted) return;
|
||||
setMessage(error instanceof Error ? error.message : "Каталог AI-модулей недоступен.");
|
||||
setState("error");
|
||||
});
|
||||
return () => request.abort();
|
||||
}, [open, sourceSessionId]);
|
||||
|
||||
const existingByModule = useMemo(() => {
|
||||
const result = new Map<string, ObservatoryRecordedJob>();
|
||||
for (const job of existingJobs) {
|
||||
if (job.state === "failed" || job.publication.state === "failed") continue;
|
||||
const moduleId = aiModuleIdForSetup(job.setupId);
|
||||
if (moduleId && !result.has(moduleId)) result.set(moduleId, job);
|
||||
}
|
||||
return result;
|
||||
}, [existingJobs]);
|
||||
|
||||
const selections = useMemo(() => groups.flatMap(({ id }) => {
|
||||
const moduleId = selected[id];
|
||||
const module = catalog?.groups.find((item) => item.group === id)?.modules.find(
|
||||
(item) => item.moduleId === moduleId,
|
||||
);
|
||||
return module ? [{ group: id, module }] : [];
|
||||
}), [catalog, selected]);
|
||||
|
||||
const selectedModuleIds = useMemo(
|
||||
() => selections.map(({ module }) => module.moduleId).sort(),
|
||||
[selections],
|
||||
);
|
||||
const matchingRun = useMemo(() => existingRuns.find((run) => (
|
||||
run.state !== "failed"
|
||||
&& [...run.moduleIds].sort().join("\0") === selectedModuleIds.join("\0")
|
||||
)), [existingRuns, selectedModuleIds]);
|
||||
const configurationAlreadyExists = selections.length > 0 && matchingRun !== undefined;
|
||||
const configurationIsRunning = matchingRun?.state === "running";
|
||||
|
||||
function selectModule(group: AIGroupId, moduleId: string): void {
|
||||
if (!catalog) return;
|
||||
setSelected((current) => {
|
||||
const next = { ...current, [group]: moduleId === "none" ? undefined : moduleId };
|
||||
const modules = catalog.groups.flatMap((item) => item.modules);
|
||||
const selectedIds = new Set(Object.values(next).filter((value): value is string => Boolean(value)));
|
||||
if (moduleId !== "none") {
|
||||
const module = modules.find((item) => item.moduleId === moduleId);
|
||||
for (const requirement of module?.requires ?? []) {
|
||||
if (requirement.startsWith("source.")) continue;
|
||||
if (modules.some((item) => selectedIds.has(item.moduleId) && item.provides.includes(requirement))) {
|
||||
continue;
|
||||
}
|
||||
const providers = modules.filter((item) => item.provides.includes(requirement));
|
||||
if (providers.length === 1) {
|
||||
const providerGroup = catalog.groups.find((item) => item.modules.some(
|
||||
(candidate) => candidate.moduleId === providers[0]?.moduleId,
|
||||
))?.group;
|
||||
if (providerGroup) {
|
||||
next[providerGroup] = providers[0]?.moduleId;
|
||||
selectedIds.add(providers[0]?.moduleId ?? "");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let changed = true;
|
||||
while (changed) {
|
||||
changed = false;
|
||||
const capabilities = new Set(modules.filter((item) => Object.values(next).includes(item.moduleId))
|
||||
.flatMap((item) => item.provides));
|
||||
for (const item of modules.filter((candidate) => Object.values(next).includes(candidate.moduleId))) {
|
||||
if (item.requires.some((requirement) => !requirement.startsWith("source.") && !capabilities.has(requirement))) {
|
||||
const dependentGroup = catalog.groups.find((candidate) => candidate.modules.some(
|
||||
(module) => module.moduleId === item.moduleId,
|
||||
))?.group;
|
||||
if (dependentGroup) {
|
||||
next[dependentGroup] = undefined;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return next;
|
||||
});
|
||||
setMessage(null);
|
||||
if (state === "error") setState("ready");
|
||||
}
|
||||
|
||||
async function calculate(): Promise<void> {
|
||||
if (selections.length === 0 || state === "saving") return;
|
||||
if (configurationAlreadyExists) {
|
||||
setMessage("Эта конфигурация уже рассчитана. Выберите другую конфигурацию.");
|
||||
return;
|
||||
}
|
||||
setState("saving");
|
||||
setMessage(null);
|
||||
try {
|
||||
const receipt = await saveAIComposition(sourceSessionId, selections);
|
||||
onCalculated(receipt);
|
||||
onClose();
|
||||
} catch (error) {
|
||||
setMessage(error instanceof Error ? error.message : "Не удалось запустить расчёт.");
|
||||
setState("error");
|
||||
}
|
||||
}
|
||||
|
||||
return <Window
|
||||
open={open}
|
||||
className="observatory-ai-config-window"
|
||||
title="Сконфигурировать AI-слой"
|
||||
subtitle={sourceLabel}
|
||||
size="lg"
|
||||
closeOnBackdrop={state !== "saving"}
|
||||
closeOnEscape={state !== "saving"}
|
||||
onClose={onClose}
|
||||
footer={<WindowFooterActions>
|
||||
{configurationAlreadyExists ? (
|
||||
<span className="observatory-ai-config__duplicate" role="status">
|
||||
{configurationIsRunning
|
||||
? "Текущая конфигурация уже рассчитывается."
|
||||
: "Текущая конфигурация уже рассчитана."}
|
||||
</span>
|
||||
) : null}
|
||||
<Button
|
||||
variant="primary"
|
||||
className={state === "saving" ? "observatory-ai-config__calculate--saving" : undefined}
|
||||
disabled={state !== "ready" || selections.length === 0 || configurationAlreadyExists}
|
||||
aria-busy={state === "saving"}
|
||||
onClick={() => void calculate()}
|
||||
>
|
||||
{state === "saving" ? "Отправляем на Worker…" : "Рассчитать"}
|
||||
</Button>
|
||||
</WindowFooterActions>}
|
||||
>
|
||||
<div className="observatory-ai-config" aria-busy={state === "loading" || state === "saving"}>
|
||||
<p className="observatory-ai-config__lead">
|
||||
Выберите модуль в каждом нужном слое. Зависимые блоки подключаются явно в этой конфигурации.
|
||||
</p>
|
||||
{state === "loading" ? <ActivityIndicator label="Загружаем AI-модули" /> : null}
|
||||
{catalog ? groups.map(({ id, label, description }) => {
|
||||
const modules = catalog.groups.find((item) => item.group === id)?.modules ?? [];
|
||||
const selectedModule = modules.find((item) => item.moduleId === selected[id]);
|
||||
const selectedExisting = selectedModule
|
||||
? existingByModule.get(selectedModule.moduleId)
|
||||
: undefined;
|
||||
const allCalculated = modules.length > 0 && modules.every(
|
||||
(module) => existingByModule.has(module.moduleId),
|
||||
);
|
||||
return <section className="observatory-ai-module-group" key={id}>
|
||||
<header>
|
||||
<div>
|
||||
<strong>{label}</strong>
|
||||
<span>{description}</span>
|
||||
</div>
|
||||
<StatusBadge tone={selectedModule ? "accent" : "neutral"}>
|
||||
{selectedExisting
|
||||
? selectedExisting.state === "succeeded" ? "Уже рассчитано" : "В расчёте"
|
||||
: selectedModule?.label ?? (allCalculated
|
||||
? "Результат доступен"
|
||||
: modules.length ? "Не используется" : "Не установлен")}
|
||||
</StatusBadge>
|
||||
</header>
|
||||
<div className="observatory-ai-module-group__body">
|
||||
<FieldFrame
|
||||
label={`Модуль: ${label}`}
|
||||
description={modules.length
|
||||
? allCalculated
|
||||
? "Готовый результат можно включить в новую конфигурацию без повторного расчёта."
|
||||
: id === "segmentation"
|
||||
? "DDRNet и EoMT — независимые альтернативы; выберите одну модель."
|
||||
: id === "range"
|
||||
? "Дистанция использует рамки детектора и синхронное облако точек."
|
||||
: id === "geometry"
|
||||
? "TGS обрабатывает LiDAR независимо от сегментации и детектора."
|
||||
: "Связи с другими блоками определяются входами выбранного модуля."
|
||||
: "Для этого слоя на Worker 006 пока нет установленного модуля."}
|
||||
>
|
||||
<Select
|
||||
label={`Выбрать модуль: ${label}`}
|
||||
value={selected[id] ?? "none"}
|
||||
options={[{ value: "none", label: "Не использовать" }, ...modules.map((module) => ({
|
||||
value: module.moduleId,
|
||||
label: module.label,
|
||||
description: existingByModule.has(module.moduleId)
|
||||
? "Готовый результат будет использован без повторного расчёта"
|
||||
: module.dockerName,
|
||||
}))]}
|
||||
disabled={modules.length === 0 || state === "saving"}
|
||||
onChange={(value) => selectModule(id, value)}
|
||||
/>
|
||||
</FieldFrame>
|
||||
</div>
|
||||
</section>;
|
||||
}) : null}
|
||||
{message ? <p className="observatory-ai-config__error" role="alert">{message}</p> : null}
|
||||
</div>
|
||||
</Window>;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { WebViewer } from "@rerun-io/web-viewer";
|
||||
|
||||
import type { RerunFrameWindow } from "./recordedRerunProtocol";
|
||||
import { createRecordedRerunOwner } from "./recordedRerunOwner";
|
||||
|
||||
// This entry is loaded only in the owned iframe. Upstream code is unmodified;
|
||||
// destroying the frame also ends its timers, WASM, video and WebGPU lifetime.
|
||||
const mount = document.getElementById("viewer");
|
||||
if (!mount) throw new Error("Recorded viewer mount is missing");
|
||||
(window as RerunFrameWindow).missionCoreRerun = createRecordedRerunOwner(() => new WebViewer(), mount);
|
||||
@@ -0,0 +1,77 @@
|
||||
import { createRecordedRerunFacade, type RecordedRerunViewer } from "./recordedRerunFacade";
|
||||
import type { RerunFrameWindow } from "./recordedRerunProtocol";
|
||||
import { relayIsolatedRerunInput } from "./isolatedRerunInput";
|
||||
|
||||
/** Own the complete recorded-viewer realm, not just its canvas or SDK handle. */
|
||||
export function createIsolatedRerunHost(host: HTMLElement) {
|
||||
let frame: HTMLIFrameElement | null = host.ownerDocument.createElement("iframe");
|
||||
frame.title = "Синхронизированная запись";
|
||||
frame.className = "rerun-viewport__runtime";
|
||||
frame.src = "/rerun-runtime.html";
|
||||
let disposed = false;
|
||||
let releaseFacade: (() => void) | null = null;
|
||||
let releaseInput: (() => void) | null = null;
|
||||
let frameWindow: RerunFrameWindow | null = null;
|
||||
let rejectReady: ((reason: Error) => void) | null = null;
|
||||
let timer: ReturnType<typeof setTimeout> | undefined;
|
||||
|
||||
const dispose = () => {
|
||||
if (disposed) return;
|
||||
disposed = true;
|
||||
clearTimeout(timer);
|
||||
frame!.onload = null;
|
||||
frame!.onerror = null;
|
||||
releaseInput?.();
|
||||
releaseInput = null;
|
||||
try {
|
||||
releaseFacade?.();
|
||||
} catch {
|
||||
// Realm teardown is required even if a partial native start failed.
|
||||
} finally {
|
||||
releaseFacade = null;
|
||||
frameWindow = null;
|
||||
frame!.remove();
|
||||
frame = null;
|
||||
rejectReady?.(new Error("Recorded viewer was disposed"));
|
||||
rejectReady = null;
|
||||
}
|
||||
};
|
||||
|
||||
const ready = new Promise<{ viewer: RecordedRerunViewer; mount: HTMLElement }>((resolve, reject) => {
|
||||
rejectReady = reject;
|
||||
frame!.onload = () => {
|
||||
if (disposed) return;
|
||||
try {
|
||||
frameWindow = frame!.contentWindow as RerunFrameWindow | null;
|
||||
const api = frameWindow?.missionCoreRerun;
|
||||
if (!api || api.version !== 2) throw new Error("Recorded viewer host is unavailable");
|
||||
const owner = createRecordedRerunFacade((request, bytes) => {
|
||||
const current = frameWindow?.missionCoreRerun;
|
||||
if (!current) throw new Error("Recorded viewer was disposed");
|
||||
return current.invoke(request, bytes);
|
||||
});
|
||||
releaseFacade = owner.dispose;
|
||||
api.connect(owner.notify);
|
||||
releaseInput = relayIsolatedRerunInput(host, frameWindow, frame);
|
||||
clearTimeout(timer);
|
||||
rejectReady = null;
|
||||
frame!.onload = null;
|
||||
frame!.onerror = null;
|
||||
resolve({ viewer: owner.facade, mount: host });
|
||||
} catch (error) {
|
||||
reject(new Error(String(error)));
|
||||
dispose();
|
||||
}
|
||||
};
|
||||
frame!.onerror = () => {
|
||||
reject(new Error("Recorded viewer host failed to load"));
|
||||
dispose();
|
||||
};
|
||||
timer = setTimeout(() => {
|
||||
reject(new Error("Recorded viewer host timed out"));
|
||||
dispose();
|
||||
}, 30_000);
|
||||
});
|
||||
host.append(frame);
|
||||
return { ready, dispose };
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/** Relay Escape from the isolated viewer realm to the owning application. */
|
||||
export function relayIsolatedRerunInput(
|
||||
host: HTMLElement,
|
||||
frameWindow: Window | null,
|
||||
_frame: HTMLIFrameElement | null,
|
||||
) {
|
||||
const parent = host.ownerDocument.defaultView;
|
||||
const escape = (event: KeyboardEvent) => {
|
||||
if (event.key !== "Escape" || event.defaultPrevented || !parent) return;
|
||||
host.dispatchEvent(new parent.KeyboardEvent("keydown", { key: "Escape", bubbles: true }));
|
||||
};
|
||||
frameWindow?.addEventListener("keydown", escape);
|
||||
return () => {
|
||||
frameWindow?.removeEventListener("keydown", escape);
|
||||
frameWindow = null;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
export type RecordedRerunCameraEye = {
|
||||
readonly position: readonly [number, number, number];
|
||||
readonly lookTarget: readonly [number, number, number];
|
||||
readonly eyeUp: readonly [number, number, number];
|
||||
};
|
||||
|
||||
type Vector3 = [number, number, number];
|
||||
type DragMode = "rotate" | "pan" | "roll";
|
||||
|
||||
const ROTATION_RADIANS_PER_POINT = 0.004;
|
||||
const RERUN_WEB_LINE_SCROLL_POINTS = 8;
|
||||
const RERUN_ORBITAL_SCROLL_DIVISOR = 200;
|
||||
const RERUN_WEB_PINCH_SCROLL_DIVISOR = 100;
|
||||
const RERUN_MIN_ORBIT_DISTANCE = 0.02;
|
||||
const DOM_WHEEL_DELTA_LINE = 1;
|
||||
const DOM_WHEEL_DELTA_PAGE = 2;
|
||||
|
||||
export const RECORDED_RERUN_ORBITAL_EYE: RecordedRerunCameraEye = {
|
||||
position: [16, -16, 18],
|
||||
lookTarget: [0, 0, 0],
|
||||
eyeUp: [0, 0, 1],
|
||||
};
|
||||
|
||||
export const RECORDED_RERUN_PLAN_EYE: RecordedRerunCameraEye = {
|
||||
position: [0, 0, 30],
|
||||
lookTarget: [0, 0, 0],
|
||||
eyeUp: [0, 1, 0],
|
||||
};
|
||||
|
||||
const vector = (value: readonly [number, number, number]): Vector3 => [...value];
|
||||
const add = (a: Vector3, b: Vector3): Vector3 => [a[0] + b[0], a[1] + b[1], a[2] + b[2]];
|
||||
const subtract = (a: Vector3, b: Vector3): Vector3 => [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
|
||||
const scale = (value: Vector3, factor: number): Vector3 => [value[0] * factor, value[1] * factor, value[2] * factor];
|
||||
const dot = (a: Vector3, b: Vector3) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
|
||||
const cross = (a: Vector3, b: Vector3): Vector3 => [
|
||||
a[1] * b[2] - a[2] * b[1],
|
||||
a[2] * b[0] - a[0] * b[2],
|
||||
a[0] * b[1] - a[1] * b[0],
|
||||
];
|
||||
const length = (value: Vector3) => Math.hypot(...value);
|
||||
const normalize = (value: Vector3, fallback: Vector3): Vector3 => {
|
||||
const magnitude = length(value);
|
||||
return magnitude > Number.EPSILON ? scale(value, 1 / magnitude) : fallback;
|
||||
};
|
||||
const clamp = (value: number, minimum: number, maximum: number) => Math.max(minimum, Math.min(maximum, value));
|
||||
const rotateAroundAxis = (value: Vector3, rawAxis: Vector3, angle: number): Vector3 => {
|
||||
const axis = normalize(rawAxis, [0, 0, 1]);
|
||||
const cosine = Math.cos(angle);
|
||||
const sine = Math.sin(angle);
|
||||
return add(
|
||||
add(scale(value, cosine), scale(cross(axis, value), sine)),
|
||||
scale(axis, dot(axis, value) * (1 - cosine)),
|
||||
);
|
||||
};
|
||||
|
||||
function cloneEye(value: RecordedRerunCameraEye): {
|
||||
position: Vector3;
|
||||
lookTarget: Vector3;
|
||||
eyeUp: Vector3;
|
||||
} {
|
||||
return {
|
||||
position: vector(value.position),
|
||||
lookTarget: vector(value.lookTarget),
|
||||
eyeUp: vector(value.eyeUp),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Mirrors Rerun 0.36.3's orbital eye math while the native viewer remains the
|
||||
* renderer. Rerun writes operator navigation into its active blueprint clone;
|
||||
* the WebViewer API cannot read that clone. Keeping the same three eye vectors
|
||||
* here lets a later layer blueprint activate with the exact operator pose.
|
||||
*/
|
||||
export function createRecordedRerunCameraJournal(
|
||||
canvas: HTMLCanvasElement,
|
||||
scope: Window & typeof globalThis,
|
||||
) {
|
||||
let eye = cloneEye(RECORDED_RERUN_ORBITAL_EYE);
|
||||
let spatialViewportStart = 0;
|
||||
let pointerId: number | null = null;
|
||||
let dragMode: DragMode | null = null;
|
||||
let previousPointer: readonly [number, number] | null = null;
|
||||
let latestPointer: readonly [number, number] | null = null;
|
||||
let maxOrbitalRadius = 1.0e17;
|
||||
|
||||
const isSpatialPoint = (event: MouseEvent) => {
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
return rect.width > 0 && (event.clientX - rect.left) / rect.width >= spatialViewportStart;
|
||||
};
|
||||
const forward = () => normalize(subtract(eye.lookTarget, eye.position), [0, 1, 0]);
|
||||
const usableUp = () => {
|
||||
const fwd = forward();
|
||||
const fallbackRight: Vector3 = Math.abs(dot(fwd, [0, 0, 1])) > 0.9999
|
||||
? [1, 0, 0]
|
||||
: cross(fwd, [0, 0, 1]);
|
||||
const fallback = normalize(cross(fwd, fallbackRight), [0, 0, 1]);
|
||||
const candidate = normalize(eye.eyeUp, fallback);
|
||||
return Math.abs(dot(candidate, fwd)) > 0.9999 ? fallback : candidate;
|
||||
};
|
||||
const orbitRadius = () => length(subtract(eye.position, eye.lookTarget));
|
||||
|
||||
const rotate = (deltaX: number, deltaY: number) => {
|
||||
const radius = orbitRadius();
|
||||
const up = usableUp();
|
||||
let fwd = forward();
|
||||
const oldPitch = Math.asin(clamp(dot(fwd, up), -1, 1));
|
||||
fwd = normalize(
|
||||
rotateAroundAxis(fwd, up, -ROTATION_RADIANS_PER_POINT * deltaX),
|
||||
fwd,
|
||||
);
|
||||
const right = normalize(cross(fwd, up), [1, 0, 0]);
|
||||
const maxPitch = 0.99 * Math.PI / 2;
|
||||
const nextPitch = clamp(
|
||||
oldPitch - ROTATION_RADIANS_PER_POINT * deltaY,
|
||||
-maxPitch,
|
||||
maxPitch,
|
||||
);
|
||||
fwd = normalize(rotateAroundAxis(fwd, right, nextPitch - oldPitch), fwd);
|
||||
eye.position = subtract(eye.lookTarget, scale(fwd, radius));
|
||||
};
|
||||
|
||||
const pan = (deltaX: number, deltaY: number) => {
|
||||
const fwd = forward();
|
||||
const right = normalize(cross(fwd, usableUp()), [1, 0, 0]);
|
||||
const screenUp = normalize(cross(right, fwd), [0, 0, 1]);
|
||||
const speed = 0.001 * orbitRadius();
|
||||
const translation = add(scale(right, -deltaX * speed), scale(screenUp, deltaY * speed));
|
||||
eye.position = add(eye.position, translation);
|
||||
eye.lookTarget = add(eye.lookTarget, translation);
|
||||
};
|
||||
|
||||
const roll = (event: PointerEvent, deltaX: number, deltaY: number) => {
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
const left = rect.left + rect.width * spatialViewportStart;
|
||||
const centerX = left + (rect.right - left) / 2;
|
||||
const centerY = rect.top + rect.height / 2;
|
||||
const relativeX = event.clientX - centerX;
|
||||
const relativeY = event.clientY - centerY;
|
||||
const divisor = relativeX * relativeX + relativeY * relativeY;
|
||||
if (divisor <= Number.EPSILON) return;
|
||||
const angle = (-deltaY * relativeX + deltaX * relativeY) / divisor;
|
||||
eye.eyeUp = normalize(rotateAroundAxis(eye.eyeUp, scale(forward(), -1), angle), eye.eyeUp);
|
||||
};
|
||||
|
||||
const pointerDown = (event: PointerEvent) => {
|
||||
latestPointer = [event.clientX, event.clientY];
|
||||
if (!isSpatialPoint(event)) return;
|
||||
pointerId = event.pointerId;
|
||||
previousPointer = [event.clientX, event.clientY];
|
||||
dragMode = event.button === 2 ? "pan" : event.button === 1 || event.altKey ? "roll" : "rotate";
|
||||
};
|
||||
const pointerMove = (event: PointerEvent) => {
|
||||
latestPointer = [event.clientX, event.clientY];
|
||||
if (event.pointerId !== pointerId || !previousPointer || !dragMode) return;
|
||||
const deltaX = event.clientX - previousPointer[0];
|
||||
const deltaY = event.clientY - previousPointer[1];
|
||||
previousPointer = [event.clientX, event.clientY];
|
||||
if (dragMode === "rotate") rotate(deltaX, deltaY);
|
||||
else if (dragMode === "pan") pan(deltaX, deltaY);
|
||||
else roll(event, deltaX, deltaY);
|
||||
};
|
||||
const pointerEnd = (event: PointerEvent) => {
|
||||
if (event.pointerId !== pointerId) return;
|
||||
pointerMove(event);
|
||||
pointerId = null;
|
||||
previousPointer = null;
|
||||
dragMode = null;
|
||||
};
|
||||
const wheel = (event: WheelEvent) => {
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
const wheelX = event.clientX >= rect.left && event.clientX <= rect.right
|
||||
? event.clientX
|
||||
: latestPointer?.[0];
|
||||
if (wheelX === undefined || rect.width <= 0) return;
|
||||
if ((wheelX - rect.left) / rect.width < spatialViewportStart) return;
|
||||
const unitMultiplier = event.deltaMode === DOM_WHEEL_DELTA_LINE
|
||||
? RERUN_WEB_LINE_SCROLL_POINTS
|
||||
: event.deltaMode === DOM_WHEEL_DELTA_PAGE
|
||||
? rect.height
|
||||
: 1;
|
||||
const scrollPoints = (event.deltaX + event.deltaY) * unitMultiplier;
|
||||
// eframe negates the DOM delta before Rerun applies
|
||||
// radius / exp(smooth_scroll_delta / 200). Over a smoothed gesture the
|
||||
// exponent products collapse to the raw accumulated DOM delta below.
|
||||
const divisor = event.ctrlKey
|
||||
? RERUN_WEB_PINCH_SCROLL_DIVISOR
|
||||
: RERUN_ORBITAL_SCROLL_DIVISOR;
|
||||
const radiusFactor = Math.exp(scrollPoints / divisor);
|
||||
const offset = subtract(eye.position, eye.lookTarget);
|
||||
const radius = length(offset);
|
||||
// Keep the same orbital bounds as Rerun 0.36.3. An eye already outside
|
||||
// the scene-derived cap is allowed to stay there and is never snapped in.
|
||||
const nextRadius = clamp(
|
||||
radius * radiusFactor,
|
||||
RERUN_MIN_ORBIT_DISTANCE,
|
||||
Math.max(radius, maxOrbitalRadius),
|
||||
);
|
||||
eye.position = add(
|
||||
eye.lookTarget,
|
||||
scale(offset, nextRadius / Math.max(radius, Number.EPSILON)),
|
||||
);
|
||||
};
|
||||
|
||||
canvas.addEventListener("pointerdown", pointerDown, true);
|
||||
scope.addEventListener("pointermove", pointerMove, true);
|
||||
scope.addEventListener("pointerup", pointerEnd, true);
|
||||
scope.addEventListener("pointercancel", pointerEnd, true);
|
||||
scope.addEventListener("wheel", wheel, { capture: true, passive: true });
|
||||
|
||||
return {
|
||||
current(): RecordedRerunCameraEye {
|
||||
return {
|
||||
position: [...eye.position],
|
||||
lookTarget: [...eye.lookTarget],
|
||||
eyeUp: [...eye.eyeUp],
|
||||
};
|
||||
},
|
||||
configure(nextEye: RecordedRerunCameraEye, nextSpatialViewportStart: number) {
|
||||
eye = cloneEye(nextEye);
|
||||
spatialViewportStart = clamp(nextSpatialViewportStart, 0, 0.9);
|
||||
pointerId = null;
|
||||
dragMode = null;
|
||||
previousPointer = null;
|
||||
},
|
||||
setSpatialViewportStart(nextSpatialViewportStart: number) {
|
||||
spatialViewportStart = clamp(nextSpatialViewportStart, 0, 0.9);
|
||||
},
|
||||
setMaxOrbitalRadius(nextMaxOrbitalRadius: number) {
|
||||
if (
|
||||
Number.isFinite(nextMaxOrbitalRadius) &&
|
||||
nextMaxOrbitalRadius >= RERUN_MIN_ORBIT_DISTANCE
|
||||
) {
|
||||
maxOrbitalRadius = nextMaxOrbitalRadius;
|
||||
}
|
||||
},
|
||||
dispose() {
|
||||
canvas.removeEventListener("pointerdown", pointerDown, true);
|
||||
scope.removeEventListener("pointermove", pointerMove, true);
|
||||
scope.removeEventListener("pointerup", pointerEnd, true);
|
||||
scope.removeEventListener("pointercancel", pointerEnd, true);
|
||||
scope.removeEventListener("wheel", wheel, true);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
import type { WebViewer } from "@rerun-io/web-viewer";
|
||||
import type { RerunInvoke } from "./recordedRerunProtocol";
|
||||
import type { RecordedRerunCameraEye } from "./recordedRerunCameraJournal";
|
||||
|
||||
type NativeChannel = ReturnType<WebViewer["open_channel"]>;
|
||||
type Channel = Pick<NativeChannel, "ready" | "send_rrd" | "close">;
|
||||
export type RecordedRerunViewer = Pick<WebViewer,
|
||||
"start" | "stop" | "ready" | "on" | "open" | "close" | "override_panel_state" |
|
||||
"get_active_recording_id" | "get_active_timeline" | "get_current_time" |
|
||||
"get_playing" | "get_time_range" | "set_active_timeline" |
|
||||
"set_current_time" | "set_playing"
|
||||
> & {
|
||||
open_channel: (name?: string) => Channel;
|
||||
configure_camera_journal: (eye: RecordedRerunCameraEye, spatialViewportStart: number) => void;
|
||||
get_camera_eye: () => RecordedRerunCameraEye;
|
||||
set_camera_viewport_start: (spatialViewportStart: number) => void;
|
||||
set_camera_max_orbital_radius: (maxOrbitalRadius: number) => void;
|
||||
};
|
||||
|
||||
/** Parent-owned values only. No SDK Promise or foreign prototype escapes. */
|
||||
export function createRecordedRerunFacade(invoke: RerunInvoke | null) {
|
||||
let nextId = 0;
|
||||
const callbacks = new Map<number, (...args: unknown[]) => void>();
|
||||
const starts = new Map<number, { resolve: () => void; reject: (error: Error) => void }>();
|
||||
const command = (method: string, args: unknown[] = [], id?: number, bytes?: Uint8Array) => {
|
||||
if (!invoke) throw new Error("Recorded viewer was disposed");
|
||||
const response = JSON.parse(invoke(JSON.stringify({ method, args, id }), bytes));
|
||||
if (response.error) throw new Error(response.error);
|
||||
return response.result;
|
||||
};
|
||||
const dispose = () => {
|
||||
callbacks.clear();
|
||||
for (const pending of starts.values()) pending.reject(new Error("Recorded viewer was disposed"));
|
||||
starts.clear();
|
||||
try { if (invoke) command("stop"); } finally { invoke = null; }
|
||||
};
|
||||
const notify = (message: string) => {
|
||||
if (!invoke) return;
|
||||
const event = JSON.parse(message);
|
||||
if (event.type === "event") callbacks.get(event.id)?.(...event.values);
|
||||
else {
|
||||
const pending = starts.get(event.id);
|
||||
starts.delete(event.id);
|
||||
if (event.type === "started") pending?.resolve();
|
||||
else pending?.reject(new Error(event.message));
|
||||
}
|
||||
};
|
||||
const facade: RecordedRerunViewer = {
|
||||
get ready() { return invoke ? command("ready") : false; },
|
||||
start(source, _parent, options) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const id = ++nextId;
|
||||
starts.set(id, { resolve, reject });
|
||||
try { command("start", [source, options], id); }
|
||||
catch (error) { starts.delete(id); reject(error); }
|
||||
});
|
||||
},
|
||||
stop: dispose,
|
||||
on: ((event: string, callback: (...args: unknown[]) => void) => {
|
||||
const id = ++nextId;
|
||||
callbacks.set(id, callback);
|
||||
try { command("on", [event], id); }
|
||||
catch (error) { callbacks.delete(id); throw error; }
|
||||
return () => {
|
||||
if (callbacks.delete(id) && invoke) command("off", [], id);
|
||||
};
|
||||
}) as WebViewer["on"],
|
||||
open_channel(name) {
|
||||
const id = ++nextId;
|
||||
command("channel-open", name === undefined ? [] : [name], id);
|
||||
let closed = false;
|
||||
return {
|
||||
get ready() { return !closed && invoke ? command("channel-ready", [], id) : false; },
|
||||
send_rrd(bytes) { if (!closed && invoke) command("channel-send", [], id, bytes); },
|
||||
close() {
|
||||
if (closed) return;
|
||||
closed = true;
|
||||
if (invoke) command("channel-close", [], id);
|
||||
},
|
||||
};
|
||||
},
|
||||
open: (...args) => command("open", args),
|
||||
close: (...args) => { if (invoke) command("close", args); },
|
||||
override_panel_state: (...args) => command("override_panel_state", args),
|
||||
get_active_recording_id: () => command("get_active_recording_id"),
|
||||
get_active_timeline: (...args) => command("get_active_timeline", args),
|
||||
get_current_time: (...args) => command("get_current_time", args),
|
||||
get_playing: (...args) => command("get_playing", args),
|
||||
get_time_range: (...args) => command("get_time_range", args),
|
||||
set_active_timeline: (...args) => command("set_active_timeline", args),
|
||||
set_current_time: (...args) => command("set_current_time", args),
|
||||
set_playing: (...args) => command("set_playing", args),
|
||||
configure_camera_journal: (eye, spatialViewportStart) => command(
|
||||
"configure-camera-journal", [eye, spatialViewportStart],
|
||||
),
|
||||
get_camera_eye: () => command("get-camera-eye"),
|
||||
set_camera_viewport_start: (spatialViewportStart) => command(
|
||||
"set-camera-viewport-start", [spatialViewportStart],
|
||||
),
|
||||
set_camera_max_orbital_radius: (maxOrbitalRadius) => command(
|
||||
"set-camera-max-orbital-radius", [maxOrbitalRadius],
|
||||
),
|
||||
};
|
||||
return { facade, dispose, notify };
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
import type { WebViewer } from "@rerun-io/web-viewer";
|
||||
import type { RerunFrameApi, RerunNotify } from "./recordedRerunProtocol";
|
||||
import { createRecordedRerunCameraJournal } from "./recordedRerunCameraJournal";
|
||||
|
||||
/** Lives entirely inside the disposable iframe, including pending SDK starts. */
|
||||
export function createRecordedRerunOwner(create: () => WebViewer, mount: HTMLElement): RerunFrameApi {
|
||||
let native: WebViewer | null = null;
|
||||
let notify: RerunNotify | null = null;
|
||||
const subscriptions = new Map<number, () => void>();
|
||||
const channels = new Map<number, ReturnType<WebViewer["open_channel"]>>();
|
||||
let cameraJournal: ReturnType<typeof createRecordedRerunCameraJournal> | null = null;
|
||||
const send = (message: object) => notify?.(JSON.stringify(message));
|
||||
const stop = () => {
|
||||
notify = null;
|
||||
for (const unsubscribe of subscriptions.values()) {
|
||||
try { unsubscribe(); } catch { /* Continue partial-start cleanup. */ }
|
||||
}
|
||||
subscriptions.clear();
|
||||
for (const channel of channels.values()) {
|
||||
try { channel.close(); } catch { /* Other channels must still close. */ }
|
||||
}
|
||||
channels.clear();
|
||||
cameraJournal?.dispose();
|
||||
cameraJournal = null;
|
||||
const current = native;
|
||||
native = null;
|
||||
try { current?.stop(); } catch { /* Realm teardown remains authoritative. */ }
|
||||
};
|
||||
const required = () => {
|
||||
if (!native) throw new Error("Recorded viewer was disposed");
|
||||
return native;
|
||||
};
|
||||
const start = async (id: number, args: [string | string[] | null, Parameters<WebViewer["start"]>[2]]) => {
|
||||
const starting = required();
|
||||
try {
|
||||
await starting.start(args[0], mount, args[1]);
|
||||
if (native === starting && starting.canvas) {
|
||||
cameraJournal?.dispose();
|
||||
cameraJournal = createRecordedRerunCameraJournal(
|
||||
starting.canvas,
|
||||
window as Window & typeof globalThis,
|
||||
);
|
||||
}
|
||||
if (native === starting) send({ type: "started", id });
|
||||
} catch (error) {
|
||||
if (native === starting) send({ type: "start-failed", id, message: String(error) });
|
||||
} finally {
|
||||
if (native !== starting) {
|
||||
try { starting.stop(); } catch { /* Late completion after disposal. */ }
|
||||
}
|
||||
}
|
||||
};
|
||||
return {
|
||||
version: 2,
|
||||
connect(callback) {
|
||||
if (native) throw new Error("Recorded viewer already has an owner");
|
||||
native = create();
|
||||
notify = callback;
|
||||
},
|
||||
invoke(request, bytes) {
|
||||
try {
|
||||
const { method, args = [], id } = JSON.parse(request);
|
||||
let result: unknown;
|
||||
switch (method) {
|
||||
case "stop": stop(); break;
|
||||
case "start": required(); void start(id, args); break;
|
||||
case "ready": result = native?.ready ?? false; break;
|
||||
case "on": {
|
||||
const subscribe = required().on as (event: string, callback: (...values: unknown[]) => void) => () => void;
|
||||
subscriptions.set(id, subscribe.call(required(), args[0], (...values) => send({ type: "event", id, values })));
|
||||
break;
|
||||
}
|
||||
case "off":
|
||||
try { subscriptions.get(id)?.(); } finally { subscriptions.delete(id); }
|
||||
break;
|
||||
case "channel-open": {
|
||||
const name = args[0] as string | undefined;
|
||||
channels.set(id, required().open_channel(name));
|
||||
break;
|
||||
}
|
||||
case "channel-ready": result = channels.get(id)?.ready ?? false; break;
|
||||
case "channel-send": if (bytes) {
|
||||
channels.get(id)?.send_rrd(bytes);
|
||||
} break;
|
||||
case "channel-close":
|
||||
try { channels.get(id)?.close(); } finally {
|
||||
channels.delete(id);
|
||||
}
|
||||
break;
|
||||
case "configure-camera-journal": cameraJournal?.configure(args[0], args[1]); break;
|
||||
case "get-camera-eye": result = cameraJournal?.current(); break;
|
||||
case "set-camera-viewport-start": cameraJournal?.setSpatialViewportStart(args[0]); break;
|
||||
case "set-camera-max-orbital-radius": cameraJournal?.setMaxOrbitalRadius(args[0]); break;
|
||||
case "open": required().open(args[0]); break;
|
||||
case "close": required().close(args[0]); break;
|
||||
case "override_panel_state": required().override_panel_state(args[0], args[1]); break;
|
||||
case "get_active_recording_id": result = required().get_active_recording_id(); break;
|
||||
case "get_active_timeline": result = required().get_active_timeline(args[0]); break;
|
||||
case "get_current_time": result = required().get_current_time(args[0], args[1]); break;
|
||||
case "get_playing": result = required().get_playing(args[0]); break;
|
||||
case "get_time_range": result = required().get_time_range(args[0], args[1]); break;
|
||||
case "set_active_timeline": required().set_active_timeline(args[0], args[1]); break;
|
||||
case "set_current_time": required().set_current_time(args[0], args[1], args[2]); break;
|
||||
case "set_playing": required().set_playing(args[0], args[1]); break;
|
||||
default: throw new Error("Unsupported recorded viewer command");
|
||||
}
|
||||
return JSON.stringify({ result });
|
||||
} catch (error) {
|
||||
// A native Error also has a foreign prototype. Return text, never throw
|
||||
// that object into a parent Promise/callback retained after disposal.
|
||||
return JSON.stringify({ error: String(error) });
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/** Only parent-owned inputs and primitive strings cross the recorded realm.
|
||||
* An SDK instance, Promise, DOM node or native callback never becomes a value
|
||||
* retained by React. This is not a second playback clock.
|
||||
*/
|
||||
export type RerunInvoke = (request: string, bytes?: Uint8Array) => string;
|
||||
export type RerunNotify = (message: string) => void;
|
||||
export interface RerunFrameApi {
|
||||
version: 2;
|
||||
connect: (notify: RerunNotify) => void;
|
||||
invoke: RerunInvoke;
|
||||
}
|
||||
export type RerunFrameWindow = Window & { missionCoreRerun?: RerunFrameApi };
|
||||
@@ -0,0 +1,630 @@
|
||||
import {
|
||||
Application,
|
||||
Asset,
|
||||
BLEND_NONE,
|
||||
BLEND_NORMAL,
|
||||
Color,
|
||||
CULLFACE_NONE,
|
||||
Entity,
|
||||
FILLMODE_NONE,
|
||||
Mat4,
|
||||
Quat,
|
||||
RESOLUTION_AUTO,
|
||||
StandardMaterial,
|
||||
Vec2,
|
||||
Vec3,
|
||||
WasmModule,
|
||||
type ContainerResource,
|
||||
type ModelComponent,
|
||||
type ScriptType,
|
||||
} from "playcanvas";
|
||||
import { CameraControls } from "playcanvas/scripts/esm/camera-controls.mjs";
|
||||
|
||||
import {
|
||||
prepareSimulationPhysics,
|
||||
SimulationUgvController,
|
||||
type AmmoApi,
|
||||
} from "./SimulationUgvController";
|
||||
|
||||
import type {
|
||||
SimulationEulerRotation,
|
||||
SimulationUgvPreset,
|
||||
SimulationViewerQuality,
|
||||
SimulationWorldManifest,
|
||||
} from "../../core/simulation/projects";
|
||||
|
||||
export type SimulationViewMode = "visual" | "collision" | "combined";
|
||||
export type SimulationQuality = SimulationViewerQuality;
|
||||
export type SimulationLayer = "visual" | "collision";
|
||||
export type SimulationCameraAxis = "horizontal" | "vertical";
|
||||
export type SimulationControlMode = "free" | "ugv";
|
||||
|
||||
export function playCanvasEulerTransform(rotation: SimulationEulerRotation): number[] {
|
||||
return Array.from(new Mat4().setFromEulerAngles(rotation.x, rotation.y, rotation.z).data);
|
||||
}
|
||||
|
||||
export interface SimulationRuntime {
|
||||
mount(canvas: HTMLCanvasElement): Promise<void>;
|
||||
loadWorld(manifest: SimulationWorldManifest): Promise<void>;
|
||||
setViewMode(mode: SimulationViewMode): Promise<void>;
|
||||
setControlMode(mode: SimulationControlMode): Promise<void>;
|
||||
setUgvPreset(preset: SimulationUgvPreset): void;
|
||||
setQuality(quality: SimulationQuality): void;
|
||||
setLayerWorldTransform(layer: SimulationLayer, transform: number[]): void;
|
||||
setCameraInversion(axis: SimulationCameraAxis, inverted: boolean): void;
|
||||
home(): void;
|
||||
focusBounds(): void;
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
type DesktopCameraInput = {
|
||||
read(): { mouse: number[] } & Record<string, number[]>;
|
||||
};
|
||||
|
||||
type CameraController = ScriptType & Pick<CameraControls, "reset" | "focus"> & {
|
||||
_desktopInput?: DesktopCameraInput;
|
||||
_state?: {
|
||||
axis: Vec3;
|
||||
shift: number;
|
||||
ctrl: number;
|
||||
mouse: number[];
|
||||
touches: number;
|
||||
};
|
||||
};
|
||||
|
||||
const HOME_POSITION = new Vec3(0, 1, 0);
|
||||
const HOME_FOCUS = new Vec3(1, 1, 0);
|
||||
const DRACO_DECODER_ROOT = "/vendor/playcanvas/draco/1.5.7";
|
||||
|
||||
const QUALITY_PROFILES: Record<SimulationQuality, {
|
||||
lodBaseDistance: number;
|
||||
lodMultiplier: number;
|
||||
lodRangeMin: number;
|
||||
lodRangeMax: number;
|
||||
pixelRatio: number;
|
||||
splatBudget: number;
|
||||
targetFps: number;
|
||||
}> = {
|
||||
low: { lodBaseDistance: 5, lodMultiplier: 2, lodRangeMin: 3, lodRangeMax: 5, pixelRatio: 0.75, splatBudget: 350_000, targetFps: 30 },
|
||||
medium: { lodBaseDistance: 5, lodMultiplier: 2, lodRangeMin: 2, lodRangeMax: 5, pixelRatio: 1, splatBudget: 700_000, targetFps: 45 },
|
||||
high: { lodBaseDistance: 5, lodMultiplier: 3, lodRangeMin: 1, lodRangeMax: 5, pixelRatio: 1.25, splatBudget: 1_200_000, targetFps: 60 },
|
||||
ultra: { lodBaseDistance: 7, lodMultiplier: 3, lodRangeMin: 0, lodRangeMax: 5, pixelRatio: 1.5, splatBudget: 2_500_000, targetFps: 60 },
|
||||
maximum: { lodBaseDistance: 7, lodMultiplier: 3, lodRangeMin: 0, lodRangeMax: 5, pixelRatio: 2, splatBudget: 4_000_000, targetFps: 60 },
|
||||
};
|
||||
|
||||
export class PlayCanvasRuntime implements SimulationRuntime {
|
||||
private app: Application | null = null;
|
||||
private canvas: HTMLCanvasElement | null = null;
|
||||
private camera: Entity | null = null;
|
||||
private cameraController: CameraController | null = null;
|
||||
private visualEntity: Entity | null = null;
|
||||
private visualAsset: Asset | null = null;
|
||||
private collisionEntity: Entity | null = null;
|
||||
private collisionVisuals: ModelComponent[] = [];
|
||||
private collisionAsset: Asset | null = null;
|
||||
private collisionMaterial: StandardMaterial | null = null;
|
||||
private collisionSource: { meshUrl: string; projectId: string; worldTransform: number[] } | null = null;
|
||||
private collisionLoadPromise: Promise<void> | null = null;
|
||||
private resizeObserver: ResizeObserver | null = null;
|
||||
private viewMode: SimulationViewMode = "visual";
|
||||
private quality: SimulationQuality = "high";
|
||||
private cameraInversion = { horizontal: true, vertical: false };
|
||||
private controlMode: SimulationControlMode = "free";
|
||||
private controlRequestVersion = 0;
|
||||
private ugvController: SimulationUgvController | null = null;
|
||||
private readonly lastCameraPosition = new Vec3();
|
||||
private readonly lastCameraRotation = new Quat();
|
||||
private cameraSnapshotReady = false;
|
||||
private renderIntervalMs = 1000 / 60;
|
||||
private renderAccumulatorMs = 0;
|
||||
private disposed = false;
|
||||
private requestGsplatFrame = (): void => {
|
||||
this.requestRender();
|
||||
};
|
||||
private updateFrame = (deltaSeconds: number): void => {
|
||||
if (!this.app || !this.camera || this.disposed) return;
|
||||
this.ugvController?.update(deltaSeconds);
|
||||
const position = this.camera.getPosition();
|
||||
const rotation = this.camera.getRotation();
|
||||
const cameraChanged = !this.cameraSnapshotReady
|
||||
|| !position.equals(this.lastCameraPosition)
|
||||
|| !rotation.equals(this.lastCameraRotation);
|
||||
if (!cameraChanged && this.controlMode === "free") return;
|
||||
this.lastCameraPosition.copy(position);
|
||||
this.lastCameraRotation.copy(rotation);
|
||||
this.cameraSnapshotReady = true;
|
||||
this.renderAccumulatorMs += deltaSeconds * 1000;
|
||||
if (this.renderAccumulatorMs < this.renderIntervalMs) return;
|
||||
this.renderAccumulatorMs %= this.renderIntervalMs;
|
||||
this.app.renderNextFrame = true;
|
||||
};
|
||||
|
||||
async mount(canvas: HTMLCanvasElement): Promise<void> {
|
||||
if (this.app) throw new Error("Runtime сцены уже запущен.");
|
||||
this.disposed = false;
|
||||
this.renderAccumulatorMs = 0;
|
||||
this.canvas = canvas;
|
||||
configureDracoDecoder();
|
||||
const ammo: AmmoApi = await prepareSimulationPhysics();
|
||||
if (this.disposed) return;
|
||||
canvas.tabIndex = 0;
|
||||
canvas.addEventListener("contextmenu", preventContextMenu);
|
||||
canvas.addEventListener("pointerdown", focusCanvas, true);
|
||||
const app = new Application(canvas, {
|
||||
graphicsDeviceOptions: {
|
||||
antialias: true,
|
||||
alpha: false,
|
||||
preserveDrawingBuffer: false,
|
||||
powerPreference: "high-performance",
|
||||
},
|
||||
});
|
||||
this.app = app;
|
||||
app.autoRender = false;
|
||||
app.on("update", this.updateFrame);
|
||||
app.systems.gsplat?.on("frame:request", this.requestGsplatFrame);
|
||||
app.setCanvasFillMode(FILLMODE_NONE, 1, 1);
|
||||
app.setCanvasResolution(RESOLUTION_AUTO);
|
||||
app.scene.ambientLight = new Color(0.35, 0.37, 0.42);
|
||||
|
||||
const camera = new Entity("SimulationCamera");
|
||||
camera.addComponent("camera", {
|
||||
clearColor: new Color(0.025, 0.03, 0.04),
|
||||
nearClip: 0.02,
|
||||
farClip: 20_000,
|
||||
fov: 58,
|
||||
});
|
||||
camera.setPosition(HOME_POSITION);
|
||||
camera.lookAt(HOME_FOCUS);
|
||||
camera.addComponent("script");
|
||||
const controller = camera.script?.create(CameraControls, {
|
||||
properties: {
|
||||
enableOrbit: true,
|
||||
enableFly: true,
|
||||
focusPoint: new Vec3(0, 0, 0),
|
||||
zoomRange: new Vec2(0.05, 20_000),
|
||||
},
|
||||
}) as CameraController | null;
|
||||
if (controller) configureCameraDrag(controller, () => this.cameraInversion);
|
||||
app.root.addChild(camera);
|
||||
this.camera = camera;
|
||||
this.cameraController = controller;
|
||||
this.ugvController = new SimulationUgvController(app, canvas, camera, ammo);
|
||||
|
||||
const light = new Entity("SimulationSun");
|
||||
light.addComponent("light", {
|
||||
type: "directional",
|
||||
color: new Color(1, 0.97, 0.9),
|
||||
intensity: 1.1,
|
||||
castShadows: false,
|
||||
});
|
||||
light.setEulerAngles(42, 28, 0);
|
||||
app.root.addChild(light);
|
||||
|
||||
this.resizeObserver = new ResizeObserver(() => this.resize());
|
||||
this.resizeObserver.observe(canvas.parentElement ?? canvas);
|
||||
this.resize();
|
||||
app.start();
|
||||
await Promise.resolve();
|
||||
}
|
||||
|
||||
async loadWorld(manifest: SimulationWorldManifest): Promise<void> {
|
||||
const app = this.requiredApp();
|
||||
this.unloadWorld();
|
||||
const sourceUrl = manifest.visual.streamedSogUrl ?? manifest.visual.previewSogUrl;
|
||||
if (!sourceUrl) throw new Error("Манифест локации не содержит визуальный слой.");
|
||||
const asset = new Asset(`SimulationWorld:${manifest.projectId}`, "gsplat", { url: sourceUrl });
|
||||
app.assets.add(asset);
|
||||
this.visualAsset = asset;
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const ready = (loaded: Asset) => {
|
||||
if (this.disposed || !this.app) {
|
||||
loaded.unload();
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
const visual = new Entity("GaussianWorld");
|
||||
visual.enabled = false;
|
||||
applyWorldTransform(visual, manifest.transforms.worldFromVisual);
|
||||
app.root.addChild(visual);
|
||||
visual.addComponent("gsplat", {
|
||||
asset: loaded,
|
||||
unified: true,
|
||||
});
|
||||
visual.enabled = true;
|
||||
this.visualEntity = visual;
|
||||
this.applyQuality();
|
||||
this.applyViewMode();
|
||||
this.requestRender();
|
||||
resolve();
|
||||
};
|
||||
const failed = (_error: unknown) => {
|
||||
reject(new Error("Визуальный слой не загрузился."));
|
||||
};
|
||||
asset.ready(ready);
|
||||
asset.once("error", failed);
|
||||
app.assets.load(asset);
|
||||
});
|
||||
if (manifest.collision.available && manifest.collision.meshUrl) {
|
||||
this.collisionSource = {
|
||||
meshUrl: manifest.collision.meshUrl,
|
||||
projectId: manifest.projectId,
|
||||
worldTransform: manifest.transforms.worldFromCollision,
|
||||
};
|
||||
}
|
||||
this.applyViewMode();
|
||||
this.home();
|
||||
}
|
||||
|
||||
async setViewMode(mode: SimulationViewMode): Promise<void> {
|
||||
this.viewMode = mode;
|
||||
this.applyViewMode();
|
||||
try {
|
||||
if (mode !== "visual" && !this.collisionEntity) {
|
||||
await this.ensureCollision();
|
||||
this.applyViewMode();
|
||||
}
|
||||
} finally {
|
||||
this.requestRender();
|
||||
this.canvas?.focus({ preventScroll: true });
|
||||
}
|
||||
}
|
||||
|
||||
async setControlMode(mode: SimulationControlMode): Promise<void> {
|
||||
const requestVersion = ++this.controlRequestVersion;
|
||||
const controller = this.ugvController;
|
||||
if (!controller) throw new Error("Управление UGV ещё не готово.");
|
||||
|
||||
if (mode === "free") {
|
||||
controller.disable();
|
||||
this.controlMode = "free";
|
||||
if (this.cameraController) this.cameraController.enabled = true;
|
||||
this.home();
|
||||
return;
|
||||
}
|
||||
if (this.controlMode === "ugv" && controller.enabled) return;
|
||||
|
||||
try {
|
||||
await this.ensureCollision();
|
||||
if (requestVersion !== this.controlRequestVersion || this.disposed) return;
|
||||
const collisionWorld = this.collisionEntity;
|
||||
if (!collisionWorld) throw new Error("Физический слой UGV отсутствует.");
|
||||
if (this.cameraController) this.cameraController.enabled = false;
|
||||
await controller.enable(collisionWorld);
|
||||
if (requestVersion !== this.controlRequestVersion || this.disposed) {
|
||||
controller.disable();
|
||||
if (this.cameraController) this.cameraController.enabled = true;
|
||||
return;
|
||||
}
|
||||
this.controlMode = "ugv";
|
||||
this.cameraSnapshotReady = false;
|
||||
this.requestRender();
|
||||
} catch (error) {
|
||||
controller.disable();
|
||||
this.controlMode = "free";
|
||||
if (this.cameraController) this.cameraController.enabled = true;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
setQuality(quality: SimulationQuality): void {
|
||||
this.quality = quality;
|
||||
this.applyQuality();
|
||||
this.requestRender();
|
||||
}
|
||||
|
||||
setUgvPreset(preset: SimulationUgvPreset): void {
|
||||
this.ugvController?.configure(preset);
|
||||
this.cameraSnapshotReady = false;
|
||||
this.requestRender();
|
||||
}
|
||||
|
||||
setLayerWorldTransform(layer: SimulationLayer, transform: number[]): void {
|
||||
if (transform.length !== 16) {
|
||||
throw new Error("World transform должен содержать матрицу 4×4.");
|
||||
}
|
||||
if (layer === "visual") {
|
||||
if (this.visualEntity) applyWorldTransform(this.visualEntity, transform);
|
||||
this.requestRender();
|
||||
return;
|
||||
}
|
||||
if (this.collisionSource) this.collisionSource.worldTransform = [...transform];
|
||||
if (this.collisionEntity) applyWorldTransform(this.collisionEntity, transform);
|
||||
this.ugvController?.syncCollisionBodies();
|
||||
this.requestRender();
|
||||
}
|
||||
|
||||
setCameraInversion(axis: SimulationCameraAxis, inverted: boolean): void {
|
||||
this.cameraInversion[axis] = inverted;
|
||||
}
|
||||
|
||||
home(): void {
|
||||
if (this.controlMode === "ugv" && this.ugvController?.enabled) {
|
||||
this.ugvController.reset();
|
||||
this.cameraSnapshotReady = false;
|
||||
this.requestRender();
|
||||
return;
|
||||
}
|
||||
this.resetCameraInteraction();
|
||||
if (this.cameraController) {
|
||||
this.cameraController.reset(HOME_FOCUS, HOME_POSITION);
|
||||
} else if (this.camera) {
|
||||
this.camera.setPosition(HOME_POSITION);
|
||||
this.camera.lookAt(HOME_FOCUS);
|
||||
}
|
||||
this.requestRender();
|
||||
this.canvas?.focus({ preventScroll: true });
|
||||
}
|
||||
|
||||
focusBounds(): void {
|
||||
this.resetCameraInteraction();
|
||||
const bounds = this.visualEntity?.gsplat?.customAabb;
|
||||
const focus = bounds?.center.clone() ?? new Vec3(0, 0, 0);
|
||||
const radius = bounds?.halfExtents.length() ?? 3;
|
||||
const distance = Math.max(1, radius * 2.4);
|
||||
const position = new Vec3(1, 0.55, 1).normalize().mulScalar(distance).add(focus);
|
||||
if (this.cameraController) {
|
||||
this.cameraController.reset(focus, position);
|
||||
} else if (this.camera) {
|
||||
this.camera.setPosition(position);
|
||||
this.camera.lookAt(focus);
|
||||
}
|
||||
this.requestRender();
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
if (this.disposed) return;
|
||||
this.disposed = true;
|
||||
this.resizeObserver?.disconnect();
|
||||
this.resizeObserver = null;
|
||||
const webgl = this.canvas?.getContext("webgl2") ?? this.canvas?.getContext("webgl");
|
||||
this.unloadWorld();
|
||||
this.ugvController?.dispose();
|
||||
this.ugvController = null;
|
||||
if (this.app) {
|
||||
this.app.autoRender = false;
|
||||
this.app.renderNextFrame = false;
|
||||
this.app.systems.gsplat?.off("frame:request", this.requestGsplatFrame);
|
||||
this.app.off("update", this.updateFrame);
|
||||
this.app.destroy();
|
||||
}
|
||||
webgl?.getExtension("WEBGL_lose_context")?.loseContext();
|
||||
if (this.canvas) {
|
||||
this.canvas.width = 1;
|
||||
this.canvas.height = 1;
|
||||
}
|
||||
if (this.canvas) {
|
||||
this.canvas.removeEventListener("contextmenu", preventContextMenu);
|
||||
this.canvas.removeEventListener("pointerdown", focusCanvas, true);
|
||||
}
|
||||
this.app = null;
|
||||
this.canvas = null;
|
||||
this.camera = null;
|
||||
this.cameraController = null;
|
||||
this.controlMode = "free";
|
||||
this.cameraSnapshotReady = false;
|
||||
}
|
||||
|
||||
private requiredApp(): Application {
|
||||
if (!this.app) throw new Error("Runtime сцены не запущен.");
|
||||
return this.app;
|
||||
}
|
||||
|
||||
private requestRender(): void {
|
||||
if (this.app && !this.disposed) this.app.renderNextFrame = true;
|
||||
}
|
||||
|
||||
private resize(): void {
|
||||
if (!this.app || !this.canvas) return;
|
||||
const bounds = this.canvas.parentElement?.getBoundingClientRect();
|
||||
const width = Math.max(1, Math.round(bounds?.width ?? this.canvas.clientWidth));
|
||||
const height = Math.max(1, Math.round(bounds?.height ?? this.canvas.clientHeight));
|
||||
this.app.resizeCanvas(width, height);
|
||||
this.requestRender();
|
||||
}
|
||||
|
||||
private applyViewMode(): void {
|
||||
if (this.visualEntity) {
|
||||
this.visualEntity.enabled = this.viewMode !== "collision" || !this.collisionEntity;
|
||||
}
|
||||
if (this.collisionEntity) {
|
||||
// Physics bodies must stay active while their green debug geometry is hidden.
|
||||
this.collisionEntity.enabled = true;
|
||||
const visible = this.viewMode !== "visual";
|
||||
for (const model of this.collisionVisuals) model.enabled = visible;
|
||||
}
|
||||
if (this.collisionMaterial) {
|
||||
const combined = this.viewMode === "combined";
|
||||
this.collisionMaterial.opacity = combined ? 0.34 : 1;
|
||||
this.collisionMaterial.blendType = combined ? BLEND_NORMAL : BLEND_NONE;
|
||||
this.collisionMaterial.depthWrite = !combined;
|
||||
this.collisionMaterial.update();
|
||||
}
|
||||
this.requestRender();
|
||||
}
|
||||
|
||||
private resetCameraInteraction(): void {
|
||||
const controller = this.cameraController;
|
||||
if (!controller) return;
|
||||
// CameraControls owns pointer capture and its input-source lifecycle. Mutating
|
||||
// those private fields here left the source unable to claim the next drag
|
||||
// after a layer switch. The documented state event drains pending deltas.
|
||||
controller.fire("state");
|
||||
const state = controller._state;
|
||||
if (state) {
|
||||
state.axis.set(0, 0, 0);
|
||||
state.shift = 0;
|
||||
state.ctrl = 0;
|
||||
state.mouse.fill(0);
|
||||
state.touches = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private applyQuality(): void {
|
||||
const gsplat = this.visualEntity?.gsplat;
|
||||
const profile = QUALITY_PROFILES[this.quality];
|
||||
if (gsplat) {
|
||||
gsplat.lodBaseDistance = profile.lodBaseDistance;
|
||||
gsplat.lodMultiplier = profile.lodMultiplier;
|
||||
gsplat.lodRangeMin = profile.lodRangeMin;
|
||||
gsplat.lodRangeMax = profile.lodRangeMax;
|
||||
}
|
||||
if (this.app) {
|
||||
this.app.scene.gsplat.splatBudget = profile.splatBudget;
|
||||
this.renderIntervalMs = 1000 / profile.targetFps;
|
||||
const devicePixelRatio = window.devicePixelRatio || 1;
|
||||
this.app.graphicsDevice.maxPixelRatio = Math.min(devicePixelRatio, profile.pixelRatio);
|
||||
this.resize();
|
||||
}
|
||||
}
|
||||
|
||||
private async loadCollision(meshUrl: string, projectId: string, worldTransform: number[]): Promise<void> {
|
||||
const app = this.requiredApp();
|
||||
const asset = new Asset(
|
||||
`SimulationCollision:${projectId}`,
|
||||
"container",
|
||||
{ url: meshUrl, filename: "scene.collision.glb" },
|
||||
);
|
||||
app.assets.add(asset);
|
||||
this.collisionAsset = asset;
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
asset.ready((loaded) => {
|
||||
try {
|
||||
if (this.disposed || !this.app) {
|
||||
loaded.unload();
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
const resource = loaded.resource as ContainerResource | null;
|
||||
if (!resource) throw new Error("Контейнер GLB не содержит ресурса.");
|
||||
const entity = resource.instantiateModelEntity({
|
||||
castShadows: false,
|
||||
receiveShadows: false,
|
||||
});
|
||||
entity.name = "CollisionWorld";
|
||||
entity.enabled = false;
|
||||
applyWorldTransform(entity, worldTransform);
|
||||
app.root.addChild(entity);
|
||||
|
||||
const material = new StandardMaterial();
|
||||
material.name = "SimulationCollisionMaterial";
|
||||
material.diffuse = new Color(0.2, 0.95, 0.42);
|
||||
material.emissive = new Color(0.08, 0.36, 0.14);
|
||||
material.opacity = 0.34;
|
||||
material.blendType = BLEND_NORMAL;
|
||||
material.depthWrite = false;
|
||||
material.cull = CULLFACE_NONE;
|
||||
material.update();
|
||||
const models = entity.findComponents("model") as ModelComponent[];
|
||||
for (const model of models) {
|
||||
for (const meshInstance of model.meshInstances ?? []) {
|
||||
meshInstance.material = material;
|
||||
}
|
||||
}
|
||||
this.collisionEntity = entity;
|
||||
this.collisionVisuals = models;
|
||||
this.collisionMaterial = material;
|
||||
this.applyViewMode();
|
||||
this.requestRender();
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(new Error(
|
||||
`Слой коллизий не открылся: ${error instanceof Error ? error.message : String(error)}`,
|
||||
));
|
||||
}
|
||||
});
|
||||
asset.once("error", (error: unknown) => {
|
||||
reject(new Error(`Слой коллизий не загрузился: ${String(error)}`));
|
||||
});
|
||||
app.assets.load(asset);
|
||||
});
|
||||
}
|
||||
|
||||
private async ensureCollision(): Promise<void> {
|
||||
if (this.collisionEntity) return;
|
||||
if (!this.collisionSource) {
|
||||
throw new Error("Слой коллизий отсутствует в манифесте локации.");
|
||||
}
|
||||
if (!this.collisionLoadPromise) {
|
||||
this.collisionLoadPromise = this.loadCollision(
|
||||
this.collisionSource.meshUrl,
|
||||
this.collisionSource.projectId,
|
||||
this.collisionSource.worldTransform,
|
||||
).finally(() => {
|
||||
this.collisionLoadPromise = null;
|
||||
});
|
||||
}
|
||||
await this.collisionLoadPromise;
|
||||
}
|
||||
|
||||
private unloadWorld(): void {
|
||||
this.controlRequestVersion += 1;
|
||||
this.ugvController?.disable();
|
||||
this.controlMode = "free";
|
||||
if (this.cameraController) this.cameraController.enabled = true;
|
||||
if (this.visualEntity) {
|
||||
this.visualEntity.destroy();
|
||||
this.visualEntity = null;
|
||||
}
|
||||
if (this.visualAsset && this.app) {
|
||||
this.app.assets.remove(this.visualAsset);
|
||||
this.visualAsset.unload();
|
||||
this.visualAsset = null;
|
||||
}
|
||||
if (this.collisionEntity) {
|
||||
this.collisionEntity.destroy();
|
||||
this.collisionEntity = null;
|
||||
}
|
||||
this.collisionVisuals = [];
|
||||
if (this.collisionAsset && this.app) {
|
||||
this.app.assets.remove(this.collisionAsset);
|
||||
this.collisionAsset.unload();
|
||||
this.collisionAsset = null;
|
||||
}
|
||||
this.collisionMaterial?.destroy();
|
||||
this.collisionMaterial = null;
|
||||
this.collisionSource = null;
|
||||
this.collisionLoadPromise = null;
|
||||
}
|
||||
}
|
||||
|
||||
function preventContextMenu(event: Event): void {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
function configureDracoDecoder(): void {
|
||||
if (WasmModule.getConfig("DracoDecoderModule")) return;
|
||||
WasmModule.setConfig("DracoDecoderModule", {
|
||||
glueUrl: `${DRACO_DECODER_ROOT}/draco_wasm_wrapper_gltf.js`,
|
||||
wasmUrl: `${DRACO_DECODER_ROOT}/draco_decoder_gltf.wasm`,
|
||||
numWorkers: 2,
|
||||
});
|
||||
}
|
||||
|
||||
function focusCanvas(event: Event): void {
|
||||
(event.currentTarget as HTMLCanvasElement | null)?.focus({ preventScroll: true });
|
||||
}
|
||||
|
||||
function configureCameraDrag(
|
||||
controller: CameraController,
|
||||
inversion: () => { horizontal: boolean; vertical: boolean },
|
||||
): void {
|
||||
// CameraControls 2.21.4 deliberately exposes one pinned input source here.
|
||||
// Keep its pointer-capture and fly/orbit behavior; only the user-selected axes change sign.
|
||||
const input = controller._desktopInput;
|
||||
if (!input) return;
|
||||
const read = input.read.bind(input);
|
||||
input.read = () => {
|
||||
const frame = read();
|
||||
const selected = inversion();
|
||||
if (selected.horizontal && frame.mouse.length > 0) frame.mouse[0] *= -1;
|
||||
if (selected.vertical && frame.mouse.length > 1) frame.mouse[1] *= -1;
|
||||
return frame;
|
||||
};
|
||||
}
|
||||
|
||||
function applyWorldTransform(entity: Entity, values: number[]): void {
|
||||
const transform = new Mat4().set(values);
|
||||
entity.setLocalPosition(transform.getTranslation());
|
||||
entity.setLocalEulerAngles(transform.getEulerAngles());
|
||||
entity.setLocalScale(transform.getScale());
|
||||
}
|
||||
@@ -0,0 +1,333 @@
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Button,
|
||||
Icon,
|
||||
IconButton,
|
||||
StatusBadge,
|
||||
} from "@nodedc/ui-react";
|
||||
|
||||
import type {
|
||||
SimulationProject,
|
||||
SimulationProjectStatus,
|
||||
SimulationUploadProgress,
|
||||
} from "../../core/simulation/projects";
|
||||
import { estimateSimulationProcessing } from "../../core/simulation/projects";
|
||||
|
||||
const ACTIVE_STATUSES = new Set<SimulationProjectStatus>([
|
||||
"uploading",
|
||||
"queued",
|
||||
"processing",
|
||||
"importing",
|
||||
]);
|
||||
|
||||
export function SimulationCatalog({
|
||||
projects,
|
||||
uploadProgress,
|
||||
loading,
|
||||
loadError,
|
||||
queueError,
|
||||
dropping,
|
||||
onCreate,
|
||||
onRetryLoad,
|
||||
onSelect,
|
||||
onEdit,
|
||||
onDelete,
|
||||
}: {
|
||||
projects: SimulationProject[];
|
||||
uploadProgress: ReadonlyMap<string, SimulationUploadProgress>;
|
||||
loading: boolean;
|
||||
loadError: string | null;
|
||||
queueError: string | null;
|
||||
dropping: boolean;
|
||||
onCreate: () => void;
|
||||
onRetryLoad: () => void;
|
||||
onSelect: (project: SimulationProject) => void;
|
||||
onEdit: (project: SimulationProject) => void;
|
||||
onDelete: (project: SimulationProject) => void;
|
||||
}) {
|
||||
const sourceBytes = projects.reduce(
|
||||
(sum, project) => sum + project.source.totalByteLength,
|
||||
0,
|
||||
);
|
||||
|
||||
return (
|
||||
<section
|
||||
className="simulation-catalog"
|
||||
data-dropping={dropping ? "true" : undefined}
|
||||
aria-label="Проекты симуляции"
|
||||
>
|
||||
<header className="simulation-catalog__summary">
|
||||
<div><span>Проектов</span><strong>{projects.length.toLocaleString("ru-RU")}</strong></div>
|
||||
<div><span>Визуал готов</span><strong>{countStatus(projects, "ready")}</strong></div>
|
||||
<div><span>В работе</span><strong>{projects.filter((project) => ACTIVE_STATUSES.has(project.status)).length}</strong></div>
|
||||
<div><span>Исходники</span><strong>{formatBytes(sourceBytes)}</strong></div>
|
||||
</header>
|
||||
|
||||
{queueError ? (
|
||||
<div className="simulation-catalog__notice" role="alert">
|
||||
<Icon name="alert" size={18} />
|
||||
<span>{queueError}</span>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{loading && projects.length === 0 ? (
|
||||
<div className="simulation-catalog__state" role="status">
|
||||
<ActivityIndicator label="Загрузка каталога" />
|
||||
<span>Читаем каталог сцен</span>
|
||||
</div>
|
||||
) : loadError && projects.length === 0 ? (
|
||||
<div className="simulation-catalog__state" role="alert">
|
||||
<Icon name="alert" size={20} />
|
||||
<div><strong>Каталог недоступен</strong><span>{loadError}</span></div>
|
||||
<Button size="compact" onClick={onRetryLoad}>Повторить</Button>
|
||||
</div>
|
||||
) : projects.length === 0 ? (
|
||||
<div className="simulation-catalog__empty">
|
||||
<span><Icon name="globe" size={20} /></span>
|
||||
<strong>Перетащите архивы в это окно</strong>
|
||||
<p>Каждый ZIP, RAR или 7z сразу станет отдельным проектом и встанет в очередь.</p>
|
||||
<Button variant="primary" icon={<Icon name="plus" size={16} />} onClick={onCreate}>
|
||||
Новый проект
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="simulation-catalog__table-wrap">
|
||||
<table className="simulation-catalog__table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Проект</th>
|
||||
<th>Источник</th>
|
||||
<th>Размер</th>
|
||||
<th>Сплаты</th>
|
||||
<th>Статус</th>
|
||||
<th>Обновлён</th>
|
||||
<th aria-label="Действия" />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{projects.map((project) => (
|
||||
<SimulationCatalogRow
|
||||
key={project.projectId}
|
||||
project={project}
|
||||
uploadProgress={uploadProgress.get(project.projectId)}
|
||||
onSelect={() => onSelect(project)}
|
||||
onEdit={() => onEdit(project)}
|
||||
onDelete={() => onDelete(project)}
|
||||
/>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{dropping ? (
|
||||
<div className="simulation-catalog__drop-overlay" role="status">
|
||||
<span className="simulation-catalog__drop-mark"><Icon name="plus" size={28} /></span>
|
||||
<strong>Отпустите архивы</strong>
|
||||
<span>ZIP · RAR · 7z · до 16 ГБ на проект</span>
|
||||
<small>Внутри — сцена LCC или LCC2 и связанные файлы</small>
|
||||
</div>
|
||||
) : null}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function SimulationCatalogRow({
|
||||
project,
|
||||
uploadProgress,
|
||||
onSelect,
|
||||
onEdit,
|
||||
onDelete,
|
||||
}: {
|
||||
project: SimulationProject;
|
||||
uploadProgress?: SimulationUploadProgress;
|
||||
onSelect: () => void;
|
||||
onEdit: () => void;
|
||||
onDelete: () => void;
|
||||
}) {
|
||||
const status = statusPresentation(project.status);
|
||||
const active = ACTIVE_STATUSES.has(project.status);
|
||||
return (
|
||||
<tr data-status={project.status}>
|
||||
<td>
|
||||
<button type="button" onClick={onSelect}>
|
||||
<span className="simulation-catalog__project-icon"><Icon name="globe" size={18} /></span>
|
||||
<span>
|
||||
<strong>{project.name}</strong>
|
||||
<small>{sourceFileLabel(project)}</small>
|
||||
</span>
|
||||
</button>
|
||||
</td>
|
||||
{active ? (
|
||||
<td colSpan={3}>
|
||||
<ProjectProgress project={project} localProgress={uploadProgress} />
|
||||
</td>
|
||||
) : (
|
||||
<>
|
||||
<td>
|
||||
{project.source.kind === "archive" ? "Архив" : "Папка"}
|
||||
<small>{project.source.files.length} файл(ов)</small>
|
||||
</td>
|
||||
<td>{formatBytes(project.source.totalByteLength)}</td>
|
||||
<td>—</td>
|
||||
</>
|
||||
)}
|
||||
<td><StatusBadge tone={status.tone}>{status.label}</StatusBadge></td>
|
||||
<td>{formatDate(project.updatedAtUtc)}</td>
|
||||
<td>
|
||||
<div className="simulation-catalog__actions">
|
||||
<IconButton label={`Удалить ${project.name}`} onClick={onDelete}>
|
||||
<Icon name="trash" size={16} />
|
||||
</IconButton>
|
||||
<IconButton label={`Редактировать ${project.name}`} onClick={onEdit}>
|
||||
<Icon name="edit" size={16} />
|
||||
</IconButton>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
function ProjectProgress({
|
||||
project,
|
||||
localProgress,
|
||||
}: {
|
||||
project: SimulationProject;
|
||||
localProgress?: SimulationUploadProgress;
|
||||
}) {
|
||||
const progress = progressPresentation(project, localProgress);
|
||||
return (
|
||||
<div
|
||||
className="simulation-catalog__progress"
|
||||
data-indeterminate={progress.indeterminate ? "true" : undefined}
|
||||
role="progressbar"
|
||||
aria-label={progress.label}
|
||||
aria-valuemin={0}
|
||||
aria-valuemax={100}
|
||||
aria-valuenow={progress.indeterminate ? undefined : progress.percent}
|
||||
>
|
||||
{progress.meta ? <small className="simulation-catalog__progress-meta">{progress.meta}</small> : null}
|
||||
<span className="simulation-catalog__progress-track">
|
||||
<i style={{ width: `${progress.percent}%` }} />
|
||||
</span>
|
||||
<small className="simulation-catalog__progress-stage">{progress.label}</small>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function progressPresentation(
|
||||
project: SimulationProject,
|
||||
local?: SimulationUploadProgress,
|
||||
): { percent: number; label: string; meta: string | null; indeterminate: boolean } {
|
||||
if (project.status === "uploading") {
|
||||
const uploaded = local?.uploadedBytes ?? project.source.uploadedByteLength;
|
||||
const total = local?.totalBytes ?? project.source.totalByteLength;
|
||||
const percent = total ? Math.min(100, uploaded / total * 100) : 0;
|
||||
return {
|
||||
percent,
|
||||
label: `${formatBytes(uploaded)} / ${formatBytes(total)}`,
|
||||
meta: progressMeta(percent, local?.estimatedSecondsRemaining),
|
||||
indeterminate: false,
|
||||
};
|
||||
}
|
||||
if (project.status === "queued") {
|
||||
return { percent: 0, label: "Ожидает обработки", meta: null, indeterminate: true };
|
||||
}
|
||||
const estimate = estimateSimulationProcessing(project);
|
||||
if (estimate) {
|
||||
return {
|
||||
percent: estimate.percent,
|
||||
label: processingLabel(project),
|
||||
meta: progressMeta(estimate.percent, estimate.estimatedSecondsRemaining),
|
||||
indeterminate: false,
|
||||
};
|
||||
}
|
||||
const completed = project.provider.progress?.completed_steps;
|
||||
const total = project.provider.progress?.total_steps;
|
||||
if (typeof completed === "number" && typeof total === "number" && total > 0) {
|
||||
const percent = Math.min(100, completed / total * 100);
|
||||
return {
|
||||
percent,
|
||||
label: processingLabel(project),
|
||||
meta: progressMeta(percent, null),
|
||||
indeterminate: false,
|
||||
};
|
||||
}
|
||||
return {
|
||||
percent: project.status === "importing" ? 96 : 0,
|
||||
label: processingLabel(project),
|
||||
meta: project.status === "importing" ? progressMeta(96, null) : null,
|
||||
indeterminate: project.status !== "importing",
|
||||
};
|
||||
}
|
||||
|
||||
function progressMeta(percent: number, seconds: number | null | undefined): string {
|
||||
const roundedPercent = Math.min(100, Math.max(0, Math.floor(percent)));
|
||||
return `Прогресс ${roundedPercent}% | ${remainingTimeLabel(seconds)}`;
|
||||
}
|
||||
|
||||
function remainingTimeLabel(seconds: number | null | undefined): string {
|
||||
if (seconds === null || seconds === undefined) return "Время уточняется";
|
||||
if (seconds <= 60) return "Осталось меньше минуты";
|
||||
const minutes = Math.ceil(seconds / 60);
|
||||
if (minutes < 60) return `Осталось примерно ${minutes} мин`;
|
||||
const hours = Math.floor(minutes / 60);
|
||||
const remainder = minutes % 60;
|
||||
return `Осталось примерно ${hours} ч${remainder ? ` ${remainder} мин` : ""}`;
|
||||
}
|
||||
|
||||
function processingLabel(project: SimulationProject): string {
|
||||
if (project.status === "importing") return "Подготавливаем сцену";
|
||||
const stage = project.provider.state;
|
||||
if (stage === "verifying_source") return "Проверяем исходник";
|
||||
if (stage === "inspecting") return "Анализируем локацию";
|
||||
if (stage === "building_preview") return "Подготавливаем локацию";
|
||||
if (stage === "building_streamed_sog") return "Собираем уровни детализации";
|
||||
if (stage === "building_collision") return "Собираем коллизии";
|
||||
return "Обрабатываем локацию";
|
||||
}
|
||||
|
||||
function statusPresentation(status: SimulationProjectStatus): {
|
||||
label: string;
|
||||
tone: "success" | "accent" | "warning" | "neutral";
|
||||
} {
|
||||
if (status === "ready") return { label: "Визуал готов", tone: "success" };
|
||||
if (status === "failed") return { label: "Ошибка", tone: "warning" };
|
||||
if (status === "uploading") return { label: "Загрузка", tone: "neutral" };
|
||||
if (status === "queued") return { label: "В очереди", tone: "neutral" };
|
||||
if (status === "importing") return { label: "Подготовка", tone: "accent" };
|
||||
return { label: "Обработка", tone: "accent" };
|
||||
}
|
||||
|
||||
function sourceFileLabel(project: SimulationProject): string {
|
||||
const first = project.source.files[0]?.logicalPath;
|
||||
if (project.source.kind === "archive" && first) return first;
|
||||
return `${sceneTypeLabel(project.sceneType)} · ${project.source.files.length} файл(ов)`;
|
||||
}
|
||||
|
||||
function sceneTypeLabel(value: SimulationProject["sceneType"]): string {
|
||||
if (value === "interior") return "Интерьер";
|
||||
if (value === "object") return "Объект";
|
||||
return "Улица";
|
||||
}
|
||||
|
||||
function countStatus(projects: SimulationProject[], status: SimulationProjectStatus): number {
|
||||
return projects.filter((project) => project.status === status).length;
|
||||
}
|
||||
|
||||
function formatDate(value: string): string {
|
||||
const date = new Date(value);
|
||||
return Number.isNaN(date.valueOf()) ? "—" : date.toLocaleString("ru-RU", {
|
||||
day: "2-digit",
|
||||
month: "short",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
});
|
||||
}
|
||||
|
||||
function formatBytes(value: number): string {
|
||||
if (value < 1024) return `${value} Б`;
|
||||
if (value < 1024 ** 2) return `${(value / 1024).toLocaleString("ru-RU", { maximumFractionDigits: 1 })} КБ`;
|
||||
if (value < 1024 ** 3) return `${(value / 1024 ** 2).toLocaleString("ru-RU", { maximumFractionDigits: 1 })} МБ`;
|
||||
return `${(value / 1024 ** 3).toLocaleString("ru-RU", { maximumFractionDigits: 2 })} ГБ`;
|
||||
}
|
||||
@@ -0,0 +1,263 @@
|
||||
import { useEffect, useMemo, useRef, useState, type DragEvent } from "react";
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Button,
|
||||
FieldFrame,
|
||||
Icon,
|
||||
Select,
|
||||
TextField,
|
||||
Window,
|
||||
WindowFooterActions,
|
||||
} from "@nodedc/ui-react";
|
||||
|
||||
import {
|
||||
createAndUploadSimulationProject,
|
||||
updateSimulationProject,
|
||||
type SimulationProject,
|
||||
type SimulationSceneType,
|
||||
type SimulationUploadCandidate,
|
||||
type SimulationUploadProgress,
|
||||
} from "../../core/simulation/projects";
|
||||
import {
|
||||
candidatesFromDrop,
|
||||
validateSimulationCandidates,
|
||||
} from "../../core/simulation/sourceFiles";
|
||||
|
||||
export function SimulationProjectWindow({
|
||||
open,
|
||||
project,
|
||||
onClose,
|
||||
onSaved,
|
||||
}: {
|
||||
open: boolean;
|
||||
project: SimulationProject | null;
|
||||
onClose: () => void;
|
||||
onSaved: (project: SimulationProject) => void;
|
||||
}) {
|
||||
const archiveInput = useRef<HTMLInputElement>(null);
|
||||
const folderInput = useRef<HTMLInputElement>(null);
|
||||
const [name, setName] = useState("");
|
||||
const [sceneType, setSceneType] = useState<SimulationSceneType>("outdoor");
|
||||
const [candidates, setCandidates] = useState<SimulationUploadCandidate[]>([]);
|
||||
const [dragging, setDragging] = useState(false);
|
||||
const [pending, setPending] = useState(false);
|
||||
const [progress, setProgress] = useState<SimulationUploadProgress | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
setName(project?.name ?? "");
|
||||
setSceneType(project?.sceneType ?? "outdoor");
|
||||
setCandidates([]);
|
||||
setProgress(null);
|
||||
setPending(false);
|
||||
setError(null);
|
||||
}, [open, project]);
|
||||
|
||||
const sourceSummary = useMemo(() => summarizeCandidates(candidates), [candidates]);
|
||||
const canSubmit = name.trim().length > 0 && (project !== null || candidates.length > 0) && !pending;
|
||||
|
||||
const acceptCandidates = (next: SimulationUploadCandidate[]) => {
|
||||
try {
|
||||
validateSimulationCandidates(next);
|
||||
setCandidates(next);
|
||||
setError(null);
|
||||
if (!name.trim() && next.length) {
|
||||
const named = next.find((candidate) => /\.lcc2?$/i.test(candidate.logicalPath)) ?? next[0];
|
||||
const first = named?.logicalPath.split("/").filter(Boolean).at(-1) ?? "Новая сцена";
|
||||
setName(first.replace(/\.(lcc2?|zip|rar|7z)$/i, ""));
|
||||
}
|
||||
} catch (caught) {
|
||||
setCandidates([]);
|
||||
setError(caught instanceof Error ? caught.message : "Источник не принят.");
|
||||
}
|
||||
};
|
||||
|
||||
const submit = async () => {
|
||||
if (!canSubmit) return;
|
||||
setPending(true);
|
||||
setError(null);
|
||||
try {
|
||||
const saved = project
|
||||
? await updateSimulationProject(project.projectId, name, sceneType)
|
||||
: await createAndUploadSimulationProject(
|
||||
name,
|
||||
sceneType,
|
||||
candidates,
|
||||
setProgress,
|
||||
);
|
||||
onSaved(saved);
|
||||
} catch (caught) {
|
||||
setError(caught instanceof Error ? caught.message : "Не удалось сохранить проект.");
|
||||
setPending(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDrop = async (event: DragEvent<HTMLDivElement>) => {
|
||||
event.preventDefault();
|
||||
setDragging(false);
|
||||
try {
|
||||
acceptCandidates(await candidatesFromDrop(event.dataTransfer));
|
||||
} catch (caught) {
|
||||
setError(caught instanceof Error ? caught.message : "Не удалось прочитать источник.");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Window
|
||||
open={open}
|
||||
title={project ? "Редактировать проект" : "Новый проект симуляции"}
|
||||
subtitle={project ? "Метаданные сцены" : "LCC/LCC2 folder или ZIP/RAR/7z"}
|
||||
size="md"
|
||||
closeOnBackdrop={!pending}
|
||||
closeOnEscape={!pending}
|
||||
onClose={onClose}
|
||||
footer={(
|
||||
<>
|
||||
<span className="simulation-project-window__footer-state">
|
||||
{pending && project ? <><ActivityIndicator /><span>Сохраняем</span></> : null}
|
||||
</span>
|
||||
<WindowFooterActions>
|
||||
<Button disabled={pending} onClick={onClose}>Отмена</Button>
|
||||
<Button variant="primary" disabled={!canSubmit} onClick={() => void submit()}>
|
||||
{project ? "Сохранить" : "Создать и запустить"}
|
||||
</Button>
|
||||
</WindowFooterActions>
|
||||
</>
|
||||
)}
|
||||
>
|
||||
<div className="simulation-project-window">
|
||||
<TextField
|
||||
label="Название"
|
||||
value={name}
|
||||
maxLength={120}
|
||||
disabled={pending}
|
||||
autoComplete="off"
|
||||
onChange={(event) => setName(event.currentTarget.value)}
|
||||
/>
|
||||
<FieldFrame label="Тип сцены" description="Сохраняется как профиль будущей физики и навигации.">
|
||||
<Select
|
||||
label="Тип сцены"
|
||||
value={sceneType}
|
||||
disabled={pending}
|
||||
onChange={(value) => setSceneType(value)}
|
||||
options={[
|
||||
{ value: "outdoor", label: "Улица" },
|
||||
{ value: "interior", label: "Интерьер" },
|
||||
{ value: "object", label: "Отдельный объект" },
|
||||
]}
|
||||
/>
|
||||
</FieldFrame>
|
||||
{!project ? (
|
||||
<FieldFrame
|
||||
label="Исходник"
|
||||
description="Имя родительской папки не фиксировано. Внутри должна быть ровно одна сцена LCC или LCC2."
|
||||
>
|
||||
<div
|
||||
className="simulation-source-drop"
|
||||
data-dragging={dragging ? "true" : undefined}
|
||||
onDragEnter={(event) => { event.preventDefault(); setDragging(true); }}
|
||||
onDragOver={(event) => event.preventDefault()}
|
||||
onDragLeave={(event) => {
|
||||
if (!event.currentTarget.contains(event.relatedTarget as Node | null)) setDragging(false);
|
||||
}}
|
||||
onDrop={(event) => void handleDrop(event)}
|
||||
>
|
||||
<Icon name={candidates.length ? "check" : "upload"} size={20} />
|
||||
<strong>{sourceSummary.title}</strong>
|
||||
<span>{sourceSummary.detail}</span>
|
||||
<div>
|
||||
<Button
|
||||
size="compact"
|
||||
icon={<Icon name="file" size={16} />}
|
||||
disabled={pending}
|
||||
onClick={() => archiveInput.current?.click()}
|
||||
>
|
||||
Выбрать архив
|
||||
</Button>
|
||||
<Button
|
||||
size="compact"
|
||||
icon={<Icon name="folder" size={16} />}
|
||||
disabled={pending}
|
||||
onClick={() => folderInput.current?.click()}
|
||||
>
|
||||
Выбрать папку
|
||||
</Button>
|
||||
</div>
|
||||
<input
|
||||
ref={archiveInput}
|
||||
type="file"
|
||||
accept=".zip,.rar,.7z"
|
||||
hidden
|
||||
onChange={(event) => {
|
||||
const file = event.currentTarget.files?.[0];
|
||||
if (file) acceptCandidates([{ file, logicalPath: file.name }]);
|
||||
event.currentTarget.value = "";
|
||||
}}
|
||||
/>
|
||||
<input
|
||||
ref={folderInput}
|
||||
type="file"
|
||||
hidden
|
||||
multiple
|
||||
{...({ webkitdirectory: "" } as Record<string, string>)}
|
||||
onChange={(event) => {
|
||||
const next = Array.from(event.currentTarget.files ?? []).map((file) => ({
|
||||
file,
|
||||
logicalPath: file.webkitRelativePath || file.name,
|
||||
}));
|
||||
if (next.length) acceptCandidates(next);
|
||||
event.currentTarget.value = "";
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</FieldFrame>
|
||||
) : null}
|
||||
{progress ? (
|
||||
<div className="simulation-upload-progress" role="status">
|
||||
<span><i style={{ width: `${Math.min(100, progress.totalBytes ? progress.uploadedBytes / progress.totalBytes * 100 : 0)}%` }} /></span>
|
||||
<div>
|
||||
<strong>
|
||||
{formatBytes(progress.uploadedBytes)} / {formatBytes(progress.totalBytes)}
|
||||
{" · "}{formatUploadEta(progress.estimatedSecondsRemaining)}
|
||||
</strong>
|
||||
<small>{progress.currentPath}</small>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
{error ? <p className="simulation-project-window__error" role="alert">{error}</p> : null}
|
||||
</div>
|
||||
</Window>
|
||||
);
|
||||
}
|
||||
|
||||
function formatUploadEta(seconds: number | null): string {
|
||||
if (seconds === 0) return "загружено";
|
||||
if (seconds === null) return "считаем время";
|
||||
if (seconds < 60) return `осталось ≈ ${Math.max(1, seconds)} сек`;
|
||||
const minutes = Math.ceil(seconds / 60);
|
||||
if (minutes < 60) return `осталось ≈ ${minutes} мин`;
|
||||
const hours = Math.floor(minutes / 60);
|
||||
const remainder = minutes % 60;
|
||||
return `осталось ≈ ${hours} ч${remainder ? ` ${remainder} мин` : ""}`;
|
||||
}
|
||||
|
||||
function summarizeCandidates(candidates: SimulationUploadCandidate[]): { title: string; detail: string } {
|
||||
if (!candidates.length) return {
|
||||
title: "Перетащите архив или папку сюда",
|
||||
detail: "ZIP, RAR, 7z либо parent folder с LCC/LCC2",
|
||||
};
|
||||
const total = candidates.reduce((sum, candidate) => sum + candidate.file.size, 0);
|
||||
const archive = candidates.length === 1 && /\.(zip|rar|7z)$/i.test(candidates[0]?.logicalPath ?? "");
|
||||
return {
|
||||
title: archive ? candidates[0]?.logicalPath ?? "Архив" : "Папка принята",
|
||||
detail: `${candidates.length.toLocaleString("ru-RU")} файл(ов) · ${formatBytes(total)}`,
|
||||
};
|
||||
}
|
||||
|
||||
function formatBytes(value: number): string {
|
||||
if (value < 1024) return `${value} Б`;
|
||||
if (value < 1024 ** 2) return `${(value / 1024).toLocaleString("ru-RU", { maximumFractionDigits: 1 })} КБ`;
|
||||
if (value < 1024 ** 3) return `${(value / 1024 ** 2).toLocaleString("ru-RU", { maximumFractionDigits: 1 })} МБ`;
|
||||
return `${(value / 1024 ** 3).toLocaleString("ru-RU", { maximumFractionDigits: 2 })} ГБ`;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,191 @@
|
||||
import {
|
||||
Button,
|
||||
Icon,
|
||||
RangeControl,
|
||||
Switch,
|
||||
TextField,
|
||||
} from "@nodedc/ui-react";
|
||||
|
||||
import {
|
||||
SIMULATION_UGV_EXACT_VALUE_LIMITS,
|
||||
type SimulationUgvPreset,
|
||||
} from "../../core/simulation/projects";
|
||||
|
||||
export type SimulationUgvPresetSaveState = "idle" | "saving" | "saved" | "failed";
|
||||
|
||||
export function SimulationUgvSettingsPanel({
|
||||
value,
|
||||
disabled,
|
||||
saveState,
|
||||
onChange,
|
||||
onSave,
|
||||
}: {
|
||||
value: SimulationUgvPreset;
|
||||
disabled: boolean;
|
||||
saveState: SimulationUgvPresetSaveState;
|
||||
onChange: (preset: SimulationUgvPreset) => void;
|
||||
onSave: () => void;
|
||||
}) {
|
||||
const dimensions = value.dimensionsMeters;
|
||||
const updateDimensions = (
|
||||
patch: Partial<SimulationUgvPreset["dimensionsMeters"]>,
|
||||
) => {
|
||||
const next = { ...dimensions, ...patch };
|
||||
next.groundClearance = Math.max(
|
||||
SIMULATION_UGV_EXACT_VALUE_LIMITS.groundClearance.min,
|
||||
Math.min(next.groundClearance, roundToHundredth(next.height - 0.05)),
|
||||
);
|
||||
onChange({ ...value, dimensionsMeters: next });
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="simulation-viewport__settings-group simulation-ugv-settings">
|
||||
<div className="simulation-viewport__settings-group-head">
|
||||
<strong>Физика UGV</strong>
|
||||
<span>Дифференциальный привод</span>
|
||||
</div>
|
||||
<p>
|
||||
A/D без W/S разворачивают UGV вокруг собственного центра. Скорость и
|
||||
динамика применяются сразу. Мышь вращает камеру вокруг машины, колесо
|
||||
меняет дистанцию; после паузы камера плавно возвращается назад. Ползунок
|
||||
работает в штатном диапазоне, точное значение можно ввести вручную.
|
||||
</p>
|
||||
<TextField
|
||||
label="Название пресета"
|
||||
value={value.presetName}
|
||||
maxLength={80}
|
||||
disabled={disabled}
|
||||
spellCheck={false}
|
||||
onChange={(event) => onChange({ ...value, presetName: event.currentTarget.value })}
|
||||
/>
|
||||
<RangeControl
|
||||
label="Масса конструкции"
|
||||
value={value.massKg}
|
||||
min={10}
|
||||
max={500}
|
||||
exactValueBounds={SIMULATION_UGV_EXACT_VALUE_LIMITS.massKg}
|
||||
step={5}
|
||||
disabled={disabled}
|
||||
formatValue={(mass) => `${Math.round(mass)} кг`}
|
||||
onChange={(massKg) => onChange({ ...value, massKg })}
|
||||
/>
|
||||
<div className="simulation-ugv-settings__subhead">
|
||||
<strong>Габариты</strong>
|
||||
<span>В метрах</span>
|
||||
</div>
|
||||
<RangeControl
|
||||
label="Длина"
|
||||
value={dimensions.length}
|
||||
min={0.4}
|
||||
max={3}
|
||||
exactValueBounds={SIMULATION_UGV_EXACT_VALUE_LIMITS.length}
|
||||
step={0.05}
|
||||
disabled={disabled}
|
||||
formatValue={formatMeters}
|
||||
onChange={(length) => updateDimensions({ length })}
|
||||
/>
|
||||
<RangeControl
|
||||
label="Ширина"
|
||||
value={dimensions.width}
|
||||
min={0.3}
|
||||
max={2}
|
||||
exactValueBounds={SIMULATION_UGV_EXACT_VALUE_LIMITS.width}
|
||||
step={0.05}
|
||||
disabled={disabled}
|
||||
formatValue={formatMeters}
|
||||
onChange={(width) => updateDimensions({ width })}
|
||||
/>
|
||||
<RangeControl
|
||||
label="Общая высота"
|
||||
value={dimensions.height}
|
||||
min={0.2}
|
||||
max={2}
|
||||
exactValueBounds={SIMULATION_UGV_EXACT_VALUE_LIMITS.height}
|
||||
step={0.05}
|
||||
disabled={disabled}
|
||||
formatValue={formatMeters}
|
||||
onChange={(height) => updateDimensions({ height })}
|
||||
/>
|
||||
<RangeControl
|
||||
label="Клиренс"
|
||||
value={dimensions.groundClearance}
|
||||
min={0.03}
|
||||
max={Math.max(0.03, Math.min(0.6, dimensions.height - 0.05))}
|
||||
exactValueBounds={{
|
||||
min: SIMULATION_UGV_EXACT_VALUE_LIMITS.groundClearance.min,
|
||||
max: Math.min(
|
||||
SIMULATION_UGV_EXACT_VALUE_LIMITS.groundClearance.max,
|
||||
dimensions.height - 0.05,
|
||||
),
|
||||
}}
|
||||
step={0.01}
|
||||
disabled={disabled}
|
||||
formatValue={formatMeters}
|
||||
onChange={(groundClearance) => updateDimensions({ groundClearance })}
|
||||
/>
|
||||
<div className="simulation-ugv-settings__subhead">
|
||||
<strong>Динамика</strong>
|
||||
<span>Ограничители движения</span>
|
||||
</div>
|
||||
<RangeControl
|
||||
label="Скорость движения"
|
||||
value={value.maxSpeedMetersPerSecond}
|
||||
min={0.1}
|
||||
max={8}
|
||||
exactValueBounds={SIMULATION_UGV_EXACT_VALUE_LIMITS.maxSpeedMetersPerSecond}
|
||||
step={0.1}
|
||||
disabled={disabled}
|
||||
formatValue={(speed) => `${formatDecimal(speed, 1)} м/с`}
|
||||
onChange={(maxSpeedMetersPerSecond) => onChange({
|
||||
...value,
|
||||
maxSpeedMetersPerSecond,
|
||||
})}
|
||||
/>
|
||||
<RangeControl
|
||||
label="Скорость разворота"
|
||||
value={value.maxTurnRateDegrees}
|
||||
min={5}
|
||||
max={180}
|
||||
exactValueBounds={SIMULATION_UGV_EXACT_VALUE_LIMITS.maxTurnRateDegrees}
|
||||
step={5}
|
||||
disabled={disabled}
|
||||
formatValue={(rate) => `${Math.round(rate)}°/с`}
|
||||
onChange={(maxTurnRateDegrees) => onChange({ ...value, maxTurnRateDegrees })}
|
||||
/>
|
||||
<Switch
|
||||
checked={value.invertSteering}
|
||||
disabled={disabled}
|
||||
label="Инверсия управления A/D"
|
||||
onChange={(invertSteering) => onChange({ ...value, invertSteering })}
|
||||
/>
|
||||
<Button
|
||||
className="simulation-ugv-settings__save"
|
||||
size="compact"
|
||||
variant="secondary"
|
||||
icon={<Icon name="save" size={16} />}
|
||||
disabled={disabled || saveState === "saving" || value.presetName.trim() === ""}
|
||||
onClick={onSave}
|
||||
>
|
||||
{saveState === "saving"
|
||||
? "Сохраняем…"
|
||||
: saveState === "saved"
|
||||
? "Пресет сохранён"
|
||||
: saveState === "failed"
|
||||
? "Повторить сохранение"
|
||||
: "Сохранить пресет"}
|
||||
</Button>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function formatMeters(value: number): string {
|
||||
return `${formatDecimal(value, 2)} м`;
|
||||
}
|
||||
|
||||
function formatDecimal(value: number, digits: number): string {
|
||||
return value.toFixed(digits).replace(".", ",");
|
||||
}
|
||||
|
||||
function roundToHundredth(value: number): number {
|
||||
return Math.round(value * 100) / 100;
|
||||
}
|
||||
@@ -0,0 +1,591 @@
|
||||
import { useEffect, useId, useRef, useState } from "react";
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Button,
|
||||
GlassMaterialSurface,
|
||||
Icon,
|
||||
IconButton,
|
||||
SegmentedControl,
|
||||
Select,
|
||||
StatusBadge,
|
||||
Switch,
|
||||
TextField,
|
||||
} from "@nodedc/ui-react";
|
||||
|
||||
import {
|
||||
saveSimulationViewerSettings,
|
||||
type SimulationEulerRotation,
|
||||
type SimulationProject,
|
||||
type SimulationUgvPreset,
|
||||
type SimulationViewerSettings,
|
||||
} from "../../core/simulation/projects";
|
||||
import {
|
||||
createLatestAsyncCommitter,
|
||||
type LatestAsyncCommitter,
|
||||
} from "../../core/runtime/latestAsyncCommitter";
|
||||
import {
|
||||
PlayCanvasRuntime,
|
||||
playCanvasEulerTransform,
|
||||
type SimulationControlMode,
|
||||
type SimulationQuality,
|
||||
type SimulationViewMode,
|
||||
} from "./PlayCanvasRuntime";
|
||||
import {
|
||||
SimulationUgvSettingsPanel,
|
||||
type SimulationUgvPresetSaveState,
|
||||
} from "./SimulationUgvSettingsPanel";
|
||||
|
||||
const ROTATION_AXES = ["x", "y", "z"] as const;
|
||||
|
||||
export function SimulationViewport({
|
||||
project,
|
||||
onProjectChange,
|
||||
}: {
|
||||
project: SimulationProject;
|
||||
onProjectChange?: (project: SimulationProject) => void;
|
||||
}) {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
const runtimeRef = useRef<PlayCanvasRuntime | null>(null);
|
||||
const viewerSettingsRef = useRef(project.viewerSettings);
|
||||
const settingsCommitterRef = useRef<LatestAsyncCommitter<SimulationViewerSettings> | null>(null);
|
||||
const settingsCommitSucceededRef = useRef(true);
|
||||
const ugvSaveRequestRef = useRef(0);
|
||||
const controlRequestRef = useRef(0);
|
||||
const settingsId = useId();
|
||||
const [state, setState] = useState<"mounting" | "loading" | "ready" | "failed">("mounting");
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [viewMode, setViewMode] = useState<SimulationViewMode>("visual");
|
||||
const [controlMode, setControlMode] = useState<SimulationControlMode>("free");
|
||||
const [controlState, setControlState] = useState<"idle" | "loading" | "ready" | "failed">("idle");
|
||||
const [quality, setQuality] = useState<SimulationQuality>(project.viewerSettings.quality);
|
||||
const [collisionState, setCollisionState] = useState<"idle" | "loading" | "ready" | "failed">("idle");
|
||||
const [settingsOpen, setSettingsOpen] = useState(false);
|
||||
const [visualRotation, setVisualRotation] = useState({
|
||||
...project.viewerSettings.visual.rotationDegrees,
|
||||
});
|
||||
const [collisionRotation, setCollisionRotation] = useState({
|
||||
...project.viewerSettings.collision.rotationDegrees,
|
||||
});
|
||||
const [horizontalInverted, setHorizontalInverted] = useState(
|
||||
project.viewerSettings.camera.invertHorizontal,
|
||||
);
|
||||
const [verticalInverted, setVerticalInverted] = useState(
|
||||
project.viewerSettings.camera.invertVertical,
|
||||
);
|
||||
const [ugvDraft, setUgvDraft] = useState<SimulationUgvPreset>(() =>
|
||||
cloneUgvPreset(project.viewerSettings.ugv),
|
||||
);
|
||||
const [ugvSaveState, setUgvSaveState] = useState<SimulationUgvPresetSaveState>("idle");
|
||||
const [settingsError, setSettingsError] = useState<string | null>(null);
|
||||
const worldManifestRevision = JSON.stringify(project.worldManifest);
|
||||
|
||||
useEffect(() => {
|
||||
const settings = project.viewerSettings;
|
||||
viewerSettingsRef.current = settings;
|
||||
setQuality(settings.quality);
|
||||
setVisualRotation({ ...settings.visual.rotationDegrees });
|
||||
setCollisionRotation({ ...settings.collision.rotationDegrees });
|
||||
setHorizontalInverted(settings.camera.invertHorizontal);
|
||||
setVerticalInverted(settings.camera.invertVertical);
|
||||
setUgvDraft(cloneUgvPreset(settings.ugv));
|
||||
setUgvSaveState("idle");
|
||||
ugvSaveRequestRef.current += 1;
|
||||
setSettingsError(null);
|
||||
setSettingsOpen(false);
|
||||
controlRequestRef.current += 1;
|
||||
setControlMode("free");
|
||||
setControlState("idle");
|
||||
}, [project.projectId]);
|
||||
|
||||
useEffect(() => {
|
||||
let active = true;
|
||||
const committer = createLatestAsyncCommitter<SimulationViewerSettings>({
|
||||
async commit(settings) {
|
||||
try {
|
||||
const saved = await saveSimulationViewerSettings(project.projectId, settings);
|
||||
settingsCommitSucceededRef.current = true;
|
||||
if (active) onProjectChange?.(saved);
|
||||
return true;
|
||||
} catch (caught) {
|
||||
settingsCommitSucceededRef.current = false;
|
||||
if (active) {
|
||||
setSettingsError(
|
||||
caught instanceof Error ? caught.message : "Не удалось сохранить настройки сцены.",
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
},
|
||||
onSettled({ applied, superseded }) {
|
||||
if (active && applied && !superseded) setSettingsError(null);
|
||||
},
|
||||
});
|
||||
settingsCommitterRef.current = committer;
|
||||
return () => {
|
||||
active = false;
|
||||
committer.dispose();
|
||||
if (settingsCommitterRef.current === committer) settingsCommitterRef.current = null;
|
||||
};
|
||||
}, [onProjectChange, project.projectId]);
|
||||
|
||||
useEffect(() => {
|
||||
const canvas = canvasRef.current;
|
||||
const manifest = project.worldManifest;
|
||||
if (!canvas || !manifest) return;
|
||||
const runtime = new PlayCanvasRuntime();
|
||||
runtimeRef.current = runtime;
|
||||
let cancelled = false;
|
||||
setState("mounting");
|
||||
setError(null);
|
||||
void runtime.mount(canvas).then(async () => {
|
||||
if (cancelled) return;
|
||||
const settings = viewerSettingsRef.current;
|
||||
runtime.setQuality(settings.quality);
|
||||
runtime.setCameraInversion("horizontal", settings.camera.invertHorizontal);
|
||||
runtime.setCameraInversion("vertical", settings.camera.invertVertical);
|
||||
runtime.setUgvPreset(settings.ugv);
|
||||
setState("loading");
|
||||
await runtime.loadWorld(manifest);
|
||||
runtime.setLayerWorldTransform(
|
||||
"visual",
|
||||
playCanvasEulerTransform(settings.visual.rotationDegrees),
|
||||
);
|
||||
runtime.setLayerWorldTransform(
|
||||
"collision",
|
||||
playCanvasEulerTransform(settings.collision.rotationDegrees),
|
||||
);
|
||||
if (!cancelled) setState("ready");
|
||||
}).catch((caught: unknown) => {
|
||||
if (cancelled) return;
|
||||
setState("failed");
|
||||
setError(caught instanceof Error ? caught.message : "Не удалось открыть сцену.");
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
controlRequestRef.current += 1;
|
||||
runtime.dispose();
|
||||
runtimeRef.current = null;
|
||||
};
|
||||
// Saving viewer preferences returns a fresh project object. Restart the GPU
|
||||
// runtime only when the manifest content changes, not when its object identity
|
||||
// changes after an otherwise unrelated settings PUT.
|
||||
}, [project.projectId, worldManifestRevision]);
|
||||
|
||||
const collisionAvailable = project.worldManifest?.collision.available ?? false;
|
||||
const commitViewerSettings = (settings: SimulationViewerSettings) => {
|
||||
viewerSettingsRef.current = settings;
|
||||
settingsCommitSucceededRef.current = false;
|
||||
settingsCommitterRef.current?.enqueue(settings);
|
||||
};
|
||||
|
||||
const changeUgvDraft = (next: SimulationUgvPreset) => {
|
||||
setUgvDraft(cloneUgvPreset(next));
|
||||
setUgvSaveState("idle");
|
||||
ugvSaveRequestRef.current += 1;
|
||||
runtimeRef.current?.setUgvPreset(next);
|
||||
};
|
||||
|
||||
const saveUgvPreset = () => {
|
||||
const committer = settingsCommitterRef.current;
|
||||
if (!committer) {
|
||||
setUgvSaveState("failed");
|
||||
return;
|
||||
}
|
||||
const request = ++ugvSaveRequestRef.current;
|
||||
const normalized = cloneUgvPreset({
|
||||
...ugvDraft,
|
||||
presetName: ugvDraft.presetName.trim(),
|
||||
});
|
||||
setUgvDraft(normalized);
|
||||
setUgvSaveState("saving");
|
||||
setSettingsError(null);
|
||||
settingsCommitSucceededRef.current = false;
|
||||
const settings = { ...viewerSettingsRef.current, ugv: normalized };
|
||||
viewerSettingsRef.current = settings;
|
||||
committer.enqueue(settings);
|
||||
void committer.waitForIdle().then(() => {
|
||||
if (request !== ugvSaveRequestRef.current) return;
|
||||
setUgvSaveState(settingsCommitSucceededRef.current ? "saved" : "failed");
|
||||
});
|
||||
};
|
||||
|
||||
const changeControlMode = (next: SimulationControlMode) => {
|
||||
const runtime = runtimeRef.current;
|
||||
if (!runtime) return;
|
||||
const request = ++controlRequestRef.current;
|
||||
setControlMode(next);
|
||||
setControlState(next === "ugv" ? "loading" : "idle");
|
||||
if (next === "ugv" && collisionState !== "ready") setCollisionState("loading");
|
||||
void runtime.setControlMode(next).then(() => {
|
||||
if (request !== controlRequestRef.current) return;
|
||||
if (next === "ugv") {
|
||||
setControlState("ready");
|
||||
setCollisionState("ready");
|
||||
}
|
||||
}).catch((caught: unknown) => {
|
||||
if (request !== controlRequestRef.current) return;
|
||||
setControlMode("free");
|
||||
setControlState("failed");
|
||||
setCollisionState("failed");
|
||||
setError(caught instanceof Error ? caught.message : "Не удалось запустить UGV.");
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<section
|
||||
className="simulation-viewport"
|
||||
data-control-mode={controlMode}
|
||||
aria-label={`Сцена ${project.name}`}
|
||||
>
|
||||
<header className="simulation-viewport__toolbar">
|
||||
<div>
|
||||
<StatusBadge tone={state === "failed" || collisionState === "failed" ? "warning" : state === "ready" && collisionState !== "loading" ? "success" : "accent"}>
|
||||
{controlState === "loading"
|
||||
? "Подготовка UGV"
|
||||
: controlState === "ready"
|
||||
? "UGV готов"
|
||||
: controlState === "failed"
|
||||
? "Ошибка UGV"
|
||||
: collisionState === "loading"
|
||||
? "Загрузка коллизий"
|
||||
: collisionState === "failed"
|
||||
? "Ошибка коллизий"
|
||||
: state === "ready"
|
||||
? "Runtime готов"
|
||||
: state === "failed"
|
||||
? "Ошибка runtime"
|
||||
: "Загрузка локации"}
|
||||
</StatusBadge>
|
||||
<span>Engine 2.21.4</span>
|
||||
</div>
|
||||
<div className="simulation-viewport__controls">
|
||||
<Select
|
||||
label="Качество Gaussian-сцены"
|
||||
value={quality}
|
||||
onChange={(next) => {
|
||||
setQuality(next);
|
||||
runtimeRef.current?.setQuality(next);
|
||||
commitViewerSettings({ ...viewerSettingsRef.current, quality: next });
|
||||
}}
|
||||
options={[
|
||||
{ value: "maximum", label: "Максимум", description: "До 4 млн сплатов · Retina до 2× · 60 FPS" },
|
||||
{ value: "ultra", label: "Ультра", description: "До 2,5 млн · Retina до 1,5× · 60 FPS" },
|
||||
{ value: "high", label: "Высокое", description: "До 1,2 млн · Retina до 1,25× · 60 FPS" },
|
||||
{ value: "medium", label: "Среднее", description: "До 700 тыс. · 1× · 45 FPS" },
|
||||
{ value: "low", label: "Низкое", description: "До 350 тыс. · 0,75× · 30 FPS" },
|
||||
]}
|
||||
minMenuWidth={230}
|
||||
menuWidth={230}
|
||||
/>
|
||||
<Select
|
||||
label="Режим управления"
|
||||
value={controlMode}
|
||||
disabled={state !== "ready" || controlState === "loading"}
|
||||
onChange={changeControlMode}
|
||||
options={[
|
||||
{
|
||||
value: "free",
|
||||
label: "Свободно",
|
||||
description: "Свободная камера и навигация",
|
||||
},
|
||||
{
|
||||
value: "ugv",
|
||||
label: "UGV",
|
||||
description: "WASD/стрелки · пробел — тормоз · R — домой",
|
||||
disabled: !collisionAvailable,
|
||||
},
|
||||
]}
|
||||
minMenuWidth={250}
|
||||
menuWidth={250}
|
||||
/>
|
||||
<SegmentedControl
|
||||
label="Слой сцены"
|
||||
value={viewMode}
|
||||
onChange={(next) => {
|
||||
setViewMode(next);
|
||||
const runtime = runtimeRef.current;
|
||||
if (!runtime) return;
|
||||
if (next !== "visual" && collisionAvailable && collisionState !== "ready") {
|
||||
setCollisionState("loading");
|
||||
}
|
||||
void runtime.setViewMode(next).then(() => {
|
||||
if (next !== "visual") setCollisionState("ready");
|
||||
}).catch((caught: unknown) => {
|
||||
setCollisionState("failed");
|
||||
setError(caught instanceof Error ? caught.message : "Не удалось открыть слой коллизий.");
|
||||
});
|
||||
}}
|
||||
items={[
|
||||
{ value: "visual", label: "Визуал" },
|
||||
{ value: "collision", label: "Коллизии", disabled: !collisionAvailable },
|
||||
{ value: "combined", label: "Вместе", disabled: !collisionAvailable },
|
||||
]}
|
||||
/>
|
||||
<Button size="compact" variant="secondary" onClick={() => runtimeRef.current?.home()}>
|
||||
Домой
|
||||
</Button>
|
||||
<div className="simulation-viewport__settings-anchor">
|
||||
<IconButton
|
||||
label="Настройки сцены"
|
||||
aria-controls={settingsId}
|
||||
aria-expanded={settingsOpen}
|
||||
aria-pressed={settingsOpen}
|
||||
onClick={() => setSettingsOpen((open) => !open)}
|
||||
>
|
||||
<Icon name="settings" size={18} />
|
||||
</IconButton>
|
||||
{settingsOpen ? (
|
||||
<GlassMaterialSurface
|
||||
id={settingsId}
|
||||
className="simulation-viewport__settings"
|
||||
role="dialog"
|
||||
aria-label="Настройки сцены"
|
||||
>
|
||||
<div className="simulation-viewport__settings-head">
|
||||
<div>
|
||||
<strong>Настройки сцены</strong>
|
||||
<span>Локация, камера и UGV</span>
|
||||
</div>
|
||||
<IconButton label="Закрыть настройки" onClick={() => setSettingsOpen(false)}>
|
||||
<Icon name="close" size={16} />
|
||||
</IconButton>
|
||||
</div>
|
||||
<div className="simulation-viewport__settings-scroll">
|
||||
<section className="simulation-viewport__settings-group">
|
||||
<div className="simulation-viewport__settings-group-head">
|
||||
<strong>Система координат</strong>
|
||||
<span>Мир сцены · Y вверх</span>
|
||||
</div>
|
||||
<p>
|
||||
Коррекция слоёв не меняет координаты камеры, навигации и будущей физики.
|
||||
</p>
|
||||
<div className="simulation-viewport__settings-rotations">
|
||||
<LayerRotationFields
|
||||
label="Гауссовый слой"
|
||||
value={visualRotation}
|
||||
disabled={state !== "ready"}
|
||||
onChange={(next) => {
|
||||
setVisualRotation(next);
|
||||
runtimeRef.current?.setLayerWorldTransform(
|
||||
"visual",
|
||||
playCanvasEulerTransform(next),
|
||||
);
|
||||
commitViewerSettings({
|
||||
...viewerSettingsRef.current,
|
||||
visual: { rotationDegrees: next },
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<LayerRotationFields
|
||||
label="Слой коллизий"
|
||||
value={collisionRotation}
|
||||
disabled={state !== "ready"}
|
||||
onChange={(next) => {
|
||||
setCollisionRotation(next);
|
||||
runtimeRef.current?.setLayerWorldTransform(
|
||||
"collision",
|
||||
playCanvasEulerTransform(next),
|
||||
);
|
||||
commitViewerSettings({
|
||||
...viewerSettingsRef.current,
|
||||
collision: { rotationDegrees: next },
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
<section className="simulation-viewport__settings-group">
|
||||
<div className="simulation-viewport__settings-group-head">
|
||||
<strong>Управление камерой</strong>
|
||||
<span>Мышь и тачпад</span>
|
||||
</div>
|
||||
<div className="simulation-viewport__settings-switches">
|
||||
<Switch
|
||||
checked={horizontalInverted}
|
||||
disabled={state !== "ready"}
|
||||
label="Инверсия управления по горизонтали"
|
||||
onChange={(checked) => {
|
||||
setHorizontalInverted(checked);
|
||||
runtimeRef.current?.setCameraInversion("horizontal", checked);
|
||||
commitViewerSettings({
|
||||
...viewerSettingsRef.current,
|
||||
camera: { ...viewerSettingsRef.current.camera, invertHorizontal: checked },
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Switch
|
||||
checked={verticalInverted}
|
||||
disabled={state !== "ready"}
|
||||
label="Инверсия управления по вертикали"
|
||||
onChange={(checked) => {
|
||||
setVerticalInverted(checked);
|
||||
runtimeRef.current?.setCameraInversion("vertical", checked);
|
||||
commitViewerSettings({
|
||||
...viewerSettingsRef.current,
|
||||
camera: { ...viewerSettingsRef.current.camera, invertVertical: checked },
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
<SimulationUgvSettingsPanel
|
||||
value={ugvDraft}
|
||||
disabled={state !== "ready"}
|
||||
saveState={ugvSaveState}
|
||||
onChange={changeUgvDraft}
|
||||
onSave={saveUgvPreset}
|
||||
/>
|
||||
{settingsError ? (
|
||||
<p className="simulation-viewport__settings-error" role="alert">
|
||||
Настройки не сохранены: {settingsError}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
</GlassMaterialSurface>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
<span className="simulation-viewport__toolbar-balance" aria-hidden="true" />
|
||||
</header>
|
||||
<div className="simulation-viewport__stage">
|
||||
<canvas ref={canvasRef} aria-label={`Сцена ${project.name}`} />
|
||||
{state !== "ready" ? (
|
||||
<div className="simulation-viewport__state" role={state === "failed" ? "alert" : "status"}>
|
||||
{state === "failed" ? null : <ActivityIndicator label="Загрузка локации" />}
|
||||
<strong>{state === "failed" ? "Локация не открылась" : "Загружаем локацию"}</strong>
|
||||
<p>{error ?? "LOD-уровни загружаются по мере готовности runtime."}</p>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
{!collisionAvailable ? (
|
||||
<footer className="simulation-viewport__notice">
|
||||
<StatusBadge tone="warning">Коллизии недоступны</StatusBadge>
|
||||
<span>Для этой сборки слой коллизий не создавался; визуальный слой остаётся доступен.</span>
|
||||
</footer>
|
||||
) : collisionState === "failed" ? (
|
||||
<footer className="simulation-viewport__notice" role="alert">
|
||||
<StatusBadge tone="warning">Коллизии не открылись</StatusBadge>
|
||||
<span>{error ?? "Слой коллизий не загрузился."}</span>
|
||||
</footer>
|
||||
) : null}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function cloneUgvPreset(preset: SimulationUgvPreset): SimulationUgvPreset {
|
||||
return {
|
||||
...preset,
|
||||
dimensionsMeters: { ...preset.dimensionsMeters },
|
||||
};
|
||||
}
|
||||
|
||||
function LayerRotationFields({
|
||||
label,
|
||||
value,
|
||||
disabled,
|
||||
onChange,
|
||||
}: {
|
||||
label: string;
|
||||
value: SimulationEulerRotation;
|
||||
disabled: boolean;
|
||||
onChange: (rotation: SimulationEulerRotation) => void;
|
||||
}) {
|
||||
return (
|
||||
<div className="simulation-viewport__rotation-row">
|
||||
<strong>{label}</strong>
|
||||
<div className="simulation-viewport__rotation-fields">
|
||||
{ROTATION_AXES.map((axis) => (
|
||||
<RotationField
|
||||
key={axis}
|
||||
label={axis.toUpperCase()}
|
||||
ariaLabel={`${label}: вращение ${axis.toUpperCase()}`}
|
||||
value={value[axis]}
|
||||
disabled={disabled}
|
||||
onChange={(angle) => onChange({ ...value, [axis]: angle })}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function RotationField({
|
||||
label,
|
||||
ariaLabel,
|
||||
value,
|
||||
disabled,
|
||||
onChange,
|
||||
}: {
|
||||
label: string;
|
||||
ariaLabel: string;
|
||||
value: number;
|
||||
disabled: boolean;
|
||||
onChange: (angle: number) => void;
|
||||
}) {
|
||||
const [draft, setDraft] = useState(() => String(value));
|
||||
const editingRef = useRef(false);
|
||||
const cancelNextBlurRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!editingRef.current) setDraft(String(value));
|
||||
}, [value]);
|
||||
|
||||
const parsedDraft = () => Number(draft.trim().replace(",", "."));
|
||||
const finishEditing = (commit: boolean) => {
|
||||
const parsed = parsedDraft();
|
||||
if (commit && draft.trim() !== "" && Number.isFinite(parsed)) {
|
||||
const normalized = Math.max(-360, Math.min(360, parsed));
|
||||
onChange(normalized);
|
||||
setDraft(String(normalized));
|
||||
} else {
|
||||
setDraft(String(value));
|
||||
}
|
||||
editingRef.current = false;
|
||||
};
|
||||
|
||||
return (
|
||||
<TextField
|
||||
label={label}
|
||||
aria-label={ariaLabel}
|
||||
fieldClassName="simulation-viewport__rotation-field"
|
||||
className="simulation-viewport__rotation-input"
|
||||
type="text"
|
||||
inputMode="decimal"
|
||||
value={draft}
|
||||
disabled={disabled}
|
||||
spellCheck={false}
|
||||
onFocus={() => {
|
||||
editingRef.current = true;
|
||||
}}
|
||||
onChange={(event) => {
|
||||
// Treat the first input event as the start of editing as well. Programmatic
|
||||
// focus (and some browser/trackpad paths) may not deliver React's focus
|
||||
// event before the controlled value changes.
|
||||
editingRef.current = true;
|
||||
const nextDraft = event.currentTarget.value;
|
||||
setDraft(nextDraft);
|
||||
const parsed = Number(nextDraft.trim().replace(",", "."));
|
||||
if (nextDraft.trim() !== "" && Number.isFinite(parsed) && parsed >= -360 && parsed <= 360) {
|
||||
onChange(parsed);
|
||||
}
|
||||
}}
|
||||
onKeyDown={(event) => {
|
||||
event.stopPropagation();
|
||||
if (event.key === "Enter") event.currentTarget.blur();
|
||||
if (event.key === "Escape") {
|
||||
event.preventDefault();
|
||||
cancelNextBlurRef.current = true;
|
||||
finishEditing(false);
|
||||
event.currentTarget.blur();
|
||||
}
|
||||
}}
|
||||
onBlur={() => {
|
||||
if (cancelNextBlurRef.current) {
|
||||
cancelNextBlurRef.current = false;
|
||||
return;
|
||||
}
|
||||
finishEditing(true);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,579 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import {
|
||||
Button,
|
||||
FeatureSettingsWindow,
|
||||
FieldFrame,
|
||||
Icon,
|
||||
Select,
|
||||
SettingsCard,
|
||||
StatusBadge,
|
||||
TextField,
|
||||
WindowFooterActions,
|
||||
} from "@nodedc/ui-react";
|
||||
|
||||
import {
|
||||
applyComputeContourNetwork,
|
||||
fetchComputeContourAgentInstall,
|
||||
fetchComputeContourNetwork,
|
||||
type ComputeContour,
|
||||
type ComputeContourAgentInstall,
|
||||
type ComputeContourDraft,
|
||||
type ComputeContourNetworkStatus,
|
||||
type ComputeContourPlatform,
|
||||
} from "../../core/system/computeContours";
|
||||
import {
|
||||
DEFAULT_TELEMETRY_POLL_INTERVAL_SECONDS,
|
||||
parseTelemetryPollIntervalDraft,
|
||||
resolveTelemetryPollIntervalDraft,
|
||||
} from "../../core/system/telemetryPollInterval";
|
||||
|
||||
interface ComputeContourSettingsWindowProps {
|
||||
open: boolean;
|
||||
contour: ComputeContour | null;
|
||||
mode: "create" | "edit";
|
||||
onClose: () => void;
|
||||
onCreate: (draft: ComputeContourDraft) => Promise<ComputeContour>;
|
||||
onUpdate: (
|
||||
contour: ComputeContour,
|
||||
draft: ComputeContourDraft,
|
||||
) => Promise<ComputeContour>;
|
||||
}
|
||||
|
||||
const platformOptions = [
|
||||
{ value: "windows", label: "Windows", description: "Worker с Windows service" },
|
||||
{ value: "linux", label: "Linux", description: "Worker с systemd service" },
|
||||
{ value: "unknown", label: "Не определено", description: "Платформа будет уточнена" },
|
||||
] satisfies Array<{
|
||||
value: ComputeContourPlatform;
|
||||
label: string;
|
||||
description: string;
|
||||
}>;
|
||||
|
||||
function emptyDraft(): ComputeContourDraft {
|
||||
return {
|
||||
display_name: "",
|
||||
expected_node_id: "",
|
||||
platform: "unknown",
|
||||
telemetry_mode: "agent-mqtt",
|
||||
address: "",
|
||||
ssh_port: 22,
|
||||
mqtt_host: "127.0.0.1",
|
||||
mqtt_port: 1883,
|
||||
mqtt_bind_address: "127.0.0.1",
|
||||
telemetry_poll_interval_seconds: DEFAULT_TELEMETRY_POLL_INTERVAL_SECONDS,
|
||||
mqtt_publish_interval_seconds: 2,
|
||||
};
|
||||
}
|
||||
|
||||
function draftFromContour(contour: ComputeContour | null): ComputeContourDraft {
|
||||
if (!contour) return emptyDraft();
|
||||
return {
|
||||
display_name: contour.display_name,
|
||||
expected_node_id: contour.expected_node_id,
|
||||
platform: contour.platform,
|
||||
telemetry_mode: contour.telemetry_mode,
|
||||
address: contour.address,
|
||||
ssh_port: contour.ssh_port,
|
||||
mqtt_host: contour.mqtt_host,
|
||||
mqtt_port: contour.mqtt_port,
|
||||
mqtt_bind_address: contour.mqtt_bind_address,
|
||||
telemetry_poll_interval_seconds: contour.telemetry_poll_interval_seconds,
|
||||
mqtt_publish_interval_seconds: contour.mqtt_publish_interval_seconds,
|
||||
};
|
||||
}
|
||||
|
||||
export function ComputeContourSettingsWindow({
|
||||
open,
|
||||
contour,
|
||||
mode,
|
||||
onClose,
|
||||
onCreate,
|
||||
onUpdate,
|
||||
}: ComputeContourSettingsWindowProps) {
|
||||
const [draft, setDraft] = useState<ComputeContourDraft>(() => draftFromContour(contour));
|
||||
const [telemetryPollIntervalDraft, setTelemetryPollIntervalDraft] = useState(
|
||||
() => String(draftFromContour(contour).telemetry_poll_interval_seconds),
|
||||
);
|
||||
const [mqttPublishIntervalDraft, setMqttPublishIntervalDraft] = useState(
|
||||
() => String(draftFromContour(contour).mqtt_publish_interval_seconds),
|
||||
);
|
||||
const [activeSection, setActiveSection] = useState<"connection" | "agent">("connection");
|
||||
const [install, setInstall] = useState<ComputeContourAgentInstall | null>(null);
|
||||
const [network, setNetwork] = useState<ComputeContourNetworkStatus | null>(null);
|
||||
const [networkAction, setNetworkAction] = useState<
|
||||
"refresh" | "broker" | "worker" | null
|
||||
>(null);
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
const nextDraft = draftFromContour(mode === "edit" ? contour : null);
|
||||
setDraft(nextDraft);
|
||||
setTelemetryPollIntervalDraft(String(nextDraft.telemetry_poll_interval_seconds));
|
||||
setMqttPublishIntervalDraft(String(nextDraft.mqtt_publish_interval_seconds));
|
||||
setActiveSection("connection");
|
||||
setInstall(null);
|
||||
setNetwork(null);
|
||||
setNetworkAction(null);
|
||||
setError(null);
|
||||
}, [contour, mode, open]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open || mode !== "edit" || !contour) return;
|
||||
const controller = new AbortController();
|
||||
setNetworkAction("refresh");
|
||||
void fetchComputeContourNetwork(contour.contour_id, controller.signal)
|
||||
.then((document) => {
|
||||
if (!controller.signal.aborted) setNetwork(document);
|
||||
})
|
||||
.catch((reason: unknown) => {
|
||||
if (!controller.signal.aborted) {
|
||||
setError(reason instanceof Error ? reason.message : "Сетевая проверка недоступна.");
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
if (!controller.signal.aborted) setNetworkAction(null);
|
||||
});
|
||||
return () => controller.abort();
|
||||
}, [contour, mode, open]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open || mode !== "edit" || !contour) return;
|
||||
const controller = new AbortController();
|
||||
void fetchComputeContourAgentInstall(contour.contour_id, controller.signal)
|
||||
.then((document) => {
|
||||
if (!controller.signal.aborted) setInstall(document);
|
||||
})
|
||||
.catch((reason: unknown) => {
|
||||
if (!controller.signal.aborted) {
|
||||
setError(reason instanceof Error ? reason.message : "Инструкция агента недоступна.");
|
||||
}
|
||||
});
|
||||
return () => controller.abort();
|
||||
}, [contour, mode, open]);
|
||||
|
||||
const telemetryPollIntervalSeconds = useMemo(
|
||||
() => parseTelemetryPollIntervalDraft(telemetryPollIntervalDraft),
|
||||
[telemetryPollIntervalDraft],
|
||||
);
|
||||
const mqttPublishIntervalSeconds = useMemo(
|
||||
() => parseTelemetryPollIntervalDraft(mqttPublishIntervalDraft),
|
||||
[mqttPublishIntervalDraft],
|
||||
);
|
||||
|
||||
const valid = useMemo(() => (
|
||||
Boolean(draft.display_name.trim())
|
||||
&& Boolean(draft.expected_node_id.trim())
|
||||
&& Boolean(draft.mqtt_host.trim())
|
||||
&& Boolean(draft.mqtt_bind_address.trim())
|
||||
&& Number.isInteger(draft.ssh_port)
|
||||
&& draft.ssh_port > 0
|
||||
&& Number.isInteger(draft.mqtt_port)
|
||||
&& draft.mqtt_port > 0
|
||||
&& telemetryPollIntervalSeconds !== null
|
||||
&& mqttPublishIntervalSeconds !== null
|
||||
), [draft, mqttPublishIntervalSeconds, telemetryPollIntervalSeconds]);
|
||||
|
||||
const networkProfileDirty = Boolean(
|
||||
contour
|
||||
&& (
|
||||
draft.mqtt_host !== contour.mqtt_host
|
||||
|| draft.mqtt_port !== contour.mqtt_port
|
||||
|| draft.mqtt_bind_address !== contour.mqtt_bind_address
|
||||
|| draft.mqtt_publish_interval_seconds !== contour.mqtt_publish_interval_seconds
|
||||
),
|
||||
);
|
||||
|
||||
const commitTelemetryPollInterval = () => {
|
||||
const resolution = resolveTelemetryPollIntervalDraft(
|
||||
telemetryPollIntervalDraft,
|
||||
draft.telemetry_poll_interval_seconds,
|
||||
);
|
||||
setTelemetryPollIntervalDraft(resolution.draft);
|
||||
if (!resolution.accepted) return;
|
||||
setDraft((current) => (
|
||||
current.telemetry_poll_interval_seconds === resolution.seconds
|
||||
? current
|
||||
: {
|
||||
...current,
|
||||
telemetry_poll_interval_seconds: resolution.seconds,
|
||||
}
|
||||
));
|
||||
};
|
||||
|
||||
const commitMqttPublishInterval = () => {
|
||||
const resolution = resolveTelemetryPollIntervalDraft(
|
||||
mqttPublishIntervalDraft,
|
||||
draft.mqtt_publish_interval_seconds,
|
||||
);
|
||||
setMqttPublishIntervalDraft(resolution.draft);
|
||||
if (!resolution.accepted) return;
|
||||
setDraft((current) => (
|
||||
current.mqtt_publish_interval_seconds === resolution.seconds
|
||||
? current
|
||||
: {
|
||||
...current,
|
||||
mqtt_publish_interval_seconds: resolution.seconds,
|
||||
}
|
||||
));
|
||||
};
|
||||
|
||||
const save = async () => {
|
||||
if (
|
||||
!valid
|
||||
|| busy
|
||||
|| telemetryPollIntervalSeconds === null
|
||||
|| mqttPublishIntervalSeconds === null
|
||||
) return;
|
||||
const committedDraft = {
|
||||
...draft,
|
||||
telemetry_poll_interval_seconds: telemetryPollIntervalSeconds,
|
||||
mqtt_publish_interval_seconds: mqttPublishIntervalSeconds,
|
||||
};
|
||||
setBusy(true);
|
||||
setError(null);
|
||||
try {
|
||||
if (mode === "edit" && contour) {
|
||||
await onUpdate(contour, committedDraft);
|
||||
} else {
|
||||
await onCreate(committedDraft);
|
||||
}
|
||||
onClose();
|
||||
} catch (reason) {
|
||||
setError(reason instanceof Error ? reason.message : "Контур не сохранён.");
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
};
|
||||
|
||||
const refreshNetwork = async () => {
|
||||
if (!contour || networkAction) return;
|
||||
setNetworkAction("refresh");
|
||||
setError(null);
|
||||
try {
|
||||
setNetwork(await fetchComputeContourNetwork(contour.contour_id));
|
||||
} catch (reason) {
|
||||
setError(reason instanceof Error ? reason.message : "Сетевая проверка недоступна.");
|
||||
} finally {
|
||||
setNetworkAction(null);
|
||||
}
|
||||
};
|
||||
|
||||
const applyNetwork = async (target: "broker" | "worker") => {
|
||||
if (!contour || networkAction || networkProfileDirty) return;
|
||||
setNetworkAction(target);
|
||||
setError(null);
|
||||
try {
|
||||
await applyComputeContourNetwork(contour.contour_id, target);
|
||||
setNetwork(await fetchComputeContourNetwork(contour.contour_id));
|
||||
} catch (reason) {
|
||||
setError(
|
||||
reason instanceof Error ? reason.message : "Сетевой профиль не применён.",
|
||||
);
|
||||
} finally {
|
||||
setNetworkAction(null);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<FeatureSettingsWindow
|
||||
open={open}
|
||||
title={mode === "create" ? "Добавить вычислительный контур" : "Настройки контура"}
|
||||
subtitle="Локальная переносимая конфигурация"
|
||||
identity={{
|
||||
title: contour?.display_name ?? "Новый контур",
|
||||
subtitle: contour?.expected_node_id ?? "Node ID не назначен",
|
||||
avatarLabel: contour?.display_name.slice(0, 2).toUpperCase() ?? "WC",
|
||||
}}
|
||||
sections={[
|
||||
{
|
||||
id: "connection",
|
||||
label: "Подключение",
|
||||
group: "ВЫЧИСЛИТЕЛЬНЫЙ КОНТУР",
|
||||
icon: "network",
|
||||
},
|
||||
{
|
||||
id: "agent",
|
||||
label: "Агент",
|
||||
group: "ТЕЛЕМЕТРИЯ",
|
||||
icon: "activity",
|
||||
disabled: mode === "create",
|
||||
},
|
||||
]}
|
||||
activeSection={activeSection}
|
||||
onSectionChange={setActiveSection}
|
||||
onClose={onClose}
|
||||
footer={(
|
||||
<WindowFooterActions>
|
||||
<Button variant="secondary" onClick={onClose}>Отмена</Button>
|
||||
<Button variant="primary" disabled={!valid || busy} onClick={() => void save()}>
|
||||
{busy ? "Сохраняем…" : mode === "create" ? "Добавить контур" : "Сохранить"}
|
||||
</Button>
|
||||
</WindowFooterActions>
|
||||
)}
|
||||
>
|
||||
<div className="compute-contour-settings">
|
||||
{activeSection === "connection" ? (
|
||||
<>
|
||||
<SettingsCard
|
||||
eyebrow="КОНТУР"
|
||||
title="Идентичность и локальная сеть"
|
||||
description="Node ID проверяется независимо от IP-адреса. Адрес можно менять при переносе комплекта в другую локальную сеть."
|
||||
>
|
||||
<div className="compute-contour-settings__form">
|
||||
<TextField
|
||||
label="Название"
|
||||
value={draft.display_name}
|
||||
placeholder="Worker 006"
|
||||
onChange={(event) => setDraft((current) => ({
|
||||
...current,
|
||||
display_name: event.currentTarget.value,
|
||||
}))}
|
||||
/>
|
||||
<TextField
|
||||
label="Ожидаемый Node ID"
|
||||
value={draft.expected_node_id}
|
||||
placeholder="DESKTOP-OPJ8J04"
|
||||
onChange={(event) => setDraft((current) => ({
|
||||
...current,
|
||||
expected_node_id: event.currentTarget.value,
|
||||
}))}
|
||||
/>
|
||||
<FieldFrame label="Операционная система">
|
||||
<Select
|
||||
label="Операционная система"
|
||||
value={draft.platform}
|
||||
options={platformOptions}
|
||||
variant="split"
|
||||
menuWidth="anchor"
|
||||
onChange={(platform) => setDraft((current) => ({ ...current, platform }))}
|
||||
/>
|
||||
</FieldFrame>
|
||||
<TextField
|
||||
label="Адрес узла"
|
||||
hint="IP или hostname в текущей LAN"
|
||||
value={draft.address}
|
||||
onChange={(event) => setDraft((current) => ({
|
||||
...current,
|
||||
address: event.currentTarget.value,
|
||||
}))}
|
||||
/>
|
||||
<TextField
|
||||
label="MQTT broker"
|
||||
hint="Стабильный hostname рекомендуется вместо выдаваемого DHCP IP"
|
||||
value={draft.mqtt_host}
|
||||
onChange={(event) => setDraft((current) => ({
|
||||
...current,
|
||||
mqtt_host: event.currentTarget.value,
|
||||
}))}
|
||||
/>
|
||||
<TextField
|
||||
label="MQTT port"
|
||||
type="number"
|
||||
min={1}
|
||||
max={65535}
|
||||
value={String(draft.mqtt_port)}
|
||||
onChange={(event) => setDraft((current) => ({
|
||||
...current,
|
||||
mqtt_port: Number(event.currentTarget.value),
|
||||
}))}
|
||||
/>
|
||||
<TextField
|
||||
label="Публикация broker"
|
||||
hint="IP текущего Mac в разрешённой LAN · 0.0.0.0 запрещён"
|
||||
value={draft.mqtt_bind_address}
|
||||
onChange={(event) => setDraft((current) => ({
|
||||
...current,
|
||||
mqtt_bind_address: event.currentTarget.value,
|
||||
}))}
|
||||
/>
|
||||
<TextField
|
||||
label="Обновление интерфейса"
|
||||
hint="1–60 с"
|
||||
type="text"
|
||||
inputMode="numeric"
|
||||
autoComplete="off"
|
||||
aria-invalid={telemetryPollIntervalSeconds === null}
|
||||
value={telemetryPollIntervalDraft}
|
||||
onChange={(event) => setTelemetryPollIntervalDraft(event.currentTarget.value)}
|
||||
onBlur={commitTelemetryPollInterval}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter") {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
commitTelemetryPollInterval();
|
||||
} else if (event.key === "Escape") {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
setTelemetryPollIntervalDraft(
|
||||
String(draft.telemetry_poll_interval_seconds),
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<TextField
|
||||
label="Интервал MQTT агента"
|
||||
hint="1–60 с · применяется конфигурацией агента"
|
||||
type="text"
|
||||
inputMode="numeric"
|
||||
autoComplete="off"
|
||||
aria-invalid={mqttPublishIntervalSeconds === null}
|
||||
value={mqttPublishIntervalDraft}
|
||||
onChange={(event) => setMqttPublishIntervalDraft(event.currentTarget.value)}
|
||||
onBlur={commitMqttPublishInterval}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter") {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
commitMqttPublishInterval();
|
||||
} else if (event.key === "Escape") {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
setMqttPublishIntervalDraft(
|
||||
String(draft.mqtt_publish_interval_seconds),
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{draft.telemetry_mode === "legacy-ssh" ? (
|
||||
<TextField
|
||||
label="SSH port"
|
||||
type="number"
|
||||
min={1}
|
||||
max={65535}
|
||||
value={String(draft.ssh_port)}
|
||||
onChange={(event) => setDraft((current) => ({
|
||||
...current,
|
||||
ssh_port: Number(event.currentTarget.value),
|
||||
}))}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
</SettingsCard>
|
||||
<SettingsCard
|
||||
eyebrow="СЕТЕВОЙ ПРОФИЛЬ"
|
||||
title="MQTT · Mac ↔ Worker"
|
||||
description="Mesh-точки одной LAN не закрепляются по отдельности. Профиль хранит стабильное имя Mac и адрес публикации broker в текущей сети."
|
||||
actions={(
|
||||
<StatusBadge tone={network?.broker.ready ? "success" : "warning"}>
|
||||
{network?.broker.ready ? "Broker доступен" : "Нужна проверка"}
|
||||
</StatusBadge>
|
||||
)}
|
||||
>
|
||||
<div className="compute-contour-settings__network">
|
||||
<dl>
|
||||
<div>
|
||||
<dt>Endpoint Worker</dt>
|
||||
<dd>{network
|
||||
? `${network.broker.configured_host}:${network.broker.configured_port}`
|
||||
: "—"}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Текущий IP</dt>
|
||||
<dd>{network?.broker.resolved_addresses.join(", ") || "—"}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Публикация Mac</dt>
|
||||
<dd>{network?.broker.bind_address ?? "—"}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>{contour?.display_name ?? "Worker"}</dt>
|
||||
<dd>{network?.worker.reachable
|
||||
? `${network.worker.mqtt_host ?? "—"}:${network.worker.mqtt_port ?? "—"}`
|
||||
: "Недоступен"}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<div className="compute-contour-settings__network-statuses">
|
||||
<StatusBadge tone={network?.broker.bind_reachable ? "success" : "danger"}>
|
||||
{network?.broker.bind_reachable
|
||||
? "Listener Mac доступен"
|
||||
: "Listener Mac недоступен"}
|
||||
</StatusBadge>
|
||||
<StatusBadge tone={network?.worker.broker_reachable ? "success" : "danger"}>
|
||||
{network?.worker.broker_reachable
|
||||
? "Worker видит broker"
|
||||
: "Worker не видит broker"}
|
||||
</StatusBadge>
|
||||
<StatusBadge tone={network?.worker.matches_profile ? "success" : "warning"}>
|
||||
{network?.worker.matches_profile
|
||||
? "Профиль синхронизирован"
|
||||
: "Профиль отличается"}
|
||||
</StatusBadge>
|
||||
</div>
|
||||
<div className="compute-contour-settings__network-actions">
|
||||
<Button
|
||||
size="compact"
|
||||
variant="secondary"
|
||||
disabled={Boolean(networkAction)}
|
||||
icon={<Icon name="refresh" size={14} />}
|
||||
onClick={() => void refreshNetwork()}
|
||||
>
|
||||
{networkAction === "refresh" ? "Проверяем…" : "Проверить"}
|
||||
</Button>
|
||||
<Button
|
||||
size="compact"
|
||||
variant="secondary"
|
||||
disabled={Boolean(networkAction) || networkProfileDirty}
|
||||
onClick={() => void applyNetwork("broker")}
|
||||
>
|
||||
{networkAction === "broker" ? "Применяем…" : "Применить на Mac"}
|
||||
</Button>
|
||||
<Button
|
||||
size="compact"
|
||||
variant="secondary"
|
||||
disabled={Boolean(networkAction) || networkProfileDirty}
|
||||
onClick={() => void applyNetwork("worker")}
|
||||
>
|
||||
{networkAction === "worker" ? "Применяем…" : "Применить на Worker"}
|
||||
</Button>
|
||||
</div>
|
||||
{networkProfileDirty ? (
|
||||
<p>Сначала сохраните изменённый сетевой профиль, затем примените его.</p>
|
||||
) : null}
|
||||
{error ? <StatusBadge tone="danger">{error}</StatusBadge> : null}
|
||||
</div>
|
||||
</SettingsCard>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
{activeSection === "agent" ? (
|
||||
<SettingsCard
|
||||
eyebrow="АГЕНТ"
|
||||
title="Telegraf · универсальный сборщик"
|
||||
description="Hardware и runtime собирает готовый агент. Mission Core нормализует только контур, LAB/run и стадии обработки."
|
||||
actions={(
|
||||
<StatusBadge tone={install?.ready ? "success" : "warning"}>
|
||||
{install?.ready ? "Готов к установке" : "Нужен scoped MQTT credential"}
|
||||
</StatusBadge>
|
||||
)}
|
||||
>
|
||||
{install ? (
|
||||
<div className="compute-contour-settings__install">
|
||||
<dl>
|
||||
<div><dt>Шаблон</dt><dd>{install.agent.configuration_template}</dd></div>
|
||||
<div><dt>Секрет</dt><dd>интерактивный ввод · не хранится в UI</dd></div>
|
||||
</dl>
|
||||
<code>{install.command}</code>
|
||||
<Button
|
||||
size="compact"
|
||||
variant="secondary"
|
||||
icon={<Icon name="copy" size={14} />}
|
||||
onClick={() => void navigator.clipboard.writeText(install.command)}
|
||||
>
|
||||
Копировать команду
|
||||
</Button>
|
||||
{install.blocked_reason ? <p>{install.blocked_reason}</p> : null}
|
||||
</div>
|
||||
) : (
|
||||
<p className="compute-contour-settings__empty">
|
||||
Сохраните контур, чтобы получить платформенную инструкцию установки.
|
||||
</p>
|
||||
)}
|
||||
{error ? <StatusBadge tone="danger">{error}</StatusBadge> : null}
|
||||
</SettingsCard>
|
||||
) : null}
|
||||
</div>
|
||||
</FeatureSettingsWindow>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import {
|
||||
AdminNavigationPanel,
|
||||
Icon,
|
||||
IconButton,
|
||||
} from "@nodedc/ui-react";
|
||||
|
||||
import { useComputeContours } from "../../core/system/ComputeContourContext";
|
||||
|
||||
interface SystemNavigationPanelProps {
|
||||
title: string;
|
||||
onAdd: () => void;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export function SystemNavigationPanel({
|
||||
title,
|
||||
onAdd,
|
||||
onClose,
|
||||
}: SystemNavigationPanelProps) {
|
||||
const { contours, selectedContour, selectContour } = useComputeContours();
|
||||
const contourCountLabel = contours.length === 1
|
||||
? "1 конфигурация"
|
||||
: `${contours.length} конфигураций`;
|
||||
|
||||
return (
|
||||
<AdminNavigationPanel
|
||||
eyebrow="MISSION CORE"
|
||||
title={title}
|
||||
closeLabel={`Закрыть раздел «${title}»`}
|
||||
navigationLabel="Вычислительные контуры"
|
||||
onClose={onClose}
|
||||
headerActions={(
|
||||
<IconButton label="Добавить вычислительный контур" onClick={onAdd}>
|
||||
<Icon name="plus" size={16} strokeWidth={1.6} />
|
||||
</IconButton>
|
||||
)}
|
||||
contexts={contours.map((contour) => ({
|
||||
id: contour.contour_id,
|
||||
label: contour.display_name,
|
||||
description: contour.expected_node_id,
|
||||
icon: <Icon name="apps" />,
|
||||
active: selectedContour?.contour_id === contour.contour_id,
|
||||
onSelect: () => selectContour(contour.contour_id),
|
||||
}))}
|
||||
items={[]}
|
||||
onItemChange={() => undefined}
|
||||
footer={(
|
||||
<>
|
||||
<span className="nodedc-admin-panel__nav-icon" aria-hidden="true">
|
||||
<Icon name="activity" />
|
||||
</span>
|
||||
<span>{contourCountLabel}</span>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { SegmentedControl } from "@nodedc/ui-react";
|
||||
|
||||
import { workspacesForRoot } from "../../productModel";
|
||||
|
||||
const systemWorkspaces = workspacesForRoot("system");
|
||||
const compactLabels: Record<string, string> = {
|
||||
modules: "Модули",
|
||||
integrations: "Интеграции",
|
||||
network: "Сеть",
|
||||
audit: "Аудит",
|
||||
};
|
||||
|
||||
interface SystemWorkspaceSelectorProps {
|
||||
value: string;
|
||||
onChange: (workspaceId: string) => void;
|
||||
}
|
||||
|
||||
export function SystemWorkspaceSelector({
|
||||
value,
|
||||
onChange,
|
||||
}: SystemWorkspaceSelectorProps) {
|
||||
return (
|
||||
<SegmentedControl
|
||||
value={value}
|
||||
items={systemWorkspaces.map((definition) => ({
|
||||
value: definition.id,
|
||||
label: compactLabels[definition.id] ?? definition.label,
|
||||
}))}
|
||||
label="Рабочая поверхность вычислительного контура"
|
||||
onChange={onChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
interface TelemetrySeriesProps {
|
||||
label: string;
|
||||
values: Array<number | null>;
|
||||
value: string;
|
||||
resource?: string | null;
|
||||
ceiling?: number;
|
||||
}
|
||||
|
||||
function linePoints(values: Array<number | null>, ceiling?: number): string {
|
||||
const finite = values.filter((value): value is number =>
|
||||
value !== null && Number.isFinite(value),
|
||||
);
|
||||
if (finite.length === 0) return "";
|
||||
const maximum = Math.max(ceiling ?? 0, ...finite, 1);
|
||||
const denominator = Math.max(1, values.length - 1);
|
||||
return values.map((value, index) => {
|
||||
const x = index / denominator * 100;
|
||||
const normalized = value === null ? 0 : Math.max(0, Math.min(maximum, value));
|
||||
const y = 36 - normalized / maximum * 34;
|
||||
return `${x.toFixed(2)},${y.toFixed(2)}`;
|
||||
}).join(" ");
|
||||
}
|
||||
|
||||
export function TelemetrySeries({
|
||||
label,
|
||||
values,
|
||||
value,
|
||||
resource,
|
||||
ceiling,
|
||||
}: TelemetrySeriesProps) {
|
||||
const points = linePoints(values, ceiling);
|
||||
return (
|
||||
<div className="system-telemetry-series">
|
||||
<div>
|
||||
<span className="system-telemetry-series__label">
|
||||
<span>{label}</span>
|
||||
{resource ? <small>{resource}</small> : null}
|
||||
</span>
|
||||
<strong>{value}</strong>
|
||||
</div>
|
||||
<svg
|
||||
viewBox="0 0 100 38"
|
||||
preserveAspectRatio="none"
|
||||
role="img"
|
||||
aria-label={`${label}: ${value}`}
|
||||
>
|
||||
<path d="M0 37 H100" />
|
||||
{points ? <polyline points={points} /> : null}
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import type { CSSProperties } from "react";
|
||||
|
||||
import type { WorkerPipelineStage } from "../../core/system/workerTelemetry";
|
||||
|
||||
interface WorkerPipelineStagesProps {
|
||||
stages: WorkerPipelineStage[];
|
||||
}
|
||||
|
||||
function stateLabel(state: WorkerPipelineStage["state"]): string {
|
||||
if (state === "active") return "выполняется";
|
||||
if (state === "waiting") return "ожидает";
|
||||
if (state === "ready") return "готов";
|
||||
return "нет live-состояния";
|
||||
}
|
||||
|
||||
function stageDuration(value: number | null): string {
|
||||
if (typeof value !== "number" || !Number.isFinite(value)) return "—";
|
||||
if (value < 1) return `${Math.round(value * 1000)} мс`;
|
||||
return `${new Intl.NumberFormat("ru-RU", {
|
||||
maximumFractionDigits: value < 10 ? 2 : 1,
|
||||
}).format(value)} с`;
|
||||
}
|
||||
|
||||
function stageDetail(stage: WorkerPipelineStage): string {
|
||||
if (
|
||||
typeof stage.activations !== "number"
|
||||
|| typeof stage.share_percent !== "number"
|
||||
|| !Number.isFinite(stage.share_percent)
|
||||
) {
|
||||
return "Измерений ещё нет";
|
||||
}
|
||||
const share = new Intl.NumberFormat("ru-RU", {
|
||||
maximumFractionDigits: 1,
|
||||
}).format(stage.share_percent);
|
||||
return `${stage.activations} проходов · ${share}% измеренного времени`;
|
||||
}
|
||||
|
||||
export function WorkerPipelineStages({ stages }: WorkerPipelineStagesProps) {
|
||||
return (
|
||||
<ol className="worker-stage-list" aria-label="Стадии текущей задачи">
|
||||
{stages.map((stage, index) => {
|
||||
const share = typeof stage.share_percent === "number"
|
||||
? Math.max(0, Math.min(100, stage.share_percent))
|
||||
: 0;
|
||||
const style = {
|
||||
"--worker-stage-share": `${share}%`,
|
||||
} as CSSProperties;
|
||||
return (
|
||||
<li
|
||||
key={stage.id}
|
||||
data-state={stage.state}
|
||||
style={style}
|
||||
aria-label={`${stage.label}: ${stageDetail(stage)}`}
|
||||
>
|
||||
<span className="worker-stage-list__fill" aria-hidden="true" />
|
||||
<span className="worker-stage-list__index">
|
||||
{String(index + 1).padStart(2, "0")}
|
||||
</span>
|
||||
<div>
|
||||
<strong>{stage.label}</strong>
|
||||
<small>{stageDetail(stage)}</small>
|
||||
</div>
|
||||
<div className="worker-stage-list__value">
|
||||
<strong>{stageDuration(stage.elapsed_seconds)}</strong>
|
||||
<small>{stateLabel(stage.state)}</small>
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ol>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import { GlassSurface, StatusBadge } from "@nodedc/ui-react";
|
||||
|
||||
import type { WorkerRuntime } from "../../core/system/workerTelemetry";
|
||||
import {
|
||||
formatOptionalPercent,
|
||||
runtimeStateLabel,
|
||||
runtimeTone,
|
||||
} from "./systemFormat";
|
||||
|
||||
export function WorkerRuntimeCard({ runtime }: { runtime: WorkerRuntime }) {
|
||||
return (
|
||||
<GlassSurface
|
||||
className="worker-runtime-card"
|
||||
padding="md"
|
||||
tone={runtime.external ? "soft" : "default"}
|
||||
data-external={runtime.external ? "true" : undefined}
|
||||
>
|
||||
<header>
|
||||
<div>
|
||||
<span className="section-eyebrow">
|
||||
{runtime.external ? "ВНЕШНЯЯ НАГРУЗКА" : "MISSION CORE RUNTIME"}
|
||||
</span>
|
||||
<h3>{runtime.role}</h3>
|
||||
<code>{runtime.name}</code>
|
||||
</div>
|
||||
<StatusBadge tone={runtimeTone(runtime)}>
|
||||
{runtimeStateLabel(runtime)}
|
||||
</StatusBadge>
|
||||
</header>
|
||||
<dl>
|
||||
<div>
|
||||
<dt>CPU</dt>
|
||||
<dd>{formatOptionalPercent(runtime.cpu_percent)}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Память</dt>
|
||||
<dd>{runtime.memory_usage ?? "—"}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Сеть I/O</dt>
|
||||
<dd>{runtime.network_io ?? "—"}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Процессы</dt>
|
||||
<dd>{runtime.pids ?? "—"}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</GlassSurface>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import type { StatusTone } from "@nodedc/ui-react";
|
||||
|
||||
import type { WorkerRuntime } from "../../core/system/workerTelemetry";
|
||||
|
||||
export function formatOptionalPercent(value: number | null | undefined): string {
|
||||
return typeof value === "number" && Number.isFinite(value)
|
||||
? `${new Intl.NumberFormat("ru-RU", { maximumFractionDigits: 1 }).format(value)}%`
|
||||
: "—";
|
||||
}
|
||||
|
||||
export function formatBytes(value: number | null | undefined): string {
|
||||
if (typeof value !== "number" || !Number.isFinite(value)) return "—";
|
||||
const units = ["Б", "КиБ", "МиБ", "ГиБ", "ТиБ"];
|
||||
let current = Math.max(0, value);
|
||||
let unit = 0;
|
||||
while (current >= 1024 && unit < units.length - 1) {
|
||||
current /= 1024;
|
||||
unit += 1;
|
||||
}
|
||||
return `${new Intl.NumberFormat("ru-RU", {
|
||||
maximumFractionDigits: current >= 100 ? 0 : 1,
|
||||
}).format(current)} ${units[unit]}`;
|
||||
}
|
||||
|
||||
export function formatRate(value: number | null | undefined): string {
|
||||
const formatted = formatBytes(value);
|
||||
return formatted === "—" ? formatted : `${formatted}/с`;
|
||||
}
|
||||
|
||||
export function formatBitRate(value: number | null | undefined): string {
|
||||
if (typeof value !== "number" || !Number.isFinite(value)) return "—";
|
||||
const units = ["бит/с", "Кбит/с", "Мбит/с", "Гбит/с"];
|
||||
let current = Math.max(0, value);
|
||||
let unit = 0;
|
||||
while (current >= 1000 && unit < units.length - 1) {
|
||||
current /= 1000;
|
||||
unit += 1;
|
||||
}
|
||||
return `${new Intl.NumberFormat("ru-RU", {
|
||||
maximumFractionDigits: current >= 100 ? 0 : 1,
|
||||
}).format(current)} ${units[unit]}`;
|
||||
}
|
||||
|
||||
export function formatDuration(value: number | null | undefined): string {
|
||||
if (typeof value !== "number" || !Number.isFinite(value)) return "—";
|
||||
const hours = Math.floor(value / 3600);
|
||||
if (hours < 24) return `${hours} ч`;
|
||||
return `${Math.floor(hours / 24)} д ${hours % 24} ч`;
|
||||
}
|
||||
|
||||
export function formatLatency(value: number | null | undefined): string {
|
||||
if (typeof value !== "number" || !Number.isFinite(value)) return "—";
|
||||
return `${Math.round(value)} мс`;
|
||||
}
|
||||
|
||||
export function runtimeTone(runtime: WorkerRuntime): StatusTone {
|
||||
if (runtime.state !== "running") return "danger";
|
||||
if (runtime.health && runtime.health !== "healthy") return "warning";
|
||||
return runtime.external ? "neutral" : "success";
|
||||
}
|
||||
|
||||
export function runtimeStateLabel(runtime: WorkerRuntime): string {
|
||||
if (runtime.state !== "running") return "Недоступен";
|
||||
if (runtime.health === "healthy") return "Работает";
|
||||
if (runtime.health) return runtime.health;
|
||||
return "Запущен";
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import { useCallback, useMemo, useState, type ReactNode } from "react";
|
||||
import type { ApplicationPanelUtilityAction } from "@nodedc/ui-react";
|
||||
|
||||
import { useComputeContours } from "../../core/system/ComputeContourContext";
|
||||
import { ComputeContourSettingsWindow } from "./ComputeContourSettingsWindow";
|
||||
|
||||
interface ComputeContourSettingsController {
|
||||
canEdit: boolean;
|
||||
openCreate: () => void;
|
||||
openEdit: () => void;
|
||||
utilityActions: readonly ApplicationPanelUtilityAction[];
|
||||
window: ReactNode;
|
||||
}
|
||||
|
||||
export function useComputeContourSettings(): ComputeContourSettingsController {
|
||||
const contours = useComputeContours();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [mode, setMode] = useState<"create" | "edit">("edit");
|
||||
const openCreate = useCallback(() => {
|
||||
setMode("create");
|
||||
setOpen(true);
|
||||
}, []);
|
||||
const openEdit = useCallback(() => {
|
||||
setMode("edit");
|
||||
setOpen(true);
|
||||
}, []);
|
||||
const utilityActions = useMemo<readonly ApplicationPanelUtilityAction[]>(() => [
|
||||
{
|
||||
label: "Обновить телеметрию выбранного контура",
|
||||
icon: "refresh",
|
||||
disabled: contours.selectedContour === null,
|
||||
onClick: contours.refreshTelemetry,
|
||||
},
|
||||
{
|
||||
label: "Настроить выбранный вычислительный контур",
|
||||
icon: "settings",
|
||||
disabled: contours.selectedContour === null,
|
||||
onClick: openEdit,
|
||||
},
|
||||
], [
|
||||
contours.refreshTelemetry,
|
||||
contours.selectedContour,
|
||||
openEdit,
|
||||
]);
|
||||
|
||||
return {
|
||||
canEdit: contours.selectedContour !== null,
|
||||
openCreate,
|
||||
openEdit,
|
||||
utilityActions,
|
||||
window: (
|
||||
<ComputeContourSettingsWindow
|
||||
open={open}
|
||||
mode={mode}
|
||||
contour={contours.selectedContour}
|
||||
onClose={() => setOpen(false)}
|
||||
onCreate={contours.createContour}
|
||||
onUpdate={contours.updateContour}
|
||||
/>
|
||||
),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
import { useMemo } from "react";
|
||||
import type { ApplicationPanelUtilityAction } from "@nodedc/ui-react";
|
||||
|
||||
import type { WorkspaceDefinition } from "../productModel";
|
||||
|
||||
interface ApplicationPanelActionsOptions {
|
||||
definition: WorkspaceDefinition | null;
|
||||
onAddVehicle?: () => void;
|
||||
refreshRuntime: () => void;
|
||||
resetConnectionScenario?: () => Promise<boolean>;
|
||||
connectionScenarioResetting: boolean;
|
||||
saveWorkspaceLayout: () => Promise<void>;
|
||||
workspaceLayoutSaving: boolean;
|
||||
systemUtilityActions: readonly ApplicationPanelUtilityAction[];
|
||||
}
|
||||
|
||||
export function deviceRuntimeUtilityAction({
|
||||
refreshRuntime,
|
||||
resetConnectionScenario,
|
||||
connectionScenarioResetting,
|
||||
}: Pick<
|
||||
ApplicationPanelActionsOptions,
|
||||
"refreshRuntime" | "resetConnectionScenario" | "connectionScenarioResetting"
|
||||
>): ApplicationPanelUtilityAction {
|
||||
return {
|
||||
label: resetConnectionScenario
|
||||
? connectionScenarioResetting
|
||||
? "Сбрасываем подключение"
|
||||
: "Сбросить подключение"
|
||||
: "Обновить состояние локального контура",
|
||||
icon: resetConnectionScenario && connectionScenarioResetting
|
||||
? "activity"
|
||||
: "refresh",
|
||||
disabled: resetConnectionScenario && connectionScenarioResetting
|
||||
? true
|
||||
: undefined,
|
||||
onClick: resetConnectionScenario
|
||||
? connectionScenarioResetting
|
||||
? () => undefined
|
||||
: () => void resetConnectionScenario()
|
||||
: refreshRuntime,
|
||||
};
|
||||
}
|
||||
|
||||
export function useApplicationPanelActions({
|
||||
definition,
|
||||
onAddVehicle,
|
||||
refreshRuntime,
|
||||
resetConnectionScenario,
|
||||
connectionScenarioResetting,
|
||||
saveWorkspaceLayout,
|
||||
workspaceLayoutSaving,
|
||||
systemUtilityActions,
|
||||
}: ApplicationPanelActionsOptions): ApplicationPanelUtilityAction[] {
|
||||
return useMemo(() => {
|
||||
const actions: ApplicationPanelUtilityAction[] = [];
|
||||
if (definition?.kind === "vehicles" && onAddVehicle) actions.push({ label: "Добавить аппарат", icon: "plus", onClick: onAddVehicle });
|
||||
if (definition?.kind === "device") {
|
||||
actions.push(deviceRuntimeUtilityAction({
|
||||
refreshRuntime,
|
||||
resetConnectionScenario,
|
||||
connectionScenarioResetting,
|
||||
}));
|
||||
}
|
||||
if (definition && ["spatial", "recordings"].includes(definition.kind)) {
|
||||
actions.push({
|
||||
label: workspaceLayoutSaving ? "Сохраняем компоновку" : "Сохранить компоновку",
|
||||
icon: "save",
|
||||
disabled: workspaceLayoutSaving,
|
||||
onClick: () => void saveWorkspaceLayout(),
|
||||
});
|
||||
}
|
||||
if (definition?.root === "system") actions.push(...systemUtilityActions);
|
||||
return actions;
|
||||
}, [
|
||||
definition,
|
||||
onAddVehicle,
|
||||
refreshRuntime,
|
||||
resetConnectionScenario,
|
||||
connectionScenarioResetting,
|
||||
saveWorkspaceLayout,
|
||||
systemUtilityActions,
|
||||
workspaceLayoutSaving,
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import type { DeviceUiPlugin } from "../core/device-plugins/contracts";
|
||||
import { xgridsK1Plugin } from "@xgrids-k1/frontend/plugin";
|
||||
|
||||
// Composition root: this is the only place where Mission Core chooses which
|
||||
// statically reviewed device plugins are shipped in the current build.
|
||||
export const installedDevicePlugins: readonly DeviceUiPlugin[] = Object.freeze([
|
||||
xgridsK1Plugin,
|
||||
]);
|
||||
@@ -0,0 +1,91 @@
|
||||
export interface ArtifactScope {
|
||||
scope_id: string;
|
||||
file_count: number;
|
||||
logical_bytes: number;
|
||||
modified_at_utc: string | null;
|
||||
}
|
||||
|
||||
export interface ArtifactHealth {
|
||||
schema_version: "missioncore.artifact-health/v1";
|
||||
generated_at_utc: string;
|
||||
overall_state: "live" | "attention" | "unavailable";
|
||||
inventory: {
|
||||
state: "live" | "degraded" | "unavailable";
|
||||
scanned_at_utc: string;
|
||||
scan_duration_ms: number;
|
||||
file_count: number;
|
||||
logical_bytes: number;
|
||||
unreadable_entries: number;
|
||||
files_delta_since_previous: number | null;
|
||||
bytes_delta_since_previous: number | null;
|
||||
scopes: ArtifactScope[];
|
||||
};
|
||||
exact_audit: {
|
||||
state: "exact-current" | "exact-stale" | "unavailable";
|
||||
result_id: string | null;
|
||||
audited_at_utc: string | null;
|
||||
root_count: number;
|
||||
file_count: number;
|
||||
logical_bytes: number;
|
||||
unique_content_bytes: number;
|
||||
duplicate_bytes: number;
|
||||
duplicate_fraction: number;
|
||||
amplification_ratio: number;
|
||||
duplicate_content_groups: number;
|
||||
changed_roots: string[];
|
||||
limitations: string[];
|
||||
};
|
||||
content_references: {
|
||||
state: "not-indexed" | "indexed";
|
||||
storage_model:
|
||||
| "materialized-copies"
|
||||
| "materialized-copies-with-reference-index";
|
||||
result_id: string | null;
|
||||
indexed_at_utc: string | null;
|
||||
logical_reference_count: number;
|
||||
canonical_content_count: number;
|
||||
duplicate_reference_count: number;
|
||||
exact_duplicate_bytes: number;
|
||||
physical_reclamation_applied: false;
|
||||
storage_migration_authorized: false;
|
||||
next_gate: string;
|
||||
};
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
||||
}
|
||||
|
||||
export async function fetchArtifactHealth(
|
||||
signal?: AbortSignal,
|
||||
): Promise<ArtifactHealth> {
|
||||
const response = await fetch("/api/v1/data/artifact-health", {
|
||||
method: "GET",
|
||||
headers: { Accept: "application/json" },
|
||||
signal,
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`API здоровья данных вернул HTTP ${response.status}.`);
|
||||
}
|
||||
const document: unknown = await response.json();
|
||||
if (
|
||||
!isRecord(document)
|
||||
|| document.schema_version !== "missioncore.artifact-health/v1"
|
||||
|| !isRecord(document.inventory)
|
||||
|| !Array.isArray(document.inventory.scopes)
|
||||
|| !isRecord(document.exact_audit)
|
||||
|| !isRecord(document.content_references)
|
||||
|| !["not-indexed", "indexed"].includes(
|
||||
String(document.content_references.state),
|
||||
)
|
||||
|| ![
|
||||
"materialized-copies",
|
||||
"materialized-copies-with-reference-index",
|
||||
].includes(String(document.content_references.storage_model))
|
||||
|| document.content_references.physical_reclamation_applied !== false
|
||||
|| document.content_references.storage_migration_authorized !== false
|
||||
) {
|
||||
throw new Error("Ответ здоровья данных не соответствует контракту.");
|
||||
}
|
||||
return document as unknown as ArtifactHealth;
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
import {
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
|
||||
import { IdleMissionRuntimeProvider } from "../runtime/MissionRuntimeContext";
|
||||
import type { DeviceUiPlugin, RegisteredDeviceModel } from "./contracts";
|
||||
import { createDevicePluginRegistry, type DevicePluginRegistry } from "./registry";
|
||||
|
||||
interface DevicePluginHostValue {
|
||||
registry: DevicePluginRegistry;
|
||||
selection: RegisteredDeviceModel | null;
|
||||
selectionTransitionPending: boolean;
|
||||
selectionTransitionError: string | null;
|
||||
selectModel: (modelId: string) => Promise<boolean>;
|
||||
clearSelection: () => Promise<boolean>;
|
||||
}
|
||||
|
||||
const DevicePluginHostContext = createContext<DevicePluginHostValue | null>(null);
|
||||
|
||||
export const DEVICE_PLUGIN_SELECTED_MODEL_STORAGE_KEY =
|
||||
"nodedc.mission-core.device-model-selection.v1";
|
||||
|
||||
export interface DeviceModelSelectionStorage {
|
||||
getItem: (key: string) => string | null;
|
||||
setItem: (key: string, value: string) => void;
|
||||
removeItem: (key: string) => void;
|
||||
}
|
||||
|
||||
function browserDeviceModelSelectionStorage(): DeviceModelSelectionStorage | null {
|
||||
if (typeof window === "undefined") return null;
|
||||
try {
|
||||
return window.localStorage;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore only a model that is present in the current reviewed registry.
|
||||
* Removed/renamed models and malformed browser values fail closed to the
|
||||
* picker and are cleared so a later remount cannot keep retrying stale state.
|
||||
*/
|
||||
export function restorePersistedDeviceModelId(
|
||||
registry: DevicePluginRegistry,
|
||||
storage: DeviceModelSelectionStorage | null,
|
||||
): string | null {
|
||||
if (!storage) return null;
|
||||
try {
|
||||
const stored = storage.getItem(DEVICE_PLUGIN_SELECTED_MODEL_STORAGE_KEY);
|
||||
const modelId = stored?.trim() || null;
|
||||
if (modelId && registry.resolveModel(modelId)) return modelId;
|
||||
if (stored !== null) {
|
||||
storage.removeItem(DEVICE_PLUGIN_SELECTED_MODEL_STORAGE_KEY);
|
||||
}
|
||||
return null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Persist only an already-admitted host transition; browser storage is never authority. */
|
||||
export function commitPersistedDeviceModelId(
|
||||
modelId: string | null,
|
||||
storage: DeviceModelSelectionStorage | null,
|
||||
): void {
|
||||
if (!storage) return;
|
||||
try {
|
||||
if (modelId === null) {
|
||||
storage.removeItem(DEVICE_PLUGIN_SELECTED_MODEL_STORAGE_KEY);
|
||||
return;
|
||||
}
|
||||
storage.setItem(DEVICE_PLUGIN_SELECTED_MODEL_STORAGE_KEY, modelId);
|
||||
} catch {
|
||||
// A denied/full localStorage must not block the in-memory host transition.
|
||||
}
|
||||
}
|
||||
|
||||
export function DevicePluginHostProvider({
|
||||
plugins,
|
||||
children,
|
||||
}: {
|
||||
plugins: readonly DeviceUiPlugin[];
|
||||
children: ReactNode;
|
||||
}) {
|
||||
const registry = useMemo(() => createDevicePluginRegistry(plugins), [plugins]);
|
||||
const selectionStorage = useMemo(browserDeviceModelSelectionStorage, []);
|
||||
const [selectedModelId, setSelectedModelId] = useState<string | null>(() =>
|
||||
restorePersistedDeviceModelId(registry, selectionStorage)
|
||||
);
|
||||
const [selectionTransitionPending, setSelectionTransitionPending] = useState(false);
|
||||
const [selectionTransitionError, setSelectionTransitionError] = useState<string | null>(null);
|
||||
const transitionInFlight = useRef(false);
|
||||
const deactivationHandlers = useRef(new Map<string, () => Promise<boolean>>());
|
||||
const selection = selectedModelId ? registry.resolveModel(selectedModelId) : null;
|
||||
|
||||
const registerDeactivation = useCallback(
|
||||
(pluginId: string, handler: () => Promise<boolean>) => {
|
||||
deactivationHandlers.current.set(pluginId, handler);
|
||||
return () => {
|
||||
if (deactivationHandlers.current.get(pluginId) === handler) {
|
||||
deactivationHandlers.current.delete(pluginId);
|
||||
}
|
||||
};
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const transitionTo = useCallback(
|
||||
async (nextModelId: string | null): Promise<boolean> => {
|
||||
if (transitionInFlight.current) return false;
|
||||
if (nextModelId !== null && !registry.resolveModel(nextModelId)) {
|
||||
throw new Error(`Модель устройства не зарегистрирована: ${nextModelId}.`);
|
||||
}
|
||||
if (nextModelId === selectedModelId) {
|
||||
// `clearSelection()` must clear a stale persisted value even when the
|
||||
// current in-memory selection is already empty.
|
||||
commitPersistedDeviceModelId(nextModelId, selectionStorage);
|
||||
return true;
|
||||
}
|
||||
|
||||
transitionInFlight.current = true;
|
||||
setSelectionTransitionPending(true);
|
||||
setSelectionTransitionError(null);
|
||||
try {
|
||||
const current = selectedModelId ? registry.resolveModel(selectedModelId) : null;
|
||||
const deactivate = current
|
||||
? deactivationHandlers.current.get(current.plugin.manifest.metadata.id)
|
||||
: undefined;
|
||||
if (current && !deactivate) {
|
||||
setSelectionTransitionError(
|
||||
"Активный плагин не зарегистрировал безопасное завершение сессии.",
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (deactivate) {
|
||||
try {
|
||||
if (!(await deactivate())) {
|
||||
setSelectionTransitionError(
|
||||
"Плагин не подтвердил завершение текущей сессии.",
|
||||
);
|
||||
return false;
|
||||
}
|
||||
} catch {
|
||||
setSelectionTransitionError(
|
||||
"Не удалось безопасно завершить текущую сессию плагина.",
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
setSelectedModelId(nextModelId);
|
||||
commitPersistedDeviceModelId(nextModelId, selectionStorage);
|
||||
return true;
|
||||
} finally {
|
||||
transitionInFlight.current = false;
|
||||
setSelectionTransitionPending(false);
|
||||
}
|
||||
},
|
||||
[registry, selectedModelId, selectionStorage],
|
||||
);
|
||||
|
||||
const deactivationRegistrars = useMemo(
|
||||
() =>
|
||||
new Map(
|
||||
registry.plugins.map((plugin) => [
|
||||
plugin.manifest.metadata.id,
|
||||
(handler: () => Promise<boolean>) =>
|
||||
registerDeactivation(plugin.manifest.metadata.id, handler),
|
||||
]),
|
||||
),
|
||||
[registerDeactivation, registry.plugins],
|
||||
);
|
||||
|
||||
const value = useMemo<DevicePluginHostValue>(
|
||||
() => ({
|
||||
registry,
|
||||
selection,
|
||||
selectionTransitionPending,
|
||||
selectionTransitionError,
|
||||
selectModel: (modelId) => transitionTo(modelId),
|
||||
clearSelection: () => transitionTo(null),
|
||||
}),
|
||||
[
|
||||
registry,
|
||||
selection,
|
||||
selectionTransitionError,
|
||||
selectionTransitionPending,
|
||||
transitionTo,
|
||||
],
|
||||
);
|
||||
|
||||
const runtimeTree = [...registry.plugins].reverse().reduce<ReactNode>((child, plugin) => {
|
||||
const RuntimeProvider = plugin.RuntimeProvider;
|
||||
return (
|
||||
<RuntimeProvider
|
||||
key={plugin.manifest.metadata.id}
|
||||
activeModel={
|
||||
selection?.plugin.manifest.metadata.id === plugin.manifest.metadata.id
|
||||
? selection.model
|
||||
: null
|
||||
}
|
||||
registerDeactivation={deactivationRegistrars.get(plugin.manifest.metadata.id)!}
|
||||
>
|
||||
{child}
|
||||
</RuntimeProvider>
|
||||
);
|
||||
}, children);
|
||||
|
||||
return (
|
||||
<DevicePluginHostContext.Provider value={value}>
|
||||
<IdleMissionRuntimeProvider>{runtimeTree}</IdleMissionRuntimeProvider>
|
||||
</DevicePluginHostContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useDevicePluginHost(): DevicePluginHostValue {
|
||||
const value = useContext(DevicePluginHostContext);
|
||||
if (!value) {
|
||||
throw new Error("DevicePluginHostProvider не подключён в composition root.");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
import type { ComponentType, ReactNode } from "react";
|
||||
|
||||
export const DEVICE_PLUGIN_API_VERSION_V1ALPHA1 = "missioncore.nodedc/v1alpha1" as const;
|
||||
export const DEVICE_PLUGIN_API_VERSION_V1ALPHA2 = "missioncore.nodedc/v1alpha2" as const;
|
||||
export const DEVICE_PLUGIN_API_VERSION = DEVICE_PLUGIN_API_VERSION_V1ALPHA2;
|
||||
export const SUPPORTED_DEVICE_PLUGIN_API_VERSIONS = Object.freeze([
|
||||
DEVICE_PLUGIN_API_VERSION_V1ALPHA1,
|
||||
DEVICE_PLUGIN_API_VERSION_V1ALPHA2,
|
||||
] as const);
|
||||
export const DEVICE_STATE_READ_ACTION_ID = "state.read" as const;
|
||||
|
||||
export type DevicePluginApiVersion =
|
||||
(typeof SUPPORTED_DEVICE_PLUGIN_API_VERSIONS)[number];
|
||||
|
||||
export interface DeviceCapability {
|
||||
id: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export interface DevicePluginActionDefinition {
|
||||
id: string;
|
||||
mutating: boolean;
|
||||
secretFields: readonly string[];
|
||||
}
|
||||
|
||||
export interface DeviceModelDefinition {
|
||||
id: string;
|
||||
vendor: string;
|
||||
displayName: string;
|
||||
category: string;
|
||||
description: string;
|
||||
verified: boolean;
|
||||
capabilities: readonly DeviceCapability[];
|
||||
ui: {
|
||||
slot: "device.connection";
|
||||
componentKey: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface DeviceCompatibilityProfileDefinition {
|
||||
profileId: string;
|
||||
path: string;
|
||||
modelId: string;
|
||||
}
|
||||
|
||||
interface DevicePluginManifestBase {
|
||||
apiVersion: DevicePluginApiVersion;
|
||||
kind: "DevicePlugin";
|
||||
metadata: {
|
||||
id: string;
|
||||
version: string;
|
||||
displayName: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface DevicePluginManifestV1Alpha1 extends DevicePluginManifestBase {
|
||||
apiVersion: typeof DEVICE_PLUGIN_API_VERSION_V1ALPHA1;
|
||||
spec: {
|
||||
hostApiRange: "v1alpha1";
|
||||
runtime: {
|
||||
backendEntrypoint: string;
|
||||
isolation: "transitional-in-process";
|
||||
};
|
||||
permissions: readonly string[];
|
||||
actions: readonly DevicePluginActionDefinition[];
|
||||
models: readonly DeviceModelDefinition[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface DevicePluginManifestV1Alpha2 extends DevicePluginManifestBase {
|
||||
apiVersion: typeof DEVICE_PLUGIN_API_VERSION_V1ALPHA2;
|
||||
spec: {
|
||||
hostApiRange: "v1alpha2";
|
||||
runtime: {
|
||||
backendEntrypoint: string;
|
||||
isolation: "transitional-in-process";
|
||||
};
|
||||
permissions: readonly string[];
|
||||
actions: readonly DevicePluginActionDefinition[];
|
||||
models: readonly DeviceModelDefinition[];
|
||||
compatibilityProfiles: readonly DeviceCompatibilityProfileDefinition[];
|
||||
};
|
||||
}
|
||||
|
||||
export type DevicePluginManifest =
|
||||
| DevicePluginManifestV1Alpha1
|
||||
| DevicePluginManifestV1Alpha2;
|
||||
|
||||
export function isDevicePluginManifestV1Alpha2(
|
||||
manifest: DevicePluginManifest,
|
||||
): manifest is DevicePluginManifestV1Alpha2 {
|
||||
return manifest.apiVersion === DEVICE_PLUGIN_API_VERSION_V1ALPHA2;
|
||||
}
|
||||
|
||||
export interface DevicePluginHostActions {
|
||||
openSpatialScene: () => void;
|
||||
activateAutomaticSpatialSource: () => void;
|
||||
}
|
||||
|
||||
export interface DevicePluginConnectionProps {
|
||||
model: DeviceModelDefinition;
|
||||
host: DevicePluginHostActions;
|
||||
}
|
||||
|
||||
export interface DeviceUiPlugin {
|
||||
manifest: DevicePluginManifest;
|
||||
RuntimeProvider: ComponentType<{
|
||||
activeModel: DeviceModelDefinition | null;
|
||||
registerDeactivation: (handler: () => Promise<boolean>) => () => void;
|
||||
children: ReactNode;
|
||||
}>;
|
||||
connectionViews: Readonly<Record<string, ComponentType<DevicePluginConnectionProps>>>;
|
||||
SpatialControlsView?: ComponentType<DevicePluginConnectionProps>;
|
||||
}
|
||||
|
||||
export interface RegisteredDeviceModel {
|
||||
plugin: DeviceUiPlugin;
|
||||
model: DeviceModelDefinition;
|
||||
ConnectionView: ComponentType<DevicePluginConnectionProps>;
|
||||
SpatialControlsView?: ComponentType<DevicePluginConnectionProps>;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Public frontend host surface for statically reviewed device plugins.
|
||||
*
|
||||
* Plugin source may depend on this module through the
|
||||
* `@mission-core/plugin-sdk` alias. It must not reach into Control Station
|
||||
* implementation paths directly.
|
||||
*/
|
||||
export * from "./contracts";
|
||||
export { parseDevicePluginManifest, requirePluginAction } from "./manifestParser";
|
||||
export {
|
||||
MissionRuntimeProvider,
|
||||
useMissionRuntime,
|
||||
} from "../runtime/MissionRuntimeContext";
|
||||
export type * from "../runtime/contracts";
|
||||
@@ -0,0 +1,313 @@
|
||||
import {
|
||||
DEVICE_PLUGIN_API_VERSION_V1ALPHA1,
|
||||
DEVICE_PLUGIN_API_VERSION_V1ALPHA2,
|
||||
type DeviceCapability,
|
||||
type DeviceCompatibilityProfileDefinition,
|
||||
type DeviceModelDefinition,
|
||||
type DevicePluginActionDefinition,
|
||||
type DevicePluginManifest,
|
||||
type DevicePluginManifestV1Alpha1,
|
||||
type DevicePluginManifestV1Alpha2,
|
||||
} from "./contracts";
|
||||
|
||||
function record(value: unknown, path: string): Record<string, unknown> {
|
||||
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
||||
throw new Error(`Некорректный manifest: ${path} должен быть объектом.`);
|
||||
}
|
||||
return value as Record<string, unknown>;
|
||||
}
|
||||
|
||||
function exactKeys(
|
||||
value: Record<string, unknown>,
|
||||
path: string,
|
||||
allowed: readonly string[],
|
||||
): void {
|
||||
const extras = Object.keys(value).filter((key) => !allowed.includes(key));
|
||||
if (extras.length) {
|
||||
throw new Error(`Некорректный manifest: ${path} содержит неизвестные поля ${extras.join(", ")}.`);
|
||||
}
|
||||
}
|
||||
|
||||
function text(value: unknown, path: string, maxLength = 160): string {
|
||||
if (typeof value !== "string" || !value.trim()) {
|
||||
throw new Error(`Некорректный manifest: ${path} должен быть непустой строкой.`);
|
||||
}
|
||||
if (value.length > maxLength) {
|
||||
throw new Error(
|
||||
`Некорректный manifest: ${path} длиннее ${maxLength} символов.`,
|
||||
);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function identifier(value: unknown, path: string): string {
|
||||
const candidate = text(value, path, 192);
|
||||
if (!/^[A-Za-z0-9][A-Za-z0-9._:/-]*$/.test(candidate)) {
|
||||
throw new Error(`Некорректный manifest: ${path} не является идентификатором.`);
|
||||
}
|
||||
return candidate;
|
||||
}
|
||||
|
||||
function flag(value: unknown, path: string): boolean {
|
||||
if (typeof value !== "boolean") {
|
||||
throw new Error(`Некорректный manifest: ${path} должен быть boolean.`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function list(value: unknown, path: string): unknown[] {
|
||||
if (!Array.isArray(value)) {
|
||||
throw new Error(`Некорректный manifest: ${path} должен быть массивом.`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function contractText(value: unknown, path: string, strictIdentifiers: boolean): string {
|
||||
return strictIdentifiers ? identifier(value, path) : text(value, path);
|
||||
}
|
||||
|
||||
function capability(
|
||||
value: unknown,
|
||||
path: string,
|
||||
strictIdentifiers: boolean,
|
||||
): DeviceCapability {
|
||||
const item = record(value, path);
|
||||
exactKeys(item, path, ["id", "label"]);
|
||||
return {
|
||||
id: contractText(item.id, `${path}.id`, strictIdentifiers),
|
||||
label: text(item.label, `${path}.label`),
|
||||
};
|
||||
}
|
||||
|
||||
function action(
|
||||
value: unknown,
|
||||
path: string,
|
||||
strictIdentifiers: boolean,
|
||||
): DevicePluginActionDefinition {
|
||||
const item = record(value, path);
|
||||
exactKeys(item, path, ["id", "mutating", "secretFields"]);
|
||||
return {
|
||||
id: contractText(item.id, `${path}.id`, strictIdentifiers),
|
||||
mutating: flag(item.mutating, `${path}.mutating`),
|
||||
secretFields: list(item.secretFields, `${path}.secretFields`).map((field, index) =>
|
||||
contractText(field, `${path}.secretFields[${index}]`, strictIdentifiers),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
function model(
|
||||
value: unknown,
|
||||
path: string,
|
||||
strictIdentifiers: boolean,
|
||||
): DeviceModelDefinition {
|
||||
const item = record(value, path);
|
||||
exactKeys(item, path, [
|
||||
"id",
|
||||
"vendor",
|
||||
"displayName",
|
||||
"category",
|
||||
"description",
|
||||
"verified",
|
||||
"capabilities",
|
||||
"ui",
|
||||
]);
|
||||
const ui = record(item.ui, `${path}.ui`);
|
||||
exactKeys(ui, `${path}.ui`, ["slot", "componentKey"]);
|
||||
const slot = text(ui.slot, `${path}.ui.slot`);
|
||||
if (slot !== "device.connection") {
|
||||
throw new Error(`Некорректный manifest: слот ${slot} пока не поддерживается.`);
|
||||
}
|
||||
return {
|
||||
id: contractText(item.id, `${path}.id`, strictIdentifiers),
|
||||
vendor: text(item.vendor, `${path}.vendor`),
|
||||
displayName: text(item.displayName, `${path}.displayName`),
|
||||
category: text(item.category, `${path}.category`),
|
||||
description: text(item.description, `${path}.description`, 1024),
|
||||
verified: flag(item.verified, `${path}.verified`),
|
||||
capabilities: list(item.capabilities, `${path}.capabilities`).map((entry, index) =>
|
||||
capability(entry, `${path}.capabilities[${index}]`, strictIdentifiers),
|
||||
),
|
||||
ui: {
|
||||
slot,
|
||||
componentKey: text(ui.componentKey, `${path}.ui.componentKey`),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function compatibilityProfile(
|
||||
value: unknown,
|
||||
path: string,
|
||||
): DeviceCompatibilityProfileDefinition {
|
||||
const item = record(value, path);
|
||||
exactKeys(item, path, ["profileId", "path", "modelId"]);
|
||||
const profilePath = text(item.path, `${path}.path`, 512);
|
||||
const pathSegments = profilePath.split("/");
|
||||
if (
|
||||
profilePath.startsWith("/") ||
|
||||
profilePath.includes("\\") ||
|
||||
!profilePath.endsWith(".json") ||
|
||||
pathSegments.some((segment) => !segment || segment === "." || segment === "..")
|
||||
) {
|
||||
throw new Error(
|
||||
`Некорректный manifest: ${path}.path должен быть безопасным относительным JSON-путём.`,
|
||||
);
|
||||
}
|
||||
return {
|
||||
profileId: identifier(item.profileId, `${path}.profileId`),
|
||||
path: profilePath,
|
||||
modelId: identifier(item.modelId, `${path}.modelId`),
|
||||
};
|
||||
}
|
||||
|
||||
function validateV1Alpha2Profiles(
|
||||
models: readonly DeviceModelDefinition[],
|
||||
profiles: readonly DeviceCompatibilityProfileDefinition[],
|
||||
): void {
|
||||
if (!profiles.length) {
|
||||
throw new Error("Manifest v1alpha2 должен объявлять compatibilityProfiles.");
|
||||
}
|
||||
const modelIds = new Set(models.map((modelItem) => modelItem.id));
|
||||
const profileIds = new Set<string>();
|
||||
const profilePaths = new Set<string>();
|
||||
const coveredModels = new Set<string>();
|
||||
for (const profile of profiles) {
|
||||
if (profileIds.has(profile.profileId) || profilePaths.has(profile.path)) {
|
||||
throw new Error(
|
||||
`Manifest v1alpha2 повторяет профиль ${profile.profileId} или его путь.`,
|
||||
);
|
||||
}
|
||||
profileIds.add(profile.profileId);
|
||||
profilePaths.add(profile.path);
|
||||
if (!modelIds.has(profile.modelId)) {
|
||||
throw new Error(
|
||||
`Профиль ${profile.profileId} ссылается на неизвестную модель ${profile.modelId}.`,
|
||||
);
|
||||
}
|
||||
coveredModels.add(profile.modelId);
|
||||
}
|
||||
const uncovered = [...modelIds].filter((modelId) => !coveredModels.has(modelId));
|
||||
if (uncovered.length) {
|
||||
throw new Error(
|
||||
`Manifest v1alpha2 не содержит разрешённого профиля для моделей: ${uncovered.join(", ")}.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function parseDevicePluginManifest(document: unknown): DevicePluginManifest {
|
||||
const root = record(document, "root");
|
||||
exactKeys(root, "root", ["apiVersion", "kind", "metadata", "spec"]);
|
||||
const apiVersion = root.apiVersion;
|
||||
if (
|
||||
(apiVersion !== DEVICE_PLUGIN_API_VERSION_V1ALPHA1 &&
|
||||
apiVersion !== DEVICE_PLUGIN_API_VERSION_V1ALPHA2) ||
|
||||
root.kind !== "DevicePlugin"
|
||||
) {
|
||||
throw new Error("Manifest использует несовместимую версию или kind.");
|
||||
}
|
||||
const metadata = record(root.metadata, "metadata");
|
||||
exactKeys(metadata, "metadata", ["id", "version", "displayName"]);
|
||||
const spec = record(root.spec, "spec");
|
||||
const specKeys = [
|
||||
"hostApiRange",
|
||||
"runtime",
|
||||
"permissions",
|
||||
"actions",
|
||||
"models",
|
||||
];
|
||||
exactKeys(
|
||||
spec,
|
||||
"spec",
|
||||
apiVersion === DEVICE_PLUGIN_API_VERSION_V1ALPHA2
|
||||
? [...specKeys, "compatibilityProfiles"]
|
||||
: specKeys,
|
||||
);
|
||||
const expectedHostApiRange =
|
||||
apiVersion === DEVICE_PLUGIN_API_VERSION_V1ALPHA2 ? "v1alpha2" : "v1alpha1";
|
||||
if (spec.hostApiRange !== expectedHostApiRange) {
|
||||
throw new Error(`Manifest требует несовместимый host API: ${String(spec.hostApiRange)}.`);
|
||||
}
|
||||
const runtime = record(spec.runtime, "spec.runtime");
|
||||
exactKeys(runtime, "spec.runtime", ["backendEntrypoint", "isolation"]);
|
||||
const isolation = text(runtime.isolation, "spec.runtime.isolation");
|
||||
if (isolation !== "transitional-in-process") {
|
||||
throw new Error(`Некорректный manifest: неизвестная изоляция ${isolation}.`);
|
||||
}
|
||||
|
||||
const strictIdentifiers = apiVersion === DEVICE_PLUGIN_API_VERSION_V1ALPHA2;
|
||||
const models = list(spec.models, "spec.models").map((entry, index) =>
|
||||
model(entry, `spec.models[${index}]`, strictIdentifiers),
|
||||
);
|
||||
if (apiVersion === DEVICE_PLUGIN_API_VERSION_V1ALPHA1 && models.length !== 1) {
|
||||
throw new Error("Manifest v1alpha1 должен объявлять ровно одну модель.");
|
||||
}
|
||||
if (apiVersion === DEVICE_PLUGIN_API_VERSION_V1ALPHA2 && !models.length) {
|
||||
throw new Error("Manifest v1alpha2 должен объявлять хотя бы одну модель.");
|
||||
}
|
||||
|
||||
const version = text(metadata.version, "metadata.version");
|
||||
if (!/^[0-9]+\.[0-9]+\.[0-9]+(?:[-+][A-Za-z0-9.-]+)?$/.test(version)) {
|
||||
throw new Error(`Некорректный manifest: версия ${version} не является semver.`);
|
||||
}
|
||||
|
||||
const common = {
|
||||
kind: "DevicePlugin",
|
||||
metadata: {
|
||||
id: contractText(metadata.id, "metadata.id", strictIdentifiers),
|
||||
version,
|
||||
displayName: text(metadata.displayName, "metadata.displayName"),
|
||||
},
|
||||
} as const;
|
||||
const commonSpec = {
|
||||
runtime: {
|
||||
backendEntrypoint: text(
|
||||
runtime.backendEntrypoint,
|
||||
"spec.runtime.backendEntrypoint",
|
||||
256,
|
||||
),
|
||||
isolation,
|
||||
},
|
||||
permissions: list(spec.permissions, "spec.permissions").map((entry, index) =>
|
||||
contractText(entry, `spec.permissions[${index}]`, strictIdentifiers),
|
||||
),
|
||||
actions: list(spec.actions, "spec.actions").map((entry, index) =>
|
||||
action(entry, `spec.actions[${index}]`, strictIdentifiers),
|
||||
),
|
||||
models,
|
||||
} as const;
|
||||
|
||||
if (apiVersion === DEVICE_PLUGIN_API_VERSION_V1ALPHA1) {
|
||||
return {
|
||||
...common,
|
||||
apiVersion,
|
||||
spec: {
|
||||
...commonSpec,
|
||||
hostApiRange: "v1alpha1",
|
||||
},
|
||||
} satisfies DevicePluginManifestV1Alpha1;
|
||||
}
|
||||
|
||||
const profiles = list(spec.compatibilityProfiles, "spec.compatibilityProfiles").map(
|
||||
(entry, index) => compatibilityProfile(entry, `spec.compatibilityProfiles[${index}]`),
|
||||
);
|
||||
validateV1Alpha2Profiles(models, profiles);
|
||||
return {
|
||||
...common,
|
||||
apiVersion,
|
||||
spec: {
|
||||
...commonSpec,
|
||||
hostApiRange: "v1alpha2",
|
||||
compatibilityProfiles: profiles,
|
||||
},
|
||||
} satisfies DevicePluginManifestV1Alpha2;
|
||||
}
|
||||
|
||||
export function requirePluginAction(
|
||||
manifest: DevicePluginManifest,
|
||||
actionId: string,
|
||||
): string {
|
||||
const action = manifest.spec.actions.find((candidate) => candidate.id === actionId);
|
||||
if (!action) {
|
||||
throw new Error(`Плагин ${manifest.metadata.id} не объявляет действие ${actionId}.`);
|
||||
}
|
||||
return action.id;
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
import {
|
||||
DEVICE_PLUGIN_API_VERSION_V1ALPHA1,
|
||||
DEVICE_STATE_READ_ACTION_ID,
|
||||
SUPPORTED_DEVICE_PLUGIN_API_VERSIONS,
|
||||
isDevicePluginManifestV1Alpha2,
|
||||
type DeviceUiPlugin,
|
||||
type RegisteredDeviceModel,
|
||||
} from "./contracts";
|
||||
|
||||
export interface DevicePluginRegistry {
|
||||
readonly plugins: readonly DeviceUiPlugin[];
|
||||
readonly models: readonly RegisteredDeviceModel[];
|
||||
resolveModel: (modelId: string) => RegisteredDeviceModel | null;
|
||||
}
|
||||
|
||||
export function createDevicePluginRegistry(
|
||||
installedPlugins: readonly DeviceUiPlugin[],
|
||||
): DevicePluginRegistry {
|
||||
const pluginIds = new Set<string>();
|
||||
const modelIds = new Set<string>();
|
||||
const models: RegisteredDeviceModel[] = [];
|
||||
|
||||
for (const plugin of installedPlugins) {
|
||||
const { manifest } = plugin;
|
||||
if (!SUPPORTED_DEVICE_PLUGIN_API_VERSIONS.includes(manifest.apiVersion)) {
|
||||
throw new Error(
|
||||
`Плагин ${manifest.metadata.id} использует несовместимый контракт ${manifest.apiVersion}.`,
|
||||
);
|
||||
}
|
||||
if (manifest.kind !== "DevicePlugin") {
|
||||
throw new Error(`Неподдерживаемый kind плагина: ${manifest.kind}.`);
|
||||
}
|
||||
if (!manifest.metadata.id.trim() || pluginIds.has(manifest.metadata.id)) {
|
||||
throw new Error(
|
||||
`Идентификатор плагина пуст или повторяется: ${manifest.metadata.id || "<empty>"}.`,
|
||||
);
|
||||
}
|
||||
pluginIds.add(manifest.metadata.id);
|
||||
|
||||
if (
|
||||
manifest.apiVersion === DEVICE_PLUGIN_API_VERSION_V1ALPHA1 &&
|
||||
manifest.spec.models.length !== 1
|
||||
) {
|
||||
throw new Error(
|
||||
`Плагин ${manifest.metadata.id} должен объявлять ровно одну модель в v1alpha1.`,
|
||||
);
|
||||
}
|
||||
if (!manifest.spec.models.length) {
|
||||
throw new Error(`Плагин ${manifest.metadata.id} не объявляет ни одной модели.`);
|
||||
}
|
||||
|
||||
const permissions = new Set(manifest.spec.permissions);
|
||||
if (permissions.size !== manifest.spec.permissions.length) {
|
||||
throw new Error(`Плагин ${manifest.metadata.id} повторяет permission.`);
|
||||
}
|
||||
|
||||
const actionIds = new Set<string>();
|
||||
for (const action of manifest.spec.actions) {
|
||||
if (!action.id.trim() || actionIds.has(action.id)) {
|
||||
throw new Error(
|
||||
`Идентификатор действия пуст или повторяется: ${action.id || "<empty>"}.`,
|
||||
);
|
||||
}
|
||||
actionIds.add(action.id);
|
||||
if (new Set(action.secretFields).size !== action.secretFields.length) {
|
||||
throw new Error(`Действие ${action.id} повторяет secret field.`);
|
||||
}
|
||||
}
|
||||
const stateRead = manifest.spec.actions.find(
|
||||
(action) => action.id === DEVICE_STATE_READ_ACTION_ID,
|
||||
);
|
||||
if (!stateRead || stateRead.mutating || stateRead.secretFields.length) {
|
||||
throw new Error(
|
||||
`Плагин ${manifest.metadata.id} должен объявлять безопасное действие state.read.`,
|
||||
);
|
||||
}
|
||||
|
||||
for (const model of manifest.spec.models) {
|
||||
if (!model.id.trim() || modelIds.has(model.id)) {
|
||||
throw new Error(`Идентификатор модели пуст или повторяется: ${model.id || "<empty>"}.`);
|
||||
}
|
||||
modelIds.add(model.id);
|
||||
const capabilityIds = new Set(model.capabilities.map((capability) => capability.id));
|
||||
if (capabilityIds.size !== model.capabilities.length) {
|
||||
throw new Error(`Модель ${model.id} повторяет capability.`);
|
||||
}
|
||||
const ConnectionView = plugin.connectionViews[model.ui.componentKey];
|
||||
if (!ConnectionView) {
|
||||
throw new Error(
|
||||
`Плагин ${manifest.metadata.id} не реализует UI ${model.ui.componentKey}.`,
|
||||
);
|
||||
}
|
||||
models.push({
|
||||
plugin,
|
||||
model,
|
||||
ConnectionView,
|
||||
...(plugin.SpatialControlsView
|
||||
? { SpatialControlsView: plugin.SpatialControlsView }
|
||||
: {}),
|
||||
});
|
||||
}
|
||||
|
||||
if (isDevicePluginManifestV1Alpha2(manifest)) {
|
||||
const profileIds = new Set<string>();
|
||||
const profilePaths = new Set<string>();
|
||||
const declaredModelIds = new Set(manifest.spec.models.map((model) => model.id));
|
||||
const coveredModelIds = new Set<string>();
|
||||
if (!manifest.spec.compatibilityProfiles.length) {
|
||||
throw new Error(`Плагин ${manifest.metadata.id} не объявляет compatibilityProfiles.`);
|
||||
}
|
||||
for (const profile of manifest.spec.compatibilityProfiles) {
|
||||
if (profileIds.has(profile.profileId) || profilePaths.has(profile.path)) {
|
||||
throw new Error(`Плагин ${manifest.metadata.id} повторяет compatibility profile.`);
|
||||
}
|
||||
profileIds.add(profile.profileId);
|
||||
profilePaths.add(profile.path);
|
||||
if (!declaredModelIds.has(profile.modelId)) {
|
||||
throw new Error(
|
||||
`Профиль ${profile.profileId} ссылается на неизвестную модель ${profile.modelId}.`,
|
||||
);
|
||||
}
|
||||
coveredModelIds.add(profile.modelId);
|
||||
}
|
||||
const uncovered = [...declaredModelIds].filter(
|
||||
(modelId) => !coveredModelIds.has(modelId),
|
||||
);
|
||||
if (uncovered.length) {
|
||||
throw new Error(
|
||||
`Плагин ${manifest.metadata.id} не имеет разрешённого профиля для ${uncovered.join(", ")}.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const modelById = new Map(models.map((registered) => [registered.model.id, registered]));
|
||||
return {
|
||||
plugins: Object.freeze([...installedPlugins]),
|
||||
models: Object.freeze(models),
|
||||
resolveModel: (modelId) => modelById.get(modelId) ?? null,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,348 @@
|
||||
import { roots, type RootId } from "../../productModel";
|
||||
|
||||
export type EnvironmentSurfaceId = "home" | RootId;
|
||||
export type EnvironmentMediaKind = "image" | "video";
|
||||
export type EnvironmentMediaSource = "file" | "url";
|
||||
|
||||
export interface EnvironmentMediaItem {
|
||||
id: string;
|
||||
source: EnvironmentMediaSource;
|
||||
url: string | null;
|
||||
mediaKind: EnvironmentMediaKind | null;
|
||||
fileName: string | null;
|
||||
}
|
||||
|
||||
export interface EnvironmentBackground {
|
||||
enabled: boolean;
|
||||
imageDurationSeconds: number;
|
||||
items: EnvironmentMediaItem[];
|
||||
}
|
||||
|
||||
export interface EnvironmentPage {
|
||||
headerLabel: string;
|
||||
eyebrow: string;
|
||||
title: string;
|
||||
description: string;
|
||||
primaryWorkspaceId: string | null;
|
||||
secondaryWorkspaceId: string | null;
|
||||
background: EnvironmentBackground;
|
||||
}
|
||||
|
||||
export interface EnvironmentSettings {
|
||||
revision: number;
|
||||
pages: Record<EnvironmentSurfaceId, EnvironmentPage>;
|
||||
}
|
||||
|
||||
export interface UploadedEnvironmentMedia {
|
||||
surfaceId: EnvironmentSurfaceId;
|
||||
itemId: string;
|
||||
url: string;
|
||||
fileName: string;
|
||||
mediaKind: EnvironmentMediaKind;
|
||||
mediaType: string;
|
||||
byteLength: number;
|
||||
sha256: string;
|
||||
}
|
||||
|
||||
const surfaceIds: readonly EnvironmentSurfaceId[] = [
|
||||
"home",
|
||||
"fleet",
|
||||
"observation",
|
||||
"missions",
|
||||
"data",
|
||||
"system",
|
||||
"polygon",
|
||||
];
|
||||
|
||||
export const defaultEnvironmentImageDurationSeconds = 10;
|
||||
export const maxEnvironmentMediaItems = 24;
|
||||
|
||||
function emptyBackground(): EnvironmentBackground {
|
||||
return {
|
||||
enabled: false,
|
||||
imageDurationSeconds: defaultEnvironmentImageDurationSeconds,
|
||||
items: [],
|
||||
};
|
||||
}
|
||||
|
||||
export function createEnvironmentMediaItem(): EnvironmentMediaItem {
|
||||
return {
|
||||
id: `media-${crypto.randomUUID()}`,
|
||||
source: "file",
|
||||
url: null,
|
||||
mediaKind: null,
|
||||
fileName: null,
|
||||
};
|
||||
}
|
||||
|
||||
export function appendEnvironmentMediaItem(
|
||||
background: EnvironmentBackground,
|
||||
item: EnvironmentMediaItem = createEnvironmentMediaItem(),
|
||||
): EnvironmentBackground {
|
||||
if (
|
||||
background.items.length >= maxEnvironmentMediaItems
|
||||
|| background.items.some((candidate) => candidate.id === item.id)
|
||||
) {
|
||||
return background;
|
||||
}
|
||||
return {
|
||||
...background,
|
||||
enabled: background.items.length === 0 ? true : background.enabled,
|
||||
items: [...background.items, item],
|
||||
};
|
||||
}
|
||||
|
||||
export function removeEnvironmentMediaItem(
|
||||
background: EnvironmentBackground,
|
||||
itemId: string,
|
||||
): EnvironmentBackground {
|
||||
const items = background.items.filter((item) => item.id !== itemId);
|
||||
if (items.length === background.items.length) return background;
|
||||
return {
|
||||
...background,
|
||||
enabled: items.length > 0 && background.enabled,
|
||||
items,
|
||||
};
|
||||
}
|
||||
|
||||
export function inferEnvironmentMediaKind(url: string): EnvironmentMediaKind {
|
||||
return /\.(mp4|webm|mov)(?:[?#].*)?$/i.test(url) ? "video" : "image";
|
||||
}
|
||||
|
||||
const defaultQuickActions: Record<
|
||||
EnvironmentSurfaceId,
|
||||
readonly [string | null, string | null]
|
||||
> = {
|
||||
home: ["spatial-scene", "local-device"],
|
||||
fleet: ["contour-health", "local-device"],
|
||||
observation: ["spatial-scene", "cameras"],
|
||||
missions: ["mission-planner", null],
|
||||
data: ["recordings", "datasets"],
|
||||
system: ["modules", "integrations"],
|
||||
polygon: ["lab-archive", null],
|
||||
};
|
||||
|
||||
export function defaultEnvironmentSettings(): EnvironmentSettings {
|
||||
const rootById = new Map(roots.map((root) => [root.id, root]));
|
||||
return {
|
||||
revision: 0,
|
||||
pages: Object.fromEntries(surfaceIds.map((surfaceId) => {
|
||||
const root = surfaceId === "home" ? null : rootById.get(surfaceId) ?? null;
|
||||
const [primaryWorkspaceId, secondaryWorkspaceId] = defaultQuickActions[surfaceId];
|
||||
return [surfaceId, {
|
||||
headerLabel: root?.label ?? "Mission Core",
|
||||
eyebrow: root?.eyebrow ?? "NODEDC / MISSION CORE",
|
||||
title: root?.title ?? "Mission Core",
|
||||
description: root?.statement
|
||||
?? "Наблюдение, планирование и корректировка миссий в одной модульной рабочей области.",
|
||||
primaryWorkspaceId,
|
||||
secondaryWorkspaceId,
|
||||
background: emptyBackground(),
|
||||
}];
|
||||
})) as Record<EnvironmentSurfaceId, EnvironmentPage>,
|
||||
};
|
||||
}
|
||||
|
||||
function requireRecord(value: unknown, path: string): Record<string, unknown> {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||
throw new Error(`${path} должен быть объектом.`);
|
||||
}
|
||||
return value as Record<string, unknown>;
|
||||
}
|
||||
|
||||
function requireString(
|
||||
value: unknown,
|
||||
path: string,
|
||||
nullable = false,
|
||||
): string | null {
|
||||
if (nullable && value === null) return null;
|
||||
if (typeof value !== "string" || !value.trim()) {
|
||||
throw new Error(`${path} должен быть непустой строкой.`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function decodeMediaItem(value: unknown, path: string): EnvironmentMediaItem {
|
||||
const record = requireRecord(value, path);
|
||||
if (
|
||||
typeof record.id !== "string"
|
||||
|| !/^media-[a-z0-9-]{1,58}$/.test(record.id)
|
||||
) {
|
||||
throw new Error(`${path}.id не поддерживается.`);
|
||||
}
|
||||
if (!["file", "url"].includes(String(record.source))) {
|
||||
throw new Error(`${path}.source не поддерживается.`);
|
||||
}
|
||||
if (!["image", "video"].includes(String(record.media_kind))) {
|
||||
throw new Error(`${path}.media_kind не поддерживается.`);
|
||||
}
|
||||
return {
|
||||
id: record.id,
|
||||
source: record.source as EnvironmentMediaSource,
|
||||
url: requireString(record.url, `${path}.url`),
|
||||
mediaKind: record.media_kind as EnvironmentMediaKind,
|
||||
fileName: requireString(record.file_name, `${path}.file_name`, true),
|
||||
};
|
||||
}
|
||||
|
||||
function decodeBackground(value: unknown, path: string): EnvironmentBackground {
|
||||
const record = requireRecord(value, path);
|
||||
if (typeof record.enabled !== "boolean") {
|
||||
throw new Error(`${path}.enabled должен быть boolean.`);
|
||||
}
|
||||
if (
|
||||
typeof record.image_duration_seconds !== "number"
|
||||
|| !Number.isSafeInteger(record.image_duration_seconds)
|
||||
|| record.image_duration_seconds < 1
|
||||
|| record.image_duration_seconds > 300
|
||||
) {
|
||||
throw new Error(`${path}.image_duration_seconds некорректен.`);
|
||||
}
|
||||
if (
|
||||
!Array.isArray(record.items)
|
||||
|| record.items.length > maxEnvironmentMediaItems
|
||||
) {
|
||||
throw new Error(`${path}.items некорректен.`);
|
||||
}
|
||||
const items = record.items.map((item, index) =>
|
||||
decodeMediaItem(item, `${path}.items[${index}]`));
|
||||
if (new Set(items.map((item) => item.id)).size !== items.length) {
|
||||
throw new Error(`${path}.items содержит повторяющиеся id.`);
|
||||
}
|
||||
return {
|
||||
enabled: record.enabled,
|
||||
imageDurationSeconds: record.image_duration_seconds,
|
||||
items,
|
||||
};
|
||||
}
|
||||
|
||||
function decodePage(value: unknown, path: string): EnvironmentPage {
|
||||
const record = requireRecord(value, path);
|
||||
return {
|
||||
headerLabel: requireString(record.header_label, `${path}.header_label`)!,
|
||||
eyebrow: requireString(record.eyebrow, `${path}.eyebrow`)!,
|
||||
title: requireString(record.title, `${path}.title`)!,
|
||||
description: requireString(record.description, `${path}.description`)!,
|
||||
primaryWorkspaceId: requireString(
|
||||
record.primary_workspace_id,
|
||||
`${path}.primary_workspace_id`,
|
||||
true,
|
||||
),
|
||||
secondaryWorkspaceId: requireString(
|
||||
record.secondary_workspace_id,
|
||||
`${path}.secondary_workspace_id`,
|
||||
true,
|
||||
),
|
||||
background: decodeBackground(record.background, `${path}.background`),
|
||||
};
|
||||
}
|
||||
|
||||
export function decodeEnvironmentSettings(value: unknown): EnvironmentSettings {
|
||||
const record = requireRecord(value, "environment");
|
||||
if (record.schema_version !== "missioncore.operator-environment/v3") {
|
||||
throw new Error("Версия настроек окружения не поддерживается.");
|
||||
}
|
||||
if (
|
||||
typeof record.revision !== "number"
|
||||
|| !Number.isSafeInteger(record.revision)
|
||||
|| record.revision < 0
|
||||
) {
|
||||
throw new Error("Ревизия настроек окружения некорректна.");
|
||||
}
|
||||
const pages = requireRecord(record.pages, "environment.pages");
|
||||
return {
|
||||
revision: record.revision,
|
||||
pages: Object.fromEntries(surfaceIds.map((surfaceId) => [
|
||||
surfaceId,
|
||||
decodePage(pages[surfaceId], `environment.pages.${surfaceId}`),
|
||||
])) as Record<EnvironmentSurfaceId, EnvironmentPage>,
|
||||
};
|
||||
}
|
||||
|
||||
export function encodeEnvironmentSettings(settings: EnvironmentSettings): unknown {
|
||||
return {
|
||||
revision: settings.revision,
|
||||
pages: Object.fromEntries(surfaceIds.map((surfaceId) => {
|
||||
const page = settings.pages[surfaceId];
|
||||
return [surfaceId, {
|
||||
header_label: page.headerLabel,
|
||||
eyebrow: page.eyebrow,
|
||||
title: page.title,
|
||||
description: page.description,
|
||||
primary_workspace_id: page.primaryWorkspaceId,
|
||||
secondary_workspace_id: page.secondaryWorkspaceId,
|
||||
background: {
|
||||
enabled: page.background.enabled,
|
||||
image_duration_seconds: page.background.imageDurationSeconds,
|
||||
items: page.background.items.map((item) => ({
|
||||
id: item.id,
|
||||
source: item.source,
|
||||
url: item.url,
|
||||
media_kind: item.mediaKind,
|
||||
file_name: item.fileName,
|
||||
})),
|
||||
},
|
||||
}];
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
export function decodeUploadedEnvironmentMedia(
|
||||
value: unknown,
|
||||
): UploadedEnvironmentMedia {
|
||||
const record = requireRecord(value, "environment media");
|
||||
if (record.schema_version !== "missioncore.operator-environment-media/v2") {
|
||||
throw new Error("Версия загруженного media не поддерживается.");
|
||||
}
|
||||
if (!surfaceIds.includes(record.surface_id as EnvironmentSurfaceId)) {
|
||||
throw new Error("Экран загруженного media не поддерживается.");
|
||||
}
|
||||
if (
|
||||
typeof record.item_id !== "string"
|
||||
|| !/^media-[a-z0-9-]{1,58}$/.test(record.item_id)
|
||||
) {
|
||||
throw new Error("Идентификатор загруженного media не поддерживается.");
|
||||
}
|
||||
if (!["image", "video"].includes(String(record.media_kind))) {
|
||||
throw new Error("Тип загруженного media не поддерживается.");
|
||||
}
|
||||
if (
|
||||
typeof record.byte_length !== "number"
|
||||
|| !Number.isSafeInteger(record.byte_length)
|
||||
|| record.byte_length <= 0
|
||||
) {
|
||||
throw new Error("Размер загруженного media некорректен.");
|
||||
}
|
||||
return {
|
||||
surfaceId: record.surface_id as EnvironmentSurfaceId,
|
||||
itemId: record.item_id,
|
||||
url: requireString(record.url, "environment media.url")!,
|
||||
fileName: requireString(record.file_name, "environment media.file_name")!,
|
||||
mediaKind: record.media_kind as EnvironmentMediaKind,
|
||||
mediaType: requireString(record.media_type, "environment media.media_type")!,
|
||||
byteLength: record.byte_length,
|
||||
sha256: requireString(record.sha256, "environment media.sha256")!,
|
||||
};
|
||||
}
|
||||
|
||||
export function cloneEnvironmentSettings(
|
||||
settings: EnvironmentSettings,
|
||||
): EnvironmentSettings {
|
||||
return {
|
||||
revision: settings.revision,
|
||||
pages: Object.fromEntries(surfaceIds.map((surfaceId) => [
|
||||
surfaceId,
|
||||
{
|
||||
...settings.pages[surfaceId],
|
||||
background: {
|
||||
...settings.pages[surfaceId].background,
|
||||
items: settings.pages[surfaceId].background.items.map((item) => ({
|
||||
...item,
|
||||
})),
|
||||
},
|
||||
},
|
||||
])) as Record<EnvironmentSurfaceId, EnvironmentPage>,
|
||||
};
|
||||
}
|
||||
|
||||
export const environmentSurfaceIds = surfaceIds;
|
||||
@@ -0,0 +1,130 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
|
||||
import {
|
||||
decodeEnvironmentSettings,
|
||||
decodeUploadedEnvironmentMedia,
|
||||
defaultEnvironmentSettings,
|
||||
encodeEnvironmentSettings,
|
||||
type EnvironmentSettings,
|
||||
type EnvironmentSurfaceId,
|
||||
type UploadedEnvironmentMedia,
|
||||
} from "./environmentSettings";
|
||||
|
||||
interface EnvironmentSettingsController {
|
||||
settings: EnvironmentSettings;
|
||||
state: "loading" | "ready" | "saving" | "error";
|
||||
error: string | null;
|
||||
save: (draft: EnvironmentSettings) => Promise<EnvironmentSettings>;
|
||||
upload: (
|
||||
surfaceId: EnvironmentSurfaceId,
|
||||
itemId: string,
|
||||
file: File,
|
||||
) => Promise<UploadedEnvironmentMedia>;
|
||||
}
|
||||
|
||||
async function responseError(response: Response, fallback: string): Promise<string> {
|
||||
try {
|
||||
const body = await response.json() as { detail?: unknown };
|
||||
if (typeof body.detail === "string" && body.detail.trim()) return body.detail;
|
||||
} catch {
|
||||
// A non-JSON reverse-proxy response still gets a stable product message.
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
export function useEnvironmentSettings(): EnvironmentSettingsController {
|
||||
const [settings, setSettings] = useState<EnvironmentSettings>(
|
||||
defaultEnvironmentSettings,
|
||||
);
|
||||
const [state, setState] = useState<EnvironmentSettingsController["state"]>(
|
||||
"loading",
|
||||
);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const controller = new AbortController();
|
||||
void fetch("/api/v1/environment/settings", {
|
||||
signal: controller.signal,
|
||||
headers: { Accept: "application/json" },
|
||||
}).then(async (response) => {
|
||||
if (!response.ok) {
|
||||
throw new Error(await responseError(
|
||||
response,
|
||||
"Не удалось загрузить настройки окружения.",
|
||||
));
|
||||
}
|
||||
return decodeEnvironmentSettings(await response.json());
|
||||
}).then((document) => {
|
||||
setSettings(document);
|
||||
setState("ready");
|
||||
setError(null);
|
||||
}).catch((reason: unknown) => {
|
||||
if (controller.signal.aborted) return;
|
||||
setState("error");
|
||||
setError(reason instanceof Error
|
||||
? reason.message
|
||||
: "Не удалось загрузить настройки окружения.");
|
||||
});
|
||||
return () => controller.abort();
|
||||
}, []);
|
||||
|
||||
const save = useCallback(async (draft: EnvironmentSettings) => {
|
||||
setState("saving");
|
||||
setError(null);
|
||||
try {
|
||||
const response = await fetch("/api/v1/environment/settings", {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(encodeEnvironmentSettings(draft)),
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(await responseError(
|
||||
response,
|
||||
"Не удалось сохранить настройки окружения.",
|
||||
));
|
||||
}
|
||||
const document = decodeEnvironmentSettings(await response.json());
|
||||
setSettings(document);
|
||||
setState("ready");
|
||||
return document;
|
||||
} catch (reason) {
|
||||
const message = reason instanceof Error
|
||||
? reason.message
|
||||
: "Не удалось сохранить настройки окружения.";
|
||||
setState("error");
|
||||
setError(message);
|
||||
throw new Error(message);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const upload = useCallback(async (
|
||||
surfaceId: EnvironmentSurfaceId,
|
||||
itemId: string,
|
||||
file: File,
|
||||
) => {
|
||||
const response = await fetch(
|
||||
`/api/v1/environment/media/${surfaceId}/${itemId}`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": file.type || "application/octet-stream",
|
||||
"X-NODEDC-File-Name": encodeURIComponent(file.name),
|
||||
},
|
||||
body: file,
|
||||
},
|
||||
);
|
||||
if (!response.ok) {
|
||||
throw new Error(await responseError(
|
||||
response,
|
||||
"Не удалось загрузить фон окружения.",
|
||||
));
|
||||
}
|
||||
return decodeUploadedEnvironmentMedia(await response.json());
|
||||
}, []);
|
||||
|
||||
return { settings, state, error, save, upload };
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
|
||||
export interface BoardHost {
|
||||
hostname: string; os: string; architecture: string; cpus: number;
|
||||
memory_kib: number | null; collected_at: string;
|
||||
networks: { name: string; up: boolean; addresses: string[] }[];
|
||||
usb: { port: string; product: string }[];
|
||||
}
|
||||
export interface Vehicle {
|
||||
id: string; node_id: string; name: string; platform: string;
|
||||
enrollment: "pending" | "paired" | "revoked" | "failed";
|
||||
connectivity: "online" | "offline"; last_seen: number | null;
|
||||
host: BoardHost | null; notice: string; revision: number;
|
||||
}
|
||||
export interface FleetPreview {
|
||||
preview_id: string; node_id: string; name: string; host: BoardHost;
|
||||
endpoint: string; expires_at: number;
|
||||
}
|
||||
export async function fleetRequest<T>(path = "", method = "GET", body?: unknown): Promise<T> {
|
||||
const response = await fetch(`/api/v1/fleet${path}`, {
|
||||
method, credentials: "same-origin", cache: "no-store",
|
||||
headers: body === undefined ? {} : { "Content-Type": "application/json" },
|
||||
body: body === undefined ? undefined : JSON.stringify(body), signal: AbortSignal.timeout(12000),
|
||||
});
|
||||
if (!response.ok) {
|
||||
const data = await response.json().catch(() => ({}));
|
||||
throw new Error(typeof data.detail === "string" ? data.detail : "Не удалось выполнить действие с аппаратом.");
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
export function useFleet() {
|
||||
const [items, setItems] = useState<Vehicle[] | null>(null);
|
||||
const [error, setError] = useState("");
|
||||
const refresh = useCallback(async () => {
|
||||
const value = await fleetRequest<{ items: Vehicle[] }>();
|
||||
setItems(value.items); setError(""); return value.items;
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
let active = true;
|
||||
let fallback: ReturnType<typeof setInterval> | undefined;
|
||||
const read = async () => {
|
||||
try { const value = await fleetRequest<{ items: Vehicle[] }>(); if (active) { setItems(value.items); setError(""); } }
|
||||
catch { if (active) setError("Реестр недоступен. Показаны последние полученные сведения; связь сейчас не подтверждена."); }
|
||||
};
|
||||
void read();
|
||||
const events = new EventSource("/api/v1/fleet/events");
|
||||
events.onmessage = event => {
|
||||
if (!active) return;
|
||||
try { const value = JSON.parse(event.data); setItems(value.items); setError(""); if (fallback) { clearInterval(fallback); fallback = undefined; } }
|
||||
catch { unavailable(); }
|
||||
};
|
||||
const unavailable = () => {
|
||||
if (!active) return;
|
||||
setError("Связь обновляется. Показаны последние полученные сведения.");
|
||||
if (!fallback) fallback = setInterval(() => void read(), 5000);
|
||||
};
|
||||
events.onerror = unavailable;
|
||||
return () => { active = false; events.close(); if (fallback) clearInterval(fallback); };
|
||||
}, []);
|
||||
return { items, error, refresh };
|
||||
}
|
||||
@@ -0,0 +1,546 @@
|
||||
import {
|
||||
AdvancedLaboratoryContractError,
|
||||
fetchOne,
|
||||
parseE31,
|
||||
parseE32,
|
||||
parseE33,
|
||||
parseE37,
|
||||
parseE38,
|
||||
parseE39,
|
||||
type AdvancedLaboratoryResults,
|
||||
type LaboratoryFetch,
|
||||
} from "./advancedResults";
|
||||
import { fetchE34TemporalLayerResult } from "./e34TemporalLayer";
|
||||
import { fetchE35DegradationRecoveryResult } from "./e35DegradationRecovery";
|
||||
import { fetchE40ProductGateResult } from "./e40ProductGate";
|
||||
import { fetchL3PointPillarsVisualAudit } from "./l3PointPillarsVisualAudit";
|
||||
import { fetchL31PointPillarsRavnoves } from "./l31PointPillarsRavnoves";
|
||||
import { fetchL32PointPillarsCameraReview } from "./l32PointPillarsCameraReview";
|
||||
import { fetchL33CameraFirstDetectorReview } from "./l33CameraFirstDetectorReview";
|
||||
import { fetchL34RightYoloxTruthIsland } from "./l34RightYoloxTruthIsland";
|
||||
import { fetchL34AAssistedYoloxErrorAudit } from "./l34aAssistedYoloxErrorAudit";
|
||||
import { fetchL34BNestedBoxConsolidation } from "./l34bNestedBoxConsolidation";
|
||||
import { fetchL34CTileSeamStitch } from "./l34cTileSeamStitch";
|
||||
import { fetchL34DCumulativePostprocessing } from "./l34dCumulativePostprocessing";
|
||||
import { fetchL34ESelfReviewDiagnostic } from "./l34eSelfReviewDiagnostic";
|
||||
import { fetchL34FFrozenResult } from "./l34fAdjudication";
|
||||
import { fetchE46BlindReviewResult } from "./e46BlindReview";
|
||||
import { fetchE46AAiEngineeringPreannotation } from "./e46aAiEngineeringPreannotation";
|
||||
import { fetchE46BTemporalMotion } from "./e46bTemporalMotion";
|
||||
import { fetchE46CFullReplayWorldTracks } from "./e46cFullReplayWorldTracks";
|
||||
import { fetchE46DTemporalFailureAudit } from "./e46dTemporalFailureAudit";
|
||||
import { fetchE46EReadyStack } from "./e46eReadyStack";
|
||||
import { fetchE46FDashCamBakeoff } from "./e46fDashCamBakeoff";
|
||||
import { fetchE46GRectifiedDetectorBakeoff } from "./e46gRectifiedDetectorBakeoff";
|
||||
import { fetchE46HFullRectifiedFrontReplay } from "./e46hFullRectifiedFrontReplay";
|
||||
import { fetchE46IGroundingDinoFullReplay } from "./e46iGroundingDinoFullReplay";
|
||||
import { fetchE46JRawFisheyeRealtime } from "./e46jRawFisheyeRealtime";
|
||||
import { fetchE47SemanticSlamResult } from "./e47SemanticSlam";
|
||||
import { fetchM4ThreatReplayResult } from "./m4ReplayThreat";
|
||||
import { fetchM47ReferenceGraphLab } from "./m47ReferenceGraph";
|
||||
import {
|
||||
fetchM48LifecycleResult,
|
||||
} from "./m48ObjectCentricQuality";
|
||||
import { fetchM48SmallStaticRegression } from "./m48SmallStaticRegression";
|
||||
import { fetchM48StaticOccupancyQualification } from "./m48StaticOccupancyQualification";
|
||||
import { fetchM48R3StaticOccupancyShadow } from "./m48r3StaticOccupancyShadow";
|
||||
import { fetchM48SFixedClassDetectorResult } from "./m48sFixedClassDetector";
|
||||
import { fetchM48TRiskQualityResult } from "./m48tRiskQuality";
|
||||
import { fetchM49TgsFailClosedResult } from "./m49TgsFailClosed";
|
||||
import { fetchM49TgsFullShadowResult } from "./m49TgsFullShadow";
|
||||
import {
|
||||
fetchVegetationBenchmarkResult,
|
||||
fetchVegetationShadowResult,
|
||||
} from "./vegetationShadow";
|
||||
|
||||
export type AdvancedLaboratoryWorkId =
|
||||
| "lab-v1-vegetation-benchmark"
|
||||
| "lab-v1-vegetation-shadow"
|
||||
| "m48-object-centric-quality"
|
||||
| "m48-small-static-passage-regression"
|
||||
| "m48-static-occupancy-qualification"
|
||||
| "m48r3-static-occupancy-shadow"
|
||||
| "m48s-fixed-class-detector"
|
||||
| "m48t-risk-quality-temporal"
|
||||
| "m49-tgs-fail-closed-evidence"
|
||||
| "m49-tgs-full-shadow"
|
||||
| "m47-reference-graph-shadow"
|
||||
| "m4-replay-threat"
|
||||
| "l3-pointpillars-visual-audit"
|
||||
| "l31-pointpillars-ravnoves"
|
||||
| "l32-pointpillars-camera-review"
|
||||
| "l33-camera-first-detector-review"
|
||||
| "e31-source-binding"
|
||||
| "e32-track-geometry"
|
||||
| "e33-worker-shadow"
|
||||
| "e34-temporal-layer"
|
||||
| "e35-degradation-recovery"
|
||||
| "e37-ravnoves-acceptance"
|
||||
| "e38-perception-baseline"
|
||||
| "e39-perception-refinement"
|
||||
| "e40-perception-product-gate"
|
||||
| "e46-detector-truth-island"
|
||||
| "e46a-ai-engineering-preannotation"
|
||||
| "e46b-temporal-motion"
|
||||
| "e46c-full-replay-world-tracks"
|
||||
| "e46d-temporal-failure-audit"
|
||||
| "e46e-ready-stack"
|
||||
| "e46f-dashcam-bakeoff"
|
||||
| "e46g-rectified-detector-bakeoff"
|
||||
| "e46h-full-rectified-front-replay"
|
||||
| "e46i-grounding-dino-full-replay"
|
||||
| "e46j-raw-fisheye-realtime"
|
||||
| "e47-semantic-slam-shadow"
|
||||
| "l34-right-yolox-truth-island-freeze"
|
||||
| "l34a-assisted-yolox-error-audit"
|
||||
| "l34b-nested-box-consolidation-shadow"
|
||||
| "l34c-tile-seam-stitch-shadow"
|
||||
| "l34d-cumulative-postprocessing-candidate"
|
||||
| "l34e-self-review-diagnostic"
|
||||
| "l34f-adjudicated-reference";
|
||||
|
||||
export interface AdvancedLaboratoryIndexItem {
|
||||
workId: AdvancedLaboratoryWorkId;
|
||||
resultId: string;
|
||||
createdAtUtc: string;
|
||||
}
|
||||
|
||||
const WORK_IDS: readonly AdvancedLaboratoryWorkId[] = [
|
||||
"lab-v1-vegetation-benchmark",
|
||||
"lab-v1-vegetation-shadow",
|
||||
"m48-object-centric-quality",
|
||||
"m48-small-static-passage-regression",
|
||||
"m48-static-occupancy-qualification",
|
||||
"m48r3-static-occupancy-shadow",
|
||||
"m48s-fixed-class-detector",
|
||||
"m48t-risk-quality-temporal",
|
||||
"m49-tgs-fail-closed-evidence",
|
||||
"m49-tgs-full-shadow",
|
||||
"m47-reference-graph-shadow",
|
||||
"m4-replay-threat",
|
||||
"l3-pointpillars-visual-audit",
|
||||
"l31-pointpillars-ravnoves",
|
||||
"l32-pointpillars-camera-review",
|
||||
"l33-camera-first-detector-review",
|
||||
"e31-source-binding",
|
||||
"e32-track-geometry",
|
||||
"e33-worker-shadow",
|
||||
"e34-temporal-layer",
|
||||
"e35-degradation-recovery",
|
||||
"e37-ravnoves-acceptance",
|
||||
"e38-perception-baseline",
|
||||
"e39-perception-refinement",
|
||||
"e40-perception-product-gate",
|
||||
"e46-detector-truth-island",
|
||||
"e46a-ai-engineering-preannotation",
|
||||
"e46b-temporal-motion",
|
||||
"e46c-full-replay-world-tracks",
|
||||
"e46d-temporal-failure-audit",
|
||||
"e46e-ready-stack",
|
||||
"e46f-dashcam-bakeoff",
|
||||
"e46g-rectified-detector-bakeoff",
|
||||
"e46h-full-rectified-front-replay",
|
||||
"e46i-grounding-dino-full-replay",
|
||||
"e46j-raw-fisheye-realtime",
|
||||
"e47-semantic-slam-shadow",
|
||||
"l34-right-yolox-truth-island-freeze",
|
||||
"l34a-assisted-yolox-error-audit",
|
||||
"l34b-nested-box-consolidation-shadow",
|
||||
"l34c-tile-seam-stitch-shadow",
|
||||
"l34d-cumulative-postprocessing-candidate",
|
||||
"l34e-self-review-diagnostic",
|
||||
"l34f-adjudicated-reference",
|
||||
];
|
||||
|
||||
const RESULT_PREFIX: Readonly<Record<AdvancedLaboratoryWorkId, string>> = {
|
||||
"lab-v1-vegetation-benchmark": "lab-v1-vegetation-benchmark",
|
||||
"lab-v1-vegetation-shadow": "lab-v1-vegetation-shadow",
|
||||
"m48-object-centric-quality": "m48-object-quality-(?:pack|result)",
|
||||
"m48-small-static-passage-regression": "m48-small-static-passage-regression",
|
||||
"m48-static-occupancy-qualification": "m48-static-occupancy-qualification",
|
||||
"m48r3-static-occupancy-shadow": "m48r3-static-occupancy-shadow",
|
||||
"m48s-fixed-class-detector": "m48s-fixed-class-detector-lab",
|
||||
"m48t-risk-quality-temporal": "(?:m48t-risk-quality-temporal-lab|m48q-native-risk-quality-lab)",
|
||||
"m49-tgs-fail-closed-evidence": "m49-tgs-fail-closed",
|
||||
"m49-tgs-full-shadow": "m49-tgs-full-shadow",
|
||||
"m47-reference-graph-shadow": "m47-reference-graph-lab",
|
||||
"m4-replay-threat": "m4-threat-replay",
|
||||
"l3-pointpillars-visual-audit": "l3-pointpillars-visual-audit",
|
||||
"l31-pointpillars-ravnoves": "l31-pointpillars-ravnoves",
|
||||
"l32-pointpillars-camera-review": "l32-pointpillars-camera-review",
|
||||
"l33-camera-first-detector-review": "l33-camera-first-detector-review",
|
||||
"e31-source-binding": "e31-source-qualification",
|
||||
"e32-track-geometry": "e32-track-geometry",
|
||||
"e33-worker-shadow": "e33-worker-shadow",
|
||||
"e34-temporal-layer": "e34-temporal-occupied",
|
||||
"e35-degradation-recovery": "e35-degradation-recovery",
|
||||
"e37-ravnoves-acceptance": "e37-ravnoves-acceptance",
|
||||
"e38-perception-baseline": "e38-perception-baseline",
|
||||
"e39-perception-refinement": "e39-perception-refinement",
|
||||
"e40-perception-product-gate": "e40-perception-product-gate",
|
||||
"e46-detector-truth-island": "e46-detector-truth-island",
|
||||
"e46a-ai-engineering-preannotation": "e46a-ai-engineering-preannotation",
|
||||
"e46b-temporal-motion": "e46b-temporal-motion",
|
||||
"e46c-full-replay-world-tracks": "e46c-full-replay-world-tracks",
|
||||
"e46d-temporal-failure-audit": "e46d-temporal-failure-audit",
|
||||
"e46e-ready-stack": "e46e-ready-stack",
|
||||
"e46f-dashcam-bakeoff": "e46f-dashcam-bakeoff",
|
||||
"e46g-rectified-detector-bakeoff": "e46g-rectified-detector-bakeoff",
|
||||
"e46h-full-rectified-front-replay": "e46h-full-rectified-front-replay",
|
||||
"e46i-grounding-dino-full-replay": "e46i-grounding-dino-full-replay",
|
||||
"e46j-raw-fisheye-realtime": "e46j-raw-fisheye-realtime",
|
||||
"e47-semantic-slam-shadow": "e47-semantic-slam",
|
||||
"l34-right-yolox-truth-island-freeze": "l34-right-yolox-truth-island-freeze",
|
||||
"l34a-assisted-yolox-error-audit": "l34a-assisted-yolox-error-audit",
|
||||
"l34b-nested-box-consolidation-shadow": "l34b-nested-box-consolidation-shadow",
|
||||
"l34c-tile-seam-stitch-shadow": "l34c-tile-seam-stitch-shadow",
|
||||
"l34d-cumulative-postprocessing-candidate": "l34d-cumulative-postprocessing-candidate",
|
||||
"l34e-self-review-diagnostic": "l34e-self-review-diagnostic",
|
||||
"l34f-adjudicated-reference": "l34f-adjudicated-reference",
|
||||
};
|
||||
|
||||
export function isAdvancedLaboratoryWorkId(
|
||||
value: string,
|
||||
): value is AdvancedLaboratoryWorkId {
|
||||
return WORK_IDS.includes(value as AdvancedLaboratoryWorkId);
|
||||
}
|
||||
|
||||
export function emptyAdvancedLaboratoryResults(): AdvancedLaboratoryResults {
|
||||
return {
|
||||
vegetationBenchmark: null,
|
||||
vegetationShadow: null,
|
||||
m47Graph: null,
|
||||
m48: null,
|
||||
m48SmallStatic: null,
|
||||
m48StaticOccupancy: null,
|
||||
m48r3StaticOccupancy: null,
|
||||
m48s: null,
|
||||
m48t: null,
|
||||
m49Tgs: null,
|
||||
m49TgsFull: null,
|
||||
m4Threat: null,
|
||||
l3: null,
|
||||
l31: null,
|
||||
l32: null,
|
||||
l33: null,
|
||||
e31: null,
|
||||
e32: null,
|
||||
e33: null,
|
||||
e34: null,
|
||||
e35: null,
|
||||
e37: null,
|
||||
e38: null,
|
||||
e39: null,
|
||||
e40: null,
|
||||
e46: null,
|
||||
e46a: null,
|
||||
e46b: null,
|
||||
e46c: null,
|
||||
e46d: null,
|
||||
e46e: null,
|
||||
e46f: null,
|
||||
e46g: null,
|
||||
e46h: null,
|
||||
e46i: null,
|
||||
e46j: null,
|
||||
e47: null,
|
||||
l34: null,
|
||||
l34a: null,
|
||||
l34b: null,
|
||||
l34c: null,
|
||||
l34d: null,
|
||||
l34e: null,
|
||||
l34f: null,
|
||||
};
|
||||
}
|
||||
|
||||
function objectValue(value: unknown, label: string): Record<string, unknown> {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||
throw new AdvancedLaboratoryContractError(`${label}: ожидался объект.`);
|
||||
}
|
||||
return value as Record<string, unknown>;
|
||||
}
|
||||
|
||||
function exactString(value: unknown, expected: string, label: string): string {
|
||||
if (value !== expected) {
|
||||
throw new AdvancedLaboratoryContractError(`${label}: нарушен контракт.`);
|
||||
}
|
||||
return expected;
|
||||
}
|
||||
|
||||
function parseIndexItem(value: unknown): AdvancedLaboratoryIndexItem {
|
||||
const item = objectValue(value, "Индекс LAB.item");
|
||||
if (
|
||||
typeof item.work_id !== "string"
|
||||
|| !isAdvancedLaboratoryWorkId(item.work_id)
|
||||
) {
|
||||
throw new AdvancedLaboratoryContractError(
|
||||
"Индекс LAB.work_id: неизвестная лабораторная работа.",
|
||||
);
|
||||
}
|
||||
const prefix = RESULT_PREFIX[item.work_id];
|
||||
if (
|
||||
typeof item.result_id !== "string"
|
||||
|| !new RegExp(`^${prefix}-[a-f0-9]{64}$`).test(item.result_id)
|
||||
) {
|
||||
throw new AdvancedLaboratoryContractError(
|
||||
"Индекс LAB.result_id: нарушена идентичность результата.",
|
||||
);
|
||||
}
|
||||
if (typeof item.created_at_utc !== "string" || !item.created_at_utc.trim()) {
|
||||
throw new AdvancedLaboratoryContractError(
|
||||
"Индекс LAB.created_at_utc: ожидалась строка.",
|
||||
);
|
||||
}
|
||||
exactString(item.access, "read-only", "Индекс LAB.item.access");
|
||||
return {
|
||||
workId: item.work_id,
|
||||
resultId: item.result_id,
|
||||
createdAtUtc: item.created_at_utc,
|
||||
};
|
||||
}
|
||||
|
||||
export async function fetchAdvancedLaboratoryIndex({
|
||||
fetcher = fetch,
|
||||
signal,
|
||||
}: {
|
||||
fetcher?: LaboratoryFetch;
|
||||
signal?: AbortSignal;
|
||||
} = {}): Promise<readonly AdvancedLaboratoryIndexItem[]> {
|
||||
const response = await fetcher("/api/v1/laboratory/advanced-index", {
|
||||
method: "GET",
|
||||
headers: { Accept: "application/json" },
|
||||
signal,
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new AdvancedLaboratoryContractError(
|
||||
`Индекс LAB недоступен: HTTP ${response.status}.`,
|
||||
);
|
||||
}
|
||||
const payload = objectValue(await response.json(), "Индекс LAB");
|
||||
exactString(
|
||||
payload.schema_version,
|
||||
"missioncore.laboratory-advanced-index/v1",
|
||||
"Индекс LAB.schema_version",
|
||||
);
|
||||
exactString(payload.access, "read-only", "Индекс LAB.access");
|
||||
if (!Array.isArray(payload.items) || payload.items.length > WORK_IDS.length) {
|
||||
throw new AdvancedLaboratoryContractError(
|
||||
"Индекс LAB.items: нарушен размер каталога.",
|
||||
);
|
||||
}
|
||||
const items = payload.items.map(parseIndexItem);
|
||||
if (new Set(items.map((item) => item.workId)).size !== items.length) {
|
||||
throw new AdvancedLaboratoryContractError(
|
||||
"Индекс LAB.items: лабораторная работа продублирована.",
|
||||
);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
export function advancedLaboratoryResultAvailable(
|
||||
workId: AdvancedLaboratoryWorkId,
|
||||
results: AdvancedLaboratoryResults,
|
||||
): boolean {
|
||||
return workId === "lab-v1-vegetation-benchmark" ? results.vegetationBenchmark !== null
|
||||
: workId === "lab-v1-vegetation-shadow" ? results.vegetationShadow !== null
|
||||
: workId === "m48-object-centric-quality" ? results.m48 !== null
|
||||
: workId === "m48-small-static-passage-regression" ? results.m48SmallStatic !== null
|
||||
: workId === "m48-static-occupancy-qualification" ? results.m48StaticOccupancy !== null
|
||||
: workId === "m48r3-static-occupancy-shadow" ? results.m48r3StaticOccupancy !== null
|
||||
: workId === "m48s-fixed-class-detector" ? results.m48s !== null
|
||||
: workId === "m48t-risk-quality-temporal" ? results.m48t !== null
|
||||
: workId === "m49-tgs-fail-closed-evidence" ? results.m49Tgs !== null
|
||||
: workId === "m49-tgs-full-shadow" ? results.m49TgsFull !== null
|
||||
: workId === "m47-reference-graph-shadow" ? results.m47Graph !== null
|
||||
: workId === "m4-replay-threat" ? results.m4Threat !== null
|
||||
: workId === "l3-pointpillars-visual-audit" ? results.l3 !== null
|
||||
: workId === "l31-pointpillars-ravnoves" ? results.l31 !== null
|
||||
: workId === "l32-pointpillars-camera-review" ? results.l32 !== null
|
||||
: workId === "l33-camera-first-detector-review" ? results.l33 !== null
|
||||
: workId === "e31-source-binding" ? results.e31 !== null
|
||||
: workId === "e32-track-geometry" ? results.e32 !== null
|
||||
: workId === "e33-worker-shadow" ? results.e33 !== null
|
||||
: workId === "e34-temporal-layer" ? results.e34 !== null
|
||||
: workId === "e35-degradation-recovery" ? results.e35 !== null
|
||||
: workId === "e37-ravnoves-acceptance" ? results.e37 !== null
|
||||
: workId === "e38-perception-baseline" ? results.e38 !== null
|
||||
: workId === "e39-perception-refinement" ? results.e39 !== null
|
||||
: workId === "e40-perception-product-gate" ? results.e40 !== null
|
||||
: workId === "e46-detector-truth-island" ? results.e46 !== null
|
||||
: workId === "e46a-ai-engineering-preannotation" ? results.e46a !== null
|
||||
: workId === "e46b-temporal-motion" ? results.e46b !== null
|
||||
: workId === "e46c-full-replay-world-tracks" ? results.e46c !== null
|
||||
: workId === "e46d-temporal-failure-audit" ? results.e46d !== null
|
||||
: workId === "e46e-ready-stack" ? results.e46e !== null
|
||||
: workId === "e46f-dashcam-bakeoff" ? results.e46f !== null
|
||||
: workId === "e46g-rectified-detector-bakeoff" ? results.e46g !== null
|
||||
: workId === "e46h-full-rectified-front-replay" ? results.e46h !== null
|
||||
: workId === "e46i-grounding-dino-full-replay" ? results.e46i !== null
|
||||
: workId === "e46j-raw-fisheye-realtime" ? results.e46j !== null
|
||||
: workId === "e47-semantic-slam-shadow" ? results.e47 !== null
|
||||
: workId === "l34-right-yolox-truth-island-freeze" ? results.l34 !== null
|
||||
: workId === "l34a-assisted-yolox-error-audit" ? results.l34a !== null
|
||||
: workId === "l34b-nested-box-consolidation-shadow" ? results.l34b !== null
|
||||
: workId === "l34c-tile-seam-stitch-shadow" ? results.l34c !== null
|
||||
: workId === "l34d-cumulative-postprocessing-candidate" ? results.l34d !== null
|
||||
: workId === "l34e-self-review-diagnostic" ? results.l34e !== null
|
||||
: results.l34f !== null;
|
||||
}
|
||||
|
||||
export async function fetchAdvancedLaboratoryResult(
|
||||
workId: AdvancedLaboratoryWorkId,
|
||||
{
|
||||
fetcher = fetch,
|
||||
signal,
|
||||
resultId,
|
||||
}: {
|
||||
fetcher?: LaboratoryFetch;
|
||||
signal?: AbortSignal;
|
||||
resultId?: string;
|
||||
} = {},
|
||||
): Promise<AdvancedLaboratoryResults> {
|
||||
const results = emptyAdvancedLaboratoryResults();
|
||||
if (workId === "lab-v1-vegetation-benchmark") {
|
||||
if (!resultId) throw new AdvancedLaboratoryContractError("Vegetation benchmark identity не выбрана.");
|
||||
results.vegetationBenchmark = await fetchVegetationBenchmarkResult(resultId, { fetcher, signal });
|
||||
} else if (workId === "lab-v1-vegetation-shadow") {
|
||||
if (!resultId) throw new AdvancedLaboratoryContractError("Vegetation LAB identity не выбрана.");
|
||||
results.vegetationShadow = await fetchVegetationShadowResult(resultId, { fetcher, signal });
|
||||
} else if (workId === "m48-object-centric-quality") {
|
||||
if (!resultId) throw new AdvancedLaboratoryContractError("M4.8 lifecycle evidence identity не выбрана.");
|
||||
results.m48 = await fetchM48LifecycleResult(resultId, { fetcher, signal });
|
||||
} else if (workId === "m48-small-static-passage-regression") {
|
||||
if (!resultId) throw new AdvancedLaboratoryContractError("M4.8R1 regression identity не выбрана.");
|
||||
results.m48SmallStatic = await fetchM48SmallStaticRegression(resultId, { fetcher, signal });
|
||||
} else if (workId === "m48-static-occupancy-qualification") {
|
||||
if (!resultId) throw new AdvancedLaboratoryContractError("M4.8R2 qualification identity не выбрана.");
|
||||
results.m48StaticOccupancy = await fetchM48StaticOccupancyQualification(resultId, { fetcher, signal });
|
||||
} else if (workId === "m48r3-static-occupancy-shadow") {
|
||||
if (!resultId) throw new AdvancedLaboratoryContractError("M4.8R3 Worker shadow identity не выбрана.");
|
||||
results.m48r3StaticOccupancy = await fetchM48R3StaticOccupancyShadow(resultId, { fetcher, signal });
|
||||
} else if (workId === "m48s-fixed-class-detector") {
|
||||
if (!resultId) throw new AdvancedLaboratoryContractError("M4.8S LAB identity не выбрана.");
|
||||
results.m48s = await fetchM48SFixedClassDetectorResult(resultId, { fetcher, signal });
|
||||
} else if (workId === "m48t-risk-quality-temporal") {
|
||||
if (!resultId) throw new AdvancedLaboratoryContractError("M4.8T LAB identity не выбрана.");
|
||||
results.m48t = await fetchM48TRiskQualityResult(resultId, { fetcher, signal });
|
||||
} else if (workId === "m49-tgs-fail-closed-evidence") {
|
||||
if (!resultId) throw new AdvancedLaboratoryContractError("M4.9 TGS LAB identity не выбрана.");
|
||||
results.m49Tgs = await fetchM49TgsFailClosedResult(resultId, { fetcher, signal });
|
||||
} else if (workId === "m49-tgs-full-shadow") {
|
||||
if (!resultId) throw new AdvancedLaboratoryContractError("M4.9 full TGS shadow identity не выбрана.");
|
||||
results.m49TgsFull = await fetchM49TgsFullShadowResult(resultId, { fetcher, signal });
|
||||
} else if (workId === "m47-reference-graph-shadow") {
|
||||
if (!resultId) {
|
||||
throw new AdvancedLaboratoryContractError("M4.7 LAB identity не выбрана.");
|
||||
}
|
||||
results.m47Graph = await fetchM47ReferenceGraphLab({
|
||||
resultId,
|
||||
fetcher,
|
||||
signal,
|
||||
});
|
||||
} else if (workId === "m4-replay-threat") {
|
||||
results.m4Threat = await fetchM4ThreatReplayResult({ fetcher, signal });
|
||||
} else if (workId === "l3-pointpillars-visual-audit") {
|
||||
results.l3 = await fetchL3PointPillarsVisualAudit({ fetcher, signal });
|
||||
} else if (workId === "l31-pointpillars-ravnoves") {
|
||||
results.l31 = await fetchL31PointPillarsRavnoves({ fetcher, signal });
|
||||
} else if (workId === "l32-pointpillars-camera-review") {
|
||||
results.l32 = await fetchL32PointPillarsCameraReview({ fetcher, signal });
|
||||
} else if (workId === "l33-camera-first-detector-review") {
|
||||
results.l33 = await fetchL33CameraFirstDetectorReview({ fetcher, signal });
|
||||
} else if (workId === "e31-source-binding") {
|
||||
results.e31 = await fetchOne(
|
||||
"/api/v1/laboratory/e31/results?limit=1",
|
||||
parseE31,
|
||||
fetcher,
|
||||
signal,
|
||||
);
|
||||
} else if (workId === "e32-track-geometry") {
|
||||
results.e32 = await fetchOne(
|
||||
"/api/v1/laboratory/e32/results?limit=1",
|
||||
parseE32,
|
||||
fetcher,
|
||||
signal,
|
||||
);
|
||||
} else if (workId === "e33-worker-shadow") {
|
||||
results.e33 = await fetchOne(
|
||||
"/api/v1/laboratory/e33/results?limit=1",
|
||||
parseE33,
|
||||
fetcher,
|
||||
signal,
|
||||
);
|
||||
} else if (workId === "e34-temporal-layer") {
|
||||
results.e34 = await fetchE34TemporalLayerResult({ fetcher, signal });
|
||||
} else if (workId === "e35-degradation-recovery") {
|
||||
results.e35 = await fetchE35DegradationRecoveryResult({ fetcher, signal });
|
||||
} else if (workId === "e37-ravnoves-acceptance") {
|
||||
results.e37 = await fetchOne(
|
||||
"/api/v1/laboratory/e37/results?limit=1",
|
||||
parseE37,
|
||||
fetcher,
|
||||
signal,
|
||||
);
|
||||
} else if (workId === "e38-perception-baseline") {
|
||||
results.e38 = await fetchOne(
|
||||
"/api/v1/laboratory/e38/results?limit=1",
|
||||
parseE38,
|
||||
fetcher,
|
||||
signal,
|
||||
);
|
||||
} else if (workId === "e39-perception-refinement") {
|
||||
results.e39 = await fetchOne(
|
||||
"/api/v1/laboratory/e39/results?limit=1",
|
||||
parseE39,
|
||||
fetcher,
|
||||
signal,
|
||||
);
|
||||
} else if (workId === "e40-perception-product-gate") {
|
||||
results.e40 = await fetchE40ProductGateResult({ fetcher, signal });
|
||||
} else if (workId === "e46-detector-truth-island") {
|
||||
results.e46 = await fetchE46BlindReviewResult({ fetcher, signal });
|
||||
} else if (workId === "e46a-ai-engineering-preannotation") {
|
||||
results.e46a = await fetchE46AAiEngineeringPreannotation({ fetcher, signal });
|
||||
} else if (workId === "e46b-temporal-motion") {
|
||||
results.e46b = await fetchE46BTemporalMotion({ fetcher, signal });
|
||||
} else if (workId === "e46c-full-replay-world-tracks") {
|
||||
results.e46c = await fetchE46CFullReplayWorldTracks({ fetcher, signal });
|
||||
} else if (workId === "e46d-temporal-failure-audit") {
|
||||
results.e46d = await fetchE46DTemporalFailureAudit({ fetcher, signal });
|
||||
} else if (workId === "e46e-ready-stack") {
|
||||
results.e46e = await fetchE46EReadyStack({ fetcher, signal });
|
||||
} else if (workId === "e46f-dashcam-bakeoff") {
|
||||
results.e46f = await fetchE46FDashCamBakeoff({ fetcher, signal });
|
||||
} else if (workId === "e46g-rectified-detector-bakeoff") {
|
||||
results.e46g = await fetchE46GRectifiedDetectorBakeoff({ fetcher, signal });
|
||||
} else if (workId === "e46h-full-rectified-front-replay") {
|
||||
results.e46h = await fetchE46HFullRectifiedFrontReplay({ fetcher, signal });
|
||||
} else if (workId === "e46i-grounding-dino-full-replay") {
|
||||
results.e46i = await fetchE46IGroundingDinoFullReplay({ fetcher, signal });
|
||||
} else if (workId === "e46j-raw-fisheye-realtime") {
|
||||
results.e46j = await fetchE46JRawFisheyeRealtime({ fetcher, signal });
|
||||
} else if (workId === "e47-semantic-slam-shadow") {
|
||||
results.e47 = await fetchE47SemanticSlamResult({ fetcher, signal });
|
||||
} else if (workId === "l34-right-yolox-truth-island-freeze") {
|
||||
results.l34 = await fetchL34RightYoloxTruthIsland({ fetcher, signal });
|
||||
} else if (workId === "l34a-assisted-yolox-error-audit") {
|
||||
results.l34a = await fetchL34AAssistedYoloxErrorAudit({ fetcher, signal });
|
||||
} else if (workId === "l34b-nested-box-consolidation-shadow") {
|
||||
results.l34b = await fetchL34BNestedBoxConsolidation({ fetcher, signal });
|
||||
} else if (workId === "l34c-tile-seam-stitch-shadow") {
|
||||
results.l34c = await fetchL34CTileSeamStitch({ fetcher, signal });
|
||||
} else if (workId === "l34d-cumulative-postprocessing-candidate") {
|
||||
results.l34d = await fetchL34DCumulativePostprocessing({ fetcher, signal });
|
||||
} else if (workId === "l34e-self-review-diagnostic") {
|
||||
results.l34e = await fetchL34ESelfReviewDiagnostic({ fetcher, signal });
|
||||
} else {
|
||||
results.l34f = await fetchL34FFrozenResult({ fetcher, signal });
|
||||
}
|
||||
if (!advancedLaboratoryResultAvailable(workId, results)) {
|
||||
throw new AdvancedLaboratoryContractError(
|
||||
"Выбранная лабораторная работа не прошла серверную проверку.",
|
||||
);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import type {
|
||||
E31LaboratoryResult,
|
||||
E32LaboratoryResult,
|
||||
E33LaboratoryResult,
|
||||
E37AcceptanceContractResult,
|
||||
E38PerceptionBaselineResult,
|
||||
E39PerceptionRefinementResult,
|
||||
} from "./advancedResults";
|
||||
import type { E34TemporalLayerResult } from "./e34TemporalLayer";
|
||||
import type { E35DegradationRecoveryResult } from "./e35DegradationRecovery";
|
||||
import type { E40PerceptionProductGateResult } from "./e40ProductGate";
|
||||
import type { L3PointPillarsVisualAuditResult } from "./l3PointPillarsVisualAudit";
|
||||
import type { L31PointPillarsRavnovesResult } from "./l31PointPillarsRavnoves";
|
||||
import type { L32PointPillarsCameraReviewResult } from "./l32PointPillarsCameraReview";
|
||||
import type { L33CameraFirstDetectorReviewResult } from "./l33CameraFirstDetectorReview";
|
||||
import type { L34RightYoloxTruthIslandResult } from "./l34RightYoloxTruthIsland";
|
||||
import type { L34AAssistedYoloxErrorAuditResult } from "./l34aAssistedYoloxErrorAudit";
|
||||
import type { L34BResult } from "./l34bNestedBoxConsolidation";
|
||||
import type { L34CResult } from "./l34cTileSeamStitch";
|
||||
import type { L34DResult } from "./l34dCumulativePostprocessing";
|
||||
import type { L34EResult } from "./l34eSelfReviewDiagnostic";
|
||||
import type { L34FFrozenResult } from "./l34fAdjudication";
|
||||
import type { E46BlindReviewResult } from "./e46BlindReview";
|
||||
import type { E46AAiEngineeringPreannotationResult } from "./e46aAiEngineeringPreannotation";
|
||||
import type { E46BTemporalMotionResult } from "./e46bTemporalMotion";
|
||||
import type { E46CFullReplayWorldTracksResult } from "./e46cFullReplayWorldTracks";
|
||||
import type { E46DTemporalFailureAuditResult } from "./e46dTemporalFailureAudit";
|
||||
import type { E46EReadyStackResult } from "./e46eReadyStack";
|
||||
import type { E46FDashCamBakeoffResult } from "./e46fDashCamBakeoff";
|
||||
import type { E46GRectifiedDetectorBakeoffResult } from "./e46gRectifiedDetectorBakeoff";
|
||||
import type { E46HFullRectifiedFrontReplayResult } from "./e46hFullRectifiedFrontReplay";
|
||||
import type { E46IGroundingDinoFullReplayResult } from "./e46iGroundingDinoFullReplay";
|
||||
import type { E46JRawFisheyeRealtimeResult } from "./e46jRawFisheyeRealtime";
|
||||
import type { E47SemanticSlamResult } from "./e47SemanticSlam";
|
||||
import type { M4ThreatReplayResult } from "./m4ReplayThreat";
|
||||
import type { M47ReferenceGraphLabResult } from "./m47ReferenceGraph";
|
||||
import type { M48AdvancedResult } from "./m48ObjectCentricQuality";
|
||||
import type { M48SmallStaticRegressionResult } from "./m48SmallStaticRegression";
|
||||
import type { M48StaticOccupancyQualificationResult } from "./m48StaticOccupancyQualification";
|
||||
import type { M48R3StaticOccupancyShadowResult } from "./m48r3StaticOccupancyShadow";
|
||||
import type { M48SFixedClassDetectorResult } from "./m48sFixedClassDetector";
|
||||
import type { M48TRiskQualityResult } from "./m48tRiskQuality";
|
||||
import type { M49TgsFailClosedResult } from "./m49TgsFailClosed";
|
||||
import type { M49TgsFullShadowResult } from "./m49TgsFullShadow";
|
||||
import type { VegetationShadowResult } from "./vegetationShadow";
|
||||
|
||||
export interface AdvancedLaboratoryResults {
|
||||
vegetationBenchmark: VegetationShadowResult | null;
|
||||
vegetationShadow: VegetationShadowResult | null;
|
||||
m47Graph: M47ReferenceGraphLabResult | null;
|
||||
m48: M48AdvancedResult | null;
|
||||
m48SmallStatic: M48SmallStaticRegressionResult | null;
|
||||
m48StaticOccupancy: M48StaticOccupancyQualificationResult | null;
|
||||
m48r3StaticOccupancy: M48R3StaticOccupancyShadowResult | null;
|
||||
m48s: M48SFixedClassDetectorResult | null;
|
||||
m48t: M48TRiskQualityResult | null;
|
||||
m49Tgs: M49TgsFailClosedResult | null;
|
||||
m49TgsFull: M49TgsFullShadowResult | null;
|
||||
m4Threat: M4ThreatReplayResult | null;
|
||||
l3: L3PointPillarsVisualAuditResult | null;
|
||||
l31: L31PointPillarsRavnovesResult | null;
|
||||
l32: L32PointPillarsCameraReviewResult | null;
|
||||
l33: L33CameraFirstDetectorReviewResult | null;
|
||||
e31: E31LaboratoryResult | null;
|
||||
e32: E32LaboratoryResult | null;
|
||||
e33: E33LaboratoryResult | null;
|
||||
e34: E34TemporalLayerResult | null;
|
||||
e35: E35DegradationRecoveryResult | null;
|
||||
e37: E37AcceptanceContractResult | null;
|
||||
e38: E38PerceptionBaselineResult | null;
|
||||
e39: E39PerceptionRefinementResult | null;
|
||||
e40: E40PerceptionProductGateResult | null;
|
||||
e46: E46BlindReviewResult | null;
|
||||
e46a: E46AAiEngineeringPreannotationResult | null;
|
||||
e46b: E46BTemporalMotionResult | null;
|
||||
e46c: E46CFullReplayWorldTracksResult | null;
|
||||
e46d: E46DTemporalFailureAuditResult | null;
|
||||
e46e: E46EReadyStackResult | null;
|
||||
e46f: E46FDashCamBakeoffResult | null;
|
||||
e46g: E46GRectifiedDetectorBakeoffResult | null;
|
||||
e46h: E46HFullRectifiedFrontReplayResult | null;
|
||||
e46i: E46IGroundingDinoFullReplayResult | null;
|
||||
e46j: E46JRawFisheyeRealtimeResult | null;
|
||||
e47: E47SemanticSlamResult | null;
|
||||
l34: L34RightYoloxTruthIslandResult | null;
|
||||
l34a: L34AAssistedYoloxErrorAuditResult | null;
|
||||
l34b: L34BResult | null;
|
||||
l34c: L34CResult | null;
|
||||
l34d: L34DResult | null;
|
||||
l34e: L34EResult | null;
|
||||
l34f: L34FFrozenResult | null;
|
||||
}
|
||||
@@ -0,0 +1,999 @@
|
||||
import { fetchE34TemporalLayerResult } from "./e34TemporalLayer";
|
||||
import { fetchE35DegradationRecoveryResult } from "./e35DegradationRecovery";
|
||||
import { fetchE40ProductGateResult } from "./e40ProductGate";
|
||||
import { settledCatalogValue } from "./catalogTransport";
|
||||
import type { AdvancedLaboratoryResults } from "./advancedLaboratoryResults";
|
||||
export type { AdvancedLaboratoryResults } from "./advancedLaboratoryResults";
|
||||
export interface E31LaboratoryResult {
|
||||
resultId: string;
|
||||
createdAtUtc: string | null;
|
||||
sourceSessionId: string;
|
||||
status: "accepted-diagnostic-source-profile";
|
||||
eligibleForE32: true;
|
||||
profileId: string;
|
||||
producerSha256: string;
|
||||
metrics: {
|
||||
frameCount: number;
|
||||
availableBindingCount: number;
|
||||
availableFraction: number;
|
||||
lidarCameraP95Ms: number;
|
||||
posePointP95Ms: number;
|
||||
selectedOffsetMs: number;
|
||||
correspondenceCount: number;
|
||||
supportedFraction: number;
|
||||
semanticSelfSampleCount: number;
|
||||
semanticSelfCollateralCount: number;
|
||||
exactGeometryCorrectionCount: number;
|
||||
geometryPointMaskStatus: string;
|
||||
};
|
||||
limitations: readonly string[];
|
||||
access: "read-only";
|
||||
}
|
||||
export interface E32LaboratoryResult {
|
||||
resultId: string;
|
||||
createdAtUtc: string | null;
|
||||
sourceSessionId: string;
|
||||
status: "accepted-diagnostic-track-geometry-replay";
|
||||
profileId: string;
|
||||
producerSha256: string;
|
||||
e31ResultId: string;
|
||||
metrics: {
|
||||
framesTotal: number;
|
||||
framesSourceAvailable: number;
|
||||
semanticPublished: number;
|
||||
semanticMasked: number;
|
||||
geometryPublished: number;
|
||||
observationsArbitrated: number;
|
||||
overlappingClaimsRemoved: number;
|
||||
qualifiedPointsPublished: number;
|
||||
qualifiedPointsWithheld: number;
|
||||
frameProcessingP95Ms: number;
|
||||
buildElapsedMs: number;
|
||||
};
|
||||
access: "read-only";
|
||||
}
|
||||
export interface E33LaboratoryResult {
|
||||
resultId: string;
|
||||
createdAtUtc: string | null;
|
||||
sourceSessionId: string;
|
||||
status: "accepted-recorded-source-paced-shadow";
|
||||
e32ResultId: string;
|
||||
pipelineId: string;
|
||||
mode: string;
|
||||
worker: {
|
||||
node: string;
|
||||
containerImage: string;
|
||||
python: string;
|
||||
numpy: string;
|
||||
};
|
||||
metrics: {
|
||||
sourceFrames: number;
|
||||
sourceAvailableFrames: number;
|
||||
sourceUnavailableFrames: number;
|
||||
deliveredFrames: number;
|
||||
inputSuperseded: number;
|
||||
resultSuperseded: number;
|
||||
effectiveDeliveryFps: number;
|
||||
sourceSpanSeconds: number;
|
||||
deadlineMissFraction: number;
|
||||
processingP95Ms: number;
|
||||
releaseLagP95Ms: number;
|
||||
resultAgeP95Ms: number;
|
||||
resultAgeMaxMs: number;
|
||||
resultDeadlineMs: number;
|
||||
processRssP95Mib: number;
|
||||
processRssMaxMib: number;
|
||||
processRssLimitMib: number;
|
||||
gpuUtilizationP95Percent: number;
|
||||
gpuVisible: boolean;
|
||||
workQueueCapacity: number;
|
||||
resultQueueCapacity: number;
|
||||
wallToIdealRatio: number;
|
||||
};
|
||||
access: "read-only";
|
||||
}
|
||||
|
||||
export interface E37AcceptanceContractResult {
|
||||
resultId: string;
|
||||
createdAtUtc: string | null;
|
||||
sourceSessionId: string;
|
||||
sourceDisplayName: string;
|
||||
status: "accepted-r0-source-scoped-contract";
|
||||
profileId: string;
|
||||
workerNode: string;
|
||||
metrics: {
|
||||
reviewedItems: number;
|
||||
developmentItems: number;
|
||||
validationItems: number;
|
||||
engineeringItems: number;
|
||||
humanExceptionItems: number;
|
||||
terminalOutcomes: number;
|
||||
accountingFraction: number;
|
||||
falseFreeClaims: number;
|
||||
};
|
||||
dimensionDistributions: {
|
||||
presence: Readonly<Record<string, number>>;
|
||||
geometryAssociation: Readonly<Record<string, number>>;
|
||||
freshness: Readonly<Record<string, number>>;
|
||||
};
|
||||
severityDistribution: Readonly<Record<string, number>>;
|
||||
labelProvenance: {
|
||||
engineeringItems: number;
|
||||
humanExceptionItems: number;
|
||||
independentGroundTruth: false;
|
||||
};
|
||||
split: {
|
||||
strategy: string;
|
||||
validationFraction: number;
|
||||
};
|
||||
targets: {
|
||||
presenceTarget: number;
|
||||
geometryAssociationTarget: number;
|
||||
freshnessTarget: number;
|
||||
};
|
||||
qualityTargetEvaluated: false;
|
||||
limitations: readonly string[];
|
||||
access: "read-only";
|
||||
}
|
||||
|
||||
export interface E38DimensionMetric {
|
||||
correct: number;
|
||||
incorrect: number;
|
||||
total: number;
|
||||
accuracy: number;
|
||||
target: number;
|
||||
passed: boolean;
|
||||
byStratum: Readonly<Record<string, {
|
||||
correct: number;
|
||||
incorrect: number;
|
||||
total: number;
|
||||
accuracy: number;
|
||||
}>>;
|
||||
confusion: readonly {
|
||||
reference: string;
|
||||
prediction: string;
|
||||
count: number;
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface E38PerceptionBaselineResult {
|
||||
resultId: string;
|
||||
createdAtUtc: string | null;
|
||||
sourceSessionId: string;
|
||||
sourceDisplayName: string;
|
||||
status: "measured-r1-source-scoped-baseline";
|
||||
profileId: string;
|
||||
workerNode: string;
|
||||
qualityGatePassed: boolean;
|
||||
metrics: {
|
||||
developmentItems: number;
|
||||
validationItems: number;
|
||||
terminalOutcomes: number;
|
||||
accountingFraction: number;
|
||||
falseFreeClaims: number;
|
||||
highSeverityFailures: number;
|
||||
dimensions: {
|
||||
presence: E38DimensionMetric;
|
||||
geometryAssociation: E38DimensionMetric;
|
||||
freshness: E38DimensionMetric;
|
||||
};
|
||||
};
|
||||
blockingChecks: readonly string[];
|
||||
limitations: readonly string[];
|
||||
access: "read-only";
|
||||
}
|
||||
|
||||
export interface DevelopmentDimensionMetric {
|
||||
correct: number;
|
||||
incorrect: number;
|
||||
total: number;
|
||||
accuracy: number;
|
||||
target: number;
|
||||
passed: boolean;
|
||||
}
|
||||
|
||||
export type E39DevelopmentDimensionMetric = DevelopmentDimensionMetric;
|
||||
|
||||
export interface E39PerceptionRefinementResult {
|
||||
resultId: string;
|
||||
createdAtUtc: string | null;
|
||||
sourceSessionId: string;
|
||||
sourceDisplayName: string;
|
||||
status: "measured-r1-source-scoped-refinement";
|
||||
profileId: string;
|
||||
workerNode: string;
|
||||
qualityGatePassed: boolean;
|
||||
developmentCrossValidation: {
|
||||
strategy: string;
|
||||
seed: string;
|
||||
folds: number;
|
||||
items: number;
|
||||
validationLabelsUsed: false;
|
||||
passed: boolean;
|
||||
dimensions: {
|
||||
presence: DevelopmentDimensionMetric;
|
||||
geometryAssociation: DevelopmentDimensionMetric;
|
||||
freshness: DevelopmentDimensionMetric;
|
||||
};
|
||||
};
|
||||
metrics: E38PerceptionBaselineResult["metrics"];
|
||||
blockingChecks: readonly string[];
|
||||
method: {
|
||||
summary: string;
|
||||
selection: string;
|
||||
dimensionProjection: string;
|
||||
};
|
||||
limitations: readonly string[];
|
||||
access: "read-only";
|
||||
}
|
||||
|
||||
export class AdvancedLaboratoryContractError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = "AdvancedLaboratoryContractError";
|
||||
}
|
||||
}
|
||||
export type LaboratoryFetch = (
|
||||
input: RequestInfo | URL,
|
||||
init?: RequestInit,
|
||||
) => Promise<Response>;
|
||||
|
||||
function record(value: unknown, label: string): Record<string, unknown> {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||
throw new AdvancedLaboratoryContractError(`${label}: ожидался объект.`);
|
||||
}
|
||||
return value as Record<string, unknown>;
|
||||
}
|
||||
|
||||
function stringValue(value: unknown, label: string): string {
|
||||
if (typeof value !== "string" || !value.trim()) {
|
||||
throw new AdvancedLaboratoryContractError(`${label}: ожидалась строка.`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function optionalString(value: unknown, label: string): string | null {
|
||||
return value === null ? null : stringValue(value, label);
|
||||
}
|
||||
|
||||
function numberValue(value: unknown, label: string): number {
|
||||
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
||||
throw new AdvancedLaboratoryContractError(`${label}: ожидалось число.`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function integerValue(value: unknown, label: string): number {
|
||||
const parsed = numberValue(value, label);
|
||||
if (!Number.isSafeInteger(parsed)) {
|
||||
throw new AdvancedLaboratoryContractError(`${label}: ожидалось целое число.`);
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function booleanValue(value: unknown, label: string): boolean {
|
||||
if (typeof value !== "boolean") {
|
||||
throw new AdvancedLaboratoryContractError(`${label}: ожидался boolean.`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function trueValue(value: unknown, label: string): true {
|
||||
if (value !== true) {
|
||||
throw new AdvancedLaboratoryContractError(`${label}: ожидалось true.`);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function falseValue(value: unknown, label: string): false {
|
||||
if (value !== false) {
|
||||
throw new AdvancedLaboratoryContractError(`${label}: ожидалось false.`);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function finiteNumber(value: unknown, label: string): number {
|
||||
if (typeof value !== "number" || !Number.isFinite(value)) {
|
||||
throw new AdvancedLaboratoryContractError(`${label}: ожидалось число.`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function exactString<T extends string>(
|
||||
value: unknown,
|
||||
expected: T,
|
||||
label: string,
|
||||
): T {
|
||||
if (value !== expected) {
|
||||
throw new AdvancedLaboratoryContractError(`${label}: неверное значение.`);
|
||||
}
|
||||
return expected;
|
||||
}
|
||||
|
||||
function strings(value: unknown, label: string): readonly string[] {
|
||||
if (!Array.isArray(value)) {
|
||||
throw new AdvancedLaboratoryContractError(`${label}: ожидался массив.`);
|
||||
}
|
||||
return value.map((item, index) => stringValue(item, `${label}[${index}]`));
|
||||
}
|
||||
|
||||
function numberRecord(
|
||||
value: unknown,
|
||||
label: string,
|
||||
): Readonly<Record<string, number>> {
|
||||
const source = record(value, label);
|
||||
return Object.fromEntries(
|
||||
Object.entries(source).map(([key, item]) => [
|
||||
key,
|
||||
integerValue(item, `${label}.${key}`),
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
function contentId(value: unknown, prefix: string, label: string): string {
|
||||
const parsed = stringValue(value, label);
|
||||
if (!new RegExp(`^${prefix}-[a-f0-9]{64}$`).test(parsed)) {
|
||||
throw new AdvancedLaboratoryContractError(`${label}: неверный content id.`);
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function sha256(value: unknown, label: string): string {
|
||||
const parsed = stringValue(value, label);
|
||||
if (!/^[a-f0-9]{64}$/.test(parsed)) {
|
||||
throw new AdvancedLaboratoryContractError(`${label}: неверный SHA-256.`);
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function diagnosticAuthority(value: unknown, label: string): void {
|
||||
const authority = record(value, label);
|
||||
if (
|
||||
authority.commands_enabled !== false
|
||||
|| authority.navigation_or_safety_accepted !== false
|
||||
) {
|
||||
throw new AdvancedLaboratoryContractError(`${label}: запрещённые полномочия.`);
|
||||
}
|
||||
}
|
||||
|
||||
function parseCatalog<T>(
|
||||
payload: unknown,
|
||||
parseItem: (value: unknown) => T,
|
||||
): T | null {
|
||||
const value = record(payload, "Каталог LAB");
|
||||
exactString(
|
||||
value.schema_version,
|
||||
"missioncore.laboratory-advanced-catalog/v1",
|
||||
"Каталог LAB.schema_version",
|
||||
);
|
||||
booleanValue(value.configured, "Каталог LAB.configured");
|
||||
integerValue(value.candidate_total, "Каталог LAB.candidate_total");
|
||||
integerValue(value.invalid_total, "Каталог LAB.invalid_total");
|
||||
exactString(value.access, "read-only", "Каталог LAB.access");
|
||||
if (!Array.isArray(value.items)) {
|
||||
throw new AdvancedLaboratoryContractError("Каталог LAB.items: ожидался массив.");
|
||||
}
|
||||
if (value.items.length > 1) {
|
||||
throw new AdvancedLaboratoryContractError("Каталог LAB.items: нарушен limit=1.");
|
||||
}
|
||||
return value.items.length ? parseItem(value.items[0]) : null;
|
||||
}
|
||||
|
||||
export function parseE31(value: unknown): E31LaboratoryResult {
|
||||
const item = record(value, "E31");
|
||||
const metrics = record(item.metrics, "E31.metrics");
|
||||
diagnosticAuthority(item.authority, "E31.authority");
|
||||
return {
|
||||
resultId: contentId(item.result_id, "e31-source-qualification", "E31.result_id"),
|
||||
createdAtUtc: optionalString(item.created_at_utc, "E31.created_at_utc"),
|
||||
sourceSessionId: stringValue(item.source_session_id, "E31.source_session_id"),
|
||||
status: exactString(
|
||||
item.status,
|
||||
"accepted-diagnostic-source-profile",
|
||||
"E31.status",
|
||||
),
|
||||
eligibleForE32: trueValue(item.eligible_for_e32, "E31.eligible_for_e32"),
|
||||
profileId: stringValue(item.profile_id, "E31.profile_id"),
|
||||
producerSha256: sha256(item.producer_sha256, "E31.producer_sha256"),
|
||||
metrics: {
|
||||
frameCount: integerValue(metrics.frame_count, "E31.metrics.frame_count"),
|
||||
availableBindingCount: integerValue(metrics.available_binding_count, "E31.metrics.available_binding_count"),
|
||||
availableFraction: numberValue(metrics.available_fraction, "E31.metrics.available_fraction"),
|
||||
lidarCameraP95Ms: numberValue(metrics.lidar_camera_p95_ms, "E31.metrics.lidar_camera_p95_ms"),
|
||||
posePointP95Ms: numberValue(metrics.pose_point_p95_ms, "E31.metrics.pose_point_p95_ms"),
|
||||
selectedOffsetMs: finiteNumber(metrics.selected_offset_ms, "E31.metrics.selected_offset_ms"),
|
||||
correspondenceCount: integerValue(metrics.correspondence_count, "E31.metrics.correspondence_count"),
|
||||
supportedFraction: numberValue(metrics.supported_fraction, "E31.metrics.supported_fraction"),
|
||||
semanticSelfSampleCount: integerValue(metrics.semantic_self_sample_count, "E31.metrics.semantic_self_sample_count"),
|
||||
semanticSelfCollateralCount: integerValue(metrics.semantic_self_collateral_count, "E31.metrics.semantic_self_collateral_count"),
|
||||
exactGeometryCorrectionCount: integerValue(metrics.exact_geometry_correction_count, "E31.metrics.exact_geometry_correction_count"),
|
||||
geometryPointMaskStatus: stringValue(metrics.geometry_point_mask_status, "E31.metrics.geometry_point_mask_status"),
|
||||
},
|
||||
limitations: strings(item.limitations, "E31.limitations"),
|
||||
access: exactString(item.access, "read-only", "E31.access"),
|
||||
};
|
||||
}
|
||||
|
||||
export function parseE32(value: unknown): E32LaboratoryResult {
|
||||
const item = record(value, "E32");
|
||||
const metrics = record(item.metrics, "E32.metrics");
|
||||
diagnosticAuthority(item.authority, "E32.authority");
|
||||
return {
|
||||
resultId: contentId(item.result_id, "e32-track-geometry", "E32.result_id"),
|
||||
createdAtUtc: optionalString(item.created_at_utc, "E32.created_at_utc"),
|
||||
sourceSessionId: stringValue(item.source_session_id, "E32.source_session_id"),
|
||||
status: exactString(
|
||||
item.status,
|
||||
"accepted-diagnostic-track-geometry-replay",
|
||||
"E32.status",
|
||||
),
|
||||
profileId: stringValue(item.profile_id, "E32.profile_id"),
|
||||
producerSha256: sha256(item.producer_sha256, "E32.producer_sha256"),
|
||||
e31ResultId: contentId(item.e31_result_id, "e31-source-qualification", "E32.e31_result_id"),
|
||||
metrics: {
|
||||
framesTotal: integerValue(metrics.frames_total, "E32.metrics.frames_total"),
|
||||
framesSourceAvailable: integerValue(metrics.frames_source_available, "E32.metrics.frames_source_available"),
|
||||
semanticPublished: integerValue(metrics.semantic_published, "E32.metrics.semantic_published"),
|
||||
semanticMasked: integerValue(metrics.semantic_masked, "E32.metrics.semantic_masked"),
|
||||
geometryPublished: integerValue(metrics.geometry_published, "E32.metrics.geometry_published"),
|
||||
observationsArbitrated: integerValue(metrics.observations_arbitrated, "E32.metrics.observations_arbitrated"),
|
||||
overlappingClaimsRemoved: integerValue(metrics.overlapping_claims_removed, "E32.metrics.overlapping_claims_removed"),
|
||||
qualifiedPointsPublished: integerValue(metrics.qualified_points_published, "E32.metrics.qualified_points_published"),
|
||||
qualifiedPointsWithheld: integerValue(metrics.qualified_points_withheld, "E32.metrics.qualified_points_withheld"),
|
||||
frameProcessingP95Ms: numberValue(metrics.frame_processing_p95_ms, "E32.metrics.frame_processing_p95_ms"),
|
||||
buildElapsedMs: numberValue(metrics.build_elapsed_ms, "E32.metrics.build_elapsed_ms"),
|
||||
},
|
||||
access: exactString(item.access, "read-only", "E32.access"),
|
||||
};
|
||||
}
|
||||
|
||||
export function parseE33(value: unknown): E33LaboratoryResult {
|
||||
const item = record(value, "E33");
|
||||
const worker = record(item.worker, "E33.worker");
|
||||
const metrics = record(item.metrics, "E33.metrics");
|
||||
diagnosticAuthority(item.authority, "E33.authority");
|
||||
return {
|
||||
resultId: contentId(item.result_id, "e33-worker-shadow", "E33.result_id"),
|
||||
createdAtUtc: optionalString(item.created_at_utc, "E33.created_at_utc"),
|
||||
sourceSessionId: stringValue(item.source_session_id, "E33.source_session_id"),
|
||||
status: exactString(
|
||||
item.status,
|
||||
"accepted-recorded-source-paced-shadow",
|
||||
"E33.status",
|
||||
),
|
||||
e32ResultId: contentId(item.e32_result_id, "e32-track-geometry", "E33.e32_result_id"),
|
||||
pipelineId: stringValue(item.pipeline_id, "E33.pipeline_id"),
|
||||
mode: stringValue(item.mode, "E33.mode"),
|
||||
worker: {
|
||||
node: stringValue(worker.node, "E33.worker.node"),
|
||||
containerImage: stringValue(worker.container_image, "E33.worker.container_image"),
|
||||
python: stringValue(worker.python, "E33.worker.python"),
|
||||
numpy: stringValue(worker.numpy, "E33.worker.numpy"),
|
||||
},
|
||||
metrics: {
|
||||
sourceFrames: integerValue(metrics.source_frames, "E33.metrics.source_frames"),
|
||||
sourceAvailableFrames: integerValue(metrics.source_available_frames, "E33.metrics.source_available_frames"),
|
||||
sourceUnavailableFrames: integerValue(metrics.source_unavailable_frames, "E33.metrics.source_unavailable_frames"),
|
||||
deliveredFrames: integerValue(metrics.delivered_frames, "E33.metrics.delivered_frames"),
|
||||
inputSuperseded: integerValue(metrics.input_superseded, "E33.metrics.input_superseded"),
|
||||
resultSuperseded: integerValue(metrics.result_superseded, "E33.metrics.result_superseded"),
|
||||
effectiveDeliveryFps: numberValue(metrics.effective_delivery_fps, "E33.metrics.effective_delivery_fps"),
|
||||
sourceSpanSeconds: numberValue(metrics.source_span_seconds, "E33.metrics.source_span_seconds"),
|
||||
deadlineMissFraction: numberValue(metrics.deadline_miss_fraction, "E33.metrics.deadline_miss_fraction"),
|
||||
processingP95Ms: numberValue(metrics.processing_p95_ms, "E33.metrics.processing_p95_ms"),
|
||||
releaseLagP95Ms: numberValue(metrics.release_lag_p95_ms, "E33.metrics.release_lag_p95_ms"),
|
||||
resultAgeP95Ms: numberValue(metrics.result_age_p95_ms, "E33.metrics.result_age_p95_ms"),
|
||||
resultAgeMaxMs: numberValue(metrics.result_age_max_ms, "E33.metrics.result_age_max_ms"),
|
||||
resultDeadlineMs: numberValue(metrics.result_deadline_ms, "E33.metrics.result_deadline_ms"),
|
||||
processRssP95Mib: numberValue(metrics.process_rss_p95_mib, "E33.metrics.process_rss_p95_mib"),
|
||||
processRssMaxMib: numberValue(metrics.process_rss_max_mib, "E33.metrics.process_rss_max_mib"),
|
||||
processRssLimitMib: numberValue(metrics.process_rss_limit_mib, "E33.metrics.process_rss_limit_mib"),
|
||||
gpuUtilizationP95Percent: numberValue(metrics.gpu_utilization_p95_percent, "E33.metrics.gpu_utilization_p95_percent"),
|
||||
gpuVisible: booleanValue(metrics.gpu_visible, "E33.metrics.gpu_visible"),
|
||||
workQueueCapacity: integerValue(metrics.work_queue_capacity, "E33.metrics.work_queue_capacity"),
|
||||
resultQueueCapacity: integerValue(metrics.result_queue_capacity, "E33.metrics.result_queue_capacity"),
|
||||
wallToIdealRatio: numberValue(metrics.wall_to_ideal_ratio, "E33.metrics.wall_to_ideal_ratio"),
|
||||
},
|
||||
access: exactString(item.access, "read-only", "E33.access"),
|
||||
};
|
||||
}
|
||||
|
||||
export function parseE37(value: unknown): E37AcceptanceContractResult {
|
||||
const item = record(value, "E37");
|
||||
const metrics = record(item.metrics, "E37.metrics");
|
||||
const distributions = record(
|
||||
item.dimension_distributions,
|
||||
"E37.dimension_distributions",
|
||||
);
|
||||
const provenance = record(item.label_provenance, "E37.label_provenance");
|
||||
const split = record(item.split, "E37.split");
|
||||
const targets = record(item.targets, "E37.targets");
|
||||
diagnosticAuthority(item.authority, "E37.authority");
|
||||
if (item.quality_target_evaluated !== false) {
|
||||
throw new AdvancedLaboratoryContractError(
|
||||
"E37.quality_target_evaluated: R0 не должен объявлять метрику проверенной.",
|
||||
);
|
||||
}
|
||||
if (provenance.independent_ground_truth !== false) {
|
||||
throw new AdvancedLaboratoryContractError(
|
||||
"E37.label_provenance: R0 не является независимой разметкой.",
|
||||
);
|
||||
}
|
||||
return {
|
||||
resultId: contentId(
|
||||
item.result_id,
|
||||
"e37-ravnoves-acceptance",
|
||||
"E37.result_id",
|
||||
),
|
||||
createdAtUtc: optionalString(item.created_at_utc, "E37.created_at_utc"),
|
||||
sourceSessionId: stringValue(
|
||||
item.source_session_id,
|
||||
"E37.source_session_id",
|
||||
),
|
||||
sourceDisplayName: stringValue(
|
||||
item.source_display_name,
|
||||
"E37.source_display_name",
|
||||
),
|
||||
status: exactString(
|
||||
item.status,
|
||||
"accepted-r0-source-scoped-contract",
|
||||
"E37.status",
|
||||
),
|
||||
profileId: stringValue(item.profile_id, "E37.profile_id"),
|
||||
workerNode: stringValue(item.worker_node, "E37.worker_node"),
|
||||
metrics: {
|
||||
reviewedItems: integerValue(
|
||||
metrics.reviewed_items,
|
||||
"E37.metrics.reviewed_items",
|
||||
),
|
||||
developmentItems: integerValue(
|
||||
metrics.development_items,
|
||||
"E37.metrics.development_items",
|
||||
),
|
||||
validationItems: integerValue(
|
||||
metrics.validation_items,
|
||||
"E37.metrics.validation_items",
|
||||
),
|
||||
engineeringItems: integerValue(
|
||||
metrics.engineering_items,
|
||||
"E37.metrics.engineering_items",
|
||||
),
|
||||
humanExceptionItems: integerValue(
|
||||
metrics.human_exception_items,
|
||||
"E37.metrics.human_exception_items",
|
||||
),
|
||||
terminalOutcomes: integerValue(
|
||||
metrics.terminal_outcomes,
|
||||
"E37.metrics.terminal_outcomes",
|
||||
),
|
||||
accountingFraction: numberValue(
|
||||
metrics.accounting_fraction,
|
||||
"E37.metrics.accounting_fraction",
|
||||
),
|
||||
falseFreeClaims: integerValue(
|
||||
metrics.false_free_claims,
|
||||
"E37.metrics.false_free_claims",
|
||||
),
|
||||
},
|
||||
dimensionDistributions: {
|
||||
presence: numberRecord(
|
||||
distributions.presence,
|
||||
"E37.dimension_distributions.presence",
|
||||
),
|
||||
geometryAssociation: numberRecord(
|
||||
distributions.geometry_association,
|
||||
"E37.dimension_distributions.geometry_association",
|
||||
),
|
||||
freshness: numberRecord(
|
||||
distributions.freshness,
|
||||
"E37.dimension_distributions.freshness",
|
||||
),
|
||||
},
|
||||
severityDistribution: numberRecord(
|
||||
item.severity_distribution,
|
||||
"E37.severity_distribution",
|
||||
),
|
||||
labelProvenance: {
|
||||
engineeringItems: integerValue(
|
||||
provenance.engineering_items,
|
||||
"E37.label_provenance.engineering_items",
|
||||
),
|
||||
humanExceptionItems: integerValue(
|
||||
provenance.human_exception_items,
|
||||
"E37.label_provenance.human_exception_items",
|
||||
),
|
||||
independentGroundTruth: false,
|
||||
},
|
||||
split: {
|
||||
strategy: stringValue(split.strategy, "E37.split.strategy"),
|
||||
validationFraction: numberValue(
|
||||
split.validation_fraction,
|
||||
"E37.split.validation_fraction",
|
||||
),
|
||||
},
|
||||
targets: {
|
||||
presenceTarget: numberValue(
|
||||
targets.presence_target,
|
||||
"E37.targets.presence_target",
|
||||
),
|
||||
geometryAssociationTarget: numberValue(
|
||||
targets.geometry_association_target,
|
||||
"E37.targets.geometry_association_target",
|
||||
),
|
||||
freshnessTarget: numberValue(
|
||||
targets.freshness_target,
|
||||
"E37.targets.freshness_target",
|
||||
),
|
||||
},
|
||||
qualityTargetEvaluated: false,
|
||||
limitations: strings(item.limitations, "E37.limitations"),
|
||||
access: exactString(item.access, "read-only", "E37.access"),
|
||||
};
|
||||
}
|
||||
|
||||
function parseE38Dimension(
|
||||
value: unknown,
|
||||
label: string,
|
||||
): E38DimensionMetric {
|
||||
const source = record(value, label);
|
||||
const byStratumSource = record(source.by_stratum, `${label}.by_stratum`);
|
||||
if (!Array.isArray(source.confusion)) {
|
||||
throw new AdvancedLaboratoryContractError(
|
||||
`${label}.confusion: ожидался массив.`,
|
||||
);
|
||||
}
|
||||
return {
|
||||
correct: integerValue(source.correct, `${label}.correct`),
|
||||
incorrect: integerValue(source.incorrect, `${label}.incorrect`),
|
||||
total: integerValue(source.total, `${label}.total`),
|
||||
accuracy: numberValue(source.accuracy, `${label}.accuracy`),
|
||||
target: numberValue(source.target, `${label}.target`),
|
||||
passed: booleanValue(source.passed, `${label}.passed`),
|
||||
byStratum: Object.fromEntries(
|
||||
Object.entries(byStratumSource).map(([stratum, value]) => {
|
||||
const metric = record(value, `${label}.by_stratum.${stratum}`);
|
||||
return [
|
||||
stratum,
|
||||
{
|
||||
correct: integerValue(
|
||||
metric.correct,
|
||||
`${label}.by_stratum.${stratum}.correct`,
|
||||
),
|
||||
incorrect: integerValue(
|
||||
metric.incorrect,
|
||||
`${label}.by_stratum.${stratum}.incorrect`,
|
||||
),
|
||||
total: integerValue(
|
||||
metric.total,
|
||||
`${label}.by_stratum.${stratum}.total`,
|
||||
),
|
||||
accuracy: numberValue(
|
||||
metric.accuracy,
|
||||
`${label}.by_stratum.${stratum}.accuracy`,
|
||||
),
|
||||
},
|
||||
];
|
||||
}),
|
||||
),
|
||||
confusion: source.confusion.map((value, index) => {
|
||||
const row = record(value, `${label}.confusion[${index}]`);
|
||||
return {
|
||||
reference: stringValue(
|
||||
row.reference,
|
||||
`${label}.confusion[${index}].reference`,
|
||||
),
|
||||
prediction: stringValue(
|
||||
row.prediction,
|
||||
`${label}.confusion[${index}].prediction`,
|
||||
),
|
||||
count: integerValue(
|
||||
row.count,
|
||||
`${label}.confusion[${index}].count`,
|
||||
),
|
||||
};
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
export function parseE38(value: unknown): E38PerceptionBaselineResult {
|
||||
const item = record(value, "E38");
|
||||
const metrics = record(item.metrics, "E38.metrics");
|
||||
const dimensions = record(metrics.dimensions, "E38.metrics.dimensions");
|
||||
diagnosticAuthority(item.authority, "E38.authority");
|
||||
return {
|
||||
resultId: contentId(
|
||||
item.result_id,
|
||||
"e38-perception-baseline",
|
||||
"E38.result_id",
|
||||
),
|
||||
createdAtUtc: optionalString(item.created_at_utc, "E38.created_at_utc"),
|
||||
sourceSessionId: stringValue(
|
||||
item.source_session_id,
|
||||
"E38.source_session_id",
|
||||
),
|
||||
sourceDisplayName: stringValue(
|
||||
item.source_display_name,
|
||||
"E38.source_display_name",
|
||||
),
|
||||
status: exactString(
|
||||
item.status,
|
||||
"measured-r1-source-scoped-baseline",
|
||||
"E38.status",
|
||||
),
|
||||
profileId: stringValue(item.profile_id, "E38.profile_id"),
|
||||
workerNode: stringValue(item.worker_node, "E38.worker_node"),
|
||||
qualityGatePassed: booleanValue(
|
||||
item.quality_gate_passed,
|
||||
"E38.quality_gate_passed",
|
||||
),
|
||||
metrics: {
|
||||
developmentItems: integerValue(
|
||||
metrics.development_items,
|
||||
"E38.metrics.development_items",
|
||||
),
|
||||
validationItems: integerValue(
|
||||
metrics.validation_items,
|
||||
"E38.metrics.validation_items",
|
||||
),
|
||||
terminalOutcomes: integerValue(
|
||||
metrics.terminal_outcomes,
|
||||
"E38.metrics.terminal_outcomes",
|
||||
),
|
||||
accountingFraction: numberValue(
|
||||
metrics.accounting_fraction,
|
||||
"E38.metrics.accounting_fraction",
|
||||
),
|
||||
falseFreeClaims: integerValue(
|
||||
metrics.false_free_claims,
|
||||
"E38.metrics.false_free_claims",
|
||||
),
|
||||
highSeverityFailures: integerValue(
|
||||
metrics.high_severity_failures,
|
||||
"E38.metrics.high_severity_failures",
|
||||
),
|
||||
dimensions: {
|
||||
presence: parseE38Dimension(
|
||||
dimensions.presence,
|
||||
"E38.metrics.dimensions.presence",
|
||||
),
|
||||
geometryAssociation: parseE38Dimension(
|
||||
dimensions.geometry_association,
|
||||
"E38.metrics.dimensions.geometry_association",
|
||||
),
|
||||
freshness: parseE38Dimension(
|
||||
dimensions.freshness,
|
||||
"E38.metrics.dimensions.freshness",
|
||||
),
|
||||
},
|
||||
},
|
||||
blockingChecks: strings(item.blocking_checks, "E38.blocking_checks"),
|
||||
limitations: strings(item.limitations, "E38.limitations"),
|
||||
access: exactString(item.access, "read-only", "E38.access"),
|
||||
};
|
||||
}
|
||||
|
||||
function parseDevelopmentDimension(
|
||||
value: unknown,
|
||||
label: string,
|
||||
): DevelopmentDimensionMetric {
|
||||
const source = record(value, label);
|
||||
return {
|
||||
correct: integerValue(source.correct, `${label}.correct`),
|
||||
incorrect: integerValue(source.incorrect, `${label}.incorrect`),
|
||||
total: integerValue(source.total, `${label}.total`),
|
||||
accuracy: numberValue(source.accuracy, `${label}.accuracy`),
|
||||
target: numberValue(source.target, `${label}.target`),
|
||||
passed: booleanValue(source.passed, `${label}.passed`),
|
||||
};
|
||||
}
|
||||
|
||||
export function parseE39(value: unknown): E39PerceptionRefinementResult {
|
||||
const item = record(value, "E39");
|
||||
const metrics = record(item.metrics, "E39.metrics");
|
||||
const dimensions = record(metrics.dimensions, "E39.metrics.dimensions");
|
||||
const developmentCrossValidation = record(
|
||||
item.development_cross_validation,
|
||||
"E39.development_cross_validation",
|
||||
);
|
||||
const developmentDimensions = record(
|
||||
developmentCrossValidation.dimensions,
|
||||
"E39.development_cross_validation.dimensions",
|
||||
);
|
||||
const method = record(item.method, "E39.method");
|
||||
diagnosticAuthority(item.authority, "E39.authority");
|
||||
return {
|
||||
resultId: contentId(
|
||||
item.result_id,
|
||||
"e39-perception-refinement",
|
||||
"E39.result_id",
|
||||
),
|
||||
createdAtUtc: optionalString(item.created_at_utc, "E39.created_at_utc"),
|
||||
sourceSessionId: stringValue(
|
||||
item.source_session_id,
|
||||
"E39.source_session_id",
|
||||
),
|
||||
sourceDisplayName: stringValue(
|
||||
item.source_display_name,
|
||||
"E39.source_display_name",
|
||||
),
|
||||
status: exactString(
|
||||
item.status,
|
||||
"measured-r1-source-scoped-refinement",
|
||||
"E39.status",
|
||||
),
|
||||
profileId: stringValue(item.profile_id, "E39.profile_id"),
|
||||
workerNode: stringValue(item.worker_node, "E39.worker_node"),
|
||||
qualityGatePassed: booleanValue(
|
||||
item.quality_gate_passed,
|
||||
"E39.quality_gate_passed",
|
||||
),
|
||||
developmentCrossValidation: {
|
||||
strategy: stringValue(
|
||||
developmentCrossValidation.strategy,
|
||||
"E39.development_cross_validation.strategy",
|
||||
),
|
||||
seed: stringValue(
|
||||
developmentCrossValidation.seed,
|
||||
"E39.development_cross_validation.seed",
|
||||
),
|
||||
folds: integerValue(
|
||||
developmentCrossValidation.folds,
|
||||
"E39.development_cross_validation.folds",
|
||||
),
|
||||
items: integerValue(
|
||||
developmentCrossValidation.items,
|
||||
"E39.development_cross_validation.items",
|
||||
),
|
||||
validationLabelsUsed: falseValue(
|
||||
developmentCrossValidation.validation_labels_used,
|
||||
"E39.development_cross_validation.validation_labels_used",
|
||||
),
|
||||
passed: booleanValue(
|
||||
developmentCrossValidation.passed,
|
||||
"E39.development_cross_validation.passed",
|
||||
),
|
||||
dimensions: {
|
||||
presence: parseDevelopmentDimension(
|
||||
developmentDimensions.presence,
|
||||
"E39.development_cross_validation.dimensions.presence",
|
||||
),
|
||||
geometryAssociation: parseDevelopmentDimension(
|
||||
developmentDimensions.geometry_association,
|
||||
"E39.development_cross_validation.dimensions.geometry_association",
|
||||
),
|
||||
freshness: parseDevelopmentDimension(
|
||||
developmentDimensions.freshness,
|
||||
"E39.development_cross_validation.dimensions.freshness",
|
||||
),
|
||||
},
|
||||
},
|
||||
metrics: {
|
||||
developmentItems: integerValue(
|
||||
metrics.development_items,
|
||||
"E39.metrics.development_items",
|
||||
),
|
||||
validationItems: integerValue(
|
||||
metrics.validation_items,
|
||||
"E39.metrics.validation_items",
|
||||
),
|
||||
terminalOutcomes: integerValue(
|
||||
metrics.terminal_outcomes,
|
||||
"E39.metrics.terminal_outcomes",
|
||||
),
|
||||
accountingFraction: numberValue(
|
||||
metrics.accounting_fraction,
|
||||
"E39.metrics.accounting_fraction",
|
||||
),
|
||||
falseFreeClaims: integerValue(
|
||||
metrics.false_free_claims,
|
||||
"E39.metrics.false_free_claims",
|
||||
),
|
||||
highSeverityFailures: integerValue(
|
||||
metrics.high_severity_failures,
|
||||
"E39.metrics.high_severity_failures",
|
||||
),
|
||||
dimensions: {
|
||||
presence: parseE38Dimension(
|
||||
dimensions.presence,
|
||||
"E39.metrics.dimensions.presence",
|
||||
),
|
||||
geometryAssociation: parseE38Dimension(
|
||||
dimensions.geometry_association,
|
||||
"E39.metrics.dimensions.geometry_association",
|
||||
),
|
||||
freshness: parseE38Dimension(
|
||||
dimensions.freshness,
|
||||
"E39.metrics.dimensions.freshness",
|
||||
),
|
||||
},
|
||||
},
|
||||
blockingChecks: strings(item.blocking_checks, "E39.blocking_checks"),
|
||||
method: {
|
||||
summary: stringValue(method.summary, "E39.method.summary"),
|
||||
selection: stringValue(method.selection, "E39.method.selection"),
|
||||
dimensionProjection: stringValue(
|
||||
method.dimension_projection,
|
||||
"E39.method.dimension_projection",
|
||||
),
|
||||
},
|
||||
limitations: strings(item.limitations, "E39.limitations"),
|
||||
access: exactString(item.access, "read-only", "E39.access"),
|
||||
};
|
||||
}
|
||||
|
||||
export async function fetchOne<T>(
|
||||
path: string,
|
||||
parser: (value: unknown) => T,
|
||||
fetcher: LaboratoryFetch,
|
||||
signal?: AbortSignal,
|
||||
): Promise<T | null> {
|
||||
const response = await fetcher(path, {
|
||||
method: "GET",
|
||||
headers: { Accept: "application/json" },
|
||||
signal,
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new AdvancedLaboratoryContractError(`Каталог LAB недоступен: HTTP ${response.status}.`);
|
||||
}
|
||||
return parseCatalog(await response.json(), parser);
|
||||
}
|
||||
export async function fetchAdvancedLaboratoryResults({
|
||||
fetcher = fetch,
|
||||
signal,
|
||||
}: {
|
||||
fetcher?: LaboratoryFetch;
|
||||
signal?: AbortSignal;
|
||||
} = {}): Promise<AdvancedLaboratoryResults> {
|
||||
const settled = await Promise.allSettled([
|
||||
fetchOne("/api/v1/laboratory/e31/results?limit=1", parseE31, fetcher, signal),
|
||||
fetchOne("/api/v1/laboratory/e32/results?limit=1", parseE32, fetcher, signal),
|
||||
fetchOne("/api/v1/laboratory/e33/results?limit=1", parseE33, fetcher, signal),
|
||||
fetchE34TemporalLayerResult({ fetcher, signal }),
|
||||
fetchE35DegradationRecoveryResult({ fetcher, signal }),
|
||||
fetchOne("/api/v1/laboratory/e37/results?limit=1", parseE37, fetcher, signal),
|
||||
fetchOne("/api/v1/laboratory/e38/results?limit=1", parseE38, fetcher, signal),
|
||||
fetchOne("/api/v1/laboratory/e39/results?limit=1", parseE39, fetcher, signal),
|
||||
fetchE40ProductGateResult({ fetcher, signal }),
|
||||
]);
|
||||
const e31 = settledCatalogValue(settled[0]);
|
||||
const e32 = settledCatalogValue(settled[1]);
|
||||
const e33 = settledCatalogValue(settled[2]);
|
||||
const e34 = settledCatalogValue(settled[3]);
|
||||
const e35 = settledCatalogValue(settled[4]);
|
||||
const e37 = settledCatalogValue(settled[5]);
|
||||
const e38 = settledCatalogValue(settled[6]);
|
||||
const e39 = settledCatalogValue(settled[7]);
|
||||
const e40 = settledCatalogValue(settled[8]);
|
||||
return {
|
||||
vegetationBenchmark: null,
|
||||
vegetationShadow: null,
|
||||
m47Graph: null, m48: null, m48SmallStatic: null, m48StaticOccupancy: null,
|
||||
m48r3StaticOccupancy: null,
|
||||
m48s: null, m48t: null, m49Tgs: null, m49TgsFull: null, m4Threat: null,
|
||||
l3: null, l31: null, l32: null, l33: null,
|
||||
e31,
|
||||
e32,
|
||||
e33,
|
||||
e34,
|
||||
e35,
|
||||
e37,
|
||||
e38,
|
||||
e39,
|
||||
e40,
|
||||
e46: null,
|
||||
e46a: null,
|
||||
e46b: null,
|
||||
e46c: null,
|
||||
e46d: null,
|
||||
e46e: null, e46f: null,
|
||||
e46g: null, e46h: null, e46i: null, e46j: null,
|
||||
e47: null,
|
||||
l34: null,
|
||||
l34a: null,
|
||||
l34b: null,
|
||||
l34c: null,
|
||||
l34d: null,
|
||||
l34e: null,
|
||||
l34f: null,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
import type { ObservationSessionReplayLaunch } from "../observation/sessionArchive";
|
||||
import type { RecordedRrdArtifactDescriptor } from "../observation/viewerProfile";
|
||||
|
||||
const SAFE_RESULT_ID = /^lab-v1-vegetation-shadow-[a-f0-9]{64}$/;
|
||||
const SAFE_SESSION_SOURCE = /^\/api\/v1\/observation-sessions\/[A-Za-z0-9][A-Za-z0-9._:-]{0,127}\/recording\.rrd$/;
|
||||
const MAX_CANONICAL_REPLAY_BYTES = 1024 * 1024 * 1024;
|
||||
|
||||
export interface CanonicalLabReplayDescriptor extends RecordedRrdArtifactDescriptor {
|
||||
blueprintSourceUrl: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the one immutable RRD used by the canonical recorded LAB.
|
||||
*
|
||||
* The server caches the merge of the sealed spatial recording and the LAB AI
|
||||
* evidence. Rerun therefore opens one source and cannot present the base store
|
||||
* before a second receiver has finished decoding the semantic layer.
|
||||
*/
|
||||
export async function resolveCanonicalLabReplay(
|
||||
resultId: string,
|
||||
launch: ObservationSessionReplayLaunch,
|
||||
{
|
||||
origin = window.location.origin,
|
||||
signal,
|
||||
fetcher = globalThis.fetch,
|
||||
sourceKind = "legacy-vegetation",
|
||||
}: {
|
||||
origin?: string;
|
||||
signal?: AbortSignal;
|
||||
fetcher?: typeof globalThis.fetch;
|
||||
sourceKind?: "legacy-vegetation" | "portable-tgs" | "portable-semantic" | "portable-objects";
|
||||
} = {},
|
||||
): Promise<CanonicalLabReplayDescriptor> {
|
||||
const base = new URL(origin);
|
||||
if (
|
||||
!(sourceKind === "portable-tgs"
|
||||
? /^m49-tgs-portable-review-[a-f0-9]{64}$/.test(resultId)
|
||||
: sourceKind === "portable-semantic"
|
||||
? /^(?:lab-v1-eomt-ddrnet|ai-layer-(?:ddrnet|eomt))-[a-f0-9]{64}$/.test(resultId)
|
||||
: sourceKind === "portable-objects"
|
||||
? /^ai-layer-(?:rf-detr|object-distance)-[a-f0-9]{64}$/.test(resultId)
|
||||
: SAFE_RESULT_ID.test(resultId))
|
||||
|| !SAFE_SESSION_SOURCE.test(launch.sourceUrl)
|
||||
|| launch.viewerSourceUrl !== `${launch.sourceUrl}?generation=${launch.sha256}`
|
||||
|| !/^[a-f0-9]{64}$/.test(launch.sha256)
|
||||
) {
|
||||
throw new Error("Канонический replay LAB имеет небезопасный descriptor.");
|
||||
}
|
||||
const sourceUrl = sourceKind !== "legacy-vegetation"
|
||||
? `/api/v1/observatory/portable-results/${resultId}/replays/${launch.sha256}/recording.rrd`
|
||||
: `/api/v1/laboratory/vegetation-shadow/${encodeURIComponent(resultId)}/canonical-replay.rrd`;
|
||||
const descriptorUrl = new URL(sourceUrl, `${base.origin}/`);
|
||||
if (sourceKind === "legacy-vegetation") descriptorUrl.searchParams.set("base_generation", launch.sha256);
|
||||
if (descriptorUrl.origin !== base.origin) {
|
||||
throw new Error("Канонический replay LAB должен быть same-origin.");
|
||||
}
|
||||
const response = await fetcher(descriptorUrl.href, {
|
||||
method: "HEAD",
|
||||
credentials: "same-origin",
|
||||
headers: { Accept: "application/vnd.rerun.rrd" },
|
||||
signal,
|
||||
});
|
||||
const contentType = response.headers.get("Content-Type")?.split(";", 1)[0].trim();
|
||||
const byteLength = Number(response.headers.get("Content-Length"));
|
||||
const sha256 = response.headers.get("ETag")?.match(/^"([a-f0-9]{64})"$/)?.[1];
|
||||
if (
|
||||
response.status !== 200
|
||||
|| contentType !== "application/vnd.rerun.rrd"
|
||||
|| response.headers.get("X-Rerun-Format") !== "RRF2"
|
||||
|| !sha256
|
||||
|| !Number.isSafeInteger(byteLength)
|
||||
|| byteLength < 4
|
||||
|| byteLength > MAX_CANONICAL_REPLAY_BYTES
|
||||
) {
|
||||
throw new Error("Единый replay LAB не прошёл проверку.");
|
||||
}
|
||||
return {
|
||||
sourceUrl,
|
||||
viewerSourceUrl: `${sourceUrl}?generation=${sha256}`,
|
||||
byteLength,
|
||||
sha256,
|
||||
blueprintSourceUrl: launch.sourceUrl.replace(/\/recording\.rrd$/, "/blueprint.rrd"),
|
||||
};
|
||||
}
|
||||
|
||||
export async function resolveAICompositionReplay(
|
||||
runId: string,
|
||||
launch: ObservationSessionReplayLaunch,
|
||||
{
|
||||
origin = window.location.origin,
|
||||
signal,
|
||||
fetcher = globalThis.fetch,
|
||||
}: {
|
||||
origin?: string;
|
||||
signal?: AbortSignal;
|
||||
fetcher?: typeof globalThis.fetch;
|
||||
} = {},
|
||||
): Promise<CanonicalLabReplayDescriptor> {
|
||||
const base = new URL(origin);
|
||||
if (
|
||||
!/^ai-composition-[a-f0-9]{64}$/.test(runId)
|
||||
|| !SAFE_SESSION_SOURCE.test(launch.sourceUrl)
|
||||
|| launch.viewerSourceUrl !== `${launch.sourceUrl}?generation=${launch.sha256}`
|
||||
|| !/^[a-f0-9]{64}$/.test(launch.sha256)
|
||||
) {
|
||||
throw new Error("Составной replay LAB имеет небезопасный descriptor.");
|
||||
}
|
||||
const sourceUrl = `/api/v1/observatory/ai-composition-runs/${runId}/replays/${launch.sha256}/recording.rrd`;
|
||||
const descriptorUrl = new URL(sourceUrl, `${base.origin}/`);
|
||||
const response = await fetcher(descriptorUrl.href, {
|
||||
method: "HEAD",
|
||||
credentials: "same-origin",
|
||||
headers: { Accept: "application/vnd.rerun.rrd" },
|
||||
signal,
|
||||
});
|
||||
const contentType = response.headers.get("Content-Type")?.split(";", 1)[0].trim();
|
||||
const byteLength = Number(response.headers.get("Content-Length"));
|
||||
const sha256 = response.headers.get("ETag")?.match(/^"([a-f0-9]{64})"$/)?.[1];
|
||||
if (
|
||||
response.status !== 200 || contentType !== "application/vnd.rerun.rrd"
|
||||
|| response.headers.get("X-Rerun-Format") !== "RRF2" || !sha256
|
||||
|| !Number.isSafeInteger(byteLength) || byteLength < 4
|
||||
|| byteLength > MAX_CANONICAL_REPLAY_BYTES
|
||||
) {
|
||||
throw new Error("Составной replay LAB не прошёл проверку.");
|
||||
}
|
||||
return {
|
||||
sourceUrl,
|
||||
viewerSourceUrl: `${sourceUrl}?generation=${sha256}`,
|
||||
byteLength,
|
||||
sha256,
|
||||
blueprintSourceUrl: launch.sourceUrl.replace(/\/recording\.rrd$/, "/blueprint.rrd"),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
export const CANONICAL_RECORDED_LAB_TGS_HISTORY_SECONDS = 1;
|
||||
export const CANONICAL_RECORDED_LAB_SPATIAL_PROFILE = "source-paced-ground-v3";
|
||||
|
||||
export interface CanonicalRecordedLabPackedCellEvidence {
|
||||
centersBodyXyM: Float32Array;
|
||||
zBoundsM: Float32Array;
|
||||
stateCodes: Uint8Array;
|
||||
}
|
||||
|
||||
export interface CanonicalRecordedLabBodyGroundFrame {
|
||||
originMapXyzM: readonly [number, number, number];
|
||||
sensorOriginMapXyzM: readonly [number, number, number];
|
||||
basisMapFromBody: readonly [
|
||||
readonly [number, number, number],
|
||||
readonly [number, number, number],
|
||||
readonly [number, number, number],
|
||||
];
|
||||
}
|
||||
|
||||
export interface CanonicalRecordedLabTgsCostmap {
|
||||
centersXyM: readonly (readonly [number, number])[];
|
||||
stateCodes: readonly number[];
|
||||
zBoundsM: readonly (readonly [number | null, number | null])[];
|
||||
}
|
||||
|
||||
export function canonicalRecordedLabTgsIsCurrent(
|
||||
currentTimeNs: number,
|
||||
anchorTimeNs: number,
|
||||
historySeconds = CANONICAL_RECORDED_LAB_TGS_HISTORY_SECONDS,
|
||||
): boolean {
|
||||
if (
|
||||
!Number.isSafeInteger(currentTimeNs)
|
||||
|| !Number.isSafeInteger(anchorTimeNs)
|
||||
|| !Number.isFinite(historySeconds)
|
||||
|| historySeconds <= 0
|
||||
) return false;
|
||||
const ageNs = currentTimeNs - anchorTimeNs;
|
||||
return ageNs >= 0 && ageNs <= Math.round(historySeconds * 1_000_000_000);
|
||||
}
|
||||
|
||||
export function canonicalMapGravityLocalPointToBodyGround(
|
||||
point: readonly [number, number, number],
|
||||
anchor: CanonicalRecordedLabBodyGroundFrame,
|
||||
current: CanonicalRecordedLabBodyGroundFrame,
|
||||
): readonly [number, number, number] {
|
||||
// TGS is translation-only map-gravity-local: its axes are map axes and its
|
||||
// origin is the LiDAR at the source frame. It is not an anchor body frame.
|
||||
const map: readonly [number, number, number] = [
|
||||
anchor.sensorOriginMapXyzM[0] + point[0],
|
||||
anchor.sensorOriginMapXyzM[1] + point[1],
|
||||
anchor.sensorOriginMapXyzM[2] + point[2],
|
||||
];
|
||||
const delta: readonly [number, number, number] = [
|
||||
map[0] - current.originMapXyzM[0],
|
||||
map[1] - current.originMapXyzM[1],
|
||||
map[2] - current.originMapXyzM[2],
|
||||
];
|
||||
return [
|
||||
current.basisMapFromBody[0][0] * delta[0]
|
||||
+ current.basisMapFromBody[1][0] * delta[1]
|
||||
+ current.basisMapFromBody[2][0] * delta[2],
|
||||
current.basisMapFromBody[0][1] * delta[0]
|
||||
+ current.basisMapFromBody[1][1] * delta[1]
|
||||
+ current.basisMapFromBody[2][1] * delta[2],
|
||||
current.basisMapFromBody[0][2] * delta[0]
|
||||
+ current.basisMapFromBody[1][2] * delta[1]
|
||||
+ current.basisMapFromBody[2][2] * delta[2],
|
||||
];
|
||||
}
|
||||
|
||||
export function canonicalRecordedLabPackedTgsCells(
|
||||
costmap: CanonicalRecordedLabTgsCostmap,
|
||||
anchor: CanonicalRecordedLabBodyGroundFrame,
|
||||
current: CanonicalRecordedLabBodyGroundFrame,
|
||||
): CanonicalRecordedLabPackedCellEvidence {
|
||||
if (
|
||||
costmap.centersXyM.length !== costmap.stateCodes.length
|
||||
|| costmap.centersXyM.length !== costmap.zBoundsM.length
|
||||
) throw new Error("Canonical recorded LAB TGS accounting changed");
|
||||
const centers: number[] = [];
|
||||
const zBounds: number[] = [];
|
||||
costmap.centersXyM.forEach(([x, y], index) => {
|
||||
const bounds = costmap.zBoundsM[index] ?? [null, null];
|
||||
const center = canonicalMapGravityLocalPointToBodyGround([x, y, 0], anchor, current);
|
||||
centers.push(center[0], center[1]);
|
||||
if (bounds[0] === null || bounds[1] === null) {
|
||||
zBounds.push(Number.NaN, Number.NaN);
|
||||
return;
|
||||
}
|
||||
const bottom = canonicalMapGravityLocalPointToBodyGround([x, y, bounds[0]], anchor, current);
|
||||
const top = canonicalMapGravityLocalPointToBodyGround([x, y, bounds[1]], anchor, current);
|
||||
zBounds.push(Math.min(bottom[2], top[2]), Math.max(bottom[2], top[2]));
|
||||
});
|
||||
return {
|
||||
centersBodyXyM: Float32Array.from(centers),
|
||||
zBoundsM: Float32Array.from(zBounds),
|
||||
stateCodes: Uint8Array.from(costmap.stateCodes),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
import type { LaboratoryFetch } from "./advancedResults";
|
||||
import { CANONICAL_RECORDED_LAB_SPATIAL_PROFILE } from "./canonicalRecordedLab";
|
||||
|
||||
const SAFE_SESSION_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
|
||||
const SHA256 = /^[a-f0-9]{64}$/;
|
||||
|
||||
export class CanonicalRecordedLabSpatialContractError extends Error {}
|
||||
|
||||
export interface CanonicalRecordedLabSpatialFrame {
|
||||
targetTimeNs: number;
|
||||
sourceTimeNs: number;
|
||||
poseTimeNs: number;
|
||||
trajectoryTimeNs: number;
|
||||
sourcePointCount: number;
|
||||
coordinateFrame: "body-ground";
|
||||
sensorHeight: {
|
||||
meters: number;
|
||||
source: "local-source-cloud-ground-quantile-median" | "session-source-cloud-fallback";
|
||||
sampleCount: number;
|
||||
madM: number;
|
||||
authority: "visual-derived";
|
||||
};
|
||||
spatialProfile: {
|
||||
profileId: typeof CANONICAL_RECORDED_LAB_SPATIAL_PROFILE;
|
||||
localSlamHistorySeconds: number;
|
||||
localSlamRadiusM: number;
|
||||
localSlamVoxelSizeM: number;
|
||||
localSlamPointLimit: number;
|
||||
};
|
||||
bodyFrame: {
|
||||
originMapXyzM: readonly [number, number, number];
|
||||
sensorOriginMapXyzM: readonly [number, number, number];
|
||||
basisMapFromBody: readonly [
|
||||
readonly [number, number, number],
|
||||
readonly [number, number, number],
|
||||
readonly [number, number, number],
|
||||
];
|
||||
};
|
||||
sourcePointsBodyXyzM: readonly (readonly [number, number, number])[];
|
||||
localSlamSourceFrameCount: number;
|
||||
localSlamSourcePointCount: number;
|
||||
localSlamBodyXyzM: readonly (readonly [number, number, number])[];
|
||||
}
|
||||
|
||||
function objectValue(value: unknown, label: string): Record<string, unknown> {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||
throw new CanonicalRecordedLabSpatialContractError(`${label}: ожидался объект.`);
|
||||
}
|
||||
return value as Record<string, unknown>;
|
||||
}
|
||||
|
||||
function arrayValue(value: unknown, label: string): unknown[] {
|
||||
if (!Array.isArray(value)) {
|
||||
throw new CanonicalRecordedLabSpatialContractError(`${label}: ожидался массив.`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function exact(value: unknown, expected: unknown, label: string): void {
|
||||
if (value !== expected) {
|
||||
throw new CanonicalRecordedLabSpatialContractError(`${label}: контракт изменён.`);
|
||||
}
|
||||
}
|
||||
|
||||
function numberValue(value: unknown, label: string): number {
|
||||
if (typeof value !== "number" || !Number.isFinite(value)) {
|
||||
throw new CanonicalRecordedLabSpatialContractError(`${label}: ожидалось число.`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function integerValue(value: unknown, label: string): number {
|
||||
const parsed = numberValue(value, label);
|
||||
if (!Number.isSafeInteger(parsed) || parsed < 0) {
|
||||
throw new CanonicalRecordedLabSpatialContractError(`${label}: ожидалось целое значение.`);
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function pointList(
|
||||
value: unknown,
|
||||
label: string,
|
||||
): readonly (readonly [number, number, number])[] {
|
||||
return arrayValue(value, label).map((entry, index) => {
|
||||
const point = arrayValue(entry, `${label}[${index}]`).map(
|
||||
(channel, channelIndex) => numberValue(channel, `${label}[${index}][${channelIndex}]`),
|
||||
);
|
||||
if (point.length !== 3) {
|
||||
throw new CanonicalRecordedLabSpatialContractError(`${label}[${index}]: размер изменён.`);
|
||||
}
|
||||
return [point[0]!, point[1]!, point[2]!] as const;
|
||||
});
|
||||
}
|
||||
|
||||
export async function fetchCanonicalRecordedLabSpatialFrame(
|
||||
sessionId: string,
|
||||
generationSha256: string,
|
||||
targetTimeNs: number,
|
||||
{
|
||||
fetcher = fetch,
|
||||
signal,
|
||||
}: { fetcher?: LaboratoryFetch; signal?: AbortSignal } = {},
|
||||
): Promise<CanonicalRecordedLabSpatialFrame> {
|
||||
if (
|
||||
!SAFE_SESSION_ID.test(sessionId)
|
||||
|| !SHA256.test(generationSha256)
|
||||
|| !Number.isSafeInteger(targetTimeNs)
|
||||
|| targetTimeNs < 0
|
||||
) {
|
||||
throw new CanonicalRecordedLabSpatialContractError(
|
||||
"Canonical LAB spatial identity недопустима.",
|
||||
);
|
||||
}
|
||||
const query = new URLSearchParams({
|
||||
generation: generationSha256,
|
||||
time_ns: String(targetTimeNs),
|
||||
profile: CANONICAL_RECORDED_LAB_SPATIAL_PROFILE,
|
||||
});
|
||||
const response = await fetcher(
|
||||
`/api/v1/observation-sessions/${encodeURIComponent(sessionId)}`
|
||||
+ `/canonical-lab/spatial-frame?${query.toString()}`,
|
||||
{ method: "GET", headers: { Accept: "application/json" }, signal },
|
||||
);
|
||||
if (!response.ok) {
|
||||
throw new CanonicalRecordedLabSpatialContractError(
|
||||
`Canonical LAB spatial frame недоступен: HTTP ${response.status}.`,
|
||||
);
|
||||
}
|
||||
const payload = objectValue(await response.json(), "canonical_lab.spatial_frame");
|
||||
exact(
|
||||
payload.schema_version,
|
||||
"missioncore.canonical-recorded-lab-spatial-frame/v3",
|
||||
"canonical_lab.spatial_frame.schema_version",
|
||||
);
|
||||
exact(payload.coordinate_frame, "body-ground", "canonical_lab.spatial_frame.coordinate_frame");
|
||||
exact(payload.target_time_ns, targetTimeNs, "canonical_lab.spatial_frame.target_time_ns");
|
||||
const sourcePoints = pointList(
|
||||
payload.source_points_body_xyz_m,
|
||||
"canonical_lab.spatial_frame.source_points",
|
||||
);
|
||||
const localSlam = pointList(
|
||||
payload.local_slam_body_xyz_m,
|
||||
"canonical_lab.spatial_frame.local_slam",
|
||||
);
|
||||
const sourcePointCount = integerValue(
|
||||
payload.source_point_count,
|
||||
"canonical_lab.spatial_frame.source_point_count",
|
||||
);
|
||||
const localSlamPointCount = integerValue(
|
||||
payload.local_slam_point_count,
|
||||
"canonical_lab.spatial_frame.local_slam_point_count",
|
||||
);
|
||||
if (
|
||||
sourcePointCount !== sourcePoints.length
|
||||
|| sourcePointCount > 100_000
|
||||
|| localSlamPointCount !== localSlam.length
|
||||
|| localSlam.length > 27_000
|
||||
) {
|
||||
throw new CanonicalRecordedLabSpatialContractError(
|
||||
"Canonical LAB spatial accounting изменён.",
|
||||
);
|
||||
}
|
||||
const bodyFrame = objectValue(payload.body_frame, "canonical_lab.spatial_frame.body_frame");
|
||||
const origin = pointList(
|
||||
[bodyFrame.origin_map_xyz_m],
|
||||
"canonical_lab.spatial_frame.body_frame.origin",
|
||||
)[0]!;
|
||||
const sensorOrigin = pointList(
|
||||
[bodyFrame.sensor_origin_map_xyz_m],
|
||||
"canonical_lab.spatial_frame.body_frame.sensor_origin",
|
||||
)[0]!;
|
||||
const basisRows = pointList(
|
||||
bodyFrame.basis_map_from_body,
|
||||
"canonical_lab.spatial_frame.body_frame.basis",
|
||||
);
|
||||
if (basisRows.length !== 3) {
|
||||
throw new CanonicalRecordedLabSpatialContractError(
|
||||
"Canonical LAB spatial basis изменён.",
|
||||
);
|
||||
}
|
||||
const sensorHeight = objectValue(payload.sensor_height, "canonical_lab.spatial_frame.sensor_height");
|
||||
if (
|
||||
sensorHeight.source !== "local-source-cloud-ground-quantile-median"
|
||||
&& sensorHeight.source !== "session-source-cloud-fallback"
|
||||
) {
|
||||
throw new CanonicalRecordedLabSpatialContractError(
|
||||
"canonical_lab.spatial_frame.sensor_height.source: контракт изменён.",
|
||||
);
|
||||
}
|
||||
exact(
|
||||
sensorHeight.authority,
|
||||
"visual-derived",
|
||||
"canonical_lab.spatial_frame.sensor_height.authority",
|
||||
);
|
||||
const spatialProfile = objectValue(
|
||||
payload.spatial_profile,
|
||||
"canonical_lab.spatial_frame.spatial_profile",
|
||||
);
|
||||
exact(
|
||||
spatialProfile.profile_id,
|
||||
CANONICAL_RECORDED_LAB_SPATIAL_PROFILE,
|
||||
"canonical_lab.spatial_frame.spatial_profile.profile_id",
|
||||
);
|
||||
return {
|
||||
targetTimeNs,
|
||||
sourceTimeNs: integerValue(payload.source_time_ns, "canonical_lab.spatial_frame.source_time_ns"),
|
||||
poseTimeNs: integerValue(payload.pose_time_ns, "canonical_lab.spatial_frame.pose_time_ns"),
|
||||
trajectoryTimeNs: integerValue(
|
||||
payload.trajectory_time_ns,
|
||||
"canonical_lab.spatial_frame.trajectory_time_ns",
|
||||
),
|
||||
sourcePointCount,
|
||||
coordinateFrame: "body-ground",
|
||||
sensorHeight: {
|
||||
meters: numberValue(sensorHeight.meters, "canonical_lab.spatial_frame.sensor_height.meters"),
|
||||
source: sensorHeight.source,
|
||||
sampleCount: integerValue(
|
||||
sensorHeight.sample_count,
|
||||
"canonical_lab.spatial_frame.sensor_height.sample_count",
|
||||
),
|
||||
madM: numberValue(sensorHeight.mad_m, "canonical_lab.spatial_frame.sensor_height.mad_m"),
|
||||
authority: "visual-derived",
|
||||
},
|
||||
spatialProfile: {
|
||||
profileId: CANONICAL_RECORDED_LAB_SPATIAL_PROFILE,
|
||||
localSlamHistorySeconds: numberValue(
|
||||
spatialProfile.local_slam_history_seconds,
|
||||
"canonical_lab.spatial_frame.spatial_profile.history",
|
||||
),
|
||||
localSlamRadiusM: numberValue(
|
||||
spatialProfile.local_slam_radius_m,
|
||||
"canonical_lab.spatial_frame.spatial_profile.radius",
|
||||
),
|
||||
localSlamVoxelSizeM: numberValue(
|
||||
spatialProfile.local_slam_voxel_size_m,
|
||||
"canonical_lab.spatial_frame.spatial_profile.voxel",
|
||||
),
|
||||
localSlamPointLimit: integerValue(
|
||||
spatialProfile.local_slam_point_limit,
|
||||
"canonical_lab.spatial_frame.spatial_profile.limit",
|
||||
),
|
||||
},
|
||||
bodyFrame: {
|
||||
originMapXyzM: origin,
|
||||
sensorOriginMapXyzM: sensorOrigin,
|
||||
basisMapFromBody: [basisRows[0]!, basisRows[1]!, basisRows[2]!],
|
||||
},
|
||||
sourcePointsBodyXyzM: sourcePoints,
|
||||
localSlamSourceFrameCount: integerValue(
|
||||
payload.local_slam_source_frame_count,
|
||||
"canonical_lab.spatial_frame.local_slam_source_frames",
|
||||
),
|
||||
localSlamSourcePointCount: integerValue(
|
||||
payload.local_slam_source_point_count,
|
||||
"canonical_lab.spatial_frame.local_slam_source_points",
|
||||
),
|
||||
localSlamBodyXyzM: localSlam,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
export function settledCatalogValue<T>(
|
||||
result: PromiseSettledResult<T>,
|
||||
): T | null {
|
||||
if (result.status === "fulfilled") {
|
||||
return result.value;
|
||||
}
|
||||
const reason = result.reason;
|
||||
const isAbort = reason instanceof Error && reason.name === "AbortError";
|
||||
const isHttpTransport = (
|
||||
reason instanceof Error
|
||||
&& /Каталог LAB.*недоступен: HTTP \d+\./.test(reason.message)
|
||||
);
|
||||
if (!isAbort && (reason instanceof TypeError || isHttpTransport)) {
|
||||
return null;
|
||||
}
|
||||
throw reason;
|
||||
}
|
||||
@@ -0,0 +1,548 @@
|
||||
export interface E29ReviewFrame {
|
||||
frameIndex: number;
|
||||
sourceFrameIndex: number;
|
||||
sessionSeconds: number;
|
||||
conflictCount: number;
|
||||
semanticObservationCount: number;
|
||||
geometryOnlyClusterCount: number;
|
||||
}
|
||||
|
||||
export interface E29EvidenceResult {
|
||||
resultId: string;
|
||||
createdAtUtc: string | null;
|
||||
status: "diagnostic-replay-complete";
|
||||
identity: {
|
||||
frameCount: number;
|
||||
timelineStartSeconds: number;
|
||||
timelineEndSeconds: number;
|
||||
profileId: string;
|
||||
producerSha256: string;
|
||||
};
|
||||
metrics: {
|
||||
frames: {
|
||||
total: number;
|
||||
sourceAvailable: number;
|
||||
localSurfaceValid: number;
|
||||
};
|
||||
semanticObservations: {
|
||||
total: number;
|
||||
current: number;
|
||||
agreementFractionOfCurrent: number;
|
||||
geometryStatus: {
|
||||
agree: number;
|
||||
cameraOnly: number;
|
||||
conflict: number;
|
||||
unknown: number;
|
||||
};
|
||||
};
|
||||
geometryOnlyOccupied: {
|
||||
clusterCount: number;
|
||||
pointCount: number;
|
||||
};
|
||||
runtime: {
|
||||
buildElapsedMs: number;
|
||||
frameProcessingP95Ms: number;
|
||||
};
|
||||
};
|
||||
decision: {
|
||||
cameraFirstContractImplemented: boolean;
|
||||
parallelGeometryOnlyLayerImplemented: boolean;
|
||||
productionPromotion: boolean;
|
||||
nextGate: string;
|
||||
};
|
||||
limitations: readonly string[];
|
||||
authority: {
|
||||
commandsEnabled: false;
|
||||
navigationOrSafetyAccepted: false;
|
||||
};
|
||||
reviewFrames: readonly E29ReviewFrame[];
|
||||
linkedEvidence: {
|
||||
sourceSessionId: string;
|
||||
localSurfaceModelId: string;
|
||||
sourcePackId: string;
|
||||
sourceResultId: string;
|
||||
publishedOnControlPlane: true;
|
||||
workerCopyRequired: false;
|
||||
};
|
||||
groundTruth: false;
|
||||
}
|
||||
|
||||
export interface E29EvidenceCatalog {
|
||||
configured: boolean;
|
||||
candidateTotal: number;
|
||||
invalidTotal: number;
|
||||
items: readonly E29EvidenceResult[];
|
||||
}
|
||||
|
||||
export interface E29SemanticObservation {
|
||||
trackId: number;
|
||||
label: string;
|
||||
geometryStatus: string;
|
||||
geometryReason: string;
|
||||
rangeM: number | null;
|
||||
score: number;
|
||||
support: {
|
||||
classifiedPoints: number;
|
||||
occupiedPoints: number;
|
||||
surfacePoints: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface E29GeometryOnlyCluster {
|
||||
nearestRangeM: number;
|
||||
pointCount: number;
|
||||
voxelCount: number;
|
||||
heightRangeM: readonly [number, number];
|
||||
}
|
||||
|
||||
export interface E29EvidenceFrame {
|
||||
resultId: string;
|
||||
frameIndex: number;
|
||||
sourceFrameIndex: number;
|
||||
sessionSeconds: number;
|
||||
semanticObservations: readonly E29SemanticObservation[];
|
||||
geometryOnlyOccupied: readonly E29GeometryOnlyCluster[];
|
||||
}
|
||||
|
||||
export class E29EvidenceContractError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = "E29EvidenceContractError";
|
||||
}
|
||||
}
|
||||
|
||||
type E29Fetch = (
|
||||
input: RequestInfo | URL,
|
||||
init?: RequestInit,
|
||||
) => Promise<Response>;
|
||||
|
||||
function record(value: unknown, label: string): Record<string, unknown> {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||
throw new E29EvidenceContractError(`${label}: ожидался объект.`);
|
||||
}
|
||||
return value as Record<string, unknown>;
|
||||
}
|
||||
|
||||
function stringValue(value: unknown, label: string): string {
|
||||
if (typeof value !== "string" || !value.trim()) {
|
||||
throw new E29EvidenceContractError(`${label}: ожидалась строка.`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function numberValue(value: unknown, label: string): number {
|
||||
if (typeof value !== "number" || !Number.isFinite(value)) {
|
||||
throw new E29EvidenceContractError(`${label}: ожидалось число.`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function integerValue(value: unknown, label: string): number {
|
||||
const parsed = numberValue(value, label);
|
||||
if (!Number.isSafeInteger(parsed) || parsed < 0) {
|
||||
throw new E29EvidenceContractError(`${label}: ожидалось целое число.`);
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function booleanValue(value: unknown, label: string): boolean {
|
||||
if (typeof value !== "boolean") {
|
||||
throw new E29EvidenceContractError(`${label}: ожидался boolean.`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function falseValue(value: unknown, label: string): false {
|
||||
if (booleanValue(value, label) !== false) {
|
||||
throw new E29EvidenceContractError(`${label}: ожидалось false.`);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function trueValue(value: unknown, label: string): true {
|
||||
if (booleanValue(value, label) !== true) {
|
||||
throw new E29EvidenceContractError(`${label}: ожидалось true.`);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function nullableNumber(value: unknown, label: string): number | null {
|
||||
return value === null ? null : numberValue(value, label);
|
||||
}
|
||||
|
||||
function tuple2(value: unknown, label: string): readonly [number, number] {
|
||||
if (!Array.isArray(value) || value.length !== 2) {
|
||||
throw new E29EvidenceContractError(`${label}: ожидалась пара чисел.`);
|
||||
}
|
||||
return [
|
||||
numberValue(value[0], `${label}[0]`),
|
||||
numberValue(value[1], `${label}[1]`),
|
||||
];
|
||||
}
|
||||
|
||||
function parseReviewFrame(value: unknown): E29ReviewFrame {
|
||||
const source = record(value, "review_frame");
|
||||
return {
|
||||
frameIndex: integerValue(source.frame_index, "review_frame.frame_index"),
|
||||
sourceFrameIndex: integerValue(
|
||||
source.source_frame_index,
|
||||
"review_frame.source_frame_index",
|
||||
),
|
||||
sessionSeconds: numberValue(
|
||||
source.session_seconds,
|
||||
"review_frame.session_seconds",
|
||||
),
|
||||
conflictCount: integerValue(
|
||||
source.conflict_count,
|
||||
"review_frame.conflict_count",
|
||||
),
|
||||
semanticObservationCount: integerValue(
|
||||
source.semantic_observation_count,
|
||||
"review_frame.semantic_observation_count",
|
||||
),
|
||||
geometryOnlyClusterCount: integerValue(
|
||||
source.geometry_only_cluster_count,
|
||||
"review_frame.geometry_only_cluster_count",
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
function parseEvidenceResult(value: unknown): E29EvidenceResult {
|
||||
const source = record(value, "E29 result");
|
||||
const identity = record(source.identity, "E29 identity");
|
||||
const profile = record(identity.profile, "E29 identity.profile");
|
||||
const metrics = record(source.metrics, "E29 metrics");
|
||||
const frames = record(metrics.frames, "E29 metrics.frames");
|
||||
const semantic = record(
|
||||
metrics.semantic_observations,
|
||||
"E29 metrics.semantic_observations",
|
||||
);
|
||||
const geometryStatus = record(
|
||||
semantic.geometry_status,
|
||||
"E29 metrics.semantic_observations.geometry_status",
|
||||
);
|
||||
const geometryOnly = record(
|
||||
metrics.geometry_only_occupied,
|
||||
"E29 metrics.geometry_only_occupied",
|
||||
);
|
||||
const runtime = record(metrics.runtime, "E29 metrics.runtime");
|
||||
const frameProcessing = record(
|
||||
runtime.frame_processing_ms,
|
||||
"E29 metrics.runtime.frame_processing_ms",
|
||||
);
|
||||
const decision = record(source.decision, "E29 decision");
|
||||
const authority = record(source.authority, "E29 authority");
|
||||
const linked = record(source.linked_evidence, "E29 linked evidence");
|
||||
const status = stringValue(source.status, "E29 status");
|
||||
if (status !== "diagnostic-replay-complete") {
|
||||
throw new E29EvidenceContractError("E29 status не завершён.");
|
||||
}
|
||||
if (!Array.isArray(source.limitations) || !Array.isArray(source.review_frames)) {
|
||||
throw new E29EvidenceContractError("E29 collections имеют неверный формат.");
|
||||
}
|
||||
return {
|
||||
resultId: stringValue(source.result_id, "E29 result_id"),
|
||||
createdAtUtc: source.created_at_utc === null
|
||||
? null
|
||||
: stringValue(source.created_at_utc, "E29 created_at_utc"),
|
||||
status,
|
||||
identity: {
|
||||
frameCount: integerValue(identity.frame_count, "E29 identity.frame_count"),
|
||||
timelineStartSeconds: numberValue(
|
||||
identity.timeline_start_seconds,
|
||||
"E29 identity.timeline_start_seconds",
|
||||
),
|
||||
timelineEndSeconds: numberValue(
|
||||
identity.timeline_end_seconds,
|
||||
"E29 identity.timeline_end_seconds",
|
||||
),
|
||||
profileId: stringValue(profile.profile_id, "E29 identity.profile.profile_id"),
|
||||
producerSha256: stringValue(
|
||||
identity.producer_sha256,
|
||||
"E29 identity.producer_sha256",
|
||||
),
|
||||
},
|
||||
metrics: {
|
||||
frames: {
|
||||
total: integerValue(frames.total, "E29 metrics.frames.total"),
|
||||
sourceAvailable: integerValue(
|
||||
frames.source_available,
|
||||
"E29 metrics.frames.source_available",
|
||||
),
|
||||
localSurfaceValid: integerValue(
|
||||
frames.local_surface_valid,
|
||||
"E29 metrics.frames.local_surface_valid",
|
||||
),
|
||||
},
|
||||
semanticObservations: {
|
||||
total: integerValue(
|
||||
semantic.total,
|
||||
"E29 metrics.semantic_observations.total",
|
||||
),
|
||||
current: integerValue(
|
||||
semantic.current,
|
||||
"E29 metrics.semantic_observations.current",
|
||||
),
|
||||
agreementFractionOfCurrent: numberValue(
|
||||
semantic.agreement_fraction_of_current,
|
||||
"E29 metrics.semantic_observations.agreement_fraction_of_current",
|
||||
),
|
||||
geometryStatus: {
|
||||
agree: integerValue(geometryStatus.agree, "E29 geometry_status.agree"),
|
||||
cameraOnly: integerValue(
|
||||
geometryStatus["single-source-camera"],
|
||||
"E29 geometry_status.single-source-camera",
|
||||
),
|
||||
conflict: integerValue(
|
||||
geometryStatus.conflict,
|
||||
"E29 geometry_status.conflict",
|
||||
),
|
||||
unknown: integerValue(
|
||||
geometryStatus.unknown,
|
||||
"E29 geometry_status.unknown",
|
||||
),
|
||||
},
|
||||
},
|
||||
geometryOnlyOccupied: {
|
||||
clusterCount: integerValue(
|
||||
geometryOnly.cluster_count,
|
||||
"E29 geometry_only_occupied.cluster_count",
|
||||
),
|
||||
pointCount: integerValue(
|
||||
geometryOnly.point_count,
|
||||
"E29 geometry_only_occupied.point_count",
|
||||
),
|
||||
},
|
||||
runtime: {
|
||||
buildElapsedMs: numberValue(
|
||||
runtime.build_elapsed_ms,
|
||||
"E29 runtime.build_elapsed_ms",
|
||||
),
|
||||
frameProcessingP95Ms: numberValue(
|
||||
frameProcessing.p95,
|
||||
"E29 runtime.frame_processing_ms.p95",
|
||||
),
|
||||
},
|
||||
},
|
||||
decision: {
|
||||
cameraFirstContractImplemented: booleanValue(
|
||||
decision.camera_first_contract_implemented,
|
||||
"E29 decision.camera_first_contract_implemented",
|
||||
),
|
||||
parallelGeometryOnlyLayerImplemented: booleanValue(
|
||||
decision.parallel_geometry_only_layer_implemented,
|
||||
"E29 decision.parallel_geometry_only_layer_implemented",
|
||||
),
|
||||
productionPromotion: booleanValue(
|
||||
decision.production_promotion,
|
||||
"E29 decision.production_promotion",
|
||||
),
|
||||
nextGate: stringValue(decision.next_gate, "E29 decision.next_gate"),
|
||||
},
|
||||
limitations: source.limitations.map((item, index) => (
|
||||
stringValue(item, `E29 limitations[${index}]`)
|
||||
)),
|
||||
authority: {
|
||||
commandsEnabled: falseValue(
|
||||
authority.commands_enabled,
|
||||
"E29 authority.commands_enabled",
|
||||
),
|
||||
navigationOrSafetyAccepted: falseValue(
|
||||
authority.navigation_or_safety_accepted,
|
||||
"E29 authority.navigation_or_safety_accepted",
|
||||
),
|
||||
},
|
||||
reviewFrames: source.review_frames.map(parseReviewFrame),
|
||||
linkedEvidence: {
|
||||
sourceSessionId: stringValue(
|
||||
linked.source_session_id,
|
||||
"E29 linked_evidence.source_session_id",
|
||||
),
|
||||
localSurfaceModelId: stringValue(
|
||||
linked.local_surface_model_id,
|
||||
"E29 linked_evidence.local_surface_model_id",
|
||||
),
|
||||
sourcePackId: stringValue(
|
||||
linked.source_pack_id,
|
||||
"E29 linked_evidence.source_pack_id",
|
||||
),
|
||||
sourceResultId: stringValue(
|
||||
linked.source_result_id,
|
||||
"E29 linked_evidence.source_result_id",
|
||||
),
|
||||
publishedOnControlPlane: trueValue(
|
||||
linked.published_on_control_plane,
|
||||
"E29 linked_evidence.published_on_control_plane",
|
||||
),
|
||||
workerCopyRequired: falseValue(
|
||||
linked.worker_copy_required,
|
||||
"E29 linked_evidence.worker_copy_required",
|
||||
),
|
||||
},
|
||||
groundTruth: falseValue(source.ground_truth, "E29 ground_truth"),
|
||||
};
|
||||
}
|
||||
|
||||
export function parseE29EvidenceCatalog(value: unknown): E29EvidenceCatalog {
|
||||
const source = record(value, "E29 catalog");
|
||||
if (source.schema_version !== "missioncore.laboratory-e29-catalog/v1") {
|
||||
throw new E29EvidenceContractError("E29 catalog schema не поддерживается.");
|
||||
}
|
||||
if (!Array.isArray(source.items)) {
|
||||
throw new E29EvidenceContractError("E29 catalog.items имеет неверный формат.");
|
||||
}
|
||||
return {
|
||||
configured: booleanValue(source.configured, "E29 catalog.configured"),
|
||||
candidateTotal: integerValue(
|
||||
source.candidate_total,
|
||||
"E29 catalog.candidate_total",
|
||||
),
|
||||
invalidTotal: integerValue(
|
||||
source.invalid_total,
|
||||
"E29 catalog.invalid_total",
|
||||
),
|
||||
items: source.items.map(parseEvidenceResult),
|
||||
};
|
||||
}
|
||||
|
||||
function parseSemanticObservation(value: unknown): E29SemanticObservation {
|
||||
const source = record(value, "E29 semantic observation");
|
||||
const support = record(source.support, "E29 semantic observation.support");
|
||||
return {
|
||||
trackId: integerValue(source.track_id, "E29 semantic observation.track_id"),
|
||||
label: stringValue(source.label, "E29 semantic observation.label"),
|
||||
geometryStatus: stringValue(
|
||||
source.geometry_status,
|
||||
"E29 semantic observation.geometry_status",
|
||||
),
|
||||
geometryReason: stringValue(
|
||||
source.geometry_reason,
|
||||
"E29 semantic observation.geometry_reason",
|
||||
),
|
||||
rangeM: nullableNumber(source.range_m, "E29 semantic observation.range_m"),
|
||||
score: numberValue(source.score, "E29 semantic observation.score"),
|
||||
support: {
|
||||
classifiedPoints: integerValue(
|
||||
support.classified_points_in_bbox,
|
||||
"E29 support.classified_points_in_bbox",
|
||||
),
|
||||
occupiedPoints: integerValue(
|
||||
support.occupied_points_in_bbox,
|
||||
"E29 support.occupied_points_in_bbox",
|
||||
),
|
||||
surfacePoints: integerValue(
|
||||
support.surface_points_in_bbox,
|
||||
"E29 support.surface_points_in_bbox",
|
||||
),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function parseGeometryOnly(value: unknown): E29GeometryOnlyCluster {
|
||||
const source = record(value, "E29 geometry-only cluster");
|
||||
return {
|
||||
nearestRangeM: numberValue(
|
||||
source.nearest_range_m,
|
||||
"E29 geometry-only.nearest_range_m",
|
||||
),
|
||||
pointCount: integerValue(
|
||||
source.point_count,
|
||||
"E29 geometry-only.point_count",
|
||||
),
|
||||
voxelCount: integerValue(
|
||||
source.voxel_count,
|
||||
"E29 geometry-only.voxel_count",
|
||||
),
|
||||
heightRangeM: tuple2(
|
||||
source.height_range_m,
|
||||
"E29 geometry-only.height_range_m",
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
export function parseE29EvidenceFrame(value: unknown): E29EvidenceFrame {
|
||||
const source = record(value, "E29 frame response");
|
||||
if (source.schema_version !== "missioncore.laboratory-e29-frame/v1") {
|
||||
throw new E29EvidenceContractError("E29 frame schema не поддерживается.");
|
||||
}
|
||||
const frame = record(source.frame, "E29 frame");
|
||||
if (
|
||||
frame.schema_version !== "missioncore.e29-camera-geometry-frame/v1"
|
||||
|| !Array.isArray(frame.semantic_observations)
|
||||
|| !Array.isArray(frame.geometry_only_occupied)
|
||||
) {
|
||||
throw new E29EvidenceContractError("E29 frame имеет неверный формат.");
|
||||
}
|
||||
return {
|
||||
resultId: stringValue(source.result_id, "E29 frame result_id"),
|
||||
frameIndex: integerValue(frame.frame_index, "E29 frame.frame_index"),
|
||||
sourceFrameIndex: integerValue(
|
||||
frame.source_frame_index,
|
||||
"E29 frame.source_frame_index",
|
||||
),
|
||||
sessionSeconds: numberValue(
|
||||
frame.session_seconds,
|
||||
"E29 frame.session_seconds",
|
||||
),
|
||||
semanticObservations: frame.semantic_observations.map(
|
||||
parseSemanticObservation,
|
||||
),
|
||||
geometryOnlyOccupied: frame.geometry_only_occupied.map(parseGeometryOnly),
|
||||
};
|
||||
}
|
||||
|
||||
async function responseJson(response: Response, fallback: string): Promise<unknown> {
|
||||
let payload: unknown = null;
|
||||
try {
|
||||
payload = await response.json();
|
||||
} catch {
|
||||
// Preserve the status-aware fallback below.
|
||||
}
|
||||
if (!response.ok) {
|
||||
const detail = payload && typeof payload === "object" && "detail" in payload
|
||||
? String((payload as { detail?: unknown }).detail)
|
||||
: fallback;
|
||||
throw new E29EvidenceContractError(detail);
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
|
||||
export async function fetchE29EvidenceCatalog(
|
||||
options: { signal?: AbortSignal; fetcher?: E29Fetch } = {},
|
||||
): Promise<E29EvidenceCatalog> {
|
||||
const fetcher = options.fetcher ?? fetch;
|
||||
const response = await fetcher("/api/v1/laboratory/e29/results?limit=1", {
|
||||
method: "GET",
|
||||
headers: { Accept: "application/json" },
|
||||
signal: options.signal,
|
||||
});
|
||||
return parseE29EvidenceCatalog(
|
||||
await responseJson(response, "Не удалось получить LAB E29."),
|
||||
);
|
||||
}
|
||||
|
||||
export async function fetchE29EvidenceFrame(
|
||||
resultId: string,
|
||||
frameIndex: number,
|
||||
options: { signal?: AbortSignal; fetcher?: E29Fetch } = {},
|
||||
): Promise<E29EvidenceFrame> {
|
||||
if (
|
||||
!/^e29-camera-geometry-[a-f0-9]{64}$/.test(resultId)
|
||||
|| !Number.isSafeInteger(frameIndex)
|
||||
|| frameIndex < 0
|
||||
) {
|
||||
throw new E29EvidenceContractError("Некорректный LAB E29 frame id.");
|
||||
}
|
||||
const fetcher = options.fetcher ?? fetch;
|
||||
const response = await fetcher(
|
||||
`/api/v1/laboratory/e29/results/${resultId}/frames/${frameIndex}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: { Accept: "application/json" },
|
||||
signal: options.signal,
|
||||
},
|
||||
);
|
||||
return parseE29EvidenceFrame(
|
||||
await responseJson(response, "Не удалось получить кадр LAB E29."),
|
||||
);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user