Optimize model viewer markers

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.

Thank you.

1 Like

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

https://input-blueprint.vercel.app/

this is what I was working on, had about 7 markers and creates a lag like yours

The issue is that I don’t know how to implement this I am still a begginer in 3d.

I have checked your demo and the framerate si not good at all…

yea am working on a fix, will send you a new link to check brother.

https://input-blueprint-optimzed.vercel.app/

check this out now

Yes but now the raycaster dose not check for the markers intersection, this is basically the issue.

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

I know but I don’t have enough experience to solve this this is why I posted it as a paid job.

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()

good luck with the rest
xar,

I’ll get back to you if I find a more accurate way of doing things.

Thank you for sharing this, I tried it but it dose not work, some markers are acceptable but some are completely wrong, can this be improved?

Adding the checking angle manually for each marker is not a solution for this project.

https://webdesign-flash.ro/ht/emv/

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 screenPosition = position.clone().project(camera);

but rather…

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 tried but there is no intersection doing it this way, maybe I am missing something.

Yeah scrap that it was wrong anyway, see this…

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…

Thank you man this is it, I will try to figure this out tomorrow!

1 Like

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.

Thank you.

This topic was automatically closed after 30 days. New replies are no longer allowed.