@expofp/floorplan
Version:
Interactive floor plan library for expos and events
8 lines (7 loc) • 5.2 kB
JavaScript
import{lineIntersection as U}from"@expofp/geometry";import{update as O}from"@expofp/renderer";import{useCallback as x,useEffect as $,useMemo as S,useRef as P}from"react";import{createCircleCanvas as v,getBounds as b}from"../../../engine/canvases";import{mapCamera as K}from"../camera";import{setPlanClickListener as E}from"../pointer";import{BUILD_ROUTE_DOTS as T,BUILD_ROUTE_LINES as H}from"./trafficLayers";import{UpdateQueue as B}from"./UpdateQueue";const C=49,R=`hsl(257, 76%, ${C}%)`,Y=`hsl(339.903, 82%, ${C}%)`,J=`hsl(257, 76%, ${Math.round(C*1.82)}%)`,I=4,j=I,D="U",A="R",N="S",M="P";class q{constructor(){this._state=[[]],this.index=0,this.pointHashSet=new Set,this.locked=!1,this.storageKey="expofp_build_route_state",this._selected=0}get points(){return this._state[this.index]}set points(e){this._state.push(e),this.setIndex(),this.save()}setIndex(){this.index=Math.max(this._state.length-1,0)}genPointHash(e){return`${e.x},${e.y}`}add(e){const l=this.genPointHash(e);this.pointHashSet.has(l)||(this.pointHashSet.add(l),this.points=[...this.points,e])}undo(){this.index&&(this._state.pop(),this.setIndex(),this.save(),this.select(this.points.length-1))}execute(e){try{if(this.locked)return;this.locked=!0,e()}finally{requestAnimationFrame(()=>{this.locked=!1})}}log(e,l=12){console.log(`%c${e}`,`color: black; font-size: ${l}px; background: LemonChiffon;`)}instruct(){this.log(`How to Build a Route:
Add Point: Click on the map.
Apply Smoothing: Press ${A}.
Apply Simplification: Press ${N}.
Undo: Press ${D}.
Enable/Disable Preview: Press ${M}.
`,16)}applyChaikinSmoothing(e,l,p){let o;if(p>=1&&e.length>=2){o=[e[0]];for(let h=1;h<e.length;h++){const t=e[h-1],u=e[h];for(let n=1;n<=p;n++){const r=n/(p+1);o.push({x:t.x+(u.x-t.x)*r,y:t.y+(u.y-t.y)*r})}o.push(u)}}else o=e.slice();for(let h=0;h<l&&!(o.length<2);h++){const t=[{...o[0]}];for(let u=1;u<o.length;u++){const n=o[u-1],r=o[u];t.push({x:.75*n.x+.25*r.x,y:.75*n.y+.25*r.y}),t.push({x:.25*n.x+.75*r.x,y:.25*n.y+.75*r.y})}t.push({...o[o.length-1]}),o=t}return o}applySmoothing(){this.points.length<3||(this.points=this.applyChaikinSmoothing(this.points,1,0),this.select(this.points.length-1))}save(){try{this.points.length?sessionStorage.setItem(this.storageKey,JSON.stringify(this.points)):sessionStorage.removeItem(this.storageKey)}catch(e){console.warn(e)}}restore(){try{const e=sessionStorage.getItem(this.storageKey);if(!e)return;const l=JSON.parse(e);if(!Array.isArray(l))return;l.forEach(p=>this.add(p)),this.select(this.points.length-1)}catch(e){console.warn(e)}}topologyPreservingSimplify(e,l){if(e.length<=2)return e.map(n=>({...n}));const p=(n,r,y)=>{const g=n.x,f=n.y,s=r.x,i=r.y,c=y.x,a=y.y,d=Math.abs((a-i)*g-(c-s)*f+c*i-a*s),m=Math.sqrt((a-i)**2+(c-s)**2);return d/(m||1e-10)},o=(n,r,y,g)=>{const f=U({p0:n,p1:r},{p0:y,p1:g});return f!==null&&f.onLine1&&f.onLine2},h=new Array(e.length).fill(!1);h[0]=h[h.length-1]=!0;const t=[],u=(n,r)=>{if(r<=n+1)return;const y=e[n],g=e[r];let f=0,s=0;for(let i=n+1;i<r;i++){const c=p(e[i],y,g);c>f&&(f=c,s=i)}if(f>l){const i=e[s];let c=!0;for(let a=0;a<t.length-1;a++)if(o(t[a],t[a+1],y,i)||o(t[a],t[a+1],i,g)){c=!1;break}c&&(h[s]=!0,u(n,s),u(s,r))}};return u(0,e.length-1),e.filter((n,r)=>h[r])}applySimplification(){this.points=this.topologyPreservingSimplify(this.points,.5),this.select(this.points.length-1)}get selected(){return this._selected}select(e){this._selected=Math.max(0,Math.min(e,this.points.length-1))}}export function useBuildRoute({enabled:_,scene:e}){const l=P(!1),p=P(),o=S(()=>v(I,R),[]),h=S(()=>v(I,Y),[]),t=S(()=>new q,[]),u=P(new B),n=S(()=>{if(!e)return{};const s=e.rootLayer.children,i=s.find(a=>a.name===H),c=s.find(a=>a.name===T);return{linesLayer:i,pointsLayer:c}},[e]),r=x(()=>{if(!n||!o||!h)return;const{linesLayer:s,pointsLayer:i}=n;if(!s||!i)return;const c=t.points,a=l.current;a?i.children.length=0:i.children=c.map(({x:m,y:L},k)=>{const w=k==t.selected?h:o;return{source:w,bounds:b(m,L,w.width,w.height,p.current)}});const d=[];for(let m=1;m<c.length;m++){const L=c[m-1],k=c[m];d.push({points:{p0:L,p1:k},color:a?R:J,width:j})}d.length?s.children=d:s.children.length=0,u.current.add(()=>O(i,s)),t.instruct(),t.log(JSON.stringify(t.points))},[t,n,o,h]),y=x(s=>{const i=s.key.toUpperCase();t.execute(()=>{switch(i){case D:s.preventDefault(),t.undo(),l.current=!1,r();break;case A:s.preventDefault(),t.applySmoothing(),l.current=!1,r();break;case N:s.preventDefault(),t.applySimplification(),l.current=!1,r();break;case M:s.preventDefault(),l.current=!l.current,r();break}})},[t,r]),g=x(s=>{p.current=s;const{pointsLayer:i}=n;if(!i)return[];const c=[];return t.execute(()=>{const a=i.children;for(const d of a)d.bounds=b(d.bounds.center.x,d.bounds.center.y,d.source.width,d.source.height,p.current),c.push(d)}),c},[n,t]),f=x(s=>{t.execute(()=>{t.add({x:s.x,y:s.y}),t.select(t.points.length-1),r()})},[t,r]);$(()=>{if(!_||!n?.pointsLayer)return;const{pointsLayer:s}=n;return p.current=K()?.state().pixelSize??1,t.execute(()=>{t.restore(),r()}),s.onScale=({pixelSize:i})=>g(i),u.current.add(()=>O(s)),E(f),window.addEventListener("keyup",y),()=>{window.removeEventListener("keyup",y),E(),s.onScale=void 0,u.current.destroy()}},[_,n,t,r,f,g,y])}