diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 47502fd71..68d4ac85d 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -5,6 +5,9 @@ #include #include +#include +#include + #include #include "../mwworld/ptr.hpp" @@ -534,7 +537,7 @@ namespace MWBase /// Spawn a blood effect for \a ptr at \a worldPosition virtual void spawnBloodEffect (const MWWorld::Ptr& ptr, const osg::Vec3f& worldPosition) = 0; - virtual void spawnEffect (const std::string& model, const std::string& textureOverride, const osg::Vec3f& worldPos) = 0; + virtual void spawnEffect (const std::string& model, const std::string& textureOverride, const osg::Vec3f& worldPos, float scale = 1.f, bool isMagicVFX = true) = 0; virtual void explodeSpell(const osg::Vec3f& origin, const ESM::EffectList& effects, const MWWorld::Ptr& caster, const MWWorld::Ptr& ignore, ESM::RangeType rangeType, const std::string& id, @@ -576,6 +579,8 @@ namespace MWBase /// Preload VFX associated with this effect list virtual void preloadEffects(const ESM::EffectList* effectList) = 0; + + virtual osg::ref_ptr getInstance (const std::string& modelName) = 0; }; } diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 68dc17915..29a6d5e3b 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -833,6 +833,7 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim refreshCurrentAnims(mIdleState, mMovementState, mJumpState, true); mAnimation->runAnimation(0.f); + mAnimation->updateEffects(0.f); unpersistAnimationState(); } @@ -1996,6 +1997,13 @@ void CharacterController::update(float duration) } osg::Vec3f moved = mAnimation->runAnimation(mSkipAnim ? 0.f : duration); + + // treat player specifically since he is not in rendering mObjects + if (mPtr == getPlayer()) + { + mAnimation->updateEffects(mSkipAnim ? 0.f : duration); + } + if(duration > 0.0f) moved /= duration; else @@ -2191,6 +2199,7 @@ void CharacterController::forceStateUpdate() } mAnimation->runAnimation(0.f); + mAnimation->updateEffects(0.f); } CharacterController::KillResult CharacterController::kill() diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 7570cb9ba..802020b0e 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -6,8 +6,12 @@ #include +#include +#include + #include #include +#include #include "../mwbase/windowmanager.hpp" #include "../mwbase/soundmanager.hpp" @@ -24,6 +28,7 @@ #include "../mwworld/inventorystore.hpp" #include "../mwrender/animation.hpp" +#include "../mwrender/vismask.hpp" #include "npcstats.hpp" #include "actorutil.hpp" @@ -996,11 +1001,13 @@ namespace MWMechanics return true; } - void CastSpell::playSpellCastingEffects(const std::string &spellid){ - + void CastSpell::playSpellCastingEffects(const std::string &spellid) + { const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); const ESM::Spell *spell = store.get().find(spellid); + std::vector addedEffects; + for (std::vector::const_iterator iter = spell->mEffects.mList.begin(); iter != spell->mEffects.mList.end(); ++iter) { @@ -1009,18 +1016,72 @@ namespace MWMechanics MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(mCaster); - if (animation && mCaster.getClass().isActor()) // TODO: Non-actors should also create a spell cast vfx even if they are disabled (animation == NULL) + const ESM::Static* castStatic; + + if (!effect->mCasting.empty()) + castStatic = store.get().find (effect->mCasting); + else + castStatic = store.get().find ("VFX_DefaultCast"); + + // check if the effect was already added + if (std::find(addedEffects.begin(), addedEffects.end(), "meshes\\" + castStatic->mModel) != addedEffects.end()) + continue; + + std::string texture = effect->mParticle; + + float scale = 1.0f; + osg::Vec3f pos (mCaster.getRefData().getPosition().asVec3()); + + if (animation && mCaster.getClass().isNpc()) { - const ESM::Static* castStatic; + // For NOC we should take race height as scaling factor + const ESM::NPC *npc = mCaster.get()->mBase; + const MWWorld::ESMStore &esmStore = + MWBase::Environment::get().getWorld()->getStore(); - if (!effect->mCasting.empty()) - castStatic = store.get().find (effect->mCasting); - else - castStatic = store.get().find ("VFX_DefaultCast"); + const ESM::Race *race = + esmStore.get().find(npc->mRace); - std::string texture = effect->mParticle; + scale = npc->isMale() ? race->mData.mHeight.mMale : race->mData.mHeight.mFemale; + } + else + { + std::string casterModel = mCaster.getClass().getModel(mCaster); + osg::ref_ptr model = MWBase::Environment::get().getWorld()->getInstance(casterModel); - animation->addEffect("meshes\\" + castStatic->mModel, effect->mIndex, false, "", texture); + osg::ComputeBoundsVisitor computeBoundsVisitor; + computeBoundsVisitor.setTraversalMask(~(MWRender::Mask_ParticleSystem|MWRender::Mask_Effect)); + model->accept(computeBoundsVisitor); + osg::BoundingBox bounds = computeBoundsVisitor.getBoundingBox(); + + if (bounds.valid()) + { + float meshSizeX = std::abs(bounds.xMax() - bounds.xMin()); + float meshSizeY = std::abs(bounds.yMax() - bounds.yMin()); + float meshSizeZ = std::abs(bounds.zMax() - bounds.zMin()); + + // TODO: take a size of particle or NPC with height and weight = 1.0 as scale = 1.0 + float scaleX = meshSizeX/60.f; + float scaleY = meshSizeY/60.f; + float scaleZ = meshSizeZ/120.f; + + scale = std::max({ scaleX, scaleY, scaleZ }); + + //pos = bounds.center(); + //pos[2] = bounds.zMin(); + } + } + + // If the caster has no animation, add the effect directly to the effectManager + if (animation) + { + animation->addEffect("meshes\\" + castStatic->mModel, effect->mIndex, false, "", texture, scale); + } + else + { + // We should set scale for effect manager manually + float meshScale = !mCaster.getClass().isActor() ? mCaster.getCellRef().getScale() : 1.0f; + MWBase::Environment::get().getWorld()->spawnEffect("meshes\\" + castStatic->mModel, effect->mParticle, pos, scale * meshScale); } if (animation && !mCaster.getClass().isActor()) @@ -1030,6 +1091,8 @@ namespace MWMechanics "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" }; + addedEffects.push_back("meshes\\" + castStatic->mModel); + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); if(!effect->mCastSound.empty()) sndMgr->playSound3D(mCaster, effect->mCastSound, 1.0f, 1.0f); diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 1bd839ead..1b508e593 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -8,6 +8,8 @@ #include #include #include +#include +#include #include #include @@ -1115,8 +1117,6 @@ namespace MWRender ++stateiter; } - updateEffects(duration); - if (mHeadController) { const float epsilon = 0.001f; @@ -1366,7 +1366,7 @@ namespace MWRender useQuadratic, quadraticValue, quadraticRadiusMult, useLinear, linearRadiusMult, linearValue); } - void Animation::addEffect (const std::string& model, int effectId, bool loop, const std::string& bonename, const std::string& texture) + void Animation::addEffect (const std::string& model, int effectId, bool loop, const std::string& bonename, const std::string& texture, float scale) { if (!mObjectRoot.get()) return; @@ -1417,7 +1417,13 @@ namespace MWRender overrideFirstRootTexture(texture, mResourceSystem, node); - // TODO: in vanilla morrowind the effect is scaled based on the host object's bounding box. + osg::Vec3f scale3f (scale, scale, scale); + + osg::ref_ptr trans = new osg::PositionAttitudeTransform; + trans->setScale(scale3f); + trans->addChild(node); + parentNode->removeChild(node); + parentNode->addChild(trans); mEffects.push_back(params); } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index cff98a4b7..74224c6bd 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -364,7 +364,7 @@ public: * @param texture override the texture specified in the model's materials - if empty, do not override * @note Will not add an effect twice. */ - void addEffect (const std::string& model, int effectId, bool loop = false, const std::string& bonename = "", const std::string& texture = ""); + void addEffect (const std::string& model, int effectId, bool loop = false, const std::string& bonename = "", const std::string& texture = "", float scale = 1.0f); void removeEffect (int effectId); void getLoopingEffects (std::vector& out) const; @@ -446,7 +446,6 @@ public: void setLoopingEnabled(const std::string &groupname, bool enabled); - /// This is typically called as part of runAnimation, but may be called manually if needed. void updateEffects(float duration); /// Return a node with the specified name, or NULL if not existing. diff --git a/apps/openmw/mwrender/effectmanager.cpp b/apps/openmw/mwrender/effectmanager.cpp index 3e785a769..c44c5428a 100644 --- a/apps/openmw/mwrender/effectmanager.cpp +++ b/apps/openmw/mwrender/effectmanager.cpp @@ -26,6 +26,13 @@ EffectManager::~EffectManager() } void EffectManager::addEffect(const std::string &model, const std::string& textureOverride, const osg::Vec3f &worldPosition, float scale, bool isMagicVFX) +{ + osg::Vec3f scale3f (scale, scale, scale); + + addEffect(model, textureOverride, worldPosition, scale3f, isMagicVFX); +} + +void EffectManager::addEffect(const std::string &model, const std::string& textureOverride, const osg::Vec3f &worldPosition, const osg::Vec3f &scale, bool isMagicVFX) { osg::ref_ptr node = mResourceSystem->getSceneManager()->getInstance(model); @@ -40,7 +47,7 @@ void EffectManager::addEffect(const std::string &model, const std::string& textu osg::ref_ptr trans = new osg::PositionAttitudeTransform; trans->setPosition(worldPosition); - trans->setScale(osg::Vec3f(scale, scale, scale)); + trans->setScale(scale); trans->addChild(node); SceneUtil::AssignControllerSourcesVisitor assignVisitor(effect.mAnimTime); diff --git a/apps/openmw/mwrender/effectmanager.hpp b/apps/openmw/mwrender/effectmanager.hpp index 5873c00dd..cc1c1b42e 100644 --- a/apps/openmw/mwrender/effectmanager.hpp +++ b/apps/openmw/mwrender/effectmanager.hpp @@ -33,6 +33,7 @@ namespace MWRender /// Add an effect. When it's finished playing, it will be removed automatically. void addEffect (const std::string& model, const std::string& textureOverride, const osg::Vec3f& worldPosition, float scale, bool isMagicVFX = true); + void addEffect (const std::string &model, const std::string& textureOverride, const osg::Vec3f &worldPosition, const osg::Vec3f &scale3f, bool isMagicVFX); void update(float dt); diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index f0a3d2e38..523f1774c 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -138,6 +138,14 @@ bool Objects::removeObject (const MWWorld::Ptr& ptr) return false; } +void Objects::updateEffects(float duration) +{ + for(PtrAnimationMap::iterator iter = mObjects.begin();iter != mObjects.end();) + { + iter->second->updateEffects(duration); + ++iter; + } +} void Objects::removeCell(const MWWorld::CellStore* store) { diff --git a/apps/openmw/mwrender/objects.hpp b/apps/openmw/mwrender/objects.hpp index 659853763..e97395213 100644 --- a/apps/openmw/mwrender/objects.hpp +++ b/apps/openmw/mwrender/objects.hpp @@ -88,6 +88,8 @@ public: bool removeObject (const MWWorld::Ptr& ptr); ///< \return found? + void updateEffects(float duration); + void removeCell(const MWWorld::CellStore* store); /// Updates containing cell for object rendering data diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 6f0ddba3a..e5933a72a 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -557,6 +557,7 @@ namespace MWRender mEffectManager->update(dt); mSky->update(dt); mWater->update(dt); + mObjects->updateEffects(dt); } mCamera->update(dt, paused); @@ -841,9 +842,11 @@ namespace MWRender mObjects->updatePtr(old, updated); } - void RenderingManager::spawnEffect(const std::string &model, const std::string &texture, const osg::Vec3f &worldPosition, float scale, bool isMagicVFX) + void RenderingManager::spawnEffect(const std::string &model, const std::string& texture, const osg::Vec3f &worldPosition, float scale, bool isMagicVFX) { - mEffectManager->addEffect(model, texture, worldPosition, scale, isMagicVFX); + osg::Vec3f scale3f (scale, scale, scale); + + mEffectManager->addEffect(model, texture, worldPosition, scale3f, isMagicVFX); } void RenderingManager::notifyWorldSpaceChanged() @@ -1122,6 +1125,12 @@ namespace MWRender updateProjectionMatrix(); } } + + osg::ref_ptr RenderingManager::getInstance(const std::string& modelName) + { + return mResourceSystem->getSceneManager()->getInstance(modelName); + } + void RenderingManager::exportSceneGraph(const MWWorld::Ptr &ptr, const std::string &filename, const std::string &format) { osg::Node* node = mViewer->getSceneData(); diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 1c689d29f..7a570f249 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -205,6 +205,8 @@ namespace MWRender LandManager* getLandManager() const; + osg::ref_ptr getInstance(const std::string& modelName); + private: void updateProjectionMatrix(); void updateTextureFiltering(); diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 59f2cc9c6..243ba7b7e 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -14,6 +14,7 @@ #include #include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/scriptmanager.hpp" #include "../mwbase/world.hpp" @@ -1051,6 +1052,7 @@ namespace MWScript MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr (targetId, false); MWMechanics::CastSpell cast(ptr, target); + cast.playSpellCastingEffects(spell); cast.mHitPosition = target.getRefData().getPosition().asVec3(); cast.mAlwaysSucceed = true; cast.cast(spell); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 6cf1ead87..bbca8a46e 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1535,6 +1535,11 @@ namespace MWWorld } } + osg::ref_ptr World::getInstance (const std::string& modelName) + { + return mRendering->getInstance(modelName); + } + const ESM::Potion *World::createRecord (const ESM::Potion& record) { return mStore.insert(record); @@ -3324,9 +3329,9 @@ namespace MWWorld mRendering->spawnEffect(model, texture, worldPosition, 1.0f, false); } - void World::spawnEffect(const std::string &model, const std::string &textureOverride, const osg::Vec3f &worldPos) + void World::spawnEffect(const std::string &model, const std::string &textureOverride, const osg::Vec3f &worldPos, float scale, bool isMagicVFX) { - mRendering->spawnEffect(model, textureOverride, worldPos); + mRendering->spawnEffect(model, textureOverride, worldPos, scale, isMagicVFX); } void World::explodeSpell(const osg::Vec3f& origin, const ESM::EffectList& effects, const Ptr& caster, const Ptr& ignore, ESM::RangeType rangeType, diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 0d168c912..023d91bca 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -640,7 +640,7 @@ namespace MWWorld /// Spawn a blood effect for \a ptr at \a worldPosition void spawnBloodEffect (const MWWorld::Ptr& ptr, const osg::Vec3f& worldPosition) override; - void spawnEffect (const std::string& model, const std::string& textureOverride, const osg::Vec3f& worldPos) override; + void spawnEffect (const std::string& model, const std::string& textureOverride, const osg::Vec3f& worldPos, float scale = 1.f, bool isMagicVFX = true) override; void explodeSpell(const osg::Vec3f& origin, const ESM::EffectList& effects, const MWWorld::Ptr& caster, const MWWorld::Ptr& ignore, ESM::RangeType rangeType, const std::string& id, const std::string& sourceName, @@ -680,6 +680,8 @@ namespace MWWorld /// Preload VFX associated with this effect list void preloadEffects(const ESM::EffectList* effectList) override; + + osg::ref_ptr getInstance (const std::string& modelName); }; }