| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882 |
- #include "ObjectParser.h"
- #include "EffectParser.h"
- #include "MaterialParser.h"
- #include "ModelParser.h"
- #include "ShaderConstantParser.h"
- #include "TextureParser.h"
- #include "UserSettingParser.h"
- #include "WallpaperEngine/Data/Builders/ColorBuilder.h"
- #include "WallpaperEngine/Data/Model/Object.h"
- #include "WallpaperEngine/Data/Model/Project.h"
- #include "WallpaperEngine/Logging/Log.h"
- #include <glm/gtc/constants.hpp>
- #include <sstream>
- using namespace WallpaperEngine::Data::Parsers;
- using namespace WallpaperEngine::Data::Model;
- ObjectUniquePtr ObjectParser::parse (const JSON& it, const Project& project) {
- const auto imageIt = it.find ("image");
- const auto soundIt = it.find ("sound");
- const auto particleIt = it.find ("particle");
- const auto textIt = it.find ("text");
- const auto lightIt = it.find ("light");
- // use shape to refer to VolumeLight
- const auto shapeIt = it.find ("shape");
- // Parse base object data
- // Some particle objects have numeric 'name' fields, so handle type mismatches gracefully
- ObjectData basedata;
- try {
- basedata = ObjectData {
- .id = it.require<int> ("id", "Object must have an id"),
- .name = it.require<std::string> ("name", "Object must have a name"),
- .dependencies = parseDependencies (it),
- .parent = it.optional<int> ("parent"),
- .origin = it.user ("origin", project.properties, glm::vec3 (0.0f)),
- .groupScale = it.user ("scale", project.properties, glm::vec3 (1.0f)),
- .groupAngles = it.user ("angles", project.properties, glm::vec3 (0.0f)),
- .groupVisible = it.user ("visible", project.properties, true),
- };
- } catch (const std::exception& e) {
- sLog.error ("Error parsing object base data: ", e.what ());
- const auto idIt = it.find ("id");
- const auto nameIt = it.find ("name");
- int id = (idIt != it.end () && idIt->is_number ()) ? idIt->get<int> () : -1;
- std::string name = "unknown";
- if (nameIt != it.end ()) {
- if (nameIt->is_string ()) {
- name = nameIt->get<std::string> ();
- } else if (nameIt->is_number ()) {
- name = std::to_string (nameIt->get<int> ());
- }
- }
- basedata = ObjectData {
- .id = id,
- .name = name,
- .dependencies = parseDependencies (it),
- .parent = it.optional<int> ("parent"),
- .origin = it.user ("origin", project.properties, glm::vec3 (0.0f)),
- .groupScale = it.user ("scale", project.properties, glm::vec3 (1.0f)),
- .groupAngles = it.user ("angles", project.properties, glm::vec3 (0.0f)),
- .groupVisible = it.user ("visible", project.properties, true),
- };
- }
- if (imageIt != it.end () && imageIt->is_string ()) {
- return parseImage (it, project, std::move (basedata), *imageIt);
- } else if (soundIt != it.end () && soundIt->is_array ()) {
- return parseSound (it, std::move (basedata));
- } else if (particleIt != it.end () && !particleIt->is_null ()) {
- return parseParticle (it, project, std::move (basedata));
- } else if (textIt != it.end () && !textIt->is_null ()) {
- return parseText (it, project, std::move (basedata));
- } else if (lightIt != it.end () && !lightIt->is_null ()) {
- sLog.error ("Light objects are not supported yet");
- } else if (shapeIt != it.end () && !shapeIt->is_null ()) {
- sLog.error ("VolumeLight objects are not supported yet");
- } else {
- if (!it.optional ("solid", false)) {
- // dump the object for now, might want to change later
- // TODO: RE-EVALUATE IF THIS MAKES SENSE, THERE'S OBJECTS THAT CONTAIN OTHER OBJECTS AND THUS AREN'T REALLY
- // ANYTHING SPECIAL
- sLog.error ("Unknown object type found: ", it.dump ());
- }
- }
- return std::make_unique<Object> (std::move (basedata));
- }
- std::vector<int> ObjectParser::parseDependencies (const JSON& it) {
- const auto dependenciesIt = it.find ("dependencies");
- if (dependenciesIt == it.end () || !dependenciesIt->is_array ()) {
- return {};
- }
- std::vector<int> result = {};
- for (const auto& cur : *dependenciesIt) {
- result.push_back (cur);
- }
- return result;
- }
- SoundUniquePtr ObjectParser::parseSound (const JSON& it, ObjectData base) {
- const auto soundIt = it.require ("sound", "Object must have a sound");
- std::vector<std::string> sounds = {};
- for (const auto& cur : soundIt) {
- sounds.push_back (cur);
- }
- return std::make_unique<Sound> (
- std::move (base),
- SoundData {
- .playbackmode = it.optional<std::string> ("playbackmode"),
- .sounds = sounds,
- }
- );
- }
- TextUniquePtr ObjectParser::parseText (const JSON& it, const Project& project, ObjectData base) {
- const auto& effects = it.optional ("effects");
- return std::make_unique<Text> (
- std::move (base),
- TextData {
- .text = it.user ("text", project.properties),
- .font = it.optional ("font", std::string ()),
- .pointSize = it.user ("pointsize", project.properties, 32.0f),
- .size = it.optional ("size", glm::vec2 (0.0f)),
- .scale = it.user ("scale", project.properties, glm::vec3 (1.0f)),
- .color = it.color ("color", project.properties, Builders::ColorBuilder::White),
- .alpha = it.user ("alpha", project.properties, 1.0f),
- .visible = it.user ("visible", project.properties, true),
- .alignment = it.optional ("horizontalalign", it.optional ("alignment", std::string ("center"))),
- .verticalalign = it.optional ("verticalalign", std::string ("center")),
- .padding = it.optional ("padding", 0),
- .effects = effects.has_value () ? parseEffects (*effects, project) : std::vector<ImageEffectUniquePtr> {},
- }
- );
- }
- ImageUniquePtr
- ObjectParser::parseImage (const JSON& it, const Project& project, ObjectData base, const std::string& image) {
- const auto& properties = project.properties;
- const auto& effects = it.optional ("effects");
- const auto& animationLayers = it.optional ("animationlayers");
- auto result = std::make_unique<Image> (
- std::move (base),
- ImageData {
- .scale = it.user ("scale", properties, glm::vec3 (1.0f)),
- .angles = it.user ("angles", properties, glm::vec3 (0.0f)),
- .visible = it.user ("visible", properties, true),
- .alpha = it.user ("alpha", properties, 1.0f),
- .color = it.color ("color", properties, Builders::ColorBuilder::White),
- .alignment = it.optional ("horizontalalign", it.optional ("alignment", std::string ("center"))),
- .size = it.user ("size", properties, glm::vec2 (0.0f))->value->getVec2 (),
- .parallaxDepth = it.user ("parallaxDepth", properties, glm::vec2 (0.0f)),
- .colorBlendMode = it.user ("colorBlendMode", properties, 0),
- .brightness = it.user ("brightness", properties, 1.0f),
- .model = ModelParser::load (project, image),
- .effects = effects.has_value () ? parseEffects (*effects, project) : std::vector<ImageEffectUniquePtr> {},
- .animationLayers = animationLayers.has_value () ? parseAnimationLayers (*animationLayers, project)
- : std::vector<ImageAnimationLayerUniquePtr> {},
- }
- );
- const auto instance = it.optional ("instance");
- if (instance.has_value () && instance->is_object () && !result->model->material->passes.empty ()) {
- auto& firstPass = **result->model->material->passes.begin ();
- const auto instanceTextures = instance->optional ("textures");
- if (instanceTextures.has_value ()) {
- const auto parsed = TextureParser::parseTextureMap (*instanceTextures);
- firstPass.textures.insert (parsed.begin (), parsed.end ());
- }
- const auto instanceUserTextures = instance->optional ("usertextures");
- if (instanceUserTextures.has_value ()) {
- const auto parsed = TextureParser::parseTextureMap (*instanceUserTextures);
- firstPass.usertextures.insert (parsed.begin (), parsed.end ());
- }
- }
- return result;
- }
- std::vector<ImageEffectUniquePtr> ObjectParser::parseEffects (const JSON& it, const Project& project) {
- if (!it.is_array ()) {
- return {};
- }
- std::vector<ImageEffectUniquePtr> result = {};
- for (const auto& cur : it) {
- result.push_back (parseEffect (cur, project));
- }
- return result;
- }
- ImageEffectUniquePtr ObjectParser::parseEffect (const JSON& it, const Project& project) {
- const auto& passsOverrides = it.optional ("passes");
- return std::make_unique<ImageEffect> (ImageEffect {
- .id = it.optional<int> ("id", -1),
- .name = it.optional<std::string> ("name", "Effect without name"),
- .visible = it.user ("visible", project.properties, true),
- .passOverrides = passsOverrides.has_value () ? parseEffectPassOverrides (passsOverrides.value (), project)
- : std::vector<ImageEffectPassOverrideUniquePtr> {},
- .effect = EffectParser::load (project, it.require ("file", "Image effect must have an effect")) });
- }
- std::vector<ImageEffectPassOverrideUniquePtr>
- ObjectParser::parseEffectPassOverrides (const JSON& it, const Project& project) {
- if (!it.is_array ()) {
- return {};
- }
- std::vector<ImageEffectPassOverrideUniquePtr> result = {};
- for (const auto& cur : it) {
- result.push_back (parseEffectPass (cur, project));
- }
- return result;
- }
- ImageEffectPassOverrideUniquePtr ObjectParser::parseEffectPass (const JSON& it, const Project& project) {
- const auto& combos = it.optional ("combos");
- const auto& textures = it.optional ("textures");
- const auto& constants = it.optional ("constantshadervalues");
- const auto& usertextures = it.optional ("usertextures");
- // TODO: PARSE CONSTANT SHADER VALUES AND FIND REFS?
- return std::make_unique<ImageEffectPassOverride> (ImageEffectPassOverride {
- .id = it.optional<int> ("id", -1),
- .combos = combos.has_value () ? parseComboMap (combos.value ()) : ComboMap {},
- .constants
- = constants.has_value () ? ShaderConstantParser::parse (constants.value (), project) : ShaderConstantMap {},
- .textures = textures.has_value () ? TextureParser::parseTextureMap (textures.value ()) : TextureMap {},
- .usertextures
- = usertextures.has_value () ? TextureParser::parseTextureMap (usertextures.value ()) : TextureMap {},
- });
- }
- ComboMap ObjectParser::parseComboMap (const JSON& it) {
- if (!it.is_object ()) {
- return {};
- }
- ComboMap result = {};
- for (const auto& cur : it.items ()) {
- result.emplace (cur.key (), cur.value ());
- }
- return result;
- }
- std::vector<ImageAnimationLayerUniquePtr> ObjectParser::parseAnimationLayers (const JSON& it, const Project& project) {
- if (!it.is_array ()) {
- return {};
- }
- std::vector<ImageAnimationLayerUniquePtr> result = {};
- for (const auto& cur : it.items ()) {
- result.push_back (parseAnimationLayer (cur.value (), project));
- }
- return result;
- }
- ImageAnimationLayerUniquePtr ObjectParser::parseAnimationLayer (const JSON& it, const Project& project) {
- const auto& properties = project.properties;
- return std::make_unique<ImageAnimationLayer> (ImageAnimationLayer {
- .id = it.require<int> ("id", "Animation layer must have an id"),
- .rate = it.user ("rate", properties, 1.0f),
- .visible = it.user ("visible", properties, false),
- .blend = it.user ("blend", properties, 1.0f),
- .animation = it.user ("animation", properties, 0),
- });
- }
- ParticleUniquePtr ObjectParser::parseParticle (const JSON& it, const Project& project, ObjectData base) {
- try {
- const auto& properties = project.properties;
- const auto particleIt = it.find ("particle");
- if (particleIt == it.end ()) {
- sLog.error ("Particle object must have a particle definition");
- return std::make_unique<Particle> (
- std::move (base),
- ParticleData {
- .scale = it.user ("scale", properties, glm::vec3 (1.0f)),
- .angles = it.user ("angles", properties, glm::vec3 (0.0f)),
- .visible = it.user ("visible", properties, true),
- .parallaxDepth = it.user ("parallaxDepth", properties, glm::vec2 (0.0f)),
- .particleFile = "",
- .animationMode = "sequence",
- .sequenceMultiplier = 1.0f,
- .maxCount = 100,
- .startTime = 0,
- .flags = 0,
- .material = nullptr,
- .emitters = {},
- .initializers = {},
- .operators = {},
- .renderers = {},
- .controlPoints = {},
- .children = {},
- .instanceOverride = {
- .enabled = Builders::UserSettingBuilder::fromValue(false),
- .alpha = Builders::UserSettingBuilder::fromValue(1.0f),
- .size = Builders::UserSettingBuilder::fromValue(1.0f),
- .lifetime = Builders::UserSettingBuilder::fromValue(1.0f),
- .rate = Builders::UserSettingBuilder::fromValue(1.0f),
- .speed = Builders::UserSettingBuilder::fromValue(1.0f),
- .count = Builders::UserSettingBuilder::fromValue(1.0f),
- .color = Builders::UserSettingBuilder::fromValue(1.0f),
- .colorn = Builders::UserSettingBuilder::fromValue(1.0f),
- },
- }
- );
- }
- std::string particleFile;
- if (particleIt->is_string ()) {
- particleFile = particleIt->get<std::string> ();
- }
- // Load particle definition from file if it's a string reference
- JSON particleJson = JSON::object ();
- if (!particleFile.empty ()) {
- try {
- particleJson
- = WallpaperEngine::Data::JSON::JSON::parse (project.assetLocator->readString (particleFile));
- } catch (std::runtime_error& e) {
- sLog.error ("Cannot load particle file: ", particleFile, " - ", e.what ());
- }
- } else if (particleIt->is_object ()) {
- particleJson = *particleIt;
- }
- // Parse emitters (note: field is named "emitter" not "emitters")
- std::vector<ParticleEmitter> emitters;
- const auto emittersIt = particleJson.find ("emitter");
- if (emittersIt != particleJson.end () && emittersIt->is_array ()) {
- for (const auto& emitter : *emittersIt) {
- emitters.push_back (parseParticleEmitter (emitter));
- }
- }
- // Parse initializers (note: field is named "initializer" not "initializers")
- std::vector<ParticleInitializerUniquePtr> initializers;
- const auto initializersIt = particleJson.find ("initializer");
- if (initializersIt != particleJson.end () && initializersIt->is_array ()) {
- for (const auto& initializer : *initializersIt) {
- auto init = parseParticleInitializer (initializer, project.properties);
- if (init) {
- initializers.push_back (std::move (init));
- }
- }
- }
- // Parse operators (note: field is named "operator" not "operators")
- std::vector<ParticleOperatorUniquePtr> operators;
- const auto operatorsIt = particleJson.find ("operator");
- if (operatorsIt != particleJson.end () && operatorsIt->is_array ()) {
- for (const auto& op : *operatorsIt) {
- auto oper = parseParticleOperator (op, project.properties);
- if (oper) {
- operators.push_back (std::move (oper));
- }
- }
- }
- // Parse renderers (note: field is named "renderer" not "renderers")
- std::vector<ParticleRenderer> renderers;
- const auto renderersIt = particleJson.find ("renderer");
- if (renderersIt != particleJson.end () && renderersIt->is_array ()) {
- for (const auto& renderer : *renderersIt) {
- renderers.push_back (parseParticleRenderer (renderer));
- }
- }
- // Add default sprite renderer if none specified
- if (renderers.empty ()) {
- renderers.push_back (
- ParticleRenderer {
- .name = "sprite",
- .length = 0.05f,
- .maxLength = 10.0f,
- .minLength = 0.0f,
- .subdivision = 1.0f,
- .segments = 4.0f,
- .uvScale = 1.0f,
- .uvScrolling = false,
- .uvSmoothing = true,
- .fadeAlpha = false,
- .fadeSize = false,
- }
- );
- }
- // Parse control points (note: field is named "controlpoint" not "controlpoints")
- std::vector<ParticleControlPoint> controlPoints;
- const auto controlPointsIt = particleJson.find ("controlpoint");
- if (controlPointsIt != particleJson.end () && controlPointsIt->is_array ()) {
- for (const auto& cp : *controlPointsIt) {
- controlPoints.push_back (parseParticleControlPoint (cp));
- }
- }
- // Parse children
- std::vector<ParticleChild> children;
- const auto childrenIt = particleJson.optional ("children");
- if (childrenIt.has_value () && childrenIt->is_array ()) {
- for (const auto& child : *childrenIt) {
- children.push_back (parseParticleChild (child, project));
- }
- }
- // Parse instance override
- ParticleInstanceOverride instanceOverride = {
- .enabled = Builders::UserSettingBuilder::fromValue (false),
- .alpha = Builders::UserSettingBuilder::fromValue (1.0f),
- .size = Builders::UserSettingBuilder::fromValue (1.0f),
- .lifetime = Builders::UserSettingBuilder::fromValue (1.0f),
- .rate = Builders::UserSettingBuilder::fromValue (1.0f),
- .speed = Builders::UserSettingBuilder::fromValue (1.0f),
- .count = Builders::UserSettingBuilder::fromValue (1.0f),
- .color = Builders::UserSettingBuilder::fromValue (1.0f),
- .colorn = Builders::UserSettingBuilder::fromValue (1.0f),
- };
- const auto instanceOverrideIt = it.optional ("instanceoverride");
- if (instanceOverrideIt.has_value ()) {
- instanceOverride = parseParticleInstanceOverride (*instanceOverrideIt, project.properties);
- }
- // Parse material - particles reference materials directly, not models
- ModelUniquePtr material = nullptr;
- const auto materialIt = particleJson.find ("material");
- if (materialIt != particleJson.end () && materialIt->is_string ()) {
- try {
- std::string materialPath = materialIt->get<std::string> ();
- // Particle materials are stored as just material definitions, not model files
- // So we need to wrap them in a model structure
- auto mat = MaterialParser::load (project, materialPath);
- material = std::make_unique<ModelStruct> (ModelStruct {
- .filename = materialPath,
- .material = std::move (mat),
- .solidlayer = false,
- .fullscreen = false,
- .passthrough = false,
- .autosize = false,
- .nopadding = false,
- .width = std::nullopt,
- .height = std::nullopt,
- .puppet = std::nullopt,
- });
- } catch (std::runtime_error& e) {
- sLog.error ("Cannot load particle material: ", materialIt->get<std::string> (), " - ", e.what ());
- }
- }
- // Parse string fields safely
- std::string animationMode = "sequence";
- const auto animModeIt = particleJson.find ("animationmode");
- if (animModeIt != particleJson.end () && animModeIt->is_string ()) {
- animationMode = animModeIt->get<std::string> ();
- }
- // Parse numeric fields safely
- float sequenceMultiplier = 1.0f;
- uint32_t maxCount = 100;
- uint32_t startTime = 0;
- uint32_t flags = 0;
- const auto seqMultIt = particleJson.find ("sequencemultiplier");
- if (seqMultIt != particleJson.end () && seqMultIt->is_number ()) {
- sequenceMultiplier = seqMultIt->get<float> ();
- }
- const auto maxCountIt = particleJson.find ("maxcount");
- if (maxCountIt != particleJson.end () && maxCountIt->is_number ()) {
- maxCount = maxCountIt->get<uint32_t> ();
- }
- const auto startTimeIt = particleJson.find ("starttime");
- if (startTimeIt != particleJson.end () && startTimeIt->is_number ()) {
- startTime = startTimeIt->get<uint32_t> ();
- }
- const auto flagsIt = particleJson.find ("flags");
- if (flagsIt != particleJson.end () && flagsIt->is_number ()) {
- flags = flagsIt->get<uint32_t> ();
- }
- return std::make_unique<Particle> (
- std::move (base),
- ParticleData {
- .scale = it.user ("scale", properties, glm::vec3 (1.0f)),
- .angles = it.user ("angles", properties, glm::vec3 (0.0f)),
- .visible = it.user ("visible", properties, true),
- .parallaxDepth = it.user ("parallaxDepth", properties, glm::vec2 (0.0f)),
- .particleFile = particleFile,
- .animationMode = animationMode,
- .sequenceMultiplier = sequenceMultiplier,
- .maxCount = maxCount,
- .startTime = startTime,
- .flags = flags,
- .material = std::move (material),
- .emitters = std::move (emitters),
- .initializers = std::move (initializers),
- .operators = std::move (operators),
- .renderers = std::move (renderers),
- .controlPoints = std::move (controlPoints),
- .children = std::move (children),
- .instanceOverride = std::move (instanceOverride),
- }
- );
- } catch (nlohmann::json::exception& e) {
- sLog.error ("Error parsing particle '", base.name, "': ", e.what ());
- sLog.error ("Particle JSON: ", it.dump ());
- throw;
- }
- }
- ParticleEmitter ObjectParser::parseParticleEmitter (const JSON& it) {
- // Parse string name safely
- std::string name;
- const auto nameIt = it.find ("name");
- if (nameIt != it.end () && nameIt->is_string ()) {
- name = nameIt->get<std::string> ();
- }
- // Helper lambda to parse vec3 fields that might be strings, arrays, single numbers, or missing
- auto parseVec3 = [&] (const char* fieldName, const glm::vec3& defaultValue) -> glm::vec3 {
- const auto fieldIt = it.find (fieldName);
- if (fieldIt == it.end ()) {
- return defaultValue;
- }
- if (fieldIt->is_string ()) {
- return it.optional (fieldName, defaultValue);
- }
- if (fieldIt->is_number ()) {
- // Single number - use for all components (common for distancemax/distancemin)
- float val = fieldIt->get<float> ();
- return glm::vec3 (val, val, val);
- }
- if (fieldIt->is_array () && fieldIt->size () >= 3) {
- return glm::vec3 ((*fieldIt)[0].get<float> (), (*fieldIt)[1].get<float> (), (*fieldIt)[2].get<float> ());
- }
- return defaultValue;
- };
- auto parseIVec3 = [&] (const char* fieldName, const glm::ivec3& defaultValue) -> glm::ivec3 {
- const auto fieldIt = it.find (fieldName);
- if (fieldIt == it.end ()) {
- return defaultValue;
- }
- if (fieldIt->is_array () && fieldIt->size () >= 3) {
- return glm::ivec3 ((*fieldIt)[0].get<int> (), (*fieldIt)[1].get<int> (), (*fieldIt)[2].get<int> ());
- }
- return defaultValue;
- };
- auto parseVec2 = [&] (const char* fieldName, const glm::vec2& defaultValue) -> glm::vec2 {
- const auto fieldIt = it.find (fieldName);
- if (fieldIt == it.end ()) {
- return defaultValue;
- }
- if (fieldIt->is_string ()) {
- return it.optional (fieldName, defaultValue);
- }
- if (fieldIt->is_array () && fieldIt->size () >= 2) {
- return glm::vec2 ((*fieldIt)[0].get<float> (), (*fieldIt)[1].get<float> ());
- }
- return defaultValue;
- };
- try {
- return ParticleEmitter {
- .id = it.optional ("id", -1),
- .name = name,
- .directions = parseVec3 ("directions", glm::vec3 (1.0f, 1.0f, 0.0f)),
- .distanceMin = parseVec3 ("distancemin", glm::vec3 (0.0f, 0.0f, 0.0f)),
- .distanceMax = parseVec3 ("distancemax", glm::vec3 (256.0f, 256.0f, 0.0f)),
- .origin = parseVec3 ("origin", glm::vec3 (0.0f)),
- .sign = parseIVec3 ("sign", glm::ivec3 (0)),
- .instantaneous = it.optional ("instantaneous", 0u),
- .speedMin = it.optional ("speedmin", 0.0f),
- .speedMax = it.optional ("speedmax", 0.0f),
- .rate = it.optional ("rate", 10.0f),
- .controlPoint = it.optional ("controlpoint", 0),
- .flags = it.optional ("flags", 0u),
- .cone = it.optional ("cone", 0.0f),
- .delay = it.optional ("delay", 0.0f),
- .duration = it.optional ("duration", 0.0f),
- .audioProcessingBounds = parseVec2 ("audioprocessingbounds", glm::vec2 (0.8f, 1.0f)),
- .audioProcessingExponent = it.optional ("audioprocessingexponent", 2),
- .audioProcessingFrequencyStart = it.optional ("audioprocessingfrequencystart", 0),
- .audioProcessingFrequencyEnd = it.optional ("audioprocessingfrequencyend", 1),
- .audioProcessingMode = it.optional ("audioprocessingmode", 0),
- .minPeriodicDelay = it.optional ("minperiodicdelay", 1.0f),
- .maxPeriodicDelay = it.optional ("maxperiodicdelay", 2.0f),
- .minPeriodicDuration = it.optional ("minperiodicduration", 2.0f),
- .maxPeriodicDuration = it.optional ("maxperiodicduration", 3.0f),
- };
- } catch (nlohmann::json::exception& e) {
- sLog.error ("Error parsing emitter: ", e.what ());
- sLog.error ("Emitter JSON: ", it.dump ());
- throw;
- }
- }
- ParticleInitializerUniquePtr ObjectParser::parseParticleInitializer (const JSON& it, const Properties& properties) {
- std::string name = it.optional<std::string> ("name", "");
- if (name == "colorrandom") {
- return std::make_unique<ColorRandomInitializer> (
- it.color ("min", properties, Builders::ColorBuilder::Black),
- it.color ("max", properties, Builders::ColorBuilder::White)
- );
- } else if (name == "sizerandom") {
- return std::make_unique<SizeRandomInitializer> (
- it.user ("min", properties, 0.0f), it.user ("max", properties, 20.0f),
- it.user ("exponent", properties, 1.0f)
- );
- } else if (name == "alpharandom") {
- return std::make_unique<AlphaRandomInitializer> (
- it.user ("min", properties, 0.05f), it.user ("max", properties, 1.0f)
- );
- } else if (name == "lifetimerandom") {
- return std::make_unique<LifetimeRandomInitializer> (
- it.user ("min", properties, 0.0f), it.user ("max", properties, 1.0f)
- );
- } else if (name == "velocityrandom") {
- return std::make_unique<VelocityRandomInitializer> (
- it.user ("min", properties, glm::vec3 (-32.0f)), it.user ("max", properties, glm::vec3 (32.0f))
- );
- } else if (name == "rotationrandom") {
- return std::make_unique<RotationRandomInitializer> (
- it.user ("min", properties, glm::vec3 (0.0f)),
- it.user ("max", properties, glm::vec3 (0.0f, 0.0f, glm::two_pi<float> ()))
- );
- } else if (name == "angularvelocityrandom") {
- return std::make_unique<AngularVelocityRandomInitializer> (
- it.user ("min", properties, glm::vec3 (0.0f, 0.0f, -5.0f)),
- it.user ("max", properties, glm::vec3 (0.0f, 0.0f, 5.0f)), it.user ("exponent", properties, 1.0f)
- );
- } else if (name == "turbulentvelocityrandom") {
- return std::make_unique<TurbulentVelocityRandomInitializer> (
- it.user ("speedmin", properties, 100.0f), it.user ("speedmax", properties, 250.0f),
- it.user ("scale", properties, 1.0f), it.user ("offset", properties, 0.0f),
- it.user ("forward", properties, glm::vec3 (0.0f, 1.0f, 0.0f)), it.user ("timescale", properties, 1.0f),
- it.user ("phasemin", properties, 0.0f), it.user ("phasemax", properties, 0.1f),
- it.user ("right", properties, glm::vec3 (0.0f, 0.0f, 1.0f))
- );
- } else if (name == "mapsequencearoundcontrolpoint") {
- return std::make_unique<MapSequenceAroundControlPointInitializer> (
- it.user ("controlpoint", properties, 0), it.user ("count", properties, 1),
- it.user ("speedmin", properties, glm::vec3 (0.0f)), it.user ("speedmax", properties, glm::vec3 (100.0f))
- );
- }
- return nullptr;
- }
- ParticleOperatorUniquePtr ObjectParser::parseParticleOperator (const JSON& it, const Properties& properties) {
- std::string name = it.optional<std::string> ("name", "");
- if (name == "movement") {
- return std::make_unique<MovementOperator> (
- it.user ("drag", properties, 0.0f), it.user ("gravity", properties, glm::vec3 (0.0f))
- );
- } else if (name == "angularmovement") {
- return std::make_unique<AngularMovementOperator> (
- it.user ("drag", properties, 0.0f), it.user ("force", properties, glm::vec3 (0.0f))
- );
- } else if (name == "alphafade") {
- return std::make_unique<AlphaFadeOperator> (
- it.user ("fadeintime", properties, 0.5f), it.user ("fadeouttime", properties, 0.5f)
- );
- } else if (name == "sizechange") {
- return std::make_unique<SizeChangeOperator> (
- it.user ("starttime", properties, 0.0f), it.user ("endtime", properties, 1.0f),
- it.user ("startvalue", properties, 1.0f), it.user ("endvalue", properties, 0.0f)
- );
- } else if (name == "alphachange") {
- return std::make_unique<AlphaChangeOperator> (
- it.user ("starttime", properties, 0.0f), it.user ("endtime", properties, 1.0f),
- it.user ("startvalue", properties, 1.0f), it.user ("endvalue", properties, 0.0f)
- );
- } else if (name == "colorchange") {
- return std::make_unique<ColorChangeOperator> (
- it.user ("starttime", properties, 0.0f), it.user ("endtime", properties, 1.0f),
- it.user ("startvalue", properties, glm::vec3 (1.0f)), it.user ("endvalue", properties, glm::vec3 (1.0f))
- );
- } else if (name == "turbulence") {
- return std::make_unique<TurbulenceOperator> (
- it.user ("scale", properties, 0.005f), it.user ("speedmin", properties, 500.0f),
- it.user ("speedmax", properties, 1000.0f), it.user ("timescale", properties, 0.01f),
- it.user ("mask", properties, glm::vec3 (1.0f, 1.0f, 0.0f)), it.user ("phasemin", properties, 0.0f),
- it.user ("phasemax", properties, 0.0f), it.user ("audioprocessingmode", properties, 0),
- it.user ("audioprocessingbounds", properties, glm::vec2 (0.0f, 1.0f)),
- it.user ("audioprocessingexponent", properties, 1.0f),
- it.user ("audioprocessingfrequencystart", properties, 0),
- it.user ("audioprocessingfrequencyend", properties, 15)
- );
- } else if (name == "vortex" || name == "vortex_v2") {
- return std::make_unique<VortexOperator> (
- it.optional ("controlpoint", 0),
- it.optional ("flags", 0), // 1 = infinite axis, 2 = maintain distance, 4 = ring shape
- it.user ("axis", properties, glm::vec3 (0.0f, 0.0f, 1.0f)),
- it.user ("offset", properties, glm::vec3 (0.0f)), it.user ("distanceinner", properties, 500.0f),
- it.user ("distanceouter", properties, 650.0f), it.user ("speedinner", properties, 2500.0f),
- it.user ("speedouter", properties, 0.0f), it.user ("centerforce", properties, 1.0f),
- it.user ("ringradius", properties, 300.0f), it.user ("ringwidth", properties, 50.0f),
- it.user ("ringpulldistance", properties, 50.0f), it.user ("ringpullforce", properties, 10.0f),
- it.user ("audioprocessingmode", properties, 0),
- it.user ("audioprocessingbounds", properties, glm::vec2 (0.0f, 1.0f))
- );
- } else if (name == "controlpointattract") {
- return std::make_unique<ControlPointAttractOperator> (
- it.optional ("controlpoint", 0), it.user ("origin", properties, glm::vec3 (0.0f)),
- it.user ("scale", properties, 100.0f), it.user ("threshold", properties, 1000.0f)
- );
- } else if (name == "oscillatealpha") {
- return std::make_unique<OscillateAlphaOperator> (
- it.user ("frequencymin", properties, 0.0f), it.user ("frequencymax", properties, 10.0f),
- it.user ("scalemin", properties, 0.0f), it.user ("scalemax", properties, 1.0f),
- it.user ("phasemin", properties, 0.0f), it.user ("phasemax", properties, glm::two_pi<float> ())
- );
- } else if (name == "oscillatesize") {
- return std::make_unique<OscillateSizeOperator> (
- it.user ("frequencymin", properties, 0.0f), it.user ("frequencymax", properties, 10.0f),
- it.user ("scalemin", properties, 0.8f), it.user ("scalemax", properties, 1.2f),
- it.user ("phasemin", properties, 0.0f), it.user ("phasemax", properties, glm::two_pi<float> ())
- );
- } else if (name == "oscillateposition") {
- return std::make_unique<OscillatePositionOperator> (
- it.user ("frequencymin", properties, 0.0f), it.user ("frequencymax", properties, 5.0f),
- it.user ("scalemin", properties, 0.0f), it.user ("scalemax", properties, 10.0f),
- it.user ("phasemin", properties, 0.0f), it.user ("phasemax", properties, glm::two_pi<float> ()),
- it.user ("mask", properties, glm::vec3 (1.0f, 1.0f, 0.0f))
- );
- }
- return nullptr;
- }
- ParticleRenderer ObjectParser::parseParticleRenderer (const JSON& it) {
- std::string name = "sprite";
- const auto nameIt = it.find ("name");
- if (nameIt != it.end () && nameIt->is_string ()) {
- name = nameIt->get<std::string> ();
- }
- // Renderer-type-specific defaults
- float subdivisionDefault = (name == "rope") ? 4.0f : 1.0f;
- float lengthDefault = (name == "ropetrail") ? 1.0f : 0.05f;
- return ParticleRenderer {
- .name = name,
- .length = it.optional ("length", lengthDefault),
- .maxLength = it.optional ("maxlength", 10.0f),
- .minLength = it.optional ("minlength", 0.0f),
- .subdivision = it.optional ("subdivision", subdivisionDefault),
- .segments = it.optional ("segments", 4.0f),
- .uvScale = it.optional ("uvscale", 1.0f),
- .uvScrolling = it.optional ("uvscrolling", false),
- .uvSmoothing = it.optional ("uvsmoothing", true),
- .fadeAlpha = it.optional ("fadealpha", false),
- .fadeSize = it.optional ("fadesize", false),
- };
- }
- ParticleControlPoint ObjectParser::parseParticleControlPoint (const JSON& it) {
- // Parse offset - can be string "x y z" or array [x,y,z]
- glm::vec3 offset (0.0f);
- const auto offsetIt = it.find ("offset");
- if (offsetIt != it.end ()) {
- if (offsetIt->is_string ()) {
- // Parse string format "x y z"
- std::string offsetStr = offsetIt->get<std::string> ();
- std::istringstream iss (offsetStr);
- iss >> offset.x >> offset.y >> offset.z;
- } else {
- // Try parsing as vec3 directly
- try {
- offset = it.optional ("offset", glm::vec3 (0.0f));
- } catch (...) {
- offset = glm::vec3 (0.0f);
- }
- }
- }
- return ParticleControlPoint {
- .id = it.optional ("id", -1),
- .flags = it.optional ("flags", 0u),
- .offset = offset,
- .lockToPointer = it.optional ("locktopointer", false),
- };
- }
- ParticleChild ObjectParser::parseParticleChild (const JSON& it, const Project& project) {
- std::string particleFile = "";
- const auto particleIt = it.find ("particle");
- if (particleIt != it.end () && particleIt->is_string ()) {
- particleFile = particleIt->get<std::string> ();
- }
- std::string type = "static";
- const auto typeIt = it.find ("type");
- if (typeIt != it.end () && typeIt->is_string ()) {
- type = typeIt->get<std::string> ();
- }
- std::string name = "";
- const auto nameIt = it.find ("name");
- if (nameIt != it.end () && nameIt->is_string ()) {
- name = nameIt->get<std::string> ();
- }
- // Helper lambda to parse vec3 fields that might be strings, arrays, single numbers, or missing
- auto parseVec3 = [&] (const char* fieldName, const glm::vec3& defaultValue) -> glm::vec3 {
- const auto fieldIt = it.find (fieldName);
- if (fieldIt == it.end ()) {
- return defaultValue;
- }
- if (fieldIt->is_string ()) {
- return it.optional (fieldName, defaultValue);
- }
- if (fieldIt->is_number ()) {
- // Single number - use for all components
- float val = fieldIt->get<float> ();
- return glm::vec3 (val, val, val);
- }
- if (fieldIt->is_array () && fieldIt->size () >= 3) {
- return glm::vec3 ((*fieldIt)[0].get<float> (), (*fieldIt)[1].get<float> (), (*fieldIt)[2].get<float> ());
- }
- return defaultValue;
- };
- return ParticleChild {
- .type = type,
- .name = name,
- .maxCount = it.optional ("maxcount", 20),
- .controlPointStartIndex = it.optional ("controlpointstartindex", 0),
- .probability = it.optional ("probability", 1.0f),
- .angles = parseVec3 ("angles", glm::vec3 (0.0f)),
- .origin = parseVec3 ("origin", glm::vec3 (0.0f)),
- .scale = parseVec3 ("scale", glm::vec3 (1.0f)),
- .particleFile = particleFile,
- };
- }
- ParticleInstanceOverride ObjectParser::parseParticleInstanceOverride (const JSON& it, const Properties& properties) {
- return ParticleInstanceOverride {
- .enabled = it.user ("enabled", properties, true),
- .alpha = it.user ("alpha", properties, 1.0f),
- .size = it.user ("size", properties, 1.0f),
- .lifetime = it.user ("lifetime", properties, 1.0f),
- .rate = it.user ("rate", properties, 1.0f),
- .speed = it.user ("speed", properties, 1.0f),
- .count = it.user ("count", properties, 1.0f),
- .color = it.user ("color", properties, glm::vec3 (1.0f)),
- .colorn = it.user ("colorn", properties, glm::vec3 (1.0f)),
- };
- }
|