Get mesh bounding box position and size in 2d screen coordinates

Hi!

I’m doing a mashup where I have a BJS scene and now I want to overlay a popper.js tooltip when I hover over some meshes.

for popper.js to be able to know where to position itself you need to pass in a referenceObject.

import { ReferenceObject } from 'popper.js'
 
const ref: ReferenceObject = {
        clientHeight: 1000, // screen width
        clientWidth: 500, // screen height
        getBoundingClientRect() {
          return {
            width: 400, // the meshes boundingbox width
            height: 300, // the meshes boundingbox height
            left: 200, // how far from the left is the boundingbox
            top: 100,
            right: 400,
            bottom: 100,
          }
        },
      }

I’ve done some reading and found this example, which i don’t fully understand:

const mesh: Mesh // the mesh I want to know the size and position of
const worldMatrix = mesh.getWorldMatrix()
const transformMatrix = scene.getTransformMatrix()
const position = mesh.position
const viewport = scene.activeCamera!.viewport
const coordinates = Vector3.Project(position, worldMatrix, transformMatrix, viewport)

And now I’m stuck, because I don’t understand how I should get the boundingbox for the mesh in 2d.

Has anyone got some insight? :slight_smile:

2 Likes

You could project all the corners of the mesh bounding box and then keep the both with respectively minX minY and maxX maxY so that it would define a rectangle top left and bottom right coordinate on screen.

1 Like

Here’s my finished function, maybe it will help someone else :slight_smile:

private getClientRectFromMesh(mesh: Mesh): ClientRect {

    // get bounding box of the mesh
    const meshVectors = mesh.getBoundingInfo().boundingBox.vectors

    // get the matrix and viewport needed to project the vectors onto the screen
    const worldMatrix = mesh.getWorldMatrix()
    const transformMatrix = this.scene.getTransformMatrix()
    const viewport = this.scene.activeCamera!.viewport

    // loop though all the vectors and project them against the current camera viewport to get a set of coordinates
    const coordinates = meshVectors.map(v => {
      const proj = Vector3.Project(v, worldMatrix, transformMatrix, viewport)
      proj.x = proj.x * canvas.clientWidth
      proj.y = proj.y * canvas.clientHeight
      return proj
    })

    // get the min and max for all the coordinates so we can calculate the largest possible screen size
    // using d3.extent
    const [minX, maxX] = extent(coordinates, c => c.x) as number[]
    const [minY, maxY] = extent(coordinates, c => c.y) as number[]

    // return a ClientRect from this
    const rect: ClientRect = {
      width: maxX - minX,
      height: maxY - minY,
      left: minX,
      top: minY,
      right: maxX,
      bottom: maxY,
    }

    // console.timeEnd('rectfrommesh') // on average 0.05ms

    return rect
  }
17 Likes

It does! :pray:

1 Like

Hey folks, I was working on our website and discovered a slight improvement to this method. The bounding box works in most cases, but if the bounding box is at an angle, the screen coordinates may be offset.

Instead of working with the bounding box, it is possible to observe the individual vertexes of the mesh and project them onto the 2D plane like so:

const meshVertexPositions = mesh.getVerticesData(VertexBuffer.PositionKind)
let meshCoordinates: Vector3[] = []
for (let i = 0; i < meshVertexPositions.length; i+=3) {
    meshCoordinates.push(
        new Vector3(
            meshVertexPositions[i],
            meshVertexPositions[i+1],
            meshVertexPositions[i+2]
        )
    )
}

You can then use the coordinates with the method above. Of course, this method can be less performant as the number of vertices can be quite large. I hope this helps someone in the future. :slight_smile: