基于贝塞尔曲线的ThreeJS 2D匀速动画速度控制求助
Hey there! Let's tackle this velocity control problem for your particle moving along a Bézier curve. The issue with your current approach is that you're stepping through discrete pre-generated curve points one by one—this ties speed directly to how many points you generate, which is super inefficient and inflexible. Here are a couple of clean, efficient ways to fix this:
1. Use Parameterized Bézier Curve Calculation
Bézier curves are inherently defined by a parameter t that ranges from 0 (start of the curve) to 1 (end of the curve). Instead of pre-generating dozens of points, we can calculate the particle's position directly using this parameter, and adjust how t changes over time to control speed.
First, implement a function to compute a point on your Bézier curve for any given t. Let's use a cubic Bézier (the most common type) as an example:
function getCubicBezierPoint(t, p0, p1, p2, p3) { const u = 1 - t; const tSquared = t * t; const uSquared = u * u; const uCubed = uSquared * u; const tCubed = tSquared * t; const x = uCubed * p0.x + 3 * uSquared * t * p1.x + 3 * u * tSquared * p2.x + tCubed * p3.x; const y = uCubed * p0.y + 3 * uSquared * t * p1.y + 3 * u * tSquared * p2.y + tCubed * p3.y; return { x, y }; }
Then, in your animation loop, track elapsed time to update t smoothly. You can set a total duration for the particle to traverse the curve, which directly controls speed:
// Initialize these variables OUTSIDE your animate loop let startTime = null; const totalTraversalTime = 4000; // 4 seconds to go from start to end const bezierControlPoints = [p0, p1, p2, p3]; // Your original curve control points function animate(timestamp) { if (!startTime) startTime = timestamp; const elapsedTime = timestamp - startTime; // Calculate t: clamp it between 0 and 1 to avoid overshooting the curve let t = Math.min(elapsedTime / totalTraversalTime, 1); // Optional: Add easing for non-uniform speed (e.g., slow start/end) // t = easeInOutQuad(t); // Get the exact position on the curve for this t value const currentPosition = getCubicBezierPoint(t, ...bezierControlPoints); particles[0].position.x = currentPosition.x; particles[0].position.y = currentPosition.y; // Keep animating until we reach the end of the curve if (t < 1) { requestAnimationFrame(animate); } } // Example easing function (remove if you want strictly uniform speed) function easeInOutQuad(t) { return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t; }
With this setup, just adjust totalTraversalTime to change speed: smaller values mean faster movement, larger values mean slower. No extra curve points needed!
2. Speed-Based Parameter Increment
If you want to define speed in physical units (like pixels per second) instead of total duration, you can calculate how much t to increment each frame based on the curve's total length.
First, approximate the total length of your Bézier curve (since it's a curved line, we'll sample segments to estimate length):
function estimateBezierLength(p0, p1, p2, p3, sampleSegments = 100) { let totalLength = 0; let lastPoint = p0; for (let i = 1; i <= sampleSegments; i++) { const t = i / sampleSegments; const currentPoint = getCubicBezierPoint(t, p0, p1, p2, p3); // Add the distance between this point and the last to total length totalLength += Math.hypot(currentPoint.x - lastPoint.x, currentPoint.y - lastPoint.y); lastPoint = currentPoint; } return totalLength; }
Then, in your animation loop, calculate how far the particle should move each frame based on your desired speed, and convert that distance to a t increment:
// Initialize variables OUTSIDE the animate loop const bezierControlPoints = [p0, p1, p2, p3]; const totalCurveLength = estimateBezierLength(...bezierControlPoints); const desiredSpeed = 120; // 120 pixels per second let currentT = 0; let lastTimestamp = 0; function animate(timestamp) { if (!lastTimestamp) lastTimestamp = timestamp; // Calculate time since last frame (in seconds) const deltaTime = (timestamp - lastTimestamp) / 1000; // How far should the particle move this frame? const distanceToMove = desiredSpeed * deltaTime; // Convert distance to a t increment (approximate) const tIncrement = distanceToMove / totalCurveLength; currentT = Math.min(currentT + tIncrement, 1); // Update particle position const currentPosition = getCubicBezierPoint(currentT, ...bezierControlPoints); particles[0].position.x = currentPosition.x; particles[0].position.y = currentPosition.y; lastTimestamp = timestamp; if (currentT < 1) { requestAnimationFrame(animate); } }
This method keeps the particle moving at a consistent speed regardless of how long or curved the path is. Adjust sampleSegments if you need a more accurate length estimate (more segments = better accuracy, minimal performance hit).
Key Takeaway
By using the Bézier curve's built-in parameter t instead of pre-generated points, you gain full, efficient control over speed. The first method is simpler for setting a total traversal time, while the second is perfect for defining speed in physical units. Both are way better than adding extra curve points!
内容的提问来源于stack exchange,提问作者Trt Trt

