mirror of
				https://github.com/OpenMW/openmw.git
				synced 2025-11-04 11:56:39 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			504 lines
		
	
	
	
		
			15 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			504 lines
		
	
	
	
		
			15 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
#include "particle.hpp"
 | 
						|
 | 
						|
#include <limits>
 | 
						|
 | 
						|
#include <osg/Version>
 | 
						|
#include <osg/MatrixTransform>
 | 
						|
#include <osg/Geometry>
 | 
						|
#include <osg/ValueObject>
 | 
						|
 | 
						|
#include <components/debug/debuglog.hpp>
 | 
						|
#include <components/misc/rng.hpp>
 | 
						|
#include <components/nif/controlled.hpp>
 | 
						|
#include <components/nif/data.hpp>
 | 
						|
 | 
						|
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 ©, const osg::CopyOp ©op)
 | 
						|
    : 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 OSG_MIN_VERSION_REQUIRED(3, 5, 6)
 | 
						|
    if(state.useVertexArrayObject(getUseVertexArrayObject()))
 | 
						|
    {
 | 
						|
        state.getCurrentVertexArrayState()->assignNormalArrayDispatcher();
 | 
						|
        state.getCurrentVertexArrayState()->setNormalArray(state, mNormalArray);
 | 
						|
    }
 | 
						|
    else
 | 
						|
    {
 | 
						|
        state.getAttributeDispatchers().activateNormalArray(mNormalArray);
 | 
						|
    }
 | 
						|
#else
 | 
						|
     state.Normal(0.3, 0.3, 0.3);
 | 
						|
#endif
 | 
						|
     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 ©, const osg::CopyOp ©op)
 | 
						|
    : 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 ©, const osg::CopyOp ©op)
 | 
						|
    : 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(static_cast<ForceType>(gravity->mType))
 | 
						|
    , mPosition(gravity->mPosition)
 | 
						|
    , mDirection(gravity->mDirection)
 | 
						|
    , mDecay(gravity->mDecay)
 | 
						|
{
 | 
						|
}
 | 
						|
 | 
						|
GravityAffector::GravityAffector()
 | 
						|
    : mForce(0), mType(Type_Wind), mDecay(0.f)
 | 
						|
{
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
GravityAffector::GravityAffector(const GravityAffector ©, const osg::CopyOp ©op)
 | 
						|
    : 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);
 | 
						|
 | 
						|
    if (mType == Type_Point || mDecay != 0.f) // we don't need the position for Wind gravity, except if decay is being applied
 | 
						|
        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 Type_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 Type_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;
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
Emitter::Emitter()
 | 
						|
    : osgParticle::Emitter()
 | 
						|
{
 | 
						|
}
 | 
						|
 | 
						|
Emitter::Emitter(const Emitter ©, const osg::CopyOp ©op)
 | 
						|
    : 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)))
 | 
						|
{
 | 
						|
}
 | 
						|
 | 
						|
Emitter::Emitter(const std::vector<int> &targets)
 | 
						|
    : mTargets(targets)
 | 
						|
{
 | 
						|
}
 | 
						|
 | 
						|
void Emitter::setShooter(osgParticle::Shooter *shooter)
 | 
						|
{
 | 
						|
    mShooter = shooter;
 | 
						|
}
 | 
						|
 | 
						|
void Emitter::setPlacer(osgParticle::Placer *placer)
 | 
						|
{
 | 
						|
    mPlacer = placer;
 | 
						|
}
 | 
						|
 | 
						|
void Emitter::setCounter(osgParticle::Counter *counter)
 | 
						|
{
 | 
						|
    mCounter = counter;
 | 
						|
}
 | 
						|
 | 
						|
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;
 | 
						|
 | 
						|
    if (!mTargets.empty())
 | 
						|
    {
 | 
						|
        int randomIndex = Misc::Rng::rollClosedProbability() * (mTargets.size() - 1);
 | 
						|
        int randomRecIndex = mTargets[randomIndex];
 | 
						|
 | 
						|
        // we could use a map here for faster lookup
 | 
						|
        FindGroupByRecIndex visitor(randomRecIndex);
 | 
						|
        getParent(0)->accept(visitor);
 | 
						|
 | 
						|
        if (!visitor.mFound)
 | 
						|
        {
 | 
						|
            Log(Debug::Info) << "Can't find emitter node" << randomRecIndex;
 | 
						|
            return;
 | 
						|
        }
 | 
						|
 | 
						|
        osg::NodePath path = visitor.mFoundPath;
 | 
						|
        path.erase(path.begin());
 | 
						|
        emitterToPs = osg::computeLocalToWorld(path) * emitterToPs;
 | 
						|
    }
 | 
						|
 | 
						|
    emitterToPs.orthoNormalize(emitterToPs);
 | 
						|
 | 
						|
    for (int i=0; i<n; ++i)
 | 
						|
    {
 | 
						|
        osgParticle::Particle* P = getParticleSystem()->createParticle(nullptr);
 | 
						|
        if (P)
 | 
						|
        {
 | 
						|
            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)
 | 
						|
    , mPlane(-collider->mPlaneNormal, collider->mPlaneDistance)
 | 
						|
{
 | 
						|
}
 | 
						|
 | 
						|
PlanarCollider::PlanarCollider()
 | 
						|
    : mBounceFactor(0.f)
 | 
						|
{
 | 
						|
}
 | 
						|
 | 
						|
PlanarCollider::PlanarCollider(const PlanarCollider ©, const osg::CopyOp ©op)
 | 
						|
    : osgParticle::Operator(copy, copyop)
 | 
						|
    , mBounceFactor(copy.mBounceFactor)
 | 
						|
    , mPlane(copy.mPlane)
 | 
						|
    , mPlaneInParticleSpace(copy.mPlaneInParticleSpace)
 | 
						|
{
 | 
						|
}
 | 
						|
 | 
						|
void PlanarCollider::beginOperate(osgParticle::Program *program)
 | 
						|
{
 | 
						|
    mPlaneInParticleSpace = mPlane;
 | 
						|
    if (program->getReferenceFrame() == osgParticle::ParticleProcessor::ABSOLUTE_RF)
 | 
						|
        mPlaneInParticleSpace.transform(program->getLocalToWorldMatrix());
 | 
						|
}
 | 
						|
 | 
						|
void PlanarCollider::operate(osgParticle::Particle *particle, double dt)
 | 
						|
{
 | 
						|
    float dotproduct = particle->getVelocity() * mPlaneInParticleSpace.getNormal();
 | 
						|
 | 
						|
    if (dotproduct > 0)
 | 
						|
    {
 | 
						|
        osg::BoundingSphere bs(particle->getPosition(), 0.f);
 | 
						|
        if (mPlaneInParticleSpace.intersect(bs) == 1)
 | 
						|
        {
 | 
						|
            osg::Vec3 reflectedVelocity = particle->getVelocity() - mPlaneInParticleSpace.getNormal() * (2 * dotproduct);
 | 
						|
            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);
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
}
 |