//----------------------------------------------------------------------------- // Torque Game Engine // Copyright (C) GarageGames.com, Inc. //----------------------------------------------------------------------------- #include "interior/interior.h" #include "math/mSphere.h" #include "sim/sceneObject.h" #include "collision/abstractPolyList.h" #include "dgl/dgl.h" #include "core/frameAllocator.h" #include "platform/profiler.h" namespace { //-------------------------------------- // Custom on plane version to reduce numerical inaccuracies in the GJK stuff. // copied from terrCollision.cc inline bool isOnPlane(const Point3F& p, const PlaneF& plane) { F32 dist = mDot(plane,p) + plane.d; return mFabs(dist) < 0.1; } } // namespace {} //-------------------------------------------------------------------------- //-------------------------------------- SurfaceHash const U32 csgHashSize = 4096; class SurfaceHash { private: U32 hash(U32 i) { return i & (csgHashSize - 1); } public: U32 mSurfaceArray[csgHashSize]; U32 mNumSurfaces; U32 mHashTable[csgHashSize]; public: SurfaceHash() { AssertFatal(isPow2(csgHashSize), "Error, size must be power of 2"); } void clear() { dMemset(mHashTable, 0xFF, sizeof(mHashTable)); mNumSurfaces = 0; } void insert(U32 surface); }; inline void SurfaceHash::insert(U32 surface) { AssertFatal(surface != 0xFFFFFFFF, "Hm, bad assumption. 0xFFFFFFFF is a valid surface index?"); if (mNumSurfaces >= csgHashSize) { AssertFatal(false, "Error, exceeded surfaceCount restriction!"); return; } U32 probe = hash(surface); U32 initialProbe = probe; while (mHashTable[probe] != 0xFFFFFFFF) { // If it's already in the table, bail. if (mHashTable[probe] == surface) return; probe = (probe + 1) % csgHashSize; AssertFatal(probe != initialProbe, "Hm, wraparound?"); } // If we're here, then the probe failed, and the index isn't in the hash // table mHashTable[probe] = surface; mSurfaceArray[mNumSurfaces++] = surface; } class InteriorPolytope { // Convex Polyhedron public: struct Vertex { Point3F point; // Temp BSP clip info S32 side; }; struct Edge { S32 vertex[2]; S32 face[2]; S32 next; }; struct Face { S32 original; // Temp BSP clip info S32 vertex; }; struct Volume { S32 edgeList; }; struct StackElement { S32 edgeList; U32 popCount; U16 nodeIndex; }; typedef Vector EdgeList; typedef Vector FaceList; typedef Vector VertexList; typedef Vector VolumeList; typedef Vector VolumeStack; // S32 sideCount; EdgeList mEdgeList; FaceList mFaceList; VertexList mVertexList; VolumeList mVolumeList; public: // InteriorPolytope(); void clear(); bool intersect(const PlaneF& plane,const Point3F& sp,const Point3F& ep); void buildBox(const Box3F& box, const MatrixF& transform, const Point3F& scale); inline bool didIntersect() { return mVolumeList.size() > 1; } }; //---------------------------------------------------------------------------- // Box should be axis aligned in the transform space provided. InteriorPolytope::InteriorPolytope() { mVertexList.reserve(256); mFaceList.reserve(200); mEdgeList.reserve(100); mVolumeList.reserve(20); sideCount = 0; } void InteriorPolytope::clear() { mVertexList.clear(); mFaceList.clear(); mEdgeList.clear(); mVolumeList.clear(); sideCount = 0; } void InteriorPolytope::buildBox(const Box3F& box, const MatrixF& transform, const Point3F&) { // Initial vertices mVertexList.setSize(8); mVertexList[0].point = Point3F(box.min.x, box.min.y, box.min.z); mVertexList[1].point = Point3F(box.min.x, box.max.y, box.min.z); mVertexList[2].point = Point3F(box.max.x, box.max.y, box.min.z); mVertexList[3].point = Point3F(box.max.x, box.min.y, box.min.z); mVertexList[4].point = Point3F(box.min.x, box.min.y, box.max.z); mVertexList[5].point = Point3F(box.min.x, box.max.y, box.max.z); mVertexList[6].point = Point3F(box.max.x, box.max.y, box.max.z); mVertexList[7].point = Point3F(box.max.x, box.min.y, box.max.z); S32 i; for (i = 0; i < 8; i++) { transform.mulP(mVertexList[i].point); mVertexList[i].side = 0; } // Initial faces mFaceList.setSize(6); for (S32 f = 0; f < 6; f++) { Face& face = mFaceList[f]; face.original = true; face.vertex = 0; } // Initial edges mEdgeList.setSize(12); Edge* edge = mEdgeList.begin(); S32 nextEdge = 0; for (i = 0; i < 4; i++) { S32 n = (i == 3)? 0: i + 1; S32 p = (i == 0)? 3: i - 1; edge->vertex[0] = i; edge->vertex[1] = n; edge->face[0] = i; edge->face[1] = 4; edge->next = ++nextEdge; edge++; edge->vertex[0] = 4 + i; edge->vertex[1] = 4 + n; edge->face[0] = i; edge->face[1] = 5; edge->next = ++nextEdge; edge++; edge->vertex[0] = i; edge->vertex[1] = 4 + i; edge->face[0] = i; edge->face[1] = p; edge->next = ++nextEdge; edge++; } edge[-1].next = -1; // Volume mVolumeList.setSize(1); Volume& volume = mVolumeList.last(); volume.edgeList = 0; sideCount = 0; } bool InteriorPolytope::intersect(const PlaneF& plane,const Point3F& sp,const Point3F& ep) { // If den == 0 then the line and plane are parallel. F32 den; Point3F dt = ep - sp; if ((den = plane.x * dt.x + plane.y * dt.y + plane.z * dt.z) == 0) return false; // Save the values in sp since the memory may go away after the increment F32 sp_x = sp.x; F32 sp_y = sp.y; F32 sp_z = sp.z; mVertexList.increment(); Vertex& v = mVertexList.last(); F32 s = -(plane.x * sp_x + plane.y * sp_y + plane.z * sp_z + plane.d) / den; v.point.x = sp_x + dt.x * s; v.point.y = sp_y + dt.y * s; v.point.z = sp_z + dt.z * s; v.side = 0; return true; } //-------------------------------------------------------------------------- void Interior::collisionFanFromSurface(const Surface& rSurface, U32* fanIndices, U32* numIndices) const { U32 tempIndices[32]; tempIndices[0] = 0; U32 idx = 1; U32 i; for (i = 1; i < rSurface.windingCount; i += 2) tempIndices[idx++] = i; for (i = ((rSurface.windingCount - 1) & (~0x1)); i > 0; i -= 2) tempIndices[idx++] = i; idx = 0; for (i = 0; i < rSurface.windingCount; i++) { if (rSurface.fanMask & (1 << i)) { fanIndices[idx++] = mWindings[rSurface.windingStart + tempIndices[i]]; } } *numIndices = idx; } void Interior::fullWindingFromSurface(const Surface& rSurface, U32* fanIndices, U32* numIndices) const { U32 tempIndices[32]; tempIndices[0] = 0; U32 idx = 1; U32 i; for (i = 1; i < rSurface.windingCount; i += 2) tempIndices[idx++] = i; for (i = ((rSurface.windingCount - 1) & (~0x1)); i > 0; i -= 2) tempIndices[idx++] = i; idx = 0; for (i = 0; i < rSurface.windingCount; i++) fanIndices[idx++] = mWindings[rSurface.windingStart + tempIndices[i]]; *numIndices = idx; } bool Interior::castRay_r(const U16 node, const U16 planeIndex, const Point3F& s, const Point3F& e, RayInfo* info) { if (isBSPLeafIndex(node) == false) { const IBSPNode& rNode = mBSPNodes[node]; const PlaneF& rPlane = getPlane(rNode.planeIndex); PlaneF::Side sSide = rPlane.whichSide(s); PlaneF::Side eSide = rPlane.whichSide(e); switch (PlaneSwitchCode(sSide, eSide)) { case PlaneSwitchCode(PlaneF::Front, PlaneF::Front): case PlaneSwitchCode(PlaneF::Front, PlaneF::On): case PlaneSwitchCode(PlaneF::On, PlaneF::Front): return castRay_r(rNode.frontIndex, planeIndex, s, e, info); break; case PlaneSwitchCode(PlaneF::On, PlaneF::Back): case PlaneSwitchCode(PlaneF::Back, PlaneF::On): case PlaneSwitchCode(PlaneF::Back, PlaneF::Back): return castRay_r(rNode.backIndex, planeIndex, s, e, info); break; case PlaneSwitchCode(PlaneF::On, PlaneF::On): // Line lies on the plane if (isBSPLeafIndex(rNode.backIndex) == false) { if (castRay_r(rNode.backIndex, planeIndex, s, e, info)) return true; } if (isBSPLeafIndex(rNode.frontIndex) == false) { if (castRay_r(rNode.frontIndex, planeIndex, s, e, info)) return true; } return false; break; case PlaneSwitchCode(PlaneF::Front, PlaneF::Back): { Point3F ip; F32 intersectT = rPlane.intersect(s, e); AssertFatal(intersectT != PARALLEL_PLANE, "Error, this should never happen in this case!"); ip.interpolate(s, e, intersectT); if (castRay_r(rNode.frontIndex, planeIndex, s, ip, info)) return true; return castRay_r(rNode.backIndex, rNode.planeIndex, ip, e, info); } break; case PlaneSwitchCode(PlaneF::Back, PlaneF::Front): { Point3F ip; F32 intersectT = rPlane.intersect(s, e); AssertFatal(intersectT != PARALLEL_PLANE, "Error, this should never happen in this case!"); ip.interpolate(s, e, intersectT); if (castRay_r(rNode.backIndex, planeIndex, s, ip, info)) return true; return castRay_r(rNode.frontIndex, rNode.planeIndex, ip, e, info); } break; default: AssertFatal(false, "Misunderstood switchCode in Interior::castRay_r"); return false; } } if (isBSPSolidLeaf(node)) { // DMM: Set material info here.. material info? hahaha info->point = s; if (planeIndex != U16(-1)) { const PlaneF& rPlane = getPlane(planeIndex); info->normal = rPlane; if (planeIsFlipped(planeIndex)) { info->normal.neg(); if (rPlane.whichSide(e) == PlaneF::Back) info->normal.neg(); } else { if (rPlane.whichSide(e) == PlaneF::Front) info->normal.neg(); } } else { // Point started in solid; if (s == e) { info->normal.set(0, 0, 1); } else { info->normal = s - e; info->normal.normalize(); } } // ok.. let's get it on! get the face that was hit info->face = U32(-1); const IBSPLeafSolid & rLeaf = mBSPSolidLeaves[getBSPSolidLeafIndex(node)]; for(U32 i = 0; i < rLeaf.surfaceCount; i++) { U32 surfaceIndex = mSolidLeafSurfaces[rLeaf.surfaceIndex + i]; if(isNullSurfaceIndex(surfaceIndex)) continue; const Surface & rSurface = mSurfaces[surfaceIndex]; if(!areEqualPlanes(rSurface.planeIndex, planeIndex)) continue; PlaneF plane = getPlane(rSurface.planeIndex); if(planeIsFlipped(rSurface.planeIndex)) plane.neg(); // unfan this surface U32 winding[32]; U32 windingCount; collisionFanFromSurface(rSurface, winding, &windingCount); // inside this surface? bool inside = true; for(U32 j = 0; inside && (j < windingCount); j++) { U32 k = (j+1) % windingCount; Point3F vec1 = mPoints[winding[k]].point - mPoints[winding[j]].point; Point3F vec2 = info->point - mPoints[winding[j]].point; Point3F cross; mCross(vec2, vec1, &cross); if(mDot(plane, cross) < 0.f) inside = false; } if(inside) { info->face = surfaceIndex; break; } } return true; } return false; } bool Interior::castRay(const Point3F& s, const Point3F& e, RayInfo* info) { // DMM: Going to need normal here eventually. bool hit = castRay_r(0, U16(-1), s, e, info); if (hit) { Point3F vec = e - s; F32 len = vec.len(); if (len < 1e-6) { info->t = 0.0f; } else { vec /= len; info->t = mDot(info->point - s, vec) / len; } } AssertFatal(!hit || (info->normal.z == info->normal.z), "NaN returned from castRay.\n\nPlease talk to DMM if you are running this in the debugger"); return hit; } void Interior::buildPolyList_r(InteriorPolytope& polytope, SurfaceHash& hash) { // Submit the first volume of the poly tope to the bsp tree static InteriorPolytope::VolumeStack stack; stack.reserve(256); stack.setSize(1); stack.last().edgeList = polytope.mVolumeList[0].edgeList; stack.last().nodeIndex = 0; stack.last().popCount = 0; static Vector collPlanes; collPlanes.reserve(64); collPlanes.clear(); while (stack.empty() == false) { InteriorPolytope::StackElement volume = stack.last(); stack.pop_back(); if (isBSPLeafIndex(volume.nodeIndex)) { if (isBSPSolidLeaf(volume.nodeIndex)) { // Export the polys from this node. const IBSPLeafSolid& rLeaf = mBSPSolidLeaves[getBSPSolidLeafIndex(volume.nodeIndex)]; for (U32 i = 0; i < rLeaf.surfaceCount; i++) { U32 surfaceIndex = mSolidLeafSurfaces[rLeaf.surfaceIndex + i]; if (isNullSurfaceIndex(surfaceIndex)) { // Is a NULL surface const NullSurface& rSurface = mNullSurfaces[getNullSurfaceIndex(surfaceIndex)]; for (U32 j = 0; j < collPlanes.size(); j++) { if (areEqualPlanes(rSurface.planeIndex, collPlanes[j]) == true) { hash.insert(surfaceIndex); break; } } } else { const Surface& rSurface = mSurfaces[surfaceIndex]; for (U32 j = 0; j < collPlanes.size(); j++) { if (areEqualPlanes(rSurface.planeIndex, collPlanes[j]) == true) { hash.insert(surfaceIndex); break; } } } } } if (volume.popCount) for (U32 i = 0; i < volume.popCount; i++) collPlanes.pop_back(); continue; } const IBSPNode& rNode = mBSPNodes[volume.nodeIndex]; // New front and back faces polytope.mFaceList.increment(2); InteriorPolytope::Face& frontFace = polytope.mFaceList[polytope.mFaceList.size() - 1]; InteriorPolytope::Face& backFace = polytope.mFaceList[polytope.mFaceList.size() - 2]; backFace.original = frontFace.original = false; backFace.vertex = frontFace.vertex = 0; // New front and back volumes InteriorPolytope::StackElement frontVolume,backVolume; frontVolume.edgeList = backVolume.edgeList = -1; PlaneF plane = getPlane(rNode.planeIndex); if (planeIsFlipped(rNode.planeIndex)) plane.neg(); S32 startVertex = polytope.mVertexList.size(); // Test & clip all the edges S32 sideBase = ++polytope.sideCount << 1; for (S32 i = volume.edgeList; i >= 0; i = polytope.mEdgeList[i].next) { // Copy into tmp first to avoid problems with the array. InteriorPolytope::Edge edge = polytope.mEdgeList[i]; InteriorPolytope::Vertex& v0 = polytope.mVertexList[edge.vertex[0]]; if (v0.side < sideBase) v0.side = sideBase + ((plane.distToPlane(v0.point) >= 0)? 0: 1); InteriorPolytope::Vertex& v1 = polytope.mVertexList[edge.vertex[1]]; if (v1.side < sideBase) v1.side = sideBase + ((plane.distToPlane(v1.point) >= 0)? 0: 1); if (v0.side != v1.side) { S32 s = v0.side - sideBase; polytope.intersect(plane,v0.point,v1.point); // Split the edge into each volume polytope.mEdgeList.increment(2); InteriorPolytope::Edge& e0 = polytope.mEdgeList.last(); e0.next = frontVolume.edgeList; frontVolume.edgeList = polytope.mEdgeList.size() - 1; InteriorPolytope::Edge& e1 = *(&e0 - 1); e1.next = backVolume.edgeList; backVolume.edgeList = frontVolume.edgeList - 1; e0.vertex[0] = edge.vertex[s]; e1.vertex[0] = edge.vertex[s ^ 1]; e0.vertex[1] = e1.vertex[1] = polytope.mVertexList.size() - 1; e0.face[0] = e1.face[0] = edge.face[0]; e0.face[1] = e1.face[1] = edge.face[1]; // Add new edges on the plane, one to each volume for (S32 f = 0; f < 2; f++) { InteriorPolytope::Face& face = polytope.mFaceList[edge.face[f]]; if (face.vertex < startVertex) { face.vertex = polytope.mVertexList.size() - 1; } else { polytope.mEdgeList.increment(2); InteriorPolytope::Edge& e0 = polytope.mEdgeList.last(); e0.next = frontVolume.edgeList; frontVolume.edgeList = polytope.mEdgeList.size() - 1; InteriorPolytope::Edge& e1 = *(&e0 - 1); e1.next = backVolume.edgeList; backVolume.edgeList = frontVolume.edgeList - 1; e1.vertex[0] = e0.vertex[0] = face.vertex; e1.vertex[1] = e0.vertex[1] = polytope.mVertexList.size() - 1; e1.face[0] = e0.face[0] = edge.face[f]; e1.face[1] = polytope.mFaceList.size() - 1; e0.face[1] = e1.face[1] - 1; } } } else { if (v0.side == sideBase) { polytope.mEdgeList.push_back(edge); InteriorPolytope::Edge& ne = polytope.mEdgeList.last(); ne.next = frontVolume.edgeList; frontVolume.edgeList = polytope.mEdgeList.size() - 1; } else { polytope.mEdgeList.push_back(edge); InteriorPolytope::Edge& ne = polytope.mEdgeList.last(); ne.next = backVolume.edgeList; backVolume.edgeList = polytope.mEdgeList.size() - 1; } } } // Push the front and back nodes if (frontVolume.edgeList >= 0 && backVolume.edgeList >= 0) { collPlanes.push_back(rNode.planeIndex); frontVolume.nodeIndex = rNode.frontIndex; frontVolume.popCount = volume.popCount + 1; stack.push_back(frontVolume); backVolume.nodeIndex = rNode.backIndex; backVolume.popCount = 0; stack.push_back(backVolume); } else if (frontVolume.edgeList >= 0) { frontVolume.nodeIndex = rNode.frontIndex; frontVolume.popCount = volume.popCount; stack.push_back(frontVolume); } else if (backVolume.edgeList >= 0) { backVolume.nodeIndex = rNode.backIndex; backVolume.popCount = volume.popCount; stack.push_back(backVolume); } else { // Pop off our own planes... if (volume.popCount) for (U32 i = 0; i < volume.popCount; i++) collPlanes.pop_back(); } } AssertFatal(collPlanes.size() == 0, "Unbalanced stack!"); } bool Interior::buildPolyList(AbstractPolyList* list, const Box3F& box, const MatrixF& transform, const Point3F& scale) { Box3F testBox; MatrixF toItr; if (!list->getMapping(&toItr,&testBox)) { // this list doesn't do this, use world space box and transform testBox = box; toItr = transform; } // construct an interior space box from testBox and toItr // source space may be world space, or may be something else... // that's up to the list // Note: transform maps to interior, but scale is itr -> world... // that is why we divide by scale below... F32 * f = toItr; F32 xx = mFabs(f[0]); F32 xy = mFabs(f[4]); F32 xz = mFabs(f[8]); F32 yx = mFabs(f[1]); F32 yy = mFabs(f[5]); F32 yz = mFabs(f[9]); F32 zx = mFabs(f[2]); F32 zy = mFabs(f[6]); F32 zz = mFabs(f[10]); F32 xlen = testBox.max.x - testBox.min.x; F32 ylen = testBox.max.y - testBox.min.y; F32 zlen = testBox.max.z - testBox.min.z; F32 invScalex = 1.0f/scale.x; F32 invScaley = 1.0f/scale.y; F32 invScalez = 1.0f/scale.z; F32 xrad = (xx * xlen + yx * ylen + zx * zlen) * invScalex; F32 yrad = (xy * xlen + yy * ylen + zy * zlen) * invScaley; F32 zrad = (xz * xlen + yz * ylen + zz * zlen) * invScalez; Box3F interiorBox; testBox.getCenter(&interiorBox.min); toItr.mulP(interiorBox.min); interiorBox.min.x *= invScalex; interiorBox.min.y *= invScaley; interiorBox.min.z *= invScalez; interiorBox.max = interiorBox.min; interiorBox.min.x -= xrad; interiorBox.min.y -= yrad; interiorBox.min.z -= zrad; interiorBox.max.x += xrad; interiorBox.max.y += yrad; interiorBox.max.z += zrad; U32 waterMark = FrameAllocator::getWaterMark(); U16* hulls = (U16*)FrameAllocator::alloc(mConvexHulls.size() * sizeof(U16)); U32 numHulls = 0; getIntersectingHulls(interiorBox,hulls, &numHulls); if (numHulls == 0) { FrameAllocator::setWaterMark(waterMark); return false; } // we've found all the hulls that intersect the lists interior space bounding box... // now cull out those hulls which don't intersect the oriented bounding box... Point3F radii = testBox.max - testBox.min; radii *= 0.5f; radii.x *= invScalex; radii.y *= invScaley; radii.z *= invScalez; // adjust toItr transform so that origin of source space is box center // Note: center of interior box will be = to transformed center of testBox Point3F center = interiorBox.min + interiorBox.max; center *= 0.5f; toItr.setColumn(3,center); // (0,0,0) now goes where box center used to... for (S32 i=0; ibegin(0, rSurface.planeIndex); for (U32 k = 0; k < rSurface.windingCount; k++) { array[k] = list->addPoint(mPoints[mWindings[rSurface.windingStart + k]].point); list->vertex(array[k]); } list->plane(getFlippedPlane(rSurface.planeIndex)); list->end(); } else { const Interior::Surface& rSurface = mSurfaces[surfaceIndex]; U32 array[32]; U32 fanVerts[32]; U32 numVerts; collisionFanFromSurface(rSurface, fanVerts, &numVerts); list->begin(0, rSurface.planeIndex); for (U32 k = 0; k < numVerts; k++) { array[k] = list->addPoint(mPoints[fanVerts[k]].point); list->vertex(array[k]); } list->plane(getFlippedPlane(rSurface.planeIndex)); list->end(); } } } FrameAllocator::setWaterMark(waterMark); return !list->isEmpty(); } bool Interior::buildLightPolyList(U32* lightSurfaces, U32* numLightSurfaces, const Box3F& box, const MatrixF& /*transform*/, const Point3F& /*scale*/) { static InteriorPolytope pTope; pTope.buildBox(box, MatrixF(true), Point3F(1, 1, 1)); static SurfaceHash hash; hash.clear(); buildPolyList_r(pTope, hash); for (U32 i = 0; i < hash.mNumSurfaces; i++) { U32 surfaceIndex = hash.mSurfaceArray[i]; if (!isNullSurfaceIndex(surfaceIndex)) { lightSurfaces[*numLightSurfaces] = surfaceIndex; (*numLightSurfaces)++; } } return *numLightSurfaces != 0; } //--------------------------------------------------------------------------------- //-------------------------------------- Zone scan. Not really collision, but hey. // struct Edge { U16 p1; U16 p2; Edge(U16 o, U16 t) : p1(o), p2(t) { } }; struct EdgeList { Vector points; Vector edges; }; void Interior::scanZoneNew(InteriorPolytope& polytope, U16* zones, U32* numZones) { PROFILE_START(InteriorScanZoneNew); // Submit the first volume of the poly tope to the bsp tree static InteriorPolytope::VolumeStack stack; stack.reserve(128); stack.setSize(1); stack.last().edgeList = polytope.mVolumeList[0].edgeList; stack.last().nodeIndex = 0; stack.last().popCount = 0; static Vector collPlanes; collPlanes.reserve(64); collPlanes.clear(); while (stack.empty() == false) { InteriorPolytope::StackElement volume = stack.last(); stack.pop_back(); if (isBSPLeafIndex(volume.nodeIndex)) { if (isBSPEmptyLeaf(volume.nodeIndex)) { U16 zone = getBSPEmptyLeafZone(volume.nodeIndex); if (zone != 0x0FFF) { bool insert = true; for (U32 i = 0; i < *numZones; i++) { if (zones[i] == zone) { insert = false; break; } } if (insert) { zones[*numZones] = zone; (*numZones)++; } } } if (volume.popCount) for (U32 i = 0; i < volume.popCount; i++) collPlanes.pop_back(); continue; } const IBSPNode& rNode = mBSPNodes[volume.nodeIndex]; if ((rNode.terminalZone & U16(0x8000)) != 0) { // Hah! we don't need to search any further U16 zone = rNode.terminalZone & (~0x8000); if (zone != 0x7FFF && zone != 0x0FFF) { bool insert = true; for (U32 i = 0; i < *numZones; i++) { if (zones[i] == zone) { insert = false; break; } } if (insert) { zones[*numZones] = zone; (*numZones)++; } } if (volume.popCount) for (U32 i = 0; i < volume.popCount; i++) collPlanes.pop_back(); continue; } // New front and back faces polytope.mFaceList.increment(2); InteriorPolytope::Face& frontFace = polytope.mFaceList.last(); InteriorPolytope::Face& backFace = *(&frontFace - 1); backFace.original = frontFace.original = false; backFace.vertex = frontFace.vertex = 0; // New front and back volumes InteriorPolytope::StackElement frontVolume,backVolume; frontVolume.edgeList = backVolume.edgeList = -1; PlaneF plane = getFlippedPlane(rNode.planeIndex); S32 startVertex = polytope.mVertexList.size(); // Test & clip all the edges S32 sideBase = ++polytope.sideCount << 1; AssertFatal(sideBase != 0, "Well, crap."); for (S32 i = volume.edgeList; i >= 0; i = polytope.mEdgeList[i].next) { // Copy into tmp first to avoid problems with the array. InteriorPolytope::Edge edge = polytope.mEdgeList[i]; InteriorPolytope::Vertex& v0 = polytope.mVertexList[edge.vertex[0]]; if (v0.side < sideBase) v0.side = sideBase + ((plane.distToPlane(v0.point) >= 0)? 0: 1); InteriorPolytope::Vertex& v1 = polytope.mVertexList[edge.vertex[1]]; if (v1.side < sideBase) v1.side = sideBase + ((plane.distToPlane(v1.point) >= 0)? 0: 1); if (v0.side != v1.side) { S32 s = v0.side - sideBase; polytope.intersect(plane,v0.point,v1.point); // Split the edge into each volume polytope.mEdgeList.increment(2); InteriorPolytope::Edge& e0 = polytope.mEdgeList.last(); e0.next = frontVolume.edgeList; frontVolume.edgeList = polytope.mEdgeList.size() - 1; InteriorPolytope::Edge& e1 = *(&e0 - 1); e1.next = backVolume.edgeList; backVolume.edgeList = frontVolume.edgeList - 1; e0.vertex[0] = edge.vertex[s]; e1.vertex[0] = edge.vertex[s ^ 1]; e0.vertex[1] = e1.vertex[1] = polytope.mVertexList.size() - 1; e0.face[0] = e1.face[0] = edge.face[0]; e0.face[1] = e1.face[1] = edge.face[1]; // Add new edges on the plane, one to each volume for (S32 f = 0; f < 2; f++) { InteriorPolytope::Face& face = polytope.mFaceList[edge.face[f]]; if (face.vertex < startVertex) { face.vertex = polytope.mVertexList.size() - 1; } else { polytope.mEdgeList.increment(2); InteriorPolytope::Edge& e0 = polytope.mEdgeList.last(); e0.next = frontVolume.edgeList; frontVolume.edgeList = polytope.mEdgeList.size() - 1; InteriorPolytope::Edge& e1 = *(&e0 - 1); e1.next = backVolume.edgeList; backVolume.edgeList = frontVolume.edgeList - 1; e1.vertex[0] = e0.vertex[0] = face.vertex; e1.vertex[1] = e0.vertex[1] = polytope.mVertexList.size() - 1; e1.face[0] = e0.face[0] = edge.face[f]; e1.face[1] = polytope.mFaceList.size() - 1; e0.face[1] = e1.face[1] - 1; } } } else { if (v0.side == sideBase) { polytope.mEdgeList.push_back(edge); InteriorPolytope::Edge& ne = polytope.mEdgeList.last(); ne.next = frontVolume.edgeList; frontVolume.edgeList = polytope.mEdgeList.size() - 1; } else { polytope.mEdgeList.push_back(edge); InteriorPolytope::Edge& ne = polytope.mEdgeList.last(); ne.next = backVolume.edgeList; backVolume.edgeList = polytope.mEdgeList.size() - 1; } } } // Push the front and back nodes if (frontVolume.edgeList >= 0 && backVolume.edgeList >= 0) { collPlanes.push_back(rNode.planeIndex); frontVolume.nodeIndex = rNode.frontIndex; frontVolume.popCount = volume.popCount + 1; stack.push_back(frontVolume); backVolume.nodeIndex = rNode.backIndex; backVolume.popCount = 0; stack.push_back(backVolume); } else if (frontVolume.edgeList >= 0) { frontVolume.nodeIndex = rNode.frontIndex; frontVolume.popCount = volume.popCount; stack.push_back(frontVolume); } else if (backVolume.edgeList >= 0) { backVolume.nodeIndex = rNode.backIndex; backVolume.popCount = volume.popCount; stack.push_back(backVolume); } else { // Pop off our own planes... if (volume.popCount) for (U32 i = 0; i < volume.popCount; i++) collPlanes.pop_back(); } } AssertFatal(collPlanes.size() == 0, "Unbalanced stack!"); PROFILE_END(); } void Interior::scanZone_r(const U16 node, const Point3F& center, const Point3F& axisx, const Point3F& axisy, const Point3F& axisz, U16* zones, U32* numZones) { if (isBSPLeafIndex(node) == false) { const IBSPNode& rNode = mBSPNodes[node]; const PlaneF& rPlane = getPlane(rNode.planeIndex); PlaneF::Side side = rPlane.whichSideBox(center, axisx, axisy, axisz, Point3F(0, 0, 0)); if (planeIsFlipped(rNode.planeIndex)) side = PlaneF::Side(-S32(side)); switch (side) { case PlaneF::Front: scanZone_r(rNode.frontIndex, center, axisx, axisy, axisz, zones, numZones); break; case PlaneF::Back: scanZone_r(rNode.backIndex, center, axisx, axisy, axisz, zones, numZones); break; case PlaneF::On: scanZone_r(rNode.frontIndex, center, axisx, axisy, axisz, zones, numZones); scanZone_r(rNode.backIndex, center, axisx, axisy, axisz, zones, numZones); break; default: AssertFatal(false, "Misunderstood switchCode in Interior::zoneRay_r"); } return; } if (isBSPEmptyLeaf(node)) { U16 zone = getBSPEmptyLeafZone(node); if (zone != 0x0FFF) { for (U32 i = 0; i < *numZones; i++) { if (zones[i] == zone) return; } zones[*numZones] = zone; (*numZones)++; } } } bool Interior::scanZones(const Box3F& box, const MatrixF& transform, U16* zones, U32* numZones) { // We don't need an exact answer, so let's just blast a box through // the planes and see what we intersect. scanZoneNew is good if you // have an exact volume to clip. Point3F center; box.getCenter(¢er); Point3F xRad((box.max.x - box.min.x) * 0.5, 0, 0); Point3F yRad(0, (box.max.y - box.min.y) * 0.5, 0); Point3F zRad(0, 0, (box.max.z - box.min.z) * 0.5); transform.mulP(center); transform.mulV(xRad); transform.mulV(yRad); transform.mulV(zRad); scanZone_r(0, center, xRad, yRad, zRad, zones, numZones); bool outsideToo = false; if (*numZones != 0) { for (U32 i = 0; i < *numZones; /**/) { if (zones[i]) { i++; continue; } outsideToo = true; zones[i] = zones[(*numZones) - 1]; (*numZones)--; } } else { // If it ain't in us, it's outside us. outsideToo = true; } return outsideToo; } bool Interior::getIntersectingHulls(const Box3F& query, U16* hulls, U32* numHulls) { AssertFatal(*numHulls == 0, "Error, some stuff in the hull vector already!"); // This is paranoia, and I probably wouldn't do it if the tag was 32 bits, but // a possible collision every 65k searches is just a little too small for comfort // DMM if (mSearchTag == 0) { for (U32 i = 0; i < mConvexHulls.size(); i++) mConvexHulls[i].searchTag = 0; mSearchTag = 1; } else { mSearchTag++; } F32 xBinSize = mBoundingBox.len_x() / F32(NumCoordBins); F32 yBinSize = mBoundingBox.len_y() / F32(NumCoordBins); F32 queryMinX = getMax(mBoundingBox.min.x, query.min.x); F32 queryMinY = getMax(mBoundingBox.min.y, query.min.y); F32 queryMaxX = getMin(mBoundingBox.max.x, query.max.x); F32 queryMaxY = getMin(mBoundingBox.max.y, query.max.y); S32 startX = S32(mFloor((queryMinX - mBoundingBox.min.x) / xBinSize)); S32 endX = S32( mCeil((queryMaxX - mBoundingBox.min.x) / xBinSize)); S32 startY = S32(mFloor((queryMinY - mBoundingBox.min.y) / yBinSize)); S32 endY = S32( mCeil((queryMaxY - mBoundingBox.min.y) / yBinSize)); AssertFatal(startX >= 0, "Error, bad startx"); AssertFatal(startY >= 0, "Error, bad starty"); AssertFatal(endX <= NumCoordBins, "Error, bad endX"); AssertFatal(endY <= NumCoordBins, "Error, bad endY"); // Handle non-debug case startX = getMax(startX, 0); endX = getMin(endX, NumCoordBins); startY = getMax(startY, 0); endY = getMin(endY, NumCoordBins); for (S32 i = startX; i < endX; i++) { for (S32 j = startY; j < endY; j++) { const CoordBin& rBin = mCoordBins[i * NumCoordBins + j]; for (U32 k = rBin.binStart; k < rBin.binStart + rBin.binCount; k++) { U16 hullIndex = mCoordBinIndices[k]; ConvexHull& rHull = mConvexHulls[hullIndex]; // Don't check twice! (We're not Santa Claus.) if (rHull.searchTag == mSearchTag) continue; rHull.searchTag = mSearchTag; Box3F qb(rHull.minX, rHull.minY, rHull.minZ, rHull.maxX, rHull.maxY, rHull.maxZ); if (query.isOverlapped(qb)) { hulls[*numHulls] = hullIndex; (*numHulls)++; } } } } return *numHulls != 0; } bool Interior::getIntersectingVehicleHulls(const Box3F& query, U16* hulls, U32* numHulls) { AssertFatal(*numHulls == 0, "Error, some stuff in the hull vector already!"); for (U16 i = 0; i < mVehicleConvexHulls.size(); i++) { ConvexHull& rHull = mVehicleConvexHulls[i]; Box3F qb(rHull.minX, rHull.minY, rHull.minZ, rHull.maxX, rHull.maxY, rHull.maxZ); if (query.isOverlapped(qb)) { hulls[*numHulls] = i; (*numHulls)++; } } return *numHulls != 0; } //-------------------------------------------------------------------------- Box3F InteriorConvex::getBoundingBox() const { return getBoundingBox(mObject->getTransform(), mObject->getScale()); } Box3F InteriorConvex::getBoundingBox(const MatrixF& mat, const Point3F& scale) const { Box3F newBox = box; newBox.min.convolve(scale); newBox.max.convolve(scale); mat.mul(newBox); return newBox; } Point3F InteriorConvex::support(const VectorF& v) const { FrameAllocatorMarker fam; if (hullId >= 0) { AssertFatal(hullId < pInterior->mConvexHulls.size(), "Out of bounds hull!"); const Interior::ConvexHull& rHull = pInterior->mConvexHulls[hullId]; F32* pDots = (F32*)fam.alloc(sizeof(F32) * rHull.hullCount); m_point3F_bulk_dot_indexed(&v.x, &pInterior->mPoints[0].point.x, rHull.hullCount, sizeof(ItrPaddedPoint), &pInterior->mHullIndices[rHull.hullStart], pDots); U32 index = 0; for (U32 i = 1; i < rHull.hullCount; i++) { if (pDots[i] > pDots[index]) index = i; } return pInterior->mPoints[pInterior->mHullIndices[rHull.hullStart + index]].point; } else { S32 actualId = -(hullId + 1); AssertFatal(actualId < pInterior->mVehicleConvexHulls.size(), "Out of bounds hull!"); const Interior::ConvexHull& rHull = pInterior->mVehicleConvexHulls[actualId]; F32* pDots = (F32*)fam.alloc(sizeof(F32) * rHull.hullCount); m_point3F_bulk_dot_indexed(&v.x, &pInterior->mVehiclePoints[0].point.x, rHull.hullCount, sizeof(ItrPaddedPoint), &pInterior->mVehicleHullIndices[rHull.hullStart], pDots); U32 index = 0; for (U32 i = 1; i < rHull.hullCount; i++) { if (pDots[i] > pDots[index]) index = i; } return pInterior->mVehiclePoints[pInterior->mVehicleHullIndices[rHull.hullStart + index]].point; } } void InteriorConvex::getFeatures(const MatrixF& mat, const VectorF& n, ConvexFeature* cf) { S32 i; cf->material = 0; cf->object = mObject; if (hullId >= 0) { // We find the support ourselves here since we want the index too... const Interior::ConvexHull& rHull = pInterior->mConvexHulls[hullId]; U32 spIndex = 0; F32 maxSp = mDot(pInterior->mPoints[pInterior->mHullIndices[rHull.hullStart + spIndex]].point, n); for (i = 1; i < rHull.hullCount; i++) { U32 index = pInterior->mHullIndices[rHull.hullStart + i]; const Point3F& rPoint = pInterior->mPoints[index].point; F32 dot = mDot(rPoint, n); if (dot > maxSp) { spIndex = i; maxSp = dot; } } // Ok, now we have the support point, let's extract the emission string for this // vertex... U32 currPos = 0; const U8* pString = &pInterior->mConvexHullEmitStrings[pInterior->mHullEmitStringIndices[rHull.hullStart + spIndex]]; FrameAllocatorMarker fam; U32* pRemaps = (U32*)fam.alloc(256 * sizeof(U32)); // Ok, this is a piece of cake. Lets dump the points first... U32 numPoints = pString[currPos++]; for (i = 0; i < numPoints; i++) { U32 index = pString[currPos++]; pRemaps[i] = cf->mVertexList.size(); cf->mVertexList.increment(); const Point3F& rPoint = pInterior->mPoints[pInterior->mHullIndices[rHull.hullStart + index]].point; mat.mulP(rPoint, &cf->mVertexList.last()); } // Then the edges... U32 numEdges = pString[currPos++]; for (i = 0; i < numEdges; i++) { U32 index0 = pString[currPos++]; U32 index1 = pString[currPos++]; cf->mEdgeList.increment(); cf->mEdgeList.last().vertex[0] = pRemaps[index0]; cf->mEdgeList.last().vertex[1] = pRemaps[index1]; } // Then the polys... U32 numPolys = pString[currPos++]; for (i = 0; i < numPolys; i++) { U32 vertexCount = pString[currPos++]; U32 planeIndex = pString[currPos++]; U32 verts[3]; verts[0] = pString[currPos++]; verts[1] = pString[currPos++]; for (U32 j = 2; j < vertexCount; j++) { verts[2] = pString[currPos++]; // Emit this poly cf->mFaceList.increment(); cf->mFaceList.last().normal = pInterior->getPlane(pInterior->mHullPlaneIndices[rHull.planeStart + planeIndex]); cf->mFaceList.last().vertex[0] = pRemaps[verts[0]]; cf->mFaceList.last().vertex[1] = pRemaps[verts[1]]; cf->mFaceList.last().vertex[2] = pRemaps[verts[2]]; PlaneF plane(cf->mVertexList[pRemaps[verts[0]]], cf->mVertexList[pRemaps[verts[1]]], cf->mVertexList[pRemaps[verts[2]]]); cf->mFaceList.last().normal = plane; // Shift the fan over verts[1] = verts[2]; } } } else { S32 actualId = -(hullId + 1); // We find the support ourselves here since we want the index too... const Interior::ConvexHull& rHull = pInterior->mVehicleConvexHulls[actualId]; U32 spIndex = 0; F32 maxSp = mDot(pInterior->mVehiclePoints[pInterior->mVehicleHullIndices[rHull.hullStart + spIndex]].point, n); for (i = 1; i < rHull.hullCount; i++) { U32 index = pInterior->mVehicleHullIndices[rHull.hullStart + i]; const Point3F& rPoint = pInterior->mVehiclePoints[index].point; F32 dot = mDot(rPoint, n); if (dot > maxSp) { spIndex = i; maxSp = dot; } } // Ok, now we have the support point, let's extract the emission string for this // vertex... U32 currPos = 0; const U8* pString = &pInterior->mVehicleConvexHullEmitStrings[ pInterior->mVehicleHullEmitStringIndices[rHull.hullStart + spIndex] ]; FrameAllocatorMarker fam; U32* pRemaps = (U32*)fam.alloc(256 * sizeof(U32)); // Ok, this is a piece of cake. Lets dump the points first... U32 numPoints = pString[currPos++]; for (i = 0; i < numPoints; i++) { U32 index = pString[currPos++]; pRemaps[i] = cf->mVertexList.size(); cf->mVertexList.increment(); const Point3F& rPoint = pInterior->mVehiclePoints[pInterior->mVehicleHullIndices[rHull.hullStart + index]].point; mat.mulP(rPoint, &cf->mVertexList.last()); } // Then the edges... U32 numEdges = pString[currPos++]; for (i = 0; i < numEdges; i++) { U32 index0 = pString[currPos++]; U32 index1 = pString[currPos++]; cf->mEdgeList.increment(); cf->mEdgeList.last().vertex[0] = pRemaps[index0]; cf->mEdgeList.last().vertex[1] = pRemaps[index1]; } // Then the polys... U32 numPolys = pString[currPos++]; for (i = 0; i < numPolys; i++) { U32 vertexCount = pString[currPos++]; U32 planeIndex = pString[currPos++]; U32 verts[3]; verts[0] = pString[currPos++]; verts[1] = pString[currPos++]; for (U32 j = 2; j < vertexCount; j++) { verts[2] = pString[currPos++]; // Emit this poly cf->mFaceList.increment(); cf->mFaceList.last().vertex[0] = pRemaps[verts[0]]; cf->mFaceList.last().vertex[1] = pRemaps[verts[1]]; cf->mFaceList.last().vertex[2] = pRemaps[verts[2]]; cf->mFaceList.last().normal = pInterior->getPlane(planeIndex); // Shift the fan over verts[1] = verts[2]; } } } } void InteriorConvex::getPolyList(AbstractPolyList* list) { // Setup collision state data { list->setTransform(&mObject->getTransform(), mObject->getScale()); list->setObject(mObject); } if (hullId >= 0) { // Get our hull const Interior::ConvexHull& rHull = pInterior->mConvexHulls[hullId]; // Build up the lists of points and strings const U8* pString = &pInterior->mPolyListStrings[rHull.polyListStringStart]; U32 currPos = 0; U32 numPlanes = pString[currPos++]; // It can happen that a hull has no collision surfaces. In that case, just bail out // here... if (numPlanes == 0) return; const U8* planeString = &pString[currPos]; currPos += numPlanes; U32 numPoints = pString[currPos++] << 8; numPoints |= pString[currPos++]; const U8* pointString = &pString[currPos]; currPos += numPoints; U32 numSurfaces = pString[currPos++]; const U16* planeIndices = &pInterior->mPolyListPlanes[rHull.polyListPlaneStart]; const U32* pointIndices = &pInterior->mPolyListPoints[rHull.polyListPointStart]; //-------------------------------------- // At this point, currPos is pointing to the first surface in the string //-------------------------------------- // First thing to do: build the interest mask, by seeing if the list is interested // in our planes... U8 interestMask = 0; U32 remappedPlaneBase; { U16 planeIndex = planeIndices[0]; PlaneF plane = pInterior->getPlane(planeIndex); if (Interior::planeIsFlipped(planeIndex)) plane.neg(); remappedPlaneBase = list->addPlane(plane); if (list->isInterestedInPlane(remappedPlaneBase)) interestMask |= planeString[0]; for (U32 i = 1; i < numPlanes; i++) { planeIndex = planeIndices[i]; plane = pInterior->getPlane(planeIndex); if (Interior::planeIsFlipped(planeIndex)) plane.neg(); list->addPlane(plane); if (list->isInterestedInPlane(remappedPlaneBase + i)) interestMask |= planeString[i]; } } // Now, whip through the points, and build up the remap table, adding only // those points that the list is interested in. Note that we use the frameAllocator // to get enoughMemory to deal with the variable sized remap array FrameAllocatorMarker fam; U32* pointRemapTable = reinterpret_cast(fam.alloc(numPoints * sizeof(U32))); { for (U32 i = 0; i < numPoints; i++) { if ((interestMask & pointString[i]) != 0) { const Point3F& rPoint = pInterior->mPoints[pointIndices[i]].point; pointRemapTable[i] = list->addPoint(rPoint); } } } // Now, whip through the surfaces, checking to make sure that we're interested in // that poly as we go. At this point, currPos should point to the first surface. // The format of the surface string can be found in interior.cc, in the // processHullPolyLists function { for (U32 i = 0; i < numSurfaces; i++) { U32 snPoints = pString[currPos++]; U32 sMask = pString[currPos++]; U32 sPlane = pString[currPos++]; if ((interestMask & sMask) != 0) { // Add the poly // list->begin(0, planeIndices[sPlane]); for (U32 j = 0; j < snPoints; j++) { U16 remappedIndex = pString[currPos++] << 8; remappedIndex |= pString[currPos++]; remappedIndex = pointRemapTable[remappedIndex]; list->vertex(remappedIndex); } list->plane(remappedPlaneBase + sPlane); list->end(); } else { // Superflous poly, just skip past the points currPos += snPoints * 2; } } } } else { S32 actualId = -(hullId + 1); // Get our hull const Interior::ConvexHull& rHull = pInterior->mVehicleConvexHulls[actualId]; // Build up the lists of points and strings const U8* pString = &pInterior->mVehiclePolyListStrings[rHull.polyListStringStart]; U32 currPos = 0; U32 numPlanes = pString[currPos++]; // It can happen that a hull has no collision surfaces. In that case, just bail out // here... if (numPlanes == 0) return; const U8* planeString = &pString[currPos]; currPos += numPlanes; U32 numPoints = pString[currPos++] << 8; numPoints |= pString[currPos++]; const U8* pointString = &pString[currPos]; currPos += numPoints; U32 numSurfaces = pString[currPos++]; const U16* planeIndices = &pInterior->mVehiclePolyListPlanes[rHull.polyListPlaneStart]; const U32* pointIndices = &pInterior->mVehiclePolyListPoints[rHull.polyListPointStart]; //-------------------------------------- // At this point, currPos is pointing to the first surface in the string //-------------------------------------- // First thing to do: build the interest mask, by seeing if the list is interested // in our planes... U8 interestMask = 0; U32 remappedPlaneBase; { U16 planeIndex = planeIndices[0]; PlaneF plane = pInterior->getPlane(planeIndex); if (Interior::planeIsFlipped(planeIndex)) plane.neg(); remappedPlaneBase = list->addPlane(plane); if (list->isInterestedInPlane(remappedPlaneBase)) interestMask |= planeString[0]; for (U32 i = 1; i < numPlanes; i++) { planeIndex = planeIndices[i]; plane = pInterior->getPlane(planeIndex); if (Interior::planeIsFlipped(planeIndex)) plane.neg(); list->addPlane(plane); if (list->isInterestedInPlane(remappedPlaneBase + i)) interestMask |= planeString[i]; } } // Now, whip through the points, and build up the remap table, adding only // those points that the list is interested in. Note that we use the frameAllocator // to get enoughMemory to deal with the variable sized remap array FrameAllocatorMarker fam; U32* pointRemapTable = reinterpret_cast(fam.alloc(numPoints * sizeof(U32))); { for (U32 i = 0; i < numPoints; i++) { if ((interestMask & pointString[i]) != 0) { const Point3F& rPoint = pInterior->mVehiclePoints[pointIndices[i]].point; pointRemapTable[i] = list->addPoint(rPoint); } } } // Now, whip through the surfaces, checking to make sure that we're interested in // that poly as we go. At this point, currPos should point to the first surface. // The format of the surface string can be found in interior.cc, in the // processHullPolyLists function { for (U32 i = 0; i < numSurfaces; i++) { U32 snPoints = pString[currPos++]; U32 sMask = pString[currPos++]; U32 sPlane = pString[currPos++]; if ((interestMask & sMask) != 0) { // Add the poly // list->begin(0, planeIndices[sPlane]); for (U32 j = 0; j < snPoints; j++) { U16 remappedIndex = pString[currPos++] << 8; remappedIndex |= pString[currPos++]; remappedIndex = pointRemapTable[remappedIndex]; list->vertex(remappedIndex); } list->plane(remappedPlaneBase + sPlane); list->end(); } else { // Superflous poly, just skip past the points currPos += snPoints * 2; } } } } }