Three.js中如何选择Mesh、Group或Object3D并实现高亮切换?
解决Three.js选中Mesh后恢复初始颜色的问题
核心思路
要实现再次选中时恢复树木初始颜色,需要记录树木的原始颜色,同时跟踪当前选中的树木对象。点击时判断是选中新树木、取消当前选中,还是重复点击同一棵树,再分别执行高亮或颜色恢复操作。
具体修改步骤
1. 保存树木的初始颜色
在创建树木的代码中,给每个树木的子Mesh添加initialColor属性(存在userData中,避免和内置属性冲突),保存初始颜色:
// 示例:创建树木时保存初始颜色 function createTree() { const tree = new Object3D(); tree.name = "tree"; // 假设这里已经创建了树干、树枝等子Mesh tree.children.forEach(child => { if (child.children.length > 0) { child.children.forEach(c2 => { // 克隆初始颜色,避免后续修改影响原始值 c2.userData.initialColor = c2.material.color.clone(); }); } else { child.userData.initialColor = child.material.color.clone(); } }); return tree; }
2. 修改MouseController跟踪选中状态
新增_selectedTree变量记录当前选中的树木,添加颜色重置方法,并优化选中逻辑:
import MouseControllerInput from "./input.js"; export default class MouseController { constructor(SceneEntity, CameraEntity) { this._scene = SceneEntity; this._camera = CameraEntity; this._input = new MouseControllerInput(); this._selectedTree = null; // 跟踪当前选中的树木 } // 恢复树木初始颜色的方法 #resetTreeColor(tree) { tree.children.forEach(c => { if (c.children.length > 0) { c.children.forEach(c2 => { c2.material.color.copy(c2.userData.initialColor); }); } else { c.material.color.copy(c.userData.initialColor); } }); } // 高亮树木的方法 #highlightTree(tree) { tree.children.forEach(c => { if (c.children.length > 0) { c.children.forEach(c2 => c2.material.color.set(0xff0000)); } else { c.material.color.set(0xff0000); } }); } Update() { if (this._input.active) { this._input.raycaster.setFromCamera(this._input.pointer, this._camera); // 第二个参数设为true,递归检测所有子对象 const intersects = this._input.raycaster.intersectObject(this._scene, true); let targetTree = null; // 从相交结果中找到最顶层的tree对象 for (const intersect of intersects) { const obj = intersect.object; if (obj.parent.name === "terrain") continue; // 向上遍历父对象,找到名为tree的节点 let current = obj; while (current && current.name !== "tree") { current = current.parent; } if (current) { targetTree = current; break; // 只处理第一个相交的树木 } } // 处理选中逻辑 if (targetTree) { if (this._selectedTree === targetTree) { // 重复点击同一棵树,恢复颜色并取消选中 this.#resetTreeColor(targetTree); this._selectedTree = null; } else { // 先取消之前选中树的高亮 if (this._selectedTree) { this.#resetTreeColor(this._selectedTree); } // 高亮新选中的树 this.#highlightTree(targetTree); this._selectedTree = targetTree; } } else { // 点击空白处,取消当前选中 if (this._selectedTree) { this.#resetTreeColor(this._selectedTree); this._selectedTree = null; } } this._input.active = false; // 处理完点击后重置状态,避免重复触发 } } }
3. 修复MouseControllerInput的事件绑定问题
原代码中事件绑定会立即执行函数,且mouseup事件会导致状态混乱,修改如下:
export default class MouseControllerInput { constructor() { this.#_init(); this._raycaster = new Raycaster(); this._pointer = new Vector2(); this._active = false; } get pointer() { return this._pointer; } get raycaster() { return this._raycaster; } get active() { return this._active; } #_init() { // 传递函数引用,而非立即执行 document.addEventListener("mousedown", this.#_onClick()); // 移除mouseup事件,仅在点击按下时触发逻辑 } #_onClick() { return (v) => { this._active = true; // 设置为true,而非取反,避免状态异常 this._pointer.x = (v.clientX / window.innerWidth) * 2 - 1; this._pointer.y = -(v.clientY / window.innerHeight) * 2 + 1; }; } }
关键说明
- 使用
userData存储初始颜色:Three.js内置的userData属性专门用于存放自定义数据,不会和框架内置属性冲突。 - 跟踪选中状态:通过
_selectedTree变量记录当前选中的树木,避免重复高亮或遗漏颜色恢复。 - 递归检测子对象:
intersectObject的第二个参数设为true,可以自动检测所有嵌套子对象,简化tree节点的查找逻辑。
内容的提问来源于stack exchange,提问作者ask4you
相关产品推荐
相关产品推荐

