UNPKG

@expofp/floorplan

Version:

Interactive floor plan library for expos and events

8 lines (7 loc) • 5.18 kB
import{lineIntersection as T}from"@expofp/geometry";import{update as v}from"@expofp/renderer";import{useCallback as x,useEffect as U,useMemo as S,useRef as I}from"react";import{mapCamera as $}from"../camera";import{circleCanvasCssSize as K,createCircleCanvas as w,getBounds as b}from"../drawing/config/canvases";import{setPlanClickListener as E}from"../pointer";import{BUILD_ROUTE_DOTS as H,BUILD_ROUTE_LINES as B}from"./trafficLayers";import{UpdateQueue as Y}from"./UpdateQueue";const O=49,R=`hsl(257, 76%, ${O}%)`,z=`hsl(339.903, 82%, ${O}%)`,J=`hsl(257, 76%, ${Math.round(O*1.82)}%)`,_=4,L=K(_),Q=_,D="U",N="R",A="S",M="P";class j{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 ${N}. Apply Simplification: Press ${A}. Undo: Press ${D}. Enable/Disable Preview: Press ${M}. `,16)}applyChaikinSmoothing(e,l,p){let c;if(p>=1&&e.length>=2){c=[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);c.push({x:t.x+(u.x-t.x)*r,y:t.y+(u.y-t.y)*r})}c.push(u)}}else c=e.slice();for(let h=0;h<l&&!(c.length<2);h++){const t=[{...c[0]}];for(let u=1;u<c.length;u++){const n=c[u-1],r=c[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({...c[c.length-1]}),c=t}return c}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,d)=>{const g=n.x,f=n.y,s=r.x,i=r.y,o=d.x,a=d.y,y=Math.abs((a-i)*g-(o-s)*f+o*i-a*s),m=Math.sqrt((a-i)**2+(o-s)**2);return y/(m||1e-10)},c=(n,r,d,g)=>{const f=T({p0:n,p1:r},{p0:d,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 d=e[n],g=e[r];let f=0,s=0;for(let i=n+1;i<r;i++){const o=p(e[i],d,g);o>f&&(f=o,s=i)}if(f>l){const i=e[s];let o=!0;for(let a=0;a<t.length-1;a++)if(c(t[a],t[a+1],d,i)||c(t[a],t[a+1],i,g)){o=!1;break}o&&(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:k,scene:e}){const l=I(!1),p=I(1),c=S(()=>w(_,R),[]),h=S(()=>w(_,z),[]),t=S(()=>new j,[]),u=I(new Y),n=S(()=>{if(!e)return{};const s=e.rootLayer.children,i=s.find(a=>a.name===B),o=s.find(a=>a.name===H);return{linesLayer:i,pointsLayer:o}},[e]),r=x(()=>{if(!n||!c||!h)return;const{linesLayer:s,pointsLayer:i}=n;if(!s||!i)return;const o=t.points,a=l.current;a?i.children.length=0:i.children=o.map(({x:m,y:C},P)=>({source:P==t.selected?h:c,bounds:b(m,C,L,L,p.current)}));const y=[];for(let m=1;m<o.length;m++){const C=o[m-1],P=o[m];y.push({points:{p0:C,p1:P},color:a?R:J,width:Q})}y.length?s.children=y:s.children.length=0,u.current.add(()=>v(i,s)),t.instruct(),t.log(JSON.stringify(t.points))},[t,n,c,h]),d=x(s=>{const i=s.key.toUpperCase();t.execute(()=>{switch(i){case D:s.preventDefault(),t.undo(),l.current=!1,r();break;case N:s.preventDefault(),t.applySmoothing(),l.current=!1,r();break;case A: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 o=[];return t.execute(()=>{const a=i.children;for(const y of a)y.bounds=b(y.bounds.center.x,y.bounds.center.y,L,L,p.current),o.push(y)}),o},[n,t]),f=x(s=>{t.execute(()=>{t.add({x:s.x,y:s.y}),t.select(t.points.length-1),r()})},[t,r]);U(()=>{if(!k||!n?.pointsLayer)return;const{pointsLayer:s}=n;p.current=$()?.state().pixelSize??1,t.execute(()=>{t.restore(),r()}),s.onScale=({pixelSize:o})=>g(o);const i=u.current;return i.add(()=>v(s)),E(f),window.addEventListener("keyup",d),()=>{window.removeEventListener("keyup",d),E(),s.onScale=void 0,i.destroy()}},[k,n,t,r,f,g,d])}