Compare commits
4
Commits
5e97fa293f
...
5a9738d6aa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a9738d6aa | ||
|
|
30227b661f | ||
|
|
4bcc35c1a0 | ||
|
|
a65ae9c285 |
@@ -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);
|
||||||
@@ -11,16 +11,21 @@ import {
|
|||||||
StandardMaterial,
|
StandardMaterial,
|
||||||
Vec2,
|
Vec2,
|
||||||
Vec3,
|
Vec3,
|
||||||
|
WasmModule,
|
||||||
type ContainerResource,
|
type ContainerResource,
|
||||||
type RenderComponent,
|
type RenderComponent,
|
||||||
type ScriptType,
|
type ScriptType,
|
||||||
} from "playcanvas";
|
} from "playcanvas";
|
||||||
import { CameraControls } from "playcanvas/scripts/esm/camera-controls.mjs";
|
import { CameraControls } from "playcanvas/scripts/esm/camera-controls.mjs";
|
||||||
|
|
||||||
import type { SimulationWorldManifest } from "../../core/simulation/projects";
|
import type {
|
||||||
|
SimulationTransformAxis,
|
||||||
|
SimulationViewerQuality,
|
||||||
|
SimulationWorldManifest,
|
||||||
|
} from "../../core/simulation/projects";
|
||||||
|
|
||||||
export type SimulationViewMode = "visual" | "collision" | "combined";
|
export type SimulationViewMode = "visual" | "collision" | "combined";
|
||||||
export type SimulationQuality = "low" | "medium" | "high" | "ultra" | "maximum";
|
export type SimulationQuality = SimulationViewerQuality;
|
||||||
export type SimulationLayer = "visual" | "collision";
|
export type SimulationLayer = "visual" | "collision";
|
||||||
export type SimulationCameraAxis = "horizontal" | "vertical";
|
export type SimulationCameraAxis = "horizontal" | "vertical";
|
||||||
|
|
||||||
@@ -38,6 +43,27 @@ export const PLAYCANVAS_X_180_TRANSFORM = [
|
|||||||
0, 0, 0, 1,
|
0, 0, 0, 1,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const PLAYCANVAS_Y_180_TRANSFORM = [
|
||||||
|
-1, 0, 0, 0,
|
||||||
|
0, 1, 0, 0,
|
||||||
|
0, 0, -1, 0,
|
||||||
|
0, 0, 0, 1,
|
||||||
|
];
|
||||||
|
|
||||||
|
export const PLAYCANVAS_Z_180_TRANSFORM = [
|
||||||
|
-1, 0, 0, 0,
|
||||||
|
0, -1, 0, 0,
|
||||||
|
0, 0, 1, 0,
|
||||||
|
0, 0, 0, 1,
|
||||||
|
];
|
||||||
|
|
||||||
|
export function playCanvasAxisTransform(axis: SimulationTransformAxis, inverted: boolean): number[] {
|
||||||
|
if (!inverted) return PLAYCANVAS_IDENTITY_TRANSFORM;
|
||||||
|
if (axis === "x") return PLAYCANVAS_X_180_TRANSFORM;
|
||||||
|
if (axis === "y") return PLAYCANVAS_Y_180_TRANSFORM;
|
||||||
|
return PLAYCANVAS_Z_180_TRANSFORM;
|
||||||
|
}
|
||||||
|
|
||||||
export interface SimulationRuntime {
|
export interface SimulationRuntime {
|
||||||
mount(canvas: HTMLCanvasElement): Promise<void>;
|
mount(canvas: HTMLCanvasElement): Promise<void>;
|
||||||
loadWorld(manifest: SimulationWorldManifest): Promise<void>;
|
loadWorld(manifest: SimulationWorldManifest): Promise<void>;
|
||||||
@@ -60,6 +86,7 @@ type CameraController = ScriptType & Pick<CameraControls, "reset" | "focus"> & {
|
|||||||
|
|
||||||
const HOME_POSITION = new Vec3(0, 1, 0);
|
const HOME_POSITION = new Vec3(0, 1, 0);
|
||||||
const HOME_FOCUS = new Vec3(1, 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, {
|
const QUALITY_PROFILES: Record<SimulationQuality, {
|
||||||
lodBaseDistance: number;
|
lodBaseDistance: number;
|
||||||
@@ -109,6 +136,7 @@ export class PlayCanvasRuntime implements SimulationRuntime {
|
|||||||
this.disposed = false;
|
this.disposed = false;
|
||||||
this.renderAccumulatorMs = 0;
|
this.renderAccumulatorMs = 0;
|
||||||
this.canvas = canvas;
|
this.canvas = canvas;
|
||||||
|
configureDracoDecoder();
|
||||||
canvas.tabIndex = 0;
|
canvas.tabIndex = 0;
|
||||||
canvas.addEventListener("contextmenu", preventContextMenu);
|
canvas.addEventListener("contextmenu", preventContextMenu);
|
||||||
canvas.addEventListener("pointerdown", focusCanvas, true);
|
canvas.addEventListener("pointerdown", focusCanvas, true);
|
||||||
@@ -217,10 +245,14 @@ export class PlayCanvasRuntime implements SimulationRuntime {
|
|||||||
async setViewMode(mode: SimulationViewMode): Promise<void> {
|
async setViewMode(mode: SimulationViewMode): Promise<void> {
|
||||||
this.viewMode = mode;
|
this.viewMode = mode;
|
||||||
this.applyViewMode();
|
this.applyViewMode();
|
||||||
|
try {
|
||||||
if (mode !== "visual" && !this.collisionEntity) {
|
if (mode !== "visual" && !this.collisionEntity) {
|
||||||
await this.ensureCollision();
|
await this.ensureCollision();
|
||||||
this.applyViewMode();
|
this.applyViewMode();
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
this.canvas?.focus({ preventScroll: true });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setQuality(quality: SimulationQuality): void {
|
setQuality(quality: SimulationQuality): void {
|
||||||
@@ -444,6 +476,15 @@ function preventContextMenu(event: Event): void {
|
|||||||
event.preventDefault();
|
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 {
|
function focusCanvas(event: Event): void {
|
||||||
(event.currentTarget as HTMLCanvasElement | null)?.focus({ preventScroll: true });
|
(event.currentTarget as HTMLCanvasElement | null)?.focus({ preventScroll: true });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,40 +10,101 @@ import {
|
|||||||
Switch,
|
Switch,
|
||||||
} from "@nodedc/ui-react";
|
} from "@nodedc/ui-react";
|
||||||
|
|
||||||
import type { SimulationProject } from "../../core/simulation/projects";
|
|
||||||
import {
|
import {
|
||||||
PLAYCANVAS_IDENTITY_TRANSFORM,
|
saveSimulationViewerSettings,
|
||||||
PLAYCANVAS_X_180_TRANSFORM,
|
type SimulationProject,
|
||||||
|
type SimulationTransformAxis,
|
||||||
|
type SimulationViewerSettings,
|
||||||
|
} from "../../core/simulation/projects";
|
||||||
|
import {
|
||||||
|
createLatestAsyncCommitter,
|
||||||
|
type LatestAsyncCommitter,
|
||||||
|
} from "../../core/runtime/latestAsyncCommitter";
|
||||||
|
import {
|
||||||
PlayCanvasRuntime,
|
PlayCanvasRuntime,
|
||||||
|
playCanvasAxisTransform,
|
||||||
type SimulationQuality,
|
type SimulationQuality,
|
||||||
type SimulationViewMode,
|
type SimulationViewMode,
|
||||||
} from "./PlayCanvasRuntime";
|
} from "./PlayCanvasRuntime";
|
||||||
|
|
||||||
export function SimulationViewport({ project }: { project: SimulationProject }) {
|
const AXIS_OPTIONS: Array<{ value: SimulationTransformAxis; label: string }> = [
|
||||||
|
{ value: "x", label: "X" },
|
||||||
|
{ value: "y", label: "Y" },
|
||||||
|
{ value: "z", label: "Z" },
|
||||||
|
];
|
||||||
|
|
||||||
|
export function SimulationViewport({
|
||||||
|
project,
|
||||||
|
onProjectChange,
|
||||||
|
}: {
|
||||||
|
project: SimulationProject;
|
||||||
|
onProjectChange?: (project: SimulationProject) => void;
|
||||||
|
}) {
|
||||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||||
const runtimeRef = useRef<PlayCanvasRuntime | null>(null);
|
const runtimeRef = useRef<PlayCanvasRuntime | null>(null);
|
||||||
const cameraInversionRef = useRef({ horizontal: true, vertical: false });
|
const viewerSettingsRef = useRef(project.viewerSettings);
|
||||||
|
const settingsCommitterRef = useRef<LatestAsyncCommitter<SimulationViewerSettings> | null>(null);
|
||||||
const settingsId = useId();
|
const settingsId = useId();
|
||||||
const [state, setState] = useState<"mounting" | "loading" | "ready" | "failed">("mounting");
|
const [state, setState] = useState<"mounting" | "loading" | "ready" | "failed">("mounting");
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [viewMode, setViewMode] = useState<SimulationViewMode>("visual");
|
const [viewMode, setViewMode] = useState<SimulationViewMode>("visual");
|
||||||
const [quality, setQuality] = useState<SimulationQuality>("high");
|
const [quality, setQuality] = useState<SimulationQuality>(project.viewerSettings.quality);
|
||||||
const [collisionState, setCollisionState] = useState<"idle" | "loading" | "ready" | "failed">("idle");
|
const [collisionState, setCollisionState] = useState<"idle" | "loading" | "ready" | "failed">("idle");
|
||||||
const [settingsOpen, setSettingsOpen] = useState(false);
|
const [settingsOpen, setSettingsOpen] = useState(false);
|
||||||
const [visualInverted, setVisualInverted] = useState(
|
const [visualInverted, setVisualInverted] = useState(project.viewerSettings.visual.inverted);
|
||||||
() => isX180Transform(project.worldManifest?.transforms.worldFromVisual),
|
const [visualAxis, setVisualAxis] = useState(project.viewerSettings.visual.axis);
|
||||||
|
const [collisionInverted, setCollisionInverted] = useState(project.viewerSettings.collision.inverted);
|
||||||
|
const [collisionAxis, setCollisionAxis] = useState(project.viewerSettings.collision.axis);
|
||||||
|
const [horizontalInverted, setHorizontalInverted] = useState(
|
||||||
|
project.viewerSettings.camera.invertHorizontal,
|
||||||
);
|
);
|
||||||
const [collisionInverted, setCollisionInverted] = useState(
|
const [verticalInverted, setVerticalInverted] = useState(
|
||||||
() => isX180Transform(project.worldManifest?.transforms.worldFromCollision),
|
project.viewerSettings.camera.invertVertical,
|
||||||
);
|
);
|
||||||
const [horizontalInverted, setHorizontalInverted] = useState(true);
|
const [settingsError, setSettingsError] = useState<string | null>(null);
|
||||||
const [verticalInverted, setVerticalInverted] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setVisualInverted(isX180Transform(project.worldManifest?.transforms.worldFromVisual));
|
const settings = project.viewerSettings;
|
||||||
setCollisionInverted(isX180Transform(project.worldManifest?.transforms.worldFromCollision));
|
viewerSettingsRef.current = settings;
|
||||||
|
setQuality(settings.quality);
|
||||||
|
setVisualInverted(settings.visual.inverted);
|
||||||
|
setVisualAxis(settings.visual.axis);
|
||||||
|
setCollisionInverted(settings.collision.inverted);
|
||||||
|
setCollisionAxis(settings.collision.axis);
|
||||||
|
setHorizontalInverted(settings.camera.invertHorizontal);
|
||||||
|
setVerticalInverted(settings.camera.invertVertical);
|
||||||
|
setSettingsError(null);
|
||||||
setSettingsOpen(false);
|
setSettingsOpen(false);
|
||||||
}, [project.projectId, project.worldManifest]);
|
}, [project.projectId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let active = true;
|
||||||
|
const committer = createLatestAsyncCommitter<SimulationViewerSettings>({
|
||||||
|
async commit(settings) {
|
||||||
|
try {
|
||||||
|
const saved = await saveSimulationViewerSettings(project.projectId, settings);
|
||||||
|
if (active) onProjectChange?.(saved);
|
||||||
|
return true;
|
||||||
|
} catch (caught) {
|
||||||
|
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(() => {
|
useEffect(() => {
|
||||||
const canvas = canvasRef.current;
|
const canvas = canvasRef.current;
|
||||||
@@ -56,10 +117,20 @@ export function SimulationViewport({ project }: { project: SimulationProject })
|
|||||||
setError(null);
|
setError(null);
|
||||||
void runtime.mount(canvas).then(async () => {
|
void runtime.mount(canvas).then(async () => {
|
||||||
if (cancelled) return;
|
if (cancelled) return;
|
||||||
runtime.setCameraInversion("horizontal", cameraInversionRef.current.horizontal);
|
const settings = viewerSettingsRef.current;
|
||||||
runtime.setCameraInversion("vertical", cameraInversionRef.current.vertical);
|
runtime.setQuality(settings.quality);
|
||||||
|
runtime.setCameraInversion("horizontal", settings.camera.invertHorizontal);
|
||||||
|
runtime.setCameraInversion("vertical", settings.camera.invertVertical);
|
||||||
setState("loading");
|
setState("loading");
|
||||||
await runtime.loadWorld(manifest);
|
await runtime.loadWorld(manifest);
|
||||||
|
runtime.setLayerWorldTransform(
|
||||||
|
"visual",
|
||||||
|
playCanvasAxisTransform(settings.visual.axis, settings.visual.inverted),
|
||||||
|
);
|
||||||
|
runtime.setLayerWorldTransform(
|
||||||
|
"collision",
|
||||||
|
playCanvasAxisTransform(settings.collision.axis, settings.collision.inverted),
|
||||||
|
);
|
||||||
if (!cancelled) setState("ready");
|
if (!cancelled) setState("ready");
|
||||||
}).catch((caught: unknown) => {
|
}).catch((caught: unknown) => {
|
||||||
if (cancelled) return;
|
if (cancelled) return;
|
||||||
@@ -74,6 +145,10 @@ export function SimulationViewport({ project }: { project: SimulationProject })
|
|||||||
}, [project.projectId, project.worldManifest]);
|
}, [project.projectId, project.worldManifest]);
|
||||||
|
|
||||||
const collisionAvailable = project.worldManifest?.collision.available ?? false;
|
const collisionAvailable = project.worldManifest?.collision.available ?? false;
|
||||||
|
const commitViewerSettings = (settings: SimulationViewerSettings) => {
|
||||||
|
viewerSettingsRef.current = settings;
|
||||||
|
settingsCommitterRef.current?.enqueue(settings);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="simulation-viewport" aria-label={`Сцена ${project.name}`}>
|
<section className="simulation-viewport" aria-label={`Сцена ${project.name}`}>
|
||||||
@@ -99,6 +174,7 @@ export function SimulationViewport({ project }: { project: SimulationProject })
|
|||||||
onChange={(next) => {
|
onChange={(next) => {
|
||||||
setQuality(next);
|
setQuality(next);
|
||||||
runtimeRef.current?.setQuality(next);
|
runtimeRef.current?.setQuality(next);
|
||||||
|
commitViewerSettings({ ...viewerSettingsRef.current, quality: next });
|
||||||
}}
|
}}
|
||||||
options={[
|
options={[
|
||||||
{ value: "maximum", label: "Максимум", description: "До 4 млн сплатов · Retina до 2× · 60 FPS" },
|
{ value: "maximum", label: "Максимум", description: "До 4 млн сплатов · Retina до 2× · 60 FPS" },
|
||||||
@@ -171,6 +247,7 @@ export function SimulationViewport({ project }: { project: SimulationProject })
|
|||||||
Коррекция слоёв не меняет координаты камеры, навигации и будущей физики.
|
Коррекция слоёв не меняет координаты камеры, навигации и будущей физики.
|
||||||
</p>
|
</p>
|
||||||
<div className="simulation-viewport__settings-switches">
|
<div className="simulation-viewport__settings-switches">
|
||||||
|
<div className="simulation-viewport__settings-layer-row">
|
||||||
<Switch
|
<Switch
|
||||||
checked={visualInverted}
|
checked={visualInverted}
|
||||||
disabled={state !== "ready"}
|
disabled={state !== "ready"}
|
||||||
@@ -179,10 +256,36 @@ export function SimulationViewport({ project }: { project: SimulationProject })
|
|||||||
setVisualInverted(checked);
|
setVisualInverted(checked);
|
||||||
runtimeRef.current?.setLayerWorldTransform(
|
runtimeRef.current?.setLayerWorldTransform(
|
||||||
"visual",
|
"visual",
|
||||||
checked ? PLAYCANVAS_X_180_TRANSFORM : PLAYCANVAS_IDENTITY_TRANSFORM,
|
playCanvasAxisTransform(visualAxis, checked),
|
||||||
);
|
);
|
||||||
|
commitViewerSettings({
|
||||||
|
...viewerSettingsRef.current,
|
||||||
|
visual: { ...viewerSettingsRef.current.visual, inverted: checked },
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<Select
|
||||||
|
className="simulation-viewport__axis-select"
|
||||||
|
label="Ось инверсии визуального слоя"
|
||||||
|
value={visualAxis}
|
||||||
|
options={AXIS_OPTIONS}
|
||||||
|
disabled={state !== "ready"}
|
||||||
|
minMenuWidth={72}
|
||||||
|
menuWidth={72}
|
||||||
|
onChange={(axis) => {
|
||||||
|
setVisualAxis(axis);
|
||||||
|
runtimeRef.current?.setLayerWorldTransform(
|
||||||
|
"visual",
|
||||||
|
playCanvasAxisTransform(axis, visualInverted),
|
||||||
|
);
|
||||||
|
commitViewerSettings({
|
||||||
|
...viewerSettingsRef.current,
|
||||||
|
visual: { ...viewerSettingsRef.current.visual, axis },
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="simulation-viewport__settings-layer-row">
|
||||||
<Switch
|
<Switch
|
||||||
checked={collisionInverted}
|
checked={collisionInverted}
|
||||||
disabled={state !== "ready"}
|
disabled={state !== "ready"}
|
||||||
@@ -191,10 +294,35 @@ export function SimulationViewport({ project }: { project: SimulationProject })
|
|||||||
setCollisionInverted(checked);
|
setCollisionInverted(checked);
|
||||||
runtimeRef.current?.setLayerWorldTransform(
|
runtimeRef.current?.setLayerWorldTransform(
|
||||||
"collision",
|
"collision",
|
||||||
checked ? PLAYCANVAS_X_180_TRANSFORM : PLAYCANVAS_IDENTITY_TRANSFORM,
|
playCanvasAxisTransform(collisionAxis, checked),
|
||||||
);
|
);
|
||||||
|
commitViewerSettings({
|
||||||
|
...viewerSettingsRef.current,
|
||||||
|
collision: { ...viewerSettingsRef.current.collision, inverted: checked },
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<Select
|
||||||
|
className="simulation-viewport__axis-select"
|
||||||
|
label="Ось инверсии collision-слоя"
|
||||||
|
value={collisionAxis}
|
||||||
|
options={AXIS_OPTIONS}
|
||||||
|
disabled={state !== "ready"}
|
||||||
|
minMenuWidth={72}
|
||||||
|
menuWidth={72}
|
||||||
|
onChange={(axis) => {
|
||||||
|
setCollisionAxis(axis);
|
||||||
|
runtimeRef.current?.setLayerWorldTransform(
|
||||||
|
"collision",
|
||||||
|
playCanvasAxisTransform(axis, collisionInverted),
|
||||||
|
);
|
||||||
|
commitViewerSettings({
|
||||||
|
...viewerSettingsRef.current,
|
||||||
|
collision: { ...viewerSettingsRef.current.collision, axis },
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section className="simulation-viewport__settings-group">
|
<section className="simulation-viewport__settings-group">
|
||||||
@@ -208,9 +336,12 @@ export function SimulationViewport({ project }: { project: SimulationProject })
|
|||||||
disabled={state !== "ready"}
|
disabled={state !== "ready"}
|
||||||
label="Инверсия управления по горизонтали"
|
label="Инверсия управления по горизонтали"
|
||||||
onChange={(checked) => {
|
onChange={(checked) => {
|
||||||
cameraInversionRef.current.horizontal = checked;
|
|
||||||
setHorizontalInverted(checked);
|
setHorizontalInverted(checked);
|
||||||
runtimeRef.current?.setCameraInversion("horizontal", checked);
|
runtimeRef.current?.setCameraInversion("horizontal", checked);
|
||||||
|
commitViewerSettings({
|
||||||
|
...viewerSettingsRef.current,
|
||||||
|
camera: { ...viewerSettingsRef.current.camera, invertHorizontal: checked },
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Switch
|
<Switch
|
||||||
@@ -218,13 +349,21 @@ export function SimulationViewport({ project }: { project: SimulationProject })
|
|||||||
disabled={state !== "ready"}
|
disabled={state !== "ready"}
|
||||||
label="Инверсия управления по вертикали"
|
label="Инверсия управления по вертикали"
|
||||||
onChange={(checked) => {
|
onChange={(checked) => {
|
||||||
cameraInversionRef.current.vertical = checked;
|
|
||||||
setVerticalInverted(checked);
|
setVerticalInverted(checked);
|
||||||
runtimeRef.current?.setCameraInversion("vertical", checked);
|
runtimeRef.current?.setCameraInversion("vertical", checked);
|
||||||
|
commitViewerSettings({
|
||||||
|
...viewerSettingsRef.current,
|
||||||
|
camera: { ...viewerSettingsRef.current.camera, invertVertical: checked },
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
{settingsError ? (
|
||||||
|
<p className="simulation-viewport__settings-error" role="alert">
|
||||||
|
Настройки не сохранены: {settingsError}
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
@@ -255,8 +394,3 @@ export function SimulationViewport({ project }: { project: SimulationProject })
|
|||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isX180Transform(transform: number[] | undefined): boolean {
|
|
||||||
if (!transform || transform.length !== 16) return false;
|
|
||||||
return transform.every((value, index) => value === PLAYCANVAS_X_180_TRANSFORM[index]);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -70,6 +70,14 @@ export interface M4ThreatAssessment {
|
|||||||
reasonCodes: readonly string[];
|
reasonCodes: readonly string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface M4ThreatCameraObstacleProjection {
|
||||||
|
bboxXyxy: readonly [number, number, number, number];
|
||||||
|
nearestDepthM: number;
|
||||||
|
projectedCellCount: number;
|
||||||
|
projection: "factory-kb4-occupied-voxel-bounds";
|
||||||
|
authority: "visual-derived";
|
||||||
|
}
|
||||||
|
|
||||||
export interface M4ThreatMetricVisual {
|
export interface M4ThreatMetricVisual {
|
||||||
componentId: string;
|
componentId: string;
|
||||||
state: "current" | "retained" | "held" | "expired";
|
state: "current" | "retained" | "held" | "expired";
|
||||||
@@ -78,6 +86,7 @@ export interface M4ThreatMetricVisual {
|
|||||||
cellCentersBodyXyzM: readonly M4Point3[];
|
cellCentersBodyXyzM: readonly M4Point3[];
|
||||||
assessment: M4ThreatAssessment;
|
assessment: M4ThreatAssessment;
|
||||||
occupancySource: M4OccupancySource;
|
occupancySource: M4OccupancySource;
|
||||||
|
cameraProjection: M4ThreatCameraObstacleProjection | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface M4ThreatCameraProposal {
|
export interface M4ThreatCameraProposal {
|
||||||
@@ -177,6 +186,7 @@ export interface M4ThreatTimeline {
|
|||||||
cameraPointSampleLimit: number;
|
cameraPointSampleLimit: number;
|
||||||
worldStateDelivery: "source-paced-latest-wins" | null;
|
worldStateDelivery: "source-paced-latest-wins" | null;
|
||||||
occupancyProvenanceDelivery: "baseline-versus-additive-component-diff" | null;
|
occupancyProvenanceDelivery: "baseline-versus-additive-component-diff" | null;
|
||||||
|
cameraObstacleProjectionDelivery: "factory-kb4-occupied-voxel-bounds" | null;
|
||||||
worldStateFrameCount: number;
|
worldStateFrameCount: number;
|
||||||
supersededFrameCount: number;
|
supersededFrameCount: number;
|
||||||
sourceRepresentationId: "registered-map-increment-v1";
|
sourceRepresentationId: "registered-map-increment-v1";
|
||||||
@@ -323,6 +333,32 @@ function parseCameraProposal(value: unknown): M4ThreatCameraProposal {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseCameraObstacleProjection(value: unknown): M4ThreatCameraObstacleProjection {
|
||||||
|
const item = object(value, "M4.8R3 camera obstacle projection");
|
||||||
|
const bbox = vector(item.bbox_xyxy, 4, "M4.8R3 camera obstacle bbox");
|
||||||
|
if (bbox[2]! < bbox[0]! || bbox[3]! < bbox[1]!) {
|
||||||
|
throw new M4ThreatContractError("M4.8R3 camera obstacle bbox: нарушена геометрия.");
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
bboxXyxy: [bbox[0]!, bbox[1]!, bbox[2]!, bbox[3]!],
|
||||||
|
nearestDepthM: number(item.nearest_depth_m, "M4.8R3 camera obstacle depth"),
|
||||||
|
projectedCellCount: integer(
|
||||||
|
item.projected_cell_count,
|
||||||
|
"M4.8R3 camera obstacle cells",
|
||||||
|
),
|
||||||
|
projection: exact(
|
||||||
|
item.projection,
|
||||||
|
"factory-kb4-occupied-voxel-bounds",
|
||||||
|
"M4.8R3 camera obstacle projection",
|
||||||
|
),
|
||||||
|
authority: exact(
|
||||||
|
item.authority,
|
||||||
|
"visual-derived",
|
||||||
|
"M4.8R3 camera obstacle authority",
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function parseMetricVisual(value: unknown): M4ThreatMetricVisual {
|
function parseMetricVisual(value: unknown): M4ThreatMetricVisual {
|
||||||
const item = object(value, "M4.6 metric visual");
|
const item = object(value, "M4.6 metric visual");
|
||||||
const state = text(item.state, "M4.6 temporal state");
|
const state = text(item.state, "M4.6 temporal state");
|
||||||
@@ -347,6 +383,9 @@ function parseMetricVisual(value: unknown): M4ThreatMetricVisual {
|
|||||||
),
|
),
|
||||||
assessment: parseAssessment(item.assessment),
|
assessment: parseAssessment(item.assessment),
|
||||||
occupancySource,
|
occupancySource,
|
||||||
|
cameraProjection: item.camera_projection === undefined
|
||||||
|
? null
|
||||||
|
: parseCameraObstacleProjection(item.camera_projection),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -704,6 +743,13 @@ export async function fetchM4ThreatTimeline(
|
|||||||
"baseline-versus-additive-component-diff",
|
"baseline-versus-additive-component-diff",
|
||||||
"M4.8R3 occupancy provenance",
|
"M4.8R3 occupancy provenance",
|
||||||
),
|
),
|
||||||
|
cameraObstacleProjectionDelivery: payload.camera_obstacle_projection_delivery == null
|
||||||
|
? null
|
||||||
|
: exact(
|
||||||
|
payload.camera_obstacle_projection_delivery,
|
||||||
|
"factory-kb4-occupied-voxel-bounds",
|
||||||
|
"M4.8R3 camera obstacle projection delivery",
|
||||||
|
),
|
||||||
worldStateFrameCount: payload.world_state_frame_count === undefined
|
worldStateFrameCount: payload.world_state_frame_count === undefined
|
||||||
? frameCount
|
? frameCount
|
||||||
: integer(payload.world_state_frame_count, "M4.6 world-state frames"),
|
: integer(payload.world_state_frame_count, "M4.6 world-state frames"),
|
||||||
@@ -756,16 +802,21 @@ export async function fetchM4ThreatTimelineChunk(
|
|||||||
fetcher = fetch,
|
fetcher = fetch,
|
||||||
signal,
|
signal,
|
||||||
endpointRoot = M4_THREAT_TIMELINE_ENDPOINT_ROOT,
|
endpointRoot = M4_THREAT_TIMELINE_ENDPOINT_ROOT,
|
||||||
|
cameraObstacleProjectionDelivery = null,
|
||||||
}: {
|
}: {
|
||||||
fetcher?: LaboratoryFetch;
|
fetcher?: LaboratoryFetch;
|
||||||
signal?: AbortSignal;
|
signal?: AbortSignal;
|
||||||
endpointRoot?: string;
|
endpointRoot?: string;
|
||||||
|
cameraObstacleProjectionDelivery?: M4ThreatTimeline["cameraObstacleProjectionDelivery"];
|
||||||
} = {},
|
} = {},
|
||||||
): Promise<M4ThreatTimelineChunk> {
|
): Promise<M4ThreatTimelineChunk> {
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
start: String(startSequence),
|
start: String(startSequence),
|
||||||
count: String(frameCount),
|
count: String(frameCount),
|
||||||
});
|
});
|
||||||
|
if (cameraObstacleProjectionDelivery !== null) {
|
||||||
|
params.set("obstacle_projection", cameraObstacleProjectionDelivery);
|
||||||
|
}
|
||||||
const response = await fetcher(
|
const response = await fetcher(
|
||||||
`${endpointRoot}/${result}/timeline/chunk?${params}`,
|
`${endpointRoot}/${result}/timeline/chunk?${params}`,
|
||||||
{ headers: { Accept: "application/json" }, signal },
|
{ headers: { Accept: "application/json" }, signal },
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
export type SimulationSceneType = "interior" | "outdoor" | "object";
|
export type SimulationSceneType = "interior" | "outdoor" | "object";
|
||||||
|
export type SimulationTransformAxis = "x" | "y" | "z";
|
||||||
|
export type SimulationViewerQuality = "low" | "medium" | "high" | "ultra" | "maximum";
|
||||||
export type SimulationProjectStatus =
|
export type SimulationProjectStatus =
|
||||||
| "uploading"
|
| "uploading"
|
||||||
| "queued"
|
| "queued"
|
||||||
@@ -32,6 +34,14 @@ export interface SimulationWorldManifest {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SimulationViewerSettings {
|
||||||
|
schemaVersion: "missioncore.simulation-viewer-settings/v1";
|
||||||
|
quality: SimulationViewerQuality;
|
||||||
|
visual: { inverted: boolean; axis: SimulationTransformAxis };
|
||||||
|
collision: { inverted: boolean; axis: SimulationTransformAxis };
|
||||||
|
camera: { invertHorizontal: boolean; invertVertical: boolean };
|
||||||
|
}
|
||||||
|
|
||||||
export interface SimulationProject {
|
export interface SimulationProject {
|
||||||
schemaVersion: "missioncore.simulation-project/v1";
|
schemaVersion: "missioncore.simulation-project/v1";
|
||||||
projectId: string;
|
projectId: string;
|
||||||
@@ -60,6 +70,7 @@ export interface SimulationProject {
|
|||||||
byteLength: number;
|
byteLength: number;
|
||||||
}>;
|
}>;
|
||||||
worldManifest: SimulationWorldManifest | null;
|
worldManifest: SimulationWorldManifest | null;
|
||||||
|
viewerSettings: SimulationViewerSettings;
|
||||||
error: string | null;
|
error: string | null;
|
||||||
createdAtUtc: string;
|
createdAtUtc: string;
|
||||||
updatedAtUtc: string;
|
updatedAtUtc: string;
|
||||||
@@ -223,6 +234,30 @@ export async function updateSimulationProject(
|
|||||||
return parseProject(await jsonResponse(response));
|
return parseProject(await jsonResponse(response));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function saveSimulationViewerSettings(
|
||||||
|
projectId: string,
|
||||||
|
settings: SimulationViewerSettings,
|
||||||
|
): Promise<SimulationProject> {
|
||||||
|
const response = await fetch(
|
||||||
|
`${API_ROOT}/${encodeURIComponent(projectId)}/viewer-settings`,
|
||||||
|
{
|
||||||
|
method: "PUT",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
schema_version: settings.schemaVersion,
|
||||||
|
quality: settings.quality,
|
||||||
|
visual: settings.visual,
|
||||||
|
collision: settings.collision,
|
||||||
|
camera: {
|
||||||
|
invert_horizontal: settings.camera.invertHorizontal,
|
||||||
|
invert_vertical: settings.camera.invertVertical,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return parseProject(await jsonResponse(response));
|
||||||
|
}
|
||||||
|
|
||||||
export async function retrySimulationProject(projectId: string): Promise<SimulationProject> {
|
export async function retrySimulationProject(projectId: string): Promise<SimulationProject> {
|
||||||
const response = await fetch(`${API_ROOT}/${encodeURIComponent(projectId)}/build`, {
|
const response = await fetch(`${API_ROOT}/${encodeURIComponent(projectId)}/build`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@@ -251,7 +286,7 @@ function parseProject(value: unknown): SimulationProject {
|
|||||||
const record = objectValue(value, "simulation project");
|
const record = objectValue(value, "simulation project");
|
||||||
exactKeys(record, [
|
exactKeys(record, [
|
||||||
"schema_version", "project_id", "name", "scene_type", "status", "source", "provider",
|
"schema_version", "project_id", "name", "scene_type", "status", "source", "provider",
|
||||||
"artifacts", "world_manifest", "error", "created_at_utc", "updated_at_utc",
|
"artifacts", "world_manifest", "viewer_settings", "error", "created_at_utc", "updated_at_utc",
|
||||||
], "simulation project");
|
], "simulation project");
|
||||||
if (record.schema_version !== "missioncore.simulation-project/v1") {
|
if (record.schema_version !== "missioncore.simulation-project/v1") {
|
||||||
throw new Error("Проект симуляции вернул неподдерживаемую версию.");
|
throw new Error("Проект симуляции вернул неподдерживаемую версию.");
|
||||||
@@ -301,12 +336,47 @@ function parseProject(value: unknown): SimulationProject {
|
|||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
worldManifest: record.world_manifest === null ? null : parseWorldManifest(record.world_manifest),
|
worldManifest: record.world_manifest === null ? null : parseWorldManifest(record.world_manifest),
|
||||||
|
viewerSettings: parseViewerSettings(record.viewer_settings),
|
||||||
error: nullableString(record.error, "project error"),
|
error: nullableString(record.error, "project error"),
|
||||||
createdAtUtc: stringValue(record.created_at_utc, "created at"),
|
createdAtUtc: stringValue(record.created_at_utc, "created at"),
|
||||||
updatedAtUtc: stringValue(record.updated_at_utc, "updated at"),
|
updatedAtUtc: stringValue(record.updated_at_utc, "updated at"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseViewerSettings(value: unknown): SimulationViewerSettings {
|
||||||
|
const record = objectValue(value, "viewer settings");
|
||||||
|
exactKeys(record, ["schema_version", "quality", "visual", "collision", "camera"], "viewer settings");
|
||||||
|
if (record.schema_version !== "missioncore.simulation-viewer-settings/v1") {
|
||||||
|
throw new Error("Настройки сцены вернули неподдерживаемую версию.");
|
||||||
|
}
|
||||||
|
const visual = parseLayerViewerSettings(record.visual, "visual settings");
|
||||||
|
const collision = parseLayerViewerSettings(record.collision, "collision settings");
|
||||||
|
const camera = objectValue(record.camera, "camera settings");
|
||||||
|
exactKeys(camera, ["invert_horizontal", "invert_vertical"], "camera settings");
|
||||||
|
return {
|
||||||
|
schemaVersion: "missioncore.simulation-viewer-settings/v1",
|
||||||
|
quality: qualityValue(record.quality),
|
||||||
|
visual,
|
||||||
|
collision,
|
||||||
|
camera: {
|
||||||
|
invertHorizontal: booleanValue(camera.invert_horizontal, "horizontal camera inversion"),
|
||||||
|
invertVertical: booleanValue(camera.invert_vertical, "vertical camera inversion"),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseLayerViewerSettings(
|
||||||
|
value: unknown,
|
||||||
|
label: string,
|
||||||
|
): { inverted: boolean; axis: SimulationTransformAxis } {
|
||||||
|
const record = objectValue(value, label);
|
||||||
|
exactKeys(record, ["inverted", "axis"], label);
|
||||||
|
return {
|
||||||
|
inverted: booleanValue(record.inverted, `${label} inversion`),
|
||||||
|
axis: axisValue(record.axis),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function parseWorldManifest(value: unknown): SimulationWorldManifest {
|
function parseWorldManifest(value: unknown): SimulationWorldManifest {
|
||||||
const record = objectValue(value, "world manifest");
|
const record = objectValue(value, "world manifest");
|
||||||
const visual = objectValue(record.visual, "visual manifest");
|
const visual = objectValue(record.visual, "visual manifest");
|
||||||
@@ -396,6 +466,18 @@ function sceneTypeValue(value: unknown): SimulationSceneType {
|
|||||||
return invalid("scene type");
|
return invalid("scene type");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function axisValue(value: unknown): SimulationTransformAxis {
|
||||||
|
if (value === "x" || value === "y" || value === "z") return value;
|
||||||
|
return invalid("transform axis");
|
||||||
|
}
|
||||||
|
|
||||||
|
function qualityValue(value: unknown): SimulationViewerQuality {
|
||||||
|
if (["low", "medium", "high", "ultra", "maximum"].includes(String(value))) {
|
||||||
|
return value as SimulationViewerQuality;
|
||||||
|
}
|
||||||
|
return invalid("viewer quality");
|
||||||
|
}
|
||||||
|
|
||||||
function statusValue(value: unknown): SimulationProjectStatus {
|
function statusValue(value: unknown): SimulationProjectStatus {
|
||||||
if (["uploading", "queued", "processing", "importing", "ready", "failed"].includes(String(value))) {
|
if (["uploading", "queued", "processing", "importing", "ready", "failed"].includes(String(value))) {
|
||||||
return value as SimulationProjectStatus;
|
return value as SimulationProjectStatus;
|
||||||
|
|||||||
@@ -440,6 +440,25 @@
|
|||||||
gap: 0.55rem;
|
gap: 0.55rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.simulation-viewport__settings-layer-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr) 4.5rem;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.55rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.simulation-viewport__axis-select {
|
||||||
|
width: 4.5rem;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.simulation-viewport__settings-error {
|
||||||
|
border-radius: 0.65rem;
|
||||||
|
background: rgb(var(--nodedc-danger-rgb) / 0.08);
|
||||||
|
color: var(--nodedc-danger) !important;
|
||||||
|
padding: 0.55rem 0.65rem;
|
||||||
|
}
|
||||||
|
|
||||||
.simulation-viewport__toolbar-balance {
|
.simulation-viewport__toolbar-balance {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ import {
|
|||||||
useM4ThreatTimelineMetadata,
|
useM4ThreatTimelineMetadata,
|
||||||
} from "./useM4ThreatTimeline";
|
} from "./useM4ThreatTimeline";
|
||||||
import { useE47SemanticTimelineFrame } from "./useE47SemanticTimeline";
|
import { useE47SemanticTimelineFrame } from "./useE47SemanticTimeline";
|
||||||
|
import { buildM4StaticObstacleBoxes } from "./m4StaticObstacleBoxes";
|
||||||
|
|
||||||
type M4ThreatMediaMode = "video" | "camera";
|
type M4ThreatMediaMode = "video" | "camera";
|
||||||
type M4ThreatMediaSelection = M4ThreatMediaMode | "none";
|
type M4ThreatMediaSelection = M4ThreatMediaMode | "none";
|
||||||
@@ -132,6 +133,7 @@ export function M4ReplayThreatVisual({
|
|||||||
const [showMediaSemantic, setShowMediaSemantic] = useState(true);
|
const [showMediaSemantic, setShowMediaSemantic] = useState(true);
|
||||||
const [showSpatialSemantic, setShowSpatialSemantic] = useState(true);
|
const [showSpatialSemantic, setShowSpatialSemantic] = useState(true);
|
||||||
const [showMediaPoints, setShowMediaPoints] = useState(false);
|
const [showMediaPoints, setShowMediaPoints] = useState(false);
|
||||||
|
const [showStaticObstacles, setShowStaticObstacles] = useState(true);
|
||||||
const [splitPrimarySize, setSplitPrimarySize] = useState(50);
|
const [splitPrimarySize, setSplitPrimarySize] = useState(50);
|
||||||
const [splitOrientation, setSplitOrientation] = useState<SplitPaneOrientation>(() => (
|
const [splitOrientation, setSplitOrientation] = useState<SplitPaneOrientation>(() => (
|
||||||
typeof window !== "undefined" && window.matchMedia("(max-width: 900px)").matches
|
typeof window !== "undefined" && window.matchMedia("(max-width: 900px)").matches
|
||||||
@@ -287,9 +289,26 @@ export function M4ReplayThreatVisual({
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
}, [frame, metadata.timeline, reviewAnchors, showReviewAnchorBoxes]);
|
}, [frame, metadata.timeline, reviewAnchors, showReviewAnchorBoxes]);
|
||||||
|
const staticObstacleBoxes = useMemo<readonly RecordedEvidenceBox[]>(() => {
|
||||||
|
const timeline = metadata.timeline;
|
||||||
|
if (
|
||||||
|
!frame
|
||||||
|
|| !timeline?.cameraObstacleProjectionDelivery
|
||||||
|
|| !showStaticObstacles
|
||||||
|
) return [];
|
||||||
|
return buildM4StaticObstacleBoxes(
|
||||||
|
frame.metricObstacles,
|
||||||
|
timeline.imageWidth,
|
||||||
|
timeline.imageHeight,
|
||||||
|
);
|
||||||
|
}, [frame, metadata.timeline, showStaticObstacles]);
|
||||||
const activeBoxes = useMemo(
|
const activeBoxes = useMemo(
|
||||||
() => [...boxes(frame?.cameraProposals ?? []), ...reviewAnchorBoxes],
|
() => [
|
||||||
[frame, reviewAnchorBoxes],
|
...boxes(frame?.cameraProposals ?? []),
|
||||||
|
...staticObstacleBoxes,
|
||||||
|
...reviewAnchorBoxes,
|
||||||
|
],
|
||||||
|
[frame, reviewAnchorBoxes, staticObstacleBoxes],
|
||||||
);
|
);
|
||||||
const semanticClasses = useMemo<readonly RecordedEvidenceSemanticClass[]>(
|
const semanticClasses = useMemo<readonly RecordedEvidenceSemanticClass[]>(
|
||||||
() => semantic?.taxonomy.map((item) => ({
|
() => semantic?.taxonomy.map((item) => ({
|
||||||
@@ -359,7 +378,8 @@ export function M4ReplayThreatVisual({
|
|||||||
(item) => item.state === "retained",
|
(item) => item.state === "retained",
|
||||||
) ?? [];
|
) ?? [];
|
||||||
const lowStepObstacles = spatialFrame?.metricObstacles.filter(
|
const lowStepObstacles = spatialFrame?.metricObstacles.filter(
|
||||||
(item) => item.occupancySource !== "baseline",
|
(item) => item.occupancySource !== "baseline"
|
||||||
|
&& (item.state === "current" || item.state === "retained"),
|
||||||
) ?? [];
|
) ?? [];
|
||||||
const nearest = spatialFrame?.metricObstacles
|
const nearest = spatialFrame?.metricObstacles
|
||||||
.map((item) => item.assessment.closestApproachM)
|
.map((item) => item.assessment.closestApproachM)
|
||||||
@@ -456,7 +476,9 @@ export function M4ReplayThreatVisual({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
const mediaLayerControls = semantic || metadata.timeline?.cameraPointDelivery ? (
|
const mediaLayerControls = semantic
|
||||||
|
|| metadata.timeline?.cameraPointDelivery
|
||||||
|
|| metadata.timeline?.cameraObstacleProjectionDelivery ? (
|
||||||
<div
|
<div
|
||||||
className="m4-replay-threat-visual__pane-layer-controls"
|
className="m4-replay-threat-visual__pane-layer-controls"
|
||||||
role="group"
|
role="group"
|
||||||
@@ -485,6 +507,18 @@ export function M4ReplayThreatVisual({
|
|||||||
POINTS
|
POINTS
|
||||||
</Button>
|
</Button>
|
||||||
) : null}
|
) : null}
|
||||||
|
{metadata.timeline?.cameraObstacleProjectionDelivery ? (
|
||||||
|
<Button
|
||||||
|
size="compact"
|
||||||
|
shape="pill"
|
||||||
|
variant={showStaticObstacles ? "primary" : "secondary"}
|
||||||
|
aria-pressed={showStaticObstacles}
|
||||||
|
title="Автоматические рамки занятых LiDAR-компонентов · без ручной разметки"
|
||||||
|
onClick={() => setShowStaticObstacles((visible) => !visible)}
|
||||||
|
>
|
||||||
|
OBSTACLES
|
||||||
|
</Button>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
) : null;
|
) : null;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
import type { RecordedEvidenceBox } from "../../components/laboratory/RecordedEvidenceVideoScene";
|
||||||
|
import type { M4ThreatMetricVisual } from "../../core/laboratory/m4ReplayThreat";
|
||||||
|
|
||||||
|
const MINIMUM_BOX_SIZE_PX = 12;
|
||||||
|
|
||||||
|
function shortComponentId(componentId: string): string {
|
||||||
|
const finalSegment = componentId.split(/[-_]/).pop();
|
||||||
|
return finalSegment && /^\d+$/.test(finalSegment)
|
||||||
|
? finalSegment
|
||||||
|
: componentId.slice(-6).toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
function clamp(value: number, minimum: number, maximum: number): number {
|
||||||
|
return Math.min(maximum, Math.max(minimum, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
function visibleBox(
|
||||||
|
box: readonly [number, number, number, number],
|
||||||
|
imageWidth: number,
|
||||||
|
imageHeight: number,
|
||||||
|
): readonly [number, number, number, number] | null {
|
||||||
|
const [sourceLeft, sourceTop, sourceRight, sourceBottom] = box;
|
||||||
|
if (
|
||||||
|
sourceRight < 0
|
||||||
|
|| sourceBottom < 0
|
||||||
|
|| sourceLeft >= imageWidth
|
||||||
|
|| sourceTop >= imageHeight
|
||||||
|
) return null;
|
||||||
|
const centerX = (sourceLeft + sourceRight) / 2;
|
||||||
|
const centerY = (sourceTop + sourceBottom) / 2;
|
||||||
|
const halfWidth = Math.max((sourceRight - sourceLeft) / 2, MINIMUM_BOX_SIZE_PX / 2);
|
||||||
|
const halfHeight = Math.max((sourceBottom - sourceTop) / 2, MINIMUM_BOX_SIZE_PX / 2);
|
||||||
|
const left = clamp(centerX - halfWidth, 0, imageWidth - 1);
|
||||||
|
const top = clamp(centerY - halfHeight, 0, imageHeight - 1);
|
||||||
|
const right = clamp(centerX + halfWidth, left + 1, imageWidth);
|
||||||
|
const bottom = clamp(centerY + halfHeight, top + 1, imageHeight);
|
||||||
|
return [left, top, right, bottom];
|
||||||
|
}
|
||||||
|
|
||||||
|
function tone(obstacle: M4ThreatMetricVisual): RecordedEvidenceBox["tone"] {
|
||||||
|
if (obstacle.assessment.decision === "threat") return "danger";
|
||||||
|
if (obstacle.assessment.decision === "not-threat") return "success";
|
||||||
|
return "warning";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build camera evidence from the same world-state components shown in 3D.
|
||||||
|
* No image detector or manual review extent participates in these boxes.
|
||||||
|
*/
|
||||||
|
export function buildM4StaticObstacleBoxes(
|
||||||
|
obstacles: readonly M4ThreatMetricVisual[],
|
||||||
|
imageWidth: number,
|
||||||
|
imageHeight: number,
|
||||||
|
): readonly RecordedEvidenceBox[] {
|
||||||
|
const result: RecordedEvidenceBox[] = [];
|
||||||
|
for (const obstacle of obstacles) {
|
||||||
|
if (
|
||||||
|
obstacle.occupancySource === "baseline"
|
||||||
|
|| (obstacle.state !== "current" && obstacle.state !== "retained")
|
||||||
|
|| obstacle.cameraProjection === null
|
||||||
|
) continue;
|
||||||
|
const projection = obstacle.cameraProjection;
|
||||||
|
const boxXyxy = visibleBox(projection.bboxXyxy, imageWidth, imageHeight);
|
||||||
|
if (!boxXyxy) continue;
|
||||||
|
const depth = projection.nearestDepthM.toLocaleString("ru-RU", {
|
||||||
|
maximumFractionDigits: 1,
|
||||||
|
});
|
||||||
|
result.push({
|
||||||
|
boxXyxy,
|
||||||
|
label: `OBS #${shortComponentId(obstacle.componentId)} · ${depth} м`,
|
||||||
|
tone: tone(obstacle),
|
||||||
|
dashed: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result.sort((left, right) => {
|
||||||
|
const leftArea = (left.boxXyxy[2] - left.boxXyxy[0]) * (left.boxXyxy[3] - left.boxXyxy[1]);
|
||||||
|
const rightArea = (right.boxXyxy[2] - right.boxXyxy[0]) * (right.boxXyxy[3] - right.boxXyxy[1]);
|
||||||
|
return rightArea - leftArea;
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -130,6 +130,7 @@ export function useM4ThreatTimelineFrame({
|
|||||||
void fetchM4ThreatTimelineChunk(resultId, start, chunkSize, {
|
void fetchM4ThreatTimelineChunk(resultId, start, chunkSize, {
|
||||||
signal: controller.signal,
|
signal: controller.signal,
|
||||||
endpointRoot,
|
endpointRoot,
|
||||||
|
cameraObstacleProjectionDelivery: timeline.cameraObstacleProjectionDelivery,
|
||||||
})
|
})
|
||||||
.then((chunk) => {
|
.then((chunk) => {
|
||||||
if (controller.signal.aborted) return;
|
if (controller.signal.aborted) return;
|
||||||
|
|||||||
@@ -78,6 +78,12 @@ export function SimulationWorkspace() {
|
|||||||
setEditing(null);
|
setEditing(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const acceptViewerProject = useCallback((project: SimulationProject) => {
|
||||||
|
setProjects((current) => current.map((item) => (
|
||||||
|
item.projectId === project.projectId ? project : item
|
||||||
|
)));
|
||||||
|
}, []);
|
||||||
|
|
||||||
const confirmDelete = async () => {
|
const confirmDelete = async () => {
|
||||||
if (!deleting) return;
|
if (!deleting) return;
|
||||||
await deleteSimulationProject(deleting.projectId);
|
await deleteSimulationProject(deleting.projectId);
|
||||||
@@ -122,7 +128,10 @@ export function SimulationWorkspace() {
|
|||||||
</StatusBadge>
|
</StatusBadge>
|
||||||
</header>
|
</header>
|
||||||
{selected.status === "ready" && selected.worldManifest ? (
|
{selected.status === "ready" && selected.worldManifest ? (
|
||||||
<SimulationViewport project={selected} />
|
<SimulationViewport
|
||||||
|
project={selected}
|
||||||
|
onProjectChange={acceptViewerProject}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<GlassSurface className="simulation-workspace__processing" padding="lg">
|
<GlassSurface className="simulation-workspace__processing" padding="lg">
|
||||||
{selected.status === "failed" ? <Icon name="alert" size={20} /> : <ActivityIndicator label="Сборка мира" />}
|
{selected.status === "failed" ? <Icon name="alert" size={20} /> : <ActivityIndicator label="Сборка мира" />}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ let synchronizeRecordedEvidencePlayback;
|
|||||||
let m4ThreatChunkWindowStarts;
|
let m4ThreatChunkWindowStarts;
|
||||||
let cancelM4ThreatChunkRequestsOutsideWindow;
|
let cancelM4ThreatChunkRequestsOutsideWindow;
|
||||||
let buildM4LocalSurface;
|
let buildM4LocalSurface;
|
||||||
|
let buildM4StaticObstacleBoxes;
|
||||||
|
|
||||||
const resultId = `m4-threat-replay-${"a".repeat(64)}`;
|
const resultId = `m4-threat-replay-${"a".repeat(64)}`;
|
||||||
|
|
||||||
@@ -50,6 +51,9 @@ before(async () => {
|
|||||||
({ buildM4LocalSurface } = await server.ssrLoadModule(
|
({ buildM4LocalSurface } = await server.ssrLoadModule(
|
||||||
"/src/core/laboratory/m4LocalSurface.ts",
|
"/src/core/laboratory/m4LocalSurface.ts",
|
||||||
));
|
));
|
||||||
|
({ buildM4StaticObstacleBoxes } = await server.ssrLoadModule(
|
||||||
|
"/src/workspaces/laboratory/m4StaticObstacleBoxes.ts",
|
||||||
|
));
|
||||||
});
|
});
|
||||||
|
|
||||||
after(async () => {
|
after(async () => {
|
||||||
@@ -365,6 +369,8 @@ test("M4.8S timeline binds factory-KB4 camera points through its exact endpoint"
|
|||||||
camera_point_window_seconds: 2,
|
camera_point_window_seconds: 2,
|
||||||
camera_point_sample_limit: 20000,
|
camera_point_sample_limit: 20000,
|
||||||
world_state_delivery: "source-paced-latest-wins",
|
world_state_delivery: "source-paced-latest-wins",
|
||||||
|
occupancy_provenance_delivery: "baseline-versus-additive-component-diff",
|
||||||
|
camera_obstacle_projection_delivery: "factory-kb4-occupied-voxel-bounds",
|
||||||
world_state_frame_count: 4481,
|
world_state_frame_count: 4481,
|
||||||
superseded_frame_count: 8,
|
superseded_frame_count: 8,
|
||||||
local_surface_visualization: {
|
local_surface_visualization: {
|
||||||
@@ -392,9 +398,14 @@ test("M4.8S timeline binds factory-KB4 camera points through its exact endpoint"
|
|||||||
assert.equal(timeline.cameraPointWindowSeconds, 2);
|
assert.equal(timeline.cameraPointWindowSeconds, 2);
|
||||||
assert.equal(timeline.worldStateFrameCount, 4481);
|
assert.equal(timeline.worldStateFrameCount, 4481);
|
||||||
assert.equal(timeline.supersededFrameCount, 8);
|
assert.equal(timeline.supersededFrameCount, 8);
|
||||||
|
assert.equal(
|
||||||
|
timeline.cameraObstacleProjectionDelivery,
|
||||||
|
"factory-kb4-occupied-voxel-bounds",
|
||||||
|
);
|
||||||
|
|
||||||
const chunk = await fetchM4ThreatTimelineChunk(replayResultId, 1, 1, {
|
const chunk = await fetchM4ThreatTimelineChunk(replayResultId, 1, 1, {
|
||||||
endpointRoot,
|
endpointRoot,
|
||||||
|
cameraObstacleProjectionDelivery: timeline.cameraObstacleProjectionDelivery,
|
||||||
fetcher: async (input) => {
|
fetcher: async (input) => {
|
||||||
requested = String(input);
|
requested = String(input);
|
||||||
return new Response(JSON.stringify({
|
return new Response(JSON.stringify({
|
||||||
@@ -411,6 +422,30 @@ test("M4.8S timeline binds factory-KB4 camera points through its exact endpoint"
|
|||||||
camera_projected_point_count: 1,
|
camera_projected_point_count: 1,
|
||||||
camera_projected_sample_count: 1,
|
camera_projected_sample_count: 1,
|
||||||
camera_projection: "factory-kb4-exact",
|
camera_projection: "factory-kb4-exact",
|
||||||
|
metric_obstacles: [{
|
||||||
|
component_id: "temporal-1855-7",
|
||||||
|
state: "current",
|
||||||
|
motion: "stationary",
|
||||||
|
centroid_body_xyz_m: [2.4, 0.1, 0.3],
|
||||||
|
cell_centers_body_xyz_m: [[2.4, 0.1, 0.3]],
|
||||||
|
occupancy_source: "additive-low-step",
|
||||||
|
camera_projection: {
|
||||||
|
bbox_xyxy: [390.5, 280.25, 408.75, 318.5],
|
||||||
|
nearest_depth_m: 2.3,
|
||||||
|
projected_cell_count: 1,
|
||||||
|
projection: "factory-kb4-occupied-voxel-bounds",
|
||||||
|
authority: "visual-derived",
|
||||||
|
},
|
||||||
|
assessment: {
|
||||||
|
component_id: "temporal-1855-7",
|
||||||
|
decision: "threat",
|
||||||
|
corridor_intersection: "intersects",
|
||||||
|
relative_speed_mps: null,
|
||||||
|
closest_approach_m: 0.2,
|
||||||
|
ttc_seconds: null,
|
||||||
|
reason_codes: ["current-corridor-intersection"],
|
||||||
|
},
|
||||||
|
}],
|
||||||
camera_url: `${endpointRoot}/${replayResultId}/timeline/frames/1/camera`,
|
camera_url: `${endpointRoot}/${replayResultId}/timeline/frames/1/camera`,
|
||||||
})],
|
})],
|
||||||
authority: "replay-simulated",
|
authority: "replay-simulated",
|
||||||
@@ -418,10 +453,18 @@ test("M4.8S timeline binds factory-KB4 camera points through its exact endpoint"
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
assert.match(requested, new RegExp(`^${endpointRoot}/${replayResultId}/timeline/chunk`));
|
assert.match(requested, new RegExp(`^${endpointRoot}/${replayResultId}/timeline/chunk`));
|
||||||
|
assert.match(
|
||||||
|
requested,
|
||||||
|
/obstacle_projection=factory-kb4-occupied-voxel-bounds/,
|
||||||
|
);
|
||||||
assert.equal(chunk.frames[0].worldStateAvailable, false);
|
assert.equal(chunk.frames[0].worldStateAvailable, false);
|
||||||
assert.equal(chunk.frames[0].terminalOutcome, "superseded");
|
assert.equal(chunk.frames[0].terminalOutcome, "superseded");
|
||||||
assert.deepEqual(chunk.frames[0].cameraProjectedPointsXyd[0], [100.5, 200.25, 3.75]);
|
assert.deepEqual(chunk.frames[0].cameraProjectedPointsXyd[0], [100.5, 200.25, 3.75]);
|
||||||
assert.equal(chunk.frames[0].cameraProjection, "factory-kb4-exact");
|
assert.equal(chunk.frames[0].cameraProjection, "factory-kb4-exact");
|
||||||
|
assert.deepEqual(
|
||||||
|
chunk.frames[0].metricObstacles[0].cameraProjection.bboxXyxy,
|
||||||
|
[390.5, 280.25, 408.75, 318.5],
|
||||||
|
);
|
||||||
|
|
||||||
const overlay = await fetchM4ThreatCameraPointOverlay(replayResultId, 1, {
|
const overlay = await fetchM4ThreatCameraPointOverlay(replayResultId, 1, {
|
||||||
endpointRoot,
|
endpointRoot,
|
||||||
@@ -453,6 +496,43 @@ test("M4.8S timeline binds factory-KB4 camera points through its exact endpoint"
|
|||||||
assert.equal(overlay.projection, "factory-kb4-causal-registered-accumulation");
|
assert.equal(overlay.projection, "factory-kb4-causal-registered-accumulation");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("M4.8R3 turns active low-step components into native fisheye obstacle boxes", () => {
|
||||||
|
const obstacle = {
|
||||||
|
componentId: "temporal-1855-7",
|
||||||
|
state: "current",
|
||||||
|
motion: "stationary",
|
||||||
|
centroidBodyXyzM: [2.4, 0.1, 0.3],
|
||||||
|
cellCentersBodyXyzM: [[2.4, 0.1, 0.3]],
|
||||||
|
occupancySource: "additive-low-step",
|
||||||
|
cameraProjection: {
|
||||||
|
bboxXyxy: [398, 298, 402, 302],
|
||||||
|
nearestDepthM: 2.3,
|
||||||
|
projectedCellCount: 1,
|
||||||
|
projection: "factory-kb4-occupied-voxel-bounds",
|
||||||
|
authority: "visual-derived",
|
||||||
|
},
|
||||||
|
assessment: {
|
||||||
|
componentId: "temporal-1855-7",
|
||||||
|
decision: "threat",
|
||||||
|
corridorIntersection: "intersects",
|
||||||
|
relativeSpeedMps: null,
|
||||||
|
closestApproachM: 0.2,
|
||||||
|
ttcSeconds: null,
|
||||||
|
reasonCodes: ["current-corridor-intersection"],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const boxes = buildM4StaticObstacleBoxes([
|
||||||
|
obstacle,
|
||||||
|
{ ...obstacle, componentId: "baseline", occupancySource: "baseline" },
|
||||||
|
{ ...obstacle, componentId: "held", state: "held" },
|
||||||
|
], 800, 600);
|
||||||
|
assert.equal(boxes.length, 1);
|
||||||
|
assert.deepEqual(boxes[0].boxXyxy, [394, 294, 406, 306]);
|
||||||
|
assert.equal(boxes[0].label, "OBS #7 · 2,3 м");
|
||||||
|
assert.equal(boxes[0].tone, "danger");
|
||||||
|
assert.equal(boxes[0].dashed, false);
|
||||||
|
});
|
||||||
|
|
||||||
test("M4.6 local SLAM surface reprojects registered increments into the active body frame", () => {
|
test("M4.6 local SLAM surface reprojects registered increments into the active body frame", () => {
|
||||||
const frames = [
|
const frames = [
|
||||||
timelineFrame(0, 10, {
|
timelineFrame(0, 10, {
|
||||||
@@ -603,6 +683,9 @@ test("M4.6 viewer keeps media and spatial panes on one playback clock", async ()
|
|||||||
assert.match(visual, /label: "3D"/);
|
assert.match(visual, /label: "3D"/);
|
||||||
assert.match(visual, /label: "PLAN"/);
|
assert.match(visual, /label: "PLAN"/);
|
||||||
assert.match(visual, />\s*POINTS\s*</);
|
assert.match(visual, />\s*POINTS\s*</);
|
||||||
|
assert.match(visual, />\s*OBSTACLES\s*</);
|
||||||
|
assert.match(visual, /useState\(true\);[\s\S]*setShowStaticObstacles/);
|
||||||
|
assert.match(visual, /buildM4StaticObstacleBoxes/);
|
||||||
assert.match(visual, /pointCloudOverlay=/);
|
assert.match(visual, /pointCloudOverlay=/);
|
||||||
assert.match(visual, /mediaMode/);
|
assert.match(visual, /mediaMode/);
|
||||||
assert.match(visual, /spatialMode/);
|
assert.match(visual, /spatialMode/);
|
||||||
|
|||||||
@@ -81,6 +81,10 @@ test("PlayCanvas owns the realtime scene graph without an iframe or React entity
|
|||||||
assert.match(runtime, /applyWorldTransform/);
|
assert.match(runtime, /applyWorldTransform/);
|
||||||
assert.match(runtime, /setLayerWorldTransform/);
|
assert.match(runtime, /setLayerWorldTransform/);
|
||||||
assert.match(runtime, /PLAYCANVAS_X_180_TRANSFORM/);
|
assert.match(runtime, /PLAYCANVAS_X_180_TRANSFORM/);
|
||||||
|
assert.match(runtime, /PLAYCANVAS_Y_180_TRANSFORM/);
|
||||||
|
assert.match(runtime, /PLAYCANVAS_Z_180_TRANSFORM/);
|
||||||
|
assert.match(runtime, /DracoDecoderModule/);
|
||||||
|
assert.match(runtime, /draco_decoder_gltf\.wasm/);
|
||||||
assert.match(runtime, /ensureCollision/);
|
assert.match(runtime, /ensureCollision/);
|
||||||
assert.match(runtime, /app\.root\.addChild/);
|
assert.match(runtime, /app\.root\.addChild/);
|
||||||
assert.match(runtime, /dispose\(\)/);
|
assert.match(runtime, /dispose\(\)/);
|
||||||
@@ -88,11 +92,15 @@ test("PlayCanvas owns the realtime scene graph without an iframe or React entity
|
|||||||
assert.match(viewport, /Collision недоступен/);
|
assert.match(viewport, /Collision недоступен/);
|
||||||
assert.match(viewport, /Для этой сборки collision GLB не был запрошен/);
|
assert.match(viewport, /Для этой сборки collision GLB не был запрошен/);
|
||||||
assert.match(viewport, /<Select[\s\S]*Качество Gaussian-сцены/);
|
assert.match(viewport, /<Select[\s\S]*Качество Gaussian-сцены/);
|
||||||
assert.match(viewport, /useState<SimulationQuality>\("high"\)/);
|
assert.match(viewport, /useState<SimulationQuality>\(project\.viewerSettings\.quality\)/);
|
||||||
assert.match(viewport, /runtimeRef\.current\?\.home\(\)/);
|
assert.match(viewport, /runtimeRef\.current\?\.home\(\)/);
|
||||||
assert.match(viewport, /Настройки сцены/);
|
assert.match(viewport, /Настройки сцены/);
|
||||||
assert.match(viewport, /Инверсия визуального слоя/);
|
assert.match(viewport, /Инверсия визуального слоя/);
|
||||||
assert.match(viewport, /Инверсия collision-слоя/);
|
assert.match(viewport, /Инверсия collision-слоя/);
|
||||||
assert.match(viewport, /Инверсия управления по горизонтали/);
|
assert.match(viewport, /Инверсия управления по горизонтали/);
|
||||||
assert.match(viewport, /Инверсия управления по вертикали/);
|
assert.match(viewport, /Инверсия управления по вертикали/);
|
||||||
|
assert.match(viewport, /Ось инверсии визуального слоя/);
|
||||||
|
assert.match(viewport, /Ось инверсии collision-слоя/);
|
||||||
|
assert.match(viewport, /saveSimulationViewerSettings/);
|
||||||
|
assert.match(viewport, /onProjectChange\?\.\(saved\)/);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -57,7 +57,8 @@ Mission Core backend
|
|||||||
DC Gaussian Pipeline
|
DC Gaussian Pipeline
|
||||||
├─ TUS bundle or archive admission
|
├─ TUS bundle or archive admission
|
||||||
├─ secure ZIP/RAR/7z normalization
|
├─ secure ZIP/RAR/7z normalization
|
||||||
└─ SplatTransform build on Worker 006
|
├─ native Vulkan SplatTransform build on Worker 006
|
||||||
|
└─ bounded collision simplification + mandatory Draco publication
|
||||||
```
|
```
|
||||||
|
|
||||||
The browser never receives the Worker token. Mission Core does not embed archive-format behavior
|
The browser never receives the Worker token. Mission Core does not embed archive-format behavior
|
||||||
@@ -70,10 +71,22 @@ Mission Core pins the direct `playcanvas` package. `PlayCanvasRuntime` owns one
|
|||||||
camera controls, GSplat asset lifecycle and later physical/debug entities. React owns no per-frame
|
camera controls, GSplat asset lifecycle and later physical/debug entities. React owns no per-frame
|
||||||
entity state. A new world is data and does not require a new frontend build.
|
entity state. A new world is data and does not require a new frontend build.
|
||||||
|
|
||||||
The first Worker 006 provider is CPU-only and therefore does not advertise collision outputs. The
|
Worker 006 runs the portable API in its own read-only Docker composition and delegates only the
|
||||||
UI disables collision/combined modes and states this explicitly; it does not synthesize a proxy.
|
SplatTransform GPU command through a confined filesystem spool to the pinned native Vulkan runtime
|
||||||
The manifest already keeps visual and collision URLs and their independent world transforms so the
|
on the RTX 4090. It does not install into or share the Python, CUDA, Triton or computer-vision
|
||||||
GPU collision build can be admitted without changing the project surface.
|
environments on the host.
|
||||||
|
|
||||||
|
Collision publication is deliberately two-stage. SplatTransform generates smooth collision and
|
||||||
|
SVO query data at a scene-type voxel size (`0.2 m` outdoors, `0.1 m` indoors/objects). The isolated
|
||||||
|
pipeline container then bounds the debug/physics mesh to a default 250,000-triangle target and
|
||||||
|
always writes `KHR_draco_mesh_compression` before the artifact digest is sealed. Simplification
|
||||||
|
controls decoded geometry cost; Draco controls transfer and stored bytes and is never treated as a
|
||||||
|
replacement for a physics mesh budget. The browser loads the result through a pinned local Draco
|
||||||
|
WASM decoder with no external decoder request.
|
||||||
|
|
||||||
|
Visual and collision layers retain independent X/Y/Z correction settings, while PlayCanvas world,
|
||||||
|
camera, navigation and future physics stay in the canonical Y-up coordinate system. Quality, both
|
||||||
|
layer transforms and camera-axis inversion are persisted atomically in each project's metadata.
|
||||||
|
|
||||||
Primary implementation references:
|
Primary implementation references:
|
||||||
|
|
||||||
@@ -92,8 +105,9 @@ Primary implementation references:
|
|||||||
- encrypted, linked, traversing, duplicate and over-limit archive entries fail closed;
|
- encrypted, linked, traversing, duplicate and over-limit archive entries fail closed;
|
||||||
- project status is durable and reflects provider state without fabricated percentages;
|
- project status is durable and reflects provider state without fabricated percentages;
|
||||||
- ready artifacts are imported digest-bound and served from Mission Core same-origin URLs;
|
- ready artifacts are imported digest-bound and served from Mission Core same-origin URLs;
|
||||||
|
- every published collision GLB is simplified to the configured budget and requires Draco;
|
||||||
- edit changes project metadata; delete removes both the Mission Core project and terminal provider
|
- edit changes project metadata; delete removes both the Mission Core project and terminal provider
|
||||||
job;
|
job;
|
||||||
- a ready project mounts direct PlayCanvas Engine and loads Streamed SOG with preview fallback;
|
- a ready project mounts direct PlayCanvas Engine and loads Streamed SOG with preview fallback;
|
||||||
- visual, collision and combined remain distinct runtime modes even when collision is not yet
|
- visual, collision and combined remain distinct runtime modes, and collision is loaded lazily;
|
||||||
available on the CPU provider.
|
- viewer quality, layer-axis correction and camera inversion survive navigation and reload.
|
||||||
|
|||||||
@@ -19,7 +19,11 @@ import numpy as np
|
|||||||
from .geometry import RecordedGeometryStore
|
from .geometry import RecordedGeometryStore
|
||||||
from .geometry_math import project_map_points_kb4
|
from .geometry_math import project_map_points_kb4
|
||||||
from .recorded_source import RECORDED_REPRESENTATION_ID
|
from .recorded_source import RECORDED_REPRESENTATION_ID
|
||||||
from .spatial_evidence import project_metric_obstacles_to_body, sample_points_in_body_frame
|
from .spatial_evidence import (
|
||||||
|
project_metric_obstacles_to_body,
|
||||||
|
project_metric_obstacles_to_camera,
|
||||||
|
sample_points_in_body_frame,
|
||||||
|
)
|
||||||
from .threat import (
|
from .threat import (
|
||||||
DEFAULT_REPLAY_THREAT_PROFILE_PATH,
|
DEFAULT_REPLAY_THREAT_PROFILE_PATH,
|
||||||
RecordedReplayBodyFrameResolver,
|
RecordedReplayBodyFrameResolver,
|
||||||
@@ -183,6 +187,11 @@ class M48sReplayTimeline:
|
|||||||
if self.frame_diff_path is not None
|
if self.frame_diff_path is not None
|
||||||
else None
|
else None
|
||||||
),
|
),
|
||||||
|
"camera_obstacle_projection_delivery": (
|
||||||
|
"factory-kb4-occupied-voxel-bounds"
|
||||||
|
if self.frame_diff_path is not None
|
||||||
|
else None
|
||||||
|
),
|
||||||
"world_state_frame_count": len(self.index.offsets_by_sequence),
|
"world_state_frame_count": len(self.index.offsets_by_sequence),
|
||||||
"superseded_frame_count": sum(
|
"superseded_frame_count": sum(
|
||||||
value == "superseded" for value in self.outcomes.values()
|
value == "superseded" for value in self.outcomes.values()
|
||||||
@@ -383,6 +392,7 @@ class M48sReplayTimeline:
|
|||||||
)
|
)
|
||||||
|
|
||||||
metric_visuals: list[dict[str, object]] = []
|
metric_visuals: list[dict[str, object]] = []
|
||||||
|
metric_rows: list[dict[str, object]] = []
|
||||||
camera_proposals: list[dict[str, object]] = []
|
camera_proposals: list[dict[str, object]] = []
|
||||||
assessments: list[dict[str, object]] = []
|
assessments: list[dict[str, object]] = []
|
||||||
if row is not None:
|
if row is not None:
|
||||||
@@ -401,7 +411,6 @@ class M48sReplayTimeline:
|
|||||||
_text(item.get("component_id"), "assessment component"): item
|
_text(item.get("component_id"), "assessment component"): item
|
||||||
for item in assessments
|
for item in assessments
|
||||||
}
|
}
|
||||||
metric_rows: list[dict[str, object]] = []
|
|
||||||
for obstacle in (
|
for obstacle in (
|
||||||
*_objects(obstacle_map.get("occupied"), "occupied obstacles"),
|
*_objects(obstacle_map.get("occupied"), "occupied obstacles"),
|
||||||
*_objects(obstacle_map.get("unknown"), "unknown obstacles"),
|
*_objects(obstacle_map.get("unknown"), "unknown obstacles"),
|
||||||
@@ -427,11 +436,26 @@ class M48sReplayTimeline:
|
|||||||
occupied_voxel_size_m=self.profile.corridor.occupied_voxel_size_m,
|
occupied_voxel_size_m=self.profile.corridor.occupied_voxel_size_m,
|
||||||
)
|
)
|
||||||
provenance = self._component_provenance(sequence)
|
provenance = self._component_provenance(sequence)
|
||||||
for visual in metric_visuals:
|
camera_projections = (
|
||||||
visual["occupancy_source"] = provenance.get(
|
{}
|
||||||
str(visual["component_id"]),
|
if frame is None or not provenance
|
||||||
"baseline",
|
else project_metric_obstacles_to_camera(
|
||||||
|
metric_rows,
|
||||||
|
position_map_xyz=frame.sensor_position_map,
|
||||||
|
orientation_map_from_lidar_xyzw=frame.sensor_orientation_xyzw,
|
||||||
|
profile=frame.projection,
|
||||||
|
occupied_voxel_size_m=(
|
||||||
|
self.profile.corridor.occupied_voxel_size_m
|
||||||
|
),
|
||||||
|
component_ids=set(provenance),
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
for visual in metric_visuals:
|
||||||
|
component_id = str(visual["component_id"])
|
||||||
|
visual["occupancy_source"] = provenance.get(component_id, "baseline")
|
||||||
|
projection = camera_projections.get(component_id)
|
||||||
|
if projection is not None:
|
||||||
|
visual["camera_projection"] = projection
|
||||||
associated = set(_strings(row.get("associated_proposal_ids"), "associated ids"))
|
associated = set(_strings(row.get("associated_proposal_ids"), "associated ids"))
|
||||||
for proposal in _objects(row.get("detector_proposals"), "detector proposals"):
|
for proposal in _objects(row.get("detector_proposals"), "detector proposals"):
|
||||||
proposal_id = _text(proposal.get("proposal_id"), "proposal id")
|
proposal_id = _text(proposal.get("proposal_id"), "proposal id")
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from collections.abc import Mapping, Sequence
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
import numpy.typing as npt
|
import numpy.typing as npt
|
||||||
|
|
||||||
|
from .geometry_math import Kb4ProjectionProfile, project_map_points_kb4
|
||||||
from .threat import ReplayBodyFrame
|
from .threat import ReplayBodyFrame
|
||||||
|
|
||||||
FloatArray = npt.NDArray[np.float64]
|
FloatArray = npt.NDArray[np.float64]
|
||||||
@@ -86,6 +87,96 @@ def project_metric_obstacles_to_body(
|
|||||||
return visuals
|
return visuals
|
||||||
|
|
||||||
|
|
||||||
|
def project_metric_obstacles_to_camera(
|
||||||
|
metric_rows: Sequence[Mapping[str, object]],
|
||||||
|
*,
|
||||||
|
position_map_xyz: npt.ArrayLike,
|
||||||
|
orientation_map_from_lidar_xyzw: npt.ArrayLike,
|
||||||
|
profile: Kb4ProjectionProfile,
|
||||||
|
occupied_voxel_size_m: float,
|
||||||
|
component_ids: set[str] | None = None,
|
||||||
|
) -> dict[str, dict[str, object]]:
|
||||||
|
"""Project ledger-owned occupied voxel bounds into the native KB4 frame.
|
||||||
|
|
||||||
|
This is a visualization projection of already accepted world-state
|
||||||
|
components. It neither reclusters geometry nor changes threat authority.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not math.isfinite(occupied_voxel_size_m) or occupied_voxel_size_m <= 0:
|
||||||
|
raise SpatialEvidenceProjectionError("occupied voxel size is invalid")
|
||||||
|
points: list[tuple[float, float, float]] = []
|
||||||
|
point_owners: list[tuple[str, int]] = []
|
||||||
|
for row in metric_rows:
|
||||||
|
component_id = row.get("component_id")
|
||||||
|
cells = row.get("cells")
|
||||||
|
if (
|
||||||
|
not isinstance(component_id, str)
|
||||||
|
or not component_id
|
||||||
|
or (component_ids is not None and component_id not in component_ids)
|
||||||
|
or not isinstance(cells, list)
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
for cell_index, raw_cell in enumerate(cells):
|
||||||
|
if not isinstance(raw_cell, dict):
|
||||||
|
raise SpatialEvidenceProjectionError("occupied cell is invalid")
|
||||||
|
indices = (
|
||||||
|
_signed_integer(raw_cell.get("x"), "cell x"),
|
||||||
|
_signed_integer(raw_cell.get("y"), "cell y"),
|
||||||
|
_signed_integer(raw_cell.get("z"), "cell z"),
|
||||||
|
)
|
||||||
|
bounds = tuple(
|
||||||
|
(index * occupied_voxel_size_m, (index + 1) * occupied_voxel_size_m)
|
||||||
|
for index in indices
|
||||||
|
)
|
||||||
|
for x in bounds[0]:
|
||||||
|
for y in bounds[1]:
|
||||||
|
for z in bounds[2]:
|
||||||
|
points.append((x, y, z))
|
||||||
|
point_owners.append((component_id, cell_index))
|
||||||
|
if not points:
|
||||||
|
return {}
|
||||||
|
projected = project_map_points_kb4(
|
||||||
|
points,
|
||||||
|
position_map_xyz=position_map_xyz,
|
||||||
|
orientation_map_from_lidar_xyzw=orientation_map_from_lidar_xyzw,
|
||||||
|
profile=profile,
|
||||||
|
)
|
||||||
|
grouped_pixels: dict[str, list[npt.NDArray[np.float64]]] = {}
|
||||||
|
grouped_depths: dict[str, list[float]] = {}
|
||||||
|
grouped_cells: dict[str, set[int]] = {}
|
||||||
|
for pixel, depth, source_index in zip(
|
||||||
|
projected.pixels_xy,
|
||||||
|
projected.depths_m,
|
||||||
|
projected.source_indices,
|
||||||
|
strict=True,
|
||||||
|
):
|
||||||
|
component_id, cell_index = point_owners[int(source_index)]
|
||||||
|
grouped_pixels.setdefault(component_id, []).append(pixel)
|
||||||
|
grouped_depths.setdefault(component_id, []).append(float(depth))
|
||||||
|
grouped_cells.setdefault(component_id, set()).add(cell_index)
|
||||||
|
result: dict[str, dict[str, object]] = {}
|
||||||
|
for component_id, pixel_values in grouped_pixels.items():
|
||||||
|
pixels = np.asarray(pixel_values, dtype=np.float64)
|
||||||
|
depths = np.asarray(grouped_depths[component_id], dtype=np.float64)
|
||||||
|
if pixels.size == 0 or depths.size == 0:
|
||||||
|
continue
|
||||||
|
minimum = np.min(pixels, axis=0)
|
||||||
|
maximum = np.max(pixels, axis=0)
|
||||||
|
result[component_id] = {
|
||||||
|
"bbox_xyxy": [
|
||||||
|
round(float(minimum[0]), 3),
|
||||||
|
round(float(minimum[1]), 3),
|
||||||
|
round(float(maximum[0]), 3),
|
||||||
|
round(float(maximum[1]), 3),
|
||||||
|
],
|
||||||
|
"nearest_depth_m": round(float(np.min(depths)), 6),
|
||||||
|
"projected_cell_count": len(grouped_cells[component_id]),
|
||||||
|
"projection": "factory-kb4-occupied-voxel-bounds",
|
||||||
|
"authority": "visual-derived",
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def _finite_vector3(value: Sequence[object], label: str) -> tuple[float, float, float]:
|
def _finite_vector3(value: Sequence[object], label: str) -> tuple[float, float, float]:
|
||||||
if len(value) != 3:
|
if len(value) != 3:
|
||||||
raise SpatialEvidenceProjectionError(f"{label} is invalid")
|
raise SpatialEvidenceProjectionError(f"{label} is invalid")
|
||||||
@@ -113,6 +204,7 @@ def _signed_integer(value: object, label: str) -> int:
|
|||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"SpatialEvidenceProjectionError",
|
"SpatialEvidenceProjectionError",
|
||||||
|
"project_metric_obstacles_to_camera",
|
||||||
"project_metric_obstacles_to_body",
|
"project_metric_obstacles_to_body",
|
||||||
"sample_points_in_body_frame",
|
"sample_points_in_body_frame",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -28,10 +28,18 @@ class MissionCoreWatchdogError(RuntimeError):
|
|||||||
|
|
||||||
@dataclass(frozen=True, slots=True)
|
@dataclass(frozen=True, slots=True)
|
||||||
class MissionCoreWatchdogPolicy:
|
class MissionCoreWatchdogPolicy:
|
||||||
startup_grace_seconds: float = 45.0
|
# Cold startup on the 18 GB control laptop includes validating the durable
|
||||||
|
# recorded-evidence catalog and can exceed two minutes under storage load.
|
||||||
|
# This applies only before the first probe; steady-state failures retain the
|
||||||
|
# much tighter bounded policy below.
|
||||||
|
startup_grace_seconds: float = 300.0
|
||||||
probe_interval_seconds: float = 2.0
|
probe_interval_seconds: float = 2.0
|
||||||
probe_timeout_seconds: float = 1.0
|
# Source hashing and resumable multi-gigabyte uploads can briefly delay the
|
||||||
consecutive_failure_limit: int = 3
|
# synchronous readiness projection without making the service unhealthy.
|
||||||
|
# Six bounded five-second probes still fail closed, but do not turn normal
|
||||||
|
# storage pressure into a restart loop that discards upload progress.
|
||||||
|
probe_timeout_seconds: float = 5.0
|
||||||
|
consecutive_failure_limit: int = 6
|
||||||
graceful_shutdown_seconds: float = 12.0
|
graceful_shutdown_seconds: float = 12.0
|
||||||
|
|
||||||
def __post_init__(self) -> None:
|
def __post_init__(self) -> None:
|
||||||
@@ -149,7 +157,7 @@ class MissionCoreSelfWatchdog:
|
|||||||
journal: MissionCoreWatchdogJournal | None = None,
|
journal: MissionCoreWatchdogJournal | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.policy = policy or MissionCoreWatchdogPolicy()
|
self.policy = policy or MissionCoreWatchdogPolicy()
|
||||||
self.probe = probe or _mission_core_health_probe(
|
self.probe = probe or _mission_core_liveness_probe(
|
||||||
self.policy.probe_timeout_seconds
|
self.policy.probe_timeout_seconds
|
||||||
)
|
)
|
||||||
self.request_shutdown = request_shutdown or _process_group_signal(signal.SIGTERM)
|
self.request_shutdown = request_shutdown or _process_group_signal(signal.SIGTERM)
|
||||||
@@ -224,7 +232,7 @@ def watchdog_enabled(environ: Mapping[str, str] = os.environ) -> bool:
|
|||||||
return environ.get(WATCHDOG_ENV) == WATCHDOG_ENABLED_VALUE
|
return environ.get(WATCHDOG_ENV) == WATCHDOG_ENABLED_VALUE
|
||||||
|
|
||||||
|
|
||||||
def _mission_core_health_probe(timeout_seconds: float) -> Probe:
|
def _mission_core_liveness_probe(timeout_seconds: float) -> Probe:
|
||||||
def probe() -> bool:
|
def probe() -> bool:
|
||||||
connection = http.client.HTTPConnection(
|
connection = http.client.HTTPConnection(
|
||||||
"127.0.0.1",
|
"127.0.0.1",
|
||||||
@@ -232,7 +240,7 @@ def _mission_core_health_probe(timeout_seconds: float) -> Probe:
|
|||||||
timeout=timeout_seconds,
|
timeout=timeout_seconds,
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
connection.request("GET", "/api/health", headers={"Connection": "close"})
|
connection.request("GET", "/api/liveness", headers={"Connection": "close"})
|
||||||
response = connection.getresponse()
|
response = connection.getresponse()
|
||||||
payload = response.read(64 * 1024 + 1)
|
payload = response.read(64 * 1024 + 1)
|
||||||
except (OSError, TimeoutError, http.client.HTTPException):
|
except (OSError, TimeoutError, http.client.HTTPException):
|
||||||
@@ -248,7 +256,7 @@ def _mission_core_health_probe(timeout_seconds: float) -> Probe:
|
|||||||
return bool(
|
return bool(
|
||||||
isinstance(document, dict)
|
isinstance(document, dict)
|
||||||
and document.get("ok") is True
|
and document.get("ok") is True
|
||||||
and document.get("status") == "ok"
|
and document.get("status") == "alive"
|
||||||
and document.get("service") == MISSION_CORE_SERVICE_ID
|
and document.get("service") == MISSION_CORE_SERVICE_ID
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ from k1link.simulation.gaussian_pipeline_gateway import (
|
|||||||
|
|
||||||
PROJECT_SCHEMA: Final = "missioncore.simulation-project/v1"
|
PROJECT_SCHEMA: Final = "missioncore.simulation-project/v1"
|
||||||
WORLD_MANIFEST_SCHEMA: Final = "missioncore.simulation-world-manifest/v1"
|
WORLD_MANIFEST_SCHEMA: Final = "missioncore.simulation-world-manifest/v1"
|
||||||
|
VIEWER_SETTINGS_SCHEMA: Final = "missioncore.simulation-viewer-settings/v1"
|
||||||
PROJECT_ID_PATTERN: Final = re.compile(r"^sim-[a-f0-9]{32}$")
|
PROJECT_ID_PATTERN: Final = re.compile(r"^sim-[a-f0-9]{32}$")
|
||||||
SOURCE_FILE_ID_PATTERN: Final = re.compile(r"^src-[0-9]{5}-[a-f0-9]{8}$")
|
SOURCE_FILE_ID_PATTERN: Final = re.compile(r"^src-[0-9]{5}-[a-f0-9]{8}$")
|
||||||
PROVIDER_JOB_ID_PATTERN: Final = re.compile(r"^gsp-[0-9]{14}-[a-f0-9]{8}$")
|
PROVIDER_JOB_ID_PATTERN: Final = re.compile(r"^gsp-[0-9]{14}-[a-f0-9]{8}$")
|
||||||
@@ -142,6 +143,7 @@ class SimulationProjectStore:
|
|||||||
},
|
},
|
||||||
"artifacts": [],
|
"artifacts": [],
|
||||||
"world_manifest": None,
|
"world_manifest": None,
|
||||||
|
"viewer_settings": _default_viewer_settings(),
|
||||||
"error": None,
|
"error": None,
|
||||||
"created_at_utc": now,
|
"created_at_utc": now,
|
||||||
"updated_at_utc": now,
|
"updated_at_utc": now,
|
||||||
@@ -189,6 +191,19 @@ class SimulationProjectStore:
|
|||||||
self._write(document)
|
self._write(document)
|
||||||
return document
|
return document
|
||||||
|
|
||||||
|
def update_viewer_settings(
|
||||||
|
self,
|
||||||
|
project_id: str,
|
||||||
|
settings: dict[str, Any],
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
normalized = _viewer_settings(settings)
|
||||||
|
with self._lock:
|
||||||
|
document = self._read(project_id)
|
||||||
|
document["viewer_settings"] = normalized
|
||||||
|
document["updated_at_utc"] = utc_now_iso()
|
||||||
|
self._write(document)
|
||||||
|
return document
|
||||||
|
|
||||||
def upload_state(self, project_id: str, file_id: str) -> tuple[int, int]:
|
def upload_state(self, project_id: str, file_id: str) -> tuple[int, int]:
|
||||||
with self._lock:
|
with self._lock:
|
||||||
document = self._read(project_id)
|
document = self._read(project_id)
|
||||||
@@ -239,7 +254,7 @@ class SimulationProjectStore:
|
|||||||
def begin_build(self, project_id: str) -> dict[str, Any]:
|
def begin_build(self, project_id: str) -> dict[str, Any]:
|
||||||
with self._lock:
|
with self._lock:
|
||||||
document = self._read(project_id)
|
document = self._read(project_id)
|
||||||
if document["status"] not in {"uploading", "failed"}:
|
if document["status"] not in {"uploading", "failed", "ready"}:
|
||||||
raise SimulationProjectConflictError("simulation project cannot start a build")
|
raise SimulationProjectConflictError("simulation project cannot start a build")
|
||||||
if any(
|
if any(
|
||||||
int(item["uploaded_bytes"]) != int(item["byte_length"])
|
int(item["uploaded_bytes"]) != int(item["byte_length"])
|
||||||
@@ -371,6 +386,7 @@ class SimulationProjectStore:
|
|||||||
or document.get("project_id") != project_id
|
or document.get("project_id") != project_id
|
||||||
):
|
):
|
||||||
raise SimulationProjectError("persisted simulation project identity is invalid")
|
raise SimulationProjectError("persisted simulation project identity is invalid")
|
||||||
|
document.setdefault("viewer_settings", _default_viewer_settings())
|
||||||
return document
|
return document
|
||||||
|
|
||||||
def _write(self, document: dict[str, Any]) -> None:
|
def _write(self, document: dict[str, Any]) -> None:
|
||||||
@@ -421,7 +437,7 @@ class SimulationProjectService:
|
|||||||
|
|
||||||
def begin_build(self, project_id: str) -> dict[str, Any]:
|
def begin_build(self, project_id: str) -> dict[str, Any]:
|
||||||
project = self.store.get(project_id)
|
project = self.store.get(project_id)
|
||||||
if project.get("status") == "failed":
|
if project.get("status") in {"failed", "ready"}:
|
||||||
job_id = project["provider"].get("job_id")
|
job_id = project["provider"].get("job_id")
|
||||||
if isinstance(job_id, str):
|
if isinstance(job_id, str):
|
||||||
provider = self.provider_factory()
|
provider = self.provider_factory()
|
||||||
@@ -616,7 +632,7 @@ def _world_manifest(project_id: str, artifacts: list[dict[str, Any]]) -> dict[st
|
|||||||
},
|
},
|
||||||
"transforms": {
|
"transforms": {
|
||||||
"world_from_visual": [1, 0, 0, 0, 0, -1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1],
|
"world_from_visual": [1, 0, 0, 0, 0, -1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1],
|
||||||
"world_from_collision": [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
|
"world_from_collision": [-1, 0, 0, 0, 0, 1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -628,11 +644,66 @@ def _collision_profile(scene_type: str) -> dict[str, Any]:
|
|||||||
"seed_position": [0, 1, 0],
|
"seed_position": [0, 1, 0],
|
||||||
"capsule_height": 1.6,
|
"capsule_height": 1.6,
|
||||||
"capsule_radius": 0.2,
|
"capsule_radius": 0.2,
|
||||||
"voxel_size": 0.05,
|
"voxel_size": 0.2 if scene_type == "outdoor" else 0.1,
|
||||||
"mesh_shape": "smooth",
|
"mesh_shape": "smooth",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _default_viewer_settings() -> dict[str, Any]:
|
||||||
|
return {
|
||||||
|
"schema_version": VIEWER_SETTINGS_SCHEMA,
|
||||||
|
"quality": "high",
|
||||||
|
"visual": {"inverted": True, "axis": "x"},
|
||||||
|
"collision": {"inverted": True, "axis": "y"},
|
||||||
|
"camera": {
|
||||||
|
"invert_horizontal": True,
|
||||||
|
"invert_vertical": False,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _viewer_settings(value: object) -> dict[str, Any]:
|
||||||
|
if not isinstance(value, dict) or set(value) != {
|
||||||
|
"schema_version", "quality", "visual", "collision", "camera"
|
||||||
|
}:
|
||||||
|
raise SimulationProjectError("simulation viewer settings contract is invalid")
|
||||||
|
if value.get("schema_version") != VIEWER_SETTINGS_SCHEMA:
|
||||||
|
raise SimulationProjectError("simulation viewer settings version is unsupported")
|
||||||
|
quality = value.get("quality")
|
||||||
|
if quality not in {"low", "medium", "high", "ultra", "maximum"}:
|
||||||
|
raise SimulationProjectError("simulation viewer quality is invalid")
|
||||||
|
|
||||||
|
def layer(name: str) -> dict[str, Any]:
|
||||||
|
raw = value.get(name)
|
||||||
|
if not isinstance(raw, dict) or set(raw) != {"inverted", "axis"}:
|
||||||
|
raise SimulationProjectError(f"simulation {name} transform is invalid")
|
||||||
|
inverted = raw.get("inverted")
|
||||||
|
axis = raw.get("axis")
|
||||||
|
if not isinstance(inverted, bool) or axis not in {"x", "y", "z"}:
|
||||||
|
raise SimulationProjectError(f"simulation {name} transform is invalid")
|
||||||
|
return {"inverted": inverted, "axis": axis}
|
||||||
|
|
||||||
|
camera = value.get("camera")
|
||||||
|
if not isinstance(camera, dict) or set(camera) != {
|
||||||
|
"invert_horizontal", "invert_vertical"
|
||||||
|
}:
|
||||||
|
raise SimulationProjectError("simulation camera settings are invalid")
|
||||||
|
horizontal = camera.get("invert_horizontal")
|
||||||
|
vertical = camera.get("invert_vertical")
|
||||||
|
if not isinstance(horizontal, bool) or not isinstance(vertical, bool):
|
||||||
|
raise SimulationProjectError("simulation camera settings are invalid")
|
||||||
|
return {
|
||||||
|
"schema_version": VIEWER_SETTINGS_SCHEMA,
|
||||||
|
"quality": quality,
|
||||||
|
"visual": layer("visual"),
|
||||||
|
"collision": layer("collision"),
|
||||||
|
"camera": {
|
||||||
|
"invert_horizontal": horizontal,
|
||||||
|
"invert_vertical": vertical,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def _project_name(value: str) -> str:
|
def _project_name(value: str) -> str:
|
||||||
normalized = " ".join(value.split())
|
normalized = " ".join(value.split())
|
||||||
if not 1 <= len(normalized) <= 120:
|
if not 1 <= len(normalized) <= 120:
|
||||||
|
|||||||
@@ -528,6 +528,17 @@ async def request_validation_error_handler(
|
|||||||
return JSONResponse(status_code=422, content={"detail": INVALID_REQUEST_DETAIL})
|
return JSONResponse(status_code=422, content={"detail": INVALID_REQUEST_DETAIL})
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/api/liveness")
|
||||||
|
async def liveness() -> dict[str, object]:
|
||||||
|
"""Prove only that the canonical event loop is responsive."""
|
||||||
|
|
||||||
|
return {
|
||||||
|
"ok": True,
|
||||||
|
"status": "alive",
|
||||||
|
"service": "mission-core-control-plane",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@app.get("/api/health")
|
@app.get("/api/health")
|
||||||
def health() -> dict[str, Any]:
|
def health() -> dict[str, Any]:
|
||||||
return build_runtime_readiness(
|
return build_runtime_readiness(
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ from k1link.simulation.gaussian_pipeline_gateway import GaussianPipelineGatewayE
|
|||||||
from k1link.simulation.projects import (
|
from k1link.simulation.projects import (
|
||||||
MAX_UPLOAD_CHUNK_BYTES,
|
MAX_UPLOAD_CHUNK_BYTES,
|
||||||
PROJECT_SCHEMA,
|
PROJECT_SCHEMA,
|
||||||
|
VIEWER_SETTINGS_SCHEMA,
|
||||||
SimulationProjectConflictError,
|
SimulationProjectConflictError,
|
||||||
SimulationProjectError,
|
SimulationProjectError,
|
||||||
SimulationProjectNotFoundError,
|
SimulationProjectNotFoundError,
|
||||||
@@ -45,6 +46,30 @@ class SimulationProjectUpdate(BaseModel):
|
|||||||
scene_type: Literal["interior", "outdoor", "object"]
|
scene_type: Literal["interior", "outdoor", "object"]
|
||||||
|
|
||||||
|
|
||||||
|
class SimulationLayerViewerSettings(BaseModel):
|
||||||
|
model_config = ConfigDict(extra="forbid", frozen=True)
|
||||||
|
|
||||||
|
inverted: bool
|
||||||
|
axis: Literal["x", "y", "z"]
|
||||||
|
|
||||||
|
|
||||||
|
class SimulationCameraViewerSettings(BaseModel):
|
||||||
|
model_config = ConfigDict(extra="forbid", frozen=True)
|
||||||
|
|
||||||
|
invert_horizontal: bool
|
||||||
|
invert_vertical: bool
|
||||||
|
|
||||||
|
|
||||||
|
class SimulationViewerSettings(BaseModel):
|
||||||
|
model_config = ConfigDict(extra="forbid", frozen=True)
|
||||||
|
|
||||||
|
schema_version: Literal["missioncore.simulation-viewer-settings/v1"] = VIEWER_SETTINGS_SCHEMA
|
||||||
|
quality: Literal["low", "medium", "high", "ultra", "maximum"]
|
||||||
|
visual: SimulationLayerViewerSettings
|
||||||
|
collision: SimulationLayerViewerSettings
|
||||||
|
camera: SimulationCameraViewerSettings
|
||||||
|
|
||||||
|
|
||||||
class SimulationProjectDocument(BaseModel):
|
class SimulationProjectDocument(BaseModel):
|
||||||
model_config = ConfigDict(extra="forbid")
|
model_config = ConfigDict(extra="forbid")
|
||||||
|
|
||||||
@@ -57,6 +82,7 @@ class SimulationProjectDocument(BaseModel):
|
|||||||
provider: dict[str, Any]
|
provider: dict[str, Any]
|
||||||
artifacts: list[dict[str, Any]]
|
artifacts: list[dict[str, Any]]
|
||||||
world_manifest: dict[str, Any] | None
|
world_manifest: dict[str, Any] | None
|
||||||
|
viewer_settings: SimulationViewerSettings
|
||||||
error: str | None
|
error: str | None
|
||||||
created_at_utc: str
|
created_at_utc: str
|
||||||
updated_at_utc: str
|
updated_at_utc: str
|
||||||
@@ -117,6 +143,21 @@ def build_simulation_projects_router(
|
|||||||
except SimulationProjectError as exc:
|
except SimulationProjectError as exc:
|
||||||
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||||||
|
|
||||||
|
@router.put(
|
||||||
|
"/projects/{project_id}/viewer-settings",
|
||||||
|
response_model=SimulationProjectDocument,
|
||||||
|
)
|
||||||
|
def update_viewer_settings(
|
||||||
|
project_id: str,
|
||||||
|
request: SimulationViewerSettings,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
try:
|
||||||
|
return store.update_viewer_settings(project_id, request.model_dump())
|
||||||
|
except SimulationProjectNotFoundError as exc:
|
||||||
|
raise HTTPException(status_code=404, detail="Проект симуляции не найден.") from exc
|
||||||
|
except SimulationProjectError as exc:
|
||||||
|
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||||||
|
|
||||||
@router.head("/projects/{project_id}/source/{file_id}")
|
@router.head("/projects/{project_id}/source/{file_id}")
|
||||||
def source_upload_state(project_id: str, file_id: str) -> Response:
|
def source_upload_state(project_id: str, file_id: str) -> Response:
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -201,7 +201,7 @@ def test_service_materializes_world_manifest_and_deletes_both_copies(tmp_path: P
|
|||||||
1, 0, 0, 0, 0, -1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1,
|
1, 0, 0, 0, 0, -1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1,
|
||||||
]
|
]
|
||||||
assert ready["world_manifest"]["transforms"]["world_from_collision"] == [
|
assert ready["world_manifest"]["transforms"]["world_from_collision"] == [
|
||||||
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1,
|
-1, 0, 0, 0, 0, 1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1,
|
||||||
]
|
]
|
||||||
assert provider.upload_calls == 1
|
assert provider.upload_calls == 1
|
||||||
assert provider.submit_calls == 1
|
assert provider.submit_calls == 1
|
||||||
@@ -216,7 +216,7 @@ def test_service_materializes_world_manifest_and_deletes_both_copies(tmp_path: P
|
|||||||
"seed_position": [0, 1, 0],
|
"seed_position": [0, 1, 0],
|
||||||
"capsule_height": 1.6,
|
"capsule_height": 1.6,
|
||||||
"capsule_radius": 0.2,
|
"capsule_radius": 0.2,
|
||||||
"voxel_size": 0.05,
|
"voxel_size": 0.1,
|
||||||
"mesh_shape": "smooth",
|
"mesh_shape": "smooth",
|
||||||
}
|
}
|
||||||
service.delete(project["project_id"])
|
service.delete(project["project_id"])
|
||||||
@@ -281,6 +281,41 @@ def test_failed_project_retries_from_retained_source_and_releases_old_job(tmp_pa
|
|||||||
assert queued["source"]["uploaded_byte_length"] == queued["source"]["total_byte_length"]
|
assert queued["source"]["uploaded_byte_length"] == queued["source"]["total_byte_length"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_ready_project_rebuilds_from_retained_source_and_preserves_viewer_settings(
|
||||||
|
tmp_path: Path,
|
||||||
|
) -> None:
|
||||||
|
store = SimulationProjectStore(tmp_path)
|
||||||
|
project = store.create(
|
||||||
|
name="Rebuild scene",
|
||||||
|
scene_type="outdoor",
|
||||||
|
source_kind="folder",
|
||||||
|
files=_folder_files(),
|
||||||
|
)
|
||||||
|
_upload_all(store, project)
|
||||||
|
store.begin_build(project["project_id"])
|
||||||
|
provider = _ReadyProvider()
|
||||||
|
service = SimulationProjectService(
|
||||||
|
store,
|
||||||
|
provider_factory=lambda: provider,
|
||||||
|
) # type: ignore[arg-type]
|
||||||
|
service.process(project["project_id"])
|
||||||
|
settings = {
|
||||||
|
"schema_version": "missioncore.simulation-viewer-settings/v1",
|
||||||
|
"quality": "medium",
|
||||||
|
"visual": {"inverted": True, "axis": "z"},
|
||||||
|
"collision": {"inverted": True, "axis": "y"},
|
||||||
|
"camera": {"invert_horizontal": False, "invert_vertical": True},
|
||||||
|
}
|
||||||
|
store.update_viewer_settings(project["project_id"], settings)
|
||||||
|
|
||||||
|
queued = service.begin_build(project["project_id"])
|
||||||
|
|
||||||
|
assert provider.deleted == ["gsp-20260826000000-deadbeef"]
|
||||||
|
assert queued["status"] == "queued"
|
||||||
|
assert queued["viewer_settings"] == settings
|
||||||
|
assert queued["source"]["uploaded_byte_length"] == queued["source"]["total_byte_length"]
|
||||||
|
|
||||||
|
|
||||||
def test_api_exposes_same_origin_resumable_upload_contract(tmp_path: Path) -> None:
|
def test_api_exposes_same_origin_resumable_upload_contract(tmp_path: Path) -> None:
|
||||||
store = SimulationProjectStore(tmp_path)
|
store = SimulationProjectStore(tmp_path)
|
||||||
service = SimulationProjectService(store, provider_factory=lambda: None)
|
service = SimulationProjectService(store, provider_factory=lambda: None)
|
||||||
@@ -296,6 +331,13 @@ def test_api_exposes_same_origin_resumable_upload_contract(tmp_path: Path) -> No
|
|||||||
})
|
})
|
||||||
assert created.status_code == 201
|
assert created.status_code == 201
|
||||||
project = created.json()
|
project = created.json()
|
||||||
|
assert project["viewer_settings"] == {
|
||||||
|
"schema_version": "missioncore.simulation-viewer-settings/v1",
|
||||||
|
"quality": "high",
|
||||||
|
"visual": {"inverted": True, "axis": "x"},
|
||||||
|
"collision": {"inverted": True, "axis": "y"},
|
||||||
|
"camera": {"invert_horizontal": True, "invert_vertical": False},
|
||||||
|
}
|
||||||
source_file = project["source"]["files"][0]
|
source_file = project["source"]["files"][0]
|
||||||
upload_url = (
|
upload_url = (
|
||||||
f"/api/v1/simulation-worlds/projects/{project['project_id']}"
|
f"/api/v1/simulation-worlds/projects/{project['project_id']}"
|
||||||
@@ -311,3 +353,21 @@ def test_api_exposes_same_origin_resumable_upload_contract(tmp_path: Path) -> No
|
|||||||
assert second.status_code == 204
|
assert second.status_code == 204
|
||||||
catalog = client.get("/api/v1/simulation-worlds/projects").json()
|
catalog = client.get("/api/v1/simulation-worlds/projects").json()
|
||||||
assert catalog["projects"][0]["project_id"] == project["project_id"]
|
assert catalog["projects"][0]["project_id"] == project["project_id"]
|
||||||
|
|
||||||
|
settings = {
|
||||||
|
"schema_version": "missioncore.simulation-viewer-settings/v1",
|
||||||
|
"quality": "medium",
|
||||||
|
"visual": {"inverted": True, "axis": "z"},
|
||||||
|
"collision": {"inverted": False, "axis": "x"},
|
||||||
|
"camera": {"invert_horizontal": False, "invert_vertical": True},
|
||||||
|
}
|
||||||
|
saved = client.put(
|
||||||
|
f"/api/v1/simulation-worlds/projects/{project['project_id']}/viewer-settings",
|
||||||
|
json=settings,
|
||||||
|
)
|
||||||
|
assert saved.status_code == 200
|
||||||
|
assert saved.json()["viewer_settings"] == settings
|
||||||
|
persisted = client.get(
|
||||||
|
f"/api/v1/simulation-worlds/projects/{project['project_id']}"
|
||||||
|
).json()
|
||||||
|
assert persisted["viewer_settings"] == settings
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from k1link.perception.geometry_math import Kb4ProjectionProfile
|
||||||
|
from k1link.perception.spatial_evidence import project_metric_obstacles_to_camera
|
||||||
|
|
||||||
|
|
||||||
|
def _identity_profile() -> Kb4ProjectionProfile:
|
||||||
|
transform = np.eye(4, dtype=np.float64)
|
||||||
|
transform.setflags(write=False)
|
||||||
|
return Kb4ProjectionProfile(
|
||||||
|
width=800,
|
||||||
|
height=600,
|
||||||
|
intrinsic_fx_fy_cx_cy=(100.0, 100.0, 400.0, 300.0),
|
||||||
|
distortion_kb4=(0.0, 0.0, 0.0, 0.0),
|
||||||
|
t_camera_from_lidar=transform,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_metric_obstacle_projection_uses_native_kb4_voxel_bounds() -> None:
|
||||||
|
projections = project_metric_obstacles_to_camera(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"component_id": "static-a",
|
||||||
|
"cells": [{"x": -1, "y": -1, "z": 2}],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"component_id": "baseline-b",
|
||||||
|
"cells": [{"x": 1, "y": 1, "z": 2}],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
position_map_xyz=(0.0, 0.0, 0.0),
|
||||||
|
orientation_map_from_lidar_xyzw=(0.0, 0.0, 0.0, 1.0),
|
||||||
|
profile=_identity_profile(),
|
||||||
|
occupied_voxel_size_m=1.0,
|
||||||
|
component_ids={"static-a"},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert set(projections) == {"static-a"}
|
||||||
|
projection = projections["static-a"]
|
||||||
|
assert projection["projection"] == "factory-kb4-occupied-voxel-bounds"
|
||||||
|
assert projection["authority"] == "visual-derived"
|
||||||
|
assert projection["projected_cell_count"] == 1
|
||||||
|
assert projection["nearest_depth_m"] == pytest.approx(2.0)
|
||||||
|
left, top, right, bottom = projection["bbox_xyxy"]
|
||||||
|
assert left < right == pytest.approx(400.0)
|
||||||
|
assert top < bottom == pytest.approx(300.0)
|
||||||
Reference in New Issue
Block a user