Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

"Fit all" (show all) feature #6784

Open
pavelvasev opened this issue Jul 3, 2015 · 36 comments
Open

"Fit all" (show all) feature #6784

pavelvasev opened this issue Jul 3, 2015 · 36 comments

Comments

@pavelvasev
Copy link
Contributor

Dear ThreeJS developers!

Please implement "fit all" ("show all") feature to the camera and/or OrbitControl and other controls.
This feature is very missing thing.

To provide some help, here is implementation of same feature in X3dom:
https://github.com/x3dom/x3dom/blob/master/src/Viewarea.js#L1077

Also, there is "fit object" (e.g. show object) feature:
https://github.com/x3dom/x3dom/blob/master/src/Runtime.js#L631

Many thanks in advance.

Please do not refer me to other feature requests like #1095

If you deny to do this, please at least point me, where is it better to code it, e.g. in camera, or in OrbitControl/etc, to get them (camera and control) synchronized?

@WestLangley
Copy link
Collaborator

Here is one related answer: http://stackoverflow.com/questions/14614252/how-to-fit-camera-to-object. (There are others.) In your case, the "object" could be a bounding box of the scene.

@pavelvasev
Copy link
Contributor Author

Many thanks to you for this information. BTW, according to your formula, dist equation is:

dist = height / 2*tg( (fov/2)  * (Pi/180)  )

But. I think it is good to have camera "show all" / "show object" commands as a build in feature, instead of pointing your users to formulas..

@WestLangley
Copy link
Collaborator

But. I think it is good to have camera "show all" / "show object" commands as a build in feature, instead of pointing your users to formulas..

The aspect ratio of the window and the desired margin are factors in specifying what "show all" means. I am concerned that such a feature would be too restrictive.

We currently provide Box3.setFromObject( object ) from which the user can get the AABB. The user can then set the camera position/fov/zoom as desired.

@pavelvasev
Copy link
Contributor Author

You can ask user about those variables (window aspect etc) as function arguments.

Or, if you think that the api will be too fat, you can put this feature in some helper class..

Or at least, if I have to do this feature by my own, please tell me which target this helper should affect - camera or camera controller (OrbitController? etc) in terms of easy switching between controllers during visualization.

@WestLangley
Copy link
Collaborator

please tell me which target this helper should affect

Sorry, I can't help you write your app. Use stackoverflow if you need help.

@pavelvasev
Copy link
Contributor Author

I'm sorry, but ThreeJs is your application. And I ask advice how to modify
it better.
06.07.2015 23:01 пользователь "WestLangley" [email protected]
написал:

please tell me which target this helper should affect

Sorry, I can't help you write your app. Use stackoverflow if you need help.


Reply to this email directly or view it on GitHub
#6784 (comment).

@WestLangley
Copy link
Collaborator

OK. Sorry. You have to be careful about changing the camera properties "behind the back" of the controller. If you want to add this feature, and you are using a controller, I expect it would be better to add it to the controller, which in turn calls a camera showObject() method.

@mrdoob
Copy link
Owner

mrdoob commented Jul 6, 2015

@pavelvasev are you suggesting something like camera.focus( object )? Maybe there is a better name than focus...

The aspect ratio of the window and the desired margin are factors in specifying what "show all" means. I am concerned that such a feature would be too restrictive.

@WestLangley Would the aspect ratio of the camera be good enough?

@WestLangley
Copy link
Collaborator

Suppose we had a method camera.focusOn( object ). How would that method work?

Would it move the camera? To where? Would it change the camera field-of-view? The zoom? The rotation? Would it change all four properties? What are the constraints on camera movement/orientation? Note that OrbitControls has constraint parameters. What happens if the solution violates those constraints? etc. etc. etc...

A method such as camera.focusOn( object ) is use-case-specific, and belongs at the application layer, IMO.

@pavelvasev
Copy link
Contributor Author

@mrdoob yes something like camera.focus( object )

Suppose we had a method camera.focusOn( object ). How would that method work?

@WestLangley I understand your skepticism about too many possible algorithms and no priorities on them. But ThreeJS can implement only one (or few) algorithm. And if somebody want another one, he can implement it's own using yours as a base.

As for me, fov and zoom camera parameters should be unchanged.

One algorithm might be:

  • bbox = bounding box of the object
  • camera.lookAt bbox.center
  • camera position = bbox.center + dist * (normalized vector from previous camera position to bbox.center)

where dist is calculated so bbox will fill the screen.

@mrdoob
Copy link
Owner

mrdoob commented Jul 7, 2015

Would it move the camera? To where? Would it change the camera field-of-view? The zoom? The rotation? Would it change all four properties? What are the constraints on camera movement/orientation?

The procedure suggested by @pavelvasev sounds good. But I would use a bounding sphere instead.

It actually solves a common issue I've seen: People tend to "lose" their objects. Nothing renders on the screen, and it's just because the object is behind the camera. Having a method like this could be handy also for us to help people.

@WestLangley
Copy link
Collaborator

But I would use a bounding sphere instead

That's fine, if you want. You need to use the bounding sphere of the object -- and all of its children.

Also, you need to take into consideration the camera frustum. If you move the camera, the bounding sphere must remain between the near and far planes. ( You can't move in too close just to see a tiny object.)

Also, it should work on orthographic and perspective cameras.

And the view needs to fit both the width and height of the canvas, so the aspect ratios come into play.

@mrdoob
Copy link
Owner

mrdoob commented Jul 7, 2015

And the view needs to fit both the width and height of the canvas, so the aspect ratios come into play.

Yep, that's why I was asking if the camera.aspect is good enough or if renderer is needed...?

@dubejf
Copy link
Contributor

dubejf commented Jul 7, 2015

This would be a nice convenience feature, but I agree with @WestLangley that it belongs to the application layer. In addition of the existing arguments, consider the lack of control over the camera movement / speed / tweening with this interface.

Maybe a new helper would be the way to go? Using the "plugin" approach championed in #6782:

THREE.SimpleCameraContols = function ( camera ) {

    // parameters, constraints here

    function focusOn( obj ) {

        // implement

    }

    // extend camera
    camera.focusOn = focusOn;

};

Usage:

camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 1, 10000 );
orbit = new THREE.OrbitControls( camera );
simple = new THREE.SimpleCameraContols( camera );
...
camera.focusOn( obj );

I'm not sure if focusOn on should be added to the camera or the controls.

@WestLangley
Copy link
Collaborator

Yep, that's why I was asking if the camera.aspect is good enough or if renderer is needed...?

Yea, I was purposely vague. : - ) Maybe both. I am not sure. It depends on the use case, I think....

Like I said, I think this should be an application-level method -- for the same reason you wanted to move the Controls to the examples -- every user has his own requirements and constraints.

@WestLangley
Copy link
Collaborator

Oh. Posted concurrently with @dubejf ...

@mrdoob
Copy link
Owner

mrdoob commented Jul 7, 2015

Like I said, I think this should be an application-level method -- for the same reason you wanted to move the Controls to the examples -- every user has his own requirements and constraints.

Yep yep. However, I think this one is closer to .lookAt() than a camera control.

@mrdoob
Copy link
Owner

mrdoob commented Jul 7, 2015

If this requires the renderer then yes, it should be a controls thing, but if it's something that can be self-contained in the camera I think it could be nice thing to have.

@WestLangley
Copy link
Collaborator

@mrdoob The OP said this:

please tell me which target this helper should affect - camera or camera controller (OrbitController? etc) in terms of easy switching between controllers during visualization.

So he wants it to work with the controls. That is why I said it would have to be a controls method -- which could in turn, call a camera method.

Finding a solution that satisfies the controls position/rotation constraints and the camera frustum constraints would be required.

You are suggesting a simpler thing: just a camera method to help a user set the camera to view an object and its children. Such a method might be useful for the editor. That can be implemented, but it would be more like a utility -- the camera can be placed in front of the object, looking in the direction of the negative world z-axis, and set at a distance from the object so the entire object is rendered.

Some users might not like that solution, however, as it involves moving the camera to a new place.

@arodic
Copy link
Contributor

arodic commented Jul 8, 2015

Keep in mind that focusing controls on an object requires both camera and target to be positioned (the rotation center of controls). Therefore, implementing this on camera level would require camera to include target property.

@mrdoob
Copy link
Owner

mrdoob commented Jul 8, 2015

Some users might not like that solution, however, as it involves moving the camera to a new place.

Yep, I think that's ok.

Keep in mind that focusing controls on an object requires both camera and target to be positioned (the rotation center of controls). Therefore, implementing this on camera level would require camera to include target property.

Even if the OP was asking this to be added to the controls, I would first focus (no pun intended 😅) in the cameras and later sort out the controls.

@pavelvasev
Copy link
Contributor Author

Probably it will be good (if possible) to split camera focus implementation
on two parts (submethods)

  1. compute new camera parameters (eg lookat, position)
  2. assign them to camera.

So somebody could use both parts and somebody only part 1… for example for
camera animation. Also maybe it will allow to interconnect with camera
controllers easier.
08.07.2015 22:38 пользователь "Ricardo Cabello" [email protected]
написал:

Some users might not like that solution, however, as it involves moving
the camera to a new place.

Yep, I think that's ok.

Keep in mind that focusing controls on an object requires both camera and
target to be positioned (the rotation center of controls). Therefore,
implementing this on camera level would require camera to include target
property.

Even if the OP was asking this to be added to the controls, I would first
focus (no pun intended 😅) in the cameras and later sort out the controls.


Reply to this email directly or view it on GitHub
#6784 (comment).

@pavelvasev
Copy link
Contributor Author

I must clarify. I mean that camera.focus method stays solid, but internally
it calls 2 other methods, eg compute and assign. And both these methods are
callable by user too.
08.07.2015 23:15 пользователь "Pavel Vasev" [email protected] написал:

Probably it will be good (if possible) to split camera focus
implementation on two parts (submethods)

  1. compute new camera parameters (eg lookat, position)
  2. assign them to camera.

So somebody could use both parts and somebody only part 1… for example for
camera animation. Also maybe it will allow to interconnect with camera
controllers easier.
08.07.2015 22:38 пользователь "Ricardo Cabello" [email protected]
написал:

Some users might not like that solution, however, as it involves moving
the camera to a new place.

Yep, I think that's ok.

Keep in mind that focusing controls on an object requires both camera and
target to be positioned (the rotation center of controls). Therefore,
implementing this on camera level would require camera to include target
property.

Even if the OP was asking this to be added to the controls, I would first
focus (no pun intended 😅) in the cameras and later sort out the
controls.


Reply to this email directly or view it on GitHub
#6784 (comment).

@Usnul
Copy link
Contributor

Usnul commented Jul 17, 2015

One thing to consider is animated objects. Currently bone animations are done on GPU, which is a good thing, but it means we don't have a way to keep track of bounding box on CPU side easily, and recalculating is would be expensive. Also, depending on complexity of your object and frequency at which its BB changes - it could take a while to update. Finally, depending on camera angle, BB that you want would change, AABB in that respect would use camera space for axis alignment. hence why "lookAt" has survived from times of glut (i think), it is straight-forward in its function (what @WestLangley mentioned) and it has constant time complexity.

@robhawkes
Copy link

I'm particularly interested in something like this myself for my work with ViziCities – there are situations where I have a Three.js scene within a HTML element that has strict dimensions and need to have the included objects fill that element (ideally with a definable padding / margin).

Currently, I'm using a mish-mash of formulas based on the various StackOverflow posts but my lack of understanding or something else is causing it to be far too verbose and complicated for my needs. It's also not quite right for some reason, probably because I need a solution that accommodates both width and height of the element, plus needs to accommodate objects that can be very wide or very tall (buildings).

Something like camera.focusOn(Object3D) or similar would be a good base for me to tweak and customise further for my needs. For example, I can use camera.focusOn(Object3D) and then pull the camera back a little from there as I need a little leeway as I have to ensure the object doesn't clip off the edges when animated with a 360 degree rotation (ie. when viewed at a diagonal).

@gpolyn
Copy link

gpolyn commented Jun 23, 2016

Just finished a demo implementation for a (bounding) box, using OrbitControl, that may be of use to readers, here: jsfiddle.net/gpolyn/k40c5du5

Edit: Don't mean to promote the demo as the basis for implementation, more as an illustration.

@smcllns
Copy link

smcllns commented Jul 18, 2017

Few thoughts from a newbie who recently hit this problem:

  1. I would definitely have welcomed a solution for this as part of the library, especially when there are other great methods such as lookAt. Implementing my own solution forced me to understand Perspective, which seems simple now, but was pretty intimidating to begin with.

  2. In my case, I had groups of objects being created around (0,0,0), and I didn't control their size or how many there were. I just needed them to be visible in the display, without the user having to adjust their view.

  3. Here's a simplified version of what I implemented:

function autoFitTo( obj, camera, controls ) {

  const boundingSphere = new Box3().setFromObject( obj ).getBoundingSphere();

  const scale = 0.618; // object size / display size
  const objectAngularSize = ( camera.fov * Math.PI / 180 ) * scale;
  const distanceToCamera = boundingSphere.radius / Math.tan( objectAngularSize / 2 )
  const len = Math.sqrt( Math.pow( distanceToCamera, 2 ) + Math.pow( distanceToCamera, 2 ) )

  camera.position.set(len, len, len);
  controls.update();

  camera.lookAt( boundingSphere.center );
  controls.target.set( boundingSphere.center.x, boundingSphere.center.y, boundingSphere.center.z );

  camera.updateProjectionMatrix();

}

// Helpful: https://stackoverflow.com/questions/34098571/fit-3d-object-collada-file-within-three-js-canvas-on-initial-load
  1. I'm probably the least experienced person on this thread so I won't pretend to understand what is a good approach for the library. FWIW, in my simple case, it would have been great to have a method on the camera, like camera.autoFitTo( obj ). I think complete noobs would be happy with the library positioning the camera for them, and if it could take an optional vector to translate the camera along, and could update my OrbitControls, like camera.autoFitTo( obj [, vector, controls ] ), that would have provided most of the functionality I needed.

@looeee
Copy link
Collaborator

looeee commented Jul 18, 2017

I'm agnostic on adding this to the library - certainly it's a useful function though. Here's my take on it, also setting the camera plane encompass the object, and optionally targeting the object centre with orbitControls and setting the zoom so that the far plane cutoff is never encountered. This is assuming a PerspectiveCamera.

  const fitCameraToObject = function ( object, offset, orbitControls ) {

    const boundingBox = new THREE.Box3();
    boundingBox.setFromObject( object );

    const center = boundingBox.getCenter();
    const size = boundingBox.getSize();

    // get the max side of the bounding box
    const maxDim = Math.max( size.x, size.y, size.z );
    const fov = this.camera.fov * ( Math.PI / 180 );
    let cameraZ = Math.abs( maxDim / 4 * Math.tan( fov * 2 ) );
    
    // offset the camera as desired - usually a value of ~ 1.25 is good to prevent
    // object filling the whole canvas
    if( offset !== undefined && offset !== 0 ) cameraZ *= offset;

    camera.position.set( center.x, center.y, cameraZ );

    // set the far plane of the camera so that it easily encompasses the whole object
    const minZ = boundingBox.min.z;
    const cameraToFarEdge = ( minZ < 0 ) ? -minZ + cameraZ : cameraZ - minZ;

    this.camera.far = cameraToFarEdge * 3;
    this.camera.updateProjectionMatrix();

    if ( orbitControls !== undefined ) {

      // set camera to rotate around center of loaded object
      orbitControls.target = center;

      // prevent camera from zooming out far enough to create far plane cutoff
      orbitControls.maxDistance = cameraToFarEdge * 2;

    }

  };

As mentioned above, there is an issue with animated object - this doesn't work as the position can be calculated on the GPU, so Box3.setFromObject doesn't work properly.

What about adding something like this to OrbitControls?

@Usnul
Copy link
Contributor

Usnul commented Jul 18, 2017

They are both good options @looeee @smcllns .

2 problems:

  • you both are fitting a sphere into the viewing frustum.
  • sphere in @looeee case is from world axis-aligned bounding box, so it will be an overestimate.

Imagine a mesh for a hexagonal pencil and the camera is positioned on the line going through the lead of the pencil. Your bounding sphere is going to have the same diameter as the length of the pencil, so you will end up seeing a small hexagon in the middle of the viewport (the pencil). instead of that hexagon filling your entire viewport.

You first need to transform coordinates of geometry vertices into view space (from world space), and only then compute the bounding box.

@looeee
Copy link
Collaborator

looeee commented Jul 18, 2017

@Usnul that's true - actually that was intentional, as I wrote the function to enable previewing models quickly using the OrbitControls, so as the camera rotates around the centre of the bounding box the full object will always be in view. It's a very common use case, hence my suggestion that this be added as a function of the controls.

It could easily be changed to display your hypothetical hexagonal pencil end fullscreen though by changing

 const maxDim = Math.max( size.x, size.y, size.z );

to

const maxDim = Math.max( size.x, size.y );

@moroine
Copy link
Contributor

moroine commented Jul 20, 2018

Hello,

I've worked on camera centering and I manage to make it fit perfectly to the camera!

https://codepen.io/bilouwan/project/full/XpgGOp/

Do you think this could be added to three.js?

@looeee
Copy link
Collaborator

looeee commented Jul 20, 2018

@WestLangley recently suggested that we add a GameUtils.js file to the examples. This seems like a good candidate for that.

@moroine I haven't examined yours in detail, but it seems rather complex. There are quite a few examples of this function in the wild, and here - I'd rather go for one of the simpler ones. People can expand it for their own use case.

@moroine
Copy link
Contributor

moroine commented Jul 20, 2018

Yeah, there is a bunch of them, but I didn't find something satisfying in my use case when objects have spread around the scene.

It's complicated, for sure but I'll try to explain the methodology and mathematical proof

@looeee
Copy link
Collaborator

looeee commented Jul 20, 2018

From my point of view, a "fit camera to object" method should not care about other objects in the scene.

You should be able to pass in any object in your scene and a camera, and the camera will be transformed and updated in whatever way neccessary to fit the view to the object, based on the bounding box of that object.

@moroine
Copy link
Contributor

moroine commented Jul 20, 2018

Yeah but here it's more about multiple objects, but I also think that this is overkill in many cases but the common approach with axis-aligned bounding box is kind of unpredictable depending on the camera orientation.

@looeee
Copy link
Collaborator

looeee commented Jul 20, 2018

axis-aligned bounding box is kind of unpredictable depending on the camera orientation.

I think we can leave dealing with that up to the user. We're not trying to create a game engine here.

Rather, would be better to create a minimal function and document the limitations:

// limitations: 
// - doesn't set clipping planes
// - doesn't take into account animation
gameUtils.fitCameraToBoundingBox = function( object3D, camera ) { ... }

Then the function is mainly just a bit of trigonometry that users may find problematic. In particular, I like adding this method because if you search for this, you'll find quite a few different answers around the internet, and quite a few are wrong. If we add this, then when the question comes up we can point to this and say "hey, here's one that we tested and with correct math".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.