ObjectParser.cpp 64 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591
  1. #include "ObjectParser.h"
  2. #include "DynamicValueParser.h"
  3. #include "EffectParser.h"
  4. #include "MaterialParser.h"
  5. #include "ModelParser.h"
  6. #include "ShaderConstantParser.h"
  7. #include "TextureParser.h"
  8. #include "UserSettingParser.h"
  9. #include "WallpaperEngine/Data/Builders/ColorBuilder.h"
  10. #include "WallpaperEngine/Data/Model/Object.h"
  11. #include "WallpaperEngine/Data/Model/Project.h"
  12. #include "WallpaperEngine/Logging/Log.h"
  13. #include "WallpaperEngine/Maths.h"
  14. #include <cmath>
  15. #include <glm/gtc/constants.hpp>
  16. #include <glm/gtc/matrix_transform.hpp>
  17. #include <sstream>
  18. using namespace WallpaperEngine::Data::Parsers;
  19. using namespace WallpaperEngine::Data::Model;
  20. namespace {
  21. /** For settings whose default depends on the scene (wallpaper64.exe picks 2D or 3D defaults), null if not set */
  22. UserSettingUniquePtr userIfSet (const JSON& it, const std::string& key, const Properties& properties) {
  23. return it.optional (key).has_value () ? it.user (key, properties) : nullptr;
  24. }
  25. /** wallpaper64.exe reads control point indices unsigned and clamps them to 7 */
  26. int controlPointIndex (const JSON& it, const std::string& key, int fallback) {
  27. const auto value = it.optional (key);
  28. if (!value.has_value () || !value->is_number ()) {
  29. return fallback;
  30. }
  31. const auto index = value->get<int64_t> ();
  32. return index < 0 || index > 7 ? 7 : static_cast<int> (index);
  33. }
  34. /** WE's "x y z" reader: atof, skip to the next space, missing components are 0 */
  35. glm::vec3 parseFloats (const std::string& text) {
  36. glm::vec3 result (0.0f);
  37. const char* cursor = text.c_str ();
  38. for (int i = 0; i < 3; i++) {
  39. result[i] = static_cast<float> (std::atof (cursor));
  40. while (*cursor != '\0' && *cursor != ' ') {
  41. cursor++;
  42. }
  43. while (*cursor == ' ') {
  44. cursor++;
  45. }
  46. }
  47. return result;
  48. }
  49. /**
  50. * A number fills every component, a string is read as up to three numbers split by spaces and the missing ones are
  51. * 0 (the remap ranges in sub_1401C5490)
  52. */
  53. glm::vec3 parseRemapRange (const JSON& it, const std::string& key, float fallback) {
  54. const auto value = it.optional (key);
  55. if (!value.has_value ()) {
  56. return glm::vec3 (fallback);
  57. }
  58. if (value->is_number ()) {
  59. return glm::vec3 (value->get<float> ());
  60. }
  61. if (!value->is_string ()) {
  62. return glm::vec3 (fallback);
  63. }
  64. return parseFloats (value->get<std::string> ());
  65. }
  66. template <typename T, size_t N>
  67. T parseName (const JSON& it, const std::string& key, const char* fallback, const char* const (&names)[N], T unknown) {
  68. const std::string name = it.optional<std::string> (key, fallback);
  69. for (size_t i = 0; i < N; i++) {
  70. if (name == names[i]) {
  71. return static_cast<T> (i);
  72. }
  73. }
  74. sLog.error ("Unknown particle ", key, " ", name);
  75. return unknown;
  76. }
  77. ParticleRemap parseRemap (const JSON& it, const char* defaultInput) {
  78. // wallpaper64.exe off_140484E80 and sub_140260FB0, sub_140261030, sub_140261120
  79. static constexpr const char* values[] = {
  80. "lifetimefraction",
  81. "maxlifetime",
  82. "size",
  83. "opacity",
  84. "speed",
  85. "rotation",
  86. "angularspeed",
  87. "distancetocontrolpoint",
  88. "positionbetweentwocontrolpoints",
  89. "runtime",
  90. "timeofday",
  91. "particlesystemtime",
  92. "layertime",
  93. "color",
  94. "position",
  95. "velocity",
  96. "controlpoint",
  97. "deltatocontrolpoint",
  98. "directiontocontrolpoint",
  99. "layerorigin",
  100. };
  101. static constexpr const char* operations[] = { "remap", "multiply", "add", "subtract" };
  102. static constexpr const char* components[] = { "all", "x", "y", "z", "sum", "average", "max", "min" };
  103. static constexpr const char* transforms[] = { "none", "sine", "square", "saw",
  104. "triangle", "simplexnoise", "fbmnoise" };
  105. return ParticleRemap {
  106. .operation = parseName (it, "operation", "multiply", operations, ParticleRemapOperation::Unknown),
  107. .input = parseName (it, "input", defaultInput, values, ParticleRemapValue::Unknown),
  108. .output = parseName (it, "output", "size", values, ParticleRemapValue::Unknown),
  109. .inputComponent = parseName (it, "inputcomponent", "all", components, ParticleRemapComponent::Unknown),
  110. .outputComponent = parseName (it, "outputcomponent", "all", components, ParticleRemapComponent::Unknown),
  111. .transform = parseName (it, "transformfunction", "none", transforms, ParticleRemapTransform::Unknown),
  112. .flags = it.optional ("flags", 1u),
  113. .inputRangeMin = parseRemapRange (it, "inputrangemin", 0.0f),
  114. .inputRangeMax = parseRemapRange (it, "inputrangemax", 1.0f),
  115. .outputRangeMin = parseRemapRange (it, "outputrangemin", 0.0f),
  116. .outputRangeMax = parseRemapRange (it, "outputrangemax", 1.0f),
  117. .inputControlPoint0 = controlPointIndex (it, "inputcontrolpoint0", 0),
  118. .inputControlPoint1 = controlPointIndex (it, "inputcontrolpoint1", 1),
  119. .outputControlPoint0 = controlPointIndex (it, "outputcontrolpoint0", 0),
  120. .outputControlPoint1 = controlPointIndex (it, "outputcontrolpoint1", 1),
  121. .transformInputScale = it.optional ("transforminputscale", 2.0f),
  122. .transformOctaves = it.optional ("transformoctaves", 3),
  123. };
  124. }
  125. ParticleBlendWindow parseBlendWindow (const JSON& it) {
  126. return ParticleBlendWindow {
  127. .inStart = it.optional ("blendinstart", 0.0f),
  128. .inEnd = it.optional ("blendinend", 0.0f),
  129. .outStart = it.optional ("blendoutstart", 1.0f),
  130. .outEnd = it.optional ("blendoutend", 1.0f),
  131. };
  132. }
  133. /** "r g b" strings with all three numbers, sub_1401C5490 skips anything else */
  134. std::vector<glm::vec3> parseColorList (const JSON& it) {
  135. std::vector<glm::vec3> colors;
  136. const auto list = it.optional ("colors");
  137. if (!list.has_value ()) {
  138. colors.emplace_back (1.0f);
  139. return colors;
  140. }
  141. if (!list->is_array ()) {
  142. return colors;
  143. }
  144. for (const auto& entry : *list) {
  145. if (!entry.is_string ()) {
  146. continue;
  147. }
  148. std::istringstream stream (entry.get<std::string> ());
  149. glm::vec3 color;
  150. if (stream >> color.r >> color.g >> color.b) {
  151. colors.push_back (color);
  152. }
  153. }
  154. return colors;
  155. }
  156. float numberOr (const JSON& it, const char* key, float fallback) {
  157. const auto value = it.find (key);
  158. return value != it.end () && value->is_number () ? value->get<float> () : fallback;
  159. }
  160. std::string stringOr (const JSON& it, const char* key, const char* fallback) {
  161. const auto value = it.find (key);
  162. return value != it.end () && value->is_string () ? value->get<std::string> () : fallback;
  163. }
  164. /**
  165. * wallpaper64.exe sub_1401C45F0: the color a particle file's color initializer centers on. The last colorrandom,
  166. * hsvcolorrandom or colorlist wins, without one the first colorchange's start value. Saved as "%.5f" and read back
  167. */
  168. std::pair<glm::vec3, bool> deriveColorReference (const JSON& particle) {
  169. glm::vec3 reference (1.0f);
  170. bool hasColor = false;
  171. const auto nameOf = [] (const JSON& entry) {
  172. return entry.is_object () ? stringOr (entry, "name", "") : std::string ();
  173. };
  174. if (const auto initializers = particle.find ("initializer");
  175. initializers != particle.end () && initializers->is_array ()) {
  176. for (const auto& entry : *initializers) {
  177. const std::string name = nameOf (entry);
  178. if (name == "colorrandom") {
  179. const glm::vec3 min = parseFloats (stringOr (entry, "min", "0 0 0"));
  180. const glm::vec3 max = parseFloats (stringOr (entry, "max", "255 255 255"));
  181. reference = ((max - min) * 0.5f + min) * 0.0039215689f;
  182. hasColor = true;
  183. } else if (name == "hsvcolorrandom") {
  184. const auto middle = [&entry] (const char* low, float lowDefault, const char* high, float highDefault) {
  185. return std::clamp ((numberOr (entry, high, highDefault) + numberOr (entry, low, lowDefault)) * 0.5f, 0.0f, 1.0f);
  186. };
  187. reference = WallpaperEngine::Maths::hsvToRgb (glm::vec3 (
  188. middle ("huemin", 0.0f, "huemax", 1.0f), middle ("saturationmin", 0.5f, "saturationmax", 1.0f),
  189. middle ("valuemin", 0.5f, "valuemax", 1.0f)
  190. ));
  191. hasColor = true;
  192. } else if (name == "colorlist") {
  193. const auto colors = entry.find ("colors");
  194. if (colors == entry.end () || !colors->is_array ()) {
  195. continue;
  196. }
  197. for (const auto& color : *colors) {
  198. if (color.is_string ()) {
  199. reference = parseFloats (color.get<std::string> ());
  200. hasColor = true;
  201. break;
  202. }
  203. }
  204. }
  205. }
  206. }
  207. if (!hasColor) {
  208. if (const auto operators = particle.find ("operator"); operators != particle.end () && operators->is_array ()) {
  209. for (const auto& entry : *operators) {
  210. if (nameOf (entry) == "colorchange") {
  211. reference = parseFloats (stringOr (entry, "startvalue", "1 1 1"));
  212. hasColor = true;
  213. break;
  214. }
  215. }
  216. }
  217. }
  218. char text[196];
  219. std::snprintf (text, sizeof (text), "%.5f %.5f %.5f", reference.r, reference.g, reference.b);
  220. return { parseFloats (text), hasColor };
  221. }
  222. } // namespace
  223. ObjectUniquePtr ObjectParser::parse (const JSON& it, const Project& project) {
  224. const auto imageIt = it.find ("image");
  225. const auto soundIt = it.find ("sound");
  226. const auto particleIt = it.find ("particle");
  227. const auto textIt = it.find ("text");
  228. const auto lightIt = it.find ("light");
  229. const auto modelIt = it.find ("model");
  230. // "shape" refers to VolumeLight
  231. const auto shapeIt = it.find ("shape");
  232. const auto cameraIt = it.find ("camera");
  233. // some particle objects have numeric 'name' fields, so handle type mismatches gracefully
  234. ObjectData basedata;
  235. try {
  236. basedata = ObjectData {
  237. .id = it.require<int> ("id", "Object must have an id"),
  238. .name = it.require<std::string> ("name", "Object must have a name"),
  239. .dependencies = parseDependencies (it),
  240. .parent = it.optional<int> ("parent"),
  241. .attachment = it.optional<std::string> ("attachment"),
  242. .sortOrder = it.optional<int> ("sortorder"),
  243. .origin = it.user ("origin", project.properties, glm::vec3 (0.0f)),
  244. .groupScale = it.user ("scale", project.properties, glm::vec3 (1.0f)),
  245. .groupAngles = it.user ("angles", project.properties, glm::vec3 (0.0f)),
  246. .groupVisible = it.user ("visible", project.properties, true),
  247. .groupParallaxDepth = it.user ("parallaxDepth", project.properties, glm::vec2 (1.0f)),
  248. .solid = it.user ("solid", project.properties, true),
  249. .disablePropagation = it.user ("disablepropagation", project.properties, false),
  250. .perspective = it.user ("perspective", project.properties, false),
  251. };
  252. } catch (const std::exception& e) {
  253. sLog.error ("Error parsing object base data: ", e.what ());
  254. const auto idIt = it.find ("id");
  255. const auto nameIt = it.find ("name");
  256. int id = (idIt != it.end () && idIt->is_number ()) ? idIt->get<int> () : -1;
  257. std::string name = "unknown";
  258. if (nameIt != it.end ()) {
  259. if (nameIt->is_string ()) {
  260. name = nameIt->get<std::string> ();
  261. } else if (nameIt->is_number ()) {
  262. name = std::to_string (nameIt->get<int> ());
  263. }
  264. }
  265. basedata = ObjectData {
  266. .id = id,
  267. .name = name,
  268. .dependencies = parseDependencies (it),
  269. .parent = it.optional<int> ("parent"),
  270. .attachment = it.optional<std::string> ("attachment"),
  271. .sortOrder = it.optional<int> ("sortorder"),
  272. .origin = it.user ("origin", project.properties, glm::vec3 (0.0f)),
  273. .groupScale = it.user ("scale", project.properties, glm::vec3 (1.0f)),
  274. .groupAngles = it.user ("angles", project.properties, glm::vec3 (0.0f)),
  275. .groupVisible = it.user ("visible", project.properties, true),
  276. .groupParallaxDepth = it.user ("parallaxDepth", project.properties, glm::vec2 (1.0f)),
  277. .solid = it.user ("solid", project.properties, true),
  278. .disablePropagation = it.user ("disablepropagation", project.properties, false),
  279. .perspective = it.user ("perspective", project.properties, false),
  280. };
  281. }
  282. if (imageIt != it.end () && imageIt->is_string ()) {
  283. return parseImage (it, project, std::move (basedata), *imageIt);
  284. } else if (soundIt != it.end () && soundIt->is_array ()) {
  285. return parseSound (it, project, std::move (basedata));
  286. } else if (particleIt != it.end () && !particleIt->is_null ()) {
  287. return parseParticle (it, project, std::move (basedata));
  288. } else if (textIt != it.end () && !textIt->is_null ()) {
  289. return parseText (it, project, std::move (basedata));
  290. } else if (lightIt != it.end () && lightIt->is_string ()) {
  291. return parseLight (it, project, std::move (basedata));
  292. } else if (modelIt != it.end () && modelIt->is_string ()) {
  293. return parseMesh (it, std::move (basedata));
  294. } else if (cameraIt != it.end () && cameraIt->is_string ()) {
  295. return parseCamera (it, project, std::move (basedata));
  296. } else if (shapeIt != it.end () && !shapeIt->is_null ()) {
  297. sLog.error ("VolumeLight objects are not supported yet");
  298. } else {
  299. if (!it.optional ("solid", false)) {
  300. // TODO: re-evaluate - some objects contain other objects and aren't really anything special
  301. sLog.error ("Unknown object type found: ", it.dump ());
  302. }
  303. }
  304. return std::make_unique<Object> (std::move (basedata));
  305. }
  306. namespace {
  307. // the "path" file of a camera object (sub_1401F2030): entries without options or with an invalid length/fps are
  308. // dropped, tracks use the property animation keyframe format
  309. std::vector<CameraTimeline> parseCameraTimelines (const std::string& filename, const Project& project) {
  310. std::vector<CameraTimeline> timelines;
  311. JSON file;
  312. try {
  313. file = JSON::parseAsset (project.assetLocator->readString (filename));
  314. } catch (const std::exception& e) {
  315. sLog.error ("Cannot load camera path file ", filename, ": ", e.what ());
  316. return timelines;
  317. }
  318. const auto list = file.find ("paths");
  319. if (list == file.end () || !list->is_array ()) {
  320. return timelines;
  321. }
  322. for (const auto& entry : *list) {
  323. if (!entry.is_object ()) {
  324. continue;
  325. }
  326. const auto options = entry.find ("options");
  327. if (options == entry.end () || !options->is_object ()) {
  328. continue;
  329. }
  330. const auto length = options->find ("length");
  331. const auto fps = options->find ("fps");
  332. if (length == options->end () || fps == options->end () || !length->is_number () || !fps->is_number ()
  333. || fps->get<float> () <= 0.0f || length->get<int> () <= 0) {
  334. continue;
  335. }
  336. CameraTimeline timeline;
  337. timeline.fps = fps->get<float> ();
  338. timeline.length = static_cast<float> (length->get<int> ());
  339. if (const auto name = entry.find ("name"); name != entry.end () && name->is_string ()) {
  340. timeline.name = name->get<std::string> ();
  341. }
  342. if (const auto visible = entry.find ("visible"); visible != entry.end () && visible->is_boolean ()) {
  343. timeline.visible = visible->get<bool> ();
  344. }
  345. if (const auto mode = options->find ("mode"); mode != options->end () && mode->is_string ()) {
  346. const auto name = mode->get<std::string> ();
  347. timeline.mode = name == "mirror" ? PropertyAnimation::Mode::Mirror
  348. : name == "single" ? PropertyAnimation::Mode::Single
  349. : PropertyAnimation::Mode::Loop;
  350. } else {
  351. timeline.mode = PropertyAnimation::Mode::Loop;
  352. }
  353. if (const auto paused = options->find ("startpaused"); paused != options->end () && paused->is_boolean ()) {
  354. timeline.startPaused = paused->get<bool> ();
  355. }
  356. const auto wrap = options->find ("wraploop");
  357. const bool wrapLoop = wrap != options->end () && wrap->is_boolean () && wrap->get<bool> ();
  358. const int frames = static_cast<int> (timeline.length);
  359. const auto readCurve = [&] (const JSON& curve, std::vector<AnimationKeyframe>& keys) {
  360. keys = DynamicValueParser::parseKeyframes (curve);
  361. if (wrapLoop) {
  362. DynamicValueParser::closeLoop (keys, frames);
  363. }
  364. };
  365. const auto readTrack = [&] (const char* key, std::array<std::vector<AnimationKeyframe>, 3>& track) {
  366. const auto it = entry.find (key);
  367. if (it == entry.end () || !it->is_object ()) {
  368. return;
  369. }
  370. for (int component = 0; component < 3; component++) {
  371. if (const auto curve = it->find ("c" + std::to_string (component)); curve != it->end ()) {
  372. readCurve (*curve, track[component]);
  373. }
  374. }
  375. };
  376. readTrack ("center", timeline.center);
  377. readTrack ("eye", timeline.eye);
  378. readTrack ("up", timeline.up);
  379. if (const auto zoom = entry.find ("zoom"); zoom != entry.end ()) {
  380. readCurve (*zoom, timeline.zoom);
  381. }
  382. if (const auto fov = entry.find ("fov"); fov != entry.end ()) {
  383. readCurve (*fov, timeline.fov);
  384. }
  385. timelines.push_back (std::move (timeline));
  386. }
  387. return timelines;
  388. }
  389. } // namespace
  390. SceneCameraUniquePtr ObjectParser::parseCamera (const JSON& it, const Project& project, ObjectData base) {
  391. // defaults from the camera object's constructor, wallpaper64.exe 2.8.42 sub_14018FF60
  392. SceneCameraData data {
  393. .fov = it.user ("fov", project.properties, 50.0f),
  394. .zoom = it.user ("zoom", project.properties, 1.0f),
  395. .timelines = {},
  396. .queueMode = it.optional ("queuemode", std::string ("random")) == "sequential" ? CameraQueueMode::Sequential
  397. : CameraQueueMode::Random,
  398. };
  399. if (const auto path = it.optional<std::string> ("path"); path.has_value ()) {
  400. data.timelines = parseCameraTimelines (*path, project);
  401. }
  402. return std::make_unique<SceneCamera> (std::move (base), std::move (data));
  403. }
  404. LightUniquePtr ObjectParser::parseLight (const JSON& it, const Project& project, ObjectData base) {
  405. const auto name = it.require<std::string> ("light", "Light must have a type");
  406. const std::map<std::string, LightType> types = {
  407. { "point", LightType::Legacy }, { "lpoint", LightType::Point },
  408. { "lspot", LightType::Spot }, { "ltube", LightType::Tube },
  409. { "ldirectional", LightType::Directional },
  410. };
  411. const auto type = types.find (name);
  412. if (type == types.end ()) {
  413. sLog.error ("Unknown light type ", name, " on object ", base.id);
  414. }
  415. return std::make_unique<Light> (
  416. std::move (base),
  417. LightData {
  418. .type = type != types.end () ? type->second : LightType::Legacy,
  419. .color = it.color ("color", project.properties, Builders::ColorBuilder::White),
  420. .intensity = it.user ("intensity", project.properties, 1.0f),
  421. .radius = it.user ("radius", project.properties, 1.0f),
  422. .visible = it.user ("visible", project.properties, true),
  423. // defaults from the light's constructor (sub_14018FF60) and property table (sub_14025DA80)
  424. .castVolumetrics = it.optional ("castvolumetrics", false),
  425. .castShadow = it.optional ("castshadow", false),
  426. .density = it.user ("density", project.properties, 2.0f),
  427. .volumetricsExponent = it.user ("volumetricsexponent", project.properties, 1.0f),
  428. }
  429. );
  430. }
  431. MeshUniquePtr ObjectParser::parseMesh (const JSON& it, ObjectData base) {
  432. return std::make_unique<Mesh> (
  433. std::move (base), MeshData { .model = it.require<std::string> ("model", "Model object must have a model") }
  434. );
  435. }
  436. std::vector<int> ObjectParser::parseDependencies (const JSON& it) {
  437. const auto dependenciesIt = it.find ("dependencies");
  438. if (dependenciesIt == it.end () || !dependenciesIt->is_array ()) {
  439. return {};
  440. }
  441. std::vector<int> result = {};
  442. for (const auto& cur : *dependenciesIt) {
  443. result.push_back (cur);
  444. }
  445. return result;
  446. }
  447. SoundUniquePtr ObjectParser::parseSound (const JSON& it, const Project& project, ObjectData base) {
  448. const auto soundIt = it.require ("sound", "Object must have a sound");
  449. std::vector<std::string> sounds = {};
  450. for (const auto& cur : soundIt) {
  451. sounds.push_back (cur);
  452. }
  453. return std::make_unique<Sound> (
  454. std::move (base),
  455. SoundData {
  456. .playbackmode = parsePlaybackMode (it.optional ("playbackmode", std::string ("single"))),
  457. .sounds = sounds,
  458. .volume = it.user<float> ("volume", project.properties, 1.0f),
  459. .startsilent = it.optional<bool> ("startsilent"),
  460. }
  461. );
  462. }
  463. SoundPlaybackMode ObjectParser::parsePlaybackMode (const std::string& mode) {
  464. if (mode == "loop") {
  465. return PlaybackMode_Loop;
  466. }
  467. if (mode == "random") {
  468. return PlaybackMode_Random;
  469. }
  470. return PlaybackMode_Single;
  471. }
  472. uint32_t ObjectParser::parseAlignment (const std::string& alignment) {
  473. uint32_t result = ImageAlignment_Center;
  474. if (alignment.find ("top") != std::string::npos) {
  475. result |= ImageAlignment_Top;
  476. } else if (alignment.find ("bottom") != std::string::npos) {
  477. result |= ImageAlignment_Bottom;
  478. }
  479. if (alignment.find ("left") != std::string::npos) {
  480. result |= ImageAlignment_Left;
  481. } else if (alignment.find ("right") != std::string::npos) {
  482. result |= ImageAlignment_Right;
  483. }
  484. return result;
  485. }
  486. TextUniquePtr ObjectParser::parseText (const JSON& it, const Project& project, ObjectData base) {
  487. const auto& effects = it.optional ("effects");
  488. const auto& properties = project.properties;
  489. // defaults are the text object constructor's in wallpaper64.exe 2.8.42 (sub_140256AE0)
  490. return std::make_unique<Text> (
  491. std::move (base),
  492. TextData {
  493. .text = it.user ("text", project.properties),
  494. .font = it.optional ("font", std::string ("systemfont_arial")),
  495. .pointSize = it.user ("pointsize", project.properties, 32.0f),
  496. .size = it.optional ("size", glm::vec2 (0.0f)),
  497. .scale = it.user ("scale", project.properties, glm::vec3 (1.0f)),
  498. .color = it.color ("color", project.properties, Builders::ColorBuilder::White),
  499. .alpha = it.user ("alpha", project.properties, 1.0f),
  500. .visible = it.user ("visible", project.properties, true),
  501. .parallaxDepth = it.user ("parallaxDepth", project.properties, glm::vec2 (1.0f)),
  502. .horizontalAlign = it.user ("horizontalalign", properties, it.optional ("alignment", std::string ("center"))),
  503. .verticalAlign = it.user ("verticalalign", properties, std::string ("center")),
  504. .anchor = it.user ("anchor", properties, std::string ("none")),
  505. .colorBlendMode = it.user ("colorBlendMode", properties, 0),
  506. .padding = it.user ("padding", properties, glm::vec2 (32.0f)),
  507. .spacing = it.user ("spacing", properties, glm::vec2 (0.0f)),
  508. .effects = effects.has_value () ? parseEffects (*effects, project) : std::vector<ImageEffectUniquePtr> {},
  509. .limitWidth = it.user ("limitwidth", properties, false),
  510. .maxWidth = it.user ("maxwidth", properties, 512.0f),
  511. .limitRows = it.user ("limitrows", properties, false),
  512. .maxRows = it.user ("maxrows", properties, 1),
  513. .limitUseEllipsis = it.user ("limituseellipsis", properties, false),
  514. .blockAlign = it.user ("blockalign", properties, false),
  515. .opaqueBackground = it.user ("opaquebackground", properties, false),
  516. .backgroundColor = it.color ("backgroundcolor", properties, Builders::ColorBuilder::Black),
  517. .backgroundBrightness = it.user ("backgroundbrightness", properties, 1.0f),
  518. .brightness = it.user ("brightness", properties, 1.0f),
  519. .msdf = it.user ("msdf", properties, false),
  520. .outline = it.user ("outline", properties, false),
  521. .outlineThickness = it.user ("outlinethickness", properties, 4.0f),
  522. .outlineColor = it.color ("outlinecolor", properties, Builders::ColorBuilder::Black),
  523. .blur = it.user ("blur", properties, false),
  524. .blurSize = it.user ("blursize", properties, 6.0f),
  525. .dropShadow = it.user ("dropshadow", properties, false),
  526. .dropShadowSize = it.user ("dropshadowsize", properties, 6.0f),
  527. .dropShadowOpacity = it.user ("dropshadowopacity", properties, 1.0f),
  528. .dropShadowOffset = it.user ("dropshadowoffset", properties, glm::vec2 (4.0f)),
  529. .dropShadowColor = it.color ("dropshadowcolor", properties, Builders::ColorBuilder::Black),
  530. }
  531. );
  532. }
  533. ImageUniquePtr
  534. ObjectParser::parseImage (const JSON& it, const Project& project, ObjectData base, const std::string& image) {
  535. const auto& properties = project.properties;
  536. const auto& effects = it.optional ("effects");
  537. const auto& animationLayers = it.optional ("animationlayers");
  538. auto result = std::make_unique<Image> (
  539. std::move (base),
  540. ImageData {
  541. .scale = it.user ("scale", properties, glm::vec3 (1.0f)),
  542. .angles = it.user ("angles", properties, glm::vec3 (0.0f)),
  543. .visible = it.user ("visible", properties, true),
  544. .alpha = it.user ("alpha", properties, 1.0f),
  545. .color = it.color ("color", properties, Builders::ColorBuilder::White),
  546. .alignment = parseAlignment (
  547. it.optional ("horizontalalign", it.optional ("alignment", std::string ("center")))
  548. ),
  549. .size = it.user ("size", properties, glm::vec2 (0.0f))->value->getVec2 (),
  550. .parallaxDepth = it.user ("parallaxDepth", properties, glm::vec2 (1.0f)),
  551. .colorBlendMode = it.user ("colorBlendMode", properties, 0),
  552. .brightness = it.user ("brightness", properties, 1.0f),
  553. .clampUVs = it.optional ("clampuvs", false),
  554. .copyBackground = it.user ("copybackground", properties, true),
  555. .model = ModelParser::load (project, image),
  556. .effects = effects.has_value () ? parseEffects (*effects, project) : std::vector<ImageEffectUniquePtr> {},
  557. .animationLayers = animationLayers.has_value () ? parseAnimationLayers (*animationLayers, project)
  558. : std::vector<ImageAnimationLayerUniquePtr> {},
  559. }
  560. );
  561. const auto instance = it.optional ("instance");
  562. if (instance.has_value () && instance->is_object () && !result->model->material->passes.empty ()) {
  563. auto& firstPass = **result->model->material->passes.begin ();
  564. const auto instanceTextures = instance->optional ("textures");
  565. if (instanceTextures.has_value ()) {
  566. // the instance's textures replace the material's, a solid layer's util/white included
  567. for (const auto& [index, texture] : TextureParser::parseTextureMap (*instanceTextures)) {
  568. firstPass.textures.insert_or_assign (index, texture);
  569. }
  570. }
  571. const auto instanceUserTextures = instance->optional ("usertextures");
  572. if (instanceUserTextures.has_value ()) {
  573. for (const auto& [index, texture] : TextureParser::parseTextureMap (*instanceUserTextures)) {
  574. firstPass.usertextures.insert_or_assign (index, texture);
  575. }
  576. }
  577. }
  578. return result;
  579. }
  580. std::vector<ImageEffectUniquePtr> ObjectParser::parseEffects (const JSON& it, const Project& project) {
  581. if (!it.is_array ()) {
  582. return {};
  583. }
  584. std::vector<ImageEffectUniquePtr> result = {};
  585. for (const auto& cur : it) {
  586. result.push_back (parseEffect (cur, project));
  587. }
  588. return result;
  589. }
  590. ImageEffectUniquePtr ObjectParser::parseEffect (const JSON& it, const Project& project) {
  591. const auto& passsOverrides = it.optional ("passes");
  592. return std::make_unique<ImageEffect> (ImageEffect {
  593. .id = it.optional<int> ("id", -1),
  594. .name = it.optional<std::string> ("name", "Effect without name"),
  595. .visible = it.user ("visible", project.properties, true),
  596. .passOverrides = passsOverrides.has_value () ? parseEffectPassOverrides (passsOverrides.value (), project)
  597. : std::vector<ImageEffectPassOverrideUniquePtr> {},
  598. .effect = EffectParser::load (project, it.require ("file", "Image effect must have an effect")) });
  599. }
  600. std::vector<ImageEffectPassOverrideUniquePtr>
  601. ObjectParser::parseEffectPassOverrides (const JSON& it, const Project& project) {
  602. if (!it.is_array ()) {
  603. return {};
  604. }
  605. std::vector<ImageEffectPassOverrideUniquePtr> result = {};
  606. for (const auto& cur : it) {
  607. result.push_back (parseEffectPass (cur, project));
  608. }
  609. return result;
  610. }
  611. ImageEffectPassOverrideUniquePtr ObjectParser::parseEffectPass (const JSON& it, const Project& project) {
  612. const auto& combos = it.optional ("combos");
  613. const auto& textures = it.optional ("textures");
  614. const auto& constants = it.optional ("constantshadervalues");
  615. const auto& usertextures = it.optional ("usertextures");
  616. return std::make_unique<ImageEffectPassOverride> (ImageEffectPassOverride {
  617. .id = it.optional<int> ("id", -1),
  618. .combos = combos.has_value () ? parseComboMap (combos.value ()) : ComboMap {},
  619. .constants
  620. = constants.has_value () ? ShaderConstantParser::parse (constants.value (), project) : ShaderConstantMap {},
  621. .textures = textures.has_value () ? TextureParser::parseTextureMap (textures.value ()) : TextureMap {},
  622. .usertextures
  623. = usertextures.has_value () ? TextureParser::parseTextureMap (usertextures.value ()) : TextureMap {},
  624. });
  625. }
  626. ComboMap ObjectParser::parseComboMap (const JSON& it) {
  627. if (!it.is_object ()) {
  628. return {};
  629. }
  630. ComboMap result = {};
  631. for (const auto& cur : it.items ()) {
  632. result.emplace (cur.key (), cur.value ());
  633. }
  634. return result;
  635. }
  636. std::vector<ImageAnimationLayerUniquePtr> ObjectParser::parseAnimationLayers (const JSON& it, const Project& project) {
  637. if (!it.is_array ()) {
  638. return {};
  639. }
  640. std::vector<ImageAnimationLayerUniquePtr> result = {};
  641. for (const auto& cur : it.items ()) {
  642. result.push_back (parseAnimationLayer (cur.value (), project));
  643. }
  644. return result;
  645. }
  646. ImageAnimationLayerUniquePtr ObjectParser::parseAnimationLayer (const JSON& it, const Project& project) {
  647. const auto& properties = project.properties;
  648. return std::make_unique<ImageAnimationLayer> (ImageAnimationLayer {
  649. .id = it.require<int> ("id", "Animation layer must have an id"),
  650. .name = it.optional<std::string> ("name", ""),
  651. .rate = it.user ("rate", properties, 1.0f),
  652. .visible = it.user ("visible", properties, false),
  653. .blend = it.user ("blend", properties, 1.0f),
  654. .animation = it.user ("animation", properties, 0),
  655. });
  656. }
  657. ParticleUniquePtr ObjectParser::parseParticle (const JSON& it, const Project& project, ObjectData base, int depth) {
  658. try {
  659. const auto& properties = project.properties;
  660. const auto particleIt = it.find ("particle");
  661. if (particleIt == it.end ()) {
  662. sLog.error ("Particle object must have a particle definition");
  663. return std::make_unique<Particle> (
  664. std::move (base),
  665. ParticleData {
  666. .scale = it.user ("scale", properties, glm::vec3 (1.0f)),
  667. .angles = it.user ("angles", properties, glm::vec3 (0.0f)),
  668. .visible = it.user ("visible", properties, true),
  669. .parallaxDepth = it.user ("parallaxDepth", properties, glm::vec2 (1.0f)),
  670. .particleFile = "",
  671. .animationMode = "sequence",
  672. .sequenceMultiplier = 1.0f,
  673. .maxCount = 100,
  674. .startTime = 0.0f,
  675. .flags = 0,
  676. .material = nullptr,
  677. .emitters = {},
  678. .initializers = {},
  679. .operators = {},
  680. .renderers = {},
  681. .controlPoints = {},
  682. .children = {},
  683. .instanceOverride = {
  684. .enabled = Builders::UserSettingBuilder::fromValue(false),
  685. .alpha = Builders::UserSettingBuilder::fromValue(1.0f),
  686. .size = Builders::UserSettingBuilder::fromValue(1.0f),
  687. .lifetime = Builders::UserSettingBuilder::fromValue(1.0f),
  688. .rate = Builders::UserSettingBuilder::fromValue(1.0f),
  689. .speed = Builders::UserSettingBuilder::fromValue(1.0f),
  690. .count = Builders::UserSettingBuilder::fromValue(1.0f),
  691. .brightness = Builders::UserSettingBuilder::fromValue(1.0f),
  692. .color = Builders::UserSettingBuilder::fromValue(1.0f),
  693. .colorn = Builders::UserSettingBuilder::fromValue(1.0f),
  694. },
  695. }
  696. );
  697. }
  698. std::string particleFile;
  699. if (particleIt->is_string ()) {
  700. particleFile = particleIt->get<std::string> ();
  701. }
  702. JSON particleJson = JSON::object ();
  703. if (!particleFile.empty ()) {
  704. try {
  705. particleJson
  706. = WallpaperEngine::Data::JSON::JSON::parseAsset (project.assetLocator->readString (particleFile));
  707. } catch (std::runtime_error& e) {
  708. sLog.error ("Cannot load particle file: ", particleFile, " - ", e.what ());
  709. }
  710. } else if (particleIt->is_object ()) {
  711. particleJson = *particleIt;
  712. }
  713. // field is named "emitter" not "emitters"
  714. std::vector<ParticleEmitter> emitters;
  715. const auto emittersIt = particleJson.find ("emitter");
  716. if (emittersIt != particleJson.end () && emittersIt->is_array ()) {
  717. for (const auto& emitter : *emittersIt) {
  718. emitters.push_back (parseParticleEmitter (emitter));
  719. }
  720. }
  721. // field is named "initializer" not "initializers"
  722. std::vector<ParticleInitializerUniquePtr> initializers;
  723. const auto initializersIt = particleJson.find ("initializer");
  724. if (initializersIt != particleJson.end () && initializersIt->is_array ()) {
  725. for (const auto& initializer : *initializersIt) {
  726. auto init = parseParticleInitializer (initializer, project.properties);
  727. if (init) {
  728. initializers.push_back (std::move (init));
  729. }
  730. }
  731. }
  732. // field is named "operator" not "operators"
  733. std::vector<ParticleOperatorUniquePtr> operators;
  734. const auto operatorsIt = particleJson.find ("operator");
  735. if (operatorsIt != particleJson.end () && operatorsIt->is_array ()) {
  736. for (const auto& op : *operatorsIt) {
  737. auto oper = parseParticleOperator (op, project.properties);
  738. if (oper) {
  739. operators.push_back (std::move (oper));
  740. }
  741. }
  742. }
  743. // wallpaper64.exe sub_1401C5490: control points a remap component outputs to are left to it
  744. uint8_t remapOutputControlPoints = 0;
  745. for (const auto* list : { &initializersIt, &operatorsIt }) {
  746. if (*list == particleJson.end () || !(*list)->is_array ()) {
  747. continue;
  748. }
  749. for (const auto& component : **list) {
  750. const auto nameIt = component.find ("name");
  751. const auto outputIt = component.find ("output");
  752. if (nameIt == component.end () || !nameIt->is_string () || outputIt == component.end ()
  753. || !outputIt->is_string () || outputIt->get<std::string> () != "controlpoint") {
  754. continue;
  755. }
  756. const std::string name = nameIt->get<std::string> ();
  757. if (name != "remapvalue" && name != "remapinitialvalue") {
  758. continue;
  759. }
  760. const auto cpIt = component.find ("outputcontrolpoint0");
  761. const int64_t cp = cpIt != component.end () && cpIt->is_number () ? cpIt->get<int64_t> () : 0;
  762. // clamped as unsigned, so negative indices end up on the last slot too
  763. remapOutputControlPoints |= static_cast<uint8_t> (1u << (cp < 0 || cp > 7 ? 7 : cp));
  764. }
  765. }
  766. // field is named "renderer" not "renderers"
  767. std::vector<ParticleRenderer> renderers;
  768. const auto renderersIt = particleJson.find ("renderer");
  769. if (renderersIt != particleJson.end () && renderersIt->is_array ()) {
  770. for (const auto& renderer : *renderersIt) {
  771. renderers.push_back (parseParticleRenderer (renderer));
  772. }
  773. }
  774. if (renderers.empty ()) {
  775. renderers.push_back (
  776. ParticleRenderer {
  777. .name = "sprite",
  778. .length = 0.05f,
  779. .maxLength = 10.0f,
  780. .minLength = 0.0f,
  781. .subdivision = 1.0f,
  782. .segments = 4.0f,
  783. .uvScale = 1.0f,
  784. .uvScrolling = false,
  785. .uvSmoothing = true,
  786. .fadeAlpha = false,
  787. .fadeSize = false,
  788. }
  789. );
  790. }
  791. // field is named "controlpoint" not "controlpoints"
  792. std::vector<ParticleControlPoint> controlPoints;
  793. const auto controlPointsIt = particleJson.find ("controlpoint");
  794. if (controlPointsIt != particleJson.end () && controlPointsIt->is_array ()) {
  795. for (const auto& cp : *controlPointsIt) {
  796. controlPoints.push_back (parseParticleControlPoint (cp));
  797. }
  798. }
  799. // real WE stops creating child systems 10 levels down, which also ends a file listing itself as a child
  800. constexpr int maxChildDepth = 10;
  801. std::vector<ParticleChild> children;
  802. const auto childrenIt = particleJson.optional ("children");
  803. if (depth < maxChildDepth && childrenIt.has_value () && childrenIt->is_array ()) {
  804. for (const auto& child : *childrenIt) {
  805. children.push_back (parseParticleChild (child, it, project, base, children.size (), depth + 1));
  806. }
  807. }
  808. ParticleInstanceOverride instanceOverride = {
  809. .enabled = Builders::UserSettingBuilder::fromValue (false),
  810. .alpha = Builders::UserSettingBuilder::fromValue (1.0f),
  811. .size = Builders::UserSettingBuilder::fromValue (1.0f),
  812. .lifetime = Builders::UserSettingBuilder::fromValue (1.0f),
  813. .rate = Builders::UserSettingBuilder::fromValue (1.0f),
  814. .speed = Builders::UserSettingBuilder::fromValue (1.0f),
  815. .count = Builders::UserSettingBuilder::fromValue (1.0f),
  816. .brightness = Builders::UserSettingBuilder::fromValue (1.0f),
  817. .color = Builders::UserSettingBuilder::fromValue (1.0f),
  818. .colorn = Builders::UserSettingBuilder::fromValue (1.0f),
  819. };
  820. const auto instanceOverrideIt = it.optional ("instanceoverride");
  821. if (instanceOverrideIt.has_value ()) {
  822. instanceOverride = parseParticleInstanceOverride (*instanceOverrideIt, project.properties);
  823. }
  824. // particles reference material definitions directly, not model files, so wrap it in a model structure
  825. ModelUniquePtr material = nullptr;
  826. const auto materialIt = particleJson.find ("material");
  827. if (materialIt != particleJson.end () && materialIt->is_string ()) {
  828. try {
  829. std::string materialPath = materialIt->get<std::string> ();
  830. auto mat = MaterialParser::load (project, materialPath);
  831. material = std::make_unique<ModelStruct> (ModelStruct {
  832. .filename = materialPath,
  833. .material = std::move (mat),
  834. .solidlayer = false,
  835. .fullscreen = false,
  836. .passthrough = false,
  837. .autosize = false,
  838. .projectlayer = false,
  839. .nopadding = false,
  840. .width = std::nullopt,
  841. .height = std::nullopt,
  842. .puppet = std::nullopt,
  843. });
  844. } catch (std::runtime_error& e) {
  845. sLog.error ("Cannot load particle material: ", materialIt->get<std::string> (), " - ", e.what ());
  846. }
  847. }
  848. std::string animationMode = "sequence";
  849. const auto animModeIt = particleJson.find ("animationmode");
  850. if (animModeIt != particleJson.end () && animModeIt->is_string ()) {
  851. animationMode = animModeIt->get<std::string> ();
  852. }
  853. float sequenceMultiplier = 1.0f;
  854. uint32_t maxCount = 100;
  855. float startTime = 0.0f;
  856. uint32_t flags = 0;
  857. const auto seqMultIt = particleJson.find ("sequencemultiplier");
  858. if (seqMultIt != particleJson.end () && seqMultIt->is_number ()) {
  859. sequenceMultiplier = seqMultIt->get<float> ();
  860. }
  861. const auto maxCountIt = particleJson.find ("maxcount");
  862. if (maxCountIt != particleJson.end () && maxCountIt->is_number ()) {
  863. maxCount = maxCountIt->get<uint32_t> ();
  864. }
  865. const auto startTimeIt = particleJson.find ("starttime");
  866. if (startTimeIt != particleJson.end () && startTimeIt->is_number ()) {
  867. startTime = startTimeIt->get<float> ();
  868. }
  869. const auto [colorReference, hasColor] = deriveColorReference (particleJson);
  870. const auto flagsIt = particleJson.find ("flags");
  871. if (flagsIt != particleJson.end () && flagsIt->is_number ()) {
  872. flags = flagsIt->get<uint32_t> ();
  873. }
  874. return std::make_unique<Particle> (
  875. std::move (base),
  876. ParticleData {
  877. .scale = it.user ("scale", properties, glm::vec3 (1.0f)),
  878. .angles = it.user ("angles", properties, glm::vec3 (0.0f)),
  879. .visible = it.user ("visible", properties, true),
  880. .parallaxDepth = it.user ("parallaxDepth", properties, glm::vec2 (1.0f)),
  881. .particleFile = particleFile,
  882. .animationMode = animationMode,
  883. .sequenceMultiplier = sequenceMultiplier,
  884. .maxCount = maxCount,
  885. .startTime = startTime,
  886. .flags = flags,
  887. .material = std::move (material),
  888. .emitters = std::move (emitters),
  889. .initializers = std::move (initializers),
  890. .operators = std::move (operators),
  891. .renderers = std::move (renderers),
  892. .controlPoints = std::move (controlPoints),
  893. .children = std::move (children),
  894. .remapOutputControlPoints = remapOutputControlPoints,
  895. .colorReference = colorReference,
  896. .hasColor = hasColor,
  897. .overrideColorTints = !hasColor || project.sceneVersion < 5,
  898. .instanceOverride = std::move (instanceOverride),
  899. }
  900. );
  901. } catch (nlohmann::json::exception& e) {
  902. sLog.error ("Error parsing particle '", base.name, "': ", e.what ());
  903. sLog.error ("Particle JSON: ", it.dump ());
  904. throw;
  905. }
  906. }
  907. ParticleEmitter ObjectParser::parseParticleEmitter (const JSON& it) {
  908. std::string name;
  909. const auto nameIt = it.find ("name");
  910. if (nameIt != it.end () && nameIt->is_string ()) {
  911. name = nameIt->get<std::string> ();
  912. }
  913. // vec3 fields may show up as strings, arrays, single numbers, or be missing entirely
  914. auto parseVec3 = [&] (const char* fieldName, const glm::vec3& defaultValue) -> glm::vec3 {
  915. const auto fieldIt = it.find (fieldName);
  916. if (fieldIt == it.end ()) {
  917. return defaultValue;
  918. }
  919. if (fieldIt->is_string ()) {
  920. return it.optional (fieldName, defaultValue);
  921. }
  922. if (fieldIt->is_number ()) {
  923. // single number applies to all components (common for distancemax/distancemin)
  924. float val = fieldIt->get<float> ();
  925. return glm::vec3 (val, val, val);
  926. }
  927. if (fieldIt->is_array () && fieldIt->size () >= 3) {
  928. return glm::vec3 ((*fieldIt)[0].get<float> (), (*fieldIt)[1].get<float> (), (*fieldIt)[2].get<float> ());
  929. }
  930. return defaultValue;
  931. };
  932. auto parseVec2 = [&] (const char* fieldName, const glm::vec2& defaultValue) -> glm::vec2 {
  933. const auto fieldIt = it.find (fieldName);
  934. if (fieldIt == it.end ()) {
  935. return defaultValue;
  936. }
  937. if (fieldIt->is_string ()) {
  938. return it.optional (fieldName, defaultValue);
  939. }
  940. if (fieldIt->is_array () && fieldIt->size () >= 2) {
  941. return glm::vec2 ((*fieldIt)[0].get<float> (), (*fieldIt)[1].get<float> ());
  942. }
  943. return defaultValue;
  944. };
  945. try {
  946. return ParticleEmitter {
  947. .id = it.optional ("id", -1),
  948. .name = name,
  949. .directions = parseVec3 ("directions", glm::vec3 (1.0f, 1.0f, 0.0f)),
  950. .distanceMin = parseVec3 ("distancemin", glm::vec3 (0.0f, 0.0f, 0.0f)),
  951. .distanceMax = parseVec3 ("distancemax", glm::vec3 (256.0f, 256.0f, 0.0f)),
  952. .origin = parseVec3 ("origin", glm::vec3 (0.0f)),
  953. .sign = parseVec3 ("sign", glm::vec3 (0.0f)),
  954. .instantaneous = it.optional ("instantaneous", 0u),
  955. .speedMin = it.optional ("speedmin", 0.0f),
  956. .speedMax = it.optional ("speedmax", 0.0f),
  957. .rate = it.optional ("rate", 10.0f),
  958. .controlPoint = it.optional ("controlpoint", 0),
  959. .flags = it.optional ("flags", 0u),
  960. .cone = it.optional ("cone", 0.0f),
  961. .delay = it.optional ("delay", 0.0f),
  962. .duration = it.optional ("duration", 0.0f),
  963. .audioProcessingBounds = parseVec2 ("audioprocessingbounds", glm::vec2 (0.8f, 1.0f)),
  964. .audioProcessingExponent = it.optional ("audioprocessingexponent", 2.0f),
  965. .audioProcessingFrequencyStart = it.optional ("audioprocessingfrequencystart", 0),
  966. .audioProcessingFrequencyEnd = it.optional ("audioprocessingfrequencyend", 1),
  967. .audioProcessingMode = it.optional ("audioprocessingmode", 0),
  968. .minPeriodicDelay = it.optional ("minperiodicdelay", 1.0f),
  969. .maxPeriodicDelay = it.optional ("maxperiodicdelay", 2.0f),
  970. .minPeriodicDuration = it.optional ("minperiodicduration", 2.0f),
  971. .maxPeriodicDuration = it.optional ("maxperiodicduration", 3.0f),
  972. };
  973. } catch (nlohmann::json::exception& e) {
  974. sLog.error ("Error parsing emitter: ", e.what ());
  975. sLog.error ("Emitter JSON: ", it.dump ());
  976. throw;
  977. }
  978. }
  979. ParticleInitializerUniquePtr ObjectParser::parseParticleInitializer (const JSON& it, const Properties& properties) {
  980. std::string name = it.optional<std::string> ("name", "");
  981. if (name == "colorrandom") {
  982. // read as plain numbers, CParticle divides them by 255 like sub_1401C5490 does ("1 1 1" is nearly black)
  983. return std::make_unique<ColorRandomInitializer> (
  984. it.user ("min", properties, glm::vec3 (0.0f)), it.user ("max", properties, glm::vec3 (255.0f)),
  985. it.user ("exponent", properties, 1.0f)
  986. );
  987. } else if (name == "sizerandom") {
  988. return std::make_unique<SizeRandomInitializer> (
  989. it.user ("min", properties, 0.0f), it.user ("max", properties, 20.0f),
  990. it.user ("exponent", properties, 1.0f)
  991. );
  992. } else if (name == "alpharandom") {
  993. return std::make_unique<AlphaRandomInitializer> (
  994. it.user ("min", properties, 0.05f), it.user ("max", properties, 1.0f)
  995. );
  996. } else if (name == "lifetimerandom") {
  997. return std::make_unique<LifetimeRandomInitializer> (
  998. it.user ("min", properties, 0.0f), it.user ("max", properties, 1.0f)
  999. );
  1000. } else if (name == "velocityrandom") {
  1001. return std::make_unique<VelocityRandomInitializer> (
  1002. it.user ("min", properties, glm::vec3 (-32.0f)), it.user ("max", properties, glm::vec3 (32.0f))
  1003. );
  1004. } else if (name == "rotationrandom") {
  1005. return std::make_unique<RotationRandomInitializer> (
  1006. it.user ("min", properties, glm::vec3 (0.0f)),
  1007. it.user ("max", properties, glm::vec3 (0.0f, 0.0f, glm::two_pi<float> ()))
  1008. );
  1009. } else if (name == "angularvelocityrandom") {
  1010. return std::make_unique<AngularVelocityRandomInitializer> (
  1011. it.user ("min", properties, glm::vec3 (0.0f, 0.0f, -5.0f)),
  1012. it.user ("max", properties, glm::vec3 (0.0f, 0.0f, 5.0f)), it.user ("exponent", properties, 1.0f)
  1013. );
  1014. } else if (name == "turbulentvelocityrandom") {
  1015. return std::make_unique<TurbulentVelocityRandomInitializer> (
  1016. it.user ("speedmin", properties, 100.0f), it.user ("speedmax", properties, 250.0f),
  1017. it.user ("scale", properties, 1.0f), it.user ("offset", properties, 0.0f),
  1018. it.user ("forward", properties, glm::vec3 (0.0f, 1.0f, 0.0f)), it.user ("timescale", properties, 1.0f),
  1019. it.user ("phasemin", properties, 0.0f), it.user ("phasemax", properties, 0.1f),
  1020. it.user ("right", properties, glm::vec3 (0.0f, 0.0f, 1.0f)), it.user ("audioprocessingmode", properties, 0),
  1021. it.user ("audioprocessingbounds", properties, glm::vec2 (0.8f, 1.0f)),
  1022. it.user ("audioprocessingexponent", properties, 2.0f),
  1023. it.user ("audioprocessingfrequencystart", properties, 0),
  1024. it.user ("audioprocessingfrequencyend", properties, 1)
  1025. );
  1026. } else if (name == "inheritinitialvaluefromevent") {
  1027. return std::make_unique<InheritInitialValueFromEventInitializer> (
  1028. parseParticleEventInput (it, ParticleEventInput::SetColor)
  1029. );
  1030. } else if (name == "inheritcontrolpointvelocity") {
  1031. return std::make_unique<InheritControlPointVelocityInitializer> (
  1032. it.optional ("controlpoint", 0), it.user ("min", properties, 0.1f), it.user ("max", properties, 0.2f)
  1033. );
  1034. } else if (name == "mapsequencearoundcontrolpoint") {
  1035. return std::make_unique<MapSequenceAroundControlPointInitializer> (
  1036. it.user ("controlpoint", properties, 0), it.user ("count", properties, 1),
  1037. it.user ("speedmin", properties, glm::vec3 (0.0f)), it.user ("speedmax", properties, glm::vec3 (100.0f))
  1038. );
  1039. } else if (name == "hsvcolorrandom") {
  1040. // defaults from wallpaper64.exe sub_1401BA3E0
  1041. return std::make_unique<HsvColorRandomInitializer> (
  1042. it.user ("huemin", properties, 0.0f), it.user ("huemax", properties, 1.0f),
  1043. std::max (0, it.optional ("huesteps", 6)), it.user ("saturationmin", properties, 0.5f),
  1044. it.user ("saturationmax", properties, 1.0f), it.user ("valuemin", properties, 0.5f),
  1045. it.user ("valuemax", properties, 1.0f)
  1046. );
  1047. } else if (name == "colorlist") {
  1048. return std::make_unique<ColorListInitializer> (
  1049. parseColorList (it), it.user ("huenoise", properties, 0.0f), it.user ("saturationnoise", properties, 0.0f),
  1050. it.user ("valuenoise", properties, 0.0f)
  1051. );
  1052. } else if (name == "positionoffsetrandom") {
  1053. // octaves clamped to 1..8 like sub_1401C5490
  1054. const int octaves = it.optional ("octaves", 6);
  1055. return std::make_unique<PositionOffsetRandomInitializer> (
  1056. userIfSet (it, "directions", properties), it.user ("sign", properties, glm::vec3 (0.0f)),
  1057. userIfSet (it, "scale", properties), userIfSet (it, "distance", properties),
  1058. it.user ("timescale", properties, 1.0f), octaves >= 8 ? 8 : (octaves == 0 ? 1 : octaves)
  1059. );
  1060. } else if (name == "mapsequencebetweencontrolpoints") {
  1061. glm::vec2 bounds (0.0f, 1.0f);
  1062. const auto boundsIt = it.optional ("bounds");
  1063. if (boundsIt.has_value () && boundsIt->is_string ()) {
  1064. const glm::vec3 parsed = parseRemapRange (it, "bounds", 0.0f);
  1065. bounds = glm::vec2 (parsed.x, parsed.y);
  1066. }
  1067. return std::make_unique<MapSequenceBetweenControlPointsInitializer> (
  1068. it.optional ("count", 32.0f), bounds, it.optional<std::string> ("limitbehavior", "repeat") == "mirror",
  1069. controlPointIndex (it, "controlpointstart", 0), controlPointIndex (it, "controlpointend", 1),
  1070. it.optional ("flags", 0u), it.user ("arcamount", properties, 0.3f),
  1071. it.user ("arcdirection", properties, glm::vec3 (0.0f, 1.0f, 0.0f)),
  1072. it.user ("sizereductionamount", properties, 0.9f)
  1073. );
  1074. } else if (name == "remapinitialvalue") {
  1075. return std::make_unique<RemapInitialValueInitializer> (parseRemap (it, "maxlifetime"));
  1076. }
  1077. return nullptr;
  1078. }
  1079. ParticleOperatorUniquePtr ObjectParser::parseParticleOperator (const JSON& it, const Properties& properties) {
  1080. std::string name = it.optional<std::string> ("name", "");
  1081. if (name == "movement") {
  1082. return std::make_unique<MovementOperator> (
  1083. it.user ("drag", properties, 0.0f), it.user ("gravity", properties, glm::vec3 (0.0f)),
  1084. it.optional ("flags", 0u)
  1085. );
  1086. } else if (name == "angularmovement") {
  1087. return std::make_unique<AngularMovementOperator> (
  1088. it.user ("drag", properties, 0.0f), it.user ("force", properties, glm::vec3 (0.0f))
  1089. );
  1090. } else if (name == "alphafade") {
  1091. return std::make_unique<AlphaFadeOperator> (
  1092. it.user ("fadeintime", properties, 0.5f), it.user ("fadeouttime", properties, 0.5f)
  1093. );
  1094. } else if (name == "sizechange") {
  1095. return std::make_unique<SizeChangeOperator> (
  1096. it.user ("starttime", properties, 0.0f), it.user ("endtime", properties, 1.0f),
  1097. it.user ("startvalue", properties, 1.0f), it.user ("endvalue", properties, 0.0f)
  1098. );
  1099. } else if (name == "alphachange") {
  1100. return std::make_unique<AlphaChangeOperator> (
  1101. it.user ("starttime", properties, 0.0f), it.user ("endtime", properties, 1.0f),
  1102. it.user ("startvalue", properties, 1.0f), it.user ("endvalue", properties, 0.0f)
  1103. );
  1104. } else if (name == "colorchange") {
  1105. return std::make_unique<ColorChangeOperator> (
  1106. it.user ("starttime", properties, 0.0f), it.user ("endtime", properties, 1.0f),
  1107. it.user ("startvalue", properties, glm::vec3 (1.0f)), it.user ("endvalue", properties, glm::vec3 (1.0f))
  1108. );
  1109. } else if (name == "turbulence") {
  1110. // defaults from sub_1401BEB80, the 2D/3D ones are picked by CParticle
  1111. return std::make_unique<TurbulenceOperator> (
  1112. userIfSet (it, "scale", properties), userIfSet (it, "speedmin", properties),
  1113. userIfSet (it, "speedmax", properties), userIfSet (it, "timescale", properties),
  1114. userIfSet (it, "mask", properties), it.user ("phasemin", properties, 0.0f),
  1115. it.user ("phasemax", properties, 0.0f), it.user ("audioprocessingmode", properties, 0),
  1116. it.user ("audioprocessingbounds", properties, glm::vec2 (0.8f, 1.0f)),
  1117. it.user ("audioprocessingexponent", properties, 2.0f),
  1118. it.user ("audioprocessingfrequencystart", properties, 0),
  1119. it.user ("audioprocessingfrequencyend", properties, 1)
  1120. );
  1121. } else if (name == "vortex" || name == "vortex_v2") {
  1122. // defaults from sub_1401BEF00 (vortex) and sub_1401BF2D0 (vortex_v2)
  1123. return std::make_unique<VortexOperator> (
  1124. name == "vortex_v2", controlPointIndex (it, "controlpoint", 0), it.optional ("flags", 0),
  1125. it.user ("axis", properties, glm::vec3 (0.0f, 0.0f, 1.0f)),
  1126. it.user ("offset", properties, glm::vec3 (0.0f)), userIfSet (it, "distanceinner", properties),
  1127. userIfSet (it, "distanceouter", properties), userIfSet (it, "speedinner", properties),
  1128. it.user ("speedouter", properties, 0.0f), it.user ("centerforce", properties, 1.0f),
  1129. userIfSet (it, "ringradius", properties), userIfSet (it, "ringwidth", properties),
  1130. userIfSet (it, "ringpulldistance", properties), userIfSet (it, "ringpullforce", properties),
  1131. it.user ("audioprocessingmode", properties, 0),
  1132. it.user ("audioprocessingbounds", properties, glm::vec2 (0.8f, 1.0f)),
  1133. it.user ("audioprocessingexponent", properties, 2.0f),
  1134. it.user ("audioprocessingfrequencystart", properties, 0),
  1135. it.user ("audioprocessingfrequencyend", properties, 1), parseBlendWindow (it)
  1136. );
  1137. } else if (name == "inheritvaluefromevent") {
  1138. return std::make_unique<InheritValueFromEventOperator> (
  1139. parseParticleEventInput (it, ParticleEventInput::SetColorOpacity),
  1140. glm::vec4 (
  1141. it.optional ("blendinstart", 0.0f), it.optional ("blendinend", 0.0f), it.optional ("blendoutstart", 1.0f),
  1142. it.optional ("blendoutend", 1.0f)
  1143. )
  1144. );
  1145. } else if (name == "controlpointattract") {
  1146. // defaults from sub_1401BDEE0. Its "offset" is loaded but never read by the operator
  1147. return std::make_unique<ControlPointAttractOperator> (
  1148. controlPointIndex (it, "controlpoint", 0), it.optional ("flags", 2), userIfSet (it, "scale", properties),
  1149. userIfSet (it, "threshold", properties), userIfSet (it, "deletethreshold", properties)
  1150. );
  1151. } else if (name == "oscillatealpha") {
  1152. return std::make_unique<OscillateAlphaOperator> (
  1153. it.user ("frequencymin", properties, 0.0f), it.user ("frequencymax", properties, 10.0f),
  1154. it.user ("scalemin", properties, 0.0f), it.user ("scalemax", properties, 1.0f),
  1155. it.user ("phasemin", properties, 0.0f), it.user ("phasemax", properties, glm::two_pi<float> ())
  1156. );
  1157. } else if (name == "oscillatesize") {
  1158. return std::make_unique<OscillateSizeOperator> (
  1159. it.user ("frequencymin", properties, 0.0f), it.user ("frequencymax", properties, 10.0f),
  1160. it.user ("scalemin", properties, 0.8f), it.user ("scalemax", properties, 1.2f),
  1161. it.user ("phasemin", properties, 0.0f), it.user ("phasemax", properties, glm::two_pi<float> ())
  1162. );
  1163. } else if (name == "oscillateposition") {
  1164. return std::make_unique<OscillatePositionOperator> (
  1165. it.user ("frequencymin", properties, 0.0f), it.user ("frequencymax", properties, 5.0f),
  1166. it.user ("scalemin", properties, 0.0f), it.user ("scalemax", properties, 10.0f),
  1167. it.user ("phasemin", properties, 0.0f), it.user ("phasemax", properties, glm::two_pi<float> ()),
  1168. it.user ("mask", properties, glm::vec3 (1.0f, 1.0f, 0.0f))
  1169. );
  1170. } else if (name == "capvelocity") {
  1171. return std::make_unique<CapVelocityOperator> (userIfSet (it, "maxspeed", properties), parseBlendWindow (it));
  1172. } else if (name == "boids") {
  1173. // defaults from wallpaper64.exe sub_1401BF700
  1174. return std::make_unique<BoidsOperator> (
  1175. userIfSet (it, "separationthreshold", properties), userIfSet (it, "neighborthreshold", properties),
  1176. userIfSet (it, "maxspeed", properties), it.user ("separationfactor", properties, 15.0f),
  1177. it.user ("alignmentfactor", properties, 1.0f), it.user ("cohesionfactor", properties, 2.0f),
  1178. it.optional ("flags", 1u)
  1179. );
  1180. } else if (name == "remapvalue") {
  1181. return std::make_unique<RemapValueOperator> (parseRemap (it, "lifetimefraction"), parseBlendWindow (it));
  1182. } else if (name == "maintaindistancetocontrolpoint") {
  1183. return std::make_unique<MaintainDistanceToControlPointOperator> (
  1184. controlPointIndex (it, "controlpoint", 0), userIfSet (it, "distance", properties),
  1185. it.user ("variablestrength", properties, 0.0f), parseBlendWindow (it)
  1186. );
  1187. } else if (name == "maintaindistancebetweencontrolpoints") {
  1188. return std::make_unique<MaintainDistanceBetweenControlPointsOperator> (
  1189. controlPointIndex (it, "controlpointstart", 0), controlPointIndex (it, "controlpointend", 1),
  1190. parseBlendWindow (it)
  1191. );
  1192. } else if (name == "reducemovementnearcontrolpoint") {
  1193. // defaults from wallpaper64.exe sub_1401BE810
  1194. return std::make_unique<ReduceMovementNearControlPointOperator> (
  1195. controlPointIndex (it, "controlpoint", 0), userIfSet (it, "distanceinner", properties),
  1196. userIfSet (it, "distanceouter", properties), it.user ("reductioninner", properties, 100.0f),
  1197. it.user ("reductionouter", properties, 0.0f), parseBlendWindow (it)
  1198. );
  1199. } else if (
  1200. name == "collisionplane" || name == "collisionsphere" || name == "collisionbox" || name == "collisionbounds"
  1201. || name == "collisionquad" || name == "collisionmodel"
  1202. ) {
  1203. static constexpr const char* behaviors[] = { "bounce", "slide", "stop", "delete" };
  1204. ParticleCollisionShape shape = ParticleCollisionShape::Plane;
  1205. if (name == "collisionsphere") {
  1206. shape = ParticleCollisionShape::Sphere;
  1207. } else if (name == "collisionbox") {
  1208. shape = ParticleCollisionShape::Box;
  1209. } else if (name == "collisionbounds") {
  1210. shape = ParticleCollisionShape::Bounds;
  1211. } else if (name == "collisionquad") {
  1212. shape = ParticleCollisionShape::Quad;
  1213. } else if (name == "collisionmodel") {
  1214. shape = ParticleCollisionShape::Model;
  1215. }
  1216. // sub_1401C03F0: anything but slide, stop or delete bounces
  1217. ParticleCollisionBehavior behavior = ParticleCollisionBehavior::Bounce;
  1218. const std::string behaviorName = it.optional<std::string> ("collisionbehavior", "bounce");
  1219. for (size_t i = 0; i < std::size (behaviors); i++) {
  1220. if (behaviorName == behaviors[i]) {
  1221. behavior = static_cast<ParticleCollisionBehavior> (i);
  1222. }
  1223. }
  1224. // defaults from wallpaper64.exe sub_1401C00A0, sub_1401C0540, sub_1401C0740 and sub_1401C0870
  1225. return std::make_unique<CollisionOperator> (
  1226. shape, behavior, it.user ("bouncefactor", properties, 0.5f), it.optional ("flags", 0u),
  1227. controlPointIndex (it, "controlpoint", 0), it.user ("plane", properties, glm::vec3 (0.0f, 1.0f, 0.0f)),
  1228. userIfSet (it, "distance", properties), userIfSet (it, "origin", properties),
  1229. userIfSet (it, "radius", properties), it.user ("forward", properties, glm::vec3 (0.0f, 0.0f, 1.0f)),
  1230. userIfSet (it, "size", properties)
  1231. );
  1232. }
  1233. return nullptr;
  1234. }
  1235. ParticleRenderer ObjectParser::parseParticleRenderer (const JSON& it) {
  1236. std::string name = "sprite";
  1237. const auto nameIt = it.find ("name");
  1238. if (nameIt != it.end () && nameIt->is_string ()) {
  1239. name = nameIt->get<std::string> ();
  1240. }
  1241. float subdivisionDefault = (name == "rope") ? 4.0f : 1.0f;
  1242. float lengthDefault = (name == "ropetrail") ? 1.0f : 0.05f;
  1243. return ParticleRenderer {
  1244. .name = name,
  1245. .length = it.optional ("length", lengthDefault),
  1246. .maxLength = it.optional ("maxlength", 10.0f),
  1247. .minLength = it.optional ("minlength", 0.0f),
  1248. .subdivision = it.optional ("subdivision", subdivisionDefault),
  1249. .segments = it.optional ("segments", 4.0f),
  1250. .uvScale = it.optional ("uvscale", 1.0f),
  1251. .uvScrolling = it.optional ("uvscrolling", false),
  1252. .uvSmoothing = it.optional ("uvsmoothing", true),
  1253. .fadeAlpha = it.optional ("fadealpha", false),
  1254. .fadeSize = it.optional ("fadesize", false),
  1255. };
  1256. }
  1257. ParticleEventInput ObjectParser::parseParticleEventInput (const JSON& it, ParticleEventInput fallback) {
  1258. // wallpaper64.exe off_140484D90
  1259. static constexpr const char* names[] = {
  1260. "setcolor", "multiplycolor", "setopacity", "multiplyopacity", "setcoloropacity", "multiplycoloropacity",
  1261. "setvelocity", "addvelocity", "setsize", "multiplysize", "setrotation", "addrotation", "setangularvelocity",
  1262. "addangularvelocity",
  1263. };
  1264. const auto inputIt = it.find ("input");
  1265. if (inputIt == it.end () || !inputIt->is_string ()) {
  1266. return fallback;
  1267. }
  1268. const std::string input = inputIt->get<std::string> ();
  1269. for (size_t i = 0; i < std::size (names); i++) {
  1270. if (input == names[i]) {
  1271. return static_cast<ParticleEventInput> (i);
  1272. }
  1273. }
  1274. sLog.error ("Unknown particle event input ", input);
  1275. return fallback;
  1276. }
  1277. ParticleControlPoint ObjectParser::parseParticleControlPoint (const JSON& it) {
  1278. // offset can be string "x y z" or array [x,y,z]
  1279. glm::vec3 offset (0.0f);
  1280. const auto offsetIt = it.find ("offset");
  1281. if (offsetIt != it.end ()) {
  1282. if (offsetIt->is_string ()) {
  1283. std::string offsetStr = offsetIt->get<std::string> ();
  1284. std::istringstream iss (offsetStr);
  1285. iss >> offset.x >> offset.y >> offset.z;
  1286. } else {
  1287. try {
  1288. offset = it.optional ("offset", glm::vec3 (0.0f));
  1289. } catch (...) {
  1290. offset = glm::vec3 (0.0f);
  1291. }
  1292. }
  1293. }
  1294. return ParticleControlPoint {
  1295. .id = it.optional ("id", -1),
  1296. .flags = it.optional ("flags", 0u),
  1297. .offset = offset,
  1298. .lockToPointer = it.optional ("locktopointer", false),
  1299. .parentControlPoint = it.optional ("parentcontrolpoint", 0),
  1300. };
  1301. }
  1302. ParticleChild ObjectParser::parseParticleChild (
  1303. const JSON& it, const JSON& owner, const Project& project, const ObjectData& ownerBase, size_t index, int depth
  1304. ) {
  1305. // defaults and type names per wallpaper64.exe sub_1401C1430 / sub_1401C5490
  1306. ParticleChildType type = ParticleChildType::Static;
  1307. std::string typeName = "static";
  1308. if (const auto typeIt = it.find ("type"); typeIt != it.end () && typeIt->is_string ()) {
  1309. typeName = typeIt->get<std::string> ();
  1310. }
  1311. if (typeName == "eventfollow") {
  1312. type = ParticleChildType::EventFollow;
  1313. } else if (typeName == "eventspawn") {
  1314. type = ParticleChildType::EventSpawn;
  1315. } else if (typeName == "eventdeath") {
  1316. type = ParticleChildType::EventDeath;
  1317. } else if (typeName != "static") {
  1318. sLog.error ("Unknown particle child type ", typeName, ", treating it as static");
  1319. }
  1320. std::string name = "";
  1321. const auto nameIt = it.find ("name");
  1322. if (nameIt != it.end () && nameIt->is_string ()) {
  1323. name = nameIt->get<std::string> ();
  1324. }
  1325. // vec3 fields may show up as strings, arrays, single numbers, or be missing entirely
  1326. auto parseVec3 = [&] (const char* fieldName, const glm::vec3& defaultValue) -> glm::vec3 {
  1327. const auto fieldIt = it.find (fieldName);
  1328. if (fieldIt == it.end ()) {
  1329. return defaultValue;
  1330. }
  1331. if (fieldIt->is_string ()) {
  1332. return it.optional (fieldName, defaultValue);
  1333. }
  1334. if (fieldIt->is_number ()) {
  1335. float val = fieldIt->get<float> ();
  1336. return glm::vec3 (val, val, val);
  1337. }
  1338. if (fieldIt->is_array () && fieldIt->size () >= 3) {
  1339. return glm::vec3 ((*fieldIt)[0].get<float> (), (*fieldIt)[1].get<float> (), (*fieldIt)[2].get<float> ());
  1340. }
  1341. return defaultValue;
  1342. };
  1343. const glm::vec3 angles = parseVec3 ("angles", glm::vec3 (0.0f));
  1344. const glm::vec3 origin = parseVec3 ("origin", glm::vec3 (0.0f));
  1345. const glm::vec3 scale = parseVec3 ("scale", glm::vec3 (1.0f));
  1346. // WE's row vector matrix: rotation rows from z/y/x angles, each row scaled by its scale component, origin last
  1347. const float cz = std::cos (angles.z), sz = std::sin (angles.z);
  1348. const float cy = std::cos (angles.y), sy = std::sin (angles.y);
  1349. const float cx = std::cos (angles.x), sx = std::sin (angles.x);
  1350. const glm::vec3 row0 (cy * cz, cy * sz, -sy);
  1351. const glm::vec3 row1 (sy * cz * sx - cx * sz, sy * sz * sx + cx * cz, sx * cy);
  1352. const glm::vec3 row2 (sx * sz + sy * cx * cz, sy * cx * sz - sx * cz, cx * cy);
  1353. const glm::mat4 weTransform (
  1354. glm::vec4 (row0 * scale.x, 0.0f), glm::vec4 (row1 * scale.y, 0.0f), glm::vec4 (row2 * scale.z, 0.0f),
  1355. glm::vec4 (origin, 1.0f)
  1356. );
  1357. // particle space here is WE's with y flipped, same as emitter origins and velocities
  1358. const glm::mat4 flipY = glm::scale (glm::mat4 (1.0f), glm::vec3 (1.0f, -1.0f, 1.0f));
  1359. // the child is loaded like a particle object of its own that shares the owner's instance override
  1360. JSON childObject = JSON::object ();
  1361. childObject["particle"] = name;
  1362. if (const auto overrideIt = owner.find ("instanceoverride"); overrideIt != owner.end ()) {
  1363. childObject["instanceoverride"] = *overrideIt;
  1364. }
  1365. // a stable id of its own keeps its random seed (LWE_FIXED_TIMESTEP) apart from the owner's
  1366. const uint32_t hashed = (static_cast<uint32_t> (ownerBase.id) * 31u + static_cast<uint32_t> (index) + 1u) * 2654435761u;
  1367. ParticleUniquePtr particle = nullptr;
  1368. if (!name.empty ()) {
  1369. try {
  1370. particle = parseParticle (
  1371. childObject, project,
  1372. ObjectData {
  1373. .id = -1 - static_cast<int> (hashed & 0x3FFFFFFFu),
  1374. .name = ownerBase.name + " > " + name,
  1375. .dependencies = {},
  1376. .parent = std::nullopt,
  1377. .attachment = std::nullopt,
  1378. .sortOrder = std::nullopt,
  1379. .origin = Builders::UserSettingBuilder::fromValue (glm::vec3 (0.0f)),
  1380. .groupScale = Builders::UserSettingBuilder::fromValue (glm::vec3 (1.0f)),
  1381. .groupAngles = Builders::UserSettingBuilder::fromValue (glm::vec3 (0.0f)),
  1382. .groupVisible = Builders::UserSettingBuilder::fromValue (true),
  1383. .groupParallaxDepth = Builders::UserSettingBuilder::fromValue (glm::vec2 (1.0f)),
  1384. .solid = Builders::UserSettingBuilder::fromValue (true),
  1385. .disablePropagation = Builders::UserSettingBuilder::fromValue (false),
  1386. .perspective = Builders::UserSettingBuilder::fromValue (false),
  1387. },
  1388. depth
  1389. );
  1390. } catch (const std::exception& e) {
  1391. sLog.error ("Cannot load child particle ", name, ": ", e.what ());
  1392. }
  1393. if (particle != nullptr && particle->material == nullptr) {
  1394. sLog.error ("Child particle ", name, " has no material, skipping it");
  1395. particle = nullptr;
  1396. }
  1397. }
  1398. return ParticleChild {
  1399. .type = type,
  1400. .name = name,
  1401. .maxCount = it.optional ("maxcount", 10),
  1402. .probability = it.optional ("probability", 1.0f),
  1403. .flags = it.optional ("flags", 0u),
  1404. .controlPointStartIndex = it.optional ("controlpointstartindex", 0),
  1405. .transform = flipY * weTransform * flipY,
  1406. .particle = std::move (particle),
  1407. };
  1408. }
  1409. ParticleInstanceOverride ObjectParser::parseParticleInstanceOverride (const JSON& it, const Properties& properties) {
  1410. auto result = ParticleInstanceOverride {
  1411. .enabled = it.user ("enabled", properties, true),
  1412. .alpha = it.user ("alpha", properties, 1.0f),
  1413. .size = it.user ("size", properties, 1.0f),
  1414. .lifetime = it.user ("lifetime", properties, 1.0f),
  1415. .rate = it.user ("rate", properties, 1.0f),
  1416. .speed = it.user ("speed", properties, 1.0f),
  1417. .count = it.user ("count", properties, 1.0f),
  1418. .brightness = it.user ("brightness", properties, 1.0f),
  1419. .color = it.user ("color", properties, glm::vec3 (1.0f)),
  1420. .colorn = it.user ("colorn", properties, glm::vec3 (1.0f)),
  1421. .hasColor = it.optional ("colorn").has_value () || it.optional ("color").has_value (),
  1422. };
  1423. // property table sub_14024D940, applied by sub_14022BD40
  1424. for (size_t i = 0; i < result.controlPoints.size (); i++) {
  1425. result.controlPoints[i] = userIfSet (it, "controlpoint" + std::to_string (i), properties);
  1426. result.controlPointAngles[i] = userIfSet (it, "controlpointangle" + std::to_string (i), properties);
  1427. }
  1428. // WE converts the legacy 0-255 "color" into colorn on load (replacing any colorn) and only reads colorn after
  1429. if (it.optional ("color").has_value ()) {
  1430. result.colorn = Builders::UserSettingBuilder::fromValue (result.color->value->getVec3 () / 255.0f);
  1431. }
  1432. return result;
  1433. }