threejs中的射线拾取(点击选中模型)

threejs版本 0.148

先说一下思路:

1、点击模型时的坐标转化;

2、射线交叉计算;

3、改变选中的模型。

我这里完整代码中的案例是多个模型渲染时(模型自备),点击其中某一部件,改变对映模型的颜色,再次点击同一模型还原颜色,点击其他时则只改变选中模型的颜色;

第一步:就要监听鼠标点击事件

找到鼠标点击的位置然后转化成模型中的位置范围(-1,1),这里的x、y就是转化后的坐标,监听时前面记得加个要监听哪个dom(如:renderer.domElement.addEventListener()),否则点哪里都会触发;(这里要注意用offsetX,而不是clientX,因为这两个的坐标原地是不一样的

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;
     
});

第二步:就是射线交叉计

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]);
	
});

第三步:改变选中的模型

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格式):

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;

效果如下: 

  • 11
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值