diff --git a/include/Nazara/Math/Frustum.inl b/include/Nazara/Math/Frustum.inl index fbdbdbdcc..45f8cc98d 100644 --- a/include/Nazara/Math/Frustum.inl +++ b/include/Nazara/Math/Frustum.inl @@ -180,7 +180,7 @@ NzFrustum& NzFrustum::Extract(const NzMatrix4& clipMatrix) plane[0] *= invLength; plane[1] *= invLength; plane[2] *= invLength; - plane[3] *= invLength; + plane[3] *= -invLength; m_planes[nzFrustumPlane_Right].Set(plane); @@ -195,7 +195,7 @@ NzFrustum& NzFrustum::Extract(const NzMatrix4& clipMatrix) plane[0] *= invLength; plane[1] *= invLength; plane[2] *= invLength; - plane[3] *= invLength; + plane[3] *= -invLength; m_planes[nzFrustumPlane_Left].Set(plane); @@ -210,7 +210,7 @@ NzFrustum& NzFrustum::Extract(const NzMatrix4& clipMatrix) plane[0] *= invLength; plane[1] *= invLength; plane[2] *= invLength; - plane[3] *= invLength; + plane[3] *= -invLength; m_planes[nzFrustumPlane_Bottom].Set(plane); @@ -225,7 +225,7 @@ NzFrustum& NzFrustum::Extract(const NzMatrix4& clipMatrix) plane[0] *= invLength; plane[1] *= invLength; plane[2] *= invLength; - plane[3] *= invLength; + plane[3] *= -invLength; m_planes[nzFrustumPlane_Top].Set(plane); @@ -240,7 +240,7 @@ NzFrustum& NzFrustum::Extract(const NzMatrix4& clipMatrix) plane[0] *= invLength; plane[1] *= invLength; plane[2] *= invLength; - plane[3] *= invLength; + plane[3] *= -invLength; m_planes[nzFrustumPlane_Far].Set(plane); @@ -255,7 +255,7 @@ NzFrustum& NzFrustum::Extract(const NzMatrix4& clipMatrix) plane[0] *= invLength; plane[1] *= invLength; plane[2] *= invLength; - plane[3] *= invLength; + plane[3] *= -invLength; m_planes[nzFrustumPlane_Near].Set(plane); @@ -332,10 +332,7 @@ NzFrustum& NzFrustum::Extract(const NzMatrix4& clipMatrix) template NzFrustum& NzFrustum::Extract(const NzMatrix4& view, const NzMatrix4& projection) { - NzMatrix4 clipMatrix(view); - clipMatrix *= projection; - - return Extract(clipMatrix); + return Extract(NzMatrix4::Concatenate(view, projection)); } template diff --git a/include/Nazara/Math/Matrix4.inl b/include/Nazara/Math/Matrix4.inl index e3d172c32..06f30ae6e 100644 --- a/include/Nazara/Math/Matrix4.inl +++ b/include/Nazara/Math/Matrix4.inl @@ -578,6 +578,21 @@ NzMatrix4& NzMatrix4::MakeIdentity() return *this; } +template +NzMatrix4& NzMatrix4::MakeLookAt(const NzVector3& eye, const NzVector3& target, const NzVector3& up) +{ + NzVector3 f = NzVector3::Normalize(target - eye); + NzVector3 s = NzVector3::Normalize(f.CrossProduct(up)); + NzVector3 u = s.CrossProduct(f); + + Set(s.x, u.x, -f.x, T(0.0), + s.y, u.y, -f.y, T(0.0), + s.z, u.z, -f.z, T(0.0), + -s.DotProduct(eye), -u.DotProduct(eye), f.DotProduct(eye), T(1.0)); + + return *this; +} + template NzMatrix4& NzMatrix4::MakeOrtho(T left, T right, T top, T bottom, T zNear, T zFar) { @@ -590,22 +605,6 @@ NzMatrix4& NzMatrix4::MakeOrtho(T left, T right, T top, T bottom, T zNear, return *this; } -template -NzMatrix4& NzMatrix4::MakeLookAt(const NzVector3& eye, const NzVector3& target, const NzVector3& up) -{ - NzVector3 f = NzVector3::Normalize(target - eye); - NzVector3 u(up.GetNormal()); - NzVector3 s = NzVector3::Normalize(f.CrossProduct(u)); - u = s.CrossProduct(f); - - Set(s.x, u.x, -f.x, T(0.0), - s.y, u.y, -f.y, T(0.0), - s.z, u.z, -f.z, T(0.0), - -s.DotProduct(eye), -u.DotProduct(eye), f.DotProduct(eye), T(1.0)); - - return *this; -} - template NzMatrix4& NzMatrix4::MakePerspective(T angle, T ratio, T zNear, T zFar) { @@ -616,12 +615,12 @@ NzMatrix4& NzMatrix4::MakePerspective(T angle, T ratio, T zNear, T zFar) angle = NzDegreeToRadian(angle/F(2.0)); #endif - T yScale = F(1.0) / std::tan(angle); + T yScale = std::tan(M_PI_2 - angle); Set(yScale / ratio, F(0.0), F(0.0), F(0.0), F(0.0), yScale, F(0.0), F(0.0), - F(0.0), F(0.0), zFar / (zNear-zFar), F(-1.0), - F(0.0), F(0.0), (zNear*zFar) / (zNear-zFar), F(0.0)); + F(0.0), F(0.0), - (zFar + zNear) / (zFar - zNear), F(-1.0), + F(0.0), F(0.0), F(-2.0) * (zNear * zFar) / (zFar - zNear), F(0.0)); return *this; } @@ -696,7 +695,7 @@ NzMatrix4& NzMatrix4::MakeViewMatrix(const NzVector3& translation, cons // Une matrice de vue doit appliquer une transformation opposée à la matrice "monde" NzQuaternion invRot = rotation.GetConjugate(); // Inverse de la rotation - return MakeTransform(-(invRot*translation), invRot); + return MakeTransform(-(invRot * translation), invRot); } template @@ -821,9 +820,9 @@ NzString NzMatrix4::ToString() const { NzStringStream ss; return ss << "Matrix4(" << m11 << ", " << m12 << ", " << m13 << ", " << m14 << ",\n" - << " " << m21 << ", " << m22 << ", " << m23 << ", " << m24 << ",\n" - << " " << m31 << ", " << m32 << ", " << m33 << ", " << m34 << ",\n" - << " " << m41 << ", " << m42 << ", " << m43 << ", " << m44 << ')'; + << " " << m21 << ", " << m22 << ", " << m23 << ", " << m24 << ",\n" + << " " << m31 << ", " << m32 << ", " << m33 << ", " << m34 << ",\n" + << " " << m41 << ", " << m42 << ", " << m43 << ", " << m44 << ')'; } template diff --git a/src/Nazara/Graphics/SkyboxBackground.cpp b/src/Nazara/Graphics/SkyboxBackground.cpp index ba806d76b..a2345734b 100644 --- a/src/Nazara/Graphics/SkyboxBackground.cpp +++ b/src/Nazara/Graphics/SkyboxBackground.cpp @@ -86,7 +86,8 @@ namespace "void main()\n" "{\n" - " gl_Position = WorldViewProjMatrix * vec4(VertexPosition, 1.0);\n" + " vec4 WVPVertex = WorldViewProjMatrix * vec4(VertexPosition, 1.0);\n" + " gl_Position = WVPVertex.xyww;\n" " vTexCoord = vec3(VertexPosition.x, VertexPosition.y, -VertexPosition.z);\n" "}\n"; @@ -101,7 +102,8 @@ namespace "void main()\n" "{\n" - " gl_Position = WorldViewProjMatrix * vec4(VertexPosition, 1.0);\n" + " vec4 WVPVertex = WorldViewProjMatrix * vec4(VertexPosition, 1.0);\n" + " gl_Position = WVPVertex.xyww;\n" " vTexCoord = vec3(VertexPosition.x, VertexPosition.y, -VertexPosition.z);\n" "}\n"; diff --git a/tests/Nazara/Math/Frustum.cpp b/tests/Nazara/Math/Frustum.cpp new file mode 100644 index 000000000..ae13afe7b --- /dev/null +++ b/tests/Nazara/Math/Frustum.cpp @@ -0,0 +1,82 @@ +#include +#include + +SCENARIO("Frustum", "[MATH][FRUSTUM]") +{ + GIVEN("One frustum (90, 1, 1, 1000, (0, 0, 0), (1, 0, 0))") + { + NzFrustumf frustum; + frustum.Build(NzFromDegrees(90.f), 1.f, 1.f, 1000.f, NzVector3f::Zero(), NzVector3f::UnitX()); + + WHEN("We ask for intersection with objects outside the frustum") + { + THEN("These results are expected") + { + NzBoundingVolumef bv(NzVector3f::Zero(), NzVector3f::Unit()); + bv.Update(NzMatrix4f::Identity()); + REQUIRE(nzIntersectionSide_Outside == frustum.Intersect(bv)); + REQUIRE(nzIntersectionSide_Outside == frustum.Intersect(NzBoxf(NzVector3f::Zero(), NzVector3f::Unit() * 0.9f))); + NzOrientedBoxf obb(NzVector3f::Zero(), NzVector3f::Unit() * 0.9f); + obb.Update(NzMatrix4f::Identity()); + REQUIRE(nzIntersectionSide_Outside == frustum.Intersect(obb)); + REQUIRE(nzIntersectionSide_Outside == frustum.Intersect(NzSpheref(NzVector3f::Zero(), 0.5f))); + NzVector3f tmp = NzVector3f::Zero(); + REQUIRE(nzIntersectionSide_Outside == frustum.Intersect(&tmp, 1)); + tmp = NzVector3f::UnitX() * -10.f; + REQUIRE(nzIntersectionSide_Outside == frustum.Intersect(&tmp, 1)); + } + } + + WHEN("We ask for intersection with objects inside the frustum") + { + THEN("These results are expected") + { + NzBoundingVolumef bv(500.f, -0.5f, -0.5f, 1.f, 1.f, 1.f); + bv.Update(NzMatrix4f::Identity()); + + REQUIRE(nzIntersectionSide_Inside == frustum.Intersect(bv)); + REQUIRE(nzIntersectionSide_Inside == frustum.Intersect(NzBoxf(NzVector3f::UnitX() * 500.f, NzVector3f::Unit()))); + NzOrientedBoxf obb(NzVector3f::UnitX() * 100.f, NzVector3f::Unit()); + obb.Update(NzMatrix4f::Identity()); + REQUIRE(nzIntersectionSide_Inside == frustum.Intersect(obb)); + REQUIRE(nzIntersectionSide_Inside == frustum.Intersect(NzSpheref(NzVector3f::UnitX() * 100.f, 0.5f))); + NzVector3f tmp = NzVector3f::UnitX() * 100.f; + REQUIRE(nzIntersectionSide_Inside == frustum.Intersect(&tmp, 1)); + } + } + + WHEN("We ask for contains with objects outside the frustum") + { + THEN("These results are expected") + { + NzBoundingVolumef bv(0.f, -0.25f, -0.25f, 0.5f, 0.5f, 0.5f); + bv.Update(NzMatrix4f::Identity()); + CHECK(!frustum.Contains(bv)); + CHECK(!frustum.Contains(NzBoxf(0.f, -0.25f, -0.25f, 0.5f, 0.5f, 0.5f))); + NzOrientedBoxf obb(0.f, -0.25f, -0.25f, 0.5f, 0.5f, 0.5f); + obb.Update(NzMatrix4f::Identity()); + CHECK(!frustum.Contains(obb)); + CHECK(!frustum.Contains(NzSpheref(NzVector3f::Zero(), 0.5f))); + NzVector3f tmp = NzVector3f::Zero(); + CHECK(!frustum.Contains(&tmp, 1)); + } + } + + WHEN("We ask for contains with objects inside the frustum") + { + THEN("These results are expected") + { + NzBoundingVolumef bv(500.f, -0.5f, -0.5f, 1.f, 1.f, 1.f); + bv.Update(NzMatrix4f::Identity()); + CHECK(frustum.Contains(bv)); + CHECK(frustum.Contains(NzBoxf(500.f, -0.5f, -0.5f, 1.f, 1.f, 1.f))); + NzOrientedBoxf obb(500.f, -0.5f, -0.5f, 1.f, 1.f, 1.f); + obb.Update(NzMatrix4f::Identity()); + CHECK(frustum.Contains(obb)); + CHECK(frustum.Contains(NzSpheref(NzVector3f::UnitX() * 500.f, 1.f))); + NzVector3f tmp = NzVector3f::UnitX() * 500.f; + CHECK(frustum.Contains(&tmp, 1)); + } + } + } +} diff --git a/tests/Nazara/Math/Matrix4.cpp b/tests/Nazara/Math/Matrix4.cpp new file mode 100644 index 000000000..ea61e4541 --- /dev/null +++ b/tests/Nazara/Math/Matrix4.cpp @@ -0,0 +1,134 @@ +#include +#include + +SCENARIO("Matrix4", "[MATH][MATRIX4]") +{ + GIVEN("Two identity matrix") + { + NzMatrix4f firstIdentity(NzMatrix4::Identity()); + NzMatrix4f secondIdentity(1.f, 0.f, 0.f, 0.f, 0.f, 1.f, 0.f, 0.f, 0.f, 0.f, 1.f, 0.f, 0.f, 0.f, 0.f, 1.f); + + WHEN("We compare them") + { + THEN("They are equal") + { + REQUIRE(firstIdentity == secondIdentity); + } + } + + WHEN("We multiply the first with a vector") + { + THEN("Vector stay the same") + { + REQUIRE(firstIdentity.Transform(NzVector2f::Unit()) == NzVector2f::Unit()); + REQUIRE(firstIdentity.Transform(NzVector3f::Unit()) == NzVector3f::Unit()); + REQUIRE(firstIdentity.Transform(NzVector4f(1.f, 1.f, 1.f, 1.f)) == NzVector4f(1.f, 1.f, 1.f, 1.f)); + } + } + + WHEN("We multiply them") + { + THEN("It keeps being a identity") + { + REQUIRE(firstIdentity.Concatenate(secondIdentity) == firstIdentity); + REQUIRE(firstIdentity.ConcatenateAffine(secondIdentity) == firstIdentity); + REQUIRE((firstIdentity * secondIdentity) == firstIdentity); + REQUIRE((1.f * firstIdentity) == firstIdentity); + REQUIRE(firstIdentity.Inverse() == secondIdentity.InverseAffine()); + } + } + } + + GIVEN("Two different matrix") + { + NzMatrix4f matrix1(1.0f, 0.0f, 0.0f, 0.0f, + 7.0f, 2.0f, 0.0f, 0.0f, + 1.0f, 5.0f, 3.0f, 0.0f, + 8.0f, 9.0f, 2.0f, 4.0f); + + NzMatrix4f matrix2(1.0f, 1.0f, 2.0f, -1.0f, + -2.0f, -1.0f, -2.0f, 2.0f, + 4.0f, 2.0f, 5.0f, -4.0f, + 5.0f, -3.0f, -7.0f, -6.0f); + + WHEN("We ask for determinant") + { + THEN("These results are expected") + { + REQUIRE(matrix1.GetDeterminant() == Approx(24.f)); + REQUIRE(matrix2.GetDeterminant() == Approx(-1.f)); + } + } + + WHEN("We multiply the matrix and its inverse") + { + NzMatrix4f invMatrix1; + matrix1.GetInverse(&invMatrix1); + + NzMatrix4f invMatrix2; + matrix2.GetInverse(&invMatrix2); + + THEN("We get the identity") + { + NzMatrix4f tmp = matrix1 * invMatrix1; + REQUIRE(tmp.m32 == Approx(0.f)); + REQUIRE(tmp.m42 == Approx(0.f)); + tmp.m32 = 0.f; + tmp.m42 = 0.f; + REQUIRE(tmp == NzMatrix4f::Identity()); + REQUIRE((matrix2 * invMatrix2) == NzMatrix4f::Identity()); + } + } + } + + GIVEN("One transformed matrix from rotation 45 and translation 0") + { + NzMatrix4f transformedMatrix = NzMatrix4f::Transform(NzVector3f::Zero(), NzQuaternionf::Identity()); + REQUIRE(transformedMatrix == NzMatrix4f::Identity()); + + WHEN("We compare with the right matrix") + { + THEN("Rotation around X") + { + transformedMatrix.MakeTransform(NzVector3f::Zero(), NzEulerAnglesf(NzFromDegrees(45.f), 0.f, 0.f).ToQuaternion()); + NzMatrix4f rotation45X(1.f, 0.f, 0.f, 0.f, + 0.f, std::sqrt(2.f) / 2.f, std::sqrt(2.f) / 2.f, 0.f, + 0.f, -std::sqrt(2.f) / 2.f, std::sqrt(2.f) / 2.f, 0.f, + 0.f, 0.f, 0.f, 1.f); + + REQUIRE(transformedMatrix == rotation45X); + transformedMatrix.MakeTransform(NzVector3f::Unit(), NzEulerAnglesf(NzFromDegrees(45.f), 0.f, 0.f).ToQuaternion()); + rotation45X.ApplyTranslation(NzVector3f::Unit()); + REQUIRE(transformedMatrix == rotation45X); + } + + THEN("Rotation around Y") + { + transformedMatrix.MakeTransform(NzVector3f::Zero(), NzEulerAnglesf(0.f, NzFromDegrees(45.f), 0.f).ToQuaternion()); + NzMatrix4f rotation45Y(std::sqrt(2.f) / 2.f, 0.f, -std::sqrt(2.f) / 2.f, 0.f, + 0.f, 1.f, 0.f, 0.f, + std::sqrt(2.f) / 2.f, 0.f, std::sqrt(2.f) / 2.f, 0.f, + 0.f, 0.f, 0.f, 1.f); + + REQUIRE(transformedMatrix == rotation45Y); + transformedMatrix.MakeTransform(NzVector3f::Unit(), NzEulerAnglesf(0.f, NzFromDegrees(45.f), 0.f).ToQuaternion()); + rotation45Y.ApplyTranslation(NzVector3f::Unit()); + REQUIRE(transformedMatrix == rotation45Y); + } + + THEN("Rotation around Z") + { + transformedMatrix.MakeTransform(NzVector3f::Zero(), NzEulerAnglesf(0.f, 0.f, NzFromDegrees(45.f)).ToQuaternion()); + NzMatrix4f rotation45Z( std::sqrt(2.f) / 2.f, std::sqrt(2.f) / 2.f, 0.f, 0.f, + -std::sqrt(2.f) / 2.f, std::sqrt(2.f) / 2.f, 0.f, 0.f, + 0.f, 0.f, 1.f, 0.f, + 0.f, 0.f, 0.f, 1.f); + + REQUIRE(transformedMatrix == rotation45Z); + transformedMatrix.MakeTransform(NzVector3f::Unit(), NzEulerAnglesf(NzEulerAnglesf(0.f, 0.f, NzFromDegrees(45.f)).ToQuaternion())); + rotation45Z.ApplyTranslation(NzVector3f::Unit()); + REQUIRE(transformedMatrix == rotation45Z); + } + } + } +} diff --git a/tests/Nazara/Math/Ray.cpp b/tests/Nazara/Math/Ray.cpp new file mode 100644 index 000000000..3b7c4648c --- /dev/null +++ b/tests/Nazara/Math/Ray.cpp @@ -0,0 +1,95 @@ +#include +#include + +SCENARIO("Ray", "[RAY]") +{ + GIVEN("Two same rays (0, 0, 0) -> (0, 1, 0)") + { + NzRayf firstRay(NzRay(NzPlane::XY(), NzPlane::YZ())); + NzRayf secondRay(0.f, 0.f, 0.f, 0.f, 1.f, 0.f); + + WHEN("We compare them") + { + THEN("They are the same and Y axis") + { + REQUIRE(firstRay == secondRay); + REQUIRE(firstRay == NzRayf::AxisY()); + } + } + + WHEN("We ask for the closest point") + { + THEN("The point that is multiple on the ray, is at multiple") + { + REQUIRE(firstRay.ClosestPoint(secondRay.GetPoint(1.f)) == Approx(1.f)); + } + } + + WHEN("We ask for intersection") + { + THEN("For the Box collision's") + { + float tmpClosest; + float tmpFurthest; + + CHECK(firstRay.Intersect(NzBoxf(-0.5f, 1.f, -0.5f, 1.f, 1.f, 1.f), &tmpClosest, &tmpFurthest)); + REQUIRE(firstRay.GetPoint(tmpClosest) == NzVector3f::UnitY()); + REQUIRE(firstRay.GetPoint(tmpFurthest) == (NzVector3f::UnitY() * 2.f)); + CHECK(!firstRay.Intersect(NzBoxf(-10.f, 1.f, -10.f, 1.f, 1.f, 1.f), &tmpClosest, &tmpFurthest)); + } + + THEN("For the Plane collision's") + { + float tmpHit; + + CHECK(firstRay.Intersect(NzPlanef(NzVector3f::UnitY(), 1.f), &tmpHit)); + REQUIRE(firstRay.GetPoint(tmpHit) == NzVector3f::UnitY()); + CHECK(firstRay.Intersect(NzPlanef::XZ(), &tmpHit)); + REQUIRE(firstRay.GetPoint(tmpHit) == NzVector3f::Zero()); + CHECK(firstRay.Intersect(NzPlanef(NzVector3f::UnitY(), 2.f), &tmpHit)); + REQUIRE(firstRay.GetPoint(tmpHit) == 2.f * NzVector3f::UnitY()); + + CHECK(!firstRay.Intersect(NzPlanef(NzVector3f::UnitX(), 1.f))); + } + + THEN("For the Sphere collision's") + { + float tmpClosest; + float tmpFurthest; + + CHECK(firstRay.Intersect(NzSpheref(NzVector3f::UnitY(), 0.1f), &tmpClosest, &tmpFurthest)); + REQUIRE(firstRay.GetPoint(tmpClosest) == NzVector3f::UnitY() * 0.9f); + REQUIRE(firstRay.GetPoint(tmpFurthest) == (NzVector3f::UnitY() * 1.1f)); + + CHECK(!firstRay.Intersect(NzSpheref(NzVector3f::UnitX(), 0.9f))); + } + + THEN("For the OBB collision's") + { + float tmpClosest; + float tmpFurthest; + + NzOrientedBoxf obb(-0.5f, 1.f, -0.5f, 1.f, 1.f, 1.f); + obb.Update(NzMatrix4f::Rotate(NzEulerAnglesf(0.f, 90.f, 0.f).ToQuaternion())); + + CHECK(firstRay.Intersect(obb, &tmpClosest, &tmpFurthest)); + REQUIRE(firstRay.GetPoint(tmpClosest) == NzVector3f::UnitY()); + REQUIRE(firstRay.GetPoint(tmpFurthest) == (NzVector3f::UnitY() * 2.f)); + + obb = NzOrientedBoxf(-10.f, 1.f, -10.f, 1.f, 1.f, 1.f); + obb.Update(NzMatrix4f::Rotate(NzEulerAnglesf(0.f, 0.f, 90.f).ToQuaternion())); + CHECK(!firstRay.Intersect(obb, &tmpClosest, &tmpFurthest)); + } + + THEN("For the bounding volume collision's") + { + NzBoundingVolumef nullVolume(nzExtend_Null); + CHECK(!firstRay.Intersect(nullVolume)); + + NzBoundingVolumef infiniteVolume(nzExtend_Infinite); + CHECK(firstRay.Intersect(infiniteVolume)); + } + + } + } +} diff --git a/tests/Nazara/Math/Ray.hpp b/tests/Nazara/Math/Ray.hpp new file mode 100644 index 000000000..24b25e110 --- /dev/null +++ b/tests/Nazara/Math/Ray.hpp @@ -0,0 +1,78 @@ +// Copyright (C) 2015 Gawaboumga (https://github.com/Gawaboumga) - Jérôme Leclercq +// This file is part of the "Nazara Engine - Mathematics module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#pragma once + +#ifndef NAZARA_RAY_HPP +#define NAZARA_RAY_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +template +class NzRay +{ + public: + NzRay() = default; + NzRay(T X, T Y, T Z, T directionX, T directionY, T directionZ); + NzRay(const T origin[3], const T direction[3]); + NzRay(const NzPlane& planeOne, const NzPlane& planeTwo); + NzRay(const NzVector3& origin, const NzVector3& direction); + template explicit NzRay(const NzRay& ray); + template explicit NzRay(const NzVector3& origin, const NzVector3& direction); + NzRay(const NzRay& ray) = default; + ~NzRay() = default; + + T ClosestPoint(const NzVector3& point) const; + + NzVector3 GetPoint(T lambda) const; + + bool Intersect(const NzBoundingVolume& volume, T* closestHit = nullptr, T* furthestHit = nullptr) const; + bool Intersect(const NzBox& box, T* closestHit = nullptr, T* furthestHit = nullptr) const; + bool Intersect(const NzBox& box, const NzMatrix4& transform, T* closestHit = nullptr, T* furthestHit = nullptr) const; + bool Intersect(const NzOrientedBox& orientedBox, T* closestHit = nullptr, T* furthestHit = nullptr) const; + bool Intersect(const NzPlane& plane, T* hit = nullptr) const; + bool Intersect(const NzSphere& sphere, T* closestHit = nullptr, T* furthestHit = nullptr) const; + + NzRay& MakeAxisX(); + NzRay& MakeAxisY(); + NzRay& MakeAxisZ(); + + NzRay& Set(T X, T Y, T Z, T directionX, T directionY, T directionZ); + NzRay& Set(const T origin[3], const T direction[3]); + NzRay& Set(const NzPlane& planeOne, const NzPlane& planeTwo); + NzRay& Set(const NzRay& ray); + NzRay& Set(const NzVector3& origin, const NzVector3& direction); + template NzRay& Set(const NzRay& ray); + template NzRay& Set(const NzVector3& origin, const NzVector3& direction); + + NzString ToString() const; + + NzVector3 operator*(T lambda) const; + + bool operator==(const NzRay& ray) const; + bool operator!=(const NzRay& ray) const; + + static NzRay AxisX(); + static NzRay AxisY(); + static NzRay AxisZ(); + static NzRay Lerp(const NzRay& from, const NzRay& to, T interpolation); + + NzVector3 direction, origin; +}; + +template std::ostream& operator<<(std::ostream& out, const NzRay& vec); + +typedef NzRay NzRayd; +typedef NzRay NzRayf; + +#include + +#endif // NAZARA_RAY_HPP diff --git a/tests/Nazara/Math/Ray.inl b/tests/Nazara/Math/Ray.inl new file mode 100644 index 000000000..d0ee2d76c --- /dev/null +++ b/tests/Nazara/Math/Ray.inl @@ -0,0 +1,443 @@ +// Copyright (C) 2015 Gawaboumga (https://github.com/Gawaboumga) - Jérôme Leclercq +// This file is part of the "Nazara Engine - Mathematics module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include + +#define F(a) static_cast(a) + +template +NzRay::NzRay(T X, T Y, T Z, T DirectionX, T DirectionY, T DirectionZ) +{ + Set(X, Y, Z, DirectionX, DirectionY, DirectionZ); +} + +template +NzRay::NzRay(const T Origin[3], const T Direction[3]) +{ + Set(Origin, Direction); +} + +template +NzRay::NzRay(const NzPlane& planeOne, const NzPlane& planeTwo) +{ + Set(planeOne, planeTwo); +} + +template +NzRay::NzRay(const NzVector3& Origin, const NzVector3& Direction) +{ + Set(Origin, Direction); +} + +template +template +NzRay::NzRay(const NzRay& ray) +{ + Set(ray); +} + +template +template +NzRay::NzRay(const NzVector3& Origin, const NzVector3& Direction) +{ + Set(Origin, Direction); +} + +template +T NzRay::ClosestPoint(const NzVector3& point) const +{ + NzVector3 delta = point - origin; + T vsq = direction.GetSquaredLength(); + T proj = delta.DotProduct(direction); + + return proj/vsq; +} + +template +NzVector3 NzRay::GetPoint(T lambda) const +{ + return origin + lambda * direction; +} + +template +bool NzRay::Intersect(const NzBoundingVolume& volume, T* closestHit, T* furthestHit) const +{ + switch (volume.extend) + { + case nzExtend_Finite: + { + if (Intersect(volume.aabb)) + return Intersect(volume.obb, closestHit, furthestHit); + + return false; + } + + case nzExtend_Infinite: + { + if (closestHit) + *closestHit = F(0.0); + + if (furthestHit) + *furthestHit = std::numeric_limits::infinity(); + + return true; + } + + case nzExtend_Null: + return false; + } + + NazaraError("Invalid extend type (0x" + NzString::Number(volume.extend, 16) + ')'); + return false; +} + +template +bool NzRay::Intersect(const NzBox& box, T* closestHit, T* furthestHit) const +{ + // http://www.gamedev.net/topic/429443-obb-ray-and-obb-plane-intersection/ + T tfirst = F(0.0); + T tlast = std::numeric_limits::infinity(); + + NzVector3 boxMin = box.GetMinimum(); + NzVector3 boxMax = box.GetMaximum(); + + for (unsigned int i = 0; i < 3; ++i) + { + T dir = direction[i]; + T ori = origin[i]; + T max = boxMax[i]; + T min = boxMin[i]; + + if (NzNumberEquals(dir, F(0.0))) + { + if (ori < max && ori > min) + continue; + + return false; + } + + T tmin = (min - ori) / dir; + T tmax = (max - ori) / dir; + if (tmin > tmax) + std::swap(tmin, tmax); + + if (tmax < tfirst || tmin > tlast) + return false; + + tfirst = std::max(tfirst, tmin); + tlast = std::min(tlast, tmax); + } + + if (closestHit) + *closestHit = tfirst; + + if (furthestHit) + *furthestHit = tlast; + + return true; +} + +template +bool NzRay::Intersect(const NzBox& box, const NzMatrix4& transform, T* closestHit, T* furthestHit) const +{ + // http://www.opengl-tutorial.org/miscellaneous/clicking-on-objects/picking-with-custom-ray-obb-function/ + // Intersection method from Real-Time Rendering and Essential Mathematics for Games + T tMin = F(0.0); + T tMax = std::numeric_limits::infinity(); + + NzVector3 boxMin = box.GetMinimum(); + NzVector3 boxMax = box.GetMaximum(); + NzVector3 delta = transform.GetTranslation() - origin; + + // Test intersection with the 2 planes perpendicular to the OBB's X axis + for (unsigned int i = 0; i < 3; ++i) + { + NzVector3 axis(transform(0, i), transform(1, i), transform(2, i)); + T e = axis.DotProduct(delta); + T f = direction.DotProduct(axis); + + if (!NzNumberEquals(f, F(0.0))) + { + T t1 = (e + boxMin[i]) / f; // Intersection with the "left" plane + T t2 = (e + boxMax[i]) / f; // Intersection with the "right" plane + // t1 and t2 now contain distances betwen ray origin and ray-plane intersections + + // We want t1 to represent the nearest intersection, + // so if it's not the case, invert t1 and t2 + if (t1 > t2) + std::swap(t1, t2); + + // tMax is the nearest "far" intersection (amongst the X,Y and Z planes pairs) + if (t2 < tMax) + tMax = t2; + + // tMin is the farthest "near" intersection (amongst the X,Y and Z planes pairs) + if (t1 > tMin) + tMin = t1; + + // And here's the trick : + // If "far" is closer than "near", then there is NO intersection. + if (tMax < tMin) + return false; + } + else + // Rare case : the ray is almost parallel to the planes, so they don't have any "intersection" + if (-e + boxMin[i] > F(0.0) || -e + boxMax[i] < F(0.0)) + return false; + } + + if (closestHit) + *closestHit = tMin; + + if (furthestHit) + *furthestHit = tMax; + + return true; +} + +template +bool NzRay::Intersect(const NzOrientedBox& orientedBox, T* closestHit, T* furthestHit) const +{ + NzVector3 corner = orientedBox.GetCorner(nzBoxCorner_FarLeftBottom); + NzVector3 oppositeCorner = orientedBox.GetCorner(nzBoxCorner_NearRightTop); + + NzVector3 width = (orientedBox.GetCorner(nzBoxCorner_NearLeftBottom) - corner); + NzVector3 height = (orientedBox.GetCorner(nzBoxCorner_FarLeftTop) - corner); + NzVector3 depth = (orientedBox.GetCorner(nzBoxCorner_FarRightBottom) - corner); + + // Construction de la matrice de transformation de l'OBB + NzMatrix4 matrix(width.x, height.x, depth.x, corner.x, + width.y, height.y, depth.y, corner.y, + width.z, height.z, depth.z, corner.z, + F(0.0), F(0.0), F(0.0), F(1.0)); + + matrix.InverseAffine(); + + corner = matrix.Transform(corner); + oppositeCorner = matrix.Transform(oppositeCorner); + + NzBox tmpBox(corner, oppositeCorner); + NzRay tmpRay(matrix.Transform(origin), matrix.Transform(direction)); + + return tmpRay.Intersect(tmpBox, closestHit, furthestHit); +} + +template +bool NzRay::Intersect(const NzPlane& plane, T* hit) const +{ + T divisor = plane.normal.DotProduct(direction); + if (NzNumberEquals(divisor, F(0.0))) + return false; // perpendicular + + T lambda = -(plane.normal.DotProduct(origin) - plane.distance) / divisor; // The plane is ax + by + cz = d + if (lambda < F(0.0)) + return false; // The plane is 'behind' the ray. + + if (hit) + *hit = lambda; + + return true; +} + +template +bool NzRay::Intersect(const NzSphere& sphere, T* closestHit, T* furthestHit) const +{ + NzVector3 sphereRay = sphere.GetPosition() - origin; + T length = sphereRay.DotProduct(direction); + + if (length < F(0.0)) + return false; // ray is perpendicular to the vector origin - center + + T squaredDistance = sphereRay.GetSquaredLength() - length*length; + T squaredRadius = sphere.radius*sphere.radius; + + if (squaredDistance > squaredRadius) + return false; // if the ray is further than the radius + + // Calcul des points d'intersection si besoin + if (closestHit || furthestHit) + { + T deltaLambda = std::sqrt(squaredRadius - squaredDistance); + + if (closestHit) + *closestHit = length - deltaLambda; + + if (furthestHit) + *furthestHit = length + deltaLambda; + } + + return true; +} + +template +NzRay& NzRay::MakeAxisX() +{ + return Set(NzVector3::Zero(), NzVector3::UnitX()); +} + +template +NzRay& NzRay::MakeAxisY() +{ + return Set(NzVector3::Zero(), NzVector3::UnitY()); +} + +template +NzRay& NzRay::MakeAxisZ() +{ + return Set(NzVector3::Zero(), NzVector3::UnitZ()); +} + +template +NzRay& NzRay::Set(T X, T Y, T Z, T directionX, T directionY, T directionZ) +{ + direction.Set(directionX, directionY, directionZ); + origin.Set(X, Y, Z); + + return *this; +} + +template +NzRay& NzRay::Set(const T Origin[3], const T Direction[3]) +{ + direction.Set(Direction); + origin.Set(Origin); + + return *this; +} + +template +NzRay& NzRay::Set(const NzPlane& planeOne, const NzPlane& planeTwo) +{ + T termOne = planeOne.normal.GetLength(); + T termTwo = planeOne.normal.DotProduct(planeTwo.normal); + T termFour = planeTwo.normal.GetLength(); + T det = termOne * termFour - termTwo * termTwo; + + #if NAZARA_MATH_SAFE + if (NzNumberEquals(det, F(0.0))) + { + NzString error("Planes are parallel."); + + NazaraError(error); + throw std::domain_error(error); + } + #endif + + T invdet = F(1.0) / det; + T fc0 = (termFour * -planeOne.distance + termTwo * planeTwo.distance) * invdet; + T fc1 = (termOne * -planeTwo.distance + termTwo * planeOne.distance) * invdet; + + direction = planeOne.normal.CrossProduct(planeTwo.normal); + origin = planeOne.normal * fc0 + planeTwo.normal * fc1; + + return *this; +} + +template +NzRay& NzRay::Set(const NzRay& ray) +{ + std::memcpy(this, &ray, sizeof(NzRay)); + + return *this; +} + +template +NzRay& NzRay::Set(const NzVector3& Origin, const NzVector3& Direction) +{ + direction = Direction; + origin = Origin; + + return *this; +} + +template +template +NzRay& NzRay::Set(const NzRay& ray) +{ + direction.Set(ray.direction); + origin.Set(ray.origin); + + return *this; +} + +template +template +NzRay& NzRay::Set(const NzVector3& Origin, const NzVector3& Direction) +{ + direction.Set(Direction); + origin.Set(Origin); + + return *this; +} + +template +NzString NzRay::ToString() const +{ + NzStringStream ss; + + return ss << "Ray(origin: " << origin.ToString() << ", direction: " << direction.ToString() << ")"; +} + +template +NzVector3 NzRay::operator*(T lambda) const +{ + return GetPoint(lambda); +} + +template +bool NzRay::operator==(const NzRay& ray) const +{ + return direction == ray.direction && origin == ray.origin; +} + +template +bool NzRay::operator!=(const NzRay& ray) const +{ + return !operator==(ray); +} + +template +NzRay NzRay::AxisX() +{ + NzRay axis; + axis.MakeAxisX(); + + return axis; +} + +template +NzRay NzRay::AxisY() +{ + NzRay axis; + axis.MakeAxisY(); + + return axis; +} + +template +NzRay NzRay::AxisZ() +{ + NzRay axis; + axis.MakeAxisZ(); + + return axis; +} + +template +NzRay NzRay::Lerp(const NzRay& from, const NzRay& to, T interpolation) +{ + return NzRay(from.origin.Lerp(to.origin, interpolation), from.direction.Lerp(to.direction, interpolation)); +} + +template +std::ostream& operator<<(std::ostream& out, const NzRay& ray) +{ + return out << ray.ToString(); +} + +#undef F + +#include