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.
- package/README.md +160 -0
- package/dist/animation-loop-bZEm2pMN.js +37 -0
- package/dist/animation-loop-fC2LjxCd.cjs +1 -0
- package/dist/core/algorithms/math.d.ts +35 -0
- package/dist/core/algorithms/pathFinding.d.ts +62 -0
- package/dist/core/algorithms/vehicleMovement.d.ts +174 -0
- package/dist/core/index.d.ts +20 -0
- package/dist/core/types/api.d.ts +185 -0
- package/dist/core/types/config.d.ts +9 -0
- package/dist/core/types/geometry.d.ts +26 -0
- package/dist/core/types/movement.d.ts +56 -0
- package/dist/core/types/vehicle.d.ts +65 -0
- package/dist/core.cjs +1 -0
- package/dist/core.js +551 -0
- package/dist/index.d.ts +45 -0
- package/dist/react/dsl-hooks/useInitialMovement.d.ts +24 -0
- package/dist/react/dsl-hooks/useMovementSequence.d.ts +27 -0
- package/dist/react/dsl-hooks/useSceneDefinition.d.ts +22 -0
- package/dist/react/hooks/useAnimation.d.ts +43 -0
- package/dist/react/hooks/useMovementQueue.d.ts +52 -0
- package/dist/react/hooks/useScene.d.ts +78 -0
- package/dist/react/hooks/useVehicleSimulation.d.ts +126 -0
- package/dist/react/hooks/useVehicles.d.ts +55 -0
- package/dist/react/index.d.ts +48 -0
- package/dist/react/providers/useVehicleEvents.d.ts +78 -0
- package/dist/react.cjs +1 -0
- package/dist/react.js +18 -0
- package/dist/useVehicleEvents-B2JQFNjc.js +923 -0
- package/dist/useVehicleEvents-CBymulau.cjs +3 -0
- package/dist/utils/animation-loop.d.ts +105 -0
- package/dist/utils/dsl-parser.d.ts +152 -0
- package/dist/utils/event-emitter.d.ts +94 -0
- package/dist/utils/index.d.ts +15 -0
- package/dist/utils/type-converters.d.ts +38 -0
- package/dist/utils/vehicle-helpers.d.ts +8 -0
- package/dist/utils.cjs +1 -0
- package/dist/utils.js +17 -0
- package/dist/vehicle-helpers-DIcksrtO.cjs +7 -0
- package/dist/vehicle-helpers-_72KxCqO.js +276 -0
- package/dist/vehicle-path.cjs +1 -0
- package/dist/vehicle-path.js +62 -0
- 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;
|