Implement Catmull-Rom interpolation and enhance ScenePath definition with curve mode and sampled resolution validation

This commit is contained in:
2026-05-12 22:45:30 +02:00
parent 975dea8479
commit f9903fc071

View File

@@ -201,6 +201,28 @@ function clampProgress(progress: number): number {
return progress;
}
export function isScenePathCurveMode(
value: unknown
): value is ScenePathCurveMode {
return value === "linear" || value === "catmullRom";
}
export function normalizeScenePathSampledResolution(value: number): number {
if (!Number.isFinite(value) || !Number.isInteger(value)) {
throw new Error("Path sampled resolution must be a finite integer.");
}
if (value < MIN_SCENE_PATH_SAMPLED_RESOLUTION) {
return MIN_SCENE_PATH_SAMPLED_RESOLUTION;
}
if (value > MAX_SCENE_PATH_SAMPLED_RESOLUTION) {
return MAX_SCENE_PATH_SAMPLED_RESOLUTION;
}
return value;
}
function resolvePathSegmentSample(
path: ResolvedPathLike<PathPointLike, ResolvedPathSegmentLike>,
progress: number
@@ -268,6 +290,38 @@ function lerpVec3(start: Vec3, end: Vec3, t: number): Vec3 {
};
}
function catmullRomVec3(
previous: Vec3,
start: Vec3,
end: Vec3,
next: Vec3,
t: number
): Vec3 {
const t2 = t * t;
const t3 = t2 * t;
return {
x:
0.5 *
(2 * start.x +
(-previous.x + end.x) * t +
(2 * previous.x - 5 * start.x + 4 * end.x - next.x) * t2 +
(-previous.x + 3 * start.x - 3 * end.x + next.x) * t3),
y:
0.5 *
(2 * start.y +
(-previous.y + end.y) * t +
(2 * previous.y - 5 * start.y + 4 * end.y - next.y) * t2 +
(-previous.y + 3 * start.y - 3 * end.y + next.y) * t3),
z:
0.5 *
(2 * start.z +
(-previous.z + end.z) * t +
(2 * previous.z - 5 * start.z + 4 * end.z - next.z) * t2 +
(-previous.z + 3 * start.z - 3 * end.z + next.z) * t3)
};
}
function buildSmoothedPolylinePoints(
path: ResolvedPathLike<PathPointLike, ResolvedPathSegmentLike>
): Vec3[] {
@@ -504,7 +558,17 @@ export function createDefaultScenePathPoints(anchor?: Vec3): ScenePathPoint[] {
export function createScenePath(
overrides: Partial<
Pick<ScenePath, "id" | "name" | "visible" | "enabled" | "loop" | "points">
Pick<
ScenePath,
| "id"
| "name"
| "visible"
| "enabled"
| "loop"
| "curveMode"
| "sampledResolution"
| "points"
>
> = {}
): ScenePath {
const points =
@@ -514,6 +578,10 @@ export function createScenePath(
const visible = overrides.visible ?? DEFAULT_SCENE_PATH_VISIBLE;
const enabled = overrides.enabled ?? DEFAULT_SCENE_PATH_ENABLED;
const loop = overrides.loop ?? DEFAULT_SCENE_PATH_LOOP;
const curveMode = overrides.curveMode ?? DEFAULT_SCENE_PATH_CURVE_MODE;
const sampledResolution = normalizeScenePathSampledResolution(
overrides.sampledResolution ?? DEFAULT_SCENE_PATH_SAMPLED_RESOLUTION
);
if (points.length < MIN_SCENE_PATH_POINT_COUNT) {
throw new Error(
@@ -533,6 +601,10 @@ export function createScenePath(
throw new Error("Path loop must be a boolean.");
}
if (!isScenePathCurveMode(curveMode)) {
throw new Error("Path curve mode must be linear or catmullRom.");
}
const seenPointIds = new Set<string>();
for (const point of points) {
@@ -554,6 +626,8 @@ export function createScenePath(
visible,
enabled,
loop,
curveMode,
sampledResolution,
points
};
}
@@ -570,6 +644,8 @@ export function areScenePathsEqual(left: ScenePath, right: ScenePath): boolean {
left.visible === right.visible &&
left.enabled === right.enabled &&
left.loop === right.loop &&
left.curveMode === right.curveMode &&
left.sampledResolution === right.sampledResolution &&
left.points.length === right.points.length &&
left.points.every(
(point, index) =>