@expofp/floorplan
Version:
Interactive floor plan library for expos and events
8 lines (7 loc) • 5.34 kB
JavaScript
import{useCallback as _,useEffect as M,useMemo as L,useRef as P}from"react";import{createCircleCanvas as S,getBounds as I}from"../../../renderer/engine-core/canvases";import{isDefaultScene as U,pickDefaultScene as $}from"../../../renderer/engine-core/defs";import{BUILD_ROUTE_DOTS as A,BUILD_ROUTE_LINES as K}from"./trafficLayers";import{UpdateQueue as T}from"./UpdateQueue";const v=49,C=`hsl(257, 76%, ${v}%)`,H=`hsl(339.903, 82%, ${v}%)`,Y=`hsl(257, 76%, ${Math.round(v*1.82)}%)`,O=4,B=O,D="U",R="R",b="S",N="P";class J{_state=[[]];index=0;pointHashSet=new Set;locked=!1;storageKey="expofp_build_route_state";_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 ${R}.
Apply Simplification: Press ${b}.
Undo: Press ${D}.
Enable/Disable Preview: Press ${N}.
`,16)}applyChaikinSmoothing(t,a,y){let c=[];if(y>=1&&t.length>=2){c=[t[0]];for(let h=1;h<t.length;h++){const s=t[h-1],u=t[h];for(let o=1;o<=y;o++){const n=o/(y+1);c.push({x:s.x+(u.x-s.x)*n,y:s.y+(u.y-s.y)*n})}c.push(u)}}else c=t.slice();for(let h=0;h<a&&!(c.length<2);h++){const s=[{...c[0]}];for(let u=1;u<c.length;u++){const o=c[u-1],n=c[u];s.push({x:.75*o.x+.25*n.x,y:.75*o.y+.25*n.y}),s.push({x:.25*o.x+.75*n.x,y:.25*o.y+.75*n.y})}s.push({...c[c.length-1]}),c=s}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(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(y=>this.add(y)),this.select(this.points.length-1)}catch(t){console.warn(t)}}topologyPreservingSimplify(t,a){if(t.length<=2)return t.map(o=>({...o}));const y=(o,n,f)=>{const d=o.x,p=o.y,e=n.x,i=n.y,r=f.x,l=f.y,x=Math.abs((l-i)*d-(r-e)*p+r*i-l*e),g=Math.sqrt((l-i)**2+(r-e)**2);return x/(g||1e-10)},c=(o,n,f,d)=>{const p=(e,i,r)=>(r.y-e.y)*(i.x-e.x)-(i.y-e.y)*(r.x-e.x);return p(o,f,d)*p(n,f,d)<0&&p(f,o,n)*p(d,o,n)<0},h=new Array(t.length).fill(!1);h[0]=h[h.length-1]=!0;const s=[],u=(o,n)=>{if(n<=o+1)return;const f=t[o],d=t[n];let p=0,e=0;for(let i=o+1;i<n;i++){const r=y(t[i],f,d);r>p&&(p=r,e=i)}if(p>a){const i=t[e];let r=!0;for(let l=0;l<s.length-1;l++)if(c(s[l],s[l+1],f,i)||c(s[l],s[l+1],i,d)){r=!1;break}r&&(h[e]=!0,u(o,e),u(e,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:k,rendererService:t}){const a=P(!1),y=P(),c=L(()=>S(O,C),[]),h=L(()=>S(O,H),[]),s=L(()=>new J,[]),u=P(new T),o=L(()=>{if(!t.scene)return{};const e=t.scene.rootLayer.children,i=e.find(l=>l.name===K),r=e.find(l=>l.name===A);return{linesLayer:i,pointsLayer:r}},[t.scene]),n=_(()=>{if(!t?.renderer||!o||!c||!h)return;const{linesLayer:e,pointsLayer:i}=o;if(!e||!i)return;const r=s.points,l=a.current;l?i.children.length=0:i.children=r.map(({x:g,y:m},w)=>{const E=w==s.selected?h:c;return{source:E,bounds:I(g,m,E.width,E.height,y.current)}});const x=[];for(let g=1;g<r.length;g++){const m=r[g-1],w=r[g];x.push({points:[m,w],color:l?C:Y,width:B})}x.length?e.children=x:e.children.length=0,u.current.add(()=>t.update(i,e)),s.instruct(),s.log(JSON.stringify(s.points))},[t,s,o,c,h]),f=_(e=>{const i=e.key.toUpperCase();s.execute(()=>{switch(i){case D:e.preventDefault(),s.undo(),a.current=!1,n();break;case R:e.preventDefault(),s.applySmoothing(),a.current=!1,n();break;case b:e.preventDefault(),s.applySimplification(),a.current=!1,n();break;case N:e.preventDefault(),a.current=!a.current,n();break}})},[s,n]),d=_(({ptScale:e,sceneId:i})=>{if(!U(i)||!t?.renderer||!o)return;const{pointsLayer:r}=o;r&&(y.current=e,s.execute(()=>{const l=r.children,x=[];for(let g=0;g<l.length;g++){const m=l[g];m.bounds=I(m.bounds.center.x,m.bounds.center.y,m.source.width,m.source.height,y.current),x.push(m)}u.current.add(()=>t.update(...x))}))},[t,o,s]),p=_(({data:e})=>{s.execute(()=>{const{point:i}=$(e);s.add({x:i.x,y:i.y}),s.select(s.points.length-1),n()})},[s,n]);M(()=>{const e=t.renderer;if(!(!k||!e))return y.current=t.scale,s.execute(()=>{s.restore(),n()}),e.events.addEventListener("pointer:click",p),e.events.addEventListener("viewport:ptscale",d),window.addEventListener("keyup",f),()=>{window.removeEventListener("keyup",f),e.events.removeEventListener("pointer:click",p),e.events.removeEventListener("viewport:ptscale",d),u.current.destroy()}},[k,t,s,n,p,d,f])}