I am building a 3d object for a client that wants HTML markers that hide and show based on the model position.
I have built the model and the logic for this but the issue is the performance which is very poor if the model is high quality.
I tried to solve this on my own and asked for help on the forum and it seems that there is a way to optimize this as they do it in the Google-model viewer using this approach “A faster alternative to what you’re doing now would be to raycast just once for each marker, storing the normal vector of the surface at the intersection. Then show the marker when that normal vector is within some range (maybe 75–80º) of facing the viewer.”
I just don’t understand how to implement it.
Everything is coded and built using Vite the only thing that is missing is the logic to optimize the markers to work with high poly models.
Here is an example with a low poly model that works well Start and here is an example with a more complex model Start, with only three markers the frame rate drops at approximately 20fps.
hey dude I was working on the same kind of usecase for a client of mine and I was wondering if you could share me a resource regarding this : “A faster alternative to what you’re doing now would be to raycast just once for each marker, storing the normal vector of the surface at the intersection. Then show the marker when that normal vector is within some range (maybe 75–80º) of facing the viewer.” , I am willing to optimize your code for you for a resonable price if I can get a general idea of how to implement that feature
its enough optimization for my usecase, am gonna refine it a bit and its good for me, if you’re going for a 100% accurate raycasted intersection check it will eventually affect the frame rate because the checks have to be done multiple times for multiple points in a single frame, hope you’ll figure it out
lmk if you find a way to maintain 60fps with 100% accuracy of the point markers, as for the implementation of what you described, here is it for free :
// Add a normal property to each point object to store the normal vector
const points = [
{
position: new THREE.Vector3(2.738, 0.502, 5.3),
element: document.querySelector(".point-0"),
normal: null,
},
{
position: new THREE.Vector3(-8.164, 0.471, 0.862),
element: document.querySelector(".point-1"),
normal: null,
},
];
const raycaster = new THREE.Raycaster();
const processPoints = () => {
const { width, height } = sizes;
const halfWidth = width * 0.5;
const halfHeight = height * 0.5;
const cameraPosition = camera.position;
for (const point of points) {
const { position, element, normal } = point;
const screenPosition = position.clone().project(camera);
if (normal === null) {
// Do the raycast if the normal vector hasn't been stored yet
raycaster.setFromCamera(screenPosition, camera);
const intersects = raycaster.intersectObjects(model.children, true);
if (intersects.length > 0) {
point.normal = intersects[0].face.normal;
}
}
if (normal !== null) {
// Calculate the vector from the intersection point to the camera
const viewVector = cameraPosition.clone().sub(position);
// Calculate the angle between the normal vector and the view vector
const angle = normal.angleTo(viewVector) * (180 / Math.PI);
// Show the marker if the angle is within a certain range
if (angle >= 75 && angle <= 105) { // Adjust the range as needed
element.classList.add("visible");
} else {
element.classList.remove("visible");
}
}
const translateX = screenPosition.x * halfWidth;
const translateY = -screenPosition.y * halfHeight;
element.style.transform = `translateX(${translateX}px) translateY(${translateY}px)`;
}
};
play with the angle range to refine things here :
if (angle >= 75 && angle <= 105) { // Adjust the range as needed
element.classList.add("visible");
} else {
element.classList.remove("visible");
}
call the process points function in your update loop, something like this :
const tick = () => {
stats.begin() // Begin monitoring
controls.update()
if (sceneReady) {
renderer.render(scene, camera)
updateCursor(model)
processPoints() //<====== like this =)
}
stats.end() // End monitoring
window.requestAnimationFrame(tick)
}
tick()
this looks almost correct but i think you’ll have better more consistent results if you fire each inital ray from the point directly to the center of the model without using…
const direction = position.clone().sub(model.position).normalize()
if (normal === null) {
raycaster.set(position, direction);
const intersects = raycaster.intersectObjects(model.children, true);
if (intersects.length > 0) {
point.normal = intersects[0].face.normal;
}
}
this way you will get the face normal that the label hovers over which you can then check, if the label direction normal is ~close enough to the intersected face normal, show the label, if not hide it…
I was working on a v3 that will do exactly what you need but got too late in the day to complete, I’ll try share an update when I have time if you haven’t solved it…
It was late yesterday I hadn’t had the chance to view the code in detail, this seems to work as expected, will look into it in detail and get back to you
Everything is working fine but I am trying to understand visually how this angle works, can you guys help me understand how this works visually so a diagram or something?
I understand how the normal works but I can’t picture in my mind the angle that is checked.
I also want to know if it is possible somehow if the intersection point and normal can move with the model animation, hopefully, it makes sense what I am trying to say.