新建会话
Ctrl
K
Kimi+
历史会话

来自分享的会话

Create a 3D Rubik's Cube in the browser using Three.js or p5.js. Allow the user to rotate different layers of the cube with the arrow keys: Up/Down/Left/Right. Include scramble and reset functionality. Add realistic 3D animations and make the controls responsive to keyboard input. Write the entire code in one HTML file.
HTML
预览
复制
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>3D Rubik's Cube</title>
    <style>
        body {
            margin: 0;
            padding: 0;
            overflow: hidden;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
        }
        
        #info {
            position: absolute;
            top: 20px;
            width: 100%;
            text-align: center;
            color: white;
            font-size: 18px;
            font-weight: 300;
            text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
            pointer-events: none;
        }
        
        #controls {
            position: absolute;
            bottom: 30px;
            width: 100%;
            text-align: center;
            color: white;
            font-size: 16px;
            font-weight: 300;
            text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
        }
        
        button {
            margin: 0 10px;
            padding: 12px 24px;
            font-size: 16px;
            font-weight: 500;
            cursor: pointer;
            color: white;
            border: none;
            border-radius: 25px;
            transition: all 0.3s ease;
            box-shadow: 0 4px 15px rgba(0,0,0,0.2);
        }
        
        #scrambleBtn {
            background: linear-gradient(135deg, #ff6b6b, #ff8787);
        }
        
        #resetBtn {
            background: linear-gradient(135deg, #4ecdc4, #44a08d);
        }
        
        button:hover {
            transform: translateY(-2px);
            box-shadow: 0 6px 20px rgba(0,0,0,0.3);
        }
        
        button:active {
            transform: translateY(0);
            box-shadow: 0 2px 10px rgba(0,0,0,0.2);
        }
        
        #layerIndicator {
            position: absolute;
            top: 60px;
            left: 20px;
            color: white;
            font-size: 16px;
            text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
            background: rgba(0,0,0,0.2);
            padding: 10px 15px;
            border-radius: 10px;
            backdrop-filter: blur(10px);
        }
        
        .key-hint {
            display: inline-block;
            background: rgba(255,255,255,0.2);
            padding: 4px 8px;
            border-radius: 4px;
            margin: 0 2px;
            font-family: monospace;
        }
    </style>
</head>
<body>
    <div id="info">
        <div>Rubik's Cube 3D</div>
        <small>Use mouse to rotate view • Click arrows to rotate layers</small>
    </div>
    <div id="layerIndicator">Current Layer: <span id="currentLayer">Middle</span></div>
    <div id="controls">
        <button id="scrambleBtn">🔀 Scramble</button>
        <button id="resetBtn">🔄 Reset</button>
    </div>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/controls/OrbitControls.min.js"></script>
    <script>
        // Initialize Three.js
        const scene = new THREE.Scene();
        scene.background = new THREE.Color(0x222244);
        scene.fog = new THREE.Fog(0x222244, 10, 50);
        
        const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
        camera.position.set(5, 5, 5);
        
        const renderer = new THREE.WebGLRenderer({ antialias: true });
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.shadowMap.enabled = true;
        renderer.shadowMap.type = THREE.PCFSoftShadowMap;
        document.body.appendChild(renderer.domElement);
        
        // Add orbit controls
        const controls = new THREE.OrbitControls(camera, renderer.domElement);
        controls.enableDamping = true;
        controls.dampingFactor = 0.05;
        
        // Add enhanced lighting
        const ambientLight = new THREE.AmbientLight(0x404040, 0.5);
        scene.add(ambientLight);
        
        const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
        directionalLight.position.set(5, 10, 5);
        directionalLight.castShadow = true;
        directionalLight.shadow.camera.near = 0.1;
        directionalLight.shadow.camera.far = 50;
        directionalLight.shadow.camera.left = -10;
        directionalLight.shadow.camera.right = 10;
        directionalLight.shadow.camera.top = 10;
        directionalLight.shadow.camera.bottom = -10;
        scene.add(directionalLight);
        
        const pointLight = new THREE.PointLight(0xffffff, 0.5);
        pointLight.position.set(-5, 5, -5);
        scene.add(pointLight);
        
        // Cube colors
        const colors = {
            front: 0xff0000,   // Red
            back: 0xffa500,    // Orange
            up: 0xffffff,      // White
            down: 0xffff00,    // Yellow
            right: 0x00ff00,   // Green
            left: 0x0000ff,    // Blue
            inner: 0x333333    // Dark gray for inner parts
        };
        
        // Create Rubik's Cube
        const cubeSize = 3;
        const cubeSpacing = 1.05;
        const cubes = [];
        const cubeGroup = new THREE.Group();
        scene.add(cubeGroup);
        
        // Create small cubes
        function createCube(x, y, z) {
            const geometry = new THREE.BoxGeometry(1, 1, 1);
            const materials = [];
            
            // Create materials for each face
            for (let i = 0; i < 6; i++) {
                let color = colors.inner;
                
                // Determine which face this is and set appropriate color
                if (i === 0 && z === 1) color = colors.front;      // Front
                if (i === 1 && z === -1) color = colors.back;      // Back
                if (i === 2 && y === 1) color = colors.up;         // Top
                if (i === 3 && y === -1) color = colors.down;      // Bottom
                if (i === 4 && x === 1) color = colors.right;      // Right
                if (i === 5 && x === -1) color = colors.left;      // Left
                
                materials.push(new THREE.MeshPhongMaterial({ 
                    color: color,
                    shininess: 100,
                    specular: 0x111111
                }));
            }
            
            const cube = new THREE.Mesh(geometry, materials);
            cube.position.set(x * cubeSpacing, y * cubeSpacing, z * cubeSpacing);
            cube.castShadow = true;
            cube.receiveShadow = true;
            cube.originalPosition = { x, y, z };
            cube.userData = { x, y, z };
            
            return cube;
        }
        
        // Create all 27 small cubes
        for (let x = -1; x <= 1; x++) {
            for (let y = -1; y <= 1; y++) {
                for (let z = -1; z <= 1; z++) {
                    if (x !== 0 || y !== 0 || z !== 0) {  // Skip the center cube
                        const cube = createCube(x, y, z);
                        cubes.push(cube);
                        cubeGroup.add(cube);
                    }
                }
            }
        }
        
        // Animation variables
        let isAnimating = false;
        let currentLayer = 'middle';  // 'top', 'middle', 'bottom', 'left', 'center', 'right'
        let rotationAxis = 'y';  // 'x', 'y', 'z'
        let rotationGroup = new THREE.Group();
        scene.add(rotationGroup);
        
        // Functions to get cubes in specific layers
        function getLayerCubes(layer, axis) {
            return cubes.filter(cube => {
                const pos = cube.position[axis] / cubeSpacing;
                switch (layer) {
                    case 'top': return pos > 0.5;
                    case 'middle': return Math.abs(pos) < 0.5;
                    case 'bottom': return pos < -0.5;
                    case 'left': return pos < -0.5;
                    case 'center': return Math.abs(pos) < 0.5;
                    case 'right': return pos > 0.5;
                    default: return false;
                }
            });
        }
        
        // Rotate a layer
        function rotateLayer(layer, axis, direction) {
            if (isAnimating) return;
            isAnimating = true;
            
            // Get cubes in the layer
            let layerCubes;
            switch (axis) {
                case 'x':
                    layerCubes = getLayerCubes(layer, 'x');
                    break;
                case 'y':
                    layerCubes = getLayerCubes(layer, 'y');
                    break;
                case 'z':
                    layerCubes = getLayerCubes(layer, 'z');
                    break;
            }
            
            // Move cubes to rotation group
            layerCubes.forEach(cube => {
                cubeGroup.remove(cube);
                rotationGroup.add(cube);
            });
            
            // Animate rotation
            const rotationAngle = Math.PI / 2 * direction;
            const startTime = Date.now();
            const duration = 300; // milliseconds
            
            function animateRotation() {
                const elapsed = Date.now() - startTime;
                const progress = Math.min(elapsed / duration, 1);
                
                // Smooth easing
                const easeProgress = 1 - Math.pow(1 - progress, 3);
                
                if (axis === 'x') {
                    rotationGroup.rotation.x = rotationAngle * easeProgress;
                } else if (axis === 'y') {
                    rotationGroup.rotation.y = rotationAngle * easeProgress;
                } else if (axis === 'z') {
                    rotationGroup.rotation.z = rotationAngle * easeProgress;
                }
                
                if (progress < 1) {
                    requestAnimationFrame(animateRotation);
                } else {
                    // Rotation complete, update cube positions and colors
                    layerCubes.forEach(cube => {
                        // Remove from rotation group and add back to cube group
                        rotationGroup.remove(cube);
                        cubeGroup.add(cube);
                        
                        // Snap to final rotation
                        if (axis === 'x') {
                            rotationGroup.rotation.x = rotationAngle;
                        } else if (axis === 'y') {
                            rotationGroup.rotation.y = rotationAngle;
                        } else if (axis === 'z') {
                            rotationGroup.rotation.z = rotationAngle;
                        }
                        
                        // Update the cube's world matrix
                        cube.updateMatrixWorld();
                        
                        // Get the cube's world position
                        const worldPos = new THREE.Vector3();
                        cube.getWorldPosition(worldPos);
                        
                        // Round the position to snap to grid
                        worldPos.x = Math.round(worldPos.x / cubeSpacing) * cubeSpacing;
                        worldPos.y = Math.round(worldPos.y / cubeSpacing) * cubeSpacing;
                        worldPos.z = Math.round(worldPos.z / cubeSpacing) * cubeSpacing;
                        
                        // Set the new position
                        cube.position.copy(worldPos);
                        
                        // Rotate the cube's user data to reflect the new orientation
                        const oldData = { ...cube.userData };
                        if (axis === 'x') {
                            cube.userData.y = direction > 0 ? oldData.z : -oldData.z;
                            cube.userData.z = direction > 0 ? -oldData.y : oldData.y;
                        } else if (axis === 'y') {
                            cube.userData.x = direction > 0 ? -oldData.z : oldData.z;
                            cube.userData.z = direction > 0 ? oldData.x : -oldData.x;
                        } else if (axis === 'z') {
                            cube.userData.x = direction > 0 ? oldData.y : -oldData.y;
                            cube.userData.y = direction > 0 ? -oldData.x : oldData.x;
                        }
                    });
                    
                    // Reset rotation group
                    rotationGroup.rotation.set(0, 0, 0);
                    isAnimating = false;
                }
            }
            
            animateRotation();
        }
        
        // Reset the cube to solved state
        function resetCube() {
            if (isAnimating) return;
            
            // Remove all cubes
            while (cubes.length > 0) {
                const cube = cubes.pop();
                cubeGroup.remove(cube);
            }
            
            // Create new cubes
            for (let x = -1; x <= 1; x++) {
                for (let y = -1; y <= 1; y++) {
                    for (let z = -1; z <= 1; z++) {
                        if (x !== 0 || y !== 0 || z !== 0) {
                            const cube = createCube(x, y, z);
                            cubes.push(cube);
                            cubeGroup.add(cube);
                        }
                    }
                }
            }
        }
        
        // Scramble the cube
        function scrambleCube() {
            if (isAnimating) return;
            
            const layers = ['top', 'middle', 'bottom'];
            const axes = ['x', 'y', 'z'];
            const directions = [1, -1];
            
            let moveCount = 0;
            const totalMoves = 20;
            
            function makeRandomMove() {
                if (moveCount >= totalMoves) return;
                
                const randomLayer = layers[Math.floor(Math.random() * layers.length)];
                const randomAxis = axes[Math.floor(Math.random() * axes.length)];
                const randomDirection = directions[Math.floor(Math.random() * directions.length)];
                
                rotateLayer(randomLayer, randomAxis, randomDirection);
                
                moveCount++;
                setTimeout(makeRandomMove, 350);
            }
            
            makeRandomMove();
        }
        
        // Handle keyboard input
        document.addEventListener('keydown', (event) => {
            if (isAnimating) return;
            
            switch (event.key) {
                case 'ArrowUp':
                    if (rotationAxis === 'y') rotateLayer(currentLayer, 'x', -1);
                    else if (rotationAxis === 'x') rotateLayer(currentLayer, 'y', 1);
                    else rotateLayer(currentLayer, 'y', 1);
                    break;
                case 'ArrowDown':
                    if (rotationAxis === 'y') rotateLayer(currentLayer, 'x', 1);
                    else if (rotationAxis === 'x') rotateLayer(currentLayer, 'y', -1);
                    else rotateLayer(currentLayer, 'y', -1);
                    break;
                case 'ArrowLeft':
                    if (rotationAxis === 'y') rotateLayer(currentLayer, 'y', -1);
                    else if (rotationAxis === 'x') rotateLayer(currentLayer, 'x', -1);
                    else rotateLayer(currentLayer, 'x', -1);
                    break;
                case 'ArrowRight':
                    if (rotationAxis === 'y') rotateLayer(currentLayer, 'y', 1);
                    else if (rotationAxis === 'x') rotateLayer(currentLayer, 'x', 1);
                    else rotateLayer(currentLayer, 'x', 1);
                    break;
                case '1':
                    currentLayer = 'top';
                    rotationAxis = 'y';
                    document.getElementById('currentLayer').textContent = 'Top';
                    break;
                case '2':
                    currentLayer = 'middle';
                    rotationAxis = 'y';
                    document.getElementById('currentLayer').textContent = 'Middle';
                    break;
                case '3':
                    currentLayer = 'bottom';
                    rotationAxis = 'y';
                    document.getElementById('currentLayer').textContent = 'Bottom';
                    break;
                case '4':
                    currentLayer = 'left';
                    rotationAxis = 'x';
                    document.getElementById('currentLayer').textContent = 'Left';
                    break;
                case '5':
                    currentLayer = 'center';
                    rotationAxis = 'x';
                    document.getElementById('currentLayer').textContent = 'Center';
                    break;
                case '6':
                    currentLayer = 'right';
                    rotationAxis = 'x';
                    document.getElementById('currentLayer').textContent = 'Right';
                    break;
                case '7':
                    currentLayer = 'left';
                    rotationAxis = 'z';
                    document.getElementById('currentLayer').textContent = 'Front';
                    break;
                case '8':
                    currentLayer = 'center';
                    rotationAxis = 'z';
                    document.getElementById('currentLayer').textContent = 'Middle';
                    break;
                case '9':
                    currentLayer = 'right';
                    rotationAxis = 'z';
                    document.getElementById('currentLayer').textContent = 'Back';
                    break;
            }
        });
        
        // Button event listeners
        document.getElementById('scrambleBtn').addEventListener('click', scrambleCube);
        document.getElementById('resetBtn').addEventListener('click', resetCube);
        
        // Handle window resize
        window.addEventListener('resize', () => {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        });
        
        // Animation loop
        function animate() {
            requestAnimationFrame(animate);
            controls.update();
            renderer.render(scene, camera);
        }
        
        animate();
    </script>
</body>
</html>