// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/> // Copyright (C) 2010 Winch Gate Property Limited // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. #include "std3d.h" #include "nel/3d/ps_zone.h" #include "nel/3d/vertex_buffer.h" #include "nel/3d/index_buffer.h" #include "nel/3d/material.h" #include "nel/3d/ps_util.h" #include "nel/3d/dru.h" #include "nel/3d/particle_system.h" #include "nel/misc/plane.h" // tmp #include "nel/3d/particle_system_model.h" #include <cmath> #include <limits> namespace NL3D { /* * Constructor */ CPSZone::CPSZone() : _BounceFactor(1.f), _CollisionBehaviour(bounce) { NL_PS_FUNC(CPSZone_CPSZone) } void CPSZone::serial(NLMISC::IStream &f) throw(NLMISC::EStream) { NL_PS_FUNC(CPSZone_serial) f.serialVersion(1); CPSTargetLocatedBindable::serial(f); f.serialEnum(_CollisionBehaviour); f.serial(_BounceFactor); if (f.isReading()) { for (TTargetCont::iterator it = _Targets.begin(); it != _Targets.end(); ++it) { // though this is not a force, this prevent parametric motion (*it)->addNonIntegrableForceRef(); } } } /** Add a new type of located for this zone to apply on. * We override this to queery the target to allocate the CollisionInfo attribute */ void CPSZone::attachTarget(CPSLocated *ptr) { NL_PS_FUNC(CPSZone_attachTarget) CPSTargetLocatedBindable::attachTarget(ptr); ptr->queryCollisionInfo(); ptr->addNonIntegrableForceRef(); } /// inherit from CPSTargetLocatedBindable. Its called when one of the targets has been detroyed void CPSZone::releaseTargetRsc(CPSLocated *target) { NL_PS_FUNC(CPSZone_releaseTargetRsc) // tell the target that we were using collision infos and that we won't use them anymore target->releaseCollisionInfo(); target->releaseNonIntegrableForceRef(); } void CPSZone::step(TPSProcessPass pass) { NL_PS_FUNC(CPSZone_step) // for zone, the PSCollision pass and the PSToolRenderPass are processed switch(pass) { case PSToolRender: show(); break; default: break; } } // build a basis with K = the normal of the plane CMatrix CPSZonePlane::buildBasis(uint32 index) const { NL_PS_FUNC(CPSZonePlane_buildBasis) CMatrix m; m.setPos(_Owner->getPos()[index]); CPSUtil::buildSchmidtBasis(_Normal[index], m); return m; } /// this compute a new speed vector, so that the located will reach the correct position at the next speed integration /// this create the illusion of collision /*void CPSZone::bounce(uint32 locatedIndex, const CVector &bouncePoint, const CVector &surfNormal, float elasticity, TAnimationTime ellapsedTime) { CVector &speed = _Owner->getSpeed()[locatedIndex]; const CVector &pos = _Owner->getPos()[locatedIndex]; CVector &bounceVect = elasticity * (speed - 2.0f * (speed * surfNormal) * surfNormal); // speed vector after collision // now check where the located will be after integration CVector d = bouncePoint - pos; TAnimationTime collideDelay = speed.norm() / d.norm(); CVector finalPos = bouncePoint + (ellapsedTime - collideDelay) * bounceVect; // now, we must have pos + ellapsedTime * newSpeed = finalPos // newSpeed = alpha * (finalPos - pos) // so alpha = 1 / ellapsedTime speed = (1.0f / ellapsedTime) * (finalPos - pos); }*/ void CPSZonePlane::show() { NL_PS_FUNC(CPSZonePlane_show) const float planeSize = 2.0f; setupDriverModelMatrix(); IDriver *driver = getDriver(); uint k = 0; setupDriverModelMatrix(); CPSLocated *loc; uint32 index; CPSLocatedBindable *lb; _Owner->getOwner()->getCurrentEditedElement(loc, index, lb); for (TPSAttribVector::const_iterator it = _Owner->getPos().begin(); it != _Owner->getPos().end(); ++it, ++k) { const CRGBA col = ((lb == NULL || this == lb) && loc == _Owner && index == k ? CRGBA::Red : CRGBA(127, 127, 127)); CMatrix mat = buildBasis(k); CPSUtil::displayBasis(getDriver(), getLocalToWorldMatrix(), mat, 1.f, *getFontGenerator(), *getFontManager()); setupDriverModelMatrix(); CDRU::drawLine(*it + (planeSize + 3) * mat.getI() + planeSize * mat.getJ(), *it - (planeSize + 3) * mat.getI() + planeSize * mat.getJ(), col, *driver); CDRU::drawLine(*it + (planeSize + 3) * mat.getI() - planeSize * mat.getJ(), *it - (planeSize + 3) * mat.getI() - planeSize * mat.getJ(), col, *driver); CDRU::drawLine(*it + planeSize * mat.getI() + (planeSize + 3) * mat.getJ(), *it + planeSize * mat.getI() - (planeSize + 3) * mat.getJ(), col, *driver); CDRU::drawLine(*it - planeSize * mat.getI() + (planeSize + 3) * mat.getJ(), *it - planeSize * mat.getI() - (planeSize + 3) * mat.getJ(), col, *driver); } } void CPSZonePlane::resize(uint32 size) { NL_PS_FUNC(CPSZonePlane_resize) nlassert(size < (1 << 16)); _Normal.resize(size); } void CPSZonePlane::newElement(const CPSEmitterInfo &info) { NL_PS_FUNC(CPSZonePlane_newElement) nlassert(_Normal.getSize() != _Normal.getMaxSize()); _Normal.insert(CVector(0, 0, 1)); } void CPSZonePlane::deleteElement(uint32 index) { NL_PS_FUNC(CPSZonePlane_deleteElement) _Normal.remove(index); } void CPSZonePlane::computeCollisions(CPSLocated &target, uint firstInstanceIndex, const NLMISC::CVector *posBefore, const NLMISC::CVector *posAfter) { NL_PS_FUNC(CPSZonePlane_computeCollisions) MINI_TIMER(PSStatsZonePlane) // for each target, we must check whether they are going through the plane // if so they must bounce TPSAttribVector::const_iterator planePosIt, planePosEnd, normalIt; CPSCollisionInfo ci; // cycle through the planes planePosEnd = _Owner->getPos().end(); for (planePosIt = _Owner->getPos().begin(), normalIt = _Normal.begin(); planePosIt != planePosEnd; ++planePosIt, ++normalIt) { // we must setup the plane in the good basis const CMatrix &m = CPSLocated::getConversionMatrix(&target, this->_Owner); const float epsilon = 0.5f * PSCollideEpsilon; NLMISC::CPlane p; p.make(m.mulVector(*normalIt), m * (*planePosIt)); // deals with each particle const NLMISC::CVector *itPosBefore = posBefore + firstInstanceIndex; const NLMISC::CVector *itPosBeforeEnd = posBefore + target.getSize(); const NLMISC::CVector *itPosAfter = posAfter + firstInstanceIndex; while (itPosBefore != itPosBeforeEnd) { float posSide = p * *itPosBefore; float negSide = p * *itPosAfter; if (posSide >= - epsilon && negSide <= epsilon) { float alpha; if (fabsf(posSide - negSide) > std::numeric_limits<float>::min()) { alpha = posSide / (posSide - negSide); } else { alpha = 0.f; } CVector startEnd = alpha * (*itPosAfter - *itPosBefore); ci.Dist = startEnd.norm(); // we translate the particle from an epsilon so that it won't get hooked to the plane ci.NewPos = *itPosBefore + startEnd + PSCollideEpsilon * p.getNormal(); const CVector &speed = target.getSpeed()[(uint32)(itPosBefore - posBefore)]; ci.NewSpeed = _BounceFactor * (speed - 2.0f * (speed * p.getNormal()) * p.getNormal()); ci.CollisionZone = this; CPSLocated::_Collisions[itPosBefore - posBefore].update(ci); } ++ itPosBefore; ++ itPosAfter; } } } void CPSZonePlane::setMatrix(uint32 index, const CMatrix &m) { NL_PS_FUNC(CPSZonePlane_setMatrix) nlassert(index < _Normal.getSize()); _Normal[index] = m.getK(); _Owner->getPos()[index] = m.getPos(); } CMatrix CPSZonePlane::getMatrix(uint32 index) const { NL_PS_FUNC(CPSZonePlane_getMatrix) return buildBasis(index); } CVector CPSZonePlane::getNormal(uint32 index) { NL_PS_FUNC(CPSZonePlane_getNormal) return _Normal[index]; } void CPSZonePlane::setNormal(uint32 index, CVector n) { NL_PS_FUNC(CPSZonePlane_setNormal) _Normal[index] = n; } void CPSZonePlane::serial(NLMISC::IStream &f) throw(NLMISC::EStream) { NL_PS_FUNC(CPSZonePlane_serial) f.serialVersion(1); CPSZone::serial(f); f.serial(_Normal); } /////////////////////////// // sphere implementation // /////////////////////////// void CPSZoneSphere::computeCollisions(CPSLocated &target, uint firstInstanceIndex, const NLMISC::CVector *posBefore, const NLMISC::CVector *posAfter) { NL_PS_FUNC(CPSZoneSphere_computeCollisions) MINI_TIMER(PSStatsZoneSphere) // for each target, we must check whether they are going through the plane // if so they must bounce TPSAttribRadiusPair::const_iterator radiusIt = _Radius.begin(); TPSAttribVector::const_iterator spherePosIt, spherePosEnd; CPSCollisionInfo ci; float rOut, rIn; // cycle through the spheres spherePosEnd = _Owner->getPos().end(); for (spherePosIt = _Owner->getPos().begin(), radiusIt = _Radius.begin(); spherePosIt != spherePosEnd; ++spherePosIt, ++radiusIt) { // we must setup the sphere in the good basis const CMatrix &m = CPSLocated::getConversionMatrix(&target, this->_Owner); CVector center = m * *spherePosIt; // deals with each particle const NLMISC::CVector *itPosBefore = posBefore + firstInstanceIndex; const NLMISC::CVector *itPosBeforeEnd = posBefore + target.getSize(); const NLMISC::CVector *itPosAfter = posAfter + firstInstanceIndex; while (itPosBefore != itPosBeforeEnd) { // check whether the located is going through the sphere // we don't use raytracing for now because it is too slow ... rOut = (*itPosBefore - center) * (*itPosBefore - center); // initial position outside the sphere ? if (rOut > radiusIt->R2) { rIn = (*itPosAfter - center) * (*itPosAfter - center); const CVector &pos = *itPosBefore; const CVector &dest = *itPosAfter; const CVector D = dest - pos; // final position inside the sphere ? if ( rIn <= radiusIt->R2) { // discriminant of the intersection equation const float b = 2.f * (pos * D - D * center), a = D * D , c = (pos * pos) + (center * center) - 2.f * (pos * center) - radiusIt->R2; float d = b * b - 4 * a * c; if (d > 0.f) { d = sqrtf(d); // roots of the equation, we take the smallest const float r1 = .5f * (-b + 2.f * d) * a, r2 = .5f * (-b - 2.f * d) * a; const float r = std::min(r1, r2); // collision point const CVector C = pos + r * D; const float alpha = ((C - pos) * D) * a; const CVector startEnd = alpha * (dest - pos); CVector normal = C - center; normal = normal * (1.f / radiusIt->R); ci.Dist = startEnd.norm(); // we translate the particle from an epsilon so that it won't get hooked to the sphere ci.NewPos = pos + startEnd + PSCollideEpsilon * normal; const CVector &speed = target.getSpeed()[(uint32)(itPosBefore - posBefore)]; ci.NewSpeed = _BounceFactor * (speed - 2.0f * (speed * normal) * normal); ci.CollisionZone = this; CPSLocated::_Collisions[itPosBefore - posBefore].update(ci); } } } ++ itPosBefore; ++ itPosAfter; } } } void CPSZoneSphere::show() { NL_PS_FUNC(CPSZoneSphere_show) CPSLocated *loc; uint32 index; CPSLocatedBindable *lb; _Owner->getOwner()->getCurrentEditedElement(loc, index, lb); TPSAttribRadiusPair::const_iterator radiusIt = _Radius.begin(); TPSAttribVector::const_iterator posIt = _Owner->getPos().begin(), endPosIt = _Owner->getPos().end(); setupDriverModelMatrix(); for (uint k = 0; posIt != endPosIt; ++posIt, ++radiusIt, ++k) { const CRGBA col = ((lb == NULL || this == lb) && loc == _Owner && index == k ? CRGBA::Red : CRGBA(127, 127, 127)); CPSUtil::displaySphere(*getDriver(), radiusIt->R, *posIt, 5, col); } } void CPSZoneSphere::setMatrix(uint32 index, const CMatrix &m) { NL_PS_FUNC(CPSZoneSphere_setMatrix) nlassert(index < _Radius.getSize()); // compute new pos _Owner->getPos()[index] = m.getPos(); } CMatrix CPSZoneSphere::getMatrix(uint32 index) const { NL_PS_FUNC(CPSZoneSphere_getMatrix) nlassert(index < _Radius.getSize()); CMatrix m; m.identity(); m.translate(_Owner->getPos()[index]); return m; } void CPSZoneSphere::setScale(uint32 k, float scale) { NL_PS_FUNC(CPSZoneSphere_setScale) _Radius[k].R = scale; _Radius[k].R2 = scale * scale; } CVector CPSZoneSphere::getScale(uint32 k) const { NL_PS_FUNC(CPSZoneSphere_getScale) return CVector(_Radius[k].R, _Radius[k].R, _Radius[k].R); } void CPSZoneSphere::serial(NLMISC::IStream &f) throw(NLMISC::EStream) { NL_PS_FUNC(CPSZoneSphere_serial) f.serialVersion(1); CPSZone::serial(f); f.serial(_Radius); } void CPSZoneSphere::resize(uint32 size) { NL_PS_FUNC(CPSZoneSphere_resize) nlassert(size < (1 << 16)); _Radius.resize(size); } void CPSZoneSphere::newElement(const CPSEmitterInfo &info) { NL_PS_FUNC(CPSZoneSphere_newElement) CRadiusPair rp; rp.R = rp.R2 = 1.f; nlassert(_Radius.getSize() != _Radius.getMaxSize()); _Radius.insert(rp); } void CPSZoneSphere::deleteElement(uint32 index) { NL_PS_FUNC(CPSZoneSphere_deleteElement) _Radius.remove(index); } //////////////////////////////// // CPSZoneDisc implementation // //////////////////////////////// void CPSZoneDisc::computeCollisions(CPSLocated &target, uint firstInstanceIndex, const NLMISC::CVector *posBefore, const NLMISC::CVector *posAfter) { NL_PS_FUNC(CPSZoneDisc_computeCollisions) MINI_TIMER(PSStatsZoneDisc) // for each target, we must check whether they are going through the disc // if so they must bounce TPSAttribVector::const_iterator discPosIt, discPosEnd, normalIt; TPSAttribRadiusPair::const_iterator radiusIt; CPSCollisionInfo ci; // the square of radius at the hit point float hitRadius2; // alpha is the ratio that gives the percent of endPos - startPos that hit the disc CVector center; // cycle through the disc discPosEnd = _Owner->getPos().end(); for (discPosIt = _Owner->getPos().begin(), radiusIt = _Radius.begin(), normalIt = _Normal.begin(); discPosIt != discPosEnd; ++discPosIt, ++normalIt, ++radiusIt) { // we must setup the disc in the good basis const CMatrix &m = CPSLocated::getConversionMatrix(&target, this->_Owner); NLMISC::CPlane p; center = m * (*discPosIt); p.make(m.mulVector(*normalIt), center); // deals with each particle const float epsilon = 0.5f * PSCollideEpsilon; // deals with each particle const NLMISC::CVector *itPosBefore = posBefore + firstInstanceIndex; const NLMISC::CVector *itPosBeforeEnd = posBefore + target.getSize(); const NLMISC::CVector *itPosAfter = posAfter + firstInstanceIndex; while (itPosBefore != itPosBeforeEnd) { float posSide = p * *itPosBefore; float negSide = p * *itPosAfter; if (posSide >= - epsilon && negSide <= epsilon) { float alpha; if (fabsf(posSide - negSide) > std::numeric_limits<float>::min()) { alpha = posSide / (posSide - negSide); } else { alpha = 0.f; } CVector startEnd = alpha * (*itPosAfter - *itPosBefore); ci.Dist = startEnd.norm(); // we translate the particle from an epsilon so that it won't get hooked to the disc ci.NewPos = *itPosBefore + startEnd + PSCollideEpsilon * p.getNormal(); // now, check the collision pos against radius hitRadius2 = (ci.NewPos - center) * (ci.NewPos - center); if (hitRadius2 < radiusIt->R2) // check collision against disc { const CVector &speed = target.getSpeed()[(uint32)(itPosBefore - posBefore)]; ci.NewSpeed = _BounceFactor * (speed - 2.0f * (speed * p.getNormal()) * p.getNormal()); ci.CollisionZone = this; CPSLocated::_Collisions[itPosBefore - posBefore].update(ci); } } ++ itPosBefore; ++ itPosAfter; } } } void CPSZoneDisc::show() { NL_PS_FUNC(CPSZoneDisc_show) TPSAttribRadiusPair::const_iterator radiusIt = _Radius.begin(); TPSAttribVector::const_iterator posIt = _Owner->getPos().begin(), endPosIt = _Owner->getPos().end() , normalIt = _Normal.begin(); setupDriverModelMatrix(); CMatrix mat; CPSLocated *loc; uint32 index; CPSLocatedBindable *lb; _Owner->getOwner()->getCurrentEditedElement(loc, index, lb); for (uint k = 0; posIt != endPosIt; ++posIt, ++radiusIt, ++normalIt, ++k) { const CRGBA col = ((lb == NULL || this == lb) && loc == _Owner && index == k ? CRGBA::Red : CRGBA(127, 127, 127)); CPSUtil::buildSchmidtBasis(*normalIt, mat); CPSUtil::displayDisc(*getDriver(), radiusIt->R, *posIt, mat, 32, col); mat.setPos(*posIt); CPSUtil::displayBasis(getDriver() ,getLocalToWorldMatrix(), mat, 1.f, *getFontGenerator(), *getFontManager()); setupDriverModelMatrix(); } } void CPSZoneDisc::setMatrix(uint32 index, const CMatrix &m) { NL_PS_FUNC(CPSZoneDisc_setMatrix) nlassert(index < _Radius.getSize()); // compute new pos _Owner->getPos()[index] = m.getPos(); // compute new normal _Normal[index] = m.getK(); } CMatrix CPSZoneDisc::getMatrix(uint32 index) const { NL_PS_FUNC(CPSZoneDisc_getMatrix) CMatrix m, b; m.translate(_Owner->getPos()[index]); CPSUtil::buildSchmidtBasis(_Normal[index], b); m = m * b; return m; } CVector CPSZoneDisc::getNormal(uint32 index) { NL_PS_FUNC(CPSZoneDisc_getNormal) return _Normal[index]; } void CPSZoneDisc::setNormal(uint32 index, CVector n) { NL_PS_FUNC(CPSZoneDisc_setNormal) _Normal[index] = n; } void CPSZoneDisc::setScale(uint32 k, float scale) { NL_PS_FUNC(CPSZoneDisc_setScale) _Radius[k].R = scale; _Radius[k].R2 = scale * scale; } CVector CPSZoneDisc::getScale(uint32 k) const { NL_PS_FUNC(CPSZoneDisc_getScale) return CVector(_Radius[k].R, _Radius[k].R, _Radius[k].R); } void CPSZoneDisc::serial(NLMISC::IStream &f) throw(NLMISC::EStream) { NL_PS_FUNC(CPSZoneDisc_serial) f.serialVersion(1); CPSZone::serial(f); f.serial(_Normal); f.serial(_Radius); } void CPSZoneDisc::resize(uint32 size) { NL_PS_FUNC(CPSZoneDisc_resize) nlassert(size < (1 << 16)); _Radius.resize(size); _Normal.resize(size); } void CPSZoneDisc::newElement(const CPSEmitterInfo &info) { NL_PS_FUNC(CPSZoneDisc_newElement) CRadiusPair rp; rp.R = rp.R2 = 1.f; nlassert(_Radius.getSize() != _Radius.getMaxSize()); _Radius.insert(rp); _Normal.insert(CVector::K); } void CPSZoneDisc::deleteElement(uint32 index) { NL_PS_FUNC(CPSZoneDisc_deleteElement) _Radius.remove(index); _Normal.remove(index); } //////////////////////////////////// // CPSZoneCylinder implementation // //////////////////////////////////// /* void CPSZoneCylinder::performMotion(TAnimationTime ellapsedTime) { TPSAttribVector::const_iterator dimIt = _Dim.begin(); CPSAttrib<CPlaneBasis>::const_iterator basisIt = _Basis.begin(); TPSAttribVector::const_iterator cylinderPosIt, cylinderPosEnd, targetPosIt, targetPosEnd; CVector dest; CPSCollisionInfo ci; CVector startEnd; uint32 k; const TPSAttribVector *speedAttr; for (TTargetCont::iterator it = _Targets.begin(); it != _Targets.end(); ++it) { speedAttr = &((*it)->getSpeed()); // cycle through the cylinders cylinderPosEnd = _Owner->getPos().end(); for (cylinderPosIt = _Owner->getPos().begin(); cylinderPosIt != cylinderPosEnd ; ++cylinderPosIt, ++dimIt, ++basisIt) { // we must setup the cylinder in the good basis const CMatrix &m = CPSLocated::getConversionMatrix(*it, this->_Owner); // compute the new center pos CVector center = m * *cylinderPosIt; // compute a basis for the cylinder CVector I = m.mulVector(basisIt->X); CVector J = m.mulVector(basisIt->Y); CVector K = m.mulVector(basisIt->X ^ basisIt->Y); // the pos projected (and scale) over the plane basis of the cylinder, the pos minus the center CVector projectedPos, tPos; // the same, but with the final position CVector destProjectedPos, destTPos; // deals with each particle targetPosEnd = (*it)->getPos().end(); for (targetPosIt = (*it)->getPos().begin(), k = 0; targetPosIt != targetPosEnd; ++targetPosIt, ++k) { const CVector &speed = (*speedAttr)[k]; const CVector &pos = *targetPosIt; // check whether current pos was outside the cylinder tPos = pos - center; projectedPos = (1 / dimIt->x) * (I * tPos) * I + (1 / dimIt->y) * (J * tPos) * J; if (! ( ((tPos * K) < dimIt->z) && ((tPos * K) > -dimIt->z) && (projectedPos * projectedPos < 1.f) ) ) { dest = pos + ellapsedTime * speed; destTPos = dest - center; destProjectedPos = (1.f / dimIt->x) * (I * tPos) * I + (1.f / dimIt->y) * (J * tPos) * J; // test whether the new position is inside the cylinder if (! ( ((destTPos * K) < dimIt->z) && ((destTPos * K) > -dimIt->z) && (destProjectedPos * destProjectedPos < 1.f) ) ) { // now, detect the closest hit point (the smallest alpha, with alpha, the percent of the move vector // to reach the hit point) const float epsilon = 10E-6f; float alphaTop, alphaBottom, alphaCyl; const float denum = (dest - pos) * K; // top plane if (fabs(denum) < epsilon) { alphaTop = (dimIt->z - (tPos * K)) / denum; if (alphaTop < 0.f) alphaTop = 1.f; } else { alphaTop = 1.f; } // bottom plane if (fabs(denum) < epsilon) { alphaBottom = (- dimIt->z - (tPos * K)) / denum; if (alphaBottom < 0.f) alphaBottom = 1.f; } else { alphaBottom = 1.f; } // cylinder //expressed the src and dest positions in the cylinder basis const float ox = tPos * I, oy = tPos * J, dx = (destTPos - tPos) * I, dy = (destTPos - tPos) * J; // coefficients of the equation : a * alpha ^ 2 + b * alpha + c = 0 const float a = (dx * dx) / (dimIt->x * dimIt->x) + (dy * dy) / (dimIt->y * dimIt->y); const float b = 2.f * (ox * dx) / (dimIt->x * dimIt->x) + (oy * dy) / (dimIt->y * dimIt->y); const float c = ox * ox + oy * oy - 1; // discriminant const float delta = b * b - 4.f * a * c; if (delta < epsilon) { alphaCyl = 1.f; } else { const float deltaRoot = sqrtf(delta); const float r1 = (- b - deltaRoot) / (2.f / a); const float r2 = (- b - deltaRoot) / (2.f / a); if (r1 < 0.f) alphaCyl = r2; else if (r2 < 0.f) alphaCyl = r1; else alphaCyl = r1 < r2 ? r1 : r2; } // now, choose the minimum positive dist if (alphaTop < alphaBottom && alphaTop < alphaCyl) { // collision with the top plane CVector startEnd = alphaTop * (dest - pos); ci.newPos = pos + startEnd + PSCollideEpsilon * K; ci.dist = startEnd.norm(); ci.newSpeed = (-2.f * (speed * K)) * K + speed; ci.collisionZone = this; (*it)->collisionUpdate(ci, k); } else if (alphaBottom < alphaCyl) { // collision with the bottom plane CVector startEnd = alphaBottom * (dest - pos); ci.newPos = pos + startEnd - PSCollideEpsilon * K; ci.dist = startEnd.norm(); ci.newSpeed = (-2.f * (speed * K)) * K + speed; ci.collisionZone = this; (*it)->collisionUpdate(ci, k); } else { // collision with the cylinder CVector startEnd = alphaCyl * (dest - pos); // normal at the hit point. It is the gradient of the implicit equation x^2 / a^2 + y^2 / b^2 - R^ 2= 0 // so we got unormalized n = (2 x / a ^ 2, 2 y / b ^ 2, 0) in the basis of the cylinder // As we'll normalize it, we don't need the 2 factor float px = ox + alphaCyl * dx; float py = oy + alphaCyl * dy; CVector normal = px / (dimIt->x * dimIt->x) * I + py / (dimIt->y * dimIt->y) * J; normal.normalize(); ci.newPos = pos + startEnd - PSCollideEpsilon * normal; ci.dist = startEnd.norm(); ci.newSpeed = (-2.f * (speed * normal)) * normal + speed; ci.collisionZone = this; (*it)->collisionUpdate(ci, k); } } } } } } } */ void CPSZoneCylinder::computeCollisions(CPSLocated &target, uint firstInstanceIndex, const NLMISC::CVector *posBefore, const NLMISC::CVector *posAfter) { NL_PS_FUNC(CPSZoneCylinder_computeCollisions) MINI_TIMER(PSStatsZoneCylinder) TPSAttribVector::const_iterator dimIt; CPSAttrib<CPlaneBasis>::const_iterator basisIt; TPSAttribVector::const_iterator cylinderPosIt, cylinderPosEnd; CPSCollisionInfo ci; // cycle through the cylinders cylinderPosEnd = _Owner->getPos().end(); for (cylinderPosIt = _Owner->getPos().begin(), basisIt = _Basis.begin(), dimIt = _Dim.begin(); cylinderPosIt != cylinderPosEnd; ++cylinderPosIt, ++dimIt, ++basisIt) { // we must setup the cylinder in the good basis const CMatrix &m = CPSLocated::getConversionMatrix(&target, this->_Owner); // compute the new center pos CVector center = m * *cylinderPosIt; // compute a basis for the cylinder CVector I = m.mulVector(basisIt->X); CVector J = m.mulVector(basisIt->Y); CVector K = m.mulVector(basisIt->X ^ basisIt->Y); // the pos projected (and scale) over the plane basis of the cylinder, the pos minus the center CVector projectedPos, tPos; // the same, but with the final position CVector destProjectedPos, destTPos; // deals with each particle // deals with each particle const NLMISC::CVector *itPosBefore = posBefore + firstInstanceIndex; const NLMISC::CVector *itPosBeforeEnd = posBefore + target.getSize(); const NLMISC::CVector *itPosAfter = posAfter + firstInstanceIndex; while (itPosBefore != itPosBeforeEnd) { const CVector &pos = *itPosBefore; // check whether current pos was outside the cylinder tPos = pos - center; projectedPos = (1 / dimIt->x) * (I * tPos) * I + (1 / dimIt->y) * (J * tPos) * J; if (! ( ((tPos * K) < dimIt->z) && ((tPos * K) > -dimIt->z) && (projectedPos * projectedPos < 1.f) ) ) { const CVector &dest = *itPosAfter; destTPos = dest - center; destProjectedPos = (1.f / dimIt->x) * (I * destTPos) * I + (1.f / dimIt->y) * (J * destTPos) * J; // test whether the new position is inside the cylinder if ( ((destTPos * K) < dimIt->z) && ((destTPos * K) > -dimIt->z) && (destProjectedPos * destProjectedPos < 1.f) ) { // now, detect the closest hit point (the smallest alpha, with alpha, the percent of the move vector // to reach the hit point) const float epsilon = 10E-3f; float alphaTop, alphaBottom, alphaCyl; const float denum = (dest - pos) * K; // top plane if (fabs(denum) < epsilon) { alphaTop = (dimIt->z - (tPos * K)) / denum; if (alphaTop < 0.f) alphaTop = 1.f; } else { alphaTop = 1.f; } // bottom plane if (fabs(denum) < epsilon) { alphaBottom = (- dimIt->z - (tPos * K)) / denum; if (alphaBottom < 0.f) alphaBottom = 1.f; } else { alphaBottom = 1.f; } // cylinder //expressed the src and dest positions in the cylinder basis const float ox = tPos * I, oy = tPos * J, dx = (destTPos - tPos) * I, dy = (destTPos - tPos) * J; // coefficients of the equation : a * alpha ^ 2 + b * alpha + c = 0 const float a = (dx * dx) / (dimIt->x * dimIt->x) + (dy * dy) / (dimIt->y * dimIt->y); const float b = 2.f * ((ox * dx) / (dimIt->x * dimIt->x) + (oy * dy) / (dimIt->y * dimIt->y)); const float c = (ox * ox) / (dimIt->x * dimIt->x) + (oy * oy) / (dimIt->y * dimIt->y) - 1; // discriminant const float delta = b * b - 4.f * a * c; if (delta < epsilon) { alphaCyl = 1.f; } else { const float deltaRoot = sqrtf(delta); const float r1 = (- b + 2.f * deltaRoot) / (2.f * a); const float r2 = (- b - 2.f * deltaRoot) / (2.f * a); if (r1 < 0.f) alphaCyl = r2; else if (r2 < 0.f) alphaCyl = r1; else alphaCyl = r1 < r2 ? r1 : r2; if (alphaCyl < 0.f) alphaCyl = 1.f; } const CVector &speed = target.getSpeed()[(uint32)(itPosBefore - posBefore)]; // now, choose the minimum positive dist if (alphaTop < alphaBottom && alphaTop < alphaCyl) { // collision with the top plane CVector startEnd = alphaTop * (dest - pos); ci.NewPos = pos + startEnd + PSCollideEpsilon * K; ci.Dist = startEnd.norm(); ci.NewSpeed = (-2.f * (speed * K)) * K + speed; ci.CollisionZone = this; CPSLocated::_Collisions[itPosBefore - posBefore].update(ci); } else if (alphaBottom < alphaCyl) { // collision with the bottom plane CVector startEnd = alphaBottom * (dest - pos); ci.NewPos = pos + startEnd - PSCollideEpsilon * K; ci.Dist = startEnd.norm(); ci.NewSpeed = (-2.f * (speed * K)) * K + speed; ci.CollisionZone = this; CPSLocated::_Collisions[itPosBefore - posBefore].update(ci); } else { // collision with the cylinder CVector startEnd = alphaCyl * (dest - pos); // normal at the hit point. It is the gradient of the implicit equation x^2 / a^2 + y^2 / b^2 - R^ 2= 0 // so we got unormalized n = (2 x / a ^ 2, 2 y / b ^ 2, 0) in the basis of the cylinder // As we'll normalize it, we don't need the 2 factor float px = ox + alphaCyl * dx; float py = oy + alphaCyl * dy; CVector normal = px / (dimIt->x * dimIt->x) * I + py / (dimIt->y * dimIt->y) * J; normal.normalize(); ci.NewPos = pos + startEnd + PSCollideEpsilon * normal; ci.Dist = startEnd.norm(); ci.NewSpeed = (-2.f * (speed * normal)) * normal + speed; ci.CollisionZone = this; CPSLocated::_Collisions[itPosBefore - posBefore].update(ci); } } } ++ itPosBefore; ++ itPosAfter; } } } void CPSZoneCylinder::show() { NL_PS_FUNC(CPSZoneCylinder_show) TPSAttribVector::const_iterator dimIt = _Dim.begin() ,posIt = _Owner->getPos().begin() , endPosIt = _Owner->getPos().end(); CPSAttrib<CPlaneBasis>::const_iterator basisIt = _Basis.begin(); setupDriverModelMatrix(); CMatrix mat; CPSLocated *loc; uint32 index; CPSLocatedBindable *lb; _Owner->getOwner()->getCurrentEditedElement(loc, index, lb); for (uint32 k = 0; posIt != endPosIt; ++posIt, ++dimIt, ++basisIt, ++k) { mat.setRot(basisIt->X, basisIt->Y, basisIt->X ^ basisIt->Y); mat.setPos(CVector::Null); const CRGBA col = ((lb == NULL || this == lb) && loc == _Owner && index == k ? CRGBA::Red : CRGBA(127, 127, 127)); CPSUtil::displayCylinder(*getDriver(), *posIt, mat, *dimIt, 32, col); mat.setPos(*posIt); CPSUtil::displayBasis(getDriver() ,getLocalToWorldMatrix(), mat, 1.f, *getFontGenerator(), *getFontManager()); setupDriverModelMatrix(); } } void CPSZoneCylinder::setMatrix(uint32 index, const CMatrix &m) { NL_PS_FUNC(CPSZoneCylinder_setMatrix) // transform the basis _Basis[index].X = m.getI(); _Basis[index].Y = m.getJ(); // compute new pos _Owner->getPos()[index] = m.getPos(); } CMatrix CPSZoneCylinder::getMatrix(uint32 index) const { NL_PS_FUNC(CPSZoneCylinder_getMatrix) CMatrix m; m.setRot(_Basis[index].X, _Basis[index].Y, _Basis[index].X ^_Basis[index].Y); m.setPos(_Owner->getPos()[index]); return m; } void CPSZoneCylinder::setScale(uint32 k, float scale) { NL_PS_FUNC(CPSZoneCylinder_setScale) _Dim[k] = CVector(scale, scale, scale); } CVector CPSZoneCylinder::getScale(uint32 k) const { NL_PS_FUNC(CPSZoneCylinder_getScale) return _Dim[k]; } void CPSZoneCylinder::setScale(uint32 index, const CVector &s) { NL_PS_FUNC(CPSZoneCylinder_setScale) _Dim[index] = s; } void CPSZoneCylinder::serial(NLMISC::IStream &f) throw(NLMISC::EStream) { NL_PS_FUNC(CPSZoneCylinder_serial) f.serialVersion(1); CPSZone::serial(f); f.serial(_Basis); f.serial(_Dim); } void CPSZoneCylinder::resize(uint32 size) { NL_PS_FUNC(CPSZoneCylinder_resize) nlassert(size < (1 << 16)); _Basis.resize(size); _Dim.resize(size); } void CPSZoneCylinder::newElement(const CPSEmitterInfo &info) { NL_PS_FUNC(CPSZoneCylinder_newElement) _Basis.insert(CPlaneBasis(CVector::K)); _Dim.insert(CVector(1, 1, 1)); } void CPSZoneCylinder::deleteElement(uint32 index) { NL_PS_FUNC(CPSZoneCylinder_deleteElement) _Basis.remove(index); _Dim.remove(index); } ////////////////////////////////////////////// // implementation of CPSZoneRectangle // ////////////////////////////////////////////// void CPSZoneRectangle::computeCollisions(CPSLocated &target, uint firstInstanceIndex, const NLMISC::CVector *posBefore, const NLMISC::CVector *posAfter) { NL_PS_FUNC(CPSZoneRectangle_computeCollisions) MINI_TIMER(PSStatsZoneRectangle) // for each target, we must check whether they are going through the rectangle // if so they must bounce TPSAttribVector::const_iterator rectanglePosIt, rectanglePosEnd; CPSAttrib<CPlaneBasis>::const_iterator basisIt; TPSAttribFloat::const_iterator widthIt, heightIt; CPSCollisionInfo ci; // alpha is the ratio that gives the percent of endPos - startPos that hit the rectangle basisIt = _Basis.begin(); heightIt = _Height.begin(); widthIt = _Width.begin(); rectanglePosEnd = _Owner->getPos().end(); for (rectanglePosIt = _Owner->getPos().begin(); rectanglePosIt != rectanglePosEnd; ++rectanglePosIt, ++basisIt, ++widthIt, ++heightIt) { // we must setup the rectangle in the good basis const CMatrix &m = CPSLocated::getConversionMatrix(&target, this->_Owner); NLMISC::CPlane p; CVector center = m * (*rectanglePosIt); const CVector X = m.mulVector(basisIt->X); const CVector Y = m.mulVector(basisIt->Y); p.make(X ^ Y, center); // deals with each particle const float epsilon = 0.5f * PSCollideEpsilon; const NLMISC::CVector *itPosBefore = posBefore + firstInstanceIndex; const NLMISC::CVector *itPosBeforeEnd = posBefore + target.getSize(); const NLMISC::CVector *itPosAfter = posAfter + firstInstanceIndex; while (itPosBefore != itPosBeforeEnd) { float posSide = p * *itPosBefore; float negSide = p * *itPosAfter; if (posSide >= - epsilon && negSide <= epsilon) { float alpha; if (fabsf(posSide - negSide) > std::numeric_limits<float>::min()) { alpha = posSide / (posSide - negSide); } else { alpha = 0.f; } CVector startEnd = alpha * (*itPosAfter - *itPosBefore); ci.Dist = startEnd.norm(); // we translate the particle from an epsilon so that it won't get hooked to the rectangle ci.NewPos = *itPosBefore + startEnd; // tmp if ( fabs( (ci.NewPos - center) * X ) < *widthIt && fabs( (ci.NewPos - center) * Y ) < *heightIt) // check collision against rectangle { ci.NewPos += PSCollideEpsilon * p.getNormal(); const CVector &speed = target.getSpeed()[(uint32)(itPosBefore - posBefore)]; ci.NewSpeed = _BounceFactor * (speed - 2.0f * (speed * p.getNormal()) * p.getNormal()); ci.CollisionZone = this; CPSLocated::_Collisions[itPosBefore - posBefore].update(ci); } } ++ itPosBefore; ++ itPosAfter; } } } void CPSZoneRectangle::show() { NL_PS_FUNC(CPSZoneRectangle_show) nlassert(_Owner); const uint size = _Owner->getSize(); if (!size) return; setupDriverModelMatrix(); CMatrix mat; CPSLocated *loc; uint32 index; CPSLocatedBindable *lb; _Owner->getOwner()->getCurrentEditedElement(loc, index, lb); for (uint k = 0; k < size; ++k) { const CVector &I = _Basis[k].X; const CVector &J = _Basis[k].Y; mat.setRot(I, J , I ^J); mat.setPos(_Owner->getPos()[k]); CPSUtil::displayBasis(getDriver(), getLocalToWorldMatrix(), mat, 1.f, *getFontGenerator(), *getFontManager()); setupDriverModelMatrix(); const CRGBA col = ((lb == NULL || this == lb) && loc == _Owner && index == k ? CRGBA::Red : CRGBA(127, 127, 127)); const CVector &pos = _Owner->getPos()[k]; CPSUtil::display3DQuad(*getDriver(), pos + I * _Width[k] + J * _Height[k] , pos + I * _Width[k] - J * _Height[k] , pos - I * _Width[k] - J * _Height[k] , pos - I * _Width[k] + J * _Height[k], col); } } void CPSZoneRectangle::setMatrix(uint32 index, const CMatrix &m) { NL_PS_FUNC(CPSZoneRectangle_setMatrix) nlassert(_Owner); _Owner->getPos()[index] = m.getPos(); _Basis[index].X = m.getI(); _Basis[index].Y = m.getJ(); } CMatrix CPSZoneRectangle::getMatrix(uint32 index) const { NL_PS_FUNC(CPSZoneRectangle_getMatrix) nlassert(_Owner); CMatrix m; m.setRot(_Basis[index].X, _Basis[index].Y, _Basis[index].X ^ _Basis[index].Y); m.setPos(_Owner->getPos()[index]); return m; } void CPSZoneRectangle::setScale(uint32 index, float scale) { NL_PS_FUNC(CPSZoneRectangle_setScale) _Width[index] = scale; _Height[index] = scale; } void CPSZoneRectangle::setScale(uint32 index, const CVector &s) { NL_PS_FUNC(CPSZoneRectangle_setScale) _Width[index] = s.x; _Height[index] = s.y; } CVector CPSZoneRectangle::getScale(uint32 index) const { NL_PS_FUNC(CPSZoneRectangle_getScale) return CVector(_Width[index], _Height[index], 1.f); } void CPSZoneRectangle::serial(NLMISC::IStream &f) throw(NLMISC::EStream) { NL_PS_FUNC(CPSZoneRectangle_IStream ) f.serialVersion(1); CPSZone::serial(f); f.serial(_Basis); f.serial(_Width); f.serial(_Height); } void CPSZoneRectangle::resize(uint32 size) { NL_PS_FUNC(CPSZoneRectangle_resize) nlassert(size < (1 << 16)); _Basis.resize(size); _Width.resize(size); _Height.resize(size); } void CPSZoneRectangle::newElement(const CPSEmitterInfo &info) { NL_PS_FUNC(CPSZoneRectangle_newElement) _Basis.insert(CPlaneBasis(CVector::K)); _Width.insert(1.f); _Height.insert(1.f); } void CPSZoneRectangle::deleteElement(uint32 index) { NL_PS_FUNC(CPSZoneRectangle_deleteElement) _Basis.remove(index); _Width.remove(index); _Height.remove(index); } } // NL3D