120 "config root must be an object",
"particles.config"));
121 const Json obj =
root;
124 if (obj.has(
"preset")) {
125 std::string preset = obj.getString(
"preset");
126 if (!preset.empty())
emitter->applyPreset(preset);
129 if (obj.has(
"x") || obj.has(
"y")) {
132 if (obj.has(
"x"))
x = obj.getFloat(
"x",
x);
133 if (obj.has(
"y"))
y = obj.getFloat(
"y",
y);
137 if (obj.has(
"emissionRate"))
emitter->setEmissionRate(obj.getFloat(
"emissionRate",
emitter->getEmissionRate()));
138 if (obj.has(
"emissionRateOverDistance"))
139 emitter->setEmissionRateOverDistance(
140 obj.getFloat(
"emissionRateOverDistance",
emitter->getEmissionRateOverDistance()));
142 float lifeMin =
emitter->getParticleLifetimeMin();
143 float lifeMax =
emitter->getParticleLifetimeMax();
144 if (readVec2(obj,
"particleLife", lifeMin, lifeMax))
145 emitter->setParticleLifetime(lifeMin, lifeMax);
147 if (obj.has(
"lifeMin")) lifeMin = obj.getFloat(
"lifeMin", lifeMin);
148 if (obj.has(
"lifeMax")) lifeMax = obj.getFloat(
"lifeMax", lifeMax);
149 if (obj.has(
"lifeMin") || obj.has(
"lifeMax"))
emitter->setParticleLifetime(lifeMin, lifeMax);
152 if (obj.has(
"emitterLife"))
emitter->setEmitterLifetime(obj.getFloat(
"emitterLife",
emitter->getEmitterLifetime()));
153 if (obj.has(
"looping"))
emitter->setLooping(obj.getBool(
"looping",
emitter->getLooping()));
154 if (obj.has(
"playbackSpeed"))
emitter->setPlaybackSpeed(obj.getFloat(
"playbackSpeed",
emitter->getPlaybackSpeed()));
155 if (obj.has(
"fixedTimeStep")) {
156 const float step = obj.getFloat(
"fixedTimeStep",
emitter->getFixedTimeStep());
157 const int maxSteps = obj.has(
"maxSubSteps") ? obj.getInt(
"maxSubSteps", 8) :
emitter->config()->maxSubSteps;
160 if (obj.has(
"randomSeed"))
emitter->setRandomSeed(obj.getInt(
"randomSeed", 0));
161 if (obj.has(
"autoRandomSeed"))
emitter->setAutoRandomSeed(obj.getBool(
"autoRandomSeed",
true));
163 if (obj.has(
"direction"))
emitter->setDirection(obj.getFloat(
"direction",
emitter->getDirection()));
164 if (obj.has(
"spread"))
emitter->setSpread(obj.getFloat(
"spread",
emitter->getSpread()));
166 float speedMin = 0.f, speedMax = 0.f;
167 if (readVec2(obj,
"speed", speedMin, speedMax))
emitter->setSpeed(speedMin, speedMax);
169 float ax0 = 0, ay0 = 0, ax1 = 0, ay1 = 0;
170 if (readVec4(obj,
"linearAcceleration", ax0, ay0, ax1, ay1))
emitter->setLinearAcceleration(ax0, ay0, ax1, ay1);
172 float rad0 = 0, rad1 = 0;
173 if (readVec2(obj,
"radialAcceleration", rad0, rad1))
emitter->setRadialAcceleration(rad0, rad1);
175 float tan0 = 0, tan1 = 0;
176 if (readVec2(obj,
"tangentialAcceleration", tan0, tan1))
emitter->setTangentialAcceleration(tan0, tan1);
178 if (obj.has(
"emissionArea")) {
179 const Json
area = obj.get(
"emissionArea");
180 if (
area.isObject()) {
181 std::string
type =
area.has(
"type") ?
area.getString(
"type") :
"none";
182 float ax =
area.has(
"x") ?
area.getFloat(
"x", 0.f) : 0.f;
183 float ay =
area.has(
"y") ?
area.getFloat(
"y", 0.f) : 0.f;
186 }
else if (obj.has(
"areaType")) {
189 readVec2(obj,
"areaSize",
ax,
ay);
190 emitter->setEmissionArea(obj.getString(
"areaType"),
ax,
ay);
193 float pw =
emitter->getParticleWidth(), ph =
emitter->getParticleHeight();
194 if (readVec2(obj,
"particleSize", pw, ph))
emitter->setParticleSize(pw, ph);
196 float ss = 1.f, se = 1.f;
197 if (readVec2(obj,
"sizes", ss, se))
emitter->setSizes(ss, se);
199 if (obj.has(
"sizeVariation"))
emitter->setSizeVariation(obj.getFloat(
"sizeVariation",
emitter->getSizeVariation()));
201 float spin0 = 0, spin1 = 0;
202 if (readVec2(obj,
"spin", spin0, spin1))
emitter->setSpin(spin0, spin1);
204 float sr0 = 0, sr1 = 0;
205 if (readVec2(obj,
"startRotation", sr0, sr1))
emitter->setStartRotation(sr0, sr1);
207 if (obj.has(
"bursts")) {
208 const Json arr = obj.get(
"bursts");
211 for (
size_t i = 0; i < arr.size(); ++i) {
214 const Json item = arr.at(i);
215 if (item.isObject()) {
216 t = item.getFloat(
"time", 0.f);
217 count = item.getInt(
"count", 0);
218 }
else if (item.isArray() && item.size() >= 2) {
219 t = item.at(0).asFloat(0.f);
220 count = item.at(1).asInt(0);
227 if (obj.has(
"prewarm"))
emitter->setPrewarm(obj.getFloat(
"prewarm", 0.f));
229 float gx = 0, gy = 0;
230 if (readVec2(obj,
"gravity", gx, gy))
emitter->setGravity(gx, gy);
231 if (obj.has(
"damping"))
emitter->setDamping(obj.getFloat(
"damping", 0.f));
232 if (obj.has(
"limitVelocity"))
emitter->setLimitVelocity(obj.getFloat(
"limitVelocity", 0.f));
233 if (obj.has(
"velocityOverLifetime")) {
234 const Json arr = obj.get(
"velocityOverLifetime");
237 readCurveArray(arr,
emitter->config()->velocityCurve);
240 if (obj.has(
"inheritVelocity"))
emitter->setInheritVelocity(obj.getFloat(
"inheritVelocity", 0.f));
241 if (obj.has(
"simulationSpace"))
emitter->setSimulationSpace(obj.getString(
"simulationSpace"));
243 if (obj.has(
"noise")) {
244 const Json
n = obj.get(
"noise");
246 float strength =
n.has(
"strength") ?
n.getFloat(
"strength", 0.f) :
emitter->config()->noiseStrength;
247 float freq =
n.has(
"frequency") ?
n.getFloat(
"frequency", 1.f) :
emitter->config()->noiseFrequency;
248 float speed =
n.has(
"speed") ?
n.getFloat(
"speed", 1.f) :
emitter->config()->noiseSpeed;
251 }
else if (obj.has(
"noiseStrength")) {
253 obj.getFloat(
"noiseStrength", 0.f),
254 obj.has(
"noiseFrequency") ? obj.getFloat(
"noiseFrequency", 1.f) :
emitter->config()->noiseFrequency,
255 obj.has(
"noiseSpeed") ? obj.getFloat(
"noiseSpeed", 1.f) :
emitter->config()->noiseSpeed);
258 if (obj.has(
"collision")) {
259 const Json col = obj.get(
"collision");
260 if (col.isObject()) {
261 std::string mode = col.has(
"mode") ? col.getString(
"mode") :
"none";
262 float radius = col.has(
"radius") ? col.getFloat(
"radius", 0.f) :
emitter->config()->collisionRadius;
264 col.has(
"restitution") ? col.getFloat(
"restitution", 0.6f) :
emitter->config()->collisionRestitution;
266 col.has(
"lifetimeLoss") ? col.getFloat(
"lifetimeLoss", 0.f) :
emitter->config()->collisionLifetimeLoss;
270 if (obj.has(
"collisionBounds")) {
271 const Json cb = obj.get(
"collisionBounds");
273 bool enabled = cb.has(
"enabled") ? cb.getBool(
"enabled",
false) :
emitter->config()->collisionBoundsEnabled;
274 float minX = cb.has(
"minX") ? cb.getFloat(
"minX", 0.f) :
emitter->config()->boundsMinX;
275 float minY = cb.has(
"minY") ? cb.getFloat(
"minY", 0.f) :
emitter->config()->boundsMinY;
276 float maxX = cb.has(
"maxX") ? cb.getFloat(
"maxX", 0.f) :
emitter->config()->boundsMaxX;
277 float maxY = cb.has(
"maxY") ? cb.getFloat(
"maxY", 0.f) :
emitter->config()->boundsMaxY;
281 if (obj.has(
"worldCollision"))
emitter->setWorldCollision(obj.getBool(
"worldCollision",
false));
282 if (obj.has(
"motionVectorPolicy"))
emitter->setMotionVectorPolicy(obj.getString(
"motionVectorPolicy"));
284 if (obj.has(
"renderMode")) {
285 float stretch = obj.has(
"stretch") ? obj.getFloat(
"stretch", 1.f) :
emitter->config()->stretchFactor;
286 emitter->setRenderMode(obj.getString(
"renderMode"), stretch);
287 }
else if (obj.has(
"stretch")) {
288 emitter->setRenderMode(
"stretched", obj.getFloat(
"stretch", 1.f));
290 if (obj.has(
"renderAxis"))
emitter->setRenderAxis(obj.getFloat(
"renderAxis", 0.f));
291 if (obj.has(
"ribbon")) {
292 const Json ribbon = obj.get(
"ribbon");
293 if (ribbon.isObject()) {
294 const float width = ribbon.has(
"width") ? ribbon.getFloat(
"width", 1.f) : 1.f;
295 const float minSegment = ribbon.has(
"minSegmentLength") ? ribbon.getFloat(
"minSegmentLength", 1.f) : 1.f;
299 if (obj.has(
"sortMode"))
emitter->setSortMode(obj.getString(
"sortMode"));
300 if (obj.has(
"materialMode"))
emitter->setMaterialMode(obj.getString(
"materialMode"));
301 if (obj.has(
"material")) {
302 const Json
material = obj.get(
"material");
305 if (
material.has(
"distortionStrength"))
306 emitter->setDistortionStrength(
material.getFloat(
"distortionStrength", 8.f));
307 if (
material.has(
"normalTexture")) {
308 const std::string
path =
material.getString(
"normalTexture");
314 if (obj.has(
"parameters")) {
315 const Json parameters = obj.get(
"parameters");
316 if (parameters.isObject()) {
317 for (
const auto &
key : parameters.keys())
318 emitter->setFloatParameter(
key, parameters.getFloat(
key.c_str(), 0.f));
321 if (obj.has(
"parameterBindings")) {
322 const Json
bindings = obj.get(
"parameterBindings");
324 emitter->clearFloatParameterBindings();
325 for (
size_t i = 0; i <
bindings.size(); ++i) {
327 if (!
binding.isObject())
continue;
334 if (obj.has(
"softParticles")) {
335 const Json
soft = obj.get(
"softParticles");
336 if (
soft.isObject()) {
337 const bool enabled =
soft.has(
"enabled") ?
soft.getBool(
"enabled",
true) :
true;
338 const float depth =
soft.has(
"depth") ?
soft.getFloat(
"depth", 0.5f) : 0.5f;
339 const float fade =
soft.has(
"fadeDistance") ?
soft.getFloat(
"fadeDistance", 0.05f) : 0.05f;
343 if (obj.has(
"overflowMode"))
emitter->setOverflowMode(obj.getString(
"overflowMode"));
344 if (obj.has(
"maxDeltaTime"))
emitter->setMaxDeltaTime(obj.getFloat(
"maxDeltaTime", 0.f));
345 if (obj.has(
"gpuSimulation"))
emitter->setGpuSimulation(obj.getBool(
"gpuSimulation",
false));
346 if (obj.has(
"priority"))
emitter->setPriority(obj.getInt(
"priority", 0));
347 if (obj.has(
"minimumQuality"))
emitter->setMinimumQuality(obj.getInt(
"minimumQuality", 0));
348 if (obj.has(
"cullingMode"))
emitter->setCullingMode(obj.getString(
"cullingMode"));
349 if (obj.has(
"cullDistance"))
emitter->setCullDistance(obj.getFloat(
"cullDistance", 0.f));
350 if (obj.has(
"maxSpawnPerFrame"))
emitter->setMaxSpawnPerFrame(obj.getInt(
"maxSpawnPerFrame", 0));
352 if (obj.has(
"forceFields")) {
353 const Json arr = obj.get(
"forceFields");
356 for (
size_t i = 0; i < arr.size(); ++i) {
357 const Json
f = arr.at(i);
358 if (!
f.isObject())
continue;
359 float x =
f.getFloat(
"x", 0.f);
360 float y =
f.getFloat(
"y", 0.f);
361 float radius =
f.getFloat(
"radius", 0.f);
362 float strength =
f.getFloat(
"strength", 0.f);
363 float falloff =
f.has(
"falloff") ?
f.getFloat(
"falloff", 1.f) : 1.f;
369 if (obj.has(
"lights")) {
370 const Json lg = obj.get(
"lights");
372 bool enabled = lg.has(
"enabled") ? lg.getBool(
"enabled",
false) :
emitter->config()->lights.enabled;
373 float radius = lg.has(
"radius") ? lg.getFloat(
"radius", 120.f) :
emitter->config()->lights.radius;
374 float intensity = lg.has(
"intensity") ? lg.getFloat(
"intensity", 1.f) :
emitter->config()->lights.intensity;
375 float lr =
emitter->config()->lights.r;
376 float lg2 =
emitter->config()->lights.g;
377 float lb =
emitter->config()->lights.b;
378 readVec3(lg,
"color", lr, lg2, lb);
379 if (lg.has(
"r")) lr = lg.getFloat(
"r", lr);
380 if (lg.has(
"g")) lg2 = lg.getFloat(
"g", lg2);
381 if (lg.has(
"b")) lb = lg.getFloat(
"b", lb);
382 int maxL = lg.has(
"max") ? lg.getInt(
"max", 4) :
emitter->config()->lights.max;
387 float r = 1,
g = 1,
b = 1,
a = 1;
388 if (readVec4(obj,
"colorStart",
r,
g,
b,
a))
emitter->setColorStart(
r,
g,
b,
a);
389 if (readVec4(obj,
"colorEnd",
r,
g,
b,
a))
emitter->setColorEnd(
r,
g,
b,
a);
391 if (obj.has(
"colorOverLife")) {
392 const Json arr = obj.get(
"colorOverLife");
395 readGradientArray(arr,
emitter->config()->colorGradient);
399 if (obj.has(
"sizeOverLife")) {
400 const Json arr = obj.get(
"sizeOverLife");
403 readCurveArray(arr,
emitter->config()->sizeCurve);
407 if (obj.has(
"rotationOverLife")) {
408 const Json arr = obj.get(
"rotationOverLife");
411 readCurveArray(arr,
emitter->config()->rotationCurve);
415 if (obj.has(
"blendMode"))
emitter->setBlendMode(obj.getString(
"blendMode"));
417 if (obj.has(
"flipbook")) {
418 const Json fb = obj.get(
"flipbook");
421 int h = fb.has(
"hframes") ? fb.getInt(
"hframes", 1) :
c->hframes;
422 int v = fb.has(
"vframes") ? fb.getInt(
"vframes", 1) :
c->vframes;
423 float rate = fb.has(
"frameRate") ? fb.getFloat(
"frameRate", 0.f) :
c->frameRate;
424 float rs = fb.has(
"frameRandomStart") ? fb.getFloat(
"frameRandomStart", 0.f) :
c->frameRandomStart;
427 }
else if (obj.has(
"hframes") || obj.has(
"vframes") || obj.has(
"frameRate") || obj.has(
"frameRandomStart")) {
429 int h = obj.has(
"hframes") ? obj.getInt(
"hframes", 1) :
c->hframes;
430 int v = obj.has(
"vframes") ? obj.getInt(
"vframes", 1) :
c->vframes;
431 float rate = obj.has(
"frameRate") ? obj.getFloat(
"frameRate", 0.f) :
c->frameRate;
432 float rs = obj.has(
"frameRandomStart") ? obj.getFloat(
"frameRandomStart", 0.f) :
c->frameRandomStart;
436 if (obj.has(
"layer"))
emitter->setLayer(obj.getInt(
"layer",
emitter->getLayer()));
437 if (obj.has(
"visible"))
emitter->setVisible(obj.getBool(
"visible",
emitter->isVisible()));
439 if (obj.has(
"texture")) {
440 std::string texPath = obj.getString(
"texture");
441 emitter->resource()->texturePath = texPath;
442 tryLoadTexture(
emitter, texPath);
445 if (obj.has(
"autoReload"))
emitter->resource()->autoReload = obj.getBool(
"autoReload",
true);
447 if (obj.has(
"autoStart") && obj.getBool(
"autoStart",
false))
emitter->start();
ECS emitter entity. Script configures components; ParticleSimSystem / ParticleRenderSystem drive per-...
bool loadConfigFile(ParticleEmitter *emitter, const std::string &path, std::string *error)
Read path via Filesystem, apply, and bind Resource.path + modtime for hot reload. Returns false if fi...
eve::Result< void > applyConfigDocument(ParticleEmitter *emitter, eve::json::Value root)
Apply particle JSON config onto an emitter, including material texture resources. Does not change buf...
bool reloadConfigFile(ParticleEmitter *emitter, std::string *error)
Re-read Resource.path if set; updates modtime.
bool applyConfigText(ParticleEmitter *emitter, const std::string &json, std::string *error)
Parse JSON text and apply.