# 如何以恒定的“下降速率”从预定义的2D（xz）线段中构建3D贝塞尔曲线样条线？

I've been exploring building a small 3D game that uses a spline for randomized, generated tracks. The closest analogue for helping visualize this is something like Impossible Road, though likely with the track being a "path" rather than a collidable physics body - so, in that sense, the gameplay will be more like Audiosurf.

Godot和Three都具有方便的贝塞尔曲线样条曲线类，并且贝塞尔曲线似乎相当容易推论，因此我开始使用三次贝塞尔曲线构建我的样条曲线。我的想法是定义不同的轨道“预制段”，这些轨道可以随机排序以形成随机生成的轨道，例如“硬左转”，“右U形转弯”，“ 45度左转”等。这就是《不可能的道路》所要做的（在该视频中请注意，检查点始终始终是片段之间的“胶合点”），这对我来说很有意义。

``````const prefabs = {
leftTurn: {
curve: new CubicBezierCurve3(
new Vector3(0, 0, 0),
new Vector3(0, 0, 0),
new Vector3(0, 0, -1),
new Vector3(-1, 0, -1)
)
}
};
``````

Notice it's flat along the Y axis, so this is just a 90 degree turn in 2D.

This made it pretty easy to glue pieces together. If I defined a path as `[leftTurn, rightTurn, leftTurn]`, when I generate my curve from those segments, I'd just keep track of the tangent line at each exit, then rotate the piece around its origin to match the "yaw" represented by the tangent (that is, its rotation around the y axis/on the xz plane):

``````/**
* Transform a list of prefab names ("leftTurn", "rightTurn") to a series of
* Bezier curves.
*/
export function convertPiecesToSplineSegments(
pieces: string[]
): SplineSegment[] {
let enterHeading = new Vector3(0, 0, -1).normalize();
let enterPoint = new Vector3(0, 0, 0);

return pieces.map((piece) => {
const prefab = prefabs[piece];

// get the angle between (0, 0, -1) and the current heading to figure out
// how much to rotate the piece by.
//
// via https://stackoverflow.com/a/33920320
const yaw = Math.atan2(
.clone()
.cross(new Vector3(0, 0, -1))
// a lil weirded out i had to use the negative y axis here, not sure what's
// going on w that...
.dot(new Vector3(0, -1, 0)),
);

const transform = (v: Vector3): Vector3 => {
return v
.clone()
.applyAxisAngle(new Vector3(0, 1, 0), yaw)
};

const a = transform(prefab.curve.v0);
const b = transform(prefab.curve.v1);
const c = transform(prefab.curve.v2);
const d = transform(prefab.curve.v3);
const curve = new CubicBezierCurve3(a, b, c, d);

enterPoint = d;

return {
curve,
}
}
}
``````

https://disco.zone/splines/1

So, clearly, rotation isn't going to be what I want; I need to the curve points add additional `y` for the descent. And this is where things get tricky.

``````a=(0, 0, 0)
b=(0, 0, -1/3)
c=(0, 0, -2/3)
d=(0, 0, -1)
``````

Then it would be easy to apply a constant rate of descent: just add a Y value of `-1/3`, `-2/3`, and `-1` to `b`, `c`, and `d`. Both `b-a` and and `d-c` would be `(0, 0, -1/3)`, so the tangents would be equal all the way down.

In practice, these are, well, curves, so it's not so simple. I think you'd need to calculate the XZ distance of `b` and `c` from `a`, and scale the `y` appropriately, but I'm not sure if this is actually a rasonable approach in any way. I have tried a bunch of random "throwing code at the wall" to see if I could come up with anything that resembled what I wanted, but so far nothing has seemed to work.

My code so far, in full, is here. While I hope you don't need to read it to understand the problem, it may help in providing solutions: https://github.com/thomasboyt/rascal