diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 66d55df35b2..02567770f85 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -17,7 +17,7 @@ source_group(game FILES ${GAME} ${GAME_HEADER}) add_openmw_dir (mwrender mask bin rendermode renderinginterface renderpass intersect rendermanager terrainstorage landmanager - animcontext bodyparts wieldingcreature npc player face scene camera itemlight effect effects projectiles spellcastglow + animcontext bodyparts wieldingcreature npc player face scene camera itemlight effect effects projectiles spellcastglow transparency env weather weatherdata map worldmap preview shadow #bulletdebugdraw ripplesimulation diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 0415a549cab..a7abf5c441c 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1248,6 +1248,7 @@ namespace MWMechanics // Fade away actors on large distance (>90% of actor's processing distance) float visibilityRatio = 1.0; + /* const float fadeStartDistance = mActorsProcessingRange * 0.9f; const float fadeEndDistance = mActorsProcessingRange; const float fadeRatio = (dist - fadeStartDistance) / (fadeEndDistance - fadeStartDistance); @@ -1255,7 +1256,7 @@ namespace MWMechanics visibilityRatio -= std::max(0.f, fadeRatio); visibilityRatio = std::min(1.f, visibilityRatio); - +*/ ctrl.setVisibility(visibilityRatio); } @@ -1738,14 +1739,14 @@ namespace MWMechanics } charactersToUpdate.push_back(&ctrl); - //updateVisibility(actor.getPtr(), ctrl); + updateVisibility(actor.getPtr(), ctrl); } if (playerCharacter) { charactersToUpdate.push_back(playerCharacter); MWBase::Environment::get().getWorld()->applyDeferredPreviewRotationToPlayer(duration); - //playerCharacter->setVisibility(1.f); + playerCharacter->setVisibility(1.f); MWBase::LuaManager::ActorControls* luaControls = MWBase::Environment::get().getLuaManager()->getActorControls(player); if (luaControls && player.getClass().getMovementSettings(player).mPosition[2] < 1) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 66e4ddc65c2..bfc3b032546 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -22,6 +22,7 @@ #include "../mwrender/effect.hpp" #include "../mwrender/itemlight.hpp" #include "../mwrender/player.hpp" +#include "../mwrender/transparency.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" @@ -914,7 +915,7 @@ namespace MWMechanics * handle knockout and death which moves the character down. */ mAnimation->setAccumulation({ 1.0f, 1.0f, 0.0f }); - MWRender::addItemLightsAndListener(*mActor->transform(), cls.getContainerStore(mPtr)); + MWRender::addItemLightsAndListener(*mObject, cls.getContainerStore(mPtr)); if (cls.hasInventoryStore(mPtr)) { @@ -1866,7 +1867,7 @@ namespace MWMechanics speed = 0.f; updateMagicEffects(); - MWAnim::updateEffects(*mObject->transform(), duration); + MWAnim::updateEffects(*mObject->node(), duration); bool isPlayer = mPtr == MWMechanics::getPlayer(); bool isFirstPersonPlayer = isPlayer && MWBase::Environment::get().getWorld()->isFirstPerson(); @@ -2423,7 +2424,6 @@ namespace MWMechanics if (mActor) { mActor->manualAnimation(mPtr.getRefData().getPosition().rot[0], duration); - MWRender::updateItemLights(*mActor->transform(), duration); if (mWielding) { if (mWeaponType != 0 && !mCurrentWeapon.empty()) @@ -2765,9 +2765,7 @@ namespace MWMechanics visibility = std::min(visibility, alpha); } - // TODO: implement a dithering shader rather than just change object transparency. - // createBin(ChameleonPostFxBin) - // mAnimation->setAlpha(visibility); + MWRender::setTransparency(*mObject, visibility); } std::string_view CharacterController::getMovementBasedAttackType() const diff --git a/apps/openmw/mwmechanics/summoning.cpp b/apps/openmw/mwmechanics/summoning.cpp index 5694fcdcbaf..4f99c5eac48 100644 --- a/apps/openmw/mwmechanics/summoning.cpp +++ b/apps/openmw/mwmechanics/summoning.cpp @@ -62,12 +62,9 @@ namespace MWMechanics for (const auto& it : summonMapToGameSetting) { - summonMap[it.first] = ESM::RefId::stringRefId(MWBase::Environment::get() - .getWorld() - ->getStore() - .get() - .find(it.second) - ->mValue.getString()); + auto creature = MWBase::Environment::get().getWorld()->getStore().get().search(it.second); + if (creature) + summonMap[it.first] = ESM::RefId::stringRefId(creature->mValue.getString()); } return summonMap; } diff --git a/apps/openmw/mwrender/effect.cpp b/apps/openmw/mwrender/effect.cpp index cb1cbc54d26..92aba0d45e3 100644 --- a/apps/openmw/mwrender/effect.cpp +++ b/apps/openmw/mwrender/effect.cpp @@ -25,27 +25,30 @@ namespace MWRender const std::string& bone, const std::string& overrideTexture) { auto world = MWBase::Environment::get().getWorld(); - auto anim = world->getAnimation(ptr); - if (!anim || !anim->animation) // if (!hasCharacterController) + auto object = world->getAnimation(ptr); + if (!object || !object->animation) // if (!hasCharacterController) return; auto static_ = world->getStore().get().search(effect); if (!static_ || static_->mModel.empty()) return; - auto ctx = anim->context(); + auto ctx = object->context(); ctx.compileContext = ctx.compileContext->clone(Mask_Effect); auto node = ctx.readEffect(static_->mModel); auto meta = Anim::Meta::get(*node); - vsg::Group* attachTo = anim->transform(); + vsg::Group* attachBone{}; + std::vector worldAttachmentPath = { object->transform() }; if (!bone.empty()) { - auto b = anim->searchBone(bone); + auto b = object->searchBone(bone); if (!b) return; - attachTo = Anim::getOrAttachBone(attachTo, b->path); + attachBone = Anim::getOrAttachBone(object->nodeToAddChildrenTo(), b->path); + for (auto& n : b->path) + worldAttachmentPath.emplace_back(n); } else if (!ptr.getClass().isNpc()) { @@ -72,11 +75,11 @@ namespace MWRender if (meta) meta->attachTo(*node); - MWAnim::addEffect(ctx, *anim->transform(), *attachTo, node, magicEffectId, loop, overrideTexture); + MWAnim::addEffect(*object, attachBone, node, worldAttachmentPath, magicEffectId, loop, overrideTexture); } void removeEffect(const MWWorld::Ptr& ptr, std::optional effectId) { if (auto anim = MWBase::Environment::get().getWorld()->getAnimation(ptr)) - MWAnim::removeEffect(*anim->transform(), effectId); + MWAnim::removeEffect(*anim->node(), effectId); } } diff --git a/apps/openmw/mwrender/effects.cpp b/apps/openmw/mwrender/effects.cpp index 1e305d42ef0..8849201e8f5 100644 --- a/apps/openmw/mwrender/effects.cpp +++ b/apps/openmw/mwrender/effects.cpp @@ -23,21 +23,18 @@ namespace MWRender void Effects::add(const std::string& model, const std::string& textureOverride, const vsg::vec3& worldPosition, float scale, bool isMagicVFX) { - MWAnim::Effect effect{.mwctx=mContext}; - effect.overrideTexture = textureOverride; - effect.overrideAllTextures = !isMagicVFX; - effect.node = mContext.readNode(model); - effect.compile(); - auto transform = vsg::ref_ptr{ new Anim::Transform }; - transform->children = { effect.node }; - effect.node = transform; - - effect.attachTo(mNode.get()); - transform->translation = worldPosition; transform->setScale(scale); + MWAnim::Effect effect; + effect.mwctx = mContext; + + auto [anim, node] = MWAnim::Effect::load(mContext, mContext.readNode(model), textureOverride, !isMagicVFX); + transform->children = { node }; + effect.compile(anim, transform, { transform }, { node.get() } ); + + effect.attachTo(mNode.get()); mEffects->effects.push_back(effect); } diff --git a/apps/openmw/mwrender/env.cpp b/apps/openmw/mwrender/env.cpp index f85b99686b8..a9d8b3d386e 100644 --- a/apps/openmw/mwrender/env.cpp +++ b/apps/openmw/mwrender/env.cpp @@ -51,11 +51,12 @@ namespace MWRender return View::dummyEnvMap(); } - vsg::ref_ptr createEnv(const vsg::vec4& color) + vsg::ref_ptr createEnv(const vsg::vec4& color, float alpha) { auto sg = vsg::StateGroup::create(); - auto object = Pipeline::Object(0); + Pipeline::Object object; object.value().envColor = color; + object.value().alpha = alpha; auto layout = Pipeline::getCompatiblePipelineLayout(); sg->stateCommands = { vsg::BindDescriptorSet::create( VK_PIPELINE_BIND_POINT_GRAPHICS, layout, Pipeline::OBJECT_SET, vsg::Descriptors{ object.descriptor() }) }; @@ -69,18 +70,18 @@ namespace MWRender return {}; } - void addEnv(vsg::ref_ptr& node, std::optional color) + void addEnv(vsg::ref_ptr& node, std::optional color, float alpha) { - if (color) + if (color || alpha != 1.f) { - auto sg = createEnv(*color); + auto sg = createEnv(*color, alpha); vsgUtil::addChildren(*sg, *node); node = sg; } } - void addEnv(vsg::ref_ptr& node, const MWWorld::ConstPtr& item) + void addEnv(vsg::ref_ptr& node, const MWWorld::ConstPtr& item, float alpha) { - addEnv(node, getGlowColor(item)); + addEnv(node, getGlowColor(item), alpha); } } diff --git a/apps/openmw/mwrender/env.hpp b/apps/openmw/mwrender/env.hpp index 4b9d594759a..2a76e4ab8b1 100644 --- a/apps/openmw/mwrender/env.hpp +++ b/apps/openmw/mwrender/env.hpp @@ -16,9 +16,9 @@ namespace MWRender * Adds reflective glow. */ vsg::ref_ptr readEnv(vsg::ref_ptr options); - vsg::ref_ptr createEnv(const vsg::vec4& color); - void addEnv(vsg::ref_ptr& node, std::optional color); - void addEnv(vsg::ref_ptr& node, const MWWorld::ConstPtr& item); + vsg::ref_ptr createEnv(const vsg::vec4& color, float alpha = 1.f); + void addEnv(vsg::ref_ptr& node, std::optional color, float alpha = 1.f); + void addEnv(vsg::ref_ptr& node, const MWWorld::ConstPtr& item, float alpha = 1.f); std::optional getGlowColor(const MWWorld::ConstPtr& item); } diff --git a/apps/openmw/mwrender/itemlight.cpp b/apps/openmw/mwrender/itemlight.cpp index 6d86169d03f..9024e5e66d1 100644 --- a/apps/openmw/mwrender/itemlight.cpp +++ b/apps/openmw/mwrender/itemlight.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include "../mwworld/containerstore.hpp" @@ -25,58 +26,63 @@ namespace MWRender class UpdateItemLights : public MWWorld::ContainerStoreListener { public: - UpdateItemLights(vsg::Group& n) - : node(n) + UpdateItemLights(MWAnim::Object& obj) + : object(obj) { } - vsg::Group& node; - void itemAdded(const MWWorld::ConstPtr& item, int count) override { addItemLightIfRequired(node, item); } - void itemRemoved(const MWWorld::ConstPtr& item, int count) override { removeItemLight(node, item); } + MWAnim::Object& object; + void itemAdded(const MWWorld::ConstPtr& item, int count) override { addItemLightIfRequired(object, item); } + void itemRemoved(const MWWorld::ConstPtr& item, int count) override { removeItemLight(object, item); } }; - void addItemLight(vsg::Group& node, const MWWorld::ConstPtr& item, const ESM::Light& light) + void addItemLight(MWAnim::Object& obj, const MWWorld::ConstPtr& item, const ESM::Light& light) { ItemLights lights; - node.getValue(sItemLights, lights); + obj.node()->getValue(sItemLights, lights); if (lights.find(item) != lights.end()) return; // osg::Vec4f ambient(1,1,1,1); ItemLight l; - l.node = MWAnim::addLight(&node, &node, light, l.update.controllers); + l.node = MWAnim::addLight(obj.nodeToAddChildrenTo(), obj.nodeToAddChildrenTo(), light, obj.autoPlay.getOrCreateGroup().controllers); lights[item] = l; - node.setValue(sItemLights, lights); + obj.node()->setValue(sItemLights, lights); } - void removeItemLight(vsg::Group& node, const MWWorld::ConstPtr& item) + void removeItemLight(MWAnim::Object& obj, const MWWorld::ConstPtr& item) { if (item.getType() != ESM::Light::sRecordId) return; + if (!obj.node()->getAuxiliary()) + return; ItemLights lights; - node.getValue(sItemLights, lights); + obj.node()->getValue(sItemLights, lights); auto l = lights.find(item); if (l == lights.end()) return; - vsgUtil::removeChild(&node, l->second.node); - node.setValue(sItemLights, lights); + vsgUtil::removeChild(obj.nodeToAddChildrenTo(), l->second.node); + + auto& ctrls = obj.autoPlay.getOrCreateGroup().controllers; + auto citr = ctrls.begin(); + while (citr != ctrls.end()) + { + if (citr->second == l->second.node) + citr = ctrls.erase(citr); + else + ++citr; + } + + obj.node()->setValue(sItemLights, lights); } - void addItemLightsAndListener(vsg::Group& node, MWWorld::ContainerStore& store) + void addItemLightsAndListener(MWAnim::Object& obj, MWWorld::ContainerStore& store) { for (auto iter = store.cbegin(MWWorld::ContainerStore::Type_Light); iter != store.cend(); ++iter) { auto light = iter->get()->mBase; if (!(light->mData.mFlags & ESM::Light::Carry)) - addItemLight(node, *iter, *light); + addItemLight(obj, *iter, *light); } - store.setContListener(new UpdateItemLights(node)); - } - - void updateItemLights(vsg::Group& node, float dt) - { - ItemLights lights; - node.getValue(sItemLights, lights); - for (auto& [key, l] : lights) - l.update.update(dt); + store.setContListener(new UpdateItemLights(obj)); } void removeListener(MWWorld::ContainerStore& store) @@ -88,12 +94,12 @@ namespace MWRender } } - void addItemLightIfRequired(vsg::Group& node, const MWWorld::ConstPtr& item) + void addItemLightIfRequired(MWAnim::Object& obj, const MWWorld::ConstPtr& item) { if (item.getType() != ESM::Light::sRecordId) return; auto light = item.get()->mBase; if (!(light->mData.mFlags & ESM::Light::Carry)) - addItemLight(node, item, *light); + addItemLight(obj, item, *light); } } diff --git a/apps/openmw/mwrender/itemlight.hpp b/apps/openmw/mwrender/itemlight.hpp index 1346f929a8d..049388889f2 100644 --- a/apps/openmw/mwrender/itemlight.hpp +++ b/apps/openmw/mwrender/itemlight.hpp @@ -3,22 +3,21 @@ #include "../mwworld/ptr.hpp" -#include - -#include - namespace ESM { class Light; } +namespace MWAnim +{ + class Object; +} namespace MWRender { - void addItemLightsAndListener(vsg::Group& node, MWWorld::ContainerStore& store); + void addItemLightsAndListener(MWAnim::Object& obj, MWWorld::ContainerStore& store); void removeListener(MWWorld::ContainerStore& store); - void addItemLightIfRequired(vsg::Group& node, const MWWorld::ConstPtr& item); - void addItemLight(vsg::Group& node, const MWWorld::ConstPtr& item, const ESM::Light& esmLight); - void removeItemLight(vsg::Group& node, const MWWorld::ConstPtr& item); - void updateItemLights(vsg::Group& node, float dt); + void addItemLightIfRequired(MWAnim::Object& obj, const MWWorld::ConstPtr& item); + void addItemLight(MWAnim::Object& obj, const MWWorld::ConstPtr& item, const ESM::Light& esmLight); + void removeItemLight(MWAnim::Object& obj, const MWWorld::ConstPtr& item); } #endif diff --git a/apps/openmw/mwrender/map.cpp b/apps/openmw/mwrender/map.cpp index 0248d489895..4025509add4 100644 --- a/apps/openmw/mwrender/map.cpp +++ b/apps/openmw/mwrender/map.cpp @@ -249,7 +249,7 @@ namespace MWRender auto view = vsgUtil::createSharedView(camera, root); // auto lightGrid = std::make_unique(shaderOptions); // setResolution() - view->mask = ~Mask_Particle; + view->mask = ~(Mask_Particle|Mask_GUI); view->viewDependentState = collectLights; view->bins = { vsg::Bin::create(Bin_DepthSorted, vsg::Bin::DESCENDING), diff --git a/apps/openmw/mwrender/npc.cpp b/apps/openmw/mwrender/npc.cpp index e63414d17df..a10da7587fc 100644 --- a/apps/openmw/mwrender/npc.cpp +++ b/apps/openmw/mwrender/npc.cpp @@ -28,10 +28,10 @@ #include "bodyparts.hpp" #include "env.hpp" +#include "transparency.hpp" namespace MWRender { - Npc::Npc(const MWAnim::Context& mwctx, const MWWorld::Ptr& ptr, ViewMode viewMode) : Wielding(mwctx) , mPtr(ptr) @@ -329,7 +329,8 @@ namespace MWRender auto node = vsgUtil::readNode(model, mPartContext->partBoneOptions[type]); bool mirror = bonename.find("Left") != std::string::npos; auto result = MWAnim::decorate(node, light, mirror); - addEnv(node, glowColor); + if (glowColor) + addEnv(node, glowColor, getTransparency(*this)); if (type == ESM::PRT_Weapon) { mAttachAmmo = result.placeholders.attachAmmo; @@ -338,11 +339,11 @@ namespace MWRender else if (type == ESM::PRT_Shield) addSwitch(node, Wield::CarriedLeft); - auto path = MWAnim::attachBonesAndNode(node, *mTransform, animation->bones, bonename); + auto path = MWAnim::attachBonesAndNode(node, *nodeToAddChildrenTo(), animation->bones, bonename); if (path.empty()) return; - Anim::Context ctx{ path, &animation->bones, &mPartContext->mask }; + Anim::Context ctx{ { transform() }, path, &animation->bones, &mPartContext->mask }; Anim::Update* dst = &mStaticControllers; if (type == ESM::PRT_Head) { @@ -377,7 +378,7 @@ namespace MWRender if (!path.empty()) { mPartContext->compileContext->detach(vsg::ref_ptr{path.back()}); - vsgUtil::prune(path); + vsgUtil::prune(path, nodeToAddChildrenTo()); path.clear(); } } diff --git a/apps/openmw/mwrender/player.cpp b/apps/openmw/mwrender/player.cpp index 45ef733c498..55d17bb1247 100644 --- a/apps/openmw/mwrender/player.cpp +++ b/apps/openmw/mwrender/player.cpp @@ -7,6 +7,8 @@ #include #include +#include "env.hpp" + namespace MWRender { Player::Player( diff --git a/apps/openmw/mwrender/projectiles.cpp b/apps/openmw/mwrender/projectiles.cpp index 33d85d50b9a..ff234386641 100644 --- a/apps/openmw/mwrender/projectiles.cpp +++ b/apps/openmw/mwrender/projectiles.cpp @@ -33,22 +33,23 @@ namespace MWRender std::optional glowColor, const std::string textureOverride, const std::vector additionalModels) { - Projectile p{ .effect = { .node = mContext.readNode(model), .mwctx=mContext } }; + std::vector> replaceDummyNodes; + for (auto& m : additionalModels) + replaceDummyNodes.push_back(mContext.readNode(m)); + + auto [anim, node] = MWAnim::Effect::load(mContext, mContext.readNode(model), textureOverride, true, replaceDummyNodes); + + Projectile p; + p.effect.mwctx = mContext; p.handle = std::make_shared(ProjectileHandle{ pos, orient }); p.transform = Anim::Transform::create(); p.transform->translation = pos; p.transform->setAttitude(orient); - auto meta = Anim::Meta::get(*p.effect.node); - addEnv(p.effect.node, glowColor); - if (meta) - meta->attachTo(*p.effect.node); - - p.effect.overrideTexture = textureOverride; - for (auto& m : additionalModels) - p.effect.replaceDummyNodes.push_back(mContext.readNode(m)); - p.effect.compile(); + addEnv(node, glowColor); + std::vector worldAttachmentPath = { p.transform.get() }; + std::vector localAttachmentPath = { node.get() }; if (autoRotate) { auto rotateNode = Anim::Transform::create(); @@ -58,10 +59,11 @@ namespace MWRender ctrl->attachTo(*rotateNode); p.effect.update.add(ctrl, rotateNode); p.transform->children = { rotateNode }; - rotateNode->children = { p.effect.node }; + rotateNode->children = { node }; + worldAttachmentPath.push_back(rotateNode.get()); } else - p.transform->children = { p.effect.node }; + p.transform->children = { node }; if (lightColor) { @@ -72,7 +74,8 @@ namespace MWRender p.transform->addChild(light); } - p.effect.node = p.transform; + p.effect.compile(anim, p.transform, worldAttachmentPath, localAttachmentPath); + p.effect.attachTo(mNode); mProjectiles.push_back(p); return p.handle; diff --git a/apps/openmw/mwrender/rendermanager.cpp b/apps/openmw/mwrender/rendermanager.cpp index 86f0834ab86..94b20421b30 100644 --- a/apps/openmw/mwrender/rendermanager.cpp +++ b/apps/openmw/mwrender/rendermanager.cpp @@ -361,7 +361,7 @@ namespace MWRender void RenderManager::setViewMode(ViewMode mode, bool enable) { - vsg::Mask mask; + vsg::Mask mask = vsg::MASK_OFF; if (mode == ViewMode::Scene) mask = ~Mask_GUI; diff --git a/apps/openmw/mwrender/scene.cpp b/apps/openmw/mwrender/scene.cpp index 7142729eb44..347cad89eb3 100644 --- a/apps/openmw/mwrender/scene.cpp +++ b/apps/openmw/mwrender/scene.cpp @@ -107,16 +107,19 @@ namespace MWRender const ESM::Light* light{}; if (allowLight && ptr.getType() == ESM::Light::sRecordId) light = ptr.get()->mBase; - vsg::ref_ptr decoration; - if (auto color = getGlowColor(ptr)) - decoration = createEnv(*color); MWAnim::Context& ctx = allowLight ? context : noParticlesContext; - auto anim = MWAnim::createObject(mesh, ptr.getClass().useAnim(), light, decoration, ctx); + auto anim = MWAnim::createObject(mesh, ptr.getClass().useAnim(), light, ctx); loadTransform(ptr, *anim->transform()); + if (auto color = getGlowColor(ptr)) + { + auto sg = createEnv(*color); + anim->getOrCreateStateGroup()->stateCommands = sg->stateCommands; + } + if (!context.compileContext->compile(anim->node())) return; diff --git a/apps/openmw/mwrender/spellcastglow.cpp b/apps/openmw/mwrender/spellcastglow.cpp index e7e04cb0533..5dd80204583 100644 --- a/apps/openmw/mwrender/spellcastglow.cpp +++ b/apps/openmw/mwrender/spellcastglow.cpp @@ -53,9 +53,7 @@ namespace MWRender if (!stateSwitch) { stateSwitch = vsg::StateSwitch::create(); - sg->stateCommands = { stateSwitch }; - sg->children = object->transform()->children; - object->transform()->children = { sg }; + object->getOrCreateStateGroup()->stateCommands.push_back(stateSwitch); } else { @@ -64,7 +62,7 @@ namespace MWRender stateSwitch->children.clear(); } - auto timeout = vsg::ref_ptr{ new Timeout(group.timer + duration) }; + auto timeout = Timeout::create(group.timer + duration); for (auto& sc : envCommands) { stateSwitch->add(vsg::MASK_ALL, sc); diff --git a/apps/openmw/mwrender/terrainstorage.cpp b/apps/openmw/mwrender/terrainstorage.cpp index 10981f72625..5c9da1fda2b 100644 --- a/apps/openmw/mwrender/terrainstorage.cpp +++ b/apps/openmw/mwrender/terrainstorage.cpp @@ -14,12 +14,10 @@ namespace MWRender ESM::Land::DATA_VCLR | ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | ESM::Land::DATA_VTEX)) , mResourceSystem(resourceSystem) { - mResourceSystem->addResourceManager(mLandManager.get()); } TerrainStorage::~TerrainStorage() { - mResourceSystem->removeResourceManager(mLandManager.get()); } LandManager* TerrainStorage::getLandManager() const diff --git a/apps/openmw/mwrender/transparency.hpp b/apps/openmw/mwrender/transparency.hpp new file mode 100644 index 00000000000..a4c3b05abf9 --- /dev/null +++ b/apps/openmw/mwrender/transparency.hpp @@ -0,0 +1,124 @@ +#ifndef VSGOPENMW_MWRENDER_TRANSPARENCY_H +#define VSGOPENMW_MWRENDER_TRANSPARENCY_H + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace MWRender +{ + class UpdateObjectData : public vsgUtil::TraverseState + { + public: + float alpha = 1; + UpdateObjectData() + { + // Some equipment pieces could be switched off temporarily, but we still want to update them. + overrideMask = vsg::MASK_ALL; + } + using vsg::Visitor::apply; + void apply(vsg::BindDescriptorSets& bds) + { + } + void apply(vsg::BindDescriptorSet& bds) + { + if (bds.firstSet != Pipeline::OBJECT_SET || bds.pipelineBindPoint != VK_PIPELINE_BIND_POINT_GRAPHICS) + return; + for (auto& descriptor : bds.descriptorSet->descriptors) + descriptor->accept(*this); + } + void apply(vsg::DescriptorBuffer& db) + { + if (db.dstBinding != 0) + return; + if (db.bufferInfoList.empty() || !db.bufferInfoList[0]->data) + return; + auto& value = static_cast&>(*db.bufferInfoList[0]->data); + value.value().alpha = alpha; + value.dirty(); + } + }; + + class GetObjectData : public vsgUtil::TraverseState + { + public: + vsg::ref_ptr> value; + using vsg::Visitor::apply; + void apply(vsg::BindDescriptorSets& bds) + { + } + void apply(vsg::BindDescriptorSet& bds) + { + if (bds.firstSet != Pipeline::OBJECT_SET || bds.pipelineBindPoint != VK_PIPELINE_BIND_POINT_GRAPHICS) + return; + for (auto& descriptor : bds.descriptorSet->descriptors) + descriptor->accept(*this); + } + void apply(vsg::DescriptorBuffer& db) + { + if (db.dstBinding != 0) + return; + if (db.bufferInfoList.empty() || !db.bufferInfoList[0]->data) + return; + value = db.bufferInfoList[0]->data->cast>(); + } + }; + + inline float getTransparency(MWAnim::Object& obj) + { + if (auto sg = obj.getStateGroup()) + { + GetObjectData visitor; + for (auto& sc : sg->stateCommands) + sc->accept(visitor); + if (visitor.value) + return visitor.value->value().alpha; + } + return 1.f; + } + + inline void setTransparency(MWAnim::Object& obj, float alpha) + { + if (alpha == 1 && !obj.getStateGroup()) + return; + auto sg = obj.getOrCreateStateGroup(); + + UpdateObjectData visitor; + visitor.alpha = alpha; + + if (alpha == 1) + { + for (auto& sc : sg->stateCommands) + obj.context().compileContext->detach(sc); + sg->stateCommands.clear(); + sg->traverse(visitor); + } + else + { + auto current = getTransparency(obj); + if (current == alpha) + return; + + if (sg->stateCommands.empty()) + { + Pipeline::Object objectData; + objectData.value().alpha = alpha; + auto layout = Pipeline::getCompatiblePipelineLayout(); + sg->stateCommands = { vsg::BindDescriptorSet::create( + VK_PIPELINE_BIND_POINT_GRAPHICS, layout, Pipeline::OBJECT_SET, vsg::Descriptors{ objectData.descriptor() }) }; + obj.context().compileContext->compile(vsg::ref_ptr{sg}); + sg->traverse(visitor); + } + else + sg->accept(visitor); + } + } +} + +#endif diff --git a/apps/openmw/mwrender/weather.hpp b/apps/openmw/mwrender/weather.hpp index 76255b7d4f9..1bea250d520 100644 --- a/apps/openmw/mwrender/weather.hpp +++ b/apps/openmw/mwrender/weather.hpp @@ -105,6 +105,7 @@ namespace MWRender /* osg::ref_ptr mParticleNode; osg::ref_ptr mParticleEffect; + osg::ref_ptr mRainParticleSystem; osg::ref_ptr mUnderwaterSwitch; */ diff --git a/apps/openmw/mwrender/wieldingcreature.cpp b/apps/openmw/mwrender/wieldingcreature.cpp index b5dfd86a506..1396f32e381 100644 --- a/apps/openmw/mwrender/wieldingcreature.cpp +++ b/apps/openmw/mwrender/wieldingcreature.cpp @@ -14,6 +14,7 @@ #include "../mwworld/inventorystore.hpp" #include "env.hpp" +#include "transparency.hpp" namespace MWRender { @@ -99,7 +100,7 @@ namespace MWRender auto node = mwctx.readNode(model); auto result = MWAnim::decorate(node, {}); - addEnv(node, item); + addEnv(node, item, getTransparency(*this)); if (slot == MWWorld::InventoryStore::Slot_CarriedRight) { mAttachAmmo = result.placeholders.attachAmmo; @@ -115,7 +116,7 @@ namespace MWRender if (mParts[i].empty()) return; - Anim::Context ctx{ mParts[i], &animation->bones, &mwctx.mask }; + Anim::Context ctx{ { transform() }, mParts[i], &animation->bones, &mwctx.mask }; Anim::Update* dst = &mStaticControllers; if (slot == MWWorld::InventoryStore::Slot_CarriedRight) dst = &mWeaponControllers; diff --git a/apps/openmw/mwrender/worldmap.cpp b/apps/openmw/mwrender/worldmap.cpp index 370e807f8cf..3e07360bcab 100644 --- a/apps/openmw/mwrender/worldmap.cpp +++ b/apps/openmw/mwrender/worldmap.cpp @@ -133,7 +133,7 @@ namespace MWRender class WorldMap::UpdateOverlay : public Render::ComputeImage { vsg::ref_ptr mArgs = vsg::vec4Array::create(1); - mutable vsgUtil::DeletionQueue mDeletionQueue = vsgUtil::DeletionQueue(7); //3; + mutable vsgUtil::DeletionQueue mDeletionQueue = vsgUtil::DeletionQueue(4); public: vsg::PipelineLayout* layout{}; diff --git a/apps/openmw/mwstate/newgame.hpp b/apps/openmw/mwstate/newgame.hpp index 3f41e8a961c..92467853975 100644 --- a/apps/openmw/mwstate/newgame.hpp +++ b/apps/openmw/mwstate/newgame.hpp @@ -85,7 +85,6 @@ namespace MWState if (MWBase::Environment::get().getWorld()->findExteriorPosition(startCell, pos)) { world->changeToExteriorCell(pos, true); - world->adjustPosition(world->getPlayerPtr(), false); } else { diff --git a/apps/openmw/vsgengine.cpp b/apps/openmw/vsgengine.cpp index c3e94085d4e..69597b304cb 100644 --- a/apps/openmw/vsgengine.cpp +++ b/apps/openmw/vsgengine.cpp @@ -19,6 +19,7 @@ #include "mwgui/windowmanagerimp.hpp" #include "mwinput/inputmanagerimp.hpp" #include "mwrender/rendermanager.hpp" +#include "mwrender/bin.hpp" #include "mwsound/soundmanagerimp.hpp" #include "mwstate/loadingscreen.hpp" #include "mwstate/statemanagerimp.hpp" @@ -230,6 +231,8 @@ namespace OMW traits->width = width; traits->height = height; traits->debugLayer = getenv("VSGOPENMW_VALIDATION") != 0; + if (traits->debugLayer && vsg::isExtensionSupported(VK_EXT_DEBUG_UTILS_EXTENSION_NAME)) + traits->instanceExtensionNames.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); traits->queueFlags |= VK_QUEUE_COMPUTE_BIT; traits->depthFormat = Render::compatibleDepthFormat; traits->swapchainPreferences.presentMode = vsync ? VK_PRESENT_MODE_FIFO_KHR : VK_PRESENT_MODE_IMMEDIATE_KHR; @@ -302,7 +305,7 @@ namespace OMW if (auto overrideString = getenv("VSGOPENMW_DECOMPRESS_BC")) useTextureCompression = overrideString == std::string("0"); mResourceSystem = std::make_unique(mVFS.get(), args.resourceDir / "shaders", - createDefaultSampler(physicalDevice, mRenderEngine->getEnabledFeatures().samplerAnisotropy), useTextureCompression); + createDefaultSampler(physicalDevice, mRenderEngine->getEnabledFeatures().samplerAnisotropy), useTextureCompression, MWRender::Bin_Compute, MWRender::Bin_DepthSorted); setWindowIcon(args.resourceDir / "openmw.png"); diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index e83f0f0da68..a45d27979d9 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -112,13 +112,13 @@ add_component_dir (mwanimation #add_base_dir(vsgutil add_component_dir (vsgutil # Object extensions. - attachable name id operation image + attachable name id operation image debugmessenger # Nodes. setviewportstate projection viewrelative computebin nullbin suspendrenderpass # Algorithms. - bounds transform computetransform intersectnormal toggle group compilecontext compileop updatethreads arraytexture + convert bounds transform computetransform intersectnormal toggle group compilecontext compileop updatethreads arraytexture # Visitors. - traverse traversestate nodepath sharebuffer cullnode searchbyname searchbytype cameradistanceintersector arraystate + traverse traversestate nodepath cullnode searchbyname searchbytype cameradistanceintersector arraystate # I/O. cache virtualfile imageio readimage decompress readnode readshader share shareimage deletionqueue # Temporary workarounds. diff --git a/components/animation/animation.hpp b/components/animation/animation.hpp index 6eaa836b50b..72323cb0f3f 100644 --- a/components/animation/animation.hpp +++ b/components/animation/animation.hpp @@ -10,13 +10,15 @@ namespace Anim class Context; /* - * Links controllers. + * Animation is a container class for a list of controllers and the objects they target. + * Stateful controllers are kept in a separate container that is not const so they can be set up before use. */ struct Animation { Controllers controllers; std::vector> clonedControllers; //{.updateOrder=controllers.updateOrder+1} + // Run the Controller::link(..) procedure for all controller/object pairs and pass them to the specified callback. void link(Context& ctx, std::function onLinked); }; } diff --git a/components/animation/autoplay.hpp b/components/animation/autoplay.hpp index 3576731db95..5bc5c414952 100644 --- a/components/animation/autoplay.hpp +++ b/components/animation/autoplay.hpp @@ -6,7 +6,8 @@ namespace Anim { /* - * Supports random phase. + * AutoPlay is a composite class that manages distinct groups of controller/object pairs where each group plays with a different time offset (phase). + * A phase group of 0 corresponds to no time offset, for all other groups a random offset is chosen when the group is created. */ struct AutoPlay { diff --git a/components/animation/billboard.hpp b/components/animation/billboard.hpp index 35aa1907594..1af39d8b25b 100644 --- a/components/animation/billboard.hpp +++ b/components/animation/billboard.hpp @@ -8,7 +8,8 @@ namespace Anim { /* - * Orients towards camera more flexibly than shader billboards. + * Billboard node is used for billboarding effects on the CPU. + * Typically used when the children of the billboard node include additional transformations, so they can't use the more efficient billboarding shader on the GPU. */ class Billboard : public vsg::Inherit { diff --git a/components/animation/bones.hpp b/components/animation/bones.hpp index 2b96dfc7742..ec418565738 100644 --- a/components/animation/bones.hpp +++ b/components/animation/bones.hpp @@ -26,7 +26,7 @@ namespace Anim }; /* - * Maps a transform hierarchy not necessarily attached to the scene graph. + * Bones is a container class providing access to a hierarchy of bones by name. */ class Bones { @@ -37,12 +37,19 @@ namespace Anim public: Bones(); ~Bones(); + /* - * Specifies whether bone nodes are attached to each other in the scene graph. - * @note Attaching only required bones may allow for a smaller scene graph. + * Specifies whether bone nodes are initially attached to each other. If set to false, all bones' children are initially empty and their hierarchy can only be obtained from the BonePath member. + * @note Attaching only required bones as needed can reduce the size of the scene graph that needs to be traversed each frame. */ const bool attached; + + /* + * Optionally supports case-insensitive searches. + */ using NormalizeFunc = std::function; + NormalizeFunc normalizeFunc = [](auto& s) { return s; }; + /* * Creates detached copy of bones from provided graph. */ @@ -52,10 +59,6 @@ namespace Anim * Reads attached bones from provided graph. */ Bones(NormalizeFunc f, vsg::Node& scene); - /* - * Optionally supports case-insensitive searches. - */ - NormalizeFunc normalizeFunc = [](auto& s) { return s; }; Bone* search(const std::string& name); }; diff --git a/components/animation/channel.hpp b/components/animation/channel.hpp index 11a1f48eddd..e5f2160088d 100644 --- a/components/animation/channel.hpp +++ b/components/animation/channel.hpp @@ -6,7 +6,8 @@ namespace Anim { /* - * Computes animation values as a function of time. + * Channel is an abstract base class/interface class for computing template animation values as a function of time, usually implemented with a keyframe track. + * Keeping it abstract allows for other uses, like composite channels that alter the value of an input channel or the time passed into it. */ template class Channel : public vsg::Object @@ -18,6 +19,9 @@ namespace Anim template using channel_ptr = vsg::ref_ptr>; + /* + * Convenience function that mirrors vsg::Object::create(..) functionality for objects not derived from vsg::Inherit. + */ template vsg::ref_ptr make_channel(Args&&... args) { diff --git a/components/animation/clone.hpp b/components/animation/clone.hpp index 42431d3f875..f7597e074c4 100644 --- a/components/animation/clone.hpp +++ b/components/animation/clone.hpp @@ -9,11 +9,16 @@ namespace Anim { /* * Overrides cloning logic by returning non-null object. + * If the cloneCallback returns a valid object then that object is used, otherwise the default cloning behavior is used. */ using CloneCallback = std::function(const vsg::Node&)>; /* - * Recursively clones a cached node as deeply as required for unique animation state. + * Recursively clones input scene graph as deeply as required for unique animation state. + * Any node that has a controller attached to itself or to any of its children is cloned, otherwise shallow copying is used. + * If any nodes have been cloned then the new hierarchy will be written to the &node parameter. + * Cloning ensures that animations can be played on the clone without affecting the original scene graph that was cloned from, and is typically used to avoid loading the same file from disk multiple times when multiple instances of a model are used in the scene. + * The cloneCallback parameter can be used to forbid shallow copies of particular objects, replace unwanted nodes with empty nodes or replace empty placeholder nodes with real nodes. */ Animation cloneIfRequired(vsg::ref_ptr& node, CloneCallback callback = {}); } diff --git a/components/animation/color.hpp b/components/animation/color.hpp index 86cbb4a52e8..c9d6fc468dc 100644 --- a/components/animation/color.hpp +++ b/components/animation/color.hpp @@ -9,7 +9,7 @@ namespace Anim { /* - * Writes color values. + * Writes color values for use with dynamic materials etc. */ class Color : public UpdateData { diff --git a/components/animation/constant.hpp b/components/animation/constant.hpp index 0fa4162d25a..d0bec50cc96 100644 --- a/components/animation/constant.hpp +++ b/components/animation/constant.hpp @@ -6,7 +6,8 @@ namespace Anim { /* - * Shortcuts calculations. + * Constant is a channel class that returns a fixed value and avoids all calculation. + * Typically used to assign default values to unused channels, and to avoid the need for checking the validity of channels in Controller::run(..) implementations. */ template class Constant : public Channel diff --git a/components/animation/contents.hpp b/components/animation/contents.hpp index eef6c5e449e..26037fd2810 100644 --- a/components/animation/contents.hpp +++ b/components/animation/contents.hpp @@ -4,18 +4,27 @@ namespace Anim { /* - * Signifies contained animations. + * Contents is a collection of flags indicating the type of animations contained in a model. + * Typically attached as a meta object to the root of the model, and used to decide what kind of optimizations like cull nodes, instancing etc. are compatible with it. */ struct Contents { enum { + // Controller is the base class for all types of animations (except Placeholders). Controllers = 1, + // If we have any transform controllers then the position/rotation/scales of transforms will be animated and can't be optimized away. TransformControllers = (1 << 1), + // If we have any particle effects, then the vsg::View needs to be set up with a bin for compute shaders and the animation context should specify a particle mask. Particles = (1 << 2), + // If skeletal animation is used, the animation context needs to be set up with a bones object for the bone hierarchy. Skins = (1 << 3), - Placeholders = (1 << 4) - // Switches + // Placeholders are empty dummy nodes to be replaced by the user if required. + // Used to specify attachment points for an optional light source or other effects added in specific conditions. + Placeholders = (1 << 4), + // Indicates that Cull/DepthSorted node bound values will be changed by animations. + DynamicBounds = (1 << 5) + //Switches = }; int contents{}; bool empty() const { return contents == 0; } diff --git a/components/animation/context.hpp b/components/animation/context.hpp index c6fbfe05740..b064124a6b0 100644 --- a/components/animation/context.hpp +++ b/components/animation/context.hpp @@ -9,17 +9,22 @@ namespace Anim { class Bones; class Mask; + class Transform; /* * Optionally provides scene graph context to animation updates. */ struct Context { - // Controls skin reference frame. + // worldAttachmentPath is the path of transforms leading from the root of the view/scene to the animation's reference frame/skeleton root. + // Useful for animation features that need to use a world reference frame, like a trail of particles. + std::vector worldAttachmentPath; + // attachmentPath is the remaining path leading from the skeleton root to the node that is being linked/attached. If there are no bones, the path should contain the node to attach as its only element. + // Note, concatenating the worldAttachmentPath and the attachmentPath gives the complete path from the root of the view/scene to the node that is being linked/attached. std::vector attachmentPath; - // Supports skins. + // Bones object used by the skinning implementation to get the current transformation of bones. Bones* bones{}; - // Toggles nodes. + // Optional mask object for turning off undesired features. const Mask* mask{}; }; } diff --git a/components/animation/controller.hpp b/components/animation/controller.hpp index 54b1ad4ebf6..208438541e2 100644 --- a/components/animation/controller.hpp +++ b/components/animation/controller.hpp @@ -8,7 +8,10 @@ namespace Anim struct Context; /* - * Controls objects as a function of time. + * Controller is an interface base class for animation logic. Implementations animate objects based on an input time value, e.g. applying the value returned by a Channel to some property of a target object. + * Typically the action performed is purely a function of the input time as the Controller::run(..) method is const. + * Controllers that have internal state that affects the outcome of actions should implement the link(..) and cloneIfRequired() methods. + * Relevant subclasses are TController and TMutable that provide type safety for controlling objects of a particular type. */ class Controller : public vsgUtil::Attachable { @@ -18,13 +21,12 @@ namespace Anim virtual void run(vsg::Object& target, float time) const = 0; - // Optionally links the controller to a specific scene graph. Unlinked controllers may be reused across scene - // graphs. + // Optionally links the controller to a specific scene graph. Unlinked controllers may be reused across scene graphs. virtual void link(Context&, vsg::Object&) {} virtual vsg::ref_ptr cloneIfRequired() const { return {}; } - // Optionally describes desired usage. + // Hints can be set as a guide of what time values are appropriate to pass to the run(..) method. struct Hints { float duration = 0.f; diff --git a/components/animation/controllermap.hpp b/components/animation/controllermap.hpp index f34b16e4253..1ed41eb39bf 100644 --- a/components/animation/controllermap.hpp +++ b/components/animation/controllermap.hpp @@ -10,6 +10,7 @@ namespace Anim { /* * Maps detached controllers by target name. + * Typically used when animations and scene graphs are in different files. */ class ControllerMap : public vsg::Object { diff --git a/components/animation/cullparticles.hpp b/components/animation/cullparticles.hpp index 661ee6c3168..c3a1137f9f4 100644 --- a/components/animation/cullparticles.hpp +++ b/components/animation/cullparticles.hpp @@ -44,7 +44,8 @@ namespace Anim void link(Anim::Context& ctx, vsg::Object& o) { mCountdown = 0.f; - mMask = ctx.mask->particle; + if (ctx.mask) + mMask = ctx.mask->particle; setAllChildren(mMask, static_cast(o)); } diff --git a/components/animation/extrapolate.hpp b/components/animation/extrapolate.hpp index dff6da71c71..ebfac7ac937 100644 --- a/components/animation/extrapolate.hpp +++ b/components/animation/extrapolate.hpp @@ -25,7 +25,8 @@ namespace Anim }; /* - * Adapts time. + * Extrapolate is a composite channel class used to adapt the time value that is passed in. + * The extrapolation mode to use is passed as a template parameter to avoid the evaluation of branches at runtime. */ template class Extrapolate : public Channel diff --git a/components/animation/mask.hpp b/components/animation/mask.hpp index 56cfedc71d9..dfee789eaf9 100644 --- a/components/animation/mask.hpp +++ b/components/animation/mask.hpp @@ -5,6 +5,7 @@ namespace Anim { + /// Mask object specifies the default masks to apply to optional nodes, and can be used to turn off rendering of particle effects when not desired. struct Mask { vsg::Mask particle{ vsg::MASK_OFF }; diff --git a/components/animation/meta.hpp b/components/animation/meta.hpp index 526a537f668..5e02bf76a7f 100644 --- a/components/animation/meta.hpp +++ b/components/animation/meta.hpp @@ -8,7 +8,7 @@ namespace Anim { /* - * Describes subgraph. + * Meta is simply a way to attach Content flags describing the subgraph, typically set on the root of an animation model. */ class Meta : public vsgUtil::Attachable { diff --git a/components/animation/morph.hpp b/components/animation/morph.hpp index 4ff78b728cd..509607397ee 100644 --- a/components/animation/morph.hpp +++ b/components/animation/morph.hpp @@ -9,7 +9,7 @@ namespace Anim { /* - * Assigns morph weights. + * Assigns a list of weights to a floatArray, typically used for morph animations. */ class Morph : public UpdateData { diff --git a/components/animation/reset.hpp b/components/animation/reset.hpp index 6b7cdf72dea..dff61899a79 100644 --- a/components/animation/reset.hpp +++ b/components/animation/reset.hpp @@ -8,7 +8,7 @@ namespace Anim { /* - * Resets vector components. + * Resets certain components of input vector channel, typically used to remove root bone movement that is supposed to be applied to the physics engine. */ class Reset : public Anim::Channel { diff --git a/components/animation/roll.hpp b/components/animation/roll.hpp index 54efbfc6cb7..d87198ca89d 100644 --- a/components/animation/roll.hpp +++ b/components/animation/roll.hpp @@ -8,6 +8,9 @@ namespace Anim { + /* + * Rotates around axis based on speed and delta time. + */ class Roll : public TMutable { DeltaTime mDt; diff --git a/components/animation/rotate.hpp b/components/animation/rotate.hpp index 0a1142724bc..33380c9bd23 100644 --- a/components/animation/rotate.hpp +++ b/components/animation/rotate.hpp @@ -10,6 +10,7 @@ namespace Anim { + /// Channel for converting a list of angle/axis rotations to a quaternion value. class Rotate : public Channel { public: diff --git a/components/animation/skin.cpp b/components/animation/skin.cpp index 0f3d52d1548..683049bcf56 100644 --- a/components/animation/skin.cpp +++ b/components/animation/skin.cpp @@ -51,11 +51,10 @@ namespace Anim class GetSkinPath : public Anim::Visitor { vsg::Object* mTarget{}; - vsg::dsphere* mCurrentCullBounds{}; - vsg::dsphere* mCurrentDepthSortedBounds{}; + std::vector mCurrentNodeBounds{}; public: - vsgUtil::SearchPath transformPath; + vsgUtil::SearchPath transformPath; vsgUtil::SearchPath statePath; GetSkinPath(vsg::Object* target) @@ -63,32 +62,34 @@ namespace Anim { overrideMask = vsg::MASK_ALL; } - vsg::dsphere* foundCullBounds{}; - vsg::dsphere* foundDepthSortedBounds{}; + std::vector foundNodeBounds; using Visitor::apply; void apply(vsg::Transform& transform) override { - mCurrentCullBounds = {}; - mCurrentDepthSortedBounds = {}; + if (!mCurrentNodeBounds.empty()) + { + //if (childContainsSkin) + // std::cerr << "!GetSkinPath::apply(vsg::Transform&): transforming bounds is not implemented, ignoring transform node." << std::endl; + } transformPath.accumulateCastAndTraverse(transform, *this); } void apply(vsg::DepthSorted& node) override { - mCurrentDepthSortedBounds = &node.bound; + mCurrentNodeBounds.push_back(&node.bound); transformPath.traverseNode(node, *this); - mCurrentDepthSortedBounds = {}; + mCurrentNodeBounds.pop_back(); } void apply(vsg::CullNode& node) override { - mCurrentCullBounds = &node.bound; + mCurrentNodeBounds.push_back(&node.bound); transformPath.traverseNode(node, *this); - mCurrentCullBounds = {}; + mCurrentNodeBounds.pop_back(); } void apply(vsg::CullGroup& node) override { - mCurrentCullBounds = &node.bound; + mCurrentNodeBounds.push_back(&node.bound); transformPath.traverseNode(node, *this); - mCurrentCullBounds = {}; + mCurrentNodeBounds.pop_back(); } void apply(vsg::StateGroup& sg) override { @@ -101,8 +102,7 @@ namespace Anim { transformPath.foundPath = transformPath.path; statePath.foundPath = statePath.path; - foundCullBounds = mCurrentCullBounds; - foundDepthSortedBounds = mCurrentDepthSortedBounds; + foundNodeBounds = mCurrentNodeBounds; } } }; @@ -132,7 +132,9 @@ namespace Anim mBones[i].matrix = skinMatrix * vsgUtil::computeTransform(mBones[i].path); array.at(i) = mBones[i].matrix * mBones[i].invBindMatrix; } - updateBounds(); + + if (!mDynamicBounds.empty()) + updateBounds(); } void Skin::link(Context& context, vsg::Object& target) @@ -172,8 +174,9 @@ namespace Anim optimizePaths(); - mDynamicBounds = visitor.foundCullBounds; - mDynamicDepthSortedBounds = visitor.foundDepthSortedBounds; + mDynamicBounds = visitor.foundNodeBounds; + if (mDynamicBounds.empty()) + std::cerr << "!GetSkinPath::foundNodeBounds" << std::endl; } void Skin::optimizePaths() @@ -212,9 +215,7 @@ namespace Anim compute.add(bones[i].bounds); } auto sphere = vsgUtil::toSphere(compute.bounds); - if (mDynamicBounds) - mDynamicBounds->set(sphere.center, sphere.radius); - if (mDynamicDepthSortedBounds) - mDynamicDepthSortedBounds->set(sphere.center, sphere.radius); + for (auto& bound : mDynamicBounds) + bound->set(sphere.center, sphere.radius); } } diff --git a/components/animation/skin.hpp b/components/animation/skin.hpp index ade630a4fe6..89d7b6218a3 100644 --- a/components/animation/skin.hpp +++ b/components/animation/skin.hpp @@ -18,27 +18,31 @@ namespace Anim class Transform; /* - * Accumulates bone matrices. + * Controller class for accumulating bone matrices for use by skinning shaders. + * The link(..) method will find the parent Cull/DepthSorted and StateGroup nodes, for updating the bounds automatically, and setting up a SoftwareSkin object for the StateGroup to use for intersections on the CPU. */ class Skin : public MUpdateData { void optimizePaths(); /* - * Automatically updates bounds of untransformed parent cull node. + * Automatically updates bounds of untransformed parent Cull/DepthSorted nodes. */ void updateBounds(); class PrivateBoneData; std::vector mBones; std::vector mReferenceFrame; - vsg::dsphere* mDynamicBounds{}; - vsg::dsphere* mDynamicDepthSortedBounds{}; + std::vector mDynamicBounds; public: Skin(); ~Skin(); + /* + * Bone information set by users. + */ std::vector bones; vsg::ref_ptr boneIndices; vsg::ref_ptr boneWeights; std::optional transform; + void apply(vsg::mat4Array& array, float); /* * Links to parent cull node and StateGroup. diff --git a/components/mwanimation/create.hpp b/components/mwanimation/create.hpp index 0625f9a2dc5..19f15db690b 100644 --- a/components/mwanimation/create.hpp +++ b/components/mwanimation/create.hpp @@ -20,20 +20,18 @@ namespace MWAnim template void addNode(ObjectType& obj, Anim::Animation& controllers, vsg::ref_ptr node) { - auto ctx = Anim::Context{ {node.get()}, {}, &obj.context().mask }; - if (obj.animation) - ctx.bones = &obj.animation->bones; + Anim::Bones* bones = obj.animation ? &obj.animation->bones : nullptr; + Anim::Context ctx{ { obj.transform() }, { node.get() }, bones, &obj.context().mask }; controllers.link(ctx, [&obj](const Anim::Controller* ctrl, vsg::Object* target) { obj.addController(ctrl, target); }); - // mTransform = findNonControlledTransform(node) - vsgUtil::addChildren(*obj.transform(), *node); + vsgUtil::addChildren(*obj.nodeToAddChildrenTo(), *node); } template - std::unique_ptr create(const Context& ctx, vsg::ref_ptr& node, const ESM::Light* light, vsg::ref_ptr optional_decoration, bool bones) + std::unique_ptr create(const Context& ctx, vsg::ref_ptr& node, const ESM::Light* light, bool bones) { auto obj = std::make_unique(ctx); CloneResult result; @@ -45,11 +43,6 @@ namespace MWAnim } else result = cloneIfRequired(node); - if (optional_decoration) - { - optional_decoration->children = { node }; - node = optional_decoration; - } if (bones) { @@ -61,7 +54,7 @@ namespace MWAnim return obj; } - std::unique_ptr createObject(const std::string& mesh, bool useAnim, const ESM::Light* light, vsg::ref_ptr optional_decoration, const Context& ctx) + std::unique_ptr createObject(const std::string& mesh, bool useAnim, const ESM::Light* light, const Context& ctx) { std::unique_ptr obj; if (mesh.empty()) @@ -78,14 +71,14 @@ namespace MWAnim if (useAnim) { node = ctx.readActor(mesh); - obj = create(ctx, node, light, optional_decoration, true); + obj = create(ctx, node, light, true); if (auto anim = ctx.readAnimation(mesh)) obj->animation->addSingleAnimSource(anim, mesh); } else { node = ctx.readNode(mesh); - obj = create(ctx, node, light, optional_decoration, false); + obj = create(ctx, node, light, false); } return obj; } diff --git a/components/mwanimation/effect.cpp b/components/mwanimation/effect.cpp index d2e3b49b15c..72ddcb63d5b 100644 --- a/components/mwanimation/effect.cpp +++ b/components/mwanimation/effect.cpp @@ -12,10 +12,11 @@ #include "clone.hpp" #include "context.hpp" +#include "object.hpp" namespace MWAnim { - void Effect::compile() + std::pair> Effect::load(const Context& in_mwctx, vsg::ref_ptr in_node, const std::string& overrideTexture, bool overrideAllTextures, const std::vector>& replaceDummyNodes) { MWAnim::CloneResult result; if (!overrideTexture.empty() || !replaceDummyNodes.empty()) @@ -24,18 +25,24 @@ namespace MWAnim if (!overrideTexture.empty()) { auto sampler = vsg::Sampler::create(); - vsgUtil::share_if(mwctx.textureOptions->sharedObjects, sampler); - auto image = vsgUtil::readImage(overrideTexture, mwctx.textureOptions); + vsgUtil::share_if(in_mwctx.textureOptions->sharedObjects, sampler); + auto image = vsgUtil::readImage(overrideTexture, in_mwctx.textureOptions); texture = vsg::DescriptorImage::create(sampler, image); - vsgUtil::share_if(mwctx.textureOptions->sharedObjects, texture); + vsgUtil::share_if(in_mwctx.textureOptions->sharedObjects, texture); } - result = cloneAndReplace(node, texture, overrideAllTextures, replaceDummyNodes); + result = cloneAndReplace(in_node, texture, overrideAllTextures, replaceDummyNodes); } else - result = cloneIfRequired(node); + result = cloneIfRequired(in_node); + return std::make_pair(result, in_node); + } + + void Effect::compile(Anim::Animation& animation, vsg::ref_ptr in_node, const std::vector& worldAttachmentPath, const std::vector& localAttachmentPath) + { + node = in_node; - auto ctx = Anim::Context{ {node.get()}, {}, &mwctx.mask }; - result.link(ctx, [this](const Anim::Controller* ctrl, vsg::Object* o) { + auto ctx = Anim::Context{ worldAttachmentPath, localAttachmentPath, {}, &mwctx.mask }; + animation.link(ctx, [this](const Anim::Controller* ctrl, vsg::Object* o) { update.add(ctrl, o); duration = std::max(ctrl->hints.duration, duration); }); @@ -49,8 +56,15 @@ namespace MWAnim void Effect::attachTo(vsg::Group* p) { // assert(compiled); - parent = p; - parent->addChild(node); + parentBone = p; + parentBone->addChild(node); + } + + void Effect::attachTo(MWAnim::Object* obj) + { + // assert(compiled); + parentObject = obj; + obj->nodeToAddChildrenTo()->addChild(node); } bool Effect::run(float dt) @@ -73,10 +87,17 @@ namespace MWAnim void Effect::detach() { - if (parent) + if (parentBone) { - vsgUtil::removeChild(parent, node); - mwctx.compileContext->detach(node); + vsgUtil::removeChild(parentBone, node); + parentBone = {}; } + else if (parentObject) + { + vsgUtil::removeChild(parentObject->nodeToAddChildrenTo(), node); + parentObject = {}; + } + + mwctx.compileContext->detach(node); } } diff --git a/components/mwanimation/effect.hpp b/components/mwanimation/effect.hpp index 87e620c006c..5e5a8a804ce 100644 --- a/components/mwanimation/effect.hpp +++ b/components/mwanimation/effect.hpp @@ -4,12 +4,19 @@ #include #include +#include #include #include "context.hpp" +namespace Anim +{ + class Transform; +} namespace MWAnim { + class Object; + struct Effect { vsg::ref_ptr node; @@ -17,17 +24,17 @@ namespace MWAnim int effectId = -1; bool loop = false; - std::string overrideTexture; - bool overrideAllTextures; - std::vector> replaceDummyNodes; - Anim::AutoPlay update; float duration = 0; - vsg::Group* parent{}; + vsg::Group* parentBone{}; + MWAnim::Object* parentObject{}; + + static std::pair> load(const Context& mwctx, vsg::ref_ptr prototype, const std::string& overrideTexture, bool overrideAllTextures, const std::vector>& replaceDummyNodes = {}); - void compile(); - void attachTo(vsg::Group* p); + void compile(Anim::Animation& animation, vsg::ref_ptr node, const std::vector& worldAttachmentPath, const std::vector& localAttachmentPath); + void attachTo(vsg::Group* bone); + void attachTo(MWAnim::Object* obj); bool run(float dt); void detach(); }; diff --git a/components/mwanimation/effects.cpp b/components/mwanimation/effects.cpp index 1379a2ce7d0..bbac6f54a59 100644 --- a/components/mwanimation/effects.cpp +++ b/components/mwanimation/effects.cpp @@ -1,6 +1,7 @@ #include "effects.hpp" #include "effect.hpp" +#include "object.hpp" namespace MWAnim { @@ -36,26 +37,30 @@ namespace MWAnim effects->update(dt); } - void addEffect(const Context& mwctx, vsg::Node& root, vsg::Group& attachTo, vsg::ref_ptr node, + void addEffect(MWAnim::Object& obj, vsg::Group* attachBone, vsg::ref_ptr prototype, const std::vector& worldAttachmentPath, int effectId, bool loop, const std::string& overrideTexture, bool overrideAllTextures) { - auto effects = Effects::getOrCreate(root); + auto effects = Effects::getOrCreate(*obj.node()); for (auto& e : effects->effects) { - if (loop && e.loop && e.parent == &attachTo) + if (loop && e.loop && e.parentBone == attachBone) return; } // node->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); - Effect effect{ .node = node, - .mwctx = mwctx, - .effectId = effectId, - .loop = loop, - .overrideTexture = overrideTexture, - .overrideAllTextures = overrideAllTextures }; - effect.compile(); - effect.attachTo(&attachTo); + Effect effect; + effect.mwctx = obj.context(); + effect.effectId = effectId; + effect.loop = loop; + + auto [anim, node] = Effect::load(obj.context(), prototype, overrideTexture, overrideAllTextures); + effect.compile(anim, node, worldAttachmentPath, { node }); + + if (attachBone) + effect.attachTo(attachBone); + else + effect.attachTo(&obj); effects->effects.push_back(effect); } diff --git a/components/mwanimation/effects.hpp b/components/mwanimation/effects.hpp index c6270ab6857..fe803566fb3 100644 --- a/components/mwanimation/effects.hpp +++ b/components/mwanimation/effects.hpp @@ -7,11 +7,19 @@ #include +namespace Anim +{ + class Transform; +} namespace MWAnim { - class Context; + class Object; class Effect; + /* + * Meta object that can be added to a node/scene graph's auxiliary container for keeping track of active effects attached to that scene graph. + * During update(dt), any expired effects will be removed automatically. + */ class Effects : public vsgUtil::Attachable { public: @@ -30,7 +38,7 @@ namespace MWAnim * you need to remove it manually using removeEffect when the effect should end. * @param texture override the texture specified in the model's materials - if empty, do not override */ - void addEffect(const Context& ctx, vsg::Node& root, vsg::Group& attachTo, vsg::ref_ptr node, + void addEffect(MWAnim::Object& obj, vsg::Group* attachBone, vsg::ref_ptr node, const std::vector& worldAttachmentPath, int effectId, bool loop = false, const std::string& overrideTexture = {}, bool overrideAllTextures = false); void updateEffects(vsg::Node& root, float dt); void removeEffect(vsg::Node& root, std::optional effectId); diff --git a/components/mwanimation/light.cpp b/components/mwanimation/light.cpp index cda33b87400..fb54789468c 100644 --- a/components/mwanimation/light.cpp +++ b/components/mwanimation/light.cpp @@ -68,7 +68,7 @@ namespace if (!(light.mData.mFlags & (ESM::Light::Flicker | ESM::Light::FlickerSlow | ESM::Light::Pulse | ESM::Light::PulseSlow))) return {}; - return vsg::ref_ptr{ new LightController(light.mData.mFlags, light.mData.mRadius) }; + return LightController::create(light.mData.mFlags, light.mData.mRadius); } } diff --git a/components/mwanimation/object.cpp b/components/mwanimation/object.cpp index 900966fe241..18c180535e5 100644 --- a/components/mwanimation/object.cpp +++ b/components/mwanimation/object.cpp @@ -1,5 +1,7 @@ #include "object.hpp" +#include + #include #include #include @@ -56,4 +58,27 @@ namespace MWAnim std::copy(bone.path.begin(), bone.path.end(), std::back_inserter(path)); return path; } + + vsg::StateGroup* Object::getOrCreateStateGroup() + { + if (!mStateGroup) + { + mStateGroup = vsg::StateGroup::create(); + mStateGroup->children = mTransform->children; + mTransform->children = { mStateGroup }; + } + return mStateGroup.get(); + } + + vsg::StateGroup* Object::getStateGroup() + { + return mStateGroup; + } + + vsg::Group* Object::nodeToAddChildrenTo() + { + if (mStateGroup) + return mStateGroup.get(); + return mTransform.get(); + } } diff --git a/components/mwanimation/object.hpp b/components/mwanimation/object.hpp index d52cf0196e3..027aea4b594 100644 --- a/components/mwanimation/object.hpp +++ b/components/mwanimation/object.hpp @@ -12,6 +12,8 @@ namespace vsg { class Node; + class Group; + class StateGroup; } namespace Anim { @@ -24,11 +26,13 @@ namespace MWAnim class Context; /* - * Contains animation scene graph. + * Object is a container class for an animation scene graph. + * Several important members are public, this is a conscious choice to allow the scene graph to be extended through composition, and to avoid the Object class hierarchy from being cluttered with rarely used features like extra lights, effects, decorations and transparency etc. */ class Object { protected: + vsg::ref_ptr mStateGroup; vsg::ref_ptr mTransform; const Context& mContext; @@ -63,9 +67,27 @@ namespace MWAnim Anim::Bone* searchBone(const std::string& name); + /* + * The transform node is used to attach and place the object within the scene, and is a member of the Object class for convenience. + */ Anim::Transform* transform() { return mTransform.get(); } const Anim::Transform* transform() const { return mTransform.get(); } + + /* + * node() returns the topmost node that should be used to attach the Object to the scene graph, currently the transform() node. + * It is expected to remain for the lifetime of the Object and can also be used to attach metadata to its auxiliary container. + */ vsg::ref_ptr node(); + + /* + * An optional StateGroup is provided for extensions. When a StateGroup is created, existing children are re-parented to it. + */ + vsg::StateGroup* getOrCreateStateGroup(); + vsg::StateGroup* getStateGroup(); + /* + * Returns the node that children should be added to and removed from. This node will change if a StateGroup is created. + */ + vsg::Group* nodeToAddChildrenTo(); }; } diff --git a/components/pipeline/object.hpp b/components/pipeline/object.hpp index 8441bfac8a1..0d9f580c16b 100644 --- a/components/pipeline/object.hpp +++ b/components/pipeline/object.hpp @@ -6,7 +6,14 @@ namespace Pipeline { - using Object = DescriptorValue; + struct Object : public DynamicDescriptorValue + { + Object() + : DynamicDescriptorValue(0) + { + value().alpha = 1; + } + }; } #endif diff --git a/components/pipeline/particle.cpp b/components/pipeline/particle.cpp index 0180250694c..2059a66f00b 100644 --- a/components/pipeline/particle.cpp +++ b/components/pipeline/particle.cpp @@ -42,7 +42,8 @@ namespace Pipeline { 1, vsg::Value::create(modes & ParticleMode_Color) }, { 2, vsg::Value::create(modes & ParticleMode_GravityPlane) }, { 3, vsg::Value::create(modes & ParticleMode_GravityPoint) }, - { 4, vsg::Value::create(modes & ParticleMode_CollidePlane) } + { 4, vsg::Value::create(modes & ParticleMode_CollidePlane) }, + { 5, vsg::Value::create(modes & ParticleMode_WorldSpace) } }; return vsg::BindComputePipeline::create(vsg::ComputePipeline::create(mLayout, shaderStage)); } diff --git a/components/pipeline/particle.hpp b/components/pipeline/particle.hpp index 5cd3f82bd04..22d102b2f34 100644 --- a/components/pipeline/particle.hpp +++ b/components/pipeline/particle.hpp @@ -15,6 +15,7 @@ namespace Pipeline ParticleMode_GravityPoint = 1<<3, ParticleMode_CollidePlane = 1<<4, ParticleMode_CollideSphere = 1<<5, + ParticleMode_WorldSpace = 1<<6, }; using ParticleModeFlags = uint32_t; diff --git a/components/render/engine.cpp b/components/render/engine.cpp index 2e8e8d17bcf..5a07bde73e3 100644 --- a/components/render/engine.cpp +++ b/components/render/engine.cpp @@ -12,6 +12,7 @@ #include #include +#include #include "headless.hpp" #include "limitframerate.hpp" @@ -25,8 +26,8 @@ namespace Render mViewer = vsg::Viewer::create(); mViewer->addWindow(mWindow); mTraits = mWindow->traits(); - //mDeletionQueue = std::make_unique(mTraits->swapchainPreferences.imageCount); //mWindow->numFrames() - mDeletionQueue = std::make_unique(mTraits->swapchainPreferences.imageCount*2+1); //mWindow->numFrames() + mDeletionQueue = std::make_unique(mTraits->swapchainPreferences.imageCount+1); + if (mTraits->debugLayer) mDebugMessenger = std::make_unique(mWindow->getOrCreateInstance()); } Engine::Engine(Callback headlessCallback, vsg::ref_ptr traits) @@ -36,6 +37,7 @@ namespace Render mTraits = traits; //mDeletionQueue = {}; mDeletionQueue = std::make_unique(3); + if (mTraits->debugLayer) mDebugMessenger = std::make_unique(mHeadless->getOrCreateInstance()); } Engine::~Engine() {} diff --git a/components/render/engine.hpp b/components/render/engine.hpp index 2ba812fdb75..1707e8d8342 100644 --- a/components/render/engine.hpp +++ b/components/render/engine.hpp @@ -27,6 +27,7 @@ namespace vsgUtil { class DeletionQueue; class CompileContext; + class DebugMessenger; } namespace Render { @@ -79,6 +80,7 @@ namespace Render vsg::ref_ptr mWindow; std::unique_ptr mHeadless; std::unique_ptr mDeletionQueue; + std::unique_ptr mDebugMessenger; }; } diff --git a/components/render/headless.hpp b/components/render/headless.hpp index 2a21b547a84..879789962cd 100644 --- a/components/render/headless.hpp +++ b/components/render/headless.hpp @@ -74,12 +74,17 @@ namespace Render // namespace vsgExamples resizeRenderGraph(); return mRenderGraph; } + vsg::ref_ptr getOrCreateInstance() + { + if (!mInstance) + mInstance = vsg::Instance::create(mTraits->instanceExtensionNames, mTraits->requestedLayers); + return mInstance; + } vsg::ref_ptr getOrCreatePhysicalDevice() { if (mPhysicalDevice) return mPhysicalDevice; - mInstance = vsg::Instance::create(mTraits->instanceExtensionNames, mTraits->requestedLayers); - std::tie(mPhysicalDevice, mQueueFamily) = mInstance->getPhysicalDeviceAndQueueFamily(mTraits->queueFlags); + std::tie(mPhysicalDevice, mQueueFamily) = getOrCreateInstance()->getPhysicalDeviceAndQueueFamily(mTraits->queueFlags); if (!mPhysicalDevice || mQueueFamily < 0) throw std::runtime_error("Could not create PhysicalDevice"); return mPhysicalDevice; diff --git a/components/resource/resourcesystem.cpp b/components/resource/resourcesystem.cpp index 7ac27ad5530..fe590cf8bac 100644 --- a/components/resource/resourcesystem.cpp +++ b/components/resource/resourcesystem.cpp @@ -71,7 +71,7 @@ namespace } vsg::ref_ptr createNodeOptions( - const VFS::Manager* vfs, const Pipeline::Builder& builder, vsg::ref_ptr textureOptions) + const VFS::Manager* vfs, const Pipeline::Builder& builder, vsg::ref_ptr textureOptions, int computeBin, int depthSortedBin) { auto nodeOptions = vsg::Options::create(); nodeOptions->sharedObjects = vsg::SharedObjects::create(); @@ -87,7 +87,7 @@ namespace fallback->fallbackObject = vsg::Node::create(); auto vfsReader = vsg::ref_ptr{ new vsgAdapters::vfs(*vfs) }; - vfsReader->add(vsg::ref_ptr{ new vsgAdapters::nif(builder, textureOptions) }); + vfsReader->add(vsg::ref_ptr{ new vsgAdapters::nif(builder, textureOptions, computeBin, depthSortedBin) }); // vfsReader->add(vsgXchange::models::create()); nodeOptions->readerWriters = { vfsReader, fallback }; @@ -120,16 +120,14 @@ namespace namespace Resource { - class NifFileManager {}; // vsgopenmw-delete-me - ResourceSystem::ResourceSystem(const VFS::Manager* vfs, const std::string& shaderPath, - vsg::ref_ptr defaultSampler, bool supportsCompressedImages) + vsg::ref_ptr defaultSampler, bool supportsCompressedImages, int computeBin, int depthSortedBin) : mVFS(vfs) , shaderOptions(createShaderOptions(shaderPath)) , builder(new Pipeline::Builder(shaderOptions, defaultSampler)) , imageOptions(createImageOptions(vfs, !supportsCompressedImages)) , textureOptions(createTextureOptions(vfs, *imageOptions)) - , nodeOptions(createNodeOptions(vfs, *builder, textureOptions)) + , nodeOptions(createNodeOptions(vfs, *builder, textureOptions, computeBin, depthSortedBin)) , animationOptions(createAnimationOptions(vfs)) { } @@ -138,12 +136,6 @@ namespace Resource { } - // vsgopenmw-delete-me - NifFileManager* ResourceSystem::getNifFileManager() - { - return {}; - } - void ResourceSystem::setExpiryDelay(double expiryDelay) {} void ResourceSystem::updateCache(double referenceTime) @@ -158,14 +150,6 @@ namespace Resource { } - void ResourceSystem::addResourceManager(BaseResourceManager* resourceMgr) - { - } - - void ResourceSystem::removeResourceManager(BaseResourceManager* resourceMgr) - { - } - const VFS::Manager* ResourceSystem::getVFS() const { return mVFS; diff --git a/components/resource/resourcesystem.hpp b/components/resource/resourcesystem.hpp index ccb67587d75..fd7204ab7b4 100644 --- a/components/resource/resourcesystem.hpp +++ b/components/resource/resourcesystem.hpp @@ -28,26 +28,16 @@ namespace Pipeline namespace Resource { - class NifFileManager; - class BaseResourceManager; - /* * Assembles vsg::Options for reading files. */ - ///( - /// @brief Wrapper class that constructs and provides access to the most commonly used resource subsystems. - /// @par Resource subsystems can be used with multiple OpenGL contexts, just like the OSG equivalents, but - /// are built around the use of a single virtual file system. - ///) class ResourceSystem { public: ResourceSystem(const VFS::Manager* vfs, const std::string& shaderPath, - vsg::ref_ptr defaultSampler, bool supportsCompressedImages); + vsg::ref_ptr defaultSampler, bool supportsCompressedImages, int computeBin, int depthSortedBin); ~ResourceSystem(); - NifFileManager* getNifFileManager(); - /// Indicates to each resource manager to clear the cache, i.e. to drop cached objects that are no longer /// referenced. /// @note May be called from any thread if you do not add or remove resource managers at that point. @@ -57,13 +47,6 @@ namespace Resource /// @note May be called from any thread if you do not add or remove resource managers at that point. void clearCache(); - /// Add this ResourceManager to be handled by the ResourceSystem. - /// @note Does not transfer ownership. - void addResourceManager(BaseResourceManager* resourceMgr); - /// @note Do nothing if resourceMgr does not exist. - /// @note Does not delete resourceMgr. - void removeResourceManager(BaseResourceManager* resourceMgr); - /// How long to keep objects in cache after no longer being referenced. void setExpiryDelay(double expiryDelay); @@ -73,12 +56,6 @@ namespace Resource void reportStats(unsigned int frameNumber, osg::Stats* stats) const; private: - std::unique_ptr mNifFileManager; - - // Store the base classes separately to get convenient access to the common interface - // Here users can register their own resourcemanager as well - std::vector mResourceManagers; - const VFS::Manager* mVFS; ResourceSystem(const ResourceSystem&); diff --git a/components/terrain/paging.cpp b/components/terrain/paging.cpp index ee351320c86..0d6bfb3104c 100644 --- a/components/terrain/paging.cpp +++ b/components/terrain/paging.cpp @@ -1,6 +1,7 @@ #include "paging.hpp" #include +#include #include "storage.hpp" #include "lod.hpp" @@ -58,7 +59,7 @@ namespace Terrain view.viewDistance = viewDistance; const double cellWorldSize = mStorage->cellWorldSize; - vsg::dvec2 viewCell = vsg::dvec2(viewPoint.x, viewPoint.y) / cellWorldSize; + vsg::dvec2 viewCell = vsgUtil::toVec2(viewPoint) / cellWorldSize; double cellViewDistance = viewDistance / cellWorldSize; int batchSize = getBatchSize(cellViewDistance); diff --git a/components/view/defaultstate.cpp b/components/view/defaultstate.cpp index c8e8aabca01..745ad5ccc7c 100644 --- a/components/view/defaultstate.cpp +++ b/components/view/defaultstate.cpp @@ -13,7 +13,7 @@ namespace View auto sg = vsg::StateGroup::create(); auto layout = Pipeline::getCompatiblePipelineLayout(); auto objectSet = vsg::DescriptorSet::create( - layout->setLayouts[Pipeline::OBJECT_SET], vsg::Descriptors{ Pipeline::Object(0).descriptor() }); + layout->setLayouts[Pipeline::OBJECT_SET], vsg::Descriptors{ Pipeline::Object().descriptor() }); sg->stateCommands = { vsg::BindDescriptorSet::create( VK_PIPELINE_BIND_POINT_GRAPHICS, layout, Pipeline::VIEW_SET, viewDescriptorSet), vsg::BindDescriptorSet::create(VK_PIPELINE_BIND_POINT_GRAPHICS, layout, Pipeline::OBJECT_SET, objectSet) }; diff --git a/components/vsgadapters/nif/nif.cpp b/components/vsgadapters/nif/nif.cpp index 57537802b1a..ab72591564f 100644 --- a/components/vsgadapters/nif/nif.cpp +++ b/components/vsgadapters/nif/nif.cpp @@ -61,10 +61,6 @@ namespace Pipeline::Descriptors } namespace { - // vsgopenmw-fixme(find-my-place) - const int computeBin = 1; - const int depthSortedBin = 0; - static const vsg::ref_ptr sNullArrayState = vsg::NullArrayState::create(); bool isTypeGeometry(int type) @@ -177,6 +173,8 @@ namespace vsgAdapters const vsg::ref_ptr& mImageOptions; bool mCanOptimize = true; bool mShowMarkers = false; + int mComputeBin; + int mDepthSortedBin; uint32_t mPhaseGroups = 0; std::string mFilename; std::unique_ptr mNifFile; @@ -188,10 +186,12 @@ namespace vsgAdapters public: nifImpl(std::istream& stream, const vsg::Options& options, const vsg::ref_ptr& imageOptions, - bool showMarkers, const Pipeline::Builder& builder) + bool showMarkers, int computeBin, int depthSortedBin, const Pipeline::Builder& builder) : mOptions(options) , mImageOptions(imageOptions) , mShowMarkers(showMarkers) + , mComputeBin(computeBin) + , mDepthSortedBin(depthSortedBin) , mBuilder(builder) { options.getValue("filename", mFilename); @@ -230,7 +230,7 @@ namespace vsgAdapters vsg::ref_ptr materialController; float alphaTestCutoff = 1.f; bool depthSorted = false; - bool autoPlay = false; + uint32_t animFlags = 0; uint32_t phaseGroup = 0; bool filterMatched = false; }; @@ -264,7 +264,7 @@ namespace vsgAdapters vsgUtil::removeGroup(nodes); vsg::ref_ptr ret; - if (!mContents.contains(Anim::Contents::TransformControllers | Anim::Contents::Skins | Anim::Contents::Particles | Anim::Contents::Placeholders)) + if (!mContents.contains(Anim::Contents::TransformControllers | Anim::Contents::DynamicBounds | Anim::Contents::Placeholders)) { ret = vsgUtil::createCullNode(nodes); vsgUtil::addLeafCullNodes(ret, 2); @@ -398,7 +398,7 @@ namespace vsgAdapters auto descriptor = vsg::DescriptorBuffer::create( boneMatrices, Pipeline::Descriptors::BONE_BINDING, 0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER); addModeDescriptor(descriptor, Pipeline::Mode::SKIN); - addAnimContents(Anim::Contents::Skins | Anim::Contents::Controllers); + addAnimContents(Anim::Contents::Skins | Anim::Contents::Controllers | Anim::Contents::DynamicBounds); } vsg::ref_ptr handleGeometry(const Nif::NiGeometry& niGeometry) @@ -489,7 +489,7 @@ namespace vsgAdapters if (nodeOptions.depthSorted) { // Alpha sorting uses the calculated bounding sphere centre. - return vsg::DepthSorted::create(depthSortedBin, vsgUtil::getBounds(sg), sg); + return vsg::DepthSorted::create(mDepthSortedBin, vsgUtil::getBounds(sg), sg); } return sg; } @@ -516,11 +516,7 @@ namespace vsgAdapters particleArray, Pipeline::Descriptors::PARTICLE_BINDING, 0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER); addModeDescriptor(particlesDescriptor, Pipeline::Mode::PARTICLE); - auto ctrl = Emitter::create(partctrl, particles.recIndex); - // ArrayEmitter - vsgUtil::setID(*sw, particles.recIndex); - - auto maxLifetime = partctrl.lifetime + partctrl.lifetimeRandom; + auto maxLifetime = ParticleSystem::calculateMaxLifetime(partctrl); Pipeline::Data::SimulateArgs args{}; args.emit = handleEmitterData(partctrl); vsg::ref_ptr colorCurve; @@ -597,6 +593,14 @@ namespace vsgAdapters descriptors.emplace_back(vsg::DescriptorImage::create(sampler, colorCurve, Pipeline::Descriptors::COLOR_CURVE_BINDING)); } + auto& nodeOptions = getNodeOptions(); + nodeOptions.numUvSets = 1; + nodeOptions.primitiveTopology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + nodeOptions.cullMode = VK_CULL_MODE_NONE; + bool worldSpace = !(nodeOptions.animFlags & Nif::NiNode::ParticleFlag_LocalSpace); + if (worldSpace) + modes |= Pipeline::ParticleMode_WorldSpace; + auto bindComputePipeline = mBuilder.particle->getOrCreate(modes); auto computeBds = vsg::BindDescriptorSet::create(VK_PIPELINE_BIND_POINT_COMPUTE, bindComputePipeline->pipeline->layout, Pipeline::TEXTURE_SET, descriptors); auto computeGroup = vsg::Commands::create(); // vsg::StateGroup::create(); @@ -606,20 +610,21 @@ namespace vsgAdapters createParticleDispatch(maxParticles) }; - auto& nodeOptions = getNodeOptions(); - nodeOptions.numUvSets = 1; - nodeOptions.primitiveTopology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP; - nodeOptions.cullMode = VK_CULL_MODE_NONE; - - auto draw = vsg::Draw::create(maxParticles * 4, 1, 0, 0); + auto draw = vsg::Draw::create(maxParticles * 6, 1, 0, 0); auto drawGroup = createStateGroup(draw); drawGroup->prototypeArrayState = sNullArrayState; - double maxRadius = maxLifetime * (partctrl.velocity + partctrl.velocityRandom) + std::max(partctrl.offsetRandom.x(), std::max(partctrl.offsetRandom.y(), partctrl.offsetRandom.z())) + partctrl.size/2.f; + double maxRadius = ParticleSystem::calculateRadius(partctrl); vsg::dsphere bound(vsg::dvec3(), maxRadius); + // bound.dataVariance = DYNAMIC; + sw->children = { - { vsg::MASK_ALL, vsg::DepthSorted::create(depthSortedBin, bound, drawGroup) }, - { vsg::MASK_ALL, vsg::DepthSorted::create(computeBin, bound, computeGroup) } + { vsg::MASK_ALL, vsg::DepthSorted::create(mDepthSortedBin, bound, drawGroup) }, + { vsg::MASK_ALL, vsg::DepthSorted::create(mComputeBin, bound, computeGroup) } }; + + auto ctrl = ParticleSystem::create(partctrl, particles.recIndex, worldSpace); + setup(partctrl, *ctrl, *frameArgsData); + auto cullCtrl = Anim::CullParticles::create(); cullCtrl->active = ctrl->active; cullCtrl->maxLifetime = maxLifetime; @@ -627,10 +632,7 @@ namespace vsgAdapters cullCtrl->dispatchIndex = 1; setup(partctrl, *cullCtrl, *sw); - addAnimContents(Anim::Contents::Particles); - setup(partctrl, *ctrl, *frameArgsData); - // ParticleFlag_LocalSpace - + addAnimContents(Anim::Contents::Particles | Anim::Contents::DynamicBounds); //return sw; return vsg::CullNode::create(bound, sw); } @@ -994,7 +996,7 @@ namespace vsgAdapters { hints.duration = std::max(hints.duration, nictrl.timeStop); auto& nodeOptions = getNodeOptions(); - hints.autoPlay = nodeOptions.autoPlay; + hints.autoPlay = nodeOptions.animFlags & Nif::NiNode::AnimFlag_AutoPlay; hints.phaseGroup = nodeOptions.phaseGroup; } @@ -1182,13 +1184,21 @@ namespace vsgAdapters return {}; } + + template + vsg::ref_ptr createGroup(const Nif::Node& nifNode) const + { + auto node = T::create(); + vsgUtil::setName(*node, std::string(nifNode.name)); + vsgUtil::setID(*node, nifNode.recIndex); + return node; + } + vsg::ref_ptr handleTransform(const Nif::Node& nifNode) { - auto trans = Anim::Transform::create(); + auto trans = createGroup(nifNode); convertTrafo(*trans, nifNode.trafo); handleTransformControllers(nifNode.controller, *trans); - vsgUtil::setName(*trans, std::string(nifNode.name)); - vsgUtil::setID(*trans, nifNode.recIndex); return trans; } @@ -1197,7 +1207,7 @@ namespace vsgAdapters if (nifNode.recType == Nif::RC_NiBSAnimationNode || nifNode.recType == Nif::RC_NiBSParticleNode) { auto& nodeOptions = getNodeOptions(); - nodeOptions.autoPlay = nifNode.flags & Nif::NiNode::AnimFlag_AutoPlay; + nodeOptions.animFlags = nifNode.flags; nodeOptions.phaseGroup = (nifNode.flags & Nif::NiNode::AnimFlag_NotRandom) ? 0u : ++mPhaseGroups; // openmw-6455-controller-random-phase } } @@ -1234,8 +1244,7 @@ namespace vsgAdapters if (nifNode.useFlags & Nif::Node::Emitter /* || animatedCollision*/) { if (!group) - retNode = group = vsg::Group::create(); - vsgUtil::setID(*group, nifNode.recIndex); + retNode = group = createGroup(nifNode); } if (nifNode.recType == Nif::RC_NiBillboardNode) @@ -1259,9 +1268,8 @@ namespace vsgAdapters if (e->recType == Nif::RC_NiTextKeyExtraData) { if (!group) - group = vsg::Group::create(); + group = createGroup(nifNode); handleTextKeys(static_cast(*e.getPtr()))->attachTo(*group); - ; } else if (e->recType == Nif::RC_NiStringExtraData) { @@ -1350,8 +1358,11 @@ namespace vsgAdapters auto child = handleParticles(static_cast(nifNode)); if (group && child) group->addChild(child); - else + else if (child) + { + vsgUtil::setID(*child, nifNode.recIndex); retNode = child; + } } if (group && !retNode) retNode = group; @@ -1378,7 +1389,7 @@ namespace vsgAdapters return {}; try { - return nifImpl(stream, *options, mImageOptions, showMarkers, mBuilder).load(); + return nifImpl(stream, *options, mImageOptions, showMarkers, mComputeBin, mDepthSortedBin, mBuilder).load(); } catch (std::exception& e) { diff --git a/components/vsgadapters/nif/nif.hpp b/components/vsgadapters/nif/nif.hpp index cd1ded50034..11cf329ce28 100644 --- a/components/vsgadapters/nif/nif.hpp +++ b/components/vsgadapters/nif/nif.hpp @@ -21,9 +21,11 @@ namespace vsgAdapters vsg::ref_ptr mImageOptions; public: - nif(const Pipeline::Builder& builder, vsg::ref_ptr imageOptions) + nif(const Pipeline::Builder& builder, vsg::ref_ptr imageOptions, int computeBin, int depthSortedBin) : mBuilder(builder) , mImageOptions(imageOptions) + , mComputeBin(computeBin) + , mDepthSortedBin(depthSortedBin) { } vsg::ref_ptr read(std::istream&, vsg::ref_ptr = {}) const override; @@ -37,6 +39,9 @@ namespace vsgAdapters // Mask to use for nodes that ignore the crosshair intersection. // This is used for NiCollisionSwitch nodes with NiCollisionSwitch state set to disabled. unsigned int intersectionDisabledMask = ~0; + + const int mComputeBin; + const int mDepthSortedBin; }; } diff --git a/components/vsgadapters/nif/particle.cpp b/components/vsgadapters/nif/particle.cpp index aea8e4bfc8f..cecd61bc29f 100644 --- a/components/vsgadapters/nif/particle.cpp +++ b/components/vsgadapters/nif/particle.cpp @@ -3,6 +3,9 @@ #include #include +#include +#include +#include #include #include @@ -12,6 +15,7 @@ #include #include #include +#include #include "anim.hpp" @@ -70,69 +74,132 @@ namespace vsgAdapters return data; } - class LinkEmitter : public vsg::ConstVisitor + class GetNodeBounds : public vsgUtil::TTraverse { - Emitter& mEmitter; + public: + std::vector nodeBounds; + using vsg::Visitor::apply; + void apply(vsg::DepthSorted& node) override + { + nodeBounds.push_back(&node.bound); + node.traverse(*this); + } + void apply(vsg::CullNode& node) override + { + nodeBounds.push_back(&node.bound); + node.traverse(*this); + } + void apply(vsg::CullGroup& node) override + { + nodeBounds.push_back(&node.bound); + node.traverse(*this); + } + }; + + class LinkParticleSystem : public vsg::Visitor + { + ParticleSystem& mParticleSystem; vsgUtil::AccumulatePath mTransformPath; vsgUtil::AccumulatePath mSwitchPath; - + bool mCollectEmitters = false; public: bool foundEmitterNode = false; bool foundParticleNode = false; - LinkEmitter(vsgAdapters::Emitter& e) - : mEmitter(e) + + LinkParticleSystem(vsgAdapters::ParticleSystem& p) + : mParticleSystem(p) { overrideMask = vsg::MASK_ALL; } - using vsg::ConstVisitor::apply; - void apply(const vsg::Switch& sw) override + using vsg::Visitor::apply; + void apply(vsg::Switch& sw) override { auto ppn = mSwitchPath.pushPop(&sw); - check(sw); - sw.traverse(*this); + checkAndTraverse(sw); } - void apply(const vsg::Transform& t) override + void apply(vsg::Transform& node) override { - auto trans = dynamic_cast(&t); - if (!trans) + if (auto trans = dynamic_cast(&node)) + { + auto ppn = mTransformPath.pushPop(trans); + checkAndTraverse(node); + } + else { - check(t); - t.traverse(*this); - return; + checkAndTraverse(node); } - auto ppn = mTransformPath.pushPop(trans); - check(t); - trans->traverse(*this); } - void apply(const vsg::Node& n) override + void apply(vsg::Node& node) override { - check(n); - n.traverse(*this); + checkAndTraverse(node); } - void check(const vsg::Node& n) + + void checkAndTraverse(vsg::Node& node) { - if (auto id = vsgUtil::ID::get(n)) + if (auto id = vsgUtil::ID::get(node)) { int index = id->id; - if (index == mEmitter.mEmitterNodeIndex) + if (index == mParticleSystem.mEmitterNodeIndex) { foundEmitterNode = true; - mEmitter.mPathToEmitter = mTransformPath.path; - mEmitter.mSwitches = mSwitchPath.path; + mParticleSystem.mSwitches = mSwitchPath.path; + if (mParticleSystem.mArrayEmitter) + { + mCollectEmitters = true; + node.traverse(*this); + mCollectEmitters = false; + } + else + { + mParticleSystem.mPathsToEmitters = { mTransformPath.path }; + } } - else if (index == mEmitter.mParticleNodeIndex) + else if (index == mParticleSystem.mParticleNodeIndex) { foundParticleNode = true; - mEmitter.mPathToParticle = mTransformPath.path; + mParticleSystem.mPathToParticle = mTransformPath.path; + + GetNodeBounds getBounds; + node.accept(getBounds); + + if (getBounds.nodeBounds.empty()) + { + std::cerr << "!LinkParticleSystem::foundNodeBounds" << std::endl; + } + else + { + mParticleSystem.mInitialBound = *getBounds.nodeBounds.front(); + mParticleSystem.mDynamicBounds = getBounds.nodeBounds; + } } + else if (mCollectEmitters) + { + mParticleSystem.mPathsToEmitters.push_back(mTransformPath.path); + node.traverse(*this); + } + else + node.traverse(*this); } + else + node.traverse(*this); } }; - Emitter::Emitter(const Nif::NiParticleSystemController& partctrl, int particleNodeIndex) - : mParticleNodeIndex(particleNodeIndex) + float ParticleSystem::calculateRadius(const Nif::NiParticleSystemController& partctrl) { - mEmitArgs = handleEmitterData(partctrl); + return calculateMaxLifetime(partctrl) * (partctrl.velocity + partctrl.velocityRandom) + std::max(partctrl.offsetRandom.x(), std::max(partctrl.offsetRandom.y(), partctrl.offsetRandom.z())) + partctrl.size/2.f; + } + + float ParticleSystem::calculateMaxLifetime(const Nif::NiParticleSystemController& partctrl) + { + return partctrl.lifetime + partctrl.lifetimeRandom; + } + + ParticleSystem::ParticleSystem(const Nif::NiParticleSystemController& partctrl, int particleNodeIndex, bool worldSpace) + : mWorldSpace(worldSpace) + , mParticleNodeIndex(particleNodeIndex) + { + mMaxLifetime = calculateMaxLifetime(partctrl); mParticlesPerSecond = getParticlesPerSecond(partctrl); if (partctrl.timeStop <= partctrl.stopTime && partctrl.timeStart >= partctrl.startTime) @@ -142,11 +209,14 @@ namespace vsgAdapters active = Anim::make_channel(partctrl.startTime, partctrl.stopTime); addExtrapolatorIfRequired(partctrl, active, partctrl.startTime, partctrl.stopTime); } + + if (partctrl.recType == Nif::RC_NiBSPArrayController) + mArrayEmitter = true; if (!partctrl.emitter.empty()) mEmitterNodeIndex = partctrl.emitter->recIndex; } - void Emitter::apply(vsg::Value& val, float time) + void ParticleSystem::apply(vsg::Value& val, float time) { // float dt = scene.dt; // openmw-7238-particle-system-time @@ -154,6 +224,36 @@ namespace vsgAdapters auto& data = val.value(); data.time.x = dt; data.time.y = time; + + if (mWorldSpace) + { + auto worldMatrix = vsgUtil::computeTransform(mLocalToWorld); + if (mLastWorldMatrix) + { + // calculate the worldOffset matrix, used by compute shaders to transform the previous frame's particle positions/velocities, leaving a trail of particles behind in local space + data.worldOffset = vsg::inverse_4x3(worldMatrix) * (*mLastWorldMatrix); + + // update Cull/DepthSorted bounds to include trailing particles + mWorldBound.timer += dt; + auto movement = data.worldOffset * vsg::vec4(0,0,0,1); + vsg::vec3 movementVec(movement.x, movement.y, movement.z); + float moved = vsg::length(movementVec); + const float epsilon = 0.003f; + if (moved > epsilon) + { + mWorldBound.totalMoved += moved; + mWorldBound.moved.emplace_back(mWorldBound.timer + mMaxLifetime, moved); + } + while (!mWorldBound.moved.empty() && mWorldBound.timer >= mWorldBound.moved.front().first) + { + mWorldBound.totalMoved -= mWorldBound.moved.front().second; + mWorldBound.moved.pop_front(); + } + updateBounds(); + } + mLastWorldMatrix = worldMatrix; + } + if (active->value(time) && emitterVisible()) { data.emitMatrix = calculateEmitMatrix(); @@ -163,19 +263,49 @@ namespace vsgAdapters data.emitCount = {}; } - void Emitter::link(Anim::Context& ctx, vsg::Object&) + void ParticleSystem::link(Anim::Context& ctx, vsg::Object&) { - LinkEmitter visitor(*this); - ctx.attachmentPath.back()->accept(visitor); + LinkParticleSystem visitor(*this); + //ctx.attachmentPath.back()->accept(visitor); + ctx.attachmentPath.front()->accept(visitor); + if (mEmitterNodeIndex != -1 && !visitor.foundEmitterNode) - std::cerr << "!foundEmitterNode(" << mEmitterNodeIndex << ")" << std::endl; - if (mParticleNodeIndex!= -1 && !visitor.foundParticleNode) - std::cerr << "!foundParticleNode(" << mParticleNodeIndex << ")" << std::endl; + std::cerr << "!LinkParticleSystem::foundEmitterNode(" << mEmitterNodeIndex << ")" << std::endl; + if (mParticleNodeIndex != -1 && !visitor.foundParticleNode) + std::cerr << "!LinkParticleSystem::foundParticleNode(" << mParticleNodeIndex << ")" << std::endl; + if (mPathsToEmitters.empty()) + mPathsToEmitters.resize(1); + + mLocalToWorld = vsgUtil::path(ctx.worldAttachmentPath); + vsgUtil::addPath(mLocalToWorld, mPathToParticle); - vsgUtil::trim(mPathToEmitter, mPathToParticle); + if (mPathsToEmitters.size() == 1) + vsgUtil::trim(mPathsToEmitters[0], mPathToParticle); + else + { + size_t trimCount = 0; + for (auto& node : mPathToParticle) + { + auto itr = mPathsToEmitters.begin(); + for (; itr != mPathsToEmitters.end(); ++itr) + { + if (itr->size() <= trimCount || (*itr)[trimCount] != node) + break; + } + if (itr != mPathsToEmitters.end()) + break; + ++trimCount; + } + if (trimCount > 0) + { + vsgUtil::trim(mPathToParticle, trimCount); + for (auto& path : mPathsToEmitters) + vsgUtil::trim(path, trimCount); + } + } } - int Emitter::calculateEmitCount(float dt) + int ParticleSystem::calculateEmitCount(float dt) { auto v = dt * mParticlesPerSecond * 0.01; int i = static_cast(v); @@ -188,14 +318,32 @@ namespace vsgAdapters return i; } - vsg::mat4 Emitter::calculateEmitMatrix() const + vsg::mat4 ParticleSystem::calculateEmitMatrix() { - auto emitterToWorld = vsgUtil::computeTransform(mPathToEmitter); + int emitIndex = std::rand() / static_cast(RAND_MAX) * mPathsToEmitters.size(); + const auto& pathToEmitter = mPathsToEmitters[emitIndex]; + auto emitterToWorld = vsgUtil::computeTransform(pathToEmitter); auto worldToPs = vsg::inverse_4x3(vsgUtil::computeTransform(mPathToParticle)); - return /*orthoNormalize(*/ worldToPs * emitterToWorld; + auto emitMatrix = /*orthoNormalize(*/ worldToPs * emitterToWorld; + auto pos = vsgUtil::translation(emitMatrix); + float length = vsg::length(pos); + if (length > mEmitRadius) + { + mEmitRadius = length; + updateBounds(); + } + return emitMatrix; + } + + void ParticleSystem::updateBounds() + { + auto bound = mInitialBound; + bound.radius += mWorldBound.totalMoved + mEmitRadius; + for (auto& b : mDynamicBounds) + *b = bound; } - bool Emitter::emitterVisible() const + bool ParticleSystem::emitterVisible() const { for (auto sw : mSwitches) for (auto& switchChild : sw->children) diff --git a/components/vsgadapters/nif/particle.hpp b/components/vsgadapters/nif/particle.hpp index 9490612ea3b..c3f65dfc201 100644 --- a/components/vsgadapters/nif/particle.hpp +++ b/components/vsgadapters/nif/particle.hpp @@ -1,6 +1,9 @@ #ifndef VSGOPENMW_VSGADAPTERS_NIF_PARTICLE_H #define VSGOPENMW_VSGADAPTERS_NIF_PARTICLE_H +#include +#include + #include #include #include @@ -26,33 +29,62 @@ namespace Pipeline::Data } namespace vsgAdapters { - class Emitter : public Anim::MUpdateData> + /* + * ParticleSystem is a controller class for updating the parameters used by particle compute shaders. + */ + class ParticleSystem : public Anim::MUpdateData> { Anim::DeltaTime mDt; - friend class LinkEmitter; + friend class LinkParticleSystem; int mCarryOver{}; - std::vector mPathToParticle; - std::vector mPathToEmitter; + using TransformPath = std::vector; + TransformPath mLocalToWorld; + std::optional mLastWorldMatrix; + TransformPath mPathToParticle; + std::vector mPathsToEmitters; std::vector mSwitches; + bool mWorldSpace = false; + + /* + * Links to parent Cull/DepthSorted nodes. + */ + struct WorldBounds + { + std::deque> moved; + float totalMoved = 0; + float timer = 0; + } mWorldBound; + float mMaxLifetime = 0; + float mEmitRadius = 0; + std::vector mDynamicBounds; + vsg::dsphere mInitialBound; + void updateBounds(); + + bool mArrayEmitter = false; int mEmitterNodeIndex = -1; int mParticleNodeIndex = -1; + float mParticlesPerSecond{}; int calculateEmitCount(float dt); - vsg::mat4 calculateEmitMatrix() const; + vsg::mat4 calculateEmitMatrix(); + /* + * Stops emitting particles when the emitter node is switched off. + */ bool emitterVisible() const; - Pipeline::Data::EmitArgs mEmitArgs; public: - Emitter(const Nif::NiParticleSystemController& partctrl, int particleNodeIndex); + ParticleSystem(const Nif::NiParticleSystemController& partctrl, int particleNodeIndex, bool worldSpace); Anim::channel_ptr active; + static float calculateRadius(const Nif::NiParticleSystemController& partctrl); + static float calculateMaxLifetime(const Nif::NiParticleSystemController& partctrl); + void apply(vsg::Value& val, float time); void link(Anim::Context& ctx, vsg::Object&) override; }; Pipeline::Data::EmitArgs handleEmitterData(const Nif::NiParticleSystemController& partctrl); - void handleInitialParticles(vsg::Array& array, const Nif::NiParticlesData& data, - const Nif::NiParticleSystemController& partctrl); + void handleInitialParticles(vsg::Array& array, const Nif::NiParticlesData& data, const Nif::NiParticleSystemController& partctrl); vsg::ref_ptr createColorCurve(const Nif::NiColorData& clrdata, float maxDuration, float timeStep); vsg::ref_ptr createParticleDispatch(uint32_t numParticles); diff --git a/components/vsgutil/attachable.hpp b/components/vsgutil/attachable.hpp index 95992749a28..5113a2620f5 100644 --- a/components/vsgutil/attachable.hpp +++ b/components/vsgutil/attachable.hpp @@ -6,7 +6,8 @@ namespace vsgUtil { /* - * Attaches to other objects. + * Base class for objects to be attached to other objects' vsg::Auxiliary container. + * Derived classes must define the sAttachKey string. */ template class Attachable : public vsg::Object diff --git a/components/vsgutil/computetransform.hpp b/components/vsgutil/computetransform.hpp index 29ee9a87532..c9cdee2afae 100644 --- a/components/vsgutil/computetransform.hpp +++ b/components/vsgutil/computetransform.hpp @@ -3,6 +3,8 @@ #include +#include "convert.hpp" + namespace vsgUtil { /* @@ -17,11 +19,19 @@ namespace vsgUtil return mat; } + /* + * Gets the translation part of a 4x4 matrix. + */ + template + vsg::t_vec3 translation(const vsg::t_mat4 matrix) + { + return vsgUtil::toVec3(matrix[3]); + } + template inline vsg::vec3 computePosition(const Nodes& nodes) { - auto vec = vsgUtil::computeTransform(nodes)[3]; - return { vec.x, vec.y, vec.z }; + return vsgUtil::translation(vsgUtil::computeTransform(nodes)); } } diff --git a/components/vsgutil/convert.hpp b/components/vsgutil/convert.hpp new file mode 100644 index 00000000000..7678ddf9614 --- /dev/null +++ b/components/vsgutil/convert.hpp @@ -0,0 +1,29 @@ +#ifndef VSGOPENMW_VSGUTIL_CONVERT_H +#define VSGOPENMW_VSGUTIL_CONVERT_H + +#include +#include +#include + +namespace vsgUtil +{ + template + vsg::t_vec3 toVec3(const vsg::t_vec4& v) + { + return { v[0], v[1], v[2] }; + } + + template + vsg::t_vec2 toVec2(const vsg::t_vec3& v) + { + return { v[0], v[1] }; + } + + template + vsg::t_vec2 toVec2(const vsg::t_vec4& v) + { + return { v[0], v[1] }; + } +} + +#endif diff --git a/components/vsgutil/debugmessenger.hpp b/components/vsgutil/debugmessenger.hpp new file mode 100644 index 00000000000..e1043ed2c1f --- /dev/null +++ b/components/vsgutil/debugmessenger.hpp @@ -0,0 +1,77 @@ +#ifndef VSGOPENMW_VSGUTIL_DEBUGMESSENGER_H +#define VSGOPENMW_VSGUTIL_DEBUGMESSENGER_H + +#include + +#include + +namespace vsgUtil +{ + /* + * DebugMessenger is an extension of vsg::Instance used to catch message output of validation layers. + * By default, logs errors and warnings to std::cerr, then calls the error() or warning() method which can be used as a breakpoint. + * Users must enable the instance extension VK_EXT_debug_utils. + */ + class DebugMessenger + { + VkDebugUtilsMessengerEXT _messenger = VK_NULL_HANDLE; + vsg::ref_ptr _instance; + public: + DebugMessenger(vsg::ref_ptr instance, + VkDebugUtilsMessageSeverityFlagsEXT severityFlags = VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT, + VkDebugUtilsMessageTypeFlagsEXT typeFlags = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT) + { + _instance = instance; + + if (auto pfnCreateDebugUtilsMessengerEXT = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(_instance->vk(), "vkCreateDebugUtilsMessengerEXT")) + { + VkDebugUtilsMessengerCreateInfoEXT createInfo = { + .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT, + .pNext = nullptr, + .flags = 0, + .messageSeverity = severityFlags, + .messageType= typeFlags, + .pfnUserCallback = DebugMessenger::callback, + .pUserData = this + }; + auto res = pfnCreateDebugUtilsMessengerEXT(_instance->vk(), &createInfo, nullptr, &_messenger); + if (res != VK_SUCCESS) + { + std::cerr << "vkCreateDebugUtilsMessengerEXT failed with VkResult = " << res << std::endl; + } + else + std::cout << "VkDebugUtilsMessengerEXT has been set up successfully. Validation errors will pass through vsgUtil::DebugMessenger::error()." << std::endl; + } + else + std::cerr << "DebugMessenger::DebugMessenger(..) failed, VK_EXT_debug_utils is not enabled." << std::endl; + } + + ~DebugMessenger() + { + if (_messenger != VK_NULL_HANDLE) + { + if (auto pfnDestroyDebugUtilsMessengerEXT = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(_instance->vk(), "vkDestroyDebugUtilsMessengerEXT")) + pfnDestroyDebugUtilsMessengerEXT(_instance->vk(), _messenger, nullptr); + } + } + + static VkBool32 callback(VkDebugUtilsMessageSeverityFlagBitsEXT severityFlags, VkDebugUtilsMessageTypeFlagsEXT typeFlags, const VkDebugUtilsMessengerCallbackDataEXT* data, void* user) + { + static_cast(user)->message(severityFlags, typeFlags, data); + } + + virtual VkBool32 message(VkDebugUtilsMessageSeverityFlagBitsEXT severityFlags, VkDebugUtilsMessageTypeFlagsEXT typeFlags, const VkDebugUtilsMessengerCallbackDataEXT* data) + { + std::cerr << data->pMessage << std::endl; + if (severityFlags & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) + error(); + if (severityFlags & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) + warning(); + return true; + } + virtual void error() {} + virtual void warning() {} + }; +} + +#endif diff --git a/components/vsgutil/image.hpp b/components/vsgutil/image.hpp index 0a8df7493cd..3d98263f317 100644 --- a/components/vsgutil/image.hpp +++ b/components/vsgutil/image.hpp @@ -5,6 +5,9 @@ namespace vsgUtil { + /* + * Adds a convenience constructor for vsg::Image. + */ inline vsg::ref_ptr createImage(VkFormat format, const VkExtent2D& extent, VkImageUsageFlags usage, VkSampleCountFlagBits samples = VK_SAMPLE_COUNT_1_BIT) { auto image = vsg::Image::create(); diff --git a/components/vsgutil/nullbin.hpp b/components/vsgutil/nullbin.hpp index fa56c226276..25491e7035c 100644 --- a/components/vsgutil/nullbin.hpp +++ b/components/vsgutil/nullbin.hpp @@ -6,7 +6,8 @@ namespace vsgUtil { /* - * Removes traversal. + * NullBin is a special bin that is not traversed so that all contents placed into the bin are discarded. + * Useful for turning off certain effects/nodes in specific views. */ class NullBin : public vsg::Inherit { diff --git a/components/vsgutil/removechild.hpp b/components/vsgutil/removechild.hpp index 68d2a282eb4..ab7df7d8087 100644 --- a/components/vsgutil/removechild.hpp +++ b/components/vsgutil/removechild.hpp @@ -29,13 +29,16 @@ namespace vsgUtil } template - void prune(const std::vector& path) + void prune(const std::vector& path, Group* root) { for (auto it = path.rbegin(); it != path.rend(); ++it) { auto pit = it + 1; if (pit == path.rend()) + { + removeChild(root, *it); break; + } auto parent = static_cast(&**pit); removeChild(parent, *it); if (!parent->children.empty()) diff --git a/components/vsgutil/transform.hpp b/components/vsgutil/transform.hpp index 7eb200cb784..13422ee7aab 100644 --- a/components/vsgutil/transform.hpp +++ b/components/vsgutil/transform.hpp @@ -7,6 +7,9 @@ namespace vsgUtil { + /* + * Optimized version of lhsMatrix * vec4(rhsVector, 0), typically used for transforming direction vectors. + */ template vsg::t_vec3 transform3x3(const vsg::t_mat4& lhs, const vsg::t_vec3& rhs) { @@ -15,6 +18,9 @@ namespace vsgUtil (lhs[0][2] * rhs[0] + lhs[1][2] * rhs[1] + lhs[2][2] * rhs[2])); } + /* + * Optimized version of vec4(lhsVector, 0) * rhsMatrix, typically used to avoid a transpose(..) call when transforming normals by an inverse transpose normal matrix. + */ template vsg::t_vec3 transform3x3(const vsg::t_vec3& lhs, const vsg::t_mat4& rhs) { @@ -23,6 +29,9 @@ namespace vsgUtil lhs[0] * rhs[2][0] + lhs[1] * rhs[2][1] + lhs[2] * rhs[2][2]); } + /* + * Gets the rotation part of a 4x4 matrix. + */ template vsg::t_mat3 getRotation(const vsg::t_mat4& mat) { @@ -33,6 +42,9 @@ namespace vsgUtil }; } + /* + * Sets the rotation part of a 4x4 matrix. + */ template void setRotation(vsg::t_mat4& mat, const vsg::t_mat3& rot) { diff --git a/components/vsgutil/updatethreads.hpp b/components/vsgutil/updatethreads.hpp index 8e3a839145d..4ecf1c5f70a 100644 --- a/components/vsgutil/updatethreads.hpp +++ b/components/vsgutil/updatethreads.hpp @@ -13,7 +13,8 @@ namespace vsgUtil { /* - * Spreads workload. + * UpdateThreads is a thread pool for distributing update tasks. + * vsg::OperationThreads is used to implement the threads, with added tracking of the number of pending tasks. */ class UpdateThreads : public vsg::Inherit { diff --git a/files/shaders/CMakeLists.txt b/files/shaders/CMakeLists.txt index d7ae2bc70c0..f3de3c155d1 100644 --- a/files/shaders/CMakeLists.txt +++ b/files/shaders/CMakeLists.txt @@ -105,6 +105,7 @@ set(SHADER_FILES lib/view/reflect.glsl lib/view/cascades.glsl lib/view/fog.glsl + lib/view/dither.glsl lib/math/random.glsl lib/math/distance.glsl lib/math/plane.glsl diff --git a/files/shaders/comp/particle/data.glsl b/files/shaders/comp/particle/data.glsl index 2accdbae812..50a97a4e779 100644 --- a/files/shaders/comp/particle/data.glsl +++ b/files/shaders/comp/particle/data.glsl @@ -1,5 +1,6 @@ struct FrameArgs { + mat4 worldOffset; mat4 emitMatrix; ivec4 emitCount; vec4 time; diff --git a/files/shaders/comp/particle/simulate.comp b/files/shaders/comp/particle/simulate.comp index 43a005108f1..0ff3923cffb 100644 --- a/files/shaders/comp/particle/simulate.comp +++ b/files/shaders/comp/particle/simulate.comp @@ -14,6 +14,7 @@ layout(constant_id=1) const bool colorCurve = false; layout(constant_id=2) const bool gravityPlane = false; layout(constant_id=3) const bool gravityPoint = false; layout(constant_id=4) const bool collidePlane = false; +layout(constant_id=5) const bool worldSpace = false; layout(set=TEXTURE_SET, binding=PARTICLE_BINDING) buffer ParticleBuffer{ Particle particles[]; @@ -39,6 +40,11 @@ void main() { Particle p = particles[gid]; p.velocityAge.w += dt; + if (worldSpace) + { + p.positionSize.xyz = (frameArgs.worldOffset * vec4(p.positionSize.xyz, 1)).xyz; + p.velocityAge.xyz = (vec4(p.velocityAge.xyz, 0) * inverse(frameArgs.worldOffset)).xyz; + } if (!isDead(p)) { p.positionSize.xyz += p.velocityAge.xyz * dt; @@ -52,7 +58,7 @@ void main() float rand3 = signedRandom(rand2); p.positionSize.xyz = (frameArgs.emitMatrix * vec4(args.emit.offsetRandom.x * rand1, args.emit.offsetRandom.y * rand2, - args.emit.offsetRandom.z * rand3, 0)).xyz; + args.emit.offsetRandom.z * rand3, 1)).xyz; rand1 = signedRandom(rand3); rand2 = signedRandom(rand1); diff --git a/files/shaders/default/main.frag b/files/shaders/default/main.frag index 9b556dbfc61..1589c68038a 100644 --- a/files/shaders/default/main.frag +++ b/files/shaders/default/main.frag @@ -17,6 +17,7 @@ layout(constant_id=START_UVSET_CONSTANTS+BUMP_UNIT) const int bumpUV=0; #include "lib/view/env.glsl" #include "lib/view/fog.glsl" #include "lib/view/shadow.glsl" +#include "lib/view/dither.glsl" #include "lib/object/object.glsl" #include "lib/material/alphatest.glsl" #include "lib/material/terms.glsl" @@ -99,4 +100,17 @@ void main() #ifdef DEBUG_SHADOW outColor = debugShadow(outColor); #endif + +#ifdef NORMAL + if (object.alpha != 1) + { + int x = int(mod(gl_FragCoord.x, 8)); + int y = int(mod(gl_FragCoord.y, 8)); + float b = texture(sceneEnv, vec3(frag_in.envUV, envIndex(scene.time))).x; + b = mix(object.alpha, 1.0, b); + float dither = dither8(x, y, b); + if (dither == 0) + discard; + } +#endif } diff --git a/files/shaders/default/main.vert b/files/shaders/default/main.vert index 8f84a4d555a..c50b10b26ed 100644 --- a/files/shaders/default/main.vert +++ b/files/shaders/default/main.vert @@ -47,12 +47,19 @@ void main() for (int i=0; i> 1) & 1); vec4 vertex = vec4(particle.positionSize.xyz, 1.0); + float deadFactor = isDead(particle) ? 0 : 1; + vec3 xAxis = (vec4(1,0,0,0) * pc.modelview).xyz; + vec3 yAxis = (vec4(0,1,0,0) * pc.modelview).xyz; + vec2 offsetCoord = texCoord - 0.5; + vertex.xyz += (offsetCoord.x * normalize(xAxis) + offsetCoord.y * normalize(yAxis)) * particle.positionSize.w * deadFactor; + vert_out.texCoord[0] = texCoord; - vert_out.color = particle.color; + vert_out.color = particle.color * deadFactor; #elif defined(VERTEX) vec4 vertex = vec4(inVertex, 1.0); #endif @@ -79,15 +86,6 @@ void main() #endif * vertex; -#ifdef PARTICLE - float deadFactor = isDead(particle) ? 0 : 1; - vec3 xAxis = (vec4(1,0,0,0) * pc.modelview).xyz; - vec3 yAxis = (vec4(0,1,0,0) * pc.modelview).xyz; - vec2 scale = vec2(length(xAxis), length(yAxis)); - viewPos.xy += (texCoord - 0.5) * particle.positionSize.w * scale * deadFactor; - vert_out.color.a *= deadFactor; -#endif - vert_out.viewPos = viewPos.xyz; gl_Position = pc.projection * viewPos; diff --git a/files/shaders/lib/object/data.glsl b/files/shaders/lib/object/data.glsl index 3a45e3795d9..75cc51d0cad 100644 --- a/files/shaders/lib/object/data.glsl +++ b/files/shaders/lib/object/data.glsl @@ -1,5 +1,5 @@ struct Object { vec4 envColor; - //float alpha; + float alpha; }; diff --git a/vsgopenmw-roadmap.doc b/vsgopenmw-roadmap.doc index a074bd0744f..d54185f57b9 100644 --- a/vsgopenmw-roadmap.doc +++ b/vsgopenmw-roadmap.doc @@ -1,10 +1,8 @@ vsgopenmw-roadmap={ "vsgopenmw-alpha" : {}, "vsgopenmw-feature-parity" : { - "vsgopenmw-world-space-particles", "vsgopenmw-weather-particles", "vsgopenmw-water-ripples", - "vsgopenmw-invisibility-effect", "vsgopenmw-paged-instanced-objects", "vsgopenmw-normal-maps", "vsgopenmw-alpha-to-coverage",