1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "third_party/blink/renderer/modules/xr/xr_ray.h"
6 
7 #include <algorithm>
8 #include <cmath>
9 #include <utility>
10 
11 #include "third_party/blink/renderer/bindings/core/v8/v8_dom_point_init.h"
12 #include "third_party/blink/renderer/bindings/modules/v8/v8_xr_ray_direction_init.h"
13 #include "third_party/blink/renderer/core/geometry/dom_point_read_only.h"
14 #include "third_party/blink/renderer/modules/xr/xr_rigid_transform.h"
15 #include "third_party/blink/renderer/modules/xr/xr_utils.h"
16 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
17 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
18 #include "ui/gfx/geometry/quaternion.h"
19 #include "ui/gfx/geometry/vector3d_f.h"
20 
21 namespace {
22 
23 constexpr char kInvalidWComponentInOrigin[] =
24     "Origin's `w` component must be set to 1.0f!";
25 constexpr char kInvalidWComponentInDirection[] =
26     "Direction's `w` component must be set to 0.0f!";
27 
28 }  // namespace
29 
30 namespace blink {
31 
XRRay()32 XRRay::XRRay() {
33   origin_ = DOMPointReadOnly::Create(0.0, 0.0, 0.0, 1.0);
34   direction_ = DOMPointReadOnly::Create(0.0, 0.0, -1.0, 0.0);
35 }
36 
XRRay(XRRigidTransform * transform,ExceptionState & exception_state)37 XRRay::XRRay(XRRigidTransform* transform, ExceptionState& exception_state) {
38   DOMFloat32Array* m = transform->matrix();
39   Set(DOMFloat32ArrayToTransformationMatrix(m), exception_state);
40 }
41 
XRRay(DOMPointInit * origin,XRRayDirectionInit * direction,ExceptionState & exception_state)42 XRRay::XRRay(DOMPointInit* origin,
43              XRRayDirectionInit* direction,
44              ExceptionState& exception_state) {
45   DCHECK(origin);
46   DCHECK(direction);
47 
48   FloatPoint3D o(origin->x(), origin->y(), origin->z());
49   FloatPoint3D d(direction->x(), direction->y(), direction->z());
50 
51   if (d.length() == 0.0f) {
52     exception_state.ThrowTypeError(kUnableToNormalizeZeroLength);
53     return;
54   }
55 
56   if (direction->w() != 0.0f) {
57     exception_state.ThrowTypeError(kInvalidWComponentInDirection);
58     return;
59   }
60 
61   if (origin->w() != 1.0f) {
62     exception_state.ThrowTypeError(kInvalidWComponentInOrigin);
63     return;
64   }
65 
66   Set(o, d, exception_state);
67 }
68 
Set(const TransformationMatrix & matrix,ExceptionState & exception_state)69 void XRRay::Set(const TransformationMatrix& matrix,
70                 ExceptionState& exception_state) {
71   FloatPoint3D origin = matrix.MapPoint(FloatPoint3D(0, 0, 0));
72   FloatPoint3D direction = matrix.MapPoint(FloatPoint3D(0, 0, -1));
73   direction.Move(-origin.X(), -origin.Y(), -origin.Z());
74 
75   Set(origin, direction, exception_state);
76 }
77 
78 // Sets member variables from passed in |origin| and |direction|.
79 // All constructors with the exception of default constructor eventually invoke
80 // this method.
81 // If the |direction|'s length is 0, this method will initialize direction to
82 // default vector (0, 0, -1).
Set(FloatPoint3D origin,FloatPoint3D direction,ExceptionState & exception_state)83 void XRRay::Set(FloatPoint3D origin,
84                 FloatPoint3D direction,
85                 ExceptionState& exception_state) {
86   DVLOG(3) << __FUNCTION__ << ": origin=" << origin.ToString()
87            << ", direction=" << direction.ToString();
88 
89   direction.Normalize();
90 
91   origin_ = DOMPointReadOnly::Create(origin.X(), origin.Y(), origin.Z(), 1.0);
92   direction_ = DOMPointReadOnly::Create(direction.X(), direction.Y(),
93                                         direction.Z(), 0.0);
94 }
95 
Create(XRRigidTransform * transform,ExceptionState & exception_state)96 XRRay* XRRay::Create(XRRigidTransform* transform,
97                      ExceptionState& exception_state) {
98   auto* result = MakeGarbageCollected<XRRay>(transform, exception_state);
99 
100   if (exception_state.HadException()) {
101     return nullptr;
102   }
103 
104   return result;
105 }
106 
Create(DOMPointInit * origin,XRRayDirectionInit * direction,ExceptionState & exception_state)107 XRRay* XRRay::Create(DOMPointInit* origin,
108                      XRRayDirectionInit* direction,
109                      ExceptionState& exception_state) {
110   auto* result =
111       MakeGarbageCollected<XRRay>(origin, direction, exception_state);
112 
113   if (exception_state.HadException()) {
114     return nullptr;
115   }
116 
117   return result;
118 }
119 
~XRRay()120 XRRay::~XRRay() {}
121 
matrix()122 DOMFloat32Array* XRRay::matrix() {
123   DVLOG(3) << __FUNCTION__;
124 
125   // A page may take the matrix value and detach it so matrix_ is a detached
126   // array buffer.  If that's the case, recompute the matrix.
127   // Step 1. If transform’s internal matrix is not null, perform the following
128   // steps:
129   //    Step 1. If the operation IsDetachedBuffer on internal matrix is false,
130   //    return transform’s internal matrix.
131   if (!matrix_ || !matrix_->Data()) {
132     // Returned matrix should represent transformation from ray originating at
133     // (0,0,0) with direction (0,0,-1) into ray originating at |origin_| with
134     // direction |direction_|.
135 
136     TransformationMatrix matrix;
137 
138     const blink::FloatPoint3D desiredRayDirection = {
139         direction_->x(), direction_->y(), direction_->z()};
140 
141     // Translation from 0 to |origin_| is simply translation by |origin_|.
142     // (implicit) Step 6: Let translation be the translation matrix with
143     // components corresponding to ray’s origin
144     matrix.Translate3d(origin_->x(), origin_->y(), origin_->z());
145 
146     // Step 2: Let z be the vector [0, 0, -1]
147     const blink::FloatPoint3D initialRayDirection =
148         blink::FloatPoint3D{0.f, 0.f, -1.f};
149 
150     // Step 3: Let axis be the vector cross product of z and ray’s direction,
151     // z × direction
152     blink::FloatPoint3D axis = initialRayDirection.Cross(desiredRayDirection);
153 
154     // Step 4: Let cos_angle be the scalar dot product of z and ray’s direction,
155     // z · direction
156     float cos_angle = initialRayDirection.Dot(desiredRayDirection);
157 
158     // Step 5: Set rotation based on the following:
159     if (cos_angle > 0.9999) {
160       // Vectors are co-linear or almost co-linear & face the same direction,
161       // no rotation is needed.
162 
163     } else if (cos_angle < -0.9999) {
164       // Vectors are co-linear or almost co-linear & face the opposite
165       // direction, rotation by 180 degrees is needed & can be around any vector
166       // perpendicular to (0,0,-1) so let's rotate by (1, 0, 0).
167       matrix.Rotate3d(1, 0, 0, 180);
168     } else {
169       // Rotation needed - create it from axis-angle.
170       matrix.Rotate3d(axis.X(), axis.Y(), axis.Z(),
171                       rad2deg(std::acos(cos_angle)));
172     }
173 
174     // Step 7: Let matrix be the result of premultiplying rotation from the left
175     // onto translation (i.e. translation * rotation) in column-vector notation.
176     // Step 8: Set ray’s internal matrix to matrix
177     matrix_ = transformationMatrixToDOMFloat32Array(matrix);
178     if (!raw_matrix_) {
179       raw_matrix_ = std::make_unique<TransformationMatrix>(matrix);
180     } else {
181       *raw_matrix_ = matrix;
182     }
183   }
184 
185   // Step 9: Return matrix
186   return matrix_;
187 }
188 
RawMatrix()189 TransformationMatrix XRRay::RawMatrix() {
190   matrix();
191 
192   DCHECK(raw_matrix_);
193 
194   return *raw_matrix_;
195 }
196 
Trace(Visitor * visitor) const197 void XRRay::Trace(Visitor* visitor) const {
198   visitor->Trace(origin_);
199   visitor->Trace(direction_);
200   visitor->Trace(matrix_);
201   ScriptWrappable::Trace(visitor);
202 }
203 
204 }  // namespace blink
205