diff --git a/Engine/source/T3D/AI/AIController.cpp b/Engine/source/T3D/AI/AIController.cpp index 8546826c7..d798585c4 100644 --- a/Engine/source/T3D/AI/AIController.cpp +++ b/Engine/source/T3D/AI/AIController.cpp @@ -168,11 +168,20 @@ bool AIController::getAIMove(Move* movePtr) { obj = getAIInfo()->mObj; } + + Point3F start = obj->getPosition(); + Point3F end = start; + start.z = obj->getBoxCenter().z; + end.z -= mControllerData->mHeightTolerance; + + obj->disableCollision(); + // Only repath if not already adjusted and on risky ground RayInfo info; - if (obj->getContainer()->castRay(obj->getPosition(), obj->getPosition() - Point3F(0, 0, mControllerData->mHeightTolerance), StaticShapeObjectType, &info)) + if (obj->getContainer()->castRay(start, end, StaticShapeObjectType, &info)) { getNav()->repath(); } + obj->enableCollision(); getGoal()->mInRange = false; } if (getGoal()->getDist() < mControllerData->mFollowTolerance ) @@ -530,11 +539,15 @@ AIControllerData::AIControllerData() mAttackRadius = 2.0f; mMoveStuckTolerance = 0.01f; mMoveStuckTestDelay = 30; - mHeightTolerance = 0.001f; + mHeightTolerance = 0.1f; mFollowTolerance = 1.0f; #ifdef TORQUE_NAVIGATION_ENABLED mLinkTypes = LinkData(AllFlags); + mFilter.setIncludeFlags(mLinkTypes.getFlags()); + mFilter.setExcludeFlags(0); + mAreaCosts.setSize(PolyAreas::NumAreas); + mAreaCosts.fill(1.0f); mNavSize = AINavigation::Regular; mFlocking.mChance = 90; mFlocking.mMin = 1.0f; @@ -560,6 +573,8 @@ AIControllerData::AIControllerData(const AIControllerData& other, bool temp_clon #ifdef TORQUE_NAVIGATION_ENABLED mLinkTypes = other.mLinkTypes; + mFilter = other.mFilter; + mAreaCosts = other.mAreaCosts; mNavSize = other.mNavSize; mFlocking.mChance = other.mFlocking.mChance; mFlocking.mMin = other.mFlocking.mMin; @@ -629,6 +644,8 @@ void AIControllerData::initPersistFields() addFieldV("FlockSideStep", TypeRangedF32, Offset(mFlocking.mSideStep, AIControllerData), &CommonValidators::PositiveFloat, "@brief Distance from destination before we stop moving out of the way."); + addField("areaCosts", TypeF32Vector, Offset(mAreaCosts, AIControllerData), + "Vector of costs for each PolyArea."); addField("allowWalk", TypeBool, Offset(mLinkTypes.walk, AIControllerData), "Allow the character to walk on dry land."); addField("allowJump", TypeBool, Offset(mLinkTypes.jump, AIControllerData), @@ -662,6 +679,10 @@ void AIControllerData::packData(BitStream* stream) #ifdef TORQUE_NAVIGATION_ENABLED //enums + stream->write(mAreaCosts.size()); + for (U32 i = 0; i < mAreaCosts.size(); i++) { + stream->write(mAreaCosts[i]); + } stream->write(mLinkTypes.getFlags()); stream->write((U32)mNavSize); // end enums @@ -684,10 +705,23 @@ void AIControllerData::unpackData(BitStream* stream) stream->read(&mFollowTolerance); #ifdef TORQUE_NAVIGATION_ENABLED + U32 num; + stream->read(&num); + mAreaCosts.setSize(num); + for (U32 i = 0; i < num; i++) + { + stream->read(&mAreaCosts[i]); + } //enums U16 linkFlags; stream->read(&linkFlags); mLinkTypes = LinkData(linkFlags); + mFilter.setIncludeFlags(mLinkTypes.getFlags()); + mFilter.setExcludeFlags(mLinkTypes.getExcludeFlags()); + for (U32 i = 0; i < PolyAreas::NumAreas; i++) + { + mFilter.setAreaCost((PolyAreas)i, mAreaCosts[i]); + } U32 navSize; stream->read(&navSize); mNavSize = (AINavigation::NavSize)(navSize); diff --git a/Engine/source/T3D/AI/AIController.h b/Engine/source/T3D/AI/AIController.h index 18d95e210..340ee5d04 100644 --- a/Engine/source/T3D/AI/AIController.h +++ b/Engine/source/T3D/AI/AIController.h @@ -168,6 +168,8 @@ public: /// Types of link we can use. LinkData mLinkTypes; + dtQueryFilter mFilter; + Vector mAreaCosts; AINavigation::NavSize mNavSize; #endif Delegate resolveYawPtr; diff --git a/Engine/source/T3D/AI/AICover.cpp b/Engine/source/T3D/AI/AICover.cpp index 97cf82a39..b680a6175 100644 --- a/Engine/source/T3D/AI/AICover.cpp +++ b/Engine/source/T3D/AI/AICover.cpp @@ -80,7 +80,7 @@ bool AIController::findCover(const Point3F& from, F32 radius) if (s.point) { // Calling setPathDestination clears cover... - bool foundPath = getNav()->setPathDestination(s.point->getPosition()); + bool foundPath = getNav()->setPathDestination(s.point->getPosition(), true); setCover(s.point); s.point->setOccupied(true); return foundPath; diff --git a/Engine/source/T3D/AI/AINavigation.cpp b/Engine/source/T3D/AI/AINavigation.cpp index 258939f06..3ff12eda4 100644 --- a/Engine/source/T3D/AI/AINavigation.cpp +++ b/Engine/source/T3D/AI/AINavigation.cpp @@ -23,7 +23,7 @@ #include "AIController.h" #include "T3D/shapeBase.h" -static U32 sAILoSMask = TerrainObjectType | StaticShapeObjectType | StaticObjectType | AIObjectType; +static U32 sAILoSMask = TerrainObjectType | StaticShapeObjectType | StaticObjectType; AINavigation::AINavigation(AIController* controller) { @@ -77,6 +77,7 @@ bool AINavigation::setPathDestination(const Point3F& pos, bool replace) path->mAlwaysRender = true; path->mLinkTypes = getCtrl()->mControllerData->mLinkTypes; path->mXray = true; + path->mFilter = getCtrl()->mControllerData->mFilter; // Paths plan automatically upon being registered. if (!path->registerObject()) { @@ -338,7 +339,11 @@ void AINavigation::repath() if (mPathData.path.isNull() || !mPathData.owned) return; - if (mRandI(0, 100) < getCtrl()->mControllerData->mFlocking.mChance && flock()) + if (avoidObstacles()) + { + mPathData.path->mTo = mMoveDestination; + } + else if (mRandI(0, 100) < getCtrl()->mControllerData->mFlocking.mChance && flock()) { mPathData.path->mTo = mMoveDestination; } @@ -379,6 +384,60 @@ void AINavigation::clearPath() mPathData = PathData(); } +bool AINavigation::avoidObstacles() +{ + SimObjectPtr obj = getCtrl()->getAIInfo()->mObj; + obj->disableCollision(); + + Point3F pos = obj->getBoxCenter(); + VectorF forward = obj->getTransform().getForwardVector(); + forward.normalizeSafe(); + + // Generate forward-left and forward-right by rotating forward vector + VectorF right = mCross(forward, Point3F(0, 0, 1)); + VectorF leftDir = forward + right * -0.5f; // front-left + VectorF rightDir = forward + right * 0.5f; // front-right + + leftDir.normalizeSafe(); + rightDir.normalizeSafe(); + + F32 rayLength = obj->getVelocity().lenSquared() * TickSec * 2 + getCtrl()->getAIInfo()->mRadius; + Point3F directions[3] = { + forward, + leftDir, + rightDir + }; + + bool hit[3] = { false, false, false }; + RayInfo info; + for (int i = 0; i < 3; ++i) + { + Point3F end = pos + directions[i] * rayLength; + if (obj->getContainer()->castRay(pos, end, sAILoSMask, &info)) + { + hit[i] = true; + } + } + + Point3F avoidance = Point3F::Zero; + if (hit[0]) avoidance += right * 1.0f; + if (hit[1]) avoidance += right * 1.5f; + if (hit[2]) avoidance -= right * 1.5f; + + if (!avoidance.isZero()) + { + avoidance.normalizeSafe(); + F32 clearance = getCtrl()->getAIInfo()->mRadius * 1.5f; + Point3F newDest = info.point + avoidance * rayLength; + mMoveDestination = newDest; + obj->enableCollision(); + return true; + } + + obj->enableCollision(); + return false; +} + bool AINavigation::flock() { AIControllerData::Flocking flockingData = getCtrl()->mControllerData->mFlocking; @@ -386,9 +445,10 @@ bool AINavigation::flock() obj->disableCollision(); Point3F pos = obj->getBoxCenter(); - Point3F searchArea = Point3F(flockingData.mMin / 2, flockingData.mMax / 2, getCtrl()->getAIInfo()->mObj->getObjBox().maxExtents.z / 2); F32 maxFlocksq = flockingData.mMax * flockingData.mMax; + Point3F searchArea = Point3F(maxFlocksq, maxFlocksq, getCtrl()->getAIInfo()->mObj->getObjBox().maxExtents.z / 2); + bool flocking = false; U32 found = 0; if (getCtrl()->getGoal()) @@ -412,41 +472,35 @@ bool AINavigation::flock() sql.mList.remove(obj); Point3F avoidanceOffset = Point3F::Zero; + F32 avoidanceAmtSq = 0; - //avoid objects in the way RayInfo info; - if (obj->getContainer()->castRay(pos, dest + Point3F(0, 0, obj->getObjBox().len_z() / 2), sAILoSMask, &info)) - { - Point3F blockerOffset = (info.point - dest); - blockerOffset.z = 0; - avoidanceOffset += blockerOffset; - } - //avoid bots that are too close for (U32 i = 0; i < sql.mList.size(); i++) { ShapeBase* other = dynamic_cast(sql.mList[i]); Point3F objectCenter = other->getBoxCenter(); - F32 sumRad = flockingData.mMin + other->getAIController()->mControllerData->mFlocking.mMin; + F32 sumMinRad = flockingData.mMin + other->getAIController()->mControllerData->mFlocking.mMin; F32 separation = getCtrl()->getAIInfo()->mRadius + other->getAIController()->getAIInfo()->mRadius; - sumRad += separation; + separation += sumMinRad; Point3F offset = (pos - objectCenter); F32 offsetLensq = offset.lenSquared(); //square roots are expensive, so use squared val compares - if ((flockingData.mMin > 0) && (offsetLensq < (sumRad * sumRad))) + if ((flockingData.mMin > 0) && (offsetLensq < (sumMinRad * sumMinRad))) { other->disableCollision(); - if (!obj->getContainer()->castRay(pos, other->getBoxCenter(), sAILoSMask, &info)) + if (!obj->getContainer()->castRay(pos, other->getBoxCenter(), sAILoSMask | AIObjectType, &info)) { found++; - offset.normalizeSafe(); - offset *= sumRad + separation; + offset *= separation; avoidanceOffset += offset; //accumulate total group, move away from that + avoidanceAmtSq += offsetLensq; } other->enableCollision(); } } + //if we don't have to worry about bumping into one another (nothing found lower than minFLock), see about grouping up if (found == 0) { @@ -455,20 +509,20 @@ bool AINavigation::flock() ShapeBase* other = static_cast(sql.mList[i]); Point3F objectCenter = other->getBoxCenter(); - F32 sumRad = flockingData.mMin + other->getAIController()->mControllerData->mFlocking.mMin; + F32 sumMaxRad = flockingData.mMax + other->getAIController()->mControllerData->mFlocking.mMax; F32 separation = getCtrl()->getAIInfo()->mRadius + other->getAIController()->getAIInfo()->mRadius; - sumRad += separation; + separation += sumMaxRad; Point3F offset = (pos - objectCenter); - if ((flockingData.mMin > 0) && ((sumRad * sumRad) < (maxFlocksq))) + F32 offsetLensq = offset.lenSquared(); //square roots are expensive, so use squared val compares + if ((flockingData.mMax > 0) && (offsetLensq < (sumMaxRad * sumMaxRad))) { other->disableCollision(); - if (!obj->getContainer()->castRay(pos, other->getBoxCenter(), sAILoSMask, &info)) + if (!obj->getContainer()->castRay(pos, other->getBoxCenter(), sAILoSMask | AIObjectType, &info)) { found++; - offset.normalizeSafe(); - offset *= sumRad + separation; avoidanceOffset -= offset; // subtract total group, move toward it + avoidanceAmtSq -= offsetLensq; } other->enableCollision(); } @@ -476,27 +530,36 @@ bool AINavigation::flock() } if (found > 0) { + //ephasize the *side* portion of sidestep to better avoid clumps + if (avoidanceOffset.x < avoidanceOffset.y) + avoidanceOffset.x *= 2.0; + else + avoidanceOffset.y *= 2.0; + + //add fuzz to sidestepping avoidanceOffset.z = 0; avoidanceOffset.x = (mRandF() * avoidanceOffset.x) * 0.5 + avoidanceOffset.x * 0.75; avoidanceOffset.y = (mRandF() * avoidanceOffset.y) * 0.5 + avoidanceOffset.y * 0.75; - if (avoidanceOffset.lenSquared() < (maxFlocksq)) + + avoidanceOffset.normalizeSafe(); + avoidanceOffset *= avoidanceAmtSq; + + if ((avoidanceAmtSq) > flockingData.mMin * flockingData.mMin) { - dest += avoidanceOffset; + dest = obj->getPosition()+avoidanceOffset; } //if we're not jumping... if (mJump == None) { dest.z = obj->getPosition().z; + //make sure we don't run off a cliff Point3F zlen(0, 0, getCtrl()->mControllerData->mHeightTolerance); if (obj->getContainer()->castRay(dest + zlen, dest - zlen, TerrainObjectType | StaticShapeObjectType | StaticObjectType, &info)) { - if ((mMoveDestination - dest).len() > getCtrl()->mControllerData->mMoveTolerance) - { - mMoveDestination = dest; - flocking = true; - } + mMoveDestination = dest; + flocking = true; } } } diff --git a/Engine/source/T3D/AI/AINavigation.h b/Engine/source/T3D/AI/AINavigation.h index 4d4f1b966..abc514011 100644 --- a/Engine/source/T3D/AI/AINavigation.h +++ b/Engine/source/T3D/AI/AINavigation.h @@ -98,6 +98,7 @@ struct AINavigation /// Move to the specified node in the current path. void moveToNode(S32 node); + bool avoidObstacles(); bool flock(); #endif }; diff --git a/Engine/source/T3D/aiPlayer.cpp b/Engine/source/T3D/aiPlayer.cpp index 68032893a..a3e54d9ab 100644 --- a/Engine/source/T3D/aiPlayer.cpp +++ b/Engine/source/T3D/aiPlayer.cpp @@ -114,6 +114,10 @@ AIPlayer::AIPlayer() mJump = None; mNavSize = Regular; mLinkTypes = LinkData(AllFlags); + mFilter.setIncludeFlags(mLinkTypes.getFlags()); + mFilter.setExcludeFlags(0); + mAreaCosts.setSize(PolyAreas::NumAreas); + mAreaCosts.fill(1.0f); #endif mIsAiControlled = true; @@ -163,7 +167,8 @@ void AIPlayer::initPersistFields() #ifdef TORQUE_NAVIGATION_ENABLED addGroup("Pathfinding"); - + addField("areaCosts", TypeF32Vector, Offset(mAreaCosts, AIPlayer), + "Vector of costs for each PolyArea."); addField("allowWalk", TypeBool, Offset(mLinkTypes.walk, AIPlayer), "Allow the character to walk on dry land."); addField("allowJump", TypeBool, Offset(mLinkTypes.jump, AIPlayer), @@ -785,6 +790,7 @@ void AIPlayer::moveToNode(S32 node) bool AIPlayer::setPathDestination(const Point3F &pos) { +#ifdef TORQUE_NAVIGATION_ENABLED // Pathfinding only happens on the server. if(!isServerObject()) return false; @@ -799,6 +805,13 @@ bool AIPlayer::setPathDestination(const Point3F &pos) return false; } + mFilter.setIncludeFlags(mLinkTypes.getFlags()); + mFilter.setExcludeFlags(mLinkTypes.getExcludeFlags()); + for (U32 i = 0; i < PolyAreas::NumAreas; i++) + { + mFilter.setAreaCost((PolyAreas)i, mAreaCosts[i]); + } + // Create a new path. NavPath *path = new NavPath(); @@ -808,6 +821,7 @@ bool AIPlayer::setPathDestination(const Point3F &pos) path->mFromSet = path->mToSet = true; path->mAlwaysRender = true; path->mLinkTypes = mLinkTypes; + path->mFilter = mFilter; path->mXray = true; // Paths plan automatically upon being registered. if(!path->registerObject()) @@ -839,6 +853,9 @@ bool AIPlayer::setPathDestination(const Point3F &pos) path->deleteObject(); return false; } +#else + setMoveDestination(pos, false); +#endif } DefineEngineMethod(AIPlayer, setPathDestination, bool, (Point3F goal),, diff --git a/Engine/source/T3D/aiPlayer.h b/Engine/source/T3D/aiPlayer.h index 1274de0b8..e5d41330d 100644 --- a/Engine/source/T3D/aiPlayer.h +++ b/Engine/source/T3D/aiPlayer.h @@ -228,6 +228,8 @@ public: /// Types of link we can use. LinkData mLinkTypes; + dtQueryFilter mFilter; + Vector mAreaCosts; /// @} #endif // TORQUE_NAVIGATION_ENABLED diff --git a/Engine/source/gfx/gfxDrawUtil.cpp b/Engine/source/gfx/gfxDrawUtil.cpp index 40e6d15c5..16b886a55 100644 --- a/Engine/source/gfx/gfxDrawUtil.cpp +++ b/Engine/source/gfx/gfxDrawUtil.cpp @@ -1486,69 +1486,73 @@ void GFXDrawUtil::_drawWireCapsule( const GFXStateBlockDesc &desc, const Point3F void GFXDrawUtil::drawCone( const GFXStateBlockDesc &desc, const Point3F &basePnt, const Point3F &tipPnt, F32 baseRadius, const ColorI &color ) { - VectorF uvec = tipPnt - basePnt; - F32 height = uvec.len(); - uvec.normalize(); - MatrixF mat( true ); - MathUtils::getMatrixFromUpVector( uvec, &mat ); - mat.setPosition(basePnt); + VectorF dir = tipPnt - basePnt; + F32 height = dir.len(); + dir.normalize(); - Point3F scale( baseRadius, baseRadius, height ); - mat.scale(scale); + MatrixF mat(true); + MathUtils::getMatrixFromUpVector(dir, &mat); + mat.setPosition(basePnt); + mat.scale(Point3F(baseRadius, baseRadius, height)); GFXTransformSaver saver; - mDevice->pushWorldMatrix(); mDevice->multWorld(mat); - S32 numPoints = sizeof(circlePoints)/sizeof(Point2F); - GFXVertexBufferHandle verts(mDevice, numPoints * 3 + 2, GFXBufferTypeVolatile); + const S32 numPoints = sizeof(circlePoints) / sizeof(Point2F); + + // Vertex index layout + const S32 baseCenterIdx = 0; + const S32 baseStartIdx = 1; + const S32 tipIdx = baseStartIdx + numPoints; + const S32 sideStartIdx = tipIdx + 1; + + const S32 totalVerts = sideStartIdx + numPoints * 3; + + GFXVertexBufferHandle verts(mDevice, totalVerts, GFXBufferTypeVolatile); verts.lock(); - F32 sign = -1.f; - S32 indexDown = 0; //counting down from numPoints - S32 indexUp = 0; //counting up from 0 - S32 index = 0; //circlePoints index for cap - for (S32 i = 0; i < numPoints + 1; i++) + // Base center vertex (at origin in local space) + verts[baseCenterIdx].point = Point3F(0, 0, 0); + verts[baseCenterIdx].color = color; + + // Base circle vertices + for (S32 i = 0; i < numPoints; i++) { - //Top cap - if (i != numPoints) - { - if (sign < 0) - index = indexDown; - else - index = indexUp; + verts[baseStartIdx + i].point = Point3F(circlePoints[i].x, circlePoints[i].y, 0); + verts[baseStartIdx + i].color = color; + } - verts[i].point = Point3F(circlePoints[index].x, circlePoints[index].y, 0); - verts[i].color = color; + // Tip vertex (pointing "up" in local Z) + verts[tipIdx].point = Point3F(0, 0, 1); + verts[tipIdx].color = color; - if (sign < 0) - indexUp += 1; - else - indexDown = numPoints - indexUp; + // Side triangles: one triangle per segment + for (S32 i = 0; i < numPoints; i++) + { + S32 triBase = sideStartIdx + i * 3; - // invert sign - sign *= -1.0f; - } + // Each triangle is (tip, base[i], base[(i+1)%numPoints]) + verts[triBase + 0].point = verts[tipIdx].point; + verts[triBase + 1].point = verts[baseStartIdx + i].point; + verts[triBase + 2].point = verts[baseStartIdx + ((i + 1) % numPoints)].point; - //cone - S32 imod = i % numPoints; - S32 vertindex = 2 * i + numPoints; - verts[vertindex].point = Point3F(circlePoints[imod].x, circlePoints[imod].y, 0); - verts[vertindex].color = color; - verts[vertindex + 1].point = Point3F(0.0f, 0.0f, 1.0f); - verts[vertindex + 1].color = color; + verts[triBase + 0].color = color; + verts[triBase + 1].color = color; + verts[triBase + 2].color = color; } verts.unlock(); - mDevice->setStateBlockByDesc( desc ); - - mDevice->setVertexBuffer( verts ); + mDevice->setStateBlockByDesc(desc); + mDevice->setVertexBuffer(verts); mDevice->setupGenericShaders(); - mDevice->drawPrimitive(GFXTriangleStrip, 0, numPoints - 2); - mDevice->drawPrimitive(GFXTriangleStrip, numPoints, numPoints * 2); + // Draw base cap using triangle fan + mDevice->drawPrimitive(GFXTriangleList, baseCenterIdx, numPoints - 2); + + // Draw sides using triangle list + mDevice->drawPrimitive(GFXTriangleList, sideStartIdx, numPoints); mDevice->popWorldMatrix(); @@ -1556,71 +1560,89 @@ void GFXDrawUtil::drawCone( const GFXStateBlockDesc &desc, const Point3F &basePn void GFXDrawUtil::drawCylinder( const GFXStateBlockDesc &desc, const Point3F &basePnt, const Point3F &tipPnt, F32 radius, const ColorI &color ) { - VectorF uvec = tipPnt - basePnt; - F32 height = uvec.len(); - uvec.normalize(); - MatrixF mat( true ); - MathUtils::getMatrixFromUpVector( uvec, &mat ); + VectorF dir = tipPnt - basePnt; + F32 height = dir.len(); + dir.normalize(); + + MatrixF mat(true); + MathUtils::getMatrixFromUpVector(dir, &mat); mat.setPosition(basePnt); + mat.scale(Point3F(radius, radius, height)); - Point3F scale( radius, radius, height * 2 ); - mat.scale(scale); GFXTransformSaver saver; - mDevice->pushWorldMatrix(); mDevice->multWorld(mat); - S32 numPoints = sizeof(circlePoints) / sizeof(Point2F); - GFXVertexBufferHandle verts(mDevice, numPoints *4 + 2, GFXBufferTypeVolatile); + const S32 numPoints = sizeof(circlePoints) / sizeof(Point2F); + + // Vertex index layout + const S32 baseCenterIdx = 0; + const S32 topCenterIdx = 1; + const S32 baseStartIdx = 2; + const S32 topStartIdx = baseStartIdx + numPoints; + const S32 sideStartIdx = topStartIdx + numPoints; + + const S32 totalVerts = sideStartIdx + numPoints * 6; + + GFXVertexBufferHandle verts(mDevice, totalVerts, GFXBufferTypeVolatile); verts.lock(); - F32 sign = -1.f; - S32 indexDown = 0; //counting down from numPoints - S32 indexUp = 0; //counting up from 0 - S32 index = 0; //circlePoints index for caps - for (S32 i = 0; i < numPoints + 1; i++) + // Base center + verts[baseCenterIdx].point = Point3F(0, 0, 0); + verts[baseCenterIdx].color = color; + + // Top center + verts[topCenterIdx].point = Point3F(0, 0, 1); + verts[topCenterIdx].color = color; + + // Base circle + for (S32 i = 0; i < numPoints; ++i) { - //Top/Bottom cap - if (i != numPoints) - { - if (sign < 0) - index = indexDown; - else - index = indexUp; + verts[baseStartIdx + i].point = Point3F(circlePoints[i].x, circlePoints[i].y, 0); + verts[baseStartIdx + i].color = color; + } - verts[i].point = Point3F(circlePoints[index].x, circlePoints[index].y, 0); - verts[i].color = color; - verts[i + numPoints].point = Point3F(circlePoints[index].x, circlePoints[index].y, 0.5f); - verts[i + numPoints].color = color; + // Top circle + for (S32 i = 0; i < numPoints; ++i) + { + verts[topStartIdx + i].point = Point3F(circlePoints[i].x, circlePoints[i].y, 1.0f); + verts[topStartIdx + i].color = color; + } - if (sign < 0) - indexUp += 1; - else - indexDown = numPoints - indexUp; + // Side triangles + for (S32 i = 0; i < numPoints; ++i) + { + S32 next = (i + 1) % numPoints; + S32 idx = sideStartIdx + i * 6; - // invert sign - sign *= -1.0f; - } + // First triangle (base[i], base[next], top[i]) + verts[idx + 0].point = verts[baseStartIdx + i].point; + verts[idx + 1].point = verts[baseStartIdx + next].point; + verts[idx + 2].point = verts[topStartIdx + i].point; - //cylinder - S32 imod = i % numPoints; - S32 vertindex = 2 * i + (numPoints * 2); - verts[vertindex].point = Point3F(circlePoints[imod].x, circlePoints[imod].y, 0); - verts[vertindex].color = color; - verts[vertindex + 1].point = Point3F(circlePoints[imod].x, circlePoints[imod].y, 0.5f); - verts[vertindex + 1].color = color; + // Second triangle (top[i], base[next], top[next]) + verts[idx + 3].point = verts[topStartIdx + i].point; + verts[idx + 4].point = verts[baseStartIdx + next].point; + verts[idx + 5].point = verts[topStartIdx + next].point; + + for (int j = 0; j < 6; ++j) + verts[idx + j].color = color; } verts.unlock(); - mDevice->setStateBlockByDesc( desc ); - - mDevice->setVertexBuffer( verts ); + mDevice->setStateBlockByDesc(desc); + mDevice->setVertexBuffer(verts); mDevice->setupGenericShaders(); - mDevice->drawPrimitive( GFXTriangleStrip, 0, numPoints-2 ); - mDevice->drawPrimitive( GFXTriangleStrip, numPoints, numPoints - 2); - mDevice->drawPrimitive( GFXTriangleStrip, numPoints*2, numPoints * 2); + // Draw base cap + mDevice->drawPrimitive(GFXTriangleList, baseCenterIdx, numPoints - 2); + + // Draw top cap + mDevice->drawPrimitive(GFXTriangleList, topCenterIdx, numPoints - 2); + + // Draw sides (2 triangles per segment) + mDevice->drawPrimitive(GFXTriangleList, sideStartIdx, numPoints * 2); mDevice->popWorldMatrix(); } diff --git a/Engine/source/gfx/gl/gfxGLCircularVolatileBuffer.h b/Engine/source/gfx/gl/gfxGLCircularVolatileBuffer.h index 6abeaf8e4..cd961a19e 100644 --- a/Engine/source/gfx/gl/gfxGLCircularVolatileBuffer.h +++ b/Engine/source/gfx/gl/gfxGLCircularVolatileBuffer.h @@ -178,39 +178,59 @@ public: U32 mSize = 0; }_getBufferData; - void lock(const U32 size, U32 offsetAlign, U32 &outOffset, void* &outPtr) + void lock(const U32 size, U32 offsetAlign, U32& outOffset, void*& outPtr) { - if( !size ) + if (!size) { - AssertFatal(0, ""); + AssertFatal(0, "GLCircularVolatileBuffer::lock - size must be > 0"); outOffset = 0; - outPtr = NULL; + outPtr = nullptr; + return; } - mLockManager.waitFirstRange( mBufferFreePos, (mBufferFreePos + size)-1 ); + // Align free pos first (before wraparound check) + if (offsetAlign) + { + mBufferFreePos = ((mBufferFreePos + offsetAlign - 1) / offsetAlign) * offsetAlign; + } - if( mBufferFreePos + size > mBufferSize ) - { - mUsedRanges.push_back( UsedRange( mBufferFreePos, mBufferSize-1 ) ); + // If the size won't fit from current pos to end, wrap around + if (mBufferFreePos + size > mBufferSize) + { + // Protect the remaining space + if (mBufferFreePos < mBufferSize) + mUsedRanges.push_back(UsedRange(mBufferFreePos, mBufferSize - 1)); + + // Reset free pos mBufferFreePos = 0; - } - // force offset buffer align - if( offsetAlign ) - mBufferFreePos = ( (mBufferFreePos/offsetAlign) + 1 ) * offsetAlign; + // Realign after wrap + if (offsetAlign) + { + mBufferFreePos = ((mBufferFreePos + offsetAlign - 1) / offsetAlign) * offsetAlign; + } + + // Now check for overlaps *after* wrapping + mLockManager.waitOverlapRanges(mBufferFreePos, mBufferFreePos + size - 1); + } + else + { + // Normal range wait + mLockManager.waitOverlapRanges(mBufferFreePos, mBufferFreePos + size - 1); + } outOffset = mBufferFreePos; - if( GFXGL->mCapabilities.bufferStorage ) - { - outPtr = (U8*)(mBufferPtr) + mBufferFreePos; - } - else if( GFXGL->glUseMap() ) + if (GFXGL->mCapabilities.bufferStorage) { - PRESERVE_BUFFER( mBinding ); + outPtr = static_cast(mBufferPtr) + mBufferFreePos; + } + else if (GFXGL->glUseMap()) + { + PRESERVE_BUFFER(mBinding); glBindBuffer(mBinding, mBufferName); - const GLbitfield access = GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT | GL_MAP_UNSYNCHRONIZED_BIT; + const GLbitfield access = GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT; outPtr = glMapBufferRange(mBinding, outOffset, size, access); } else @@ -218,14 +238,13 @@ public: _getBufferData.mOffset = outOffset; _getBufferData.mSize = size; - outPtr = mFrameAllocator.lock( size ); - } + outPtr = mFrameAllocator.lock(size); + } - //set new buffer pos - mBufferFreePos = mBufferFreePos + size; + mBufferFreePos += size; //align 4bytes - mBufferFreePos = ( (mBufferFreePos/4) + 1 ) * 4; + mBufferFreePos = ((mBufferFreePos + 4 - 1) / 4) * 4; } void unlock() diff --git a/Engine/source/navigation/ChunkyTriMesh.cpp b/Engine/source/navigation/ChunkyTriMesh.cpp new file mode 100644 index 000000000..242bf285a --- /dev/null +++ b/Engine/source/navigation/ChunkyTriMesh.cpp @@ -0,0 +1,315 @@ +// +// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +// + +#include "ChunkyTriMesh.h" +#include +#include +#include + +struct BoundsItem +{ + float bmin[2]; + float bmax[2]; + int i; +}; + +static int compareItemX(const void* va, const void* vb) +{ + const BoundsItem* a = (const BoundsItem*)va; + const BoundsItem* b = (const BoundsItem*)vb; + if (a->bmin[0] < b->bmin[0]) + return -1; + if (a->bmin[0] > b->bmin[0]) + return 1; + return 0; +} + +static int compareItemY(const void* va, const void* vb) +{ + const BoundsItem* a = (const BoundsItem*)va; + const BoundsItem* b = (const BoundsItem*)vb; + if (a->bmin[1] < b->bmin[1]) + return -1; + if (a->bmin[1] > b->bmin[1]) + return 1; + return 0; +} + +static void calcExtends(const BoundsItem* items, const int /*nitems*/, + const int imin, const int imax, + float* bmin, float* bmax) +{ + bmin[0] = items[imin].bmin[0]; + bmin[1] = items[imin].bmin[1]; + + bmax[0] = items[imin].bmax[0]; + bmax[1] = items[imin].bmax[1]; + + for (int i = imin+1; i < imax; ++i) + { + const BoundsItem& it = items[i]; + if (it.bmin[0] < bmin[0]) bmin[0] = it.bmin[0]; + if (it.bmin[1] < bmin[1]) bmin[1] = it.bmin[1]; + + if (it.bmax[0] > bmax[0]) bmax[0] = it.bmax[0]; + if (it.bmax[1] > bmax[1]) bmax[1] = it.bmax[1]; + } +} + +inline int longestAxis(float x, float y) +{ + return y > x ? 1 : 0; +} + +static void subdivide(BoundsItem* items, int nitems, int imin, int imax, int trisPerChunk, + int& curNode, rcChunkyTriMeshNode* nodes, const int maxNodes, + int& curTri, int* outTris, const int* inTris) +{ + int inum = imax - imin; + int icur = curNode; + + if (curNode >= maxNodes) + return; + + rcChunkyTriMeshNode& node = nodes[curNode++]; + + if (inum <= trisPerChunk) + { + // Leaf + calcExtends(items, nitems, imin, imax, node.bmin, node.bmax); + + // Copy triangles. + node.i = curTri; + node.n = inum; + + for (int i = imin; i < imax; ++i) + { + const int* src = &inTris[items[i].i*3]; + int* dst = &outTris[curTri*3]; + curTri++; + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; + } + } + else + { + // Split + calcExtends(items, nitems, imin, imax, node.bmin, node.bmax); + + int axis = longestAxis(node.bmax[0] - node.bmin[0], + node.bmax[1] - node.bmin[1]); + + if (axis == 0) + { + // Sort along x-axis + qsort(items+imin, static_cast(inum), sizeof(BoundsItem), compareItemX); + } + else if (axis == 1) + { + // Sort along y-axis + qsort(items+imin, static_cast(inum), sizeof(BoundsItem), compareItemY); + } + + int isplit = imin+inum/2; + + // Left + subdivide(items, nitems, imin, isplit, trisPerChunk, curNode, nodes, maxNodes, curTri, outTris, inTris); + // Right + subdivide(items, nitems, isplit, imax, trisPerChunk, curNode, nodes, maxNodes, curTri, outTris, inTris); + + int iescape = curNode - icur; + // Negative index means escape. + node.i = -iescape; + } +} + +bool rcCreateChunkyTriMesh(const float* verts, const int* tris, int ntris, + int trisPerChunk, rcChunkyTriMesh* cm) +{ + int nchunks = (ntris + trisPerChunk-1) / trisPerChunk; + + cm->nodes = new rcChunkyTriMeshNode[nchunks*4]; + if (!cm->nodes) + return false; + + cm->tris = new int[ntris*3]; + if (!cm->tris) + return false; + + cm->ntris = ntris; + + // Build tree + BoundsItem* items = new BoundsItem[ntris]; + if (!items) + return false; + + for (int i = 0; i < ntris; i++) + { + const int* t = &tris[i*3]; + BoundsItem& it = items[i]; + it.i = i; + // Calc triangle XZ bounds. + it.bmin[0] = it.bmax[0] = verts[t[0]*3+0]; + it.bmin[1] = it.bmax[1] = verts[t[0]*3+2]; + for (int j = 1; j < 3; ++j) + { + const float* v = &verts[t[j]*3]; + if (v[0] < it.bmin[0]) it.bmin[0] = v[0]; + if (v[2] < it.bmin[1]) it.bmin[1] = v[2]; + + if (v[0] > it.bmax[0]) it.bmax[0] = v[0]; + if (v[2] > it.bmax[1]) it.bmax[1] = v[2]; + } + } + + int curTri = 0; + int curNode = 0; + subdivide(items, ntris, 0, ntris, trisPerChunk, curNode, cm->nodes, nchunks*4, curTri, cm->tris, tris); + + delete [] items; + + cm->nnodes = curNode; + + // Calc max tris per node. + cm->maxTrisPerChunk = 0; + for (int i = 0; i < cm->nnodes; ++i) + { + rcChunkyTriMeshNode& node = cm->nodes[i]; + const bool isLeaf = node.i >= 0; + if (!isLeaf) continue; + if (node.n > cm->maxTrisPerChunk) + cm->maxTrisPerChunk = node.n; + } + + return true; +} + + +inline bool checkOverlapRect(const float amin[2], const float amax[2], + const float bmin[2], const float bmax[2]) +{ + bool overlap = true; + overlap = (amin[0] > bmax[0] || amax[0] < bmin[0]) ? false : overlap; + overlap = (amin[1] > bmax[1] || amax[1] < bmin[1]) ? false : overlap; + return overlap; +} + +int rcGetChunksOverlappingRect(const rcChunkyTriMesh* cm, + float bmin[2], float bmax[2], + int* ids, const int maxIds) +{ + // Traverse tree + int i = 0; + int n = 0; + while (i < cm->nnodes) + { + const rcChunkyTriMeshNode* node = &cm->nodes[i]; + const bool overlap = checkOverlapRect(bmin, bmax, node->bmin, node->bmax); + const bool isLeafNode = node->i >= 0; + + if (isLeafNode && overlap) + { + if (n < maxIds) + { + ids[n] = i; + n++; + } + } + + if (overlap || isLeafNode) + i++; + else + { + const int escapeIndex = -node->i; + i += escapeIndex; + } + } + + return n; +} + + + +static bool checkOverlapSegment(const float p[2], const float q[2], + const float bmin[2], const float bmax[2]) +{ + static const float EPSILON = 1e-6f; + + float tmin = 0; + float tmax = 1; + float d[2]; + d[0] = q[0] - p[0]; + d[1] = q[1] - p[1]; + + for (int i = 0; i < 2; i++) + { + if (fabsf(d[i]) < EPSILON) + { + // Ray is parallel to slab. No hit if origin not within slab + if (p[i] < bmin[i] || p[i] > bmax[i]) + return false; + } + else + { + // Compute intersection t value of ray with near and far plane of slab + float ood = 1.0f / d[i]; + float t1 = (bmin[i] - p[i]) * ood; + float t2 = (bmax[i] - p[i]) * ood; + if (t1 > t2) { float tmp = t1; t1 = t2; t2 = tmp; } + if (t1 > tmin) tmin = t1; + if (t2 < tmax) tmax = t2; + if (tmin > tmax) return false; + } + } + return true; +} + +int rcGetChunksOverlappingSegment(const rcChunkyTriMesh* cm, + float p[2], float q[2], + int* ids, const int maxIds) +{ + // Traverse tree + int i = 0; + int n = 0; + while (i < cm->nnodes) + { + const rcChunkyTriMeshNode* node = &cm->nodes[i]; + const bool overlap = checkOverlapSegment(p, q, node->bmin, node->bmax); + const bool isLeafNode = node->i >= 0; + + if (isLeafNode && overlap) + { + if (n < maxIds) + { + ids[n] = i; + n++; + } + } + + if (overlap || isLeafNode) + i++; + else + { + const int escapeIndex = -node->i; + i += escapeIndex; + } + } + + return n; +} diff --git a/Engine/source/navigation/ChunkyTriMesh.h b/Engine/source/navigation/ChunkyTriMesh.h new file mode 100644 index 000000000..0870d64ef --- /dev/null +++ b/Engine/source/navigation/ChunkyTriMesh.h @@ -0,0 +1,59 @@ +// +// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +// + +#ifndef CHUNKYTRIMESH_H +#define CHUNKYTRIMESH_H + +struct rcChunkyTriMeshNode +{ + float bmin[2]; + float bmax[2]; + int i; + int n; +}; + +struct rcChunkyTriMesh +{ + inline rcChunkyTriMesh() : nodes(0), nnodes(0), tris(0), ntris(0), maxTrisPerChunk(0) {} + inline ~rcChunkyTriMesh() { delete [] nodes; delete [] tris; } + + rcChunkyTriMeshNode* nodes; + int nnodes; + int* tris; + int ntris; + int maxTrisPerChunk; + +private: + // Explicitly disabled copy constructor and copy assignment operator. + rcChunkyTriMesh(const rcChunkyTriMesh&); + rcChunkyTriMesh& operator=(const rcChunkyTriMesh&); +}; + +/// Creates partitioned triangle mesh (AABB tree), +/// where each node contains at max trisPerChunk triangles. +bool rcCreateChunkyTriMesh(const float* verts, const int* tris, int ntris, + int trisPerChunk, rcChunkyTriMesh* cm); + +/// Returns the chunk indices which overlap the input rectable. +int rcGetChunksOverlappingRect(const rcChunkyTriMesh* cm, float bmin[2], float bmax[2], int* ids, const int maxIds); + +/// Returns the chunk indices which overlap the input segment. +int rcGetChunksOverlappingSegment(const rcChunkyTriMesh* cm, float p[2], float q[2], int* ids, const int maxIds); + + +#endif // CHUNKYTRIMESH_H diff --git a/Engine/source/navigation/coverPoint.cpp b/Engine/source/navigation/coverPoint.cpp index 29eda49af..c599c1a5a 100644 --- a/Engine/source/navigation/coverPoint.cpp +++ b/Engine/source/navigation/coverPoint.cpp @@ -31,6 +31,7 @@ #include "gfx/gfxDrawUtil.h" #include "renderInstance/renderPassManager.h" #include "console/engineAPI.h" +#include "T3D/gameBase/gameConnection.h" extern bool gEditingMission; @@ -285,14 +286,22 @@ void CoverPoint::prepRenderImage(SceneRenderState *state) void CoverPoint::render(ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat) { - initGFXResources(); + if (!state->isDiffusePass()) return; - if(overrideMat) + GameConnection* conn = GameConnection::getConnectionToServer(); + MatrixF camTrans; + conn->getControlCameraTransform(0, &camTrans); + + if ((getPosition() - camTrans.getPosition()).lenSquared() > 2500) return; //50 unit clamp + + if (overrideMat) return; + initGFXResources(); + if(smVertexBuffer[mSize].isNull()) return; - + PROFILE_SCOPE(CoverPoint_Render); // Set up a GFX debug event (this helps with debugging rendering events in external tools) diff --git a/Engine/source/navigation/duDebugDrawTorque.cpp b/Engine/source/navigation/duDebugDrawTorque.cpp index 0c911e181..8475095f5 100644 --- a/Engine/source/navigation/duDebugDrawTorque.cpp +++ b/Engine/source/navigation/duDebugDrawTorque.cpp @@ -38,28 +38,64 @@ duDebugDrawTorque::duDebugDrawTorque() { + VECTOR_SET_ASSOCIATION(mVertList); + VECTOR_SET_ASSOCIATION(mDrawCache); mPrimType = 0; - mQuadsMode = false; mVertCount = 0; - mGroup = 0; - mCurrColor = 0; - mOverrideColor = 0; - mOverride = false; - dMemset(&mStore, 0, sizeof(mStore)); + mOverrideState = false; } duDebugDrawTorque::~duDebugDrawTorque() { - clear(); } void duDebugDrawTorque::depthMask(bool state) { - mDesc.setZReadWrite(state, state); + if (mOverrideState) + return; + + mDesc.setZReadWrite(state); + if (!state) + { + mDesc.setCullMode(GFXCullNone); + mDesc.setBlend(true); + } + else + { + mDesc.setCullMode(GFXCullCW); + mDesc.setBlend(false); + } +} + +void duDebugDrawTorque::depthMask(bool state, bool isOverride) +{ + depthMask(state); + mOverrideState = isOverride; +} + +void duDebugDrawTorque::blend(bool blend) +{ + mDesc.setBlend(true); } void duDebugDrawTorque::texture(bool state) { + // need a checker texture?...... if(state is true) then set first slot to that texture. +} + +unsigned int duDebugDrawTorque::areaToCol(unsigned int area) +{ + switch (area) + { + // Ground (1) : light blue + case GroundArea: return duRGBA(0, 192, 255, 255); + // Water : blue + case WaterArea: return duRGBA(0, 0, 255, 255); + // Road : brown + case OffMeshArea: return duRGBA(50, 20, 12, 255); + // Unexpected : red + default: return duRGBA(255, 0, 0, 255); + } } /// Begin drawing primitives. @@ -67,27 +103,20 @@ void duDebugDrawTorque::texture(bool state) /// @param size [in] size of a primitive, applies to point size and line width only. void duDebugDrawTorque::begin(duDebugDrawPrimitives prim, float size) { - mCurrColor = -1; - mQuadsMode = false; + if (!mVertList.empty()) + mVertList.clear(); + mVertCount = 0; mPrimType = 0; - switch(prim) - { - case DU_DRAW_POINTS: mPrimType = GFXPointList; break; - case DU_DRAW_LINES: mPrimType = GFXLineList; break; - case DU_DRAW_TRIS: mPrimType = GFXTriangleList; break; - case DU_DRAW_QUADS: mPrimType = GFXTriangleList; - mQuadsMode = true; break; - } - mBuffers.push_back(Buffer(mPrimType)); - mBuffers.last().group = mGroup; - mDesc.setCullMode(GFXCullNone); - mDesc.setBlend(true); -} -void duDebugDrawTorque::beginGroup(U32 group) -{ - mGroup = group; + switch (prim) + { + case DU_DRAW_POINTS: mPrimType = DU_DRAW_POINTS; break; + case DU_DRAW_LINES: mPrimType = DU_DRAW_LINES; break; + case DU_DRAW_TRIS: mPrimType = DU_DRAW_TRIS; break; + case DU_DRAW_QUADS: mPrimType = DU_DRAW_QUADS; break; + } + } /// Submit a vertex @@ -103,30 +132,7 @@ void duDebugDrawTorque::vertex(const float* pos, unsigned int color) /// @param color [in] color of the verts. void duDebugDrawTorque::vertex(const float x, const float y, const float z, unsigned int color) { - if(mQuadsMode) - { - if(mVertCount == 3) - { - _vertex(x, -z, y, color); - _vertex(mStore[0][0], mStore[0][1], mStore[0][2], color); - _vertex(mStore[1][0], mStore[1][1], mStore[1][2], color); - _vertex(mStore[1][0], mStore[1][1], mStore[1][2], color); - _vertex(mStore[2][0], mStore[2][1], mStore[2][2], color); - _vertex(x, -z, y, color); - mVertCount = 0; - } - else - { - mStore[mVertCount][0] = x; - mStore[mVertCount][1] = -z; - mStore[mVertCount][2] = y; - mVertCount++; - } - } - else - { - _vertex(x, -z, y, color); - } + _vertex(x, -z, y, color); } /// Submit a vertex @@ -148,105 +154,325 @@ void duDebugDrawTorque::vertex(const float x, const float y, const float z, unsi /// Push a vertex onto the buffer. void duDebugDrawTorque::_vertex(const float x, const float y, const float z, unsigned int color) { - // Use override color if we must. - //if(mOverride) - //color = mOverrideColor; - if(mCurrColor != color || !mBuffers.last().buffer.size()) - { - U8 r, g, b, a; - // Convert color integer to components. - rcCol(color, r, g, b, a); - mBuffers.last().buffer.push_back(Instruction(r, g, b, a)); - mCurrColor = color; - } - // Construct vertex data. - mBuffers.last().buffer.push_back(Instruction(x, y, z)); + GFXVertexPCT vert; + vert.point.set(x, y, z); + + U8 r, g, b, a; + // Convert color integer to components. + rcCol(color, r, g, b, a); + + vert.color.set(r, g, b, a); + + mVertList.push_back(vert); } /// End drawing primitives. void duDebugDrawTorque::end() { -} + if (mVertList.empty()) + return; -void duDebugDrawTorque::overrideColor(unsigned int col) -{ - mOverride = true; - mOverrideColor = col; -} - -void duDebugDrawTorque::cancelOverride() -{ - mOverride = false; -} - -void duDebugDrawTorque::renderBuffer(Buffer &b) -{ - PrimBuild::begin(b.primType, b.buffer.size()); - Vector &buf = b.buffer; - for(U32 i = 0; i < buf.size(); i++) + const U32 maxVertsPerDraw = GFX_MAX_DYNAMIC_VERTS; + + switch (mPrimType) { - switch(buf[i].type) + case DU_DRAW_POINTS: + { + const U32 totalPoints = mVertList.size(); + + for (U32 p = 0; p < totalPoints;) { - case Instruction::POINT: - PrimBuild::vertex3f(buf[i].data.point.x, - buf[i].data.point.y, - buf[i].data.point.z); - break; + const U32 pointsThisBatch = getMin(maxVertsPerDraw, totalPoints - p); + const U32 batchVerts = pointsThisBatch; + Box3F box; + box.minExtents.set(F32_MAX, F32_MAX, F32_MAX); + box.maxExtents.set(-F32_MAX, -F32_MAX, -F32_MAX); - case Instruction::COLOR: - if(mOverride) - break; - PrimBuild::color4i(buf[i].data.color.r, - buf[i].data.color.g, - buf[i].data.color.b, - buf[i].data.color.a); - break; - - default: - break; + GFXVertexBufferHandle buffer; + buffer.set(GFX, batchVerts, GFXBufferTypeStatic); + GFXVertexPCT* verts = buffer.lock(); + + for (U32 i = 0; i < pointsThisBatch; ++i) + { + verts[i] = mVertList[p + i]; + box.minExtents.setMin(verts[i].point); + box.maxExtents.setMax(verts[i].point); + } + buffer.unlock(); + + // --- Build index buffer + GFXPrimitiveBufferHandle pb; + pb.set(GFX, pointsThisBatch, pointsThisBatch, GFXBufferTypeStatic); + U16* indices = nullptr; + pb.lock(&indices); + + for (U32 i = 0; i < pointsThisBatch; ++i) + { + indices[i] = i; + } + + pb.unlock(); + + CachedDraw batch; + batch.primType = GFXPointList; + batch.buffer = buffer; + batch.vertexCount = batchVerts; + batch.primitiveBuffer = pb; + batch.primitiveCount = pointsThisBatch; + batch.state = mDesc; + batch.bounds = box; + + mDrawCache.push_back(batch); + + p += pointsThisBatch; } + break; } - PrimBuild::end(); + + case DU_DRAW_LINES: + { + AssertFatal(mVertList.size() % 2 == 0, "DU_DRAW_LINES given invalid vertex count."); + + const U32 vertsPerLine = 2; + const U32 totalLines = mVertList.size() / vertsPerLine; + + for (U32 l = 0; l < totalLines;) + { + const U32 linesThisBatch = getMin(maxVertsPerDraw / vertsPerLine, totalLines - l); + const U32 batchVerts = linesThisBatch * vertsPerLine; + Box3F box; + box.minExtents.set(F32_MAX, F32_MAX, F32_MAX); + box.maxExtents.set(-F32_MAX, -F32_MAX, -F32_MAX); + + GFXVertexBufferHandle buffer; + buffer.set(GFX, batchVerts, GFXBufferTypeStatic); + GFXVertexPCT* verts = buffer.lock(); + + for (U32 i = 0; i < linesThisBatch * vertsPerLine; ++i) + { + verts[i] = mVertList[l * vertsPerLine + i]; + box.minExtents.setMin(verts[i].point); + box.maxExtents.setMax(verts[i].point); + } + buffer.unlock(); + + // --- Build index buffer + GFXPrimitiveBufferHandle pb; + pb.set(GFX, linesThisBatch * 2, linesThisBatch, GFXBufferTypeStatic); + U16* indices = nullptr; + pb.lock(&indices); + + for (U32 i = 0; i < linesThisBatch; ++i) + { + indices[i * 2 + 0] = i * 2; + indices[i * 2 + 1] = i * 2 + 1; + } + + pb.unlock(); + + CachedDraw batch; + batch.primType = GFXLineList; + batch.buffer = buffer; + batch.vertexCount = batchVerts; + batch.primitiveBuffer = pb; + batch.primitiveCount = linesThisBatch; + batch.state = mDesc; + batch.bounds = box; + + mDrawCache.push_back(batch); + + l += linesThisBatch; + } + + break; + } + + case DU_DRAW_TRIS: + { + AssertFatal(mVertList.size() % 3 == 0, "DU_DRAW_TRIS given invalid vertex count."); + + const U32 vertsPerTri = 3; + const U32 totalTris = mVertList.size() / vertsPerTri; + + for (U32 t = 0; t < totalTris;) + { + const U32 trisThisBatch = getMin(maxVertsPerDraw / vertsPerTri, totalTris - t); + const U32 batchVerts = trisThisBatch * vertsPerTri; + Box3F box; + box.minExtents.set(F32_MAX, F32_MAX, F32_MAX); + box.maxExtents.set(-F32_MAX, -F32_MAX, -F32_MAX); + + GFXVertexBufferHandle buffer; + buffer.set(GFX, batchVerts, GFXBufferTypeStatic); + GFXVertexPCT* verts = buffer.lock(); + + for (U32 i = 0; i < trisThisBatch * vertsPerTri; ++i) + { + verts[i] = mVertList[t * vertsPerTri + i]; + box.minExtents.setMin(verts[i].point); + box.maxExtents.setMax(verts[i].point); + } + + buffer.unlock(); + + // --- Build index buffer + GFXPrimitiveBufferHandle pb; + pb.set(GFX, trisThisBatch*3, trisThisBatch, GFXBufferTypeStatic); + U16* indices = nullptr; + pb.lock(&indices); + + for (U32 i = 0; i < trisThisBatch; ++i) + { + indices[i * 3 + 0] = i * 3 + 0; + indices[i * 3 + 1] = i * 3 + 1; + indices[i * 3 + 2] = i * 3 + 2; + } + + pb.unlock(); + + CachedDraw batch; + batch.primType = GFXTriangleList; + batch.buffer = buffer; + batch.vertexCount = batchVerts; + batch.primitiveBuffer = pb; + batch.primitiveCount = trisThisBatch; + batch.state = mDesc; + batch.bounds = box; + + mDrawCache.push_back(batch); + + t += trisThisBatch; + } + + break; + } + + case DU_DRAW_QUADS: + { + AssertFatal(mVertList.size() % 4 == 0, "DU_DRAW_QUADS given wrong number of vertices."); + const U32 vertsPerQuad = 4; + const U32 totalQuads = mVertList.size() / 4; + + for (U32 q = 0; q < totalQuads;) + { + const U32 quadsThisBatch = getMin(maxVertsPerDraw / vertsPerQuad, totalQuads - q); + const U32 batchVerts = quadsThisBatch * vertsPerQuad; + const U32 batchIndices = quadsThisBatch * 6; + Box3F box; + box.minExtents.set(F32_MAX, F32_MAX, F32_MAX); + box.maxExtents.set(-F32_MAX, -F32_MAX, -F32_MAX); + + GFXVertexBufferHandle buffer; + buffer.set(GFX, batchVerts, GFXBufferTypeStatic); + GFXVertexPCT* verts = buffer.lock(); + + U32 outIdx = 0; + for (U32 i = 0; i < quadsThisBatch; ++i) + { + const GFXVertexPCT& v0 = mVertList[(q + i) * 4 + 0]; + const GFXVertexPCT& v1 = mVertList[(q + i) * 4 + 1]; + const GFXVertexPCT& v2 = mVertList[(q + i) * 4 + 2]; + const GFXVertexPCT& v3 = mVertList[(q + i) * 4 + 3]; + + verts[outIdx++] = v0; + verts[outIdx++] = v1; + verts[outIdx++] = v2; + verts[outIdx++] = v3; + } + + buffer.unlock(); + + GFXPrimitiveBufferHandle pb; + pb.set(GFX, batchIndices, quadsThisBatch*2, GFXBufferTypeStatic); + U16* indices = nullptr; + pb.lock(&indices); + + for (U32 i = 0; i < quadsThisBatch; ++i) + { + const U16 base = i * 4; + indices[i * 6 + 0] = base + 0; + indices[i * 6 + 1] = base + 1; + indices[i * 6 + 2] = base + 2; + indices[i * 6 + 3] = base + 0; + indices[i * 6 + 4] = base + 2; + indices[i * 6 + 5] = base + 3; + } + + pb.unlock(); + + CachedDraw batch; + batch.primType = GFXTriangleList; + batch.buffer = buffer; + batch.vertexCount = batchVerts; + batch.primitiveBuffer = pb; + batch.primitiveCount = quadsThisBatch*2; + batch.state = mDesc; + + mDrawCache.push_back(batch); + + q += quadsThisBatch; + } + break; + } + + } + + mVertList.clear(); } -void duDebugDrawTorque::render() +void duDebugDrawTorque::clearCache() { - GFXStateBlockRef sb = GFX->createStateBlock(mDesc); - GFX->setStateBlock(sb); - // Use override color for all rendering. - if(mOverride) + mDrawCache.clear(); +} + +void duDebugDrawTorque::render(SceneRenderState* state) +{ + if (!state->isDiffusePass()) return; + + const Frustum& frustum = state->getCameraFrustum(); + + for (U32 i = 0; i < mDrawCache.size(); ++i) { - U8 r, g, b, a; - rcCol(mOverrideColor, r, g, b, a); - PrimBuild::color4i(r, g, b, a); - } - for(U32 b = 0; b < mBuffers.size(); b++) - { - renderBuffer(mBuffers[b]); + const CachedDraw& draw = mDrawCache[i]; + + if (!frustum.getBounds().isOverlapped(draw.bounds)) + continue; + + GFX->setPrimitiveBuffer(draw.primitiveBuffer); + GFX->setStateBlockByDesc(draw.state); + GFX->setupGenericShaders(GFXDevice::GSColor); + GFX->setVertexBuffer(draw.buffer); + + GFX->drawIndexedPrimitive( + draw.primType, + 0, // start vertex + 0, // min vertex index + draw.vertexCount, // vertex count + 0, // start index + draw.primitiveCount // primitive count + ); } } -void duDebugDrawTorque::renderGroup(U32 group) +void duDebugDrawTorque::immediateRender() { - GFXStateBlockRef sb = GFX->createStateBlock(mDesc); - GFX->setStateBlock(sb); - // Use override color for all rendering. - if(mOverride) + for (U32 i = 0; i < mDrawCache.size(); ++i) { - U8 r, g, b, a; - rcCol(mOverrideColor, r, g, b, a); - PrimBuild::color4i(r, g, b, a); - } - for(U32 b = 0; b < mBuffers.size(); b++) - { - if(mBuffers[b].group == group) - renderBuffer(mBuffers[b]); + const CachedDraw& draw = mDrawCache[i]; + + GFX->setPrimitiveBuffer(draw.primitiveBuffer); + GFX->setStateBlockByDesc(draw.state); + GFX->setupGenericShaders(GFXDevice::GSColor); + GFX->setVertexBuffer(draw.buffer); + + GFX->drawIndexedPrimitive( + draw.primType, + 0, // start vertex + 0, // min vertex index + draw.vertexCount, // vertex count + 0, // start index + draw.primitiveCount // primitive count + ); } } -void duDebugDrawTorque::clear() -{ - for(U32 b = 0; b < mBuffers.size(); b++) - mBuffers[b].buffer.clear(); - mBuffers.clear(); -} diff --git a/Engine/source/navigation/duDebugDrawTorque.h b/Engine/source/navigation/duDebugDrawTorque.h index 782f51d13..ba7a0c9dd 100644 --- a/Engine/source/navigation/duDebugDrawTorque.h +++ b/Engine/source/navigation/duDebugDrawTorque.h @@ -23,12 +23,37 @@ #ifndef _DU_DEBUG_DRAW_TORQUE_H_ #define _DU_DEBUG_DRAW_TORQUE_H_ +#ifndef _TVECTOR_H_ #include "core/util/tVector.h" +#endif #include -#include "gfx/gfxStateBlock.h" -/// @brief Implements the duDebugDraw interface in Torque. -class duDebugDrawTorque: public duDebugDraw { +#ifndef _GFXSTATEBLOCK_H_ +#include "gfx/gfxStateBlock.h" +#endif + +#ifndef _GFXVERTEXTYPES_H_ +#include "gfx/gfxVertexTypes.h" +#endif + +#ifndef _GFXVERTEXBUFFER_H_ +#include "gfx/gfxVertexBuffer.h" +#endif + +#ifndef _SCENERENDERSTATE_H_ +#include "scene/sceneRenderState.h" +#endif + +/** +* @class duDebugDrawTorque +* @brief Implements the duDebugDraw interface in Torque. +* +* Every debug draw from recast goes through a process of begin, add vertex and then end +* just like our primbuilder class, but we need to catch these vertices +* and add them to a GFXVertexBuffer as recast supports GL_QUADS which is now +* deprecated. +*/ +class duDebugDrawTorque : public duDebugDraw { public: duDebugDrawTorque(); ~duDebugDrawTorque(); @@ -36,23 +61,20 @@ public: /// Enable/disable Z read. void depthMask(bool state) override; - /// Enable/disable texturing. Not used. - void texture(bool state) override; + /// + /// Enable/disable Z read and overrides any setting that will come from detour. + /// + /// Z read state. + /// Set to true to override any future changes. + void depthMask(bool state, bool isOverride); - /// Special colour overwrite for when I get picky about the colours Mikko chose. - void overrideColor(unsigned int col); - - /// Stop the colour override. - void cancelOverride(); + void blend(bool blend); /// Begin drawing primitives. /// @param prim [in] primitive type to draw, one of rcDebugDrawPrimitives. /// @param size [in] size of a primitive, applies to point size and line width only. void begin(duDebugDrawPrimitives prim, float size = 1.0f) override; - /// All new buffers go into this group. - void beginGroup(U32 group); - /// Submit a vertex /// @param pos [in] position of the verts. /// @param color [in] color of the verts. @@ -66,92 +88,57 @@ public: /// Submit a vertex /// @param pos [in] position of the verts. /// @param color [in] color of the verts. + /// @param uv [in] the uv coordinates. void vertex(const float* pos, unsigned int color, const float* uv) override; /// Submit a vertex /// @param x,y,z [in] position of the verts. /// @param color [in] color of the verts. + /// @param u [in] the u coordinate. + /// @param v [in] the v coordinate. void vertex(const float x, const float y, const float z, unsigned int color, const float u, const float v) override; + /// Set a texture + /// @param state, use a texture in this draw, usually a checker texture. + void texture(bool state) override; + + /// + /// Assigns a colour to an area type. + /// + /// The area type. + /// The colour in recast format for the area. + unsigned int areaToCol(unsigned int area) override; + /// End drawing primitives. void end() override; - /// Render buffered primitive. - void render(); + void clearCache(); + void render(SceneRenderState* state); + void immediateRender(); - /// Render buffered primitives in a group. - void renderGroup(U32 group); - - /// Delete buffered primitive. - void clear(); - private: + + struct CachedDraw { + GFXPrimitiveType primType; + GFXVertexBufferHandle buffer; + GFXPrimitiveBufferHandle primitiveBuffer; + U32 vertexCount; + U32 primitiveCount; + GFXStateBlockDesc state; + Box3F bounds; + }; + + Vector mDrawCache; + GFXStateBlockDesc mDesc; + Vector mVertList; // Our vertex list for setting up vertexBuffer in the End function. + GFXVertexBufferHandle mVertexBuffer; // our vertex buffer for drawing. U32 mPrimType; - bool mQuadsMode; - U32 mVertCount; - F32 mStore[3][3]; - - U32 mGroup; - - struct Instruction { - // Contain either a point or a color command. - union { - struct { - U8 r, g, b, a; - } color; - struct { - float x, y, z; - } point; - U32 primType; - } data; - // Which type of data do we store? - enum { - COLOR, - POINT, - PRIMTYPE, - } type; - // Construct as color instruction. - Instruction(U8 r, U8 g, U8 b, U8 a) { - type = COLOR; - data.color.r = r; - data.color.g = g; - data.color.b = b; - data.color.a = a; - } - // Construct as point. - Instruction(float x, float y, float z) { - type = POINT; - data.point.x = x; - data.point.y = y; - data.point.z = z; - } - Instruction(U32 t = 0) { - type = PRIMTYPE; - data.primType = t; - } - }; - - struct Buffer { - U32 group; - Vector buffer; - GFXPrimitiveType primType; - Buffer(U32 type = 0) { - primType = (GFXPrimitiveType)type; - group = 0; - } - }; - Vector mBuffers; - - U32 mCurrColor; - U32 mOverrideColor; - bool mOverride; + bool mOverrideState; void _vertex(const float x, const float y, const float z, unsigned int color); - - void renderBuffer(Buffer &b); }; #endif diff --git a/Engine/source/navigation/guiNavEditorCtrl.cpp b/Engine/source/navigation/guiNavEditorCtrl.cpp index eae204447..d01470817 100644 --- a/Engine/source/navigation/guiNavEditorCtrl.cpp +++ b/Engine/source/navigation/guiNavEditorCtrl.cpp @@ -38,6 +38,7 @@ #include "gui/worldEditor/undoActions.h" #include "T3D/gameBase/gameConnection.h" #include "T3D/AI/AIController.h" +#include "navigation/navMeshTool.h" IMPLEMENT_CONOBJECT(GuiNavEditorCtrl); @@ -47,24 +48,11 @@ ConsoleDocClass(GuiNavEditorCtrl, "@internal" ); -// Each of the mode names directly correlates with the Nav Editor's tool palette. -const String GuiNavEditorCtrl::mSelectMode = "SelectMode"; -const String GuiNavEditorCtrl::mLinkMode = "LinkMode"; -const String GuiNavEditorCtrl::mCoverMode = "CoverMode"; -const String GuiNavEditorCtrl::mTileMode = "TileMode"; -const String GuiNavEditorCtrl::mTestMode = "TestMode"; - GuiNavEditorCtrl::GuiNavEditorCtrl() { - mMode = mSelectMode; mIsDirty = false; mStartDragMousePoint = InvalidMousePoint; mMesh = NULL; - mCurTile = mTile = -1; - mPlayer = mCurPlayer = NULL; - mSpawnClass = mSpawnDatablock = ""; - mLinkStart = Point3F::Max; - mLink = mCurLink = -1; } GuiNavEditorCtrl::~GuiNavEditorCtrl() @@ -101,25 +89,21 @@ void GuiNavEditorCtrl::initPersistFields() docsURL; addField("isDirty", TypeBool, Offset(mIsDirty, GuiNavEditorCtrl)); - addField("spawnClass", TypeRealString, Offset(mSpawnClass, GuiNavEditorCtrl), - "Class of object to spawn in test mode."); - addField("spawnDatablock", TypeRealString, Offset(mSpawnDatablock, GuiNavEditorCtrl), - "Datablock to give new objects in test mode."); - Parent::initPersistFields(); } void GuiNavEditorCtrl::onSleep() { Parent::onSleep(); - - //mMode = mSelectMode; } void GuiNavEditorCtrl::selectMesh(NavMesh *mesh) { mesh->setSelected(true); mMesh = mesh; + + if (mTool) + mTool->setActiveNavMesh(mMesh); } DefineEngineMethod(GuiNavEditorCtrl, selectMesh, void, (S32 id),, @@ -141,125 +125,6 @@ DefineEngineMethod(GuiNavEditorCtrl, getMesh, S32, (),, return object->getMeshId(); } -S32 GuiNavEditorCtrl::getPlayerId() -{ - return mPlayer.isNull() ? 0 : mPlayer->getId(); -} - -DefineEngineMethod(GuiNavEditorCtrl, getPlayer, S32, (),, - "@brief Select a NavMesh object.") -{ - return object->getPlayerId(); -} - -void GuiNavEditorCtrl::deselect() -{ - if(!mMesh.isNull()) - mMesh->setSelected(false); - mMesh = NULL; - mPlayer = mCurPlayer = NULL; - mCurTile = mTile = -1; - mLinkStart = Point3F::Max; - mLink = mCurLink = -1; -} - -DefineEngineMethod(GuiNavEditorCtrl, deselect, void, (),, - "@brief Deselect whatever is currently selected in the editor.") -{ - object->deselect(); -} - -void GuiNavEditorCtrl::deleteLink() -{ - if(!mMesh.isNull() && mLink != -1) - { - mMesh->selectLink(mLink, false); - mMesh->deleteLink(mLink); - mLink = -1; - Con::executef(this, "onLinkDeselected"); - } -} - -DefineEngineMethod(GuiNavEditorCtrl, deleteLink, void, (),, - "@brief Delete the currently selected link.") -{ - object->deleteLink(); -} - -void GuiNavEditorCtrl::setLinkFlags(const LinkData &d) -{ - if(mMode == mLinkMode && !mMesh.isNull() && mLink != -1) - { - mMesh->setLinkFlags(mLink, d); - } -} - -DefineEngineMethod(GuiNavEditorCtrl, setLinkFlags, void, (U32 flags),, - "@Brief Set jump and drop properties of the selected link.") -{ - object->setLinkFlags(LinkData(flags)); -} - -void GuiNavEditorCtrl::buildTile() -{ - if(!mMesh.isNull() && mTile != -1) - mMesh->buildTile(mTile); -} - -DefineEngineMethod(GuiNavEditorCtrl, buildTile, void, (),, - "@brief Build the currently selected tile.") -{ - object->buildTile(); -} - -void GuiNavEditorCtrl::spawnPlayer(const Point3F &pos) -{ - SceneObject *obj = (SceneObject*)Sim::spawnObject(mSpawnClass, mSpawnDatablock); - if(obj) - { - MatrixF mat(true); - mat.setPosition(pos); - obj->setTransform(mat); - SimObject* cleanup = Sim::findObject("MissionCleanup"); - if(cleanup) - { - SimGroup* missionCleanup = dynamic_cast(cleanup); - missionCleanup->addObject(obj); - } - mPlayer = obj; -#ifdef TORQUE_NAVIGATION_ENABLED - AIPlayer* asAIPlayer = dynamic_cast(obj); - if (asAIPlayer) //try direct - { - Con::executef(this, "onPlayerSelected", Con::getIntArg(asAIPlayer->mLinkTypes.getFlags())); - } - else - { - ShapeBase* sbo = dynamic_cast(obj); - if (sbo->getAIController()) - { - if (sbo->getAIController()->mControllerData) - Con::executef(this, "onPlayerSelected", Con::getIntArg(sbo->getAIController()->mControllerData->mLinkTypes.getFlags())); - } - else - { -#endif - Con::executef(this, "onPlayerSelected"); -#ifdef TORQUE_NAVIGATION_ENABLED - } - } -#endif - } -} - -DefineEngineMethod(GuiNavEditorCtrl, spawnPlayer, void, (),, - "@brief Spawn an AIPlayer at the centre of the screen.") -{ - Point3F c; - if(object->get3DCentre(c)) - object->spawnPlayer(c); -} - void GuiNavEditorCtrl::get3DCursor(GuiCursor *&cursor, bool &visible, const Gui3DMouseEvent &event_) @@ -315,148 +180,12 @@ void GuiNavEditorCtrl::on3DMouseDown(const Gui3DMouseEvent & event) { mGizmo->on3DMouseDown(event); - if(!isFirstResponder()) - setFirstResponder(); + if (mTool) + mTool->on3DMouseDown(event); mouseLock(); - // Construct a LineSegment from the camera position to 1000 meters away in - // the direction clicked. - // If that segment hits the terrain, truncate the ray to only be that length. - - Point3F startPnt = event.pos; - Point3F endPnt = event.pos + event.vec * 1000.0f; - - RayInfo ri; - - U8 keys = Input::getModifierKeys(); - bool shift = keys & SI_LSHIFT; - bool ctrl = keys & SI_LCTRL; - - if(mMode == mLinkMode && !mMesh.isNull()) - { - if(gServerContainer.castRay(startPnt, endPnt, StaticObjectType, &ri)) - { - U32 link = mMesh->getLink(ri.point); - if(link != -1) - { - if(mLink != -1) - mMesh->selectLink(mLink, false); - mMesh->selectLink(link, true, false); - mLink = link; - LinkData d = mMesh->getLinkFlags(mLink); - Con::executef(this, "onLinkSelected", Con::getIntArg(d.getFlags())); - } - else - { - if(mLink != -1) - { - mMesh->selectLink(mLink, false); - mLink = -1; - Con::executef(this, "onLinkDeselected"); - } - else - { - if(mLinkStart != Point3F::Max) - { - mMesh->addLink(mLinkStart, ri.point); - if(!shift) - mLinkStart = Point3F::Max; - } - else - { - mLinkStart = ri.point; - } - } - } - } - else - { - mMesh->selectLink(mLink, false); - mLink = -1; - Con::executef(this, "onLinkDeselected"); - } - } - - if(mMode == mTileMode && !mMesh.isNull()) - { - if(gServerContainer.castRay(startPnt, endPnt, StaticShapeObjectType, &ri)) - { - mTile = mMesh->getTile(ri.point); - dd.clear(); - mMesh->renderTileData(dd, mTile); - } - } - - if(mMode == mTestMode) - { - // Spawn new character - if(ctrl) - { - if(gServerContainer.castRay(startPnt, endPnt, StaticObjectType, &ri)) - spawnPlayer(ri.point); - } - // Deselect character - else if(shift) - { - mPlayer = NULL; - Con::executef(this, "onPlayerDeselected"); - } - // Select/move character - else - { - if(gServerContainer.castRay(startPnt, endPnt, PlayerObjectType | VehicleObjectType, &ri)) - { - if(ri.object) - { - mPlayer = ri.object; -#ifdef TORQUE_NAVIGATION_ENABLED - AIPlayer* asAIPlayer = dynamic_cast(mPlayer.getPointer()); - if (asAIPlayer) //try direct - { - Con::executef(this, "onPlayerSelected", Con::getIntArg(asAIPlayer->mLinkTypes.getFlags())); - } - else - { - ShapeBase* sbo = dynamic_cast(mPlayer.getPointer()); - if (sbo->getAIController()) - { - if (sbo->getAIController()->mControllerData) - Con::executef(this, "onPlayerSelected", Con::getIntArg(sbo->getAIController()->mControllerData->mLinkTypes.getFlags())); - } - else - { -#endif - Con::executef(this, "onPlayerSelected"); - } -#ifdef TORQUE_NAVIGATION_ENABLED - } - } -#endif - } - else if (!mPlayer.isNull() && gServerContainer.castRay(startPnt, endPnt, StaticObjectType, &ri)) - { - AIPlayer* asAIPlayer = dynamic_cast(mPlayer.getPointer()); - if (asAIPlayer) //try direct - { -#ifdef TORQUE_NAVIGATION_ENABLED - asAIPlayer->setPathDestination(ri.point); -#else - asAIPlayer->setMoveDestination(ri.point,false); -#endif - } - else - { - ShapeBase* sbo = dynamic_cast(mPlayer.getPointer()); - if (sbo->getAIController()) - { - if (sbo->getAIController()->mControllerData) - sbo->getAIController()->getNav()->setPathDestination(ri.point, true); - } - } - } - } - } + return; } void GuiNavEditorCtrl::on3DMouseUp(const Gui3DMouseEvent & event) @@ -464,64 +193,18 @@ void GuiNavEditorCtrl::on3DMouseUp(const Gui3DMouseEvent & event) // Keep the Gizmo up to date. mGizmo->on3DMouseUp(event); + if (mTool) + mTool->on3DMouseUp(event); + mouseUnlock(); } void GuiNavEditorCtrl::on3DMouseMove(const Gui3DMouseEvent & event) { - //if(mSelRiver != NULL && mSelNode != -1) - //mGizmo->on3DMouseMove(event); + if (mTool) + mTool->on3DMouseMove(event); - Point3F startPnt = event.pos; - Point3F endPnt = event.pos + event.vec * 1000.0f; - - RayInfo ri; - - if(mMode == mLinkMode && !mMesh.isNull()) - { - if(gServerContainer.castRay(startPnt, endPnt, StaticObjectType, &ri)) - { - U32 link = mMesh->getLink(ri.point); - if(link != -1) - { - if(link != mLink) - { - if(mCurLink != -1) - mMesh->selectLink(mCurLink, false); - mMesh->selectLink(link, true, true); - } - mCurLink = link; - } - else - { - if(mCurLink != mLink) - mMesh->selectLink(mCurLink, false); - mCurLink = -1; - } - } - else - { - mMesh->selectLink(mCurLink, false); - mCurLink = -1; - } - } - - // Select a tile from our current NavMesh. - if(mMode == mTileMode && !mMesh.isNull()) - { - if(gServerContainer.castRay(startPnt, endPnt, StaticObjectType, &ri)) - mCurTile = mMesh->getTile(ri.point); - else - mCurTile = -1; - } - - if(mMode == mTestMode) - { - if(gServerContainer.castRay(startPnt, endPnt, PlayerObjectType | VehicleObjectType, &ri)) - mCurPlayer = ri.object; - else - mCurPlayer = NULL; - } + return; } void GuiNavEditorCtrl::on3DMouseDragged(const Gui3DMouseEvent & event) @@ -549,6 +232,11 @@ void GuiNavEditorCtrl::on3DMouseLeave(const Gui3DMouseEvent & event) void GuiNavEditorCtrl::updateGuiInfo() { + if (mTool) + { + if (mTool->updateGuiInfo()) + return; + } } void GuiNavEditorCtrl::onRender(Point2I offset, const RectI &updateRect) @@ -559,22 +247,6 @@ void GuiNavEditorCtrl::onRender(Point2I offset, const RectI &updateRect) return; } -static void renderBoxOutline(const Box3F &box, const ColorI &col) -{ - if(box != Box3F::Invalid) - { - GFXStateBlockDesc desc; - desc.setCullMode(GFXCullNone); - desc.setFillModeSolid(); - desc.setZReadWrite(true, false); - desc.setBlend(true); - GFX->getDrawUtil()->drawCube(desc, box, ColorI(col, 20)); - desc.setFillModeWireframe(); - desc.setBlend(false); - GFX->getDrawUtil()->drawCube(desc, box, ColorI(col, 255)); - } -} - void GuiNavEditorCtrl::renderScene(const RectI & updateRect) { GFX->setStateBlock(mZDisableSB); @@ -592,45 +264,15 @@ void GuiNavEditorCtrl::renderScene(const RectI & updateRect) Point3F camPos; mat.getColumn(3,&camPos); - if(mMode == mLinkMode) - { - if(mLinkStart != Point3F::Max) - { - GFXStateBlockDesc desc; - desc.setBlend(false); - desc.setZReadWrite(true ,true); - MatrixF linkMat(true); - linkMat.setPosition(mLinkStart); - Point3F scale(0.8f, 0.8f, 0.8f); - GFX->getDrawUtil()->drawTransform(desc, linkMat, &scale); - } - } - - if(mMode == mTileMode && !mMesh.isNull()) - { - renderBoxOutline(mMesh->getTileBox(mCurTile), ColorI::BLUE); - renderBoxOutline(mMesh->getTileBox(mTile), ColorI::GREEN); - if(Con::getBoolVariable("$Nav::Editor::renderVoxels", false)) dd.renderGroup(0); - if(Con::getBoolVariable("$Nav::Editor::renderInput", false)) - { - dd.depthMask(false); - dd.renderGroup(1); - dd.depthMask(true); - } - } - - if(mMode == mTestMode) - { - if(!mCurPlayer.isNull()) - renderBoxOutline(mCurPlayer->getWorldBox(), ColorI::BLUE); - if(!mPlayer.isNull()) - renderBoxOutline(mPlayer->getWorldBox(), ColorI::GREEN); - } + if (mTool) + mTool->onRender3D(); duDebugDrawTorque d; - if(!mMesh.isNull()) + if (!mMesh.isNull()) + { mMesh->renderLinks(d); - d.render(); + d.immediateRender(); + } // Now draw all the 2d stuff! GFX->setClipRect(updateRect); @@ -652,15 +294,6 @@ bool GuiNavEditorCtrl::getStaticPos(const Gui3DMouseEvent & event, Point3F &tpos return hit; } -void GuiNavEditorCtrl::setMode(String mode, bool sourceShortcut = false) -{ - mMode = mode; - Con::executef(this, "onModeSet", mode); - - if(sourceShortcut) - Con::executef(this, "paletteSync", mode); -} - void GuiNavEditorCtrl::submitUndo(const UTF8 *name) { // Grab the mission editor undo manager. @@ -691,13 +324,41 @@ void GuiNavEditorCtrl::_prepRenderImage(SceneManager* sceneGraph, const SceneRen }*/ } -DefineEngineMethod(GuiNavEditorCtrl, getMode, const char*, (), , "") +void GuiNavEditorCtrl::setActiveTool(NavMeshTool* tool) { - return object->getMode(); + if (mTool) + { + mTool->onDeactivated(); + } + + mTool = tool; + + if (mTool) + { + mTool->setActiveEditor(this); + mTool->setActiveNavMesh(mMesh); + mTool->onActivated(mLastEvent); + } } -DefineEngineMethod(GuiNavEditorCtrl, setMode, void, (String mode),, "setMode(String mode)") +void GuiNavEditorCtrl::setDrawMode(S32 id) { - object->setMode(mode); + if (mMesh.isNull()) + return; + + mMesh->setDrawMode((NavMesh::DrawMode)id); } + +DefineEngineMethod(GuiNavEditorCtrl, setDrawMode, void, (S32 id), , + "@brief Deselect whatever is currently selected in the editor.") +{ + object->setDrawMode(id); +} + +DefineEngineMethod(GuiNavEditorCtrl, setActiveTool, void, (const char* toolName), , "( NavMeshTool tool )") +{ + NavMeshTool* tool = dynamic_cast(Sim::findObject(toolName)); + object->setActiveTool(tool); +} + #endif diff --git a/Engine/source/navigation/guiNavEditorCtrl.h b/Engine/source/navigation/guiNavEditorCtrl.h index c5d2f1b5a..c6eeea58f 100644 --- a/Engine/source/navigation/guiNavEditorCtrl.h +++ b/Engine/source/navigation/guiNavEditorCtrl.h @@ -34,6 +34,10 @@ #include "gui/worldEditor/gizmo.h" #endif +//#ifndef _NAVMESH_TOOL_H_ +//#include "navigation/navMeshTool.h" +//#endif + #include "navMesh.h" #include "T3D/aiPlayer.h" @@ -41,6 +45,7 @@ struct ObjectRenderInst; class SceneManager; class SceneRenderState; class BaseMatInstance; +class NavMeshTool; class GuiNavEditorCtrl : public EditTSCtrl { @@ -51,7 +56,6 @@ public: static const String mSelectMode; static const String mLinkMode; static const String mCoverMode; - static const String mTileMode; static const String mTestMode; GuiNavEditorCtrl(); @@ -96,26 +100,14 @@ public: bool getStaticPos(const Gui3DMouseEvent & event, Point3F &tpos); - void setMode(String mode, bool sourceShortcut); - String getMode() { return mMode; } - void selectMesh(NavMesh *mesh); - void deselect(); S32 getMeshId(); - S32 getPlayerId(); - - String mSpawnClass; - String mSpawnDatablock; - - void deleteLink(); - void setLinkFlags(const LinkData &d); - - void buildTile(); - - void spawnPlayer(const Point3F &pos); /// @} + void setActiveTool(NavMeshTool* tool); + + void setDrawMode(S32 id); protected: @@ -128,38 +120,21 @@ protected: bool mIsDirty; - String mMode; - /// Currently-selected NavMesh. SimObjectPtr mMesh; - /// @name Link mode - /// @{ - - Point3F mLinkStart; - S32 mCurLink; - S32 mLink; + /// The active tool in used by the editor. + SimObjectPtr mTool; /// @} /// @name Tile mode /// @{ - S32 mCurTile; - S32 mTile; - duDebugDrawTorque dd; /// @} - /// @name Test mode - /// @{ - - SimObjectPtr mPlayer; - SimObjectPtr mCurPlayer; - - /// @} - Gui3DMouseEvent mLastMouseEvent; #define InvalidMousePoint Point2I(-100,-100) diff --git a/Engine/source/navigation/navMesh.cpp b/Engine/source/navigation/navMesh.cpp index 253ee7922..dc4b9c6cc 100644 --- a/Engine/source/navigation/navMesh.cpp +++ b/Engine/source/navigation/navMesh.cpp @@ -40,12 +40,13 @@ #include "math/mathIO.h" #include "core/stream/fileStream.h" +#include "T3D/assets/LevelAsset.h" extern bool gEditingMission; IMPLEMENT_CO_NETOBJECT_V1(NavMesh); -const U32 NavMesh::mMaxVertsPerPoly = 3; +const U32 NavMesh::mMaxVertsPerPoly = DT_VERTS_PER_POLYGON; SimObjectPtr NavMesh::smServerSet = NULL; @@ -173,6 +174,13 @@ DefineEngineFunction(NavMeshUpdateOne, void, (S32 meshid, S32 objid, bool remove } NavMesh::NavMesh() +: m_triareas(0), + m_solid(0), + m_chf(0), + m_cset(0), + m_pmesh(0), + m_dmesh(0), + m_drawMode(DRAWMODE_NAVMESH) { mTypeMask |= StaticShapeObjectType | MarkerObjectType; mFileName = StringTable->EmptyString(); @@ -181,10 +189,10 @@ NavMesh::NavMesh() mSaveIntermediates = false; nm = NULL; ctx = NULL; + m_geo = NULL; mWaterMethod = Ignore; - dMemset(&cfg, 0, sizeof(cfg)); mCellSize = mCellHeight = 0.2f; mWalkableHeight = 2.0f; mWalkableClimb = 0.3f; @@ -214,10 +222,16 @@ NavMesh::NavMesh() mBuilding = false; mCurLinkID = 0; + + mWaterVertStart = 0; + mWaterTriStart = 0; + + mQuery = NULL; } NavMesh::~NavMesh() { + dtFreeNavMeshQuery(mQuery); dtFreeNavMesh(nm); nm = NULL; delete ctx; @@ -329,7 +343,7 @@ void NavMesh::initPersistFields() "Sets the sampling distance to use when generating the detail mesh."); addFieldV("detailSampleError", TypeRangedF32, Offset(mDetailSampleMaxError, NavMesh), &CommonValidators::PositiveFloat, "The maximum distance the detail mesh surface should deviate from heightfield data."); - addFieldV("maxEdgeLen", TypeRangedS32, Offset(mDetailSampleDist, NavMesh), &CommonValidators::PositiveInt, + addFieldV("maxEdgeLen", TypeRangedS32, Offset(mMaxEdgeLen, NavMesh), &CommonValidators::PositiveInt, "The maximum allowed length for contour edges along the border of the mesh."); addFieldV("simplificationError", TypeRangedF32, Offset(mMaxSimplificationError, NavMesh), &CommonValidators::PositiveFloat, "The maximum distance a simplfied contour's border edges should deviate from the original raw contour."); @@ -359,7 +373,7 @@ bool NavMesh::onAdd() if(gEditingMission || mAlwaysRender) { mNetFlags.set(Ghostable); - if(isClientObject()) + if (isClientObject()) renderToDrawer(); } @@ -397,7 +411,7 @@ void NavMesh::setScale(const VectorF &scale) Parent::setScale(scale); } -S32 NavMesh::addLink(const Point3F &from, const Point3F &to, U32 flags) +S32 NavMesh::addLink(const Point3F &from, const Point3F &to, bool biDir, F32 rad, U32 flags) { Point3F rcFrom = DTStoRC(from), rcTo = DTStoRC(to); mLinkVerts.push_back(rcFrom.x); @@ -407,8 +421,8 @@ S32 NavMesh::addLink(const Point3F &from, const Point3F &to, U32 flags) mLinkVerts.push_back(rcTo.y); mLinkVerts.push_back(rcTo.z); mLinksUnsynced.push_back(true); - mLinkRads.push_back(mWalkableRadius); - mLinkDirs.push_back(0); + mLinkRads.push_back(rad); + mLinkDirs.push_back(biDir ? 1 : 0); mLinkAreas.push_back(OffMeshArea); if (flags == 0) { Point3F dir = to - from; @@ -427,11 +441,11 @@ S32 NavMesh::addLink(const Point3F &from, const Point3F &to, U32 flags) return mLinkIDs.size() - 1; } -DefineEngineMethod(NavMesh, addLink, S32, (Point3F from, Point3F to, U32 flags), (0), +DefineEngineMethod(NavMesh, addLink, S32, (Point3F from, Point3F to, bool biDir, U32 flags), (0), "Add a link to this NavMesh between two points.\n\n" "") { - return object->addLink(from, to, flags); + return object->addLink(from, to, biDir, flags); } S32 NavMesh::getLink(const Point3F &pos) @@ -474,6 +488,43 @@ LinkData NavMesh::getLinkFlags(U32 idx) return LinkData(); } +bool NavMesh::getLinkDir(U32 idx) +{ + if (idx < mLinkIDs.size()) + { + return mLinkDirs[idx]; + } + + return false; +} + +F32 NavMesh::getLinkRadius(U32 idx) +{ + if (idx < mLinkIDs.size()) + { + return mLinkRads[idx]; + } + + return -1.0f; +} + +void NavMesh::setLinkDir(U32 idx, bool biDir) +{ + if (idx < mLinkIDs.size()) + { + mLinkDirs[idx] = biDir ? 1 : 0; + mLinksUnsynced[idx] = true; + } +} + +void NavMesh::setLinkRadius(U32 idx, F32 rad) +{ + if (idx < mLinkIDs.size()) + { + mLinkRads[idx] = rad; + } +} + DefineEngineMethod(NavMesh, getLinkFlags, S32, (U32 id),, "Get the flags set for a particular off-mesh link.") { @@ -599,6 +650,13 @@ DefineEngineMethod(NavMesh, deleteLinks, void, (),, //object->eraseLinks(); } +static void buildCallback(SceneObject* object, void* key) +{ + SceneContainer::CallbackInfo* info = reinterpret_cast(key); + if (!object->mPathfindingIgnore) + object->buildPolyList(info->context, info->polyList, info->boundingBox, info->boundingSphere); +} + bool NavMesh::build(bool background, bool saveIntermediates) { if(mBuilding) @@ -610,6 +668,7 @@ bool NavMesh::build(bool background, bool saveIntermediates) } mBuilding = true; + m_geo = NULL; ctx->startTimer(RC_TIMER_TOTAL); @@ -622,14 +681,27 @@ bool NavMesh::build(bool background, bool saveIntermediates) return false; } - updateConfig(); + // Needed for the recast config and generation params. + Box3F rc_box = DTStoRC(getWorldBox()); + S32 gw = 0, gh = 0; + rcCalcGridSize(rc_box.minExtents, rc_box.maxExtents, mCellSize, &gw, &gh); + const S32 ts = (S32)(mTileSize / mCellSize); + const S32 tw = (gw + ts - 1) / ts; + const S32 th = (gh + ts - 1) / ts; + Con::printf("NavMesh::Build - Tiles %d x %d", tw, th); + + U32 tileBits = mMin(getBinLog2(getNextPow2(tw * th)), 14); + if (tileBits > 14) tileBits = 14; + U32 maxTiles = 1 << tileBits; + U32 polyBits = 22 - tileBits; + mMaxPolysPerTile = 1 << polyBits; // Build navmesh parameters from console members. dtNavMeshParams params; - rcVcopy(params.orig, cfg.bmin); - params.tileWidth = cfg.tileSize * mCellSize; - params.tileHeight = cfg.tileSize * mCellSize; - params.maxTiles = mCeil(getWorldBox().len_x() / params.tileWidth) * mCeil(getWorldBox().len_y() / params.tileHeight); + rcVcopy(params.orig, rc_box.minExtents); + params.tileWidth = mTileSize; + params.tileHeight = mTileSize; + params.maxTiles = maxTiles; params.maxPolys = mMaxPolysPerTile; // Initialise our navmesh. @@ -688,31 +760,25 @@ void NavMesh::inspectPostApply() cancelBuild(); } -void NavMesh::updateConfig() +void NavMesh::createNewFile() { - // Build rcConfig object from our console members. - dMemset(&cfg, 0, sizeof(cfg)); - cfg.cs = mCellSize; - cfg.ch = mCellHeight; - Box3F box = DTStoRC(getWorldBox()); - rcVcopy(cfg.bmin, box.minExtents); - rcVcopy(cfg.bmax, box.maxExtents); - rcCalcGridSize(cfg.bmin, cfg.bmax, cfg.cs, &cfg.width, &cfg.height); + // We need to construct a default file name + String levelAssetId(Con::getVariable("$Client::LevelAsset")); - cfg.walkableHeight = mCeil(mWalkableHeight / mCellHeight); - cfg.walkableClimb = mCeil(mWalkableClimb / mCellHeight); - cfg.walkableRadius = mCeil(mWalkableRadius / mCellSize); - cfg.walkableSlopeAngle = mWalkableSlope; - cfg.borderSize = cfg.walkableRadius + 3; + LevelAsset* levelAsset; + if (!Sim::findObject(levelAssetId.c_str(), levelAsset)) + { + Con::errorf("NavMesh::createNewFile() - Unable to find current level's LevelAsset. Unable to construct NavMesh filePath"); + return; + } - cfg.detailSampleDist = mDetailSampleDist; - cfg.detailSampleMaxError = mDetailSampleMaxError; - cfg.maxEdgeLen = mMaxEdgeLen; - cfg.maxSimplificationError = mMaxSimplificationError; - cfg.maxVertsPerPoly = mMaxVertsPerPoly; - cfg.minRegionArea = mMinRegionArea; - cfg.mergeRegionArea = mMergeRegionArea; - cfg.tileSize = mTileSize / cfg.cs; + Torque::Path basePath(levelAsset->getNavmeshPath()); + + if (basePath.isEmpty()) + basePath = (Torque::Path)(levelAsset->getLevelPath()); + + String fileName = Torque::FS::MakeUniquePath(basePath.getPath(), basePath.getFileName(), "nav"); + mFileName = StringTable->insert(fileName.c_str()); } S32 NavMesh::getTile(const Point3F& pos) @@ -741,20 +807,21 @@ void NavMesh::updateTiles(bool dirty) return; mTiles.clear(); - mTileData.clear(); mDirtyTiles.clear(); const Box3F &box = DTStoRC(getWorldBox()); if(box.isEmpty()) return; - updateConfig(); - // Calculate tile dimensions. - const U32 ts = cfg.tileSize; - const U32 tw = (cfg.width + ts-1) / ts; - const U32 th = (cfg.height + ts-1) / ts; - const F32 tcs = cfg.tileSize * cfg.cs; + const F32* bmin = box.minExtents; + const F32* bmax = box.maxExtents; + S32 gw = 0, gh = 0; + rcCalcGridSize(bmin, bmax, mCellSize, &gw, &gh); + const S32 ts = (S32)(mTileSize / mCellSize); + const S32 tw = (gw + ts - 1) / ts; + const S32 th = (gh + ts - 1) / ts; + const F32 tcs = mTileSize; // Iterate over tiles. F32 tileBmin[3], tileBmax[3]; @@ -762,13 +829,13 @@ void NavMesh::updateTiles(bool dirty) { for(U32 x = 0; x < tw; ++x) { - tileBmin[0] = cfg.bmin[0] + x*tcs; - tileBmin[1] = cfg.bmin[1]; - tileBmin[2] = cfg.bmin[2] + y*tcs; + tileBmin[0] = bmin[0] + x*tcs; + tileBmin[1] = bmin[1]; + tileBmin[2] = bmin[2] + y*tcs; - tileBmax[0] = cfg.bmin[0] + (x+1)*tcs; - tileBmax[1] = cfg.bmax[1]; - tileBmax[2] = cfg.bmin[2] + (y+1)*tcs; + tileBmax[0] = bmin[0] + (x+1)*tcs; + tileBmax[1] = bmax[1]; + tileBmax[2] = bmin[2] + (y+1)*tcs; mTiles.push_back( Tile(RCtoDTS(tileBmin, tileBmax), @@ -777,9 +844,6 @@ void NavMesh::updateTiles(bool dirty) if(dirty) mDirtyTiles.push_back_unique(mTiles.size() - 1); - - if(mSaveIntermediates) - mTileData.increment(); } } } @@ -792,22 +856,54 @@ void NavMesh::processTick(const Move *move) void NavMesh::buildNextTile() { PROFILE_SCOPE(NavMesh_buildNextTile); + if(!mDirtyTiles.empty()) { + dtFreeNavMeshQuery(mQuery); + mQuery = NULL; // Pop a single dirty tile and process it. U32 i = mDirtyTiles.front(); mDirtyTiles.pop_front(); - const Tile &tile = mTiles[i]; - // Intermediate data for tile build. - TileData tempdata; - TileData &tdata = mSaveIntermediates ? mTileData[i] : tempdata; - + Tile &tile = mTiles[i]; + // Remove any previous data. nm->removeTile(nm->getTileRefAt(tile.x, tile.y, 0), 0, 0); // Generate navmesh for this tile. U32 dataSize = 0; - unsigned char* data = buildTileData(tile, tdata, dataSize); + unsigned char* data = buildTileData(tile, dataSize); + // cache our result (these only exist if keep intermediates is ticked) + if (m_chf) + { + tile.chf = m_chf; + m_chf = 0; + } + if (m_solid) + { + tile.solid = m_solid; + m_solid = 0; + } + if (m_cset) + { + tile.cset = m_cset; + m_cset = 0; + } + if (m_pmesh) + { + tile.pmesh = m_pmesh; + m_pmesh = 0; + } + if (m_dmesh) + { + tile.dmesh = m_dmesh; + m_dmesh = 0; + } + if (m_triareas) + { + tile.triareas = m_triareas; + m_triareas = nullptr; + } + if(data) { // Add new data (navmesh owns and deletes the data). @@ -834,6 +930,12 @@ void NavMesh::buildNextTile() // Did we just build the last tile? if(mDirtyTiles.empty()) { + mQuery = dtAllocNavMeshQuery(); + if (dtStatusFailed(mQuery->init(nm, 2048))) + { + Con::errorf("NavMesh - Failed to create navmesh query"); + } + ctx->stopTimer(RC_TIMER_TOTAL); if(getEventManager()) { @@ -844,114 +946,193 @@ void NavMesh::buildNextTile() mBuilding = false; } } + + if (mQuery == NULL) + { + mQuery = dtAllocNavMeshQuery(); + if (dtStatusFailed(mQuery->init(nm, 2048))) + { + Con::errorf("NavMesh - Failed to create navmesh query"); + } + } } -static void buildCallback(SceneObject* object,void *key) +unsigned char *NavMesh::buildTileData(const Tile &tile, U32 &dataSize) { - SceneContainer::CallbackInfo* info = reinterpret_cast(key); - if (!object->mPathfindingIgnore) - object->buildPolyList(info->context,info->polyList,info->boundingBox,info->boundingSphere); -} -unsigned char *NavMesh::buildTileData(const Tile &tile, TileData &data, U32 &dataSize) -{ + cleanup(); + // Push out tile boundaries a bit. F32 tileBmin[3], tileBmax[3]; rcVcopy(tileBmin, tile.bmin); rcVcopy(tileBmax, tile.bmax); - tileBmin[0] -= cfg.borderSize * cfg.cs; - tileBmin[2] -= cfg.borderSize * cfg.cs; - tileBmax[0] += cfg.borderSize * cfg.cs; - tileBmax[2] += cfg.borderSize * cfg.cs; + // Setup our rcConfig + dMemset(&m_cfg, 0, sizeof(m_cfg)); + m_cfg.cs = mCellSize; + m_cfg.ch = mCellHeight; + m_cfg.walkableSlopeAngle = mWalkableSlope; + m_cfg.walkableHeight = (S32)mCeil(mWalkableHeight / m_cfg.ch); + m_cfg.walkableClimb = (S32)mFloor(mWalkableClimb / m_cfg.ch); + m_cfg.walkableRadius = (S32)mCeil(mWalkableRadius / m_cfg.cs); + m_cfg.maxEdgeLen = (S32)(mMaxEdgeLen / mCellSize); + m_cfg.maxSimplificationError = mMaxSimplificationError; + m_cfg.minRegionArea = (S32)mSquared((F32)mMinRegionArea); + m_cfg.mergeRegionArea = (S32)mSquared((F32)mMergeRegionArea); + m_cfg.maxVertsPerPoly = (S32)mMaxVertsPerPoly; + m_cfg.tileSize = (S32)(mTileSize / mCellSize); + m_cfg.borderSize = mMax(m_cfg.walkableRadius + 3, mBorderSize); // use the border size if it is bigger. + m_cfg.width = m_cfg.tileSize + m_cfg.borderSize * 2; + m_cfg.height = m_cfg.tileSize + m_cfg.borderSize * 2; + m_cfg.detailSampleDist = mDetailSampleDist < 0.9f ? 0 : mCellSize * mDetailSampleDist; + m_cfg.detailSampleMaxError = mCellHeight * mDetailSampleMaxError; + rcVcopy(m_cfg.bmin, tileBmin); + rcVcopy(m_cfg.bmax, tileBmax); + m_cfg.bmin[0] -= m_cfg.borderSize * m_cfg.cs; + m_cfg.bmin[2] -= m_cfg.borderSize * m_cfg.cs; + m_cfg.bmax[0] += m_cfg.borderSize * m_cfg.cs; + m_cfg.bmax[2] += m_cfg.borderSize * m_cfg.cs; - // Parse objects from level into RC-compatible format. - Box3F box = RCtoDTS(tileBmin, tileBmax); + Box3F worldBox = RCtoDTS(m_cfg.bmin, m_cfg.bmax); SceneContainer::CallbackInfo info; info.context = PLC_Navigation; - info.boundingBox = box; - data.geom.clear(); - info.polyList = &data.geom; + info.boundingBox = worldBox; + m_geo = new RecastPolyList; + info.polyList = m_geo; info.key = this; - getContainer()->findObjects(box, StaticObjectType | DynamicShapeObjectType, buildCallback, &info); + getContainer()->findObjects(worldBox, StaticObjectType | DynamicShapeObjectType, buildCallback, &info); // Parse water objects into the same list, but remember how much geometry was /not/ water. - U32 nonWaterVertCount = data.geom.getVertCount(); - U32 nonWaterTriCount = data.geom.getTriCount(); - if(mWaterMethod != Ignore) + mWaterVertStart = m_geo->getVertCount(); + mWaterTriStart = m_geo->getTriCount(); + if (mWaterMethod != Ignore) { - getContainer()->findObjects(box, WaterObjectType, buildCallback, &info); + getContainer()->findObjects(worldBox, WaterObjectType, buildCallback, &info); } // Check for no geometry. - if (!data.geom.getVertCount()) + if (!m_geo->getVertCount()) { - data.geom.clear(); + m_geo->clear(); return NULL; } - // Figure out voxel dimensions of this tile. - U32 width = 0, height = 0; - width = cfg.tileSize + cfg.borderSize * 2; - height = cfg.tileSize + cfg.borderSize * 2; + const rcChunkyTriMesh* chunkyMesh = m_geo->getChunkyMesh(); - // Create a heightfield to voxelise our input geometry. - data.hf = rcAllocHeightfield(); - if(!data.hf) + // Create a heightfield to voxelize our input geometry. + m_solid = rcAllocHeightfield(); + if(!m_solid) { Con::errorf("Out of memory (rcHeightField) for NavMesh %s", getIdString()); return NULL; } - if(!rcCreateHeightfield(ctx, *data.hf, width, height, tileBmin, tileBmax, cfg.cs, cfg.ch)) + if (!rcCreateHeightfield(ctx, *m_solid, m_cfg.width, m_cfg.height, m_cfg.bmin, m_cfg.bmax, m_cfg.cs, m_cfg.ch)) { Con::errorf("Could not generate rcHeightField for NavMesh %s", getIdString()); return NULL; } - unsigned char *areas = new unsigned char[data.geom.getTriCount()]; - - dMemset(areas, 0, data.geom.getTriCount() * sizeof(unsigned char)); - - // Mark walkable triangles with the appropriate area flags, and rasterize. - if(mWaterMethod == Solid) + m_triareas = new unsigned char[chunkyMesh->maxTrisPerChunk]; + if (!m_triareas) { - // Treat water as solid: i.e. mark areas as walkable based on angle. - rcMarkWalkableTriangles(ctx, cfg.walkableSlopeAngle, - data.geom.getVerts(), data.geom.getVertCount(), - data.geom.getTris(), data.geom.getTriCount(), areas); - } - else - { - // Treat water as impassable: leave all area flags 0. - rcMarkWalkableTriangles(ctx, cfg.walkableSlopeAngle, - data.geom.getVerts(), nonWaterVertCount, - data.geom.getTris(), nonWaterTriCount, areas); - } - rcRasterizeTriangles(ctx, - data.geom.getVerts(), data.geom.getVertCount(), - data.geom.getTris(), areas, data.geom.getTriCount(), - *data.hf, cfg.walkableClimb); - - delete[] areas; - - // Filter out areas with low ceilings and other stuff. - rcFilterLowHangingWalkableObstacles(ctx, cfg.walkableClimb, *data.hf); - rcFilterLedgeSpans(ctx, cfg.walkableHeight, cfg.walkableClimb, *data.hf); - rcFilterWalkableLowHeightSpans(ctx, cfg.walkableHeight, *data.hf); - - data.chf = rcAllocCompactHeightfield(); - if(!data.chf) - { - Con::errorf("Out of memory (rcCompactHeightField) for NavMesh %s", getIdString()); + Con::errorf("NavMesh::buildTileData: Out of memory 'm_triareas' (%d).", chunkyMesh->maxTrisPerChunk); return NULL; } - if(!rcBuildCompactHeightfield(ctx, cfg.walkableHeight, cfg.walkableClimb, *data.hf, *data.chf)) + + F32 tbmin[2], tbmax[2]; + tbmin[0] = m_cfg.bmin[0]; + tbmin[1] = m_cfg.bmin[2]; + tbmax[0] = m_cfg.bmax[0]; + tbmax[1] = m_cfg.bmax[2]; + int cid[512]; + const int ncid = rcGetChunksOverlappingRect(chunkyMesh, tbmin, tbmax, cid, 512); + if (!ncid) + return 0; + + for (int i = 0; i < ncid; ++i) { - Con::errorf("Could not generate rcCompactHeightField for NavMesh %s", getIdString()); + const rcChunkyTriMeshNode& node = chunkyMesh->nodes[cid[i]]; + const int* ctris = &chunkyMesh->tris[node.i * 3]; + const int nctris = node.n; + + memset(m_triareas, 0, nctris * sizeof(unsigned char)); + rcMarkWalkableTriangles(ctx, m_cfg.walkableSlopeAngle, + m_geo->getVerts(), m_geo->getVertCount(), ctris, nctris, m_triareas); + + // Post-process triangle areas if we need to capture water. + if (mWaterMethod != Ignore) + { + for (int t = 0; t < nctris; ++t) + { + const int* tri = &ctris[t * 3]; + + bool isWater = false; + for (int j = 0; j < 3; ++j) + { + if (tri[j] >= mWaterVertStart) + { + isWater = true; + break; + } + } + + if (isWater) + { + if (mWaterMethod == Solid) + { + m_triareas[t] = WaterArea; // Custom enum you define, like 64 + } + else if (mWaterMethod == Impassable) + { + m_triareas[t] = NullArea; + } + } + } + } + + if (!rcRasterizeTriangles(ctx, m_geo->getVerts(), m_geo->getVertCount(), ctris, m_triareas, nctris, *m_solid, m_cfg.walkableClimb)) + return NULL; + } + + if (!mSaveIntermediates) + { + delete[] m_triareas; + m_triareas = 0; + } + + // these should be optional. + //if (m_filterLowHangingObstacles) + rcFilterLowHangingWalkableObstacles(ctx, m_cfg.walkableClimb, *m_solid); + //if (m_filterLedgeSpans) + rcFilterLedgeSpans(ctx, m_cfg.walkableHeight, m_cfg.walkableClimb, *m_solid); + //if (m_filterWalkableLowHeightSpans) + rcFilterWalkableLowHeightSpans(ctx, m_cfg.walkableHeight, *m_solid); + + + // Compact the heightfield so that it is faster to handle from now on. + // This will result more cache coherent data as well as the neighbours + // between walkable cells will be calculated. + m_chf = rcAllocCompactHeightfield(); + if (!m_chf) + { + Con::errorf("NavMesh::buildTileData: Out of memory 'chf'."); return NULL; } - if(!rcErodeWalkableArea(ctx, cfg.walkableRadius, *data.chf)) + if (!rcBuildCompactHeightfield(ctx, m_cfg.walkableHeight, m_cfg.walkableClimb, *m_solid, *m_chf)) { - Con::errorf("Could not erode walkable area for NavMesh %s", getIdString()); + Con::errorf("NavMesh::buildTileData: Could not build compact data."); + return NULL; + } + + if (!mSaveIntermediates) + { + rcFreeHeightField(m_solid); + m_solid = NULL; + } + + // Erode the walkable area by agent radius. + if (!rcErodeWalkableArea(ctx, m_cfg.walkableRadius, *m_chf)) + { + Con::errorf("NavMesh::buildTileData: Could not erode."); return NULL; } @@ -962,132 +1143,186 @@ unsigned char *NavMesh::buildTileData(const Tile &tile, TileData &data, U32 &dat //rcMarkConvexPolyArea(m_ctx, vols[i].verts, vols[i].nverts, vols[i].hmin, vols[i].hmax, (unsigned char)vols[i].area, *m_chf); //-------------------------- - if(false) + // Partition the heightfield so that we can use simple algorithm later to triangulate the walkable areas. + // There are 3 martitioning methods, each with some pros and cons: + // These should be implemented. + // 1) Watershed partitioning + // - the classic Recast partitioning + // - creates the nicest tessellation + // - usually slowest + // - partitions the heightfield into nice regions without holes or overlaps + // - the are some corner cases where this method creates produces holes and overlaps + // - holes may appear when a small obstacles is close to large open area (triangulation can handle this) + // - overlaps may occur if you have narrow spiral corridors (i.e stairs), this make triangulation to fail + // * generally the best choice if you precompute the nacmesh, use this if you have large open areas + // 2) Monotone partioning + // - fastest + // - partitions the heightfield into regions without holes and overlaps (guaranteed) + // - creates long thin polygons, which sometimes causes paths with detours + // * use this if you want fast navmesh generation + // 3) Layer partitoining + // - quite fast + // - partitions the heighfield into non-overlapping regions + // - relies on the triangulation code to cope with holes (thus slower than monotone partitioning) + // - produces better triangles than monotone partitioning + // - does not have the corner cases of watershed partitioning + // - can be slow and create a bit ugly tessellation (still better than monotone) + // if you have large open areas with small obstacles (not a problem if you use tiles) + // * good choice to use for tiled navmesh with medium and small sized tiles + + + if (/*m_partitionType == SAMPLE_PARTITION_WATERSHED*/ true) { - if(!rcBuildRegionsMonotone(ctx, *data.chf, cfg.borderSize, cfg.minRegionArea, cfg.mergeRegionArea)) + // Prepare for region partitioning, by calculating distance field along the walkable surface. + if (!rcBuildDistanceField(ctx, *m_chf)) { - Con::errorf("Could not build regions for NavMesh %s", getIdString()); + Con::errorf("NavMesh::buildTileData: Could not build distance field."); + return 0; + } + + // Partition the walkable surface into simple regions without holes. + if (!rcBuildRegions(ctx, *m_chf, m_cfg.borderSize, m_cfg.minRegionArea, m_cfg.mergeRegionArea)) + { + Con::errorf("NavMesh::buildTileData: Could not build watershed regions."); return NULL; } } - else + else if (/*m_partitionType == SAMPLE_PARTITION_MONOTONE*/ false) { - if(!rcBuildDistanceField(ctx, *data.chf)) + // Partition the walkable surface into simple regions without holes. + // Monotone partitioning does not need distancefield. + if (!rcBuildRegionsMonotone(ctx, *m_chf, m_cfg.borderSize, m_cfg.minRegionArea, m_cfg.mergeRegionArea)) { - Con::errorf("Could not build distance field for NavMesh %s", getIdString()); + Con::errorf("NavMesh::buildTileData: Could not build monotone regions."); return NULL; } - if(!rcBuildRegions(ctx, *data.chf, cfg.borderSize, cfg.minRegionArea, cfg.mergeRegionArea)) + } + else // SAMPLE_PARTITION_LAYERS + { + // Partition the walkable surface into simple regions without holes. + if (!rcBuildLayerRegions(ctx, *m_chf, m_cfg.borderSize, m_cfg.minRegionArea)) { - Con::errorf("Could not build regions for NavMesh %s", getIdString()); + Con::errorf("NavMesh::buildTileData: Could not build layer regions."); return NULL; } } - data.cs = rcAllocContourSet(); - if(!data.cs) + m_cset = rcAllocContourSet(); + if (!m_cset) { - Con::errorf("Out of memory (rcContourSet) for NavMesh %s", getIdString()); - return NULL; - } - if(!rcBuildContours(ctx, *data.chf, cfg.maxSimplificationError, cfg.maxEdgeLen, *data.cs)) - { - Con::errorf("Could not construct rcContourSet for NavMesh %s", getIdString()); - return NULL; - } - if(data.cs->nconts <= 0) - { - Con::errorf("No contours in rcContourSet for NavMesh %s", getIdString()); + Con::errorf("NavMesh::buildTileData: Out of memory 'cset'"); return NULL; } - data.pm = rcAllocPolyMesh(); - if(!data.pm) + if (!rcBuildContours(ctx, *m_chf, m_cfg.maxSimplificationError, m_cfg.maxEdgeLen, *m_cset)) { - Con::errorf("Out of memory (rcPolyMesh) for NavMesh %s", getIdString()); - return NULL; - } - if(!rcBuildPolyMesh(ctx, *data.cs, cfg.maxVertsPerPoly, *data.pm)) - { - Con::errorf("Could not construct rcPolyMesh for NavMesh %s", getIdString()); + Con::errorf("NavMesh::buildTileData: Could not create contours"); return NULL; } - data.pmd = rcAllocPolyMeshDetail(); - if(!data.pmd) + if (m_cset->nconts == 0) + return NULL; + + // Build polygon navmesh from the contours. + m_pmesh = rcAllocPolyMesh(); + if (!m_pmesh) { - Con::errorf("Out of memory (rcPolyMeshDetail) for NavMesh %s", getIdString()); + Con::errorf("NavMesh::buildTileData: Out of memory 'pmesh'."); return NULL; } - if(!rcBuildPolyMeshDetail(ctx, *data.pm, *data.chf, cfg.detailSampleDist, cfg.detailSampleMaxError, *data.pmd)) + if (!rcBuildPolyMesh(ctx, *m_cset, m_cfg.maxVertsPerPoly, *m_pmesh)) { - Con::errorf("Could not construct rcPolyMeshDetail for NavMesh %s", getIdString()); + Con::errorf("NavMesh::buildTileData: Could not triangulate contours."); return NULL; } - if(data.pm->nverts >= 0xffff) + // Build detail mesh. + m_dmesh = rcAllocPolyMeshDetail(); + if (!m_dmesh) { - Con::errorf("Too many vertices in rcPolyMesh for NavMesh %s", getIdString()); + Con::errorf("NavMesh::buildTileData: Out of memory 'dmesh'."); return NULL; } - for(U32 i = 0; i < data.pm->npolys; i++) - { - if(data.pm->areas[i] == RC_WALKABLE_AREA) - data.pm->areas[i] = GroundArea; - if(data.pm->areas[i] == GroundArea) - data.pm->flags[i] |= WalkFlag; - if(data.pm->areas[i] == WaterArea) - data.pm->flags[i] |= SwimFlag; + if (!rcBuildPolyMeshDetail(ctx, *m_pmesh, *m_chf, m_cfg.detailSampleDist, m_cfg.detailSampleMaxError, *m_dmesh)) + { + Con::errorf("NavMesh::buildTileData: Could build polymesh detail."); + return NULL; + } + + if (!mSaveIntermediates) + { + rcFreeCompactHeightfield(m_chf); + m_chf = 0; + rcFreeContourSet(m_cset); + m_cset = 0; } unsigned char* navData = 0; int navDataSize = 0; - - dtNavMeshCreateParams params; - dMemset(¶ms, 0, sizeof(params)); - - params.verts = data.pm->verts; - params.vertCount = data.pm->nverts; - params.polys = data.pm->polys; - params.polyAreas = data.pm->areas; - params.polyFlags = data.pm->flags; - params.polyCount = data.pm->npolys; - params.nvp = data.pm->nvp; - - params.detailMeshes = data.pmd->meshes; - params.detailVerts = data.pmd->verts; - params.detailVertsCount = data.pmd->nverts; - params.detailTris = data.pmd->tris; - params.detailTriCount = data.pmd->ntris; - - params.offMeshConVerts = mLinkVerts.address(); - params.offMeshConRad = mLinkRads.address(); - params.offMeshConDir = mLinkDirs.address(); - params.offMeshConAreas = mLinkAreas.address(); - params.offMeshConFlags = mLinkFlags.address(); - params.offMeshConUserID = mLinkIDs.address(); - params.offMeshConCount = mLinkIDs.size(); - - params.walkableHeight = mWalkableHeight; - params.walkableRadius = mWalkableRadius; - params.walkableClimb = mWalkableClimb; - params.tileX = tile.x; - params.tileY = tile.y; - params.tileLayer = 0; - rcVcopy(params.bmin, data.pm->bmin); - rcVcopy(params.bmax, data.pm->bmax); - params.cs = cfg.cs; - params.ch = cfg.ch; - params.buildBvTree = true; - - if(!dtCreateNavMeshData(¶ms, &navData, &navDataSize)) + if (m_cfg.maxVertsPerPoly <= DT_VERTS_PER_POLYGON) { - Con::errorf("Could not create dtNavMeshData for tile (%d, %d) of NavMesh %s", - tile.x, tile.y, getIdString()); - return NULL; - } + if (m_pmesh->nverts >= 0xffff) + { + // The vertex indices are ushorts, and cannot point to more than 0xffff vertices. + Con::errorf("NavMesh::buildTileData: Too many vertices per tile %d (max: %d).", m_pmesh->nverts, 0xffff); + return NULL; + } + for (U32 i = 0; i < m_pmesh->npolys; i++) + { + if (m_pmesh->areas[i] == RC_WALKABLE_AREA) + m_pmesh->areas[i] = GroundArea; + + if (m_pmesh->areas[i] == GroundArea) + m_pmesh->flags[i] |= WalkFlag; + if (m_pmesh->areas[i] == WaterArea) + m_pmesh->flags[i] |= SwimFlag; + } + + dtNavMeshCreateParams params; + dMemset(¶ms, 0, sizeof(params)); + + params.verts = m_pmesh->verts; + params.vertCount = m_pmesh->nverts; + params.polys = m_pmesh->polys; + params.polyAreas = m_pmesh->areas; + params.polyFlags = m_pmesh->flags; + params.polyCount = m_pmesh->npolys; + params.nvp = m_pmesh->nvp; + + params.detailMeshes = m_dmesh->meshes; + params.detailVerts = m_dmesh->verts; + params.detailVertsCount = m_dmesh->nverts; + params.detailTris = m_dmesh->tris; + params.detailTriCount = m_dmesh->ntris; + + params.offMeshConVerts = mLinkVerts.address(); + params.offMeshConRad = mLinkRads.address(); + params.offMeshConDir = mLinkDirs.address(); + params.offMeshConAreas = mLinkAreas.address(); + params.offMeshConFlags = mLinkFlags.address(); + params.offMeshConUserID = mLinkIDs.address(); + params.offMeshConCount = mLinkIDs.size(); + + params.walkableHeight = mWalkableHeight; + params.walkableRadius = mWalkableRadius; + params.walkableClimb = mWalkableClimb; + params.tileX = tile.x; + params.tileY = tile.y; + params.tileLayer = 0; + rcVcopy(params.bmin, m_pmesh->bmin); + rcVcopy(params.bmax, m_pmesh->bmax); + params.cs = m_cfg.cs; + params.ch = m_cfg.ch; + params.buildBvTree = true; + + if (!dtCreateNavMeshData(¶ms, &navData, &navDataSize)) + { + Con::errorf("NavMesh::buildTileData: Could not build Detour navmesh."); + return NULL; + } + } dataSize = navDataSize; return navData; @@ -1129,6 +1364,7 @@ void NavMesh::buildTile(const U32 &tile) { mDirtyTiles.push_back_unique(tile); ctx->startTimer(RC_TIMER_TOTAL); + m_geo = NULL; } } @@ -1329,25 +1565,129 @@ bool NavMesh::testEdgeCover(const Point3F &pos, const VectorF &dir, CoverPointDa void NavMesh::renderToDrawer() { - mDbgDraw.clear(); + mDbgDraw.clearCache(); // Recast debug draw - NetObject *no = getServerObject(); - if(no) + NetObject* no = getServerObject(); + if (no) { - NavMesh *n = static_cast(no); - - if(n->nm) + NavMesh* n = static_cast(no); + mDbgDraw.depthMask(true, true); + if (n->nm && + (m_drawMode == DRAWMODE_NAVMESH || + m_drawMode == DRAWMODE_NAVMESH_TRANS || + m_drawMode == DRAWMODE_NAVMESH_BVTREE || + m_drawMode == DRAWMODE_NAVMESH_NODES || + m_drawMode == DRAWMODE_NAVMESH_PORTALS || + m_drawMode == DRAWMODE_NAVMESH_INVIS)) { - mDbgDraw.beginGroup(0); - duDebugDrawNavMesh (&mDbgDraw, *n->nm, 0); - mDbgDraw.beginGroup(1); - duDebugDrawNavMeshPortals(&mDbgDraw, *n->nm); - mDbgDraw.beginGroup(2); - duDebugDrawNavMeshBVTree (&mDbgDraw, *n->nm); + if (m_drawMode != DRAWMODE_NAVMESH_INVIS) + { + if (m_drawMode == DRAWMODE_NAVMESH_TRANS) + mDbgDraw.blend(true); + duDebugDrawNavMeshWithClosedList(&mDbgDraw, *n->nm, *n->mQuery, 0); + mDbgDraw.blend(false); + } + if(m_drawMode == DRAWMODE_NAVMESH_BVTREE) + duDebugDrawNavMeshBVTree(&mDbgDraw, *n->nm); + if(m_drawMode == DRAWMODE_NAVMESH_PORTALS) + duDebugDrawNavMeshPortals(&mDbgDraw, *n->nm); + } + + mDbgDraw.depthMask(true, false); + + for (Tile& tile : n->mTiles) + { + if (tile.chf && m_drawMode == DRAWMODE_COMPACT) + { + duDebugDrawCompactHeightfieldSolid(&mDbgDraw, *tile.chf); + } + + if (tile.chf && m_drawMode == DRAWMODE_COMPACT_DISTANCE) + { + duDebugDrawCompactHeightfieldDistance(&mDbgDraw, *tile.chf); + } + + if (tile.chf && m_drawMode == DRAWMODE_COMPACT_REGIONS) + { + duDebugDrawCompactHeightfieldRegions(&mDbgDraw, *tile.chf); + } + + if (tile.solid && m_drawMode == DRAWMODE_VOXELS) + { + duDebugDrawHeightfieldSolid(&mDbgDraw, *tile.solid); + } + + if (tile.solid && m_drawMode == DRAWMODE_VOXELS_WALKABLE) + { + duDebugDrawHeightfieldWalkable(&mDbgDraw, *tile.solid); + } + + if (tile.cset && m_drawMode == DRAWMODE_RAW_CONTOURS) + { + mDbgDraw.depthMask(false); + duDebugDrawRawContours(&mDbgDraw, *tile.cset); + mDbgDraw.depthMask(true); + } + + if (tile.cset && m_drawMode == DRAWMODE_BOTH_CONTOURS) + { + mDbgDraw.depthMask(false); + duDebugDrawRawContours(&mDbgDraw, *tile.cset); + duDebugDrawContours(&mDbgDraw, *tile.cset); + mDbgDraw.depthMask(true); + } + + if (tile.cset && m_drawMode == DRAWMODE_CONTOURS) + { + mDbgDraw.depthMask(false); + duDebugDrawContours(&mDbgDraw, *tile.cset); + mDbgDraw.depthMask(true); + } + + if (tile.chf && tile.cset && m_drawMode == DRAWMODE_REGION_CONNECTIONS) + { + + duDebugDrawCompactHeightfieldRegions(&mDbgDraw, *tile.chf); + mDbgDraw.depthMask(false); + duDebugDrawRegionConnections(&mDbgDraw, *tile.cset); + mDbgDraw.depthMask(true); + } + + if (tile.pmesh && m_drawMode == DRAWMODE_POLYMESH) + { + mDbgDraw.depthMask(false); + duDebugDrawPolyMesh(&mDbgDraw, *tile.pmesh); + mDbgDraw.depthMask(true); + } + + if (tile.dmesh && m_drawMode == DRAWMODE_POLYMESH_DETAIL) + { + mDbgDraw.depthMask(false); + duDebugDrawPolyMeshDetail(&mDbgDraw, *tile.dmesh); + mDbgDraw.depthMask(true); + } + } } } +void NavMesh::cleanup() +{ + delete[] m_triareas; + m_triareas = 0; + rcFreeHeightField(m_solid); + m_solid = 0; + rcFreeCompactHeightfield(m_chf); + m_chf = 0; + rcFreeContourSet(m_cset); + m_cset = 0; + rcFreePolyMesh(m_pmesh); + m_pmesh = 0; + rcFreePolyMeshDetail(m_dmesh); + m_dmesh = 0; + SAFE_DELETE(m_geo); +} + void NavMesh::prepRenderImage(SceneRenderState *state) { ObjectRenderInst *ri = state->getRenderPass()->allocInst(); @@ -1374,37 +1714,10 @@ void NavMesh::render(ObjectRenderInst *ri, SceneRenderState *state, BaseMatInsta { NavMesh *n = static_cast(no); - if(n->isSelected()) + if ((!gEditingMission && n->mAlwaysRender) || gEditingMission) { - GFXDrawUtil *drawer = GFX->getDrawUtil(); - - GFXStateBlockDesc desc; - desc.setZReadWrite(true, false); - desc.setBlend(true); - desc.setCullMode(GFXCullNone); - - drawer->drawCube(desc, getWorldBox(), n->mBuilding - ? ColorI(255, 0, 0, 80) - : ColorI(136, 228, 255, 45)); - desc.setFillModeWireframe(); - drawer->drawCube(desc, getWorldBox(), ColorI::BLACK); + mDbgDraw.render(state); } - - if(n->mBuilding) - { - int alpha = 80; - if(!n->isSelected() || !Con::getBoolVariable("$Nav::EditorOpen")) - alpha = 20; - mDbgDraw.overrideColor(duRGBA(255, 0, 0, alpha)); - } - else - { - mDbgDraw.cancelOverride(); - } - - if((!gEditingMission && n->mAlwaysRender) || (gEditingMission && Con::getBoolVariable("$Nav::Editor::renderMesh", 1))) mDbgDraw.renderGroup(0); - if(Con::getBoolVariable("$Nav::Editor::renderPortals")) mDbgDraw.renderGroup(1); - if(Con::getBoolVariable("$Nav::Editor::renderBVTree")) mDbgDraw.renderGroup(2); } } @@ -1412,8 +1725,8 @@ void NavMesh::renderLinks(duDebugDraw &dd) { if(mBuilding) return; - dd.depthMask(true); dd.begin(DU_DRAW_LINES); + dd.depthMask(false); for(U32 i = 0; i < mLinkIDs.size(); i++) { U32 col = 0; @@ -1431,7 +1744,7 @@ void NavMesh::renderLinks(duDebugDraw &dd) s[0], s[1], s[2], e[0], e[1], e[2], 0.3f, - 0.0f, mLinkFlags[i] == DropFlag ? 0.0f : 0.4f, + (mLinkDirs[i]&1) ? 0.6f : 0.0f, mLinkFlags[i] == DropFlag ? 0.0f : 0.6f, col); if(!mDeleteLinks[i]) duAppendCircle(&dd, e[0], e[1], e[2], mLinkRads[i], col); @@ -1439,22 +1752,36 @@ void NavMesh::renderLinks(duDebugDraw &dd) dd.end(); } +void NavMesh::renderSearch(duDebugDraw& dd) +{ + if (mQuery == NULL) + return; + + if (m_drawMode == DRAWMODE_NAVMESH_NODES) + duDebugDrawNavMeshNodes(&dd, *mQuery); +} + void NavMesh::renderTileData(duDebugDrawTorque &dd, U32 tile) { - if(tile >= mTileData.size()) + if (tile > mTiles.size()) return; + if(nm) { - dd.beginGroup(0); - if(mTileData[tile].chf) duDebugDrawCompactHeightfieldSolid(&dd, *mTileData[tile].chf); + //duDebugDrawNavMesh(&dd, *nm, 0); + if(mTiles[tile].chf) + duDebugDrawCompactHeightfieldSolid(&dd, *mTiles[tile].chf); + + duDebugDrawNavMeshPortals(&dd, *nm); + + if (!m_geo) + return; - dd.beginGroup(1); int col = duRGBA(255, 0, 255, 255); - RecastPolyList &in = mTileData[tile].geom; dd.begin(DU_DRAW_LINES); - const F32 *verts = in.getVerts(); - const S32 *tris = in.getTris(); - for(U32 t = 0; t < in.getTriCount(); t++) + const F32 *verts = m_geo->getVerts(); + const S32 *tris = m_geo->getTris(); + for(U32 t = 0; t < m_geo->getTriCount(); t++) { dd.vertex(&verts[tris[t*3]*3], col); dd.vertex(&verts[tris[t*3+1]*3], col); @@ -1491,6 +1818,7 @@ U32 NavMesh::packUpdate(NetConnection *conn, U32 mask, BitStream *stream) mathWrite(*stream, getTransform()); mathWrite(*stream, getScale()); stream->writeFlag(mAlwaysRender); + stream->write((U32)m_drawMode); return retMask; } @@ -1502,8 +1830,10 @@ void NavMesh::unpackUpdate(NetConnection *conn, BitStream *stream) mathRead(*stream, &mObjToWorld); mathRead(*stream, &mObjScale); mAlwaysRender = stream->readFlag(); - setTransform(mObjToWorld); + U32 draw; + stream->read(&draw); + m_drawMode = (DrawMode)draw; renderToDrawer(); } @@ -1630,8 +1960,13 @@ DefineEngineMethod(NavMesh, load, bool, (),, bool NavMesh::save() { - if(!dStrlen(mFileName) || !nm) + if (!nm) return false; + + if (!dStrlen(mFileName) || !nm) + { + createNewFile(); + } FileStream stream; if(!stream.open(mFileName, Torque::FS::File::Write)) diff --git a/Engine/source/navigation/navMesh.h b/Engine/source/navigation/navMesh.h index 5c78f81f9..599ee8878 100644 --- a/Engine/source/navigation/navMesh.h +++ b/Engine/source/navigation/navMesh.h @@ -102,6 +102,7 @@ public: U32 mMergeRegionArea; F32 mTileSize; U32 mMaxPolysPerTile; + duDebugDrawTorque mDbgDraw; /// @} /// @name Water @@ -112,6 +113,29 @@ public: Impassable }; + enum DrawMode + { + DRAWMODE_NAVMESH, + DRAWMODE_NAVMESH_TRANS, + DRAWMODE_NAVMESH_BVTREE, + DRAWMODE_NAVMESH_NODES, + DRAWMODE_NAVMESH_PORTALS, + DRAWMODE_NAVMESH_INVIS, + DRAWMODE_MESH, + DRAWMODE_VOXELS, + DRAWMODE_VOXELS_WALKABLE, + DRAWMODE_COMPACT, + DRAWMODE_COMPACT_DISTANCE, + DRAWMODE_COMPACT_REGIONS, + DRAWMODE_REGION_CONNECTIONS, + DRAWMODE_RAW_CONTOURS, + DRAWMODE_BOTH_CONTOURS, + DRAWMODE_CONTOURS, + DRAWMODE_POLYMESH, + DRAWMODE_POLYMESH_DETAIL, + MAX_DRAWMODE + }; + WaterMethod mWaterMethod; /// @} @@ -127,7 +151,7 @@ public: /// @{ /// Add an off-mesh link. - S32 addLink(const Point3F &from, const Point3F &to, U32 flags = 0); + S32 addLink(const Point3F &from, const Point3F &to, bool biDir, F32 rad, U32 flags = 0); /// Get the ID of the off-mesh link near the point. S32 getLink(const Point3F &pos); @@ -144,15 +168,27 @@ public: /// Get the flags used by a link. LinkData getLinkFlags(U32 idx); + bool getLinkDir(U32 idx); + + F32 getLinkRadius(U32 idx); + + void setLinkDir(U32 idx, bool biDir); + + void setLinkRadius(U32 idx, F32 rad); + /// Set flags used by a link. void setLinkFlags(U32 idx, const LinkData &d); + void setDrawMode(DrawMode mode) { m_drawMode = mode; setMaskBits(LoadFlag); } + /// Set the selected state of a link. void selectLink(U32 idx, bool select, bool hover = true); /// Delete the selected link. void deleteLink(U32 idx); + dtNavMeshQuery* getNavMeshQuery() { return mQuery; } + /// @} /// Should small characters use this mesh? @@ -230,6 +266,7 @@ public: void prepRenderImage(SceneRenderState *state) override; void render(ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat); void renderLinks(duDebugDraw &dd); + void renderSearch(duDebugDraw& dd); void renderTileData(duDebugDrawTorque &dd, U32 tile); bool mAlwaysRender; @@ -249,15 +286,13 @@ public: void inspectPostApply() override; + void createNewFile(); + protected: dtNavMesh const* getNavMesh() { return nm; } private: - /// Generates a navigation mesh for the collection of objects in this - /// mesh. Returns true if successful. Stores the created mesh in tnm. - bool generateMesh(); - /// Builds the next tile in the dirty list. void buildNextTile(); @@ -275,56 +310,45 @@ private: /// Recast min and max points. F32 bmin[3], bmax[3]; /// Default constructor. - Tile() : box(Box3F::Invalid), x(0), y(0) + Tile() : box(Box3F::Invalid), x(0), y(0), chf(0), solid(0), cset(0), pmesh(0), dmesh(0), triareas(nullptr) { bmin[0] = bmin[1] = bmin[2] = bmax[0] = bmax[1] = bmax[2] = 0.0f; } /// Value constructor. Tile(const Box3F &b, U32 _x, U32 _y, const F32 *min, const F32 *max) - : box(b), x(_x), y(_y) + : box(b), x(_x), y(_y), chf(0), solid(0), cset(0), pmesh(0), dmesh(0), triareas(nullptr) { rcVcopy(bmin, min); rcVcopy(bmax, max); } - }; - /// Intermediate data for tile creation. - struct TileData { - RecastPolyList geom; - rcHeightfield *hf; - rcCompactHeightfield *chf; - rcContourSet *cs; - rcPolyMesh *pm; - rcPolyMeshDetail *pmd; - TileData() + ~Tile() { - hf = NULL; - chf = NULL; - cs = NULL; - pm = NULL; - pmd = NULL; - } - void freeAll() - { - geom.clear(); - rcFreeHeightField(hf); - rcFreeCompactHeightfield(chf); - rcFreeContourSet(cs); - rcFreePolyMesh(pm); - rcFreePolyMeshDetail(pmd); - } - ~TileData() - { - freeAll(); + if (chf) + delete chf; + if (cset) + delete cset; + if (solid) + delete solid; + if (pmesh) + delete pmesh; + if (dmesh) + delete dmesh; + if (triareas) + delete[] triareas; } + + unsigned char* triareas; + rcCompactHeightfield* chf; + rcHeightfield* solid; + rcContourSet* cset; + rcPolyMesh* pmesh; + rcPolyMeshDetail* dmesh; }; /// List of tiles. Vector mTiles; - /// List of tile intermediate data. - Vector mTileData; - /// List of indices to the tile array which are dirty. Vector mDirtyTiles; @@ -332,7 +356,7 @@ private: void updateTiles(bool dirty = false); /// Generates navmesh data for a single tile. - unsigned char *buildTileData(const Tile &tile, TileData &data, U32 &dataSize); + unsigned char *buildTileData(const Tile &tile, U32 &dataSize); /// @} @@ -363,19 +387,9 @@ private: /// @} - /// @name Intermediate data - /// @{ - - /// Config struct. - rcConfig cfg; - - /// Updates our config from console members. - void updateConfig(); - dtNavMesh *nm; rcContext *ctx; - - /// @} + dtNavMeshQuery* mQuery; /// @name Cover /// @{ @@ -408,8 +422,6 @@ private: /// @name Rendering /// @{ - duDebugDrawTorque mDbgDraw; - void renderToDrawer(); /// @} @@ -419,6 +431,21 @@ private: /// Use this object to manage update events. static SimObjectPtr smEventManager; + +protected: + RecastPolyList* m_geo; + unsigned char* m_triareas; + rcHeightfield* m_solid; + rcCompactHeightfield* m_chf; + rcContourSet* m_cset; + rcPolyMesh* m_pmesh; + rcPolyMeshDetail* m_dmesh; + rcConfig m_cfg; + DrawMode m_drawMode; + U32 mWaterVertStart; + U32 mWaterTriStart; + + void cleanup(); }; typedef NavMesh::WaterMethod NavMeshWaterMethod; diff --git a/Engine/source/navigation/navMeshTool.cpp b/Engine/source/navigation/navMeshTool.cpp new file mode 100644 index 000000000..758bc3626 --- /dev/null +++ b/Engine/source/navigation/navMeshTool.cpp @@ -0,0 +1,40 @@ + +#include "platform/platform.h" +#include "navigation/navMeshTool.h" +#ifdef TORQUE_TOOLS +#include "util/undo.h" +#include "math/mMath.h" +#include "math/mathUtils.h" + +IMPLEMENT_CONOBJECT(NavMeshTool); + +ConsoleDocClass(NavMeshTool, + "@brief Base class for NavMesh Editor specific tools\n\n" + "Editor use only.\n\n" + "@internal" +); + +void NavMeshTool::_submitUndo(UndoAction* action) +{ + AssertFatal(action, "NavMeshTool::_submitUndo() - No undo action!"); + + // Grab the mission editor undo manager. + UndoManager* undoMan = NULL; + if (!Sim::findObject("EUndoManager", undoMan)) + { + Con::errorf("NavMeshTool::_submitUndo() - EUndoManager not found!"); + return; + } + + undoMan->addAction(action); +} + +NavMeshTool::NavMeshTool() + : mNavMesh(NULL) +{ +} + +NavMeshTool::~NavMeshTool() +{ +} +#endif diff --git a/Engine/source/navigation/navMeshTool.h b/Engine/source/navigation/navMeshTool.h new file mode 100644 index 000000000..ef6e9ad3c --- /dev/null +++ b/Engine/source/navigation/navMeshTool.h @@ -0,0 +1,58 @@ +#pragma once +#ifndef _NAVMESH_TOOL_H_ +#define _NAVMESH_TOOL_H_ +#ifdef TORQUE_TOOLS +#ifndef _SIMBASE_H_ +#include "console/simBase.h" +#endif +#ifndef _GUITYPES_H_ +#include "gui/core/guiTypes.h" +#endif +#ifndef _NAVMESH_H_ +#include "navigation/navMesh.h" +#endif + +#ifndef _GUINAVEDITORCTRL_H_ +#include "navigation/guiNavEditorCtrl.h" +#endif + +class UndoAction; + +class NavMeshTool : public SimObject +{ + typedef SimObject Parent; +protected: + SimObjectPtr mNavMesh; + SimObjectPtr mCurEditor; + + void _submitUndo(UndoAction* action); + +public: + + NavMeshTool(); + virtual ~NavMeshTool(); + + DECLARE_CONOBJECT(NavMeshTool); + + virtual void setActiveNavMesh(NavMesh* nav_mesh) { mNavMesh = nav_mesh; } + virtual void setActiveEditor(GuiNavEditorCtrl* nav_editor) { mCurEditor = nav_editor; } + + virtual void onActivated(const Gui3DMouseEvent& lastEvent) {} + virtual void onDeactivated() {} + + virtual void on3DMouseDown(const Gui3DMouseEvent& evt) {} + virtual void on3DMouseUp(const Gui3DMouseEvent& evt) {} + virtual void on3DMouseMove(const Gui3DMouseEvent& evt) {} + virtual void on3DMouseDragged(const Gui3DMouseEvent& evt) {} + virtual void on3DMouseEnter(const Gui3DMouseEvent& evt) {} + virtual void on3DMouseLeave(const Gui3DMouseEvent& evt) {} + virtual bool onMouseWheel(const GuiEvent& evt) { return false; } + virtual void onRender3D() {} + virtual void onRender2D() {} + virtual void updateGizmo() {} + virtual bool updateGuiInfo() { return false; } + virtual void onUndoAction() {} + +}; +#endif +#endif // !_NAVMESH_TOOL_H_ diff --git a/Engine/source/navigation/navMeshTools/coverTool.cpp b/Engine/source/navigation/navMeshTools/coverTool.cpp new file mode 100644 index 000000000..fda36ba8d --- /dev/null +++ b/Engine/source/navigation/navMeshTools/coverTool.cpp @@ -0,0 +1,40 @@ +#include "coverTool.h" + +IMPLEMENT_CONOBJECT(CoverTool); + +CoverTool::CoverTool() +{ +} + +void CoverTool::onActivated(const Gui3DMouseEvent& evt) +{ + Con::executef(this, "onActivated"); +} + +void CoverTool::onDeactivated() +{ + Con::executef(this, "onDeactivated"); +} + +void CoverTool::on3DMouseDown(const Gui3DMouseEvent& evt) +{ + if (mNavMesh.isNull()) + return; +} + +void CoverTool::on3DMouseMove(const Gui3DMouseEvent& evt) +{ + if (mNavMesh.isNull()) + return; +} + +void CoverTool::onRender3D() +{ + if (mNavMesh.isNull()) + return; +} + +bool CoverTool::updateGuiInfo() +{ + return false; +} diff --git a/Engine/source/navigation/navMeshTools/coverTool.h b/Engine/source/navigation/navMeshTools/coverTool.h new file mode 100644 index 000000000..73978a26c --- /dev/null +++ b/Engine/source/navigation/navMeshTools/coverTool.h @@ -0,0 +1,33 @@ +#ifndef _COVERTOOL_H_ +#define _COVERTOOL_H_ + +#ifndef _NAVMESH_TOOL_H_ +#include "navigation/navMeshTool.h" +#endif + +#ifndef _NAVPATH_H_ +#include "navigation/navPath.h" +#endif + +class CoverTool : public NavMeshTool +{ + typedef NavMeshTool Parent; +public: + DECLARE_CONOBJECT(CoverTool); + + CoverTool(); + + virtual ~CoverTool() {} + + void onActivated(const Gui3DMouseEvent& evt) override; + void onDeactivated() override; + + void on3DMouseDown(const Gui3DMouseEvent& evt) override; + void on3DMouseMove(const Gui3DMouseEvent& evt) override; + void onRender3D() override; + + bool updateGuiInfo() override; +}; + +#endif // !_COVERTOOL_H_ + diff --git a/Engine/source/navigation/navMeshTools/navMeshSelectTool.cpp b/Engine/source/navigation/navMeshTools/navMeshSelectTool.cpp new file mode 100644 index 000000000..c80d9fab5 --- /dev/null +++ b/Engine/source/navigation/navMeshTools/navMeshSelectTool.cpp @@ -0,0 +1,121 @@ +#include "navMeshSelectTool.h" +#include "console/consoleTypes.h" +#include "gfx/gfxDrawUtil.h" + +IMPLEMENT_CONOBJECT(NavMeshSelectTool); + +static void renderBoxOutline(const Box3F& box, const ColorI& col) +{ + if (box != Box3F::Invalid) + { + GFXStateBlockDesc desc; + desc.setCullMode(GFXCullNone); + desc.setFillModeSolid(); + desc.setZReadWrite(true, false); + desc.setBlend(true); + GFX->getDrawUtil()->drawCube(desc, box, ColorI(col, 20)); + desc.setFillModeWireframe(); + desc.setBlend(false); + GFX->getDrawUtil()->drawCube(desc, box, ColorI(col, 255)); + } +} + +NavMeshSelectTool::NavMeshSelectTool() +{ + mCurMesh = NULL; +} + +void NavMeshSelectTool::onActivated(const Gui3DMouseEvent& evt) +{ + Con::executef(this, "onActivated"); +} + +void NavMeshSelectTool::onDeactivated() +{ + Con::executef(this, "onDeactivated"); +} + +void NavMeshSelectTool::on3DMouseDown(const Gui3DMouseEvent& evt) +{ + if (mCurEditor.isNull()) + return; + + Point3F startPnt = evt.pos; + Point3F endPnt = evt.pos + evt.vec * 1000.0f; + + RayInfo ri; + if (gServerContainer.collideBox(startPnt, endPnt, MarkerObjectType, &ri)) + { + if (!ri.object) + return; + + NavMesh* selNavMesh = dynamic_cast(ri.object); + if (selNavMesh) + { + mCurEditor->selectMesh(selNavMesh); + mSelMesh = selNavMesh; + Con::executef(this, "onNavMeshSelected"); + return; + } + } + +} + +void NavMeshSelectTool::on3DMouseMove(const Gui3DMouseEvent& evt) +{ + if (mCurEditor.isNull()) + return; + + Point3F startPnt = evt.pos; + Point3F endPnt = evt.pos + evt.vec * 1000.0f; + + RayInfo ri; + if (gServerContainer.collideBox(startPnt, endPnt, MarkerObjectType, &ri)) + { + NavMesh* selNavMesh = dynamic_cast(ri.object); + if (selNavMesh) + { + mCurMesh = selNavMesh; + } + else + { + mCurMesh = NULL; + } + } + else + { + mCurMesh = NULL; + } +} + +void NavMeshSelectTool::onRender3D() +{ + if (!mCurMesh.isNull()) + renderBoxOutline(mCurMesh->getWorldBox(), ColorI::LIGHT); + if (!mSelMesh.isNull()) + renderBoxOutline(mSelMesh->getWorldBox(), ColorI::LIGHT); +} + +bool NavMeshSelectTool::updateGuiInfo() +{ + SimObject* statusbar; + Sim::findObject("EditorGuiStatusBar", statusbar); + + GuiTextCtrl* selectionBar; + Sim::findObject("EWorldEditorStatusBarSelection", selectionBar); + + String text; + text = "LMB To select a NavMesh."; + + if (statusbar) + Con::executef(statusbar, "setInfo", text.c_str()); + + text = ""; + if(mSelMesh) + text = String::ToString("NavMesh Selected: %d", mSelMesh->getId()); + + if (selectionBar) + selectionBar->setText(text); + + return true; +} diff --git a/Engine/source/navigation/navMeshTools/navMeshSelectTool.h b/Engine/source/navigation/navMeshTools/navMeshSelectTool.h new file mode 100644 index 000000000..521a23fe8 --- /dev/null +++ b/Engine/source/navigation/navMeshTools/navMeshSelectTool.h @@ -0,0 +1,32 @@ +#ifndef _NAVMESHSELECTTOOL_H_ +#define _NAVMESHSELECTTOOL_H_ + + +#ifndef _NAVMESH_TOOL_H_ +#include "navigation/navMeshTool.h" +#endif + +class NavMeshSelectTool : public NavMeshTool +{ + typedef NavMeshTool Parent; +protected: + SimObjectPtr mCurMesh; + SimObjectPtr mSelMesh; +public: + DECLARE_CONOBJECT(NavMeshSelectTool); + + NavMeshSelectTool(); + virtual ~NavMeshSelectTool() {} + + void setActiveNavMesh(NavMesh* nav_mesh) { mNavMesh = nav_mesh; mSelMesh = nav_mesh; } + void onActivated(const Gui3DMouseEvent& evt) override; + void onDeactivated() override; + + void on3DMouseDown(const Gui3DMouseEvent& evt) override; + void on3DMouseMove(const Gui3DMouseEvent& evt) override; + void onRender3D() override; + + bool updateGuiInfo() override; +}; + +#endif diff --git a/Engine/source/navigation/navMeshTools/navMeshTestTool.cpp b/Engine/source/navigation/navMeshTools/navMeshTestTool.cpp new file mode 100644 index 000000000..b2612a841 --- /dev/null +++ b/Engine/source/navigation/navMeshTools/navMeshTestTool.cpp @@ -0,0 +1,426 @@ +#include "navMeshTestTool.h" +#include "navigation/guiNavEditorCtrl.h" +#include "console/consoleTypes.h" +#include "gfx/gfxDrawUtil.h" +#include "scene/sceneManager.h" +#include "math/mathUtils.h" +#include "T3D/gameBase/gameConnection.h" +#include "T3D/AI/AIController.h" + +IMPLEMENT_CONOBJECT(NavMeshTestTool); + +//// take control +// GameConnection* conn = GameConnection::getConnectionToServer(); +// if (conn) +// conn->setControlObject(static_cast(obj)); +//} + +static void renderBoxOutline(const Box3F& box, const ColorI& col) +{ + if (box != Box3F::Invalid) + { + GFXStateBlockDesc desc; + desc.setCullMode(GFXCullNone); + desc.setFillModeSolid(); + desc.setZReadWrite(true, false); + desc.setBlend(true); + GFX->getDrawUtil()->drawCube(desc, box, ColorI(col, 20)); + desc.setFillModeWireframe(); + desc.setBlend(false); + GFX->getDrawUtil()->drawCube(desc, box, ColorI(col, 255)); + } +} + +void NavMeshTestTool::spawnPlayer(const Point3F& position) +{ + if (mSpawnClass.isEmpty() || mSpawnDatablock.isEmpty()) + { + Con::warnf("NavMeshTestTool::spawnPlayer - spawn class/datablock not set!"); + return; + } + + SimObject* spawned = Sim::spawnObject(mSpawnClass, mSpawnDatablock); + SceneObject* obj = dynamic_cast(spawned); + + if (!obj) + { + Con::warnf("NavMeshTestTool::spawnPlayer - spawn failed or not a SceneObject"); + if (spawned) + spawned->deleteObject(); + return; + } + + obj->setPosition(position); + SimObject* cleanup = Sim::findObject("MissionCleanup"); + if (cleanup) + { + SimGroup* missionCleanup = dynamic_cast(cleanup); + missionCleanup->addObject(obj); + } + mPlayer = obj; + Con::executef(this, "onPlayerSpawned", obj->getIdString()); + +#ifdef TORQUE_NAVIGATION_ENABLED + AIPlayer* asAIPlayer = dynamic_cast(mPlayer.getPointer()); + if (asAIPlayer) + { + Con::executef(this, "onPlayerSelected"); + } + else + { + ShapeBase* sbo = dynamic_cast(mPlayer.getPointer()); + if (sbo && sbo->getAIController() && sbo->getAIController()->mControllerData) + { + Con::executef(this, "onPlayerSelected"); + } + else + { + Con::executef(this, "onPlayerSelected"); + } + } +#else + Con::executef(this, "onPlayerSelected"); +#endif +} + +void NavMeshTestTool::drawAgent(duDebugDrawTorque& dd, const F32* pos, F32 r, F32 h, F32 c, const U32 col) +{ + dd.depthMask(false); + + // Agent dimensions. + duDebugDrawCylinderWire(&dd, pos[0] - r, pos[1] + 0.02f, pos[2] - r, pos[0] + r, pos[1] + h, pos[2] + r, col, 2.0f); + + duDebugDrawCircle(&dd, pos[0], pos[1] + c, pos[2], r, duRGBA(0, 0, 0, 64), 1.0f); + + U32 colb = duRGBA(0, 0, 0, 196); + dd.begin(DU_DRAW_LINES); + dd.vertex(pos[0], pos[1] - c, pos[2], colb); + dd.vertex(pos[0], pos[1] + c, pos[2], colb); + dd.vertex(pos[0] - r / 2, pos[1] + 0.02f, pos[2], colb); + dd.vertex(pos[0] + r / 2, pos[1] + 0.02f, pos[2], colb); + dd.vertex(pos[0], pos[1] + 0.02f, pos[2] - r / 2, colb); + dd.vertex(pos[0], pos[1] + 0.02f, pos[2] + r / 2, colb); + dd.end(); + + dd.depthMask(true); +} + +NavMeshTestTool::NavMeshTestTool() +{ + mSpawnClass = String::EmptyString; + mSpawnDatablock = String::EmptyString; + mPlayer = NULL; + mCurPlayer = NULL; + + mFollowObject = NULL; + mCurFollowObject = NULL; + + mPathStart = Point3F::Max; + mPathEnd = Point3F::Max; + + mTestPath = NULL; + + mLinkTypes = LinkData(AllFlags); + mFilter.setIncludeFlags(mLinkTypes.getFlags()); + mFilter.setExcludeFlags(0); + mSelectFollow = false; +} + +void NavMeshTestTool::onActivated(const Gui3DMouseEvent& evt) +{ + mSelectFollow = false; + Con::executef(this, "onActivated"); +} + +void NavMeshTestTool::onDeactivated() +{ + if (mTestPath != NULL) { + mTestPath->deleteObject(); + mTestPath = NULL; + } + + if (mPlayer != NULL) + { + mPlayer = NULL; + Con::executef(this, "onPlayerDeselected"); + } + + mSelectFollow = false; + + Con::executef(this, "onDeactivated"); +} + +void NavMeshTestTool::on3DMouseDown(const Gui3DMouseEvent& evt) +{ + if (mNavMesh.isNull()) + return; + + Point3F startPnt = evt.pos; + Point3F endPnt = evt.pos + evt.vec * 1000.0f; + + RayInfo ri; + bool shift = evt.modifier & SI_LSHIFT; + bool ctrl = evt.modifier & SI_LCTRL; + + if (ctrl) + { + if (gServerContainer.castRay(startPnt, endPnt, StaticObjectType, &ri)) + { + spawnPlayer(ri.point); + } + return; + } + + if (shift) + { + mPlayer = NULL; + Con::executef(this, "onPlayerDeselected"); + return; + } + + if (gServerContainer.castRay(startPnt, endPnt, PlayerObjectType | VehicleObjectType, &ri)) + { + if (!ri.object) + return; + if (mSelectFollow) + { + mFollowObject = ri.object; + Con::executef(this, "onFollowSelected"); + mSelectFollow = false; + return; + } + else + { + mPlayer = ri.object; + } + +#ifdef TORQUE_NAVIGATION_ENABLED + AIPlayer* asAIPlayer = dynamic_cast(mPlayer.getPointer()); + if (asAIPlayer) + { + Con::executef(this, "onPlayerSelected"); + } + else + { + ShapeBase* sbo = dynamic_cast(mPlayer.getPointer()); + if (sbo && sbo->getAIController() && sbo->getAIController()->mControllerData) + { + Con::executef(this, "onPlayerSelected"); + } + else + { + Con::executef(this, "onPlayerSelected"); + } + } +#else + Con::executef(this, "onPlayerSelected"); +#endif + } + else if (gServerContainer.castRay(startPnt, endPnt, StaticObjectType, &ri)) + { + if (mPlayer.isNull()) + { + if (mPathStart != Point3F::Max && mPathEnd != Point3F::Max) // start a new path. + { + mPathStart = Point3F::Max; + mPathEnd = Point3F::Max; + } + + if (mPathStart != Point3F::Max) + { + mPathEnd = ri.point; + mTestPath = new NavPath(); + + mTestPath->mMesh = mNavMesh; + mTestPath->mFrom = mPathStart; + mTestPath->mTo = mPathEnd; + mTestPath->mFromSet = mTestPath->mToSet = true; + mTestPath->mAlwaysRender = true; + mTestPath->mLinkTypes = mLinkTypes; + mTestPath->mFilter = mFilter; + mTestPath->mXray = true; + // Paths plan automatically upon being registered. + if (!mTestPath->registerObject()) + { + delete mTestPath; + return; + } + } + else + { + mPathStart = ri.point; + if (mTestPath != NULL) { + mTestPath->deleteObject(); + mTestPath = NULL; + } + } + } + else + { + AIPlayer* asAIPlayer = dynamic_cast(mPlayer.getPointer()); + if (asAIPlayer) //try direct + { + asAIPlayer->setPathDestination(ri.point); + mPathStart = mPlayer->getPosition(); + mPathEnd = ri.point; + } + else + { + ShapeBase* sbo = dynamic_cast(mPlayer.getPointer()); + if (sbo->getAIController()) + { + if (sbo->getAIController()->mControllerData) + { + sbo->getAIController()->getNav()->setPathDestination(ri.point, true); + mPathStart = mPlayer->getPosition(); + mPathEnd = ri.point; + } + } + } + } + } + +} + +void NavMeshTestTool::on3DMouseMove(const Gui3DMouseEvent& evt) +{ + if (mNavMesh.isNull()) + return; + + Point3F startPnt = evt.pos; + Point3F endPnt = evt.pos + evt.vec * 1000.0f; + + RayInfo ri; + + if (gServerContainer.castRay(startPnt, endPnt, PlayerObjectType | VehicleObjectType, &ri)) + { + if (mSelectFollow) + mCurFollowObject = ri.object; + else + mCurPlayer = ri.object; + } + else + { + mCurFollowObject = NULL; + mCurPlayer = NULL; + } +} + +void NavMeshTestTool::onRender3D() +{ + if (mNavMesh.isNull()) + return; + + static const U32 startCol = duRGBA(128, 25, 0, 192); + static const U32 endCol = duRGBA(51, 102, 0, 129); + + const F32 agentRadius = mNavMesh->mWalkableRadius; + const F32 agentHeight = mNavMesh->mWalkableHeight; + const F32 agentClimb = mNavMesh->mWalkableClimb; + + duDebugDrawTorque dd; + dd.depthMask(false); + if (mPathStart != Point3F::Max) + { + drawAgent(dd, DTStoRC(mPathStart), agentRadius, agentHeight, agentClimb, startCol); + } + + if (mPathEnd != Point3F::Max) + { + drawAgent(dd, DTStoRC(mPathEnd), agentRadius, agentHeight, agentClimb, endCol); + } + dd.depthMask(true); + + mNavMesh->renderSearch(dd); + + dd.immediateRender(); + + if (!mCurFollowObject.isNull()) + renderBoxOutline(mCurFollowObject->getWorldBox(), ColorI::LIGHT); + if (!mCurPlayer.isNull()) + renderBoxOutline(mCurPlayer->getWorldBox(), ColorI::BLUE); + if (!mPlayer.isNull()) + renderBoxOutline(mPlayer->getWorldBox(), ColorI::GREEN); + if (!mFollowObject.isNull()) + renderBoxOutline(mFollowObject->getWorldBox(), ColorI::WHITE); + +} + +bool NavMeshTestTool::updateGuiInfo() +{ + SimObject* statusbar; + Sim::findObject("EditorGuiStatusBar", statusbar); + + GuiTextCtrl* selectionBar; + Sim::findObject("EWorldEditorStatusBarSelection", selectionBar); + + String text; + + if (mPlayer) + text = "LMB To Select move Destination. LSHIFT+LMB To Deselect Current Bot."; + if (mCurPlayer != NULL && mCurPlayer != mPlayer) + text = "LMB To select Bot."; + + if (mPlayer == NULL) + { + text = "LMB To place start/end for test path."; + } + + if (mSpawnClass != String::EmptyString && mSpawnDatablock != String::EmptyString) + text += " CTRL+LMB To spawn a new Bot."; + + if (mSelectFollow) + text = "LMB To select Follow Target."; + + if (statusbar) + Con::executef(statusbar, "setInfo", text.c_str()); + + text = ""; + if (mPlayer) + text = String::ToString("Bot Selected: %d", mPlayer->getId()); + + if (selectionBar) + selectionBar->setText(text); + + return true; +} + +S32 NavMeshTestTool::getPlayerId() +{ + return mPlayer.isNull() ? 0 : mPlayer->getId(); +} + +S32 NavMeshTestTool::getFollowObjectId() +{ + return mFollowObject.isNull() ? 0 : mFollowObject->getId(); +} + + +DefineEngineMethod(NavMeshTestTool, getPlayer, S32, (), , + "@brief Return the current player id.") +{ + return object->getPlayerId(); +} + +DefineEngineMethod(NavMeshTestTool, getFollowObject, S32, (), , + "@brief Return the current follow object id.") +{ + return object->getFollowObjectId(); +} + +DefineEngineMethod(NavMeshTestTool, setSpawnClass, void, (String className), , "") +{ + object->setSpawnClass(className); +} + +DefineEngineMethod(NavMeshTestTool, setSpawnDatablock, void, (String dbName), , "") +{ + object->setSpawnDatablock(dbName); +} + +DefineEngineMethod(NavMeshTestTool, followSelectMode, void, (), , + "@brief Set NavMeshTool to select a follow object.") +{ + return object->followSelectMode(); +} + diff --git a/Engine/source/navigation/navMeshTools/navMeshTestTool.h b/Engine/source/navigation/navMeshTools/navMeshTestTool.h new file mode 100644 index 000000000..2ee56e43e --- /dev/null +++ b/Engine/source/navigation/navMeshTools/navMeshTestTool.h @@ -0,0 +1,60 @@ +#ifndef _NAVMESHTESTTOOL_H_ +#define _NAVMESHTESTTOOL_H_ + + +#ifndef _NAVMESH_TOOL_H_ +#include "navigation/navMeshTool.h" +#endif + +#ifndef _NAVPATH_H_ +#include "navigation/navPath.h" +#endif + +class NavMeshTestTool : public NavMeshTool +{ + typedef NavMeshTool Parent; +protected: + String mSpawnClass; + String mSpawnDatablock; + SimObjectPtr mPlayer; + SimObjectPtr mCurPlayer; + SimObjectPtr mFollowObject; + SimObjectPtr mCurFollowObject; + Point3F mPathStart; + Point3F mPathEnd; + NavPath* mTestPath; + LinkData mLinkTypes; + dtQueryFilter mFilter; + bool mSelectFollow; + +public: + DECLARE_CONOBJECT(NavMeshTestTool); + + void spawnPlayer(const Point3F& position); + + void drawAgent(duDebugDrawTorque& dd, const F32* pos, F32 r, F32 h, F32 c, const U32 col); + + NavMeshTestTool(); + + virtual ~NavMeshTestTool() {} + + void onActivated(const Gui3DMouseEvent& evt) override; + void onDeactivated() override; + + void on3DMouseDown(const Gui3DMouseEvent& evt) override; + void on3DMouseMove(const Gui3DMouseEvent& evt) override; + void onRender3D() override; + + bool updateGuiInfo() override; + + S32 getPlayerId(); + S32 getFollowObjectId(); + + void setSpawnClass(String className) { mSpawnClass = className; } + void setSpawnDatablock(String dbName) { mSpawnDatablock = dbName; } + void followSelectMode() { mSelectFollow = true; } + +}; + + +#endif // !_NAVMESHTESTTOOL_H_ diff --git a/Engine/source/navigation/navMeshTools/offMeshConnTool.cpp b/Engine/source/navigation/navMeshTools/offMeshConnTool.cpp new file mode 100644 index 000000000..ef01db43b --- /dev/null +++ b/Engine/source/navigation/navMeshTools/offMeshConnTool.cpp @@ -0,0 +1,200 @@ +#include "offMeshConnTool.h" +#include "navigation/guiNavEditorCtrl.h" +#include "console/consoleTypes.h" +#include "gfx/gfxDrawUtil.h" +#include "scene/sceneManager.h" +#include "math/mathUtils.h" + +IMPLEMENT_CONOBJECT(OffMeshConnectionTool); + +void OffMeshConnectionTool::onActivated(const Gui3DMouseEvent& evt) +{ + Con::executef(this, "onActivated"); +} + +void OffMeshConnectionTool::onDeactivated() +{ + Con::executef(this, "onDeactivated"); +} + +void OffMeshConnectionTool::on3DMouseDown(const Gui3DMouseEvent& evt) +{ + if (mNavMesh.isNull()) + return; + + Point3F startPnt = evt.pos; + Point3F endPnt = evt.pos + evt.vec * 1000.0f; + + RayInfo ri; + bool shift = evt.modifier & SI_LSHIFT; + bool ctrl = evt.modifier & SI_LCTRL; + + if (gServerContainer.castRay(startPnt, endPnt, StaticObjectType, &ri)) + { + U32 link = mNavMesh->getLink(ri.point); + if (link != -1) + { + if (mLink != -1) + mNavMesh->selectLink(mLink, false); + mNavMesh->selectLink(link, true, false); + mLink = link; + + if (ctrl) + { + mNavMesh->selectLink(mLink, false); + mNavMesh->deleteLink(mLink); + mLink = -1; + Con::executef(this, "onLinkDeselected"); + return; + } + else + { + LinkData d = mNavMesh->getLinkFlags(mLink); + bool biDir = mNavMesh->getLinkDir(mLink); + F32 linkRad = mNavMesh->getLinkRadius(mLink); + Con::executef(this, "onLinkSelected", Con::getIntArg(d.getFlags()), Con::getBoolArg(biDir), Con::getFloatArg(linkRad)); + } + } + else + { + if (mLink != -1) + { + mNavMesh->selectLink(mLink, false); + mLink = -1; + Con::executef(this, "onLinkDeselected"); + } + + if (mLinkStart != Point3F::Max) + { + mLink = mNavMesh->addLink(mLinkStart, ri.point, mBiDir, mLinkRadius); + mNavMesh->selectLink(mLink, true, false); + + if (shift) + mLinkStart = ri.point; + else + mLinkStart = Point3F::Max; + + Con::executef(this, "onLinkSelected", Con::getIntArg(mLinkCache.getFlags()), Con::getBoolArg(mBiDir), Con::getFloatArg(mLinkRadius)); + } + else + { + mLinkStart = ri.point; + } + } + } + else + { + if (mLink != -1) + { + mNavMesh->selectLink(mLink, false); + mLink = -1; + Con::executef(this, "onLinkDeselected"); + } + } + +} + +void OffMeshConnectionTool::on3DMouseMove(const Gui3DMouseEvent& evt) +{ + if (mNavMesh.isNull()) + return; + + Point3F startPnt = evt.pos; + Point3F endPnt = evt.pos + evt.vec * 1000.0f; + + RayInfo ri; + if (gServerContainer.castRay(startPnt, endPnt, StaticObjectType, &ri)) + { + U32 link = mNavMesh->getLink(ri.point); + if (link != -1) + { + if (link != mLink) + { + if (mCurLink != -1) + mNavMesh->selectLink(mCurLink, false); + mNavMesh->selectLink(link, true, true); + } + mCurLink = link; + } + else + { + if (mCurLink != mLink) + mNavMesh->selectLink(mCurLink, false); + mCurLink = -1; + } + } + else + { + mNavMesh->selectLink(mCurLink, false); + mCurLink = -1; + } +} + +void OffMeshConnectionTool::onRender3D() +{ + if (mNavMesh.isNull()) + return; + + duDebugDrawTorque dd; + + if (mLinkStart != Point3F::Max) + { + Point3F rcFrom = DTStoRC(mLinkStart); + dd.begin(DU_DRAW_LINES); + dd.depthMask(false); + duAppendCircle(&dd, rcFrom.x, rcFrom.y, rcFrom.z, mLinkRadius, duRGBA(0, 255, 0, 255)); + dd.end(); + } + + //mNavMesh->renderLinks(dd); + + dd.immediateRender(); +} + +bool OffMeshConnectionTool::updateGuiInfo() +{ + SimObject* statusbar; + Sim::findObject("EditorGuiStatusBar", statusbar); + + GuiTextCtrl* selectionBar; + Sim::findObject("EWorldEditorStatusBarSelection", selectionBar); + + String text; + text = "LMB To Select Link. CTRL+LMB To Delete Link"; + + if (mLinkStart != Point3F::Max) + text = "LinkStarted: LMB To place End Point. Hold Left Shift to start a new Link from the end point."; + + if (statusbar) + Con::executef(statusbar, "setInfo", text.c_str()); + + if (mLink != -1) + text = String::ToString("Selected Link: %d", mLink); + else + text = ""; + + if (selectionBar) + selectionBar->setText(text); + + return true; +} + +void OffMeshConnectionTool::setLinkProperties(const LinkData& d, bool biDir, F32 rad) +{ + if (!mNavMesh.isNull() && mLink != -1) + { + mNavMesh->setLinkFlags(mLink, d); + mNavMesh->setLinkDir(mLink, biDir); + mNavMesh->setLinkRadius(mLink, rad); + } + + mLinkCache = d; + mBiDir = biDir; + mLinkRadius = rad; +} + +DefineEngineMethod(OffMeshConnectionTool, setLinkProperties, void, (U32 flags, bool biDir, F32 rad), , + "@Brief Set properties of the selected link.") +{ + object->setLinkProperties(LinkData(flags), biDir, rad); +} diff --git a/Engine/source/navigation/navMeshTools/offMeshConnTool.h b/Engine/source/navigation/navMeshTools/offMeshConnTool.h new file mode 100644 index 000000000..97b2ffa46 --- /dev/null +++ b/Engine/source/navigation/navMeshTools/offMeshConnTool.h @@ -0,0 +1,53 @@ +#ifndef _OFFMESHCONNTOOL_H_ +#define _OFFMESHCONNTOOL_H_ + + +#ifndef _NAVMESH_TOOL_H_ +#include "navigation/navMeshTool.h" +#endif + +class OffMeshConnectionTool : public NavMeshTool +{ + typedef NavMeshTool Parent; + bool mStartPosSet; + bool mBiDir; + S32 mLink; + S32 mCurLink; + Point3F mLinkStart; + LinkData mLinkCache; + F32 mLinkRadius; +public: + + DECLARE_CONOBJECT(OffMeshConnectionTool); + + OffMeshConnectionTool() { + mStartPosSet = false; + mBiDir = false; + mLink = -1; + mCurLink = -1; + mLinkStart = Point3F::Max; + mLinkCache = LinkData(0); + mLinkRadius = 1.0; + } + virtual ~OffMeshConnectionTool() {} + + void setActiveNavMesh(NavMesh* nav_mesh) override { + mNavMesh = nav_mesh; + + if (!mNavMesh.isNull()) + mLinkRadius = mNavMesh->mWalkableRadius; + } + + void onActivated(const Gui3DMouseEvent& evt) override; + void onDeactivated() override; + + void on3DMouseDown(const Gui3DMouseEvent& evt) override; + void on3DMouseMove(const Gui3DMouseEvent& evt) override; + void onRender3D() override; + + bool updateGuiInfo() override; + + void setLinkProperties(const LinkData& d, bool biDir, F32 rad); +}; + +#endif diff --git a/Engine/source/navigation/navMeshTools/tileTool.cpp b/Engine/source/navigation/navMeshTools/tileTool.cpp new file mode 100644 index 000000000..c77923832 --- /dev/null +++ b/Engine/source/navigation/navMeshTools/tileTool.cpp @@ -0,0 +1,115 @@ +#include "tileTool.h" +#include "navigation/guiNavEditorCtrl.h" +#include "console/consoleTypes.h" +#include "gfx/gfxDrawUtil.h" +#include "scene/sceneManager.h" +#include "math/mathUtils.h" + +IMPLEMENT_CONOBJECT(TileTool); + +static void renderBoxOutline(const Box3F& box, const ColorI& col) +{ + if (box != Box3F::Invalid) + { + GFXStateBlockDesc desc; + desc.setCullMode(GFXCullNone); + desc.setFillModeSolid(); + desc.setZReadWrite(true, false); + desc.setBlend(true); + GFX->getDrawUtil()->drawCube(desc, box, ColorI(col, 20)); + desc.setFillModeWireframe(); + desc.setBlend(false); + GFX->getDrawUtil()->drawCube(desc, box, ColorI(col, 255)); + } +} + +void TileTool::onActivated(const Gui3DMouseEvent& lastEvent) +{ + Con::executef(this, "onActivated"); +} + +void TileTool::onDeactivated() +{ + Con::executef(this, "onDeactivated"); +} + +void TileTool::on3DMouseDown(const Gui3DMouseEvent& evt) +{ + if (mNavMesh.isNull()) + return; + + Point3F start = evt.pos; + Point3F end = evt.pos + evt.vec * 1000.0f; + + RayInfo ri; + if (gServerContainer.castRay(start, end, StaticObjectType, &ri)) + { + mSelTile = mNavMesh->getTile(ri.point); + } +} + +void TileTool::on3DMouseMove(const Gui3DMouseEvent& evt) +{ + if (mNavMesh.isNull()) + return; + + Point3F startPnt = evt.pos; + Point3F endPnt = evt.pos + evt.vec * 1000.0f; + + RayInfo ri; + + if (gServerContainer.castRay(startPnt, endPnt, StaticObjectType, &ri)) + mCurTile = mNavMesh->getTile(ri.point); + else + mCurTile = -1; +} + +void TileTool::onRender3D() +{ + if (mNavMesh.isNull()) + return; + + if(mCurTile != -1) + renderBoxOutline(mNavMesh->getTileBox(mCurTile), ColorI::BLUE); + + if(mSelTile != -1) + renderBoxOutline(mNavMesh->getTileBox(mSelTile), ColorI::GREEN); +} + +void TileTool::buildTile() +{ + if (!mNavMesh.isNull() && mSelTile != -1) + mNavMesh->buildTile(mSelTile); +} + +bool TileTool::updateGuiInfo() +{ + SimObject* statusbar; + Sim::findObject("EditorGuiStatusBar", statusbar); + + GuiTextCtrl* selectionBar; + Sim::findObject("EWorldEditorStatusBarSelection", selectionBar); + + String text; + + text = "LMB To select NavMesh Tile"; + + if (statusbar) + Con::executef(statusbar, "setInfo", text.c_str()); + + if (mSelTile != -1) + text = String::ToString("Selected Tile: %d", mSelTile); + else + text = ""; + + if (selectionBar) + selectionBar->setText(text); + + return true; +} + +DefineEngineMethod(TileTool, buildTile, void, (), , + "@brief Build the currently selected tile.") +{ + return object->buildTile(); +} diff --git a/Engine/source/navigation/navMeshTools/tileTool.h b/Engine/source/navigation/navMeshTools/tileTool.h new file mode 100644 index 000000000..a973e7bad --- /dev/null +++ b/Engine/source/navigation/navMeshTools/tileTool.h @@ -0,0 +1,31 @@ +#ifndef _TILETOOL_H_ +#define _TILETOOL_H_ + + +#ifndef _NAVMESH_TOOL_H_ +#include "navigation/navMeshTool.h" +#endif + +class TileTool : public NavMeshTool +{ + typedef NavMeshTool Parent; + S32 mCurTile; + S32 mSelTile; +public: + DECLARE_CONOBJECT(TileTool); + + TileTool() { mCurTile = -1; mSelTile = -1; } + virtual ~TileTool() {} + + void onActivated(const Gui3DMouseEvent& evt) override; + void onDeactivated() override; + + void on3DMouseDown(const Gui3DMouseEvent& evt) override; + void on3DMouseMove(const Gui3DMouseEvent& evt) override; + void onRender3D() override; + void buildTile(); + + bool updateGuiInfo() override; +}; + +#endif diff --git a/Engine/source/navigation/navPath.cpp b/Engine/source/navigation/navPath.cpp index 50d25408b..db9ba20ee 100644 --- a/Engine/source/navigation/navPath.cpp +++ b/Engine/source/navigation/navPath.cpp @@ -69,14 +69,11 @@ NavPath::NavPath() : mXray = false; mRenderSearch = false; - mQuery = NULL; mStatus = DT_FAILURE; } NavPath::~NavPath() { - dtFreeNavMeshQuery(mQuery); - mQuery = NULL; } void NavPath::checkAutoUpdate() @@ -264,9 +261,6 @@ bool NavPath::onAdd() if(isServerObject()) { - mQuery = dtAllocNavMeshQuery(); - if(!mQuery) - return false; checkAutoUpdate(); if(!plan()) setProcessTick(true); @@ -293,7 +287,8 @@ bool NavPath::init() return false; // Initialise our query. - if(dtStatusFailed(mQuery->init(mMesh->getNavMesh(), MaxPathLen))) + mQuery = mMesh->getNavMeshQuery(); + if(!mQuery) return false; mPoints.clear(); @@ -372,9 +367,6 @@ void NavPath::resize() bool NavPath::plan() { PROFILE_SCOPE(NavPath_plan); - // Initialise filter. - mFilter.setIncludeFlags(mLinkTypes.getFlags()); - // Initialise query and visit locations. if(!init()) return false; @@ -640,9 +632,8 @@ void NavPath::renderSimple(ObjectRenderInst *ri, SceneRenderState *state, BaseMa if(np->mQuery && !dtStatusSucceed(np->mStatus)) { duDebugDrawTorque dd; - dd.overrideColor(duRGBA(250, 20, 20, 255)); duDebugDrawNavMeshNodes(&dd, *np->mQuery); - dd.render(); + dd.immediateRender(); } } } diff --git a/Engine/source/navigation/navPath.h b/Engine/source/navigation/navPath.h index fe6246c40..194058b8a 100644 --- a/Engine/source/navigation/navPath.h +++ b/Engine/source/navigation/navPath.h @@ -57,7 +57,7 @@ public: /// What sort of link types are we allowed to move on? LinkData mLinkTypes; - + dtQueryFilter mFilter; /// Plan the path. bool plan(); @@ -152,7 +152,6 @@ private: dtNavMeshQuery *mQuery; dtStatus mStatus; - dtQueryFilter mFilter; S32 mCurIndex; Vector mPoints; Vector mFlags; diff --git a/Engine/source/navigation/recastPolyList.cpp b/Engine/source/navigation/recastPolyList.cpp index 565a47892..0513ccbb5 100644 --- a/Engine/source/navigation/recastPolyList.cpp +++ b/Engine/source/navigation/recastPolyList.cpp @@ -27,12 +27,16 @@ #include "gfx/primBuilder.h" #include "gfx/gfxStateBlock.h" -RecastPolyList::RecastPolyList() +RecastPolyList::RecastPolyList() : mChunkyMesh(0) { nverts = 0; verts = NULL; vertcap = 0; + nnormals = 0; + normals = NULL; + normalcap = 0; + ntris = 0; tris = NULL; tricap = 0; @@ -44,6 +48,28 @@ RecastPolyList::~RecastPolyList() clear(); } +rcChunkyTriMesh* RecastPolyList::getChunkyMesh() +{ + if (!mChunkyMesh) + { + mChunkyMesh = new rcChunkyTriMesh; + if (!mChunkyMesh) + { + Con::errorf("Build tile navigation: out of memory"); + return NULL; + } + + if (!rcCreateChunkyTriMesh(getVerts(), getTris(), getTriCount(), 256, mChunkyMesh)) + { + Con::errorf("Build tile navigation: out of memory"); + return NULL; + } + + } + + return mChunkyMesh; +} + void RecastPolyList::clear() { nverts = 0; @@ -51,6 +77,11 @@ void RecastPolyList::clear() verts = NULL; vertcap = 0; + nnormals = 0; + delete[] normals; + normals = NULL; + normalcap = 0; + ntris = 0; delete[] tris; tris = NULL; @@ -134,6 +165,39 @@ void RecastPolyList::vertex(U32 vi) void RecastPolyList::end() { + // Fetch current triangle indices + const U32 i0 = tris[ntris * 3 + 0]; + const U32 i1 = tris[ntris * 3 + 1]; + const U32 i2 = tris[ntris * 3 + 2]; + + // Rebuild vertices + Point3F v0(verts[i0 * 3 + 0], verts[i0 * 3 + 1], verts[i0 * 3 + 2]); + Point3F v1(verts[i1 * 3 + 0], verts[i1 * 3 + 1], verts[i1 * 3 + 2]); + Point3F v2(verts[i2 * 3 + 0], verts[i2 * 3 + 1], verts[i2 * 3 + 2]); + + // Compute normal + Point3F edge1 = v1 - v0; + Point3F edge2 = v2 - v0; + Point3F normal = mCross(edge1, edge2); + normal.normalizeSafe(); + + // Allocate/resize normal buffer if needed + if (nnormals == normalcap) + { + normalcap = (normalcap == 0) ? 16 : normalcap * 2; + F32* newNormals = new F32[normalcap * 3]; + if (normals) + dMemcpy(newNormals, normals, nnormals * 3 * sizeof(F32)); + delete[] normals; + normals = newNormals; + } + + // Store normal + normals[nnormals * 3 + 0] = normal.x; + normals[nnormals * 3 + 1] = normal.y; + normals[nnormals * 3 + 2] = normal.z; + + nnormals++; ntris++; } @@ -147,6 +211,11 @@ const F32 *RecastPolyList::getVerts() const return verts; } +const F32* RecastPolyList::getNormals() const +{ + return normals; +} + U32 RecastPolyList::getTriCount() const { return ntris; diff --git a/Engine/source/navigation/recastPolyList.h b/Engine/source/navigation/recastPolyList.h index 4425c2317..62d43ff91 100644 --- a/Engine/source/navigation/recastPolyList.h +++ b/Engine/source/navigation/recastPolyList.h @@ -26,6 +26,10 @@ #include "collision/abstractPolyList.h" #include "core/util/tVector.h" +#ifndef CHUNKYTRIMESH_H +#include "ChunkyTriMesh.h" +#endif + /// Represents polygons in the same manner as the .obj file format. Handy for /// padding data to Recast, since it expects this data format. At the moment, /// this class only accepts triangles. @@ -57,6 +61,8 @@ public: U32 getVertCount() const; const F32 *getVerts() const; + const F32* getNormals() const; + U32 getTriCount() const; const S32 *getTris() const; @@ -70,6 +76,9 @@ public: /// Default destructor. ~RecastPolyList(); + rcChunkyTriMesh* getChunkyMesh(); + + protected: /// Number of vertices defined. U32 nverts; @@ -78,6 +87,13 @@ protected: /// Size of vertex array. U32 vertcap; + // Number of normals defined. + U32 nnormals; + // Array of normals (xyz in float array) + F32* normals; + // Size of normal array (matches verts) + U32 normalcap; + /// Number of triangles defined. U32 ntris; /// Array of triangle vertex indices. Size ntris*3 @@ -93,6 +109,8 @@ protected: /// Another inherited utility function. const PlaneF& getIndexedPlane(const U32 index) override { return planes[index]; } + rcChunkyTriMesh* mChunkyMesh; + private: }; diff --git a/Engine/source/navigation/torqueRecast.h b/Engine/source/navigation/torqueRecast.h index f6e41affd..914de002c 100644 --- a/Engine/source/navigation/torqueRecast.h +++ b/Engine/source/navigation/torqueRecast.h @@ -52,6 +52,7 @@ inline void rcCol(unsigned int col, U8 &r, U8 &g, U8 &b, U8 &a) } enum PolyAreas { + NullArea = 0, GroundArea, WaterArea, OffMeshArea, @@ -99,6 +100,10 @@ struct LinkData { (climb ? ClimbFlag : 0) | (teleport ? TeleportFlag : 0); } + U16 getExcludeFlags() const + { + return AllFlags & ~getFlags(); + } }; #endif diff --git a/Templates/BaseGame/game/data/Prototyping/scripts/managedData/managedDatablocks.tscript b/Templates/BaseGame/game/data/Prototyping/scripts/managedData/managedDatablocks.tscript index 60dbe9c8a..e73435b9b 100644 --- a/Templates/BaseGame/game/data/Prototyping/scripts/managedData/managedDatablocks.tscript +++ b/Templates/BaseGame/game/data/Prototyping/scripts/managedData/managedDatablocks.tscript @@ -4,3 +4,58 @@ datablock ItemData(PrototypeItemData) ShapeAsset = "Prototyping:TorusPrimitive_shape"; cameraMaxDist = "0.75"; }; + +datablock PlayerData( ProtoPlayer ) { + // Third person shape + ShapeAsset = "Prototyping:Playerbot_shape"; + controlMap = "playerKeyMap"; + AIControllerData = "aiPlayerControl"; +}; + +datablock WheeledVehicleTire(ProtoCarTire) +{ + // Tires act as springs and generate lateral and longitudinal + // forces to move the vehicle. These distortion/spring forces + // are what convert wheel angular velocity into forces that + // act on the rigid body. + shapeAsset = "Prototyping:carwheel_shape"; + + staticFriction = 1; + kineticFriction = 4.2; + + // Spring that generates lateral tire forces + lateralForce = 150000; + lateralDamping = 30000; + lateralRelaxation = 0.1; + + // Spring that generates longitudinal tire forces + longitudinalForce = 600; + longitudinalDamping = 1600; + longitudinalRelaxation = 0.1; +}; + +datablock WheeledVehicleSpring(ProtoCarSpring) +{ + // Wheel suspension properties + length = "0.6"; // Suspension travel + force = 3600; // Spring force + damping = 2800; // Spring damping + antiSwayForce = 300; // Lateral anti-sway force +}; + +datablock WheeledVehicleData(ProtoCar) +{ + category = "Vehicles"; + shapeAsset = "Prototyping:car_shape"; + + collisionMul = 0; + impactMul = 0; + controlMap = "vehicleKeyMap"; + AIControllerData = "aiCarControl"; + cameraMaxDist = "2.81993"; + ShapeFile = "data/Prototyping/shapes/Vehicles/car.dae"; + mass = "1000"; + originalAssetName = "ProtoCar"; + massCenter = "0 0.75 0"; + dragForce = "0.1"; +}; diff --git a/Templates/BaseGame/game/data/Prototyping/scripts/server/protoCar.tscript b/Templates/BaseGame/game/data/Prototyping/scripts/server/protoCar.tscript new file mode 100644 index 000000000..23d59229e --- /dev/null +++ b/Templates/BaseGame/game/data/Prototyping/scripts/server/protoCar.tscript @@ -0,0 +1,47 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +// This file contains script methods unique to the WheeledVehicle class. All +// other necessary methods are contained in "../server/scripts/vehicle.cs" in +// which the "generic" Vehicle class methods that are shared by all vehicles, +// (flying, hover, and wheeled) can be found. + +function ProtoCar::onAdd(%this, %obj) +{ + Parent::onAdd(%this, %obj); + + // Setup the car with some tires & springs + for (%i = %obj.getWheelCount() - 1; %i >= 0; %i--) + { + %obj.setWheelTire(%i, ProtoCarTire); + %obj.setWheelSpring(%i, ProtoCarSpring); + %obj.setWheelPowered(%i, false); + } + + // Steer with the front tires + %obj.setWheelSteering(0, 1); + %obj.setWheelSteering(1, 1); + + // Only power the two rear wheels... assuming there are only 4 wheels. + %obj.setWheelPowered(2, true); + %obj.setWheelPowered(3, true); +} \ No newline at end of file diff --git a/Templates/BaseGame/game/tools/navEditor/NavEditorGui.gui b/Templates/BaseGame/game/tools/navEditor/NavEditorGui.gui index 18646fe1a..064a56ad0 100644 --- a/Templates/BaseGame/game/tools/navEditor/NavEditorGui.gui +++ b/Templates/BaseGame/game/tools/navEditor/NavEditorGui.gui @@ -279,24 +279,22 @@ $guiContent = new GuiNavEditorCtrl(NavEditorGui, EditorGuiGroup) { HorizSizing = "width"; VertSizing = "bottom"; Position = "4 24"; - Extent = "202 85"; + Extent = "202 136"; Docking = "Top"; Margin = "3 3 3 3"; internalName = "ActionsBox"; - new GuiTextCtrl(){ - Profile = "EditorTextProfile"; - HorizSizing = "right"; - VertSizing = "bottom"; - Position = "5 0"; - Extent = "86 18"; - text = "Actions"; + new GuiPopUpMenuCtrl(DrawModeSelector) { + position = "7 0"; + extent = "190 20"; + profile = "ToolsGuiPopUpMenuProfile"; + tooltipProfile = "GuiToolTipProfile"; }; new GuiStackControl() { internalName = "SelectActions"; position = "7 21"; - extent = "190 64"; + extent = "190 136"; new GuiButtonCtrl() { Profile = "ToolsGuiButtonProfile"; @@ -372,7 +370,7 @@ $guiContent = new GuiNavEditorCtrl(NavEditorGui, EditorGuiGroup) { { internalName = "LinkActions"; position = "7 21"; - extent = "190 64"; + extent = "190 136"; new GuiButtonCtrl() { Profile = "ToolsGuiButtonProfile"; @@ -388,7 +386,7 @@ $guiContent = new GuiNavEditorCtrl(NavEditorGui, EditorGuiGroup) { { internalName = "CoverActions"; position = "7 21"; - extent = "190 64"; + extent = "190 136"; new GuiButtonCtrl() { Profile = "ToolsGuiButtonProfile"; @@ -413,7 +411,7 @@ $guiContent = new GuiNavEditorCtrl(NavEditorGui, EditorGuiGroup) { { internalName = "TileActions"; position = "7 21"; - extent = "190 64"; + extent = "190 136"; new GuiButtonCtrl() { Profile = "ToolsGuiButtonProfile"; @@ -422,23 +420,54 @@ $guiContent = new GuiNavEditorCtrl(NavEditorGui, EditorGuiGroup) { VertSizing = "bottom"; Extent = "182 18"; text = "Rebuild tile"; - command = "NavEditorGui.buildTile();"; + command = "NavMeshTools->TileTool.buildTile();"; }; }; new GuiStackControl() { internalName = "TestActions"; position = "7 21"; - extent = "190 64"; - - new GuiButtonCtrl() { - Profile = "ToolsGuiButtonProfile"; - buttonType = "PushButton"; - HorizSizing = "right"; - VertSizing = "bottom"; - Extent = "180 18"; - text = "Spawn"; - command = "NavEditorGui.spawnPlayer();"; + extent = "190 136"; + new GuiControl() { + profile = "GuiDefaultProfile"; + Extent = "190 20"; + new GuiTextCtrl(){ + Profile = "EditorTextProfile"; + extent = "89 20"; + text = "Spawn Class"; + }; + new GuiTextCtrl(){ + Profile = "EditorTextProfile"; + Position = "100 0"; + extent = "89 20"; + text = "Spawn Datablock"; + }; + }; + new GuiControl() { + profile = "GuiDefaultProfile"; + Extent = "190 20"; + new GuiPopUpMenuCtrl(SpawnClassSelector) { + extent = "89 20"; + profile = "ToolsGuiPopUpMenuProfile"; + tooltipProfile = "GuiToolTipProfile"; + }; + + new GuiPopUpMenuCtrl(SpawnDatablockSelector) { + position = "100 0"; + extent = "89 20"; + profile = "ToolsGuiPopUpMenuProfile"; + tooltipProfile = "GuiToolTipProfile"; + }; + }; + + new GuiControl() { + profile = "GuiDefaultProfile"; + Extent = "190 20"; + new GuiTextCtrl(){ + Profile = "EditorTextProfile"; + extent = "89 20"; + text = "AI Actions"; + }; }; new GuiControl() { profile = "GuiDefaultProfile"; @@ -451,7 +480,9 @@ $guiContent = new GuiNavEditorCtrl(NavEditorGui, EditorGuiGroup) { VertSizing = "bottom"; Extent = "90 18"; text = "Delete"; - command = "NavEditorGui.getPlayer().delete();"; + tooltipProfile = "GuiToolTipProfile"; + tooltip = "Delete Selected Bot."; + command = "NavMeshTools->TestTool.getPlayer().delete();NavInspector.inspect();"; }; new GuiButtonCtrl() { position = "100 0"; @@ -460,8 +491,8 @@ $guiContent = new GuiNavEditorCtrl(NavEditorGui, EditorGuiGroup) { HorizSizing = "right"; VertSizing = "bottom"; Extent = "90 18"; - text = "Find cover"; - command = "NavEditorGui.findCover();"; + text = "Stop"; + command = "NavMeshTools->TestTool.stop();"; }; }; new GuiControl() { @@ -474,8 +505,8 @@ $guiContent = new GuiNavEditorCtrl(NavEditorGui, EditorGuiGroup) { HorizSizing = "right"; VertSizing = "bottom"; Extent = "90 18"; - text = "Follow"; - command = "NavEditorGui.followObject();"; + text = "Select Follow"; + command = "NavMeshTools->TestTool.followObject();"; }; new GuiButtonCtrl() { position = "100 0"; @@ -484,8 +515,39 @@ $guiContent = new GuiNavEditorCtrl(NavEditorGui, EditorGuiGroup) { HorizSizing = "right"; VertSizing = "bottom"; Extent = "90 18"; - text = "Stop"; - command = "NavEditorGui.stop();"; + text = "Find cover"; + command = "NavMeshTools->TestTool.findCover();"; + }; + }; + new GuiControl() { + profile = "GuiDefaultProfile"; + Extent = "190 18"; + + new GuiButtonCtrl() { + Profile = "ToolsGuiButtonProfile"; + buttonType = "PushButton"; + HorizSizing = "right"; + VertSizing = "bottom"; + Extent = "90 18"; + text = "Toggle Follow"; + command = "NavMeshTools->TestTool.toggleFollow();"; + }; + + new GuiTextEditSliderCtrl(CoverRadius) { + position = "100 0"; + extent = "90 18"; + format = "%3.2f"; + range = "0 1e+03"; + increment = "0.1"; + focusOnMouseWheel = "0"; + historySize = "0"; + password = "0"; + tabComplete = "0"; + sinkAllKeyEvents = "0"; + hovertime = "1000"; + profile = "ToolsGuiTextEditProfile"; + tooltipProfile = "GuiToolTipProfile"; + toolTip = "The radius to search for cover"; }; }; }; @@ -567,7 +629,45 @@ $guiContent = new GuiNavEditorCtrl(NavEditorGui, EditorGuiGroup) { position = "7 21"; extent = "186 64"; padding = "2 2 2 2"; - + new GuiTextEditSliderCtrl() { + internalName = "LinkRadius"; + class = "NavMeshLinkRadius"; + extent = "50 15"; + format = "%3.2f"; + range = "0 1e+03"; + increment = "0.1"; + focusOnMouseWheel = "0"; + historySize = "0"; + password = "0"; + tabComplete = "0"; + sinkAllKeyEvents = "0"; + hovertime = "1000"; + profile = "ToolsGuiTextEditProfile"; + tooltipProfile = "GuiToolTipProfile"; + toolTip = "The radius for this link."; + AltCommand = "NavMeshTools->LinkTool.updateRadius();"; + }; + new GuiCheckBoxCtrl() { + internalName = "LinkBiDirection"; + class = "NavMeshLinkBiDirection"; + text = " Link Bi-Directional"; + buttonType = "ToggleButton"; + useMouseEvents = "0"; + extent = "159 15"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "ToolsGuiCheckBoxProfile"; + visible = "1"; + active = "0"; + tooltipProfile = "GuiToolTipProfile"; + toolTip = "This link is bidirectional."; + hovertime = "1000"; + isContainer = "0"; + canSave = "1"; + canSaveDynamicFields = "0"; + }; + new GuiCheckBoxCtrl() { internalName = "LinkWalkFlag"; class = "NavMeshLinkFlagButton"; @@ -732,194 +832,6 @@ $guiContent = new GuiNavEditorCtrl(NavEditorGui, EditorGuiGroup) { variable = "$Nav::Editor::renderVoxels"; }; }; - new GuiStackControl() { - internalName = "TestProperties"; - position = "7 21"; - extent = "186 64"; - padding = "2 2 2 2"; - - new GuiTextCtrl() { - text = "Cover"; - profile = "ToolsGuiTextProfile"; - extent = "180 20"; - minExtent = "8 2"; - visible = "1"; - }; - new GuiTextEditCtrl() { - internalName = "CoverRadius"; - text = "10"; - profile = "ToolsGuiTextEditProfile"; - extent = "40 20"; - minExtent = "8 2"; - visible = "1"; - tooltipProfile = "GuiToolTipProfile"; - toolTip = "Radius for cover-finding."; - }; - new GuiTextEditCtrl() { - internalName = "CoverPosition"; - text = "LocalClientConnection.getControlObject().getPosition();"; - profile = "ToolsGuiTextEditProfile"; - extent = "140 20"; - minExtent = "8 2"; - visible = "1"; - tooltipProfile = "GuiToolTipProfile"; - toolTip = "Position to find cover from."; - }; - new GuiTextCtrl() { - text = "Follow"; - profile = "ToolsuiTextProfile"; - extent = "180 20"; - minExtent = "8 2"; - visible = "1"; - }; - new GuiTextEditCtrl() { - internalName = "FollowRadius"; - text = "1"; - profile = "ToolsGuiTextEditProfile"; - extent = "40 20"; - minExtent = "8 2"; - visible = "1"; - tooltipProfile = "GuiToolTipProfile"; - toolTip = "Radius for following."; - }; - new GuiTextEditCtrl() { - internalName = "FollowObject"; - text = "LocalClientConnection.player"; - profile = "ToolsGuiTextEditProfile"; - extent = "140 20"; - minExtent = "8 2"; - visible = "1"; - tooltipProfile = "GuiToolTipProfile"; - toolTip = "Object to follow."; - }; - new GuiTextCtrl() { - text = "Movement"; - profile = "ToolsGuiTextProfile"; - extent = "180 20"; - minExtent = "8 2"; - visible = "1"; - }; - new GuiCheckBoxCtrl() { - internalName = "LinkWalkFlag"; - class = "NavMeshTestFlagButton"; - text = " Walk"; - buttonType = "ToggleButton"; - useMouseEvents = "0"; - extent = "159 15"; - minExtent = "8 2"; - horizSizing = "right"; - vertSizing = "bottom"; - profile = "ToolsGuiCheckBoxProfile"; - visible = "1"; - active = "0"; - tooltipProfile = "GuiToolTipProfile"; - toolTip = "Can this character walk on ground?"; - hovertime = "1000"; - isContainer = "0"; - canSave = "1"; - canSaveDynamicFields = "0"; - }; - new GuiCheckBoxCtrl() { - internalName = "LinkJumpFlag"; - class = "NavMeshTestFlagButton"; - text = " Jump"; - buttonType = "ToggleButton"; - useMouseEvents = "0"; - extent = "159 15"; - minExtent = "8 2"; - horizSizing = "right"; - vertSizing = "bottom"; - profile = "ToolsGuiCheckBoxProfile"; - visible = "1"; - active = "0"; - tooltipProfile = "GuiToolTipProfile"; - toolTip = "Can this character jump?"; - hovertime = "1000"; - isContainer = "0"; - canSave = "1"; - canSaveDynamicFields = "0"; - }; - new GuiCheckBoxCtrl() { - internalName = "LinkDropFlag"; - class = "NavMeshTestFlagButton"; - text = " Drop"; - buttonType = "ToggleButton"; - useMouseEvents = "0"; - extent = "159 15"; - minExtent = "8 2"; - horizSizing = "right"; - vertSizing = "bottom"; - profile = "ToolsGuiCheckBoxProfile"; - visible = "1"; - active = "0"; - tooltipProfile = "GuiToolTipProfile"; - toolTip = "Can this character drop over edges?"; - hovertime = "1000"; - isContainer = "0"; - canSave = "1"; - canSaveDynamicFields = "0"; - }; - new GuiCheckBoxCtrl() { - internalName = "LinkLedgeFlag"; - class = "NavMeshTestFlagButton"; - text = " Ledge"; - buttonType = "ToggleButton"; - useMouseEvents = "0"; - extent = "159 15"; - minExtent = "8 2"; - horizSizing = "right"; - vertSizing = "bottom"; - profile = "ToolsGuiCheckBoxProfile"; - visible = "1"; - active = "0"; - tooltipProfile = "GuiToolTipProfile"; - toolTip = "Can this character jump from ledges?"; - hovertime = "1000"; - isContainer = "0"; - canSave = "1"; - canSaveDynamicFields = "0"; - }; - new GuiCheckBoxCtrl() { - internalName = "LinkClimbFlag"; - class = "NavMeshTestFlagButton"; - text = " Climb"; - buttonType = "ToggleButton"; - useMouseEvents = "0"; - extent = "159 15"; - minExtent = "8 2"; - horizSizing = "right"; - vertSizing = "bottom"; - profile = "ToolsGuiCheckBoxProfile"; - visible = "1"; - active = "0"; - tooltipProfile = "GuiToolTipProfile"; - toolTip = "Can this character climb?"; - hovertime = "1000"; - isContainer = "0"; - canSave = "1"; - canSaveDynamicFields = "0"; - }; - new GuiCheckBoxCtrl() { - internalName = "LinkTeleportFlag"; - class = "NavMeshTestFlagButton"; - text = " Teleport"; - buttonType = "ToggleButton"; - useMouseEvents = "0"; - extent = "159 15"; - minExtent = "8 2"; - horizSizing = "right"; - vertSizing = "bottom"; - profile = "ToolsGuiCheckBoxProfile"; - visible = "1"; - active = "0"; - tooltipProfile = "GuiToolTipProfile"; - toolTip = "Can this character teleport?"; - hovertime = "1000"; - isContainer = "0"; - canSave = "1"; - canSaveDynamicFields = "0"; - }; - }; }; new GuiMLTextCtrl(NavFieldInfoControl) { canSaveDynamicFields = "0"; diff --git a/Templates/BaseGame/game/tools/navEditor/NavEditorToolbar.gui b/Templates/BaseGame/game/tools/navEditor/NavEditorToolbar.gui index 38c86f678..8068d3526 100644 --- a/Templates/BaseGame/game/tools/navEditor/NavEditorToolbar.gui +++ b/Templates/BaseGame/game/tools/navEditor/NavEditorToolbar.gui @@ -77,68 +77,5 @@ $guiContent = new GuiControl(NavEditorToolbar,EditorGuiGroup) { canSave = "1"; canSaveDynamicFields = "0"; }; - new GuiCheckBoxCtrl() { - text = "Mesh"; - groupNum = "-1"; - buttonType = "ToggleButton"; - useMouseEvents = "0"; - position = "167 1"; - extent = "50 30"; - minExtent = "8 2"; - horizSizing = "right"; - vertSizing = "bottom"; - profile = "ToolsGuiCheckBoxProfile"; - visible = "1"; - active = "1"; - variable = "$Nav::Editor::renderMesh"; - tooltipProfile = "GuiToolTipProfile"; - hovertime = "1000"; - isContainer = "0"; - internalName = "MeshButton"; - canSave = "1"; - canSaveDynamicFields = "0"; - }; - new GuiCheckBoxCtrl() { - text = "Portals"; - groupNum = "-1"; - buttonType = "ToggleButton"; - useMouseEvents = "0"; - position = "224 1"; - extent = "54 30"; - minExtent = "8 2"; - horizSizing = "right"; - vertSizing = "bottom"; - profile = "ToolsGuiCheckBoxProfile"; - visible = "1"; - active = "1"; - variable = "$Nav::Editor::renderPortals"; - tooltipProfile = "GuiToolTipProfile"; - hovertime = "1000"; - isContainer = "0"; - internalName = "PortalButton"; - canSave = "1"; - canSaveDynamicFields = "0"; - }; - new GuiCheckBoxCtrl() { - text = "BV tree"; - groupNum = "-1"; - buttonType = "ToggleButton"; - useMouseEvents = "0"; - position = "286 1"; - extent = "140 30"; - minExtent = "8 2"; - horizSizing = "right"; - vertSizing = "bottom"; - profile = "ToolsGuiCheckBoxProfile"; - visible = "1"; - active = "1"; - variable = "$Nav::Editor::renderBVTree"; - tooltipProfile = "GuiToolTipProfile"; - hovertime = "1000"; - isContainer = "0"; - internalName = "BVTreeButton"; - canSave = "1"; - canSaveDynamicFields = "0"; - }; }; //--- OBJECT WRITE END --- diff --git a/Templates/BaseGame/game/tools/navEditor/main.tscript b/Templates/BaseGame/game/tools/navEditor/main.tscript index 3c2ef7aab..65c107376 100644 --- a/Templates/BaseGame/game/tools/navEditor/main.tscript +++ b/Templates/BaseGame/game/tools/navEditor/main.tscript @@ -57,6 +57,44 @@ function initializeNavEditor() editorGui = NavEditorGui; }; + new SimSet(NavMeshTools) + { + new NavMeshSelectTool() + { + internalName = "SelectTool"; + toolTip = "Edit NavMesh"; + buttonImage = "ToolsModule:visibility_toggle_n_image"; + }; + + new OffMeshConnectionTool() + { + internalName = "LinkTool"; + toolTip = "Link tool"; + buttonImage = "ToolsModule:nav_link_n_image"; + }; + + new CoverTool() + { + internalName = "NavCoverTool"; + toolTip = "Cover Tool"; + buttonImage = "ToolsModule:nav_cover_n_image"; + }; + + new TileTool() + { + internalName = "TileTool"; + toolTip = "Tile selection tool"; + buttonImage = "ToolsModule:select_bounds_n_image"; + }; + + new NavMeshTestTool() + { + internalName = "TestTool"; + toolTip = "PathFinding Test tool"; + buttonImage = "ToolsModule:3rd_person_camera_n_image"; + }; + }; + // Bind shortcuts for the nav editor. %map = new ActionMap(); %map.bindCmd(keyboard, "1", "ENavEditorSelectModeBtn.performClick();", ""); @@ -118,12 +156,12 @@ function EditorGui::SetNavPalletBar() EWToolsPaletteWindow.setActionMap(WorldEditorInspectorPlugin.map); //Adds a button to the pallete stack - //Name Icon Click Command Tooltip text Keybind - EWToolsPaletteWindow.addButton("ViewNavMesh", "ToolsModule:visibility_toggle_n_image", "NavEditorGui.prepSelectionMode();", "", "View NavMesh", "1"); - EWToolsPaletteWindow.addButton("LinkMode", "ToolsModule:nav_link_n_image", "NavEditorGui.setMode(\"LinkMode\");", "", "Create off-mesh links", "2"); - EWToolsPaletteWindow.addButton("CoverMode", "ToolsModule:nav_cover_n_image", "NavEditorGui.setMode(\"CoverMode\");", "", "Edit cover", "3"); - EWToolsPaletteWindow.addButton("TileMode", "ToolsModule:select_bounds_n_image", "NavEditorGui.setMode(\"TileMode\");", "", "View tiles", "4"); - EWToolsPaletteWindow.addButton("TestMode", "ToolsModule:3rd_person_camera_n_image", "NavEditorGui.setMode(\"TestMode\");", "", "Test pathfinding", "5"); + //Name Icon Click Command Tooltip text Keybind + EWToolsPaletteWindow.addButton("EditMode", "ToolsModule:visibility_toggle_n_image", "NavEditorGui.setActiveTool(NavMeshTools->SelectTool);", "", "Edit NavMesh", "1"); + EWToolsPaletteWindow.addButton("LinkMode", "ToolsModule:nav_link_n_image", "NavEditorGui.setActiveTool(NavMeshTools->LinkTool);", "", "Create off-mesh links", "2"); + EWToolsPaletteWindow.addButton("CoverMode","ToolsModule:nav_cover_n_image", "NavEditorGui.setActiveTool(NavMeshTools->NavCoverTool);", "", "Create Cover Points.", "3"); + EWToolsPaletteWindow.addButton("TileMode", "ToolsModule:select_bounds_n_image", "NavEditorGui.setActiveTool(NavMeshTools->TileTool);" , "", "View and Edit Tiles", "4"); + EWToolsPaletteWindow.addButton("TestMode", "ToolsModule:3rd_person_camera_n_image", "NavEditorGui.setActiveTool(NavMeshTools->TestTool);", "", "Test pathfinding", "5"); EWToolsPaletteWindow.refresh(); } @@ -135,7 +173,22 @@ function NavEditorPlugin::onActivated(%this) $Nav::EditorOpen = true; // Start off in Select mode. - ToolsPaletteArray->NavEditorSelectMode.performClick(); + // Callback when the nav editor changes mode. Set the appropriate dynamic + // GUI contents in the properties/actions boxes. + NavInspector.setVisible(false); + + %actions = NavEditorOptionsWindow->ActionsBox; + %actions->SelectActions.setVisible(false); + %actions->LinkActions.setVisible(false); + %actions->CoverActions.setVisible(false); + %actions->TileActions.setVisible(false); + %actions->TestActions.setVisible(false); + + %properties = NavEditorOptionsWindow->PropertiesBox; + %properties->LinkProperties.setVisible(false); + %properties->TileProperties.setVisible(false); + + ENavEditorSelectModeBtn.performClick(); EditorGui.bringToFront(NavEditorGui); NavEditorGui.setVisible(true); @@ -177,6 +230,9 @@ function NavEditorPlugin::onActivated(%this) Parent::onActivated(%this); EditorGui.SetNavPalletBar(); + + DrawModeSelector.init(); + DrawModeSelector.selectDefault(); } function NavEditorPlugin::onDeactivated(%this) @@ -242,7 +298,7 @@ function NavEditorPlugin::initSettings(%this) EditorSettings.beginGroup("NavEditor", true); EditorSettings.setDefaultValue("SpawnClass", "AIPlayer"); - EditorSettings.setDefaultValue("SpawnDatablock", "DefaultPlayerData"); + EditorSettings.setDefaultValue("SpawnDatablock", "ProtoPlayer"); EditorSettings.endGroup(); } @@ -252,9 +308,6 @@ function NavEditorPlugin::readSettings(%this) EditorSettings.beginGroup("NavEditor", true); // Currently these are globals because of the way they are accessed in navMesh.cpp. - $Nav::Editor::renderMesh = EditorSettings.value("RenderMesh"); - $Nav::Editor::renderPortals = EditorSettings.value("RenderPortals"); - $Nav::Editor::renderBVTree = EditorSettings.value("RenderBVTree"); NavEditorGui.spawnClass = EditorSettings.value("SpawnClass"); NavEditorGui.spawnDatablock = EditorSettings.value("SpawnDatablock"); NavEditorGui.backgroundBuild = EditorSettings.value("BackgroundBuild"); @@ -274,9 +327,6 @@ function NavEditorPlugin::writeSettings(%this) { EditorSettings.beginGroup("NavEditor", true); - EditorSettings.setValue("RenderMesh", $Nav::Editor::renderMesh); - EditorSettings.setValue("RenderPortals", $Nav::Editor::renderPortals); - EditorSettings.setValue("RenderBVTree", $Nav::Editor::renderBVTree); EditorSettings.setValue("SpawnClass", NavEditorGui.spawnClass); EditorSettings.setValue("SpawnDatablock", NavEditorGui.spawnDatablock); EditorSettings.setValue("BackgroundBuild", NavEditorGui.backgroundBuild); diff --git a/Templates/BaseGame/game/tools/navEditor/navEditor.tscript b/Templates/BaseGame/game/tools/navEditor/navEditor.tscript index 1acbab8ef..3a501cddd 100644 --- a/Templates/BaseGame/game/tools/navEditor/navEditor.tscript +++ b/Templates/BaseGame/game/tools/navEditor/navEditor.tscript @@ -296,79 +296,314 @@ function NavEditorGui::showSidePanel() %parent.panelHidden = false; } -//------------------------------------------------------------------------------ +//------------------------------------------------------ +// NAVMESHSELECTTOOL +//------------------------------------------------------ -function NavEditorGui::onModeSet(%this, %mode) +function NavMeshSelectTool::onActivated(%this) +{ + NavInspector.setVisible(false); + %actions = NavEditorOptionsWindow->ActionsBox; + NavInspector.setVisible(true); + %actions->SelectActions.setVisible(true); + + NavInspector.inspect(NavEditorGui.getMesh()); +} + +function NavMeshSelectTool::onDeactivated(%this) +{ + NavInspector.setVisible(false); + %actions = NavEditorOptionsWindow->ActionsBox; + %actions->SelectActions.setVisible(false); +} + +function NavMeshSelectTool::onNavMeshSelected(%this) +{ + NavTreeView.clearSelection(); + if(isObject(NavEditorGui.getMesh())) + NavTreeView.selectItem(NavEditorGui.getMesh()); + // we set the naveditorgui navmesh in source so just get it + // and update here. + NavInspector.inspect(NavEditorGui.getMesh()); +} + +//------------------------------------------------------ +// OffMeshConnectionTool +//------------------------------------------------------ + +function OffMeshConnectionTool::onActivated(%this) { - // Callback when the nav editor changes mode. Set the appropriate dynamic - // GUI contents in the properties/actions boxes. NavInspector.setVisible(false); %actions = NavEditorOptionsWindow->ActionsBox; - %actions->SelectActions.setVisible(false); - %actions->LinkActions.setVisible(false); - %actions->CoverActions.setVisible(false); - %actions->TileActions.setVisible(false); - %actions->TestActions.setVisible(false); + %properties = NavEditorOptionsWindow->PropertiesBox; + %actions->LinkActions.setVisible(true); + %properties->LinkProperties.setVisible(true); +} + +function OffMeshConnectionTool::onDeactivated(%this) +{ + NavInspector.setVisible(false); + + %actions = NavEditorOptionsWindow->ActionsBox; + %actions->LinkActions.setVisible(false); %properties = NavEditorOptionsWindow->PropertiesBox; %properties->LinkProperties.setVisible(false); - %properties->TileProperties.setVisible(false); - %properties->TestProperties.setVisible(false); +} - switch$(%mode) +function OffMeshConnectionTool::updateLinkFlags(%this) +{ + %properties = NavEditorOptionsWindow-->LinkProperties; + %this.setLinkProperties(getLinkFlags(%properties), %properties->LinkBiDirection.isStateOn(), %properties->LinkRadius.getValue()); +} + +function updateLinkData(%control, %flags, %biDir, %radius) +{ + %control->LinkRadius.setActive(true); + %control->LinkBiDirection.setActive(true); + %control->LinkWalkFlag.setActive(true); + %control->LinkJumpFlag.setActive(true); + %control->LinkDropFlag.setActive(true); + %control->LinkLedgeFlag.setActive(true); + %control->LinkClimbFlag.setActive(true); + %control->LinkTeleportFlag.setActive(true); + + %control->LinkRadius.setValue(%radius); + %control->LinkBiDirection.setStateOn(%biDir); + %control->LinkWalkFlag.setStateOn(%flags & $Nav::WalkFlag); + %control->LinkJumpFlag.setStateOn(%flags & $Nav::JumpFlag); + %control->LinkDropFlag.setStateOn(%flags & $Nav::DropFlag); + %control->LinkLedgeFlag.setStateOn(%flags & $Nav::LedgeFlag); + %control->LinkClimbFlag.setStateOn(%flags & $Nav::ClimbFlag); + %control->LinkTeleportFlag.setStateOn(%flags & $Nav::TeleportFlag); +} + +function getLinkFlags(%control) +{ + return (%control->LinkWalkFlag.isStateOn() ? $Nav::WalkFlag : 0) | + (%control->LinkJumpFlag.isStateOn() ? $Nav::JumpFlag : 0) | + (%control->LinkDropFlag.isStateOn() ? $Nav::DropFlag : 0) | + (%control->LinkLedgeFlag.isStateOn() ? $Nav::LedgeFlag : 0) | + (%control->LinkClimbFlag.isStateOn() ? $Nav::ClimbFlag : 0) | + (%control->LinkTeleportFlag.isStateOn() ? $Nav::TeleportFlag : 0); +} + +function disableLinkData(%control) +{ + %control->LinkRadius.setActive(false); + %control->LinkBiDirection.setActive(false); + %control->LinkWalkFlag.setActive(false); + %control->LinkJumpFlag.setActive(false); + %control->LinkDropFlag.setActive(false); + %control->LinkLedgeFlag.setActive(false); + %control->LinkClimbFlag.setActive(false); + %control->LinkTeleportFlag.setActive(false); +} + +function OffMeshConnectionTool::onLinkSelected(%this, %flags, %biDir, %radius) +{ + updateLinkData(NavEditorOptionsWindow-->LinkProperties, %flags, %biDir, %radius); +} + +function OffMeshConnectionTool::onLinkDeselected(%this) +{ + disableLinkData(NavEditorOptionsWindow-->LinkProperties); +} + +function OffMeshConnectionTool::updateRadius(%this) +{ + %this.updateLinkFlags(); +} + +function NavMeshLinkFlagButton::onClick(%this) +{ + NavMeshTools->LinkTool.updateLinkFlags(); +} + +function NavMeshLinkBiDirection::onClick(%this) +{ + NavMeshTools->LinkTool.updateLinkFlags(); +} + +//------------------------------------------------------ +// CoverTool +//------------------------------------------------------ + +function CoverTool::onActivated(%this) +{ + %actions = NavEditorOptionsWindow->ActionsBox; + %actions->CoverActions.setVisible(true); +} + +function CoverTool::onDeactivated(%this) +{ + %actions = NavEditorOptionsWindow->ActionsBox; + %actions->CoverActions.setVisible(false); +} + +//------------------------------------------------------ +// NAVMESHTESTTOOL +//------------------------------------------------------ + +function NavMeshTestTool::onActivated(%this) +{ + NavInspector.setVisible(false); + + %actions = NavEditorOptionsWindow->ActionsBox; + %actions->TestActions.setVisible(true); + + %classList = enumerateConsoleClasses("Player") TAB enumerateConsoleClasses("Vehicle"); + //echo(%classList); + + SpawnClassSelector.clear(); + foreach$(%class in %classList) { - case "SelectMode": + if(%class !$= "Vehicle") // vehicle doesnt work, purely virtual class. + SpawnClassSelector.add(%class); + } + + SpawnClassSelector.setFirstSelected(true); +} + +function NavMeshTestTool::onDeactivated(%this) +{ + NavInspector.setVisible(false); + + %actions = NavEditorOptionsWindow->ActionsBox; + %actions->TestActions.setVisible(false); +} + +function NavMeshTestTool::onPlayerSelected(%this) +{ + if (!isObject(%this.getPlayer().aiController)) + { + %this.getPlayer().aiController = new AIController(){ ControllerData = %this.getPlayer().getDatablock().aiControllerData; }; + %this.getPlayer().setAIController(%this.getPlayer().aiController); + } + + if(%this.getPlayer().isMemberOfClass("AIPlayer")) + { + NavInspector.inspect(%this.getPlayer()); NavInspector.setVisible(true); - %actions->SelectActions.setVisible(true); - case "LinkMode": - %actions->LinkActions.setVisible(true); - %properties->LinkProperties.setVisible(true); - case "CoverMode": - // - %actions->CoverActions.setVisible(true); - case "TileMode": - %actions->TileActions.setVisible(true); - %properties->TileProperties.setVisible(true); - case "TestMode": - %actions->TestActions.setVisible(true); - %properties->TestProperties.setVisible(true); + } + else + { + NavInspector.inspect(%this.getPlayer().getDatablock().aiControllerData); + NavInspector.setVisible(true); + } + + NavMeshIgnore(%this.getPlayer(), true); + %this.getPlayer().setDamageState("Enabled"); +} + +function NavMeshTestTool::onPlayerDeselected(%this) +{ + NavInspector.inspect(); +} + +function NavMeshTestTool::stop(%this) +{ + if (isObject(%this.getPlayer().aiController)) + %this.getPlayer().aiController.stop(); + else + { + %this.getPlayer().stop(); + } +} + +function NavMeshTestTool::toggleFollow(%this) +{ + + if(isObject(%this.getFollowObject()) && isObject(%this.getPlayer())) + { + %player = %this.getPlayer(); + if(%player.isMemberOfClass("AIPlayer")) + %player.followObject(%this.getFollowObject(), "2.0"); + else + %player.getAIController().followObject(%this.getFollowObject(), %player.getDatablock().aiControllerData.mFollowTolerance); } } -function NavEditorGui::paletteSync(%this, %mode) +function NavMeshTestTool::followObject(%this) { - // Synchronise the palette (small buttons on the left) with the actual mode - // the nav editor is in. - %evalShortcut = "ToolsPaletteArray-->" @ %mode @ ".setStateOn(1);"; - eval(%evalShortcut); -} + %this.followSelectMode(); +} + +function NavMeshTestTool::findCover(%this) +{ + if(isObject(%this.getPlayer())) + { + %player = %this.getPlayer(); + %pos = %player.getPosition(); + + if(%player.isMemberOfClass("AIPlayer")) + %player.findCover(%pos, CoverRadius.getText()); + else + %player.getAIController().findCover(%pos, CoverRadius.getText()); + } +} + +function SpawnClassSelector::onSelect(%this, %id) +{ + %className = %this.getTextById(%id); + + NavMeshTools->TestTool.setSpawnClass(%className); + + SpawnDatablockSelector.clear(); + %classData = %className @ "Data"; + if(%className $= "AIPlayer") + { + %classData = "PlayerData"; + } + + // add the datablocks + for(%i = 0; %i < DataBlockGroup.getCount(); %i++) + { + %obj = DataBlockGroup.getObject(%i); + if( isMemberOfClass( %obj.getClassName(), %classData )) + SpawnDatablockSelector.add(%obj.getName()); + } + + SpawnDatablockSelector.setFirstSelected(true); + +} + +function SpawnDatablockSelector::onSelect(%this, %id) +{ + %className = %this.getTextById(%id); + NavMeshTools->TestTool.setSpawnDatablock(%className); +} + +//------------------------------------------------------ +// TILETOOL +//------------------------------------------------------ + +function TileTool::onActivated(%this) +{ + NavInspector.setVisible(false); + + %actions = NavEditorOptionsWindow->ActionsBox; + %properties = NavEditorOptionsWindow->PropertiesBox; + %actions->TileActions.setVisible(true); + %properties->TileProperties.setVisible(true); +} + +function TileTool::onDeactivated(%this) +{ + NavInspector.setVisible(false); + + %actions = NavEditorOptionsWindow->ActionsBox; + %actions->TileActions.setVisible(false); + %properties = NavEditorOptionsWindow->PropertiesBox; + %properties->TileProperties.setVisible(false); +} function NavEditorGui::onEscapePressed(%this) { return false; } -function NavEditorGui::selectObject(%this, %obj) -{ - NavTreeView.clearSelection(); - if(isObject(%obj)) - NavTreeView.selectItem(%obj); - %this.onObjectSelected(%obj); -} - -function NavEditorGui::onObjectSelected(%this, %obj) -{ - if(isObject(%this.selectedObject)) - %this.deselect(); - %this.selectedObject = %obj; - if(isObject(%obj)) - { - %this.selectMesh(%obj); - NavInspector.inspect(%obj); - } -} - function NavEditorGui::deleteMesh(%this) { if(isObject(%this.selectedObject)) @@ -388,12 +623,12 @@ function NavEditorGui::deleteSelected(%this) toolsMessageBoxYesNo("Warning", "Are you sure you want to delete" SPC NavEditorGui.selectedObject.getName(), "NavEditorGui.deleteMesh();"); - case "TestMode": - %this.getPlayer().delete(); - %this.onPlayerDeselected(); - case "LinkMode": - %this.deleteLink(); - %this.isDirty = true; + // case "TestMode": + // %this.getPlayer().delete(); + // %this.onPlayerDeselected(); + // case "LinkMode": + // %this.deleteLink(); + // %this.isDirty = true; } } @@ -415,99 +650,6 @@ function NavEditorGui::buildLinks(%this) } } -function updateLinkData(%control, %flags) -{ - %control->LinkWalkFlag.setActive(true); - %control->LinkJumpFlag.setActive(true); - %control->LinkDropFlag.setActive(true); - %control->LinkLedgeFlag.setActive(true); - %control->LinkClimbFlag.setActive(true); - %control->LinkTeleportFlag.setActive(true); - - %control->LinkWalkFlag.setStateOn(%flags & $Nav::WalkFlag); - %control->LinkJumpFlag.setStateOn(%flags & $Nav::JumpFlag); - %control->LinkDropFlag.setStateOn(%flags & $Nav::DropFlag); - %control->LinkLedgeFlag.setStateOn(%flags & $Nav::LedgeFlag); - %control->LinkClimbFlag.setStateOn(%flags & $Nav::ClimbFlag); - %control->LinkTeleportFlag.setStateOn(%flags & $Nav::TeleportFlag); -} - -function getLinkFlags(%control) -{ - return (%control->LinkWalkFlag.isStateOn() ? $Nav::WalkFlag : 0) | - (%control->LinkJumpFlag.isStateOn() ? $Nav::JumpFlag : 0) | - (%control->LinkDropFlag.isStateOn() ? $Nav::DropFlag : 0) | - (%control->LinkLedgeFlag.isStateOn() ? $Nav::LedgeFlag : 0) | - (%control->LinkClimbFlag.isStateOn() ? $Nav::ClimbFlag : 0) | - (%control->LinkTeleportFlag.isStateOn() ? $Nav::TeleportFlag : 0); -} - -function disableLinkData(%control) -{ - %control->LinkWalkFlag.setActive(false); - %control->LinkJumpFlag.setActive(false); - %control->LinkDropFlag.setActive(false); - %control->LinkLedgeFlag.setActive(false); - %control->LinkClimbFlag.setActive(false); - %control->LinkTeleportFlag.setActive(false); -} - -function NavEditorGui::onLinkSelected(%this, %flags) -{ - updateLinkData(NavEditorOptionsWindow-->LinkProperties, %flags); -} - -function NavEditorGui::onPlayerSelected(%this, %flags) -{ - if (!isObject(%this.getPlayer().aiController) || (isObject(%this.getPlayer().aiController) && !(%this.getPlayer().isMemberOfClass("AIPlayer")))) - { - %this.getPlayer().aiController = new AIController(){ ControllerData = %this.getPlayer().getDatablock().aiControllerData; }; - %this.getPlayer().setAIController(%this.getPlayer().aiController); - } - NavMeshIgnore(%this.getPlayer(), true); - %this.getPlayer().setDamageState("Enabled"); - - updateLinkData(NavEditorOptionsWindow-->TestProperties, %flags); -} - -function NavEditorGui::updateLinkFlags(%this) -{ - if(isObject(%this.getMesh())) - { - %properties = NavEditorOptionsWindow-->LinkProperties; - %this.setLinkFlags(getLinkFlags(%properties)); - %this.isDirty = true; - } -} - -function NavEditorGui::updateTestFlags(%this) -{ - if(isObject(%this.getPlayer())) - { - %properties = NavEditorOptionsWindow-->TestProperties; - %player = %this.getPlayer(); - - %player.allowWwalk = %properties->LinkWalkFlag.isStateOn(); - %player.allowJump = %properties->LinkJumpFlag.isStateOn(); - %player.allowDrop = %properties->LinkDropFlag.isStateOn(); - %player.allowLedge = %properties->LinkLedgeFlag.isStateOn(); - %player.allowClimb = %properties->LinkClimbFlag.isStateOn(); - %player.allowTeleport = %properties->LinkTeleportFlag.isStateOn(); - - %this.isDirty = true; - } -} - -function NavEditorGui::onLinkDeselected(%this) -{ - disableLinkData(NavEditorOptionsWindow-->LinkProperties); -} - -function NavEditorGui::onPlayerDeselected(%this) -{ - disableLinkData(NavEditorOptionsWindow-->TestProperties); -} - function NavEditorGui::createCoverPoints(%this) { if(isObject(%this.getMesh())) @@ -526,48 +668,6 @@ function NavEditorGui::deleteCoverPoints(%this) } } -function NavEditorGui::findCover(%this) -{ - if(%this.getMode() $= "TestMode" && isObject(%this.getPlayer())) - { - %pos = LocalClientConnection.getControlObject().getPosition(); - %text = NavEditorOptionsWindow-->TestProperties->CoverPosition.getText(); - if(%text !$= "") - %pos = eval("return " @ %text); - %this.getPlayer().getAIController().findCover(%pos, NavEditorOptionsWindow-->TestProperties->CoverRadius.getText()); - } -} - -function NavEditorGui::followObject(%this) -{ - if(%this.getMode() $= "TestMode" && isObject(%this.getPlayer())) - { - %obj = LocalClientConnection.player; - %text = NavEditorOptionsWindow-->TestProperties->FollowObject.getText(); - if(%text !$= "") - { - %command = "return " @ %text; - if(!endsWith(%command, ";")) - %command = %command @ ";"; - - %obj = eval(%command); - if(!isObject(%obj)) - toolsMessageBoxOk("Error", "Cannot find object" SPC %text); - } - if(isObject(%obj)) - %this.getPlayer().getAIController().followObject(%obj, NavEditorOptionsWindow-->TestProperties->FollowRadius.getText()); - } -} - -function NavEditorGui::stop(%this) -{ - if (isObject(%this.getPlayer().aiController)) - %this.getPlayer().aiController.stop(); - else - { - NavEditorGui.getPlayer().stop(); - } -} function NavInspector::inspect(%this, %obj) { %name = ""; @@ -598,13 +698,13 @@ function NavTreeView::onInspect(%this, %obj) function NavTreeView::onSelect(%this, %obj) { NavInspector.inspect(%obj); - NavEditorGui.onObjectSelected(%obj); + + NavEditorGui.selectMesh(%obj); } function NavEditorGui::prepSelectionMode(%this) { - %this.setMode("SelectMode"); - ToolsPaletteArray-->NavEditorSelectMode.setStateOn(1); + NavEditorGui.setActiveTool(NavMeshTools->SelectTool); } //----------------------------------------------------------------------------- @@ -618,16 +718,6 @@ function ENavEditorPaletteButton::onClick(%this) //----------------------------------------------------------------------------- -function NavMeshLinkFlagButton::onClick(%this) -{ - NavEditorGui.updateLinkFlags(); -} - -function NavMeshTestFlagButton::onClick(%this) -{ - NavEditorGui.updateTestFlags(); -} - singleton GuiControlProfile(NavEditorProfile) { canKeyFocus = true; @@ -635,3 +725,38 @@ singleton GuiControlProfile(NavEditorProfile) fillColor = "192 192 192 192"; category = "Editor"; }; + +function DrawModeSelector::init(%this) +{ + %this.clear(); + + %this.add("Draw NavMesh", 0); + %this.add("Draw NavMesh Transparent", 1); + %this.add("Draw NavMesh BVTree", 2); + %this.add("Draw NavMesh Nodes", 3); + %this.add("Draw NavMesh Portals", 4); + %this.add("Draw NavMesh Invis", 5); + %this.add("Draw Mesh", 6); + %this.add("Draw Voxels", 7); + %this.add("Draw Walkable Voxels", 8); + %this.add("Draw Compact Heightfield", 9); + %this.add("Draw Compact Distance", 10); + %this.add("Draw Compact Regions", 11); + %this.add("Draw Region Connections", 12); + %this.add("Draw Raw Contours", 13); + %this.add("Draw Both Contours", 14); + %this.add("Draw Contours", 15); + %this.add("Draw PolyMesh", 16); + %this.add("Draw PolyMesh Detail", 17); + +} + +function DrawModeSelector::selectDefault(%this) +{ + %this.setSelected(0); +} + +function DrawModeSelector::onSelect(%this, %id) +{ + NavEditorGui.setDrawMode(%id); +} \ No newline at end of file diff --git a/Templates/BaseGame/game/tools/worldEditor/gui/ToolsPaletteGroups/NavEditorPalette.ed.gui b/Templates/BaseGame/game/tools/worldEditor/gui/ToolsPaletteGroups/NavEditorPalette.ed.gui index b099f5835..499b433d9 100644 --- a/Templates/BaseGame/game/tools/worldEditor/gui/ToolsPaletteGroups/NavEditorPalette.ed.gui +++ b/Templates/BaseGame/game/tools/worldEditor/gui/ToolsPaletteGroups/NavEditorPalette.ed.gui @@ -11,7 +11,6 @@ $paletteId = new GuiControl(NavEditorPalette,EditorGuiGroup) { canSave = "1"; Visible = "1"; hovertime = "1000"; - new GuiBitmapButtonCtrl(ENavEditorSelectModeBtn) { canSaveDynamicFields = "1"; class = ENavEditorPaletteButton; @@ -26,9 +25,9 @@ $paletteId = new GuiControl(NavEditorPalette,EditorGuiGroup) { MinExtent = "8 2"; canSave = "1"; Visible = "1"; - Command = "NavEditorGui.prepSelectionMode();"; + Command = "NavEditorGui.setActiveTool(NavMeshTools->SelectTool);"; tooltipprofile = "GuiToolTipProfile"; - ToolTip = "View NavMesh (1)."; + ToolTip = "Edit NavMesh (1)."; DetailedDesc = ""; hovertime = "1000"; bitmapAsset = "ToolsModule:visibility_toggle_n_image"; @@ -49,10 +48,10 @@ $paletteId = new GuiControl(NavEditorPalette,EditorGuiGroup) { MinExtent = "8 2"; canSave = "1"; Visible = "1"; - Command = "NavEditorGui.setMode(\"LinkMode\");"; + Command = "NavEditorGui.setActiveTool(NavMeshTools->LinkTool);"; tooltipprofile = "GuiToolTipProfile"; - ToolTip = "Create off-mesh links (2)."; - DetailedDesc = "Click to select/add. Shift-click to add multiple end points."; + ToolTip = "Edit Links (2)."; + DetailedDesc = ""; hovertime = "1000"; bitmapAsset = "ToolsModule:nav_link_n_image"; buttonType = "RadioButton"; @@ -72,7 +71,7 @@ $paletteId = new GuiControl(NavEditorPalette,EditorGuiGroup) { MinExtent = "8 2"; canSave = "1"; Visible = "1"; - Command = "NavEditorGui.setMode(\"CoverMode\");"; + Command = "NavEditorGui.setActiveTool(NavMeshTools->NavCoverTool);"; tooltipprofile = "GuiToolTipProfile"; ToolTip = "Edit cover (3)."; DetailedDesc = ""; @@ -95,7 +94,7 @@ $paletteId = new GuiControl(NavEditorPalette,EditorGuiGroup) { MinExtent = "8 2"; canSave = "1"; Visible = "1"; - Command = "NavEditorGui.setMode(\"TileMode\");"; + Command = "NavEditorGui.setActiveTool(NavMeshTools->TileTool);"; tooltipprofile = "GuiToolTipProfile"; ToolTip = "View tiles (4)."; DetailedDesc = "Click to select."; @@ -118,7 +117,7 @@ $paletteId = new GuiControl(NavEditorPalette,EditorGuiGroup) { MinExtent = "8 2"; canSave = "1"; Visible = "1"; - Command = "NavEditorGui.setMode(\"TestMode\");"; + Command = "NavEditorGui.setActiveTool(NavMeshTools->TestTool);"; tooltipprofile = "GuiToolTipProfile"; ToolTip = "Test pathfinding (5)."; DetailedDesc = "Click to select/move character, CTRL-click to spawn, SHIFT-click to deselect."; diff --git a/Tools/CMake/modules/navigation.cmake b/Tools/CMake/modules/navigation.cmake index a76868107..a7a58e746 100644 --- a/Tools/CMake/modules/navigation.cmake +++ b/Tools/CMake/modules/navigation.cmake @@ -4,12 +4,15 @@ option(TORQUE_NAVIGATION "Enable Navigation module" ON) if(TORQUE_NAVIGATION) message("Enabling Navigation Module") - file(GLOB_RECURSE TORQUE_NAV_SOURCES "${CMAKE_SOURCE_DIR}/Engine/source/navigation/*.cpp" "${CMAKE_SOURCE_DIR}/Engine/source/navigation/*.h" ) - set(TORQUE_SOURCE_FILES ${TORQUE_SOURCE_FILES} ${TORQUE_NAV_SOURCES}) + file(GLOB TORQUE_NAV_SOURCES "${CMAKE_SOURCE_DIR}/Engine/source/navigation/*.cpp" "${CMAKE_SOURCE_DIR}/Engine/source/navigation/*.h") + if(TORQUE_TOOLS) + file(GLOB_RECURSE TORQUE_NAV_TOOLS "${CMAKE_SOURCE_DIR}/Engine/source/navigation/navMeshTools/*.cpp" "${CMAKE_SOURCE_DIR}/Engine/source/navigation/navMeshTools/*.h") + endif() + set(TORQUE_SOURCE_FILES ${TORQUE_SOURCE_FILES} ${TORQUE_NAV_SOURCES} ${TORQUE_NAV_TOOLS}) set(TORQUE_LINK_LIBRARIES ${TORQUE_LINK_LIBRARIES} recast) set(TORQUE_COMPILE_DEFINITIONS ${TORQUE_COMPILE_DEFINITIONS} recast TORQUE_NAVIGATION_ENABLED) # Since recast lives elsewhere we need to ensure it is known to Torque when providing a link to it add_subdirectory("${TORQUE_LIB_ROOT_DIRECTORY}/recast" ${TORQUE_LIB_TARG_DIRECTORY}/recast EXCLUDE_FROM_ALL) - source_group(TREE "${CMAKE_SOURCE_DIR}/Engine/source/navigation/" PREFIX "Modules/NAVIGATION" FILES ${TORQUE_NAV_SOURCES}) + source_group(TREE "${CMAKE_SOURCE_DIR}/Engine/source/navigation/" PREFIX "Modules/NAVIGATION" FILES ${TORQUE_NAV_SOURCES} ${TORQUE_NAV_TOOLS}) endif(TORQUE_NAVIGATION)