threejs版本 0.148
先说一下思路:
1、点击模型时的坐标转化;
2、射线交叉计算;
3、改变选中的模型。
我这里完整代码中的案例是多个模型渲染时(模型自备),点击其中某一部件,改变对映模型的颜色,再次点击同一模型还原颜色,点击其他时则只改变选中模型的颜色;
第一步:就要监听鼠标点击事件
找到鼠标点击的位置然后转化成模型中的位置范围(-1,1),这里的x、y就是转化后的坐标,监听时前面记得加个要监听哪个dom(如:renderer.domElement.addEventListener()),否则点哪里都会触发;(这里要注意用offsetX,而不是clientX,因为这两个的坐标原地是不一样的)
ini
addEventListener('click', function (event) {
// 鼠标点击的坐标
const px = event.offsetX;
const py = event.offsetY;
// 转化坐标 (为什么这么写 具体可以看官网文档)
const x = (event.offsetX / container.clientWidth) * 2 - 1;
const y = -(event.offsetY / container.clientHeight) * 2 + 1;
});
第二步:就是射线交叉计
ini
addEventListener('click', function (event) {
// 鼠标点击的坐标转化
const px = (event.offsetX / container.clientWidth) * 2 - 1;
const py = -(event.offsetY / container.clientHeight) * 2 + 1;
// 获取射线交点
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(new THREE.Vector2(px, py), camera);
// 检测和射线相交的模型
const interscet = raycaster.intersectObjects([mesh1, mesh2, mesh3]);
});
第三步:改变选中的模型
ini
addEventListener('click', function (event) {
// 鼠标点击的坐标转化
const px = (event.offsetX / container.clientWidth) * 2 - 1;
const py = -(event.offsetY / container.clientHeight) * 2 + 1;
// 获取射线交点
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(new THREE.Vector2(px, py), camera);
const interscet = raycaster.intersectObjects([mesh1, mesh2, mesh3]);
// 判断是否有物体与射线相交
if (interscet.length > 0) {
// 改变与射线相交的最近的物体 也就是你的点击的物体
interscet[0].object.material.color.set(0xff0000);
}
});
完整代码如下(这里渲染的模型是stl格式):
ini
import { useEffect, useRef } from 'react';
import * as THREE from 'three';
import { STLLoader } from 'three/examples/jsm/loaders/STLLoader.js';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
function Demo(props) {
const modelRefs = useRef([]); // 单个模型
const containerRef = useRef(null); // 获取容器的引用
let camera;
let originalColor = 0xdadada; // 定义原始颜色
let currentClickedModel = null; // 追踪当前被点击的模型
useEffect(() => {
const container = containerRef.current; // 获取容器元素
const scene = new THREE.Scene(); // 创建场景
const stlLoader = new STLLoader();
const modelFiles = [props.fileName, props.fileName2, props.fileName3]; // 假设props中传入了模型的文件路径
modelFiles.forEach((fileName, index) => {
stlLoader.load(fileName, (geometry) => {
const material = new THREE.MeshLambertMaterial({
color: 0xdadada,
side: THREE.DoubleSide,
});
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
modelRefs.current[index] = mesh; // 保存模型引用
});
}); // 加载模型
const width = container.clientWidth; // 获取容器宽度
const height = container.clientHeight; // 获取容器高度
const k = width / height;
camera = new THREE.PerspectiveCamera(70, k, 0.1, 100000);
camera.position.set(8000, 0, 0); // 相机位置
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6); // 创建环境光,第二个参数是光的强度
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.6); // 创建平行光,第1个参数是光的强度
directionalLight.position.set(1000, 1000, 1000); //光源位置
scene.add(directionalLight);
const renderer = new THREE.WebGLRenderer({
antialias: true, // 抗锯齿属性
});
renderer.setSize(width, height); //设置渲染区域范围
container.appendChild(renderer.domElement);
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableRotate = true; // 允许旋转
function handleWindowResize() {
renderer.setSize(width, height);
camera.aspect = width / height;
}
window.addEventListener('resize', handleWindowResize);
function render() {
renderer.render(scene, camera);
requestAnimationFrame(render);
controls.update(); // 更新控制器
camera.updateProjectionMatrix(); // 防止模型变形
}
render();
const raycaster = new THREE.Raycaster(); // 光线投射
const mouse = new THREE.Vector2(); // 鼠标点击的二维向量
/** 点击模型操作 */
function onModelClick(event) {
event.preventDefault(); // 取消默认的右键菜单等功能
mouse.x = (event.offsetX / renderer.domElement.clientWidth) * 2 - 1;
mouse.y =
-(event.offsetY / renderer.domElement.clientHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera); // 更新射线投射器的起点和方向
// 射线与模型相交的情况
const intersects = raycaster.intersectObjects(modelRefs.current);
// 如果有相交的模型,则选中第一个相交的模型
if (intersects.length > 0) {
const object = intersects[0].object;
// 检查是否点击的是同一个模型
if (currentClickedModel === object) {
// 如果是,则还原颜色并清除引用
object.material.color.setHex(originalColor);
currentClickedModel = null;
} else {
// 如果不是,先将之前的模型颜色还原
if (currentClickedModel) {
currentClickedModel.material.color.setHex(
originalColor
);
}
// 更新当前被点击的模型,并改变颜色
currentClickedModel = object;
object.material.color.setHex(0xfff000); // 设置新的颜色
}
}
}
container.addEventListener('click', onModelClick);
return () => {
window.removeEventListener('resize', handleWindowResize);
container.removeEventListener('click', onModelClick);
// 清空场景
scene.clear();
// 移除渲染器
container.removeChild(renderer.domElement);
};
}, []);
return (
<div
id="webgl"
ref={containerRef}
style={{ width: '100%', height: '100%' }}
/>
);
}
export default Demo;
效果如下: