|
GLM-4-32B
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>六边形弹跳小球</title>
<style>
body {
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #f0f0f0;
font-family: 'Arial', sans-serif;
overflow: hidden;
}
.container {
position: relative;
width: 100%;
max-width: 800px;
text-align: center;
}
canvas {
background-color: #fff;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
border-radius: 8px;
margin-bottom: 20px;
}
.controls {
margin-top: 20px;
display: flex;
justify-content: center;
gap: 15px;
}
button {
padding: 8px 16px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
transition: background-color 0.3s;
}
button:hover {
background-color: #45a049;
}
.slider-container {
display: flex;
align-items: center;
gap: 10px;
margin-top: 10px;
}
.slider-container label {
width: 120px;
text-align: right;
}
input[type="range"] {
width: 200px;
}
.value-display {
width: 50px;
text-align: left;
}
</style>
</head>
<body>
<div class="container">
<h1>六边形弹跳小球模拟</h1>
<canvas id="canvas" width="600" height="600"></canvas>
<div class="controls">
<button id="resetBtn">重置</button>
<button id="pauseBtn">暂停/继续</button>
</div>
<div class="slider-container">
<label for="gravitySlider">重力大小:</label>
<input type="range" id="gravitySlider" min="0" max="1" step="0.01" value="0.2">
<span class="value-display" id="gravityValue">0.2</span>
</div>
<div class="slider-container">
<label for="frictionSlider">摩擦系数:</label>
<input type="range" id="frictionSlider" min="0" max="0.1" step="0.001" value="0.01">
<span class="value-display" id="frictionValue">0.01</span>
</div>
<div class="slider-container">
<label for="rotationSlider">旋转速度:</label>
<input type="range" id="rotationSlider" min="0" max="0.05" step="0.001" value="0.01">
<span class="value-display" id="rotationValue">0.01</span>
</div>
</div>
<script>
// 获取Canvas元素和上下文
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
// 获取控制元素
const resetBtn = document.getElementById('resetBtn');
const pauseBtn = document.getElementById('pauseBtn');
const gravitySlider = document.getElementById('gravitySlider');
const frictionSlider = document.getElementById('frictionSlider');
const rotationSlider = document.getElementById('rotationSlider');
const gravityValue = document.getElementById('gravityValue');
const frictionValue = document.getElementById('frictionValue');
const rotationValue = document.getElementById('rotationValue');
// 初始化参数
let isPaused = false;
let gravity = parseFloat(gravitySlider.value);
let friction = parseFloat(frictionSlider.value);
let rotationSpeed = parseFloat(rotationSlider.value);
let angle = 0;
// 六边形参数
const hexRadius = 200;
const hexCenterX = canvas.width / 2;
const hexCenterY = canvas.height / 2;
// 小球参数
let ball = {
x: hexCenterX,
y: hexCenterY,
radius: 15,
velocityX: 3,
velocityY: 0,
color: '#3498db'
};
// 计算六边形的顶点
function calculateHexVertices() {
const vertices = [];
for (let i = 0; i < 6; i++) {
const vertexAngle = angle + i * Math.PI / 3;
const x = hexCenterX + hexRadius * Math.cos(vertexAngle);
const y = hexCenterY + hexRadius * Math.sin(vertexAngle);
vertices.push({ x, y });
}
return vertices;
}
// 绘制六边形
function drawHexagon() {
const vertices = calculateHexVertices();
ctx.beginPath();
ctx.moveTo(vertices[0].x, vertices[0].y);
for (let i = 1; i < 6; i++) {
ctx.lineTo(vertices.x, vertices.y);
}
ctx.closePath();
ctx.strokeStyle = '#2c3e50';
ctx.lineWidth = 3;
ctx.stroke();
// 填充六边形(半透明)
ctx.fillStyle = 'rgba(236, 240, 241, 0.5)';
ctx.fill();
return vertices;
}
// 绘制小球
function drawBall() {
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
ctx.fillStyle = ball.color;
ctx.fill();
ctx.strokeStyle = '#2980b9';
ctx.lineWidth = 2;
ctx.stroke();
}
// 检测小球与六边形的碰撞
function checkCollision(vertices) {
// 检查小球是否在六边形内部
function isPointInHexagon(point, vertices) {
let inside = false;
for (let i = 0, j = vertices.length - 1; i < vertices.length; j = i++) {
const xi = vertices.x, yi = vertices.y;
const xj = vertices[j].x, yj = vertices[j].y;
const intersect = ((yi > point.y) !== (yj > point.y))
&& (point.x < (xj - xi) * (point.y - yi) / (yj - yi) + xi);
if (intersect) inside = !inside;
}
return inside;
}
// 如果小球在六边形外部,找到最近的边并反弹
if (!isPointInHexagon(ball, vertices)) {
// 找到最近的边
let minDistance = Infinity;
let closestEdge = null;
for (let i = 0; i < 6; i++) {
const j = (i + 1) % 6;
const edge = {
start: vertices,
end: vertices[j]
};
// 计算小球到边的距离
const distance = distanceToLine(ball, edge);
if (distance < minDistance) {
minDistance = distance;
closestEdge = edge;
}
}
if (closestEdge) {
// 计算边的法向量
const edgeVector = {
x: closestEdge.end.x - closestEdge.start.x,
y: closestEdge.end.y - closestEdge.start.y
};
// 法向量(垂直于边)
const normal = {
x: -edgeVector.y,
y: edgeVector.x
};
// 单位化法向量
const normalLength = Math.sqrt(normal.x * normal.x + normal.y * normal.y);
normal.x /= normalLength;
normal.y /= normalLength;
// 确保法向量指向六边形内部
const centerToBall = {
x: ball.x - hexCenterX,
y: ball.y - hexCenterY
};
const dotProduct = normal.x * centerToBall.x + normal.y * centerToBall.y;
if (dotProduct > 0) {
normal.x = -normal.x;
normal.y = -normal.y;
}
// 计算反弹后的速度
const dot = ball.velocityX * normal.x + ball.velocityY * normal.y;
ball.velocityX = ball.velocityX - 2 * dot * normal.x;
ball.velocityY = ball.velocityY - 2 * dot * normal.y;
// 应用摩擦力
ball.velocityX *= (1 - friction);
ball.velocityY *= (1 - friction);
// 将小球推回六边形内部
const overlap = ball.radius - minDistance;
if (overlap > 0) {
ball.x += normal.x * overlap;
ball.y += normal.y * overlap;
}
}
}
}
// 计算点到线段的距离
function distanceToLine(point, line) {
const A = point.x - line.start.x;
const B = point.y - line.start.y;
const C = line.end.x - line.start.x;
const D = line.end.y - line.start.y;
const dot = A * C + B * D;
const lenSq = C * C + D * D;
let param = -1;
if (lenSq !== 0) {
param = dot / lenSq;
}
let xx, yy;
if (param < 0) {
xx = line.start.x;
yy = line.start.y;
} else if (param > 1) {
xx = line.end.x;
yy = line.end.y;
} else {
xx = line.start.x + param * C;
yy = line.start.y + param * D;
}
const dx = point.x - xx;
const dy = point.y - yy;
return Math.sqrt(dx * dx + dy * dy);
}
// 更新小球位置
function updateBall() {
// 应用重力
ball.velocityY += gravity;
// 更新位置
ball.x += ball.velocityX;
ball.y += ball.velocityY;
// 检测碰撞
const vertices = calculateHexVertices();
checkCollision(vertices);
}
// 动画循环
function animate() {
if (!isPaused) {
// 清除画布
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 更新旋转角度
angle += rotationSpeed;
// 绘制六边形
const vertices = drawHexagon();
// 更新和绘制小球
updateBall();
drawBall();
}
requestAnimationFrame(animate);
}
// 重置小球
function resetBall() {
ball.x = hexCenterX;
ball.y = hexCenterY;
ball.velocityX = 3;
ball.velocityY = 0;
angle = 0;
}
// 事件监听器
resetBtn.addEventListener('click', resetBall);
pauseBtn.addEventListener('click', function() {
isPaused = !isPaused;
});
gravitySlider.addEventListener('input', function() {
gravity = parseFloat(this.value);
gravityValue.textContent = gravity.toFixed(2);
});
frictionSlider.addEventListener('input', function() {
friction = parseFloat(this.value);
frictionValue.textContent = friction.toFixed(3);
});
rotationSlider.addEventListener('input', function() {
rotationSpeed = parseFloat(this.value);
rotationValue.textContent = rotationSpeed.toFixed(3);
});
// 初始化显示值
gravityValue.textContent = gravity.toFixed(2);
frictionValue.textContent = friction.toFixed(3);
rotationValue.textContent = rotationSpeed.toFixed(3);
// 开始动画
animate();
</script>
</body>
</html> |
|