Aplicativo de Cubo Mágico 3D
De Wikiprompt, a enciclopédia livre de prompts
Aplicativo de Cubo Mágico 3D Um prompt abrangente para criar um Cubo Mágico 3D interativo usando Three.js, com controles, interface do usuário e animações suaves.
Conteúdo do PromptSalvar
🌐
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Rubik's Cube 3D - Interactive</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 24px;
border-radius: 30px;
border: 1px solid rgba(255,255,255,0.2);
box-shadow: 0 4px 20px rgba(0,0,0,0.5);
font-size: 14px;
letter-spacing: 1px;
z-index: 10;
pointer-events: none;
}
#move-counter {
position: absolute;
top: 20px;
right: 20px;
background: rgba(20, 20, 20, 0.8);
backdrop-filter: blur(8px);
padding: 12px 24px;
border-radius: 30px;
border: 1px solid rgba(255,255,255,0.2);
box-shadow: 0 4px 20px rgba(0,0,0,0.5);
font-size: 16px;
font-weight: 600;
z-index: 10;
pointer-events: none;
color: #ffaa00;
}
.btn-container {
position: absolute;
bottom: 30px;
left: 0;
right: 0;
display: flex;
justify-content: space-between;
padding: 0 30px;
z-index: 20;
pointer-events: none;
}
.btn {
pointer-events: auto;
background: rgba(30, 30, 40, 0.85);
backdrop-filter: blur(10px);
border: 1px solid rgba(255,255,255,0.25);
color: white;
padding: 14px 32px;
border-radius: 50px;
font-size: 18px;
font-weight: 600;
letter-spacing: 1px;
cursor: pointer;
transition: all 0.2s ease;
box-shadow: 0 8px 30px rgba(0,0,0,0.6);
text-transform: uppercase;
border: 1px solid rgba(255,255,255,0.2);
}
.btn:hover {
background: rgba(50, 50, 70, 0.9);
transform: scale(1.05);
border-color: rgba(255,255,255,0.5);
}
.btn:active {
transform: scale(0.95);
}
#scramble-btn {
background: rgba(255, 100, 80, 0.2);
border-color: rgba(255, 120, 100, 0.5);
}
#scramble-btn:hover {
background: rgba(255, 100, 80, 0.4);
}
#solve-btn {
background: rgba(80, 200, 120, 0.2);
border-color: rgba(100, 255, 150, 0.5);
}
#solve-btn:hover {
background: rgba(80, 200, 120, 0.4);
}
#hint {
position: absolute;
bottom: 100px;
left: 50%;
transform: translateX(-50%);
background: rgba(0,0,0,0.5);
backdrop-filter: blur(4px);
padding: 8px 20px;
border-radius: 20px;
font-size: 14px;
color: #ccc;
border: 1px solid rgba(255,255,255,0.1);
z-index: 5;
white-space: nowrap;
}
@media (max-width: 600px) {
.btn { padding: 10px 20px; font-size: 14px; }
#hint { display: none; }
}
</style>
</head>
<body>
<div id="info">🧊 RUBIK'S CUBE 3D</div>
<div id="move-counter">Moves: 0</div>
<div class="btn-container">
<button id="scramble-btn" class="btn">🔀 Scramble</button>
<button id="solve-btn" class="btn">✨ Solve</button>
</div>
<div id="hint">Drag to rotate • Scroll to zoom • R = scramble • S = solve</div>
<!-- Import Three.js and OrbitControls from CDN -->
<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';
// --- Setup Scene, Camera, Renderer ---
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x111122);
// Add subtle fog for depth
scene.fog = new THREE.Fog(0x111122, 10, 30);
const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(5, 5, 5);
camera.lookAt(0, 0, 0);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.setPixelRatio(window.devicePixelRatio);
document.body.appendChild(renderer.domElement);
// --- Controls ---
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.autoRotate = false;
controls.enableZoom = true;
controls.rotateSpeed = 1.0;
controls.zoomSpeed = 1.2;
controls.target.set(0, 0, 0);
controls.maxPolarAngle = Math.PI / 2;
// --- Lighting ---
// Ambient light for base illumination
const ambientLight = new THREE.AmbientLight(0x404060);
scene.add(ambientLight);
// Main directional light with shadows
const dirLight = new THREE.DirectionalLight(0xffffff, 1);
dirLight.position.set(3, 5, 2);
dirLight.castShadow = true;
dirLight.receiveShadow = true;
scene.add(dirLight);
// Fill light from opposite side
const fillLight = new THREE.DirectionalLight(0x4466ff, 0.3);
fillLight.position.set(-3, 1, -2);
scene.add(fillLight);
// Back light for rim
const backLight = new THREE.DirectionalLight(0xffaa44, 0.2);
backLight.position.set(0, 2, -5);
scene.add(backLight);
// Soft point light for overall glow
const pointLight = new THREE.PointLight(0xffffff, 0.5, 10);
pointLight.position.set(1, 3, 2);
scene.add(pointLight);
// --- Create the Rubik's Cube ---
const cubeGroup = new THREE.Group();
scene.add(cubeGroup);
// Colors for faces: standard Rubik's colors
const colors = {
white: 0xffffff,
yellow: 0xffdd00,
red: 0xff3333,
orange: 0xff8800,
blue: 0x2266ff,
green: 0x33cc33
};
// Size of each cubie
const size = 0.95;
const gap = 0.05;
const totalSize = size + gap;
// Materials for each face (order: right, left, up, down, front, back)
// We'll create 6 materials per cubie, but only visible on the outer faces.
// For simplicity, we'll create cubies with 6 colored materials, but set the inner faces to black/dark.
// Actually, to make it look clean, we'll use a single material with an array for each face.
// But for performance and simplicity, we'll create cubies with 6 materials and set the inner ones to dark.
// However, a more elegant approach: use a BoxGeometry with 6 materials, and for inner faces use a dark color.
// But since we want the classic look, we'll assign colors only to the outer faces.
// We'll create 26 cubies (skip center? no, include all 27 for simplicity, but center is hidden anyway)
// Actually, we need 26 visible cubies (center is hidden). We'll create all 27 and hide the center.
const cubies = [];
const cubeSize = 3;
// Helper to create a cubie with proper face colors
function createCubie(x, y, z) {
// Determine which faces are exposed (on the outer layer)
const isOuterX = (x === 1 || x === -1);
const isOuterY = (y === 1 || y === -1);
const isOuterZ = (z === 1 || z === -1);
// Materials: order in Three.js BoxGeometry: +x, -x, +y, -y, +z, -z
const materials = [
new THREE.MeshStandardMaterial({ color: isOuterX && x === 1 ? colors.red : 0x222222, roughness: 0.3, metalness: 0.1 }), // +x
new THREE.MeshStandardMaterial({ color: isOuterX && x === -1 ? colors.orange : 0x222222, roughness: 0.3, metalness: 0.1 }), // -x
new THREE.MeshStandardMaterial({ color: isOuterY && y === 1 ? colors.white : 0x222222, roughness: 0.3, metalness: 0.1 }), // +y
new THREE.MeshStandardMaterial({ color: isOuterY && y === -1 ? colors.yellow : 0x222222, roughness: 0.3, metalness: 0.1 }), // -y
new THREE.MeshStandardMaterial({ color: isOuterZ && z === 1 ? colors.green : 0x222222, roughness: 0.3, metalness: 0.1 }), // +z
new THREE.MeshStandardMaterial({ color: isOuterZ && z === -1 ? colors.blue : 0x222222, roughness: 0.3, metalness: 0.1 }) // -z
];
const geometry = new THREE.BoxGeometry(size, size, size);
const cubie = new THREE.Mesh(geometry, materials);
// Position
cubie.position.set(x * totalSize, y * totalSize, z * totalSize);
// Enable shadows
cubie.castShadow = true;
cubie.receiveShadow = true;
// Store original position for potential reset
cubie.userData = { x, y, z };
return cubie;
}
// Create all cubies (including center, but we'll hide it)
for (let x = -1; x <= 1; x++) {
for (let y = -1; y <= 1; y++) {
for (let z = -1; z <= 1; z++) {
// Skip the very center (0,0,0) - it's invisible anyway
if (x === 0 && y === 0 && z === 0) continue;
const cubie = createCubie(x, y, z);
cubeGroup.add(cubie);
cubies.push(cubie);
}
}
}
// Add a subtle wireframe or edge lines? Not necessary, but we can add a slight emissive glow.
// --- Animation State ---
let moveQueue = []; // Queue of moves to execute
let isAnimating = false;
let moveCount = 0;
const moveCounterElement = document.getElementById('move-counter');
// Define move types: axis, layer, angle (90 deg)
// We'll implement standard moves: U, D, L, R, F, B (and their inverses)
// For simplicity, we'll just do random moves on axes with layer -1, 0, 1
// Function to update move counter
function updateMoveCounter() {
moveCounterElement.textContent = `Moves: ${moveCount}`;
}
// Function to get the cubies in a specific layer
function getCubiesInLayer(axis, layer) {
const result = [];
const threshold = 0.1;
cubies.forEach(cubie => {
const pos = cubie.position;
if (axis === 'x') {
if (Math.abs(pos.x - layer * totalSize) < threshold) result.push(cubie);
} else if (axis === 'y') {
if (Math.abs(pos.y - layer * totalSize) < threshold) result.push(cubie);
} else if (axis === 'z') {
if (Math.abs(pos.z - layer * totalSize) < threshold) result.push(cubie);
}
});
return result;
}
// Function to rotate a layer around an axis
function rotateLayer(axis, layer, angle, callback) {
const cubiesInLayer = getCubiesInLayer(axis, layer);
if (cubiesInLayer.length === 0) {
if (callback) callback();
return;
}
// Create a pivot group
const pivot = new THREE.Group();
scene.add(pivot);
// Attach cubies to pivot
cubiesInLayer.forEach(cubie => {
pivot.attach(cubie);
});
// Determine rotation axis
let rotAxis;
if (axis === 'x') rotAxis = new THREE.Vector3(1, 0, 0);
else if (axis === 'y') rotAxis = new THREE.Vector3(0, 1, 0);
else if (axis === 'z') rotAxis = new THREE.Vector3(0, 0, 1);
// Animate rotation
const duration = 300; // ms
const startTime = performance.now();
const startRotation = pivot.rotation.clone();
const targetRotation = startRotation.clone();
// Add angle to the specific axis
if (axis === 'x') targetRotation.x += angle;
else if (axis === 'y') targetRotation.y += angle;
else if (axis === 'z') targetRotation.z += angle;
function animateRotation(time) {
const elapsed = time - startTime;
const progress = Math.min(elapsed / duration, 1);
// Ease in-out
const eased = progress < 0.5 ? 2 * progress * progress : 1 - Math.pow(-2 * progress + 2, 2) / 2;
// Interpolate rotation
pivot.rotation.x = startRotation.x + (targetRotation.x - startRotation.x) * eased;
pivot.rotation.y = startRotation.y + (targetRotation.y - startRotation.y) * eased;
pivot.rotation.z = startRotation.z + (targetRotation.z - startRotation.z) * eased;
if (progress < 1) {
requestAnimationFrame(animateRotation);
} else {
// Finalize: detach cubies from pivot and add back to cubeGroup
cubiesInLayer.forEach(cubie => {
cubeGroup.attach(cubie);
});
scene.remove(pivot);
// Snap rotations to nearest 90 deg to avoid drift
cubiesInLayer.forEach(cubie => {
cubie.rotation.x = Math.round(cubie.rotation.x / (Math.PI/2)) * (Math.PI/2);
cubie.rotation.y = Math.round(cubie.rotation.y / (Math.PI/2)) * (Math.PI/2);
cubie.rotation.z = Math.round(cubie.rotation.z / (Math.PI/2)) * (Math.PI/2);
});
if (callback) callback();
}
}
requestAnimationFrame(animateRotation);
}
// Function to perform a random move
function performRandomMove(callback) {
const axes = ['x', 'y', 'z'];
const layers = [-1, 0, 1];
const angles = [Math.PI/2, -Math.PI/2];
const axis = axes[Math.floor(Math.random() * axes.length)];
const layer = layers[Math.floor(Math.random() * layers.length)];
const angle = angles[Math.floor(Math.random() * angles.length)];
moveCount++;
updateMoveCounter();
rotateLayer(axis, layer, angle, callback);
}
// Scramble function
function scramble() {
if (isAnimating) return;
isAnimating = true;
const numMoves = 15 + Math.floor(Math.random() * 6); // 15-20
let movesDone = 0;
function doNextMove() {
if (movesDone >= numMoves) {
isAnimating = false;
return;
}
movesDone++;
performRandomMove(() => {
// Small delay between moves for visual clarity
setTimeout(doNextMove, 50);
});
}
doNextMove();
}
// Solve function: reverse the scramble moves
// Since we don't track the exact moves, we'll just do a reverse scramble (random moves)
// But to actually solve, we need to reverse the exact moves. For simplicity, we'll just do a "reverse" scramble.
// However, the requirement says "reverses the scramble moves automatically".
// To do this properly, we need to store the moves. Let's implement that.
// We'll store the moves performed during scramble.
let moveHistory = [];
// Override performRandomMove to record moves
function performRandomMoveRecorded(callback) {
const axes = ['x', 'y', 'z'];
const layers = [-1, 0, 1];
const angles = [Math.PI/2, -Math.PI/2];
const axis = axes[Math.floor(Math.random() * axes.length)];
const layer = layers[Math.floor(Math.random() * layers.length)];
const angle = angles[Math.floor(Math.random() * angles.length)];
moveHistory.push({ axis, layer, angle });
moveCount++;
updateMoveCounter();
rotateLayer(axis, layer, angle, callback);
}
// Scramble using recorded moves
function scrambleRecorded() {
if (isAnimating) return;
isAnimating = true;
moveHistory = [];
const numMoves = 15 + Math.floor(Math.random() * 6);
let movesDone = 0;
function doNextMove() {
if (movesDone >= numMoves) {
isAnimating = false;
return;
}
movesDone++;
performRandomMoveRecorded(() => {
setTimeout(doNextMove, 50);
});
}
doNextMove();
}
// Solve function: reverse the moves
function solve() {
if (isAnimating) return;
if (moveHistory.length === 0) {
// If no history, just do nothing or maybe scramble? We'll just return.
return;
}
isAnimating = true;
// Reverse the moves
const reverseMoves = moveHistory.slice().reverse().map(m => ({
axis: m.axis,
layer: m.layer,
angle: -m.angle
}));
// Clear history after solving
moveHistory = [];
let movesDone = 0;
const totalMoves = reverseMoves.length;
function doNextMove() {
if (movesDone >= totalMoves) {
isAnimating = false;
moveCount = 0;
updateMoveCounter();
return;
}
const move = reverseMoves[movesDone];
movesDone++;
moveCount++;
updateMoveCounter();
rotateLayer(move.axis, move.layer, move.angle, () => {
setTimeout(doNextMove, 50);
});
}
doNextMove();
}
// --- UI Event Listeners ---
document.getElementById('scramble-btn').addEventListener('click', () => {
if (!isAnimating) {
// Reset move count
moveCount = 0;
updateMoveCounter();
scrambleRecorded();
}
});
document.getElementById('solve-btn').addEventListener('click', () => {
if (!isAnimating) {
solve();
}
});
// Keyboard shortcuts
window.addEventListener('keydown', (e) => {
if (e.key === 'r' || e.key === 'R') {
if (!isAnimating) {
moveCount = 0;
updateMoveCounter();
scrambleRecorded();
}
}
if (e.key === 's' || e.key === 'S') {
if (!isAnimating) {
solve();
}
}
});
// --- Animation Loop ---
function animate() {
requestAnimationFrame(animate);
// Update controls
controls.update();
// Render
renderer.render(scene, camera);
}
animate();
// --- Resize Handler ---
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
// --- Initial move counter ---
updateMoveCounter();
// --- Add a subtle starfield or background effect? Not necessary, but we can add some particles.
// Optional: Add a few floating particles for ambiance
const particleGeo = new THREE.BufferGeometry();
const particleCount = 500;
const posArray = new Float32Array(particleCount * 3);
for (let i = 0; i < particleCount * 3; i += 3) {
posArray[i] = (Math.random() - 0.5) * 40;
posArray[i+1] = (Math.random() - 0.5) * 40;
posArray[i+2] = (Math.random() - 0.5) * 40;
}
particleGeo.setAttribute('position', new THREE.BufferAttribute(posArray, 3));
const particleMat = new THREE.PointsMaterial({
color: 0x88aaff,
size: 0.05,
transparent: true,
opacity: 0.6
});
const particles = new THREE.Points(particleGeo, particleMat);
scene.add(particles);
// Add a subtle ground reflection? Not needed.
// Add a small glow around the cube? Could use a point light.
const glowLight = new THREE.PointLight(0x4466ff, 0.3, 8);
glowLight.position.set(0, 0, 0);
scene.add(glowLight);
console.log('Rubik\'s Cube ready!');
</script>
</body>
</html>
Entre para ver o prompt completo
Continuar com:
Ao entrar, você concorda com nossos Termos de uso e Política de privacidade
Uso
Este prompt foi projetado para uso com coding. Copie o conteúdo acima e cole na sua ferramenta de IA preferida.
Para melhores resultados, personalize os marcadores (indicados por colchetes ou maiúsculas) com seus requisitos específicos.
Referências
- Categoria: Prompts de coding
- Fonte: https://x.com/eyishazyer/status/2075850010000764951
Discussão
0 comentários