@arcgis/core
Version:
ArcGIS Maps SDK for JavaScript: A complete 2D and 3D mapping and data visualization API
3 lines (2 loc) • 11.8 kB
JavaScript
/* COPYRIGHT Esri - https://js.arcgis.com/5.0.8/LICENSE.txt */
import e from"../../../../core/ObjectPool.js";import t from"../../../../core/PooledArray.js";import{lerp as n,squaredDistance as r,scale as i,add as o}from"../../../../core/libs/gl-matrix-2/math/vec3.js";import{fromValues as s,create as a}from"../../../../core/libs/gl-matrix-2/factories/vec3f64.js";import{intersectsSphere as h}from"../../../../geometry/support/frustum.js";import{wrap as d}from"../../../../geometry/support/ray.js";import{Sphere as u}from"../../../../geometry/support/sphere.js";import{rayBoxTest as c}from"./Util.js";class l{get bounds(){return this._root.bounds}get halfSize(){return this._root.halfSize}get root(){return this._root.node}get maximumObjectsPerNode(){return this._maximumObjectsPerNode}get maximumDepth(){return this._maximumDepth}get objectCount(){return this._objectCount}constructor(e,t){this.objectToBoundingSphere=e,this._maximumObjectsPerNode=10,this._maximumDepth=20,this._degenerateObjects=new Set,this._root=new m,this._objectCount=0,t&&(void 0!==t.maximumObjectsPerNode&&(this._maximumObjectsPerNode=t.maximumObjectsPerNode),void 0!==t.maximumDepth&&(this._maximumDepth=t.maximumDepth))}destroy(){this._degenerateObjects.clear(),m.clearPool(),R[0]=null,C.prune(),J.prune()}add(e){const t=Array.from(e);this._grow(t);const n=m.acquire();for(const r of t)++this._objectCount,this._isDegenerate(r)?this._degenerateObjects.add(r):(n.init(this._root),this._add(r,n));m.release(n)}remove(e,t=null){this._objectCount-=e.length;const n=m.acquire();for(const r of e){const e=t??this.objectToBoundingSphere(r);A(e.radius)?(n.init(this._root),p(r,e,n)):this._degenerateObjects.delete(r)}m.release(n),this._shrink()}update(e,t){if(!A(t.radius)&&this._isDegenerate(e))return;const n=v(e);this.remove(n,t),this.add(n)}forEachAlongRay(e,t,n){const r=d(e,t);f(this._root,e=>{if(!g(r,e))return!1;const t=e.node;return t.terminals.forAll(e=>{this._intersectsObject(r,e)&&n(e)}),null!==t.residents&&t.residents.forAll(e=>{this._intersectsObject(r,e)&&n(e)}),!0})}forEachAlongRayWithVerticalOffset(e,t,n,r){const i=d(e,t);f(this._root,e=>{if(!S(i,e,r))return!1;const t=e.node;return t.terminals.forAll(e=>{this._intersectsObjectWithOffset(i,e,r)&&n(e)}),null!==t.residents&&t.residents.forAll(e=>{this._intersectsObjectWithOffset(i,e,r)&&n(e)}),!0})}forEach(e){f(this._root,t=>{const n=t.node;return n.terminals.forAll(e),null!==n.residents&&n.residents.forAll(e),!0}),this._degenerateObjects.forEach(e)}forEachDegenerateObject(e){this._degenerateObjects.forEach(e)}findClosest(e,t,n,r=()=>!0,s=1/0){let a=1/0,d=1/0,u=null;const c=T(e,t),l=i=>{if(--s,!r(i))return;const o=this.objectToBoundingSphere(i);if(!h(n,o))return;const c=D(e,t,o.center),l=c-o.radius,m=c+o.radius;l<a&&(a=l,d=m,u=i)};return _(this._root,r=>{if(s<=0||!h(n,r.bounds))return!1;i(E,c,r.halfSize),o(E,E,r.bounds.center);if(D(e,t,E)>d)return!1;const a=r.node;return a.terminals.forAll(e=>l(e)),null!==a.residents&&a.residents.forAll(e=>l(e)),!0},e,t),u}forEachInDepthRange(e,t,n,r,s,a,d){let u=-1/0,c=1/0;const l={setRange:e=>{1===n?(u=Math.max(u,e.near),c=Math.min(c,e.far)):(u=Math.max(u,-e.far),c=Math.min(c,-e.near))}};l.setRange(r);const m=D(t,n,e),f=T(t,n),p=T(t,-n),b=e=>{if(!d(e))return;const r=this.objectToBoundingSphere(e),i=D(t,n,r.center)-m,o=i-r.radius,f=i+r.radius;o>c||f<u||!h(a,r)||s(e,l)};_(this._root,e=>{if(!h(a,e.bounds))return!1;i(E,f,e.halfSize),o(E,E,e.bounds.center);if(D(t,n,E)-m>c)return!1;i(E,p,e.halfSize),o(E,E,e.bounds.center);if(D(t,n,E)-m<u)return!1;const r=e.node;return r.terminals.forAll(e=>b(e)),null!==r.residents&&r.residents.forAll(e=>b(e)),!0},t,n)}forEachNode(e){f(this._root,t=>e(t.node,t.bounds,t.halfSize,t.depth))}forEachNeighbor(e,t){const n=t.radius,i=t.center,o=t=>{const o=this.objectToBoundingSphere(t),s=o.radius,a=n+s;return!(r(o.center,i)-a*a<=0)||e(t)};let s=!0;const a=e=>{s&&(s=o(e))};f(this._root,e=>{const t=e.bounds.radius,o=n+t;if(r(e.bounds.center,i)-o*o>0)return!1;const h=e.node;return h.terminals.forAll(a),s&&null!==h.residents&&h.residents.forAll(a),s}),s&&this.forEachDegenerateObject(a)}_intersectsObject(e,t){const n=this.objectToBoundingSphere(t);return!(n.radius>0)||n.intersectRay(e)}_intersectsObjectWithOffset(e,t,n){const r=this.objectToBoundingSphere(t);return!(r.radius>0)||n.applyToBoundingSphere(r).intersectRay(e)}_add(e,t){t.advanceTo(this.objectToBoundingSphere(e))?t.node.terminals.push(e):(t.node.residents.push(e),t.node.residents.length>this._maximumObjectsPerNode&&t.depth<this._maximumDepth&&this._split(t))}_split(e){const t=e.node.residents;e.node.residents=null;for(let n=0;n<t.length;n++){const r=m.acquire().init(e);this._add(t.at(n),r),m.release(r)}}_grow(e){if(M(e,e=>this.objectToBoundingSphere(e),k),A(k.radius)&&!this._fitsInsideTree(k))if(j(this._root.node))this._root.bounds.copyFrom(k),this._root.halfSize=1.25*this._root.bounds.radius,this._root.updateBoundsRadiusFromHalfSize();else{const e=this._rootBoundsForRootAsSubNode(k);this._placingRootViolatesMaxDepth(e)?this._rebuildTree(k,e):this._growRootAsSubNode(e),m.release(e)}}_rebuildTree(e,t){I.center=t.bounds.center,I.radius=t.halfSize,M([e,I],e=>e,L);const n=m.acquire().init(this._root);this._root.initFrom(null,L,L.radius),this._root.increaseHalfSize(1.25),f(n,e=>(this.add(e.node.terminals.data),null!==e.node.residents&&this.add(e.node.residents.data),!0)),m.release(n)}_placingRootViolatesMaxDepth(e){const t=Math.log(e.halfSize/this._root.halfSize)*Math.LOG2E;let n=0;return f(this._root,e=>(n=Math.max(n,e.depth),n+t<=this._maximumDepth)),n+t>this._maximumDepth}_rootBoundsForRootAsSubNode(e){const t=e.radius,n=e.center;let r=-1/0;const i=this._root.bounds.center,o=this._root.halfSize;for(let a=0;a<3;a++){const e=i[a]-o-(n[a]-t),s=n[a]+t-(i[a]+o),h=Math.max(0,Math.ceil(e/(2*o))),d=Math.max(0,Math.ceil(s/(2*o)))+1,u=2**Math.ceil(Math.log(h+d)*Math.LOG2E);r=Math.max(r,u),H[a].min=h,H[a].max=d}for(let a=0;a<3;a++){let e=H[a].min,t=H[a].max;const n=(r-(e+t))/2;e+=Math.ceil(n),t+=Math.floor(n);const s=i[a]-o-e*o*2;F.center[a]=s+(t+e)*o}const s=r*o;return F.radius=s*y,m.acquire().initFrom(null,F,s,0)}_growRootAsSubNode(e){const t=this._root.node;k.center=this._root.bounds.center,k.radius=this._root.halfSize,this._root.init(e),e.advanceTo(k,null,!0),e.node.children=t.children,e.node.residents=t.residents,e.node.terminals=t.terminals}_shrink(){for(;;){const e=this._findShrinkIndex();if(-1===e)break;this._root.advance(e),this._root.depth=0}}_findShrinkIndex(){if(0!==this._root.node.terminals.length||this._root.isLeaf())return-1;let e=null;const t=this._root.node.children;let n=0,r=0;for(;r<t.length&&null==e;)n=r++,e=t[n];for(;r<t.length;)if(t[r++])return-1;return n}_isDegenerate(e){return!A(this.objectToBoundingSphere(e).radius)}_fitsInsideTree(e){const t=this._root.bounds,n=this._root.halfSize;return e.radius<=n&&e.center[0]>=t.center[0]-n&&e.center[0]<=t.center[0]+n&&e.center[1]>=t.center[1]-n&&e.center[1]<=t.center[1]+n&&e.center[2]>=t.center[2]-n&&e.center[2]<=t.center[2]+n}toJSON(){const{maximumDepth:e,maximumObjectsPerNode:t,_objectCount:n}=this,r=this._nodeToJSON(this._root.node);return{maximumDepth:e,maximumObjectsPerNode:t,objectCount:n,root:{bounds:this._root.bounds,halfSize:this._root.halfSize,depth:this._root.depth,node:r}}}_nodeToJSON(e){const t=e.children.map(e=>e?this._nodeToJSON(e):null),n=e.residents?.map(e=>this.objectToBoundingSphere(e)),r=e.terminals?.map(e=>this.objectToBoundingSphere(e));return{children:t,residents:n,terminals:r}}static fromJSON(e){const t=new l(e=>e,{maximumDepth:e.maximumDepth,maximumObjectsPerNode:e.maximumObjectsPerNode});return t._objectCount=e.objectCount,t._root.initFrom(e.root.node,e.root.bounds,e.root.halfSize,e.root.depth),t}}class m{constructor(){this.bounds=new u,this.halfSize=0,this.initFrom(null,null,0,0)}init(e){return this.initFrom(e.node,e.bounds,e.halfSize,e.depth)}initFrom(e,t,n,r=this.depth){return this.node=null!=e?e:m.createEmptyNode(),t&&this.bounds.copyFrom(t),this.halfSize=n,this.depth=r,this}increaseHalfSize(e){this.halfSize*=e,this.updateBoundsRadiusFromHalfSize()}updateBoundsRadiusFromHalfSize(){this.bounds.radius=this.halfSize*y}advance(e){let t=this.node.children[e];t||(t=m.createEmptyNode(),this.node.children[e]=t),this.node=t,this.halfSize/=2,this.depth++;const n=w[e];return this.bounds.center[0]+=n[0]*this.halfSize,this.bounds.center[1]+=n[1]*this.halfSize,this.bounds.center[2]+=n[2]*this.halfSize,this.updateBoundsRadiusFromHalfSize(),this}advanceTo(e,t,n=!1){for(;;){if(this.isTerminalFor(e))return t?.(this,-1),!0;if(this.isLeaf()){if(!n)return t?.(this,-1),!1;this.node.residents=null}const r=this._childIndex(e);t?.(this,r),this.advance(r)}}isLeaf(){return null!=this.node.residents}isTerminalFor(e){return e.radius>this.halfSize/2}_childIndex(e){const t=this.bounds.center;return(t[0]<e.center[0]?1:0)+(t[1]<e.center[1]?2:0)+(t[2]<e.center[2]?4:0)}static createEmptyNode(){return{children:[null,null,null,null,null,null,null,null],terminals:new t({shrink:!0}),residents:new t({shrink:!0})}}static{this._pool=new e(()=>new m)}static acquire(){return m._pool.acquire()}static release(e){m._pool.release(e)}static clearPool(){m._pool.prune()}}function f(e,t){let n=m.acquire().init(e);const r=[n];for(;0!==r.length;){if(n=r.pop(),t(n)&&!n.isLeaf())for(let e=0;e<n.node.children.length;e++){n.node.children[e]&&r.push(m.acquire().init(n).advance(e))}m.release(n)}}function _(e,t,n,r=1){let i=m.acquire().init(e);const o=[i];for(N(n,r,W);0!==o.length;){if(i=o.pop(),t(i)&&!i.isLeaf())for(let e=7;e>=0;--e){const t=W[e];i.node.children[t]&&o.push(m.acquire().init(i).advance(t))}m.release(i)}}function p(e,t,n){C.clear();const r=n.advanceTo(t,(e,t)=>{C.push(e.node),C.push(t)})?n.node.terminals:n.node.residents;if(r.removeUnordered(e),0===r.length)for(let i=C.length-2;i>=0;i-=2){if(!b(C.data[i],C.data[i+1]))break}}function b(e,n){return n>=0&&(e.children[n]=null),!!j(e)&&(null===e.residents&&(e.residents=new t({shrink:!0})),!0)}function g(e,t){return z(t.bounds.center,2*-t.halfSize,P),z(t.bounds.center,2*t.halfSize,q),c(e.origin,e.direction,P,q)}function S(e,t,n){return z(t.bounds.center,2*-t.halfSize,P),z(t.bounds.center,2*t.halfSize,q),n.applyToMinMax(P,q),c(e.origin,e.direction,P,q)}function j(e){if(0!==e.terminals.length)return!1;if(null!==e.residents)return 0===e.residents.length;for(let t=0;t<e.children.length;t++)if(e.children[t])return!1;return!0}function x(e,t){e[0]=Math.min(e[0],t.center[0]-t.radius),e[1]=Math.min(e[1],t.center[1]-t.radius),e[2]=Math.min(e[2],t.center[2]-t.radius)}function O(e,t){e[0]=Math.max(e[0],t.center[0]+t.radius),e[1]=Math.max(e[1],t.center[1]+t.radius),e[2]=Math.max(e[2],t.center[2]+t.radius)}function z(e,t,n){n[0]=e[0]+t,n[1]=e[1]+t,n[2]=e[2]+t}function M(e,t,r){P[0]=1/0,P[1]=1/0,P[2]=1/0,q[0]=-1/0,q[1]=-1/0,q[2]=-1/0;for(const n of e){const e=t(n);A(e.radius)&&(x(P,e),O(q,e))}n(r.center,P,q,.5),r.radius=Math.max(q[0]-P[0],q[1]-P[1],q[2]-P[2])/2}function N(e,t,n){if(!J.length)for(let r=0;r<8;++r)J.push({index:0,distance:0});for(let r=0;r<8;++r){const n=w[r];J.data[r].index=r,J.data[r].distance=D(e,t,n)}J.sort((e,t)=>e.distance-t.distance);for(let r=0;r<8;++r)n[r]=J.data[r].index}function T(e,t){let n,r=1/0;for(let i=0;i<8;++i){const o=D(e,t,B[i]);o<r&&(r=o,n=B[i])}return n}function D(e,t,n){return t*(e[0]*n[0]+e[1]*n[1]+e[2]*n[2])}function A(e){return!isNaN(e)&&e!==-1/0&&e!==1/0&&e>0}const w=[s(-1,-1,-1),s(1,-1,-1),s(-1,1,-1),s(1,1,-1),s(-1,-1,1),s(1,-1,1),s(-1,1,1),s(1,1,1)],B=[s(-1,-1,-1),s(-1,-1,1),s(-1,1,-1),s(-1,1,1),s(1,-1,-1),s(1,-1,1),s(1,1,-1),s(1,1,1)],y=Math.sqrt(3),R=[null];function v(e){return R[0]=e,R}const F=new u,E=a(),P=a(),q=a(),C=new t,k=new u,I=new u,L=new u,H=[{min:0,max:0},{min:0,max:0},{min:0,max:0}],J=new t,W=[0,0,0,0,0,0,0,0];export{l as Octree};