vehicle-path2 1.0.0

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.
Files changed (42) hide show
  1. package/README.md +160 -0
  2. package/dist/animation-loop-bZEm2pMN.js +37 -0
  3. package/dist/animation-loop-fC2LjxCd.cjs +1 -0
  4. package/dist/core/algorithms/math.d.ts +35 -0
  5. package/dist/core/algorithms/pathFinding.d.ts +62 -0
  6. package/dist/core/algorithms/vehicleMovement.d.ts +174 -0
  7. package/dist/core/index.d.ts +20 -0
  8. package/dist/core/types/api.d.ts +185 -0
  9. package/dist/core/types/config.d.ts +9 -0
  10. package/dist/core/types/geometry.d.ts +26 -0
  11. package/dist/core/types/movement.d.ts +56 -0
  12. package/dist/core/types/vehicle.d.ts +65 -0
  13. package/dist/core.cjs +1 -0
  14. package/dist/core.js +551 -0
  15. package/dist/index.d.ts +45 -0
  16. package/dist/react/dsl-hooks/useInitialMovement.d.ts +24 -0
  17. package/dist/react/dsl-hooks/useMovementSequence.d.ts +27 -0
  18. package/dist/react/dsl-hooks/useSceneDefinition.d.ts +22 -0
  19. package/dist/react/hooks/useAnimation.d.ts +43 -0
  20. package/dist/react/hooks/useMovementQueue.d.ts +52 -0
  21. package/dist/react/hooks/useScene.d.ts +78 -0
  22. package/dist/react/hooks/useVehicleSimulation.d.ts +126 -0
  23. package/dist/react/hooks/useVehicles.d.ts +55 -0
  24. package/dist/react/index.d.ts +48 -0
  25. package/dist/react/providers/useVehicleEvents.d.ts +78 -0
  26. package/dist/react.cjs +1 -0
  27. package/dist/react.js +18 -0
  28. package/dist/useVehicleEvents-B2JQFNjc.js +923 -0
  29. package/dist/useVehicleEvents-CBymulau.cjs +3 -0
  30. package/dist/utils/animation-loop.d.ts +105 -0
  31. package/dist/utils/dsl-parser.d.ts +152 -0
  32. package/dist/utils/event-emitter.d.ts +94 -0
  33. package/dist/utils/index.d.ts +15 -0
  34. package/dist/utils/type-converters.d.ts +38 -0
  35. package/dist/utils/vehicle-helpers.d.ts +8 -0
  36. package/dist/utils.cjs +1 -0
  37. package/dist/utils.js +17 -0
  38. package/dist/vehicle-helpers-DIcksrtO.cjs +7 -0
  39. package/dist/vehicle-helpers-_72KxCqO.js +276 -0
  40. package/dist/vehicle-path.cjs +1 -0
  41. package/dist/vehicle-path.js +62 -0
  42. package/package.json +103 -0
@@ -0,0 +1,3 @@
1
+ "use strict";const o=require("react"),q=require("./core.cjs"),O=require("./vehicle-helpers-DIcksrtO.cjs"),te=require("react/jsx-runtime");function F(s){return Array.isArray(s)?{x:s[0],y:s[1]}:s}function z(s){return{id:s.id,start:F(s.start),end:F(s.end)}}function j(s){const c=s.fromIsPercentage!==!1,I=s.toIsPercentage!==!1;return{fromLineId:s.from,toLineId:s.to,fromOffset:s.fromPosition!==void 0?c?s.fromPosition*100:s.fromPosition:void 0,fromIsPercentage:s.fromPosition!==void 0?c:void 0,toOffset:s.toPosition!==void 0?I?s.toPosition*100:s.toPosition:void 0,toIsPercentage:s.toPosition!==void 0?I:void 0}}function N(s){const c=s.position??0,I=s.isPercentage!==!1;return{vehicleId:s.id,lineId:s.lineId,offset:I?c*100:c,isPercentage:I}}function H(s){const c=s.isPercentage!==!1,I=s.targetPosition??1;return{vehicleId:s.vehicleId,targetLineId:s.targetLineId,targetOffset:c?I*100:I,isPercentage:c,awaitConfirmation:s.wait,payload:s.payload}}function se(s){const c=[],I=new Set;for(const r of s.lines)I.has(r.id)&&c.push(`Duplicate line ID: ${r.id}`),I.add(r.id);if(s.connections)for(const r of s.connections){I.has(r.from)||c.push(`Connection references non-existent line: ${r.from}`),I.has(r.to)||c.push(`Connection references non-existent line: ${r.to}`);const m=r.fromIsPercentage!==!1,t=r.toIsPercentage!==!1;!m&&r.fromPosition===void 0&&c.push("fromPosition is required when fromIsPercentage is false"),!t&&r.toPosition===void 0&&c.push("toPosition is required when toIsPercentage is false"),r.fromPosition!==void 0&&(m&&(r.fromPosition<0||r.fromPosition>1)?c.push(`Invalid fromPosition: ${r.fromPosition} (must be 0-1 for percentage)`):!m&&r.fromPosition<0&&c.push(`Invalid fromPosition: ${r.fromPosition} (must be >= 0 for absolute distance)`)),r.toPosition!==void 0&&(t&&(r.toPosition<0||r.toPosition>1)?c.push(`Invalid toPosition: ${r.toPosition} (must be 0-1 for percentage)`):!t&&r.toPosition<0&&c.push(`Invalid toPosition: ${r.toPosition} (must be >= 0 for absolute distance)`))}return{valid:c.length===0,errors:c}}function J(){const[s,c]=o.useState([]),[I,r]=o.useState([]),[m,t]=o.useState(null),f=o.useCallback((n,P)=>{c(n),r(P),t(null)},[]),w=o.useCallback(n=>{const P=se(n);if(!P.valid)return t(P.errors.join("; ")),{success:!1,errors:P.errors};const i=n.lines.map(z),d=n.connections?.map(j)||[];return f(i,d),{success:!0}},[f]),$=o.useCallback(n=>{if(s.some(i=>i.id===n.id)){const i=`Line with ID '${n.id}' already exists`;return t(i),{success:!1,error:i}}return c(i=>[...i,z(n)]),t(null),{success:!0}},[s]),b=o.useCallback((n,P)=>{if(s.findIndex(d=>d.id===n)===-1){const d=`Line with ID '${n}' not found`;return t(d),{success:!1,error:d}}return c(d=>d.map(C=>C.id!==n?C:{...C,start:P.start?F(P.start):C.start,end:P.end?F(P.end):C.end})),t(null),{success:!0}},[s]),S=o.useCallback(n=>{if(!s.some(i=>i.id===n)){const i=`Line with ID '${n}' not found`;return t(i),{success:!1,error:i}}return c(i=>i.filter(d=>d.id!==n)),r(i=>i.filter(d=>d.fromLineId!==n&&d.toLineId!==n)),t(null),{success:!0}},[s]),L=o.useCallback(n=>{const P=s.some(a=>a.id===n.from),i=s.some(a=>a.id===n.to);if(!P){const a=`Line '${n.from}' not found`;return t(a),{success:!1,error:a}}if(!i){const a=`Line '${n.to}' not found`;return t(a),{success:!1,error:a}}const d=n.fromIsPercentage!==!1,C=n.toIsPercentage!==!1;if(!d&&n.fromPosition===void 0){const a="fromPosition is required when fromIsPercentage is false";return t(a),{success:!1,error:a}}if(!C&&n.toPosition===void 0){const a="toPosition is required when toIsPercentage is false";return t(a),{success:!1,error:a}}if(I.some(a=>a.fromLineId===n.from&&a.toLineId===n.to)){const a=`Connection from '${n.from}' to '${n.to}' already exists`;return t(a),{success:!1,error:a}}return r(a=>[...a,j(n)]),t(null),{success:!0}},[s,I]),g=o.useCallback((n,P,i)=>{const d=I.findIndex(e=>e.fromLineId===n&&e.toLineId===P);if(d===-1){const e=`Connection from '${n}' to '${P}' not found`;return t(e),{success:!1,error:e}}const C=I[d],V=i.fromIsPercentage??C.fromIsPercentage,a=i.toIsPercentage??C.toIsPercentage;let E;i.fromOffset!==void 0?E=i.fromOffset:C.fromOffset!==void 0&&(E=C.fromIsPercentage!==!1?C.fromOffset/100:C.fromOffset);let h;if(i.toOffset!==void 0?h=i.toOffset:C.toOffset!==void 0&&(h=C.toIsPercentage!==!1?C.toOffset/100:C.toOffset),E!==void 0){if(V!==!1&&(E<0||E>1)){const e=`Invalid fromOffset: ${E} (must be 0-1 for percentage)`;return t(e),{success:!1,error:e}}if(V===!1&&E<0){const e=`Invalid fromOffset: ${E} (must be >= 0 for absolute distance)`;return t(e),{success:!1,error:e}}}if(h!==void 0){if(a!==!1&&(h<0||h>1)){const e=`Invalid toOffset: ${h} (must be 0-1 for percentage)`;return t(e),{success:!1,error:e}}if(a===!1&&h<0){const e=`Invalid toOffset: ${h} (must be >= 0 for absolute distance)`;return t(e),{success:!1,error:e}}}if(V===!1&&E===void 0){const e="fromOffset is required when fromIsPercentage is false";return t(e),{success:!1,error:e}}if(a===!1&&h===void 0){const e="toOffset is required when toIsPercentage is false";return t(e),{success:!1,error:e}}const x={from:n,to:P,fromPosition:E,fromIsPercentage:V,toPosition:h,toIsPercentage:a};return r(e=>e.map((u,l)=>l===d?j(x):u)),t(null),{success:!0}},[I]),v=o.useCallback((n,P)=>{if(!I.some(d=>d.fromLineId===n&&d.toLineId===P)){const d=`Connection from '${n}' to '${P}' not found`;return t(d),{success:!1,error:d}}return r(d=>d.filter(C=>!(C.fromLineId===n&&C.toLineId===P))),t(null),{success:!0}},[I]),p=o.useCallback(()=>{c([]),r([]),t(null)},[]);return{lines:s,curves:I,setScene:w,addLine:$,updateLine:b,removeLine:S,addConnection:L,updateConnection:g,removeConnection:v,clear:p,error:m,_loadScene:f}}function B({lines:s,wheelbase:c}){const[I,r]=o.useState([]),[m,t]=o.useState(null),f=o.useRef([]),w=o.useCallback(g=>{f.current=g,r(g),t(null)},[]),$=o.useCallback(g=>{const v=Array.isArray(g)?g:[g],p=[];for(const d of v)f.current.some(V=>V.id===d.id)&&p.push(`Vehicle with ID '${d.id}' already exists`);if(p.length>0)return t(p.join("; ")),{success:!1,errors:p};const n=v.map(N),{vehicles:P,errors:i}=O.validateAndCreateVehicles(n,s,c);return i.length>0?(t(i.join("; ")),{success:!1,errors:i}):(f.current=[...f.current,...P],r(f.current),t(null),{success:!0})},[s,c]),b=o.useCallback((g,v)=>{const p=f.current.findIndex(h=>h.id===g);if(p===-1){const h=`Vehicle with ID '${g}' not found`;return t(h),{success:!1,error:h}}const n=f.current[p];if(n.state!=="idle"){const h=`Cannot update vehicle '${g}' while it is ${n.state}. Vehicle must be idle.`;return t(h),{success:!1,error:h}}const P=v.lineId??n.lineId;if(!s.find(h=>h.id===P)){const h=`Line '${P}' not found`;return t(h),{success:!1,error:h}}let d,C;v.lineId!==void 0&&v.position===void 0?(d=0,C=!0):v.position!==void 0?(d=v.position,C=v.isPercentage??!0):(d=n.offset,C=n.isPercentage);const V={vehicleId:g,lineId:P,offset:C?d*100:d,isPercentage:C},{vehicles:a,errors:E}=O.validateAndCreateVehicles([V],s,c);return E.length>0?(t(E.join("; ")),{success:!1,error:E.join("; ")}):(f.current=f.current.map((h,x)=>x===p?a[0]:h),r(f.current),t(null),{success:!0})},[s,c]),S=o.useCallback(g=>{if(!f.current.some(p=>p.id===g)){const p=`Vehicle with ID '${g}' not found`;return t(p),{success:!1,error:p}}return f.current=f.current.filter(p=>p.id!==g),r(f.current),t(null),{success:!0}},[]),L=o.useCallback(()=>{f.current=[],r([]),t(null)},[]);return{vehicles:I,addVehicles:$,updateVehicle:b,removeVehicle:S,clear:L,error:m,_loadVehicles:w}}function W({vehicles:s,lines:c}){const[I,r]=o.useState(new Map),[m,t]=o.useState(null),f=o.useCallback(b=>{r(b),t(null)},[]),w=o.useCallback((b,S)=>{if(!s.some(i=>i.id===b)){const i=`Vehicle '${b}' not found`;return t(i),{success:!1,error:i}}const g=c.find(i=>i.id===S.targetLineId);if(!g){const i=`Line '${S.targetLineId}' not found`;return t(i),{success:!1,error:i}}const v=S.isPercentage!==!1,p=q.distance(g.start,g.end);if(!v&&S.targetPosition===void 0){const i="targetPosition is required when isPercentage is false";return t(i),{success:!1,error:i}}const n=S.targetPosition??1;if(v){if(n<0||n>1){const i=`Invalid targetPosition: ${n} (must be 0-1 for percentage)`;return t(i),{success:!1,error:i}}}else{if(n<0){const i=`Invalid targetPosition: ${n} (must be >= 0 for absolute distance)`;return t(i),{success:!1,error:i}}if(n>p){const i=`Position ${n} exceeds line length ${p}`;return t(i),{success:!1,error:i}}}const P=H({vehicleId:b,...S});return r(i=>{const d=new Map(i),C=d.get(b)||[];return d.set(b,[...C,P]),d}),t(null),{success:!0}},[s,c]),$=o.useCallback(b=>{if(b!==void 0){if(!s.some(L=>L.id===b)){const L=`Vehicle '${b}' not found`;return t(L),{success:!1,error:L}}r(L=>{const g=new Map(L);return g.delete(b),g})}else r(new Map);return t(null),{success:!0}},[s]);return{vehicleQueues:I,queueMovement:w,clearQueue:$,error:m,_loadQueues:f}}function Z({vehicles:s,lines:c,vehicleQueues:I,wheelbase:r,tangentMode:m,curves:t,eventEmitter:f}){const[w,$]=o.useState([]),b=o.useMemo(()=>({wheelbase:r,tangentMode:m}),[r,m]),S=o.useMemo(()=>new Map(c.map(V=>[V.id,V])),[c]),L=o.useRef(new Map);o.useEffect(()=>{const{movingVehicles:V,stateMap:a}=q.initializeAllVehicles(s,S);L.current=a;const E=setTimeout(()=>{$(V)},0);return()=>clearTimeout(E)},[s,S]);const g=o.useRef(null);o.useEffect(()=>{c.length>0&&(g.current=q.buildGraph(c,t,b))},[c,t,b]);const v=o.useRef(0),p=o.useRef(!1),n=o.useCallback(V=>{if(!p.current)return!1;let a=!1;for(const[,h]of L.current)if(h.vehicle.state==="moving"){a=!0;break}if(!a)return!1;const E=[];for(const[h,x]of L.current){if(x.vehicle.state!=="moving"||!x.execution)continue;const e=x.execution;let u;if(e.front.currentSegmentIndex<e.path.segments.length){const y=e.path.segments[e.front.currentSegmentIndex];if(y.type==="line"){const M=S.get(y.lineId);M&&(u=Math.sqrt(Math.pow(M.end.x-M.start.x,2)+Math.pow(M.end.y-M.start.y,2)))}}const l=q.updateAxlePosition(x.vehicle.rear,e.rear,e.path,V,S,e.curveDataMap),k=q.updateAxlePosition(x.vehicle.front,e.front,e.path,V,S,e.curveDataMap,u);if(x.vehicle={...x.vehicle,rear:l.axleState,front:k.axleState},x.execution.rear=l.execution,x.execution.front=k.execution,l.completed){const y={linesMap:S,config:b,vehicleQueues:I,curves:t,graphRef:g,prepareCommandPath:q.prepareCommandPath,onCommandComplete:Q=>E.push({type:"commandComplete",data:Q}),onCommandStart:Q=>E.push({type:"commandStart",data:Q})},M=q.handleArrival(x,y);x.vehicle=M.vehicle,M.newExecution!==void 0&&(x.execution=M.newExecution),M.vehicle.state!=="moving"&&E.push({type:"stateChange",data:{vehicleId:h,from:"moving",to:M.vehicle.state}});const D=M.vehicle.rear.position,T=M.vehicle.front.position;E.push({type:"positionUpdate",data:{vehicleId:h,rear:D,front:T,center:{x:(D.x+T.x)/2,y:(D.y+T.y)/2},angle:Math.atan2(T.y-D.y,T.x-D.x)}})}}if($(h=>h.map(x=>{const e=L.current.get(x.id);return e?e.vehicle:x})),f&&E.length>0){const h=v.current;setTimeout(()=>{v.current===h&&E.forEach(({type:x,data:e})=>{f.emit(x,e)})},0)}for(const[,h]of L.current)if(h.vehicle.state==="moving")return!0;return!1},[S,t,b,I,f]),P=o.useCallback(()=>{if(p.current)return!0;const V=g.current;if(!V)return!1;const a=[];let E=!1;for(const[h,x]of L.current){const e=x.vehicle,u=I.get(h);if(!u||u.length===0)continue;const l=u[0],k={graph:V,linesMap:S,curves:t,config:b},y=q.prepareCommandPath(e,l,k);if(!y){console.warn(`No path found for vehicle ${h}`);continue}const M=q.calculateFrontAxlePosition(y.path,0,0,r);L.current.set(h,{...x,execution:{path:y.path,curveDataMap:y.curveDataMap,currentCommandIndex:0,rear:{currentSegmentIndex:0,segmentDistance:0},front:M?{currentSegmentIndex:M.segmentIndex,segmentDistance:M.segmentDistance}:{currentSegmentIndex:0,segmentDistance:0}},vehicle:{...e,state:"moving"}}),E=!0,e.state!=="moving"&&a.push({id:h,fromState:e.state,command:l,startPosition:{lineId:e.rear.lineId,absoluteOffset:e.rear.absoluteOffset,position:e.rear.position}})}if(!E)return!1;if(p.current=!0,$(h=>h.map(x=>{const e=L.current.get(x.id);return e?e.vehicle:x})),f&&a.length>0){const h=v.current;setTimeout(()=>{v.current===h&&a.forEach(({id:x,fromState:e,command:u,startPosition:l})=>{f.emit("commandStart",{vehicleId:x,command:u,commandIndex:0,startPosition:l}),f.emit("stateChange",{vehicleId:x,from:e,to:"moving"})})},0)}return!0},[S,t,I,b,r,f]),i=o.useCallback(()=>{v.current++,p.current=!1;const{movingVehicles:V,stateMap:a}=q.initializeAllVehicles(s,S);L.current=a,$(V)},[s,S]),d=o.useCallback(V=>{const a=L.current.get(V);if(!a||a.vehicle.state!=="waiting")return!1;const E=I.get(V),h=a.execution;if(!h)return!1;const x=h.currentCommandIndex+1;if(E&&x<E.length){const e=g.current;if(e){const u=E[x],l={graph:e,linesMap:S,curves:t,config:b},k=q.prepareCommandPath(a.vehicle,u,l);if(k){const y=q.calculateFrontAxlePosition(k.path,0,0,r);if(a.execution={path:k.path,curveDataMap:k.curveDataMap,currentCommandIndex:x,rear:{currentSegmentIndex:0,segmentDistance:0},front:y?{currentSegmentIndex:y.segmentIndex,segmentDistance:y.segmentDistance}:{currentSegmentIndex:0,segmentDistance:0}},a.vehicle={...a.vehicle,state:"moving"},$(M=>M.map(D=>D.id===V?a.vehicle:D)),f){const M=v.current;setTimeout(()=>{v.current===M&&f.emit("stateChange",{vehicleId:V,from:"waiting",to:"moving"})},0)}return!0}}}if(a.vehicle={...a.vehicle,state:"idle"},a.execution=null,$(e=>e.map(u=>u.id===V?a.vehicle:u)),f){const e=v.current;setTimeout(()=>{v.current===e&&f.emit("stateChange",{vehicleId:V,from:"waiting",to:"idle"})},0)}return!0},[I,S,t,b,r,f]),C=o.useCallback(()=>{for(const[,V]of L.current)if(V.vehicle.state==="moving")return!0;return!1},[]);return{movingVehicles:w,prepare:P,tick:n,reset:i,continueVehicle:d,isMoving:C,isPrepared:p.current}}function re({wheelbase:s,tangentMode:c="proportional-40",eventEmitter:I}){const r=J(),m=B({lines:r.lines,wheelbase:s}),t=W({vehicles:m.vehicles,lines:r.lines,curves:r.curves}),f=Z({vehicles:m.vehicles,lines:r.lines,vehicleQueues:t.vehicleQueues,wheelbase:s,tangentMode:c,curves:r.curves,eventEmitter:I}),w=o.useCallback(e=>m.vehicles.filter(u=>u.lineId===e||u.rear.lineId===e),[m.vehicles]),$=o.useCallback(e=>w(e).length>0,[w]),b=o.useCallback(e=>{const u=r.addLine(e);return u.success?{success:!0}:{success:!1,error:u.error}},[r]),S=o.useCallback((e,u)=>{const l=r.updateLine(e,u);return l.success?{success:!0}:{success:!1,error:l.error}},[r]),L=o.useCallback(e=>{const u=[],l=w(e);l.length>0&&(u.push({type:"vehicle_on_removed_line",message:`${l.length} vehicle(s) are on line '${e}'`,details:{lineId:e,vehicleIds:l.map(M=>M.id)}}),l.forEach(M=>{m.removeVehicle(M.id),t.clearQueue(M.id)}));const k=r.curves.filter(M=>M.fromLineId===e||M.toLineId===e);k.length>0&&u.push({type:"orphaned_connection",message:`${k.length} connection(s) will be removed`,details:{lineId:e,connectionCount:k.length}});const y=r.removeLine(e);return y.success?{success:!0,warnings:u.length>0?u:void 0}:{success:!1,error:y.error}},[r,w,m,t]),g=o.useCallback(()=>{r.clear(),m.clear(),t.clearQueue()},[r,m,t]),v=o.useCallback((e,u,l)=>{const k=r.addConnection({from:e,to:u,fromPosition:l?.fromOffset,fromIsPercentage:l?.fromIsPercentage,toPosition:l?.toOffset,toIsPercentage:l?.toIsPercentage});return k.success?{success:!0}:{success:!1,error:k.error}},[r]),p=o.useCallback((e,u,l)=>{const k=r.updateConnection(e,u,l);return k.success?{success:!0}:{success:!1,error:k.error}},[r]),n=o.useCallback((e,u)=>{const l=r.removeConnection(e,u);return l.success?{success:!0}:{success:!1,error:l.error}},[r]),P=o.useCallback(e=>{const u=m.addVehicles(e);return u.success?{success:!0}:{success:!1,error:u.errors?.join("; ")}},[m]),i=o.useCallback((e,u)=>{const l=m.updateVehicle(e,u);return l.success?{success:!0}:{success:!1,error:l.error}},[m]),d=o.useCallback(e=>{const u=[],l=t.vehicleQueues.get(e);l&&l.length>0&&(u.push({type:"movement_queue_cleared",message:`${l.length} queued movement(s) will be cleared for vehicle '${e}'`,details:{vehicleId:e}}),t.clearQueue(e));const k=m.removeVehicle(e);return k.success?{success:!0,warnings:u.length>0?u:void 0}:{success:!1,error:k.error}},[m,t]),C=o.useCallback(()=>{m.clear(),t.clearQueue()},[m,t]),V=o.useCallback(e=>{const u={targetLineId:e.lineId,targetPosition:e.position??1,isPercentage:e.isPercentage,wait:e.wait,payload:e.payload},l=t.queueMovement(e.id,u);return l.success?{success:!0}:{success:!1,error:l.error}},[t]),a=o.useCallback(e=>{const u=t.clearQueue(e);return u.success?{success:!0}:{success:!1,error:u.error}},[t]),E=o.useCallback(e=>{const u=[],l=[],{scene:k,vehicles:y,movements:M}=O.parseAllDSL(e);k.errors.length>0&&l.push(...k.errors),y.errors.length>0&&l.push(...y.errors),M.errors.length>0&&l.push(...M.errors);const D=k.data.lines.map(z),T=(k.data.connections||[]).map(j),Q=y.data.map(N),{vehicles:A,errors:_}=O.validateAndCreateVehicles(Q,D,s);_.length>0&&l.push(..._);const U=new Map;for(const G of M.data){const Y=U.get(G.vehicleId)||[];Y.push(H(G)),U.set(G.vehicleId,Y)}return r._loadScene(D,T),m._loadVehicles(A),t._loadQueues(U),l.length>0&&u.push({type:"dsl_parse_error",message:`DSL loading had ${l.length} error(s)`,details:{errors:l}}),{success:!0,warnings:u.length>0?u:void 0}},[r,m,t,s]),h=o.useCallback(e=>{const u=[],l=[],k=e.lines.map(z),y=(e.connections||[]).map(j),M=(e.vehicles||[]).map(N),{vehicles:D,errors:T}=O.validateAndCreateVehicles(M,k,s);T.length>0&&l.push(...T);const Q=new Map;for(const A of e.movements||[]){const _=Q.get(A.vehicleId)||[];_.push(H({vehicleId:A.vehicleId,targetLineId:A.targetLineId,targetPosition:A.targetPosition,isPercentage:A.isPercentage,wait:A.wait,payload:A.payload})),Q.set(A.vehicleId,_)}return r._loadScene(k,y),m._loadVehicles(D),t._loadQueues(Q),l.length>0&&u.push({type:"dsl_parse_error",message:`JSON loading had ${l.length} error(s)`,details:{errors:l}}),{success:!0,warnings:u.length>0?u:void 0}},[r,m,t,s]),x=o.useMemo(()=>r.error||m.error||t.error,[r.error,m.error,t.error]);return{lines:r.lines,curves:r.curves,vehicles:m.vehicles,movingVehicles:f.movingVehicles,vehicleQueues:t.vehicleQueues,error:x,addLine:b,updateLine:S,removeLine:L,clearScene:g,connect:v,updateConnection:p,disconnect:n,addVehicles:P,updateVehicle:i,removeVehicle:d,clearVehicles:C,goto:V,clearQueue:a,prepare:f.prepare,tick:f.tick,reset:f.reset,continueVehicle:f.continueVehicle,isMoving:f.isMoving,loadFromDSL:E,loadFromJSON:h,getVehiclesOnLine:w,hasVehiclesOnLine:$}}function R(s){return s.map(c=>({id:c.id,start:c.start,end:c.end}))}function K(s){return s.map(c=>({from:c.fromLineId,to:c.toLineId,fromPosition:c.fromOffset!==void 0?c.fromIsPercentage?c.fromOffset/100:c.fromOffset:void 0,fromIsPercentage:c.fromIsPercentage,toPosition:c.toOffset!==void 0?c.toIsPercentage?c.toOffset/100:c.toOffset:void 0,toIsPercentage:c.toIsPercentage}))}function ne(){const[s,c]=o.useState(""),[I,r]=o.useState(null),{lines:m,curves:t,setScene:f}=J(),w=o.useRef(!1),$=o.useRef("");o.useEffect(()=>{$.current=s},[s]),o.useEffect(()=>{if(w.current)return;const g={lines:R(m),connections:t.length>0?K(t):void 0},v=O.generateSceneDSL(g);v!==$.current&&(w.current=!0,c(v),setTimeout(()=>{w.current=!1},50))},[m,t]);const b=o.useCallback(g=>{w.current=!0,c(g);try{const{data:v,errors:p}=O.parseSceneDSL(g);p.length>0&&r(p.join("; "));const n=f(v);!n.success&&n.errors?r(P=>P?`${P}; ${n.errors.join("; ")}`:n.errors.join("; ")):p.length===0&&r(null)}catch(v){r(v instanceof Error?v.message:"Invalid scene definition")}setTimeout(()=>{w.current=!1},50)},[f]),S=o.useCallback(g=>{const v=typeof g=="function"?g(m):g,p=K(t);f({lines:R(v),connections:p.length>0?p:void 0});const n={lines:R(v),connections:p.length>0?p:void 0},P=O.generateSceneDSL(n);w.current=!0,c(P),setTimeout(()=>{w.current=!1},50)},[m,t,f]),L=o.useCallback(g=>{const v=typeof g=="function"?g(t):g,p=K(v);f({lines:R(m),connections:p.length>0?p:void 0});const n={lines:R(m),connections:p.length>0?p:void 0},P=O.generateSceneDSL(n);w.current=!0,c(P),setTimeout(()=>{w.current=!1},50)},[m,t,f]);return{lines:m,curves:t,sceneDefinitionText:s,sceneError:I,isDebouncing:!1,debounceKey:0,setLines:S,setCurves:L,setSceneDefinitionText:b}}function oe({lines:s,wheelbase:c}){const[I,r]=o.useState(""),[m,t]=o.useState(null),{vehicles:f,addVehicles:w,clear:$,error:b}=B({lines:s,wheelbase:c}),S=o.useRef(!1),L=o.useCallback(g=>{S.current=!0,r(g);try{const{data:v,errors:p}=O.parseVehiclesDSL(g),n=[...p];$();for(const P of v){const i=w(P);!i.success&&i.errors&&n.push(...i.errors)}n.length>0?t(n.join(`
2
+ `)):t(null)}catch(v){t(v instanceof Error?v.message:"Invalid initial movement")}setTimeout(()=>{S.current=!1},50)},[w,$]);return{vehicles:f,initialMovementText:I,movementError:m||b,isDebouncing:!1,debounceKey:0,setInitialMovementText:L}}function ce({lines:s,vehicles:c}){const[I,r]=o.useState(""),[m,t]=o.useState([]),[f,w]=o.useState(null),{vehicleQueues:$,queueMovement:b,clearQueue:S,error:L}=W({vehicles:c,lines:s}),g=o.useRef(!1),v=o.useCallback(p=>{g.current=!0,r(p);try{const{data:n,errors:P}=O.parseMovementDSL(p),i=[...P];S();for(const d of n){const C=b(d.vehicleId,{targetLineId:d.targetLineId,targetPosition:d.targetPosition,isPercentage:d.isPercentage,wait:d.wait,payload:d.payload});!C.success&&C.error&&i.push(C.error)}t(n),i.length>0?w(i.join(`
3
+ `)):w(null)}catch(n){w(n instanceof Error?n.message:"Invalid movement sequence"),t([])}setTimeout(()=>{g.current=!1},50)},[b,S]);return{movementSequenceText:I,gotoCommands:m,vehicleQueues:$,sequenceError:f||L,isDebouncing:!1,debounceKey:0,setMovementSequenceText:v}}const X=o.createContext(null);function ee(){const s=o.useContext(X);if(!s)throw new Error("useVehicleEventEmitter must be used within a VehicleEventProvider");return s}function ie(){return o.useMemo(()=>new O.VehicleEventEmitter,[])}function ae(s,c,I=[]){const r=ee();o.useEffect(()=>r.on(s,c),[r,s,...I])}function ue({children:s}){const c=o.useMemo(()=>new O.VehicleEventEmitter,[]);return te.jsx(X.Provider,{value:c,children:s})}exports.VehicleEventContext=X;exports.VehicleEventProvider=ue;exports.useAnimation=Z;exports.useCreateVehicleEventEmitter=ie;exports.useInitialMovement=oe;exports.useMovementQueue=W;exports.useMovementSequence=ce;exports.useScene=J;exports.useSceneDefinition=ne;exports.useVehicleEvent=ae;exports.useVehicleEventEmitter=ee;exports.useVehicleSimulation=re;exports.useVehicles=B;
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Animation Loop Utility
3
+ *
4
+ * A simple utility for creating animation loops with start/pause/stop controls.
5
+ * Uses requestAnimationFrame for smooth animations.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { createAnimationLoop } from 'vehicle-path/utils'
10
+ *
11
+ * const { start, pause, stop, isRunning } = createAnimationLoop({
12
+ * onTick: (deltaTime) => {
13
+ * // Update animation state
14
+ * tick(velocity)
15
+ * },
16
+ * onComplete: () => {
17
+ * console.log('Animation complete!')
18
+ * }
19
+ * })
20
+ *
21
+ * // Start the animation
22
+ * start()
23
+ *
24
+ * // Pause (can resume with start())
25
+ * pause()
26
+ *
27
+ * // Stop completely (resets state)
28
+ * stop()
29
+ * ```
30
+ */
31
+ export interface AnimationLoopOptions {
32
+ /**
33
+ * Called on each animation frame
34
+ * @param deltaTime - Time elapsed since last frame in milliseconds
35
+ * @returns true to continue, false to complete the animation
36
+ */
37
+ onTick: (deltaTime: number) => boolean | void;
38
+ /**
39
+ * Called when the animation completes (onTick returns false)
40
+ */
41
+ onComplete?: () => void;
42
+ /**
43
+ * Called when the animation starts
44
+ */
45
+ onStart?: () => void;
46
+ /**
47
+ * Called when the animation pauses
48
+ */
49
+ onPause?: () => void;
50
+ /**
51
+ * Called when the animation stops
52
+ */
53
+ onStop?: () => void;
54
+ }
55
+ export interface AnimationLoopControls {
56
+ /**
57
+ * Start or resume the animation
58
+ */
59
+ start: () => void;
60
+ /**
61
+ * Pause the animation (can be resumed with start())
62
+ */
63
+ pause: () => void;
64
+ /**
65
+ * Stop the animation completely
66
+ */
67
+ stop: () => void;
68
+ /**
69
+ * Check if the animation is currently running
70
+ */
71
+ isRunning: () => boolean;
72
+ /**
73
+ * Check if the animation is paused
74
+ */
75
+ isPaused: () => boolean;
76
+ }
77
+ /**
78
+ * Create an animation loop with start/pause/stop controls
79
+ *
80
+ * @param options - Animation loop configuration
81
+ * @returns Control functions for the animation loop
82
+ */
83
+ export declare function createAnimationLoop(options: AnimationLoopOptions): AnimationLoopControls;
84
+ /**
85
+ * React hook for using animation loop with automatic cleanup
86
+ *
87
+ * @example
88
+ * ```typescript
89
+ * import { useAnimationLoop } from 'vehicle-path/utils'
90
+ *
91
+ * function MyComponent() {
92
+ * const { start, pause, stop, isRunning } = useAnimationLoop({
93
+ * onTick: (deltaTime) => tick(velocity),
94
+ * onComplete: () => setFinished(true)
95
+ * })
96
+ *
97
+ * return (
98
+ * <button onClick={isRunning() ? pause : start}>
99
+ * {isRunning() ? 'Pause' : 'Start'}
100
+ * </button>
101
+ * )
102
+ * }
103
+ * ```
104
+ */
105
+ export declare function useAnimationLoop(options: AnimationLoopOptions): AnimationLoopControls;
@@ -0,0 +1,152 @@
1
+ /**
2
+ * DSL Parser Utilities
3
+ *
4
+ * These utilities convert DSL text into API-compatible types that can be used
5
+ * with the programmatic hooks (useScene, useVehicles, useMovement).
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { parseSceneDSL, parseVehiclesDSL, parseMovementDSL } from 'vehicle-path/utils'
10
+ *
11
+ * const sceneConfig = parseSceneDSL(`
12
+ * line001 : (100, 100) -> (700, 100)
13
+ * line002 : (700, 100) -> (700, 400)
14
+ * line001 -> line002
15
+ * `)
16
+ * setScene(sceneConfig)
17
+ *
18
+ * const vehicles = parseVehiclesDSL(`
19
+ * v1 start line001 0%
20
+ * v2 start line002 50%
21
+ * `)
22
+ * vehicles.forEach(v => addVehicle(v))
23
+ *
24
+ * const movements = parseMovementDSL(`
25
+ * v1 goto line002 100% --wait
26
+ * `)
27
+ * movements.forEach(m => queueMovement(m.vehicleId, m))
28
+ * ```
29
+ */
30
+ import type { SceneConfig, VehicleInput, GotoCommandInput } from '../core/types/api';
31
+ /**
32
+ * Parse result with errors for validation feedback
33
+ */
34
+ export interface ParseResult<T> {
35
+ data: T;
36
+ errors: string[];
37
+ }
38
+ /**
39
+ * Movement command parsed from DSL (includes vehicleId for routing)
40
+ */
41
+ export interface MovementCommand extends GotoCommandInput {
42
+ vehicleId: string;
43
+ }
44
+ /**
45
+ * Parse scene DSL into SceneConfig for useScene.setScene()
46
+ *
47
+ * DSL Format:
48
+ * ```
49
+ * # Lines
50
+ * line001 : (100, 100) -> (700, 100)
51
+ * line002 : (700, 100) -> (700, 400)
52
+ *
53
+ * # Connections (curves)
54
+ * line001 -> line002
55
+ * line001 80% -> line002 20%
56
+ * ```
57
+ *
58
+ * @param text - DSL text to parse
59
+ * @returns ParseResult containing SceneConfig and any parsing errors
60
+ */
61
+ export declare function parseSceneDSL(text: string): ParseResult<SceneConfig>;
62
+ /**
63
+ * Parse vehicle DSL into VehicleInput[] for useVehicles.addVehicles()
64
+ *
65
+ * DSL Format:
66
+ * ```
67
+ * v1 start line001 0%
68
+ * v2 start line002 50%
69
+ * v3 start line001 100 # absolute offset
70
+ * ```
71
+ *
72
+ * @param text - DSL text to parse
73
+ * @returns ParseResult containing VehicleInput[] and any parsing errors
74
+ */
75
+ export declare function parseVehiclesDSL(text: string): ParseResult<VehicleInput[]>;
76
+ /**
77
+ * Parse movement DSL into MovementCommand[] for useMovement.queueMovement()
78
+ *
79
+ * DSL Format:
80
+ * ```
81
+ * v1 goto line001 100%
82
+ * v1 goto line002 50% --wait
83
+ * v2 goto line001 0% --payload {"orderId": "123"}
84
+ * v1 goto line003 100% --wait --payload {"message": "hello"}
85
+ * ```
86
+ *
87
+ * @param text - DSL text to parse
88
+ * @returns ParseResult containing MovementCommand[] and any parsing errors
89
+ */
90
+ export declare function parseMovementDSL(text: string): ParseResult<MovementCommand[]>;
91
+ /**
92
+ * Parse all DSL types from a single text block
93
+ *
94
+ * This is useful when you have a combined DSL that includes scene, vehicles, and movements.
95
+ *
96
+ * @param text - Combined DSL text to parse
97
+ * @returns Object containing parsed scene, vehicles, and movements with errors
98
+ */
99
+ export declare function parseAllDSL(text: string): {
100
+ scene: ParseResult<SceneConfig>;
101
+ vehicles: ParseResult<VehicleInput[]>;
102
+ movements: ParseResult<MovementCommand[]>;
103
+ };
104
+ /**
105
+ * Generate scene DSL from SceneConfig
106
+ *
107
+ * @param config - SceneConfig from programmatic API
108
+ * @returns DSL text representation
109
+ *
110
+ * @example
111
+ * ```typescript
112
+ * const dsl = generateSceneDSL({
113
+ * lines: [{ id: 'line001', start: [100, 100], end: [500, 100] }],
114
+ * connections: [{ from: 'line001', to: 'line002' }]
115
+ * })
116
+ * // Returns:
117
+ * // line001 : (100, 100) -> (500, 100)
118
+ * //
119
+ * // line001 -> line002
120
+ * ```
121
+ */
122
+ export declare function generateSceneDSL(config: SceneConfig): string;
123
+ /**
124
+ * Generate vehicles DSL from VehicleInput[]
125
+ *
126
+ * @param vehicles - Array of VehicleInput from programmatic API
127
+ * @returns DSL text representation
128
+ *
129
+ * @example
130
+ * ```typescript
131
+ * const dsl = generateVehiclesDSL([
132
+ * { id: 'v1', lineId: 'line001', position: 0.5, isPercentage: true }
133
+ * ])
134
+ * // Returns: "v1 start line001 50%"
135
+ * ```
136
+ */
137
+ export declare function generateVehiclesDSL(vehicles: VehicleInput[]): string;
138
+ /**
139
+ * Generate movement DSL from MovementCommand[]
140
+ *
141
+ * @param commands - Array of MovementCommand from programmatic API
142
+ * @returns DSL text representation
143
+ *
144
+ * @example
145
+ * ```typescript
146
+ * const dsl = generateMovementDSL([
147
+ * { vehicleId: 'v1', targetLineId: 'line002', targetPosition: 1.0, wait: true }
148
+ * ])
149
+ * // Returns: "v1 goto line002 100% --wait"
150
+ * ```
151
+ */
152
+ export declare function generateMovementDSL(commands: MovementCommand[]): string;
@@ -0,0 +1,94 @@
1
+ import type { GotoCompletionInfo, GotoCommand, VehicleState } from '../core/types/vehicle';
2
+ import type { Point } from '../core/types/geometry';
3
+ /**
4
+ * Info when a command starts execution
5
+ */
6
+ export interface CommandStartInfo {
7
+ vehicleId: string;
8
+ command: GotoCommand;
9
+ commandIndex: number;
10
+ startPosition: {
11
+ lineId: string;
12
+ absoluteOffset: number;
13
+ position: Point;
14
+ };
15
+ }
16
+ /**
17
+ * Position update data for a vehicle
18
+ */
19
+ export interface VehiclePositionUpdate {
20
+ vehicleId: string;
21
+ /** Rear axle position */
22
+ rear: Point;
23
+ /** Front axle position */
24
+ front: Point;
25
+ /** Center point between rear and front axles */
26
+ center: Point;
27
+ /** Angle in radians from rear to front (heading direction) */
28
+ angle: number;
29
+ }
30
+ /**
31
+ * Event map for vehicle-related events
32
+ */
33
+ export interface VehicleEventMap {
34
+ /** Fired when a goto command starts execution */
35
+ commandStart: CommandStartInfo;
36
+ /** Fired when a goto command completes (vehicle arrives at destination) */
37
+ commandComplete: GotoCompletionInfo;
38
+ /** Fired when vehicle state changes */
39
+ stateChange: {
40
+ vehicleId: string;
41
+ from: VehicleState;
42
+ to: VehicleState;
43
+ };
44
+ /** Fired on each frame when vehicle position updates */
45
+ positionUpdate: VehiclePositionUpdate;
46
+ }
47
+ export type VehicleEventType = keyof VehicleEventMap;
48
+ export type VehicleEventCallback<K extends VehicleEventType> = (data: VehicleEventMap[K]) => void;
49
+ export type Unsubscribe = () => void;
50
+ /**
51
+ * Event emitter for vehicle movement events.
52
+ * Allows multiple subscribers to listen for events like command completion,
53
+ * state changes, etc.
54
+ *
55
+ * @example
56
+ * ```typescript
57
+ * const emitter = new VehicleEventEmitter()
58
+ *
59
+ * // Subscribe to events
60
+ * const unsubscribe = emitter.on('commandComplete', (info) => {
61
+ * console.log(`Vehicle ${info.vehicleId} arrived with payload:`, info.payload)
62
+ * })
63
+ *
64
+ * // Later, unsubscribe
65
+ * unsubscribe()
66
+ * ```
67
+ */
68
+ export declare class VehicleEventEmitter {
69
+ private listeners;
70
+ /**
71
+ * Subscribe to an event
72
+ * @param event - The event type to listen for
73
+ * @param callback - Function to call when event is emitted
74
+ * @returns Unsubscribe function
75
+ */
76
+ on<K extends VehicleEventType>(event: K, callback: VehicleEventCallback<K>): Unsubscribe;
77
+ /**
78
+ * Emit an event to all subscribers
79
+ * @param event - The event type to emit
80
+ * @param data - The event data
81
+ */
82
+ emit<K extends VehicleEventType>(event: K, data: VehicleEventMap[K]): void;
83
+ /**
84
+ * Remove all listeners for a specific event, or all events if no event specified
85
+ * @param event - Optional event type to clear listeners for
86
+ */
87
+ off(event?: VehicleEventType): void;
88
+ /**
89
+ * Get the number of listeners for a specific event
90
+ * @param event - The event type
91
+ * @returns Number of listeners
92
+ */
93
+ listenerCount(event: VehicleEventType): number;
94
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Utils Layer - Optional utilities
3
+ *
4
+ * This layer contains utilities that are optional and can be replaced with
5
+ * your own implementations. Includes DSL parser, event emitter, animation loop, etc.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { parseSceneDSL, VehicleEventEmitter, createAnimationLoop } from 'vehicle-path/utils'
10
+ * ```
11
+ */
12
+ export { VehicleEventEmitter, type VehicleEventMap, type VehicleEventType, type VehicleEventCallback, type VehiclePositionUpdate, type Unsubscribe, type CommandStartInfo } from './event-emitter';
13
+ export { parseSceneDSL, parseVehiclesDSL, parseMovementDSL, parseAllDSL, generateSceneDSL, generateVehiclesDSL, generateMovementDSL, type ParseResult, type MovementCommand } from './dsl-parser';
14
+ export { createAnimationLoop, useAnimationLoop, type AnimationLoopOptions, type AnimationLoopControls } from './animation-loop';
15
+ export { validateAndCreateVehicles, getNextStartVehicleId, getNextGotoVehicleId } from './vehicle-helpers';
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Type Converters
3
+ *
4
+ * Convert API types to internal types. Used by hooks and loadFromDSL.
5
+ */
6
+ import type { SceneLineInput, SceneConnectionInput, CoordinateInput, VehicleInput } from '../core/types/api';
7
+ import type { Line, Curve } from '../core/types/geometry';
8
+ import type { VehicleStart, GotoCommand } from '../core/types/vehicle';
9
+ /**
10
+ * Convert coordinate input to Point
11
+ */
12
+ export declare function toPoint(coord: CoordinateInput): {
13
+ x: number;
14
+ y: number;
15
+ };
16
+ /**
17
+ * Convert SceneLineInput to internal Line type
18
+ */
19
+ export declare function toLine(input: SceneLineInput): Line;
20
+ /**
21
+ * Convert SceneConnectionInput to internal Curve type
22
+ */
23
+ export declare function toCurve(input: SceneConnectionInput): Curve;
24
+ /**
25
+ * Convert VehicleInput to internal VehicleStart format
26
+ */
27
+ export declare function toVehicleStart(input: VehicleInput): VehicleStart;
28
+ /**
29
+ * Convert GotoCommand input (with optional fields) to internal GotoCommand format (all required)
30
+ */
31
+ export declare function toGotoCommand(cmd: {
32
+ vehicleId: string;
33
+ targetLineId: string;
34
+ targetPosition?: number;
35
+ isPercentage?: boolean;
36
+ wait?: boolean;
37
+ payload?: unknown;
38
+ }): GotoCommand;
@@ -0,0 +1,8 @@
1
+ import type { Line } from '../core/types/geometry';
2
+ import type { Vehicle, VehicleStart, GotoCommand } from '../core/types/vehicle';
3
+ export declare function validateAndCreateVehicles(vehicleStarts: VehicleStart[], lines: Line[], wheelbase?: number): {
4
+ vehicles: Vehicle[];
5
+ errors: string[];
6
+ };
7
+ export declare function getNextStartVehicleId(existingVehicles: VehicleStart[]): string;
8
+ export declare function getNextGotoVehicleId(existingCommands: GotoCommand[], vehicles: Vehicle[]): string | null;
package/dist/utils.cjs ADDED
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./vehicle-helpers-DIcksrtO.cjs"),t=require("./animation-loop-fC2LjxCd.cjs");exports.VehicleEventEmitter=e.VehicleEventEmitter;exports.generateMovementDSL=e.generateMovementDSL;exports.generateSceneDSL=e.generateSceneDSL;exports.generateVehiclesDSL=e.generateVehiclesDSL;exports.getNextGotoVehicleId=e.getNextGotoVehicleId;exports.getNextStartVehicleId=e.getNextStartVehicleId;exports.parseAllDSL=e.parseAllDSL;exports.parseMovementDSL=e.parseMovementDSL;exports.parseSceneDSL=e.parseSceneDSL;exports.parseVehiclesDSL=e.parseVehiclesDSL;exports.validateAndCreateVehicles=e.validateAndCreateVehicles;exports.createAnimationLoop=t.createAnimationLoop;exports.useAnimationLoop=t.useAnimationLoop;
package/dist/utils.js ADDED
@@ -0,0 +1,17 @@
1
+ import { V as t, g as s, a as r, b as o, c as n, d as i, p as c, e as S, f as l, h as p, v as L } from "./vehicle-helpers-_72KxCqO.js";
2
+ import { c as m, u as D } from "./animation-loop-bZEm2pMN.js";
3
+ export {
4
+ t as VehicleEventEmitter,
5
+ m as createAnimationLoop,
6
+ s as generateMovementDSL,
7
+ r as generateSceneDSL,
8
+ o as generateVehiclesDSL,
9
+ n as getNextGotoVehicleId,
10
+ i as getNextStartVehicleId,
11
+ c as parseAllDSL,
12
+ S as parseMovementDSL,
13
+ l as parseSceneDSL,
14
+ p as parseVehiclesDSL,
15
+ D as useAnimationLoop,
16
+ L as validateAndCreateVehicles
17
+ };
@@ -0,0 +1,7 @@
1
+ "use strict";const m=require("./core.cjs");class P{listeners=new Map;on(e,t){return this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e).add(t),()=>{this.listeners.get(e)?.delete(t)}}emit(e,t){this.listeners.get(e)?.forEach(s=>{try{s(t)}catch(o){console.error(`Error in event listener for "${e}":`,o)}})}off(e){e?this.listeners.delete(e):this.listeners.clear()}listenerCount(e){return this.listeners.get(e)?.size??0}}function g(i){const e=[],t=[],s=[],o=i.trim().split(`
2
+ `);let h=0;for(const n of o){h++;const r=n.trim();if(!r||r.startsWith("#"))continue;const a=r.match(/^(\w+)\s*:\s*\((-?\d+(?:\.\d+)?),\s*(-?\d+(?:\.\d+)?)\)\s*->\s*\((-?\d+(?:\.\d+)?),\s*(-?\d+(?:\.\d+)?)\)/);if(a){e.push({id:a[1],start:[parseFloat(a[2]),parseFloat(a[3])],end:[parseFloat(a[4]),parseFloat(a[5])]});continue}const c=r.match(/^(\w+)(?:\s+(\d+(?:\.\d+)?)(%?))??\s*->\s*(\w+)(?:\s+(\d+(?:\.\d+)?)(%?))?/);if(c){const l={from:c[1],to:c[4]};if(c[2]){const f=parseFloat(c[2]);l.fromPosition=(c[3]==="%",f/100)}if(c[5]){const f=parseFloat(c[5]);l.toPosition=(c[6]==="%",f/100)}t.push(l);continue}r.match(/^\w+\s+start\s+/)||r.match(/^\w+\s+goto\s+/)||s.push(`Line ${h}: Unable to parse "${r}"`)}return{data:{lines:e,connections:t.length>0?t:void 0},errors:s}}function $(i){const e=[],t=[],s=i.trim().split(`
3
+ `);let o=0;for(const h of s){o++;const n=h.trim();if(!n||n.startsWith("#"))continue;const r=n.match(/^(\w+)\s+start\s+(\w+)\s+(\d+(?:\.\d+)?)(%?)/);if(r){const a=parseFloat(r[3]),c=r[4]==="%";e.push({id:r[1],lineId:r[2],position:c?a/100:a,isPercentage:c});continue}n.match(/^\w+\s*:\s*\(/)||n.match(/^\w+.*->\s*\w+/)||n.match(/^\w+\s+goto\s+/)||n.match(/^\w+\s+start/)&&t.push(`Line ${o}: Invalid vehicle start format "${n}"`)}return{data:e,errors:t}}function I(i){const e=[],t=[],s=i.trim().split(`
4
+ `);let o=0;for(const h of s){o++;const n=h.trim();if(!n||n.startsWith("#"))continue;const r=n.match(/^(\w+)\s+goto\s+(\w+)\s+(\d+(?:\.\d+)?)(%?)/);if(r){const a=n.slice(r[0].length),c=a.includes("--wait");let l;const f=a.match(/--payload\s+(\{.*\})/);if(f)try{l=JSON.parse(f[1])}catch{t.push(`Line ${o}: Invalid JSON payload "${f[1]}"`)}const d=parseFloat(r[3]),u=r[4]==="%";e.push({vehicleId:r[1],targetLineId:r[2],targetPosition:u?d/100:d,isPercentage:u,wait:c||void 0,payload:l});continue}n.match(/^\w+\s*:\s*\(/)||n.match(/^\w+.*->\s*\w+/)||n.match(/^\w+\s+start\s+/)||n.match(/^\w+\s+goto/)&&t.push(`Line ${o}: Invalid goto command format "${n}"`)}return{data:e,errors:t}}function L(i){return{scene:g(i),vehicles:$(i),movements:I(i)}}function p(i){return Array.isArray(i)?{x:i[0],y:i[1]}:i}function w(i){const e=[];for(const t of i.lines){const s=p(t.start),o=p(t.end);e.push(`${t.id} : (${Math.round(s.x)}, ${Math.round(s.y)}) -> (${Math.round(o.x)}, ${Math.round(o.y)})`)}if(i.lines.length>0&&i.connections&&i.connections.length>0&&e.push(""),i.connections)for(const t of i.connections){let s=t.from;t.fromPosition!==void 0&&(t.fromIsPercentage!==!1?s+=` ${t.fromPosition*100}%`:s+=` ${t.fromPosition}`),s+=" -> ",s+=t.to,t.toPosition!==void 0&&(t.toIsPercentage!==!1?s+=` ${t.toPosition*100}%`:s+=` ${t.toPosition}`),e.push(s)}return e.join(`
5
+ `)}function S(i){return i.map(e=>{const t=e.position??0;return e.isPercentage!==!1?`${e.id} start ${e.lineId} ${t*100}%`:`${e.id} start ${e.lineId} ${t}`}).join(`
6
+ `)}function M(i){return i.map(e=>{const t=e.targetPosition??1,s=e.isPercentage!==!1;let o=e.vehicleId;return o+=` goto ${e.targetLineId}`,s?o+=` ${t*100}%`:o+=` ${t}`,e.wait&&(o+=" --wait"),e.payload!==void 0&&(o+=` --payload ${JSON.stringify(e.payload)}`),o}).join(`
7
+ `)}function V(i,e,t=0){const s=[],o=[],h=new Set;for(const n of i){if(h.has(n.vehicleId)){o.push(`Duplicate vehicle ID: ${n.vehicleId}`);continue}h.add(n.vehicleId);const r=e.find(v=>v.id===n.lineId);if(!r){o.push(`Vehicle ${n.vehicleId}: Line "${n.lineId}" not found`);continue}const a=m.distance(r.start,r.end),c=Math.max(0,a-t);let l;if(n.isPercentage){if(n.offset<0||n.offset>100){o.push(`Vehicle ${n.vehicleId}: Offset ${n.offset}% must be between 0% and 100%`);continue}l=n.offset/100*c}else{if(n.offset<0||n.offset>a){o.push(`Vehicle ${n.vehicleId}: Offset ${n.offset} exceeds line length ${a.toFixed(2)}`);continue}l=Math.min(n.offset,c)}const f=m.getPointOnLineByOffset(r,l,!1),d={lineId:n.lineId,position:f,absoluteOffset:l},u=m.calculateInitialFrontPosition(n.lineId,l,t,r);s.push({id:n.vehicleId,lineId:n.lineId,offset:n.offset,isPercentage:n.isPercentage,state:"idle",rear:d,front:u})}return{vehicles:s,errors:o}}function y(i){const e=i.map(s=>{const o=s.vehicleId.match(/^v(\d+)$/);return o?parseInt(o[1]):0}).filter(s=>s>0);return`v${(e.length>0?Math.max(...e):0)+1}`}function D(i,e){if(e.length===0)return null;if(i.length===0)return e[0].id;const t=new Map;for(const s of e)t.set(s.id,0);for(const s of i){const o=t.get(s.vehicleId)||0;t.set(s.vehicleId,o+1)}return e[0].id}exports.VehicleEventEmitter=P;exports.generateMovementDSL=M;exports.generateSceneDSL=w;exports.generateVehiclesDSL=S;exports.getNextGotoVehicleId=D;exports.getNextStartVehicleId=y;exports.parseAllDSL=L;exports.parseMovementDSL=I;exports.parseSceneDSL=g;exports.parseVehiclesDSL=$;exports.validateAndCreateVehicles=V;