//----------------------------------------------------------------------------- // V12 Engine // // Copyright (c) 2001 GarageGames.Com // Portions Copyright (c) 2001 by Sierra Online, Inc. //----------------------------------------------------------------------------- #include "ai/graphFloorPlan.h" #include "console/consoleTypes.h" #include "interior/interiorResObjects.h" #include "game/gameBase.h" #include "ai/graphData.h" #include "core/fileStream.h" //---------------------------------------------------------------- // // FloorPlan Implementation // //---------------------------------------------------------------- FloorPlan *gFloorPlan = NULL; IMPLEMENT_CONOBJECT(FloorPlan); static F32 sCollinearDist = 0.01; static F32 sParallelDot = 0.992; Point3F FloorPlan::sDebugBreakPoint(-11.31f, -132.0f, 131.4f); F32 FloorPlan::sFloorThresh = 70; F32 FloorPlan::sMinDivRadius = 0.8; F32 FloorPlan::sMaxDivRadius = 20; bool FloorPlan::sSubDivide = true; bool FloorPlan::sDrawConnections = true; bool FloorPlan::sDrawVolumeHts = false; F32 FloorPlan::sHeightDiff = 10; F32 FloorPlan::sSpecialMaxRadius = 1; bool FloorPlan::sUseSpecial = false; S32 FloorPlan::amountToDivide = 3; bool FloorPlan::sDisableAsserts = false; bool FloorPlan::LogMode = false; FloorPlan::FloorPlan() { mGraphDetails = NULL; currInterior = NULL; currInstance = NULL; mNumInteriors = 0; mExtraInteriors = 0; mDataCollected = false; newLog = true; numZones = 0; } //------------------------------------------------------------------------------------- #define _FP_DEBUG_ 1 #if _FP_DEBUG_ static void debugStopPt() { } #endif //---------------------------------------------------------------------------- FloorPlan::~FloorPlan() { if(mGraphDetails) delete [] mGraphDetails; } //---------------------------------------------------------------------------- bool FloorPlan::onAdd() { if(!Parent::onAdd()) return false; gFloorPlan = this; return true; } //---------------------------------------------------------------------------- void FloorPlan::onRemove() { Parent::onRemove(); gFloorPlan = NULL; } //---------------------------------------------------------------------------- static void findObjectsCallback(SceneObject *obj, S32 val) { Vector *list = (Vector *)val; list->push_back(obj); } //---------------------------------------------------------------------------- // Sift though all the objects that are in the Server container. // Extract the data if its an interior. //---------------------------------------------------------------------------- void FloorPlan::snoopInteriors() { U32 mask = InteriorObjectType; Vector objects; gServerContainer.findObjects(mask, findObjectsCallback, (S32)&objects); mNumInteriors = objects.size(); mGraphDetails = new GraphDetails[mNumInteriors+1024]; mSlopeThresh = mCos(mDegToRad(sFloorThresh)); for(S32 i = 0; i < mNumInteriors; i++) { InteriorInstance *intInstant = dynamic_cast(objects[i]); MatrixF const &transform = intInstant->getTransform(); Point3F const &scale = intInstant->getScale(); Interior *interior = intInstant->getDetailLevel(0); currInterior = interior; currInstance = intInstant; numZones = interior->mZones.size(); #if !DEBUGMODE InteriorDetail detail; #endif detail.mIndex = i; /* char buffer[256]; dSprintf(buffer, sizeof(buffer), "interiors/%s", intInstant->mInteriorFileName); Resource interiorRes; interiorRes = ResourceManager->load(buffer); */ // Special nodes (basically just the chute hints) for(S32 g = 0; g < intInstant->mInteriorRes->getNumSpecialNodes(); g++) { AISpecialNode * node = intInstant->mInteriorRes->getSpecialNode(g); // Scale and transform to world space Point3F loc = node->mPos; loc.x *= scale.x; loc.y *= scale.y; loc.z *= scale.z; transform.mulP(loc); mGraphDetails[i].chutes.push_back(loc); // DSpecNode sNode; // sNode.pos = node->mPos; // dStricmp(node->mName, sNode.name); // sNode.pos.x *= scale.x; // sNode.pos.y *= scale.y; // sNode.pos.z *= scale.z; // transform.mulP(sNode.pos); // detail.mSpecialNodes.push_back(sNode); } detail.mPoints.setSize(interior->mPoints.size()); for(S32 j = 0; j < detail.mPoints.size(); j++) { detail.mPoints[j].e = NULL; detail.mPoints[j].x = interior->mPoints[j].point.x; detail.mPoints[j].y = interior->mPoints[j].point.y; detail.mPoints[j].z = interior->mPoints[j].point.z; detail.mPoints[j].x *= scale.x; detail.mPoints[j].y *= scale.y; detail.mPoints[j].z *= scale.z; transform.mulP(detail.mPoints[j]); detail.mPoints[j].flags.clear(); } detail.mNumUnObstructed = 0; // extract all plane information extractData(interior, &detail, transform); #if ITERATEMODE for(S32 divideCount = 0; divideCount < amountToDivide; divideCount++) { Con::printf(""); Con::printf(""); Con::printf(""); Con::printf("interation: %d", divideCount); subdivideSurfaces(&detail); } #else // subdivide until we have a workable graph for this interior while(subdivideSurfaces(&detail) > 0) mDivLevel++; #endif // sort surface list based on zone sortSurfaceList(&detail); // connect all volumes buildConnections(&detail); // extract the graph graphExtraction(&detail, &(mGraphDetails[i])); buildNodesFromPortals(&detail); log(avar("Interior %d Volumes: %d", i, detail.mVolumes.size())); #if !DEBUGMODE // clean up memory... YAY!! detail.mEdgeTable.clear(); for(S32 x = 0; x < detail.mSurfaces.size(); x++) delete [] detail.mSurfaces[x].mEdges; #endif } #if !DEBUGMODE // external static shape hook buildStaticShapeCenterNodes(); buildStaticShapeGeometryNodes(); #endif } //---------------------------------------------------------------------------- void FloorPlan::log(const char *string) { if(!LogMode) return; FileStream LogFile; LogFile.open("GenMetrics.log", FileStream::ReadWrite); if(LogFile.getStatus() == Stream::Ok) { LogFile.setPosition(LogFile.getStreamSize()); LogFile.write(dStrlen(string), string); LogFile.write(2, "\r\n"); } LogFile.close(); } //---------------------------------------------------------------------------- // The whole idea here is too extract all data from the passed interior. // Once all data has been extracted, surfaces that won't be needed // ex: surfaces that a bot can't traverse. are thrown out. // What we should end up with are floor surfaces, which will then be // flooded with nodes(subdivision) and finally connected together to // form the graph for this interior //---------------------------------------------------------------------------- void FloorPlan::extractData(Interior *interior, InteriorDetail *detail, const MatrixF &transform) { detail->mSurfaces.setSize(interior->mSurfaces.size()); Interior::Surface surf; // extract out the portals we need from this interior extractPortals(interior, detail, transform); U32 i; for(i = 0; i < detail->mSurfaces.size(); i++) { // grab a surface.... surf = interior->mSurfaces[i]; DSurf *currSurf = &(detail->mSurfaces[i]); currSurf->mFlags.clear(); currSurf->mFlipStates.clear(); S32 vertCount = surf.windingCount; currSurf->mEdges = new DEdge *[vertCount]; currSurf->mDivLevel = 0; currSurf->parent = NULL; currSurf->mSubSurfCount = 0; // go ahead and extract interior->collisionFanFromSurface(surf, currSurf->fullWinding, &(currSurf->mNumEdges)); // cull out all unwanted surfaces. Ceilings...ect PlaneF plane = interior->getPlane(surf.planeIndex); if(Interior::planeIsFlipped(surf.planeIndex)) plane.neg(); VectorF normal = plane; transform.mulV(normal); currSurf->mNormal = normal; if(normal.z <= 0) { currSurf->mFlags.set(DSurf::isWall); currSurf->mFlags.set(DSurf::shouldBeCulled); } // what kind of slope does this surface have? if(!currSurf->mFlags.test(DSurf::shouldBeCulled)) { F32 dot = mDot(normal, VectorF(0, 0, 1)); if(dot < mSlopeThresh) currSurf->mFlags.set(DSurf::shouldBeCulled); } // keep a handle to this surface for quick traversable // surface only operations if(!currSurf->mFlags.test(DSurf::shouldBeCulled)) detail->mTraversable.push_back(i); } // build all edges for this interior buildEdgeTable(detail); buildZoneInfo(detail); } //-------------------------------------------------------------------------- void FloorPlan::sortSurfaceList(InteriorDetail *d) { d->sortedSurfList.clear(); d->sortedSurfList = d->mUnobstructed; d->mUnobstructed.clear(); for(S32 zone = -1; zone < numZones; zone++) { for(S32 surfCount = 0; surfCount < d->sortedSurfList.size(); surfCount++) { U32 key = d->sortedSurfList[surfCount]; DSurf &surf = d->mSurfaces[key]; if(surf.mZone == zone) d->mUnobstructed.push_back(key); } } } //-------------------------------------------------------------------------- void FloorPlan::buildZoneInfo(InteriorDetail *d) { S32 surfCount = d->mTraversable.size(); DPoint center; MatrixF transform = currInstance->mWorldToObj; Point3F scale = currInstance->getScale(); for(S32 j = 0; j < surfCount; j++) { S32 whichSurface = d->mTraversable[j]; DSurf &surf = d->mSurfaces[whichSurface]; createCenterNode(d, &surf, ¢er); center.z += 1; transform.mulP(center); center.x /= scale.x; center.y /= scale.y; center.z /= scale.z; surf.mZone = currInterior->getZoneForPoint(center); } } //-------------------------------------------------------------------------- static const F32 sPortalThresh = mCos(mDegToRad(20.0f)); void FloorPlan::extractPortals(Interior *i, InteriorDetail *d, const MatrixF &transform) { S32 count = i->mPortals.size(); for(S32 pIdx = 0; pIdx < count; pIdx++) { Interior::Portal *sourcePortal = &(i->mPortals[pIdx]); U32 index = sourcePortal->planeIndex; PlaneF plane = i->getPlane(index); if(Interior::planeIsFlipped(index)) plane.neg(); VectorF normal = plane; transform.mulV(normal); if(mFabs(normal.z) > sPortalThresh) { DPortal portal; if(buildPortalCenter(d, i, sourcePortal, &portal)) d->mPortals.push_back(portal); } else continue; } } //-------------------------------------------------------------------------- bool FloorPlan::buildPortalCenter(InteriorDetail *d, Interior *interior, Interior::Portal *sourcePortal, DPortal *portal) { // extract the trifans and find center Point3F center(0, 0, 0); //Point3F start(interior->mPoints[interior->mWindings[interior->mWindingIndices[sourcePortal->triFanStart]]].point); //transform.mulP(start); for(U32 j = 0; j < sourcePortal->triFanCount; j++) { const Interior::TriFan &fan = interior->mWindingIndices[sourcePortal->triFanStart + j]; U32 numPoints = fan.windingCount; if(!numPoints) continue; for(U32 k = 0; k < numPoints; k++) { const Point3F &pt = d->mPoints[interior->mWindings[fan.windingStart + k]]; center += pt; } center /= numPoints; portal->center = center; } // only use portals that are big enough for players to fit thru if(1)//(center - start).len() >= 1.0) return true; else return false; } //-------------------------------------------------------------------------- void FloorPlan::buildEdgeTable(InteriorDetail *detail) { U32 i, k; for(i = 0; i < detail->mSurfaces.size(); i++) { DSurf *currSurf = &(detail->mSurfaces[i]); for(k = 0; k < currSurf->mNumEdges; k++) { DEdge edge; edge.left = NULL; edge.right = NULL; edge.start = currSurf->fullWinding[k]; edge.flags.clear(); // edge.totalBelongsTo = 0; if(!currSurf->mFlags.test(DSurf::shouldBeCulled)) edge.flags.set(DEdge::traversable); if(k == currSurf->mNumEdges - 1) edge.end = currSurf->fullWinding[0]; else edge.end = currSurf->fullWinding[k+1]; if(!detail->mEdgeTable.contains(edge)) { edge.length = (detail->mPoints[edge.end] - detail->mPoints[edge.start]).len(); detail->mEdgeTable.insert(edge); currSurf->mEdges[k] = detail->mEdgeTable.find(edge); detail->mPoints[edge.start].e = currSurf->mEdges[k]; } else { currSurf->mEdges[k] = detail->mEdgeTable.find(edge); if(!detail->mPoints[edge.start].e) detail->mPoints[edge.start].e = currSurf->mEdges[k]; if(edge.start != currSurf->mEdges[k]->start) currSurf->mFlipStates.set(BIT(k)); } } // done! compute some more. findLongestEdge(currSurf); computeSurfaceRadius(detail, currSurf); if(currSurf->mFlags.test(DSurf::isWall)) setToWallNodes(detail, *currSurf); } } //------------------------------------------------------------------------- // Handle thin nodes differently. bool FloorPlan::shouldDivideSurf(DSurf &surf) { if (surf.mMinRadius < 0.4) { if (surf.mMinRadius > 0.2) { bool canDiv = surf.mMaxRadius >= 2.5; if(canDiv) return true; else { surf.mFlags.set(DSurf::tooSmall); return false; } } else { surf.mFlags.set(DSurf::tooSmall); return false; } } else return surf.mMaxRadius >= sMinDivRadius; } //-------------------------------------------------------------------------- void FloorPlan::setToWallNodes(InteriorDetail *d, DSurf &surf) { U32 i; for(i = 0; i < surf.mNumEdges; i++) { d->mPoints[surf.mEdges[i]->start].flags.set(DPoint::wall); d->mPoints[surf.mEdges[i]->end].flags.set(DPoint::wall); } } //-------------------------------------------------------------------------- U32 FloorPlan::subdivideSurfaces(InteriorDetail *d) { U32 totalDivided = 0; // Traversable array grows during below loop - we only want to do the ones that // are ready at this iteration of the divide. S32 surfCount = d->mTraversable.size(); for(S32 j = 0; j < surfCount; j++) { S32 whichSurface = d->mTraversable[j]; DSurf &surf = d->mSurfaces[whichSurface]; if(surf.mNumEdges >= 32) continue; if (shouldDivideSurf(surf)) { if(!surf.mFlags.test(DSurf::unObstructed | DSurf::divided)) { // check for obstructions DVolume vol; surfCollisionInfo surfColInfo; if((obstructedSurf(d, surf, &vol, surfColInfo)) || (surf.mMaxRadius > sMaxDivRadius)) { // something is obstructing this surf, so divided it down // so as to isolate the surfaces that are obstructed if(!surfColInfo.specialCase) { //Con::printf("subdividing surface %d", whichSurface); subdivideSurface(d, surf, whichSurface); } else { //Con::printf("split surface %d", whichSurface); //if(whichSurface == 26) //subdivideSurface(d, surf, whichSurface); //else splitSurface(d, surf, whichSurface, surfColInfo); } totalDivided++; } /* even though this surface is traversable we need to check if there are some little holes in the ceiling that players can jet out of. If there are, just divide more. else if(offHeightDistribution(d, surf)) { subdivideSurface(d, surf, whichSurface); totalDivided++; } this surface is unobstructed!*/ else { surf.mFlags.set(DSurf::unObstructed); createCenterNode(d, &surf, &(surf.mCntr)); vol.capPt.x = surf.mCntr.x; vol.capPt.y = surf.mCntr.y; vol.capPt.z = (surf.mCntr.z + vol.ht); vol.surfIdx = whichSurface; surf.volIdx = d->mVolumes.size(); d->mVolumes.push_back(vol); d->mUnobstructed.push_back(whichSurface); } } } } return totalDivided; } //---------------------------------------------------------------------------- void FloorPlan::subdivideSurface(InteriorDetail *d, DSurf &surf, U32 surfIdx) { BitSet32 edgeTracker; U32 newSurfCount = 0; DEdge *edgePtr; U32 i; for(i = 0; i < surf.mNumEdges; i++) { edgePtr = surf.mEdges[i]; if(shouldDivideEdge(surf, edgePtr)) { edgeTracker.set(BIT(i)); newSurfCount++; if(!edgePtr->flags.test(DEdge::divided)) subdivideEdge(d, edgePtr, 0.5f); } } // we have a special case here if(newSurfCount < 2) { edgeTracker.clear(); newSurfCount = 0; for(i = 0; i < surf.mNumEdges; i++) { edgePtr = surf.mEdges[i]; if(!shouldDivideEdge(surf, edgePtr) && edgePtr->length >= 2) { edgeTracker.set(BIT(i)); subdivideEdge(d, edgePtr, 0.5f); newSurfCount++; } } if(newSurfCount < 2) { surf.mFlags.set(DSurf::divided); return; } } // create all the new surfaces surf.mSubSurfCount = newSurfCount; DEdge **edges = new DEdge *[32]; if(newSurfCount > 2) { DPoint center; createCenterNode(d, &surf, ¢er); center.flags.clear(); center.flags.set(DPoint::divGenerated | DPoint::center); d->mPoints.push_back(center); U32 centIdx = (d->mPoints.size() - 1); generateNewEdges(d, centIdx, surf, edges, edgeTracker); createNewSurfaces(d, surfIdx, edges, edgeTracker); } else { newSurfacesSpecialCase(d, surfIdx, edgeTracker); } delete [] edges; } //---------------------------------------------------------------------------- void FloorPlan::splitSurface(InteriorDetail *d, DSurf &surf, U32 surfIdx, surfCollisionInfo &info) { BitSet32 edgeTracker; surf.mSubSurfCount = 2; F32 newFactor = info.factor - (0.1 / surf.mEdges[info.divideEdge1]->length); edgeTracker.set(BIT(info.divideEdge1)); edgeTracker.set(BIT(info.divideEdge2)); if(!surf.mFlipStates.test(BIT(info.divideEdge1))) subdivideEdge(d, surf.mEdges[info.divideEdge1], newFactor); else subdivideEdge(d, surf.mEdges[info.divideEdge1], 1 - newFactor); if(!surf.mFlipStates.test(BIT(info.divideEdge2))) subdivideEdge(d, surf.mEdges[info.divideEdge2], 1 - newFactor); else subdivideEdge(d, surf.mEdges[info.divideEdge2], newFactor); newSurfacesSpecialCase(d, surfIdx, edgeTracker); } //---------------------------------------------------------------------------- void FloorPlan::newSurfacesSpecialCase(InteriorDetail *d, U32 surfIdx, BitSet32 &edgeTracker) { U32 edgeIdx[2]; U32 count = 0; d->mSurfaces[surfIdx].mFlags.set(DSurf::split); d->mSurfaces[surfIdx].mFlags.set(DSurf::divided); U32 i; for(i = 0; i < d->mSurfaces[surfIdx].mNumEdges; i++) if(edgeTracker.test(BIT(i))) edgeIdx[count++] = i; DEdge *edge1 = d->mSurfaces[surfIdx].mEdges[edgeIdx[0]]; DEdge *edge2 = d->mSurfaces[surfIdx].mEdges[edgeIdx[1]]; DEdge midEdge; midEdge.start = edge1->midPoint; midEdge.end = edge2->midPoint; midEdge.flags.set(DEdge::divGenerated); midEdge.length = (d->mPoints[midEdge.end] - d->mPoints[midEdge.start]).len(); d->mEdgeTable.insert(midEdge); DEdge *edge = d->mEdgeTable.find(midEdge); // start with a divided edge U32 edgeCount = 0; // keeps track of the master surf's edge idx while(!edgeTracker.test(BIT(edgeCount))) edgeCount++; for(i = 0; i < 2; i++) { DSurf newSurf; DSurf *currSurf = &newSurf; currSurf->mEdges = new DEdge *[32]; currSurf->mDivLevel = 0; currSurf->mNormal = d->mSurfaces[surfIdx].mNormal; currSurf->mFlags.clear(); currSurf->mFlipStates.clear(); currSurf->parent = &(d->mSurfaces[surfIdx]); currSurf->mSubSurfCount = 0; currSurf->mZone = d->mSurfaces[surfIdx].mZone; U32 edgeIdx = 0; // keeps track of new surf edge idx's // add the first edge to the new surf's edge list if(!d->mSurfaces[surfIdx].mFlipStates.test(BIT(edgeCount))) { currSurf->mEdges[edgeIdx++] = d->mSurfaces[surfIdx].mEdges[edgeCount]->right; } else { currSurf->mEdges[edgeIdx++] = d->mSurfaces[surfIdx].mEdges[edgeCount]->left; currSurf->mFlipStates.set(BIT(edgeIdx - 1)); } // wrap around if(edgeCount == (d->mSurfaces[surfIdx].mNumEdges - 1)) edgeCount = 0; else edgeCount++; // keep adding edges if they are not divided while(!edgeTracker.test(BIT(edgeCount))) { if(d->mSurfaces[surfIdx].mFlipStates.test(BIT(edgeCount))) currSurf->mFlipStates.set(BIT(edgeIdx)); currSurf->mEdges[edgeIdx++] = d->mSurfaces[surfIdx].mEdges[edgeCount]; // wrap around if(edgeCount == d->mSurfaces[surfIdx].mNumEdges - 1) edgeCount = 0; else edgeCount++; } // add the last generated edges to complete this surf if(!d->mSurfaces[surfIdx].mFlipStates.test(BIT(edgeCount))) { currSurf->mEdges[edgeIdx++] = d->mSurfaces[surfIdx].mEdges[edgeCount]->left; } else { currSurf->mEdges[edgeIdx] = d->mSurfaces[surfIdx].mEdges[edgeCount]->right; currSurf->mFlipStates.set(BIT(edgeIdx)); edgeIdx++; } currSurf->mEdges[edgeIdx] = edge; edgeIdx++; if(!i) currSurf->mFlipStates.set(BIT(edgeIdx - 1)); // update total edges for this surf currSurf->mNumEdges = edgeIdx; // this surf is created, update other members. findLongestEdge(currSurf); computeSurfaceRadius(d, currSurf); d->mSurfaces[surfIdx].mSubSurfs[i] = d->mSurfaces.size(); d->mSurfaces.push_back(newSurf); d->mTraversable.push_back(d->mSurfaces.size() - 1); } } //---------------------------------------------------------------------------- void FloorPlan::createNewSurfaces(InteriorDetail *d, U32 surfIdx, DEdge **edges, BitSet32 &edgeTracker) { // start with a divided edge U32 edgeCount = 0; // keeps track of the outer surf's edge idx while(!edgeTracker.test(BIT(edgeCount))) edgeCount++; U32 i; for(i = 0; i < d->mSurfaces[surfIdx].mSubSurfCount; i++) { DSurf newSurf; DSurf *currSurf = &newSurf; currSurf->mEdges = new DEdge *[32]; currSurf->mDivLevel = 0; currSurf->mNormal = d->mSurfaces[surfIdx].mNormal; // all new surfs are co-planar currSurf->mFlags.clear(); currSurf->mFlipStates.clear(); currSurf->parent = &(d->mSurfaces[surfIdx]); currSurf->mSubSurfCount = 0; currSurf->mZone = d->mSurfaces[surfIdx].mZone; U32 edgeIdx = 0; // keeps track of new surf edge idx's U32 startEdge = edgeCount; // keeps track of which edge we started at // add the first edge to the new surf's edge list if(!d->mSurfaces[surfIdx].mFlipStates.test(BIT(edgeCount))) { currSurf->mEdges[edgeIdx++] = d->mSurfaces[surfIdx].mEdges[edgeCount]->right; } else { currSurf->mEdges[edgeIdx++] = d->mSurfaces[surfIdx].mEdges[edgeCount]->left; currSurf->mFlipStates.set(BIT(edgeIdx - 1)); } // wrap around if(edgeCount == (d->mSurfaces[surfIdx].mNumEdges - 1)) edgeCount = 0; else edgeCount++; // keep adding edges if they are not divided while(!edgeTracker.test(BIT(edgeCount))) { if(d->mSurfaces[surfIdx].mFlipStates.test(BIT(edgeCount))) currSurf->mFlipStates.set(BIT(edgeIdx)); currSurf->mEdges[edgeIdx++] = d->mSurfaces[surfIdx].mEdges[edgeCount]; // wrap around if(edgeCount == d->mSurfaces[surfIdx].mNumEdges - 1) edgeCount = 0; else edgeCount++; } // add the last generated edges to complete this surf if(!d->mSurfaces[surfIdx].mFlipStates.test(BIT(edgeCount))) { currSurf->mEdges[edgeIdx++] = d->mSurfaces[surfIdx].mEdges[edgeCount]->left; } else { currSurf->mEdges[edgeIdx++] = d->mSurfaces[surfIdx].mEdges[edgeCount]->right; currSurf->mFlipStates.set(BIT(edgeIdx - 1)); } currSurf->mEdges[edgeIdx++] = edges[edgeCount]; currSurf->mEdges[edgeIdx++] = edges[startEdge]; // this will be flipped for sure currSurf->mFlipStates.set(BIT(edgeIdx - 1)); currSurf->mNumEdges = edgeIdx; // this surf is created, update other members. findLongestEdge(currSurf); computeSurfaceRadius(d, currSurf); d->mSurfaces[surfIdx].mSubSurfs[i] = d->mSurfaces.size(); d->mSurfaces.push_back(newSurf); d->mTraversable.push_back(d->mSurfaces.size() - 1); } d->mSurfaces[surfIdx].mFlags.set(DSurf::divided); } //---------------------------------------------------------------------------- void FloorPlan::findLongestEdge(DSurf *surf) { F32 longest = 0; DEdge *longPtr; DEdge *edgePtr; U32 i; for(i = 0; i < surf->mNumEdges; i++) { edgePtr = surf->mEdges[i]; if(edgePtr->length > longest) { longPtr = edgePtr; longest = edgePtr->length; } } surf->mLongest = longPtr; } //---------------------------------------------------------------------------- void FloorPlan::subdivideEdge(InteriorDetail *d, DEdge *edge, F32 factor) { DPoint &p1 = d->mPoints[edge->start]; DPoint &p2 = d->mPoints[edge->end]; // subdivide the edge DPoint p3; p3.x = p1.x + ((p2.x - p1.x) * factor); p3.y = p1.y + ((p2.y - p1.y) * factor); p3.z = p1.z + ((p2.z - p1.z) * factor); p3.flags.set(DPoint::divGenerated); p3.e = edge; // if both are wall nodes, so is this one if(p1.flags.test(DPoint::wall) && p2.flags.test(DPoint::wall)) p3.flags.set(DPoint::wall); d->mPoints.push_back(p3); U32 idx = (d->mPoints.size() - 1); edge->midPoint = idx; DEdge e1, e2; e1.flags.clear(); e2.flags.clear(); e1.flags.set(DEdge::divGenerated | DEdge::traversable); e2.flags.set(DEdge::divGenerated | DEdge::traversable); // construct the new edges and add them to the edgeTable e1.start = edge->start; e1.end = idx; e2.start = idx; e2.end = edge->end; e1.right = e1.left = e1.next = NULL; F32 length = edge->length - (edge->length * factor); e2.length = length; e2.right = e2.left = e2.next = NULL; e1.length = (edge->length * factor); d->mEdgeTable.insert(e1); d->mEdgeTable.insert(e2); edge->right = d->mEdgeTable.find(e2); edge->left = d->mEdgeTable.find(e1); edge->flags.set(DEdge::divided); } //---------------------------------------------------------------------------- // only call this after subdividing all edges of this surface and after // creating its center node void FloorPlan::generateNewEdges(InteriorDetail *d, U32 cenIdx, DSurf &surf, DEdge **edges, BitSet32 &edgeTracker) { DEdge *edgePtr; U32 count = 0; U32 i; for(i = 0; i < surf.mNumEdges; i++) { edgePtr = surf.mEdges[i]; if(edgeTracker.test(BIT(i))) { DEdge edge; edge.start = edgePtr->midPoint; edge.end = cenIdx; edge.length = (d->mPoints[edge.end] - d->mPoints[edge.start]).len(); edge.flags.clear(); edge.flags.set(DEdge::divGenerated | DEdge::traversable); edge.right = edge.left = edge.next = NULL; d->mEdgeTable.insert(edge); edges[i] = d->mEdgeTable.find(edge); } else edges[i] = NULL; } } //---------------------------------------------------------------------------- // use the center of mass of a poly as the center of this surf. void FloorPlan::createCenterNode(InteriorDetail *d, DSurf *surf, DPoint *CM) { Point2F* points = new Point2F[surf->mNumEdges]; F32 heightSum = 0; for(S32 i = 0; i < surf->mNumEdges; i++) { S32 ind = (surf->mFlipStates.test(BIT(i)) ? surf->mEdges[i]->end : surf->mEdges[i]->start); points[i].x = d->mPoints[ind].x; points[i].y = d->mPoints[ind].y; heightSum += d->mPoints[ind].z; } Point2F c; bool ok = polygonCM(surf->mNumEdges, points, &c); CM->x = c.x; CM->y = c.y; CM->z = heightSum/surf->mNumEdges; CM->e = surf->mEdges[0]; // trying to track a bug here. bool nodeOk = validatePoint(*CM); if(!nodeOk) { CM = NULL; return; } delete [] points; } //---------------------------------------------------------------------------- void FloorPlan::graphExtraction(InteriorDetail *d, GraphDetails *g) { EdgeInfoList edges; NodeInfoList nodes; EdgeInfoList segs; GraphVolumeList volumes; U32 indexCount = 0; BitVector tracker; U32 *reMap; GraphEdgeInfo *edgePtr; IndoorNodeInfo *nodePtr; GraphVolInfo *volPtr; U32 planeCount = 0; // Take each edge from each traversable surface and first remap // its verts into the new vertex array, and then finely add the // edge to the edge list. I can't possibly see how a stray node // that isn't part of any edge could be passed to the navGraph. U32 j, k; reMap = new U32[d->mConPoints.size()]; tracker.setSize(d->mConPoints.size()); tracker.clear(); for(j = 0; j < d->mConnections.size(); j++) { DConnection &con = d->mConnections[j]; U32 p1Idx = con.start; U32 p2Idx = con.end; // get each node index of this edge. remap it then add it // to the point list if(!tracker.test(p1Idx)) { tracker.set(p1Idx); reMap[p1Idx] = indexCount; nodes.increment(); nodePtr = &(nodes[indexCount++]); nodePtr->pos = Point3F(d->mConPoints[p1Idx].x, d->mConPoints[p1Idx].y, d->mConPoints[p1Idx].z); nodePtr->flags.clear(); // add volume info DVolume *vol = &(d->mVolumes[d->mSurfaces[d->mConPoints[p1Idx].surfIdx].volIdx]); volumes.increment(); volPtr = &(volumes[volumes.size() - 1]); volPtr->mPlaneCount = vol->mNumPlanes; volPtr->mPlaneIndex = planeCount; for(k = 0; k < vol->mNumPlanes; k++) { volumes.mPlanes.increment(); volumes.mPlanes[planeCount++] = vol->mPlanes[k]; } } if(!tracker.test(p2Idx)) { tracker.set(p2Idx); reMap[p2Idx] = indexCount; nodes.increment(); nodePtr = &(nodes[indexCount++]); nodePtr->pos = Point3F(d->mConPoints[p2Idx].x, d->mConPoints[p2Idx].y, d->mConPoints[p2Idx].z); nodePtr->flags.clear(); // add volume info DVolume *vol = &(d->mVolumes[d->mSurfaces[d->mConPoints[p2Idx].surfIdx].volIdx]); volumes.increment(); volPtr = &(volumes[volumes.size() - 1]); volPtr->mPlaneCount = vol->mNumPlanes; volPtr->mPlaneIndex = planeCount; for(k = 0; k < vol->mNumPlanes; k++) { volumes.mPlanes.increment(); volumes.mPlanes[planeCount++] = vol->mPlanes[k]; } } // now pack the edge into the edge list edges.increment(); edgePtr = &(edges[edges.size() - 1]); edgePtr->to[0].dest = reMap[con.start]; edgePtr->to[1].dest = reMap[con.end]; // now pack the interface data GraphEdgeInfo edgeSeg; edgeSeg.segPoints[0] = con.segNodes[0]; edgeSeg.segPoints[1] = con.segNodes[1]; edgeSeg.to[0].dest = reMap[con.start]; edgeSeg.to[1].dest = reMap[con.end]; segs.push_back(edgeSeg); } // find all islands and add them to the node list U32 idx, pIdx; for(idx = 0; idx < tracker.getSize(); idx++) { if (!tracker.test(idx)) { tracker.set(idx); reMap[idx] = indexCount; nodes.increment(); nodePtr = &(nodes[indexCount++]); bool invNode = d->mConPoints[idx].flags.test(DPoint::inventory); nodePtr->pos = Point3F(d->mConPoints[idx].x, d->mConPoints[idx].y, d->mConPoints[idx].z); nodePtr->flags.clear(); if(invNode) nodePtr->setInventory(); // volume info if(!invNode) { DVolume &vol = d->mVolumes[d->mSurfaces[d->mConPoints[idx].surfIdx].volIdx]; volumes.increment(); volPtr = &(volumes[volumes.size() - 1]); volPtr->mPlaneCount = vol.mNumPlanes; volPtr->mPlaneIndex = planeCount; for(pIdx = 0; pIdx < vol.mNumPlanes; pIdx++) { volumes.mPlanes.increment(); volumes.mPlanes[planeCount++] = vol.mPlanes[pIdx]; } } else { DVolume vol; createInventoryVol(d->mConPoints[idx], vol); volumes.increment(); volPtr = &(volumes[volumes.size() - 1]); volPtr->mPlaneCount = vol.mNumPlanes; volPtr->mPlaneIndex = planeCount; for(pIdx = 0; pIdx < vol.mNumPlanes; pIdx++) { volumes.mPlanes.increment(); volumes.mPlanes[planeCount++] = vol.mPlanes[pIdx]; } } } } delete [] reMap; g->volumes = volumes; g->edges = edges; g->nodes = nodes; g->segs = segs; } //---------------------------------------------------------------------------- void FloorPlan::upload2NavGraph(NavigationGraph *navGraph) { AssertFatal(navGraph, "Upload2NavGraph(): Bad pointer to navGraph passed."); EdgeInfoList edges; NodeInfoList nodes; EdgeInfoList segs; GraphVolumeList volumes; Vector planes; Vector chutes; for(S32 i = 0; i < (mNumInteriors + mExtraInteriors); i++) { // The graph data for this interior GraphDetails &g = mGraphDetails[i]; // Offset the node indices on all the edges- for (S32 D = 0; D < 2; D++) { for(S32 e = 0; e < g.edges.size(); e++) g.edges[e].to[D].dest += nodes.size(); for(S32 s = 0; s < g.segs.size(); s++) g.segs[s].to[D].dest += nodes.size(); } // Offset plane indices on the volumes- for(S32 p = 0; p < g.volumes.size(); p++) g.volumes[p].mPlaneIndex += planes.size(); // Accumulate the master lists of edges, nodes, volumes, etc. edges.merge(g.edges); segs.merge(g.segs); nodes.merge(g.nodes); volumes.merge(g.volumes); planes.merge(g.volumes.mPlanes); chutes.merge(g.chutes); } // set the plane list to the volume plane list volumes.mPlanes = planes; // pass all this data back to navGraph for build navGraph->setAlgorithmic(edges, nodes); navGraph->setEdgesAndNodes(segs, nodes); navGraph->setNodeVolumes(volumes); navGraph->setChuteHints(chutes); } //---------------------------------------------------------------------------- #define defaultVolHt 3 void FloorPlan::createInventoryVol(DPoint p, DVolume &vol) { vol.mNumPlanes = 6; vol.ht = defaultVolHt; vol.capPt.set(p.x, p.y, (p.z + defaultVolHt)); Point3F origPt(p.x, p.y, p.z); Point3F pt; F32 interfaceWidth = 0.8f; // define the volume's planes pt.set(origPt.x - interfaceWidth, origPt.y, origPt.z); vol.mPlanes[0].set(pt, VectorF(-1, 0, 0)); pt.set(origPt.x, origPt.y + interfaceWidth, origPt.z); vol.mPlanes[1].set(pt, VectorF(0, 1, 0)); pt.set(origPt.x + interfaceWidth, origPt.y, origPt.z); vol.mPlanes[2].set(pt, VectorF(1, 0, 0)); pt.set(origPt.x, origPt.y - interfaceWidth, origPt.z); vol.mPlanes[3].set(pt, VectorF(0, -1, 0)); pt.set(origPt.x, origPt.y, origPt.z - 1); vol.mPlanes[4].set(pt, VectorF(0, 0, -1)); pt.set(origPt.x, origPt.y, origPt.z + 2.0); vol.mPlanes[5].set(pt, VectorF(0, 0, 1)); } //---------------------------------------------------------------------------- void FloorPlan::buildNodesFromPortals(InteriorDetail *d) { for(S32 portalIdx = 0; portalIdx < d->mPortals.size(); portalIdx++) { bool usePnt = true; // get the point to create a node from Point3F pos; Point3F start = d->mPortals[portalIdx].center; Point3F end = start; end.z -= 1000; RayInfo info; if(gServerContainer.castRay(start, end, (InteriorObjectType | TerrainObjectType), &info)) { pos = info.point; pos.z += 0.1; } else continue; if (d->haveSurfaceNear(pos, 0.15)) continue; for(S32 ptsCount = 0; ptsCount < portalPoints.size(); ptsCount++) if(pointsAreEqual(pos, portalPoints[ptsCount])) { usePnt = false; break; } if(usePnt) { portalPoints.push_back(pos); // create the node IndoorNodeInfo node; node.pos.set(pos.x, pos.y, pos.z); node.setBelowPortal(); // create the volume DVolume invVol; GraphVolInfo vol; DPoint p; p.set(pos.x, pos.y, pos.z); createInventoryVol(p, invVol); vol.mPlaneIndex = 0; vol.mPlaneCount = invVol.mNumPlanes; // now make the simple graph object & add all the data GraphDetails d; d.nodes.push_back(node); d.volumes.push_back(vol); // add the planes that will define this node for(U32 k = 0; k < 6; k++) d.volumes.mPlanes.push_back(invVol.mPlanes[k]); // no segs or edges needed, woohoo! // now push this onto the graphDetails list mGraphDetails[mNumInteriors + mExtraInteriors++] = d; } } } //---------------------------------------------------------------------------- bool FloorPlan::pointsAreEqual(Point3F &a, Point3F &b) { if((b - a).len() > 2) return false; else return true; } //---------------------------------------------------------------------------- void FloorPlan::buildStaticShapeCenterNodes() { Con::executef(this, 1, "staticShapeListConstruct"); Vector::iterator i; for(i = mStaticShapeCenterList.begin(); i != mStaticShapeCenterList.end(); i++) processShape(i, true); } //---------------------------------------------------------------------------- void FloorPlan::buildStaticShapeGeometryNodes() { Con::executef(this, 1, "staticShapeListConstruct"); Vector::iterator i; for(i = mStaticShapeGeometryList.begin(); i != mStaticShapeGeometryList.end(); i++) processShape(i, false); } //---------------------------------------------------------------------------- void FloorPlan::processShape(ShapeBase **s, bool center) { // for now this only includes inventory shapes if(center) { // get the point to create a node from Point3F pos; (*s)->getTransform().getColumn(3, &pos); pos.z++; // raise it up a bit // create the node IndoorNodeInfo node; node.pos.set(pos.x, pos.y, pos.z); node.flags.clear(); node.setInventory(); // create the volume DVolume invVol; GraphVolInfo vol; DPoint p; p.set(pos.x, pos.y, pos.z); createInventoryVol(p, invVol); vol.mPlaneIndex = 0; vol.mPlaneCount = invVol.mNumPlanes; // now make the simple graph object & add all the data GraphDetails d; d.nodes.push_back(node); d.volumes.push_back(vol); // add the planes that will define this node U32 k; for(k = 0; k < 6; k++) d.volumes.mPlanes.push_back(invVol.mPlanes[k]); // no segs or edges needed, woohoo! // now push this onto the graphDetails list mGraphDetails[mNumInteriors + mExtraInteriors++] = d; } // this graph will be based off of this shape's geometry else { U32 i, j, k; Box3F box = (*s)->getWorldBox(); // simple way to get our collision surfs ClippedPolyList polyList; polyList.mPlaneList.clear(); polyList.mNormal.set(0, 0, 0); // this is all we will need from the polylist Vector polys; Vector verts; // these will be needed for reMap of verts U32 reMapSize = 0; U32 vCount = 0; U32 *reMap = NULL; // get our collision surfs from the polylist polys U32 mask = StaticShapeObjectType; if(gServerContainer.buildPolyList(box, mask, &polyList)) { for(i = 0; i < polyList.mPolyList.size(); i++) { ClippedPolyList::Poly *poly = &(polyList.mPolyList[i]); if(poly->object->getId() == (*s)->getId()) // make sure its the same obj { VectorF normal; normal.set(poly->plane.x, poly->plane.y, poly->plane.z); mSlopeThresh = mCos(mDegToRad(45.0f)); // what kind of slope does this surface have? F32 dot = mDot(normal, VectorF(0, 0, 1)); if(dot >= mSlopeThresh) polys.push_back(polyList.mPolyList[i]); } } } else return; // didn't get any polys back from the polylist InteriorDetail d; // copy over the point list for(i = 0; i < polyList.mVertexList.size(); i++) { Point3F pt = polyList.mVertexList[i].point; DPoint dpt; dpt.set(pt.x, pt.y, pt.z); dpt.flags.clear(); dpt.e = NULL; d.mPoints.push_back(dpt); } // now create the surfs for(i = 0; i < polys.size(); i++) { DSurf currSurf; currSurf.mFlags.clear(); currSurf.mFlipStates.clear(); S32 vertCount = polys[i].vertexCount; currSurf.mEdges = new DEdge *[vertCount]; currSurf.mDivLevel = 0; currSurf.parent = NULL; currSurf.mSubSurfCount = 0; currSurf.mNormal = polys[i].plane; for(j = 0, k = polys[i].vertexStart; k < (polys[i].vertexStart + polys[i].vertexCount); j++, k++) currSurf.fullWinding[j] = polyList.mIndexList[k]; currSurf.mNumEdges = polys[i].vertexCount; d.mSurfaces.push_back(currSurf); d.mTraversable.push_back(i); } // build the edge table for this shape buildEdgeTable(&d); // subdivide until we have a workable graph for this shape U32 dummy = 0; while(subdivideSurfaces(&d) > 0) dummy++; // connect all volumes buildConnections(&d); // extract the graph graphExtraction(&d, &(mGraphDetails[mNumInteriors + mExtraInteriors++])); // clean up memory... YAY!! d.mEdgeTable.clear(); U32 x; for(x = 0; x < d.mSurfaces.size(); x++) delete [] d.mSurfaces[x].mEdges; } } //---------------------------------------------------------------------------- Point3F FloorPlan::getMinBoxPoint(InteriorDetail *d, U8 n_edges, DEdge **edges) { Point3F minPt(1e9, 1e9, 1e9); for(S32 i = 0; i < n_edges; i++) minPt.setMin(d->mPoints[edges[i]->start]); return minPt; } //---------------------------------------------------------------------------- Point3F FloorPlan::getMaxBoxPoint(InteriorDetail *d, U8 n_edges, DEdge **edges) { Point3F maxPt(-1e9, -1e9, -1e9); for(S32 i = 0; i < n_edges; i++) maxPt.setMax(d->mPoints[edges[i]->start]); return maxPt; } //---------------------------------------------------------------------------- void FloorPlan::buildEdgeNormals(InteriorDetail *d, DSurf &surf, VectorF *normalList) { Point3F start, end; F32 temp; U32 i; for(i = 0; i < surf.mNumEdges; i++) { start = d->mPoints[surf.mEdges[i]->start]; end = d->mPoints[surf.mEdges[i]->end]; normalList[i] = end - start; temp = normalList[i].x; if(!surf.mFlipStates.test(BIT(i))) { normalList[i].x = (normalList[i].y * -1); normalList[i].y = temp; } else { normalList[i].x = normalList[i].y; normalList[i].y = (temp * -1); } normalList[i].z = 0; normalList[i].normalize(); } } //---------------------------------------------------------------------------- bool FloorPlan::obstructedSurf(InteriorDetail *d, DSurf &surf, DVolume *vol, surfCollisionInfo &info) { F32 ht; bool obstruction = setupPolyList(d, surf, &ht, vol, info); if(obstruction) if(info.specialCase) return true; else return (ht < 2.4); else return false; } //------------------------------------------------------------------------------------- // We lower the top when collisions are found so that later extrusion processing // doesn't get a collision. #define LowerTheTop 0.05f bool FloorPlan::setupPolyList(InteriorDetail *d, DSurf &surf, F32 *height, DVolume *vol, surfCollisionInfo &info) { info; Box3F bBox; bBox.min = getMinBoxPoint(d, surf.mNumEdges, surf.mEdges); bBox.max = getMaxBoxPoint(d, surf.mNumEdges, surf.mEdges); bBox.max.z += (UncappedNodeVolumeHt + 10); F32 len; createCenterNode(d, &surf, &(surf.mCntr)); if(surf.mLongest->length == surf.mEdges[0]->length) len = surf.mEdges[1]->length; else len = surf.mEdges[0]->length; F32 threshold = 0.3 * len; bool useSpecial = false; struct collisionInfo { enum CollisionType { normal = 0, ground = 1, }; CollisionType type; F32 ht; collisionInfo() {ht = 10000000; type = normal;} }; ClippedPolyList polyList; ClippedPolyList testList; // build a volume for this surface vol->mNumPlanes = surf.mNumEdges+2; polyList.mPlaneList.clear(); polyList.mPlaneList.setSize(surf.mNumEdges+1); polyList.mNormal.set(0, 0, 0); testList.mPlaneList.clear(); testList.mPlaneList.setSize(surf.mNumEdges+1); testList.mNormal.set(0, 0, 0); VectorF *normals; normals = new VectorF[surf.mNumEdges]; buildEdgeNormals(d, surf, normals); #if _FP_DEBUG_ VectorF saveNormals[40]; Point3F savePoints[40]; S32 saveIndex = 0; #endif for(S32 i = 0; i < surf.mNumEdges; i++) { // check flipstate for edge S32 ind = (surf.mFlipStates.test(BIT(i)) ? surf.mEdges[i]->end : surf.mEdges[i]->start); Point3F pnt = d->mPoints[ind]; Point3F tPnt = d->mPoints[ind]; // Note that we output a shrunk volume. It is adjusted back out later after // some further processing is done on volumes. pnt -= (normals[i] * NodeVolumeShrink); tPnt -= (normals[i] * threshold); polyList.mPlaneList[i].set(pnt, normals[i]); testList.mPlaneList[i].set(tPnt, normals[i]); vol->mPlanes[i].set(pnt, normals[i]); #if _FP_DEBUG_ saveNormals[saveIndex] = normals[i]; savePoints[saveIndex++] = pnt; #endif } delete [] normals; // cap the bottom for surfaces that have some slope Point3F p = d->mPoints[surf.mEdges[0]->start]; VectorF vec(surf.mNormal); p += (vec * NodeVolumeShrink); vec.neg(); polyList.mPlaneList[surf.mNumEdges].set(p, vec); testList.mPlaneList[surf.mNumEdges].set(p, vec); // cap the top of the test volume Point3F aP = p; aP.z += 2.5; VectorF pU(0,0,1); PlaneF tP(aP, pU); testList.mPlaneList.push_back(tP); vol->mPlanes[surf.mNumEdges].set(p, vec); *height = 0.0f; #if _FP_DEBUG_ saveNormals[saveIndex] = vec; savePoints[saveIndex++] = p; // Debug check to stop on a surface containing sDebugBreakPoint if (mFabs(vol->mPlanes[surf.mNumEdges].distToPlane(sDebugBreakPoint)) < 0.6) { // volume doesn't have top plane right now, hence dec & inc: vol->mNumPlanes--; if (vol->checkInside(sDebugBreakPoint)) debugStopPt(); vol->mNumPlanes++; } #endif #if TESTSPECIAL // determine if we can split this surface in a way that will better our floorPlan useSpecial = isQuadSurface(d, surf) && specSurfTest(surf, testList, bBox); #endif // build the poly list U32 mask = InteriorObjectType | StaticShapeObjectType | TerrainObjectType | StaticObjectType; if(gServerContainer.buildPolyList(bBox, mask, &polyList)) { if (polyList.mPolyList.size()) { F32 maxHt = maxZforSurf(d, surf); Point3F lowPt; F32 ht; collisionInfo lowCollision; DSide sides[4]; for(S32 j = 0; j < polyList.mPolyList.size(); j++) { ClippedPolyList::Poly *p = &(polyList.mPolyList[j]); for(S32 k = p->vertexStart; k < (p->vertexStart + p->vertexCount); k++) { d->polyTestPoints.push_back(polyList.mVertexList[polyList.mIndexList[k]].point); ht = polyList.mVertexList[polyList.mIndexList[k]].point.z - maxHt; if(ht < lowCollision.ht) { lowPt = polyList.mVertexList[polyList.mIndexList[k]].point; lowPt.z -= LowerTheTop; lowCollision.ht = (ht - LowerTheTop); // determine collision type if(p->object->getTypeMask() & TerrainObjectType) lowCollision.type = collisionInfo::ground; else lowCollision.type = collisionInfo::normal; } #if TESTSPECIAL if(useSpecial && ht < 2.4) // ************optimize the division **************** { Point3F colPt = polyList.mVertexList[polyList.mIndexList[k]].point; F32 distToPlane; bool found = false; for(U32 side = 0; side < 4; side++) { distToPlane = mFabs(polyList.mPlaneList[side].distToPlane(colPt)); if(distToPlane <= threshold) { if(distToPlane >= 0.05) { sides[side].closePts++; if(distToPlane > sides[side].bestDist) sides[side].bestDist = distToPlane; } found = true; } } if(!found) useSpecial = false; } #endif } } #if TESTSPECIAL if(useSpecial && haveGoodData(sides)) { setCollisionInfoData(info, sides, surf); return true; } #endif // if the lowest height was terrain, this space must be obstructed if(lowCollision.type == collisionInfo::ground) *height = 0; else { *height = lowCollision.ht; vol->ht = *height; vol->mPlanes[surf.mNumEdges+1].set(lowPt, VectorF(0, 0, 1)); } return true; } } //if(!inSolid(surf.mCntr)) //{ *height = UncappedNodeVolumeHt; Point3F pt = d->mPoints[surf.mEdges[0]->start]; pt.z += UncappedNodeVolumeHt; vol->mPlanes[surf.mNumEdges+1].set(pt, VectorF(0, 0, 1)); vol->ht = UncappedNodeVolumeHt; return false; //} //else //{ //*height = 0; //return true; //} } //------------------------------------------------------------------------------------- bool FloorPlan::inSolid(DPoint ¢er) { Point3F start, end; start = center; start.z += 0.1; end = start; end.z += 1; RayInfo info; if(gServerContainer.castRay(start, end, InteriorObjectType, &info)) return true; else return false; } //------------------------------------------------------------------------------------- bool FloorPlan::specSurfTest(DSurf &surf, ClippedPolyList &list, Box3F &b) { surf; U32 mask = InteriorObjectType | StaticShapeObjectType | TerrainObjectType | StaticObjectType; if(gServerContainer.buildPolyList(b, mask, &list))// || inSolid(surf.mCntr)) return false; else return true; } //------------------------------------------------------------------------------------- bool FloorPlan::haveGoodData(DSide *sides) { S32 sideToUse = -1; S32 highSideCount = 0; for(S32 side = 0; side < 4; side++) { if(sides[side].closePts > highSideCount) { sideToUse = side; highSideCount = sides[side].closePts; } } return sideToUse != -1; } //------------------------------------------------------------------------------------- bool FloorPlan::isQuadSurface(InteriorDetail *d, DSurf &surf) { if(surf.mNumEdges == 4) { DEdge *edge0 = surf.mEdges[0]; DEdge *edge1 = surf.mEdges[1]; DEdge *edge2 = surf.mEdges[2]; DEdge *edge3 = surf.mEdges[3]; VectorF e0, e1, e2, e3; if(!surf.mFlipStates.test(BIT(0))) e0 = d->mPoints[edge0->end] - d->mPoints[edge0->start]; else e0 = d->mPoints[edge0->start] - d->mPoints[edge0->end]; if(!surf.mFlipStates.test(BIT(1))) e1 = d->mPoints[edge1->end] - d->mPoints[edge1->start]; else e1 = d->mPoints[edge1->start] - d->mPoints[edge1->end]; if(!surf.mFlipStates.test(BIT(2))) e2 = d->mPoints[edge2->end] - d->mPoints[edge2->start]; else e2 = d->mPoints[edge2->start] - d->mPoints[edge2->end]; if(!surf.mFlipStates.test(BIT(3))) e3 = d->mPoints[edge3->end] - d->mPoints[edge3->start]; else e3 = d->mPoints[edge3->start] - d->mPoints[edge3->end]; e0.normalize(); e1.normalize(); e2.normalize(); e3.normalize(); bool E0parE2 = (mFabs(mDot(e0, e2)) > 0.992); bool E1parE3 = (mFabs(mDot(e1, e3)) > 0.992); bool useSpecial = E0parE2 && E1parE3; return useSpecial; } else return false; } //------------------------------------------------------------------------------------- void FloorPlan::setCollisionInfoData(surfCollisionInfo &info, DSide *sides, DSurf &surf) { info.specialCase = true; S32 sideToUse = -1; S32 highSideCount = 0; for(S32 side = 0; side < 4; side++) { if(sides[side].closePts > highSideCount) { sideToUse = side; highSideCount = sides[side].closePts; } } AssertFatal(sideToUse != -1, "no side chosen for collision info data"); switch(sideToUse) { case 0: info.divideEdge1 = 3; info.divideEdge2 = 1; break; case 1: info.divideEdge1 = 0; info.divideEdge2 = 2; break; case 2: info.divideEdge1 = 1; info.divideEdge2 = 3; break; case 3: info.divideEdge1 = 2; info.divideEdge2 = 0; break; } S32 sideA = info.divideEdge1; S32 sideB = info.divideEdge2; F32 factorA = 1 - (sides[sideToUse].bestDist / surf.mEdges[sideA]->length); info.factor = factorA; } //------------------------------------------------------------------------------------- void FloorPlan::computeSurfaceRadius(InteriorDetail *d, DSurf *surf) { if (surf->mFlags.test(DSurf::shouldBeCulled)) return; // else we get NaN values below (center of vertical surfaces) DPoint center; createCenterNode(d, surf, ¢er); F32 longest = -1e9f; F32 shortest = 1e9f; DEdge * * edges = surf->mEdges; for(S32 i = 0; i < surf->mNumEdges; i++) { LineSegment seg(d->mPoints[edges[i]->start], d->mPoints[edges[i]->end]); F32 D = seg.distance(center); longest = getMax(longest, D); shortest = getMin(shortest, D); } // longest from center to a vert = radius surf->mMaxRadius = longest; surf->mMinRadius = shortest; } //---------------------------------------------------------------------------- F32 FloorPlan::maxZforSurf(InteriorDetail *d, DSurf &surf) { F32 maxZ = -1e12; for(S32 i = 0; i < surf.mNumEdges; i++) if(d->mPoints[surf.mEdges[i]->start].z > maxZ) maxZ = d->mPoints[surf.mEdges[i]->start].z; return maxZ; } //---------------------------------------------------------------------------- void FloorPlan::newConnection(InteriorDetail *d, U32 start, U32 end, DConnection &con) { DConnection connect; connect.start = start; connect.end = end; connect.segNodes[0] = con.segNodes[0]; connect.segNodes[1] = con.segNodes[1]; d->mConnections.push_back(connect); } //---------------------------------------------------------------------------- void FloorPlan::buildConnections(InteriorDetail *d) { U32 j, k, c, g; d->mConPoints.setSize(d->mUnobstructed.size()); for(c = 0; c < d->mUnobstructed.size(); c++) { DSurf &s = d->mSurfaces[d->mUnobstructed[c]]; d->mConPoints[c] = s.mCntr; d->mConPoints[c].surfIdx = d->mUnobstructed[c]; d->mConPoints[c].flags.clear(); s.mCtrIdx = c; log(avar("surface %d zone: %d", c, s.mZone)); } d->mConnections.clear(); for(j = 0; j < d->mUnobstructed.size(); j++) { DSurf &surf_1 = d->mSurfaces[d->mUnobstructed[j]]; for(k = (j+1); k < d->mUnobstructed.size(); k++) { DSurf &surf_2 = d->mSurfaces[d->mUnobstructed[k]]; // early out based on distance if(!passDistanceCheck(surf_1, surf_2)) continue; for(c = 0; c < surf_1.mNumEdges; c++) { DEdge *edge_1 = surf_1.mEdges[c]; for(g = 0; g < surf_2.mNumEdges; g++) { DEdge *edge_2 = surf_2.mEdges[g]; DConnection con; if(isSharedEdge(d, edge_1, edge_2, con)) { newConnection(d, surf_1.mCtrIdx, surf_2.mCtrIdx, con); break; } } } } } //findDuplicateCons(d); } //---------------------------------------------------------------------------- bool FloorPlan::offHeightDistribution(InteriorDetail *d, DSurf &surf) { // only perform this on flat surfaces if(surf.mNormal.z != 1) return false; // grab all the data Vector points; points.setSize(surf.mNumEdges+1); createCenterNode(d, &surf, &points[0]); points[0].z += 0.1; U32 i; for(i = 1; i <= surf.mNumEdges; i++) { if(!surf.mFlipStates.test(BIT(i-1))) points[i] = d->mPoints[surf.mEdges[i-1]->start]; else points[i] = d->mPoints[surf.mEdges[i-1]->end]; points[i].z += 0.1; } // adjust our points VectorF *vecs = new VectorF[surf.mNumEdges]; for(i = 1; i <= surf.mNumEdges; i++) { vecs[i-1] = points[0] - points[i]; vecs[i-1].normalize(); points[i] += (1.75 * vecs[i-1]); } // now do the line of sights bool contact = false; F32 *hts = new F32[surf.mNumEdges+1]; for(i = 0; i <= surf.mNumEdges; i++) { RayInfo info; Point3F end(points[i]); end.z += 100; if(gServerContainer.castRay(points[i], end, InteriorObjectType, &info)) { contact = true; hts[i] = info.point.z - points[i].z; } else hts[i] = 100; } // don't bother if we have a completly unobstructed volume bool differs = false; if(contact) if(surf.mMaxRadius > sSpecialMaxRadius) differs = htsDifferAlot(hts, surf.mNumEdges+1); // clean up delete [] vecs; delete [] hts; return differs; } //---------------------------------------------------------------------------- bool FloorPlan::htsDifferAlot(F32 *hts, U8 n) { // sort these first U32 i, j; for(i = 0; i < n; i++) { for(j = i; j < n; j++) { if(hts[i] > hts[j]) { F32 temp = hts[i]; hts[i] = hts[j]; hts[j] = temp; } } } return (hts[n-1] - hts[0] > sHeightDiff); } //---------------------------------------------------------------------------- bool FloorPlan::passDistanceCheck(DSurf &surf_1, DSurf &surf_2) { bool distance = (surf_2.mCntr - surf_1.mCntr).len() < (sMaxDivRadius*3); // VectorF floor(0, 0, 1); // if((surf_1.mNormal == floor) && (surf_2.mNormal == floor)) // { // bool sameZ = mFabs(surf_2.mCntr.z) == mFabs(surf_1.mCntr.z); // return distance && sameZ; // } // else return distance; } //---------------------------------------------------------------------------- bool FloorPlan::isSharedEdge(InteriorDetail *d, DEdge *edge_1, DEdge *edge_2, DConnection &con) { DEdge *small; DEdge *big; DEdge *sourceEdge = edge_1; if(edge_1->length > edge_2->length) { big = edge_1; small = edge_2; } else { big = edge_2; small = edge_1; } // are any points equal? bool ss_bs = small->start == big->start; bool ss_be = small->start == big->end; bool se_bs = small->end == big->start; bool se_be = small->end == big->end; // easy case, hook up and leave if(((ss_bs) || (ss_be)) && ((se_bs) || (se_be))) { con.segNodes[0] = d->mPoints[small->start]; con.segNodes[1] = d->mPoints[small->end]; return isUsableInterface(con); } VectorF sourceVec = (d->mPoints[sourceEdge->end] - d->mPoints[sourceEdge->start]); VectorF vec_1 = (d->mPoints[big->end] - d->mPoints[big->start]); VectorF vec_2 = (d->mPoints[small->end] - d->mPoints[small->start]); vec_1.normalize(); vec_2.normalize(); sourceVec.normalize(); Point3F p1 = d->mPoints[small->start]; Point3F p2 = d->mPoints[small->end]; Point3F p3 = d->mPoints[big->start]; Point3F p4 = d->mPoints[big->end]; // check if parallel bool parallel = (mFabs(mDot(vec_1, vec_2)) > sParallelDot); if(parallel) { LineSegment line(d->mPoints[big->start], d->mPoints[big->end]); F32 dist_1 = line.distance(d->mPoints[small->start]); F32 dist_2 = line.distance(d->mPoints[small->end]); // check if collinear if((dist_1 < sCollinearDist) || (dist_2 < sCollinearDist) || isSteppable(d, big, small)) { // project Points of small line onto larger line VectorF vec_3 = (d->mPoints[small->start] - d->mPoints[big->start]); VectorF vec_4 = (d->mPoints[small->end] - d->mPoints[big->start]); F32 proj_1 = mDot(vec_1, vec_3); F32 proj_2 = mDot(vec_1, vec_4); // get our t values based on the projection. F32 t1 = proj_1/big->length; F32 t2 = proj_2/big->length; // sorting makes things alittle easier bool flipped = false; if(t1 > t2) { F32 temp = t1; t1 = t2; t2 = temp; flipped = true; } // check if lines are overlapping if(t2 <= 0 || t1 >= 1) return false; // we know the small line is overlapping the bigger line // we just need to determine which case of overlap // case 1 // containment or equal if((t1 >= 0) && (t2 <= 1)) { con.segNodes[0] = d->mPoints[small->start]; con.segNodes[1] = d->mPoints[small->end]; } else { // case 2 // the edges overlap on the start end if(t1 < 0) { if (!flipped) { con.segNodes[0] = d->mPoints[big->start]; con.segNodes[1] = d->mPoints[small->end]; } else { con.segNodes[0] = d->mPoints[big->start]; con.segNodes[1] = d->mPoints[small->start]; } } // case 3 // the edges overlap with t1 >= 0 and t2 extending past big->end else { if (!flipped) { con.segNodes[0] = d->mPoints[small->start]; con.segNodes[1] = d->mPoints[big->end]; } else { con.segNodes[0] = d->mPoints[small->end]; con.segNodes[1] = d->mPoints[big->end]; } } } // Project both nodes onto the proper segment VectorF conVec1 = (con.segNodes[0] - d->mPoints[sourceEdge->start]); VectorF conVec2 = (con.segNodes[1] - d->mPoints[sourceEdge->start]); F32 conPro1 = mDot(sourceVec, conVec1); F32 conPro2 = mDot(sourceVec, conVec2); F32 tval1 = conPro1 / sourceEdge->length; F32 tval2 = conPro2 / sourceEdge->length; con.segNodes[0] = d->mPoints[sourceEdge->start] + ((d->mPoints[sourceEdge->end] - d->mPoints[sourceEdge->start]) * tval1); con.segNodes[1] = d->mPoints[sourceEdge->start] + ((d->mPoints[sourceEdge->end] - d->mPoints[sourceEdge->start]) * tval2); return isUsableInterface(con); } } return false; } //---------------------------------------------------------------------------- bool FloorPlan::isSteppable(InteriorDetail *d, DEdge *big, DEdge *small) { F32 smallZ = d->mPoints[small->start].z; F32 bigZ = d->mPoints[big->start].z; if(mFabs(smallZ - bigZ) > 0.75) return false; Point3F ss = d->mPoints[small->start]; Point3F se = d->mPoints[small->end]; Point3F bs = d->mPoints[big->start]; Point3F be = d->mPoints[big->end]; se.z = ss.z = bs.z = be.z = 0.0f; LineSegment line(bs, be); return (line.distance(ss) < sCollinearDist) || (line.distance(se) < sCollinearDist); } //---------------------------------------------------------------------------- void FloorPlan::initPersistFields() { Parent::initPersistFields(); } //---------------------------------------------------------------------------- void FloorPlan::generate() { mDivLevel = 0; snoopInteriors(); Con::printf("Total portal nodes created: %d\n", portalPoints.size()); } //---------------------------------------------------------------------------- void FloorPlan::findDuplicateCons(InteriorDetail *d) { U32 theSame = 0; U32 i, j; for(i = 0; i < d->mConnections.size(); i++) { DConnection &con_1 = d->mConnections[i]; for(j = (i+1); j < d->mConnections.size(); j++) { DConnection &con_2 = d->mConnections[j]; if(con_1 == con_2) { theSame++; d->mConnections.erase(j--); } } } Con::printf("duplicate connections removed from %d: %d\n", d->mIndex, theSame); } // console functions //---------------------------------------------------------------------------- static void cGenerate(SimObject *ptr, S32, const char **) { FloorPlan *obj = static_cast(ptr); if(obj) obj->generate(); } //---------------------------------------------------------------------------- static void cUpload(SimObject *ptr, S32, const char **) { FloorPlan *obj = static_cast(ptr); NavigationGraph *ng = dynamic_cast(Sim::findObject("NavGraph")); if(!ng) Con::printf("no navigationGraph exists!"); if(obj) obj->upload2NavGraph(ng); } //---------------------------------------------------------------------------- static void cAddStaticCenter(SimObject *ptr, S32, const char **argv) { FloorPlan *fp = static_cast(ptr); S32 id = dAtoi(argv[2]); ShapeBase *shape = NULL; Sim::findObject(id, shape); fp->mStaticShapeCenterList.push_back(shape); } //---------------------------------------------------------------------------- static void cAddStaticGeom(SimObject *ptr, S32, const char **argv) { FloorPlan *fp = static_cast(ptr); S32 id = dAtoi(argv[2]); ShapeBase *shape = NULL; Sim::findObject(id, shape); fp->mStaticShapeGeometryList.push_back(shape); } //---------------------------------------------------------------------------- void FloorPlan::consoleInit() { Parent::consoleInit(); Con::addVariable("FloorPlan::CollinearDist", TypeF32, &sCollinearDist); Con::addVariable("FloorPlan::ParallelDot", TypeF32, &sParallelDot); Con::addVariable("FloorPlan::FloorAngleThresh", TypeS32, &sFloorThresh); Con::addVariable("FloorPlan::HeightDiff", TypeF32, &sHeightDiff); Con::addVariable("FloorPlan::SpecialMaxRadius", TypeF32, &sSpecialMaxRadius); Con::addVariable("FloorPlan::MaxRadius", TypeF32, &sMaxDivRadius); Con::addVariable("FloorPlan::MinRadius", TypeF32, &sMinDivRadius); Con::addVariable("FloorPlan::subdivide", TypeBool, &sSubDivide); Con::addVariable("FloorPlan::drawConnections", TypeBool, &sDrawConnections); Con::addVariable("FloorPlan::drawVols", TypeBool, &sDrawVolumeHts); Con::addVariable("FP::special", TypeBool, &sUseSpecial); Con::addVariable("FP::DisableAsserts", TypeBool, &sDisableAsserts); Con::addVariable("FP::divCount", TypeS32, &amountToDivide); Con::addCommand("FloorPlan", "generate", cGenerate, "obj.generate()", 2, 2); Con::addCommand("FloorPlan", "upload", cUpload, "obj.upload()", 2, 2); Con::addCommand("FloorPlan", "addStaticCenter", cAddStaticCenter, "obj.addStaticCenter( shape )", 3, 3); Con::addCommand("FloorPlan", "addStaticGeom", cAddStaticGeom, "obj.addStaticGeom( shape )", 3, 3); Con::linkNamespaces("SimObject", "FloorPlan"); }