text: /*************************************************************************
  > File Name: ParticleSystemSolver3.h
  > Project Name: CubbyFlow
  > Author: Chan-Ho Chris Ohk
  > Purpose: Basic 3-D particle system solver.
  > Created Time: 2017/05/21
  > Copyright (c) 2018, Chan-Ho Chris Ohk
  *************************************************************************/
  #ifndef CUBBYFLOW_PARTICLE_SYSTEM_SOLVER3_H
  #define CUBBYFLOW_PARTICLE_SYSTEM_SOLVER3_H
  
  #include <Core/Animation/PhysicsAnimation.h>
  #include <Core/Collider/Collider3.h>
  #include <Core/Emitter/ParticleEmitter3.h>
  #include <Core/Field/VectorField3.h>
  #include <Core/Particle/ParticleSystemData3.h>
  #include <Core/Utils/Constants.h>
  #include <Core/Vector/Vector3.h>
  
  namespace CubbyFlow
  {
  	//!
  	//! \brief      Basic 3-D particle system solver.
  	//!
  	//! This class implements basic particle system solver. It includes gravity,
  	//! air drag, and collision. But it does not compute particle-to-particle
  	//! interaction. Thus, this solver is suitable for performing simple spray-like
  	//! simulations with low computational cost. This class can be further extend
  	//! to add more sophisticated simulations, such as SPH, to handle
  	//! particle-to-particle intersection.
  	//!
  	//! \see        SphSolver3
  	//!
  	class ParticleSystemSolver3 : public PhysicsAnimation
  	{
  	public:
  		class Builder;
  
  		//! Constructs an empty solver.
  		ParticleSystemSolver3();
  
  		//! Constructs a solver with particle parameters.
  		ParticleSystemSolver3(double radius, double mass);
  
  		//! Destructor.
  		virtual ~ParticleSystemSolver3();
  
  		//! Returns the drag coefficient.
  		double GetDragCoefficient() const;
  
  		//!
  		//! \brief      Sets the drag coefficient.
  		//!
  		//! The drag coefficient controls the amount of air-drag. The coefficient
  		//! should be a positive number and 0 means no drag force.
  		//!
  		//! \param[in]  newDragCoefficient The new drag coefficient.
  		//!
  		void SetDragCoefficient(double newDragCoefficient);
  		
  		//! Gets the restitution coefficient.
  		double GetRestitutionCoefficient() const;
  
  		//!
  		//! \brief      Sets the restitution coefficient.
  		//!
  		//! The restitution coefficient controls the bounciness of a particle when
  		//! it hits a collider surface. The range of the coefficient should be 0 to
  		//! 1 -- 0 means no bounce back and 1 means perfect reflection.
  		//!
  		//! \param[in]  newRestitutionCoefficient The new restitution coefficient.
  		//!
  		void SetRestitutionCoefficient(double newRestitutionCoefficient);
  
  		//! Returns the gravity.
  		const Vector3D& GetGravity() const;
  
  		//! Sets the gravity.
  		void SetGravity(const Vector3D& newGravity);
  
  		//!
  		//! \brief      Returns the particle system data.
  		//!
  		//! This function returns the particle system data. The data is created when
  		//! this solver is constructed and also owned by the solver.
  		//!
  		//! \return     The particle system data.
  		//!
  		const ParticleSystemData3Ptr& GetParticleSystemData() const;
  
  		//! Returns the collider.
  		const Collider3Ptr& GetCollider() const;
  
  		//! Sets the collider.
  		void SetCollider(const Collider3Ptr& newCollider);
  
  		//! Returns the emitter.
  		const ParticleEmitter3Ptr& GetEmitter() const;
  
  		//! Sets the emitter.
  		void SetEmitter(const ParticleEmitter3Ptr& newEmitter);
  
  		//! Returns the wind field.
  		const VectorField3Ptr& GetWind() const;
  
  		//!
  		//! \brief      Sets the wind.
  		//!
  		//! Wind can be applied to the particle system by setting a vector field to
  		//! the solver.
  		//!
  		//! \param[in]  newWind The new wind.
  		//!
  		void SetWind(const VectorField3Ptr& newWind);
  
  		//! Returns builder fox ParticleSystemSolver3.
  		static Builder GetBuilder();
  
  	protected:
  		//! Initializes the simulator.
  		void OnInitialize() override;
  
  		//! Called to advance a single time-step.
  		void OnAdvanceTimeStep(double timeStepInSeconds) override;
  
  		//! Accumulates forces applied to the particles.
  		virtual void AccumulateForces(double timeStepInSeconds);
  
  		//! Called when a time-step is about to begin.
  		virtual void OnBeginAdvanceTimeStep(double timeStepInSeconds);
  
  		//! Called after a time-step is completed.
  		virtual void OnEndAdvanceTimeStep(double timeStepInSeconds);
  
  		//! Resolves any collisions occurred by the particles.
  		void ResolveCollision();
  
  		//! Resolves any collisions occurred by the particles where the particle
  		//! state is given by the position and velocity arrays.
  		void ResolveCollision(ArrayAccessor1<Vector3D> newPositions, ArrayAccessor1<Vector3D> newVelocities);
  
  		//! Assign a new particle system data.
  		void SetParticleSystemData(const ParticleSystemData3Ptr& newParticles);
  
  	private:
  		double m_dragCoefficient = 1e-4;
  		double m_restitutionCoefficient = 0.0;
  		Vector3D m_gravity = Vector3D(0.0, GRAVITY, 0.0);
  
  		ParticleSystemData3Ptr m_particleSystemData;
  		ParticleSystemData3::VectorData m_newPositions;
  		ParticleSystemData3::VectorData m_newVelocities;
  		Collider3Ptr m_collider;
  		ParticleEmitter3Ptr m_emitter;
  		VectorField3Ptr m_wind;
  
  		void BeginAdvanceTimeStep(double timeStepInSeconds);
  
  		void EndAdvanceTimeStep(double timeStepInSeconds);
  
  		void AccumulateExternalForces();
  		
  		void TimeIntegration(double timeStepInSeconds);
  
  		void UpdateCollider(double timeStepInSeconds) const;
  
  		void UpdateEmitter(double timeStepInSeconds) const;
  	};
  
  	//! Shared pointer type for the ParticleSystemSolver3.
  	using ParticleSystemSolver3Ptr = std::shared_ptr<ParticleSystemSolver3>;
  
  	//!
  	//! \brief Base class for particle-based solver builder.
  	//!
  	template <typename DerivedBuilder>
  	class ParticleSystemSolverBuilderBase3
  	{
  	public:
  		//! Returns builder with particle radius.
  		DerivedBuilder& WithRadius(double radius);
  
  		//! Returns builder with mass per particle.
  		DerivedBuilder& WithMass(double mass);
  
  	protected:
  		double m_radius = 1e-3;
  		double m_mass = 1e-3;
  	};
  
  	template <typename T>
  	T& ParticleSystemSolverBuilderBase3<T>::WithRadius(double radius)
  	{
  		m_radius = radius;
  		return static_cast<T&>(*this);
  	}
  
  	template <typename T>
  	T& ParticleSystemSolverBuilderBase3<T>::WithMass(double mass)
  	{
  		m_mass = mass;
  		return static_cast<T&>(*this);
  	}
  
  	//!
  	//! \brief Front-end to create ParticleSystemSolver3 objects step by step.
  	//!
  	class ParticleSystemSolver3::Builder final
  		: public ParticleSystemSolverBuilderBase3<ParticleSystemSolver3::Builder>
  	{
  	public:
  		//! Builds ParticleSystemSolver3.
  		ParticleSystemSolver3 Build() const;
  
  		//! Builds shared pointer of ParticleSystemSolver3 instance.
  		ParticleSystemSolver3Ptr MakeShared() const;
  	};
  }
  
  #endif