/*
**  ClanLib SDK
**  Copyright (c) 1997-2020 The ClanLib Team
**
**  This software is provided 'as-is', without any express or implied
**  warranty.  In no event will the authors be held liable for any damages
**  arising from the use of this software.
**
**  Permission is granted to anyone to use this software for any purpose,
**  including commercial applications, and to alter it and redistribute it
**  freely, subject to the following restrictions:
**
**  1. The origin of this software must not be misrepresented; you must not
**     claim that you wrote the original software. If you use this software
**     in a product, an acknowledgment in the product documentation would be
**     appreciated but is not required.
**  2. Altered source versions must be plainly marked as such, and must not be
**     misrepresented as being the original software.
**  3. This notice may not be removed or altered from any source distribution.
**
**  Note: Some of the libraries ClanLib may link to may have additional
**  requirements or restrictions.
**
**  File Author(s):
**
**    Magnus Norddahl
*/

#include "Core/precomp.h"
#include "API/Core/Math/intersection_test.h"
#include "API/Core/Math/aabb.h"
#include "API/Core/Math/obb.h"
#include "API/Core/Math/frustum_planes.h"

namespace clan
{
	IntersectionTest::Result IntersectionTest::plane_aabb(const Vec4f &plane, const AxisAlignedBoundingBox &aabb)
	{
		Vec3f center = aabb.center();
		Vec3f extents = aabb.extents();
		float e = extents.x * std::abs(plane.x) + extents.y * std::abs(plane.y) + extents.z * std::abs(plane.z);
		float s = center.x * plane.x + center.y * plane.y + center.z * plane.z + plane.w;
		if (s - e > 0)
			return inside;
		else if (s + e < 0)
			return outside;
		else
			return intersecting;
	}

	IntersectionTest::Result IntersectionTest::plane_obb(const Vec4f &plane, const OrientedBoundingBox &obb)
	{
		Vec3f n(plane);
		float d = plane.w;
		float e = obb.extents.x * std::abs(Vec3f::dot(obb.axis_x, n)) + obb.extents.y * std::abs(Vec3f::dot(obb.axis_y, n)) + obb.extents.z * std::abs(Vec3f::dot(obb.axis_z, n));
		float s = Vec3f::dot(obb.center, n) + d;
		if (s - e > 0)
			return inside;
		else if (s + e < 0)
			return outside;
		else
			return intersecting;
	}

	IntersectionTest::OverlapResult IntersectionTest::sphere(const Vec3f &center1, float radius1, const Vec3f &center2, float radius2)
	{
		Vec3f h = center1 - center2;
		float square_distance = Vec3f::dot(h, h);
		float radius_sum = radius1 + radius2;
		if (square_distance > radius_sum * radius_sum)
			return disjoint;
		else
			return overlap;
	}

	IntersectionTest::OverlapResult IntersectionTest::sphere_aabb(const Vec3f &center, float radius, const AxisAlignedBoundingBox &aabb)
	{
		Vec3f a = aabb.aabb_min - center;
		Vec3f b = center - aabb.aabb_max;
		a.x = max(a.x, 0.0f);
		a.y = max(a.y, 0.0f);
		a.z = max(a.z, 0.0f);
		b.x = max(b.x, 0.0f);
		b.y = max(b.y, 0.0f);
		b.z = max(b.z, 0.0f);
		Vec3f e = a + b;
		float d = Vec3f::dot(e, e);
		if (d > radius * radius)
			return disjoint;
		else
			return overlap;
	}

	IntersectionTest::OverlapResult IntersectionTest::aabb(const AxisAlignedBoundingBox &a, const AxisAlignedBoundingBox &b)
	{
		if (a.aabb_min.x > b.aabb_max.x || b.aabb_min.x > a.aabb_max.x ||
			a.aabb_min.y > b.aabb_max.y || b.aabb_min.y > a.aabb_max.y ||
			a.aabb_min.z > b.aabb_max.z || b.aabb_min.z > a.aabb_max.z)
		{
			return disjoint;
		}
		else
		{
			return overlap;
		}
	}

	IntersectionTest::Result IntersectionTest::frustum_aabb(const FrustumPlanes &frustum, const AxisAlignedBoundingBox &box)
	{
		bool is_intersecting = false;
		for (int i = 0; i < 6; i++)
		{
			Result result = plane_aabb(frustum.planes[i], box);
			if (result == outside)
				return outside;
			else if (result == intersecting)
				is_intersecting = true;
			break;
		}
		if (is_intersecting)
			return intersecting;
		else
			return inside;
	}

	IntersectionTest::Result IntersectionTest::frustum_obb(const FrustumPlanes &frustum, const OrientedBoundingBox &box)
	{
		bool is_intersecting = false;
		for (int i = 0; i < 6; i++)
		{
			Result result = plane_obb(frustum.planes[i], box);
			if (result == outside)
				return outside;
			else if (result == intersecting)
				is_intersecting = true;
		}
		if (is_intersecting)
			return intersecting;
		else
			return inside;
	}

	IntersectionTest::OverlapResult IntersectionTest::ray_aabb(const Vec3f &ray_start, const Vec3f &ray_end, const AxisAlignedBoundingBox &aabb)
	{
		Vec3f c = (ray_start + ray_end) * 0.5f;
		Vec3f w = ray_end - c;
		Vec3f h = aabb.extents();

		c -= aabb.center();

		Vec3f v(std::abs(w.x), std::abs(w.y), std::abs(w.z));

		if (std::abs(c.x) > v.x + h.x || std::abs(c.y) > v.y + h.y || std::abs(c.z) > v.z + h.z)
			return disjoint;

		if (std::abs(c.y * w.z - c.z * w.y) > h.y * v.z + h.z * v.y ||
			std::abs(c.x * w.z - c.z * w.x) > h.x * v.z + h.z * v.x ||
			std::abs(c.x * w.y - c.y * w.x) > h.x * v.y + h.y * v.x)
			return disjoint;

		return overlap;
	}
}
