Compare commits

..

3 Commits

Author SHA1 Message Date
d4bac06d94 Added recastnavigation support 2026-02-06 18:07:42 +03:00
0405214388 New game works almost as intended, quest system 2026-02-05 18:21:25 +03:00
4fb7e94fed Fixed up GOAP support 2026-02-04 04:45:32 +03:00
56 changed files with 13209 additions and 732 deletions

View File

@@ -71,6 +71,7 @@ add_subdirectory(src/world)
add_subdirectory(src/tests)
add_subdirectory(src/physics)
add_subdirectory(src/editor)
add_subdirectory(src/crowd)
add_subdirectory(assets/blender/buildings/parts)
add_subdirectory(assets/blender/characters)
add_subdirectory(resources)
@@ -82,6 +83,7 @@ target_link_libraries(Game OgreBites OgrePaging OgreTerrain OgreMeshLodGenerator
GameData
sound
sceneloader physics
OgreCrowd
flecs::flecs_static
-Wl,--as-needed
)

155
Game.cpp
View File

@@ -17,6 +17,7 @@
#include "TerrainModule.h"
#include "GUIModuleCommon.h"
#include "AppModule.h"
#include "GUIModule.h"
#include "sound.h"
class App;
class SkyRenderer : public Ogre::SceneManager::Listener {
@@ -470,16 +471,10 @@ public:
// TODO: implement rough water level calculation
float getWaterLevel(const Ogre::Vector3 &position)
{
Ogre::Vector3::UNIT_Y;
float etime =
Ogre::ControllerManager::getSingleton().getElapsedTime();
return 0.0f;
}
void updateWorld(float delta)
{
#if 0
mDynWorld->getBtWorld()->stepSimulation(delta, 3);
#endif
if (!ECS::get().has<ECS::GUI>())
goto end;
{
@@ -495,13 +490,9 @@ public:
}
end:
ECS::update(delta);
#if 0
if (ECS::get<ECS::EngineData>().enableDbgDraw)
mDbgDraw->update();
#endif
}
class InputListenerChainFlexible : public OgreBites::InputListener {
#if 0
class InputListenerChainFlexible : public OgreBites::InputListener {
protected:
std::vector<OgreBites::InputListener *> mListenerChain;
@@ -604,6 +595,7 @@ end:
bool
mousePressed(const OgreBites::MouseButtonEvent &evt) override
{
std::cout << mListenerChain.size() << std::endl;
for (auto listner : mListenerChain) {
if (listner->mousePressed(evt))
return true;
@@ -628,6 +620,7 @@ end:
return false;
}
};
#endif
flecs::entity input_update;
flecs::entity find_wait_gui;
void setupInput()
@@ -652,108 +645,44 @@ end:
"Skybox/Dynamic", "General");
OgreAssert(m, "Sky box material not found.");
m->load();
ECS::get().component<ECS::RenderWindow>().add(flecs::Singleton);
ECS::get().set<ECS::RenderWindow>(
{ getRenderWindow(), getDisplayDPI() });
ECS::setupExteriorScene(mScnMgr,
/*mDynWorld.get(), */ mCameraNode,
mCamera, getRenderWindow());
ECS::get().set<ECS::RenderWindow>(
{ getRenderWindow(), getDisplayDPI() });
ECS::get()
.observer<ECS::GUI>("UpdateGrab")
.event(flecs::OnSet)
.each([this](ECS::GUI &gui) {
if (gui.grabChanged)
setWindowGrab(gui.grab);
std::cout << "grab: " << gui.grab << "\n";
std::cout << "GUI enabled: " << gui.enabled
<< "\n";
});
ECS::get()
.observer<ECS::App>("UpdateInputListener")
.event(flecs::OnSet)
.each([this](ECS::App &app) {
if (app.mInput)
removeInputListener(app.mInput);
delete app.mInput;
app.mInput =
OGRE_NEW OgreBites::InputListenerChain(
app.listeners);
addInputListener(app.mInput);
});
#if 0
ECS::get()
.observer<ECS::GUI, ECS::App>("SetInputListener2")
.event(flecs::OnSet)
.each([this](ECS::GUI &gui, ECS::App &app) {
if (gui.mGuiInpitListener &&
app.listeners.size() == 1) {
app.listeners.clear();
app.listeners.push_back(
gui.mGuiInpitListener);
app.listeners.push_back(&mKbd);
ECS::modified<ECS::App>();
}
});
#endif
#if 0
input_update =
ECS::get()
.system<ECS::GUI, ECS::App>("SetInputListener")
.kind(flecs::OnUpdate)
.each([this](ECS::GUI &gui, ECS::App &app) {
if (app.listeners.size() < 2 &&
gui.mGuiInpitListener) {
OgreBites::InputListener *guiListener =
gui.mGuiInpitListener;
if (guiListener) {
app.listeners.clear();
app.listeners.push_back(
guiListener);
app.listeners.push_back(
&mKbd);
std::cout
<< "input update complete\n";
gui.mGuiInpitListener =
guiListener;
if (app.mInput)
removeInputListener(
app.mInput);
delete app.mInput;
app.mInput = new OgreBites::
InputListenerChain(
app.listeners);
addInputListener(
app.mInput);
std::cout
<< "update listeners: "
<< app.listeners
.size()
<< "\n";
if (app.listeners
.size() ==
2)
OgreAssert(
app.listeners.size() ==
2,
"");
input_update.disable();
OgreAssert(false, "");
} else {
app.listeners.clear();
app.listeners.push_back(
&mKbd);
}
} else
input_update.disable();
std::cout << "input update "
<< app.listeners.size()
<< "\n";
});
#endif
ECS::get().set<ECS::App>(
{ initialiseImGui(),
nullptr,
{ getImGuiInputListener(), &mKbd } });
Sound::setup();
ECS::get()
.observer<ECS::App>("UpdateInputListener")
.event(flecs::OnSet)
.each([this](ECS::App &app) {
if (app.mInput)
removeInputListener(app.mInput);
delete app.mInput;
app.mInput =
OGRE_NEW OgreBites::InputListenerChain(
app.listeners);
addInputListener(app.mInput);
});
ECS::get().set<ECS::App>(
{ initialiseImGui(),
nullptr,
{ getImGuiInputListener(), &mKbd } });
/* FIXME: this is bad */
ECS::GUIModule::configure();
ECS::get()
.observer<ECS::GUI>("UpdateGrab")
.event(flecs::OnSet)
.each([this](ECS::GUI &gui) {
if (gui.grabChanged)
setWindowGrab(gui.grab);
std::cout << "grab: " << gui.grab << "\n";
std::cout << "GUI enabled: " << gui.enabled
<< "\n";
});
ECS::get_mut<ECS::GUI>().grab = false;
ECS::get_mut<ECS::GUI>().grabChanged = true;
ECS::modified<ECS::GUI>();
Sound::setup();
Sound::ding();
}
void create_entity_node(const Ogre::String &name, int key)
@@ -786,10 +715,6 @@ end:
{
return mCamera;
}
flecs::entity getPlayer() const
{
return ECS::player;
}
void enableDbgDraw(bool enable)
{
ECS::get_mut<ECS::EngineData>().enableDbgDraw = enable;

View File

@@ -219,30 +219,99 @@ function Quest(name, book)
return quest
end
function StartGameQuest()
local quest = {}
-- Parse a book from the Ink file.
local book = narrator.parse_file('stories.initiation')
local quest = Quest('start game', book)
quest.base = {}
quest.base.activate = quest.activate
quest.base.complete = quest.complete
quest.boat = false
quest.activate = function(this)
print('activate...')
local mc_is_free = function()
this.boat = true
local ent = ecs_get_player_entity()
ecs_character("params-set", ent, "gravity", true)
ecs_character("params-set", ent, "buoyancy", true)
end
this.story:bind('mc_is_free', mc_is_free)
this.base.activate(this)
local book = narrator.parse_file('stories.initiation')
local story = narrator.init_story(book)
this._add_narration({
book = book,
story = story,
activate = function(this)
local mc_is_free = function()
this.boat = true
local ent = ecs_get_player_entity()
ecs_character("params-set", ent, "gravity", true)
ecs_character("params-set", ent, "buoyancy", true)
end
this.story:bind('mc_is_free', mc_is_free)
this.story:begin()
this:narration_update()
end,
event = function(this, event)
if event == "narration_progress" then
this:narration_update()
end
if event == "narration_answered" then
local answer = this._get_narration_answer()
this.story:choose(answer)
this:narration_update()
end
end,
finish = function(this)
end,
narration_update = function(this)
local ret = ""
local choices = {}
local have_choice = false
local have_paragraph = false
print("narration_update")
if this.story:can_continue() then
print("CAN continue")
have_paragraph = true
local paragraph = this.story:continue(1)
print(dump(paragraph))
local text = paragraph.text
if paragraph.tags then
-- text = text .. ' #' .. table.concat(paragraph.tags, ' #')
for i, tag in ipairs(paragraph.tags) do
if tag == 'discard' then
text = ''
elseif tag == 'crash' then
print(text)
crash()
end
this:handle_tag(tag)
end
end
ret = text
if this.story:can_choose() then
have_choice = true
local ch = this.story:get_choices()
for i, choice in ipairs(ch) do
table.insert(choices, choice.text)
print(i, dump(choice))
end
if #choices == 1 and choices[1] == "Ascend" then
this.story:choose(1)
choices = {}
end
if #choices == 1 and choices[1] == "Continue" then
this.story:choose(1)
choices = {}
end
end
else
print("can NOT continue")
end
print(ret)
if (#choices > 0) then
print("choices!!!")
this._narration(ret, choices)
else
this._narration(ret, {})
end
if not have_choice and not have_paragraph then
this._finish()
end
end,
handle_tag = function(this, tag)
print("tag: " .. tag)
end,
})
end
quest.complete = function(this)
this.base.complete(this)
this.active = false
if not this.boat then
ecs_save_object_debug(boat, 'boat.scene')
end
quest.finish = function(this)
end
return quest
end
@@ -616,25 +685,27 @@ setup_handler(function(event, trigger_entity, what_entity)
return
end
if event == "startup" then
main_menu()
--
elseif event == "narration_progress" then
print("narration progress!")
elseif event == "narration_answered" then
local answer = narration_get_answer()
print("answered:", answer)
elseif event == "spawn_player" then
elseif event == "new_game" then
local ent = ecs_get_player_entity()
ecs_character("params-set", ent, "gravity", true)
ecs_character("params-set", ent, "buoyancy", false)
local quest = StartGameQuest()
quests[quest.name] = quest
for k, v in pairs(quests) do
print(k, v.active)
end
quest:activate()
local start_boat = create_boat()
table.insert(vehicles, start_boat)
table.insert(player_vehicles, start_boat)
local quest = StartGameQuest()
add_quest("main", quest)
-- quests[quest.name] = quest
-- for k, v in pairs(quests) do
-- print(k, v.active)
-- end
-- quest:activate()
elseif event == "actuator_created" then
print(trigger_entity)
local act = create_actuator2(trigger_entity)
@@ -829,3 +900,5 @@ setup_action_handler("talk", function(town, index, word)
end)
]]--
main_menu()

33
src/crowd/CMakeLists.txt Normal file
View File

@@ -0,0 +1,33 @@
cmake_minimum_required(VERSION 3.10)
project(OgreCrowd)
find_package(OGRE REQUIRED CONFIG)
find_package(RecastNavigation REQUIRED CONFIG)
include_directories(include src fastlz)
set(ogrecrowdcpp
src/AnimateableCharacter.cpp
src/Character.cpp
src/ConvexShapeObstacle.cpp
src/CrowdManager.cpp
src/CylinderObstacle.cpp
src/InstancedCharacter.cpp
src/Obstacle.cpp
src/OgreDetourCrowd.cpp
src/OgreDetourTileCache.cpp
src/OgreRecast.cpp
src/OgreRecastNavmeshPruner.cpp
src/RecastConvexHull.cpp
src/RecastInputGeom.cpp
src/TestCharacter.cpp
fastlz/fastlz.c
)
add_library(OgreCrowd ${ogrecrowdcpp} )
target_link_libraries(OgreCrowd PRIVATE OgreMain PUBLIC RecastNavigation::Recast RecastNavigation::Detour RecastNavigation::DebugUtils RecastNavigation::DetourCrowd RecastNavigation::DetourTileCache)
INSTALL(TARGETS OgreCrowd
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib)

21
src/crowd/License.txt Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2012 Jonas Hauquier
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.

View File

@@ -0,0 +1,75 @@
FastLZ - lightning-fast lossless compression library
Author: Ariya Hidayat
Official website: http://www.fastlz.org
FastLZ is distributed using the MIT license, see file LICENSE
for details.
FastLZ consists of two files: fastlz.h and fastlz.c. Just add these
files to your project in order to use FastLZ. For information on
compression and decompression routines, see fastlz.h.
A simple file compressor called 6pack is included as an example
on how to use FastLZ. The corresponding decompressor is 6unpack.
To compile using GCC:
gcc -o 6pack 6pack.c fastlz.c
gcc -o 6unpack 6unpack.c fastlz.c
To compile using MinGW:
mingw32-gcc -o 6pack 6pack.c fastlz.c
mingw32-gcc -o 6unpack 6unpack.c fastlz.c
To compile using Microsoft Visual C++:
cl 6pack.c fastlz.c
cl 6unpack.c fastlz.c
To compile using Borland C++:
bcc32 6pack.c fastlz.c
bcc32 6unpack.c fastlz.c
To compile using OpenWatcom C/C++:
cl386 6pack.c fastlz.c
cl386 6unpack.c fastlz.c
To compile using Intel C++ compiler for Windows:
icl 6pack.c fastlz.c
icl 6unpack.c fastlz.c
To compile using Intel C++ compiler for Linux:
icc -o 6pack 6pack.c fastlz.c
icc -o 6unpack 6unpack.c fastlz.c
To compile 6pack using LCC-Win32:
lc 6pack.c fastlz.c
lc 6unpack.c fastlz.c
To compile 6pack using Pelles C:
pocc 6pack.c
pocc 6unpack.c
pocc fastlz.c
polink 6pack.obj fastlz.obj
polink 6unpack.obj fastlz.obj
For speed optimization, always use proper compile flags for optimization options.
Typical compiler flags are given below:
* GCC (pre 4.2): -march=pentium -O3 -fomit-frame-pointer -mtune=pentium
* GCC 4.2 or later: -march=pentium -O3 -fomit-frame-pointer -mtune=generic
* Digital Mars C/C++: -o+all -5
* Intel C++ (Windows): /O3 /Qipo
* Intel C++ (Linux): -O2 -march=pentium -mtune=pentium
* Borland C++: -O2 -5
* LCC-Win32: -O
* Pelles C: /O2

556
src/crowd/fastlz/fastlz.c Normal file
View 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/crowd/fastlz/fastlz.h Normal file
View 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 */

View File

@@ -0,0 +1,108 @@
/*
OgreCrowd
---------
Copyright (c) 2012 Jonas Hauquier
Additional contributions by:
- mkultra333
- Paul Wilson
Sincere thanks and to:
- Mikko Mononen (developer of Recast navigation libraries)
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 ANIMATEABLECHARACTER_H
#define ANIMATEABLECHARACTER_H
#include "Character.h"
/**
* More complex character that shows an animated walking human to represent an agent.
* This is probably closer to what you would use in a real scenario. This class can just
* be replaced with your character class if you already have one and it mainly serves
* as an example of how characters can be integrated with detour agents.
**/
class AnimateableCharacter : public Character
{
public:
/**
* Create a new human character with specified name, the entities will be placed in the specified scene manager.
* detourCrowd is the crowd manager in which an agent for this character will be created (make sure you don't create
* more characters than MAX_AGENTS).
* Set debugDraw to true to initially draw debug geometry (can be disabled afterwards too).
* Position defines initial position the character has to be placed on (should be a valid position on the navmesh).
**/
AnimateableCharacter(Ogre::String name, Ogre::SceneManager* sceneMgr, OgreDetourCrowd* detourCrowd, bool debugDraw = false, Ogre::Vector3 position = Ogre::Vector3::ZERO);
/**
* The entity that represents this character in the scene
**/
virtual Ogre::Entity* getEntity(void);
/**
* Update one tick in the render loop. Advances animation and character position.
* In order for the agents to be updated, you first need to call the detourCrowd
* update function.
**/
virtual void update(Ogre::Real timeSinceLastFrame);
/**
* @see{Character::setDebugVisibility(bool)}
**/
virtual void setDebugVisibility(bool visible);
virtual bool getDebugVisibility(void);
virtual void show(void);
void randomizeAnimationPosition(void);
protected:
bool mDebugDraw;
/**
* Main entity that represents this character.
**/
Ogre::Entity *mEnt;
/**
* Currently active animation state.
**/
Ogre::AnimationState* mAnimState;
/**
* Speed scaling factor of the animation playback.
**/
Ogre::Real mAnimSpeedScale;
/**
* Scenenode that stores all geometry related to
* recast debug drawing. Can be made visible with
* setDebugVisibility().
**/
Ogre::SceneNode *mDebugNode;
};
#endif // ANIMATEABLECHARACTER_H

View File

@@ -0,0 +1,365 @@
/*
OgreCrowd
---------
Copyright (c) 2012 Jonas Hauquier
Additional contributions by:
- mkultra333
- Paul Wilson
Sincere thanks and to:
- Mikko Mononen (developer of Recast navigation libraries)
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 CHARACTER_H
#define CHARACTER_H
#include <Ogre.h>
#include "OgreDetourCrowd.h"
#include "DetourCrowd.h"
#include "OgreDetourTileCache.h"
/**
* An animeatable character that acts as crowd agent
**/
class Character
{
public:
enum QueryFlags {
DEFAULT_MASK = 1u<<0,
NAVMESH_MASK = 1u<<1,
OBSTACLE_MASK= 1u<<2
};
/**
* Create a character with specified name, for which entities will be drawn on the specified scene manager.
* detourCrowd specifies the detour crowd manager on which an agent for this character will be created
* (make sure you don't create more characters than MAX_AGENTS).
* Position defines initial position the character has to be placed on (should be a valid position on the navmesh).
**/
Character(Ogre::String name, Ogre::SceneManager* sceneMgr, OgreDetourCrowd* detourCrowd, Ogre::Vector3 position = Ogre::Vector3::ZERO);
~Character();
/**
* The delta offset an agent must be from its destination before considering the destination reached.
* Set this as the squared value of the actual delta value (squared distance is calculated for perfocmance).
**/
static const Ogre::Real DESTINATION_RADIUS;
/**
* The scenenode this character is attached to
**/
virtual Ogre::SceneNode* getNode(void) const;
/**
* The height of the agent for this character.
**/
virtual Ogre::Real getAgentHeight(void) const;
/**
* The radius of the agent for this character.
**/
virtual Ogre::Real getAgentRadius(void) const;
/**
* Update this character for drawing a new frame.
* Updates one tick in the render loop.
* In order for the agents to be updated, you first need to call the detourCrowd
* update function.
* What is updated specifically is up to a specific implementation of Character,
* but at the very least the position in the scene should be updated to reflect
* the detour agent position (possibly with additional physics engine clipping
* and collision testing).
**/
virtual void update(Ogre::Real timeSinceLastFrame) = 0;
/**
* Update the destination for this agent.
* If updatePreviousPath is set to true the previous path will be reused instead
* of calculating a completely new path, but this can only be used if the new
* destination is close to the previous (eg. when chasing a moving entity).
**/
virtual void updateDestination(Ogre::Vector3 destination, bool updatePreviousPath= false);
/**
* The destination set for this character.
**/
virtual Ogre::Vector3 getDestination(void) const;
/**
* Place character at new position.
* The character will start following the globally set destination in the detourCrowd,
* unless you give it an individual destination using updateDestination().
**/
virtual void setPosition(Ogre::Vector3 position);
/**
* The current position of the agent.
* Is only up to date once update() has been called in a frame.
**/
virtual Ogre::Vector3 getPosition(void) const;
/**
* Index ID identifying the agent of this character in the crowd
**/
virtual int getAgentID();
/**
* The agent that steers this character within the crowd
**/
virtual const dtCrowdAgent* getAgent();
/**
* Returns true when this character has reached its set destination.
**/
virtual bool destinationReached(void);
/**
* Request to set a manual velocity for this character, to control it
* manually.
* The set velocity will stay active, meaning that the character will
* keep moving in the set direction, until you stop() it.
* Manually controlling a character offers no absolute control as the
* laws of acceleration and max speed still apply to an agent, as well
* as the fact that it cannot steer off the navmesh or into other agents.
* You will notice small corrections to steering when walking into objects
* or the border of the navmesh (which is usually a wall or object).
**/
void setVelocity(Ogre::Vector3 velocity);
/**
* Manually control the character moving it forward.
**/
virtual void moveForward(void);
/**
* Stop any movement this character is currently doing. This means losing
* the requested velocity or target destination.
**/
virtual void stop(void);
/**
* The current velocity (speed and direction) this character is traveling
* at.
**/
virtual Ogre::Vector3 getVelocity(void);
/**
* The current speed this character is traveling at.
**/
virtual Ogre::Real getSpeed(void);
/**
* The maximum speed this character can attain.
* This parameter is configured for the agent controlling this character.
**/
virtual Ogre::Real getMaxSpeed(void);
/**
* The maximum acceleration this character has towards its maximum speed.
* This parameter is configured for the agent controlling this character.
**/
virtual Ogre::Real getMaxAcceleration(void);
/**
* Returns true if this character is moving.
**/
virtual bool isMoving(void);
/**
* The direction in which the character is currently looking.
**/
virtual Ogre::Vector3 getLookingDirection(void);
/**
* Set to true to show visual recast debugging geometry to represent
* the agent of this character..
* Will be initialized to OgreRecastApplication::getDebugDrawing()
* at character construction.
**/
virtual void setDebugVisibility(bool visible) = 0;
/**
* Set whether this character is controlled by an agent or whether it
* will position itself independently based on the requested velocity.
* Set to true to let the character be controlled by an agent.
* Set to false to manually control it without agent, you need to set
* detourTileCache first.
**/
void setAgentControlled(bool agentControlled);
/**
* Determines whether this character is controlled by an agent.
**/
bool isAgentControlled(void);
/**
* Set detour tile cache component.
* This is needed for controlling the agent manually without agent,
* as it will use dtTileCache to add a temporary obstacle at its
* current position to make other characters in the crowd avoid it.
**/
void setDetourTileCache(OgreDetourTileCache* dtTileCache);
/**
* Makes the character clip to the terrain height of the specified
* terrain set. Specify NULL as terrainGroup to disable.
* Height clipping only happens for the visiual character entity,
* the position of the detour crowd agent that controls it is
* not changed.
**/
void clipToTerrain(Ogre::TerrainGroup *terrainGroup);
bool isLoaded(void) const { return mAgentID >= 0; }
virtual void load(void);
virtual void load(Ogre::Vector3 position);
virtual void unLoad(void);
virtual void show(void);
virtual void hide(void);
virtual Ogre::Vector3 getRelativeLookingDirection(void);
virtual void setRelativeLookingDirection(Ogre::Vector3 direction);
protected:
/**
* Update current position of this character to the current position of its agent.
**/
virtual void updatePosition(Ogre::Real timeSinceLastFrame);
/**
* Set destination member variable directly without updating the agent state.
* Usually you should call updateDestination() externally, unless you are controlling
* the agents directly and need to update the corresponding character class to reflect
* the change in state (see OgreRecastApplication friendship).
**/
void setDestination(Ogre::Vector3 destination);
/**
* Scene manager that manages this character.
**/
Ogre::SceneManager *mSceneMgr;
/**
* Node in which this character is.
**/
Ogre::SceneNode *mNode;
/**
* Name of this character.
**/
Ogre::String mName;
/**
* Crowd in which the agent of this character is.
**/
OgreDetourCrowd *mDetourCrowd;
/**
* The agent controlling this character.
**/
const dtCrowdAgent *mAgent;
/**
* ID of mAgent within the crowd.
**/
int mAgentID;
/**
* The current destination set for this agent.
* Take care in properly setting this variable, as it is only updated properly when
* using Character::updateDestination() to set an individual destination for this character.
* After updating the destination of all agents this variable should be set externally using
* setDestination().
**/
Ogre::Vector3 mDestination;
/**
* Velocity set for this agent for manually controlling it.
* If this is not zero then a manually set velocity is currently controlling the movement
* of this character (not pathplanning towards a set destination).
**/
Ogre::Vector3 mManualVelocity;
/**
* True if this character is stopped.
**/
bool mStopped;
/**
* True if character is controlled by agent.
* False if character is manually controlled without agent.
**/
bool mAgentControlled;
/**
* Detour Tile Cache component needed when controlling this character
* manually without agent. It needs the dtTileCache to place a temporary
* obstacle at its current position to make other characters in the crowd
* avoid it.
**/
OgreDetourTileCache* mDetourTileCache;
/**
* Obstacle used when character is not being controlled by agent.
* The temp obstacle is placed on the current position of this character
* so that other agents in the crowd will not walk through it.
**/
dtTileRef mTempObstacle;
/**
* Helper to fix the height of the character to the terrain height after
* a position update. Only the visual character entity's height will be
* altered, not that of the detour crowd agent that controls it.
**/
virtual void clipToTerrainHeight(void);
/**
* The terraingroup the character height will be clipped to.
* Set to NULL to disable this feature.
**/
Ogre::TerrainGroup *mClipTo;
/**
* Ray query used to query the terrain height when terrain clipping is
* enabled. This value is stored as a member for optimization.
**/
Ogre::RaySceneQuery *mRaySceneQuery;
Ogre::Vector3 mLookingDirection;
// Friend the application class to allow setDestinationForAllAgents to update character destination values
friend class OgreRecastApplication;
};
#endif // CHARACTER_H

View File

@@ -0,0 +1,152 @@
/*
OgreCrowd
---------
Copyright (c) 2012 Jonas Hauquier
Additional contributions by:
- mkultra333
- Paul Wilson
Sincere thanks and to:
- Mikko Mononen (developer of Recast navigation libraries)
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 CONVEXSHAPEOBSTACLE_H
#define CONVEXSHAPEOBSTACLE_H
#include "Obstacle.h"
#include "RecastConvexHull.h"
/**
* Convex obstacles are a more complex type of temporary obstacles that can be used together with
* the Detour Tile Cache.
* In contrast to simple cylindrical obstacles they allow any type of geometry to be defined as an obstacle.
* A convex obstacle relies on a simple convex hull of the obstacle it represents. It uses a simplified 2D
* approximation of the convex hull with a height (the hull is on the x-z ground plane, and has one height
* that is the same for each point, kind of like an upright cylinder where the sides don't have to be simply
* round but can be any shape).
* Convex obstacles can both be moved and given a different orientation (rotation). When rotating the convex
* hull approximation has to be rebuilt, as it is only a simple 2D approximation instead of a full 3D hull.
* Also note that the convex hull algorithm that is currently used is very fast but also very limited. Don't
* feed it too much geometry.
* Alternatively, when not rotating obstacles it's also possible to use the bounding box of the object instead
* of calculating a hull (in case the bounding box approximates the obstacle well, for example in case of a
* box or rectangular shape).
**/
class ConvexShapeObstacle : public Obstacle
{
public:
/**
* Construct a convex shape obstacle on the specified position.
* To make this work with detour tileCache a simplified convex hull is created
* from the obstacle entity's geometry. The hull is offset with the specified
* amount from the mesh (offset should generally be agent radius).
* DetourTileCache parameter specifies the tilecache on which the obstacle will
* be added.
**/
ConvexShapeObstacle(Ogre::Vector3 position, Ogre::Real offset, OgreDetourTileCache *detourTileCache);
virtual ~ConvexShapeObstacle(); // Important that the destructor is virtual!
/**
* @see{Obstacle::update()}
**/
virtual void update(long time);
/**
* @see{Obstacle::getEntity()}
**/
virtual Ogre::Entity* getEntity(void);
/**
* @see{Obstacle::getPosition()}
**/
virtual Ogre::Vector3 getPosition(void);
/**
* @see{Obstacle::getOrientation()}
**/
virtual Ogre::Quaternion getOrientation(void);
/**
* Update the orientation (mainly intended for rotation and position) of the obstacle
* to the specified orientation.
* Replaces the old rotation completely (this is not cumulative).
**/
virtual void updateOrientation(Ogre::Quaternion orientation);
/**
* @see{Obstacle::updatePosition(Ogre::Vector3)}
**/
virtual void updatePosition(Ogre::Vector3 position);
protected:
/**
* Current position of this obstacle.
**/
Ogre::Vector3 mPosition;
/**
* Current orientation of this obstacle.
**/
Ogre::Quaternion mOrientation;
/**
* The entity representing this obstacle in the scene.
**/
Ogre::Entity *mEnt;
/**
* The scene node in which the obstacle entity is located.
**/
Ogre::SceneNode *mNode;
/**
* Name of this obstacle.
**/
Ogre::String mName;
/**
* 2D Convex hull with height calculated for this obstacle (for the current rotation).
**/
ConvexVolume *mConvexHull;
/**
* Inputgeom object containing the original geometry of this obstacle (used to generate convex hull from).
**/
InputGeom *mInputGeom;
/**
* Amount that the convex hull is offset from the inputGeom. Normally detour requires the hulls to be offset
* with at least the radius of the agents.
**/
Ogre::Real mOffset;
/**
* Visual debug representation of the calculated convex hull, in the form of a line drawing.
**/
Ogre::ManualObject *mConvexHullDebug;
};
#endif // CONVEXSHAPEOBSTACLE_H

View File

@@ -0,0 +1,208 @@
/*
OgreCrowd
---------
Copyright (c) 2012 Jonas Hauquier
Additional contributions by:
- mkultra333
- Paul Wilson
Sincere thanks and to:
- Mikko Mononen (developer of Recast navigation libraries)
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 CROWDMANAGER_H
#define CROWDMANAGER_H
#include <Ogre.h>
class Character;
class OgreRecast;
class OgreDetourCrowd;
class OgreDetourTileCache;
// TODO maybe add support for "persistent" characters, eg. those that are always visible, or at least of which the agent and AI remains when out of view distance
/**
* Manages and instances a crowd of characters in the vicinity of the camera.
*
**/
class CrowdManager
{
public:
CrowdManager(OgreDetourTileCache *tileCache, Ogre::SceneManager *sceneManager, Ogre::Camera *camera);
void setCamera(Ogre::Camera *camera) { mCamera = camera; }
Ogre::Camera* getCamera(void) { return mCamera; }
void update(Ogre::Real timeSinceLastFrame);
int getNbLoadedTiles(void);
int getNbBorderTiles(void);
void setDebugVisibility(bool visible);
/**
* The size of the crowd
**/
int getSize(void) { return mCrowdSize; }
int getNbAssignedAgents(void) { return mAssignedCharacters.size(); }
int getGridDimensions(void) { return mDimension; }
// TODO define setters for these?
static const Ogre::Real CROWD_PAGE_UPDATE_DELTA;
static const Ogre::Real MAX_CROWD_SIZE;
static const Ogre::Real RADIUS_EPSILON;
static bool HUMAN_CHARACTERS;
static bool INSTANCED_CROWD;
// TODO move this struct to OgreDetourTileCache??
// TODO functionality corresponds largely to OgreDetourTileCache::TileSelection, merge them without breaking anything
struct NavmeshTileSet
{
int xMin; // Min tile X index (inclusive)
int yMin; // Min tile Y index (inclusive)
int xMax; // Max tile X index (inclusive)
int yMax; // Min tile Y index (inclusive)
int getXWidth(void) { return 1+ xMax - xMin ; }
int getYWidth(void) { return 1+ yMax - yMin; }
int getNbTiles(void) { return getXWidth()*getYWidth(); }
};
protected:
/**
* Calculate the navmesh tile-aligned bounding area around the
* current camera position that has to be populated with crowd agents.
**/
NavmeshTileSet calculatePopulatedArea(void);
bool updatePagedCrowd(Ogre::Real timeSinceLastFrame);
void loadAgents(int tx, int ty, int nbAgents);
void unloadAgents(int tx, int ty);
// Make sure tileExists(tx,ty) !!
Ogre::Vector3 placeAgent(Character* character, int tx, int ty);
void placeAgentOnRandomBorderTile(Character *character);
Ogre::Vector3 getRandomPositionInNavmeshTile(int tx, int ty);
Ogre::Vector3 getRandomPositionInNavmeshTileSet(NavmeshTileSet tileSet);
Ogre::AxisAlignedBox getNavmeshTileSetBounds(NavmeshTileSet tileSet);
/**
* Determines whether the tile with specified grid coordinates exists
* and is loaded (in the tilecache).
**/
virtual bool tileExists(int tx, int ty);
void updatePagedAreaDebug(NavmeshTileSet pagedArea);
void debugPrint(Ogre::String message);
Ogre::String tileToStr(int tx, int ty);
Ogre::String tileToStr(Ogre::Vector2 tilePos);
Ogre::Vector3 assignAgentDestination(Character* character);
void unloadAgentsOutsideArea(NavmeshTileSet area);
NavmeshTileSet getExistingArea(NavmeshTileSet area);
void updateBorderTiles(void);
virtual bool walkedOffGrid(const Character* character);
virtual void initAgents(void);
OgreDetourTileCache *mDetourTileCache;
OgreRecast *mRecast;
OgreDetourCrowd *mDetourCrowd;
Ogre::SceneManager *mSceneMgr;
/**
* Camera around which the crowd is intantiated
**/
Ogre::Camera *mCamera;
/**
* All characters in the scene that represent an agent.
**/
std::vector<Character*> mCharacters;
// TODO do I need this list?
std::vector<Character*> mAssignedCharacters; // TODO a linked list might be better
std::vector<Character*> mUnassignedCharacters;
NavmeshTileSet mCurrentlyPagedArea;
/**
* Size (in number of navmesh tiles) in x and y direction
* that the paged area centered around camera position will
* continue and will be populated with agents.
* Defines the size of the area to be populated with agents.
**/
int mPagedAreaDistance;
// TODO allow other methods of selecting area to page? Like a circle around camera position
int mNbPagedTiles;
int mNbTilesInBorder;
int mCrowdSize;
int mDimension;
Ogre::Real mTimeSinceLastUpdate;
Ogre::Entity *mAreaDebug;
/**
* Current visibility of recast visual debug structures.
* True renders them in the scene, false hides them.
**/
bool mDebugDraw;
std::vector<Ogre::Vector2> mBorderTiles;
Ogre::InstanceManager* mInstanceManager;
};
#endif // CROWDMANAGER_H

View File

@@ -0,0 +1,120 @@
/*
OgreCrowd
---------
Copyright (c) 2012 Jonas Hauquier
Additional contributions by:
- mkultra333
- Paul Wilson
Sincere thanks and to:
- Mikko Mononen (developer of Recast navigation libraries)
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 CYLINDEROBSTACLE_H
#define CYLINDEROBSTACLE_H
#include "Obstacle.h"
/**
* A cylinder obstacle is the most simple of obstacles (and probably the fastest).
* Cylinder obstacles are upright cylinders (cannot be rotated, only be moved) that have
* a height, a radius and are placed at a specified position.
* Cylindrical obstacles are ideal for symbolizing things like persons or pillars (in fact
* detourCrowd agents are also cylinders) and are very fast to update on the navmesh.
**/
class CylinderObstacle : public Obstacle
{
public:
/**
* Construct a simple cylindrical obstacle with radius TEMP_OBSTACLE_RADIUS,
* at specified position.
**/
CylinderObstacle(Ogre::Vector3 position, OgreDetourTileCache *detourTileCache);
~CylinderObstacle();
/**
* @see{Obstacle::update()}
**/
virtual void update(long time);
/**
* The reference to the temp obstacle for this obstacle in the detourTileCache.
**/
virtual dtObstacleRef getObstacleRef(void);
/**
* @see{Obstacle::getEntity()}
**/
virtual Ogre::Entity* getEntity(void);
/**
* @see{Obstacle::getPosition()}
**/
virtual Ogre::Vector3 getPosition(void);
/**
* @see{Obstacle::getOrientation()}
**/
virtual Ogre::Quaternion getOrientation(void);
/**
* Not applicable to cylindrical obstacles. Does nothing.
**/
virtual void updateOrientation(Ogre::Quaternion orientation);
/**
* @see{Obstacle::updatePosition(Ogre::Vector3)}
**/
virtual void updatePosition(Ogre::Vector3 position);
protected:
/**
* Current position of this obstacle.
**/
Ogre::Vector3 mPosition;
/**
* The reference to the obstacle in the detourTileCache.
**/
dtObstacleRef mObstacleRef;
/**
* The entity representing this obstacle in the scene.
**/
Ogre::Entity *mEnt;
/**
* The scene node in which the obstacle entity is located.
**/
Ogre::SceneNode *mNode;
/**
* Name of this obstacle.
**/
Ogre::String mName;
};
#endif // CYLINDEROBSTACLE_H

View File

@@ -0,0 +1,98 @@
/*
OgreCrowd
---------
Copyright (c) 2012 Jonas Hauquier
Additional contributions by:
- mkultra333
- Paul Wilson
Sincere thanks and to:
- Mikko Mononen (developer of Recast navigation libraries)
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 INSTANCEDCHARACTER_H
#define INSTANCEDCHARACTER_H
#include <OgrePrerequisites.h>
#include "Character.h"
class InstancedCharacter : public Character
{
public:
InstancedCharacter(Ogre::String name, Ogre::SceneManager* sceneMgr, OgreDetourCrowd* detourCrowd, Ogre::InstanceManager* instanceMgr, bool debugDraw = false, Ogre::Vector3 position = Ogre::Vector3::ZERO);
/**
* Update one tick in the render loop. Advances animation and character position.
* In order for the agents to be updated, you first need to call the detourCrowd
* update function.
**/
virtual void update(Ogre::Real timeSinceLastFrame);
/**
* The instanced entity that represents this character in the scene
**/
virtual Ogre::InstancedEntity* getEntity(void);
/**
* @see{Character::setDebugVisibility(bool)}
**/
virtual void setDebugVisibility(bool visible);
virtual bool getDebugVisibility(void);
virtual void show(void);
void randomizeAnimationPosition(void);
protected:
bool mDebugDraw;
/**
* Main entity that represents this character.
**/
Ogre::InstancedEntity *mEnt;
Ogre::InstanceManager* mInstanceManager;
/**
* Currently active animation state.
**/
Ogre::AnimationState* mAnimState;
/**
* Speed scaling factor of the animation playback.
**/
Ogre::Real mAnimSpeedScale;
/**
* Scenenode that stores all geometry related to
* recast debug drawing. Can be made visible with
* setDebugVisibility().
**/
Ogre::SceneNode *mDebugNode;
};
#endif // INSTANCEDCHARACTER_H

View File

@@ -0,0 +1,125 @@
/*
OgreCrowd
---------
Copyright (c) 2012 Jonas Hauquier
Additional contributions by:
- mkultra333
- Paul Wilson
Sincere thanks and to:
- Mikko Mononen (developer of Recast navigation libraries)
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 OBSTACLE_H
#define OBSTACLE_H
#include "OgreDetourTileCache.h"
/**
* Dynamic obstacle that can be put on the navmesh when using DetourTileCache.
* Obstacles only mark polygons of the navmesh with a special flag (such as unwalkable)
* and do not actually add new geometry to the navmesh input. This makes them faster,
* but no new areas can be added to the navmesh, only areas can be marked or cut out.
* Obstacles are a lot faster than rebuilding a navmesh tile with new geometry, however.
**/
class Obstacle
{
public:
enum QueryFlags {
DEFAULT_MASK = 1u<<0,
NAVMESH_MASK = 1u<<1,
OBSTACLE_MASK= 1u<<2
};
/**
* Construct an obstacle that will be added to the specified detour Tilecache.
**/
Obstacle(OgreDetourTileCache *detourTileCache);
virtual ~Obstacle();
/**
* No use at the moment.
* The future plan was to use it to do deferred updates of obstacle position/rotation.
* Not sure whether it has much use, however.
**/
virtual void update(long time) = 0;
/**
* The entity that represents this obstacle in the scene.
**/
virtual Ogre::Entity* getEntity(void)=0;
/**
* Update the orientation (mainly intended for rotation and position) of the obstacle
* to the specified orientation. Does not work for all types of obstacles (will have
* no effect in that case).
* Replaces previous rotation completely, not cumulative.
**/
virtual void updateOrientation(Ogre::Quaternion orientation) = 0;
/**
* Update position of the obstacle with this new position.
* The obstacle position will be updated on the navmesh if the difference
* in squared distance with the previous position is larger than SQUARED_DISTANCE_EPSILON.
* Replaces old position completely, not cumulative.
**/
virtual void updatePosition(Ogre::Vector3 position) = 0;
/**
* The current position of the obstacle.
**/
virtual Ogre::Vector3 getPosition(void) = 0;
/**
* The current orientation of the obstacle.
**/
virtual Ogre::Quaternion getOrientation(void) = 0;
/**
* The minimum distance an obstacle has to be moved before the obstacle is updated on the
* navmesh.
**/
static const Ogre::Real SQUARED_DISTANCE_EPSILON;
/**
* Delta tolerance in degrees with which a new orientation has to differ from the previous one
* in order for the obstacle to be updated.
**/
static const Ogre::Real ORIENTATION_TOLERANCE_DEGREES;
protected:
/**
* The detour tile cache that manages this obstacle.
**/
OgreDetourTileCache *mDetourTileCache;
/**
* Scene manager on which the visual representation of this obstacle is drawn.
**/
Ogre::SceneManager *mSceneMgr;
};
#endif // OBSTACLE_H

View File

@@ -0,0 +1,267 @@
/*
OgreCrowd
---------
Copyright (c) 2012 Jonas Hauquier
Additional contributions by:
- mkultra333
- Paul Wilson
Sincere thanks and to:
- Mikko Mononen (developer of Recast navigation libraries)
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 OGREDETOURCROWD_H
#define OGREDETOURCROWD_H
#include "OgreRecastDefinitions.h"
#include "OgreRecast.h"
#include "DetourCrowd.h"
#include <vector>
/**
* Ogre wrapper around DetourCrowd.
* Controls a crowd of agents that can steer to avoid each other and follow
* individual paths.
*
* This class is largely based on the CrowdTool used in the original recastnavigation
* demo.
**/
class OgreDetourCrowd
{
public:
/**
* Initialize a detour crowd that will manage agents on the specified
* recast navmesh. It does not matter how this navmesh is constructed
* (either with OgreRecast directly or with DetourTileCache).
* Parameters such as agent dimensions will be taken from the specified
* recast component.
**/
OgreDetourCrowd(OgreRecast *recast);
~OgreDetourCrowd(void);
/**
* Add an agent to the crowd
* Returns ID of created agent (-1 if maximum agents is already created)
**/
int addAgent(const Ogre::Vector3 position);
/**
* Retrieve agent with specified ID from the crowd.
**/
const dtCrowdAgent* getAgent(int id);
/**
* Remove agent with specified ID from the crowd.
**/
void removeAgent(const int idx);
/**
* Set global destination or target for all agents in the crowd.
* Setting adjust to true will try to adjust the current calculated path
* of the agents slightly to end at the new destination, avoiding the need
* to calculate a completely new path. This only works if the destination is
* close to the previously set one, for example when chasing a moving entity.
**/
void setMoveTarget(Ogre::Vector3 position, bool adjust);
/**
* Set target or destination for an individual agent.
* Setting adjust to true will try to adjust the current calculated path
* of the agent slightly to end at the new destination, avoiding the need
* to calculate a completely new path. This only works if the destination is
* close to the previously set one, for example when chasing a moving entity.
**/
void setMoveTarget(int agentId, Ogre::Vector3 position, bool adjust);
/**
* Request a specified velocity for the agent with specified index.
* Requesting a velocity means manually controlling an agent.
* Returns true if the request was successful.
**/
bool requestVelocity(int agentId, Ogre::Vector3 velocity);
/**
* Cancels any request for the specified agent, making it stop.
* Returns true if the request was successul.
**/
bool stopAgent(int agentId);
/**
* Helper that calculates the needed velocity to steer an agent to a target destination.
* Parameters:
* position is the current position of the agent
* target is the target destination to reach
* speed is the (max) speed the agent can travel at
* Returns the calculated velocity.
*
* This function can be used together with requestMoveVelocity to achieve the functionality
* of adjustMoveTarget function.
**/
static Ogre::Vector3 calcVel(Ogre::Vector3 position, Ogre::Vector3 target, Ogre::Real speed);
static float getDistanceToGoal(const dtCrowdAgent* agent, const float maxRange);
static bool destinationReached(const dtCrowdAgent* agent, const float maxDistanceFromTarget);
/**
* Update method for the crowd manager. Will calculate new positions for moving agents.
* Call this method in your frameloop every frame to make your agents move.
*
* DetourCrowd uses sampling based local steering to calculate a velocity vector for each
* agent. The calculation time of this is limited to the number of agents in the crowd and
* the sampling amount (which is a constant).
*
* Additionally pathfinding tasks are queued and the number of computations is limited, to
* limit the maximum amount of time spent for preparing a frame. This can have as consequence
* that some agents will only start to move after a few frames, when their paths are calculated.
**/
void updateTick(const float dt);
/**
* The height of agents in this crowd. All agents in a crowd have the same height, and height is
* determined by the agent height parameter with which the navmesh is build.
**/
Ogre::Real getAgentHeight(void);
/**
* The radius of agents in this crowd. All agents in a crowd have the same radius, and radius
* determined by the agent radius parameter with which the navmesh is build.
**/
Ogre::Real getAgentRadius(void);
/**
* The number of (active) agents in this crowd.
**/
int getNbAgents(void);
/**
* The maximum number of agents that are allowed in this crowd.
**/
int getMaxNbAgents(void);
/**
* Get all (active) agents in this crowd.
**/
std::vector<dtCrowdAgent*> getActiveAgents(void);
/**
* Get the IDs of all (active) agents in this crowd.
**/
std::vector<int> getActiveAgentIds(void);
/**
* The last set destination for the crowd. This is the destination
* that will be assigned to newly added agents.
**/
Ogre::Vector3 getLastDestination(void);
/**
* Reference to the DetourCrowd object that is wrapped.
**/
dtCrowd* m_crowd;
/**
* Reference to the Recast/Detour wrapper object for Ogre.
**/
OgreRecast *m_recast;
/**
* The latest set target or destination section in the recast navmesh.
**/
dtPolyRef m_targetRef;
/**
* The latest set target or destination position.
**/
float m_targetPos[3];
/**
* Max pathlength for calculated paths.
**/
static const int AGENT_MAX_TRAIL = 64;
/**
* Max number of agents allowed in this crowd.
**/
static const int MAX_AGENTS = 128;
/**
* Stores the calculated paths for each agent in the crowd.
**/
struct AgentTrail
{
float trail[AGENT_MAX_TRAIL*3];
int htrail;
};
AgentTrail m_trails[MAX_AGENTS];
/**
* Debug info object used in the original recast/detour demo, not used in this
* application.
**/
dtCrowdAgentDebugInfo m_agentDebug;
/**
* Parameters for obstacle avoidance of DetourCrowd steering.
**/
dtObstacleAvoidanceDebugData* m_vod;
// Agent configuration parameters
bool m_anticipateTurns;
bool m_optimizeVis;
bool m_optimizeTopo;
bool m_obstacleAvoidance;
bool m_separation;
float m_obstacleAvoidanceType;
float m_separationWeight;
protected:
/**
* Helper to calculate the needed velocity to steer an agent to a target destination.
* Parameters:
* velocity is the return parameter, the calculated velocity
* position is the current position of the agent
* target is the target destination to reach
* speed is the (max) speed the agent can travel at
*
* This function can be used together with requestMoveVelocity to achieve the functionality
* of the old adjustMoveTarget function.
**/
static void calcVel(float* velocity, const float* position, const float* target, const float speed);
private:
/**
* Number of (active) agents in the crowd.
**/
int m_activeAgents;
};
#endif // OGREDETOURCROWD_H

View File

@@ -0,0 +1,822 @@
/*
OgreCrowd
---------
Copyright (c) 2012 Jonas Hauquier
Additional contributions by:
- mkultra333
- Paul Wilson
Sincere thanks and to:
- Mikko Mononen (developer of Recast navigation libraries)
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 OGREDETOURTILECACHE_H
#define OGREDETOURTILECACHE_H
#include <Ogre.h>
#include "OgreRecast.h"
#include "DetourTileCacheBuilder.h"
#include "DetourTileCache.h"
#include "DetourCommon.h"
#include "fastlz.h"
#include "RecastInputGeom.h"
const float TEMP_OBSTACLE_RADIUS = 1.0f;
const float TEMP_OBSTACLE_HEIGHT = 2.0f;
struct TileSelection
{
Ogre::AxisAlignedBox bounds;
int minTx;
int maxTx;
int minTy;
int maxTy;
};
/**
* Struct to store a request to add or remove
* a convex obstacle to the tilecache as a deferred
* command (currently unused).
**/
struct ogredtTileCacheConvexObstacle
{
ConvexVolume *obstacle;
dtCompressedTileRef touched[DT_MAX_TOUCHED_TILES];
dtCompressedTileRef pending[DT_MAX_TOUCHED_TILES];
unsigned short salt;
unsigned char state;
unsigned char ntouched;
unsigned char npending;
ogredtTileCacheConvexObstacle* next; // Single linked list
};
/**
* Implementation of the meshProcess callback that detourTileCache
* does after building a navmesh. It allows you to do some extra
* processing on the navmesh, such as connecting off-mesh connections
* and assigning flags to certain poly areas.
* The reason it is initialized with an inputGeom object is that it
* is intended that the inputGeom not only stores the input geometry,
* but also information that has to be added to the navmesh in this
* post-processing phase.
**/
struct MeshProcess : public dtTileCacheMeshProcess
{
InputGeom* m_geom;
inline MeshProcess() : m_geom(0) {
}
inline void init(InputGeom* geom) {
m_geom = geom;
}
/**
* Callback that happens after navmesh has been constructed.
* Allows you to do some additional post-processing on the navmesh,
* such as adding off-mesh connections or marking poly areas with
* certain flags.
**/
virtual void process(struct dtNavMeshCreateParams* params,
unsigned char* polyAreas, unsigned short* polyFlags)
{
// Update poly flags from areas.
for (int i = 0; i < params->polyCount; ++i)
{
if (polyAreas[i] == DT_TILECACHE_WALKABLE_AREA)
polyAreas[i] = SAMPLE_POLYAREA_GROUND;
if (polyAreas[i] == SAMPLE_POLYAREA_GROUND ||
polyAreas[i] == SAMPLE_POLYAREA_GRASS ||
polyAreas[i] == SAMPLE_POLYAREA_ROAD)
{
polyFlags[i] = SAMPLE_POLYFLAGS_WALK;
}
else if (polyAreas[i] == SAMPLE_POLYAREA_WATER)
{
polyFlags[i] = SAMPLE_POLYFLAGS_SWIM;
}
else if (polyAreas[i] == SAMPLE_POLYAREA_DOOR)
{
polyFlags[i] = SAMPLE_POLYFLAGS_WALK | SAMPLE_POLYFLAGS_DOOR;
}
}
// Pass in off-mesh connections.
if (m_geom)
{
// TODO implement off-mesh connections
/*
params->offMeshConVerts = m_geom->getOffMeshConnectionVerts();
params->offMeshConRad = m_geom->getOffMeshConnectionRads();
params->offMeshConDir = m_geom->getOffMeshConnectionDirs();
params->offMeshConAreas = m_geom->getOffMeshConnectionAreas();
params->offMeshConFlags = m_geom->getOffMeshConnectionFlags();
params->offMeshConUserID = m_geom->getOffMeshConnectionId();
params->offMeshConCount = m_geom->getOffMeshConnectionCount();
*/
}
}
};
/**
* FastLZ implementation of detour tile cache tile compressor.
* You can define a custom implementation if you wish to use
* a different compression algorithm for compressing your
* detour heightfield tiles.
* The result of compression runs is the data that detourTileCache
* stores in memory (or can save out to disk).
* The compressed heightfield tiles are stored in ram as they allow
* to quickly generate a navmesh tile, possibly with obstacles added
* to them, without the need for a full rebuild.
**/
struct FastLZCompressor : public dtTileCacheCompressor
{
virtual int maxCompressedSize(const int bufferSize)
{
return (int)(bufferSize* 1.05f);
}
virtual dtStatus compress(const unsigned char* buffer, const int bufferSize,
unsigned char* compressed, const int /*maxCompressedSize*/, int* compressedSize)
{
*compressedSize = fastlz_compress((const void *const)buffer, bufferSize, compressed);
return DT_SUCCESS;
}
virtual dtStatus decompress(const unsigned char* compressed, const int compressedSize,
unsigned char* buffer, const int maxBufferSize, int* bufferSize)
{
*bufferSize = fastlz_decompress(compressed, compressedSize, buffer, maxBufferSize);
return *bufferSize < 0 ? DT_FAILURE : DT_SUCCESS;
}
};
/**
* Allows a custom memory allocation technique to be implemented
* for storing compressed tiles. This implementation does a linear
* memory allocation.
**/
struct LinearAllocator : public dtTileCacheAlloc
{
unsigned char* buffer;
int capacity;
int top;
int high;
LinearAllocator(const int cap) : buffer(0), capacity(0), top(0), high(0)
{
resize(cap);
}
~LinearAllocator()
{
dtFree(buffer);
}
void resize(const int cap)
{
if (buffer) dtFree(buffer);
buffer = (unsigned char*)dtAlloc(cap, DT_ALLOC_PERM);
capacity = cap;
}
virtual void reset()
{
high = dtMax(high, top);
top = 0;
}
virtual void* alloc(const int size)
{
if (!buffer)
return 0;
if (top+size > capacity)
return 0;
unsigned char* mem = &buffer[top];
top += size;
return mem;
}
virtual void free(void* /*ptr*/)
{
// Empty
}
};
/**
* Maximum layers (floor levels) that 2D navmeshes can have in the tilecache.
* This determines the domain size of the tilecache pages, as their dimensions
* are width*height*layers.
**/
static const int MAX_LAYERS = 32;
/**
* Struct that stores the actual tile data in binary form.
**/
struct TileCacheData
{
unsigned char* data;
int dataSize;
};
/**
* Rasterization context stores temporary data used
* when rasterizing inputGeom into a navmesh.
**/
struct RasterizationContext
{
RasterizationContext() :
solid(0),
triareas(0),
lset(0),
chf(0),
ntiles(0)
{
memset(tiles, 0, sizeof(TileCacheData)*MAX_LAYERS);
}
~RasterizationContext()
{
rcFreeHeightField(solid);
delete [] triareas;
rcFreeHeightfieldLayerSet(lset);
rcFreeCompactHeightfield(chf);
for (int i = 0; i < MAX_LAYERS; ++i)
{
dtFree(tiles[i].data);
tiles[i].data = 0;
}
}
rcHeightfield* solid;
unsigned char* triareas;
rcHeightfieldLayerSet* lset;
rcCompactHeightfield* chf;
TileCacheData tiles[MAX_LAYERS];
int ntiles;
};
/**
* Build context stores temporary data used while
* building a navmesh tile.
**/
struct BuildContext
{
inline BuildContext(struct dtTileCacheAlloc* a) : layer(0), lcset(0), lmesh(0), alloc(a) {}
inline ~BuildContext() { 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;
};
// TODO put in class context
/**
* Calculate the memory space used by the tilecache.
**/
static int calcLayerBufferSize(const int gridWidth, const int gridHeight)
{
const int headerSize = dtAlign4(sizeof(dtTileCacheLayerHeader));
const int gridSize = gridWidth * gridHeight;
return headerSize + gridSize*4;
}
/**
* DetourTileCache manages a large grid of individual navmeshes stored in pages to
* allow managing a navmesh for a very large map. Navmesh pages can be requested
* when needed or swapped out when they are no longer needed.
* Using a tilecache the navigation problem is localized to one tile, but pathfinding
* can still find a path that references to other neighbour tiles on the higher hierarchy
* level of the tilecache. Localizing the pathfinding problem allows it to be more scalable,
* also for very large worlds.
* DetouTileCache stores navmeshes in an intermediary format as 2D heightfields
* that can have multiple levels. It allows to quickly generate a 3D navmesh from
* this intermediary format, with the additional option of adding or removing
* temporary obstacles to the navmesh and regenerating it.
**/
class OgreDetourTileCache
{
public:
/**
* Create a tilecache that will build a tiled recast navmesh stored at the specified
* OgreRecast component. Will use specified tilesize (a multiple of 8 between 16 and 128),
* all other configuration parameters are copied from the OgreRecast component configuration.
* Tilesize is the number of (recast) cells per tile.
**/
OgreDetourTileCache(OgreRecast *recast, int tileSize = 48);
~OgreDetourTileCache(void);
/**
* Configure the tilecache for building navmesh tiles from the specified input geometry.
* The inputGeom is mainly used for determining the bounds of the world for which a navmesh
* will be built, so at least bmin and bmax of inputGeom should be set to your world's outer
* bounds. This world bounding box is used to calculate the grid size that the tilecache has
* to initialize.
* This method has to be called once after construction, and before any tile builds happen.
**/
bool configure(InputGeom *inputGeom);
/**
* Find tiles that (partially or completely) intersect the specified bounding area.
* The selectionArea has to be in world units.
* TileCache needs to be configured before this method can work (needs to know world size
* of tilecache).
* TileSelection contains bounding box aligned to the tile bounds and tx ty index ranges
* for the affected tiles. Note that tile ranges are inclusive! (eg. if minTx=1 and maxTx=1
* then tile at x=1 has to be rebuilt)
* It is not necessary for tiles to be already built in order for
* them to be included in the selection.
* Make sure you use the included bounding box instead of an arbitrary selection bounding
* box to bound inputGeom used for rebuilding tiles. Or you might not include all geometry
* needed to rebuild all affected tiles correctly.
**/
TileSelection getTileSelection(const Ogre::AxisAlignedBox &selectionArea);
/**
* Returns a bounding box that matches the tile bounds of this cache and that is at least
* as large as the specified selectionArea bounding box. Height (y) coordinates will be set
* to the min and max height of this tilecache. (tile selection only happens in x-z plane).
* Use this function to get correct bounding boxes to cull your inputGeom or scene geometry
* with for tile rebuilding.
**/
Ogre::AxisAlignedBox getTileAlignedBox(const Ogre::AxisAlignedBox &selectionArea);
/**
* Returns the world-space bounds of the tile at specified grid position.
* Make sure that tx and ty satisfy isWithinBounds(tx, ty).
**/
Ogre::AxisAlignedBox getTileBounds(int tx, int ty);
/**
* The size of one tile in world units.
* This equals the number of cells per tile, multiplied with the cellsize.
**/
inline Ogre::Real getTileSize(void) { return m_tileSize*m_cellSize; }
OgreRecast* getRecast(void) { return m_recast; }
/**
* Build all tiles of the tilecache and construct a recast navmesh from the
* specified entities. These entities need to be already added to the scene so that
* their world position and orientation can be calculated.
*
* This is an Ogre adaptation of Sample_TempObstacles::handleBuild()
* First init the OgreRecast module like you would construct a simple single
* navmesh, then invoke this method instead of OgreRecast::NavMeshBuild() to create
* a tileCache from the specified ogre geometry.
* The specified ogre entities need to be added to a scenenode in the scene before this
* method is called.
* The resulting navmesh will be created in the OgreRecast module, at OgreRecast::m_navMesh;
*
* Will issue a configure() call so the entities specified will determine the world bounds
* of the tilecache.
**/
bool TileCacheBuild(std::vector<Ogre::Entity*> srcMeshes);
/**
* Build all navmesh tiles from specified input geom.
*
* Will issue a configure() call so the inputGeom specified will determine the world bounds
* of the tilecache. Therefore you must specify the inputGeom for the entire world.
*
* @see OgreDetourTileCache::TileCacheBuild(std::vector<Ogre::Entity*>)
**/
bool TileCacheBuild(InputGeom *inputGeom);
// TODO maybe provide isLoaded(tx, ty) method
// TODO create better distinction between loading compressed tiles in cache and building navmesh from them?
// TODO are both updateFromGeometry() and buildTiles() necessary, or can update be dropped? It might be confusing.
/**
* Build or rebuild a cache tile at the specified x and y position in the tile grid.
* Tile is built or rebuilt no matter whether there was already a tile at that position in the grid
* or not. If there previously was a tile in the specified grid position, it is first removed from the
* tilecache and replaced with the new one.
*
* At the moment this will issue an immediate update of the navmesh at the
* corresponding tiles. (the alternative is adding a request that is processed as deferred command)
*
* Note that you can speed this up by building an inputGeom from only the area that is rebuilt.
* Don't use an arbitrary bounding box for culling the inputGeom, but use getTileAlignedBox() instead!
**/
bool buildTile(const int tx, const int ty, InputGeom *inputGeom);
/**
* Build or rebuild a cache tiles or tiles that cover the specified bounding box area.
*
* The tiles are built or rebuilt no matter whether there was already a tile at that position in the grid
* or not. If there previously was a tile in the specified grid position, it is first removed from the
* tilecache and replaced with the new one.
*
* Make sure that the specified inputGeom is either the inputGeom of the complete scene (inefficient) or is
* built with a tile aligned bounding box (getTileAlignedBox())! The areaToUpdate value can be arbitrary,
* but will be converted to a tile aligned box.
*
* At the moment this will issue an immediate update of the navmesh at the
* corresponding tiles. (the alternative is adding a request that is processed as deferred command)
**/
void buildTiles(InputGeom *inputGeom, const Ogre::AxisAlignedBox *areaToUpdate = NULL);
/**
* Build or rebuild tile from list of entities.
* @see{buildTiles(InputGeom*, const Ogre::AxisAlignedBox*)}
**/
void buildTiles(std::vector<Ogre::Entity*> srcEntities, const Ogre::AxisAlignedBox *areaToUpdate = NULL);
// TODO maybe also add a unloadAllTilesExcept(boundingBox) method
/**
* Unload all tiles that cover the specified bounding box. The tiles are removed from the
* cache.
**/
void unloadTiles(const Ogre::AxisAlignedBox &areaToUpdate);
/**
* Gets grid coordinates of the tile that contains the specified world position.
**/
void getTileAtPos(const float* pos, int& tx, int& ty);
/**
* Gets grid coordinates of the tile that contains the specified world position.
**/
Ogre::Vector2 getTileAtPos(const Ogre::Vector3 pos);
/**
* Determines whether there is a tile loaded at the specified grid position.
**/
bool tileExists(int tx, int ty);
/**
* Determines whether the specified grid index is within the outer bounds of this tilecache.
**/
bool isWithinBounds(int tx, int ty);
/**
* Determines whether the specified world position is within the outer bounds of this tilecache,
* ie the coordinates are contained within a tile that is within the cache bounds.
**/
bool isWithinBounds(Ogre::Vector3 pos);
Ogre::AxisAlignedBox getWorldSpaceBounds(void);
TileSelection getBounds(void);
bool saveAll(Ogre::String filename);
bool loadAll(Ogre::String filename);
/**
* Update (tick) the tilecache.
* You must call this method in your render loop continuously to dynamically
* update the navmesh when obstacles are added or removed.
* Navmesh rebuilding happens per tile and only where needed. Tile rebuilding is
* timesliced.
**/
void handleUpdate(const float dt);
/**
* Remove all (cylindrical) temporary obstacles from the tilecache.
* The navmesh will be rebuilt after the next (one or more) update()
* call.
**/
void clearAllTempObstacles(void);
/**
* Add a temporary (cylindrical) obstacle to the tilecache (as a deferred request).
* The navmesh will be updated correspondingly after the next (one or many)
* update() call as a deferred command.
* If m_tileCache->m_params->maxObstacles obstacles are already added, this call
* will have no effect. Also, at one time only MAX_REQUESTS can be added, or nothing
* will happen.
*
* If successful returns a reference to the added obstacle.
**/
dtObstacleRef addTempObstacle(Ogre::Vector3 pos);
/**
* Remove temporary (cylindrical) obstacle with specified reference. The affected tiles
* will be rebuilt. This operation is deferred and will happen in one of the next
* update() calls. At one time only MAX_REQUESTS obstacles can be removed, or nothing will happen.
**/
bool removeTempObstacle(dtObstacleRef obstacleRef);
/**
* Remove a temporary (cylindrical) obstacle from the tilecache (as a deferred request).
* Uses a ray query to find the temp obstacle.
* The navmesh will be updated correspondingly after the next (one or many)
* update() call as a deferred command.
* At one time only MAX_REQUESTS obstacles can be removed, or nothing will happen.
**/
dtObstacleRef removeTempObstacle(Ogre::Vector3 raySource, Ogre::Vector3 rayHit);
/**
* Execute a ray intersection query to find the first temporary (cylindrical) obstacle that
* hits the ray, if any.
**/
dtObstacleRef hitTestObstacle(const dtTileCache* tc, const float* sp, const float* sq);
/**
* Returns a list of tile references to compressed tiles that cover the specified bounding
* box area.
**/
std::vector<dtCompressedTileRef> getTilesContainingBox(Ogre::Vector3 boxMin, Ogre::Vector3 boxMax);
/**
* Returns a list of tile references to compressed tiles that cover the area of a circle with
* specified radius around the specified position.
**/
std::vector<dtCompressedTileRef> getTilesAroundPoint(Ogre::Vector3 point, Ogre::Real radius);
/**
* Add a convex shaped temporary obstacle to the tilecache in pretty much the same way as cylindrical
* obstacles are added.
* Currently this is implemented a lot less efficiently than cylindrical obstacles, as it issues a complete
* rebuild of the affected tiles, instead of just cutting out the poly area of the obstacle.
* This is a big TODO that I'm holding off because it requires changes to the recast libraries themselves.
* I wait in hopes that this feature will appear in the original recast code.
* In the meanwhile, if you are looking for this, someone implemented it and shared it on the mailing list:
* http://groups.google.com/group/recastnavigation/msg/92d5f131561ddad1
* And corresponding ticket: http://code.google.com/p/recastnavigation/issues/detail?id=206
*
* The current implementation of convex obstacles is very simple and not deferred. Also obstacles
* are stored in the inputGeom, which is not really nice.
**/
//TODO by adding deferred tasking to add and removeConvexShapeObstacle one can add multiple shapes at once to the same tile without it being rebuilt multiple times
int addConvexShapeObstacle(ConvexVolume *obstacle);
/**
* Remove convex obstacle from the tileCache. The affected navmesh tiles will be rebuilt.
**/
bool removeConvexShapeObstacle(ConvexVolume* convexHull);
/**
* Remove convex obstacle with specified id from the tileCache. The affected navmesh tiles will be rebuilt.
* If removedObstacle is a valid pointer it will contain a reference to the removed obstacle.
**/
bool removeConvexShapeObstacleById(int obstacleIndex, ConvexVolume** removedObstacle = NULL);
/**
* Raycast the inputGeom and remove the hit convex obstacle. The affected navmesh tiles will be rebuilt.
* If removedObstacle is a valid pointer it will contain a reference to the removed obstacle.
**/
int removeConvexShapeObstacle(Ogre::Vector3 raySource, Ogre::Vector3 rayHit, ConvexVolume** removedObstacle = NULL);
/**
* Returns the id of the specified convex obstacle. Returns -1 if this obstacle is not currently added to the tilecache.
* Note: Ids are just array indexes and can change when obstacles are added or removed. Use with care!
**/
int getConvexShapeObstacleId(ConvexVolume *convexHull);
/**
* Returns the convex obstacle with specified id or index.
**/
ConvexVolume* getConvexShapeObstacle(int obstacleIndex);
/**
* Raycast inputGeom to find intersection with a convex obstacle. Returns the id of the hit
* obstacle, -1 if none hit.
**/
int hitTestConvexShapeObstacle(Ogre::Vector3 raySource, Ogre::Vector3 rayHit);
/**
* Remove the tile with specified reference from the tilecache. The associated navmesh tile will also
* be removed.
**/
bool removeTile(dtCompressedTileRef tileRef);
/**
* Debug draw the tile at specified grid location.
**/
void drawDetail(const int tx, const int ty);
/**
* Debug draw all tiles in the navmesh.
**/
void drawNavMesh(void);
/**
* Unused debug drawing function from the original recast demo.
* Used for drawing the obstacles in the scene.
* In this demo application we use the Obstacle class to represent obstacles in the scene.
**/
void drawObstacles(const dtTileCache* tc);
/**
* Ogre Recast component that holds the recast config and where the navmesh will be built.
**/
OgreRecast *m_recast;
/**
* Max number of layers a tile can have
**/
static const int EXPECTED_LAYERS_PER_TILE;
/**
* Max number of (temp) obstacles that can be added to the tilecache
**/
static const int MAX_OBSTACLES;
/**
*
* Extra padding added to the border size of tiles (together with agent radius)
**/
static const float BORDER_PADDING;
/**
* Set to false to disable debug drawing. Improves performance.
**/
static bool DEBUG_DRAW;
/**
* Set to true to draw the bounding box of the tile areas that were rebuilt.
**/
static bool DEBUG_DRAW_REBUILT_BB;
protected:
/**
* Build the 2D navigation grid divided in layers that is the intermediary format stored in the tilecache.
* Builds the specified tile from the given input geometry. Only the part of the geometry that intersects the
* needed tile is used.
* From this format a 3D navmesh can be quickly generated at runtime.
* This process uses a large part of the recast navmesh building pipeline (implemented in OgreRecast::NavMeshBuild()),
* up till step 4.
**/
int rasterizeTileLayers(InputGeom* geom, const int tx, const int ty, const rcConfig& cfg, TileCacheData* tiles, const int maxTiles);
/**
* Debug draw a navmesh poly
**/
void drawPolyMesh(const Ogre::String tileName, const struct dtTileCachePolyMesh &mesh, const float *orig, const float cs, const float ch, const struct dtTileCacheLayer &regionLayers, bool colorRegions=true);
/**
* Inits the tilecache. Helper used by constructors.
**/
bool initTileCache(void);
/**
* InputGeom from which the tileCache is initially inited (it's bounding box is considered the bounding box
* for the entire world that the navmesh will cover). Tile build methods without specific geometry or entity
* input will build navmesh from this geometry.
* It also stored the convex temp obstacles. (will be gone in the future)
* In the future this variable will probably disappear.
**/
InputGeom* m_geom;
// TODO maybe in the future I don't want to store inputgeom anymore, at the moment it's only used for adding convex shapes (what really should be done from compressed tiles instead of rebuilding from input geom) The whole navmesh can be stored as compressed tiles, the input geom does not need to be stored.
/**
* Set to true to keep intermediary results from navmesh build for debugging purposes.
* Set to false to free up memory after navmesh was built.
* Same as in official recast demo. (it's a checkbox in the gui)
**/
bool m_keepInterResults;
/**
* The tile cache memory allocator implementation used.
**/
struct LinearAllocator *m_talloc;
/**
* The tile compression implementation used.
**/
struct FastLZCompressor* m_tcomp;
/**
* Callback handler that processes right after processing
* a tile mesh. Adds off-mesh connections to the mesh.
**/
struct MeshProcess *m_tmproc;
/**
* The detourTileCache component this class wraps.
**/
class dtTileCache *m_tileCache;
/**
* Recast config (copied from the OgreRecast component).
**/
rcConfig m_cfg;
/**
* DetourTileCache configuration parameters.
**/
dtTileCacheParams m_tcparams;
/**
* Context that stores temporary working variables when navmesh building.
**/
rcContext *m_ctx;
/**
* Metrics for measuring and profiling build times and memory usage.
**/
float m_cacheBuildTimeMs;
int m_cacheCompressedSize;
int m_cacheRawSize;
int m_cacheLayerCount;
int m_cacheBuildMemUsage;
/**
* Configuration parameters.
**/
int m_maxTiles;
int m_maxPolysPerTile;
int m_tileSize;
float m_cellSize;
/**
* Size of the tile grid (x dimension)
**/
int m_tw;
/**
* Size of the tile grid (y dimension)
**/
int m_th;
/**
* Unused.
* Could serve for deferring convex obstacle adding/removing requests.
**/
ConvexVolume* mChangedConvexVolumes[InputGeom::MAX_VOLUMES]; // TODO is this really MAX_VOLUMES? would be more like MAX_REQUESTS
int mChangedConvexVolumesCount;
/**
* Pointer to debug drawn bounding box of rebuilt tiles.
* Used when DEBUG_DRAW_REBUILT_BB is true.
**/
Ogre::ManualObject* mDebugRebuiltBB;
static const int TILECACHESET_MAGIC = 'T'<<24 | 'S'<<16 | 'E'<<8 | 'T'; //'TSET';
static const int TILECACHESET_VERSION = 2;
struct TileCacheSetHeader
{
int magic;
int version;
int numTiles;
dtNavMeshParams meshParams;
dtTileCacheParams cacheParams;
rcConfig recastConfig;
};
struct TileCacheTileHeader
{
dtCompressedTileRef tileRef;
int dataSize;
};
};
#endif // OGREDETOURTILECACHE_H

View File

@@ -0,0 +1,950 @@
/*
OgreCrowd
---------
Copyright (c) 2012 Jonas Hauquier
Additional contributions by:
- mkultra333
- Paul Wilson
Sincere thanks and to:
- Mikko Mononen (developer of Recast navigation libraries)
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 __OgreRecast_h_
#define __OgreRecast_h_
#include "OgreRecastDefinitions.h"
#include <Ogre.h>
#include "RecastInputGeom.h"
class OgreRecastNavmeshPruner;
/**
* Configuration parameters for recast navmesh building.
* A lot of the descripions of the parameters are not mine but come from the very
* useful CritterAI page (http://www.critterai.org/nmgen_config).
* For more detail and pictures, have a look there.
* Some other descriptions come from stevesp's doxygen API docs for recast itself
* (http://www.stevefsp.org/projects/rcndoc/prod/structrcConfig.html).
*
* Some settings are derived from easier to set parameters, those are denoted by
* the _ that follows their setter and getter. The easier to set parameters correspond
* to the settings available in the demo that comes with the recast library.
* You can overwrite those values by calling the setters preceeded with _.
* Otherwise it suffices to set a value for each setter without a preceding _.
**/
class OgreRecastConfigParams
{
public:
/**
* Initialize some default recast parameters
**/
OgreRecastConfigParams(void)
: cellSize(0.3),
cellHeight(0.2),
agentMaxSlope(20),
agentHeight(2.5),
agentMaxClimb(1),
agentRadius(0.5),
edgeMaxLen(12),
edgeMaxError(1.3),
regionMinSize(50),
regionMergeSize(20),
vertsPerPoly(DT_VERTS_PER_POLYGON), // (=6)
detailSampleDist(6),
detailSampleMaxError(1),
keepInterResults(false)
{ eval(); }
///////////////////////
// GUI SETTINGS:
///////////////////////
/*****************
* Rasterization
*****************/
/**
* @see{cellSize}
**/
inline void setCellSize(Ogre::Real cellSize) { this->cellSize = cellSize; eval(); }
/**
* @see{cellHeight}
**/
inline void setCellHeight(Ogre::Real cellHeight) { this->cellHeight = cellHeight; eval(); }
/*****************
* Agent
*****************/
/**
* @see{agentHeight}
**/
inline void setAgentHeight(Ogre::Real agentHeight) { this->agentHeight = agentHeight; eval(); }
/**
* @see{agentRadius}
**/
inline void setAgentRadius(Ogre::Real agentRadius) { this->agentRadius = agentRadius; eval(); }
/**
* @see{agentMaxClimb}
**/
inline void setAgentMaxClimb(Ogre::Real agentMaxClimb) { this->agentMaxClimb = agentMaxClimb; eval(); }
/**
* @see{agentMaxSlope}
**/
inline void setAgentMaxSlope(Ogre::Real agentMaxSlope) { this->agentMaxSlope = agentMaxSlope; }
/*****************
* Region
*****************/
/**
* @see{regionMinSize}
**/
inline void setRegionMinSize(Ogre::Real regionMinSize) { this->regionMinSize = regionMinSize; eval(); }
/**
* @see{regionMergeSize}
**/
inline void setRegionMergeSize(Ogre::Real regionMergeSize) { this->regionMergeSize = regionMergeSize; eval(); }
// TODO Add "monotone partitioning" option to call rcBuildRegionsMonotone in single navmesh building.
/*****************
* Polygonization
*****************/
/**
* @see{edgeMaxLen}
**/
inline void setEdgeMaxLen(Ogre::Real edgeMaxLength) { this->edgeMaxLen = edgeMaxLength; eval(); }
/**
* @see{edgeMaxError}
**/
inline void setEdgeMaxError(Ogre::Real edgeMaxError) { this->edgeMaxError = edgeMaxError;}
/**
* @see{vertsPerPoly}
**/
inline void setVertsPerPoly(int vertsPerPoly) { this->vertsPerPoly = vertsPerPoly; }
/*****************
* Detail mesh
*****************/
/**
* @see{detailSampleDist}
**/
inline void setDetailSampleDist(Ogre::Real detailSampleDist) { this->detailSampleDist = detailSampleDist; eval(); }
/**
* @see{detailSampleMaxError}
**/
inline void setDetailSampleMaxError(Ogre::Real detailSampleMaxError) { this->detailSampleMaxError = detailSampleMaxError; eval(); }
/**
* @see{keepInterResults}
**/
inline void setKeepInterResults(bool keepInterResults) { this->keepInterResults = keepInterResults; }
/**
* @see{_walkableHeight}
**/
inline void _setWalkableHeight(int walkableHeight) { this->_walkableHeight = walkableHeight; }
/**
* @see{_walkableClimb}
**/
inline void _setWalkableClimb(int walkableClimb) { this->_walkableClimb = walkableClimb; }
/**
* @see{_walkableRadius}
**/
inline void _setWalkableRadius(int walkableRadius) { this->_walkableRadius = walkableRadius; }
/**
* @see{_maxEdgeLen}
**/
inline void _setMaxEdgeLen(int maxEdgeLen) { this->_maxEdgeLen = maxEdgeLen; }
/**
* @see{_minRegionArea}
**/
inline void _setMinRegionArea(int minRegionArea) { this->_minRegionArea = minRegionArea; }
/**
* @see{_mergeRegionArea}
**/
inline void _setMergeRegionArea(int mergeRegionArea) { this->_mergeRegionArea = mergeRegionArea; }
/**
* @see{_detailSampleDist}
**/
inline void _setDetailSampleDist(Ogre::Real detailSampleDist) { this->_detailSampleDist = detailSampleDist; }
/**
* @see{_detailSampleMaxError}
**/
inline void _setDetailSampleMaxError(Ogre::Real detailSampleMaxError) { this->_detailSampleMaxError = detailSampleMaxError; }
/**
* @see{cellSize}
**/
inline Ogre::Real getCellSize(void) { return cellSize; }
/**
* @see{cellHeight}
**/
inline Ogre::Real getCellHeight(void) { return cellHeight; }
/**
* @see{agentMaxSlope}
**/
inline Ogre::Real getAgentMaxSlope(void) { return agentMaxSlope; }
/**
* @see{agentHeight}
**/
inline Ogre::Real getAgentHeight(void) { return agentHeight; }
/**
* @see{agentMaxClimb}
**/
inline Ogre::Real getAgentMaxClimb(void) { return agentMaxClimb; }
/**
* @see{agentRadius}
**/
inline Ogre::Real getAgentRadius(void) { return agentRadius; }
/**
* @see{edgeMaxLen}
**/
inline Ogre::Real getEdgeMaxLen(void) { return edgeMaxLen; }
/**
* @see{edgeMaxError}
**/
inline Ogre::Real getEdgeMaxError(void) { return edgeMaxError; }
/**
* @see{regionMinSize}
**/
inline Ogre::Real getRegionMinSize(void) { return regionMinSize; }
/**
* @see{regionMergeSize}
**/
inline Ogre::Real getRegionMergeSize(void) { return regionMergeSize; }
/**
* @see{vertsPerPoly}
**/
inline int getVertsPerPoly(void) { return vertsPerPoly; }
/**
* @see{detailSampleDist}
**/
inline Ogre::Real getDetailSampleDist(void) { return detailSampleDist; }
/**
* @see{detailSampleMaxError}
**/
inline Ogre::Real getDetailSampleMaxError(void) { return detailSampleMaxError; }
/**
* @see{keepInterResults}
**/
inline bool getKeepInterResults(void) { return keepInterResults; }
/**
* @see{_walkableHeight}
**/
inline int _getWalkableheight(void) { return _walkableHeight; }
/**
* @see{_walkableClimb}
**/
inline int _getWalkableClimb(void) { return _walkableClimb; }
/**
* @see{_walkableRadius}
**/
inline int _getWalkableRadius(void) { return _walkableRadius; }
/**
* @see{_maxEdgeLen}
**/
inline int _getMaxEdgeLen(void) { return _maxEdgeLen; }
/**
* @see{_minRegionArea}
**/
inline int _getMinRegionArea(void) { return _minRegionArea; }
/**
* @see{_mergeRegionArea}
**/
inline int _getMergeRegionArea(void) { return _mergeRegionArea; }
/**
* @see{_detailSampleDist}
**/
inline int _getDetailSampleDist(void) { return (int)_detailSampleDist; }
/**
* @see{detailSampleMaxError}
**/
inline int _getDetailSampleMaxError(void) { return (int)_detailSampleMaxError; }
private:
/**
* Derive non-directly set parameters
* This is the default behaviour and these parameters can be overridden using
* _ setters.
**/
inline void eval(void) {
_walkableHeight = (int)ceilf(agentHeight / cellHeight);
_walkableClimb = (int)floorf(agentMaxClimb / cellHeight);
_walkableRadius = (int)ceilf(agentRadius / cellSize);
_maxEdgeLen = (int)(edgeMaxLen / cellSize);
_minRegionArea = (int)rcSqr(regionMinSize); // Note: area = size*size
_mergeRegionArea = (int)rcSqr(regionMergeSize); // Note: area = size*size
_detailSampleDist = detailSampleDist < 0.9f ? 0 : cellSize * detailSampleDist;
_detailSampleMaxError = cellHeight * detailSampleMaxError;
}
/**
* Cellsize (cs) is the width and depth resolution used when sampling the source geometry.
* The width and depth of the cell columns that make up voxel fields.
* Cells are laid out on the width/depth plane of voxel fields. Width is associated with the x-axis of the source geometry. Depth is associated with the z-axis.
* A lower value allows for the generated meshes to more closely match the source geometry, but at a higher processing and memory cost.
*
* The xz-plane cell size to use for fields. [Limit: > 0] [Units: wu].
* 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.
**/
Ogre::Real cellSize;
/**
* Cellheight (ch) is the height resolution used when sampling the source geometry. The height of the voxels in voxel fields.
* Height is associated with the y-axis of the source geometry.
* A smaller value allows for the final meshes to more closely match the source geometry at a potentially higher processing cost.
* (Unlike cellSize, using a lower value for cellHeight does not significantly increase memory use.)
*
* The y-axis cell size to use for fields. [Limit: > 0] [Units: wu].
* 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.
*
* Setting ch lower will result in more accurate detection of areas the agent can still pass under, as min walkable height is discretisized
* in number of cells. Also walkableClimb's precision is affected by ch in the same way, along with some other parameters.
**/
Ogre::Real cellHeight;
/**
* The maximum slope that is considered traversable (in degrees).
* [Limits: 0 <= value < 90]
* The practical upper limit for this parameter is usually around 85 degrees.
*
* Also called maxTraversableSlope
**/
Ogre::Real agentMaxSlope;
/**
* The height of an agent. Defines the minimum height that
* agents can walk under. Parts of the navmesh with lower ceilings
* will be pruned off.
*
* This parameter serves at setting walkableHeight (minTraversableHeight) parameter, precision of this parameter is determined by cellHeight (ch).
**/
Ogre::Real agentHeight;
/**
* The Maximum ledge height that is considered to still be traversable.
* This parameter serves at setting walkableClimb (maxTraversableStep) parameter, precision of this parameter is determined by cellHeight (ch).
* [Limit: >=0]
* 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.
**/
Ogre::Real agentMaxClimb;
/**
* The radius on the xz (ground) plane of the circle that describes the agent (character) size.
* Serves at setting walkableRadius (traversableAreaBorderSize) parameter, the precision of walkableRadius is affected by cellSize (cs).
*
* This parameter is also used by DetourCrowd to determine the area other agents have to avoid in order not to collide with an agent.
* The distance to erode/shrink the walkable area of the heightfield away from obstructions.
* [Limit: >=0]
*
* 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.
* While a value of zero is legal, it is not recommended and can result in odd edge case issues.
*
**/
Ogre::Real agentRadius;
/**
* The maximum allowed length for contour edges along the border of the mesh.
* [Limit: >=0]
* Extra vertices will be inserted as needed to keep contour edges below this length. A value of zero effectively disables this feature.
* Serves at setting maxEdgeLen, the precision of maxEdgeLen is affected by cellSize (cs).
**/
Ogre::Real edgeMaxLen;
/**
* The maximum distance a simplfied contour's border edges should deviate the original raw contour. (edge matching)
* [Limit: >=0] [Units: wu]
* The effect of this parameter only applies to the xz-plane.
*
* Also called maxSimplificationError or edgeMaxDeviation
* The maximum distance the edges of meshes may deviate from the source geometry.
* A lower value will result in mesh edges following the xz-plane geometry contour more accurately at the expense of an increased triangle count.
* A value to zero is not recommended since it can result in a large increase in the number of polygons in the final meshes at a high processing cost.
**/
Ogre::Real edgeMaxError;
/**
* The minimum number of cells allowed to form isolated island areas (size).
* [Limit: >=0]
* 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.
* Serves at setting minRegionArea, which will be set to the square of this value (the regions are square, thus area=size*size)
**/
Ogre::Real regionMinSize;
/**
* Any regions with a span count smaller than this value will, if possible, be merged with larger regions.
* [Limit: >=0] [Units: vx]
* Serves at setting MergeRegionArea, which will be set to the square of this value (the regions are square, thus area=size*size)
**/
Ogre::Real regionMergeSize;
/**
* The maximum number of vertices allowed for polygons generated during the contour to polygon conversion process.
* [Limit: >= 3]
* 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 (=6).
*
* Also called maxVertsPerPoly
* The maximum number of vertices per polygon for polygons generated during the voxel to polygon conversion process.
* Higher values increase processing cost, but can also result in better formed polygons in the final meshes. A value of around 6 is generally adequate with diminishing returns for higher values.
**/
int vertsPerPoly;
/**
* Sets the sampling distance to use when generating the detail mesh.
* (For height detail only.) [Limits: 0 or >= 0.9] [Units: wu]
*
* Also called contourSampleDistance
* Sets the sampling distance to use when matching the detail mesh to the surface of the original geometry.
* Impacts how well the final detail mesh conforms to the surface contour of the original geometry. Higher values result in a detail mesh which conforms more closely to the original geometry's surface at the cost of a higher final triangle count and higher processing cost.
* Setting this argument to less than 0.9 disables this functionality.
**/
Ogre::Real detailSampleDist;
/**
* The maximum distance the detail mesh surface should deviate from heightfield data.
* (For height detail only.) [Limit: >=0] [Units: wu]
*
* Also called contourMaxDeviation
* The maximum distance the surface of the detail mesh may deviate from the surface of the original geometry.
* The accuracy is impacted by contourSampleDistance.
* The value of this parameter has no meaning if contourSampleDistance is set to zero.
* Setting the value to zero is not recommended since it can result in a large increase in the number of triangles in the final detail mesh at a high processing cost.
* Stronly related to detailSampleDist (contourSampleDistance).
**/
Ogre::Real detailSampleMaxError;
/**
* Determines whether intermediary results are stored in OgreRecast class or whether they are removed after navmesh creation.
**/
bool keepInterResults;
/**
* Minimum height in number of (voxel) cells that the ceiling needs to be
* for an agent to be able to walk under. Related to cellHeight (ch) and
* agentHeight.
*
* Minimum floor to 'ceiling' height that will still allow the floor area to be considered walkable.
* [Limit: >= 3] [Units: vx]
* 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.
*
* Also called minTraversableHeight
* This value should be at least two times the value of cellHeight in order to get good results.
**/
int _walkableHeight;
/**
* Maximum ledge height that is considered to still be traversable, in number of cells (height).
* [Limit: >=0] [Units: vx].
* 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.
*
* Also called maxTraversableStep
* Represents the maximum ledge height that is considered to still be traversable.
* Prevents minor deviations in height from improperly showing as obstructions. Permits detection of stair-like structures, curbs, etc.
**/
int _walkableClimb;
/**
* The distance to erode/shrink the walkable area of the heightfield away from obstructions, in cellsize units.
* [Limit: >=0] [Units: vx]
* 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.
* While a value of zero is legal, it is not recommended and can result in odd edge case issues.
*
* Also called traversableAreaBorderSize
* Represents the closest any part of a mesh can get to an obstruction in the source geometry.
* Usually this value is set to the maximum bounding radius of agents utilizing the meshes for navigation decisions.
*
* This value must be greater than the cellSize to have an effect.
* The actual border will be larger around ledges if ledge clipping is enabled. See the clipLedges parameter for more information.
* The actual border area will be larger if smoothingTreshold is > 0. See the smoothingThreshold parameter for more information.
**/
int _walkableRadius;
/**
* The maximum allowed length for contour edges along the border of the mesh.
* [Limit: >=0] [Units: vx].
* Extra vertices will be inserted as needed to keep contour edges below this length. A value of zero effectively disables this feature.
*
* Also called maxEdgeLength
* The maximum length of polygon edges that represent the border of meshes.
* More vertices will be added to border edges if this value is exceeded for a particular edge.
* In certain cases this will reduce the number of long thin triangles.
* A value of zero will disable this feature.
**/
int _maxEdgeLen;
/**
* The minimum number of cells allowed to form isolated island areas.
* [Limit: >=0] [Units: vx].
* 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.
*
* Also called minUnconnectedRegionSize
* The minimum region size for unconnected (island) regions.
* The value is in voxels.
* Regions that are not connected to any other region and are smaller than this size will be culled before mesh generation. I.e. They will no longer be considered traversable.
**/
int _minRegionArea;
/**
* Any regions with a span count smaller than this value will, if possible, be merged with larger regions.
* [Limit: >=0] [Units: vx]
*
* Also called mergeRegionSize or mergeRegionArea
* Any regions smaller than this size will, if possible, be merged with larger regions.
* Value is in voxels.
* Helps reduce the number of small regions. This is especially an issue in diagonal path regions where inherent faults in the region generation algorithm can result in unnecessarily small regions.
* Small regions are left unchanged if they cannot be legally merged with a neighbor region. (E.g. Merging will result in a non-simple polygon.)
**/
int _mergeRegionArea;
/**
* Sets the sampling distance to use when generating the detail mesh.
* (For height detail only.) [Limits: 0 or >= 0.9] [Units: wu]
*
* Also called contourSampleDistance
* Sets the sampling distance to use when matching the detail mesh to the surface of the original geometry.
* Impacts how well the final detail mesh conforms to the surface contour of the original geometry. Higher values result in a
* detail mesh which conforms more closely to the original geometry's surface at the cost of a higher final triangle count and higher processing cost.
* Setting this argument to less than 0.9 disables this functionality.
*
* The difference between this parameter and edge matching (edgeMaxError) is that this parameter operates on the height rather than the xz-plane.
* It also matches the entire detail mesh surface to the contour of the original geometry. Edge matching only matches edges of meshes to the contour of the original geometry.
**/
Ogre::Real _detailSampleDist;
/**
* The maximum distance the detail mesh surface should deviate from heightfield data.
* (For height detail only.) [Limit: >=0] [Units: wu]
*
* Also called contourMaxDeviation
* The maximum distance the surface of the detail mesh may deviate from the surface of the original geometry.
* The accuracy is impacted by contourSampleDistance (detailSampleDist).
* The value of this parameter has no meaning if contourSampleDistance is set to zero.
* Setting the value to zero is not recommended since it can result in a large increase in the number of triangles in the final detail mesh at a high processing cost.
* This parameter has no impact if contourSampleDistance is set to zero.
**/
Ogre::Real _detailSampleMaxError;
};
// Advance declarations, needed for expressing friendship relation
class OgreDetourTileCache;
class OgreDetourCrowd;
/**
* This class serves as a wrapper between Ogre and Recast/Detour
* It's not a full wrapper, but instead offers the main features needed
* to integrate the Ogre demo with Recast and Detour.
**/
class OgreRecast
{
public:
/**
* Use static geometry for debug drawing the navmesh.
* This is only useful when drawing tiled navmeshes (detourTileCache) and when there are
* a lot of tiles, otherwise this will result in a too high batchcount.
**/
static bool STATIC_GEOM_DEBUG;
/**
* Set to true to print verbose messages about debug drawing.
**/
static bool VERBOSE;
/**
* Initialize an OgreRecast module to generate single navmeshes or store tiled navmeshes generated by OgreDetourTileCache.
* You can specify custom parameters for navmesh building (which are also used by OgreDetourTileCache and some by DetourCrowd).
* Not specifying parameters will result in defaults being used.
**/
OgreRecast(Ogre::SceneManager* sceneMgr, OgreRecastConfigParams configParams = OgreRecastConfigParams());
/**
* Should be called every frame. Used for updating navmesh debug drawing when using static geometry
* (STATIC_GEOM_DEBUG).
**/
void update(void);
/**
* The agent radius for which this navmesh is built.
**/
float getAgentRadius(void);
/**
* The agent height for which this navmesh is built.
**/
float getAgentHeight(void);
/**
* The amount with which the drawn debug path is offset from the ground
**/
float getPathOffsetFromGround(void);
/**
* The amount with which the drawn debug navmesh polys are offset from the ground.
**/
float getNavmeshOffsetFromGround(void);
/**
* Size of each cell (the length of one dimension on the x-y plane) in world
* units.
**/
float getCellSize(void) { return m_cellSize; }
/**
* Height in world units of one navmesh cell.
**/
float getCellHeight(void) { return m_cellHeight; }
/**
* Cleanup recast parameters and variables.
* This does not clean up the objects related to debug drawing.
**/
void RecastCleanup();
/**
* Configure navbuild parameters for this module.
* Sets m_cfg and other parameters.
**/
void configure(OgreRecastConfigParams params);
/**
* Build a navigation mesh from the specified list of Ogre::Entities as source.
* It is required that all supplied entities are attached to a scenenode in the scene
* before calling this method.
*
* Recast will construct a navmesh using some configuration parameters, which are currently
* just set inside this method, but should be extracted to somewhere else in the future.
* The most important parameters to set are cellsize, agentHeight and agentRadius.
**/
bool NavMeshBuild(std::vector<Ogre::Entity*> srcMeshesA);
/**
* Build a navmesh from the specified input geometry.
* @see{OgreRecast::NavMeshBuild(std::vector<Ogre::Entity*>)}
**/
bool NavMeshBuild(InputGeom* input);
/**
* Find a path beween start point and end point and, if possible, generates a list of lines in a path.
* It might fail if the start or end points aren't near any navmesh polygons, or if the path is too long,
* or it can't make a path, or various other reasons.
*
* nPathSlot: The index number for the slot in which the found path is to be stored.
* nTarget: Number identifying the target the path leads to. Recast does nothing with this, but you can give them
* meaning in your own application.
*
* Return codes:
* 0 found path
* -1 Couldn't find polygon nearest to start point
* -2 Couldn't find polygon nearest to end point
* -3 Couldn't create a path
* -4 Couldn't find a path
* -5 Couldn't create a straight path
* -6 Couldn't find a straight path
**/
int FindPath(float* pStartPos, float* pEndPos, int nPathSlot, int nTarget);
/**
* Find a path between start and end position, works with Ogre::Vector3 points.
* @see{OgreRecast::FindPath(float*, float*, int, int)}
**/
int FindPath(Ogre::Vector3 startPos, Ogre::Vector3 endPos, int nPathSlot, int nTarget);
/**
* Retrieve the path at specified slot defined as a line along an ordered set of 3D positions.
* The path has a maximum length of MAX_PATHVERT, and is an empty list in case no path is
* defined or an invalid pathSlot index is given.
**/
std::vector<Ogre::Vector3> getPath(int pathSlot);
/**
* The ID number identifying the target for the path at specified slot. Targets have
* no meaning for OgreRecast but you can use them to give them their own meanings.
* Returns 0 when a faulty pathSlot is given.
**/
int getTarget(int pathSlot);
/**
* Draw the nav mesh for debug purposes. The navmesh is converted to an Ogre::Mesh and
* put inside the scene for rendering.
**/
void drawNavMesh(void);
/**
* Calculate visual Ogre meshes to visualize the recast navigation mesh for debugging.
*
* Convert the calculated navmesh into an Ogre::ManualObject mesh, and put it in the scene.
* A scenenode with name "RecastSN" will be created in the root of the scenegraph. This
* scenenode is referenced by the m_pRecastSN member variable.
*
* Within this scenenode a mesh with name "RecastMOWalk" will be added, which is stored in
* member variable m_pRecastMOWalk. This mesh will represents the faces of the segments in
* the navmesh. Not the lines indicating the edges of the navmesh.
*
* The manual object referenced by member variable m_pRecastMONeighbour and with name
* "RecastMONeighbour" is also added to the same scenenode. This object contains the lines
* between neighbouring segments of the navmesh. These are all except the outer edges of
* the navmesh.
*
* Finally, the manual object referenced by member variable m_pRecastMOBoundary and with name
* "RecastMOBoundary" is added to the scene node. It is a collection of lines that represent
* the outer edges of the navmesh, being the ones that do not have any neighbouring segments.
**/
void CreateRecastPolyMesh(const Ogre::String name, const unsigned short *verts, const int nverts,
const unsigned short *polys, const int npolys, const unsigned char *areas,
const int maxpolys, const unsigned short *regions, const int nvp,
const float cs, const float ch, const float *orig, bool colorRegions=true);
/**
* Remove debug drawn navmesh for navmesh tile with specified (compressed detourtile) reference.
**/
void removeDrawnNavmesh(unsigned int tileRef);
/**
* Create an Ogre::ManualObject mesh to visually debug a path on the navmesh found
* using detour. The path stored in the specified slot number is visualized, and the
* result is stored under the m_pRecastMOPath member variable and has the name "RecastMOPath".
**/
void CreateRecastPathLine(int nPathSlot);
/**
* Returns the poly filter that will be used for all (random) point and nearest poly searches,
* as well as for pathfinding.
**/
dtQueryFilter getFilter(void);
/**
* Set the poly filter that will be used for all (random) point and nearest poly searches,
* as well as for pathfinding.
**/
void setFilter(const dtQueryFilter filter);
/**
* Get the offset size (box) around points used to look for nav polygons.
* This offset is used in all search for points on the navmesh.
* The maximum offset that a specified point can be off from the navmesh.
**/
Ogre::Vector3 getPointExtents(void);
/**
* Set the offset size (box) around points used to look for nav polygons.
* This offset is used in all search for points on the navmesh.
* The maximum offset that a specified point can be off from the navmesh.
**/
void setPointExtents(Ogre::Vector3 extents);
/**
* Find a point on the navmesh closest to the specified point position, within predefined
* bounds.
* Returns true if such a point is found (returned as resultPt), returns false
* if no point is found. When false is returned, resultPt is not altered.
**/
bool findNearestPointOnNavmesh(Ogre::Vector3 position, Ogre::Vector3 &resultPt);
bool findNearestPolyOnNavmesh(Ogre::Vector3 position, Ogre::Vector3 &resultPt, dtPolyRef &resultPoly);
/**
* Returns a random point on the navmesh.
**/
Ogre::Vector3 getRandomNavMeshPoint();
/**
* Returns a random point on the navmesh that is within the circle of specified radius
* and center. Will return a random point within each navmesh polygon that touches the
* circle (so the exact point could in fact be a little outside of the specified circle).
* Returns center when no random point can be found (eg. center is too far from a navmesh
* poly).
**/
Ogre::Vector3 getRandomNavMeshPointInCircle(Ogre::Vector3 center, Ogre::Real radius);
/**
* Convenience function for converting between Ogre::Vector3
* and float* used by recast.
**/
static void OgreVect3ToFloatA(const Ogre::Vector3 vect, float* result);
/**
* Convenience function for converting between float* used by recast
* and Ogre::Vector3.
**/
static void FloatAToOgreVect3(const float* vect, Ogre::Vector3 &result);
/**
* Translate error code of detour findPath into a readable explanation.
**/
Ogre::String getPathFindErrorMsg(int errorCode);
/**
* The configuration of the recast navmesh.
**/
rcConfig getConfig(void);
OgreRecastNavmeshPruner* getNavmeshPruner(void);
// helper debug drawing stuff
Ogre::ManualObject* m_pRecastMOWalk ;
Ogre::ManualObject* m_pRecastMONeighbour ;
Ogre::ManualObject* m_pRecastMOBoundary ;
Ogre::ManualObject* m_pRecastMOPath ;
Ogre::SceneNode* m_pRecastSN ;
Ogre::SceneManager* m_pSceneMgr;
protected:
/**
* Draw the specified recast poly mesh to scene for debugging.
**/
void drawPolyMesh(const struct rcPolyMesh &mesh, bool colorRegions=true);
unsigned char* m_triareas;
rcHeightfield* m_solid;
rcCompactHeightfield* m_chf;
rcContourSet* m_cset;
rcPolyMesh* m_pmesh;
rcConfig m_cfg;
rcPolyMeshDetail* m_dmesh;
class InputGeom* m_geom;
class dtNavMesh* m_navMesh;
class dtNavMeshQuery* m_navQuery;
unsigned char m_navMeshDrawFlags;
rcContext* m_ctx;
float m_cellSize;
float m_cellHeight;
float m_agentHeight;
float m_agentRadius;
float m_agentMaxClimb;
float m_agentMaxSlope;
float m_regionMinSize;
float m_regionMergeSize;
float m_edgeMaxLen;
float m_edgeMaxError;
int m_vertsPerPoly;
float m_detailSampleDist;
float m_detailSampleMaxError;
bool m_keepInterResults ;
// Off-Mesh connections. Not used yet.
static const int MAX_OFFMESH_CONNECTIONS = 256;
float m_offMeshConVerts[MAX_OFFMESH_CONNECTIONS*3*2];
float m_offMeshConRads[MAX_OFFMESH_CONNECTIONS];
unsigned char m_offMeshConDirs[MAX_OFFMESH_CONNECTIONS];
unsigned char m_offMeshConAreas[MAX_OFFMESH_CONNECTIONS];
unsigned short m_offMeshConFlags[MAX_OFFMESH_CONNECTIONS];
unsigned int m_offMeshConId[MAX_OFFMESH_CONNECTIONS];
int m_offMeshConCount;
// helper debug drawing stuff
int m_nAreaCount ;
Ogre::StaticGeometry *m_sg;
bool m_rebuildSg;
float m_flTestStart[3] ;
float m_flTestEnd[3] ;
float *m_normals;
int m_flDataX;
int m_flDataY;
/**
* Offset that the debug path is drawn from the ground.
**/
float m_pathOffsetFromGround;
/**
* Offset that the debug navmesh is drawn from the ground.
**/
float m_navMeshOffsetFromGround;
/**
* Offset that the navmesh edges are drawn from the ground.
**/
float m_navMeshEdgesOffsetFromGround;
/**
* Colours used for the various debug drawing objects.
**/
Ogre::ColourValue m_navmeshNeighbourEdgeCol;
Ogre::ColourValue m_navmeshOuterEdgeCol;
Ogre::ColourValue m_navmeshGroundPolygonCol;
Ogre::ColourValue m_navmeshOtherPolygonCol;
Ogre::ColourValue m_pathCol;
/**
* Stores all created paths
**/
PATHDATA m_PathStore[MAX_PATHSLOT];
/**
* The poly filter that will be used for all (random) point and nearest poly searches.
**/
dtQueryFilter *mFilter;
/**
* The offset size (box) around points used to look for nav polygons.
* This offset is used in all search for points on the navmesh.
* The maximum offset that a specified point can be off from the navmesh.
**/
float mExtents[3];
Ogre::LogManager* m_pLog;
OgreRecastNavmeshPruner *mNavmeshPruner;
private:
/**
* Retrieve the vertices from a manual object, even if they are not referenced by faces.
* Does not retrieve faces, as it is intended to retrieve line drawings.
**/
std::vector<Ogre::Vector3> getManualObjectVertices(Ogre::ManualObject *man);
// Friend OgreDetourTileCache so it can access the navmesh of this component
friend class OgreDetourTileCache;
// Friend OgreDetourCrowd so it can access the navmesh of this component
friend class OgreDetourCrowd;
};
#endif // #ifndef __OgreRecast_h_

View File

@@ -0,0 +1,91 @@
/*
OgreCrowd
---------
Copyright (c) 2012 Jonas Hauquier
Additional contributions by:
- mkultra333
- Paul Wilson
Sincere thanks and to:
- Mikko Mononen (developer of Recast navigation libraries)
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 __OgreRecastDefinitions_h_
#define __OgreRecastDefinitions_h_
/**
* This file sets up all definitions needed by Recast/Detour.
* Most of it is just taken from the official demo application.
**/
// recast/detour stuff
#include <recastnavigation/Recast.h>
#include <recastnavigation/DetourNavMesh.h>
#include <recastnavigation/DetourNavMeshBuilder.h>
#include <recastnavigation/DetourNavMeshQuery.h>
#define MAX_PATHSLOT 128 // how many paths we can store
#define MAX_PATHPOLY 256 // max number of polygons in a path
#define MAX_PATHVERT 512 // most verts in a path
// structure for storing output straight line paths
typedef struct
{
float PosX[MAX_PATHVERT] ;
float PosY[MAX_PATHVERT] ;
float PosZ[MAX_PATHVERT] ;
int MaxVertex ;
int Target ;
}
PATHDATA ;
// These are just sample areas to use consistent values across the samples.
// The use should specify these base on his needs.
// bzn most aren't used yet, just SAMPLE_POLYAREA_GROUND and SAMPLE_POLYFLAGS_WALK
enum SamplePolyAreas
{
SAMPLE_POLYAREA_GROUND,
SAMPLE_POLYAREA_WATER,
SAMPLE_POLYAREA_ROAD,
SAMPLE_POLYAREA_DOOR,
SAMPLE_POLYAREA_GRASS,
SAMPLE_POLYAREA_JUMP,
};
enum SamplePolyFlags
{
SAMPLE_POLYFLAGS_WALK = 0x01, // Ability to walk (ground, grass, road)
SAMPLE_POLYFLAGS_SWIM = 0x02, // Ability to swim (water).
SAMPLE_POLYFLAGS_DOOR = 0x04, // Ability to move through doors.
SAMPLE_POLYFLAGS_JUMP = 0x08, // Ability to jump.
SAMPLE_POLYFLAGS_DISABLED = 0x10, // Disabled polygon
SAMPLE_POLYFLAGS_ALL = 0xffff // All abilities.
};
#endif // #ifndef __OgreRecastDefinitions_h_

View File

@@ -0,0 +1,69 @@
/*
OgreCrowd
---------
Copyright (c) 2012 Jonas Hauquier
Additional contributions by:
- mkultra333
- Paul Wilson
Sincere thanks and to:
- Mikko Mononen (developer of Recast navigation libraries)
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 OGRERECASTNAVMESHPRUNER_H
#define OGRERECASTNAVMESHPRUNER_H
#include "OgreRecast.h"
/**
* Tool to flood-fill the navmesh polygons starting at a specific polygon and marking
* all reachable neighbours. This allows to prune off unreachable parts of the navmesh.
*
* Based on NavMeshPruneTool from the original recast sample.
* The navmesh prune tool allows to disable unreachable polygons. There is currently no
* way to discard the disabled polys, because the search is done on the final navmesh
* data (when it is already rasterized) and it is really hard to modify.
**/
class OgreRecastNavmeshPruner
{
public:
OgreRecastNavmeshPruner(OgreRecast *recast, dtNavMesh *navMesh);
bool floodNavmesh(Ogre::Vector3 startPoint);
void clearSelection(void);
void pruneSelected(void);
protected:
void floodNavmesh(dtPolyRef start, unsigned char flag);
void disableUnvisitedPolys(void);
OgreRecast *mRecast;
dtNavMesh *mNavmesh;
class NavmeshFlags* mFlags;
};
#endif // OGRERECASTNAVMESHPRUNER_H

View File

@@ -0,0 +1,146 @@
/*
OgreCrowd
---------
Copyright (c) 2012 Jonas Hauquier
Additional contributions by:
- mkultra333
- Paul Wilson
Sincere thanks and to:
- Mikko Mononen (developer of Recast navigation libraries)
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 RECASTCONVEXHULL_H
#define RECASTCONVEXHULL_H
#include <Ogre.h>
class InputGeom; // Advance declaration
/**
* The maximum amount of points that a convex hull
* used for dynamic obstacles on a navmesh can consist of.
* For performance reasons this cannot be too high.
* But has to have at least twice the number of vertices that
* the input has!!!
**/
static const int MAX_CONVEXVOL_PTS = 90; // TODO increase? or find better convex hull algorithm
/**
* Volume describing a convex hull around geometry. Or a convex area to mark on the navmesh.
* Can be used as a collision mesh for dynamic obstacles.
*
* Can also be used for marking areas of navmesh polygons with a specific flag.
* For example, mark a convex area with area set to RC_NULL_AREA to make the area unwalkable (obstacle).
* Area marking can also be used for marking more specific areas, such as swim (water),
* mud, door, ...
* Areas can assign weights to navmesh polygons that can be used during A* pathfinding
* for determining the optimal path.
*
* Note that convex volume marking is not the only way of assigning flags to navmesh polygons.
* Navmesh polygons can also be assigned a flag directly, the problem is that with automatic
* navmesh generation you have no control over the exact size and position of each polygon on the
* navmesh, making it sometimes impossible to mark an intended area precisely. That's why recast
* offers a rcMarkConvexArea (and in the future hopefully dtMarkConvexArea) to allow you to
* explicitly mark where a separate polygon should be in the navmesh, so that you can assign it
* a custom area flag. Convex area marking is also referred to as cookie cutter algorithm.
*
* If you define a custom constructor for convexVolume objects, note that recast requires the
* vertices of convex shapes to be wound in clockwise order (this is very important, or convex
* area marking will not work)!
*
* Also note that recast requires convex volumes to be offset with the agent radius so the agent
* does not collide with the edges.
**/
class ConvexVolume
{
public:
/**
* Create a convex hull in 2D space (on the xz plane) from
* the specified 3D points.
**/
ConvexVolume(InputGeom *geom, float offset = 0.0f);
/**
* Create a convex hull from a bounding box
**/
ConvexVolume(Ogre::AxisAlignedBox boundingBox, float offset = 0.0f);
/**
* Move this convex hull to a new world position offset with specified
* translation vector.
* Can be done pretty fast. Due to the 2D nature of this convex hull it's
* not possible to apply an arbitrary rotation, however (though in theory
* rotations around Y axis would work).
**/
void move(Ogre::Vector3 translate);
/**
* The vertices of this convex hull.
* Vertices are stored as three subsequent values, in order x, y, z
* Size of this array is always a multiple of 3, exactly 3*nverts
**/
float verts[MAX_CONVEXVOL_PTS*3];
/**
* Number of vertices in verts.
* The size of the verts array is actually nverts*3
**/
int nverts;
/**
* Minimum and maximum height of this convex hull.
**/
float hmin, hmax;
/**
* Axis aligned boundig box minimum and maximum of this convex hull.
**/
float bmin[3], bmax[3];
/**
* Area flag for the navmesh polygon that will be marked with this convex volume.
* For example, set to RC_NULL_AREA to make the area unwalkable (obstacle).
* Area marking can also be used for marking more specific areas, such as swim (water),
* mud, door, ...
* Areas can assign weights to navmesh polygons that can be used during A* pathfinding
* for determining the optimal path.
**/
int area;
private:
/**
* Compare two points. Returns true if they are equal.
**/
static inline bool cmppt(const float* a, const float* b);
/**
* isLeftOf comparison. Returns true if point c is left of line a-b.
**/
static inline bool left(const float* a, const float* b, const float* c);
};
#endif // RECASTCONVEXHULL_H

View File

@@ -0,0 +1,477 @@
/*
OgreCrowd
---------
Copyright (c) 2012 Jonas Hauquier
Additional contributions by:
- mkultra333
- Paul Wilson
Sincere thanks and to:
- Mikko Mononen (developer of Recast navigation libraries)
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 RECASTINPUTGEOM_H
#define RECASTINPUTGEOM_H
#include <Ogre.h>
#include <Terrain/OgreTerrain.h>
#include <Terrain/OgreTerrainGroup.h>
#include "RecastConvexHull.h"
/**
* One node or chunk of the chunky tri mesh.
* Contains a 2D xz plane bounding box.
* n is the number of tris contained in this chunk.
* i is the starting index of the tris contained in this chunk.
* The actual tris are in the chunkyMesh object itself, in linear
* order per node, so that each node only needs the begin position
* and tri count to reference its tris.
**/
struct rcChunkyTriMeshNode
{
float bmin[2], bmax[2];
int i, n;
};
/**
* Spatial subdivision structure that structures triangles
* in axis-aligned boxes of a fixed size.
* This allows to quickly retrieve the triangles in a specific box,
* at the cost of a small pre-process step and extra memory usage.
**/
struct rcChunkyTriMesh
{
inline rcChunkyTriMesh() : nodes(0), tris(0) {};
inline ~rcChunkyTriMesh() { if(nodes) delete [] nodes; if(tris) delete [] tris; }
rcChunkyTriMeshNode* nodes;
int nnodes;
int* tris;
int ntris;
int maxTrisPerChunk;
};
/// Creates partitioned triangle mesh (AABB tree),
/// where each node contains at max trisPerChunk triangles.
bool rcCreateChunkyTriMesh(const float* verts, const int* tris, int ntris,
int trisPerChunk, rcChunkyTriMesh* cm);
/// Returns the chunk indices which overlap the input rectable.
int rcGetChunksOverlappingRect(const rcChunkyTriMesh* cm, float bmin[2], float bmax[2], int* ids, const int maxIds);
/// Returns the chunk indices which overlap the input segment.
int rcGetChunksOverlappingSegment(const rcChunkyTriMesh* cm, float p[2], float q[2], int* ids, const int maxIds);
/**
* Helper class to manage input geometry used as input for recast
* to build a navmesh.
* Adds extra features such as creating off-mesh connections between
* points in the geometry, raycasting to polygon level and bounding box
* intersection tests.
* It also allows to add or remove temporary obstacles to the geometry and
* add convex shapes as extra obstacles.
*
* This class handles the conversion of Ogre::Entities to recast compatible
* input format.
**/
class InputGeom
{
public:
/**
* Create recast compatible inputgeom from the specified entities. The entities have to be added to the
* scene before this call, as we need to calculate the world coordinates of the entity.
* Vertices and faces of the specified source entities are stored in this inputGeom, individual entity
* grouping and origin points are lost.
**/
InputGeom(std::vector<Ogre::Entity*> srcMeshes);
/**
* @see{InputGeom(std::vector<Ogre::Entity*>)}
* The same, only for a single entity.
**/
InputGeom(Ogre::Entity* srcMesh);
/**
* Construct inputGeom from the specified entities, only geometry that falls within the specified bounds
* is stored. Note: this is not done optimally, only bounding box intersections are used. But tile building
* is further optimized due to the use of the chunky tri mesh structure that is built within this inputGeom.
* Further this constructor is the same as @see{InputGeom(std::vector<Ogre::Entity*>)}
* Make sure to adapt your tileBounds fall together with the tilecache bounds so that they cover exactly the
* tiles you want to rebuild!! Don't call this with an arbitrary bounding box!
* Use OgreDetourTileCache::getTileAlignedBox() instead.
**/
InputGeom(std::vector<Ogre::Entity*> srcMeshes, const Ogre::AxisAlignedBox &tileBounds);
/**
* Construct inputGeom from terrain geometry and specified entities.
* The entity part is the same as @see{InputGeom(std::vector<Ogre::Entity*>)}
* Terrain geometry is added from highest LOD level of terrain.
**/
InputGeom(Ogre::TerrainGroup *terrainGroup, std::vector<Ogre::Entity*> srcMeshes = std::vector<Ogre::Entity*>());
/**
* Create inputGeom from terrain and entity polys that fall within specified bounding box, for rebuilding only tiles that fall within the box.
* For terrain, only the x-z bounding plane of the box is looked at (height is ignored), and only the necessary tris from the terrain are copied into inputGeom.
* For entities only simple bounding box tests happen for determining whether an entity should be added in its entirety to this inputGeom or not.
* Make sure to adapt your tileBounds fall together with the tilecache bounds so that they cover exactly the tiles you want to rebuild.
**/
InputGeom(const Ogre::AxisAlignedBox &tileBounds, Ogre::TerrainGroup *terrainGroup, std::vector<Ogre::Entity*> srcMeshes = std::vector<Ogre::Entity*>());
/**
* Output inputGeom to obj wavefront file.
* This can be used to test your inputGeom in the original recast demo (which loads .obj files).
**/
void writeObj(Ogre::String filename);
/**
* Retrieve a bounding box for the entire inputGeom, in world space
* coordinates.
**/
Ogre::AxisAlignedBox getBoundingBox(void);
/**
* Convenience funtion to calculate the bounding box of an entity in
* world coordinates.
* Entity needs to be added to the scene before calling this function.
**/
static Ogre::AxisAlignedBox getWorldSpaceBoundingBox(Ogre::MovableObject *ent);
/**
* Apply the specified rotation to all vertices of this geometry.
* Note that this does not affect the entities from which the inputGeom was
* originally built!
* Rotation will happen around specified pivot point (because inputGeom
* is in world-space coordinates and has no explicit reference to its
* origin or center point). Default value for pivot point is the world origin.
* Usually you specify the world position of the entity that this inputGeom was
* built from as pivot point (or the center of the bounding box in case of multiple
* entities).
*
* In this implementation, bounding boxes are not recalculated after a rotation (
* this is a little more difficult to do efficiently than it might seem).
* You cannot continuously rotate the same AABB because it will keep growing in size.
* You can either calculate a bounding box that fits the model in all possible rotations,
* or recalculate a bounding box from all verts (inefficient), or use a bounding box or
* convex hull from the physics engine to speed it up.
**/
void applyOrientation(Ogre::Quaternion orientation, Ogre::Vector3 pivot = Ogre::Vector3::ZERO);
/**
* Move all vertices of this inputGeom with the offset specified by the
* translation vector.
**/
void move(Ogre::Vector3 translate);
~InputGeom();
/**
* Retrieves the vertices stored within this inputGeom. The verts are an array of floats in which each
* subsequent three floats are in order the x, y and z coordinates of a vert. The size of this array is
* always a multiple of three and is exactly 3*getVertCount().
**/
float* getVerts(void);
/**
* The number of vertices stored in this inputGeom.
**/
int getVertCount(void);
/**
* Retrieves the tris stored in this inputGeom.
* A tri is defined by a sequence of three indexes which refer to an index position in the getVerts() array.
* Similar to getVerts, the size of this array is a multitude of 3 and is exactly 3*getTriCount().
**/
int* getTris(void);
/**
* The number of triangles stored in this inputGeom.
**/
int getTriCount(void);
/**
* Retrieve the normals calculated for this inputGeom. Note that the normals are not exact and are not meant for rendering,
* but they are good enough for navmesh calculation. Each normal corresponds to one vertex from getVerts() with the same index.
* The size of the normals array is 3*getVertCount().
**/
float* getNormals(void);
/**
* The axis aligned bounding box minimum of this input Geom.
**/
float* getMeshBoundsMin(void);
/**
* The axis aligned bounding box maximum of this input Geom.
**/
float* getMeshBoundsMax(void);
/**
* Determines whether this inputGeom has no geometry stored.
* Returns true if this inputGeom has no geometry.
**/
bool isEmpty(void);
/**
* Use this to verify whether the generated geometry in this inputGeom matches the geometry in your scene.
* Draws all inputGeom vertices as points in the scene.
**/
void debugMesh(Ogre::SceneManager *sceneMgr);
/**
* Maximum number of convex volume obstacles that can be added to this inputGeom.
**/
static const int MAX_VOLUMES = 256;
/**
* Retrieve vertex data from a mesh
* From http://www.ogre3d.org/tikiwiki/RetrieveVertexData
*
* This example is taken from monster's OgreODE project. The full source can be found under ogreaddons/ogreode in the Ogre SVN.
* It has been adopted, so that it can be used separately. Just copy/paste it into your own project.
*
* Note that this code assumes sizeof(long) == sizeof(uint32_t), which is not true on AMD64 Linux.
**/
static void getMeshInformation(const Ogre::MeshPtr mesh,
size_t &vertex_count,
Ogre::Vector3* &vertices,
size_t &index_count,
unsigned long* &indices,
const Ogre::Vector3 &position = Ogre::Vector3::ZERO,
const Ogre::Quaternion &orient = Ogre::Quaternion::IDENTITY,
const Ogre::Vector3 &scale = Ogre::Vector3::UNIT_SCALE);
/**
* getMeshInformation for manual meshes.
**/
static void getManualMeshInformation(const Ogre::ManualObject *manual,
size_t &vertex_count,
Ogre::Vector3* &vertices,
size_t &index_count,
unsigned long* &indices,
const Ogre::Vector3 &position = Ogre::Vector3::ZERO,
const Ogre::Quaternion &orient = Ogre::Quaternion::IDENTITY,
const Ogre::Vector3 &scale = Ogre::Vector3::UNIT_SCALE);
/**
* Debug function for drawing a convex hull as lines in the scene.
* Returns the manual object added to the scene.
* It's possible to specify a custom color for the drawn lines, default is grey.
**/
static Ogre::ManualObject* drawConvexVolume(ConvexVolume *vol, Ogre::SceneManager* sceneMgr, Ogre::ColourValue color=Ogre::ColourValue(0.5, 0.5, 0.5));
/**
* Debug function for drawing a bounding box as lines in the scene.
* Returns the manual object added to the scene.
* It's possible to specify a custom color for the drawn lines, default is grey.
**/
static Ogre::ManualObject* drawBoundingBox(Ogre::AxisAlignedBox box, Ogre::SceneManager *sceneMgr, Ogre::ColourValue color=Ogre::ColourValue(0.5, 0.5, 0.5));
/**
* The chunky tri mesh generated for this inputGeom.
* Chunky tri meshes are only used when building tiled navmeshes, and are not essential,
* just more optimized.
**/
inline const rcChunkyTriMesh* getChunkyMesh() const { return m_chunkyMesh; }
/**
* Raycast this inputGeometry.
**/
bool raycastMesh(float* src, float* dst, float& tmin);
/**
* See OgreDetourTileCache::hitTestObstacle, but here it serves for
* finding convexVolumes.
**/
int hitTestConvexVolume(const float* sp, const float* sq);
/**
* Retrieve the convex volume obstacle with specified index from this inputGeom.
**/
ConvexVolume* getConvexVolume(int volIdx);
// TODO off-mesh connections not implemented yet
/// @name Off-Mesh connections.
///@{
int getOffMeshConnectionCount() const { return m_offMeshConCount; }
const float* getOffMeshConnectionVerts() const { return m_offMeshConVerts; }
const float* getOffMeshConnectionRads() const { return m_offMeshConRads; }
const unsigned char* getOffMeshConnectionDirs() const { return m_offMeshConDirs; }
const unsigned char* getOffMeshConnectionAreas() const { return m_offMeshConAreas; }
const unsigned short* getOffMeshConnectionFlags() const { return m_offMeshConFlags; }
const unsigned int* getOffMeshConnectionId() const { return m_offMeshConId; }
void addOffMeshConnection(const float* spos, const float* epos, const float rad,
unsigned char bidir, unsigned char area, unsigned short flags);
void deleteOffMeshConnection(int i);
///@}
/// @name Box Volumes.
///@{
int getConvexVolumeCount() const { return m_volumeCount; }
const ConvexVolume* const* getConvexVolumes() const { return m_volumes; }
int addConvexVolume(ConvexVolume *vol);
bool deleteConvexVolume(int i, ConvexVolume** = NULL);
// Not implemented
void drawConvexVolumes(struct duDebugDraw* dd, bool hilight = false);
int getConvexVolumeId(ConvexVolume *convexHull);
///@}
/**
* Create a convex hull that fits around the points
* in this geometry.
* This type of convex hull is in fact a 2D shape on the
* xz plane (on the ground) and with a certain height, and
* at a certain distance above the ground (min 0, which means
* on the ground).
*
* Offset determines the offset of the hull from the
* geometry. 0 offset means the convex hull wraps
* tightly around the mesh.
*
* Convex hulls can be used as temporary or dynamic obstacles
* on a navmesh.
* You probably only want to create convex hulls from single
* entities or a few entities that are close together.
**/
ConvexVolume* getConvexHull(Ogre::Real offset = 0.0f);
private:
/**
* Calculate max and min bounds of this geometry.
**/
void calculateExtents(void);
/**
* Build chunky tri mesh.
* Only needed for building tiled navmeshes.
**/
void buildChunkyTriMesh(void);
/**
* Convert triangles and verticies of ogre entities
* to recast internal geometry format.
**/
void convertOgreEntities(void);
/**
* Convert ogre entities whose bounding box intersects the specified bounds
* to inputGeom.
**/
void convertOgreEntities(const Ogre::AxisAlignedBox &tileBounds);
/**
* Recast input vertices
**/
float* verts;
/**
* Number of verts
* The actual size of the verts array is 3*nverts
**/
int nverts;
/**
* Recast input tris
* Tris are index references to verts array
**/
int* tris;
/**
* The number of tris
* The actual size of the tris array is 3*ntris
**/
int ntris;
/**
* Normals calculated for verts
* Normals are not entirely accurate but good enough for recast use.
* Size of the normals array is 3*nverts
**/
float* normals;
/**
* Axis aligned bounding box of this inputGeom minimum.
**/
float* bmin;
/**
* Axis aligned bounding box of this inputGeom maximum.
**/
float* bmax;
/**
* Ogre entities this inputGeom was constructed from.
**/
std::vector<Ogre::Entity*> mSrcMeshes;
/**
* Reference node to which the absolute coordinates of the verts in this inputGeom was calculated.
* Is usually the scene rootnode.
**/
Ogre::SceneNode *mReferenceNode;
/**
* Terrain pages from which this inputGeom was constructed.
**/
Ogre::TerrainGroup *mTerrainGroup;
/**
* Optimized structures that stores triangles in axis aligned boxes of uniform size
* (tiles). Allows quick access to a part of the geometry, but requires more memory to store.
**/
rcChunkyTriMesh *m_chunkyMesh;
// Not implemented yet
/// @name Off-Mesh connections.
///@{
static const int MAX_OFFMESH_CONNECTIONS = 256;
float m_offMeshConVerts[MAX_OFFMESH_CONNECTIONS*3*2];
float m_offMeshConRads[MAX_OFFMESH_CONNECTIONS];
unsigned char m_offMeshConDirs[MAX_OFFMESH_CONNECTIONS];
unsigned char m_offMeshConAreas[MAX_OFFMESH_CONNECTIONS];
unsigned short m_offMeshConFlags[MAX_OFFMESH_CONNECTIONS];
unsigned int m_offMeshConId[MAX_OFFMESH_CONNECTIONS];
int m_offMeshConCount;
///@}
/// @name Convex Volumes (temporary) added to this geometry.
///@{
ConvexVolume* m_volumes[MAX_VOLUMES];
int m_volumeCount;
///@}
};
#endif // RECASTINPUTGEOM_H

View File

@@ -0,0 +1,80 @@
/*
OgreCrowd
---------
Copyright (c) 2012 Jonas Hauquier
Additional contributions by:
- mkultra333
- Paul Wilson
Sincere thanks and to:
- Mikko Mononen (developer of Recast navigation libraries)
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 TESTCHARACTER_H
#define TESTCHARACTER_H
#include "Character.h"
#include <Ogre.h>
/**
* Simple representation of an agent. This is the most simple way to show and debug
* detour crowd agents.
* Agents are represented as blue cylinders.
**/
class TestCharacter : public Character
{
public:
/**
* Create a simple test character, the entities will be placed in the specified scene manager.
* detourCrowd is the crowd manager in which an agent for this character will be created (make sure you don't create
* more characters than MAX_AGENTS).
* Position defines initial position the character has to be placed on (should be a valid position on the navmesh).
**/
TestCharacter(Ogre::String name, Ogre::SceneManager *sceneMgr, OgreDetourCrowd* detourCrowd, Ogre::Vector3 position = Ogre::Vector3::ZERO);
/**
* The entity that represents this character in the scene
**/
virtual Ogre::Entity* getEntity(void);
/**
* @see{Character::update(Ogre::Real)}
**/
virtual void update(Ogre::Real timeSinceLastFrame);
/**
* @see{Character::setDebugVisibility(bool)}
**/
virtual void setDebugVisibility(bool visible);
protected:
/**
* Main entity that represents this character.
**/
Ogre::Entity *mEnt;
};
#endif // TESTCHARACTER_H

View File

@@ -0,0 +1,151 @@
/*
OgreCrowd
---------
Copyright (c) 2012 Jonas Hauquier
Additional contributions by:
- mkultra333
- Paul Wilson
Sincere thanks and to:
- Mikko Mononen (developer of Recast navigation libraries)
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.
*/
#include "AnimateableCharacter.h"
// #include "OgreRecastApplication.h"
AnimateableCharacter::AnimateableCharacter(Ogre::String name, Ogre::SceneManager *sceneMgr, OgreDetourCrowd* detourCrowd, bool debugDraw, Ogre::Vector3 position)
: Character(name, sceneMgr, detourCrowd, position),
mAnimState(NULL),
mEnt(NULL),
mAnimSpeedScale(1),
mDebugNode(NULL),
mDebugDraw(debugDraw)
{
mNode = sceneMgr->getRootSceneNode()->createChildSceneNode(name+"Node");
mEnt = sceneMgr->createEntity(name, "Gamechar-male.mesh");
// Set looking direction for this model
setRelativeLookingDirection( -Ogre::Vector3::UNIT_Z );
// Assign random texture
int i = (int)Ogre::Math::RangeRandom(0,14);
if (i > 13)
i = 13;
mEnt->setMaterialName("GameChar_Male_Mat_"+Ogre::StringConverter::toString(i));
mEnt->setQueryFlags(DEFAULT_MASK); // Exclude from ray queries
mNode->attachObject(mEnt);
mNode->setPosition(position);
// Assign animation
mAnimState= mEnt->getAnimationState("Walk");
mAnimState->setEnabled(true);
mAnimState->setLoop(true);
Ogre::Vector3 bBoxSize = mEnt->getBoundingBox().getSize();
Ogre::Real agentRadius = mDetourCrowd->getAgentRadius();
Ogre::Real agentHeight = mDetourCrowd->getAgentHeight();
// Set Height to match that of agent
//mNode->setScale((agentRadius*2)/bBoxSize.y, agentHeight/bBoxSize.y, (agentRadius*2)/bBoxSize.y);
Ogre::Real scale = agentHeight/bBoxSize.y;
mNode->setScale(scale, scale, scale);
// Set animation speed scaling
mAnimSpeedScale = 0.35*(scale*4);
// Debug draw agent
mDebugNode = mNode->createChildSceneNode(name+"AgentDebugNode");
mDebugNode->setPosition(0, mDetourCrowd->m_recast->getNavmeshOffsetFromGround(), 0);
Ogre::Entity* debugEnt = sceneMgr->createEntity(name+"AgentDebug", "Cylinder.mesh");
debugEnt->setMaterialName("Cylinder/Wires/LightBlue");
mDebugNode->attachObject(debugEnt);
// Set marker scale to size of agent
mDebugNode->setInheritScale(false);
mDebugNode->setScale(agentRadius*2, agentHeight, agentRadius*2);
debugEnt->setQueryFlags(DEFAULT_MASK); // Exclude from ray queries
mDebugNode->setVisible(mDebugDraw);
}
void AnimateableCharacter::update(Ogre::Real timeSinceLastFrame)
{
updatePosition(timeSinceLastFrame);
if (mClipTo)
clipToTerrainHeight();
Ogre::Vector3 velocity = getVelocity(); // Current velocity of agent
Ogre::Real speed = velocity.length();
if(speed > 0.15) {
mAnimState->setEnabled(true);
mAnimState->addTime(mAnimSpeedScale * speed * timeSinceLastFrame);
if(speed > 0/*0.8*/) { // Avoid jitter (TODO keep this?)
// Rotate to look in walking direction
Ogre::Vector3 src = getLookingDirection();
src.y = 0; // Ignore y direction
velocity.y = 0;
velocity.normalise();
mNode->rotate(src.getRotationTo(velocity));
}
} else { // Assume character has stopped
mAnimState->setEnabled(false);
mAnimState->setTimePosition(0);
}
}
Ogre::Entity* AnimateableCharacter::getEntity()
{
return mEnt;
}
void AnimateableCharacter::setDebugVisibility(bool visible)
{
mDebugDraw = visible;
mDebugNode->setVisible(mDebugDraw);
}
bool AnimateableCharacter::getDebugVisibility()
{
return mDebugDraw;
}
void AnimateableCharacter::show()
{
Character::show();
mDebugNode->setVisible(getDebugVisibility());
}
void AnimateableCharacter::randomizeAnimationPosition()
{
mAnimState->setTimePosition( Ogre::Math::RangeRandom(0, mAnimState->getLength()) );
}

392
src/crowd/src/Character.cpp Normal file
View File

@@ -0,0 +1,392 @@
/*
OgreCrowd
---------
Copyright (c) 2012 Jonas Hauquier
Additional contributions by:
- mkultra333
- Paul Wilson
Sincere thanks and to:
- Mikko Mononen (developer of Recast navigation libraries)
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.
*/
#include "Character.h"
// Remember: this value should be squared and should be strictly >0 !
const Ogre::Real Character::DESTINATION_RADIUS = 1 * 1;
// TODO it's also possible to calculate this relative to the agent radius
Character::Character(Ogre::String name, Ogre::SceneManager *sceneMgr, OgreDetourCrowd* detourCrowd, Ogre::Vector3 position)
: mName(name),
mSceneMgr(sceneMgr),
mDetourCrowd(detourCrowd),
mNode(NULL),
mAgent(NULL),
mAgentID(-1),
mManualVelocity(Ogre::Vector3::ZERO),
mDestination(detourCrowd->getLastDestination()),
mStopped(false),
mAgentControlled(true),
mDetourTileCache(NULL),
mTempObstacle(0),
mClipTo(0),
mRaySceneQuery(0),
mLookingDirection(Ogre::Vector3::UNIT_X)
{
// TODO maybe create mNode in this consructor instead of in subclasses
load(position);
}
Character::~Character()
{
unLoad();
}
int Character::getAgentID()
{
return mAgentID;
}
const dtCrowdAgent* Character::getAgent()
{
return mAgent;
}
Ogre::SceneNode* Character::getNode(void) const
{
return mNode;
}
Ogre::Vector3 Character::getDestination() const
{
if (mAgentControlled && isLoaded())
return mDestination;
return Ogre::Vector3::ZERO; // TODO this is not ideal
}
void Character::setPosition(Ogre::Vector3 position)
{
if(!mAgentControlled || !isLoaded()) {
getNode()->setPosition(position);
return;
}
// Find position on navmesh
if (!mDetourCrowd->m_recast->findNearestPointOnNavmesh(position, position))
return;
// Remove agent from crowd and re-add at position
mDetourCrowd->removeAgent(mAgentID);
mAgentID = mDetourCrowd->addAgent(position);
mAgent = mDetourCrowd->getAgent(mAgentID);
if(getNode()) // TODO remove this check by initing mNode in this class' constructor
getNode()->setPosition(position);
}
void Character::updateDestination(Ogre::Vector3 destination, bool updatePreviousPath)
{
if(!mAgentControlled || !isLoaded())
return;
// Find position on navmesh
if(!mDetourCrowd->m_recast->findNearestPointOnNavmesh(destination, destination))
return;
mDetourCrowd->setMoveTarget(mAgentID, destination, updatePreviousPath);
mDestination = destination;
mStopped = false;
mManualVelocity = Ogre::Vector3::ZERO;
}
Ogre::Vector3 Character::getPosition() const
{
return getNode()->getPosition();
}
void Character::updatePosition(Ogre::Real timeSinceLastFrame)
{
if(!isLoaded())
return;
if (mAgentControlled) {
if(getAgent()->active) {
Ogre::Vector3 agentPos;
OgreRecast::FloatAToOgreVect3(getAgent()->npos, agentPos);
getNode()->setPosition(agentPos);
}
} else {
// Move character manually to new position
if(getVelocity().isZeroLength())
return;
// Make other agents avoid first character by placing a temporary obstacle in its position
mDetourTileCache->removeTempObstacle(mTempObstacle); // Remove old obstacle
getNode()->setPosition(getPosition() + timeSinceLastFrame * getVelocity());
// TODO check whether this position is within navmesh
mTempObstacle = mDetourTileCache->addTempObstacle(getPosition()); // Add new obstacle
}
// Clip position to terrain height
if (mClipTo)
clipToTerrainHeight();
}
void Character::clipToTerrain(Ogre::TerrainGroup *terrainGroup)
{
mClipTo = terrainGroup;
}
void Character::clipToTerrainHeight()
{
// Setup the scene query
Ogre::Ray queryRay(getNode()->getPosition(), Ogre::Vector3::NEGATIVE_UNIT_Y);
// Perform the scene query
Ogre::TerrainGroup::RayResult result = mClipTo->rayIntersects(queryRay);
if(result.hit) {
Ogre::Real terrainHeight = result.position.y;
Ogre::Vector3 pos = getNode()->getPosition();
pos.y = terrainHeight;
getNode()->setPosition(pos);
} else {
// Try querying terrain above character
queryRay.setOrigin(getNode()->getPosition());
queryRay.setDirection(Ogre::Vector3::UNIT_Y);
// Perform scene query again
result = mClipTo->rayIntersects(queryRay);
if(result.hit) {
Ogre::Real terrainHeight = result.position.y;
Ogre::Vector3 pos = getNode()->getPosition();
pos.y = terrainHeight;
getNode()->setPosition(pos);
}
}
}
bool Character::destinationReached()
{
if(!isLoaded())
return false;
if(getPosition().squaredDistance(getDestination()) <= Character::DESTINATION_RADIUS)
return true;
return mDetourCrowd->destinationReached(getAgent(), Character::DESTINATION_RADIUS);
}
void Character::setDestination(Ogre::Vector3 destination)
{
if (!mAgentControlled || !isLoaded())
return;
mDestination = destination;
mManualVelocity = Ogre::Vector3::ZERO;
mStopped = false;
}
void Character::stop()
{
if(!mAgentControlled || !isLoaded()) {
mManualVelocity = Ogre::Vector3::ZERO;
mStopped = true;
return;
}
if(mDetourCrowd->stopAgent(getAgentID())) {
mDestination = Ogre::Vector3::ZERO; // TODO this is not ideal
mManualVelocity = Ogre::Vector3::ZERO;
mStopped = true;
}
}
Ogre::Vector3 Character::getLookingDirection()
{
return mNode->getOrientation() * getRelativeLookingDirection();
}
void Character::moveForward()
{
Ogre::Vector3 lookDirection = getLookingDirection();
lookDirection.normalise();
setVelocity(getMaxSpeed() * lookDirection);
}
void Character::setVelocity(Ogre::Vector3 velocity)
{
mManualVelocity = velocity;
mStopped = false;
mDestination = Ogre::Vector3::ZERO; // TODO this is not ideal
if(mAgentControlled && isLoaded())
mDetourCrowd->requestVelocity(getAgentID(), mManualVelocity);
}
Ogre::Vector3 Character::getVelocity()
{
if(!isLoaded())
return Ogre::Vector3::ZERO;
if(mAgentControlled) {
Ogre::Vector3 velocity;
OgreRecast::FloatAToOgreVect3(getAgent()->nvel, velocity);
return velocity;
} else {
return mManualVelocity;
}
}
Ogre::Real Character::getSpeed()
{
return getVelocity().length();
}
Ogre::Real Character::getMaxSpeed()
{
if(isLoaded())
return getAgent()->params.maxSpeed;
else
return 0.0f;
}
Ogre::Real Character::getMaxAcceleration()
{
if(isLoaded())
return getAgent()->params.maxAcceleration;
else
return 0.0f;
}
bool Character::isMoving()
{
return !mStopped || getSpeed() != 0;
}
Ogre::Real Character::getAgentHeight(void) const
{
return mDetourCrowd->getAgentHeight();
}
Ogre::Real Character::getAgentRadius(void) const
{
return mDetourCrowd->getAgentRadius();
}
void Character::setAgentControlled(bool agentControlled)
{
if (mAgentControlled != agentControlled) {
if (agentControlled) {
if(mTempObstacle)
mDetourTileCache->removeTempObstacle(mTempObstacle);
mTempObstacle = 0;
mAgentID = mDetourCrowd->addAgent(getPosition());
mDestination = mDetourCrowd->getLastDestination();
mManualVelocity = Ogre::Vector3::ZERO;
mStopped = true;
} else {
mTempObstacle = mDetourTileCache->addTempObstacle(getPosition()); // Add temp obstacle at current position
mDetourCrowd->removeAgent(mAgentID);
mAgentID = -1;
mDestination = Ogre::Vector3::ZERO; // TODO this is not ideal
mStopped = false;
}
mAgentControlled = agentControlled;
}
}
bool Character::isAgentControlled()
{
return mAgentControlled;
}
void Character::setDetourTileCache(OgreDetourTileCache *dtTileCache)
{
mDetourTileCache = dtTileCache;
}
void Character::load()
{
if(isLoaded())
return; // nothing to do
load(getPosition());
}
void Character::load(Ogre::Vector3 position)
{
if(isLoaded()) {
setPosition(position);
} else {
mAgentID = mDetourCrowd->addAgent(position);
mAgent = mDetourCrowd->getAgent(mAgentID);
}
setPosition(position);
show();
}
void Character::unLoad()
{
mDetourCrowd->removeAgent(getAgentID());
mAgentID = -1;
mAgent = NULL;
hide();
}
void Character::show()
{
if(getNode()) {
getNode()->setVisible(true);
}
}
void Character::hide()
{
if(getNode())
getNode()->setVisible(false);
}
Ogre::Vector3 Character::getRelativeLookingDirection()
{
return mLookingDirection;
}
void Character::setRelativeLookingDirection(Ogre::Vector3 direction)
{
mLookingDirection = direction;
}

View File

@@ -0,0 +1,201 @@
/*
OgreCrowd
---------
Copyright (c) 2012 Jonas Hauquier
Additional contributions by:
- mkultra333
- Paul Wilson
Sincere thanks and to:
- Mikko Mononen (developer of Recast navigation libraries)
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.
*/
#include "ConvexShapeObstacle.h"
ConvexShapeObstacle::ConvexShapeObstacle(Ogre::Vector3 position, Ogre::Real offset, OgreDetourTileCache *detourTileCache)
: Obstacle(detourTileCache),
mPosition(position),
mEnt(0),
mNode(0),
mConvexHullDebug(0),
mInputGeom(0),
mOffset(offset)
{
// Randomly place a box or a pot as obstacle
mNode = mSceneMgr->getRootSceneNode()->createChildSceneNode();
if (Ogre::Math::RangeRandom(0,2) < 1.5) {
mEnt = mSceneMgr->createEntity("Box.mesh");
} else {
// For more complex entities, convex hull building will consider only MAX_CONVEXVOL_PTS vertices and the result can be sub-optimal.
// A more robust convex hull building algorithm might be preferred.
mEnt = mSceneMgr->createEntity("Pot.mesh");
mNode->setScale(0.3, 0.3, 0.3);
}
mNode->attachObject(mEnt);
mNode->setPosition(mPosition);
mOrientation = mNode->getOrientation();
mEnt->setQueryFlags(OBSTACLE_MASK); // add to query group for obstacles
// Transfer entitiy geometry to recast compatible format
if(mEnt->getMesh()->getName() == "Pot.mesh") {
// Create a convex hull from the mesh geometry
// Note that it is important to first add your entity to the scene before creating an inputGeom from it.
// This is so that it can calculate the world space coordinates for the object, which are needed for recast.
mInputGeom = new InputGeom(mEnt);
// Create convex obstacle in the detourTileCache
// Create convex hull with agent radios offset around the object (this is important so agents don't walk through the edges of the obstacle!)
mConvexHull = mInputGeom->getConvexHull(mOffset);
} else {
// Create a convex hull simply from the bounding box
// The bounding box has to be transformed into world-space coordinates
mConvexHull = new ConvexVolume(InputGeom::getWorldSpaceBoundingBox(mEnt), offset);
}
// WARNING: Watch out for memory leaks here! ConvexVolume objects are not managed by any system (except this class).
mConvexHull->area = RC_NULL_AREA; // Set area described by convex polygon to "unwalkable"
// Add convex hull to detourTileCache as obstacle
mDetourTileCache->addConvexShapeObstacle(mConvexHull);
// Debug draw convex hull
// TODO add debug flag, grey lines around boxes should disappear when disabling debug drawing
mConvexHullDebug = InputGeom::drawConvexVolume(mConvexHull, mSceneMgr); // Debug convex volume
//if(mObstacleId == -1)
// TODO exception when something goes wrong!
//mEnt->setVisible(false); // TODO maybe make boxes semi-transparent in debug draw mode?
}
ConvexShapeObstacle::~ConvexShapeObstacle()
{
// Remove obstacle from DetourTileCache
mDetourTileCache->removeConvexShapeObstacle(mConvexHull);
mNode->removeAllChildren();
mNode->getParentSceneNode()->removeChild(mNode);
mSceneMgr->destroyEntity(mEnt);
mSceneMgr->destroySceneNode(mNode);
mNode = NULL;
mEnt = NULL;
mConvexHullDebug->detachFromParent();
mSceneMgr->destroyManualObject(mConvexHullDebug);
delete mInputGeom;
mConvexHullDebug = NULL;
}
void ConvexShapeObstacle::update(long time)
{
}
Ogre::Entity* ConvexShapeObstacle::getEntity()
{
return mEnt;
}
void ConvexShapeObstacle::updatePosition(Ogre::Vector3 position)
{
// Modify position if larger than epsilon
if ( mPosition.squaredDistance(position) > SQUARED_DISTANCE_EPSILON ) {
// Remove obstacle
mDetourTileCache->removeConvexShapeObstacle(mConvexHull);
// Transform hull to new location
mConvexHull->move(position-mPosition);
// Re-add hull as obstacle at new location
mDetourTileCache->addConvexShapeObstacle(mConvexHull);
mPosition = position;
// Now also set the position to the visual obstacle entity
mNode->setPosition(position);
// Create new debug drawing of the convex hull
mConvexHullDebug->detachFromParent();
mSceneMgr->destroyManualObject(mConvexHullDebug);
mConvexHullDebug = InputGeom::drawConvexVolume(mConvexHull, mSceneMgr);
}
}
Ogre::Vector3 ConvexShapeObstacle::getPosition()
{
return mPosition;
}
void ConvexShapeObstacle::updateOrientation(Ogre::Quaternion orientation)
{
// Modify orientation if difference larger than epsilon
if(! mOrientation.equals(orientation, Ogre::Degree(ORIENTATION_TOLERANCE_DEGREES))) {
// Remove old obstacle from tilecache
mDetourTileCache->removeConvexShapeObstacle(mConvexHull);
// In case we didn't generate inputgeom yet (we previously generated hull directly from the bounding box), do it now
if(!mInputGeom)
mInputGeom = new InputGeom(mEnt);
// Apply rotation to the inputGeometry and calculate a new 2D convex hull
Ogre::Quaternion relativeOrientation = orientation * mOrientation.Inverse(); // Calculate relative rotation from current rotation to the specified one
orientation.normalise(); // Make sure quaternion is normalized
mInputGeom->applyOrientation(relativeOrientation, mPosition); // Rotate around obstacle position (center or origin point)
if (mConvexHull)
delete mConvexHull;
mConvexHull = mInputGeom->getConvexHull(mOffset);
mConvexHull->area = RC_NULL_AREA; // Be sure to set the proper area for the convex shape!
// Add new hull as obstacle to tilecache
mDetourTileCache->addConvexShapeObstacle(mConvexHull);
mOrientation = orientation;
// Now also set the rotation to the visual obstacle entity
mNode->setOrientation(orientation);
// Create new debug drawing of the convex hull
mConvexHullDebug->detachFromParent();
mSceneMgr->destroyManualObject(mConvexHullDebug);
mConvexHullDebug = InputGeom::drawConvexVolume(mConvexHull, mSceneMgr);
}
}
Ogre::Quaternion ConvexShapeObstacle::getOrientation()
{
return mOrientation;
}

View File

@@ -0,0 +1,562 @@
/*
OgreCrowd
---------
Copyright (c) 2012 Jonas Hauquier
Additional contributions by:
- mkultra333
- Paul Wilson
Sincere thanks and to:
- Mikko Mononen (developer of Recast navigation libraries)
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.
*/
#include "CrowdManager.h"
#include "OgreDetourCrowd.h"
#include "Character.h"
#include "AnimateableCharacter.h"
#include "TestCharacter.h"
#include "InstancedCharacter.h"
const Ogre::Real CrowdManager::CROWD_PAGE_UPDATE_DELTA = 1;
bool CrowdManager::HUMAN_CHARACTERS = true;
bool CrowdManager::INSTANCED_CROWD = false;
// TODO this can also be DetourCrowd::MAX_AGENTS or allow setting of Max agents in detourCrowd
const Ogre::Real CrowdManager::MAX_CROWD_SIZE = 100; // TODO make this a cfg parameter
const Ogre::Real CrowdManager::RADIUS_EPSILON = 1;
CrowdManager::CrowdManager(OgreDetourTileCache *tileCache, Ogre::SceneManager *sceneManager, Ogre::Camera *camera)
: mDetourTileCache(tileCache)
, mRecast(mDetourTileCache->getRecast())
, mSceneMgr(sceneManager)
, mCamera(camera)
, mDetourCrowd(0)
, mCharacters()
, mCurrentlyPagedArea()
, mPagedAreaDistance(2)
, mNbPagedTiles(0)
, mNbTilesInBorder(0)
, mAreaDebug(0)
, mBorderTiles()
, mInstanceManager(0)
{
// Number of tiles filled with agents
if(mPagedAreaDistance == 0) {
mNbPagedTiles = 1;
mNbTilesInBorder = 1;
mDimension = 1;
} else {
// Dimensions of the square grid (dimension x dimension)
mDimension = (2*(mPagedAreaDistance+1))-1;
mNbPagedTiles = mDimension*mDimension;
// Number of tiles in the outer border
mNbTilesInBorder = (2*mDimension) + 2*(mDimension-2);
}
if(MAX_CROWD_SIZE < OgreDetourCrowd::MAX_AGENTS)
mCrowdSize = MAX_CROWD_SIZE;
else
mCrowdSize = OgreDetourCrowd::MAX_AGENTS;
// TODO make sure crowdSize is a multiple of nbPagedTiles?
mCharacters.reserve(mCrowdSize);
mUnassignedCharacters.reserve(mCrowdSize);
mAssignedCharacters.reserve(mCrowdSize);
mBorderTiles.reserve(mNbTilesInBorder);
// DetourCrowd component managed by this CrowdManager
mDetourCrowd = new OgreDetourCrowd(mRecast); // TODO add option of specifying max crowd size?
if(INSTANCED_CROWD) {
// Most compatible SM2+ technique
Ogre::InstanceManager::InstancingTechnique instanceTechnique = Ogre::InstanceManager::ShaderBased;
// Create instance manager for managing instances of the robot mesh
mInstanceManager = mSceneMgr->createInstanceManager(
"CrowdCharacter_1_InstanceMgr", "Gamechar-male.mesh",
Ogre::ResourceGroupManager::AUTODETECT_RESOURCE_GROUP_NAME, instanceTechnique,
mCrowdSize); // TODO experiment with batch size
}
initAgents();
}
void CrowdManager::initAgents()
{
// Make sure camera is already set!
mCurrentlyPagedArea = calculatePopulatedArea();
updateBorderTiles(); // Make sure agents that walk out of the area are placed in the right border tiles
updatePagedAreaDebug(mCurrentlyPagedArea);
// Initialize and place agents: distribute uniformly
int agentsPerTile = mCrowdSize/mNbPagedTiles;
int nbAgents = -1;
for (int x = mCurrentlyPagedArea.xMin; x <= mCurrentlyPagedArea.xMax; x++) {
for (int y = mCurrentlyPagedArea.yMin; y <= mCurrentlyPagedArea.yMax; y++) {
if(tileExists(x,y)) {
debugPrint("Init: load "+Ogre::StringConverter::toString(agentsPerTile)+" agents on tile "+tileToStr(x,y)+".");
for(int i = 0; i < agentsPerTile; i++) {
nbAgents++;
//Ogre::Vector3 position = getRandomPositionInNavmeshTileSet(NavmeshTileSet);
Ogre::Vector3 position = getRandomPositionInNavmeshTile(x, y);
Character *character;
// TODO make configurable which type of character is exactly instanced (maybe allow keeping sets of different populations)
if(INSTANCED_CROWD) {
character = new InstancedCharacter("Character_"+Ogre::StringConverter::toString(nbAgents), mSceneMgr, mDetourCrowd, mInstanceManager, false, position);
} else if(HUMAN_CHARACTERS) {
character = new AnimateableCharacter("Character_"+Ogre::StringConverter::toString(nbAgents), mSceneMgr, mDetourCrowd, false, position);
} else {
character = new TestCharacter("Character_"+Ogre::StringConverter::toString(nbAgents), mSceneMgr, mDetourCrowd, position);
}
mCharacters.push_back(character);
mAssignedCharacters.push_back(character);
assignAgentDestination(character);
}
} else {
debugPrint("Init: Tile "+tileToStr(x,y)+" does not exist, 0 agents loaded.");
}
}
}
// Create any remaining unassigned characters
nbAgents++;
while(nbAgents < mCrowdSize) {
Character *character;
if(INSTANCED_CROWD) {
character = new InstancedCharacter("Character_"+Ogre::StringConverter::toString(nbAgents), mSceneMgr, mDetourCrowd, mInstanceManager);
} else if(HUMAN_CHARACTERS) {
character = new AnimateableCharacter("Character_"+Ogre::StringConverter::toString(nbAgents), mSceneMgr, mDetourCrowd);
} else {
character = new TestCharacter("Character_"+Ogre::StringConverter::toString(nbAgents), mSceneMgr, mDetourCrowd);
}
character->unLoad();
mCharacters.push_back(character);
mUnassignedCharacters.push_back(character);
nbAgents++;
}
}
Ogre::Vector3 CrowdManager::assignAgentDestination(Character* character)
{
// Assign a random destination (wandering)
Ogre::Vector3 rndPt = mRecast->getRandomNavMeshPoint();
character->updateDestination(rndPt);
return rndPt;
}
void CrowdManager::setDebugVisibility(bool visible)
{
mDebugDraw = visible;
if(mAreaDebug)
mAreaDebug->setVisible(mDebugDraw);
for(std::vector<Character*>::iterator iter = mCharacters.begin(); iter != mCharacters.end(); iter++) {
Character *character = *iter;
character->setDebugVisibility(visible);
}
}
void CrowdManager::update(Ogre::Real timeSinceLastFrame)
{
// Update crowd agents
mDetourCrowd->updateTick(timeSinceLastFrame);
// Then update all characters controlled by the agents
for(std::vector<Character*>::iterator iter=mCharacters.begin(); iter != mCharacters.end(); iter++) {
Character *character = *iter;
// Update character (position, animations, state)
character->update(timeSinceLastFrame);
// Set new destinations when agents reach their current destination (random wander)
if ( character->destinationReached() ) {
character->updateDestination( mRecast->getRandomNavMeshPoint() );
}
}
// Update paged crowd
updatePagedCrowd(timeSinceLastFrame);
}
CrowdManager::NavmeshTileSet CrowdManager::calculatePopulatedArea()
{
NavmeshTileSet result;
Ogre::Vector3 cameraPos = mCamera->getRealPosition();
Ogre::Vector2 camTilePos = mDetourTileCache->getTileAtPos(cameraPos);
result.xMin = camTilePos.x - (mPagedAreaDistance);
result.xMax = camTilePos.x + (mPagedAreaDistance);
result.yMin = camTilePos.y - (mPagedAreaDistance);
result.yMax = camTilePos.y + (mPagedAreaDistance);
return result;
}
bool CrowdManager::updatePagedCrowd(Ogre::Real timeSinceLastFrame)
{
// Update paged area when camera has moved, also re-add agents that walked out of the grid
mTimeSinceLastUpdate += timeSinceLastFrame;
bool update = false;
NavmeshTileSet newPagedArea = calculatePopulatedArea();
// DETECT wheter the grid has changed (ie. whether tiles should be unloaded and others loaded)
if(newPagedArea.xMin != mCurrentlyPagedArea.xMin || newPagedArea.yMin != mCurrentlyPagedArea.yMin) {
// Paged area has changed
if((int)Ogre::Math::Abs(newPagedArea.xMin - mCurrentlyPagedArea.xMin) < 2
&&
(int)Ogre::Math::Abs(newPagedArea.yMin - mCurrentlyPagedArea.yMin) < 2) {
// Paged area has slid by one tile, only perform one update per time delta
if(mTimeSinceLastUpdate > CROWD_PAGE_UPDATE_DELTA)
update = true;
else
return false; // Don't update now, also don't re-add agents that walked out of the grid (this could be useless as the grid will soon change)
} else {
// Paged area has moved somewhere else, update immediately
update = true;
}
}
if(!update) {
// Unload agents that walked of the grid, add them somewhere in some random border tile
for(std::vector<Character*>::iterator iter = mAssignedCharacters.begin(); iter != mAssignedCharacters.end(); iter++) {
Character *character = *iter;
if(walkedOffGrid(character)) {
Ogre::Vector3 oldPos = character->getPosition();
// Place agent in new random border tile
placeAgentOnRandomBorderTile(character);
debugPrint("Agent "+Ogre::StringConverter::toString(character->getAgentID())+" walked off grid ("+Ogre::StringConverter::toString(oldPos)+" "+tileToStr(mDetourTileCache->getTileAtPos(oldPos))+") placed on new random tile "+tileToStr(mDetourTileCache->getTileAtPos(character->getPosition()))+" "+Ogre::StringConverter::toString(character->getPosition()));
}
}
}
// Remove all agents outside of the new paged area
unloadAgentsOutsideArea(newPagedArea);
// Grid has changed: unload tiles, load others
if(update) {
updatePagedAreaDebug(newPagedArea);
// Add agents to newly loaded tiles
for (int x = newPagedArea.xMin; x <= newPagedArea.xMax; x++) {
if(x < mCurrentlyPagedArea.xMin) {
for(int y = newPagedArea.yMin; y <= newPagedArea.yMax; y++)
loadAgents(x,y, mCrowdSize/mNbPagedTiles);
} else if(x > mCurrentlyPagedArea.xMax) {
for(int y = newPagedArea.yMin; y <= newPagedArea.yMax; y++)
loadAgents(x,y, mCrowdSize/mNbPagedTiles);
} else /*if(x >= mCurrentlyPagedArea.xMin && x <= mCurrentlyPagedArea.xMax)*/ {
for (int y = newPagedArea.yMin; y <= newPagedArea.yMax; y++) {
if(y < mCurrentlyPagedArea.yMin) {
loadAgents(x,y, mCrowdSize/mNbPagedTiles);
} else if(y > mCurrentlyPagedArea.yMax) {
loadAgents(x,y, mCrowdSize/mNbPagedTiles);
}// else x,y were also in current paged area
}
}
}
mTimeSinceLastUpdate = 0;
mCurrentlyPagedArea = newPagedArea;
// Update list with existing border tiles
updateBorderTiles();
}
return update;
}
void CrowdManager::updateBorderTiles()
{
// TODO is it a good idea to use existing area, or really only use outer borders of area? the disadvantage of this approach is that agents could appear closer to the camera, the advantage that new agents can always appear, even if there is only one tile available in the current grid
NavmeshTileSet existingArea = getExistingArea(mCurrentlyPagedArea);
mBorderTiles.clear();
for(int y = existingArea.yMin; y <= existingArea.yMax; y++) {
if(tileExists(existingArea.xMin, y))
mBorderTiles.push_back(Ogre::Vector2(existingArea.xMin, y));
if(tileExists(existingArea.xMax, y))
mBorderTiles.push_back(Ogre::Vector2(existingArea.xMax, y));
}
}
CrowdManager::NavmeshTileSet CrowdManager::getExistingArea(NavmeshTileSet area)
{
TileSelection bounds = mDetourTileCache->getBounds();
if(area.xMin < bounds.minTx)
area.xMin = bounds.minTx;
if(area.yMin < bounds.minTy)
area.yMin = bounds.minTy;
if(area.xMax > bounds.maxTx)
area.xMax = bounds.maxTx;
if(area.yMax > bounds.maxTy)
area.yMax = bounds.maxTy;
return area; // Note: there can still be non-existing tiles in-between tiles. Use tileExists() to test.
}
int CrowdManager::getNbLoadedTiles()
{
int count = 0;
for (int x = mCurrentlyPagedArea.xMin; x <= mCurrentlyPagedArea.xMax; x++) {
for (int y = mCurrentlyPagedArea.yMin; y <= mCurrentlyPagedArea.yMax; y++) {
if(tileExists(x,y))
count++;
}
}
return count;
}
int CrowdManager::getNbBorderTiles()
{
return mBorderTiles.size();
}
void CrowdManager::placeAgentOnRandomBorderTile(Character *character)
{
if(mBorderTiles.size() == 0 )
return;
int borderTile = (int)Ogre::Math::RangeRandom(0, mBorderTiles.size());
Ogre::Vector2 tile = mBorderTiles[borderTile];
placeAgent(character, tile.x,tile.y);
}
bool CrowdManager::tileExists(int tx, int ty)
{
return mDetourTileCache->tileExists(tx, ty);
}
bool CrowdManager::walkedOffGrid(const Character *character)
{
// TODO detect whether agent is at border tile, at the outer edge, and is pointing towards walking off (or has no velocity anymore, but beware of recently added agents that were not yet assigned a velocity)
// TODO also think about how to assign waypoint destinations, could be a problem if they are to locations of which no navmesh is loaded. Then agents will navigate to the closest point on the navmesh and stop, this is not necessarily the right path. Maybe a higher-level checkpoint graph.
// Detect whether an agent has moved to a tile outside of the current area
// NOTE: for this to work, the navmesh tiles that extend at least one tile outside of the currently populated area
// have to be loaded in the tilecache.
Ogre::Vector2 tpos = mDetourTileCache->getTileAtPos(character->getPosition());
if(tpos.x < mCurrentlyPagedArea.xMin || tpos.x > mCurrentlyPagedArea.xMax)
return true;
if(tpos.y < mCurrentlyPagedArea.yMin || tpos.y > mCurrentlyPagedArea.yMax)
return true;
return false;
}
void CrowdManager::unloadAgents(int tx, int ty)
{
if(! tileExists(tx,ty))
return;
u_int i = 0;
int agentsRemoved = 0;
while(i < mAssignedCharacters.size()) {
Character *character = mAssignedCharacters[i];
Ogre::Vector2 tilePos = mDetourTileCache->getTileAtPos(character->getPosition());
if(tilePos.x == tx && tilePos.y == ty) { //TODO Is this safe? tile positions are ints, but they were stored in a float
agentsRemoved++;
character->unLoad();
mUnassignedCharacters.push_back(character);
mAssignedCharacters.erase(mAssignedCharacters.begin()+i);
// Don't advance i, current position contains the next element
} else {
i++;
}
}
debugPrint("Unloaded "+Ogre::StringConverter::toString(agentsRemoved)+" agents from tile "+tileToStr(tx,ty)+".");
}
void CrowdManager::unloadAgentsOutsideArea(NavmeshTileSet tileSet)
{
u_int i = 0;
int agentsRemoved = 0;
while(i < mAssignedCharacters.size()) {
Character *character = mAssignedCharacters[i];
Ogre::Vector2 tilePos = mDetourTileCache->getTileAtPos(character->getPosition());
if( tilePos.x < tileSet.xMin || tilePos.x > tileSet.xMax
|| tilePos.y < tileSet.yMin || tilePos.y > tileSet.yMax) {
agentsRemoved++;
character->unLoad();
mUnassignedCharacters.push_back(character);
mAssignedCharacters.erase(mAssignedCharacters.begin()+i);
// Don't advance i, current position contains the next element
} else {
i++;
}
}
if(agentsRemoved)
debugPrint("Unloaded "+Ogre::StringConverter::toString(agentsRemoved)+" agents.");
}
void CrowdManager::debugPrint(Ogre::String message)
{
if(mDebugDraw)
Ogre::LogManager::getSingletonPtr()->logMessage(message);
}
Ogre::String CrowdManager::tileToStr(Ogre::Vector2 tilePos)
{
return tileToStr((int)tilePos.x, (int)tilePos.y);
}
Ogre::String CrowdManager::tileToStr(int tx, int ty)
{
return "("+Ogre::StringConverter::toString(tx)+", "+Ogre::StringConverter::toString(ty)+")";
}
void CrowdManager::loadAgents(int tx, int ty, int nbAgents)
{
if(! tileExists(tx,ty)) {
debugPrint("Will not load agents on tile "+tileToStr(tx,ty)+": does not exist.");
return;
}
// Iterate over free agent list and distribute evenly
// TODO allow other distributions
int agentsPlaced = 0;
while(mUnassignedCharacters.size() != 0 && agentsPlaced < nbAgents) {
Character *character = mUnassignedCharacters[mUnassignedCharacters.size()-1];
mUnassignedCharacters.pop_back();
Ogre::Vector3 pos = placeAgent(character, tx, ty);
mAssignedCharacters.push_back(character);
agentsPlaced++;
}
debugPrint("Loaded "+Ogre::StringConverter::toString(agentsPlaced)+" agents on tile "+tileToStr(tx,ty)+"." /*+agentsString*/);
}
Ogre::Vector3 CrowdManager::getRandomPositionInNavmeshTile(int tx, int ty)
{
Ogre::AxisAlignedBox tileBounds = mDetourTileCache->getTileBounds(tx, ty);
Ogre::Vector3 center = tileBounds.getCenter(); // Center of the specified tile
//center.y = tileBounds.getMinimum().y; // Place on the ground
// TODO centering probably has the biggest change of the point clipping to the navmesh
// Get random point in tile (in circle in the middle of the tile with radius of tilesize/2)
Ogre::Real radius = mDetourTileCache->getTileSize()/2;
return mRecast->getRandomNavMeshPointInCircle(center, radius-RADIUS_EPSILON); // TODO I could also make RADIUS_EPSILON be a fraction of the tileSize
}
Ogre::Vector3 CrowdManager::placeAgent(Character* character, int tx, int ty)
{
Ogre::Vector3 rndPos = getRandomPositionInNavmeshTile(tx, ty);
character->load(rndPos);
// Start walking animation at random position to avoid obvious synchronized movement
if(INSTANCED_CROWD)
((InstancedCharacter*) character)->randomizeAnimationPosition();
else if(HUMAN_CHARACTERS)
((AnimateableCharacter*) character)->randomizeAnimationPosition();
// TODO this code replication is stupid. Fix up character classes with a better inheritance scheme, abstracting out demo specific and reusable classes
assignAgentDestination(character);
return rndPos;
}
Ogre::AxisAlignedBox CrowdManager::getNavmeshTileSetBounds(NavmeshTileSet tileSet)
{
// TODO if I declare NavmeshTileSet struct in OgreDetourTileCache I can move this method to the tilecache
Ogre::AxisAlignedBox tileMinBounds = mDetourTileCache->getTileBounds(tileSet.xMin, tileSet.yMin);
Ogre::AxisAlignedBox tileMaxBounds = mDetourTileCache->getTileBounds(tileSet.xMax, tileSet.yMax);
Ogre::AxisAlignedBox tileSetBounds;
tileSetBounds.setMinimum(tileMinBounds.getMinimum());
tileSetBounds.setMaximum(tileMaxBounds.getMaximum());
return tileSetBounds;
}
Ogre::Vector3 CrowdManager::getRandomPositionInNavmeshTileSet(NavmeshTileSet tileSet)
{
Ogre::AxisAlignedBox tileSetBounds = getNavmeshTileSetBounds(tileSet);
Ogre::Vector3 center = tileSetBounds.getCenter();
//center.y = tileSetBounds.getMinimum().y;
// TODO centering probably has the biggest change of the point clipping to the navmesh
Ogre::Real radius = ( tileSet.getNbTiles()*mDetourTileCache->getTileSize() )/2;
return mRecast->getRandomNavMeshPointInCircle(center, radius - RADIUS_EPSILON);
}
void CrowdManager::updatePagedAreaDebug(NavmeshTileSet pagedArea)
{
/*
if(mAreaDebug) {
mAreaDebug->detachFromParent();
mSceneMgr->destroyManualObject(mAreaDebug);
}
*/
Ogre::AxisAlignedBox areaBounds = getNavmeshTileSetBounds(pagedArea);
if(! mAreaDebug) {
Ogre::SceneNode *areaSn = mSceneMgr->getRootSceneNode()->createChildSceneNode();
mAreaDebug = mSceneMgr->createEntity("AreaDemarkationDebug", "Demarkation.mesh");
areaSn->attachObject(mAreaDebug);
Ogre::Vector3 scale = areaBounds.getSize();
if(scale.y < 5)
scale.y = 10;
areaSn->setScale(scale);
mAreaDebug->setVisible(mDebugDraw);
}
if(!mDebugDraw)
return;
Ogre::Vector3 position = areaBounds.getCenter();
position.y = areaBounds.getMinimum().y;
mAreaDebug->getParentSceneNode()->setPosition(position);
}

View File

@@ -0,0 +1,121 @@
/*
OgreCrowd
---------
Copyright (c) 2012 Jonas Hauquier
Additional contributions by:
- mkultra333
- Paul Wilson
Sincere thanks and to:
- Mikko Mononen (developer of Recast navigation libraries)
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.
*/
#include "CylinderObstacle.h"
CylinderObstacle::CylinderObstacle(Ogre::Vector3 position, OgreDetourTileCache *detourTileCache)
: Obstacle(detourTileCache),
mPosition(position),
mEnt(0),
mNode(0)
{
// Create cylindrical obstacle in detourTileCache
mObstacleRef = mDetourTileCache->addTempObstacle(mPosition);
if (mObstacleRef) {
mName = "CylinderObstacle_"+Ogre::StringConverter::toString(mObstacleRef);
// Depict osbtacle as red cylinder
mNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(mName+"Node");
mEnt = mSceneMgr->createEntity(mName, "Cylinder.mesh");
mEnt->setMaterialName("Cylinder/Red");
mNode->attachObject(mEnt);
mNode->setPosition(mPosition);
mNode->setScale(TEMP_OBSTACLE_RADIUS, TEMP_OBSTACLE_HEIGHT, TEMP_OBSTACLE_RADIUS);
// mNode->setVisible(mDebugDraw);
mEnt->setQueryFlags(OBSTACLE_MASK); // add to query group for obstacles
}
// TODO have some way of notifying that obstacle creation failed? (exception maybe?)
}
CylinderObstacle::~CylinderObstacle()
{
// Remove obstacle from detour tilecache
mDetourTileCache->removeTempObstacle(mObstacleRef);
mNode->removeAllChildren();
mNode->getParentSceneNode()->removeChild(mNode);
mSceneMgr->destroyEntity(mEnt);
mSceneMgr->destroySceneNode(mNode);
mNode = NULL;
mEnt = NULL;
}
void CylinderObstacle::update(long time)
{
}
dtObstacleRef CylinderObstacle::getObstacleRef()
{
return mObstacleRef;
}
Ogre::Entity* CylinderObstacle::getEntity()
{
return mEnt;
}
void CylinderObstacle::updatePosition(Ogre::Vector3 position)
{
// Modify position if larger than epsilon
if ( mPosition.squaredDistance(position) > SQUARED_DISTANCE_EPSILON ) {
mPosition = position;
// Remove obstacle and re-add it at new location
mDetourTileCache->removeTempObstacle(mObstacleRef);
mObstacleRef = mDetourTileCache->addTempObstacle(mPosition);
}
}
Ogre::Vector3 CylinderObstacle::getPosition()
{
return mPosition;
}
void CylinderObstacle::updateOrientation(Ogre::Quaternion orientation)
{
// Do nothing
return;
}
Ogre::Quaternion CylinderObstacle::getOrientation()
{
return Ogre::Quaternion::IDENTITY;
}

View File

@@ -0,0 +1,149 @@
/*
OgreCrowd
---------
Copyright (c) 2012 Jonas Hauquier
Additional contributions by:
- mkultra333
- Paul Wilson
Sincere thanks and to:
- Mikko Mononen (developer of Recast navigation libraries)
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.
*/
#include <OgrePrerequisites.h>
#include "InstancedCharacter.h"
InstancedCharacter::InstancedCharacter(Ogre::String name, Ogre::SceneManager* sceneMgr, OgreDetourCrowd* detourCrowd, Ogre::InstanceManager* instanceMgr, bool debugDraw, Ogre::Vector3 position)
: Character(name, sceneMgr, detourCrowd, position),
mAnimState(NULL),
mAnimSpeedScale(1),
mDebugNode(NULL),
mEnt(NULL),
mDebugDraw(debugDraw),
mInstanceManager(instanceMgr)
{
mNode = sceneMgr->getRootSceneNode()->createChildSceneNode(name+"Node");
// Assign random texture
int i = (int)Ogre::Math::RangeRandom(0,14);
if (i > 13)
i = 13;
mEnt = mInstanceManager->createInstancedEntity("Examples/Instancing/ShaderBased/Male_"+Ogre::StringConverter::toString(i));
// Set looking direction for model
setRelativeLookingDirection( - Ogre::Vector3::UNIT_Z );
mEnt->setQueryFlags(DEFAULT_MASK); // Exclude from ray queries
mNode->attachObject(mEnt);
mNode->setPosition(position);
// Assign animation
mAnimState= mEnt->getAnimationState("Walk");
mAnimState->setEnabled(true);
mAnimState->setLoop(true);
Ogre::Vector3 bBoxSize = mEnt->getBoundingBox().getSize();
Ogre::Real agentRadius = mDetourCrowd->getAgentRadius();
Ogre::Real agentHeight = mDetourCrowd->getAgentHeight();
// Set Height to match that of agent
Ogre::Real scale = agentHeight/bBoxSize.y;
mNode->setScale(scale, scale, scale);
// Set animation speed scaling
mAnimSpeedScale = 0.2;
// Debug draw agent
mDebugNode = mNode->createChildSceneNode(name+"AgentDebugNode");
mDebugNode->setPosition(0, mDetourCrowd->m_recast->getNavmeshOffsetFromGround(), 0);
Ogre::Entity* debugEnt = sceneMgr->createEntity(name+"AgentDebug", "Cylinder.mesh");
debugEnt->setMaterialName("Cylinder/Wires/LightBlue");
mDebugNode->attachObject(debugEnt);
// Set marker scale to size of agent
mDebugNode->setInheritScale(false);
mDebugNode->setScale(agentRadius*2, agentHeight, agentRadius*2);
debugEnt->setQueryFlags(DEFAULT_MASK); // Exclude from ray queries
mDebugNode->setVisible(mDebugDraw);
}
void InstancedCharacter::update(Ogre::Real timeSinceLastFrame)
{
updatePosition(timeSinceLastFrame);
if (mClipTo)
clipToTerrainHeight();
Ogre::Vector3 velocity = getVelocity(); // Current velocity of agent
Ogre::Real speed = velocity.length();
if(speed > 0.15) {
mAnimState->setEnabled(true);
mAnimState->addTime(mAnimSpeedScale * speed * timeSinceLastFrame);
if(speed > 0/*0.8*/) { // Avoid jitter (TODO keep this?)
// Rotate to look in walking direction
Ogre::Vector3 src = getLookingDirection();
src.y = 0; // Ignore y direction
velocity.y = 0;
velocity.normalise();
mNode->rotate(src.getRotationTo(velocity));
// TODO average direction over multiple velocity samples
}
} else { // Assume character has stopped
mAnimState->setEnabled(false);
mAnimState->setTimePosition(0);
}
}
Ogre::InstancedEntity* InstancedCharacter::getEntity()
{
return mEnt;
}
void InstancedCharacter ::setDebugVisibility(bool visible)
{
mDebugNode->setVisible(visible);
}
bool InstancedCharacter::getDebugVisibility()
{
return mDebugDraw;
}
void InstancedCharacter::show()
{
Character::show();
mDebugNode->setVisible(getDebugVisibility());
}
void InstancedCharacter::randomizeAnimationPosition()
{
mAnimState->setTimePosition( Ogre::Math::RangeRandom(0, mAnimState->getLength()) );
}

View File

@@ -0,0 +1,55 @@
/*
OgreCrowd
---------
Copyright (c) 2012 Jonas Hauquier
Additional contributions by:
- mkultra333
- Paul Wilson
Sincere thanks and to:
- Mikko Mononen (developer of Recast navigation libraries)
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.
*/
#include "Obstacle.h"
// Minimum distance an obstacle has to be moved before the obstacle is updated
const Ogre::Real Obstacle::SQUARED_DISTANCE_EPSILON = 0.1f * 0.1f;
// Minimum difference a new orientation has to have from the previous one for obstacle orientation to be updated
const Ogre::Real Obstacle::ORIENTATION_TOLERANCE_DEGREES = 2.0f;
Obstacle::Obstacle(OgreDetourTileCache *detourTileCache)
: mDetourTileCache(detourTileCache),
mSceneMgr(detourTileCache->m_recast->m_pSceneMgr)
{
}
Obstacle::~Obstacle()
{
}

View File

@@ -0,0 +1,392 @@
/*
OgreCrowd
---------
Copyright (c) 2012 Jonas Hauquier
Additional contributions by:
- mkultra333
- Paul Wilson
Sincere thanks and to:
- Mikko Mononen (developer of Recast navigation libraries)
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.
*/
#include "Ogre.h"
#include "OgreDetourCrowd.h"
#include "DetourCommon.h"
OgreDetourCrowd::OgreDetourCrowd(OgreRecast *recast)
: m_crowd(0),
m_recast(recast),
m_targetRef(0),
m_activeAgents(0)
{
m_crowd = dtAllocCrowd();
if(!m_crowd)
Ogre::LogManager::getSingletonPtr()->logMessage("Error: Could not allocate crowd instance.");
// Set default agent parameters
m_anticipateTurns = true;
m_optimizeVis = true;
m_optimizeTopo = true;
m_obstacleAvoidance = true;
m_separation = false;
m_obstacleAvoidanceType = 3.0f;
m_separationWeight = 2.0f;
memset(m_trails, 0, sizeof(m_trails));
m_vod = dtAllocObstacleAvoidanceDebugData();
m_vod->init(2048);
memset(&m_agentDebug, 0, sizeof(m_agentDebug));
m_agentDebug.idx = -1;
m_agentDebug.vod = m_vod;
dtNavMesh* nav = recast->m_navMesh;
dtCrowd* crowd = m_crowd;
if (nav && crowd && crowd->getAgentCount() == 0)
{
crowd->init(MAX_AGENTS, m_recast->getAgentRadius(), nav);
// Make polygons with 'disabled' flag invalid.
crowd->getEditableFilter(0)->setExcludeFlags(SAMPLE_POLYFLAGS_DISABLED);
// Create different avoidance settings presets. The crowd object can store multiple, identified by an index number.
// Setup local avoidance params to different qualities.
dtObstacleAvoidanceParams params;
// Use mostly default settings, copy from dtCrowd.
memcpy(&params, crowd->getObstacleAvoidanceParams(0), sizeof(dtObstacleAvoidanceParams));
// Low (11)
params.velBias = 0.5f;
params.adaptiveDivs = 5;
params.adaptiveRings = 2;
params.adaptiveDepth = 1;
crowd->setObstacleAvoidanceParams(0, &params);
// Medium (22)
params.velBias = 0.5f;
params.adaptiveDivs = 5;
params.adaptiveRings = 2;
params.adaptiveDepth = 2;
crowd->setObstacleAvoidanceParams(1, &params);
// Good (45)
params.velBias = 0.5f;
params.adaptiveDivs = 7;
params.adaptiveRings = 2;
params.adaptiveDepth = 3;
crowd->setObstacleAvoidanceParams(2, &params);
// High (66)
params.velBias = 0.5f;
params.adaptiveDivs = 7;
params.adaptiveRings = 3;
params.adaptiveDepth = 3;
crowd->setObstacleAvoidanceParams(3, &params);
}
}
OgreDetourCrowd::~OgreDetourCrowd()
{
dtFreeCrowd(m_crowd);
dtFreeObstacleAvoidanceDebugData(m_vod);
}
Ogre::Real OgreDetourCrowd::getAgentHeight()
{
return m_recast->getAgentHeight();
}
Ogre::Real OgreDetourCrowd::getAgentRadius()
{
return m_recast->getAgentRadius();
}
void OgreDetourCrowd::updateTick(const float dt)
{
dtNavMesh* nav = m_recast->m_navMesh;
dtCrowd* crowd = m_crowd;
if (!nav || !crowd) return;
// TimeVal startTime = getPerfTime();
crowd->update(dt, &m_agentDebug);
// TimeVal endTime = getPerfTime();
// Update agent trails
for (int i = 0; i < crowd->getAgentCount(); ++i)
{
const dtCrowdAgent* ag = crowd->getAgent(i);
AgentTrail* trail = &m_trails[i];
if (!ag->active)
continue;
// Update agent movement trail.
trail->htrail = (trail->htrail + 1) % AGENT_MAX_TRAIL;
dtVcopy(&trail->trail[trail->htrail*3], ag->npos);
}
m_agentDebug.vod->normalizeSamples();
//m_crowdSampleCount.addSample((float)crowd->getVelocitySampleCount());
//m_crowdTotalTime.addSample(getPerfDeltaTimeUsec(startTime, endTime) / 1000.0f);
}
int OgreDetourCrowd::addAgent(const Ogre::Vector3 position)
{
// Define parameters for agent in crowd
dtCrowdAgentParams ap;
memset(&ap, 0, sizeof(ap));
ap.radius = getAgentRadius();
ap.height = getAgentHeight();
ap.maxAcceleration = 8.0f;
ap.maxSpeed = (ap.height/2)*1.5f;//3.5
ap.collisionQueryRange = ap.radius * 12.0f;
ap.pathOptimizationRange = ap.radius * 30.0f;
// Set update flags according to config
ap.updateFlags = 0;
if (m_anticipateTurns)
ap.updateFlags |= DT_CROWD_ANTICIPATE_TURNS;
if (m_optimizeVis)
ap.updateFlags |= DT_CROWD_OPTIMIZE_VIS;
if (m_optimizeTopo)
ap.updateFlags |= DT_CROWD_OPTIMIZE_TOPO;
if (m_obstacleAvoidance)
ap.updateFlags |= DT_CROWD_OBSTACLE_AVOIDANCE;
if (m_separation)
ap.updateFlags |= DT_CROWD_SEPARATION;
ap.obstacleAvoidanceType = (unsigned char)m_obstacleAvoidanceType;
ap.separationWeight = m_separationWeight;
float p[3];
OgreRecast::OgreVect3ToFloatA(position, p);
int idx = m_crowd->addAgent(p, &ap);
if (idx != -1)
{
// If a move target is defined: move agent towards it
// TODO do we want to set newly added agent's destination to previously set target? or remove this behaviour?
if (m_targetRef)
m_crowd->requestMoveTarget(idx, m_targetRef, m_targetPos);
// Init trail
AgentTrail* trail = &m_trails[idx];
for (int i = 0; i < AGENT_MAX_TRAIL; ++i)
dtVcopy(&trail->trail[i*3], p);
trail->htrail = 0;
}
m_activeAgents++;
return idx;
}
int OgreDetourCrowd::getNbAgents()
{
return m_activeAgents;
}
int OgreDetourCrowd::getMaxNbAgents()
{
return m_crowd->getAgentCount();
}
std::vector<dtCrowdAgent*> OgreDetourCrowd::getActiveAgents()
{
dtCrowdAgent** resultEntries = new dtCrowdAgent*[getMaxNbAgents()];
int size = m_crowd->getActiveAgents(resultEntries,getMaxNbAgents());
std::vector<dtCrowdAgent*> result(resultEntries, resultEntries + size);
delete[] resultEntries;
return result;
}
std::vector<int> OgreDetourCrowd::getActiveAgentIds(void)
{
std::vector<int> result = std::vector<int>();
const dtCrowdAgent* agent = NULL;
for(int i=0; i<getMaxNbAgents(); i++) {
agent = m_crowd->getAgent(i);
if(agent->active)
result.push_back(i);
}
return result;
}
void OgreDetourCrowd::removeAgent(const int idx)
{
m_crowd->removeAgent(idx);
m_activeAgents--;
}
const dtCrowdAgent* OgreDetourCrowd::getAgent(int id)
{
return m_crowd->getAgent(id);
}
Ogre::Vector3 OgreDetourCrowd::calcVel(Ogre::Vector3 position, Ogre::Vector3 target, Ogre::Real speed)
{
float pos[3];
OgreRecast::OgreVect3ToFloatA(position, pos);
float tgt[3];
OgreRecast::OgreVect3ToFloatA(target, tgt);
float res[3];
calcVel(res, pos, tgt, speed);
Ogre::Vector3 result;
OgreRecast::FloatAToOgreVect3(res, result);
return result;
}
void OgreDetourCrowd::calcVel(float* velocity, const float* position, const float* target, const float speed)
{
dtVsub(velocity, target, position);
velocity[1] = 0.0;
dtVnormalize(velocity);
dtVscale(velocity, velocity, speed);
}
void OgreDetourCrowd::setMoveTarget(Ogre::Vector3 position, bool adjust)
{
// Find nearest point on navmesh and set move request to that location.
dtNavMeshQuery* navquery = m_recast->m_navQuery;
dtCrowd* crowd = m_crowd;
const dtQueryFilter* filter = crowd->getFilter(0);
const float* ext = crowd->getQueryExtents();
float p[3];
OgreRecast::OgreVect3ToFloatA(position, p);
navquery->findNearestPoly(p, ext, filter, &m_targetRef, m_targetPos);
// Adjust target using tiny local search. (instead of recalculating full path)
if (adjust)
{
for (int i = 0; i < crowd->getAgentCount(); ++i)
{
const dtCrowdAgent* ag = crowd->getAgent(i);
if (!ag->active) continue;
float vel[3];
calcVel(vel, ag->npos, p, ag->params.maxSpeed);
crowd->requestMoveVelocity(i, vel);
}
}
else
{
// Move target using path finder (recalculate a full new path)
for (int i = 0; i < crowd->getAgentCount(); ++i)
{
const dtCrowdAgent* ag = crowd->getAgent(i);
if (!ag->active) continue;
crowd->requestMoveTarget(i, m_targetRef, m_targetPos);
}
}
}
void OgreDetourCrowd::setMoveTarget(int agentId, Ogre::Vector3 position, bool adjust)
{
// TODO extract common method
// Find nearest point on navmesh and set move request to that location.
dtNavMeshQuery* navquery = m_recast->m_navQuery;
dtCrowd* crowd = m_crowd;
const dtQueryFilter* filter = crowd->getFilter(0);
const float* ext = crowd->getQueryExtents();
float p[3];
OgreRecast::OgreVect3ToFloatA(position, p);
navquery->findNearestPoly(p, ext, filter, &m_targetRef, m_targetPos);
// ----
if (adjust) {
const dtCrowdAgent *ag = getAgent(agentId);
float vel[3];
calcVel(vel, ag->npos, p, ag->params.maxSpeed);
crowd->requestMoveVelocity(agentId, vel);
} else {
m_crowd->requestMoveTarget(agentId, m_targetRef, m_targetPos);
}
}
Ogre::Vector3 OgreDetourCrowd::getLastDestination()
{
Ogre::Vector3 result;
OgreRecast::FloatAToOgreVect3(m_targetPos, result);
return result;
}
bool OgreDetourCrowd::requestVelocity(int agentId, Ogre::Vector3 velocity)
{
if (!getAgent(agentId)->active)
return false;
float vel[3];
OgreRecast::OgreVect3ToFloatA(velocity, vel);
return m_crowd->requestMoveVelocity(agentId, vel);
}
bool OgreDetourCrowd::stopAgent(int agentId)
{
float zeroVel[] = {0,0,0};
return m_crowd->resetMoveTarget(agentId) && m_crowd->requestMoveVelocity(agentId, zeroVel);
}
float OgreDetourCrowd::getDistanceToGoal(const dtCrowdAgent* agent, const float maxRange)
{
if (!agent->ncorners)
return maxRange;
const bool endOfPath = (agent->cornerFlags[agent->ncorners-1] & DT_STRAIGHTPATH_END) ? true : false;
if (endOfPath)
return dtMin(dtVdist2D(agent->npos, &agent->cornerVerts[(agent->ncorners-1)*3]), maxRange);
return maxRange;
}
bool OgreDetourCrowd::destinationReached(const dtCrowdAgent* agent, const float maxDistanceFromTarget)
{
return getDistanceToGoal(agent, maxDistanceFromTarget) < maxDistanceFromTarget;
}

File diff suppressed because it is too large Load Diff

1264
src/crowd/src/OgreRecast.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,279 @@
/*
OgreCrowd
---------
Copyright (c) 2012 Jonas Hauquier
Additional contributions by:
- mkultra333
- Paul Wilson
Sincere thanks and to:
- Mikko Mononen (developer of Recast navigation libraries)
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.
============================================================================
The Recast navigation library is made available under the ZLib license.
Copyright (c) 2009 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 "OgreRecastNavmeshPruner.h"
#include "DetourAssert.h"
// TODO implement drawing funtions to allow coloring of navmesh
class PolyRefArray
{
dtPolyRef* m_data;
int m_size, m_cap;
inline PolyRefArray(const PolyRefArray&);
inline PolyRefArray& operator=(const PolyRefArray&);
public:
inline PolyRefArray() : m_data(0), m_size(0), m_cap(0) {}
inline PolyRefArray(int n) : m_data(0), m_size(0), m_cap(0) { resize(n); }
inline ~PolyRefArray() { dtFree(m_data); }
void resize(int n)
{
if (n > m_cap)
{
if (!m_cap) m_cap = n;
while (m_cap < n) m_cap *= 2;
dtPolyRef* newData = (dtPolyRef*)dtAlloc(m_cap*sizeof(dtPolyRef), DT_ALLOC_TEMP);
if (m_size && newData) memcpy(newData, m_data, m_size*sizeof(dtPolyRef));
dtFree(m_data);
m_data = newData;
}
m_size = n;
}
inline void push(int item) { resize(m_size+1); m_data[m_size-1] = item; }
inline dtPolyRef pop() { if (m_size > 0) m_size--; return m_data[m_size]; }
inline const dtPolyRef& operator[](int i) const { return m_data[i]; }
inline dtPolyRef& operator[](int i) { return m_data[i]; }
inline int size() const { return m_size; }
};
class NavmeshFlags
{
struct TileFlags
{
inline void purge() { dtFree(flags); }
unsigned char* flags;
int nflags;
dtPolyRef base;
};
const dtNavMesh* m_nav;
TileFlags* m_tiles;
int m_ntiles;
public:
NavmeshFlags() :
m_nav(0), m_tiles(0), m_ntiles(0)
{
}
~NavmeshFlags()
{
for (int i = 0; i < m_ntiles; ++i)
m_tiles[i].purge();
dtFree(m_tiles);
}
bool init(const dtNavMesh* nav)
{
m_ntiles = nav->getMaxTiles();
if (!m_ntiles)
return true;
m_tiles = (TileFlags*)dtAlloc(sizeof(TileFlags)*m_ntiles, DT_ALLOC_TEMP);
if (!m_tiles)
{
return false;
}
memset(m_tiles, 0, sizeof(TileFlags)*m_ntiles);
// Alloc flags for each tile.
for (int i = 0; i < nav->getMaxTiles(); ++i)
{
const dtMeshTile* tile = nav->getTile(i);
if (!tile->header) continue;
TileFlags* tf = &m_tiles[i];
tf->nflags = tile->header->polyCount;
tf->base = nav->getPolyRefBase(tile);
if (tf->nflags)
{
tf->flags = (unsigned char*)dtAlloc(tf->nflags, DT_ALLOC_TEMP);
if (!tf->flags)
return false;
memset(tf->flags, 0, tf->nflags);
}
}
m_nav = nav;
return false;
}
inline void clearAllFlags()
{
for (int i = 0; i < m_ntiles; ++i)
{
TileFlags* tf = &m_tiles[i];
if (tf->nflags)
memset(tf->flags, 0, tf->nflags);
}
}
inline unsigned char getFlags(dtPolyRef ref)
{
dtAssert(m_nav);
dtAssert(m_ntiles);
// Assume the ref is valid, no bounds checks.
unsigned int salt, it, ip;
m_nav->decodePolyId(ref, salt, it, ip);
return m_tiles[it].flags[ip];
}
inline void setFlags(dtPolyRef ref, unsigned char flags)
{
dtAssert(m_nav);
dtAssert(m_ntiles);
// Assume the ref is valid, no bounds checks.
unsigned int salt, it, ip;
m_nav->decodePolyId(ref, salt, it, ip);
m_tiles[it].flags[ip] = flags;
}
};
OgreRecastNavmeshPruner::OgreRecastNavmeshPruner(OgreRecast *recast, dtNavMesh *navMesh)
: mNavmesh(navMesh)
, mRecast(recast)
{
mFlags = new NavmeshFlags();
mFlags->init(mNavmesh);
}
bool OgreRecastNavmeshPruner::floodNavmesh(Ogre::Vector3 startPoint)
{
unsigned char flag = 1; // Flag to assign to all marked polys (must not be already assigned)
// First find nearest navmesh poly to startPoint
dtPolyRef startPoly;
Ogre::Vector3 foundPt;
if (!mRecast->findNearestPolyOnNavmesh(startPoint, foundPt, startPoly))
return false;
floodNavmesh(startPoly, flag);
return true;
}
void OgreRecastNavmeshPruner::floodNavmesh(dtPolyRef start, unsigned char flag)
{
// If already visited, skip.
if (mFlags->getFlags(start))
return;
PolyRefArray openList;
openList.push(start);
while (openList.size())
{
const dtPolyRef ref = openList.pop();
// Get current poly and tile.
// The API input has been cheked already, skip checking internal data.
const dtMeshTile* tile = 0;
const dtPoly* poly = 0;
mNavmesh->getTileAndPolyByRefUnsafe(ref, &tile, &poly);
// Visit linked polygons.
for (unsigned int i = poly->firstLink; i != DT_NULL_LINK; i = tile->links[i].next)
{
const dtPolyRef neiRef = tile->links[i].ref;
// Skip invalid and already visited.
if (!neiRef || mFlags->getFlags(neiRef))
continue;
// Mark as visited
mFlags->setFlags(neiRef, flag);
// Visit neighbours
openList.push(neiRef);
}
}
}
void OgreRecastNavmeshPruner::disableUnvisitedPolys()
{
for (int i = 0; i < mNavmesh->getMaxTiles(); ++i)
{
const dtMeshTile* tile = ((const dtNavMesh*)mNavmesh)->getTile(i);
if (!tile->header) continue;
const dtPolyRef base = mNavmesh->getPolyRefBase(tile);
for (int j = 0; j < tile->header->polyCount; ++j)
{
const dtPolyRef ref = base | (unsigned int)j;
if (!mFlags->getFlags(ref))
{
unsigned short f = 0;
// Assign DISABLED flag to not-connected polygons
mNavmesh->getPolyFlags(ref, &f);
mNavmesh->setPolyFlags(ref, f | SAMPLE_POLYFLAGS_DISABLED);
}
}
}
}
void OgreRecastNavmeshPruner::clearSelection()
{
mFlags->clearAllFlags();
}
void OgreRecastNavmeshPruner::pruneSelected()
{
disableUnvisitedPolys();
// delete m_flags;
// m_flags = 0;
}

View File

@@ -0,0 +1,185 @@
/*
OgreCrowd
---------
Copyright (c) 2012 Jonas Hauquier
Additional contributions by:
- mkultra333
- Paul Wilson
Sincere thanks and to:
- Mikko Mononen (developer of Recast navigation libraries)
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.
*/
#include "RecastConvexHull.h"
#include "RecastInputGeom.h"
#include "Recast.h"
#include "OgreRecastDefinitions.h"
#include "OgreRecast.h"
// Calculates convex hull on xz-plane of points on 'pts'
ConvexVolume::ConvexVolume(InputGeom* geom, float offset)
{
// TODO protect against too many vectors!
int hullVertIndices[MAX_CONVEXVOL_PTS];
float* pts = geom->getVerts();
int npts = geom->getVertCount();
// Find lower-leftmost point.
int hull = 0;
for (int i = 1; i < npts; ++i)
if (ConvexVolume::cmppt(&pts[i*3], &pts[hull*3]))
hull = i;
// Gift wrap hull.
int endpt = 0;
int i = 0;
do
{
hullVertIndices[i++] = hull;
endpt = 0;
for (int j = 1; j < npts; ++j)
if (hull == endpt || ConvexVolume::left(&pts[hull*3], &pts[endpt*3], &pts[j*3]))
endpt = j;
hull = endpt;
}
while (endpt != hullVertIndices[0] && i < MAX_CONVEXVOL_PTS/2); // TODO: number of hull points is limited, but in a naive way. In large meshes the best candidate points for the hull might not be selected
// Leave the other half of the points for expanding the hull
nverts = i;
// Copy geometry vertices to convex hull
for (int i = 0; i < nverts; i++)
rcVcopy(&verts[i*3], &pts[hullVertIndices[i]*3]);
area = SAMPLE_POLYAREA_DOOR; // You can choose whatever flag you assing to the poly area
// Find min and max height of convex hull
hmin = geom->getMeshBoundsMin()[1];
hmax = geom->getMeshBoundsMax()[1];
// 3D mesh min and max bounds
rcVcopy(bmin, geom->getMeshBoundsMin());
rcVcopy(bmax, geom->getMeshBoundsMax());
//TODO offsetting is still broken for a lot of shapes! Fix this!
// Offset convex hull if needed
if(offset > 0.01f) {
float offsetVerts[MAX_CONVEXVOL_PTS * 3]; // An offset hull is allowed twice the number of vertices
int nOffsetVerts = rcOffsetPoly(verts, nverts, offset, offsetVerts, MAX_CONVEXVOL_PTS);
if (nOffsetVerts <= 0)
return;
for(int i = 0; i < nOffsetVerts; i++)
rcVcopy(&verts[i*3], &offsetVerts[i*3]);
nverts = nOffsetVerts;
// Modify the bounds with offset (except height)
bmin[0] = bmin[0]-offset;
bmin[2] = bmin[2]-offset;
bmax[0] = bmax[0]+offset;
bmax[2] = bmax[2]+offset;
}
}
// Returns true if 'a' is more lower-left than 'b'.
bool ConvexVolume::cmppt(const float* a, const float* b)
{
if (a[0] < b[0]) return true;
if (a[0] > b[0]) return false;
if (a[2] < b[2]) return true;
if (a[2] > b[2]) return false;
return false;
}
// Returns true if 'c' is left of line 'a'-'b'.
bool ConvexVolume::left(const float* a, const float* b, const float* c)
{
const float u1 = b[0] - a[0];
const float v1 = b[2] - a[2];
const float u2 = c[0] - a[0];
const float v2 = c[2] - a[2];
return u1 * v2 - v1 * u2 < 0;
}
ConvexVolume::ConvexVolume(Ogre::AxisAlignedBox boundingBox, float offset)
{
Ogre::Vector3 max = boundingBox.getMaximum();
Ogre::Vector3 min = boundingBox.getMinimum();
// Offset bounding box (except height)
if(offset > 0.01f) {
max = max + offset*Ogre::Vector3(1,0,1);
min = min - offset*Ogre::Vector3(1,0,1);
}
// Create box verts (in clockwise fashion!!)
verts[0]= min.x; verts[1]= min.y; verts[2]= max.z;
verts[3]= max.x; verts[4]= max.y; verts[5]= max.z;
verts[6]= max.x; verts[7]= max.y; verts[8]= min.z;
verts[9]= min.x; verts[10]= min.y; verts[11]= min.z;
nverts = 4; // For rcMarkConvexPoly the verts of the shape need to be in clockwise order
// Set bounding box limits
OgreRecast::OgreVect3ToFloatA(min, bmin);
OgreRecast::OgreVect3ToFloatA(max, bmax);
// Set height limits
hmin = min.y;
hmax = max.y;
area = SAMPLE_POLYAREA_DOOR; // You can choose whatever flag you assing to the poly area
}
void ConvexVolume::move(Ogre::Vector3 translate)
{
// Offset all verts with translation vector
for( int i = 0; i < nverts; i++) {
verts[3*i +0] += translate.x;
verts[3*i +1] += translate.y;
verts[3*i +2] += translate.z;
}
// Recalculate bounds
bmin[0] += translate.x;
bmin[1] += translate.y;
bmin[2] += translate.z;
bmax[0] += translate.x;
bmax[1] += translate.y;
bmax[2] += translate.z;
hmin += translate.y;
hmax += translate.y;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,68 @@
/*
OgreCrowd
---------
Copyright (c) 2012 Jonas Hauquier
Additional contributions by:
- mkultra333
- Paul Wilson
Sincere thanks and to:
- Mikko Mononen (developer of Recast navigation libraries)
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.
*/
#include "TestCharacter.h"
TestCharacter::TestCharacter(Ogre::String name, Ogre::SceneManager *sceneMgr, OgreDetourCrowd* detourCrowd, Ogre::Vector3 position)
: Character(name, sceneMgr, detourCrowd, position),
mEnt(NULL)
{
// Depict agent as blue cylinder
mNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(name+"Node");
mEnt = mSceneMgr->createEntity(name, "Cylinder.mesh");
mEnt->setMaterialName("Cylinder/Blue");
mNode->attachObject(mEnt);
mNode->setPosition(position);
// Set marker scale to size of agent
mNode->setScale(detourCrowd->getAgentRadius()*2, detourCrowd->getAgentHeight(), detourCrowd->getAgentRadius()*2);
mEnt->setQueryFlags(DEFAULT_MASK); // Exclude from ray queries
}
Ogre::Entity* TestCharacter::getEntity()
{
return mEnt;
}
void TestCharacter::update(Ogre::Real timeSinceLastFrame)
{
updatePosition(timeSinceLastFrame);
}
void TestCharacter::setDebugVisibility(bool visible)
{
return;
}

View File

@@ -661,10 +661,6 @@ public:
ECS::get().get_mut<ECS::GUI>().enabled = active;
ECS::get().modified<ECS::GUI>();
}
flecs::entity getPlayer() const
{
return ECS::player;
}
void enableDbgDraw(bool enable)
{
ECS::get_mut<ECS::EngineData>().enableDbgDraw = enable;

View File

@@ -8,7 +8,7 @@ add_subdirectory(items)
add_library(GameData STATIC GameData.cpp CharacterModule.cpp WaterModule.cpp SunModule.cpp TerrainModule.cpp
GUIModule.cpp EditorGUIModule.cpp LuaData.cpp WorldMapModule.cpp BoatModule.cpp EventTriggerModule.cpp
CharacterAnimationModule.cpp PhysicsModule.cpp EventModule.cpp CharacterManagerModule.cpp
VehicleManagerModule.cpp AppModule.cpp StaticGeometryModule.cpp SmartObject.cpp SlotsModule.cpp
VehicleManagerModule.cpp AppModule.cpp StaticGeometryModule.cpp SmartObject.cpp SlotsModule.cpp QuestModule.cpp
PlayerActionModule.cpp CharacterAIModule.cpp goap.cpp)
target_link_libraries(GameData PUBLIC
lua

View File

@@ -33,7 +33,6 @@ class ActionNodeActions {
const ActionNodeList &alist =
ECS::get<ActionNodeList>();
state.apply(effects);
// node position can change in case of character
const Ogre::Vector3 &nodePosition =
alist.dynamicNodes[node].position;
state.setPosition(nodePosition);
@@ -49,13 +48,8 @@ class ActionNodeActions {
{
const ActionNodeList &alist =
ECS::get<ActionNodeList>();
// const TownNPCs &npcs = bb.town.get<TownNPCs>();
// const TownAI &ai = bb.town.get<TownAI>();
const Ogre::Vector3 &nodePosition =
alist.dynamicNodes[node].position;
// flecs::entity e = npcs.npcs.at(bb.index).e;
// bool validActive = e.is_valid() &&
// e.has<CharacterBase>();
const Ogre::Vector3 &npcPosition =
bb.getPosition();
float dist = npcPosition.squaredDistance(
@@ -164,16 +158,16 @@ public:
}
Blackboard actionPrereq({ jactionPrereq });
Blackboard actionEffect({ jactionEffect });
if (!prereq.stats.is_null())
if (!prereq.is_valid())
actionPrereq.apply(prereq);
m_actions.push_back(OGRE_NEW RunActionNode(
node, action, actionPrereq, actionEffect, cost));
if (effectName == "") {
std::cout << props.dump(4) << std::endl;
std::cout << "Prereq" << std::endl;
std::cout << actionPrereq.stats.dump(4) << std::endl;
actionPrereq.dump_bits();
std::cout << "Effect" << std::endl;
std::cout << actionEffect.stats.dump(4) << std::endl;
actionEffect.dump_bits();
OgreAssert(false, "action");
}
}
@@ -241,7 +235,7 @@ CharacterAIModule::CharacterAIModule(flecs::world &ecs)
2000 },
#endif
{ "EatMedicine",
{ { { "have_medicine", 1 }, { "healty", 0 } } },
{ { { "have_medicine", 1 }, { "healthy", 0 } } },
{ { { "healthy", 1 } } },
100 },
{ "UseToilet",
@@ -344,24 +338,12 @@ CharacterAIModule::CharacterAIModule(flecs::world &ecs)
.interval(0.1f)
.each([this](flecs::entity town, ActionNodeList &alist,
TownAI &ai, const TownNPCs &npcs) {
for (auto it = npcs.npcs.begin(); it != npcs.npcs.end();
it++) {
const auto &npc = npcs.npcs.at(it->first);
if (ai.blackboards.find(it->first) ==
ai.blackboards.end())
continue;
ai.blackboards.at(it->first).setPosition(
npc.position);
}
Ogre::Root::getSingleton().getWorkQueue()->addTask(
[this, town, &alist, npcs, &ai]() {
{
std::lock_guard<std::mutex> lock(
ecs_mutex);
alist.build();
updateBlackboardsBits(
town, alist, npcs, ai);
updateBlackboards(town, alist,
npcs, ai);
}
@@ -370,12 +352,12 @@ CharacterAIModule::CharacterAIModule(flecs::world &ecs)
->addMainThreadTask([this, town,
&alist]() {
town.modified<TownAI>();
town.modified<TownNPCs>();
ECS::modified<
ActionNodeList>();
});
});
});
#if 1
ecs.system<TownAI, TownNPCs>("PlanAI")
.kind(flecs::OnUpdate)
.interval(0.5f)
@@ -394,7 +376,6 @@ CharacterAIModule::CharacterAIModule(flecs::world &ecs)
});
});
});
#endif
}
void CharacterAIModule::createAI(flecs::entity town)
@@ -402,91 +383,90 @@ void CharacterAIModule::createAI(flecs::entity town)
town.add<TownAI>();
}
struct PlanTask {
Blackboard blackboard;
TownAI::goal_t goal;
TownAI::Plan plan;
TownAI::planner_t *planner;
bool operator()()
{
auto buildPlan = [this](Blackboard &blackboard,
const TownAI::goal_t &goal,
TownAI::Plan &plan) -> bool {
if (goal.is_reached(blackboard))
return false;
plan.goal = &goal;
std::vector<goap::BaseAction<Blackboard> *> path;
int actionCount = blackboard.getActionsCount();
auto actionsData = blackboard.getActionsData();
path.resize(actionCount * actionCount);
int path_length = planner->plan(
blackboard, goal, actionsData, actionCount,
path.data(), path.size());
if (path_length > 0) {
plan.goal = &goal;
plan.plan.insert(plan.plan.end(), path.begin(),
path.begin() + path_length);
return true;
}
return false;
};
return buildPlan(blackboard, goal, plan);
}
PlanTask(Blackboard &blackboard, const TownAI::goal_t &goal,
TownAI::planner_t *planner)
: blackboard(blackboard)
, goal(goal)
, planner(planner)
{
}
};
static std::deque<PlanTask> plan_tasks;
void CharacterAIModule::buildPlans(flecs::entity town, const TownNPCs &npcs,
TownAI &ai)
{
OgreAssert(town.is_valid(), "Bad town entity");
std::lock_guard<std::mutex> lock(*ai.mutex);
auto planner = ai.planner;
auto buildPlan = [planner](Blackboard &blackboard,
const TownAI::goal_t &goal,
TownAI::Plan &plan) -> bool {
if (goal.is_reached(blackboard))
return false;
plan.goal = &goal;
std::vector<goap::BaseAction<Blackboard> *> path;
int actionCount = blackboard.getActionsCount();
auto actionsData = blackboard.getActionsData();
path.resize(actionCount * actionCount);
int path_length = planner->plan(blackboard, goal, actionsData,
actionCount, path.data(),
path.size());
if (path_length > 0) {
plan.goal = &goal;
plan.plan.insert(plan.plan.end(), path.begin(),
path.begin() + path_length);
return true;
}
return false;
};
for (auto it = npcs.npcs.begin(); it != npcs.npcs.end(); it++) {
if (ai.blackboards.find(it->first) == ai.blackboards.end())
continue;
auto &bb = ai.blackboards.at(it->first);
/* if there are plans, skip until these get discarded */
if (ai.plans.find(it->first) != ai.plans.end() &&
ai.plans.at(it->first).size() > 0)
continue;
const auto &npc = npcs.npcs.at(it->first);
int index = it->first;
ai.plans[index] = {};
for (const auto &goal : ai.goals) {
struct TownAI::Plan plan;
bool created = buildPlan(bb, goal, plan);
#if 0
std::cout << "blackboard: "
<< bb.stats.dump(4)
<< std::endl;
std::cout << "goal: "
<< goal.goal.stats.dump(4)
<< std::endl;
#endif
#if 0
std::cout << "Actions: " << std::endl;
for (auto &action : actions) {
std::cout << "name: "
<< action->get_name()
<< std::endl;
std::cout
<< "\tprereq:\n"
<< action->prereq.stats
.dump(4)
<< std::endl;
std::cout
<< "\teffects:\n"
<< action->effects.stats
.dump(4)
<< std::endl;
}
#endif
#if 1
if (created) {
std::cout << bb.index << " ";
std::cout << "Goal: " << goal.get_name();
std::cout << std::endl;
std::cout << "Path: ";
for (auto &action : plan.plan) {
OgreAssert(action, "No action");
std::cout << action->get_name() + " ";
}
std::cout << " size: " << plan.plan.size()
<< std::endl;
ai.plans[it->first].push_back(plan);
OgreAssert(false, "plan");
if (plan_tasks.size() > 0) {
bool created = (plan_tasks.front())();
if (created) {
std::cout << plan_tasks.front().blackboard.index << " ";
std::cout << "Goal: "
<< plan_tasks.front().goal.get_name();
plan_tasks.front().goal.goal.dump_bits();
std::cout << std::endl;
std::cout << "Path: ";
for (auto &action : plan_tasks.front().plan.plan) {
OgreAssert(action, "No action");
std::cout << action->get_name() + " ";
}
#endif
std::cout << " size: "
<< plan_tasks.front().plan.plan.size()
<< std::endl;
ai.plans[plan_tasks.front().blackboard.index].push_back(
plan_tasks.front().plan);
}
plan_tasks.pop_front();
} else
for (auto it = npcs.npcs.begin(); it != npcs.npcs.end(); it++) {
if (ai.blackboards.find(it->first) ==
ai.blackboards.end())
continue;
auto &bb = ai.blackboards.at(it->first);
/* if there are plans, skip until these get discarded */
if (ai.plans.find(it->first) != ai.plans.end() &&
ai.plans.at(it->first).size() > 0)
continue;
const auto &npc = npcs.npcs.at(it->first);
int index = it->first;
ai.plans[index] = {};
bb.query_ai();
for (const auto &goal : ai.goals)
plan_tasks.emplace_back(bb, goal,
ai.planner.get());
}
}
}
void CharacterAIModule::createBlackboards(flecs::entity town,
@@ -505,20 +485,24 @@ void CharacterAIModule::createBlackboards(flecs::entity town,
strength += 10;
dexterity += 10;
}
// FIXME: use separate "memory" for stats
// Do not keep actual stats in blackboard
nlohmann::json memory;
memory["strength"] = strength;
memory["dexterity"] = dexterity;
memory["health"] = health;
memory["stamina"] = stamina;
memory["needs_hunger"] = 0;
memory["needs_thirst"] = 0;
memory["needs_toilet"] = 0;
nlohmann::json bb;
bb["strength"] = strength;
bb["dexterity"] = dexterity;
bb["health"] = health;
bb["stamina"] = stamina;
bb["needs_hunger"] = 0;
bb["needs_thirst"] = 0;
bb["needs_toilet"] = 0;
bb["have_water"] = 0;
bb["have_food"] = 0;
bb["have_medicine"] = 0;
bb["at_object"] = 0;
ai.memory[it->first] = memory;
ai.blackboards[it->first] = Blackboard(bb);
ai.blackboards[it->first].index = it->first;
ai.blackboards.at(it->first).town = town;
@@ -526,103 +510,103 @@ void CharacterAIModule::createBlackboards(flecs::entity town,
ai.blackboards.at(it->first).actionRefResize(0);
ai.blackboards.at(it->first).actionRefAddActions(
ai.actions);
}
}
}
void CharacterAIModule::updateBlackboardsBits(flecs::entity town,
ActionNodeList &alist,
const TownNPCs &npcs, TownAI &ai)
{
OgreAssert(town.is_valid(), "Bad town entity");
std::lock_guard<std::mutex> lock(*ai.mutex);
struct UpdateBit {
Ogre::String checkValue;
int recover;
int minValue;
int maxValue;
int threshold;
Ogre::String writeValue;
};
struct UpdateBit updateBits[] = {
{ "health", 0, 0, 100, 20, "healthy" },
{ "needs_hunger", 1, 0, 10000, 2000, "hungry" },
{ "needs_thirst", 1, 0, 10000, 1000, "thirsty" },
{ "needs_toilet", 1, 0, 10000, 1500, "toilet" }
};
for (auto it = npcs.npcs.begin(); it != npcs.npcs.end(); it++) {
if (ai.blackboards.find(it->first) == ai.blackboards.end())
continue;
auto &stats = ai.blackboards.at(it->first).stats;
auto &object = ai.blackboards.at(it->first).object;
ai.blackboards.at(it->first).index = it->first;
ai.blackboards.at(it->first).town = town;
for (const auto &bits : updateBits) {
int value = stats[bits.checkValue].get<int>();
int maxValue = bits.maxValue;
int minValue = bits.minValue;
int threshold = bits.threshold;
if (it->second.props.find(bits.checkValue + "_max") !=
it->second.props.end())
maxValue =
it->second
.props[bits.checkValue + "_max"]
.get<int>();
if (it->second.props.find(bits.checkValue +
"_threshold") !=
it->second.props.end())
threshold = it->second
.props[bits.checkValue +
"_threshold"]
.get<int>();
value += bits.recover;
if (value > maxValue)
value = maxValue;
if (value >= threshold)
stats[bits.writeValue] = 1;
else
stats[bits.writeValue] = 0;
if (value < bits.minValue)
value = bits.minValue;
stats[bits.checkValue] = value;
ai.conditions =
TownAI::BitEvolutionBuilder()
.addRule(
"healthy",
[](const nlohmann::json &data)
-> bool {
return data["health"]
.get<int>() >
20;
})
->addRule(
"hungry",
[](const nlohmann::json &data)
-> bool {
return data["needs_hunger"]
.get<int>() >
2000;
})
->addRule(
"thirsty",
[](const nlohmann::json &data)
-> bool {
return data["needs_thirst"]
.get<int>() >
1000;
})
->addRule(
"toilet",
[](const nlohmann::json &data)
-> bool {
return data["needs_toilet"]
.get<int>() >
1500;
})
->build();
ai.memoryUpdates =
TownAI::MemoryUpdateBuilder()
.addUpdate(
"health",
[](int index,
const std::string &name,
int value) -> int {
if (value < 50)
return value +
1;
else
return value;
})
.addUpdate("needs_hunger",
[](int index,
const std::string &name,
int value) -> int {
return value + 1;
})
.addUpdate("needs_thirst",
[](int index,
const std::string &name,
int value) -> int {
return value + 1;
})
.addUpdate("needs_toilet",
[](int index,
const std::string &name,
int value) -> int {
return value + 1;
})
.build();
}
}
}
void CharacterAIModule::updateBlackboards(flecs::entity town,
const ActionNodeList &alist,
ActionNodeList &alist,
const TownNPCs &npcs, TownAI &ai)
{
std::lock_guard<std::mutex> lock(*ai.mutex);
OgreAssert(town.is_valid(), "Bad town entity");
alist.build();
for (auto it = npcs.npcs.begin(); it != npcs.npcs.end(); it++) {
if (ai.blackboards.find(it->first) == ai.blackboards.end())
continue;
auto &stats = ai.blackboards.at(it->first).stats;
auto &object = ai.blackboards.at(it->first).object;
ai.blackboards.at(it->first).index = it->first;
ai.blackboards.at(it->first).town = town;
ai.blackboards.at(it->first).setPosition(
npcs.npcs.at(it->first).position);
for (auto e : ai.memoryUpdates) {
std::string key = e.name;
auto &memory = ai.memory.at(it->first);
if (memory.find(key) == memory.end())
memory[key] = 0;
int value = memory[key].get<int>();
int new_value = e.update(it->first, key, value);
if (value != new_value)
memory[key] = new_value;
}
auto &bb = ai.blackboards.at(it->first);
bb.query_ai();
#if 0
OgreAssert(nodeActionCount > 0 ||
points.size() == 0,
"no node actions and no points");
if (nodeActionCount == 0) {
std::cout << "nodes:"
<< alist.nodes.size() << " "
<< alist.dynamicNodes.size()
<< std::endl;
std::cout << "points: " << points.size()
<< std::endl;
std::cout << "position: " << position
<< std::endl;
}
OgreAssert(nodeActionCount > 0,
"no node actions");
#endif
bb.fixupBooleanKeys();
bb.updateBits(ai, ai.memory.at(it->first), it->second.props);
}
}
@@ -640,23 +624,56 @@ void Blackboard::_actionRefResize(int count)
}
Blackboard::Blackboard()
: stats(nlohmann::json::object())
, object(-1)
: object(-1)
, index(-1)
, actionRefCount(0)
, mutex(std::make_shared<std::mutex>())
, bits(0)
, mask(0)
{
}
std::unordered_map<std::string, size_t> Blackboard::mapping;
Blackboard::Blackboard(const nlohmann::json &stats)
: Blackboard()
{
this->stats = stats;
populate(stats, mapping);
}
void Blackboard::populate(const nlohmann::json &stats,
std::unordered_map<std::string, size_t> &mapping)
{
if (stats.empty())
return;
for (auto &[key, value] : stats.items()) {
if (value.is_number_integer()) {
int val = value.get<int>();
if (val == 0 || val == 1) {
// Update mapping if key is new
if (mapping.find(key) == mapping.end()) {
size_t next_bit = mapping.size();
if (next_bit < 64) {
mapping[key] = next_bit;
} else {
OgreAssert(false,
"Out of bits");
continue;
}
}
size_t bit_idx = mapping[key];
bits.set(bit_idx, val == 1);
mask.set(bit_idx);
}
}
}
}
bool Blackboard::operator==(const Blackboard &other) const
{
return is_satisfied_by(this->stats, other.stats);
OgreAssert(mask != 0 && other.mask != 0, "blackboard not prepared");
return (bits & other.mask) == (other.bits & other.mask);
}
bool Blackboard::operator!=(const Blackboard &other) const
@@ -667,7 +684,13 @@ bool Blackboard::operator!=(const Blackboard &other) const
void Blackboard::apply(const Blackboard &other)
{
std::lock_guard<std::mutex> lock(*mutex);
stats.update(other.stats);
// stats.update(other.stats);
// 1. Clear bits in 'values' that are about to be overwritten (where effect mask is 1)
// 2. OR the result with the effect values (filtered by the effect mask)
bits = (bits & ~other.mask) | (other.bits & other.mask);
// 3. Update our mask to include any new state definitions introduced by the effect
mask |= other.mask;
}
Ogre::String Blackboard::dumpActions()
@@ -679,8 +702,10 @@ Ogre::String Blackboard::dumpActions()
for (count = 0; count < actionRefCount; count++) {
auto &action = actionRef[count];
ret += "name: " + action->get_name() + "\n";
ret += "\tprereq:\n" + action->prereq.stats.dump(4) + "\n";
ret += "\teffects:\n" + action->effects.stats.dump(4) + "\n";
ret += "\tprereq:\n";
action->prereq.dump_bits();
ret += "\teffects:\n";
action->effects.dump_bits();
}
return ret;
}
@@ -690,23 +715,6 @@ void Blackboard::printActions()
std::cout << dumpActions() << std::endl;
}
void Blackboard::fixupBooleanKeys()
{
std::lock_guard<std::mutex> lock(*mutex);
int count;
for (count = 0; count < actionRefCount; count++) {
auto &action = actionRef[count];
const nlohmann::json &prereq = action->prereq.stats;
const nlohmann::json &effects = action->effects.stats;
for (auto it = prereq.begin(); it != prereq.end(); it++)
if (stats.find(it.key()) == stats.end())
stats[it.key()] = 0;
for (auto it = effects.begin(); it != effects.end(); it++)
if (stats.find(it.key()) == stats.end())
stats[it.key()] = 0;
}
}
void Blackboard::actionRefResize(int count)
{
std::lock_guard<std::mutex> lock(*mutex);
@@ -759,6 +767,17 @@ void Blackboard::actionRefAddActions(goap::BaseAction<Blackboard> **actions,
}
}
void Blackboard::updateBits(const TownAI &ai, const nlohmann::json &memory,
const nlohmann::json &props)
{
for (const auto &mcond : ai.conditions) {
if (mapping.find(mcond.key) == mapping.end())
populate(nlohmann::json::object({ { mcond.key, 0 } }),
mapping);
bits.set(mapping[mcond.key], mcond.condition(memory));
}
}
struct ComparePair {
const nlohmann::json &current;
const nlohmann::json &target;
@@ -802,33 +821,8 @@ bool Blackboard::is_satisfied_by(const nlohmann::json &current,
int Blackboard::distance_to(const Blackboard &goal) const
{
int distance = 0;
OgreAssert(goal.stats.is_object(),
"Not an object:\n" + goal.stats.dump(4));
for (auto it = goal.stats.begin(); it != goal.stats.end(); ++it) {
const std::string &key = it.key();
const auto &goalVal = it.value();
// If current state doesn't have the key, treat it as a maximum difference
if (stats.find(key) == stats.end()) {
distance += 100; // Example: High cost for missing state
continue;
}
const auto &currentVal = stats[key];
if (goalVal.is_number() && currentVal.is_number()) {
// Add numerical difference
distance += std::abs(goalVal.get<float>() -
currentVal.get<float>());
} else {
// Check non-numeric equality
if (goalVal != currentVal) {
distance += 1; // Penalty for mismatch
}
}
}
return distance;
OgreAssert(mask != 0 && goal.mask != 0, "blackboard not prepared");
return ((bits ^ goal.bits) & goal.mask).count();
}
void Blackboard::setPosition(const Ogre::Vector3 &position)
@@ -854,7 +848,6 @@ void Blackboard::query_ai()
ActionNodeList &alist = ECS::get_mut<ActionNodeList>();
alist.query_ai(position, distance, points, distances);
_actionRefResize(ai.actions.size());
int actionRefIndex = ai.actions.size();
int nodeActionCount = 0;
for (size_t point : points) {
Ogre::Vector3 &p = alist.dynamicNodes[point].position;
@@ -863,10 +856,10 @@ void Blackboard::query_ai()
if (object >= 0 && (size_t)object == point &&
distance > radius * radius) {
object = -1;
stats["at_object"] = 0;
bits.set(mapping["at_object"], false);
} else if (object >= 0 && (size_t)object == point &&
distance <= radius * radius)
stats["at_object"] = 1;
bits.set(mapping["at_object"], true);
/* some nodes do not have usable actions */
if (ai.nodeActions[point].size() > 0) {
OgreAssert(ai.nodeActions[point].size() > 0,
@@ -883,7 +876,6 @@ void Blackboard::query_ai()
j["global_position_y"] = p.y;
j["global_position_z"] = p.z;
nodes.push_back(j);
stats["nodes"] = nodes;
}
}
}

View File

@@ -8,14 +8,26 @@
namespace ECS
{
struct TownAI;
struct Blackboard {
nlohmann::json stats;
struct UpdateBit {
Ogre::String checkValue;
int recover;
int minValue;
int maxValue;
int threshold;
Ogre::String writeValue;
};
int object;
int index;
flecs::entity town;
std::shared_ptr<std::mutex> mutex;
static std::unordered_map<std::string, size_t> mapping;
private:
// nlohmann::json stats;
std::bitset<64> bits, mask;
std::vector<goap::BaseAction<Blackboard> *> actionRef;
int actionRefCount;
int actionRefPtr;
@@ -25,6 +37,7 @@ private:
void _actionRefResize(int count);
void _actionRefAddActions(
const std::vector<goap::BaseAction<Blackboard> *> &actions);
void populate(const nlohmann::json &stats, std::unordered_map<std::string, size_t>& mapping);
public:
Blackboard();
@@ -34,21 +47,40 @@ public:
void apply(const Blackboard &other);
Ogre::String dumpActions();
void printActions();
void fixupBooleanKeys();
void actionRefResize(int count);
void actionRefAddAction(goap::BaseAction<Blackboard> *action);
void actionRefAddActions(
const std::vector<goap::BaseAction<Blackboard> *> &actions);
void actionRefAddActions(goap::BaseAction<Blackboard> **actions,
int count);
const goap::BaseAction<Blackboard> *const *getActionsData() const
{
return actionRef.data();
}
goap::BaseAction<Blackboard> **getActionsData()
{
return actionRef.data();
}
int getActionsCount()
int getActionsCount() const
{
return actionRefCount;
}
#if 0
void commit()
{
populate(stats, mapping);
}
#endif
void dump_bits() const
{
std::cout << "bits: " << bits << std::endl;
std::cout << "mask: " << mask << std::endl;
}
bool is_valid() const
{
return /* !stats.is_null() && */ mask != 0;
}
void updateBits(const TownAI &ai, const nlohmann::json &memory, const nlohmann::json &props);
private:
static bool is_satisfied_by(const nlohmann::json &current,
@@ -78,13 +110,62 @@ struct TownAI {
goap::BasePlanner<Blackboard, goap::BaseAction<Blackboard> > >
planner;
std::unordered_map<int, Blackboard> blackboards;
typedef goap::BasePlanner<Blackboard, goap::BaseAction<Blackboard> >::BaseGoal goal_t;
typedef goap::BasePlanner<
Blackboard, goap::BaseAction<Blackboard> >::BaseGoal goal_t;
typedef goap::BasePlanner<Blackboard, goap::BaseAction<Blackboard> >
planner_t;
struct Plan {
const goal_t *goal;
std::vector<goap::BaseAction<Blackboard> *> plan;
};
std::unordered_map<int, std::vector<struct Plan> > plans;
std::unordered_map<int, std::vector<goap::BaseAction<Blackboard> *> > nodeActions;
std::unordered_map<int, std::vector<goap::BaseAction<Blackboard> *> >
nodeActions;
struct Condition {
std::string key;
std::function<bool(const nlohmann::json &data)> condition;
};
struct MemoryUpdate {
std::string name;
std::function<int(int index, const std::string &name, int value)>
update;
};
struct BitEvolutionBuilder {
std::vector<Condition> conditions;
BitEvolutionBuilder *
addRule(const std::string &key,
std::function<bool(const nlohmann::json &data)>
condition)
{
conditions.push_back({ key, condition });
return this;
}
std::vector<Condition> build()
{
return conditions;
}
};
struct MemoryUpdateBuilder {
std::vector<MemoryUpdate> memoryUpdates;
MemoryUpdateBuilder &
addUpdate(const std::string &name,
std::function<int(int index, const std::string &name,
int value)>
update)
{
memoryUpdates.push_back({ name, update });
return *this;
}
std::vector<MemoryUpdate> build()
{
return memoryUpdates;
}
};
std::vector<Condition> conditions;
std::unordered_map<int, nlohmann::json> memory;
std::vector<MemoryUpdate> memoryUpdates;
};
struct CharacterAIModule {
@@ -92,8 +173,7 @@ struct CharacterAIModule {
void createAI(flecs::entity town);
void buildPlans(flecs::entity town, const TownNPCs &npcs, TownAI &ai);
void createBlackboards(flecs::entity town, const TownNPCs &npcs, TownAI &ai);
void updateBlackboardsBits(flecs::entity town, ActionNodeList &alist, const TownNPCs &npcs, TownAI &ai);
void updateBlackboards(flecs::entity town, const ActionNodeList &alist, const TownNPCs &npcs, TownAI &ai);
void updateBlackboards(flecs::entity town, ActionNodeList &alist, const TownNPCs &npcs, TownAI &ai);
};
}

View File

@@ -259,15 +259,6 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
}
#endif
v.velocity = rot * boneMotion / safeDelta;
#if 0
if (!e.has<CharacterDisablePhysics>() &&
!e.has<CharacterInActuator>()) {
if (eng.startupDelay <= 0.0f)
v.velocity += v.gvelocity;
v.velocity.y = Ogre::Math::Clamp(v.velocity.y,
-10.5f, 10.0f);
}
#endif
// if (v.velocity.squaredLength() > 1.4f * 1.4f)
// v.velocity = v.velocity.normalisedCopy() * 1.4f;
// ch.mBoneMotion = Ogre::Vector3::ZERO;

View File

@@ -307,30 +307,6 @@ CharacterModule::CharacterModule(flecs::world &ecs)
// "need contact response");
});
#endif
#if 0
ecs.system<const EngineData, CharacterBase, CharacterBody>(
"UpdateCharacterPhysics")
.kind(flecs::OnUpdate)
.with<Character>()
.with<TerrainReady>()
.with<WaterReady>()
.each([](const EngineData &eng, CharacterBase &ch,
CharacterBody &body) {
#if 0
if (ch.mBodyNode && !body.mController &&
eng.startupDelay < 0.0f) {
body.mController =
new Ogre::Bullet::KinematicMotionSimple(
body.mGhostObject,
ch.mBodyNode);
body.mController->enableManualNarrowPhase(true);
eng.mWorld->getBtWorld()->addAction(
body.mController);
OgreAssert(body.mController, "Need controller");
}
#endif
});
#endif
#define CAM_HEIGHT 1.6f // height of camera above character's center of mass
ecs.system<const EngineData, Camera, const CharacterBase>(
"UpdateCamera")

View File

@@ -78,5 +78,8 @@ struct Body2Entity {
struct EditorSceneSwitch {
int scene;
};
struct GameState {
bool running;
};
}
#endif

View File

@@ -26,6 +26,7 @@
#include "CharacterManagerModule.h"
#include "items.h"
#include "physics.h"
#include "QuestModule.h"
#include "GUIModule.h"
#include "GUIModuleCommon.h"
namespace ECS
@@ -60,13 +61,6 @@ struct GUIListener : public Ogre::RenderTargetListener {
bigFont = ECS::get<GUIData>().mGuiOverlay->addFont("bigFont",
"General");
OgreAssert(bigFont, "Could not load font");
#if 0
Ogre::FontPtr _midFont = createFont("midFont", "General",
"Kenney Bold.ttf", 28.0f);
#endif
#if 0
ImGui::GetIO().Fonts->Build();
#endif
}
Ogre::FontPtr createFont(const Ogre::String &name,
const Ogre::String &group,
@@ -100,8 +94,6 @@ struct GUIListener : public Ogre::RenderTargetListener {
ImGui::SetNextWindowSize(ImVec2(window_width, window_height),
ImGuiCond_Always);
ImGui::Begin("Control");
// if (ECS::get().get<GUI>().enabled)
// ECS::get().get<App>().app->setWindowGrab(true);
if (ImGui::Button("Quit"))
Ogre::Root::getSingleton().queueEndRendering();
if (ImGui::Button("Return"))
@@ -251,11 +243,15 @@ struct GUIListener : public Ogre::RenderTargetListener {
{
int i;
Ogre::ImGuiOverlay::NewFrame();
if (ECS::get().get<EngineData>().startupDelay > 0.0f) {
if (ECS::get().get<EngineData>().startupDelay > 0.0f &&
ECS::get().has<GameState>()) {
ImVec2 size = ImGui::GetMainViewport()->Size;
ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always);
ImGui::SetNextWindowSize(ImVec2(size.x, size.y),
ImGuiCond_Always);
ImVec4 solidColor = ImVec4(0.0f, 0.0f, 0.0f, 1.0f);
ImGui::PushStyleColor(ImGuiCol_WindowBg, solidColor);
ImGui::Begin(
"StartupScreen", nullptr,
ImGuiWindowFlags_NoTitleBar |
@@ -274,6 +270,7 @@ struct GUIListener : public Ogre::RenderTargetListener {
"This game does not autosave. Please use save function to keep your state");
ImGui::PopFont();
ImGui::End();
ImGui::PopStyleColor();
} else if (ECS::get<GUI>().narrationHandlers.size() > 0) {
ECS::get_mut<GUI>().grab = false;
ECS::get_mut<GUI>().grabChanged = true;
@@ -376,9 +373,14 @@ struct GUIListener : public Ogre::RenderTargetListener {
ImVec2 size = ImGui::GetMainViewport()->Size;
ImGui::SetNextWindowPos(ImVec2(0, 0),
ImGuiCond_Always);
ImGui::SetNextWindowSize(ImVec2(size.x, size.y),
ImGui::SetNextWindowSize(ImVec2(size.x + 4,
size.y + 1),
ImGuiCond_Always);
ImGui::Begin(
ImVec4 solidColor =
ImVec4(0.0f, 0.0f, 0.0f, 1.0f);
ImGui::PushStyleColor(ImGuiCol_WindowBg,
solidColor);
ImGui::Begin(
"MainMenu", nullptr,
ImGuiWindowFlags_NoTitleBar |
ImGuiWindowFlags_NoDecoration |
@@ -388,8 +390,6 @@ struct GUIListener : public Ogre::RenderTargetListener {
ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoFocusOnAppearing |
0);
// if (ECS::get().get<GUI>().enabled)
// ECS::get().get<App>().app->setWindowGrab(true);
ImGui::PushFont(bigFont);
ImGui::TextWrapped("%s", "Booo!!!!");
bool pressed = false;
@@ -412,16 +412,22 @@ struct GUIListener : public Ogre::RenderTargetListener {
ImGui::PopFont();
ImGui::Spacing();
ImGui::End();
if (quit)
ImGui::PopStyleColor();
if (quit)
Ogre::Root::getSingleton()
.queueEndRendering();
if (pressed)
ECS::get().get<GUI>().finish();
if (new_game) {
ECS::get<LuaBase>().mLua->call_handler(
"new_game");
}
} else {
ECS::get().set<GameState>({ true });
ECS::get<LuaBase>().mLua->call_handler(
"new_game_selected");
} else if (load_game) {
ECS::get().set<GameState>({ true });
ECS::get<LuaBase>().mLua->call_handler(
"load_game_selected");
}
} else {
buttons_panel();
if (enableEditor)
buildings_editor();
@@ -439,7 +445,6 @@ struct GUIListener : public Ogre::RenderTargetListener {
ImGui::SetNextWindowSize(ImVec2(window_width,
window_height),
ImGuiCond_Always);
// ImGui::Begin("Dumb and Stupid", &mKbd.gui_active);
ImGui::Begin("Panel...");
std::deque<Ogre::SceneNode *> tree_input_queue,
tree_output_queue;
@@ -645,41 +650,6 @@ struct GUIListener : public Ogre::RenderTargetListener {
.action_text
.c_str());
}
#if 0
ImGui::SetNextWindowPos(
ImVec2(screenPos.x,
screenPos.y),
ImGuiCond_Always,
ImVec2(0.5f, 0.5f));
ImGui::Begin(
("SensorLabel##" +
Ogre::StringConverter::toString(
p))
.c_str(),
nullptr,
ImGuiWindowFlags_NoBackground |
ImGuiWindowFlags_NoTitleBar |
ImGuiWindowFlags_AlwaysAutoResize |
ImGuiWindowFlags_NoInputs);
ImDrawList *drawList =
ImGui::GetWindowDrawList();
ImVec2 cursor =
ImGui::GetCursorScreenPos();
drawList->AddCircleFilled(
ImVec2(cursor.x, cursor.y),
16.0f,
IM_COL32(0, 0, 255, 255));
drawList->AddCircle(
ImVec2(cursor.x, cursor.y),
16.0f,
IM_COL32(32, 32, 255, 255));
ImGui::TextColored(
ImVec4(1, 0, 0, 1), "%s",
list.nodes[p]
.action_text.c_str());
ImGui::End();
#endif
}
}
}
@@ -690,13 +660,14 @@ GUIModule::GUIModule(flecs::world &ecs)
{
ecs.module<GUIModule>();
ecs.import <AppModule>();
ecs.import <QuestModule>();
ecs.component<GUI>()
.on_add([](GUI &gui) {
gui.enabled = false;
gui.grab = false;
gui.grabChanged = false;
gui.enableActions = true;
})
})
.add(flecs::Singleton);
ecs.component<GUIData>()
.on_add([](GUIData &priv) {
@@ -705,51 +676,39 @@ GUIModule::GUIModule(flecs::world &ecs)
priv.mGuiOverlay = nullptr;
})
.add(flecs::Singleton);
ecs.set<GUI>({ false, true, false, false, false, true, "", {}, -1 });
ecs.set<GUIData>({ nullptr, {}, nullptr });
ui_wait =
ecs.system<const RenderWindow, App, GUIData>("SetupGUI")
.kind(flecs::OnUpdate)
.each([this](const RenderWindow &window, App &app,
GUIData &gui) {
if (!gui.mGuiOverlay) {
float vpScale =
window.dpi / 96 *
window.window->getWidth() /
1600.0f;
Ogre::OverlayManager::getSingleton()
.setPixelRatio(vpScale);
std::cout << "GUI configure\n";
OgreAssert(app.mGuiOverlay,
"No ImGUI overlay");
gui.mGuiOverlay = app.mGuiOverlay;
gui.mGUIListener = new GUIListener();
gui.mGuiOverlay->setZOrder(300);
gui.mGuiOverlay->show();
gui.mGUIListener->panel_width = 300.0f;
gui.mGUIListener->enableEditor = false;
window.window->addListener(
gui.mGUIListener);
int i;
}
const std::vector<Ogre::String> &groups =
Ogre::ResourceGroupManager::
getSingleton()
.getResourceGroups();
for (i = 0; i < groups.size(); i++) {
std::vector<Ogre::String> names =
*Ogre::ResourceGroupManager::getSingleton()
.findResourceNames(
groups[i],
"*.glb");
gui.glb_names.insert(
gui.glb_names.end(),
names.begin(),
names.end());
}
ECS::modified<ECS::GUI>();
std::cout << "GUI configure finished\n";
}
});
void GUIModule::configure()
{
ECS::get().set<GUIData>({ nullptr, {}, nullptr });
const RenderWindow &window = ECS::get<RenderWindow>();
GUIData &gui = ECS::get_mut<GUIData>();
const App &app = ECS::get<App>();
if (gui.mGuiOverlay)
return;
float vpScale = window.dpi / 96 * window.window->getWidth() / 1600.0f;
Ogre::OverlayManager::getSingleton().setPixelRatio(vpScale);
std::cout << "GUI configure\n";
OgreAssert(app.mGuiOverlay, "No ImGUI overlay");
gui.mGuiOverlay = app.mGuiOverlay;
gui.mGUIListener = new GUIListener();
gui.mGuiOverlay->setZOrder(300);
gui.mGuiOverlay->show();
gui.mGUIListener->panel_width = 300.0f;
gui.mGUIListener->enableEditor = false;
window.window->addListener(gui.mGUIListener);
int i;
const std::vector<Ogre::String> &groups =
Ogre::ResourceGroupManager::getSingleton().getResourceGroups();
for (i = 0; i < groups.size(); i++) {
std::vector<Ogre::String> names =
*Ogre::ResourceGroupManager::getSingleton()
.findResourceNames(groups[i], "*.glb");
gui.glb_names.insert(gui.glb_names.end(), names.begin(),
names.end());
}
ECS::modified<GUIData>();
std::cout << "GUI configure finished\n";
}
}

View File

@@ -9,6 +9,7 @@ namespace ECS
struct GUIModule {
flecs::entity ui_wait;
GUIModule(flecs::world &ecs);
static void configure();
};
}
#endif

View File

@@ -25,6 +25,7 @@
#include "PlayerActionModule.h"
#include "AppModule.h"
#include "CharacterAIModule.h"
#include "QuestModule.h"
#include "world-build.h"
namespace ECS
@@ -49,9 +50,9 @@ void setupExteriorScene(Ogre::SceneManager *scnMgr, Ogre::SceneNode *cameraNode,
Ogre::Camera *camera, Ogre::RenderWindow *window)
{
std::cout << "Setup GameData\n";
setup_minimal();
ecs.component<RenderWindow>().add(flecs::Singleton);
ecs.import <CharacterModule>();
setup_minimal();
ecs.component<GameState>().add(flecs::Singleton);
ecs.import <CharacterModule>();
ecs.import <BoatModule>();
ecs.import <PhysicsModule>();
ecs.import <WaterModule>();
@@ -64,8 +65,11 @@ void setupExteriorScene(Ogre::SceneManager *scnMgr, Ogre::SceneNode *cameraNode,
ecs.import <CharacterAnimationModule>();
ecs.import <PlayerActionModule>();
ecs.import <CharacterAIModule>();
ecs.import <QuestModule>();
ecs.add<ActionNodeList>();
ecs.set<GUI>({ false, false, true, false, false, true, "", {}, -1 });
ecs.system<EngineData>("UpdateDelta")
.kind(flecs::OnUpdate)
.each([](EngineData &eng) {
@@ -73,6 +77,7 @@ void setupExteriorScene(Ogre::SceneManager *scnMgr, Ogre::SceneNode *cameraNode,
});
ecs.system<EngineData>("UpdateDelay")
.kind(flecs::OnUpdate)
.with<GameState>()
.with<TerrainReady>()
.with<WaterReady>()
.with<GroundCheckReady>()
@@ -101,17 +106,22 @@ void setupExteriorScene(Ogre::SceneManager *scnMgr, Ogre::SceneNode *cameraNode,
ecs.set<Camera>({ cameraNode, camera, false });
ecs.add<GameData>();
ecs.add<Input>();
ecs.add<WaterSurface>();
ecs.set<Sun>({ nullptr, nullptr, nullptr, nullptr });
ecs.set<Terrain>({ nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
false,
{ 0, 0, 0 } });
ecs.observer<GameState>("Game_Start_Scen_Startup")
.event(flecs::OnAdd)
.each([](GameState &game) {
ECS::get().add<WaterSurface>();
ECS::get().set<Sun>(
{ nullptr, nullptr, nullptr, nullptr });
ECS::get().set<Terrain>({ nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
false,
{ 0, 0, 0 } });
});
if (!ecs.has<LuaBase>())
ecs.add<LuaBase>();
if (!ecs.has<LuaEvent>())
@@ -119,49 +129,69 @@ void setupExteriorScene(Ogre::SceneManager *scnMgr, Ogre::SceneNode *cameraNode,
// ecs.set<Body2Entity>({});
std::cout << "Setup GameData done\n";
ecs.system("SpawnPlayer").kind(flecs::OnUpdate).interval(0.5f).run([&](flecs::iter &it) {
flecs::entity player =
ECS::get<CharacterManagerModule>().getPlayer();
if (!player.is_valid()) {
/* Create player */
Ogre::Vector3 position;
JPH::BodyID id;
long x, y;
Ogre::TerrainGroup *tg =
ECS::get<ECS::Terrain>().mTerrainGroup;
if (tg->isDerivedDataUpdateInProgress())
return;
tg->convertWorldPositionToTerrainSlot(
Ogre::Vector3(0, 0, 4), &x, &y);
Ogre::Terrain *terrain = tg->getTerrain(x, y);
if (terrain && terrain->isLoaded()) {
if (PhysicsModule::raycastQuery(
Ogre::Vector3(0, 500, 4),
Ogre::Vector3(0, -500, 4), position,
id)) {
if (position.y < -10.0f &&
position.y > -50.0f) {
player =
ecs.get_mut<
CharacterManagerModule>()
.createPlayer(
{ position.x,
position.y,
position.z },
Ogre::Quaternion(
Ogre::Radian(
Ogre::Math::
PI),
Ogre::Vector3::
UNIT_Y));
std::cout << position
<< std::endl;
// OgreAssert(false, "spawn");
ecs.system<Terrain, GameState>("SpawnPlayer")
.kind(flecs::OnUpdate)
.interval(0.5f)
.each([&](Terrain &mterrain, GameState &game) {
flecs::entity player =
ECS::get<CharacterManagerModule>().getPlayer();
if (!player.is_valid()) {
/* Create player */
Ogre::Vector3 position;
JPH::BodyID id;
long x, y;
Ogre::TerrainGroup *tg = mterrain.mTerrainGroup;
if (tg->isDerivedDataUpdateInProgress())
return;
tg->convertWorldPositionToTerrainSlot(
Ogre::Vector3(0, 0, 4), &x, &y);
Ogre::Terrain *terrain = tg->getTerrain(x, y);
if (terrain && terrain->isLoaded()) {
if (PhysicsModule::raycastQuery(
Ogre::Vector3(0, 500, 4),
Ogre::Vector3(0, -500, 4),
position, id)) {
if (position.y < -10.0f &&
position.y > -50.0f) {
player =
ecs.get_mut<
CharacterManagerModule>()
.createPlayer(
{ position.x,
position.y,
position.z },
Ogre::Quaternion(
Ogre::Radian(
Ogre::Math::
PI),
Ogre::Vector3::
UNIT_Y));
std::cout << position
<< std::endl;
}
}
}
}
}
});
}
});
// FIXME: convert this later to scene setup event
static flecs::entity new_game_run =
ecs.system<GameState>("NewGame")
.kind(flecs::OnUpdate)
.interval(0.5f)
.each([&](GameState &game) {
flecs::entity player =
ECS::get<CharacterManagerModule>()
.getPlayer();
if (player.is_valid() &&
player.has<CharacterBase>() &&
player.has<Character>() &&
player.has<Player>()) {
ECS::get<LuaBase>().mLua->call_handler(
"new_game");
new_game_run.destruct();
}
});
std::cout << "scene setup done" << std::endl;
}
void setupInteriorScene(Ogre::SceneManager *scnMgr, Ogre::SceneNode *cameraNode,
Ogre::Camera *camera, Ogre::RenderWindow *window)
@@ -250,8 +280,6 @@ void setupEditor(Ogre::SceneManager *scnMgr, Ogre::SceneNode *cameraNode,
ecs.modified<GUI>();
ecs.get_mut<GUI>().setWindowGrab(true);
ecs.modified<GUI>();
ecs.get_mut<GUI>().enabled = true;
ecs.modified<GUI>();
}
void update(float delta)

View File

@@ -1,9 +1,9 @@
#ifndef GAMEDATA_H
#define GAMEDATA_H
#include <Ogre.h>
#include <flecs.h>
namespace ECS
{
extern flecs::entity player;
void setup_minimal();
void setupExteriorScene(Ogre::SceneManager *scnMgr, Ogre::SceneNode *cameraNode,
Ogre::Camera *camera, Ogre::RenderWindow *window);

View File

@@ -12,6 +12,7 @@
#include "SlotsModule.h"
#include "world-build.h"
#include "PlayerActionModule.h"
#include "QuestModule.h"
#include "LuaData.h"
#include "lua.hpp"
extern "C" {
@@ -123,7 +124,7 @@ int LuaData::call_handler(const Ogre::String &event)
lua_pushstring(L, event.c_str());
lua_pushinteger(L, -1);
lua_pushinteger(L, -1);
if (lua_pcall(L, 3, 0, 0)) {
if (lua_pcall(L, 3, 0, 0) != LUA_OK) {
Ogre::LogManager::getSingleton().stream()
<< lua_tostring(L, -1);
OgreAssert(false, "Lua error");
@@ -141,7 +142,7 @@ int LuaData::call_handler(const Ogre::String &event, flecs::entity e,
lua_pushstring(L, event.c_str());
lua_pushinteger(L, idmap.add_entity(e));
lua_pushinteger(L, idmap.add_entity(o));
if (lua_pcall(L, 3, 0, 0)) {
if (lua_pcall(L, 3, 0, 0) != LUA_OK) {
Ogre::LogManager::getSingleton().stream()
<< lua_tostring(L, -1);
OgreAssert(false, "Lua error");
@@ -315,6 +316,14 @@ LuaData::LuaData()
return 0;
});
lua_setglobal(L, "setup_action_handler");
lua_pushcfunction(L, [](lua_State *L) -> int {
luaL_checktype(L, 1, LUA_TSTRING);
luaL_checktype(L, 2, LUA_TTABLE);
ECS::get_mut<QuestModule>().addLuaQuest(L);
ECS::modified<QuestModule>();
return 0;
});
lua_setglobal(L, "add_quest");
lua_pushcfunction(L, [](lua_State *L) -> int {
// FIXME
return 0;

View File

@@ -70,19 +70,6 @@ PhysicsModule::PhysicsModule(flecs::world &ecs)
c.mCameraNode);
ECS::modified<Physics>();
});
#if 0
ecs.system<PhysicsBody>("create_body")
.kind(flecs::OnUpdate)
.without<JPH::BodyID>()
.each([&](flecs::entity e, PhysicsBody &rb) {
JPH::BodyID id =
JoltPhysicsWrapper::getSingleton().createBody(
rb.shape.get(), rb.node,
(JPH::EMotionType)rb.motion,
(JPH::ObjectLayer)rb.layer);
e.set<JPH::BodyID>(id);
});
#endif
ecs.system<EngineData, Physics>("physics_update")
.kind(PhysicsUpdate)
.each([&](EngineData &e, Physics &ph) {
@@ -285,57 +272,10 @@ PhysicsModule::PhysicsModule(flecs::world &ecs)
"_exit",
trigger_e,
other_e);
#if 0
/* do not delete triggers until exit from actuator */
other_e.remove<InTrigger>(
trigger_e);
trigger_e.remove<TriggeredBy>(
other_e);
#endif
}
ECS::modified<LuaEvent>();
});
});
#if 0
ecs.system<const EngineData, const EventTrigger, const JPH::BodyID>(
"UpdateTriggerPhysicsPre")
.kind(PhysicsPreUpdate)
.with<TerrainReady>()
.with<WaterReady>()
.with<WaterBody>()
.each([](flecs::entity e, const EngineData &eng,
const EventTrigger &trigger, const JPH::BodyID &id) {
/* FIXME: update positions for triggers, probably need to move somewhere */
JoltPhysicsWrapper::getSingleton()
.setPositionAndRotation(
id, trigger.node->_getDerivedPosition(),
trigger.node->_getDerivedOrientation());
#if 0
std::cout << trigger.node->_getDerivedPosition() << " "
<< trigger.node->getPosition() << " "
<< trigger.node->getParent()->getName()
<< ": " << trigger.node->getName()
<< std::endl;
// OgreAssert(false, "update triggers");
#endif
});
ecs.system<const EngineData, const EventTrigger, const JPH::BodyID>(
"UpdateTriggerPhysicsPost")
.kind(PhysicsPostUpdate)
.with<TerrainReady>()
.with<WaterReady>()
.with<WaterBody>()
.each([](flecs::entity e, const EngineData &eng,
const EventTrigger &trigger, const JPH::BodyID &id) {
/* FIXME: update positions for triggers, probably need to move somewhere */
Ogre::Vector3 position;
Ogre::Quaternion rotation;
JoltPhysicsWrapper::getSingleton()
.getPositionAndRotation(id, position, rotation);
trigger.node->_setDerivedPosition(position);
trigger.node->_setDerivedOrientation(rotation);
});
#endif
ecs.system<const EngineData>("init_water")
.kind(PhysicsPreUpdate)
.with<TerrainReady>()
@@ -344,16 +284,6 @@ PhysicsModule::PhysicsModule(flecs::world &ecs)
.each([this](const EngineData &eng) {
ECS::get().set<WaterBody>({});
});
#if 0
ecs.system<const EngineData>("DebugData")
.kind(PhysicsPostUpdate)
.each([this](const EngineData &eng) {
std::cout << "TerrainReady: "
<< ECS::get().has<TerrainReady>();
std::cout << " WaterReady: "
<< ECS::get().has<WaterReady>() << std::endl;
});
#endif
ecs.system<const EngineData, WaterBody>("update_water")
.kind(PhysicsPostUpdate)
.with<TerrainReady>()
@@ -365,57 +295,6 @@ PhysicsModule::PhysicsModule(flecs::world &ecs)
eng.delta,
water.mWaterNode->_getDerivedPosition(),
body.inWater);
#if 0
for (JPH::BodyID inBodyID : body.inWater) {
if (JoltPhysicsWrapper::getSingleton().isActive(
inBodyID)) {
float my =
JoltPhysicsWrapper::getSingleton()
.getPosition(inBodyID)
.y;
float myv =
JoltPhysicsWrapper::getSingleton()
.getLinearVelocity(
inBodyID)
.y;
float b = 1.0f;
float drag = 0.5f;
float adrag = 0.05f;
float level = -1.3f;
float mdec = 1.0f;
float minc = 1.0f;
float h = -my + level;
if (h < 0)
h = 0;
if (my < level - 0.1f)
b *= 1.1f * (1.0f + 1.2f * h);
else if (my > level + 0.1f) {
b *= 0.8f;
if (myv > 0.1f)
b *= 0.9f;
if (my > level + 0.4f)
b *= 0.5f;
}
if (myv < 0.0f)
drag = 0.7f;
JoltPhysicsWrapper::getSingleton().applyBuoyancyImpulse(
inBodyID,
water.mWaterNode->_getDerivedPosition() -
Ogre::Vector3(0, 0.1f,
0),
Ogre::Vector3::UNIT_Y, b, drag,
adrag, Ogre::Vector3::ZERO,
eng.delta);
// std::cout << b << std::endl;
#if 0
std::cout << "addHit: "
<< JoltPhysics::convert(
body.GetPosition())
<< std::endl;
#endif
}
}
#endif
ECS::get().add<WaterReady>();
});
ecs.system<const JPH::BodyID, const WaterBody>("update_water_status1")
@@ -507,12 +386,6 @@ PhysicsModule::PhysicsModule(flecs::world &ecs)
float myv = JoltPhysicsWrapper::getSingleton()
.getLinearVelocity(id)
.y;
#if 0
if (my < level && myv < 0)
b = 10.0f;
else if (my > level && myv > 0)
b = 0.8f;
#endif
/* max = 2.7; min = 1.7 */
if (my < level)
b = 2.2f;
@@ -601,13 +474,6 @@ PhysicsModule::PhysicsModule(flecs::world &ecs)
Ogre::Vector3::UNIT_Y, b, drag,
adrag, Ogre::Vector3::ZERO,
eng.delta);
// std::cout << b << std::endl;
#if 0
std::cout << "addHit: "
<< JoltPhysics::convert(
body.GetPosition())
<< std::endl;
#endif
}
});
ecs.system<const EngineData, const CharacterBody>("UpdatePhysics")

View File

@@ -0,0 +1,326 @@
#include <iostream>
#include "Components.h"
#include "GameData.h"
#include "GUIModuleCommon.h"
#include "QuestModule.h"
namespace ECS
{
QuestModule::QuestModule(flecs::world &ecs)
{
ecs.module<QuestModule>();
ecs.observer<GameState>("EnableQuests")
.event(flecs::OnAdd)
.each([this](GameState &game) {
quest_update =
ECS::get()
.system<const EngineData>(
"UpdateQuests")
.each([this](const EngineData &eng) {
for (auto &quest : quests) {
if (!quest->_is_complete())
quest->_update(
eng.delta);
}
});
});
ecs.observer<GameState>("DisableQuests")
.event(flecs::OnRemove)
.each([this](GameState &game) {
if (quest_update.is_valid())
quest_update.destruct();
});
}
void QuestModule::addQuest(Quest *quest)
{
quests.insert(quest);
}
void QuestModule::removeQuest(Quest *quest)
{
quests.erase(quests.find(quest));
}
struct LuaNarrationHandler : GUI::NarrationHandler {
int ref;
lua_State *L;
LuaNarrationHandler(lua_State *L, int ref)
: ref(ref)
, L(L)
{
lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
lua_pushlightuserdata(L, this);
lua_pushcclosure(
L,
[](lua_State *L) {
luaL_checktype(L, 1, LUA_TSTRING);
luaL_checktype(L, 2, LUA_TTABLE);
LuaNarrationHandler *handler =
static_cast<LuaNarrationHandler *>(
lua_touserdata(
L,
lua_upvalueindex(1)));
Ogre::String event = lua_tostring(L, 1);
std::vector<Ogre::String> choices;
int choicesLen = (int)lua_rawlen(L, 2);
choices.reserve(choicesLen);
for (int i = 1; i <= choicesLen; ++i) {
lua_rawgeti(L, 2, i);
if (lua_isstring(L, -1))
choices.push_back(
lua_tostring(L, -1));
lua_pop(L, 1);
}
handler->_narration(event, choices);
return 0;
},
1);
lua_setfield(L, -2, "_narration");
lua_pushlightuserdata(L, this);
lua_pushcclosure(
L,
[](lua_State *L) {
LuaNarrationHandler *handler =
static_cast<LuaNarrationHandler *>(
lua_touserdata(
L,
lua_upvalueindex(1)));
handler->_finish();
return 0;
},
1);
lua_setfield(L, -2, "_finish");
lua_pushlightuserdata(L, this);
lua_pushcclosure(
L,
[](lua_State *L) {
LuaNarrationHandler *handler =
static_cast<LuaNarrationHandler *>(
lua_touserdata(
L,
lua_upvalueindex(1)));
int answer = handler->getNarrationAnswer();
lua_pushinteger(L, answer);
return 1;
},
1);
lua_setfield(L, -2, "_get_narration_answer");
lua_pushlightuserdata(L, this);
lua_pushcclosure(
L,
[](lua_State *L) {
LuaNarrationHandler *handler =
static_cast<LuaNarrationHandler *>(
lua_touserdata(
L,
lua_upvalueindex(1)));
lua_pushstring(
L, handler->getProperties().c_str());
return 1;
},
1);
lua_setfield(L, -2, "_get_properties");
lua_pop(L, 1);
}
void finish() override
{
lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
int type = lua_getfield(L, -1, "finish");
OgreAssert(type == LUA_TFUNCTION, "bad finish()");
lua_insert(L, -2);
if (lua_pcall(L, 1, 0, 0) != 0) {
std::cerr << lua_tostring(L, -1) << std::endl;
OgreAssert(false, "lua error");
lua_pop(L, 1);
}
_clear_narration();
}
void activate() override
{
lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
int type = lua_getfield(L, -1, "activate");
OgreAssert(type == LUA_TFUNCTION, "bad activate()");
lua_insert(L, -2);
if (lua_pcall(L, 1, 0, 0) != 0) {
std::cerr << lua_tostring(L, -1) << std::endl;
OgreAssert(false, "lua error");
lua_pop(L, 1);
}
}
void event(const Ogre::String &evt) override
{
lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
int type = lua_getfield(L, -1, "event");
OgreAssert(type == LUA_TFUNCTION, "bad event()");
lua_insert(L, -2);
lua_pushstring(L, evt.c_str());
if (lua_pcall(L, 2, 0, 0) != 0) {
std::cerr << lua_tostring(L, -1) << std::endl;
OgreAssert(false, "lua error");
lua_pop(L, 1);
}
}
};
struct LuaQuest : QuestModule::Quest {
lua_State *L;
int ref;
LuaQuest(const std::string &name, lua_State *L, int ref)
: Quest(name)
, L(L)
, ref(ref)
{
OgreAssert(L, "bad Lua state");
lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
lua_pushlightuserdata(L, this);
lua_pushcclosure(
L,
[](lua_State *L) {
LuaQuest *_this = static_cast<LuaQuest *>(
lua_touserdata(L, lua_upvalueindex(1)));
_this->_finish(LuaQuest::OK);
return 0;
},
1);
lua_setfield(L, -2, "_finish");
lua_pushlightuserdata(L, this);
lua_pushcclosure(
L,
[](lua_State *L) {
LuaQuest *_this = static_cast<LuaQuest *>(
lua_touserdata(L, lua_upvalueindex(1)));
luaL_checktype(L, 1, LUA_TTABLE);
lua_pushvalue(L, 1);
int nref = luaL_ref(L, LUA_REGISTRYINDEX);
LuaNarrationHandler *handle =
OGRE_NEW LuaNarrationHandler(L, nref);
ECS::get_mut<GUI>().addNarrationHandler(handle);
ECS::modified<GUI>();
return 0;
},
1);
lua_setfield(L, -2, "_add_narration");
lua_pop(L, 1);
}
void finish(int rc) override
{
lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
int type = lua_getfield(L, -1, "finish");
OgreAssert(type == LUA_TFUNCTION, "bad finish()");
lua_insert(L, -2);
lua_pushinteger(L, rc);
if (lua_pcall(L, 2, 0, 0) != 0) {
std::cerr << lua_tostring(L, -1) << std::endl;
OgreAssert(false, "lua error");
lua_pop(L, 1);
}
}
void activate() override
{
lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
int type = lua_getfield(L, -1, "activate");
OgreAssert(type == LUA_TFUNCTION, "bad activate()");
lua_insert(L, -2);
if (lua_pcall(L, 1, 0, 0) != 0) {
std::cerr << lua_tostring(L, -1) << std::endl;
OgreAssert(false, "lua error");
lua_pop(L, 1);
}
}
void event(const Ogre::String &evt) override
{
lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
int type = lua_getfield(L, -1, "event");
OgreAssert(type == LUA_TFUNCTION, "bad event()");
lua_insert(L, -2);
lua_pushstring(L, evt.c_str());
if (lua_pcall(L, 2, 0, 0) != 0) {
std::cerr << lua_tostring(L, -1) << std::endl;
OgreAssert(false, "lua error");
lua_pop(L, 1);
}
}
int update(float delta) override
{
lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
int type = lua_getfield(L, -1, "finish");
OgreAssert(type == LUA_TFUNCTION, "bad finish()");
lua_insert(L, -2);
if (lua_pcall(L, 1, 1, 0) != LUA_OK) {
std::cerr << lua_tostring(L, -1) << std::endl;
OgreAssert(false, "lua error");
lua_pop(L, 1);
} else {
int ret = lua_tointeger(L, -1);
lua_pop(L, 1);
return ret;
}
return ERROR;
}
};
void QuestModule::addLuaQuest(lua_State *L)
{
luaL_checktype(L, 1, LUA_TSTRING);
luaL_checktype(L, 2, LUA_TTABLE);
Ogre::String name = lua_tostring(L, 1);
lua_pushvalue(L, 2);
int ref = luaL_ref(L, LUA_REGISTRYINDEX);
struct LuaQuest *quest = OGRE_NEW LuaQuest(name, L, ref);
addQuest(quest);
}
void QuestModule::Quest::_activate()
{
activate();
active = true;
}
void QuestModule::Quest::_finish(int rc)
{
finish(rc);
active = false;
if (rc == OK)
complete = true;
}
QuestModule::Quest::Quest(const std::string &name)
: name(name)
, active(false)
, complete(false)
, updatePeriod(1)
, timeAcc(0)
{
}
QuestModule::Quest::~Quest()
{
}
int QuestModule::Quest::_update(float delta)
{
if (!active && !_can_activate())
return ERROR;
if (!active)
_activate();
timeAcc += delta;
if (timeAcc > updatePeriod)
timeAcc = 0;
else
return BUSY;
int ret = update(delta);
if (ret == BUSY)
return ret;
_finish(ret);
return ret;
}
void QuestModule::Quest::_event(const std::string &evt)
{
event(evt);
}
} // namespace ECS

View File

@@ -0,0 +1,47 @@
#ifndef ECS_QUESTMODULE_H
#define ECS_QUESTMODULE_H
#include <flecs.h>
#include <Ogre.h>
#include <lua.hpp>
namespace ECS {
struct QuestModule
{
struct Quest {
enum {OK = 0, BUSY, ERROR};
private:
virtual void activate() = 0;
virtual int update(float delta) = 0;
virtual void finish(int ret) = 0;
virtual void event(const std::string &evt) = 0;
virtual bool _can_activate() {return true;};
protected:
std::string name;
bool active;
bool complete;
float updatePeriod;
float timeAcc;
void _activate();
void _finish(int ret);
public:
Quest(const std::string &name);
virtual ~Quest();
int _update(float delta);
void _event(const std::string &evt);
bool _is_active() {return active;}
bool _is_complete() {return complete;}
};
std::set<Quest *> quests;
flecs::entity quest_update;
public:
QuestModule(flecs::world &ecs);
void addQuest(Quest *quest);
void removeQuest(Quest *quest);
void addLuaQuest(lua_State *L);
};
} // namespace ECS
#endif // ECS_QUESTMODULE_H

View File

@@ -84,6 +84,8 @@ StaticGeometryModule::StaticGeometryModule(flecs::world &ecs)
new Ogre::MeshLodGenerator();
ecs.system("AddGeometryQueue").kind(flecs::OnUpdate).run([&](flecs::iter &it) {
std::list<flecs::entity> items;
if (!ECS::get().has<Terrain>())
return;
if (!ECS::get<Terrain>().mTerrainGroup)
return;
if (!itemsLoaded) {

View File

@@ -572,6 +572,8 @@ public:
, job_system(JPH::cMaxPhysicsJobs, JPH::cMaxPhysicsBarriers,
std::thread::hardware_concurrency() - 1)
, mDebugRenderer(new DebugRenderer(scnMgr, cameraNode))
, object_vs_broadphase_layer_filter{}
, object_vs_object_layer_filter{}
, debugDraw(false)
{
// Create a factory, this class is responsible for creating instances of classes based on their name or hash and is mainly used for deserialization of saved data.
@@ -585,7 +587,7 @@ public:
// This is the max amount of rigid bodies that you can add to the physics system. If you try to add more you'll get an error.
// Note: This value is low because this is a simple test. For a real project use something in the order of 65536.
const uint cMaxBodies = 1024;
const uint cMaxBodies = 65536;
// This determines how many mutexes to allocate to protect rigid bodies from concurrent access. Set it to 0 for the default settings.
const uint cNumBodyMutexes = 0;
@@ -594,12 +596,12 @@ public:
// body pairs based on their bounding boxes and will insert them into a queue for the narrowphase). If you make this buffer
// too small the queue will fill up and the broad phase jobs will start to do narrow phase work. This is slightly less efficient.
// Note: This value is low because this is a simple test. For a real project use something in the order of 65536.
const uint cMaxBodyPairs = 1024;
const uint cMaxBodyPairs = 65536;
// This is the maximum size of the contact constraint buffer. If more contacts (collisions between bodies) are detected than this
// number then these contacts will be ignored and bodies will start interpenetrating / fall through the world.
// Note: This value is low because this is a simple test. For a real project use something in the order of 10240.
const uint cMaxContactConstraints = 1024;
const uint cMaxContactConstraints = 10240;
// Now we can create the actual physics system.
physics_system.Init(cMaxBodies, cNumBodyMutexes, cMaxBodyPairs,

View File

@@ -2,6 +2,7 @@ project(tests)
find_package(pugixml CONFIG)
find_package(assimp REQUIRED CONFIG)
find_package(OGRE REQUIRED COMPONENTS Bites Bullet Paging Terrain CONFIG)
find_package(RecastNavigation REQUIRED)
add_executable(check_uv check_uv.cpp)
target_link_libraries(check_uv ${ASSIMP_LIBRARIES})
@@ -9,4 +10,8 @@ add_executable(ogre_check_uv ogre_check_uv.cpp)
target_link_libraries(ogre_check_uv OgreBites OgreMain)
add_executable(check_scene_loader check_scene_loader.cpp)
target_link_libraries(check_scene_loader OgreBites OgreMain)
add_executable(check_nav nav.cpp)
#target_link_libraries(check_nav recastnavigation::Recast recastnavigation::Detour recastnavigation::DetourTileCache)
target_link_libraries(check_nav RecastNavigation::Recast RecastNavigation::Detour RecastNavigation::DetourTileCache)
#target_link_libraries(check_nav Recast Detour DetourTileCache)

5
src/tests/nav.cpp Normal file
View File

@@ -0,0 +1,5 @@
int main()
{
return 0;
}