烟花
2020-1-21 ~ 2021-4-2
(11)
这是一个简单的烟花程序,纯代码实现,
代码总长约 260 行,若有心去看应该不难看懂,相关注释已写于源码中。
3D 到 2D 转换 和 物理轨迹严格按照公式计算,
其余个别参数根据估计调试而设置,不用深究。
程序用到的 EasyX 绘图 主要是 solidcircle 和 putpixel,
更多的是对 语言特性、基础数学和物理的应用,亦可见其重要性。
编程之路,需要的不仅仅是语言,愿君共勉。
最后祝大家新年快乐!
界面截图:
源代码:
/*
程序名称:
烟花(Fireworks)
简介:
用 EasyX 实现的一个简单的烟花程序
加入 z 轴以实现简单的 3D 视觉效果
物理计算时 1 像素等效 10cm
注:计算 3D->2D 时,未采用斜二测画法,而是不计 z 轴对 x-y 的影响
理由是:
1. 视觉的 z 轴是垂直窗口的
2. 这不是画三维图像,只是一些点的合集
3. 实测效果
环境:
VS2019 + EasyX_20190529(beta)
时间:
2020.1.20
作者:
Teternity(QQ:1926594835)
版权:
作者原创,无抄袭,不涉及版权问题,仅用作学习
*/
/* *************** HeadFiles *************** */
#include <easyx.h>
#include <cmath>
#include <ctime>
#include <list>
/* *************** GlobleValue *************** */
long ret_useless = 0; // Capture the _getwch() return value to eliminate warnings
const int GW = 640; // Screen width
const int GH = 480; // Screen height
const double g = 9.8; // Acceleration of gravity
const double PI = 3.1415926;
const int len_max = 80; // The maximum length of LightLine
const int h_max = GH - len_max; // The maximum height that LightLine can reach
const double v_max = sqrt(2 * g * h_max / 10.0); // Maximum initial velocity of LightLine
const int n_max = 5; // Maximum number of fireworks on the screen
/* *************** LightLine *************** */
class LightLine
{
public:
LightLine(int, double);
void Draw() const;
void Move();
bool Stopped() const { return v == 0; }
bool OverLine() const { return py < h_max * n_max / (n_max + 1); } // Necessary condition for the next fireworks to rise
int GetX() const { return px; }
int GetY() const { return py; }
private:
int px; // Position_x
int py; // Position_y
int len; // Length
double v; // Velocity (The -y axis is positiva)
clock_t ct = 0; // Recording time
};
LightLine::LightLine(int x = rand() % (GW - 80) + 40, double vv = (rand() % 20 + 76.0) / 100.0 * v_max) :px(x), py(h_max)
{
v = vv; // The initial velocity determines the height can be reached
len = int(v / v_max * len_max); // v : v_max = len : len_max
}
void LightLine::Draw() const
{
for (int j = py; j < py + len; ++j)
{
float hsv_v = 0.8f * (len - (j - py)) / len + 0.2f; // Gradient color
setfillcolor(HSVtoRGB(0, 1.0f, hsv_v));
solidcircle(px, j, 1);
}
}
void LightLine::Move()
{
if (v == 0)
return;
if (ct == 0)
{
ct = clock();
Draw();
return;
}
clock_t t = clock() - ct;
ct = clock();
double v_cur = v - g * t / 1000.0; // The -y axis is positiva for the velocity
if (v_cur > 0)
{
py += int(10 * (v_cur * v_cur - v * v) / 2 / g);
v = v_cur;
}
else
{
py -= int(10 * v * v / 2 / g);
v = 0;
}
len = int(v / v_max * len_max);
Draw();
}
/* *************** ParticleSwarm *************** */
class ParticleSwarm
{
struct Particle
{
int x;
int y;
int z = 0; // Z axis vertical screen inword
double vy; // The y axis is positiva for the velocity
Particle(int xx, int yy, double vv) :x(xx), y(yy), vy(vv) {}
};
public:
ParticleSwarm(int, int, float);
void Draw() const;
void Move();
bool Finish() const { return vec.size() <= 1; }
private:
double vx;
double vz = 0;
float hsv_h; // Color parameter
clock_t ct = 0;
std::list<Particle> vec; // For saving particles
};
ParticleSwarm::ParticleSwarm(int x, int y, float colorh = float(rand() % 256))
{
// Cylindrical coordinate to xyz (parameters: len, radian_xz, radian_yx)
hsv_h = colorh + rand() % 20;
hsv_h = hsv_h > 255 ? hsv_h - 256 : hsv_h;
double vm = v_max / 2 * (rand() % 5 + 15.0) / 20.0;
double radian_xz = (rand() % 360) * PI / 180;
double radian_yx = (rand() % 90) * PI / 180 + PI / 2;
vx = vm * cos(radian_yx) * cos(radian_xz);
vz = vm * cos(radian_yx) * sin(radian_xz);
double vy = vm * sin(radian_yx);
int len = rand() % 30 + 50;
while (len)
{
// Use len as time parameter
// You can change 200 to 50 to see the effect
int xx = x + int(10 * vx * len / 200.0);
int zz = int(10 * vz * len / 200.0);
double cvy = vy - g * len / 200.0;
int yy = y + int(10 * (cvy * cvy - vy * vy) / 2 / g);
vec.push_back(Particle(xx, yy, cvy));
--len;
}
}
void ParticleSwarm::Draw() const
{
int n = 0;
auto size = vec.size();
for (auto& x : vec)
{
if (x.x >= 0 && x.x < GW && x.y >= 0 && x.y < GH)
{
float cv = 0.2f + 0.8f * (size - n) / size - x.z / 40 * 0.1f;
auto color = HSVtoRGB(hsv_h, 1.0f, cv > 0 ? cv : 0);
if (x.z < 0) // Z axis vertical screen inword
{
setfillcolor(color);
solidcircle(x.x, x.y, abs(x.z) / 80 > 1 ? 2 : 1);
}
else
putpixel(x.x, x.y, color);
}
++n;
}
}
void ParticleSwarm::Move()
{
if (ct == 0)
{
ct = clock();
Draw();
return;
}
for (int i = 0; i < 3 && vec.size() > 1; i++)
vec.pop_back(); // Delete particles for shortening length
clock_t t = clock() - ct;
ct = clock();
for (auto& x : vec)
{
double vy_cur = x.vy - g * t / 1000.0;
x.x += int(10 * vx * t / 1000.0);
x.y += int(10 * (vy_cur * vy_cur - x.vy * x.vy) / 2 / g);
x.z += int(10 * vz * t / 1000.0);
x.vy = vy_cur;
}
Draw();
}
/* *************** Fireworks *************** */
class Fireworks
{
public:
Fireworks(int, int);
void Move();
bool Empty() const { return vec.empty(); }
private:
std::list<ParticleSwarm> vec;
};
Fireworks::Fireworks(int x, int y)
{
bool colorful = rand() % 100 < 20 ? true : false;
float h = float(rand() % 256);
int n = rand() % 5 + 45;
for (int i = 0; i < n; i++)
{
if (colorful)
vec.push_back(ParticleSwarm(x, y));
else
vec.push_back(ParticleSwarm(x, y, h));
}
}
void Fireworks::Move()
{
std::list<decltype(vec.begin())> toDel;
for (auto it = vec.begin(); it != vec.end(); ++it)
{
if (it->Finish())
{
toDel.push_back(it);
continue;
}
it->Move();
}
for (auto& x : toDel)
vec.erase(x);
}
/* *************** main *************** */
int main()
{
initgraph(GW, GH);
setrop2(R2_MERGEPEN);
srand((unsigned)time(nullptr));
// Refresh once in 50ms
clock_t ct = clock();
// LightLine list
std::list<LightLine> vec;
vec.push_back(LightLine());
// Fireworks list
std::list<Fireworks> vec2;
BeginBatchDraw();
while (!(GetAsyncKeyState(VK_ESCAPE) & 0x8000))
{
if (clock() - ct > 50)
{
cleardevice();
ct = clock();
// LightLine list
std::list<decltype(vec.begin())> toDel;
if (vec.size() == 0)
vec.push_back(LightLine());
else if (vec.size() < n_max && rand() % 100 < 10 && (--vec.end())->OverLine())
vec.push_back(LightLine());
for (auto it = vec.begin(); it != vec.end(); ++it)
{
if (it->Stopped())
{
vec2.push_back(Fireworks(it->GetX(), it->GetY()));
toDel.push_back(it);
continue;
}
it->Move();
}
for (auto& it : toDel)
vec.erase(it);
// Fireworks list
std::list<decltype(vec2.begin())> toDel2;
for (auto it = vec2.begin(); it != vec2.end(); ++it)
{
if (it->Empty())
{
toDel2.push_back(it);
continue;
}
it->Move();
}
for (auto& it : toDel2)
vec2.erase(it);
FlushBatchDraw();
}
Sleep(1);
}
EndBatchDraw();
closegraph();
return 0;
}
这是怎么一回事?求解......