├── .babelrc ├── .editorconfig ├── .eslintrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── dist ├── index.js ├── index.js.map ├── index.min.js └── index.min.js.map ├── gulpfile.js ├── package-lock.json ├── package.json ├── src ├── glb2-bounding-box.js ├── gltf-reader.js ├── gltf1-bounding-box.js ├── gltf2-bounding-box.js ├── index.js └── precise.js └── test ├── .eslintrc ├── example-models ├── suzanne.glb └── suzanne.gltf ├── runner.html ├── setup ├── .globals.json ├── browser.js ├── node.js └── setup.js └── unit ├── glb2-bounding-box.js └── gltf1-bounding-box.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env"], 3 | "plugins": [ 4 | "add-module-exports" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | root = true; 4 | 5 | [*] 6 | # Ensure there's no lingering whitespace 7 | trim_trailing_whitespace = true 8 | # Ensure a newline at the end of each file 9 | insert_final_newline = true 10 | 11 | [*.js] 12 | # Unix-style newlines 13 | end_of_line = lf 14 | charset = utf-8 15 | indent_style = space 16 | indent_size = 2 17 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | "ecmaVersion": 6, 4 | "sourceType": "module" 5 | }, 6 | "rules": {}, 7 | "env": { 8 | "browser": true, 9 | "node": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Commenting this out is preferred by some people, see 24 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 25 | node_modules 26 | bower_components 27 | coverage 28 | tmp 29 | 30 | # Users Environment Variables 31 | .lock-wscript 32 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test/ 2 | .babelrc 3 | .editorconfig 4 | .eslintrc 5 | .travis.yml 6 | gulpfile.js 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6" 4 | - "8" 5 | - "10" 6 | sudo: false 7 | script: "gulp test && gulp coverage" 8 | after_success: 9 | - npm install -g codeclimate-test-reporter 10 | - codeclimate-test-reporter < coverage/lcov.info 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017, Wanadev 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of Wanadev nor the 12 | names of its contributors may be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL WANADEV BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gltf-bounding-box 2 | 3 | Computes the global bounding box of a gltf model 4 | 5 | [![npm version](http://img.shields.io/npm/v/gltf-bounding-box.svg)](https://www.npmjs.com/package/gltf-bounding-box) 6 | [![Travis build status](http://img.shields.io/travis/wanadev/gltf-bounding-box.svg?style=flat)](https://travis-ci.org/wanadev/gltf-bounding-box) 7 | [![Dependency Status](https://david-dm.org/wanadev/gltf-bounding-box.svg)](https://david-dm.org/wanadev/gltf-bounding-box) 8 | [![devDependency Status](https://david-dm.org/wanadev/gltf-bounding-box/dev-status.svg)](https://david-dm.org/wanadev/gltf-bounding-box#info=devDependencies) 9 | 10 | ## Usage 11 | 12 | ```javascript 13 | import gltfBoundingBox from 'gltf-bounding-box'; 14 | 15 | const model = JSON.parse(fs.readFileSync('suzanne.gltf'), 'utf8'); 16 | 17 | const boundings = gltfBoundingBox.computeBoundings(model); 18 | 19 | // boundings: 20 | { 21 | dimensions: { 22 | width: 3, 23 | depth: 2, 24 | height: 2, 25 | }, 26 | center: { 27 | x: 0, 28 | y: 0, 29 | z: 0, 30 | }, 31 | } 32 | ``` 33 | -------------------------------------------------------------------------------- /dist/index.min.js: -------------------------------------------------------------------------------- 1 | !function(t,n){"object"==typeof exports&&"object"==typeof module?module.exports=n():"function"==typeof define&&define.amd?define([],n):"object"==typeof exports?exports.gltfBoundingBox=n():t.gltfBoundingBox=n()}(this,function(){return function(r){var e={};function i(t){if(e[t])return e[t].exports;var n=e[t]={i:t,l:!1,exports:{}};return r[t].call(n.exports,n,n.exports,i),n.l=!0,n.exports}return i.m=r,i.c=e,i.d=function(t,n,r){i.o(t,n)||Object.defineProperty(t,n,{enumerable:!0,get:r})},i.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},i.t=function(n,t){if(1&t&&(n=i(n)),8&t)return n;if(4&t&&"object"==typeof n&&n&&n.__esModule)return n;var r=Object.create(null);if(i.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:n}),2&t&&"string"!=typeof n)for(var e in n)i.d(r,e,function(t){return n[t]}.bind(null,e));return r},i.n=function(t){var n=t&&t.__esModule?function(){return t.default}:function(){return t};return i.d(n,"a",n),n},i.o=function(t,n){return Object.prototype.hasOwnProperty.call(t,n)},i.p="",i(i.s=6)}([function(t,n,r){"use strict";n.Matrix=r(8)},function(t,T,M){(function(O,I){var S;(function(){var Ku,Gu="Expected a function",Ju="__lodash_hash_undefined__",Hu="__lodash_placeholder__",Xu=16,Qu=32,to=64,no=128,ro=256,eo=9007199254740991,io=4294967295,uo=[["ary",no],["bind",1],["bindKey",2],["curry",8],["curryRight",Xu],["flip",512],["partial",Qu],["partialRight",to],["rearg",ro]],oo="[object Arguments]",fo="[object Array]",ao="[object Boolean]",co="[object Date]",so="[object Error]",lo="[object Function]",ho="[object GeneratorFunction]",po="[object Map]",vo="[object Number]",go="[object Object]",_o="[object RegExp]",yo="[object Set]",wo="[object String]",mo="[object Symbol]",bo="[object WeakMap]",Ao="[object ArrayBuffer]",xo="[object DataView]",Eo="[object Float32Array]",Ro="[object Float64Array]",Bo="[object Int8Array]",Po="[object Int16Array]",jo="[object Int32Array]",Oo="[object Uint8Array]",Io="[object Uint8ClampedArray]",So="[object Uint16Array]",To="[object Uint32Array]",Mo=/\b__p \+= '';/g,Uo=/\b(__p \+=) '' \+/g,Lo=/(__e\(.*?\)|\b__t\)) \+\n'';/g,ko=/&(?:amp|lt|gt|quot|#39);/g,Do=/[&<>"']/g,Co=RegExp(ko.source),zo=RegExp(Do.source),No=/<%-([\s\S]+?)%>/g,Yo=/<%([\s\S]+?)%>/g,Wo=/<%=([\s\S]+?)%>/g,Fo=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,$o=/^\w*$/,Vo=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g,qo=/[\\^$.*+?()[\]{}|]/g,Zo=RegExp(qo.source),Ko=/^\s+|\s+$/g,Go=/^\s+/,Jo=/\s+$/,Ho=/\{(?:\n\/\* \[wrapped with .+\] \*\/)?\n?/,Xo=/\{\n\/\* \[wrapped with (.+)\] \*/,Qo=/,? & /,tf=/[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g,nf=/\\(\\)?/g,rf=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g,ef=/\w*$/,uf=/^[-+]0x[0-9a-f]+$/i,of=/^0b[01]+$/i,ff=/^\[object .+?Constructor\]$/,af=/^0o[0-7]+$/i,cf=/^(?:0|[1-9]\d*)$/,sf=/[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g,lf=/($^)/,hf=/['\n\r\u2028\u2029\\]/g,t="\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff",n="\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000",r="["+n+"]",e="["+t+"]",i="\\d+",u="[a-z\\xdf-\\xf6\\xf8-\\xff]",o="[^\\ud800-\\udfff"+n+i+"\\u2700-\\u27bfa-z\\xdf-\\xf6\\xf8-\\xffA-Z\\xc0-\\xd6\\xd8-\\xde]",f="\\ud83c[\\udffb-\\udfff]",a="[^\\ud800-\\udfff]",c="(?:\\ud83c[\\udde6-\\uddff]){2}",s="[\\ud800-\\udbff][\\udc00-\\udfff]",l="[A-Z\\xc0-\\xd6\\xd8-\\xde]",h="(?:"+u+"|"+o+")",p="(?:"+l+"|"+o+")",v="(?:"+e+"|"+f+")?",g="[\\ufe0e\\ufe0f]?"+v+"(?:\\u200d(?:"+[a,c,s].join("|")+")[\\ufe0e\\ufe0f]?"+v+")*",_="(?:"+["[\\u2700-\\u27bf]",c,s].join("|")+")"+g,y="(?:"+[a+e+"?",e,c,s,"[\\ud800-\\udfff]"].join("|")+")",pf=RegExp("['’]","g"),vf=RegExp(e,"g"),d=RegExp(f+"(?="+f+")|"+y+g,"g"),gf=RegExp([l+"?"+u+"+(?:['’](?:d|ll|m|re|s|t|ve))?(?="+[r,l,"$"].join("|")+")",p+"+(?:['’](?:D|LL|M|RE|S|T|VE))?(?="+[r,l+h,"$"].join("|")+")",l+"?"+h+"+(?:['’](?:d|ll|m|re|s|t|ve))?",l+"+(?:['’](?:D|LL|M|RE|S|T|VE))?","\\d*(?:1ST|2ND|3RD|(?![123])\\dTH)(?=\\b|[a-z_])","\\d*(?:1st|2nd|3rd|(?![123])\\dth)(?=\\b|[A-Z_])",i,_].join("|"),"g"),w=RegExp("[\\u200d\\ud800-\\udfff"+t+"\\ufe0e\\ufe0f]"),_f=/[a-z][A-Z]|[A-Z]{2,}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/,yf=["Array","Buffer","DataView","Date","Error","Float32Array","Float64Array","Function","Int8Array","Int16Array","Int32Array","Map","Math","Object","Promise","RegExp","Set","String","Symbol","TypeError","Uint8Array","Uint8ClampedArray","Uint16Array","Uint32Array","WeakMap","_","clearTimeout","isFinite","parseInt","setTimeout"],df=-1,wf={};wf[Eo]=wf[Ro]=wf[Bo]=wf[Po]=wf[jo]=wf[Oo]=wf[Io]=wf[So]=wf[To]=!0,wf[oo]=wf[fo]=wf[Ao]=wf[ao]=wf[xo]=wf[co]=wf[so]=wf[lo]=wf[po]=wf[vo]=wf[go]=wf[_o]=wf[yo]=wf[wo]=wf[bo]=!1;var mf={};mf[oo]=mf[fo]=mf[Ao]=mf[xo]=mf[ao]=mf[co]=mf[Eo]=mf[Ro]=mf[Bo]=mf[Po]=mf[jo]=mf[po]=mf[vo]=mf[go]=mf[_o]=mf[yo]=mf[wo]=mf[mo]=mf[Oo]=mf[Io]=mf[So]=mf[To]=!0,mf[so]=mf[lo]=mf[bo]=!1;var m={"\\":"\\","'":"'","\n":"n","\r":"r","\u2028":"u2028","\u2029":"u2029"},bf=parseFloat,Af=parseInt,b="object"==typeof O&&O&&O.Object===Object&&O,A="object"==typeof self&&self&&self.Object===Object&&self,xf=b||A||Function("return this")(),x="object"==typeof T&&T&&!T.nodeType&&T,E=x&&"object"==typeof I&&I&&!I.nodeType&&I,Ef=E&&E.exports===x,R=Ef&&b.process,B=function(){try{return E&&E.require&&E.require("util").types||R&&R.binding&&R.binding("util")}catch(t){}}(),Rf=B&&B.isArrayBuffer,Bf=B&&B.isDate,Pf=B&&B.isMap,jf=B&&B.isRegExp,Of=B&&B.isSet,If=B&&B.isTypedArray;function Sf(t,n,r){switch(r.length){case 0:return t.call(n);case 1:return t.call(n,r[0]);case 2:return t.call(n,r[0],r[1]);case 3:return t.call(n,r[0],r[1],r[2])}return t.apply(n,r)}function Tf(t,n,r,e){for(var i=-1,u=null==t?0:t.length;++i":">",'"':""","'":"'"});function oa(t){return"\\"+m[t]}function fa(t){return w.test(t)}function aa(t){var r=-1,e=Array(t.size);return t.forEach(function(t,n){e[++r]=[n,t]}),e}function ca(n,r){return function(t){return n(r(t))}}function sa(t,n){for(var r=-1,e=t.length,i=0,u=[];++r",""":'"',"'":"'"}),_a=function t(n){var r,j=(n=null==n?xf:_a.defaults(xf.Object(),n,_a.pick(xf,yf))).Array,e=n.Date,i=n.Error,_=n.Function,u=n.Math,x=n.Object,y=n.RegExp,s=n.String,O=n.TypeError,o=j.prototype,f=_.prototype,l=x.prototype,a=n["__core-js_shared__"],c=f.toString,E=l.hasOwnProperty,h=0,p=(r=/[^.]+$/.exec(a&&a.keys&&a.keys.IE_PROTO||""))?"Symbol(src)_1."+r:"",v=l.toString,g=c.call(x),d=xf._,w=y("^"+c.call(E).replace(qo,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$"),m=Ef?n.Buffer:Ku,b=n.Symbol,A=n.Uint8Array,R=m?m.allocUnsafe:Ku,B=ca(x.getPrototypeOf,x),P=x.create,I=l.propertyIsEnumerable,S=o.splice,T=b?b.isConcatSpreadable:Ku,M=b?b.iterator:Ku,U=b?b.toStringTag:Ku,L=function(){try{var t=Ur(x,"defineProperty");return t({},"",{}),t}catch(t){}}(),k=n.clearTimeout!==xf.clearTimeout&&n.clearTimeout,D=e&&e.now!==xf.Date.now&&e.now,C=n.setTimeout!==xf.setTimeout&&n.setTimeout,z=u.ceil,N=u.floor,Y=x.getOwnPropertySymbols,W=m?m.isBuffer:Ku,F=n.isFinite,$=o.join,V=ca(x.keys,x),q=u.max,Z=u.min,K=e.now,G=n.parseInt,J=u.random,H=o.reverse,X=Ur(n,"DataView"),Q=Ur(n,"Map"),tt=Ur(n,"Promise"),nt=Ur(n,"Set"),rt=Ur(n,"WeakMap"),et=Ur(x,"create"),it=rt&&new rt,ut={},ot=fe(X),ft=fe(Q),at=fe(tt),ct=fe(nt),st=fe(rt),lt=b?b.prototype:Ku,ht=lt?lt.valueOf:Ku,pt=lt?lt.toString:Ku;function vt(t){if(Ei(t)&&!pi(t)&&!(t instanceof dt)){if(t instanceof yt)return t;if(E.call(t,"__wrapped__"))return ae(t)}return new yt(t)}var gt=function(){function r(){}return function(t){if(!xi(t))return{};if(P)return P(t);r.prototype=t;var n=new r;return r.prototype=Ku,n}}();function _t(){}function yt(t,n){this.__wrapped__=t,this.__actions__=[],this.__chain__=!!n,this.__index__=0,this.__values__=Ku}function dt(t){this.__wrapped__=t,this.__actions__=[],this.__dir__=1,this.__filtered__=!1,this.__iteratees__=[],this.__takeCount__=io,this.__views__=[]}function wt(t){var n=-1,r=null==t?0:t.length;for(this.clear();++n>>0,n>>>=0;for(var u=j(i);++e>>1,o=t[u];null!==o&&!Si(o)&&(r?o<=n:o>>0)?(t=Yi(t))&&("string"==typeof n||null!=n&&!ji(n))&&!(n=Tn(n))&&fa(t)?$n(va(t),0,r):t.split(n,r):[]},vt.spread=function(e,i){if("function"!=typeof e)throw new O(Gu);return i=null==i?0:q(Di(i),0),An(function(t){var n=t[i],r=$n(t,0,i);return n&&zf(r,n),Sf(e,this,r)})},vt.tail=function(t){var n=null==t?0:t.length;return n?Bn(t,1,n):[]},vt.take=function(t,n,r){return t&&t.length?Bn(t,0,(n=r||n===Ku?1:Di(n))<0?0:n):[]},vt.takeRight=function(t,n,r){var e=null==t?0:t.length;return e?Bn(t,(n=e-(n=r||n===Ku?1:Di(n)))<0?0:n,e):[]},vt.takeRightWhile=function(t,n){return t&&t.length?kn(t,Sr(n,3),!1,!0):[]},vt.takeWhile=function(t,n){return t&&t.length?kn(t,Sr(n,3)):[]},vt.tap=function(t,n){return n(t),t},vt.throttle=function(t,n,r){var e=!0,i=!0;if("function"!=typeof t)throw new O(Gu);return xi(r)&&(e="leading"in r?!!r.leading:e,i="trailing"in r?!!r.trailing:i),ti(t,n,{leading:e,maxWait:n,trailing:i})},vt.thru=ke,vt.toArray=Li,vt.toPairs=fu,vt.toPairsIn=au,vt.toPath=function(t){return pi(t)?Cf(t,oe):Si(t)?[t]:Xn(ue(Yi(t)))},vt.toPlainObject=Ni,vt.transform=function(t,e,i){var n=pi(t),r=n||yi(t)||Ti(t);if(e=Sr(e,4),null==i){var u=t&&t.constructor;i=r?n?new u:[]:xi(t)&&mi(u)?gt(B(t)):{}}return(r?Mf:qt)(t,function(t,n,r){return e(i,t,n,r)}),i},vt.unary=function(t){return Je(t,1)},vt.union=Ee,vt.unionBy=Re,vt.unionWith=Be,vt.uniq=function(t){return t&&t.length?Mn(t):[]},vt.uniqBy=function(t,n){return t&&t.length?Mn(t,Sr(n,2)):[]},vt.uniqWith=function(t,n){return n="function"==typeof n?n:Ku,t&&t.length?Mn(t,Ku,n):[]},vt.unset=function(t,n){return null==t||Un(t,n)},vt.unzip=Pe,vt.unzipWith=je,vt.update=function(t,n,r){return null==t?t:Ln(t,n,Yn(r))},vt.updateWith=function(t,n,r,e){return e="function"==typeof e?e:Ku,null==t?t:Ln(t,n,Yn(r),e)},vt.values=cu,vt.valuesIn=function(t){return null==t?[]:ta(t,nu(t))},vt.without=Oe,vt.words=mu,vt.wrap=function(t,n){return oi(Yn(n),t)},vt.xor=Ie,vt.xorBy=Se,vt.xorWith=Te,vt.zip=Me,vt.zipObject=function(t,n){return zn(t||[],n||[],Pt)},vt.zipObjectDeep=function(t,n){return zn(t||[],n||[],xn)},vt.zipWith=Ue,vt.entries=fu,vt.entriesIn=au,vt.extend=Fi,vt.extendWith=$i,Iu(vt,vt),vt.add=Yu,vt.attempt=bu,vt.camelCase=su,vt.capitalize=lu,vt.ceil=Wu,vt.clamp=function(t,n,r){return r===Ku&&(r=n,n=Ku),r!==Ku&&(r=(r=zi(r))==r?r:0),n!==Ku&&(n=(n=zi(n))==n?n:0),Mt(zi(t),n,r)},vt.clone=function(t){return Ut(t,4)},vt.cloneDeep=function(t){return Ut(t,5)},vt.cloneDeepWith=function(t,n){return Ut(t,5,n="function"==typeof n?n:Ku)},vt.cloneWith=function(t,n){return Ut(t,4,n="function"==typeof n?n:Ku)},vt.conformsTo=function(t,n){return null==n||Lt(t,n,tu(n))},vt.deburr=hu,vt.defaultTo=function(t,n){return null==t||t!=t?n:t},vt.divide=Fu,vt.endsWith=function(t,n,r){t=Yi(t),n=Tn(n);var e=t.length,i=r=r===Ku?e:Mt(Di(r),0,e);return 0<=(r-=n.length)&&t.slice(r,i)==n},vt.eq=ci,vt.escape=function(t){return(t=Yi(t))&&zo.test(t)?t.replace(Do,ua):t},vt.escapeRegExp=function(t){return(t=Yi(t))&&Zo.test(t)?t.replace(qo,"\\$&"):t},vt.every=function(t,n,r){var e=pi(t)?Uf:Nt;return r&&Wr(t,n,r)&&(n=Ku),e(t,Sr(n,3))},vt.find=ze,vt.findIndex=he,vt.findKey=function(t,n){return Ff(t,Sr(n,3),qt)},vt.findLast=Ne,vt.findLastIndex=pe,vt.findLastKey=function(t,n){return Ff(t,Sr(n,3),Zt)},vt.floor=$u,vt.forEach=Ye,vt.forEachRight=We,vt.forIn=function(t,n){return null==t?t:$t(t,Sr(n,3),nu)},vt.forInRight=function(t,n){return null==t?t:Vt(t,Sr(n,3),nu)},vt.forOwn=function(t,n){return t&&qt(t,Sr(n,3))},vt.forOwnRight=function(t,n){return t&&Zt(t,Sr(n,3))},vt.get=Gi,vt.gt=si,vt.gte=li,vt.has=function(t,n){return null!=t&&Cr(t,n,Qt)},vt.hasIn=Ji,vt.head=ge,vt.identity=Bu,vt.includes=function(t,n,r,e){t=gi(t)?t:cu(t),r=r&&!e?Di(r):0;var i=t.length;return r<0&&(r=q(i+r,0)),Ii(t)?r<=i&&-1=Z(i=n,u=r)&&e=this.__values__.length;return{done:t,value:t?Ku:this.__values__[this.__index__++]}},vt.prototype.plant=function(t){for(var n,r=this;r instanceof _t;){var e=ae(r);e.__index__=0,e.__values__=Ku,n?i.__wrapped__=e:n=e;var i=e;r=r.__wrapped__}return i.__wrapped__=t,n},vt.prototype.reverse=function(){var t=this.__wrapped__;if(t instanceof dt){var n=t;return this.__actions__.length&&(n=new dt(this)),(n=n.reverse()).__actions__.push({func:ke,args:[xe],thisArg:Ku}),new yt(n,this.__chain__)}return this.thru(xe)},vt.prototype.toJSON=vt.prototype.valueOf=vt.prototype.value=function(){return Dn(this.__wrapped__,this.__actions__)},vt.prototype.first=vt.prototype.head,M&&(vt.prototype[M]=function(){return this}),vt}();xf._=_a,(S=function(){return _a}.call(T,M,T,I))===Ku||(I.exports=S)}).call(this)}).call(this,M(2),M(10)(t))},function(uV,vV){var wV;wV=function(){return this}();try{wV=wV||Function("return this")()||eval("this")}catch(uV){"object"==typeof window&&(wV=window)}uV.exports=wV},function(t,n,r){"use strict";(function(i){Object.defineProperty(n,"__esModule",{value:!0});var c={loadPositions:function(n,t){var r=2r[n]?t:r[n]}),t},{min:[1/0,1/0,1/0],max:[-1/0,-1/0,-1/0]}),o=i?f.default.ceil:f.default.round;return{dimensions:{width:o(u.max[0]-u.min[0],e),depth:o(u.max[2]-u.min[2],e),height:o(u.max[1]-u.min[1],e)},center:{x:f.default.round((u.max[0]+u.min[0])/2,e+1),y:f.default.round((u.max[2]+u.min[2])/2,e+1),z:f.default.round((u.max[1]+u.min[1])/2,e+1)}}},getMeshesTransformMatrices:function(u,o,f){var a=this;return u.forEach(function(t,n){return t.index=n}),u.filter(function(t){return void 0!==t.mesh}).reduce(function(t,n){var r=a.getParentNodesMatrices(n,u).map(function(t){return new c.Matrix(4,4,!1).setData(t)}),e=c.Matrix.multiply.apply(c.Matrix,l(r)),i=a.getPointsFromArray((0,s.loadPositions)(o,n.mesh,f)).map(function(t){return c.Matrix.multiply(t,e)});return t.concat(i)},[])},getParentNodesMatrices:function(n,t){var r=t.find(function(t){return t.children&&(0,i.includes)(t.children,n.index)}),e=n.matrix||[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1];return void 0!==r?[e].concat(l(this.getParentNodesMatrices(r,t))).filter(function(t){return t}):[e]},getPointsFromArray:function(t){for(var n=[],r=0;rr[n]?t:r[n]}),t},{min:[1/0,1/0,1/0],max:[-1/0,-1/0,-1/0]}),u=!0===e?o.default.ceil:o.default.round;return{dimensions:{width:u(i.max[0]-i.min[0],r),depth:u(i.max[2]-i.min[2],r),height:u(i.max[1]-i.min[1],r)},center:{x:o.default.round((i.max[0]+i.min[0])/2,r+1),y:o.default.round((i.max[2]+i.min[2])/2,r+1),z:o.default.round((i.max[1]+i.min[1])/2,r+1)}}},getMeshesTransformMatrices:function(f,a){var c=this;return Object.keys(f).filter(function(t){return f[t].meshes}).reduce(function(t,n){return[].concat(h(t),h(f[n].meshes.map(function(t){return{mesh:t,nodeName:n}})))},[]).reduce(function(t,n){var r=n.mesh,e=n.nodeName,i=c.getParentNodesMatrices(e,f).map(function(t){return new s.Matrix(4,4,!1).setData(t)}),u=s.Matrix.multiply.apply(s.Matrix,h(i)),o=c.getPointsFromArray((0,l.loadPositions)(a,r)).map(function(t){return s.Matrix.multiply(t,u)});return t.concat(o)},[])},getParentNodesMatrices:function(n,r){var t=Object.keys(r).find(function(t){return r[t].children&&(0,i.includes)(r[t].children,n)}),e=r[n].matrix||[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1];return t?[e].concat(h(this.getParentNodesMatrices(t,r))).filter(function(t){return t}):[e]},getPointsFromArray:function(t){for(var n=[],r=0;r=r())throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+r().toString(16)+" bytes");return 0|t}function p(t,n){if(l.isBuffer(t))return t.length;if("undefined"!=typeof ArrayBuffer&&"function"==typeof ArrayBuffer.isView&&(ArrayBuffer.isView(t)||t instanceof ArrayBuffer))return t.byteLength;"string"!=typeof t&&(t=""+t);var r=t.length;if(0===r)return 0;for(var e=!1;;)switch(n){case"ascii":case"latin1":case"binary":return r;case"utf8":case"utf-8":case void 0:return M(t).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*r;case"hex":return r>>>1;case"base64":return U(t).length;default:if(e)return M(t).length;n=(""+n).toLowerCase(),e=!0}}function v(t,n,r){var e=t[n];t[n]=t[r],t[r]=e}function g(t,n,r,e,i){if(0===t.length)return-1;if("string"==typeof r?(e=r,r=0):2147483647=t.length){if(i)return-1;r=t.length-1}else if(r<0){if(!i)return-1;r=0}if("string"==typeof n&&(n=l.from(n,e)),l.isBuffer(n))return 0===n.length?-1:_(t,n,r,e,i);if("number"==typeof n)return n&=255,l.TYPED_ARRAY_SUPPORT&&"function"==typeof Uint8Array.prototype.indexOf?i?Uint8Array.prototype.indexOf.call(t,n,r):Uint8Array.prototype.lastIndexOf.call(t,n,r):_(t,[n],r,e,i);throw new TypeError("val must be string, number or Buffer")}function _(t,n,r,e,i){var u,o=1,f=t.length,a=n.length;if(void 0!==e&&("ucs2"===(e=String(e).toLowerCase())||"ucs-2"===e||"utf16le"===e||"utf-16le"===e)){if(t.length<2||n.length<2)return-1;f/=o=2,a/=2,r/=2}function c(t,n){return 1===o?t[n]:t.readUInt16BE(n*o)}if(i){var s=-1;for(u=r;ui&&(e=i):e=i;var u=n.length;if(u%2!=0)throw new TypeError("Invalid hex string");u/2>>10&1023|55296),s=56320|1023&s),e.push(s),i+=l}return function(t){var n=t.length;if(n<=m)return String.fromCharCode.apply(String,t);for(var r="",e=0;ethis.length)return"";if((void 0===r||r>this.length)&&(r=this.length),r<=0)return"";if((r>>>=0)<=(n>>>=0))return"";for(t||(t="utf8");;)switch(t){case"hex":return x(this,n,r);case"utf8":case"utf-8":return w(this,n,r);case"ascii":return b(this,n,r);case"latin1":case"binary":return A(this,n,r);case"base64":return e=this,u=r,0===(i=n)&&u===e.length?f.fromByteArray(e):f.fromByteArray(e.slice(i,u));case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return E(this,n,r);default:if(o)throw new TypeError("Unknown encoding: "+t);t=(t+"").toLowerCase(),o=!0}}.apply(this,arguments)},l.prototype.equals=function(t){if(!l.isBuffer(t))throw new TypeError("Argument must be a Buffer");return this===t||0===l.compare(this,t)},l.prototype.inspect=function(){var t="",n=k.INSPECT_MAX_BYTES;return 0n&&(t+=" ... ")),""},l.prototype.compare=function(t,n,r,e,i){if(!l.isBuffer(t))throw new TypeError("Argument must be a Buffer");if(void 0===n&&(n=0),void 0===r&&(r=t?t.length:0),void 0===e&&(e=0),void 0===i&&(i=this.length),n<0||r>t.length||e<0||i>this.length)throw new RangeError("out of range index");if(i<=e&&r<=n)return 0;if(i<=e)return-1;if(r<=n)return 1;if(this===t)return 0;for(var u=(i>>>=0)-(e>>>=0),o=(r>>>=0)-(n>>>=0),f=Math.min(u,o),a=this.slice(e,i),c=t.slice(n,r),s=0;sthis.length)throw new RangeError("Attempt to write outside buffer bounds");e||(e="utf8");for(var v=!1;;)switch(e){case"hex":return y(this,t,n,r);case"utf8":case"utf-8":return l=n,h=r,L(M(t,(s=this).length-l),s,l,h);case"ascii":return d(this,t,n,r);case"latin1":case"binary":return d(this,t,n,r);case"base64":return f=this,a=n,c=r,L(U(t),f,a,c);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return u=n,o=r,L(function(t,n){for(var r,e,i,u=[],o=0;o>8,i=r%256,u.push(i),u.push(e);return u}(t,(i=this).length-u),i,u,o);default:if(v)throw new TypeError("Unknown encoding: "+e);e=(""+e).toLowerCase(),v=!0}},l.prototype.toJSON=function(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};var m=4096;function b(t,n,r){var e="";r=Math.min(t.length,r);for(var i=n;it.length)throw new RangeError("Index out of range")}function P(t,n,r,e){n<0&&(n=65535+n+1);for(var i=0,u=Math.min(t.length-r,2);i>>8*(e?i:1-i)}function j(t,n,r,e){n<0&&(n=4294967295+n+1);for(var i=0,u=Math.min(t.length-r,4);i>>8*(e?i:3-i)&255}function O(t,n,r,e,i,u){if(r+e>t.length)throw new RangeError("Index out of range");if(r<0)throw new RangeError("Index out of range")}function I(t,n,r,e,i){return i||O(t,0,r,4),u.write(t,n,r,e,23,4),r+4}function S(t,n,r,e,i){return i||O(t,0,r,8),u.write(t,n,r,e,52,8),r+8}l.prototype.slice=function(t,n){var r,e=this.length;if((t=~~t)<0?(t+=e)<0&&(t=0):e=(i*=128)&&(e-=Math.pow(2,8*n)),e},l.prototype.readIntBE=function(t,n,r){t|=0,n|=0,r||R(t,n,this.length);for(var e=n,i=1,u=this[t+--e];0=(i*=128)&&(u-=Math.pow(2,8*n)),u},l.prototype.readInt8=function(t,n){return n||R(t,1,this.length),128&this[t]?-1*(255-this[t]+1):this[t]},l.prototype.readInt16LE=function(t,n){n||R(t,2,this.length);var r=this[t]|this[t+1]<<8;return 32768&r?4294901760|r:r},l.prototype.readInt16BE=function(t,n){n||R(t,2,this.length);var r=this[t+1]|this[t]<<8;return 32768&r?4294901760|r:r},l.prototype.readInt32LE=function(t,n){return n||R(t,4,this.length),this[t]|this[t+1]<<8|this[t+2]<<16|this[t+3]<<24},l.prototype.readInt32BE=function(t,n){return n||R(t,4,this.length),this[t]<<24|this[t+1]<<16|this[t+2]<<8|this[t+3]},l.prototype.readFloatLE=function(t,n){return n||R(t,4,this.length),u.read(this,t,!0,23,4)},l.prototype.readFloatBE=function(t,n){return n||R(t,4,this.length),u.read(this,t,!1,23,4)},l.prototype.readDoubleLE=function(t,n){return n||R(t,8,this.length),u.read(this,t,!0,52,8)},l.prototype.readDoubleBE=function(t,n){return n||R(t,8,this.length),u.read(this,t,!1,52,8)},l.prototype.writeUIntLE=function(t,n,r,e){t=+t,n|=0,r|=0,e||B(this,t,n,r,Math.pow(2,8*r)-1,0);var i=1,u=0;for(this[n]=255&t;++u>>8):P(this,t,n,!0),n+2},l.prototype.writeUInt16BE=function(t,n,r){return t=+t,n|=0,r||B(this,t,n,2,65535,0),l.TYPED_ARRAY_SUPPORT?(this[n]=t>>>8,this[n+1]=255&t):P(this,t,n,!1),n+2},l.prototype.writeUInt32LE=function(t,n,r){return t=+t,n|=0,r||B(this,t,n,4,4294967295,0),l.TYPED_ARRAY_SUPPORT?(this[n+3]=t>>>24,this[n+2]=t>>>16,this[n+1]=t>>>8,this[n]=255&t):j(this,t,n,!0),n+4},l.prototype.writeUInt32BE=function(t,n,r){return t=+t,n|=0,r||B(this,t,n,4,4294967295,0),l.TYPED_ARRAY_SUPPORT?(this[n]=t>>>24,this[n+1]=t>>>16,this[n+2]=t>>>8,this[n+3]=255&t):j(this,t,n,!1),n+4},l.prototype.writeIntLE=function(t,n,r,e){if(t=+t,n|=0,!e){var i=Math.pow(2,8*r-1);B(this,t,n,r,i-1,-i)}var u=0,o=1,f=0;for(this[n]=255&t;++u>0)-f&255;return n+r},l.prototype.writeIntBE=function(t,n,r,e){if(t=+t,n|=0,!e){var i=Math.pow(2,8*r-1);B(this,t,n,r,i-1,-i)}var u=r-1,o=1,f=0;for(this[n+u]=255&t;0<=--u&&(o*=256);)t<0&&0===f&&0!==this[n+u+1]&&(f=1),this[n+u]=(t/o>>0)-f&255;return n+r},l.prototype.writeInt8=function(t,n,r){return t=+t,n|=0,r||B(this,t,n,1,127,-128),l.TYPED_ARRAY_SUPPORT||(t=Math.floor(t)),t<0&&(t=255+t+1),this[n]=255&t,n+1},l.prototype.writeInt16LE=function(t,n,r){return t=+t,n|=0,r||B(this,t,n,2,32767,-32768),l.TYPED_ARRAY_SUPPORT?(this[n]=255&t,this[n+1]=t>>>8):P(this,t,n,!0),n+2},l.prototype.writeInt16BE=function(t,n,r){return t=+t,n|=0,r||B(this,t,n,2,32767,-32768),l.TYPED_ARRAY_SUPPORT?(this[n]=t>>>8,this[n+1]=255&t):P(this,t,n,!1),n+2},l.prototype.writeInt32LE=function(t,n,r){return t=+t,n|=0,r||B(this,t,n,4,2147483647,-2147483648),l.TYPED_ARRAY_SUPPORT?(this[n]=255&t,this[n+1]=t>>>8,this[n+2]=t>>>16,this[n+3]=t>>>24):j(this,t,n,!0),n+4},l.prototype.writeInt32BE=function(t,n,r){return t=+t,n|=0,r||B(this,t,n,4,2147483647,-2147483648),t<0&&(t=4294967295+t+1),l.TYPED_ARRAY_SUPPORT?(this[n]=t>>>24,this[n+1]=t>>>16,this[n+2]=t>>>8,this[n+3]=255&t):j(this,t,n,!1),n+4},l.prototype.writeFloatLE=function(t,n,r){return I(this,t,n,!0,r)},l.prototype.writeFloatBE=function(t,n,r){return I(this,t,n,!1,r)},l.prototype.writeDoubleLE=function(t,n,r){return S(this,t,n,!0,r)},l.prototype.writeDoubleBE=function(t,n,r){return S(this,t,n,!1,r)},l.prototype.copy=function(t,n,r,e){if(r||(r=0),e||0===e||(e=this.length),n>=t.length&&(n=t.length),n||(n=0),0=this.length)throw new RangeError("sourceStart out of bounds");if(e<0)throw new RangeError("sourceEnd out of bounds");e>this.length&&(e=this.length),t.length-n>>=0,r=void 0===r?this.length:r>>>0,t||(t=0),"number"==typeof t)for(u=n;u>6|192,63&r|128)}else if(r<65536){if((n-=3)<0)break;u.push(r>>12|224,r>>6&63|128,63&r|128)}else{if(!(r<1114112))throw new Error("Invalid code point");if((n-=4)<0)break;u.push(r>>18|240,r>>12&63|128,r>>6&63|128,63&r|128)}}return u}function U(t){return f.toByteArray(function(t){if((t=(n=t,n.trim?n.trim():n.replace(/^\s+|\s+$/g,"")).replace(T,"")).length<2)return"";for(var n;t.length%4!=0;)t+="=";return t}(t))}function L(t,n,r,e){for(var i=0;i=n.length||i>=t.length);++i)n[i+r]=t[i];return i}}).call(this,n(2))},function(t,n,r){"use strict";n.byteLength=function(t){var n=l(t),r=n[0],e=n[1];return 3*(r+e)/4-e},n.toByteArray=function(t){for(var n,r=l(t),e=r[0],i=r[1],u=new s(3*(e+i)/4-i),o=0,f=0>16&255,u[o++]=n>>8&255,u[o++]=255&n;return 2===i&&(n=c[t.charCodeAt(a)]<<2|c[t.charCodeAt(a+1)]>>4,u[o++]=255&n),1===i&&(n=c[t.charCodeAt(a)]<<10|c[t.charCodeAt(a+1)]<<4|c[t.charCodeAt(a+2)]>>2,u[o++]=n>>8&255,u[o++]=255&n),u},n.fromByteArray=function(t){for(var n,r=t.length,e=r%3,i=[],u=0,o=r-e;u>2]+f[n<<4&63]+"==")):2===e&&(n=(t[r-2]<<8)+t[r-1],i.push(f[n>>10]+f[n>>4&63]+f[n<<2&63]+"=")),i.join("")};for(var f=[],c=[],s="undefined"!=typeof Uint8Array?Uint8Array:Array,e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",i=0,u=e.length;i>18&63]+f[o>>12&63]+f[o>>6&63]+f[63&o]);var o;return i.join("")}c["-".charCodeAt(0)]=62,c["_".charCodeAt(0)]=63},function(t,n){n.read=function(t,n,r,e,i){var u,o,f=8*i-e-1,a=(1<>1,s=-7,l=r?i-1:0,h=r?-1:1,p=t[n+l];for(l+=h,u=p&(1<<-s)-1,p>>=-s,s+=f;0>=-s,s+=e;0>1,h=23===i?Math.pow(2,-24)-Math.pow(2,-77):0,p=e?0:u-1,v=e?1:-1,g=n<0||0===n&&1/n<0?1:0;for(n=Math.abs(n),isNaN(n)||n===1/0?(f=isNaN(n)?1:0,o=s):(o=Math.floor(Math.log(n)/Math.LN2),n*(a=Math.pow(2,-o))<1&&(o--,a*=2),2<=(n+=1<=o+l?h/a:h*Math.pow(2,1-l))*a&&(o++,a/=2),s<=o+l?(f=0,o=s):1<=o+l?(f=(n*a-1)*Math.pow(2,i),o+=l):(f=n*Math.pow(2,l-1)*Math.pow(2,i),o=0));8<=i;t[r+p]=255&f,p+=v,f/=256,i-=8);for(o=o< done()); 25 | } 26 | 27 | function cleanTmp(done) { 28 | del(['tmp']).then(() => done()); 29 | } 30 | 31 | // Lint a set of files 32 | function lint(files) { 33 | return gulp.src(files) 34 | .pipe($.eslint()) 35 | .pipe($.eslint.format()) 36 | .pipe($.eslint.failAfterError()); 37 | } 38 | 39 | function lintSrc() { 40 | return lint('src/**/*.js'); 41 | } 42 | 43 | function lintTest() { 44 | return lint('test/**/*.js'); 45 | } 46 | 47 | function lintGulpfile() { 48 | return lint('gulpfile.js'); 49 | } 50 | 51 | function build() { 52 | return gulp.src(path.join('src', config.entryFileName)) 53 | .pipe(webpackStream({ 54 | output: { 55 | filename: `${exportFileName}.js`, 56 | libraryTarget: 'umd', 57 | library: config.mainVarName, 58 | globalObject: 'this' 59 | }, 60 | mode: "production", 61 | externals: {}, 62 | module: { 63 | rules: [ 64 | {test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader'} 65 | ] 66 | }, 67 | devtool: 'source-map' 68 | })) 69 | .pipe(gulp.dest(destinationFolder)) 70 | .pipe($.filter(['**', '!**/*.js.map'])) 71 | .pipe($.rename(`${exportFileName}.min.js`)) 72 | .pipe($.sourcemaps.init({loadMaps: true})) 73 | .pipe($.uglify()) 74 | .pipe($.sourcemaps.write('./')) 75 | .pipe(gulp.dest(destinationFolder)); 76 | } 77 | 78 | function _mocha() { 79 | return gulp.src(['test/setup/node.js', 'test/unit/**/*.js'], {read: false}) 80 | .pipe($.mocha({ 81 | reporter: 'dot', 82 | globals: Object.keys(mochaGlobals.globals), 83 | ignoreLeaks: false 84 | })); 85 | } 86 | 87 | function _registerBabel() { 88 | require('babel-register'); 89 | } 90 | 91 | function test() { 92 | _registerBabel(); 93 | return _mocha(); 94 | } 95 | 96 | function coverage(done) { 97 | _registerBabel(); 98 | gulp.src(['src/**/*.js']) 99 | .pipe($.istanbul({ 100 | instrumenter: Instrumenter, 101 | includeUntested: true 102 | })) 103 | .pipe($.istanbul.hookRequire()) 104 | .on('finish', () => { 105 | return test() 106 | .pipe($.istanbul.writeReports()) 107 | .on('end', done); 108 | }); 109 | } 110 | 111 | const watchFiles = ['src/**/*', 'test/**/*', 'package.json', '**/.eslintrc']; 112 | 113 | // Run the headless unit tests as you make changes. 114 | function watch() { 115 | gulp.watch(watchFiles, ['test']); 116 | } 117 | 118 | function testBrowser() { 119 | // Our testing bundle is made up of our unit tests, which 120 | // should individually load up pieces of our application. 121 | // We also include the browser setup file. 122 | const testFiles = glob.sync('./test/unit/**/*.js'); 123 | const allFiles = ['./test/setup/browser.js'].concat(testFiles); 124 | 125 | // Lets us differentiate between the first build and subsequent builds 126 | var firstBuild = true; 127 | 128 | // This empty stream might seem like a hack, but we need to specify all of our files through 129 | // the `entry` option of webpack. Otherwise, it ignores whatever file(s) are placed in here. 130 | return gulp.src('') 131 | .pipe($.plumber()) 132 | .pipe(webpackStream({ 133 | watch: true, 134 | entry: allFiles, 135 | output: { 136 | filename: '__spec-build.js' 137 | }, 138 | // Externals isn't necessary here since these are for tests. 139 | module: { 140 | loaders: [ 141 | // This is what allows us to author in future JavaScript 142 | {test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader'}, 143 | // This allows the test setup scripts to load `package.json` 144 | {test: /\.json$/, exclude: /node_modules/, loader: 'json-loader'} 145 | ] 146 | }, 147 | plugins: [ 148 | // By default, webpack does `n=>n` compilation with entry files. This concatenates 149 | // them into a single chunk. 150 | new webpack.optimize.LimitChunkCountPlugin({maxChunks: 1}) 151 | ], 152 | devtool: 'inline-source-map' 153 | }, null, () => { 154 | if (firstBuild) { 155 | $.livereload.listen({port: 35729, host: 'localhost', start: true}); 156 | gulp.watch(watchFiles, ['lint']); 157 | } else { 158 | $.livereload.reload('./tmp/__spec-build.js'); 159 | } 160 | firstBuild = false; 161 | })) 162 | .pipe(gulp.dest('./tmp')); 163 | } 164 | 165 | // Remove the built files 166 | gulp.task('clean', cleanDist); 167 | 168 | // Remove our temporary files 169 | gulp.task('clean-tmp', cleanTmp); 170 | 171 | // Lint our source code 172 | gulp.task('lint-src', lintSrc); 173 | 174 | // Lint our test code 175 | gulp.task('lint-test', lintTest); 176 | 177 | // Lint this file 178 | gulp.task('lint-gulpfile', lintGulpfile); 179 | 180 | // Lint everything 181 | gulp.task('lint', ['lint-src', 'lint-test', 'lint-gulpfile']); 182 | 183 | // Build two versions of the library 184 | gulp.task('build', ['lint', 'clean'], build); 185 | 186 | // Lint and run our tests 187 | gulp.task('test', ['lint'], test); 188 | 189 | // Set up coverage and run tests 190 | gulp.task('coverage', ['lint'], coverage); 191 | 192 | // Set up a livereload environment for our spec runner `test/runner.html` 193 | gulp.task('test-browser', ['lint', 'clean-tmp'], testBrowser); 194 | 195 | // Run the headless unit tests as you make changes. 196 | gulp.task('watch', watch); 197 | 198 | // An alias of test 199 | gulp.task('default', ['test']); 200 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gltf-bounding-box", 3 | "version": "0.4.1", 4 | "description": "Computes the global bounding box of a gltf model", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "test": "gulp", 8 | "lint": "gulp lint", 9 | "test-browser": "gulp test-browser", 10 | "watch": "gulp watch", 11 | "build": "gulp build", 12 | "coverage": "gulp coverage" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/wanadev/gltf-bounding-box.git" 17 | }, 18 | "keywords": [ 19 | "gltf", 20 | "gltf2", 21 | "boudings", 22 | "3d", 23 | "models" 24 | ], 25 | "author": "Wanadev ", 26 | "license": "BSD-3-Clause", 27 | "bugs": { 28 | "url": "https://github.com/wanadev/gltf-bounding-box/issues" 29 | }, 30 | "homepage": "https://github.com/wanadev/gltf-bounding-box", 31 | "devDependencies": { 32 | "babel-core": "6.26.3", 33 | "babel-loader": "7.1.5", 34 | "babel-plugin-add-module-exports": "^0.2.1", 35 | "babel-preset-env": "^1.7.0", 36 | "babel-register": "6.26.0", 37 | "chai": "4.1.2", 38 | "del": "3.0.0", 39 | "glob": "7.1.2", 40 | "gulp": "3.9.1", 41 | "gulp-eslint": "5.0.0", 42 | "gulp-filter": "5.1.0", 43 | "gulp-istanbul": "1.1.3", 44 | "gulp-livereload": "3.8.1", 45 | "gulp-load-plugins": "1.5.0", 46 | "gulp-mocha": "6.0.0", 47 | "gulp-plumber": "1.2.0", 48 | "gulp-rename": "1.4.0", 49 | "gulp-sourcemaps": "2.6.4", 50 | "gulp-uglify": "3.0.1", 51 | "isparta": "4.1.0", 52 | "json-loader": "0.5.7", 53 | "mocha": "5.2.0", 54 | "natives": "1.1.6", 55 | "sinon": "6.1.4", 56 | "sinon-chai": "3.2.0", 57 | "webpack": "4.16.5", 58 | "webpack-stream": "5.1.1" 59 | }, 60 | "babelBoilerplateOptions": { 61 | "entryFileName": "index.js", 62 | "mainVarName": "gltfBoundingBox" 63 | }, 64 | "dependencies": { 65 | "lodash": "^4.17.5", 66 | "matrixmath": "^2.2.2" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/glb2-bounding-box.js: -------------------------------------------------------------------------------- 1 | import gltf2BoundingBox from './gltf2-bounding-box'; 2 | 3 | const glb2BoundingBox = { 4 | 5 | computeBoundings(glb, options) { 6 | // Extract json chunk 7 | const jsonChunkLength = glb.readUInt32LE(12); 8 | const jsonChunkData = glb.slice(20, 20 + jsonChunkLength); 9 | const gltf = JSON.parse(jsonChunkData.toString()); 10 | 11 | // Extract bin chunk 12 | const binChunkOffset = 20 + jsonChunkLength; 13 | const binChunkLength = glb.readUInt32LE(binChunkOffset); 14 | const binChunkData = glb.slice(binChunkOffset + 8, binChunkOffset + 8 + binChunkLength); 15 | 16 | return gltf2BoundingBox.computeBoundings(gltf, [binChunkData], options); 17 | }, 18 | 19 | }; 20 | 21 | export default glb2BoundingBox; 22 | -------------------------------------------------------------------------------- /src/gltf-reader.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const gltfReader = { 4 | /** 5 | * @private 6 | * @param {Object} gltf 7 | * @param {String|Number} meshName A number in glTF2 8 | * @param {Object} [buffers={}] External buffers associations uri -> buffer 9 | * @return {Object} Mesh geometry data 10 | */ 11 | loadPositions(gltf, meshName, buffers = {}) { 12 | const mesh = gltf.meshes[meshName]; 13 | const primitivesCount = mesh.primitives ? mesh.primitives.length : 0; 14 | 15 | if (primitivesCount === 0) { 16 | console.error("gltfReader: Mesh has no primitive."); 17 | return null; 18 | } 19 | 20 | let positions = []; 21 | mesh.primitives.forEach((primitive) => { 22 | // Attributes 23 | if (!primitive.attributes) return; 24 | 25 | positions = positions.concat(gltfReader._loadAccessor( 26 | gltf, 27 | primitive.attributes.POSITION, 28 | buffers 29 | )); 30 | }) 31 | 32 | return positions; 33 | }, 34 | 35 | /** 36 | * @private 37 | * @param {Object} gltf 38 | * @param {String|Number} accessorName A number in glTF2 39 | * @param {Object} buffers 40 | * @return {Number[]|null} 41 | */ 42 | _loadAccessor(gltf, accessorName, buffers) { 43 | if (accessorName === undefined) return null; 44 | 45 | const accessor = gltf.accessors[accessorName]; 46 | const offset = accessor.byteOffset || 0; 47 | 48 | const buffer = gltfReader._loadBufferView( 49 | gltf, 50 | accessor.bufferView, 51 | offset, 52 | buffers 53 | ); 54 | 55 | const array = []; 56 | switch (accessor.componentType) { 57 | case 5123: // UNSIGNED_SHORT 58 | for (let i = 0; i < buffer.length; i += 2) { 59 | array.push(buffer.readUInt16LE(i)); 60 | } 61 | break; 62 | case 5126: // FLOAT 63 | for (let i = 0; i < buffer.length; i += 4) { 64 | array.push(buffer.readFloatLE(i)); 65 | } 66 | break; 67 | default: 68 | console.error( 69 | "gltfLoader: Unsupported component type: " + accessor.componentType 70 | ); 71 | } 72 | 73 | return array; 74 | }, 75 | 76 | /** 77 | * @private 78 | * @param {Object} gltf 79 | * @param {String|Number} bufferViewName A number in glTF2 80 | * @param {Number} offset 81 | * @param {Object} buffers 82 | * @return {Buffer} 83 | */ 84 | _loadBufferView(gltf, bufferViewName, offset, buffers) { 85 | 86 | const bufferView = gltf.bufferViews[bufferViewName]; 87 | const length = bufferView.byteLength || 0; 88 | 89 | offset += bufferView.byteOffset ? bufferView.byteOffset : 0; 90 | 91 | const buffer = gltfReader._loadBuffer(gltf, bufferView.buffer, buffers); 92 | return buffer.slice(offset, offset + length); 93 | }, 94 | 95 | /** 96 | * @private 97 | * @param {Object} gltf 98 | * @param {String|Number} bufferName A number in glTF2 99 | * @param {Object} buffers 100 | * @return {Buffer} 101 | */ 102 | _loadBuffer(gltf, bufferName, buffers) { 103 | if (buffers[bufferName]) { 104 | return buffers[bufferName]; 105 | } 106 | 107 | const buffer = gltf.buffers[bufferName]; 108 | 109 | if (!buffer.uri.startsWith("data:")) { 110 | console.error( 111 | "gltfReader: Currently unable to load buffers that are not data-URI based." 112 | ); 113 | return null; 114 | } 115 | 116 | buffers[bufferName] = Buffer.from( 117 | buffer.uri.split(",")[1], 118 | "base64" 119 | ); 120 | return buffers[bufferName]; 121 | } 122 | }; 123 | 124 | export default gltfReader; 125 | -------------------------------------------------------------------------------- /src/gltf1-bounding-box.js: -------------------------------------------------------------------------------- 1 | import { Matrix } from 'matrixmath'; 2 | import { flattenDeep, includes } from 'lodash'; 3 | import { loadPositions } from './gltf-reader'; 4 | 5 | import precise from './precise'; 6 | 7 | const gltf1BoundingBox = { 8 | 9 | computeBoundings(gltf, { precision, ceilDimensions } = {}) { 10 | // get all the points and retrieve min max 11 | const boundings = this.getMeshesTransformMatrices(gltf.nodes, gltf).reduce((acc, point) => { 12 | acc.min = acc.min.map((elt, i) => elt < point[i] ? elt : point[i]); 13 | acc.max = acc.max.map((elt, i) => elt > point[i] ? elt : point[i]); 14 | return acc; 15 | },{min: [Infinity, Infinity, Infinity], max: [-Infinity, -Infinity, -Infinity]}); 16 | 17 | // Return the dimensions of the bounding box 18 | const dimensionsRound = ceilDimensions === true ? precise.ceil : precise.round; 19 | const res = { 20 | dimensions: { 21 | width: dimensionsRound(boundings.max[0] - boundings.min[0], precision), 22 | depth: dimensionsRound(boundings.max[2] - boundings.min[2], precision), 23 | height: dimensionsRound(boundings.max[1] - boundings.min[1], precision), 24 | }, 25 | center: { 26 | x: precise.round((boundings.max[0] + boundings.min[0]) / 2, precision + 1), 27 | y: precise.round((boundings.max[2] + boundings.min[2]) / 2, precision + 1), 28 | z: precise.round((boundings.max[1] + boundings.min[1]) / 2, precision + 1), 29 | }, 30 | }; 31 | 32 | return res; 33 | }, 34 | 35 | getMeshesTransformMatrices(nodes, gltf) { 36 | return Object.keys(nodes) 37 | 38 | // Get every node which have meshes 39 | .filter(nodeName => nodes[nodeName].meshes) 40 | 41 | // Get a list of every mesh with a reference to its parent node name 42 | .reduce((meshes, nodeName) => [ 43 | ...meshes, 44 | ...nodes[nodeName].meshes 45 | .map(mesh => ({ mesh, nodeName })) 46 | ], []) 47 | 48 | .reduce((acc, { mesh, nodeName }) => { 49 | 50 | // Climb up the tree to retrieve all the transform matrices 51 | const matrices = this.getParentNodesMatrices(nodeName, nodes) 52 | .map(transformMatrix => new Matrix(4, 4, false).setData(transformMatrix)); 53 | 54 | // Compute the global transform matrix 55 | const matrix = Matrix.multiply(...matrices); 56 | const positions = this.getPointsFromArray(loadPositions(gltf, mesh)); 57 | 58 | 59 | const transformedPoints = positions.map(point => Matrix.multiply(point, matrix)); 60 | return acc.concat(transformedPoints); 61 | }, []); 62 | }, 63 | 64 | getParentNodesMatrices(childNodeName, nodes) { 65 | 66 | // Find the node which has the given node as a child 67 | const parentNodeName = Object.keys(nodes) 68 | .find( 69 | nodeName => nodes[nodeName].children && 70 | includes(nodes[nodeName].children, childNodeName) 71 | ); 72 | 73 | // Specify identity matrix if not present 74 | const nodeMatrix = nodes[childNodeName].matrix || [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; 75 | 76 | return parentNodeName ? 77 | 78 | // If found, return the current matrix and continue climbing 79 | [ 80 | nodeMatrix, 81 | ...this.getParentNodesMatrices(parentNodeName, nodes), 82 | ].filter(matrix => matrix) : 83 | 84 | // If not, only return the current matrix (if any) 85 | [nodeMatrix]; 86 | }, 87 | 88 | getPointsFromArray(array) { 89 | const res = []; 90 | for(let i = 0; i< array.length ; i+=3) { 91 | res.push(new Matrix(1,4,false).setData([array[i], array[i+1], array[i+2], 1])); 92 | } 93 | return res; 94 | }, 95 | 96 | }; 97 | 98 | export default gltf1BoundingBox; 99 | -------------------------------------------------------------------------------- /src/gltf2-bounding-box.js: -------------------------------------------------------------------------------- 1 | import { Matrix } from 'matrixmath'; 2 | import { flattenDeep, includes } from 'lodash'; 3 | import { loadPositions } from './gltf-reader'; 4 | 5 | import precise from './precise'; 6 | 7 | const gltf2BoundingBox = { 8 | 9 | computeBoundings(gltf, buffers=[], { precision, ceilDimensions } = {}) { 10 | const boundings = this.getMeshesTransformMatrices(gltf.nodes, gltf, buffers).reduce((acc, point) => { 11 | acc.min = acc.min.map((elt, i) => elt < point[i] ? elt : point[i]); 12 | acc.max = acc.max.map((elt, i) => elt > point[i] ? elt : point[i]); 13 | return acc; 14 | },{min: [Infinity, Infinity, Infinity], max: [-Infinity, -Infinity, -Infinity]}); 15 | 16 | // Return the dimensions of the bounding box 17 | const dimensionRound = ceilDimensions ? precise.ceil : precise.round; 18 | const res = { 19 | dimensions: { 20 | width: dimensionRound(boundings.max[0] - boundings.min[0], precision), 21 | depth: dimensionRound(boundings.max[2] - boundings.min[2], precision), 22 | height: dimensionRound(boundings.max[1] - boundings.min[1], precision), 23 | }, 24 | center: { 25 | x: precise.round((boundings.max[0] + boundings.min[0]) / 2, precision + 1), 26 | y: precise.round((boundings.max[2] + boundings.min[2]) / 2, precision + 1), 27 | z: precise.round((boundings.max[1] + boundings.min[1]) / 2, precision + 1), 28 | }, 29 | }; 30 | 31 | return res; 32 | }, 33 | 34 | getMeshesTransformMatrices(nodes, gltf, buffers) { 35 | nodes.forEach((node, index) => node.index = index); 36 | 37 | return nodes 38 | 39 | // Get every node which have meshes 40 | .filter(node => (node.mesh !== undefined)) 41 | 42 | .reduce((acc, node) => { 43 | // Climb up the tree to retrieve all the transform matrices 44 | const matrices = this.getParentNodesMatrices(node, nodes) 45 | .map(transformMatrix => new Matrix(4, 4, false).setData(transformMatrix)); 46 | 47 | // Compute the global transform matrix 48 | const matrix = Matrix.multiply(...matrices); 49 | const positions = this.getPointsFromArray(loadPositions(gltf, node.mesh, buffers)); 50 | 51 | const transformedPoints = positions.map(point => Matrix.multiply(point, matrix)); 52 | return acc.concat(transformedPoints); 53 | }, []); 54 | }, 55 | 56 | getParentNodesMatrices(childNode, nodes) { 57 | // Find the node which has the given node as a child 58 | const parentNode = nodes 59 | .find( 60 | node => node.children && 61 | includes(node.children, childNode.index) 62 | ); 63 | 64 | // Specify identity matrix if not present 65 | const childNodeMatrix = childNode.matrix || [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; 66 | 67 | return (parentNode !== undefined) ? 68 | 69 | // If found, return the current matrix and continue climbing 70 | [ 71 | childNodeMatrix, 72 | ...this.getParentNodesMatrices(parentNode, nodes), 73 | ].filter(matrix => matrix) : 74 | 75 | // If not, only return the current matrix (if any) 76 | [childNodeMatrix]; 77 | }, 78 | 79 | getPointsFromArray(array) { 80 | const res = []; 81 | for (let i = 0; i < array.length ; i+=3) { 82 | res.push(new Matrix(1,4,false).setData([array[i], array[i+1], array[i+2], 1])); 83 | } 84 | return res; 85 | }, 86 | 87 | }; 88 | 89 | export default gltf2BoundingBox; 90 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import gltf1BoundingBox from './gltf1-bounding-box'; 2 | import gltf2BoundingBox from './gltf2-bounding-box'; 3 | import glb2BoundingBox from './glb2-bounding-box'; 4 | 5 | const gltfBoundingBox = { 6 | 7 | /** 8 | * @param {Object|Buffer} gltf 9 | * @param {Buffer} buffers External buffers list if any. 10 | * @param {Object} options 11 | * @param {number} options.precision boundings precision, number of decimals. 12 | * @param {boolean} options.ceilDimensions ceil bounding box dimensions to prevent it of being smaller than the actual object. 13 | */ 14 | computeBoundings(gltf, buffers = [], options) { 15 | options = Object.assign({ 16 | precision: 0, 17 | ceilDimensions: false, 18 | }, options) 19 | if (Boolean(gltf.readUInt32LE)) { 20 | const version = gltf.readUInt32LE(4); 21 | if (version === 2) { 22 | return glb2BoundingBox.computeBoundings(gltf, options); 23 | } else { 24 | throw new Error("gltf-bounding-box only currently handles glTF1 and glTF/glb2."); 25 | } 26 | } else { 27 | if (+gltf.asset.version === 1) { 28 | return gltf1BoundingBox.computeBoundings(gltf, options); 29 | } else if (+gltf.asset.version === 2) { 30 | return gltf2BoundingBox.computeBoundings(gltf, buffers, options); 31 | } else { 32 | throw new Error("gltf-bounding-box only currently handles glTF1 and glTF/glb2."); 33 | } 34 | } 35 | }, 36 | 37 | }; 38 | 39 | export default gltfBoundingBox; 40 | -------------------------------------------------------------------------------- /src/precise.js: -------------------------------------------------------------------------------- 1 | const precise = { 2 | 3 | /** 4 | * @public 5 | * @param {Number} number 6 | * @param {String|Number} precision the precision to round up the number 7 | * @return {Number} the rounded number 8 | */ 9 | round(number, precision) { 10 | return precise._operation(Math.round, number, precision); 11 | }, 12 | 13 | ceil(number, precision = 0) { 14 | return precise._operation(Math.ceil, number, precision); 15 | }, 16 | 17 | _operation(operation, number, precision = 0) { 18 | if (precision === 0) { 19 | return operation(number); 20 | } 21 | const factor = Math.pow(10, precision); 22 | const tempNumber = number * factor; 23 | const roundedTempNumber = operation(tempNumber); 24 | return roundedTempNumber / factor; 25 | } 26 | }; 27 | 28 | export default precise; 29 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./setup/.globals.json", 3 | "parserOptions": { 4 | "ecmaVersion": 6, 5 | "sourceType": "module" 6 | }, 7 | "rules": { 8 | "strict": 0, 9 | "quotes": [2, "single"], 10 | "no-unused-expressions": 0 11 | }, 12 | "env": { 13 | "browser": true, 14 | "node": true, 15 | "mocha": true 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/example-models/suzanne.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanadev/gltf-bounding-box/8201cbb5307e4dedbd76d527c2fd757f4789d13e/test/example-models/suzanne.glb -------------------------------------------------------------------------------- /test/runner.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Tests 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | -------------------------------------------------------------------------------- /test/setup/.globals.json: -------------------------------------------------------------------------------- 1 | { 2 | "globals": { 3 | "expect": true, 4 | "mock": true, 5 | "sandbox": true, 6 | "spy": true, 7 | "stub": true, 8 | "useFakeServer": true, 9 | "useFakeTimers": true, 10 | "useFakeXMLHttpRequest": true 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/setup/browser.js: -------------------------------------------------------------------------------- 1 | var mochaGlobals = require('./.globals.json').globals; 2 | 3 | window.mocha.setup('bdd'); 4 | window.onload = function() { 5 | window.mocha.checkLeaks(); 6 | window.mocha.globals(Object.keys(mochaGlobals)); 7 | window.mocha.run(); 8 | require('./setup')(window); 9 | }; 10 | -------------------------------------------------------------------------------- /test/setup/node.js: -------------------------------------------------------------------------------- 1 | global.chai = require('chai'); 2 | global.sinon = require('sinon'); 3 | global.chai.use(require('sinon-chai')); 4 | 5 | require('babel-core/register'); 6 | require('./setup')(); 7 | 8 | /* 9 | Uncomment the following if your library uses features of the DOM, 10 | for example if writing a jQuery extension, and 11 | add 'simple-jsdom' to the `devDependencies` of your package.json 12 | 13 | Note that JSDom doesn't implement the entire DOM API. If you're using 14 | more advanced or experimental features, you may need to switch to 15 | PhantomJS. Setting that up is currently outside of the scope of this 16 | boilerplate. 17 | */ 18 | // import simpleJSDom from 'simple-jsdom'; 19 | // simpleJSDom.install(); 20 | -------------------------------------------------------------------------------- /test/setup/setup.js: -------------------------------------------------------------------------------- 1 | module.exports = function(root) { 2 | root = root ? root : global; 3 | root.expect = root.chai.expect; 4 | 5 | beforeEach(() => { 6 | // Using these globally-available Sinon features is preferrable, as they're 7 | // automatically restored for you in the subsequent `afterEach` 8 | root.sandbox = root.sinon.sandbox.create(); 9 | root.stub = root.sandbox.stub.bind(root.sandbox); 10 | root.spy = root.sandbox.spy.bind(root.sandbox); 11 | root.mock = root.sandbox.mock.bind(root.sandbox); 12 | root.useFakeTimers = root.sandbox.useFakeTimers.bind(root.sandbox); 13 | root.useFakeXMLHttpRequest = root.sandbox.useFakeXMLHttpRequest.bind(root.sandbox); 14 | root.useFakeServer = root.sandbox.useFakeServer.bind(root.sandbox); 15 | }); 16 | 17 | afterEach(() => { 18 | delete root.stub; 19 | delete root.spy; 20 | root.sandbox.restore(); 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /test/unit/glb2-bounding-box.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import glb2BoundingBox from '../../src/glb2-bounding-box'; 3 | import lib from '../../src/index'; 4 | 5 | describe('glb2BoundingBox', () => { 6 | describe('Compute boundings', () => { 7 | beforeEach(() => { 8 | const model = fs.readFileSync('./test/example-models/suzanne.glb'); 9 | 10 | spy(glb2BoundingBox, 'computeBoundings'); 11 | const boundings = lib.computeBoundings(model); 12 | }); 13 | 14 | it('should have been run once', () => { 15 | expect(glb2BoundingBox.computeBoundings).to.have.been.calledOnce; 16 | }); 17 | 18 | it('should have always returned the right boundings', () => { 19 | expect(glb2BoundingBox.computeBoundings).to.have.always.returned({ 20 | dimensions: { 21 | width: 3, 22 | depth: 2, 23 | height: 2, 24 | }, 25 | center: { 26 | x: 0, 27 | y: 0, 28 | z: 0, 29 | }, 30 | }); 31 | }); 32 | }); 33 | 34 | describe('Compute boundings with more precision', () => { 35 | beforeEach(() => { 36 | const model = fs.readFileSync('./test/example-models/suzanne.glb'); 37 | 38 | spy(glb2BoundingBox, 'computeBoundings'); 39 | const boundings = lib.computeBoundings(model, undefined, {precision: 1}); 40 | }); 41 | 42 | it('should have been run once', () => { 43 | expect(glb2BoundingBox.computeBoundings).to.have.been.calledOnce; 44 | }); 45 | 46 | it('should have always returned the right boundings', () => { 47 | expect(glb2BoundingBox.computeBoundings).to.have.always.returned({ 48 | dimensions: { 49 | width: 2.7, 50 | depth: 2, 51 | height: 2, 52 | }, 53 | center: { 54 | x: 0, 55 | y: 0, 56 | z: 0, 57 | }, 58 | }); 59 | }); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /test/unit/gltf1-bounding-box.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import gltf1BoundingBox from '../../src/gltf1-bounding-box'; 3 | import lib from '../../src/index'; 4 | 5 | describe('gltf1BoundingBox', () => { 6 | describe('Compute boundings', () => { 7 | beforeEach(() => { 8 | const model = JSON.parse(fs.readFileSync('./test/example-models/suzanne.gltf').toString()); 9 | 10 | spy(gltf1BoundingBox, 'computeBoundings'); 11 | const boundings = lib.computeBoundings(model); 12 | }); 13 | 14 | it('should have been run once', () => { 15 | expect(gltf1BoundingBox.computeBoundings).to.have.been.calledOnce; 16 | }); 17 | 18 | it('should have always returned the right boundings', () => { 19 | expect(gltf1BoundingBox.computeBoundings).to.have.always.returned({ 20 | dimensions: { 21 | width: 3, 22 | depth: 2, 23 | height: 2, 24 | }, 25 | center: { 26 | x: 0, 27 | y: 0, 28 | z: 0, 29 | }, 30 | }); 31 | }); 32 | }); 33 | 34 | describe('Compute boundings with more precision', () => { 35 | beforeEach(() => { 36 | const model = JSON.parse(fs.readFileSync('./test/example-models/suzanne.gltf').toString()); 37 | 38 | spy(gltf1BoundingBox, 'computeBoundings'); 39 | const boundings = lib.computeBoundings(model, undefined, {precision: 1}); 40 | }); 41 | 42 | it('should have been run once', () => { 43 | expect(gltf1BoundingBox.computeBoundings).to.have.been.calledOnce; 44 | }); 45 | 46 | it('should have always returned the right boundings', () => { 47 | expect(gltf1BoundingBox.computeBoundings).to.have.always.returned({ 48 | dimensions: { 49 | width: 2.7, 50 | depth: 2, 51 | height: 2, 52 | }, 53 | center: { 54 | x: 0, 55 | y: 0, 56 | z: 0, 57 | }, 58 | }); 59 | }); 60 | }); 61 | }); 62 | --------------------------------------------------------------------------------