You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Catmull-Rom样条驱动的过山车物体无法到达样条终点的问题排查求助

Catmull-Rom样条驱动的过山车物体无法到达样条终点的问题排查求助

我正在实现一个过山车系统,以下是代码中几个关键函数的说明和具体实现:

  • computeArcLength:计算样条参数t及对应的累计弧长,生成弧长对照表
  • mapStoT:将归一化弧长参数s映射到样条的参数t
  • 主函数moveObjectWithVelocity:负责计算物体的位置与旋转,驱动其沿样条运动

问题描述

我设置的速度曲线是简单的抛物线,但物体始终无法运动到样条的终点位置,希望能帮忙排查问题所在,谢谢!


关键函数实现代码

computeArcLength 函数

function computeArcLength(controlPoints, segments = 40) {
    const arcLengthTable = [];
    let totalLength = 0;

    let prevPoint = getCatmullRomPosition(0, controlPoints.map(p => p.position));
    arcLengthTable.push({ t: 0, length: 0 });

    for (let i = 1; i <= segments; i++) {
        const t = i / segments * (controlPoints.length - 1);
        const currentPoint = getCatmullRomPosition(t, controlPoints.map(p => p.position));
        const segmentLength = prevPoint.distanceTo(currentPoint);
        totalLength += segmentLength;

        arcLengthTable.push({ t, length: totalLength });
        prevPoint = currentPoint;
    }

    return { arcLengthTable, totalLength };
}

mapStoT 函数

function mapStoT(s, arcLengthTable, totalLength) {
    const targetLength = s * totalLength;

    for (let i = 1; i < arcLengthTable.length; i++) {
        if (arcLengthTable[i].length >= targetLength) {
            const prev = arcLengthTable[i - 1];
            const curr = arcLengthTable[i];

            // Linear interpolation to find t
            const segmentLength = curr.length - prev.length;
            const localS = (targetLength - prev.length) / segmentLength;

            return prev.t + localS * (curr.t - prev.t);
        }
    }

    return arcLengthTable[arcLengthTable.length - 1].t;
}

moveObjectWithVelocity 函数

function moveObjectWithVelocity(object, controlPoints, velocityCurve, stepSize) {
    const { arcLengthTable, totalLength } = computeArcLength(controlPoints);
    let s = 0; // Normalized arc length parameter [0, 1]

    function animate() {
        if (s >= 1) {
            s = 0; // Loop back for continuous movement
        }

        // Map s to t
        const t = mapStoT(s, arcLengthTable, totalLength);

        // Get interpolated position
        const interpolatedPos = getCatmullRomPosition(t, controlPoints.map(p => p.position));
        object.position.copy(interpolatedPos);

        // Interpolate normals for smooth tilting
        const currentIndex = Math.floor(t);
        const nextIndex = Math.min(currentIndex + 1, controlPoints.length - 1);
        const normal = new THREE.Vector3().lerpVectors(
            controlPoints[currentIndex].normal,
            controlPoints[nextIndex].normal,
            t - currentIndex
        );
        const up = new THREE.Vector3(0, 1, 0); // Reference up vector
        const quaternion = new THREE.Quaternion().setFromUnitVectors(up, normal);
        object.quaternion.copy(quaternion);

        // Increment s based on velocity
        const velocity = velocityCurve(s); // Get velocity at the current s
        s += 0.03;

        requestAnimationFrame(animate);
    }

    animate();
}

问题排查与修复建议

我梳理了你的代码,发现几个核心问题导致物体无法到达终点:

1. 硬编码的s增量完全忽略了速度曲线和时间步长

你直接使用s += 0.03;作为参数增量,这是一个固定值:

  • 传入的stepSize(时间步长)参数完全未被使用,速度曲线的设置也失去了作用
  • s接近1时(比如s=0.98),加上0.03后s=1.01,直接触发重置为0的逻辑,物体还没到达终点就被强制拉回起点循环

修复方案
根据当前速度、时间步长和总弧长计算正确的s增量:

// 替换原来的 s += 0.03;
const velocity = velocityCurve(s);
// 计算当前帧应移动的弧长,再转换为归一化的s增量
const deltaS = (velocity * stepSize) / totalLength;
s += deltaS;

2. s的重置逻辑跳过了终点位置的渲染

当前逻辑是先判断s >=1就重置,再计算位置:

if (s >= 1) {
    s = 0;
}
// 再计算位置

这意味着当s刚好等于1时,会直接被重置为0,物体没有机会渲染终点位置。

修复方案
调整顺序,先处理当前s对应的位置(包括s=1的情况),再执行增量和重置:

// 先计算并应用当前s对应的位置与旋转
// ... 原位置、旋转计算代码 ...

// 再处理s的增量与重置
const velocity = velocityCurve(s);
const deltaS = (velocity * stepSize) / totalLength;
s += deltaS;

if (s >= 1) {
    // 确保终点位置已渲染后再重置
    s = 0;
    // 若需要物体停在终点,可移除重置逻辑,根据需求调整
}

3. 可选优化:提升弧长对照表的精度(非核心问题)

computeArcLengtht的计算方式在分段数较少时,可能导致弧长对照表精度不足。如果需要更高精度,可以改用均匀的t步进(比如t += 1/segments),但这不是导致无法到达终点的直接原因。


按照上述修改调整后,物体应该能正确到达样条终点,同时速度曲线也会正常生效。如果还有问题,可以检查getCatmullRomPosition函数是否正确处理了t = controlPoints.length - 1的情况,确保返回最后一个控制点的位置。

备注:内容来源于stack exchange,提问作者COLONEL FF

火山引擎 最新活动