import { describe, expect, it } from "vitest"; import { createScenePath, getScenePathLength, mapWorldPointToScenePathProgressBetweenPoints, resolveNearestPointOnResolvedScenePath, resolveScenePath, sampleResolvedScenePathPosition, sampleResolvedScenePathTangent, sampleScenePathPosition, sampleScenePathTangent } from "../../src/document/paths"; describe("scene paths", () => { it("resolves linear segments and samples position and tangent by progress", () => { const path = createScenePath({ id: "path-open", points: [ { id: "point-a", position: { x: 0, y: 0, z: 0 } }, { id: "point-b", position: { x: 0, y: 0, z: 3 } }, { id: "point-c", position: { x: 4, y: 0, z: 3 } } ] }); const resolvedPath = resolveScenePath(path); expect(getScenePathLength(path)).toBe(7); expect(resolvedPath.totalLength).toBe(7); expect(resolvedPath.segments).toHaveLength(2); expect(sampleScenePathPosition(path, 0)).toEqual({ x: 0, y: 0, z: 0 }); expect(sampleScenePathTangent(path, 0)).toEqual({ x: 0, y: 0, z: 1 }); expect(sampleResolvedScenePathPosition(resolvedPath, 0.5)).toEqual({ x: 0.5, y: 0, z: 3 }); expect(sampleResolvedScenePathTangent(resolvedPath, 0.5)).toEqual({ x: 1, y: 0, z: 0 }); expect(sampleScenePathPosition(path, 1)).toEqual({ x: 4, y: 0, z: 3 }); }); it("supports looped paths and wraps progress 1 back to the first point", () => { const path = createScenePath({ id: "path-loop", loop: true, points: [ { id: "point-a", position: { x: 0, y: 0, z: 0 } }, { id: "point-b", position: { x: 2, y: 0, z: 0 } }, { id: "point-c", position: { x: 2, y: 0, z: 2 } }, { id: "point-d", position: { x: 0, y: 0, z: 2 } } ] }); const resolvedPath = resolveScenePath(path); expect(resolvedPath.segments).toHaveLength(4); expect(resolvedPath.totalLength).toBe(8); expect(sampleResolvedScenePathPosition(resolvedPath, 1)).toEqual({ x: 0, y: 0, z: 0 }); expect(sampleResolvedScenePathTangent(resolvedPath, 1)).toEqual({ x: 0, y: 0, z: -1 }); }); it("can smooth followed paths into rounded corners", () => { const path = createScenePath({ id: "path-smooth-corner", points: [ { id: "point-a", position: { x: 0, y: 0, z: 0 } }, { id: "point-b", position: { x: 0, y: 0, z: 3 } }, { id: "point-c", position: { x: 4, y: 0, z: 3 } } ] }); const resolvedPath = resolveScenePath(path); const smoothedPosition = sampleResolvedScenePathPosition(resolvedPath, 0.5, { smooth: true }); const smoothedTangent = sampleResolvedScenePathTangent(resolvedPath, 0.5, { smooth: true }); expect(smoothedPosition).not.toEqual({ x: 0.5, y: 0, z: 3 }); expect(smoothedPosition.x).toBeGreaterThan(0); expect(smoothedPosition.z).toBeLessThan(3); expect(smoothedTangent.x).toBeGreaterThan(0); expect(smoothedTangent.z).toBeGreaterThan(0); }); it("projects world points onto the nearest resolved path segment and returns progress", () => { const path = createScenePath({ id: "path-nearest-point", points: [ { id: "point-a", position: { x: 0, y: 0, z: 0 } }, { id: "point-b", position: { x: 0, y: 0, z: 4 } }, { id: "point-c", position: { x: 6, y: 0, z: 4 } } ] }); const resolvedPath = resolveScenePath(path); expect( resolveNearestPointOnResolvedScenePath(resolvedPath, { x: 2, y: 0, z: 5 }) ).toEqual({ progress: 0.6, distance: 1, distanceAlongPath: 6, segmentIndex: 1, position: { x: 2, y: 0, z: 4 }, tangent: { x: 1, y: 0, z: 0 } }); }); it("maps world points between authored track points into clamped rail progress", () => { expect( mapWorldPointToScenePathProgressBetweenPoints({ point: { x: 12, y: 1, z: 2 }, trackStartPoint: { x: 0, y: 1, z: 2 }, trackEndPoint: { x: 10, y: 1, z: 2 }, railStartProgress: 0.25, railEndProgress: 0.75 }) ).toEqual({ trackProgress: 1, railProgress: 0.75, projectedTrackPosition: { x: 10, y: 1, z: 2 } }); }); });