3D魔方应用
来自 Wikiprompt,自由的提示词百科全书
3D魔方应用 一个使用Three.js创建交互式3D魔方的综合提示,包含控件、用户界面和流畅动画。
提示词内容收藏
🌐
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>3D 魔方 - 交互式演示</title>
<style>
body {
margin: 0;
overflow: hidden;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #111;
color: white;
}
#info {
position: absolute;
top: 20px;
left: 20px;
background: rgba(20, 20, 20, 0.8);
backdrop-filter: blur(8px);
padding: 12px 20px;
border-radius: 30px;
border: 1px solid rgba(255,255,255,0.15);
font-size: 14px;
letter-spacing: 1px;
box-shadow: 0 4px 15px rgba(0,0,0,0.5);
pointer-events: none;
z-index: 10;
}
#move-counter {
position: absolute;
bottom: 30px;
left: 50%;
transform: translateX(-50%);
background: rgba(20, 20, 20, 0.8);
backdrop-filter: blur(8px);
padding: 10px 24px;
border-radius: 40px;
border: 1px solid rgba(255,255,255,0.15);
font-size: 18px;
font-weight: 600;
color: #fff;
box-shadow: 0 4px 15px rgba(0,0,0,0.5);
z-index: 10;
text-align: center;
pointer-events: none;
letter-spacing: 1px;
}
.btn-container {
position: absolute;
bottom: 30px;
display: flex;
gap: 20px;
z-index: 20;
}
.btn-left {
left: 30px;
}
.btn-right {
right: 30px;
}
.btn {
background: rgba(30, 30, 40, 0.8);
backdrop-filter: blur(8px);
border: 1px solid rgba(255,255,255,0.2);
color: white;
padding: 12px 28px;
border-radius: 40px;
font-size: 16px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
box-shadow: 0 4px 15px rgba(0,0,0,0.4);
letter-spacing: 0.5px;
text-transform: uppercase;
user-select: none;
}
.btn:hover {
background: rgba(60, 60, 80, 0.9);
border-color: rgba(255,255,255,0.4);
transform: scale(1.02);
box-shadow: 0 6px 20px rgba(0,0,0,0.6);
}
.btn:active {
transform: scale(0.98);
}
.btn-scramble {
border-left: 4px solid #ffaa33;
}
.btn-solve {
border-right: 4px solid #33aaff;
}
#status {
position: absolute;
top: 20px;
right: 20px;
background: rgba(20, 20, 20, 0.7);
backdrop-filter: blur(8px);
padding: 8px 16px;
border-radius: 20px;
font-size: 13px;
color: #aaa;
border: 1px solid rgba(255,255,255,0.1);
z-index: 10;
pointer-events: none;
}
@media (max-width: 600px) {
.btn { padding: 8px 16px; font-size: 14px; }
#move-counter { font-size: 14px; padding: 6px 16px; }
}
</style>
</head>
<body>
<div id="info">🧊 3D 魔方</div>
<div id="status">拖动旋转 · 滚轮缩放</div>
<div id="move-counter">步数: 0</div>
<div class="btn-container btn-left">
<button class="btn btn-scramble" id="scrambleBtn">🎲 打乱</button>
</div>
<div class="btn-container btn-right">
<button class="btn btn-solve" id="solveBtn">✨ 还原</button>
</div>
<!-- 引入 Three.js 核心库和扩展 -->
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three@0.128.0/build/three.module.js",
"three/addons/": "https://unpkg.com/three@0.128.0/examples/jsm/"
}
}
</script>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
// --- 初始化场景、相机、渲染器 ---
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x111122);
// 添加一些环境光效
const ambientLight = new THREE.AmbientLight(0x404060);
scene.add(ambientLight);
const dirLight = new THREE.DirectionalLight(0xffffff, 1);
dirLight.position.set(2, 5, 3);
scene.add(dirLight);
const fillLight = new THREE.DirectionalLight(0xccddff, 0.5);
fillLight.position.set(-3, 1, -2);
scene.add(fillLight);
// 辅助光,让魔方更立体
const backLight = new THREE.PointLight(0x4466ff, 0.3);
backLight.position.set(0, 0, -5);
scene.add(backLight);
// 相机
const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(5, 5, 8);
camera.lookAt(0, 0, 0);
// 渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = false; // 不需要阴影提升性能
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
document.body.appendChild(renderer.domElement);
// 控制器
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.autoRotate = false;
controls.enableZoom = true;
controls.rotateSpeed = 0.8;
controls.target.set(0, 0, 0);
// --- 魔方数据 ---
const cubeGroup = new THREE.Group();
scene.add(cubeGroup);
// 颜色定义 (标准魔方配色)
const colors = {
right: 0xff4d4d, // 红
left: 0xff8c1a, // 橙
up: 0xffffff, // 白
down: 0xffd500, // 黄
front: 0x4dff4d, // 绿
back: 0x4d4dff // 蓝
};
// 每个小块的尺寸和间隙
const size = 0.9;
const gap = 1.05; // 间距,使小块之间有视觉间隙
// 存储所有小块 (cubies)
const cubies = [];
// 创建 27 个小块 (包括中心块,虽然不可见但保留)
for (let x = -1; x <= 1; x++) {
for (let y = -1; y <= 1; y++) {
for (let z = -1; z <= 1; z++) {
// 创建一个小块几何体
const geometry = new THREE.BoxGeometry(size, size, size);
// 根据位置设置材质颜色 (六个面)
const materials = [
new THREE.MeshStandardMaterial({ color: x === 1 ? colors.right : 0x222222, roughness: 0.3, metalness: 0.1 }), // +x 右
new THREE.MeshStandardMaterial({ color: x === -1 ? colors.left : 0x222222, roughness: 0.3, metalness: 0.1 }), // -x 左
new THREE.MeshStandardMaterial({ color: y === 1 ? colors.up : 0x222222, roughness: 0.3, metalness: 0.1 }), // +y 上
new THREE.MeshStandardMaterial({ color: y === -1 ? colors.down : 0x222222, roughness: 0.3, metalness: 0.1 }), // -y 下
new THREE.MeshStandardMaterial({ color: z === 1 ? colors.front : 0x222222, roughness: 0.3, metalness: 0.1 }), // +z 前
new THREE.MeshStandardMaterial({ color: z === -1 ? colors.back : 0x222222, roughness: 0.3, metalness: 0.1 }) // -z 后
];
const cubie = new THREE.Mesh(geometry, materials);
cubie.position.set(x * gap, y * gap, z * gap);
// 添加边框效果 (使用线框辅助,但为了美观,我们使用边缘线)
const edges = new THREE.EdgesGeometry(geometry);
const line = new THREE.LineSegments(edges, new THREE.LineBasicMaterial({ color: 0x000000 }));
cubie.add(line); // 将边框作为子元素,随 cubie 移动旋转
cubeGroup.add(cubie);
cubies.push(cubie);
}
}
}
// --- 移动系统 (旋转层) ---
// 定义移动类型
const moveQueue = [];
let isAnimating = false;
let moveCount = 0;
const moveCounterElement = document.getElementById('move-counter');
// 更新步数显示
function updateMoveCounter() {
moveCounterElement.textContent = `步数: ${moveCount}`;
}
// 获取某一层的 cubies (axis: 'x', 'y', 'z'; layer: -1, 0, 1)
function getLayerCubies(axis, layer) {
return cubies.filter(cubie => {
const pos = cubie.position;
if (axis === 'x') return Math.round(pos.x / gap) === layer;
if (axis === 'y') return Math.round(pos.y / gap) === layer;
if (axis === 'z') return Math.round(pos.z / gap) === layer;
return false;
});
}
// 执行旋转动画 (axis, layer, angle, duration)
function animateRotation(axis, layer, angle, duration = 300) {
if (isAnimating) return;
const targetCubies = getLayerCubies(axis, layer);
if (targetCubies.length === 0) return;
isAnimating = true;
const startTime = performance.now();
const startRotation = new THREE.Euler(0, 0, 0);
// 记录每个 cubie 的初始世界位置和旋转 (相对于组)
const initialData = targetCubies.map(cubie => ({
cubie,
startPos: cubie.position.clone(),
startQuat: cubie.quaternion.clone()
}));
// 计算旋转轴 (世界坐标)
let rotAxis = new THREE.Vector3();
if (axis === 'x') rotAxis.set(1, 0, 0);
else if (axis === 'y') rotAxis.set(0, 1, 0);
else if (axis === 'z') rotAxis.set(0, 0, 1);
// 动画循环
function animateStep(now) {
const elapsed = now - startTime;
const progress = Math.min(elapsed / duration, 1);
// 缓动函数 easeInOutCubic
const eased = progress < 0.5 ? 4 * progress * progress * progress : 1 - Math.pow(-2 * progress + 2, 3) / 2;
const currentAngle = angle * eased;
// 对每个 cubie 应用旋转
targetCubies.forEach((cubie, index) => {
const data = initialData[index];
// 计算相对于原点的旋转: 先旋转位置,再旋转自身朝向
// 由于 cubie 是 cubeGroup 的子元素,我们需要在局部空间旋转
// 更简单的方法: 使用 pivot 方法,但为了简洁,我们手动计算
// 这里我们使用矩阵变换
const pos = data.startPos.clone();
const quat = data.startQuat.clone();
// 构建旋转矩阵
const rotMat = new THREE.Matrix4().makeRotationAxis(rotAxis, currentAngle);
// 应用旋转到位置
pos.applyMatrix4(rotMat);
// 应用旋转到四元数
quat.premultiply(new THREE.Quaternion().setFromAxisAngle(rotAxis, currentAngle));
cubie.position.copy(pos);
cubie.quaternion.copy(quat);
});
if (progress < 1) {
requestAnimationFrame(animateStep);
} else {
// 动画结束,确保最终位置精确
targetCubies.forEach((cubie, index) => {
const data = initialData[index];
const pos = data.startPos.clone();
const quat = data.startQuat.clone();
const rotMat = new THREE.Matrix4().makeRotationAxis(rotAxis, angle);
pos.applyMatrix4(rotMat);
quat.premultiply(new THREE.Quaternion().setFromAxisAngle(rotAxis, angle));
cubie.position.copy(pos);
cubie.quaternion.copy(quat);
});
// 更新步数
moveCount++;
updateMoveCounter();
isAnimating = false;
// 处理队列中的下一个移动
if (moveQueue.length > 0) {
const nextMove = moveQueue.shift();
executeMove(nextMove.axis, nextMove.layer, nextMove.angle);
}
}
}
requestAnimationFrame(animateStep);
}
// 执行一个移动 (加入队列)
function executeMove(axis, layer, angle) {
if (isAnimating) {
moveQueue.push({ axis, layer, angle });
} else {
animateRotation(axis, layer, angle);
}
}
// 生成随机打乱序列
function scrambleCube() {
if (isAnimating) return; // 如果正在动画,忽略
// 清空队列
moveQueue.length = 0;
const axes = ['x', 'y', 'z'];
const layers = [-1, 0, 1];
const angles = [Math.PI/2, -Math.PI/2];
const numMoves = 15 + Math.floor(Math.random() * 6); // 15-20
for (let i = 0; i < numMoves; i++) {
const axis = axes[Math.floor(Math.random() * 3)];
const layer = layers[Math.floor(Math.random() * 3)];
const angle = angles[Math.floor(Math.random() * 2)];
// 直接执行 (因为会排队)
executeMove(axis, layer, angle);
}
// 重置步数显示?打乱后步数继续累计,但我们可以保留计数,或者重置为0?这里保留累计。
}
// 还原 (反向执行所有已执行的移动)
// 由于我们记录了步数,但没有存储历史,简单方案:通过记录所有移动历史,然后反向执行
// 为了演示,我们使用一个简单的历史栈
let moveHistory = [];
// 重写 executeMove 以记录历史 (仅记录成功的移动)
function executeMoveWithHistory(axis, layer, angle) {
if (isAnimating) {
moveQueue.push({ axis, layer, angle });
} else {
// 记录历史
moveHistory.push({ axis, layer, angle });
animateRotation(axis, layer, angle);
}
}
// 覆盖之前的 executeMove 引用
// 修改 scramble 和 solve 使用新的函数
// 为了简洁,我们直接修改上面函数,但为了不影响,我们重新定义
// 这里我们重新定义 executeMove 来包含历史
const originalExecuteMove = executeMove;
executeMove = function(axis, layer, angle) {
if (isAnimating) {
moveQueue.push({ axis, layer, angle });
} else {
moveHistory.push({ axis, layer, angle });
animateRotation(axis, layer, angle);
}
};
// 打乱按钮
document.getElementById('scrambleBtn').addEventListener('click', () => {
if (isAnimating) return;
// 清空历史
moveHistory = [];
moveCount = 0;
updateMoveCounter();
scrambleCube();
});
// 还原按钮
document.getElementById('solveBtn').addEventListener('click', () => {
if (isAnimating) return;
// 反向执行历史
const reverseMoves = moveHistory.slice().reverse();
moveHistory = []; // 清空历史
moveCount = 0;
updateMoveCounter();
// 逐个反向执行
reverseMoves.forEach(move => {
executeMove(move.axis, move.layer, -move.angle);
});
});
// 键盘快捷键
document.addEventListener('keydown', (e) => {
if (e.key === 'r' || e.key === 'R') {
document.getElementById('scrambleBtn').click();
} else if (e.key === 's' || e.key === 'S') {
document.getElementById('solveBtn').click();
}
});
// 初始化步数显示
updateMoveCounter();
// --- 动画循环 ---
function animate() {
requestAnimationFrame(animate);
controls.update(); // 启用阻尼
renderer.render(scene, camera);
}
animate();
// 窗口大小自适应
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
// 添加一些星光背景效果 (简单粒子)
const starsGeometry = new THREE.BufferGeometry();
const starsCount = 1000;
const starsPositions = new Float32Array(starsCount * 3);
for (let i = 0; i < starsCount * 3; i += 3) {
starsPositions[i] = (Math.random() - 0.5) * 200;
starsPositions[i+1] = (Math.random() - 0.5) * 200;
starsPositions[i+2] = (Math.random() - 0.5) * 200;
}
starsGeometry.setAttribute('position', new THREE.BufferAttribute(starsPositions, 3));
const starsMaterial = new THREE.PointsMaterial({ color: 0x88aaff, size: 0.15, transparent: true });
const stars = new THREE.Points(starsGeometry, starsMaterial);
scene.add(stars);
// 添加一个简单的环境光晕
const light = new THREE.PointLight(0x4466ff, 0.5);
light.position.set(3, 3, 3);
scene.add(light);
console.log('3D 魔方已加载!使用 R 打乱,S 还原');
</script>
</body>
</html>
用法
此提示词专为 coding 设计。复制上方内容并粘贴到你常用的 AI 工具中。
为获得最佳效果,可将占位符(方括号或大写字母标示)替换为你的具体需求。
讨论
0 条评论