/*
**	Command & Conquer Generals Zero Hour(tm)
**	Copyright 2025 Electronic Arts Inc.
**
**	This program is free software: you can redistribute it and/or modify
**	it under the terms of the GNU General Public License as published by
**	the Free Software Foundation, either version 3 of the License, or
**	(at your option) any later version.
**
**	This program is distributed in the hope that it will be useful,
**	but WITHOUT ANY WARRANTY; without even the implied warranty of
**	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
**	GNU General Public License for more details.
**
**	You should have received a copy of the GNU General Public License
**	along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

/***********************************************************************************************
 ***              C O N F I D E N T I A L  ---  W E S T W O O D  S T U D I O S               ***
 ***********************************************************************************************
 *                                                                                             *
 *                 Project Name : G                                                            *
 *                                                                                             *
 *                     $Archive:: /G/WWMath/normalcone.h                                      $*
 *                                                                                             *
 *                      $Author:: Eric_c                                                      $*
 *                                                                                             *
 *                     $Modtime:: 11/01/99 2:44p                                              $*
 *                                                                                             *
 *                    $Revision:: 8                                                           $*
 *                                                                                             *
 *---------------------------------------------------------------------------------------------*
 * Functions:                                                                                  *
 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

/*
**	The NormalCone is a class used to represent a cone of unit length. It can be used to 
** loosely represent a collection of other normals allowing backface culling to be 
** performed at a heirarchial level rather than at a triangle level.
**
**	The term 'NormalCone' is a bit of a misnomer; it is really a circular portion of a sphere.
** -ehc
*/

#ifndef NORMALCONE_H
#define NORMALCONE_H

#include "vector3.h"
#include "matrix3.h"

class NormalCone : public Vector3
{
	public:

		NormalCone() {}

		NormalCone(const Vector3 & normal, float angle = 1.0f)
		:	Vector3(normal),
			Angle(angle)
		{}

		void Set(const Vector3 & normal, float angle = 1.0f)
		{
			Vector3::Set(normal);
			Angle = angle;
		}

		void Set(const NormalCone & src)
		{
			Set(src, src.Angle);
		}

		// return true if this object has degenerated into a sphere.
		inline bool Complete_Sphere()
		{
			return (Angle - WWMATH_EPSILON) <= -1.0f;
		}

		// find the two vectors on the edge of the cone residing on the same plane as the input vector.
		// Note: these two Get_Coplanar functions return floats in an attempt to reduce float/int CPU state changes... 
		inline float Get_Coplanar_Normals(const Vector3 & Input, Vector3 & Output1, Vector3 & Output2) const;

		// find the two vectors on the edge of the cone residing on the same plane as the input vector and their dot products with the input.
		inline float Get_Coplanar_Normals_And_Dots(const Vector3 & Input, Vector3 & Output1, Vector3 & Output2, float & dot1, float & dot2) const;

		// evaluate the input vector, expanding the angle of the cone and recalculating the
		// new center vector as needed.
		inline void Merge(const Vector3 & Input);

		// merge the input normal cone's coplanar normals with this object.
		inline void Merge(const NormalCone & Input);

		// this function returns the dot product between the input vector and the nearest coplanar normal
		// contained by the cone.
		// If the input vector is also contained by the cone, the result is always 1.0f.
		// Note that in the case of a complete sphere, the nearest coplanar normal will be pointing in 
		// the opposite direction of the input vector. 
		inline float Smallest_Dot_Product(const Vector3 & Input);

		// this value is the dot product of the edge of the cone and the center of the cone.
		// A value of 1.0f indicates that it is a degenerate cone which is basically a cone with radius zero.
		// A value of zero indicates the cone is actually hemisphere.
		// A value of -1.0f indicates that it is a complete sphere.
		float Angle;

};


// find the two vectors on the edge of the cone residing on the same plane as the input vector.
inline float NormalCone::Get_Coplanar_Normals(const Vector3 & Input, Vector3 & Output1, Vector3 & Output2) const
{
	// get the cross product of the existing normal and the new one
	Vector3 cross;
	Vector3::Cross_Product(Input, *this, & cross);

	float length = cross.Length2();
	if(length < WWMATH_EPSILON) 
		return 0.0f;
	
	length = sqrt(length);
	cross /= length;

	// Make a matrix3 which uses it as an axis of rotation and
	// rotate this about that axis twice, once +Angle, once -Angle.
	float radians = (1.0f - Angle) * WWMATH_PI * 0.5f;
	Matrix3 m1(cross, radians);
	Matrix3 m2(cross, -radians);

	Matrix3::Rotate_Vector(m1, *this, & Output1);
	Matrix3::Rotate_Vector(m2, *this, & Output2);

	return length;
}

inline float NormalCone::Get_Coplanar_Normals_And_Dots(const Vector3 & Input, Vector3 & Output1, Vector3 & Output2, float & Dot1, float & Dot2) const
{
	float length = Get_Coplanar_Normals(Input, Output1, Output2);

	if(length < WWMATH_EPSILON) 
		return 0.0f;

	// get the dot products of the new normal with the two coplanar normals and the current average
	Dot1 = Vector3::Dot_Product(Input, Output1);
	Dot2 = Vector3::Dot_Product(Input, Output2);
	return length;
}

// evaluate the input vector, expanding the angle of the cone and recalculating the
// new center vector as needed.
inline void NormalCone::Merge(const Vector3 & Input)
{
	// early exit if this normal cone has already turned into a complete sphere.
	if(Complete_Sphere()) 
		return;

	// get the dot of the new vector with the current center vector
	float dot0 = Vector3::Dot_Product(Input, * this) + WWMATH_EPSILON;

	// if the dot value is greater than the existing cone angle, then the new vector fits
	// within the cone, so return.
	if(dot0 >= Angle)
		return;

	// get the two normals found in the cone which are coplanar to the one passed to this function.
	Vector3 normal1, normal2;
	float dot1, dot2;
	if(Get_Coplanar_Normals_And_Dots(Input, normal1, normal2, dot1, dot2) <= WWMATH_EPSILON)
		return;

	// test the case where the current average has a lower dot than either of the coplanar normals.
	// If true, this means that the object now represents a complete sphere with normals facing every
	// direction.
	if((dot0 < dot1) && (dot0 < dot2))
	{
		Angle = -1;
		return;
	}

	// the smaller of the dot values we have is going to indicate which of the two coplanar normals to use
	// for averaging into the new center normal.
	if(dot1 < dot2) 
		Set(Input + normal1, dot1);
	else
		Set(Input + normal2, dot2);
	
	// if the angle is < 0, reverse the direction of the averaged normal since we have constructed
	// something more like a sphere with a cone shape taken out of it (a negative cone).
	if(Angle < WWMATH_EPSILON)
		*this *= -1;

	Normalize();
}


// merge the input normal cone's coplanar normals with this object.
inline void NormalCone::Merge(const NormalCone & Input)
{
	Vector3 n1, n2;
	
	if(Input.Get_Coplanar_Normals(*this, n1,n2) >= WWMATH_EPSILON)
	{
		Merge(n1);
		Merge(n2);
	}
}

// this function returns the dot product between the input vector and the nearest coplanar normal
// contained by the cone.
// If the input vector is also contained by the cone, the result is always 1.0f.
// Note that in the case of a complete sphere, the nearest coplanar normal will be pointing in 
// the opposite direction of the input vector. 
inline float NormalCone::Smallest_Dot_Product(const Vector3 & Input)
{
	if(Complete_Sphere())
		return -1.0f;

		// get the dot of the new vector with the current center vector
	float dot0 = Vector3::Dot_Product(Input, * this);

	// if the negative dot value is greater than the existing cone angle, then the new vector is
	// parallel to one of the vectors contained in the cone but in negative
	// direction, so return -1.0f
	if(-dot0 + WWMATH_EPSILON >= Angle)
		return -1.0f;

	// if the dot value is greater than the existing cone angle, then the new vector is
	// parallel to one of the vectors contained in the cone, so return 1.0f
	if(dot0 + WWMATH_EPSILON >= Angle)
		return 1.0f;

	// get the two normals found in the cone which are coplanar to the one passed to this function.
	Vector3 normal1, normal2;
	float dot1, dot2;
	Get_Coplanar_Normals_And_Dots(Input, normal1, normal2, dot1, dot2);

	// return the smaller of the two dot products
	if(dot1 < dot2)
		return dot1;
	return dot2;
}

#endif
