diff options
| -rw-r--r-- | public/App.js | 31 | ||||
| -rw-r--r-- | public/index.html | 10 | ||||
| -rw-r--r-- | public/vendor/addons/libs/draco/draco_decoder.wasm | bin | 0 -> 192420 bytes | |||
| -rw-r--r-- | public/vendor/addons/libs/draco/draco_wasm_wrapper.js | 116 | ||||
| -rw-r--r-- | public/vendor/addons/loaders/DRACOLoader.js | 613 | ||||
| -rw-r--r-- | public/vendor/three-setup.js | 2 | ||||
| -rw-r--r-- | src/SomhairlesDream.Frontend/App.fs | 32 | ||||
| -rw-r--r-- | src/SomhairlesDream.Frontend/Bindings.fs | 9 | ||||
| -rw-r--r-- | src/SomhairlesDream.Modeling/Definitions.fs | 102 | ||||
| -rw-r--r-- | src/SomhairlesDream.Server/Program.fs | 13 | ||||
| -rw-r--r-- | stage/blender/ship.py | 1705 |
11 files changed, 1861 insertions, 772 deletions
diff --git a/public/App.js b/public/App.js index a91f298..7e73305 100644 --- a/public/App.js +++ b/public/App.js @@ -1,8 +1,8 @@ import { Union, Record } from "./fable_modules/fable-library-js.4.24.0/Types.js"; import { union_type, obj_type, class_type, record_type, bool_type, string_type } from "./fable_modules/fable-library-js.4.24.0/Reflection.js"; -import { comparePrimitives, defaultOf, equals, createAtom } from "./fable_modules/fable-library-js.4.24.0/Util.js"; +import { defaultOf, equals, comparePrimitives, createAtom } from "./fable_modules/fable-library-js.4.24.0/Util.js"; import { Replay_frames } from "./SomhairlesDream.Shared/Replay.js"; -import { sortBy, map, equalsWith, tryFind, iterateIndexed, item } from "./fable_modules/fable-library-js.4.24.0/Array.js"; +import { sortBy, map, equalsWith, tryFind, iterateIndexed, sort, item } from "./fable_modules/fable-library-js.4.24.0/Array.js"; import { join, isNullOrWhiteSpace, printf, toText } from "./fable_modules/fable-library-js.4.24.0/String.js"; import { MeshData_triangleIndices, MeshData_positionData } from "./SomhairlesDream.Shared/Domain.js"; import { min, max } from "./fable_modules/fable-library-js.4.24.0/Double.js"; @@ -253,19 +253,23 @@ export function frameObject(view, node) { const size = new THREE.Vector3(); box.getCenter(center); box.getSize(size); - const radius = 0.5 * size.length(); - if (radius > 1E-06) { + const axes = sort(new Float64Array([size.x, size.y, size.z]), { + Compare: comparePrimitives, + }); + const longest = item(2, axes); + const smallest = item(0, axes); + if (longest > 1E-06) { node.position.set(-center.x, -center.y, -center.z); const camera = view.Camera; - const aspect = max(1, view.Canvas.clientWidth) / max(1, view.Canvas.clientHeight); + const aspect = max(0.3, max(1, view.Canvas.clientWidth) / max(1, view.Canvas.clientHeight)); camera.aspect = aspect; const fovY = (camera.fov * 3.141592653589793) / 180; - const limitingFov = min(fovY, 2 * Math.atan(Math.tan(fovY / 2) * aspect)); - const distance = (1.28 * radius) / Math.sin(limitingFov / 2); - const norm = Math.sqrt(((0.62 * 0.62) + (0.45 * 0.45)) + (0.7 * 0.7)); - camera.position.set((0.62 / norm) * distance, (0.45 / norm) * distance, (0.7 / norm) * distance); - camera.near = max(distance * 0.01, distance - (radius * 2.5)); - camera.far = (distance + (radius * 4)); + const tanHalf = Math.tan(fovY / 2); + const distance = 1.25 * max((0.5 * longest) / (tanHalf * aspect), (0.5 * smallest) / tanHalf); + const norm = Math.sqrt(((0.3 * 0.3) + (0.44 * 0.44)) + (0.85 * 0.85)); + camera.position.set((0.3 / norm) * distance, (0.44 / norm) * distance, (0.85 / norm) * distance); + camera.near = max(1, distance * 0.05); + camera.far = (distance + (longest * 3)); camera.lookAt(0, 0, 0); camera.updateProjectionMatrix(); const matchValue_3 = view.Controls; @@ -438,6 +442,9 @@ export function startStepLoad(view, owner, historical) { loadedStep(undefined); document.getElementById("viewport-loading").classList.toggle('is-hidden', false); const loader = new GLTFLoader(); + const draco = new DRACOLoader(); + draco.setDecoderPath("/vendor/addons/libs/draco/"); + loader.setDRACOLoader(draco); const projectId = encodeURIComponent(owner.ProjectId); const runId = encodeURIComponent(owner.RunId); const stamp = ~~(Date.now() % 100000000) | 0; @@ -884,7 +891,7 @@ export function inputValue(id) { export const probeSelection = new RunSelection("probe-001", "run-probe-real-2", "probe-base-000", "probe-target-007", false); -export const shipSelection = new RunSelection("ship-001", "run-ship-001", "ship-base-000", "ship-target-007", false); +export const shipSelection = new RunSelection("ship-001", "run-ship-002", "ship-base-000", "ship-target-007", false); export let activeModel = createAtom(probeSelection); diff --git a/public/index.html b/public/index.html index b52705c..84dd147 100644 --- a/public/index.html +++ b/public/index.html @@ -14,7 +14,7 @@ } } </script> - <script type="module" src="/vendor/three-setup.js?v=orbit1"></script> + <script type="module" src="/vendor/three-setup.js?v=draco1"></script> </head> <body> <main class="shell"> @@ -49,9 +49,9 @@ <span class="model-name">YJ-01 盘形探测器</span> <span class="model-desc">无人星际探测器 · 光帆/RTG · 7 步构建</span> </button> - <button type="button" class="model-card" data-project="ship-001" data-run="run-ship-001" data-base="ship-base-000" data-target="ship-target-007"> - <span class="model-name">HNX-01 "Keel" 载人星舰</span> - <span class="model-desc">旋转 habitat 环 · 水屏蔽 · 聚变火炬 · 7 步构建</span> + <button type="button" class="model-card" data-project="ship-001" data-run="run-ship-002" data-base="ship-base-000" data-target="ship-target-007"> + <span class="model-name">HNX-02 "Copernicus" 载人行星际飞船</span> + <span class="model-desc">核热推进 · 旋转离心机 · 水墙屏蔽 · 7 步构建</span> </button> </div> <div class="version-block"> @@ -140,6 +140,6 @@ <span>heritage 参考线保留占位演示 · F# / OPEN CODE</span> </footer> </main> - <script type="module" src="/App.js?v=ui7"></script> + <script type="module" src="/App.js?v=ui10"></script> </body> </html> diff --git a/public/vendor/addons/libs/draco/draco_decoder.wasm b/public/vendor/addons/libs/draco/draco_decoder.wasm Binary files differnew file mode 100644 index 0000000..469904e --- /dev/null +++ b/public/vendor/addons/libs/draco/draco_decoder.wasm diff --git a/public/vendor/addons/libs/draco/draco_wasm_wrapper.js b/public/vendor/addons/libs/draco/draco_wasm_wrapper.js new file mode 100644 index 0000000..43f1556 --- /dev/null +++ b/public/vendor/addons/libs/draco/draco_wasm_wrapper.js @@ -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); diff --git a/public/vendor/addons/loaders/DRACOLoader.js b/public/vendor/addons/loaders/DRACOLoader.js new file mode 100644 index 0000000..b176ebf --- /dev/null +++ b/public/vendor/addons/loaders/DRACOLoader.js @@ -0,0 +1,613 @@ +import { + BufferAttribute, + BufferGeometry, + Color, + FileLoader, + Loader, + LinearSRGBColorSpace, + SRGBColorSpace +} from 'three'; + +const _taskCache = new WeakMap(); + +class DRACOLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + this.decoderPath = ''; + this.decoderConfig = {}; + this.decoderBinary = null; + this.decoderPending = null; + + this.workerLimit = 4; + this.workerPool = []; + this.workerNextTaskID = 1; + this.workerSourceURL = ''; + + this.defaultAttributeIDs = { + position: 'POSITION', + normal: 'NORMAL', + color: 'COLOR', + uv: 'TEX_COORD' + }; + this.defaultAttributeTypes = { + position: 'Float32Array', + normal: 'Float32Array', + color: 'Float32Array', + uv: 'Float32Array' + }; + + } + + setDecoderPath( path ) { + + this.decoderPath = path; + + return this; + + } + + setDecoderConfig( config ) { + + this.decoderConfig = config; + + return this; + + } + + setWorkerLimit( workerLimit ) { + + this.workerLimit = workerLimit; + + return this; + + } + + load( url, onLoad, onProgress, onError ) { + + const loader = new FileLoader( this.manager ); + + loader.setPath( this.path ); + loader.setResponseType( 'arraybuffer' ); + loader.setRequestHeader( this.requestHeader ); + loader.setWithCredentials( this.withCredentials ); + + loader.load( url, ( buffer ) => { + + this.parse( buffer, onLoad, onError ); + + }, onProgress, onError ); + + } + + + parse( buffer, onLoad, onError = ()=>{} ) { + + this.decodeDracoFile( buffer, onLoad, null, null, SRGBColorSpace ).catch( onError ); + + } + + decodeDracoFile( buffer, callback, attributeIDs, attributeTypes, vertexColorSpace = LinearSRGBColorSpace, onError = () => {} ) { + + const taskConfig = { + attributeIDs: attributeIDs || this.defaultAttributeIDs, + attributeTypes: attributeTypes || this.defaultAttributeTypes, + useUniqueIDs: !! attributeIDs, + vertexColorSpace: vertexColorSpace, + }; + + return this.decodeGeometry( buffer, taskConfig ).then( callback ).catch( onError ); + + } + + decodeGeometry( buffer, taskConfig ) { + + const taskKey = JSON.stringify( taskConfig ); + + // Check for an existing task using this buffer. A transferred buffer cannot be transferred + // again from this thread. + if ( _taskCache.has( buffer ) ) { + + const cachedTask = _taskCache.get( buffer ); + + if ( cachedTask.key === taskKey ) { + + return cachedTask.promise; + + } else if ( buffer.byteLength === 0 ) { + + // Technically, it would be possible to wait for the previous task to complete, + // transfer the buffer back, and decode again with the second configuration. That + // is complex, and I don't know of any reason to decode a Draco buffer twice in + // different ways, so this is left unimplemented. + throw new Error( + + 'THREE.DRACOLoader: Unable to re-decode a buffer with different ' + + 'settings. Buffer has already been transferred.' + + ); + + } + + } + + // + + let worker; + const taskID = this.workerNextTaskID ++; + const taskCost = buffer.byteLength; + + // Obtain a worker and assign a task, and construct a geometry instance + // when the task completes. + const geometryPending = this._getWorker( taskID, taskCost ) + .then( ( _worker ) => { + + worker = _worker; + + return new Promise( ( resolve, reject ) => { + + worker._callbacks[ taskID ] = { resolve, reject }; + + worker.postMessage( { type: 'decode', id: taskID, taskConfig, buffer }, [ buffer ] ); + + // this.debug(); + + } ); + + } ) + .then( ( message ) => this._createGeometry( message.geometry ) ); + + // Remove task from the task list. + // Note: replaced '.finally()' with '.catch().then()' block - iOS 11 support (#19416) + geometryPending + .catch( () => true ) + .then( () => { + + if ( worker && taskID ) { + + this._releaseTask( worker, taskID ); + + // this.debug(); + + } + + } ); + + // Cache the task result. + _taskCache.set( buffer, { + + key: taskKey, + promise: geometryPending + + } ); + + return geometryPending; + + } + + _createGeometry( geometryData ) { + + const geometry = new BufferGeometry(); + + if ( geometryData.index ) { + + geometry.setIndex( new BufferAttribute( geometryData.index.array, 1 ) ); + + } + + for ( let i = 0; i < geometryData.attributes.length; i ++ ) { + + const result = geometryData.attributes[ i ]; + const name = result.name; + const array = result.array; + const itemSize = result.itemSize; + + const attribute = new BufferAttribute( array, itemSize ); + + if ( name === 'color' ) { + + this._assignVertexColorSpace( attribute, result.vertexColorSpace ); + + attribute.normalized = ( array instanceof Float32Array ) === false; + + } + + geometry.setAttribute( name, attribute ); + + } + + return geometry; + + } + + _assignVertexColorSpace( attribute, inputColorSpace ) { + + // While .drc files do not specify colorspace, the only 'official' tooling + // is PLY and OBJ converters, which use sRGB. We'll assume sRGB when a .drc + // file is passed into .load() or .parse(). GLTFLoader uses internal APIs + // to decode geometry, and vertex colors are already Linear-sRGB in there. + + if ( inputColorSpace !== SRGBColorSpace ) return; + + const _color = new Color(); + + for ( let i = 0, il = attribute.count; i < il; i ++ ) { + + _color.fromBufferAttribute( attribute, i ).convertSRGBToLinear(); + attribute.setXYZ( i, _color.r, _color.g, _color.b ); + + } + + } + + _loadLibrary( url, responseType ) { + + const loader = new FileLoader( this.manager ); + loader.setPath( this.decoderPath ); + loader.setResponseType( responseType ); + loader.setWithCredentials( this.withCredentials ); + + return new Promise( ( resolve, reject ) => { + + loader.load( url, resolve, undefined, reject ); + + } ); + + } + + preload() { + + this._initDecoder(); + + return this; + + } + + _initDecoder() { + + if ( this.decoderPending ) return this.decoderPending; + + const useJS = typeof WebAssembly !== 'object' || this.decoderConfig.type === 'js'; + const librariesPending = []; + + if ( useJS ) { + + librariesPending.push( this._loadLibrary( 'draco_decoder.js', 'text' ) ); + + } else { + + librariesPending.push( this._loadLibrary( 'draco_wasm_wrapper.js', 'text' ) ); + librariesPending.push( this._loadLibrary( 'draco_decoder.wasm', 'arraybuffer' ) ); + + } + + this.decoderPending = Promise.all( librariesPending ) + .then( ( libraries ) => { + + const jsContent = libraries[ 0 ]; + + if ( ! useJS ) { + + this.decoderConfig.wasmBinary = libraries[ 1 ]; + + } + + const fn = DRACOWorker.toString(); + + const body = [ + '/* draco decoder */', + jsContent, + '', + '/* worker */', + fn.substring( fn.indexOf( '{' ) + 1, fn.lastIndexOf( '}' ) ) + ].join( '\n' ); + + this.workerSourceURL = URL.createObjectURL( new Blob( [ body ] ) ); + + } ); + + return this.decoderPending; + + } + + _getWorker( taskID, taskCost ) { + + return this._initDecoder().then( () => { + + if ( this.workerPool.length < this.workerLimit ) { + + const worker = new Worker( this.workerSourceURL ); + + worker._callbacks = {}; + worker._taskCosts = {}; + worker._taskLoad = 0; + + worker.postMessage( { type: 'init', decoderConfig: this.decoderConfig } ); + + worker.onmessage = function ( e ) { + + const message = e.data; + + switch ( message.type ) { + + case 'decode': + worker._callbacks[ message.id ].resolve( message ); + break; + + case 'error': + worker._callbacks[ message.id ].reject( message ); + break; + + default: + console.error( 'THREE.DRACOLoader: Unexpected message, "' + message.type + '"' ); + + } + + }; + + this.workerPool.push( worker ); + + } else { + + this.workerPool.sort( function ( a, b ) { + + return a._taskLoad > b._taskLoad ? - 1 : 1; + + } ); + + } + + const worker = this.workerPool[ this.workerPool.length - 1 ]; + worker._taskCosts[ taskID ] = taskCost; + worker._taskLoad += taskCost; + return worker; + + } ); + + } + + _releaseTask( worker, taskID ) { + + worker._taskLoad -= worker._taskCosts[ taskID ]; + delete worker._callbacks[ taskID ]; + delete worker._taskCosts[ taskID ]; + + } + + debug() { + + console.log( 'Task load: ', this.workerPool.map( ( worker ) => worker._taskLoad ) ); + + } + + dispose() { + + for ( let i = 0; i < this.workerPool.length; ++ i ) { + + this.workerPool[ i ].terminate(); + + } + + this.workerPool.length = 0; + + if ( this.workerSourceURL !== '' ) { + + URL.revokeObjectURL( this.workerSourceURL ); + + } + + return this; + + } + +} + +/* WEB WORKER */ + +function DRACOWorker() { + + let decoderConfig; + let decoderPending; + + onmessage = function ( e ) { + + const message = e.data; + + switch ( message.type ) { + + case 'init': + decoderConfig = message.decoderConfig; + decoderPending = new Promise( function ( resolve/*, reject*/ ) { + + decoderConfig.onModuleLoaded = function ( draco ) { + + // Module is Promise-like. Wrap before resolving to avoid loop. + resolve( { draco: draco } ); + + }; + + DracoDecoderModule( decoderConfig ); // eslint-disable-line no-undef + + } ); + break; + + case 'decode': + const buffer = message.buffer; + const taskConfig = message.taskConfig; + decoderPending.then( ( module ) => { + + const draco = module.draco; + const decoder = new draco.Decoder(); + + try { + + const geometry = decodeGeometry( draco, decoder, new Int8Array( buffer ), taskConfig ); + + const buffers = geometry.attributes.map( ( attr ) => attr.array.buffer ); + + if ( geometry.index ) buffers.push( geometry.index.array.buffer ); + + self.postMessage( { type: 'decode', id: message.id, geometry }, buffers ); + + } catch ( error ) { + + console.error( error ); + + self.postMessage( { type: 'error', id: message.id, error: error.message } ); + + } finally { + + draco.destroy( decoder ); + + } + + } ); + break; + + } + + }; + + function decodeGeometry( draco, decoder, array, taskConfig ) { + + const attributeIDs = taskConfig.attributeIDs; + const attributeTypes = taskConfig.attributeTypes; + + let dracoGeometry; + let decodingStatus; + + const geometryType = decoder.GetEncodedGeometryType( array ); + + if ( geometryType === draco.TRIANGULAR_MESH ) { + + dracoGeometry = new draco.Mesh(); + decodingStatus = decoder.DecodeArrayToMesh( array, array.byteLength, dracoGeometry ); + + } else if ( geometryType === draco.POINT_CLOUD ) { + + dracoGeometry = new draco.PointCloud(); + decodingStatus = decoder.DecodeArrayToPointCloud( array, array.byteLength, dracoGeometry ); + + } else { + + throw new Error( 'THREE.DRACOLoader: Unexpected geometry type.' ); + + } + + if ( ! decodingStatus.ok() || dracoGeometry.ptr === 0 ) { + + throw new Error( 'THREE.DRACOLoader: Decoding failed: ' + decodingStatus.error_msg() ); + + } + + const geometry = { index: null, attributes: [] }; + + // Gather all vertex attributes. + for ( const attributeName in attributeIDs ) { + + const attributeType = self[ attributeTypes[ attributeName ] ]; + + let attribute; + let attributeID; + + // A Draco file may be created with default vertex attributes, whose attribute IDs + // are mapped 1:1 from their semantic name (POSITION, NORMAL, ...). Alternatively, + // a Draco file may contain a custom set of attributes, identified by known unique + // IDs. glTF files always do the latter, and `.drc` files typically do the former. + if ( taskConfig.useUniqueIDs ) { + + attributeID = attributeIDs[ attributeName ]; + attribute = decoder.GetAttributeByUniqueId( dracoGeometry, attributeID ); + + } else { + + attributeID = decoder.GetAttributeId( dracoGeometry, draco[ attributeIDs[ attributeName ] ] ); + + if ( attributeID === - 1 ) continue; + + attribute = decoder.GetAttribute( dracoGeometry, attributeID ); + + } + + const attributeResult = decodeAttribute( draco, decoder, dracoGeometry, attributeName, attributeType, attribute ); + + if ( attributeName === 'color' ) { + + attributeResult.vertexColorSpace = taskConfig.vertexColorSpace; + + } + + geometry.attributes.push( attributeResult ); + + } + + // Add index. + if ( geometryType === draco.TRIANGULAR_MESH ) { + + geometry.index = decodeIndex( draco, decoder, dracoGeometry ); + + } + + draco.destroy( dracoGeometry ); + + return geometry; + + } + + function decodeIndex( draco, decoder, dracoGeometry ) { + + const numFaces = dracoGeometry.num_faces(); + const numIndices = numFaces * 3; + const byteLength = numIndices * 4; + + const ptr = draco._malloc( byteLength ); + decoder.GetTrianglesUInt32Array( dracoGeometry, byteLength, ptr ); + const index = new Uint32Array( draco.HEAPF32.buffer, ptr, numIndices ).slice(); + draco._free( ptr ); + + return { array: index, itemSize: 1 }; + + } + + function decodeAttribute( draco, decoder, dracoGeometry, attributeName, attributeType, attribute ) { + + const numComponents = attribute.num_components(); + const numPoints = dracoGeometry.num_points(); + const numValues = numPoints * numComponents; + const byteLength = numValues * attributeType.BYTES_PER_ELEMENT; + const dataType = getDracoDataType( draco, attributeType ); + + const ptr = draco._malloc( byteLength ); + decoder.GetAttributeDataArrayForAllPoints( dracoGeometry, attribute, dataType, byteLength, ptr ); + const array = new attributeType( draco.HEAPF32.buffer, ptr, numValues ).slice(); + draco._free( ptr ); + + return { + name: attributeName, + array: array, + itemSize: numComponents + }; + + } + + function getDracoDataType( draco, attributeType ) { + + switch ( attributeType ) { + + case Float32Array: return draco.DT_FLOAT32; + case Int8Array: return draco.DT_INT8; + case Int16Array: return draco.DT_INT16; + case Int32Array: return draco.DT_INT32; + case Uint8Array: return draco.DT_UINT8; + case Uint16Array: return draco.DT_UINT16; + case Uint32Array: return draco.DT_UINT32; + + } + + } + +} + +export { DRACOLoader }; diff --git a/public/vendor/three-setup.js b/public/vendor/three-setup.js index 7fe818a..c69cc2e 100644 --- a/public/vendor/three-setup.js +++ b/public/vendor/three-setup.js @@ -1,7 +1,9 @@ import * as THREE from 'three'; import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; +import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js'; window.THREE = THREE; window.GLTFLoader = GLTFLoader; window.OrbitControls = OrbitControls; +window.DRACOLoader = DRACOLoader; diff --git a/src/SomhairlesDream.Frontend/App.fs b/src/SomhairlesDream.Frontend/App.fs index 82bae88..72d1be2 100644 --- a/src/SomhairlesDream.Frontend/App.fs +++ b/src/SomhairlesDream.Frontend/App.fs @@ -184,25 +184,33 @@ let frameObject (view: ViewState) (node: ThreeNode) = let size = createVector3 () box.getCenter center |> ignore box.getSize size |> ignore - let radius = 0.5 * size.length () + // Fit the longest axis horizontally (it runs across the screen in the + // side-on hero camera) and the next-longest vertically. + let axes = [| size.x; size.y; size.z |] |> Array.sort + let longest = axes.[2] + let smallest = axes.[0] - if radius > 0.000001 then + if longest > 0.000001 then node.position.set(-center.x, -center.y, -center.z) |> ignore let camera = view.Camera let width = max 1.0 (float view.Canvas.clientWidth) let height = max 1.0 (float view.Canvas.clientHeight) - let aspect = width / height + let aspect = max 0.3 (width / height) camera.aspect <- aspect let fovY = camera.fov * Math.PI / 180.0 - let fovX = 2.0 * atan (tan (fovY / 2.0) * aspect) - let limitingFov = min fovY fovX - let distance = 1.28 * radius / sin (limitingFov / 2.0) - let dirX, dirY, dirZ = 0.62, 0.45, 0.70 + let tanHalf = tan (fovY / 2.0) + // Longest axis lies across the screen; the smallest axis is the + // one stacking vertically in a side-on view of a slender vehicle. + let halfW = 0.5 * longest / (tanHalf * aspect) + let halfH = 0.5 * smallest / tanHalf + let distance = 1.25 * max halfW halfH + // Side-on hero camera: look across the hull, not down its nose. + let dirX, dirY, dirZ = 0.30, 0.44, 0.85 let norm = sqrt (dirX * dirX + dirY * dirY + dirZ * dirZ) camera.position.set(dirX / norm * distance, dirY / norm * distance, dirZ / norm * distance) |> ignore - camera.near <- max (distance * 0.01) (distance - radius * 2.5) - camera.far <- distance + radius * 4.0 + camera.near <- max 1.0 (distance * 0.05) + camera.far <- distance + longest * 3.0 camera.lookAt(0.0, 0.0, 0.0) camera.updateProjectionMatrix () @@ -374,6 +382,10 @@ let startStepLoad (view: ViewState) (owner: LoadOwner) (historical: bool) = loadedStep <- None toggleHidden ("viewport-loading", false) let loader = createGLTFLoader () + // Attach the Draco decoder so geometry-compressed GLBs stream in small. + let draco = createDRACOLoader () + setDracoDecoderPath draco "/vendor/addons/libs/draco/" + attachDracoLoader loader draco let projectId = JS.encodeURIComponent owner.ProjectId let runId = JS.encodeURIComponent owner.RunId // Cache-buster: the GLB is always re-fetched from the server. @@ -723,7 +735,7 @@ let probeSelection : RunSelection = let shipSelection : RunSelection = { ProjectId = "ship-001" - RunId = "run-ship-001" + RunId = "run-ship-002" BaseId = "ship-base-000" TargetId = "ship-target-007" Render = false } diff --git a/src/SomhairlesDream.Frontend/Bindings.fs b/src/SomhairlesDream.Frontend/Bindings.fs index 7f0ce31..05d8e95 100644 --- a/src/SomhairlesDream.Frontend/Bindings.fs +++ b/src/SomhairlesDream.Frontend/Bindings.fs @@ -59,6 +59,15 @@ type ServerEvent = type GLTFScene = abstract scene: ThreeNode with get +[<Emit("new DRACOLoader()")>] +let createDRACOLoader () : obj = jsNative + +[<Emit("$0.setDecoderPath($1)")>] +let setDracoDecoderPath (draco: obj) (path: string) : unit = jsNative + +[<Emit("$0.setDRACOLoader($1)")>] +let attachDracoLoader (loader: obj) (draco: obj) : unit = jsNative + [<Emit("new GLTFLoader()")>] let createGLTFLoader () : obj = jsNative diff --git a/src/SomhairlesDream.Modeling/Definitions.fs b/src/SomhairlesDream.Modeling/Definitions.fs index e1d2739..44e1998 100644 --- a/src/SomhairlesDream.Modeling/Definitions.fs +++ b/src/SomhairlesDream.Modeling/Definitions.fs @@ -92,87 +92,65 @@ module Design = ObjectIds = prior (prior (prior (prior (prior (prior bus booms) rtgs) hga) instruments) mast) [||] Changes = liveryChanges } |] - // HNX-01 crewed interstellar starship (Concept A "Keel"): fusion-torch - // study with a spin-gravity habitat torus, water-shadow radiation - // shield and an axial keel separating crew from the reactor section. - // Display-scale visual research; no mass/thrust/shielding closure is - // claimed — the ship.py module header lists the deliberately open - // engineering problems. + // HNX-02 "Copernicus-class" crewed interplanetary transfer vehicle: + // nuclear-thermal propulsion stage, spin-gravity centrifuge ring, + // water-wall radiation shielding, Whipple MMOD shielding, and an + // Orion-class shuttle docked as the scale reference. Built from + // procedural engineering structures (trusses, pressure vessels, bell + // nozzles), not stacked primitives. Display-scale visual research; + // no mass/thrust/shielding closure is claimed. let shipSteps : DesignStep array = let keel = - [| "starship.keel.spine"; "starship.nose.cone"; "starship.dock.ring" |] + [| "hnx02.hull.core"; "hnx02.spine.truss"; "hnx02.node.forward" |] let hab = - [| "starship.hab.torus"; "starship.hub.core" - "starship.spoke.00"; "starship.spoke.01"; "starship.spoke.02" - "starship.spoke.03"; "starship.spoke.04"; "starship.spoke.05" |] + [| "hnx02.hab.a"; "hnx02.hab.b"; "hnx02.hab.whipple" |] let radiation = - [| "starship.shadow.collar"; "starship.storm.cellar" |] + [| "hnx02.rad.waterwall"; "hnx02.centrifuge.ring" |] let reactor = - [| "starship.core.pressure"; "starship.core.shield.plug" - "starship.nozzle.magnetic" - "starship.radiator.00"; "starship.radiator.01"; "starship.radiator.02" - "starship.radiator.03"; "starship.radiator.04"; "starship.radiator.05" |] + [| "hnx02.prop.tank"; "hnx02.reactor.core"; "hnx02.thrust.cone" + "hnx02.nozzle.cluster" |] let power = - [| "starship.power.truss"; "starship.capacitor.ring" - "starship.comm.dish"; "starship.comm.feed" |] + [| "hnx02.power.solar"; "hnx02.power.radiator" |] let cabin = - [| "starship.window.00"; "starship.window.01"; "starship.window.02" - "starship.window.03" - "starship.airlock.ring"; "starship.airlock.tunnel" - "starship.cargo.pod.00"; "starship.cargo.pod.01" |] + [| "hnx02.cabin.airlock"; "hnx02.shuttle.orion" |] + let livery = + [| "hnx02.detail.greeble"; "hnx02.detail.antenna" |] let prior (prefix: string array) (current: string array) = Array.append prefix current let keelChanges = - [| { ObjectId = "starship.keel.spine"; Kind = "add"; Summary = "Add axial keel spine from bow to nozzle bay" } - { ObjectId = "starship.nose.cone"; Kind = "add"; Summary = "Add forward nose cone" } - { ObjectId = "starship.dock.ring"; Kind = "add"; Summary = "Add forward docking ring" } |] + [| { ObjectId = "hnx02.hull.core"; Kind = "add"; Summary = "Add continuous 16 m pressure hull with viewport bands and ring frames" } + { ObjectId = "hnx02.spine.truss"; Kind = "add"; Summary = "Add outrigger trusses with utility conduits and equipment rows" } + { ObjectId = "hnx02.node.forward"; Kind = "add"; Summary = "Add five-port forward docking node" } |] let habChanges = - Array.append - [| { ObjectId = "starship.hab.torus"; Kind = "add"; Summary = "Add rotating spin-gravity habitat torus" } - { ObjectId = "starship.hub.core"; Kind = "add"; Summary = "Add zero-g hub with bearing core" } |] - (Array.init 6 (fun index -> - { ObjectId = sprintf "starship.spoke.%02d" index; Kind = "add"; Summary = "Add pressurized transfer spoke" })) + [| { ObjectId = "hnx02.hab.a"; Kind = "add"; Summary = "Add forward habitat module with viewport rows" } + { ObjectId = "hnx02.hab.b"; Kind = "add"; Summary = "Add aft habitat module" } + { ObjectId = "hnx02.hab.whipple"; Kind = "add"; Summary = "Add Whipple MMOD shield panels" } |] let radiationChanges = - [| { ObjectId = "starship.shadow.collar"; Kind = "add"; Summary = "Add water-shadow radiation collar" } - { ObjectId = "starship.storm.cellar"; Kind = "add"; Summary = "Add storm cellar shelter volume" } |] + [| { ObjectId = "hnx02.rad.waterwall"; Kind = "add"; Summary = "Add water-wall radiation shield tanks" } + { ObjectId = "hnx02.centrifuge.ring"; Kind = "add"; Summary = "Add spin-gravity centrifuge ring with crew cars" } |] let reactorChanges = - Array.append - [| { ObjectId = "starship.core.pressure"; Kind = "add"; Summary = "Add fusion core pressure vessel" } - { ObjectId = "starship.core.shield.plug"; Kind = "add"; Summary = "Add reactor shield plug" } - { ObjectId = "starship.nozzle.magnetic"; Kind = "add"; Summary = "Add magnetic exhaust nozzle" } |] - (Array.init 6 (fun index -> - { ObjectId = sprintf "starship.radiator.%02d" index; Kind = "add"; Summary = "Add waste-heat radiator panel" })) + [| { ObjectId = "hnx02.prop.tank"; Kind = "add"; Summary = "Add cryogenic propellant tanks with MLI blankets" } + { ObjectId = "hnx02.reactor.core"; Kind = "add"; Summary = "Add reactor core with control drums" } + { ObjectId = "hnx02.thrust.cone"; Kind = "add"; Summary = "Add thrust structure and shadow shield" } + { ObjectId = "hnx02.nozzle.cluster"; Kind = "add"; Summary = "Add nuclear-thermal engine cluster" } |] let powerChanges = - [| { ObjectId = "starship.power.truss"; Kind = "add"; Summary = "Add power conditioning truss" } - { ObjectId = "starship.capacitor.ring"; Kind = "add"; Summary = "Add pulsed-power capacitor hoop" } - { ObjectId = "starship.comm.dish"; Kind = "add"; Summary = "Add high-gain communication dish" } - { ObjectId = "starship.comm.feed"; Kind = "add"; Summary = "Add communication feed horn" } |] + [| { ObjectId = "hnx02.power.solar"; Kind = "add"; Summary = "Add deployable solar arrays" } + { ObjectId = "hnx02.power.radiator"; Kind = "add"; Summary = "Add finned heat-rejection radiators" } |] let cabinChanges = - Array.append - (Array.init 4 (fun index -> - { ObjectId = sprintf "starship.window.%02d" index; Kind = "add"; Summary = "Add habitat observation window" })) - [| { ObjectId = "starship.airlock.ring"; Kind = "add"; Summary = "Add forward airlock ring" } - { ObjectId = "starship.airlock.tunnel"; Kind = "add"; Summary = "Add pressurized airlock tunnel" } - { ObjectId = "starship.cargo.pod.00"; Kind = "add"; Summary = "Add port cargo pod" } - { ObjectId = "starship.cargo.pod.01"; Kind = "add"; Summary = "Add starboard cargo pod" } |] + [| { ObjectId = "hnx02.cabin.airlock"; Kind = "add"; Summary = "Add EVA airlock with external hatch" } + { ObjectId = "hnx02.shuttle.orion"; Kind = "add"; Summary = "Add docked Orion-class crew shuttle" } |] let liveryChanges = - [| { ObjectId = "starship.keel.spine"; Kind = "update"; Summary = "Livery pass: bone-white hull structure" } - { ObjectId = "starship.hab.torus"; Kind = "update"; Summary = "Livery pass: habitat ring bone finish" } - { ObjectId = "starship.shadow.collar"; Kind = "update"; Summary = "Livery pass: ceramic shield darkening" } - { ObjectId = "starship.storm.cellar"; Kind = "update"; Summary = "Livery pass: ceramic cellar finish" } - { ObjectId = "starship.dock.ring"; Kind = "update"; Summary = "Livery pass: amber accent on dock ring" } - { ObjectId = "starship.capacitor.ring"; Kind = "update"; Summary = "Livery pass: amber capacitor hoop" } - { ObjectId = "starship.window.00"; Kind = "update"; Summary = "Livery pass: glass glazing" } - { ObjectId = "starship.nozzle.magnetic"; Kind = "update"; Summary = "Livery pass: nozzle coil graphite" } |] + [| { ObjectId = "hnx02.detail.greeble"; Kind = "add"; Summary = "Add equipment rows, cable looms and navigation lights" } + { ObjectId = "hnx02.detail.antenna"; Kind = "add"; Summary = "Add high-gain communication antennas" } |] [| { StepId = "ship-01-keel"; Index = 0; ObjectIds = keel; Changes = keelChanges } - { StepId = "ship-02-hab"; Index = 1; ObjectIds = Array.append keel hab; Changes = habChanges } - { StepId = "ship-03-radiation"; Index = 2; ObjectIds = Array.append (Array.append keel hab) radiation; Changes = radiationChanges } - { StepId = "ship-04-reactor"; Index = 3; ObjectIds = Array.append (Array.append (Array.append keel hab) radiation) reactor; Changes = reactorChanges } - { StepId = "ship-05-power"; Index = 4; ObjectIds = Array.append (Array.append (Array.append (Array.append keel hab) radiation) reactor) power; Changes = powerChanges } - { StepId = "ship-06-cabin"; Index = 5; ObjectIds = Array.append (Array.append (Array.append (Array.append (Array.append keel hab) radiation) reactor) power) cabin; Changes = cabinChanges } - { StepId = "ship-07-livery"; Index = 6; ObjectIds = Array.append (Array.append (Array.append (Array.append (Array.append (Array.append keel hab) radiation) reactor) power) cabin) [||]; Changes = liveryChanges } |] + { StepId = "ship-02-hab"; Index = 1; ObjectIds = prior keel hab; Changes = habChanges } + { StepId = "ship-03-radiation"; Index = 2; ObjectIds = prior (prior keel hab) radiation; Changes = radiationChanges } + { StepId = "ship-04-reactor"; Index = 3; ObjectIds = prior (prior (prior keel hab) radiation) reactor; Changes = reactorChanges } + { StepId = "ship-05-power"; Index = 4; ObjectIds = prior (prior (prior (prior keel hab) radiation) reactor) power; Changes = powerChanges } + { StepId = "ship-06-cabin"; Index = 5; ObjectIds = prior (prior (prior (prior (prior keel hab) radiation) reactor) power) cabin; Changes = cabinChanges } + { StepId = "ship-07-livery"; Index = 6; ObjectIds = prior (prior (prior (prior (prior (prior keel hab) radiation) reactor) power) cabin) livery; Changes = liveryChanges } |] let forProject (projectId: string) : DesignStep array = if projectId.StartsWith("probe-") then probeSteps diff --git a/src/SomhairlesDream.Server/Program.fs b/src/SomhairlesDream.Server/Program.fs index 53cfef6..b2ea8a8 100644 --- a/src/SomhairlesDream.Server/Program.fs +++ b/src/SomhairlesDream.Server/Program.fs @@ -6,6 +6,7 @@ open System.Threading.Tasks open Microsoft.AspNetCore.Builder open Microsoft.AspNetCore.Hosting open Microsoft.AspNetCore.Http +open Microsoft.AspNetCore.StaticFiles open Microsoft.Extensions.FileProviders open Microsoft.Extensions.Hosting open SomhairlesDream.Server @@ -57,7 +58,17 @@ let events (context: HttpContext) : Task = } :> Task) app.UseDefaultFiles() |> ignore -app.UseStaticFiles() |> ignore + +// Serve geometry and decoder binaries with correct MIME types: ASP.NET's +// default map has no entry for .glb/.wasm, so those requests would 404. +let staticFileOptions = + let provider = FileExtensionContentTypeProvider() + provider.Mappings.[".glb"] <- "model/gltf-binary" + provider.Mappings.[".gltf"] <- "model/gltf+json" + provider.Mappings.[".wasm"] <- "application/wasm" + StaticFileOptions(ContentTypeProvider = provider) + +app.UseStaticFiles(staticFileOptions) |> ignore app.MapGet("/health", Func<IResult>(fun () -> Results.Ok({| status = "ok" |}))) |> ignore diff --git a/stage/blender/ship.py b/stage/blender/ship.py index e1dd30c..0f30d54 100644 --- a/stage/blender/ship.py +++ b/stage/blender/ship.py @@ -1,697 +1,1051 @@ -"""HNX-01 crewed interstellar starship dataset for the artifact pipeline. +"""HNX-02 "Copernicus-class" crewed interplanetary transfer vehicle. -Concept A "Keel" — a crewed fusion-torch starship study built on the same -Blender contract as the YJ-01 probe dataset (stage/blender/probe.py): +Rebuild of the crewed starship with procedural engineering structures +(space trusses, pressure vessels, habitat modules, a spin-gravity +centrifuge, an NTP engine cluster, and a docked Orion-class shuttle as +the scale reference) instead of stacked primitives. --step-id <id> --output <glb> --result-output <json> [--render-output <png>] -Design intent (visual research, NOT a closed engineering design): - - Long axial keel so the crew habitat sits as far as practical from the - reactor section (inverse-square separation for neutron/gamma dose). - - Rotating habitat torus for continuous spin gravity; six spokes carry - the pressurized transfer tunnels; the hub is the zero-g docking core. - - Heavy water-shadow shield collar and a storm cellar between the - habitat and the reactor. - - Aft fusion core with a magnetic nozzle; six droop radiators reject the - waste heat. These hardware positions are placeholders — the script - does not claim thrust, burn time, shielding closure, or life-support - closure. - -Object ids: starship.<part>[.<index>], lowercase dotted, stable per step. +Design intent (display-scale visual research, NOT closed engineering): + - Nuclear-thermal propulsion (NERVA-class stainless/epoxy composite + fuel element heritage) with a shadow shield between reactor and crew. + - Axial layout keeps the crew section as far from the reactor as the + truss allows; the water-wall tanks double as radiation shielding. + - The centrifuge ring provides spin gravity; cars are the pressurized + crew volumes. No mass/thrust/shielding closure is claimed. + +Object ids: hnx02.<part>[.<index>], lowercase dotted, stable per step. Cumulative object lists must stay in lockstep with Design.shipSteps in src/SomhairlesDream.Modeling/Definitions.fs. """ - import argparse import json import math import os +import random import sys -from datetime import datetime, timezone -import bpy # noqa: F401 (must be imported before mathutils: registers the bundled mathutils module) +import bpy # noqa: F401 (must be imported first: registers bundled bmesh/mathutils) +import bmesh from mathutils import Vector +# --------------------------------------------------------------- contract + STEP_OBJECTS = { "ship-01-keel": [ - "starship.keel.spine", - "starship.nose.cone", - "starship.dock.ring", + "hnx02.hull.core", + "hnx02.spine.truss", + "hnx02.node.forward", ], "ship-02-hab": [ - "starship.keel.spine", - "starship.nose.cone", - "starship.dock.ring", - "starship.hab.torus", - "starship.hub.core", - "starship.spoke.00", - "starship.spoke.01", - "starship.spoke.02", - "starship.spoke.03", - "starship.spoke.04", - "starship.spoke.05", + "hnx02.hull.core", + "hnx02.spine.truss", + "hnx02.node.forward", + "hnx02.hab.a", + "hnx02.hab.b", + "hnx02.hab.whipple", ], "ship-03-radiation": [ - "starship.keel.spine", - "starship.nose.cone", - "starship.dock.ring", - "starship.hab.torus", - "starship.hub.core", - "starship.spoke.00", - "starship.spoke.01", - "starship.spoke.02", - "starship.spoke.03", - "starship.spoke.04", - "starship.spoke.05", - "starship.shadow.collar", - "starship.storm.cellar", + "hnx02.hull.core", + "hnx02.spine.truss", + "hnx02.node.forward", + "hnx02.hab.a", + "hnx02.hab.b", + "hnx02.hab.whipple", + "hnx02.rad.waterwall", + "hnx02.centrifuge.ring", ], "ship-04-reactor": [ - "starship.keel.spine", - "starship.nose.cone", - "starship.dock.ring", - "starship.hab.torus", - "starship.hub.core", - "starship.spoke.00", - "starship.spoke.01", - "starship.spoke.02", - "starship.spoke.03", - "starship.spoke.04", - "starship.spoke.05", - "starship.shadow.collar", - "starship.storm.cellar", - "starship.core.pressure", - "starship.core.shield.plug", - "starship.nozzle.magnetic", - "starship.radiator.00", - "starship.radiator.01", - "starship.radiator.02", - "starship.radiator.03", - "starship.radiator.04", - "starship.radiator.05", + "hnx02.hull.core", + "hnx02.spine.truss", + "hnx02.node.forward", + "hnx02.hab.a", + "hnx02.hab.b", + "hnx02.hab.whipple", + "hnx02.rad.waterwall", + "hnx02.centrifuge.ring", + "hnx02.prop.tank", + "hnx02.reactor.core", + "hnx02.thrust.cone", + "hnx02.nozzle.cluster", ], "ship-05-power": [ - "starship.keel.spine", - "starship.nose.cone", - "starship.dock.ring", - "starship.hab.torus", - "starship.hub.core", - "starship.spoke.00", - "starship.spoke.01", - "starship.spoke.02", - "starship.spoke.03", - "starship.spoke.04", - "starship.spoke.05", - "starship.shadow.collar", - "starship.storm.cellar", - "starship.core.pressure", - "starship.core.shield.plug", - "starship.nozzle.magnetic", - "starship.radiator.00", - "starship.radiator.01", - "starship.radiator.02", - "starship.radiator.03", - "starship.radiator.04", - "starship.radiator.05", - "starship.power.truss", - "starship.capacitor.ring", - "starship.comm.dish", - "starship.comm.feed", + "hnx02.hull.core", + "hnx02.spine.truss", + "hnx02.node.forward", + "hnx02.hab.a", + "hnx02.hab.b", + "hnx02.hab.whipple", + "hnx02.rad.waterwall", + "hnx02.centrifuge.ring", + "hnx02.prop.tank", + "hnx02.reactor.core", + "hnx02.thrust.cone", + "hnx02.nozzle.cluster", + "hnx02.power.solar", + "hnx02.power.radiator", ], "ship-06-cabin": [ - "starship.keel.spine", - "starship.nose.cone", - "starship.dock.ring", - "starship.hab.torus", - "starship.hub.core", - "starship.spoke.00", - "starship.spoke.01", - "starship.spoke.02", - "starship.spoke.03", - "starship.spoke.04", - "starship.spoke.05", - "starship.shadow.collar", - "starship.storm.cellar", - "starship.core.pressure", - "starship.core.shield.plug", - "starship.nozzle.magnetic", - "starship.radiator.00", - "starship.radiator.01", - "starship.radiator.02", - "starship.radiator.03", - "starship.radiator.04", - "starship.radiator.05", - "starship.power.truss", - "starship.capacitor.ring", - "starship.comm.dish", - "starship.comm.feed", - "starship.window.00", - "starship.window.01", - "starship.window.02", - "starship.window.03", - "starship.airlock.ring", - "starship.airlock.tunnel", - "starship.cargo.pod.00", - "starship.cargo.pod.01", + "hnx02.hull.core", + "hnx02.spine.truss", + "hnx02.node.forward", + "hnx02.hab.a", + "hnx02.hab.b", + "hnx02.hab.whipple", + "hnx02.rad.waterwall", + "hnx02.centrifuge.ring", + "hnx02.prop.tank", + "hnx02.reactor.core", + "hnx02.thrust.cone", + "hnx02.nozzle.cluster", + "hnx02.power.solar", + "hnx02.power.radiator", + "hnx02.cabin.airlock", + "hnx02.shuttle.orion", ], "ship-07-livery": [ - "starship.keel.spine", - "starship.nose.cone", - "starship.dock.ring", - "starship.hab.torus", - "starship.hub.core", - "starship.spoke.00", - "starship.spoke.01", - "starship.spoke.02", - "starship.spoke.03", - "starship.spoke.04", - "starship.spoke.05", - "starship.shadow.collar", - "starship.storm.cellar", - "starship.core.pressure", - "starship.core.shield.plug", - "starship.nozzle.magnetic", - "starship.radiator.00", - "starship.radiator.01", - "starship.radiator.02", - "starship.radiator.03", - "starship.radiator.04", - "starship.radiator.05", - "starship.power.truss", - "starship.capacitor.ring", - "starship.comm.dish", - "starship.comm.feed", - "starship.window.00", - "starship.window.01", - "starship.window.02", - "starship.window.03", - "starship.airlock.ring", - "starship.airlock.tunnel", - "starship.cargo.pod.00", - "starship.cargo.pod.01", + "hnx02.hull.core", + "hnx02.spine.truss", + "hnx02.node.forward", + "hnx02.hab.a", + "hnx02.hab.b", + "hnx02.hab.whipple", + "hnx02.rad.waterwall", + "hnx02.centrifuge.ring", + "hnx02.prop.tank", + "hnx02.reactor.core", + "hnx02.thrust.cone", + "hnx02.nozzle.cluster", + "hnx02.power.solar", + "hnx02.power.radiator", + "hnx02.cabin.airlock", + "hnx02.shuttle.orion", + "hnx02.detail.greeble", + "hnx02.detail.antenna", ], } -LIVERY_STEPS = {"ship-07-livery"} +# Material keys per object id. +OBJECT_MATERIAL = { + "hnx02.hull.core": "white", + "hnx02.spine.truss": "hull", + "hnx02.node.forward": "white", + "hnx02.hab.a": "white", + "hnx02.hab.b": "white", + "hnx02.hab.whipple": "shield", + "hnx02.rad.waterwall": "hull", + "hnx02.centrifuge.ring": "hull", + "hnx02.reactor.core": "dark", + "hnx02.prop.tank": "gold", + "hnx02.thrust.cone": "hull", + "hnx02.nozzle.cluster": "dark", + "hnx02.power.solar": "pv", + "hnx02.power.radiator": "radiator", + "hnx02.cabin.airlock": "white", + "hnx02.shuttle.orion": "white", + "hnx02.detail.greeble": "hull", + "hnx02.detail.antenna": "hull", +} def utc_now(): - return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z") + from datetime import datetime, timezone + + return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") def fail(message, code): - print(message, file=sys.stderr) - raise SystemExit(code) + print(json.dumps({"status": "failed", "message": message})) + sys.exit(code) -def rgba(rgb, alpha=1.0): - return (*rgb, alpha) +# --------------------------------------------------------------- materials -def make_material(bpy, name, color, *, metallic=0.0, roughness=0.45, emission=None, emission_strength=0.0): - material = bpy.data.materials.get(name) or bpy.data.materials.new(name) +def make_material(bpy, name, color, *, metallic=0.0, roughness=0.45): + material = bpy.data.materials.get(name) + if material is not None: + return material + material = bpy.data.materials.new(name) material.use_nodes = True - material.diffuse_color = rgba(color) - shader = material.node_tree.nodes.get("Principled BSDF") - shader.inputs["Base Color"].default_value = rgba(color) - shader.inputs["Metallic"].default_value = metallic - shader.inputs["Roughness"].default_value = roughness - if emission is not None: - emission_input = shader.inputs.get("Emission Color") or shader.inputs.get("Emission") - if emission_input: - emission_input.default_value = rgba(emission) - strength_input = shader.inputs.get("Emission Strength") - if strength_input: - strength_input.default_value = emission_strength + bsdf = material.node_tree.nodes.get("Principled BSDF") + bsdf.inputs["Base Color"].default_value = (*color, 1.0) + bsdf.inputs["Metallic"].default_value = metallic + bsdf.inputs["Roughness"].default_value = roughness return material def build_materials(bpy): return { - "clay_body": make_material(bpy, "CLAY BODY", (0.38, 0.42, 0.46), metallic=0.20, roughness=0.38), - "clay_structure": make_material(bpy, "CLAY STRUCTURE", (0.17, 0.21, 0.25), metallic=0.65, roughness=0.32), - "clay_dark": make_material(bpy, "CLAY DARK", (0.045, 0.055, 0.065), metallic=0.45, roughness=0.28), - "clay_white": make_material(bpy, "CLAY REFLECTOR", (0.62, 0.65, 0.67), metallic=0.18, roughness=0.34), - "clay_glass": make_material(bpy, "CLAY GLASS", (0.035, 0.10, 0.13), metallic=0.12, roughness=0.14, emission=(0.01, 0.05, 0.07), emission_strength=0.15), - "livery_hull": make_material(bpy, "LIVERY HULL BONE", (0.72, 0.70, 0.64), metallic=0.30, roughness=0.30), - "livery_structure": make_material(bpy, "LIVERY STRUCTURE", (0.13, 0.18, 0.22), metallic=0.78, roughness=0.24), - "livery_dark": make_material(bpy, "LIVERY GRAPHITE", (0.018, 0.025, 0.032), metallic=0.70, roughness=0.24), - "livery_ceramic": make_material(bpy, "LIVERY SHADOW CERAMIC", (0.10, 0.115, 0.125), metallic=0.25, roughness=0.42), - "livery_amber": make_material(bpy, "LIVERY AMBER STRIPE", (0.62, 0.34, 0.05), metallic=0.25, roughness=0.30), - "livery_white": make_material(bpy, "LIVERY DISH WHITE", (0.78, 0.81, 0.79), metallic=0.15, roughness=0.28), - "livery_glass": make_material(bpy, "LIVERY GLASS", (0.008, 0.055, 0.085), metallic=0.20, roughness=0.10, emission=(0.005, 0.045, 0.09), emission_strength=0.20), - "livery_nozzle": make_material(bpy, "LIVERY NOZZLE COIL", (0.16, 0.17, 0.20), metallic=0.85, roughness=0.20), + "hull": make_material(bpy, "hnx02.hull", (0.62, 0.64, 0.66), metallic=0.88, roughness=0.38), + "white": make_material(bpy, "hnx02.white", (0.86, 0.86, 0.84), metallic=0.18, roughness=0.52), + "shield": make_material(bpy, "hnx02.shield", (0.44, 0.46, 0.50), metallic=0.30, roughness=0.72), + "dark": make_material(bpy, "hnx02.dark", (0.10, 0.11, 0.13), metallic=0.72, roughness=0.48), + "radiator": make_material(bpy, "hnx02.radiator", (0.90, 0.91, 0.92), metallic=0.08, roughness=0.30), + "pv": make_material(bpy, "hnx02.pv", (0.07, 0.10, 0.24), metallic=0.35, roughness=0.28), + "gold": make_material(bpy, "hnx02.gold", (0.82, 0.58, 0.19), metallic=1.0, roughness=0.30), + "copper": make_material(bpy, "hnx02.copper", (0.70, 0.44, 0.19), metallic=1.0, roughness=0.34), } -def clear_scene(bpy): - bpy.ops.wm.read_factory_settings(use_empty=True) - +# ------------------------------------------------------------ bmesh kit + + +def basis(axis): + z = Vector(axis).normalized() + ref = Vector((0.0, 0.0, 1.0)) if abs(z.z) < 0.9 else Vector((1.0, 0.0, 0.0)) + x = ref.cross(z).normalized() + y = z.cross(x).normalized() + return x, y, z + + +def cyl(bm, p1, p2, r1, r2=None, seg=12, cap1=True, cap2=True): + p1, p2 = Vector(p1), Vector(p2) + r2 = r1 if r2 is None else r2 + d = p2 - p1 + if d.length < 1e-7: + return + x, y, _ = basis(d) + ring1, ring2 = [], [] + for i in range(seg): + a = 2.0 * math.pi * i / seg + off = x * math.cos(a) + y * math.sin(a) + ring1.append(bm.verts.new(p1 + off * r1)) + ring2.append(bm.verts.new(p2 + off * r2)) + for i in range(seg): + j = (i + 1) % seg + bm.faces.new((ring1[i], ring1[j], ring2[j], ring2[i])) + if cap1: + bm.faces.new(tuple(reversed(ring1))) + if cap2: + bm.faces.new(tuple(ring2)) + + +def torus(bm, center, R, r, axis=(0.0, 0.0, 1.0), seg=24, rseg=8): + center = Vector(center) + x, y, z = basis(axis) + rings = [] + for i in range(seg): + a = 2.0 * math.pi * i / seg + radial = x * math.cos(a) + y * math.sin(a) + c = center + radial * R + ring = [] + for j in range(rseg): + b = 2.0 * math.pi * j / rseg + ring.append(bm.verts.new(c + radial * (r * math.cos(b)) + z * (r * math.sin(b)))) + rings.append(ring) + for i in range(seg): + i2 = (i + 1) % seg + for j in range(rseg): + j2 = (j + 1) % rseg + bm.faces.new((rings[i][j], rings[i][j2], rings[i2][j2], rings[i2][j])) + + +def dome(bm, base, direction, radius, seg=20, layers=5): + d = Vector(direction).normalized() + x, y, _ = basis(d) + prev = None + for i in range(layers + 1): + t = i / layers + phi = t * math.pi / 2.0 + r = radius * math.cos(phi) + h = radius * math.sin(phi) + c = Vector(base) + d * h + if r < 1e-6: + tip = bm.verts.new(c) + if prev: + for j in range(seg): + bm.faces.new((prev[j], prev[(j + 1) % seg], tip)) + break + ring = [] + for j in range(seg): + a = 2.0 * math.pi * j / seg + ring.append(bm.verts.new(c + (x * math.cos(a) + y * math.sin(a)) * r)) + if prev: + for j in range(seg): + j2 = (j + 1) % seg + bm.faces.new((prev[j], prev[j2], ring[j2], ring[j])) + prev = ring + + +def bell(bm, throat, direction, length, r_throat, r_exit, seg=32, layers=14, exponent=1.7): + d = Vector(direction).normalized() + x, y, _ = basis(d) + prev = None + for i in range(layers + 1): + t = i / layers + r = r_throat + (r_exit - r_throat) * (t ** exponent) + c = Vector(throat) + d * (length * t) + ring = [] + for j in range(seg): + a = 2.0 * math.pi * j / seg + ring.append(bm.verts.new(c + (x * math.cos(a) + y * math.sin(a)) * r)) + if prev: + for j in range(seg): + j2 = (j + 1) % seg + bm.faces.new((prev[j], prev[j2], ring[j2], ring[j])) + prev = ring + + +def box(bm, center, ex, ey, ez, ax, ay, az): + c = Vector(center) + ax, ay, az = Vector(ax), Vector(ay), Vector(az) + vs = {} + for sx in (-1, 1): + for sy in (-1, 1): + for sz in (-1, 1): + vs[(sx, sy, sz)] = bm.verts.new(c + ax * (sx * ex) + ay * (sy * ey) + az * (sz * ez)) + quads = [ + (vs[(-1, -1, -1)], vs[(-1, 1, -1)], vs[(-1, 1, 1)], vs[(-1, -1, 1)]), + (vs[(1, 1, -1)], vs[(1, -1, -1)], vs[(1, -1, 1)], vs[(1, 1, 1)]), + (vs[(-1, 1, -1)], vs[(1, 1, -1)], vs[(1, 1, 1)], vs[(-1, 1, 1)]), + (vs[(1, -1, -1)], vs[(-1, -1, -1)], vs[(-1, -1, 1)], vs[(1, -1, 1)]), + (vs[(-1, -1, 1)], vs[(-1, 1, 1)], vs[(1, 1, 1)], vs[(1, -1, 1)]), + (vs[(1, -1, -1)], vs[(1, 1, -1)], vs[(-1, 1, -1)], vs[(-1, -1, -1)]), + ] + for q in quads: + bm.faces.new(q) + + +# ------------------------------------------------------------ structures + + +def truss(bm, p1, p2, width, bays=6, chord_r=0.09, strut_r=0.05, angles=None): + p1, p2 = Vector(p1), Vector(p2) + if angles is None: + angles = [90.0, 210.0, 330.0] + x, y, _ = basis(p2 - p1) + nodes = [] + for deg in angles: + a = math.radians(deg) + off = (x * math.cos(a) + y * math.sin(a)) * width + nodes.append((p1 + off, p2 + off)) + for a, b in nodes: + cyl(bm, a, b, chord_r, seg=8) + n = len(nodes) + for i in range(bays + 1): + t = i / bays + pts = [a.lerp(b, t) for a, b in nodes] + for k in range(n): + cyl(bm, pts[k], pts[(k + 1) % n], strut_r, seg=6) + if i < bays: + t2 = (i + 1) / bays + pts2 = [a.lerp(b, t2) for a, b in nodes] + for k in range(n): + cyl(bm, pts[k], pts2[(k + 1) % n], strut_r, seg=6) + + +def ring_frame(bm, center, radius, axis, tube=0.07, seg=28, spokes=0, spoke_r=0.05): + center = Vector(center) + torus(bm, center, radius, tube, axis=axis, seg=seg, rseg=6) + if spokes: + x, y, _ = basis(axis) + for k in range(spokes): + a = 2.0 * math.pi * k / spokes + radial = x * math.cos(a) + y * math.sin(a) + cyl(bm, center + radial * radius * 0.12, center + radial * radius, spoke_r, seg=6) + + +def pressure_vessel(bm, p1, p2, radius, rings=5, seg=24, ports=True): + p1, p2 = Vector(p1), Vector(p2) + d = (p2 - p1).normalized() + cyl(bm, p1, p2, radius, seg=seg, cap1=False, cap2=False) + dome(bm, p1, -d, radius, seg=seg) + dome(bm, p2, d, radius, seg=seg) + for i in range(1, rings + 1): + t = i / (rings + 1) + torus(bm, p1.lerp(p2, t), radius, 0.06, axis=d, seg=seg, rseg=6) + if ports: + x, y, _ = basis(d) + for t in (0.2, 0.5, 0.8): + c = p1.lerp(p2, t) + off = x * radius + cyl(bm, c + off * 0.88, c + off * 1.45, 0.10, seg=8) + torus(bm, c + off * 1.45, 0.13, 0.04, axis=x, seg=10, rseg=5) + + +def hab_module(bm, p1, p2, radius, ribs=8, windows=10, seg=28, hatch=True): + p1, p2 = Vector(p1), Vector(p2) + d = (p2 - p1).normalized() + x, y, _ = basis(d) + cyl(bm, p1, p2, radius, seg=seg) + for i in range(1, ribs + 1): + t = i / (ribs + 1) + torus(bm, p1.lerp(p2, t), radius, 0.075, axis=d, seg=seg, rseg=6) + length = (p2 - p1).length + for row, tt in ((0, 0.34), (1, 0.66)): + for k in range(windows): + a = 2.0 * math.pi * k / windows + row * math.pi / windows + radial = x * math.cos(a) + y * math.sin(a) + c = p1.lerp(p2, tt) + radial * radius + cyl(bm, c - radial * 0.08, c + radial * 0.14, 0.17, seg=12, cap1=False) + torus(bm, c + radial * 0.14, 0.185, 0.04, axis=radial, seg=14, rseg=5) + box(bm, c + radial * 0.16, 0.10, 0.30, 0.012, radial, d, d.cross(radial)) + if hatch: + for end, dirv in ((p2, d), (p1, -d)): + torus(bm, end + dirv * 0.05, radius * 0.52, 0.11, axis=d, seg=seg, rseg=6) + for k in range(8): + a = 2.0 * math.pi * k / 8 + radial = x * math.cos(a) + y * math.sin(a) + cyl(bm, end + radial * radius * 0.52 + dirv * 0.05, + end + radial * radius * 0.70 + dirv * 0.05, 0.05, seg=6) + + +def centrifuge_ring(bm, center, radius, axis=(0.0, 1.0, 0.0), cars=6, seg=44): + """Rotating gravity ring: twin truss hoops, spokes, suspended cars.""" + center = Vector(center) + x, y, z = basis(axis) + for off in (-0.55, 0.55): + c = center + z * off + torus(bm, c, radius, 0.085, axis=z, seg=seg, rseg=6) + torus(bm, c, radius * 0.995, 0.045, axis=z, seg=seg, rseg=5) + # cross bracing between the two hoops + for k in range(0, seg, 2): + a = 2.0 * math.pi * k / seg + a2 = 2.0 * math.pi * (k + 1) / seg + radial = x * math.cos(a) + y * math.sin(a) + radial2 = x * math.cos(a2) + y * math.sin(a2) + p_a0 = center + radial * radius + z * -0.55 + p_a1 = center + radial * radius + z * 0.55 + p_b0 = center + radial2 * radius + z * -0.55 + p_b1 = center + radial2 * radius + z * 0.55 + cyl(bm, p_a0, p_b1, 0.035, seg=5) + cyl(bm, p_a1, p_b0, 0.035, seg=5) + cyl(bm, p_a0, p_a1, 0.035, seg=5) + # spokes to hub + for k in range(cars): + a = 2.0 * math.pi * k / cars + radial = x * math.cos(a) + y * math.sin(a) + outer = center + radial * radius + cyl(bm, center, outer, 0.17, seg=10) + for tang in (-1.0, 1.0): + tangent = z * tang + p0 = center + radial * radius * 0.18 + tangent * 0.7 + p1 = center + radial * radius * 0.55 + tangent * 0.7 + cyl(bm, p0, p1, 0.07, seg=6) + cyl(bm, center + radial * radius * 0.5 - z * 0.9, center + radial * radius * 0.5 + z * 0.9, 0.09, seg=6) + # de-spin bearing rings at the hub + for zz in (-0.9, 0.9): + torus(bm, center + z * zz, radius * 0.135, 0.11, axis=z, seg=28, rseg=6) + # suspended cars + for k in range(cars): + a = 2.0 * math.pi * k / cars + math.pi / cars + radial = x * math.cos(a) + y * math.sin(a) + car = center + radial * (radius + 0.62) + cyl(bm, car - z * 0.85, car + z * 0.85, 0.62, seg=18) + torus(bm, car - z * 0.85, 0.64, 0.07, axis=z, seg=18, rseg=5) + torus(bm, car + z * 0.85, 0.64, 0.07, axis=z, seg=18, rseg=5) + cyl(bm, center + radial * radius, car, 0.075, seg=7) + for kk in range(5): + aa = 2.0 * math.pi * kk / 5 + off = (z * math.cos(aa) + radial * math.sin(aa)) * 0.2 + cyl(bm, car + off, car + off * 2.6, 0.03, seg=5) + + +def gimbal(bm, pos, direction, radius=0.28, s=1.0): + pos = Vector(pos) + d = Vector(direction).normalized() + x, _, _ = basis(d) + torus(bm, pos, radius * s, 0.055 * s, axis=d, seg=16, rseg=5) + torus(bm, pos + d * 0.15 * s, radius * s * 0.9, 0.055 * s, axis=x, seg=16, rseg=5) + for k in (0, 1): + side = 1.0 if k == 0 else -1.0 + cyl(bm, pos + x * side * radius * s * 1.35 - d * 0.12 * s, + pos + x * side * radius * s * 0.8 + d * 0.3 * s, 0.04 * s, seg=6) + + +def regen_channels(bm, throat, direction, length, r_throat, r_exit, turns=6, tube=0.035, seg_pts=15): + throat = Vector(throat) + d = Vector(direction).normalized() + x, y, _ = basis(d) + steps = turns * seg_pts + pts = [] + for i in range(steps + 1): + t = i / steps + r = r_throat + (r_exit - r_throat) * (t ** 1.7) + tube * 1.3 + a = 2.0 * math.pi * turns * t + c = throat + d * (length * t) + pts.append(c + (x * math.cos(a) + y * math.sin(a)) * r) + for a, b in zip(pts, pts[1:]): + cyl(bm, a, b, tube, seg=5) + + +def ntp_engine(bm, mount, direction, scale=1.0): + """Nuclear thermal engine: thrust structure, chamber, bell, turbopump.""" + mount = Vector(mount) + d = Vector(direction).normalized() + s = scale + x, y, _ = basis(d) + cyl(bm, mount, mount + d * 0.5 * s, 0.44 * s, seg=14) + chamber = mount + d * 0.5 * s + cyl(bm, chamber, chamber + d * 0.45 * s, 0.36 * s, 0.31 * s, seg=18) + bell(bm, chamber + d * 0.45 * s, d, 2.3 * s, 0.31 * s, 1.05 * s, seg=32, layers=14) + for t in (0.3, 0.55, 0.78, 0.95): + c = chamber + d * (0.45 * s + 2.3 * s * t) + r = 0.31 * s + (1.05 * s - 0.31 * s) * (t ** 1.7) + torus(bm, c, r + 0.02 * s, 0.032 * s, axis=d, seg=24, rseg=5) + regen_channels(bm, chamber + d * 0.45 * s, d, 2.3 * s, 0.31 * s, 1.05 * s) + # turbopump cluster + pump = mount + d * 0.75 * s + x * 0.5 * s + cyl(bm, pump, pump + d * 0.6 * s, 0.21 * s, seg=12) + cyl(bm, pump - x * 0.22 * s, pump + x * 0.6 * s, 0.10 * s, seg=8) + cyl(bm, chamber + x * 0.32 * s, pump + d * 0.32 * s + x * 0.27 * s, 0.075 * s, seg=7) + cyl(bm, mount + d * 0.18 * s + x * 0.48 * s, chamber + x * 0.32 * s, 0.055 * s, seg=6) + # nozzle exit stiffener ring + exit_c = chamber + d * (0.45 * s + 2.3 * s) + torus(bm, exit_c - d * 0.05 * s, 1.05 * s, 0.05 * s, axis=d, seg=32, rseg=6) + + +def shadow_shield(bm, center, direction, radius, seg=32, ribs=10): + center = Vector(center) + d = Vector(direction).normalized() + x, y, _ = basis(d) + cyl(bm, center - d * 0.12, center + d * 0.12, radius, seg=seg) + for k in range(ribs): + a = 2.0 * math.pi * k / ribs + radial = x * math.cos(a) + y * math.sin(a) + cyl(bm, center + radial * radius * 0.12 - d * 0.12, + center + radial * radius * 0.96 - d * 0.12, 0.05, seg=6) + torus(bm, center, radius, 0.06, axis=d, seg=seg, rseg=6) + for k in range(6): + a = 2.0 * math.pi * k / 6 + radial = x * math.cos(a) + y * math.sin(a) + cyl(bm, center + radial * radius * 0.55, center + radial * radius * 0.55 - d * 1.2, 0.08, seg=7) + + +def thrust_cone(bm, mount, direction, r_base, r_top, length, struts=10): + mount = Vector(mount) + d = Vector(direction).normalized() + x, y, _ = basis(d) + top = mount + d * length + for i, (c, r) in enumerate(((mount, r_base), (top, r_top))): + torus(bm, c, r, 0.06, axis=d, seg=28, rseg=5) + for k in range(struts): + a = 2.0 * math.pi * k / struts + radial = x * math.cos(a) + y * math.sin(a) + cyl(bm, mount + radial * r_base, top + radial * r_top, 0.055, seg=6) + for k in range(struts): + a = 2.0 * math.pi * k / struts + b = 2.0 * math.pi * (k + 1) / struts + ra = x * math.cos(a) + y * math.sin(a) + rb = x * math.cos(b) + y * math.sin(b) + mid = mount + d * (length * 0.5) + cyl(bm, mount + ra * r_base, mid + rb * ((r_base + r_top) * 0.5), 0.04, seg=5) + + +def mli_bands(bm, p1, p2, radius, bands=10, seg=24, wobble=0.035): + p1, p2 = Vector(p1), Vector(p2) + d = (p2 - p1).normalized() + for i in range(1, bands + 1): + t = i / (bands + 1) + c = p1.lerp(p2, t) + w = 1.0 + wobble * math.sin(i * 2.3) + torus(bm, c, radius * w, 0.085, axis=d, seg=seg, rseg=7) + + +def solar_array(bm, root, direction, span, width, cells=72, rows=14, face_normal=(0.0, 0.0, 1.0)): + """Deployable PV blanket: cell grid, frame rails, torque tube, hinge.""" + root = Vector(root) + d = Vector(direction).normalized() + x, _y, _z = basis(d) + fn = Vector(face_normal).normalized() + if abs(fn.dot(d)) > 0.9: + fn = _z + x = d.cross(fn).normalized() + cw = span / cells + ch = width / rows + for i in range(cells): + for j in range(rows): + c = root + d * (cw * (i + 0.5)) + x * ((j - rows * 0.5 + 0.5) * ch) + box(bm, c, cw * 0.40, ch * 0.38, 0.02, d, x, fn) + for side in (-1.0, 1.0): + off = x * side * width * 0.5 + box(bm, root + d * (span * 0.5) + off, span * 0.5, 0.05, 0.05, d, x, fn) + cyl(bm, root - x * width * 0.5, root + x * width * 0.5, 0.10, seg=10) + cyl(bm, root, root - d * 0.6, 0.13, seg=10) + torus(bm, root, 0.24, 0.06, axis=x, seg=16, rseg=5) + + +def fin_radiator(bm, p1, p2, width, fins=48, fin_h=0.42, coolant=True): + p1, p2 = Vector(p1), Vector(p2) + d = (p2 - p1).normalized() + x, y, _ = basis(d) + cyl(bm, p1, p2, width * 0.5, seg=4) + for i in range(fins): + t = (i + 0.5) / fins + c = p1.lerp(p2, t) + for side in (0.5, -0.5): + off = y * side * 0.05 + box(bm, c + off + y * side * fin_h * 0.5, width * 0.46, fin_h * 0.5, 0.014, x, y, d) + if coolant: + for side in (-0.30, 0.0, 0.30): + off = x * (width * side) + cyl(bm, p1 + off, p2 + off, 0.035, seg=6) + + +def pipe_run(bm, points, r=0.06, seg=8): + for a, b in zip(points, points[1:]): + cyl(bm, a, b, r, seg=seg) + + +def rcs_cluster(bm, pos, direction, s=1.0): + pos = Vector(pos) + d = Vector(direction).normalized() + x, y, _ = basis(d) + cyl(bm, pos, pos + d * 0.22 * s, 0.17 * s, seg=12) + for k in range(4): + a = 2.0 * math.pi * k / 4 + math.pi / 4 + off = (x * math.cos(a) + y * math.sin(a)) * 0.14 * s + cyl(bm, pos + d * 0.22 * s + off, pos + d * 0.40 * s + off * 1.35, 0.055 * s, 0.032 * s, seg=6) + + +def antenna_dish(bm, base, direction, radius, depth=0.4, ribs=8, s=1.0): + base = Vector(base) + d = Vector(direction).normalized() + x, y, _ = basis(d) + cyl(bm, base - d * 0.1 * s, base + d * 0.28 * s, 0.16 * s, seg=10) + gimbal(bm, base + d * 0.28 * s, d, radius=0.2 * s, s=s) + dish_center = base + d * 0.45 * s + layers = 5 + prev = None + for i in range(layers + 1): + t = i / layers + r = radius * t + h = depth * (1.0 - t * t) + ring = [] + for j in range(24): + a = 2.0 * math.pi * j / 24 + ring.append(bm.verts.new(dish_center + (x * math.cos(a) + y * math.sin(a)) * r + d * h)) + if prev: + for j in range(24): + j2 = (j + 1) % 24 + bm.faces.new((prev[j], prev[j2], ring[j2], ring[j])) + prev = ring + for k in range(ribs): + a = 2.0 * math.pi * k / ribs + radial = x * math.cos(a) + y * math.sin(a) + cyl(bm, dish_center + radial * radius * 0.06, dish_center + radial * radius * 0.92 + d * 0.02, 0.03 * s, seg=5) + feed = dish_center + d * (depth + 1.15 * s * 0.6) + cyl(bm, dish_center + d * 0.06, feed, 0.035 * s, seg=6) + cyl(bm, feed - d * 0.06 * s, feed + d * 0.10 * s, 0.09 * s, seg=8) + for k in range(3): + a = 2.0 * math.pi * k / 3 + radial = x * math.cos(a) + y * math.sin(a) + cyl(bm, dish_center + radial * radius * 0.8 + d * depth * 0.9, feed, 0.022 * s, seg=5) + + +def whipple_panels(bm, p1, p2, radius, rows=6, cols=14, gap=0.04): + """MMOD shield: arc panels in a regular grid over a cylinder.""" + p1, p2 = Vector(p1), Vector(p2) + d = (p2 - p1).normalized() + x, y, _ = basis(d) + length = (p2 - p1).length + for i in range(cols): + t = (i + 0.5) / cols + for j in range(rows): + a = 2.0 * math.pi * (j + 0.5) / rows + radial = x * math.cos(a) + y * math.sin(a) + tangent = d.cross(radial) + c = p1 + d * (length * t) + radial * (radius + 0.09) + box(bm, c, length / cols * 0.44, radius * math.pi / rows * 0.42, 0.035, + d, tangent, radial) + + +def equipment_row(bm, start, direction, lateral, count, spacing, s_lo, s_hi, seed, lateral_axis=None): + """A deliberately arranged row of machinery boxes (not noise).""" + rng = random.Random(seed) + start = Vector(start) + direction = Vector(direction).normalized() + lateral = Vector(lateral).normalized() + ax = lateral_axis if lateral_axis is not None else direction.cross(lateral).normalized() + for i in range(count): + t = (i - (count - 1) * 0.5) * spacing + base = start + direction * t + w = rng.uniform(s_lo, s_hi) + h = rng.uniform(s_lo, s_hi) + dep = rng.uniform(s_lo, s_hi * 1.3) + box(bm, base + lateral * (dep * 0.5), w, h, dep * 0.5, direction, ax, lateral) + if rng.random() < 0.4: + cyl(bm, base + lateral * dep * 0.3 - ax * w * 0.6, + base + lateral * (dep * 0.5) - ax * w * 0.6, w * 0.18, seg=6) + + +def panel_bank(bm, p1, p2, radius, cols=16, rows=8, depth=0.05): + """Regular rectangular panel grid over a cylinder (machined surface look).""" + p1, p2 = Vector(p1), Vector(p2) + d = (p2 - p1).normalized() + x, y, _ = basis(d) + length = (p2 - p1).length + for i in range(cols): + t = (i + 0.5) / cols + for j in range(rows): + a = 2.0 * math.pi * (j + 0.5) / rows + radial = x * math.cos(a) + y * math.sin(a) + tangent = d.cross(radial) + c = p1 + d * (length * t) + radial * radius + box(bm, c, length / cols * 0.42, radius * math.pi / rows * 0.40, depth, + d, tangent, radial) + + +# --------------------------------------------------------------- assembly -def assign_variants(obj, clay_material, livery_material): - obj["object_id"] = obj.name - obj["clay_material"] = clay_material.name - obj["livery_material"] = livery_material.name - obj.data.materials.clear() - obj.data.materials.append(clay_material) - return obj - -def apply_material_mode(bpy, materials, mode): - if mode not in {"clay", "livery"}: - raise ValueError(f"unknown material mode: {mode}") - for obj in bpy.context.scene.objects: - material_name = obj.get(f"{mode}_material") - if not material_name or not obj.data or not hasattr(obj.data, "materials"): - continue - material = bpy.data.materials.get(material_name) - if material is None: - raise RuntimeError(f"missing material {material_name!r} for {obj.name}") - obj.data.materials.clear() - obj.data.materials.append(material) - - -def add_beveled_cube(bpy, name, location, dimensions, clay_material, livery_material, bevel=0.04, rotation=None): - bpy.ops.mesh.primitive_cube_add(location=location, rotation=rotation or (0.0, 0.0, 0.0)) - obj = bpy.context.object - obj.name = name - obj.data.name = name + ".mesh" - obj.dimensions = dimensions - bpy.ops.object.transform_apply(location=False, rotation=False, scale=True) - if bevel: - modifier = obj.modifiers.new("Structural edge softening", "BEVEL") - modifier.width = bevel - modifier.segments = 3 - return assign_variants(obj, clay_material, livery_material) - - -def add_cylinder(bpy, name, location, radius, depth, clay_material, livery_material, *, rotation=(0.0, 0.0, 0.0), vertices=32): - bpy.ops.mesh.primitive_cylinder_add( - vertices=vertices, - radius=radius, - depth=depth, - location=location, - rotation=rotation, - ) - obj = bpy.context.object - obj.name = name - obj.data.name = name + ".mesh" - return assign_variants(obj, clay_material, livery_material) - - -def add_cone(bpy, name, location, radius1, radius2, depth, clay_material, livery_material, *, rotation=(0.0, 0.0, 0.0)): - bpy.ops.mesh.primitive_cone_add( - vertices=24, - radius1=radius1, - radius2=radius2, - depth=depth, - location=location, - rotation=rotation, - ) - obj = bpy.context.object - obj.name = name - obj.data.name = name + ".mesh" - return assign_variants(obj, clay_material, livery_material) - - -def add_torus(bpy, name, location, major_radius, minor_radius, clay_material, livery_material, *, rotation=(0.0, 0.0, 0.0), minor_segments=12): - bpy.ops.mesh.primitive_torus_add( - major_radius=major_radius, - minor_radius=minor_radius, - major_segments=64, - minor_segments=minor_segments, - location=location, - rotation=rotation, - ) - obj = bpy.context.object - obj.name = name - obj.data.name = name + ".mesh" - return assign_variants(obj, clay_material, livery_material) - - -def add_between(bpy, name, start, end, radius, clay_material, livery_material, vertices=20): - start_vec = Vector(start) - end_vec = Vector(end) - delta = end_vec - start_vec - obj = add_cylinder( - bpy, - name, - (start_vec + end_vec) / 2.0, - radius, - delta.length, - clay_material, - livery_material, - vertices=vertices, - ) - obj.rotation_euler = delta.to_track_quat("Z", "Y").to_euler() - return obj +def clear_scene(bpy): + bpy.ops.wm.read_factory_settings(use_empty=True) -def add_dish(bpy, name, base_x, center_y, center_z, radius, depth, clay_material, livery_material): - """Create a shallow open paraboloid whose boresight points along +X.""" - segments = 64 - rings = 9 - vertices = [(base_x, center_y, center_z)] - for ring in range(1, rings + 1): - fraction = ring / rings - ring_radius = radius * fraction - ring_x = base_x + depth * fraction * fraction - for segment in range(segments): - angle = 2.0 * math.pi * segment / segments - vertices.append( - ( - ring_x, - center_y + ring_radius * math.cos(angle), - center_z + ring_radius * math.sin(angle), - ) - ) - faces = [] - for segment in range(segments): - faces.append((0, 1 + segment, 1 + (segment + 1) % segments)) - for ring in range(1, rings): - first = 1 + (ring - 1) * segments - next_first = first + segments - for segment in range(segments): - current = first + segment - following = first + (segment + 1) % segments - next_current = next_first + segment - next_following = next_first + (segment + 1) % segments - faces.append((current, next_current, next_following, following)) - mesh = bpy.data.meshes.new(name + ".mesh") - mesh.from_pydata(vertices, [], faces) - mesh.update() +def build_object(bpy, name, material, builder): + bm = bmesh.new() + builder(bm) + bmesh.ops.recalc_face_normals(bm, faces=bm.faces[:]) + mesh = bpy.data.meshes.new(name) + bm.to_mesh(mesh) + bm.free() obj = bpy.data.objects.new(name, mesh) bpy.context.collection.objects.link(obj) - assign_variants(obj, clay_material, livery_material) - solidify = obj.modifiers.new("Reflector thickness", "SOLIDIFY") - solidify.thickness = 0.012 - return obj - - - - -def merge_details(bpy, master, extras): - """Join detail meshes into the master object so the exported GLB keeps - exactly the contract object names while carrying real secondary detail.""" - if not extras: - return master - for obj in bpy.context.selected_objects: - obj.select_set(False) - for extra in extras: - extra.select_set(True) - master.select_set(True) - bpy.context.view_layer.objects.active = master - bpy.ops.object.join() - return master - - -def detail_torus(bpy, name, location, major_radius, minor_radius, rotation=(0.0, 0.0, 0.0), minor_segments=10): - bpy.ops.mesh.primitive_torus_add( - major_radius=major_radius, - minor_radius=minor_radius, - major_segments=48, - minor_segments=minor_segments, - location=location, - rotation=rotation, - ) - obj = bpy.context.object - obj.name = name - return obj - - -def detail_cube(bpy, name, location, dimensions, rotation=(0.0, 0.0, 0.0)): - bpy.ops.mesh.primitive_cube_add(location=location, rotation=rotation) - obj = bpy.context.object - obj.name = name - obj.dimensions = dimensions - bpy.ops.object.transform_apply(location=False, rotation=False, scale=True) - return obj - - -def detail_cylinder(bpy, name, location, radius, depth, rotation=(0.0, 0.0, 0.0), vertices=20): - bpy.ops.mesh.primitive_cylinder_add( - vertices=vertices, - radius=radius, - depth=depth, - location=location, - rotation=rotation, - ) - obj = bpy.context.object - obj.name = name + obj.data.materials.append(material) return obj -def build_details(bpy, object_id, master, materials): - """Secondary engineering detail per contract object (joined back into the - master mesh before export). Names below never reach the GLB as objects.""" - clay = None - details = [] - - if object_id == "starship.keel.spine": - # Reinforcement rings along the spine + twin conduit lines + equipment - # blocks riding the top ridge. - for index in range(14): - x = -5.2 + index * 0.72 - details.append(detail_torus( - bpy, - f"{master.name}.detail.ring.{index:02d}", - (x, 0.0, 0.0), - 0.40, - 0.028, - rotation=(0.0, math.pi / 2.0, 0.0), - )) - details.append(detail_cylinder(bpy, master.name + ".detail.conduit.0", (-0.8, 0.30, 0.10), 0.035, 9.4, rotation=(0.0, math.pi / 2.0, 0.0))) - details.append(detail_cylinder(bpy, master.name + ".detail.conduit.1", (-0.8, -0.30, 0.10), 0.035, 9.4, rotation=(0.0, math.pi / 2.0, 0.0))) - for index, x in enumerate((-3.9, -1.9, 0.6, 2.9)): - details.append(detail_cube(bpy, f"{master.name}.detail.box.{index:02d}", (x, 0.0, 0.30), (0.5, 0.30, 0.16))) - elif object_id == "starship.hab.torus": - # Outer panel ribs every 15 degrees, inner twin conduits, and two - # comm masts riding the ring. - for index in range(24): - angle = 2.0 * math.pi * index / 24.0 - y = HAB_RADIUS * math.cos(angle) - z = HAB_RADIUS * math.sin(angle) - rotation = (angle, 0.0, 0.0) - details.append(detail_cube( - bpy, - f"{master.name}.detail.rib.{index:02d}", - (HAB_X + 0.06, y, z), - (0.10, 0.08, 0.09), - rotation=rotation, - )) - for offset in (-0.10, 0.10): - details.append(detail_torus( - bpy, - f"{master.name}.detail.conduit.{offset:+.2f}", - (HAB_X + offset, 0.0, 0.0), - HAB_RADIUS - HAB_MINOR - 0.04, - 0.020, - rotation=(0.0, math.pi / 2.0, 0.0), - minor_segments=8, - )) - details.append(detail_cylinder(bpy, master.name + ".detail.mast.0", (HAB_X - 0.02, HAB_RADIUS + HAB_MINOR, 0.0), 0.02, 0.5, rotation=(0.0, math.pi / 2.0, 0.0))) - details.append(detail_cylinder(bpy, master.name + ".detail.mast.1", (HAB_X + 0.02, 0.0, -(HAB_RADIUS + HAB_MINOR)), 0.02, 0.5)) - elif object_id == "starship.hub.core": - for offset, radius in ((-0.74, 0.40), (0.74, 0.40)): - details.append(detail_cylinder(bpy, f"{master.name}.detail.flange.{offset:+.2f}", (HAB_X + offset, 0.0, 0.0), radius, 0.06, rotation=(0.0, math.pi / 2.0, 0.0))) - details.append(detail_torus(bpy, master.name + ".detail.bearing", (HAB_X, 0.0, 0.0), 0.34, 0.035, rotation=(0.0, math.pi / 2.0, 0.0), minor_segments=10)) - elif object_id == "starship.core.pressure": - for index in range(4): - x = -4.05 + index * 0.42 - details.append(detail_torus(bpy, f"{master.name}.detail.band.{index:02d}", (x, 0.0, 0.0), 0.58, 0.030, rotation=(0.0, math.pi / 2.0, 0.0), minor_segments=10)) - for offset in (-0.92, 0.92): - details.append(detail_cylinder(bpy, f"{master.name}.detail.flange.{offset:+.2f}", (-3.4 + offset, 0.0, 0.0), 0.60, 0.07, rotation=(0.0, math.pi / 2.0, 0.0))) - elif object_id == "starship.shadow.collar": - for index in range(8): - angle = 2.0 * math.pi * index / 8.0 - y = 0.86 * math.cos(angle) - z = 0.86 * math.sin(angle) - details.append(detail_cube(bpy, f"{master.name}.detail.rib.{index:02d}", (-1.35, y, z), (0.34, 0.10, 0.10), rotation=(angle, 0.0, 0.0))) - elif object_id == "starship.storm.cellar": - details.append(detail_cube(bpy, master.name + ".detail.doorframe", (-0.55, 0.0, 0.30), (0.66, 0.52, 0.05))) - details.append(detail_cylinder(bpy, master.name + ".detail.hinge", (-0.55, -0.32, 0.0), 0.035, 0.5)) - elif object_id == "starship.nozzle.magnetic": - # Coil grooves down the bell + coolant lines hugging the cone. - for index in range(5): - fraction = 0.25 + index * 0.14 - x = -6.35 + 2.2 * fraction - radius = 1.15 - 0.55 * fraction - details.append(detail_torus(bpy, f"{master.name}.detail.coil.{index:02d}", (x, 0.0, 0.0), radius, 0.034, rotation=(0.0, math.pi / 2.0, 0.0), minor_segments=10)) - for index in range(4): - angle = 2.0 * math.pi * index / 4.0 + math.pi / 4.0 - y = 0.95 * math.cos(angle) - z = 0.95 * math.sin(angle) - details.append(detail_cylinder(bpy, f"{master.name}.detail.coolant.{index:02d}", (-5.35, y, z), 0.028, 2.5, rotation=(0.0, math.pi / 2.0, 0.0))) - elif object_id == "starship.power.truss": - for index in range(6): - t = index / 5.0 - x = -1.2 + t * 5.1 - details.append(detail_cylinder(bpy, f"{master.name}.detail.diagonal.{index:02d}", (x, 0.0, 0.34), 0.022, 0.34, rotation=(0.0, math.pi / 2.0, 0.0), vertices=8)) - details.append(detail_torus(bpy, master.name + ".detail.hoop", (1.6, 0.0, 0.34), 0.40, 0.028, rotation=(math.pi / 2.0, 0.0, 0.0), minor_segments=10)) - elif object_id.startswith("starship.radiator."): - index = int(object_id.rsplit(".", 1)[-1]) - angle = 2.0 * math.pi * index / 6.0 + math.pi / 6.0 - y = 0.95 * math.cos(angle) - z = 0.95 * math.sin(angle) - details.append(detail_cube(bpy, master.name + ".detail.backbone", (-3.6, y, z), (1.5, 0.05, 0.06))) - for rib in (-0.45, 0.0, 0.45): - details.append(detail_cube(bpy, f"{master.name}.detail.stiff.{rib:+.2f}", (-3.6 + rib, y, z), (0.05, 0.06, 0.40))) - elif object_id.startswith("starship.cargo.pod."): - index = int(object_id.rsplit(".", 1)[-1]) - y = (-0.62, 0.62)[index] - for offset in (-0.5, 0.0, 0.5): - details.append(detail_cube(bpy, f"{master.name}.detail.rib.{offset:+.2f}", (0.35 + offset, y, 0.0), (0.05, 0.26, 0.32))) - details.append(detail_cylinder(bpy, master.name + ".detail.endcap", (0.97, y, 0.0), 0.10, 0.05, rotation=(0.0, math.pi / 2.0, 0.0))) - elif object_id == "starship.nose.cone": - details.append(detail_torus(bpy, master.name + ".detail.seam.0", (4.45, 0.0, 0.0), 0.26, 0.02, rotation=(0.0, math.pi / 2.0, 0.0), minor_segments=8)) - details.append(detail_torus(bpy, master.name + ".detail.seam.1", (4.75, 0.0, 0.0), 0.16, 0.018, rotation=(0.0, math.pi / 2.0, 0.0), minor_segments=8)) - elif object_id == "starship.comm.dish": - # Mounting mast rising from the keel deck + gimbal block under the - # dish base, so the dish reads as bolted hardware instead of floating. - details.append(detail_cylinder(bpy, master.name + ".detail.post", (3.40, 0.0, 0.37), 0.045, 0.30, vertices=12)) - details.append(detail_cube(bpy, master.name + ".detail.gimbal", (3.40, 0.0, 0.46), (0.16, 0.16, 0.10))) - details.append(detail_cube(bpy, master.name + ".detail.baseplate", (3.40, 0.0, 0.24), (0.26, 0.26, 0.05))) - elif object_id.startswith("starship.spoke."): - index = int(object_id.rsplit(".", 1)[-1]) - angle = 2.0 * math.pi * index / SPOKE_COUNT - end_y = HAB_RADIUS * math.cos(angle) - end_z = HAB_RADIUS * math.sin(angle) - # Collar at the ring end and a hinge ring at the hub end. - details.append(detail_torus(bpy, master.name + ".detail.cuff", (HAB_X, end_y * 0.92, end_z * 0.92), 0.16, 0.045, rotation=(angle, 0.0, 0.0), minor_segments=12)) - details.append(detail_torus(bpy, master.name + ".detail.root", (HAB_X + 0.06, 0.0, 0.0), 0.40, 0.045, rotation=(0.0, math.pi / 2.0, 0.0), minor_segments=12)) - else: - return [] - - _ = clay - return details - - -# --------------------------------------------------------------------------- -# Starship layout (units are display metres, unscaled示意 dimensions). -# -# nose +X : forward docking / sensor section -# -X : reactor + nozzle (the ship accelerates toward -X) -# -# habitat : torus in the Y-Z plane at x=+1.2 (far from reactor at x<-3) -# shadow : water-shadow collar at x=-1.2, storm cellar hugs the keel -# reactor : fusion pressure vessel x in [-4.2, -2.6], nozzle to x=-5.6 -# --------------------------------------------------------------------------- - -HAB_X = 1.2 -HAB_RADIUS = 1.7 -HAB_MINOR = 0.30 -SPOKE_COUNT = 6 +# Geometry constants shared across steps (units: 1 = 2 m). +# Scale philosophy: few large elements, not many small ones. The hull core +# is a continuous 16 m-diameter pressure body; everything else hangs off it. +HULL_P1 = (0.0, -4.0, 0.0) +HULL_P2 = (0.0, 42.0, 0.0) +HULL_R = 4.0 # 16 m diameter +NODE_C = (0.0, 44.6, 0.0) +CENTRIFUGE_CENTER = (0.0, 6.0, 0.0) +CENTRIFUGE_R = 14.0 # 56 m ring +SPINE_P1 = (0.0, -34.0, 0.0) +SPINE_P2 = (0.0, 20.0, 0.0) +SPINE_R = 1.2 +HAB_A = ((5.6, 12.0, 0.0), (5.6, 30.0, 0.0), 2.6) # side-mounted lab module +HAB_B = ((-5.6, 12.0, 0.0), (-5.6, 30.0, 0.0), 2.6) # side-mounted hab module +TANK_X = 7.6 +TANK_R = 2.5 # 10 m diameter tanks +REACTOR_C = (0.0, -30.5, 0.0) +NOZZLE_MOUNT_Y = -40.5 + + +# --------------------------------------------------------------- builders + + +def b_hull_core(bm): + """Continuous 16 m-diameter pressure hull — the ship's body.""" + p1, p2 = Vector(HULL_P1), Vector(HULL_P2) + r = HULL_R + seg = 56 + cyl(bm, p1, p2, r, seg=seg, cap1=False, cap2=False) + dome(bm, p1, (0.0, -1.0, 0.0), r, seg=seg) + dome(bm, p2, (0.0, 1.0, 0.0), r, seg=seg) + frames = 24 + for i in range(1, frames): + t = i / frames + torus(bm, p1.lerp(p2, t), r, 0.115, axis=(0, 1, 0), seg=seg, rseg=7) + for k in range(8): + a = 2.0 * math.pi * k / 8 + math.pi / 8 + radial = Vector((math.cos(a), 0.0, math.sin(a))) + cyl(bm, p1 + radial * r, p2 + radial * r, 0.10, seg=7) + for band_t, count in ((0.18, 22), (0.46, 26), (0.74, 22)): + c0 = p1.lerp(p2, band_t) + for k in range(count): + a = 2.0 * math.pi * k / count + radial = Vector((math.cos(a), 0.0, math.sin(a))) + wc = c0 + radial * r + cyl(bm, wc - radial * 0.12, wc + radial * 0.18, 0.22, seg=12, cap1=False) + torus(bm, wc + radial * 0.18, 0.245, 0.05, axis=radial, seg=14, rseg=5) + panel_bank(bm, p1 + Vector((0.0, 3.5, 0.0)), p2 - Vector((0.0, 3.5, 0.0)), + r + 0.035, cols=72, rows=18, depth=0.05) + for (t0, count, seed) in ((0.10, 26, 601), (0.60, 30, 602), (0.90, 22, 603)): + base = p1.lerp(p2, t0) + equipment_row(bm, base + Vector((0.0, 0.0, r + 0.12)), (0, 1, 0), (0, 0, 1), count, 1.35, 0.14, 0.34, seed=seed) + equipment_row(bm, base + Vector((0.0, 0.0, -r - 0.12)), (0, 1, 0), (0, 0, -1), count, 1.35, 0.14, 0.34, seed=seed + 50) + + +def b_spine(bm): + """Outrigger trusses carrying tanks, arrays and radiators.""" + for sx in (-1.0, 1.0): + x0 = sx * (HULL_R + 1.6) + truss(bm, (x0, -34.0, 0.0), (x0, 20.0, 0.0), 0.85, bays=36, chord_r=0.085, strut_r=0.05, + angles=[0.0, 120.0, 240.0] if sx > 0 else [180.0, 60.0, 300.0]) + for st in (-30.0, -22.0, -14.0, -6.0, 2.0, 10.0): + cyl(bm, Vector((sx * HULL_R, st, 0.0)), Vector((x0, st, 0.0)), 0.10, seg=7) + + +def b_node(bm): + c = Vector(NODE_C) + cyl(bm, c - Vector((0, 1.3, 0)), c + Vector((0, 1.3, 0)), 2.6, seg=24) + torus(bm, c - Vector((0, 1.3, 0)), 2.63, 0.09, axis=(0, 1, 0), seg=28, rseg=6) + torus(bm, c + Vector((0, 1.3, 0)), 2.63, 0.09, axis=(0, 1, 0), seg=28, rseg=6) + for dirv in ((0, 1, 0), (1, 0, 0), (-1, 0, 0), (0, 0, 1), (0, 0, -1)): + d = Vector(dirv) + x, y, _ = basis(d) + base = c + d * 1.25 + cyl(bm, base, base + d * 0.65, 1.05, seg=18) + torus(bm, base + d * 0.65, 1.12, 0.085, axis=d, seg=20, rseg=6) + for k in range(10): + a = 2.0 * math.pi * k / 10 + radial = x * math.cos(a) + y * math.sin(a) + cyl(bm, base + d * 0.65 + radial * 1.12, base + d * 0.80 + radial * 1.22, 0.055, seg=6) + + +def b_hab_a(bm): + p1, p2, r = HAB_A + hab_module(bm, p1, p2, r, ribs=9, windows=12, seg=32) + panel_bank(bm, p1, p2, r + 0.02, cols=30, rows=18, depth=0.04) + equipment_row(bm, (6.6 + r + 0.12, 14.0, 0.0), (0, 1, 0), (1, 0, 0), 12, 1.25, 0.12, 0.28, seed=201) + equipment_row(bm, (6.6, 14.5, r + 0.12), (0, 1, 0), (0, 0, 1), 10, 1.4, 0.10, 0.26, seed=203) + + +def b_hab_b(bm): + p1, p2, r = HAB_B + hab_module(bm, p1, p2, r, ribs=9, windows=12, seg=32) + panel_bank(bm, p1, p2, r + 0.02, cols=30, rows=18, depth=0.04) + equipment_row(bm, (-6.6 - r - 0.12, 15.0, 0.0), (0, 1, 0), (-1, 0, 0), 12, 1.25, 0.12, 0.28, seed=211) + equipment_row(bm, (-6.6, 15.5, r + 0.12), (0, 1, 0), (0, 0, 1), 10, 1.4, 0.10, 0.26, seed=213) + + +def b_whipple(bm): + p1, p2 = Vector(HULL_P1), Vector(HULL_P2) + whipple_panels(bm, p1 + Vector((0, 3.0, 0)), p2 - Vector((0, 3.0, 0)), HULL_R, rows=14, cols=30) + + +def b_waterwall(bm): + """Water tanks ringed around the forward hull as radiation shielding.""" + for k in range(14): + a = 2.0 * math.pi * k / 14 + radial = Vector((math.cos(a), 0.0, math.sin(a))) + p1 = Vector((0.0, 22.0, 0.0)) + radial * (HULL_R + 1.15) + p2 = Vector((0.0, 38.0, 0.0)) + radial * (HULL_R + 1.15) + pressure_vessel(bm, p1, p2, 1.15, rings=3, seg=16, ports=False) + mli_bands(bm, p1, p2, 1.18, bands=7, seg=18, wobble=0.02) + for t in (0.25, 0.75): + cco = p1.lerp(p2, t) + cyl(bm, cco - radial * 1.1, cco - radial * 0.4, 0.08, seg=6) + + +def b_centrifuge(bm): + centrifuge_ring(bm, CENTRIFUGE_CENTER, CENTRIFUGE_R, axis=(0, 1, 0), cars=8, seg=72) + hub = Vector(CENTRIFUGE_CENTER) + cyl(bm, hub - Vector((0, 2.2, 0)), hub + Vector((0, 2.2, 0)), 1.9, seg=24) + torus(bm, hub + Vector((0, 2.2, 0)), 1.93, 0.09, axis=(0, 1, 0), seg=28, rseg=6) + torus(bm, hub - Vector((0, 2.2, 0)), 1.93, 0.09, axis=(0, 1, 0), seg=28, rseg=6) + for k in range(8): + a = 2.0 * math.pi * k / 8 + radial = Vector((math.cos(a), 0.0, math.sin(a))) + cyl(bm, hub + radial * 1.9, hub + radial * 3.0, 0.11, seg=7) + for k in range(32): + a = 2.0 * math.pi * k / 32 + radial = Vector((math.cos(a), 0.0, math.sin(a))) + c = Vector(CENTRIFUGE_CENTER) + radial * (CENTRIFUGE_R - 1.9) + cyl(bm, c - Vector((0, 1.1, 0)), c + Vector((0, 1.1, 0)), 0.62, seg=16) + torus(bm, c - Vector((0, 1.1, 0)), 0.64, 0.055, axis=(0, 1, 0), seg=16, rseg=5) + torus(bm, c + Vector((0, 1.1, 0)), 0.64, 0.055, axis=(0, 1, 0), seg=16, rseg=5) + for k in range(28): + a = 2.0 * math.pi * k / 28 + 0.22 + radial = Vector((math.cos(a), 0.0, math.sin(a))) + c = Vector(CENTRIFUGE_CENTER) + radial * (CENTRIFUGE_R + 0.4) + box(bm, c, 0.38, 0.62, 0.38, radial, Vector((0, 1, 0)), Vector((-math.sin(a), 0.0, math.cos(a)))) + + +def b_prop_tank(bm): + for sx in (-1.0, 1.0): + for yc in (-14.0, -23.0): + x0 = sx * TANK_X + p1 = (x0, yc - 4.5, 0.0) + p2 = (x0, yc + 4.5, 0.0) + pressure_vessel(bm, p1, p2, TANK_R, rings=5, seg=30) + mli_bands(bm, p1, p2, TANK_R + 0.05, bands=13, seg=30, wobble=0.03) + panel_bank(bm, Vector(p1) + Vector((0, 1.4, 0)), Vector(p2) - Vector((0, 1.4, 0)), + TANK_R + 0.05, cols=22, rows=16, depth=0.035) + for st in (yc - 3.0, yc, yc + 3.0): + cyl(bm, Vector((sx * (HULL_R + 1.6), st, 0.0)), Vector((x0, st, 0.0)), 0.16, seg=8) + cyl(bm, Vector((sx * (HULL_R + 1.6), st - 0.5, 0.0)), Vector((x0, st, 0.6)), 0.07, seg=6) + torus(bm, Vector((x0, st, 0.0)), TANK_R + 0.07, 0.06, axis=(0, 1, 0), seg=24, rseg=5) + + +def b_reactor(bm): + base = Vector(REACTOR_C) + cyl(bm, base - Vector((0, 1.8, 0)), base + Vector((0, 1.8, 0)), 2.1, seg=26) + for yy in (-1.8, 1.8): + torus(bm, base + Vector((0, yy, 0)), 2.13, 0.09, axis=(0, 1, 0), seg=28, rseg=6) + for k in range(14): + a = 2.0 * math.pi * k / 14 + radial = Vector((math.cos(a), 0.0, math.sin(a))) + cyl(bm, base + radial * 2.0 - Vector((0, 1.5, 0)), base + radial * 2.0 + Vector((0, 1.5, 0)), 0.11, seg=6) + for k in range(8): + a = 2.0 * math.pi * k / 8 + radial = Vector((math.cos(a), 0.0, math.sin(a))) + cyl(bm, base + radial * 2.1, base + radial * 3.1 - Vector((0, 3.4, 0)), 0.10, seg=7) + + +def b_thrust_cone(bm): + thrust_cone(bm, (0.0, -33.5, 0.0), (0, -1, 0), 3.6, 2.9, 1.8, struts=14) + shadow_shield(bm, (0.0, -35.6, 0.0), (0, -1, 0), 4.4, seg=36, ribs=12) + + +def b_nozzle_cluster(bm): + for ex in (-2.4, 2.4): + mount = Vector((ex, NOZZLE_MOUNT_Y, 0.0)) + gimbal(bm, mount, (0, -1, 0), radius=0.55, s=2.0) + ntp_engine(bm, mount, (0, -1, 0), scale=2.0) + + +def b_solar(bm): + for sx in (-1.0, 1.0): + for yy in (24.0, -18.0): + root = Vector((sx * (HULL_R + 2.4), yy, 0.0)) + solar_array(bm, root, (sx, 0, 0), 30.0, 13.0, cells=84, rows=18) + d = Vector((sx, 0, 0)) + x, _, _ = basis(d) + for k in range(16): + t = (k + 0.5) / 16 + c = root + d * (30.0 * t) + cyl(bm, c - x * 6.5, c + x * 6.5, 0.05, seg=6) + for side in (-1.0, 1.0): + off = x * 6.5 * side + cyl(bm, root + off, root + d * 30.0 + off, 0.07, seg=6) + cyl(bm, root, root + Vector((sx * 2.4, 0, 0)), 0.20, seg=10) + for k in range(4): + a = 2.0 * math.pi * k / 4 + cyl(bm, Vector((sx * (HULL_R + 0.4), yy, 0.0)), + root + Vector((sx * 1.2, 0, 0)) + x * 0.8 * math.cos(a), 0.06, seg=6) + + +def b_radiator(bm): + for sx in (-1.0, 1.0): + for yy in (-32.0, -12.0, 32.0): + p1 = Vector((sx * (HULL_R + 3.4), yy - 6.0, 0.0)) + p2 = Vector((sx * (HULL_R + 3.4), yy + 6.0, 0.0)) + fin_radiator(bm, p1, p2, 3.4, fins=64, fin_h=0.55) + mid = p1.lerp(p2, 0.5) + cyl(bm, Vector((sx * (HULL_R + 0.6), mid.y, 0.0)), mid, 0.11, seg=8) + torus(bm, mid, 1.7, 0.06, axis=(1, 0, 0), seg=20, rseg=5) + + +def b_airlock(bm): + p1 = Vector((0.0, 16.0, HULL_R - 0.5)) + p2 = Vector((0.0, 16.0, HULL_R + 4.6)) + cyl(bm, p1, p2, 1.6, seg=22) + for zz in (HULL_R - 0.5, HULL_R + 4.6): + torus(bm, Vector((0.0, 16.0, zz)), 1.63, 0.08, axis=(0, 0, 1), seg=24, rseg=6) + hatch = Vector((0.0, 16.0, HULL_R + 4.6 + 0.4)) + cyl(bm, hatch - Vector((0, 0, 0.4)), hatch + Vector((0, 0, 0.25)), 1.0, seg=18) + torus(bm, hatch + Vector((0, 0, 0.25)), 1.06, 0.075, axis=(0, 0, 1), seg=20, rseg=6) + for k in range(8): + a = 2.0 * math.pi * k / 8 + off = Vector((math.cos(a), math.sin(a), 0.0)) * 1.06 + cyl(bm, hatch + off + Vector((0, 0, 0.25)), hatch + off + Vector((0, 0, 0.42)), 0.055, seg=6) + equipment_row(bm, (1.9, 16.0, HULL_R + 2.4), (0, 0, 1), (1, 0, 0), 8, 0.5, 0.10, 0.22, seed=401) + + +def b_orion(bm): + """Orion-class shuttle docked forward — the human-scale reference.""" + base = Vector((0.0, 47.6, 0.0)) + cyl(bm, base, base + Vector((0, 5.4, 0)), 1.5, seg=20) + for t in (0.3, 0.62): + torus(bm, base + Vector((0, 5.4 * t, 0)), 1.53, 0.065, axis=(0, 1, 0), seg=22, rseg=5) + cyl(bm, base + Vector((0, 5.4, 0)), base + Vector((0, 7.8, 0)), 1.5, 0.55, seg=20) + torus(bm, base + Vector((0, 5.4, 0)), 1.53, 0.08, axis=(0, 1, 0), seg=22, rseg=6) + cyl(bm, base + Vector((0, 7.8, 0)), base + Vector((0, 8.5, 0)), 0.55, seg=14) + torus(bm, base + Vector((0, 8.5, 0)), 0.65, 0.07, axis=(0, 1, 0), seg=16, rseg=6) + for k in range(8): + a = 2.0 * math.pi * k / 8 + radial = Vector((math.cos(a), 0.0, math.sin(a))) + c = base + Vector((0, 6.9, 0)) + radial * 1.06 + cyl(bm, c - radial * 0.05, c + radial * 0.11, 0.13, seg=10, cap1=False) + for k in range(4): + a = 2.0 * math.pi * k / 4 + math.pi / 4 + radial = Vector((math.cos(a), 0.0, math.sin(a))) + root = base + Vector((0, 5.4, 0)) + radial * 1.5 + solar_array(bm, root, radial, 4.6, 2.0, cells=22, rows=8) + bell(bm, base - Vector((0, 0.6, 0)), (0, -1, 0), 1.5, 0.22, 0.68, seg=22, layers=9) + + +def b_greeble(bm): + """Organised machinery, cable looms, nav lights, hatch plates.""" + p1, p2 = Vector(HULL_P1), Vector(HULL_P2) + r = HULL_R + for rx, (yy, ang) in ((0, (28.0, 0.9)), (0, (36.0, 2.5)), (0, (10.0, 4.2))): + radial = Vector((math.cos(ang), 0.0, math.sin(ang))) + base = Vector((0.0, yy, 0.0)) + radial * r + cyl(bm, base - radial * 0.05, base + radial * 0.13, 0.55, seg=14) + torus(bm, base + radial * 0.13, 0.60, 0.055, axis=radial, seg=16, rseg=5) + for sx in (-1.0, 1.0): + x0 = sx * (HULL_R + 1.6) + for dz in (0.95, -0.95): + pipe_run(bm, [(x0, -34.0, dz), (x0, 20.0, dz)], 0.055) + for (pos, dirv, rad) in (((-4.0, 40.0, 0.0), (-1, 0, 0), 0.20), + ((4.0, 40.0, 0.0), (1, 0, 0), 0.20), + ((0.0, 40.0, 4.0), (0, 0, 1), 0.20), + ((-4.0, -2.0, 0.0), (-1, 0, 0), 0.16), + ((4.0, -2.0, 0.0), (1, 0, 0), 0.16)): + d = Vector(dirv) + cyl(bm, Vector(pos), Vector(pos) + d * 0.15, rad, seg=10) + cyl(bm, Vector(pos) + d * 0.15, Vector(pos) + d * 0.34, rad * 0.7, seg=10) + equipment_row(bm, (0.0, 34.0, r + 0.15), (0, 1, 0), (0, 0, 1), 12, 1.0, 0.10, 0.24, seed=501) + equipment_row(bm, (0.0, 6.0, -r - 0.15), (0, 1, 0), (0, 0, -1), 14, 1.0, 0.10, 0.24, seed=502) + + +def b_antenna(bm): + antenna_dish(bm, (0.0, 30.0, HULL_R + 1.4), (0.0, 0.3, 1.0), 2.6, depth=0.7, ribs=12) + antenna_dish(bm, (HULL_R + 1.2, 38.0, 0.0), (1.0, 0.25, 0.1), 1.4, depth=0.4, ribs=9) + for (bx, by, bz, ll) in ((0.0, 42.5, 3.0, 2.2), (0.0, 8.0, -3.0, 2.0), (3.0, 41.0, 0.0, 1.6)): + cyl(bm, Vector((bx, by, bz)), Vector((bx, by + 0.2, bz + ll)), 0.055, seg=6) + torus(bm, Vector((bx, by + 0.2, bz + ll)), 0.4, 0.045, axis=(0, 0, 1), seg=16, rseg=5) + + +BUILDERS = { + "hnx02.hull.core": b_hull_core, + "hnx02.spine.truss": b_spine, + "hnx02.node.forward": b_node, + "hnx02.hab.a": b_hab_a, + "hnx02.hab.b": b_hab_b, + "hnx02.hab.whipple": b_whipple, + "hnx02.rad.waterwall": b_waterwall, + "hnx02.centrifuge.ring": b_centrifuge, + "hnx02.prop.tank": b_prop_tank, + "hnx02.reactor.core": b_reactor, + "hnx02.thrust.cone": b_thrust_cone, + "hnx02.nozzle.cluster": b_nozzle_cluster, + "hnx02.power.solar": b_solar, + "hnx02.power.radiator": b_radiator, + "hnx02.cabin.airlock": b_airlock, + "hnx02.shuttle.orion": b_orion, + "hnx02.detail.greeble": b_greeble, + "hnx02.detail.antenna": b_antenna, +} def build_geometry(bpy, materials, step_id): - """Build every object of the cumulative step, in STEP_OBJECTS order.""" - clay_body = materials["clay_body"] - clay_structure = materials["clay_structure"] - clay_dark = materials["clay_dark"] - clay_white = materials["clay_white"] - clay_glass = materials["clay_glass"] - livery_hull = materials["livery_hull"] - livery_structure = materials["livery_structure"] - livery_dark = materials["livery_dark"] - livery_ceramic = materials["livery_ceramic"] - livery_amber = materials["livery_amber"] - livery_white = materials["livery_white"] - livery_glass = materials["livery_glass"] - livery_nozzle = materials["livery_nozzle"] - - def spoke_end(index): - angle = 2.0 * math.pi * index / SPOKE_COUNT - return (HAB_X, HAB_RADIUS * math.cos(angle), HAB_RADIUS * math.sin(angle)) - - objects = {} + objects = [] for object_id in STEP_OBJECTS[step_id]: - if object_id == "starship.keel.spine": - objects[object_id] = add_beveled_cube(bpy, object_id, (-0.8, 0.0, 0.0), (10.0, 0.46, 0.42), clay_structure, livery_structure, bevel=0.05) - elif object_id == "starship.nose.cone": - objects[object_id] = add_cone(bpy, object_id, (4.6, 0.0, 0.0), 0.42, 0.08, 1.0, clay_body, livery_hull, rotation=(0.0, -math.pi / 2.0, 0.0)) - elif object_id == "starship.dock.ring": - objects[object_id] = add_torus(bpy, object_id, (4.1, 0.0, 0.0), 0.44, 0.06, clay_structure, livery_amber, rotation=(0.0, math.pi / 2.0, 0.0)) - elif object_id == "starship.hab.torus": - objects[object_id] = add_torus(bpy, object_id, (HAB_X, 0.0, 0.0), HAB_RADIUS, HAB_MINOR, clay_body, livery_hull, rotation=(0.0, math.pi / 2.0, 0.0), minor_segments=18) - elif object_id == "starship.hub.core": - objects[object_id] = add_cylinder(bpy, object_id, (HAB_X, 0.0, 0.0), 0.34, 1.4, clay_structure, livery_structure, rotation=(0.0, math.pi / 2.0, 0.0)) - elif object_id.startswith("starship.spoke."): - index = int(object_id.rsplit(".", 1)[-1]) - end = spoke_end(index) - objects[object_id] = add_between(bpy, object_id, (HAB_X, 0.0, 0.0), end, 0.11, clay_structure, livery_structure, vertices=16) - elif object_id == "starship.shadow.collar": - objects[object_id] = add_torus(bpy, object_id, (-1.35, 0.0, 0.0), 0.78, 0.24, clay_dark, livery_ceramic, rotation=(0.0, math.pi / 2.0, 0.0), minor_segments=16) - elif object_id == "starship.storm.cellar": - objects[object_id] = add_beveled_cube(bpy, object_id, (-0.55, 0.0, 0.0), (1.1, 0.62, 0.58), clay_dark, livery_ceramic, bevel=0.06) - elif object_id == "starship.core.pressure": - objects[object_id] = add_cylinder(bpy, object_id, (-3.4, 0.0, 0.0), 0.56, 1.7, clay_structure, livery_nozzle, rotation=(0.0, math.pi / 2.0, 0.0)) - elif object_id == "starship.core.shield.plug": - objects[object_id] = add_cylinder(bpy, object_id, (-2.45, 0.0, 0.0), 0.60, 0.5, clay_dark, livery_ceramic, rotation=(0.0, math.pi / 2.0, 0.0)) - elif object_id == "starship.nozzle.magnetic": - objects[object_id] = add_cone(bpy, object_id, (-5.35, 0.0, 0.0), 1.15, 0.60, 2.4, clay_structure, livery_nozzle, rotation=(0.0, math.pi / 2.0, 0.0)) - elif object_id.startswith("starship.radiator."): - index = int(object_id.rsplit(".", 1)[-1]) - angle = 2.0 * math.pi * index / 6.0 + math.pi / 6.0 - y = 0.95 * math.cos(angle) - z = 0.95 * math.sin(angle) - objects[object_id] = add_beveled_cube(bpy, object_id, (-3.6, y, z), (1.5, 0.04, 0.42), clay_white, livery_white, bevel=0.02, rotation=(angle, 0.0, 0.0)) - elif object_id == "starship.power.truss": - objects[object_id] = add_between(bpy, object_id, (-1.2, 0.0, 0.34), (3.9, 0.0, 0.34), 0.09, clay_structure, livery_structure, vertices=14) - elif object_id == "starship.capacitor.ring": - objects[object_id] = add_torus(bpy, object_id, (2.6, 0.0, 0.34), 0.42, 0.08, clay_structure, livery_amber, rotation=(math.pi / 2.0, 0.0, 0.0)) - elif object_id == "starship.comm.dish": - objects[object_id] = add_dish(bpy, object_id, 3.4, 0.0, 0.50, 0.55, 0.10, clay_white, livery_white) - elif object_id == "starship.comm.feed": - objects[object_id] = add_cylinder(bpy, object_id, (3.72, 0.0, 0.50), 0.05, 0.09, clay_glass, livery_glass, rotation=(0.0, math.pi / 2.0, 0.0)) - elif object_id.startswith("starship.window."): - index = int(object_id.rsplit(".", 1)[-1]) - angle = 2.0 * math.pi * index / SPOKE_COUNT + math.pi / 6.0 - y = (HAB_RADIUS + HAB_MINOR * 0.55) * math.cos(angle) - z = (HAB_RADIUS + HAB_MINOR * 0.55) * math.sin(angle) - objects[object_id] = add_cylinder(bpy, object_id, (HAB_X + 0.16, y, z), 0.14, 0.10, clay_glass, livery_glass, rotation=(angle, 0.0, 0.0), vertices=18) - elif object_id == "starship.airlock.ring": - objects[object_id] = add_torus(bpy, object_id, (4.1, 0.0, 0.0), 0.30, 0.05, clay_structure, livery_amber, rotation=(0.0, math.pi / 2.0, 0.0)) - elif object_id == "starship.airlock.tunnel": - objects[object_id] = add_cylinder(bpy, object_id, (4.55, 0.0, 0.0), 0.26, 0.9, clay_body, livery_hull, rotation=(0.0, math.pi / 2.0, 0.0)) - elif object_id.startswith("starship.cargo.pod."): - index = int(object_id.rsplit(".", 1)[-1]) - y = (-0.62, 0.62)[index] - objects[object_id] = add_beveled_cube(bpy, object_id, (0.35, y, 0.0), (1.2, 0.24, 0.30), clay_body, livery_hull, bevel=0.03) - else: - fail("unknown object id: " + object_id, 4) - - # Secondary engineering detail is generated per contract object and - # merged into the master mesh, keeping the GLB object contract intact. - for object_id in list(STEP_OBJECTS[step_id]): - master = objects[object_id] - merge_details(bpy, master, build_details(bpy, object_id, master, materials)) - - ordered = [objects[object_id] for object_id in STEP_OBJECTS[step_id]] - for obj, object_id in zip(ordered, STEP_OBJECTS[step_id]): - if obj.get("object_id") != object_id: - fail("source object custom property verification failed", 4) - return ordered + material = materials[OBJECT_MATERIAL[object_id]] + objects.append(build_object(bpy, object_id, material, BUILDERS[object_id])) + return objects -def add_render_setup(bpy): - from mathutils import Vector +# ------------------------------------------------------------ render / io + +def add_render_setup(bpy): scene = bpy.context.scene - scene.render.resolution_x = 512 - scene.render.resolution_y = 512 + scene.render.resolution_x = 1280 + scene.render.resolution_y = 860 scene.render.resolution_percentage = 100 scene.render.image_settings.file_format = "PNG" if scene.world is None: - scene.world = bpy.data.worlds.new("ship.world") - scene.world.color = (0.025, 0.035, 0.045) + scene.world = bpy.data.worlds.new("hnx02.world") + scene.world.color = (0.02, 0.03, 0.04) - camera_data = bpy.data.cameras.new("ship.camera") + camera_data = bpy.data.cameras.new("hnx02.camera") camera_data.type = "ORTHO" - camera_data.ortho_scale = 13.5 - camera = bpy.data.objects.new("ship.camera", camera_data) + camera_data.ortho_scale = 118.0 + camera = bpy.data.objects.new("hnx02.camera", camera_data) bpy.context.collection.objects.link(camera) - camera.location = (7.0, -13.5, 7.5) - camera.rotation_euler = (Vector((-0.5, 0.0, 0.0)) - camera.location).to_track_quat("-Z", "Y").to_euler() + camera.location = (52.0, -66.0, 40.0) + camera.rotation_euler = (Vector((0.0, 2.0, 0.0)) - camera.location).to_track_quat("-Z", "Y").to_euler() scene.camera = camera def add_light(name, location, energy, size, color=(1.0, 1.0, 1.0)): @@ -703,12 +1057,12 @@ def add_render_setup(bpy): light = bpy.data.objects.new(name, light_data) bpy.context.collection.objects.link(light) light.location = location - light.rotation_euler = (Vector((-0.5, 0.0, 0.0)) - Vector(location)).to_track_quat("-Z", "Y").to_euler() + light.rotation_euler = (Vector((0.0, 2.0, 0.0)) - Vector(location)).to_track_quat("-Z", "Y").to_euler() return light - add_light("ship.key", (5.0, -6.0, 9.0), 1400, 6.0, (1.0, 0.98, 0.94)) - add_light("ship.fill", (-4.0, -9.0, 2.0), 420, 8.0, (0.72, 0.82, 1.0)) - add_light("ship.rim", (-7.5, 5.0, -3.0), 900, 3.0, (1.0, 0.86, 0.62)) + add_light("hnx02.key", (34.0, -30.0, 40.0), 16000, 26.0, (1.0, 0.97, 0.92)) + add_light("hnx02.fill", (-30.0, -42.0, 10.0), 5200, 30.0, (0.68, 0.79, 1.0)) + add_light("hnx02.rim", (-36.0, 26.0, -16.0), 9000, 16.0, (1.0, 0.85, 0.58)) def export_glb(bpy, objects, output): @@ -717,7 +1071,17 @@ def export_glb(bpy, objects, output): for obj in objects: obj.select_set(True) bpy.context.view_layer.objects.active = objects[-1] - result = bpy.ops.export_scene.gltf(filepath=output, export_format="GLB", use_selection=True, export_apply=True) + result = bpy.ops.export_scene.gltf( + filepath=output, + export_format="GLB", + use_selection=True, + export_apply=True, + export_draco_mesh_compression_enable=True, + export_draco_mesh_compression_level=6, + export_draco_position_quantization=14, + export_draco_normal_quantization=10, + export_draco_texcoord_quantization=12, + ) if "FINISHED" not in result or not os.path.isfile(output) or os.path.getsize(output) <= 0: fail("GLB export failed", 4) @@ -738,11 +1102,9 @@ def verify_reimport(bpy, output, expected): result = bpy.ops.import_scene.gltf(filepath=output) if "FINISHED" not in result: fail("GLB reimport failed", 4) - names = [obj.name for obj in bpy.context.scene.objects if obj.type == "MESH"] if len(names) != len(expected) or set(names) != set(expected): fail("GLB reimport object IDs did not match", 4) - return [object_id for object_id in expected if object_id in names] @@ -754,61 +1116,40 @@ def main(): parser.add_argument("--result-output", required=True) args = parser.parse_args() - if args.step_id not in STEP_OBJECTS: - fail("unknown step id", 2) - if bool(args.render_output) and os.path.abspath(args.render_output) == os.path.abspath(args.output): - fail("render output must differ from GLB output", 2) - - try: - import bpy - except Exception as exc: - fail("bpy is unavailable: " + str(exc), 3) - - if tuple(bpy.app.version) < (3, 0, 0): - fail("bpy version is unsupported", 3) - - output = os.path.abspath(args.output) - result_output = os.path.abspath(args.result_output) - os.makedirs(os.path.dirname(output), exist_ok=True) - - try: - clear_scene(bpy) - materials = build_materials(bpy) - objects = build_geometry(bpy, materials, args.step_id) - apply_material_mode(bpy, materials, "livery" if args.step_id in LIVERY_STEPS else "clay") - export_glb(bpy, objects, output) - exported_at = utc_now() - glb_bytes = os.path.getsize(output) - - rendered_at = None - if args.render_output: - render_output = os.path.abspath(args.render_output) - os.makedirs(os.path.dirname(render_output), exist_ok=True) - add_render_setup(bpy) - bpy.context.scene.render.filepath = render_output - result = bpy.ops.render.render(write_still=True) - if "FINISHED" not in result or not os.path.isfile(render_output) or os.path.getsize(render_output) <= 0: - fail("PNG render failed", 4) - rendered_at = utc_now() - - verified = verify_reimport(bpy, output, STEP_OBJECTS[args.step_id]) - response = { - "ok": True, - "blenderVersion": ".".join(str(part) for part in bpy.app.version), - "exportedAt": exported_at, - "objectIds": STEP_OBJECTS[args.step_id], - "verifiedObjectIds": verified, - "glbBytes": glb_bytes, - } - if args.render_output: - response["renderPath"] = os.path.abspath(args.render_output) - response["renderedAt"] = rendered_at - write_result(result_output, response) - print("starship result written: " + result_output) - except SystemExit: - raise - except Exception as exc: - fail("starship failed: " + str(exc), 4) + step_id = args.step_id + if step_id not in STEP_OBJECTS: + fail("unknown step id: " + step_id, 3) + + clear_scene(bpy) + materials = build_materials(bpy) + objects = build_geometry(bpy, materials, step_id) + + render_path = None + if args.render_output: + add_render_setup(bpy) + scene = bpy.context.scene + scene.render.filepath = args.render_output + bpy.ops.render.render(write_still=True) + if os.path.isfile(args.render_output): + render_path = os.path.abspath(args.render_output) + + exported_at = utc_now() + export_glb(bpy, objects, args.output) + reimported = verify_reimport(bpy, args.output, STEP_OBJECTS[step_id]) + + response = { + "ok": True, + "blenderVersion": ".".join(str(part) for part in bpy.app.version), + "exportedAt": exported_at, + "objectIds": STEP_OBJECTS[step_id], + "verifiedObjectIds": reimported, + "glbBytes": os.path.getsize(args.output), + } + if render_path is not None: + response["renderPath"] = render_path + response["renderedAt"] = utc_now() + write_result(args.result_output, response) + print("hnx02 result written: " + args.result_output) if __name__ == "__main__": |
