🎮ArcadeLab

我的房间 - 终极数字孪生 3D 还原

by EpicCoder88
691 lines35.5 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; top: 20px; left: 20px;
            color: rgba(255,255,255,0.9); font-size: 14px;
            background: rgba(0,0,0,0.7); padding: 15px 25px; border-radius: 10px;
            pointer-events: none; line-height: 1.8; backdrop-filter: blur(10px);
            border-left: 4px solid #4ecdc4;
        }
        #info b { color: #4ecdc4; }
    </style>
</head>
<body>
    <div id="info">
        <b>🏠 数字孪生房间 - 极致细节版</b><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, 35, 80);

        const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 100);
        camera.position.set(18, 14, 28);
        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.1;
        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.05;

        // ==========================================
        // 2. 高级光照系统 (模拟真实环境光)
        // ==========================================
        // 半球光 (天空色与地面色)
        const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444, 0.6);
        scene.add(hemiLight);
        
        // 主光源 (阳光)
        const dirLight = new THREE.DirectionalLight(0xfff5e6, 1.5);
        dirLight.position.set(20, 25, 15);
        dirLight.castShadow = true;
        dirLight.shadow.mapSize.width = 4096;
        dirLight.shadow.mapSize.height = 4096;
        dirLight.shadow.camera.near = 1;
        dirLight.shadow.camera.far = 80;
        dirLight.shadow.camera.left = -30;
        dirLight.shadow.camera.right = 30;
        dirLight.shadow.camera.top = 30;
        dirLight.shadow.camera.bottom = -30;
        dirLight.shadow.bias = -0.0003;
        dirLight.shadow.radius = 2;
        scene.add(dirLight);

        // 局部补光 (台灯发光)
        const lampLight = new THREE.PointLight(0xffcc88, 0.8, 15);
        lampLight.position.set(13, 6, 10);
        scene.add(lampLight);

        // ==========================================
        // 3. 程序化纹理生成器 (CanvasTexture)
        // ==========================================
        
        // 生成木纹纹理
        function createWoodTexture(baseColor, streakColor) {
            const canvas = document.createElement('canvas'); canvas.width = 512; canvas.height = 512;
            const ctx = canvas.getContext('2d');
            ctx.fillStyle = baseColor; ctx.fillRect(0, 0, 512, 512);
            ctx.strokeStyle = streakColor; ctx.lineWidth = 1;
            for(let i=0; i<100; i++) {
                ctx.beginPath();
                let y = Math.random() * 512;
                ctx.moveTo(0, y);
                for(let x=0; x<512; x+=20) {
                    ctx.lineTo(x, y + Math.sin(x*0.05) * 5 + Math.random() * 3);
                }
                ctx.stroke();
            }
            const tex = new THREE.CanvasTexture(canvas);
            tex.wrapS = THREE.RepeatWrapping; tex.wrapT = THREE.RepeatWrapping;
            return tex;
        }

        // 生成瓷砖纹理
        function createTileTexture() {
            const canvas = document.createElement('canvas'); canvas.width = 512; canvas.height = 512;
            const ctx = canvas.getContext('2d');
            ctx.fillStyle = '#e8ebee'; ctx.fillRect(0, 0, 512, 512);
            ctx.strokeStyle = '#b0b5ba'; ctx.lineWidth = 2;
            // 绘制瓷砖缝隙
            for(let i=0; i<=512; i+=128) {
                ctx.beginPath(); ctx.moveTo(i, 0); ctx.lineTo(i, 512); ctx.stroke();
                ctx.beginPath(); ctx.moveTo(0, i); ctx.lineTo(512, i); ctx.stroke();
            }
            const tex = new THREE.CanvasTexture(canvas);
            tex.wrapS = THREE.RepeatWrapping; tex.wrapT = THREE.RepeatWrapping;
            tex.repeat.set(4, 4);
            return tex;
        }

        // 生成布料纹理(带噪点)
        function createFabricTexture(baseColor) {
            const canvas = document.createElement('canvas'); canvas.width = 256; canvas.height = 256;
            const ctx = canvas.getContext('2d');
            ctx.fillStyle = baseColor; ctx.fillRect(0, 0, 256, 256);
            ctx.fillStyle = 'rgba(0,0,0,0.05)';
            for(let i=0; i<2000; i++) {
                ctx.fillRect(Math.random()*256, Math.random()*256, 2, 2);
            }
            const tex = new THREE.CanvasTexture(canvas);
            tex.wrapS = THREE.RepeatWrapping; tex.wrapT = THREE.RepeatWrapping;
            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);
            // 多层半透明圆形叠加
            for(let i=0; i<30; i++) {
                ctx.fillStyle = `rgba(180, 150, 110, ${0.05 + Math.random()*0.1})`;
                ctx.beginPath();
                ctx.arc(256 + Math.random()*200 - 100, 256 + Math.random()*200 - 100, 50 + Math.random()*150, 0, Math.PI*2);
                ctx.fill();
            }
            return new THREE.CanvasTexture(canvas);
        }

        // ==========================================
        // 4. 材质库
        // ==========================================
        const woodTex = createWoodTexture('#8b5a2b', '#654321');
        const darkWoodTex = createWoodTexture('#654321', '#4a3018');
        const tileTex = createTileTexture();
        const fabricGrayTex = createFabricTexture('#d3d3d3');
        const fabricPinkTex = createFabricTexture('#f4b6c2');

        const matWood = new THREE.MeshStandardMaterial({ map: woodTex, roughness: 0.7, metalness: 0.1 });
        const matDarkWood = new THREE.MeshStandardMaterial({ map: darkWoodTex, roughness: 0.8, metalness: 0.0 });
        const matTile = new THREE.MeshStandardMaterial({ map: tileTex, roughness: 0.4, metalness: 0.1 });
        const matWall = new THREE.MeshStandardMaterial({ color: 0xfdfaf7, roughness: 0.95 });
        const matMetal = new THREE.MeshStandardMaterial({ color: 0xcccccc, roughness: 0.2, metalness: 0.8 });
        const matGlass = new THREE.MeshPhysicalMaterial({ color: 0xffffff, transparent: true, opacity: 0.2, roughness: 0.1, metalness: 0.1, transmission: 0.8 });
        const matFabricGray = new THREE.MeshStandardMaterial({ map: fabricGrayTex, roughness: 0.95 });
        const matFabricPink = new THREE.MeshStandardMaterial({ map: fabricPinkTex, roughness: 0.9 });

        // ==========================================
        // 5. 房间结构 (地、墙、踢脚线)
        // ==========================================
        const floor = new THREE.Mesh(new THREE.PlaneGeometry(36, 36), matTile);
        floor.rotation.x = -Math.PI / 2; floor.receiveShadow = true; scene.add(floor);

        const wallThickness = 0.4;
        // 左墙
        const leftWall = new THREE.Mesh(new THREE.BoxGeometry(wallThickness, 16, 36), matWall);
        leftWall.position.set(-18, 8, 0); leftWall.receiveShadow = true; scene.add(leftWall);
        // 后墙
        const backWall = new THREE.Mesh(new THREE.BoxGeometry(36, 16, wallThickness), matWall);
        backWall.position.set(0, 8, -18); backWall.receiveShadow = true; scene.add(backWall);
        // 右墙
        const rightWall = new THREE.Mesh(new THREE.BoxGeometry(wallThickness, 16, 36), matWall);
        rightWall.position.set(18, 8, 0); rightWall.receiveShadow = true; scene.add(rightWall);

        // 水渍 (后墙)
        const stainMat = new THREE.MeshStandardMaterial({ map: createStainTexture(), transparent: true, opacity: 0.8, depthWrite: false });
        const stain = new THREE.Mesh(new THREE.PlaneGeometry(10, 8), stainMat);
        stain.position.set(8, 4, -17.7); stain.rotation.y = Math.PI; scene.add(stain);

        // 踢脚线
        const baseboardMat = new THREE.MeshStandardMaterial({ color: 0x333333, roughness: 0.8 });
        const baseboardBack = new THREE.Mesh(new THREE.BoxGeometry(36, 0.6, 0.2), baseboardMat);
        baseboardBack.position.set(0, 0.3, -17.9); scene.add(baseboardBack);
        
        // 墙面开关和插座
        const switchPlate = new THREE.Mesh(new THREE.BoxGeometry(0.4, 0.6, 0.05), matMetal);
        switchPlate.position.set(-8, 5, -17.7); scene.add(switchPlate);
        const socket = new THREE.Mesh(new THREE.BoxGeometry(0.6, 0.4, 0.05), matMetal);
        socket.position.set(-4, 1, -17.7); scene.add(socket);

        // ==========================================
        // 6. 超精细衣柜 (带内部结构)
        // ==========================================
        const wardrobeGroup = new THREE.Group();
        const wW = 10, wH = 14, wD = 4;
        
        // 主柜体
        const cabinetBody = new THREE.Mesh(new THREE.BoxGeometry(wW, wH, wD), matWood);
        cabinetBody.position.set(0, wH/2, 0);
        cabinetBody.castShadow = true; cabinetBody.receiveShadow = true;
        wardrobeGroup.add(cabinetBody);

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

        // 顶柜
        const topCabinet = new THREE.Mesh(new THREE.BoxGeometry(wW, 3, wD), matWood);
        topCabinet.position.set(0, wH - 1.5, 0);
        topCabinet.castShadow = true; topCabinet.receiveShadow = true;
        wardrobeGroup.add(topCabinet);
        
        // 抽屉把手 (金属)
        for(let i = -3; i <= 3; i+=6) {
            const handle = new THREE.Mesh(new THREE.CylinderGeometry(0.05, 0.05, 1.5, 8), matMetal);
            handle.rotation.x = Math.PI / 2;
            handle.position.set(i, wH - 1.5, wD/2 + 0.1);
            wardrobeGroup.add(handle);
        }

        // 挂衣服 (用弯曲的平面和圆柱体精细模拟)
        const clothesColors = [0xffb6c1, 0xadd8e6, 0xffffff, 0xf5f5dc, 0x87ceeb, 0xffe4b5, 0xd3d3d3, 0xffdead, 0x98fb98];
        for(let i=0; i<60; i++) {
            const clothMat = new THREE.MeshStandardMaterial({ color: clothesColors[Math.floor(Math.random() * clothesColors.length)], roughness: 0.9 });
            const clothH = 2 + Math.random() * 3;
            const clothW = 0.8 + Math.random() * 0.5;
            
            // 使用弯曲的几何体模拟衣服下摆
            const clothGeo = new THREE.PlaneGeometry(clothW, clothH, 4, 4);
            const posAttr = clothGeo.attributes.position;
            for(let j=0; j<posAttr.count; j++) {
                const y = posAttr.getY(j);
                posAttr.setZ(j, Math.sin(y * 2) * 0.1); // 衣摆波浪
            }
            clothGeo.computeVertexNormals();
            
            const cloth = new THREE.Mesh(clothGeo, clothMat);
            cloth.rotation.x = -Math.PI / 2;
            
            // 随机挂在不同层
            const level = Math.floor(i / 12) * 2.5 + 3;
            const posX = (Math.random() - 0.5) * 4 + 1.5;
            const posZ = (Math.random() - 0.5) * 2;
            
            cloth.position.set(posX, level + 1.5, posZ);
            cloth.rotation.y = (Math.random() - 0.5) * 0.8;
            cloth.castShadow = true;
            wardrobeGroup.add(cloth);
            
            // 衣架勾
            const hanger = new THREE.Mesh(new THREE.TorusGeometry(0.1, 0.02, 8, 16, Math.PI), matMetal);
            hanger.rotation.z = Math.PI / 2;
            hanger.position.set(posX, level + 3, posZ);
            wardrobeGroup.add(hanger);
        }

        // 推拉门 (磨砂玻璃)
        const doorMat = new THREE.MeshPhysicalMaterial({ 
            color: 0xcce0cc, transparent: true, opacity: 0.5, roughness: 0.2, transmission: 0.6 
        });
        const doorLeft = new THREE.Mesh(new THREE.BoxGeometry(5, 11, 0.2), doorMat);
        doorLeft.position.set(-2.5, 5.5, wD/2); wardrobeGroup.add(doorLeft);
        const doorRight = new THREE.Mesh(new THREE.BoxGeometry(5, 11, 0.2), doorMat);
        doorRight.position.set(2.5, 5.5, wD/2); wardrobeGroup.add(doorRight);

        // 门框
        const frameL = new THREE.Mesh(new THREE.BoxGeometry(0.3, 11, 0.3), matMetal);
        frameL.position.set(-5, 5.5, wD/2); wardrobeGroup.add(frameL);
        const frameR = new THREE.Mesh(new THREE.BoxGeometry(0.3, 11, 0.3), matMetal);
        frameR.position.set(5, 5.5, wD/2); wardrobeGroup.add(frameR);
        const frameMid = new THREE.Mesh(new THREE.BoxGeometry(0.15, 11, 0.3), matMetal);
        frameMid.position.set(0, 5.5, wD/2); wardrobeGroup.add(frameMid);

        wardrobeGroup.position.set(-13, 0, -10);
        wardrobeGroup.rotation.y = 0.1; // 稍微倾斜一点,显得自然
        scene.add(wardrobeGroup);

        // ==========================================
        // 7. 飘窗与窗户 (深度细化)
        // ==========================================
        const windowX = 17.7, windowZ = 3;

        // 窗台
        const windowSill = new THREE.Mesh(new THREE.BoxGeometry(3.5, 0.6, 14), matWall);
        windowSill.position.set(windowX - 1.75, 2.5, windowZ); windowSill.receiveShadow = true; scene.add(windowSill);

        // 绿色毛绒垫
        const rugMat = new THREE.MeshStandardMaterial({ color: 0x7ec850, roughness: 1.0 });
        const rug = new THREE.Mesh(new THREE.BoxGeometry(3.2, 0.2, 13.5), rugMat);
        rug.position.set(windowX - 1.75, 2.9, windowZ); rug.receiveShadow = true; scene.add(rug);

        // 窗户框架 (绿色,带细节)
        const frameMatGreen = new THREE.MeshStandardMaterial({ color: 0x2e8b57, roughness: 0.5 });
        const winFrameTop = new THREE.Mesh(new THREE.BoxGeometry(0.3, 0.4, 14), frameMatGreen);
        winFrameTop.position.set(windowX, 9, windowZ); scene.add(winFrameTop);
        const winFrameBottom = new THREE.Mesh(new THREE.BoxGeometry(0.3, 0.4, 14), frameMatGreen);
        winFrameBottom.position.set(windowX, 2.8, windowZ); scene.add(winFrameBottom);
        
        // 窗格 (横竖交错)
        for(let i = -6.5; i <= 6.5; i+=2.1) {
            const bar = new THREE.Mesh(new THREE.BoxGeometry(0.3, 6.2, 0.2), frameMatGreen);
            bar.position.set(windowX, 5.9, windowZ + i); scene.add(bar);
        }
        for(let i = 4; i <= 8; i+=2) {
            const bar = new THREE.Mesh(new THREE.BoxGeometry(0.3, 0.2, 14), frameMatGreen);
            bar.position.set(windowX, i, windowZ); scene.add(bar);
        }

        // 玻璃 (带一点反射)
        const glass = new THREE.Mesh(new THREE.PlaneGeometry(13.8, 6), matGlass);
        glass.position.set(windowX, 5.9, windowZ); glass.rotation.y = -Math.PI / 2; scene.add(glass);

        // 防盗网 (细密网格)
        const barMat = new THREE.MeshStandardMaterial({ color: 0xaaaaaa, roughness: 0.4, metalness: 0.6 });
        for(let i = -6; i <= 6; i+=0.6) {
            const bar = new THREE.Mesh(new THREE.CylinderGeometry(0.02, 0.02, 6, 6), barMat);
            bar.position.set(windowX - 0.2, 5.9, windowZ + i); scene.add(bar);
        }
        for(let i = 3.5; i <= 8.5; i+=1) {
            const bar = new THREE.Mesh(new THREE.CylinderGeometry(0.02, 0.02, 13.8, 6), barMat);
            bar.rotation.z = Math.PI / 2;
            bar.position.set(windowX - 0.2, i, windowZ); scene.add(bar);
        }

        // 飘窗上堆叠的被子 (使用顶点变形模拟褶皱)
        const quiltGeo = new THREE.BoxGeometry(2.5, 1.5, 5, 10, 10, 10);
        const quiltMat = new THREE.MeshStandardMaterial({ color: 0xbbccee, roughness: 0.95 });
        const quilt = new THREE.Mesh(quiltGeo, quiltMat);
        // 顶点随机偏移
        const posAttr = quiltGeo.attributes.position;
        for(let i=0; i<posAttr.count; i++) {
            posAttr.setX(i, posAttr.getX(i) + (Math.random() - 0.5) * 0.2);
            posAttr.setY(i, posAttr.getY(i) + (Math.random() - 0.5) * 0.2);
            posAttr.setZ(i, posAttr.getZ(i) + (Math.random() - 0.5) * 0.2);
        }
        quilt.geometry.computeVertexNormals();
        quilt.position.set(windowX - 1.5, 3.8, windowZ - 2);
        quilt.rotation.z = 0.1; quilt.castShadow = true;
        scene.add(quilt);
        
        // 另一床叠好的被子
        const quilt2 = new THREE.Mesh(new THREE.BoxGeometry(2, 0.8, 4), matFabricGray);
        quilt2.position.set(windowX - 2, 3.5, windowZ - 1.5); quilt2.rotation.y = 0.1; quilt2.castShadow = true; scene.add(quilt2);

        // 枕头
        const pillow = new THREE.Mesh(new THREE.BoxGeometry(2, 1, 2), new THREE.MeshStandardMaterial({ color: 0xffee88, roughness: 0.9 }));
        pillow.position.set(windowX - 1.5, 4, windowZ + 5); pillow.rotation.y = 0.3; pillow.castShadow = true; scene.add(pillow);

        // 窗帘 (顶点波浪)
        const valanceMat = new THREE.MeshStandardMaterial({ color: 0xfdfaf7, roughness: 0.9 });
        const valance = new THREE.Mesh(new THREE.BoxGeometry(0.6, 2.5, 14), valanceMat);
        valance.position.set(windowX - 0.8, 12, windowZ); scene.add(valance);
        
        const curtainGeo = new THREE.PlaneGeometry(4, 14, 30, 30);
        const curtain = new THREE.Mesh(curtainGeo, valanceMat);
        const cPosAttr = curtain.geometry.attributes.position;
        for (let i = 0; i < cPosAttr.count; i++) {
            const y = cPosAttr.getY(i);
            const x = cPosAttr.getX(i);
            // 双重波浪
            cPosAttr.setZ(i, Math.sin(y * 1.5) * 0.3 + Math.cos(x * 2) * 0.1);
        }
        curtain.geometry.computeVertexNormals();
        curtain.position.set(windowX - 1, 7, windowZ - 6);
        curtain.rotation.y = -Math.PI / 2;
        scene.add(curtain);

        // ==========================================
        // 8. 书桌与电脑 (细化)
        // ==========================================
        const deskGroup = new THREE.Group();
        // 桌面
        deskGroup.add(new THREE.Mesh(new THREE.BoxGeometry(7, 0.3, 3), matWood)).castShadow = true;
        // 抽屉
        const drawer = new THREE.Mesh(new THREE.BoxGeometry(2.5, 4, 2.8), matDarkWood);
        drawer.position.set(-2.2, -2.15, 0); drawer.castShadow = true; deskGroup.add(drawer);
        // 抽屉把手
        const drawerHandle = new THREE.Mesh(new THREE.BoxGeometry(1, 0.1, 0.1), matMetal);
        drawerHandle.position.set(-2.2, -1, 1.45); deskGroup.add(drawerHandle);
        
        // 显示器
        const monitorGroup = new THREE.Group();
        monitorGroup.add(new THREE.Mesh(new THREE.CylinderGeometry(0.6, 0.8, 0.2, 16), matMetal)).position.y = 0.1;
        monitorGroup.add(new THREE.Mesh(new THREE.CylinderGeometry(0.1, 0.1, 1.2, 8), matMetal)).position.y = 0.8;
        const screen = new THREE.Mesh(new THREE.BoxGeometry(2.5, 1.5, 0.1), new THREE.MeshStandardMaterial({ color: 0x111111, roughness: 0.2 }));
        screen.position.y = 1.6; monitorGroup.add(screen);
        // 屏幕反光面
        const screenFace = new THREE.Mesh(new THREE.PlaneGeometry(2.4, 1.4), new THREE.MeshStandardMaterial({ color: 0x222222, roughness: 0.05, metalness: 0.2 }));
        screenFace.position.set(0, 1.6, 0.06); monitorGroup.add(screenFace);
        
        monitorGroup.position.set(1.5, 0.2, -0.5);
        deskGroup.add(monitorGroup);

        // 键盘与鼠标
        const keyboard = new THREE.Mesh(new THREE.BoxGeometry(1.5, 0.1, 0.5), new THREE.MeshStandardMaterial({ color: 0x333333, roughness: 0.5 }));
        keyboard.position.set(1.5, 0.2, 0.8); deskGroup.add(keyboard);
        const mouse = new THREE.Mesh(new THREE.SphereGeometry(0.2, 16, 16), new THREE.MeshStandardMaterial({ color: 0x333333, roughness: 0.5 }));
        mouse.scale.set(0.8, 0.5, 1.2); mouse.position.set(2.5, 0.2, 0.8); deskGroup.add(mouse);

        // 电脑主机 (带散热孔)
        const pcTower = new THREE.Mesh(new THREE.BoxGeometry(1.2, 2.8, 2.2), new THREE.MeshStandardMaterial({ color: 0x1a1a1a, roughness: 0.3, metalness: 0.5 }));
        pcTower.position.set(3, -1.4, 0.5); deskGroup.add(pcTower);
        // 主机上的蓝色指示灯
        const led = new THREE.Mesh(new THREE.BoxGeometry(0.05, 0.05, 0.05), new THREE.MeshBasicMaterial({ color: 0x00aaff }));
        led.position.set(3, -0.5, 1.65); deskGroup.add(led);

        // 电线 (使用曲线)
        const wireCurve = new THREE.CatmullRomCurve3([
            new THREE.Vector3(3, -0.5, 1.6),
            new THREE.Vector3(2, -1, 2),
            new THREE.Vector3(0, -1.5, 2),
            new THREE.Vector3(-1, -2, 1)
        ]);
        const wire = new THREE.Mesh(new THREE.TubeGeometry(wireCurve, 20, 0.05, 8, false), new THREE.MeshStandardMaterial({ color: 0x222222, roughness: 0.8 }));
        deskGroup.add(wire);

        deskGroup.position.set(9, 3, -14);
        scene.add(deskGroup);

        // ==========================================
        // 9. 粉色转椅 (带曲面)
        // ==========================================
        const chairGroup = new THREE.Group();
        // 靠背 (使用曲面)
        const backGeo = new THREE.CylinderGeometry(1.5, 1.5, 2.5, 32, 1, true, 0, Math.PI);
        const chairBack = new THREE.Mesh(backGeo, matFabricPink);
        chairBack.scale.set(1, 1, 0.6);
        chairBack.position.set(0, 1.5, -0.8);
        chairBack.castShadow = true; chairGroup.add(chairBack);
        // 坐垫
        const cushion = new THREE.Mesh(new THREE.BoxGeometry(2, 0.4, 2), matFabricPink);
        cushion.castShadow = true; chairGroup.add(cushion);
        // 气压杆
        const pole = new THREE.Mesh(new THREE.CylinderGeometry(0.15, 0.15, 1.5, 12), matMetal);
        pole.position.set(0, -0.8, 0); chairGroup.add(pole);
        // 五星脚
        const base = new THREE.Mesh(new THREE.CylinderGeometry(1.5, 1.5, 0.1, 32), matMetal);
        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 wheel = new THREE.Mesh(new THREE.CylinderGeometry(0.15, 0.15, 0.1, 16), matDarkWood);
            wheel.rotation.z = Math.PI / 2;
            wheel.position.set(Math.cos(angle) * 1.4, -1.6, Math.sin(angle) * 1.4);
            wheel.castShadow = true;
            chairGroup.add(wheel);
        }
        chairGroup.position.set(7, 2.5, -11);
        scene.add(chairGroup);

        // ==========================================
        // 10. 空调与风扇
        // ==========================================
        // 空调
        const acGroup = new THREE.Group();
        acGroup.add(new THREE.Mesh(new THREE.BoxGeometry(4.5, 1.5, 1), new THREE.MeshStandardMaterial({ color: 0xffffff, roughness: 0.3 }))).castShadow = true;
        // 出风口
        const acVent = new THREE.Mesh(new THREE.BoxGeometry(4.1, 0.2, 0.1), new THREE.MeshStandardMaterial({ color: 0xdddddd, roughness: 0.5 }));
        acVent.position.set(0, -0.55, 0.45); acGroup.add(acVent);
        // 显示屏
        const acDisplay = new THREE.Mesh(new THREE.BoxGeometry(0.5, 0.2, 0.05), new THREE.MeshBasicMaterial({ color: 0x00ff00 }));
        acDisplay.position.set(1.5, 0.2, 0.51); acGroup.add(acDisplay);
        acGroup.position.set(0, 13, -17.6);
        scene.add(acGroup);

        // 落地风扇 (精细网罩)
        const fanGroup = new THREE.Group();
        // 底座
        fanGroup.add(new THREE.Mesh(new THREE.CylinderGeometry(1.5, 1.5, 0.2, 32), new THREE.MeshStandardMaterial({ color: 0xeeeeee, roughness: 0.4 }))).castShadow = true;
        // 杆
        const fanPole = new THREE.Mesh(new THREE.CylinderGeometry(0.1, 0.1, 5, 16), new THREE.MeshStandardMaterial({ color: 0xeeeeee, roughness: 0.4 }));
        fanPole.position.y = 2.5; fanPole.castShadow = true; fanGroup.add(fanPole);
        // 控制面板
        const panel = new THREE.Mesh(new THREE.BoxGeometry(0.3, 0.8, 0.1), new THREE.MeshStandardMaterial({ color: 0xdddddd, roughness: 0.3 }));
        panel.position.set(0.2, 1.5, 0); fanGroup.add(panel);
        // 电机外壳
        const motor = new THREE.Mesh(new THREE.CylinderGeometry(0.6, 0.6, 0.8, 16), new THREE.MeshStandardMaterial({ color: 0xcccccc, roughness: 0.3, metalness: 0.5 }));
        motor.rotation.x = Math.PI / 2; motor.position.set(0, 5.5, 0.3); fanGroup.add(motor);
        // 扇叶
        const bladeMat = new THREE.MeshStandardMaterial({ color: 0xeeeeee, roughness: 0.2, side: THREE.DoubleSide });
        for(let i=0; i<3; i++) {
            const blade = new THREE.Mesh(new THREE.PlaneGeometry(1.2, 0.4), bladeMat);
            blade.rotation.z = (i / 3) * Math.PI * 2;
            blade.rotation.x = 0.3;
            blade.position.set(0, 5.5, 0.1);
            blade.rotation.y = (i / 3) * Math.PI * 2;
            fanGroup.add(blade);
        }
        // 网罩 (用多个圆环和径向线组合)
        const ringMat = new THREE.MeshStandardMaterial({ color: 0xaaaaaa, roughness: 0.4, metalness: 0.6 });
        for(let r=0.5; r<=1.6; r+=0.3) {
            const ring = new THREE.Mesh(new THREE.TorusGeometry(r, 0.02, 8, 32), ringMat);
            ring.position.set(0, 5.5, 0); ring.rotation.y = Math.PI / 2;
            fanGroup.add(ring);
        }
        for(let i=0; i<16; i++) {
            const angle = (i / 16) * Math.PI * 2;
            const spoke = new THREE.Mesh(new THREE.CylinderGeometry(0.01, 0.01, 3.2, 6), ringMat);
            spoke.rotation.z = Math.PI / 2;
            spoke.rotation.y = angle;
            spoke.position.set(0, 5.5, 0);
            fanGroup.add(spoke);
        }
        
        fanGroup.position.set(-8, 0, -8);
        fanGroup.rotation.y = 0.3;
        scene.add(fanGroup);

        // ==========================================
        // 11. 床边细节 (床头柜、台灯、玩偶)
        // ==========================================
        const bedsideX = 14, bedsideZ = 12;
        // 床头板
        const headboard = new THREE.Mesh(new THREE.BoxGeometry(22, 3.5, 0.4), matWall);
        headboard.position.set(-2, 2.5, 18.2); headboard.receiveShadow = true; scene.add(headboard);
        
        // 床头柜
        const nightstand = new THREE.Mesh(new THREE.BoxGeometry(3, 4, 3), new THREE.MeshStandardMaterial({ color: 0xffffff, roughness: 0.6 }));
        nightstand.position.set(bedsideX, 2, bedsideZ); nightstand.castShadow = true; nightstand.receiveShadow = true; scene.add(nightstand);
        
        // 抽屉 (半开)
        const nsDrawer = new THREE.Mesh(new THREE.BoxGeometry(2.8, 1.5, 0.2), new THREE.MeshStandardMaterial({ color: 0xe0e0e0, roughness: 0.6 }));
        nsDrawer.position.set(bedsideX, 1.5, bedsideZ + 1.6); scene.add(nsDrawer);
        // 抽屉把手
        const nsHandle = new THREE.Mesh(new THREE.BoxGeometry(1.2, 0.1, 0.1), matMetal);
        nsHandle.position.set(bedsideX, 1.5, bedsideZ + 1.75); scene.add(nsHandle);
        
        // 抽屉内部杂物
        const tissueBox = new THREE.Mesh(new THREE.BoxGeometry(1, 0.6, 0.5), new THREE.MeshStandardMaterial({ color: 0xd8bfd8, roughness: 0.9 }));
        tissueBox.position.set(bedsideX - 0.6, 1.8, bedsideZ + 1.2); scene.add(tissueBox);
        const tissuePack = new THREE.Mesh(new THREE.BoxGeometry(0.6, 0.4, 0.4), new THREE.MeshStandardMaterial({ color: 0xadd8e6, roughness: 0.9 }));
        tissuePack.position.set(bedsideX + 0.6, 1.7, bedsideZ + 1.3); scene.add(tissuePack);

        // 红色折叠台灯
        const lampGroup = new THREE.Group();
        const lampBase = new THREE.Mesh(new THREE.CylinderGeometry(1, 1, 0.3, 16), new THREE.MeshStandardMaterial({ color: 0xff3333, roughness: 0.4 }));
        lampBase.position.y = 0.15; lampGroup.add(lampBase);
        // 关节臂
        const arm1 = new THREE.Mesh(new THREE.CylinderGeometry(0.1, 0.1, 2.5, 8), matDarkWood);
        arm1.position.set(0, 1.5, 0); arm1.rotation.z = 0.4; lampGroup.add(arm1);
        const arm2 = new THREE.Mesh(new THREE.CylinderGeometry(0.08, 0.08, 2.5, 8), matDarkWood);
        arm2.position.set(0.8, 3.5, 0); arm2.rotation.z = -0.6; lampGroup.add(arm2);
        // 灯罩
        const lampshade = new THREE.Mesh(new THREE.ConeGeometry(1.2, 1.8, 16, 1, true), new THREE.MeshStandardMaterial({ color: 0xff4444, roughness: 0.3, side: THREE.DoubleSide }));
        lampshade.position.set(1.5, 5, 0); lampshade.rotation.z = -0.8; lampGroup.add(lampshade);
        // 灯泡发光效果
        const bulb = new THREE.Mesh(new THREE.SphereGeometry(0.4, 16, 16), new THREE.MeshBasicMaterial({ color: 0xffdd88 }));
        bulb.position.set(1.2, 4.2, 0); lampGroup.add(bulb);
        
        lampGroup.position.set(bedsideX - 0.5, 4, bedsideZ - 0.5);
        scene.add(lampGroup);

        // 床上毛绒小熊 (灰粉色)
        const bearGroup = new THREE.Group();
        const bearMat = new THREE.MeshStandardMaterial({ color: 0x999999, roughness: 0.9 });
        const bearHead = new THREE.Mesh(new THREE.SphereGeometry(1, 16, 16), bearMat);
        bearHead.position.y = 1.2; bearGroup.add(bearHead);
        const bearBody = new THREE.Mesh(new THREE.CylinderGeometry(0.8, 1, 2, 16), bearMat);
        bearBody.position.y = 0; bearGroup.add(bearBody);
        // 耳朵
        const earL = new THREE.Mesh(new THREE.SphereGeometry(0.3, 8, 8), bearMat);
        earL.position.set(-0.8, 2, 0); bearGroup.add(earL);
        const earR = earL.clone(); earR.position.x = 0.8; bearGroup.add(earR);
        // 粉色眼罩
        const eyeMask = new THREE.Mesh(new THREE.BoxGeometry(1.5, 0.4, 0.3), matFabricPink);
        eyeMask.position.set(0, 1.4, 0.9); bearGroup.add(eyeMask);
        // 鼻子
        const nose = new THREE.Mesh(new THREE.SphereGeometry(0.15, 8, 8), new THREE.MeshStandardMaterial({ color: 0x333333 }));
        nose.position.set(0, 1, 1); bearGroup.add(nose);
        
        bearGroup.position.set(-5, 1.5, 16);
        bearGroup.rotation.y = 0.6;
        bearGroup.scale.set(0.8, 0.8, 0.8);
        scene.add(bearGroup);

        // 蝴蝶墙贴 (使用 Canvas 动态绘制)
        function createButterflyTexture() {
            const canvas = document.createElement('canvas'); canvas.width = 1024; canvas.height = 256;
            const ctx = canvas.getContext('2d');
            ctx.clearRect(0, 0, 1024, 256);
            const colors = ['#ff6b6b', '#4ecdc4', '#ffe66d', '#a06cd5', '#ff9f1c'];
            for(let i=0; i<8; i++) {
                ctx.fillStyle = colors[i % colors.length];
                const x = 80 + i * 120;
                const y = 128 + Math.sin(i) * 40;
                // 画蝴蝶翅膀
                ctx.beginPath(); ctx.ellipse(x, y, 20, 40, Math.PI/6, 0, Math.PI*2); ctx.fill();
                ctx.beginPath(); ctx.ellipse(x, y, 20, 40, -Math.PI/6, 0, Math.PI*2); ctx.fill();
                // 身体
                ctx.fillStyle = '#333'; ctx.beginPath(); ctx.ellipse(x, y, 4, 25, 0, 0, Math.PI*2); ctx.fill();
            }
            return new THREE.CanvasTexture(canvas);
        }
        const butterflyMat = new THREE.MeshStandardMaterial({ map: createButterflyTexture(), transparent: true, depthWrite: false });
        const butterflyPlane = new THREE.Mesh(new THREE.PlaneGeometry(20, 5), butterflyMat);
        butterflyPlane.position.set(-2, 6, 18);
        butterflyPlane.rotation.y = Math.PI;
        scene.add(butterflyPlane);

        // ==========================================
        // 12. 前景床铺 (凉席顶点变形)
        // ==========================================
        // 凉席
        const matGeo = new THREE.BoxGeometry(24, 0.6, 14, 20, 2, 20);
        const matTexture = createMatTexture();
        const bedMat = new THREE.MeshStandardMaterial({ map: matTexture, roughness: 0.95 });
        const bedBase = new THREE.Mesh(matGeo, bedMat);
        // 让床铺边缘有轻微的起伏
        const bPosAttr = matGeo.attributes.position;
        for(let i=0; i<bPosAttr.count; i++) {
            const x = bPosAttr.getX(i);
            const z = bPosAttr.getZ(i);
            // 边缘翘起
            if (Math.abs(x) > 10 || Math.abs(z) > 6) {
                bPosAttr.setY(i, bPosAttr.getY(i) + Math.random() * 0.1);
            }
        }
        bedBase.geometry.computeVertexNormals();
        bedBase.position.set(-2, 0.3, 15);
        bedBase.receiveShadow = true;
        scene.add(bedBase);

        // 床铺白边
        const bedEdgeMat = new THREE.MeshStandardMaterial({ color: 0xd3d3d3, roughness: 0.8 });
        const bedFrontEdge = new THREE.Mesh(new THREE.BoxGeometry(24.2, 0.2, 0.6), bedEdgeMat);
        bedFrontEdge.position.set(-2, 0.8, 21.8); bedFrontEdge.receiveShadow = true; scene.add(bedFrontEdge);

        // 灰色靠背大抱枕
        const bigPillowGeo = new THREE.BoxGeometry(6, 4, 1.5, 10, 10, 10);
        const bigPillow = new THREE.Mesh(bigPillowGeo, matFabricGray);
        // 抱枕褶皱
        const pPosAttr = bigPillowGeo.attributes.position;
        for(let i=0; i<pPosAttr.count; i++) {
            const x = pPosAttr.getX(i);
            const y = pPosAttr.getY(i);
            pPosAttr.setZ(i, pPosAttr.getZ(i) + Math.sin(x*2) * 0.1 + Math.cos(y*2) * 0.1);
        }
        bigPillow.geometry.computeVertexNormals();
        bigPillow.position.set(-6, 3, 17); bigPillow.rotation.x = 0.1; bigPillow.rotation.y = 0.2;
        bigPillow.castShadow = true; scene.add(bigPillow);

        // 紫色花纹抱枕
        const patternPillow = new THREE.Mesh(new THREE.BoxGeometry(4, 1.5, 2.5), new THREE.MeshStandardMaterial({ color: 0xd8bfd8, roughness: 0.9 }));
        patternPillow.position.set(-2, 1.5, 16); patternPillow.rotation.y = -0.3;
        patternPillow.castShadow = true; scene.add(patternPillow);

        // ==========================================
        // 13. 动画与渲染
        // ==========================================
        function animate() {
            requestAnimationFrame(animate);
            
            // 风扇旋转动画
            if (fanGroup.children[4]) { // 扇叶组
                fanGroup.children[4].rotation.z += 0.2;
            }
            
            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 (691 lines, 35.5 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-mujjvr59" to link back to the original. Then publish at arcadelab.ai/publish.