/*==LICENSE==* CyanWorlds.com Engine - MMOG client, server and tools Copyright (C) 2011 Cyan Worlds, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . You can contact Cyan Worlds, Inc. by email legal@cyan.com or by snail mail at: Cyan Worlds, Inc. 14617 N Newport Hwy Mead, WA 99021 *==LICENSE==*/ #include "hsTypes.h" #include "plVolumeIsect.h" #include "hsBounds.h" #include "hsFastMath.h" #include "hsStream.h" #include "hsResMgr.h" #include "plIntersect/plClosest.h" static const hsScalar kDefLength = 5.f; plSphereIsect::plSphereIsect() : fRadius(1.f) { fCenter.Set(0,0,0); int i; for( i = 0; i < 3; i++ ) { fMins[i] = -fRadius; fMaxs[i] = fRadius; } } plSphereIsect::~plSphereIsect() { } void plSphereIsect::SetCenter(const hsPoint3& c) { fWorldCenter = fCenter = c; int i; for( i = 0; i < 3; i++ ) { fMins[i] += c[i]; fMaxs[i] += c[i]; } } void plSphereIsect::SetRadius(hsScalar r) { hsScalar del = r - fRadius; int i; for( i = 0; i < 3; i++ ) { fMins[i] -= del; fMaxs[i] += del; } fRadius = r; } void plSphereIsect::SetTransform(const hsMatrix44& l2w, const hsMatrix44& w2l) { fWorldCenter = l2w * fCenter; fMaxs = fMins = fWorldCenter; int i; for( i = 0; i < 3; i++ ) { fMins[i] -= fRadius; fMaxs[i] += fRadius; } } // Could use ClosestPoint to find the closest point on the bounds // to our center, and do a distance test on that. Would be more // accurate than this box test approx, but whatever. plVolumeCullResult plSphereIsect::Test(const hsBounds3Ext& bnd) const { const hsPoint3& maxs = bnd.GetMaxs(); const hsPoint3& mins = bnd.GetMins(); if( (maxs.fX < fMins.fX) || (maxs.fY < fMins.fY) || (maxs.fZ < fMins.fZ) ) return kVolumeCulled; if( (mins.fX > fMaxs.fX) || (mins.fY > fMaxs.fY) || (mins.fZ > fMaxs.fZ) ) return kVolumeCulled; if( (maxs.fX > fMaxs.fX) || (maxs.fY > fMaxs.fY) || (maxs.fZ > fMaxs.fZ) ) return kVolumeSplit; if( (mins.fX < fMins.fX) || (mins.fY < fMins.fY) || (mins.fZ < fMins.fZ) ) return kVolumeSplit; return kVolumeClear; } hsScalar plSphereIsect::Test(const hsPoint3& pos) const { hsScalar dist = (pos - fWorldCenter).MagnitudeSquared(); if( dist < fRadius*fRadius ) return 0; dist = hsSquareRoot(dist); return dist - fRadius; } void plSphereIsect::Read(hsStream* s, hsResMgr* mgr) { fCenter.Read(s); fWorldCenter.Read(s); fRadius = s->ReadSwapScalar(); fMins.Read(s); fMaxs.Read(s); } void plSphereIsect::Write(hsStream* s, hsResMgr* mgr) { fCenter.Write(s); fWorldCenter.Write(s); s->WriteSwapScalar(fRadius); fMins.Write(s); fMaxs.Write(s); } /////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// plConeIsect::plConeIsect() : fLength(kDefLength), fRadAngle(hsScalarPI*0.25f), fCapped(false) { ISetup(); } plConeIsect::~plConeIsect() { } void plConeIsect::SetAngle(hsScalar rads) { fRadAngle = rads; ISetup(); } void plConeIsect::ISetup() { hsScalar sinAng, cosAng; hsFastMath::SinCosInRangeAppr(fRadAngle, sinAng, cosAng); const hsScalar kHither = 0.1f; fLightToNDC.Reset(); fLightToNDC.fMap[0][0] = hsScalarDiv( cosAng, sinAng ); fLightToNDC.fMap[1][1] = hsScalarDiv( cosAng, sinAng ); fLightToNDC.fMap[2][2] = -hsScalarDiv( fLength, fLength - kHither ); fLightToNDC.fMap[3][3] = hsIntToScalar( 0 ); fLightToNDC.fMap[3][2] = hsIntToScalar( -1 ); fLightToNDC.fMap[2][3] = -hsScalarMulDiv( fLength, kHither, fLength - kHither ); fLightToNDC.NotIdentity(); } plVolumeCullResult plConeIsect::Test(const hsBounds3Ext& bnd) const { plVolumeCullResult retVal = kVolumeClear; hsPoint2 depth; hsVector3 normDir = -fWorldNorm; bnd.TestPlane(normDir, depth); if( depth.fY < normDir.InnerProduct(fWorldTip) ) return kVolumeCulled; int last = fCapped ? 5 : 4; int i; for( i = 0; i < last; i++ ) { bnd.TestPlane(fNorms[i], depth); if( depth.fY + fDists[i] <= 0 ) return kVolumeCulled; if( depth.fX + fDists[i] <= 0 ) retVal = kVolumeSplit; } if( retVal == kVolumeSplit ) { hsVector3 axis = normDir % hsVector3(&bnd.GetCenter(), &fWorldTip); hsFastMath::NormalizeAppr(axis); hsVector3 perp = axis % normDir; hsScalar sinAng, cosAng; hsFastMath::SinCosInRangeAppr(fRadAngle, sinAng, cosAng); hsVector3 tangent = normDir + sinAng * perp + (1-cosAng) * (axis % perp); hsVector3 normIn = tangent % axis; hsVector3 normIn2 = perp + sinAng * (perp % axis) + (1-cosAng) * (axis % (axis % perp)); bnd.TestPlane(normIn, depth); hsScalar normInDotTip = normIn.InnerProduct(fWorldTip); if( depth.fY < normInDotTip ) return kVolumeCulled; } return retVal; } hsScalar plConeIsect::Test(const hsPoint3& pos) const { UInt32 clampFlags = fCapped ? plClosest::kClamp : plClosest::kClampLower; hsPoint3 cp; plClosest::PointOnLine(pos, fWorldTip, fWorldNorm, cp, clampFlags); hsScalar radDist = (pos - cp).Magnitude(); hsScalar axDist = fWorldNorm.InnerProduct(pos - fWorldTip) / fLength; if( axDist < 0 ) { return radDist; } hsScalar sinAng, cosAng; hsFastMath::SinCosInRangeAppr(fRadAngle, sinAng, cosAng); hsScalar radius = axDist * sinAng / cosAng; radDist -= radius; axDist -= fLength; if( fCapped && (axDist > 0) ) { return axDist > radDist ? axDist : radDist; } return radDist > 0 ? radDist : 0; } //#define MF_DEBUG_NORM #ifdef MF_DEBUG_NORM #define IDEBUG_NORMALIZE( a, b ) { hsScalar len = 1.f / a.Magnitude(); a *= len; b *= len; } #else // MF_DEBUG_NORM #define IDEBUG_NORMALIZE( a, b ) #endif // MF_DEBUG_NORM void plConeIsect::SetTransform(const hsMatrix44& l2w, const hsMatrix44& w2l) { fWorldTip = l2w.GetTranslate(); fWorldNorm.Set(l2w.fMap[0][2], l2w.fMap[1][2], l2w.fMap[2][2]); fWorldToNDC = fLightToNDC * w2l; int i; for( i = 0; i < 2; i++ ) { fNorms[i].Set(fWorldToNDC.fMap[3][0] - fWorldToNDC.fMap[i][0], fWorldToNDC.fMap[3][1] - fWorldToNDC.fMap[i][1], fWorldToNDC.fMap[3][2] - fWorldToNDC.fMap[i][2]); fDists[i] = fWorldToNDC.fMap[3][3] - fWorldToNDC.fMap[i][3]; IDEBUG_NORMALIZE( fNorms[i], fDists[i] ); fNorms[i+2].Set(fWorldToNDC.fMap[3][0] + fWorldToNDC.fMap[i][0], fWorldToNDC.fMap[3][1] + fWorldToNDC.fMap[i][1], fWorldToNDC.fMap[3][2] + fWorldToNDC.fMap[i][2]); fDists[i+2] = fWorldToNDC.fMap[3][3] + fWorldToNDC.fMap[i][3]; IDEBUG_NORMALIZE( fNorms[i+2], fDists[i+2] ); } if( fCapped ) { fNorms[4].Set(fWorldToNDC.fMap[3][0] - fWorldToNDC.fMap[2][0], fWorldToNDC.fMap[3][1] - fWorldToNDC.fMap[2][1], fWorldToNDC.fMap[3][2] - fWorldToNDC.fMap[2][2]); fDists[4] = fWorldToNDC.fMap[3][3] - fWorldToNDC.fMap[2][3]; IDEBUG_NORMALIZE( fNorms[4], fDists[4] ); } } void plConeIsect::SetLength(hsScalar d) { if( d > 0 ) { fCapped = true; fLength = d; } else { fCapped = false; fLength = kDefLength; } ISetup(); } void plConeIsect::Read(hsStream* s, hsResMgr* mgr) { fCapped = s->ReadSwap32(); fRadAngle = s->ReadSwapScalar(); fLength = s->ReadSwapScalar(); fWorldTip.Read(s); fWorldNorm.Read(s); fWorldToNDC.Read(s); fLightToNDC.Read(s); int n = fCapped ? 5 : 4; int i; for(i = 0; i < n; i++ ) { fNorms[i].Read(s); fDists[i] = s->ReadSwapScalar(); } } void plConeIsect::Write(hsStream* s, hsResMgr* mgr) { s->WriteSwap32(fCapped); s->WriteSwapScalar(fRadAngle); s->WriteSwapScalar(fLength); fWorldTip.Write(s); fWorldNorm.Write(s); fWorldToNDC.Write(s); fLightToNDC.Write(s); int n = fCapped ? 5 : 4; int i; for(i = 0; i < n; i++ ) { fNorms[i].Write(s); s->WriteSwapScalar(fDists[i]); } } /////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// plCylinderIsect::plCylinderIsect() { } plCylinderIsect::~plCylinderIsect() { } void plCylinderIsect::ISetupCyl(const hsPoint3& wTop, const hsPoint3& wBot, hsScalar radius) { fWorldNorm.Set(&wTop, &wBot); fLength = fWorldNorm.Magnitude(); fMin = fWorldNorm.InnerProduct(wBot); fMax = fWorldNorm.InnerProduct(wTop); if( fMin > fMax ) { hsScalar t = fMin; fMin = fMax; fMax = t; } fRadius = radius; } void plCylinderIsect::SetCylinder(const hsPoint3& lTop, const hsPoint3& lBot, hsScalar radius) { fTop = lTop; fBot = lBot; fRadius = radius; ISetupCyl(fTop, fBot, fRadius); } void plCylinderIsect::SetCylinder(const hsPoint3& lBot, const hsVector3& axis, hsScalar radius) { fBot = lBot; fTop = fBot; fTop += axis; ISetupCyl(fTop, fBot, radius); } void plCylinderIsect::SetTransform(const hsMatrix44& l2w, const hsMatrix44& w2l) { hsPoint3 wTop = l2w * fTop; hsPoint3 wBot = l2w * fBot; ISetupCyl(wTop, wBot, fRadius); } plVolumeCullResult plCylinderIsect::Test(const hsBounds3Ext& bnd) const { plVolumeCullResult radVal = kVolumeClear; // Central axis test hsPoint2 depth; bnd.TestPlane(fWorldNorm, depth); if( depth.fX > fMax ) return kVolumeCulled; if( depth.fY < fMin ) return kVolumeCulled; if( (depth.fX < fMin) ||(depth.fY > fMax) ) { radVal = kVolumeSplit; } // Radial test plVolumeCullResult retVal = kVolumeCulled; // Find the closest point on/in the bounds to our central axis. // If that closest point is inside the cylinder, we have a hit. hsPoint3 corner; bnd.GetCorner(&corner); hsVector3 axes[3]; bnd.GetAxes(axes+0, axes+1, axes+2); hsPoint3 cp = corner; hsScalar bndRadiusSq = bnd.GetRadius(); bndRadiusSq *= bndRadiusSq; hsScalar radiusSq = fRadius*fRadius; hsScalar maxClearDistSq = fRadius - bnd.GetRadius(); maxClearDistSq *= maxClearDistSq; int i; for( i = 0; i < 3; i++ ) { hsPoint3 cp0; hsPoint3 currPt; plClosest::PointsOnLines(fWorldBot, fWorldNorm, cp, axes[i], cp0, currPt, plClosest::kClamp); hsScalar distSq = (cp0 - currPt).MagnitudeSquared(); if( distSq < radiusSq ) { if( distSq < maxClearDistSq ) { return kVolumeClear == radVal ? kVolumeClear : kVolumeSplit; } retVal = kVolumeSplit; } cp = currPt; } return retVal; } hsScalar plCylinderIsect::Test(const hsPoint3& pos) const { hsPoint3 cp; plClosest::PointOnLine(pos, fWorldBot, fWorldNorm, cp, plClosest::kClamp); hsScalar radDist = (pos - cp).Magnitude() - fRadius; hsScalar axDist = fWorldNorm.InnerProduct(pos - fWorldBot) / fLength; if( axDist < 0 ) axDist = -axDist; else axDist -= fLength; hsScalar dist = axDist > radDist ? axDist : radDist; return dist > 0 ? dist : 0; } void plCylinderIsect::Read(hsStream* s, hsResMgr* mgr) { fTop.Read(s); fBot.Read(s); fRadius = s->ReadSwapScalar(); fWorldBot.Read(s); fWorldNorm.Read(s); fLength = s->ReadSwapScalar(); fMin = s->ReadSwapScalar(); fMax = s->ReadSwapScalar(); } void plCylinderIsect::Write(hsStream* s, hsResMgr* mgr) { fTop.Write(s); fBot.Write(s); s->WriteSwapScalar(fRadius); fWorldBot.Write(s); fWorldNorm.Write(s); s->WriteSwapScalar(fLength); s->WriteSwapScalar(fMin); s->WriteSwapScalar(fMax); } /////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// plParallelIsect::plParallelIsect() { } plParallelIsect::~plParallelIsect() { } void plParallelIsect::SetNumPlanes(int n) { fPlanes.SetCount(n); } void plParallelIsect::SetPlane(int which, const hsPoint3& locPosOne, const hsPoint3& locPosTwo) { fPlanes[which].fPosOne = locPosOne; fPlanes[which].fPosTwo = locPosTwo; } void plParallelIsect::SetTransform(const hsMatrix44& l2w, const hsMatrix44& w2l) { int i; for( i = 0; i < fPlanes.GetCount(); i++ ) { hsPoint3 wPosOne = l2w * fPlanes[i].fPosOne; hsPoint3 wPosTwo = l2w * fPlanes[i].fPosTwo; hsVector3 norm; norm.Set(&wPosOne, &wPosTwo); fPlanes[i].fNorm = norm; hsScalar t0 = norm.InnerProduct(wPosOne); hsScalar t1 = norm.InnerProduct(wPosTwo); if( t0 > t1 ) { fPlanes[i].fMin = t1; fPlanes[i].fMax = t0; } else { fPlanes[i].fMin = t0; fPlanes[i].fMax = t1; } } } plVolumeCullResult plParallelIsect::Test(const hsBounds3Ext& bnd) const { plVolumeCullResult retVal = kVolumeClear; int i; for( i = 0; i < fPlanes.GetCount(); i++ ) { hsPoint2 depth; bnd.TestPlane(fPlanes[i].fNorm, depth); if( depth.fY < fPlanes[i].fMin ) return kVolumeCulled; if( depth.fX > fPlanes[i].fMax ) return kVolumeCulled; if( depth.fX < fPlanes[i].fMin ) retVal = kVolumeSplit; if( depth.fY > fPlanes[i].fMax ) retVal = kVolumeSplit; } return retVal; } hsScalar plParallelIsect::Test(const hsPoint3& pos) const { hsScalar maxDist = 0; int i; for( i = 0; i < fPlanes.GetCount(); i++ ) { hsScalar dist = fPlanes[i].fNorm.InnerProduct(pos); if( dist > fPlanes[i].fMax ) { dist -= fPlanes[i].fMax; dist *= hsFastMath::InvSqrtAppr(fPlanes[i].fNorm.MagnitudeSquared()); if( dist > maxDist ) maxDist = dist; } else if( dist < fPlanes[i].fMin ) { dist = fPlanes[i].fMin - dist; dist *= hsFastMath::InvSqrtAppr(fPlanes[i].fNorm.MagnitudeSquared()); if( dist > maxDist ) maxDist = dist; } } return maxDist; } void plParallelIsect::Read(hsStream* s, hsResMgr* mgr) { int n = s->ReadSwap16(); fPlanes.SetCount(n); int i; for( i = 0; i < n; i++ ) { fPlanes[i].fNorm.Read(s); fPlanes[i].fMin = s->ReadSwapScalar(); fPlanes[i].fMax = s->ReadSwapScalar(); fPlanes[i].fPosOne.Read(s); fPlanes[i].fPosTwo.Read(s); } } void plParallelIsect::Write(hsStream* s, hsResMgr* mgr) { s->WriteSwap16(fPlanes.GetCount()); int i; for( i = 0; i < fPlanes.GetCount(); i++ ) { fPlanes[i].fNorm.Write(s); s->WriteSwapScalar(fPlanes[i].fMin); s->WriteSwapScalar(fPlanes[i].fMax); fPlanes[i].fPosOne.Write(s); fPlanes[i].fPosTwo.Write(s); } } /////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// plConvexIsect::plConvexIsect() { } plConvexIsect::~plConvexIsect() { } void plConvexIsect::AddPlaneUnchecked(const hsVector3& n, hsScalar dist) { SinglePlane plane; plane.fNorm = n; plane.fPos.Set(0,0,0); plane.fDist = dist; fPlanes.Append(plane); } void plConvexIsect::AddPlane(const hsVector3& n, const hsPoint3& p) { hsVector3 nNorm = n; hsFastMath::Normalize(nNorm); // First, make sure some idiot isn't adding the same plane in twice. // Also, look for the degenerate case of two parallel planes. In that // case, take the outer. int i; for( i = 0; i < fPlanes.GetCount(); i++ ) { const hsScalar kCloseToOne = 1.f - 1.e-4f; if( fPlanes[i].fNorm.InnerProduct(nNorm) >= kCloseToOne ) { hsScalar dist = nNorm.InnerProduct(p); if( dist > fPlanes[i].fDist ) { fPlanes[i].fDist = dist; fPlanes[i].fPos = p; } return; } } SinglePlane plane; plane.fNorm = nNorm; plane.fPos = p; plane.fDist = nNorm.InnerProduct(p); fPlanes.Append(plane); } void plConvexIsect::SetTransform(const hsMatrix44& l2w, const hsMatrix44& w2l) { int i; for( i = 0; i < fPlanes.GetCount(); i++ ) { hsPoint3 wPos = l2w * fPlanes[i].fPos; // Normal gets transpose of inverse. fPlanes[i].fWorldNorm.fX = w2l.fMap[0][0] * fPlanes[i].fNorm.fX + w2l.fMap[1][0] * fPlanes[i].fNorm.fY + w2l.fMap[2][0] * fPlanes[i].fNorm.fZ; fPlanes[i].fWorldNorm.fY = w2l.fMap[0][1] * fPlanes[i].fNorm.fX + w2l.fMap[1][1] * fPlanes[i].fNorm.fY + w2l.fMap[2][1] * fPlanes[i].fNorm.fZ; fPlanes[i].fWorldNorm.fZ = w2l.fMap[0][2] * fPlanes[i].fNorm.fX + w2l.fMap[1][2] * fPlanes[i].fNorm.fY + w2l.fMap[2][2] * fPlanes[i].fNorm.fZ; hsFastMath::NormalizeAppr(fPlanes[i].fWorldNorm); fPlanes[i].fWorldDist = fPlanes[i].fWorldNorm.InnerProduct(wPos); } } plVolumeCullResult plConvexIsect::Test(const hsBounds3Ext& bnd) const { plVolumeCullResult retVal = kVolumeClear; int i; for( i = 0; i < fPlanes.GetCount(); i++ ) { hsPoint2 depth; bnd.TestPlane(fPlanes[i].fWorldNorm, depth); if( depth.fX > fPlanes[i].fWorldDist ) return kVolumeCulled; if( depth.fY > fPlanes[i].fWorldDist ) retVal = kVolumeSplit; } return retVal; } hsScalar plConvexIsect::Test(const hsPoint3& pos) const { hsScalar maxDist = 0; int i; for( i = 0; i < fPlanes.GetCount(); i++ ) { hsScalar dist = fPlanes[i].fWorldNorm.InnerProduct(pos) - fPlanes[i].fWorldDist; if( dist > maxDist ) maxDist = dist; } return maxDist; } void plConvexIsect::Read(hsStream* s, hsResMgr* mgr) { Int16 n = s->ReadSwap16(); fPlanes.SetCount(n); int i; for( i = 0; i < n; i++ ) { fPlanes[i].fNorm.Read(s); fPlanes[i].fPos.Read(s); fPlanes[i].fDist = s->ReadSwapScalar(); fPlanes[i].fWorldNorm.Read(s); fPlanes[i].fWorldDist = s->ReadSwapScalar(); } } void plConvexIsect::Write(hsStream* s, hsResMgr* mgr) { s->WriteSwap16(fPlanes.GetCount()); int i; for( i = 0; i < fPlanes.GetCount(); i++ ) { fPlanes[i].fNorm.Write(s); fPlanes[i].fPos.Write(s); s->WriteSwapScalar(fPlanes[i].fDist); fPlanes[i].fWorldNorm.Write(s); s->WriteSwapScalar(fPlanes[i].fWorldDist); } } /////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// plBoundsIsect::plBoundsIsect() { } plBoundsIsect::~plBoundsIsect() { } void plBoundsIsect::SetBounds(const hsBounds3Ext& bnd) { fLocalBounds = bnd; fWorldBounds = bnd; } void plBoundsIsect::SetTransform(const hsMatrix44& l2w, const hsMatrix44& w2l) { fWorldBounds = fLocalBounds; fWorldBounds.Transform(&l2w); } plVolumeCullResult plBoundsIsect::Test(const hsBounds3Ext& bnd) const { int retVal = fWorldBounds.TestBound(bnd); if( retVal < 0 ) return kVolumeCulled; if( retVal > 0 ) return kVolumeClear; retVal = bnd.TestBound(fWorldBounds); return retVal < 0 ? kVolumeCulled : kVolumeSplit; } hsScalar plBoundsIsect::Test(const hsPoint3& pos) const { hsAssert(false, "Unimplemented"); return 0.f; } void plBoundsIsect::Read(hsStream* s, hsResMgr* mgr) { fLocalBounds.Read(s); fWorldBounds.Read(s); } void plBoundsIsect::Write(hsStream* s, hsResMgr* mgr) { fLocalBounds.Write(s); fWorldBounds.Write(s); } /////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// plComplexIsect::plComplexIsect() { } plComplexIsect::~plComplexIsect() { int i; for( i = 0; i < fVolumes.GetCount(); i++ ) delete fVolumes[i]; } void plComplexIsect::AddVolume(plVolumeIsect* v) { fVolumes.Append(v); } void plComplexIsect::SetTransform(const hsMatrix44& l2w, const hsMatrix44& w2l) { int i; for( i = 0; i < fVolumes.GetCount(); i++ ) fVolumes[i]->SetTransform(l2w, w2l); } void plComplexIsect::Read(hsStream* s, hsResMgr* mgr) { int n = s->ReadSwap16(); fVolumes.SetCount(n); int i; for( i = 0; i < n; i++ ) { fVolumes[i] = plVolumeIsect::ConvertNoRef(mgr->ReadCreatable(s)); hsAssert(fVolumes[i], "Failure reading in a sub-volume"); } } void plComplexIsect::Write(hsStream* s, hsResMgr* mgr) { s->WriteSwap16(fVolumes.GetCount()); int i; for( i = 0; i < fVolumes.GetCount(); i++ ) mgr->WriteCreatable(s, fVolumes[i]); } /////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// plUnionIsect::plUnionIsect() { } plUnionIsect::~plUnionIsect() { } plVolumeCullResult plUnionIsect::Test(const hsBounds3Ext& bnd) const { plVolumeCullResult retVal = kVolumeCulled; int i; for( i = 0; i < fVolumes.GetCount(); i++ ) { plVolumeCullResult ret = fVolumes[i]->Test(bnd); switch( ret ) { case kVolumeCulled: break; case kVolumeClear: return kVolumeClear; case kVolumeSplit: retVal = kVolumeSplit; break; }; } return retVal; } hsScalar plUnionIsect::Test(const hsPoint3& pos) const { hsScalar retVal = 1.e33f; int i; for( i = 0; i < fVolumes.GetCount(); i++ ) { hsScalar ret = fVolumes[i]->Test(pos); if( ret <= 0 ) return 0; if( ret < retVal ) retVal = ret; } return retVal; } /////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// plIntersectionIsect::plIntersectionIsect() { } plIntersectionIsect::~plIntersectionIsect() { } plVolumeCullResult plIntersectionIsect::Test(const hsBounds3Ext& bnd) const { plVolumeCullResult retVal = kVolumeClear; int i; for( i = 0; i < fVolumes.GetCount(); i++ ) { plVolumeCullResult ret = fVolumes[i]->Test(bnd); switch( ret ) { case kVolumeCulled: return kVolumeCulled; case kVolumeClear: break; case kVolumeSplit: retVal = kVolumeSplit; break; }; } return retVal; } hsScalar plIntersectionIsect::Test(const hsPoint3& pos) const { hsScalar retVal = -1.f; int i; for( i = 0; i < fVolumes.GetCount(); i++ ) { hsScalar ret = fVolumes[i]->Test(pos); if( ret > retVal ) retVal = ret; } return retVal; }