'use client'; import React, { useState, useEffect, useRef } from 'react'; const VIEW_WIDTH = 800; const VIEW_HEIGHT = 600; const BALL_RADIUS = 30; const STRING_LENGTH = 250; const PIVOT_Y = 100; const CENTER_X = VIEW_WIDTH / 2; const MAX_ANGLE = Math.PI / 4; // 45 degrees swing export const NewtonsCradle = () => { const [time, setTime] = useState(Math.PI); // Start with left ball ready to swing down const requestRef = useRef(); // Physics loop const animate = () => { // Increment time to drive the sine wave // Speed factor 0.05 controls the swing speed setTime(prevTime => prevTime + 0.08); requestRef.current = requestAnimationFrame(animate); }; useEffect(() => { requestRef.current = requestAnimationFrame(animate); return () => { if (requestRef.current) cancelAnimationFrame(requestRef.current); }; }, []); // Calculate angles based on a clamped sine wave // Cycle: // sin(t) < 0: Left ball swings (clamped to negative values) // sin(t) > 0: Right ball swings (clamped to positive values) // sin(t) approx 0: Collision moment const cycle = Math.sin(time); // Ball 0 (Leftmost) only moves when cycle is negative (swings left) // We use Math.min(0, cycle) so it stays at 0 when cycle is positive const angle0 = Math.min(0, cycle) * MAX_ANGLE; // Ball 4 (Rightmost) only moves when cycle is positive (swings right) const angle4 = Math.max(0, cycle) * MAX_ANGLE; // Middle balls stay mostly still (perfect elasticity idealization) const angle1 = 0; const angle2 = 0; const angle3 = 0; const balls = [ { id: 0, angle: angle0, label: '动量' }, { id: 1, angle: angle1, label: '' }, { id: 2, angle: angle2, label: '' }, { id: 3, angle: angle3, label: '' }, { id: 4, angle: angle4, label: '守恒' }, ]; // Helper to calculate ball position const getBallPos = (index: number, angle: number) => { // Calculate the pivot X for this specific ball (they are spaced out) // Total width of 5 balls is 5 * (2 * radius). Center is index 2. const offsetFromCenter = (index - 2) * (BALL_RADIUS * 2); const pivotX = CENTER_X + offsetFromCenter; const x = pivotX + STRING_LENGTH * Math.sin(angle); const y = PIVOT_Y + STRING_LENGTH * Math.cos(angle); return { x, y, pivotX }; }; return ( {/* Definitions for Gradients */} {/* Metallic Gradient for Balls */} {/* Bright highlight */} {/* Base metal */} {/* Shadow */} {/* Dark edge */} {/* Metallic Gradient for Top Bar */} {/* Glow filter for impact */} {/* Title Text */} 牛顿摆 (Newton's Cradle) 完美弹性碰撞演示 {/* Top Mounting Bar */} {/* Render Balls and Strings */} {balls.map((ball) => { const { x, y, pivotX } = getBallPos(ball.id, ball.angle); return ( {/* String */} {/* Connection Point at Top */} {/* The Metal Ball */} {/* Highlight Reflection (fake glossy look) */} {/* Optional Text on balls if moving */} {ball.label && ( 0.05 ? 1 : 0} // Only show when swinging style={{ transition: 'opacity 0.2s' }} > {ball.label} )} ); })} {/* Impact visual effect (Flash when balls meet at center) */} {Math.abs(Math.sin(time)) < 0.1 && ( )} ); };