diff --git a/modules/openxr/doc_classes/OpenXRCompositionLayer.xml b/modules/openxr/doc_classes/OpenXRCompositionLayer.xml index 66baaf096d68..168e0bf077f2 100644 --- a/modules/openxr/doc_classes/OpenXRCompositionLayer.xml +++ b/modules/openxr/doc_classes/OpenXRCompositionLayer.xml @@ -10,6 +10,15 @@ + + + + + + Returns UV coordinates where the given ray intersects with the composition layer. [param origin] and [param direction] must be in global space. + Returns [code]Vector2(-1.0, -1.0)[/code] if the ray doesn't intersect. + + diff --git a/modules/openxr/scene/openxr_composition_layer.cpp b/modules/openxr/scene/openxr_composition_layer.cpp index 120914485f20..2d8b95cae85e 100644 --- a/modules/openxr/scene/openxr_composition_layer.cpp +++ b/modules/openxr/scene/openxr_composition_layer.cpp @@ -88,6 +88,8 @@ void OpenXRCompositionLayer::_bind_methods() { ClassDB::bind_method(D_METHOD("is_natively_supported"), &OpenXRCompositionLayer::is_natively_supported); + ClassDB::bind_method(D_METHOD("intersects_ray", "origin", "direction"), &OpenXRCompositionLayer::intersects_ray); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "layer_viewport", PROPERTY_HINT_NODE_TYPE, "SubViewport"), "set_layer_viewport", "get_layer_viewport"); ADD_PROPERTY(PropertyInfo(Variant::INT, "sort_order", PROPERTY_HINT_NONE, ""), "set_sort_order", "get_sort_order"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "alpha_blend", PROPERTY_HINT_NONE, ""), "set_alpha_blend", "get_alpha_blend"); @@ -199,6 +201,10 @@ bool OpenXRCompositionLayer::is_natively_supported() const { return false; } +Vector2 OpenXRCompositionLayer::intersects_ray(const Vector3 &p_origin, const Vector3 &p_direction) const { + return Vector2(-1.0, -1.0); +} + void OpenXRCompositionLayer::_reset_fallback_material() { ERR_FAIL_NULL(fallback); diff --git a/modules/openxr/scene/openxr_composition_layer.h b/modules/openxr/scene/openxr_composition_layer.h index f683aea647d4..b997d0da149b 100644 --- a/modules/openxr/scene/openxr_composition_layer.h +++ b/modules/openxr/scene/openxr_composition_layer.h @@ -84,6 +84,8 @@ public: virtual PackedStringArray get_configuration_warnings() const override; + virtual Vector2 intersects_ray(const Vector3 &p_origin, const Vector3 &p_direction) const; + OpenXRCompositionLayer(); ~OpenXRCompositionLayer(); }; diff --git a/modules/openxr/scene/openxr_composition_layer_cylinder.cpp b/modules/openxr/scene/openxr_composition_layer_cylinder.cpp index dcc9971ec964..ae5a8da5f312 100644 --- a/modules/openxr/scene/openxr_composition_layer_cylinder.cpp +++ b/modules/openxr/scene/openxr_composition_layer_cylinder.cpp @@ -188,3 +188,48 @@ void OpenXRCompositionLayerCylinder::set_fallback_segments(uint32_t p_fallback_s uint32_t OpenXRCompositionLayerCylinder::get_fallback_segments() const { return fallback_segments; } + +Vector2 OpenXRCompositionLayerCylinder::intersects_ray(const Vector3 &p_origin, const Vector3 &p_direction) const { + Transform3D cylinder_transform = get_global_transform(); + Vector3 cylinder_axis = cylinder_transform.basis.get_column(1); + + Vector3 offset = p_origin - cylinder_transform.origin; + float a = p_direction.dot(p_direction - cylinder_axis * p_direction.dot(cylinder_axis)); + float b = 2.0 * (p_direction.dot(offset - cylinder_axis * offset.dot(cylinder_axis))); + float c = offset.dot(offset - cylinder_axis * offset.dot(cylinder_axis)) - (radius * radius); + + float discriminant = b * b - 4.0 * a * c; + if (discriminant < 0.0) { + return Vector2(-1.0, -1.0); + } + + float t0 = (-b - Math::sqrt(discriminant)) / (2.0 * a); + float t1 = (-b + Math::sqrt(discriminant)) / (2.0 * a); + float t = MAX(t0, t1); + + if (t < 0.0) { + return Vector2(-1.0, -1.0); + } + Vector3 intersection = p_origin + p_direction * t; + + Basis correction = cylinder_transform.basis.inverse(); + correction.rotate(Vector3(0.0, 1.0, 0.0), -Math_PI / 2.0); + Vector3 relative_point = correction.xform(intersection - cylinder_transform.origin); + + Vector2 projected_point = Vector2(relative_point.x, relative_point.z); + float intersection_angle = Math::atan2(projected_point.y, projected_point.x); + if (Math::abs(intersection_angle) > central_angle / 2.0) { + return Vector2(-1.0, -1.0); + } + + float arc_length = radius * central_angle; + float height = aspect_ratio * arc_length; + if (Math::abs(relative_point.y) > height / 2.0) { + return Vector2(-1.0, -1.0); + } + + float u = 0.5 + (intersection_angle / central_angle); + float v = 1.0 - (0.5 + (relative_point.y / height)); + + return Vector2(u, v); +} diff --git a/modules/openxr/scene/openxr_composition_layer_cylinder.h b/modules/openxr/scene/openxr_composition_layer_cylinder.h index dec7dac529ca..aed0fabd783d 100644 --- a/modules/openxr/scene/openxr_composition_layer_cylinder.h +++ b/modules/openxr/scene/openxr_composition_layer_cylinder.h @@ -66,6 +66,8 @@ public: void set_fallback_segments(uint32_t p_fallback_segments); uint32_t get_fallback_segments() const; + virtual Vector2 intersects_ray(const Vector3 &p_origin, const Vector3 &p_direction) const override; + OpenXRCompositionLayerCylinder(); ~OpenXRCompositionLayerCylinder(); }; diff --git a/modules/openxr/scene/openxr_composition_layer_equirect.cpp b/modules/openxr/scene/openxr_composition_layer_equirect.cpp index c911dbf6aee0..d67e71443c98 100644 --- a/modules/openxr/scene/openxr_composition_layer_equirect.cpp +++ b/modules/openxr/scene/openxr_composition_layer_equirect.cpp @@ -207,3 +207,54 @@ void OpenXRCompositionLayerEquirect::set_fallback_segments(uint32_t p_fallback_s uint32_t OpenXRCompositionLayerEquirect::get_fallback_segments() const { return fallback_segments; } + +Vector2 OpenXRCompositionLayerEquirect::intersects_ray(const Vector3 &p_origin, const Vector3 &p_direction) const { + Transform3D equirect_transform = get_global_transform(); + + Vector3 offset = p_origin - equirect_transform.origin; + float a = p_direction.dot(p_direction); + float b = 2.0 * offset.dot(p_direction); + float c = offset.dot(offset) - (radius * radius); + + float discriminant = b * b - 4.0 * a * c; + if (discriminant < 0.0) { + return Vector2(-1.0, -1.0); + } + + float t0 = (-b - Math::sqrt(discriminant)) / (2.0 * a); + float t1 = (-b + Math::sqrt(discriminant)) / (2.0 * a); + float t = MAX(t0, t1); + + if (t < 0.0) { + return Vector2(-1.0, -1.0); + } + Vector3 intersection = p_origin + p_direction * t; + + Basis correction = equirect_transform.basis.inverse(); + correction.rotate(Vector3(0.0, 1.0, 0.0), -Math_PI / 2.0); + Vector3 relative_point = correction.xform(intersection - equirect_transform.origin); + + float horizontal_intersection_angle = Math::atan2(relative_point.z, relative_point.x); + if (Math::abs(horizontal_intersection_angle) > central_horizontal_angle / 2.0) { + return Vector2(-1.0, -1.0); + } + + float vertical_intersection_angle = Math::acos(relative_point.y / radius) - (Math_PI / 2.0); + if (vertical_intersection_angle < 0) { + if (Math::abs(vertical_intersection_angle) > upper_vertical_angle) { + return Vector2(-1.0, -1.0); + } + } else if (vertical_intersection_angle > lower_vertical_angle) { + return Vector2(-1.0, -1.0); + } + + // Re-center the intersection angle if the vertical angle is uneven between upper and lower. + if (upper_vertical_angle != lower_vertical_angle) { + vertical_intersection_angle -= (-upper_vertical_angle + lower_vertical_angle) / 2.0; + } + + float u = 0.5 + (horizontal_intersection_angle / central_horizontal_angle); + float v = 0.5 + (vertical_intersection_angle / (upper_vertical_angle + lower_vertical_angle)); + + return Vector2(u, v); +} diff --git a/modules/openxr/scene/openxr_composition_layer_equirect.h b/modules/openxr/scene/openxr_composition_layer_equirect.h index b74e131f35d1..7a002a48dc2a 100644 --- a/modules/openxr/scene/openxr_composition_layer_equirect.h +++ b/modules/openxr/scene/openxr_composition_layer_equirect.h @@ -70,6 +70,8 @@ public: void set_fallback_segments(uint32_t p_fallback_segments); uint32_t get_fallback_segments() const; + virtual Vector2 intersects_ray(const Vector3 &p_origin, const Vector3 &p_direction) const override; + OpenXRCompositionLayerEquirect(); ~OpenXRCompositionLayerEquirect(); }; diff --git a/modules/openxr/scene/openxr_composition_layer_quad.cpp b/modules/openxr/scene/openxr_composition_layer_quad.cpp index 342df0580827..17d57851e44f 100644 --- a/modules/openxr/scene/openxr_composition_layer_quad.cpp +++ b/modules/openxr/scene/openxr_composition_layer_quad.cpp @@ -96,3 +96,36 @@ void OpenXRCompositionLayerQuad::set_quad_size(const Size2 &p_size) { Size2 OpenXRCompositionLayerQuad::get_quad_size() const { return quad_size; } + +Vector2 OpenXRCompositionLayerQuad::intersects_ray(const Vector3 &p_origin, const Vector3 &p_direction) const { + Transform3D quad_transform = get_global_transform(); + Vector3 quad_normal = quad_transform.basis.get_column(2); + + float denom = quad_normal.dot(p_direction); + if (Math::abs(denom) > 0.0001) { + Vector3 vector = quad_transform.origin - p_origin; + float t = vector.dot(quad_normal) / denom; + if (t < 0.0) { + return Vector2(-1.0, -1.0); + } + Vector3 intersection = p_origin + p_direction * t; + + Vector3 relative_point = intersection - quad_transform.origin; + Vector2 projected_point = Vector2( + relative_point.dot(quad_transform.basis.get_column(0)), + relative_point.dot(quad_transform.basis.get_column(1))); + if (Math::abs(projected_point.x) > quad_size.x / 2.0) { + return Vector2(-1.0, -1.0); + } + if (Math::abs(projected_point.y) > quad_size.y / 2.0) { + return Vector2(-1.0, -1.0); + } + + float u = 0.5 + (projected_point.x / quad_size.x); + float v = 1.0 - (0.5 + (projected_point.y / quad_size.y)); + + return Vector2(u, v); + } + + return Vector2(-1.0, -1.0); +} diff --git a/modules/openxr/scene/openxr_composition_layer_quad.h b/modules/openxr/scene/openxr_composition_layer_quad.h index e1141586c136..d88b596984bd 100644 --- a/modules/openxr/scene/openxr_composition_layer_quad.h +++ b/modules/openxr/scene/openxr_composition_layer_quad.h @@ -54,6 +54,8 @@ public: void set_quad_size(const Size2 &p_size); Size2 get_quad_size() const; + virtual Vector2 intersects_ray(const Vector3 &p_origin, const Vector3 &p_direction) const override; + OpenXRCompositionLayerQuad(); ~OpenXRCompositionLayerQuad(); };