GLTF Mesh CSS2D Annotation wrong position

Hi there,

i have a GLTF model of a map with points of interests. My goal is to add annotations on top of the meshes so that a user can trigger an animation of that mesh on hover.

This is the method for adding a label:

const moveTriggerToMesh = (mesh: any) => {
  const domElem = document.querySelector(

  if (domElem) {
    domElem.innerHTML = = '#fff'
    const label = new CSS2DObject(domElem)

    const screenPos = mesh.position.clone().project(
    const { x, y, z } = screenPos
    label.position.set(x, y, z)

    // Alternative without CSS2dObject
    /*const { width, height } = rendererC.value.renderer.getContext().canvas*/
    /*const vector = new Vector3(0, 0, 0)


    const x = Math.round(
      (0.5 + vector.x / 2) * (width / window.devicePixelRatio)
    const y = Math.round(
      (0.5 - vector.y / 2) * (height / window.devicePixelRatio)

    /*const x = ((screenPos.x + 1) / 2) * width
    const y = ((1 - screenPos.y) / 2) * height'--trigger-pos-x', `${x}px`)'--trigger-pos-y', `${y}px`)*/

and the methods gets called when the GLTF model got loaded:

const onModelLoaded = (model: { scene: { children: any } }) => {
  dataLoading.value = false

  setupPopups({ model: model })


  const someMesh = model.scene.children.find((el) => === 'Halde_01')


  rendererC.value.onBeforeRender(() => {

In the second screenshot you can see that the label gets positioned but it looks like the label is always positioned in the center. when i swap out the code in my moveTriggerToMesh in order to use the Vector3 approach the label gets again positioned in the exact same spot.

I tried the same with an simple GLTF with two Cubes and that worked fine, which leads me to think the blender file isn’T configured properly? In the first screenshot you see the blender file and the current element that i’m trying to annotate; You can see it’s in a totally different position.

Would love to receive some help or guidance. If you want to i can provide the blender file

Thanks in advance

fixed it by calculating the mesh’s center:

const getCenterPoint = (mesh) => {
  const geom = mesh.geometry
  let center = new Vector3()

  return center
1 Like

That’s pretty cool. Is that terrain made from those little cylinders? You can raise them/lower them based on height values? If so that’s cool. Another way you can generate those is through the THREE.TextBufferGeometry(). You can create the shape from a TTF font file, then set the “height:” property of the text to control the extrude depth dynamically. You can even instance it. You can modify the Text Generator if you want just to create instanced geometry from shapes like “wingdings” and stuff too. You can control vertex count with “segments”. Then just run merge on vertexes and faces before you instance them. Just a fyi. I always love finding easy ways to generate geometry and extrudes with vertex control built-in. :slight_smile:

1 Like

Hey thanks a lot for your response! I’m very new to 3d and three.js and what you are seeing is created by a colleague in blender. Your idea sounds very good since the gltf is 78MB big and i can imagine creating the same with only TextBufferGeometry would decrease the size a lot. If you want to i can provide you the blender file but i will take a look at this nonetheless :slight_smile:

1 Like

did you mean mesh.matrixWorld here

Ya, that’s big. Thats a very simple repeating geometry. You can instance that entire thing as one mesh. You have just one material for the whole geometry?

Well I just didnt know if you were trying to make the cylinders change height or not. Make one terrain morph into another by changing cylinder position and height. Making a simple cylinder geometry is easy. No need for textGeometryBuffer. But you could make the terrain as one instancedMesh with a bunch of child cylinder instances and control their scale in one direction to adjust height. So that terrain could be one draw call either with height adjustments built into the into the
Cylinders (as child instances) or none at all. Btw, Is the geometry 72mb? Or does it have a large textureMap that goes with it? If the geometry is that big then ya you can shrink that down “a lot”. It is a little bit janky but the leftmost dot triggers an animation and on animationEnd the popup gets triggered. Now afaik this is not a single Mesh, this map is an abstract visualization of the “Ruhrgebiet”, an area in west germany where you got a lot of so called “Heaps” - hills that get created because of the mining work in the past. And every Heap is a mesh. I send you an direct-link of the blender file so you can see whats going on. Now what i need to know is, how can we separate all these sub-areas and also add a 2D Button on the center of it? because in the end, a user who is interested should be able to get more infos about these heaps. How do you control shape keys / morphing objects? I thought the way you do it is to add an animation as a sub-layer to the mesh but my colleague sent me a file with lots of layers that seem to control other layers and at this point i’m confused with blender^^ So, again thanks and would love to hear your ideas!

I took a look. I’m not sure what you’re saying when you say that meshes control other meshes. Without knowing your design I would go on instinct on navigating maps. I would think that when you click a “dump mesh” you want the map to orient itself (rotate) so that the clicked area is in focus, right?… Then you switch to 2D as in a “satellite” or “birds eye” view looking down on the area?

The blenderfile contains a layer for every area and a corresponding layer with the suffix “_Ende” (End in german) which is altered in a way that it shows the end state of the animation. By pressing the space bar you should see the animations. My colleague told me thats the way you morph layers…

So yeah when you enter the mesh’s area this animation should start and on end a popup appears on top of the dot. no need to move the camera, only simple popup trigger mechanics. All i need to know now is how i can animate based on the corresponding layer. Hope this clears things up!


Sorry Mortenassi, I dont understand the ask exactly. I’m also just a community member and I get pulled away often for life things. I’m sorry but I cannot offer a lot of time to you on this.

Triggering animations seems trivial to me so I think I’m missing something here. I looked at your model, I saw the popups. Im guessing you just have an html onclick firing them. So, how to get a click to fire on a 3D element? If so, you need to raycast and setup an event listener for the click.

All good mate, if you got time to look again you see an updated map with highighted spots you can click on. It’s working fine now; Only downside is i added two point lights that emit shadows from left and right. Setting receiveShadow and castShadow on too many meshes throttles the performance even on my M1 MBP so i ahve to play around with ambient occlusion