-
-
Notifications
You must be signed in to change notification settings - Fork 35.2k
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
PerspectiveCamera - New helper method .getMarginAt(distance)
#27556
Comments
@WestLangley How about we turn your answer into a separate helper method first. Considering the upvotes for your answer, it seems a lot of devs are searching for this topic. Maybe something like Sidenote: Since the dimension at a distance between |
Just a note that the answer provided in the link is only relevant for the symmetrical projection matrices set via the PerspectiveCamera fields. It will not provide a correct answer for manually assigned skewed or other custom projection matrices like those WebXRManager assigns. |
Anyway, I would prefer an API that is something like the following: frustumHeight( distance ) { // or visibleHeight()
return 2 * Math.tan( MathUtils.DEG2RAD * 0.5 * this.fov ) * distance / this.zoom;
}
frustumWidth( distance ) { // or visibleWidth()
return this.frustumHeight( distance ) * this.aspect;
} |
I like the API suggestion proposed 👍 Took that and added a couple of things:
Plan to commit in the docs bump soon and PR. |
Do devs typically want both the frustum height and width at the same time? Or are there use cases where you only want one of them? If you need both most of the time, I find it a waste that
As long as we didn't solve #26659, maybe it's better to ignore the scale topic at the moment. |
Imo this should operate on the projection matrix itself rather than using the settings on the camera. And I think it's okay to make an assumption that the projection matrix does not include other rotation or translation transforms in it. Again because these matrices can be off-axis it's not enough to return a width and height. If a user is using this with a WebXR frustum to place something the result will not be what's expected. Providing a 2d min bounds and 2d max bounds point should be enough to represent the view bounds at a certain distance in the frustum. These would be bounds provided in the camera frame. const distance = 1;
const min = new Vector2();
const max = new Vector2();
camera.getViewBounds( distance, min, max )
I think I would expect both of them to be provided simultaneously as all other size getter functions do. |
Of my own common use-cases, most of them need both. Most of the dev inquiries about this in discord/forum/SO are for cases where both are needed.
I noticed this too, but did like the API layout of separate height/width methods 🤔. For performance sensitive implementation you could always run
Agree with this. Here's an alternate version where we have:
I'd like to improve and account for WebXR frustum use-cases also. Believe either of the past two implementations adds a lot of value for default PerspectiveCamera based development already. |
So I see that we can extract some props from the PerspectiveMatrix: const aspectRatio = projectionMatrix.elements[5] / projectionMatrix.elements[0];
const fovRadians = 2 * Math.atan(1 / projectionMatrix.elements[5]);
// const zoomFactor = ???? Couldn't figure this one? Possibly irrelevant b/c of update to matrix[0]/matrix[5]? I think fov calc is marginally faster and aspect ratio is marginally slower. I guess the benefits are more to do with custom perspectiveMatrix situations. Also I'm a little unclear how two |
I'd start with a correct answer before worrying about marginal performance differences.
As an example here's a visualization of a more extreme off axis frustum from this presentation. As you can see providing just the width and height of the frustum at a certain distance is not enough to capture the box that describes the view.
It's the same principle as how Box3 can describe an axis aligned bounding box with two 3d Vector3s. Each point describes the minimum and maximum extent along the x and y axis You can find the bottom left and top right points of this 2d bounds by projecting a ray out onto the distance you want the bounds at, similar to raycaster.setFromCamera. Note that this code is notional and should be validated: function getBounds( dist, minTarget, maxTarget ) {
temp.set( - 1, - 1, 0.5 ).unproject( camera );
temp.multiplyScalar( dist / Math.abs( temp.z ) );
minTarget.x = temp.x;
minTarget.y = temp.y;
temp.set( 1, 1, 0.5 ).unproject( camera );
temp.multiplyScalar( dist / Math.abs( temp.z ) );
maxTarget.x = temp.x;
maxTarget.y = temp.y;
} |
So if developers want the width and height of the visible rectangular region at a certain distance, they would do this: camera.getBounds( dist, minTarget, maxTarget );
const width = maxTarget.x - minTarget.x;
const height = maxTarget.y - minTarget.y; How about we simplify the width/height computation and provide an additional method like getFrustumDimensions( dist, target ) {
camera.getBounds( dist, minTarget, maxTarget );
target.x = maxTarget.x - minTarget.x;
target.y = maxTarget.y - minTarget.y;
return target;
}
Everyone happy with this? 😇 |
Are there use cases that only need the dimensions of the view port but not the bounds? The ones all listed in OP require placing or animating something relative to the view rectangle. I suppose if a user could just be assuming that the frustum is symmetrical when placing something? Either way I'm fine with the proposal. I'm just curious if the width and height are actually what's needed for the provided use cases. |
I guess it does not hurt if @Bug-Reaper Are you in for a PR^^? |
Some of the common scenarios lend themselves to a centerpiece object that needs to be scaled to fit perfectly within height/width. In these cases, the specific bounds are not needed since object is placed in the already-known center of view. Example codepen (not mine) where some 3D stuff is sized based on frustum height/width and not bounds. Proposal sounds good to me 🤝 Tested // ...
temp.set( - 1, - 1, 0.5 ).unproject( camera ).applyMatrix4(cam.matrixWorldInverse);
// ...
temp.set( 1, 1, 0.5 ).unproject( camera ).applyMatrix4(cam.matrixWorldInverse);
// ...
// EDIT: Looks like we can temp.set( x, y, z ).applyMatrix4( camera.projectionMatrixInverse ) to accomplish same as above 3x faster. Plan to amend in final proposed code Love this approach! All together here's 3 methods: getBounds( distance : float, minTarget : Vector2, maxTarget : Vector2 ) : undefined
frustumDimensions( distance : float ) : Vector2
frustumCorners( distance : float ) : Object All methods should now account for asymmetric perspective matrix via the improved |
@Bug-Reaper Some feedback for your commit:
this.updateWorldMatrix( true, false );
|
Thanks @Mugen87 ! Got these changes in and added docs 👌 As an aside, realized that getBounds/frustumDimensions are agnostic to the matrix world lol. We now run Also did some simple benchmarks and these all should be renderLoop safe ~0.01ms or less |
Thanks everyone 👍 New PerspectiveCamera methods added #27574 Method names updated here #27605
Both should be available in version >161 Plan to start a new discussion/PR for a CameraUtils method that gets the Vector3 positions of the camera corners (in world-space) at a given distance at some later time. |
Description
Looking to PR a new PerspectiveCamera helper method
.getMarginAt(distance)
which returns the height, width and Vector3s for the corners of the camera's viewable area at the input arg distance.A PerspectiveCamera's viewable margins at an arbitrary distance is a foundational piece of info you can build a lot of clean experiences from. It enables consistent position and scale for 3D designs and is practical for devices or UX with a variable aspect ratio that must be accounted for.
Some common use-cases include:
Solution
Took a function I've been copying around for years and modified it to be a helper method like so.
Alternatives
Wanted some takes on how to best return the result, currently returns the info in a JSON obj like this:
I can't think of any methods that return JSON like this but perhaps it's okay? I also couldn't find any native THREE class that contains all of the above in an easily accessible way. If we don't want to just return JSON obj like this perhaps there's an opportunity to extend an existing class or create a new one for this use-case.
The text was updated successfully, but these errors were encountered: