栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

图形学-粒子系统 (Particle System)

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

图形学-粒子系统 (Particle System)

一. 说明

基于 C++ 11 和 EasyX库 实现。

主要在 Krissi 大佬 代码基础上,修改和添加一些功能。

功能:

  • 能模拟一些简单的粒子行为。
  • 添加新的行为容易。

不足:

  • 功能过于简陋。
  • 公式没有参考严谨的物理与数学。

本人代码萌新,虽然有注释,但是存在不明白或错误之处,欢迎大家一起讨论和交流。

二. 截图


菜单中文翻译:

状态

  • 平均帧数
  • 帧时间
  • 粒子数 / 粒子总数

鼠标

  • 移动:搅动粒子
  • 左击:聚集粒子
  • 右击:吹散粒子
  • 滚轮:缩放粒子

键盘

  • Esc:退出系统
  • Tab:隐藏菜单
  • Space:暂停系统
  • Enter:重新模拟

特效(开启或者关闭)

  • A:显示鼠标落点
  • B:反弹墙壁
  • C:速度和大小关联
  • D:吹散
  • E:聚集
  • F:搅动
  • G:摩擦力
  • H:漩涡
  • I:随机移动
  • J:缩放
  • K:轨迹消失
  • L:重力
  • M:生命周期
三. 代码

C++ 源码有两个文件,ParticleSystem.h 和 ParticleSystem.cpp

我先介绍如何使用

Main.cpp

#include "ParticleSystem.h"

int main()
{
	// step1:初始化粒子系统
	FParticleSystem ParticleSystem(1000, 600);

	// step2:生成自定义的发射器
	FEmitter EmitterOne;
	FParticleSystem::EmitterManager.BindEmitter(EmitterOne)		// 绑定要处理的发射器
									.SetNum(333)				// 进行一系列参数设置
									.SetStartTime(0.0f)
									.SetColor(RED)
									.SetRadiusMin(1.0f)
									.SetRadiusMax(1.5f)
									.SetLifeTimeMin(5.0f)
									.SetLifeTimeMax(10.0f)
									.GenerateParticles();		// 最后完成粒子的生成

	FEmitter EmitterTwo;
	FParticleSystem::EmitterManager.BindEmitter(EmitterTwo)
									.SetStartTime(1.0f)
									.SetNum(333)
									.SetColor(RAND)
									.SetRadiusMin(2.0f)
									.SetRadiusMax(3.0f)
									.SetLifeTimeMin(0.1f)
									.SetLifeTimeMax(5.0f)
									.GenerateParticles();

	FEmitter EmitterThree;
	FParticleSystem::EmitterManager.BindEmitter(EmitterThree)
									.SetStartTime(1.2f)
									.SetNum(333)
									.SetColor(RAND)
									.SetRadiusMin(2.0f)
									.SetRadiusMax(3.0f)
									.SetLifeTimeMin(0.1f)
									.SetLifeTimeMax(5.0f)
									.GenerateParticles();

	// step3:将处理好的发射器加入粒子系统
	ParticleSystem.AddEmitter(EmitterOne);
	ParticleSystem.AddEmitter(EmitterTwo);
	ParticleSystem.AddEmitter(EmitterThree);

	// step4:粒子系统运行所有的发射器
	ParticleSystem.RunEmitters();

	return 0;
}

可以看到,只需要设置好发射器的相关参数,就可以轻松的使用粒子系统。

当然上面的例子只是演示,没有做什么效果,萌新初学乍到——_+,期待大佬们给出更好的样例。

而且呃,很多好玩且有用的参数和功能并未实现,不过之后会一直更新的√。

下面是源码!!!

ParticleSystem.h






#pragma once
#include 
#include 
#include 
#include 
#define RAND RGB(255, 255, 255)+1


struct FVector2D
{
	float X, Y;

	FVector2D operator+(const FVector2D& Other) const
	{
		return {X + Other.X, Y + Other.Y};
	}

	FVector2D operator-(const FVector2D& Other) const
	{
		return {X - Other.X, Y - Other.Y};
	}
};


struct FVortex
{
	FVector2D Position;
	float Radius;
	float RotateSpeed;
};


class Utlis
{
public:
	
	static float GenerateRandomNumber(const float Min, const float Max, const float FractionalNum)
	{
		const float PowerNumber = powf(10, FractionalNum);
		const int MinInt = static_cast(Min * PowerNumber);
		const int MaxInt = static_cast(Max * PowerNumber);
		return static_cast(MinInt + GenerateRandomNumber() % (MaxInt - MinInt + 1)) / PowerNumber;
	}

	
	static int GenerateRandomNumber(const int Min, const int Max)
	{
		return Min + GenerateRandomNumber() % (Max - Min + 1);
	}

	
	static int GenerateRandomNumber(const int Max)
	{
		return GenerateRandomNumber() % (Max + 1);
	}

	
	static float GenerateRandomNumber(const float Max, const float FractionalNum)
	{
		const float PowerNumber = powf(10, FractionalNum);
		const int MaxInt = static_cast(Max * PowerNumber);
		return static_cast(GenerateRandomNumber() % (MaxInt + 1)) / PowerNumber;
	}

	
	static int GenerateRandomNumber()
	{
		return rand();
	}

	
	template 
	static ElementType GenerateRandomSign()
	{
		return static_cast(pow(-1, GenerateRandomNumber() % 2));
	}

	
	template 
	static ElementType Clamp(ElementType Number, ElementType Min, ElementType Max)
	{
		return Number >= Min ? Number <= Max ? Number : Max : Min;
	}

	
	static FVector2D Normalize(const FVector2D& Vector2D)
	{
		const float Length = GetLength(Vector2D);
		return {Vector2D.X / Length, Vector2D.Y / Length};
	}

	
	static float GetLength(const FVector2D& Vector2D)
	{
		return sqrt(Vector2D.X * Vector2D.X + Vector2D.Y * Vector2D.Y);
	}
};


class FWindowManager
{
private:
	FWindowManager(const FWindowManager&) = default;
	FWindowManager(FWindowManager&&) = default;
	FWindowManager() = default;
	FWindowManager& operator=(const FWindowManager&) = default;
	FWindowManager& operator=(FWindowManager&&) = default;
public:
	FWindowManager(float InWidth, float InHeight)
	{
		// 创建绘图窗口
		initgraph(static_cast(InWidth), static_cast(InHeight));
		// 启用批绘图模式
		BeginBatchDraw();
	}

	~FWindowManager()
	{
		// 关闭批绘图模式
		EndBatchDraw();
		// 关闭绘图窗口
		closegraph();
	}

	void Flush() const
	{
		// 显示缓存的绘制内容
		FlushBatchDraw();
	}
};



struct FParticle
{
public:
	COLORREF Color = RED;		// 颜色
	float PosX = 0.0f;			// 坐标
	float PosY = 0.0f;
	float VelX = 0.0f;			// 速度
	float VelY = 0.0f;
	float Friction = 0.96f;		// 摩擦力
	float Radius = 0.4f;		// 半径
	float LifeTime = 0.5f;		// 生命周期
public:
	float NextX, NextY;			// 下一次坐标
	bool IsPendingKilled;		// 是否进入死亡

	FParticle(COLORREF InColor = RED, float InPosX = 0.0f, float InPosY = 0.0f, float InVelX = 0.0f,
			float InVelY = 0.0f,
			float InFriction = 0.96f, float InRadius = 0.4f, float InLifeTime = 0.0f) : Color(InColor)
		, PosX(InPosX)
		, PosY(InPosY)
		, VelX(InVelX)
		, VelY(InVelY)
		, Friction(InFriction)
		, Radius(InRadius)
		, LifeTime(InLifeTime)
	{
		NextX = 0.0f;
		NextY = 0.0f;
		IsPendingKilled = false;
	}
};


struct FEmitter
{
public:
	unsigned int Num = 666;				// 粒子总数
	float StartTime = 0.0f;				// 发射开始时间
	COLORREF Color = RAND;				// 颜色(默认随机)
	float StartPosX = 0.0f;				// 起始坐标
	float StartPosY = 0.0f;
	float StartVelX = 0.0f;				// 起始速度
	float StartVelY = 0.0f;
	float VelocityMin = 0.01f;			// 速度最小值
	float VelocityMax = 50.0f;			// 速度最大值
	float FrictionMin = 0.93f;			// 摩擦力最小值
	float FrictionMax = 0.96f;			// 摩擦力最大值
	float RadiusMin = 0.40f;			// 半径最小值
	float RadiusMax = 1.66f;			// 半径最大值
	float LifeTimeMin = 2.0f;			// 生命周期最小值
	float LifeTimeMax = 100.0f;			// 生命周期最大值
public:
	std::vector Particles;	// 粒子数组
	bool IsRender = false;				// 是否已经渲染
};


class FEmitterManager
{
private:
	FEmitterManager()
	{
		Emitter = new FEmitter();
	}

	~FEmitterManager()
	{
		delete Emitter;
	}

	FEmitterManager(const FEmitterManager&) = default;
	FEmitterManager(FEmitterManager&&) = default;
	FEmitterManager& operator=(const FEmitterManager&) = default;
	FEmitterManager& operator=(FEmitterManager&&) = default;
public:
	void AvoidWarning() const
	{
		Emitter->Num = Emitter->Num + 0;
	}

	static FEmitterManager& GetInstance()
	{
		static FEmitterManager ParticleManager;
		return ParticleManager;
	}

	FEmitterManager& BindEmitter(FEmitter& InEmitter)
	{
		Emitter = &InEmitter;
		return GetInstance();
	}

	void GenerateParticles() const
	{
		std::vector& Particles = Emitter->Particles;
		Particles = {};
		Particles.reserve(Emitter->Num);
		for (size_t i = 0; i < Emitter->Num; ++i)
		{
			Particles.emplace_back(
				Emitter->Color == RAND
					? RGB(Utlis::GenerateRandomNumber(255), Utlis::GenerateRandomNumber(255),
						Utlis::GenerateRandomNumber(255))
					: Emitter->Color,
				1000 * 0.5f,
				600 * 0.5f,
				cos(static_cast(i)) * Utlis::GenerateRandomNumber(33.0f, 1.0f),
				sin(static_cast(i)) * Utlis::GenerateRandomNumber(33.0f, 1.0f),
				Utlis::GenerateRandomNumber(Emitter->FrictionMin, Emitter->FrictionMax, 2),
				Utlis::GenerateRandomNumber(Emitter->RadiusMin, Emitter->RadiusMax, 2),
				Utlis::GenerateRandomNumber(Emitter->LifeTimeMin, Emitter->LifeTimeMax, 2)
			);
		}
	}

	std::vector& GetParticles() const
	{
		return Emitter->Particles;
	}


public:
	FEmitterManager& SetIsRender(const bool InIsRender) const
	{
		Emitter->IsRender = InIsRender;
		return GetInstance();
	}

	bool GetIsRender() const
	{
		return Emitter->IsRender;
	}

	FEmitterManager& SetStartTime(const float InStartTime) const
	{
		Emitter->StartTime = Utlis::Clamp(InStartTime, ValuePositiveMin, ValuePositiveMax);
		return GetInstance();
	}

	float GetStartTime() const
	{
		return Emitter->StartTime;
	}

	FEmitterManager& SetNum(const unsigned int InNum) const
	{
		Emitter->Num = static_cast(
			Utlis::Clamp(static_cast(InNum), ValuePositiveMin, ValuePositiveMax));
		return GetInstance();
	}

	unsigned int GetNum() const
	{
		return Emitter->Num;
	}

	FEmitterManager& SetColor(const COLORREF InColor) const
	{
		Emitter->Color = Utlis::Clamp(InColor, RGB(0, 0, 0), RAND);
		return GetInstance();
	}

	COLORREF GetColor() const
	{
		return Emitter->Color;
	}

	FEmitterManager& SetStartPosX(const float InStartPosX) const
	{
		Emitter->StartPosX = Utlis::Clamp(InStartPosX, -ValuePositiveMax, ValuePositiveMax);
		return GetInstance();
	}

	float GetStartPosX() const
	{
		return Emitter->StartPosX;
	}

	FEmitterManager& SetStartPosY(const float InStartPosY) const
	{
		Emitter->StartPosY = Utlis::Clamp(InStartPosY, -ValuePositiveMax, ValuePositiveMax);
		return GetInstance();
	}

	float GetStartPosY() const
	{
		return Emitter->StartPosY;
	}

	FEmitterManager& SetStartVelX(const float InStartVelX) const
	{
		Emitter->StartVelX = Utlis::Clamp(InStartVelX, -ValuePositiveMax, ValuePositiveMax);
		return GetInstance();
	}

	float GetStartVelX() const
	{
		return Emitter->StartVelX;
	}

	FEmitterManager& SetStartVelY(const float InStartVelY) const
	{
		Emitter->StartVelY = Utlis::Clamp(InStartVelY, -ValuePositiveMax, ValuePositiveMax);
		return GetInstance();
	}

	float GetStartVelY() const
	{
		return Emitter->StartVelY;
	}

	FEmitterManager& SetVelocityMin(const float InVelocityMin) const
	{
		Emitter->VelocityMin = Utlis::Clamp(InVelocityMin, ValuePositiveMin, ValuePositiveMax);
		return GetInstance();
	}

	float GetVelocityMin() const
	{
		return Emitter->VelocityMin;
	}

	FEmitterManager& SetVelocityMax(const float InVelocityMax) const
	{
		Emitter->VelocityMax = Utlis::Clamp(InVelocityMax, ValuePositiveMin, ValuePositiveMax);
		return GetInstance();
	}

	float GetVelocityMax() const
	{
		return Emitter->VelocityMax;
	}

	FEmitterManager& SetFrictionMin(const float InFrictionMin) const
	{
		Emitter->FrictionMin = Utlis::Clamp(InFrictionMin, ValuePositiveMin, ValuePositiveMax);
		return GetInstance();
	}

	float GetFrictionMin() const
	{
		return Emitter->FrictionMin;
	}

	FEmitterManager& SetFrictionMax(const float InFrictionMax) const
	{
		Emitter->FrictionMax = Utlis::Clamp(InFrictionMax, ValuePositiveMin, ValuePositiveMax);
		return GetInstance();
	}

	float GetFrictionMax() const
	{
		return Emitter->FrictionMax;
	}

	FEmitterManager& SetRadiusMin(const float InRadiusMin) const
	{
		Emitter->RadiusMin = Utlis::Clamp(InRadiusMin, ValuePositiveMin, ValuePositiveMax);
		return GetInstance();
	}

	float GetRadiusMin() const
	{
		return Emitter->RadiusMin;
	}

	FEmitterManager& SetRadiusMax(const float InRadiusMax) const
	{
		Emitter->RadiusMax = Utlis::Clamp(InRadiusMax, ValuePositiveMin, ValuePositiveMax);
		return GetInstance();
	}

	float GetRadiusMax() const
	{
		return Emitter->RadiusMax;
	}

	FEmitterManager& SetLifeTimeMin(const float InLifeTimeMin) const
	{
		Emitter->LifeTimeMin = Utlis::Clamp(InLifeTimeMin, ValuePositiveMin, ValuePositiveMax);
		return GetInstance();
	}

	float GetLifeTimeMin() const
	{
		return Emitter->LifeTimeMin;
	}

	FEmitterManager& SetLifeTimeMax(const float InLifeTimeMax) const
	{
		Emitter->LifeTimeMax = Utlis::Clamp(InLifeTimeMax, ValuePositiveMin, ValuePositiveMax);
		return GetInstance();
	}

	float GetLifeTimeMax() const
	{
		return Emitter->LifeTimeMax;
	}

private:
	FEmitter* Emitter;
public:
	static const float ValuePercentMax;
	static const float ValuePercentMin;
	static const float ValuePositiveMax;
	static const float ValuePositiveMin;
};



class FParticleSystem
{
public:
	FParticleSystem(float InWidth = 1540.0f, float InHeight = 840.0f);

	
	void AddEmitter(FEmitter& InEmitter);

	
	void RunEmitters();

public:
	void SetDeltaTime(const float InDeltaTime)
	{
		CustomDeltaTime = InDeltaTime;
	}

	float GetDeltaTime() const
	{
		return CustomDeltaTime / 1000.0f;
	}

private:
	
	void HandleInput();

	
	void ShowHelp() const;

	
	void Render(const float InDeltaTime);

	
	float CalculateFps(const float InElapsedTime);

	
	float CalculateDeltaTime(const clock_t InCurTime);

	
	static void Delay(const clock_t Ms);

	
	float LimitFps(const float InElapsedTime) const;

private:
	
	void OnEffectMouseField(const float InDeltaTime) const;

	
	void OnEffectDisappearTrack(const float InDeltaTime) const;

	
	void OnEffectReboundWall(const float InDeltaTime, FParticle& InParticle) const;

	
	void OnEffectRadiusAboutSpeed(const float InDeltaTime, FParticle& InParticle) const;

	
	void OnEffectBlow(const float InDeltaTime, FParticle& InParticle) const;

	
	void OnEffectGather(const float InDeltaTime, FParticle& InParticle) const;

	
	void OnEffectStir(const float InDeltaTime, FParticle& InParticle) const;

	
	void OnEffectFriction(const float InDeltaTime, FParticle& InParticle) const;

	
	void OnEffectVortex(const float InDeltaTime, FParticle& InParticle) const;

	
	void OnEffectRandomMove(const float InDeltaTime, FParticle& InParticle) const;

	
	void OnEffectScaleWithWheel(const float InDeltaTime, FParticle& InParticle) const;

	
	void OnEffectGravity(const float InDeltaTime, FParticle& InParticle) const;

	
	void OnEffectDeath(const float InDeltaTime, FParticle& InParticle) const;
private:
	const float Width;							// 屏幕宽度
	const float Height;							// 屏幕高度
	size_t ParticleTotalNum = 0;				// 粒子总数
	size_t ParticleCurNum = 0;					// 粒子当前数
	std::vector Emitters;				// 发射器数组
	float CurMousePosX = 0.0f;					// 当前鼠标坐标
	float CurMousePosY = 0.0f;
	float PrevMousePosX = 0.0f;					// 上次鼠标坐标
	float PrevMousePosY = 0.0f;
	float MouseVelX = 0.0f;						// 鼠标速度
	float MouseVelY = 0.0f;
	bool IsLeftMouseDown = false;				// 是否按下鼠标左键
	bool IsRightMouseDown = false;				// 是否按下鼠标右键
	DWORD* GBuffer = nullptr;					// 显示缓冲区指针
	float CustomHighFps = 90.0f;				// 限制高帧率
	float CustomLowFps = 10.0f;					// 限制低帧率
	float CustomDeltaTime = 1000.0f / 90.0f;	// 增量时间(ms)
	float GraphicsFps = 90.0f;					// 当前帧数
	float GraphicsTime = 1000.0f / 90.0f;		// 当前帧时间
	float SimulateTotalTime = 0.0f;				// 模拟总时间
	FWindowManager WindowManager;				// 窗口管理器
public:
	static FEmitterManager& EmitterManager;		// 粒子管理器
private:
	float RandomDis;	// 进入随机距离
	float BlowDis;		// 进入扩散距离
	float GatherDis;	// 进入聚集距离
	float StirDis;		// 进入搅拌距离
private:
	float RandomSpeed;	// 随机速度
	float BlowSpeed;	// 扩散速度
	float GatherSpeed;	// 聚集速度
	float StirSpeed;	// 搅拌速度
private:
	bool IsShowHelp = true;			// 是否显示帮助
	bool IsContinueGraphics = true;	// 是否继续系统
	bool IsContinueSimulate = true;	// 是否继续模拟
	bool IsRestartSimulate = false;	// 是否重新模拟
	mutable float ScaleSize = 1.0f;	// 滚轮缩放大小
private:
	// 特效控制,顾名思义
	bool IsEffectMouseField = false;
	bool IsEffectReboundWall = true;
	bool IsEffectRadiusAboutSpeed = false;
	bool IsEffectBlow = true;
	bool IsEffectGather = true;
	bool IsEffectStir = true;
	bool IsEffectFriction = true;
	bool IsEffectVortex = true;
	bool IsEffectRandomMove = true;
	bool IsEffectScaleWithWheel = true;
	bool IsEffectDisappearTrack = true;
	bool IsEffectGravity = false;
	bool IsEffectDeath = false;
};

ParticleSystem.cpp

#include "ParticleSystem.h"
#include 
#include 
#include 

FEmitterManager& FParticleSystem::EmitterManager = FEmitterManager::GetInstance();
const float FEmitterManager::ValuePositiveMax = 10000.0f;
const float FEmitterManager::ValuePositiveMin = 0.0001f;
const float FEmitterManager::ValuePercentMax = 99.9999f;
const float FEmitterManager::ValuePercentMin = 0.0001f;

FParticleSystem::FParticleSystem(float InWidth, float InHeight) : Width(InWidth), Height(InHeight),
																WindowManager(Width, Height)
{
	// 设置随机种子
	srand(static_cast(time(nullptr) + 5201314));

	// 获取显示缓冲区指针
	GBuffer = GetImageBuffer(nullptr);

	// 初始化鼠标变量
	CurMousePosX = PrevMousePosX = InWidth / 2;
	CurMousePosY = PrevMousePosY = InWidth / 2;

	// 特效参数
	RandomDis = InWidth;
	BlowDis = InWidth * 0.5f;
	GatherDis = InWidth * 0.86f;
	StirDis = InWidth * 0.125f;

	RandomSpeed = 20.0f;
	BlowSpeed = 5.0f;
	GatherSpeed = 26.0f;
	StirSpeed = 30.0f;

	// 其他
	CustomDeltaTime = 1000.0f / CustomHighFps;
}

void FParticleSystem::AddEmitter(FEmitter& InEmitter)
{
	Emitters.push_back(InEmitter);

	EmitterManager.BindEmitter(InEmitter);

	ParticleCurNum += EmitterManager.GetNum();
	ParticleTotalNum += EmitterManager.GetNum();
}

void FParticleSystem::RunEmitters()
{
	static clock_t OldTime;
	static clock_t NewTime;
	while (IsContinueGraphics)
	{
		OldTime = clock();

		// 计算增量时间
		CalculateDeltaTime(clock());

		// 处理输入消息
		HandleInput();

		// 绘制所需图形
		Render(GetDeltaTime());

		// 显示缓存绘制
		WindowManager.Flush();

		NewTime = clock();

		// 限制并计算帧数
		CalculateFps(LimitFps(static_cast(NewTime - OldTime)));

		// 计算总时间
		SimulateTotalTime += (IsContinueSimulate == true ? static_cast(clock() - OldTime) / 1000.0f : 0.0f);
	}
}

void FParticleSystem::HandleInput()
{
	ExMessage Input{};
	while (peekmessage(&Input, EM_MOUSE | EM_KEY))
	{
		switch (Input.message)
		{
			case WM_MOUSEMOVE:
			{
				CurMousePosX = static_cast(Input.x);
				CurMousePosY = static_cast(Input.y);
				break;
			}
			case WM_MOUSEWHEEL:
			{
				ScaleSize = min(max(ScaleSize + Input.wheel / 500.0f, 0.5f), 5.0f);
				break;
			}
			case WM_LBUTTONDOWN:
			{
				IsLeftMouseDown = true;
				break;
			}
			case WM_LBUTTONUP:
			{
				IsLeftMouseDown = false;
				break;
			}
			case WM_RBUTTONDOWN:
			{
				IsRightMouseDown = true;
				break;
			}
			case WM_RBUTTONUP:
			{
				IsRightMouseDown = false;
				break;
			}
			case WM_KEYDOWN:
			{
				switch (Input.vkcode)
				{
					case VK_RETURN: IsRestartSimulate = true;
						break;
					case VK_ESCAPE: IsContinueGraphics = false;
						break;
					case VK_SPACE: IsContinueSimulate = !IsContinueSimulate;
						break;
					case VK_TAB: IsShowHelp = !IsShowHelp;
						break;
					case 'A':
					case 'a': IsEffectMouseField = !IsEffectMouseField;
						break;
					case 'B':
					case 'b': IsEffectReboundWall = !IsEffectReboundWall;
						break;
					case 'C':
					case 'c': IsEffectRadiusAboutSpeed = !IsEffectRadiusAboutSpeed;
						break;
					case 'D':
					case 'd': IsEffectBlow = !IsEffectBlow;
						break;
					case 'E':
					case 'e': IsEffectGather = !IsEffectGather;
						break;
					case 'F':
					case 'f': IsEffectStir = !IsEffectStir;
						break;
					case 'G':
					case 'g': IsEffectFriction = !IsEffectFriction;
						break;
					case 'H':
					case 'h': IsEffectVortex = !IsEffectVortex;
						break;
					case 'I':
					case 'i': IsEffectRandomMove = !IsEffectRandomMove;
						break;
					case 'J':
					case 'j': IsEffectScaleWithWheel = !IsEffectScaleWithWheel;
						break;
					case 'K':
					case 'k': IsEffectDisappearTrack = !IsEffectDisappearTrack;
						break;
					case 'L':
					case 'l': IsEffectGravity = !IsEffectGravity;
						break;
					case 'M':
					case 'm': IsEffectDeath = !IsEffectDeath;
						break;
					default: break;
				}
				break;
			}
			default: break;
		}
	}
}

void FParticleSystem::ShowHelp() const
{
	if (IsShowHelp)
	{
		// 不用返回值会报 warning,暂时解决
		static int AvoidWarning = 0;

		static wchar_t Message[255];
		static constexpr int TextHeight = 16;
		int Lines = -1 + AvoidWarning * 0;

		AvoidWarning = swprintf_s(Message, _T("ttfps: %.1lf"), static_cast(GraphicsFps));
		outtextxy(0, TextHeight * (++Lines), Message);

		AvoidWarning = swprintf_s(Message, _T("tttime: %.1lfms"), static_cast(GraphicsTime));
		outtextxy(0, TextHeight * (++Lines), Message);

		AvoidWarning = swprintf_s(Message,
								_T("ttparticles: %u / %u"),
								static_cast(ParticleCurNum),
								static_cast(ParticleTotalNum));
		outtextxy(0, TextHeight * (++Lines), Message);

		++Lines;
		outtextxy(0, TextHeight * (++Lines), _T("ttMouse"));
		outtextxy(0, TextHeight * (++Lines), _T("ttMove: Stir"));
		outtextxy(0, TextHeight * (++Lines), _T("ttLeft: Gather"));
		outtextxy(0, TextHeight * (++Lines), _T("ttRight: Blow"));
		outtextxy(0, TextHeight * (++Lines), _T("ttWheel: Scale"));

		++Lines;
		outtextxy(0, TextHeight * (++Lines), _T("ttKeyboard"));

		outtextxy(0, TextHeight * (++Lines), _T("tt[Esc]: Exit System"));
		outtextxy(0, TextHeight * (++Lines), _T("tt[Tab]: Hide Status"));
		outtextxy(0, TextHeight * (++Lines), _T("tt[Space]: Pause System"));
		outtextxy(0, TextHeight * (++Lines), _T("tt[Enter]: Restart Simulate"));

		++Lines;
		outtextxy(0, TextHeight * (++Lines), _T("ttSpecial"));

		AvoidWarning = swprintf_s(Message, _T("tt[A]: Effect - MouseField %s"),
								IsEffectMouseField == true ? _T("ON") : _T("OFF"));
		outtextxy(0, TextHeight * (++Lines), Message);

		AvoidWarning = swprintf_s(Message, _T("tt[B]: Effect - ReboundWall %s"),
								IsEffectReboundWall == true ? _T("ON") : _T("OFF"));
		outtextxy(0, TextHeight * (++Lines), Message);

		AvoidWarning = swprintf_s(Message, _T("tt[C]: Effect - RadiusAboutSpeed %s"),
								IsEffectRadiusAboutSpeed == true ? _T("ON") : _T("OFF"));
		outtextxy(0, TextHeight * (++Lines), Message);

		AvoidWarning = swprintf_s(Message, _T("tt[D]: Effect - Blow %s"),
								IsEffectBlow == true ? _T("ON") : _T("OFF"));
		outtextxy(0, TextHeight * (++Lines), Message);

		AvoidWarning = swprintf_s(Message, _T("tt[E]: Effect - Gather %s"),
								IsEffectGather == true ? _T("ON") : _T("OFF"));
		outtextxy(0, TextHeight * (++Lines), Message);

		AvoidWarning = swprintf_s(Message, _T("tt[F]: Effect - Stir %s"),
								IsEffectStir == true ? _T("ON") : _T("OFF"));
		outtextxy(0, TextHeight * (++Lines), Message);

		AvoidWarning = swprintf_s(Message, _T("tt[G]: Effect - Friction %s"),
								IsEffectFriction == true ? _T("ON") : _T("OFF"));
		outtextxy(0, TextHeight * (++Lines), Message);

		AvoidWarning = swprintf_s(Message, _T("tt[H]: Effect - Vortex %s"),
								IsEffectVortex == true ? _T("ON") : _T("OFF"));
		outtextxy(0, TextHeight * (++Lines), Message);

		AvoidWarning = swprintf_s(Message, _T("tt[I]: Effect - RandomMove %s"),
								IsEffectRandomMove == true ? _T("ON") : _T("OFF"));
		outtextxy(0, TextHeight * (++Lines), Message);

		AvoidWarning = swprintf_s(Message, _T("tt[J]: Effect - Scale %s"),
								IsEffectScaleWithWheel == true ? _T("ON") : _T("OFF"));
		outtextxy(0, TextHeight * (++Lines), Message);

		AvoidWarning = swprintf_s(Message, _T("tt[K]: Effect - DisappearTrack %s"),
								IsEffectDisappearTrack == true ? _T("ON") : _T("OFF"));
		outtextxy(0, TextHeight * (++Lines), Message);

		AvoidWarning = swprintf_s(Message, _T("tt[L]: Effect - Gravity %s"),
								IsEffectGravity == true ? _T("ON") : _T("OFF"));
		outtextxy(0, TextHeight * (++Lines), Message);

		AvoidWarning = swprintf_s(Message, _T("tt[M]: Effect - Death %s"),
								IsEffectDeath == true ? _T("ON") : _T("OFF"));
		outtextxy(0, TextHeight * (++Lines), Message);
	}
}

void FParticleSystem::Render(const float InDeltaTime)
{
	if (IsContinueSimulate)
	{
		if (IsRestartSimulate)
		{
			IsRestartSimulate = false;
			SimulateTotalTime = 0;
			ParticleCurNum = 0;
			ParticleTotalNum = 0;
			for (auto& Emitter : Emitters)
			{
				EmitterManager.BindEmitter(Emitter)
							.GenerateParticles();
				ParticleTotalNum += EmitterManager.GetNum();
			}
		}
		else
		{
			ShowHelp();
			OnEffectMouseField(InDeltaTime);
			OnEffectDisappearTrack(InDeltaTime);

			MouseVelX = CurMousePosX - PrevMousePosX;
			MouseVelY = CurMousePosY - PrevMousePosY;

			PrevMousePosX = CurMousePosX;
			PrevMousePosY = CurMousePosY;

			ParticleCurNum = 0;
			for (auto& Emitter : Emitters)
			{
				std::vector& Particles = EmitterManager
													.BindEmitter(Emitter)
													.GetParticles();
				if (EmitterManager.GetStartTime() <= SimulateTotalTime)
				{
					EmitterManager.SetIsRender(true).AvoidWarning();
					for (size_t i = 0; i < Particles.size(); ++i)
					{
						if (Particles[i].IsPendingKilled == true)
						{
							std::swap(Particles[i], Particles[Particles.size() - 1]);
							Particles.pop_back();
						}
						else
						{
							FParticle Temp = Particles[i];

							OnEffectDeath(InDeltaTime, Temp);
							OnEffectRandomMove(InDeltaTime, Temp);
							OnEffectBlow(InDeltaTime, Temp);
							OnEffectGather(InDeltaTime, Temp);
							OnEffectStir(InDeltaTime, Temp);
							OnEffectFriction(InDeltaTime, Temp);
							OnEffectRadiusAboutSpeed(InDeltaTime, Temp);
							OnEffectVortex(InDeltaTime, Temp);
							OnEffectGravity(InDeltaTime, Temp);
							OnEffectScaleWithWheel(InDeltaTime, Temp);

							Temp.NextX = Temp.PosX + Temp.VelX;
							Temp.NextY = Temp.PosY + Temp.VelY;

							// 位置计算完成
							OnEffectReboundWall(InDeltaTime, Temp);

							Particles[i] = Temp;

							// 画粒子
							setfillcolor(Particles[i].Color);
							solidcircle(static_cast(Particles[i].NextX),
										static_cast(Particles[i].NextY),
										static_cast(floorf(Particles[i].Radius * ScaleSize)));
							Particles[i].PosX = Particles[i].NextX;
							Particles[i].PosY = Particles[i].NextY;
						}
					}
					ParticleCurNum += Particles.size();
				}
			}
		}
	}
}

float FParticleSystem::CalculateFps(const float InElapsedTime)
{
	static constexpr int FpsCount = 8; // 每 8 次计算一次帧数
	static int Count = 0;
	static float TotalElapsedTime = 0;

	++Count;
	GraphicsTime = InElapsedTime;
	TotalElapsedTime += InElapsedTime;
	if (Count >= FpsCount)
	{
		GraphicsFps = static_cast(Count) / (TotalElapsedTime / 1000.0f);
		Count = 0;
		TotalElapsedTime = 0;
	}

	return GraphicsFps;
}

float FParticleSystem::CalculateDeltaTime(const clock_t InCurTime)
{
	static clock_t LastTime = clock();
	static const float LowLimit = 1000.0f / CustomLowFps;
	static const float HighLimit = 1000.0f / CustomHighFps;

	auto CurDeltaTime = static_cast(InCurTime - LastTime);

	if (CurDeltaTime > LowLimit)
	{
		CurDeltaTime = LowLimit;
	}
	if (CurDeltaTime < HighLimit)
	{
		CurDeltaTime = HighLimit;
	}

	SetDeltaTime(CurDeltaTime);
	LastTime = InCurTime;

	return CurDeltaTime;
}

void FParticleSystem::Delay(const clock_t Ms)
{
	static clock_t OldTime = clock();
	OldTime = clock();
	while ((clock() - OldTime) < Ms)
	{
		Sleep(1);
	}
}

float FParticleSystem::LimitFps(const float InElapsedTime) const
{
	float OffsetTime = (1000.0f / CustomHighFps) - InElapsedTime;
	if (OffsetTime >= 0.01f)
	{
		Delay(static_cast(OffsetTime + 1));
	}
	else
	{
		OffsetTime = 0.0f;
	}
	return InElapsedTime + OffsetTime;
}

void FParticleSystem::OnEffectMouseField(const float InDeltaTime) const
{
	if (IsEffectMouseField)
	{
		static float Radius = 2.0f;
		static float Times = 1.5f;
		static float MouseFieldSpeed = 0.5f;
		static COLORREF Color = RGB(187, 34, 128);

		if (IsLeftMouseDown)
		{
			if (Times == 0.0f)
			{
				Times = 1.5f;
			}
			else
			{
				Times += 1.5f;
			}
			Times -= MouseFieldSpeed * InDeltaTime;
			Radius += MouseFieldSpeed * InDeltaTime;
			setfillcolor(Color);
			setlinecolor(Color);
			fillcircle(static_cast(CurMousePosX + 0.01f * MouseVelX),
						static_cast(CurMousePosY + 0.01f * MouseVelY),
						static_cast(max(Radius, 5.0f)));
		}
		else
		{
			if (Times > 0.0f)
			{
				Times -= MouseFieldSpeed * InDeltaTime;
				Radius -= MouseFieldSpeed * InDeltaTime;
				setfillcolor(Color);
				setlinecolor(Color);
				fillcircle(static_cast(CurMousePosX),
							static_cast(CurMousePosY),
							static_cast(max(Radius, 2.0f)));
			}
			else
			{
				Times = 0.0f;
				Color = RGB(Utlis::GenerateRandomNumber(255), Utlis::GenerateRandomNumber(255),
							Utlis::GenerateRandomNumber(255));
			}
		}
	}
}

void FParticleSystem::OnEffectDisappearTrack(const float InDeltaTime) const
{
	if (IsEffectDisappearTrack)
	{
		for (int i = static_cast(Width * Height) - 1; i >= 0; --i)
		{
			if (GBuffer[i] != 0)
			{
				GBuffer[i] = RGB(GetRValue(GBuffer[i]) >> 1, GetGValue(GBuffer[i]) >> 1, GetBValue(GBuffer[i]) >> 1);
			}
		}
	}
}

void FParticleSystem::OnEffectReboundWall(const float InDeltaTime, FParticle& InParticle) const
{
	if (IsEffectReboundWall)
	{
		static float ReboundLoss = 0.85f;
		if (InParticle.NextX > Width)
		{
			InParticle.NextX = Width;
			InParticle.VelX *= -1 * ReboundLoss;
		}
		else if (InParticle.NextX < 0)
		{
			InParticle.NextX = 0;
			InParticle.VelX *= -1 * ReboundLoss;
		}
		if (InParticle.NextY > Height)
		{
			InParticle.NextY = Height;
			InParticle.VelY *= -1 * ReboundLoss;
		}
		else if (InParticle.NextY < 0)
		{
			InParticle.NextY = 0;
			InParticle.VelY *= -1 * ReboundLoss;
		}
	}
}

void FParticleSystem::OnEffectRadiusAboutSpeed(const float InDeltaTime, FParticle& InParticle) const
{
	if (IsEffectRadiusAboutSpeed)
	{
		const float AvgVx = fabs(InParticle.VelX);
		const float AvgVy = fabs(InParticle.VelY);
		const float AvgV = (AvgVx + AvgVy) * 0.5f;

		InParticle.Radius = AvgV * 0.45f + 0.5f;
		InParticle.Radius = max(min(InParticle.Radius, 1.66f), 0.4f);
	}
}

void FParticleSystem::OnEffectBlow(const float InDeltaTime, FParticle& InParticle) const
{
	if (IsEffectBlow)
	{
		float DisX = InParticle.PosX - CurMousePosX;
		float DisY = InParticle.PosY - CurMousePosY;
		const float Radius = sqrt(DisX * DisX + DisY * DisY);
		DisX = Radius > 0.01f ? DisX / Radius : 0;
		DisY = Radius > 0.01f ? DisY / Radius : 0;

		if (IsRightMouseDown && Radius < BlowDis)
		{
			const float BlowAcc = (1 - (Radius / BlowDis)) * 14;
			InParticle.VelX += DisX * BlowAcc * BlowSpeed * InDeltaTime + 0.5f - static_cast(
				Utlis::GenerateRandomNumber()) / RAND_MAX;
			InParticle.VelY += DisY * BlowAcc * BlowSpeed * InDeltaTime + 0.5f - static_cast(
				Utlis::GenerateRandomNumber()) / RAND_MAX;
		}
	}
}

void FParticleSystem::OnEffectGather(const float InDeltaTime, FParticle& InParticle) const
{
	if (IsEffectGather)
	{
		float DisX = InParticle.PosX - CurMousePosX;
		float DisY = InParticle.PosY - CurMousePosY;
		const float Radius = sqrt(DisX * DisX + DisY * DisY);

		DisX = Radius > 0.01f ? DisX / Radius : 0;
		DisY = Radius > 0.01f ? DisY / Radius : 0;

		if (IsLeftMouseDown && Radius < GatherDis)
		{
			const float GatherAcc = (1 - (Radius / GatherDis)) * Width * 0.0014f;
			InParticle.VelX -= DisX * GatherAcc * GatherSpeed * InDeltaTime;
			InParticle.VelY -= DisY * GatherAcc * GatherSpeed * InDeltaTime;
		}
	}
}

void FParticleSystem::OnEffectStir(const float InDeltaTime, FParticle& InParticle) const
{
	if (IsEffectStir)
	{
		const float DisX = InParticle.PosX - CurMousePosX;
		const float DisY = InParticle.PosY - CurMousePosY;
		const float Radius = sqrt(DisX * DisX + DisY * DisY);

		if (!IsLeftMouseDown && !IsRightMouseDown && Radius < StirDis)
		{
			const float StirAcc = (1 - (Radius / StirDis)) * Width * 0.00026f;
			InParticle.VelX += MouseVelX * StirAcc * StirSpeed * InDeltaTime;
			InParticle.VelY += MouseVelY * StirAcc * StirSpeed * InDeltaTime;
		}
	}
}

void FParticleSystem::OnEffectFriction(const float InDeltaTime, FParticle& InParticle) const
{
	if (IsEffectFriction)
	{
		InParticle.VelX *= InParticle.Friction;
		InParticle.VelY *= InParticle.Friction;
	}
}

void FParticleSystem::OnEffectVortex(const float InDeltaTime, FParticle& InParticle) const
{
	if (IsEffectVortex)
	{
		static std::vector Vortexes;
		static constexpr int VortexesNum = 3;
		if (Vortexes.empty())
		{
			for (int i = 0; i < VortexesNum; ++i)
			{
				float PosX = 10.0f + Utlis::GenerateRandomNumber(Width - 1, 1.0f);
				float PosY = 10.0f + Utlis::GenerateRandomNumber(Height - 1, 1.0f);
				float Radius = max(30.0f, 30.0f + Utlis::GenerateRandomNumber(29.0f, 1.0f));
				const float Speed = 20.0f + Utlis::GenerateRandomNumber(50.0f, 1.0f);
				if ((Width - PosX) <= 10)
				{
					PosX = Width - 20;
					Radius = 15.0f;
				}
				if ((Height - PosY) <= 10)
				{
					PosY = Height - 20;
					Radius = 15.0f;
				}
				Vortexes.push_back({{PosX, PosY}, Radius, Speed});
			}
		}
		else
		{
			for (int i = 0; i < VortexesNum; ++i)
			{
				const float Vx = InParticle.PosX - Vortexes[i].Position.X - 1;
				const float Vy = InParticle.PosY - Vortexes[i].Position.Y - 1;
				const float Distance = sqrt(Vx * Vx + Vy * Vy);
				static float RotationSpeed = 35.0f + Utlis::GenerateRandomNumber(10.0f, 1.0f);
				if (Distance <= Vortexes[i].Radius)
				{
					// 不学习数学是这样的
					// InParticle.PosX = Vortexes[i].Position.X + Distance * cos(acos(Vx / Distance)*114.6 + Rotate);
					// InParticle.PosY = Vortexes[i].Position.Y + Distance * sin(asin(Vy / Distance)*114.6 + Rotate);
					InParticle.PosX = Vortexes[i].Position.X + Vx * cos(RotationSpeed * InDeltaTime) + Vy * sin(
						Vortexes[i].RotateSpeed * InDeltaTime);
					InParticle.PosY = Vortexes[i].Position.Y + Vy * cos(RotationSpeed * InDeltaTime) - Vx * sin(
						Vortexes[i].RotateSpeed * InDeltaTime);
					if (Utlis::GenerateRandomNumber(10) > 7)
					{
						InParticle.PosX += Utlis::GenerateRandomSign() * Utlis::GenerateRandomNumber(5.0f, 1.0f);
						InParticle.PosY += Utlis::GenerateRandomSign() * Utlis::GenerateRandomNumber(5.0f, 1.0f);
					}
				}
			}
		}
	}
}

void FParticleSystem::OnEffectRandomMove(const float InDeltaTime, FParticle& InParticle) const
{
	if (IsEffectRandomMove)
	{
		float DisX = InParticle.PosX - CurMousePosX;
		float DisY = InParticle.PosY - CurMousePosY;
		const float Radius = sqrt(DisX * DisX + DisY * DisY);

		DisX = Radius > 0.01f ? DisX / Radius : 0;
		DisY = Radius > 0.01f ? DisY / Radius : 0;

		const float RandomAcc = Utlis::GenerateRandomSign() * Radius / RandomDis * 0.5f;
		InParticle.VelX += DisX * RandomAcc * RandomSpeed * InDeltaTime + 0.5f - static_cast(
			Utlis::GenerateRandomNumber()) / RAND_MAX;
		InParticle.VelY += DisY * RandomAcc * RandomSpeed * InDeltaTime + 0.5f - static_cast(
			Utlis::GenerateRandomNumber()) / RAND_MAX;

		const float AvgVx = fabs(InParticle.VelX);
		const float AvgVy = fabs(InParticle.VelY);
		if (AvgVx < 0.1f) InParticle.VelX *= static_cast(Utlis::GenerateRandomNumber()) / RAND_MAX * 2;
		if (AvgVy < 0.1f) InParticle.VelY *= static_cast(Utlis::GenerateRandomNumber()) / RAND_MAX * 2;
	}
}

void FParticleSystem::OnEffectScaleWithWheel(const float InDeltaTime, FParticle& InParticle) const
{
	if (IsEffectScaleWithWheel)
	{
		// nothing to do
	}
	else
	{
		ScaleSize = 1.0f;
	}
}

void FParticleSystem::OnEffectGravity(const float InDeltaTime, FParticle& InParticle) const
{
	if (IsEffectGravity)
	{
		if ((Height - InParticle.PosY) >= 5)
		{
			static float GravitySpeed = 1.0f;
			static float Gravity = 10.0f;
			InParticle.VelY += Gravity * GravitySpeed * InDeltaTime;
		}
	}
}

void FParticleSystem::OnEffectDeath(const float InDeltaTime, FParticle& InParticle) const
{
	if (IsEffectDeath)
	{
		static float GoDieSpeed = 1.0f;
		InParticle.LifeTime -= GoDieSpeed * InDeltaTime;
		if (InParticle.LifeTime <= 0)
		{
			InParticle.IsPendingKilled = true;
		}
	}
}

感谢看完!!!

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1034618.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号