Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 123 additions & 0 deletions core/math/projection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,118 @@ Projection Projection::create_fit_aabb(const AABB &p_aabb) {
return proj;
}

Projection Projection::create_combined_projection(const Transform3D &p_center, const Projection &p_projection_a, const Transform3D &p_offset_a, const Projection &p_projection_b, const Transform3D &p_offset_b) {
Vector<Plane> planes[2];
Projection proj;

/////////////////////////////////////////////////////////////////////////////
// Get some basic information

// 1. obtain our planes
planes[0] = p_projection_a.get_projection_planes(p_offset_a);
planes[1] = p_projection_b.get_projection_planes(p_offset_b);

// 2. Intersect horizon, left and right to obtain the combined camera origin.
Plane horizon(Vector3(0.0, 1.0, 0.0), Vector3());
Vector3 origin;
ERR_FAIL_COND_V_MSG(
!horizon.intersect_3(planes[0][Projection::PLANE_LEFT], planes[1][Projection::PLANE_RIGHT], &origin), proj, "Can't determine camera origin");

// 3. figure out our near and far plane, this could use some improvement, we may have our far plane too close like this, not sure if this matters
Vector3 near_center = (planes[0][Projection::PLANE_NEAR].get_center() + planes[1][Projection::PLANE_NEAR].get_center()) * 0.5;
Plane near_plane = Plane(Vector3(0.0, 0.0, -1.0), near_center);
Vector3 far_center = (planes[0][Projection::PLANE_FAR].get_center() + planes[1][Projection::PLANE_FAR].get_center()) * 0.5;
Plane far_plane = Plane(Vector3(0.0, 0.0, -1.0), far_center);

/////////////////////////////////////////////////////////////////////////////
// Figure out our top/bottom planes

// 4. Intersect far and left planes with top planes from both eyes, save the point with highest y as top_left.
Vector3 top_left, other;
ERR_FAIL_COND_V_MSG(
!far_plane.intersect_3(planes[0][Projection::PLANE_LEFT], planes[0][Projection::PLANE_TOP], &top_left), proj, "Can't determine left camera far/left/top vector");
ERR_FAIL_COND_V_MSG(
!far_plane.intersect_3(planes[1][Projection::PLANE_LEFT], planes[1][Projection::PLANE_TOP], &other), proj, "Can't determine right camera far/left/top vector");
if (top_left.y < other.y) {
top_left = other;
}

// 5. Intersect far and left planes with bottom planes from both eyes, save the point with lowest y as bottom_left.
Vector3 bottom_left;
ERR_FAIL_COND_V_MSG(
!far_plane.intersect_3(planes[0][Projection::PLANE_LEFT], planes[0][Projection::PLANE_BOTTOM], &bottom_left), proj, "Can't determine left camera far/left/bottom vector");
ERR_FAIL_COND_V_MSG(
!far_plane.intersect_3(planes[1][Projection::PLANE_LEFT], planes[1][Projection::PLANE_BOTTOM], &other), proj, "Can't determine right camera far/left/bottom vector");
if (other.y < bottom_left.y) {
bottom_left = other;
}

// 6. Intersect far and right planes with top planes from both eyes, save the point with highest y as top_right.
Vector3 top_right;
ERR_FAIL_COND_V_MSG(
!far_plane.intersect_3(planes[0][Projection::PLANE_RIGHT], planes[0][Projection::PLANE_TOP], &top_right), proj, "Can't determine left camera far/right/top vector");
ERR_FAIL_COND_V_MSG(
!far_plane.intersect_3(planes[1][Projection::PLANE_RIGHT], planes[1][Projection::PLANE_TOP], &other), proj, "Can't determine right camera far/right/top vector");
if (top_right.y < other.y) {
top_right = other;
}

// 7. Intersect far and right planes with bottom planes from both eyes, save the point with lowest y as bottom_right.
Vector3 bottom_right;
ERR_FAIL_COND_V_MSG(
!far_plane.intersect_3(planes[0][Projection::PLANE_RIGHT], planes[0][Projection::PLANE_BOTTOM], &bottom_right), proj, "Can't determine left camera far/right/bottom vector");
ERR_FAIL_COND_V_MSG(
!far_plane.intersect_3(planes[1][Projection::PLANE_RIGHT], planes[1][Projection::PLANE_BOTTOM], &other), proj, "Can't determine right camera far/right/bottom vector");
if (other.y < bottom_right.y) {
bottom_right = other;
}

// 8. Create top plane with these points: camera origin, top_left, top_right
Plane top(origin, top_left, top_right);

// 9. Create bottom plane with these points: camera origin, bottom_left, bottom_right
Plane bottom(origin, bottom_left, bottom_right);

/////////////////////////////////////////////////////////////////////////////
// Figure out our near plane points

// 10. Intersect near plane with bottm/left planes, to obtain min_vec then top/right to obtain max_vec
Vector3 min_vec;
ERR_FAIL_COND_V_MSG(
!near_plane.intersect_3(bottom, planes[0][Projection::PLANE_LEFT], &min_vec), proj, "Can't determine left camera near/left/bottom vector");
ERR_FAIL_COND_V_MSG(
!near_plane.intersect_3(bottom, planes[1][Projection::PLANE_LEFT], &other), proj, "Can't determine right camera near/left/bottom vector");
if (other.x < min_vec.x) {
min_vec = other;
}

Vector3 max_vec;
ERR_FAIL_COND_V_MSG(
!near_plane.intersect_3(top, planes[0][Projection::PLANE_RIGHT], &max_vec), proj, "Can't determine left camera near/right/top vector");
ERR_FAIL_COND_V_MSG(
!near_plane.intersect_3(top, planes[1][Projection::PLANE_RIGHT], &other), proj, "Can't determine right camera near/right/top vector");
if (max_vec.x < other.x) {
max_vec = other;
}

// 11. get x and y from these to obtain left, top, right bottom for the frustum. Get the distance from near plane to camera origin to obtain near, and the distance from the far plane to the camera origin to obtain far.
float z_near = -near_plane.distance_to(origin);
float z_far = -far_plane.distance_to(origin);

// Safeguard our near and far values
z_near = MAX(z_near, origin.z + 0.01);
z_far = MAX(z_far, z_near + 1.0);

// 12. Use this to build the combined camera matrix.
proj.set_frustum(min_vec.x, max_vec.x, min_vec.y, max_vec.y, z_near, z_far);

// 13. Add in offset to origin
Transform3D main_offset(Basis(), -origin);
proj = proj * Projection(main_offset);

return proj;
}

Projection Projection::perspective_znear_adjusted(real_t p_new_znear) const {
Projection proj = *this;
proj.adjust_perspective_znear(p_new_znear);
Expand Down Expand Up @@ -876,6 +988,17 @@ bool Projection::is_orthogonal() const {
return columns[2][3] == 0.0;
}

bool Projection::is_asymmetrical() const {
// NOTE: This assumes that the matrix is a projection across z-axis
// i.e. is invertible and columns[0][1], [0][3], [1][0] and [1][3] == 0
return columns[2][0] != 0.0 || columns[2][1] != 0.0;
}

bool Projection::is_z_axis_projection() const {
// These need to all be zero for this to be a projection along the z-axis.
return columns[0][1] == 0.0 && columns[1][0] == 0.0 && columns[0][3] == 0.0 && columns[1][3] == 0.0;
}

real_t Projection::get_fov() const {
// NOTE: This assumes a rectangular projection plane, i.e. that :
// - the matrix is a projection across z-axis (i.e. is invertible and columns[0][1], [0][3], [1][0] and [1][3] == 0)
Expand Down
3 changes: 3 additions & 0 deletions core/math/projection.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ struct [[nodiscard]] Projection {
static Projection create_frustum(real_t p_left, real_t p_right, real_t p_bottom, real_t p_top, real_t p_near, real_t p_far);
static Projection create_frustum_aspect(real_t p_size, real_t p_aspect, Vector2 p_offset, real_t p_near, real_t p_far, bool p_flip_fov = false);
static Projection create_fit_aabb(const AABB &p_aabb);
static Projection create_combined_projection(const Transform3D &p_center, const Projection &p_projection_a, const Transform3D &p_offset_a, const Projection &p_projection_b, const Transform3D &p_offset_b);
Projection perspective_znear_adjusted(real_t p_new_znear) const;
Plane get_projection_plane(Planes p_plane) const;
Projection flipped_y() const;
Expand All @@ -109,6 +110,8 @@ struct [[nodiscard]] Projection {
real_t get_aspect() const;
real_t get_fov() const;
bool is_orthogonal() const;
bool is_asymmetrical() const;
bool is_z_axis_projection() const;

Vector<Plane> get_projection_planes(const Transform3D &p_transform) const;

Expand Down
2 changes: 2 additions & 0 deletions core/variant/variant_call.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2625,6 +2625,8 @@ static void _register_variant_builtin_methods_misc() {
bind_method(Projection, get_aspect, sarray(), varray());
bind_method(Projection, get_fov, sarray(), varray());
bind_method(Projection, is_orthogonal, sarray(), varray());
bind_method(Projection, is_asymmetrical, sarray(), varray());
bind_method(Projection, is_z_axis_projection, sarray(), varray());

bind_method(Projection, get_viewport_half_extents, sarray(), varray());
bind_method(Projection, get_far_plane_half_extents, sarray(), varray());
Expand Down
3 changes: 3 additions & 0 deletions doc/classes/ProjectSettings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3540,6 +3540,9 @@
<member name="xr/openxr/binding_modifiers/dpad_binding" type="bool" setter="" getter="" default="false">
If [code]true[/code], enables the D-pad binding modifier if supported by the XR runtime.
</member>
<member name="xr/openxr/create_default_foveated_inset_viewport" type="bool" setter="" getter="" default="true">
If [code]true[/code], and [member xr/openxr/view_configuration] is set to [code]Stereo with Foveated Inset[/code], and foveated inset is supported, Godot will add and configure an [OpenXRFoveatedInsetViewport] node for rendering the foveated inset.
</member>
<member name="xr/openxr/default_action_map" type="String" setter="" getter="" default="&quot;res://openxr_action_map.tres&quot;">
Action map configuration to load by default.
</member>
Expand Down
12 changes: 12 additions & 0 deletions doc/classes/Projection.xml
Original file line number Diff line number Diff line change
Expand Up @@ -243,12 +243,24 @@
Returns a [Projection] that performs the inverse of this [Projection]'s projective transformation.
</description>
</method>
<method name="is_asymmetrical" qualifiers="const">
<return type="bool" />
<description>
Returns [code]true[/code] if this [Projection] is an asymmetrical projection.
</description>
</method>
<method name="is_orthogonal" qualifiers="const">
<return type="bool" />
<description>
Returns [code]true[/code] if this [Projection] performs an orthogonal projection.
</description>
</method>
<method name="is_z_axis_projection" qualifiers="const">
<return type="bool" />
<description>
Returns [code]true[/code] if this [Projection] is z-axis aligned.
</description>
</method>
<method name="jitter_offseted" qualifiers="const">
<return type="Projection" />
<param index="0" name="offset" type="Vector2" />
Expand Down
9 changes: 9 additions & 0 deletions doc/classes/RenderingServer.xml
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,15 @@
Sets camera to use perspective projection. Objects on the screen becomes smaller when they are far away.
</description>
</method>
<method name="camera_set_projections">
<return type="void" />
<param index="0" name="camera" type="RID" />
<param index="1" name="projections" type="Projection[]" />
<param index="2" name="offsets" type="Transform3D[]" default="[]" />
<description>
Sets camera to use any set of [param projections]. You can specify one projection for each view set on the viewport. If the views are offset (e.g. ocular distance) and/or skewed, this data can be provided through [param offsets].
</description>
</method>
<method name="camera_set_transform">
<return type="void" />
<param index="0" name="camera" type="RID" />
Expand Down
5 changes: 4 additions & 1 deletion doc/classes/XRCamera3D.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@
<description>
A camera node which automatically positions itself based on XR tracking data.
In contrast to [XRController3D], the render thread has access to more up-to-date tracking data, and the location of the [XRCamera3D] node can lag a few milliseconds behind what is used for rendering.
[b]Note:[/b] If [member Viewport.use_xr] is [code]true[/code], most of the camera properties are overridden by the active [XRInterface]. The only properties that can be trusted are the near and far planes.
</description>
<tutorials>
<link title="XR documentation index">$DOCS_URL/tutorials/xr/index.html</link>
</tutorials>
<members>
<member name="physics_interpolation_mode" type="int" setter="set_physics_interpolation_mode" getter="get_physics_interpolation_mode" overrides="Node" enum="Node.PhysicsInterpolationMode" default="2" />
<member name="tracker" type="StringName" setter="set_tracker" getter="get_tracker" default="&amp;&quot;head&quot;">
The name of the camera tracker we're bound to. Which trackers are available is not known during design time.
The default tracker refers to the HMD position of the main player. Consult the documentation of the [XRInterface] for any additional trackers. There may be additional tracked headsets for multiplayer systems or a tracker may be available for a physical camera in a mixed reality scenario.
</member>
</members>
</class>
21 changes: 19 additions & 2 deletions doc/classes/XRInterface.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,23 @@
If this is an AR interface that requires displaying a camera feed as the background, this method returns the feed ID in the [CameraServer] for this interface.
</description>
</method>
<method name="get_camera_offsets">
<return type="Transform3D[]" />
<param index="0" name="tracker_name" type="StringName" />
<description>
Gets an array of offset transforms for each view for tracker [param tracker_name]. [param tracker_name] must refer to an [XRPositionalTracker] used as a camera. Returns an empty array if this interface is not responsible for this tracker.
</description>
</method>
<method name="get_camera_projections">
<return type="Projection[]" />
<param index="0" name="tracker_name" type="StringName" />
<param index="1" name="aspect" type="float" />
<param index="2" name="near" type="float" />
<param index="3" name="far" type="float" />
<description>
Gets an array of projection matrices for each view for tracker [param tracker_name]. [param tracker_name] must refer to an [XRPositionalTracker] used as a camera. Returns an empty array if this interface is not responsible for this tracker.
</description>
</method>
<method name="get_capabilities" qualifiers="const">
<return type="int" />
<description>
Expand All @@ -35,7 +52,7 @@
Returns an array of vectors that represent the physical play area mapped to the virtual space around the [XROrigin3D] point. The points form a convex polygon that can be used to react to or visualize the play area. This returns an empty array if this feature is not supported or if the information is not yet available.
</description>
</method>
<method name="get_projection_for_view">
<method name="get_projection_for_view" deprecated="Use get_camera_projections">
<return type="Projection" />
<param index="0" name="view" type="int" />
<param index="1" name="aspect" type="float" />
Expand Down Expand Up @@ -70,7 +87,7 @@
If supported, returns the status of our tracking. This will allow you to provide feedback to the user whether there are issues with positional tracking.
</description>
</method>
<method name="get_transform_for_view">
<method name="get_transform_for_view" deprecated="Use get_camera_offsets">
<return type="Transform3D" />
<param index="0" name="view" type="int" />
<param index="1" name="cam_transform" type="Transform3D" />
Expand Down
21 changes: 19 additions & 2 deletions doc/classes/XRInterfaceExtension.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,23 @@
Returns the camera feed ID for the [CameraFeed] registered with the [CameraServer] that should be presented as the background on an AR capable device (if applicable).
</description>
</method>
<method name="_get_camera_offsets" qualifiers="virtual">
<return type="Transform3D[]" />
<param index="0" name="tracker_name" type="StringName" />
<description>
Gets an array of offset transforms for each view for tracker [param tracker_name]. [param tracker_name] must refer to an [XRPositionalTracker] used as a camera. Returns an empty array if this interface is not responsible for this tracker.
</description>
</method>
<method name="_get_camera_projections" qualifiers="virtual">
<return type="Projection[]" />
<param index="0" name="tracker_name" type="StringName" />
<param index="1" name="aspect" type="float" />
<param index="2" name="z_near" type="float" />
<param index="3" name="z_far" type="float" />
<description>
Gets an array of projection matrices for each view for tracker [param tracker_name]. [param tracker_name] must refer to an [XRPositionalTracker] used as a camera. Returns an empty array if this interface is not responsible for this tracker.
</description>
</method>
<method name="_get_camera_transform" qualifiers="virtual">
<return type="Transform3D" />
<description>
Expand Down Expand Up @@ -70,7 +87,7 @@
Returns the play area mode that sets up our play area.
</description>
</method>
<method name="_get_projection_for_view" qualifiers="virtual">
<method name="_get_projection_for_view" qualifiers="virtual" deprecated="Implement _get_camera_projections">
<return type="PackedFloat64Array" />
<param index="0" name="view" type="int" />
<param index="1" name="aspect" type="float" />
Expand Down Expand Up @@ -111,7 +128,7 @@
Returns the current status of our tracking.
</description>
</method>
<method name="_get_transform_for_view" qualifiers="virtual">
<method name="_get_transform_for_view" qualifiers="virtual" deprecated="Implement _get_camera_offsets">
<return type="Transform3D" />
<param index="0" name="view" type="int" />
<param index="1" name="cam_transform" type="Transform3D" />
Expand Down
26 changes: 24 additions & 2 deletions doc/classes/XRServer.xml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,25 @@
Finds an interface by its [param name]. For example, if your project uses capabilities of an AR/VR platform, you can find the interface for that platform by name and initialize it.
</description>
</method>
<method name="get_camera_offsets">
<return type="Transform3D[]" />
<param index="0" name="tracker_name" type="StringName" />
<description>
Gets an array of offset transforms for each view for tracker [param tracker_name]. [param tracker_name] must refer to an [XRPositionalTracker] used as a camera.
Will check our primary interface first, then check each active interface. Returns empty if no interfaces manage this tracker.
</description>
</method>
<method name="get_camera_projections">
<return type="Projection[]" />
<param index="0" name="tracker_name" type="StringName" />
<param index="1" name="aspect" type="float" />
<param index="2" name="near" type="float" />
<param index="3" name="far" type="float" />
<description>
Gets an array of projection matrices for each view for tracker [param tracker_name]. [param tracker_name] must refer to an [XRPositionalTracker] used as a camera.
Will check our primary interface first, then check each active interface. Returns empty if no interfaces manage this tracker.
</description>
</method>
<method name="get_hmd_transform">
<return type="Transform3D" />
<description>
Expand Down Expand Up @@ -172,8 +191,8 @@
</signal>
</signals>
<constants>
<constant name="TRACKER_HEAD" value="1" enum="TrackerType">
The tracker tracks the location of the player's head. This is usually a location centered between the player's eyes. Note that for handheld AR devices this can be the current location of the device.
<constant name="TRACKER_CAMERA" value="1" enum="TrackerType">
The tracker tracks the position of an XR camera (HMD, external camera, phone camera for phone based AR).
</constant>
<constant name="TRACKER_CONTROLLER" value="2" enum="TrackerType">
The tracker tracks the location of a controller.
Expand Down Expand Up @@ -202,6 +221,9 @@
<constant name="TRACKER_ANY" value="255" enum="TrackerType">
Used internally to select all trackers.
</constant>
<constant name="TRACKER_HEAD" value="1" enum="TrackerType" deprecated="Use TRACKER_CAMERA">
The tracker tracks the location of the player's head. This is usually a location centered between the player's eyes. Note that for handheld AR devices this can be the current location of the device.
</constant>
<constant name="RESET_FULL_ROTATION" value="0" enum="RotationMode">
Fully reset the orientation of the HMD. Regardless of what direction the user is looking to in the real world. The user will look dead ahead in the virtual world.
</constant>
Expand Down
Loading