🎮ArcadeLab

我的房间 - 极致精细 3D 数字孪生

by EpicCoder88
676 lines33.9 KB🛠️ Three.js (3D graphics)
▶ Play
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <title>我的房间 - 极致精细 3D 数字孪生</title>
    <style>
        body { margin: 0; overflow: hidden; background-color: #1a1a1a; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; }
        canvas { display: block; }
        #info {
            position: absolute; bottom: 20px; left: 20px;
            color: rgba(255,255,255,0.85); font-size: 13px;
            background: rgba(0,0,0,0.6); padding: 12px 24px; border-radius: 12px;
            pointer-events: none; line-height: 1.6; backdrop-filter: blur(5px);
            border: 1px solid rgba(255,255,255,0.1);
        }
    </style>
</head>
<body>
    <div id="info">
        🖱️ 鼠标左键旋转 | 滚轮缩放 | 右键平移<br>
        ✨ 极致精细版:加入动态纹理、复杂光影与生活杂物<br>
        💡 提示:靠近床头柜,看看红色台灯发出的真实暖光
    </div>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.js"></script>

    <script>
        // ==========================================
        // 1. 初始化场景与渲染器
        // ==========================================
        const scene = new THREE.Scene();
        scene.background = new THREE.Color(0xeef2f5);
        scene.fog = new THREE.Fog(0xeef2f5, 30, 80); // 增加景深感

        const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 100);
        camera.position.set(14, 12, 26);
        camera.lookAt(0, 5, 0);

        const renderer = new THREE.WebGLRenderer({ antialias: true });
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
        renderer.shadowMap.enabled = true;
        renderer.shadowMap.type = THREE.PCFSoftShadowMap;
        renderer.toneMapping = THREE.ACESFilmicToneMapping;
        renderer.toneMappingExposure = 1.0;
        renderer.outputEncoding = THREE.sRGBEncoding;
        document.body.appendChild(renderer.domElement);

        const controls = new THREE.OrbitControls(camera, renderer.domElement);
        controls.target.set(0, 5, 0);
        controls.enableDamping = true;
        controls.dampingFactor = 0.05;
        controls.maxPolarAngle = Math.PI / 2 + 0.1; // 允许稍微看到一点地板边缘

        // ==========================================
        // 2. 精细光照系统 (自然光 + 台灯暖光)
        // ==========================================
        // 环境光
        scene.add(new THREE.AmbientLight(0xffffff, 0.4));
        
        // 半球光模拟漫反射
        const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444, 0.4);
        hemiLight.position.set(0, 20, 0);
        scene.add(hemiLight);

        // 主平行光 (模拟窗外阳光)
        const dirLight = new THREE.DirectionalLight(0xfff8e7, 1.2);
        dirLight.position.set(20, 25, 15);
        dirLight.castShadow = true;
        dirLight.shadow.mapSize.width = 2048;
        dirLight.shadow.mapSize.height = 2048;
        dirLight.shadow.camera.near = 1;
        dirLight.shadow.camera.far = 60;
        dirLight.shadow.camera.left = -25;
        dirLight.shadow.camera.right = 25;
        dirLight.shadow.camera.top = 25;
        dirLight.shadow.camera.bottom = -25;
        dirLight.shadow.bias = -0.0005;
        scene.add(dirLight);

        // 床头柜红色台灯的暖黄点光源 (精细氛围)
        const lampLight = new THREE.PointLight(0xffaa55, 1.5, 8, 2);
        lampLight.position.set(11.5, 5, 9.5);
        lampLight.castShadow = true;
        lampLight.shadow.mapSize.width = 1024;
        lampLight.shadow.mapSize.height = 1024;
        scene.add(lampLight);

        // ==========================================
        // 3. 高级材质纹理生成器 (Canvas)
        // ==========================================
        // 木纹生成
        function createWoodTexture() {
            const canvas = document.createElement('canvas');
            canvas.width = 512; canvas.height = 512;
            const ctx = canvas.getContext('2d');
            ctx.fillStyle = '#8b5a2b'; ctx.fillRect(0, 0, 512, 512);
            ctx.strokeStyle = '#5c3a1a'; ctx.lineWidth = 2;
            for(let i=0; i<50; i++) {
                ctx.beginPath();
                ctx.moveTo(0, Math.random() * 512);
                ctx.bezierCurveTo(128, Math.random() * 512, 384, Math.random() * 512, 512, Math.random() * 512);
                ctx.stroke();
            }
            const tex = new THREE.CanvasTexture(canvas);
            tex.wrapS = THREE.RepeatWrapping; tex.wrapT = THREE.RepeatWrapping;
            return tex;
        }

        // 凉席纹理生成
        function createMatTexture() {
            const canvas = document.createElement('canvas');
            canvas.width = 256; canvas.height = 256;
            const ctx = canvas.getContext('2d');
            ctx.fillStyle = '#a05a38'; ctx.fillRect(0, 0, 256, 256);
            ctx.strokeStyle = '#7a4225'; ctx.lineWidth = 1;
            for (let i = 0; i <= 256; i += 10) {
                ctx.beginPath(); ctx.moveTo(i, 0); ctx.lineTo(i, 256); ctx.stroke();
                ctx.beginPath(); ctx.moveTo(0, i); ctx.lineTo(256, i); ctx.stroke();
            }
            const tex = new THREE.CanvasTexture(canvas);
            tex.wrapS = THREE.RepeatWrapping; tex.wrapT = THREE.RepeatWrapping;
            tex.repeat.set(6, 4);
            return tex;
        }

        // 墙面水渍纹理
        function createStainTexture() {
            const canvas = document.createElement('canvas');
            canvas.width = 512; canvas.height = 512;
            const ctx = canvas.getContext('2d');
            ctx.clearRect(0, 0, 512, 512);
            // 多层模糊模拟水渍渗透
            ctx.fillStyle = 'rgba(160, 130, 90, 0.3)';
            for(let i=0; i<30; i++) {
                ctx.beginPath();
                ctx.arc(256 + Math.random()*200 - 100, 256 + Math.random()*200 - 100, 30 + Math.random()*80, 0, Math.PI*2);
                ctx.fill();
            }
            return new THREE.CanvasTexture(canvas);
        }

        // 蝴蝶墙贴
        function createButterflyTexture() {
            const canvas = document.createElement('canvas');
            canvas.width = 512; canvas.height = 256;
            const ctx = canvas.getContext('2d');
            ctx.clearRect(0, 0, 512, 256);
            const colors = ['#ff6b6b', '#4ecdc4', '#ffe66d', '#a06cd5', '#ff9f43'];
            for(let i=0; i<8; i++) {
                const x = 50 + i * 60;
                const y = 50 + Math.random() * 150;
                ctx.fillStyle = colors[i % colors.length];
                // 简笔蝴蝶
                ctx.beginPath(); ctx.ellipse(x, y, 12, 25, Math.PI/4, 0, Math.PI*2); ctx.fill();
                ctx.beginPath(); ctx.ellipse(x, y, 12, 25, -Math.PI/4, 0, Math.PI*2); ctx.fill();
            }
            return new THREE.CanvasTexture(canvas);
        }

        const woodTexture = createWoodTexture();
        const matTexture = createMatTexture();
        const stainTexture = createStainTexture();
        const butterflyTexture = createButterflyTexture();

        // 通用材质工具
        const getMat = (color, roughness = 0.8, metalness = 0.0, map = null) => 
            new THREE.MeshStandardMaterial({ color, roughness, metalness, map });

        // 木纹材质
        const woodMat = getMat(0x8b5a2b, 0.7, 0.0, woodTexture);
        const woodDarkMat = getMat(0x654321, 0.8, 0.0, woodTexture);
        // 磨砂玻璃材质 (衣柜门)
        const frostedGlassMat = new THREE.MeshPhysicalMaterial({ 
            color: 0xcce0cc, transparent: true, opacity: 0.5, roughness: 0.2, transmission: 0.9 
        });

        // ==========================================
        // 4. 空间结构 (地板、墙壁、天花板、地脚线)
        // ==========================================
        // 地板
        const floor = new THREE.Mesh(new THREE.PlaneGeometry(40, 40), getMat(0xdcdfe3, 0.6, 0.1));
        floor.rotation.x = -Math.PI / 2; floor.receiveShadow = true; scene.add(floor);

        // 墙壁
        const wallMat = getMat(0xfdfaf7, 0.9);
        const wallThickness = 0.5;
        const roomW = 32, roomD = 32, roomH = 16;
        
        // 后墙 (z = -16)
        const backWall = new THREE.Mesh(new THREE.BoxGeometry(roomW, roomH, wallThickness), wallMat);
        backWall.position.set(0, roomH/2, -roomD/2); backWall.receiveShadow = true; scene.add(backWall);
        // 左墙 (x = -16)
        const leftWall = new THREE.Mesh(new THREE.BoxGeometry(wallThickness, roomH, roomD), wallMat);
        leftWall.position.set(-roomW/2, roomH/2, 0); leftWall.receiveShadow = true; scene.add(leftWall);
        // 右墙 (x = 16)
        const rightWall = new THREE.Mesh(new THREE.BoxGeometry(wallThickness, roomH, roomD), wallMat);
        rightWall.position.set(roomW/2, roomH/2, 0); rightWall.receiveShadow = true; scene.add(rightWall);
        // 天花板
        const ceiling = new THREE.Mesh(new THREE.PlaneGeometry(roomW, roomD), getMat(0xffffff, 1.0));
        ceiling.rotation.x = Math.PI / 2; ceiling.position.y = roomH; scene.add(ceiling);

        // 墙面水渍
        const stainMat = new THREE.MeshStandardMaterial({ map: stainTexture, transparent: true, opacity: 0.6, depthWrite: false });
        const stainBack = new THREE.Mesh(new THREE.PlaneGeometry(10, 8), stainMat);
        stainBack.position.set(8, 4, -15.7); stainBack.rotation.y = Math.PI; scene.add(stainBack);
        const stainRight = new THREE.Mesh(new THREE.PlaneGeometry(12, 10), stainMat);
        stainRight.position.set(15.7, 5, 2); stainRight.rotation.y = -Math.PI / 2; scene.add(stainRight);

        // 地脚线
        const baseboardMat = getMat(0x333333, 0.8);
        const bbBack = new THREE.Mesh(new THREE.BoxGeometry(roomW, 0.6, 0.2), baseboardMat);
        bbBack.position.set(0, 0.3, -15.9); scene.add(bbBack);
        const bbRight = new THREE.Mesh(new THREE.BoxGeometry(0.2, 0.6, roomD), baseboardMat);
        bbRight.position.set(15.9, 0.3, 0); scene.add(bbRight);

        // 墙面插座
        const socket = new THREE.Mesh(new THREE.BoxGeometry(0.8, 0.8, 0.1), getMat(0xffffff, 0.3));
        socket.position.set(10, 4, -15.7); scene.add(socket);

        // ==========================================
        // 5. 极致细节家具:左侧巨无霸衣柜
        // ==========================================
        const wardrobeGroup = new THREE.Group();
        const wW = 9, wH = 13, wD = 4.5;
        
        // 柜体框架
        const wBody = new THREE.Mesh(new THREE.BoxGeometry(wW, wH, wD), woodMat);
        wBody.position.y = wH/2; wBody.castShadow = true; wBody.receiveShadow = true;
        wardrobeGroup.add(wBody);

        // 顶柜
        const topCabinet = new THREE.Mesh(new THREE.BoxGeometry(wW, 3.5, wD), woodMat);
        topCabinet.position.y = wH - 1.75; topCabinet.castShadow = true; wardrobeGroup.add(topCabinet);
        
        // 顶柜把手
        for(let i = -3; i <= 3; i+=6) {
            const handle = new THREE.Mesh(new THREE.BoxGeometry(0.1, 1.2, 0.1), getMat(0xcccccc, 0.2, 0.8));
            handle.position.set(i, wH - 1.75, wD/2 + 0.05);
            wardrobeGroup.add(handle);
        }

        // 内部隔板
        for(let y = 3; y <= 9; y += 3) {
            const shelf = new THREE.Mesh(new THREE.BoxGeometry(wW - 0.4, 0.2, wD - 0.4), woodDarkMat);
            shelf.position.set(0, y, 0); wardrobeGroup.add(shelf);
        }
        const vertShelf = new THREE.Mesh(new THREE.BoxGeometry(0.2, wH, wD - 0.4), woodDarkMat);
        vertShelf.position.set(1.5, wH/2, 0); wardrobeGroup.add(vertShelf);

        // 挂满衣服 (加入衣架和斜度)
        const clothesColors = [0xffb6c1, 0xadd8e6, 0xffffff, 0xf5f5dc, 0x87ceeb, 0xffe4b5, 0xd3d3d3, 0xffdead, 0x9370db];
        for(let i=0; i<50; i++) {
            const clothMat = getMat(clothesColors[Math.floor(Math.random() * clothesColors.length)], 0.95);
            const clothH = 1.5 + Math.random() * 2.5;
            const clothW = 0.6 + Math.random() * 0.5;
            const cloth = new THREE.Mesh(new THREE.BoxGeometry(clothW, clothH, 0.15), clothMat);
            
            // 衣服位置
            const shelfY = Math.floor(i / 12) * 3 + 3;
            const posX = (Math.random() - 0.5) * 4 + 0.5;
            const posZ = (Math.random() - 0.5) * 2;
            cloth.position.set(posX, shelfY + 0.8, posZ);
            cloth.rotation.y = (Math.random() - 0.5) * 0.8;
            cloth.rotation.z = (Math.random() - 0.5) * 0.1;
            cloth.castShadow = true;
            wardrobeGroup.add(cloth);

            // 加上衣架 (金属细杆)
            const hanger = new THREE.Mesh(new THREE.BoxGeometry(clothW * 0.8, 0.05, 0.05), getMat(0xcccccc, 0.3, 0.8));
            hanger.position.set(posX, shelfY + 0.8 + clothH/2, posZ);
            hanger.rotation.y = cloth.rotation.y;
            wardrobeGroup.add(hanger);
        }

        // 推拉门 (带金属边框)
        const doorW = wW / 2;
        const doorLeft = new THREE.Mesh(new THREE.BoxGeometry(doorW, wH - 1, 0.2), frostedGlassMat);
        doorLeft.position.set(-doorW/2 + 0.1, (wH-1)/2, wD/2);
        wardrobeGroup.add(doorLeft);
        const doorRight = new THREE.Mesh(new THREE.BoxGeometry(doorW, wH - 1, 0.2), frostedGlassMat);
        doorRight.position.set(doorW/2 - 0.1, (wH-1)/2, wD/2);
        wardrobeGroup.add(doorRight);

        // 门框边缘
        const frameMat = getMat(0xaaaaaa, 0.3, 0.8);
        const doorFrames = [
            {w: 0.15, h: wH-1, d: 0.3, x: -wW/2+0.15, y: (wH-1)/2},
            {w: 0.15, h: wH-1, d: 0.3, x: wW/2-0.15, y: (wH-1)/2},
            {w: wW, h: 0.15, d: 0.3, x: 0, y: 0.15}
        ];
        doorFrames.forEach(f => {
            const frame = new THREE.Mesh(new THREE.BoxGeometry(f.w, f.h, f.d), frameMat);
            frame.position.set(f.x, f.y, wD/2); wardrobeGroup.add(frame);
        });

        wardrobeGroup.position.set(-12, 0, -8);
        scene.add(wardrobeGroup);

        // ==========================================
        // 6. 飘窗与窗户系统 (右侧深化)
        // ==========================================
        const winX = 15.7, winZ = 2;
        
        // 飘窗台面
        const sill = new THREE.Mesh(new THREE.BoxGeometry(3.5, 0.5, 13), getMat(0xffffff, 0.7));
        sill.position.set(winX - 1.75, 2, winZ); sill.receiveShadow = true; scene.add(sill);

        // 绿色毛绒垫 (加厚)
        const rug = new THREE.Mesh(new THREE.BoxGeometry(3.3, 0.2, 12.5), getMat(0x7ec850, 1.0));
        rug.position.set(winX - 1.75, 2.35, winZ); rug.receiveShadow = true; scene.add(rug);

        // 窗户框架 (绿色)
        const frameMatGreen = getMat(0x2e8b57, 0.6);
        const winFrameTop = new THREE.Mesh(new THREE.BoxGeometry(0.2, 0.3, 13), frameMatGreen);
        winFrameTop.position.set(winX, 8.5, winZ); scene.add(winFrameTop);
        const winFrameBottom = new THREE.Mesh(new THREE.BoxGeometry(0.2, 0.3, 13), frameMatGreen);
        winFrameBottom.position.set(winX, 2.5, winZ); scene.add(winFrameBottom);
        
        // 竖向窗格
        for(let i = -6; i <= 6; i+=3) {
            const bar = new THREE.Mesh(new THREE.BoxGeometry(0.2, 6, 0.2), frameMatGreen);
            bar.position.set(winX, 5.5, winZ + i); scene.add(bar);
        }
        // 横向窗格
        for(let i = 3.5; i <= 7.5; i+=2) {
            const bar = new THREE.Mesh(new THREE.BoxGeometry(0.2, 0.2, 13), frameMatGreen);
            bar.position.set(winX, i, winZ); scene.add(bar);
        }

        // 玻璃
        const glass = new THREE.Mesh(new THREE.PlaneGeometry(12.5, 6), new THREE.MeshPhysicalMaterial({ color: 0xaaccff, transparent: true, opacity: 0.1, roughness: 0.05, transmission: 0.95 }));
        glass.position.set(winX, 5.5, winZ); glass.rotation.y = -Math.PI / 2; scene.add(glass);

        // 防盗网
        const barMat = getMat(0xaaaaaa, 0.5, 0.8);
        for(let i = -6; i <= 6; i+=0.8) {
            const bar = new THREE.Mesh(new THREE.CylinderGeometry(0.03, 0.03, 6, 8), barMat);
            bar.position.set(winX - 0.15, 5.5, winZ + i); scene.add(bar);
        }

        // 飘窗上的衣物堆叠 (更逼真的褶皱)
        const quiltMat = getMat(0xbbccee, 0.95);
        const quilt1 = new THREE.Mesh(new THREE.BoxGeometry(2.2, 1.2, 4.5), quiltMat);
        quilt1.position.set(winX - 1.5, 3.1, winZ - 1.5); quilt1.rotation.set(0.1, 0.2, 0.1); quilt1.castShadow = true; scene.add(quilt1);
        
        const quilt2 = new THREE.Mesh(new THREE.BoxGeometry(2, 0.8, 3.5), getMat(0xe0e0e0, 0.95));
        quilt2.position.set(winX - 1.7, 2.9, winZ - 1.8); quilt2.rotation.set(-0.1, -0.3, 0.05); quilt2.castShadow = true; scene.add(quilt2);

        const pillow = new THREE.Mesh(new THREE.BoxGeometry(2, 0.9, 1.8), getMat(0xffee88, 0.95));
        pillow.position.set(winX - 1.5, 3.1, winZ + 4.5); pillow.rotation.set(0.1, 0.5, 0.1); pillow.castShadow = true; scene.add(pillow);

        // 窗帘帷幔 (带波浪形)
        const valanceMat = getMat(0xfdfaf7, 0.9);
        const valance = new THREE.Mesh(new THREE.BoxGeometry(0.6, 2.5, 13), valanceMat);
        valance.position.set(winX - 0.8, 11.5, winZ); scene.add(valance);
        
        const curtain = new THREE.Mesh(new THREE.PlaneGeometry(3.5, 13, 30, 30), valanceMat);
        const posAttr = curtain.geometry.attributes.position;
        for (let i = 0; i < posAttr.count; i++) {
            const y = posAttr.getY(i);
            posAttr.setZ(i, Math.sin(y * 2.5) * 0.4 + Math.cos(y * 1.5) * 0.2);
        }
        curtain.geometry.computeVertexNormals();
        curtain.position.set(winX - 1.2, 7.5, winZ - 5.5);
        curtain.rotation.y = -Math.PI / 2;
        scene.add(curtain);

        // ==========================================
        // 7. 书桌、电脑与办公区 (精细建模)
        // ==========================================
        const deskGroup = new THREE.Group();
        // 桌面 (加厚带边缘)
        const deskTop = new THREE.Mesh(new THREE.BoxGeometry(7, 0.3, 3), woodMat);
        deskTop.castShadow = true; deskTop.receiveShadow = true;
        deskGroup.add(deskTop);
        // 抽屉柜
        const drawerBody = new THREE.Mesh(new THREE.BoxGeometry(2.5, 4, 2.8), woodDarkMat);
        drawerBody.position.set(-2.5, -2.15, 0); drawerBody.castShadow = true; drawerBody.receiveShadow = true;
        deskGroup.add(drawerBody);
        // 抽屉把手
        for(let y = -1; y >= -3; y-=1.5) {
            const handle = new THREE.Mesh(new THREE.BoxGeometry(1, 0.08, 0.08), getMat(0xcccccc, 0.2, 0.8));
            handle.position.set(-2.5, y, 1.45); deskGroup.add(handle);
        }

        // 显示器 (带弧面背板)
        const monitorBase = new THREE.Mesh(new THREE.CylinderGeometry(0.6, 0.8, 0.2, 16), getMat(0x222222, 0.4));
        monitorBase.position.set(2, 0.25, 0); deskGroup.add(monitorBase);
        const monitorStand = new THREE.Mesh(new THREE.CylinderGeometry(0.08, 0.08, 1.2, 8), getMat(0x333333, 0.4));
        monitorStand.position.set(2, 0.8, 0); deskGroup.add(monitorStand);
        const screen = new THREE.Mesh(new THREE.BoxGeometry(2.5, 1.6, 0.1), getMat(0x111111, 0.2));
        screen.position.set(2, 1.8, 0); screen.castShadow = true; deskGroup.add(screen);
        // 屏幕反光面
        const screenFace = new THREE.Mesh(new THREE.PlaneGeometry(2.3, 1.4), getMat(0x2a2a2a, 0.1, 0.5));
        screenFace.position.set(2, 1.8, 0.06); deskGroup.add(screenFace);

        // 键盘和鼠标
        const keyboard = new THREE.Mesh(new THREE.BoxGeometry(1.8, 0.1, 0.6), getMat(0xdddddd, 0.5));
        keyboard.position.set(1.2, 0.2, 0.8); deskGroup.add(keyboard);
        // 键帽
        for(let kx=-0.7; kx<=0.7; kx+=0.15) {
            for(let kz=-0.2; kz<=0.2; kz+=0.15) {
                const key = new THREE.Mesh(new THREE.BoxGeometry(0.1, 0.05, 0.1), getMat(0x333333, 0.5));
                key.position.set(1.2+kx, 0.27, 0.8+kz); deskGroup.add(key);
            }
        }
        const mouse = new THREE.Mesh(new THREE.SphereGeometry(0.2, 16, 16), getMat(0x333333, 0.3));
        mouse.scale.set(1, 0.6, 1.3); mouse.position.set(2.5, 0.25, 0.8); deskGroup.add(mouse);

        // 电脑主机
        const pcTower = new THREE.Mesh(new THREE.BoxGeometry(1.2, 2.8, 2), getMat(0x1a1a1a, 0.3, 0.5));
        pcTower.position.set(3, -1.4, 0.5); pcTower.castShadow = true; deskGroup.add(pcTower);

        // 桌面上杂乱的物品 (笔筒、书本、水杯)
        const book = new THREE.Mesh(new THREE.BoxGeometry(1.5, 0.2, 1), getMat(0xaa4444, 0.8));
        book.position.set(-1, 0.25, -0.5); deskGroup.add(book);
        const cup = new THREE.Mesh(new THREE.CylinderGeometry(0.2, 0.2, 0.5, 16), getMat(0xffffff, 0.5));
        cup.position.set(0.5, 0.4, -0.8); deskGroup.add(cup);
        // 小盆栽
        const pot = new THREE.Mesh(new THREE.CylinderGeometry(0.25, 0.2, 0.3, 12), getMat(0x8b4513, 0.8));
        pot.position.set(-1.5, 0.3, -0.8); deskGroup.add(pot);
        const plant = new THREE.Mesh(new THREE.SphereGeometry(0.3, 8, 8), getMat(0x2e8b57, 0.9));
        plant.position.set(-1.5, 0.6, -0.8); deskGroup.add(plant);

        deskGroup.position.set(9, 2.5, -13);
        scene.add(deskGroup);

        // ==========================================
        // 8. 粉色转椅 (平滑曲面 + 滚轮细化)
        // ==========================================
        const chairGroup = new THREE.Group();
        const pinkMat = getMat(0xf4b6c2, 0.6);
        
        // 靠背 (使用圆柱体切割,更加圆润)
        const chairBackGeo = new THREE.CylinderGeometry(1.2, 1.2, 2.2, 32, 1, false, 0, Math.PI);
        const chairBack = new THREE.Mesh(chairBackGeo, pinkMat);
        chairBack.rotation.y = Math.PI; 
        chairBack.position.set(0, 1.2, -0.8);
        chairBack.scale.set(1, 1, 0.7); // 压扁一些
        chairBack.castShadow = true;
        chairGroup.add(chairBack);
        
        // 坐垫 (带边缘弧度)
        const cushionGeo = new THREE.BoxGeometry(2, 0.5, 2);
        const cushion = new THREE.Mesh(cushionGeo, pinkMat);
        cushion.position.set(0, 0, 0); cushion.castShadow = true;
        chairGroup.add(cushion);
        // 坐垫边缘修饰
        const cushionEdge = new THREE.Mesh(new THREE.BoxGeometry(2.1, 0.1, 2.1), getMat(0xe8a2b0, 0.6));
        cushionEdge.position.set(0, -0.2, 0); chairGroup.add(cushionEdge);

        // 气压杆
        const pole = new THREE.Mesh(new THREE.CylinderGeometry(0.15, 0.15, 1.5, 12), getMat(0xcccccc, 0.2, 0.8));
        pole.position.set(0, -0.8, 0); chairGroup.add(pole);
        
        // 五星脚底座
        const base = new THREE.Mesh(new THREE.CylinderGeometry(1.4, 1.4, 0.1, 32), getMat(0xffffff, 0.3));
        base.position.set(0, -1.5, 0); base.castShadow = true; chairGroup.add(base);
        
        // 滚轮 (细化每个轮的连接件)
        for(let i=0; i<5; i++) {
            const angle = (i / 5) * Math.PI * 2;
            const wheelArm = new THREE.Mesh(new THREE.BoxGeometry(0.6, 0.1, 0.1), getMat(0xffffff, 0.3));
            wheelArm.position.set(Math.cos(angle) * 0.7, -1.55, Math.sin(angle) * 0.7);
            wheelArm.rotation.y = -angle;
            chairGroup.add(wheelArm);
            
            const wheel = new THREE.Mesh(new THREE.CylinderGeometry(0.12, 0.12, 0.08, 16), getMat(0x333333, 0.5));
            wheel.rotation.z = Math.PI / 2;
            wheel.rotation.y = angle;
            wheel.position.set(Math.cos(angle) * 1.3, -1.6, Math.sin(angle) * 1.3);
            wheel.castShadow = true;
            chairGroup.add(wheel);
        }

        chairGroup.position.set(6.5, 2.5, -10);
        chairGroup.rotation.y = -0.2; // 稍微偏向书桌
        scene.add(chairGroup);

        // ==========================================
        // 9. 空调、风扇与地面小物件
        // ==========================================
        // 空调
        const acGroup = new THREE.Group();
        const acBody = new THREE.Mesh(new THREE.BoxGeometry(4.5, 1.3, 0.8), getMat(0xffffff, 0.4));
        acBody.castShadow = true; acGroup.add(acBody);
        const acVent = new THREE.Mesh(new THREE.BoxGeometry(4, 0.2, 0.1), getMat(0xcccccc, 0.2));
        acVent.position.set(0, -0.5, 0.4); acGroup.add(acVent);
        // 指示灯
        const acLight = new THREE.Mesh(new THREE.SphereGeometry(0.05, 8, 8), getMat(0x00ff00, 0.2));
        acLight.position.set(1.5, 0.3, 0.45); acGroup.add(acLight);
        acGroup.position.set(2, 12, -15.7); scene.add(acGroup);

        // 风扇 (网罩细化)
        const fanGroup = new THREE.Group();
        const fanBase = new THREE.Mesh(new THREE.CylinderGeometry(1.3, 1.3, 0.2, 32), getMat(0xeeeeee, 0.4));
        fanBase.position.y = 0.1; fanBase.castShadow = true; fanGroup.add(fanBase);
        const fanPole = new THREE.Mesh(new THREE.CylinderGeometry(0.1, 0.1, 4.5, 16), getMat(0xeeeeee, 0.4));
        fanPole.position.y = 2.25; fanPole.castShadow = true; fanGroup.add(fanPole);
        
        // 风扇头外壳
        const fanHead = new THREE.Mesh(new THREE.CylinderGeometry(1.5, 1.5, 0.7, 32), getMat(0xdddddd, 0.4));
        fanHead.position.y = 5; fanHead.rotation.x = Math.PI / 2; fanHead.castShadow = true; fanGroup.add(fanHead);
        
        // 立体网罩 (多个圆环)
        const ringMat = new THREE.MeshBasicMaterial({ color: 0x555555 });
        for(let r = 0.3; r <= 1.5; r+=0.3) {
            const ring = new THREE.Mesh(new THREE.TorusGeometry(r, 0.02, 8, 40), ringMat);
            ring.position.y = 5; ring.position.z = 0.35; // 在外壳前面一点
            ring.rotation.x = Math.PI / 2;
            fanGroup.add(ring);
        }
        // 中心电机
        const motor = new THREE.Mesh(new THREE.SphereGeometry(0.4, 16, 16), getMat(0x555555, 0.3));
        motor.position.set(0, 5, 0.4); fanGroup.add(motor);
        // 扇叶
        for(let i=0; i<4; i++) {
            const blade = new THREE.Mesh(new THREE.BoxGeometry(0.15, 1.2, 0.05), getMat(0xdddddd, 0.4));
            blade.position.y = 5; blade.position.z = 0.3;
            blade.rotation.z = (i / 4) * Math.PI * 2;
            fanGroup.add(blade);
        }

        fanGroup.position.set(-6, 0, -6);
        scene.add(fanGroup);

        // 地面杂物:蓝色呼啦圈
        const ringGeo = new THREE.TorusGeometry(1.2, 0.08, 16, 40);
        const ring = new THREE.Mesh(ringGeo, getMat(0x4a90e2, 0.5));
        ring.position.set(-10, 0.1, -1.5); ring.rotation.x = -Math.PI / 2; ring.castShadow = true;
        scene.add(ring);

        // 绿色收纳盒
        const greenBox = new THREE.Mesh(new THREE.BoxGeometry(1, 1.5, 0.8), getMat(0x2e8b57, 0.8));
        greenBox.position.set(-14, 0.75, -3); greenBox.castShadow = true; scene.add(greenBox);
        const greenBox2 = greenBox.clone(); greenBox2.position.set(-12.8, 0.75, -3); scene.add(greenBox2);

        // 垃圾桶
        const trash = new THREE.Mesh(new THREE.CylinderGeometry(0.8, 0.6, 1.6, 16), getMat(0x8fa3ad, 0.7));
        trash.position.set(14, 0.8, -5); trash.castShadow = true; scene.add(trash);
        // 垃圾袋边缘
        const trashBag = new THREE.Mesh(new THREE.CylinderGeometry(0.85, 0.85, 0.1, 16), getMat(0x9b59b6, 0.9));
        trashBag.position.set(14, 1.6, -5); scene.add(trashBag);

        // ==========================================
        // 10. 床铺、床头细节与台灯 (核心生活区)
        // ==========================================
        // 床架
        const bedMat = new THREE.MeshStandardMaterial({ color: 0x9e5a38, roughness: 0.9, map: matTexture });
        const bedBase = new THREE.Mesh(new THREE.BoxGeometry(24, 0.8, 14), bedMat);
        bedBase.position.set(-2, 0.4, 15);
        bedBase.receiveShadow = true; scene.add(bedBase);
        
        // 床铺白边
        const bedEdgeMat = getMat(0xd3d3d3, 0.8);
        const bedFrontEdge = new THREE.Mesh(new THREE.BoxGeometry(24.2, 0.2, 0.5), bedEdgeMat);
        bedFrontEdge.position.set(-2, 0.9, 22.25); scene.add(bedFrontEdge);
        const bedLeftEdge = new THREE.Mesh(new THREE.BoxGeometry(0.5, 0.2, 14), bedEdgeMat);
        bedLeftEdge.position.set(-14.1, 0.9, 15); scene.add(bedLeftEdge);

        // 白色床头板
        const headboard = new THREE.Mesh(new THREE.BoxGeometry(20, 4, 0.6), getMat(0xffffff, 0.6));
        headboard.position.set(-2, 2.5, 21.7); headboard.receiveShadow = true; scene.add(headboard);

        // 蝴蝶墙贴
        const butterflyMat = new THREE.MeshStandardMaterial({ map: butterflyTexture, transparent: true, depthWrite: false });
        const butterflyPlane = new THREE.Mesh(new THREE.PlaneGeometry(16, 5), butterflyMat);
        butterflyPlane.position.set(-2, 6.5, 21.5);
        butterflyPlane.rotation.y = Math.PI;
        scene.add(butterflyPlane);

        // 床头柜与抽屉
        const nightstand = new THREE.Mesh(new THREE.BoxGeometry(3, 4, 3), getMat(0xf0f0f0, 0.8));
        nightstand.position.set(12, 2, 10);
        nightstand.castShadow = true; nightstand.receiveShadow = true;
        scene.add(nightstand);
        // 抽屉拉出
        const drawerFront = new THREE.Mesh(new THREE.BoxGeometry(2.8, 1.5, 0.2), getMat(0xe0e0e0, 0.8));
        drawerFront.position.set(12, 1.2, 11.5);
        scene.add(drawerFront);
        // 抽屉里的物品
        const tissueBox = new THREE.Mesh(new THREE.BoxGeometry(1, 0.6, 0.5), getMat(0xd8bfd8, 0.9));
        tissueBox.position.set(11.5, 1.5, 10.8); scene.add(tissueBox);
        const tissuePack = new THREE.Mesh(new THREE.BoxGeometry(0.6, 0.4, 0.4), getMat(0xadd8e6, 0.9));
        tissuePack.position.set(12.5, 1.4, 11.1); scene.add(tissuePack);

        // 红色折叠台灯 (极其精细)
        const lampGroup = new THREE.Group();
        const lampBase = new THREE.Mesh(new THREE.CylinderGeometry(0.8, 0.8, 0.25, 16), getMat(0xff3333, 0.4));
        lampBase.position.y = 0.125; lampGroup.add(lampBase);
        // 底座电源线
        const cable = new THREE.Mesh(new THREE.TorusGeometry(0.5, 0.05, 8, 20, Math.PI), getMat(0x333333, 0.6));
        cable.position.set(0, 0.05, 0); cable.rotation.x = Math.PI/2; lampGroup.add(cable);
        
        // 机械臂 (关节)
        const joint1 = new THREE.Mesh(new THREE.CylinderGeometry(0.15, 0.15, 0.4, 8), getMat(0x333333, 0.3));
        joint1.position.set(0, 0.4, 0); lampGroup.add(joint1);
        const arm1 = new THREE.Mesh(new THREE.BoxGeometry(0.1, 1.8, 0.1), getMat(0x333333, 0.3));
        arm1.position.set(0, 1.3, 0); arm1.rotation.z = 0.3; lampGroup.add(arm1);
        
        const joint2 = new THREE.Mesh(new THREE.CylinderGeometry(0.12, 0.12, 0.3, 8), getMat(0x333333, 0.3));
        joint2.position.set(0.45, 2.2, 0); lampGroup.add(joint2);
        const arm2 = new THREE.Mesh(new THREE.BoxGeometry(0.1, 1.8, 0.1), getMat(0x333333, 0.3));
        arm2.position.set(0.65, 3.1, 0); arm2.rotation.z = -0.4; lampGroup.add(arm2);
        
        // 灯罩 (锥形 + 开口)
        const lampshade = new THREE.Mesh(new THREE.CylinderGeometry(0.2, 1.2, 1.8, 16, 1, true), getMat(0xff4444, 0.4));
        lampshade.position.set(1.1, 4, 0); lampshade.rotation.z = -0.8; lampshade.material.side = THREE.DoubleSide;
        lampGroup.add(lampshade);
        
        // 灯泡发光
        const bulb = new THREE.Mesh(new THREE.SphereGeometry(0.3, 16, 16), new THREE.MeshBasicMaterial({ color: 0xffaa55 }));
        bulb.position.set(1.2, 3.8, 0); lampGroup.add(bulb);

        lampGroup.position.set(11.5, 4, 9.5);
        scene.add(lampGroup);

        // 遥控器与纸巾盒 (床头柜上)
        const remote = new THREE.Mesh(new THREE.BoxGeometry(0.9, 0.15, 0.3), getMat(0xffffff, 0.5));
        remote.position.set(12.5, 4.1, 9.5); scene.add(remote);

        // 灰色毛绒小熊 (床头)
        const bearGroup = new THREE.Group();
        const bearMat = getMat(0x999999, 0.95); // 灰色
        const bearHead = new THREE.Mesh(new THREE.SphereGeometry(0.9, 16, 16), bearMat);
        bearHead.position.y = 1.2; bearGroup.add(bearHead);
        const bearBody = new THREE.Mesh(new THREE.CylinderGeometry(0.7, 0.9, 1.8, 16), bearMat);
        bearBody.position.y = 0.2; bearGroup.add(bearBody);
        // 耳朵
        const earL = new THREE.Mesh(new THREE.SphereGeometry(0.35, 12, 12), bearMat); earL.position.set(-0.6, 1.8, 0); bearGroup.add(earL);
        const earR = earL.clone(); earR.position.set(0.6, 1.8, 0); bearGroup.add(earR);
        // 粉色眼罩
        const eyeMaskMat = getMat(0xffb6c1, 0.9);
        const eyeMask = new THREE.Mesh(new THREE.BoxGeometry(1.4, 0.4, 0.3), eyeMaskMat);
        eyeMask.position.set(0, 1.3, 0.8); bearGroup.add(eyeMask);
        
        bearGroup.position.set(-7, 1.5, 19);
        bearGroup.rotation.y = 0.6;
        bearGroup.scale.set(0.9, 0.9, 0.9);
        scene.add(bearGroup);

        // 床边矮柜
        const fileCabinet = new THREE.Mesh(new THREE.BoxGeometry(2.5, 2.5, 2.5), getMat(0xffffff, 0.7));
        fileCabinet.position.set(15, 1.25, 14); fileCabinet.castShadow = true; scene.add(fileCabinet);

        // 床上抱枕
        const bigPillow = new THREE.Mesh(new THREE.BoxGeometry(6, 4, 2), getMat(0xd3d3d3, 0.9));
        bigPillow.position.set(-5, 2.5, 18); bigPillow.rotation.x = 0.15; bigPillow.rotation.y = 0.1;
        bigPillow.castShadow = true; scene.add(bigPillow);

        const patternPillow = new THREE.Mesh(new THREE.BoxGeometry(3.5, 1.2, 2.5), getMat(0xd8bfd8, 0.9));
        patternPillow.position.set(-2, 1.6, 17.5); patternPillow.rotation.y = -0.3;
        scene.add(patternPillow);

        // ==========================================
        // 11. 渲染循环与窗口适配
        // ==========================================
        function animate() {
            requestAnimationFrame(animate);
            controls.update();
            renderer.render(scene, camera);
        }

        window.addEventListener('resize', () => {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        });

        animate();
    </script>
</body>
</html>

Game Source: 我的房间 - 极致精细 3D 数字孪生

Creator: EpicCoder88

Libraries: three

Complexity: complex (676 lines, 33.9 KB)

The full source code is displayed above on this page.

Remix Instructions

To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: 3d-epiccoder88-musdnmvo" to link back to the original. Then publish at arcadelab.ai/publish.