[Tizen] Allow epsilon value for ray-test
[platform/core/uifw/dali-core.git] / dali / internal / event / events / ray-test.cpp
1 /*
2  * Copyright (c) 2023 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17
18 // CLASS HEADER
19 #include <dali/internal/event/events/ray-test.h>
20
21 // EXTERNAL INCLUDES
22 #include <algorithm> ///< for std::min, std::max
23
24 // INTERNAL INCLUDES
25 #include <dali/internal/event/actors/actor-impl.h>
26 #include <dali/internal/event/common/event-thread-services.h>
27 #include <dali/internal/update/nodes/node.h>
28 #include <dali/public-api/math/compile-time-math.h>
29 #include <dali/public-api/math/vector2.h>
30 #include <dali/public-api/math/vector3.h>
31 #include <dali/public-api/math/vector4.h>
32
33 using Dali::Internal::SceneGraph::Node;
34
35 namespace
36 {
37 constexpr float RAY_TEST_ABSOLUTE_EPSILON = Dali::Epsilon<10>::value;   ///< 0.0000011920928955078125
38 constexpr float RAY_TEST_RELATIVE_EPSILON = Dali::Epsilon<1000>::value; ///< 0.00011920928955078125
39
40 /**
41  * @brief Get the epsilon value that we allow to test.
42  * It will be used when some numeric error occured.
43  *
44  * @param targetScale Scale of target value
45  * @return The epsilon value for target scale touch case.
46  */
47 constexpr float GetEpsilon(const float targetScale)
48 {
49   return std::max(RAY_TEST_ABSOLUTE_EPSILON, RAY_TEST_RELATIVE_EPSILON * targetScale);
50 }
51 } // namespace
52
53 namespace Dali
54 {
55 namespace Internal
56 {
57 RayTest::RayTest()
58 : mEventThreadServices(EventThreadServices::Get())
59 {
60 }
61
62 bool RayTest::SphereTest(const Internal::Actor& actor, const Vector4& rayOrigin, const Vector4& rayDir) const
63 {
64   /*
65    http://wiki.cgsociety.org/index.php/Ray_Sphere_Intersection
66
67    Mathematical Formulation
68
69    Given the above mentioned sphere, a point 'p' lies on the surface of the sphere if
70
71    ( p - c ) dot ( p - c ) = r^2
72
73    Given a ray with a point of origin 'o', and a direction vector 'd':
74
75    ray(t) = o + td, t >= 0
76
77    we can find the t at which the ray intersects the sphere by setting ray(t) equal to 'p'
78
79    (o + td - c ) dot ( o + td - c ) = r^2
80
81    To solve for t we first expand the above into a more recognisable quadratic equation form
82
83    ( d dot d )t^2 + 2( o - c ) dot dt + ( o - c ) dot ( o - c ) - r^2 = 0
84
85    or
86
87    At2 + Bt + C = 0
88
89    where
90
91    A = d dot d
92    B = 2( o - c ) dot d
93    C = ( o - c ) dot ( o - c ) - r^2
94
95    which can be solved using a standard quadratic formula.
96
97    Note that in the absence of positive, real, roots, the ray does not intersect the sphere.
98
99    Practical Simplification
100
101    In a renderer, we often differentiate between world space and object space. In the object space
102    of a sphere it is centred at origin, meaning that if we first transform the ray from world space
103    into object space, the mathematical solution presented above can be simplified significantly.
104
105    If a sphere is centred at origin, a point 'p' lies on a sphere of radius r2 if
106
107    p dot p = r^2
108
109    and we can find the t at which the (transformed) ray intersects the sphere by
110
111    ( o + td ) dot ( o + td ) = r^2
112
113    According to the reasoning above, we expand the above quadratic equation into the general form
114
115    At2 + Bt + C = 0
116
117    which now has coefficients:
118
119    A = d dot d
120    B = 2( d dot o )
121    C = o dot o - r^2
122    */
123
124   // Early out if not on the scene
125   if(!actor.OnScene())
126   {
127     return false;
128   }
129
130   const Node&       node            = actor.GetNode();
131   const BufferIndex bufferIndex     = EventThreadServices::Get().GetEventBufferIndex();
132   const Vector3&    translation     = node.GetWorldPosition(bufferIndex);
133   const Vector3&    size            = node.GetSize(bufferIndex);
134   const Vector3&    scale           = node.GetWorldScale(bufferIndex);
135   const Rect<int>&  touchAreaOffset = actor.GetTouchAreaOffset(); // (left, right, bottom, top)
136
137   // Transforms the ray to the local reference system. As the test is against a sphere, only the translation and scale are needed.
138   const Vector3 rayOriginLocal(rayOrigin.x - translation.x - (touchAreaOffset.left + touchAreaOffset.right) * 0.5, rayOrigin.y - translation.y - (touchAreaOffset.top + touchAreaOffset.bottom) * 0.5, rayOrigin.z - translation.z);
139
140   // Computing the radius is not needed, a square radius is enough so can just use size but we do need to scale the sphere
141   const float width  = size.width * scale.width + touchAreaOffset.right - touchAreaOffset.left;
142   const float height = size.height * scale.height + touchAreaOffset.bottom - touchAreaOffset.top;
143
144   // Correction numeric error.
145   const float epsilon = GetEpsilon(std::max(width, height));
146
147   float squareSphereRadius = 0.5f * (width * width + height * height) + epsilon;
148
149   float a  = rayDir.Dot(rayDir);                                      // a
150   float b2 = rayDir.Dot(rayOriginLocal);                              // b/2
151   float c  = rayOriginLocal.Dot(rayOriginLocal) - squareSphereRadius; // c
152
153   return (b2 * b2 - a * c) >= 0.0f;
154 }
155
156 bool RayTest::ActorTest(const Internal::Actor& actor, const Vector4& rayOrigin, const Vector4& rayDir, Vector2& hitPointLocal, float& distance) const
157 {
158   bool hit = false;
159
160   if(actor.OnScene())
161   {
162     const Node& node = actor.GetNode();
163
164     // Transforms the ray to the local reference system.
165     // Calculate the inverse of Model matrix
166     Matrix invModelMatrix(false /*don't init*/);
167     invModelMatrix = node.GetWorldMatrix(0);
168     invModelMatrix.Invert();
169
170     Vector4 rayOriginLocal(invModelMatrix * rayOrigin);
171     Vector4 rayDirLocal(invModelMatrix * rayDir - invModelMatrix.GetTranslation());
172
173     // Test with the actor's XY plane (Normal = 0 0 1 1).
174     float a = -rayOriginLocal.z;
175     float b = rayDirLocal.z;
176
177     if(fabsf(b) > Math::MACHINE_EPSILON_1)
178     {
179       // Ray travels distance * rayDirLocal to intersect with plane.
180       distance = a / b;
181
182       const Vector2&   size            = Vector2(node.GetSize(EventThreadServices::Get().GetEventBufferIndex()));
183       const Rect<int>& touchAreaOffset = actor.GetTouchAreaOffset(); // (left, right, bottom, top)
184       hitPointLocal.x                  = rayOriginLocal.x + rayDirLocal.x * distance + size.x * 0.5f;
185       hitPointLocal.y                  = rayOriginLocal.y + rayDirLocal.y * distance + size.y * 0.5f;
186
187       // Correction numeric error.
188       const float epsilon = GetEpsilon(std::max(size.x, size.y));
189
190       // Test with the actor's geometry.
191       hit = (hitPointLocal.x >= touchAreaOffset.left - epsilon) && (hitPointLocal.x <= (size.x + touchAreaOffset.right + epsilon) && (hitPointLocal.y >= touchAreaOffset.top - epsilon) && (hitPointLocal.y <= (size.y + touchAreaOffset.bottom + epsilon)));
192     }
193   }
194
195   return hit;
196 }
197
198 } // namespace Internal
199
200 } // namespace Dali