在日常开发中接到一个需求,需要在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;
标签:const,模型,THREE,three,let,result,position,new,r3f
From: https://www.cnblogs.com/sunyan97/p/16809548.html