UNPKG

@expofp/floorplan

Version:

Interactive floor plan library for expos and events

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