react下实现threeJs加载fbx格式三维模型并实现爆炸拆解和热点交互效果

先看一下最终效果

1.前端工程中安装threeJs,加载fbx模型必须使用FBXLoader,FBXLoader在three-stdlib库中

npm install three
npm install three-stdlib

2. 创建组件,引入相关依赖,绑定需要渲染的dom对象。

import * as Three from 'three';
import {useEffect, useRef} from 'react';
import {FBXLoader} from "three-stdlib";
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';

export default () => {
  const canvasRef = useRef();
  return (
      <div ref={canvasRef}>

      </div>
  )
}

3.添加场景、相机、渲染器,并挂载到dom上

// 场景
  const scene = new Three.Scene();
  const light = new Three.HemisphereLight( '#ffffff', '#000000', 2 );
  scene.add( light );
  // 辅助线
  scene.add(new Three.AxesHelper(1000))
  // 相机
  const camera = new Three.PerspectiveCamera(50, 2, 0.1, 10000);
  camera.position.y = 100;
  camera.position.z = 500;
  camera.lookAt(scene.position)
  //渲染器
  const renderer = new Three.WebGLRenderer();
  renderer.setClearColor('#f2f2f2');
  renderer.setSize(600, 300);
  const render = () => {
    renderer.render(scene, camera)
  }
  renderer.setAnimationLoop(render)

  const loader = new FBXLoader();
  loader.load('./02.fbx',(fbx) => {
    console.log('fbx', fbx)
    // fbx.scale.set(0.01, 0.01, 0.01);
    scene.add(fbx)
  })

  useEffect(() => {
    canvasRef?.current.appendChild(renderer.domElement);
  },[])

这时模型已经能正常加载

4.加载完模型后解析模型对象信息

// 调整模型大小和位置
            const box = new Three.Box3().setFromObject(fbx);
            const size = box.getSize(new Three.Vector3());
            const maxDim = Math.max(size.x, size.y, size.z);
            const scale = 200 / maxDim;
            fbx.scale.multiplyScalar(scale);

            // 将模型居中并保存中心点
            const center = box.getCenter(new Three.Vector3());
            fbx.position.sub(center.multiplyScalar(scale));
            const modelCenter = new Three.Vector3(0, 0, 0);

            // 收集所有网格
            const parts = [];
            fbx.traverse((child) => {
                if (child.isMesh) {
                    child.geometry = child.geometry.clone();

                    if (child.material) {
                        if (Array.isArray(child.material)) {
                            child.material = child.material.map(mat =>
                                mat ? new Three.MeshPhongMaterial().copy(mat) : new Three.MeshPhongMaterial()
                            );
                        } else {
                            child.material = new Three.MeshPhongMaterial().copy(child.material);
                        }
                    } else {
                        child.material = new Three.MeshPhongMaterial({ color: 0x808080 });
                    }
                    // 保存原始材质颜色
                    if (Array.isArray(child.material)) {
                        child.userData.originalColor = child.material.map(mat => mat.color.clone());
                    } else {
                        child.userData.originalColor = child.material.color.clone();
                    }

                    // 保存原始位置和爆炸进度
                    child.userData.originalPosition = child.position.clone();
                    child.userData.explosionProgress = 0;
                    parts.push(child);
                }
            });

5.定义爆炸方向

const explosionDirections = [
        new Three.Vector3(1, 0, 0),    // 右
        new Three.Vector3(-1, 0, 0),   // 左
        new Three.Vector3(0, 1, 0),    // 上
        new Three.Vector3(0, -1, 0),   // 下
        new Three.Vector3(0, 0, 1),    // 前
        new Three.Vector3(0, 0, -1),   // 后
        new Three.Vector3(1, 1, 0),    // 右上
        new Three.Vector3(-1, 1, 0),   // 左上
        new Three.Vector3(1, -1, 0),   // 右下
        new Three.Vector3(-1, -1, 0),  // 左下
        new Three.Vector3(1, 0, 1),    // 右前
        new Three.Vector3(-1, 0, 1),   // 左前
        new Three.Vector3(1, 0, -1),   // 右后
        new Three.Vector3(-1, 0, -1),  // 左后
        new Three.Vector3(0, 1, 1),    // 上前
        new Three.Vector3(0, -1, 1),   // 下前
        new Three.Vector3(0, 1, -1),   // 上后
        new Three.Vector3(0, -1, -1),  // 下后
    ];

6.模型扩散代码

if (boomStateRef.current) { //爆炸扩散判断依据
                modelPartsRef.current.forEach((part, index) => {
                    if (part.userData.explosionProgress < 1) {
                        // 获取该部件的爆炸方向
                        const direction = explosionDirections[index % explosionDirections.length];
                        const distance = 200;
                        
                        // 更新爆炸进度
                        part.userData.explosionProgress += 0.02;
                        
                        // 计算目标位置
                        const targetX = direction.x * distance;
                        const targetY = direction.y * distance;
                        const targetZ = direction.z * distance;
                        
                        // 使用缓动函数计算当前位置
                        const progress = part.userData.explosionProgress;
                        const easeProgress = progress * progress * (3 - 2 * progress); // 平滑缓动
                        
                        part.position.x = targetX * easeProgress;
                        part.position.y = targetY * easeProgress;
                        part.position.z = targetZ * easeProgress;
                    }
                });
            } else {
                modelPartsRef.current.forEach(part => {
                    if (part.userData.explosionProgress > 0) {
                        // 更新还原进度
                        part.userData.explosionProgress -= 0.02;
                        
                        // 计算当前位置
                        const progress = part.userData.explosionProgress;
                        const easeProgress = progress * progress * (3 - 2 * progress);
                        
                        // 还原到原始位置
                        part.position.x = part.userData.originalPosition.x * easeProgress;
                        part.position.y = part.userData.originalPosition.y * easeProgress;
                        part.position.z = part.userData.originalPosition.z * easeProgress;
                    }
                });
            }

获取源码+v: X1161876604    

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值