vehicle-path 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const f=require("react");function R(e,t){const n=t.x-e.x,s=t.y-e.y;return Math.sqrt(n*n+s*s)}function K(e,t){const n=t.x-e.x,s=t.y-e.y,r=Math.sqrt(n*n+s*s);return r===0?{x:0,y:0}:{x:n/r,y:s/r}}function we(e,t){return t*(e==="proportional-40"?.4:.5522)}function ne(e,t,n,s=!1,r){const{wheelbase:c,tangentMode:i}=n;let o;r?.fromOffset!==void 0?o=H(e,r.fromOffset,r.fromIsPercentage??!1):o=e.end;let l;r?.toOffset!==void 0?l=H(t,r.toOffset,r.toIsPercentage??!1):l=t.start;const a=K(e.start,e.end),u=s?{x:o.x-a.x*c,y:o.y-a.y*c}:o,g=K(e.start,e.end),m=K(t.start,t.end),x=R(u,l),p=we(i,x),d=s?{x:u.x-g.x*p,y:u.y-g.y*p}:{x:u.x+g.x*p,y:u.y+g.y*p},v={x:l.x-m.x*p,y:l.y-m.y*p};return{p0:u,p1:d,p2:v,p3:l}}function le(e,t){return{x:e.start.x+(e.end.x-e.start.x)*t,y:e.start.y+(e.end.y-e.start.y)*t}}function H(e,t,n){const s=R(e.start,e.end);let r;return n?r=t/100:r=s>0?t/s:0,r=Math.max(0,Math.min(1,r)),le(e,r)}function Y(e,t){const{p0:n,p1:s,p2:r,p3:c}=e,i=1-t,o=i*i,l=o*i,a=t*t,u=a*t;return{x:l*n.x+3*o*t*s.x+3*i*a*r.x+u*c.x,y:l*n.y+3*o*t*s.y+3*i*a*r.y+u*c.y}}function de(e,t=100){const n=[{t:0,distance:0}];let s=e.p0,r=0;for(let c=1;c<=t;c++){const i=c/t,o=Y(e,i);r+=R(s,o),n.push({t:i,distance:r}),s=o}return n}function ge(e,t){if(t<=0)return 0;const n=e[e.length-1].distance;if(t>=n)return 1;let s=0,r=e.length-1;for(;s<r-1;){const u=Math.floor((s+r)/2);e[u].distance<t?s=u:r=u}const c=e[s].distance,i=e[r].distance,o=e[s].t,l=e[r].t;if(i===c)return o;const a=(t-c)/(i-c);return o+a*(l-o)}function Te(e){return e[e.length-1].distance}function me(e,t=100){let n=0,s=e.p0;for(let r=1;r<=t;r++){const c=r/t,i=Y(e,c);n+=R(s,i),s=i}return n}function Ve(e,t,n,s){const r=R(e.start,e.end);return t===void 0?s/100*r:n?t/100*r:t}function se(e,t,n,s,r){const c=R(e.start,e.end),i=c-r;if(i<=0)return c;let o;if(t===void 0)o=s;else if(n)o=t;else return Math.max(r,Math.min(t,c));return r+o/100*i}function re(e,t,n,s,r){const i=R(e.start,e.end)-r;if(i<=0)return 0;let o;if(t===void 0)o=s;else if(n)o=t;else return Math.max(0,Math.min(t,i));return o/100*i}function he(e,t,n){const s=new Map,r=new Map,c=new Map;for(const i of e)r.set(i.id,i),c.set(i.id,R(i.start,i.end)),s.set(i.id,[]);for(let i=0;i<t.length;i++){const o=t[i],l=r.get(o.fromLineId),a=r.get(o.toLineId);if(!l||!a)continue;const u=se(l,o.fromOffset,o.fromIsPercentage,100,n.wheelbase),g=re(a,o.toOffset,o.toIsPercentage,0,n.wheelbase),m=ne(l,a,n,!1,{fromOffset:u,fromIsPercentage:!1,toOffset:g,toIsPercentage:!1}),x=me(m),p={curveIndex:i,fromLineId:o.fromLineId,toLineId:o.toLineId,fromOffset:u,toOffset:g,curveLength:x};s.get(o.fromLineId).push(p)}return{adjacency:s,lines:r,lineLengths:c}}function oe(e,t,n,s,r=!1){const{adjacency:c,lines:i,lineLengths:o}=e;if(!i.get(n))return null;const a=o.get(n),u=r?s/100*a:s,g=[],m=new Map,x=(d,v)=>`${d}:${Math.round(v)}`;if(t.lineId===n&&u>=t.offset){const d=u-t.offset;return{segments:[{type:"line",lineId:t.lineId,startOffset:t.offset,endOffset:u,length:d}],totalDistance:d}}const p=c.get(t.lineId)||[];for(const d of p){if(d.fromOffset<t.offset)continue;const v=d.fromOffset-t.offset,O=v+d.curveLength,L={type:"line",lineId:t.lineId,startOffset:t.offset,endOffset:d.fromOffset,length:v},h={type:"curve",curveIndex:d.curveIndex,startOffset:0,endOffset:d.curveLength,length:d.curveLength};g.push({lineId:d.toLineId,entryOffset:d.toOffset,totalDistance:O,path:[L,h]})}for(g.sort((d,v)=>d.totalDistance-v.totalDistance);g.length>0;){const d=g.shift(),v=x(d.lineId,d.entryOffset),O=m.get(v);if(O!==void 0&&O<=d.totalDistance)continue;if(m.set(v,d.totalDistance),d.lineId===n){const h=Math.abs(u-d.entryOffset);if(u>=d.entryOffset){const y={type:"line",lineId:n,startOffset:d.entryOffset,endOffset:u,length:h};return{segments:[...d.path,y],totalDistance:d.totalDistance+h}}}const L=c.get(d.lineId)||[];for(const h of L){if(h.fromOffset<d.entryOffset)continue;const y=h.fromOffset-d.entryOffset,M=d.totalDistance+y+h.curveLength,C=x(h.toLineId,h.toOffset),$=m.get(C);if($!==void 0&&$<=M)continue;const P={type:"line",lineId:d.lineId,startOffset:d.entryOffset,endOffset:h.fromOffset,length:y},I={type:"curve",curveIndex:h.curveIndex,startOffset:0,endOffset:h.curveLength,length:h.curveLength};g.push({lineId:h.toLineId,entryOffset:h.toOffset,totalDistance:M,path:[...d.path,P,I]})}g.sort((h,y)=>h.totalDistance-y.totalDistance)}return null}function $e(e,t,n,s,r=!1){return oe(e,t,n,s,r)!==null}function be(e,t,n){return(e.adjacency.get(t)||[]).filter(r=>r.fromOffset>=n)}function ie(e,t){const n=Math.sqrt(Math.pow(e.end.x-e.start.x,2)+Math.pow(e.end.y-e.start.y,2)),s=n>0?t/n:0;return{x:e.start.x+(e.end.x-e.start.x)*Math.min(1,Math.max(0,s)),y:e.start.y+(e.end.y-e.start.y)*Math.min(1,Math.max(0,s))}}function pe(e){return Math.sqrt(Math.pow(e.end.x-e.start.x,2)+Math.pow(e.end.y-e.start.y,2))}function ve(e,t,n){let s=0;for(let r=0;r<t;r++)s+=e.segments[r].length;return s+=n,s}function Ie(e,t){let n=0;for(let s=0;s<e.segments.length;s++){const r=e.segments[s],c=n+r.length;if(t<c)return{segmentIndex:s,segmentDistance:t-n};if(t===c)return s+1<e.segments.length?{segmentIndex:s+1,segmentDistance:0}:{segmentIndex:s,segmentDistance:r.length};n+=r.length}return null}function X(e,t,n,s){const c=ve(e,t,n)+s;return Ie(e,c)}function ce(e,t,n){const s=e.execution,r=n.vehicleQueues.get(e.vehicle.id),c=r?.[s.currentCommandIndex];if(c&&n.onCommandComplete&&n.onCommandComplete({vehicleId:e.vehicle.id,command:c,finalPosition:{lineId:e.vehicle.rear.lineId,absoluteOffset:e.vehicle.rear.absoluteOffset,position:e.vehicle.rear.position},payload:c.payload}),c?.awaitConfirmation)return{handled:!0,vehicle:{...e.vehicle,state:"waiting"},newExecution:s,isWaiting:!0};const i=s.currentCommandIndex+1;if(r&&i<r.length){const l=r[i],a=n.graphRef.current;if(a){const u={graph:a,linesMap:n.linesMap,curves:n.curves,config:n.config},g=n.prepareCommandPath(e.vehicle,l,u);if(g){const m=X(g.path,0,t,n.config.wheelbase),x={path:g.path,curveDataMap:g.curveDataMap,currentCommandIndex:i,rear:{currentSegmentIndex:0,segmentDistance:t},front:m?{currentSegmentIndex:m.segmentIndex,segmentDistance:m.segmentDistance}:{currentSegmentIndex:0,segmentDistance:t}};return{handled:!0,vehicle:{...e.vehicle,state:"moving"},newExecution:x}}}}return{handled:!0,vehicle:{...e.vehicle,state:"idle"},newExecution:null}}function Ee(e,t){const n=e.execution;return n?n.rear.currentSegmentIndex>=n.path.segments.length?ce(e,n.rear.segmentDistance,t):{handled:!1,vehicle:e.vehicle}:{handled:!1,vehicle:e.vehicle}}function k(e,t){return{position:ie(e,t),lineId:e.id,absoluteOffset:t}}function U(e,t){const n=ge(e.arcLengthTable,t);return{position:Y(e.bezier,n)}}function Z(e,t,n,s,r,c,i){const o=n.segments[t.currentSegmentIndex],l=t.segmentDistance+s;if(l>=o.length){const u=l-o.length,g=t.currentSegmentIndex+1;if(g>=n.segments.length){if(i!==void 0&&o.type==="line"){const d=r.get(o.lineId),v=o.startOffset+l;if(v<=i){const L=k(d,v);return{axleState:{...e,...L},execution:{...t,segmentDistance:l},completed:!1}}const O=k(d,i);return{axleState:{...e,...O},execution:{...t,segmentDistance:i-o.startOffset},completed:!0}}const p=o.type==="line"?k(r.get(o.lineId),o.endOffset):U(c.get(o.curveIndex),o.length);return{axleState:{...e,...p},execution:{...t,segmentDistance:o.length},completed:!0}}const m=n.segments[g],x=m.type==="line"?k(r.get(m.lineId),m.startOffset+u):U(c.get(m.curveIndex),u);return{axleState:{...e,...x},execution:{currentSegmentIndex:g,segmentDistance:u},completed:!1}}const a=o.type==="line"?k(r.get(o.lineId),o.startOffset+l):U(c.get(o.curveIndex),l);return{axleState:{...e,...a},execution:{...t,segmentDistance:l},completed:!1}}function xe(e,t,n,s){const r=Math.sqrt(Math.pow(s.end.x-s.start.x,2)+Math.pow(s.end.y-s.start.y,2));let c=t+n;c=Math.min(c,r);const i=ie(s,c);return{lineId:e,position:i,absoluteOffset:c}}function ye(e,t){return{...e,state:"idle"}}function Se(e){return{vehicle:e,execution:null}}function ee(e,t){const n=[],s=new Map;for(const r of e){if(!t.get(r.lineId))continue;const i=ye(r);n.push(i);const o=Se(i);s.set(r.id,o)}return{movingVehicles:n,stateMap:s}}function Re(e,t,n,s){const r=new Map;for(const c of e.segments)if(c.type==="curve"&&c.curveIndex!==void 0){const i=t[c.curveIndex];if(i){const o=n.get(i.fromLineId),l=n.get(i.toLineId);if(o&&l){const a=se(o,i.fromOffset,i.fromIsPercentage,100,s.wheelbase),u=re(l,i.toOffset,i.toIsPercentage,0,s.wheelbase),g=ne(o,l,s,!1,{fromOffset:a,fromIsPercentage:!1,toOffset:u,toIsPercentage:!1}),m=de(g);r.set(c.curveIndex,{bezier:g,arcLengthTable:m})}}}return r}function W(e,t,n){const{graph:s,linesMap:r,curves:c,config:i}=n,o=r.get(t.targetLineId);if(!o)return null;const a=pe(o)-i.wheelbase;if(a<=0)return null;const u=t.isPercentage?t.targetOffset/100*a:Math.min(t.targetOffset,a),g=oe(s,{lineId:e.lineId,offset:e.rear.absoluteOffset},t.targetLineId,u,!1);if(!g)return null;const m=Re(g,c,r,i);return{path:g,curveDataMap:m}}function Ae({vehicles:e,lines:t,vehicleQueues:n,velocity:s,wheelbase:r,tangentMode:c,curves:i,eventEmitter:o}){const[l,a]=f.useState("stopped"),[u,g]=f.useState([]),m=f.useMemo(()=>({wheelbase:r,tangentMode:c}),[r,c]),x=f.useMemo(()=>new Map(t.map(I=>[I.id,I])),[t]),p=f.useRef(null),d=f.useRef(new Map);f.useEffect(()=>{const{movingVehicles:I,stateMap:S}=ee(e,x);d.current=S;const D=setTimeout(()=>{g(I)},0);return()=>clearTimeout(D)},[e,x]);const v=f.useRef(null);f.useEffect(()=>{t.length>0&&(v.current=he(t,i,m))},[t,i,m]);const O=f.useRef(!1),L=f.useRef(()=>{}),h=f.useRef(0),y=f.useRef(new Set);f.useEffect(()=>{L.current=()=>{const I=s;let S=!1;for(const[,D]of d.current)D.vehicle.state==="moving"&&(S=!0);if(!S){O.current=!1,h.current=0;return}g(D=>D.map(b=>{const w=d.current.get(b.id);if(!w||b.state!=="moving"||!w.execution)return b;const T=w.execution;let A;if(T.front.currentSegmentIndex<T.path.segments.length){const F=T.path.segments[T.front.currentSegmentIndex];if(F.type==="line"){const V=x.get(F.lineId);V&&(A=Math.sqrt(Math.pow(V.end.x-V.start.x,2)+Math.pow(V.end.y-V.start.y,2)))}}const q=Z(b.rear,T.rear,T.path,I,x,T.curveDataMap),E=Z(b.front,T.front,T.path,I,x,T.curveDataMap,A);if(q.completed){const F={linesMap:x,config:m,vehicleQueues:n,curves:i,graphRef:v,prepareCommandPath:W,onCommandComplete:o?j=>o.emit("commandComplete",j):void 0},V={...b,rear:q.axleState,front:E.axleState};w.vehicle=V,w.execution.rear=q.execution,w.execution.front=E.execution;const ae=q.execution.segmentDistance,z=ce(w,ae,F);if(w.vehicle=z.vehicle,z.newExecution!==void 0&&(w.execution=z.newExecution),o&&z.vehicle.state!=="moving"&&o.emit("stateChange",{vehicleId:b.id,from:"moving",to:z.vehicle.state}),o){const j=z.vehicle.rear.position,G=z.vehicle.front.position;o.emit("positionUpdate",{vehicleId:z.vehicle.id,rear:j,front:G,center:{x:(j.x+G.x)/2,y:(j.y+G.y)/2},angle:Math.atan2(G.y-j.y,G.x-j.x)})}return z.vehicle}const N={...b,rear:q.axleState,front:E.axleState};if(w.vehicle=N,w.execution.rear=q.execution,w.execution.front=E.execution,o){const F=N.rear.position,V=N.front.position;o.emit("positionUpdate",{vehicleId:N.id,rear:F,front:V,center:{x:(F.x+V.x)/2,y:(F.y+V.y)/2},angle:Math.atan2(V.y-F.y,V.x-F.x)})}return N})),O.current&&(p.current=requestAnimationFrame(()=>L.current()))}},[s,x,i,m,n,o]),f.useEffect(()=>(l==="running"?(O.current=!0,p.current=requestAnimationFrame(()=>L.current())):O.current=!1,()=>{p.current&&cancelAnimationFrame(p.current)}),[l]);const M=f.useCallback(()=>{if(l==="running")return;const I=v.current;I&&(h.current=0,y.current.clear(),g(S=>S.map(D=>{const b=n.get(D.id);if(!b||b.length===0)return D;const w=b[0],A=W(D,w,{graph:I,linesMap:x,curves:i,config:m});if(!A)return console.warn(`No path found for vehicle ${D.id}`),D;const q=d.current.get(D.id);if(q){const E=X(A.path,0,0,r);q.execution={path:A.path,curveDataMap:A.curveDataMap,currentCommandIndex:0,rear:{currentSegmentIndex:0,segmentDistance:0},front:E?{currentSegmentIndex:E.segmentIndex,segmentDistance:E.segmentDistance}:{currentSegmentIndex:0,segmentDistance:0}},q.vehicle={...D,state:"moving"},o&&D.state!=="moving"&&o.emit("stateChange",{vehicleId:D.id,from:D.state,to:"moving"})}return{...D,state:"moving"}})),a("running"))},[l,x,i,n,m,r,o]),C=f.useCallback(()=>{p.current&&(cancelAnimationFrame(p.current),p.current=null),a("paused")},[]),$=f.useCallback(()=>{p.current&&(cancelAnimationFrame(p.current),p.current=null);const{movingVehicles:I,stateMap:S}=ee(e,x);d.current=S,g(I),a("stopped")},[e,x]),P=f.useCallback(I=>{const S=d.current.get(I);if(!S||S.vehicle.state!=="waiting")return!1;const D=n.get(I),b=S.execution;if(!b)return!1;const w=b.currentCommandIndex+1;if(D&&w<D.length){const T=v.current;if(T){const A=D[w],q={graph:T,linesMap:x,curves:i,config:m},E=W(S.vehicle,A,q);if(E){const N=X(E.path,0,0,r);return S.execution={path:E.path,curveDataMap:E.curveDataMap,currentCommandIndex:w,rear:{currentSegmentIndex:0,segmentDistance:0},front:N?{currentSegmentIndex:N.segmentIndex,segmentDistance:N.segmentDistance}:{currentSegmentIndex:0,segmentDistance:0}},S.vehicle={...S.vehicle,state:"moving"},o&&o.emit("stateChange",{vehicleId:I,from:"waiting",to:"moving"}),g(F=>F.map(V=>V.id===I?S.vehicle:V)),l!=="running"&&a("running"),!0}}}return S.vehicle={...S.vehicle,state:"idle"},S.execution=null,o&&o.emit("stateChange",{vehicleId:I,from:"waiting",to:"idle"}),g(T=>T.map(A=>A.id===I?S.vehicle:A)),!0},[n,x,i,m,r,l,o]);return{movingVehicles:u,playbackState:l,handleRun:M,handlePause:C,handleReset:$,continueVehicle:P}}class Le{listeners=new Map;on(t,n){return this.listeners.has(t)||this.listeners.set(t,new Set),this.listeners.get(t).add(n),()=>{this.listeners.get(t)?.delete(n)}}emit(t,n){this.listeners.get(t)?.forEach(s=>{try{s(n)}catch(r){console.error(`Error in event listener for "${t}":`,r)}})}off(t){t?this.listeners.delete(t):this.listeners.clear()}listenerCount(t){return this.listeners.get(t)?.size??0}}const Me=f.createContext(null);function Oe(){const e=f.useContext(Me);if(!e)throw new Error("useVehicleEventEmitter must be used within a VehicleEventProvider");return e}function qe(){return f.useMemo(()=>new Le,[])}function Fe(e,t,n=[]){const s=Oe();f.useEffect(()=>s.on(e,t),[s,e,...n])}function te(e){const t=[],n=[],s=[],r=e.trim().split(`
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const f=require("react");function R(e,t){const n=t.x-e.x,s=t.y-e.y;return Math.sqrt(n*n+s*s)}function K(e,t){const n=t.x-e.x,s=t.y-e.y,r=Math.sqrt(n*n+s*s);return r===0?{x:0,y:0}:{x:n/r,y:s/r}}function we(e,t){return t*(e==="proportional-40"?.4:.5522)}function ne(e,t,n,s=!1,r){const{wheelbase:c,tangentMode:i}=n;let o;r?.fromOffset!==void 0?o=H(e,r.fromOffset,r.fromIsPercentage??!1):o=e.end;let l;r?.toOffset!==void 0?l=H(t,r.toOffset,r.toIsPercentage??!1):l=t.start;const a=K(e.start,e.end),u=s?{x:o.x-a.x*c,y:o.y-a.y*c}:o,g=K(e.start,e.end),m=K(t.start,t.end),x=R(u,l),p=we(i,x),d=s?{x:u.x-g.x*p,y:u.y-g.y*p}:{x:u.x+g.x*p,y:u.y+g.y*p},v={x:l.x-m.x*p,y:l.y-m.y*p};return{p0:u,p1:d,p2:v,p3:l}}function le(e,t){return{x:e.start.x+(e.end.x-e.start.x)*t,y:e.start.y+(e.end.y-e.start.y)*t}}function H(e,t,n){const s=R(e.start,e.end);let r;return n?r=t/100:r=s>0?t/s:0,r=Math.max(0,Math.min(1,r)),le(e,r)}function Y(e,t){const{p0:n,p1:s,p2:r,p3:c}=e,i=1-t,o=i*i,l=o*i,a=t*t,u=a*t;return{x:l*n.x+3*o*t*s.x+3*i*a*r.x+u*c.x,y:l*n.y+3*o*t*s.y+3*i*a*r.y+u*c.y}}function de(e,t=100){const n=[{t:0,distance:0}];let s=e.p0,r=0;for(let c=1;c<=t;c++){const i=c/t,o=Y(e,i);r+=R(s,o),n.push({t:i,distance:r}),s=o}return n}function ge(e,t){if(t<=0)return 0;const n=e[e.length-1].distance;if(t>=n)return 1;let s=0,r=e.length-1;for(;s<r-1;){const u=Math.floor((s+r)/2);e[u].distance<t?s=u:r=u}const c=e[s].distance,i=e[r].distance,o=e[s].t,l=e[r].t;if(i===c)return o;const a=(t-c)/(i-c);return o+a*(l-o)}function Te(e){return e[e.length-1].distance}function me(e,t=100){let n=0,s=e.p0;for(let r=1;r<=t;r++){const c=r/t,i=Y(e,c);n+=R(s,i),s=i}return n}function Ve(e,t,n,s){const r=R(e.start,e.end);return t===void 0?s/100*r:n?t/100*r:t}function se(e,t,n,s,r){const c=R(e.start,e.end),i=c-r;if(i<=0)return c;let o;if(t===void 0)o=s;else if(n)o=t;else return Math.max(r,Math.min(t,c));return r+o/100*i}function re(e,t,n,s,r){const i=R(e.start,e.end)-r;if(i<=0)return 0;let o;if(t===void 0)o=s;else if(n)o=t;else return Math.max(0,Math.min(t,i));return o/100*i}function he(e,t,n){const s=new Map,r=new Map,c=new Map;for(const i of e)r.set(i.id,i),c.set(i.id,R(i.start,i.end)),s.set(i.id,[]);for(let i=0;i<t.length;i++){const o=t[i],l=r.get(o.fromLineId),a=r.get(o.toLineId);if(!l||!a)continue;const u=se(l,o.fromOffset,o.fromIsPercentage,100,n.wheelbase),g=re(a,o.toOffset,o.toIsPercentage,0,n.wheelbase),m=ne(l,a,n,!1,{fromOffset:u,fromIsPercentage:!1,toOffset:g,toIsPercentage:!1}),x=me(m),p={curveIndex:i,fromLineId:o.fromLineId,toLineId:o.toLineId,fromOffset:u,toOffset:g,curveLength:x};s.get(o.fromLineId).push(p)}return{adjacency:s,lines:r,lineLengths:c}}function oe(e,t,n,s,r=!1){const{adjacency:c,lines:i,lineLengths:o}=e;if(!i.get(n))return null;const a=o.get(n),u=r?s/100*a:s,g=[],m=new Map,x=(d,v)=>`${d}:${Math.round(v)}`;if(t.lineId===n&&u>=t.offset){const d=u-t.offset;return{segments:[{type:"line",lineId:t.lineId,startOffset:t.offset,endOffset:u,length:d}],totalDistance:d}}const p=c.get(t.lineId)||[];for(const d of p){if(d.fromOffset<t.offset)continue;const v=d.fromOffset-t.offset,O=v+d.curveLength,L={type:"line",lineId:t.lineId,startOffset:t.offset,endOffset:d.fromOffset,length:v},h={type:"curve",curveIndex:d.curveIndex,startOffset:0,endOffset:d.curveLength,length:d.curveLength};g.push({lineId:d.toLineId,entryOffset:d.toOffset,totalDistance:O,path:[L,h]})}for(g.sort((d,v)=>d.totalDistance-v.totalDistance);g.length>0;){const d=g.shift(),v=x(d.lineId,d.entryOffset),O=m.get(v);if(O!==void 0&&O<=d.totalDistance)continue;if(m.set(v,d.totalDistance),d.lineId===n){const h=Math.abs(u-d.entryOffset);if(u>=d.entryOffset){const y={type:"line",lineId:n,startOffset:d.entryOffset,endOffset:u,length:h};return{segments:[...d.path,y],totalDistance:d.totalDistance+h}}}const L=c.get(d.lineId)||[];for(const h of L){if(h.fromOffset<d.entryOffset)continue;const y=h.fromOffset-d.entryOffset,M=d.totalDistance+y+h.curveLength,C=x(h.toLineId,h.toOffset),$=m.get(C);if($!==void 0&&$<=M)continue;const P={type:"line",lineId:d.lineId,startOffset:d.entryOffset,endOffset:h.fromOffset,length:y},I={type:"curve",curveIndex:h.curveIndex,startOffset:0,endOffset:h.curveLength,length:h.curveLength};g.push({lineId:h.toLineId,entryOffset:h.toOffset,totalDistance:M,path:[...d.path,P,I]})}g.sort((h,y)=>h.totalDistance-y.totalDistance)}return null}function $e(e,t,n,s,r=!1){return oe(e,t,n,s,r)!==null}function be(e,t,n){return(e.adjacency.get(t)||[]).filter(r=>r.fromOffset>=n)}function ie(e,t){const n=Math.sqrt(Math.pow(e.end.x-e.start.x,2)+Math.pow(e.end.y-e.start.y,2)),s=n>0?t/n:0;return{x:e.start.x+(e.end.x-e.start.x)*Math.min(1,Math.max(0,s)),y:e.start.y+(e.end.y-e.start.y)*Math.min(1,Math.max(0,s))}}function pe(e){return Math.sqrt(Math.pow(e.end.x-e.start.x,2)+Math.pow(e.end.y-e.start.y,2))}function ve(e,t,n){let s=0;for(let r=0;r<t;r++)s+=e.segments[r].length;return s+=n,s}function Ie(e,t){let n=0;for(let s=0;s<e.segments.length;s++){const r=e.segments[s],c=n+r.length;if(t<c)return{segmentIndex:s,segmentDistance:t-n};if(t===c)return s+1<e.segments.length?{segmentIndex:s+1,segmentDistance:0}:{segmentIndex:s,segmentDistance:r.length};n+=r.length}return null}function X(e,t,n,s){const c=ve(e,t,n)+s;return Ie(e,c)}function ce(e,t,n){const s=e.execution,r=n.vehicleQueues.get(e.vehicle.id),c=r?.[s.currentCommandIndex];if(c&&n.onCommandComplete&&n.onCommandComplete({vehicleId:e.vehicle.id,command:c,finalPosition:{lineId:e.vehicle.rear.lineId,absoluteOffset:e.vehicle.rear.absoluteOffset,position:e.vehicle.rear.position},payload:c.payload}),c?.awaitConfirmation)return{handled:!0,vehicle:{...e.vehicle,state:"waiting"},newExecution:s,isWaiting:!0};const i=s.currentCommandIndex+1;if(r&&i<r.length){const l=r[i],a=n.graphRef.current;if(a){const u={graph:a,linesMap:n.linesMap,curves:n.curves,config:n.config},g=n.prepareCommandPath(e.vehicle,l,u);if(g){const m=X(g.path,0,t,n.config.wheelbase),x={path:g.path,curveDataMap:g.curveDataMap,currentCommandIndex:i,rear:{currentSegmentIndex:0,segmentDistance:t},front:m?{currentSegmentIndex:m.segmentIndex,segmentDistance:m.segmentDistance}:{currentSegmentIndex:0,segmentDistance:t}};return{handled:!0,vehicle:{...e.vehicle,state:"moving"},newExecution:x}}}}return{handled:!0,vehicle:{...e.vehicle,state:"idle"},newExecution:null}}function Ee(e,t){const n=e.execution;return n?n.rear.currentSegmentIndex>=n.path.segments.length?ce(e,n.rear.segmentDistance,t):{handled:!1,vehicle:e.vehicle}:{handled:!1,vehicle:e.vehicle}}function k(e,t){return{position:ie(e,t),lineId:e.id,absoluteOffset:t}}function U(e,t){const n=ge(e.arcLengthTable,t);return{position:Y(e.bezier,n)}}function Z(e,t,n,s,r,c,i){const o=n.segments[t.currentSegmentIndex],l=t.segmentDistance+s;if(l>=o.length){const u=l-o.length,g=t.currentSegmentIndex+1;if(g>=n.segments.length){if(i!==void 0&&o.type==="line"){const d=r.get(o.lineId),v=o.startOffset+l;if(v<=i){const L=k(d,v);return{axleState:{...e,...L},execution:{...t,segmentDistance:l},completed:!1}}const O=k(d,i);return{axleState:{...e,...O},execution:{...t,segmentDistance:i-o.startOffset},completed:!0}}const p=o.type==="line"?k(r.get(o.lineId),o.endOffset):U(c.get(o.curveIndex),o.length);return{axleState:{...e,...p},execution:{...t,segmentDistance:o.length},completed:!0}}const m=n.segments[g],x=m.type==="line"?k(r.get(m.lineId),m.startOffset+u):U(c.get(m.curveIndex),u);return{axleState:{...e,...x},execution:{currentSegmentIndex:g,segmentDistance:u},completed:!1}}const a=o.type==="line"?k(r.get(o.lineId),o.startOffset+l):U(c.get(o.curveIndex),l);return{axleState:{...e,...a},execution:{...t,segmentDistance:l},completed:!1}}function xe(e,t,n,s){const r=Math.sqrt(Math.pow(s.end.x-s.start.x,2)+Math.pow(s.end.y-s.start.y,2));let c=t+n;c=Math.min(c,r);const i=ie(s,c);return{lineId:e,position:i,absoluteOffset:c}}function ye(e,t){return{...e,state:"idle"}}function Se(e){return{vehicle:e,execution:null}}function ee(e,t){const n=[],s=new Map;for(const r of e){if(!t.get(r.lineId))continue;const i=ye(r);n.push(i);const o=Se(i);s.set(r.id,o)}return{movingVehicles:n,stateMap:s}}function Re(e,t,n,s){const r=new Map;for(const c of e.segments)if(c.type==="curve"&&c.curveIndex!==void 0){const i=t[c.curveIndex];if(i){const o=n.get(i.fromLineId),l=n.get(i.toLineId);if(o&&l){const a=se(o,i.fromOffset,i.fromIsPercentage,100,s.wheelbase),u=re(l,i.toOffset,i.toIsPercentage,0,s.wheelbase),g=ne(o,l,s,!1,{fromOffset:a,fromIsPercentage:!1,toOffset:u,toIsPercentage:!1}),m=de(g);r.set(c.curveIndex,{bezier:g,arcLengthTable:m})}}}return r}function W(e,t,n){const{graph:s,linesMap:r,curves:c,config:i}=n,o=r.get(t.targetLineId);if(!o)return null;const a=pe(o)-i.wheelbase;if(a<=0)return null;const u=t.isPercentage?t.targetOffset/100*a:Math.min(t.targetOffset,a),g=oe(s,{lineId:e.rear.lineId,offset:e.rear.absoluteOffset},t.targetLineId,u,!1);if(!g)return null;const m=Re(g,c,r,i);return{path:g,curveDataMap:m}}function Ae({vehicles:e,lines:t,vehicleQueues:n,velocity:s,wheelbase:r,tangentMode:c,curves:i,eventEmitter:o}){const[l,a]=f.useState("stopped"),[u,g]=f.useState([]),m=f.useMemo(()=>({wheelbase:r,tangentMode:c}),[r,c]),x=f.useMemo(()=>new Map(t.map(I=>[I.id,I])),[t]),p=f.useRef(null),d=f.useRef(new Map);f.useEffect(()=>{const{movingVehicles:I,stateMap:S}=ee(e,x);d.current=S;const D=setTimeout(()=>{g(I)},0);return()=>clearTimeout(D)},[e,x]);const v=f.useRef(null);f.useEffect(()=>{t.length>0&&(v.current=he(t,i,m))},[t,i,m]);const O=f.useRef(!1),L=f.useRef(()=>{}),h=f.useRef(0),y=f.useRef(new Set);f.useEffect(()=>{L.current=()=>{const I=s;let S=!1;for(const[,D]of d.current)D.vehicle.state==="moving"&&(S=!0);if(!S){O.current=!1,h.current=0;return}g(D=>D.map(b=>{const w=d.current.get(b.id);if(!w||b.state!=="moving"||!w.execution)return b;const T=w.execution;let A;if(T.front.currentSegmentIndex<T.path.segments.length){const F=T.path.segments[T.front.currentSegmentIndex];if(F.type==="line"){const V=x.get(F.lineId);V&&(A=Math.sqrt(Math.pow(V.end.x-V.start.x,2)+Math.pow(V.end.y-V.start.y,2)))}}const q=Z(b.rear,T.rear,T.path,I,x,T.curveDataMap),E=Z(b.front,T.front,T.path,I,x,T.curveDataMap,A);if(q.completed){const F={linesMap:x,config:m,vehicleQueues:n,curves:i,graphRef:v,prepareCommandPath:W,onCommandComplete:o?j=>o.emit("commandComplete",j):void 0},V={...b,rear:q.axleState,front:E.axleState};w.vehicle=V,w.execution.rear=q.execution,w.execution.front=E.execution;const ae=q.execution.segmentDistance,z=ce(w,ae,F);if(w.vehicle=z.vehicle,z.newExecution!==void 0&&(w.execution=z.newExecution),o&&z.vehicle.state!=="moving"&&o.emit("stateChange",{vehicleId:b.id,from:"moving",to:z.vehicle.state}),o){const j=z.vehicle.rear.position,G=z.vehicle.front.position;o.emit("positionUpdate",{vehicleId:z.vehicle.id,rear:j,front:G,center:{x:(j.x+G.x)/2,y:(j.y+G.y)/2},angle:Math.atan2(G.y-j.y,G.x-j.x)})}return z.vehicle}const N={...b,rear:q.axleState,front:E.axleState};if(w.vehicle=N,w.execution.rear=q.execution,w.execution.front=E.execution,o){const F=N.rear.position,V=N.front.position;o.emit("positionUpdate",{vehicleId:N.id,rear:F,front:V,center:{x:(F.x+V.x)/2,y:(F.y+V.y)/2},angle:Math.atan2(V.y-F.y,V.x-F.x)})}return N})),O.current&&(p.current=requestAnimationFrame(()=>L.current()))}},[s,x,i,m,n,o]),f.useEffect(()=>(l==="running"?(O.current=!0,p.current=requestAnimationFrame(()=>L.current())):O.current=!1,()=>{p.current&&cancelAnimationFrame(p.current)}),[l]);const M=f.useCallback(()=>{if(l==="running")return;const I=v.current;I&&(h.current=0,y.current.clear(),g(S=>S.map(D=>{const b=n.get(D.id);if(!b||b.length===0)return D;const w=b[0],A=W(D,w,{graph:I,linesMap:x,curves:i,config:m});if(!A)return console.warn(`No path found for vehicle ${D.id}`),D;const q=d.current.get(D.id);if(q){const E=X(A.path,0,0,r);q.execution={path:A.path,curveDataMap:A.curveDataMap,currentCommandIndex:0,rear:{currentSegmentIndex:0,segmentDistance:0},front:E?{currentSegmentIndex:E.segmentIndex,segmentDistance:E.segmentDistance}:{currentSegmentIndex:0,segmentDistance:0}},q.vehicle={...D,state:"moving"},o&&D.state!=="moving"&&o.emit("stateChange",{vehicleId:D.id,from:D.state,to:"moving"})}return{...D,state:"moving"}})),a("running"))},[l,x,i,n,m,r,o]),C=f.useCallback(()=>{p.current&&(cancelAnimationFrame(p.current),p.current=null),a("paused")},[]),$=f.useCallback(()=>{p.current&&(cancelAnimationFrame(p.current),p.current=null);const{movingVehicles:I,stateMap:S}=ee(e,x);d.current=S,g(I),a("stopped")},[e,x]),P=f.useCallback(I=>{const S=d.current.get(I);if(!S||S.vehicle.state!=="waiting")return!1;const D=n.get(I),b=S.execution;if(!b)return!1;const w=b.currentCommandIndex+1;if(D&&w<D.length){const T=v.current;if(T){const A=D[w],q={graph:T,linesMap:x,curves:i,config:m},E=W(S.vehicle,A,q);if(E){const N=X(E.path,0,0,r);return S.execution={path:E.path,curveDataMap:E.curveDataMap,currentCommandIndex:w,rear:{currentSegmentIndex:0,segmentDistance:0},front:N?{currentSegmentIndex:N.segmentIndex,segmentDistance:N.segmentDistance}:{currentSegmentIndex:0,segmentDistance:0}},S.vehicle={...S.vehicle,state:"moving"},o&&o.emit("stateChange",{vehicleId:I,from:"waiting",to:"moving"}),g(F=>F.map(V=>V.id===I?S.vehicle:V)),l!=="running"&&a("running"),!0}}}return S.vehicle={...S.vehicle,state:"idle"},S.execution=null,o&&o.emit("stateChange",{vehicleId:I,from:"waiting",to:"idle"}),g(T=>T.map(A=>A.id===I?S.vehicle:A)),!0},[n,x,i,m,r,l,o]);return{movingVehicles:u,playbackState:l,handleRun:M,handlePause:C,handleReset:$,continueVehicle:P}}class Le{listeners=new Map;on(t,n){return this.listeners.has(t)||this.listeners.set(t,new Set),this.listeners.get(t).add(n),()=>{this.listeners.get(t)?.delete(n)}}emit(t,n){this.listeners.get(t)?.forEach(s=>{try{s(n)}catch(r){console.error(`Error in event listener for "${t}":`,r)}})}off(t){t?this.listeners.delete(t):this.listeners.clear()}listenerCount(t){return this.listeners.get(t)?.size??0}}const Me=f.createContext(null);function Oe(){const e=f.useContext(Me);if(!e)throw new Error("useVehicleEventEmitter must be used within a VehicleEventProvider");return e}function qe(){return f.useMemo(()=>new Le,[])}function Fe(e,t,n=[]){const s=Oe();f.useEffect(()=>s.on(e,t),[s,e,...n])}function te(e){const t=[],n=[],s=[],r=e.trim().split(`
2
2
  `);for(const c of r){const i=c.trim();if(!i||i.startsWith("#"))continue;const o=i.match(/^(\w+)\s*:\s*\((\d+),\s*(\d+)\)\s*->\s*\((\d+),\s*(\d+)\)/);if(o){t.push({id:o[1],start:{x:parseInt(o[2]),y:parseInt(o[3])},end:{x:parseInt(o[4]),y:parseInt(o[5])}});continue}const l=i.match(/^(\w+)(?:\s+(\d+(?:\.\d+)?)(%?))??\s*->\s*(\w+)(?:\s+(\d+(?:\.\d+)?)(%?))?/);if(l){const u={fromLineId:l[1],toLineId:l[4]};l[2]&&(u.fromOffset=parseFloat(l[2]),u.fromIsPercentage=l[3]==="%"),l[5]&&(u.toOffset=parseFloat(l[5]),u.toIsPercentage=l[6]==="%"),n.push(u);continue}const a=i.match(/^(\w+)\s+start\s+(\w+)\s+(\d+(?:\.\d+)?)(%?)/);if(a){const u=parseFloat(a[3]),g=a[4]==="%";s.push({vehicleId:a[1],lineId:a[2],offset:u,isPercentage:g});continue}}return{lines:t,curves:n,vehicles:s}}function Ce(e){const t=[];return e.lines.forEach(n=>{t.push(`${n.id} : (${Math.round(n.start.x)}, ${Math.round(n.start.y)}) -> (${Math.round(n.end.x)}, ${Math.round(n.end.y)})`)}),e.lines.length>0&&e.curves.length>0&&t.push(""),e.curves.forEach(n=>{let s=`${n.fromLineId}`;n.fromOffset!==void 0&&(s+=` ${n.fromOffset}${n.fromIsPercentage?"%":""}`),s+=" -> ",s+=`${n.toLineId}`,n.toOffset!==void 0&&(s+=` ${n.toOffset}${n.toIsPercentage?"%":""}`),t.push(s)}),(e.lines.length>0||e.curves.length>0)&&e.vehicles.length>0&&t.push(""),e.vehicles.forEach(n=>{const s=n.isPercentage?`${n.offset}%`:`${n.offset}`;t.push(`${n.vehicleId} start ${n.lineId} ${s}`)}),t.join(`
3
3
  `)}function B(e){const t=[],n=e.trim().split(`
4
4
  `);for(const s of n){const r=s.trim();if(!r||r.startsWith("#"))continue;const c=r.match(/^(\w+)\s+start\s+(\w+)\s+(\d+(?:\.\d+)?)(%?)/);c&&t.push({vehicleId:c[1],lineId:c[2],offset:parseFloat(c[3]),isPercentage:c[4]==="%"})}return t}function De(e){return e.map(t=>{const n=t.isPercentage?`${t.offset}%`:`${t.offset}`;return`${t.vehicleId} start ${t.lineId} ${n}`}).join(`
@@ -509,7 +509,7 @@ function X(e, t, n) {
509
509
  if (a <= 0) return null;
510
510
  const u = t.isPercentage ? t.targetOffset / 100 * a : Math.min(t.targetOffset, a), d = de(
511
511
  s,
512
- { lineId: e.lineId, offset: e.rear.absoluteOffset },
512
+ { lineId: e.rear.lineId, offset: e.rear.absoluteOffset },
513
513
  t.targetLineId,
514
514
  u,
515
515
  !1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vehicle-path",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Vehicle motion simulator library for dual-axle vehicle movement along paths composed of lines and Bezier curves",
5
5
  "type": "module",
6
6
  "main": "./dist/vehicle-path.cjs",