1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
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
    }
}