mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2024-12-28 18:18:52 +00:00
742 lines
25 KiB
C++
742 lines
25 KiB
C++
#include "particle.hpp"
|
|
|
|
#include <limits>
|
|
#include <optional>
|
|
|
|
#include <osg/Geometry>
|
|
#include <osg/MatrixTransform>
|
|
#include <osg/ValueObject>
|
|
|
|
#include <components/debug/debuglog.hpp>
|
|
#include <components/misc/rng.hpp>
|
|
#include <components/nif/data.hpp>
|
|
#include <components/sceneutil/morphgeometry.hpp>
|
|
#include <components/sceneutil/riggeometry.hpp>
|
|
|
|
namespace
|
|
{
|
|
class FindFirstGeometry : public osg::NodeVisitor
|
|
{
|
|
public:
|
|
FindFirstGeometry()
|
|
: osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN)
|
|
, mGeometry(nullptr)
|
|
{
|
|
}
|
|
|
|
void apply(osg::Node& node) override
|
|
{
|
|
if (mGeometry)
|
|
return;
|
|
|
|
traverse(node);
|
|
}
|
|
|
|
void apply(osg::Drawable& drawable) override
|
|
{
|
|
if (auto morph = dynamic_cast<SceneUtil::MorphGeometry*>(&drawable))
|
|
{
|
|
mGeometry = morph->getSourceGeometry();
|
|
return;
|
|
}
|
|
else if (auto rig = dynamic_cast<SceneUtil::RigGeometry*>(&drawable))
|
|
{
|
|
mGeometry = rig->getSourceGeometry();
|
|
return;
|
|
}
|
|
|
|
traverse(drawable);
|
|
}
|
|
|
|
void apply(osg::Geometry& geometry) override { mGeometry = &geometry; }
|
|
|
|
osg::Geometry* mGeometry;
|
|
};
|
|
|
|
class LocalToWorldAccumulator : public osg::NodeVisitor
|
|
{
|
|
public:
|
|
LocalToWorldAccumulator(osg::Matrix& matrix)
|
|
: osg::NodeVisitor()
|
|
, mMatrix(matrix)
|
|
{
|
|
}
|
|
|
|
virtual void apply(osg::Transform& transform)
|
|
{
|
|
if (&transform != mLastAppliedTransform)
|
|
{
|
|
mLastAppliedTransform = &transform;
|
|
mLastMatrix = mMatrix;
|
|
}
|
|
transform.computeLocalToWorldMatrix(mMatrix, this);
|
|
}
|
|
|
|
void accumulate(const osg::NodePath& path)
|
|
{
|
|
if (path.empty())
|
|
return;
|
|
|
|
size_t i = path.size();
|
|
|
|
for (auto rit = path.rbegin(); rit != path.rend(); rit++, --i)
|
|
{
|
|
const osg::Camera* camera = (*rit)->asCamera();
|
|
if (camera
|
|
&& (camera->getReferenceFrame() != osg::Transform::RELATIVE_RF || camera->getParents().empty()))
|
|
break;
|
|
}
|
|
|
|
for (; i < path.size(); ++i)
|
|
path[i]->accept(*this);
|
|
}
|
|
|
|
osg::Matrix& mMatrix;
|
|
std::optional<osg::Matrix> mLastMatrix;
|
|
osg::Transform* mLastAppliedTransform = nullptr;
|
|
};
|
|
}
|
|
|
|
namespace NifOsg
|
|
{
|
|
|
|
ParticleSystem::ParticleSystem()
|
|
: osgParticle::ParticleSystem()
|
|
, mQuota(std::numeric_limits<int>::max())
|
|
{
|
|
mNormalArray = new osg::Vec3Array(1);
|
|
mNormalArray->setBinding(osg::Array::BIND_OVERALL);
|
|
(*mNormalArray.get())[0] = osg::Vec3(0.3, 0.3, 0.3);
|
|
}
|
|
|
|
ParticleSystem::ParticleSystem(const ParticleSystem& copy, const osg::CopyOp& copyop)
|
|
: osgParticle::ParticleSystem(copy, copyop)
|
|
, mQuota(copy.mQuota)
|
|
{
|
|
mNormalArray = new osg::Vec3Array(1);
|
|
mNormalArray->setBinding(osg::Array::BIND_OVERALL);
|
|
(*mNormalArray.get())[0] = osg::Vec3(0.3, 0.3, 0.3);
|
|
|
|
// For some reason the osgParticle constructor doesn't copy the particles
|
|
for (int i = 0; i < copy.numParticles() - copy.numDeadParticles(); ++i)
|
|
ParticleSystem::createParticle(copy.getParticle(i));
|
|
}
|
|
|
|
void ParticleSystem::setQuota(int quota)
|
|
{
|
|
mQuota = quota;
|
|
}
|
|
|
|
osgParticle::Particle* ParticleSystem::createParticle(const osgParticle::Particle* ptemplate)
|
|
{
|
|
if (numParticles() - numDeadParticles() < mQuota)
|
|
return osgParticle::ParticleSystem::createParticle(ptemplate);
|
|
return nullptr;
|
|
}
|
|
|
|
void ParticleSystem::drawImplementation(osg::RenderInfo& renderInfo) const
|
|
{
|
|
osg::State& state = *renderInfo.getState();
|
|
if (state.useVertexArrayObject(getUseVertexArrayObject()))
|
|
{
|
|
state.getCurrentVertexArrayState()->assignNormalArrayDispatcher();
|
|
state.getCurrentVertexArrayState()->setNormalArray(state, mNormalArray);
|
|
}
|
|
else
|
|
{
|
|
state.getAttributeDispatchers().activateNormalArray(mNormalArray);
|
|
}
|
|
osgParticle::ParticleSystem::drawImplementation(renderInfo);
|
|
}
|
|
|
|
void InverseWorldMatrix::operator()(osg::MatrixTransform* node, osg::NodeVisitor* nv)
|
|
{
|
|
osg::NodePath path = nv->getNodePath();
|
|
path.pop_back();
|
|
|
|
osg::Matrix mat = osg::computeLocalToWorld(path);
|
|
mat.orthoNormalize(mat); // don't undo the scale
|
|
mat.invert(mat);
|
|
node->setMatrix(mat);
|
|
|
|
traverse(node, nv);
|
|
}
|
|
|
|
ParticleShooter::ParticleShooter(float minSpeed, float maxSpeed, float horizontalDir, float horizontalAngle,
|
|
float verticalDir, float verticalAngle, float lifetime, float lifetimeRandom)
|
|
: mMinSpeed(minSpeed)
|
|
, mMaxSpeed(maxSpeed)
|
|
, mHorizontalDir(horizontalDir)
|
|
, mHorizontalAngle(horizontalAngle)
|
|
, mVerticalDir(verticalDir)
|
|
, mVerticalAngle(verticalAngle)
|
|
, mLifetime(lifetime)
|
|
, mLifetimeRandom(lifetimeRandom)
|
|
{
|
|
}
|
|
|
|
ParticleShooter::ParticleShooter()
|
|
: mMinSpeed(0.f)
|
|
, mMaxSpeed(0.f)
|
|
, mHorizontalDir(0.f)
|
|
, mHorizontalAngle(0.f)
|
|
, mVerticalDir(0.f)
|
|
, mVerticalAngle(0.f)
|
|
, mLifetime(0.f)
|
|
, mLifetimeRandom(0.f)
|
|
{
|
|
}
|
|
|
|
ParticleShooter::ParticleShooter(const ParticleShooter& copy, const osg::CopyOp& copyop)
|
|
: osgParticle::Shooter(copy, copyop)
|
|
{
|
|
mMinSpeed = copy.mMinSpeed;
|
|
mMaxSpeed = copy.mMaxSpeed;
|
|
mHorizontalDir = copy.mHorizontalDir;
|
|
mHorizontalAngle = copy.mHorizontalAngle;
|
|
mVerticalDir = copy.mVerticalDir;
|
|
mVerticalAngle = copy.mVerticalAngle;
|
|
mLifetime = copy.mLifetime;
|
|
mLifetimeRandom = copy.mLifetimeRandom;
|
|
}
|
|
|
|
void ParticleShooter::shoot(osgParticle::Particle* particle) const
|
|
{
|
|
float hdir = mHorizontalDir + mHorizontalAngle * (2.f * Misc::Rng::rollClosedProbability() - 1.f);
|
|
float vdir = mVerticalDir + mVerticalAngle * (2.f * Misc::Rng::rollClosedProbability() - 1.f);
|
|
|
|
osg::Vec3f dir
|
|
= (osg::Quat(vdir, osg::Vec3f(0, 1, 0)) * osg::Quat(hdir, osg::Vec3f(0, 0, 1))) * osg::Vec3f(0, 0, 1);
|
|
|
|
float vel = mMinSpeed + (mMaxSpeed - mMinSpeed) * Misc::Rng::rollClosedProbability();
|
|
particle->setVelocity(dir * vel);
|
|
|
|
// Not supposed to set this here, but there doesn't seem to be a better way of doing it
|
|
particle->setLifeTime(std::max(
|
|
std::numeric_limits<float>::epsilon(), mLifetime + mLifetimeRandom * Misc::Rng::rollClosedProbability()));
|
|
}
|
|
|
|
GrowFadeAffector::GrowFadeAffector(float growTime, float fadeTime)
|
|
: mGrowTime(growTime)
|
|
, mFadeTime(fadeTime)
|
|
, mCachedDefaultSize(0.f)
|
|
{
|
|
}
|
|
|
|
GrowFadeAffector::GrowFadeAffector()
|
|
: mGrowTime(0.f)
|
|
, mFadeTime(0.f)
|
|
, mCachedDefaultSize(0.f)
|
|
{
|
|
}
|
|
|
|
GrowFadeAffector::GrowFadeAffector(const GrowFadeAffector& copy, const osg::CopyOp& copyop)
|
|
: osgParticle::Operator(copy, copyop)
|
|
{
|
|
mGrowTime = copy.mGrowTime;
|
|
mFadeTime = copy.mFadeTime;
|
|
mCachedDefaultSize = copy.mCachedDefaultSize;
|
|
}
|
|
|
|
void GrowFadeAffector::beginOperate(osgParticle::Program* program)
|
|
{
|
|
mCachedDefaultSize = program->getParticleSystem()->getDefaultParticleTemplate().getSizeRange().minimum;
|
|
}
|
|
|
|
void GrowFadeAffector::operate(osgParticle::Particle* particle, double /* dt */)
|
|
{
|
|
float size = mCachedDefaultSize;
|
|
if (particle->getAge() < mGrowTime && mGrowTime != 0.f)
|
|
size *= particle->getAge() / mGrowTime;
|
|
if (particle->getLifeTime() - particle->getAge() < mFadeTime && mFadeTime != 0.f)
|
|
size *= (particle->getLifeTime() - particle->getAge()) / mFadeTime;
|
|
particle->setSizeRange(osgParticle::rangef(size, size));
|
|
}
|
|
|
|
ParticleColorAffector::ParticleColorAffector(const Nif::NiColorData* clrdata)
|
|
: mData(clrdata->mKeyMap, osg::Vec4f(1, 1, 1, 1))
|
|
{
|
|
}
|
|
|
|
ParticleColorAffector::ParticleColorAffector() {}
|
|
|
|
ParticleColorAffector::ParticleColorAffector(const ParticleColorAffector& copy, const osg::CopyOp& copyop)
|
|
: osgParticle::Operator(copy, copyop)
|
|
{
|
|
mData = copy.mData;
|
|
}
|
|
|
|
void ParticleColorAffector::operate(osgParticle::Particle* particle, double /* dt */)
|
|
{
|
|
assert(particle->getLifeTime() > 0);
|
|
float time = static_cast<float>(particle->getAge() / particle->getLifeTime());
|
|
osg::Vec4f color = mData.interpKey(time);
|
|
float alpha = color.a();
|
|
color.a() = 1.0f;
|
|
|
|
particle->setColorRange(osgParticle::rangev4(color, color));
|
|
particle->setAlphaRange(osgParticle::rangef(alpha, alpha));
|
|
}
|
|
|
|
GravityAffector::GravityAffector(const Nif::NiGravity* gravity)
|
|
: mForce(gravity->mForce)
|
|
, mType(gravity->mType)
|
|
, mPosition(gravity->mPosition)
|
|
, mDirection(gravity->mDirection)
|
|
, mDecay(gravity->mDecay)
|
|
{
|
|
}
|
|
|
|
GravityAffector::GravityAffector(const GravityAffector& copy, const osg::CopyOp& copyop)
|
|
: osgParticle::Operator(copy, copyop)
|
|
{
|
|
mForce = copy.mForce;
|
|
mType = copy.mType;
|
|
mPosition = copy.mPosition;
|
|
mDirection = copy.mDirection;
|
|
mDecay = copy.mDecay;
|
|
mCachedWorldPosition = copy.mCachedWorldPosition;
|
|
mCachedWorldDirection = copy.mCachedWorldDirection;
|
|
}
|
|
|
|
void GravityAffector::beginOperate(osgParticle::Program* program)
|
|
{
|
|
bool absolute = (program->getReferenceFrame() == osgParticle::ParticleProcessor::ABSOLUTE_RF);
|
|
|
|
// We don't need the position for Wind gravity, except if decay is being applied
|
|
if (mType == Nif::ForceType::Point || mDecay != 0.f)
|
|
mCachedWorldPosition = absolute ? program->transformLocalToWorld(mPosition) : mPosition;
|
|
|
|
mCachedWorldDirection = absolute ? program->rotateLocalToWorld(mDirection) : mDirection;
|
|
mCachedWorldDirection.normalize();
|
|
}
|
|
|
|
void GravityAffector::operate(osgParticle::Particle* particle, double dt)
|
|
{
|
|
const float magic = 1.6f;
|
|
switch (mType)
|
|
{
|
|
case Nif::ForceType::Wind:
|
|
{
|
|
float decayFactor = 1.f;
|
|
if (mDecay != 0.f)
|
|
{
|
|
osg::Plane gravityPlane(mCachedWorldDirection, mCachedWorldPosition);
|
|
float distance = std::abs(gravityPlane.distance(particle->getPosition()));
|
|
decayFactor = std::exp(-1.f * mDecay * distance);
|
|
}
|
|
|
|
particle->addVelocity(mCachedWorldDirection * mForce * dt * decayFactor * magic);
|
|
|
|
break;
|
|
}
|
|
case Nif::ForceType::Point:
|
|
{
|
|
osg::Vec3f diff = mCachedWorldPosition - particle->getPosition();
|
|
|
|
float decayFactor = 1.f;
|
|
if (mDecay != 0.f)
|
|
decayFactor = std::exp(-1.f * mDecay * diff.length());
|
|
|
|
diff.normalize();
|
|
|
|
particle->addVelocity(diff * mForce * dt * decayFactor * magic);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
ParticleBomb::ParticleBomb(const Nif::NiParticleBomb* bomb)
|
|
: mRange(bomb->mRange)
|
|
, mStrength(bomb->mStrength)
|
|
, mDecayType(bomb->mDecayType)
|
|
, mSymmetryType(bomb->mSymmetryType)
|
|
, mPosition(bomb->mPosition)
|
|
, mDirection(bomb->mDirection)
|
|
{
|
|
}
|
|
|
|
ParticleBomb::ParticleBomb(const ParticleBomb& copy, const osg::CopyOp& copyop)
|
|
: osgParticle::Operator(copy, copyop)
|
|
{
|
|
mRange = copy.mRange;
|
|
mStrength = copy.mStrength;
|
|
mDecayType = copy.mDecayType;
|
|
mSymmetryType = copy.mSymmetryType;
|
|
mCachedWorldPosition = copy.mCachedWorldPosition;
|
|
mCachedWorldDirection = copy.mCachedWorldDirection;
|
|
}
|
|
|
|
void ParticleBomb::beginOperate(osgParticle::Program* program)
|
|
{
|
|
bool absolute = (program->getReferenceFrame() == osgParticle::ParticleProcessor::ABSOLUTE_RF);
|
|
|
|
mCachedWorldPosition = absolute ? program->transformLocalToWorld(mPosition) : mPosition;
|
|
|
|
// We don't need the direction for Spherical bomb
|
|
if (mSymmetryType != Nif::SymmetryType::Spherical)
|
|
{
|
|
mCachedWorldDirection = absolute ? program->rotateLocalToWorld(mDirection) : mDirection;
|
|
mCachedWorldDirection.normalize();
|
|
}
|
|
}
|
|
|
|
void ParticleBomb::operate(osgParticle::Particle* particle, double dt)
|
|
{
|
|
float decay = 1.f;
|
|
osg::Vec3f explosionDir;
|
|
|
|
osg::Vec3f particleDir = particle->getPosition() - mCachedWorldPosition;
|
|
float distance = particleDir.length();
|
|
particleDir.normalize();
|
|
|
|
switch (mDecayType)
|
|
{
|
|
case Nif::DecayType::None:
|
|
break;
|
|
case Nif::DecayType::Linear:
|
|
decay = 1.f - distance / mRange;
|
|
break;
|
|
case Nif::DecayType::Exponential:
|
|
decay = std::exp(-distance / mRange);
|
|
break;
|
|
}
|
|
|
|
if (decay <= 0.f)
|
|
return;
|
|
|
|
switch (mSymmetryType)
|
|
{
|
|
case Nif::SymmetryType::Spherical:
|
|
explosionDir = particleDir;
|
|
break;
|
|
case Nif::SymmetryType::Cylindrical:
|
|
explosionDir = particleDir - mCachedWorldDirection * (mCachedWorldDirection * particleDir);
|
|
explosionDir.normalize();
|
|
break;
|
|
case Nif::SymmetryType::Planar:
|
|
explosionDir = mCachedWorldDirection;
|
|
if (explosionDir * particleDir < 0)
|
|
explosionDir = -explosionDir;
|
|
break;
|
|
}
|
|
|
|
particle->addVelocity(explosionDir * mStrength * decay * dt);
|
|
}
|
|
|
|
Emitter::Emitter()
|
|
: osgParticle::Emitter()
|
|
, mFlags(0)
|
|
, mGeometryEmitterTarget(std::nullopt)
|
|
{
|
|
}
|
|
|
|
Emitter::Emitter(const Emitter& copy, const osg::CopyOp& copyop)
|
|
: osgParticle::Emitter(copy, copyop)
|
|
, mTargets(copy.mTargets)
|
|
, mPlacer(copy.mPlacer)
|
|
, mShooter(copy.mShooter)
|
|
// need a deep copy because the remainder is stored in the object
|
|
, mCounter(static_cast<osgParticle::Counter*>(copy.mCounter->clone(osg::CopyOp::DEEP_COPY_ALL)))
|
|
, mFlags(copy.mFlags)
|
|
, mGeometryEmitterTarget(copy.mGeometryEmitterTarget)
|
|
, mCachedGeometryEmitter(copy.mCachedGeometryEmitter)
|
|
{
|
|
}
|
|
|
|
Emitter::Emitter(const std::vector<int>& targets)
|
|
: mTargets(targets)
|
|
, mFlags(0)
|
|
, mGeometryEmitterTarget(std::nullopt)
|
|
{
|
|
}
|
|
|
|
void Emitter::emitParticles(double dt)
|
|
{
|
|
int n = mCounter->numParticlesToCreate(dt);
|
|
if (n == 0)
|
|
return;
|
|
|
|
osg::Matrix worldToPs;
|
|
|
|
// maybe this could be optimized by halting at the lowest common ancestor of the particle and emitter nodes
|
|
osg::NodePathList partsysNodePaths = getParticleSystem()->getParentalNodePaths();
|
|
if (!partsysNodePaths.empty())
|
|
{
|
|
osg::Matrix psToWorld = osg::computeLocalToWorld(partsysNodePaths[0]);
|
|
worldToPs = osg::Matrix::inverse(psToWorld);
|
|
}
|
|
|
|
const osg::Matrix& ltw = getLocalToWorldMatrix();
|
|
osg::Matrix emitterToPs = ltw * worldToPs;
|
|
|
|
osg::ref_ptr<osg::Vec3Array> geometryVertices = nullptr;
|
|
|
|
const bool useGeometryEmitter = mFlags & Nif::NiParticleSystemController::BSPArrayController_AtVertex;
|
|
|
|
if (useGeometryEmitter || !mTargets.empty())
|
|
{
|
|
int recIndex;
|
|
|
|
if (useGeometryEmitter)
|
|
{
|
|
if (!mGeometryEmitterTarget.has_value())
|
|
return;
|
|
|
|
recIndex = mGeometryEmitterTarget.value();
|
|
}
|
|
else
|
|
{
|
|
int randomIndex = Misc::Rng::rollClosedProbability() * (mTargets.size() - 1);
|
|
recIndex = mTargets[randomIndex];
|
|
}
|
|
|
|
// we could use a map here for faster lookup
|
|
FindGroupByRecIndex visitor(recIndex);
|
|
getParent(0)->accept(visitor);
|
|
|
|
if (!visitor.mFound)
|
|
{
|
|
Log(Debug::Info) << "Can't find emitter node" << recIndex;
|
|
return;
|
|
}
|
|
|
|
if (useGeometryEmitter)
|
|
{
|
|
if (!mCachedGeometryEmitter.lock(geometryVertices))
|
|
{
|
|
FindFirstGeometry geometryVisitor;
|
|
visitor.mFound->accept(geometryVisitor);
|
|
|
|
if (geometryVisitor.mGeometry)
|
|
{
|
|
if (auto* vertices = dynamic_cast<osg::Vec3Array*>(geometryVisitor.mGeometry->getVertexArray()))
|
|
{
|
|
mCachedGeometryEmitter = osg::observer_ptr<osg::Vec3Array>(vertices);
|
|
geometryVertices = vertices;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
osg::NodePath path = visitor.mFoundPath;
|
|
path.erase(path.begin());
|
|
if (!useGeometryEmitter && (mFlags & Nif::NiParticleSystemController::BSPArrayController_AtNode)
|
|
&& path.size())
|
|
{
|
|
osg::Matrix current;
|
|
|
|
LocalToWorldAccumulator accum(current);
|
|
accum.accumulate(path);
|
|
|
|
osg::Matrix parent = accum.mLastMatrix.value_or(current);
|
|
|
|
auto p1 = parent.getTrans();
|
|
auto p2 = current.getTrans();
|
|
current.setTrans((p2 - p1) * Misc::Rng::rollClosedProbability() + p1);
|
|
|
|
emitterToPs = current * emitterToPs;
|
|
}
|
|
else
|
|
{
|
|
emitterToPs = osg::computeLocalToWorld(path) * emitterToPs;
|
|
}
|
|
}
|
|
|
|
emitterToPs.orthoNormalize(emitterToPs);
|
|
|
|
if (useGeometryEmitter && (!geometryVertices.valid() || geometryVertices->empty()))
|
|
return;
|
|
|
|
for (int i = 0; i < n; ++i)
|
|
{
|
|
osgParticle::Particle* P = getParticleSystem()->createParticle(nullptr);
|
|
if (P)
|
|
{
|
|
if (useGeometryEmitter)
|
|
P->setPosition((*geometryVertices)[Misc::Rng::rollDice(geometryVertices->getNumElements())]);
|
|
else if (mPlacer)
|
|
mPlacer->place(P);
|
|
|
|
mShooter->shoot(P);
|
|
|
|
P->transformPositionVelocity(emitterToPs);
|
|
}
|
|
}
|
|
}
|
|
|
|
FindGroupByRecIndex::FindGroupByRecIndex(unsigned int recIndex)
|
|
: osg::NodeVisitor(TRAVERSE_ALL_CHILDREN)
|
|
, mFound(nullptr)
|
|
, mRecIndex(recIndex)
|
|
{
|
|
}
|
|
|
|
void FindGroupByRecIndex::apply(osg::Node& node)
|
|
{
|
|
applyNode(node);
|
|
}
|
|
|
|
void FindGroupByRecIndex::apply(osg::MatrixTransform& node)
|
|
{
|
|
applyNode(node);
|
|
}
|
|
|
|
void FindGroupByRecIndex::apply(osg::Geometry& node)
|
|
{
|
|
applyNode(node);
|
|
}
|
|
|
|
void FindGroupByRecIndex::applyNode(osg::Node& searchNode)
|
|
{
|
|
unsigned int recIndex;
|
|
if (searchNode.getUserValue("recIndex", recIndex) && mRecIndex == recIndex)
|
|
{
|
|
osg::Group* group = searchNode.asGroup();
|
|
if (!group)
|
|
group = searchNode.getParent(0);
|
|
|
|
mFound = group;
|
|
mFoundPath = getNodePath();
|
|
return;
|
|
}
|
|
traverse(searchNode);
|
|
}
|
|
|
|
PlanarCollider::PlanarCollider(const Nif::NiPlanarCollider* collider)
|
|
: mBounceFactor(collider->mBounceFactor)
|
|
, mExtents(collider->mExtents)
|
|
, mPosition(collider->mPosition)
|
|
, mXVector(collider->mXVector)
|
|
, mYVector(collider->mYVector)
|
|
, mPlane(-collider->mPlaneNormal, collider->mPlaneDistance)
|
|
{
|
|
}
|
|
|
|
PlanarCollider::PlanarCollider(const PlanarCollider& copy, const osg::CopyOp& copyop)
|
|
: osgParticle::Operator(copy, copyop)
|
|
, mBounceFactor(copy.mBounceFactor)
|
|
, mExtents(copy.mExtents)
|
|
, mPosition(copy.mPosition)
|
|
, mPositionInParticleSpace(copy.mPositionInParticleSpace)
|
|
, mXVector(copy.mXVector)
|
|
, mXVectorInParticleSpace(copy.mXVectorInParticleSpace)
|
|
, mYVector(copy.mYVector)
|
|
, mYVectorInParticleSpace(copy.mYVectorInParticleSpace)
|
|
, mPlane(copy.mPlane)
|
|
, mPlaneInParticleSpace(copy.mPlaneInParticleSpace)
|
|
{
|
|
}
|
|
|
|
void PlanarCollider::beginOperate(osgParticle::Program* program)
|
|
{
|
|
mPositionInParticleSpace = mPosition;
|
|
mPlaneInParticleSpace = mPlane;
|
|
mXVectorInParticleSpace = mXVector;
|
|
mYVectorInParticleSpace = mYVector;
|
|
if (program->getReferenceFrame() == osgParticle::ParticleProcessor::ABSOLUTE_RF)
|
|
{
|
|
mPositionInParticleSpace = program->transformLocalToWorld(mPosition);
|
|
mPlaneInParticleSpace.transform(program->getLocalToWorldMatrix());
|
|
mXVectorInParticleSpace = program->rotateLocalToWorld(mXVector);
|
|
mYVectorInParticleSpace = program->rotateLocalToWorld(mYVector);
|
|
}
|
|
}
|
|
|
|
void PlanarCollider::operate(osgParticle::Particle* particle, double dt)
|
|
{
|
|
// Does the particle in question move towards the collider?
|
|
float velDotProduct = particle->getVelocity() * mPlaneInParticleSpace.getNormal();
|
|
if (velDotProduct <= 0)
|
|
return;
|
|
|
|
// Does it intersect the collider's plane?
|
|
osg::BoundingSphere bs(particle->getPosition(), 0.f);
|
|
if (mPlaneInParticleSpace.intersect(bs) != 1)
|
|
return;
|
|
|
|
// Is it inside the collider's bounds?
|
|
osg::Vec3f relativePos = particle->getPosition() - mPositionInParticleSpace;
|
|
float xDotProduct = relativePos * mXVectorInParticleSpace;
|
|
float yDotProduct = relativePos * mYVectorInParticleSpace;
|
|
if (-mExtents.x() * 0.5f > xDotProduct || mExtents.x() * 0.5f < xDotProduct)
|
|
return;
|
|
if (-mExtents.y() * 0.5f > yDotProduct || mExtents.y() * 0.5f < yDotProduct)
|
|
return;
|
|
|
|
// Deflect the particle
|
|
osg::Vec3 reflectedVelocity = particle->getVelocity() - mPlaneInParticleSpace.getNormal() * (2 * velDotProduct);
|
|
reflectedVelocity *= mBounceFactor;
|
|
particle->setVelocity(reflectedVelocity);
|
|
}
|
|
|
|
SphericalCollider::SphericalCollider(const Nif::NiSphericalCollider* collider)
|
|
: mBounceFactor(collider->mBounceFactor)
|
|
, mSphere(collider->mCenter, collider->mRadius)
|
|
{
|
|
}
|
|
|
|
SphericalCollider::SphericalCollider()
|
|
: mBounceFactor(1.0f)
|
|
{
|
|
}
|
|
|
|
SphericalCollider::SphericalCollider(const SphericalCollider& copy, const osg::CopyOp& copyop)
|
|
: osgParticle::Operator(copy, copyop)
|
|
, mBounceFactor(copy.mBounceFactor)
|
|
, mSphere(copy.mSphere)
|
|
, mSphereInParticleSpace(copy.mSphereInParticleSpace)
|
|
{
|
|
}
|
|
|
|
void SphericalCollider::beginOperate(osgParticle::Program* program)
|
|
{
|
|
mSphereInParticleSpace = mSphere;
|
|
if (program->getReferenceFrame() == osgParticle::ParticleProcessor::ABSOLUTE_RF)
|
|
mSphereInParticleSpace.center() = program->transformLocalToWorld(mSphereInParticleSpace.center());
|
|
}
|
|
|
|
void SphericalCollider::operate(osgParticle::Particle* particle, double dt)
|
|
{
|
|
osg::Vec3f cent
|
|
= (particle->getPosition() - mSphereInParticleSpace.center()); // vector from sphere center to particle
|
|
|
|
bool insideSphere = cent.length2() <= mSphereInParticleSpace.radius2();
|
|
|
|
if (insideSphere
|
|
|| (cent * particle->getVelocity()
|
|
< 0.0f)) // if outside, make sure the particle is flying towards the sphere
|
|
{
|
|
// Collision test (finding point of contact) is performed by solving a quadratic equation:
|
|
// ||vec(cent) + vec(vel)*k|| = R /^2
|
|
// k^2 + 2*k*(vec(cent)*vec(vel))/||vec(vel)||^2 + (||vec(cent)||^2 - R^2)/||vec(vel)||^2 = 0
|
|
|
|
float b = -(cent * particle->getVelocity()) / particle->getVelocity().length2();
|
|
|
|
osg::Vec3f u = cent + particle->getVelocity() * b;
|
|
|
|
if (insideSphere || (u.length2() < mSphereInParticleSpace.radius2()))
|
|
{
|
|
float d = (mSphereInParticleSpace.radius2() - u.length2()) / particle->getVelocity().length2();
|
|
float k = insideSphere ? (std::sqrt(d) + b) : (b - std::sqrt(d));
|
|
|
|
if (k < dt)
|
|
{
|
|
// collision detected; reflect off the tangent plane
|
|
osg::Vec3f contact = particle->getPosition() + particle->getVelocity() * k;
|
|
|
|
osg::Vec3 normal = (contact - mSphereInParticleSpace.center());
|
|
normal.normalize();
|
|
|
|
float dotproduct = particle->getVelocity() * normal;
|
|
|
|
osg::Vec3 reflectedVelocity = particle->getVelocity() - normal * (2 * dotproduct);
|
|
reflectedVelocity *= mBounceFactor;
|
|
particle->setVelocity(reflectedVelocity);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|