ObjectParser.cpp 43 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108
  1. #include "ObjectParser.h"
  2. #include "EffectParser.h"
  3. #include "MaterialParser.h"
  4. #include "ModelParser.h"
  5. #include "ShaderConstantParser.h"
  6. #include "UserSettingParser.h"
  7. #include "WallpaperEngine/Data/Model/Object.h"
  8. #include "WallpaperEngine/Data/Model/Project.h"
  9. #include "WallpaperEngine/Data/Model/ScriptedDynamicValue.h"
  10. #include "WallpaperEngine/Logging/Log.h"
  11. #include <glm/gtc/constants.hpp>
  12. #include <sstream>
  13. using namespace WallpaperEngine::Data::Parsers;
  14. using namespace WallpaperEngine::Data::Model;
  15. namespace {
  16. // Wraps a string value in a UserSetting with a String-typed DynamicValue.
  17. // UserSettingParser would try to numeric-parse single-char strings like "-"/":",
  18. // so we bypass it for raw string script-property values.
  19. UserSettingUniquePtr makeStringSetting (const std::string& s) {
  20. auto dv = std::make_unique<DynamicValue> ();
  21. dv->update (s);
  22. return std::make_unique<UserSetting> (UserSetting {
  23. .value = std::move (dv),
  24. .property = nullptr,
  25. .condition = std::nullopt,
  26. });
  27. }
  28. // Resolves the `script` field of a scripted text node: if it looks like a
  29. // single-line path ending in .js, read it through the asset locator.
  30. // Returns empty string on failure (caller treats empty script as static).
  31. std::string resolveScriptSource (std::string raw, const Project& project) {
  32. const bool looksLikePath =
  33. !raw.empty () && raw.find ('\n') == std::string::npos && raw.size () >= 3
  34. && raw.compare (raw.size () - 3, 3, ".js") == 0;
  35. if (!looksLikePath || project.assetLocator == nullptr)
  36. return raw;
  37. try {
  38. return project.assetLocator->readString (raw);
  39. } catch (const std::exception& e) {
  40. sLog.error ("CText: cannot load script asset '", raw, "': ", e.what ());
  41. return {};
  42. }
  43. }
  44. // Parses a text object's `scriptproperties` subtree into a map of UserSettings.
  45. // Plain string values bypass UserSettingParser (which stof-throws on "-"/":"),
  46. // object values with a string `value` field also go through the string path.
  47. std::map<std::string, UserSettingUniquePtr>
  48. parseScriptProperties (const JSON& propsObj, const Properties& properties) {
  49. std::map<std::string, UserSettingUniquePtr> out;
  50. for (const auto& [key, propData] : propsObj.items ()) {
  51. try {
  52. if (propData.is_string ()) {
  53. out.emplace (key, makeStringSetting (propData.template get<std::string> ()));
  54. continue;
  55. }
  56. if (propData.is_object ()) {
  57. if (const auto valueField = propData.find ("value");
  58. valueField != propData.end () && valueField->is_string ()) {
  59. out.emplace (key, makeStringSetting (valueField->template get<std::string> ()));
  60. continue;
  61. }
  62. }
  63. out.emplace (key, UserSettingParser::parse (propData, properties));
  64. } catch (const std::exception& e) {
  65. sLog.error ("CText: failed to parse scriptProperty '", key, "': ", e.what ());
  66. }
  67. }
  68. return out;
  69. }
  70. // The `color` user setting may arrive as vec3 or ivec3; normalize to vec4 with
  71. // full alpha so downstream renderers can assume a uniform type.
  72. void widenColorToVec4 (DynamicValue& v) {
  73. if (v.getType () == DynamicValue::UnderlyingType::Vec3) {
  74. v.update (glm::vec4 (v.getVec3 (), 1.0f));
  75. } else if (v.getType () == DynamicValue::UnderlyingType::IVec3) {
  76. const glm::ivec3 icolor = v.getIVec3 ();
  77. v.update (glm::vec4 (glm::vec3 (icolor), 255.0f) / 255.0f);
  78. }
  79. }
  80. static void bindScriptContext (
  81. const UserSettingUniquePtr& setting,
  82. int objectId,
  83. const std::string& objectName,
  84. const std::string& propertyName
  85. ) {
  86. if (!setting || !setting->value) {
  87. return;
  88. }
  89. auto* scripted = dynamic_cast<ScriptedDynamicValue*> (setting->value.get ());
  90. if (!scripted) {
  91. return;
  92. }
  93. scripted->setBindingContext (ScriptBindingContext {
  94. .objectId = objectId,
  95. .objectName = objectName,
  96. .propertyName = propertyName,
  97. });
  98. }
  99. } // namespace
  100. ObjectUniquePtr ObjectParser::parse (const JSON& it, const Project& project) {
  101. const auto imageIt = it.find ("image");
  102. const auto soundIt = it.find ("sound");
  103. const auto particleIt = it.find ("particle");
  104. const auto textIt = it.find ("text");
  105. const auto lightIt = it.find ("light");
  106. // use shape to refer to VolumeLight
  107. const auto shapeIt = it.find ("shape");
  108. // Parse base object data
  109. // Some particle objects have numeric 'name' fields, so handle type mismatches gracefully
  110. ObjectData basedata;
  111. try {
  112. basedata = ObjectData {
  113. .id = it.require<int> ("id", "Object must have an id"),
  114. .name = it.require<std::string> ("name", "Object must have a name"),
  115. .dependencies = parseDependencies (it),
  116. .parent = it.optional<int> ("parent"),
  117. .origin = it.user ("origin", project.properties, glm::vec3 (0.0f)),
  118. .groupScale = it.user ("scale", project.properties, glm::vec3 (1.0f)),
  119. .groupAngles = it.user ("angles", project.properties, glm::vec3 (0.0f)),
  120. .groupVisible = it.user ("visible", project.properties, true),
  121. };
  122. } catch (const std::exception& e) {
  123. sLog.error ("Error parsing object base data: ", e.what ());
  124. const auto idIt = it.find ("id");
  125. const auto nameIt = it.find ("name");
  126. int id = (idIt != it.end () && idIt->is_number ()) ? idIt->get<int> () : -1;
  127. std::string name = "unknown";
  128. if (nameIt != it.end ()) {
  129. if (nameIt->is_string ()) {
  130. name = nameIt->get<std::string> ();
  131. } else if (nameIt->is_number ()) {
  132. name = std::to_string (nameIt->get<int> ());
  133. }
  134. }
  135. basedata = ObjectData {
  136. .id = id,
  137. .name = name,
  138. .dependencies = parseDependencies (it),
  139. .parent = it.optional<int> ("parent"),
  140. .origin = it.user ("origin", project.properties, glm::vec3 (0.0f)),
  141. .groupScale = it.user ("scale", project.properties, glm::vec3 (1.0f)),
  142. .groupAngles = it.user ("angles", project.properties, glm::vec3 (0.0f)),
  143. .groupVisible = it.user ("visible", project.properties, true),
  144. };
  145. }
  146. bindScriptContext (basedata.origin, basedata.id, basedata.name, "origin");
  147. bindScriptContext (basedata.groupScale, basedata.id, basedata.name, "scale");
  148. bindScriptContext (basedata.groupAngles, basedata.id, basedata.name, "angles");
  149. bindScriptContext (basedata.groupVisible, basedata.id, basedata.name, "visible");
  150. if (imageIt != it.end () && imageIt->is_string ()) {
  151. return parseImage (it, project, std::move (basedata), *imageIt);
  152. } else if (soundIt != it.end () && soundIt->is_array ()) {
  153. return parseSound (it, std::move (basedata));
  154. } else if (particleIt != it.end ()) {
  155. return parseParticle (it, project, std::move (basedata));
  156. } else if (textIt != it.end ()) {
  157. return parseText (it, project, std::move (basedata));
  158. } else if (lightIt != it.end ()) {
  159. sLog.error ("Light objects are not supported yet");
  160. } else if (shapeIt != it.end ()) {
  161. sLog.error ("VolumeLight objects are not supported yet");
  162. } else {
  163. if (!it.optional ("solid", false)) {
  164. // dump the object for now, might want to change later
  165. // TODO: RE-EVALUATE IF THIS MAKES SENSE, THERE'S OBJECTS THAT CONTAIN OTHER OBJECTS AND THUS AREN'T REALLY
  166. // ANYTHING SPECIAL
  167. sLog.error ("Unknown object type found: ", it.dump ());
  168. }
  169. }
  170. return std::make_unique<Object> (std::move (basedata));
  171. }
  172. std::vector<int> ObjectParser::parseDependencies (const JSON& it) {
  173. const auto dependenciesIt = it.find ("dependencies");
  174. if (dependenciesIt == it.end () || !dependenciesIt->is_array ()) {
  175. return {};
  176. }
  177. std::vector<int> result = {};
  178. for (const auto& cur : *dependenciesIt) {
  179. result.push_back (cur);
  180. }
  181. return result;
  182. }
  183. SoundUniquePtr ObjectParser::parseSound (const JSON& it, ObjectData base) {
  184. const auto soundIt = it.require ("sound", "Object must have a sound");
  185. std::vector<std::string> sounds = {};
  186. for (const auto& cur : soundIt) {
  187. sounds.push_back (cur);
  188. }
  189. return std::make_unique<Sound> (
  190. std::move (base),
  191. SoundData {
  192. .playbackmode = it.optional<std::string> ("playbackmode"),
  193. .sounds = sounds,
  194. }
  195. );
  196. }
  197. TextUniquePtr ObjectParser::parseText (const JSON& it, const Project& project, ObjectData base) {
  198. const auto& properties = project.properties;
  199. const auto textIt = it.require ("text", "Text object must have a text field");
  200. std::string text;
  201. std::string script;
  202. std::map<std::string, UserSettingUniquePtr> scriptProps;
  203. if (textIt.is_string ()) {
  204. text = textIt.get<std::string> ();
  205. } else if (textIt.is_object ()) {
  206. // Scripted text: carries the JS source (either inline or as a .js asset path),
  207. // an initial placeholder `value`, and typed initial values for scriptProperties.
  208. if (const auto scriptIt = textIt.optional<std::string> ("script"); scriptIt.has_value ())
  209. script = resolveScriptSource (*scriptIt, project);
  210. if (const auto valueIt = textIt.optional<std::string> ("value"); valueIt.has_value ())
  211. text = *valueIt;
  212. if (const auto propsIt = textIt.find ("scriptproperties"); propsIt != textIt.end () && propsIt->is_object ())
  213. scriptProps = parseScriptProperties (*propsIt, properties);
  214. }
  215. auto result = std::make_unique<Text> (
  216. std::move (base),
  217. TextData {
  218. .text = std::move (text),
  219. .script = std::move (script),
  220. .scriptProperties = std::move (scriptProps),
  221. .font = it.optional ("font", std::string ()),
  222. .pointsize = it.optional ("pointsize", 32.0f),
  223. .size = it.optional ("size", glm::vec2 (0.0f)),
  224. .scale = it.user ("scale", properties, glm::vec3 (1.0f)),
  225. .color = it.user ("color", properties, glm::vec4 (1.0f)),
  226. .alpha = it.user ("alpha", properties, 1.0f),
  227. .visible = it.user ("visible", properties, true),
  228. .alignment = it.optional ("horizontalalign", it.optional ("alignment", std::string ("center"))),
  229. .verticalalign = it.optional ("verticalalign", std::string ("center")),
  230. .padding = it.optional ("padding", 0),
  231. }
  232. );
  233. widenColorToVec4 (*result->color->value);
  234. bindScriptContext (result->visible, result->id, result->name, "visible");
  235. bindScriptContext (result->color, result->id, result->name, "color");
  236. bindScriptContext (result->alpha, result->id, result->name, "alpha");
  237. bindScriptContext (result->scale, result->id, result->name, "scale");
  238. return result;
  239. }
  240. ImageUniquePtr
  241. ObjectParser::parseImage (const JSON& it, const Project& project, ObjectData base, const std::string& image) {
  242. const auto& properties = project.properties;
  243. const auto& effects = it.optional ("effects");
  244. const auto& animationLayers = it.optional ("animationlayers");
  245. auto result = std::make_unique<Image> (
  246. std::move (base),
  247. ImageData {
  248. .scale = it.user ("scale", properties, glm::vec3 (1.0f)),
  249. .angles = it.user ("angles", properties, glm::vec3 (0.0)),
  250. .visible = it.user ("visible", properties, true),
  251. .alpha = it.user ("alpha", properties, 1.0f),
  252. .color = it.user ("color", properties, glm::vec4 (1.0f)),
  253. .alignment = it.optional ("horizontalalign", it.optional ("alignment", std::string ("center"))),
  254. .size = it.user ("size", project.properties, glm::vec2 (0.0f))->value->getVec2 (),
  255. .parallaxDepth = it.user ("parallaxDepth", properties, glm::vec2 (0.0f)),
  256. .colorBlendMode = it.user ("colorBlendMode", properties, 0),
  257. .brightness = it.user ("brightness", properties, 1.0f),
  258. .model = ModelParser::load (project, image),
  259. .effects = effects.has_value () ? parseEffects (*effects, project) : std::vector<ImageEffectUniquePtr> {},
  260. .animationLayers = animationLayers.has_value () ? parseAnimationLayers (*animationLayers, project)
  261. : std::vector<ImageAnimationLayerUniquePtr> {},
  262. }
  263. );
  264. // color should be a vec4 for alpha, but it's read as vec3
  265. if (result->color->value->getType () == DynamicValue::UnderlyingType::Vec3) {
  266. result->color->value->update (glm::vec4 (result->color->value->getVec3 (), 1.0f));
  267. } else if (result->color->value->getType () == DynamicValue::UnderlyingType::IVec3) {
  268. result->color->value->update (glm::vec4 (result->color->value->getIVec3 (), 255));
  269. }
  270. bindScriptContext (result->scale, result->id, result->name, "scale");
  271. bindScriptContext (result->angles, result->id, result->name, "angles");
  272. bindScriptContext (result->visible, result->id, result->name, "visible");
  273. bindScriptContext (result->alpha, result->id, result->name, "alpha");
  274. bindScriptContext (result->color, result->id, result->name, "color");
  275. bindScriptContext (result->parallaxDepth, result->id, result->name, "parallaxDepth");
  276. const auto instance = it.optional ("instance");
  277. if (instance.has_value () && instance->is_object () && !result->model->material->passes.empty ()) {
  278. auto& firstPass = **result->model->material->passes.begin ();
  279. const auto instanceTextures = instance->optional ("textures");
  280. if (instanceTextures.has_value ()) {
  281. const auto parsed = parseTextureMap (*instanceTextures);
  282. firstPass.textures.insert (parsed.begin (), parsed.end ());
  283. }
  284. const auto instanceUserTextures = instance->optional ("usertextures");
  285. if (instanceUserTextures.has_value ()) {
  286. const auto parsed = parseTextureMap (*instanceUserTextures);
  287. firstPass.usertextures.insert (parsed.begin (), parsed.end ());
  288. }
  289. }
  290. for (const auto& effect : result->effects) {
  291. for (const auto& pass : effect->passOverrides) {
  292. for (const auto& [name, constant] : pass->constants) {
  293. bindScriptContext (constant, result->id, result->name, name);
  294. }
  295. }
  296. }
  297. return result;
  298. }
  299. std::vector<ImageEffectUniquePtr> ObjectParser::parseEffects (const JSON& it, const Project& project) {
  300. if (!it.is_array ()) {
  301. return {};
  302. }
  303. std::vector<ImageEffectUniquePtr> result = {};
  304. for (const auto& cur : it) {
  305. result.push_back (parseEffect (cur, project));
  306. }
  307. return result;
  308. }
  309. ImageEffectUniquePtr ObjectParser::parseEffect (const JSON& it, const Project& project) {
  310. const auto& passsOverrides = it.optional ("passes");
  311. return std::make_unique<ImageEffect> (ImageEffect {
  312. .id = it.optional<int> ("id", -1),
  313. .name = it.optional<std::string> ("name", "Effect without name"),
  314. .visible = it.user ("visible", project.properties, true),
  315. .passOverrides = passsOverrides.has_value () ? parseEffectPassOverrides (passsOverrides.value (), project)
  316. : std::vector<ImageEffectPassOverrideUniquePtr> {},
  317. .effect = EffectParser::load (project, it.require ("file", "Image effect must have an effect")) });
  318. }
  319. std::vector<ImageEffectPassOverrideUniquePtr>
  320. ObjectParser::parseEffectPassOverrides (const JSON& it, const Project& project) {
  321. if (!it.is_array ()) {
  322. return {};
  323. }
  324. std::vector<ImageEffectPassOverrideUniquePtr> result = {};
  325. for (const auto& cur : it) {
  326. result.push_back (parseEffectPass (cur, project));
  327. }
  328. return result;
  329. }
  330. ImageEffectPassOverrideUniquePtr ObjectParser::parseEffectPass (const JSON& it, const Project& project) {
  331. const auto& combos = it.optional ("combos");
  332. const auto& textures = it.optional ("textures");
  333. const auto& constants = it.optional ("constantshadervalues");
  334. // TODO: PARSE CONSTANT SHADER VALUES AND FIND REFS?
  335. return std::make_unique<ImageEffectPassOverride> (ImageEffectPassOverride {
  336. .id = it.optional<int> ("id", -1),
  337. .combos = combos.has_value () ? parseComboMap (combos.value ()) : ComboMap {},
  338. .constants
  339. = constants.has_value () ? ShaderConstantParser::parse (constants.value (), project) : ShaderConstantMap {},
  340. .textures = textures.has_value () ? parseTextureMap (textures.value ()) : TextureMap {},
  341. });
  342. }
  343. TextureMap ObjectParser::parseTextureMap (const JSON& it) {
  344. if (!it.is_array ()) {
  345. return {};
  346. }
  347. TextureMap result = {};
  348. int textureIndex = -1;
  349. for (const auto& cur : it) {
  350. textureIndex++;
  351. if (cur.is_null ()) {
  352. continue;
  353. } else if (cur.is_object ()) {
  354. const auto nameIt = cur.find ("name");
  355. if (nameIt != cur.end () && nameIt->is_string ()) {
  356. result.emplace (textureIndex, nameIt->get<std::string> ());
  357. }
  358. } else {
  359. std::string texName = cur;
  360. if (!texName.empty ()) {
  361. result.emplace (textureIndex, texName);
  362. }
  363. }
  364. }
  365. return result;
  366. }
  367. ComboMap ObjectParser::parseComboMap (const JSON& it) {
  368. if (!it.is_object ()) {
  369. return {};
  370. }
  371. ComboMap result = {};
  372. for (const auto& cur : it.items ()) {
  373. result.emplace (cur.key (), cur.value ());
  374. }
  375. return result;
  376. }
  377. std::vector<ImageAnimationLayerUniquePtr> ObjectParser::parseAnimationLayers (const JSON& it, const Project& project) {
  378. if (!it.is_array ()) {
  379. return {};
  380. }
  381. std::vector<ImageAnimationLayerUniquePtr> result = {};
  382. for (const auto& cur : it.items ()) {
  383. result.push_back (parseAnimationLayer (cur.value (), project));
  384. }
  385. return result;
  386. }
  387. ImageAnimationLayerUniquePtr ObjectParser::parseAnimationLayer (const JSON& it, const Project& project) {
  388. const auto& properties = project.properties;
  389. return std::make_unique<ImageAnimationLayer> (ImageAnimationLayer {
  390. .id = it.require<int> ("id", "Animation layer must have an id"),
  391. .rate = it.user ("rate", properties, 1.0f),
  392. .visible = it.user ("visible", properties, false),
  393. .blend = it.user ("blend", properties, 1.0f),
  394. .animation = it.user ("animation", properties, 0),
  395. });
  396. }
  397. ParticleUniquePtr ObjectParser::parseParticle (const JSON& it, const Project& project, ObjectData base) {
  398. try {
  399. const auto& properties = project.properties;
  400. const auto particleIt = it.find ("particle");
  401. if (particleIt == it.end ()) {
  402. sLog.error ("Particle object must have a particle definition");
  403. return std::make_unique<Particle> (
  404. std::move (base),
  405. ParticleData {
  406. .scale = it.user ("scale", properties, glm::vec3 (1.0f)),
  407. .angles = it.user ("angles", properties, glm::vec3 (0.0f)),
  408. .visible = it.user ("visible", properties, true),
  409. .parallaxDepth = it.user ("parallaxDepth", properties, glm::vec2 (0.0f)),
  410. .particleFile = "",
  411. .animationMode = "sequence",
  412. .sequenceMultiplier = 1.0f,
  413. .maxCount = 100,
  414. .startTime = 0,
  415. .flags = 0,
  416. .material = nullptr,
  417. .emitters = {},
  418. .initializers = {},
  419. .operators = {},
  420. .renderers = {},
  421. .controlPoints = {},
  422. .children = {},
  423. .instanceOverride
  424. = { .enabled
  425. = std::make_unique<UserSetting> (UserSetting { .value = std::make_unique<DynamicValue> (false),
  426. .property = nullptr,
  427. .condition = std::nullopt }),
  428. .alpha
  429. = std::make_unique<UserSetting> (UserSetting { .value = std::make_unique<DynamicValue> (1.0f),
  430. .property = nullptr,
  431. .condition = std::nullopt }),
  432. .size
  433. = std::make_unique<UserSetting> (UserSetting { .value = std::make_unique<DynamicValue> (1.0f),
  434. .property = nullptr,
  435. .condition = std::nullopt }),
  436. .lifetime
  437. = std::make_unique<UserSetting> (UserSetting { .value = std::make_unique<DynamicValue> (1.0f),
  438. .property = nullptr,
  439. .condition = std::nullopt }),
  440. .rate
  441. = std::make_unique<UserSetting> (UserSetting { .value = std::make_unique<DynamicValue> (1.0f),
  442. .property = nullptr,
  443. .condition = std::nullopt }),
  444. .speed
  445. = std::make_unique<UserSetting> (UserSetting { .value = std::make_unique<DynamicValue> (1.0f),
  446. .property = nullptr,
  447. .condition = std::nullopt }),
  448. .count
  449. = std::make_unique<UserSetting> (UserSetting { .value = std::make_unique<DynamicValue> (1.0f),
  450. .property = nullptr,
  451. .condition = std::nullopt }),
  452. .color = std::make_unique<UserSetting> (UserSetting {
  453. .value = std::make_unique<DynamicValue> (glm::vec3 (1.0f)),
  454. .property = nullptr,
  455. .condition = std::nullopt }),
  456. .colorn = std::make_unique<UserSetting> (UserSetting {
  457. .value = std::make_unique<DynamicValue> (glm::vec3 (1.0f)),
  458. .property = nullptr,
  459. .condition = std::nullopt }) },
  460. }
  461. );
  462. }
  463. std::string particleFile;
  464. if (particleIt->is_string ()) {
  465. particleFile = particleIt->get<std::string> ();
  466. }
  467. // Load particle definition from file if it's a string reference
  468. JSON particleJson = JSON::object ();
  469. if (!particleFile.empty ()) {
  470. try {
  471. particleJson
  472. = WallpaperEngine::Data::JSON::JSON::parse (project.assetLocator->readString (particleFile));
  473. } catch (std::runtime_error& e) {
  474. sLog.error ("Cannot load particle file: ", particleFile, " - ", e.what ());
  475. }
  476. } else if (particleIt->is_object ()) {
  477. particleJson = *particleIt;
  478. }
  479. // Parse emitters (note: field is named "emitter" not "emitters")
  480. std::vector<ParticleEmitter> emitters;
  481. const auto emittersIt = particleJson.find ("emitter");
  482. if (emittersIt != particleJson.end () && emittersIt->is_array ()) {
  483. for (const auto& emitter : *emittersIt) {
  484. emitters.push_back (parseParticleEmitter (emitter));
  485. }
  486. }
  487. // Parse initializers (note: field is named "initializer" not "initializers")
  488. std::vector<ParticleInitializerUniquePtr> initializers;
  489. const auto initializersIt = particleJson.find ("initializer");
  490. if (initializersIt != particleJson.end () && initializersIt->is_array ()) {
  491. for (const auto& initializer : *initializersIt) {
  492. auto init = parseParticleInitializer (initializer, project.properties);
  493. if (init) {
  494. initializers.push_back (std::move (init));
  495. }
  496. }
  497. }
  498. // Parse operators (note: field is named "operator" not "operators")
  499. std::vector<ParticleOperatorUniquePtr> operators;
  500. const auto operatorsIt = particleJson.find ("operator");
  501. if (operatorsIt != particleJson.end () && operatorsIt->is_array ()) {
  502. for (const auto& op : *operatorsIt) {
  503. auto oper = parseParticleOperator (op, project.properties);
  504. if (oper) {
  505. operators.push_back (std::move (oper));
  506. }
  507. }
  508. }
  509. // Parse renderers (note: field is named "renderer" not "renderers")
  510. std::vector<ParticleRenderer> renderers;
  511. const auto renderersIt = particleJson.find ("renderer");
  512. if (renderersIt != particleJson.end () && renderersIt->is_array ()) {
  513. for (const auto& renderer : *renderersIt) {
  514. renderers.push_back (parseParticleRenderer (renderer));
  515. }
  516. }
  517. // Add default sprite renderer if none specified
  518. if (renderers.empty ()) {
  519. renderers.push_back (
  520. ParticleRenderer {
  521. .name = "sprite",
  522. .length = 0.05f,
  523. .maxLength = 10.0f,
  524. .minLength = 0.0f,
  525. .subdivision = 1.0f,
  526. .segments = 4.0f,
  527. .uvScale = 1.0f,
  528. .uvScrolling = false,
  529. .uvSmoothing = true,
  530. .fadeAlpha = false,
  531. .fadeSize = false,
  532. }
  533. );
  534. }
  535. // Parse control points (note: field is named "controlpoint" not "controlpoints")
  536. std::vector<ParticleControlPoint> controlPoints;
  537. const auto controlPointsIt = particleJson.find ("controlpoint");
  538. if (controlPointsIt != particleJson.end () && controlPointsIt->is_array ()) {
  539. for (const auto& cp : *controlPointsIt) {
  540. controlPoints.push_back (parseParticleControlPoint (cp));
  541. }
  542. }
  543. // Parse children
  544. std::vector<ParticleChild> children;
  545. const auto childrenIt = particleJson.optional ("children");
  546. if (childrenIt.has_value () && childrenIt->is_array ()) {
  547. for (const auto& child : *childrenIt) {
  548. children.push_back (parseParticleChild (child, project));
  549. }
  550. }
  551. // Parse instance override
  552. ParticleInstanceOverride instanceOverride = {
  553. .enabled = std::make_unique<UserSetting> (UserSetting {
  554. .value = std::make_unique<DynamicValue> (false), .property = nullptr, .condition = std::nullopt }),
  555. .alpha = std::make_unique<UserSetting> (UserSetting {
  556. .value = std::make_unique<DynamicValue> (1.0f), .property = nullptr, .condition = std::nullopt }),
  557. .size = std::make_unique<UserSetting> (UserSetting {
  558. .value = std::make_unique<DynamicValue> (1.0f), .property = nullptr, .condition = std::nullopt }),
  559. .lifetime = std::make_unique<UserSetting> (UserSetting {
  560. .value = std::make_unique<DynamicValue> (1.0f), .property = nullptr, .condition = std::nullopt }),
  561. .rate = std::make_unique<UserSetting> (UserSetting {
  562. .value = std::make_unique<DynamicValue> (1.0f), .property = nullptr, .condition = std::nullopt }),
  563. .speed = std::make_unique<UserSetting> (UserSetting {
  564. .value = std::make_unique<DynamicValue> (1.0f), .property = nullptr, .condition = std::nullopt }),
  565. .count = std::make_unique<UserSetting> (UserSetting {
  566. .value = std::make_unique<DynamicValue> (1.0f), .property = nullptr, .condition = std::nullopt }),
  567. .color
  568. = std::make_unique<UserSetting> (UserSetting { .value = std::make_unique<DynamicValue> (glm::vec3 (1.0f)),
  569. .property = nullptr,
  570. .condition = std::nullopt }),
  571. .colorn
  572. = std::make_unique<UserSetting> (UserSetting { .value = std::make_unique<DynamicValue> (glm::vec3 (1.0f)),
  573. .property = nullptr,
  574. .condition = std::nullopt })
  575. };
  576. const auto instanceOverrideIt = it.optional ("instanceoverride");
  577. if (instanceOverrideIt.has_value ()) {
  578. instanceOverride = parseParticleInstanceOverride (*instanceOverrideIt, project.properties);
  579. }
  580. // Parse material - particles reference materials directly, not models
  581. ModelUniquePtr material = nullptr;
  582. const auto materialIt = particleJson.find ("material");
  583. if (materialIt != particleJson.end () && materialIt->is_string ()) {
  584. try {
  585. std::string materialPath = materialIt->get<std::string> ();
  586. // Particle materials are stored as just material definitions, not model files
  587. // So we need to wrap them in a model structure
  588. auto mat = MaterialParser::load (project, materialPath);
  589. material = std::make_unique<ModelStruct> (ModelStruct {
  590. .filename = materialPath,
  591. .material = std::move (mat),
  592. .solidlayer = false,
  593. .fullscreen = false,
  594. .passthrough = false,
  595. .autosize = false,
  596. .nopadding = false,
  597. .width = std::nullopt,
  598. .height = std::nullopt,
  599. .puppet = std::nullopt,
  600. });
  601. } catch (std::runtime_error& e) {
  602. sLog.error ("Cannot load particle material: ", materialIt->get<std::string> (), " - ", e.what ());
  603. }
  604. }
  605. // Parse string fields safely
  606. std::string animationMode = "sequence";
  607. const auto animModeIt = particleJson.find ("animationmode");
  608. if (animModeIt != particleJson.end () && animModeIt->is_string ()) {
  609. animationMode = animModeIt->get<std::string> ();
  610. }
  611. // Parse numeric fields safely
  612. float sequenceMultiplier = 1.0f;
  613. uint32_t maxCount = 100;
  614. uint32_t startTime = 0;
  615. uint32_t flags = 0;
  616. const auto seqMultIt = particleJson.find ("sequencemultiplier");
  617. if (seqMultIt != particleJson.end () && seqMultIt->is_number ()) {
  618. sequenceMultiplier = seqMultIt->get<float> ();
  619. }
  620. const auto maxCountIt = particleJson.find ("maxcount");
  621. if (maxCountIt != particleJson.end () && maxCountIt->is_number ()) {
  622. maxCount = maxCountIt->get<uint32_t> ();
  623. }
  624. const auto startTimeIt = particleJson.find ("starttime");
  625. if (startTimeIt != particleJson.end () && startTimeIt->is_number ()) {
  626. startTime = startTimeIt->get<uint32_t> ();
  627. }
  628. const auto flagsIt = particleJson.find ("flags");
  629. if (flagsIt != particleJson.end () && flagsIt->is_number ()) {
  630. flags = flagsIt->get<uint32_t> ();
  631. }
  632. auto result = std::make_unique<Particle> (
  633. std::move (base),
  634. ParticleData {
  635. .scale = it.user ("scale", properties, glm::vec3 (1.0f)),
  636. .angles = it.user ("angles", properties, glm::vec3 (0.0f)),
  637. .visible = it.user ("visible", properties, true),
  638. .parallaxDepth = it.user ("parallaxDepth", properties, glm::vec2 (0.0f)),
  639. .particleFile = particleFile,
  640. .animationMode = animationMode,
  641. .sequenceMultiplier = sequenceMultiplier,
  642. .maxCount = maxCount,
  643. .startTime = startTime,
  644. .flags = flags,
  645. .material = std::move (material),
  646. .emitters = std::move (emitters),
  647. .initializers = std::move (initializers),
  648. .operators = std::move (operators),
  649. .renderers = std::move (renderers),
  650. .controlPoints = std::move (controlPoints),
  651. .children = std::move (children),
  652. .instanceOverride = std::move (instanceOverride),
  653. }
  654. );
  655. bindScriptContext (result->scale, result->id, result->name, "scale");
  656. bindScriptContext (result->angles, result->id, result->name, "angles");
  657. bindScriptContext (result->visible, result->id, result->name, "visible");
  658. bindScriptContext (result->parallaxDepth, result->id, result->name, "parallaxDepth");
  659. return result;
  660. } catch (nlohmann::json::exception& e) {
  661. sLog.error ("Error parsing particle '", base.name, "': ", e.what ());
  662. sLog.error ("Particle JSON: ", it.dump ());
  663. throw;
  664. }
  665. }
  666. ParticleEmitter ObjectParser::parseParticleEmitter (const JSON& it) {
  667. // Parse string name safely
  668. std::string name = "";
  669. const auto nameIt = it.find ("name");
  670. if (nameIt != it.end () && nameIt->is_string ()) {
  671. name = nameIt->get<std::string> ();
  672. }
  673. // Helper lambda to parse vec3 fields that might be strings, arrays, single numbers, or missing
  674. auto parseVec3 = [&] (const char* fieldName, const glm::vec3& defaultValue) -> glm::vec3 {
  675. const auto fieldIt = it.find (fieldName);
  676. if (fieldIt == it.end ()) {
  677. return defaultValue;
  678. }
  679. if (fieldIt->is_string ()) {
  680. return it.optional (fieldName, defaultValue);
  681. }
  682. if (fieldIt->is_number ()) {
  683. // Single number - use for all components (common for distancemax/distancemin)
  684. float val = fieldIt->get<float> ();
  685. return glm::vec3 (val, val, val);
  686. }
  687. if (fieldIt->is_array () && fieldIt->size () >= 3) {
  688. return glm::vec3 ((*fieldIt)[0].get<float> (), (*fieldIt)[1].get<float> (), (*fieldIt)[2].get<float> ());
  689. }
  690. return defaultValue;
  691. };
  692. auto parseIVec3 = [&] (const char* fieldName, const glm::ivec3& defaultValue) -> glm::ivec3 {
  693. const auto fieldIt = it.find (fieldName);
  694. if (fieldIt == it.end ()) {
  695. return defaultValue;
  696. }
  697. if (fieldIt->is_array () && fieldIt->size () >= 3) {
  698. return glm::ivec3 ((*fieldIt)[0].get<int> (), (*fieldIt)[1].get<int> (), (*fieldIt)[2].get<int> ());
  699. }
  700. return defaultValue;
  701. };
  702. auto parseVec2 = [&] (const char* fieldName, const glm::vec2& defaultValue) -> glm::vec2 {
  703. const auto fieldIt = it.find (fieldName);
  704. if (fieldIt == it.end ()) {
  705. return defaultValue;
  706. }
  707. if (fieldIt->is_string ()) {
  708. return it.optional (fieldName, defaultValue);
  709. }
  710. if (fieldIt->is_array () && fieldIt->size () >= 2) {
  711. return glm::vec2 ((*fieldIt)[0].get<float> (), (*fieldIt)[1].get<float> ());
  712. }
  713. return defaultValue;
  714. };
  715. try {
  716. return ParticleEmitter {
  717. .id = it.optional ("id", -1),
  718. .name = name,
  719. .directions = parseVec3 ("directions", glm::vec3 (1.0f, 1.0f, 0.0f)),
  720. .distanceMin = parseVec3 ("distancemin", glm::vec3 (0.0f, 0.0f, 0.0f)),
  721. .distanceMax = parseVec3 ("distancemax", glm::vec3 (256.0f, 256.0f, 0.0f)),
  722. .origin = parseVec3 ("origin", glm::vec3 (0.0f)),
  723. .sign = parseIVec3 ("sign", glm::ivec3 (0)),
  724. .instantaneous = it.optional ("instantaneous", 0u),
  725. .speedMin = it.optional ("speedmin", 0.0f),
  726. .speedMax = it.optional ("speedmax", 0.0f),
  727. .rate = it.optional ("rate", 10.0f),
  728. .controlPoint = it.optional ("controlpoint", 0),
  729. .flags = it.optional ("flags", 0u),
  730. .cone = it.optional ("cone", 0.0f),
  731. .delay = it.optional ("delay", 0.0f),
  732. .duration = it.optional ("duration", 0.0f),
  733. .audioProcessingBounds = parseVec2 ("audioprocessingbounds", glm::vec2 (0.8f, 1.0f)),
  734. .audioProcessingExponent = it.optional ("audioprocessingexponent", 2),
  735. .audioProcessingFrequencyStart = it.optional ("audioprocessingfrequencystart", 0),
  736. .audioProcessingFrequencyEnd = it.optional ("audioprocessingfrequencyend", 1),
  737. .audioProcessingMode = it.optional ("audioprocessingmode", 0),
  738. .minPeriodicDelay = it.optional ("minperiodicdelay", 1.0f),
  739. .maxPeriodicDelay = it.optional ("maxperiodicdelay", 2.0f),
  740. .minPeriodicDuration = it.optional ("minperiodicduration", 2.0f),
  741. .maxPeriodicDuration = it.optional ("maxperiodicduration", 3.0f),
  742. };
  743. } catch (nlohmann::json::exception& e) {
  744. sLog.error ("Error parsing emitter: ", e.what ());
  745. sLog.error ("Emitter JSON: ", it.dump ());
  746. throw;
  747. }
  748. }
  749. ParticleInitializerUniquePtr ObjectParser::parseParticleInitializer (const JSON& it, const Properties& properties) {
  750. std::string name = it.optional<std::string> ("name", "");
  751. if (name == "colorrandom") {
  752. // Only normalize if there's no property connection or values are > 1.0
  753. auto minSetting = it.user ("min", properties, glm::vec3 (0.0f));
  754. auto maxSetting = it.user ("max", properties, glm::vec3 (255.0f));
  755. auto minVec = minSetting->value->getVec3 ();
  756. if (minSetting->property == nullptr && (minVec.x > 1.0f || minVec.y > 1.0f || minVec.z > 1.0f)) {
  757. minSetting->value->update (minVec / 255.0f);
  758. }
  759. auto maxVec = maxSetting->value->getVec3 ();
  760. if (maxSetting->property == nullptr && (maxVec.x > 1.0f || maxVec.y > 1.0f || maxVec.z > 1.0f)) {
  761. maxSetting->value->update (maxVec / 255.0f);
  762. }
  763. return std::make_unique<ColorRandomInitializer> (std::move (minSetting), std::move (maxSetting));
  764. } else if (name == "sizerandom") {
  765. return std::make_unique<SizeRandomInitializer> (
  766. it.user ("min", properties, 0.0f), it.user ("max", properties, 20.0f),
  767. it.user ("exponent", properties, 1.0f)
  768. );
  769. } else if (name == "alpharandom") {
  770. return std::make_unique<AlphaRandomInitializer> (
  771. it.user ("min", properties, 0.05f), it.user ("max", properties, 1.0f)
  772. );
  773. } else if (name == "lifetimerandom") {
  774. return std::make_unique<LifetimeRandomInitializer> (
  775. it.user ("min", properties, 0.0f), it.user ("max", properties, 1.0f)
  776. );
  777. } else if (name == "velocityrandom") {
  778. return std::make_unique<VelocityRandomInitializer> (
  779. it.user ("min", properties, glm::vec3 (-32.0f)), it.user ("max", properties, glm::vec3 (32.0f))
  780. );
  781. } else if (name == "rotationrandom") {
  782. return std::make_unique<RotationRandomInitializer> (
  783. it.user ("min", properties, glm::vec3 (0.0f)),
  784. it.user ("max", properties, glm::vec3 (0.0f, 0.0f, glm::two_pi<float> ()))
  785. );
  786. } else if (name == "angularvelocityrandom") {
  787. return std::make_unique<AngularVelocityRandomInitializer> (
  788. it.user ("min", properties, glm::vec3 (0.0f, 0.0f, -5.0f)),
  789. it.user ("max", properties, glm::vec3 (0.0f, 0.0f, 5.0f)), it.user ("exponent", properties, 1.0f)
  790. );
  791. } else if (name == "turbulentvelocityrandom") {
  792. return std::make_unique<TurbulentVelocityRandomInitializer> (
  793. it.user ("speedmin", properties, 100.0f), it.user ("speedmax", properties, 250.0f),
  794. it.user ("scale", properties, 1.0f), it.user ("offset", properties, 0.0f),
  795. it.user ("forward", properties, glm::vec3 (0.0f, 1.0f, 0.0f)), it.user ("timescale", properties, 1.0f),
  796. it.user ("phasemin", properties, 0.0f), it.user ("phasemax", properties, 0.1f),
  797. it.user ("right", properties, glm::vec3 (0.0f, 0.0f, 1.0f))
  798. );
  799. } else if (name == "mapsequencearoundcontrolpoint") {
  800. return std::make_unique<MapSequenceAroundControlPointInitializer> (
  801. it.user ("controlpoint", properties, 0), it.user ("count", properties, 1),
  802. it.user ("speedmin", properties, glm::vec3 (0.0f)), it.user ("speedmax", properties, glm::vec3 (100.0f))
  803. );
  804. }
  805. return nullptr;
  806. }
  807. ParticleOperatorUniquePtr ObjectParser::parseParticleOperator (const JSON& it, const Properties& properties) {
  808. std::string name = it.optional<std::string> ("name", "");
  809. if (name == "movement") {
  810. return std::make_unique<MovementOperator> (
  811. it.user ("drag", properties, 0.0f), it.user ("gravity", properties, glm::vec3 (0.0f))
  812. );
  813. } else if (name == "angularmovement") {
  814. return std::make_unique<AngularMovementOperator> (
  815. it.user ("drag", properties, 0.0f), it.user ("force", properties, glm::vec3 (0.0f))
  816. );
  817. } else if (name == "alphafade") {
  818. return std::make_unique<AlphaFadeOperator> (
  819. it.user ("fadeintime", properties, 0.5f), it.user ("fadeouttime", properties, 0.5f)
  820. );
  821. } else if (name == "sizechange") {
  822. return std::make_unique<SizeChangeOperator> (
  823. it.user ("starttime", properties, 0.0f), it.user ("endtime", properties, 1.0f),
  824. it.user ("startvalue", properties, 1.0f), it.user ("endvalue", properties, 0.0f)
  825. );
  826. } else if (name == "alphachange") {
  827. return std::make_unique<AlphaChangeOperator> (
  828. it.user ("starttime", properties, 0.0f), it.user ("endtime", properties, 1.0f),
  829. it.user ("startvalue", properties, 1.0f), it.user ("endvalue", properties, 0.0f)
  830. );
  831. } else if (name == "colorchange") {
  832. return std::make_unique<ColorChangeOperator> (
  833. it.user ("starttime", properties, 0.0f), it.user ("endtime", properties, 1.0f),
  834. it.user ("startvalue", properties, glm::vec3 (1.0f)), it.user ("endvalue", properties, glm::vec3 (1.0f))
  835. );
  836. } else if (name == "turbulence") {
  837. return std::make_unique<TurbulenceOperator> (
  838. it.user ("scale", properties, 0.005f), it.user ("speedmin", properties, 500.0f),
  839. it.user ("speedmax", properties, 1000.0f), it.user ("timescale", properties, 0.01f),
  840. it.user ("mask", properties, glm::vec3 (1.0f, 1.0f, 0.0f)), it.user ("phasemin", properties, 0.0f),
  841. it.user ("phasemax", properties, 0.0f), it.user ("audioprocessingmode", properties, 0),
  842. it.user ("audioprocessingbounds", properties, glm::vec2 (0.0f, 1.0f)),
  843. it.user ("audioprocessingexponent", properties, 1.0f),
  844. it.user ("audioprocessingfrequencystart", properties, 0),
  845. it.user ("audioprocessingfrequencyend", properties, 15)
  846. );
  847. } else if (name == "vortex" || name == "vortex_v2") {
  848. return std::make_unique<VortexOperator> (
  849. it.optional ("controlpoint", 0),
  850. it.optional ("flags", 0), // 1 = infinite axis, 2 = maintain distance, 4 = ring shape
  851. it.user ("axis", properties, glm::vec3 (0.0f, 0.0f, 1.0f)),
  852. it.user ("offset", properties, glm::vec3 (0.0f)), it.user ("distanceinner", properties, 500.0f),
  853. it.user ("distanceouter", properties, 650.0f), it.user ("speedinner", properties, 2500.0f),
  854. it.user ("speedouter", properties, 0.0f), it.user ("centerforce", properties, 1.0f),
  855. it.user ("ringradius", properties, 300.0f), it.user ("ringwidth", properties, 50.0f),
  856. it.user ("ringpulldistance", properties, 50.0f), it.user ("ringpullforce", properties, 10.0f),
  857. it.user ("audioprocessingmode", properties, 0),
  858. it.user ("audioprocessingbounds", properties, glm::vec2 (0.0f, 1.0f))
  859. );
  860. } else if (name == "controlpointattract") {
  861. return std::make_unique<ControlPointAttractOperator> (
  862. it.optional ("controlpoint", 0), it.user ("origin", properties, glm::vec3 (0.0f)),
  863. it.user ("scale", properties, 100.0f), it.user ("threshold", properties, 1000.0f)
  864. );
  865. } else if (name == "oscillatealpha") {
  866. return std::make_unique<OscillateAlphaOperator> (
  867. it.user ("frequencymin", properties, 0.0f), it.user ("frequencymax", properties, 10.0f),
  868. it.user ("scalemin", properties, 0.0f), it.user ("scalemax", properties, 1.0f),
  869. it.user ("phasemin", properties, 0.0f), it.user ("phasemax", properties, glm::two_pi<float> ())
  870. );
  871. } else if (name == "oscillatesize") {
  872. return std::make_unique<OscillateSizeOperator> (
  873. it.user ("frequencymin", properties, 0.0f), it.user ("frequencymax", properties, 10.0f),
  874. it.user ("scalemin", properties, 0.8f), it.user ("scalemax", properties, 1.2f),
  875. it.user ("phasemin", properties, 0.0f), it.user ("phasemax", properties, glm::two_pi<float> ())
  876. );
  877. } else if (name == "oscillateposition") {
  878. return std::make_unique<OscillatePositionOperator> (
  879. it.user ("frequencymin", properties, 0.0f), it.user ("frequencymax", properties, 5.0f),
  880. it.user ("scalemin", properties, 0.0f), it.user ("scalemax", properties, 10.0f),
  881. it.user ("phasemin", properties, 0.0f), it.user ("phasemax", properties, glm::two_pi<float> ()),
  882. it.user ("mask", properties, glm::vec3 (1.0f, 1.0f, 0.0f))
  883. );
  884. }
  885. return nullptr;
  886. }
  887. ParticleRenderer ObjectParser::parseParticleRenderer (const JSON& it) {
  888. std::string name = "sprite";
  889. const auto nameIt = it.find ("name");
  890. if (nameIt != it.end () && nameIt->is_string ()) {
  891. name = nameIt->get<std::string> ();
  892. }
  893. // Renderer-type-specific defaults
  894. float subdivisionDefault = (name == "rope") ? 4.0f : 1.0f;
  895. float lengthDefault = (name == "ropetrail") ? 1.0f : 0.05f;
  896. return ParticleRenderer {
  897. .name = name,
  898. .length = it.optional ("length", lengthDefault),
  899. .maxLength = it.optional ("maxlength", 10.0f),
  900. .minLength = it.optional ("minlength", 0.0f),
  901. .subdivision = it.optional ("subdivision", subdivisionDefault),
  902. .segments = it.optional ("segments", 4.0f),
  903. .uvScale = it.optional ("uvscale", 1.0f),
  904. .uvScrolling = it.optional ("uvscrolling", false),
  905. .uvSmoothing = it.optional ("uvsmoothing", true),
  906. .fadeAlpha = it.optional ("fadealpha", false),
  907. .fadeSize = it.optional ("fadesize", false),
  908. };
  909. }
  910. ParticleControlPoint ObjectParser::parseParticleControlPoint (const JSON& it) {
  911. // Parse offset - can be string "x y z" or array [x,y,z]
  912. glm::vec3 offset (0.0f);
  913. const auto offsetIt = it.find ("offset");
  914. if (offsetIt != it.end ()) {
  915. if (offsetIt->is_string ()) {
  916. // Parse string format "x y z"
  917. std::string offsetStr = offsetIt->get<std::string> ();
  918. std::istringstream iss (offsetStr);
  919. iss >> offset.x >> offset.y >> offset.z;
  920. } else {
  921. // Try parsing as vec3 directly
  922. try {
  923. offset = it.optional ("offset", glm::vec3 (0.0f));
  924. } catch (...) {
  925. offset = glm::vec3 (0.0f);
  926. }
  927. }
  928. }
  929. return ParticleControlPoint {
  930. .id = it.optional ("id", -1),
  931. .flags = it.optional ("flags", 0u),
  932. .offset = offset,
  933. .lockToPointer = it.optional ("locktopointer", false),
  934. };
  935. }
  936. ParticleChild ObjectParser::parseParticleChild (const JSON& it, const Project& project) {
  937. std::string particleFile = "";
  938. const auto particleIt = it.find ("particle");
  939. if (particleIt != it.end () && particleIt->is_string ()) {
  940. particleFile = particleIt->get<std::string> ();
  941. }
  942. std::string type = "static";
  943. const auto typeIt = it.find ("type");
  944. if (typeIt != it.end () && typeIt->is_string ()) {
  945. type = typeIt->get<std::string> ();
  946. }
  947. std::string name = "";
  948. const auto nameIt = it.find ("name");
  949. if (nameIt != it.end () && nameIt->is_string ()) {
  950. name = nameIt->get<std::string> ();
  951. }
  952. // Helper lambda to parse vec3 fields that might be strings, arrays, single numbers, or missing
  953. auto parseVec3 = [&] (const char* fieldName, const glm::vec3& defaultValue) -> glm::vec3 {
  954. const auto fieldIt = it.find (fieldName);
  955. if (fieldIt == it.end ()) {
  956. return defaultValue;
  957. }
  958. if (fieldIt->is_string ()) {
  959. return it.optional (fieldName, defaultValue);
  960. }
  961. if (fieldIt->is_number ()) {
  962. // Single number - use for all components
  963. float val = fieldIt->get<float> ();
  964. return glm::vec3 (val, val, val);
  965. }
  966. if (fieldIt->is_array () && fieldIt->size () >= 3) {
  967. return glm::vec3 ((*fieldIt)[0].get<float> (), (*fieldIt)[1].get<float> (), (*fieldIt)[2].get<float> ());
  968. }
  969. return defaultValue;
  970. };
  971. return ParticleChild {
  972. .type = type,
  973. .name = name,
  974. .maxCount = it.optional ("maxcount", 20),
  975. .controlPointStartIndex = it.optional ("controlpointstartindex", 0),
  976. .probability = it.optional ("probability", 1.0f),
  977. .angles = parseVec3 ("angles", glm::vec3 (0.0f)),
  978. .origin = parseVec3 ("origin", glm::vec3 (0.0f)),
  979. .scale = parseVec3 ("scale", glm::vec3 (1.0f)),
  980. .particleFile = particleFile,
  981. };
  982. }
  983. ParticleInstanceOverride ObjectParser::parseParticleInstanceOverride (const JSON& it, const Properties& properties) {
  984. return ParticleInstanceOverride {
  985. .enabled = it.user ("enabled", properties, true),
  986. .alpha = it.user ("alpha", properties, 1.0f),
  987. .size = it.user ("size", properties, 1.0f),
  988. .lifetime = it.user ("lifetime", properties, 1.0f),
  989. .rate = it.user ("rate", properties, 1.0f),
  990. .speed = it.user ("speed", properties, 1.0f),
  991. .count = it.user ("count", properties, 1.0f),
  992. .color = it.user ("color", properties, glm::vec3 (1.0f)),
  993. .colorn = it.user ("colorn", properties, glm::vec3 (1.0f)),
  994. };
  995. }