2 * Copyright (c) 2021 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 <dali/internal/event/actors/actor-impl.h>
23 #include <dali/internal/event/common/event-thread-services.h>
24 #include <dali/internal/update/nodes/node.h>
25 #include <dali/public-api/math/vector2.h>
26 #include <dali/public-api/math/vector3.h>
27 #include <dali/public-api/math/vector4.h>
29 using Dali::Internal::SceneGraph::Node;
36 : mEventThreadServices(EventThreadServices::Get())
40 bool RayTest::SphereTest(const Internal::Actor& actor, const Vector4& rayOrigin, const Vector4& rayDir) const
43 http://wiki.cgsociety.org/index.php/Ray_Sphere_Intersection
45 Mathematical Formulation
47 Given the above mentioned sphere, a point 'p' lies on the surface of the sphere if
49 ( p - c ) dot ( p - c ) = r^2
51 Given a ray with a point of origin 'o', and a direction vector 'd':
53 ray(t) = o + td, t >= 0
55 we can find the t at which the ray intersects the sphere by setting ray(t) equal to 'p'
57 (o + td - c ) dot ( o + td - c ) = r^2
59 To solve for t we first expand the above into a more recognisable quadratic equation form
61 ( d dot d )t^2 + 2( o - c ) dot dt + ( o - c ) dot ( o - c ) - r^2 = 0
71 C = ( o - c ) dot ( o - c ) - r^2
73 which can be solved using a standard quadratic formula.
75 Note that in the absence of positive, real, roots, the ray does not intersect the sphere.
77 Practical Simplification
79 In a renderer, we often differentiate between world space and object space. In the object space
80 of a sphere it is centred at origin, meaning that if we first transform the ray from world space
81 into object space, the mathematical solution presented above can be simplified significantly.
83 If a sphere is centred at origin, a point 'p' lies on a sphere of radius r2 if
87 and we can find the t at which the (transformed) ray intersects the sphere by
89 ( o + td ) dot ( o + td ) = r^2
91 According to the reasoning above, we expand the above quadratic equation into the general form
95 which now has coefficients:
102 // Early out if not on the scene
108 const Node& node = actor.GetNode();
109 const BufferIndex bufferIndex = EventThreadServices::Get().GetEventBufferIndex();
110 const Vector3& translation = node.GetWorldPosition(bufferIndex);
111 const Vector3& size = node.GetSize(bufferIndex);
112 const Vector3& scale = node.GetWorldScale(bufferIndex);
113 const Rect<int>& touchAreaOffset = actor.GetTouchAreaOffset(); // (left, right, bottom, top)
115 // Transforms the ray to the local reference system. As the test is against a sphere, only the translation and scale are needed.
116 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);
118 // Computing the radius is not needed, a square radius is enough so can just use size but we do need to scale the sphere
119 const float width = size.width * scale.width + touchAreaOffset.right - touchAreaOffset.left;
120 const float height = size.height * scale.height + touchAreaOffset.bottom - touchAreaOffset.top;
122 float squareSphereRadius = 0.5f * (width * width + height * height);
124 float a = rayDir.Dot(rayDir); // a
125 float b2 = rayDir.Dot(rayOriginLocal); // b/2
126 float c = rayOriginLocal.Dot(rayOriginLocal) - squareSphereRadius; // c
128 return (b2 * b2 - a * c) >= 0.0f;
131 bool RayTest::ActorTest(const Internal::Actor& actor, const Vector4& rayOrigin, const Vector4& rayDir, Vector2& hitPointLocal, float& distance) const
137 const Node& node = actor.GetNode();
139 // Transforms the ray to the local reference system.
140 // Calculate the inverse of Model matrix
141 Matrix invModelMatrix(false /*don't init*/);
142 invModelMatrix = node.GetWorldMatrix(0);
143 invModelMatrix.Invert();
145 Vector4 rayOriginLocal(invModelMatrix * rayOrigin);
146 Vector4 rayDirLocal(invModelMatrix * rayDir - invModelMatrix.GetTranslation());
148 // Test with the actor's XY plane (Normal = 0 0 1 1).
149 float a = -rayOriginLocal.z;
150 float b = rayDirLocal.z;
152 if(fabsf(b) > Math::MACHINE_EPSILON_1)
154 // Ray travels distance * rayDirLocal to intersect with plane.
157 const Vector2& size = Vector2(node.GetSize(EventThreadServices::Get().GetEventBufferIndex()));
158 const Rect<int>& touchAreaOffset = actor.GetTouchAreaOffset(); // (left, right, bottom, top)
159 hitPointLocal.x = rayOriginLocal.x + rayDirLocal.x * distance + size.x * 0.5f;
160 hitPointLocal.y = rayOriginLocal.y + rayDirLocal.y * distance + size.y * 0.5f;
162 // Test with the actor's geometry.
163 hit = (hitPointLocal.x >= touchAreaOffset.left) && (hitPointLocal.x <= (size.x + touchAreaOffset.right) && (hitPointLocal.y >= touchAreaOffset.top) && (hitPointLocal.y <= (size.y + touchAreaOffset.bottom)));
170 } // namespace Internal