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.
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.
Here’s my finished function, maybe it will help someone else
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
}
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.