mrdoob / three.js

JavaScript 3D Library.
https://threejs.org/
MIT License
102.95k stars 35.4k forks source link

Mesh: allow caller to specify which geometry to raycast and ignore material visibility #28958

Closed sguimmara closed 3 months ago

sguimmara commented 4 months ago

Description

Currently, calling Mesh.raycast() will use the .geometry of the mesh for raycasting purposes. However, in some cases it might be useful to specify another geometry (for example, a simplified convex hull geometry).

Currently, the only way to do that is if the simplified geometry is its own mesh in the scene graph (as a child of the renderable mesh). The renderable mesh would then override raycast() to raycast the simplified mesh instead.

However, since the simplified mesh is not renderable, adding it to the scene graph is superfluous and might lead to performance overhead (even if the mesh is not visible).

Solution

A possible solution would be to extract the mesh raycasting logic in a helper (that Mesh would use). Something like:

MeshRaycaster.raycast(geometry, matrixWorld, raycaster, intersectList);

This way, any object would be able to perform mesh intersection tests without having a geometry itself, or maybe if it has to use a different geometry than the renderable one.

An additional benefit is that currently the implementation of raycast() by the mesh checks if the material is visible, and does not perform the raycast if the material is not visible. The MeshRaycaster helper does not care about the material, so this logic could be circumvented for geometries used purely for raycasting.

Alternatives

Another possibility would be to add options to Mesh.raycast(), to allow for overriding the geometry used:

// In a subclass of Mesh:
raycast(raycaster, intersectList) {
    super.raycast(raycaster, intersectList, { geometry: myCustomColliderGeometry, checkMaterial: false });
}

However this would change the API (although not a breaking change), so it is less elegant than the helper solution.

Additional context

When raycasting against complex geometries, we might want a simplified geometry for raycasting purposes.

For example, the Unity3D game engine distinguish between the collider component of the GameObject (which can have its own mesh) and the renderable mesh.

gkjohnson commented 4 months ago

I think this is something that's fairly easily handled in user-applications. There are many ways to improve or speed up raycasting and I don't think it's reasonable to expect three.js to lean too far into supporting any specific approach since there is no one size fits all solution.

Some of the more obvious methods are:

gkjohnson commented 3 months ago

I think we can close this for now until it's understood why these other solutions aren't viable.

sguimmara commented 3 months ago

Sorry for the late reply. The main issue is that the main function to compute raycasting against meshes is the method Mesh._computeIntersections(). This method has several issues:

_computeIntersections( raycaster, intersects, rayLocalSpace ) {

        let intersection;

        const geometry = this.geometry;
        const material = this.material;

               ...
}

So as a user, if I want to circumvent those limitations, I have to copy/paste this code and modify it, which I want to avoid.

gkjohnson commented 3 months ago

It might be nice to modularize some of these internal functions with more simple APIs but it's not immediately clear to me how that can be done without likely sacrificing some performance. It's also not the case, though, that you have to copy and paste code or rely on private functions to do what you're suggesting. You can just as easily use a temporary mesh instance to perform the raycasting which is what is done in BatchedMesh (see here and here).

sguimmara commented 3 months ago

It might be nice to modularize some of these internal functions with more simple APIs but it's not immediately clear to me how that can be done without likely sacrificing some performance. It's also not the case, though, that you have to copy and paste code or rely on private functions to do what you're suggesting. You can just as easily use a temporary mesh instance to perform the raycasting which is what is done in BatchedMesh (see here and here).

Indeed, but having to create a whole new mesh with an useless material feels a bit clunky.