🎮ArcadeLab

我的房间 - 全景 3D 还原

by EpicCoder88
474 lines22.8 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; }
        canvas { display: block; }
        #info {
            position: absolute; bottom: 20px; left: 20px;
            color: rgba(255,255,255,0.7); font-family: sans-serif; font-size: 12px;
            pointer-events: none; background: rgba(0,0,0,0.5); padding: 10px; border-radius: 5px;
        }
    </style>
</head>
<body>
    <div id="info">鼠标左键旋转 | 滚轮缩放 | 右键平移<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(0xf0f4f8);
        scene.fog = new THREE.Fog(0xf0f4f8, 25, 60);

        const camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 100);
        // 调整相机位置,展示整个房间
        camera.position.set(16, 12, 22);
        camera.lookAt(0, 4, 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, 4, 0);
        controls.enableDamping = true;
        controls.dampingFactor = 0.05;
        controls.maxPolarAngle = Math.PI / 2; // 限制不能钻到地板下面

        // ==========================================
        // 2. 精细光照
        // ==========================================
        const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
        scene.add(ambientLight);

        // 主光源:模拟窗外自然光,从右前方照射
        const dirLight = new THREE.DirectionalLight(0xfff5e6, 1.2);
        dirLight.position.set(15, 15, 10);
        dirLight.castShadow = true;
        dirLight.shadow.mapSize.width = 2048;
        dirLight.shadow.mapSize.height = 2048;
        dirLight.shadow.camera.near = 1;
        dirLight.shadow.camera.far = 50;
        dirLight.shadow.camera.left = -20;
        dirLight.shadow.camera.right = 20;
        dirLight.shadow.camera.top = 20;
        dirLight.shadow.camera.bottom = -20;
        dirLight.shadow.bias = -0.0005;
        scene.add(dirLight);

        // 补光
        const fillLight = new THREE.DirectionalLight(0xffffff, 0.3);
        fillLight.position.set(-10, 10, -10);
        scene.add(fillLight);

        // ==========================================
        // 3. 工具函数与纹理生成
        // ==========================================
        const createMat = (color, roughness = 0.8, metalness = 0.0, map = null) => {
            return new THREE.MeshStandardMaterial({ color, roughness, metalness, map });
        };

        // 生成水渍纹理
        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(180, 150, 110, 0.4)';
            for(let i=0; i<20; i++) {
                ctx.beginPath();
                ctx.arc(256 + Math.random()*100 - 50, 256 + Math.random()*100 - 50, 50 + Math.random()*100, 0, Math.PI*2);
                ctx.fill();
            }
            const texture = new THREE.CanvasTexture(canvas);
            return texture;
        }

        // 生成凉席纹理
        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 += 8) {
                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 texture = new THREE.CanvasTexture(canvas);
            texture.wrapS = THREE.RepeatWrapping; texture.wrapT = THREE.RepeatWrapping;
            texture.repeat.set(4, 4);
            return texture;
        }
        const matTexture = createMatTexture();

        // ==========================================
        // 4. 房间基础结构
        // ==========================================
        // 地板
        const floorMat = createMat(0xdcdfe3, 0.6, 0.1);
        const floor = new THREE.Mesh(new THREE.PlaneGeometry(30, 30), floorMat);
        floor.rotation.x = -Math.PI / 2;
        floor.receiveShadow = true;
        scene.add(floor);

        // 墙壁
        const wallMat = createMat(0xfdfaf7, 0.9);
        const wallThickness = 0.5;
        // 左侧墙 (x = -15)
        const leftWall = new THREE.Mesh(new THREE.BoxGeometry(wallThickness, 15, 30), wallMat);
        leftWall.position.set(-15, 7.5, 0);
        leftWall.receiveShadow = true; scene.add(leftWall);
        // 后侧墙 (z = -15)
        const backWall = new THREE.Mesh(new THREE.BoxGeometry(30, 15, wallThickness), wallMat);
        backWall.position.set(0, 7.5, -15);
        backWall.receiveShadow = true; scene.add(backWall);
        // 右侧墙 (x = 15)
        const rightWall = new THREE.Mesh(new THREE.BoxGeometry(wallThickness, 15, 30), wallMat);
        rightWall.position.set(15, 7.5, 0);
        rightWall.receiveShadow = true; scene.add(rightWall);

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

        // 墙面水渍 (右墙飘窗下方)
        const stainRight = new THREE.Mesh(new THREE.PlaneGeometry(8, 6), stainMat);
        stainRight.position.set(14.7, 2.5, 2);
        stainRight.rotation.y = -Math.PI / 2;
        scene.add(stainRight);

        // 地脚线
        const baseboardMat = createMat(0x333333, 0.8);
        const baseboardBack = new THREE.Mesh(new THREE.BoxGeometry(30, 0.6, 0.2), baseboardMat);
        baseboardBack.position.set(0, 0.3, -14.9);
        scene.add(baseboardBack);
        
        const baseboardRight = new THREE.Mesh(new THREE.BoxGeometry(0.2, 0.6, 30), baseboardMat);
        baseboardRight.position.set(14.9, 0.3, 0);
        scene.add(baseboardRight);

        // ==========================================
        // 5. 核心家具重建 (书桌、空调、风扇、椅子)
        // ==========================================
        
        // --- 壁挂空调 ---
        const acGroup = new THREE.Group();
        const acBody = new THREE.Mesh(new THREE.BoxGeometry(4, 1.2, 0.8), createMat(0xffffff, 0.4));
        acBody.castShadow = true; acBody.receiveShadow = true;
        acGroup.add(acBody);
        const acVent = new THREE.Mesh(new THREE.BoxGeometry(3.6, 0.15, 0.1), createMat(0xdddddd, 0.2));
        acVent.position.set(0, -0.45, 0.35); acGroup.add(acVent);
        // 管道
        const pipeCurve = new THREE.CatmullRomCurve3([
            new THREE.Vector3(2, -0.2, 0), new THREE.Vector3(4, -0.8, 0.5),
            new THREE.Vector3(6, -4, 0.5), new THREE.Vector3(6, -8, 0.5)
        ]);
        const pipe = new THREE.Mesh(new THREE.TubeGeometry(pipeCurve, 20, 0.05, 8, false), createMat(0xeeeeee, 0.8));
        acGroup.add(pipe);
        acGroup.position.set(0, 11, -14.6);
        scene.add(acGroup);

        // --- 右侧书桌 (带抽屉和电脑) ---
        const deskGroup = new THREE.Group();
        const deskX = 8, deskY = 2.5, deskZ = -12;
        const deskTop = new THREE.Mesh(new THREE.BoxGeometry(6, 0.2, 2.5), createMat(0x8b5a2b, 0.6));
        deskTop.castShadow = true; deskTop.receiveShadow = true;
        deskGroup.add(deskTop);
        // 抽屉
        const drawerBody = new THREE.Mesh(new THREE.BoxGeometry(2, 3.8, 2.3), createMat(0x754a22, 0.7));
        drawerBody.position.set(-2, -2, 0); drawerBody.castShadow = true; drawerBody.receiveShadow = true;
        deskGroup.add(drawerBody);
        // 显示器
        const monitorBase = new THREE.Mesh(new THREE.CylinderGeometry(0.5, 0.7, 0.2, 16), createMat(0x222222, 0.4));
        monitorBase.position.set(1.5, 0.2, 0); deskGroup.add(monitorBase);
        const monitorStand = new THREE.Mesh(new THREE.CylinderGeometry(0.08, 0.08, 1, 8), createMat(0x333333, 0.4));
        monitorStand.position.set(1.5, 0.7, 0); deskGroup.add(monitorStand);
        const screen = new THREE.Mesh(new THREE.BoxGeometry(2, 1.3, 0.1), createMat(0x111111, 0.2));
        screen.position.set(1.5, 1.5, 0); screen.castShadow = true; deskGroup.add(screen);
        // 电脑主机
        const pcTower = new THREE.Mesh(new THREE.BoxGeometry(1, 2.5, 1.8), createMat(0x1a1a1a, 0.3, 0.5));
        pcTower.position.set(2.5, -1.25, 0.5); pcTower.castShadow = true; pcTower.receiveShadow = true;
        deskGroup.add(pcTower);
        
        deskGroup.position.set(deskX, deskY, deskZ);
        scene.add(deskGroup);

        // --- 粉色转椅 ---
        const chairGroup = new THREE.Group();
        const backGeo = new THREE.CylinderGeometry(1.1, 1.1, 2, 32, 1, false, 0, Math.PI);
        const chairMat = createMat(0xf4b6c2, 0.6);
        const chairBack = new THREE.Mesh(backGeo, chairMat);
        chairBack.rotation.y = Math.PI; chairBack.position.set(0, 1.2, -0.7);
        chairBack.scale.set(1, 1, 0.6); chairBack.castShadow = true; chairGroup.add(chairBack);
        
        const cushion = new THREE.Mesh(new THREE.BoxGeometry(1.8, 0.4, 1.8), chairMat);
        cushion.castShadow = true; chairGroup.add(cushion);
        
        const pole = new THREE.Mesh(new THREE.CylinderGeometry(0.15, 0.15, 1.5, 12), createMat(0xcccccc, 0.2, 0.8));
        pole.position.set(0, -0.8, 0); chairGroup.add(pole);
        
        const base = new THREE.Mesh(new THREE.CylinderGeometry(1.3, 1.3, 0.1, 32), createMat(0xffffff, 0.3));
        base.position.set(0, -1.5, 0); base.castShadow = true; chairGroup.add(base);
        
        const wheelMat = createMat(0x333333, 0.5);
        for(let i=0; i<5; i++) {
            const angle = (i / 5) * Math.PI * 2;
            const wheel = new THREE.Mesh(new THREE.CylinderGeometry(0.12, 0.12, 0.1, 16), wheelMat);
            wheel.rotation.z = Math.PI / 2;
            wheel.position.set(Math.cos(angle) * 1.2, -1.6, Math.sin(angle) * 1.2);
            chairGroup.add(wheel);
        }
        chairGroup.position.set(6, 2.5, -9);
        scene.add(chairGroup);

        // ==========================================
        // 6. 新增区域:右侧飘窗与窗户
        // ==========================================
        const windowX = 14.7, windowZ = 2;
        
        // 窗台(飘窗台面)
        const windowSillMat = createMat(0xffffff, 0.7);
        const windowSill = new THREE.Mesh(new THREE.BoxGeometry(2.5, 0.5, 10), windowSillMat);
        windowSill.position.set(windowX - 1.25, 2, windowZ);
        windowSill.receiveShadow = true; scene.add(windowSill);

        // 飘窗毛绒垫(绿色)
        const rugMat = createMat(0x7ec850, 1.0);
        const rug = new THREE.Mesh(new THREE.BoxGeometry(2.3, 0.1, 9.5), rugMat);
        rug.position.set(windowX - 1.25, 2.3, windowZ);
        rug.receiveShadow = true; scene.add(rug);

        // 窗户边框(绿色)
        const frameMat = createMat(0x2e8b57, 0.6);
        const frameTop = new THREE.Mesh(new THREE.BoxGeometry(0.2, 0.3, 10), frameMat);
        frameTop.position.set(windowX, 6.5, windowZ); scene.add(frameTop);
        const frameBottom = new THREE.Mesh(new THREE.BoxGeometry(0.2, 0.3, 10), frameMat);
        frameBottom.position.set(windowX, 2.5, windowZ); scene.add(frameBottom);
        const frameLeft = new THREE.Mesh(new THREE.BoxGeometry(0.2, 4, 0.3), frameMat);
        frameLeft.position.set(windowX, 4.5, windowZ - 4.85); scene.add(frameLeft);
        const frameRight = new THREE.Mesh(new THREE.BoxGeometry(0.2, 4, 0.3), frameMat);
        frameRight.position.set(windowX, 4.5, windowZ + 4.85); scene.add(frameRight);
        
        // 窗户玻璃
        const glassMat = new THREE.MeshStandardMaterial({ color: 0xaaccff, transparent: true, opacity: 0.2, roughness: 0.1, metalness: 0.1 });
        const glass = new THREE.Mesh(new THREE.PlaneGeometry(9.5, 4), glassMat);
        glass.position.set(windowX, 4.5, windowZ);
        glass.rotation.y = -Math.PI / 2;
        scene.add(glass);

        // 防盗网(简化为细圆柱体)
        const barMat = createMat(0xaaaaaa, 0.5, 0.8);
        for(let i = -4; i <= 4; i+=1) {
            const bar = new THREE.Mesh(new THREE.CylinderGeometry(0.04, 0.04, 4, 8), barMat);
            bar.position.set(windowX - 0.1, 4.5, windowZ + i * 1.1);
            scene.add(bar);
        }

        // 飘窗上的被子(用不规则的几何体模拟)
        const quiltMat = createMat(0xbbccee, 0.9);
        const quilt = new THREE.Mesh(new THREE.BoxGeometry(1.8, 0.8, 3), quiltMat);
        quilt.position.set(windowX - 1, 2.8, windowZ - 1);
        quilt.rotation.z = 0.1; quilt.castShadow = true;
        scene.add(quilt);
        
        // 飘窗上的枕头
        const pillowMat = createMat(0xffee88, 0.9);
        const pillow = new THREE.Mesh(new THREE.BoxGeometry(1.5, 0.6, 1.2), pillowMat);
        pillow.position.set(windowX - 1.2, 2.8, windowZ + 3.5);
        pillow.rotation.y = 0.5; pillow.castShadow = true;
        scene.add(pillow);

        // 窗帘(波浪形)
        const curtainMat = createMat(0xfdfaf7, 0.9, 0.0, createMatTextureWithPattern());
        function createMatTextureWithPattern() {
            const canvas = document.createElement('canvas');
            canvas.width = 256; canvas.height = 256;
            const ctx = canvas.getContext('2d');
            ctx.fillStyle = '#fdfaf7'; ctx.fillRect(0, 0, 256, 256);
            ctx.fillStyle = '#fce4ec'; // 淡粉色花纹
            for(let i=0; i<256; i+=32) {
                ctx.beginPath(); ctx.arc(i, 64, 10, 0, Math.PI*2); ctx.fill();
                ctx.beginPath(); ctx.arc(i+16, 192, 10, 0, Math.PI*2); ctx.fill();
            }
            const tex = new THREE.CanvasTexture(canvas);
            tex.wrapS = THREE.RepeatWrapping; tex.wrapT = THREE.RepeatWrapping;
            tex.repeat.set(2, 4);
            return tex;
        }
        
        const curtainGeo = new THREE.PlaneGeometry(2.5, 12, 20, 20);
        const curtain = new THREE.Mesh(curtainGeo, curtainMat);
        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) * 0.2);
        }
        curtain.geometry.computeVertexNormals();
        curtain.position.set(windowX - 0.5, 7, windowZ - 4);
        curtain.rotation.y = -Math.PI / 2;
        scene.add(curtain);

        // 帷幔(顶部装饰)
        const valance = new THREE.Mesh(new THREE.BoxGeometry(0.5, 1.5, 10), curtainMat);
        valance.position.set(windowX - 0.5, 10, windowZ);
        scene.add(valance);

        // ==========================================
        // 7. 新增区域:左侧衣柜与烘干机
        // ==========================================
        const wardrobeX = -12, wardrobeZ = -7;
        
        // 衣柜主体(木色)
        const wardrobeMat = createMat(0x8b5a2b, 0.7);
        const wardrobeBody = new THREE.Mesh(new THREE.BoxGeometry(6, 10, 4), wardrobeMat);
        wardrobeBody.position.set(wardrobeX, 5, wardrobeZ);
        wardrobeBody.castShadow = true; wardrobeBody.receiveShadow = true;
        scene.add(wardrobeBody);

        // 衣柜门(磨砂玻璃)
        const doorMat = new THREE.MeshStandardMaterial({ color: 0xcce0cc, transparent: true, opacity: 0.5, roughness: 0.1 });
        const wardrobeDoor = new THREE.Mesh(new THREE.BoxGeometry(0.2, 9.5, 3.5), doorMat);
        wardrobeDoor.position.set(wardrobeX + 3, 4.8, wardrobeZ);
        scene.add(wardrobeDoor);

        // 衣柜内部隔板
        const shelfMat = createMat(0x654321, 0.8);
        for(let y = 2; y <= 8; y += 2) {
            const shelf = new THREE.Mesh(new THREE.BoxGeometry(5.5, 0.2, 3.5), shelfMat);
            shelf.position.set(wardrobeX - 0.2, y, wardrobeZ);
            scene.add(shelf);
        }

        // --- 蓝色便携烘干机 ---
        const dryerX = -6, dryerZ = -11;
        const dryerBody = new THREE.Mesh(new THREE.BoxGeometry(3, 6, 3), createMat(0x5bbce4, 0.7));
        dryerBody.position.set(dryerX, 3, dryerZ);
        dryerBody.castShadow = true; dryerBody.receiveShadow = true;
        scene.add(dryerBody);
        
        // 烘干机拉链口(用深色平面模拟)
        const dryerZip = new THREE.Mesh(new THREE.PlaneGeometry(1.5, 3), createMat(0x1a6a99, 0.5));
        dryerZip.position.set(dryerX, 3, dryerZ + 1.51);
        scene.add(dryerZip);
        
        // 烘干机底座
        const dryerBase = new THREE.Mesh(new THREE.BoxGeometry(3.2, 0.5, 3.2), createMat(0xffffff, 0.5));
        dryerBase.position.set(dryerX, 0.25, dryerZ);
        scene.add(dryerBase);

        // --- 木制衣帽架 ---
        const rackX = -3, rackZ = -12;
        const rackPole = new THREE.Mesh(new THREE.CylinderGeometry(0.15, 0.15, 9, 12), wardrobeMat);
        rackPole.position.set(rackX, 4.5, rackZ);
        rackPole.castShadow = true; scene.add(rackPole);
        
        // 挂钩
        for(let i=0; i<4; i++) {
            const angle = (i / 4) * Math.PI * 2;
            const hook = new THREE.Mesh(new THREE.CylinderGeometry(0.05, 0.05, 1.5, 8), wardrobeMat);
            hook.rotation.z = Math.PI / 2;
            hook.position.set(rackX + Math.cos(angle)*0.75, 8, rackZ + Math.sin(angle)*0.75);
            hook.rotation.y = angle;
            scene.add(hook);
        }

        // 挂着的衣服(简化为不同颜色的方块)
        const clothMat1 = createMat(0xffffff, 0.9); // 白色衬衫
        const cloth1 = new THREE.Mesh(new THREE.BoxGeometry(0.8, 2.5, 0.1), clothMat1);
        cloth1.position.set(rackX, 7, rackZ + 0.5); cloth1.rotation.y = 0.2; scene.add(cloth1);
        
        const clothMat2 = createMat(0x444444, 0.9); // 黑色外套
        const cloth2 = new THREE.Mesh(new THREE.BoxGeometry(1, 2.8, 0.2), clothMat2);
        cloth2.position.set(rackX - 0.5, 6.5, rackZ - 0.5); cloth2.rotation.y = -0.3; scene.add(cloth2);

        // ==========================================
        // 8. 新增区域:床边角落(床头柜、藤椅)
        // ==========================================
        // 白色小床头柜
        const tableX = 11, tableZ = 8;
        const nightstand = new THREE.Mesh(new THREE.BoxGeometry(2, 3, 2), createMat(0xf0f0f0, 0.8));
        nightstand.position.set(tableX, 1.5, tableZ);
        nightstand.castShadow = true; nightstand.receiveShadow = true;
        scene.add(nightstand);
        
        // 床头柜上的纸张
        const paper = new THREE.Mesh(new THREE.PlaneGeometry(1.5, 1), createMat(0xffffff, 0.9));
        paper.rotation.x = -Math.PI / 2; paper.position.set(tableX, 3.01, tableZ);
        scene.add(paper);

        // 藤编圆椅(用棕色圆环和圆柱模拟)
        const rattanX = 14, rattanZ = 8;
        const rattanBase = new THREE.Mesh(new THREE.CylinderGeometry(1.2, 1.2, 0.2, 16), createMat(0xa0522d, 0.9));
        rattanBase.position.set(rattanX, 1, rattanZ); rattanBase.castShadow = true;
        scene.add(rattanBase);
        const rattanBack = new THREE.Mesh(new THREE.TorusGeometry(1, 0.1, 8, 20, Math.PI), createMat(0xa0522d, 0.9));
        rattanBack.position.set(rattanX, 2, rattanZ); rattanBack.rotation.y = Math.PI/2; rattanBack.rotation.x = Math.PI/4;
        scene.add(rattanBack);
        
        // 黄色卡通抱枕(简化为黄色球体+白色球体组合)
        const plushYellow = new THREE.Mesh(new THREE.SphereGeometry(0.8, 16, 16), createMat(0xffcc00, 0.8));
        plushYellow.position.set(rattanX, 1.8, rattanZ); plushYellow.scale.set(1, 0.8, 1); plushYellow.castShadow = true;
        scene.add(plushYellow);
        const plushWhite = new THREE.Mesh(new THREE.SphereGeometry(0.5, 16, 16), createMat(0xffffff, 0.8));
        plushWhite.position.set(rattanX, 2.2, rattanZ - 0.2); plushWhite.castShadow = true;
        scene.add(plushWhite);

        // ==========================================
        // 9. 前景床铺 (凉席)
        // ==========================================
        const bedBase = new THREE.Mesh(new THREE.BoxGeometry(20, 0.8, 10), createMat(0x9e5a38, 0.9, 0.0, matTexture));
        bedBase.position.set(0, 0.4, 12);
        bedBase.receiveShadow = true;
        scene.add(bedBase);
        
        // 床铺白边
        const bedEdgeMat = createMat(0xd3d3d3, 0.8);
        const bedFrontEdge = new THREE.Mesh(new THREE.BoxGeometry(20.2, 0.2, 0.5), bedEdgeMat);
        bedFrontEdge.position.set(0, 0.9, 16.75); bedFrontEdge.receiveShadow = true; scene.add(bedFrontEdge);
        const bedLeftEdge = new THREE.Mesh(new THREE.BoxGeometry(0.5, 0.2, 10), bedEdgeMat);
        bedLeftEdge.position.set(-9.85, 0.9, 12); bedLeftEdge.receiveShadow = true; scene.add(bedLeftEdge);
        const bedRightEdge = bedLeftEdge.clone(); bedRightEdge.position.x = 9.85; scene.add(bedRightEdge);

        // 床上的紫色花纹抱枕
        const pillowPatternMat = createMat(0xd8bfd8, 0.9);
        const bedPillow = new THREE.Mesh(new THREE.BoxGeometry(3, 0.8, 1.5), pillowPatternMat);
        bedPillow.position.set(-4, 1.3, 14); bedPillow.rotation.y = 0.2;
        scene.add(bedPillow);

        // ==========================================
        // 10. 动画与渲染循环
        // ==========================================
        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 (474 lines, 22.8 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" to link back to the original. Then publish at arcadelab.ai/publish.