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

PerspectiveCamera: New helper methods getFrustumBounds(), getFrustumSize(). #27574

Merged
merged 27 commits into from
Jan 19, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
16563ef
🎥 : WIP : New PerspectiveCamera helper method - getMarginAt()
Bug-Reaper Jan 12, 2024
2c3d816
🐛 : FIX : Various small fixes
Bug-Reaper Jan 12, 2024
e8a73c0
🎥 : UPGRADE : Account for camera's matrix world
Bug-Reaper Jan 13, 2024
99bf0c7
🎥 : REFACTOR : getMarginsAt() => frustumHeight(), frustumWidth(), fru…
Bug-Reaper Jan 14, 2024
8351897
🎥 : UPGRADE : More refactor experimentation
Bug-Reaper Jan 14, 2024
253fb2a
🎥 : UPGRADE : Refactored methods provided - Closer to final implement…
Bug-Reaper Jan 16, 2024
98886c9
🎥 : UPGRADE : Tmp vars moved to module scope & Proper imports for Vec…
Bug-Reaper Jan 16, 2024
537e1ea
🎥 : UPGRADE : Consolidate module scoped temp vars w/better re-use
Bug-Reaper Jan 16, 2024
7162815
🎥 : UPGRADE : 3x speed improvement to getBounds() via simplified matr…
Bug-Reaper Jan 16, 2024
8376562
🎥 : NEW : Documentation for getBounds()/frustumDimensions()/frustumCo…
Bug-Reaper Jan 16, 2024
8e019c3
🎥 : UPGRADE : MatrixWorld only relevant on frustumCorners()
Bug-Reaper Jan 16, 2024
a8e2add
🎥 : UPGRADE : MrDoob approves style updates
Bug-Reaper Jan 16, 2024
fc2867d
🎥 : UPGRADE : Gonna gamble this is desired /*__PURE__*/
Bug-Reaper Jan 16, 2024
27a49c6
🎥 : UPGRADE : frustumDimensions => getFrustumDimensions & Use target …
Bug-Reaper Jan 17, 2024
ab65b6e
🎥 : REFACTOR : Move getFrustumCorners() from PerspectiveCamera module…
Bug-Reaper Jan 18, 2024
84b0c11
🎥 : REFACTOR : Rename function + Update comment
Bug-Reaper Jan 18, 2024
fcab13f
🎥 : REFACTOR : Fix case-mistake on function name & updated comment
Bug-Reaper Jan 18, 2024
ef8ba73
Update PerspectiveCamera.js
Mugen87 Jan 18, 2024
e81542d
Update PerspectiveCamera.js
Mugen87 Jan 18, 2024
601461c
Update PerspectiveCamera.js
Mugen87 Jan 18, 2024
d66410e
Update PerspectiveCamera.html
Mugen87 Jan 18, 2024
2432011
Update CameraUtils.js
Mugen87 Jan 18, 2024
03bdbe2
Update PerspectiveCamera.js
Mugen87 Jan 19, 2024
7ae93f5
🚛 : DELETE : `getFrustumCorners()` - Utility to be addressed in a lat…
Bug-Reaper Jan 19, 2024
c1024ad
🎥 : UPGRADE : Bump docs description
Bug-Reaper Jan 19, 2024
c9f5641
🧼 : CLEANUP : Removal of leftover method artifacts
Bug-Reaper Jan 19, 2024
45f8fae
Update CameraUtils.js
Mugen87 Jan 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
23 changes: 21 additions & 2 deletions docs/api/en/cameras/PerspectiveCamera.html
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,26 @@ <h2>Methods</h2>
<h3>[method:undefined clearViewOffset]()</h3>
<p>Removes any offset set by the [page:PerspectiveCamera.setViewOffset .setViewOffset] method.</p>

<h3>[method:Object frustumCorners]( [param:Float distance] )</h3>
<p>
Provides positions of the camera's frustum corners at a given distance from the camera.
Returns an object with topLeft, topRight, bottomRight, and bottomLeft properties where each property is a
[page:Vector3].
</p>

<h3>[method:Vector2 frustumDimensions]( [param:Float distance] )</h3>
<p>
Calculates the height and width of the camera's frustum at a given distance from the camera.
Returns a [page:Vector2] where x is width and y is height.
</p>

<h3>[method:undefined getBounds]( [param:Float distance], [param:Vector2 minTarget], [param:Vector2 maxTarget] )
</h3>
<p>
Calculates the XY of the min/max bounds for the camera's viewable rectangle at a given distance.
Results are copied into minTarget and maxTarget input vars.
</p>

<h3>[method:Float getEffectiveFOV]()</h3>
<p>Returns the current vertical field of view angle in degrees considering .zoom.</p>

Expand Down Expand Up @@ -214,6 +234,5 @@ <h2>Source</h2>
<p>
[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
</p>

</body>
</html>
</html>
35 changes: 30 additions & 5 deletions examples/jsm/utils/CameraUtils.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import {
MathUtils,
Quaternion,
Vector3
Vector3,
Vector2
} from 'three';

const _va = /*@__PURE__*/ new Vector3(), // from pe to pa
Expand All @@ -11,8 +12,9 @@ const _va = /*@__PURE__*/ new Vector3(), // from pe to pa
_vu = /*@__PURE__*/ new Vector3(), // up axis of screen
_vn = /*@__PURE__*/ new Vector3(), // normal vector of screen
_vec = /*@__PURE__*/ new Vector3(), // temporary vector
_quat = /*@__PURE__*/ new Quaternion(); // temporary quaternion

_quat = /*@__PURE__*/ new Quaternion(), // temporary quaternion
_minTarget = /*@__PURE__*/ new Vector2(), // temporary vector2 for minBound
_maxTarget = /*@__PURE__*/ new Vector2(); // temporary vector2 for maxBound

/** Set a PerspectiveCamera's projectionMatrix and quaternion
* to exactly frame the corners of an arbitrary rectangle.
Expand Down Expand Up @@ -64,10 +66,33 @@ function frameCorners( camera, bottomLeftCorner, bottomRightCorner, topLeftCorne
camera.fov =
MathUtils.RAD2DEG / Math.min( 1.0, camera.aspect ) *
Math.atan( ( _vec.copy( pb ).sub( pa ).length() +
( _vec.copy( pc ).sub( pa ).length() ) ) / _va.length() );
( _vec.copy( pc ).sub( pa ).length() ) ) / _va.length() );

}

}

export { frameCorners };
/** Calculate a PerspectiveCamera frustum's corners at a given distance from the camera.
* Returns an object with topLeft, topRight, bottomRight, and bottomLeft properties. Each property is a Vector3.
* Should work on all PerspectiveCameras where the frustum is rectangular in shape.
* Including asymmetric rectangular frustums like those used by webXR cameras.
* @param {number} distance - Distance from the camera
* @param {camera} camera - The PerspectiveCamera to calculate the frustum corners from */
function getFrustumCorners( camera, distance ) {

camera.getBounds( distance, _minTarget, _maxTarget );
Mugen87 marked this conversation as resolved.
Show resolved Hide resolved

camera.updateMatrixWorld( true, false );
Mugen87 marked this conversation as resolved.
Show resolved Hide resolved

// .set() -> Calculates the corner position
// .applyMatrix4() -> Accounts for camera position/rotation/scale
return {
topLeft: _vec.set( _minTarget.x, _maxTarget.y, - distance ).applyMatrix4( camera.matrixWorld ).clone(),
Mugen87 marked this conversation as resolved.
Show resolved Hide resolved
topRight: _vec.set( _maxTarget.x, _maxTarget.y, - distance ).applyMatrix4( camera.matrixWorld ).clone(),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would return values in camera space - not world space. topRight, topLeft, have no meaning once the camera is rotated.

Copy link
Collaborator

@Mugen87 Mugen87 Jan 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems the tests developers usually perform with these coordinates happen in world space. If the vectors are now in camera space, users have to convert their data into camera space as well OR convert the frustum corner points to world space.

I wonder if this makes the workflow unnecessarily complicated.

We often compute bounding volumes in world space knowing they are only valid as long as the object does not move. I think we can make the same assumptions for the corner points as well.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I said, topRight, topLeft, have no meaning in world space. Return the values in camera space. Any user using this method knows how to convert a point from camera space into world space -- if that is what they want.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See it as a stretch to say topLeft fully looses all meaning if the camera is rotated? Agnostic of camera rotation or reference coordinate space, the topLeft vector will still be the point that is rendered to the topLeft of the screen.

Agree that if we return the results in camera-space it blunts the utility meant to be added here. Think world-space is the right play and that the nature of the top/bottom/left/right is still easily apparent.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In addition to my previous comments, I do not think .getFrustumBounds() and .getFrustumCorners() should return results in different coordinate frames.

And I agree, .getFrustumCorners() provides little added utility.

Copy link
Collaborator

@Mugen87 Mugen87 Jan 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Bug-Reaper To avoid an impasse here, I suggest you remove getFrustumCorners() from this PR and create a separate one. In this way, we can at least get the changes to PerspectiveCamera merged now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay guys, gonna remove getFrustumCorners() from this PR so we can get the goods merged ⭐️

I do believe there's tangible utility added from a method that returns the corners in world-space. Perhaps the name conventions can be improved to clearly indicate the functionality. Will take the feedback from this discussion to heart and try to keep it in mind when I get a chance to do a seperate PR for this method.

bottomRight: _vec.set( _maxTarget.x, _minTarget.y, - distance ).applyMatrix4( camera.matrixWorld ).clone(),
bottomLeft: _vec.set( _minTarget.x, _minTarget.y, - distance ).applyMatrix4( camera.matrixWorld ).clone(),
};

}

export { frameCorners, getFrustumCorners };
41 changes: 41 additions & 0 deletions src/cameras/PerspectiveCamera.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import { Camera } from './Camera.js';
import * as MathUtils from '../math/MathUtils.js';
import { Vector2 } from '../math/Vector2.js';
import { Vector3 } from '../math/Vector3.js';

// Temp constants used by getFrustumBounds()/getFrustumDimensions()
const _tempV3 = /*@__PURE__*/ new Vector3();
const _minTarget = /*@__PURE__*/ new Vector2();
const _maxTarget = /*@__PURE__*/ new Vector2();


class PerspectiveCamera extends Camera {

Expand Down Expand Up @@ -99,6 +107,39 @@ class PerspectiveCamera extends Camera {

}

/*
* Computes 2D bounds of the camera's frustum at a given distance along the viewing direction.
* Copies max/min height and width of the frustum's rectangle into minTarget and maxTarget.
* Results are in the camera's local coordinate space, independent of its global position/rotation/scale.
*/
getFrustumBounds( distance, minTarget, maxTarget ) {

_tempV3.set( - 1, - 1, 0.5 ).applyMatrix4( this.projectionMatrixInverse );
_tempV3.multiplyScalar( distance / Math.abs( _tempV3.z ) );
minTarget.x = _tempV3.x;
minTarget.y = _tempV3.y;

_tempV3.set( 1, 1, 0.5 ).applyMatrix4( this.projectionMatrixInverse );
_tempV3.multiplyScalar( distance / Math.abs( _tempV3.z ) );
maxTarget.x = _tempV3.x;
maxTarget.y = _tempV3.y;

}

/*
* Computes the height/width of the camera's frustum at a given distance along the viewing direction.
* Copies the result into provided target Vector2 where x is width and y is height.
*/
getFrustumDimensions( distance, target ) {
Mugen87 marked this conversation as resolved.
Show resolved Hide resolved

this.getBounds( distance, _minTarget, _maxTarget );
Mugen87 marked this conversation as resolved.
Show resolved Hide resolved
target.x = _maxTarget.x - _minTarget.x;
target.y = _maxTarget.y - _minTarget.y;

return target;

}

/**
* Sets an offset in a larger frustum. This is useful for multi-window or
* multi-monitor/multi-machine setups.
Expand Down