Bounding sphere projected height on screen

I have a sphere from a box3.getBoundingSphere(), and a PerspectiveCamera camera rotated (configured with OrbitControls).

I’d like to know the projected sphere’s radius value on my screen.

I know I can use v.project(camera), v being a Vector3 in world coordinates: my problem is I can’t figure out v

Thank you in advance

vector.fromBufferAttribute(mesh.geometry.attributes.position, i);
mesh.localToWorld(vector);

Thank you makc3d,

My mesh is arbitrary, that’s why I made a box3 of it and a bounding sphere from it – but my mesh isn’t a SphereGeometry.

BTW, what I understand from your answer, is to grab any vertex from the sphere surface?

Even though your bounding sphere is based on a box3, you can still compute the bounding box of that bounding sphere via Sphere.getBoundingBox, which would result in another (obviously bigger than your first) box3. That would give you the upmost and the downmost points of the sphere via the box’ min and max, but they will be axis aligned, so not corresponding to the rotated camera.

Since a sphere looks the same from any angle, you’d only need to rotate a suited representation of the new box3 (or alternatively, just the two vectors of the points above) to “face the camera” so you can then project them and get the sphere’s radius in screen space by calculating half of the (let’s say y) difference between the two projections multiplied by half of the screen’s vertical resolution (since the screen space is between -1 and 1, hence size 2).

If you create an invisible cube to represent the rotated box3 (the new bigger one, of course), for example, such a rotation could be achieved via yourcube.lookAt(camera.getWorldPosition(somevector));. Alternatively, if you use just the two vectors the approach could be even simpler, via some .applyMatrix4(), .applyQuaternion() and similar, based on the camera’s corresponding world properties.

1 Like

Your detailed answer is excellent Yin. I will try an implementation of it tomorrow, and maybe share a minimal example of it with you, so it can serve to others as well.

In the meantime, there is one thing I haven’t understood: how can I rotate a box3 face to the camera? I’ve checked in source-code, and Box3 does not inherit from Object3D, so .lookAt method will not available on a Box3.
EDIT: ok I think I got it now → with an (invisible) cube, like you said and as described in this SO answer three.js - Any way to get the faces of the bounding box - Stack Overflow

Thank you again

not any, all of them. then you can do a 2D bounding box and/or radius

this is not going to work since due to a perspective effect other (closer) vertices can be farther apart in screen space.

Oh ok, with all vertices it makes now sense to me.

It seems to be a very systematic technique, but projecting all sphere’s vertices won’t be expensive? Of course it depends on sphere’s segments, but I’m considering this because I’ll have to do that operation at anim rate (60fps), and maybe on several meshes.

Also projecting the vertices the camera can’t see (on sphere’s “back”) isn’t kinda redundant?

Thank you

it will be :sob:

Maybe just decrease radius depend of distance to camera and maybe FOV and size of canvas.
Distance 0 meter, radius 1 meter.
Distance 10 meter, radius 0.5 meter.

Hey Chaser_Code, can you elaborate? Sorry I don’t get it but I’d really like to understand the technique you are suggesting which seems very simple.

Do you mean we can pre-compute projected values for every distances (for a given object size, say of 1m and a given fov) and then interpolate between? (sort of a standard meter camera-grid)

Yes, you got it right. That’s precisely the reason why - if you want to use an object, that is, and not the vectors themselves - a suited representation of that box3, but as an Object3D (so you can manipulate it properly), would be needed.

That might be true, but only if you use the cube “corners”. If you use the “middle” points of the cube sides (which can be easily calculated and represent the points where the sphere is tangent or “touches” the circumscribed cube), I believe you’d get the right values.

The only slight difference would be the fact that in a perspective projection, you can never see the actual top and bottom of a sphere because of its curvature (you’d have to be at an infinite distance from it to theoretically do that), so the circumscribed cube would be a bit larger than the upmost and downmost points of the sphere that you can see. In other words, from a practical point of view, you’d have to apply some form of the angular diameter formula for spheres (i.e. the arcsin() version of the formula, instead of the arctan() one) to “shrink” the cube’s top and bottom values and get the visible topmost and bottom-most points of the sphere.

Alternatively, this could probably be solved from a mathematical point of view (i.e. without bothering to create the second, larger, box3, or iterating through the sphere’s vertices) just by using the first - or the initial - box3 to calculate the top and bottom of a circumscribed sphere, since the radius of a circumscribed sphere of a cube / box is sphereradius = boxsidelength * (Math.sqrt(3) / 2), and boxsidelength = box3.min.y - box3.max.y. Applying a suited form of the angular diameter formula for spheres to lower the radius result to the visible one, rotating the (side’s middle placed, as mentioned above) vectors according to the camera rotation, and then projecting them to get the screen space values converted in pixels would more or less achieve the same, but without creating any other objects or performing intensive operations.

Naturally, feel free to correct me if I’m wrong on this. :wink:

I believe he refers to the fact that given a known size of an object, a known distance from it, and a constant FOV, its size at another distance would be inversely proportional with that distance. In other words, if you have an object whose size is 6 m when seen from 10 m, its size will be 3 m when seen from 20 m. Not sure if that would help in this case, since you deal with projections and rotations, but that’s what it looked like.

1 Like

yes I was specifically talking about these:
image

the pink line would always be under the orange, negligible effect if you are far away from the sphere, but up close it will be significant.

2 Likes

Indeed, you’re right. I believe I covered that when talking about using the arcsin() version of the apparent diameter formula, to account and adjust for the visible up and down points of the sphere, instead of the strictly mathematical middle of sides. It seems I misunderstood what you were saying before, because if you talk about what you illustrated in the picture (nice drawing skills, by the way), we are in complete agreement. :+1:

I have made a (visual) implementation of your solution (the first one, with the bigger circumscribed invisible cube that always lookAt the camera), and it seems to works pretty well: https://codepen.io/abernier/pen/zYjJwKo?editors=0011

As expected, it remains an issue when the camera is getting close:

I will try to fix this with the apparent diameter formula you pointed out, but I’m not sure to understand what δ stands for in the formula: I guess the FOV?
image

I’d like to be able to compute the missing “offset” my top/bottom blue spheres need to “touch” the projected surface.

But I’m not sure about how…

Don’t hesitate to review/fork the pen if you want, I’m not so confident with my code (particularly when I zoom a lot, the computed height is kinda strange, and I don’t know why…)

Alright, good work with the implementation - I’ll take a look at it tomorrow if needed, since now it’s a bit late here. In the meantime, regarding computing the said offsets (well, actually, the apparent size in terms of distance between the top and bottom visible points on the sphere, since you can then easily figure out the rest) it’s just simple right triangle math. And yes, δ is camera.fov.

[I had a nice photoshop figure explaining things, but I accidentally closed it without saving it first (which is not something I normally do), so I’ll explain using your figure, although you’d have to imagine things or write the schematics on a piece of paper instead, for easier understanding.]

First, let’s call the camera point C, the center of the sphere O and the points where the tangents touch the sphere A and B (up and down, respectively), with the AB segment intersecting the CO (of length D) in a point called X. What you need for the apparent length is the AB segment length, instead of d (which is basically R * 2, with R being the sphere radius), so if you know the length of AX, you multiply that with 2 and get AB.

Now, you can easily notice that the CAO and CBO triangles are right triangles, and so are the CAX and CBX ones, or the OAX and OBX ones. In a right triangle, the sides can be expressed as either hypothenuse * sin(oppositeangle) or hypothenuse * cos(adjacentangle) to the said side. That’s the reason for the δ or fov formula in the apparent diameter for the sphere, because you have the CAO right triangle where AO = CO * sin(ACO) aka d / 2 = D * sin(δ / 2), thus sin(δ / 2) = d / 2 * D and then applying arcsin becoming δ / 2 = arcsin(d / 2 * D). You may also notice that in this triangle, the COA angle is 90 degrees minus the other acute angle aka half of the fov, so by extension the XOA angle has the same value, PI - δ / 2.

On the second right triangle of interest, i.e. OAX, given the fact that we know the value of the hypothenuse (d / 2 or R) and the opposite angle of the AX segment aka the XOA angle (PI - δ / 2), it becomes clear that AX = d / 2 * sin(PI - δ / 2) = R * sin(PI - δ / 2), or even R * cos(δ / 2) if you find it easier.

Now you can multiply by 2 the above value and get the “length” of the apparent diameter, as 2 * R * sin(PI - δ / 2) = 2 * R * cos(δ / 2). In other words, you’ll need to multiply the actual diameter d with either sin(PI - δ / 2) or cos(δ / 2) to get the visible diameter. :wink:

are you sure? because both numbers are < 1, and visible diameter is supposed to be larger, not smaller.

It’s now perfectly clear for me, that we are looking for AB length (that was what I was missing because the wikipedia diagram was not exaggerated enough to see the difference with d)

I did my trigonometry following your awesome and detailed reasoning, I post here the doodle for reference :wink:

Now that I have my mind clear about the formula, I’m going to apply it to my code, I let you know :wink:

Thank you very much Yin