navmesh
This commit is contained in:
@@ -8,6 +8,9 @@ find_package(SDL2 REQUIRED)
|
||||
find_package(Jolt REQUIRED)
|
||||
find_package(OgreProcedural REQUIRED CONFIG)
|
||||
|
||||
# Build RecastNavigation from copied source (RTTI-compatible)
|
||||
add_subdirectory(recastnavigation)
|
||||
|
||||
# Collect all source files
|
||||
set(EDITSCENE_SOURCES
|
||||
main.cpp
|
||||
@@ -33,6 +36,11 @@ set(EDITSCENE_SOURCES
|
||||
systems/PlayerControllerSystem.cpp
|
||||
systems/CharacterSlotSystem.cpp
|
||||
systems/AnimationTreeSystem.cpp
|
||||
systems/BehaviorTreeSystem.cpp
|
||||
systems/NavMeshSystem.cpp
|
||||
recast/TileCacheNavMesh.cpp
|
||||
recast/PartitionedMesh.cpp
|
||||
recast/fastlz.c
|
||||
systems/CharacterSystem.cpp
|
||||
ui/TransformEditor.cpp
|
||||
ui/RenderableEditor.cpp
|
||||
@@ -65,7 +73,21 @@ set(EDITSCENE_SOURCES
|
||||
ui/StartupMenuEditor.cpp
|
||||
ui/PlayerControllerEditor.cpp
|
||||
ui/BuoyancyInfoEditor.cpp
|
||||
ui/GoapBlackboardEditor.cpp
|
||||
ui/BehaviorTreeEditor.cpp
|
||||
ui/InlineBehaviorTreeEditor.cpp
|
||||
ui/NavMeshEditor.cpp
|
||||
ui/ActionDatabaseEditor.cpp
|
||||
ui/ActionDebugEditor.cpp
|
||||
ui/ComponentRegistration.cpp
|
||||
components/GoapBlackboard.cpp
|
||||
components/GoapExpression.cpp
|
||||
components/GoapGoal.cpp
|
||||
components/ActionDatabase.cpp
|
||||
components/ActionDatabaseModule.cpp
|
||||
components/ActionDebugModule.cpp
|
||||
components/GoapBlackboardModule.cpp
|
||||
components/NavMeshModule.cpp
|
||||
components/LightModule.cpp
|
||||
components/CameraModule.cpp
|
||||
components/LodModule.cpp
|
||||
@@ -133,6 +155,11 @@ set(EDITSCENE_HEADERS
|
||||
systems/ProceduralMeshSystem.hpp
|
||||
systems/CharacterSlotSystem.hpp
|
||||
systems/AnimationTreeSystem.hpp
|
||||
systems/BehaviorTreeSystem.hpp
|
||||
systems/NavMeshSystem.hpp
|
||||
recast/TileCacheNavMesh.hpp
|
||||
recast/PartitionedMesh.hpp
|
||||
recast/fastlz.h
|
||||
systems/CharacterSystem.hpp
|
||||
systems/ProceduralTextureSystem.hpp
|
||||
systems/StaticGeometrySystem.hpp
|
||||
@@ -179,6 +206,22 @@ set(EDITSCENE_HEADERS
|
||||
ui/StartupMenuEditor.hpp
|
||||
ui/PlayerControllerEditor.hpp
|
||||
ui/BuoyancyInfoEditor.hpp
|
||||
ui/GoapBlackboardEditor.hpp
|
||||
ui/GoapBlackboardComponentEditor.hpp
|
||||
ui/BehaviorTreeEditor.hpp
|
||||
ui/InlineBehaviorTreeEditor.hpp
|
||||
ui/NavMeshEditor.hpp
|
||||
ui/NavMeshGeometrySourceEditor.hpp
|
||||
ui/ActionDatabaseEditor.hpp
|
||||
ui/ActionDebugEditor.hpp
|
||||
components/GoapBlackboard.hpp
|
||||
components/GoapExpression.hpp
|
||||
components/NavMesh.hpp
|
||||
components/BehaviorTree.hpp
|
||||
components/GoapAction.hpp
|
||||
components/GoapGoal.hpp
|
||||
components/ActionDatabase.hpp
|
||||
components/ActionDebug.hpp
|
||||
camera/EditorCamera.hpp
|
||||
gizmo/Gizmo.hpp
|
||||
physics/physics.h
|
||||
@@ -198,10 +241,20 @@ target_link_libraries(editSceneEditor
|
||||
nlohmann_json::nlohmann_json
|
||||
Jolt::Jolt
|
||||
OgreProcedural::OgreProcedural
|
||||
RecastNavigation::Recast
|
||||
RecastNavigation::Detour
|
||||
RecastNavigation::DetourTileCache
|
||||
RecastNavigation::DetourCrowd
|
||||
RecastNavigation::DebugUtils
|
||||
)
|
||||
|
||||
target_include_directories(editSceneEditor PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/recastnavigation/Recast/Include
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/recastnavigation/Detour/Include
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/recastnavigation/DetourTileCache/Include
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/recastnavigation/DetourCrowd/Include
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/recastnavigation/DebugUtils/Include
|
||||
)
|
||||
|
||||
# Copy local resources (materials, etc.)
|
||||
@@ -224,5 +277,9 @@ add_custom_command(TARGET editSceneEditor POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/resources.cfg"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/resources.cfg"
|
||||
# Re-copy editScene-specific resources so they aren't overwritten
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/resources"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/resources"
|
||||
COMMENT "Copying resources to editSceneEditor build directory"
|
||||
)
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
#include "systems/ProceduralMeshSystem.hpp"
|
||||
#include "systems/CharacterSlotSystem.hpp"
|
||||
#include "systems/AnimationTreeSystem.hpp"
|
||||
#include "systems/BehaviorTreeSystem.hpp"
|
||||
#include "systems/NavMeshSystem.hpp"
|
||||
#include "systems/CharacterSystem.hpp"
|
||||
#include "systems/CellGridSystem.hpp"
|
||||
#include "systems/RoomLayoutSystem.hpp"
|
||||
@@ -53,6 +55,11 @@
|
||||
#include "components/PlayerController.hpp"
|
||||
#include "components/CellGrid.hpp"
|
||||
#include "components/CellGridModule.hpp"
|
||||
#include "components/ActionDatabase.hpp"
|
||||
#include "components/ActionDebug.hpp"
|
||||
#include "components/BehaviorTree.hpp"
|
||||
#include "components/GoapBlackboard.hpp"
|
||||
#include "components/NavMesh.hpp"
|
||||
#include <OgreRTShaderSystem.h>
|
||||
#include <imgui.h>
|
||||
|
||||
@@ -297,6 +304,13 @@ void EditorApp::setup()
|
||||
m_animationTreeSystem = std::make_unique<AnimationTreeSystem>(
|
||||
m_world, m_sceneMgr);
|
||||
m_animationTreeSystem->initialize();
|
||||
m_behaviorTreeSystem = std::make_unique<BehaviorTreeSystem>(
|
||||
m_world, m_sceneMgr,
|
||||
m_animationTreeSystem.get());
|
||||
|
||||
// Setup NavMesh system
|
||||
m_navMeshSystem = std::make_unique<NavMeshSystem>(
|
||||
m_world, m_sceneMgr);
|
||||
|
||||
// Setup Character physics system
|
||||
m_characterSystem =
|
||||
@@ -308,6 +322,11 @@ void EditorApp::setup()
|
||||
std::make_unique<CellGridSystem>(m_world, m_sceneMgr);
|
||||
m_cellGridSystem->initialize();
|
||||
|
||||
// Wire CellGridSystem into NavMeshSystem so it can collect
|
||||
// batched frame/furniture geometry from StaticGeometry.
|
||||
m_navMeshSystem->setCellGridSystem(
|
||||
m_cellGridSystem.get());
|
||||
|
||||
// Setup RoomLayout system
|
||||
m_roomLayoutSystem =
|
||||
std::make_unique<RoomLayoutSystem>(m_world, m_sceneMgr);
|
||||
@@ -516,6 +535,16 @@ void EditorApp::setupECS()
|
||||
m_world.component<SunComponent>();
|
||||
m_world.component<SkyboxComponent>();
|
||||
|
||||
// Register AI/GOAP components
|
||||
m_world.component<ActionDatabase>();
|
||||
m_world.component<ActionDebug>();
|
||||
m_world.component<BehaviorTreeComponent>();
|
||||
m_world.component<GoapBlackboard>();
|
||||
|
||||
// Register Navigation components
|
||||
m_world.component<NavMeshComponent>();
|
||||
m_world.component<NavMeshGeometrySource>();
|
||||
|
||||
// Register CellGrid/Town components
|
||||
CellGridModule::registerComponents(m_world);
|
||||
}
|
||||
@@ -664,6 +693,9 @@ bool EditorApp::frameRenderingQueued(const Ogre::FrameEvent &evt)
|
||||
/* --- Animation / procedural generation --- */
|
||||
if (m_animationTreeSystem) {
|
||||
m_animationTreeSystem->update(evt.timeSinceLastFrame);
|
||||
if (m_behaviorTreeSystem)
|
||||
m_behaviorTreeSystem->update(
|
||||
evt.timeSinceLastFrame);
|
||||
}
|
||||
if (m_proceduralMeshSystem) {
|
||||
m_proceduralMeshSystem->update();
|
||||
@@ -677,6 +709,11 @@ bool EditorApp::frameRenderingQueued(const Ogre::FrameEvent &evt)
|
||||
m_cellGridSystem->update();
|
||||
}
|
||||
|
||||
/* --- NavMesh builds after static geometry is ready --- */
|
||||
if (m_navMeshSystem) {
|
||||
m_navMeshSystem->update(evt.timeSinceLastFrame);
|
||||
}
|
||||
|
||||
/* --- Dynamic physics (characters after static world) --- */
|
||||
if (m_characterSystem) {
|
||||
m_characterSystem->update(evt.timeSinceLastFrame);
|
||||
|
||||
@@ -23,6 +23,8 @@ class ProceduralMaterialSystem;
|
||||
class ProceduralMeshSystem;
|
||||
class CharacterSlotSystem;
|
||||
class AnimationTreeSystem;
|
||||
class BehaviorTreeSystem;
|
||||
class NavMeshSystem;
|
||||
class CharacterSystem;
|
||||
class CellGridSystem;
|
||||
class RoomLayoutSystem;
|
||||
@@ -211,6 +213,8 @@ private:
|
||||
std::unique_ptr<ProceduralMeshSystem> m_proceduralMeshSystem;
|
||||
std::unique_ptr<CharacterSlotSystem> m_characterSlotSystem;
|
||||
std::unique_ptr<AnimationTreeSystem> m_animationTreeSystem;
|
||||
std::unique_ptr<BehaviorTreeSystem> m_behaviorTreeSystem;
|
||||
std::unique_ptr<NavMeshSystem> m_navMeshSystem;
|
||||
std::unique_ptr<CharacterSystem> m_characterSystem;
|
||||
std::unique_ptr<CellGridSystem> m_cellGridSystem;
|
||||
std::unique_ptr<RoomLayoutSystem> m_roomLayoutSystem;
|
||||
|
||||
87
src/features/editScene/components/ActionDatabase.cpp
Normal file
87
src/features/editScene/components/ActionDatabase.cpp
Normal file
@@ -0,0 +1,87 @@
|
||||
#include "ActionDatabase.hpp"
|
||||
|
||||
const GoapAction *ActionDatabase::findAction(const Ogre::String &name) const
|
||||
{
|
||||
for (const auto &action : actions) {
|
||||
if (action.name == name)
|
||||
return &action;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
GoapAction *ActionDatabase::findAction(const Ogre::String &name)
|
||||
{
|
||||
for (auto &action : actions) {
|
||||
if (action.name == name)
|
||||
return &action;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const GoapGoal *ActionDatabase::findGoal(const Ogre::String &name) const
|
||||
{
|
||||
for (const auto &goal : goals) {
|
||||
if (goal.name == name)
|
||||
return &goal;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
GoapGoal *ActionDatabase::findGoal(const Ogre::String &name)
|
||||
{
|
||||
for (auto &goal : goals) {
|
||||
if (goal.name == name)
|
||||
return &goal;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool ActionDatabase::removeAction(const Ogre::String &name)
|
||||
{
|
||||
for (auto it = actions.begin(); it != actions.end(); ++it) {
|
||||
if (it->name == name) {
|
||||
actions.erase(it);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ActionDatabase::removeGoal(const Ogre::String &name)
|
||||
{
|
||||
for (auto it = goals.begin(); it != goals.end(); ++it) {
|
||||
if (it->name == name) {
|
||||
goals.erase(it);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const GoapGoal *ActionDatabase::selectBestGoal(
|
||||
const GoapBlackboard &blackboard) const
|
||||
{
|
||||
const GoapGoal *best = nullptr;
|
||||
int bestPriority = -1;
|
||||
|
||||
for (const auto &goal : goals) {
|
||||
if (!goal.isValid(blackboard))
|
||||
continue;
|
||||
if (goal.priority > bestPriority) {
|
||||
bestPriority = goal.priority;
|
||||
best = &goal;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
std::vector<const GoapAction *> ActionDatabase::getValidActions(
|
||||
const GoapBlackboard &blackboard) const
|
||||
{
|
||||
std::vector<const GoapAction *> result;
|
||||
for (const auto &action : actions) {
|
||||
if (action.canRun(blackboard))
|
||||
result.push_back(&action);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
42
src/features/editScene/components/ActionDatabase.hpp
Normal file
42
src/features/editScene/components/ActionDatabase.hpp
Normal file
@@ -0,0 +1,42 @@
|
||||
#ifndef EDITSCENE_ACTION_DATABASE_HPP
|
||||
#define EDITSCENE_ACTION_DATABASE_HPP
|
||||
#pragma once
|
||||
|
||||
#include "GoapAction.hpp"
|
||||
#include "GoapGoal.hpp"
|
||||
#include <vector>
|
||||
|
||||
/**
|
||||
* Global action database component.
|
||||
*
|
||||
* Holds the master list of GOAP actions and goals that characters can use.
|
||||
* Typically attached to a single "game manager" entity.
|
||||
*/
|
||||
struct ActionDatabase {
|
||||
std::vector<GoapAction> actions;
|
||||
std::vector<GoapGoal> goals;
|
||||
|
||||
// Find an action by name
|
||||
const GoapAction *findAction(const Ogre::String &name) const;
|
||||
GoapAction *findAction(const Ogre::String &name);
|
||||
|
||||
// Find a goal by name
|
||||
const GoapGoal *findGoal(const Ogre::String &name) const;
|
||||
GoapGoal *findGoal(const Ogre::String &name);
|
||||
|
||||
// Remove an action by name
|
||||
bool removeAction(const Ogre::String &name);
|
||||
|
||||
// Remove a goal by name
|
||||
bool removeGoal(const Ogre::String &name);
|
||||
|
||||
// Select the best valid goal for a given blackboard
|
||||
// Returns nullptr if no valid goal exists
|
||||
const GoapGoal *selectBestGoal(const GoapBlackboard &blackboard) const;
|
||||
|
||||
// Build a list of actions that can run from a given blackboard state
|
||||
std::vector<const GoapAction *> getValidActions(
|
||||
const GoapBlackboard &blackboard) const;
|
||||
};
|
||||
|
||||
#endif // EDITSCENE_ACTION_DATABASE_HPP
|
||||
48
src/features/editScene/components/ActionDatabaseModule.cpp
Normal file
48
src/features/editScene/components/ActionDatabaseModule.cpp
Normal file
@@ -0,0 +1,48 @@
|
||||
#include "ActionDatabase.hpp"
|
||||
#include "ActionDebug.hpp"
|
||||
#include "BehaviorTree.hpp"
|
||||
#include "GoapAction.hpp"
|
||||
#include "GoapGoal.hpp"
|
||||
#include "GoapBlackboard.hpp"
|
||||
#include "../ui/ComponentRegistration.hpp"
|
||||
#include "../ui/ActionDatabaseEditor.hpp"
|
||||
#include "../ui/BehaviorTreeEditor.hpp"
|
||||
|
||||
REGISTER_COMPONENT_GROUP("Action Database", "AI", ActionDatabase,
|
||||
ActionDatabaseEditor)
|
||||
{
|
||||
registry.registerComponent<ActionDatabase>(
|
||||
"Action Database", "AI",
|
||||
std::make_unique<ActionDatabaseEditor>(),
|
||||
// Adder
|
||||
[](flecs::entity e) {
|
||||
if (!e.has<ActionDatabase>())
|
||||
e.set<ActionDatabase>({});
|
||||
},
|
||||
// Remover
|
||||
[](flecs::entity e) {
|
||||
if (e.has<ActionDatabase>())
|
||||
e.remove<ActionDatabase>();
|
||||
});
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT_GROUP("Behavior Tree", "AI", BehaviorTreeComponent,
|
||||
BehaviorTreeEditor)
|
||||
{
|
||||
registry.registerComponent<BehaviorTreeComponent>(
|
||||
"Behavior Tree", "AI",
|
||||
std::make_unique<BehaviorTreeEditor>(),
|
||||
// Adder
|
||||
[](flecs::entity e) {
|
||||
if (!e.has<BehaviorTreeComponent>()) {
|
||||
BehaviorTreeComponent bt;
|
||||
bt.root.type = "sequence";
|
||||
e.set<BehaviorTreeComponent>(bt);
|
||||
}
|
||||
},
|
||||
// Remover
|
||||
[](flecs::entity e) {
|
||||
if (e.has<BehaviorTreeComponent>())
|
||||
e.remove<BehaviorTreeComponent>();
|
||||
});
|
||||
}
|
||||
35
src/features/editScene/components/ActionDebug.hpp
Normal file
35
src/features/editScene/components/ActionDebug.hpp
Normal file
@@ -0,0 +1,35 @@
|
||||
#ifndef EDITSCENE_ACTION_DEBUG_HPP
|
||||
#define EDITSCENE_ACTION_DEBUG_HPP
|
||||
#pragma once
|
||||
|
||||
#include "GoapBlackboard.hpp"
|
||||
#include <Ogre.h>
|
||||
|
||||
/**
|
||||
* Per-character action debug component.
|
||||
*
|
||||
* Allows test-running individual actions and inspecting the character's
|
||||
* local blackboard state. Used for debugging AI behavior in the editor.
|
||||
*/
|
||||
struct ActionDebug {
|
||||
// Character's local GOAP blackboard
|
||||
GoapBlackboard blackboard;
|
||||
|
||||
// Currently selected action for test-running
|
||||
Ogre::String selectedActionName;
|
||||
|
||||
// Currently selected goal for testing
|
||||
Ogre::String selectedGoalName;
|
||||
|
||||
// Test-run state
|
||||
bool isRunning = false;
|
||||
float runTimer = 0.0f;
|
||||
Ogre::String currentActionName;
|
||||
|
||||
// Debug output
|
||||
Ogre::String lastResult;
|
||||
|
||||
ActionDebug() = default;
|
||||
};
|
||||
|
||||
#endif // EDITSCENE_ACTION_DEBUG_HPP
|
||||
21
src/features/editScene/components/ActionDebugModule.cpp
Normal file
21
src/features/editScene/components/ActionDebugModule.cpp
Normal file
@@ -0,0 +1,21 @@
|
||||
#include "ActionDebug.hpp"
|
||||
#include "../ui/ComponentRegistration.hpp"
|
||||
#include "../ui/ActionDebugEditor.hpp"
|
||||
|
||||
REGISTER_COMPONENT_GROUP("Action Debug", "AI", ActionDebug,
|
||||
ActionDebugEditor)
|
||||
{
|
||||
registry.registerComponent<ActionDebug>(
|
||||
"Action Debug", "AI",
|
||||
std::make_unique<ActionDebugEditor>(),
|
||||
// Adder
|
||||
[](flecs::entity e) {
|
||||
if (!e.has<ActionDebug>())
|
||||
e.set<ActionDebug>({});
|
||||
},
|
||||
// Remover
|
||||
[](flecs::entity e) {
|
||||
if (e.has<ActionDebug>())
|
||||
e.remove<ActionDebug>();
|
||||
});
|
||||
}
|
||||
86
src/features/editScene/components/BehaviorTree.hpp
Normal file
86
src/features/editScene/components/BehaviorTree.hpp
Normal file
@@ -0,0 +1,86 @@
|
||||
#ifndef EDITSCENE_BEHAVIOR_TREE_HPP
|
||||
#define EDITSCENE_BEHAVIOR_TREE_HPP
|
||||
#pragma once
|
||||
|
||||
#include <Ogre.h>
|
||||
#include <vector>
|
||||
|
||||
/**
|
||||
* Data-driven behavior tree node for AI action execution.
|
||||
*
|
||||
* Node types:
|
||||
* "sequence" - Execute children in order until one fails
|
||||
* "selector" - Execute children in order until one succeeds
|
||||
* "invert" - Invert the result of a single child
|
||||
* "task" - Leaf action (references a named task)
|
||||
* "check" - Leaf condition (references a named check)
|
||||
* "debugPrint" - Leaf: prints 'name' to console once when active
|
||||
* "setAnimationState"- Leaf: sets animation state (name="SM/State")
|
||||
* "isAnimationEnded" - Leaf check: true if anim in state machine ended
|
||||
* "setBit" - Leaf: sets blackboard bit (name=bit, params=0/1)
|
||||
* "checkBit" - Leaf check: true if blackboard bit is set
|
||||
* "setValue" - Leaf: sets blackboard value (name=key, params=val)
|
||||
* "checkValue" - Leaf check: blackboard comparison (name=key, params="op val")
|
||||
* "blackboardDump" - Leaf: dumps entire blackboard to log
|
||||
*/
|
||||
struct BehaviorTreeNode {
|
||||
Ogre::String type = "task";
|
||||
Ogre::String name; // Action/condition name, or message, or SM/State
|
||||
Ogre::String params; // Optional extra parameters
|
||||
std::vector<BehaviorTreeNode> children;
|
||||
|
||||
BehaviorTreeNode() = default;
|
||||
|
||||
BehaviorTreeNode *findChild(const Ogre::String &childName)
|
||||
{
|
||||
for (auto &child : children) {
|
||||
if (child.name == childName)
|
||||
return &child;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const BehaviorTreeNode *findChild(const Ogre::String &childName) const
|
||||
{
|
||||
for (const auto &child : children) {
|
||||
if (child.name == childName)
|
||||
return &child;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool canHaveChildren() const
|
||||
{
|
||||
return type == "sequence" || type == "selector" ||
|
||||
type == "invert";
|
||||
}
|
||||
|
||||
bool isLeaf() const
|
||||
{
|
||||
return type == "task" || type == "check" ||
|
||||
type == "debugPrint" || type == "setAnimationState" ||
|
||||
type == "isAnimationEnded" || type == "setBit" ||
|
||||
type == "checkBit" || type == "setValue" ||
|
||||
type == "checkValue" || type == "blackboardDump";
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Behavior tree asset component.
|
||||
*
|
||||
* Can be attached to an entity to define a reusable behavior tree,
|
||||
* or referenced by name from a GoapAction.
|
||||
*/
|
||||
struct BehaviorTreeComponent {
|
||||
BehaviorTreeNode root;
|
||||
Ogre::String treeName;
|
||||
bool enabled = true;
|
||||
bool dirty = true;
|
||||
|
||||
void markDirty()
|
||||
{
|
||||
dirty = true;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // EDITSCENE_BEHAVIOR_TREE_HPP
|
||||
45
src/features/editScene/components/GoapAction.hpp
Normal file
45
src/features/editScene/components/GoapAction.hpp
Normal file
@@ -0,0 +1,45 @@
|
||||
#ifndef EDITSCENE_GOAP_ACTION_HPP
|
||||
#define EDITSCENE_GOAP_ACTION_HPP
|
||||
#pragma once
|
||||
|
||||
#include "GoapBlackboard.hpp"
|
||||
#include "BehaviorTree.hpp"
|
||||
#include <Ogre.h>
|
||||
|
||||
/**
|
||||
* A GOAP action definition.
|
||||
*
|
||||
* Actions live in the ActionDatabase and can be executed by any character.
|
||||
* Each action has preconditions (required blackboard state),
|
||||
* effects (resulting blackboard state), a cost, and a behavior tree.
|
||||
*/
|
||||
struct GoapAction {
|
||||
Ogre::String name;
|
||||
int cost = 1;
|
||||
|
||||
// GOAP preconditions and effects
|
||||
GoapBlackboard preconditions;
|
||||
GoapBlackboard effects;
|
||||
|
||||
// Behavior tree to execute when this action is selected
|
||||
BehaviorTreeNode behaviorTree;
|
||||
|
||||
// Optional: reference to a named behavior tree asset
|
||||
Ogre::String behaviorTreeName;
|
||||
|
||||
GoapAction() = default;
|
||||
|
||||
explicit GoapAction(const Ogre::String &name_, int cost_ = 1)
|
||||
: name(name_)
|
||||
, cost(cost_)
|
||||
{
|
||||
}
|
||||
|
||||
// Check if the given blackboard satisfies this action's preconditions
|
||||
bool canRun(const GoapBlackboard &blackboard) const
|
||||
{
|
||||
return blackboard.satisfies(preconditions);
|
||||
}
|
||||
};
|
||||
|
||||
#endif // EDITSCENE_GOAP_ACTION_HPP
|
||||
196
src/features/editScene/components/GoapBlackboard.cpp
Normal file
196
src/features/editScene/components/GoapBlackboard.cpp
Normal file
@@ -0,0 +1,196 @@
|
||||
#include "GoapBlackboard.hpp"
|
||||
#include <cstdlib>
|
||||
|
||||
std::array<std::string, 64> &GoapBlackboard::getBitNameRegistry()
|
||||
{
|
||||
static std::array<std::string, 64> registry;
|
||||
static bool initialized = false;
|
||||
if (!initialized) {
|
||||
for (auto &s : registry)
|
||||
s.clear();
|
||||
initialized = true;
|
||||
}
|
||||
return registry;
|
||||
}
|
||||
|
||||
void GoapBlackboard::setBitName(int index, const std::string &name)
|
||||
{
|
||||
if (index < 0 || index >= 64)
|
||||
return;
|
||||
getBitNameRegistry()[index] = name;
|
||||
}
|
||||
|
||||
const char *GoapBlackboard::getBitName(int index)
|
||||
{
|
||||
if (index < 0 || index >= 64)
|
||||
return nullptr;
|
||||
const auto &name = getBitNameRegistry()[index];
|
||||
return name.empty() ? nullptr : name.c_str();
|
||||
}
|
||||
|
||||
int GoapBlackboard::findBitByName(const std::string &name)
|
||||
{
|
||||
if (name.empty())
|
||||
return -1;
|
||||
const auto ®istry = getBitNameRegistry();
|
||||
for (int i = 0; i < 64; i++) {
|
||||
if (registry[i] == name)
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool GoapBlackboard::satisfies(const GoapBlackboard &other) const
|
||||
{
|
||||
// Check bits: for every bit set in other's mask, our bit must match
|
||||
uint64_t commonMask = mask & other.mask;
|
||||
if ((bits & commonMask) != (other.bits & commonMask))
|
||||
return false;
|
||||
// Also check bits that other has set but we don't
|
||||
uint64_t missingMask = other.mask & ~mask;
|
||||
if (missingMask) {
|
||||
// Other requires bits we don't have set -> fail
|
||||
// But only if other has those bits set to 1
|
||||
if ((other.bits & missingMask) != 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check values: for every key in other, our value must match
|
||||
for (const auto &pair : other.values) {
|
||||
auto it = values.find(pair.first);
|
||||
if (it == values.end()) {
|
||||
// If we don't have the key, only satisfy if target is 0
|
||||
if (pair.second != 0)
|
||||
return false;
|
||||
} else if (it->second != pair.second) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void GoapBlackboard::apply(const GoapBlackboard &other)
|
||||
{
|
||||
// Apply bit effects
|
||||
bits = (bits & ~other.mask) | (other.bits & other.mask);
|
||||
mask |= other.mask;
|
||||
|
||||
// Apply value effects
|
||||
for (const auto &pair : other.values)
|
||||
values[pair.first] = pair.second;
|
||||
}
|
||||
|
||||
bool GoapBlackboard::getScalarValue(const std::string &key,
|
||||
float &out) const
|
||||
{
|
||||
auto itf = floatValues.find(key);
|
||||
if (itf != floatValues.end()) {
|
||||
out = itf->second;
|
||||
return true;
|
||||
}
|
||||
auto iti = values.find(key);
|
||||
if (iti != values.end()) {
|
||||
out = static_cast<float>(iti->second);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int GoapBlackboard::distanceTo(const GoapBlackboard &target,
|
||||
bool ignoreValues) const
|
||||
{
|
||||
int distance = 0;
|
||||
|
||||
// Bit differences
|
||||
uint64_t commonMask = mask & target.mask;
|
||||
distance += __builtin_popcountll((bits ^ target.bits) & commonMask);
|
||||
|
||||
// Bits target cares about but we don't have
|
||||
uint64_t missingInUs = target.mask & ~mask;
|
||||
distance += __builtin_popcountll(target.bits & missingInUs);
|
||||
|
||||
// Bits we care about but target doesn't (we may need to unset them)
|
||||
uint64_t missingInTarget = mask & ~target.mask;
|
||||
distance += __builtin_popcountll(bits & missingInTarget);
|
||||
|
||||
if (ignoreValues)
|
||||
return distance;
|
||||
|
||||
// Value differences (int only — planner ignores float/vec3)
|
||||
for (const auto &pair : target.values) {
|
||||
auto it = values.find(pair.first);
|
||||
if (it == values.end())
|
||||
distance += std::abs(pair.second);
|
||||
else
|
||||
distance += std::abs(it->second - pair.second);
|
||||
}
|
||||
|
||||
// Values we have that target doesn't (may need to be cleared)
|
||||
for (const auto &pair : values) {
|
||||
if (target.values.find(pair.first) == target.values.end())
|
||||
distance += std::abs(pair.second);
|
||||
}
|
||||
|
||||
return distance;
|
||||
}
|
||||
|
||||
Ogre::String GoapBlackboard::dump() const
|
||||
{
|
||||
Ogre::String result = "Blackboard:\n";
|
||||
|
||||
result += " Bits:\n";
|
||||
for (int i = 0; i < 64; i++) {
|
||||
if (hasBit(i)) {
|
||||
const char *name = getBitName(i);
|
||||
if (name)
|
||||
result += " " + Ogre::String(name) + " = " +
|
||||
(getBit(i) ? "true" : "false") + "\n";
|
||||
else
|
||||
result += " bit[" +
|
||||
Ogre::StringConverter::toString(i) + "] = " +
|
||||
(getBit(i) ? "true" : "false") + "\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (!values.empty()) {
|
||||
result += " Int values:\n";
|
||||
for (const auto &pair : values)
|
||||
result += " " + pair.first + " = " +
|
||||
Ogre::StringConverter::toString(pair.second) +
|
||||
"\n";
|
||||
}
|
||||
|
||||
if (!floatValues.empty()) {
|
||||
result += " Float values:\n";
|
||||
for (const auto &pair : floatValues)
|
||||
result += " " + pair.first + " = " +
|
||||
Ogre::StringConverter::toString(pair.second) +
|
||||
"\n";
|
||||
}
|
||||
|
||||
if (!vec3Values.empty()) {
|
||||
result += " Vec3 values:\n";
|
||||
for (const auto &pair : vec3Values)
|
||||
result += " " + pair.first + " = (" +
|
||||
Ogre::StringConverter::toString(pair.second.x) +
|
||||
", " +
|
||||
Ogre::StringConverter::toString(pair.second.y) +
|
||||
", " +
|
||||
Ogre::StringConverter::toString(pair.second.z) +
|
||||
")\n";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<int> GoapBlackboard::getSetBits() const
|
||||
{
|
||||
std::vector<int> result;
|
||||
uint64_t m = mask;
|
||||
while (m) {
|
||||
int bit = __builtin_ctzll(m);
|
||||
result.push_back(bit);
|
||||
m &= m - 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
198
src/features/editScene/components/GoapBlackboard.hpp
Normal file
198
src/features/editScene/components/GoapBlackboard.hpp
Normal file
@@ -0,0 +1,198 @@
|
||||
#ifndef EDITSCENE_GOAP_BLACKBOARD_HPP
|
||||
#define EDITSCENE_GOAP_BLACKBOARD_HPP
|
||||
#pragma once
|
||||
|
||||
#include <Ogre.h>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
/**
|
||||
* Lightweight GOAP blackboard for action preconditions, effects,
|
||||
* and per-character runtime state.
|
||||
*
|
||||
* Uses a 64-bit bitfield for fast boolean flag checks (used by GOAP planner).
|
||||
* Supports int, float, and Vector3 values (int is used for preconditions/effects;
|
||||
* float/vec3 are for behavior-tree-driven character state).
|
||||
*/
|
||||
struct GoapBlackboard {
|
||||
// Boolean flags: 64 bits available. These are used by the GOAP planner.
|
||||
uint64_t bits = 0;
|
||||
uint64_t mask = 0; // which bits are actually set
|
||||
|
||||
// Named integer values (health, hunger, etc.) — used by preconditions/effects
|
||||
std::unordered_map<std::string, int> values;
|
||||
|
||||
// Named float values — runtime character state
|
||||
std::unordered_map<std::string, float> floatValues;
|
||||
|
||||
// Named Vector3 values — runtime character state
|
||||
std::unordered_map<std::string, Ogre::Vector3> vec3Values;
|
||||
|
||||
GoapBlackboard() = default;
|
||||
|
||||
/* --- Bit accessors --- */
|
||||
void setBit(int index, bool value)
|
||||
{
|
||||
if (index < 0 || index >= 64)
|
||||
return;
|
||||
uint64_t bit = 1ULL << index;
|
||||
mask |= bit;
|
||||
if (value)
|
||||
bits |= bit;
|
||||
else
|
||||
bits &= ~bit;
|
||||
}
|
||||
|
||||
bool getBit(int index) const
|
||||
{
|
||||
if (index < 0 || index >= 64)
|
||||
return false;
|
||||
return (bits >> index) & 1ULL;
|
||||
}
|
||||
|
||||
bool hasBit(int index) const
|
||||
{
|
||||
if (index < 0 || index >= 64)
|
||||
return false;
|
||||
return (mask >> index) & 1ULL;
|
||||
}
|
||||
|
||||
void clearBit(int index)
|
||||
{
|
||||
if (index < 0 || index >= 64)
|
||||
return;
|
||||
uint64_t bit = 1ULL << index;
|
||||
mask &= ~bit;
|
||||
bits &= ~bit;
|
||||
}
|
||||
|
||||
/* --- Integer value accessors (backward compat) --- */
|
||||
void setValue(const std::string &key, int value)
|
||||
{
|
||||
values[key] = value;
|
||||
}
|
||||
|
||||
int getValue(const std::string &key, int defaultValue = 0) const
|
||||
{
|
||||
auto it = values.find(key);
|
||||
if (it != values.end())
|
||||
return it->second;
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
bool hasValue(const std::string &key) const
|
||||
{
|
||||
return values.find(key) != values.end();
|
||||
}
|
||||
|
||||
void removeValue(const std::string &key)
|
||||
{
|
||||
values.erase(key);
|
||||
}
|
||||
|
||||
/* --- Float value accessors --- */
|
||||
void setFloatValue(const std::string &key, float value)
|
||||
{
|
||||
floatValues[key] = value;
|
||||
}
|
||||
|
||||
float getFloatValue(const std::string &key,
|
||||
float defaultValue = 0.0f) const
|
||||
{
|
||||
auto it = floatValues.find(key);
|
||||
if (it != floatValues.end())
|
||||
return it->second;
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
bool hasFloatValue(const std::string &key) const
|
||||
{
|
||||
return floatValues.find(key) != floatValues.end();
|
||||
}
|
||||
|
||||
void removeFloatValue(const std::string &key)
|
||||
{
|
||||
floatValues.erase(key);
|
||||
}
|
||||
|
||||
/* --- Vector3 value accessors --- */
|
||||
void setVec3Value(const std::string &key, const Ogre::Vector3 &value)
|
||||
{
|
||||
vec3Values[key] = value;
|
||||
}
|
||||
|
||||
Ogre::Vector3 getVec3Value(
|
||||
const std::string &key,
|
||||
const Ogre::Vector3 &defaultValue = Ogre::Vector3::ZERO) const
|
||||
{
|
||||
auto it = vec3Values.find(key);
|
||||
if (it != vec3Values.end())
|
||||
return it->second;
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
bool hasVec3Value(const std::string &key) const
|
||||
{
|
||||
return vec3Values.find(key) != vec3Values.end();
|
||||
}
|
||||
|
||||
void removeVec3Value(const std::string &key)
|
||||
{
|
||||
vec3Values.erase(key);
|
||||
}
|
||||
|
||||
/* --- Generic scalar lookup (tries int then float) --- */
|
||||
bool getScalarValue(const std::string &key, float &out) const;
|
||||
|
||||
/* --- GOAP methods --- */
|
||||
bool satisfies(const GoapBlackboard &other) const;
|
||||
void apply(const GoapBlackboard &other);
|
||||
int distanceTo(const GoapBlackboard &target,
|
||||
bool ignoreValues = false) const;
|
||||
|
||||
/* --- Utility --- */
|
||||
bool isValid() const
|
||||
{
|
||||
return mask != 0 || !values.empty() || !floatValues.empty() ||
|
||||
!vec3Values.empty();
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
bits = 0;
|
||||
mask = 0;
|
||||
values.clear();
|
||||
floatValues.clear();
|
||||
vec3Values.clear();
|
||||
}
|
||||
|
||||
Ogre::String dump() const;
|
||||
|
||||
// Bit naming: global registry for human-readable bit names
|
||||
static std::array<std::string, 64> &getBitNameRegistry();
|
||||
static void setBitName(int index, const std::string &name);
|
||||
static const char *getBitName(int index);
|
||||
static int findBitByName(const std::string &name);
|
||||
|
||||
// List all set bit indices
|
||||
std::vector<int> getSetBits() const;
|
||||
|
||||
// Equality
|
||||
bool operator==(const GoapBlackboard &other) const
|
||||
{
|
||||
return bits == other.bits && mask == other.mask &&
|
||||
values == other.values &&
|
||||
floatValues == other.floatValues &&
|
||||
vec3Values == other.vec3Values;
|
||||
}
|
||||
|
||||
bool operator!=(const GoapBlackboard &other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
};
|
||||
|
||||
#endif // EDITSCENE_GOAP_BLACKBOARD_HPP
|
||||
21
src/features/editScene/components/GoapBlackboardModule.cpp
Normal file
21
src/features/editScene/components/GoapBlackboardModule.cpp
Normal file
@@ -0,0 +1,21 @@
|
||||
#include "GoapBlackboard.hpp"
|
||||
#include "../ui/ComponentRegistration.hpp"
|
||||
#include "../ui/GoapBlackboardComponentEditor.hpp"
|
||||
|
||||
REGISTER_COMPONENT_GROUP("Blackboard", "AI", GoapBlackboard,
|
||||
GoapBlackboardComponentEditor)
|
||||
{
|
||||
registry.registerComponent<GoapBlackboard>(
|
||||
"Blackboard", "AI",
|
||||
std::make_unique<GoapBlackboardComponentEditor>(),
|
||||
// Adder
|
||||
[](flecs::entity e) {
|
||||
if (!e.has<GoapBlackboard>())
|
||||
e.set<GoapBlackboard>({});
|
||||
},
|
||||
// Remover
|
||||
[](flecs::entity e) {
|
||||
if (e.has<GoapBlackboard>())
|
||||
e.remove<GoapBlackboard>();
|
||||
});
|
||||
}
|
||||
283
src/features/editScene/components/GoapExpression.cpp
Normal file
283
src/features/editScene/components/GoapExpression.cpp
Normal file
@@ -0,0 +1,283 @@
|
||||
#include "GoapExpression.hpp"
|
||||
#include <cctype>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
void GoapExpression::skipWhitespace()
|
||||
{
|
||||
while (*m_pos == ' ' || *m_pos == '\t' || *m_pos == '\n' ||
|
||||
*m_pos == '\r')
|
||||
m_pos++;
|
||||
}
|
||||
|
||||
bool GoapExpression::match(const char *s)
|
||||
{
|
||||
skipWhitespace();
|
||||
size_t len = strlen(s);
|
||||
if (strncmp(m_pos, s, len) == 0) {
|
||||
m_pos += len;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
GoapExpression::Node *GoapExpression::parsePrimary()
|
||||
{
|
||||
skipWhitespace();
|
||||
|
||||
// Parenthesized expression
|
||||
if (match("(")) {
|
||||
Node *node = parseExpression();
|
||||
if (!node)
|
||||
return nullptr;
|
||||
if (!match(")")) {
|
||||
setError("Expected ')'");
|
||||
delete node;
|
||||
return nullptr;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
// Integer literal
|
||||
if (isdigit(*m_pos) || (*m_pos == '-' && isdigit(m_pos[1]))) {
|
||||
bool negative = false;
|
||||
if (*m_pos == '-') {
|
||||
negative = true;
|
||||
m_pos++;
|
||||
}
|
||||
int value = 0;
|
||||
while (isdigit(*m_pos)) {
|
||||
value = value * 10 + (*m_pos - '0');
|
||||
m_pos++;
|
||||
}
|
||||
Node *node = new Node(Node::Value);
|
||||
node->value = negative ? -value : value;
|
||||
return node;
|
||||
}
|
||||
|
||||
// Variable name
|
||||
if (isalpha(*m_pos) || *m_pos == '_') {
|
||||
std::string name;
|
||||
while (isalnum(*m_pos) || *m_pos == '_') {
|
||||
name += *m_pos;
|
||||
m_pos++;
|
||||
}
|
||||
Node *node = new Node(Node::Variable);
|
||||
node->name = name;
|
||||
return node;
|
||||
}
|
||||
|
||||
setError("Unexpected character in expression");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
GoapExpression::Node *GoapExpression::parseComparison()
|
||||
{
|
||||
Node *left = parsePrimary();
|
||||
if (!left)
|
||||
return nullptr;
|
||||
|
||||
skipWhitespace();
|
||||
if (match("==")) {
|
||||
Node *right = parsePrimary();
|
||||
if (!right) {
|
||||
delete left;
|
||||
return nullptr;
|
||||
}
|
||||
Node *node = new Node(Node::Equal);
|
||||
node->left = left;
|
||||
node->right = right;
|
||||
return node;
|
||||
} else if (match("!=")) {
|
||||
Node *right = parsePrimary();
|
||||
if (!right) {
|
||||
delete left;
|
||||
return nullptr;
|
||||
}
|
||||
Node *node = new Node(Node::NotEqual);
|
||||
node->left = left;
|
||||
node->right = right;
|
||||
return node;
|
||||
} else if (match("<=")) {
|
||||
Node *right = parsePrimary();
|
||||
if (!right) {
|
||||
delete left;
|
||||
return nullptr;
|
||||
}
|
||||
Node *node = new Node(Node::LessEqual);
|
||||
node->left = left;
|
||||
node->right = right;
|
||||
return node;
|
||||
} else if (match(">=")) {
|
||||
Node *right = parsePrimary();
|
||||
if (!right) {
|
||||
delete left;
|
||||
return nullptr;
|
||||
}
|
||||
Node *node = new Node(Node::GreaterEqual);
|
||||
node->left = left;
|
||||
node->right = right;
|
||||
return node;
|
||||
} else if (match("<")) {
|
||||
Node *right = parsePrimary();
|
||||
if (!right) {
|
||||
delete left;
|
||||
return nullptr;
|
||||
}
|
||||
Node *node = new Node(Node::Less);
|
||||
node->left = left;
|
||||
node->right = right;
|
||||
return node;
|
||||
} else if (match(">")) {
|
||||
Node *right = parsePrimary();
|
||||
if (!right) {
|
||||
delete left;
|
||||
return nullptr;
|
||||
}
|
||||
Node *node = new Node(Node::Greater);
|
||||
node->left = left;
|
||||
node->right = right;
|
||||
return node;
|
||||
}
|
||||
|
||||
return left;
|
||||
}
|
||||
|
||||
GoapExpression::Node *GoapExpression::parseNot()
|
||||
{
|
||||
skipWhitespace();
|
||||
if (match("!")) {
|
||||
Node *child = parseNot();
|
||||
if (!child)
|
||||
return nullptr;
|
||||
Node *node = new Node(Node::Not);
|
||||
node->left = child;
|
||||
return node;
|
||||
}
|
||||
return parseComparison();
|
||||
}
|
||||
|
||||
GoapExpression::Node *GoapExpression::parseAnd()
|
||||
{
|
||||
Node *left = parseNot();
|
||||
if (!left)
|
||||
return nullptr;
|
||||
|
||||
while (true) {
|
||||
if (match("&&")) {
|
||||
Node *right = parseNot();
|
||||
if (!right) {
|
||||
delete left;
|
||||
return nullptr;
|
||||
}
|
||||
Node *node = new Node(Node::And);
|
||||
node->left = left;
|
||||
node->right = right;
|
||||
left = node;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return left;
|
||||
}
|
||||
|
||||
GoapExpression::Node *GoapExpression::parseExpression()
|
||||
{
|
||||
Node *left = parseAnd();
|
||||
if (!left)
|
||||
return nullptr;
|
||||
|
||||
while (true) {
|
||||
if (match("||")) {
|
||||
Node *right = parseAnd();
|
||||
if (!right) {
|
||||
delete left;
|
||||
return nullptr;
|
||||
}
|
||||
Node *node = new Node(Node::Or);
|
||||
node->left = left;
|
||||
node->right = right;
|
||||
left = node;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return left;
|
||||
}
|
||||
|
||||
bool GoapExpression::parse(const char *expr)
|
||||
{
|
||||
clear();
|
||||
m_expr = expr;
|
||||
m_pos = expr;
|
||||
m_root = parseExpression();
|
||||
if (!m_root)
|
||||
return false;
|
||||
skipWhitespace();
|
||||
if (*m_pos != '\0') {
|
||||
setError("Unexpected trailing characters");
|
||||
clear();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int GoapExpression::evalNode(Node *node, const GoapBlackboard &bb) const
|
||||
{
|
||||
if (!node)
|
||||
return 0;
|
||||
|
||||
switch (node->type) {
|
||||
case Node::Value:
|
||||
return node->value;
|
||||
case Node::Variable:
|
||||
return bb.getValue(node->name, 0);
|
||||
case Node::Equal:
|
||||
return evalNode(node->left, bb) == evalNode(node->right, bb) ? 1 : 0;
|
||||
case Node::NotEqual:
|
||||
return evalNode(node->left, bb) != evalNode(node->right, bb) ? 1 : 0;
|
||||
case Node::Less:
|
||||
return evalNode(node->left, bb) < evalNode(node->right, bb) ? 1 : 0;
|
||||
case Node::Greater:
|
||||
return evalNode(node->left, bb) > evalNode(node->right, bb) ? 1 : 0;
|
||||
case Node::LessEqual:
|
||||
return evalNode(node->left, bb) <= evalNode(node->right, bb) ? 1 : 0;
|
||||
case Node::GreaterEqual:
|
||||
return evalNode(node->left, bb) >= evalNode(node->right, bb) ? 1 : 0;
|
||||
case Node::And:
|
||||
return evalNode(node->left, bb) && evalNode(node->right, bb) ? 1 : 0;
|
||||
case Node::Or:
|
||||
return evalNode(node->left, bb) || evalNode(node->right, bb) ? 1 : 0;
|
||||
case Node::Not:
|
||||
return !evalNode(node->left, bb) ? 1 : 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool GoapExpression::evaluate(const GoapBlackboard &blackboard) const
|
||||
{
|
||||
if (!m_root)
|
||||
return false;
|
||||
return evalNode(m_root, blackboard) != 0;
|
||||
}
|
||||
|
||||
void GoapExpression::setError(const char *msg)
|
||||
{
|
||||
m_error = msg;
|
||||
if (m_pos && m_expr) {
|
||||
m_error += " at position ";
|
||||
m_error += std::to_string(m_pos - m_expr);
|
||||
m_error += " near \"";
|
||||
m_error += std::string(m_pos, strnlen(m_pos, 20));
|
||||
m_error += "\"";
|
||||
}
|
||||
}
|
||||
|
||||
void GoapExpression::clear()
|
||||
{
|
||||
delete m_root;
|
||||
m_root = nullptr;
|
||||
m_expr = nullptr;
|
||||
m_pos = nullptr;
|
||||
m_error.clear();
|
||||
}
|
||||
85
src/features/editScene/components/GoapExpression.hpp
Normal file
85
src/features/editScene/components/GoapExpression.hpp
Normal file
@@ -0,0 +1,85 @@
|
||||
#ifndef EDITSCENE_GOAP_EXPRESSION_HPP
|
||||
#define EDITSCENE_GOAP_EXPRESSION_HPP
|
||||
#pragma once
|
||||
|
||||
#include "GoapBlackboard.hpp"
|
||||
#include <string>
|
||||
|
||||
/**
|
||||
* Simple expression evaluator for GOAP goal conditions.
|
||||
*
|
||||
* Supports:
|
||||
* - Variable names (looked up in blackboard values, default 0)
|
||||
* - Integer literals
|
||||
* - Comparisons: ==, !=, <, >, <=, >=
|
||||
* - Boolean operators: &&, ||
|
||||
* - Parentheses for grouping
|
||||
* - Unary negation: !
|
||||
*
|
||||
* Example: "health > 20 && (hunger > 50 || have_food == 1)"
|
||||
*/
|
||||
class GoapExpression {
|
||||
public:
|
||||
GoapExpression() = default;
|
||||
|
||||
// Parse an expression string. Returns true on success.
|
||||
bool parse(const char *expr);
|
||||
|
||||
// Evaluate the parsed expression against a blackboard.
|
||||
// Returns false if expression was not parsed successfully.
|
||||
bool evaluate(const GoapBlackboard &blackboard) const;
|
||||
|
||||
// Get last error message
|
||||
const std::string &getError() const { return m_error; }
|
||||
|
||||
private:
|
||||
struct Node {
|
||||
enum Type {
|
||||
Value, // integer literal
|
||||
Variable, // blackboard variable name
|
||||
Equal,
|
||||
NotEqual,
|
||||
Less,
|
||||
Greater,
|
||||
LessEqual,
|
||||
GreaterEqual,
|
||||
And,
|
||||
Or,
|
||||
Not
|
||||
} type;
|
||||
int value = 0; // for Value
|
||||
std::string name; // for Variable
|
||||
Node *left = nullptr;
|
||||
Node *right = nullptr;
|
||||
|
||||
Node(Type t)
|
||||
: type(t)
|
||||
{
|
||||
}
|
||||
~Node()
|
||||
{
|
||||
delete left;
|
||||
delete right;
|
||||
}
|
||||
};
|
||||
|
||||
const char *m_expr = nullptr;
|
||||
const char *m_pos = nullptr;
|
||||
std::string m_error;
|
||||
Node *m_root = nullptr;
|
||||
|
||||
void skipWhitespace();
|
||||
bool match(const char *s);
|
||||
Node *parseExpression(); // ||
|
||||
Node *parseAnd(); // &&
|
||||
Node *parseNot(); // !
|
||||
Node *parseComparison(); // ==, !=, <, >, <=, >=
|
||||
Node *parsePrimary(); // value, variable, (expr)
|
||||
|
||||
int evalNode(Node *node, const GoapBlackboard &bb) const;
|
||||
|
||||
void setError(const char *msg);
|
||||
void clear();
|
||||
};
|
||||
|
||||
#endif // EDITSCENE_GOAP_EXPRESSION_HPP
|
||||
24
src/features/editScene/components/GoapGoal.cpp
Normal file
24
src/features/editScene/components/GoapGoal.cpp
Normal file
@@ -0,0 +1,24 @@
|
||||
#include "GoapGoal.hpp"
|
||||
#include "GoapExpression.hpp"
|
||||
|
||||
bool GoapGoal::isSatisfied(const GoapBlackboard &blackboard) const
|
||||
{
|
||||
return blackboard.satisfies(target);
|
||||
}
|
||||
|
||||
bool GoapGoal::isValid(const GoapBlackboard &blackboard) const
|
||||
{
|
||||
// If already satisfied, not a valid goal to pursue
|
||||
if (isSatisfied(blackboard))
|
||||
return false;
|
||||
|
||||
// Evaluate condition if present
|
||||
if (!condition.empty()) {
|
||||
GoapExpression expr;
|
||||
if (expr.parse(condition.c_str())) {
|
||||
return expr.evaluate(blackboard);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
43
src/features/editScene/components/GoapGoal.hpp
Normal file
43
src/features/editScene/components/GoapGoal.hpp
Normal file
@@ -0,0 +1,43 @@
|
||||
#ifndef EDITSCENE_GOAP_GOAL_HPP
|
||||
#define EDITSCENE_GOAP_GOAL_HPP
|
||||
#pragma once
|
||||
|
||||
#include "GoapBlackboard.hpp"
|
||||
#include <Ogre.h>
|
||||
|
||||
/**
|
||||
* A GOAP goal definition.
|
||||
*
|
||||
* Goals are selected based on priority and validity.
|
||||
* The target blackboard defines the desired world state.
|
||||
* The condition string provides additional runtime validity checks
|
||||
* using a simple expression language against blackboard values.
|
||||
*/
|
||||
struct GoapGoal {
|
||||
Ogre::String name;
|
||||
int priority = 1;
|
||||
|
||||
// Target blackboard state to achieve
|
||||
GoapBlackboard target;
|
||||
|
||||
// Optional condition expression (e.g. "health > 20 && hunger > 50")
|
||||
// If empty, the goal is always considered for validity checking
|
||||
Ogre::String condition;
|
||||
|
||||
GoapGoal() = default;
|
||||
|
||||
explicit GoapGoal(const Ogre::String &name_, int priority_ = 1)
|
||||
: name(name_)
|
||||
, priority(priority_)
|
||||
{
|
||||
}
|
||||
|
||||
// Check if the goal is already satisfied by the given blackboard
|
||||
bool isSatisfied(const GoapBlackboard &blackboard) const;
|
||||
|
||||
// Check if the goal is valid for the given blackboard
|
||||
// (condition evaluates to true and goal is not already satisfied)
|
||||
bool isValid(const GoapBlackboard &blackboard) const;
|
||||
};
|
||||
|
||||
#endif // EDITSCENE_GOAP_GOAL_HPP
|
||||
59
src/features/editScene/components/NavMesh.hpp
Normal file
59
src/features/editScene/components/NavMesh.hpp
Normal file
@@ -0,0 +1,59 @@
|
||||
#ifndef EDITSCENE_NAVMESH_HPP
|
||||
#define EDITSCENE_NAVMESH_HPP
|
||||
#pragma once
|
||||
|
||||
#include <Ogre.h>
|
||||
#include <cstdint>
|
||||
|
||||
/**
|
||||
* Navigation mesh component.
|
||||
*
|
||||
* Attached to a single "manager" entity that owns the tiled navmesh
|
||||
* for the entire scene. The NavMeshSystem collects geometry from
|
||||
* static rigid bodies, StaticGeometry members, and entities with
|
||||
* NavMeshGeometrySource to build the mesh.
|
||||
*/
|
||||
struct NavMeshComponent {
|
||||
// Recast build parameters
|
||||
float cellSize = 0.3f;
|
||||
float cellHeight = 0.2f;
|
||||
float agentHeight = 2.5f;
|
||||
float agentRadius = 0.5f;
|
||||
float agentMaxClimb = 1.0f;
|
||||
float agentMaxSlope = 20.0f;
|
||||
float edgeMaxLen = 12.0f;
|
||||
float edgeMaxError = 1.3f;
|
||||
float regionMinSize = 50.0f;
|
||||
float regionMergeSize = 20.0f;
|
||||
int tileSize = 48; // cells per tile
|
||||
|
||||
// Runtime flags
|
||||
bool enabled = true;
|
||||
bool debugDraw = false;
|
||||
bool dirty = true;
|
||||
|
||||
// Partial rebuild tracking (not serialized)
|
||||
bool needsPartialRebuild = false;
|
||||
Ogre::AxisAlignedBox rebuildArea;
|
||||
};
|
||||
|
||||
/**
|
||||
* Component that forces an entity's geometry to be included in
|
||||
* navmesh generation regardless of physics state.
|
||||
*/
|
||||
struct NavMeshGeometrySource {
|
||||
bool include = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Component for entities that want pathfinding on this navmesh.
|
||||
*/
|
||||
struct NavMeshAgent {
|
||||
Ogre::Vector3 targetPos = Ogre::Vector3::ZERO;
|
||||
bool hasTarget = false;
|
||||
bool pathPending = false;
|
||||
std::vector<Ogre::Vector3> currentPath;
|
||||
int pathIndex = 0;
|
||||
};
|
||||
|
||||
#endif // EDITSCENE_NAVMESH_HPP
|
||||
38
src/features/editScene/components/NavMeshModule.cpp
Normal file
38
src/features/editScene/components/NavMeshModule.cpp
Normal file
@@ -0,0 +1,38 @@
|
||||
#include "NavMesh.hpp"
|
||||
#include "../ui/ComponentRegistration.hpp"
|
||||
#include "../ui/NavMeshEditor.hpp"
|
||||
#include "../ui/NavMeshGeometrySourceEditor.hpp"
|
||||
|
||||
REGISTER_COMPONENT_GROUP("NavMesh", "Navigation", NavMeshComponent,
|
||||
NavMeshEditor)
|
||||
{
|
||||
registry.registerComponent<NavMeshComponent>(
|
||||
"NavMesh", "Navigation",
|
||||
std::make_unique<NavMeshEditor>(),
|
||||
// Adder
|
||||
[](flecs::entity e) {
|
||||
if (!e.has<NavMeshComponent>())
|
||||
e.set<NavMeshComponent>({});
|
||||
},
|
||||
// Remover
|
||||
[](flecs::entity e) {
|
||||
if (e.has<NavMeshComponent>())
|
||||
e.remove<NavMeshComponent>();
|
||||
});
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT_GROUP("NavMesh Geometry Source", "Navigation",
|
||||
NavMeshGeometrySource, NavMeshGeometrySourceEditor)
|
||||
{
|
||||
registry.registerComponent<NavMeshGeometrySource>(
|
||||
"NavMesh Geometry Source", "Navigation",
|
||||
std::make_unique<NavMeshGeometrySourceEditor>(),
|
||||
[](flecs::entity e) {
|
||||
if (!e.has<NavMeshGeometrySource>())
|
||||
e.set<NavMeshGeometrySource>({});
|
||||
},
|
||||
[](flecs::entity e) {
|
||||
if (e.has<NavMeshGeometrySource>())
|
||||
e.remove<NavMeshGeometrySource>();
|
||||
});
|
||||
}
|
||||
279
src/features/editScene/recast/PartitionedMesh.cpp
Normal file
279
src/features/editScene/recast/PartitionedMesh.cpp
Normal file
@@ -0,0 +1,279 @@
|
||||
//
|
||||
// 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 "PartitionedMesh.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
|
||||
struct IndexedBounds
|
||||
{
|
||||
float bmin[2];
|
||||
float bmax[2];
|
||||
int index;
|
||||
};
|
||||
|
||||
namespace
|
||||
{
|
||||
int compareMinX(const void* va, const void* vb)
|
||||
{
|
||||
return static_cast<int>(static_cast<const IndexedBounds*>(va)->bmin[0] - static_cast<const IndexedBounds*>(vb)->bmin[0]);
|
||||
}
|
||||
|
||||
int compareMinY(const void* va, const void* vb)
|
||||
{
|
||||
return static_cast<int>(static_cast<const IndexedBounds*>(va)->bmin[1] - static_cast<const IndexedBounds*>(vb)->bmin[1]);
|
||||
}
|
||||
|
||||
/// Calculates the total extent of all bounds in the given index range
|
||||
void calcTotalBounds(const std::vector<IndexedBounds> bounds, const int start, const int end, float* outBMin, float* outBMax)
|
||||
{
|
||||
outBMin[0] = bounds[start].bmin[0];
|
||||
outBMin[1] = bounds[start].bmin[1];
|
||||
|
||||
outBMax[0] = bounds[start].bmax[0];
|
||||
outBMax[1] = bounds[start].bmax[1];
|
||||
|
||||
for (int boundIndex = start + 1; boundIndex < end; ++boundIndex)
|
||||
{
|
||||
const IndexedBounds& it = bounds[boundIndex];
|
||||
outBMin[0] = std::min(it.bmin[0], outBMin[0]);
|
||||
outBMin[1] = std::min(it.bmin[1], outBMin[1]);
|
||||
|
||||
outBMax[0] = std::max(it.bmax[0], outBMax[0]);
|
||||
outBMax[1] = std::max(it.bmax[1], outBMax[1]);
|
||||
}
|
||||
}
|
||||
|
||||
void subdivide(
|
||||
std::vector<IndexedBounds> triBounds,
|
||||
int imin,
|
||||
int imax,
|
||||
int trisPerChunk,
|
||||
int& curNode,
|
||||
PartitionedMesh::Node* nodes,
|
||||
const int maxNodes,
|
||||
int& curTri,
|
||||
int* outTris,
|
||||
const int* inTris)
|
||||
{
|
||||
const int numTriBoundsInRange = imax - imin;
|
||||
const int icur = curNode;
|
||||
|
||||
if (curNode >= maxNodes)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PartitionedMesh::Node& node = nodes[curNode];
|
||||
curNode++;
|
||||
|
||||
if (numTriBoundsInRange <= trisPerChunk) // Leaf
|
||||
{
|
||||
// Get total bounds of all triangles
|
||||
calcTotalBounds(triBounds, imin, imax, node.bmin, node.bmax);
|
||||
|
||||
// Copy triangles.
|
||||
node.triIndex = curTri;
|
||||
node.numTris = numTriBoundsInRange;
|
||||
for (int triIndex = imin; triIndex < imax; ++triIndex)
|
||||
{
|
||||
const int* src = &inTris[triBounds[triIndex].index * 3];
|
||||
int* dst = &outTris[curTri * 3];
|
||||
curTri++;
|
||||
dst[0] = src[0];
|
||||
dst[1] = src[1];
|
||||
dst[2] = src[2];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Split
|
||||
calcTotalBounds(triBounds, imin, imax, node.bmin, node.bmax);
|
||||
|
||||
float xLength = node.bmax[0] - node.bmin[0];
|
||||
float yLength = node.bmax[1] - node.bmin[1];
|
||||
|
||||
// Sort along the longest axis
|
||||
qsort(
|
||||
triBounds.data() + imin,
|
||||
static_cast<size_t>(numTriBoundsInRange),
|
||||
sizeof(IndexedBounds),
|
||||
(xLength >= yLength) ? compareMinX : compareMinY);
|
||||
|
||||
int isplit = imin + numTriBoundsInRange / 2;
|
||||
|
||||
// Left
|
||||
subdivide(triBounds, imin, isplit, trisPerChunk, curNode, nodes, maxNodes, curTri, outTris, inTris);
|
||||
// Right
|
||||
subdivide(triBounds, isplit, imax, trisPerChunk, curNode, nodes, maxNodes, curTri, outTris, inTris);
|
||||
|
||||
// Negative index means escape.
|
||||
node.triIndex = icur - curNode;
|
||||
}
|
||||
}
|
||||
|
||||
bool checkOverlapRect(const float amin[2], const float amax[2], const float bmin[2], const float bmax[2])
|
||||
{
|
||||
return amin[0] <= bmax[0] && amax[0] >= bmin[0] && amin[1] <= bmax[1] && amax[1] >= bmin[1];
|
||||
}
|
||||
|
||||
bool checkOverlapSegment(const float p[2], const float q[2], const float bmin[2], const float bmax[2])
|
||||
{
|
||||
float tmin = 0;
|
||||
float tmax = 1;
|
||||
float d[]{q[0] - p[0], q[1] - p[1]};
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
static const float EPSILON = 1e-6f;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
void PartitionedMesh::PartitionMesh(const float* verts, const int* tris, int numTris, int trisPerChunk)
|
||||
{
|
||||
// Calculate the XZ bounds of every triangle.
|
||||
std::vector<IndexedBounds> triBounds;
|
||||
triBounds.resize(numTris);
|
||||
for (int triIndex = 0; triIndex < numTris; triIndex++)
|
||||
{
|
||||
const int* tri = &tris[triIndex * 3];
|
||||
IndexedBounds& bound = triBounds[triIndex];
|
||||
bound.index = triIndex;
|
||||
bound.bmin[0] = bound.bmax[0] = verts[tri[0] * 3 + 0];
|
||||
bound.bmin[1] = bound.bmax[1] = verts[tri[0] * 3 + 2];
|
||||
for (int vertIndex = 1; vertIndex < 3; ++vertIndex)
|
||||
{
|
||||
const float x = verts[tri[vertIndex] * 3 + 0];
|
||||
bound.bmin[0] = std::min(x, bound.bmin[0]);
|
||||
bound.bmax[0] = std::max(x, bound.bmax[0]);
|
||||
|
||||
const float z = verts[tri[vertIndex] * 3 + 2];
|
||||
bound.bmin[1] = std::min(z, bound.bmin[1]);
|
||||
bound.bmax[1] = std::max(z, bound.bmax[1]);
|
||||
}
|
||||
}
|
||||
|
||||
// Build tree
|
||||
int numChunks = static_cast<int>(ceilf(static_cast<float>(numTris) / static_cast<float>(trisPerChunk)));
|
||||
nodes.resize(numChunks * 4);
|
||||
this->tris.resize(numTris * 3);
|
||||
int curTri = 0;
|
||||
int curNode = 0;
|
||||
subdivide(triBounds, 0, numTris, trisPerChunk, curNode, nodes.data(), numChunks * 4, curTri, this->tris.data(), tris);
|
||||
nnodes = curNode;
|
||||
|
||||
// Calc max tris per chunk.
|
||||
maxTrisPerChunk = 0;
|
||||
for (auto& node : nodes)
|
||||
{
|
||||
// Skip if it's not a leaf node
|
||||
if (node.triIndex < 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
maxTrisPerChunk = std::max(maxTrisPerChunk, node.numTris);
|
||||
}
|
||||
}
|
||||
|
||||
void PartitionedMesh::GetNodesOverlappingRect(float bmin[2], float bmax[2], std::vector<int>& outNodes) const
|
||||
{
|
||||
// Traverse tree
|
||||
for (int nodeIndex = 0; nodeIndex < this->nnodes;)
|
||||
{
|
||||
const Node* node = &this->nodes[nodeIndex];
|
||||
const bool overlap = checkOverlapRect(bmin, bmax, node->bmin, node->bmax);
|
||||
const bool isLeafNode = node->triIndex >= 0;
|
||||
|
||||
if (isLeafNode && overlap)
|
||||
{
|
||||
outNodes.emplace_back(nodeIndex);
|
||||
}
|
||||
|
||||
if (overlap || isLeafNode)
|
||||
{
|
||||
nodeIndex++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// escape index
|
||||
nodeIndex -= node->triIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PartitionedMesh::GetNodesOverlappingSegment(float start[2], float end[2], std::vector<int>& outNodes) const
|
||||
{
|
||||
// Traverse tree
|
||||
for (int nodeIndex = 0; nodeIndex < this->nnodes;)
|
||||
{
|
||||
const Node* node = &this->nodes[nodeIndex];
|
||||
const bool overlap = checkOverlapSegment(start, end, node->bmin, node->bmax);
|
||||
const bool isLeafNode = node->triIndex >= 0;
|
||||
|
||||
if (isLeafNode && overlap)
|
||||
{
|
||||
outNodes.emplace_back(nodeIndex);
|
||||
}
|
||||
|
||||
if (overlap || isLeafNode)
|
||||
{
|
||||
nodeIndex++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// escape index
|
||||
nodeIndex -= node->triIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
50
src/features/editScene/recast/PartitionedMesh.hpp
Normal file
50
src/features/editScene/recast/PartitionedMesh.hpp
Normal file
@@ -0,0 +1,50 @@
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
/// A spatially-partitioned mesh (k/d tree),
|
||||
/// where each node contains at max trisPerChunk triangles.
|
||||
struct PartitionedMesh
|
||||
{
|
||||
struct Node
|
||||
{
|
||||
// xy bounds
|
||||
float bmin[2];
|
||||
float bmax[2];
|
||||
|
||||
int triIndex;
|
||||
int numTris;
|
||||
};
|
||||
|
||||
std::vector<Node> nodes{};
|
||||
int nnodes = 0;
|
||||
|
||||
std::vector<int> tris{};
|
||||
int maxTrisPerChunk = 0;
|
||||
|
||||
void PartitionMesh(const float* verts, const int* tris, int numTris, int trisPerChunk);
|
||||
|
||||
/// Finds the chunk indices that overlap the input rectangle.
|
||||
void GetNodesOverlappingRect(float bmin[2], float bmax[2], std::vector<int>& outNodes) const;
|
||||
|
||||
/// Returns the chunk indices which overlap the input segment.
|
||||
void GetNodesOverlappingSegment(float start[2], float end[2], std::vector<int>& outNodes) const;
|
||||
};
|
||||
1016
src/features/editScene/recast/TileCacheNavMesh.cpp
Normal file
1016
src/features/editScene/recast/TileCacheNavMesh.cpp
Normal file
File diff suppressed because it is too large
Load Diff
120
src/features/editScene/recast/TileCacheNavMesh.hpp
Normal file
120
src/features/editScene/recast/TileCacheNavMesh.hpp
Normal file
@@ -0,0 +1,120 @@
|
||||
#ifndef EDITSCENE_TILECACHE_NAVMESH_HPP
|
||||
#define EDITSCENE_TILECACHE_NAVMESH_HPP
|
||||
#pragma once
|
||||
|
||||
#include <Ogre.h>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
// Forward declarations
|
||||
struct rcContext;
|
||||
struct dtNavMesh;
|
||||
struct dtNavMeshQuery;
|
||||
struct dtQueryFilter;
|
||||
struct dtTileCache;
|
||||
struct dtTileCacheAlloc;
|
||||
struct dtTileCacheCompressor;
|
||||
struct dtTileCacheMeshProcess;
|
||||
|
||||
class PartitionedMesh;
|
||||
|
||||
/**
|
||||
* Self-contained DetourTileCache navmesh builder and query engine.
|
||||
*
|
||||
* Builds a tiled navmesh from Ogre::Entity geometry. Supports partial
|
||||
* tile rebuilds when geometry in a specific area changes.
|
||||
*/
|
||||
class TileCacheNavMesh {
|
||||
public:
|
||||
struct BuildParams {
|
||||
float cellSize = 0.3f;
|
||||
float cellHeight = 0.2f;
|
||||
float agentHeight = 2.5f;
|
||||
float agentRadius = 0.5f;
|
||||
float agentMaxClimb = 1.0f;
|
||||
float agentMaxSlope = 20.0f;
|
||||
float edgeMaxLen = 12.0f;
|
||||
float edgeMaxError = 1.3f;
|
||||
float regionMinSize = 20.0f;
|
||||
float regionMergeSize = 20.0f;
|
||||
int vertsPerPoly = 6;
|
||||
float detailSampleDist = 6.0f;
|
||||
float detailSampleMaxError = 1.0f;
|
||||
int tileSize = 48; // voxels per tile
|
||||
};
|
||||
|
||||
TileCacheNavMesh(Ogre::SceneManager *sceneMgr,
|
||||
const BuildParams ¶ms);
|
||||
~TileCacheNavMesh();
|
||||
|
||||
TileCacheNavMesh(const TileCacheNavMesh &) = delete;
|
||||
TileCacheNavMesh &operator=(const TileCacheNavMesh &) = delete;
|
||||
|
||||
// --- Build ---
|
||||
bool build(const std::vector<Ogre::Entity *> &entities);
|
||||
bool isBuilt() const { return m_navMesh != nullptr; }
|
||||
|
||||
// --- Partial rebuild ---
|
||||
void rebuildTilesInArea(const Ogre::AxisAlignedBox &area);
|
||||
|
||||
// --- Queries ---
|
||||
bool findPath(const Ogre::Vector3 &start,
|
||||
const Ogre::Vector3 &end,
|
||||
std::vector<Ogre::Vector3> &path);
|
||||
bool findNearestPoint(const Ogre::Vector3 &pos,
|
||||
Ogre::Vector3 &out);
|
||||
Ogre::Vector3 getRandomPoint();
|
||||
|
||||
// --- Debug ---
|
||||
void drawNavMesh();
|
||||
void clearDebugDraw();
|
||||
|
||||
private:
|
||||
Ogre::SceneManager *m_sceneMgr;
|
||||
BuildParams m_params;
|
||||
|
||||
// Input geometry
|
||||
std::vector<float> m_verts;
|
||||
std::vector<int> m_tris;
|
||||
float m_bmin[3];
|
||||
float m_bmax[3];
|
||||
std::unique_ptr<PartitionedMesh> m_partitionedMesh;
|
||||
|
||||
// Tile cache
|
||||
dtTileCacheAlloc *m_talloc;
|
||||
dtTileCacheCompressor *m_tcomp;
|
||||
dtTileCacheMeshProcess *m_tmproc;
|
||||
dtTileCache *m_tileCache;
|
||||
|
||||
// Detour output
|
||||
dtNavMesh *m_navMesh;
|
||||
dtNavMeshQuery *m_navQuery;
|
||||
dtQueryFilter *m_filter;
|
||||
|
||||
// Tile grid dimensions
|
||||
int m_tw = 0;
|
||||
int m_th = 0;
|
||||
float m_cellSize = 0;
|
||||
|
||||
// Recast context
|
||||
rcContext *m_ctx;
|
||||
|
||||
// Debug draw
|
||||
Ogre::ManualObject *m_debugMO;
|
||||
Ogre::SceneNode *m_debugNode;
|
||||
|
||||
// Extents for nearest-poly searches
|
||||
float m_extents[3];
|
||||
|
||||
// Internal helpers
|
||||
bool extractMeshData(const std::vector<Ogre::Entity *> &entities);
|
||||
bool initTileCache();
|
||||
int rasterizeTileLayers(int tx, int ty,
|
||||
struct TileCacheData *tiles,
|
||||
int maxTiles);
|
||||
bool buildTile(int tx, int ty);
|
||||
bool removeTile(int tx, int ty);
|
||||
void getTileCoords(const float *pos, int &tx, int &ty);
|
||||
};
|
||||
|
||||
#endif // EDITSCENE_TILECACHE_NAVMESH_HPP
|
||||
556
src/features/editScene/recast/fastlz.c
Normal file
556
src/features/editScene/recast/fastlz.c
Normal file
@@ -0,0 +1,556 @@
|
||||
/*
|
||||
FastLZ - lightning-fast lossless compression library
|
||||
|
||||
Copyright (C) 2007 Ariya Hidayat (ariya@kde.org)
|
||||
Copyright (C) 2006 Ariya Hidayat (ariya@kde.org)
|
||||
Copyright (C) 2005 Ariya Hidayat (ariya@kde.org)
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
#if !defined(FASTLZ__COMPRESSOR) && !defined(FASTLZ_DECOMPRESSOR)
|
||||
|
||||
/*
|
||||
* Always check for bound when decompressing.
|
||||
* Generally it is best to leave it defined.
|
||||
*/
|
||||
#define FASTLZ_SAFE
|
||||
|
||||
/*
|
||||
* Give hints to the compiler for branch prediction optimization.
|
||||
*/
|
||||
#if defined(__GNUC__) && (__GNUC__ > 2)
|
||||
#define FASTLZ_EXPECT_CONDITIONAL(c) (__builtin_expect((c), 1))
|
||||
#define FASTLZ_UNEXPECT_CONDITIONAL(c) (__builtin_expect((c), 0))
|
||||
#else
|
||||
#define FASTLZ_EXPECT_CONDITIONAL(c) (c)
|
||||
#define FASTLZ_UNEXPECT_CONDITIONAL(c) (c)
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Use inlined functions for supported systems.
|
||||
*/
|
||||
#if defined(__GNUC__) || defined(__DMC__) || defined(__POCC__) || defined(__WATCOMC__) || defined(__SUNPRO_C)
|
||||
#define FASTLZ_INLINE inline
|
||||
#elif defined(__BORLANDC__) || defined(_MSC_VER) || defined(__LCC__)
|
||||
#define FASTLZ_INLINE __inline
|
||||
#else
|
||||
#define FASTLZ_INLINE
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Prevent accessing more than 8-bit at once, except on x86 architectures.
|
||||
*/
|
||||
#if !defined(FASTLZ_STRICT_ALIGN)
|
||||
#define FASTLZ_STRICT_ALIGN
|
||||
#if defined(__i386__) || defined(__386) /* GNU C, Sun Studio */
|
||||
#undef FASTLZ_STRICT_ALIGN
|
||||
#elif defined(__i486__) || defined(__i586__) || defined(__i686__) /* GNU C */
|
||||
#undef FASTLZ_STRICT_ALIGN
|
||||
#elif defined(_M_IX86) /* Intel, MSVC */
|
||||
#undef FASTLZ_STRICT_ALIGN
|
||||
#elif defined(__386)
|
||||
#undef FASTLZ_STRICT_ALIGN
|
||||
#elif defined(_X86_) /* MinGW */
|
||||
#undef FASTLZ_STRICT_ALIGN
|
||||
#elif defined(__I86__) /* Digital Mars */
|
||||
#undef FASTLZ_STRICT_ALIGN
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/*
|
||||
* FIXME: use preprocessor magic to set this on different platforms!
|
||||
*/
|
||||
typedef unsigned char flzuint8;
|
||||
typedef unsigned short flzuint16;
|
||||
typedef unsigned int flzuint32;
|
||||
|
||||
/* Disable "conversion from A to B, possible loss of data" warning when using MSVC */
|
||||
#if defined(_MSC_VER)
|
||||
#pragma warning(disable: 4244)
|
||||
#endif
|
||||
|
||||
/* prototypes */
|
||||
int fastlz_compress(const void* input, int length, void* output);
|
||||
int fastlz_compress_level(int level, const void* input, int length, void* output);
|
||||
int fastlz_decompress(const void* input, int length, void* output, int maxout);
|
||||
|
||||
#define MAX_COPY 32
|
||||
#define MAX_LEN 264 /* 256 + 8 */
|
||||
#define MAX_DISTANCE 8192
|
||||
|
||||
#if !defined(FASTLZ_STRICT_ALIGN)
|
||||
#define FASTLZ_READU16(p) *((const flzuint16*)(p))
|
||||
#else
|
||||
#define FASTLZ_READU16(p) ((p)[0] | (p)[1]<<8)
|
||||
#endif
|
||||
|
||||
#define HASH_LOG 13
|
||||
#define HASH_SIZE (1<< HASH_LOG)
|
||||
#define HASH_MASK (HASH_SIZE-1)
|
||||
#define HASH_FUNCTION(v,p) { v = FASTLZ_READU16(p); v ^= FASTLZ_READU16(p+1)^(v>>(16-HASH_LOG));v &= HASH_MASK; }
|
||||
|
||||
#undef FASTLZ_LEVEL
|
||||
#define FASTLZ_LEVEL 1
|
||||
|
||||
#undef FASTLZ_COMPRESSOR
|
||||
#undef FASTLZ_DECOMPRESSOR
|
||||
#define FASTLZ_COMPRESSOR fastlz1_compress
|
||||
#define FASTLZ_DECOMPRESSOR fastlz1_decompress
|
||||
static FASTLZ_INLINE int FASTLZ_COMPRESSOR(const void* input, int length, void* output);
|
||||
static FASTLZ_INLINE int FASTLZ_DECOMPRESSOR(const void* input, int length, void* output, int maxout);
|
||||
#include "fastlz.c"
|
||||
|
||||
#undef FASTLZ_LEVEL
|
||||
#define FASTLZ_LEVEL 2
|
||||
|
||||
#undef MAX_DISTANCE
|
||||
#define MAX_DISTANCE 8191
|
||||
#define MAX_FARDISTANCE (65535+MAX_DISTANCE-1)
|
||||
|
||||
#undef FASTLZ_COMPRESSOR
|
||||
#undef FASTLZ_DECOMPRESSOR
|
||||
#define FASTLZ_COMPRESSOR fastlz2_compress
|
||||
#define FASTLZ_DECOMPRESSOR fastlz2_decompress
|
||||
static FASTLZ_INLINE int FASTLZ_COMPRESSOR(const void* input, int length, void* output);
|
||||
static FASTLZ_INLINE int FASTLZ_DECOMPRESSOR(const void* input, int length, void* output, int maxout);
|
||||
#include "fastlz.c"
|
||||
|
||||
int fastlz_compress(const void* input, int length, void* output)
|
||||
{
|
||||
/* for short block, choose fastlz1 */
|
||||
if(length < 65536)
|
||||
return fastlz1_compress(input, length, output);
|
||||
|
||||
/* else... */
|
||||
return fastlz2_compress(input, length, output);
|
||||
}
|
||||
|
||||
int fastlz_decompress(const void* input, int length, void* output, int maxout)
|
||||
{
|
||||
/* magic identifier for compression level */
|
||||
int level = ((*(const flzuint8*)input) >> 5) + 1;
|
||||
|
||||
if(level == 1)
|
||||
return fastlz1_decompress(input, length, output, maxout);
|
||||
if(level == 2)
|
||||
return fastlz2_decompress(input, length, output, maxout);
|
||||
|
||||
/* unknown level, trigger error */
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fastlz_compress_level(int level, const void* input, int length, void* output)
|
||||
{
|
||||
if(level == 1)
|
||||
return fastlz1_compress(input, length, output);
|
||||
if(level == 2)
|
||||
return fastlz2_compress(input, length, output);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else /* !defined(FASTLZ_COMPRESSOR) && !defined(FASTLZ_DECOMPRESSOR) */
|
||||
|
||||
static FASTLZ_INLINE int FASTLZ_COMPRESSOR(const void* input, int length, void* output)
|
||||
{
|
||||
const flzuint8* ip = (const flzuint8*) input;
|
||||
const flzuint8* ip_bound = ip + length - 2;
|
||||
const flzuint8* ip_limit = ip + length - 12;
|
||||
flzuint8* op = (flzuint8*) output;
|
||||
|
||||
const flzuint8* htab[HASH_SIZE];
|
||||
const flzuint8** hslot;
|
||||
flzuint32 hval;
|
||||
|
||||
flzuint32 copy;
|
||||
|
||||
/* sanity check */
|
||||
if(FASTLZ_UNEXPECT_CONDITIONAL(length < 4))
|
||||
{
|
||||
if(length)
|
||||
{
|
||||
/* create literal copy only */
|
||||
*op++ = length-1;
|
||||
ip_bound++;
|
||||
while(ip <= ip_bound)
|
||||
*op++ = *ip++;
|
||||
return length+1;
|
||||
}
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* initializes hash table */
|
||||
for (hslot = htab; hslot < htab + HASH_SIZE; hslot++)
|
||||
*hslot = ip;
|
||||
|
||||
/* we start with literal copy */
|
||||
copy = 2;
|
||||
*op++ = MAX_COPY-1;
|
||||
*op++ = *ip++;
|
||||
*op++ = *ip++;
|
||||
|
||||
/* main loop */
|
||||
while(FASTLZ_EXPECT_CONDITIONAL(ip < ip_limit))
|
||||
{
|
||||
const flzuint8* ref;
|
||||
flzuint32 distance;
|
||||
|
||||
/* minimum match length */
|
||||
flzuint32 len = 3;
|
||||
|
||||
/* comparison starting-point */
|
||||
const flzuint8* anchor = ip;
|
||||
|
||||
/* check for a run */
|
||||
#if FASTLZ_LEVEL==2
|
||||
if(ip[0] == ip[-1] && FASTLZ_READU16(ip-1)==FASTLZ_READU16(ip+1))
|
||||
{
|
||||
distance = 1;
|
||||
ip += 3;
|
||||
ref = anchor - 1 + 3;
|
||||
goto match;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* find potential match */
|
||||
HASH_FUNCTION(hval,ip);
|
||||
hslot = htab + hval;
|
||||
ref = htab[hval];
|
||||
|
||||
/* calculate distance to the match */
|
||||
distance = anchor - ref;
|
||||
|
||||
/* update hash table */
|
||||
*hslot = anchor;
|
||||
|
||||
/* is this a match? check the first 3 bytes */
|
||||
if(distance==0 ||
|
||||
#if FASTLZ_LEVEL==1
|
||||
(distance >= MAX_DISTANCE) ||
|
||||
#else
|
||||
(distance >= MAX_FARDISTANCE) ||
|
||||
#endif
|
||||
*ref++ != *ip++ || *ref++!=*ip++ || *ref++!=*ip++)
|
||||
goto literal;
|
||||
|
||||
#if FASTLZ_LEVEL==2
|
||||
/* far, needs at least 5-byte match */
|
||||
if(distance >= MAX_DISTANCE)
|
||||
{
|
||||
if(*ip++ != *ref++ || *ip++!= *ref++)
|
||||
goto literal;
|
||||
len += 2;
|
||||
}
|
||||
|
||||
match:
|
||||
#endif
|
||||
|
||||
/* last matched byte */
|
||||
ip = anchor + len;
|
||||
|
||||
/* distance is biased */
|
||||
distance--;
|
||||
|
||||
if(!distance)
|
||||
{
|
||||
/* zero distance means a run */
|
||||
flzuint8 x = ip[-1];
|
||||
while(ip < ip_bound)
|
||||
if(*ref++ != x) break; else ip++;
|
||||
}
|
||||
else
|
||||
for(;;)
|
||||
{
|
||||
/* safe because the outer check against ip limit */
|
||||
if(*ref++ != *ip++) break;
|
||||
if(*ref++ != *ip++) break;
|
||||
if(*ref++ != *ip++) break;
|
||||
if(*ref++ != *ip++) break;
|
||||
if(*ref++ != *ip++) break;
|
||||
if(*ref++ != *ip++) break;
|
||||
if(*ref++ != *ip++) break;
|
||||
if(*ref++ != *ip++) break;
|
||||
while(ip < ip_bound)
|
||||
if(*ref++ != *ip++) break;
|
||||
break;
|
||||
}
|
||||
|
||||
/* if we have copied something, adjust the copy count */
|
||||
if(copy)
|
||||
/* copy is biased, '0' means 1 byte copy */
|
||||
*(op-copy-1) = copy-1;
|
||||
else
|
||||
/* back, to overwrite the copy count */
|
||||
op--;
|
||||
|
||||
/* reset literal counter */
|
||||
copy = 0;
|
||||
|
||||
/* length is biased, '1' means a match of 3 bytes */
|
||||
ip -= 3;
|
||||
len = ip - anchor;
|
||||
|
||||
/* encode the match */
|
||||
#if FASTLZ_LEVEL==2
|
||||
if(distance < MAX_DISTANCE)
|
||||
{
|
||||
if(len < 7)
|
||||
{
|
||||
*op++ = (len << 5) + (distance >> 8);
|
||||
*op++ = (distance & 255);
|
||||
}
|
||||
else
|
||||
{
|
||||
*op++ = (7 << 5) + (distance >> 8);
|
||||
for(len-=7; len >= 255; len-= 255)
|
||||
*op++ = 255;
|
||||
*op++ = len;
|
||||
*op++ = (distance & 255);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* far away, but not yet in the another galaxy... */
|
||||
if(len < 7)
|
||||
{
|
||||
distance -= MAX_DISTANCE;
|
||||
*op++ = (len << 5) + 31;
|
||||
*op++ = 255;
|
||||
*op++ = distance >> 8;
|
||||
*op++ = distance & 255;
|
||||
}
|
||||
else
|
||||
{
|
||||
distance -= MAX_DISTANCE;
|
||||
*op++ = (7 << 5) + 31;
|
||||
for(len-=7; len >= 255; len-= 255)
|
||||
*op++ = 255;
|
||||
*op++ = len;
|
||||
*op++ = 255;
|
||||
*op++ = distance >> 8;
|
||||
*op++ = distance & 255;
|
||||
}
|
||||
}
|
||||
#else
|
||||
|
||||
if(FASTLZ_UNEXPECT_CONDITIONAL(len > MAX_LEN-2))
|
||||
while(len > MAX_LEN-2)
|
||||
{
|
||||
*op++ = (7 << 5) + (distance >> 8);
|
||||
*op++ = MAX_LEN - 2 - 7 -2;
|
||||
*op++ = (distance & 255);
|
||||
len -= MAX_LEN-2;
|
||||
}
|
||||
|
||||
if(len < 7)
|
||||
{
|
||||
*op++ = (len << 5) + (distance >> 8);
|
||||
*op++ = (distance & 255);
|
||||
}
|
||||
else
|
||||
{
|
||||
*op++ = (7 << 5) + (distance >> 8);
|
||||
*op++ = len - 7;
|
||||
*op++ = (distance & 255);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* update the hash at match boundary */
|
||||
HASH_FUNCTION(hval,ip);
|
||||
htab[hval] = ip++;
|
||||
HASH_FUNCTION(hval,ip);
|
||||
htab[hval] = ip++;
|
||||
|
||||
/* assuming literal copy */
|
||||
*op++ = MAX_COPY-1;
|
||||
|
||||
continue;
|
||||
|
||||
literal:
|
||||
*op++ = *anchor++;
|
||||
ip = anchor;
|
||||
copy++;
|
||||
if(FASTLZ_UNEXPECT_CONDITIONAL(copy == MAX_COPY))
|
||||
{
|
||||
copy = 0;
|
||||
*op++ = MAX_COPY-1;
|
||||
}
|
||||
}
|
||||
|
||||
/* left-over as literal copy */
|
||||
ip_bound++;
|
||||
while(ip <= ip_bound)
|
||||
{
|
||||
*op++ = *ip++;
|
||||
copy++;
|
||||
if(copy == MAX_COPY)
|
||||
{
|
||||
copy = 0;
|
||||
*op++ = MAX_COPY-1;
|
||||
}
|
||||
}
|
||||
|
||||
/* if we have copied something, adjust the copy length */
|
||||
if(copy)
|
||||
*(op-copy-1) = copy-1;
|
||||
else
|
||||
op--;
|
||||
|
||||
#if FASTLZ_LEVEL==2
|
||||
/* marker for fastlz2 */
|
||||
*(flzuint8*)output |= (1 << 5);
|
||||
#endif
|
||||
|
||||
return op - (flzuint8*)output;
|
||||
}
|
||||
|
||||
static FASTLZ_INLINE int FASTLZ_DECOMPRESSOR(const void* input, int length, void* output, int maxout)
|
||||
{
|
||||
const flzuint8* ip = (const flzuint8*) input;
|
||||
const flzuint8* ip_limit = ip + length;
|
||||
flzuint8* op = (flzuint8*) output;
|
||||
flzuint8* op_limit = op + maxout;
|
||||
flzuint32 ctrl = (*ip++) & 31;
|
||||
int loop = 1;
|
||||
|
||||
do
|
||||
{
|
||||
const flzuint8* ref = op;
|
||||
flzuint32 len = ctrl >> 5;
|
||||
flzuint32 ofs = (ctrl & 31) << 8;
|
||||
|
||||
if(ctrl >= 32)
|
||||
{
|
||||
#if FASTLZ_LEVEL==2
|
||||
flzuint8 code;
|
||||
#endif
|
||||
len--;
|
||||
ref -= ofs;
|
||||
if (len == 7-1)
|
||||
#if FASTLZ_LEVEL==1
|
||||
len += *ip++;
|
||||
ref -= *ip++;
|
||||
#else
|
||||
do
|
||||
{
|
||||
code = *ip++;
|
||||
len += code;
|
||||
} while (code==255);
|
||||
code = *ip++;
|
||||
ref -= code;
|
||||
|
||||
/* match from 16-bit distance */
|
||||
if(FASTLZ_UNEXPECT_CONDITIONAL(code==255))
|
||||
if(FASTLZ_EXPECT_CONDITIONAL(ofs==(31 << 8)))
|
||||
{
|
||||
ofs = (*ip++) << 8;
|
||||
ofs += *ip++;
|
||||
ref = op - ofs - MAX_DISTANCE;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef FASTLZ_SAFE
|
||||
if (FASTLZ_UNEXPECT_CONDITIONAL(op + len + 3 > op_limit))
|
||||
return 0;
|
||||
|
||||
if (FASTLZ_UNEXPECT_CONDITIONAL(ref-1 < (flzuint8 *)output))
|
||||
return 0;
|
||||
#endif
|
||||
|
||||
if(FASTLZ_EXPECT_CONDITIONAL(ip < ip_limit))
|
||||
ctrl = *ip++;
|
||||
else
|
||||
loop = 0;
|
||||
|
||||
if(ref == op)
|
||||
{
|
||||
/* optimize copy for a run */
|
||||
flzuint8 b = ref[-1];
|
||||
*op++ = b;
|
||||
*op++ = b;
|
||||
*op++ = b;
|
||||
for(; len; --len)
|
||||
*op++ = b;
|
||||
}
|
||||
else
|
||||
{
|
||||
#if !defined(FASTLZ_STRICT_ALIGN)
|
||||
const flzuint16* p;
|
||||
flzuint16* q;
|
||||
#endif
|
||||
/* copy from reference */
|
||||
ref--;
|
||||
*op++ = *ref++;
|
||||
*op++ = *ref++;
|
||||
*op++ = *ref++;
|
||||
|
||||
#if !defined(FASTLZ_STRICT_ALIGN)
|
||||
/* copy a byte, so that now it's word aligned */
|
||||
if(len & 1)
|
||||
{
|
||||
*op++ = *ref++;
|
||||
len--;
|
||||
}
|
||||
|
||||
/* copy 16-bit at once */
|
||||
q = (flzuint16*) op;
|
||||
op += len;
|
||||
p = (const flzuint16*) ref;
|
||||
for(len>>=1; len > 4; len-=4)
|
||||
{
|
||||
*q++ = *p++;
|
||||
*q++ = *p++;
|
||||
*q++ = *p++;
|
||||
*q++ = *p++;
|
||||
}
|
||||
for(; len; --len)
|
||||
*q++ = *p++;
|
||||
#else
|
||||
for(; len; --len)
|
||||
*op++ = *ref++;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ctrl++;
|
||||
#ifdef FASTLZ_SAFE
|
||||
if (FASTLZ_UNEXPECT_CONDITIONAL(op + ctrl > op_limit))
|
||||
return 0;
|
||||
if (FASTLZ_UNEXPECT_CONDITIONAL(ip + ctrl > ip_limit))
|
||||
return 0;
|
||||
#endif
|
||||
|
||||
*op++ = *ip++;
|
||||
for(--ctrl; ctrl; ctrl--)
|
||||
*op++ = *ip++;
|
||||
|
||||
loop = FASTLZ_EXPECT_CONDITIONAL(ip < ip_limit);
|
||||
if(loop)
|
||||
ctrl = *ip++;
|
||||
}
|
||||
}
|
||||
while(FASTLZ_EXPECT_CONDITIONAL(loop));
|
||||
|
||||
return op - (flzuint8*)output;
|
||||
}
|
||||
|
||||
#endif /* !defined(FASTLZ_COMPRESSOR) && !defined(FASTLZ_DECOMPRESSOR) */
|
||||
100
src/features/editScene/recast/fastlz.h
Normal file
100
src/features/editScene/recast/fastlz.h
Normal file
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
FastLZ - lightning-fast lossless compression library
|
||||
|
||||
Copyright (C) 2007 Ariya Hidayat (ariya@kde.org)
|
||||
Copyright (C) 2006 Ariya Hidayat (ariya@kde.org)
|
||||
Copyright (C) 2005 Ariya Hidayat (ariya@kde.org)
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
#ifndef FASTLZ_H
|
||||
#define FASTLZ_H
|
||||
|
||||
#define FASTLZ_VERSION 0x000100
|
||||
|
||||
#define FASTLZ_VERSION_MAJOR 0
|
||||
#define FASTLZ_VERSION_MINOR 0
|
||||
#define FASTLZ_VERSION_REVISION 0
|
||||
|
||||
#define FASTLZ_VERSION_STRING "0.1.0"
|
||||
|
||||
#if defined (__cplusplus)
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
Compress a block of data in the input buffer and returns the size of
|
||||
compressed block. The size of input buffer is specified by length. The
|
||||
minimum input buffer size is 16.
|
||||
|
||||
The output buffer must be at least 5% larger than the input buffer
|
||||
and can not be smaller than 66 bytes.
|
||||
|
||||
If the input is not compressible, the return value might be larger than
|
||||
length (input buffer size).
|
||||
|
||||
The input buffer and the output buffer can not overlap.
|
||||
*/
|
||||
|
||||
int fastlz_compress(const void* input, int length, void* output);
|
||||
|
||||
/**
|
||||
Decompress a block of compressed data and returns the size of the
|
||||
decompressed block. If error occurs, e.g. the compressed data is
|
||||
corrupted or the output buffer is not large enough, then 0 (zero)
|
||||
will be returned instead.
|
||||
|
||||
The input buffer and the output buffer can not overlap.
|
||||
|
||||
Decompression is memory safe and guaranteed not to write the output buffer
|
||||
more than what is specified in maxout.
|
||||
*/
|
||||
|
||||
int fastlz_decompress(const void* input, int length, void* output, int maxout);
|
||||
|
||||
/**
|
||||
Compress a block of data in the input buffer and returns the size of
|
||||
compressed block. The size of input buffer is specified by length. The
|
||||
minimum input buffer size is 16.
|
||||
|
||||
The output buffer must be at least 5% larger than the input buffer
|
||||
and can not be smaller than 66 bytes.
|
||||
|
||||
If the input is not compressible, the return value might be larger than
|
||||
length (input buffer size).
|
||||
|
||||
The input buffer and the output buffer can not overlap.
|
||||
|
||||
Compression level can be specified in parameter level. At the moment,
|
||||
only level 1 and level 2 are supported.
|
||||
Level 1 is the fastest compression and generally useful for short data.
|
||||
Level 2 is slightly slower but it gives better compression ratio.
|
||||
|
||||
Note that the compressed data, regardless of the level, can always be
|
||||
decompressed using the function fastlz_decompress above.
|
||||
*/
|
||||
|
||||
int fastlz_compress_level(int level, const void* input, int length, void* output);
|
||||
|
||||
#if defined (__cplusplus)
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* FASTLZ_H */
|
||||
86
src/features/editScene/recastnavigation/.clang-format
Normal file
86
src/features/editScene/recastnavigation/.clang-format
Normal file
@@ -0,0 +1,86 @@
|
||||
---
|
||||
# clang-format settings
|
||||
Language: Cpp
|
||||
BasedOnStyle: LLVM
|
||||
Standard: Auto
|
||||
ForEachMacros: [ for ]
|
||||
|
||||
# indentation
|
||||
TabWidth: 4
|
||||
IndentWidth: 4
|
||||
UseTab: AlignWithSpaces
|
||||
AccessModifierOffset: -4
|
||||
ContinuationIndentWidth: 4
|
||||
IndentCaseLabels: false
|
||||
|
||||
# whitespace
|
||||
SpaceAfterCStyleCast: false
|
||||
SpacesBeforeTrailingComments: 2
|
||||
KeepEmptyLines:
|
||||
AtEndOfFile: false
|
||||
AtStartOfBlock: false
|
||||
AtStartOfFile: false
|
||||
|
||||
# line breaks
|
||||
AllowShortFunctionsOnASingleLine: Inline
|
||||
AllowShortBlocksOnASingleLine: Empty
|
||||
AllowShortIfStatementsOnASingleLine: Never
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowAllParametersOfDeclarationOnNextLine: false
|
||||
AlwaysBreakBeforeMultilineStrings: true
|
||||
AlwaysBreakTemplateDeclarations: true
|
||||
BreakAfterReturnType: Automatic
|
||||
PenaltyReturnTypeOnItsOwnLine: 999999
|
||||
|
||||
# constructor initializer lists
|
||||
PackConstructorInitializers: CurrentLine
|
||||
BreakConstructorInitializers: BeforeComma
|
||||
ConstructorInitializerIndentWidth: 0
|
||||
|
||||
# function calls
|
||||
BinPackArguments: false
|
||||
AllowAllArgumentsOnNextLine: false
|
||||
|
||||
# function declarations
|
||||
BinPackParameters: false
|
||||
AlignAfterOpenBracket: AlwaysBreak
|
||||
BreakBeforeBraces: Allman
|
||||
|
||||
# style
|
||||
InsertBraces: true
|
||||
PointerAlignment: Left
|
||||
CompactNamespaces: true
|
||||
ColumnLimit: 128
|
||||
AlignEscapedNewlines: LeftWithLastLine
|
||||
AlignArrayOfStructures: Left
|
||||
FixNamespaceComments: false
|
||||
|
||||
# includes & preprocessor
|
||||
IncludeBlocks: Regroup
|
||||
IncludeCategories:
|
||||
- Regex: '.*(PCH).*'
|
||||
Priority: -1
|
||||
- Regex: '".*"'
|
||||
Priority: 1
|
||||
- Regex: '^<.*\.(h)>'
|
||||
Priority: 3
|
||||
- Regex: '^<.*>'
|
||||
Priority: 4
|
||||
IncludeIsMainRegex: '([-_](test|unittest))?$'
|
||||
IndentPPDirectives: AfterHash
|
||||
|
||||
# Hints for detecting supported languages code blocks in raw strings
|
||||
RawStringFormats:
|
||||
- Language: Cpp
|
||||
Delimiters:
|
||||
- cc
|
||||
- CC
|
||||
- cpp
|
||||
- Cpp
|
||||
- CPP
|
||||
- 'c++'
|
||||
- 'C++'
|
||||
CanonicalDelimiter: ''
|
||||
BasedOnStyle: google
|
||||
...
|
||||
3
src/features/editScene/recastnavigation/.clang-tidy
Normal file
3
src/features/editScene/recastnavigation/.clang-tidy
Normal file
@@ -0,0 +1,3 @@
|
||||
Checks: 'clang-analyzer-*'
|
||||
WarningsAsErrors: '*'
|
||||
HeaderFilterRegex: '.*'
|
||||
12
src/features/editScene/recastnavigation/.editorconfig
Normal file
12
src/features/editScene/recastnavigation/.editorconfig
Normal file
@@ -0,0 +1,12 @@
|
||||
# editorconfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_size = 4
|
||||
indent_style = tab
|
||||
|
||||
[*.yml]
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
11
src/features/editScene/recastnavigation/.github/actions/setup-sdl2-linux/action.yaml
vendored
Normal file
11
src/features/editScene/recastnavigation/.github/actions/setup-sdl2-linux/action.yaml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
name: 'Setup SDL2 on Linux'
|
||||
description: 'Installs SDL2 and OpenGL development libraries for Linux builds'
|
||||
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Install SDL2 and OpenGL
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libgl1-mesa-dev libglu1-mesa-dev libsdl2-dev
|
||||
16
src/features/editScene/recastnavigation/.github/actions/setup-sdl2-macos/action.yaml
vendored
Normal file
16
src/features/editScene/recastnavigation/.github/actions/setup-sdl2-macos/action.yaml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
name: 'Setup SDL2 on macOS'
|
||||
description: 'Downloads and installs SDL2 framework for macOS builds'
|
||||
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Download & install SDL
|
||||
shell: bash
|
||||
run: |
|
||||
curl -L -o SDL2.dmg https://github.com/libsdl-org/SDL/releases/download/release-2.32.10/SDL2-2.32.10.dmg
|
||||
hdiutil attach SDL2.dmg
|
||||
cp -r /Volumes/SDL2/SDL2.framework ${{github.workspace}}/RecastDemo/Bin/SDL2.framework
|
||||
hdiutil detach /Volumes/SDL2
|
||||
rm SDL2.dmg
|
||||
# Create symlink so <SDL2/SDL.h> style includes work (used internally by SDL headers)
|
||||
ln -s SDL2.framework/Headers ${{github.workspace}}/RecastDemo/Bin/SDL2
|
||||
14
src/features/editScene/recastnavigation/.github/actions/setup-sdl2-windows/action.yaml
vendored
Normal file
14
src/features/editScene/recastnavigation/.github/actions/setup-sdl2-windows/action.yaml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
name: 'Setup SDL2 on Windows'
|
||||
description: 'Downloads and installs SDL2 development libraries for Windows builds'
|
||||
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Download & install SDL2
|
||||
shell: pwsh
|
||||
run: |
|
||||
$sdlZip = "${{github.workspace}}/RecastDemo/Contrib/SDL.zip"
|
||||
(New-Object System.Net.WebClient).DownloadFile("https://github.com/libsdl-org/SDL/releases/download/release-2.32.10/SDL2-devel-2.32.10-VC.zip", $sdlZip)
|
||||
Expand-Archive -Path $sdlZip -DestinationPath "${{github.workspace}}/RecastDemo/Contrib"
|
||||
Rename-Item -Path "${{github.workspace}}/RecastDemo/Contrib/SDL2-2.32.10" -NewName "SDL"
|
||||
Remove-Item $sdlZip
|
||||
193
src/features/editScene/recastnavigation/.github/workflows/Build.yaml
vendored
Normal file
193
src/features/editScene/recastnavigation/.github/workflows/Build.yaml
vendored
Normal file
@@ -0,0 +1,193 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "**" ]
|
||||
pull_request:
|
||||
branches: [ "**" ]
|
||||
|
||||
jobs:
|
||||
macOS-premake:
|
||||
strategy:
|
||||
matrix:
|
||||
conf:
|
||||
- Debug
|
||||
- Release
|
||||
|
||||
runs-on: macos-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup SDL2
|
||||
uses: ./.github/actions/setup-sdl2-macos
|
||||
|
||||
- name: Download & install premake
|
||||
working-directory: RecastDemo
|
||||
run: |
|
||||
curl -L -o premake.tar.gz https://github.com/premake/premake-core/releases/download/v5.0.0-beta8/premake-5.0.0-beta8-macosx.tar.gz
|
||||
tar -xzf premake.tar.gz
|
||||
rm premake.tar.gz
|
||||
chmod 777 ./premake5
|
||||
|
||||
- name: Run premake
|
||||
working-directory: RecastDemo
|
||||
run: ./premake5 xcode4
|
||||
|
||||
- name: Build With Xcode
|
||||
working-directory: RecastDemo/Build/xcode4/
|
||||
run: xcodebuild -scheme RecastDemo -configuration ${{matrix.conf}} -project RecastDemo.xcodeproj build
|
||||
|
||||
- name: Build Unit Tests With Xcode
|
||||
working-directory: RecastDemo/Build/xcode4/
|
||||
run: xcodebuild -scheme Tests -configuration ${{matrix.conf}} -project Tests.xcodeproj build
|
||||
|
||||
macos-cmake:
|
||||
strategy:
|
||||
matrix:
|
||||
conf:
|
||||
- Debug
|
||||
- Release
|
||||
|
||||
runs-on: macos-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup SDL2
|
||||
uses: ./.github/actions/setup-sdl2-macos
|
||||
|
||||
- name: Configure CMake
|
||||
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{matrix.conf}}
|
||||
|
||||
- name: Build
|
||||
run: cmake --build ${{github.workspace}}/build --config ${{matrix.conf}}
|
||||
|
||||
linux-premake:
|
||||
strategy:
|
||||
matrix:
|
||||
conf:
|
||||
- debug
|
||||
- release
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup SDL2
|
||||
uses: ./.github/actions/setup-sdl2-linux
|
||||
|
||||
- name: Install clang
|
||||
run: |
|
||||
sudo apt-get install -y clang
|
||||
clang --version
|
||||
|
||||
- name: Download & Install premake
|
||||
working-directory: RecastDemo
|
||||
run: |
|
||||
curl -L -o premake.tar.gz https://github.com/premake/premake-core/releases/download/v5.0.0-beta8/premake-5.0.0-beta8-linux.tar.gz
|
||||
tar -xzf premake.tar.gz
|
||||
rm premake.tar.gz
|
||||
chmod 777 ./premake5
|
||||
|
||||
- name: Run premake
|
||||
working-directory: RecastDemo
|
||||
run: ./premake5 --cc=clang gmake
|
||||
|
||||
- name: Build
|
||||
working-directory: RecastDemo/Build/gmake
|
||||
run: make config=${{matrix.conf}} verbose=true
|
||||
|
||||
linux-cmake:
|
||||
strategy:
|
||||
matrix:
|
||||
conf:
|
||||
- Debug
|
||||
- Release
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup SDL2
|
||||
uses: ./.github/actions/setup-sdl2-linux
|
||||
|
||||
- name: Install clang
|
||||
run: |
|
||||
sudo apt-get install -y clang
|
||||
clang --version
|
||||
|
||||
- name: Configure CMake
|
||||
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{matrix.conf}} -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++
|
||||
|
||||
- name: Build
|
||||
run: cmake --build ${{github.workspace}}/build --config ${{matrix.conf}}
|
||||
|
||||
windows-premake:
|
||||
strategy:
|
||||
matrix:
|
||||
conf:
|
||||
- Debug
|
||||
- Release
|
||||
vs-version:
|
||||
- vs2022
|
||||
include:
|
||||
- vs-version: vs2022
|
||||
version-range: '17.0'
|
||||
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Add msbuild to PATH
|
||||
uses: microsoft/setup-msbuild@v1.1
|
||||
with:
|
||||
vs-version: ${{matrix.version-range}}
|
||||
|
||||
- name: Setup SDL2
|
||||
uses: ./.github/actions/setup-sdl2-windows
|
||||
|
||||
- name: Download and Install Premake
|
||||
working-directory: RecastDemo
|
||||
shell: pwsh
|
||||
run: |
|
||||
(new-object System.Net.WebClient).DownloadFile("https://github.com/premake/premake-core/releases/download/v5.0.0-beta8/premake-5.0.0-beta8-windows.zip","${{github.workspace}}/RecastDemo/premake.zip")
|
||||
tar -xf premake.zip
|
||||
del premake.zip
|
||||
|
||||
- name: Run Premake
|
||||
working-directory: RecastDemo
|
||||
run: ./premake5.exe ${{matrix.vs-version}}
|
||||
|
||||
- name: Build
|
||||
working-directory: RecastDemo/Build/${{matrix.vs-version}}
|
||||
run: msbuild RecastDemo.vcxproj -property:Configuration=${{matrix.conf}} -property:Platform=x64
|
||||
|
||||
windows-cmake:
|
||||
strategy:
|
||||
matrix:
|
||||
conf:
|
||||
- Debug
|
||||
- Release
|
||||
vs-version:
|
||||
- vs2022
|
||||
include:
|
||||
- vs-version: vs2022
|
||||
cmake-generator: Visual Studio 17 2022
|
||||
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup SDL2
|
||||
uses: ./.github/actions/setup-sdl2-windows
|
||||
|
||||
- name: Configure CMake
|
||||
run: cmake -G "${{matrix.cmake-generator}}" -B ${{github.workspace}}/build -D CMAKE_BUILD_TYPE=${{matrix.conf}} -D CMAKE_INSTALL_PREFIX=${{github.workspace}}/build
|
||||
|
||||
- name: Build with CMake
|
||||
run: cmake --build ${{github.workspace}}/build --config ${{matrix.conf}}
|
||||
29
src/features/editScene/recastnavigation/.github/workflows/Docs.yaml
vendored
Normal file
29
src/features/editScene/recastnavigation/.github/workflows/Docs.yaml
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
name: Publish Docs
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install Doxygen
|
||||
run: sudo apt-get install -y doxygen
|
||||
|
||||
- name: Build Doxygen Documentation
|
||||
run: doxygen ./Doxyfile
|
||||
|
||||
- name: Deploy Documentation
|
||||
uses: JamesIves/github-pages-deploy-action@v4
|
||||
with:
|
||||
folder: ./Docs/html
|
||||
branch: gh-pages
|
||||
103
src/features/editScene/recastnavigation/.github/workflows/Tests.yaml
vendored
Normal file
103
src/features/editScene/recastnavigation/.github/workflows/Tests.yaml
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
name: Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "**" ]
|
||||
pull_request:
|
||||
branches: [ "**" ]
|
||||
|
||||
jobs:
|
||||
macos-tests:
|
||||
runs-on: macos-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup SDL2
|
||||
uses: ./.github/actions/setup-sdl2-macos
|
||||
|
||||
- name: Download & install premake
|
||||
working-directory: RecastDemo
|
||||
run: |
|
||||
curl -L -o premake.tar.gz https://github.com/premake/premake-core/releases/download/v5.0.0-beta8/premake-5.0.0-beta8-macosx.tar.gz
|
||||
tar -xzf premake.tar.gz
|
||||
rm premake.tar.gz
|
||||
chmod 777 ./premake5
|
||||
|
||||
- name: Run premake
|
||||
working-directory: RecastDemo
|
||||
run: ./premake5 xcode4
|
||||
|
||||
- name: Build Unit Tests With Xcode
|
||||
working-directory: RecastDemo/Build/xcode4/
|
||||
run: xcodebuild -scheme Tests -configuration Debug -project Tests.xcodeproj build
|
||||
|
||||
- name: Run unit tests
|
||||
working-directory: RecastDemo/Bin
|
||||
run: ./Tests --verbosity high --success
|
||||
|
||||
linux-tests:
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup SDL2
|
||||
uses: ./.github/actions/setup-sdl2-linux
|
||||
|
||||
- name: Install clang
|
||||
run: |
|
||||
sudo apt-get install -y clang
|
||||
clang --version
|
||||
|
||||
- name: Download & Install premake
|
||||
working-directory: RecastDemo
|
||||
run: |
|
||||
curl -L -o premake.tar.gz https://github.com/premake/premake-core/releases/download/v5.0.0-beta8/premake-5.0.0-beta8-linux.tar.gz
|
||||
tar -xzf premake.tar.gz
|
||||
rm premake.tar.gz
|
||||
chmod 777 ./premake5
|
||||
|
||||
- name: Run premake
|
||||
working-directory: RecastDemo
|
||||
run: ./premake5 --cc=clang gmake
|
||||
|
||||
- name: Build
|
||||
working-directory: RecastDemo/Build/gmake
|
||||
run: make config=debug verbose=true
|
||||
|
||||
- name: Run Tests
|
||||
working-directory: RecastDemo/Bin
|
||||
run: ./Tests --verbosity high --success
|
||||
|
||||
windows-tests:
|
||||
runs-on: windows-2022
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Add msbuild to PATH
|
||||
uses: microsoft/setup-msbuild@v1.1
|
||||
|
||||
- name: Setup SDL2
|
||||
uses: ./.github/actions/setup-sdl2-windows
|
||||
|
||||
- name: Download and Install Premake
|
||||
working-directory: RecastDemo
|
||||
shell: pwsh
|
||||
run: |
|
||||
(new-object System.Net.WebClient).DownloadFile("https://github.com/premake/premake-core/releases/download/v5.0.0-beta8/premake-5.0.0-beta8-windows.zip","${{github.workspace}}/RecastDemo/premake.zip")
|
||||
tar -xf premake.zip
|
||||
del premake.zip
|
||||
|
||||
- name: Run Premake
|
||||
working-directory: RecastDemo
|
||||
run: ./premake5.exe vs2022
|
||||
|
||||
- name: Build
|
||||
working-directory: RecastDemo/Build/vs2022
|
||||
run: msbuild Tests.vcxproj -property:Configuration=Debug -property:Platform=x64
|
||||
|
||||
- name: Run Tests
|
||||
working-directory: RecastDemo/Bin/
|
||||
run: ./Tests.exe --verbosity high --success
|
||||
62
src/features/editScene/recastnavigation/.gitignore
vendored
Normal file
62
src/features/editScene/recastnavigation/.gitignore
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
## Compiled source #
|
||||
*.com
|
||||
*.class
|
||||
*.dll
|
||||
*.exe
|
||||
*.ilk
|
||||
*.o
|
||||
*.pdb
|
||||
*.so
|
||||
*.idb
|
||||
|
||||
# clangd
|
||||
.cache/
|
||||
compile_commands.json
|
||||
|
||||
## Linux exes have no extension
|
||||
RecastDemo/Bin/RecastDemo
|
||||
RecastDemo/Bin/Tests
|
||||
|
||||
# Build directory
|
||||
RecastDemo/Build
|
||||
|
||||
# XCode debug symbols archive
|
||||
RecastDemo/Bin/*.dSYM
|
||||
|
||||
# Ignore meshes
|
||||
RecastDemo/Bin/Meshes/*
|
||||
|
||||
# Dear IMGUI state
|
||||
RecastDemo/Bin/imgui.ini
|
||||
|
||||
## Logs and databases #
|
||||
*.log
|
||||
*.sql
|
||||
*.sqlite
|
||||
|
||||
## OS generated files #
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
## xcode specific
|
||||
*xcuserdata*
|
||||
|
||||
## SDL contrib
|
||||
RecastDemo/Contrib/SDL/*
|
||||
|
||||
## Generated doc files
|
||||
Docs/html
|
||||
|
||||
## CMake build cache
|
||||
build
|
||||
|
||||
## IDE files
|
||||
.idea/
|
||||
cmake-build-*/
|
||||
100
src/features/editScene/recastnavigation/CHANGELOG.md
Normal file
100
src/features/editScene/recastnavigation/CHANGELOG.md
Normal file
@@ -0,0 +1,100 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
<h2>[Unreleased](https://github.com/recastnavigation/recastnavigation/compare/1.6.0...HEAD)</h2>
|
||||
<h2>[1.6.0](https://github.com/recastnavigation/recastnavigation/compare/1.5.1...1.6.0) - 2023-05-21</h2>
|
||||
|
||||
### Added
|
||||
- CMake build support
|
||||
- Unit testing with Catch2 (#147)
|
||||
- Support for AABB and OBB obstacles in `dtTileCache` (#215, #278)
|
||||
- `dtTileCache` supports timesliced updates (#203)
|
||||
- Support for custom assertion functions (#250)
|
||||
- Variant of `findNearestPoly` that exposes distance and isOverPoly (#448)
|
||||
- `dtNavMeshQuery::getPathFromDijkstraSearch` gets a path from the explored nodes in a navmesh search (#211)
|
||||
- A version of `dtPolyQuery::queryPolygon` that operates on batches of polygons rather than just 128 (#175) (Fixes #107)
|
||||
- `rcNew`/`rcDelete` to match `rcAlloc`/`rcFree` (#324)
|
||||
- Better error reporting and input sanitization (#179, #303)
|
||||
- Better debug draw (#253, #254, #255, #256)
|
||||
- Improved docstrings, documentation
|
||||
- (RecastDemo) Load/Save navmesh data (#258)
|
||||
|
||||
### Fixed
|
||||
- Improved robustness, speed and accuracy of navmesh point queries (#205, #208, #228, #231, #364, #381, #560)
|
||||
- Incorrect rasterization at tile borders (#476)
|
||||
- Off-mesh links in tiles were sometimes added twice (#202)
|
||||
- Potential heap corruption when collecting region layers (#214)
|
||||
- `findPath` returns `DT_OUT_OF_NODES` appropriately (#222)
|
||||
- Spans are filtered if there is just enough height (#626)
|
||||
- Increased epsilon in detour common segment polygon intersection test (#612)
|
||||
- Array overrun in `removeVertex` in `DetourTileCacheBuilder` (#601)
|
||||
- Potential rounding error computing bounding box size in `dtNavMesh::connectExtLinks` (#428)
|
||||
- An indexing error in updating agents in `DetourCrowd` (#450)
|
||||
- Allocation perf issues in rcVectorBase (#467)
|
||||
- Dead website links in comments
|
||||
- RecastDemo bugs (#180, #184, #186, #187, #200)
|
||||
- Uninitialized class member values, small memory leaks, rule-of-three violations, other minor issues
|
||||
|
||||
### Changed
|
||||
- Updated stb_image (#184)
|
||||
- Updated stb_truetype (#183)
|
||||
|
||||
### Removed
|
||||
- Use of _USE_MATH_DEFINES directive (#596)
|
||||
|
||||
## [1.5.1](https://github.com/recastnavigation/recastnavigation/compare/1.5.0...1.5.1) - 2016-02-22
|
||||
|
||||
Patch release; one bug has been fixed, which would cause silent failure if too many nodes were requested and used in a dtNavMeshQuery.
|
||||
|
||||
- Fail when too many nodes are requested (#179)
|
||||
|
||||
## 1.5.0 - 2016-01-24
|
||||
|
||||
This is the first release of the Recast and Detour libraries since August 2009, containing all fixes and enhancements made since then. As you can imagine, this includes a huge number of commits, so we will forego the list of changes for this release - future releases will contain at least a summary of changes.
|
||||
|
||||
We have decided to use Semantic Versioning for version numbers from now onwards - beginning at 1.5.0 rather than 1.0.0 since the last old release on Google Code was 1.4.
|
||||
|
||||
## 1.4.0 - 2009-08-24
|
||||
|
||||
(Release 1.4 and earlier can be found on the old [archived google code repository](https://code.google.com/archive/p/recastnavigation/))
|
||||
|
||||
- Added detail height mesh generation (RecastDetailMesh.cpp) for single, tiled statmeshes as well as tilemesh.
|
||||
- Added feature to contour tracing which detects extra vertices along tile edges which should be removed later.
|
||||
- Changed the tiled stat mesh preprocess, so that it first generated polymeshes per tile and finally combines them.
|
||||
- Fixed bug in the GUI code where invisible buttons could be pressed.
|
||||
|
||||
## 1.3.1 - 2009-07-24
|
||||
|
||||
- Better cost and heuristic functions.
|
||||
- Fixed tile navmesh raycast on tile borders.
|
||||
|
||||
## 1.3.1 - 2009-07-14
|
||||
|
||||
- Added dtTileNavMesh which allows dynamically adding and removing navmesh pieces at runtime.
|
||||
- Renamed stat navmesh types to dtStat* (i.e. dtPoly is now dtStatPoly).
|
||||
- Moved common code used by tile and stat navmesh to DetourNode.h/cpp and DetourCommon.h/cpp.
|
||||
- Refactor the demo code.
|
||||
|
||||
## 1.2.0 - 2009-06-17
|
||||
|
||||
- Added tiled mesh generation. The tiled generation allows to generate navigation for much larger worlds, it removes some of the artifacts that comes from distance fields in open areas, and allows later streaming and dynamic runtime generation
|
||||
- Improved and added some debug draw modes
|
||||
- API change: The helper function rcBuildNavMesh does not exists anymore, had to change few internal things to cope with the tiled processing, similar API functionality will be added later once the tiled process matures
|
||||
- The demo is getting way too complicated, need to split demos
|
||||
- Fixed several filtering functions so that the mesh is tighter to the geometry, sometimes there could be up error up to tow voxel units close to walls, now it should be just one.
|
||||
|
||||
## 1.1.0 - 2009-04-11
|
||||
|
||||
This is the first release of Detour.
|
||||
|
||||
## 1.0.0 - 2009-03-29
|
||||
|
||||
This is the first release of Recast.
|
||||
|
||||
The process is not always as robust as I would wish. The watershed phase sometimes swallows tiny islands which are close to edges. These droppings are handled in rcBuildContours, but the code is not particularly robust either.
|
||||
|
||||
Another non-robust case is when portal contours (contours shared between two regions) are always assumed to be straight. That can lead to overlapping contours specially when the level has large open areas.
|
||||
25
src/features/editScene/recastnavigation/CMakeLists.txt
Normal file
25
src/features/editScene/recastnavigation/CMakeLists.txt
Normal file
@@ -0,0 +1,25 @@
|
||||
# Minimal RecastNavigation build for editScene
|
||||
# No demos, no tests, no -fno-rtti / -fno-exceptions
|
||||
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(recastnavigation-editscene LANGUAGES C CXX)
|
||||
|
||||
# Version info for the libraries
|
||||
set(SOVERSION 1)
|
||||
set(LIB_VERSION 1.6.0)
|
||||
|
||||
# We want RTTI and exceptions to be compatible with the rest of the C++ project.
|
||||
# The upstream CMakeLists adds -fno-rtti -fno-exceptions; we skip that here.
|
||||
|
||||
# Disable unwanted parts
|
||||
set(RECASTNAVIGATION_DEMO OFF CACHE BOOL "" FORCE)
|
||||
set(RECASTNAVIGATION_TESTS OFF CACHE BOOL "" FORCE)
|
||||
set(RECASTNAVIGATION_EXAMPLES OFF CACHE BOOL "" FORCE)
|
||||
set(RECASTNAVIGATION_ENABLE_ASSERTS "$<CONFIG:Debug>" CACHE STRING "" FORCE)
|
||||
|
||||
# Build the core libraries only
|
||||
add_subdirectory(Recast)
|
||||
add_subdirectory(Detour)
|
||||
add_subdirectory(DetourTileCache)
|
||||
add_subdirectory(DetourCrowd)
|
||||
add_subdirectory(DebugUtils)
|
||||
132
src/features/editScene/recastnavigation/CODE_OF_CONDUCT.md
Normal file
132
src/features/editScene/recastnavigation/CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,132 @@
|
||||
|
||||
# Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, caste, color, religion, or sexual
|
||||
identity and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the overall
|
||||
community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or advances of
|
||||
any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email address,
|
||||
without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement by emailing organizers at recastnav dot com.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series of
|
||||
actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or permanent
|
||||
ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within the
|
||||
community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.1, available at
|
||||
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
||||
|
||||
Community Impact Guidelines were inspired by
|
||||
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
|
||||
[https://www.contributor-covenant.org/translations][translations].
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
||||
[Mozilla CoC]: https://github.com/mozilla/diversity
|
||||
[FAQ]: https://www.contributor-covenant.org/faq
|
||||
[translations]: https://www.contributor-covenant.org/translations
|
||||
90
src/features/editScene/recastnavigation/CONTRIBUTING.md
Normal file
90
src/features/editScene/recastnavigation/CONTRIBUTING.md
Normal file
@@ -0,0 +1,90 @@
|
||||
# Contribution Guidelines
|
||||
|
||||
We'd love for you to contribute to Recast and Detour and help make them even better than they are today! Here are the guidelines we'd like you to follow:
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
This project adheres to the [Contributor Covenant Code of Conduct][code-of-conduct]. By participating in the Recast community, you are required to adhere to this code.
|
||||
|
||||
## Have a Question or Problem?
|
||||
|
||||
Questions about how to best use Recast, what it's capable of, and other inquiries should be directed to the [Q&A section of Github Discussions][q-and-a]. Questions submitted as Github issues will be converted to discussions.
|
||||
|
||||
You can also submit questions to the older Recast [Google Group][groups] discussion list, or chat on [Gitter][gitter].
|
||||
|
||||
## Want a New Feature?
|
||||
|
||||
Check out our [development roadmap](Docs/_99_Roadmap.md) to see what new features are already planned. The roadmap is a great resource if you're looking to help out with Recast but aren't sure where to start.
|
||||
|
||||
You can request a new feature by submitting a github issue. See below for guidelines on submitting issues.
|
||||
|
||||
If you would like to implement a new feature then consider what category of change it is:
|
||||
* **Major Changes** that you wish to contribute to the project should be discussed first in a [GitHub Issue][github-issues] or in our [Google Group][groups] so that we can coordinate efforts, prevent duplication of work, and help you to craft the change so that it is successfully accepted.
|
||||
* **Small Changes** can be submitted as pull requests on [GitHub][github].
|
||||
|
||||
## Found a Bug?
|
||||
|
||||
If you've found a bug or an issue with the documentation you can help us by submitting an issue to our [GitHub Repository][github]. Even better you can submit a Pull Request with a fix.
|
||||
|
||||
### Submitting an Issue
|
||||
|
||||
Before you submit your issue search the [GitHub Issues][github-issues] and [Github Discussions][q-and-a] archives, maybe your question was already answered.
|
||||
|
||||
If your issue is a bug and hasn't been reported, open a new issue. Providing the following information will increase the chances of your issue being dealt with quickly:
|
||||
|
||||
* **Overview of the Issue** - what type of issue is it, and why is it an issue for you?
|
||||
* **Callstack** - if it's a crash or other runtime error, a callstack will help diagnosis
|
||||
* **Screenshots** - for navmesh generation problems, a picture really is worth a thousand words. Implement `duDebugDraw` and call some methods from DetourDebugDraw.h. Seriously, just do it, we'll definitely ask you to if you haven't!
|
||||
* **Logs** - stdout and stderr from the console, or log files if there are any.
|
||||
If integrating into your own codebase, be sure to implement the log callbacks in `rcContext`.
|
||||
* **Reproduction steps** - a minimal, unambiguous set of steps including input, that causes the error for you. e.g. input geometry and settings you can use to input into RecastDemo to get it to fail.
|
||||
* Note: In RecastDemo you can save the intput parameters to Recast by pressing the `9` key. The resulting `.gset` file can be shared with the `.obj` (if it is not one of the default ones) which will help tremendously in getting your bug repro'd and fixed.
|
||||
* **Recast version(s) and/or git commit hashes** - particularly if you can find the point at which the error first started happening
|
||||
* **Environment** - operating system, compiler etc.
|
||||
* **Related issues** - have similar issues been reported before?
|
||||
* **Suggest a Fix** - if you can't fix the bug yourself, perhaps you can point to what might be causing the problem (line of code or commit)
|
||||
|
||||
Here is a great example of a well defined issue: https://github.com/recastnavigation/recastnavigation/issues/12
|
||||
|
||||
**If you get help, help others. Good karma rulez!**
|
||||
|
||||
### Submitting a Pull Request
|
||||
|
||||
Check out [Github's official documentation](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request) on how best to crete, work with and submit pull requests.
|
||||
|
||||
Before you submit your pull request consider the following:
|
||||
|
||||
* Search [GitHub Pull Requests][github-pulls] for an open or closed Pull Request that relates to your submission. You don't want to duplicate effort.
|
||||
* Do your best to factor commits appropriately. It's better to submit multiple pull separate requests than include every change in a single one. For example, if you're cleaning up some code and submitting a bug fix, it's best to submit a PR for the cleanup work separately from the bugfix. This helps ensure your PR's are reviewed and merged quickly, and that an issue with some part of your changes doesn't hold back all of them.
|
||||
* If applicable, please try to include some unit tests that test the issue and fix that you're submitting.
|
||||
* Ensure your PR is rebased onto the head of `main`. If you don't do this, you'll have some issues when github does it for
|
||||
you.
|
||||
|
||||
### Commit Message Format
|
||||
Please format PR messages as follows (based on this [excellent post](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)):
|
||||
|
||||
```
|
||||
Summarize change in 50 characters or less
|
||||
|
||||
Provide more detail after the first line. Leave one blank line below the
|
||||
summary and wrap all lines at 72 characters or less.
|
||||
|
||||
If the change fixes an issue, leave another blank line after the final
|
||||
paragraph and indicate which issue is fixed in the specific format
|
||||
below.
|
||||
|
||||
Fixes #42
|
||||
```
|
||||
|
||||
Important things you should try to include in PR messages include:
|
||||
* Motivation for the change
|
||||
* Differences from previous behaviour
|
||||
* Whether the change alters the public API, or meaningfully affects existing behaviour
|
||||
|
||||
[code-of-conduct]: https://github.com/recastnavigation/recastnavigation/blob/main/CODE_OF_CONDUCT.md
|
||||
[q-and-a]: https://github.com/recastnavigation/recastnavigation/discussions/categories/q-a
|
||||
[github]: https://github.com/recastnavigation/recastnavigation
|
||||
[github-issues]: https://github.com/recastnavigation/recastnavigation/issues
|
||||
[github-pulls]: https://github.com/recastnavigation/recastnavigation/pulls
|
||||
[gitter]: https://gitter.im/recastnavigation/chat
|
||||
[groups]: https://groups.google.com/forum/?fromgroups#!forum/recastnavigation
|
||||
@@ -0,0 +1,44 @@
|
||||
add_library(DebugUtils)
|
||||
add_library(RecastNavigation::DebugUtils ALIAS DebugUtils)
|
||||
|
||||
set_target_properties(DebugUtils PROPERTIES
|
||||
DEBUG_POSTFIX -d
|
||||
SOVERSION ${SOVERSION}
|
||||
VERSION ${LIB_VERSION}
|
||||
COMPILE_PDB_OUTPUT_DIRECTORY .
|
||||
COMPILE_PDB_NAME "DebugUtils-d"
|
||||
CXX_STANDARD 98
|
||||
CXX_STANDARD_REQUIRED ON
|
||||
CXX_EXTENSIONS OFF # Disable compiler-specific extensions
|
||||
)
|
||||
|
||||
target_include_directories(DebugUtils PUBLIC
|
||||
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/Include>"
|
||||
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/recastnavigation>"
|
||||
)
|
||||
|
||||
target_sources(DebugUtils PRIVATE
|
||||
Source/DebugDraw.cpp
|
||||
Source/DetourDebugDraw.cpp
|
||||
Source/RecastDebugDraw.cpp
|
||||
Source/RecastDump.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(DebugUtils Recast Detour DetourTileCache)
|
||||
|
||||
install(TARGETS DebugUtils
|
||||
EXPORT recastnavigation-targets
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT library
|
||||
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} ${CMAKE_INSTALL_INCLUDEDIR}/recastnavigation
|
||||
)
|
||||
|
||||
install(DIRECTORY Include/
|
||||
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/recastnavigation
|
||||
FILES_MATCHING PATTERN "*.h"
|
||||
)
|
||||
|
||||
if (MSVC)
|
||||
install(FILES "$<TARGET_FILE_DIR:DebugUtils>/DebugUtils-d.pdb" CONFIGURATIONS "Debug" DESTINATION "lib" OPTIONAL)
|
||||
endif()
|
||||
@@ -0,0 +1,225 @@
|
||||
//
|
||||
// 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 DEBUGDRAW_H
|
||||
#define DEBUGDRAW_H
|
||||
|
||||
// Some math headers don't have PI defined.
|
||||
static const float DU_PI = 3.14159265f;
|
||||
|
||||
enum duDebugDrawPrimitives
|
||||
{
|
||||
DU_DRAW_POINTS,
|
||||
DU_DRAW_LINES,
|
||||
DU_DRAW_TRIS,
|
||||
DU_DRAW_QUADS
|
||||
};
|
||||
|
||||
/// Abstract debug draw interface.
|
||||
struct duDebugDraw
|
||||
{
|
||||
virtual ~duDebugDraw() = 0;
|
||||
|
||||
virtual void depthMask(bool state) = 0;
|
||||
|
||||
virtual void texture(bool state) = 0;
|
||||
|
||||
/// 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.
|
||||
virtual void begin(duDebugDrawPrimitives prim, float size = 1.0f) = 0;
|
||||
|
||||
/// Submit a vertex
|
||||
/// @param pos [in] position of the verts.
|
||||
/// @param color [in] color of the verts.
|
||||
virtual void vertex(const float* pos, unsigned int color) = 0;
|
||||
|
||||
/// Submit a vertex
|
||||
/// @param x,y,z [in] position of the verts.
|
||||
/// @param color [in] color of the verts.
|
||||
virtual void vertex(const float x, const float y, const float z, unsigned int color) = 0;
|
||||
|
||||
/// Submit a vertex
|
||||
/// @param pos [in] position of the verts.
|
||||
/// @param color [in] color of the verts.
|
||||
/// @param uv [in] the uv coordinates of the verts.
|
||||
virtual void vertex(const float* pos, unsigned int color, const float* uv) = 0;
|
||||
|
||||
/// Submit a vertex
|
||||
/// @param x,y,z [in] position of the verts.
|
||||
/// @param color [in] color of the verts.
|
||||
/// @param u,v [in] the uv coordinates of the verts.
|
||||
virtual void vertex(const float x, const float y, const float z, unsigned int color, const float u, const float v) = 0;
|
||||
|
||||
/// End drawing primitives.
|
||||
virtual void end() = 0;
|
||||
|
||||
/// Compute a color for given area.
|
||||
virtual unsigned int areaToCol(unsigned int area);
|
||||
};
|
||||
|
||||
inline unsigned int duRGBA(int r, int g, int b, int a)
|
||||
{
|
||||
return ((unsigned int)r) | ((unsigned int)g << 8) | ((unsigned int)b << 16) | ((unsigned int)a << 24);
|
||||
}
|
||||
|
||||
inline unsigned int duRGBAf(float fr, float fg, float fb, float fa)
|
||||
{
|
||||
unsigned char r = (unsigned char)(fr*255.0f);
|
||||
unsigned char g = (unsigned char)(fg*255.0f);
|
||||
unsigned char b = (unsigned char)(fb*255.0f);
|
||||
unsigned char a = (unsigned char)(fa*255.0f);
|
||||
return duRGBA(r,g,b,a);
|
||||
}
|
||||
|
||||
unsigned int duIntToCol(int i, int a);
|
||||
void duIntToCol(int i, float* col);
|
||||
|
||||
inline unsigned int duMultCol(const unsigned int col, const unsigned int d)
|
||||
{
|
||||
const unsigned int r = col & 0xff;
|
||||
const unsigned int g = (col >> 8) & 0xff;
|
||||
const unsigned int b = (col >> 16) & 0xff;
|
||||
const unsigned int a = (col >> 24) & 0xff;
|
||||
return duRGBA((r*d) >> 8, (g*d) >> 8, (b*d) >> 8, a);
|
||||
}
|
||||
|
||||
inline unsigned int duDarkenCol(unsigned int col)
|
||||
{
|
||||
return ((col >> 1) & 0x007f7f7f) | (col & 0xff000000);
|
||||
}
|
||||
|
||||
inline unsigned int duLerpCol(unsigned int ca, unsigned int cb, unsigned int u)
|
||||
{
|
||||
const unsigned int ra = ca & 0xff;
|
||||
const unsigned int ga = (ca >> 8) & 0xff;
|
||||
const unsigned int ba = (ca >> 16) & 0xff;
|
||||
const unsigned int aa = (ca >> 24) & 0xff;
|
||||
const unsigned int rb = cb & 0xff;
|
||||
const unsigned int gb = (cb >> 8) & 0xff;
|
||||
const unsigned int bb = (cb >> 16) & 0xff;
|
||||
const unsigned int ab = (cb >> 24) & 0xff;
|
||||
|
||||
unsigned int r = (ra*(255-u) + rb*u)/255;
|
||||
unsigned int g = (ga*(255-u) + gb*u)/255;
|
||||
unsigned int b = (ba*(255-u) + bb*u)/255;
|
||||
unsigned int a = (aa*(255-u) + ab*u)/255;
|
||||
return duRGBA(r,g,b,a);
|
||||
}
|
||||
|
||||
inline unsigned int duTransCol(unsigned int c, unsigned int a)
|
||||
{
|
||||
return (a<<24) | (c & 0x00ffffff);
|
||||
}
|
||||
|
||||
|
||||
void duCalcBoxColors(unsigned int* colors, unsigned int colTop, unsigned int colSide);
|
||||
|
||||
void duDebugDrawCylinderWire(struct duDebugDraw* dd, float minx, float miny, float minz,
|
||||
float maxx, float maxy, float maxz, unsigned int col, const float lineWidth);
|
||||
|
||||
void duDebugDrawBoxWire(struct duDebugDraw* dd, float minx, float miny, float minz,
|
||||
float maxx, float maxy, float maxz, unsigned int col, const float lineWidth);
|
||||
|
||||
void duDebugDrawArc(struct duDebugDraw* dd, const float x0, const float y0, const float z0,
|
||||
const float x1, const float y1, const float z1, const float h,
|
||||
const float as0, const float as1, unsigned int col, const float lineWidth);
|
||||
|
||||
void duDebugDrawArrow(struct duDebugDraw* dd, const float x0, const float y0, const float z0,
|
||||
const float x1, const float y1, const float z1,
|
||||
const float as0, const float as1, unsigned int col, const float lineWidth);
|
||||
|
||||
void duDebugDrawCircle(struct duDebugDraw* dd, const float x, const float y, const float z,
|
||||
const float r, unsigned int col, const float lineWidth);
|
||||
|
||||
void duDebugDrawCross(struct duDebugDraw* dd, const float x, const float y, const float z,
|
||||
const float size, unsigned int col, const float lineWidth);
|
||||
|
||||
void duDebugDrawBox(struct duDebugDraw* dd, float minx, float miny, float minz,
|
||||
float maxx, float maxy, float maxz, const unsigned int* fcol);
|
||||
|
||||
void duDebugDrawCylinder(struct duDebugDraw* dd, float minx, float miny, float minz,
|
||||
float maxx, float maxy, float maxz, unsigned int col);
|
||||
|
||||
void duDebugDrawGridXZ(struct duDebugDraw* dd, const float ox, const float oy, const float oz,
|
||||
const int w, const int h, const float size,
|
||||
const unsigned int col, const float lineWidth);
|
||||
|
||||
|
||||
// Versions without begin/end, can be used to draw multiple primitives.
|
||||
void duAppendCylinderWire(struct duDebugDraw* dd, float minx, float miny, float minz,
|
||||
float maxx, float maxy, float maxz, unsigned int col);
|
||||
|
||||
void duAppendBoxWire(struct duDebugDraw* dd, float minx, float miny, float minz,
|
||||
float maxx, float maxy, float maxz, unsigned int col);
|
||||
|
||||
void duAppendBoxPoints(struct duDebugDraw* dd, float minx, float miny, float minz,
|
||||
float maxx, float maxy, float maxz, unsigned int col);
|
||||
|
||||
void duAppendArc(struct duDebugDraw* dd, const float x0, const float y0, const float z0,
|
||||
const float x1, const float y1, const float z1, const float h,
|
||||
const float as0, const float as1, unsigned int col);
|
||||
|
||||
void duAppendArrow(struct duDebugDraw* dd, const float x0, const float y0, const float z0,
|
||||
const float x1, const float y1, const float z1,
|
||||
const float as0, const float as1, unsigned int col);
|
||||
|
||||
void duAppendCircle(struct duDebugDraw* dd, const float x, const float y, const float z,
|
||||
const float r, unsigned int col);
|
||||
|
||||
void duAppendCross(struct duDebugDraw* dd, const float x, const float y, const float z,
|
||||
const float size, unsigned int col);
|
||||
|
||||
void duAppendBox(struct duDebugDraw* dd, float minx, float miny, float minz,
|
||||
float maxx, float maxy, float maxz, const unsigned int* fcol);
|
||||
|
||||
void duAppendCylinder(struct duDebugDraw* dd, float minx, float miny, float minz,
|
||||
float maxx, float maxy, float maxz, unsigned int col);
|
||||
|
||||
|
||||
class duDisplayList : public duDebugDraw
|
||||
{
|
||||
float* m_pos;
|
||||
unsigned int* m_color;
|
||||
int m_size;
|
||||
int m_cap;
|
||||
|
||||
duDebugDrawPrimitives m_prim;
|
||||
float m_primSize;
|
||||
bool m_depthMask;
|
||||
|
||||
void resize(int cap);
|
||||
|
||||
public:
|
||||
duDisplayList(int cap = 512);
|
||||
virtual ~duDisplayList();
|
||||
virtual void depthMask(bool state);
|
||||
virtual void begin(duDebugDrawPrimitives prim, float size = 1.0f);
|
||||
virtual void vertex(const float x, const float y, const float z, unsigned int color);
|
||||
virtual void vertex(const float* pos, unsigned int color);
|
||||
virtual void end();
|
||||
void clear();
|
||||
void draw(struct duDebugDraw* dd);
|
||||
private:
|
||||
// Explicitly disabled copy constructor and copy assignment operator.
|
||||
duDisplayList(const duDisplayList&);
|
||||
duDisplayList& operator=(const duDisplayList&);
|
||||
};
|
||||
|
||||
|
||||
#endif // DEBUGDRAW_H
|
||||
48
src/features/editScene/recastnavigation/DebugUtils/Include/DetourDebugDraw.h
Executable file
48
src/features/editScene/recastnavigation/DebugUtils/Include/DetourDebugDraw.h
Executable file
@@ -0,0 +1,48 @@
|
||||
//
|
||||
// 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 DETOURDEBUGDRAW_H
|
||||
#define DETOURDEBUGDRAW_H
|
||||
|
||||
#include "DetourNavMesh.h"
|
||||
#include "DetourNavMeshQuery.h"
|
||||
#include "DetourTileCacheBuilder.h"
|
||||
|
||||
enum DrawNavMeshFlags
|
||||
{
|
||||
DU_DRAWNAVMESH_OFFMESHCONS = 0x01,
|
||||
DU_DRAWNAVMESH_CLOSEDLIST = 0x02,
|
||||
DU_DRAWNAVMESH_COLOR_TILES = 0x04
|
||||
};
|
||||
|
||||
void duDebugDrawNavMesh(struct duDebugDraw* dd, const dtNavMesh& mesh, unsigned char flags);
|
||||
void duDebugDrawNavMeshWithClosedList(struct duDebugDraw* dd, const dtNavMesh& mesh, const dtNavMeshQuery& query, unsigned char flags);
|
||||
void duDebugDrawNavMeshNodes(struct duDebugDraw* dd, const dtNavMeshQuery& query);
|
||||
void duDebugDrawNavMeshBVTree(struct duDebugDraw* dd, const dtNavMesh& mesh);
|
||||
void duDebugDrawNavMeshPortals(struct duDebugDraw* dd, const dtNavMesh& mesh);
|
||||
void duDebugDrawNavMeshPolysWithFlags(struct duDebugDraw* dd, const dtNavMesh& mesh, const unsigned short polyFlags, const unsigned int col);
|
||||
void duDebugDrawNavMeshPoly(struct duDebugDraw* dd, const dtNavMesh& mesh, dtPolyRef ref, const unsigned int col);
|
||||
|
||||
void duDebugDrawTileCacheLayerAreas(struct duDebugDraw* dd, const dtTileCacheLayer& layer, const float cs, const float ch);
|
||||
void duDebugDrawTileCacheLayerRegions(struct duDebugDraw* dd, const dtTileCacheLayer& layer, const float cs, const float ch);
|
||||
void duDebugDrawTileCacheContours(duDebugDraw* dd, const struct dtTileCacheContourSet& lcset,
|
||||
const float* orig, const float cs, const float ch);
|
||||
void duDebugDrawTileCachePolyMesh(duDebugDraw* dd, const struct dtTileCachePolyMesh& lmesh,
|
||||
const float* orig, const float cs, const float ch);
|
||||
|
||||
#endif // DETOURDEBUGDRAW_H
|
||||
@@ -0,0 +1,42 @@
|
||||
//
|
||||
// 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 RECAST_DEBUGDRAW_H
|
||||
#define RECAST_DEBUGDRAW_H
|
||||
|
||||
void duDebugDrawTriMesh(struct duDebugDraw* dd, const float* verts, int nverts, const int* tris, const float* normals, int ntris, const unsigned char* flags, const float texScale);
|
||||
void duDebugDrawTriMeshSlope(struct duDebugDraw* dd, const float* verts, int nverts, const int* tris, const float* normals, int ntris, const float walkableSlopeAngle, const float texScale);
|
||||
|
||||
void duDebugDrawHeightfieldSolid(struct duDebugDraw* dd, const struct rcHeightfield& hf);
|
||||
void duDebugDrawHeightfieldWalkable(struct duDebugDraw* dd, const struct rcHeightfield& hf);
|
||||
|
||||
void duDebugDrawCompactHeightfieldSolid(struct duDebugDraw* dd, const struct rcCompactHeightfield& chf);
|
||||
void duDebugDrawCompactHeightfieldRegions(struct duDebugDraw* dd, const struct rcCompactHeightfield& chf);
|
||||
void duDebugDrawCompactHeightfieldDistance(struct duDebugDraw* dd, const struct rcCompactHeightfield& chf);
|
||||
|
||||
void duDebugDrawHeightfieldLayer(duDebugDraw* dd, const struct rcHeightfieldLayer& layer, const int idx);
|
||||
void duDebugDrawHeightfieldLayers(duDebugDraw* dd, const struct rcHeightfieldLayerSet& lset);
|
||||
void duDebugDrawHeightfieldLayersRegions(duDebugDraw* dd, const struct rcHeightfieldLayerSet& lset);
|
||||
|
||||
void duDebugDrawRegionConnections(struct duDebugDraw* dd, const struct rcContourSet& cset, const float alpha = 1.0f);
|
||||
void duDebugDrawRawContours(struct duDebugDraw* dd, const struct rcContourSet& cset, const float alpha = 1.0f);
|
||||
void duDebugDrawContours(struct duDebugDraw* dd, const struct rcContourSet& cset, const float alpha = 1.0f);
|
||||
void duDebugDrawPolyMesh(struct duDebugDraw* dd, const struct rcPolyMesh& mesh);
|
||||
void duDebugDrawPolyMeshDetail(struct duDebugDraw* dd, const struct rcPolyMeshDetail& dmesh);
|
||||
|
||||
#endif // RECAST_DEBUGDRAW_H
|
||||
@@ -0,0 +1,44 @@
|
||||
//
|
||||
// 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 RECAST_DUMP_H
|
||||
#define RECAST_DUMP_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
struct duFileIO
|
||||
{
|
||||
virtual ~duFileIO();
|
||||
virtual bool isWriting() const = 0;
|
||||
virtual bool isReading() const = 0;
|
||||
virtual bool write(const void* ptr, const size_t size) = 0;
|
||||
virtual bool read(void* ptr, const size_t size) = 0;
|
||||
};
|
||||
|
||||
bool duDumpPolyMeshToObj(struct rcPolyMesh& pmesh, duFileIO* io);
|
||||
bool duDumpPolyMeshDetailToObj(struct rcPolyMeshDetail& dmesh, duFileIO* io);
|
||||
|
||||
bool duDumpContourSet(struct rcContourSet& cset, duFileIO* io);
|
||||
bool duReadContourSet(struct rcContourSet& cset, duFileIO* io);
|
||||
|
||||
bool duDumpCompactHeightfield(struct rcCompactHeightfield& chf, duFileIO* io);
|
||||
bool duReadCompactHeightfield(struct rcCompactHeightfield& chf, duFileIO* io);
|
||||
|
||||
void duLogBuildTimes(rcContext& ctx, const int totalTileUsec);
|
||||
|
||||
#endif // RECAST_DUMP_H
|
||||
@@ -0,0 +1,611 @@
|
||||
//
|
||||
// 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 <string.h>
|
||||
#include "DebugDraw.h"
|
||||
#include "DetourMath.h"
|
||||
#include "DetourNavMesh.h"
|
||||
|
||||
|
||||
duDebugDraw::~duDebugDraw()
|
||||
{
|
||||
// Empty
|
||||
}
|
||||
|
||||
unsigned int duDebugDraw::areaToCol(unsigned int area)
|
||||
{
|
||||
if (area == 0)
|
||||
{
|
||||
// Treat zero area type as default.
|
||||
return duRGBA(0, 192, 255, 255);
|
||||
}
|
||||
else
|
||||
{
|
||||
return duIntToCol(area, 255);
|
||||
}
|
||||
}
|
||||
|
||||
inline int bit(int a, int b)
|
||||
{
|
||||
return (a & (1 << b)) >> b;
|
||||
}
|
||||
|
||||
unsigned int duIntToCol(int i, int a)
|
||||
{
|
||||
int r = bit(i, 1) + bit(i, 3) * 2 + 1;
|
||||
int g = bit(i, 2) + bit(i, 4) * 2 + 1;
|
||||
int b = bit(i, 0) + bit(i, 5) * 2 + 1;
|
||||
return duRGBA(r*63,g*63,b*63,a);
|
||||
}
|
||||
|
||||
void duIntToCol(int i, float* col)
|
||||
{
|
||||
int r = bit(i, 0) + bit(i, 3) * 2 + 1;
|
||||
int g = bit(i, 1) + bit(i, 4) * 2 + 1;
|
||||
int b = bit(i, 2) + bit(i, 5) * 2 + 1;
|
||||
col[0] = 1 - r*63.0f/255.0f;
|
||||
col[1] = 1 - g*63.0f/255.0f;
|
||||
col[2] = 1 - b*63.0f/255.0f;
|
||||
}
|
||||
|
||||
void duCalcBoxColors(unsigned int* colors, unsigned int colTop, unsigned int colSide)
|
||||
{
|
||||
if (!colors) return;
|
||||
|
||||
colors[0] = duMultCol(colTop, 250);
|
||||
colors[1] = duMultCol(colSide, 140);
|
||||
colors[2] = duMultCol(colSide, 165);
|
||||
colors[3] = duMultCol(colSide, 217);
|
||||
colors[4] = duMultCol(colSide, 165);
|
||||
colors[5] = duMultCol(colSide, 217);
|
||||
}
|
||||
|
||||
void duDebugDrawCylinderWire(struct duDebugDraw* dd, float minx, float miny, float minz,
|
||||
float maxx, float maxy, float maxz, unsigned int col, const float lineWidth)
|
||||
{
|
||||
if (!dd) return;
|
||||
|
||||
dd->begin(DU_DRAW_LINES, lineWidth);
|
||||
duAppendCylinderWire(dd, minx,miny,minz, maxx,maxy,maxz, col);
|
||||
dd->end();
|
||||
}
|
||||
|
||||
void duDebugDrawBoxWire(struct duDebugDraw* dd, float minx, float miny, float minz,
|
||||
float maxx, float maxy, float maxz, unsigned int col, const float lineWidth)
|
||||
{
|
||||
if (!dd) return;
|
||||
|
||||
dd->begin(DU_DRAW_LINES, lineWidth);
|
||||
duAppendBoxWire(dd, minx,miny,minz, maxx,maxy,maxz, col);
|
||||
dd->end();
|
||||
}
|
||||
|
||||
void duDebugDrawArc(struct duDebugDraw* dd, const float x0, const float y0, const float z0,
|
||||
const float x1, const float y1, const float z1, const float h,
|
||||
const float as0, const float as1, unsigned int col, const float lineWidth)
|
||||
{
|
||||
if (!dd) return;
|
||||
|
||||
dd->begin(DU_DRAW_LINES, lineWidth);
|
||||
duAppendArc(dd, x0,y0,z0, x1,y1,z1, h, as0, as1, col);
|
||||
dd->end();
|
||||
}
|
||||
|
||||
void duDebugDrawArrow(struct duDebugDraw* dd, const float x0, const float y0, const float z0,
|
||||
const float x1, const float y1, const float z1,
|
||||
const float as0, const float as1, unsigned int col, const float lineWidth)
|
||||
{
|
||||
if (!dd) return;
|
||||
|
||||
dd->begin(DU_DRAW_LINES, lineWidth);
|
||||
duAppendArrow(dd, x0,y0,z0, x1,y1,z1, as0, as1, col);
|
||||
dd->end();
|
||||
}
|
||||
|
||||
void duDebugDrawCircle(struct duDebugDraw* dd, const float x, const float y, const float z,
|
||||
const float r, unsigned int col, const float lineWidth)
|
||||
{
|
||||
if (!dd) return;
|
||||
|
||||
dd->begin(DU_DRAW_LINES, lineWidth);
|
||||
duAppendCircle(dd, x,y,z, r, col);
|
||||
dd->end();
|
||||
}
|
||||
|
||||
void duDebugDrawCross(struct duDebugDraw* dd, const float x, const float y, const float z,
|
||||
const float size, unsigned int col, const float lineWidth)
|
||||
{
|
||||
if (!dd) return;
|
||||
|
||||
dd->begin(DU_DRAW_LINES, lineWidth);
|
||||
duAppendCross(dd, x,y,z, size, col);
|
||||
dd->end();
|
||||
}
|
||||
|
||||
void duDebugDrawBox(struct duDebugDraw* dd, float minx, float miny, float minz,
|
||||
float maxx, float maxy, float maxz, const unsigned int* fcol)
|
||||
{
|
||||
if (!dd) return;
|
||||
|
||||
dd->begin(DU_DRAW_QUADS);
|
||||
duAppendBox(dd, minx,miny,minz, maxx,maxy,maxz, fcol);
|
||||
dd->end();
|
||||
}
|
||||
|
||||
void duDebugDrawCylinder(struct duDebugDraw* dd, float minx, float miny, float minz,
|
||||
float maxx, float maxy, float maxz, unsigned int col)
|
||||
{
|
||||
if (!dd) return;
|
||||
|
||||
dd->begin(DU_DRAW_TRIS);
|
||||
duAppendCylinder(dd, minx,miny,minz, maxx,maxy,maxz, col);
|
||||
dd->end();
|
||||
}
|
||||
|
||||
void duDebugDrawGridXZ(struct duDebugDraw* dd, const float ox, const float oy, const float oz,
|
||||
const int w, const int h, const float size,
|
||||
const unsigned int col, const float lineWidth)
|
||||
{
|
||||
if (!dd) return;
|
||||
|
||||
dd->begin(DU_DRAW_LINES, lineWidth);
|
||||
for (int i = 0; i <= h; ++i)
|
||||
{
|
||||
dd->vertex(ox,oy,oz+i*size, col);
|
||||
dd->vertex(ox+w*size,oy,oz+i*size, col);
|
||||
}
|
||||
for (int i = 0; i <= w; ++i)
|
||||
{
|
||||
dd->vertex(ox+i*size,oy,oz, col);
|
||||
dd->vertex(ox+i*size,oy,oz+h*size, col);
|
||||
}
|
||||
dd->end();
|
||||
}
|
||||
|
||||
|
||||
void duAppendCylinderWire(struct duDebugDraw* dd, float minx, float miny, float minz,
|
||||
float maxx, float maxy, float maxz, unsigned int col)
|
||||
{
|
||||
if (!dd) return;
|
||||
|
||||
static const int NUM_SEG = 16;
|
||||
static float dir[NUM_SEG*2];
|
||||
static bool init = false;
|
||||
if (!init)
|
||||
{
|
||||
init = true;
|
||||
for (int i = 0; i < NUM_SEG; ++i)
|
||||
{
|
||||
const float a = (float)i/(float)NUM_SEG*DU_PI*2;
|
||||
dir[i*2] = dtMathCosf(a);
|
||||
dir[i*2+1] = dtMathSinf(a);
|
||||
}
|
||||
}
|
||||
|
||||
const float cx = (maxx + minx)/2;
|
||||
const float cz = (maxz + minz)/2;
|
||||
const float rx = (maxx - minx)/2;
|
||||
const float rz = (maxz - minz)/2;
|
||||
|
||||
for (int i = 0, j = NUM_SEG-1; i < NUM_SEG; j = i++)
|
||||
{
|
||||
dd->vertex(cx+dir[j*2+0]*rx, miny, cz+dir[j*2+1]*rz, col);
|
||||
dd->vertex(cx+dir[i*2+0]*rx, miny, cz+dir[i*2+1]*rz, col);
|
||||
dd->vertex(cx+dir[j*2+0]*rx, maxy, cz+dir[j*2+1]*rz, col);
|
||||
dd->vertex(cx+dir[i*2+0]*rx, maxy, cz+dir[i*2+1]*rz, col);
|
||||
}
|
||||
for (int i = 0; i < NUM_SEG; i += NUM_SEG/4)
|
||||
{
|
||||
dd->vertex(cx+dir[i*2+0]*rx, miny, cz+dir[i*2+1]*rz, col);
|
||||
dd->vertex(cx+dir[i*2+0]*rx, maxy, cz+dir[i*2+1]*rz, col);
|
||||
}
|
||||
}
|
||||
|
||||
void duAppendBoxWire(struct duDebugDraw* dd, float minx, float miny, float minz,
|
||||
float maxx, float maxy, float maxz, unsigned int col)
|
||||
{
|
||||
if (!dd) return;
|
||||
// Top
|
||||
dd->vertex(minx, miny, minz, col);
|
||||
dd->vertex(maxx, miny, minz, col);
|
||||
dd->vertex(maxx, miny, minz, col);
|
||||
dd->vertex(maxx, miny, maxz, col);
|
||||
dd->vertex(maxx, miny, maxz, col);
|
||||
dd->vertex(minx, miny, maxz, col);
|
||||
dd->vertex(minx, miny, maxz, col);
|
||||
dd->vertex(minx, miny, minz, col);
|
||||
|
||||
// bottom
|
||||
dd->vertex(minx, maxy, minz, col);
|
||||
dd->vertex(maxx, maxy, minz, col);
|
||||
dd->vertex(maxx, maxy, minz, col);
|
||||
dd->vertex(maxx, maxy, maxz, col);
|
||||
dd->vertex(maxx, maxy, maxz, col);
|
||||
dd->vertex(minx, maxy, maxz, col);
|
||||
dd->vertex(minx, maxy, maxz, col);
|
||||
dd->vertex(minx, maxy, minz, col);
|
||||
|
||||
// Sides
|
||||
dd->vertex(minx, miny, minz, col);
|
||||
dd->vertex(minx, maxy, minz, col);
|
||||
dd->vertex(maxx, miny, minz, col);
|
||||
dd->vertex(maxx, maxy, minz, col);
|
||||
dd->vertex(maxx, miny, maxz, col);
|
||||
dd->vertex(maxx, maxy, maxz, col);
|
||||
dd->vertex(minx, miny, maxz, col);
|
||||
dd->vertex(minx, maxy, maxz, col);
|
||||
}
|
||||
|
||||
void duAppendBoxPoints(struct duDebugDraw* dd, float minx, float miny, float minz,
|
||||
float maxx, float maxy, float maxz, unsigned int col)
|
||||
{
|
||||
if (!dd) return;
|
||||
// Top
|
||||
dd->vertex(minx, miny, minz, col);
|
||||
dd->vertex(maxx, miny, minz, col);
|
||||
dd->vertex(maxx, miny, minz, col);
|
||||
dd->vertex(maxx, miny, maxz, col);
|
||||
dd->vertex(maxx, miny, maxz, col);
|
||||
dd->vertex(minx, miny, maxz, col);
|
||||
dd->vertex(minx, miny, maxz, col);
|
||||
dd->vertex(minx, miny, minz, col);
|
||||
|
||||
// bottom
|
||||
dd->vertex(minx, maxy, minz, col);
|
||||
dd->vertex(maxx, maxy, minz, col);
|
||||
dd->vertex(maxx, maxy, minz, col);
|
||||
dd->vertex(maxx, maxy, maxz, col);
|
||||
dd->vertex(maxx, maxy, maxz, col);
|
||||
dd->vertex(minx, maxy, maxz, col);
|
||||
dd->vertex(minx, maxy, maxz, col);
|
||||
dd->vertex(minx, maxy, minz, col);
|
||||
}
|
||||
|
||||
void duAppendBox(struct duDebugDraw* dd, float minx, float miny, float minz,
|
||||
float maxx, float maxy, float maxz, const unsigned int* fcol)
|
||||
{
|
||||
if (!dd) return;
|
||||
const float verts[8*3] =
|
||||
{
|
||||
minx, miny, minz,
|
||||
maxx, miny, minz,
|
||||
maxx, miny, maxz,
|
||||
minx, miny, maxz,
|
||||
minx, maxy, minz,
|
||||
maxx, maxy, minz,
|
||||
maxx, maxy, maxz,
|
||||
minx, maxy, maxz,
|
||||
};
|
||||
static const unsigned char inds[6*4] =
|
||||
{
|
||||
7, 6, 5, 4,
|
||||
0, 1, 2, 3,
|
||||
1, 5, 6, 2,
|
||||
3, 7, 4, 0,
|
||||
2, 6, 7, 3,
|
||||
0, 4, 5, 1,
|
||||
};
|
||||
|
||||
const unsigned char* in = inds;
|
||||
for (int i = 0; i < 6; ++i)
|
||||
{
|
||||
dd->vertex(&verts[*in*3], fcol[i]); in++;
|
||||
dd->vertex(&verts[*in*3], fcol[i]); in++;
|
||||
dd->vertex(&verts[*in*3], fcol[i]); in++;
|
||||
dd->vertex(&verts[*in*3], fcol[i]); in++;
|
||||
}
|
||||
}
|
||||
|
||||
void duAppendCylinder(struct duDebugDraw* dd, float minx, float miny, float minz,
|
||||
float maxx, float maxy, float maxz, unsigned int col)
|
||||
{
|
||||
if (!dd) return;
|
||||
|
||||
static const int NUM_SEG = 16;
|
||||
static float dir[NUM_SEG*2];
|
||||
static bool init = false;
|
||||
if (!init)
|
||||
{
|
||||
init = true;
|
||||
for (int i = 0; i < NUM_SEG; ++i)
|
||||
{
|
||||
const float a = (float)i/(float)NUM_SEG*DU_PI*2;
|
||||
dir[i*2] = cosf(a);
|
||||
dir[i*2+1] = sinf(a);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int col2 = duMultCol(col, 160);
|
||||
|
||||
const float cx = (maxx + minx)/2;
|
||||
const float cz = (maxz + minz)/2;
|
||||
const float rx = (maxx - minx)/2;
|
||||
const float rz = (maxz - minz)/2;
|
||||
|
||||
for (int i = 2; i < NUM_SEG; ++i)
|
||||
{
|
||||
const int a = 0, b = i-1, c = i;
|
||||
dd->vertex(cx+dir[a*2+0]*rx, miny, cz+dir[a*2+1]*rz, col2);
|
||||
dd->vertex(cx+dir[b*2+0]*rx, miny, cz+dir[b*2+1]*rz, col2);
|
||||
dd->vertex(cx+dir[c*2+0]*rx, miny, cz+dir[c*2+1]*rz, col2);
|
||||
}
|
||||
for (int i = 2; i < NUM_SEG; ++i)
|
||||
{
|
||||
const int a = 0, b = i, c = i-1;
|
||||
dd->vertex(cx+dir[a*2+0]*rx, maxy, cz+dir[a*2+1]*rz, col);
|
||||
dd->vertex(cx+dir[b*2+0]*rx, maxy, cz+dir[b*2+1]*rz, col);
|
||||
dd->vertex(cx+dir[c*2+0]*rx, maxy, cz+dir[c*2+1]*rz, col);
|
||||
}
|
||||
for (int i = 0, j = NUM_SEG-1; i < NUM_SEG; j = i++)
|
||||
{
|
||||
dd->vertex(cx+dir[i*2+0]*rx, miny, cz+dir[i*2+1]*rz, col2);
|
||||
dd->vertex(cx+dir[j*2+0]*rx, miny, cz+dir[j*2+1]*rz, col2);
|
||||
dd->vertex(cx+dir[j*2+0]*rx, maxy, cz+dir[j*2+1]*rz, col);
|
||||
|
||||
dd->vertex(cx+dir[i*2+0]*rx, miny, cz+dir[i*2+1]*rz, col2);
|
||||
dd->vertex(cx+dir[j*2+0]*rx, maxy, cz+dir[j*2+1]*rz, col);
|
||||
dd->vertex(cx+dir[i*2+0]*rx, maxy, cz+dir[i*2+1]*rz, col);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
inline void evalArc(const float x0, const float y0, const float z0,
|
||||
const float dx, const float dy, const float dz,
|
||||
const float h, const float u, float* res)
|
||||
{
|
||||
res[0] = x0 + dx * u;
|
||||
res[1] = y0 + dy * u + h * (1-(u*2-1)*(u*2-1));
|
||||
res[2] = z0 + dz * u;
|
||||
}
|
||||
|
||||
|
||||
inline void vcross(float* dest, const float* v1, const float* v2)
|
||||
{
|
||||
dest[0] = v1[1]*v2[2] - v1[2]*v2[1];
|
||||
dest[1] = v1[2]*v2[0] - v1[0]*v2[2];
|
||||
dest[2] = v1[0]*v2[1] - v1[1]*v2[0];
|
||||
}
|
||||
|
||||
inline void vnormalize(float* v)
|
||||
{
|
||||
float d = 1.0f / sqrtf(v[0]*v[0] + v[1]*v[1] + v[2]*v[2]);
|
||||
v[0] *= d;
|
||||
v[1] *= d;
|
||||
v[2] *= d;
|
||||
}
|
||||
|
||||
inline void vsub(float* dest, const float* v1, const float* v2)
|
||||
{
|
||||
dest[0] = v1[0]-v2[0];
|
||||
dest[1] = v1[1]-v2[1];
|
||||
dest[2] = v1[2]-v2[2];
|
||||
}
|
||||
|
||||
inline float vdistSqr(const float* v1, const float* v2)
|
||||
{
|
||||
const float x = v1[0]-v2[0];
|
||||
const float y = v1[1]-v2[1];
|
||||
const float z = v1[2]-v2[2];
|
||||
return x*x + y*y + z*z;
|
||||
}
|
||||
|
||||
|
||||
void appendArrowHead(struct duDebugDraw* dd, const float* p, const float* q,
|
||||
const float s, unsigned int col)
|
||||
{
|
||||
const float eps = 0.001f;
|
||||
if (!dd) return;
|
||||
if (vdistSqr(p,q) < eps*eps) return;
|
||||
float ax[3], ay[3] = {0,1,0}, az[3];
|
||||
vsub(az, q, p);
|
||||
vnormalize(az);
|
||||
vcross(ax, ay, az);
|
||||
vcross(ay, az, ax);
|
||||
vnormalize(ay);
|
||||
|
||||
dd->vertex(p, col);
|
||||
// dd->vertex(p[0]+az[0]*s+ay[0]*s/2, p[1]+az[1]*s+ay[1]*s/2, p[2]+az[2]*s+ay[2]*s/2, col);
|
||||
dd->vertex(p[0]+az[0]*s+ax[0]*s/3, p[1]+az[1]*s+ax[1]*s/3, p[2]+az[2]*s+ax[2]*s/3, col);
|
||||
|
||||
dd->vertex(p, col);
|
||||
// dd->vertex(p[0]+az[0]*s-ay[0]*s/2, p[1]+az[1]*s-ay[1]*s/2, p[2]+az[2]*s-ay[2]*s/2, col);
|
||||
dd->vertex(p[0]+az[0]*s-ax[0]*s/3, p[1]+az[1]*s-ax[1]*s/3, p[2]+az[2]*s-ax[2]*s/3, col);
|
||||
|
||||
}
|
||||
|
||||
void duAppendArc(struct duDebugDraw* dd, const float x0, const float y0, const float z0,
|
||||
const float x1, const float y1, const float z1, const float h,
|
||||
const float as0, const float as1, unsigned int col)
|
||||
{
|
||||
if (!dd) return;
|
||||
static const int NUM_ARC_PTS = 8;
|
||||
static const float PAD = 0.05f;
|
||||
static const float ARC_PTS_SCALE = (1.0f-PAD*2) / (float)NUM_ARC_PTS;
|
||||
const float dx = x1 - x0;
|
||||
const float dy = y1 - y0;
|
||||
const float dz = z1 - z0;
|
||||
const float len = sqrtf(dx*dx + dy*dy + dz*dz);
|
||||
float prev[3];
|
||||
evalArc(x0,y0,z0, dx,dy,dz, len*h, PAD, prev);
|
||||
for (int i = 1; i <= NUM_ARC_PTS; ++i)
|
||||
{
|
||||
const float u = PAD + i * ARC_PTS_SCALE;
|
||||
float pt[3];
|
||||
evalArc(x0,y0,z0, dx,dy,dz, len*h, u, pt);
|
||||
dd->vertex(prev[0],prev[1],prev[2], col);
|
||||
dd->vertex(pt[0],pt[1],pt[2], col);
|
||||
prev[0] = pt[0]; prev[1] = pt[1]; prev[2] = pt[2];
|
||||
}
|
||||
|
||||
// End arrows
|
||||
if (as0 > 0.001f)
|
||||
{
|
||||
float p[3], q[3];
|
||||
evalArc(x0,y0,z0, dx,dy,dz, len*h, PAD, p);
|
||||
evalArc(x0,y0,z0, dx,dy,dz, len*h, PAD+0.05f, q);
|
||||
appendArrowHead(dd, p, q, as0, col);
|
||||
}
|
||||
|
||||
if (as1 > 0.001f)
|
||||
{
|
||||
float p[3], q[3];
|
||||
evalArc(x0,y0,z0, dx,dy,dz, len*h, 1-PAD, p);
|
||||
evalArc(x0,y0,z0, dx,dy,dz, len*h, 1-(PAD+0.05f), q);
|
||||
appendArrowHead(dd, p, q, as1, col);
|
||||
}
|
||||
}
|
||||
|
||||
void duAppendArrow(struct duDebugDraw* dd, const float x0, const float y0, const float z0,
|
||||
const float x1, const float y1, const float z1,
|
||||
const float as0, const float as1, unsigned int col)
|
||||
{
|
||||
if (!dd) return;
|
||||
|
||||
dd->vertex(x0,y0,z0, col);
|
||||
dd->vertex(x1,y1,z1, col);
|
||||
|
||||
// End arrows
|
||||
const float p[3] = {x0,y0,z0}, q[3] = {x1,y1,z1};
|
||||
if (as0 > 0.001f)
|
||||
appendArrowHead(dd, p, q, as0, col);
|
||||
if (as1 > 0.001f)
|
||||
appendArrowHead(dd, q, p, as1, col);
|
||||
}
|
||||
|
||||
void duAppendCircle(struct duDebugDraw* dd, const float x, const float y, const float z,
|
||||
const float r, unsigned int col)
|
||||
{
|
||||
if (!dd) return;
|
||||
static const int NUM_SEG = 40;
|
||||
static float dir[40*2];
|
||||
static bool init = false;
|
||||
if (!init)
|
||||
{
|
||||
init = true;
|
||||
for (int i = 0; i < NUM_SEG; ++i)
|
||||
{
|
||||
const float a = (float)i/(float)NUM_SEG*DU_PI*2;
|
||||
dir[i*2] = cosf(a);
|
||||
dir[i*2+1] = sinf(a);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0, j = NUM_SEG-1; i < NUM_SEG; j = i++)
|
||||
{
|
||||
dd->vertex(x+dir[j*2+0]*r, y, z+dir[j*2+1]*r, col);
|
||||
dd->vertex(x+dir[i*2+0]*r, y, z+dir[i*2+1]*r, col);
|
||||
}
|
||||
}
|
||||
|
||||
void duAppendCross(struct duDebugDraw* dd, const float x, const float y, const float z,
|
||||
const float s, unsigned int col)
|
||||
{
|
||||
if (!dd) return;
|
||||
dd->vertex(x-s,y,z, col);
|
||||
dd->vertex(x+s,y,z, col);
|
||||
dd->vertex(x,y-s,z, col);
|
||||
dd->vertex(x,y+s,z, col);
|
||||
dd->vertex(x,y,z-s, col);
|
||||
dd->vertex(x,y,z+s, col);
|
||||
}
|
||||
|
||||
duDisplayList::duDisplayList(int cap) :
|
||||
m_pos(0),
|
||||
m_color(0),
|
||||
m_size(0),
|
||||
m_cap(0),
|
||||
m_prim(DU_DRAW_LINES),
|
||||
m_primSize(1.0f),
|
||||
m_depthMask(true)
|
||||
{
|
||||
if (cap < 8)
|
||||
cap = 8;
|
||||
resize(cap);
|
||||
}
|
||||
|
||||
duDisplayList::~duDisplayList()
|
||||
{
|
||||
delete [] m_pos;
|
||||
delete [] m_color;
|
||||
}
|
||||
|
||||
void duDisplayList::resize(int cap)
|
||||
{
|
||||
float* newPos = new float[cap*3];
|
||||
if (m_size)
|
||||
memcpy(newPos, m_pos, sizeof(float)*3*m_size);
|
||||
delete [] m_pos;
|
||||
m_pos = newPos;
|
||||
|
||||
unsigned int* newColor = new unsigned int[cap];
|
||||
if (m_size)
|
||||
memcpy(newColor, m_color, sizeof(unsigned int)*m_size);
|
||||
delete [] m_color;
|
||||
m_color = newColor;
|
||||
|
||||
m_cap = cap;
|
||||
}
|
||||
|
||||
void duDisplayList::clear()
|
||||
{
|
||||
m_size = 0;
|
||||
}
|
||||
|
||||
void duDisplayList::depthMask(bool state)
|
||||
{
|
||||
m_depthMask = state;
|
||||
}
|
||||
|
||||
void duDisplayList::begin(duDebugDrawPrimitives prim, float size)
|
||||
{
|
||||
clear();
|
||||
m_prim = prim;
|
||||
m_primSize = size;
|
||||
}
|
||||
|
||||
void duDisplayList::vertex(const float x, const float y, const float z, unsigned int color)
|
||||
{
|
||||
if (m_size+1 >= m_cap)
|
||||
resize(m_cap*2);
|
||||
float* p = &m_pos[m_size*3];
|
||||
p[0] = x;
|
||||
p[1] = y;
|
||||
p[2] = z;
|
||||
m_color[m_size] = color;
|
||||
m_size++;
|
||||
}
|
||||
|
||||
void duDisplayList::vertex(const float* pos, unsigned int color)
|
||||
{
|
||||
vertex(pos[0],pos[1],pos[2],color);
|
||||
}
|
||||
|
||||
void duDisplayList::end()
|
||||
{
|
||||
}
|
||||
|
||||
void duDisplayList::draw(struct duDebugDraw* dd)
|
||||
{
|
||||
if (!dd) return;
|
||||
if (!m_size) return;
|
||||
dd->depthMask(m_depthMask);
|
||||
dd->begin(m_prim, m_primSize);
|
||||
for (int i = 0; i < m_size; ++i)
|
||||
dd->vertex(&m_pos[i*3], m_color[i]);
|
||||
dd->end();
|
||||
}
|
||||
@@ -0,0 +1,864 @@
|
||||
//
|
||||
// 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 "DebugDraw.h"
|
||||
#include "DetourDebugDraw.h"
|
||||
#include "DetourNavMesh.h"
|
||||
#include "DetourCommon.h"
|
||||
#include "DetourNode.h"
|
||||
|
||||
|
||||
static float distancePtLine2d(const float* pt, const float* p, const float* q)
|
||||
{
|
||||
float pqx = q[0] - p[0];
|
||||
float pqz = q[2] - p[2];
|
||||
float dx = pt[0] - p[0];
|
||||
float dz = pt[2] - p[2];
|
||||
float d = pqx*pqx + pqz*pqz;
|
||||
float t = pqx*dx + pqz*dz;
|
||||
if (d != 0) t /= d;
|
||||
dx = p[0] + t*pqx - pt[0];
|
||||
dz = p[2] + t*pqz - pt[2];
|
||||
return dx*dx + dz*dz;
|
||||
}
|
||||
|
||||
static void drawPolyBoundaries(duDebugDraw* dd, const dtMeshTile* tile,
|
||||
const unsigned int col, const float linew,
|
||||
bool inner)
|
||||
{
|
||||
static const float thr = 0.01f*0.01f;
|
||||
|
||||
dd->begin(DU_DRAW_LINES, linew);
|
||||
|
||||
for (int i = 0; i < tile->header->polyCount; ++i)
|
||||
{
|
||||
const dtPoly* p = &tile->polys[i];
|
||||
|
||||
if (p->getType() == DT_POLYTYPE_OFFMESH_CONNECTION) continue;
|
||||
|
||||
const dtPolyDetail* pd = &tile->detailMeshes[i];
|
||||
|
||||
for (int j = 0, nj = (int)p->vertCount; j < nj; ++j)
|
||||
{
|
||||
unsigned int c = col;
|
||||
if (inner)
|
||||
{
|
||||
if (p->neis[j] == 0) continue;
|
||||
if (p->neis[j] & DT_EXT_LINK)
|
||||
{
|
||||
bool con = false;
|
||||
for (unsigned int k = p->firstLink; k != DT_NULL_LINK; k = tile->links[k].next)
|
||||
{
|
||||
if (tile->links[k].edge == j)
|
||||
{
|
||||
con = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (con)
|
||||
c = duRGBA(255,255,255,48);
|
||||
else
|
||||
c = duRGBA(0,0,0,48);
|
||||
}
|
||||
else
|
||||
c = duRGBA(0,48,64,32);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (p->neis[j] != 0) continue;
|
||||
}
|
||||
|
||||
const float* v0 = &tile->verts[p->verts[j]*3];
|
||||
const float* v1 = &tile->verts[p->verts[(j+1) % nj]*3];
|
||||
|
||||
// Draw detail mesh edges which align with the actual poly edge.
|
||||
// This is really slow.
|
||||
for (int k = 0; k < pd->triCount; ++k)
|
||||
{
|
||||
const unsigned char* t = &tile->detailTris[(pd->triBase+k)*4];
|
||||
const float* tv[3];
|
||||
for (int m = 0; m < 3; ++m)
|
||||
{
|
||||
if (t[m] < p->vertCount)
|
||||
tv[m] = &tile->verts[p->verts[t[m]]*3];
|
||||
else
|
||||
tv[m] = &tile->detailVerts[(pd->vertBase+(t[m]-p->vertCount))*3];
|
||||
}
|
||||
for (int m = 0, n = 2; m < 3; n=m++)
|
||||
{
|
||||
if ((dtGetDetailTriEdgeFlags(t[3], n) & DT_DETAIL_EDGE_BOUNDARY) == 0)
|
||||
continue;
|
||||
|
||||
if (distancePtLine2d(tv[n],v0,v1) < thr &&
|
||||
distancePtLine2d(tv[m],v0,v1) < thr)
|
||||
{
|
||||
dd->vertex(tv[n], c);
|
||||
dd->vertex(tv[m], c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
dd->end();
|
||||
}
|
||||
|
||||
static void drawMeshTile(duDebugDraw* dd, const dtNavMesh& mesh, const dtNavMeshQuery* query,
|
||||
const dtMeshTile* tile, unsigned char flags)
|
||||
{
|
||||
dtPolyRef base = mesh.getPolyRefBase(tile);
|
||||
|
||||
int tileNum = mesh.decodePolyIdTile(base);
|
||||
const unsigned int tileColor = duIntToCol(tileNum, 128);
|
||||
|
||||
dd->depthMask(false);
|
||||
|
||||
dd->begin(DU_DRAW_TRIS);
|
||||
for (int i = 0; i < tile->header->polyCount; ++i)
|
||||
{
|
||||
const dtPoly* p = &tile->polys[i];
|
||||
if (p->getType() == DT_POLYTYPE_OFFMESH_CONNECTION) // Skip off-mesh links.
|
||||
continue;
|
||||
|
||||
const dtPolyDetail* pd = &tile->detailMeshes[i];
|
||||
|
||||
unsigned int col;
|
||||
if (query && query->isInClosedList(base | (dtPolyRef)i))
|
||||
col = duRGBA(255,196,0,64);
|
||||
else
|
||||
{
|
||||
if (flags & DU_DRAWNAVMESH_COLOR_TILES)
|
||||
col = tileColor;
|
||||
else
|
||||
col = duTransCol(dd->areaToCol(p->getArea()), 64);
|
||||
}
|
||||
|
||||
for (int j = 0; j < pd->triCount; ++j)
|
||||
{
|
||||
const unsigned char* t = &tile->detailTris[(pd->triBase+j)*4];
|
||||
for (int k = 0; k < 3; ++k)
|
||||
{
|
||||
if (t[k] < p->vertCount)
|
||||
dd->vertex(&tile->verts[p->verts[t[k]]*3], col);
|
||||
else
|
||||
dd->vertex(&tile->detailVerts[(pd->vertBase+t[k]-p->vertCount)*3], col);
|
||||
}
|
||||
}
|
||||
}
|
||||
dd->end();
|
||||
|
||||
// Draw inter poly boundaries
|
||||
drawPolyBoundaries(dd, tile, duRGBA(0,48,64,32), 1.5f, true);
|
||||
|
||||
// Draw outer poly boundaries
|
||||
drawPolyBoundaries(dd, tile, duRGBA(0,48,64,220), 2.5f, false);
|
||||
|
||||
if (flags & DU_DRAWNAVMESH_OFFMESHCONS)
|
||||
{
|
||||
dd->begin(DU_DRAW_LINES, 2.0f);
|
||||
for (int i = 0; i < tile->header->polyCount; ++i)
|
||||
{
|
||||
const dtPoly* p = &tile->polys[i];
|
||||
if (p->getType() != DT_POLYTYPE_OFFMESH_CONNECTION) // Skip regular polys.
|
||||
continue;
|
||||
|
||||
unsigned int col, col2;
|
||||
if (query && query->isInClosedList(base | (dtPolyRef)i))
|
||||
col = duRGBA(255,196,0,220);
|
||||
else
|
||||
col = duDarkenCol(duTransCol(dd->areaToCol(p->getArea()), 220));
|
||||
|
||||
const dtOffMeshConnection* con = &tile->offMeshCons[i - tile->header->offMeshBase];
|
||||
const float* va = &tile->verts[p->verts[0]*3];
|
||||
const float* vb = &tile->verts[p->verts[1]*3];
|
||||
|
||||
// Check to see if start and end end-points have links.
|
||||
bool startSet = false;
|
||||
bool endSet = false;
|
||||
for (unsigned int k = p->firstLink; k != DT_NULL_LINK; k = tile->links[k].next)
|
||||
{
|
||||
if (tile->links[k].edge == 0)
|
||||
startSet = true;
|
||||
if (tile->links[k].edge == 1)
|
||||
endSet = true;
|
||||
}
|
||||
|
||||
// End points and their on-mesh locations.
|
||||
dd->vertex(va[0],va[1],va[2], col);
|
||||
dd->vertex(con->pos[0],con->pos[1],con->pos[2], col);
|
||||
col2 = startSet ? col : duRGBA(220,32,16,196);
|
||||
duAppendCircle(dd, con->pos[0],con->pos[1]+0.1f,con->pos[2], con->rad, col2);
|
||||
|
||||
dd->vertex(vb[0],vb[1],vb[2], col);
|
||||
dd->vertex(con->pos[3],con->pos[4],con->pos[5], col);
|
||||
col2 = endSet ? col : duRGBA(220,32,16,196);
|
||||
duAppendCircle(dd, con->pos[3],con->pos[4]+0.1f,con->pos[5], con->rad, col2);
|
||||
|
||||
// End point vertices.
|
||||
dd->vertex(con->pos[0],con->pos[1],con->pos[2], duRGBA(0,48,64,196));
|
||||
dd->vertex(con->pos[0],con->pos[1]+0.2f,con->pos[2], duRGBA(0,48,64,196));
|
||||
|
||||
dd->vertex(con->pos[3],con->pos[4],con->pos[5], duRGBA(0,48,64,196));
|
||||
dd->vertex(con->pos[3],con->pos[4]+0.2f,con->pos[5], duRGBA(0,48,64,196));
|
||||
|
||||
// Connection arc.
|
||||
duAppendArc(dd, con->pos[0],con->pos[1],con->pos[2], con->pos[3],con->pos[4],con->pos[5], 0.25f,
|
||||
(con->flags & 1) ? 0.6f : 0, 0.6f, col);
|
||||
}
|
||||
dd->end();
|
||||
}
|
||||
|
||||
const unsigned int vcol = duRGBA(0,0,0,196);
|
||||
dd->begin(DU_DRAW_POINTS, 3.0f);
|
||||
for (int i = 0; i < tile->header->vertCount; ++i)
|
||||
{
|
||||
const float* v = &tile->verts[i*3];
|
||||
dd->vertex(v[0], v[1], v[2], vcol);
|
||||
}
|
||||
dd->end();
|
||||
|
||||
dd->depthMask(true);
|
||||
}
|
||||
|
||||
void duDebugDrawNavMesh(duDebugDraw* dd, const dtNavMesh& mesh, unsigned char flags)
|
||||
{
|
||||
if (!dd) return;
|
||||
|
||||
for (int i = 0; i < mesh.getMaxTiles(); ++i)
|
||||
{
|
||||
const dtMeshTile* tile = mesh.getTile(i);
|
||||
if (!tile->header) continue;
|
||||
drawMeshTile(dd, mesh, 0, tile, flags);
|
||||
}
|
||||
}
|
||||
|
||||
void duDebugDrawNavMeshWithClosedList(struct duDebugDraw* dd, const dtNavMesh& mesh, const dtNavMeshQuery& query, unsigned char flags)
|
||||
{
|
||||
if (!dd) return;
|
||||
|
||||
const dtNavMeshQuery* q = (flags & DU_DRAWNAVMESH_CLOSEDLIST) ? &query : 0;
|
||||
|
||||
for (int i = 0; i < mesh.getMaxTiles(); ++i)
|
||||
{
|
||||
const dtMeshTile* tile = mesh.getTile(i);
|
||||
if (!tile->header) continue;
|
||||
drawMeshTile(dd, mesh, q, tile, flags);
|
||||
}
|
||||
}
|
||||
|
||||
void duDebugDrawNavMeshNodes(struct duDebugDraw* dd, const dtNavMeshQuery& query)
|
||||
{
|
||||
if (!dd) return;
|
||||
|
||||
const dtNodePool* pool = query.getNodePool();
|
||||
if (pool)
|
||||
{
|
||||
const float off = 0.5f;
|
||||
dd->begin(DU_DRAW_POINTS, 4.0f);
|
||||
for (int i = 0; i < pool->getHashSize(); ++i)
|
||||
{
|
||||
for (dtNodeIndex j = pool->getFirst(i); j != DT_NULL_IDX; j = pool->getNext(j))
|
||||
{
|
||||
const dtNode* node = pool->getNodeAtIdx(j+1);
|
||||
if (!node) continue;
|
||||
dd->vertex(node->pos[0],node->pos[1]+off,node->pos[2], duRGBA(255,192,0,255));
|
||||
}
|
||||
}
|
||||
dd->end();
|
||||
|
||||
dd->begin(DU_DRAW_LINES, 2.0f);
|
||||
for (int i = 0; i < pool->getHashSize(); ++i)
|
||||
{
|
||||
for (dtNodeIndex j = pool->getFirst(i); j != DT_NULL_IDX; j = pool->getNext(j))
|
||||
{
|
||||
const dtNode* node = pool->getNodeAtIdx(j+1);
|
||||
if (!node) continue;
|
||||
if (!node->pidx) continue;
|
||||
const dtNode* parent = pool->getNodeAtIdx(node->pidx);
|
||||
if (!parent) continue;
|
||||
dd->vertex(node->pos[0],node->pos[1]+off,node->pos[2], duRGBA(255,192,0,128));
|
||||
dd->vertex(parent->pos[0],parent->pos[1]+off,parent->pos[2], duRGBA(255,192,0,128));
|
||||
}
|
||||
}
|
||||
dd->end();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void drawMeshTileBVTree(duDebugDraw* dd, const dtMeshTile* tile)
|
||||
{
|
||||
// Draw BV nodes.
|
||||
const float cs = 1.0f / tile->header->bvQuantFactor;
|
||||
dd->begin(DU_DRAW_LINES, 1.0f);
|
||||
for (int i = 0; i < tile->header->bvNodeCount; ++i)
|
||||
{
|
||||
const dtBVNode* n = &tile->bvTree[i];
|
||||
if (n->i < 0) // Leaf indices are positive.
|
||||
continue;
|
||||
duAppendBoxWire(dd, tile->header->bmin[0] + n->bmin[0]*cs,
|
||||
tile->header->bmin[1] + n->bmin[1]*cs,
|
||||
tile->header->bmin[2] + n->bmin[2]*cs,
|
||||
tile->header->bmin[0] + n->bmax[0]*cs,
|
||||
tile->header->bmin[1] + n->bmax[1]*cs,
|
||||
tile->header->bmin[2] + n->bmax[2]*cs,
|
||||
duRGBA(255,255,255,128));
|
||||
}
|
||||
dd->end();
|
||||
}
|
||||
|
||||
void duDebugDrawNavMeshBVTree(duDebugDraw* dd, const dtNavMesh& mesh)
|
||||
{
|
||||
if (!dd) return;
|
||||
|
||||
for (int i = 0; i < mesh.getMaxTiles(); ++i)
|
||||
{
|
||||
const dtMeshTile* tile = mesh.getTile(i);
|
||||
if (!tile->header) continue;
|
||||
drawMeshTileBVTree(dd, tile);
|
||||
}
|
||||
}
|
||||
|
||||
static void drawMeshTilePortal(duDebugDraw* dd, const dtMeshTile* tile)
|
||||
{
|
||||
// Draw portals
|
||||
const float padx = 0.04f;
|
||||
const float pady = tile->header->walkableClimb;
|
||||
|
||||
dd->begin(DU_DRAW_LINES, 2.0f);
|
||||
|
||||
for (int side = 0; side < 8; ++side)
|
||||
{
|
||||
unsigned short m = DT_EXT_LINK | (unsigned short)side;
|
||||
|
||||
for (int i = 0; i < tile->header->polyCount; ++i)
|
||||
{
|
||||
dtPoly* poly = &tile->polys[i];
|
||||
|
||||
// Create new links.
|
||||
const int nv = poly->vertCount;
|
||||
for (int j = 0; j < nv; ++j)
|
||||
{
|
||||
// Skip edges which do not point to the right side.
|
||||
if (poly->neis[j] != m)
|
||||
continue;
|
||||
|
||||
// Create new links
|
||||
const float* va = &tile->verts[poly->verts[j]*3];
|
||||
const float* vb = &tile->verts[poly->verts[(j+1) % nv]*3];
|
||||
|
||||
if (side == 0 || side == 4)
|
||||
{
|
||||
unsigned int col = side == 0 ? duRGBA(128,0,0,128) : duRGBA(128,0,128,128);
|
||||
|
||||
const float x = va[0] + ((side == 0) ? -padx : padx);
|
||||
|
||||
dd->vertex(x,va[1]-pady,va[2], col);
|
||||
dd->vertex(x,va[1]+pady,va[2], col);
|
||||
|
||||
dd->vertex(x,va[1]+pady,va[2], col);
|
||||
dd->vertex(x,vb[1]+pady,vb[2], col);
|
||||
|
||||
dd->vertex(x,vb[1]+pady,vb[2], col);
|
||||
dd->vertex(x,vb[1]-pady,vb[2], col);
|
||||
|
||||
dd->vertex(x,vb[1]-pady,vb[2], col);
|
||||
dd->vertex(x,va[1]-pady,va[2], col);
|
||||
}
|
||||
else if (side == 2 || side == 6)
|
||||
{
|
||||
unsigned int col = side == 2 ? duRGBA(0,128,0,128) : duRGBA(0,128,128,128);
|
||||
|
||||
const float z = va[2] + ((side == 2) ? -padx : padx);
|
||||
|
||||
dd->vertex(va[0],va[1]-pady,z, col);
|
||||
dd->vertex(va[0],va[1]+pady,z, col);
|
||||
|
||||
dd->vertex(va[0],va[1]+pady,z, col);
|
||||
dd->vertex(vb[0],vb[1]+pady,z, col);
|
||||
|
||||
dd->vertex(vb[0],vb[1]+pady,z, col);
|
||||
dd->vertex(vb[0],vb[1]-pady,z, col);
|
||||
|
||||
dd->vertex(vb[0],vb[1]-pady,z, col);
|
||||
dd->vertex(va[0],va[1]-pady,z, col);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dd->end();
|
||||
}
|
||||
|
||||
void duDebugDrawNavMeshPortals(duDebugDraw* dd, const dtNavMesh& mesh)
|
||||
{
|
||||
if (!dd) return;
|
||||
|
||||
for (int i = 0; i < mesh.getMaxTiles(); ++i)
|
||||
{
|
||||
const dtMeshTile* tile = mesh.getTile(i);
|
||||
if (!tile->header) continue;
|
||||
drawMeshTilePortal(dd, tile);
|
||||
}
|
||||
}
|
||||
|
||||
void duDebugDrawNavMeshPolysWithFlags(struct duDebugDraw* dd, const dtNavMesh& mesh,
|
||||
const unsigned short polyFlags, const unsigned int col)
|
||||
{
|
||||
if (!dd) return;
|
||||
|
||||
for (int i = 0; i < mesh.getMaxTiles(); ++i)
|
||||
{
|
||||
const dtMeshTile* tile = mesh.getTile(i);
|
||||
if (!tile->header) continue;
|
||||
dtPolyRef base = mesh.getPolyRefBase(tile);
|
||||
|
||||
for (int j = 0; j < tile->header->polyCount; ++j)
|
||||
{
|
||||
const dtPoly* p = &tile->polys[j];
|
||||
if ((p->flags & polyFlags) == 0) continue;
|
||||
duDebugDrawNavMeshPoly(dd, mesh, base|(dtPolyRef)j, col);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void duDebugDrawNavMeshPoly(duDebugDraw* dd, const dtNavMesh& mesh, dtPolyRef ref, const unsigned int col)
|
||||
{
|
||||
if (!dd) return;
|
||||
|
||||
const dtMeshTile* tile = 0;
|
||||
const dtPoly* poly = 0;
|
||||
if (dtStatusFailed(mesh.getTileAndPolyByRef(ref, &tile, &poly)))
|
||||
return;
|
||||
|
||||
dd->depthMask(false);
|
||||
|
||||
const unsigned int c = duTransCol(col, 64);
|
||||
const unsigned int ip = (unsigned int)(poly - tile->polys);
|
||||
|
||||
if (poly->getType() == DT_POLYTYPE_OFFMESH_CONNECTION)
|
||||
{
|
||||
dtOffMeshConnection* con = &tile->offMeshCons[ip - tile->header->offMeshBase];
|
||||
|
||||
dd->begin(DU_DRAW_LINES, 2.0f);
|
||||
|
||||
// Connection arc.
|
||||
duAppendArc(dd, con->pos[0],con->pos[1],con->pos[2], con->pos[3],con->pos[4],con->pos[5], 0.25f,
|
||||
(con->flags & 1) ? 0.6f : 0.0f, 0.6f, c);
|
||||
|
||||
dd->end();
|
||||
}
|
||||
else
|
||||
{
|
||||
const dtPolyDetail* pd = &tile->detailMeshes[ip];
|
||||
|
||||
dd->begin(DU_DRAW_TRIS);
|
||||
for (int i = 0; i < pd->triCount; ++i)
|
||||
{
|
||||
const unsigned char* t = &tile->detailTris[(pd->triBase+i)*4];
|
||||
for (int j = 0; j < 3; ++j)
|
||||
{
|
||||
if (t[j] < poly->vertCount)
|
||||
dd->vertex(&tile->verts[poly->verts[t[j]]*3], c);
|
||||
else
|
||||
dd->vertex(&tile->detailVerts[(pd->vertBase+t[j]-poly->vertCount)*3], c);
|
||||
}
|
||||
}
|
||||
dd->end();
|
||||
}
|
||||
|
||||
dd->depthMask(true);
|
||||
|
||||
}
|
||||
|
||||
static void debugDrawTileCachePortals(struct duDebugDraw* dd, const dtTileCacheLayer& layer, const float cs, const float ch)
|
||||
{
|
||||
const int w = (int)layer.header->width;
|
||||
const int h = (int)layer.header->height;
|
||||
const float* bmin = layer.header->bmin;
|
||||
|
||||
// Portals
|
||||
unsigned int pcol = duRGBA(255,255,255,255);
|
||||
|
||||
const int segs[4*4] = {0,0,0,1, 0,1,1,1, 1,1,1,0, 1,0,0,0};
|
||||
|
||||
// Layer portals
|
||||
dd->begin(DU_DRAW_LINES, 2.0f);
|
||||
for (int y = 0; y < h; ++y)
|
||||
{
|
||||
for (int x = 0; x < w; ++x)
|
||||
{
|
||||
const int idx = x+y*w;
|
||||
const int lh = (int)layer.heights[idx];
|
||||
if (lh == 0xff) continue;
|
||||
|
||||
for (int dir = 0; dir < 4; ++dir)
|
||||
{
|
||||
if (layer.cons[idx] & (1<<(dir+4)))
|
||||
{
|
||||
const int* seg = &segs[dir*4];
|
||||
const float ax = bmin[0] + (x+seg[0])*cs;
|
||||
const float ay = bmin[1] + (lh+2)*ch;
|
||||
const float az = bmin[2] + (y+seg[1])*cs;
|
||||
const float bx = bmin[0] + (x+seg[2])*cs;
|
||||
const float by = bmin[1] + (lh+2)*ch;
|
||||
const float bz = bmin[2] + (y+seg[3])*cs;
|
||||
dd->vertex(ax, ay, az, pcol);
|
||||
dd->vertex(bx, by, bz, pcol);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
dd->end();
|
||||
}
|
||||
|
||||
void duDebugDrawTileCacheLayerAreas(struct duDebugDraw* dd, const dtTileCacheLayer& layer, const float cs, const float ch)
|
||||
{
|
||||
const int w = (int)layer.header->width;
|
||||
const int h = (int)layer.header->height;
|
||||
const float* bmin = layer.header->bmin;
|
||||
const float* bmax = layer.header->bmax;
|
||||
const int idx = layer.header->tlayer;
|
||||
|
||||
unsigned int color = duIntToCol(idx+1, 255);
|
||||
|
||||
// Layer bounds
|
||||
float lbmin[3], lbmax[3];
|
||||
lbmin[0] = bmin[0] + layer.header->minx*cs;
|
||||
lbmin[1] = bmin[1];
|
||||
lbmin[2] = bmin[2] + layer.header->miny*cs;
|
||||
lbmax[0] = bmin[0] + (layer.header->maxx+1)*cs;
|
||||
lbmax[1] = bmax[1];
|
||||
lbmax[2] = bmin[2] + (layer.header->maxy+1)*cs;
|
||||
duDebugDrawBoxWire(dd, lbmin[0],lbmin[1],lbmin[2], lbmax[0],lbmax[1],lbmax[2], duTransCol(color,128), 2.0f);
|
||||
|
||||
// Layer height
|
||||
dd->begin(DU_DRAW_QUADS);
|
||||
for (int y = 0; y < h; ++y)
|
||||
{
|
||||
for (int x = 0; x < w; ++x)
|
||||
{
|
||||
const int lidx = x+y*w;
|
||||
const int lh = (int)layer.heights[lidx];
|
||||
if (lh == 0xff) continue;
|
||||
|
||||
const unsigned char area = layer.areas[lidx];
|
||||
unsigned int col;
|
||||
if (area == 63)
|
||||
col = duLerpCol(color, duRGBA(0,192,255,64), 32);
|
||||
else if (area == 0)
|
||||
col = duLerpCol(color, duRGBA(0,0,0,64), 32);
|
||||
else
|
||||
col = duLerpCol(color, dd->areaToCol(area), 32);
|
||||
|
||||
const float fx = bmin[0] + x*cs;
|
||||
const float fy = bmin[1] + (lh+1)*ch;
|
||||
const float fz = bmin[2] + y*cs;
|
||||
|
||||
dd->vertex(fx, fy, fz, col);
|
||||
dd->vertex(fx, fy, fz+cs, col);
|
||||
dd->vertex(fx+cs, fy, fz+cs, col);
|
||||
dd->vertex(fx+cs, fy, fz, col);
|
||||
}
|
||||
}
|
||||
dd->end();
|
||||
|
||||
debugDrawTileCachePortals(dd, layer, cs, ch);
|
||||
}
|
||||
|
||||
void duDebugDrawTileCacheLayerRegions(struct duDebugDraw* dd, const dtTileCacheLayer& layer, const float cs, const float ch)
|
||||
{
|
||||
const int w = (int)layer.header->width;
|
||||
const int h = (int)layer.header->height;
|
||||
const float* bmin = layer.header->bmin;
|
||||
const float* bmax = layer.header->bmax;
|
||||
const int idx = layer.header->tlayer;
|
||||
|
||||
unsigned int color = duIntToCol(idx+1, 255);
|
||||
|
||||
// Layer bounds
|
||||
float lbmin[3], lbmax[3];
|
||||
lbmin[0] = bmin[0] + layer.header->minx*cs;
|
||||
lbmin[1] = bmin[1];
|
||||
lbmin[2] = bmin[2] + layer.header->miny*cs;
|
||||
lbmax[0] = bmin[0] + (layer.header->maxx+1)*cs;
|
||||
lbmax[1] = bmax[1];
|
||||
lbmax[2] = bmin[2] + (layer.header->maxy+1)*cs;
|
||||
duDebugDrawBoxWire(dd, lbmin[0],lbmin[1],lbmin[2], lbmax[0],lbmax[1],lbmax[2], duTransCol(color,128), 2.0f);
|
||||
|
||||
// Layer height
|
||||
dd->begin(DU_DRAW_QUADS);
|
||||
for (int y = 0; y < h; ++y)
|
||||
{
|
||||
for (int x = 0; x < w; ++x)
|
||||
{
|
||||
const int lidx = x+y*w;
|
||||
const int lh = (int)layer.heights[lidx];
|
||||
if (lh == 0xff) continue;
|
||||
const unsigned char reg = layer.regs[lidx];
|
||||
|
||||
unsigned int col = duLerpCol(color, duIntToCol(reg, 255), 192);
|
||||
|
||||
const float fx = bmin[0] + x*cs;
|
||||
const float fy = bmin[1] + (lh+1)*ch;
|
||||
const float fz = bmin[2] + y*cs;
|
||||
|
||||
dd->vertex(fx, fy, fz, col);
|
||||
dd->vertex(fx, fy, fz+cs, col);
|
||||
dd->vertex(fx+cs, fy, fz+cs, col);
|
||||
dd->vertex(fx+cs, fy, fz, col);
|
||||
}
|
||||
}
|
||||
dd->end();
|
||||
|
||||
debugDrawTileCachePortals(dd, layer, cs, ch);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*struct dtTileCacheContour
|
||||
{
|
||||
int nverts;
|
||||
unsigned char* verts;
|
||||
unsigned char reg;
|
||||
unsigned char area;
|
||||
};
|
||||
|
||||
struct dtTileCacheContourSet
|
||||
{
|
||||
int nconts;
|
||||
dtTileCacheContour* conts;
|
||||
};*/
|
||||
|
||||
void duDebugDrawTileCacheContours(duDebugDraw* dd, const struct dtTileCacheContourSet& lcset,
|
||||
const float* orig, const float cs, const float ch)
|
||||
{
|
||||
if (!dd) return;
|
||||
|
||||
const unsigned char a = 255;// (unsigned char)(alpha*255.0f);
|
||||
|
||||
const int offs[2*4] = {-1,0, 0,1, 1,0, 0,-1};
|
||||
|
||||
dd->begin(DU_DRAW_LINES, 2.0f);
|
||||
|
||||
for (int i = 0; i < lcset.nconts; ++i)
|
||||
{
|
||||
const dtTileCacheContour& c = lcset.conts[i];
|
||||
unsigned int color = 0;
|
||||
|
||||
color = duIntToCol(i, a);
|
||||
|
||||
for (int j = 0; j < c.nverts; ++j)
|
||||
{
|
||||
const int k = (j+1) % c.nverts;
|
||||
const unsigned char* va = &c.verts[j*4];
|
||||
const unsigned char* vb = &c.verts[k*4];
|
||||
const float ax = orig[0] + va[0]*cs;
|
||||
const float ay = orig[1] + (va[1]+1+(i&1))*ch;
|
||||
const float az = orig[2] + va[2]*cs;
|
||||
const float bx = orig[0] + vb[0]*cs;
|
||||
const float by = orig[1] + (vb[1]+1+(i&1))*ch;
|
||||
const float bz = orig[2] + vb[2]*cs;
|
||||
unsigned int col = color;
|
||||
if ((va[3] & 0xf) != 0xf)
|
||||
{
|
||||
// Portal segment
|
||||
col = duRGBA(255,255,255,128);
|
||||
int d = va[3] & 0xf;
|
||||
|
||||
const float cx = (ax+bx)*0.5f;
|
||||
const float cy = (ay+by)*0.5f;
|
||||
const float cz = (az+bz)*0.5f;
|
||||
|
||||
const float dx = cx + offs[d*2+0]*2*cs;
|
||||
const float dy = cy;
|
||||
const float dz = cz + offs[d*2+1]*2*cs;
|
||||
|
||||
dd->vertex(cx,cy,cz,duRGBA(255,0,0,255));
|
||||
dd->vertex(dx,dy,dz,duRGBA(255,0,0,255));
|
||||
}
|
||||
|
||||
duAppendArrow(dd, ax,ay,az, bx,by,bz, 0.0f, cs*0.5f, col);
|
||||
}
|
||||
}
|
||||
dd->end();
|
||||
|
||||
dd->begin(DU_DRAW_POINTS, 4.0f);
|
||||
|
||||
for (int i = 0; i < lcset.nconts; ++i)
|
||||
{
|
||||
const dtTileCacheContour& c = lcset.conts[i];
|
||||
unsigned int color = 0;
|
||||
|
||||
for (int j = 0; j < c.nverts; ++j)
|
||||
{
|
||||
const unsigned char* va = &c.verts[j*4];
|
||||
|
||||
color = duDarkenCol(duIntToCol(i, a));
|
||||
if (va[3] & 0x80)
|
||||
{
|
||||
// Border vertex
|
||||
color = duRGBA(255,0,0,255);
|
||||
}
|
||||
|
||||
float fx = orig[0] + va[0]*cs;
|
||||
float fy = orig[1] + (va[1]+1+(i&1))*ch;
|
||||
float fz = orig[2] + va[2]*cs;
|
||||
dd->vertex(fx,fy,fz, color);
|
||||
}
|
||||
}
|
||||
dd->end();
|
||||
}
|
||||
|
||||
void duDebugDrawTileCachePolyMesh(duDebugDraw* dd, const struct dtTileCachePolyMesh& lmesh,
|
||||
const float* orig, const float cs, const float ch)
|
||||
{
|
||||
if (!dd) return;
|
||||
|
||||
const int nvp = lmesh.nvp;
|
||||
|
||||
const int offs[2*4] = {-1,0, 0,1, 1,0, 0,-1};
|
||||
|
||||
dd->begin(DU_DRAW_TRIS);
|
||||
|
||||
for (int i = 0; i < lmesh.npolys; ++i)
|
||||
{
|
||||
const unsigned short* p = &lmesh.polys[i*nvp*2];
|
||||
const unsigned char area = lmesh.areas[i];
|
||||
|
||||
unsigned int color;
|
||||
if (area == DT_TILECACHE_WALKABLE_AREA)
|
||||
color = duRGBA(0,192,255,64);
|
||||
else if (area == DT_TILECACHE_NULL_AREA)
|
||||
color = duRGBA(0,0,0,64);
|
||||
else
|
||||
color = dd->areaToCol(area);
|
||||
|
||||
unsigned short vi[3];
|
||||
for (int j = 2; j < nvp; ++j)
|
||||
{
|
||||
if (p[j] == DT_TILECACHE_NULL_IDX) break;
|
||||
vi[0] = p[0];
|
||||
vi[1] = p[j-1];
|
||||
vi[2] = p[j];
|
||||
for (int k = 0; k < 3; ++k)
|
||||
{
|
||||
const unsigned short* v = &lmesh.verts[vi[k]*3];
|
||||
const float x = orig[0] + v[0]*cs;
|
||||
const float y = orig[1] + (v[1]+1)*ch;
|
||||
const float z = orig[2] + v[2]*cs;
|
||||
dd->vertex(x,y,z, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
dd->end();
|
||||
|
||||
// Draw neighbours edges
|
||||
const unsigned int coln = duRGBA(0,48,64,32);
|
||||
dd->begin(DU_DRAW_LINES, 1.5f);
|
||||
for (int i = 0; i < lmesh.npolys; ++i)
|
||||
{
|
||||
const unsigned short* p = &lmesh.polys[i*nvp*2];
|
||||
for (int j = 0; j < nvp; ++j)
|
||||
{
|
||||
if (p[j] == DT_TILECACHE_NULL_IDX) break;
|
||||
if (p[nvp+j] & 0x8000) continue;
|
||||
const int nj = (j+1 >= nvp || p[j+1] == DT_TILECACHE_NULL_IDX) ? 0 : j+1;
|
||||
int vi[2] = {p[j], p[nj]};
|
||||
|
||||
for (int k = 0; k < 2; ++k)
|
||||
{
|
||||
const unsigned short* v = &lmesh.verts[vi[k]*3];
|
||||
const float x = orig[0] + v[0]*cs;
|
||||
const float y = orig[1] + (v[1]+1)*ch + 0.1f;
|
||||
const float z = orig[2] + v[2]*cs;
|
||||
dd->vertex(x, y, z, coln);
|
||||
}
|
||||
}
|
||||
}
|
||||
dd->end();
|
||||
|
||||
// Draw boundary edges
|
||||
const unsigned int colb = duRGBA(0,48,64,220);
|
||||
dd->begin(DU_DRAW_LINES, 2.5f);
|
||||
for (int i = 0; i < lmesh.npolys; ++i)
|
||||
{
|
||||
const unsigned short* p = &lmesh.polys[i*nvp*2];
|
||||
for (int j = 0; j < nvp; ++j)
|
||||
{
|
||||
if (p[j] == DT_TILECACHE_NULL_IDX) break;
|
||||
if ((p[nvp+j] & 0x8000) == 0) continue;
|
||||
const int nj = (j+1 >= nvp || p[j+1] == DT_TILECACHE_NULL_IDX) ? 0 : j+1;
|
||||
int vi[2] = {p[j], p[nj]};
|
||||
|
||||
unsigned int col = colb;
|
||||
if ((p[nvp+j] & 0xf) != 0xf)
|
||||
{
|
||||
const unsigned short* va = &lmesh.verts[vi[0]*3];
|
||||
const unsigned short* vb = &lmesh.verts[vi[1]*3];
|
||||
|
||||
const float ax = orig[0] + va[0]*cs;
|
||||
const float ay = orig[1] + (va[1]+1+(i&1))*ch;
|
||||
const float az = orig[2] + va[2]*cs;
|
||||
const float bx = orig[0] + vb[0]*cs;
|
||||
const float by = orig[1] + (vb[1]+1+(i&1))*ch;
|
||||
const float bz = orig[2] + vb[2]*cs;
|
||||
|
||||
const float cx = (ax+bx)*0.5f;
|
||||
const float cy = (ay+by)*0.5f;
|
||||
const float cz = (az+bz)*0.5f;
|
||||
|
||||
int d = p[nvp+j] & 0xf;
|
||||
|
||||
const float dx = cx + offs[d*2+0]*2*cs;
|
||||
const float dy = cy;
|
||||
const float dz = cz + offs[d*2+1]*2*cs;
|
||||
|
||||
dd->vertex(cx,cy,cz,duRGBA(255,0,0,255));
|
||||
dd->vertex(dx,dy,dz,duRGBA(255,0,0,255));
|
||||
|
||||
col = duRGBA(255,255,255,128);
|
||||
}
|
||||
|
||||
for (int k = 0; k < 2; ++k)
|
||||
{
|
||||
const unsigned short* v = &lmesh.verts[vi[k]*3];
|
||||
const float x = orig[0] + v[0]*cs;
|
||||
const float y = orig[1] + (v[1]+1)*ch + 0.1f;
|
||||
const float z = orig[2] + v[2]*cs;
|
||||
dd->vertex(x, y, z, col);
|
||||
}
|
||||
}
|
||||
}
|
||||
dd->end();
|
||||
|
||||
dd->begin(DU_DRAW_POINTS, 3.0f);
|
||||
const unsigned int colv = duRGBA(0,0,0,220);
|
||||
for (int i = 0; i < lmesh.nverts; ++i)
|
||||
{
|
||||
const unsigned short* v = &lmesh.verts[i*3];
|
||||
const float x = orig[0] + v[0]*cs;
|
||||
const float y = orig[1] + (v[1]+1)*ch + 0.1f;
|
||||
const float z = orig[2] + v[2]*cs;
|
||||
dd->vertex(x,y,z, colv);
|
||||
}
|
||||
dd->end();
|
||||
}
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,449 @@
|
||||
//
|
||||
// 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 <math.h>
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include "Recast.h"
|
||||
#include "RecastAlloc.h"
|
||||
#include "RecastDump.h"
|
||||
|
||||
duFileIO::~duFileIO()
|
||||
{
|
||||
// Defined out of line to fix the weak v-tables warning
|
||||
}
|
||||
|
||||
static void ioprintf(duFileIO* io, const char* format, ...)
|
||||
{
|
||||
char line[256];
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
const int n = vsnprintf(line, sizeof(line), format, ap);
|
||||
va_end(ap);
|
||||
if (n > 0)
|
||||
io->write(line, sizeof(char)*n);
|
||||
}
|
||||
|
||||
bool duDumpPolyMeshToObj(rcPolyMesh& pmesh, duFileIO* io)
|
||||
{
|
||||
if (!io)
|
||||
{
|
||||
printf("duDumpPolyMeshToObj: input IO is null.\n");
|
||||
return false;
|
||||
}
|
||||
if (!io->isWriting())
|
||||
{
|
||||
printf("duDumpPolyMeshToObj: input IO not writing.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
const int nvp = pmesh.nvp;
|
||||
const float cs = pmesh.cs;
|
||||
const float ch = pmesh.ch;
|
||||
const float* orig = pmesh.bmin;
|
||||
|
||||
ioprintf(io, "# Recast Navmesh\n");
|
||||
ioprintf(io, "o NavMesh\n");
|
||||
|
||||
ioprintf(io, "\n");
|
||||
|
||||
for (int i = 0; i < pmesh.nverts; ++i)
|
||||
{
|
||||
const unsigned short* v = &pmesh.verts[i*3];
|
||||
const float x = orig[0] + v[0]*cs;
|
||||
const float y = orig[1] + (v[1]+1)*ch + 0.1f;
|
||||
const float z = orig[2] + v[2]*cs;
|
||||
ioprintf(io, "v %f %f %f\n", x,y,z);
|
||||
}
|
||||
|
||||
ioprintf(io, "\n");
|
||||
|
||||
for (int i = 0; i < pmesh.npolys; ++i)
|
||||
{
|
||||
const unsigned short* p = &pmesh.polys[i*nvp*2];
|
||||
for (int j = 2; j < nvp; ++j)
|
||||
{
|
||||
if (p[j] == RC_MESH_NULL_IDX) break;
|
||||
ioprintf(io, "f %d %d %d\n", p[0]+1, p[j-1]+1, p[j]+1);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool duDumpPolyMeshDetailToObj(rcPolyMeshDetail& dmesh, duFileIO* io)
|
||||
{
|
||||
if (!io)
|
||||
{
|
||||
printf("duDumpPolyMeshDetailToObj: input IO is null.\n");
|
||||
return false;
|
||||
}
|
||||
if (!io->isWriting())
|
||||
{
|
||||
printf("duDumpPolyMeshDetailToObj: input IO not writing.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
ioprintf(io, "# Recast Navmesh\n");
|
||||
ioprintf(io, "o NavMesh\n");
|
||||
|
||||
ioprintf(io, "\n");
|
||||
|
||||
for (int i = 0; i < dmesh.nverts; ++i)
|
||||
{
|
||||
const float* v = &dmesh.verts[i*3];
|
||||
ioprintf(io, "v %f %f %f\n", v[0],v[1],v[2]);
|
||||
}
|
||||
|
||||
ioprintf(io, "\n");
|
||||
|
||||
for (int i = 0; i < dmesh.nmeshes; ++i)
|
||||
{
|
||||
const unsigned int* m = &dmesh.meshes[i*4];
|
||||
const unsigned int bverts = m[0];
|
||||
const unsigned int btris = m[2];
|
||||
const unsigned int ntris = m[3];
|
||||
const unsigned char* tris = &dmesh.tris[btris*4];
|
||||
for (unsigned int j = 0; j < ntris; ++j)
|
||||
{
|
||||
ioprintf(io, "f %d %d %d\n",
|
||||
(int)(bverts+tris[j*4+0])+1,
|
||||
(int)(bverts+tris[j*4+1])+1,
|
||||
(int)(bverts+tris[j*4+2])+1);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static const int CSET_MAGIC = ('c' << 24) | ('s' << 16) | ('e' << 8) | 't';
|
||||
static const int CSET_VERSION = 2;
|
||||
|
||||
bool duDumpContourSet(struct rcContourSet& cset, duFileIO* io)
|
||||
{
|
||||
if (!io)
|
||||
{
|
||||
printf("duDumpContourSet: input IO is null.\n");
|
||||
return false;
|
||||
}
|
||||
if (!io->isWriting())
|
||||
{
|
||||
printf("duDumpContourSet: input IO not writing.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
io->write(&CSET_MAGIC, sizeof(CSET_MAGIC));
|
||||
io->write(&CSET_VERSION, sizeof(CSET_VERSION));
|
||||
|
||||
io->write(&cset.nconts, sizeof(cset.nconts));
|
||||
|
||||
io->write(cset.bmin, sizeof(cset.bmin));
|
||||
io->write(cset.bmax, sizeof(cset.bmax));
|
||||
|
||||
io->write(&cset.cs, sizeof(cset.cs));
|
||||
io->write(&cset.ch, sizeof(cset.ch));
|
||||
|
||||
io->write(&cset.width, sizeof(cset.width));
|
||||
io->write(&cset.height, sizeof(cset.height));
|
||||
io->write(&cset.borderSize, sizeof(cset.borderSize));
|
||||
|
||||
for (int i = 0; i < cset.nconts; ++i)
|
||||
{
|
||||
const rcContour& cont = cset.conts[i];
|
||||
io->write(&cont.nverts, sizeof(cont.nverts));
|
||||
io->write(&cont.nrverts, sizeof(cont.nrverts));
|
||||
io->write(&cont.reg, sizeof(cont.reg));
|
||||
io->write(&cont.area, sizeof(cont.area));
|
||||
io->write(cont.verts, sizeof(int)*4*cont.nverts);
|
||||
io->write(cont.rverts, sizeof(int)*4*cont.nrverts);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool duReadContourSet(struct rcContourSet& cset, duFileIO* io)
|
||||
{
|
||||
if (!io)
|
||||
{
|
||||
printf("duReadContourSet: input IO is null.\n");
|
||||
return false;
|
||||
}
|
||||
if (!io->isReading())
|
||||
{
|
||||
printf("duReadContourSet: input IO not reading.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
int magic = 0;
|
||||
int version = 0;
|
||||
|
||||
io->read(&magic, sizeof(magic));
|
||||
io->read(&version, sizeof(version));
|
||||
|
||||
if (magic != CSET_MAGIC)
|
||||
{
|
||||
printf("duReadContourSet: Bad voodoo.\n");
|
||||
return false;
|
||||
}
|
||||
if (version != CSET_VERSION)
|
||||
{
|
||||
printf("duReadContourSet: Bad version.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
io->read(&cset.nconts, sizeof(cset.nconts));
|
||||
|
||||
cset.conts = (rcContour*)rcAlloc(sizeof(rcContour)*cset.nconts, RC_ALLOC_PERM);
|
||||
if (!cset.conts)
|
||||
{
|
||||
printf("duReadContourSet: Could not alloc contours (%d)\n", cset.nconts);
|
||||
return false;
|
||||
}
|
||||
memset(cset.conts, 0, sizeof(rcContour)*cset.nconts);
|
||||
|
||||
io->read(cset.bmin, sizeof(cset.bmin));
|
||||
io->read(cset.bmax, sizeof(cset.bmax));
|
||||
|
||||
io->read(&cset.cs, sizeof(cset.cs));
|
||||
io->read(&cset.ch, sizeof(cset.ch));
|
||||
|
||||
io->read(&cset.width, sizeof(cset.width));
|
||||
io->read(&cset.height, sizeof(cset.height));
|
||||
io->read(&cset.borderSize, sizeof(cset.borderSize));
|
||||
|
||||
for (int i = 0; i < cset.nconts; ++i)
|
||||
{
|
||||
rcContour& cont = cset.conts[i];
|
||||
io->read(&cont.nverts, sizeof(cont.nverts));
|
||||
io->read(&cont.nrverts, sizeof(cont.nrverts));
|
||||
io->read(&cont.reg, sizeof(cont.reg));
|
||||
io->read(&cont.area, sizeof(cont.area));
|
||||
|
||||
cont.verts = (int*)rcAlloc(sizeof(int)*4*cont.nverts, RC_ALLOC_PERM);
|
||||
if (!cont.verts)
|
||||
{
|
||||
printf("duReadContourSet: Could not alloc contour verts (%d)\n", cont.nverts);
|
||||
return false;
|
||||
}
|
||||
cont.rverts = (int*)rcAlloc(sizeof(int)*4*cont.nrverts, RC_ALLOC_PERM);
|
||||
if (!cont.rverts)
|
||||
{
|
||||
printf("duReadContourSet: Could not alloc contour rverts (%d)\n", cont.nrverts);
|
||||
return false;
|
||||
}
|
||||
|
||||
io->read(cont.verts, sizeof(int)*4*cont.nverts);
|
||||
io->read(cont.rverts, sizeof(int)*4*cont.nrverts);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static const int CHF_MAGIC = ('r' << 24) | ('c' << 16) | ('h' << 8) | 'f';
|
||||
static const int CHF_VERSION = 3;
|
||||
|
||||
bool duDumpCompactHeightfield(struct rcCompactHeightfield& chf, duFileIO* io)
|
||||
{
|
||||
if (!io)
|
||||
{
|
||||
printf("duDumpCompactHeightfield: input IO is null.\n");
|
||||
return false;
|
||||
}
|
||||
if (!io->isWriting())
|
||||
{
|
||||
printf("duDumpCompactHeightfield: input IO not writing.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
io->write(&CHF_MAGIC, sizeof(CHF_MAGIC));
|
||||
io->write(&CHF_VERSION, sizeof(CHF_VERSION));
|
||||
|
||||
io->write(&chf.width, sizeof(chf.width));
|
||||
io->write(&chf.height, sizeof(chf.height));
|
||||
io->write(&chf.spanCount, sizeof(chf.spanCount));
|
||||
|
||||
io->write(&chf.walkableHeight, sizeof(chf.walkableHeight));
|
||||
io->write(&chf.walkableClimb, sizeof(chf.walkableClimb));
|
||||
io->write(&chf.borderSize, sizeof(chf.borderSize));
|
||||
|
||||
io->write(&chf.maxDistance, sizeof(chf.maxDistance));
|
||||
io->write(&chf.maxRegions, sizeof(chf.maxRegions));
|
||||
|
||||
io->write(chf.bmin, sizeof(chf.bmin));
|
||||
io->write(chf.bmax, sizeof(chf.bmax));
|
||||
|
||||
io->write(&chf.cs, sizeof(chf.cs));
|
||||
io->write(&chf.ch, sizeof(chf.ch));
|
||||
|
||||
int tmp = 0;
|
||||
if (chf.cells) tmp |= 1;
|
||||
if (chf.spans) tmp |= 2;
|
||||
if (chf.dist) tmp |= 4;
|
||||
if (chf.areas) tmp |= 8;
|
||||
|
||||
io->write(&tmp, sizeof(tmp));
|
||||
|
||||
if (chf.cells)
|
||||
io->write(chf.cells, sizeof(rcCompactCell)*chf.width*chf.height);
|
||||
if (chf.spans)
|
||||
io->write(chf.spans, sizeof(rcCompactSpan)*chf.spanCount);
|
||||
if (chf.dist)
|
||||
io->write(chf.dist, sizeof(unsigned short)*chf.spanCount);
|
||||
if (chf.areas)
|
||||
io->write(chf.areas, sizeof(unsigned char)*chf.spanCount);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool duReadCompactHeightfield(struct rcCompactHeightfield& chf, duFileIO* io)
|
||||
{
|
||||
if (!io)
|
||||
{
|
||||
printf("duReadCompactHeightfield: input IO is null.\n");
|
||||
return false;
|
||||
}
|
||||
if (!io->isReading())
|
||||
{
|
||||
printf("duReadCompactHeightfield: input IO not reading.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
int magic = 0;
|
||||
int version = 0;
|
||||
|
||||
io->read(&magic, sizeof(magic));
|
||||
io->read(&version, sizeof(version));
|
||||
|
||||
if (magic != CHF_MAGIC)
|
||||
{
|
||||
printf("duReadCompactHeightfield: Bad voodoo.\n");
|
||||
return false;
|
||||
}
|
||||
if (version != CHF_VERSION)
|
||||
{
|
||||
printf("duReadCompactHeightfield: Bad version.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
io->read(&chf.width, sizeof(chf.width));
|
||||
io->read(&chf.height, sizeof(chf.height));
|
||||
io->read(&chf.spanCount, sizeof(chf.spanCount));
|
||||
|
||||
io->read(&chf.walkableHeight, sizeof(chf.walkableHeight));
|
||||
io->read(&chf.walkableClimb, sizeof(chf.walkableClimb));
|
||||
io->read(&chf.borderSize, sizeof(chf.borderSize));
|
||||
|
||||
io->read(&chf.maxDistance, sizeof(chf.maxDistance));
|
||||
io->read(&chf.maxRegions, sizeof(chf.maxRegions));
|
||||
|
||||
io->read(chf.bmin, sizeof(chf.bmin));
|
||||
io->read(chf.bmax, sizeof(chf.bmax));
|
||||
|
||||
io->read(&chf.cs, sizeof(chf.cs));
|
||||
io->read(&chf.ch, sizeof(chf.ch));
|
||||
|
||||
int tmp = 0;
|
||||
io->read(&tmp, sizeof(tmp));
|
||||
|
||||
if (tmp & 1)
|
||||
{
|
||||
chf.cells = (rcCompactCell*)rcAlloc(sizeof(rcCompactCell)*chf.width*chf.height, RC_ALLOC_PERM);
|
||||
if (!chf.cells)
|
||||
{
|
||||
printf("duReadCompactHeightfield: Could not alloc cells (%d)\n", chf.width*chf.height);
|
||||
return false;
|
||||
}
|
||||
io->read(chf.cells, sizeof(rcCompactCell)*chf.width*chf.height);
|
||||
}
|
||||
if (tmp & 2)
|
||||
{
|
||||
chf.spans = (rcCompactSpan*)rcAlloc(sizeof(rcCompactSpan)*chf.spanCount, RC_ALLOC_PERM);
|
||||
if (!chf.spans)
|
||||
{
|
||||
printf("duReadCompactHeightfield: Could not alloc spans (%d)\n", chf.spanCount);
|
||||
return false;
|
||||
}
|
||||
io->read(chf.spans, sizeof(rcCompactSpan)*chf.spanCount);
|
||||
}
|
||||
if (tmp & 4)
|
||||
{
|
||||
chf.dist = (unsigned short*)rcAlloc(sizeof(unsigned short)*chf.spanCount, RC_ALLOC_PERM);
|
||||
if (!chf.dist)
|
||||
{
|
||||
printf("duReadCompactHeightfield: Could not alloc dist (%d)\n", chf.spanCount);
|
||||
return false;
|
||||
}
|
||||
io->read(chf.dist, sizeof(unsigned short)*chf.spanCount);
|
||||
}
|
||||
if (tmp & 8)
|
||||
{
|
||||
chf.areas = (unsigned char*)rcAlloc(sizeof(unsigned char)*chf.spanCount, RC_ALLOC_PERM);
|
||||
if (!chf.areas)
|
||||
{
|
||||
printf("duReadCompactHeightfield: Could not alloc areas (%d)\n", chf.spanCount);
|
||||
return false;
|
||||
}
|
||||
io->read(chf.areas, sizeof(unsigned char)*chf.spanCount);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static void logLine(rcContext& ctx, rcTimerLabel label, const char* name, const float pc)
|
||||
{
|
||||
const int t = ctx.getAccumulatedTime(label);
|
||||
if (t < 0) return;
|
||||
ctx.log(RC_LOG_PROGRESS, "%s:\t%.2fms\t(%.1f%%)", name, t/1000.0f, t*pc);
|
||||
}
|
||||
|
||||
void duLogBuildTimes(rcContext& ctx, const int totalTimeUsec)
|
||||
{
|
||||
const float pc = 100.0f / totalTimeUsec;
|
||||
|
||||
ctx.log(RC_LOG_PROGRESS, "Build Times");
|
||||
logLine(ctx, RC_TIMER_RASTERIZE_TRIANGLES, "- Rasterize", pc);
|
||||
logLine(ctx, RC_TIMER_BUILD_COMPACTHEIGHTFIELD, "- Build Compact", pc);
|
||||
logLine(ctx, RC_TIMER_FILTER_BORDER, "- Filter Border", pc);
|
||||
logLine(ctx, RC_TIMER_FILTER_WALKABLE, "- Filter Walkable", pc);
|
||||
logLine(ctx, RC_TIMER_ERODE_AREA, "- Erode Area", pc);
|
||||
logLine(ctx, RC_TIMER_MEDIAN_AREA, "- Median Area", pc);
|
||||
logLine(ctx, RC_TIMER_MARK_BOX_AREA, "- Mark Box Area", pc);
|
||||
logLine(ctx, RC_TIMER_MARK_CONVEXPOLY_AREA, "- Mark Convex Area", pc);
|
||||
logLine(ctx, RC_TIMER_MARK_CYLINDER_AREA, "- Mark Cylinder Area", pc);
|
||||
logLine(ctx, RC_TIMER_BUILD_DISTANCEFIELD, "- Build Distance Field", pc);
|
||||
logLine(ctx, RC_TIMER_BUILD_DISTANCEFIELD_DIST, " - Distance", pc);
|
||||
logLine(ctx, RC_TIMER_BUILD_DISTANCEFIELD_BLUR, " - Blur", pc);
|
||||
logLine(ctx, RC_TIMER_BUILD_REGIONS, "- Build Regions", pc);
|
||||
logLine(ctx, RC_TIMER_BUILD_REGIONS_WATERSHED, " - Watershed", pc);
|
||||
logLine(ctx, RC_TIMER_BUILD_REGIONS_EXPAND, " - Expand", pc);
|
||||
logLine(ctx, RC_TIMER_BUILD_REGIONS_FLOOD, " - Find Basins", pc);
|
||||
logLine(ctx, RC_TIMER_BUILD_REGIONS_FILTER, " - Filter", pc);
|
||||
logLine(ctx, RC_TIMER_BUILD_LAYERS, "- Build Layers", pc);
|
||||
logLine(ctx, RC_TIMER_BUILD_CONTOURS, "- Build Contours", pc);
|
||||
logLine(ctx, RC_TIMER_BUILD_CONTOURS_TRACE, " - Trace", pc);
|
||||
logLine(ctx, RC_TIMER_BUILD_CONTOURS_SIMPLIFY, " - Simplify", pc);
|
||||
logLine(ctx, RC_TIMER_BUILD_POLYMESH, "- Build Polymesh", pc);
|
||||
logLine(ctx, RC_TIMER_BUILD_POLYMESHDETAIL, "- Build Polymesh Detail", pc);
|
||||
logLine(ctx, RC_TIMER_MERGE_POLYMESH, "- Merge Polymeshes", pc);
|
||||
logLine(ctx, RC_TIMER_MERGE_POLYMESHDETAIL, "- Merge Polymesh Details", pc);
|
||||
ctx.log(RC_LOG_PROGRESS, "=== TOTAL:\t%.2fms", totalTimeUsec/1000.0f);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
add_library(Detour)
|
||||
add_library(RecastNavigation::Detour ALIAS Detour)
|
||||
|
||||
set_target_properties(Detour PROPERTIES
|
||||
DEBUG_POSTFIX -d
|
||||
SOVERSION ${SOVERSION}
|
||||
VERSION ${LIB_VERSION}
|
||||
COMPILE_PDB_OUTPUT_DIRECTORY .
|
||||
COMPILE_PDB_NAME "Detour-d"
|
||||
CXX_STANDARD 98
|
||||
CXX_STANDARD_REQUIRED ON
|
||||
CXX_EXTENSIONS OFF # Disable compiler-specific extensions
|
||||
)
|
||||
|
||||
# Set build options
|
||||
if (RECASTNAVIGATION_DT_POLYREF64)
|
||||
target_compile_definitions(Detour PUBLIC DT_POLYREF64)
|
||||
endif()
|
||||
|
||||
if (RECASTNAVIGATION_DT_VIRTUAL_QUERYFILTER)
|
||||
target_compile_definitions(Detour PUBLIC DT_VIRTUAL_QUERYFILTER)
|
||||
endif()
|
||||
|
||||
target_compile_definitions(Detour PUBLIC "$<$<NOT:${RECASTNAVIGATION_ENABLE_ASSERTS}>:RC_DISABLE_ASSERTS>")
|
||||
|
||||
target_include_directories(Detour PUBLIC
|
||||
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/Include>"
|
||||
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/recastnavigation>"
|
||||
)
|
||||
|
||||
target_sources(Detour PRIVATE
|
||||
Source/DetourAlloc.cpp
|
||||
Source/DetourAssert.cpp
|
||||
Source/DetourCommon.cpp
|
||||
Source/DetourNavMesh.cpp
|
||||
Source/DetourNavMeshBuilder.cpp
|
||||
Source/DetourNavMeshQuery.cpp
|
||||
Source/DetourNode.cpp
|
||||
)
|
||||
|
||||
install(TARGETS Detour
|
||||
EXPORT recastnavigation-targets
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT library
|
||||
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} ${CMAKE_INSTALL_INCLUDEDIR}/recastnavigation
|
||||
)
|
||||
|
||||
install(DIRECTORY Include/
|
||||
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/recastnavigation
|
||||
FILES_MATCHING PATTERN "*.h"
|
||||
)
|
||||
|
||||
if(MSVC)
|
||||
install(FILES "$<TARGET_FILE_DIR:Detour>/Detour-d.pdb"
|
||||
CONFIGURATIONS "Debug"
|
||||
DESTINATION "lib"
|
||||
OPTIONAL)
|
||||
endif()
|
||||
@@ -0,0 +1,61 @@
|
||||
//
|
||||
// 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 DETOURALLOCATOR_H
|
||||
#define DETOURALLOCATOR_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
/// Provides hint values to the memory allocator on how long the
|
||||
/// memory is expected to be used.
|
||||
enum dtAllocHint
|
||||
{
|
||||
DT_ALLOC_PERM, ///< Memory persist after a function call.
|
||||
DT_ALLOC_TEMP ///< Memory used temporarily within a function.
|
||||
};
|
||||
|
||||
/// A memory allocation function.
|
||||
// @param[in] size The size, in bytes of memory, to allocate.
|
||||
// @param[in] rcAllocHint A hint to the allocator on how long the memory is expected to be in use.
|
||||
// @return A pointer to the beginning of the allocated memory block, or null if the allocation failed.
|
||||
/// @see dtAllocSetCustom
|
||||
typedef void* (dtAllocFunc)(size_t size, dtAllocHint hint);
|
||||
|
||||
/// A memory deallocation function.
|
||||
/// @param[in] ptr A pointer to a memory block previously allocated using #dtAllocFunc.
|
||||
/// @see dtAllocSetCustom
|
||||
typedef void (dtFreeFunc)(void* ptr);
|
||||
|
||||
/// Sets the base custom allocation functions to be used by Detour.
|
||||
/// @param[in] allocFunc The memory allocation function to be used by #dtAlloc
|
||||
/// @param[in] freeFunc The memory de-allocation function to be used by #dtFree
|
||||
void dtAllocSetCustom(dtAllocFunc *allocFunc, dtFreeFunc *freeFunc);
|
||||
|
||||
/// Allocates a memory block.
|
||||
/// @param[in] size The size, in bytes of memory, to allocate.
|
||||
/// @param[in] hint A hint to the allocator on how long the memory is expected to be in use.
|
||||
/// @return A pointer to the beginning of the allocated memory block, or null if the allocation failed.
|
||||
/// @see dtFree
|
||||
void* dtAlloc(size_t size, dtAllocHint hint);
|
||||
|
||||
/// Deallocates a memory block.
|
||||
/// @param[in] ptr A pointer to a memory block previously allocated using #dtAlloc.
|
||||
/// @see dtAlloc
|
||||
void dtFree(void* ptr);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,56 @@
|
||||
//
|
||||
// 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 DETOURASSERT_H
|
||||
#define DETOURASSERT_H
|
||||
|
||||
// Note: This header file's only purpose is to include define assert.
|
||||
// Feel free to change the file and include your own implementation instead.
|
||||
|
||||
#ifdef RC_DISABLE_ASSERTS
|
||||
|
||||
// From https://web.archive.org/web/20210117002833/http://cnicholson.net/2009/02/stupid-c-tricks-adventures-in-assert/
|
||||
# define dtAssert(x) do { (void)sizeof(x); } while((void)(__LINE__==-1),false)
|
||||
|
||||
#else
|
||||
|
||||
/// An assertion failure function.
|
||||
// @param[in] expression asserted expression.
|
||||
// @param[in] file Filename of the failed assertion.
|
||||
// @param[in] line Line number of the failed assertion.
|
||||
/// @see dtAssertFailSetCustom
|
||||
typedef void (dtAssertFailFunc)(const char* expression, const char* file, int line);
|
||||
|
||||
/// Sets the base custom assertion failure function to be used by Detour.
|
||||
/// @param[in] assertFailFunc The function to be invoked in case of failure of #dtAssert
|
||||
void dtAssertFailSetCustom(dtAssertFailFunc *assertFailFunc);
|
||||
|
||||
/// Gets the base custom assertion failure function to be used by Detour.
|
||||
dtAssertFailFunc* dtAssertFailGetCustom();
|
||||
|
||||
# include <assert.h>
|
||||
# define dtAssert(expression) \
|
||||
{ \
|
||||
dtAssertFailFunc* failFunc = dtAssertFailGetCustom(); \
|
||||
if(failFunc == NULL) { assert(expression); } \
|
||||
else if(!(expression)) { (*failFunc)(#expression, __FILE__, __LINE__); } \
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif // DETOURASSERT_H
|
||||
@@ -0,0 +1,571 @@
|
||||
//
|
||||
// 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 DETOURCOMMON_H
|
||||
#define DETOURCOMMON_H
|
||||
|
||||
#include "DetourMath.h"
|
||||
#include <stddef.h>
|
||||
|
||||
/**
|
||||
@defgroup detour Detour
|
||||
|
||||
Members in this module are used to create, manipulate, and query navigation
|
||||
meshes.
|
||||
|
||||
@note This is a summary list of members. Use the index or search
|
||||
feature to find minor members.
|
||||
*/
|
||||
|
||||
/// @name General helper functions
|
||||
/// @{
|
||||
|
||||
/// Used to ignore a function parameter. VS complains about unused parameters
|
||||
/// and this silences the warning.
|
||||
template<class T> void dtIgnoreUnused(const T&) { }
|
||||
|
||||
/// Swaps the values of the two parameters.
|
||||
/// @param[in,out] a Value A
|
||||
/// @param[in,out] b Value B
|
||||
template<class T> inline void dtSwap(T& a, T& b) { T t = a; a = b; b = t; }
|
||||
|
||||
/// Returns the minimum of two values.
|
||||
/// @param[in] a Value A
|
||||
/// @param[in] b Value B
|
||||
/// @return The minimum of the two values.
|
||||
template<class T> inline T dtMin(T a, T b) { return a < b ? a : b; }
|
||||
|
||||
/// Returns the maximum of two values.
|
||||
/// @param[in] a Value A
|
||||
/// @param[in] b Value B
|
||||
/// @return The maximum of the two values.
|
||||
template<class T> inline T dtMax(T a, T b) { return a > b ? a : b; }
|
||||
|
||||
/// Returns the absolute value.
|
||||
/// @param[in] a The value.
|
||||
/// @return The absolute value of the specified value.
|
||||
template<class T> inline T dtAbs(T a) { return a < 0 ? -a : a; }
|
||||
|
||||
/// Returns the square of the value.
|
||||
/// @param[in] a The value.
|
||||
/// @return The square of the value.
|
||||
template<class T> inline T dtSqr(T a) { return a*a; }
|
||||
|
||||
/// Clamps the value to the specified range.
|
||||
/// @param[in] v The value to clamp.
|
||||
/// @param[in] mn The minimum permitted return value.
|
||||
/// @param[in] mx The maximum permitted return value.
|
||||
/// @return The value, clamped to the specified range.
|
||||
template<class T> inline T dtClamp(T v, T mn, T mx) { return v < mn ? mn : (v > mx ? mx : v); }
|
||||
|
||||
/// @}
|
||||
/// @name Vector helper functions.
|
||||
/// @{
|
||||
|
||||
/// Derives the cross product of two vectors. (@p v1 x @p v2)
|
||||
/// @param[out] dest The cross product. [(x, y, z)]
|
||||
/// @param[in] v1 A Vector [(x, y, z)]
|
||||
/// @param[in] v2 A vector [(x, y, z)]
|
||||
inline void dtVcross(float* dest, const float* v1, const float* v2)
|
||||
{
|
||||
dest[0] = v1[1]*v2[2] - v1[2]*v2[1];
|
||||
dest[1] = v1[2]*v2[0] - v1[0]*v2[2];
|
||||
dest[2] = v1[0]*v2[1] - v1[1]*v2[0];
|
||||
}
|
||||
|
||||
/// Derives the dot product of two vectors. (@p v1 . @p v2)
|
||||
/// @param[in] v1 A Vector [(x, y, z)]
|
||||
/// @param[in] v2 A vector [(x, y, z)]
|
||||
/// @return The dot product.
|
||||
inline float dtVdot(const float* v1, const float* v2)
|
||||
{
|
||||
return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2];
|
||||
}
|
||||
|
||||
/// Performs a scaled vector addition. (@p v1 + (@p v2 * @p s))
|
||||
/// @param[out] dest The result vector. [(x, y, z)]
|
||||
/// @param[in] v1 The base vector. [(x, y, z)]
|
||||
/// @param[in] v2 The vector to scale and add to @p v1. [(x, y, z)]
|
||||
/// @param[in] s The amount to scale @p v2 by before adding to @p v1.
|
||||
inline void dtVmad(float* dest, const float* v1, const float* v2, const float s)
|
||||
{
|
||||
dest[0] = v1[0]+v2[0]*s;
|
||||
dest[1] = v1[1]+v2[1]*s;
|
||||
dest[2] = v1[2]+v2[2]*s;
|
||||
}
|
||||
|
||||
/// Performs a linear interpolation between two vectors. (@p v1 toward @p v2)
|
||||
/// @param[out] dest The result vector. [(x, y, x)]
|
||||
/// @param[in] v1 The starting vector.
|
||||
/// @param[in] v2 The destination vector.
|
||||
/// @param[in] t The interpolation factor. [Limits: 0 <= value <= 1.0]
|
||||
inline void dtVlerp(float* dest, const float* v1, const float* v2, const float t)
|
||||
{
|
||||
dest[0] = v1[0]+(v2[0]-v1[0])*t;
|
||||
dest[1] = v1[1]+(v2[1]-v1[1])*t;
|
||||
dest[2] = v1[2]+(v2[2]-v1[2])*t;
|
||||
}
|
||||
|
||||
/// Performs a vector addition. (@p v1 + @p v2)
|
||||
/// @param[out] dest The result vector. [(x, y, z)]
|
||||
/// @param[in] v1 The base vector. [(x, y, z)]
|
||||
/// @param[in] v2 The vector to add to @p v1. [(x, y, z)]
|
||||
inline void dtVadd(float* dest, const float* v1, const float* v2)
|
||||
{
|
||||
dest[0] = v1[0]+v2[0];
|
||||
dest[1] = v1[1]+v2[1];
|
||||
dest[2] = v1[2]+v2[2];
|
||||
}
|
||||
|
||||
/// Performs a vector subtraction. (@p v1 - @p v2)
|
||||
/// @param[out] dest The result vector. [(x, y, z)]
|
||||
/// @param[in] v1 The base vector. [(x, y, z)]
|
||||
/// @param[in] v2 The vector to subtract from @p v1. [(x, y, z)]
|
||||
inline void dtVsub(float* dest, const float* v1, const float* v2)
|
||||
{
|
||||
dest[0] = v1[0]-v2[0];
|
||||
dest[1] = v1[1]-v2[1];
|
||||
dest[2] = v1[2]-v2[2];
|
||||
}
|
||||
|
||||
/// Scales the vector by the specified value. (@p v * @p t)
|
||||
/// @param[out] dest The result vector. [(x, y, z)]
|
||||
/// @param[in] v The vector to scale. [(x, y, z)]
|
||||
/// @param[in] t The scaling factor.
|
||||
inline void dtVscale(float* dest, const float* v, const float t)
|
||||
{
|
||||
dest[0] = v[0]*t;
|
||||
dest[1] = v[1]*t;
|
||||
dest[2] = v[2]*t;
|
||||
}
|
||||
|
||||
/// Selects the minimum value of each element from the specified vectors.
|
||||
/// @param[in,out] mn A vector. (Will be updated with the result.) [(x, y, z)]
|
||||
/// @param[in] v A vector. [(x, y, z)]
|
||||
inline void dtVmin(float* mn, const float* v)
|
||||
{
|
||||
mn[0] = dtMin(mn[0], v[0]);
|
||||
mn[1] = dtMin(mn[1], v[1]);
|
||||
mn[2] = dtMin(mn[2], v[2]);
|
||||
}
|
||||
|
||||
/// Selects the maximum value of each element from the specified vectors.
|
||||
/// @param[in,out] mx A vector. (Will be updated with the result.) [(x, y, z)]
|
||||
/// @param[in] v A vector. [(x, y, z)]
|
||||
inline void dtVmax(float* mx, const float* v)
|
||||
{
|
||||
mx[0] = dtMax(mx[0], v[0]);
|
||||
mx[1] = dtMax(mx[1], v[1]);
|
||||
mx[2] = dtMax(mx[2], v[2]);
|
||||
}
|
||||
|
||||
/// Sets the vector elements to the specified values.
|
||||
/// @param[out] dest The result vector. [(x, y, z)]
|
||||
/// @param[in] x The x-value of the vector.
|
||||
/// @param[in] y The y-value of the vector.
|
||||
/// @param[in] z The z-value of the vector.
|
||||
inline void dtVset(float* dest, const float x, const float y, const float z)
|
||||
{
|
||||
dest[0] = x; dest[1] = y; dest[2] = z;
|
||||
}
|
||||
|
||||
/// Performs a vector copy.
|
||||
/// @param[out] dest The result. [(x, y, z)]
|
||||
/// @param[in] a The vector to copy. [(x, y, z)]
|
||||
inline void dtVcopy(float* dest, const float* a)
|
||||
{
|
||||
dest[0] = a[0];
|
||||
dest[1] = a[1];
|
||||
dest[2] = a[2];
|
||||
}
|
||||
|
||||
/// Derives the scalar length of the vector.
|
||||
/// @param[in] v The vector. [(x, y, z)]
|
||||
/// @return The scalar length of the vector.
|
||||
inline float dtVlen(const float* v)
|
||||
{
|
||||
return dtMathSqrtf(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
|
||||
}
|
||||
|
||||
/// Derives the square of the scalar length of the vector. (len * len)
|
||||
/// @param[in] v The vector. [(x, y, z)]
|
||||
/// @return The square of the scalar length of the vector.
|
||||
inline float dtVlenSqr(const float* v)
|
||||
{
|
||||
return v[0]*v[0] + v[1]*v[1] + v[2]*v[2];
|
||||
}
|
||||
|
||||
/// Returns the distance between two points.
|
||||
/// @param[in] v1 A point. [(x, y, z)]
|
||||
/// @param[in] v2 A point. [(x, y, z)]
|
||||
/// @return The distance between the two points.
|
||||
inline float dtVdist(const float* v1, const float* v2)
|
||||
{
|
||||
const float dx = v2[0] - v1[0];
|
||||
const float dy = v2[1] - v1[1];
|
||||
const float dz = v2[2] - v1[2];
|
||||
return dtMathSqrtf(dx*dx + dy*dy + dz*dz);
|
||||
}
|
||||
|
||||
/// Returns the square of the distance between two points.
|
||||
/// @param[in] v1 A point. [(x, y, z)]
|
||||
/// @param[in] v2 A point. [(x, y, z)]
|
||||
/// @return The square of the distance between the two points.
|
||||
inline float dtVdistSqr(const float* v1, const float* v2)
|
||||
{
|
||||
const float dx = v2[0] - v1[0];
|
||||
const float dy = v2[1] - v1[1];
|
||||
const float dz = v2[2] - v1[2];
|
||||
return dx*dx + dy*dy + dz*dz;
|
||||
}
|
||||
|
||||
/// Derives the distance between the specified points on the xz-plane.
|
||||
/// @param[in] v1 A point. [(x, y, z)]
|
||||
/// @param[in] v2 A point. [(x, y, z)]
|
||||
/// @return The distance between the point on the xz-plane.
|
||||
///
|
||||
/// The vectors are projected onto the xz-plane, so the y-values are ignored.
|
||||
inline float dtVdist2D(const float* v1, const float* v2)
|
||||
{
|
||||
const float dx = v2[0] - v1[0];
|
||||
const float dz = v2[2] - v1[2];
|
||||
return dtMathSqrtf(dx*dx + dz*dz);
|
||||
}
|
||||
|
||||
/// Derives the square of the distance between the specified points on the xz-plane.
|
||||
/// @param[in] v1 A point. [(x, y, z)]
|
||||
/// @param[in] v2 A point. [(x, y, z)]
|
||||
/// @return The square of the distance between the point on the xz-plane.
|
||||
inline float dtVdist2DSqr(const float* v1, const float* v2)
|
||||
{
|
||||
const float dx = v2[0] - v1[0];
|
||||
const float dz = v2[2] - v1[2];
|
||||
return dx*dx + dz*dz;
|
||||
}
|
||||
|
||||
/// Normalizes the vector.
|
||||
/// @param[in,out] v The vector to normalize. [(x, y, z)]
|
||||
inline void dtVnormalize(float* v)
|
||||
{
|
||||
float d = 1.0f / dtMathSqrtf(dtSqr(v[0]) + dtSqr(v[1]) + dtSqr(v[2]));
|
||||
v[0] *= d;
|
||||
v[1] *= d;
|
||||
v[2] *= d;
|
||||
}
|
||||
|
||||
/// Performs a 'sloppy' colocation check of the specified points.
|
||||
/// @param[in] p0 A point. [(x, y, z)]
|
||||
/// @param[in] p1 A point. [(x, y, z)]
|
||||
/// @return True if the points are considered to be at the same location.
|
||||
///
|
||||
/// Basically, this function will return true if the specified points are
|
||||
/// close enough to eachother to be considered colocated.
|
||||
inline bool dtVequal(const float* p0, const float* p1)
|
||||
{
|
||||
static const float thr = dtSqr(1.0f/16384.0f);
|
||||
const float d = dtVdistSqr(p0, p1);
|
||||
return d < thr;
|
||||
}
|
||||
|
||||
/// Checks that the specified vector's components are all finite.
|
||||
/// @param[in] v A point. [(x, y, z)]
|
||||
/// @return True if all of the point's components are finite, i.e. not NaN
|
||||
/// or any of the infinities.
|
||||
inline bool dtVisfinite(const float* v)
|
||||
{
|
||||
bool result =
|
||||
dtMathIsfinite(v[0]) &&
|
||||
dtMathIsfinite(v[1]) &&
|
||||
dtMathIsfinite(v[2]);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Checks that the specified vector's 2D components are finite.
|
||||
/// @param[in] v A point. [(x, y, z)]
|
||||
inline bool dtVisfinite2D(const float* v)
|
||||
{
|
||||
bool result = dtMathIsfinite(v[0]) && dtMathIsfinite(v[2]);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Derives the dot product of two vectors on the xz-plane. (@p u . @p v)
|
||||
/// @param[in] u A vector [(x, y, z)]
|
||||
/// @param[in] v A vector [(x, y, z)]
|
||||
/// @return The dot product on the xz-plane.
|
||||
///
|
||||
/// The vectors are projected onto the xz-plane, so the y-values are ignored.
|
||||
inline float dtVdot2D(const float* u, const float* v)
|
||||
{
|
||||
return u[0]*v[0] + u[2]*v[2];
|
||||
}
|
||||
|
||||
/// Derives the xz-plane 2D perp product of the two vectors. (uz*vx - ux*vz)
|
||||
/// @param[in] u The LHV vector [(x, y, z)]
|
||||
/// @param[in] v The RHV vector [(x, y, z)]
|
||||
/// @return The perp dot product on the xz-plane.
|
||||
///
|
||||
/// The vectors are projected onto the xz-plane, so the y-values are ignored.
|
||||
inline float dtVperp2D(const float* u, const float* v)
|
||||
{
|
||||
return u[2]*v[0] - u[0]*v[2];
|
||||
}
|
||||
|
||||
/// @}
|
||||
/// @name Computational geometry helper functions.
|
||||
/// @{
|
||||
|
||||
/// Derives the signed xz-plane area of the triangle ABC, or the relationship of line AB to point C.
|
||||
/// @param[in] a Vertex A. [(x, y, z)]
|
||||
/// @param[in] b Vertex B. [(x, y, z)]
|
||||
/// @param[in] c Vertex C. [(x, y, z)]
|
||||
/// @return The signed xz-plane area of the triangle.
|
||||
inline float dtTriArea2D(const float* a, const float* b, const float* c)
|
||||
{
|
||||
const float abx = b[0] - a[0];
|
||||
const float abz = b[2] - a[2];
|
||||
const float acx = c[0] - a[0];
|
||||
const float acz = c[2] - a[2];
|
||||
return acx*abz - abx*acz;
|
||||
}
|
||||
|
||||
/// Determines if two axis-aligned bounding boxes overlap.
|
||||
/// @param[in] amin Minimum bounds of box A. [(x, y, z)]
|
||||
/// @param[in] amax Maximum bounds of box A. [(x, y, z)]
|
||||
/// @param[in] bmin Minimum bounds of box B. [(x, y, z)]
|
||||
/// @param[in] bmax Maximum bounds of box B. [(x, y, z)]
|
||||
/// @return True if the two AABB's overlap.
|
||||
/// @see dtOverlapBounds
|
||||
inline bool dtOverlapQuantBounds(const unsigned short amin[3], const unsigned short amax[3],
|
||||
const unsigned short bmin[3], const unsigned short bmax[3])
|
||||
{
|
||||
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;
|
||||
overlap = (amin[2] > bmax[2] || amax[2] < bmin[2]) ? false : overlap;
|
||||
return overlap;
|
||||
}
|
||||
|
||||
/// Determines if two axis-aligned bounding boxes overlap.
|
||||
/// @param[in] amin Minimum bounds of box A. [(x, y, z)]
|
||||
/// @param[in] amax Maximum bounds of box A. [(x, y, z)]
|
||||
/// @param[in] bmin Minimum bounds of box B. [(x, y, z)]
|
||||
/// @param[in] bmax Maximum bounds of box B. [(x, y, z)]
|
||||
/// @return True if the two AABB's overlap.
|
||||
/// @see dtOverlapQuantBounds
|
||||
inline bool dtOverlapBounds(const float* amin, const float* amax,
|
||||
const float* bmin, const float* bmax)
|
||||
{
|
||||
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;
|
||||
overlap = (amin[2] > bmax[2] || amax[2] < bmin[2]) ? false : overlap;
|
||||
return overlap;
|
||||
}
|
||||
|
||||
/// Derives the closest point on a triangle from the specified reference point.
|
||||
/// @param[out] closest The closest point on the triangle.
|
||||
/// @param[in] p The reference point from which to test. [(x, y, z)]
|
||||
/// @param[in] a Vertex A of triangle ABC. [(x, y, z)]
|
||||
/// @param[in] b Vertex B of triangle ABC. [(x, y, z)]
|
||||
/// @param[in] c Vertex C of triangle ABC. [(x, y, z)]
|
||||
void dtClosestPtPointTriangle(float* closest, const float* p,
|
||||
const float* a, const float* b, const float* c);
|
||||
|
||||
/// Derives the y-axis height of the closest point on the triangle from the specified reference point.
|
||||
/// @param[in] p The reference point from which to test. [(x, y, z)]
|
||||
/// @param[in] a Vertex A of triangle ABC. [(x, y, z)]
|
||||
/// @param[in] b Vertex B of triangle ABC. [(x, y, z)]
|
||||
/// @param[in] c Vertex C of triangle ABC. [(x, y, z)]
|
||||
/// @param[out] h The resulting height.
|
||||
bool dtClosestHeightPointTriangle(const float* p, const float* a, const float* b, const float* c, float& h);
|
||||
|
||||
bool dtIntersectSegmentPoly2D(const float* p0, const float* p1,
|
||||
const float* verts, int nverts,
|
||||
float& tmin, float& tmax,
|
||||
int& segMin, int& segMax);
|
||||
|
||||
bool dtIntersectSegSeg2D(const float* ap, const float* aq,
|
||||
const float* bp, const float* bq,
|
||||
float& s, float& t);
|
||||
|
||||
/// Determines if the specified point is inside the convex polygon on the xz-plane.
|
||||
/// @param[in] pt The point to check. [(x, y, z)]
|
||||
/// @param[in] verts The polygon vertices. [(x, y, z) * @p nverts]
|
||||
/// @param[in] nverts The number of vertices. [Limit: >= 3]
|
||||
/// @return True if the point is inside the polygon.
|
||||
bool dtPointInPolygon(const float* pt, const float* verts, const int nverts);
|
||||
|
||||
bool dtDistancePtPolyEdgesSqr(const float* pt, const float* verts, const int nverts,
|
||||
float* ed, float* et);
|
||||
|
||||
float dtDistancePtSegSqr2D(const float* pt, const float* p, const float* q, float& t);
|
||||
|
||||
/// Derives the centroid of a convex polygon.
|
||||
/// @param[out] tc The centroid of the polgyon. [(x, y, z)]
|
||||
/// @param[in] idx The polygon indices. [(vertIndex) * @p nidx]
|
||||
/// @param[in] nidx The number of indices in the polygon. [Limit: >= 3]
|
||||
/// @param[in] verts The polygon vertices. [(x, y, z) * vertCount]
|
||||
void dtCalcPolyCenter(float* tc, const unsigned short* idx, int nidx, const float* verts);
|
||||
|
||||
/// Determines if the two convex polygons overlap on the xz-plane.
|
||||
/// @param[in] polya Polygon A vertices. [(x, y, z) * @p npolya]
|
||||
/// @param[in] npolya The number of vertices in polygon A.
|
||||
/// @param[in] polyb Polygon B vertices. [(x, y, z) * @p npolyb]
|
||||
/// @param[in] npolyb The number of vertices in polygon B.
|
||||
/// @return True if the two polygons overlap.
|
||||
bool dtOverlapPolyPoly2D(const float* polya, const int npolya,
|
||||
const float* polyb, const int npolyb);
|
||||
|
||||
/// @}
|
||||
/// @name Miscellanious functions.
|
||||
/// @{
|
||||
|
||||
inline unsigned int dtNextPow2(unsigned int v)
|
||||
{
|
||||
v--;
|
||||
v |= v >> 1;
|
||||
v |= v >> 2;
|
||||
v |= v >> 4;
|
||||
v |= v >> 8;
|
||||
v |= v >> 16;
|
||||
v++;
|
||||
return v;
|
||||
}
|
||||
|
||||
inline unsigned int dtIlog2(unsigned int v)
|
||||
{
|
||||
unsigned int r;
|
||||
unsigned int shift;
|
||||
r = (v > 0xffff) << 4; v >>= r;
|
||||
shift = (v > 0xff) << 3; v >>= shift; r |= shift;
|
||||
shift = (v > 0xf) << 2; v >>= shift; r |= shift;
|
||||
shift = (v > 0x3) << 1; v >>= shift; r |= shift;
|
||||
r |= (v >> 1);
|
||||
return r;
|
||||
}
|
||||
|
||||
inline int dtAlign4(int x) { return (x+3) & ~3; }
|
||||
|
||||
inline int dtOppositeTile(int side) { return (side+4) & 0x7; }
|
||||
|
||||
inline void dtSwapByte(unsigned char* a, unsigned char* b)
|
||||
{
|
||||
unsigned char tmp = *a;
|
||||
*a = *b;
|
||||
*b = tmp;
|
||||
}
|
||||
|
||||
inline void dtSwapEndian(unsigned short* v)
|
||||
{
|
||||
unsigned char* x = (unsigned char*)v;
|
||||
dtSwapByte(x+0, x+1);
|
||||
}
|
||||
|
||||
inline void dtSwapEndian(short* v)
|
||||
{
|
||||
unsigned char* x = (unsigned char*)v;
|
||||
dtSwapByte(x+0, x+1);
|
||||
}
|
||||
|
||||
inline void dtSwapEndian(unsigned int* v)
|
||||
{
|
||||
unsigned char* x = (unsigned char*)v;
|
||||
dtSwapByte(x+0, x+3); dtSwapByte(x+1, x+2);
|
||||
}
|
||||
|
||||
inline void dtSwapEndian(int* v)
|
||||
{
|
||||
unsigned char* x = (unsigned char*)v;
|
||||
dtSwapByte(x+0, x+3); dtSwapByte(x+1, x+2);
|
||||
}
|
||||
|
||||
inline void dtSwapEndian(float* v)
|
||||
{
|
||||
unsigned char* x = (unsigned char*)v;
|
||||
dtSwapByte(x+0, x+3); dtSwapByte(x+1, x+2);
|
||||
}
|
||||
|
||||
void dtRandomPointInConvexPoly(const float* pts, const int npts, float* areas,
|
||||
const float s, const float t, float* out);
|
||||
|
||||
template<typename TypeToRetrieveAs>
|
||||
TypeToRetrieveAs* dtGetThenAdvanceBufferPointer(const unsigned char*& buffer, const size_t distanceToAdvance)
|
||||
{
|
||||
TypeToRetrieveAs* returnPointer = reinterpret_cast<TypeToRetrieveAs*>(buffer);
|
||||
buffer += distanceToAdvance;
|
||||
return returnPointer;
|
||||
}
|
||||
|
||||
template<typename TypeToRetrieveAs>
|
||||
TypeToRetrieveAs* dtGetThenAdvanceBufferPointer(unsigned char*& buffer, const size_t distanceToAdvance)
|
||||
{
|
||||
TypeToRetrieveAs* returnPointer = reinterpret_cast<TypeToRetrieveAs*>(buffer);
|
||||
buffer += distanceToAdvance;
|
||||
return returnPointer;
|
||||
}
|
||||
|
||||
|
||||
/// @}
|
||||
|
||||
#endif // DETOURCOMMON_H
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// This section contains detailed documentation for members that don't have
|
||||
// a source file. It reduces clutter in the main section of the header.
|
||||
|
||||
/**
|
||||
|
||||
@fn float dtTriArea2D(const float* a, const float* b, const float* c)
|
||||
@par
|
||||
|
||||
The vertices are projected onto the xz-plane, so the y-values are ignored.
|
||||
|
||||
This is a low cost function than can be used for various purposes. Its main purpose
|
||||
is for point/line relationship testing.
|
||||
|
||||
In all cases: A value of zero indicates that all vertices are collinear or represent the same point.
|
||||
(On the xz-plane.)
|
||||
|
||||
When used for point/line relationship tests, AB usually represents a line against which
|
||||
the C point is to be tested. In this case:
|
||||
|
||||
A positive value indicates that point C is to the left of line AB, looking from A toward B.<br/>
|
||||
A negative value indicates that point C is to the right of lineAB, looking from A toward B.
|
||||
|
||||
When used for evaluating a triangle:
|
||||
|
||||
The absolute value of the return value is two times the area of the triangle when it is
|
||||
projected onto the xz-plane.
|
||||
|
||||
A positive return value indicates:
|
||||
|
||||
<ul>
|
||||
<li>The vertices are wrapped in the normal Detour wrap direction.</li>
|
||||
<li>The triangle's 3D face normal is in the general up direction.</li>
|
||||
</ul>
|
||||
|
||||
A negative return value indicates:
|
||||
|
||||
<ul>
|
||||
<li>The vertices are reverse wrapped. (Wrapped opposite the normal Detour wrap direction.)</li>
|
||||
<li>The triangle's 3D face normal is in the general down direction.</li>
|
||||
</ul>
|
||||
|
||||
*/
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
@defgroup detour Detour
|
||||
|
||||
Members in this module are wrappers around the standard math library
|
||||
*/
|
||||
|
||||
#ifndef DETOURMATH_H
|
||||
#define DETOURMATH_H
|
||||
|
||||
#include <math.h>
|
||||
|
||||
inline float dtMathFabsf(float x) { return fabsf(x); }
|
||||
inline float dtMathSqrtf(float x) { return sqrtf(x); }
|
||||
inline float dtMathFloorf(float x) { return floorf(x); }
|
||||
inline float dtMathCeilf(float x) { return ceilf(x); }
|
||||
inline float dtMathCosf(float x) { return cosf(x); }
|
||||
inline float dtMathSinf(float x) { return sinf(x); }
|
||||
inline float dtMathAtan2f(float y, float x) { return atan2f(y, x); }
|
||||
inline bool dtMathIsfinite(float x)
|
||||
{
|
||||
#ifndef RC_FAST_MATH
|
||||
return isfinite(x);
|
||||
#else
|
||||
// Infinity and NaN are disabled when compiling with -ffast-math
|
||||
(void)x;
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,781 @@
|
||||
//
|
||||
// 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 DETOURNAVMESH_H
|
||||
#define DETOURNAVMESH_H
|
||||
|
||||
#include "DetourAlloc.h"
|
||||
#include "DetourStatus.h"
|
||||
|
||||
// Undefine (or define in a build config) the following line to use 64bit polyref.
|
||||
// Generally not needed, useful for very large worlds.
|
||||
// Note: tiles build using 32bit refs are not compatible with 64bit refs!
|
||||
//#define DT_POLYREF64 1
|
||||
|
||||
#ifdef DT_POLYREF64
|
||||
// TODO: figure out a multiplatform version of uint64_t
|
||||
// - maybe: https://code.google.com/p/msinttypes/
|
||||
// - or: http://www.azillionmonkeys.com/qed/pstdint.h
|
||||
#include <stdint.h>
|
||||
#endif
|
||||
|
||||
// Note: If you want to use 64-bit refs, change the types of both dtPolyRef & dtTileRef.
|
||||
// It is also recommended that you change dtHashRef() to a proper 64-bit hash.
|
||||
|
||||
/// A handle to a polygon within a navigation mesh tile.
|
||||
/// @ingroup detour
|
||||
#ifdef DT_POLYREF64
|
||||
static const unsigned int DT_SALT_BITS = 16;
|
||||
static const unsigned int DT_TILE_BITS = 28;
|
||||
static const unsigned int DT_POLY_BITS = 20;
|
||||
typedef uint64_t dtPolyRef;
|
||||
#else
|
||||
typedef unsigned int dtPolyRef;
|
||||
#endif
|
||||
|
||||
/// A handle to a tile within a navigation mesh.
|
||||
/// @ingroup detour
|
||||
#ifdef DT_POLYREF64
|
||||
typedef uint64_t dtTileRef;
|
||||
#else
|
||||
typedef unsigned int dtTileRef;
|
||||
#endif
|
||||
|
||||
/// The maximum number of vertices per navigation polygon.
|
||||
/// @ingroup detour
|
||||
static const int DT_VERTS_PER_POLYGON = 6;
|
||||
|
||||
/// @{
|
||||
/// @name Tile Serialization Constants
|
||||
/// These constants are used to detect whether a navigation tile's data
|
||||
/// and state format is compatible with the current build.
|
||||
///
|
||||
|
||||
/// A magic number used to detect compatibility of navigation tile data.
|
||||
static const int DT_NAVMESH_MAGIC = 'D'<<24 | 'N'<<16 | 'A'<<8 | 'V';
|
||||
|
||||
/// A version number used to detect compatibility of navigation tile data.
|
||||
static const int DT_NAVMESH_VERSION = 7;
|
||||
|
||||
/// A magic number used to detect the compatibility of navigation tile states.
|
||||
static const int DT_NAVMESH_STATE_MAGIC = 'D'<<24 | 'N'<<16 | 'M'<<8 | 'S';
|
||||
|
||||
/// A version number used to detect compatibility of navigation tile states.
|
||||
static const int DT_NAVMESH_STATE_VERSION = 1;
|
||||
|
||||
/// @}
|
||||
|
||||
/// A flag that indicates that an entity links to an external entity.
|
||||
/// (E.g. A polygon edge is a portal that links to another polygon.)
|
||||
static const unsigned short DT_EXT_LINK = 0x8000;
|
||||
|
||||
/// A value that indicates the entity does not link to anything.
|
||||
static const unsigned int DT_NULL_LINK = 0xffffffff;
|
||||
|
||||
/// A flag that indicates that an off-mesh connection can be traversed in both directions. (Is bidirectional.)
|
||||
static const unsigned int DT_OFFMESH_CON_BIDIR = 1;
|
||||
|
||||
/// The maximum number of user defined area ids.
|
||||
/// @ingroup detour
|
||||
static const int DT_MAX_AREAS = 64;
|
||||
|
||||
/// Tile flags used for various functions and fields.
|
||||
/// For an example, see dtNavMesh::addTile().
|
||||
enum dtTileFlags
|
||||
{
|
||||
/// The navigation mesh owns the tile memory and is responsible for freeing it.
|
||||
DT_TILE_FREE_DATA = 0x01
|
||||
};
|
||||
|
||||
/// Vertex flags returned by dtNavMeshQuery::findStraightPath.
|
||||
enum dtStraightPathFlags
|
||||
{
|
||||
DT_STRAIGHTPATH_START = 0x01, ///< The vertex is the start position in the path.
|
||||
DT_STRAIGHTPATH_END = 0x02, ///< The vertex is the end position in the path.
|
||||
DT_STRAIGHTPATH_OFFMESH_CONNECTION = 0x04 ///< The vertex is the start of an off-mesh connection.
|
||||
};
|
||||
|
||||
/// Options for dtNavMeshQuery::findStraightPath.
|
||||
enum dtStraightPathOptions
|
||||
{
|
||||
DT_STRAIGHTPATH_AREA_CROSSINGS = 0x01, ///< Add a vertex at every polygon edge crossing where area changes.
|
||||
DT_STRAIGHTPATH_ALL_CROSSINGS = 0x02 ///< Add a vertex at every polygon edge crossing.
|
||||
};
|
||||
|
||||
|
||||
/// Options for dtNavMeshQuery::initSlicedFindPath and updateSlicedFindPath
|
||||
enum dtFindPathOptions
|
||||
{
|
||||
DT_FINDPATH_ANY_ANGLE = 0x02 ///< use raycasts during pathfind to "shortcut" (raycast still consider costs)
|
||||
};
|
||||
|
||||
/// Options for dtNavMeshQuery::raycast
|
||||
enum dtRaycastOptions
|
||||
{
|
||||
DT_RAYCAST_USE_COSTS = 0x01 ///< Raycast should calculate movement cost along the ray and fill RaycastHit::cost
|
||||
};
|
||||
|
||||
enum dtDetailTriEdgeFlags
|
||||
{
|
||||
DT_DETAIL_EDGE_BOUNDARY = 0x01 ///< Detail triangle edge is part of the poly boundary
|
||||
};
|
||||
|
||||
|
||||
/// Limit raycasting during any angle pahfinding
|
||||
/// The limit is given as a multiple of the character radius
|
||||
static const float DT_RAY_CAST_LIMIT_PROPORTIONS = 50.0f;
|
||||
|
||||
/// Flags representing the type of a navigation mesh polygon.
|
||||
enum dtPolyTypes
|
||||
{
|
||||
/// The polygon is a standard convex polygon that is part of the surface of the mesh.
|
||||
DT_POLYTYPE_GROUND = 0,
|
||||
/// The polygon is an off-mesh connection consisting of two vertices.
|
||||
DT_POLYTYPE_OFFMESH_CONNECTION = 1
|
||||
};
|
||||
|
||||
|
||||
/// Defines a polygon within a dtMeshTile object.
|
||||
/// @ingroup detour
|
||||
struct dtPoly
|
||||
{
|
||||
/// Index to first link in linked list. (Or #DT_NULL_LINK if there is no link.)
|
||||
unsigned int firstLink;
|
||||
|
||||
/// The indices of the polygon's vertices.
|
||||
/// The actual vertices are located in dtMeshTile::verts.
|
||||
unsigned short verts[DT_VERTS_PER_POLYGON];
|
||||
|
||||
/// Packed data representing neighbor polygons references and flags for each edge.
|
||||
unsigned short neis[DT_VERTS_PER_POLYGON];
|
||||
|
||||
/// The user defined polygon flags.
|
||||
unsigned short flags;
|
||||
|
||||
/// The number of vertices in the polygon.
|
||||
unsigned char vertCount;
|
||||
|
||||
/// The bit packed area id and polygon type.
|
||||
/// @note Use the structure's set and get methods to access this value.
|
||||
unsigned char areaAndtype;
|
||||
|
||||
/// Sets the user defined area id. [Limit: < #DT_MAX_AREAS]
|
||||
inline void setArea(unsigned char a) { areaAndtype = (areaAndtype & 0xc0) | (a & 0x3f); }
|
||||
|
||||
/// Sets the polygon type. (See: #dtPolyTypes.)
|
||||
inline void setType(unsigned char t) { areaAndtype = (areaAndtype & 0x3f) | (t << 6); }
|
||||
|
||||
/// Gets the user defined area id.
|
||||
inline unsigned char getArea() const { return areaAndtype & 0x3f; }
|
||||
|
||||
/// Gets the polygon type. (See: #dtPolyTypes)
|
||||
inline unsigned char getType() const { return areaAndtype >> 6; }
|
||||
};
|
||||
|
||||
/// Defines the location of detail sub-mesh data within a dtMeshTile.
|
||||
struct dtPolyDetail
|
||||
{
|
||||
unsigned int vertBase; ///< The offset of the vertices in the dtMeshTile::detailVerts array.
|
||||
unsigned int triBase; ///< The offset of the triangles in the dtMeshTile::detailTris array.
|
||||
unsigned char vertCount; ///< The number of vertices in the sub-mesh.
|
||||
unsigned char triCount; ///< The number of triangles in the sub-mesh.
|
||||
};
|
||||
|
||||
/// Defines a link between polygons.
|
||||
/// @note This structure is rarely if ever used by the end user.
|
||||
/// @see dtMeshTile
|
||||
struct dtLink
|
||||
{
|
||||
dtPolyRef ref; ///< Neighbour reference. (The neighbor that is linked to.)
|
||||
unsigned int next; ///< Index of the next link.
|
||||
unsigned char edge; ///< Index of the polygon edge that owns this link.
|
||||
unsigned char side; ///< If a boundary link, defines on which side the link is.
|
||||
unsigned char bmin; ///< If a boundary link, defines the minimum sub-edge area.
|
||||
unsigned char bmax; ///< If a boundary link, defines the maximum sub-edge area.
|
||||
};
|
||||
|
||||
/// Bounding volume node.
|
||||
/// @note This structure is rarely if ever used by the end user.
|
||||
/// @see dtMeshTile
|
||||
struct dtBVNode
|
||||
{
|
||||
unsigned short bmin[3]; ///< Minimum bounds of the node's AABB. [(x, y, z)]
|
||||
unsigned short bmax[3]; ///< Maximum bounds of the node's AABB. [(x, y, z)]
|
||||
int i; ///< The node's index. (Negative for escape sequence.)
|
||||
};
|
||||
|
||||
/// Defines an navigation mesh off-mesh connection within a dtMeshTile object.
|
||||
/// An off-mesh connection is a user defined traversable connection made up to two vertices.
|
||||
struct dtOffMeshConnection
|
||||
{
|
||||
/// The endpoints of the connection. [(ax, ay, az, bx, by, bz)]
|
||||
float pos[6];
|
||||
|
||||
/// The radius of the endpoints. [Limit: >= 0]
|
||||
float rad;
|
||||
|
||||
/// The polygon reference of the connection within the tile.
|
||||
unsigned short poly;
|
||||
|
||||
/// Link flags.
|
||||
/// @note These are not the connection's user defined flags. Those are assigned via the
|
||||
/// connection's dtPoly definition. These are link flags used for internal purposes.
|
||||
unsigned char flags;
|
||||
|
||||
/// End point side.
|
||||
unsigned char side;
|
||||
|
||||
/// The id of the offmesh connection. (User assigned when the navigation mesh is built.)
|
||||
unsigned int userId;
|
||||
};
|
||||
|
||||
/// Provides high level information related to a dtMeshTile object.
|
||||
/// @ingroup detour
|
||||
struct dtMeshHeader
|
||||
{
|
||||
int magic; ///< Tile magic number. (Used to identify the data format.)
|
||||
int version; ///< Tile data format version number.
|
||||
int x; ///< The x-position of the tile within the dtNavMesh tile grid. (x, y, layer)
|
||||
int y; ///< The y-position of the tile within the dtNavMesh tile grid. (x, y, layer)
|
||||
int layer; ///< The layer of the tile within the dtNavMesh tile grid. (x, y, layer)
|
||||
unsigned int userId; ///< The user defined id of the tile.
|
||||
int polyCount; ///< The number of polygons in the tile.
|
||||
int vertCount; ///< The number of vertices in the tile.
|
||||
int maxLinkCount; ///< The number of allocated links.
|
||||
int detailMeshCount; ///< The number of sub-meshes in the detail mesh.
|
||||
|
||||
/// The number of unique vertices in the detail mesh. (In addition to the polygon vertices.)
|
||||
int detailVertCount;
|
||||
|
||||
int detailTriCount; ///< The number of triangles in the detail mesh.
|
||||
int bvNodeCount; ///< The number of bounding volume nodes. (Zero if bounding volumes are disabled.)
|
||||
int offMeshConCount; ///< The number of off-mesh connections.
|
||||
int offMeshBase; ///< The index of the first polygon which is an off-mesh connection.
|
||||
float walkableHeight; ///< The height of the agents using the tile.
|
||||
float walkableRadius; ///< The radius of the agents using the tile.
|
||||
float walkableClimb; ///< The maximum climb height of the agents using the tile.
|
||||
float bmin[3]; ///< The minimum bounds of the tile's AABB. [(x, y, z)]
|
||||
float bmax[3]; ///< The maximum bounds of the tile's AABB. [(x, y, z)]
|
||||
|
||||
/// The bounding volume quantization factor.
|
||||
float bvQuantFactor;
|
||||
};
|
||||
|
||||
/// Defines a navigation mesh tile.
|
||||
/// @ingroup detour
|
||||
struct dtMeshTile
|
||||
{
|
||||
unsigned int salt; ///< Counter describing modifications to the tile.
|
||||
|
||||
unsigned int linksFreeList; ///< Index to the next free link.
|
||||
dtMeshHeader* header; ///< The tile header.
|
||||
dtPoly* polys; ///< The tile polygons. [Size: dtMeshHeader::polyCount]
|
||||
float* verts; ///< The tile vertices. [(x, y, z) * dtMeshHeader::vertCount]
|
||||
dtLink* links; ///< The tile links. [Size: dtMeshHeader::maxLinkCount]
|
||||
dtPolyDetail* detailMeshes; ///< The tile's detail sub-meshes. [Size: dtMeshHeader::detailMeshCount]
|
||||
|
||||
/// The detail mesh's unique vertices. [(x, y, z) * dtMeshHeader::detailVertCount]
|
||||
float* detailVerts;
|
||||
|
||||
/// The detail mesh's triangles. [(vertA, vertB, vertC, triFlags) * dtMeshHeader::detailTriCount].
|
||||
/// See dtDetailTriEdgeFlags and dtGetDetailTriEdgeFlags.
|
||||
unsigned char* detailTris;
|
||||
|
||||
/// The tile bounding volume nodes. [Size: dtMeshHeader::bvNodeCount]
|
||||
/// (Will be null if bounding volumes are disabled.)
|
||||
dtBVNode* bvTree;
|
||||
|
||||
dtOffMeshConnection* offMeshCons; ///< The tile off-mesh connections. [Size: dtMeshHeader::offMeshConCount]
|
||||
|
||||
unsigned char* data; ///< The tile data. (Not directly accessed under normal situations.)
|
||||
int dataSize; ///< Size of the tile data.
|
||||
int flags; ///< Tile flags. (See: #dtTileFlags)
|
||||
dtMeshTile* next; ///< The next free tile, or the next tile in the spatial grid.
|
||||
};
|
||||
|
||||
/// Get flags for edge in detail triangle.
|
||||
/// @param[in] triFlags The flags for the triangle (last component of detail vertices above).
|
||||
/// @param[in] edgeIndex The index of the first vertex of the edge. For instance, if 0,
|
||||
/// returns flags for edge AB.
|
||||
inline int dtGetDetailTriEdgeFlags(unsigned char triFlags, int edgeIndex)
|
||||
{
|
||||
return (triFlags >> (edgeIndex * 2)) & 0x3;
|
||||
}
|
||||
|
||||
/// Configuration parameters used to define multi-tile navigation meshes.
|
||||
/// The values are used to allocate space during the initialization of a navigation mesh.
|
||||
/// @see dtNavMesh::init()
|
||||
/// @ingroup detour
|
||||
struct dtNavMeshParams
|
||||
{
|
||||
float orig[3]; ///< The world space origin of the navigation mesh's tile space. [(x, y, z)]
|
||||
float tileWidth; ///< The width of each tile. (Along the x-axis.)
|
||||
float tileHeight; ///< The height of each tile. (Along the z-axis.)
|
||||
int maxTiles; ///< The maximum number of tiles the navigation mesh can contain. This and maxPolys are used to calculate how many bits are needed to identify tiles and polygons uniquely.
|
||||
int maxPolys; ///< The maximum number of polygons each tile can contain. This and maxTiles are used to calculate how many bits are needed to identify tiles and polygons uniquely.
|
||||
};
|
||||
|
||||
/// A navigation mesh based on tiles of convex polygons.
|
||||
/// @ingroup detour
|
||||
class dtNavMesh
|
||||
{
|
||||
public:
|
||||
dtNavMesh();
|
||||
~dtNavMesh();
|
||||
|
||||
/// @{
|
||||
/// @name Initialization and Tile Management
|
||||
|
||||
/// Initializes the navigation mesh for tiled use.
|
||||
/// @param[in] params Initialization parameters.
|
||||
/// @return The status flags for the operation.
|
||||
dtStatus init(const dtNavMeshParams* params);
|
||||
|
||||
/// Initializes the navigation mesh for single tile use.
|
||||
/// @param[in] data Data of the new tile. (See: #dtCreateNavMeshData)
|
||||
/// @param[in] dataSize The data size of the new tile.
|
||||
/// @param[in] flags The tile flags. (See: #dtTileFlags)
|
||||
/// @return The status flags for the operation.
|
||||
/// @see dtCreateNavMeshData
|
||||
dtStatus init(unsigned char* data, const int dataSize, const int flags);
|
||||
|
||||
/// The navigation mesh initialization params.
|
||||
const dtNavMeshParams* getParams() const;
|
||||
|
||||
/// Adds a tile to the navigation mesh.
|
||||
/// @param[in] data Data for the new tile mesh. (See: #dtCreateNavMeshData)
|
||||
/// @param[in] dataSize Data size of the new tile mesh.
|
||||
/// @param[in] flags Tile flags. (See: #dtTileFlags)
|
||||
/// @param[in] lastRef The desired reference for the tile. (When reloading a tile.) [opt] [Default: 0]
|
||||
/// @param[out] result The tile reference. (If the tile was succesfully added.) [opt]
|
||||
/// @return The status flags for the operation.
|
||||
dtStatus addTile(unsigned char* data, int dataSize, int flags, dtTileRef lastRef, dtTileRef* result);
|
||||
|
||||
/// Removes the specified tile from the navigation mesh.
|
||||
/// @param[in] ref The reference of the tile to remove.
|
||||
/// @param[out] data Data associated with deleted tile.
|
||||
/// @param[out] dataSize Size of the data associated with deleted tile.
|
||||
/// @return The status flags for the operation.
|
||||
dtStatus removeTile(dtTileRef ref, unsigned char** data, int* dataSize);
|
||||
|
||||
/// @}
|
||||
|
||||
/// @{
|
||||
/// @name Query Functions
|
||||
|
||||
/// Calculates the tile grid location for the specified world position.
|
||||
/// @param[in] pos The world position for the query. [(x, y, z)]
|
||||
/// @param[out] tx The tile's x-location. (x, y)
|
||||
/// @param[out] ty The tile's y-location. (x, y)
|
||||
void calcTileLoc(const float* pos, int* tx, int* ty) const;
|
||||
|
||||
/// Gets the tile at the specified grid location.
|
||||
/// @param[in] x The tile's x-location. (x, y, layer)
|
||||
/// @param[in] y The tile's y-location. (x, y, layer)
|
||||
/// @param[in] layer The tile's layer. (x, y, layer)
|
||||
/// @return The tile, or null if the tile does not exist.
|
||||
const dtMeshTile* getTileAt(const int x, const int y, const int layer) const;
|
||||
|
||||
/// Gets all tiles at the specified grid location. (All layers.)
|
||||
/// @param[in] x The tile's x-location. (x, y)
|
||||
/// @param[in] y The tile's y-location. (x, y)
|
||||
/// @param[out] tiles A pointer to an array of tiles that will hold the result.
|
||||
/// @param[in] maxTiles The maximum tiles the tiles parameter can hold.
|
||||
/// @return The number of tiles returned in the tiles array.
|
||||
int getTilesAt(const int x, const int y,
|
||||
dtMeshTile const** tiles, const int maxTiles) const;
|
||||
|
||||
/// Gets the tile reference for the tile at specified grid location.
|
||||
/// @param[in] x The tile's x-location. (x, y, layer)
|
||||
/// @param[in] y The tile's y-location. (x, y, layer)
|
||||
/// @param[in] layer The tile's layer. (x, y, layer)
|
||||
/// @return The tile reference of the tile, or 0 if there is none.
|
||||
dtTileRef getTileRefAt(int x, int y, int layer) const;
|
||||
|
||||
/// Gets the tile reference for the specified tile.
|
||||
/// @param[in] tile The tile.
|
||||
/// @return The tile reference of the tile.
|
||||
dtTileRef getTileRef(const dtMeshTile* tile) const;
|
||||
|
||||
/// Gets the tile for the specified tile reference.
|
||||
/// @param[in] ref The tile reference of the tile to retrieve.
|
||||
/// @return The tile for the specified reference, or null if the
|
||||
/// reference is invalid.
|
||||
const dtMeshTile* getTileByRef(dtTileRef ref) const;
|
||||
|
||||
/// The maximum number of tiles supported by the navigation mesh.
|
||||
/// @return The maximum number of tiles supported by the navigation mesh.
|
||||
int getMaxTiles() const;
|
||||
|
||||
/// Gets the tile at the specified index.
|
||||
/// @param[in] i The tile index. [Limit: 0 >= index < #getMaxTiles()]
|
||||
/// @return The tile at the specified index.
|
||||
const dtMeshTile* getTile(int i) const;
|
||||
|
||||
/// Gets the tile and polygon for the specified polygon reference.
|
||||
/// @param[in] ref The reference for the a polygon.
|
||||
/// @param[out] tile The tile containing the polygon.
|
||||
/// @param[out] poly The polygon.
|
||||
/// @return The status flags for the operation.
|
||||
dtStatus getTileAndPolyByRef(const dtPolyRef ref, const dtMeshTile** tile, const dtPoly** poly) const;
|
||||
|
||||
/// Returns the tile and polygon for the specified polygon reference.
|
||||
/// @param[in] ref A known valid reference for a polygon.
|
||||
/// @param[out] tile The tile containing the polygon.
|
||||
/// @param[out] poly The polygon.
|
||||
void getTileAndPolyByRefUnsafe(const dtPolyRef ref, const dtMeshTile** tile, const dtPoly** poly) const;
|
||||
|
||||
/// Checks the validity of a polygon reference.
|
||||
/// @param[in] ref The polygon reference to check.
|
||||
/// @return True if polygon reference is valid for the navigation mesh.
|
||||
bool isValidPolyRef(dtPolyRef ref) const;
|
||||
|
||||
/// Gets the polygon reference for the tile's base polygon.
|
||||
/// @param[in] tile The tile.
|
||||
/// @return The polygon reference for the base polygon in the specified tile.
|
||||
dtPolyRef getPolyRefBase(const dtMeshTile* tile) const;
|
||||
|
||||
/// Gets the endpoints for an off-mesh connection, ordered by "direction of travel".
|
||||
/// @param[in] prevRef The reference of the polygon before the connection.
|
||||
/// @param[in] polyRef The reference of the off-mesh connection polygon.
|
||||
/// @param[out] startPos The start position of the off-mesh connection. [(x, y, z)]
|
||||
/// @param[out] endPos The end position of the off-mesh connection. [(x, y, z)]
|
||||
/// @return The status flags for the operation.
|
||||
dtStatus getOffMeshConnectionPolyEndPoints(dtPolyRef prevRef, dtPolyRef polyRef, float* startPos, float* endPos) const;
|
||||
|
||||
/// Gets the specified off-mesh connection.
|
||||
/// @param[in] ref The polygon reference of the off-mesh connection.
|
||||
/// @return The specified off-mesh connection, or null if the polygon reference is not valid.
|
||||
const dtOffMeshConnection* getOffMeshConnectionByRef(dtPolyRef ref) const;
|
||||
|
||||
/// @}
|
||||
|
||||
/// @{
|
||||
/// @name State Management
|
||||
/// These functions do not effect #dtTileRef or #dtPolyRef's.
|
||||
|
||||
/// Sets the user defined flags for the specified polygon.
|
||||
/// @param[in] ref The polygon reference.
|
||||
/// @param[in] flags The new flags for the polygon.
|
||||
/// @return The status flags for the operation.
|
||||
dtStatus setPolyFlags(dtPolyRef ref, unsigned short flags);
|
||||
|
||||
/// Gets the user defined flags for the specified polygon.
|
||||
/// @param[in] ref The polygon reference.
|
||||
/// @param[out] resultFlags The polygon flags.
|
||||
/// @return The status flags for the operation.
|
||||
dtStatus getPolyFlags(dtPolyRef ref, unsigned short* resultFlags) const;
|
||||
|
||||
/// Sets the user defined area for the specified polygon.
|
||||
/// @param[in] ref The polygon reference.
|
||||
/// @param[in] area The new area id for the polygon. [Limit: < #DT_MAX_AREAS]
|
||||
/// @return The status flags for the operation.
|
||||
dtStatus setPolyArea(dtPolyRef ref, unsigned char area);
|
||||
|
||||
/// Gets the user defined area for the specified polygon.
|
||||
/// @param[in] ref The polygon reference.
|
||||
/// @param[out] resultArea The area id for the polygon.
|
||||
/// @return The status flags for the operation.
|
||||
dtStatus getPolyArea(dtPolyRef ref, unsigned char* resultArea) const;
|
||||
|
||||
/// Gets the size of the buffer required by #storeTileState to store the specified tile's state.
|
||||
/// @param[in] tile The tile.
|
||||
/// @return The size of the buffer required to store the state.
|
||||
int getTileStateSize(const dtMeshTile* tile) const;
|
||||
|
||||
/// Stores the non-structural state of the tile in the specified buffer. (Flags, area ids, etc.)
|
||||
/// @param[in] tile The tile.
|
||||
/// @param[out] data The buffer to store the tile's state in.
|
||||
/// @param[in] maxDataSize The size of the data buffer. [Limit: >= #getTileStateSize]
|
||||
/// @return The status flags for the operation.
|
||||
dtStatus storeTileState(const dtMeshTile* tile, unsigned char* data, const int maxDataSize) const;
|
||||
|
||||
/// Restores the state of the tile.
|
||||
/// @param[in] tile The tile.
|
||||
/// @param[in] data The new state. (Obtained from #storeTileState.)
|
||||
/// @param[in] maxDataSize The size of the state within the data buffer.
|
||||
/// @return The status flags for the operation.
|
||||
dtStatus restoreTileState(dtMeshTile* tile, const unsigned char* data, const int maxDataSize);
|
||||
|
||||
/// @}
|
||||
|
||||
/// @{
|
||||
/// @name Encoding and Decoding
|
||||
/// These functions are generally meant for internal use only.
|
||||
|
||||
/// Derives a standard polygon reference.
|
||||
/// @note This function is generally meant for internal use only.
|
||||
/// @param[in] salt The tile's salt value.
|
||||
/// @param[in] it The index of the tile.
|
||||
/// @param[in] ip The index of the polygon within the tile.
|
||||
inline dtPolyRef encodePolyId(unsigned int salt, unsigned int it, unsigned int ip) const
|
||||
{
|
||||
#ifdef DT_POLYREF64
|
||||
return ((dtPolyRef)salt << (DT_POLY_BITS+DT_TILE_BITS)) | ((dtPolyRef)it << DT_POLY_BITS) | (dtPolyRef)ip;
|
||||
#else
|
||||
return ((dtPolyRef)salt << (m_polyBits+m_tileBits)) | ((dtPolyRef)it << m_polyBits) | (dtPolyRef)ip;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Decodes a standard polygon reference.
|
||||
/// @note This function is generally meant for internal use only.
|
||||
/// @param[in] ref The polygon reference to decode.
|
||||
/// @param[out] salt The tile's salt value.
|
||||
/// @param[out] it The index of the tile.
|
||||
/// @param[out] ip The index of the polygon within the tile.
|
||||
/// @see #encodePolyId
|
||||
inline void decodePolyId(dtPolyRef ref, unsigned int& salt, unsigned int& it, unsigned int& ip) const
|
||||
{
|
||||
#ifdef DT_POLYREF64
|
||||
const dtPolyRef saltMask = ((dtPolyRef)1<<DT_SALT_BITS)-1;
|
||||
const dtPolyRef tileMask = ((dtPolyRef)1<<DT_TILE_BITS)-1;
|
||||
const dtPolyRef polyMask = ((dtPolyRef)1<<DT_POLY_BITS)-1;
|
||||
salt = (unsigned int)((ref >> (DT_POLY_BITS+DT_TILE_BITS)) & saltMask);
|
||||
it = (unsigned int)((ref >> DT_POLY_BITS) & tileMask);
|
||||
ip = (unsigned int)(ref & polyMask);
|
||||
#else
|
||||
const dtPolyRef saltMask = ((dtPolyRef)1<<m_saltBits)-1;
|
||||
const dtPolyRef tileMask = ((dtPolyRef)1<<m_tileBits)-1;
|
||||
const dtPolyRef polyMask = ((dtPolyRef)1<<m_polyBits)-1;
|
||||
salt = (unsigned int)((ref >> (m_polyBits+m_tileBits)) & saltMask);
|
||||
it = (unsigned int)((ref >> m_polyBits) & tileMask);
|
||||
ip = (unsigned int)(ref & polyMask);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Extracts a tile's salt value from the specified polygon reference.
|
||||
/// @note This function is generally meant for internal use only.
|
||||
/// @param[in] ref The polygon reference.
|
||||
/// @see #encodePolyId
|
||||
inline unsigned int decodePolyIdSalt(dtPolyRef ref) const
|
||||
{
|
||||
#ifdef DT_POLYREF64
|
||||
const dtPolyRef saltMask = ((dtPolyRef)1<<DT_SALT_BITS)-1;
|
||||
return (unsigned int)((ref >> (DT_POLY_BITS+DT_TILE_BITS)) & saltMask);
|
||||
#else
|
||||
const dtPolyRef saltMask = ((dtPolyRef)1<<m_saltBits)-1;
|
||||
return (unsigned int)((ref >> (m_polyBits+m_tileBits)) & saltMask);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Extracts the tile's index from the specified polygon reference.
|
||||
/// @note This function is generally meant for internal use only.
|
||||
/// @param[in] ref The polygon reference.
|
||||
/// @see #encodePolyId
|
||||
inline unsigned int decodePolyIdTile(dtPolyRef ref) const
|
||||
{
|
||||
#ifdef DT_POLYREF64
|
||||
const dtPolyRef tileMask = ((dtPolyRef)1<<DT_TILE_BITS)-1;
|
||||
return (unsigned int)((ref >> DT_POLY_BITS) & tileMask);
|
||||
#else
|
||||
const dtPolyRef tileMask = ((dtPolyRef)1<<m_tileBits)-1;
|
||||
return (unsigned int)((ref >> m_polyBits) & tileMask);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Extracts the polygon's index (within its tile) from the specified polygon reference.
|
||||
/// @note This function is generally meant for internal use only.
|
||||
/// @param[in] ref The polygon reference.
|
||||
/// @see #encodePolyId
|
||||
inline unsigned int decodePolyIdPoly(dtPolyRef ref) const
|
||||
{
|
||||
#ifdef DT_POLYREF64
|
||||
const dtPolyRef polyMask = ((dtPolyRef)1<<DT_POLY_BITS)-1;
|
||||
return (unsigned int)(ref & polyMask);
|
||||
#else
|
||||
const dtPolyRef polyMask = ((dtPolyRef)1<<m_polyBits)-1;
|
||||
return (unsigned int)(ref & polyMask);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// @}
|
||||
|
||||
private:
|
||||
// Explicitly disabled copy constructor and copy assignment operator.
|
||||
dtNavMesh(const dtNavMesh&);
|
||||
dtNavMesh& operator=(const dtNavMesh&);
|
||||
|
||||
/// Returns pointer to tile in the tile array.
|
||||
dtMeshTile* getTile(int i);
|
||||
|
||||
/// Returns neighbour tile based on side.
|
||||
int getTilesAt(const int x, const int y,
|
||||
dtMeshTile** tiles, const int maxTiles) const;
|
||||
|
||||
/// Returns neighbour tile based on side.
|
||||
int getNeighbourTilesAt(const int x, const int y, const int side,
|
||||
dtMeshTile** tiles, const int maxTiles) const;
|
||||
|
||||
/// Returns all polygons in neighbour tile based on portal defined by the segment.
|
||||
int findConnectingPolys(const float* va, const float* vb,
|
||||
const dtMeshTile* tile, int side,
|
||||
dtPolyRef* con, float* conarea, int maxcon) const;
|
||||
|
||||
/// Builds internal polygons links for a tile.
|
||||
void connectIntLinks(dtMeshTile* tile);
|
||||
/// Builds internal polygons links for a tile.
|
||||
void baseOffMeshLinks(dtMeshTile* tile);
|
||||
|
||||
/// Builds external polygon links for a tile.
|
||||
void connectExtLinks(dtMeshTile* tile, dtMeshTile* target, int side);
|
||||
/// Builds external polygon links for a tile.
|
||||
void connectExtOffMeshLinks(dtMeshTile* tile, dtMeshTile* target, int side);
|
||||
|
||||
/// Removes external links at specified side.
|
||||
void unconnectLinks(dtMeshTile* tile, dtMeshTile* target);
|
||||
|
||||
|
||||
// TODO: These methods are duplicates from dtNavMeshQuery, but are needed for off-mesh connection finding.
|
||||
|
||||
/// Queries polygons within a tile.
|
||||
int queryPolygonsInTile(const dtMeshTile* tile, const float* qmin, const float* qmax,
|
||||
dtPolyRef* polys, const int maxPolys) const;
|
||||
/// Find nearest polygon within a tile.
|
||||
dtPolyRef findNearestPolyInTile(const dtMeshTile* tile, const float* center,
|
||||
const float* halfExtents, float* nearestPt) const;
|
||||
/// Returns whether position is over the poly and the height at the position if so.
|
||||
bool getPolyHeight(const dtMeshTile* tile, const dtPoly* poly, const float* pos, float* height) const;
|
||||
/// Returns closest point on polygon.
|
||||
void closestPointOnPoly(dtPolyRef ref, const float* pos, float* closest, bool* posOverPoly) const;
|
||||
|
||||
dtNavMeshParams m_params; ///< Current initialization params. TODO: do not store this info twice.
|
||||
float m_orig[3]; ///< Origin of the tile (0,0)
|
||||
float m_tileWidth, m_tileHeight; ///< Dimensions of each tile.
|
||||
int m_maxTiles; ///< Max number of tiles.
|
||||
int m_tileLutSize; ///< Tile hash lookup size (must be pot).
|
||||
int m_tileLutMask; ///< Tile hash lookup mask.
|
||||
|
||||
dtMeshTile** m_posLookup; ///< Tile hash lookup.
|
||||
dtMeshTile* m_nextFree; ///< Freelist of tiles.
|
||||
dtMeshTile* m_tiles; ///< List of tiles.
|
||||
|
||||
#ifndef DT_POLYREF64
|
||||
unsigned int m_saltBits; ///< Number of salt bits in the tile ID.
|
||||
unsigned int m_tileBits; ///< Number of tile bits in the tile ID.
|
||||
unsigned int m_polyBits; ///< Number of poly bits in the tile ID.
|
||||
#endif
|
||||
|
||||
friend class dtNavMeshQuery;
|
||||
};
|
||||
|
||||
/// Allocates a navigation mesh object using the Detour allocator.
|
||||
/// @return A navigation mesh that is ready for initialization, or null on failure.
|
||||
/// @ingroup detour
|
||||
dtNavMesh* dtAllocNavMesh();
|
||||
|
||||
/// Frees the specified navigation mesh object using the Detour allocator.
|
||||
/// @param[in] navmesh A navigation mesh allocated using #dtAllocNavMesh
|
||||
/// @ingroup detour
|
||||
void dtFreeNavMesh(dtNavMesh* navmesh);
|
||||
|
||||
#endif // DETOURNAVMESH_H
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// This section contains detailed documentation for members that don't have
|
||||
// a source file. It reduces clutter in the main section of the header.
|
||||
|
||||
/**
|
||||
|
||||
@typedef dtPolyRef
|
||||
@par
|
||||
|
||||
Polygon references are subject to the same invalidate/preserve/restore
|
||||
rules that apply to #dtTileRef's. If the #dtTileRef for the polygon's
|
||||
tile changes, the polygon reference becomes invalid.
|
||||
|
||||
Changing a polygon's flags, area id, etc. does not impact its polygon
|
||||
reference.
|
||||
|
||||
@typedef dtTileRef
|
||||
@par
|
||||
|
||||
The following changes will invalidate a tile reference:
|
||||
|
||||
- The referenced tile has been removed from the navigation mesh.
|
||||
- The navigation mesh has been initialized using a different set
|
||||
of #dtNavMeshParams.
|
||||
|
||||
A tile reference is preserved/restored if the tile is added to a navigation
|
||||
mesh initialized with the original #dtNavMeshParams and is added at the
|
||||
original reference location. (E.g. The lastRef parameter is used with
|
||||
dtNavMesh::addTile.)
|
||||
|
||||
Basically, if the storage structure of a tile changes, its associated
|
||||
tile reference changes.
|
||||
|
||||
|
||||
@var unsigned short dtPoly::neis[DT_VERTS_PER_POLYGON]
|
||||
@par
|
||||
|
||||
Each entry represents data for the edge starting at the vertex of the same index.
|
||||
E.g. The entry at index n represents the edge data for vertex[n] to vertex[n+1].
|
||||
|
||||
A value of zero indicates the edge has no polygon connection. (It makes up the
|
||||
border of the navigation mesh.)
|
||||
|
||||
The information can be extracted as follows:
|
||||
@code
|
||||
neighborRef = neis[n] & 0xff; // Get the neighbor polygon reference.
|
||||
|
||||
if (neis[n] & #DT_EX_LINK)
|
||||
{
|
||||
// The edge is an external (portal) edge.
|
||||
}
|
||||
@endcode
|
||||
|
||||
@var float dtMeshHeader::bvQuantFactor
|
||||
@par
|
||||
|
||||
This value is used for converting between world and bounding volume coordinates.
|
||||
For example:
|
||||
@code
|
||||
const float cs = 1.0f / tile->header->bvQuantFactor;
|
||||
const dtBVNode* n = &tile->bvTree[i];
|
||||
if (n->i >= 0)
|
||||
{
|
||||
// This is a leaf node.
|
||||
float worldMinX = tile->header->bmin[0] + n->bmin[0]*cs;
|
||||
float worldMinY = tile->header->bmin[0] + n->bmin[1]*cs;
|
||||
// Etc...
|
||||
}
|
||||
@endcode
|
||||
|
||||
@struct dtMeshTile
|
||||
@par
|
||||
|
||||
Tiles generally only exist within the context of a dtNavMesh object.
|
||||
|
||||
Some tile content is optional. For example, a tile may not contain any
|
||||
off-mesh connections. In this case the associated pointer will be null.
|
||||
|
||||
If a detail mesh exists it will share vertices with the base polygon mesh.
|
||||
Only the vertices unique to the detail mesh will be stored in #detailVerts.
|
||||
|
||||
@warning Tiles returned by a dtNavMesh object are not guarenteed to be populated.
|
||||
For example: The tile at a location might not have been loaded yet, or may have been removed.
|
||||
In this case, pointers will be null. So if in doubt, check the polygon count in the
|
||||
tile's header to determine if a tile has polygons defined.
|
||||
|
||||
@var float dtOffMeshConnection::pos[6]
|
||||
@par
|
||||
|
||||
For a properly built navigation mesh, vertex A will always be within the bounds of the mesh.
|
||||
Vertex B is not required to be within the bounds of the mesh.
|
||||
|
||||
*/
|
||||
@@ -0,0 +1,149 @@
|
||||
//
|
||||
// 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 DETOURNAVMESHBUILDER_H
|
||||
#define DETOURNAVMESHBUILDER_H
|
||||
|
||||
#include "DetourAlloc.h"
|
||||
|
||||
/// Represents the source data used to build an navigation mesh tile.
|
||||
/// @ingroup detour
|
||||
struct dtNavMeshCreateParams
|
||||
{
|
||||
|
||||
/// @name Polygon Mesh Attributes
|
||||
/// Used to create the base navigation graph.
|
||||
/// See #rcPolyMesh for details related to these attributes.
|
||||
/// @{
|
||||
|
||||
const unsigned short* verts; ///< The polygon mesh vertices. [(x, y, z) * #vertCount] [Unit: vx]
|
||||
int vertCount; ///< The number vertices in the polygon mesh. [Limit: >= 3]
|
||||
const unsigned short* polys; ///< The polygon data. [Size: #polyCount * 2 * #nvp]
|
||||
const unsigned short* polyFlags; ///< The user defined flags assigned to each polygon. [Size: #polyCount]
|
||||
const unsigned char* polyAreas; ///< The user defined area ids assigned to each polygon. [Size: #polyCount]
|
||||
int polyCount; ///< Number of polygons in the mesh. [Limit: >= 1]
|
||||
int nvp; ///< Number maximum number of vertices per polygon. [Limit: >= 3]
|
||||
|
||||
/// @}
|
||||
/// @name Height Detail Attributes (Optional)
|
||||
/// See #rcPolyMeshDetail for details related to these attributes.
|
||||
/// @{
|
||||
|
||||
const unsigned int* detailMeshes; ///< The height detail sub-mesh data. [Size: 4 * #polyCount]
|
||||
const float* detailVerts; ///< The detail mesh vertices. [Size: 3 * #detailVertsCount] [Unit: wu]
|
||||
int detailVertsCount; ///< The number of vertices in the detail mesh.
|
||||
const unsigned char* detailTris; ///< The detail mesh triangles. [Size: 4 * #detailTriCount]
|
||||
int detailTriCount; ///< The number of triangles in the detail mesh.
|
||||
|
||||
/// @}
|
||||
/// @name Off-Mesh Connections Attributes (Optional)
|
||||
/// Used to define a custom point-to-point edge within the navigation graph, an
|
||||
/// off-mesh connection is a user defined traversable connection made up to two vertices,
|
||||
/// at least one of which resides within a navigation mesh polygon.
|
||||
/// @{
|
||||
|
||||
/// Off-mesh connection vertices. [(ax, ay, az, bx, by, bz) * #offMeshConCount] [Unit: wu]
|
||||
const float* offMeshConVerts;
|
||||
/// Off-mesh connection radii. [Size: #offMeshConCount] [Unit: wu]
|
||||
const float* offMeshConRad;
|
||||
/// User defined flags assigned to the off-mesh connections. [Size: #offMeshConCount]
|
||||
const unsigned short* offMeshConFlags;
|
||||
/// User defined area ids assigned to the off-mesh connections. [Size: #offMeshConCount]
|
||||
const unsigned char* offMeshConAreas;
|
||||
/// The permitted travel direction of the off-mesh connections. [Size: #offMeshConCount]
|
||||
///
|
||||
/// 0 = Travel only from endpoint A to endpoint B.<br/>
|
||||
/// #DT_OFFMESH_CON_BIDIR = Bidirectional travel.
|
||||
const unsigned char* offMeshConDir;
|
||||
/// The user defined ids of the off-mesh connection. [Size: #offMeshConCount]
|
||||
const unsigned int* offMeshConUserID;
|
||||
/// The number of off-mesh connections. [Limit: >= 0]
|
||||
int offMeshConCount;
|
||||
|
||||
/// @}
|
||||
/// @name Tile Attributes
|
||||
/// @note The tile grid/layer data can be left at zero if the destination is a single tile mesh.
|
||||
/// @{
|
||||
|
||||
unsigned int userId; ///< The user defined id of the tile.
|
||||
int tileX; ///< The tile's x-grid location within the multi-tile destination mesh. (Along the x-axis.)
|
||||
int tileY; ///< The tile's y-grid location within the multi-tile destination mesh. (Along the z-axis.)
|
||||
int tileLayer; ///< The tile's layer within the layered destination mesh. [Limit: >= 0] (Along the y-axis.)
|
||||
float bmin[3]; ///< The minimum bounds of the tile. [(x, y, z)] [Unit: wu]
|
||||
float bmax[3]; ///< The maximum bounds of the tile. [(x, y, z)] [Unit: wu]
|
||||
|
||||
/// @}
|
||||
/// @name General Configuration Attributes
|
||||
/// @{
|
||||
|
||||
float walkableHeight; ///< The agent height. [Unit: wu]
|
||||
float walkableRadius; ///< The agent radius. [Unit: wu]
|
||||
float walkableClimb; ///< The agent maximum traversable ledge. (Up/Down) [Unit: wu]
|
||||
float cs; ///< The xz-plane cell size of the polygon mesh. [Limit: > 0] [Unit: wu]
|
||||
float ch; ///< The y-axis cell height of the polygon mesh. [Limit: > 0] [Unit: wu]
|
||||
|
||||
/// True if a bounding volume tree should be built for the tile.
|
||||
/// @note The BVTree is not normally needed for layered navigation meshes.
|
||||
bool buildBvTree;
|
||||
|
||||
/// @}
|
||||
};
|
||||
|
||||
/// Builds navigation mesh tile data from the provided tile creation data.
|
||||
/// @ingroup detour
|
||||
/// @param[in] params Tile creation data.
|
||||
/// @param[out] outData The resulting tile data.
|
||||
/// @param[out] outDataSize The size of the tile data array.
|
||||
/// @return True if the tile data was successfully created.
|
||||
bool dtCreateNavMeshData(dtNavMeshCreateParams* params, unsigned char** outData, int* outDataSize);
|
||||
|
||||
/// Swaps the endianness of the tile data's header (#dtMeshHeader).
|
||||
/// @param[in,out] data The tile data array.
|
||||
/// @param[in] dataSize The size of the data array.
|
||||
bool dtNavMeshHeaderSwapEndian(unsigned char* data, const int dataSize);
|
||||
|
||||
/// Swaps endianness of the tile data.
|
||||
/// @param[in,out] data The tile data array.
|
||||
/// @param[in] dataSize The size of the data array.
|
||||
bool dtNavMeshDataSwapEndian(unsigned char* data, const int dataSize);
|
||||
|
||||
#endif // DETOURNAVMESHBUILDER_H
|
||||
|
||||
// This section contains detailed documentation for members that don't have
|
||||
// a source file. It reduces clutter in the main section of the header.
|
||||
|
||||
/**
|
||||
|
||||
@struct dtNavMeshCreateParams
|
||||
@par
|
||||
|
||||
This structure is used to marshal data between the Recast mesh generation pipeline and Detour navigation components.
|
||||
|
||||
See the rcPolyMesh and rcPolyMeshDetail documentation for detailed information related to mesh structure.
|
||||
|
||||
Units are usually in voxels (vx) or world units (wu). The units for voxels, grid size, and cell size
|
||||
are all based on the values of #cs and #ch.
|
||||
|
||||
The standard navigation mesh build process is to create tile data using dtCreateNavMeshData, then add the tile
|
||||
to a navigation mesh using either the dtNavMesh single tile <tt>init()</tt> function or the dtNavMesh::addTile()
|
||||
function.
|
||||
|
||||
@see dtCreateNavMeshData
|
||||
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,590 @@
|
||||
//
|
||||
// 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 DETOURNAVMESHQUERY_H
|
||||
#define DETOURNAVMESHQUERY_H
|
||||
|
||||
#include "DetourNavMesh.h"
|
||||
#include "DetourStatus.h"
|
||||
|
||||
|
||||
// Define DT_VIRTUAL_QUERYFILTER if you wish to derive a custom filter from dtQueryFilter.
|
||||
// On certain platforms indirect or virtual function call is expensive. The default
|
||||
// setting is to use non-virtual functions, the actual implementations of the functions
|
||||
// are declared as inline for maximum speed.
|
||||
|
||||
//#define DT_VIRTUAL_QUERYFILTER 1
|
||||
|
||||
/// Defines polygon filtering and traversal costs for navigation mesh query operations.
|
||||
/// @ingroup detour
|
||||
class dtQueryFilter
|
||||
{
|
||||
float m_areaCost[DT_MAX_AREAS]; ///< Cost per area type. (Used by default implementation.)
|
||||
unsigned short m_includeFlags; ///< Flags for polygons that can be visited. (Used by default implementation.)
|
||||
unsigned short m_excludeFlags; ///< Flags for polygons that should not be visited. (Used by default implementation.)
|
||||
|
||||
public:
|
||||
dtQueryFilter();
|
||||
|
||||
#ifdef DT_VIRTUAL_QUERYFILTER
|
||||
virtual ~dtQueryFilter() { }
|
||||
#endif
|
||||
|
||||
/// Returns true if the polygon can be visited. (I.e. Is traversable.)
|
||||
/// @param[in] ref The reference id of the polygon test.
|
||||
/// @param[in] tile The tile containing the polygon.
|
||||
/// @param[in] poly The polygon to test.
|
||||
#ifdef DT_VIRTUAL_QUERYFILTER
|
||||
virtual bool passFilter(const dtPolyRef ref,
|
||||
const dtMeshTile* tile,
|
||||
const dtPoly* poly) const;
|
||||
#else
|
||||
bool passFilter(const dtPolyRef ref,
|
||||
const dtMeshTile* tile,
|
||||
const dtPoly* poly) const;
|
||||
#endif
|
||||
|
||||
/// Returns cost to move from the beginning to the end of a line segment
|
||||
/// that is fully contained within a polygon.
|
||||
/// @param[in] pa The start position on the edge of the previous and current polygon. [(x, y, z)]
|
||||
/// @param[in] pb The end position on the edge of the current and next polygon. [(x, y, z)]
|
||||
/// @param[in] prevRef The reference id of the previous polygon. [opt]
|
||||
/// @param[in] prevTile The tile containing the previous polygon. [opt]
|
||||
/// @param[in] prevPoly The previous polygon. [opt]
|
||||
/// @param[in] curRef The reference id of the current polygon.
|
||||
/// @param[in] curTile The tile containing the current polygon.
|
||||
/// @param[in] curPoly The current polygon.
|
||||
/// @param[in] nextRef The refernece id of the next polygon. [opt]
|
||||
/// @param[in] nextTile The tile containing the next polygon. [opt]
|
||||
/// @param[in] nextPoly The next polygon. [opt]
|
||||
#ifdef DT_VIRTUAL_QUERYFILTER
|
||||
virtual float getCost(const float* pa, const float* pb,
|
||||
const dtPolyRef prevRef, const dtMeshTile* prevTile, const dtPoly* prevPoly,
|
||||
const dtPolyRef curRef, const dtMeshTile* curTile, const dtPoly* curPoly,
|
||||
const dtPolyRef nextRef, const dtMeshTile* nextTile, const dtPoly* nextPoly) const;
|
||||
#else
|
||||
float getCost(const float* pa, const float* pb,
|
||||
const dtPolyRef prevRef, const dtMeshTile* prevTile, const dtPoly* prevPoly,
|
||||
const dtPolyRef curRef, const dtMeshTile* curTile, const dtPoly* curPoly,
|
||||
const dtPolyRef nextRef, const dtMeshTile* nextTile, const dtPoly* nextPoly) const;
|
||||
#endif
|
||||
|
||||
/// @name Getters and setters for the default implementation data.
|
||||
///@{
|
||||
|
||||
/// Returns the traversal cost of the area.
|
||||
/// @param[in] i The id of the area.
|
||||
/// @returns The traversal cost of the area.
|
||||
inline float getAreaCost(const int i) const { return m_areaCost[i]; }
|
||||
|
||||
/// Sets the traversal cost of the area.
|
||||
/// @param[in] i The id of the area.
|
||||
/// @param[in] cost The new cost of traversing the area.
|
||||
inline void setAreaCost(const int i, const float cost) { m_areaCost[i] = cost; }
|
||||
|
||||
/// Returns the include flags for the filter.
|
||||
/// Any polygons that include one or more of these flags will be
|
||||
/// included in the operation.
|
||||
inline unsigned short getIncludeFlags() const { return m_includeFlags; }
|
||||
|
||||
/// Sets the include flags for the filter.
|
||||
/// @param[in] flags The new flags.
|
||||
inline void setIncludeFlags(const unsigned short flags) { m_includeFlags = flags; }
|
||||
|
||||
/// Returns the exclude flags for the filter.
|
||||
/// Any polygons that include one ore more of these flags will be
|
||||
/// excluded from the operation.
|
||||
inline unsigned short getExcludeFlags() const { return m_excludeFlags; }
|
||||
|
||||
/// Sets the exclude flags for the filter.
|
||||
/// @param[in] flags The new flags.
|
||||
inline void setExcludeFlags(const unsigned short flags) { m_excludeFlags = flags; }
|
||||
|
||||
///@}
|
||||
|
||||
};
|
||||
|
||||
/// Provides information about raycast hit
|
||||
/// filled by dtNavMeshQuery::raycast
|
||||
/// @ingroup detour
|
||||
struct dtRaycastHit
|
||||
{
|
||||
/// The hit parameter. (FLT_MAX if no wall hit.)
|
||||
float t;
|
||||
|
||||
/// hitNormal The normal of the nearest wall hit. [(x, y, z)]
|
||||
float hitNormal[3];
|
||||
|
||||
/// The index of the edge on the final polygon where the wall was hit.
|
||||
int hitEdgeIndex;
|
||||
|
||||
/// Pointer to an array of reference ids of the visited polygons. [opt]
|
||||
dtPolyRef* path;
|
||||
|
||||
/// The number of visited polygons. [opt]
|
||||
int pathCount;
|
||||
|
||||
/// The maximum number of polygons the @p path array can hold.
|
||||
int maxPath;
|
||||
|
||||
/// The cost of the path until hit.
|
||||
float pathCost;
|
||||
};
|
||||
|
||||
/// Provides custom polygon query behavior.
|
||||
/// Used by dtNavMeshQuery::queryPolygons.
|
||||
/// @ingroup detour
|
||||
class dtPolyQuery
|
||||
{
|
||||
public:
|
||||
virtual ~dtPolyQuery();
|
||||
|
||||
/// Called for each batch of unique polygons touched by the search area in dtNavMeshQuery::queryPolygons.
|
||||
/// This can be called multiple times for a single query.
|
||||
virtual void process(const dtMeshTile* tile, dtPoly** polys, dtPolyRef* refs, int count) = 0;
|
||||
};
|
||||
|
||||
/// Provides the ability to perform pathfinding related queries against
|
||||
/// a navigation mesh.
|
||||
/// @ingroup detour
|
||||
class dtNavMeshQuery
|
||||
{
|
||||
public:
|
||||
dtNavMeshQuery();
|
||||
~dtNavMeshQuery();
|
||||
|
||||
/// Initializes the query object.
|
||||
/// @param[in] nav Pointer to the dtNavMesh object to use for all queries.
|
||||
/// @param[in] maxNodes Maximum number of search nodes. [Limits: 0 < value <= 65535]
|
||||
/// @returns The status flags for the query.
|
||||
dtStatus init(const dtNavMesh* nav, const int maxNodes);
|
||||
|
||||
/// @name Standard Pathfinding Functions
|
||||
/// @{
|
||||
|
||||
/// Finds a path from the start polygon to the end polygon.
|
||||
/// @param[in] startRef The reference id of the start polygon.
|
||||
/// @param[in] endRef The reference id of the end polygon.
|
||||
/// @param[in] startPos A position within the start polygon. [(x, y, z)]
|
||||
/// @param[in] endPos A position within the end polygon. [(x, y, z)]
|
||||
/// @param[in] filter The polygon filter to apply to the query.
|
||||
/// @param[out] path An ordered list of polygon references representing the path. (Start to end.)
|
||||
/// [(polyRef) * @p pathCount]
|
||||
/// @param[out] pathCount The number of polygons returned in the @p path array.
|
||||
/// @param[in] maxPath The maximum number of polygons the @p path array can hold. [Limit: >= 1]
|
||||
dtStatus findPath(dtPolyRef startRef, dtPolyRef endRef,
|
||||
const float* startPos, const float* endPos,
|
||||
const dtQueryFilter* filter,
|
||||
dtPolyRef* path, int* pathCount, const int maxPath) const;
|
||||
|
||||
/// Finds the straight path from the start to the end position within the polygon corridor.
|
||||
/// @param[in] startPos Path start position. [(x, y, z)]
|
||||
/// @param[in] endPos Path end position. [(x, y, z)]
|
||||
/// @param[in] path An array of polygon references that represent the path corridor.
|
||||
/// @param[in] pathSize The number of polygons in the @p path array.
|
||||
/// @param[out] straightPath Points describing the straight path. [(x, y, z) * @p straightPathCount].
|
||||
/// @param[out] straightPathFlags Flags describing each point. (See: #dtStraightPathFlags) [opt]
|
||||
/// @param[out] straightPathRefs The reference id of the polygon that is being entered at each point. [opt]
|
||||
/// @param[out] straightPathCount The number of points in the straight path.
|
||||
/// @param[in] maxStraightPath The maximum number of points the straight path arrays can hold. [Limit: > 0]
|
||||
/// @param[in] options Query options. (see: #dtStraightPathOptions)
|
||||
/// @returns The status flags for the query.
|
||||
dtStatus findStraightPath(const float* startPos, const float* endPos,
|
||||
const dtPolyRef* path, const int pathSize,
|
||||
float* straightPath, unsigned char* straightPathFlags, dtPolyRef* straightPathRefs,
|
||||
int* straightPathCount, const int maxStraightPath, const int options = 0) const;
|
||||
|
||||
///@}
|
||||
/// @name Sliced Pathfinding Functions
|
||||
/// Common use case:
|
||||
/// -# Call initSlicedFindPath() to initialize the sliced path query.
|
||||
/// -# Call updateSlicedFindPath() until it returns complete.
|
||||
/// -# Call finalizeSlicedFindPath() to get the path.
|
||||
///@{
|
||||
|
||||
/// Initializes a sliced path query.
|
||||
/// @param[in] startRef The reference id of the start polygon.
|
||||
/// @param[in] endRef The reference id of the end polygon.
|
||||
/// @param[in] startPos A position within the start polygon. [(x, y, z)]
|
||||
/// @param[in] endPos A position within the end polygon. [(x, y, z)]
|
||||
/// @param[in] filter The polygon filter to apply to the query.
|
||||
/// @param[in] options query options (see: #dtFindPathOptions)
|
||||
/// @returns The status flags for the query.
|
||||
dtStatus initSlicedFindPath(dtPolyRef startRef, dtPolyRef endRef,
|
||||
const float* startPos, const float* endPos,
|
||||
const dtQueryFilter* filter, const unsigned int options = 0);
|
||||
|
||||
/// Updates an in-progress sliced path query.
|
||||
/// @param[in] maxIter The maximum number of iterations to perform.
|
||||
/// @param[out] doneIters The actual number of iterations completed. [opt]
|
||||
/// @returns The status flags for the query.
|
||||
dtStatus updateSlicedFindPath(const int maxIter, int* doneIters);
|
||||
|
||||
/// Finalizes and returns the results of a sliced path query.
|
||||
/// @param[out] path An ordered list of polygon references representing the path. (Start to end.)
|
||||
/// [(polyRef) * @p pathCount]
|
||||
/// @param[out] pathCount The number of polygons returned in the @p path array.
|
||||
/// @param[in] maxPath The max number of polygons the path array can hold. [Limit: >= 1]
|
||||
/// @returns The status flags for the query.
|
||||
dtStatus finalizeSlicedFindPath(dtPolyRef* path, int* pathCount, const int maxPath);
|
||||
|
||||
/// Finalizes and returns the results of an incomplete sliced path query, returning the path to the furthest
|
||||
/// polygon on the existing path that was visited during the search.
|
||||
/// @param[in] existing An array of polygon references for the existing path.
|
||||
/// @param[in] existingSize The number of polygon in the @p existing array.
|
||||
/// @param[out] path An ordered list of polygon references representing the path. (Start to end.)
|
||||
/// [(polyRef) * @p pathCount]
|
||||
/// @param[out] pathCount The number of polygons returned in the @p path array.
|
||||
/// @param[in] maxPath The max number of polygons the @p path array can hold. [Limit: >= 1]
|
||||
/// @returns The status flags for the query.
|
||||
dtStatus finalizeSlicedFindPathPartial(const dtPolyRef* existing, const int existingSize,
|
||||
dtPolyRef* path, int* pathCount, const int maxPath);
|
||||
|
||||
///@}
|
||||
/// @name Dijkstra Search Functions
|
||||
/// @{
|
||||
|
||||
/// Finds the polygons along the navigation graph that touch the specified circle.
|
||||
/// @param[in] startRef The reference id of the polygon where the search starts.
|
||||
/// @param[in] centerPos The center of the search circle. [(x, y, z)]
|
||||
/// @param[in] radius The radius of the search circle.
|
||||
/// @param[in] filter The polygon filter to apply to the query.
|
||||
/// @param[out] resultRef The reference ids of the polygons touched by the circle. [opt]
|
||||
/// @param[out] resultParent The reference ids of the parent polygons for each result.
|
||||
/// Zero if a result polygon has no parent. [opt]
|
||||
/// @param[out] resultCost The search cost from @p centerPos to the polygon. [opt]
|
||||
/// @param[out] resultCount The number of polygons found. [opt]
|
||||
/// @param[in] maxResult The maximum number of polygons the result arrays can hold.
|
||||
/// @returns The status flags for the query.
|
||||
dtStatus findPolysAroundCircle(dtPolyRef startRef, const float* centerPos, const float radius,
|
||||
const dtQueryFilter* filter,
|
||||
dtPolyRef* resultRef, dtPolyRef* resultParent, float* resultCost,
|
||||
int* resultCount, const int maxResult) const;
|
||||
|
||||
/// Finds the polygons along the naviation graph that touch the specified convex polygon.
|
||||
/// @param[in] startRef The reference id of the polygon where the search starts.
|
||||
/// @param[in] verts The vertices describing the convex polygon. (CCW)
|
||||
/// [(x, y, z) * @p nverts]
|
||||
/// @param[in] nverts The number of vertices in the polygon.
|
||||
/// @param[in] filter The polygon filter to apply to the query.
|
||||
/// @param[out] resultRef The reference ids of the polygons touched by the search polygon. [opt]
|
||||
/// @param[out] resultParent The reference ids of the parent polygons for each result. Zero if a
|
||||
/// result polygon has no parent. [opt]
|
||||
/// @param[out] resultCost The search cost from the centroid point to the polygon. [opt]
|
||||
/// @param[out] resultCount The number of polygons found.
|
||||
/// @param[in] maxResult The maximum number of polygons the result arrays can hold.
|
||||
/// @returns The status flags for the query.
|
||||
dtStatus findPolysAroundShape(dtPolyRef startRef, const float* verts, const int nverts,
|
||||
const dtQueryFilter* filter,
|
||||
dtPolyRef* resultRef, dtPolyRef* resultParent, float* resultCost,
|
||||
int* resultCount, const int maxResult) const;
|
||||
|
||||
/// Gets a path from the explored nodes in the previous search.
|
||||
/// @param[in] endRef The reference id of the end polygon.
|
||||
/// @param[out] path An ordered list of polygon references representing the path. (Start to end.)
|
||||
/// [(polyRef) * @p pathCount]
|
||||
/// @param[out] pathCount The number of polygons returned in the @p path array.
|
||||
/// @param[in] maxPath The maximum number of polygons the @p path array can hold. [Limit: >= 0]
|
||||
/// @returns The status flags. Returns DT_FAILURE | DT_INVALID_PARAM if any parameter is wrong, or if
|
||||
/// @p endRef was not explored in the previous search. Returns DT_SUCCESS | DT_BUFFER_TOO_SMALL
|
||||
/// if @p path cannot contain the entire path. In this case it is filled to capacity with a partial path.
|
||||
/// Otherwise returns DT_SUCCESS.
|
||||
/// @remarks The result of this function depends on the state of the query object. For that reason it should only
|
||||
/// be used immediately after one of the two Dijkstra searches, findPolysAroundCircle or findPolysAroundShape.
|
||||
dtStatus getPathFromDijkstraSearch(dtPolyRef endRef, dtPolyRef* path, int* pathCount, int maxPath) const;
|
||||
|
||||
/// @}
|
||||
/// @name Local Query Functions
|
||||
///@{
|
||||
|
||||
/// Finds the polygon nearest to the specified center point.
|
||||
/// [opt] means the specified parameter can be a null pointer, in that case the output parameter will not be set.
|
||||
///
|
||||
/// @param[in] center The center of the search box. [(x, y, z)]
|
||||
/// @param[in] halfExtents The search distance along each axis. [(x, y, z)]
|
||||
/// @param[in] filter The polygon filter to apply to the query.
|
||||
/// @param[out] nearestRef The reference id of the nearest polygon. Will be set to 0 if no polygon is found.
|
||||
/// @param[out] nearestPt The nearest point on the polygon. Unchanged if no polygon is found. [opt] [(x, y, z)]
|
||||
/// @returns The status flags for the query.
|
||||
dtStatus findNearestPoly(const float* center, const float* halfExtents,
|
||||
const dtQueryFilter* filter,
|
||||
dtPolyRef* nearestRef, float* nearestPt) const;
|
||||
|
||||
/// Finds the polygon nearest to the specified center point.
|
||||
/// [opt] means the specified parameter can be a null pointer, in that case the output parameter will not be set.
|
||||
///
|
||||
/// @param[in] center The center of the search box. [(x, y, z)]
|
||||
/// @param[in] halfExtents The search distance along each axis. [(x, y, z)]
|
||||
/// @param[in] filter The polygon filter to apply to the query.
|
||||
/// @param[out] nearestRef The reference id of the nearest polygon. Will be set to 0 if no polygon is found.
|
||||
/// @param[out] nearestPt The nearest point on the polygon. Unchanged if no polygon is found. [opt] [(x, y, z)]
|
||||
/// @param[out] isOverPoly Set to true if the point's X/Z coordinate lies inside the polygon, false otherwise. Unchanged if no polygon is found. [opt]
|
||||
/// @returns The status flags for the query.
|
||||
dtStatus findNearestPoly(const float* center, const float* halfExtents,
|
||||
const dtQueryFilter* filter,
|
||||
dtPolyRef* nearestRef, float* nearestPt, bool* isOverPoly) const;
|
||||
|
||||
/// Finds polygons that overlap the search box.
|
||||
/// @param[in] center The center of the search box. [(x, y, z)]
|
||||
/// @param[in] halfExtents The search distance along each axis. [(x, y, z)]
|
||||
/// @param[in] filter The polygon filter to apply to the query.
|
||||
/// @param[out] polys The reference ids of the polygons that overlap the query box.
|
||||
/// @param[out] polyCount The number of polygons in the search result.
|
||||
/// @param[in] maxPolys The maximum number of polygons the search result can hold.
|
||||
/// @returns The status flags for the query.
|
||||
dtStatus queryPolygons(const float* center, const float* halfExtents,
|
||||
const dtQueryFilter* filter,
|
||||
dtPolyRef* polys, int* polyCount, const int maxPolys) const;
|
||||
|
||||
/// Finds polygons that overlap the search box.
|
||||
/// @param[in] center The center of the search box. [(x, y, z)]
|
||||
/// @param[in] halfExtents The search distance along each axis. [(x, y, z)]
|
||||
/// @param[in] filter The polygon filter to apply to the query.
|
||||
/// @param[in] query The query. Polygons found will be batched together and passed to this query.
|
||||
dtStatus queryPolygons(const float* center, const float* halfExtents,
|
||||
const dtQueryFilter* filter, dtPolyQuery* query) const;
|
||||
|
||||
/// Finds the non-overlapping navigation polygons in the local neighbourhood around the center position.
|
||||
/// @param[in] startRef The reference id of the polygon where the search starts.
|
||||
/// @param[in] centerPos The center of the query circle. [(x, y, z)]
|
||||
/// @param[in] radius The radius of the query circle.
|
||||
/// @param[in] filter The polygon filter to apply to the query.
|
||||
/// @param[out] resultRef The reference ids of the polygons touched by the circle.
|
||||
/// @param[out] resultParent The reference ids of the parent polygons for each result.
|
||||
/// Zero if a result polygon has no parent. [opt]
|
||||
/// @param[out] resultCount The number of polygons found.
|
||||
/// @param[in] maxResult The maximum number of polygons the result arrays can hold.
|
||||
/// @returns The status flags for the query.
|
||||
dtStatus findLocalNeighbourhood(dtPolyRef startRef, const float* centerPos, const float radius,
|
||||
const dtQueryFilter* filter,
|
||||
dtPolyRef* resultRef, dtPolyRef* resultParent,
|
||||
int* resultCount, const int maxResult) const;
|
||||
|
||||
/// Moves from the start to the end position constrained to the navigation mesh.
|
||||
/// @param[in] startRef The reference id of the start polygon.
|
||||
/// @param[in] startPos A position of the mover within the start polygon. [(x, y, x)]
|
||||
/// @param[in] endPos The desired end position of the mover. [(x, y, z)]
|
||||
/// @param[in] filter The polygon filter to apply to the query.
|
||||
/// @param[out] resultPos The result position of the mover. [(x, y, z)]
|
||||
/// @param[out] visited The reference ids of the polygons visited during the move.
|
||||
/// @param[out] visitedCount The number of polygons visited during the move.
|
||||
/// @param[in] maxVisitedSize The maximum number of polygons the @p visited array can hold.
|
||||
/// @returns The status flags for the query.
|
||||
dtStatus moveAlongSurface(dtPolyRef startRef, const float* startPos, const float* endPos,
|
||||
const dtQueryFilter* filter,
|
||||
float* resultPos, dtPolyRef* visited, int* visitedCount, const int maxVisitedSize) const;
|
||||
|
||||
/// Casts a 'walkability' ray along the surface of the navigation mesh from
|
||||
/// the start position toward the end position.
|
||||
/// @note A wrapper around raycast(..., RaycastHit*). Retained for backward compatibility.
|
||||
/// @param[in] startRef The reference id of the start polygon.
|
||||
/// @param[in] startPos A position within the start polygon representing
|
||||
/// the start of the ray. [(x, y, z)]
|
||||
/// @param[in] endPos The position to cast the ray toward. [(x, y, z)]
|
||||
/// @param[in] filter The polygon filter to apply to the query.
|
||||
/// @param[out] t The hit parameter. (FLT_MAX if no wall hit.)
|
||||
/// @param[out] hitNormal The normal of the nearest wall hit. [(x, y, z)]
|
||||
/// @param[out] path The reference ids of the visited polygons. [opt]
|
||||
/// @param[out] pathCount The number of visited polygons. [opt]
|
||||
/// @param[in] maxPath The maximum number of polygons the @p path array can hold.
|
||||
/// @returns The status flags for the query.
|
||||
dtStatus raycast(dtPolyRef startRef, const float* startPos, const float* endPos,
|
||||
const dtQueryFilter* filter,
|
||||
float* t, float* hitNormal, dtPolyRef* path, int* pathCount, const int maxPath) const;
|
||||
|
||||
/// Casts a 'walkability' ray along the surface of the navigation mesh from
|
||||
/// the start position toward the end position.
|
||||
/// @param[in] startRef The reference id of the start polygon.
|
||||
/// @param[in] startPos A position within the start polygon representing
|
||||
/// the start of the ray. [(x, y, z)]
|
||||
/// @param[in] endPos The position to cast the ray toward. [(x, y, z)]
|
||||
/// @param[in] filter The polygon filter to apply to the query.
|
||||
/// @param[in] options govern how the raycast behaves. See dtRaycastOptions
|
||||
/// @param[out] hit Pointer to a raycast hit structure which will be filled by the results.
|
||||
/// @param[in] prevRef parent of start ref. Used during for cost calculation [opt]
|
||||
/// @returns The status flags for the query.
|
||||
dtStatus raycast(dtPolyRef startRef, const float* startPos, const float* endPos,
|
||||
const dtQueryFilter* filter, const unsigned int options,
|
||||
dtRaycastHit* hit, dtPolyRef prevRef = 0) const;
|
||||
|
||||
|
||||
/// Finds the distance from the specified position to the nearest polygon wall.
|
||||
/// @param[in] startRef The reference id of the polygon containing @p centerPos.
|
||||
/// @param[in] centerPos The center of the search circle. [(x, y, z)]
|
||||
/// @param[in] maxRadius The radius of the search circle.
|
||||
/// @param[in] filter The polygon filter to apply to the query.
|
||||
/// @param[out] hitDist The distance to the nearest wall from @p centerPos.
|
||||
/// @param[out] hitPos The nearest position on the wall that was hit. [(x, y, z)]
|
||||
/// @param[out] hitNormal The normalized ray formed from the wall point to the
|
||||
/// source point. [(x, y, z)]
|
||||
/// @returns The status flags for the query.
|
||||
dtStatus findDistanceToWall(dtPolyRef startRef, const float* centerPos, const float maxRadius,
|
||||
const dtQueryFilter* filter,
|
||||
float* hitDist, float* hitPos, float* hitNormal) const;
|
||||
|
||||
/// Returns the segments for the specified polygon, optionally including portals.
|
||||
/// @param[in] ref The reference id of the polygon.
|
||||
/// @param[in] filter The polygon filter to apply to the query.
|
||||
/// @param[out] segmentVerts The segments. [(ax, ay, az, bx, by, bz) * segmentCount]
|
||||
/// @param[out] segmentRefs The reference ids of each segment's neighbor polygon.
|
||||
/// Or zero if the segment is a wall. [opt] [(parentRef) * @p segmentCount]
|
||||
/// @param[out] segmentCount The number of segments returned.
|
||||
/// @param[in] maxSegments The maximum number of segments the result arrays can hold.
|
||||
/// @returns The status flags for the query.
|
||||
dtStatus getPolyWallSegments(dtPolyRef ref, const dtQueryFilter* filter,
|
||||
float* segmentVerts, dtPolyRef* segmentRefs, int* segmentCount,
|
||||
const int maxSegments) const;
|
||||
|
||||
/// Returns random location on navmesh.
|
||||
/// Polygons are chosen weighted by area. The search runs in linear related to number of polygon.
|
||||
/// @param[in] filter The polygon filter to apply to the query.
|
||||
/// @param[in] frand Function returning a random number [0..1).
|
||||
/// @param[out] randomRef The reference id of the random location.
|
||||
/// @param[out] randomPt The random location.
|
||||
/// @returns The status flags for the query.
|
||||
dtStatus findRandomPoint(const dtQueryFilter* filter, float (*frand)(),
|
||||
dtPolyRef* randomRef, float* randomPt) const;
|
||||
|
||||
/// Returns random location on navmesh within the reach of specified location.
|
||||
/// Polygons are chosen weighted by area. The search runs in linear related to number of polygon.
|
||||
/// The location is not exactly constrained by the circle, but it limits the visited polygons.
|
||||
/// @param[in] startRef The reference id of the polygon where the search starts.
|
||||
/// @param[in] centerPos The center of the search circle. [(x, y, z)]
|
||||
/// @param[in] maxRadius The radius of the search circle. [Units: wu]
|
||||
/// @param[in] filter The polygon filter to apply to the query.
|
||||
/// @param[in] frand Function returning a random number [0..1).
|
||||
/// @param[out] randomRef The reference id of the random location.
|
||||
/// @param[out] randomPt The random location. [(x, y, z)]
|
||||
/// @returns The status flags for the query.
|
||||
dtStatus findRandomPointAroundCircle(dtPolyRef startRef, const float* centerPos, const float maxRadius,
|
||||
const dtQueryFilter* filter, float (*frand)(),
|
||||
dtPolyRef* randomRef, float* randomPt) const;
|
||||
|
||||
/// Finds the closest point on the specified polygon.
|
||||
/// @param[in] ref The reference id of the polygon.
|
||||
/// @param[in] pos The position to check. [(x, y, z)]
|
||||
/// @param[out] closest The closest point on the polygon. [(x, y, z)]
|
||||
/// @param[out] posOverPoly True of the position is over the polygon.
|
||||
/// @returns The status flags for the query.
|
||||
dtStatus closestPointOnPoly(dtPolyRef ref, const float* pos, float* closest, bool* posOverPoly) const;
|
||||
|
||||
/// Returns a point on the boundary closest to the source point if the source point is outside the
|
||||
/// polygon's xz-bounds.
|
||||
/// @param[in] ref The reference id to the polygon.
|
||||
/// @param[in] pos The position to check. [(x, y, z)]
|
||||
/// @param[out] closest The closest point. [(x, y, z)]
|
||||
/// @returns The status flags for the query.
|
||||
dtStatus closestPointOnPolyBoundary(dtPolyRef ref, const float* pos, float* closest) const;
|
||||
|
||||
/// Gets the height of the polygon at the provided position using the height detail. (Most accurate.)
|
||||
/// @param[in] ref The reference id of the polygon.
|
||||
/// @param[in] pos A position within the xz-bounds of the polygon. [(x, y, z)]
|
||||
/// @param[out] height The height at the surface of the polygon.
|
||||
/// @returns The status flags for the query.
|
||||
dtStatus getPolyHeight(dtPolyRef ref, const float* pos, float* height) const;
|
||||
|
||||
/// @}
|
||||
/// @name Miscellaneous Functions
|
||||
/// @{
|
||||
|
||||
/// Returns true if the polygon reference is valid and passes the filter restrictions.
|
||||
/// @param[in] ref The polygon reference to check.
|
||||
/// @param[in] filter The filter to apply.
|
||||
bool isValidPolyRef(dtPolyRef ref, const dtQueryFilter* filter) const;
|
||||
|
||||
/// Returns true if the polygon reference is in the closed list.
|
||||
/// @param[in] ref The reference id of the polygon to check.
|
||||
/// @returns True if the polygon is in closed list.
|
||||
bool isInClosedList(dtPolyRef ref) const;
|
||||
|
||||
/// Gets the node pool.
|
||||
/// @returns The node pool.
|
||||
class dtNodePool* getNodePool() const { return m_nodePool; }
|
||||
|
||||
/// Gets the navigation mesh the query object is using.
|
||||
/// @return The navigation mesh the query object is using.
|
||||
const dtNavMesh* getAttachedNavMesh() const { return m_nav; }
|
||||
|
||||
/// @}
|
||||
|
||||
private:
|
||||
// Explicitly disabled copy constructor and copy assignment operator
|
||||
dtNavMeshQuery(const dtNavMeshQuery&);
|
||||
dtNavMeshQuery& operator=(const dtNavMeshQuery&);
|
||||
|
||||
/// Queries polygons within a tile.
|
||||
void queryPolygonsInTile(const dtMeshTile* tile, const float* qmin, const float* qmax,
|
||||
const dtQueryFilter* filter, dtPolyQuery* query) const;
|
||||
|
||||
/// Returns portal points between two polygons.
|
||||
dtStatus getPortalPoints(dtPolyRef from, dtPolyRef to, float* left, float* right,
|
||||
unsigned char& fromType, unsigned char& toType) const;
|
||||
dtStatus getPortalPoints(dtPolyRef from, const dtPoly* fromPoly, const dtMeshTile* fromTile,
|
||||
dtPolyRef to, const dtPoly* toPoly, const dtMeshTile* toTile,
|
||||
float* left, float* right) const;
|
||||
|
||||
/// Returns edge mid point between two polygons.
|
||||
dtStatus getEdgeMidPoint(dtPolyRef from, dtPolyRef to, float* mid) const;
|
||||
dtStatus getEdgeMidPoint(dtPolyRef from, const dtPoly* fromPoly, const dtMeshTile* fromTile,
|
||||
dtPolyRef to, const dtPoly* toPoly, const dtMeshTile* toTile,
|
||||
float* mid) const;
|
||||
|
||||
// Appends vertex to a straight path
|
||||
dtStatus appendVertex(const float* pos, const unsigned char flags, const dtPolyRef ref,
|
||||
float* straightPath, unsigned char* straightPathFlags, dtPolyRef* straightPathRefs,
|
||||
int* straightPathCount, const int maxStraightPath) const;
|
||||
|
||||
// Appends intermediate portal points to a straight path.
|
||||
dtStatus appendPortals(const int startIdx, const int endIdx, const float* endPos, const dtPolyRef* path,
|
||||
float* straightPath, unsigned char* straightPathFlags, dtPolyRef* straightPathRefs,
|
||||
int* straightPathCount, const int maxStraightPath, const int options) const;
|
||||
|
||||
// Gets the path leading to the specified end node.
|
||||
dtStatus getPathToNode(struct dtNode* endNode, dtPolyRef* path, int* pathCount, int maxPath) const;
|
||||
|
||||
const dtNavMesh* m_nav; ///< Pointer to navmesh data.
|
||||
|
||||
struct dtQueryData
|
||||
{
|
||||
dtStatus status;
|
||||
struct dtNode* lastBestNode;
|
||||
float lastBestNodeCost;
|
||||
dtPolyRef startRef, endRef;
|
||||
float startPos[3], endPos[3];
|
||||
const dtQueryFilter* filter;
|
||||
unsigned int options;
|
||||
float raycastLimitSqr;
|
||||
};
|
||||
dtQueryData m_query; ///< Sliced query state.
|
||||
|
||||
class dtNodePool* m_tinyNodePool; ///< Pointer to small node pool.
|
||||
class dtNodePool* m_nodePool; ///< Pointer to node pool.
|
||||
class dtNodeQueue* m_openList; ///< Pointer to open list queue.
|
||||
};
|
||||
|
||||
/// Allocates a query object using the Detour allocator.
|
||||
/// @return An allocated query object, or null on failure.
|
||||
/// @ingroup detour
|
||||
dtNavMeshQuery* dtAllocNavMeshQuery();
|
||||
|
||||
/// Frees the specified query object using the Detour allocator.
|
||||
/// @param[in] query A query object allocated using #dtAllocNavMeshQuery
|
||||
/// @ingroup detour
|
||||
void dtFreeNavMeshQuery(dtNavMeshQuery* query);
|
||||
|
||||
#endif // DETOURNAVMESHQUERY_H
|
||||
@@ -0,0 +1,168 @@
|
||||
//
|
||||
// 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 DETOURNODE_H
|
||||
#define DETOURNODE_H
|
||||
|
||||
#include "DetourNavMesh.h"
|
||||
|
||||
enum dtNodeFlags
|
||||
{
|
||||
DT_NODE_OPEN = 0x01,
|
||||
DT_NODE_CLOSED = 0x02,
|
||||
DT_NODE_PARENT_DETACHED = 0x04 // parent of the node is not adjacent. Found using raycast.
|
||||
};
|
||||
|
||||
typedef unsigned short dtNodeIndex;
|
||||
static const dtNodeIndex DT_NULL_IDX = (dtNodeIndex)~0;
|
||||
|
||||
static const int DT_NODE_PARENT_BITS = 24;
|
||||
static const int DT_NODE_STATE_BITS = 2;
|
||||
struct dtNode
|
||||
{
|
||||
float pos[3]; ///< Position of the node.
|
||||
float cost; ///< Cost from previous node to current node.
|
||||
float total; ///< Cost up to the node.
|
||||
unsigned int pidx : DT_NODE_PARENT_BITS; ///< Index to parent node.
|
||||
unsigned int state : DT_NODE_STATE_BITS; ///< extra state information. A polyRef can have multiple nodes with different extra info. see DT_MAX_STATES_PER_NODE
|
||||
unsigned int flags : 3; ///< Node flags. A combination of dtNodeFlags.
|
||||
dtPolyRef id; ///< Polygon ref the node corresponds to.
|
||||
};
|
||||
|
||||
static const int DT_MAX_STATES_PER_NODE = 1 << DT_NODE_STATE_BITS; // number of extra states per node. See dtNode::state
|
||||
|
||||
class dtNodePool
|
||||
{
|
||||
public:
|
||||
dtNodePool(int maxNodes, int hashSize);
|
||||
~dtNodePool();
|
||||
void clear();
|
||||
|
||||
// Get a dtNode by ref and extra state information. If there is none then - allocate
|
||||
// There can be more than one node for the same polyRef but with different extra state information
|
||||
dtNode* getNode(dtPolyRef id, unsigned char state=0);
|
||||
dtNode* findNode(dtPolyRef id, unsigned char state);
|
||||
unsigned int findNodes(dtPolyRef id, dtNode** nodes, const int maxNodes);
|
||||
|
||||
inline unsigned int getNodeIdx(const dtNode* node) const
|
||||
{
|
||||
if (!node) return 0;
|
||||
return (unsigned int)(node - m_nodes) + 1;
|
||||
}
|
||||
|
||||
inline dtNode* getNodeAtIdx(unsigned int idx)
|
||||
{
|
||||
if (!idx) return 0;
|
||||
return &m_nodes[idx - 1];
|
||||
}
|
||||
|
||||
inline const dtNode* getNodeAtIdx(unsigned int idx) const
|
||||
{
|
||||
if (!idx) return 0;
|
||||
return &m_nodes[idx - 1];
|
||||
}
|
||||
|
||||
inline int getMemUsed() const
|
||||
{
|
||||
return sizeof(*this) +
|
||||
sizeof(dtNode)*m_maxNodes +
|
||||
sizeof(dtNodeIndex)*m_maxNodes +
|
||||
sizeof(dtNodeIndex)*m_hashSize;
|
||||
}
|
||||
|
||||
inline int getMaxNodes() const { return m_maxNodes; }
|
||||
|
||||
inline int getHashSize() const { return m_hashSize; }
|
||||
inline dtNodeIndex getFirst(int bucket) const { return m_first[bucket]; }
|
||||
inline dtNodeIndex getNext(int i) const { return m_next[i]; }
|
||||
inline int getNodeCount() const { return m_nodeCount; }
|
||||
|
||||
private:
|
||||
// Explicitly disabled copy constructor and copy assignment operator.
|
||||
dtNodePool(const dtNodePool&);
|
||||
dtNodePool& operator=(const dtNodePool&);
|
||||
|
||||
dtNode* m_nodes;
|
||||
dtNodeIndex* m_first;
|
||||
dtNodeIndex* m_next;
|
||||
const int m_maxNodes;
|
||||
const int m_hashSize;
|
||||
int m_nodeCount;
|
||||
};
|
||||
|
||||
class dtNodeQueue
|
||||
{
|
||||
public:
|
||||
dtNodeQueue(int n);
|
||||
~dtNodeQueue();
|
||||
|
||||
inline void clear() { m_size = 0; }
|
||||
|
||||
inline dtNode* top() { return m_heap[0]; }
|
||||
|
||||
inline dtNode* pop()
|
||||
{
|
||||
dtNode* result = m_heap[0];
|
||||
m_size--;
|
||||
trickleDown(0, m_heap[m_size]);
|
||||
return result;
|
||||
}
|
||||
|
||||
inline void push(dtNode* node)
|
||||
{
|
||||
m_size++;
|
||||
bubbleUp(m_size-1, node);
|
||||
}
|
||||
|
||||
inline void modify(dtNode* node)
|
||||
{
|
||||
for (int i = 0; i < m_size; ++i)
|
||||
{
|
||||
if (m_heap[i] == node)
|
||||
{
|
||||
bubbleUp(i, node);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline bool empty() const { return m_size == 0; }
|
||||
|
||||
inline int getMemUsed() const
|
||||
{
|
||||
return sizeof(*this) +
|
||||
sizeof(dtNode*) * (m_capacity + 1);
|
||||
}
|
||||
|
||||
inline int getCapacity() const { return m_capacity; }
|
||||
|
||||
private:
|
||||
// Explicitly disabled copy constructor and copy assignment operator.
|
||||
dtNodeQueue(const dtNodeQueue&);
|
||||
dtNodeQueue& operator=(const dtNodeQueue&);
|
||||
|
||||
void bubbleUp(int i, dtNode* node);
|
||||
void trickleDown(int i, dtNode* node);
|
||||
|
||||
dtNode** m_heap;
|
||||
const int m_capacity;
|
||||
int m_size;
|
||||
};
|
||||
|
||||
|
||||
#endif // DETOURNODE_H
|
||||
@@ -0,0 +1,65 @@
|
||||
//
|
||||
// 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 DETOURSTATUS_H
|
||||
#define DETOURSTATUS_H
|
||||
|
||||
typedef unsigned int dtStatus;
|
||||
|
||||
// High level status.
|
||||
static const unsigned int DT_FAILURE = 1u << 31; // Operation failed.
|
||||
static const unsigned int DT_SUCCESS = 1u << 30; // Operation succeed.
|
||||
static const unsigned int DT_IN_PROGRESS = 1u << 29; // Operation still in progress.
|
||||
|
||||
// Detail information for status.
|
||||
static const unsigned int DT_STATUS_DETAIL_MASK = 0x0ffffff;
|
||||
static const unsigned int DT_WRONG_MAGIC = 1 << 0; // Input data is not recognized.
|
||||
static const unsigned int DT_WRONG_VERSION = 1 << 1; // Input data is in wrong version.
|
||||
static const unsigned int DT_OUT_OF_MEMORY = 1 << 2; // Operation ran out of memory.
|
||||
static const unsigned int DT_INVALID_PARAM = 1 << 3; // An input parameter was invalid.
|
||||
static const unsigned int DT_BUFFER_TOO_SMALL = 1 << 4; // Result buffer for the query was too small to store all results.
|
||||
static const unsigned int DT_OUT_OF_NODES = 1 << 5; // Query ran out of nodes during search.
|
||||
static const unsigned int DT_PARTIAL_RESULT = 1 << 6; // Query did not reach the end location, returning best guess.
|
||||
static const unsigned int DT_ALREADY_OCCUPIED = 1 << 7; // A tile has already been assigned to the given x,y coordinate
|
||||
|
||||
|
||||
// Returns true of status is success.
|
||||
inline bool dtStatusSucceed(dtStatus status)
|
||||
{
|
||||
return (status & DT_SUCCESS) != 0;
|
||||
}
|
||||
|
||||
// Returns true of status is failure.
|
||||
inline bool dtStatusFailed(dtStatus status)
|
||||
{
|
||||
return (status & DT_FAILURE) != 0;
|
||||
}
|
||||
|
||||
// Returns true of status is in progress.
|
||||
inline bool dtStatusInProgress(dtStatus status)
|
||||
{
|
||||
return (status & DT_IN_PROGRESS) != 0;
|
||||
}
|
||||
|
||||
// Returns true if specific detail is set.
|
||||
inline bool dtStatusDetail(dtStatus status, unsigned int detail)
|
||||
{
|
||||
return (status & detail) != 0;
|
||||
}
|
||||
|
||||
#endif // DETOURSTATUS_H
|
||||
@@ -0,0 +1,50 @@
|
||||
//
|
||||
// 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 <stdlib.h>
|
||||
#include "DetourAlloc.h"
|
||||
|
||||
static void *dtAllocDefault(size_t size, dtAllocHint)
|
||||
{
|
||||
return malloc(size);
|
||||
}
|
||||
|
||||
static void dtFreeDefault(void *ptr)
|
||||
{
|
||||
free(ptr);
|
||||
}
|
||||
|
||||
static dtAllocFunc* sAllocFunc = dtAllocDefault;
|
||||
static dtFreeFunc* sFreeFunc = dtFreeDefault;
|
||||
|
||||
void dtAllocSetCustom(dtAllocFunc *allocFunc, dtFreeFunc *freeFunc)
|
||||
{
|
||||
sAllocFunc = allocFunc ? allocFunc : dtAllocDefault;
|
||||
sFreeFunc = freeFunc ? freeFunc : dtFreeDefault;
|
||||
}
|
||||
|
||||
void* dtAlloc(size_t size, dtAllocHint hint)
|
||||
{
|
||||
return sAllocFunc(size, hint);
|
||||
}
|
||||
|
||||
void dtFree(void* ptr)
|
||||
{
|
||||
if (ptr)
|
||||
sFreeFunc(ptr);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// 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 "DetourAssert.h"
|
||||
|
||||
#ifndef RC_DISABLE_ASSERTS
|
||||
|
||||
static dtAssertFailFunc* sAssertFailFunc = 0;
|
||||
|
||||
void dtAssertFailSetCustom(dtAssertFailFunc *assertFailFunc)
|
||||
{
|
||||
sAssertFailFunc = assertFailFunc;
|
||||
}
|
||||
|
||||
dtAssertFailFunc* dtAssertFailGetCustom()
|
||||
{
|
||||
return sAssertFailFunc;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,387 @@
|
||||
//
|
||||
// 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 "DetourCommon.h"
|
||||
#include "DetourMath.h"
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void dtClosestPtPointTriangle(float* closest, const float* p,
|
||||
const float* a, const float* b, const float* c)
|
||||
{
|
||||
// Check if P in vertex region outside A
|
||||
float ab[3], ac[3], ap[3];
|
||||
dtVsub(ab, b, a);
|
||||
dtVsub(ac, c, a);
|
||||
dtVsub(ap, p, a);
|
||||
float d1 = dtVdot(ab, ap);
|
||||
float d2 = dtVdot(ac, ap);
|
||||
if (d1 <= 0.0f && d2 <= 0.0f)
|
||||
{
|
||||
// barycentric coordinates (1,0,0)
|
||||
dtVcopy(closest, a);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if P in vertex region outside B
|
||||
float bp[3];
|
||||
dtVsub(bp, p, b);
|
||||
float d3 = dtVdot(ab, bp);
|
||||
float d4 = dtVdot(ac, bp);
|
||||
if (d3 >= 0.0f && d4 <= d3)
|
||||
{
|
||||
// barycentric coordinates (0,1,0)
|
||||
dtVcopy(closest, b);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if P in edge region of AB, if so return projection of P onto AB
|
||||
float vc = d1*d4 - d3*d2;
|
||||
if (vc <= 0.0f && d1 >= 0.0f && d3 <= 0.0f)
|
||||
{
|
||||
// barycentric coordinates (1-v,v,0)
|
||||
float v = d1 / (d1 - d3);
|
||||
closest[0] = a[0] + v * ab[0];
|
||||
closest[1] = a[1] + v * ab[1];
|
||||
closest[2] = a[2] + v * ab[2];
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if P in vertex region outside C
|
||||
float cp[3];
|
||||
dtVsub(cp, p, c);
|
||||
float d5 = dtVdot(ab, cp);
|
||||
float d6 = dtVdot(ac, cp);
|
||||
if (d6 >= 0.0f && d5 <= d6)
|
||||
{
|
||||
// barycentric coordinates (0,0,1)
|
||||
dtVcopy(closest, c);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if P in edge region of AC, if so return projection of P onto AC
|
||||
float vb = d5*d2 - d1*d6;
|
||||
if (vb <= 0.0f && d2 >= 0.0f && d6 <= 0.0f)
|
||||
{
|
||||
// barycentric coordinates (1-w,0,w)
|
||||
float w = d2 / (d2 - d6);
|
||||
closest[0] = a[0] + w * ac[0];
|
||||
closest[1] = a[1] + w * ac[1];
|
||||
closest[2] = a[2] + w * ac[2];
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if P in edge region of BC, if so return projection of P onto BC
|
||||
float va = d3*d6 - d5*d4;
|
||||
if (va <= 0.0f && (d4 - d3) >= 0.0f && (d5 - d6) >= 0.0f)
|
||||
{
|
||||
// barycentric coordinates (0,1-w,w)
|
||||
float w = (d4 - d3) / ((d4 - d3) + (d5 - d6));
|
||||
closest[0] = b[0] + w * (c[0] - b[0]);
|
||||
closest[1] = b[1] + w * (c[1] - b[1]);
|
||||
closest[2] = b[2] + w * (c[2] - b[2]);
|
||||
return;
|
||||
}
|
||||
|
||||
// P inside face region. Compute Q through its barycentric coordinates (u,v,w)
|
||||
float denom = 1.0f / (va + vb + vc);
|
||||
float v = vb * denom;
|
||||
float w = vc * denom;
|
||||
closest[0] = a[0] + ab[0] * v + ac[0] * w;
|
||||
closest[1] = a[1] + ab[1] * v + ac[1] * w;
|
||||
closest[2] = a[2] + ab[2] * v + ac[2] * w;
|
||||
}
|
||||
|
||||
bool dtIntersectSegmentPoly2D(const float* p0, const float* p1,
|
||||
const float* verts, int nverts,
|
||||
float& tmin, float& tmax,
|
||||
int& segMin, int& segMax)
|
||||
{
|
||||
static const float EPS = 0.000001f;
|
||||
|
||||
tmin = 0;
|
||||
tmax = 1;
|
||||
segMin = -1;
|
||||
segMax = -1;
|
||||
|
||||
float dir[3];
|
||||
dtVsub(dir, p1, p0);
|
||||
|
||||
for (int i = 0, j = nverts-1; i < nverts; j=i++)
|
||||
{
|
||||
float edge[3], diff[3];
|
||||
dtVsub(edge, &verts[i*3], &verts[j*3]);
|
||||
dtVsub(diff, p0, &verts[j*3]);
|
||||
const float n = dtVperp2D(edge, diff);
|
||||
const float d = dtVperp2D(dir, edge);
|
||||
if (fabsf(d) < EPS)
|
||||
{
|
||||
// S is nearly parallel to this edge
|
||||
if (n < 0)
|
||||
return false;
|
||||
else
|
||||
continue;
|
||||
}
|
||||
const float t = n / d;
|
||||
if (d < 0)
|
||||
{
|
||||
// segment S is entering across this edge
|
||||
if (t > tmin)
|
||||
{
|
||||
tmin = t;
|
||||
segMin = j;
|
||||
// S enters after leaving polygon
|
||||
if (tmin > tmax)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// segment S is leaving across this edge
|
||||
if (t < tmax)
|
||||
{
|
||||
tmax = t;
|
||||
segMax = j;
|
||||
// S leaves before entering polygon
|
||||
if (tmax < tmin)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
float dtDistancePtSegSqr2D(const float* pt, const float* p, const float* q, float& t)
|
||||
{
|
||||
float pqx = q[0] - p[0];
|
||||
float pqz = q[2] - p[2];
|
||||
float dx = pt[0] - p[0];
|
||||
float dz = pt[2] - p[2];
|
||||
float d = pqx*pqx + pqz*pqz;
|
||||
t = pqx*dx + pqz*dz;
|
||||
if (d > 0) t /= d;
|
||||
if (t < 0) t = 0;
|
||||
else if (t > 1) t = 1;
|
||||
dx = p[0] + t*pqx - pt[0];
|
||||
dz = p[2] + t*pqz - pt[2];
|
||||
return dx*dx + dz*dz;
|
||||
}
|
||||
|
||||
void dtCalcPolyCenter(float* tc, const unsigned short* idx, int nidx, const float* verts)
|
||||
{
|
||||
tc[0] = 0.0f;
|
||||
tc[1] = 0.0f;
|
||||
tc[2] = 0.0f;
|
||||
for (int j = 0; j < nidx; ++j)
|
||||
{
|
||||
const float* v = &verts[idx[j]*3];
|
||||
tc[0] += v[0];
|
||||
tc[1] += v[1];
|
||||
tc[2] += v[2];
|
||||
}
|
||||
const float s = 1.0f / nidx;
|
||||
tc[0] *= s;
|
||||
tc[1] *= s;
|
||||
tc[2] *= s;
|
||||
}
|
||||
|
||||
bool dtClosestHeightPointTriangle(const float* p, const float* a, const float* b, const float* c, float& h)
|
||||
{
|
||||
const float EPS = 1e-6f;
|
||||
float v0[3], v1[3], v2[3];
|
||||
|
||||
dtVsub(v0, c, a);
|
||||
dtVsub(v1, b, a);
|
||||
dtVsub(v2, p, a);
|
||||
|
||||
// Compute scaled barycentric coordinates
|
||||
float denom = v0[0] * v1[2] - v0[2] * v1[0];
|
||||
if (fabsf(denom) < EPS)
|
||||
return false;
|
||||
|
||||
float u = v1[2] * v2[0] - v1[0] * v2[2];
|
||||
float v = v0[0] * v2[2] - v0[2] * v2[0];
|
||||
|
||||
if (denom < 0) {
|
||||
denom = -denom;
|
||||
u = -u;
|
||||
v = -v;
|
||||
}
|
||||
|
||||
// If point lies inside the triangle, return interpolated ycoord.
|
||||
if (u >= 0.0f && v >= 0.0f && (u + v) <= denom) {
|
||||
h = a[1] + (v0[1] * u + v1[1] * v) / denom;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// @par
|
||||
///
|
||||
/// All points are projected onto the xz-plane, so the y-values are ignored.
|
||||
bool dtPointInPolygon(const float* pt, const float* verts, const int nverts)
|
||||
{
|
||||
// TODO: Replace pnpoly with triArea2D tests?
|
||||
int i, j;
|
||||
bool c = false;
|
||||
for (i = 0, j = nverts-1; i < nverts; j = i++)
|
||||
{
|
||||
const float* vi = &verts[i*3];
|
||||
const float* vj = &verts[j*3];
|
||||
if (((vi[2] > pt[2]) != (vj[2] > pt[2])) &&
|
||||
(pt[0] < (vj[0]-vi[0]) * (pt[2]-vi[2]) / (vj[2]-vi[2]) + vi[0]) )
|
||||
c = !c;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
bool dtDistancePtPolyEdgesSqr(const float* pt, const float* verts, const int nverts,
|
||||
float* ed, float* et)
|
||||
{
|
||||
// TODO: Replace pnpoly with triArea2D tests?
|
||||
int i, j;
|
||||
bool c = false;
|
||||
for (i = 0, j = nverts-1; i < nverts; j = i++)
|
||||
{
|
||||
const float* vi = &verts[i*3];
|
||||
const float* vj = &verts[j*3];
|
||||
if (((vi[2] > pt[2]) != (vj[2] > pt[2])) &&
|
||||
(pt[0] < (vj[0]-vi[0]) * (pt[2]-vi[2]) / (vj[2]-vi[2]) + vi[0]) )
|
||||
c = !c;
|
||||
ed[j] = dtDistancePtSegSqr2D(pt, vj, vi, et[j]);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
static void projectPoly(const float* axis, const float* poly, const int npoly,
|
||||
float& rmin, float& rmax)
|
||||
{
|
||||
rmin = rmax = dtVdot2D(axis, &poly[0]);
|
||||
for (int i = 1; i < npoly; ++i)
|
||||
{
|
||||
const float d = dtVdot2D(axis, &poly[i*3]);
|
||||
rmin = dtMin(rmin, d);
|
||||
rmax = dtMax(rmax, d);
|
||||
}
|
||||
}
|
||||
|
||||
inline bool overlapRange(const float amin, const float amax,
|
||||
const float bmin, const float bmax,
|
||||
const float eps)
|
||||
{
|
||||
return ((amin+eps) > bmax || (amax-eps) < bmin) ? false : true;
|
||||
}
|
||||
|
||||
/// @par
|
||||
///
|
||||
/// All vertices are projected onto the xz-plane, so the y-values are ignored.
|
||||
bool dtOverlapPolyPoly2D(const float* polya, const int npolya,
|
||||
const float* polyb, const int npolyb)
|
||||
{
|
||||
const float eps = 1e-4f;
|
||||
|
||||
for (int i = 0, j = npolya-1; i < npolya; j=i++)
|
||||
{
|
||||
const float* va = &polya[j*3];
|
||||
const float* vb = &polya[i*3];
|
||||
const float n[3] = { vb[2]-va[2], 0, -(vb[0]-va[0]) };
|
||||
float amin,amax,bmin,bmax;
|
||||
projectPoly(n, polya, npolya, amin,amax);
|
||||
projectPoly(n, polyb, npolyb, bmin,bmax);
|
||||
if (!overlapRange(amin,amax, bmin,bmax, eps))
|
||||
{
|
||||
// Found separating axis
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (int i = 0, j = npolyb-1; i < npolyb; j=i++)
|
||||
{
|
||||
const float* va = &polyb[j*3];
|
||||
const float* vb = &polyb[i*3];
|
||||
const float n[3] = { vb[2]-va[2], 0, -(vb[0]-va[0]) };
|
||||
float amin,amax,bmin,bmax;
|
||||
projectPoly(n, polya, npolya, amin,amax);
|
||||
projectPoly(n, polyb, npolyb, bmin,bmax);
|
||||
if (!overlapRange(amin,amax, bmin,bmax, eps))
|
||||
{
|
||||
// Found separating axis
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Returns a random point in a convex polygon.
|
||||
// Adapted from Graphics Gems article.
|
||||
void dtRandomPointInConvexPoly(const float* pts, const int npts, float* areas,
|
||||
const float s, const float t, float* out)
|
||||
{
|
||||
// Calc triangle araes
|
||||
float areasum = 0.0f;
|
||||
for (int i = 2; i < npts; i++) {
|
||||
areas[i] = dtTriArea2D(&pts[0], &pts[(i-1)*3], &pts[i*3]);
|
||||
areasum += dtMax(0.001f, areas[i]);
|
||||
}
|
||||
// Find sub triangle weighted by area.
|
||||
const float thr = s*areasum;
|
||||
float acc = 0.0f;
|
||||
float u = 1.0f;
|
||||
int tri = npts - 1;
|
||||
for (int i = 2; i < npts; i++) {
|
||||
const float dacc = areas[i];
|
||||
if (thr >= acc && thr < (acc+dacc))
|
||||
{
|
||||
u = (thr - acc) / dacc;
|
||||
tri = i;
|
||||
break;
|
||||
}
|
||||
acc += dacc;
|
||||
}
|
||||
|
||||
float v = dtMathSqrtf(t);
|
||||
|
||||
const float a = 1 - v;
|
||||
const float b = (1 - u) * v;
|
||||
const float c = u * v;
|
||||
const float* pa = &pts[0];
|
||||
const float* pb = &pts[(tri-1)*3];
|
||||
const float* pc = &pts[tri*3];
|
||||
|
||||
out[0] = a*pa[0] + b*pb[0] + c*pc[0];
|
||||
out[1] = a*pa[1] + b*pb[1] + c*pc[1];
|
||||
out[2] = a*pa[2] + b*pb[2] + c*pc[2];
|
||||
}
|
||||
|
||||
inline float vperpXZ(const float* a, const float* b) { return a[0]*b[2] - a[2]*b[0]; }
|
||||
|
||||
bool dtIntersectSegSeg2D(const float* ap, const float* aq,
|
||||
const float* bp, const float* bq,
|
||||
float& s, float& t)
|
||||
{
|
||||
float u[3], v[3], w[3];
|
||||
dtVsub(u,aq,ap);
|
||||
dtVsub(v,bq,bp);
|
||||
dtVsub(w,ap,bp);
|
||||
float d = vperpXZ(u,v);
|
||||
if (fabsf(d) < 1e-6f) return false;
|
||||
s = vperpXZ(v,w) / d;
|
||||
t = vperpXZ(u,w) / d;
|
||||
return true;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,802 @@
|
||||
//
|
||||
// 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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <float.h>
|
||||
#include "DetourNavMesh.h"
|
||||
#include "DetourCommon.h"
|
||||
#include "DetourMath.h"
|
||||
#include "DetourNavMeshBuilder.h"
|
||||
#include "DetourAlloc.h"
|
||||
#include "DetourAssert.h"
|
||||
|
||||
static unsigned short MESH_NULL_IDX = 0xffff;
|
||||
|
||||
|
||||
struct BVItem
|
||||
{
|
||||
unsigned short bmin[3];
|
||||
unsigned short bmax[3];
|
||||
int i;
|
||||
};
|
||||
|
||||
static int compareItemX(const void* va, const void* vb)
|
||||
{
|
||||
const BVItem* a = (const BVItem*)va;
|
||||
const BVItem* b = (const BVItem*)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 BVItem* a = (const BVItem*)va;
|
||||
const BVItem* b = (const BVItem*)vb;
|
||||
if (a->bmin[1] < b->bmin[1])
|
||||
return -1;
|
||||
if (a->bmin[1] > b->bmin[1])
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int compareItemZ(const void* va, const void* vb)
|
||||
{
|
||||
const BVItem* a = (const BVItem*)va;
|
||||
const BVItem* b = (const BVItem*)vb;
|
||||
if (a->bmin[2] < b->bmin[2])
|
||||
return -1;
|
||||
if (a->bmin[2] > b->bmin[2])
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void calcExtends(BVItem* items, const int /*nitems*/, const int imin, const int imax,
|
||||
unsigned short* bmin, unsigned short* bmax)
|
||||
{
|
||||
bmin[0] = items[imin].bmin[0];
|
||||
bmin[1] = items[imin].bmin[1];
|
||||
bmin[2] = items[imin].bmin[2];
|
||||
|
||||
bmax[0] = items[imin].bmax[0];
|
||||
bmax[1] = items[imin].bmax[1];
|
||||
bmax[2] = items[imin].bmax[2];
|
||||
|
||||
for (int i = imin+1; i < imax; ++i)
|
||||
{
|
||||
const BVItem& 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.bmin[2] < bmin[2]) bmin[2] = it.bmin[2];
|
||||
|
||||
if (it.bmax[0] > bmax[0]) bmax[0] = it.bmax[0];
|
||||
if (it.bmax[1] > bmax[1]) bmax[1] = it.bmax[1];
|
||||
if (it.bmax[2] > bmax[2]) bmax[2] = it.bmax[2];
|
||||
}
|
||||
}
|
||||
|
||||
inline int longestAxis(unsigned short x, unsigned short y, unsigned short z)
|
||||
{
|
||||
int axis = 0;
|
||||
unsigned short maxVal = x;
|
||||
if (y > maxVal)
|
||||
{
|
||||
axis = 1;
|
||||
maxVal = y;
|
||||
}
|
||||
if (z > maxVal)
|
||||
{
|
||||
axis = 2;
|
||||
}
|
||||
return axis;
|
||||
}
|
||||
|
||||
static void subdivide(BVItem* items, int nitems, int imin, int imax, int& curNode, dtBVNode* nodes)
|
||||
{
|
||||
int inum = imax - imin;
|
||||
int icur = curNode;
|
||||
|
||||
dtBVNode& node = nodes[curNode++];
|
||||
|
||||
if (inum == 1)
|
||||
{
|
||||
// Leaf
|
||||
node.bmin[0] = items[imin].bmin[0];
|
||||
node.bmin[1] = items[imin].bmin[1];
|
||||
node.bmin[2] = items[imin].bmin[2];
|
||||
|
||||
node.bmax[0] = items[imin].bmax[0];
|
||||
node.bmax[1] = items[imin].bmax[1];
|
||||
node.bmax[2] = items[imin].bmax[2];
|
||||
|
||||
node.i = items[imin].i;
|
||||
}
|
||||
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],
|
||||
node.bmax[2] - node.bmin[2]);
|
||||
|
||||
if (axis == 0)
|
||||
{
|
||||
// Sort along x-axis
|
||||
qsort(items+imin, inum, sizeof(BVItem), compareItemX);
|
||||
}
|
||||
else if (axis == 1)
|
||||
{
|
||||
// Sort along y-axis
|
||||
qsort(items+imin, inum, sizeof(BVItem), compareItemY);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Sort along z-axis
|
||||
qsort(items+imin, inum, sizeof(BVItem), compareItemZ);
|
||||
}
|
||||
|
||||
int isplit = imin+inum/2;
|
||||
|
||||
// Left
|
||||
subdivide(items, nitems, imin, isplit, curNode, nodes);
|
||||
// Right
|
||||
subdivide(items, nitems, isplit, imax, curNode, nodes);
|
||||
|
||||
int iescape = curNode - icur;
|
||||
// Negative index means escape.
|
||||
node.i = -iescape;
|
||||
}
|
||||
}
|
||||
|
||||
static int createBVTree(dtNavMeshCreateParams* params, dtBVNode* nodes, int /*nnodes*/)
|
||||
{
|
||||
// Build tree
|
||||
float quantFactor = 1 / params->cs;
|
||||
BVItem* items = (BVItem*)dtAlloc(sizeof(BVItem)*params->polyCount, DT_ALLOC_TEMP);
|
||||
for (int i = 0; i < params->polyCount; i++)
|
||||
{
|
||||
BVItem& it = items[i];
|
||||
it.i = i;
|
||||
// Calc polygon bounds. Use detail meshes if available.
|
||||
if (params->detailMeshes)
|
||||
{
|
||||
int vb = (int)params->detailMeshes[i*4+0];
|
||||
int ndv = (int)params->detailMeshes[i*4+1];
|
||||
float bmin[3];
|
||||
float bmax[3];
|
||||
|
||||
const float* dv = ¶ms->detailVerts[vb*3];
|
||||
dtVcopy(bmin, dv);
|
||||
dtVcopy(bmax, dv);
|
||||
|
||||
for (int j = 1; j < ndv; j++)
|
||||
{
|
||||
dtVmin(bmin, &dv[j * 3]);
|
||||
dtVmax(bmax, &dv[j * 3]);
|
||||
}
|
||||
|
||||
// BV-tree uses cs for all dimensions
|
||||
it.bmin[0] = (unsigned short)dtClamp((int)((bmin[0] - params->bmin[0])*quantFactor), 0, 0xffff);
|
||||
it.bmin[1] = (unsigned short)dtClamp((int)((bmin[1] - params->bmin[1])*quantFactor), 0, 0xffff);
|
||||
it.bmin[2] = (unsigned short)dtClamp((int)((bmin[2] - params->bmin[2])*quantFactor), 0, 0xffff);
|
||||
|
||||
it.bmax[0] = (unsigned short)dtClamp((int)((bmax[0] - params->bmin[0])*quantFactor), 0, 0xffff);
|
||||
it.bmax[1] = (unsigned short)dtClamp((int)((bmax[1] - params->bmin[1])*quantFactor), 0, 0xffff);
|
||||
it.bmax[2] = (unsigned short)dtClamp((int)((bmax[2] - params->bmin[2])*quantFactor), 0, 0xffff);
|
||||
}
|
||||
else
|
||||
{
|
||||
const unsigned short* p = ¶ms->polys[i*params->nvp * 2];
|
||||
it.bmin[0] = it.bmax[0] = params->verts[p[0] * 3 + 0];
|
||||
it.bmin[1] = it.bmax[1] = params->verts[p[0] * 3 + 1];
|
||||
it.bmin[2] = it.bmax[2] = params->verts[p[0] * 3 + 2];
|
||||
|
||||
for (int j = 1; j < params->nvp; ++j)
|
||||
{
|
||||
if (p[j] == MESH_NULL_IDX) break;
|
||||
unsigned short x = params->verts[p[j] * 3 + 0];
|
||||
unsigned short y = params->verts[p[j] * 3 + 1];
|
||||
unsigned short z = params->verts[p[j] * 3 + 2];
|
||||
|
||||
if (x < it.bmin[0]) it.bmin[0] = x;
|
||||
if (y < it.bmin[1]) it.bmin[1] = y;
|
||||
if (z < it.bmin[2]) it.bmin[2] = z;
|
||||
|
||||
if (x > it.bmax[0]) it.bmax[0] = x;
|
||||
if (y > it.bmax[1]) it.bmax[1] = y;
|
||||
if (z > it.bmax[2]) it.bmax[2] = z;
|
||||
}
|
||||
// Remap y
|
||||
it.bmin[1] = (unsigned short)dtMathFloorf((float)it.bmin[1] * params->ch / params->cs);
|
||||
it.bmax[1] = (unsigned short)dtMathCeilf((float)it.bmax[1] * params->ch / params->cs);
|
||||
}
|
||||
}
|
||||
|
||||
int curNode = 0;
|
||||
subdivide(items, params->polyCount, 0, params->polyCount, curNode, nodes);
|
||||
|
||||
dtFree(items);
|
||||
|
||||
return curNode;
|
||||
}
|
||||
|
||||
static unsigned char classifyOffMeshPoint(const float* pt, const float* bmin, const float* bmax)
|
||||
{
|
||||
static const unsigned char XP = 1<<0;
|
||||
static const unsigned char ZP = 1<<1;
|
||||
static const unsigned char XM = 1<<2;
|
||||
static const unsigned char ZM = 1<<3;
|
||||
|
||||
unsigned char outcode = 0;
|
||||
outcode |= (pt[0] >= bmax[0]) ? XP : 0;
|
||||
outcode |= (pt[2] >= bmax[2]) ? ZP : 0;
|
||||
outcode |= (pt[0] < bmin[0]) ? XM : 0;
|
||||
outcode |= (pt[2] < bmin[2]) ? ZM : 0;
|
||||
|
||||
switch (outcode)
|
||||
{
|
||||
case XP: return 0;
|
||||
case XP|ZP: return 1;
|
||||
case ZP: return 2;
|
||||
case XM|ZP: return 3;
|
||||
case XM: return 4;
|
||||
case XM|ZM: return 5;
|
||||
case ZM: return 6;
|
||||
case XP|ZM: return 7;
|
||||
};
|
||||
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
// TODO: Better error handling.
|
||||
|
||||
/// @par
|
||||
///
|
||||
/// The output data array is allocated using the detour allocator (dtAlloc()). The method
|
||||
/// used to free the memory will be determined by how the tile is added to the navigation
|
||||
/// mesh.
|
||||
///
|
||||
/// @see dtNavMesh, dtNavMesh::addTile()
|
||||
bool dtCreateNavMeshData(dtNavMeshCreateParams* params, unsigned char** outData, int* outDataSize)
|
||||
{
|
||||
if (params->nvp > DT_VERTS_PER_POLYGON)
|
||||
return false;
|
||||
if (params->vertCount >= 0xffff)
|
||||
return false;
|
||||
if (!params->vertCount || !params->verts)
|
||||
return false;
|
||||
if (!params->polyCount || !params->polys)
|
||||
return false;
|
||||
|
||||
const int nvp = params->nvp;
|
||||
|
||||
// Classify off-mesh connection points. We store only the connections
|
||||
// whose start point is inside the tile.
|
||||
unsigned char* offMeshConClass = 0;
|
||||
int storedOffMeshConCount = 0;
|
||||
int offMeshConLinkCount = 0;
|
||||
|
||||
if (params->offMeshConCount > 0)
|
||||
{
|
||||
offMeshConClass = (unsigned char*)dtAlloc(sizeof(unsigned char)*params->offMeshConCount*2, DT_ALLOC_TEMP);
|
||||
if (!offMeshConClass)
|
||||
return false;
|
||||
|
||||
// Find tight heigh bounds, used for culling out off-mesh start locations.
|
||||
float hmin = FLT_MAX;
|
||||
float hmax = -FLT_MAX;
|
||||
|
||||
if (params->detailVerts && params->detailVertsCount)
|
||||
{
|
||||
for (int i = 0; i < params->detailVertsCount; ++i)
|
||||
{
|
||||
const float h = params->detailVerts[i*3+1];
|
||||
hmin = dtMin(hmin,h);
|
||||
hmax = dtMax(hmax,h);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < params->vertCount; ++i)
|
||||
{
|
||||
const unsigned short* iv = ¶ms->verts[i*3];
|
||||
const float h = params->bmin[1] + iv[1] * params->ch;
|
||||
hmin = dtMin(hmin,h);
|
||||
hmax = dtMax(hmax,h);
|
||||
}
|
||||
}
|
||||
hmin -= params->walkableClimb;
|
||||
hmax += params->walkableClimb;
|
||||
float bmin[3], bmax[3];
|
||||
dtVcopy(bmin, params->bmin);
|
||||
dtVcopy(bmax, params->bmax);
|
||||
bmin[1] = hmin;
|
||||
bmax[1] = hmax;
|
||||
|
||||
for (int i = 0; i < params->offMeshConCount; ++i)
|
||||
{
|
||||
const float* p0 = ¶ms->offMeshConVerts[(i*2+0)*3];
|
||||
const float* p1 = ¶ms->offMeshConVerts[(i*2+1)*3];
|
||||
offMeshConClass[i*2+0] = classifyOffMeshPoint(p0, bmin, bmax);
|
||||
offMeshConClass[i*2+1] = classifyOffMeshPoint(p1, bmin, bmax);
|
||||
|
||||
// Zero out off-mesh start positions which are not even potentially touching the mesh.
|
||||
if (offMeshConClass[i*2+0] == 0xff)
|
||||
{
|
||||
if (p0[1] < bmin[1] || p0[1] > bmax[1])
|
||||
offMeshConClass[i*2+0] = 0;
|
||||
}
|
||||
|
||||
// Cound how many links should be allocated for off-mesh connections.
|
||||
if (offMeshConClass[i*2+0] == 0xff)
|
||||
offMeshConLinkCount++;
|
||||
if (offMeshConClass[i*2+1] == 0xff)
|
||||
offMeshConLinkCount++;
|
||||
|
||||
if (offMeshConClass[i*2+0] == 0xff)
|
||||
storedOffMeshConCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// Off-mesh connections are stored as polygons, adjust values.
|
||||
const int totPolyCount = params->polyCount + storedOffMeshConCount;
|
||||
const int totVertCount = params->vertCount + storedOffMeshConCount*2;
|
||||
|
||||
// Find portal edges which are at tile borders.
|
||||
int edgeCount = 0;
|
||||
int portalCount = 0;
|
||||
for (int i = 0; i < params->polyCount; ++i)
|
||||
{
|
||||
const unsigned short* p = ¶ms->polys[i*2*nvp];
|
||||
for (int j = 0; j < nvp; ++j)
|
||||
{
|
||||
if (p[j] == MESH_NULL_IDX) break;
|
||||
edgeCount++;
|
||||
|
||||
if (p[nvp+j] & 0x8000)
|
||||
{
|
||||
unsigned short dir = p[nvp+j] & 0xf;
|
||||
if (dir != 0xf)
|
||||
portalCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const int maxLinkCount = edgeCount + portalCount*2 + offMeshConLinkCount*2;
|
||||
|
||||
// Find unique detail vertices.
|
||||
int uniqueDetailVertCount = 0;
|
||||
int detailTriCount = 0;
|
||||
if (params->detailMeshes)
|
||||
{
|
||||
// Has detail mesh, count unique detail vertex count and use input detail tri count.
|
||||
detailTriCount = params->detailTriCount;
|
||||
for (int i = 0; i < params->polyCount; ++i)
|
||||
{
|
||||
const unsigned short* p = ¶ms->polys[i*nvp*2];
|
||||
int ndv = params->detailMeshes[i*4+1];
|
||||
int nv = 0;
|
||||
for (int j = 0; j < nvp; ++j)
|
||||
{
|
||||
if (p[j] == MESH_NULL_IDX) break;
|
||||
nv++;
|
||||
}
|
||||
ndv -= nv;
|
||||
uniqueDetailVertCount += ndv;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// No input detail mesh, build detail mesh from nav polys.
|
||||
uniqueDetailVertCount = 0; // No extra detail verts.
|
||||
detailTriCount = 0;
|
||||
for (int i = 0; i < params->polyCount; ++i)
|
||||
{
|
||||
const unsigned short* p = ¶ms->polys[i*nvp*2];
|
||||
int nv = 0;
|
||||
for (int j = 0; j < nvp; ++j)
|
||||
{
|
||||
if (p[j] == MESH_NULL_IDX) break;
|
||||
nv++;
|
||||
}
|
||||
detailTriCount += nv-2;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate data size
|
||||
const int headerSize = dtAlign4(sizeof(dtMeshHeader));
|
||||
const int vertsSize = dtAlign4(sizeof(float)*3*totVertCount);
|
||||
const int polysSize = dtAlign4(sizeof(dtPoly)*totPolyCount);
|
||||
const int linksSize = dtAlign4(sizeof(dtLink)*maxLinkCount);
|
||||
const int detailMeshesSize = dtAlign4(sizeof(dtPolyDetail)*params->polyCount);
|
||||
const int detailVertsSize = dtAlign4(sizeof(float)*3*uniqueDetailVertCount);
|
||||
const int detailTrisSize = dtAlign4(sizeof(unsigned char)*4*detailTriCount);
|
||||
const int bvTreeSize = params->buildBvTree ? dtAlign4(sizeof(dtBVNode)*params->polyCount*2) : 0;
|
||||
const int offMeshConsSize = dtAlign4(sizeof(dtOffMeshConnection)*storedOffMeshConCount);
|
||||
|
||||
const int dataSize = headerSize + vertsSize + polysSize + linksSize +
|
||||
detailMeshesSize + detailVertsSize + detailTrisSize +
|
||||
bvTreeSize + offMeshConsSize;
|
||||
|
||||
unsigned char* data = (unsigned char*)dtAlloc(sizeof(unsigned char)*dataSize, DT_ALLOC_PERM);
|
||||
if (!data)
|
||||
{
|
||||
dtFree(offMeshConClass);
|
||||
return false;
|
||||
}
|
||||
memset(data, 0, dataSize);
|
||||
|
||||
unsigned char* d = data;
|
||||
|
||||
dtMeshHeader* header = dtGetThenAdvanceBufferPointer<dtMeshHeader>(d, headerSize);
|
||||
float* navVerts = dtGetThenAdvanceBufferPointer<float>(d, vertsSize);
|
||||
dtPoly* navPolys = dtGetThenAdvanceBufferPointer<dtPoly>(d, polysSize);
|
||||
d += linksSize; // Ignore links; just leave enough space for them. They'll be created on load.
|
||||
dtPolyDetail* navDMeshes = dtGetThenAdvanceBufferPointer<dtPolyDetail>(d, detailMeshesSize);
|
||||
float* navDVerts = dtGetThenAdvanceBufferPointer<float>(d, detailVertsSize);
|
||||
unsigned char* navDTris = dtGetThenAdvanceBufferPointer<unsigned char>(d, detailTrisSize);
|
||||
dtBVNode* navBvtree = dtGetThenAdvanceBufferPointer<dtBVNode>(d, bvTreeSize);
|
||||
dtOffMeshConnection* offMeshCons = dtGetThenAdvanceBufferPointer<dtOffMeshConnection>(d, offMeshConsSize);
|
||||
|
||||
|
||||
// Store header
|
||||
header->magic = DT_NAVMESH_MAGIC;
|
||||
header->version = DT_NAVMESH_VERSION;
|
||||
header->x = params->tileX;
|
||||
header->y = params->tileY;
|
||||
header->layer = params->tileLayer;
|
||||
header->userId = params->userId;
|
||||
header->polyCount = totPolyCount;
|
||||
header->vertCount = totVertCount;
|
||||
header->maxLinkCount = maxLinkCount;
|
||||
dtVcopy(header->bmin, params->bmin);
|
||||
dtVcopy(header->bmax, params->bmax);
|
||||
header->detailMeshCount = params->polyCount;
|
||||
header->detailVertCount = uniqueDetailVertCount;
|
||||
header->detailTriCount = detailTriCount;
|
||||
header->bvQuantFactor = 1.0f / params->cs;
|
||||
header->offMeshBase = params->polyCount;
|
||||
header->walkableHeight = params->walkableHeight;
|
||||
header->walkableRadius = params->walkableRadius;
|
||||
header->walkableClimb = params->walkableClimb;
|
||||
header->offMeshConCount = storedOffMeshConCount;
|
||||
header->bvNodeCount = params->buildBvTree ? params->polyCount*2 : 0;
|
||||
|
||||
const int offMeshVertsBase = params->vertCount;
|
||||
const int offMeshPolyBase = params->polyCount;
|
||||
|
||||
// Store vertices
|
||||
// Mesh vertices
|
||||
for (int i = 0; i < params->vertCount; ++i)
|
||||
{
|
||||
const unsigned short* iv = ¶ms->verts[i*3];
|
||||
float* v = &navVerts[i*3];
|
||||
v[0] = params->bmin[0] + iv[0] * params->cs;
|
||||
v[1] = params->bmin[1] + iv[1] * params->ch;
|
||||
v[2] = params->bmin[2] + iv[2] * params->cs;
|
||||
}
|
||||
// Off-mesh link vertices.
|
||||
int n = 0;
|
||||
for (int i = 0; i < params->offMeshConCount; ++i)
|
||||
{
|
||||
// Only store connections which start from this tile.
|
||||
if (offMeshConClass[i*2+0] == 0xff)
|
||||
{
|
||||
const float* linkv = ¶ms->offMeshConVerts[i*2*3];
|
||||
float* v = &navVerts[(offMeshVertsBase + n*2)*3];
|
||||
dtVcopy(&v[0], &linkv[0]);
|
||||
dtVcopy(&v[3], &linkv[3]);
|
||||
n++;
|
||||
}
|
||||
}
|
||||
|
||||
// Store polygons
|
||||
// Mesh polys
|
||||
const unsigned short* src = params->polys;
|
||||
for (int i = 0; i < params->polyCount; ++i)
|
||||
{
|
||||
dtPoly* p = &navPolys[i];
|
||||
p->vertCount = 0;
|
||||
p->flags = params->polyFlags[i];
|
||||
p->setArea(params->polyAreas[i]);
|
||||
p->setType(DT_POLYTYPE_GROUND);
|
||||
for (int j = 0; j < nvp; ++j)
|
||||
{
|
||||
if (src[j] == MESH_NULL_IDX) break;
|
||||
p->verts[j] = src[j];
|
||||
if (src[nvp+j] & 0x8000)
|
||||
{
|
||||
// Border or portal edge.
|
||||
unsigned short dir = src[nvp+j] & 0xf;
|
||||
if (dir == 0xf) // Border
|
||||
p->neis[j] = 0;
|
||||
else if (dir == 0) // Portal x-
|
||||
p->neis[j] = DT_EXT_LINK | 4;
|
||||
else if (dir == 1) // Portal z+
|
||||
p->neis[j] = DT_EXT_LINK | 2;
|
||||
else if (dir == 2) // Portal x+
|
||||
p->neis[j] = DT_EXT_LINK | 0;
|
||||
else if (dir == 3) // Portal z-
|
||||
p->neis[j] = DT_EXT_LINK | 6;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Normal connection
|
||||
p->neis[j] = src[nvp+j]+1;
|
||||
}
|
||||
|
||||
p->vertCount++;
|
||||
}
|
||||
src += nvp*2;
|
||||
}
|
||||
// Off-mesh connection vertices.
|
||||
n = 0;
|
||||
for (int i = 0; i < params->offMeshConCount; ++i)
|
||||
{
|
||||
// Only store connections which start from this tile.
|
||||
if (offMeshConClass[i*2+0] == 0xff)
|
||||
{
|
||||
dtPoly* p = &navPolys[offMeshPolyBase+n];
|
||||
p->vertCount = 2;
|
||||
p->verts[0] = (unsigned short)(offMeshVertsBase + n*2+0);
|
||||
p->verts[1] = (unsigned short)(offMeshVertsBase + n*2+1);
|
||||
p->flags = params->offMeshConFlags[i];
|
||||
p->setArea(params->offMeshConAreas[i]);
|
||||
p->setType(DT_POLYTYPE_OFFMESH_CONNECTION);
|
||||
n++;
|
||||
}
|
||||
}
|
||||
|
||||
// Store detail meshes and vertices.
|
||||
// The nav polygon vertices are stored as the first vertices on each mesh.
|
||||
// We compress the mesh data by skipping them and using the navmesh coordinates.
|
||||
if (params->detailMeshes)
|
||||
{
|
||||
unsigned short vbase = 0;
|
||||
for (int i = 0; i < params->polyCount; ++i)
|
||||
{
|
||||
dtPolyDetail& dtl = navDMeshes[i];
|
||||
const int vb = (int)params->detailMeshes[i*4+0];
|
||||
const int ndv = (int)params->detailMeshes[i*4+1];
|
||||
const int nv = navPolys[i].vertCount;
|
||||
dtl.vertBase = (unsigned int)vbase;
|
||||
dtl.vertCount = (unsigned char)(ndv-nv);
|
||||
dtl.triBase = (unsigned int)params->detailMeshes[i*4+2];
|
||||
dtl.triCount = (unsigned char)params->detailMeshes[i*4+3];
|
||||
// Copy vertices except the first 'nv' verts which are equal to nav poly verts.
|
||||
if (ndv-nv)
|
||||
{
|
||||
memcpy(&navDVerts[vbase*3], ¶ms->detailVerts[(vb+nv)*3], sizeof(float)*3*(ndv-nv));
|
||||
vbase += (unsigned short)(ndv-nv);
|
||||
}
|
||||
}
|
||||
// Store triangles.
|
||||
memcpy(navDTris, params->detailTris, sizeof(unsigned char)*4*params->detailTriCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create dummy detail mesh by triangulating polys.
|
||||
int tbase = 0;
|
||||
for (int i = 0; i < params->polyCount; ++i)
|
||||
{
|
||||
dtPolyDetail& dtl = navDMeshes[i];
|
||||
const int nv = navPolys[i].vertCount;
|
||||
dtl.vertBase = 0;
|
||||
dtl.vertCount = 0;
|
||||
dtl.triBase = (unsigned int)tbase;
|
||||
dtl.triCount = (unsigned char)(nv-2);
|
||||
// Triangulate polygon (local indices).
|
||||
for (int j = 2; j < nv; ++j)
|
||||
{
|
||||
unsigned char* t = &navDTris[tbase*4];
|
||||
t[0] = 0;
|
||||
t[1] = (unsigned char)(j-1);
|
||||
t[2] = (unsigned char)j;
|
||||
// Bit for each edge that belongs to poly boundary.
|
||||
t[3] = (1<<2);
|
||||
if (j == 2) t[3] |= (1<<0);
|
||||
if (j == nv-1) t[3] |= (1<<4);
|
||||
tbase++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Store and create BVtree.
|
||||
if (params->buildBvTree)
|
||||
{
|
||||
createBVTree(params, navBvtree, 2*params->polyCount);
|
||||
}
|
||||
|
||||
// Store Off-Mesh connections.
|
||||
n = 0;
|
||||
for (int i = 0; i < params->offMeshConCount; ++i)
|
||||
{
|
||||
// Only store connections which start from this tile.
|
||||
if (offMeshConClass[i*2+0] == 0xff)
|
||||
{
|
||||
dtOffMeshConnection* con = &offMeshCons[n];
|
||||
con->poly = (unsigned short)(offMeshPolyBase + n);
|
||||
// Copy connection end-points.
|
||||
const float* endPts = ¶ms->offMeshConVerts[i*2*3];
|
||||
dtVcopy(&con->pos[0], &endPts[0]);
|
||||
dtVcopy(&con->pos[3], &endPts[3]);
|
||||
con->rad = params->offMeshConRad[i];
|
||||
con->flags = params->offMeshConDir[i] ? DT_OFFMESH_CON_BIDIR : 0;
|
||||
con->side = offMeshConClass[i*2+1];
|
||||
if (params->offMeshConUserID)
|
||||
con->userId = params->offMeshConUserID[i];
|
||||
n++;
|
||||
}
|
||||
}
|
||||
|
||||
dtFree(offMeshConClass);
|
||||
|
||||
*outData = data;
|
||||
*outDataSize = dataSize;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool dtNavMeshHeaderSwapEndian(unsigned char* data, const int /*dataSize*/)
|
||||
{
|
||||
dtMeshHeader* header = (dtMeshHeader*)data;
|
||||
|
||||
int swappedMagic = DT_NAVMESH_MAGIC;
|
||||
int swappedVersion = DT_NAVMESH_VERSION;
|
||||
dtSwapEndian(&swappedMagic);
|
||||
dtSwapEndian(&swappedVersion);
|
||||
|
||||
if ((header->magic != DT_NAVMESH_MAGIC || header->version != DT_NAVMESH_VERSION) &&
|
||||
(header->magic != swappedMagic || header->version != swappedVersion))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
dtSwapEndian(&header->magic);
|
||||
dtSwapEndian(&header->version);
|
||||
dtSwapEndian(&header->x);
|
||||
dtSwapEndian(&header->y);
|
||||
dtSwapEndian(&header->layer);
|
||||
dtSwapEndian(&header->userId);
|
||||
dtSwapEndian(&header->polyCount);
|
||||
dtSwapEndian(&header->vertCount);
|
||||
dtSwapEndian(&header->maxLinkCount);
|
||||
dtSwapEndian(&header->detailMeshCount);
|
||||
dtSwapEndian(&header->detailVertCount);
|
||||
dtSwapEndian(&header->detailTriCount);
|
||||
dtSwapEndian(&header->bvNodeCount);
|
||||
dtSwapEndian(&header->offMeshConCount);
|
||||
dtSwapEndian(&header->offMeshBase);
|
||||
dtSwapEndian(&header->walkableHeight);
|
||||
dtSwapEndian(&header->walkableRadius);
|
||||
dtSwapEndian(&header->walkableClimb);
|
||||
dtSwapEndian(&header->bmin[0]);
|
||||
dtSwapEndian(&header->bmin[1]);
|
||||
dtSwapEndian(&header->bmin[2]);
|
||||
dtSwapEndian(&header->bmax[0]);
|
||||
dtSwapEndian(&header->bmax[1]);
|
||||
dtSwapEndian(&header->bmax[2]);
|
||||
dtSwapEndian(&header->bvQuantFactor);
|
||||
|
||||
// Freelist index and pointers are updated when tile is added, no need to swap.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// @par
|
||||
///
|
||||
/// @warning This function assumes that the header is in the correct endianness already.
|
||||
/// Call #dtNavMeshHeaderSwapEndian() first on the data if the data is expected to be in wrong endianness
|
||||
/// to start with. Call #dtNavMeshHeaderSwapEndian() after the data has been swapped if converting from
|
||||
/// native to foreign endianness.
|
||||
bool dtNavMeshDataSwapEndian(unsigned char* data, const int /*dataSize*/)
|
||||
{
|
||||
// Make sure the data is in right format.
|
||||
dtMeshHeader* header = (dtMeshHeader*)data;
|
||||
if (header->magic != DT_NAVMESH_MAGIC)
|
||||
return false;
|
||||
if (header->version != DT_NAVMESH_VERSION)
|
||||
return false;
|
||||
|
||||
// Patch header pointers.
|
||||
const int headerSize = dtAlign4(sizeof(dtMeshHeader));
|
||||
const int vertsSize = dtAlign4(sizeof(float)*3*header->vertCount);
|
||||
const int polysSize = dtAlign4(sizeof(dtPoly)*header->polyCount);
|
||||
const int linksSize = dtAlign4(sizeof(dtLink)*(header->maxLinkCount));
|
||||
const int detailMeshesSize = dtAlign4(sizeof(dtPolyDetail)*header->detailMeshCount);
|
||||
const int detailVertsSize = dtAlign4(sizeof(float)*3*header->detailVertCount);
|
||||
const int detailTrisSize = dtAlign4(sizeof(unsigned char)*4*header->detailTriCount);
|
||||
const int bvtreeSize = dtAlign4(sizeof(dtBVNode)*header->bvNodeCount);
|
||||
const int offMeshLinksSize = dtAlign4(sizeof(dtOffMeshConnection)*header->offMeshConCount);
|
||||
|
||||
unsigned char* d = data + headerSize;
|
||||
float* verts = dtGetThenAdvanceBufferPointer<float>(d, vertsSize);
|
||||
dtPoly* polys = dtGetThenAdvanceBufferPointer<dtPoly>(d, polysSize);
|
||||
d += linksSize; // Ignore links; they technically should be endian-swapped but all their data is overwritten on load anyway.
|
||||
//dtLink* links = dtGetThenAdvanceBufferPointer<dtLink>(d, linksSize);
|
||||
dtPolyDetail* detailMeshes = dtGetThenAdvanceBufferPointer<dtPolyDetail>(d, detailMeshesSize);
|
||||
float* detailVerts = dtGetThenAdvanceBufferPointer<float>(d, detailVertsSize);
|
||||
d += detailTrisSize; // Ignore detail tris; single bytes can't be endian-swapped.
|
||||
//unsigned char* detailTris = dtGetThenAdvanceBufferPointer<unsigned char>(d, detailTrisSize);
|
||||
dtBVNode* bvTree = dtGetThenAdvanceBufferPointer<dtBVNode>(d, bvtreeSize);
|
||||
dtOffMeshConnection* offMeshCons = dtGetThenAdvanceBufferPointer<dtOffMeshConnection>(d, offMeshLinksSize);
|
||||
|
||||
// Vertices
|
||||
for (int i = 0; i < header->vertCount*3; ++i)
|
||||
{
|
||||
dtSwapEndian(&verts[i]);
|
||||
}
|
||||
|
||||
// Polys
|
||||
for (int i = 0; i < header->polyCount; ++i)
|
||||
{
|
||||
dtPoly* p = &polys[i];
|
||||
// poly->firstLink is update when tile is added, no need to swap.
|
||||
for (int j = 0; j < DT_VERTS_PER_POLYGON; ++j)
|
||||
{
|
||||
dtSwapEndian(&p->verts[j]);
|
||||
dtSwapEndian(&p->neis[j]);
|
||||
}
|
||||
dtSwapEndian(&p->flags);
|
||||
}
|
||||
|
||||
// Links are rebuild when tile is added, no need to swap.
|
||||
|
||||
// Detail meshes
|
||||
for (int i = 0; i < header->detailMeshCount; ++i)
|
||||
{
|
||||
dtPolyDetail* pd = &detailMeshes[i];
|
||||
dtSwapEndian(&pd->vertBase);
|
||||
dtSwapEndian(&pd->triBase);
|
||||
}
|
||||
|
||||
// Detail verts
|
||||
for (int i = 0; i < header->detailVertCount*3; ++i)
|
||||
{
|
||||
dtSwapEndian(&detailVerts[i]);
|
||||
}
|
||||
|
||||
// BV-tree
|
||||
for (int i = 0; i < header->bvNodeCount; ++i)
|
||||
{
|
||||
dtBVNode* node = &bvTree[i];
|
||||
for (int j = 0; j < 3; ++j)
|
||||
{
|
||||
dtSwapEndian(&node->bmin[j]);
|
||||
dtSwapEndian(&node->bmax[j]);
|
||||
}
|
||||
dtSwapEndian(&node->i);
|
||||
}
|
||||
|
||||
// Off-mesh Connections.
|
||||
for (int i = 0; i < header->offMeshConCount; ++i)
|
||||
{
|
||||
dtOffMeshConnection* con = &offMeshCons[i];
|
||||
for (int j = 0; j < 6; ++j)
|
||||
dtSwapEndian(&con->pos[j]);
|
||||
dtSwapEndian(&con->rad);
|
||||
dtSwapEndian(&con->poly);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,200 @@
|
||||
//
|
||||
// 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 "DetourNode.h"
|
||||
#include "DetourAlloc.h"
|
||||
#include "DetourAssert.h"
|
||||
#include "DetourCommon.h"
|
||||
#include <string.h>
|
||||
|
||||
#ifdef DT_POLYREF64
|
||||
// From Thomas Wang, https://gist.github.com/badboy/6267743
|
||||
inline unsigned int dtHashRef(dtPolyRef a)
|
||||
{
|
||||
a = (~a) + (a << 18); // a = (a << 18) - a - 1;
|
||||
a = a ^ (a >> 31);
|
||||
a = a * 21; // a = (a + (a << 2)) + (a << 4);
|
||||
a = a ^ (a >> 11);
|
||||
a = a + (a << 6);
|
||||
a = a ^ (a >> 22);
|
||||
return (unsigned int)a;
|
||||
}
|
||||
#else
|
||||
inline unsigned int dtHashRef(dtPolyRef a)
|
||||
{
|
||||
a += ~(a<<15);
|
||||
a ^= (a>>10);
|
||||
a += (a<<3);
|
||||
a ^= (a>>6);
|
||||
a += ~(a<<11);
|
||||
a ^= (a>>16);
|
||||
return (unsigned int)a;
|
||||
}
|
||||
#endif
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
dtNodePool::dtNodePool(int maxNodes, int hashSize) :
|
||||
m_nodes(0),
|
||||
m_first(0),
|
||||
m_next(0),
|
||||
m_maxNodes(maxNodes),
|
||||
m_hashSize(hashSize),
|
||||
m_nodeCount(0)
|
||||
{
|
||||
dtAssert(dtNextPow2(m_hashSize) == (unsigned int)m_hashSize);
|
||||
// pidx is special as 0 means "none" and 1 is the first node. For that reason
|
||||
// we have 1 fewer nodes available than the number of values it can contain.
|
||||
dtAssert(m_maxNodes > 0 && m_maxNodes <= DT_NULL_IDX && m_maxNodes <= (1 << DT_NODE_PARENT_BITS) - 1);
|
||||
|
||||
m_nodes = (dtNode*)dtAlloc(sizeof(dtNode)*m_maxNodes, DT_ALLOC_PERM);
|
||||
m_next = (dtNodeIndex*)dtAlloc(sizeof(dtNodeIndex)*m_maxNodes, DT_ALLOC_PERM);
|
||||
m_first = (dtNodeIndex*)dtAlloc(sizeof(dtNodeIndex)*hashSize, DT_ALLOC_PERM);
|
||||
|
||||
dtAssert(m_nodes);
|
||||
dtAssert(m_next);
|
||||
dtAssert(m_first);
|
||||
|
||||
memset(m_first, 0xff, sizeof(dtNodeIndex)*m_hashSize);
|
||||
memset(m_next, 0xff, sizeof(dtNodeIndex)*m_maxNodes);
|
||||
}
|
||||
|
||||
dtNodePool::~dtNodePool()
|
||||
{
|
||||
dtFree(m_nodes);
|
||||
dtFree(m_next);
|
||||
dtFree(m_first);
|
||||
}
|
||||
|
||||
void dtNodePool::clear()
|
||||
{
|
||||
memset(m_first, 0xff, sizeof(dtNodeIndex)*m_hashSize);
|
||||
m_nodeCount = 0;
|
||||
}
|
||||
|
||||
unsigned int dtNodePool::findNodes(dtPolyRef id, dtNode** nodes, const int maxNodes)
|
||||
{
|
||||
int n = 0;
|
||||
unsigned int bucket = dtHashRef(id) & (m_hashSize-1);
|
||||
dtNodeIndex i = m_first[bucket];
|
||||
while (i != DT_NULL_IDX)
|
||||
{
|
||||
if (m_nodes[i].id == id)
|
||||
{
|
||||
if (n >= maxNodes)
|
||||
return n;
|
||||
nodes[n++] = &m_nodes[i];
|
||||
}
|
||||
i = m_next[i];
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
dtNode* dtNodePool::findNode(dtPolyRef id, unsigned char state)
|
||||
{
|
||||
unsigned int bucket = dtHashRef(id) & (m_hashSize-1);
|
||||
dtNodeIndex i = m_first[bucket];
|
||||
while (i != DT_NULL_IDX)
|
||||
{
|
||||
if (m_nodes[i].id == id && m_nodes[i].state == state)
|
||||
return &m_nodes[i];
|
||||
i = m_next[i];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
dtNode* dtNodePool::getNode(dtPolyRef id, unsigned char state)
|
||||
{
|
||||
unsigned int bucket = dtHashRef(id) & (m_hashSize-1);
|
||||
dtNodeIndex i = m_first[bucket];
|
||||
dtNode* node = 0;
|
||||
while (i != DT_NULL_IDX)
|
||||
{
|
||||
if (m_nodes[i].id == id && m_nodes[i].state == state)
|
||||
return &m_nodes[i];
|
||||
i = m_next[i];
|
||||
}
|
||||
|
||||
if (m_nodeCount >= m_maxNodes)
|
||||
return 0;
|
||||
|
||||
i = (dtNodeIndex)m_nodeCount;
|
||||
m_nodeCount++;
|
||||
|
||||
// Init node
|
||||
node = &m_nodes[i];
|
||||
node->pidx = 0;
|
||||
node->cost = 0;
|
||||
node->total = 0;
|
||||
node->id = id;
|
||||
node->state = state;
|
||||
node->flags = 0;
|
||||
|
||||
m_next[i] = m_first[bucket];
|
||||
m_first[bucket] = i;
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
dtNodeQueue::dtNodeQueue(int n) :
|
||||
m_heap(0),
|
||||
m_capacity(n),
|
||||
m_size(0)
|
||||
{
|
||||
dtAssert(m_capacity > 0);
|
||||
|
||||
m_heap = (dtNode**)dtAlloc(sizeof(dtNode*)*(m_capacity+1), DT_ALLOC_PERM);
|
||||
dtAssert(m_heap);
|
||||
}
|
||||
|
||||
dtNodeQueue::~dtNodeQueue()
|
||||
{
|
||||
dtFree(m_heap);
|
||||
}
|
||||
|
||||
void dtNodeQueue::bubbleUp(int i, dtNode* node)
|
||||
{
|
||||
int parent = (i-1)/2;
|
||||
// note: (index > 0) means there is a parent
|
||||
while ((i > 0) && (m_heap[parent]->total > node->total))
|
||||
{
|
||||
m_heap[i] = m_heap[parent];
|
||||
i = parent;
|
||||
parent = (i-1)/2;
|
||||
}
|
||||
m_heap[i] = node;
|
||||
}
|
||||
|
||||
void dtNodeQueue::trickleDown(int i, dtNode* node)
|
||||
{
|
||||
int child = (i*2)+1;
|
||||
while (child < m_size)
|
||||
{
|
||||
if (((child+1) < m_size) &&
|
||||
(m_heap[child]->total > m_heap[child+1]->total))
|
||||
{
|
||||
child++;
|
||||
}
|
||||
m_heap[i] = m_heap[child];
|
||||
i = child;
|
||||
child = (i*2)+1;
|
||||
}
|
||||
bubbleUp(i, node);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
add_library(DetourCrowd)
|
||||
add_library(RecastNavigation::DetourCrowd ALIAS DetourCrowd)
|
||||
|
||||
set_target_properties(DetourCrowd PROPERTIES
|
||||
DEBUG_POSTFIX -d
|
||||
SOVERSION ${SOVERSION}
|
||||
VERSION ${LIB_VERSION}
|
||||
COMPILE_PDB_OUTPUT_DIRECTORY .
|
||||
COMPILE_PDB_NAME "DetourCrowd-d"
|
||||
CXX_STANDARD 98
|
||||
CXX_STANDARD_REQUIRED ON
|
||||
CXX_EXTENSIONS OFF # Disable compiler-specific extensions
|
||||
)
|
||||
|
||||
target_include_directories(DetourCrowd PUBLIC
|
||||
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/Include>"
|
||||
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/recastnavigation>"
|
||||
)
|
||||
|
||||
target_sources(DetourCrowd PRIVATE
|
||||
Source/DetourCrowd.cpp
|
||||
Source/DetourLocalBoundary.cpp
|
||||
Source/DetourObstacleAvoidance.cpp
|
||||
Source/DetourPathCorridor.cpp
|
||||
Source/DetourPathQueue.cpp
|
||||
Source/DetourProximityGrid.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(DetourCrowd Detour)
|
||||
|
||||
install(TARGETS DetourCrowd
|
||||
EXPORT recastnavigation-targets
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT library
|
||||
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} ${CMAKE_INSTALL_INCLUDEDIR}/recastnavigation
|
||||
)
|
||||
|
||||
install(DIRECTORY Include/
|
||||
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/recastnavigation
|
||||
FILES_MATCHING PATTERN "*.h"
|
||||
)
|
||||
|
||||
if(MSVC)
|
||||
install(FILES "$<TARGET_FILE_DIR:DetourCrowd>/DetourCrowd-d.pdb"
|
||||
CONFIGURATIONS "Debug"
|
||||
DESTINATION "lib"
|
||||
OPTIONAL)
|
||||
endif()
|
||||
@@ -0,0 +1,460 @@
|
||||
//
|
||||
// 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 DETOURCROWD_H
|
||||
#define DETOURCROWD_H
|
||||
|
||||
#include "DetourNavMeshQuery.h"
|
||||
#include "DetourObstacleAvoidance.h"
|
||||
#include "DetourLocalBoundary.h"
|
||||
#include "DetourPathCorridor.h"
|
||||
#include "DetourProximityGrid.h"
|
||||
#include "DetourPathQueue.h"
|
||||
|
||||
/// The maximum number of neighbors that a crowd agent can take into account
|
||||
/// for steering decisions.
|
||||
/// @ingroup crowd
|
||||
static const int DT_CROWDAGENT_MAX_NEIGHBOURS = 6;
|
||||
|
||||
/// The maximum number of corners a crowd agent will look ahead in the path.
|
||||
/// This value is used for sizing the crowd agent corner buffers.
|
||||
/// Due to the behavior of the crowd manager, the actual number of useful
|
||||
/// corners will be one less than this number.
|
||||
/// @ingroup crowd
|
||||
static const int DT_CROWDAGENT_MAX_CORNERS = 4;
|
||||
|
||||
/// The maximum number of crowd avoidance configurations supported by the
|
||||
/// crowd manager.
|
||||
/// @ingroup crowd
|
||||
/// @see dtObstacleAvoidanceParams, dtCrowd::setObstacleAvoidanceParams(), dtCrowd::getObstacleAvoidanceParams(),
|
||||
/// dtCrowdAgentParams::obstacleAvoidanceType
|
||||
static const int DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS = 8;
|
||||
|
||||
/// The maximum number of query filter types supported by the crowd manager.
|
||||
/// @ingroup crowd
|
||||
/// @see dtQueryFilter, dtCrowd::getFilter() dtCrowd::getEditableFilter(),
|
||||
/// dtCrowdAgentParams::queryFilterType
|
||||
static const int DT_CROWD_MAX_QUERY_FILTER_TYPE = 16;
|
||||
|
||||
/// Provides neighbor data for agents managed by the crowd.
|
||||
/// @ingroup crowd
|
||||
/// @see dtCrowdAgent::neis, dtCrowd
|
||||
struct dtCrowdNeighbour
|
||||
{
|
||||
int idx; ///< The index of the neighbor in the crowd.
|
||||
float dist; ///< The distance between the current agent and the neighbor.
|
||||
};
|
||||
|
||||
/// The type of navigation mesh polygon the agent is currently traversing.
|
||||
/// @ingroup crowd
|
||||
enum CrowdAgentState
|
||||
{
|
||||
DT_CROWDAGENT_STATE_INVALID, ///< The agent is not in a valid state.
|
||||
DT_CROWDAGENT_STATE_WALKING, ///< The agent is traversing a normal navigation mesh polygon.
|
||||
DT_CROWDAGENT_STATE_OFFMESH ///< The agent is traversing an off-mesh connection.
|
||||
};
|
||||
|
||||
/// Configuration parameters for a crowd agent.
|
||||
/// @ingroup crowd
|
||||
struct dtCrowdAgentParams
|
||||
{
|
||||
float radius; ///< Agent radius. [Limit: >= 0]
|
||||
float height; ///< Agent height. [Limit: > 0]
|
||||
float maxAcceleration; ///< Maximum allowed acceleration. [Limit: >= 0]
|
||||
float maxSpeed; ///< Maximum allowed speed. [Limit: >= 0]
|
||||
|
||||
/// Defines how close a collision element must be before it is considered for steering behaviors. [Limits: > 0]
|
||||
float collisionQueryRange;
|
||||
|
||||
float pathOptimizationRange; ///< The path visibility optimization range. [Limit: > 0]
|
||||
|
||||
/// How aggresive the agent manager should be at avoiding collisions with this agent. [Limit: >= 0]
|
||||
float separationWeight;
|
||||
|
||||
/// Flags that impact steering behavior. (See: #UpdateFlags)
|
||||
unsigned char updateFlags;
|
||||
|
||||
/// The index of the avoidance configuration to use for the agent.
|
||||
/// [Limits: 0 <= value <= #DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS]
|
||||
unsigned char obstacleAvoidanceType;
|
||||
|
||||
/// The index of the query filter used by this agent.
|
||||
unsigned char queryFilterType;
|
||||
|
||||
/// User defined data attached to the agent.
|
||||
void* userData;
|
||||
};
|
||||
|
||||
enum MoveRequestState
|
||||
{
|
||||
DT_CROWDAGENT_TARGET_NONE = 0,
|
||||
DT_CROWDAGENT_TARGET_FAILED,
|
||||
DT_CROWDAGENT_TARGET_VALID,
|
||||
DT_CROWDAGENT_TARGET_REQUESTING,
|
||||
DT_CROWDAGENT_TARGET_WAITING_FOR_QUEUE,
|
||||
DT_CROWDAGENT_TARGET_WAITING_FOR_PATH,
|
||||
DT_CROWDAGENT_TARGET_VELOCITY
|
||||
};
|
||||
|
||||
/// Represents an agent managed by a #dtCrowd object.
|
||||
/// @ingroup crowd
|
||||
struct dtCrowdAgent
|
||||
{
|
||||
/// True if the agent is active, false if the agent is in an unused slot in the agent pool.
|
||||
bool active;
|
||||
|
||||
/// The type of mesh polygon the agent is traversing. (See: #CrowdAgentState)
|
||||
unsigned char state;
|
||||
|
||||
/// True if the agent has valid path (targetState == DT_CROWDAGENT_TARGET_VALID) and the path does not lead to the requested position, else false.
|
||||
bool partial;
|
||||
|
||||
/// The path corridor the agent is using.
|
||||
dtPathCorridor corridor;
|
||||
|
||||
/// The local boundary data for the agent.
|
||||
dtLocalBoundary boundary;
|
||||
|
||||
/// Time since the agent's path corridor was optimized.
|
||||
float topologyOptTime;
|
||||
|
||||
/// The known neighbors of the agent.
|
||||
dtCrowdNeighbour neis[DT_CROWDAGENT_MAX_NEIGHBOURS];
|
||||
|
||||
/// The number of neighbors.
|
||||
int nneis;
|
||||
|
||||
/// The desired speed.
|
||||
float desiredSpeed;
|
||||
|
||||
float npos[3]; ///< The current agent position. [(x, y, z)]
|
||||
float disp[3]; ///< A temporary value used to accumulate agent displacement during iterative collision resolution. [(x, y, z)]
|
||||
float dvel[3]; ///< The desired velocity of the agent. Based on the current path, calculated from scratch each frame. [(x, y, z)]
|
||||
float nvel[3]; ///< The desired velocity adjusted by obstacle avoidance, calculated from scratch each frame. [(x, y, z)]
|
||||
float vel[3]; ///< The actual velocity of the agent. The change from nvel -> vel is constrained by max acceleration. [(x, y, z)]
|
||||
|
||||
/// The agent's configuration parameters.
|
||||
dtCrowdAgentParams params;
|
||||
|
||||
/// The local path corridor corners for the agent. (Staight path.) [(x, y, z) * #ncorners]
|
||||
float cornerVerts[DT_CROWDAGENT_MAX_CORNERS*3];
|
||||
|
||||
/// The local path corridor corner flags. (See: #dtStraightPathFlags) [(flags) * #ncorners]
|
||||
unsigned char cornerFlags[DT_CROWDAGENT_MAX_CORNERS];
|
||||
|
||||
/// The reference id of the polygon being entered at the corner. [(polyRef) * #ncorners]
|
||||
dtPolyRef cornerPolys[DT_CROWDAGENT_MAX_CORNERS];
|
||||
|
||||
/// The number of corners.
|
||||
int ncorners;
|
||||
|
||||
unsigned char targetState; ///< State of the movement request.
|
||||
dtPolyRef targetRef; ///< Target polyref of the movement request.
|
||||
float targetPos[3]; ///< Target position of the movement request (or velocity in case of DT_CROWDAGENT_TARGET_VELOCITY).
|
||||
dtPathQueueRef targetPathqRef; ///< Path finder ref.
|
||||
bool targetReplan; ///< Flag indicating that the current path is being replanned.
|
||||
float targetReplanTime; /// <Time since the agent's target was replanned.
|
||||
};
|
||||
|
||||
struct dtCrowdAgentAnimation
|
||||
{
|
||||
bool active;
|
||||
float initPos[3], startPos[3], endPos[3];
|
||||
dtPolyRef polyRef;
|
||||
float t, tmax;
|
||||
};
|
||||
|
||||
/// Crowd agent update flags.
|
||||
/// @ingroup crowd
|
||||
/// @see dtCrowdAgentParams::updateFlags
|
||||
enum UpdateFlags
|
||||
{
|
||||
DT_CROWD_ANTICIPATE_TURNS = 1,
|
||||
DT_CROWD_OBSTACLE_AVOIDANCE = 2,
|
||||
DT_CROWD_SEPARATION = 4,
|
||||
DT_CROWD_OPTIMIZE_VIS = 8, ///< Use #dtPathCorridor::optimizePathVisibility() to optimize the agent path.
|
||||
DT_CROWD_OPTIMIZE_TOPO = 16 ///< Use dtPathCorridor::optimizePathTopology() to optimize the agent path.
|
||||
};
|
||||
|
||||
struct dtCrowdAgentDebugInfo
|
||||
{
|
||||
int idx;
|
||||
float optStart[3], optEnd[3];
|
||||
dtObstacleAvoidanceDebugData* vod;
|
||||
};
|
||||
|
||||
/// Provides local steering behaviors for a group of agents.
|
||||
/// @ingroup crowd
|
||||
class dtCrowd
|
||||
{
|
||||
int m_maxAgents;
|
||||
dtCrowdAgent* m_agents;
|
||||
dtCrowdAgent** m_activeAgents;
|
||||
dtCrowdAgentAnimation* m_agentAnims;
|
||||
|
||||
dtPathQueue m_pathq;
|
||||
|
||||
dtObstacleAvoidanceParams m_obstacleQueryParams[DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS];
|
||||
dtObstacleAvoidanceQuery* m_obstacleQuery;
|
||||
|
||||
dtProximityGrid* m_grid;
|
||||
|
||||
dtPolyRef* m_pathResult;
|
||||
int m_maxPathResult;
|
||||
|
||||
float m_agentPlacementHalfExtents[3];
|
||||
|
||||
dtQueryFilter m_filters[DT_CROWD_MAX_QUERY_FILTER_TYPE];
|
||||
|
||||
float m_maxAgentRadius;
|
||||
|
||||
int m_velocitySampleCount;
|
||||
|
||||
dtNavMeshQuery* m_navquery;
|
||||
|
||||
void updateTopologyOptimization(dtCrowdAgent** agents, const int nagents, const float dt);
|
||||
void updateMoveRequest(const float dt);
|
||||
void checkPathValidity(dtCrowdAgent** agents, const int nagents, const float dt);
|
||||
|
||||
inline int getAgentIndex(const dtCrowdAgent* agent) const { return (int)(agent - m_agents); }
|
||||
|
||||
bool requestMoveTargetReplan(const int idx, dtPolyRef ref, const float* pos);
|
||||
|
||||
void purge();
|
||||
|
||||
public:
|
||||
dtCrowd();
|
||||
~dtCrowd();
|
||||
|
||||
/// Initializes the crowd.
|
||||
/// @param[in] maxAgents The maximum number of agents the crowd can manage. [Limit: >= 1]
|
||||
/// @param[in] maxAgentRadius The maximum radius of any agent that will be added to the crowd. [Limit: > 0]
|
||||
/// @param[in] nav The navigation mesh to use for planning.
|
||||
/// @return True if the initialization succeeded.
|
||||
bool init(const int maxAgents, const float maxAgentRadius, dtNavMesh* nav);
|
||||
|
||||
/// Sets the shared avoidance configuration for the specified index.
|
||||
/// @param[in] idx The index. [Limits: 0 <= value < #DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS]
|
||||
/// @param[in] params The new configuration.
|
||||
void setObstacleAvoidanceParams(const int idx, const dtObstacleAvoidanceParams* params);
|
||||
|
||||
/// Gets the shared avoidance configuration for the specified index.
|
||||
/// @param[in] idx The index of the configuration to retreive.
|
||||
/// [Limits: 0 <= value < #DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS]
|
||||
/// @return The requested configuration.
|
||||
const dtObstacleAvoidanceParams* getObstacleAvoidanceParams(const int idx) const;
|
||||
|
||||
/// Gets the specified agent from the pool.
|
||||
/// @param[in] idx The agent index. [Limits: 0 <= value < #getAgentCount()]
|
||||
/// @return The requested agent.
|
||||
const dtCrowdAgent* getAgent(const int idx);
|
||||
|
||||
/// Gets the specified agent from the pool.
|
||||
/// @param[in] idx The agent index. [Limits: 0 <= value < #getAgentCount()]
|
||||
/// @return The requested agent.
|
||||
dtCrowdAgent* getEditableAgent(const int idx);
|
||||
|
||||
/// The maximum number of agents that can be managed by the object.
|
||||
/// @return The maximum number of agents.
|
||||
int getAgentCount() const;
|
||||
|
||||
/// Adds a new agent to the crowd.
|
||||
/// @param[in] pos The requested position of the agent. [(x, y, z)]
|
||||
/// @param[in] params The configuration of the agent.
|
||||
/// @return The index of the agent in the agent pool. Or -1 if the agent could not be added.
|
||||
int addAgent(const float* pos, const dtCrowdAgentParams* params);
|
||||
|
||||
/// Updates the specified agent's configuration.
|
||||
/// @param[in] idx The agent index. [Limits: 0 <= value < #getAgentCount()]
|
||||
/// @param[in] params The new agent configuration.
|
||||
void updateAgentParameters(const int idx, const dtCrowdAgentParams* params);
|
||||
|
||||
/// Removes the agent from the crowd.
|
||||
/// @param[in] idx The agent index. [Limits: 0 <= value < #getAgentCount()]
|
||||
void removeAgent(const int idx);
|
||||
|
||||
/// Submits a new move request for the specified agent.
|
||||
/// @param[in] idx The agent index. [Limits: 0 <= value < #getAgentCount()]
|
||||
/// @param[in] ref The position's polygon reference.
|
||||
/// @param[in] pos The position within the polygon. [(x, y, z)]
|
||||
/// @return True if the request was successfully submitted.
|
||||
bool requestMoveTarget(const int idx, dtPolyRef ref, const float* pos);
|
||||
|
||||
/// Submits a new move request for the specified agent.
|
||||
/// @param[in] idx The agent index. [Limits: 0 <= value < #getAgentCount()]
|
||||
/// @param[in] vel The movement velocity. [(x, y, z)]
|
||||
/// @return True if the request was successfully submitted.
|
||||
bool requestMoveVelocity(const int idx, const float* vel);
|
||||
|
||||
/// Resets any request for the specified agent.
|
||||
/// @param[in] idx The agent index. [Limits: 0 <= value < #getAgentCount()]
|
||||
/// @return True if the request was successfully reseted.
|
||||
bool resetMoveTarget(const int idx);
|
||||
|
||||
/// Gets the active agents int the agent pool.
|
||||
/// @param[out] agents An array of agent pointers. [(#dtCrowdAgent *) * maxAgents]
|
||||
/// @param[in] maxAgents The size of the crowd agent array.
|
||||
/// @return The number of agents returned in @p agents.
|
||||
int getActiveAgents(dtCrowdAgent** agents, const int maxAgents);
|
||||
|
||||
/// Updates the steering and positions of all agents.
|
||||
/// @param[in] dt The time, in seconds, to update the simulation. [Limit: > 0]
|
||||
/// @param[out] debug A debug object to load with debug information. [Opt]
|
||||
void update(const float dt, dtCrowdAgentDebugInfo* debug);
|
||||
|
||||
/// Gets the filter used by the crowd.
|
||||
/// @return The filter used by the crowd.
|
||||
inline const dtQueryFilter* getFilter(const int i) const { return (i >= 0 && i < DT_CROWD_MAX_QUERY_FILTER_TYPE) ? &m_filters[i] : 0; }
|
||||
|
||||
/// Gets the filter used by the crowd.
|
||||
/// @return The filter used by the crowd.
|
||||
inline dtQueryFilter* getEditableFilter(const int i) { return (i >= 0 && i < DT_CROWD_MAX_QUERY_FILTER_TYPE) ? &m_filters[i] : 0; }
|
||||
|
||||
/// Gets the search halfExtents [(x, y, z)] used by the crowd for query operations.
|
||||
/// @return The search halfExtents used by the crowd. [(x, y, z)]
|
||||
const float* getQueryHalfExtents() const { return m_agentPlacementHalfExtents; }
|
||||
|
||||
/// Same as getQueryHalfExtents. Left to maintain backwards compatibility.
|
||||
/// @return The search halfExtents used by the crowd. [(x, y, z)]
|
||||
const float* getQueryExtents() const { return m_agentPlacementHalfExtents; }
|
||||
|
||||
/// Gets the velocity sample count.
|
||||
/// @return The velocity sample count.
|
||||
inline int getVelocitySampleCount() const { return m_velocitySampleCount; }
|
||||
|
||||
/// Gets the crowd's proximity grid.
|
||||
/// @return The crowd's proximity grid.
|
||||
const dtProximityGrid* getGrid() const { return m_grid; }
|
||||
|
||||
/// Gets the crowd's path request queue.
|
||||
/// @return The crowd's path request queue.
|
||||
const dtPathQueue* getPathQueue() const { return &m_pathq; }
|
||||
|
||||
/// Gets the query object used by the crowd.
|
||||
const dtNavMeshQuery* getNavMeshQuery() const { return m_navquery; }
|
||||
|
||||
private:
|
||||
// Explicitly disabled copy constructor and copy assignment operator.
|
||||
dtCrowd(const dtCrowd&);
|
||||
dtCrowd& operator=(const dtCrowd&);
|
||||
};
|
||||
|
||||
/// Allocates a crowd object using the Detour allocator.
|
||||
/// @return A crowd object that is ready for initialization, or null on failure.
|
||||
/// @ingroup crowd
|
||||
dtCrowd* dtAllocCrowd();
|
||||
|
||||
/// Frees the specified crowd object using the Detour allocator.
|
||||
/// @param[in] ptr A crowd object allocated using #dtAllocCrowd
|
||||
/// @ingroup crowd
|
||||
void dtFreeCrowd(dtCrowd* ptr);
|
||||
|
||||
|
||||
#endif // DETOURCROWD_H
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// This section contains detailed documentation for members that don't have
|
||||
// a source file. It reduces clutter in the main section of the header.
|
||||
|
||||
/**
|
||||
|
||||
@defgroup crowd Crowd
|
||||
|
||||
Members in this module implement local steering and dynamic avoidance features.
|
||||
|
||||
The crowd is the big beast of the navigation features. It not only handles a
|
||||
lot of the path management for you, but also local steering and dynamic
|
||||
avoidance between members of the crowd. I.e. It can keep your agents from
|
||||
running into each other.
|
||||
|
||||
Main class: #dtCrowd
|
||||
|
||||
The #dtNavMeshQuery and #dtPathCorridor classes provide perfectly good, easy
|
||||
to use path planning features. But in the end they only give you points that
|
||||
your navigation client should be moving toward. When it comes to deciding things
|
||||
like agent velocity and steering to avoid other agents, that is up to you to
|
||||
implement. Unless, of course, you decide to use #dtCrowd.
|
||||
|
||||
Basically, you add an agent to the crowd, providing various configuration
|
||||
settings such as maximum speed and acceleration. You also provide a local
|
||||
target to more toward. The crowd manager then provides, with every update, the
|
||||
new agent position and velocity for the frame. The movement will be
|
||||
constrained to the navigation mesh, and steering will be applied to ensure
|
||||
agents managed by the crowd do not collide with each other.
|
||||
|
||||
This is very powerful feature set. But it comes with limitations.
|
||||
|
||||
The biggest limitation is that you must give control of the agent's position
|
||||
completely over to the crowd manager. You can update things like maximum speed
|
||||
and acceleration. But in order for the crowd manager to do its thing, it can't
|
||||
allow you to constantly be giving it overrides to position and velocity. So
|
||||
you give up direct control of the agent's movement. It belongs to the crowd.
|
||||
|
||||
The second biggest limitation revolves around the fact that the crowd manager
|
||||
deals with local planning. So the agent's target should never be more than
|
||||
256 polygons aways from its current position. If it is, you risk
|
||||
your agent failing to reach its target. So you may still need to do long
|
||||
distance planning and provide the crowd manager with intermediate targets.
|
||||
|
||||
Other significant limitations:
|
||||
|
||||
- All agents using the crowd manager will use the same #dtQueryFilter.
|
||||
- Crowd management is relatively expensive. The maximum agents under crowd
|
||||
management at any one time is between 20 and 30. A good place to start
|
||||
is a maximum of 25 agents for 0.5ms per frame.
|
||||
|
||||
@note This is a summary list of members. Use the index or search
|
||||
feature to find minor members.
|
||||
|
||||
@struct dtCrowdAgentParams
|
||||
@see dtCrowdAgent, dtCrowd::addAgent(), dtCrowd::updateAgentParameters()
|
||||
|
||||
@var dtCrowdAgentParams::obstacleAvoidanceType
|
||||
@par
|
||||
|
||||
#dtCrowd permits agents to use different avoidance configurations. This value
|
||||
is the index of the #dtObstacleAvoidanceParams within the crowd.
|
||||
|
||||
@see dtObstacleAvoidanceParams, dtCrowd::setObstacleAvoidanceParams(),
|
||||
dtCrowd::getObstacleAvoidanceParams()
|
||||
|
||||
@var dtCrowdAgentParams::collisionQueryRange
|
||||
@par
|
||||
|
||||
Collision elements include other agents and navigation mesh boundaries.
|
||||
|
||||
This value is often based on the agent radius and/or maximum speed. E.g. radius * 8
|
||||
|
||||
@var dtCrowdAgentParams::pathOptimizationRange
|
||||
@par
|
||||
|
||||
Only applicable if #updateFlags includes the #DT_CROWD_OPTIMIZE_VIS flag.
|
||||
|
||||
This value is often based on the agent radius. E.g. radius * 30
|
||||
|
||||
@see dtPathCorridor::optimizePathVisibility()
|
||||
|
||||
@var dtCrowdAgentParams::separationWeight
|
||||
@par
|
||||
|
||||
A higher value will result in agents trying to stay farther away from each other at
|
||||
the cost of more difficult steering in tight spaces.
|
||||
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
//
|
||||
// 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 DETOURLOCALBOUNDARY_H
|
||||
#define DETOURLOCALBOUNDARY_H
|
||||
|
||||
#include "DetourNavMeshQuery.h"
|
||||
|
||||
|
||||
class dtLocalBoundary
|
||||
{
|
||||
static const int MAX_LOCAL_SEGS = 8;
|
||||
static const int MAX_LOCAL_POLYS = 16;
|
||||
|
||||
struct Segment
|
||||
{
|
||||
float s[6]; ///< Segment start/end
|
||||
float d; ///< Distance for pruning.
|
||||
};
|
||||
|
||||
float m_center[3];
|
||||
Segment m_segs[MAX_LOCAL_SEGS];
|
||||
int m_nsegs;
|
||||
|
||||
dtPolyRef m_polys[MAX_LOCAL_POLYS];
|
||||
int m_npolys;
|
||||
|
||||
void addSegment(const float dist, const float* s);
|
||||
|
||||
public:
|
||||
dtLocalBoundary();
|
||||
~dtLocalBoundary();
|
||||
|
||||
void reset();
|
||||
|
||||
void update(dtPolyRef ref, const float* pos, const float collisionQueryRange,
|
||||
dtNavMeshQuery* navquery, const dtQueryFilter* filter);
|
||||
|
||||
bool isValid(dtNavMeshQuery* navquery, const dtQueryFilter* filter);
|
||||
|
||||
inline const float* getCenter() const { return m_center; }
|
||||
inline int getSegmentCount() const { return m_nsegs; }
|
||||
inline const float* getSegment(int i) const { return m_segs[i].s; }
|
||||
|
||||
private:
|
||||
// Explicitly disabled copy constructor and copy assignment operator.
|
||||
dtLocalBoundary(const dtLocalBoundary&);
|
||||
dtLocalBoundary& operator=(const dtLocalBoundary&);
|
||||
};
|
||||
|
||||
#endif // DETOURLOCALBOUNDARY_H
|
||||
@@ -0,0 +1,159 @@
|
||||
//
|
||||
// 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 DETOUROBSTACLEAVOIDANCE_H
|
||||
#define DETOUROBSTACLEAVOIDANCE_H
|
||||
|
||||
struct dtObstacleCircle
|
||||
{
|
||||
float p[3]; ///< Position of the obstacle
|
||||
float vel[3]; ///< Velocity of the obstacle
|
||||
float dvel[3]; ///< Velocity of the obstacle
|
||||
float rad; ///< Radius of the obstacle
|
||||
float dp[3], np[3]; ///< Use for side selection during sampling.
|
||||
};
|
||||
|
||||
struct dtObstacleSegment
|
||||
{
|
||||
float p[3], q[3]; ///< End points of the obstacle segment
|
||||
bool touch;
|
||||
};
|
||||
|
||||
|
||||
class dtObstacleAvoidanceDebugData
|
||||
{
|
||||
public:
|
||||
dtObstacleAvoidanceDebugData();
|
||||
~dtObstacleAvoidanceDebugData();
|
||||
|
||||
bool init(const int maxSamples);
|
||||
void reset();
|
||||
void addSample(const float* vel, const float ssize, const float pen,
|
||||
const float vpen, const float vcpen, const float spen, const float tpen);
|
||||
|
||||
void normalizeSamples();
|
||||
|
||||
inline int getSampleCount() const { return m_nsamples; }
|
||||
inline const float* getSampleVelocity(const int i) const { return &m_vel[i*3]; }
|
||||
inline float getSampleSize(const int i) const { return m_ssize[i]; }
|
||||
inline float getSamplePenalty(const int i) const { return m_pen[i]; }
|
||||
inline float getSampleDesiredVelocityPenalty(const int i) const { return m_vpen[i]; }
|
||||
inline float getSampleCurrentVelocityPenalty(const int i) const { return m_vcpen[i]; }
|
||||
inline float getSamplePreferredSidePenalty(const int i) const { return m_spen[i]; }
|
||||
inline float getSampleCollisionTimePenalty(const int i) const { return m_tpen[i]; }
|
||||
|
||||
private:
|
||||
// Explicitly disabled copy constructor and copy assignment operator.
|
||||
dtObstacleAvoidanceDebugData(const dtObstacleAvoidanceDebugData&);
|
||||
dtObstacleAvoidanceDebugData& operator=(const dtObstacleAvoidanceDebugData&);
|
||||
|
||||
int m_nsamples;
|
||||
int m_maxSamples;
|
||||
float* m_vel;
|
||||
float* m_ssize;
|
||||
float* m_pen;
|
||||
float* m_vpen;
|
||||
float* m_vcpen;
|
||||
float* m_spen;
|
||||
float* m_tpen;
|
||||
};
|
||||
|
||||
dtObstacleAvoidanceDebugData* dtAllocObstacleAvoidanceDebugData();
|
||||
void dtFreeObstacleAvoidanceDebugData(dtObstacleAvoidanceDebugData* ptr);
|
||||
|
||||
|
||||
static const int DT_MAX_PATTERN_DIVS = 32; ///< Max numver of adaptive divs.
|
||||
static const int DT_MAX_PATTERN_RINGS = 4; ///< Max number of adaptive rings.
|
||||
|
||||
struct dtObstacleAvoidanceParams
|
||||
{
|
||||
float velBias;
|
||||
float weightDesVel;
|
||||
float weightCurVel;
|
||||
float weightSide;
|
||||
float weightToi;
|
||||
float horizTime;
|
||||
unsigned char gridSize; ///< grid
|
||||
unsigned char adaptiveDivs; ///< adaptive
|
||||
unsigned char adaptiveRings; ///< adaptive
|
||||
unsigned char adaptiveDepth; ///< adaptive
|
||||
};
|
||||
|
||||
class dtObstacleAvoidanceQuery
|
||||
{
|
||||
public:
|
||||
dtObstacleAvoidanceQuery();
|
||||
~dtObstacleAvoidanceQuery();
|
||||
|
||||
bool init(const int maxCircles, const int maxSegments);
|
||||
|
||||
void reset();
|
||||
|
||||
void addCircle(const float* pos, const float rad,
|
||||
const float* vel, const float* dvel);
|
||||
|
||||
void addSegment(const float* p, const float* q);
|
||||
|
||||
int sampleVelocityGrid(const float* pos, const float rad, const float vmax,
|
||||
const float* vel, const float* dvel, float* nvel,
|
||||
const dtObstacleAvoidanceParams* params,
|
||||
dtObstacleAvoidanceDebugData* debug = 0);
|
||||
|
||||
int sampleVelocityAdaptive(const float* pos, const float rad, const float vmax,
|
||||
const float* vel, const float* dvel, float* nvel,
|
||||
const dtObstacleAvoidanceParams* params,
|
||||
dtObstacleAvoidanceDebugData* debug = 0);
|
||||
|
||||
inline int getObstacleCircleCount() const { return m_ncircles; }
|
||||
const dtObstacleCircle* getObstacleCircle(const int i) { return &m_circles[i]; }
|
||||
|
||||
inline int getObstacleSegmentCount() const { return m_nsegments; }
|
||||
const dtObstacleSegment* getObstacleSegment(const int i) { return &m_segments[i]; }
|
||||
|
||||
private:
|
||||
// Explicitly disabled copy constructor and copy assignment operator.
|
||||
dtObstacleAvoidanceQuery(const dtObstacleAvoidanceQuery&);
|
||||
dtObstacleAvoidanceQuery& operator=(const dtObstacleAvoidanceQuery&);
|
||||
|
||||
void prepare(const float* pos, const float* dvel);
|
||||
|
||||
float processSample(const float* vcand, const float cs,
|
||||
const float* pos, const float rad,
|
||||
const float* vel, const float* dvel,
|
||||
const float minPenalty,
|
||||
dtObstacleAvoidanceDebugData* debug);
|
||||
|
||||
dtObstacleAvoidanceParams m_params;
|
||||
float m_invHorizTime;
|
||||
float m_vmax;
|
||||
float m_invVmax;
|
||||
|
||||
int m_maxCircles;
|
||||
dtObstacleCircle* m_circles;
|
||||
int m_ncircles;
|
||||
|
||||
int m_maxSegments;
|
||||
dtObstacleSegment* m_segments;
|
||||
int m_nsegments;
|
||||
};
|
||||
|
||||
dtObstacleAvoidanceQuery* dtAllocObstacleAvoidanceQuery();
|
||||
void dtFreeObstacleAvoidanceQuery(dtObstacleAvoidanceQuery* ptr);
|
||||
|
||||
|
||||
#endif // DETOUROBSTACLEAVOIDANCE_H
|
||||
@@ -0,0 +1,151 @@
|
||||
//
|
||||
// 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 DETOUTPATHCORRIDOR_H
|
||||
#define DETOUTPATHCORRIDOR_H
|
||||
|
||||
#include "DetourNavMeshQuery.h"
|
||||
|
||||
/// Represents a dynamic polygon corridor used to plan agent movement.
|
||||
/// @ingroup crowd, detour
|
||||
class dtPathCorridor
|
||||
{
|
||||
float m_pos[3];
|
||||
float m_target[3];
|
||||
|
||||
dtPolyRef* m_path;
|
||||
int m_npath;
|
||||
int m_maxPath;
|
||||
|
||||
public:
|
||||
dtPathCorridor();
|
||||
~dtPathCorridor();
|
||||
|
||||
/// Allocates the corridor's path buffer.
|
||||
/// @param[in] maxPath The maximum path size the corridor can handle.
|
||||
/// @return True if the initialization succeeded.
|
||||
bool init(const int maxPath);
|
||||
|
||||
/// Resets the path corridor to the specified position.
|
||||
/// @param[in] ref The polygon reference containing the position.
|
||||
/// @param[in] pos The new position in the corridor. [(x, y, z)]
|
||||
void reset(dtPolyRef ref, const float* pos);
|
||||
|
||||
/// Finds the corners in the corridor from the position toward the target. (The straightened path.)
|
||||
/// @param[out] cornerVerts The corner vertices. [(x, y, z) * cornerCount] [Size: <= maxCorners]
|
||||
/// @param[out] cornerFlags The flag for each corner. [(flag) * cornerCount] [Size: <= maxCorners]
|
||||
/// @param[out] cornerPolys The polygon reference for each corner. [(polyRef) * cornerCount]
|
||||
/// [Size: <= @p maxCorners]
|
||||
/// @param[in] maxCorners The maximum number of corners the buffers can hold.
|
||||
/// @param[in] navquery The query object used to build the corridor.
|
||||
/// @param[in] filter The filter to apply to the operation.
|
||||
/// @return The number of corners returned in the corner buffers. [0 <= value <= @p maxCorners]
|
||||
int findCorners(float* cornerVerts, unsigned char* cornerFlags,
|
||||
dtPolyRef* cornerPolys, const int maxCorners,
|
||||
dtNavMeshQuery* navquery, const dtQueryFilter* filter);
|
||||
|
||||
/// Attempts to optimize the path if the specified point is visible from the current position.
|
||||
/// @param[in] next The point to search toward. [(x, y, z])
|
||||
/// @param[in] pathOptimizationRange The maximum range to search. [Limit: > 0]
|
||||
/// @param[in] navquery The query object used to build the corridor.
|
||||
/// @param[in] filter The filter to apply to the operation.
|
||||
void optimizePathVisibility(const float* next, const float pathOptimizationRange,
|
||||
dtNavMeshQuery* navquery, const dtQueryFilter* filter);
|
||||
|
||||
/// Attempts to optimize the path using a local area search. (Partial replanning.)
|
||||
/// @param[in] navquery The query object used to build the corridor.
|
||||
/// @param[in] filter The filter to apply to the operation.
|
||||
bool optimizePathTopology(dtNavMeshQuery* navquery, const dtQueryFilter* filter);
|
||||
|
||||
bool moveOverOffmeshConnection(dtPolyRef offMeshConRef, dtPolyRef* refs,
|
||||
float* startPos, float* endPos,
|
||||
dtNavMeshQuery* navquery);
|
||||
|
||||
bool fixPathStart(dtPolyRef safeRef, const float* safePos);
|
||||
|
||||
bool trimInvalidPath(dtPolyRef safeRef, const float* safePos,
|
||||
dtNavMeshQuery* navquery, const dtQueryFilter* filter);
|
||||
|
||||
/// Checks the current corridor path to see if its polygon references remain valid.
|
||||
/// @param[in] maxLookAhead The number of polygons from the beginning of the corridor to search.
|
||||
/// @param[in] navquery The query object used to build the corridor.
|
||||
/// @param[in] filter The filter to apply to the operation.
|
||||
bool isValid(const int maxLookAhead, dtNavMeshQuery* navquery, const dtQueryFilter* filter);
|
||||
|
||||
/// Moves the position from the current location to the desired location, adjusting the corridor
|
||||
/// as needed to reflect the change.
|
||||
/// @param[in] npos The desired new position. [(x, y, z)]
|
||||
/// @param[in] navquery The query object used to build the corridor.
|
||||
/// @param[in] filter The filter to apply to the operation.
|
||||
/// @return Returns true if move succeeded.
|
||||
bool movePosition(const float* npos, dtNavMeshQuery* navquery, const dtQueryFilter* filter);
|
||||
|
||||
/// Moves the target from the curent location to the desired location, adjusting the corridor
|
||||
/// as needed to reflect the change.
|
||||
/// @param[in] npos The desired new target position. [(x, y, z)]
|
||||
/// @param[in] navquery The query object used to build the corridor.
|
||||
/// @param[in] filter The filter to apply to the operation.
|
||||
/// @return Returns true if move succeeded.
|
||||
bool moveTargetPosition(const float* npos, dtNavMeshQuery* navquery, const dtQueryFilter* filter);
|
||||
|
||||
/// Loads a new path and target into the corridor.
|
||||
/// @param[in] target The target location within the last polygon of the path. [(x, y, z)]
|
||||
/// @param[in] path The path corridor. [(polyRef) * @p npolys]
|
||||
/// @param[in] npath The number of polygons in the path.
|
||||
void setCorridor(const float* target, const dtPolyRef* polys, const int npath);
|
||||
|
||||
/// Gets the current position within the corridor. (In the first polygon.)
|
||||
/// @return The current position within the corridor.
|
||||
inline const float* getPos() const { return m_pos; }
|
||||
|
||||
/// Gets the current target within the corridor. (In the last polygon.)
|
||||
/// @return The current target within the corridor.
|
||||
inline const float* getTarget() const { return m_target; }
|
||||
|
||||
/// The polygon reference id of the first polygon in the corridor, the polygon containing the position.
|
||||
/// @return The polygon reference id of the first polygon in the corridor. (Or zero if there is no path.)
|
||||
inline dtPolyRef getFirstPoly() const { return m_npath ? m_path[0] : 0; }
|
||||
|
||||
/// The polygon reference id of the last polygon in the corridor, the polygon containing the target.
|
||||
/// @return The polygon reference id of the last polygon in the corridor. (Or zero if there is no path.)
|
||||
inline dtPolyRef getLastPoly() const { return m_npath ? m_path[m_npath-1] : 0; }
|
||||
|
||||
/// The corridor's path.
|
||||
/// @return The corridor's path. [(polyRef) * #getPathCount()]
|
||||
inline const dtPolyRef* getPath() const { return m_path; }
|
||||
|
||||
/// The number of polygons in the current corridor path.
|
||||
/// @return The number of polygons in the current corridor path.
|
||||
inline int getPathCount() const { return m_npath; }
|
||||
|
||||
private:
|
||||
// Explicitly disabled copy constructor and copy assignment operator.
|
||||
dtPathCorridor(const dtPathCorridor&);
|
||||
dtPathCorridor& operator=(const dtPathCorridor&);
|
||||
};
|
||||
|
||||
int dtMergeCorridorStartMoved(dtPolyRef* path, const int npath, const int maxPath,
|
||||
const dtPolyRef* visited, const int nvisited);
|
||||
|
||||
int dtMergeCorridorEndMoved(dtPolyRef* path, const int npath, const int maxPath,
|
||||
const dtPolyRef* visited, const int nvisited);
|
||||
|
||||
int dtMergeCorridorStartShortcut(dtPolyRef* path, const int npath, const int maxPath,
|
||||
const dtPolyRef* visited, const int nvisited);
|
||||
|
||||
#endif // DETOUTPATHCORRIDOR_H
|
||||
@@ -0,0 +1,79 @@
|
||||
//
|
||||
// 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 DETOURPATHQUEUE_H
|
||||
#define DETOURPATHQUEUE_H
|
||||
|
||||
#include "DetourNavMesh.h"
|
||||
#include "DetourNavMeshQuery.h"
|
||||
|
||||
static const unsigned int DT_PATHQ_INVALID = 0;
|
||||
|
||||
typedef unsigned int dtPathQueueRef;
|
||||
|
||||
class dtPathQueue
|
||||
{
|
||||
struct PathQuery
|
||||
{
|
||||
dtPathQueueRef ref;
|
||||
/// Path find start and end location.
|
||||
float startPos[3], endPos[3];
|
||||
dtPolyRef startRef, endRef;
|
||||
/// Result.
|
||||
dtPolyRef* path;
|
||||
int npath;
|
||||
/// State.
|
||||
dtStatus status;
|
||||
int keepAlive;
|
||||
const dtQueryFilter* filter; ///< TODO: This is potentially dangerous!
|
||||
};
|
||||
|
||||
static const int MAX_QUEUE = 8;
|
||||
PathQuery m_queue[MAX_QUEUE];
|
||||
dtPathQueueRef m_nextHandle;
|
||||
int m_maxPathSize;
|
||||
int m_queueHead;
|
||||
dtNavMeshQuery* m_navquery;
|
||||
|
||||
void purge();
|
||||
|
||||
public:
|
||||
dtPathQueue();
|
||||
~dtPathQueue();
|
||||
|
||||
bool init(const int maxPathSize, const int maxSearchNodeCount, dtNavMesh* nav);
|
||||
|
||||
void update(const int maxIters);
|
||||
|
||||
dtPathQueueRef request(dtPolyRef startRef, dtPolyRef endRef,
|
||||
const float* startPos, const float* endPos,
|
||||
const dtQueryFilter* filter);
|
||||
|
||||
dtStatus getRequestStatus(dtPathQueueRef ref) const;
|
||||
|
||||
dtStatus getPathResult(dtPathQueueRef ref, dtPolyRef* path, int* pathSize, const int maxPath);
|
||||
|
||||
inline const dtNavMeshQuery* getNavQuery() const { return m_navquery; }
|
||||
|
||||
private:
|
||||
// Explicitly disabled copy constructor and copy assignment operator.
|
||||
dtPathQueue(const dtPathQueue&);
|
||||
dtPathQueue& operator=(const dtPathQueue&);
|
||||
};
|
||||
|
||||
#endif // DETOURPATHQUEUE_H
|
||||
@@ -0,0 +1,74 @@
|
||||
//
|
||||
// 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 DETOURPROXIMITYGRID_H
|
||||
#define DETOURPROXIMITYGRID_H
|
||||
|
||||
class dtProximityGrid
|
||||
{
|
||||
float m_cellSize;
|
||||
float m_invCellSize;
|
||||
|
||||
struct Item
|
||||
{
|
||||
unsigned short id;
|
||||
short x,y;
|
||||
unsigned short next;
|
||||
};
|
||||
Item* m_pool;
|
||||
int m_poolHead;
|
||||
int m_poolSize;
|
||||
|
||||
unsigned short* m_buckets;
|
||||
int m_bucketsSize;
|
||||
|
||||
int m_bounds[4];
|
||||
|
||||
public:
|
||||
dtProximityGrid();
|
||||
~dtProximityGrid();
|
||||
|
||||
bool init(const int poolSize, const float cellSize);
|
||||
|
||||
void clear();
|
||||
|
||||
void addItem(const unsigned short id,
|
||||
const float minx, const float miny,
|
||||
const float maxx, const float maxy);
|
||||
|
||||
int queryItems(const float minx, const float miny,
|
||||
const float maxx, const float maxy,
|
||||
unsigned short* ids, const int maxIds) const;
|
||||
|
||||
int getItemCountAt(const int x, const int y) const;
|
||||
|
||||
inline const int* getBounds() const { return m_bounds; }
|
||||
inline float getCellSize() const { return m_cellSize; }
|
||||
|
||||
private:
|
||||
// Explicitly disabled copy constructor and copy assignment operator.
|
||||
dtProximityGrid(const dtProximityGrid&);
|
||||
dtProximityGrid& operator=(const dtProximityGrid&);
|
||||
};
|
||||
|
||||
dtProximityGrid* dtAllocProximityGrid();
|
||||
void dtFreeProximityGrid(dtProximityGrid* ptr);
|
||||
|
||||
|
||||
#endif // DETOURPROXIMITYGRID_H
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,137 @@
|
||||
//
|
||||
// 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 <float.h>
|
||||
#include <string.h>
|
||||
#include "DetourLocalBoundary.h"
|
||||
#include "DetourNavMeshQuery.h"
|
||||
#include "DetourCommon.h"
|
||||
#include "DetourAssert.h"
|
||||
|
||||
|
||||
dtLocalBoundary::dtLocalBoundary() :
|
||||
m_nsegs(0),
|
||||
m_npolys(0)
|
||||
{
|
||||
dtVset(m_center, FLT_MAX,FLT_MAX,FLT_MAX);
|
||||
}
|
||||
|
||||
dtLocalBoundary::~dtLocalBoundary()
|
||||
{
|
||||
}
|
||||
|
||||
void dtLocalBoundary::reset()
|
||||
{
|
||||
dtVset(m_center, FLT_MAX,FLT_MAX,FLT_MAX);
|
||||
m_npolys = 0;
|
||||
m_nsegs = 0;
|
||||
}
|
||||
|
||||
void dtLocalBoundary::addSegment(const float dist, const float* s)
|
||||
{
|
||||
// Insert neighbour based on the distance.
|
||||
Segment* seg = 0;
|
||||
if (!m_nsegs)
|
||||
{
|
||||
// First, trivial accept.
|
||||
seg = &m_segs[0];
|
||||
}
|
||||
else if (dist >= m_segs[m_nsegs-1].d)
|
||||
{
|
||||
// Further than the last segment, skip.
|
||||
if (m_nsegs >= MAX_LOCAL_SEGS)
|
||||
return;
|
||||
// Last, trivial accept.
|
||||
seg = &m_segs[m_nsegs];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Insert inbetween.
|
||||
int i;
|
||||
for (i = 0; i < m_nsegs; ++i)
|
||||
if (dist <= m_segs[i].d)
|
||||
break;
|
||||
const int tgt = i+1;
|
||||
const int n = dtMin(m_nsegs-i, MAX_LOCAL_SEGS-tgt);
|
||||
dtAssert(tgt+n <= MAX_LOCAL_SEGS);
|
||||
if (n > 0)
|
||||
memmove(&m_segs[tgt], &m_segs[i], sizeof(Segment)*n);
|
||||
seg = &m_segs[i];
|
||||
}
|
||||
|
||||
seg->d = dist;
|
||||
memcpy(seg->s, s, sizeof(float)*6);
|
||||
|
||||
if (m_nsegs < MAX_LOCAL_SEGS)
|
||||
m_nsegs++;
|
||||
}
|
||||
|
||||
void dtLocalBoundary::update(dtPolyRef ref, const float* pos, const float collisionQueryRange,
|
||||
dtNavMeshQuery* navquery, const dtQueryFilter* filter)
|
||||
{
|
||||
static const int MAX_SEGS_PER_POLY = DT_VERTS_PER_POLYGON*3;
|
||||
|
||||
if (!ref)
|
||||
{
|
||||
dtVset(m_center, FLT_MAX,FLT_MAX,FLT_MAX);
|
||||
m_nsegs = 0;
|
||||
m_npolys = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
dtVcopy(m_center, pos);
|
||||
|
||||
// First query non-overlapping polygons.
|
||||
navquery->findLocalNeighbourhood(ref, pos, collisionQueryRange,
|
||||
filter, m_polys, 0, &m_npolys, MAX_LOCAL_POLYS);
|
||||
|
||||
// Secondly, store all polygon edges.
|
||||
m_nsegs = 0;
|
||||
float segs[MAX_SEGS_PER_POLY*6];
|
||||
int nsegs = 0;
|
||||
for (int j = 0; j < m_npolys; ++j)
|
||||
{
|
||||
navquery->getPolyWallSegments(m_polys[j], filter, segs, 0, &nsegs, MAX_SEGS_PER_POLY);
|
||||
for (int k = 0; k < nsegs; ++k)
|
||||
{
|
||||
const float* s = &segs[k*6];
|
||||
// Skip too distant segments.
|
||||
float tseg;
|
||||
const float distSqr = dtDistancePtSegSqr2D(pos, s, s+3, tseg);
|
||||
if (distSqr > dtSqr(collisionQueryRange))
|
||||
continue;
|
||||
addSegment(distSqr, s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool dtLocalBoundary::isValid(dtNavMeshQuery* navquery, const dtQueryFilter* filter)
|
||||
{
|
||||
if (!m_npolys)
|
||||
return false;
|
||||
|
||||
// Check that all polygons still pass query filter.
|
||||
for (int i = 0; i < m_npolys; ++i)
|
||||
{
|
||||
if (!navquery->isValidPolyRef(m_polys[i], filter))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,619 @@
|
||||
//
|
||||
// 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 "DetourObstacleAvoidance.h"
|
||||
#include "DetourCommon.h"
|
||||
#include "DetourMath.h"
|
||||
#include "DetourAlloc.h"
|
||||
#include "DetourAssert.h"
|
||||
#include <string.h>
|
||||
#include <float.h>
|
||||
#include <new>
|
||||
|
||||
static const float DT_PI = 3.14159265f;
|
||||
|
||||
static int sweepCircleCircle(const float* c0, const float r0, const float* v,
|
||||
const float* c1, const float r1,
|
||||
float& tmin, float& tmax)
|
||||
{
|
||||
static const float EPS = 0.0001f;
|
||||
float s[3];
|
||||
dtVsub(s,c1,c0);
|
||||
float r = r0+r1;
|
||||
float c = dtVdot2D(s,s) - r*r;
|
||||
float a = dtVdot2D(v,v);
|
||||
if (a < EPS) return 0; // not moving
|
||||
|
||||
// Overlap, calc time to exit.
|
||||
float b = dtVdot2D(v,s);
|
||||
float d = b*b - a*c;
|
||||
if (d < 0.0f) return 0; // no intersection.
|
||||
a = 1.0f / a;
|
||||
const float rd = dtMathSqrtf(d);
|
||||
tmin = (b - rd) * a;
|
||||
tmax = (b + rd) * a;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int isectRaySeg(const float* ap, const float* u,
|
||||
const float* bp, const float* bq,
|
||||
float& t)
|
||||
{
|
||||
float v[3], w[3];
|
||||
dtVsub(v,bq,bp);
|
||||
dtVsub(w,ap,bp);
|
||||
float d = dtVperp2D(u,v);
|
||||
if (dtMathFabsf(d) < 1e-6f) return 0;
|
||||
d = 1.0f/d;
|
||||
t = dtVperp2D(v,w) * d;
|
||||
if (t < 0 || t > 1) return 0;
|
||||
float s = dtVperp2D(u,w) * d;
|
||||
if (s < 0 || s > 1) return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
dtObstacleAvoidanceDebugData* dtAllocObstacleAvoidanceDebugData()
|
||||
{
|
||||
void* mem = dtAlloc(sizeof(dtObstacleAvoidanceDebugData), DT_ALLOC_PERM);
|
||||
if (!mem) return 0;
|
||||
return new(mem) dtObstacleAvoidanceDebugData;
|
||||
}
|
||||
|
||||
void dtFreeObstacleAvoidanceDebugData(dtObstacleAvoidanceDebugData* ptr)
|
||||
{
|
||||
if (!ptr) return;
|
||||
ptr->~dtObstacleAvoidanceDebugData();
|
||||
dtFree(ptr);
|
||||
}
|
||||
|
||||
|
||||
dtObstacleAvoidanceDebugData::dtObstacleAvoidanceDebugData() :
|
||||
m_nsamples(0),
|
||||
m_maxSamples(0),
|
||||
m_vel(0),
|
||||
m_ssize(0),
|
||||
m_pen(0),
|
||||
m_vpen(0),
|
||||
m_vcpen(0),
|
||||
m_spen(0),
|
||||
m_tpen(0)
|
||||
{
|
||||
}
|
||||
|
||||
dtObstacleAvoidanceDebugData::~dtObstacleAvoidanceDebugData()
|
||||
{
|
||||
dtFree(m_vel);
|
||||
dtFree(m_ssize);
|
||||
dtFree(m_pen);
|
||||
dtFree(m_vpen);
|
||||
dtFree(m_vcpen);
|
||||
dtFree(m_spen);
|
||||
dtFree(m_tpen);
|
||||
}
|
||||
|
||||
bool dtObstacleAvoidanceDebugData::init(const int maxSamples)
|
||||
{
|
||||
dtAssert(maxSamples);
|
||||
m_maxSamples = maxSamples;
|
||||
|
||||
m_vel = (float*)dtAlloc(sizeof(float)*3*m_maxSamples, DT_ALLOC_PERM);
|
||||
if (!m_vel)
|
||||
return false;
|
||||
m_pen = (float*)dtAlloc(sizeof(float)*m_maxSamples, DT_ALLOC_PERM);
|
||||
if (!m_pen)
|
||||
return false;
|
||||
m_ssize = (float*)dtAlloc(sizeof(float)*m_maxSamples, DT_ALLOC_PERM);
|
||||
if (!m_ssize)
|
||||
return false;
|
||||
m_vpen = (float*)dtAlloc(sizeof(float)*m_maxSamples, DT_ALLOC_PERM);
|
||||
if (!m_vpen)
|
||||
return false;
|
||||
m_vcpen = (float*)dtAlloc(sizeof(float)*m_maxSamples, DT_ALLOC_PERM);
|
||||
if (!m_vcpen)
|
||||
return false;
|
||||
m_spen = (float*)dtAlloc(sizeof(float)*m_maxSamples, DT_ALLOC_PERM);
|
||||
if (!m_spen)
|
||||
return false;
|
||||
m_tpen = (float*)dtAlloc(sizeof(float)*m_maxSamples, DT_ALLOC_PERM);
|
||||
if (!m_tpen)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void dtObstacleAvoidanceDebugData::reset()
|
||||
{
|
||||
m_nsamples = 0;
|
||||
}
|
||||
|
||||
void dtObstacleAvoidanceDebugData::addSample(const float* vel, const float ssize, const float pen,
|
||||
const float vpen, const float vcpen, const float spen, const float tpen)
|
||||
{
|
||||
if (m_nsamples >= m_maxSamples)
|
||||
return;
|
||||
dtAssert(m_vel);
|
||||
dtAssert(m_ssize);
|
||||
dtAssert(m_pen);
|
||||
dtAssert(m_vpen);
|
||||
dtAssert(m_vcpen);
|
||||
dtAssert(m_spen);
|
||||
dtAssert(m_tpen);
|
||||
dtVcopy(&m_vel[m_nsamples*3], vel);
|
||||
m_ssize[m_nsamples] = ssize;
|
||||
m_pen[m_nsamples] = pen;
|
||||
m_vpen[m_nsamples] = vpen;
|
||||
m_vcpen[m_nsamples] = vcpen;
|
||||
m_spen[m_nsamples] = spen;
|
||||
m_tpen[m_nsamples] = tpen;
|
||||
m_nsamples++;
|
||||
}
|
||||
|
||||
static void normalizeArray(float* arr, const int n)
|
||||
{
|
||||
// Normalize penaly range.
|
||||
float minPen = FLT_MAX;
|
||||
float maxPen = -FLT_MAX;
|
||||
for (int i = 0; i < n; ++i)
|
||||
{
|
||||
minPen = dtMin(minPen, arr[i]);
|
||||
maxPen = dtMax(maxPen, arr[i]);
|
||||
}
|
||||
const float penRange = maxPen-minPen;
|
||||
const float s = penRange > 0.001f ? (1.0f / penRange) : 1;
|
||||
for (int i = 0; i < n; ++i)
|
||||
arr[i] = dtClamp((arr[i]-minPen)*s, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
void dtObstacleAvoidanceDebugData::normalizeSamples()
|
||||
{
|
||||
normalizeArray(m_pen, m_nsamples);
|
||||
normalizeArray(m_vpen, m_nsamples);
|
||||
normalizeArray(m_vcpen, m_nsamples);
|
||||
normalizeArray(m_spen, m_nsamples);
|
||||
normalizeArray(m_tpen, m_nsamples);
|
||||
}
|
||||
|
||||
|
||||
dtObstacleAvoidanceQuery* dtAllocObstacleAvoidanceQuery()
|
||||
{
|
||||
void* mem = dtAlloc(sizeof(dtObstacleAvoidanceQuery), DT_ALLOC_PERM);
|
||||
if (!mem) return 0;
|
||||
return new(mem) dtObstacleAvoidanceQuery;
|
||||
}
|
||||
|
||||
void dtFreeObstacleAvoidanceQuery(dtObstacleAvoidanceQuery* ptr)
|
||||
{
|
||||
if (!ptr) return;
|
||||
ptr->~dtObstacleAvoidanceQuery();
|
||||
dtFree(ptr);
|
||||
}
|
||||
|
||||
|
||||
dtObstacleAvoidanceQuery::dtObstacleAvoidanceQuery() :
|
||||
m_invHorizTime(0),
|
||||
m_vmax(0),
|
||||
m_invVmax(0),
|
||||
m_maxCircles(0),
|
||||
m_circles(0),
|
||||
m_ncircles(0),
|
||||
m_maxSegments(0),
|
||||
m_segments(0),
|
||||
m_nsegments(0)
|
||||
{
|
||||
}
|
||||
|
||||
dtObstacleAvoidanceQuery::~dtObstacleAvoidanceQuery()
|
||||
{
|
||||
dtFree(m_circles);
|
||||
dtFree(m_segments);
|
||||
}
|
||||
|
||||
bool dtObstacleAvoidanceQuery::init(const int maxCircles, const int maxSegments)
|
||||
{
|
||||
m_maxCircles = maxCircles;
|
||||
m_ncircles = 0;
|
||||
m_circles = (dtObstacleCircle*)dtAlloc(sizeof(dtObstacleCircle)*m_maxCircles, DT_ALLOC_PERM);
|
||||
if (!m_circles)
|
||||
return false;
|
||||
memset(m_circles, 0, sizeof(dtObstacleCircle)*m_maxCircles);
|
||||
|
||||
m_maxSegments = maxSegments;
|
||||
m_nsegments = 0;
|
||||
m_segments = (dtObstacleSegment*)dtAlloc(sizeof(dtObstacleSegment)*m_maxSegments, DT_ALLOC_PERM);
|
||||
if (!m_segments)
|
||||
return false;
|
||||
memset(m_segments, 0, sizeof(dtObstacleSegment)*m_maxSegments);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void dtObstacleAvoidanceQuery::reset()
|
||||
{
|
||||
m_ncircles = 0;
|
||||
m_nsegments = 0;
|
||||
}
|
||||
|
||||
void dtObstacleAvoidanceQuery::addCircle(const float* pos, const float rad,
|
||||
const float* vel, const float* dvel)
|
||||
{
|
||||
if (m_ncircles >= m_maxCircles)
|
||||
return;
|
||||
|
||||
dtObstacleCircle* cir = &m_circles[m_ncircles++];
|
||||
dtVcopy(cir->p, pos);
|
||||
cir->rad = rad;
|
||||
dtVcopy(cir->vel, vel);
|
||||
dtVcopy(cir->dvel, dvel);
|
||||
}
|
||||
|
||||
void dtObstacleAvoidanceQuery::addSegment(const float* p, const float* q)
|
||||
{
|
||||
if (m_nsegments >= m_maxSegments)
|
||||
return;
|
||||
|
||||
dtObstacleSegment* seg = &m_segments[m_nsegments++];
|
||||
dtVcopy(seg->p, p);
|
||||
dtVcopy(seg->q, q);
|
||||
}
|
||||
|
||||
void dtObstacleAvoidanceQuery::prepare(const float* pos, const float* dvel)
|
||||
{
|
||||
// Prepare obstacles
|
||||
for (int i = 0; i < m_ncircles; ++i)
|
||||
{
|
||||
dtObstacleCircle* cir = &m_circles[i];
|
||||
|
||||
// Side
|
||||
const float* pa = pos;
|
||||
const float* pb = cir->p;
|
||||
|
||||
const float orig[3] = {0,0,0};
|
||||
float dv[3];
|
||||
dtVsub(cir->dp,pb,pa);
|
||||
dtVnormalize(cir->dp);
|
||||
dtVsub(dv, cir->dvel, dvel);
|
||||
|
||||
const float a = dtTriArea2D(orig, cir->dp,dv);
|
||||
if (a < 0.01f)
|
||||
{
|
||||
cir->np[0] = -cir->dp[2];
|
||||
cir->np[2] = cir->dp[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
cir->np[0] = cir->dp[2];
|
||||
cir->np[2] = -cir->dp[0];
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < m_nsegments; ++i)
|
||||
{
|
||||
dtObstacleSegment* seg = &m_segments[i];
|
||||
|
||||
// Precalc if the agent is really close to the segment.
|
||||
const float r = 0.01f;
|
||||
float t;
|
||||
seg->touch = dtDistancePtSegSqr2D(pos, seg->p, seg->q, t) < dtSqr(r);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Calculate the collision penalty for a given velocity vector
|
||||
*
|
||||
* @param vcand sampled velocity
|
||||
* @param dvel desired velocity
|
||||
* @param minPenalty threshold penalty for early out
|
||||
*/
|
||||
float dtObstacleAvoidanceQuery::processSample(const float* vcand, const float cs,
|
||||
const float* pos, const float rad,
|
||||
const float* vel, const float* dvel,
|
||||
const float minPenalty,
|
||||
dtObstacleAvoidanceDebugData* debug)
|
||||
{
|
||||
// penalty for straying away from the desired and current velocities
|
||||
const float vpen = m_params.weightDesVel * (dtVdist2D(vcand, dvel) * m_invVmax);
|
||||
const float vcpen = m_params.weightCurVel * (dtVdist2D(vcand, vel) * m_invVmax);
|
||||
|
||||
// find the threshold hit time to bail out based on the early out penalty
|
||||
// (see how the penalty is calculated below to understand)
|
||||
float minPen = minPenalty - vpen - vcpen;
|
||||
float tThresold = (m_params.weightToi / minPen - 0.1f) * m_params.horizTime;
|
||||
if (tThresold - m_params.horizTime > -FLT_EPSILON)
|
||||
return minPenalty; // already too much
|
||||
|
||||
// Find min time of impact and exit amongst all obstacles.
|
||||
float tmin = m_params.horizTime;
|
||||
float side = 0;
|
||||
int nside = 0;
|
||||
|
||||
for (int i = 0; i < m_ncircles; ++i)
|
||||
{
|
||||
const dtObstacleCircle* cir = &m_circles[i];
|
||||
|
||||
// RVO
|
||||
float vab[3];
|
||||
dtVscale(vab, vcand, 2);
|
||||
dtVsub(vab, vab, vel);
|
||||
dtVsub(vab, vab, cir->vel);
|
||||
|
||||
// Side
|
||||
side += dtClamp(dtMin(dtVdot2D(cir->dp,vab)*0.5f+0.5f, dtVdot2D(cir->np,vab)*2), 0.0f, 1.0f);
|
||||
nside++;
|
||||
|
||||
float htmin = 0, htmax = 0;
|
||||
if (!sweepCircleCircle(pos,rad, vab, cir->p,cir->rad, htmin, htmax))
|
||||
continue;
|
||||
|
||||
// Handle overlapping obstacles.
|
||||
if (htmin < 0.0f && htmax > 0.0f)
|
||||
{
|
||||
// Avoid more when overlapped.
|
||||
htmin = -htmin * 0.5f;
|
||||
}
|
||||
|
||||
if (htmin >= 0.0f)
|
||||
{
|
||||
// The closest obstacle is somewhere ahead of us, keep track of nearest obstacle.
|
||||
if (htmin < tmin)
|
||||
{
|
||||
tmin = htmin;
|
||||
if (tmin < tThresold)
|
||||
return minPenalty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < m_nsegments; ++i)
|
||||
{
|
||||
const dtObstacleSegment* seg = &m_segments[i];
|
||||
float htmin = 0;
|
||||
|
||||
if (seg->touch)
|
||||
{
|
||||
// Special case when the agent is very close to the segment.
|
||||
float sdir[3], snorm[3];
|
||||
dtVsub(sdir, seg->q, seg->p);
|
||||
snorm[0] = -sdir[2];
|
||||
snorm[2] = sdir[0];
|
||||
// If the velocity is pointing towards the segment, no collision.
|
||||
if (dtVdot2D(snorm, vcand) < 0.0f)
|
||||
continue;
|
||||
// Else immediate collision.
|
||||
htmin = 0.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!isectRaySeg(pos, vcand, seg->p, seg->q, htmin))
|
||||
continue;
|
||||
}
|
||||
|
||||
// Avoid less when facing walls.
|
||||
htmin *= 2.0f;
|
||||
|
||||
// The closest obstacle is somewhere ahead of us, keep track of nearest obstacle.
|
||||
if (htmin < tmin)
|
||||
{
|
||||
tmin = htmin;
|
||||
if (tmin < tThresold)
|
||||
return minPenalty;
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize side bias, to prevent it dominating too much.
|
||||
if (nside)
|
||||
side /= nside;
|
||||
|
||||
const float spen = m_params.weightSide * side;
|
||||
const float tpen = m_params.weightToi * (1.0f/(0.1f+tmin*m_invHorizTime));
|
||||
|
||||
const float penalty = vpen + vcpen + spen + tpen;
|
||||
|
||||
// Store different penalties for debug viewing
|
||||
if (debug)
|
||||
debug->addSample(vcand, cs, penalty, vpen, vcpen, spen, tpen);
|
||||
|
||||
return penalty;
|
||||
}
|
||||
|
||||
int dtObstacleAvoidanceQuery::sampleVelocityGrid(const float* pos, const float rad, const float vmax,
|
||||
const float* vel, const float* dvel, float* nvel,
|
||||
const dtObstacleAvoidanceParams* params,
|
||||
dtObstacleAvoidanceDebugData* debug)
|
||||
{
|
||||
prepare(pos, dvel);
|
||||
|
||||
memcpy(&m_params, params, sizeof(dtObstacleAvoidanceParams));
|
||||
m_invHorizTime = 1.0f / m_params.horizTime;
|
||||
m_vmax = vmax;
|
||||
m_invVmax = vmax > 0 ? 1.0f / vmax : FLT_MAX;
|
||||
|
||||
dtVset(nvel, 0,0,0);
|
||||
|
||||
if (debug)
|
||||
debug->reset();
|
||||
|
||||
const float cvx = dvel[0] * m_params.velBias;
|
||||
const float cvz = dvel[2] * m_params.velBias;
|
||||
const float cs = vmax * 2 * (1 - m_params.velBias) / (float)(m_params.gridSize-1);
|
||||
const float half = (m_params.gridSize-1)*cs*0.5f;
|
||||
|
||||
float minPenalty = FLT_MAX;
|
||||
int ns = 0;
|
||||
|
||||
for (int y = 0; y < m_params.gridSize; ++y)
|
||||
{
|
||||
for (int x = 0; x < m_params.gridSize; ++x)
|
||||
{
|
||||
float vcand[3];
|
||||
vcand[0] = cvx + x*cs - half;
|
||||
vcand[1] = 0;
|
||||
vcand[2] = cvz + y*cs - half;
|
||||
|
||||
if (dtSqr(vcand[0])+dtSqr(vcand[2]) > dtSqr(vmax+cs/2)) continue;
|
||||
|
||||
const float penalty = processSample(vcand, cs, pos,rad,vel,dvel, minPenalty, debug);
|
||||
ns++;
|
||||
if (penalty < minPenalty)
|
||||
{
|
||||
minPenalty = penalty;
|
||||
dtVcopy(nvel, vcand);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ns;
|
||||
}
|
||||
|
||||
|
||||
// vector normalization that ignores the y-component.
|
||||
inline void dtNormalize2D(float* v)
|
||||
{
|
||||
float d = dtMathSqrtf(v[0] * v[0] + v[2] * v[2]);
|
||||
if (d==0)
|
||||
return;
|
||||
d = 1.0f / d;
|
||||
v[0] *= d;
|
||||
v[2] *= d;
|
||||
}
|
||||
|
||||
// vector normalization that ignores the y-component.
|
||||
inline void dtRorate2D(float* dest, const float* v, float ang)
|
||||
{
|
||||
float c = cosf(ang);
|
||||
float s = sinf(ang);
|
||||
dest[0] = v[0]*c - v[2]*s;
|
||||
dest[2] = v[0]*s + v[2]*c;
|
||||
dest[1] = v[1];
|
||||
}
|
||||
|
||||
|
||||
int dtObstacleAvoidanceQuery::sampleVelocityAdaptive(const float* pos, const float rad, const float vmax,
|
||||
const float* vel, const float* dvel, float* nvel,
|
||||
const dtObstacleAvoidanceParams* params,
|
||||
dtObstacleAvoidanceDebugData* debug)
|
||||
{
|
||||
prepare(pos, dvel);
|
||||
|
||||
memcpy(&m_params, params, sizeof(dtObstacleAvoidanceParams));
|
||||
m_invHorizTime = 1.0f / m_params.horizTime;
|
||||
m_vmax = vmax;
|
||||
m_invVmax = vmax > 0 ? 1.0f / vmax : FLT_MAX;
|
||||
|
||||
dtVset(nvel, 0,0,0);
|
||||
|
||||
if (debug)
|
||||
debug->reset();
|
||||
|
||||
// Build sampling pattern aligned to desired velocity.
|
||||
float pat[(DT_MAX_PATTERN_DIVS*DT_MAX_PATTERN_RINGS+1)*2];
|
||||
int npat = 0;
|
||||
|
||||
const int ndivs = (int)m_params.adaptiveDivs;
|
||||
const int nrings= (int)m_params.adaptiveRings;
|
||||
const int depth = (int)m_params.adaptiveDepth;
|
||||
|
||||
const int nd = dtClamp(ndivs, 1, DT_MAX_PATTERN_DIVS);
|
||||
const int nr = dtClamp(nrings, 1, DT_MAX_PATTERN_RINGS);
|
||||
const float da = (1.0f/nd) * DT_PI*2;
|
||||
const float ca = cosf(da);
|
||||
const float sa = sinf(da);
|
||||
|
||||
// desired direction
|
||||
float ddir[6];
|
||||
dtVcopy(ddir, dvel);
|
||||
dtNormalize2D(ddir);
|
||||
dtRorate2D (ddir+3, ddir, da*0.5f); // rotated by da/2
|
||||
|
||||
// Always add sample at zero
|
||||
pat[npat*2+0] = 0;
|
||||
pat[npat*2+1] = 0;
|
||||
npat++;
|
||||
|
||||
for (int j = 0; j < nr; ++j)
|
||||
{
|
||||
const float r = (float)(nr-j)/(float)nr;
|
||||
pat[npat*2+0] = ddir[(j%2)*3] * r;
|
||||
pat[npat*2+1] = ddir[(j%2)*3+2] * r;
|
||||
float* last1 = pat + npat*2;
|
||||
float* last2 = last1;
|
||||
npat++;
|
||||
|
||||
for (int i = 1; i < nd-1; i+=2)
|
||||
{
|
||||
// get next point on the "right" (rotate CW)
|
||||
pat[npat*2+0] = last1[0]*ca + last1[1]*sa;
|
||||
pat[npat*2+1] = -last1[0]*sa + last1[1]*ca;
|
||||
// get next point on the "left" (rotate CCW)
|
||||
pat[npat*2+2] = last2[0]*ca - last2[1]*sa;
|
||||
pat[npat*2+3] = last2[0]*sa + last2[1]*ca;
|
||||
|
||||
last1 = pat + npat*2;
|
||||
last2 = last1 + 2;
|
||||
npat += 2;
|
||||
}
|
||||
|
||||
if ((nd&1) == 0)
|
||||
{
|
||||
pat[npat*2+2] = last2[0]*ca - last2[1]*sa;
|
||||
pat[npat*2+3] = last2[0]*sa + last2[1]*ca;
|
||||
npat++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Start sampling.
|
||||
float cr = vmax * (1.0f - m_params.velBias);
|
||||
float res[3];
|
||||
dtVset(res, dvel[0] * m_params.velBias, 0, dvel[2] * m_params.velBias);
|
||||
int ns = 0;
|
||||
|
||||
for (int k = 0; k < depth; ++k)
|
||||
{
|
||||
float minPenalty = FLT_MAX;
|
||||
float bvel[3];
|
||||
dtVset(bvel, 0,0,0);
|
||||
|
||||
for (int i = 0; i < npat; ++i)
|
||||
{
|
||||
float vcand[3];
|
||||
vcand[0] = res[0] + pat[i*2+0]*cr;
|
||||
vcand[1] = 0;
|
||||
vcand[2] = res[2] + pat[i*2+1]*cr;
|
||||
|
||||
if (dtSqr(vcand[0])+dtSqr(vcand[2]) > dtSqr(vmax+0.001f)) continue;
|
||||
|
||||
const float penalty = processSample(vcand,cr/10, pos,rad,vel,dvel, minPenalty, debug);
|
||||
ns++;
|
||||
if (penalty < minPenalty)
|
||||
{
|
||||
minPenalty = penalty;
|
||||
dtVcopy(bvel, vcand);
|
||||
}
|
||||
}
|
||||
|
||||
dtVcopy(res, bvel);
|
||||
|
||||
cr *= 0.5f;
|
||||
}
|
||||
|
||||
dtVcopy(nvel, res);
|
||||
|
||||
return ns;
|
||||
}
|
||||
@@ -0,0 +1,597 @@
|
||||
//
|
||||
// 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 <string.h>
|
||||
#include "DetourPathCorridor.h"
|
||||
#include "DetourNavMeshQuery.h"
|
||||
#include "DetourCommon.h"
|
||||
#include "DetourAssert.h"
|
||||
#include "DetourAlloc.h"
|
||||
|
||||
|
||||
int dtMergeCorridorStartMoved(dtPolyRef* path, const int npath, const int maxPath,
|
||||
const dtPolyRef* visited, const int nvisited)
|
||||
{
|
||||
int furthestPath = -1;
|
||||
int furthestVisited = -1;
|
||||
|
||||
// Find furthest common polygon.
|
||||
for (int i = npath-1; i >= 0; --i)
|
||||
{
|
||||
bool found = false;
|
||||
for (int j = nvisited-1; j >= 0; --j)
|
||||
{
|
||||
if (path[i] == visited[j])
|
||||
{
|
||||
furthestPath = i;
|
||||
furthestVisited = j;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (found)
|
||||
break;
|
||||
}
|
||||
|
||||
// If no intersection found just return current path.
|
||||
if (furthestPath == -1 || furthestVisited == -1)
|
||||
return npath;
|
||||
|
||||
// Concatenate paths.
|
||||
|
||||
// Adjust beginning of the buffer to include the visited.
|
||||
const int req = nvisited - furthestVisited;
|
||||
const int orig = dtMin(furthestPath+1, npath);
|
||||
int size = dtMax(0, npath-orig);
|
||||
if (req+size > maxPath)
|
||||
size = maxPath-req;
|
||||
if (size > 0)
|
||||
memmove(path+req, path+orig, size*sizeof(dtPolyRef));
|
||||
|
||||
// Store visited
|
||||
for (int i = 0, n = dtMin(req, maxPath); i < n; ++i)
|
||||
path[i] = visited[(nvisited-1)-i];
|
||||
|
||||
return req+size;
|
||||
}
|
||||
|
||||
int dtMergeCorridorEndMoved(dtPolyRef* path, const int npath, const int maxPath,
|
||||
const dtPolyRef* visited, const int nvisited)
|
||||
{
|
||||
int furthestPath = -1;
|
||||
int furthestVisited = -1;
|
||||
|
||||
// Find furthest common polygon.
|
||||
for (int i = 0; i < npath; ++i)
|
||||
{
|
||||
bool found = false;
|
||||
for (int j = nvisited-1; j >= 0; --j)
|
||||
{
|
||||
if (path[i] == visited[j])
|
||||
{
|
||||
furthestPath = i;
|
||||
furthestVisited = j;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (found)
|
||||
break;
|
||||
}
|
||||
|
||||
// If no intersection found just return current path.
|
||||
if (furthestPath == -1 || furthestVisited == -1)
|
||||
return npath;
|
||||
|
||||
// Concatenate paths.
|
||||
const int ppos = furthestPath+1;
|
||||
const int vpos = furthestVisited+1;
|
||||
const int count = dtMin(nvisited-vpos, maxPath-ppos);
|
||||
dtAssert(ppos+count <= maxPath);
|
||||
if (count)
|
||||
memcpy(path+ppos, visited+vpos, sizeof(dtPolyRef)*count);
|
||||
|
||||
return ppos+count;
|
||||
}
|
||||
|
||||
int dtMergeCorridorStartShortcut(dtPolyRef* path, const int npath, const int maxPath,
|
||||
const dtPolyRef* visited, const int nvisited)
|
||||
{
|
||||
int furthestPath = -1;
|
||||
int furthestVisited = -1;
|
||||
|
||||
// Find furthest common polygon.
|
||||
for (int i = npath-1; i >= 0; --i)
|
||||
{
|
||||
bool found = false;
|
||||
for (int j = nvisited-1; j >= 0; --j)
|
||||
{
|
||||
if (path[i] == visited[j])
|
||||
{
|
||||
furthestPath = i;
|
||||
furthestVisited = j;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (found)
|
||||
break;
|
||||
}
|
||||
|
||||
// If no intersection found just return current path.
|
||||
if (furthestPath == -1 || furthestVisited == -1)
|
||||
return npath;
|
||||
|
||||
// Concatenate paths.
|
||||
|
||||
// Adjust beginning of the buffer to include the visited.
|
||||
const int req = furthestVisited;
|
||||
if (req <= 0)
|
||||
return npath;
|
||||
|
||||
const int orig = furthestPath;
|
||||
int size = dtMax(0, npath-orig);
|
||||
if (req+size > maxPath)
|
||||
size = maxPath-req;
|
||||
if (size)
|
||||
memmove(path+req, path+orig, size*sizeof(dtPolyRef));
|
||||
|
||||
// Store visited
|
||||
for (int i = 0; i < req; ++i)
|
||||
path[i] = visited[i];
|
||||
|
||||
return req+size;
|
||||
}
|
||||
|
||||
/**
|
||||
@class dtPathCorridor
|
||||
@par
|
||||
|
||||
The corridor is loaded with a path, usually obtained from a #dtNavMeshQuery::findPath() query. The corridor
|
||||
is then used to plan local movement, with the corridor automatically updating as needed to deal with inaccurate
|
||||
agent locomotion.
|
||||
|
||||
Example of a common use case:
|
||||
|
||||
-# Construct the corridor object and call #init() to allocate its path buffer.
|
||||
-# Obtain a path from a #dtNavMeshQuery object.
|
||||
-# Use #reset() to set the agent's current position. (At the beginning of the path.)
|
||||
-# Use #setCorridor() to load the path and target.
|
||||
-# Use #findCorners() to plan movement. (This handles dynamic path straightening.)
|
||||
-# Use #movePosition() to feed agent movement back into the corridor. (The corridor will automatically adjust as needed.)
|
||||
-# If the target is moving, use #moveTargetPosition() to update the end of the corridor.
|
||||
(The corridor will automatically adjust as needed.)
|
||||
-# Repeat the previous 3 steps to continue to move the agent.
|
||||
|
||||
The corridor position and target are always constrained to the navigation mesh.
|
||||
|
||||
One of the difficulties in maintaining a path is that floating point errors, locomotion inaccuracies, and/or local
|
||||
steering can result in the agent crossing the boundary of the path corridor, temporarily invalidating the path.
|
||||
This class uses local mesh queries to detect and update the corridor as needed to handle these types of issues.
|
||||
|
||||
The fact that local mesh queries are used to move the position and target locations results in two beahviors that
|
||||
need to be considered:
|
||||
|
||||
Every time a move function is used there is a chance that the path will become non-optimial. Basically, the further
|
||||
the target is moved from its original location, and the further the position is moved outside the original corridor,
|
||||
the more likely the path will become non-optimal. This issue can be addressed by periodically running the
|
||||
#optimizePathTopology() and #optimizePathVisibility() methods.
|
||||
|
||||
All local mesh queries have distance limitations. (Review the #dtNavMeshQuery methods for details.) So the most accurate
|
||||
use case is to move the position and target in small increments. If a large increment is used, then the corridor
|
||||
may not be able to accurately find the new location. Because of this limiation, if a position is moved in a large
|
||||
increment, then compare the desired and resulting polygon references. If the two do not match, then path replanning
|
||||
may be needed. E.g. If you move the target, check #getLastPoly() to see if it is the expected polygon.
|
||||
|
||||
*/
|
||||
|
||||
dtPathCorridor::dtPathCorridor() :
|
||||
m_path(0),
|
||||
m_npath(0),
|
||||
m_maxPath(0)
|
||||
{
|
||||
}
|
||||
|
||||
dtPathCorridor::~dtPathCorridor()
|
||||
{
|
||||
dtFree(m_path);
|
||||
}
|
||||
|
||||
/// @par
|
||||
///
|
||||
/// @warning Cannot be called more than once.
|
||||
bool dtPathCorridor::init(const int maxPath)
|
||||
{
|
||||
dtAssert(!m_path);
|
||||
m_path = (dtPolyRef*)dtAlloc(sizeof(dtPolyRef)*maxPath, DT_ALLOC_PERM);
|
||||
if (!m_path)
|
||||
return false;
|
||||
m_npath = 0;
|
||||
m_maxPath = maxPath;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// @par
|
||||
///
|
||||
/// Essentially, the corridor is set of one polygon in size with the target
|
||||
/// equal to the position.
|
||||
void dtPathCorridor::reset(dtPolyRef ref, const float* pos)
|
||||
{
|
||||
dtAssert(m_path);
|
||||
dtVcopy(m_pos, pos);
|
||||
dtVcopy(m_target, pos);
|
||||
m_path[0] = ref;
|
||||
m_npath = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
@par
|
||||
|
||||
This is the function used to plan local movement within the corridor. One or more corners can be
|
||||
detected in order to plan movement. It performs essentially the same function as #dtNavMeshQuery::findStraightPath.
|
||||
|
||||
Due to internal optimizations, the maximum number of corners returned will be (@p maxCorners - 1)
|
||||
For example: If the buffers are sized to hold 10 corners, the function will never return more than 9 corners.
|
||||
So if 10 corners are needed, the buffers should be sized for 11 corners.
|
||||
|
||||
If the target is within range, it will be the last corner and have a polygon reference id of zero.
|
||||
*/
|
||||
int dtPathCorridor::findCorners(float* cornerVerts, unsigned char* cornerFlags,
|
||||
dtPolyRef* cornerPolys, const int maxCorners,
|
||||
dtNavMeshQuery* navquery, const dtQueryFilter* /*filter*/)
|
||||
{
|
||||
dtAssert(m_path);
|
||||
dtAssert(m_npath);
|
||||
|
||||
static const float MIN_TARGET_DIST = 0.01f;
|
||||
|
||||
int ncorners = 0;
|
||||
navquery->findStraightPath(m_pos, m_target, m_path, m_npath,
|
||||
cornerVerts, cornerFlags, cornerPolys, &ncorners, maxCorners);
|
||||
|
||||
// Prune points in the beginning of the path which are too close.
|
||||
while (ncorners)
|
||||
{
|
||||
if ((cornerFlags[0] & DT_STRAIGHTPATH_OFFMESH_CONNECTION) ||
|
||||
dtVdist2DSqr(&cornerVerts[0], m_pos) > dtSqr(MIN_TARGET_DIST))
|
||||
break;
|
||||
ncorners--;
|
||||
if (ncorners)
|
||||
{
|
||||
memmove(cornerFlags, cornerFlags+1, sizeof(unsigned char)*ncorners);
|
||||
memmove(cornerPolys, cornerPolys+1, sizeof(dtPolyRef)*ncorners);
|
||||
memmove(cornerVerts, cornerVerts+3, sizeof(float)*3*ncorners);
|
||||
}
|
||||
}
|
||||
|
||||
// Prune points after an off-mesh connection.
|
||||
for (int i = 0; i < ncorners; ++i)
|
||||
{
|
||||
if (cornerFlags[i] & DT_STRAIGHTPATH_OFFMESH_CONNECTION)
|
||||
{
|
||||
ncorners = i+1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ncorners;
|
||||
}
|
||||
|
||||
/**
|
||||
@par
|
||||
|
||||
Inaccurate locomotion or dynamic obstacle avoidance can force the argent position significantly outside the
|
||||
original corridor. Over time this can result in the formation of a non-optimal corridor. Non-optimal paths can
|
||||
also form near the corners of tiles.
|
||||
|
||||
This function uses an efficient local visibility search to try to optimize the corridor
|
||||
between the current position and @p next.
|
||||
|
||||
The corridor will change only if @p next is visible from the current position and moving directly toward the point
|
||||
is better than following the existing path.
|
||||
|
||||
The more inaccurate the agent movement, the more beneficial this function becomes. Simply adjust the frequency
|
||||
of the call to match the needs to the agent.
|
||||
|
||||
This function is not suitable for long distance searches.
|
||||
*/
|
||||
void dtPathCorridor::optimizePathVisibility(const float* next, const float pathOptimizationRange,
|
||||
dtNavMeshQuery* navquery, const dtQueryFilter* filter)
|
||||
{
|
||||
dtAssert(m_path);
|
||||
|
||||
// Clamp the ray to max distance.
|
||||
float goal[3];
|
||||
dtVcopy(goal, next);
|
||||
float dist = dtVdist2D(m_pos, goal);
|
||||
|
||||
// If too close to the goal, do not try to optimize.
|
||||
if (dist < 0.01f)
|
||||
return;
|
||||
|
||||
// Overshoot a little. This helps to optimize open fields in tiled meshes.
|
||||
dist = dtMin(dist+0.01f, pathOptimizationRange);
|
||||
|
||||
// Adjust ray length.
|
||||
float delta[3];
|
||||
dtVsub(delta, goal, m_pos);
|
||||
dtVmad(goal, m_pos, delta, pathOptimizationRange/dist);
|
||||
|
||||
static const int MAX_RES = 32;
|
||||
dtPolyRef res[MAX_RES];
|
||||
float t, norm[3];
|
||||
int nres = 0;
|
||||
navquery->raycast(m_path[0], m_pos, goal, filter, &t, norm, res, &nres, MAX_RES);
|
||||
if (nres > 1 && t > 0.99f)
|
||||
{
|
||||
m_npath = dtMergeCorridorStartShortcut(m_path, m_npath, m_maxPath, res, nres);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@par
|
||||
|
||||
Inaccurate locomotion or dynamic obstacle avoidance can force the agent position significantly outside the
|
||||
original corridor. Over time this can result in the formation of a non-optimal corridor. This function will use a
|
||||
local area path search to try to re-optimize the corridor.
|
||||
|
||||
The more inaccurate the agent movement, the more beneficial this function becomes. Simply adjust the frequency of
|
||||
the call to match the needs to the agent.
|
||||
*/
|
||||
bool dtPathCorridor::optimizePathTopology(dtNavMeshQuery* navquery, const dtQueryFilter* filter)
|
||||
{
|
||||
dtAssert(navquery);
|
||||
dtAssert(filter);
|
||||
dtAssert(m_path);
|
||||
|
||||
if (m_npath < 3)
|
||||
return false;
|
||||
|
||||
static const int MAX_ITER = 32;
|
||||
static const int MAX_RES = 32;
|
||||
|
||||
dtPolyRef res[MAX_RES];
|
||||
int nres = 0;
|
||||
navquery->initSlicedFindPath(m_path[0], m_path[m_npath-1], m_pos, m_target, filter);
|
||||
navquery->updateSlicedFindPath(MAX_ITER, 0);
|
||||
dtStatus status = navquery->finalizeSlicedFindPathPartial(m_path, m_npath, res, &nres, MAX_RES);
|
||||
|
||||
if (dtStatusSucceed(status) && nres > 0)
|
||||
{
|
||||
m_npath = dtMergeCorridorStartShortcut(m_path, m_npath, m_maxPath, res, nres);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool dtPathCorridor::moveOverOffmeshConnection(dtPolyRef offMeshConRef, dtPolyRef* refs,
|
||||
float* startPos, float* endPos,
|
||||
dtNavMeshQuery* navquery)
|
||||
{
|
||||
dtAssert(navquery);
|
||||
dtAssert(m_path);
|
||||
dtAssert(m_npath);
|
||||
|
||||
// Advance the path up to and over the off-mesh connection.
|
||||
dtPolyRef prevRef = 0, polyRef = m_path[0];
|
||||
int npos = 0;
|
||||
while (npos < m_npath && polyRef != offMeshConRef)
|
||||
{
|
||||
prevRef = polyRef;
|
||||
polyRef = m_path[npos];
|
||||
npos++;
|
||||
}
|
||||
if (npos == m_npath)
|
||||
{
|
||||
// Could not find offMeshConRef
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prune path
|
||||
for (int i = npos; i < m_npath; ++i)
|
||||
m_path[i-npos] = m_path[i];
|
||||
m_npath -= npos;
|
||||
|
||||
refs[0] = prevRef;
|
||||
refs[1] = polyRef;
|
||||
|
||||
const dtNavMesh* nav = navquery->getAttachedNavMesh();
|
||||
dtAssert(nav);
|
||||
|
||||
dtStatus status = nav->getOffMeshConnectionPolyEndPoints(refs[0], refs[1], startPos, endPos);
|
||||
if (dtStatusSucceed(status))
|
||||
{
|
||||
dtVcopy(m_pos, endPos);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@par
|
||||
|
||||
Behavior:
|
||||
|
||||
- The movement is constrained to the surface of the navigation mesh.
|
||||
- The corridor is automatically adjusted (shorted or lengthened) in order to remain valid.
|
||||
- The new position will be located in the adjusted corridor's first polygon.
|
||||
|
||||
The expected use case is that the desired position will be 'near' the current corridor. What is considered 'near'
|
||||
depends on local polygon density, query search half extents, etc.
|
||||
|
||||
The resulting position will differ from the desired position if the desired position is not on the navigation mesh,
|
||||
or it can't be reached using a local search.
|
||||
*/
|
||||
bool dtPathCorridor::movePosition(const float* npos, dtNavMeshQuery* navquery, const dtQueryFilter* filter)
|
||||
{
|
||||
dtAssert(m_path);
|
||||
dtAssert(m_npath);
|
||||
|
||||
// Move along navmesh and update new position.
|
||||
float result[3];
|
||||
static const int MAX_VISITED = 16;
|
||||
dtPolyRef visited[MAX_VISITED];
|
||||
int nvisited = 0;
|
||||
dtStatus status = navquery->moveAlongSurface(m_path[0], m_pos, npos, filter,
|
||||
result, visited, &nvisited, MAX_VISITED);
|
||||
if (dtStatusSucceed(status)) {
|
||||
m_npath = dtMergeCorridorStartMoved(m_path, m_npath, m_maxPath, visited, nvisited);
|
||||
|
||||
// Adjust the position to stay on top of the navmesh.
|
||||
float h = m_pos[1];
|
||||
navquery->getPolyHeight(m_path[0], result, &h);
|
||||
result[1] = h;
|
||||
dtVcopy(m_pos, result);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@par
|
||||
|
||||
Behavior:
|
||||
|
||||
- The movement is constrained to the surface of the navigation mesh.
|
||||
- The corridor is automatically adjusted (shorted or lengthened) in order to remain valid.
|
||||
- The new target will be located in the adjusted corridor's last polygon.
|
||||
|
||||
The expected use case is that the desired target will be 'near' the current corridor. What is considered 'near' depends on local polygon density, query search half extents, etc.
|
||||
|
||||
The resulting target will differ from the desired target if the desired target is not on the navigation mesh, or it can't be reached using a local search.
|
||||
*/
|
||||
bool dtPathCorridor::moveTargetPosition(const float* npos, dtNavMeshQuery* navquery, const dtQueryFilter* filter)
|
||||
{
|
||||
dtAssert(m_path);
|
||||
dtAssert(m_npath);
|
||||
|
||||
// Move along navmesh and update new position.
|
||||
float result[3];
|
||||
static const int MAX_VISITED = 16;
|
||||
dtPolyRef visited[MAX_VISITED];
|
||||
int nvisited = 0;
|
||||
dtStatus status = navquery->moveAlongSurface(m_path[m_npath-1], m_target, npos, filter,
|
||||
result, visited, &nvisited, MAX_VISITED);
|
||||
if (dtStatusSucceed(status))
|
||||
{
|
||||
m_npath = dtMergeCorridorEndMoved(m_path, m_npath, m_maxPath, visited, nvisited);
|
||||
// TODO: should we do that?
|
||||
// Adjust the position to stay on top of the navmesh.
|
||||
/* float h = m_target[1];
|
||||
navquery->getPolyHeight(m_path[m_npath-1], result, &h);
|
||||
result[1] = h;*/
|
||||
|
||||
dtVcopy(m_target, result);
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// @par
|
||||
///
|
||||
/// The current corridor position is expected to be within the first polygon in the path. The target
|
||||
/// is expected to be in the last polygon.
|
||||
///
|
||||
/// @warning The size of the path must not exceed the size of corridor's path buffer set during #init().
|
||||
void dtPathCorridor::setCorridor(const float* target, const dtPolyRef* path, const int npath)
|
||||
{
|
||||
dtAssert(m_path);
|
||||
dtAssert(npath > 0);
|
||||
dtAssert(npath <= m_maxPath);
|
||||
|
||||
dtVcopy(m_target, target);
|
||||
memcpy(m_path, path, sizeof(dtPolyRef)*npath);
|
||||
m_npath = npath;
|
||||
}
|
||||
|
||||
bool dtPathCorridor::fixPathStart(dtPolyRef safeRef, const float* safePos)
|
||||
{
|
||||
dtAssert(m_path);
|
||||
|
||||
dtVcopy(m_pos, safePos);
|
||||
if (m_npath < 3 && m_npath > 0)
|
||||
{
|
||||
m_path[2] = m_path[m_npath-1];
|
||||
m_path[0] = safeRef;
|
||||
m_path[1] = 0;
|
||||
m_npath = 3;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_path[0] = safeRef;
|
||||
m_path[1] = 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool dtPathCorridor::trimInvalidPath(dtPolyRef safeRef, const float* safePos,
|
||||
dtNavMeshQuery* navquery, const dtQueryFilter* filter)
|
||||
{
|
||||
dtAssert(navquery);
|
||||
dtAssert(filter);
|
||||
dtAssert(m_path);
|
||||
|
||||
// Keep valid path as far as possible.
|
||||
int n = 0;
|
||||
while (n < m_npath && navquery->isValidPolyRef(m_path[n], filter)) {
|
||||
n++;
|
||||
}
|
||||
|
||||
if (n == m_npath)
|
||||
{
|
||||
// All valid, no need to fix.
|
||||
return true;
|
||||
}
|
||||
else if (n == 0)
|
||||
{
|
||||
// The first polyref is bad, use current safe values.
|
||||
dtVcopy(m_pos, safePos);
|
||||
m_path[0] = safeRef;
|
||||
m_npath = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// The path is partially usable.
|
||||
m_npath = n;
|
||||
}
|
||||
|
||||
// Clamp target pos to last poly
|
||||
float tgt[3];
|
||||
dtVcopy(tgt, m_target);
|
||||
navquery->closestPointOnPolyBoundary(m_path[m_npath-1], tgt, m_target);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// @par
|
||||
///
|
||||
/// The path can be invalidated if there are structural changes to the underlying navigation mesh, or the state of
|
||||
/// a polygon within the path changes resulting in it being filtered out. (E.g. An exclusion or inclusion flag changes.)
|
||||
bool dtPathCorridor::isValid(const int maxLookAhead, dtNavMeshQuery* navquery, const dtQueryFilter* filter)
|
||||
{
|
||||
// Check that all polygons still pass query filter.
|
||||
const int n = dtMin(m_npath, maxLookAhead);
|
||||
for (int i = 0; i < n; ++i)
|
||||
{
|
||||
if (!navquery->isValidPolyRef(m_path[i], filter))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
//
|
||||
// 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 <string.h>
|
||||
#include "DetourPathQueue.h"
|
||||
#include "DetourNavMesh.h"
|
||||
#include "DetourNavMeshQuery.h"
|
||||
#include "DetourAlloc.h"
|
||||
#include "DetourCommon.h"
|
||||
|
||||
|
||||
dtPathQueue::dtPathQueue() :
|
||||
m_nextHandle(1),
|
||||
m_maxPathSize(0),
|
||||
m_queueHead(0),
|
||||
m_navquery(0)
|
||||
{
|
||||
for (int i = 0; i < MAX_QUEUE; ++i)
|
||||
m_queue[i].path = 0;
|
||||
}
|
||||
|
||||
dtPathQueue::~dtPathQueue()
|
||||
{
|
||||
purge();
|
||||
}
|
||||
|
||||
void dtPathQueue::purge()
|
||||
{
|
||||
dtFreeNavMeshQuery(m_navquery);
|
||||
m_navquery = 0;
|
||||
for (int i = 0; i < MAX_QUEUE; ++i)
|
||||
{
|
||||
dtFree(m_queue[i].path);
|
||||
m_queue[i].path = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool dtPathQueue::init(const int maxPathSize, const int maxSearchNodeCount, dtNavMesh* nav)
|
||||
{
|
||||
purge();
|
||||
|
||||
m_navquery = dtAllocNavMeshQuery();
|
||||
if (!m_navquery)
|
||||
return false;
|
||||
if (dtStatusFailed(m_navquery->init(nav, maxSearchNodeCount)))
|
||||
return false;
|
||||
|
||||
m_maxPathSize = maxPathSize;
|
||||
for (int i = 0; i < MAX_QUEUE; ++i)
|
||||
{
|
||||
m_queue[i].ref = DT_PATHQ_INVALID;
|
||||
m_queue[i].path = (dtPolyRef*)dtAlloc(sizeof(dtPolyRef)*m_maxPathSize, DT_ALLOC_PERM);
|
||||
if (!m_queue[i].path)
|
||||
return false;
|
||||
}
|
||||
|
||||
m_queueHead = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void dtPathQueue::update(const int maxIters)
|
||||
{
|
||||
static const int MAX_KEEP_ALIVE = 2; // in update ticks.
|
||||
|
||||
// Update path request until there is nothing to update
|
||||
// or upto maxIters pathfinder iterations has been consumed.
|
||||
int iterCount = maxIters;
|
||||
|
||||
for (int i = 0; i < MAX_QUEUE; ++i)
|
||||
{
|
||||
PathQuery& q = m_queue[m_queueHead % MAX_QUEUE];
|
||||
|
||||
// Skip inactive requests.
|
||||
if (q.ref == DT_PATHQ_INVALID)
|
||||
{
|
||||
m_queueHead++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle completed request.
|
||||
if (dtStatusSucceed(q.status) || dtStatusFailed(q.status))
|
||||
{
|
||||
// If the path result has not been read in few frames, free the slot.
|
||||
q.keepAlive++;
|
||||
if (q.keepAlive > MAX_KEEP_ALIVE)
|
||||
{
|
||||
q.ref = DT_PATHQ_INVALID;
|
||||
q.status = 0;
|
||||
}
|
||||
|
||||
m_queueHead++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle query start.
|
||||
if (q.status == 0)
|
||||
{
|
||||
q.status = m_navquery->initSlicedFindPath(q.startRef, q.endRef, q.startPos, q.endPos, q.filter);
|
||||
}
|
||||
// Handle query in progress.
|
||||
if (dtStatusInProgress(q.status))
|
||||
{
|
||||
int iters = 0;
|
||||
q.status = m_navquery->updateSlicedFindPath(iterCount, &iters);
|
||||
iterCount -= iters;
|
||||
}
|
||||
if (dtStatusSucceed(q.status))
|
||||
{
|
||||
q.status = m_navquery->finalizeSlicedFindPath(q.path, &q.npath, m_maxPathSize);
|
||||
}
|
||||
|
||||
if (iterCount <= 0)
|
||||
break;
|
||||
|
||||
m_queueHead++;
|
||||
}
|
||||
}
|
||||
|
||||
dtPathQueueRef dtPathQueue::request(dtPolyRef startRef, dtPolyRef endRef,
|
||||
const float* startPos, const float* endPos,
|
||||
const dtQueryFilter* filter)
|
||||
{
|
||||
// Find empty slot
|
||||
int slot = -1;
|
||||
for (int i = 0; i < MAX_QUEUE; ++i)
|
||||
{
|
||||
if (m_queue[i].ref == DT_PATHQ_INVALID)
|
||||
{
|
||||
slot = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Could not find slot.
|
||||
if (slot == -1)
|
||||
return DT_PATHQ_INVALID;
|
||||
|
||||
dtPathQueueRef ref = m_nextHandle++;
|
||||
if (m_nextHandle == DT_PATHQ_INVALID) m_nextHandle++;
|
||||
|
||||
PathQuery& q = m_queue[slot];
|
||||
q.ref = ref;
|
||||
dtVcopy(q.startPos, startPos);
|
||||
q.startRef = startRef;
|
||||
dtVcopy(q.endPos, endPos);
|
||||
q.endRef = endRef;
|
||||
|
||||
q.status = 0;
|
||||
q.npath = 0;
|
||||
q.filter = filter;
|
||||
q.keepAlive = 0;
|
||||
|
||||
return ref;
|
||||
}
|
||||
|
||||
dtStatus dtPathQueue::getRequestStatus(dtPathQueueRef ref) const
|
||||
{
|
||||
for (int i = 0; i < MAX_QUEUE; ++i)
|
||||
{
|
||||
if (m_queue[i].ref == ref)
|
||||
return m_queue[i].status;
|
||||
}
|
||||
return DT_FAILURE;
|
||||
}
|
||||
|
||||
dtStatus dtPathQueue::getPathResult(dtPathQueueRef ref, dtPolyRef* path, int* pathSize, const int maxPath)
|
||||
{
|
||||
for (int i = 0; i < MAX_QUEUE; ++i)
|
||||
{
|
||||
if (m_queue[i].ref == ref)
|
||||
{
|
||||
PathQuery& q = m_queue[i];
|
||||
dtStatus details = q.status & DT_STATUS_DETAIL_MASK;
|
||||
// Free request for reuse.
|
||||
q.ref = DT_PATHQ_INVALID;
|
||||
q.status = 0;
|
||||
// Copy path
|
||||
int n = dtMin(q.npath, maxPath);
|
||||
memcpy(path, q.path, sizeof(dtPolyRef)*n);
|
||||
*pathSize = n;
|
||||
return details | DT_SUCCESS;
|
||||
}
|
||||
}
|
||||
return DT_FAILURE;
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
//
|
||||
// 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 <string.h>
|
||||
#include <new>
|
||||
#include "DetourProximityGrid.h"
|
||||
#include "DetourCommon.h"
|
||||
#include "DetourMath.h"
|
||||
#include "DetourAlloc.h"
|
||||
#include "DetourAssert.h"
|
||||
|
||||
|
||||
dtProximityGrid* dtAllocProximityGrid()
|
||||
{
|
||||
void* mem = dtAlloc(sizeof(dtProximityGrid), DT_ALLOC_PERM);
|
||||
if (!mem) return 0;
|
||||
return new(mem) dtProximityGrid;
|
||||
}
|
||||
|
||||
void dtFreeProximityGrid(dtProximityGrid* ptr)
|
||||
{
|
||||
if (!ptr) return;
|
||||
ptr->~dtProximityGrid();
|
||||
dtFree(ptr);
|
||||
}
|
||||
|
||||
|
||||
inline int hashPos2(int x, int y, int n)
|
||||
{
|
||||
return ((x*73856093) ^ (y*19349663)) & (n-1);
|
||||
}
|
||||
|
||||
|
||||
dtProximityGrid::dtProximityGrid() :
|
||||
m_cellSize(0),
|
||||
m_invCellSize(0),
|
||||
m_pool(0),
|
||||
m_poolHead(0),
|
||||
m_poolSize(0),
|
||||
m_buckets(0),
|
||||
m_bucketsSize(0)
|
||||
{
|
||||
}
|
||||
|
||||
dtProximityGrid::~dtProximityGrid()
|
||||
{
|
||||
dtFree(m_buckets);
|
||||
dtFree(m_pool);
|
||||
}
|
||||
|
||||
bool dtProximityGrid::init(const int poolSize, const float cellSize)
|
||||
{
|
||||
dtAssert(poolSize > 0);
|
||||
dtAssert(cellSize > 0.0f);
|
||||
|
||||
m_cellSize = cellSize;
|
||||
m_invCellSize = 1.0f / m_cellSize;
|
||||
|
||||
// Allocate hashs buckets
|
||||
m_bucketsSize = dtNextPow2(poolSize);
|
||||
m_buckets = (unsigned short*)dtAlloc(sizeof(unsigned short)*m_bucketsSize, DT_ALLOC_PERM);
|
||||
if (!m_buckets)
|
||||
return false;
|
||||
|
||||
// Allocate pool of items.
|
||||
m_poolSize = poolSize;
|
||||
m_poolHead = 0;
|
||||
m_pool = (Item*)dtAlloc(sizeof(Item)*m_poolSize, DT_ALLOC_PERM);
|
||||
if (!m_pool)
|
||||
return false;
|
||||
|
||||
clear();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void dtProximityGrid::clear()
|
||||
{
|
||||
memset(m_buckets, 0xff, sizeof(unsigned short)*m_bucketsSize);
|
||||
m_poolHead = 0;
|
||||
m_bounds[0] = 0xffff;
|
||||
m_bounds[1] = 0xffff;
|
||||
m_bounds[2] = -0xffff;
|
||||
m_bounds[3] = -0xffff;
|
||||
}
|
||||
|
||||
void dtProximityGrid::addItem(const unsigned short id,
|
||||
const float minx, const float miny,
|
||||
const float maxx, const float maxy)
|
||||
{
|
||||
const int iminx = (int)dtMathFloorf(minx * m_invCellSize);
|
||||
const int iminy = (int)dtMathFloorf(miny * m_invCellSize);
|
||||
const int imaxx = (int)dtMathFloorf(maxx * m_invCellSize);
|
||||
const int imaxy = (int)dtMathFloorf(maxy * m_invCellSize);
|
||||
|
||||
m_bounds[0] = dtMin(m_bounds[0], iminx);
|
||||
m_bounds[1] = dtMin(m_bounds[1], iminy);
|
||||
m_bounds[2] = dtMax(m_bounds[2], imaxx);
|
||||
m_bounds[3] = dtMax(m_bounds[3], imaxy);
|
||||
|
||||
for (int y = iminy; y <= imaxy; ++y)
|
||||
{
|
||||
for (int x = iminx; x <= imaxx; ++x)
|
||||
{
|
||||
if (m_poolHead < m_poolSize)
|
||||
{
|
||||
const int h = hashPos2(x, y, m_bucketsSize);
|
||||
const unsigned short idx = (unsigned short)m_poolHead;
|
||||
m_poolHead++;
|
||||
Item& item = m_pool[idx];
|
||||
item.x = (short)x;
|
||||
item.y = (short)y;
|
||||
item.id = id;
|
||||
item.next = m_buckets[h];
|
||||
m_buckets[h] = idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int dtProximityGrid::queryItems(const float minx, const float miny,
|
||||
const float maxx, const float maxy,
|
||||
unsigned short* ids, const int maxIds) const
|
||||
{
|
||||
const int iminx = (int)dtMathFloorf(minx * m_invCellSize);
|
||||
const int iminy = (int)dtMathFloorf(miny * m_invCellSize);
|
||||
const int imaxx = (int)dtMathFloorf(maxx * m_invCellSize);
|
||||
const int imaxy = (int)dtMathFloorf(maxy * m_invCellSize);
|
||||
|
||||
int n = 0;
|
||||
|
||||
for (int y = iminy; y <= imaxy; ++y)
|
||||
{
|
||||
for (int x = iminx; x <= imaxx; ++x)
|
||||
{
|
||||
const int h = hashPos2(x, y, m_bucketsSize);
|
||||
unsigned short idx = m_buckets[h];
|
||||
while (idx != 0xffff)
|
||||
{
|
||||
Item& item = m_pool[idx];
|
||||
if ((int)item.x == x && (int)item.y == y)
|
||||
{
|
||||
// Check if the id exists already.
|
||||
const unsigned short* end = ids + n;
|
||||
unsigned short* i = ids;
|
||||
while (i != end && *i != item.id)
|
||||
++i;
|
||||
// Item not found, add it.
|
||||
if (i == end)
|
||||
{
|
||||
if (n >= maxIds)
|
||||
return n;
|
||||
ids[n++] = item.id;
|
||||
}
|
||||
}
|
||||
idx = item.next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
int dtProximityGrid::getItemCountAt(const int x, const int y) const
|
||||
{
|
||||
int n = 0;
|
||||
|
||||
const int h = hashPos2(x, y, m_bucketsSize);
|
||||
unsigned short idx = m_buckets[h];
|
||||
while (idx != 0xffff)
|
||||
{
|
||||
Item& item = m_pool[idx];
|
||||
if ((int)item.x == x && (int)item.y == y)
|
||||
n++;
|
||||
idx = item.next;
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
add_library(DetourTileCache)
|
||||
add_library(RecastNavigation::DetourTileCache ALIAS DetourTileCache)
|
||||
|
||||
set_target_properties(DetourTileCache PROPERTIES
|
||||
DEBUG_POSTFIX -d
|
||||
SOVERSION ${SOVERSION}
|
||||
VERSION ${LIB_VERSION}
|
||||
COMPILE_PDB_OUTPUT_DIRECTORY .
|
||||
COMPILE_PDB_NAME "DetourTileCache-d"
|
||||
CXX_STANDARD 98
|
||||
CXX_STANDARD_REQUIRED ON
|
||||
CXX_EXTENSIONS OFF # Disable compiler-specific extensions
|
||||
)
|
||||
|
||||
target_include_directories(DetourTileCache PUBLIC
|
||||
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/Include>"
|
||||
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/recastnavigation>"
|
||||
)
|
||||
|
||||
target_sources(DetourTileCache PRIVATE
|
||||
Source/DetourTileCache.cpp
|
||||
Source/DetourTileCacheBuilder.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(DetourTileCache Detour)
|
||||
|
||||
install(TARGETS DetourTileCache
|
||||
EXPORT recastnavigation-targets
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT library
|
||||
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} ${CMAKE_INSTALL_INCLUDEDIR}/recastnavigation
|
||||
)
|
||||
|
||||
install(DIRECTORY Include/
|
||||
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/recastnavigation
|
||||
FILES_MATCHING PATTERN "*.h"
|
||||
)
|
||||
|
||||
if (MSVC)
|
||||
install(FILES "$<TARGET_FILE_DIR:DetourTileCache>/DetourTileCache-d.pdb" CONFIGURATIONS "Debug" DESTINATION "lib" OPTIONAL)
|
||||
endif()
|
||||
@@ -0,0 +1,256 @@
|
||||
#ifndef DETOURTILECACHE_H
|
||||
#define DETOURTILECACHE_H
|
||||
|
||||
#include "DetourStatus.h"
|
||||
|
||||
typedef unsigned int dtObstacleRef;
|
||||
typedef unsigned int dtCompressedTileRef;
|
||||
|
||||
/// Flags for addTile
|
||||
enum dtCompressedTileFlags
|
||||
{
|
||||
DT_COMPRESSEDTILE_FREE_DATA = 0x01 ///< Navmesh owns the tile memory and should free it.
|
||||
};
|
||||
|
||||
struct dtCompressedTile
|
||||
{
|
||||
unsigned int salt; ///< Counter describing modifications to the tile.
|
||||
struct dtTileCacheLayerHeader* header;
|
||||
unsigned char* compressed;
|
||||
int compressedSize;
|
||||
unsigned char* data;
|
||||
int dataSize;
|
||||
unsigned int flags;
|
||||
dtCompressedTile* next;
|
||||
};
|
||||
|
||||
enum ObstacleState
|
||||
{
|
||||
DT_OBSTACLE_EMPTY,
|
||||
DT_OBSTACLE_PROCESSING,
|
||||
DT_OBSTACLE_PROCESSED,
|
||||
DT_OBSTACLE_REMOVING
|
||||
};
|
||||
|
||||
enum ObstacleType
|
||||
{
|
||||
DT_OBSTACLE_CYLINDER,
|
||||
DT_OBSTACLE_BOX, // AABB
|
||||
DT_OBSTACLE_ORIENTED_BOX // OBB
|
||||
};
|
||||
|
||||
struct dtObstacleCylinder
|
||||
{
|
||||
float pos[ 3 ];
|
||||
float radius;
|
||||
float height;
|
||||
};
|
||||
|
||||
struct dtObstacleBox
|
||||
{
|
||||
float bmin[ 3 ];
|
||||
float bmax[ 3 ];
|
||||
};
|
||||
|
||||
struct dtObstacleOrientedBox
|
||||
{
|
||||
float center[ 3 ];
|
||||
float halfExtents[ 3 ];
|
||||
float rotAux[ 2 ]; //{ cos(0.5f*angle)*sin(-0.5f*angle); cos(0.5f*angle)*cos(0.5f*angle) - 0.5 }
|
||||
};
|
||||
|
||||
static const int DT_MAX_TOUCHED_TILES = 8;
|
||||
struct dtTileCacheObstacle
|
||||
{
|
||||
union
|
||||
{
|
||||
dtObstacleCylinder cylinder;
|
||||
dtObstacleBox box;
|
||||
dtObstacleOrientedBox orientedBox;
|
||||
};
|
||||
|
||||
dtCompressedTileRef touched[DT_MAX_TOUCHED_TILES];
|
||||
dtCompressedTileRef pending[DT_MAX_TOUCHED_TILES];
|
||||
unsigned short salt;
|
||||
unsigned char type;
|
||||
unsigned char state;
|
||||
unsigned char ntouched;
|
||||
unsigned char npending;
|
||||
dtTileCacheObstacle* next;
|
||||
};
|
||||
|
||||
struct dtTileCacheParams
|
||||
{
|
||||
float orig[3];
|
||||
float cs, ch;
|
||||
int width, height;
|
||||
float walkableHeight;
|
||||
float walkableRadius;
|
||||
float walkableClimb;
|
||||
float maxSimplificationError;
|
||||
int maxTiles;
|
||||
int maxObstacles;
|
||||
};
|
||||
|
||||
struct dtTileCacheMeshProcess
|
||||
{
|
||||
virtual ~dtTileCacheMeshProcess();
|
||||
virtual void process(struct dtNavMeshCreateParams* params, unsigned char* polyAreas, unsigned short* polyFlags) = 0;
|
||||
};
|
||||
|
||||
class dtTileCache
|
||||
{
|
||||
public:
|
||||
dtTileCache();
|
||||
~dtTileCache();
|
||||
|
||||
struct dtTileCacheAlloc* getAlloc() { return m_talloc; }
|
||||
struct dtTileCacheCompressor* getCompressor() { return m_tcomp; }
|
||||
const dtTileCacheParams* getParams() const { return &m_params; }
|
||||
|
||||
inline int getTileCount() const { return m_params.maxTiles; }
|
||||
inline const dtCompressedTile* getTile(const int i) const { return &m_tiles[i]; }
|
||||
|
||||
inline int getObstacleCount() const { return m_params.maxObstacles; }
|
||||
inline const dtTileCacheObstacle* getObstacle(const int i) const { return &m_obstacles[i]; }
|
||||
|
||||
const dtTileCacheObstacle* getObstacleByRef(dtObstacleRef ref);
|
||||
|
||||
dtObstacleRef getObstacleRef(const dtTileCacheObstacle* obmin) const;
|
||||
|
||||
dtStatus init(const dtTileCacheParams* params,
|
||||
struct dtTileCacheAlloc* talloc,
|
||||
struct dtTileCacheCompressor* tcomp,
|
||||
struct dtTileCacheMeshProcess* tmproc);
|
||||
|
||||
int getTilesAt(const int tx, const int ty, dtCompressedTileRef* tiles, const int maxTiles) const ;
|
||||
|
||||
dtCompressedTile* getTileAt(const int tx, const int ty, const int tlayer);
|
||||
dtCompressedTileRef getTileRef(const dtCompressedTile* tile) const;
|
||||
const dtCompressedTile* getTileByRef(dtCompressedTileRef ref) const;
|
||||
|
||||
dtStatus addTile(unsigned char* data, const int dataSize, unsigned char flags, dtCompressedTileRef* result);
|
||||
|
||||
dtStatus removeTile(dtCompressedTileRef ref, unsigned char** data, int* dataSize);
|
||||
|
||||
// Cylinder obstacle.
|
||||
dtStatus addObstacle(const float* pos, const float radius, const float height, dtObstacleRef* result);
|
||||
|
||||
// Aabb obstacle.
|
||||
dtStatus addBoxObstacle(const float* bmin, const float* bmax, dtObstacleRef* result);
|
||||
|
||||
// Box obstacle: can be rotated in Y.
|
||||
dtStatus addBoxObstacle(const float* center, const float* halfExtents, const float yRadians, dtObstacleRef* result);
|
||||
|
||||
dtStatus removeObstacle(const dtObstacleRef ref);
|
||||
|
||||
dtStatus queryTiles(const float* bmin, const float* bmax,
|
||||
dtCompressedTileRef* results, int* resultCount, const int maxResults) const;
|
||||
|
||||
/// Updates the tile cache by rebuilding tiles touched by unfinished obstacle requests.
|
||||
/// @param[in] dt The time step size. Currently not used.
|
||||
/// @param[in] navmesh The mesh to affect when rebuilding tiles.
|
||||
/// @param[out] upToDate Whether the tile cache is fully up to date with obstacle requests and tile rebuilds.
|
||||
/// If the tile cache is up to date another (immediate) call to update will have no effect;
|
||||
/// otherwise another call will continue processing obstacle requests and tile rebuilds.
|
||||
dtStatus update(const float dt, class dtNavMesh* navmesh, bool* upToDate = 0);
|
||||
|
||||
dtStatus buildNavMeshTilesAt(const int tx, const int ty, class dtNavMesh* navmesh);
|
||||
|
||||
dtStatus buildNavMeshTile(const dtCompressedTileRef ref, class dtNavMesh* navmesh);
|
||||
|
||||
void calcTightTileBounds(const struct dtTileCacheLayerHeader* header, float* bmin, float* bmax) const;
|
||||
|
||||
void getObstacleBounds(const struct dtTileCacheObstacle* ob, float* bmin, float* bmax) const;
|
||||
|
||||
|
||||
/// Encodes a tile id.
|
||||
inline dtCompressedTileRef encodeTileId(unsigned int salt, unsigned int it) const
|
||||
{
|
||||
return ((dtCompressedTileRef)salt << m_tileBits) | (dtCompressedTileRef)it;
|
||||
}
|
||||
|
||||
/// Decodes a tile salt.
|
||||
inline unsigned int decodeTileIdSalt(dtCompressedTileRef ref) const
|
||||
{
|
||||
const dtCompressedTileRef saltMask = ((dtCompressedTileRef)1<<m_saltBits)-1;
|
||||
return (unsigned int)((ref >> m_tileBits) & saltMask);
|
||||
}
|
||||
|
||||
/// Decodes a tile id.
|
||||
inline unsigned int decodeTileIdTile(dtCompressedTileRef ref) const
|
||||
{
|
||||
const dtCompressedTileRef tileMask = ((dtCompressedTileRef)1<<m_tileBits)-1;
|
||||
return (unsigned int)(ref & tileMask);
|
||||
}
|
||||
|
||||
/// Encodes an obstacle id.
|
||||
inline dtObstacleRef encodeObstacleId(unsigned int salt, unsigned int it) const
|
||||
{
|
||||
return ((dtObstacleRef)salt << 16) | (dtObstacleRef)it;
|
||||
}
|
||||
|
||||
/// Decodes an obstacle salt.
|
||||
inline unsigned int decodeObstacleIdSalt(dtObstacleRef ref) const
|
||||
{
|
||||
const dtObstacleRef saltMask = ((dtObstacleRef)1<<16)-1;
|
||||
return (unsigned int)((ref >> 16) & saltMask);
|
||||
}
|
||||
|
||||
/// Decodes an obstacle id.
|
||||
inline unsigned int decodeObstacleIdObstacle(dtObstacleRef ref) const
|
||||
{
|
||||
const dtObstacleRef tileMask = ((dtObstacleRef)1<<16)-1;
|
||||
return (unsigned int)(ref & tileMask);
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
// Explicitly disabled copy constructor and copy assignment operator.
|
||||
dtTileCache(const dtTileCache&);
|
||||
dtTileCache& operator=(const dtTileCache&);
|
||||
|
||||
enum ObstacleRequestAction
|
||||
{
|
||||
REQUEST_ADD,
|
||||
REQUEST_REMOVE
|
||||
};
|
||||
|
||||
struct ObstacleRequest
|
||||
{
|
||||
int action;
|
||||
dtObstacleRef ref;
|
||||
};
|
||||
|
||||
int m_tileLutSize; ///< Tile hash lookup size (must be pot).
|
||||
int m_tileLutMask; ///< Tile hash lookup mask.
|
||||
|
||||
dtCompressedTile** m_posLookup; ///< Tile hash lookup.
|
||||
dtCompressedTile* m_nextFreeTile; ///< Freelist of tiles.
|
||||
dtCompressedTile* m_tiles; ///< List of tiles.
|
||||
|
||||
unsigned int m_saltBits; ///< Number of salt bits in the tile ID.
|
||||
unsigned int m_tileBits; ///< Number of tile bits in the tile ID.
|
||||
|
||||
dtTileCacheParams m_params;
|
||||
|
||||
dtTileCacheAlloc* m_talloc;
|
||||
dtTileCacheCompressor* m_tcomp;
|
||||
dtTileCacheMeshProcess* m_tmproc;
|
||||
|
||||
dtTileCacheObstacle* m_obstacles;
|
||||
dtTileCacheObstacle* m_nextFreeObstacle;
|
||||
|
||||
static const int MAX_REQUESTS = 64;
|
||||
ObstacleRequest m_reqs[MAX_REQUESTS];
|
||||
int m_nreqs;
|
||||
|
||||
static const int MAX_UPDATE = 64;
|
||||
dtCompressedTileRef m_update[MAX_UPDATE];
|
||||
int m_nupdate;
|
||||
};
|
||||
|
||||
dtTileCache* dtAllocTileCache();
|
||||
void dtFreeTileCache(dtTileCache* tc);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,156 @@
|
||||
//
|
||||
// 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 DETOURTILECACHEBUILDER_H
|
||||
#define DETOURTILECACHEBUILDER_H
|
||||
|
||||
#include "DetourAlloc.h"
|
||||
#include "DetourStatus.h"
|
||||
|
||||
static const int DT_TILECACHE_MAGIC = 'D'<<24 | 'T'<<16 | 'L'<<8 | 'R'; ///< 'DTLR';
|
||||
static const int DT_TILECACHE_VERSION = 1;
|
||||
|
||||
static const unsigned char DT_TILECACHE_NULL_AREA = 0;
|
||||
static const unsigned char DT_TILECACHE_WALKABLE_AREA = 63;
|
||||
static const unsigned short DT_TILECACHE_NULL_IDX = 0xffff;
|
||||
|
||||
struct dtTileCacheLayerHeader
|
||||
{
|
||||
int magic; ///< Data magic
|
||||
int version; ///< Data version
|
||||
int tx,ty,tlayer;
|
||||
float bmin[3], bmax[3];
|
||||
unsigned short hmin, hmax; ///< Height min/max range
|
||||
unsigned char width, height; ///< Dimension of the layer.
|
||||
unsigned char minx, maxx, miny, maxy; ///< Usable sub-region.
|
||||
};
|
||||
|
||||
struct dtTileCacheLayer
|
||||
{
|
||||
dtTileCacheLayerHeader* header;
|
||||
unsigned char regCount; ///< Region count.
|
||||
unsigned char* heights;
|
||||
unsigned char* areas;
|
||||
unsigned char* cons;
|
||||
unsigned char* regs;
|
||||
};
|
||||
|
||||
struct dtTileCacheContour
|
||||
{
|
||||
int nverts;
|
||||
unsigned char* verts;
|
||||
unsigned char reg;
|
||||
unsigned char area;
|
||||
};
|
||||
|
||||
struct dtTileCacheContourSet
|
||||
{
|
||||
int nconts;
|
||||
dtTileCacheContour* conts;
|
||||
};
|
||||
|
||||
struct dtTileCachePolyMesh
|
||||
{
|
||||
int nvp;
|
||||
int nverts; ///< Number of vertices.
|
||||
int npolys; ///< Number of polygons.
|
||||
unsigned short* verts; ///< Vertices of the mesh, 3 elements per vertex.
|
||||
unsigned short* polys; ///< Polygons of the mesh, nvp*2 elements per polygon.
|
||||
unsigned short* flags; ///< Per polygon flags.
|
||||
unsigned char* areas; ///< Area ID of polygons.
|
||||
};
|
||||
|
||||
|
||||
struct dtTileCacheAlloc
|
||||
{
|
||||
virtual ~dtTileCacheAlloc();
|
||||
|
||||
virtual void reset() {}
|
||||
|
||||
virtual void* alloc(const size_t size)
|
||||
{
|
||||
return dtAlloc(size, DT_ALLOC_TEMP);
|
||||
}
|
||||
|
||||
virtual void free(void* ptr)
|
||||
{
|
||||
dtFree(ptr);
|
||||
}
|
||||
};
|
||||
|
||||
struct dtTileCacheCompressor
|
||||
{
|
||||
virtual ~dtTileCacheCompressor();
|
||||
|
||||
virtual int maxCompressedSize(const int bufferSize) = 0;
|
||||
virtual dtStatus compress(const unsigned char* buffer, const int bufferSize,
|
||||
unsigned char* compressed, const int maxCompressedSize, int* compressedSize) = 0;
|
||||
virtual dtStatus decompress(const unsigned char* compressed, const int compressedSize,
|
||||
unsigned char* buffer, const int maxBufferSize, int* bufferSize) = 0;
|
||||
};
|
||||
|
||||
|
||||
dtStatus dtBuildTileCacheLayer(dtTileCacheCompressor* comp,
|
||||
dtTileCacheLayerHeader* header,
|
||||
const unsigned char* heights,
|
||||
const unsigned char* areas,
|
||||
const unsigned char* cons,
|
||||
unsigned char** outData, int* outDataSize);
|
||||
|
||||
void dtFreeTileCacheLayer(dtTileCacheAlloc* alloc, dtTileCacheLayer* layer);
|
||||
|
||||
dtStatus dtDecompressTileCacheLayer(dtTileCacheAlloc* alloc, dtTileCacheCompressor* comp,
|
||||
unsigned char* compressed, const int compressedSize,
|
||||
dtTileCacheLayer** layerOut);
|
||||
|
||||
dtTileCacheContourSet* dtAllocTileCacheContourSet(dtTileCacheAlloc* alloc);
|
||||
void dtFreeTileCacheContourSet(dtTileCacheAlloc* alloc, dtTileCacheContourSet* cset);
|
||||
|
||||
dtTileCachePolyMesh* dtAllocTileCachePolyMesh(dtTileCacheAlloc* alloc);
|
||||
void dtFreeTileCachePolyMesh(dtTileCacheAlloc* alloc, dtTileCachePolyMesh* lmesh);
|
||||
|
||||
dtStatus dtMarkCylinderArea(dtTileCacheLayer& layer, const float* orig, const float cs, const float ch,
|
||||
const float* pos, const float radius, const float height, const unsigned char areaId);
|
||||
|
||||
dtStatus dtMarkBoxArea(dtTileCacheLayer& layer, const float* orig, const float cs, const float ch,
|
||||
const float* bmin, const float* bmax, const unsigned char areaId);
|
||||
|
||||
dtStatus dtMarkBoxArea(dtTileCacheLayer& layer, const float* orig, const float cs, const float ch,
|
||||
const float* center, const float* halfExtents, const float* rotAux, const unsigned char areaId);
|
||||
|
||||
dtStatus dtBuildTileCacheRegions(dtTileCacheAlloc* alloc,
|
||||
dtTileCacheLayer& layer,
|
||||
const int walkableClimb);
|
||||
|
||||
dtStatus dtBuildTileCacheContours(dtTileCacheAlloc* alloc,
|
||||
dtTileCacheLayer& layer,
|
||||
const int walkableClimb, const float maxError,
|
||||
dtTileCacheContourSet& lcset);
|
||||
|
||||
dtStatus dtBuildTileCachePolyMesh(dtTileCacheAlloc* alloc,
|
||||
dtTileCacheContourSet& lcset,
|
||||
dtTileCachePolyMesh& mesh);
|
||||
|
||||
/// Swaps the endianness of the compressed tile data's header (#dtTileCacheLayerHeader).
|
||||
/// Tile layer data does not need endian swapping as it consist only of bytes.
|
||||
/// @param[in,out] data The tile data array.
|
||||
/// @param[in] dataSize The size of the data array.
|
||||
bool dtTileCacheHeaderSwapEndian(unsigned char* data, const int dataSize);
|
||||
|
||||
|
||||
#endif // DETOURTILECACHEBUILDER_H
|
||||
@@ -0,0 +1,825 @@
|
||||
#include "DetourTileCache.h"
|
||||
#include "DetourTileCacheBuilder.h"
|
||||
#include "DetourNavMeshBuilder.h"
|
||||
#include "DetourNavMesh.h"
|
||||
#include "DetourCommon.h"
|
||||
#include "DetourMath.h"
|
||||
#include "DetourAlloc.h"
|
||||
#include "DetourAssert.h"
|
||||
#include <string.h>
|
||||
#include <new>
|
||||
|
||||
dtTileCache* dtAllocTileCache()
|
||||
{
|
||||
void* mem = dtAlloc(sizeof(dtTileCache), DT_ALLOC_PERM);
|
||||
if (!mem) return 0;
|
||||
return new(mem) dtTileCache;
|
||||
}
|
||||
|
||||
void dtFreeTileCache(dtTileCache* tc)
|
||||
{
|
||||
if (!tc) return;
|
||||
tc->~dtTileCache();
|
||||
dtFree(tc);
|
||||
}
|
||||
|
||||
static bool contains(const dtCompressedTileRef* a, const int n, const dtCompressedTileRef v)
|
||||
{
|
||||
for (int i = 0; i < n; ++i)
|
||||
if (a[i] == v)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
inline int computeTileHash(int x, int y, const int mask)
|
||||
{
|
||||
const unsigned int h1 = 0x8da6b343; // Large multiplicative constants;
|
||||
const unsigned int h2 = 0xd8163841; // here arbitrarily chosen primes
|
||||
unsigned int n = h1 * x + h2 * y;
|
||||
return (int)(n & mask);
|
||||
}
|
||||
|
||||
|
||||
struct NavMeshTileBuildContext
|
||||
{
|
||||
inline NavMeshTileBuildContext(struct dtTileCacheAlloc* a) : layer(0), lcset(0), lmesh(0), alloc(a) {}
|
||||
inline ~NavMeshTileBuildContext() { purge(); }
|
||||
void purge()
|
||||
{
|
||||
dtFreeTileCacheLayer(alloc, layer);
|
||||
layer = 0;
|
||||
dtFreeTileCacheContourSet(alloc, lcset);
|
||||
lcset = 0;
|
||||
dtFreeTileCachePolyMesh(alloc, lmesh);
|
||||
lmesh = 0;
|
||||
}
|
||||
struct dtTileCacheLayer* layer;
|
||||
struct dtTileCacheContourSet* lcset;
|
||||
struct dtTileCachePolyMesh* lmesh;
|
||||
struct dtTileCacheAlloc* alloc;
|
||||
};
|
||||
|
||||
|
||||
dtTileCache::dtTileCache() :
|
||||
m_tileLutSize(0),
|
||||
m_tileLutMask(0),
|
||||
m_posLookup(0),
|
||||
m_nextFreeTile(0),
|
||||
m_tiles(0),
|
||||
m_saltBits(0),
|
||||
m_tileBits(0),
|
||||
m_talloc(0),
|
||||
m_tcomp(0),
|
||||
m_tmproc(0),
|
||||
m_obstacles(0),
|
||||
m_nextFreeObstacle(0),
|
||||
m_nreqs(0),
|
||||
m_nupdate(0)
|
||||
{
|
||||
memset(&m_params, 0, sizeof(m_params));
|
||||
memset(m_reqs, 0, sizeof(ObstacleRequest) * MAX_REQUESTS);
|
||||
}
|
||||
|
||||
dtTileCache::~dtTileCache()
|
||||
{
|
||||
for (int i = 0; i < m_params.maxTiles; ++i)
|
||||
{
|
||||
if (m_tiles[i].flags & DT_COMPRESSEDTILE_FREE_DATA)
|
||||
{
|
||||
dtFree(m_tiles[i].data);
|
||||
m_tiles[i].data = 0;
|
||||
}
|
||||
}
|
||||
dtFree(m_obstacles);
|
||||
m_obstacles = 0;
|
||||
dtFree(m_posLookup);
|
||||
m_posLookup = 0;
|
||||
dtFree(m_tiles);
|
||||
m_tiles = 0;
|
||||
m_nreqs = 0;
|
||||
m_nupdate = 0;
|
||||
}
|
||||
|
||||
const dtCompressedTile* dtTileCache::getTileByRef(dtCompressedTileRef ref) const
|
||||
{
|
||||
if (!ref)
|
||||
return 0;
|
||||
unsigned int tileIndex = decodeTileIdTile(ref);
|
||||
unsigned int tileSalt = decodeTileIdSalt(ref);
|
||||
if ((int)tileIndex >= m_params.maxTiles)
|
||||
return 0;
|
||||
const dtCompressedTile* tile = &m_tiles[tileIndex];
|
||||
if (tile->salt != tileSalt)
|
||||
return 0;
|
||||
return tile;
|
||||
}
|
||||
|
||||
|
||||
dtStatus dtTileCache::init(const dtTileCacheParams* params,
|
||||
dtTileCacheAlloc* talloc,
|
||||
dtTileCacheCompressor* tcomp,
|
||||
dtTileCacheMeshProcess* tmproc)
|
||||
{
|
||||
m_talloc = talloc;
|
||||
m_tcomp = tcomp;
|
||||
m_tmproc = tmproc;
|
||||
m_nreqs = 0;
|
||||
memcpy(&m_params, params, sizeof(m_params));
|
||||
|
||||
// Alloc space for obstacles.
|
||||
m_obstacles = (dtTileCacheObstacle*)dtAlloc(sizeof(dtTileCacheObstacle)*m_params.maxObstacles, DT_ALLOC_PERM);
|
||||
if (!m_obstacles)
|
||||
return DT_FAILURE | DT_OUT_OF_MEMORY;
|
||||
memset(m_obstacles, 0, sizeof(dtTileCacheObstacle)*m_params.maxObstacles);
|
||||
m_nextFreeObstacle = 0;
|
||||
for (int i = m_params.maxObstacles-1; i >= 0; --i)
|
||||
{
|
||||
m_obstacles[i].salt = 1;
|
||||
m_obstacles[i].next = m_nextFreeObstacle;
|
||||
m_nextFreeObstacle = &m_obstacles[i];
|
||||
}
|
||||
|
||||
// Init tiles
|
||||
m_tileLutSize = dtNextPow2(m_params.maxTiles/4);
|
||||
if (!m_tileLutSize) m_tileLutSize = 1;
|
||||
m_tileLutMask = m_tileLutSize-1;
|
||||
|
||||
m_tiles = (dtCompressedTile*)dtAlloc(sizeof(dtCompressedTile)*m_params.maxTiles, DT_ALLOC_PERM);
|
||||
if (!m_tiles)
|
||||
return DT_FAILURE | DT_OUT_OF_MEMORY;
|
||||
m_posLookup = (dtCompressedTile**)dtAlloc(sizeof(dtCompressedTile*)*m_tileLutSize, DT_ALLOC_PERM);
|
||||
if (!m_posLookup)
|
||||
return DT_FAILURE | DT_OUT_OF_MEMORY;
|
||||
memset(m_tiles, 0, sizeof(dtCompressedTile)*m_params.maxTiles);
|
||||
memset(m_posLookup, 0, sizeof(dtCompressedTile*)*m_tileLutSize);
|
||||
m_nextFreeTile = 0;
|
||||
for (int i = m_params.maxTiles-1; i >= 0; --i)
|
||||
{
|
||||
m_tiles[i].salt = 1;
|
||||
m_tiles[i].next = m_nextFreeTile;
|
||||
m_nextFreeTile = &m_tiles[i];
|
||||
}
|
||||
|
||||
// Init ID generator values.
|
||||
m_tileBits = dtIlog2(dtNextPow2((unsigned int)m_params.maxTiles));
|
||||
// Only allow 31 salt bits, since the salt mask is calculated using 32bit uint and it will overflow.
|
||||
m_saltBits = dtMin((unsigned int)31, 32 - m_tileBits);
|
||||
if (m_saltBits < 10)
|
||||
return DT_FAILURE | DT_INVALID_PARAM;
|
||||
|
||||
return DT_SUCCESS;
|
||||
}
|
||||
|
||||
int dtTileCache::getTilesAt(const int tx, const int ty, dtCompressedTileRef* tiles, const int maxTiles) const
|
||||
{
|
||||
int n = 0;
|
||||
|
||||
// Find tile based on hash.
|
||||
int h = computeTileHash(tx,ty,m_tileLutMask);
|
||||
dtCompressedTile* tile = m_posLookup[h];
|
||||
while (tile)
|
||||
{
|
||||
if (tile->header &&
|
||||
tile->header->tx == tx &&
|
||||
tile->header->ty == ty)
|
||||
{
|
||||
if (n < maxTiles)
|
||||
tiles[n++] = getTileRef(tile);
|
||||
}
|
||||
tile = tile->next;
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
dtCompressedTile* dtTileCache::getTileAt(const int tx, const int ty, const int tlayer)
|
||||
{
|
||||
// Find tile based on hash.
|
||||
int h = computeTileHash(tx,ty,m_tileLutMask);
|
||||
dtCompressedTile* tile = m_posLookup[h];
|
||||
while (tile)
|
||||
{
|
||||
if (tile->header &&
|
||||
tile->header->tx == tx &&
|
||||
tile->header->ty == ty &&
|
||||
tile->header->tlayer == tlayer)
|
||||
{
|
||||
return tile;
|
||||
}
|
||||
tile = tile->next;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
dtCompressedTileRef dtTileCache::getTileRef(const dtCompressedTile* tile) const
|
||||
{
|
||||
if (!tile) return 0;
|
||||
const unsigned int it = (unsigned int)(tile - m_tiles);
|
||||
return (dtCompressedTileRef)encodeTileId(tile->salt, it);
|
||||
}
|
||||
|
||||
dtObstacleRef dtTileCache::getObstacleRef(const dtTileCacheObstacle* ob) const
|
||||
{
|
||||
if (!ob) return 0;
|
||||
const unsigned int idx = (unsigned int)(ob - m_obstacles);
|
||||
return encodeObstacleId(ob->salt, idx);
|
||||
}
|
||||
|
||||
const dtTileCacheObstacle* dtTileCache::getObstacleByRef(dtObstacleRef ref)
|
||||
{
|
||||
if (!ref)
|
||||
return 0;
|
||||
unsigned int idx = decodeObstacleIdObstacle(ref);
|
||||
if ((int)idx >= m_params.maxObstacles)
|
||||
return 0;
|
||||
const dtTileCacheObstacle* ob = &m_obstacles[idx];
|
||||
unsigned int salt = decodeObstacleIdSalt(ref);
|
||||
if (ob->salt != salt)
|
||||
return 0;
|
||||
return ob;
|
||||
}
|
||||
|
||||
dtTileCacheMeshProcess::~dtTileCacheMeshProcess()
|
||||
{
|
||||
// Defined out of line to fix the weak v-tables warning
|
||||
}
|
||||
|
||||
dtStatus dtTileCache::addTile(unsigned char* data, const int dataSize, unsigned char flags, dtCompressedTileRef* result)
|
||||
{
|
||||
// Make sure the data is in right format.
|
||||
dtTileCacheLayerHeader* header = (dtTileCacheLayerHeader*)data;
|
||||
if (header->magic != DT_TILECACHE_MAGIC)
|
||||
return DT_FAILURE | DT_WRONG_MAGIC;
|
||||
if (header->version != DT_TILECACHE_VERSION)
|
||||
return DT_FAILURE | DT_WRONG_VERSION;
|
||||
|
||||
// Make sure the location is free.
|
||||
if (getTileAt(header->tx, header->ty, header->tlayer))
|
||||
return DT_FAILURE;
|
||||
|
||||
// Allocate a tile.
|
||||
dtCompressedTile* tile = 0;
|
||||
if (m_nextFreeTile)
|
||||
{
|
||||
tile = m_nextFreeTile;
|
||||
m_nextFreeTile = tile->next;
|
||||
tile->next = 0;
|
||||
}
|
||||
|
||||
// Make sure we could allocate a tile.
|
||||
if (!tile)
|
||||
return DT_FAILURE | DT_OUT_OF_MEMORY;
|
||||
|
||||
// Insert tile into the position lut.
|
||||
int h = computeTileHash(header->tx, header->ty, m_tileLutMask);
|
||||
tile->next = m_posLookup[h];
|
||||
m_posLookup[h] = tile;
|
||||
|
||||
// Init tile.
|
||||
const int headerSize = dtAlign4(sizeof(dtTileCacheLayerHeader));
|
||||
tile->header = (dtTileCacheLayerHeader*)data;
|
||||
tile->data = data;
|
||||
tile->dataSize = dataSize;
|
||||
tile->compressed = tile->data + headerSize;
|
||||
tile->compressedSize = tile->dataSize - headerSize;
|
||||
tile->flags = flags;
|
||||
|
||||
if (result)
|
||||
*result = getTileRef(tile);
|
||||
|
||||
return DT_SUCCESS;
|
||||
}
|
||||
|
||||
dtStatus dtTileCache::removeTile(dtCompressedTileRef ref, unsigned char** data, int* dataSize)
|
||||
{
|
||||
if (!ref)
|
||||
return DT_FAILURE | DT_INVALID_PARAM;
|
||||
unsigned int tileIndex = decodeTileIdTile(ref);
|
||||
unsigned int tileSalt = decodeTileIdSalt(ref);
|
||||
if ((int)tileIndex >= m_params.maxTiles)
|
||||
return DT_FAILURE | DT_INVALID_PARAM;
|
||||
dtCompressedTile* tile = &m_tiles[tileIndex];
|
||||
if (tile->salt != tileSalt)
|
||||
return DT_FAILURE | DT_INVALID_PARAM;
|
||||
|
||||
// Remove tile from hash lookup.
|
||||
const int h = computeTileHash(tile->header->tx,tile->header->ty,m_tileLutMask);
|
||||
dtCompressedTile* prev = 0;
|
||||
dtCompressedTile* cur = m_posLookup[h];
|
||||
while (cur)
|
||||
{
|
||||
if (cur == tile)
|
||||
{
|
||||
if (prev)
|
||||
prev->next = cur->next;
|
||||
else
|
||||
m_posLookup[h] = cur->next;
|
||||
break;
|
||||
}
|
||||
prev = cur;
|
||||
cur = cur->next;
|
||||
}
|
||||
|
||||
// Reset tile.
|
||||
if (tile->flags & DT_COMPRESSEDTILE_FREE_DATA)
|
||||
{
|
||||
// Owns data
|
||||
dtFree(tile->data);
|
||||
tile->data = 0;
|
||||
tile->dataSize = 0;
|
||||
if (data) *data = 0;
|
||||
if (dataSize) *dataSize = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (data) *data = tile->data;
|
||||
if (dataSize) *dataSize = tile->dataSize;
|
||||
}
|
||||
|
||||
tile->header = 0;
|
||||
tile->data = 0;
|
||||
tile->dataSize = 0;
|
||||
tile->compressed = 0;
|
||||
tile->compressedSize = 0;
|
||||
tile->flags = 0;
|
||||
|
||||
// Update salt, salt should never be zero.
|
||||
tile->salt = (tile->salt+1) & ((1<<m_saltBits)-1);
|
||||
if (tile->salt == 0)
|
||||
tile->salt++;
|
||||
|
||||
// Add to free list.
|
||||
tile->next = m_nextFreeTile;
|
||||
m_nextFreeTile = tile;
|
||||
|
||||
return DT_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
dtStatus dtTileCache::addObstacle(const float* pos, const float radius, const float height, dtObstacleRef* result)
|
||||
{
|
||||
if (m_nreqs >= MAX_REQUESTS)
|
||||
return DT_FAILURE | DT_BUFFER_TOO_SMALL;
|
||||
|
||||
dtTileCacheObstacle* ob = 0;
|
||||
if (m_nextFreeObstacle)
|
||||
{
|
||||
ob = m_nextFreeObstacle;
|
||||
m_nextFreeObstacle = ob->next;
|
||||
ob->next = 0;
|
||||
}
|
||||
if (!ob)
|
||||
return DT_FAILURE | DT_OUT_OF_MEMORY;
|
||||
|
||||
unsigned short salt = ob->salt;
|
||||
memset(ob, 0, sizeof(dtTileCacheObstacle));
|
||||
ob->salt = salt;
|
||||
ob->state = DT_OBSTACLE_PROCESSING;
|
||||
ob->type = DT_OBSTACLE_CYLINDER;
|
||||
dtVcopy(ob->cylinder.pos, pos);
|
||||
ob->cylinder.radius = radius;
|
||||
ob->cylinder.height = height;
|
||||
|
||||
ObstacleRequest* req = &m_reqs[m_nreqs++];
|
||||
memset(req, 0, sizeof(ObstacleRequest));
|
||||
req->action = REQUEST_ADD;
|
||||
req->ref = getObstacleRef(ob);
|
||||
|
||||
if (result)
|
||||
*result = req->ref;
|
||||
|
||||
return DT_SUCCESS;
|
||||
}
|
||||
|
||||
dtStatus dtTileCache::addBoxObstacle(const float* bmin, const float* bmax, dtObstacleRef* result)
|
||||
{
|
||||
if (m_nreqs >= MAX_REQUESTS)
|
||||
return DT_FAILURE | DT_BUFFER_TOO_SMALL;
|
||||
|
||||
dtTileCacheObstacle* ob = 0;
|
||||
if (m_nextFreeObstacle)
|
||||
{
|
||||
ob = m_nextFreeObstacle;
|
||||
m_nextFreeObstacle = ob->next;
|
||||
ob->next = 0;
|
||||
}
|
||||
if (!ob)
|
||||
return DT_FAILURE | DT_OUT_OF_MEMORY;
|
||||
|
||||
unsigned short salt = ob->salt;
|
||||
memset(ob, 0, sizeof(dtTileCacheObstacle));
|
||||
ob->salt = salt;
|
||||
ob->state = DT_OBSTACLE_PROCESSING;
|
||||
ob->type = DT_OBSTACLE_BOX;
|
||||
dtVcopy(ob->box.bmin, bmin);
|
||||
dtVcopy(ob->box.bmax, bmax);
|
||||
|
||||
ObstacleRequest* req = &m_reqs[m_nreqs++];
|
||||
memset(req, 0, sizeof(ObstacleRequest));
|
||||
req->action = REQUEST_ADD;
|
||||
req->ref = getObstacleRef(ob);
|
||||
|
||||
if (result)
|
||||
*result = req->ref;
|
||||
|
||||
return DT_SUCCESS;
|
||||
}
|
||||
|
||||
dtStatus dtTileCache::addBoxObstacle(const float* center, const float* halfExtents, const float yRadians, dtObstacleRef* result)
|
||||
{
|
||||
if (m_nreqs >= MAX_REQUESTS)
|
||||
return DT_FAILURE | DT_BUFFER_TOO_SMALL;
|
||||
|
||||
dtTileCacheObstacle* ob = 0;
|
||||
if (m_nextFreeObstacle)
|
||||
{
|
||||
ob = m_nextFreeObstacle;
|
||||
m_nextFreeObstacle = ob->next;
|
||||
ob->next = 0;
|
||||
}
|
||||
if (!ob)
|
||||
return DT_FAILURE | DT_OUT_OF_MEMORY;
|
||||
|
||||
unsigned short salt = ob->salt;
|
||||
memset(ob, 0, sizeof(dtTileCacheObstacle));
|
||||
ob->salt = salt;
|
||||
ob->state = DT_OBSTACLE_PROCESSING;
|
||||
ob->type = DT_OBSTACLE_ORIENTED_BOX;
|
||||
dtVcopy(ob->orientedBox.center, center);
|
||||
dtVcopy(ob->orientedBox.halfExtents, halfExtents);
|
||||
|
||||
float coshalf= cosf(0.5f*yRadians);
|
||||
float sinhalf = sinf(-0.5f*yRadians);
|
||||
ob->orientedBox.rotAux[0] = coshalf*sinhalf;
|
||||
ob->orientedBox.rotAux[1] = coshalf*coshalf - 0.5f;
|
||||
|
||||
ObstacleRequest* req = &m_reqs[m_nreqs++];
|
||||
memset(req, 0, sizeof(ObstacleRequest));
|
||||
req->action = REQUEST_ADD;
|
||||
req->ref = getObstacleRef(ob);
|
||||
|
||||
if (result)
|
||||
*result = req->ref;
|
||||
|
||||
return DT_SUCCESS;
|
||||
}
|
||||
|
||||
dtStatus dtTileCache::removeObstacle(const dtObstacleRef ref)
|
||||
{
|
||||
if (!ref)
|
||||
return DT_SUCCESS;
|
||||
if (m_nreqs >= MAX_REQUESTS)
|
||||
return DT_FAILURE | DT_BUFFER_TOO_SMALL;
|
||||
|
||||
ObstacleRequest* req = &m_reqs[m_nreqs++];
|
||||
memset(req, 0, sizeof(ObstacleRequest));
|
||||
req->action = REQUEST_REMOVE;
|
||||
req->ref = ref;
|
||||
|
||||
return DT_SUCCESS;
|
||||
}
|
||||
|
||||
dtStatus dtTileCache::queryTiles(const float* bmin, const float* bmax,
|
||||
dtCompressedTileRef* results, int* resultCount, const int maxResults) const
|
||||
{
|
||||
const int MAX_TILES = 32;
|
||||
dtCompressedTileRef tiles[MAX_TILES];
|
||||
|
||||
int n = 0;
|
||||
|
||||
const float tw = m_params.width * m_params.cs;
|
||||
const float th = m_params.height * m_params.cs;
|
||||
const int tx0 = (int)dtMathFloorf((bmin[0]-m_params.orig[0]) / tw);
|
||||
const int tx1 = (int)dtMathFloorf((bmax[0]-m_params.orig[0]) / tw);
|
||||
const int ty0 = (int)dtMathFloorf((bmin[2]-m_params.orig[2]) / th);
|
||||
const int ty1 = (int)dtMathFloorf((bmax[2]-m_params.orig[2]) / th);
|
||||
|
||||
for (int ty = ty0; ty <= ty1; ++ty)
|
||||
{
|
||||
for (int tx = tx0; tx <= tx1; ++tx)
|
||||
{
|
||||
const int ntiles = getTilesAt(tx,ty,tiles,MAX_TILES);
|
||||
|
||||
for (int i = 0; i < ntiles; ++i)
|
||||
{
|
||||
const dtCompressedTile* tile = &m_tiles[decodeTileIdTile(tiles[i])];
|
||||
float tbmin[3], tbmax[3];
|
||||
calcTightTileBounds(tile->header, tbmin, tbmax);
|
||||
|
||||
if (dtOverlapBounds(bmin,bmax, tbmin,tbmax))
|
||||
{
|
||||
if (n < maxResults)
|
||||
results[n++] = tiles[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*resultCount = n;
|
||||
|
||||
return DT_SUCCESS;
|
||||
}
|
||||
|
||||
dtStatus dtTileCache::update(const float /*dt*/, dtNavMesh* navmesh,
|
||||
bool* upToDate)
|
||||
{
|
||||
if (m_nupdate == 0)
|
||||
{
|
||||
// Process requests.
|
||||
for (int i = 0; i < m_nreqs; ++i)
|
||||
{
|
||||
ObstacleRequest* req = &m_reqs[i];
|
||||
|
||||
unsigned int idx = decodeObstacleIdObstacle(req->ref);
|
||||
if ((int)idx >= m_params.maxObstacles)
|
||||
continue;
|
||||
dtTileCacheObstacle* ob = &m_obstacles[idx];
|
||||
unsigned int salt = decodeObstacleIdSalt(req->ref);
|
||||
if (ob->salt != salt)
|
||||
continue;
|
||||
|
||||
if (req->action == REQUEST_ADD)
|
||||
{
|
||||
// Find touched tiles.
|
||||
float bmin[3], bmax[3];
|
||||
getObstacleBounds(ob, bmin, bmax);
|
||||
|
||||
int ntouched = 0;
|
||||
queryTiles(bmin, bmax, ob->touched, &ntouched, DT_MAX_TOUCHED_TILES);
|
||||
ob->ntouched = (unsigned char)ntouched;
|
||||
// Add tiles to update list.
|
||||
ob->npending = 0;
|
||||
for (int j = 0; j < ob->ntouched; ++j)
|
||||
{
|
||||
if (m_nupdate < MAX_UPDATE)
|
||||
{
|
||||
if (!contains(m_update, m_nupdate, ob->touched[j]))
|
||||
m_update[m_nupdate++] = ob->touched[j];
|
||||
ob->pending[ob->npending++] = ob->touched[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (req->action == REQUEST_REMOVE)
|
||||
{
|
||||
// Prepare to remove obstacle.
|
||||
ob->state = DT_OBSTACLE_REMOVING;
|
||||
// Add tiles to update list.
|
||||
ob->npending = 0;
|
||||
for (int j = 0; j < ob->ntouched; ++j)
|
||||
{
|
||||
if (m_nupdate < MAX_UPDATE)
|
||||
{
|
||||
if (!contains(m_update, m_nupdate, ob->touched[j]))
|
||||
m_update[m_nupdate++] = ob->touched[j];
|
||||
ob->pending[ob->npending++] = ob->touched[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_nreqs = 0;
|
||||
}
|
||||
|
||||
dtStatus status = DT_SUCCESS;
|
||||
// Process updates
|
||||
if (m_nupdate)
|
||||
{
|
||||
// Build mesh
|
||||
const dtCompressedTileRef ref = m_update[0];
|
||||
status = buildNavMeshTile(ref, navmesh);
|
||||
m_nupdate--;
|
||||
if (m_nupdate > 0)
|
||||
memmove(m_update, m_update+1, m_nupdate*sizeof(dtCompressedTileRef));
|
||||
|
||||
// Update obstacle states.
|
||||
for (int i = 0; i < m_params.maxObstacles; ++i)
|
||||
{
|
||||
dtTileCacheObstacle* ob = &m_obstacles[i];
|
||||
if (ob->state == DT_OBSTACLE_PROCESSING || ob->state == DT_OBSTACLE_REMOVING)
|
||||
{
|
||||
// Remove handled tile from pending list.
|
||||
for (int j = 0; j < (int)ob->npending; j++)
|
||||
{
|
||||
if (ob->pending[j] == ref)
|
||||
{
|
||||
ob->pending[j] = ob->pending[(int)ob->npending-1];
|
||||
ob->npending--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If all pending tiles processed, change state.
|
||||
if (ob->npending == 0)
|
||||
{
|
||||
if (ob->state == DT_OBSTACLE_PROCESSING)
|
||||
{
|
||||
ob->state = DT_OBSTACLE_PROCESSED;
|
||||
}
|
||||
else if (ob->state == DT_OBSTACLE_REMOVING)
|
||||
{
|
||||
ob->state = DT_OBSTACLE_EMPTY;
|
||||
// Update salt, salt should never be zero.
|
||||
ob->salt = (ob->salt+1) & ((1<<16)-1);
|
||||
if (ob->salt == 0)
|
||||
ob->salt++;
|
||||
// Return obstacle to free list.
|
||||
ob->next = m_nextFreeObstacle;
|
||||
m_nextFreeObstacle = ob;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (upToDate)
|
||||
*upToDate = m_nupdate == 0 && m_nreqs == 0;
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
dtStatus dtTileCache::buildNavMeshTilesAt(const int tx, const int ty, dtNavMesh* navmesh)
|
||||
{
|
||||
const int MAX_TILES = 32;
|
||||
dtCompressedTileRef tiles[MAX_TILES];
|
||||
const int ntiles = getTilesAt(tx,ty,tiles,MAX_TILES);
|
||||
|
||||
for (int i = 0; i < ntiles; ++i)
|
||||
{
|
||||
dtStatus status = buildNavMeshTile(tiles[i], navmesh);
|
||||
if (dtStatusFailed(status))
|
||||
return status;
|
||||
}
|
||||
|
||||
return DT_SUCCESS;
|
||||
}
|
||||
|
||||
dtStatus dtTileCache::buildNavMeshTile(const dtCompressedTileRef ref, dtNavMesh* navmesh)
|
||||
{
|
||||
dtAssert(m_talloc);
|
||||
dtAssert(m_tcomp);
|
||||
|
||||
unsigned int idx = decodeTileIdTile(ref);
|
||||
if (idx > (unsigned int)m_params.maxTiles)
|
||||
return DT_FAILURE | DT_INVALID_PARAM;
|
||||
const dtCompressedTile* tile = &m_tiles[idx];
|
||||
unsigned int salt = decodeTileIdSalt(ref);
|
||||
if (tile->salt != salt)
|
||||
return DT_FAILURE | DT_INVALID_PARAM;
|
||||
|
||||
m_talloc->reset();
|
||||
|
||||
NavMeshTileBuildContext bc(m_talloc);
|
||||
const int walkableClimbVx = (int)(m_params.walkableClimb / m_params.ch);
|
||||
dtStatus status;
|
||||
|
||||
// Decompress tile layer data.
|
||||
status = dtDecompressTileCacheLayer(m_talloc, m_tcomp, tile->data, tile->dataSize, &bc.layer);
|
||||
if (dtStatusFailed(status))
|
||||
return status;
|
||||
|
||||
// Rasterize obstacles.
|
||||
for (int i = 0; i < m_params.maxObstacles; ++i)
|
||||
{
|
||||
const dtTileCacheObstacle* ob = &m_obstacles[i];
|
||||
if (ob->state == DT_OBSTACLE_EMPTY || ob->state == DT_OBSTACLE_REMOVING)
|
||||
continue;
|
||||
if (contains(ob->touched, ob->ntouched, ref))
|
||||
{
|
||||
if (ob->type == DT_OBSTACLE_CYLINDER)
|
||||
{
|
||||
dtMarkCylinderArea(*bc.layer, tile->header->bmin, m_params.cs, m_params.ch,
|
||||
ob->cylinder.pos, ob->cylinder.radius, ob->cylinder.height, 0);
|
||||
}
|
||||
else if (ob->type == DT_OBSTACLE_BOX)
|
||||
{
|
||||
dtMarkBoxArea(*bc.layer, tile->header->bmin, m_params.cs, m_params.ch,
|
||||
ob->box.bmin, ob->box.bmax, 0);
|
||||
}
|
||||
else if (ob->type == DT_OBSTACLE_ORIENTED_BOX)
|
||||
{
|
||||
dtMarkBoxArea(*bc.layer, tile->header->bmin, m_params.cs, m_params.ch,
|
||||
ob->orientedBox.center, ob->orientedBox.halfExtents, ob->orientedBox.rotAux, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build navmesh
|
||||
status = dtBuildTileCacheRegions(m_talloc, *bc.layer, walkableClimbVx);
|
||||
if (dtStatusFailed(status))
|
||||
return status;
|
||||
|
||||
bc.lcset = dtAllocTileCacheContourSet(m_talloc);
|
||||
if (!bc.lcset)
|
||||
return DT_FAILURE | DT_OUT_OF_MEMORY;
|
||||
status = dtBuildTileCacheContours(m_talloc, *bc.layer, walkableClimbVx,
|
||||
m_params.maxSimplificationError, *bc.lcset);
|
||||
if (dtStatusFailed(status))
|
||||
return status;
|
||||
|
||||
bc.lmesh = dtAllocTileCachePolyMesh(m_talloc);
|
||||
if (!bc.lmesh)
|
||||
return DT_FAILURE | DT_OUT_OF_MEMORY;
|
||||
status = dtBuildTileCachePolyMesh(m_talloc, *bc.lcset, *bc.lmesh);
|
||||
if (dtStatusFailed(status))
|
||||
return status;
|
||||
|
||||
// Early out if the mesh tile is empty.
|
||||
if (!bc.lmesh->npolys)
|
||||
{
|
||||
// Remove existing tile.
|
||||
navmesh->removeTile(navmesh->getTileRefAt(tile->header->tx,tile->header->ty,tile->header->tlayer),0,0);
|
||||
return DT_SUCCESS;
|
||||
}
|
||||
|
||||
dtNavMeshCreateParams params;
|
||||
memset(¶ms, 0, sizeof(params));
|
||||
params.verts = bc.lmesh->verts;
|
||||
params.vertCount = bc.lmesh->nverts;
|
||||
params.polys = bc.lmesh->polys;
|
||||
params.polyAreas = bc.lmesh->areas;
|
||||
params.polyFlags = bc.lmesh->flags;
|
||||
params.polyCount = bc.lmesh->npolys;
|
||||
params.nvp = DT_VERTS_PER_POLYGON;
|
||||
params.walkableHeight = m_params.walkableHeight;
|
||||
params.walkableRadius = m_params.walkableRadius;
|
||||
params.walkableClimb = m_params.walkableClimb;
|
||||
params.tileX = tile->header->tx;
|
||||
params.tileY = tile->header->ty;
|
||||
params.tileLayer = tile->header->tlayer;
|
||||
params.cs = m_params.cs;
|
||||
params.ch = m_params.ch;
|
||||
params.buildBvTree = false;
|
||||
dtVcopy(params.bmin, tile->header->bmin);
|
||||
dtVcopy(params.bmax, tile->header->bmax);
|
||||
|
||||
if (m_tmproc)
|
||||
{
|
||||
m_tmproc->process(¶ms, bc.lmesh->areas, bc.lmesh->flags);
|
||||
}
|
||||
|
||||
unsigned char* navData = 0;
|
||||
int navDataSize = 0;
|
||||
if (!dtCreateNavMeshData(¶ms, &navData, &navDataSize))
|
||||
return DT_FAILURE;
|
||||
|
||||
// Remove existing tile.
|
||||
navmesh->removeTile(navmesh->getTileRefAt(tile->header->tx,tile->header->ty,tile->header->tlayer),0,0);
|
||||
|
||||
// Add new tile, or leave the location empty.
|
||||
if (navData)
|
||||
{
|
||||
// Let the navmesh own the data.
|
||||
status = navmesh->addTile(navData,navDataSize,DT_TILE_FREE_DATA,0,0);
|
||||
if (dtStatusFailed(status))
|
||||
{
|
||||
dtFree(navData);
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
return DT_SUCCESS;
|
||||
}
|
||||
|
||||
void dtTileCache::calcTightTileBounds(const dtTileCacheLayerHeader* header, float* bmin, float* bmax) const
|
||||
{
|
||||
const float cs = m_params.cs;
|
||||
bmin[0] = header->bmin[0] + header->minx*cs;
|
||||
bmin[1] = header->bmin[1];
|
||||
bmin[2] = header->bmin[2] + header->miny*cs;
|
||||
bmax[0] = header->bmin[0] + (header->maxx+1)*cs;
|
||||
bmax[1] = header->bmax[1];
|
||||
bmax[2] = header->bmin[2] + (header->maxy+1)*cs;
|
||||
}
|
||||
|
||||
void dtTileCache::getObstacleBounds(const struct dtTileCacheObstacle* ob, float* bmin, float* bmax) const
|
||||
{
|
||||
if (ob->type == DT_OBSTACLE_CYLINDER)
|
||||
{
|
||||
const dtObstacleCylinder &cl = ob->cylinder;
|
||||
|
||||
bmin[0] = cl.pos[0] - cl.radius;
|
||||
bmin[1] = cl.pos[1];
|
||||
bmin[2] = cl.pos[2] - cl.radius;
|
||||
bmax[0] = cl.pos[0] + cl.radius;
|
||||
bmax[1] = cl.pos[1] + cl.height;
|
||||
bmax[2] = cl.pos[2] + cl.radius;
|
||||
}
|
||||
else if (ob->type == DT_OBSTACLE_BOX)
|
||||
{
|
||||
dtVcopy(bmin, ob->box.bmin);
|
||||
dtVcopy(bmax, ob->box.bmax);
|
||||
}
|
||||
else if (ob->type == DT_OBSTACLE_ORIENTED_BOX)
|
||||
{
|
||||
const dtObstacleOrientedBox &orientedBox = ob->orientedBox;
|
||||
|
||||
float maxr = 1.41f*dtMax(orientedBox.halfExtents[0], orientedBox.halfExtents[2]);
|
||||
bmin[0] = orientedBox.center[0] - maxr;
|
||||
bmax[0] = orientedBox.center[0] + maxr;
|
||||
bmin[1] = orientedBox.center[1] - orientedBox.halfExtents[1];
|
||||
bmax[1] = orientedBox.center[1] + orientedBox.halfExtents[1];
|
||||
bmin[2] = orientedBox.center[2] - maxr;
|
||||
bmax[2] = orientedBox.center[2] + maxr;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
194
src/features/editScene/recastnavigation/Docs/DoxygenLayout.xml
Normal file
194
src/features/editScene/recastnavigation/Docs/DoxygenLayout.xml
Normal file
@@ -0,0 +1,194 @@
|
||||
<doxygenlayout version="1.0">
|
||||
<!-- Generated by doxygen 1.8.6 -->
|
||||
<!-- Navigation index tabs for HTML output -->
|
||||
<navindex>
|
||||
<tab type="mainpage" visible="yes" title=""/>
|
||||
<tab type="pages" visible="yes" title="" intro=""/>
|
||||
<tab type="modules" visible="yes" title="" intro=""/>
|
||||
<tab type="namespaces" visible="no" title="">
|
||||
<tab type="namespacelist" visible="yes" title="" intro=""/>
|
||||
<tab type="namespacemembers" visible="yes" title="" intro=""/>
|
||||
</tab>
|
||||
<tab type="classes" visible="yes" title="">
|
||||
<tab type="classlist" visible="yes" title="" intro=""/>
|
||||
<tab type="classindex" visible="yes" title=""/>
|
||||
<tab type="hierarchy" visible="yes" title="" intro=""/>
|
||||
<tab type="classmembers" visible="yes" title="" intro=""/>
|
||||
</tab>
|
||||
<tab type="files" visible="yes" title="">
|
||||
<tab type="filelist" visible="yes" title="" intro=""/>
|
||||
<tab type="globals" visible="yes" title="" intro=""/>
|
||||
</tab>
|
||||
<tab type="examples" visible="yes" title="" intro=""/>
|
||||
</navindex>
|
||||
|
||||
<!-- Layout definition for a class page -->
|
||||
<class>
|
||||
<briefdescription visible="yes"/>
|
||||
<includes visible="$SHOW_INCLUDE_FILES"/>
|
||||
<inheritancegraph visible="$CLASS_GRAPH"/>
|
||||
<collaborationgraph visible="$COLLABORATION_GRAPH"/>
|
||||
<memberdecl>
|
||||
<nestedclasses visible="yes" title=""/>
|
||||
<publictypes title=""/>
|
||||
<services title=""/>
|
||||
<interfaces title=""/>
|
||||
<publicslots title=""/>
|
||||
<signals title=""/>
|
||||
<publicmethods title=""/>
|
||||
<publicstaticmethods title=""/>
|
||||
<publicattributes title=""/>
|
||||
<publicstaticattributes title=""/>
|
||||
<protectedtypes title=""/>
|
||||
<protectedslots title=""/>
|
||||
<protectedmethods title=""/>
|
||||
<protectedstaticmethods title=""/>
|
||||
<protectedattributes title=""/>
|
||||
<protectedstaticattributes title=""/>
|
||||
<packagetypes title=""/>
|
||||
<packagemethods title=""/>
|
||||
<packagestaticmethods title=""/>
|
||||
<packageattributes title=""/>
|
||||
<packagestaticattributes title=""/>
|
||||
<properties title=""/>
|
||||
<events title=""/>
|
||||
<privatetypes title=""/>
|
||||
<privateslots title=""/>
|
||||
<privatemethods title=""/>
|
||||
<privatestaticmethods title=""/>
|
||||
<privateattributes title=""/>
|
||||
<privatestaticattributes title=""/>
|
||||
<friends title=""/>
|
||||
<related title="" subtitle=""/>
|
||||
<membergroups visible="yes"/>
|
||||
</memberdecl>
|
||||
<detaileddescription title="Description"/>
|
||||
<memberdef>
|
||||
<inlineclasses title=""/>
|
||||
<typedefs title=""/>
|
||||
<enums title=""/>
|
||||
<services title=""/>
|
||||
<interfaces title=""/>
|
||||
<constructors title=""/>
|
||||
<functions title=""/>
|
||||
<related title=""/>
|
||||
<variables title=""/>
|
||||
<properties title=""/>
|
||||
<events title=""/>
|
||||
</memberdef>
|
||||
<allmemberslink visible="yes"/>
|
||||
<usedfiles visible="$SHOW_USED_FILES"/>
|
||||
<authorsection visible="yes"/>
|
||||
</class>
|
||||
|
||||
<!-- Layout definition for a namespace page -->
|
||||
<namespace>
|
||||
<briefdescription visible="yes"/>
|
||||
<memberdecl>
|
||||
<nestednamespaces visible="yes" title=""/>
|
||||
<constantgroups visible="yes" title=""/>
|
||||
<classes visible="yes" title=""/>
|
||||
<typedefs title=""/>
|
||||
<enums title=""/>
|
||||
<functions title=""/>
|
||||
<variables title=""/>
|
||||
<membergroups visible="yes"/>
|
||||
</memberdecl>
|
||||
<detaileddescription title="Description"/>
|
||||
<memberdef>
|
||||
<inlineclasses title=""/>
|
||||
<typedefs title=""/>
|
||||
<enums title=""/>
|
||||
<functions title=""/>
|
||||
<variables title=""/>
|
||||
</memberdef>
|
||||
<authorsection visible="yes"/>
|
||||
</namespace>
|
||||
|
||||
<!-- Layout definition for a file page -->
|
||||
<file>
|
||||
<briefdescription visible="yes"/>
|
||||
<detaileddescription title=""/>
|
||||
<includes visible="$SHOW_INCLUDE_FILES"/>
|
||||
<includegraph visible="$INCLUDE_GRAPH"/>
|
||||
<includedbygraph visible="$INCLUDED_BY_GRAPH"/>
|
||||
<sourcelink visible="yes"/>
|
||||
<memberdecl>
|
||||
<classes visible="yes" title=""/>
|
||||
<namespaces visible="yes" title=""/>
|
||||
<constantgroups visible="yes" title=""/>
|
||||
<defines title=""/>
|
||||
<typedefs title=""/>
|
||||
<enums title=""/>
|
||||
<functions title=""/>
|
||||
<variables title=""/>
|
||||
<membergroups visible="yes"/>
|
||||
</memberdecl>
|
||||
<memberdef>
|
||||
<inlineclasses title=""/>
|
||||
<defines title=""/>
|
||||
<typedefs title=""/>
|
||||
<enums title=""/>
|
||||
<functions title=""/>
|
||||
<variables title=""/>
|
||||
</memberdef>
|
||||
<authorsection/>
|
||||
</file>
|
||||
|
||||
<!-- Layout definition for a group page -->
|
||||
<group>
|
||||
<briefdescription visible="yes"/>
|
||||
<groupgraph visible="$GROUP_GRAPHS"/>
|
||||
<memberdecl>
|
||||
<nestedgroups visible="yes" title=""/>
|
||||
<dirs visible="yes" title=""/>
|
||||
<files visible="yes" title=""/>
|
||||
<namespaces visible="yes" title=""/>
|
||||
<classes visible="yes" title=""/>
|
||||
<defines title=""/>
|
||||
<typedefs title=""/>
|
||||
<enums title=""/>
|
||||
<enumvalues title=""/>
|
||||
<functions title=""/>
|
||||
<variables title=""/>
|
||||
<signals title=""/>
|
||||
<publicslots title=""/>
|
||||
<protectedslots title=""/>
|
||||
<privateslots title=""/>
|
||||
<events title=""/>
|
||||
<properties title=""/>
|
||||
<friends title=""/>
|
||||
<membergroups visible="yes"/>
|
||||
</memberdecl>
|
||||
<detaileddescription title="Description"/>
|
||||
<memberdef>
|
||||
<pagedocs/>
|
||||
<inlineclasses title=""/>
|
||||
<defines title=""/>
|
||||
<typedefs title=""/>
|
||||
<enums title=""/>
|
||||
<enumvalues title=""/>
|
||||
<functions title=""/>
|
||||
<variables title=""/>
|
||||
<signals title=""/>
|
||||
<publicslots title=""/>
|
||||
<protectedslots title=""/>
|
||||
<privateslots title=""/>
|
||||
<events title=""/>
|
||||
<properties title=""/>
|
||||
<friends title=""/>
|
||||
</memberdef>
|
||||
<authorsection visible="yes"/>
|
||||
</group>
|
||||
|
||||
<!-- Layout definition for a directory page -->
|
||||
<directory>
|
||||
<briefdescription visible="yes"/>
|
||||
<directorygraph visible="yes"/>
|
||||
<memberdecl>
|
||||
<dirs visible="yes"/>
|
||||
<files visible="yes"/>
|
||||
</memberdecl>
|
||||
<detaileddescription title=""/>
|
||||
</directory>
|
||||
</doxygenlayout>
|
||||
682
src/features/editScene/recastnavigation/Docs/Extern/Recast_api.txt
vendored
Normal file
682
src/features/editScene/recastnavigation/Docs/Extern/Recast_api.txt
vendored
Normal file
@@ -0,0 +1,682 @@
|
||||
// This file contains the detail API documentation for
|
||||
// elements defined in the Recast.h.
|
||||
|
||||
/**
|
||||
|
||||
@defgroup recast Recast
|
||||
|
||||
Members in this module are used to create mesh data that is then
|
||||
used to create Detour navigation meshes.
|
||||
|
||||
The are a large number of possible ways to building navigation mesh data.
|
||||
One of the simple piplines is as follows:
|
||||
|
||||
-# Prepare the input triangle mesh.
|
||||
-# Build a #rcHeightfield.
|
||||
-# Build a #rcCompactHeightfield.
|
||||
-# Build a #rcContourSet.
|
||||
-# Build a #rcPolyMesh.
|
||||
-# Build a #rcPolyMeshDetail.
|
||||
-# Use the rcPolyMesh and rcPolyMeshDetail to build a Detour navigation mesh
|
||||
tile.
|
||||
|
||||
The general life-cycle of the main classes is as follows:
|
||||
|
||||
-# Allocate the object using the Recast allocator. (E.g. #rcAllocHeightfield)
|
||||
-# Initialize or build the object. (E.g. #rcCreateHeightfield)
|
||||
-# Update the object as needed. (E.g. #rcRasterizeTriangles)
|
||||
-# Use the object as part of the pipeline.
|
||||
-# Free the object using the Recast allocator. (E.g. #rcFreeHeightField)
|
||||
|
||||
@struct rcConfig
|
||||
@par
|
||||
|
||||
The is a convenience structure that represents an aggregation of parameters
|
||||
used at different stages in the Recast build process. Some
|
||||
values are derived during the build process. Not all parameters
|
||||
are used for all build processes.
|
||||
|
||||
Units are usually in voxels (vx) or world units (wu). The units for voxels,
|
||||
grid size, and cell size are all based on the values of #cs and #ch.
|
||||
|
||||
In this documentation, the term 'field' refers to heightfield and
|
||||
contour data structures that define spacial information using an integer
|
||||
grid.
|
||||
|
||||
The upper and lower limits for the various parameters often depend on
|
||||
the platform's floating point accuraccy as well as interdependencies between
|
||||
the values of multiple parameters. See the individual parameter
|
||||
documentation for details.
|
||||
|
||||
@note First you should decide the size of your agent's logical cylinder.
|
||||
If your game world uses meters as units, a reasonable starting point for
|
||||
a human-sized agent might be a radius of `0.4` and a height of `2.0`.
|
||||
|
||||
@var rcConfig::borderSize
|
||||
@par
|
||||
|
||||
This value represents the the closest the walkable area of the heightfield
|
||||
should come to the xz-plane AABB of the field. It does not have any
|
||||
impact on the borders around internal obstructions.
|
||||
|
||||
@var rcConfig::tileSize
|
||||
@par
|
||||
|
||||
This field is only used when building multi-tile meshes.
|
||||
|
||||
@var rcConfig::cs
|
||||
@par
|
||||
|
||||
The voxelization cell size #cs defines the voxel size along both axes of
|
||||
the ground plane: x and z in Recast. This value is usually derived from the
|
||||
character radius `r`. A recommended starting value for #cs is either `r/2`
|
||||
or `r/3`. Smaller values of #cs will increase rasterization resolution and
|
||||
navmesh detail, but total generation time will increase exponentially. In
|
||||
outdoor environments, `r/2` is often good enough. For indoor scenes with
|
||||
tight spaces you might want the extra precision, so a value of `r/3` or
|
||||
smaller may give better results.
|
||||
|
||||
The initial instinct is to reduce this value to something very close to zero
|
||||
to maximize the detail of the generated navmesh. This quickly becomes a case
|
||||
of diminishing returns, however. Beyond a certain point there's usually not
|
||||
much perceptable difference in the generated navmesh, but huge increases in
|
||||
generation time. This hinders your ability to quickly iterate on level
|
||||
designs and provides little benefit. The general recommendation here is to
|
||||
use as large a value for #cs as you can get away with.
|
||||
|
||||
#cs and #ch define voxel/grid/cell size. So their values have significant
|
||||
side effects on all parameters defined in voxel units.
|
||||
|
||||
The minimum value for this parameter depends on the platform's floating point
|
||||
accuracy, with the practical minimum usually around 0.05.
|
||||
|
||||
@var rcConfig::ch
|
||||
@par
|
||||
|
||||
The voxelization cell height #ch is defined separately in order to allow for
|
||||
greater precision in height tests. A good starting point for #ch is half the
|
||||
#cs value. Smaller #ch values ensure that the navmesh properly connects areas
|
||||
that are only separated by a small curb or ditch. If small holes are generated
|
||||
in your navmesh around where there are discontinuities in height (for example,
|
||||
stairs or curbs), you may want to decrease the cell height value to increase
|
||||
the vertical rasterization precision of Recast.
|
||||
|
||||
#cs and #ch define voxel/grid/cell size. So their values have significant
|
||||
side effects on all parameters defined in voxel units.
|
||||
|
||||
The minimum value for this parameter depends on the platform's floating point
|
||||
accuracy, with the practical minimum usually around 0.05.
|
||||
|
||||
@var rcConfig::walkableSlopeAngle
|
||||
@par
|
||||
|
||||
The parameter #walkableSlopeAngle is to filter out areas of the world where
|
||||
the ground slope would be too steep for an agent to traverse. This value is
|
||||
defined as a maximum angle in degrees that the surface normal of a polgyon
|
||||
can differ from the world's up vector. This value must be within the range
|
||||
`[0, 90]`.
|
||||
|
||||
The practical upper limit for this parameter is usually around 85 degrees.
|
||||
|
||||
@var rcConfig::walkableHeight
|
||||
@par
|
||||
|
||||
This value defines the worldspace height `h` of the agent in voxels. Th value
|
||||
of #walkableHeight should be calculated as `ceil(h / ch)`. Note this is based
|
||||
on #ch not #cs since it's a height value.
|
||||
|
||||
Permits detection of overhangs in the source geometry that make the geometry
|
||||
below un-walkable. The value is usually set to the maximum agent height.
|
||||
|
||||
@var rcConfig::walkableClimb
|
||||
@par
|
||||
|
||||
The #walkableClimb value defines the maximum height of ledges and steps that
|
||||
the agent can walk up. Given a designer-defined `maxClimb` distance in world
|
||||
units, the value of #walkableClimb should be calculated as `ceil(maxClimb / ch)`.
|
||||
Note that this is using #ch not #cs because it's a height-based value.
|
||||
|
||||
Allows the mesh to flow over low lying obstructions such as curbs and
|
||||
up/down stairways. The value is usually set to how far up/down an agent can step.
|
||||
|
||||
@var rcConfig::walkableRadius
|
||||
@par
|
||||
|
||||
The parameter #walkableRadius defines the worldspace agent radius `r` in voxels.
|
||||
Most often, this value of #walkableRadius should be calculated as `ceil(r / cs)`.
|
||||
Note this is based on #cs since the agent radius is always parallel to the ground
|
||||
plane.
|
||||
|
||||
If the #walkableRadius value is greater than zero, the edges of the navmesh will
|
||||
be pushed away from all obstacles by this amount.
|
||||
|
||||
A non-zero #walkableRadius allows for much simpler runtime navmesh collision checks.
|
||||
The game only needs to check that the center point of the agent is contained within
|
||||
a navmesh polygon. Without this erosion, runtime navigation checks need to collide
|
||||
the geometric projection of the agent's logical cylinder onto the navmesh with the
|
||||
boundary edges of the navmesh polygons.
|
||||
|
||||
In general, this is the closest any part of the final mesh should get to an
|
||||
obstruction in the source geometry. It is usually set to the maximum
|
||||
agent radius.
|
||||
|
||||
If you want to have tight-fitting navmesh, or want to reuse the same navmesh for
|
||||
multiple agents with differing radii, you can use a `walkableRadius` value of zero.
|
||||
Be advised though that you will need to perform your own collisions with the navmesh
|
||||
edges, and odd edge cases issues in the mesh generation can potentially occur. For
|
||||
these reasons, specifying a radius of zero is allowed but is not recommended.
|
||||
|
||||
@var rcConfig::maxEdgeLen
|
||||
@par
|
||||
|
||||
In certain cases, long outer edges may decrease the quality of the resulting
|
||||
triangulation, creating very long thin triangles. This can sometimes be
|
||||
remedied by limiting the maximum edge length, causing the problematic long
|
||||
edges to be broken up into smaller segments.
|
||||
|
||||
The parameter #maxEdgeLen defines the maximum edge length and is defined in
|
||||
terms of voxels. A good value for #maxEdgeLen is something like
|
||||
`walkableRadius * 8`. A good way to adjust this value is to first set it really
|
||||
high and see if your data creates long edges. If it does, decrease #maxEdgeLen
|
||||
until you find the largest value which improves the resulting tesselation.
|
||||
|
||||
Extra vertices will be inserted as needed to keep contour edges below this
|
||||
length. A value of zero effectively disables this feature.
|
||||
|
||||
@var rcConfig::maxSimplificationError
|
||||
@par
|
||||
|
||||
When the rasterized areas are converted back to a vectorized representation,
|
||||
the #maxSimplificationError describes how loosely the simplification is done.
|
||||
The simplification process uses the
|
||||
<a href="https://en.wikipedia.org/wiki/Ramer–Douglas–Peucker_algorithm">Ramer–Douglas-Peucker algorithm</a>,
|
||||
and this value describes the max deviation in voxels.
|
||||
|
||||
Good values for #maxSimplificationError are in the range `[1.1, 1.5]`.
|
||||
A value of `1.3` is a good starting point and usually yields good results.
|
||||
If the value is less than `1.1`, some sawtoothing starts to appear at the
|
||||
generated edges. If the value is more than `1.5`, the mesh simplification
|
||||
starts to cut some corners it shouldn't.
|
||||
|
||||
The effect of this parameter only applies to the xz-plane.
|
||||
|
||||
@var rcConfig::minRegionArea
|
||||
@par
|
||||
|
||||
Watershed partitioning is really prone to noise in the input distance field.
|
||||
In order to get nicer areas, the areas are merged and small disconnected areas
|
||||
are removed after the water shed partitioning. The parameter #minRegionArea
|
||||
describes the minimum isolated region size that is still kept. A region is
|
||||
removed if the number of voxels in the region is less than the square of
|
||||
#minRegionArea.
|
||||
|
||||
Any regions that are smaller than this area will be marked as unwalkable.
|
||||
This is useful in removing useless regions that can sometimes form on
|
||||
geometry such as table tops, box tops, etc.
|
||||
|
||||
@var rcConfig::maxVertsPerPoly
|
||||
@par
|
||||
|
||||
If the mesh data is to be used to construct a Detour navigation mesh, then the upper limit
|
||||
is limited to <= #DT_VERTS_PER_POLYGON.
|
||||
|
||||
@var rcConfig::mergeRegionArea
|
||||
@par
|
||||
|
||||
The triangulation process works best with small, localized voxel regions.
|
||||
The parameter #mergeRegionArea controls the maximum voxel area of a region
|
||||
that is allowed to be merged with another region. If you see small patches
|
||||
missing here and there, you could lower the #minRegionArea value.
|
||||
|
||||
@struct rcHeightfield
|
||||
@par
|
||||
|
||||
The grid of a heightfield is layed out on the xz-plane based on the
|
||||
value of #cs. Spans exist within the grid columns with the span
|
||||
min/max values at increments of #ch from the base of the grid. The smallest
|
||||
possible span size is <tt>(#cs width) * (#cs depth) * (#ch height)</tt>. (Which is a single voxel.)
|
||||
|
||||
The standard process for buidling a heightfield is to allocate it using
|
||||
#rcAllocHeightfield, initialize it using #rcCreateHeightfield, then
|
||||
add spans using the various helper functions such as #rcRasterizeTriangle.
|
||||
|
||||
Building a heightfield is one of the first steps in creating a polygon mesh
|
||||
from source geometry. After it is populated, it is used to build a
|
||||
rcCompactHeightfield.
|
||||
|
||||
Example of iterating the spans in a heightfield:
|
||||
@code
|
||||
// Where hf is a reference to an heightfield object.
|
||||
|
||||
const float* orig = hf.bmin;
|
||||
const float cs = hf.cs;
|
||||
const float ch = hf.ch;
|
||||
|
||||
const int w = hf.width;
|
||||
const int h = hf.height;
|
||||
|
||||
for (int y = 0; y < h; ++y)
|
||||
{
|
||||
for (int x = 0; x < w; ++x)
|
||||
{
|
||||
// Deriving the minimum corner of the grid location.
|
||||
float fx = orig[0] + x*cs;
|
||||
float fz = orig[2] + y*cs;
|
||||
// The base span in the column. (May be null.)
|
||||
const rcSpan* s = hf.spans[x + y*w];
|
||||
while (s)
|
||||
{
|
||||
// Detriving the minium and maximum world position of the span.
|
||||
float fymin = orig[1]+s->smin*ch;
|
||||
float fymax = orig[1] + s->smax*ch;
|
||||
// Do other things with the span before moving up the column.
|
||||
s = s->next;
|
||||
}
|
||||
}
|
||||
}
|
||||
@endcode
|
||||
|
||||
@see rcAllocHeightfield, rcFreeHeightField, rcCreateHeightfield
|
||||
|
||||
@struct rcCompactCell
|
||||
@par
|
||||
|
||||
See the rcCompactHeightfield documentation for an example of how compact cells
|
||||
are used to iterate the heightfield.
|
||||
|
||||
Useful instances of this type can only by obtained from a #rcCompactHeightfield object.
|
||||
|
||||
@see rcCompactHeightfield
|
||||
|
||||
@struct rcCompactSpan
|
||||
@par
|
||||
|
||||
The span represents open, unobstructed space within a compact heightfield column.
|
||||
See the rcCompactHeightfield documentation for an example of iterating spans and searching
|
||||
span connections.
|
||||
|
||||
Useful instances of this type can only by obtained from a #rcCompactHeightfield object.
|
||||
|
||||
@see rcCompactHeightfield
|
||||
|
||||
|
||||
@struct rcCompactHeightfield
|
||||
@par
|
||||
|
||||
For this type of heightfield, the spans represent the open (unobstructed)
|
||||
space above the solid surfaces of a voxel field. It is usually created from
|
||||
a #rcHeightfield object. Data is stored in a compact, efficient manner,
|
||||
but the structure is not condusive to adding and removing spans.
|
||||
|
||||
The standard process for buidling a compact heightfield is to allocate it
|
||||
using #rcAllocCompactHeightfield, build it using #rcBuildCompactHeightfield,
|
||||
then run it through the various helper functions to generate neighbor
|
||||
and region data.
|
||||
|
||||
Connected neighbor spans form non-overlapping surfaces. When neighbor
|
||||
information is generated, spans will include data that can be used to
|
||||
locate axis-neighbors. Axis-neighbors are connected
|
||||
spans that are offset from the current cell column as follows:
|
||||
<pre>
|
||||
Direction 0 = (-1, 0)
|
||||
Direction 1 = (0, 1)
|
||||
Direction 2 = (1, 0)
|
||||
Direction 3 = (0, -1)
|
||||
</pre>
|
||||
|
||||
Example of iterating and inspecting spans, including connected neighbors:
|
||||
|
||||
@code
|
||||
// Where chf is an instance of a rcCompactHeightfield.
|
||||
|
||||
const float cs = chf.cs;
|
||||
const float ch = chf.ch;
|
||||
|
||||
for (int y = 0; y < chf.height; ++y)
|
||||
{
|
||||
for (int x = 0; x < chf.width; ++x)
|
||||
{
|
||||
// Deriving the minimum corner of the grid location.
|
||||
const float fx = chf.bmin[0] + x*cs;
|
||||
const float fz = chf.bmin[2] + y*cs;
|
||||
|
||||
// Get the cell for the grid location then iterate
|
||||
// up the column.
|
||||
const rcCompactCell& c = chf.cells[x+y*chf.width];
|
||||
for (unsigned i = c.index, ni = c.index+c.count; i < ni; ++i)
|
||||
{
|
||||
const rcCompactSpan& s = chf.spans[i];
|
||||
|
||||
Deriving the minimum (floor) of the span.
|
||||
const float fy = chf.bmin[1] + (s.y+1)*ch;
|
||||
|
||||
// Testing the area assignment of the span.
|
||||
if (chf.areas[i] == RC_WALKABLE_AREA)
|
||||
{
|
||||
// The span is in the default 'walkable area'.
|
||||
}
|
||||
else if (chf.areas[i] == RC_NULL_AREA)
|
||||
{
|
||||
// The surface is not considered walkable.
|
||||
// E.g. It was filtered out during the build processes.
|
||||
}
|
||||
else
|
||||
{
|
||||
// Do something. (Only applicable for custom build
|
||||
// build processes.)
|
||||
}
|
||||
|
||||
// Iterating the connected axis-neighbor spans.
|
||||
for (int dir = 0; dir < 4; ++dir)
|
||||
{
|
||||
if (rcGetCon(s, dir) != RC_NOT_CONNECTED)
|
||||
{
|
||||
// There is a neighbor in this direction.
|
||||
const int nx = x + rcGetDirOffsetX(dir);
|
||||
const int ny = y + rcGetDirOffsetY(dir);
|
||||
const int ni = (int)chf.cells[nx+ny*w].index + rcGetCon(s, 0);
|
||||
const rcCompactSpan& ns = chf.spans[ni];
|
||||
// Do something with the neighbor span.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@endcode
|
||||
|
||||
@see rcAllocCompactHeightfield, rcFreeCompactHeightfield, rcBuildCompactHeightfield
|
||||
|
||||
@struct rcContour
|
||||
@par
|
||||
|
||||
A contour only exists within the context of a #rcContourSet object.
|
||||
|
||||
While the height of the contour's border may vary, the contour will always
|
||||
form a simple polygon when projected onto the xz-plane.
|
||||
|
||||
Example of converting vertices into world space:
|
||||
|
||||
@code
|
||||
// Where cset is the rcContourSet object to which the contour belongs.
|
||||
float worldX = cset.bmin[0] + vertX * cset.cs;
|
||||
float worldY = cset.bmin[1] + vertY * cset.ch;
|
||||
float worldZ = cset.bmin[2] + vertZ * cset.cs;
|
||||
@endcode
|
||||
|
||||
@see rcContourSet
|
||||
|
||||
@var rcContour::verts
|
||||
@par
|
||||
|
||||
The simplified contour is a version of the raw contour with all
|
||||
'unnecessary' vertices removed. Whether a vertex is
|
||||
considered unnecessary depends on the contour build process.
|
||||
|
||||
The data format is as follows: (x, y, z, r) * #nverts
|
||||
|
||||
A contour edge is formed by the current and next vertex. The r-value
|
||||
represents region and connection information for the edge. For example:
|
||||
|
||||
@code
|
||||
int r = verts[i*4+3];
|
||||
|
||||
int regionId = r & RC_CONTOUR_REG_MASK;
|
||||
|
||||
if (r & RC_BORDER_VERTEX)
|
||||
{
|
||||
// The edge represents a solid border.
|
||||
}
|
||||
|
||||
if (r & RC_AREA_BORDER)
|
||||
{
|
||||
// The edge represents a transition between different areas.
|
||||
}
|
||||
@endcode
|
||||
|
||||
@var rcContour::rverts
|
||||
@par
|
||||
|
||||
See #verts for information on element layout.
|
||||
|
||||
@struct rcContourSet
|
||||
@par
|
||||
|
||||
All contours within the set share the minimum bounds and cell sizes of the set.
|
||||
|
||||
The standard process for building a contour set is to allocate it
|
||||
using #rcAllocContourSet, then initialize it using #rcBuildContours.
|
||||
|
||||
@see rcAllocContourSet, rcFreeContourSet, rcBuildContours
|
||||
|
||||
@struct rcPolyMesh
|
||||
@par
|
||||
|
||||
A mesh of potentially overlapping convex polygons of between three
|
||||
and #nvp vertices. The mesh exists within the context of an axis-aligned
|
||||
bounding box (AABB) with vertices laid out in an evenly spaced grid, based
|
||||
on the values of #cs and #ch.
|
||||
|
||||
The standard process for building a contour set is to allocate it using
|
||||
#rcAllocPolyMesh, the initialize it using #rcBuildPolyMesh
|
||||
|
||||
Example of iterating the polygons:
|
||||
|
||||
@code
|
||||
// Where mesh is a reference to a rcPolyMesh object.
|
||||
|
||||
const int nvp = mesh.nvp;
|
||||
const float cs = mesh.cs;
|
||||
const float ch = mesh.ch;
|
||||
const float* orig = mesh.bmin;
|
||||
|
||||
for (int i = 0; i < mesh.npolys; ++i)
|
||||
{
|
||||
const unsigned short* p = &mesh.polys[i*nvp*2];
|
||||
|
||||
// Iterate the vertices.
|
||||
unsigned short vi[3]; // The vertex indices.
|
||||
for (int j = 0; j < nvp; ++j)
|
||||
{
|
||||
if (p[j] == RC_MESH_NULL_IDX)
|
||||
break; // End of vertices.
|
||||
|
||||
if (p[j + nvp] == RC_MESH_NULL_IDX)
|
||||
{
|
||||
// The edge beginning with this vertex is a solid border.
|
||||
}
|
||||
else
|
||||
{
|
||||
// The edge beginning with this vertex connects to
|
||||
// polygon p[j + nvp].
|
||||
}
|
||||
|
||||
// Convert to world space.
|
||||
const unsigned short* v = &mesh.verts[p[j]*3];
|
||||
const float x = orig[0] + v[0]*cs;
|
||||
const float y = orig[1] + v[1]*ch;
|
||||
const float z = orig[2] + v[2]*cs;
|
||||
// Do something with the vertices.
|
||||
}
|
||||
}
|
||||
@endcode
|
||||
|
||||
@see rcAllocPolyMesh, rcFreePolyMesh, rcBuildPolyMesh
|
||||
|
||||
@var rcPolyMesh::verts
|
||||
@par
|
||||
|
||||
The values of #bmin ,#cs, and #ch are used to convert vertex coordinates
|
||||
to world space as follows:
|
||||
|
||||
@code
|
||||
float worldX = bmin[0] + verts[i*3+0] * cs
|
||||
float worldY = bmin[1] + verts[i*3+1] * ch
|
||||
float worldZ = bmin[2] + verts[i*3+2] * cs
|
||||
@endcode
|
||||
|
||||
@var rcPolyMesh::polys
|
||||
@par
|
||||
|
||||
Each entry is <tt>2 * #nvp</tt> in length. The first half of the entry
|
||||
contains the indices of the polygon. The first instance of #RC_MESH_NULL_IDX
|
||||
indicates the end of the indices for the entry. The second half contains
|
||||
indices to neighbor polygons. A value of #RC_MESH_NULL_IDX indicates no
|
||||
connection for the associated edge. (I.e. The edge is a solid border.)
|
||||
|
||||
For example:
|
||||
<pre>
|
||||
nvp = 6
|
||||
For the entry: (1, 3, 4, 8, RC_MESH_NULL_IDX, RC_MESH_NULL_IDX,
|
||||
18, RC_MESH_NULL_IDX , 21, RC_MESH_NULL_IDX, RC_MESH_NULL_IDX, RC_MESH_NULL_IDX)
|
||||
|
||||
(1, 3, 4, 8) defines a polygon with 4 vertices.
|
||||
Edge 1->3 is shared with polygon 18.
|
||||
Edge 4->8 is shared with polygon 21.
|
||||
Edges 3->4 and 4->8 are border edges not shared with any other polygon.
|
||||
</pre>
|
||||
|
||||
@var rcPolyMesh::areas
|
||||
@par
|
||||
|
||||
The standard build process assigns the value of #RC_WALKABLE_AREA to all walkable polygons.
|
||||
This value can then be changed to meet user requirements.
|
||||
|
||||
@struct rcPolyMeshDetail
|
||||
@par
|
||||
|
||||
The detail mesh is made up of triangle sub-meshes that provide extra
|
||||
height detail for each polygon in its assoicated polygon mesh.
|
||||
|
||||
The standard process for building a detail mesh is to allocate it
|
||||
using #rcAllocPolyMeshDetail, then build it using #rcBuildPolyMeshDetail.
|
||||
|
||||
See the individual field definitions for details realted to the structure
|
||||
the mesh.
|
||||
|
||||
@see rcAllocPolyMeshDetail, rcFreePolyMeshDetail, rcBuildPolyMeshDetail, rcPolyMesh
|
||||
|
||||
@var rcPolyMeshDetail::meshes
|
||||
@par
|
||||
|
||||
[(baseVertIndex, vertCount, baseTriIndex, triCount) * #nmeshes]
|
||||
|
||||
Maximum number of vertices per sub-mesh: 127<br/>
|
||||
Maximum number of triangles per sub-mesh: 255
|
||||
|
||||
The sub-meshes are stored in the same order as the polygons from the
|
||||
rcPolyMesh they represent. E.g. rcPolyMeshDetail sub-mesh 5 is associated
|
||||
with #rcPolyMesh polygon 5.
|
||||
|
||||
Example of iterating the triangles in a sub-mesh.
|
||||
|
||||
@code
|
||||
// Where dmesh is a reference to a rcPolyMeshDetail object.
|
||||
|
||||
// Iterate the sub-meshes. (One for each source polygon.)
|
||||
for (int i = 0; i < dmesh.nmeshes; ++i)
|
||||
{
|
||||
const unsigned int* meshDef = &dmesh.meshes[i*4];
|
||||
const unsigned int baseVerts = meshDef[0];
|
||||
const unsigned int baseTri = meshDef[2];
|
||||
const int ntris = (int)meshDef[3];
|
||||
|
||||
const float* verts = &dmesh.verts[baseVerts*3];
|
||||
const unsigned char* tris = &dmesh.tris[baseTri*4];
|
||||
|
||||
// Iterate the sub-mesh's triangles.
|
||||
for (int j = 0; j < ntris; ++j)
|
||||
{
|
||||
const float x = verts[tris[j*4+0]*3];
|
||||
const float y = verts[tris[j*4+1]*3];
|
||||
const float z = verts[tris[j*4+2]*3];
|
||||
// Do something with the vertex.
|
||||
}
|
||||
}
|
||||
@endcode
|
||||
|
||||
@var rcPolyMeshDetail::verts
|
||||
@par
|
||||
|
||||
[(x, y, z) * #nverts]
|
||||
|
||||
The vertices are grouped by sub-mesh and will contain duplicates since
|
||||
each sub-mesh is independently defined.
|
||||
|
||||
The first group of vertices for each sub-mesh are in the same order as
|
||||
the vertices for the sub-mesh's associated PolyMesh polygon. These
|
||||
vertices are followed by any additional detail vertices. So it the
|
||||
associated polygon has 5 vertices, the sub-mesh will have a minimum
|
||||
of 5 vertices and the first 5 vertices will be equivalent to the 5
|
||||
polygon vertices.
|
||||
|
||||
@var rcPolyMeshDetail::tris
|
||||
@par
|
||||
|
||||
[(vertIndexA, vertIndexB, vertIndexC, flags) * #ntris]
|
||||
|
||||
The triangles are grouped by sub-mesh.
|
||||
|
||||
<b>Vertex Indices</b>
|
||||
|
||||
The vertex indices in the triangle array are local to the sub-mesh, not global.
|
||||
To translate into an global index in the vertices array, the values must be
|
||||
offset by the sub-mesh's base vertex index.
|
||||
|
||||
Example: If the baseVertexIndex for the sub-mesh is 5 and the triangle entry
|
||||
is (4, 8, 7, 0), then the actual indices for the vertices are (4 + 5, 8 + 5, 7 + 5).
|
||||
|
||||
@b Flags
|
||||
|
||||
The flags entry indicates which edges are internal and which are external to
|
||||
the sub-mesh. Internal edges connect to other triangles within the same sub-mesh.
|
||||
External edges represent portals to other sub-meshes or the null region.
|
||||
|
||||
Each flag is stored in a 2-bit position. Where position 0 is the lowest 2-bits
|
||||
and position 4 is the highest 2-bits:
|
||||
|
||||
<tt>
|
||||
Position 0: Edge AB (>> 0)<br/>
|
||||
Position 1: Edge BC (>> 2)<br/>
|
||||
Position 2: Edge CA (>> 4)<br/>
|
||||
Position 4: Unused<br/>
|
||||
</tt>
|
||||
|
||||
Testing can be performed as follows:
|
||||
|
||||
@code
|
||||
if (((flags >> 2) & 0x3) != 0)
|
||||
{
|
||||
// Edge BC is an external edge.
|
||||
}
|
||||
@endcode
|
||||
|
||||
@fn void rcSetCon(rcCompactSpan &span, int direction, int neighborIndex)
|
||||
@par
|
||||
|
||||
This function is used by the build process. It is rarely of use to end users.
|
||||
|
||||
@see #rcCompactHeightfield, #rcCompactSpan
|
||||
|
||||
@fn int rcGetCon(const rcCompactSpan &span, int direction)
|
||||
@par
|
||||
|
||||
Can be used to locate neighbor spans in a compact heightfield. See the
|
||||
#rcCompactHeightfield documentation for details on its use.
|
||||
|
||||
@see #rcCompactHeightfield, #rcCompactSpan
|
||||
|
||||
@fn int rcGetDirOffsetX(int direction)
|
||||
@par
|
||||
|
||||
The value of @p dir will be automatically wrapped. So a value of 6 will be interpreted as 2.
|
||||
|
||||
See the #rcCompactHeightfield documentation for usage details.
|
||||
|
||||
@fn int rcGetDirOffsetY(int direction)
|
||||
@par
|
||||
|
||||
The value of @p dir will be automatically wrapped. So a value of 6 will be interpreted as 2.
|
||||
|
||||
See the #rcCompactHeightfield documentation for usage details.
|
||||
|
||||
*/
|
||||
BIN
src/features/editScene/recastnavigation/Docs/Images/logo.png
Normal file
BIN
src/features/editScene/recastnavigation/Docs/Images/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 450 KiB |
34
src/features/editScene/recastnavigation/Docs/Readme.md
Normal file
34
src/features/editScene/recastnavigation/Docs/Readme.md
Normal file
@@ -0,0 +1,34 @@
|
||||
This directory contains source files for the documentation as well as the generated doxygen output.
|
||||
|
||||
# Directory Layout
|
||||
|
||||
`. (Docs root)` High level content and format files. (E.g. css, header, footer.)
|
||||
|
||||
`./Extern` API documentation that is located outside the source files. When the API documentation gets too big or complex for the header and source files, it goes in this directory.
|
||||
|
||||
`./Images` Images related to the documentation.
|
||||
|
||||
`./html` The target for the Doxygen build. (Created during the build process.)
|
||||
|
||||
# Documentation Style
|
||||
|
||||
One of the requirements for the API documentation is that it
|
||||
has the minimum possible impact on the declarations in the
|
||||
header files. So, in general, the header file declarations only
|
||||
contain summary documentation. The detail documentation
|
||||
is placed as follows:
|
||||
|
||||
1. If an element is defined in a cpp file, then place
|
||||
the detail documentation in the source file.
|
||||
2. If an element does not have an associated cpp file, then
|
||||
place the detail documentation at the end of the header file.
|
||||
3. If there is a lot of detail documentation cluttering up
|
||||
the end of a header file, then the content is moved to
|
||||
a separate file in the Extern directory.
|
||||
|
||||
# Building the Documentation
|
||||
|
||||
1. Download and install the appropriate Doxygen version. (See the first line in the Doxyfile for the current version.)
|
||||
2. Run "doxygen" in the project root directory. (The location of the Doxyfile.) No arguments are required.
|
||||
|
||||
The generated html files will be located in the /Docs/html directory.
|
||||
@@ -0,0 +1,85 @@
|
||||
# Introduction
|
||||
|
||||
## What is a Navmesh and how does it work?
|
||||
|
||||
Most modern, complex 3D games rely on [navmeshes](https://en.wikipedia.org/wiki/Navigation_mesh) to drive their pathfinding and AI agent movement and navigation.
|
||||
|
||||
A navmesh is a simplified view of the traversable area of a game world, optimized for AI pathfiding and movement calculations. Navmeshes are usually built from the physics colliders of the world, since they define the areas an AI can legally exist.
|
||||
|
||||
Triangles in a navmesh represent nodes in the pathfinding graph, and edges between adjacent polygons define connections between these nodes. Pathfinding queries first construct a path consisting of a strip of adjacent polygons. This represents the "corridor" of space that leads the agent to their goal. That polygonal path is refined into sequence of line-segments through a [string-pulling algorithm](http://idm-lab.org/bib/abstracts/papers/socs20c.pdf). Agents can then follow this sequence of waypoints to reach their goal.
|
||||
|
||||
### Agent Attributes
|
||||
|
||||
A navmesh is usually tailored to a specific type of AI agent with a specific size and set of movement capabilities. After all, the areas that a huge ogre, or a medium human, or a tiny mouse can move in are very different. Because of this, it's often necessary to build multiple navmeshes for the same environment to accommodate different agent types.
|
||||
|
||||
AI size is the most important attribute and is defined in Recast as a radius and height. You can think of these values as representing either a cylinder or a capsule.
|
||||
|
||||
A maximum slope value defines the steepest angle geometry a character can walk. This helps ensure it doesn't try to walk up or down steep cliffs.
|
||||
|
||||
Finally, a maximum "step up" height defines the highest obstacle an AI agent can step up. This helps Recast know that an AI shouldn't get stuck thinking it can't step over a small curb or up a flight of stairs.
|
||||
|
||||
## What is a Navmesh not?
|
||||
|
||||
While a navmesh is a simplified version of a world's collision geometry, it is solving a fundamentally different problem. Very often, navmesh generation will sacrifice some accuracy in favor of geometry simplification. With navmesh pathfinding in games, it's more often than not better to have faster, slightly less accurate queries, than extremely-precise optimal paths.
|
||||
|
||||
For these reasons you should not use the navmesh or paths generated by the navmesh as collision geometry or animation splines. Detour provides an AI movement controller that demonstrates how path-following should work with the paths generated by navmesh path queries.
|
||||
|
||||
## What is Recast Navigation?
|
||||
|
||||
Recast is a set of tools for fully automating the navmesh building process, loading and streaming navmesh data, pathfidning and querying navmeshes at runtime, controlling AI agent movement, and more. It was originally created by Mikko Mononen in 2009 and has shipped in countless indie games, AAA titles, and commercial and open-source game engines such as Unity, Unreal, Godot, and O3DE.
|
||||
|
||||
Recast is roughly divided into two parts. "Recast" is the process that automates the navmesh building process, and is what most games and game engines use. "Detour" is the set of runtime systems for working with navmesh data, performing path queries, and controlling agent movement.
|
||||
|
||||
# High-level overview of the Recast Navmesh-Building Process
|
||||
|
||||
Recast builds navmeshes utilizing an mesh rasterization process. This process takes your input geometry, converts it to voxels, performs transformations to refine and filter the voxel data, and then re-triangulates voxels into an output navmesh. This has a number of benefits:
|
||||
|
||||
* Robustly handles large amounts of overlapping geometry, small triangles, and imperfect geometry.
|
||||
* Allows for large amounts of customization and specialization of the navigation data. Recasts's process allows you to hook in your own code for things like automated cover-point generation.
|
||||
* Clear, fine-grained performance/quality tradeoff. By modifying the rasterization voxel size, it's possible to significantly alter the generation time and quality of the resulting navmesh to suit specific workflow or budget needs.
|
||||
* Issues with navmesh generation are easy to debug and visualize as voxels, rather than large sets of complex geometry.
|
||||
* The voxelization process allows for straightforward mesh re-building and streaming.
|
||||
|
||||
Recast showcases two approaches to building navmeshes in the "RecastDemo" project.
|
||||
|
||||
`Sample_SoloMesh.cpp` shows the basics of building a single navmesh for a small game world. This process is the simplest version of working with Recast. The build process is outlined in `Sample_SoloMesh::handleBuild`, but essentially breaks down to calling the following functions:
|
||||
|
||||
```
|
||||
// Figure out how big the raster voxel grid will be based on the input geometry bounds.
|
||||
rcCalcGridSize
|
||||
|
||||
// Voxelize the input geometry
|
||||
rcAllocHeightfield
|
||||
rcCreateHeightfield
|
||||
rcMarkWalkableTriangles
|
||||
rcRasterizeTriangles
|
||||
|
||||
// Clean up the voxel data and filter out non-walkable areas.
|
||||
rcFilterLowHangingWalkableObstacles
|
||||
rcFilterLedgeSpans
|
||||
rcFilterWalkableLowHeightSpans
|
||||
|
||||
// Consolidate the voxel data into a more compact representation
|
||||
rcAllocCompactHeightfield
|
||||
rcBuildCompactHeightfield
|
||||
|
||||
// Further refine the voxel representation
|
||||
rcErodeWalkableArea
|
||||
rcBuildDistanceField
|
||||
rcBuildRegions
|
||||
|
||||
// Triangulate the navmesh polygons from the voxel data
|
||||
rcAllocContourSet
|
||||
rcBuildContours
|
||||
rcAllocPolyMesh
|
||||
rcBuildPolyMesh
|
||||
|
||||
// Package the mesh with additional metadata that's useful at runtime.
|
||||
rcAllocPolyMeshDetail
|
||||
rcBuildPolyMeshDetail
|
||||
|
||||
// Cleanup
|
||||
rcFreeHeightField
|
||||
rcFreeCompactHeightfield
|
||||
rcFreeContourSet
|
||||
```
|
||||
@@ -0,0 +1,83 @@
|
||||
# Building & Integrating
|
||||
|
||||
Recast is designed to be integrated into your project as source files and built with whatever build system you use.
|
||||
|
||||
For building RecastDemo and for Recast library development there are [premake5](http://premake.github.io/) and [cmake](https://cmake.org/) build scripts. Premake5 is recommended unless you're already familiar with cmake.
|
||||
|
||||
## Building RecastDemo
|
||||
|
||||
While Recast and Detour don't require any dependencies, RecastDemo requires SDL (not included), and the unit tests rely on Catch2 (included in the repository).
|
||||
|
||||
The following steps outline the process to install SDL2 in the appropriate location and build RecastDemo with Premake and your local . Before you begin, you'll need to [download](https://github.com/premake/premake-core/releases) and install premake5 and ensure that the `premake5` executable location is included in your system path.
|
||||
|
||||
You can also reference the github actions build script `.github/Build.yaml` for examples of building with specific platform and toolchain combinations.
|
||||
|
||||
### Windows
|
||||
|
||||
- Grab the latest SDL2 development library release from [here](https://github.com/libsdl-org/SDL) and unzip it into `RecastDemo/Contrib`. Rename the SDL folder such that the path `RecastDemo/Contrib/SDL/lib/x86` is valid.
|
||||
- Navigate to the `RecastDemo` folder and run `premake5 vs2022`
|
||||
- Open `Build/vs2022/recastnavigation.sln` in Visual Studio 2022 or Jetbrains Rider.
|
||||
- Set `RecastDemo` as the startup project, build, and run.
|
||||
|
||||
### macOS
|
||||
|
||||
- Grab the latest SDL2 development library dmg from [here](https://github.com/libsdl-org/SDL) and place `SDL2.framework` in `RecastDemo/Bin`
|
||||
- Navigate to the `RecastDemo` folder and run `premake5 xcode4`
|
||||
- Open `Build/xcode4/recastnavigation.xcworkspace` in XCode
|
||||
- Set the RecastDemo project as the target and build.
|
||||
|
||||
### Linux
|
||||
|
||||
- Install SDL2 and its dependencies according to your distro's guidelines.
|
||||
- Navigate to the `RecastDemo` folder and run `premake5 gmake`
|
||||
- Navigate to `RecastDemo/Build/gmake` and run `make`
|
||||
- Navigate to `RecastDemo/Bin` and run `./RecastDemo`
|
||||
|
||||
## Preprocessor Defines
|
||||
|
||||
There are a few symbols you may wish to define when building Recast.
|
||||
|
||||
| Symbol | Usage |
|
||||
|-------------------------|--------------------------------------------------------------------------------------------------------------------------|
|
||||
| `RC_DISABLE_ASSERTS` | Disables assertion macros. Useful for release builds that need to maximize performance. You can also customize Recasts's assetion behavior with your own assertion handler. See `RecastAssert.h` and `DetourAssert.h`.
|
||||
| `DT_POLYREF64` | Use 64 bit (rather than 32 bit) polygon ID references. Generally not needed, but sometimes useful for very large worlds. |
|
||||
| `DT_VIRTUAL_QUERYFILTER`| Define this if you plan to sub-class `dtQueryFilter`. Enables the virtual destructor in `dtQueryFilter`. |
|
||||
|
||||
## Running Unit tests
|
||||
|
||||
- Follow the instructions to build RecastDemo above. Premake should generate another build target called "Tests".
|
||||
- Build the "Tests" project. This will generate an executable named "Tests" in `RecastDemo/Bin/`
|
||||
- Run the "Tests" executable. It will execute all the unit tests, indicate those that failed, and display a count of those that succeeded. Check out the [Catch2 documentation](https://github.com/catchorg/Catch2/blob/devel/docs/command-line.md#top) for information on additional command line options.
|
||||
|
||||
## Integration
|
||||
|
||||
There are a few ways to integrate Recast and Detour into your project. Source integration is the most popular and most flexible, and is what the project was designed for from the beginning.
|
||||
|
||||
### Source Integration
|
||||
|
||||
It is recommended to add the source directories `DebugUtils`, `Detour`, `DetourCrowd`, `DetourTileCache`, and `Recast` directly into your project depending on which parts of the project you need. For example your level building tool could include `DebugUtils`, `Recast`, and `Detour`, and your game runtime could just include `Detour`.
|
||||
|
||||
- *Recast*: Core navmesh building system.
|
||||
- *Detour*: Runtime navmesh interface and query system
|
||||
- *DetourCrowd*: Runtime movement, obstacle avoidance and crowd sim systems
|
||||
- *DetourTileCache*: Runtime navmesh dynamic obstacle and re-baking system
|
||||
|
||||
### Installation through vcpkg
|
||||
|
||||
If you are using the [vcpkg](https://github.com/Microsoft/vcpkg/) dependency manager you can alternatively download, build, and install Recast with:
|
||||
|
||||
```
|
||||
vcpkg install recast
|
||||
```
|
||||
|
||||
## Customizing Allocation Behavior
|
||||
|
||||
Recast and Detour strive to avoid heap allocations whenever possible. In the cases where they are needed, all allocations are routed through allocation functions that by default just wrap `malloc` and `free`. Additionally, the `rcAllocHint` enum gives some coarse-grained information about the intended lifetime of the allocation.
|
||||
|
||||
You can specify your own `rcAllocFunc` and `rcFreeFunc` in `RecastAlloc.cpp` (and similarly in `DetourAlloc.cpp`) to tune heap usage to your specific needs.
|
||||
|
||||
## A Note on DLL exports and C API
|
||||
|
||||
Recast does not yet provide a stable C API for use in a DLL or as bindings for another language. The design of Recast relies on some C++ specific features, so providing a stable API is not easy without a few significant changes to Recast.
|
||||
|
||||
There are a number of projects that offer unofficial language bindings for Recast, but official support for a C API is currently on our [development roadmap](_99_Roadmap.md).
|
||||
42
src/features/editScene/recastnavigation/Docs/_3_FAQ.md
Normal file
42
src/features/editScene/recastnavigation/Docs/_3_FAQ.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# FAQ
|
||||
|
||||
## Which C++ version and features do Recast use?
|
||||
|
||||
All code in Recast and Detour strictly adheres to the following:
|
||||
|
||||
* C++98
|
||||
* no STL
|
||||
* no exceptions
|
||||
* no RTTI
|
||||
* minimal inheritance
|
||||
* minimal templates
|
||||
|
||||
RecastDemo is a bit looser with these requirements, as it's only meant to showcase Recast usage and functionality, not be part of a shipped product.
|
||||
|
||||
## What coordinate system and triangle winding order does Recast use?
|
||||
|
||||
Recast expects clockwise-winding triangles and uses a right-handed, Y-up coordinate system.
|
||||
|
||||
## Why doesn't Recast use STL/Exceptions/RTTI/C++11/my favorite C++ feature?
|
||||
|
||||
Recast has always strived to maximize its ease of integration into an existing codebase, its portability to unique platforms, and its runtime performance. Recast was forged in the fires of game development, and as such follows the cultural norms of the games industry.
|
||||
|
||||
For example, some platforms have limited C++ compilers and STL implementations, so avoiding newer C++ features and the STL entirely helps tremendously when working in those environments. Additionally, exceptions and RTTI require a non-trivial runtime overhead to support. This is in conflict with Recast's goal of maximum performance.
|
||||
|
||||
## How do I use Recast to build a navmesh?
|
||||
|
||||
The process is thoroughly outlined and documented in the RecastDemo project. `Sample_SoloMesh.cpp` is a good introduction to the general process of building a navmesh. It builds a single, unified navmesh and is the simpler and more limited of the two examples. `Sample_TileMesh.cpp` builds a tiled navmesh that supports all of Recast and Detour's features around dynamic obstacles and runtime re-meshing.
|
||||
|
||||
## How do Recast and Detour handle memory allocations?
|
||||
|
||||
Recast and Detour strive to avoid heap allocations whenver possible. When heap allocations are necessary, they are routed through centralized allocation and de-allocation functions. These centralized functions allow you to optionally override them with custom allocator implementations, but just use `malloc` and `free` by default. Check out `RecastAlloc.h` and `DetourAlloc.h` for more.
|
||||
|
||||
## Does Recast do any logging?
|
||||
|
||||
Yes and no: recast provides a `log(...)` function through `rcContext`. There are currently 3 levels: progress, warning, and error. If logging is enabled, the context calls the doLog() virtual function which is empty by default.
|
||||
|
||||
## What are the dependencies for RecastDemo?
|
||||
|
||||
* SDL 2
|
||||
* OpenGL
|
||||
* GLU
|
||||
74
src/features/editScene/recastnavigation/Docs/_99_Roadmap.md
Normal file
74
src/features/editScene/recastnavigation/Docs/_99_Roadmap.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# Development Roadmap
|
||||
|
||||
This document describes the current development roadmap for Recast and Detour. It is initially focused on maintaining and polishing the current functionality in a way that makes it more appealing and easier to use, but also covers a few large pieces of new functionality.
|
||||
|
||||
If you're excited about contributing to Recast or want to understand what its future looks like, this roadmap is a great place to start.
|
||||
|
||||
## Short Term
|
||||
|
||||
### Documentation & Web Presence (WIP)
|
||||
- ✅ **Project website** (GitHub pages). A home for docs, info, tutorials, etc. that's easy to find and navigate. There's stuff like the wiki system in GitHub that can serve this purpose, but it's not the greatest.
|
||||
- ✅ **Hosted API Reference**: We have extensive doxygen docs that we should also host on github pages. Ideally this would be implemented as a job for the CI process.
|
||||
- **High-level design/overview**. Basically taking a lot of the "how does Recast work?" docs we have (and stuff on Mikko's blog) and surfacing them in a place that's easier to find. e.g. [this information](http://digestingduck.blogspot.com/2010/02/slides-from-past.html) should be integrated to the documentation.
|
||||
- **FAQ** to include answers to common questions like "can I use Recast on a spherical world?" etc. The small group of questions that come up often.
|
||||
- **Expand on configuration parameter docstrings**. Expand on the docstrings for the fields in `rcConfig`.
|
||||
- **Projects using recast page**. A place to show off integrations of Recast into different games and engines. Having this visible will help give people confidence in the quality of Recast.
|
||||
|
||||
### More explicit variable names (WIP)
|
||||
There's quite a few variables that are single-letter or very condensed acronyms. Stuff like `u`, `v`, `cs`, `nnei` etc. These are certainly easier to type than more descriptive names, and can allow for some nice code formatting, but the readability gains of less condensed names outweigh that. These should be updated to be more easily maintainable and less of a hurdle for newcomers.
|
||||
|
||||
### Opt-in config value validation system
|
||||
There's a number of documentation-defined valid value ranges, as well as some suggested value ranges for config values like those in rcConfig. Recast should have an _opt-in_ system for validating value ranges and value relationships. Some implementations [like Godot's](https://github.com/godotengine/godot/blob/c7ceb94e372216b1b033d7c2ac26d5b7545c4dac/modules/navigation/navigation_mesh_generator.cpp#L545-L568) include similar configuration value checks already. This would help alert users to potential problems arising from misconfiguration. Recast shouldn't restrict people from going outside the recommended bounds, but for the majority of users values that are out of bounds are likely a mistake and Recast should at least raise the concern when prompted.
|
||||
|
||||
## Medium Term
|
||||
|
||||
### STB-Style Single-Header Release Packaging
|
||||
For ease of integration. This should probably be implemented as a build packaging script, similar to the release packaging process for the Catch unit testing library.
|
||||
|
||||
### Ensure there's a good threading story
|
||||
Currently Recast provides no specific threading support. Many aspects of navmesh generation (especialy with tile generation) can be easily multithreaded, as can parts of detour (threaded pathfinding, etc). This likely requires a good example integration to work with - it probably shouldn't be designed in isolation.
|
||||
|
||||
### More Tests
|
||||
This is a good place to start when making any changes to the internals or fixing bugs. It's important to have a way to validate that bugfixes or enhancements are not adversely affecting the base Recast functionality. Lots of parts of the code are fairly simple to test, but some would require some extra effort with mocking data or similar.
|
||||
|
||||
### More POD structs for clarity in internals (WIP)
|
||||
Small structs like a simple 3-float POD vector would go a long way to help clarify a lot of the internals. Any changes here shouldn't impact performance or functionality. They should just be simple POD structs that get optimized away but make the code a bit clearer to read and work with.
|
||||
|
||||
### Revisit structural organization
|
||||
There some number of generally low-level geometry functions that are implemented both in Recast and in Detour. We should probably take stock of the general library layout and determine if there's enough of a benefit to slightly restructuring things to better share core functionality. There may not be enough code like this to be worth it, so it might not be a net win. Organization and re-use benefits should vastly outweighing the cost of defining a shared dependency to make this worthwhile. There's likely just minimal gains here since things are already laid out logically, but it's worth looking into.
|
||||
|
||||
## Longer-Term
|
||||
|
||||
### Higher-Level APIs
|
||||
The current API level is excellent for providing maximum control over the entire navmesh building process. For many people, integrating Recast involves copying over all the code from the tiled mesh demo with minimal tweaks. Recast can better serve these people, and hopefully boost adoption by providing some higher-level API calls.
|
||||
|
||||
### C API
|
||||
This would help tremendously with adoption. There are a number of integrations of Recast to other languages right now, and a number of them are using their own C API wrappers around the existing Recast API. Providing a first-party C API would help normalize the different integrations of Recast out there, as well as improve the integration process for people with engines written in C or other languages with C interop.
|
||||
|
||||
There's enough cases where we pass around object pointers in API calls that may cause this to be a bit more work than it seems.
|
||||
|
||||
# New Recast/Detour Functionality
|
||||
|
||||
## Nav Volumes & 3D Navigation
|
||||
Obviously a big feature that would enable flying agents. Probably not terribly difficult to integrate due to the voxelization process, but it's probably a lot of detailed geometric processes and challenges. We'd at least need to retain unwalkable polygons longer in the process (they get thrown away pretty early) so we should ensure it doesn't have any adversarial affects to the perf or capabilities of the normal navmesh process. 3d navigation should be a separate set of APIs, as the considerations and use cases are quite different than normal navmesh navigation.
|
||||
|
||||
## Climbing Markup & Navigation
|
||||
This is a common problem with many modern games. There's a number of ways to solve this, so we should make sure to consult with people who are actively using Recast and building games with climbing to understand their needs.
|
||||
|
||||
## Tooling
|
||||
Recast Demo already has a number of sample tools to explore and test the generated navmesh data. There's a number of middleware providers that tout workflow features and tooling for generating and manipulating navmesh data. RecastDemo could be expanded (and maybe re-named) to be a more fully fleshed out standalone tool, rather than be mostly focused as an integration demo and feature showcase. There's almost certainly a ton of custom data and markup most people will want to add to the generated navmeshes, so there should be a reasonable way for people to integrate their customizations and markup with any tooling Recast provide.
|
||||
|
||||
## More spatial querying abilities
|
||||
There's already a number of spatial query functions, and fleshing out that set some more is likely quite easy and useful.
|
||||
|
||||
## Auto-markup system
|
||||
This is something recast already gives you access to add yourself, thanks to its low-level API and how each step in the process is explicit. It is worthwhile to consider what kind of help other middleware systems provide in this process. For example, are other navigation middleware solutions providing a structured way to define spatial queries that result in nav markup? Some easy examples of useful features here might be automatic jump point or cover annotations. It would be good to talk to people in the AAA FPS space to see what markup types are most useful to them.
|
||||
|
||||
## Formations, Group behaviors
|
||||
Extending DetourCrowd to support formations, group navigation, group behaviors and similar beyond what's it's already capable of.
|
||||
|
||||
## Vehicle Navigation & Movement
|
||||
A feature set centered on pathfinding and movement planning for characters with kinematic movement constraints.
|
||||
|
||||
## Crowd Simulation and Flowfield movement systems
|
||||
Movement systems for large crowds that leverage flowfields or other highly-scalable navigation and movement approaches.
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 jothepro
|
||||
|
||||
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.
|
||||
@@ -0,0 +1,124 @@
|
||||
# Doxygen Awesome
|
||||
|
||||
[](https://github.com/jothepro/doxygen-awesome-css/releases/latest)
|
||||
[](https://github.com/jothepro/doxygen-awesome-css/blob/main/LICENSE)
|
||||

|
||||
|
||||
<div class="title_screenshot">
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
**Doxygen Awesome** is a custom **CSS theme for Doxygen HTML-documentation** with lots of customization parameters.
|
||||
|
||||
## Motivation
|
||||
|
||||
I really like how the Doxygen HTML-documentation is structured! But IMHO it looks a bit outdated.
|
||||
|
||||
This theme is an attempt to update the visuals of Doxygen without changing its overall layout too much.
|
||||
|
||||
## Features
|
||||
|
||||
- 🌈 Clean, modern design
|
||||
- 🚀 Heavily customizable by adjusting CSS-variables
|
||||
- 🧩 No changes to the HTML structure of Doxygen required
|
||||
- 📱 Improved mobile usability
|
||||
- 🌘 Dark mode support!
|
||||
- 🥇 Works best with **doxygen 1.9.1** - **1.9.4**
|
||||
|
||||
## Examples
|
||||
|
||||
Some websites using this theme:
|
||||
|
||||
- [Documentation of this repository](https://jothepro.github.io/doxygen-awesome-css/)
|
||||
- [wxWidgets](https://docs.wxwidgets.org/3.2/)
|
||||
- [OpenCV 5.x](https://docs.opencv.org/5.x/)
|
||||
- [Zephyr](https://docs.zephyrproject.org/latest/doxygen/html/index.html)
|
||||
- [FELTOR](https://mwiesenberger.github.io/feltor/dg/html/modules.html)
|
||||
- [Spatial Audio Framework (SAF)](https://leomccormack.github.io/Spatial_Audio_Framework/index.html)
|
||||
- [libCloudSync](https://jothepro.github.io/libCloudSync/)
|
||||
- [libsl3](https://a4z.github.io/libsl3/)
|
||||
|
||||
## Installation
|
||||
|
||||
To use the theme in your documentation, copy the required CSS and JS files from this repository into your project or add the repository as submodule and check out the latest release:
|
||||
|
||||
```bash
|
||||
git submodule add https://github.com/jothepro/doxygen-awesome-css.git
|
||||
cd doxygen-awesome-css
|
||||
git checkout v2.1.0
|
||||
```
|
||||
|
||||
All theme files are located in the root of this repository and start with the prefix `doxygen-awesome-`. You may not need all of them. Follow the install instructions to figure out what files are required for your setup.
|
||||
|
||||
### Choosing a layout
|
||||
|
||||
There is two layout options. Choose one of them and configure Doxygen accordingly:
|
||||
|
||||
<div class="darkmode_inverted_image">
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
#### Base Theme (1)
|
||||
|
||||
Comes with the typical Doxygen titlebar. Optionally the treeview in the sidebar can be enabled.
|
||||
|
||||
Required files: `doxygen-awesome.css`
|
||||
|
||||
Required `Doxyfile` configuration:
|
||||
```
|
||||
GENERATE_TREEVIEW = YES # optional. Also works without treeview
|
||||
DISABLE_INDEX = NO
|
||||
FULL_SIDEBAR = NO
|
||||
HTML_EXTRA_STYLESHEET = doxygen-awesome-css/doxygen-awesome.css
|
||||
```
|
||||
|
||||
#### Sidebar-Only Theme (2)
|
||||
|
||||
Hides the top titlebar to give more space to the content. The treeview must be enabled in order for this theme to work.
|
||||
|
||||
Required files: `doxygen-awesome.css`, `doxygen-awesome-sidebar-only.css`
|
||||
|
||||
Required `Doxyfile` configuration:
|
||||
```
|
||||
GENERATE_TREEVIEW = YES # required!
|
||||
DISABLE_INDEX = NO
|
||||
FULL_SIDEBAR = NO
|
||||
HTML_EXTRA_STYLESHEET = doxygen-awesome-css/doxygen-awesome.css \
|
||||
doxygen-awesome-css/doxygen-awesome-sidebar-only.css
|
||||
```
|
||||
|
||||
|
||||
**Caution**: This theme is not compatible with the `FULL_SIDEBAR = YES` option provided by Doxygen!
|
||||
|
||||
### Further installation instructions:
|
||||
|
||||
- [Installing extensions](docs/extensions.md)
|
||||
- [Customizing the theme (colors, spacing, border-radius, ...)](docs/customization.md)
|
||||
- [Tips and Tricks for further configuration](docs/tricks.md)
|
||||
|
||||
## Browser support
|
||||
|
||||
Tested with
|
||||
|
||||
- Chrome 104, Chrome 104 for Android, Chrome 103 for iOS
|
||||
- Safari 15, Safari for iOS 15
|
||||
- Firefox 103, Firefox 103 for Android, Firefox Daylight 102 for iOS
|
||||
- Edge 104
|
||||
|
||||
The theme does not strive to be backwards compatible to (significantly) older browser versions.
|
||||
|
||||
## Credits
|
||||
|
||||
- This theme was initially inspired by the [vuepress](https://vuepress.vuejs.org/) static site generator default theme.
|
||||
- Thank you for all the bug reports, pull requests and inspiring feedback on github!
|
||||
|
||||
<span class="next_section_button">
|
||||
|
||||
Read Next: [Extensions](docs/extensions.md)
|
||||
</span>
|
||||
@@ -0,0 +1,157 @@
|
||||
/**
|
||||
|
||||
Doxygen Awesome
|
||||
https://github.com/jothepro/doxygen-awesome-css
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 - 2022 jothepro
|
||||
|
||||
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.
|
||||
|
||||
*/
|
||||
|
||||
class DoxygenAwesomeDarkModeToggle extends HTMLElement {
|
||||
// SVG icons from https://fonts.google.com/icons
|
||||
// Licensed under the Apache 2.0 license:
|
||||
// https://www.apache.org/licenses/LICENSE-2.0.html
|
||||
static lightModeIcon = `<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#FCBF00"><rect fill="none" height="24" width="24"/><circle cx="12" cy="12" opacity=".3" r="3"/><path d="M12,9c1.65,0,3,1.35,3,3s-1.35,3-3,3s-3-1.35-3-3S10.35,9,12,9 M12,7c-2.76,0-5,2.24-5,5s2.24,5,5,5s5-2.24,5-5 S14.76,7,12,7L12,7z M2,13l2,0c0.55,0,1-0.45,1-1s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S1.45,13,2,13z M20,13l2,0c0.55,0,1-0.45,1-1 s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S19.45,13,20,13z M11,2v2c0,0.55,0.45,1,1,1s1-0.45,1-1V2c0-0.55-0.45-1-1-1S11,1.45,11,2z M11,20v2c0,0.55,0.45,1,1,1s1-0.45,1-1v-2c0-0.55-0.45-1-1-1C11.45,19,11,19.45,11,20z M5.99,4.58c-0.39-0.39-1.03-0.39-1.41,0 c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0s0.39-1.03,0-1.41L5.99,4.58z M18.36,16.95 c-0.39-0.39-1.03-0.39-1.41,0c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0c0.39-0.39,0.39-1.03,0-1.41 L18.36,16.95z M19.42,5.99c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06c-0.39,0.39-0.39,1.03,0,1.41 s1.03,0.39,1.41,0L19.42,5.99z M7.05,18.36c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06 c-0.39,0.39-0.39,1.03,0,1.41s1.03,0.39,1.41,0L7.05,18.36z"/></svg>`
|
||||
static darkModeIcon = `<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#FE9700"><rect fill="none" height="24" width="24"/><path d="M9.37,5.51C9.19,6.15,9.1,6.82,9.1,7.5c0,4.08,3.32,7.4,7.4,7.4c0.68,0,1.35-0.09,1.99-0.27 C17.45,17.19,14.93,19,12,19c-3.86,0-7-3.14-7-7C5,9.07,6.81,6.55,9.37,5.51z" opacity=".3"/><path d="M9.37,5.51C9.19,6.15,9.1,6.82,9.1,7.5c0,4.08,3.32,7.4,7.4,7.4c0.68,0,1.35-0.09,1.99-0.27C17.45,17.19,14.93,19,12,19 c-3.86,0-7-3.14-7-7C5,9.07,6.81,6.55,9.37,5.51z M12,3c-4.97,0-9,4.03-9,9s4.03,9,9,9s9-4.03,9-9c0-0.46-0.04-0.92-0.1-1.36 c-0.98,1.37-2.58,2.26-4.4,2.26c-2.98,0-5.4-2.42-5.4-5.4c0-1.81,0.89-3.42,2.26-4.4C12.92,3.04,12.46,3,12,3L12,3z"/></svg>`
|
||||
static title = "Toggle Light/Dark Mode"
|
||||
|
||||
static prefersLightModeInDarkModeKey = "prefers-light-mode-in-dark-mode"
|
||||
static prefersDarkModeInLightModeKey = "prefers-dark-mode-in-light-mode"
|
||||
|
||||
static _staticConstructor = function() {
|
||||
DoxygenAwesomeDarkModeToggle.enableDarkMode(DoxygenAwesomeDarkModeToggle.userPreference)
|
||||
// Update the color scheme when the browsers preference changes
|
||||
// without user interaction on the website.
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => {
|
||||
DoxygenAwesomeDarkModeToggle.onSystemPreferenceChanged()
|
||||
})
|
||||
// Update the color scheme when the tab is made visible again.
|
||||
// It is possible that the appearance was changed in another tab
|
||||
// while this tab was in the background.
|
||||
document.addEventListener("visibilitychange", visibilityState => {
|
||||
if (document.visibilityState === 'visible') {
|
||||
DoxygenAwesomeDarkModeToggle.onSystemPreferenceChanged()
|
||||
}
|
||||
});
|
||||
}()
|
||||
|
||||
static init() {
|
||||
$(function() {
|
||||
$(document).ready(function() {
|
||||
const toggleButton = document.createElement('doxygen-awesome-dark-mode-toggle')
|
||||
toggleButton.title = DoxygenAwesomeDarkModeToggle.title
|
||||
toggleButton.updateIcon()
|
||||
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => {
|
||||
toggleButton.updateIcon()
|
||||
})
|
||||
document.addEventListener("visibilitychange", visibilityState => {
|
||||
if (document.visibilityState === 'visible') {
|
||||
toggleButton.updateIcon()
|
||||
}
|
||||
});
|
||||
|
||||
$(document).ready(function(){
|
||||
document.getElementById("MSearchBox").parentNode.appendChild(toggleButton)
|
||||
})
|
||||
$(window).resize(function(){
|
||||
document.getElementById("MSearchBox").parentNode.appendChild(toggleButton)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.onclick=this.toggleDarkMode
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns `true` for dark-mode, `false` for light-mode system preference
|
||||
*/
|
||||
static get systemPreference() {
|
||||
return window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns `true` for dark-mode, `false` for light-mode user preference
|
||||
*/
|
||||
static get userPreference() {
|
||||
return (!DoxygenAwesomeDarkModeToggle.systemPreference && localStorage.getItem(DoxygenAwesomeDarkModeToggle.prefersDarkModeInLightModeKey)) ||
|
||||
(DoxygenAwesomeDarkModeToggle.systemPreference && !localStorage.getItem(DoxygenAwesomeDarkModeToggle.prefersLightModeInDarkModeKey))
|
||||
}
|
||||
|
||||
static set userPreference(userPreference) {
|
||||
DoxygenAwesomeDarkModeToggle.darkModeEnabled = userPreference
|
||||
if(!userPreference) {
|
||||
if(DoxygenAwesomeDarkModeToggle.systemPreference) {
|
||||
localStorage.setItem(DoxygenAwesomeDarkModeToggle.prefersLightModeInDarkModeKey, true)
|
||||
} else {
|
||||
localStorage.removeItem(DoxygenAwesomeDarkModeToggle.prefersDarkModeInLightModeKey)
|
||||
}
|
||||
} else {
|
||||
if(!DoxygenAwesomeDarkModeToggle.systemPreference) {
|
||||
localStorage.setItem(DoxygenAwesomeDarkModeToggle.prefersDarkModeInLightModeKey, true)
|
||||
} else {
|
||||
localStorage.removeItem(DoxygenAwesomeDarkModeToggle.prefersLightModeInDarkModeKey)
|
||||
}
|
||||
}
|
||||
DoxygenAwesomeDarkModeToggle.onUserPreferenceChanged()
|
||||
}
|
||||
|
||||
static enableDarkMode(enable) {
|
||||
if(enable) {
|
||||
DoxygenAwesomeDarkModeToggle.darkModeEnabled = true
|
||||
document.documentElement.classList.add("dark-mode")
|
||||
document.documentElement.classList.remove("light-mode")
|
||||
} else {
|
||||
DoxygenAwesomeDarkModeToggle.darkModeEnabled = false
|
||||
document.documentElement.classList.remove("dark-mode")
|
||||
document.documentElement.classList.add("light-mode")
|
||||
}
|
||||
}
|
||||
|
||||
static onSystemPreferenceChanged() {
|
||||
DoxygenAwesomeDarkModeToggle.darkModeEnabled = DoxygenAwesomeDarkModeToggle.userPreference
|
||||
DoxygenAwesomeDarkModeToggle.enableDarkMode(DoxygenAwesomeDarkModeToggle.darkModeEnabled)
|
||||
}
|
||||
|
||||
static onUserPreferenceChanged() {
|
||||
DoxygenAwesomeDarkModeToggle.enableDarkMode(DoxygenAwesomeDarkModeToggle.darkModeEnabled)
|
||||
}
|
||||
|
||||
toggleDarkMode() {
|
||||
DoxygenAwesomeDarkModeToggle.userPreference = !DoxygenAwesomeDarkModeToggle.userPreference
|
||||
this.updateIcon()
|
||||
}
|
||||
|
||||
updateIcon() {
|
||||
if(DoxygenAwesomeDarkModeToggle.darkModeEnabled) {
|
||||
this.innerHTML = DoxygenAwesomeDarkModeToggle.darkModeIcon
|
||||
} else {
|
||||
this.innerHTML = DoxygenAwesomeDarkModeToggle.lightModeIcon
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("doxygen-awesome-dark-mode-toggle", DoxygenAwesomeDarkModeToggle);
|
||||
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
|
||||
Doxygen Awesome
|
||||
https://github.com/jothepro/doxygen-awesome-css
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 jothepro
|
||||
|
||||
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.
|
||||
|
||||
*/
|
||||
|
||||
class DoxygenAwesomeFragmentCopyButton extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
this.onclick=this.copyContent
|
||||
}
|
||||
static title = "Copy to clipboard"
|
||||
static copyIcon = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/></svg>`
|
||||
static successIcon = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z"/></svg>`
|
||||
static successDuration = 980
|
||||
static init() {
|
||||
$(function() {
|
||||
$(document).ready(function() {
|
||||
if(navigator.clipboard) {
|
||||
const fragments = document.getElementsByClassName("fragment")
|
||||
for(const fragment of fragments) {
|
||||
const fragmentWrapper = document.createElement("div")
|
||||
fragmentWrapper.className = "doxygen-awesome-fragment-wrapper"
|
||||
const fragmentCopyButton = document.createElement("doxygen-awesome-fragment-copy-button")
|
||||
fragmentCopyButton.innerHTML = DoxygenAwesomeFragmentCopyButton.copyIcon
|
||||
fragmentCopyButton.title = DoxygenAwesomeFragmentCopyButton.title
|
||||
|
||||
fragment.parentNode.replaceChild(fragmentWrapper, fragment)
|
||||
fragmentWrapper.appendChild(fragment)
|
||||
fragmentWrapper.appendChild(fragmentCopyButton)
|
||||
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
copyContent() {
|
||||
const content = this.previousSibling.cloneNode(true)
|
||||
// filter out line number from file listings
|
||||
content.querySelectorAll(".lineno, .ttc").forEach((node) => {
|
||||
node.remove()
|
||||
})
|
||||
let textContent = content.textContent
|
||||
// remove trailing newlines that appear in file listings
|
||||
let numberOfTrailingNewlines = 0
|
||||
while(textContent.charAt(textContent.length - (numberOfTrailingNewlines + 1)) == '\n') {
|
||||
numberOfTrailingNewlines++;
|
||||
}
|
||||
textContent = textContent.substring(0, textContent.length - numberOfTrailingNewlines)
|
||||
navigator.clipboard.writeText(textContent);
|
||||
this.classList.add("success")
|
||||
this.innerHTML = DoxygenAwesomeFragmentCopyButton.successIcon
|
||||
window.setTimeout(() => {
|
||||
this.classList.remove("success")
|
||||
this.innerHTML = DoxygenAwesomeFragmentCopyButton.copyIcon
|
||||
}, DoxygenAwesomeFragmentCopyButton.successDuration);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("doxygen-awesome-fragment-copy-button", DoxygenAwesomeFragmentCopyButton)
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
|
||||
Doxygen Awesome
|
||||
https://github.com/jothepro/doxygen-awesome-css
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 jothepro
|
||||
|
||||
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.
|
||||
|
||||
*/
|
||||
|
||||
class DoxygenAwesomeInteractiveToc {
|
||||
static topOffset = 38
|
||||
static hideMobileMenu = true
|
||||
static headers = []
|
||||
|
||||
static init() {
|
||||
window.addEventListener("load", () => {
|
||||
let toc = document.querySelector(".contents > .toc")
|
||||
if(toc) {
|
||||
toc.classList.add("interactive")
|
||||
if(!DoxygenAwesomeInteractiveToc.hideMobileMenu) {
|
||||
toc.classList.add("open")
|
||||
}
|
||||
document.querySelector(".contents > .toc > h3")?.addEventListener("click", () => {
|
||||
if(toc.classList.contains("open")) {
|
||||
toc.classList.remove("open")
|
||||
} else {
|
||||
toc.classList.add("open")
|
||||
}
|
||||
})
|
||||
|
||||
document.querySelectorAll(".contents > .toc > ul a").forEach((node) => {
|
||||
let id = node.getAttribute("href").substring(1)
|
||||
DoxygenAwesomeInteractiveToc.headers.push({
|
||||
node: node,
|
||||
headerNode: document.getElementById(id)
|
||||
})
|
||||
|
||||
document.getElementById("doc-content")?.addEventListener("scroll", () => {
|
||||
DoxygenAwesomeInteractiveToc.update()
|
||||
})
|
||||
})
|
||||
DoxygenAwesomeInteractiveToc.update()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
static update() {
|
||||
let active = DoxygenAwesomeInteractiveToc.headers[0]?.node
|
||||
DoxygenAwesomeInteractiveToc.headers.forEach((header) => {
|
||||
let position = header.headerNode.getBoundingClientRect().top
|
||||
header.node.classList.remove("active")
|
||||
header.node.classList.remove("aboveActive")
|
||||
if(position < DoxygenAwesomeInteractiveToc.topOffset) {
|
||||
active = header.node
|
||||
active?.classList.add("aboveActive")
|
||||
}
|
||||
})
|
||||
active?.classList.add("active")
|
||||
active?.classList.remove("aboveActive")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
|
||||
Doxygen Awesome
|
||||
https://github.com/jothepro/doxygen-awesome-css
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 jothepro
|
||||
|
||||
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.
|
||||
|
||||
*/
|
||||
|
||||
class DoxygenAwesomeParagraphLink {
|
||||
// Icon from https://fonts.google.com/icons
|
||||
// Licensed under the Apache 2.0 license:
|
||||
// https://www.apache.org/licenses/LICENSE-2.0.html
|
||||
static icon = `<svg xmlns="http://www.w3.org/2000/svg" height="20px" viewBox="0 0 24 24" width="20px"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M17 7h-4v2h4c1.65 0 3 1.35 3 3s-1.35 3-3 3h-4v2h4c2.76 0 5-2.24 5-5s-2.24-5-5-5zm-6 8H7c-1.65 0-3-1.35-3-3s1.35-3 3-3h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-2zm-3-4h8v2H8z"/></svg>`
|
||||
static title = "Permanent Link"
|
||||
static init() {
|
||||
$(function() {
|
||||
$(document).ready(function() {
|
||||
document.querySelectorAll(".contents a.anchor[id], .contents .groupheader > a[id]").forEach((node) => {
|
||||
let anchorlink = document.createElement("a")
|
||||
anchorlink.setAttribute("href", `#${node.getAttribute("id")}`)
|
||||
anchorlink.setAttribute("title", DoxygenAwesomeParagraphLink.title)
|
||||
anchorlink.classList.add("anchorlink")
|
||||
node.classList.add("anchor")
|
||||
anchorlink.innerHTML = DoxygenAwesomeParagraphLink.icon
|
||||
node.parentElement.appendChild(anchorlink)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
|
||||
/**
|
||||
|
||||
Doxygen Awesome
|
||||
https://github.com/jothepro/doxygen-awesome-css
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 jothepro
|
||||
|
||||
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.
|
||||
|
||||
*/
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
|
||||
#MSearchBox {
|
||||
width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)) - var(--searchbar-height) - 1px);
|
||||
}
|
||||
|
||||
#MSearchField {
|
||||
width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)) - 66px - var(--searchbar-height));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
|
||||
Doxygen Awesome
|
||||
https://github.com/jothepro/doxygen-awesome-css
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 jothepro
|
||||
|
||||
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.
|
||||
|
||||
*/
|
||||
|
||||
html {
|
||||
/* side nav width. MUST be = `TREEVIEW_WIDTH`.
|
||||
* Make sure it is wide enough to contain the page title (logo + title + version)
|
||||
*/
|
||||
--side-nav-fixed-width: 335px;
|
||||
--menu-display: none;
|
||||
|
||||
--top-height: 120px;
|
||||
--toc-sticky-top: -25px;
|
||||
--toc-max-height: calc(100vh - 2 * var(--spacing-medium) - 25px);
|
||||
}
|
||||
|
||||
#projectname {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
html {
|
||||
--searchbar-background: var(--page-background-color);
|
||||
}
|
||||
|
||||
#side-nav {
|
||||
min-width: var(--side-nav-fixed-width);
|
||||
max-width: var(--side-nav-fixed-width);
|
||||
top: var(--top-height);
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
#nav-tree, #side-nav {
|
||||
height: calc(100vh - var(--top-height)) !important;
|
||||
}
|
||||
|
||||
#nav-tree {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#top {
|
||||
display: block;
|
||||
border-bottom: none;
|
||||
height: var(--top-height);
|
||||
margin-bottom: calc(0px - var(--top-height));
|
||||
max-width: var(--side-nav-fixed-width);
|
||||
overflow: hidden;
|
||||
background: var(--side-nav-background);
|
||||
}
|
||||
#main-nav {
|
||||
float: left;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.ui-resizable-handle {
|
||||
cursor: default;
|
||||
width: 1px !important;
|
||||
box-shadow: 0 calc(-2 * var(--top-height)) 0 0 var(--separator-color);
|
||||
}
|
||||
|
||||
#nav-path {
|
||||
position: fixed;
|
||||
right: 0;
|
||||
left: var(--side-nav-fixed-width);
|
||||
bottom: 0;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
#doc-content {
|
||||
height: calc(100vh - 31px) !important;
|
||||
padding-bottom: calc(3 * var(--spacing-large));
|
||||
padding-top: calc(var(--top-height) - 80px);
|
||||
box-sizing: border-box;
|
||||
margin-left: var(--side-nav-fixed-width) !important;
|
||||
}
|
||||
|
||||
#MSearchBox {
|
||||
width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)));
|
||||
}
|
||||
|
||||
#MSearchField {
|
||||
width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)) - 65px);
|
||||
}
|
||||
|
||||
#MSearchResultsWindow {
|
||||
left: var(--spacing-medium) !important;
|
||||
right: auto;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user