一切|

sy0313

园龄:4年9个月粉丝:10关注:1

three.js中使用r3f在模型上打多处孔洞(模型相减)

在日常开发中接到一个需求,需要在three.js构建的3D场景中模拟激光打孔的操作,通过一个名为three-csg-ts库实现模型相减的操作。

之前使用一个名为@react-three/csg的库,但是官方文档中也注明,此库功能不算完善,使用之后发现,多个模型相减做不到相对动态的构建。所以换了three-csg-ts,动态构建模型相减比较灵活。
对于射线和目标物相交的点,一开始的想法是引入物理库,但是测试后发现,物理库的碰撞点有不小的偏差,与直接使用THREE.Ray区别不大,而且还需要下载额外的包,所以直接使用的THREE.Ray,得到和标的物相交的世界坐标后,转化成组内的局部坐标,在局部坐标中的标的物上打上对应孔洞。

新建孔洞函数:

//新建孔洞
const hole = new THREE.Mesh(new THREE.CylinderGeometry(0.01, 0.01, 5, 32));
hole.visible = false;
const createHole = (position: number[]) => {
  // const hole = new THREE.Mesh(new THREE.CylinderGeometry(0.01, 0.01, 5, 32));
  hole.rotation.x = Math.PI / 2;
  hole.position.set(position[0], position[1], position[2]);
  hole.updateMatrix();
  return hole;
};

组合多个相减形状:

//组合形状
const additionShap = (
  list = [
    [0, 0, 0],
    [0.03, 0.24, -1.04],
  ]
): THREE.Mesh => {
  let result;
  if (list.length > 1) {
    result = list.reduce((pre: any, item): any => {
      // console.log("-----",pre instanceof Array);
      return CSG.union(
        pre instanceof Array ? createHole(pre) : pre,
        createHole(item)
      );
    });
  }
  if (list.length == 1) {
    result = CSG.union(createHole(list[0]), createHole([1000, 1000, 1000]));
  }

  return result as unknown as THREE.Mesh;
};

确定射线和透镜相交位置:

//确定射线和透镜相交位置
//targetRef 目标物
//局部坐标标志物
const getRayIntersect = (
  target: THREE.Mesh,
  localBox: THREE.Object3D
): THREE.Vector3 => {
  console.log("调用函数");
  let ray = new THREE.Ray(); //初始化射线
  ray.origin = new THREE.Vector3(1.5, 0.24, -1.04); //确定射线发出原点
  ray.direction = new THREE.Vector3(-1, 0, 0).normalize(); //射线发出方向
  let result: THREE.Vector3 = new THREE.Vector3(0, 0, 0); //结果

  let box = new THREE.Box3(); //初始化碰撞盒子
  target.geometry.computeBoundingBox(); //计算盒子边界
  box //转化盒子边界坐标
    .copy(target.geometry.boundingBox as THREE.Box3)
    .applyMatrix4(target.matrixWorld);
  const point: any = ray.intersectBox(box, result) ?? { x: 5, y: 5, z: 5 };
  let localPosition = localBox.worldToLocal(point);
  // ray.recast();
  return localPosition;
};

模型上打孔:

//模型打孔
const modelPerforate = (
  model: THREE.Mesh,
  holesPosition: number[][]
): THREE.Mesh => {
  const subResult = CSG.subtract(model, additionShap(holesPosition));
  return subResult;
};

完整源代码:

import React, { useRef, useEffect, useState } from "react";
import * as THREE from "three";
import { useThree } from "@react-three/fiber";
import { useGLTF, Html } from "@react-three/drei";
import { CSG } from "three-csg-ts";

type Props = {};

//新建孔洞
const hole = new THREE.Mesh(new THREE.CylinderGeometry(0.01, 0.01, 5, 32));
hole.visible = false;
const createHole = (position: number[]) => {
  // const hole = new THREE.Mesh(new THREE.CylinderGeometry(0.01, 0.01, 5, 32));
  hole.rotation.x = Math.PI / 2;
  hole.position.set(position[0], position[1], position[2]);
  hole.updateMatrix();
  return hole;
};
//组合形状
const additionShap = (
  list = [
    [0, 0, 0],
    [0.03, 0.24, -1.04],
  ]
): THREE.Mesh => {
  let result;
  if (list.length > 1) {
    result = list.reduce((pre: any, item): any => {
      // console.log("-----",pre instanceof Array);
      return CSG.union(
        pre instanceof Array ? createHole(pre) : pre,
        createHole(item)
      );
    });
  }
  if (list.length == 1) {
    result = CSG.union(createHole(list[0]), createHole([1000, 1000, 1000]));
  }

  return result as unknown as THREE.Mesh;
};
//确定射线和透镜相交位置
//targetRef 目标物
//局部坐标标志物
const getRayIntersect = (
  target: THREE.Mesh,
  localBox: THREE.Object3D
): THREE.Vector3 => {
  console.log("调用函数");
  let ray = new THREE.Ray(); //初始化射线
  ray.origin = new THREE.Vector3(1.5, 0.24, -1.04); //确定射线发出原点
  ray.direction = new THREE.Vector3(-1, 0, 0).normalize(); //射线发出方向
  let result: THREE.Vector3 = new THREE.Vector3(0, 0, 0); //结果

  let box = new THREE.Box3(); //初始化碰撞盒子
  target.geometry.computeBoundingBox(); //计算盒子边界
  box //转化盒子边界坐标
    .copy(target.geometry.boundingBox as THREE.Box3)
    .applyMatrix4(target.matrixWorld);
  const point: any = ray.intersectBox(box, result) ?? { x: 5, y: 5, z: 5 };
  let localPosition = localBox.worldToLocal(point);
  // ray.recast();
  return localPosition;
};
//模型打孔
const modelPerforate = (
  model: THREE.Mesh,
  holesPosition: number[][]
): THREE.Mesh => {
  const subResult = CSG.subtract(model, additionShap(holesPosition));
  return subResult;
};

function LaserAndTarget({}: Props) {
  const localGroup = useRef<any | null>(); //本地组
  const lineRef = useRef<any | null>(); //红色射线
  const theMirror = useRef<any | null>(); //目标镜子
  const [holesPosition, setHolesPosition] = useState<number[][]>([]); //孔洞位置
  const [mirrorPosition, setMirrorPosition] = useState<THREE.Vector3>(
    new THREE.Vector3(0.05, 0.31, -0.33)
  ); //镜子位置
  const { nodes, materials } = useGLTF("/glb/MarkerMirror.glb") as any;
  materials.材质 = new THREE.MeshPhysicalMaterial({
    color: 0xffffff,
    transmission: 0.4,
    opacity: 1,
    metalness: 0,
    roughness: 0,
    ior: 1.45,
    specularIntensity: 10,
    specularColor: new THREE.Color("red"),
  });

  //确定位置
  useEffect(() => {
    console.log("位置重绘");
    let result = getRayIntersect(theMirror.current, localGroup.current);
    console.log("命中位置", result);
    let position = holesPosition;
    position.push([result.x - 0.01, result.y, result.z]);
    setHolesPosition(position);
    // const cube2 = new THREE.Mesh(
    //   new THREE.BoxGeometry(0.02, 0.02, 0.02),
    //   new THREE.MeshNormalMaterial()
    // );
    // cube2.position.set(result.x, result.y, result.z);
    // localGroup.current.add(cube2);
  }, [mirrorPosition]);
  //打孔
  useEffect(() => {
    theMirror.current.updateMatrix();
    theMirror.current.needsUpdate = true;
    theMirror.current.geometry = modelPerforate(
      theMirror.current,
      holesPosition
    ).geometry;
  }, [holesPosition, mirrorPosition]);
  return (
    <group ref={localGroup}>
      <mesh
        ref={lineRef}
        rotation-x={Math.PI / 2}
        position={[0.03, 0.24, -1.04]}
      >
        <cylinderGeometry args={[0.008, 0.008, 1000, 32]} />
        <meshBasicMaterial color={"red"} side={THREE.DoubleSide} />
      </mesh>
      {/* 射线起点 */}
      <mesh position={[0.03, 0.24, 5.55]}>
        <boxGeometry args={[0.03, 0.03, 0.03]} />
        <meshStandardMaterial color={"gold"} />
      </mesh>
      {/* 目标 */}
      <mesh
        ref={theMirror}
        scale={0.06}
        position={mirrorPosition}
        rotation-x={Math.PI / 2}
        geometry={nodes.圆柱.geometry}
        material={materials["材质"]}
      />
      <Html>
        <button
          onClick={() => {
            console.log("点击");
            let position = new THREE.Vector3(
              mirrorPosition.x,
              mirrorPosition.y,
              mirrorPosition.z
            );
            position.x += 0.02;
            setMirrorPosition(position);
          }}
        >
          右
        </button>
        <button
          onClick={() => {
            console.log("点击");
            let position = new THREE.Vector3(
              mirrorPosition.x,
              mirrorPosition.y,
              mirrorPosition.z
            );
            position.y += 0.02;
            setMirrorPosition(position);
          }}
        >
          上
        </button>
        <button onClick={()=>{
          console.log("点击")
          let position = new THREE.Vector3(mirrorPosition.x,mirrorPosition.y,mirrorPosition.z);
          position.y-=0.02;
          setMirrorPosition(position)
        }}>下</button>
          <button onClick={()=>{
          console.log("点击")
          let position = new THREE.Vector3(mirrorPosition.x,mirrorPosition.y,mirrorPosition.z);
          position.x-=0.02;
          setMirrorPosition(position)
        }}>左</button>
      </Html>
    </group>
  );
}

export default LaserAndTarget;

本文作者:sy0313

本文链接:https://www.cnblogs.com/sunyan97/p/16809548.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   sy0313  阅读(482)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起