I'm also running a bit behind schedule, but I managed to convert the scscsc code into TypeScript. Additionally, I have incorporated an additional feature where I compute the bounding box only from visible objects/meshes.
The coordinates range from -1 to 1, and to determine sizes, simply multiply these values by the canvas size.
Update: I included normalization after projection because in certain scenarios, I was getting negative or larger values than [-1, 1].
computeScreenSpaceBoundingBox(obj: Object3D, camera: Camera): Box2 {
let min: Vector2 | undefined = undefined;
let max: Vector2 | undefined = undefined;
// Check if it's an array of objects
if (Array.isArray(obj)) {
for (let i = 0; i < obj.length; ++i) {
if (obj[i].visible) {
const box2 = Utils3D.computeScreenSpaceBoundingBox(obj[i], camera);
if (min === undefined) min = box2.min.clone();
else min.min(box2.min);
if (max === undefined) max = box2.max.clone();
else max.max(box2.max);
}
}
}
// Does the object have geometry?
if (obj.visible) {
if (obj instanceof Mesh && obj.geometry !== undefined) {
const vertices = obj.geometry.vertices;
if (vertices === undefined && obj.geometry.attributes !== undefined && "position" in obj.geometry.attributes) {
// Buffered geometry
const vertex = new Vector3();
const pos = obj.geometry.attributes.position;
for (let i = 0; i < pos.count * pos.itemSize; i += pos.itemSize) {
vertex.set(pos.array[i], pos.array[i + 1], pos.array[i + 2]);
const vertexWorldCoord = vertex.applyMatrix4(obj.matrixWorld);
const vertexScreenSpace = vertexWorldCoord.project(camera).normalize();
if (min === undefined) {
min = new Vector2(vertexScreenSpace.x, vertexScreenSpace.y);
} else {
Utils3D.min(min, vertexScreenSpace);
}
if (max === undefined) max = new Vector2(vertexScreenSpace.x, vertexScreenSpace.y);
else Utils3D.max(max, vertexScreenSpace);
}
} else {
// Regular geometry
const vertex = new Vector3();
for (let i = 0; i < vertices.length; ++i) {
const vertexWorldCoord = vertex.copy(vertices[i]).applyMatrix4(obj.matrixWorld);
const vertexScreenSpace = vertexWorldCoord.project(camera).normalize();
if (min === undefined) {
min = new Vector2(vertexScreenSpace.x, vertexScreenSpace.y);
} else {
Utils3D.min(min, vertexScreenSpace);
}
if (max === undefined) max = new Vector2(vertexScreenSpace.x, vertexScreenSpace.y);
else Utils3D.max(max, vertexScreenSpace);
}
}
}
}
// Are there any children objects?
if (obj.children !== undefined) {
for (let i = 0; i < obj.children.length; ++i) {
if (obj.children[i].visible) {
const box2 = Utils3D.computeScreenSpaceBoundingBox(obj.children[i], camera);
if (min === undefined) min = box2.min.clone();
else min.min(box2.min);
if (max === undefined) max = box2.max.clone();
else max.max(box2.max);
}
}
}
return new Box2(min, max);
}
Utilizing Utils3D functions which were necessary due to errors in types definition.
static min(v2: Vector2, v3: Vector3): void {
if (v2.x > v3.x) v2.x = v3.x;
if (v2.y > v3.y) v2.y = v3.y;
}
static max(v2: Vector2, v3: Vector3): void {
if (v2.x < v3.x) v2.x = v3.x;
if (v2.y < v3.y) v2.y = v3.y;
}