2 * Copyright (c) 2023 Samsung Electronics Co., Ltd.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 #include <dali/internal/event/events/ray-test.h>
22 #include <algorithm> ///< for std::min, std::max
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>
33 using Dali::Internal::SceneGraph::Node;
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
41 * @brief Get the epsilon value that we allow to test.
42 * It will be used when some numeric error occured.
44 * @param targetScale Scale of target value
45 * @return The epsilon value for target scale touch case.
47 constexpr float GetEpsilon(const float targetScale)
49 return std::max(RAY_TEST_ABSOLUTE_EPSILON, RAY_TEST_RELATIVE_EPSILON * targetScale);
58 : mEventThreadServices(EventThreadServices::Get())
62 bool RayTest::SphereTest(const Internal::Actor& actor, const Vector4& rayOrigin, const Vector4& rayDir) const
65 http://wiki.cgsociety.org/index.php/Ray_Sphere_Intersection
67 Mathematical Formulation
69 Given the above mentioned sphere, a point 'p' lies on the surface of the sphere if
71 ( p - c ) dot ( p - c ) = r^2
73 Given a ray with a point of origin 'o', and a direction vector 'd':
75 ray(t) = o + td, t >= 0
77 we can find the t at which the ray intersects the sphere by setting ray(t) equal to 'p'
79 (o + td - c ) dot ( o + td - c ) = r^2
81 To solve for t we first expand the above into a more recognisable quadratic equation form
83 ( d dot d )t^2 + 2( o - c ) dot dt + ( o - c ) dot ( o - c ) - r^2 = 0
93 C = ( o - c ) dot ( o - c ) - r^2
95 which can be solved using a standard quadratic formula.
97 Note that in the absence of positive, real, roots, the ray does not intersect the sphere.
99 Practical Simplification
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.
105 If a sphere is centred at origin, a point 'p' lies on a sphere of radius r2 if
109 and we can find the t at which the (transformed) ray intersects the sphere by
111 ( o + td ) dot ( o + td ) = r^2
113 According to the reasoning above, we expand the above quadratic equation into the general form
117 which now has coefficients:
124 // Early out if not on the scene
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)
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);
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;
144 // Correction numeric error.
145 const float epsilon = GetEpsilon(std::max(width, height));
147 float squareSphereRadius = 0.5f * (width * width + height * height) + epsilon;
149 float a = rayDir.Dot(rayDir); // a
150 float b2 = rayDir.Dot(rayOriginLocal); // b/2
151 float c = rayOriginLocal.Dot(rayOriginLocal) - squareSphereRadius; // c
153 return (b2 * b2 - a * c) >= 0.0f;
156 bool RayTest::ActorTest(const Internal::Actor& actor, const Vector4& rayOrigin, const Vector4& rayDir, Vector2& hitPointLocal, float& distance) const
162 const Node& node = actor.GetNode();
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();
170 Vector4 rayOriginLocal(invModelMatrix * rayOrigin);
171 Vector4 rayDirLocal(invModelMatrix * rayDir - invModelMatrix.GetTranslation());
173 // Test with the actor's XY plane (Normal = 0 0 1 1).
174 float a = -rayOriginLocal.z;
175 float b = rayDirLocal.z;
177 if(fabsf(b) > Math::MACHINE_EPSILON_1)
179 // Ray travels distance * rayDirLocal to intersect with plane.
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;
187 // Correction numeric error.
188 const float epsilon = GetEpsilon(std::max(size.x, size.y));
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)));
198 } // namespace Internal