15#include <unordered_map>
22constexpr float kRad2Deg = 180.f / 3.14159265358979323846f;
24void sampleColor(
const ParticleEmitter::Config &cfg,
float t, Color &out) {
25 if (!cfg.colorGradient.empty()) {
27 cfg.colorGradient.sample(t, r, g,
b,
a);
31 t = t < 0.f ? 0.f : (t > 1.f ? 1.f : t);
32 out.r = cfg.colorStart.r + (cfg.colorEnd.r - cfg.colorStart.r) * t;
33 out.g = cfg.colorStart.g + (cfg.colorEnd.g - cfg.colorStart.g) * t;
34 out.b = cfg.colorStart.b + (cfg.colorEnd.b - cfg.colorStart.b) * t;
35 out.a = cfg.colorStart.a + (cfg.colorEnd.a - cfg.colorStart.a) * t;
38void sampleScale(
const ParticleEmitter::Config &cfg,
float t,
float &
scale) {
39 if (!cfg.sizeCurve.empty()) {
40 scale = cfg.sizeCurve.sample(t, 1.f);
43 t = t < 0.f ? 0.f : (t > 1.f ? 1.f : t);
44 scale = cfg.sizeStart + (cfg.sizeEnd - cfg.sizeStart) * t;
47void flipbookUV(
const ParticleEmitter::Config &cfg,
float frame,
float &u0,
float &v0,
float &u1,
49 const int total = cfg.hframes * cfg.vframes;
50 if (total <= 1 || cfg.hframes <= 0 || cfg.vframes <= 0) {
57 int fi = int(std::floor(frame));
58 fi = ((fi % total) + total) % total;
59 const int col = fi % cfg.hframes;
60 const int row = fi / cfg.hframes;
61 const float iw = 1.f / float(cfg.hframes);
62 const float ih = 1.f / float(cfg.vframes);
69void appendParticleItem(
const ParticleEmitter::Config &cfg,
const ParticleEmitter::Draw &draw,
70 const Particle &
p,
int order, std::vector<graphics::DrawItem2D> &out) {
71 const float t =
p.lifetime > 0.f ? 1.f - (
p.life /
p.lifetime) : 1.f;
73 sampleColor(cfg, t,
c);
75 sampleScale(cfg, t,
scale);
76 scale *=
p.size > 0.f ?
p.size : 1.f;
77 const float w = cfg.particleW *
scale;
78 const float h = cfg.particleH *
scale;
80 graphics::DrawItem2D item;
81 item.x =
p.x -
w * 0.5f;
82 item.y =
p.y -
h * 0.5f;
85 if (cfg.renderMode ==
"stretched") {
87 const float speed = std::sqrt(
p.vx *
p.vx +
p.vy *
p.vy);
88 const float len = std::max(
w, speed * cfg.stretchFactor);
90 item.rotation = std::atan2(
p.vy,
p.vx) * kRad2Deg;
92 item.rotation =
p.rot * kRad2Deg;
93 if (!cfg.rotationCurve.empty()) item.rotation += cfg.rotationCurve.sample(t, 0.f);
98 item.layer = draw.layer;
99 item.blend = draw.blend;
100 item.texture = draw.texture;
101 item.shader = draw.shader;
102 item.canvas = draw.canvas;
103 item.camera = draw.camera;
104 item.receiveLight =
false;
105 if (draw.texture && (cfg.hframes > 1 || cfg.vframes > 1)) {
106 flipbookUV(cfg,
p.frame, item.u0, item.v0, item.u1, item.v1);
113bool emitterOffscreen(
const ParticleEmitter::Config &cfg,
const ParticleEmitter::Draw &draw) {
114 auto *cam = draw.camera;
115 if (!cam)
return false;
116 auto *gfx = eve::ModuleManager::getInstance<eve::graphics::Graphics>(
"Graphics");
117 if (!gfx)
return false;
118 const float viewW = draw.canvas ? float(draw.canvas->getWidth()) : float(gfx->getWidth());
119 const float viewH = draw.canvas ? float(draw.canvas->getHeight()) : float(gfx->getHeight());
120 if (viewW <= 0.f || viewH <= 0.f)
return false;
121 const float z = cam->data()->zoom > 0.f ? cam->data()->zoom : 1e-4f;
122 const float sx = (cfg.x - cam->data()->x) *
z + viewW * 0.5f;
123 const float sy = (cfg.y - cam->data()->y) *
z + viewH * 0.5f;
124 const float maxHalf =
125 std::max(cfg.particleW, cfg.particleH) *
126 std::max(std::abs(cfg.sizeStart), std::abs(cfg.sizeEnd)) * 0.5f +
129 cfg.speedMax * cfg.lifeMax + std::max(cfg.areaX, cfg.areaY) + maxHalf;
133int64_t fileModtime(
const std::string &path) {
134 auto *fs = eve::ModuleManager::getInstance<eve::filesystem::Filesystem>(
"Filesystem");
135 if (!fs) fs = eve::filesystem::Filesystem::create();
137 if (!fs->getInfo(path, info))
return -1;
144 if (ecs::current()->getManager<ParticleEmitter>() ==
nullptr)
return;
149 for (
auto it =
view.begin(); it !=
view.end(); ++it) {
150 auto [cfg, sim, draw, attach, skinSrc, gpuSim] = *it;
152 if (sim->alive <= 0 && emitterOffscreen(*cfg, *draw))
continue;
168 if (ecs::current()->getManager<ParticleEmitter>() ==
nullptr)
return;
170 std::vector<graphics::DrawItem2D> items;
173 bool anyCanvas =
false;
175 for (
auto it =
view.begin(); it !=
view.end(); ++it) {
176 auto [cfg, sim, draw] = *it;
177 if (!draw->visible || sim->alive <= 0)
continue;
178 if (draw->canvas) anyCanvas =
true;
179 for (
int i = 0; i < sim->alive; ++i) {
180 appendParticleItem(*cfg, *draw, sim->particles[
size_t(i)], order++, items);
183 if (items.empty())
return;
192 if (ecs::current()->getManager<ParticleEmitter>() ==
nullptr)
return;
196 std::vector<ParticleEmitter *> emitters;
198 auto view = ecs::View<ParticleEmitter, ParticleEmitter::Config>();
199 for (
auto it =
view.begin(); it !=
view.end(); ++it) {
201 if (cfg->entity) emitters.push_back(cfg->entity);
206 for (
auto *em : emitters) {
207 auto cfg = em->config();
208 auto sim = em->sim();
209 auto draw = em->draw();
210 auto lights = em->lights();
211 if (!cfg->lights.enabled) {
212 for (
auto *l : lights->pool)
213 if (l) l->setEnabled(
false);
216 const int maxL = cfg->lights.max > 0 ? (cfg->lights.max > 8 ? 8 : cfg->lights.max) : 0;
217 while (
int(lights->pool.size()) < maxL)
219 const int n = sim->alive < maxL ? sim->alive : maxL;
220 for (
int i = 0; i < maxL; ++i) {
223 const Particle &
p = sim->particles[size_t(i)];
226 l->
setColor(cfg->lights.r, cfg->lights.g, cfg->lights.b, cfg->lights.intensity);
237 if (ecs::current()->getManager<ParticleEmitter>() ==
nullptr)
return 0;
242 ecs::View<ParticleEmitter, ParticleEmitter::Config, ParticleEmitter::Resource>();
243 for (
auto it =
view.begin(); it !=
view.end(); ++it) {
244 auto [cfg, res] = *it;
245 if (!res->autoReload || res->path.empty() || !cfg->entity)
continue;
247 const int64_t mt = fileModtime(res->path);
248 if (mt < 0 || mt == res->modtime)
continue;
virtual void setCanvas(Canvas *canvas)=0
nullptr or this → screen. Switching flushes pending draws to the previous target.
Declarative 2D light. Collected by RenderSystem (max 8 per canvas/frame). type: "point" | "dir" (≤15 ...
void setPosition(float x, float y)
void setColor(float r, float g, float b, float intensity=1.f)
void setCanvas(Canvas *canvas)
void setEnabled(bool enabled)
static Light2D * createLight(const std::string &type="point")
void setRadius(float radius)
static void drawItems(Graphics &gfx, std::vector< DrawItem2D > &items, bool present)
Sort and draw items. If present=true, calls gfx.present() at the end. Map::render uses present=false ...
ECS emitter entity. Script configures components; ParticleSimSystem / ParticleRenderSystem drive per-...
static void render(graphics::Graphics *gfx)
static void update(float dt)
glm::vec4 Color
RGBA color used by every graphics draw call. Lives inside eve::graphics so including a graphics heade...
bool reloadConfigFile(ParticleEmitter *emitter, std::string *error)
Re-read Resource.path if set; updates modtime.
void stepEmitterSim(ParticleEmitter::Config &cfg, ParticleEmitter::Sim &sim, float dt)
void syncEmitterSources(ParticleEmitter::Config &cfg, ParticleEmitter::Sim &, ParticleEmitter::Attach &attach, ParticleEmitter::SkinSource &skinSrc)
Sync bone attach + refresh skin cache; call before stepEmitterSim when using Attach/SkinSource.
WidgetDesc row(std::vector< WidgetDesc > children, std::string id)
Horizontal elastic layout row.
Optional bone attachment. When enabled, syncAttach() writes Config.x/y (and optionally direction) fro...
GPU-accelerated simulation state (see ParticleGpuKernel.h for layout).
Optional skinned-mesh surface source. When enabled, newly spawned particles sample random (optionally...
Single live particle (CPU simulation).