Three.js 使用 Raycaster 检测 ArrowHelper 的线和圆锥子项
Three.js Using Raycaster to detect line and cone children of ArrowHelper
我有一个用于简单绘画应用程序的正常运行的 Raycaster。我将它用作“桶工具”,用户可以在其中单击一个对象并更改其颜色。它适用于 BoxGeometry 和 CircleGeometry 等几何对象,但我很难将其应用于 ArrowHelper 对象的子对象。因为 ArrowHelper 不是形状并且不具有几何属性,所以 Raycaster 在检查 scene.children
交叉点时不会检测与其位置的碰撞。但是,ArrowHelper 对象的子对象总是两种东西:一条线和一个圆锥体,它们都具有几何、material 和位置属性。
我试过:
- 将函数
.intersectObjects(objects: Array, recursive: Boolean, optionalTarget: Array )
的 recursive
布尔值切换为真,以便它包含数组中对象的子对象。
- 通过遍历 ArrowHelper 对象
scene.children
并将其线条和圆锥体添加到单独的对象数组中,从而绕过 ArrowHelper 父对象。从那里我试图检查仅与直线和圆锥体列表的交叉点,但未检测到交叉点。
Raycaster 设置:
const runRaycaster = (mouseEvent) => {
... // sets mouse and canvas bounds here
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(scene.children, true);
if (intersects.length > 0) {
for (let i = 0; i < intersects.length; i++) {
// works for GEOMETRY ONLY
// needs modifications for checking ArrowHelpers
intersects[i].object.material.color.set(currentColor);
}
}
};
这是我在没有 ArrowHelper 父级的情况下单独检查线条和圆锥体的尝试:
let arrowObjectsList = [];
for (let i = 0; i < scene.children.length; i++) {
if (scene.children[i].type === 'ArrowHelper') {
arrowObjectsList.push(scene.children[i].line);
arrowObjectsList.push(scene.children[i].cone);
} else {
console.log(scene.children[i].type);
}
}
console.log(arrowObjectsList); // returns 2 objects per arrow on the canvas
// intersectsArrows always returns empty
const intersectsArrows = raycaster.intersectObjects(arrowObjectsList, true);
一些注意事项:
- 每个 ArrowHelper、它的线和它的锥体都有唯一可识别的名称,因此它们可以 recolored/repositioned/deleted 以后。
- Raycaster 随每个 onMouseDown 和 onMouseMove 事件一起运行。
- 值得注意的是,ArrowHelpers 的线和圆锥体子项分别是 BufferGeometry 和 CylinderBufferGeometry,而不是 Geometry 的变体。我想知道这是否与它有关。根据 Three.JS 文档网站的 this example,BufferGeometry 可以用类似的方式被 Raycaster 检测到。
设置recursion = true
对我有用。 运行下面的简单代码,然后点击箭头。您将看到打印到控制台的路口信息。 (three.js r125)
let W = window.innerWidth;
let H = window.innerHeight;
const renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
});
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(28, 1, 1, 1000);
camera.position.set(5, 5, 5);
camera.lookAt(scene.position);
scene.add(camera);
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(0, 0, -1);
camera.add(light);
const mesh = new THREE.ArrowHelper(
new THREE.Vector3(0, 0, 1),
new THREE.Vector3(0, 0, 0),
2,
0xff0000,
1,
1
);
scene.add(mesh);
function render() {
renderer.render(scene, camera);
}
function resize() {
W = window.innerWidth;
H = window.innerHeight;
renderer.setSize(W, H);
camera.aspect = W / H;
camera.updateProjectionMatrix();
render();
}
window.addEventListener("resize", resize);
resize();
render();
// RAYCASTER STUFF
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
renderer.domElement.addEventListener('mousedown', function(e) {
mouse.set(
(event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1
);
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(scene.children, true);
console.log(intersects);
});
html,
body {
width: 100%;
height: 100%;
padding: 0;
margin: 0;
overflow: hidden;
background: skyblue;
}
<script src="https://threejs.org/build/three.min.js"></script>
仔细一看,是设置位置的问题,不一定是箭头。箭头的位置根据用户单击鼠标以指定起点而变化。但是,它仍然存在几个问题: select 该行非常困难,因为 LineBasicMaterial
的 lineWidth
值除了 1 之外不能有任何其他值,尽管它是可编辑的。这是由于 OpenGL 核心配置文件中的限制,如 the docs and in this question 中所述。同样,圆锥体不会响应 setLength
。这严重限制了 ArrowHelper 工具的自定义。
因此,我决定用两个耦合在一起的对象完全替换 ArrowHelper:tubeGeometry 和 coneGeometry,它们都指定了 MeshBasicMaterial,Raycaster 可以开箱即用。
... // the pos Float32Array is set according to user mouse coordinates.
const v1 = new THREE.Vector3(pos[0], pos[1], pos[2]);
const v2 = new THREE.Vector3(pos[3], pos[4], pos[5]);
const material = new THREE.MeshBasicMaterial({
color: color,
side: THREE.DoubleSide,
});
// Because there are only two vectors, no actual curve occurs.
// Therefore, it's our straight line.
const tubeGeometry = new THREE.TubeBufferGeometry(
new THREE.CatmullRomCurve3([v1, v2]), 1, 3, 3, false);
const coneGeometry = new THREE.ConeGeometry(10, 10, 3, 1, false);
arrowLine = new THREE.Mesh(tubeGeometry, material);
arrowTip = new THREE.Mesh(coneGeometry, material);
// needs names to be updated later.
arrowLine.name = 'arrowLineName';
arrowTip.name = 'arrowTipName';
放置箭头时,用户会通过点击和拖动来指定箭头的起点和终点,所以箭头和它的尖端必须更新为onMouseMove
。我们必须使用 Math.atan2 来获取 v1 和 v2 之间的角度,以 v1 为中心。减去 90 将旋转定向到默认位置。
... // on the onMouseMove event, pos is updated with new coords.
const setDirection = () => {
const v1 = new THREE.Vector3(pos[0], pos[1], pos[2]);
const v2 = new THREE.Vector3(pos[3], pos[4], pos[5]);
// copying the v2 pos ensures that the arrow tip is always at the end.
arrowTip.position.copy(v2);
// rotating the arrow tip according to the angle between start and end
// points, v1 and v2.
let angleDegrees = 180 - (Math.atan2(pos[1] - pos[4], pos[3] - pos[0]) * 180 / Math.PI - 90);
const angleRadians = angleDegrees * Math.PI / 180;
arrowTip.rotation.set(0, 0, angleRadians);
// NOT VERY EFFICIENT, but it does the job to "update" the curve.
arrowLine.geometry.copy( new THREE.TubeBufferGeometry(new THREE.CatmullRomCurve3([v1, v2]),1,3,3,false));
scene.add(arrowLine);
scene.add(arrowTip);
};
开箱即用,这个“箭头”允许我 select 并使用 Raycaster 毫无问题地对其进行编辑。不用担心线条定位、线条粗细或线条长度。
我有一个用于简单绘画应用程序的正常运行的 Raycaster。我将它用作“桶工具”,用户可以在其中单击一个对象并更改其颜色。它适用于 BoxGeometry 和 CircleGeometry 等几何对象,但我很难将其应用于 ArrowHelper 对象的子对象。因为 ArrowHelper 不是形状并且不具有几何属性,所以 Raycaster 在检查 scene.children
交叉点时不会检测与其位置的碰撞。但是,ArrowHelper 对象的子对象总是两种东西:一条线和一个圆锥体,它们都具有几何、material 和位置属性。
我试过:
- 将函数
.intersectObjects(objects: Array, recursive: Boolean, optionalTarget: Array )
的recursive
布尔值切换为真,以便它包含数组中对象的子对象。 - 通过遍历 ArrowHelper 对象
scene.children
并将其线条和圆锥体添加到单独的对象数组中,从而绕过 ArrowHelper 父对象。从那里我试图检查仅与直线和圆锥体列表的交叉点,但未检测到交叉点。
Raycaster 设置:
const runRaycaster = (mouseEvent) => {
... // sets mouse and canvas bounds here
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(scene.children, true);
if (intersects.length > 0) {
for (let i = 0; i < intersects.length; i++) {
// works for GEOMETRY ONLY
// needs modifications for checking ArrowHelpers
intersects[i].object.material.color.set(currentColor);
}
}
};
这是我在没有 ArrowHelper 父级的情况下单独检查线条和圆锥体的尝试:
let arrowObjectsList = [];
for (let i = 0; i < scene.children.length; i++) {
if (scene.children[i].type === 'ArrowHelper') {
arrowObjectsList.push(scene.children[i].line);
arrowObjectsList.push(scene.children[i].cone);
} else {
console.log(scene.children[i].type);
}
}
console.log(arrowObjectsList); // returns 2 objects per arrow on the canvas
// intersectsArrows always returns empty
const intersectsArrows = raycaster.intersectObjects(arrowObjectsList, true);
一些注意事项:
- 每个 ArrowHelper、它的线和它的锥体都有唯一可识别的名称,因此它们可以 recolored/repositioned/deleted 以后。
- Raycaster 随每个 onMouseDown 和 onMouseMove 事件一起运行。
- 值得注意的是,ArrowHelpers 的线和圆锥体子项分别是 BufferGeometry 和 CylinderBufferGeometry,而不是 Geometry 的变体。我想知道这是否与它有关。根据 Three.JS 文档网站的 this example,BufferGeometry 可以用类似的方式被 Raycaster 检测到。
设置recursion = true
对我有用。 运行下面的简单代码,然后点击箭头。您将看到打印到控制台的路口信息。 (three.js r125)
let W = window.innerWidth;
let H = window.innerHeight;
const renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
});
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(28, 1, 1, 1000);
camera.position.set(5, 5, 5);
camera.lookAt(scene.position);
scene.add(camera);
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(0, 0, -1);
camera.add(light);
const mesh = new THREE.ArrowHelper(
new THREE.Vector3(0, 0, 1),
new THREE.Vector3(0, 0, 0),
2,
0xff0000,
1,
1
);
scene.add(mesh);
function render() {
renderer.render(scene, camera);
}
function resize() {
W = window.innerWidth;
H = window.innerHeight;
renderer.setSize(W, H);
camera.aspect = W / H;
camera.updateProjectionMatrix();
render();
}
window.addEventListener("resize", resize);
resize();
render();
// RAYCASTER STUFF
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
renderer.domElement.addEventListener('mousedown', function(e) {
mouse.set(
(event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1
);
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(scene.children, true);
console.log(intersects);
});
html,
body {
width: 100%;
height: 100%;
padding: 0;
margin: 0;
overflow: hidden;
background: skyblue;
}
<script src="https://threejs.org/build/three.min.js"></script>
仔细一看,是设置位置的问题,不一定是箭头。箭头的位置根据用户单击鼠标以指定起点而变化。但是,它仍然存在几个问题: select 该行非常困难,因为 LineBasicMaterial
的 lineWidth
值除了 1 之外不能有任何其他值,尽管它是可编辑的。这是由于 OpenGL 核心配置文件中的限制,如 the docs and in this question 中所述。同样,圆锥体不会响应 setLength
。这严重限制了 ArrowHelper 工具的自定义。
因此,我决定用两个耦合在一起的对象完全替换 ArrowHelper:tubeGeometry 和 coneGeometry,它们都指定了 MeshBasicMaterial,Raycaster 可以开箱即用。
... // the pos Float32Array is set according to user mouse coordinates.
const v1 = new THREE.Vector3(pos[0], pos[1], pos[2]);
const v2 = new THREE.Vector3(pos[3], pos[4], pos[5]);
const material = new THREE.MeshBasicMaterial({
color: color,
side: THREE.DoubleSide,
});
// Because there are only two vectors, no actual curve occurs.
// Therefore, it's our straight line.
const tubeGeometry = new THREE.TubeBufferGeometry(
new THREE.CatmullRomCurve3([v1, v2]), 1, 3, 3, false);
const coneGeometry = new THREE.ConeGeometry(10, 10, 3, 1, false);
arrowLine = new THREE.Mesh(tubeGeometry, material);
arrowTip = new THREE.Mesh(coneGeometry, material);
// needs names to be updated later.
arrowLine.name = 'arrowLineName';
arrowTip.name = 'arrowTipName';
放置箭头时,用户会通过点击和拖动来指定箭头的起点和终点,所以箭头和它的尖端必须更新为onMouseMove
。我们必须使用 Math.atan2 来获取 v1 和 v2 之间的角度,以 v1 为中心。减去 90 将旋转定向到默认位置。
... // on the onMouseMove event, pos is updated with new coords.
const setDirection = () => {
const v1 = new THREE.Vector3(pos[0], pos[1], pos[2]);
const v2 = new THREE.Vector3(pos[3], pos[4], pos[5]);
// copying the v2 pos ensures that the arrow tip is always at the end.
arrowTip.position.copy(v2);
// rotating the arrow tip according to the angle between start and end
// points, v1 and v2.
let angleDegrees = 180 - (Math.atan2(pos[1] - pos[4], pos[3] - pos[0]) * 180 / Math.PI - 90);
const angleRadians = angleDegrees * Math.PI / 180;
arrowTip.rotation.set(0, 0, angleRadians);
// NOT VERY EFFICIENT, but it does the job to "update" the curve.
arrowLine.geometry.copy( new THREE.TubeBufferGeometry(new THREE.CatmullRomCurve3([v1, v2]),1,3,3,false));
scene.add(arrowLine);
scene.add(arrowTip);
};
开箱即用,这个“箭头”允许我 select 并使用 Raycaster 毫无问题地对其进行编辑。不用担心线条定位、线条粗细或线条长度。