用半天时间,threejs手搓了一个机柜

那是一个普通的周三早晨,我正对着产品经理刚丢过来的需求发呆------"在管理系统里加个3D机柜展示,要能开门的那种"。

"这不就是个模型展示吗?"我心想,"AI应该能搞定吧?"

9:30 AM - 启动摸鱼模式

我熟练地打开代码编辑器,把需求复制粘贴进AI对话框: "用Three.js实现一个带开门动画的机柜模型,要求有金属质感,门能90度旋转"

点击发送后,我惬意地靠在椅背上,顺手打开了B站。"让AI先忙会儿~"

10:30 AM - 验收时刻

一集《凡人修仙传》看完,我懒洋洋地切回编辑器。AI果然交出了答卷:

11:00 AM - 血压升高现场

看着AI生成的"未来科技风"机柜,我深吸一口气,决定亲自下场。毕竟,程序员最后的尊严就是------"还是自己来吧"。

11:30 AM - 手动抢救

首先手动创建一个空场景吧

js 复制代码
class SceneManager {
  constructor() {
    this.scene = new THREE.Scene();
    this.camera = new THREE.PerspectiveCamera(
      75,
      window.innerWidth / window.innerHeight,
      0.1,
      1000
    );
    this.camera.position.set(0, 2, 5);

    this.renderer = new THREE.WebGLRenderer();
    this.renderer.setSize(window.innerWidth, window.innerHeight);
    const canvas = document.getElementById('renderCanvas');
    canvas.appendChild(this.renderer.domElement);

    this.controls = new OrbitControls(this.camera, this.renderer.domElement);
    this.controls.enableDamping = true;
    this.controls.dampingFactor = 0.05;
    this.controls.target.set(0, 3, 0);
    this.controls.update();

    this.addLights();
    this.addFloor();
  }

  addLights() {
    const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
    this.scene.add(ambientLight);

    const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
    directionalLight.position.set(5, 5, 5);
    this.scene.add(directionalLight);
  }

  addFloor() {
    const floorGeometry = new THREE.PlaneGeometry(10, 10);
    const floorMaterial = new THREE.MeshStandardMaterial({ color: 0x888888 });
    const floor = new THREE.Mesh(floorGeometry, floorMaterial);
    floor.rotation.x = -Math.PI / 2;
    this.scene.add(floor);
  }

  animate() {
    const animateLoop = () => {
      requestAnimationFrame(animateLoop);
      this.controls.update();
      this.renderer.render(this.scene, this.camera);
    };
    animateLoop();
  }

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

然后这机柜怎么画呢,不管了,先去吃个饭,天大地大肚子最大

12:30 PM - 程序员的能量补给时刻

淦!先干饭!" 我一把推开键盘,决定暂时逃离这个三维世界。毕竟------

  1. 饥饿值已经降到30%以下
  2. 右手开始不受控制地颤抖
  3. 看Three.js文档出现了重影

扒饭间隙,手机突然震动。产品经理发来消息:"那个3D机柜..."

我差点被饭粒呛到,赶紧回复:"正在深度优化用户体验!"

(十分钟风卷残云后)

1:00 PM - 回归正题

吃饱喝足,终于可以专心搞机柜了,(此处可怜一下我的午休)

拆分机柜结构

机柜的结构可以分为以下几个部分:

  1. 不可操作结构

    • 底部:承载整个机柜的重量,通常是一个坚固的平面。
    • 顶部:封闭机柜的顶部,提供额外的支撑。
    • 左侧和右侧:机柜的侧板,通常是固定的,用于保护内部设备。
  2. 可操作结构

    • 前门:单门设计,通常是透明或半透明材质,便于观察内部设备。
    • 后门:双开门设计,方便从后方接入设备的电缆和接口。

实现步骤

  1. 创建不可操作结构 : 使用BoxGeometry创建底部、顶部、左侧和右侧的平面,并将它们组合成一个整体。

  2. 添加前门: 前门使用透明材质,并设置旋转轴以实现开门动画。

  3. 添加后门: 后门分为左右两部分,分别设置旋转轴以实现双开门效果。

  4. 优化细节

    • 添加螺丝孔和通风口。
    • 使用高光材质提升视觉效果。

接下来,我们开始用代码实现这些结构。

机柜结构的实现

1. 创建不可操作结构

底部

javascript 复制代码
export function createCabinetBase(scene) {
  const geometry = new THREE.BoxGeometry(0.6,  0.05, 0.64);
  const base = new THREE.Mesh(geometry,  materials.baseMaterial);
  base.position.y = -0.05; // 调整位置
  scene.add(base);
}

底部使用BoxGeometry创建,设置了深灰色金属材质,位置调整为机柜的最底部。

顶部

javascript 复制代码
export function createCabinetTop(scene) {
  const geometry = new THREE.BoxGeometry(0.6, 0.05,  0.64);
  const top = new THREE.Mesh(geometry, materials.baseMaterial);
  top.position.y = 1.95; // 调整位置
  scene.add(top);
}

顶部与底部类似,位置调整为机柜的最顶部。

侧面

javascript 复制代码
export function createCabinetSides(scene) {
  const geometry = new THREE.BoxGeometry(0.04, 2, 0.6);
  const material = materials.baseMaterial;
  // 左侧面
  const leftSide = new THREE.Mesh(geometry, material);
  leftSide.position.set(-0.28, 0.95, 0); // 调整位置
  scene.add(leftSide);
  // 右侧面
  const rightSide = new THREE.Mesh(geometry, material);
  rightSide.position.set(0.28, 0.95, 0); // 调整位置
  scene.add(rightSide);
}

侧面使用两个BoxGeometry分别创建左侧和右侧,位置对称分布。

2. 创建可操作结构

前门

javascript 复制代码
export function createCabinetFrontDoor(scene) {
    const doorGroup = new THREE.Group();
    const doorWidth = 0.04;
    const doorHeight = 2;
    const doorDepth = 0.6;

    const frameMaterial = materials.baseMaterial;
    const frameThickness = 0.04;

    // 上边框
    const topFrameGeo = new THREE.BoxGeometry(doorWidth, frameThickness, doorDepth);
    const topFrame = new THREE.Mesh(topFrameGeo, frameMaterial);
    topFrame.position.set(0, 1 - frameThickness / 2, 0);
    doorGroup.add(topFrame);

    // 下边框
    const bottomFrameGeo = new THREE.BoxGeometry(doorWidth, frameThickness, doorDepth);
    const bottomFrame = new THREE.Mesh(bottomFrameGeo, frameMaterial);
    bottomFrame.position.set(0, -doorHeight / 2 + 0.05, 0);
    doorGroup.add(bottomFrame);

    // 左右边框
    const leftFrameGeo = new THREE.BoxGeometry(doorWidth, doorHeight - 2 * frameThickness, frameThickness);
    const leftFrame = new THREE.Mesh(leftFrameGeo, frameMaterial);
    leftFrame.position.set(0, 1 - doorHeight / 2, -doorDepth / 2 + frameThickness / 2);
    doorGroup.add(leftFrame);

    const rightFrameGeo = new THREE.BoxGeometry(doorWidth, doorHeight - 2 * frameThickness, frameThickness);
    const rightFrame = new THREE.Mesh(rightFrameGeo, frameMaterial);
    rightFrame.position.set(0, 1 - doorHeight / 2, doorDepth / 2 - frameThickness / 2);
    doorGroup.add(rightFrame);

    scene.add(doorGroup);
    return doorGroup;
}

前门由一个Group组装而成,包含上下左右边框,材质与机柜一致,后续将添加玻璃部分和动画。

前门动画的实现

前门的动画使用gsap库实现,设置旋转轴为左侧边框。

javascript 复制代码
    gsap.to(frontDoor.rotation, {
        y: Math.PI / 2, // 90度旋转
        duration: 1,   // 动画时长
        ease: "power2.inOut",
    });

通过gsap.to方法,前门可以实现平滑的开门效果。

3. 添加后门

后门采用双开设计,左右两扇门分别由多个边框组成,并通过Group进行组合。 为了优化细节我还加入了网孔结构(此处心疼一下我为写他掉的头发)

后门的实现

javascript 复制代码
export function createCabinetBackDoor(scene) {
    const doorGroup = new THREE.Group();
    const doorWidth = 0.04;
    const doorHeight = 2;
    const doorDepth = 0.6;
    const singleDoorDepth = doorDepth / 2;
    const frameMaterial = materials.baseMaterial;
    const frameThickness = 0.04;

    function createSingleBackDoor(isLeft) {
        const singleGroup = new THREE.Group();

        // 上边框
        const topFrameGeo = new THREE.BoxGeometry(doorWidth, frameThickness, singleDoorDepth);
        const topFrame = new THREE.Mesh(topFrameGeo, frameMaterial);
        topFrame.position.set(0, 1 - frameThickness / 2, 0);
        singleGroup.add(topFrame);

        // 下边框
        const bottomFrameGeo = new THREE.BoxGeometry(doorWidth, frameThickness, singleDoorDepth);
        const bottomFrame = new THREE.Mesh(bottomFrameGeo, frameMaterial);
        bottomFrame.position.set(0, -doorHeight / 2 + 0.05, 0);
        singleGroup.add(bottomFrame);

        // 外侧边框
        const sideFrameGeo = new THREE.BoxGeometry(doorWidth, doorHeight - 2 * frameThickness, frameThickness);
        const sideFrame = new THREE.Mesh(sideFrameGeo, frameMaterial);
        sideFrame.position.set(
            0,
            1 - doorHeight / 2,
            isLeft
                ? -singleDoorDepth / 2 + frameThickness / 2
                : singleDoorDepth / 2 - frameThickness / 2
        );
        singleGroup.add(sideFrame);

        return singleGroup;
    }

    const leftDoor = createSingleBackDoor(true);
    const rightDoor = createSingleBackDoor(false);

    doorGroup.add(leftDoor);
    doorGroup.add(rightDoor);
    scene.add(doorGroup);

    return { group: doorGroup, leftDoor, rightDoor };
}

后门的实现与前门类似,采用双扇门设计,左右各一扇。

后门动画的实现

后门的动画同样使用gsap库实现,分别设置左右门的旋转轴。

javascript 复制代码
 gsap.to(leftDoor.rotation, {
        y: Math.PI / 2, // 左门向外旋转90度
        duration: 1,
        ease: "power2.inOut",
    });

    gsap.to(rightDoor.rotation, {
        y: -Math.PI / 2, // 右门向外旋转90度
        duration: 1,
        ease: "power2.inOut",
    });

通过gsap.to方法,后门可以实现平滑的双开效果。

2:00 PM - 项目收尾

终于,随着最后一行代码的敲定,3D机柜模型在屏幕上完美呈现。前门优雅地打开,后门平滑地双开,仿佛在向我点头致意。

我靠在椅背上,长舒一口气,心中默念:"果然,程序员的尊严还是要靠自己守护。"

可拓展功能

虽然当前的3D机柜模型已经实现了基本的展示和交互功能,但在实际项目中,我们可以进一步扩展以下功能:

1. U位标记

2. U位资产管理

3. 动态灯光效果

4. 数据联动

将3D机柜与后台数据联动:

  • 实时更新设备状态。
  • 显示设备的实时监控数据(如温度、功耗等)。
  • 支持通过API接口获取和更新设备信息。

不说了,需求又来了()我还是继续去搬砖了

相关推荐
Ace_31750887767 分钟前
义乌购平台店铺商品接口开发指南
前端
ZJ_9 分钟前
Electron自动更新详解—包教会版
前端·javascript·electron
哆啦美玲10 分钟前
Callback 🥊 Promise 🥊 Async/Await:谁才是异步之王?
前端·javascript·面试
brzhang17 分钟前
我们复盘了100个失败的AI Agent项目,总结出这3个“必踩的坑”
前端·后端·架构
万能的小裴同学25 分钟前
让没有小窗播放的视频网站的视频小窗播放
前端·javascript
小小琪_Bmob后端云1 小时前
引流之评论区截流实验
前端·后端·产品
我科绝伦(Huanhuan Zhou)1 小时前
MOP数据库备份脚本生成工具
前端·css·数据库
海的诗篇_1 小时前
前端开发面试题总结-vue2框架篇(三)
前端·javascript·css·面试·vue·html
Danny_FD1 小时前
在 React 函数组件中实现 `<textarea>` 平滑自动滚动到底部
前端
掘金一周1 小时前
数据脱敏的这6种方案,真香!| 掘金一周 5.29
前端·人工智能·后端