@expofp/floorplan
Version:
Interactive floor plan library for expos and events
2 lines (1 loc) • 5.31 kB
JavaScript
import{Box as P,pointRotate as p,Polygon as R}from"@expofp/geometry";import{getTrianglesFromFpPaths as M}from"../../../../data/svg.js";const T=.5,v=.3,w=10,$=.01,b=8,C=2,G=.01,k=8,A=4,D=50;export function getBoothBadgeFootprint(f){const[t,...s]=f.paths??[],e=f.layer?.name||"",n=t?M(t.index,e):[],i=buildBadgeGeometry(n);if(!s.length)return{geometry:i,outlineInset:0};const{polygon:c,edges:o}=i;let r=0;for(const l of s){let a=0,h=!1;for(const u of M(l.index,e))for(const[y,m]of u){const x={x:y,y:m};if(c.containsPoint(x,{areaTolerance:0})){const d=S(x,o);a=Math.max(a,d),d<=$&&(h=!0)}else h=!0}h&&(r=Math.max(r,a))}return{geometry:i,outlineInset:r}}export class BoothBadgeAnchors{static{this.idle=new Set}static{this.idleScheduled=!1}constructor(t,s,e,n){this.booth=t,this.border=e,this.source=n,this.fits=[],this.prefetchRotation=0,this.vertices=null,{triangles:this.triangles,polygon:this.polygon,edges:this.edges,bbox:this.bbox}=s}fit(t,s){let e=this.fits[t];return e||(this.fits[t]=e=this.compute(t,s),this.fits.filter(Boolean).length===4&&(this.vertices=null)),e}prefetch(t){BoothBadgeAnchors.idle.has(this)||(this.prefetchRotation=t,BoothBadgeAnchors.idle.add(this),BoothBadgeAnchors.scheduleIdle())}cancelPrefetch(){BoothBadgeAnchors.idle.delete(this)}static scheduleIdle(){BoothBadgeAnchors.idleScheduled||!BoothBadgeAnchors.idle.size||(BoothBadgeAnchors.idleScheduled=!0,typeof requestIdleCallback=="function"?requestIdleCallback(BoothBadgeAnchors.runIdle):typeof requestAnimationFrame=="function"?requestAnimationFrame(()=>setTimeout(BoothBadgeAnchors.runSlice,0)):setTimeout(BoothBadgeAnchors.runSlice,D))}static runSlice(){const t=performance.now()+A;BoothBadgeAnchors.runIdle({timeRemaining:()=>t-performance.now()})}static runIdle(t){BoothBadgeAnchors.idleScheduled=!1;const s=performance.now()+k;try{for(const e of BoothBadgeAnchors.idle){if(t.timeRemaining()<=0||performance.now()>=s)break;let n=!1;try{n=e.prefetchNext()}finally{n||BoothBadgeAnchors.idle.delete(e)}}}finally{BoothBadgeAnchors.scheduleIdle()}}prefetchNext(){for(let t=1;t<4;t++)if(!this.fits[t])return this.fit(t,this.prefetchRotation+t*Math.PI/2),t<3;return!1}compute(t,s){const e=t===0||t===3?1:-1,n=t===0||t===1?1:-1;let i=this.findAnchor(e,n),c=this.clampScale(i.x,i.y,this.rectMaxScale(t%2===1),s);const o=(this.booth.rotate??0)+t*Math.PI/2;for(let r=0;r<C;r++){const l=this.nudge(i,c,o);if(!l)break;i=l,this.fitsAt(i.x,i.y,c,s)||(c=this.clampScale(i.x,i.y,c,s))}return{x:i.x,y:i.y,maxScale:c}}nudge(t,s,e){if(!this.triangles.length||!s)return null;const n=Math.cos(e),i=Math.sin(e),c=this.source.x*s,o=this.source.y*s,r=(y,m)=>{let x=0;for(let d=0;d<=b;d++){const g={x:t.x+y*d/b,y:t.y+m*d/b},I=S(g,this.edges),E=this.polygon.containsPoint(g);x=Math.max(x,E?this.border-I:this.border+I)}return x},l=r(-i*o,n*o),a=r(n*c,i*c),h=this.border*G;if(l<=h&&a<=h)return null;const u={x:t.x+Math.max(l,0)*n-Math.max(a,0)*i,y:t.y+Math.max(l,0)*i+Math.max(a,0)*n};return this.polygon.containsPoint(u)?u:null}rectMaxScale(t){const s=this.booth.rect.size.width,e=this.booth.rect.size.height,n=t?e:s,i=t?s:e;return Math.min(n>0?n*T/this.source.x:1/0,i>0?i*v/this.source.y:1/0)}findAnchor(t,s){const{booth:e,border:n,bbox:i}=this;if(i){const l=t>0?i.min.x:i.max.x,a=s>0?i.min.y:i.max.y,h=[];for(const[u,y]of this.distinctVertices()){const m=u+n*t,x=y+n*s,d=Math.abs(m-l)+Math.abs(x-a);d<1/0&&h.push({x:m,y:x,score:d,order:h.length})}h.sort((u,y)=>u.score-y.score||u.order-y.order);for(const u of h){const y={x:u.x,y:u.y};if(this.polygon.containsPoint(y))return y}}const c=t>0?e.rect.min.x+n:e.rect.max.x-n,o=s>0?e.rect.min.y+n:e.rect.max.y-n,r=e.rotate??0;return r?p({x:c,y:o},r,e.rect.center):{x:c,y:o}}clampScale(t,s,e,n){const{polygon:i}=this;if(!this.triangles.length)return e;const c={x:t,y:s};if(!i.containsPoint(c))return e;let o=0,r=e;for(let l=0;l<w;l++){const a=(o+r)/2;this.fitsAt(t,s,a,n)?o=a:r=a}return o}fitsAt(t,s,e,n){const{polygon:i,source:c}=this,o={x:t,y:s},r=c.x*e,l=c.y*e;return i.containsPoint(p({x:t+r,y:s},n,o))&&i.containsPoint(p({x:t,y:s+l},n,o))&&i.containsPoint(p({x:t+r,y:s+l},n,o))}distinctVertices(){if(this.vertices)return this.vertices;const t=new Set,s=[];for(const e of this.triangles)for(const n of e){const i=`${n[0]},${n[1]}`;t.has(i)||(t.add(i),s.push([n[0],n[1]]))}return this.vertices=s}}export function trianglesBoundingBox(f){if(!f.length)return null;let t=1/0,s=1/0,e=-1/0,n=-1/0;for(const i of f)for(const[c,o]of i)c<t&&(t=c),o<s&&(s=o),c>e&&(e=c),o>n&&(n=o);return P.fromMinMax({x:t,y:s},{x:e,y:n})}function L(f){const t=[],s=[];for(const e of f){const n=t.length;t.push({x:e[0][0],y:e[0][1]}),t.push({x:e[1][0],y:e[1][1]}),t.push({x:e[2][0],y:e[2][1]}),s.push([n,n+1,n+2])}return new R(t,s)}export function buildBadgeGeometry(f){return{triangles:f,polygon:L(f),edges:N(f),bbox:trianglesBoundingBox(f)}}function N(f){const t=new Map;for(const e of f)for(let n=0;n<3;n++){const i=e[n],c=e[(n+1)%3],o=`${i[0]},${i[1]}`,r=`${c[0]},${c[1]}`,l=o<r?`${o}|${r}`:`${r}|${o}`,a=t.get(l);a?a.count++:t.set(l,{edge:[i,c],count:1})}const s=[];for(const{edge:e,count:n}of t.values())n===1&&s.push(e);return s}function S(f,t){let s=1/0;for(const[[e,n],[i,c]]of t){const o=i-e,r=c-n,l=o*o+r*r,a=l?Math.max(0,Math.min(1,((f.x-e)*o+(f.y-n)*r)/l)):0;s=Math.min(s,Math.hypot(f.x-e-a*o,f.y-n-a*r))}return s}