Three.js实现两组件间带自然垂坠的绳索连接函数
Hey there! I see you're trying to build a realistic rope connection between two points (like a guitar cable) with natural sag, but your current code uses a fixed offset that doesn't respect the actual rope length or physical behavior. Let's fix that by switching to a catenary curve—this is the exact mathematical shape of a hanging rope under gravity, which will give you that authentic垂坠 look you're after.
The Problem with Your Current Code
Your existing drawSpline function uses a fixed shiftRatio to offset the middle point of a Catmull-Rom curve. This works for specific positions but falls short in key ways:
- It ignores the
ropeLengthparameter specified in your required function signature - The sag won't scale naturally when the distance between start/end points changes
- The offset direction is arbitrary, not rooted in physical logic
The Solution: Catenary Curve Implementation
We'll rewrite the drawSpline function to calculate a catenary curve based on the start point, end point, and rope length. Here's the breakdown:
- Calculate the straight-line distance between points to validate the rope length
- Solve for the catenary's shape parameter (using a numerical approximation since there's no closed-form solution)
- Generate evenly spaced points along the catenary
- Convert these points to Three.js coordinates and create the final Line object
Full Modified Code
const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); scene.add(camera); camera.position.z = 10; const renderer = new THREE.WebGLRenderer(); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); function animate() { requestAnimationFrame(animate); renderer.render(scene, camera); } animate(); const RADIUS = 1; const SEGMENTS = 16; const RINGS = 16; const sphereMaterial = new THREE.MeshLambertMaterial({ color: 0xCC0000 }); const sphere1 = new THREE.Mesh(new THREE.SphereGeometry(RADIUS, SEGMENTS, RINGS), sphereMaterial); sphere1.position.set(0, -3, 0); scene.add(sphere1); const sphere2 = new THREE.Mesh(new THREE.SphereGeometry(RADIUS, SEGMENTS, RINGS), sphereMaterial); sphere2.position.set(6, 1, 0); scene.add(sphere2); const pointLight = new THREE.PointLight(0xFFFFFF); pointLight.position.set(10, 50, 130); scene.add(pointLight); // Test the function: rope length is longer than straight-line distance to get visible sag scene.add(drawSpline({x: 0, y: -3, z: 0}, {x: 6, y: 1, z: 0}, 10, 'blue')); /** * Draws a natural sagging rope between two points using a catenary curve * @param {Object} startPoint - {x, y, z} start coordinates * @param {Object} endPoint - {x, y, z} end coordinates * @param {number} ropeLength - Total length of the rope * @param {string} clr - Color of the rope (default: white) * @returns {THREE.Line} Three.js Line object with the sagging rope */ function drawSpline(startPoint, endPoint, ropeLength, clr = 'white') { // Convert start/end to Three.js vectors for easier calculations const start = new THREE.Vector3(startPoint.x, startPoint.y, startPoint.z); const end = new THREE.Vector3(endPoint.x, endPoint.y, endPoint.z); // Calculate straight-line distance between the two points const straightLineDist = start.distanceTo(end); // If rope length is shorter than straight line, return a straight line (no sag possible) if (ropeLength <= straightLineDist) { const points = [start, end]; const geometry = new THREE.BufferGeometry().setFromPoints(points); const material = new THREE.LineBasicMaterial({ color: clr }); return new THREE.Line(geometry, material); } // Calculate horizontal (X-Z plane) and vertical (Y) components between points const dx = end.x - start.x; const dy = end.y - start.y; const dz = end.z - start.z; const horizontalDist = Math.sqrt(dx * dx + dz * dz); // Solve for catenary parameter 'a' using Newton-Raphson numerical approximation let a = horizontalDist / 2; const tolerance = 0.001; let iterations = 0; while (iterations < 100) { const f = 2 * a * Math.sinh(horizontalDist / (2 * a)) - Math.sqrt(ropeLength * ropeLength - dy * dy); const fPrime = 2 * Math.sinh(horizontalDist / (2 * a)) - (horizontalDist / a) * Math.cosh(horizontalDist / (2 * a)); const delta = f / fPrime; a -= delta; if (Math.abs(delta) < tolerance) break; iterations++; } // Generate smooth points along the catenary curve const numPoints = 30; // Adjust for smoother/performance tradeoff const points = []; for (let i = 0; i <= numPoints; i++) { const t = i / numPoints; const x = start.x + dx * t; const z = start.z + dz * t; // Calculate catenary Y position based on the parameter 'a' const s = t * horizontalDist; const y = start.y + dy * t + a * (Math.cosh((s - horizontalDist / 2) / a) - Math.cosh(horizontalDist / (2 * a))); points.push(new THREE.Vector3(x, y, z)); } // Assemble the final Line object const geometry = new THREE.BufferGeometry().setFromPoints(points); const material = new THREE.LineBasicMaterial({ color: clr }); const curveObject = new THREE.Line(geometry, material); return curveObject; }
Key Details Explained
- Validation Check: The function first ensures the rope length is longer than the straight-line distance (you can't have a sagging rope shorter than the distance between two points!).
- Numerical Solver: We use the Newton-Raphson method to find the catenary parameter
a, which controls how much the rope sags—this is the key to getting realistic behavior. - 3D Adaptation: Since we're working in 3D, we calculate the horizontal distance in the X-Z plane and apply the sag to the Y axis, mimicking real-world gravity.
- Smoothness Control: The
numPointsvariable lets you adjust curve smoothness—more points mean a smoother rope, fewer points mean better performance.
Testing Tips
- Try different rope lengths: Longer lengths will create dramatic sag, while lengths just slightly longer than the straight-line distance will have minimal, subtle sag.
- Move the start/end points around—the sag will adjust automatically based on their position and the rope length.
内容的提问来源于stack exchange,提问作者Rob

