How to determine a sphere's radius to completely fill the viewport?

My world is centered on the origin. Given an arbitrary container canvas and an ArcRotateCamera at a fixed distance (no zooming, starts at -Z, looking at the origin) and with perspective projection is there a straight-forward way to determine the radius of a sphere that would completely fill the canvas, but no more? That is, any ray cast would intersect the sphere, and zooming out would immediately leave gaps in the corners of the scene/canvas.

I wondered if something like creating a temporary plane on the camera near-plane and casting a ray from the corner of the canvas to it and using the distance as a radius would work. It seemed a bit of a hack. Similarly multiple concentric sphere’s and hope for the best or camera fustrum plane intersection. My geometry-foo is rusty and I don’t know if I should reach for more generalised geometry libraries and do plane/plane intersections. Another thought was that I should be able to use the camera distance from the origin with zmin=0, but this gives me too bit a sphere.

For context I want to drag objects round a (smaller) sphere and need to have a sensible direction to point them in when the user’s pointer is not over this smaller sphere.

Other than that, loving Babylon.js. Thanks in advance for any pointers. Oh, and happy to clarify any points if what’s in my head hasn’t made it onto the page!

Welcome aboard!

Maybe you can try something like that:

It works by first computing the screen z coordinate corresponding to the (0,0,0) 3D world (because it’s where the final sphere will be created). Then it uses this z coordinate in screen space to back project the screen corners (0, 0, z) and (screen_width, screen_height, z) to world coordinates. Then the diameter of the sphere is computed as the distance between those two points.

It only works if the camera is looking right ahead. You will need additional work for arbitrary look at vectors.

1 Like

Thanks for looking into this. Your approach works for a disc but - I’m guessing due to the perspective projection - if I substitute a sphere in its place it’s too large. The rays from the corners of the screen intersect the sphere and are not tangential to it.

I may be over-thinking things and it may be sufficient to simply work out the (2D) angle between pointer and center of screen, apply the camera rotation to that (with z=0) and use that (now 3D) as the rotation for my cursor-following-on-sphere object. I’m happy converting that to a quaternion representation.

I’ve also had a play with a combination of unproject and pick which give me the/a front and back world coordinate for the camera FoV. This is the vector that I (think I) want my large sphere to be tangential to. But as I say, maybe I’m overthinking it.

Further to the above musing I found a solution that calculates the radius of the sphere tangent to my picked/unprojected line using maths found on http://paulbourke.net. Turns out that it’s not actually the best solution but it’s the journey, not the destination, right? Ugly, unoptimised but serviceable code below. No playground since I had issues getting the scene matrices in getScene() (both were null). I’m sure there’s a way of having them initialised prior to the render loop but I’ve not found it yet.

I’ll probably go back to a 2D angle and rotate my dragged objects according to the camera rotation. I remember an old Arcball implementation I did using the same. Thanks again for the pointers.

const material = new StandardMaterial("material", this.scene!)
material.wireframe = true

// Pointer coords (and also width/height)          
const x = engine.getRenderWidth(), width = x
const y = engine.getRenderHeight(), height = y
                
// Back projection
const P1 = Vector3.Unproject(new Vector3(x, y, -11), width,height, Matrix.Identity(), scene.getViewMatrix(), scene.getProjectionMatrix());
            
// Front projection
const P2 = scene.pick(x,y)!.ray!.origin

const a = Math.pow(P2.x - P1.x, 2) + Math.pow(P2.y - P1.y, 2) + Math.pow(P2.z - P1.z, 2)
const b = 2 * (((P2.x - P1.x) * P1.x) + ((P2.y - P1.y) * P1.y) + ((P2.z - P1.z) * P1.z))
const r = new Vector3(
    P1.x +(-b/(2*a) * (P2.x-P1.x)),
    P1.y +(-b/(2*a) * (P2.y-P1.y)),
    P1.z +(-b/(2*a) * (P2.z-P1.z))).length()
this.allSphere = createSphere("allSphere", material, r, scene)
1 Like