aboutsummaryrefslogtreecommitdiffstats
path: root/components/shared/webxr/hittest.rs
diff options
context:
space:
mode:
Diffstat (limited to 'components/shared/webxr/hittest.rs')
-rw-r--r--components/shared/webxr/hittest.rs179
1 files changed, 179 insertions, 0 deletions
diff --git a/components/shared/webxr/hittest.rs b/components/shared/webxr/hittest.rs
new file mode 100644
index 00000000000..3e56ff8c357
--- /dev/null
+++ b/components/shared/webxr/hittest.rs
@@ -0,0 +1,179 @@
+use crate::ApiSpace;
+use crate::Native;
+use crate::Space;
+use euclid::Point3D;
+use euclid::RigidTransform3D;
+use euclid::Rotation3D;
+use euclid::Vector3D;
+use std::f32::EPSILON;
+use std::iter::FromIterator;
+
+#[derive(Clone, Copy, Debug)]
+#[cfg_attr(feature = "ipc", derive(serde::Serialize, serde::Deserialize))]
+/// https://immersive-web.github.io/hit-test/#xrray
+pub struct Ray<Space> {
+ /// The origin of the ray
+ pub origin: Vector3D<f32, Space>,
+ /// The direction of the ray. Must be normalized.
+ pub direction: Vector3D<f32, Space>,
+}
+
+#[derive(Clone, Copy, Debug)]
+#[cfg_attr(feature = "ipc", derive(serde::Serialize, serde::Deserialize))]
+/// https://immersive-web.github.io/hit-test/#enumdef-xrhittesttrackabletype
+pub enum EntityType {
+ Point,
+ Plane,
+ Mesh,
+}
+
+#[derive(Copy, Clone, Debug)]
+#[cfg_attr(feature = "ipc", derive(serde::Serialize, serde::Deserialize))]
+/// https://immersive-web.github.io/hit-test/#dictdef-xrhittestoptionsinit
+pub struct HitTestSource {
+ pub id: HitTestId,
+ pub space: Space,
+ pub ray: Ray<ApiSpace>,
+ pub types: EntityTypes,
+}
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
+#[cfg_attr(feature = "ipc", derive(serde::Serialize, serde::Deserialize))]
+pub struct HitTestId(pub u32);
+
+#[derive(Copy, Clone, Debug, Default)]
+#[cfg_attr(feature = "ipc", derive(serde::Serialize, serde::Deserialize))]
+/// Vec<EntityType>, but better
+pub struct EntityTypes {
+ pub point: bool,
+ pub plane: bool,
+ pub mesh: bool,
+}
+
+#[derive(Copy, Clone, Debug)]
+#[cfg_attr(feature = "ipc", derive(serde::Serialize, serde::Deserialize))]
+pub struct HitTestResult {
+ pub id: HitTestId,
+ pub space: RigidTransform3D<f32, HitTestSpace, Native>,
+}
+
+#[derive(Clone, Copy, Debug)]
+#[cfg_attr(feature = "ipc", derive(serde::Serialize, serde::Deserialize))]
+/// The coordinate space of a hit test result
+pub struct HitTestSpace;
+
+#[derive(Copy, Clone, Debug)]
+#[cfg_attr(feature = "ipc", derive(serde::Serialize, serde::Deserialize))]
+pub struct Triangle {
+ pub first: Point3D<f32, Native>,
+ pub second: Point3D<f32, Native>,
+ pub third: Point3D<f32, Native>,
+}
+
+impl EntityTypes {
+ pub fn is_type(self, ty: EntityType) -> bool {
+ match ty {
+ EntityType::Point => self.point,
+ EntityType::Plane => self.plane,
+ EntityType::Mesh => self.mesh,
+ }
+ }
+
+ pub fn add_type(&mut self, ty: EntityType) {
+ match ty {
+ EntityType::Point => self.point = true,
+ EntityType::Plane => self.plane = true,
+ EntityType::Mesh => self.mesh = true,
+ }
+ }
+}
+
+impl FromIterator<EntityType> for EntityTypes {
+ fn from_iter<T>(iter: T) -> Self
+ where
+ T: IntoIterator<Item = EntityType>,
+ {
+ iter.into_iter().fold(Default::default(), |mut acc, e| {
+ acc.add_type(e);
+ acc
+ })
+ }
+}
+
+impl Triangle {
+ /// https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm
+ pub fn intersect(
+ self,
+ ray: Ray<Native>,
+ ) -> Option<RigidTransform3D<f32, HitTestSpace, Native>> {
+ let Triangle {
+ first: v0,
+ second: v1,
+ third: v2,
+ } = self;
+
+ let edge1 = v1 - v0;
+ let edge2 = v2 - v0;
+
+ let h = ray.direction.cross(edge2);
+ let a = edge1.dot(h);
+ if a > -EPSILON && a < EPSILON {
+ // ray is parallel to triangle
+ return None;
+ }
+
+ let f = 1. / a;
+
+ let s = ray.origin - v0.to_vector();
+
+ // barycentric coordinate of intersection point u
+ let u = f * s.dot(h);
+ // barycentric coordinates have range (0, 1)
+ if u < 0. || u > 1. {
+ // the intersection is outside the triangle
+ return None;
+ }
+
+ let q = s.cross(edge1);
+ // barycentric coordinate of intersection point v
+ let v = f * ray.direction.dot(q);
+
+ // barycentric coordinates have range (0, 1)
+ // and their sum must not be greater than 1
+ if v < 0. || u + v > 1. {
+ // the intersection is outside the triangle
+ return None;
+ }
+
+ let t = f * edge2.dot(q);
+
+ if t > EPSILON {
+ let origin = ray.origin + ray.direction * t;
+
+ // this is not part of the Möller-Trumbore algorithm, the hit test spec
+ // requires it has an orientation such that the Y axis points along
+ // the triangle normal
+ let normal = edge1.cross(edge2).normalize();
+ let y = Vector3D::new(0., 1., 0.);
+ let dot = normal.dot(y);
+ let rotation = if dot > -EPSILON && dot < EPSILON {
+ // vectors are parallel, return the vector itself
+ // XXXManishearth it's possible for the vectors to be
+ // antiparallel, unclear if normals need to be flipped
+ Rotation3D::identity()
+ } else {
+ let axis = normal.cross(y);
+ let cos = normal.dot(y);
+ // This is Rotation3D::around_axis(axis.normalize(), theta), however
+ // that is just Rotation3D::quaternion(axis.normalize().xyz * sin, cos),
+ // which is Rotation3D::quaternion(cross, dot)
+ Rotation3D::quaternion(axis.x, axis.y, axis.z, cos)
+ };
+
+ return Some(RigidTransform3D::new(rotation, origin));
+ }
+
+ // triangle is behind ray
+ None
+ }
+}