/* 
 * 
 * Confidential Information of Telekinesys Research Limited (t/a Havok). Not for disclosure or distribution without Havok's
 * prior written consent. This software contains code, techniques and know-how which is confidential and proprietary to Havok.
 * Level 2 and Level 3 source code contains trade secrets of Havok. Havok Software (C) Copyright 1999-2009 Telekinesys Research Limited t/a Havok. All Rights Reserved. Use of this software is subject to the terms of an end user license agreement.
 * 
 */


#ifndef HK_COLLIDE2_COLLISION_DISPATCHER_H
#define HK_COLLIDE2_COLLISION_DISPATCHER_H

#include <Physics/Collide/Agent/Collidable/hkpCdBody.h>
#include <Physics/Collide/Shape/hkpShape.h>
#include <Physics/Internal/Collide/Agent3/hkpAgent3.h>
#include <Physics/Collide/Agent/Collidable/hkpCollidableQualityType.h>
#include <Physics/Collide/Agent/hkpCollisionQualityInfo.h>
#include <Physics/Collide/Agent/hkpCollisionInput.h>


class hkpCollidable;
class hkpCollisionAgent;
class hkpContactMgr;
struct hkpCollisionInput;
class hkpContactMgrFactory;
class hkpCdPointCollector;
enum hkpShapeType;



class hkpCdPointCollector;
class hkpCdBodyPairCollector;
struct hkpLinearCastCollisionInput;

enum hkpCollisionDispatcherAgentType
{
	HK_AGENT_TYPE_NULL = 0,
	HK_AGENT_TYPE_BRIDGE = 1
};


	/// This class is used to find the correct function which performs collision detection between pairs of shapes.
	/// It's based on the following rules:
	///  - Each shapes has a type
	///  - For each pair of types, there must be a function registered to handle this pair of types
	///
	/// However implementing all combinations would be quite complex. Therefore we allow for:
	///  - shapes to actually subclass other more basic shapes (e.g. a box is a convex shape)
	///    and then reuse a common base function
	///  - functions, which work on a pair of shapes, where only one shape type is used (e.g. hkpTransformShape, hkpBvTreeShape)
	///    and the other ignored (HK_SHAPE_ALL).
	///    in this case we have to register a function with a pair of types ( HK_SHAPE_MY_TYPE, HK_SHAPE_ALL )
	/// 
	/// To implement this behavior, the user has to
	///  - first declare the shape inheritance hierarchy by calling registerAlternateShapeType for each inheritance link
	///  - register all functions which handle collisions between two shape types.
	/// 
	/// Note: You still cannot call registerAlternateShapeType after you registered your first agent
	/// <br>
	/// Also this class can dispatch hkpContactMgrFactory s. This dispatch is quite simple as there is no inheritance.
	/// Also this class combines two hkCollidableQualityTypes into a hkCdInteractionQualtityType
	/// <br>
	/// In addition to that, this class also dispatches a pair of hkpCollidableQualityType into a hkpCollisionQualityInfo structure
class hkpCollisionDispatcher : public hkReferencedObject
{
	public:
		enum {
			HK_MAX_RESPONSE_TYPE = 8,
			HK_MAX_SHAPE_TYPE    = 32,
			HK_MAX_AGENT2_TYPES  = 64,
			HK_MAX_AGENT3_TYPES = 16,
			HK_MAX_COLLISION_QUALITIES = 8
		};
	public:

		HK_DECLARE_CLASS_ALLOCATOR(HK_MEMORY_CLASS_CDINFO);

			/// A function to create a collision agent
		typedef hkpCollisionAgent* (HK_CALL *CreateFunc)(const hkpCdBody& collA, const hkpCdBody& collB, const hkpCollisionInput& env, hkpContactMgr* mgr);

			/// A function to check whether two objects are penetrating
		typedef void (HK_CALL *GetPenetrationsFunc)     (const hkpCdBody& bodyA, const hkpCdBody& bodyB, const hkpCollisionInput& input, hkpCdBodyPairCollector& collector );

			/// A function to find the closest points
		typedef void (HK_CALL *GetClosestPointsFunc)   (const hkpCdBody& bodyA, const hkpCdBody& bodyB, const hkpCollisionInput& input, hkpCdPointCollector& output );

			/// A function to do linear casts
		typedef void (HK_CALL *LinearCastFunc)         (const hkpCdBody& bodyA, const hkpCdBody& bodyB, const hkpLinearCastCollisionInput& input, hkpCdPointCollector& castCollector, hkpCdPointCollector* startCollector );

			/// For backward compatibility
		typedef CreateFunc CollisionAgentCreationFunction;

		typedef hkChar CollisionQualityIndex;

			/// A helper struct, which is used as an input to registerCollisionAgent
		struct AgentFuncs
		{
			HK_DECLARE_NONVIRTUAL_CLASS_ALLOCATOR( HK_MEMORY_CLASS_COLLIDE, AgentFuncs );

				/// The agent creation functions
			CreateFunc           m_createFunc;

				/// A static penetration function
			GetPenetrationsFunc  m_getPenetrationsFunc;

				/// A static get closest distance functions
			GetClosestPointsFunc m_getClosestPointFunc;

				/// A static linear cast function
			LinearCastFunc       m_linearCastFunc;

				/// A flag telling the system, whether a symmetric agent is involved.
				/// If the collision detector sees the flag to be set to true, it tries
				/// to swap objectA with objectB
			hkBool				 m_isFlipped;

				/// A flag indicating whether the object can handle continuous collision
			hkBool				 m_isPredictive;

			AgentFuncs() : m_isFlipped(false), m_isPredictive(false) {}
		};

			/// A helper struct, which is used as an input to registerCollisionAgent
			/// for streamed agents. Read hkAgent3 for details
		struct Agent3Funcs
		{
			HK_DECLARE_NONVIRTUAL_CLASS_ALLOCATOR( HK_MEMORY_CLASS_COLLIDE, Agent3Funcs );
			Agent3Funcs() : m_isPredictive(false), m_ignoreSymmetricVersion(false), m_reusePreviousEntry(false)
			{
				m_updateFilterFunc	= HK_NULL;
				m_calcStatisticsFunc= HK_NULL;
				m_invalidateTimFunc = HK_NULL;
				m_warpTimeFunc		= HK_NULL;
				m_sepNormalFunc		= HK_NULL;
			}

			HK_ALIGN16(hkAgent3::CreateFunc      m_createFunc);
				// Called when the agent is destoyed
			hkAgent3::DestroyFunc     m_destroyFunc;
				// Called when the agent has all points removed.
				// Info: This is only used when you have Tim's -- and there fore m_sepNormalFunc assigned.
			hkAgent3::CleanupFunc     m_cleanupFunc;
				// Point remove/commit/zombie functions used for welding
			hkAgent3::RemovePointFunc m_removePointFunc;
			hkAgent3::CommitPotentialFunc m_commitPotentialFunc;
			hkAgent3::CreateZombieFunc  m_createZombieFunc;
				// Should be implemented for all agents
			hkAgent3::UpdateFilterFunc  m_updateFilterFunc;
			hkAgent3::CalcStatisticsFunc  m_calcStatisticsFunc;
			hkAgent3::InvalidateTimFunc m_invalidateTimFunc;
			hkAgent3::WarpTimeFunc      m_warpTimeFunc;
				// Only needed if the agent uses Tim's
			hkAgent3::SepNormalFunc     m_sepNormalFunc;
			hkAgent3::ProcessFunc       m_processFunc;
			hkBool						m_isPredictive;
			hkBool						m_ignoreSymmetricVersion;
			hkBool						m_reusePreviousEntry;
		};

		struct InitCollisionQualityInfo
		{
			HK_DECLARE_NONVIRTUAL_CLASS_ALLOCATOR( HK_MEMORY_CLASS_COLLIDE, InitCollisionQualityInfo );

			hkReal m_gravityLength;
			hkReal m_collisionTolerance;
			hkReal m_minDeltaTime;
			hkReal m_maxLinearVelocity;
			hkReal m_numToisTillAllowedPenetrationSimplifiedToi;
			hkReal m_numToisTillAllowedPenetrationToi;
			hkReal m_numToisTillAllowedPenetrationToiHigher;
			hkReal m_numToisTillAllowedPenetrationToiForced;
			hkUint16 m_defaultConstraintPriority;
			hkUint16 m_toiConstraintPriority;
			hkUint16 m_toiHigherConstraintPriority;
			hkUint16 m_toiForcedConstraintPriority;
			hkBool m_wantContinuousCollisionDetection;
			hkBool m_enableNegativeManifoldTims;
			hkBool m_enableNegativeToleranceToCreateNon4dContacts;
			hkBool m_enableToiWeldRejection;
		};


		enum TableType
		{
			TABLE_TYPE_AGENT2,
			TABLE_TYPE_AGENT2_PRED,
			TABLE_TYPE_AGENT3,
			TABLE_TYPE_AGENT3_PRED
		};

		enum IsAgentPredictive
		{
			IS_NOT_PREDICTIVE,
			IS_PREDICTIVE
		};



	public:

			/// create the collision dispatcher
		hkpCollisionDispatcher( CreateFunc defaultCreationFunction, hkpContactMgrFactory* defaultContactMgrFactory );

			/// delete it
		~hkpCollisionDispatcher();


			//
			//	Shape Types
			//


			/// register a shape type and a possible alternative shape type, which
			/// is used if the first type does not give a valid answer
			/// Note: you should register all alternate shapes BEFORE you can register the collision
			/// agent creation functions (otherwise it gets very CPU intensive).<br>
			/// <br>
			/// Background:<br>
			/// Since havok2.2, the inheritance of shapes is defined in the collision dispatcher,
			/// not in the shapes. That means before registering agents, all shape types and their
			/// alternate(=superClass) type needs to be registered.
			/// - Example: A triangle is a convex shape, so in order for the convex agent to 
			///   pick up a triangle the following lines must precede the registration of the convex
			///   agent: dispatcher->registerAlternateShapeTypes( HK_SHAPE_TRIANGLE, HK_SHAPE_CONVEX );
		void registerAlternateShapeType( hkpShapeType primaryType, hkpShapeType alternateType );


			/// returns true, if type has a path to alternateType or type == alternateType
			/// Note: This function is really fast
		inline hkBool32 hasAlternateType( hkpShapeType type, hkpShapeType alternateType );
			

			//
			//	Agents
			//


			/// register an agent for a given pair of shape types, use HK_SHAPE_ALL for one type if you want to register
			/// the other type with all agents.
			/// Later calls to this functions override existing values, that means later calls automatically have a higher priority.
			///  - Note: You should register all shape alternate Types before you call registerCollisionAgent()
			///  - Note: A call to this function is slow
			///  - Note: Agent using the streaming technology must be registered just after the non
			///          streaming version is registered
			/// 
			/// Build in checks:<br>
			/// Example: Say you register a fast sphere-sphere agent first and than you register a slow convex-convex agent.
			///			 As later registrations overrides earlier, the convex-convex agent will actually override the
			///			 sphere-sphere one (given that there is a rule that a sphere is a convex object). Bad :-( <br>
			///          Unfortunately this  wrong registration is usually difficult to find.<br>
			///
			///	Solution:
			///  - Currently the program checks, if a more general agent overrides an more specialized agent and asserts.
			///    However in some cases the check does not work, so you can disable it by calling setEnableChecks(false). 
			///  - Call debugPrintTable() to get a text file which shows you check the contents of the table by hand
		void registerCollisionAgent( AgentFuncs& f, hkpShapeType typeA, hkpShapeType typeB );

			/// Register an agent with the collision dispatcher. Does not register bridge agent.
			/// This means this agent will only be used
			/// - if static collision queries are used or
			/// - if agent is used by a phantom or
			/// - if the agent is owned by a transform agent
		void registerCollisionAgent2( AgentFuncs& f, hkpShapeType typeA, hkpShapeType typeB );


			/// disable checks if you want to force override existing entries when calling registerCollisionAgent.
			/// Don't forget to re-enabling the checks, once you have done your unchecked operations.
		void setEnableChecks( hkBool checkEnabled);

			/// Debug print of the current table entries
		void debugPrintTable();

			/// Disables debugging. This should be done at the end of registering all agents to save some memory.
			/// Note: Checking is only enabled in debug
		void disableDebugging();


			/// returns a new collision agent for a collidable pair
		HK_FORCE_INLINE hkpCollisionAgent* getNewCollisionAgent(	const hkpCdBody& a, const hkpCdBody& b, const hkpCollisionInput& env, hkpContactMgr* mgr) const;

			/// get the agent creation function registered for a given pair. 
		HK_FORCE_INLINE CreateFunc getCollisionAgentCreationFunction( hkpShapeType typeA, hkpShapeType typeB, IsAgentPredictive predictive ) const;

			/// get the functions, which allows you to query two collision collidables for penetration cases
		HK_FORCE_INLINE GetPenetrationsFunc getGetPenetrationsFunc( hkpShapeType typeA, hkpShapeType typeB ) const;

			/// get the functions, which allows you to query two collision collidables for penetration cases
		HK_FORCE_INLINE GetClosestPointsFunc getGetClosestPointsFunc( hkpShapeType typeA, hkpShapeType typeB ) const;

			/// get the functions, which allows you to query two collision collidables for penetration cases
		HK_FORCE_INLINE LinearCastFunc getLinearCastFunc( hkpShapeType typeA, hkpShapeType typeB ) const;

		HK_FORCE_INLINE hkBool getIsFlipped( hkpShapeType typeA, hkpShapeType typeB ) const;


			/// Same as registerCollisionAgent, but for the hkAgent3 technology
			/// if reusePreviousEntry is set to true, than f is ignored and the f of previous call to registerAgent3 is used
		int registerAgent3( Agent3Funcs& f, hkpShapeType typeA, hkpShapeType typeB );

		HK_FORCE_INLINE hkAgent3::AgentType getAgent3Type( hkpShapeType typeA, hkpShapeType typeB, hkBool32 predicitve ) const;

		HK_FORCE_INLINE hkAgent3::CreateFunc           getAgent3CreateFunc  ( hkAgent3::AgentType type );
		HK_FORCE_INLINE hkAgent3::DestroyFunc          getAgent3DestroyFunc ( hkAgent3::AgentType type );
		HK_FORCE_INLINE hkAgent3::CleanupFunc          getAgent3CleanupFunc ( hkAgent3::AgentType type );
		HK_FORCE_INLINE hkAgent3::RemovePointFunc      getAgent3RemovePointFunc ( hkAgent3::AgentType type );
		HK_FORCE_INLINE hkAgent3::CommitPotentialFunc  getAgent3CommitPotentialFunc ( hkAgent3::AgentType type );
		HK_FORCE_INLINE hkAgent3::CreateZombieFunc     getAgent3CreateZombieFunc ( hkAgent3::AgentType type );
		HK_FORCE_INLINE hkAgent3::UpdateFilterFunc     getAgent3UpdateFilterFunc ( hkAgent3::AgentType type ) const;
		HK_FORCE_INLINE hkAgent3::CalcStatisticsFunc   getAgent3CalcStatisticsFunc ( hkAgent3::AgentType type ) const;
		HK_FORCE_INLINE hkAgent3::InvalidateTimFunc    getAgent3InvalidateTimFunc ( hkAgent3::AgentType type );
		HK_FORCE_INLINE hkAgent3::WarpTimeFunc	       getAgent3WarpTimeFunc ( hkAgent3::AgentType type );
		HK_FORCE_INLINE hkAgent3::ProcessFunc          getAgent3ProcessFunc ( hkAgent3::AgentType type );
		HK_FORCE_INLINE hkAgent3::SepNormalFunc        getAgent3SepNormalFunc( hkAgent3::AgentType type );
		HK_FORCE_INLINE hkAgent3::Symmetric            getAgent3Symmetric( hkAgent3::AgentType type );
			//
			//	Contact Mgrs
			//

			/// register a contact factory
		void registerContactMgrFactory( hkpContactMgrFactory*, int responseA, int responseB );


			/// Get a contact manager factory - used to create a contact manager.
		HK_FORCE_INLINE hkpContactMgrFactory* getContactMgrFactory(int responseA, int responseB) const;

			/// register a contact factory with all other types: Note overrides existing entries
		void registerContactMgrFactoryWithAll( hkpContactMgrFactory*, int responseA );


			//
			//	hkpCollisionQualityInfo
			//
		HK_FORCE_INLINE CollisionQualityIndex getCollisionQualityIndex( hkpCollidableQualityType a, hkpCollidableQualityType b);

		HK_FORCE_INLINE hkpCollisionQualityInfo* getCollisionQualityInfo( CollisionQualityIndex index );
		
		void initCollisionQualityInfo( InitCollisionQualityInfo& input );
		
			// hkReferencedObject interface implementation
		void calcContentStatistics( hkStatisticsCollector* collector, const hkClass* cls ) const;

	public:

		struct ShapeInheritance
		{
			HK_DECLARE_NONVIRTUAL_CLASS_ALLOCATOR( HK_MEMORY_CLASS_COLLIDE, ShapeInheritance );

			hkpShapeType m_primaryType;
			hkpShapeType m_alternateType;
		};

		struct AgentEntry
		{
			HK_DECLARE_NONVIRTUAL_CLASS_ALLOCATOR( HK_MEMORY_CLASS_COLLIDE, AgentEntry );

			hkpShapeType m_typeA;
			hkpShapeType m_typeB;
			hkBool m_checkEnabled;
			AgentFuncs  m_createFunc;
		};

		struct Agent3Entry
		{
			HK_DECLARE_NONVIRTUAL_CLASS_ALLOCATOR( HK_MEMORY_CLASS_COLLIDE, Agent3Entry );

			hkpShapeType m_typeA;
			hkpShapeType m_typeB;
			hkBool m_checkEnabled;
			Agent3Funcs  m_funcs;
		};

			// A helper struct, which is used as an input to registerFullAgent
		struct Agent3FuncsIntern: public Agent3Funcs
		{
			HK_DECLARE_NONVIRTUAL_CLASS_ALLOCATOR( HK_MEMORY_CLASS_COLLIDE, Agent3FuncsIntern );

			hkAgent3::Symmetric       m_symmetric;
		};

		struct DebugEntry
		{
			HK_DECLARE_NONVIRTUAL_CLASS_ALLOCATOR( HK_MEMORY_CLASS_COLLIDE, DebugEntry );

				/// Shape Type A
			char m_typeA;
				/// Shape Type B
			char m_typeB;
				
				/// The sum of all shape rules which have to be applied to get to this entry.
				/// E.g. we have two spheres (m_typeAB = HK_SHAPE_SPHERE)
				///  - if we register a hkSphereSphere Agent, no indirections are needed and the priority is 0
				///  - if we register a hkSphereConvex Agent, 1 indirections is needed and the priority is 1
				///  - if we register a hkConvexConvex Agent, 2 indirections are needed and the priority is 2
				///  - if we register a hkSphere-All Agent, 1 indirections is needed and the priority is 1
			char m_priority;
		};

		typedef DebugEntry	DebugTable[HK_MAX_SHAPE_TYPE][HK_MAX_SHAPE_TYPE];


	protected:
		void internalRegisterCollisionAgent( hkUchar agentTypesTable[HK_MAX_SHAPE_TYPE][HK_MAX_SHAPE_TYPE], int agentType, hkpShapeType typeA, hkpShapeType typeB, hkpShapeType origA, hkpShapeType origB, DebugTable *debugTable, int recursionDepth );

		void updateHasAlternateType( hkpShapeType primaryType, hkpShapeType alternateType, int depth );

		void resetCreationFunctions();


	public:

				/// Collision quality setting for a collision between a pair of objects
			// values must be corresponding to hkpConstraintInstance::ConstraintPriority
		enum CollisionQualityLevel
		{
				/// Invalid.
			COLLISION_QUALITY_INVALID,

				/// Use this to get discrete stepping behavior or for fixed objects.
			COLLISION_QUALITY_PSI,

				/// Use this to avoid fast-moving debris object penetrating through the ground and other static objects.
				/// This only backsteps the debris objects.
			COLLISION_QUALITY_SIMPLIFIED_TOI,

				/// Application: Ensure non-penetration between pairs of non-fixed objects
				/// Use this if you want to prevent bullet through paper
			COLLISION_QUALITY_TOI,

				/// Same as above.
				/// Application: Ensure non-penetration between moving objects & fixed objects
				/// Tech. Info: This ensures constraints to be treated as 'higher-priority' in PSI solver
			COLLISION_QUALITY_TOI_HIGHER,

				/// Application: Ensure and monitor non-penetration between moving objects & fixed objects.
				///              You receive callbacks when penetration occurs.
				/// Tech. Info: This ensures constraints to be treated as 'higher-priority' in PSI & TOI solvers
				///             plus 'forced' during TOI handling
			COLLISION_QUALITY_TOI_FORCED,

				/// This quality is the same as COLLISION_QUALITY_TOI_FORCED with some additional tolerances
				/// set to enable more accurate generation of contact points.
			COLLISION_QUALITY_CHARACTER,

				/// Only used internally for expanding contact manifold in hkpContinuousSimulation::updateManifold().
				///	Will try to collect as many contact points as possible.
			COLLISION_QUALITY_TMP_EXPAND_MANIFOLD

		};


		CreateFunc	m_defaultCollisionAgent;

		hkpContactMgrFactory*	m_contactMgrFactory[HK_MAX_RESPONSE_TYPE][HK_MAX_RESPONSE_TYPE];

			/// for each shapeType T,  m_hasAlternateType[T] has a bit set for each superclass
		HK_ALIGN16(hkUint32				m_hasAlternateType[ HK_MAX_SHAPE_TYPE ]);

			/// CollidableType x collidableType x {0, Pred} -> agentType dispatching for agent2 & agent3 technology.
			///  - m_agent2Types hold the proper agent's index for a given collision pair for non predictive collision detection
		    ///  - m_agent2TypesPred .................................................... for predictive collision detection
		int						m_numAgent2Types;
		HK_ALIGN16(hkUchar	    m_agent2Types    [HK_MAX_SHAPE_TYPE][HK_MAX_SHAPE_TYPE]);
		hkUchar				    m_agent2TypesPred[HK_MAX_SHAPE_TYPE][HK_MAX_SHAPE_TYPE];
		AgentFuncs				m_agent2Func     [ HK_MAX_AGENT2_TYPES ];

		int						m_numAgent3Types;
		HK_ALIGN16(hkUchar		m_agent3Types    [HK_MAX_SHAPE_TYPE][HK_MAX_SHAPE_TYPE]);
		HK_ALIGN16(hkUchar		m_agent3TypesPred[HK_MAX_SHAPE_TYPE][HK_MAX_SHAPE_TYPE]);
		Agent3FuncsIntern       m_agent3Func     [ HK_MAX_AGENT3_TYPES ];

			/// CollidableQuality x collidableQuality -> collisionQuality dispatching
		HK_ALIGN16(hkChar				m_collisionQualityTable[ HK_COLLIDABLE_QUALITY_MAX][HK_COLLIDABLE_QUALITY_MAX]);
		HK_ALIGN16(hkpCollisionQualityInfo  m_collisionQualityInfo[ HK_MAX_COLLISION_QUALITIES ]);


			/// This is set to true if there is at least one call to registerCollisionAgent() made
		hkBool m_collisionAgentRegistered;
		hkBool m_agent3Registered;

		hkBool m_midphaseAgent3Registered;

			/// If set, produces an Assert whenever a more-general agent overrides a more-specific agent in dispatch tables.
		hkBool m_checkEnabled;

			/// All alternate shape rules (a simple collection of all registerAlternateShapeType() calls )
		hkArray<ShapeInheritance> m_shapeInheritance;

			/// Additional information to the main tables. This allows for debugging 
			/// a wrong order of registering agents, see DebugEntry
		DebugTable	*m_debugAgent2Table;
		DebugTable	*m_debugAgent2TablePred;
		DebugTable	*m_debugAgent3Table;
		DebugTable	*m_debugAgent3TablePred;

	public:
		//
		// The following two members are only used when you call hkpWorld::getCinfo(...)
		// They're set upon dispatcher initialization.
		//
		hkReal m_expectedMaxLinearVelocity;
		hkReal m_expectedMinPsiDeltaTime;
};



#include <Physics/Collide/Dispatch/hkpCollisionDispatcher.inl>

#endif // HK_COLLIDE2_COLLISION_DISPATCHER_H

/*
* Havok SDK - NO SOURCE PC DOWNLOAD, BUILD(#20090216)
* 
* Confidential Information of Havok.  (C) Copyright 1999-2009
* Telekinesys Research Limited t/a Havok. All Rights Reserved. The Havok
* Logo, and the Havok buzzsaw logo are trademarks of Havok.  Title, ownership
* rights, and intellectual property rights in the Havok software remain in
* Havok and/or its suppliers.
* 
* Use of this software for evaluation purposes is subject to and indicates
* acceptance of the End User licence Agreement for this product. A copy of
* the license is included with this software and is also available at www.havok.com/tryhavok.
* 
*/
