讨论

3D魔方应用

来自 Wikiprompt,自由的提示词百科全书

Eyisha Zyer

2026年7月11日

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 工具中。

为获得最佳效果,可将占位符(方括号或大写字母标示)替换为你的具体需求。

参考资料

分类:coding| twitter| 3d-cube| three-js

讨论