Compare commits

...

39 Commits

Author SHA1 Message Date
b99474a66b Fixed uninitialized variable 2026-02-08 23:29:37 +03:00
3ffda0f2d5 Fixed character creation missing components 2026-02-08 19:31:09 +03:00
56436ea652 Better protect allocated Ogre character data; proper blend path setting 2026-02-08 18:17:48 +03:00
afc5ab02a3 Added GLB size checks 2026-02-08 15:33:01 +03:00
71a560ab3f Better way to import vrms; fix physics in editor 2026-02-08 14:36:45 +03:00
d139e77969 Continued working on GOAP action executor 2026-02-07 17:03:12 +03:00
685b15933a Physics initialization update 2026-02-07 08:31:07 +03:00
a54b042e49 Starting to implement GOAP plan 2026-02-06 22:14:48 +03:00
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
4d47125ea9 Fix addon 2026-02-03 08:03:26 +03:00
1d09728bc0 add blender2ogre addon 2026-02-03 07:20:49 +03:00
d2d7d36655 Install VRM addon for Blender 3.6 2026-02-03 05:05:11 +03:00
4da4b0d529 Fixed file paths 2026-02-03 03:33:16 +03:00
8de2de63ad Updated planner code 2026-02-03 01:46:13 +03:00
c854605a96 Forgot to add file 2026-02-03 00:27:16 +03:00
d2c7e92ce4 Update character logic 2026-02-03 00:25:41 +03:00
f22a374e11 Remove luaaa dependency 2026-02-01 23:34:31 +03:00
69035351a6 Action nodes handling 2026-02-01 23:25:38 +03:00
e6efd89bb0 Action nodes work better now 2026-02-01 00:13:33 +03:00
201aa92fe7 Helper script for action node editing 2026-01-31 04:37:38 +03:00
c860152f9b Using threads more 2026-01-30 01:04:23 +03:00
da4c1fee0e Threads and tasks 2026-01-29 15:28:50 +03:00
4cf0ea5321 Added json.lua 2026-01-23 21:31:15 +03:00
8320b14358 Started working on dialogue 2026-01-23 21:28:25 +03:00
1d358d206e Much better narration/dialogue/action handling (and for lua too) 2026-01-23 02:42:29 +03:00
cd91174f5d Better narration processing 2026-01-22 17:15:19 +03:00
4b24d85123 Action nodes support and lots of other updates 2026-01-19 00:07:03 +03:00
f86e7fd96c Furniture placement 2026-01-09 22:29:26 +03:00
272e202774 Now can create roofs from lua script 2026-01-08 00:16:46 +03:00
80fba23cd2 Can create roofs now 2026-01-07 01:35:08 +03:00
c031056e17 house generation almost complete 2026-01-02 18:08:20 +03:00
49fc547295 Building interior walls created with editor 2026-01-02 01:41:58 +03:00
d6d61229f8 Started town procedural generation code 2025-12-28 06:23:29 +03:00
5bb529bc31 Refactoring: split items 2025-12-27 16:05:32 +03:00
d3c93c5c18 Consistent startup 2025-12-19 06:09:07 +03:00
9bb9e2c09b Generating path from shore to pier 2025-12-15 01:41:44 +03:00
3f99099919 Creating pier 2025-12-14 06:59:12 +03:00
119 changed files with 31315 additions and 2981 deletions

1
.gitattributes vendored
View File

@@ -1,4 +1,5 @@
*.blend filter=lfs diff=lfs merge=lfs -text
*.zip filter=lfs diff=lfs merge=lfs -text
*.kra filter=lfs diff=lfs merge=lfs -text
*.vrm filter=lfs diff=lfs merge=lfs -text
*.vroid filter=lfs diff=lfs merge=lfs -text

View File

@@ -11,11 +11,6 @@ set(CREATE_DIRECTORIES
${CMAKE_BINARY_DIR}/characters/shapes/female/chibi
${CMAKE_BINARY_DIR}/characters/male
${CMAKE_BINARY_DIR}/characters/female)
#set(CREATE_SCENES
# ${CMAKE_SOURCE_DIR}/characters/female/vroid-normal-female.scene
# ${CMAKE_SOURCE_DIR}/characters/male/vroid-normal-male.scene
# )
# workaround horribly broken assimp cmake, fixed with assimp 5.1
#add_library(assimp INTERFACE IMPORTED)
#set_target_properties(assimp PROPERTIES
@@ -76,6 +71,10 @@ 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)
add_executable(Game Game.cpp ${WATER_SRC})
target_include_directories(Game PRIVATE src/gamedata)
@@ -84,6 +83,7 @@ target_link_libraries(Game OgreBites OgrePaging OgreTerrain OgreMeshLodGenerator
GameData
sound
sceneloader physics
OgreCrowd
flecs::flecs_static
-Wl,--as-needed
)
@@ -202,34 +202,8 @@ add_custom_target(stage_stories ALL DEPENDS stage_lua_scripts ${CMAKE_BINARY_DIR
add_custom_command(
OUTPUT ${CMAKE_BINARY_DIR}/resources.cfg
COMMAND cp ${CMAKE_SOURCE_DIR}/resources.cfg ${CMAKE_BINARY_DIR}/resources.cfg
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/resources/main
${CMAKE_BINARY_DIR}/resources/main
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/resources/shaderlib
${CMAKE_BINARY_DIR}/resources/shaderlib
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/resources/terrain
${CMAKE_BINARY_DIR}/resources/terrain
# COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/lua-scripts
# ${CMAKE_BINARY_DIR}/lua-scripts
# COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/skybox
# ${CMAKE_BINARY_DIR}/skybox
# COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/resources/debug
# ${CMAKE_BINARY_DIR}/resources/debug
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/resources.cfg ${CMAKE_BINARY_DIR}/resources.cfg
DEPENDS ${CMAKE_SOURCE_DIR}/resources.cfg)
add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/resources/terrain/world_map.png
COMMAND unzip -o ${CMAKE_SOURCE_DIR}/world_map.kra mergedimage.png -d ${CMAKE_BINARY_DIR}/world_map
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_BINARY_DIR}/world_map/mergedimage.png
${CMAKE_BINARY_DIR}/resources/terrain/world_map.png
COMMAND ${CMAKE_COMMAND} -E remove_directory ${CMAKE_BINARY_DIR}/world_map
DEPENDS ${CMAKE_SOURCE_DIR}/world_map.kra)
add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/resources/terrain/brushes.png
COMMAND unzip -o ${CMAKE_SOURCE_DIR}/brushes.kra mergedimage.png -d ${CMAKE_BINARY_DIR}/brushes
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_BINARY_DIR}/brushes/mergedimage.png
${CMAKE_BINARY_DIR}/resources/terrain/brushes.png
COMMAND ${CMAKE_COMMAND} -E remove_directory ${CMAKE_BINARY_DIR}/brushes
DEPENDS ${CMAKE_SOURCE_DIR}/brushes.kra)
set(SKYBOX_SRC
early_morning_bk.jpg
@@ -279,110 +253,14 @@ foreach(MATERIAL_FILE ${MATERIAL_FILES})
list(APPEND MATERIALS_OUTPUT ${OUTPUT_FILE})
endforeach()
file(GLOB VRM_FILES RELATIVE ${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/assets/vroid/*.vrm)
file(GLOB MIXAMO_FILES RELATIVE ${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/assets/blender/mixamo/**/*.fbx)
set(VRM_SOURCE)
foreach(VRM_FILE ${VRM_FILES} ${MIXAMO_FILES})
set(OUTPUT_FILE "${CMAKE_BINARY_DIR}/${VRM_FILE}")
set(INPUT_FILE "${CMAKE_SOURCE_DIR}/${VRM_FILE}")
add_custom_command(OUTPUT "${OUTPUT_FILE}"
COMMAND ${CMAKE_COMMAND} -E copy "${INPUT_FILE}" "${OUTPUT_FILE}"
DEPENDS "${INPUT_FILE}" VERBATIM)
list(APPEND VRM_SOURCE "${OUTPUT_FILE}")
endforeach()
set(VRM_IMPORTED_BLENDS
${CMAKE_BINARY_DIR}/assets/blender/vrm-vroid-normal-female.blend
${CMAKE_BINARY_DIR}/assets/blender/vrm-vroid-normal-male.blend
${CMAKE_BINARY_DIR}/assets/blender/shapes/male/vrm-vroid-normal-male-chibi.blend
)
add_custom_command(OUTPUT ${VRM_IMPORTED_BLENDS}
COMMAND ${CMAKE_COMMAND} -E make_directory ${CREATE_DIRECTORIES}
COMMAND ${BLENDER} -b -Y -P ${CMAKE_SOURCE_DIR}/assets/blender/scripts/import_vrm.py
COMMAND ${CMAKE_COMMAND} -E touch_nocreate ${VRM_IMPORTED_BLENDS}
DEPENDS ${CMAKE_SOURCE_DIR}/assets/blender/scripts/import_vrm.py
${VRM_SOURCE}
WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
set(EDITED_BLENDS_LIST
male
female
)
set(EDITED_BLEND_TARGETS)
set(CHARACTER_GLBS)
foreach(EDITED_BLEND ${EDITED_BLENDS_LIST})
add_custom_command(
OUTPUT ${CMAKE_BINARY_DIR}/assets/blender/edited-normal-${EDITED_BLEND}.blend
DEPENDS ${CMAKE_SOURCE_DIR}/assets/blender/edited-normal-${EDITED_BLEND}.blend
${CMAKE_BINARY_DIR}/assets/blender/vrm-vroid-normal-${EDITED_BLEND}.blend
COMMAND ${CMAKE_COMMAND}
-E copy ${CMAKE_SOURCE_DIR}/assets/blender/edited-normal-${EDITED_BLEND}.blend
${CMAKE_BINARY_DIR}/assets/blender/edited-normal-${EDITED_BLEND}.blend
COMMAND ${BLENDER} -b -Y
${CMAKE_BINARY_DIR}/assets/blender/edited-normal-${EDITED_BLEND}.blend
-P ${CMAKE_SOURCE_DIR}/assets/blender/scripts/copy_animations.py --
${CMAKE_BINARY_DIR}/assets/blender/vrm-vroid-normal-${EDITED_BLEND}.blend ${EDITED_BLEND}
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)
list(APPEND EDITED_BLEND_TARGETS ${CMAKE_BINARY_DIR}/assets/blender/edited-normal-${EDITED_BLEND}.blend)
list(APPEND CHARACTER_GLBS ${CMAKE_BINARY_DIR}/characters/${EDITED_BLEND}/normal-${EDITED_BLEND}.glb)
endforeach()
add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/characters/shapes/male/chibi/vroid-normal-male-chibi.glb
DEPENDS ${CMAKE_BINARY_DIR}/assets/blender/characters/shapes/male/chibi/vroid-normal-male-chibi.glb
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/assets/blender/characters/shapes/male/chibi/vroid-normal-male-chibi.glb
${CMAKE_BINARY_DIR}/characters/shapes/male/chibi/vroid-normal-male-chibi.glb)
add_custom_target(morph ALL DEPENDS MorphTargetsResearch ${CMAKE_BINARY_DIR}/characters/shapes/male/chibi/vroid-normal-male-chibi.glb)
set(COPY_BLENDS edited-shape-test-male.blend edited-normal-male-base.blend)
foreach (COPY_BLEND_FILE ${COPY_BLENDS})
add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/assets/blender/${COPY_BLEND_FILE}
DEPENDS ${CMAKE_SOURCE_DIR}/assets/blender/${COPY_BLEND_FILE}
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_SOURCE_DIR}/assets/blender/${COPY_BLEND_FILE}
${CMAKE_BINARY_DIR}/assets/blender/${COPY_BLEND_FILE}
)
list(APPEND EDITED_BLEND_TARGETS ${CMAKE_BINARY_DIR}/assets/blender/${COPY_BLEND_FILE})
endforeach()
add_custom_target(edited-blends ALL DEPENDS ${EDITED_BLEND_TARGETS})
add_custom_command(
OUTPUT ${CHARACTER_GLBS}
COMMAND ${CMAKE_COMMAND} -E make_directory ${CREATE_DIRECTORIES}
COMMAND ${BLENDER} -b -Y -P ${CMAKE_SOURCE_DIR}/assets/blender/scripts/export_models.py
COMMAND ${CMAKE_COMMAND} -E touch ${CHARACTER_GLBS}
DEPENDS ${CMAKE_SOURCE_DIR}/assets/blender/scripts/export_models.py ${VRM_IMPORTED_BLENDS} ${EDITED_BLEND_TARGETS}
WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
#add_custom_command(
# OUTPUT ${CMAKE_SOURCE_DIR}/characters/female/vroid-normal-female.scene
# ${CMAKE_SOURCE_DIR}/characters/male/vroid-normal-male.scene
# ${CMAKE_SOURCE_DIR}/assets/blender/vrm-vroid-normal-female.blend
# ${CMAKE_SOURCE_DIR}/assets/blender/vrm-vroid-normal-male.blend
# ${CMAKE_SOURCE_DIR}/assets/blender/shapes/male/vrm-vroid-normal-male-chibi.blend
# COMMAND mkdir -p ${CREATE_DIRECTORIES}
# COMMAND ${BLENDER} -b -Y -P ${CMAKE_SOURCE_DIR}/assets/blender/scripts/import_vrm.py
# COMMAND ${BLENDER} -b -Y -P ${CMAKE_SOURCE_DIR}/assets/blender/scripts/export_models.py
## COMMAND ${BLENDER} -b -Y -P ${CMAKE_SOURCE_DIR}/assets/blender/scripts/export_ogre_scene.py
# COMMAND echo rm -Rf ${CMAKE_SOURCE_DIR}/characters/male/*.material ${CMAKE_SOURCE_DIR}/characters/female/*.material
# COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/characters
# ${CMAKE_BINARY_DIR}/characters
# DEPENDS ${CMAKE_SOURCE_DIR}/assets/blender/scripts/export_models.py ${CMAKE_SOURCE_DIR}/assets/blender/scripts/import_vrm.py
# ${CMAKE_SOURCE_DIR}/assets/vroid/buch1.vrm
# ${CMAKE_SOURCE_DIR}/assets/vroid/buch1-chibi.vrm
# ${CMAKE_SOURCE_DIR}/assets/vroid/jane2-dress.vrm
# WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
add_custom_target(stage_files ALL DEPENDS ${CMAKE_BINARY_DIR}/resources.cfg ${MATERIALS_OUTPUT}
${CMAKE_BINARY_DIR}/resources/terrain/world_map.png
${CMAKE_BINARY_DIR}/resources/terrain/brushes.png
edited-blends)
edited-blends stage_resources)
add_custom_target(remove_scenes COMMAND rm -f ${VRM_SOURCE} ${VRM_IMPORTED_BLENDS} ${CHARACTER_GLBS})
add_custom_target(import_vrm DEPENDS ${CHARACTER_GLBS})
target_compile_definitions(Game PRIVATE FLECS_CPP_NO_AUTO_REGISTRATION)
target_compile_definitions(Game PRIVATE FLECS_CPP_NO_AUTO_REGISTRATION JPH_PROFILE_ENABLED)
install(TARGETS Game DESTINATION bin)
install(TARGETS Editor DESTINATION bin)

284
Game.cpp
View File

@@ -15,8 +15,11 @@
#include "Components.h"
#include "CharacterModule.h"
#include "TerrainModule.h"
#include "GUIModule.h"
#include "GUIModuleCommon.h"
#include "AppModule.h"
#include "GUIModule.h"
#include "PhysicsModule.h"
#include "physics.h"
#include "sound.h"
class App;
class SkyRenderer : public Ogre::SceneManager::Listener {
@@ -236,15 +239,14 @@ public:
else if (key == 'f')
control |= 64;
if (key == 'w' || key == 'a' || key == 's' || key == 'd' ||
key == 'e' || key == OgreBites::SDLK_LSHIFT)
key == 'e' || key == OgreBites::SDLK_LSHIFT || key == 'e' ||
key == 'f')
return true;
return false;
}
bool keyReleased(const OgreBites::KeyboardEvent &evt) override
{
OgreBites::Keycode key = evt.keysym.sym;
if (isGuiEnabled())
return false;
if (key == 'w')
control &= ~1;
else if (key == 'a')
@@ -255,8 +257,14 @@ public:
control &= ~8;
else if (key == OgreBites::SDLK_LSHIFT)
control &= ~16;
if (key == 'w' || key == 'a' || key == 's' || key == 'd' ||
key == OgreBites::SDLK_LSHIFT)
else if (key == 'e')
control &= ~32;
else if (key == 'f')
control &= ~64;
if (isGuiEnabled())
return false;
if (key == 'w' || key == 'a' || key == 's' || key == 'd' ||
key == OgreBites::SDLK_LSHIFT || key == 'e' || key == 'f')
return true;
return false;
}
@@ -295,9 +303,9 @@ public:
};
class App : public OgreBites::ApplicationContext {
Ogre::SceneNode *mCameraNode, *mCameraPivot, *mCameraGoal;
Ogre::Camera *mCamera;
Ogre::Camera *mCamera, *mCameraInterior, *mCameraInventory;
Ogre::Real mPivotPitch;
Ogre::SceneManager *mScnMgr;
Ogre::SceneManager *mScnMgr, *mScnMgrInterior, *mScnMgrInventory;
Ogre::Viewport *mViewport;
SkyBoxRenderer *sky;
bool mGrab;
@@ -313,15 +321,19 @@ public:
virtual ~App()
{
}
void setup()
void setup() override
{
OgreBites::ApplicationContext::setup();
Ogre::Root *root = getRoot();
Ogre::SceneManager *scnMgr = root->createSceneManager();
mScnMgr = scnMgr;
Ogre::OverlaySystem *pOverlaySystem = getOverlaySystem();
mScnMgrInterior = root->createSceneManager();
mScnMgrInventory = root->createSceneManager();
Ogre::OverlaySystem *pOverlaySystem = getOverlaySystem();
mScnMgr->addRenderQueueListener(pOverlaySystem);
// mTrayMgr = new OgreBites::TrayManager("AppTrays",
mScnMgrInterior->addRenderQueueListener(pOverlaySystem);
mScnMgrInventory->addRenderQueueListener(pOverlaySystem);
// mTrayMgr = new OgreBites::TrayManager("AppTrays",
// getRenderWindow());
}
bool isWindowGrab()
@@ -347,7 +359,32 @@ public:
mGrab = grab;
ApplicationContextBase::setWindowGrab(grab);
}
void initInteriorCamera()
{
Ogre::SceneNode *cameraNode =
mScnMgrInterior->getRootSceneNode()
->createChildSceneNode("InteriorCameraNode");
cameraNode->setPosition(0, 2, 3);
cameraNode->lookAt(Ogre::Vector3(0, 1, -1),
Ogre::Node::TS_PARENT);
mCameraInterior = mScnMgr->createCamera("interior_camera");
mCameraInterior->setNearClipDistance(0.05f);
mCameraInterior->setAutoAspectRatio(true);
cameraNode->attachObject(mCameraInterior);
}
void initInventoryCamera()
{
Ogre::SceneNode *cameraNode =
mScnMgrInventory->getRootSceneNode()
->createChildSceneNode("InteriorCameraNode");
cameraNode->setPosition(0, 2, 3);
cameraNode->lookAt(Ogre::Vector3(0, 1, -1),
Ogre::Node::TS_PARENT);
mCameraInventory = mScnMgr->createCamera("inventory_camera");
mCameraInventory->setNearClipDistance(0.05f);
mCameraInventory->setAutoAspectRatio(true);
cameraNode->attachObject(mCameraInventory);
}
void initCamera()
{
mCameraNode = mScnMgr->getRootSceneNode()->createChildSceneNode(
@@ -378,12 +415,16 @@ public:
mCamera->setNearClipDistance(0.1f);
mCamera->setFarClipDistance(800);
mPivotPitch = 0;
initInteriorCamera();
initInventoryCamera();
}
void configure()
{
std::cout << "Startup" << "\n";
std::cout << "Startup"
<< "\n";
initApp();
std::cout << "Set up RTSS" << "\n";
std::cout << "Set up RTSS"
<< "\n";
Ogre::Root *root = getRoot();
Ogre::SceneManager *scnMgr = getSceneManager();
@@ -392,24 +433,32 @@ public:
Ogre::RTShader::ShaderGenerator::getSingletonPtr();
shadergen->addSceneManager(scnMgr);
setWindowGrab(true);
std::cout << "Init camera" << "\n";
std::cout << "Init camera"
<< "\n";
initCamera();
std::cout << "Set up water" << "\n";
std::cout << "Set up cursor" << "\n";
std::cout << "Set up water"
<< "\n";
std::cout << "Set up cursor"
<< "\n";
Ogre::ResourceGroupManager::getSingleton()
.initialiseAllResourceGroups();
// OgreBites::ApplicationContext::loadResources();
// setupCursor();
std::cout << "Setup input" << "\n";
std::cout << "Setup input"
<< "\n";
setupInput();
std::cout << "Create content" << "\n";
std::cout << "Create content"
<< "\n";
createContent();
std::cout << "Setup done" << "\n";
std::cout << "Setup done"
<< "\n";
#if 0
mDbgDraw->setDebugMode(mDbgDraw->getDebugMode() |
btIDebugDraw::DBG_DrawContactPoints);
#endif
}
Ogre::LogManager::getSingleton().setMinLogLevel(
Ogre::LML_CRITICAL);
}
Ogre::SceneManager *getSceneManager()
{
return mScnMgr;
@@ -424,33 +473,28 @@ 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>())
return;
/* Update window grab */
ECS::GUI &gui = ECS::get().get_mut<ECS::GUI>();
if (gui.grabChanged) {
setWindowGrab(gui.grab);
gui.grabChanged = false;
ECS::get().modified<ECS::GUI>();
}
goto end;
{
/* Update window grab */
ECS::GUI &gui = ECS::get().get_mut<ECS::GUI>();
if (gui.grabChanged) {
setWindowGrab(gui.grab);
gui.grabChanged = false;
ECS::get().modified<ECS::GUI>();
std::cout << "updateWorld " << gui.grabChanged
<< " " << gui.grab << std::endl;
}
}
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;
@@ -553,6 +597,7 @@ public:
bool
mousePressed(const OgreBites::MouseButtonEvent &evt) override
{
std::cout << mListenerChain.size() << std::endl;
for (auto listner : mListenerChain) {
if (listner->mousePressed(evt))
return true;
@@ -577,15 +622,19 @@ public:
return false;
}
};
#endif
flecs::entity input_update;
flecs::entity find_wait_gui;
void setupInput()
{
}
JoltPhysicsWrapper *mJolt;
void createContent()
{
int i;
sky = new SkyBoxRenderer(getSceneManager());
mJolt = new JoltPhysicsWrapper(mScnMgr, mCameraNode);
sky = new SkyBoxRenderer(getSceneManager());
bool drawFirst = true;
uint8_t renderQueue = drawFirst ?
Ogre::RENDER_QUEUE_SKIES_EARLY :
@@ -601,107 +650,50 @@ public:
"Skybox/Dynamic", "General");
OgreAssert(m, "Sky box material not found.");
m->load();
ECS::setup(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().component<ECS::RenderWindow>().add(flecs::Singleton);
ECS::get().set<ECS::RenderWindow>(
{ getRenderWindow(), getDisplayDPI() });
ECS::setupExteriorScene(mScnMgr,
/*mDynWorld.get(), */ mCameraNode,
mCamera, getRenderWindow());
ECS::get().import <ECS::PhysicsModule>();
ECS::Physics &ph = ECS::get().ensure<ECS::Physics>();
ph.physics = mJolt;
ECS::modified<ECS::Physics>();
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)
@@ -734,12 +726,6 @@ public:
{
return mCamera;
}
flecs::entity getPlayer() const
{
flecs::entity player =
ECS::get().lookup("ECS::CharacterModule::player");
return player;
}
void enableDbgDraw(bool enable)
{
ECS::get_mut<ECS::EngineData>().enableDbgDraw = enable;

View File

@@ -0,0 +1,31 @@
project(building-parts)
set(PARTS_FILES pier.blend)
set(FURNITURE_FILES furniture.blend furniture-sofa.blend)
set(PARTS_OUTPUT_DIRS)
foreach(PARTS_FILE ${PARTS_FILES})
get_filename_component(FILE_NAME ${PARTS_FILE} NAME_WE)
set(PARTS_OUTPUT_DIR ${CMAKE_BINARY_DIR}/resources/buildings/parts/${FILE_NAME})
add_custom_command(
OUTPUT ${PARTS_OUTPUT_DIR}
COMMAND ${CMAKE_COMMAND} -E make_directory ${PARTS_OUTPUT_DIR}
COMMAND ${BLENDER} ${CMAKE_CURRENT_SOURCE_DIR}/${PARTS_FILE}
-b -Y -P
${CMAKE_SOURCE_DIR}/assets/blender/scripts/export_building_parts.py
-- ${PARTS_OUTPUT_DIR}
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${PARTS_FILE} ${CMAKE_SOURCE_DIR}/assets/blender/scripts/export_building_parts.py)
list(APPEND PARTS_OUTPUT_DIRS ${PARTS_OUTPUT_DIR})
endforeach()
foreach(FURNITURE_FILE ${FURNITURE_FILES})
get_filename_component(FILE_NAME ${FURNITURE_FILE} NAME_WE)
set(PARTS_OUTPUT_DIR ${CMAKE_BINARY_DIR}/resources/buildings/parts/${FILE_NAME})
add_custom_command(
OUTPUT ${PARTS_OUTPUT_DIR}
COMMAND ${CMAKE_COMMAND} -E make_directory ${PARTS_OUTPUT_DIR}
COMMAND ${BLENDER} ${CMAKE_CURRENT_SOURCE_DIR}/${FURNITURE_FILE}
-b -Y -P
${CMAKE_CURRENT_SOURCE_DIR}/export_furniture_parts.py
-- ${PARTS_OUTPUT_DIR}
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${PARTS_FILE} ${CMAKE_CURRENT_SOURCE_DIR}/export_furniture_parts.py)
list(APPEND PARTS_OUTPUT_DIRS ${PARTS_OUTPUT_DIR})
endforeach()
add_custom_target(import_building_parts ALL DEPENDS ${PARTS_OUTPUT_DIRS})

View File

@@ -0,0 +1,362 @@
import bpy
import mathutils
ACTION_PROPS = ["name", "action", "action_text", "height", "radius", "tags"]
FURNITURE_PROPS = ["name", "furniture_tags"]
test_tracking = {"lib_name": None}
class TEST_OT_LinkAndPlay(bpy.types.Operator):
bl_idname = "test.link_and_play"
bl_label = "Link and Play Sitting"
filepath: bpy.props.StringProperty()
rig_name: bpy.props.StringProperty()
action_name: bpy.props.StringProperty()
def execute(self, context):
target = context.active_object
path = bpy.path.abspath(self.filepath)
mesh_name = "Body"
with bpy.data.libraries.load(path, link=True) as (data_from, data_to):
# We request all three components
data_to.objects = [n for n in [self.rig_name, mesh_name] if n in data_from.objects]
data_to.actions = [self.action_name] if self.action_name in data_from.actions else []
# 2. Add to scene and create Override
rig_obj = None
for obj in data_to.objects:
offset = mathutils.Matrix.Translation((0, -0.1, 0))
bpy.context.collection.objects.link(obj)
obj.matrix_world = target.matrix_world @ offset
if obj.type == 'ARMATURE':
rig_obj = obj
if obj.library:
test_tracking["lib_name"] = obj.library.name
if rig_obj:
if not rig_obj.animation_data:
rig_obj.animation_data_create()
if data_to.actions:
rig_obj.animation_data.action = data_to.actions[0]
bpy.ops.screen.animation_play()
return {'FINISHED'}
class TEST_OT_Cleanup(bpy.types.Operator):
bl_idname = "test.cleanup"
bl_label = "Stop & Cleanup"
def execute(self, context):
if context.screen.is_animation_playing:
bpy.ops.screen.animation_cancel(restore_frame=True)
lib_name = test_tracking.get("lib_name")
if lib_name and lib_name in bpy.data.libraries:
lib = bpy.data.libraries[lib_name]
bpy.data.batch_remove(ids=(lib,))
test_tracking["lib_name"] = None
# Deep purge orphans (meshes, armatures, etc)
bpy.ops.outliner.orphans_purge(do_local_ids=True, do_linked_ids=True, do_recursive=True)
return {'FINISHED'}
class TEST_PT_ObjectPanel(bpy.types.Panel):
"""Only appears in Object Properties when a 'position-' Empty is selected"""
bl_label = "Character Placement Test"
bl_idname = "TEST_PT_object_panel"
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "object"
@classmethod
def poll(cls, context):
obj = context.active_object
return obj and obj.type == 'EMPTY' and obj.name.startswith("position-")
def draw(self, context):
layout = self.layout
layout.label(text="Test Controls:", icon='OUTLINER_OB_EMPTY')
# Link button with preset arguments
row = layout.row(align=True)
op = row.operator("test.link_and_play", text="Link & Sit", icon='PLAY')
op.filepath = "//../../edited-normal-male.blend" # SET YOUR FILEPATH
op.rig_name = "male" # SET YOUR RIG NAME
op.action_name = "sitting-chair" # SET YOUR ACTION NAME
# Cleanup button
layout.operator("test.cleanup", text="Stop & Clear", icon='TRASH')
class OBJECT_OT_CreateActionProps(bpy.types.Operator):
"""Initializes custom properties on the selected action- empty"""
bl_idname = "object.create_action_props"
bl_label = "Create Action"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
obj = context.object
for p in ACTION_PROPS:
if p not in obj:
if p in ["height"]:
obj[p] = 1.0
elif p in ["radius"]:
obj[p] = 0.5
elif p == "name":
obj[p] = obj.name.replace("action-","")
elif p == "action_text":
if "action" in obj:
obj[p] = obj["action"].capitalize()
else:
obj[p] = ""
else:
obj[p] = "" # Initialize as empty text string
return {'FINISHED'}
class OBJECT_OT_CreateFurnitureProps(bpy.types.Operator):
"""Initializes custom properties on the selected furniture- empty"""
bl_idname = "object.create_furniture_props"
bl_label = "Create Furniture"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
obj = context.object
for p in FURNITURE_PROPS:
if p not in obj:
if p == "name":
obj[p] = obj.name.replace("furniture-","")
elif p == "furniture_tags":
obj[p] = ""
otags = []
if obj.name.find("toilet") >= 0:
otags.append("wc")
otags.append("bathroom")
otags.append("toilet")
elif obj.name.find("bed") >= 0:
otags.append("bed")
otags.append("bedroom")
obj[p] = ", ".join(otags)
else:
obj[p] = "" # Initialize as empty text string
return {'FINISHED'}
class OBJECT_OT_CreateChildEmpties(bpy.types.Operator):
"""Creates two child empties"""
bl_idname = "object.create_child_empties"
bl_label = "Create Child Nodes"
bl_options = {'REGISTER', 'UNDO'}
target_val: bpy.props.StringProperty()
def execute(self, context):
parent = context.object
# Define offsets and their corresponding labels for the name
configs = []
configs_enter = [((0, 0.5, -parent.location.z), "enter")]
configs_exit = [((0, -0.5, -parent.location.z), "exit")]
if self.target_val == "enter":
configs = configs_enter
elif self.target_val == "exit":
configs = configs_exit
elif self.target_val == "all":
configs = configs_enter + configs_exit
for offset, label in configs:
# Create new Empty with the "position-" prefix
child_name = f"position-{label}.000"
child = bpy.data.objects.new(child_name, None)
# Link to the current collection
context.collection.objects.link(child)
# Set parent and local offset
child.parent = parent
child.location = offset
child["name"] = label
return {'FINISHED'}
class OBJECT_OT_CreateChildActionNode(bpy.types.Operator):
"""Creates child action node"""
bl_idname = "object.create_child_action_node"
bl_label = "Create Child Action Node"
bl_options = {'REGISTER', 'UNDO'}
target_val: bpy.props.StringProperty()
def execute(self, context):
parent = context.object
# Define offsets and their corresponding labels for the name
configs = [((0, 0.1, -parent.location.z + 0.5), self.target_val)]
for offset, label in configs:
# Create new Empty with the "position-" prefix
child_name = f"action-{label}.000"
child = bpy.data.objects.new(child_name, None)
# Link to the current collection
context.collection.objects.link(child)
# Set parent and local offset
child.parent = parent
child.location = offset
child["name"] = label
child["action"] = label
for p in ACTION_PROPS:
if p not in child:
if p in ["height"]:
child[p] = 1.0
elif p in ["radius"]:
child[p] = 0.5
elif p == "name":
child[p] = child.name.replace("action-","")
elif p == "action_text":
if "action" in child:
child[p] = child["action"].capitalize()
else:
child[p] = ""
else:
child[p] = "" # Initialize as empty text string
return {'FINISHED'}
class OBJECT_OT_DestroyChildByProp(bpy.types.Operator):
"""Deletes children starting with 'position-' where custom prop 'name' matches value"""
bl_idname = "object.destroy_child_by_prop"
bl_label = "Destroy Child"
bl_options = {'REGISTER', 'UNDO'}
target_val: bpy.props.StringProperty()
def execute(self, context):
parent = context.object
# Collect children to delete to avoid modifying list during iteration
to_delete = [
child for child in parent.children
if child.name.startswith("position-") and child.get("name") == self.target_val
]
for child in to_delete:
bpy.data.objects.remove(child, do_unlink=True)
return {'FINISHED'}
class OBJECT_PT_ActionEmptyPanel(bpy.types.Panel):
"""Custom GUI panel for 'action-' prefixed Empties"""
bl_label = "Action Controls"
bl_idname = "OBJECT_PT_action_empty_panel"
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "object"
@classmethod
def poll(cls, context):
obj = context.object
return (obj is not None and
obj.type == 'EMPTY' and
obj.name.startswith("action-"))
def draw(self, context):
layout = self.layout
obj = context.object
# Check if all required properties already exist
props_exist = all(p in obj for p in ACTION_PROPS)
if not props_exist:
# Show ONLY the button if properties are missing
layout.operator("object.create_action_props", icon='PLUS')
else:
# Show the text fields if properties exist
box = layout.box()
box.label(text="Action Settings", icon='PROPERTIES')
for p in ACTION_PROPS:
box.prop(obj, f'["{p}"]', text=p.replace("_", " ").title())
layout.separator()
have_enter = False
have_exit = False
for c in obj.children:
if c.name.startswith("position-") and "name" in c and c["name"] == "enter":
have_enter = True
break
for c in obj.children:
if c.name.startswith("position-") and "name" in c and c["name"] == "exit":
have_exit = True
break
if not have_enter:
op_enter = layout.operator("object.create_child_empties", icon='EMPTY_DATA', text="Add Enter Position")
op_enter.target_val = "enter"
else:
op_enter = layout.operator("object.destroy_child_by_prop", text="Destroy 'Enter' Node", icon='X')
op_enter.target_val = "enter"
if not have_exit:
op_exit = layout.operator("object.create_child_empties", icon='EMPTY_DATA', text="Add Exit Position")
op_exit.target_val = "exit"
else:
op_exit = layout.operator("object.destroy_child_by_prop", text="Destroy 'Exit' Node", icon='X')
op_exit.target_val = "exit"
class OBJECT_PT_FurnitureEmptyPanel(bpy.types.Panel):
"""Custom GUI panel for 'furniture-' prefixed Empties"""
bl_label = "Furniture Controls"
bl_idname = "OBJECT_PT_action_furniture_panel"
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "object"
@classmethod
def poll(cls, context):
obj = context.object
return (obj is not None and
obj.type == 'EMPTY' and
obj.name.startswith("furniture-"))
def draw(self, context):
layout = self.layout
obj = context.object
# Check if all required properties already exist
props_exist = all(p in obj for p in FURNITURE_PROPS)
if not props_exist:
# Show ONLY the button if properties are missing
layout.operator("object.create_furniture_props", icon='PLUS')
else:
# Show the text fields if properties exist
box = layout.box()
box.label(text="Furnitore Settings", icon='PROPERTIES')
for p in FURNITURE_PROPS:
box.prop(obj, f'["{p}"]', text=p.replace("_", " ").title())
op_use = layout.operator("object.create_child_action_node", icon='EMPTY_DATA', text="Add Use Action Node")
op_use.target_val = "use"
op_use = layout.operator("object.create_child_action_node", icon='EMPTY_DATA', text="Add Sit Action Node")
op_use.target_val = "sit"
def register():
bpy.utils.register_class(OBJECT_OT_CreateActionProps)
bpy.utils.register_class(OBJECT_OT_CreateFurnitureProps)
bpy.utils.register_class(OBJECT_PT_ActionEmptyPanel)
bpy.utils.register_class(OBJECT_OT_CreateChildEmpties)
bpy.utils.register_class(OBJECT_OT_DestroyChildByProp)
bpy.utils.register_class(OBJECT_PT_FurnitureEmptyPanel)
bpy.utils.register_class(OBJECT_OT_CreateChildActionNode)
bpy.utils.register_class(TEST_OT_LinkAndPlay)
bpy.utils.register_class(TEST_OT_Cleanup)
bpy.utils.register_class(TEST_PT_ObjectPanel)
def unregister():
bpy.utils.register_class(OBJECT_OT_CreateActionProps)
bpy.utils.register_class(OBJECT_OT_CreateFurnitureProps)
bpy.utils.unregister_class(OBJECT_PT_ActionEmptyPanel)
bpy.utils.unregister_class(OBJECT_OT_CreateChildEmpties)
bpy.utils.unregister_class(OBJECT_OT_DestroyChildByProp)
bpy.utils.unregister_class(OBJECT_PT_FurnitureEmptyPanel)
bpy.utils.unregister_class(OBJECT_OT_CreateChildActionNode)
bpy.utils.unregister_class(TEST_OT_LinkAndPlay)
bpy.utils.unregister_class(TEST_OT_Cleanup)
bpy.utils.unregister_class(TEST_PT_ObjectPanel)
if __name__ == "__main__":
register()

View File

@@ -0,0 +1,196 @@
import bpy
import os, sys, time
import json
from mathutils import Matrix, Quaternion
argv = sys.argv
argv = argv[argv.index("--") + 1:]
incpath = os.path.dirname(__file__)
sys.path.insert(0, incpath)
outdir = argv[0]
basis_change = Matrix((
(1, 0, 0, 0),
(0, 0, 1, 0),
(0, -1, 0, 0),
(0, 0, 0, 1)
))
def export_root_objects_to_gltf(output_dir):
# Ensure the output directory exists
if not os.path.exists(output_dir):
os.makedirs(output_dir)
# Deselect all objects
bpy.ops.object.select_all(action='DESELECT')
# Iterate through all objects in the scene
for obj in bpy.context.scene.objects:
# Check if the object has no parent and has children
# The original request specifies "objects having no parent with children".
# This condition captures those that are explicitly root objects of a hierarchy.
if obj.parent is None:
# Select the root object and all its children
print(obj.name)
if not obj.name.startswith("furniture-"):
continue
desc = {}
desc["name"] = obj.name.replace("furniture-","")
desc["mesh"] = obj.name + ".glb"
if "furniture_tags" in obj:
mtags = obj["furniture_tags"]
if "," in mtags:
atags = [m.strip() for m in mtags.split(",")]
desc["tags"] = atags
else:
desc["tags"] = [mtags]
else:
desc["tags"] = []
desc["sensors"] = []
desc["actions"] = []
desc["positions"] = []
for child in obj.children:
if child.name.startswith("action-"):
if not "action" in child:
continue
if not "height" in child:
continue
if not "radius" in child:
continue
local_matrix = child.matrix_local
ogre_local_matrix = basis_change @ local_matrix @ basis_change.inverted()
local_pos_d = ogre_local_matrix.to_translation()
local_rot_d = ogre_local_matrix.to_quaternion()
local_pos = [round(x, 4) for x in local_pos_d]
local_rot = [round(x, 4) for x in local_rot_d]
action = {}
action["position_x"] = local_pos[0]
action["position_y"] = local_pos[1]
action["position_z"] = local_pos[2]
action["rotation_w"] = local_rot[0]
action["rotation_x"] = local_rot[1]
action["rotation_y"] = local_rot[2]
action["rotation_z"] = local_rot[3]
action["height"] = child["height"]
action["radius"] = child["radius"]
action["action"] = child["action"]
if "action_text" in child:
action["action_text"] = child["action_text"]
else:
action["action_text"] = child["action"].capitalize()
if "name" in child:
action["name"] = child["name"]
else:
action["name"] = desc["name"] + "_" + str(len(desc["actions"]))
if "tags" in child:
atags = child["tags"]
if "," in atags:
atags = [m.strip() for m in atags.split(",")]
action["tags"] = atags
else:
action["tags"] = [atags]
else:
action["tags"] = []
for k, v in child.items():
if k.startswith("goap_prereq_"):
action[k] = v
elif k.startswith("goap_effect_"):
action[k] = v
action["furniture"] = {}
action["furniture"]["name"] = desc["name"]
action["furniture"]["tags"] = desc["tags"]
action["positions"] = []
for schild in child.children:
if schild.name.startswith("position-"):
local_matrix = schild.matrix_local
ogre_local_matrix = basis_change @ local_matrix @ basis_change.inverted()
local_pos_d = ogre_local_matrix.to_translation()
local_rot_d = ogre_local_matrix.to_quaternion()
local_pos = [round(x, 4) for x in local_pos_d]
local_rot = [round(x, 4) for x in local_rot_d]
position = {}
if "name" in schild:
position["name"] = schild["name"]
if "type" in schild:
position["type"] = schild["type"]
position["position_x"] = local_pos[0]
position["position_y"] = local_pos[1]
position["position_z"] = local_pos[2]
position["rotation_w"] = local_rot[0]
position["rotation_x"] = local_rot[1]
position["rotation_y"] = local_rot[2]
position["rotation_z"] = local_rot[3]
action["positions"].append(position)
desc["actions"].append(action)
if child.name.startswith("position-"):
local_matrix = child.matrix_local
ogre_local_matrix = basis_change @ local_matrix @ basis_change.inverted()
local_pos_d = ogre_local_matrix.to_translation()
local_rot_d = ogre_local_matrix.to_quaternion()
local_pos = [round(x, 4) for x in local_pos_d]
local_rot = [round(x, 4) for x in local_rot_d]
position = {}
if "name" in child:
position["name"] = child["name"]
if "type" in child:
position["type"] = child["type"]
position["position_x"] = local_pos[0]
position["position_y"] = local_pos[1]
position["position_z"] = local_pos[2]
position["rotation_w"] = local_rot[0]
position["rotation_x"] = local_rot[1]
position["rotation_y"] = local_rot[2]
position["rotation_z"] = local_rot[3]
desc["positions"].append(position)
obj.select_set(True)
for child in obj.children_recursive:
child.select_set(True)
obj.location = (0.0, 0.0, 0.0)
# Set the root object as the active object for the export operator
bpy.context.view_layer.objects.active = obj
# Define the output file path
file_path = os.path.join(output_dir, f"{obj.name}.glb")
# Export the selected objects to a glTF file
bpy.ops.export_scene.gltf(filepath=file_path,
use_selection=True,
check_existing=False,
export_format='GLB',
export_texture_dir='textures', export_texcoords=True,
export_normals=True,
export_tangents=True,
export_materials='EXPORT',
export_colors=True,
use_mesh_edges=False,
use_mesh_vertices=False,
export_cameras=False,
use_visible=False,
use_renderable=False,
export_yup=True,
export_apply=True,
export_animations=True,
export_force_sampling=True,
export_def_bones=False,
export_current_frame=False,
export_morph=True,
export_morph_animation=False,
export_morph_normal=True,
export_morph_tangent=True,
export_lights=False,
export_skins=True)
# Deselect all objects for the next iteration
bpy.ops.object.select_all(action='DESELECT')
print(f"Exported {obj.name} and its children to {file_path}")
with open(file_path + ".json", "w") as json_data:
json.dump(desc, json_data)
export_root_objects_to_gltf(outdir)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,140 @@
project(characters)
set(EDITED_BLENDS_LIST
male
female
)
set(EDITED_BLEND_TARGETS)
set(CHARACTER_GLBS)
foreach(EDITED_BLEND ${EDITED_BLENDS_LIST})
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/edited-normal-${EDITED_BLEND}.blend
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/edited-normal-${EDITED_BLEND}.blend
${CMAKE_BINARY_DIR}/assets/blender/vrm-vroid-normal-${EDITED_BLEND}.blend
COMMAND ${CMAKE_COMMAND}
-E copy ${CMAKE_CURRENT_SOURCE_DIR}/edited-normal-${EDITED_BLEND}.blend
${CMAKE_CURRENT_BINARY_DIR}/edited-normal-${EDITED_BLEND}.blend
COMMAND ${BLENDER} -b -Y
${CMAKE_CURRENT_BINARY_DIR}/edited-normal-${EDITED_BLEND}.blend
-P ${CMAKE_SOURCE_DIR}/assets/blender/scripts/copy_animations.py --
${CMAKE_BINARY_DIR}/assets/blender/vrm-vroid-normal-${EDITED_BLEND}.blend ${EDITED_BLEND}
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)
list(APPEND EDITED_BLEND_TARGETS ${CMAKE_BINARY_DIR}/assets/blender/characters/edited-normal-${EDITED_BLEND}.blend)
list(APPEND CHARACTER_GLBS ${CMAKE_BINARY_DIR}/characters/${EDITED_BLEND}/normal-${EDITED_BLEND}.glb)
endforeach()
set(VRM_IMPORTED_BLENDS
${CMAKE_BINARY_DIR}/assets/blender/vrm-vroid-normal-female.blend
${CMAKE_BINARY_DIR}/assets/blender/vrm-vroid-normal-male.blend
${CMAKE_BINARY_DIR}/assets/blender/shapes/male/vrm-vroid-normal-male-chibi.blend
)
add_custom_command(
OUTPUT ${CHARACTER_GLBS}
COMMAND ${CMAKE_COMMAND} -E make_directory ${CREATE_DIRECTORIES}
COMMAND ${BLENDER} -b -Y -P ${CMAKE_SOURCE_DIR}/assets/blender/scripts/export_models.py
COMMAND ${CMAKE_COMMAND} -D FILE=${CMAKE_BINARY_DIR}/characters/male/normal-male.glb -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/check_file_size.cmake
COMMAND ${CMAKE_COMMAND} -D FILE=${CMAKE_BINARY_DIR}/characters/female/normal-female.glb -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/check_file_size.cmake
COMMAND ${CMAKE_COMMAND} -E touch_nocreate ${CHARACTER_GLBS}
DEPENDS ${CMAKE_SOURCE_DIR}/assets/blender/scripts/export_models.py ${VRM_IMPORTED_BLENDS} ${EDITED_BLEND_TARGETS}
WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
set(VRM_SOURCE)
set(VRM_FILES buch1-chibi.vrm buch1.vrm jane2-dress.vrm jane2.vrm)
file(GLOB MIXAMO_FILES RELATIVE ${CMAKE_SOURCE_DIR}/assets/blender/mixamo ${CMAKE_SOURCE_DIR}/assets/blender/mixamo/**/*.fbx)
foreach(VRM_FILE ${VRM_FILES})
set(OUTPUT_FILE "${CMAKE_BINARY_DIR}/assets/vroid/${VRM_FILE}")
set(INPUT_FILE "${CMAKE_SOURCE_DIR}/assets/vroid/${VRM_FILE}")
add_custom_command(OUTPUT "${OUTPUT_FILE}"
COMMAND ${CMAKE_COMMAND} -E copy "${INPUT_FILE}" "${OUTPUT_FILE}"
DEPENDS "${INPUT_FILE}" VERBATIM)
list(APPEND VRM_SOURCE "${OUTPUT_FILE}")
endforeach()
foreach(MIXAMO_FILE ${MIXAMO_FILES})
set(OUTPUT_FILE "${CMAKE_BINARY_DIR}/assets/blender/mixamo/${MIXAMO_FILE}")
set(INPUT_FILE "${CMAKE_SOURCE_DIR}/assets/blender/mixamo/${MIXAMO_FILE}")
add_custom_command(OUTPUT "${OUTPUT_FILE}"
COMMAND ${CMAKE_COMMAND} -E copy "${INPUT_FILE}" "${OUTPUT_FILE}"
DEPENDS "${INPUT_FILE}" VERBATIM)
list(APPEND VRM_SOURCE "${OUTPUT_FILE}")
endforeach()
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/blender-addons-installed
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/assets/blender/scripts/addons ${CMAKE_BINARY_DIR}/assets/blender/scripts/addons
COMMAND ${BLENDER} -b -Y -P ${CMAKE_SOURCE_DIR}/assets/blender/scripts/install_addons.py
COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_BINARY_DIR}/blender-addons-installed
DEPENDS ${CMAKE_SOURCE_DIR}/assets/blender/scripts/addons/3.6/VRM_Addon_for_Blender-release.zip
${CMAKE_SOURCE_DIR}/assets/blender/scripts/addons/3.6/io_ogre.zip
${CMAKE_SOURCE_DIR}/assets/blender/scripts/install_addons.py
)
#add_custom_command(OUTPUT ${VRM_IMPORTED_BLENDS}
# COMMAND ${CMAKE_COMMAND} -E make_directory ${CREATE_DIRECTORIES}
# COMMAND ${BLENDER} -b -Y -P ${CMAKE_CURRENT_SOURCE_DIR}/import_vrm.py
# COMMAND ${CMAKE_COMMAND} -E touch_nocreate ${VRM_IMPORTED_BLENDS}
# DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/import_vrm.py
# ${CMAKE_CURRENT_BINARY_DIR}/blender-assons-installed
# ${VRM_SOURCE}
# WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
function(blender_import_vrm BLEND VRM EDITABLE RIG)
get_filename_component(TARGET_NAME ${BLEND} NAME_WE)
get_filename_component(VRM_NAME ${VRM} NAME_WE)
add_custom_command(OUTPUT ${BLEND}
COMMAND ${CMAKE_COMMAND} -E make_directory ${CREATE_DIRECTORIES}
COMMAND ${BLENDER} -b -Y -P ${CMAKE_SOURCE_DIR}/assets/blender/scripts/import_vrm2.py -- ${VRM_NAME}.vrm ${TARGET_NAME}.blend ${EDITABLE} ${RIG}
COMMAND ${CMAKE_COMMAND} -D FILE=${BLEND} -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/check_file_size.cmake
COMMAND ${CMAKE_COMMAND} -E touch_nocreate ${BLEND}
DEPENDS ${CMAKE_SOURCE_DIR}/assets/blender/scripts/import_vrm2.py
${CMAKE_CURRENT_BINARY_DIR}/blender-addons-installed
${VRM}
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)
endfunction()
blender_import_vrm(${CMAKE_BINARY_DIR}/assets/blender/vrm-vroid-normal-female.blend
${CMAKE_BINARY_DIR}/assets/vroid/jane2-dress.vrm
modelling-vroid-normal-female.blend female)
blender_import_vrm(${CMAKE_BINARY_DIR}/assets/blender/vrm-vroid-normal-male.blend
${CMAKE_BINARY_DIR}/assets/vroid/buch1.vrm
modelling-vroid-normal-male.blend male)
blender_import_vrm(${CMAKE_BINARY_DIR}/assets/blender/shapes/male/vrm-vroid-normal-male-chibi.blend
${CMAKE_BINARY_DIR}/assets/vroid/buch1-chibi.vrm
modelling-shape-male-chibi.blend male)
#add_custom_command(OUTPUT ${VRM_IMPORTED_BLENDS}
# COMMAND ${CMAKE_COMMAND} -E make_directory ${CREATE_DIRECTORIES}
# COMMAND ${BLENDER} -b -Y -P ${CMAKE_CURRENT_SOURCE_DIR}/import_vrm2.py jane2-dress.vrm vrm-vroid-normal-female.blend modelling-vroid-normal-female.blend female
# COMMAND ${BLENDER} -b -Y -P ${CMAKE_CURRENT_SOURCE_DIR}/import_vrm2.py buch1.vrm vrm-vroid-normal-male.blend modelling-vroid-normal-male.blend male
# COMMAND ${BLENDER} -b -Y -P ${CMAKE_CURRENT_SOURCE_DIR}/import_vrm2.py buch1-chibi.vrm shapes/male/vrm-vroid-normal-male-chibi.blend modelling-shape-male-chibi.blend male
# COMMAND ${CMAKE_COMMAND} -E touch_nocreate ${VRM_IMPORTED_BLENDS}
# DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/import_vrm.py
# ${CMAKE_CURRENT_BINARY_DIR}/blender-assons-installed
# ${VRM_SOURCE}
# WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
set(COPY_BLENDS edited-shape-test-male.blend edited-normal-male-base.blend)
foreach (COPY_BLEND_FILE ${COPY_BLENDS})
add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/assets/blender/${COPY_BLEND_FILE}
DEPENDS ${CMAKE_SOURCE_DIR}/assets/blender/${COPY_BLEND_FILE}
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_SOURCE_DIR}/assets/blender/${COPY_BLEND_FILE}
${CMAKE_BINARY_DIR}/assets/blender/${COPY_BLEND_FILE}
)
list(APPEND EDITED_BLEND_TARGETS ${CMAKE_BINARY_DIR}/assets/blender/${COPY_BLEND_FILE})
endforeach()
add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/characters/shapes/male/chibi/vroid-normal-male-chibi.glb
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/shapes/male/chibi/vroid-normal-male-chibi.glb
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/shapes/male/chibi/vroid-normal-male-chibi.glb
${CMAKE_BINARY_DIR}/characters/shapes/male/chibi/vroid-normal-male-chibi.glb)
add_custom_target(morph ALL DEPENDS MorphTargetsResearch ${CHARACTER_GLBS})
add_custom_target(edited-blends ALL DEPENDS ${EDITED_BLEND_TARGETS})
add_custom_target(import_vrm DEPENDS ${CHARACTER_GLBS})

View File

@@ -0,0 +1,11 @@
if(EXISTS "${FILE}")
file(SIZE "${FILE}" FILE_SIZE)
if(FILE_SIZE GREATER 0)
message(STATUS "Build-time check: ${FILE} exists and is not empty.")
else()
message(FATAL_ERROR "Build-time check: ${FILE} exists but is empty!")
endif()
else()
message(FATAL_ERROR "Build-time check: ${FILE} does not exist!")
endif()

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,71 @@
import bpy
import os, sys, time
argv = sys.argv
argv = argv[argv.index("--") + 1:]
incpath = os.path.dirname(__file__)
sys.path.insert(0, incpath)
outdir = argv[0]
def export_root_objects_to_gltf(output_dir):
# Ensure the output directory exists
if not os.path.exists(output_dir):
os.makedirs(output_dir)
# Deselect all objects
bpy.ops.object.select_all(action='DESELECT')
# Iterate through all objects in the scene
for obj in bpy.context.scene.objects:
# Check if the object has no parent and has children
# The original request specifies "objects having no parent with children".
# This condition captures those that are explicitly root objects of a hierarchy.
if obj.parent is None:
# Select the root object and all its children
print(obj.name)
obj.select_set(True)
for child in obj.children_recursive:
child.select_set(True)
# Set the root object as the active object for the export operator
bpy.context.view_layer.objects.active = obj
# Define the output file path
file_path = os.path.join(output_dir, f"{obj.name}.glb")
# Export the selected objects to a glTF file
bpy.ops.export_scene.gltf(filepath=file_path,
use_selection=True,
check_existing=False,
export_format='GLB',
export_texture_dir='textures', export_texcoords=True,
export_normals=True,
export_tangents=True,
export_materials='EXPORT',
export_colors=True,
use_mesh_edges=False,
use_mesh_vertices=False,
export_cameras=False,
use_visible=False,
use_renderable=False,
export_yup=True,
export_apply=True,
export_animations=True,
export_force_sampling=True,
export_def_bones=False,
export_current_frame=False,
export_morph=True,
export_morph_animation=False,
export_morph_normal=True,
export_morph_tangent=True,
export_lights=False,
export_skins=True)
# Deselect all objects for the next iteration
bpy.ops.object.select_all(action='DESELECT')
print(f"Exported {obj.name} and its children to {file_path}")
export_root_objects_to_gltf(outdir)

View File

@@ -0,0 +1,211 @@
#!/usr/bin/env python
import os, sys, time
import bpy
from math import pi
import glob
import shutil
from mathutils import Vector, Matrix
from math import radians, pi
argv = sys.argv
argv = argv[argv.index("--") + 1:]
if bpy.app.version[0] == 3:
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)) + "/mixamo/3.6")
if bpy.app.version[0] == 4:
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)) + "/mixamo/4.3")
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from vrm import rename
if bpy.app.version[0] == 3:
from mixamo import mixamo_rig
from mixamo.lib.armature import *
from settings import VRMDataFemale, VRMDataMale, VRMDataMaleBabyShape, basepath
from geometry import tris2quads
class mkrig:
ik_arms = True
ik_legs = True
def __init__(self):
mixamo_rig._make_rig(self)
mixamo_rig.remove_temp_objects()
mixamo_rig.clean_scene()
def get_anim(filepath, root_bone_name="Root", hip_bone_name="mixamorig:Hips", remove_prefix=False, name_prefix="mixamorig:", insert_root=False, delete_armatures=False):
old_objs = set(bpy.context.scene.objects)
try:
import_armature(filepath, root_bone_name, hip_bone_name, remove_prefix, name_prefix, insert_root, delete_armatures)
imported_objects = set(bpy.context.scene.objects) - old_objs
if delete_armatures and num_files > 1:
deleteArmature(imported_objects)
except Exception as e:
log.error("[Mixamo Root] ERROR get_all_anims raised %s when processing %s" % (str(e), filepath))
bpy.context.scene.frame_start = 0
bpy.ops.object.mode_set(mode='OBJECT')
def create_root_bone(anim_obj):
bpy.context.view_layer.objects.active = anim_obj
bpy.ops.object.mode_set(mode='EDIT')
root_bone = anim_obj.data.edit_bones.new("Root")
root_bone.tail.y = 0.5
anim_obj.data.edit_bones["mixamorig:Hips"].parent = anim_obj.data.edit_bones["Root"]
bpy.ops.object.mode_set(mode='OBJECT')
def hips_to_root(anim_obj):
for fc in anim_obj.animation_data.action.fcurves:
if "mixamorig:Hips" in fc.data_path:
if "location" in fc.data_path:
print(fc, fc.data_path, fc.array_index)
if fc.array_index in [0, 2]:
fc.data_path = fc.data_path.replace("mixamorig:Hips", "Root")
def hips_to_root(anim_obj):
for fc in anim_obj.animation_data.action.fcurves:
if "mixamorig:Hips" in fc.data_path:
if "location" in fc.data_path:
print(fc, fc.data_path, fc.array_index)
if fc.array_index in [0, 2]:
fc.data_path = fc.data_path.replace("mixamorig:Hips", "Root")
def hips_to_root2(anim_obj):
for fc in anim_obj.animation_data.action.fcurves:
if "Ctrl_Hips" in fc.data_path:
if "location" in fc.data_path:
print(fc, fc.data_path, fc.array_index)
if fc.array_index in [0, 2]:
data_path = fc.data_path[:]
fcr = anim_obj.animation_data.action.fcurves.new(data_path = data_path.replace("Ctrl_Hips", "Root"), index = fc.array_index)
keys = [0] * (2 * len(fc.keyframe_points))
fcr.keyframe_points.add(len(fc.keyframe_points))
fc.keyframe_points.foreach_get("co", keys)
fcr.keyframe_points.foreach_set("co", keys)
print("ROOT")
class ImpData:
path = argv[0]
outfile = argv[1]
editfile = argv[2]
armature_name = argv[3]
mixamo_animation_path = basepath + "/assets/blender/mixamo/" + argv[3] + "/"
mixamo_animations = ["idle", "walking", "running", "jump", "left_turn", "right_turn", "swimming", "treading_water", "sitting", "hanging-climb", "hanging-idle"]
fbx_scale = 1.0
def __init__(self):
if self.armature_name == "female":
self.fbx_scale = 0.89
if self.path == "buch1-chibi.vrm":
self.mixamo_animations = []
imp = ImpData()
bpy.ops.wm.read_homefile(use_empty=True)
for o in bpy.data.objects:
bpy.data.objects.remove(o)
print(basepath + "/assets/vroid/" + imp.path)
result = bpy.ops.import_scene.vrm(filepath=(basepath + "/assets/vroid/" + imp.path))
if result != {"FINISHED"}:
raise Exception(f"Failed to import vrm: {result}")
for o in bpy.data.objects:
if o.type == 'MESH':
tris2quads.tris2quads(o)
armature_count = 0
for obj in bpy.data.objects:
if (obj.type == "ARMATURE"):
armature_count += 1
if armature_count > 1:
raise Exception("Bad scene data")
main_armature = None
for obj in bpy.data.objects:
if (obj.type == "ARMATURE"):
main_armature = obj
break
if main_armature == None:
raise Exception("Bad scene data")
print("Renaming...")
main_armature.select_set(True)
bpy.context.view_layer.objects.active = main_armature
rename.rename_bones(main_armature)
main_armature.select_set(False)
main_armature.name = imp.armature_name
main_armature["VRM_IMPORTED_NAME"] = "female"
if main_armature.animation_data is None:
main_armature.animation_data_create()
main_armature.select_set(True)
bpy.context.view_layer.objects.active = main_armature
mkrig()
main_armature.select_set(False)
bpy.context.view_layer.objects.active = None
for anim in imp.mixamo_animations:
anim_path = imp.mixamo_animation_path + anim + ".fbx"
print("Load BVH..." + anim_path)
old_objs = set(bpy.context.scene.objects)
bpy.ops.import_scene.fbx(filepath=anim_path, global_scale=imp.fbx_scale)
imported_objects = set(bpy.context.scene.objects) - old_objs
for anim_obj in imported_objects:
if anim_obj == main_armature:
continue
if "VRM_IMPORTED_NAME" in anim_obj:
continue
if main_armature == anim_obj or anim_obj.type != "ARMATURE":
continue
if anim_obj.animation_data == None:
print("Bad object: " + anim_obj.name + " " + anim_obj.type)
bpy.data.objects.remove(anim_obj)
continue
if anim_obj.animation_data.action == None:
print("Bad object: " + anim_obj.name + " " + anim_obj.type)
bpy.data.objects.remove(anim_obj)
continue
print("importing: " + anim_obj.animation_data.action.name)
mixamo_rig._import_anim(anim_obj, main_armature, True)
print("===" + main_armature.animation_data.action.name)
main_armature.animation_data.action.name = anim
hips_to_root2(main_armature)
track = main_armature.animation_data.nla_tracks.new()
action = main_armature.animation_data.action
track.name = action.name
track.strips.new(action.name, int(action.frame_range[0]), action)
track.mute = True
track.lock = True
bpy.data.objects.remove(anim_obj)
imported_objects.clear()
imported_objects = set(bpy.context.scene.objects) - old_objs
for anim_obj in imported_objects:
bpy.data.objects.remove(anim_obj)
main_armature.animation_data.action = bpy.data.actions.new(name="default")
default_action = main_armature.animation_data.action
bpy.context.view_layer.objects.active = main_armature
main_armature.select_set(True)
layers = enable_all_armature_layers()
bpy.ops.object.mode_set(mode='POSE')
for b in main_armature.pose.bones:
b.location = [0,0,0]
b.rotation_euler = [0,0,0]
b.rotation_quaternion = [1,0,0,0]
b.scale = [1,1,1]
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.mode_set(mode='POSE')
for b in main_armature.data.bones:
if b.name.find("Ctrl_") < 0 and b.name.find("ctrl_") < 0:
continue
main_armature.pose.bones[b.name].matrix_basis.identity()
main_armature.pose.bones[b.name].keyframe_insert(data_path='location', frame=1)
rotation_mode = main_armature.pose.bones[b.name].rotation_mode
if rotation_mode == "QUATERNION":
main_armature.pose.bones[b.name].keyframe_insert(data_path='rotation_quaternion', frame=1)
elif rotation_mode == "AXIS_ANGLE":
main_armature.pose.bones[b.name].keyframe_insert(data_path='rotation_axis_angle', frame=1)
else:
main_armature.pose.bones[b.name].keyframe_insert(data_path='rotation_euler', frame=1)
main_armature.pose.bones[b.name].keyframe_insert(data_path='scale', frame=1)
bpy.ops.object.mode_set(mode='OBJECT')
track = main_armature.animation_data.nla_tracks.new()
action = main_armature.animation_data.action
track.name = action.name
track.strips.new(action.name, int(action.frame_range[0]), action)
track.mute = True
track.lock = True
restore_armature_layers(layers)
main_armature.animation_data.action = default_action
main_armature.select_set(False)
bpy.context.view_layer.objects.active = None
bpy.ops.wm.save_as_mainfile(filepath=(basepath + "/assets/blender/" + imp.outfile))

View File

@@ -6,7 +6,7 @@ script_list = [item for item in file_list if item.endswith('.zip')]
for file in file_list:
path_to_file = os.path.join(path_to_script_dir, file)
bpy.ops.preferences.addon_install(overwrite=True, target='DEFAULT', filepath=path_to_file, filter_folder=True, filter_python=False, filter_glob="*.py;*.zip")
enableTheseAddons = ["VRM_Addon_for_Blender-release"]
enableTheseAddons = ["VRM_Addon_for_Blender-release", "io_ogre"]
for string in enableTheseAddons:
name = enableTheseAddons
bpy.ops.preferences.addon_enable(module = string)

View File

@@ -54,7 +54,7 @@ class ExportMappingMale:
self.files.append({"name": fobj})
class ExportMappingMaleEdited:
blend_path = "assets/blender/" + "edited-normal-male.blend"
blend_path = "assets/blender/characters/" + "edited-normal-male.blend"
gltf_path = "characters/male/normal-male.gltf"
ogre_scene = "characters/male/vroid-normal-male.scene"
inner_path = "Object"
@@ -68,7 +68,7 @@ class ExportMappingMaleEdited:
self.files.append({"name": fobj})
class ExportMappingMaleClothedEdited:
blend_path = "assets/blender/" + "edited-normal-male.blend"
blend_path = "assets/blender/characters/" + "edited-normal-male.blend"
gltf_path = "characters/male/normal-male.gltf"
ogre_scene = "characters/male/vroid-normal-male.scene"
inner_path = "Object"
@@ -82,7 +82,7 @@ class ExportMappingMaleClothedEdited:
self.files.append({"name": fobj})
class ExportMappingFemaleEdited:
blend_path = "assets/blender/" + "edited-normal-female.blend"
blend_path = "assets/blender/characters/" + "edited-normal-female.blend"
gltf_path = "characters/female/normal-female.gltf"
ogre_scene = "characters/female/vroid-normal-female.scene"
inner_path = "Object"

View File

@@ -3,7 +3,7 @@ set(LUA_SCRIPTS_SRC
data.lua
)
set(LUA_SCRIPTS_OUTPUT)
set(LUA_PACKAGES narrator stories)
set(LUA_PACKAGES narrator stories json)
foreach(LUA_SCRIPT_FILE ${LUA_SCRIPTS_SRC})
get_filename_component(FILE_NAME ${LUA_SCRIPT_FILE} NAME_WE)
set(LUA_SCRIPT_OUTPUT_FILE ${CMAKE_CURRENT_BINARY_DIR}/${FILE_NAME}.lua)
@@ -21,4 +21,4 @@ foreach(LUA_PACKAGE ${LUA_PACKAGES})
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${LUA_PACKAGE})
list(APPEND LUA_PACKAGES_OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${LUA_PACKAGE})
endforeach()
add_custom_target(stage_lua_scripts ALL DEPENDS ${LUA_SCRIPTS_OUTPUT} ${LUA_PACKAGES_OUTPUT})
add_custom_target(stage_lua_scripts ALL DEPENDS ${LUA_SCRIPTS_OUTPUT} ${LUA_PACKAGES_OUTPUT})

View File

@@ -62,6 +62,7 @@ dropped you into the sea. Last thing you heard before you hit the water was happ
}
]]--
local narrator = require('narrator.narrator')
local json = require('json.json')
function dump(o)
if type(o) == 'table' then
@@ -218,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
@@ -615,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)
@@ -655,3 +727,178 @@ setup_handler(function(event, trigger_entity, what_entity)
--]]
end
end)
setup_action_handler("talk", {
activate = function(this)
local book = narrator.parse_file('stories.talk')
this.story = narrator.init_story(book)
local get_crash_bind = function(this)
local crash_bind = function()
print("variables")
print(dump(this.story.variables))
crash()
end
return crash_bind
end
this.story:bind('crash', get_crash_bind(this))
local props = this._get_properties()
local goals = this._get_goals()
print("node activated")
-- print(dump(goals))
local json_data = json.decode(props)
print(dump(json_data))
for i, v in ipairs(goals) do
print("Goal: ", i)
local goal_data = json.decode(v)
print(dump(goal_data))
end
this.story:begin()
this:narration_update()
get_crash_bind(this)()
crash()
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,
})
setup_action_handler("sit", {
activate = function(this)
print('sit activate')
crash()
end,
event = function(this, event)
print('event', event)
end,
finish = function(this)
end,
})
setup_action_handler("use", {
activate = function(this)
print('sit activate')
crash()
end,
event = function(this, event)
print('event', event)
end,
finish = function(this)
end,
})
--[[
active_dialogues = {}
setup_action_handler("talk", function(town, index, word)
local ret = ""
local choices = {}
local have_choice = false
local have_paragraph = false
local book = narrator.parse_file('stories.talk')
local story = narrator.init_story(book)
if story == nil then
crash()
end
story:begin()
narrate("Boo!!!!")
if story.can_continue() then
local paragraph = story:continue(1)
print(dump(paragraph))
if story:can_choose() then
have_choice = true
local ch = 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
story:choose(1)
choices = {}
end
if #choices == 1 and choices[1] == "Continue" then
this.story:choose(1)
choices = {}
end
end
end
if (#choices > 0) then
print("choices!!!")
narrate(ret, choices)
else
narrate(ret)
end
if not have_choice and not have_paragraph then
-- complete
crash()
else
print("can continue")
end
end)
]]--
main_menu()

388
lua-scripts/json/json.lua Normal file
View File

@@ -0,0 +1,388 @@
--
-- json.lua
--
-- Copyright (c) 2020 rxi
--
-- 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.
--
local json = { _version = "0.1.2" }
-------------------------------------------------------------------------------
-- Encode
-------------------------------------------------------------------------------
local encode
local escape_char_map = {
[ "\\" ] = "\\",
[ "\"" ] = "\"",
[ "\b" ] = "b",
[ "\f" ] = "f",
[ "\n" ] = "n",
[ "\r" ] = "r",
[ "\t" ] = "t",
}
local escape_char_map_inv = { [ "/" ] = "/" }
for k, v in pairs(escape_char_map) do
escape_char_map_inv[v] = k
end
local function escape_char(c)
return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte()))
end
local function encode_nil(val)
return "null"
end
local function encode_table(val, stack)
local res = {}
stack = stack or {}
-- Circular reference?
if stack[val] then error("circular reference") end
stack[val] = true
if rawget(val, 1) ~= nil or next(val) == nil then
-- Treat as array -- check keys are valid and it is not sparse
local n = 0
for k in pairs(val) do
if type(k) ~= "number" then
error("invalid table: mixed or invalid key types")
end
n = n + 1
end
if n ~= #val then
error("invalid table: sparse array")
end
-- Encode
for i, v in ipairs(val) do
table.insert(res, encode(v, stack))
end
stack[val] = nil
return "[" .. table.concat(res, ",") .. "]"
else
-- Treat as an object
for k, v in pairs(val) do
if type(k) ~= "string" then
error("invalid table: mixed or invalid key types")
end
table.insert(res, encode(k, stack) .. ":" .. encode(v, stack))
end
stack[val] = nil
return "{" .. table.concat(res, ",") .. "}"
end
end
local function encode_string(val)
return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
end
local function encode_number(val)
-- Check for NaN, -inf and inf
if val ~= val or val <= -math.huge or val >= math.huge then
error("unexpected number value '" .. tostring(val) .. "'")
end
return string.format("%.14g", val)
end
local type_func_map = {
[ "nil" ] = encode_nil,
[ "table" ] = encode_table,
[ "string" ] = encode_string,
[ "number" ] = encode_number,
[ "boolean" ] = tostring,
}
encode = function(val, stack)
local t = type(val)
local f = type_func_map[t]
if f then
return f(val, stack)
end
error("unexpected type '" .. t .. "'")
end
function json.encode(val)
return ( encode(val) )
end
-------------------------------------------------------------------------------
-- Decode
-------------------------------------------------------------------------------
local parse
local function create_set(...)
local res = {}
for i = 1, select("#", ...) do
res[ select(i, ...) ] = true
end
return res
end
local space_chars = create_set(" ", "\t", "\r", "\n")
local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",")
local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u")
local literals = create_set("true", "false", "null")
local literal_map = {
[ "true" ] = true,
[ "false" ] = false,
[ "null" ] = nil,
}
local function next_char(str, idx, set, negate)
for i = idx, #str do
if set[str:sub(i, i)] ~= negate then
return i
end
end
return #str + 1
end
local function decode_error(str, idx, msg)
local line_count = 1
local col_count = 1
for i = 1, idx - 1 do
col_count = col_count + 1
if str:sub(i, i) == "\n" then
line_count = line_count + 1
col_count = 1
end
end
error( string.format("%s at line %d col %d", msg, line_count, col_count) )
end
local function codepoint_to_utf8(n)
-- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa
local f = math.floor
if n <= 0x7f then
return string.char(n)
elseif n <= 0x7ff then
return string.char(f(n / 64) + 192, n % 64 + 128)
elseif n <= 0xffff then
return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)
elseif n <= 0x10ffff then
return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,
f(n % 4096 / 64) + 128, n % 64 + 128)
end
error( string.format("invalid unicode codepoint '%x'", n) )
end
local function parse_unicode_escape(s)
local n1 = tonumber( s:sub(1, 4), 16 )
local n2 = tonumber( s:sub(7, 10), 16 )
-- Surrogate pair?
if n2 then
return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)
else
return codepoint_to_utf8(n1)
end
end
local function parse_string(str, i)
local res = ""
local j = i + 1
local k = j
while j <= #str do
local x = str:byte(j)
if x < 32 then
decode_error(str, j, "control character in string")
elseif x == 92 then -- `\`: Escape
res = res .. str:sub(k, j - 1)
j = j + 1
local c = str:sub(j, j)
if c == "u" then
local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1)
or str:match("^%x%x%x%x", j + 1)
or decode_error(str, j - 1, "invalid unicode escape in string")
res = res .. parse_unicode_escape(hex)
j = j + #hex
else
if not escape_chars[c] then
decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string")
end
res = res .. escape_char_map_inv[c]
end
k = j + 1
elseif x == 34 then -- `"`: End of string
res = res .. str:sub(k, j - 1)
return res, j + 1
end
j = j + 1
end
decode_error(str, i, "expected closing quote for string")
end
local function parse_number(str, i)
local x = next_char(str, i, delim_chars)
local s = str:sub(i, x - 1)
local n = tonumber(s)
if not n then
decode_error(str, i, "invalid number '" .. s .. "'")
end
return n, x
end
local function parse_literal(str, i)
local x = next_char(str, i, delim_chars)
local word = str:sub(i, x - 1)
if not literals[word] then
decode_error(str, i, "invalid literal '" .. word .. "'")
end
return literal_map[word], x
end
local function parse_array(str, i)
local res = {}
local n = 1
i = i + 1
while 1 do
local x
i = next_char(str, i, space_chars, true)
-- Empty / end of array?
if str:sub(i, i) == "]" then
i = i + 1
break
end
-- Read token
x, i = parse(str, i)
res[n] = x
n = n + 1
-- Next token
i = next_char(str, i, space_chars, true)
local chr = str:sub(i, i)
i = i + 1
if chr == "]" then break end
if chr ~= "," then decode_error(str, i, "expected ']' or ','") end
end
return res, i
end
local function parse_object(str, i)
local res = {}
i = i + 1
while 1 do
local key, val
i = next_char(str, i, space_chars, true)
-- Empty / end of object?
if str:sub(i, i) == "}" then
i = i + 1
break
end
-- Read key
if str:sub(i, i) ~= '"' then
decode_error(str, i, "expected string for key")
end
key, i = parse(str, i)
-- Read ':' delimiter
i = next_char(str, i, space_chars, true)
if str:sub(i, i) ~= ":" then
decode_error(str, i, "expected ':' after key")
end
i = next_char(str, i + 1, space_chars, true)
-- Read value
val, i = parse(str, i)
-- Set
res[key] = val
-- Next token
i = next_char(str, i, space_chars, true)
local chr = str:sub(i, i)
i = i + 1
if chr == "}" then break end
if chr ~= "," then decode_error(str, i, "expected '}' or ','") end
end
return res, i
end
local char_func_map = {
[ '"' ] = parse_string,
[ "0" ] = parse_number,
[ "1" ] = parse_number,
[ "2" ] = parse_number,
[ "3" ] = parse_number,
[ "4" ] = parse_number,
[ "5" ] = parse_number,
[ "6" ] = parse_number,
[ "7" ] = parse_number,
[ "8" ] = parse_number,
[ "9" ] = parse_number,
[ "-" ] = parse_number,
[ "t" ] = parse_literal,
[ "f" ] = parse_literal,
[ "n" ] = parse_literal,
[ "[" ] = parse_array,
[ "{" ] = parse_object,
}
parse = function(str, idx)
local chr = str:sub(idx, idx)
local f = char_func_map[chr]
if f then
return f(str, idx)
end
decode_error(str, idx, "unexpected character '" .. chr .. "'")
end
function json.decode(str)
if type(str) ~= "string" then
error("expected argument of type string, got " .. type(str))
end
local res, idx = parse(str, next_char(str, 1, space_chars, true))
idx = next_char(str, idx, space_chars, true)
if idx <= #str then
decode_error(str, idx, "trailing garbage")
end
return res
end
return json

View File

@@ -0,0 +1,20 @@
Yes?
-> start
=== start ===
+ [Common] -> common
+ [Debug] -> debug
+ [Leave] -> END
=== common ===
# discard
+ [What do you think about me?]
I don't know.
-> END
+ [How are you?]
I think everything is allright.
-> END
=== debug ===
Debug #discard
+ Crash
#crash
-> END

View File

@@ -20,6 +20,8 @@ FileSystem=resources/terrain
[General]
FileSystem=skybox
FileSystem=resources/buildings
FileSystem=resources/buildings/parts/pier
FileSystem=resources/buildings/parts/furniture
FileSystem=resources/vehicles
FileSystem=resources/debug
FileSystem=resources/fonts

34
resources/CMakeLists.txt Normal file
View File

@@ -0,0 +1,34 @@
project(resources)
set(DIRECTORY_LIST
main
shaderlib
terrain
)
set(TARGET_PATHS)
foreach(DIR_NAME ${DIRECTORY_LIST})
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${DIR_NAME}
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_CURRENT_SOURCE_DIR}/${DIR_NAME}
${CMAKE_CURRENT_BINARY_DIR}/${DIR_NAME}
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${DIR_NAME})
list(APPEND TARGET_PATHS ${CMAKE_CURRENT_BINARY_DIR}/${DIR_NAME})
endforeach()
#add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/terrain/world_map.png
# COMMAND unzip -o ${CMAKE_CURRENT_SOURCE_DIR}/world_map.kra mergedimage.png -d ${CMAKE_CURRENT_BINARY_DIR}/world_map
# COMMAND ${CMAKE_COMMAND} -E copy
# ${CMAKE_BINARY_DIR}/world_map/mergedimage.png
# ${CMAKE_BINARY_DIR}/resources/terrain/world_map.png
# COMMAND ${CMAKE_COMMAND} -E remove_directory ${CMAKE_BINARY_DIR}/world_map
# DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/world_map.kra)
#list(APPEND TARGET_PATHS ${CMAKE_CURRENT_BINARY_DIR}/terrain/world_map.png)
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/terrain/brushes.png
COMMAND unzip -o ${CMAKE_CURRENT_SOURCE_DIR}/brushes.kra mergedimage.png -d ${CMAKE_CURRENT_BINARY_DIR}/brushes
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_BINARY_DIR}/brushes/mergedimage.png
${CMAKE_CURRENT_BINARY_DIR}/terrain/brushes.png
COMMAND ${CMAKE_COMMAND} -E remove_directory ${CMAKE_CURRENT_BINARY_DIR}/brushes
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/brushes.kra)
list(APPEND TARGET_PATHS ${CMAKE_CURRENT_BINARY_DIR}/terrain/brushes.png)
add_custom_target(stage_resources ALL DEPENDS ${TARGET_PATHS})

View File

@@ -0,0 +1,16 @@
// This file is part of the OGRE project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at https://www.ogre3d.org/licensing.
// SPDX-License-Identifier: MIT
#ifdef USE_LINEAR_COLOURS
#define ENABLE_LINEAR_COLOUR(colour) colour.rgb = pow(colour.rgb, vec3_splat(2.2))
#else
#define ENABLE_LINEAR_COLOUR(colour)
#endif
#if defined(USE_LINEAR_COLOURS) && !defined(TARGET_CONSUMES_LINEAR)
#define COLOUR_TRANSFER(colour) colour.rgb = pow(colour.rgb, vec3_splat(1.0/2.2))
#else
#define COLOUR_TRANSFER(colour)
#endif

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

File diff suppressed because it is too large Load Diff

22
src/FastNoiseLite/LICENSE Normal file
View File

@@ -0,0 +1,22 @@
MIT License
Copyright(c) 2020 Jordan Peck (jordan.me2@gmail.com)
Copyright(c) 2020 Contributors
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,23 @@
## Getting Started
Here's an example for creating a 128x128 array of OpenSimplex2 noise
```cpp
// Create and configure FastNoise object
FastNoiseLite noise;
noise.SetNoiseType(FastNoiseLite::NoiseType_OpenSimplex2);
// Gather noise data
std::vector<float> noiseData(128 * 128);
int index = 0;
for (int y = 0; y < 128; y++)
{
for (int x = 0; x < 128; x++)
{
noiseData[index++] = noise.GetNoise((float)x, (float)y);
}
}
// Do something with this data...
```

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

@@ -9,7 +9,7 @@ find_package(OgreProcedural REQUIRED CONFIG)
find_package(pugixml REQUIRED CONFIG)
find_package(flecs REQUIRED CONFIG)
set(COPY_DIRECTORIES resources skybox water)
set(COPY_DIRECTORIES characters resources skybox water resources/buildings/parts)
set(INSTALL_DEPS ${CMAKE_CURRENT_BINARY_DIR}/resources.cfg)
foreach(DIR_NAME ${COPY_DIRECTORIES})
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${DIR_NAME}
@@ -19,7 +19,7 @@ foreach(DIR_NAME ${COPY_DIRECTORIES})
list(APPEND INSTALL_DEPS ${CMAKE_CURRENT_BINARY_DIR}/${DIR_NAME})
endforeach()
add_custom_target(install_resources DEPENDS ${INSTALL_DEPS})
add_custom_target(install_resources DEPENDS import_buildings import_building_parts ${INSTALL_DEPS})
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/resources.cfg
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/../../resources.cfg ${CMAKE_CURRENT_BINARY_DIR}/resources.cfg
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/../../resources.cfg
@@ -29,6 +29,7 @@ add_library(editor STATIC EditorGizmoModule.cpp EditorInputModule.cpp)
target_link_libraries(editor PRIVATE
OgreMain
GameData
physics
)
target_include_directories(editor PUBLIC .)

View File

@@ -1,5 +1,7 @@
#include <iostream>
#include <Ogre.h>
#include <Jolt/Jolt.h>
#include <Jolt/Physics/Body/BodyID.h>
#include "Components.h"
#include "GameData.h"
#include "PhysicsModule.h"
@@ -35,8 +37,6 @@ EditorInputModule::EditorInputModule(flecs::world &ecs)
.kind(flecs::OnUpdate)
.each([this](const EngineData &eng, Input &input,
Camera &camera) {
if (ECS::player.is_valid())
return;
if (!camera.configured) {
// create a pivot at roughly the character's shoulder
camera.mCameraPivot =
@@ -147,10 +147,11 @@ EditorInputModule::EditorInputModule(flecs::world &ecs)
position =
ray.getPoint(ogreResult.second);
Ogre::Vector3 position2;
JPH::BodyID id;
bool hit = PhysicsModule::raycastQuery(
ray.getOrigin(),
ray.getPoint(2000.0f),
position2);
position2, id);
if (hit) {
float d1 =
ray.getOrigin()
@@ -169,10 +170,11 @@ EditorInputModule::EditorInputModule(flecs::world &ecs)
.sceneNode->_setDerivedPosition(
position);
} else {
JPH::BodyID id;
bool hit = PhysicsModule::raycastQuery(
ray.getOrigin(),
ray.getPoint(2000.0f),
position);
ray.getPoint(2000.0f), position,
id);
if (hit) {
std::cout
<< "HIT!: " << position
@@ -238,4 +240,4 @@ EditorInputModule::EditorInputModule(flecs::world &ecs)
ECS::get().modified<ECS::Input>();
});
}
}
}

View File

@@ -16,10 +16,12 @@
#include "Components.h"
#include "CharacterModule.h"
#include "TerrainModule.h"
#include "GUIModule.h"
#include "GUIModuleCommon.h"
#include "AppModule.h"
#include "EditorGizmoModule.h"
#include "EditorInputModule.h"
#include "PhysicsModule.h"
#include "physics.h"
#include "sound.h"
class App;
@@ -296,15 +298,71 @@ public:
void frameRendered(const Ogre::FrameEvent &evt) override;
};
class App : public OgreBites::ApplicationContext {
Ogre::SceneNode *mCameraNode, *mCameraPivot, *mCameraGoal;
Ogre::Camera *mCamera;
Ogre::Real mPivotPitch;
struct SceneData {
Ogre::SceneManager *mScnMgr;
Ogre::Camera *mCamera;
Ogre::SceneNode *mCameraNode, *mCameraPivot, *mCameraGoal;
Ogre::Real mPivotPitch;
SceneData()
: mScnMgr(nullptr)
, mCamera(nullptr)
, mCameraNode(nullptr)
, mCameraPivot(nullptr)
, mCameraGoal(nullptr)
{
}
void setup(Ogre::Root *root, Ogre::OverlaySystem *overlaySystem)
{
Ogre::SceneManager *scnMgr = root->createSceneManager();
mScnMgr = scnMgr;
mScnMgr->addRenderQueueListener(overlaySystem);
}
void initCamera(const Ogre::String &cameraName)
{
mCameraNode = mScnMgr->getRootSceneNode()->createChildSceneNode(
cameraName + "CameraNode");
mCameraNode->setPosition(0, 2, 3);
mCameraNode->lookAt(Ogre::Vector3(0, 1, -1),
Ogre::Node::TS_PARENT);
// create the camera
mCamera = mScnMgr->createCamera(cameraName);
mCamera->setNearClipDistance(0.05f);
mCamera->setAutoAspectRatio(true);
mCameraNode->attachObject(mCamera);
mCameraPivot =
mScnMgr->getRootSceneNode()->createChildSceneNode(
cameraName + "FPSCameraPivot");
mCameraGoal = mCameraPivot->createChildSceneNode(
cameraName + "FPSCameraGoal", Ogre::Vector3(0, 2, 3));
mCameraNode->setPosition(mCameraPivot->getPosition() +
mCameraGoal->getPosition());
mCameraPivot->setFixedYawAxis(true);
mCameraGoal->setFixedYawAxis(true);
mCameraNode->setFixedYawAxis(true);
// our model is quite small, so reduce the clipping planes
mCamera->setNearClipDistance(0.1f);
mCamera->setFarClipDistance(800);
mPivotPitch = 0;
}
void setupRTSS()
{
Ogre::RTShader::ShaderGenerator *shadergen =
Ogre::RTShader::ShaderGenerator::getSingletonPtr();
shadergen->addSceneManager(mScnMgr);
}
Ogre::SceneManager *getSceneManager()
{
return mScnMgr;
}
};
class App : public OgreBites::ApplicationContext {
Ogre::Viewport *mViewport;
SkyBoxRenderer *sky;
bool mGrab;
KeyboardListener mKbd;
SceneData mEditorNormalScene;
public:
App()
@@ -316,16 +374,11 @@ public:
virtual ~App()
{
}
void setup()
void setup() override
{
OgreBites::ApplicationContext::setup();
Ogre::Root *root = getRoot();
Ogre::SceneManager *scnMgr = root->createSceneManager();
mScnMgr = scnMgr;
Ogre::OverlaySystem *pOverlaySystem = getOverlaySystem();
mScnMgr->addRenderQueueListener(pOverlaySystem);
// mTrayMgr = new OgreBites::TrayManager("AppTrays",
// getRenderWindow());
mEditorNormalScene.setup(root, getOverlaySystem());
}
bool isWindowGrab()
{
@@ -353,77 +406,41 @@ public:
void initCamera()
{
mCameraNode = mScnMgr->getRootSceneNode()->createChildSceneNode(
"CameraNode");
mCameraNode->setPosition(0, 2, 3);
mCameraNode->lookAt(Ogre::Vector3(0, 1, -1),
Ogre::Node::TS_PARENT);
// create the camera
mCamera = mScnMgr->createCamera("fps_camera");
mCamera->setNearClipDistance(0.05f);
mCamera->setAutoAspectRatio(true);
mCameraNode->attachObject(mCamera);
// and tell it to render into the main window
mViewport = getRenderWindow()->addViewport(mCamera);
mCameraPivot =
mScnMgr->getRootSceneNode()->createChildSceneNode(
"FPSCameraPivot");
mCameraGoal = mCameraPivot->createChildSceneNode(
"FPSCameraGoal", Ogre::Vector3(0, 2, 3));
mCameraNode->setPosition(mCameraPivot->getPosition() +
mCameraGoal->getPosition());
mCameraPivot->setFixedYawAxis(true);
mCameraGoal->setFixedYawAxis(true);
mCameraNode->setFixedYawAxis(true);
// our model is quite small, so reduce the clipping planes
mCamera->setNearClipDistance(0.1f);
mCamera->setFarClipDistance(800);
mPivotPitch = 0;
mEditorNormalScene.initCamera("fps_camera");
mViewport = getRenderWindow()->addViewport(
mEditorNormalScene.mCamera);
}
void configure()
{
std::cout << "Startup" << "\n";
std::cout << "Startup"
<< "\n";
initApp();
std::cout << "Set up RTSS" << "\n";
std::cout << "Set up RTSS"
<< "\n";
Ogre::Root *root = getRoot();
Ogre::SceneManager *scnMgr = getSceneManager();
// register our scene with the RTSS
Ogre::RTShader::ShaderGenerator *shadergen =
Ogre::RTShader::ShaderGenerator::getSingletonPtr();
shadergen->addSceneManager(scnMgr);
mEditorNormalScene.setupRTSS();
setWindowGrab(false);
std::cout << "Init camera" << "\n";
std::cout << "Init camera"
<< "\n";
initCamera();
std::cout << "Set up water" << "\n";
std::cout << "Set up cursor" << "\n";
Ogre::ResourceGroupManager::getSingleton()
.initialiseAllResourceGroups();
// OgreBites::ApplicationContext::loadResources();
// setupCursor();
std::cout << "Setup input" << "\n";
std::cout << "Setup input"
<< "\n";
setupInput();
std::cout << "Create content" << "\n";
std::cout << "Create content"
<< "\n";
createContent();
std::cout << "Setup done" << "\n";
#if 0
mDbgDraw->setDebugMode(mDbgDraw->getDebugMode() |
btIDebugDraw::DBG_DrawContactPoints);
#endif
}
Ogre::SceneManager *getSceneManager()
{
return mScnMgr;
}
std::cout << "Setup done"
<< "\n";
Ogre::LogManager::getSingleton().setMinLogLevel(
Ogre::LML_TRIVIAL);
}
Ogre::Timer mTerrainUpd;
// 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)
@@ -572,10 +589,15 @@ public:
void setupInput()
{
}
void createContent()
bool switchWindow = false;
JoltPhysicsWrapper *mJolt;
void createContent()
{
int i;
sky = new SkyBoxRenderer(getSceneManager());
mJolt = new JoltPhysicsWrapper(
mEditorNormalScene.getSceneManager(),
mEditorNormalScene.mCameraNode);
sky = new SkyBoxRenderer(mEditorNormalScene.getSceneManager());
bool drawFirst = true;
uint8_t renderQueue = drawFirst ?
Ogre::RENDER_QUEUE_SKIES_EARLY :
@@ -591,10 +613,16 @@ public:
"Skybox/Dynamic", "General");
OgreAssert(m, "Sky box material not found.");
m->load();
ECS::setupEditor(mScnMgr, /*mDynWorld.get(), */ mCameraNode,
mCamera, getRenderWindow());
ECS::get().import <ECS::EditorGizmoModule>();
ECS::get().import <ECS::EditorInputModule>();
ECS::setupEditor(
mEditorNormalScene.mScnMgr,
/*mDynWorld.get(), */ mEditorNormalScene.mCameraNode,
mEditorNormalScene.mCamera, getRenderWindow());
ECS::get().import <ECS::PhysicsModule>();
ECS::get().import <ECS::EditorGizmoModule>();
ECS::get().import <ECS::EditorInputModule>();
ECS::Physics &ph = ECS::get().ensure<ECS::Physics>();
ph.physics = mJolt;
ECS::modified<ECS::Physics>();
ECS::get().set<ECS::RenderWindow>(
{ getRenderWindow(), getDisplayDPI() });
ECS::get()
@@ -625,86 +653,14 @@ public:
{ getImGuiInputListener(), &mKbd } });
ECS::get().add<ECS::EditorDebugMaterial>();
std::shared_ptr<Ogre::ManualObject> manualObj(
mScnMgr->createManualObject("EditorGizmo"));
mEditorNormalScene.mScnMgr->createManualObject(
"EditorGizmo"));
Ogre::SceneNode *gizmoNode =
mScnMgr->getRootSceneNode()->createChildSceneNode(
"EditorGizmoNode");
mEditorNormalScene.mScnMgr->getRootSceneNode()
->createChildSceneNode("EditorGizmoNode");
gizmoNode->attachObject(manualObj.get());
manualObj->setRenderQueueGroup(Ogre::RENDER_QUEUE_OVERLAY);
ECS::get().set<ECS::EditorGizmo>({ manualObj, gizmoNode });
#if 0
ECS::get()
.system<const ECS::EngineData, ECS::Camera,
const ECS::Input>("UpdateEditorCamera")
.kind(flecs::OnUpdate)
.each([&](const ECS::EngineData &eng,
ECS::Camera &camera,
const ECS::Input &input) {
if (!camera.configured) {
// create a pivot at roughly the character's shoulder
camera.mCameraPivot =
eng.mScnMgr->getRootSceneNode()
->createChildSceneNode();
camera.mCameraGoal =
camera.mCameraPivot
->createChildSceneNode(
Ogre::Vector3(
0, 2,
0));
camera.mCameraNode->setPosition(
camera.mCameraPivot
->getPosition() +
camera.mCameraGoal
->getPosition());
camera.mCameraGoal->lookAt(
Ogre::Vector3(0, 0, -10),
Ogre::Node::TS_PARENT);
camera.mCameraPivot->setFixedYawAxis(
true);
camera.mCameraGoal->setFixedYawAxis(
true);
camera.mCameraNode->setFixedYawAxis(
true);
// our model is quite small, so reduce the clipping planes
camera.mCamera->setNearClipDistance(
0.1f);
camera.mCamera->setFarClipDistance(700);
camera.mPivotPitch = 0;
camera.configured = true;
} else {
// place the camera pivot roughly at the character's shoulder
#if 0
camera.mCameraPivot->setPosition(
ch.mBodyNode->getPosition() +
Ogre::Vector3::UNIT_Y * CAM_HEIGHT);
#endif
// move the camera smoothly to the goal
Ogre::Vector3 goalOffset =
camera.mCameraGoal
->_getDerivedPosition() -
camera.mCameraNode
->getPosition();
camera.mCameraNode->translate(
goalOffset * eng.delta * 9.0f);
// always look at the pivot
#if 0
camera.mCameraNode->lookAt(
camera.mCameraPivot
->_getDerivedPosition(),
Ogre::Node::TS_PARENT);
#endif
camera.mCameraNode->_setDerivedOrientation(
camera.mCameraGoal
->_getDerivedOrientation());
}
if (input.control & 512 && input.mouse_moved) {
mCameraPivot->yaw(
Ogre::Radian(-input.mouse.x *
3.0f * eng.delta));
}
});
#endif
}
bool get_gui_active()
{
@@ -715,16 +671,6 @@ public:
ECS::get().get_mut<ECS::GUI>().enabled = active;
ECS::get().modified<ECS::GUI>();
}
Ogre::Camera *getCamera()
{
return mCamera;
}
flecs::entity getPlayer() const
{
flecs::entity player =
ECS::get().lookup("ECS::CharacterModule::player");
return player;
}
void enableDbgDraw(bool enable)
{
ECS::get_mut<ECS::EngineData>().enableDbgDraw = enable;
@@ -789,7 +735,7 @@ int main()
{
App ctx;
ctx.configure();
ctx.enableDbgDraw(false);
// ctx.enableDbgDraw(false);
ctx.getRoot()->startRendering();
ctx.setWindowGrab(false);
ctx.closeApp();

View File

@@ -3,17 +3,20 @@ set(CMAKE_CXX_STANDARD 17)
find_package(OGRE REQUIRED COMPONENTS Bites Bullet Paging Terrain Overlay CONFIG)
find_package(Bullet REQUIRED)
find_package(nlohmann_json REQUIRED)
find_package(OgreProcedural REQUIRED CONFIG)
add_subdirectory(items)
add_library(GameData STATIC GameData.cpp CharacterModule.cpp WaterModule.cpp SunModule.cpp TerrainModule.cpp
GUIModule.cpp LuaData.cpp WorldMapModule.cpp BoatModule.cpp EventTriggerModule.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 SmartObject.cpp SlotsModule.cpp goap.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
flecs::flecs_static
nlohmann_json::nlohmann_json
OgreMain
OgreBites
OgrePaging OgreTerrain OgreOverlay
OgrePaging OgreTerrain OgreOverlay OgreProcedural::OgreProcedural items
PRIVATE sceneloader world-build physics editor)
target_include_directories(GameData PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${BULLET_INCLUDE_DIR} ../luaaa)
target_include_directories(GameData PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${BULLET_INCLUDE_DIR} ../luaaa ../aitoolkit/include)
target_compile_definitions(GameData PRIVATE FLECS_CPP_NO_AUTO_REGISTRATION)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,181 @@
#ifndef CHARACTERAIMODULE_H
#define CHARACTERAIMODULE_H
#include <vector>
#include <flecs.h>
#include <nlohmann/json.hpp>
#include "goap.h"
namespace ECS
{
struct TownAI;
struct Blackboard {
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;
Ogre::Vector3 position;
std::vector<size_t> points;
std::vector<float> distances;
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();
Blackboard(const nlohmann::json &stats);
bool operator==(const Blackboard &other) const;
bool operator!=(const Blackboard &other) const;
void apply(const Blackboard &other);
Ogre::String dumpActions();
void printActions();
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() 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,
const nlohmann::json &target,
float epsilon = 1e-4);
public:
int distance_to(const Blackboard &goal) const;
void setPosition(const Ogre::Vector3 &position);
const Ogre::Vector3 &getPosition() const
{
return position;
}
void query_ai();
};
struct TownNPCs;
struct ActionNodeList;
struct TownAI {
std::shared_ptr<std::mutex> mutex;
std::vector<goap::BasePlanner<Blackboard,
goap::BaseAction<Blackboard> >::BaseGoal>
goals;
std::vector<goap::BaseAction<Blackboard> *> actions;
std::shared_ptr<
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> >
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;
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 {
CharacterAIModule(flecs::world &ecs);
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 updateBlackboards(flecs::entity town, ActionNodeList &alist, const TownNPCs &npcs, TownAI &ai);
};
}
#endif // CHARACTERAIMODULE_H

View File

@@ -22,38 +22,28 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
.kind(flecs::OnUpdate)
.each([this](flecs::entity e, const CharacterBase &ch,
AnimationControl &anim) {
if (!anim.configured && ch.mSkeleton) {
if (!anim.configured) {
int i, j;
e.set<EventData>({});
ch.mSkeleton->setBlendMode(
ch.mBodyEnt->getSkeleton()->setBlendMode(
Ogre::ANIMBLEND_CUMULATIVE);
Ogre::String animNames[] = {
"idle",
"walking",
"running",
"treading_water",
"swimming",
"hanging-idle",
"hanging-climb",
"swimming-hold-edge",
"swimming-edge-climb",
"character-talk",
"pass-character",
"idle-act",
"sitting-chair",
"sitting-ground"
};
int state_count = sizeof(animNames) /
sizeof(animNames[0]);
anim.mAnimationSystem =
new AnimationSystem(false);
for (i = 0; i < state_count; i++) {
Ogre::AnimationStateSet *animStateSet =
ch.mBodyEnt->getAllAnimationStates();
const Ogre::AnimationStateMap &animMap =
animStateSet->getAnimationStates();
anim.mAnimationSystem =
new AnimationSystem(false);
ch.mBodyEnt->getSkeleton()
->getBone("Root")
->removeAllChildren();
for (auto it = animMap.begin();
it != animMap.end(); it++) {
Animation *animation = new Animation(
ch.mSkeleton,
ch.mBodyEnt->getAnimationState(
animNames[i]),
ch.mSkeleton->getAnimation(
animNames[i]),
ch.mBodyEnt->getSkeleton(),
it->second,
ch.mBodyEnt->getSkeleton()
->getAnimation(
it->first),
e);
#ifdef VDEBUG
std::cout
@@ -62,7 +52,7 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
#endif
animation->setLoop(true);
anim.mAnimationSystem->add_animation(
animNames[i], animation);
it->first, animation);
}
anim.mAnimationSystem
->builder()
@@ -125,7 +115,10 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
->state("sitting-ground")
->animation("sitting-ground")
->end()
->transition_end("swimming-edge-climb", "idle")
->state("sitting-chair")
->animation("sitting-chair")
->end()
->transition_end("swimming-edge-climb", "idle")
->transition_end("hanging-climb", "idle")
->transition_end("pass-character", "idle")
->end()
@@ -157,10 +150,39 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
CharacterBase &ch, AnimationControl &anim) {
float delta = eng.delta;
// ch.mBoneMotion = Ogre::Vector3::ZERO;
bool result = anim.mAnimationSystem->addTime(delta);
if (!ch.mRootBone)
if (!anim.mAnimationSystem)
return;
// The value we get is interpolated value. When result is true it is new step
#if 0
ch.mBodyEnt->getSkeleton()->getBone("Root")->reset();
ch.mBodyEnt->getSkeleton()->getBone("Root")->setPosition(
Ogre::Vector3::ZERO);
#endif
bool result = anim.mAnimationSystem->addTime(delta);
Ogre::Vector3 rootMotion =
anim.mAnimationSystem->getRootMotionDelta();
ch.mBonePrevMotion = ch.mBoneMotion;
ch.mBoneMotion = rootMotion;
ch.mBodyEnt->_updateAnimation();
ch.mBodyEnt->getSkeleton()->getBone("Root")->setPosition(
Ogre::Vector3::ZERO);
#if 0
Ogre::Vector3 deltaMotion;
ch.mBodyEnt->_updateAnimation();
std::cout << "motion: " << ch.mBoneMotion << " "
<< rootMotion << " " << ch.mBonePrevMotion
<< std::endl;
rootMotion = ch.mBodyEnt->getSkeleton()
->getBone("Root")
->getPosition();
if (rootMotion.squaredLength() <
ch.mBoneMotion.squaredLength())
deltaMotion = rootMotion;
else
deltaMotion = rootMotion - ch.mBoneMotion;
ch.mBonePrevMotion = ch.mBoneMotion;
ch.mBoneMotion = deltaMotion;
#endif
// The value we get is interpolated value. When result is true it is new step
#if 0
Ogre::Vector3 offset = ch.mRootBone->getPosition();
ch.mBoneMotion = static_cast<RootMotionListener *>(
@@ -237,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;
@@ -302,54 +315,7 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
Ogre::Vector3 colNormal;
bool is_on_floor = false;
bool penetration = false;
#if 0
if (eng.startupDelay < 0.0f) {
if (body.mController) {
Ogre::Vector3 rotMotion =
v.velocity * eng.delta;
rotMotion.x = Ogre::Math::Clamp(
rotMotion.x, -0.04f, 0.04f);
rotMotion.y = Ogre::Math::Clamp(
rotMotion.y, -0.025f, 0.1f);
rotMotion.z = Ogre::Math::Clamp(
rotMotion.z, -0.04f, 0.04f);
btVector3 currentPosition =
body.mGhostObject
->getWorldTransform()
.getOrigin();
is_on_floor =
body.mController->isOnFloor();
penetration = body.mController
->isPenetrating();
if (is_on_floor)
v.gvelocity =
Ogre::Vector3::ZERO;
btTransform from(
Ogre::Bullet::convert(
ch.mBodyNode
->getOrientation()),
Ogre::Bullet::convert(
ch.mBodyNode
->getPosition()));
ch.mBodyNode->_setDerivedPosition(
ch.mBodyNode
->_getDerivedPosition() +
rotMotion);
}
}
#endif
});
#if 0
ecs.system<CharacterVelocity, CharacterBase>("HandleRootMotionEnd")
.kind(flecs::OnUpdate)
.each([this](flecs::entity e, CharacterVelocity &v,
CharacterBase &ch) {
// zero the velocity;
// v.velocity = Ogre::Vector3::ZERO;
// ch.mBoneMotion = Ogre::Vector3::ZERO;
});
#endif
ecs.system<const Input, const CharacterBase, AnimationControl>(
"HandleNPCAnimations")
@@ -360,6 +326,8 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
const CharacterBase &ch, AnimationControl &anim) {
if (!anim.configured)
return;
if (!anim.mAnimationSystem)
return;
AnimationNodeStateMachine *state_machine =
anim.mAnimationSystem
->get<AnimationNodeStateMachine>(
@@ -401,10 +369,13 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
.kind(flecs::OnUpdate)
.with<Character>()
.without<CharacterInActuator>()
.each([](flecs::entity e, const CharacterBase &ch,
.without<CharacterControlDisable>()
.each([](flecs::entity e, const CharacterBase &ch,
AnimationControl &anim) {
if (!anim.configured)
return;
if (!anim.mAnimationSystem)
return;
AnimationNodeStateMachine *main_sm =
anim.mAnimationSystem
->get<AnimationNodeStateMachine>(
@@ -419,6 +390,7 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
.with<Character>()
.with<Player>()
.without<CharacterInActuator>()
.without<CharacterControlDisable>()
.each([](flecs::entity e, const Input &input,
const CharacterBase &ch, AnimationControl &anim) {
if (!anim.configured)
@@ -498,6 +470,7 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
.kind(flecs::OnUpdate)
.with<Character>()
.with<Player>()
.without<CharacterControlDisable>()
.each([](flecs::entity e, const Input &input,
const CharacterBase &ch, AnimationControl &anim,
CharacterInActuator &act) {
@@ -568,15 +541,6 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
act.prevMotion.z = input.motion.z;
}
#if 0
if (!controls_idle) {
if (Ogre::Math::Abs(input.motion.z - act.prevMotion.z) > 0.001f) {
if (input.motion.z < 0)
ECS::get_mut<LuaData>().call_handler("actuator_forward", e);
}
ECS::get_mut<LuaData>().call_handler("actuator_controls_update");
}
#endif
act.prevMotion = input.motion;
});
ecs.system<EventData>("UpdateEvents")
@@ -616,87 +580,6 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
<< "h=" << ch.mBodyNode->_getDerivedPosition().y
<< std::endl;
});
#endif
#if 0
ecs.system<const EngineData, const CharacterBase, CharacterBody>(
"UpdateBodyCast")
.kind(flecs::OnUpdate)
.without<CharacterInActuator>()
.each([](flecs::entity e, const EngineData &eng,
const CharacterBase &ch, CharacterBody &body) {
struct ResultCallback
: public btCollisionWorld::RayResultCallback {
btCollisionObject *m_me;
btVector3 m_from, m_to, m_hitNormalWorld,
m_hitPointWorld;
ResultCallback(btCollisionObject *me,
const btVector3 &from,
const btVector3 &to)
: m_me(me)
, m_from(from)
, m_to(to)
{
}
btScalar addSingleResult(
btCollisionWorld::LocalRayResult
&rayResult,
bool normalInWorldSpace) override
{
if (rayResult.m_collisionObject == m_me)
return 1.0f;
if (!btPairCachingGhostObject::upcast(
rayResult.m_collisionObject))
return 1.0f;
if (!(rayResult.m_collisionObject
->getCollisionFlags() &
btCollisionObject::
CF_CHARACTER_OBJECT))
return 1.0f;
m_closestHitFraction =
rayResult.m_hitFraction;
m_collisionObject =
rayResult.m_collisionObject;
if (normalInWorldSpace)
m_hitNormalWorld =
rayResult
.m_hitNormalLocal;
else
m_hitNormalWorld =
m_collisionObject
->getWorldTransform()
.getBasis() *
rayResult
.m_hitNormalLocal;
m_hitPointWorld.setInterpolate3(
m_from, m_to,
rayResult.m_hitFraction);
return rayResult.m_hitFraction;
}
};
Ogre::Vector3 offset(0.0f, 0.5f, 0.0f);
float dist = 0.5f;
btVector3 a = Ogre::Bullet::convert(
ch.mBodyNode->getPosition() + offset),
b(Ogre::Bullet::convert(
ch.mBodyNode->getPosition() +
ch.mBodyNode->getOrientation() *
Ogre::Vector3(0, 0, dist) +
offset));
ResultCallback result(body.mGhostObject, a, b);
// body.mGhostObject->rayTest(a, b, result);
eng.mWorld->getBtWorld()->rayTest(a, b, result);
if (result.hasHit()) {
std::cout << "Hit!!! " << result.m_hitPointWorld
<< std::endl;
e.set<CharacterInActuator>(
{ "idle", { 0, 0, 0 } });
ECS::get<LuaBase>().mLua->call_handler(
"character_enter", e,
ECS::get<Body2Entity>().entities.at(
const_cast<btCollisionObject *>(
result.m_collisionObject)));
}
});
#endif
struct AnimationSetCommand : public GameWorld::Command {
int operator()(const std::vector<GameWorld::Parameter *> &args)

View File

@@ -9,22 +9,16 @@
namespace ECS
{
class RootMotionListener : public Ogre::NodeAnimationTrack::Listener {
Ogre::Vector3 prevTranslation;
mutable Ogre::Vector3 deltaMotion;
flecs::entity e;
public:
RootMotionListener(flecs::entity e)
: Ogre::NodeAnimationTrack::Listener()
, e(e)
, prevTranslation(Ogre::Vector3::ZERO)
, deltaMotion(Ogre::Vector3::ZERO)
{
}
bool getInterpolatedKeyFrame(const Ogre::AnimationTrack *t,
const Ogre::TimeIndex &timeIndex,
Ogre::KeyFrame *kf) override
{
#if 0
Ogre::TransformKeyFrame *vkf =
static_cast<Ogre::TransformKeyFrame *>(kf);
Ogre::KeyFrame *kf1, *kf2;
@@ -36,14 +30,12 @@ public:
k2 = static_cast<Ogre::TransformKeyFrame *>(kf2);
Ogre::Vector3 translation;
Ogre::Quaternion rotation;
if (tm == 0.0f) {
Ogre::Vector3 deltaMotion;
Ogre::Vector3 prevMotion;
if (tm == 0.0f) {
rotation = k1->getRotation();
translation = k1->getTranslate();
deltaMotion = translation;
// vkf->setRotation(k1->getRotation());
// vkf->setTranslate(k1->getTranslate());
// vkf->setScale(k1->getScale());
} else {
rotation = Ogre::Quaternion::nlerp(
tm, k1->getRotation(), k2->getRotation(), true);
@@ -55,14 +47,7 @@ public:
translation.squaredLength())
deltaMotion = translation;
}
#if 0
std::cout << "time: " << tm
<< " Position: " << deltaMotion;
std::cout << " Quaternion: " << rotation;
std::cout << std::endl;
#endif
vkf->setTranslate(deltaMotion);
// vkf->setTranslate(translation);
vkf->setRotation(rotation);
vkf->setScale(Ogre::Vector3(1, 1, 1));
prevTranslation = translation;
@@ -70,7 +55,9 @@ public:
e.get_mut<CharacterBase>().mBonePrevMotion = prevTranslation;
e.modified<CharacterBase>();
return true;
}
#endif
return false;
}
};
struct AnimationTrigger;
struct AnimationTriggerSubscriber {
@@ -126,46 +113,66 @@ struct AnimationTrigger {
{
}
};
struct SetupTracks {
Ogre::NodeAnimationTrack *mHipsTrack;
Ogre::NodeAnimationTrack *mRootTrack;
RootMotionListener *mListener;
Ogre::Vector3 mRootTranslation;
SetupTracks(flecs::entity e, Ogre::Skeleton *skeleton,
Ogre::Animation *animation)
: mListener(OGRE_NEW RootMotionListener(e))
{
mHipsTrack = nullptr;
mRootTrack = nullptr;
for (const auto &it : animation->_getNodeTrackList()) {
Ogre::NodeAnimationTrack *track = it.second;
Ogre::String trackName =
track->getAssociatedNode()->getName();
if (trackName == "mixamorig:Hips") {
mHipsTrack = track;
} else if (trackName == "Root") {
mRootTrack = track;
// mRootTracks[i]->removeAllKeyFrames();
}
}
if (!mRootTrack) {
Ogre::Bone *bone = skeleton->getBone("Root");
mRootTrack = animation->createNodeTrack(
bone->getHandle(), bone);
Ogre::TransformKeyFrame *kf =
mRootTrack->createNodeKeyFrame(0.0f);
kf->setTranslate(Ogre::Vector3::ZERO);
kf->setRotation(Ogre::Quaternion::IDENTITY);
}
// if (e.has<Player>()) // FIXME
// mRootTrack->setListener(mListener);
Ogre::TransformKeyFrame *tkfBeg =
(Ogre::TransformKeyFrame *)mRootTrack->getKeyFrame(0);
Ogre::TransformKeyFrame *tkfEnd =
(Ogre::TransformKeyFrame *)mRootTrack->getKeyFrame(
mRootTrack->getNumKeyFrames() - 1);
mRootTranslation =
tkfEnd->getTranslate() - tkfBeg->getTranslate();
}
};
struct Animation {
Ogre::AnimationState *mAnimationState;
Ogre::Animation *mSkelAnimation;
Ogre::NodeAnimationTrack *mHipsTrack;
Ogre::NodeAnimationTrack *mRootTrack;
RootMotionListener *mListener;
SetupTracks *mTracks;
float m_weight;
float m_accWeight;
flecs::entity e;
Ogre::Skeleton *mSkeleton;
Animation(Ogre::Skeleton *skeleton, Ogre::AnimationState *animState,
Ogre::Animation *skelAnimation, flecs::entity e)
: mAnimationState(animState)
: mTracks(OGRE_NEW SetupTracks(e, skeleton, skelAnimation))
, mAnimationState(animState)
, mSkelAnimation(skelAnimation)
, mListener(OGRE_NEW RootMotionListener(e))
, m_weight(0)
, m_accWeight(0)
, e(e)
, mSkeleton(skeleton)
{
int j;
mRootTrack = nullptr;
mHipsTrack = nullptr;
for (const auto &it : mSkelAnimation->_getNodeTrackList()) {
Ogre::NodeAnimationTrack *track = it.second;
Ogre::String trackName =
track->getAssociatedNode()->getName();
if (trackName == "mixamorig:Hips") {
mHipsTrack = track;
} else if (trackName == "Root") {
mRootTrack = track;
// mRootTracks[i]->removeAllKeyFrames();
}
}
if (!mRootTrack) {
Ogre::Bone *bone = skeleton->getBone("Root");
mRootTrack = mSkelAnimation->createNodeTrack(
bone->getHandle(), bone);
Ogre::TransformKeyFrame *kf =
mRootTrack->createNodeKeyFrame(0.0f);
kf->setTranslate(Ogre::Vector3::ZERO);
kf->setRotation(Ogre::Quaternion::IDENTITY);
}
mRootTrack->setListener(mListener);
}
Ogre::String getName()
{
@@ -198,22 +205,134 @@ struct Animation {
{
return m_weight;
}
bool addTime(float time)
Ogre::Vector3 rootMotion;
Ogre::Vector3 getRootMotionDelta()
{
#if 0
Ogre::KeyFrame *kf1, *kf2;
Ogre::TransformKeyFrame *k1, *k2;
unsigned short firstKeyIndex;
Ogre::Real timePos = mAnimationState->getTimePosition();
Ogre::TimeIndex index = mSkelAnimation->_getTimeIndex(timePos);
float tm = mTracks->mRootTrack->getKeyFramesAtTime(
index, &kf1, &kf2, &firstKeyIndex);
k1 = static_cast<Ogre::TransformKeyFrame *>(kf1);
k2 = static_cast<Ogre::TransformKeyFrame *>(kf2);
Ogre::Vector3 translation;
Ogre::Quaternion rotation;
if (tm == 0.0f) {
rotation = k1->getRotation();
translation = k1->getTranslate();
} else {
rotation = Ogre::Quaternion::nlerp(
tm, k1->getRotation(),
k2->getRotation(), true);
translation = k1->getTranslate() +
(k2->getTranslate() -
k1->getTranslate()) *
tm;
}
return translation * mAnimationState->getWeight();
#endif
if (mAnimationState->getEnabled())
return rootMotion * mAnimationState->getWeight();
else
return Ogre::Vector3(0, 0, 0);
}
#if 0
void updateRootMotion(Ogre::Real timePos)
{
Ogre::KeyFrame *kf1, *kf2;
Ogre::TransformKeyFrame *k1, *k2;
unsigned short firstKeyIndex;
mSkeleton->getBone("Root")->setManuallyControlled(true);
Ogre::TimeIndex index = mSkelAnimation->_getTimeIndex(timePos);
float tm = mTracks->mRootTrack->getKeyFramesAtTime(
index, &kf1, &kf2, &firstKeyIndex);
k1 = static_cast<Ogre::TransformKeyFrame *>(kf1);
k2 = static_cast<Ogre::TransformKeyFrame *>(kf2);
Ogre::Vector3 translation;
Ogre::Quaternion rotation;
Ogre::Vector3 deltaMotion =
e.get_mut<CharacterBase>().mBoneMotion;
Ogre::Vector3 prevTranslation =
e.get_mut<CharacterBase>().mBonePrevMotion;
if (tm == 0.0f) {
rotation = k1->getRotation() * m_weight;
translation = k1->getTranslate() * m_weight;
deltaMotion = translation;
} else {
rotation = Ogre::Quaternion::nlerp(
tm, k1->getRotation() * m_weight,
k2->getRotation() * m_weight, true);
translation = k1->getTranslate() * m_weight +
(k2->getTranslate() * m_weight -
k1->getTranslate() * m_weight) *
tm;
deltaMotion = translation - prevTranslation;
if (deltaMotion.squaredLength() >
translation.squaredLength())
deltaMotion = translation;
}
e.get_mut<CharacterBase>().mBoneMotion = deltaMotion;
e.get_mut<CharacterBase>().mBonePrevMotion = prevTranslation;
e.modified<CharacterBase>();
#if 1
if (timePos > 0.5f && m_weight > 0.2 && translation.squaredLength() > 0.0f) {
std::cout << timePos << " " << m_weight << " "
<< e.get<CharacterBase>()
.mBodyEnt->getMesh()
->getName()
<< " " << deltaMotion << " "
<< prevTranslation << std::endl;
std::cout << translation << " " << rotation << std::endl;
// OgreAssert(false, "updateRootMotion");
}
#endif
mTracks->mRootTrack->getAssociatedNode()->setPosition(
Ogre::Vector3());
mSkeleton->getBone("Root")->reset();
}
#endif
void getKeyframeIndices(Ogre::Real timePos, unsigned short *pindex)
{
Ogre::TimeIndex index = mSkelAnimation->_getTimeIndex(timePos);
Ogre::KeyFrame *kf1, *kf2;
mTracks->mRootTrack->getKeyFramesAtTime(index, &kf1, &kf2, pindex);
}
bool addTime(float time)
{
bool result = mAnimationState->getEnabled();
if (!result)
return result;
Ogre::TimeIndex index = mSkelAnimation->_getTimeIndex(
mAnimationState->getTimePosition());
Ogre::KeyFrame *kf1, *kf2;
unsigned short prev_index, next_index;
mRootTrack->getKeyFramesAtTime(index, &kf1, &kf2, &prev_index);
unsigned short prev_index, next_index;
Ogre::TimeIndex index = mSkelAnimation->_getTimeIndex(mAnimationState->getTimePosition());
getKeyframeIndices(mAnimationState->getTimePosition(), &prev_index);
unsigned int previous_frame = index.getKeyIndex();
float lastTime = mAnimationState->getTimePosition();
mAnimationState->addTime(time);
index = mSkelAnimation->_getTimeIndex(
mAnimationState->getTimePosition());
mRootTrack->getKeyFramesAtTime(index, &kf1, &kf2, &next_index);
return prev_index != next_index;
float thisTime = mAnimationState->getTimePosition();
float length = mAnimationState->getLength();
bool loop = mAnimationState->getLoop();
int loops = loop ? (int)std::round((lastTime + time - thisTime) / length) : 0;
Ogre::TransformKeyFrame tkf(0, 0);
mTracks->mRootTrack->getInterpolatedKeyFrame(lastTime, &tkf);
Ogre::Vector3 lastRootPos = tkf.getTranslate();
mTracks->mRootTrack->getInterpolatedKeyFrame(thisTime, &tkf);
Ogre::Vector3 thisRootPos = tkf.getTranslate();
#if 0
if (thisTime > lastTime)
rootMotion = thisRootPos - lastRootPos;
else
rootMotion = mTracks->mRootTranslation + thisRootPos - lastRootPos;
#else
rootMotion = (thisRootPos - lastRootPos) + (loops * mTracks->mRootTranslation);
#endif
getKeyframeIndices(mAnimationState->getTimePosition(), &next_index);
// updateRootMotion(mAnimationState->getTimePosition());
return prev_index != next_index;
}
void reset()
{
@@ -827,6 +946,14 @@ struct AnimationSystem : AnimationNode {
}
};
AnimationSystemBuilder m_builder;
Ogre::Vector3 getRootMotionDelta()
{
Ogre::Vector3 motionDelta(0, 0, 0);
int i;
for (i = 0; i < vanimation_list.size(); i++)
motionDelta += vanimation_list[i]->getRootMotionDelta();
return motionDelta;
}
bool addTime(float time)
{
int i;

View File

@@ -4,26 +4,192 @@
#include "Components.h"
#include "CharacterModule.h"
#include "CharacterAnimationModule.h"
#include "StaticGeometryModule.h"
#include "PhysicsModule.h"
#include "PlayerActionModule.h"
#include "items.h"
#include "CharacterManagerModule.h"
namespace ECS
{
void createNPCActionNodes(flecs::entity town, int index)
{
TownNPCs &npcs = town.get_mut<TownNPCs>();
TownNPCs::NPCData &npc = npcs.npcs.at(index);
flecs::entity e = npc.e;
nlohmann::json npcprops = npc.props;
const CharacterBase &ch = e.get<CharacterBase>();
Ogre::Vector3 characterPos = ch.mBodyNode->_getDerivedPosition();
Ogre::Quaternion characterRot = ch.mBodyNode->_getDerivedOrientation();
if (npc.actionNodes.size() > 0) {
int i;
for (i = 0; i < npc.actionNodes.size(); i++) {
auto &anode = npc.actionNodes[i];
Ogre::Vector3 offset = Ogre::Vector3::UNIT_Z * 0.3f +
Ogre::Vector3::UNIT_Y;
if (i == 1)
offset = Ogre::Vector3::NEGATIVE_UNIT_Z * 0.3f +
Ogre::Vector3::UNIT_Y;
anode.position = characterPos + characterRot * offset;
anode.rotation = characterRot;
to_json(anode.props["position"], anode.position);
to_json(anode.props["rotation"], anode.rotation);
}
return;
}
{
ActionNodeList::ActionNode anode;
anode.props["action"] = "talk";
anode.props["action_text"] = "Talk";
anode.action = "talk";
anode.action_text = "Talk";
Ogre::Vector3 offset = Ogre::Vector3::UNIT_Z * 0.25f +
Ogre::Vector3::UNIT_Y * 1.5f;
anode.position = characterPos + characterRot * offset;
anode.rotation = characterRot;
anode.radius = 0.6f;
anode.height = 1.0f;
to_json(anode.props["position"], anode.position);
to_json(anode.props["rotation"], anode.rotation);
anode.props["radius"] = anode.radius;
anode.props["height"] = anode.height;
anode.props["town"] = town.id();
anode.props["index"] = index;
anode.props["npc"] = npcprops;
anode.dynamic = true;
npc.actionNodes.push_back(anode);
}
{
ActionNodeList::ActionNode anode;
anode.props["action"] = "action";
anode.props["action_text"] = "Action";
anode.action = "action";
anode.action_text = "Action";
Ogre::Vector3 offset = Ogre::Vector3::NEGATIVE_UNIT_Z * 0.2f +
Ogre::Vector3::UNIT_Y;
anode.position = characterPos + characterRot * offset;
anode.rotation = characterRot;
anode.radius = 0.3f;
anode.height = 1.0f;
to_json(anode.props["position"], anode.position);
to_json(anode.props["rotation"], anode.rotation);
anode.props["radius"] = anode.radius;
anode.props["height"] = anode.height;
anode.props["town"] = town.id();
anode.props["index"] = index;
anode.props["npc"] = npcprops;
anode.dynamic = true;
npc.actionNodes.push_back(anode);
}
}
CharacterManagerModule::CharacterManagerModule(flecs::world &ecs)
{
ecs.module<CharacterManagerModule>();
ecs.import <CharacterModule>();
ecs.import <CharacterAnimationModule>();
ecs.import <PhysicsModule>();
ecs.import <PlayerActionModule>();
ecs.component<TownCharacterHolder>();
ecs.component<TownNPCs>();
ecs.component<LivesIn>();
ecs.system<TerrainItem, TownNPCs>("UpdateCharacters")
.kind(flecs::OnUpdate)
.interval(1.0f)
.write<CharacterBase>()
.write<CharacterLocation>()
.write<Character>()
.write<LivesIn>()
.each([this](flecs::entity town, TerrainItem &item,
TownNPCs &npcs) {
if (!player.is_valid())
return;
if (!player.has<CharacterBase>())
return;
Ogre::Root::getSingleton().getWorkQueue()->addMainThreadTask([this,
town]() {
flecs::entity player =
ECS::get<CharacterManagerModule>()
.getPlayer();
if (!player.is_valid())
return;
if (!player.has<CharacterBase>())
return;
TownNPCs &npcs = town.get_mut<TownNPCs>();
Ogre::Vector3 cameraPos =
player.get<CharacterBase>()
.mBodyNode
->_getDerivedPosition();
for (auto &npc : npcs.npcs) {
int index = npc.first;
TownNPCs::NPCData &data = npc.second;
Ogre::Vector3 npcPosition =
data.position;
Ogre::Quaternion npcOrientation =
data.orientation;
if (cameraPos.squaredDistance(
npcPosition) < 10000.0f) {
if (!data.e.is_valid()) {
data.e = createCharacterData(
data.model,
data.position,
data.orientation);
data.e.add<LivesIn>(
town);
break;
}
}
if (cameraPos.squaredDistance(
npcPosition) > 22500.0f) {
if (data.e.is_valid()) {
data.e.destruct();
data.e =
flecs::entity();
break;
}
}
}
for (auto &npc : npcs.npcs) {
int index = npc.first;
TownNPCs::NPCData &data = npc.second;
Ogre::Vector3 npcPosition =
data.position;
Ogre::Quaternion npcOrientation =
data.orientation;
if (cameraPos.squaredDistance(
npcPosition) < 10000.0f) {
if (data.e.is_valid()) {
if (data.e.has<
CharacterBase>() &&
data.e.has<LivesIn>(
town))
createNPCActionNodes(
town,
index);
}
}
}
town.modified<TownNPCs>();
});
});
}
flecs::entity
CharacterManagerModule::createPlayer(const Ogre::Vector3 &position,
const Ogre::Quaternion &rotation)
{
static int count = 0;
OgreAssert(count == 0, "overspawn");
OgreAssert(!player.is_valid(), "Player already created");
player = ECS::get().entity("player");
Ogre::Vector3 playerPos(0, 0, 4);
player.set<CharacterLocation>({ rotation, position });
player.set<CharacterConf>({ "normal-male.glb" });
player.add<Character>();
player.add<Player>();
OgreAssert(player.is_valid(), "Can't create player");
std::cout << "Begin player create" << std::endl;
player.add<Player>();
ECS::get_mut<CharacterModule>().createCharacter(
player, position, rotation, "normal-male.glb");
ECS::modified<CharacterModule>();
std::cout << "End player create" << std::endl;
count++;
return player;
}
flecs::entity
@@ -31,12 +197,46 @@ CharacterManagerModule::createCharacterData(const Ogre::String model,
const Ogre::Vector3 &position,
const Ogre::Quaternion &rotation)
{
flecs::entity e =
ECS::get()
.entity()
.set<CharacterLocation>({ rotation, position })
.set<CharacterConf>({ model })
.add<Character>();
return e;
flecs::entity e = ECS::get().entity();
ECS::get_mut<CharacterModule>().createCharacter(e, position, rotation,
model);
ECS::modified<CharacterModule>();
return e;
}
void CharacterManagerModule::registerTownCharacters(flecs::entity town)
{
Ogre::MeshManager::getSingleton().load("normal-male.glb", "General");
Ogre::MeshManager::getSingleton().load("normal-female.glb", "General");
Ogre::String props = StaticGeometryModule::getItemProperties(town);
nlohmann::json j = nlohmann::json::parse(props);
nlohmann::json npcs = nlohmann::json::array();
if (town.has<TownNPCs>())
return;
if (j.find("npcs") != j.end())
npcs = j["npcs"];
std::cout << npcs.dump(4) << std::endl;
int index = 0;
std::map<int, TownNPCs::NPCData> npcMap;
for (auto &npc : npcs) {
const char *models[] = { "normal-male.glb",
"normal-female.glb" };
int sex = npc["sex"].get<int>();
Ogre::Vector3 npcPosition;
Ogre::Quaternion npcOrientation;
from_json(npc["position"], npcPosition);
from_json(npc["orientation"], npcOrientation);
Ogre::String model = models[sex];
TownNPCs::NPCData npcData;
npcData.e = flecs::entity();
npcData.model = model;
npcData.orientation = npcOrientation;
npcData.position = npcPosition;
npcData.props = npc;
npcMap[index] = npcData;
index++;
}
town.set<TownNPCs>({ npcMap });
}
}
}

View File

@@ -1,10 +1,27 @@
#ifndef _CHARACTER_MANAGER_MODULE_
#define _CHARACTER_MANAGER_MODULE_
#include <flecs.h>
#include <nlohmann/json.hpp>
#include "PlayerActionModule.h"
namespace ECS
{
struct TownCharacterHolder{int index;};
struct TownNPCs {
struct NPCData {
flecs::entity e;
nlohmann::json props;
Ogre::Vector3 position;
Ogre::Quaternion orientation;
Ogre::String model;
std::vector<ActionNodeList::ActionNode> actionNodes;
};
std::map<int, NPCData> npcs;
};
struct LivesIn {};
struct CharacterManagerModule {
std::set<flecs::entity> characters;
flecs::entity player;
CharacterManagerModule(flecs::world &ecs);
flecs::entity createPlayer(const Ogre::Vector3 &position,
const Ogre::Quaternion &rotation);
@@ -12,6 +29,14 @@ struct CharacterManagerModule {
const Ogre::Vector3 &position,
const Ogre::Quaternion &rotation);
void removeCharacterData(int id);
flecs::entity getPlayer() const
{
return player;
}
void registerTownCharacters(flecs::entity town);
void setTownCharacter(flecs::entity town, int index, bool enable);
CharacterManagerModule(CharacterManagerModule &&) = delete;
CharacterManagerModule &operator=(CharacterManagerModule&&) = delete;
};
}
#endif
#endif

View File

@@ -5,9 +5,10 @@
#include "TerrainModule.h"
#include "Components.h"
#include "PhysicsModule.h"
#include "physics.h"
#include "CharacterAnimationModule.h"
#include "CharacterManagerModule.h"
#include "CharacterModule.h"
#include "goap.h"
namespace ECS
{
CharacterModule::CharacterModule(flecs::world &ecs)
@@ -16,20 +17,61 @@ CharacterModule::CharacterModule(flecs::world &ecs)
ecs.module<CharacterModule>();
ecs.component<Character>();
ecs.component<Player>();
ecs.component<CharacterBase>();
ecs.component<CharacterBase>()
.on_remove([this](flecs::entity e, CharacterBase &ch) {
// FIXME: clean up data
if (characterEntities.find(e) !=
characterEntities.end() ||
characterNodes.find(e) != characterNodes.end()) {
characterEntities.erase(e);
characterNodes.erase(e);
ECS::modified<CharacterModule>();
}
})
.on_add([this](flecs::entity e, CharacterBase &ch) {
if (characterNodes.find(e) == characterNodes.end()) {
OgreAssert(characterModels.find(e) !=
characterModels.end(),
"no model set");
const EngineData &eng = ECS::get<EngineData>();
Ogre::SceneNode *bodyNode =
eng.mScnMgr->getRootSceneNode()
->createChildSceneNode();
Ogre::Entity *bodyEnt =
eng.mScnMgr->createEntity(
characterModels[e]);
characterNodes[e] = bodyNode;
characterEntities[e] = bodyEnt;
ECS::modified<CharacterModule>();
}
OgreAssert(characterOrientations.find(e) !=
characterOrientations.end(),
"Bad orientation/position");
ch.mBodyEnt = characterEntities[e];
ch.mBodyNode = characterNodes[e];
ch.mBodyNode->setOrientation(characterOrientations[e]);
ch.mBodyNode->setPosition(characterPositions[e]);
ch.mBodyNode->attachObject(ch.mBodyEnt);
OgreAssert(ch.mBodyEnt->getSkeleton()->hasBone("Root"),
"No root bone");
ch.mBoneMotion = Ogre::Vector3::ZERO;
ch.mBonePrevMotion = Ogre::Vector3::ZERO;
});
ecs.component<CharacterGravity>();
ecs.component<CharacterLocation>();
ecs.component<CharacterLocation>().on_set(
[this](flecs::entity e, CharacterLocation &loc) {
characterOrientations[e] = loc.orientation;
characterPositions[e] = loc.position;
ECS::modified<CharacterModule>();
});
ecs.component<CharacterBuoyancy>();
ecs.component<CharacterConf>();
ecs.component<CharacterDisablePhysics>();
ecs.component<CharacterUpdatePhysicsState>();
ecs.component<CharacterInActuator>();
ecs.component<Blackboard>();
ecs.component<ActionTarget>();
ecs.component<Plan>();
ecs.component<Male>();
ecs.component<Female>();
ecs.component<Planner>().add(flecs::Singleton);
ecs.component<CharacterControlDisable>();
ecs.import <CharacterAnimationModule>();
ecs.import <TerrainModule>();
ecs.import <WaterModule>();
@@ -41,8 +83,10 @@ CharacterModule::CharacterModule(flecs::world &ecs)
ecs.system<Input, Camera>("HandleInput")
.kind(flecs::OnUpdate)
.each([this](Input &input, Camera &camera) {
if (!ECS::player.is_valid())
return;
flecs::entity player =
ECS::get<CharacterManagerModule>().getPlayer();
if (!player.is_valid())
return;
/* handle input */
// if (input.control == input.control_prev)
// return;
@@ -131,75 +175,14 @@ CharacterModule::CharacterModule(flecs::world &ecs)
else if (current_subm < 0.8f)
ch.is_submerged = false;
});
#if 0
ecs.system<const EngineData, const CharacterBase, CharacterVelocity>(
"HandleGravityBouyanceWater")
.kind(flecs::OnUpdate)
.with<TerrainReady>()
.with<WaterReady>()
.with<InWater>()
.without<CharacterDisablePhysics>()
.without<CharacterUpdatePhysicsState>()
.each([this](flecs::entity e, const EngineData &eng,
const CharacterBase &ch, CharacterVelocity &gr) {
Ogre::Vector3 gravity(0, -9.8f, 0);
Ogre::Vector3 pos = ch.mBodyNode->getPosition();
Ogre::Vector3 v(0, 0, 0);
if (e.has<CharacterGravity>())
v += gravity;
if (e.has<CharacterBuoyancy>()) {
float volume = 2.0f * 0.5f * 0.5f;
float density = 900.0f;
float full_subm = 2.0f;
float mass = 80.0f;
float multiplier = 0.25f;
float current_subm = -Ogre::Math::Clamp(
pos.y + Ogre::Math::Sin(ch.mTimer *
0.13f +
130.0f) *
0.07f,
-full_subm, 0.0f);
Ogre::Vector3 b = -gravity * density * volume *
multiplier * current_subm /
full_subm / mass;
v += b;
}
gr.gvelocity += v * eng.delta;
gr.gvelocity.y =
Ogre::Math::Clamp(gr.gvelocity.y, -2.5f, 1.5f);
gr.gvelocity *= (1.0 - eng.delta);
gr.velocity.y *= (1.0 - eng.delta);
});
#endif
#if 0
ecs.system<const EngineData, const CharacterBase, CharacterVelocity>(
"HandleGravityNoWater")
.kind(flecs::OnUpdate)
.with<TerrainReady>()
.with<WaterReady>()
.without<InWater>()
.with<CharacterGravity>()
.without<CharacterDisablePhysics>()
.each([this](flecs::entity e, const EngineData &eng,
const CharacterBase &ch, CharacterVelocity &gr) {
Ogre::Vector3 gravity(0, -9.8f, 0);
Ogre::Vector3 pos = ch.mBodyNode->getPosition();
gr.gvelocity += gravity * eng.delta;
if (pos.y < -1.2) {
gr.gvelocity.y = 0.0f;
}
gr.gvelocity *= (1.0 - eng.delta);
gr.velocity.y *= (1.0 - eng.delta);
});
#endif
#define TURN_SPEED 500.0f // character turning in degrees per second
ecs.system<const Input, const Camera, CharacterBase>("UpdateBody")
.kind(flecs::OnUpdate)
.with<Character>()
.with<Player>()
.without<CharacterInActuator>()
.each([](flecs::entity e, const Input &input,
.without<CharacterControlDisable>()
.each([](flecs::entity e, const Input &input,
const Camera &camera, CharacterBase &ch) {
ch.mGoalDirection = Ogre::Vector3::ZERO;
float delta = e.world().delta_time();
@@ -263,38 +246,13 @@ CharacterModule::CharacterModule(flecs::world &ecs)
}
});
#endif
ecs.observer<const EngineData, const CharacterLocation,
const CharacterConf>("SetupCharacterM")
.event(flecs::OnSet)
.with<Character>()
.without<CharacterBase>()
.each([](flecs::entity e, const EngineData &eng,
const CharacterLocation &loc,
const CharacterConf &conf) {
CharacterBase &ch = e.ensure<CharacterBase>();
AnimationControl &anim = e.ensure<AnimationControl>();
ch.mBodyEnt = eng.mScnMgr->createEntity(conf.type);
ch.mBodyNode = eng.mScnMgr->getRootSceneNode()
->createChildSceneNode();
ch.mBodyNode->setOrientation(loc.orientation);
ch.mBodyNode->setPosition(loc.position);
ch.mBodyNode->attachObject(ch.mBodyEnt);
ch.mSkeleton = ch.mBodyEnt->getSkeleton();
OgreAssert(ch.mBodyEnt->getSkeleton()->hasBone("Root"),
"No root bone");
OgreAssert(ch.mSkeleton->hasBone("Root"),
"No root bone");
ch.mRootBone = ch.mSkeleton->getBone("Root");
OgreAssert(ch.mRootBone, "No root bone");
// body.mController = nullptr;
ch.mBoneMotion = Ogre::Vector3::ZERO;
ch.mBonePrevMotion = Ogre::Vector3::ZERO;
e.set<CharacterVelocity>(
{ { 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f } });
e.add<CharacterGravity>();
e.add<CharacterBuoyancy>();
anim.configured = false;
});
static int characterCount = 0;
ecs.observer<AnimationControl>("SetupAnimationControlObs")
.event(flecs::OnAdd)
.each([](flecs::entity e, AnimationControl &anim) {
anim.configured = false;
});
#if 0
ecs.system<const EngineData, const CharacterLocation,
const CharacterConf, Body2Entity>("SetupCharacter")
@@ -374,30 +332,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")
@@ -557,6 +491,7 @@ void CharacterModule::updateCameraGoal(Camera &camera, Ogre::Real deltaYaw,
Ogre::Real deltaPitch,
Ogre::Real deltaZoom)
{
static float canonDist = 0;
camera.mCameraPivot->yaw(Ogre::Degree(deltaYaw), Ogre::Node::TS_PARENT);
if (!(camera.mPivotPitch + deltaPitch > 25 && deltaPitch > 0) &&
!(camera.mPivotPitch + deltaPitch < -60 && deltaPitch < 0)) {
@@ -569,187 +504,152 @@ void CharacterModule::updateCameraGoal(Camera &camera, Ogre::Real deltaYaw,
Ogre::Real distChange = deltaZoom * dist;
// bound the zoom
if (!(dist + distChange < 8 && distChange < 0) &&
!(dist + distChange > 25 && distChange > 0))
if (!(dist + distChange < 1.5f && distChange < 0) &&
!(dist + distChange > 10 && distChange > 0)) {
camera.mCameraGoal->translate(0, 0, distChange,
Ogre::Node::TS_LOCAL);
}
CharacterAIModule::CharacterAIModule(flecs::world &ecs)
{
ecs.module<CharacterAIModule>();
ecs.system<Blackboard>("UpdateCharacters")
.kind(flecs::OnUpdate)
.each([&](flecs::entity e, Blackboard &bb) {
bb.flags &=
~(Blackboard::LOW_HEALTH |
Blackboard::FULL_HEALTH |
Blackboard::LOW_STAMINA |
Blackboard::FULL_STAMINA |
Blackboard::LOW_LUST | Blackboard::HIGH_LUST |
Blackboard::FULL_LUST);
if (bb.health < 5)
bb.flags |= Blackboard::LOW_HEALTH;
else if (bb.health >= 100)
bb.flags |= Blackboard::FULL_HEALTH;
if (bb.stamina < 5)
bb.flags |= Blackboard::LOW_STAMINA;
if (bb.stamina >= 100)
bb.flags |= Blackboard::FULL_STAMINA;
if (bb.lust >= 100)
bb.flags |= Blackboard::FULL_LUST;
if (bb.lust > 10)
bb.flags |= Blackboard::HIGH_LUST;
if (bb.lust < 5)
bb.flags |= Blackboard::LOW_LUST;
if (bb.stamina < 0)
bb.stamina = 0;
else if (bb.stamina > 100)
bb.stamina = 100;
if (bb.lust < 0)
bb.lust = 0;
else if (bb.lust > 100)
bb.lust = 100;
});
ecs.system<Blackboard, Plan>("UpdateCharactersPlan2")
.kind(flecs::OnUpdate)
.each([&](flecs::entity e, Blackboard &bb, Plan &p) {
int i;
bool ok_plan = true;
canonDist += distChange;
}
JPH::BodyID id;
Ogre::Vector3 position;
Ogre::Vector3 d = (camera.mCameraPivot->_getDerivedPosition() -
camera.mCameraGoal->_getDerivedPosition())
.normalisedCopy();
for (i = p.position; i < p.length; i++) {
if (!p.actions[i]->can_run(bb, true)) {
ok_plan = false;
break;
}
int ret = p.actions[i]->execute(bb);
p.position = i;
if (ret == BaseAction::BUSY)
break;
else if (ret == BaseAction::ABORT) {
ok_plan = false;
break;
}
if (ret == BaseAction::OK && i == p.length - 1)
ok_plan = false;
// stop_this = true;
}
if (!ok_plan) {
std::cout << e.name() << ": invalidated plan"
<< " step: " << i << std::endl;
for (i = 0; i < p.length; i++)
p.actions[i]->stop(bb);
e.remove<Plan>();
}
});
ecs.system<Blackboard, Planner>("UpdateCharacters2")
.kind(flecs::OnUpdate)
.without<Plan>()
.each([&](flecs::entity e, Blackboard &bb, Planner &planner) {
int i;
std::vector<BaseAction *> actions;
actions.insert(actions.end(), planner.actions.begin(),
planner.actions.end());
e.world()
.query_builder<const ActionTarget>()
.build()
.each([&](flecs::entity me,
const ActionTarget &t) {
actions.insert(actions.end(),
t.actions.begin(),
t.actions.end());
#if 0
auto it = t.actions.begin();
while (it != t.actions.end()) {
if (me != bb.me)
actions.push_back(*it);
// std::cout << (*it)->get_name()
// << std::endl;
it++;
}
#endif
});
#if 0
for (i = 0; i < actions.size(); i++)
std::cout << "action: " << i << " "
<< actions[i]->get_name()
<< std::endl;
#endif
if (actions.size() == 0)
return;
int len = -1;
OgreAssert(
bb.stamina < 100 ||
bb.stamina >= 100 &&
!bb.get_flag(
Blackboard::LOW_STAMINA),
"bad thing");
for (i = 0; i < planner.goals.size(); i++) {
if (planner.goals[i]->distance_to(bb) > 1000000)
continue;
len = planner.planner.plan(
bb, *planner.goals[i], actions.data(),
actions.size(), planner.path.data(),
100);
std::cout << bb.me.name() << ": goal: " << len
<< " " << planner.goals[i]->get_name()
<< std::endl;
if (len > 0)
break;
}
// std::cout << "plan length: " << len << std::endl;
#if 0
if (len > 0)
stop_this = true;
if (len < 0)
stop_this = true;
#endif
if (len > 0) {
Plan &p = e.ensure<Plan>();
p.actions = planner.path;
p.position = 0;
p.length = len;
for (i = 0; i < len; i++)
std::cout
<< i << " "
<< planner.path[i]->get_name()
<< " "
<< planner.path[i]->get_cost(bb)
<< std::endl;
bool ok_plan = true;
for (i = 0; i < len; i++) {
if (!planner.path[i]->can_run(bb,
true)) {
ok_plan = false;
break;
}
int ret = planner.path[i]->execute(bb);
p.position = i;
std::cout << "exec: " << i << " "
<< planner.path[i]->get_name()
<< std::endl;
if (ret == BaseAction::BUSY)
break;
else if (ret == BaseAction::ABORT) {
ok_plan = false;
} else if (ret == BaseAction::OK)
std::cout
<< "exec: complete "
<< i << " "
<< planner.path[i]
->get_name()
<< std::endl;
}
e.modified<Plan>();
if (!ok_plan) {
std::cout << e.name()
<< ": invalidate plan"
<< " step: " << i
<< std::endl;
for (i = 0; i < len; i++)
planner.path[i]->stop(bb);
e.remove<Plan>();
}
}
});
if (JoltPhysicsWrapper::getSingleton().raycastQuery(
camera.mCameraPivot->_getDerivedPosition(),
camera.mCameraGoal->_getDerivedPosition() - d * 0.6,
position, id)) {
float l = camera.mCameraPivot->_getDerivedPosition()
.squaredDistance(
camera.mCameraGoal
->_getDerivedPosition());
float m = camera.mCameraPivot->_getDerivedPosition()
.squaredDistance(position);
if (m < l)
camera.mCameraGoal->_setDerivedPosition(position +
d * 0.6f);
} else {
Ogre::Real dist2 =
camera.mCameraGoal->_getDerivedPosition().distance(
camera.mCameraPivot->_getDerivedPosition());
if (deltaZoom < 0.0f || deltaZoom > 0.0f)
canonDist = dist2;
else {
if (canonDist < dist2)
canonDist = dist2;
if (dist2 < canonDist)
camera.mCameraGoal->translate(
0, 0, 0.08f, Ogre::Node::TS_LOCAL);
}
}
}
void applyWeightBasedScale(Ogre::Entity *ent,
const Ogre::String &targetBoneName,
const Ogre::Vector3 &scale)
{
Ogre::MeshPtr mesh = ent->getMesh();
Ogre::SkeletonInstance *skel = ent->getSkeleton();
Ogre::Bone *targetBone = skel->getBone(targetBoneName);
// Create a non-uniform scale matrix relative to the bone's local space
Ogre::Matrix4 scaleMatrix = Ogre::Matrix4::IDENTITY;
scaleMatrix.setScale(scale);
for (unsigned short i = 0; i < mesh->getNumSubMeshes(); ++i) {
Ogre::SubMesh *submesh = mesh->getSubMesh(i);
Ogre::VertexData *vertexData = submesh->useSharedVertices ?
mesh->sharedVertexData :
submesh->vertexData;
// 1. Get Position Element
const Ogre::VertexElement *posElem =
vertexData->vertexDeclaration->findElementBySemantic(
Ogre::VES_POSITION);
Ogre::HardwareVertexBufferSharedPtr vbuf =
vertexData->vertexBufferBinding->getBuffer(
posElem->getSource());
// 2. Access Bone Assignments
// This map tells us which bones influence which vertex and by how much
const Ogre::Mesh::VertexBoneAssignmentList &vbal =
submesh->getBoneAssignments();
// Lock buffer for reading and writing
float *pVertices = static_cast<float *>(
vbuf->lock(Ogre::HardwareBuffer::HBL_NORMAL));
for (size_t vIdx = 0; vIdx < vertexData->vertexCount; ++vIdx) {
float totalWeight = 0.0f;
// Find assignments for this specific vertex
for (auto const &[index, assignment] : vbal) {
if (assignment.vertexIndex == vIdx &&
assignment.boneIndex ==
targetBone->getHandle()) {
totalWeight = assignment.weight;
break;
}
}
if (totalWeight > 0.0f) {
float *pPos;
posElem->baseVertexPointerToElement(
reinterpret_cast<unsigned char *>(
pVertices) +
vIdx * vbuf->getVertexSize(),
&pPos);
Ogre::Vector3 vertexPos(pPos[0], pPos[1],
pPos[2]);
// Transform vertex to bone local space, scale it, then move back
// This ensures scaling happens along the bone's axis, not the world axis
Ogre::Vector3 localPos =
targetBone->_getDerivedOrientation()
.Inverse() *
(vertexPos -
targetBone->_getDerivedPosition());
Ogre::Vector3 scaledLocalPos =
scaleMatrix * localPos;
Ogre::Vector3 worldPos =
(targetBone->_getDerivedOrientation() *
scaledLocalPos) +
targetBone->_getDerivedPosition();
// Interpolate based on weight (Lerp) to handle vertices shared with other bones
Ogre::Vector3 finalPos = Ogre::Math::lerp(
vertexPos, worldPos, totalWeight);
pPos[0] = finalPos.x;
pPos[1] = finalPos.y;
pPos[2] = finalPos.z;
}
}
vbuf->unlock();
}
}
void CharacterModule::createCharacter(flecs::entity e,
const Ogre::Vector3 &position,
const Ogre::Quaternion &rotation,
const Ogre::String model)
{
if (e.has<CharacterBase>() || e.has<AnimationControl>())
return;
if (characterNodes.find(e) != characterNodes.end())
return;
e.set<CharacterLocation>({ rotation, position });
characterOrientations[e] = rotation;
characterPositions[e] = position;
characterModels[e] = model;
e.set<CharacterVelocity>(
{ { 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f } });
e.add<CharacterGravity>();
e.add<CharacterBuoyancy>();
e.add<Character>();
e.add<CharacterBase>();
e.add<AnimationControl>();
}
}

View File

@@ -2,7 +2,7 @@
#define CHARACTER_MODULE_H_
#include <flecs.h>
#include <Ogre.h>
#include "goap.h"
#include "Components.h"
namespace ECS
{
struct Camera;
@@ -19,48 +19,34 @@ struct Female {};
struct CharacterBase {
Ogre::String type;
float mTimer;
Ogre::SceneNode *mBodyNode;
Ogre::Entity *mBodyEnt;
Ogre::Skeleton *mSkeleton;
Ogre::Node *mRootBone;
Ogre::Vector3 mBoneMotion;
Ogre::Vector3 mBonePrevMotion;
Ogre::Vector3 mGoalDirection; // actual intended direction in world-space
bool is_submerged;
Ogre::SceneNode *mBodyNode;
Ogre::Entity *mBodyEnt;
bool is_submerged;
};
struct CharacterLocation {
Ogre::Quaternion orientation;
Ogre::Vector3 position;
};
struct CharacterConf {
Ogre::String type;
};
struct CharacterInActuator {
Ogre::String animationState;
Vector3 prevMotion;
Vector3 prevMotion;
};
struct ActionTarget {
std::vector<BaseAction *> actions;
};
struct Plan {
std::vector<BaseAction *> actions;
int position;
int length;
};
struct Planner {
BasePlanner<ECS::Blackboard, BaseAction> planner;
std::vector<BaseAction *> path;
std::vector<BasePlanner<ECS::Blackboard, BaseAction>::BaseGoal *> goals;
std::vector<BaseAction *> actions;
};
struct CharacterControlDisable {};
struct CharacterModule {
CharacterModule(flecs::world &ecs);
void updateCameraGoal(Camera &camera, Ogre::Real deltaYaw,
Ogre::Real deltaPitch, Ogre::Real deltaZoom);
};
struct CharacterAIModule {
CharacterAIModule(flecs::world &ecs);
void createCharacter(flecs::entity e, const Ogre::Vector3 &position,
const Ogre::Quaternion &rotation,
const Ogre::String model);
std::unordered_map<flecs::entity_t, Ogre::SceneNode *> characterNodes;
std::unordered_map<flecs::entity_t, Ogre::Entity *> characterEntities;
std::unordered_map<flecs::entity_t, Ogre::String> characterModels;
std::unordered_map<flecs::entity_t, Ogre::Vector3> characterPositions;
std::unordered_map<flecs::entity_t, Ogre::Quaternion> characterOrientations;
};
}
#endif

View File

@@ -75,5 +75,11 @@ struct GroundCheckReady {};
struct Body2Entity {
/* std::unordered_map<btCollisionObject *, flecs::entity> entities; */
};
struct EditorSceneSwitch {
int scene;
};
struct GameState {
bool running;
};
}
#endif
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,9 @@
#ifndef __EDITORGUIMODULE_H__
#define __EDITORGUIMODULE_H__
namespace ECS {
struct EditorGUIModule {
EditorGUIModule(flecs::world &ecs);
};
}
#endif // EDITORGUIMODULE_H

View File

@@ -26,4 +26,4 @@ struct EventModule {
flecs::entity to);
};
}
#endif
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -6,40 +6,10 @@ namespace OgreBites
}
namespace ECS
{
struct GUI {
bool enabled;
bool grab;
bool grabChanged;
bool narrationBox;
bool mainMenu;
Ogre::String narrationText;
std::vector<Ogre::String> choices;
int narration_answer;
static void setWindowGrab(bool g = true)
{
ECS::GUI &gui = ECS::get().get_mut<GUI>();
if (gui.grab != g) {
gui.grab = g;
gui.grabChanged = true;
ECS::get().modified<GUI>();
}
}
static void finish()
{
ECS::GUI &gui = ECS::get().get_mut<ECS::GUI>();
gui.enabled = false;
gui.mainMenu = false;
gui.narrationBox = false;
ECS::get().modified<ECS::GUI>();
setWindowGrab(true);
}
};
struct GUIModule {
flecs::entity ui_wait;
GUIModule(flecs::world &ecs);
};
struct EditorGUIModule {
EditorGUIModule(flecs::world &ecs);
static void configure();
};
}
#endif
#endif

View File

@@ -0,0 +1,142 @@
#ifndef __GUIMODULECOMMON_H__
#define __GUIMODULECOMMON_H__
#include <iostream>
#include <Ogre.h>
#include <nlohmann/json.hpp>
#include "Components.h"
#include "GameData.h"
namespace ECS
{
struct GUI {
bool enabled;
bool grab;
bool grabChanged;
bool narrationBox;
bool mainMenu;
bool enableActions;
Ogre::String narrationText;
std::vector<Ogre::String> choices;
int narration_answer;
struct NarrationHandler {
private:
Ogre::String mnarrationText;
std::vector<Ogre::String> mchoices;
int narration_answer;
nlohmann::json props;
private:
bool complete;
bool active;
public:
bool is_complete()
{
return complete;
}
bool is_active()
{
return active;
}
const Ogre::String &getNarrationText() const
{
return mnarrationText;
}
const std::vector<Ogre::String> &getChoices() const
{
return mchoices;
}
void progress()
{
_event("narration_progress");
}
void setNarrationAnswer(int answer)
{
narration_answer = answer;
_event("narration_answered");
}
int getNarrationAnswer() const
{
return narration_answer;
}
NarrationHandler(): complete(false), active(false) {}
private:
virtual void finish() = 0;
virtual void activate() = 0;
virtual void event(const Ogre::String &event) = 0;
protected:
void _activate()
{
activate();
active = true;
}
void _finish()
{
finish();
complete = true;
}
void _narration(const Ogre::String &text, const std::vector<Ogre::String> &choices)
{
mnarrationText = text;
mchoices = choices;
}
void _clear_narration()
{
mnarrationText = "";
mchoices.clear();
}
public:
void _event(const Ogre::String &ev)
{
if (!active && !complete)
_activate();
event(ev);
}
virtual ~NarrationHandler() {}
void setProperties(const Ogre::String &properties)
{
props = nlohmann::json::parse(properties);
}
Ogre::String getProperties() const
{
return props.dump();
}
const nlohmann::json &getPropsJSON() const
{
return props;
}
};
static void setWindowGrab(bool g = true)
{
ECS::GUI &gui = ECS::get().get_mut<GUI>();
if (gui.grab != g) {
gui.grab = g;
gui.grabChanged = true;
ECS::get().modified<GUI>();
}
}
static void finish()
{
ECS::GUI &gui = ECS::get().get_mut<ECS::GUI>();
gui.enabled = false;
gui.mainMenu = false;
gui.narrationBox = false;
ECS::get().modified<ECS::GUI>();
setWindowGrab(true);
}
std::vector<NarrationHandler *> narrationHandlers;
void addNarrationHandler(struct NarrationHandler *handler)
{
narrationHandlers.push_back(handler);
}
void removeNarrationHandler(struct NarrationHandler *handler)
{
auto it = std::find(narrationHandlers.begin(), narrationHandlers.end(), handler);
narrationHandlers.erase(it);
}
};
}
#endif // GUIMODULECOMMON_H

View File

@@ -1,12 +1,18 @@
#include <iostream>
#include <Ogre.h>
#include <OgreTerrain.h>
#include <OgreTerrainGroup.h>
#include <Jolt/Jolt.h>
#include <Jolt/Physics/Body/BodyID.h>
#include "GameData.h"
#include "Components.h"
#include "CharacterModule.h"
#include "WaterModule.h"
#include "TerrainModule.h"
#include "SunModule.h"
#include "GUIModuleCommon.h"
#include "GUIModule.h"
#include "EditorGUIModule.h"
#include "LuaData.h"
#include "WorldMapModule.h"
#include "BoatModule.h"
@@ -16,13 +22,16 @@
#include "EventModule.h"
#include "CharacterManagerModule.h"
#include "VehicleManagerModule.h"
#include "PlayerActionModule.h"
#include "AppModule.h"
#include "CharacterAIModule.h"
#include "QuestModule.h"
#include "world-build.h"
#include "physics.h"
namespace ECS
{
static flecs::world ecs;
flecs::entity player;
void setup_minimal()
{
ecs.component<EngineData>().add(flecs::Singleton);
@@ -38,23 +47,31 @@ void setup_minimal()
/* lots of things depend on it */
ecs.component<Body2Entity>().add(flecs::Singleton);
}
void setup(Ogre::SceneManager *scnMgr, Ogre::SceneNode *cameraNode,
Ogre::Camera *camera, Ogre::RenderWindow *window)
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);
std::cout << "Setup Editor\n";
ecs.component<GameState>().add(flecs::Singleton);
ecs.import <CharacterModule>();
ecs.import <BoatModule>();
ecs.import <PhysicsModule>();
ecs.import <WaterModule>();
ecs.import <SunModule>();
ecs.import <TerrainModule>();
ecs.import <GUIModule>();
ecs.import <GUIModule>();
ecs.import <EventTriggerModule>();
ecs.import <LuaModule>();
ecs.import <WorldMapModule>();
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)
@@ -63,6 +80,7 @@ void setup(Ogre::SceneManager *scnMgr, Ogre::SceneNode *cameraNode,
});
ecs.system<EngineData>("UpdateDelay")
.kind(flecs::OnUpdate)
.with<GameState>()
.with<TerrainReady>()
.with<WaterReady>()
.with<GroundCheckReady>()
@@ -87,20 +105,26 @@ void setup(Ogre::SceneManager *scnMgr, Ogre::SceneNode *cameraNode,
#endif
});
ecs.set<EngineData>({ scnMgr, 0.0f, 5.0f, (int)window->getWidth(),
(int)window->getHeight(), false });
(int)window->getHeight(), false });
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,
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>())
@@ -108,18 +132,87 @@ void setup(Ogre::SceneManager *scnMgr, Ogre::SceneNode *cameraNode,
// ecs.set<Body2Entity>({});
std::cout << "Setup GameData done\n";
/* Create player */
player = ecs.get_mut<CharacterManagerModule>().createPlayer(
{ 0, 0, 4 }, Ogre::Quaternion(Ogre::Radian(Ogre::Math::PI),
Ogre::Vector3::UNIT_Y));
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 setupEditor(Ogre::SceneManager *scnMgr, Ogre::SceneNode *cameraNode,
Ogre::Camera *camera, Ogre::RenderWindow *window)
void setupInteriorScene(Ogre::SceneManager *scnMgr, Ogre::SceneNode *cameraNode,
Ogre::Camera *camera, Ogre::RenderWindow *window)
{
std::cout << "Setup Editor\n";
setup_minimal();
ecs.component<RenderWindow>().add(flecs::Singleton);
}
void setupInventoryScene(Ogre::SceneManager *scnMgr,
Ogre::SceneNode *cameraNode, Ogre::Camera *camera,
Ogre::RenderWindow *window)
{
}
void setupEditor(Ogre::SceneManager *scnMgr, Ogre::SceneNode *cameraNode,
Ogre::Camera *camera, Ogre::RenderWindow *window)
{
setup_minimal();
ecs.component<RenderWindow>().add(flecs::Singleton);
ecs.component<EditorSceneSwitch>().add(flecs::Singleton);
ecs.import <CharacterModule>();
ecs.import <BoatModule>();
ecs.import <PhysicsModule>();
@@ -131,6 +224,8 @@ void setupEditor(Ogre::SceneManager *scnMgr, Ogre::SceneNode *cameraNode,
ecs.import <LuaModule>();
// ecs.import <WorldMapModule>();
ecs.import <CharacterAnimationModule>();
ecs.import <PlayerActionModule>();
ecs.add<ActionNodeList>();
ecs.system<EngineData>("UpdateDelta")
.kind(flecs::OnUpdate)
@@ -163,9 +258,12 @@ void setupEditor(Ogre::SceneManager *scnMgr, Ogre::SceneNode *cameraNode,
#endif
});
ecs.set<EngineData>({ scnMgr, 0.0f, 5.0f, (int)window->getWidth(),
(int)window->getHeight(), false });
(int)window->getHeight(), false });
ecs.set<Camera>({ cameraNode, camera, false });
ecs.add<GameData>();
#if 0
ecs.set<EditorSceneSwitch>({ 0 });
#endif
ecs.add<GameData>();
ecs.add<Input>();
ecs.add<WaterSurface>();
ecs.set<Sun>({ nullptr, nullptr, nullptr, nullptr });
@@ -175,16 +273,15 @@ void setupEditor(Ogre::SceneManager *scnMgr, Ogre::SceneNode *cameraNode,
nullptr,
nullptr,
nullptr,
nullptr,
false,
{ 0, 0, 0 } });
ecs.set<GUI>({ true, true, true, false, false, "", {}, -1 });
ecs.set<GUI>({ true, true, true, false, false, true, "", {}, -1 });
ecs.get_mut<GUI>().enabled = true;
ecs.get_mut<GUI>().setWindowGrab(false);
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)
@@ -200,4 +297,5 @@ bool Vector3::zeroLength() const
float l = x * x + y * y + z * z;
return (l < 1e-06 * 1e-06);
}
}

View File

@@ -1,14 +1,20 @@
#ifndef GAMEDATA_H
#define GAMEDATA_H
#include <Ogre.h>
#include <flecs.h>
namespace ECS
{
extern flecs::entity player;
void setup_minimal();
void setup(Ogre::SceneManager *scnMgr, Ogre::SceneNode *cameraNode,
Ogre::Camera *camera, Ogre::RenderWindow *window);
void setupExteriorScene(Ogre::SceneManager *scnMgr, Ogre::SceneNode *cameraNode,
Ogre::Camera *camera, Ogre::RenderWindow *window);
void setupInteriorScene(Ogre::SceneManager *scnMgr, Ogre::SceneNode *cameraNode,
Ogre::Camera *camera, Ogre::RenderWindow *window);
void setupInventoryScene(Ogre::SceneManager *scnMgr, Ogre::SceneNode *cameraNode,
Ogre::Camera *camera, Ogre::RenderWindow *window);
void setupEditor(Ogre::SceneManager *scnMgr, Ogre::SceneNode *cameraNode,
Ogre::Camera *camera, Ogre::RenderWindow *window);
Ogre::Camera *camera, Ogre::RenderWindow *window);
void setupEditorAlt(Ogre::SceneManager *scnMgr, Ogre::SceneNode *cameraNode,
Ogre::Camera *camera, Ogre::RenderWindow *window);
void update(float delta);
flecs::world get();
template <class T> const T &get()

View File

@@ -1,7 +1,7 @@
#include <OgreFileSystemLayer.h>
#include "GameData.h"
#include "Components.h"
#include "GUIModule.h"
#include "GUIModuleCommon.h"
#include "PhysicsModule.h"
#include "CharacterModule.h"
#include "CharacterAnimationModule.h"
@@ -11,8 +11,10 @@
#include "EventTriggerModule.h"
#include "SlotsModule.h"
#include "world-build.h"
#include "PlayerActionModule.h"
#include "QuestModule.h"
#include "LuaData.h"
#include "luaaa.hpp"
#include "lua.hpp"
extern "C" {
int luaopen_lpeg(lua_State *L);
}
@@ -23,6 +25,7 @@ struct FooPosition {
return value;
}
};
#if 0
namespace luaaa
{
template <> struct LuaStack<FooPosition> {
@@ -66,6 +69,7 @@ template <> struct LuaStack<FooPosition> {
};
}
#endif
namespace ECS
{
struct LuaEcsEntity {
@@ -120,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");
@@ -138,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");
@@ -269,6 +273,7 @@ LuaData::LuaData()
luaVector3.fun("squaredDistance", &Ogre::Vector3::squaredDistance);
luaVector3.fun("distance", &Ogre::Vector3::distance);
#endif
#if 0
luaaa::LuaModule luaECS(L, "_ecs");
luaECS.fun("position", [](int id) -> FooPosition {
#if 0
@@ -287,6 +292,7 @@ LuaData::LuaData()
#endif
return FooPosition();
});
#endif
lua_pushcfunction(L, [](lua_State *L) -> int {
OgreAssert(false, "Crash function called");
@@ -299,7 +305,31 @@ LuaData::LuaData()
return 0;
});
lua_setglobal(L, "setup_handler");
lua_pushcfunction(L, [](lua_State *L) -> int {
lua_pushcfunction(L, [](lua_State *L) -> int {
luaL_checktype(L, 1, LUA_TSTRING);
if (lua_type(L, 2) == LUA_TFUNCTION)
luaL_checktype(L, 2, LUA_TFUNCTION);
else
luaL_checktype(L, 2, LUA_TTABLE);
ECS::get_mut<PlayerActionModule>().setupLuaActionHandler(L);
ECS::modified<PlayerActionModule>();
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;
});
lua_setglobal(L, "setup_narration_handler");
lua_pushcfunction(L, [](lua_State *L) -> int {
int args = lua_gettop(L);
if (args < 1)
return 0;
@@ -846,6 +876,7 @@ LuaModule::LuaModule(flecs::world &ecs)
ecs.module<LuaModule>();
ecs.import <SlotsModule>();
ecs.import <VehicleManagerModule>();
ecs.import <PlayerActionModule>();
ecs.component<LuaChildEventTrigger>();
ecs.component<LuaBase>()
.on_add([](LuaBase &lua) {
@@ -911,4 +942,4 @@ LuaModule::LuaModule(flecs::world &ecs)
}
});
}
}
}

View File

@@ -11,7 +11,7 @@ struct LuaData {
lua_State *L;
std::vector<int> setup_handlers;
int setup_handler();
int call_handler(const Ogre::String &event);
int call_handler(const Ogre::String &event);
int call_handler(const Ogre::String &event, flecs::entity e,
flecs::entity o);

View File

@@ -61,28 +61,6 @@ PhysicsModule::PhysicsModule(flecs::world &ecs)
ecs.component<CachedMass>();
ecs.import <TerrainModule>();
ecs.import <WaterModule>();
ecs.system<const EngineData, const Camera>("physics_init")
.kind(PhysicsPreUpdate)
.without<Physics>()
.each([&](const EngineData &e, const Camera &c) {
Physics &ph = ECS::get().ensure<Physics>();
ph.physics = new JoltPhysicsWrapper(e.mScnMgr,
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) {
@@ -172,14 +150,18 @@ PhysicsModule::PhysicsModule(flecs::world &ecs)
.event(flecs::OnRemove)
.each([&](flecs::entity e, const JPH::BodyID &id) {
JoltPhysicsWrapper::getSingleton().removeBody(id);
JoltPhysicsWrapper::getSingleton().destroyBody(id);
if (e.has<CharacterBase>() || e.has<Character>())
return;
JoltPhysicsWrapper::getSingleton().destroyBody(id);
std::cout << "body destroyed" << std::endl;
});
ecs.observer<const EngineData, const CharacterBase>("SetupCharacterPh")
.event(flecs::OnSet)
ecs.system<const EngineData, const CharacterBase>("SetupCharacterPh")
.kind(flecs::OnUpdate)
.with<Character>()
.without<CharacterBody>()
.write<CharacterBody>()
.without<CharacterBody>()
.without<JPH::BodyID>()
.write<CharacterBody>()
.write<JPH::BodyID>()
.each([](flecs::entity e, const EngineData &eng,
const CharacterBase &base) {
CharacterBody &b = e.ensure<CharacterBody>();
@@ -281,57 +263,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>()
@@ -340,16 +275,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>()
@@ -361,57 +286,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")
@@ -503,12 +377,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;
@@ -597,13 +465,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")
@@ -652,7 +513,10 @@ PhysicsModule::PhysicsModule(flecs::world &ecs)
}
// gr.velocity.y = 0.0f;
// v.y = 0.0f;
ch->SetLinearVelocity(JoltPhysics::convert(v));
OgreAssert(v.squaredLength() < 1000.0f,
"shitty velocity setting");
ch->SetLinearVelocity(
JoltPhysics::convert<JPH::Vec3>(v));
gr.velocity = Ogre::Vector3::ZERO;
});
ecs.system<const EngineData, CharacterBase, const CharacterBody,
@@ -750,18 +614,26 @@ void PhysicsModule::controlPhysics(flecs::entity e, bool enable)
}
bool PhysicsModule::raycastQuery(const Ogre::Vector3 &startPos,
const Ogre::Vector3 &endPos,
Ogre::Vector3 &position)
Ogre::Vector3 &position, JPH::BodyID &id)
{
return JoltPhysicsWrapper::getSingleton().raycastQuery(startPos, endPos,
position);
position, id);
}
void PhysicsModule::setDebugDraw(bool enable)
{
JoltPhysicsWrapper::getSingleton().setDebugDraw(enable);
}
bool WaterBody::isInWater(const JPH::BodyID &id) const
{
#if 0
flecs::entity e =
ECS::get().query_builder<const JPH::BodyID>().build().find(
[&](const JPH::BodyID &bodyid) {
return bodyid == id;
});
#endif
return inWater.find(id) != inWater.end();
}
}

View File

@@ -1,6 +1,7 @@
#ifndef _PHYSICS_MODULE_H_
#define _PHYSICS_MODULE_H_
#include <flecs.h>
#include <Ogre.h>
class JoltPhysicsWrapper;
namespace JPH
{
@@ -59,7 +60,8 @@ struct PhysicsModule {
static void controlPhysics(flecs::entity e, bool enable);
static bool raycastQuery(const Ogre::Vector3 &startPos,
const Ogre::Vector3 &endPos,
Ogre::Vector3 &position);
Ogre::Vector3 &position, JPH::BodyID &id);
static void setDebugDraw(bool enable);
};
}
#endif

View File

@@ -0,0 +1,886 @@
#include <nanoflann.hpp>
#include <vector>
#include <iostream>
#include <flecs.h>
#include <nlohmann/json.hpp>
#include <lua.hpp>
#include "Components.h"
#include "GameData.h"
#include "CharacterManagerModule.h"
#include "CharacterModule.h"
#include "items.h"
#include "GUIModule.h"
#include "GUIModuleCommon.h"
#include "LuaData.h"
#include "PhysicsModule.h"
#include "PlayerActionModule.h"
namespace ECS
{
struct OgreVector3Adaptor {
const std::vector<ActionNodeList::ActionNode> &nodes;
OgreVector3Adaptor(const std::vector<ActionNodeList::ActionNode> &nodes)
: nodes(nodes)
{
}
// Required by nanoflann: Number of data points
inline size_t kdtree_get_point_count() const
{
return nodes.size();
}
// Required by nanoflann: Returns the distance between the vector and a point
// Using squared distance is standard for performance
inline float kdtree_get_pt(const size_t idx, const size_t dim) const
{
return nodes[idx].position[dim];
}
// Optional: bounding box optimization (return false if not implemented)
template <class BBOX> bool kdtree_get_bbox(BBOX & /*bb*/) const
{
return false;
}
};
typedef nanoflann::KDTreeSingleIndexAdaptor<
nanoflann::L2_Simple_Adaptor<float, OgreVector3Adaptor>,
OgreVector3Adaptor, 3 /* dimensionality */
>
OgreKDTree;
struct ActionNodeList::indexObject {
OgreVector3Adaptor adaptor;
OgreKDTree index;
indexObject(const std::vector<ActionNodeList::ActionNode> &nodes)
: adaptor(nodes)
, index(3, adaptor,
nanoflann::KDTreeSingleIndexAdaptorParams(10))
{
}
};
struct TestNarrativeHandler : GUI::NarrationHandler {
int count;
TestNarrativeHandler()
: GUI::NarrationHandler()
, count(0)
{
}
void finish() override
{
_clear_narration();
}
void activate() override
{
_narration("Greetings...", {});
std::cout << getPropsJSON().dump(4) << std::endl;
count = 0;
}
void event(const Ogre::String &evt) override
{
if (evt == "narration_progress" ||
evt == "narration_answered") {
count++;
if (count == 1) {
_narration(
"Question..." +
Ogre::StringConverter::toString(
count),
{ "Answer1", "Answer2" });
} else {
_narration(
"Whatever..." +
Ogre::StringConverter::toString(
count),
{});
}
if (count > 5)
_finish();
}
if (evt == "narration_answered")
std::cout << "answer: " << getNarrationAnswer()
<< std::endl;
}
};
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_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());
const std::vector<ActionNodeList::ActionNode>
&nodes = ECS::get<ActionNodeList>()
.dynamicNodes;
lua_newtable(L);
int i;
for (i = 0; i < nodes.size(); i++) {
lua_pushinteger(L, i + 1);
lua_pushstring(
L,
nodes[i].props.dump().c_str());
lua_settable(L, -3);
}
return 1;
},
1);
lua_setfield(L, -2, "_get_goals");
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 SimpleWordHandler : PlayerActionModule::ActionWordHandler {
void operator()(int actor, flecs::entity town, int index,
const Ogre::String &word, int actionNode) override
{
if (index >= 0) {
TestNarrativeHandler *handle =
OGRE_NEW TestNarrativeHandler();
/* this is for NPCs only, right? */
const TownNPCs::NPCData &npc =
town.get<TownNPCs>().npcs.at(index);
flecs::entity e = npc.e;
for (const auto &anode : npc.actionNodes) {
if (anode.action == word) {
nlohmann::json props = anode.props;
props["initiator"] = actor;
props["recipient"] = index;
handle->setProperties(props.dump());
break;
}
}
ECS::get_mut<GUI>().addNarrationHandler(handle);
ECS::modified<GUI>();
}
}
};
PlayerActionModule::PlayerActionModule(flecs::world &ecs)
{
ecs.module<PlayerActionModule>();
ecs.import <CharacterManagerModule>();
ecs.component<ActionNodeList>()
.on_add([](flecs::entity e, ActionNodeList &alist) {
alist.nodeMutex = std::make_shared<std::mutex>();
alist.setDirty();
alist.nodes.reserve(1000);
alist.dynamicNodes.reserve(1000);
alist.setUISelected(-1);
alist.setReady();
})
.add(flecs::Singleton);
ecs.system<ActionNodeList>("updateNodeList")
.kind(flecs::OnUpdate)
.each([](ActionNodeList &list) {
if (list.isBusy())
return;
if (list.nodes.size() > 0) {
Ogre::SceneNode *cameraNode =
ECS::get<Camera>().mCameraNode;
Ogre::Vector3 cameraPos =
cameraNode->_getDerivedPosition();
flecs::entity player =
ECS::get<CharacterManagerModule>()
.getPlayer();
if (player.is_valid()) {
Ogre::Vector3 playerPos =
player.get<CharacterBase>()
.mBodyNode
->_getDerivedPosition();
list.UIquery(playerPos);
} else {
list.UIquery(cameraPos);
}
}
});
ecs.system<ActionNodeList, const Input>("ActivateActionNode")
.kind(flecs::OnUpdate)
.each([this](ActionNodeList &list, const Input &input) {
std::lock_guard<std::mutex> lock(*list.nodeMutex);
if (input.control & 32)
std::cout << "act pressed" << std::endl;
if (list.isBusy())
return;
if (input.act_pressed &&
list.getUIData().selected >= 0) {
std::cout
<< list.dynamicNodes[list.getUIData()
.selected]
.props.dump(4)
<< std::endl;
flecs::entity_t townid =
list.dynamicNodes[list.getUIData()
.selected]
.props["town"]
.get<flecs::entity_t>();
flecs::entity town = ECS::get().entity(townid);
int index = list.dynamicNodes[list.getUIData()
.selected]
.props["index"]
.get<int>();
for (auto it = actionWords.begin();
it != actionWords.end(); it++) {
if (it->first ==
list.dynamicNodes[list.getUIData()
.selected]
.action) {
(*it->second)(
-1, town, index,
list.dynamicNodes
[list.getUIData()
.selected]
.action,
list.getUIData()
.selected);
list.setBusy();
}
}
}
if (!ECS::get<GUI>().enabled)
list.setReady();
});
ecs.system<const EngineData>("UpdateActivatedWords")
.kind(flecs::OnUpdate)
.each([this](const EngineData &eng) {
for (auto it = activatedWords.begin();
it != activatedWords.end();) {
int ret = it->second->_update(eng.delta);
if (ret != ActivatedWordHandler::BUSY) {
delete it->second;
it = activatedWords.erase(it);
} else
it++;
}
});
}
void PlayerActionModule::addWordHandler(const Ogre::String &word,
ActionWordHandler *handler)
{
actionWords.insert({ word, handler });
}
void PlayerActionModule::removeWordHandler(const Ogre::String &word,
ActionWordHandler *handler)
{
for (auto it = actionWords.begin(); it != actionWords.end();) {
if (it->first == word && it->second == handler)
it = actionWords.erase(it);
else
it++;
}
}
static std::set<int> activeActors;
struct TestActivatedWordHandler : PlayerActionModule::ActivatedWordHandler {
int actor;
flecs::entity town;
int index;
Ogre::String word;
int actionNode;
ActionNodeList::ActionNode anode;
flecs::entity ch;
int state;
float delay;
std::unordered_map<Ogre::String, Ogre::Vector3> placeLocalOffset;
std::unordered_map<Ogre::String, Ogre::Quaternion> placeLocalRotation;
TestActivatedWordHandler(int actor, flecs::entity town, int index,
const Ogre::String &word, int actionNode)
: PlayerActionModule::ActivatedWordHandler()
, actor(actor)
, town(town)
, index(index)
, word(word)
, actionNode(actionNode)
, state(0)
, delay(0)
{
activeActors.insert(actor);
// dynamic nodes can disappear on us so to avoid that use a copy
anode = ECS::get<ActionNodeList>().dynamicNodes[actionNode];
if (actor == -1)
ch = ECS::get<CharacterManagerModule>().getPlayer();
else if (actor >= 0) {
const TownNPCs &npcs = town.get<TownNPCs>();
ch = npcs.npcs.at(actor).e;
}
if (anode.props.find("positions") == anode.props.end())
goto out;
for (const auto &position : anode.props["positions"]) {
if (position.find("name") == position.end())
continue;
Ogre::Vector3 localPosition;
Ogre::Quaternion localRotation;
localPosition.x = position["position_x"].get<float>();
localPosition.y = position["position_y"].get<float>();
localPosition.z = position["position_z"].get<float>();
localRotation.w = position["rotation_w"].get<float>();
localRotation.x = position["rotation_x"].get<float>();
localRotation.y = position["rotation_y"].get<float>();
localRotation.z = position["rotation_z"].get<float>();
placeLocalOffset[position["name"].get<Ogre::String>()] =
localPosition;
placeLocalRotation[position["name"].get<Ogre::String>()] =
localRotation;
}
out:;
}
void teleport(const Ogre::String &place)
{
if (placeLocalOffset.find(place) == placeLocalOffset.end())
return;
std::cout << "local offset: " << placeLocalOffset[place]
<< std::endl;
std::cout << "parent offset: " << anode.position << std::endl;
Ogre::Quaternion newRotation =
anode.rotation * placeLocalRotation[place];
Ogre::Vector3 newPosition =
anode.position +
anode.rotation * placeLocalOffset[place];
if (ch.is_valid() && ch.has<CharacterBase>()) {
ch.get<CharacterBase>()
.mBodyNode->_setDerivedOrientation(newRotation);
ch.get<CharacterBase>().mBodyNode->_setDerivedPosition(
newPosition);
}
if (actor >= 0) {
town.get_mut<TownNPCs>().npcs[actor].position =
newPosition;
town.get_mut<TownNPCs>().npcs[actor].orientation =
newRotation;
town.modified<TownNPCs>();
}
}
int update(float delta)
{
switch (state) {
case 0:
if (ECS::get<Input>().act)
delay += delta;
// activate anly after delay
if (ECS::get<Input>().act == 0 && delay > 0.2f) {
delay = 0.0f;
state = 10;
// Yay!!!
std::cout << "Node data: " << std::endl;
std::cout << anode.props.dump(4) << std::endl;
} else if (ECS::get<Input>().act == 0 &&
delay <= 0.2f) {
delay = 0.0f;
state = 100;
}
break;
case 10:
if (ch.is_valid()) {
PhysicsModule::controlPhysics(ch, false);
// no control by player or ai
ch.add<CharacterControlDisable>();
// else
// ch.set<CharacterInActuator>(
// { "idle", { 0, 0, 0 } });
}
delay = 0.0f;
state++;
break;
case 11:
teleport("enter");
delay = 0.0f;
state++;
break;
case 12:
if (ch.is_valid()) {
if (word == "sit")
ch.set<CharacterInActuator>(
{ "sitting-chair",
{ 0, 0, 0 } });
}
teleport("enter");
delay = 0.0f;
state++;
break;
case 13:
teleport("enter");
delay = 0.0f;
state = 50;
break;
case 50:
// teleport again to handle possible problems caused by root motion
delay = 0.0f;
teleport("enter");
state++;
break;
case 51:
// do not move anywhere until we depress key and wait a bit
if (ECS::get<Input>().act == 0)
delay += delta;
if (delay > 1.0f) {
delay = 0.0f;
state++;
}
break;
case 52:
// if the key is pressed for a second move to next state
if (ECS::get<Input>().act)
delay += delta;
if (delay > 1.0f) {
delay = 0.0f;
state++;
}
break;
case 53:
delay = 0.0;
state = 100;
break;
case 100:
delay = 0.0;
state++;
break;
case 101:
// wait until key is depressed for a second
if (ECS::get<Input>().act == 0)
delay += delta;
if (delay > 1.0f) {
delay = 0.0;
state++;
}
break;
case 102:
delay = 0.0f;
state = 0;
return OK;
break;
}
std::cout << this << " delay: " << delay << " state: " << state
<< std::endl;
return BUSY;
}
void enter()
{
delay = 0.0f;
state = 0;
ECS::get_mut<GUI>().enableActions = false;
ECS::modified<GUI>();
std::cout << "enter" << std::endl;
}
void exit(int result)
{
ch.remove<CharacterInActuator>();
ch.remove<CharacterControlDisable>();
PhysicsModule::controlPhysics(ch, true);
// OgreAssert(false, "exit");
delay = 0.0f;
state = 0;
ECS::get_mut<GUI>().enableActions = true;
ECS::modified<GUI>();
std::cout << "exit" << std::endl;
}
virtual ~TestActivatedWordHandler()
{
activeActors.erase(actor);
}
};
struct LuaWordHandler : PlayerActionModule::ActionWordHandler {
lua_State *L;
int ref;
void operator()(int actor, flecs::entity town, int index,
const Ogre::String &word, int actionNode) override
{
lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
if (lua_type(L, -1) == LUA_TFUNCTION) {
luaL_checktype(L, -1, LUA_TFUNCTION);
lua_pushinteger(L, town.id());
lua_pushinteger(L, index);
lua_pushstring(L, word.c_str());
if (lua_pcall(L, 3, 0, 0)) {
Ogre::LogManager::getSingleton().stream()
<< lua_tostring(L, -1);
OgreAssert(false, "Lua error");
}
} else if (lua_type(L, -1) == LUA_TTABLE) {
luaL_checktype(L, -1, LUA_TTABLE);
lua_pop(L, 1);
/* trying to talk to NPC activates narration mode */
if (index >= 0 && word == "talk") {
LuaNarrationHandler *handle =
OGRE_NEW LuaNarrationHandler(L, ref);
const TownNPCs::NPCData &npc =
town.get<TownNPCs>().npcs.at(index);
flecs::entity e = npc.e;
for (const auto &anode : npc.actionNodes) {
if (anode.action == word) {
nlohmann::json props =
anode.props;
props["initiator"] = actor;
props["recipient"] = index;
handle->setProperties(
props.dump());
break;
}
}
ECS::get_mut<GUI>().addNarrationHandler(handle);
ECS::modified<GUI>();
} else {
if (activeActors.find(actor) ==
activeActors.end()) {
TestActivatedWordHandler *handler =
OGRE_NEW TestActivatedWordHandler(
actor, town, index,
word, actionNode);
ECS::get_mut<PlayerActionModule>()
.addActivatedWordHandler(
word, handler);
ECS::modified<PlayerActionModule>();
}
}
}
}
};
void PlayerActionModule::addLuaWordHandler(const Ogre::String &word,
lua_State *L, int ref)
{
struct LuaWordHandler *handler = OGRE_NEW LuaWordHandler;
handler->L = L;
handler->ref = ref;
addWordHandler(word, handler);
}
void PlayerActionModule::removeLuaWordHandler(const Ogre::String &word,
lua_State *L, int ref)
{
for (auto it = actionWords.begin(); it != actionWords.end();) {
LuaWordHandler *handler =
static_cast<LuaWordHandler *>(it->second);
if (it->first == word && handler->L == L && handler->ref == ref)
it = actionWords.erase(it);
else
it++;
}
}
int PlayerActionModule::setupLuaActionHandler(lua_State *L)
{
luaL_checktype(L, 1, LUA_TSTRING);
if (lua_type(L, 2) == LUA_TFUNCTION) {
luaL_checktype(L, 2, LUA_TFUNCTION);
Ogre::String word = lua_tostring(L, 1);
lua_pushvalue(L, 2);
int ref = luaL_ref(L, LUA_REGISTRYINDEX);
addLuaWordHandler(word, L, ref);
} else if (lua_type(L, 2) == LUA_TTABLE) {
luaL_checktype(L, 2, LUA_TTABLE);
Ogre::String word = lua_tostring(L, 1);
lua_pushvalue(L, 2);
int ref = luaL_ref(L, LUA_REGISTRYINDEX);
addLuaWordHandler(word, L, ref);
}
return 0;
}
void PlayerActionModule::addActivatedWordHandler(const Ogre::String &word,
ActivatedWordHandler *handler)
{
for (auto it = activatedWords.begin(); it != activatedWords.end();
it++) {
}
activatedWords.insert({ word, handler });
}
void PlayerActionModule::removeActivatedWordHandler(
const Ogre::String &word, ActivatedWordHandler *handler)
{
for (auto it = activatedWords.begin(); it != activatedWords.end();) {
if (it->first == word && it->second == handler)
it = activatedWords.erase(it);
else
it++;
}
}
void ActionNodeList::updateDynamicNodes()
{
std::lock_guard<std::mutex> lock(*nodeMutex);
if (dynamicNodes.size() > nodes.size())
dynamicNodes.resize(nodes.size());
else {
dynamicNodes.clear();
dynamicNodes.insert(dynamicNodes.end(), nodes.begin(),
nodes.end());
}
ECS::get().query_builder<const TownNPCs>().each(
[this](flecs::entity town, const TownNPCs &npcs) {
for (auto it = npcs.npcs.begin(); it != npcs.npcs.end();
it++) {
dynamicNodes.insert(
dynamicNodes.end(),
it->second.actionNodes.begin(),
it->second.actionNodes.end());
}
});
dirty = true;
}
void ActionNodeList::build()
{
std::lock_guard<std::mutex> lock(*nodeMutex);
indexObj = std::make_shared<ActionNodeList::indexObject>(dynamicNodes);
indexObj->index.buildIndex();
dirty = false;
}
bool ActionNodeList::_query(const Ogre::Vector3 &position,
std::vector<size_t> &points,
std::vector<float> &distances)
{
std::vector<size_t> tmppoints;
std::vector<float> tmpdistances;
points.clear();
points.reserve(4);
distances.clear();
distances.reserve(4);
tmppoints.resize(4);
tmpdistances.resize(4);
if (!indexObj) {
dirty = true;
return false;
}
nanoflann::KNNResultSet<float> resultSet(4);
resultSet.init(tmppoints.data(), tmpdistances.data());
bool ret = indexObj->index.findNeighbors(resultSet, &position.x,
nanoflann::SearchParameters());
int i;
for (i = 0; i < resultSet.size(); i++)
if (tmpdistances[i] < 25.0f) {
points.push_back(tmppoints[i]);
distances.push_back(tmpdistances[i]);
}
return ret;
}
bool ActionNodeList::query_ai(const Ogre::Vector3 &position, float distance,
std::vector<size_t> &points,
std::vector<float> &distances)
{
std::lock_guard<std::mutex> lock(*nodeMutex);
std::vector<size_t> tmppoints;
std::vector<float> tmpdistances;
points.clear();
points.reserve(100);
distances.clear();
distances.reserve(100);
tmppoints.resize(100);
tmpdistances.resize(100);
if (!indexObj) {
dirty = true;
return false;
}
nanoflann::KNNResultSet<float> resultSet(100);
resultSet.init(tmppoints.data(), tmpdistances.data());
bool ret = indexObj->index.findNeighbors(resultSet, &position.x,
nanoflann::SearchParameters());
int i;
for (i = 0; i < resultSet.size(); i++)
if (tmpdistances[i] < distance) {
points.push_back(tmppoints[i]);
distances.push_back(tmpdistances[i]);
}
return ret;
}
int ActionNodeList::addNode(ActionNode &node)
{
std::lock_guard<std::mutex> lock(*nodeMutex);
int index = nodes.size();
nodes.push_back(node);
dirty = true;
return index;
}
void ActionNodeList::removeNode(int index)
{
std::lock_guard<std::mutex> lock(*nodeMutex);
nodes.erase(nodes.begin() + index);
dirty = true;
}
const ActionNodeList::UIData &ActionNodeList::getUIData()
{
std::lock_guard<std::mutex> lock(*uidata.mutex);
return uidata;
}
void ActionNodeList::setUISelected(int selected)
{
std::lock_guard<std::mutex> lock(*uidata.mutex);
uidata.selected = selected;
}
void ActionNodeList::setUIPoints(const std::vector<size_t> &points,
const std::vector<float> &distances)
{
std::lock_guard<std::mutex> lock(*uidata.mutex);
uidata.points = points;
uidata.distances = distances;
}
void ActionNodeList::UIquery(const Ogre::Vector3 &position)
{
bool needBuild = false;
{
std::lock_guard<std::mutex> lock(*nodeMutex);
if (dirty || !indexObj)
needBuild = true;
}
if (needBuild)
build();
{
std::lock_guard<std::mutex> lock(*uidata.mutex);
_query(position, uidata.points, uidata.distances);
}
}
void ActionNodeList::setDirty()
{
dirty = true;
}
void ActionNodeList::setReady()
{
busy = false;
}
void ActionNodeList::setBusy()
{
busy = true;
}
bool ActionNodeList::isBusy()
{
return busy;
}
}

View File

@@ -0,0 +1,109 @@
#ifndef PLAYERACTIONMODULE_H
#define PLAYERACTIONMODULE_H
#include <flecs.h>
#include <nlohmann/json.hpp>
#include <lua.h>
#include <Ogre.h>
namespace ECS {
struct ActionNodeList {
struct indexObject;
struct ActionNode {
Ogre::String action;
Ogre::String action_text;
Ogre::Vector3 position;
Ogre::Quaternion rotation;
float height;
float radius;
bool dynamic;
nlohmann::json props;
};
struct UIData {
std::shared_ptr<std::mutex> mutex;
int selected;
std::vector<size_t> points;
std::vector<float> distances;
UIData()
: mutex(std::make_shared<std::mutex>())
, selected(-1)
{
}
};
private:
bool dirty;
bool busy;
std::shared_ptr<indexObject> indexObj;
struct UIData uidata;
bool _query(const Ogre::Vector3 &position, std::vector<size_t> &points,
std::vector<float> &distances);
public:
std::shared_ptr<std::mutex> nodeMutex;
std::vector<ActionNode> nodes, dynamicNodes;
void updateDynamicNodes();
void build();
bool query_ai(const Ogre::Vector3 &position, float distance,
std::vector<size_t> &points,
std::vector<float> &distances);
int addNode(struct ActionNodeList::ActionNode &node);
void removeNode(int index);
const UIData &getUIData();
void setUISelected(int selected);
void setUIPoints(const std::vector<size_t> &points,
const std::vector<float> &distances);
void UIquery(const Ogre::Vector3 &position);
void setDirty(); // node was added or removed
void setReady();
void setBusy();
bool isBusy();
};
struct PlayerActionModule {
struct ActionWordHandler {
/** actor is -1 for player >=0 for NPCs */
virtual void operator()(int actor, flecs::entity town, int index,
const Ogre::String &word, int actionNode) = 0;
};
struct ActivatedWordHandler {
enum {OK = 0, BUSY, ERROR};
private:
bool active;
public:
ActivatedWordHandler(): active(false) {}
int _update(float delta)
{
if (!active) {
enter();
active = true;
}
int ret = update(delta);
if (ret != BUSY) {
exit(ret);
active = false;
}
return ret;
}
virtual ~ActivatedWordHandler() {}
bool _is_active() {return active;}
private:
virtual void enter() = 0;
virtual void exit(int result) = 0;
virtual int update(float delta) = 0;
};
std::multimap<Ogre::String, ActionWordHandler *>
actionWords;
std::multimap<Ogre::String, ActivatedWordHandler *> activatedWords;
PlayerActionModule(flecs::world &ecs);
void addWordHandler(const Ogre::String &word, ActionWordHandler *handler);
void removeWordHandler(const Ogre::String &word, ActionWordHandler *handler);
void addLuaWordHandler(const Ogre::String &word, lua_State *L, int ref);
void removeLuaWordHandler(const Ogre::String &word, lua_State *L, int ref);
int setupLuaActionHandler(lua_State *L);
void addActivatedWordHandler(const Ogre::String &word, ActivatedWordHandler *handler);
void removeActivatedWordHandler(const Ogre::String &word, ActivatedWordHandler *handler);
};
}
#endif // PLAYERACTIONMODULE_H

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

@@ -0,0 +1,808 @@
#include <iostream>
#include <algorithm>
#include <nlohmann/json.hpp>
#include <OgreTerrainGroup.h>
#include <OgreFileSystemLayer.h>
#include <OgreRTShaderSystem.h>
#include <OgreStaticGeometry.h>
#include <OgreMeshLodGenerator.h>
#include <OgreWorkQueue.h>
#include <Procedural.h>
#include "Components.h"
#include "GameData.h"
#include "TerrainModule.h"
#include "physics.h"
#include "PhysicsModule.h"
#include "items.h"
#include "StaticGeometryModule.h"
namespace ECS
{
static bool itemsLoaded = false;
static bool furnitureLoaded = false;
static bool templatesLoaded = false;
static std::list<std::pair<long, long> > addQueue;
StaticGeometryModule::StaticGeometryModule(flecs::world &ecs)
{
ecs.module<StaticGeometryModule>();
ecs.component<TerrainSlotParent>();
ecs.component<TerrainItem>();
ecs.component<FurnitureItem>();
ecs.component<FurnitureInstance>()
.on_remove([](flecs::entity e, FurnitureInstance &instance) {
if (instance.furniture) {
instance.furniture
->destroyAllChildrenAndObjects();
instance.furniture->getCreator()
->destroySceneNode(instance.furniture);
instance.furniture = nullptr;
}
})
.on_set([](flecs::entity e, FurnitureInstance &instance) {
if (instance.furniture !=
e.get<FurnitureInstance>().furniture) {
FurnitureInstance &f =
e.get_mut<FurnitureInstance>();
if (f.furniture) {
f.furniture
->destroyAllChildrenAndObjects();
f.furniture->getCreator()
->destroySceneNode(f.furniture);
}
}
})
.on_add([](flecs::entity e, FurnitureInstance &instance) {
instance.furniture = nullptr;
});
ecs.component<TerrainItemNode>().on_remove([](flecs::entity e,
TerrainItemNode &item) {
if (item.itemNode) {
item.itemNode->destroyAllChildrenAndObjects();
item.itemNode->getCreator()->destroySceneNode(
item.itemNode);
item.itemNode = nullptr;
}
if (item.geo) {
ECS::get<EngineData>().mScnMgr->destroyStaticGeometry(
item.geo);
item.geo = nullptr;
}
});
ecs.component<TerrainItemMeshNode>();
ecs.component<GeometryUpdateItem>();
ecs.import <TerrainModule>();
ecs.observer<const Terrain>("LoadTerrainItems")
.event(flecs::OnSet)
.each([&](const Terrain &terrain) {
if (terrain.mTerrainGroup && !itemsLoaded) {
loadItems();
itemsLoaded = true;
}
});
if (!Ogre::MeshLodGenerator::getSingletonPtr())
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) {
loadItems();
itemsLoaded = true;
return;
}
if (!furnitureLoaded) {
loadFurniture();
furnitureLoaded = true;
}
if (!templatesLoaded) {
loadTemplates();
templatesLoaded = true;
}
std::list<std::pair<long, long> > output;
while (!addQueue.empty()) {
std::pair<long, long> item = addQueue.front();
std::pair<long, long> slot = { item.first,
item.second };
flecs::entity parent =
ECS::get()
.query_builder<const TerrainSlotParent>()
.build()
.find([&slot](const TerrainSlotParent
&parent) {
return parent.slot == slot;
});
if (parent.is_valid()) {
items.clear();
ECS::get()
.query_builder<const TerrainItem>()
.with(flecs::ChildOf, parent)
.without<TerrainItemNode>()
.write<TerrainItemNode>()
.build()
.each([&](flecs::entity e,
const TerrainItem &item) {
items.push_back(e);
});
for (auto e : items) {
createItemGeometry(e);
}
addQueue.pop_front();
} else {
output.push_back(item);
addQueue.pop_front();
}
}
OgreAssert(addQueue.empty(), "queue is not empty");
if (!output.empty())
addQueue = output;
});
}
void StaticGeometryModule::addGeometryForSlot(long x, long y)
{
addQueue.push_back({ x, y });
}
void StaticGeometryModule::removeGeometryForSlot(long x, long y)
{
std::pair<long, long> slot = { x, y };
flecs::entity parent =
ECS::get().query_builder<const TerrainSlotParent>().build().find(
[&slot](const TerrainSlotParent &parent) {
return parent.slot == slot;
});
if (parent.is_valid()) {
ECS::get()
.query_builder<const TerrainItem>()
.with(flecs::ChildOf, parent)
.without<TerrainItemNode>()
.build()
.each([](flecs::entity e, const TerrainItem &item) {
OgreAssert(false, "Implement item geo destroy" +
item.properties);
});
}
}
flecs::entity
StaticGeometryModule::createItem(const Ogre::Vector3 &position,
const Ogre::Quaternion &orientation,
const Ogre::String &type)
{
long x, y;
ECS::get<Terrain>().mTerrainGroup->convertWorldPositionToTerrainSlot(
position, &x, &y);
std::pair<long, long> pos{ x, y };
flecs::entity slot =
ECS::get().query_builder<const TerrainSlotParent>().build().find(
[&](const TerrainSlotParent &slot) -> bool {
return slot.slot == pos;
});
if (!slot.is_valid())
slot = ECS::get().entity().set<TerrainSlotParent>({ pos });
flecs::entity item = ECS::get().entity().child_of(slot);
nlohmann::json jproperties;
jproperties["type"] = type;
item.set<TerrainItem>({ position, orientation, jproperties.dump() });
std::cout << "createItem: " << x << " " << y << " " << item.id()
<< std::endl;
return item;
}
void StaticGeometryModule::setItemProperties(flecs::entity id,
Ogre::String properties)
{
OgreAssert(id.is_valid(), "bad id");
id.get_mut<TerrainItem>().properties = properties;
id.modified<TerrainItem>();
}
const Ogre::String &StaticGeometryModule::getItemProperties(flecs::entity id)
{
OgreAssert(id.is_valid(), "bad id");
return id.get<TerrainItem>().properties;
}
nlohmann::json templates;
void StaticGeometryModule::loadTemplates()
{
if (!Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(
"templates.list"))
return;
Ogre::String group =
Ogre::ResourceGroupManager::getSingleton()
.findGroupContainingResource("templates.list");
Ogre::DataStreamPtr stream =
Ogre::ResourceGroupManager::getSingleton().openResource(
"templates.list", group);
Ogre::String json = stream->getAsString();
nlohmann::json jtemplates = nlohmann::json::parse(json);
templates = jtemplates;
}
void StaticGeometryModule::saveTemplates()
{
Ogre::String path = "resources/buildings/templates.list";
if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(
"templates.list")) {
Ogre::String group =
Ogre::ResourceGroupManager::getSingleton()
.findGroupContainingResource("templates.list");
Ogre::FileInfoListPtr fileInfoList(
Ogre::ResourceGroupManager::getSingleton()
.findResourceFileInfo(group, "templates.list"));
OgreAssert(fileInfoList->size() == 1,
"templates.list should be there and only once");
path = fileInfoList->at(0).archive->getName() + "/" +
"templates.list";
Ogre::FileSystemLayer::removeFile(path);
}
std::fstream fout(path.c_str(), std::ios::out);
fout << templates.dump();
fout.close();
}
void StaticGeometryModule::saveItems()
{
Ogre::String path = "resources/buildings/items.list";
if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(
"items.list")) {
Ogre::String group =
Ogre::ResourceGroupManager::getSingleton()
.findGroupContainingResource("items.list");
Ogre::FileInfoListPtr fileInfoList(
Ogre::ResourceGroupManager::getSingleton()
.findResourceFileInfo(group, "items.list"));
OgreAssert(fileInfoList->size() == 1,
"worpd_map.png should be there and only once");
path = fileInfoList->at(0).archive->getName() + "/" +
"items.list";
Ogre::FileSystemLayer::removeFile(path);
}
std::fstream fout(path.c_str(), std::ios::out);
nlohmann::json jitemlist;
ECS::get().query_builder<const TerrainItem>().build().each(
[&](flecs::entity e, const TerrainItem &item) {
nlohmann::json jitem;
to_json(jitem["position"], item.position);
to_json(jitem["orientation"], item.orientation);
to_json(jitem["properties"], item.properties);
jitem["objects"] = nlohmann::json::array();
ECS::get()
.query_builder<const TerrainItem>()
.with(flecs::ChildOf, e)
.build()
.each([&](flecs::entity obje,
const TerrainItem &objItem) {
nlohmann::json jobjitem;
to_json(jobjitem["position"],
objItem.position);
to_json(jobjitem["orientation"],
objItem.orientation);
to_json(jobjitem["properties"],
objItem.properties);
jitem["objects"].push_back(jobjitem);
});
jitemlist.push_back(jitem);
});
fout << jitemlist.dump();
fout.close();
}
void StaticGeometryModule::loadItems()
{
if (!Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(
"items.list"))
return;
Ogre::String group = Ogre::ResourceGroupManager::getSingleton()
.findGroupContainingResource("items.list");
Ogre::DataStreamPtr stream =
Ogre::ResourceGroupManager::getSingleton().openResource(
"items.list", group);
Ogre::String json = stream->getAsString();
nlohmann::json jlist = nlohmann::json::parse(json);
ECS::get().delete_with<TerrainItem>();
ECS::get().delete_with<TerrainSlotParent>();
for (const auto &v : jlist) {
Ogre::Vector3 position;
Ogre::Quaternion orientation;
Ogre::String properties;
from_json(v["position"], position);
from_json(v["orientation"], orientation);
properties = v["properties"].get<Ogre::String>();
std::cout << v.dump(4) << std::endl;
long x, y;
ECS::get<Terrain>()
.mTerrainGroup->convertWorldPositionToTerrainSlot(
position, &x, &y);
std::pair<long, long> pos{ x, y };
flecs::entity slot =
ECS::get()
.query_builder<const TerrainSlotParent>()
.build()
.find([&](const TerrainSlotParent &slot)
-> bool {
return slot.slot == pos;
});
if (!slot.is_valid())
slot = ECS::get().entity().set<TerrainSlotParent>(
{ pos });
OgreAssert(slot.is_valid(), "Failed to create slot");
flecs::entity item =
ECS::get().entity().child_of(slot).set<TerrainItem>(
{ position, orientation, properties });
if (v.find("objects") != v.end()) {
for (auto &obj : v["objects"]) {
Ogre::Vector3 objposition;
Ogre::Quaternion objorientation;
Ogre::String objproperties;
from_json(obj["position"], objposition);
from_json(obj["orientation"], objorientation);
objproperties =
obj["properties"].get<Ogre::String>();
flecs::entity itemObj =
ECS::get()
.entity()
.child_of(item)
.set<TerrainItem>(
{ objposition,
objorientation,
objproperties });
}
}
std::cout << "createItem: " << x << " " << y << " " << item.id()
<< std::endl;
std::cout << "position: " << item.id() << " " << position
<< std::endl;
}
}
void StaticGeometryModule::saveFurniture()
{
/* No saving - furniture is generated by blender */
}
void StaticGeometryModule::loadFurniture()
{
ECS::get().delete_with<FurnitureItem>();
static std::vector<Ogre::String> glb_names;
const std::vector<Ogre::String> &groups =
Ogre::ResourceGroupManager::getSingleton().getResourceGroups();
if (glb_names.size() == 0) {
int i;
for (i = 0; i < groups.size(); i++) {
std::vector<Ogre::String> names =
*Ogre::ResourceGroupManager::getSingleton()
.findResourceNames(
groups[i],
"furniture-*.glb.json");
glb_names.insert(glb_names.end(), names.begin(),
names.end());
}
}
for (auto &g : glb_names) {
Ogre::String group = Ogre::ResourceGroupManager::getSingleton()
.findGroupContainingResource(g);
Ogre::DataStreamPtr stream =
Ogre::ResourceGroupManager::getSingleton().openResource(
g, group);
Ogre::String json = stream->getAsString();
nlohmann::json jdata = nlohmann::json::parse(json);
std::vector<Ogre::String> tags;
for (auto &tag : jdata["tags"]) {
Ogre::String stag = tag.get<Ogre::String>();
tags.push_back(stag);
}
Ogre::String meshName = jdata["mesh"].get<Ogre::String>();
Ogre::MeshPtr mesh =
Ogre::MeshManager::getSingleton().getByName(meshName);
if (mesh) {
Ogre::LodConfig meshconf(mesh);
Geometry::setupLods(meshconf);
}
ECS::get().entity().set<FurnitureItem>({ json, tags });
std::cout << "path: " << g << std::endl;
}
}
void StaticGeometryModule::getItemPositionPerSlot(
long x, long y, std::list<Ogre::Vector3> *positions)
{
std::pair<long, long> pos{ x, y };
if (!positions)
return;
flecs::entity slot =
ECS::get().query_builder<const TerrainSlotParent>().build().find(
[&](const TerrainSlotParent &slot) -> bool {
return slot.slot == pos;
});
if (!slot.is_valid())
return;
ECS::get()
.query_builder<const TerrainItem>()
.with(flecs::ChildOf, slot)
.build()
.each([&](flecs::entity e, const TerrainItem &item) {
positions->push_back(item.position);
});
}
void StaticGeometryModule::getItemPositions(std::list<Ogre::Vector3> *positions)
{
ECS::get().query_builder<const TerrainItem>().build().each(
[&](flecs::entity e, const TerrainItem &item) {
positions->push_back(item.position);
});
}
void StaticGeometryModule::getItemPositionAndRotation(
flecs::entity e, Ogre::Vector3 &position, Ogre::Quaternion &orientation)
{
position = e.get<TerrainItem>().position;
orientation = e.get<TerrainItem>().orientation;
}
void StaticGeometryModule::getItemsProperties(
std::list<std::pair<flecs::entity, Ogre::String> > *items)
{
ECS::get().query_builder<const TerrainItem>().build().each(
[&](flecs::entity e, const TerrainItem &item) {
items->push_back({ e, item.properties });
});
}
void StaticGeometryModule::createItemGeometry(flecs::entity e)
{
Geometry::createItemGeometry(e);
}
void StaticGeometryModule::destroyItemGeometry(flecs::entity e)
{
Geometry::destroyItemGeometry(e);
}
nlohmann::json &StaticGeometryModule::getTemplates()
{
return templates;
}
void StaticGeometryModule::updateItemGeometry(flecs::entity e)
{
if (e.has<GeometryUpdateItem>())
return;
e.add<GeometryUpdateItem>();
Ogre::Root::getSingleton().getWorkQueue()->addTask([e]() {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
Ogre::Root::getSingleton().getWorkQueue()->addMainThreadTask(
[e]() {
Geometry::updateItemGeometry(e);
e.remove<GeometryUpdateItem>();
});
});
}
void StaticGeometryModule::addTriangleBufferWork(
const Ogre::String &meshName, Ogre::StaticGeometry *geo,
const Ogre::Vector3 &position, const Ogre::Quaternion &rotation,
const Procedural::TriangleBuffer &tb)
{
struct WorkData {
Ogre::String meshName;
Ogre::StaticGeometry *geo;
Ogre::Vector3 position;
Ogre::Quaternion rotation;
Procedural::TriangleBuffer tb;
};
WorkData data = { meshName, geo, position, rotation, tb };
Ogre::Root::getSingleton().getWorkQueue()->addTask([captData = std::move(
data)]() {
Ogre::Root::getSingleton().getWorkQueue()->addMainThreadTask(
[captData]() {
Ogre::MeshPtr mesh =
captData.tb.transformToMesh(
captData.meshName);
Ogre::Entity *ent =
ECS::get<EngineData>()
.mScnMgr->createEntity(mesh);
captData.geo->addEntity(ent, captData.position,
captData.rotation);
ECS::get<EngineData>().mScnMgr->destroyEntity(
ent);
});
});
}
struct TiledMeshes {
struct Tile {
Ogre::String materialName;
std::shared_ptr<Ogre::VertexData> vertexData;
std::shared_ptr<Ogre::IndexData> indexData;
std::set<uint32_t> positions;
};
std::map<Ogre::String, Tile> tiles;
uint32_t packKey(const Ogre::Vector3i &position)
{
uint32_t key = 0;
key |= (uint32_t)(position[2] + 512) << 20;
key |= (uint32_t)(position[1] + 512) << 10;
key |= (uint32_t)(position[0] + 512) << 0;
return key;
}
void unpackKey(uint32_t key, Ogre::Vector3i &position)
{
uint32_t mask = 0x3ff;
position[0] = (int)(key & mask) - 512;
position[1] = (int)((key >> 10) & mask) - 512;
position[2] = (int)((key >> 20) & mask) - 512;
}
void setTile(const Ogre::String &name, const Ogre::Vector3i &position)
{
if (tiles.find(name) == tiles.end())
return;
tiles[name].positions.insert(packKey(position));
}
void clearTile(const Ogre::String &name, const Ogre::Vector3i &position)
{
if (tiles.find(name) == tiles.end())
return;
tiles[name].positions.erase(packKey(position));
}
void addTile(const Ogre::String &name, Ogre::MeshPtr mesh)
{
if (mesh->getSubMeshes().size() != 1)
return;
Ogre::SubMesh *submesh = mesh->getSubMesh(0);
Ogre::VertexData *vertexData;
if (submesh->useSharedVertices)
vertexData = mesh->sharedVertexData->clone();
else
vertexData = submesh->vertexData->clone();
tiles[name] = { submesh->getMaterialName(),
std::shared_ptr<Ogre::VertexData>(vertexData),
std::shared_ptr<Ogre::IndexData>(
submesh->indexData->clone()),
{} };
}
struct buildSettings {
Ogre::String meshName;
Ogre::String materialName;
Ogre::MeshPtr mesh;
Ogre::SubMesh *sm;
int vertexCount, vertexOffset;
int indexCount, indexOffset;
Ogre::VertexDeclaration *vdecl;
Ogre::HardwareVertexBufferSharedPtr vbuf;
Ogre::AxisAlignedBox bounds;
bool setBounds;
};
void configureSettings(struct buildSettings &settings)
{
settings.mesh = Ogre::MeshManager::getSingleton().createManual(
settings.meshName,
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
settings.mesh->createVertexData();
settings.sm = settings.mesh->createSubMesh();
settings.vertexCount = 0;
settings.indexCount = 0;
settings.vdecl = nullptr;
for (const auto &tile : tiles) {
settings.vertexCount +=
tile.second.vertexData->vertexCount *
tile.second.positions.size();
settings.indexCount +=
tile.second.indexData->indexCount *
tile.second.positions.size();
if (!settings.vdecl) {
settings.vdecl =
tile.second.vertexData
->vertexDeclaration->clone();
settings.materialName =
tile.second.materialName;
}
}
settings.mesh->sharedVertexData->vertexStart = 0;
settings.mesh->sharedVertexData->vertexCount =
settings.vertexCount;
settings.mesh->sharedVertexData->vertexDeclaration =
settings.vdecl;
settings.vbuf =
Ogre::HardwareBufferManager::getSingleton()
.createVertexBuffer(
settings.vdecl->getVertexSize(0),
settings.vertexCount,
Ogre::HBU_GPU_ONLY);
settings.mesh->sharedVertexData->vertexBufferBinding->setBinding(
0, settings.vbuf);
settings.sm->indexData->indexStart = 0;
settings.sm->indexData->indexCount = settings.indexCount;
settings.sm->indexData->indexBuffer =
Ogre::HardwareBufferManager::getSingleton()
.createIndexBuffer(
Ogre::HardwareIndexBuffer::IT_32BIT,
settings.sm->indexData->indexCount * 8,
Ogre::HBU_GPU_ONLY);
settings.sm->setMaterialName(settings.materialName);
settings.setBounds = true;
}
void processIndex(struct buildSettings &settings,
const struct Tile &tile, unsigned int *dstIndices)
{
int j;
std::shared_ptr<Ogre::IndexData> srcIndexData = tile.indexData;
int srcIndexCount = srcIndexData->indexCount;
std::shared_ptr<Ogre::VertexData> srcVertexData =
tile.vertexData;
int srcVertexCount = srcVertexData->vertexCount;
Ogre::HardwareIndexBufferSharedPtr srcIbuf =
srcIndexData->indexBuffer;
Ogre::HardwareBufferLockGuard srcIndexLock(
srcIbuf, Ogre::HardwareBuffer::HBL_READ_ONLY);
if (srcIndexData->indexBuffer->getType() ==
Ogre::HardwareIndexBuffer::IT_32BIT) {
unsigned int *indices =
static_cast<unsigned int *>(srcIndexLock.pData);
for (j = 0; j < srcIndexCount; j++)
dstIndices[settings.indexOffset + j] =
indices[j] + settings.vertexOffset;
} else if (srcIndexData->indexBuffer->getType() ==
Ogre::HardwareIndexBuffer::IT_16BIT) {
unsigned short *indices = static_cast<unsigned short *>(
srcIndexLock.pData);
for (j = 0; j < srcIndexCount; j++)
dstIndices[settings.indexOffset + j] =
indices[j] + settings.vertexOffset;
}
}
void processSingleVertex(
struct buildSettings &settings,
const Ogre::VertexDeclaration::VertexElementList &srcElements,
uint32_t offset, unsigned char *srcData, unsigned char *dstData)
{
for (const auto &srcElement : srcElements) {
unsigned char *srcPtr, *dstPtr;
const Ogre::VertexElement *destElement =
settings.vdecl->findElementBySemantic(
srcElement.getSemantic(),
srcElement.getIndex());
if (!destElement)
goto out;
if (srcElement.getType() != destElement->getType() ||
srcElement.getSize() != destElement->getSize())
goto out;
srcPtr = srcData + srcElement.getOffset();
dstPtr = dstData + destElement->getOffset();
if (destElement->getSemantic() == Ogre::VES_POSITION) {
float *srcPositionData =
reinterpret_cast<float *>(srcPtr);
float *dstPositionData =
reinterpret_cast<float *>(dstPtr);
Ogre::Vector3 position(srcPositionData[0],
srcPositionData[1],
srcPositionData[2]);
Ogre::Vector3i offsetv;
unpackKey(offset, offsetv);
position.x += (float)offsetv[0];
position.y += (float)offsetv[1];
position.z += (float)offsetv[2];
dstPositionData[0] = position.x;
dstPositionData[1] = position.y;
dstPositionData[2] = position.z;
if (settings.setBounds) {
settings.bounds.setMinimum(position);
settings.bounds.setMaximum(position);
settings.setBounds = false;
} else
settings.bounds.merge(position);
} else if (destElement->getSemantic() ==
Ogre::VES_NORMAL) {
float *srcNormalData =
reinterpret_cast<float *>(srcPtr);
float *dstNormalData =
reinterpret_cast<float *>(dstPtr);
Ogre::Vector3 normal(srcNormalData[0],
srcNormalData[1],
srcNormalData[2]);
dstNormalData[0] = normal.x;
dstNormalData[1] = normal.y;
dstNormalData[2] = normal.z;
} else
memcpy(dstPtr, srcPtr, srcElement.getSize());
out:;
}
}
void processTile(struct buildSettings &settings,
const struct Tile &tile, uint32_t position,
unsigned char *dstpData)
{
std::shared_ptr<Ogre::VertexData> srcVertexData =
tile.vertexData;
const Ogre::VertexDeclaration *srcDecl =
srcVertexData->vertexDeclaration;
const Ogre::VertexBufferBinding *srcBind =
srcVertexData->vertexBufferBinding;
int srcVertexCount = srcVertexData->vertexCount;
Ogre::HardwareVertexBufferSharedPtr srcVbuf =
srcBind->getBuffer(0);
std::shared_ptr<Ogre::IndexData> srcIndexData = tile.indexData;
int srcIndexCount = srcIndexData->indexCount;
Ogre::HardwareBufferLockGuard srcVertexLock(
srcVbuf, 0, srcVertexCount * srcDecl->getVertexSize(0),
Ogre::HardwareBuffer::HBL_READ_ONLY);
const Ogre::VertexDeclaration::VertexElementList &srcElements =
srcDecl->getElements();
int j;
unsigned char *srcpData =
static_cast<unsigned char *>(srcVertexLock.pData);
for (j = 0; j < srcVertexCount; j++) {
unsigned char *srcData =
srcpData + j * srcVbuf->getVertexSize();
unsigned char *dstData =
dstpData + (settings.vertexOffset +
j) * settings.vbuf->getVertexSize();
processSingleVertex(settings, srcElements, position,
srcData, dstData);
}
}
Ogre::MeshPtr build(const Ogre::String &meshName)
{
buildSettings settings;
settings.meshName = meshName;
configureSettings(settings);
{
Ogre::HardwareBufferLockGuard vertexLock(
settings.vbuf, 0,
settings.vertexCount *
settings.vdecl->getVertexSize(0),
Ogre::HardwareBuffer::HBL_NO_OVERWRITE);
Ogre::HardwareBufferLockGuard indexLock(
settings.sm->indexData->indexBuffer,
Ogre::HardwareBuffer::HBL_NO_OVERWRITE);
settings.vertexOffset = 0;
settings.indexOffset = 0;
unsigned char *dstpData =
static_cast<unsigned char *>(vertexLock.pData);
unsigned int *dstIndices =
static_cast<unsigned int *>(indexLock.pData);
for (const auto &tile : tiles) {
std::shared_ptr<Ogre::IndexData> srcIndexData =
tile.second.indexData;
int srcIndexCount = srcIndexData->indexCount;
std::shared_ptr<Ogre::VertexData> srcVertexData =
tile.second.vertexData;
int srcVertexCount = srcVertexData->vertexCount;
for (const auto &position :
tile.second.positions) {
processTile(settings, tile.second,
position, dstpData);
processIndex(settings, tile.second,
dstIndices);
settings.vertexOffset += srcVertexCount;
settings.indexOffset += srcIndexCount;
Ogre::Vector3i vposition;
unpackKey(position, vposition);
std::cout << "position: " << position
<< " " << vposition
<< std::endl;
}
}
settings.mesh->_setBounds(settings.bounds);
}
Ogre::LodConfig config(settings.mesh);
// config.advanced.useCompression = false;
// config.advanced.useVertexNormals = true;
config.advanced.preventPunchingHoles = true;
config.advanced.preventBreakingLines = true;
config.createGeneratedLodLevel(2, 0.15f);
config.createGeneratedLodLevel(20, 0.49f);
config.createGeneratedLodLevel(15, 0.49f);
config.createGeneratedLodLevel(150, 0.75f);
config.advanced.useBackgroundQueue = false;
Ogre::MeshLodGenerator::getSingleton().generateLodLevels(
config);
return settings.mesh;
}
void removeTile(const Ogre::String &name)
{
tiles.erase(name);
}
TiledMeshes()
{
}
};
}

View File

@@ -0,0 +1,78 @@
#ifndef _STATIC_GEOMETRY_MODULE_H_
#define _STATIC_GEOMETRY_MODULE_H_
#include <flecs.h>
#include <nlohmann/json.hpp>
#include <Ogre.h>
namespace Procedural
{
class TriangleBuffer;
}
namespace Ogre
{
struct LodConfig;
}
namespace ECS
{
struct TerrainSlotParent {
std::pair<long, long> slot;
};
struct TerrainItem {
Ogre::Vector3 position;
Ogre::Quaternion orientation;
Ogre::String properties;
};
struct TerrainItemNode {
Ogre::SceneNode *itemNode;
Ogre::StaticGeometry *geo;
};
struct TerrainItemMeshNode {
Ogre::SceneNode *itemNode;
Ogre::StaticGeometry *geo;
};
struct FurnitureItem {
Ogre::String properties;
std::vector<Ogre::String> tags;
};
struct FurnitureInstance {
Ogre::SceneNode *furniture;
};
struct GeometryUpdateItem {};
struct TownCollider {};
struct StaticGeometryModule {
StaticGeometryModule(flecs::world &ecs);
static void addGeometryForSlot(long x, long y);
static void removeGeometryForSlot(long x, long y);
static flecs::entity createItem(const Ogre::Vector3 &position,
const Ogre::Quaternion &orientation,
const Ogre::String &type);
static void setItemProperties(flecs::entity id,
Ogre::String properties);
static const Ogre::String &getItemProperties(flecs::entity id);
static void loadTemplates();
static void saveTemplates();
static void saveItems();
static void loadItems();
static void saveFurniture();
static void loadFurniture();
static void getItemPositionPerSlot(long x, long y,
std::list<Ogre::Vector3> *positions);
static void getItemPositions(std::list<Ogre::Vector3> *positions);
static void getItemPositionAndRotation(flecs::entity e,
Ogre::Vector3 &position,
Ogre::Quaternion &orientation);
static void getItemsProperties(
std::list<std::pair<flecs::entity, Ogre::String> > *items);
static void createItemGeometry(flecs::entity e);
static void destroyItemGeometry(flecs::entity e);
static nlohmann::json &getTemplates();
static void updateItemGeometry(flecs::entity e);
static void addTriangleBufferWork(const Ogre::String &meshName,
Ogre::StaticGeometry *geo,
const Ogre::Vector3 &position,
const Ogre::Quaternion &rotation,
const Procedural::TriangleBuffer &tb);
};
}
#endif

View File

@@ -16,10 +16,11 @@
#include "CharacterModule.h"
#include "SunModule.h"
#include "PhysicsModule.h"
#include "StaticGeometryModule.h"
#include "TerrainModule.h"
#define TERRAIN_SIZE 129
#define TERRAIN_WORLD_SIZE 1000.0f
#define TERRAIN_SIZE 65
#define TERRAIN_WORLD_SIZE 500.0f
#define ENDLESS_TERRAIN_FILE_PREFIX Ogre::String("EndlessWorldTerrain")
#define ENDLESS_TERRAIN_FILE_SUFFIX Ogre::String("dat")
@@ -32,6 +33,12 @@
#define ENDLESS_PAGE_MAX_Y 0x7FFF
namespace ECS
{
class DummyPageProvider;
/* Components */
struct TerrainPrivate {
DummyPageProvider *mDummyPageProvider;
Ogre::Timer mSunUpdate;
};
#define BRUSH_SIZE 64
struct HeightData {
@@ -83,15 +90,17 @@ struct HeightData {
}
int get_img_x(float world_x)
{
float world_img_x = world_x + img.getWidth() * BRUSH_SIZE / 2;
int ret = world_img_x / BRUSH_SIZE;
float world_img_x = world_x + (float)img.getWidth() *
(float)BRUSH_SIZE / 2.0f;
int ret = (world_img_x + BRUSH_SIZE - 1) / BRUSH_SIZE;
// ret = Ogre::Math::Clamp(ret, 0, (int)img.getWidth() - 1);
return ret;
}
int get_img_y(float world_z)
{
float world_img_y = world_z + img.getHeight() * BRUSH_SIZE / 2;
int ret = world_img_y / BRUSH_SIZE;
float world_img_y = world_z + (float)img.getHeight() *
(float)BRUSH_SIZE / 2.0f;
int ret = (world_img_y + BRUSH_SIZE - 1) / BRUSH_SIZE;
// ret = Ogre::Math::Clamp(ret, 0, (int)img.getHeight() - 1);
return ret;
}
@@ -189,10 +198,12 @@ out:
long world_grid_y = world_y + grid_center_y;
float amplitude = 150.0f;
if (world_grid_x < 0 || world_grid_y < 0) {
#if 0
std::cout << "world: " << world_x << " " << world_y
<< " ";
std::cout << "grid: " << world_grid_x << " ";
std::cout << world_grid_y << std::endl;
#endif
return -amplitude;
}
OgreAssert(world_grid_x >= 0, "bad world x");
@@ -234,6 +245,7 @@ class FlatTerrainDefiner
long y;
};
std::deque<struct gen_collider> collider_queue;
std::deque<struct gen_collider> colliderRemove_queue;
public:
FlatTerrainDefiner(Ogre::SceneManager *
@@ -247,6 +259,8 @@ public:
}
private:
std::mutex mtx;
public:
void createTerrainChunk(Ogre::TerrainGroup *terrainGroup, long x,
long y)
@@ -264,7 +278,7 @@ public:
float worldSize = terrain->getWorldSize();
float scaled = worldSize / (size - 1);
Ogre::Vector3 bodyPosition = terrain->getPosition();
bodyPosition.y += (maxH + minH) / 2.0f;
// bodyPosition.y += (maxH + minH) / 2.0f;
bodyPosition.x += worldSize / 2.0f;
bodyPosition.z += worldSize / 2.0f;
Ogre::Vector3 offset =
@@ -276,6 +290,7 @@ public:
}
void define(Ogre::TerrainGroup *terrainGroup, long x, long y) override
{
std::lock_guard<std::mutex> guard(mtx);
uint16_t terrainSize = terrainGroup->getTerrainSize();
float *heightMap = OGRE_ALLOC_T(float, terrainSize *terrainSize,
MEMCATEGORY_GEOMETRY);
@@ -295,19 +310,9 @@ public:
long world_y = (long)(worldPos.z + i -
(terrainSize - 1) / 2);
float height = 0.0f;
int k, l;
for (l = -1; l < 2; l++)
for (k = -1; k < 2; k++) {
height +=
HeightData::get_singleton()
->get_height(
terrainGroup,
world_x +
4 * k,
world_y +
4 * l);
}
height /= 9.0f;
height +=
HeightData::get_singleton()->get_height(
terrainGroup, world_x, world_y);
// height = -2.0f;
heightMap[i * terrainSize + j] = height;
@@ -333,17 +338,30 @@ public:
}
void update()
{
std::lock_guard<std::mutex> guard(mtx);
static bool created = false;
std::deque<struct gen_collider> output;
while (!collider_queue.empty()) {
Ogre::TerrainGroup *group =
collider_queue.front().group;
if (group->isDerivedDataUpdateInProgress())
break;
long x = collider_queue.front().x;
long y = collider_queue.front().y;
// std::cout << x << " " << y << " "
// << collider_queue.size() << std::endl;
Ogre::Terrain *terrain = group->getTerrain(x, y);
Ogre::Vector3 worldPos;
group->convertTerrainSlotToWorldPosition(x, y,
&worldPos);
#if 0
std::cout << "terrain: " << terrain;
if (terrain)
std::cout
<< terrain->getHeightData() << " "
<< terrain->isLoaded() << " "
<< terrain->isDerivedDataUpdateInProgress()
<< std::endl;
#endif
if (terrain && terrain->getHeightData() &&
terrain->isLoaded() &&
!terrain->isDerivedDataUpdateInProgress()) {
@@ -352,8 +370,8 @@ public:
Ogre::StringConverter::toString(x) +
" " +
Ogre::StringConverter::toString(y));
float minH = terrain->getMinHeight();
float maxH = terrain->getMaxHeight();
// float minH = terrain->getMinHeight();
// float maxH = terrain->getMaxHeight();
int size = terrain->getSize();
float worldSize = terrain->getWorldSize();
{
@@ -400,8 +418,8 @@ public:
created = true;
}
#endif
collider_queue.pop_front();
// FIXME: create entities and components instead
#if 0
Ogre::SceneNode *items =
terrain->_getRootSceneNode()
->createChildSceneNode();
@@ -421,17 +439,24 @@ public:
what->setOrientation(item.rotation);
what->setPosition(item.position);
}
} else {
output.push_back(collider_queue.front());
#endif
/* Spawn items */
StaticGeometryModule::addGeometryForSlot(x, y);
collider_queue.pop_front();
}
if (collider_queue.empty() &&
!ECS::get<Terrain>().mTerrainReady) {
ECS::get_mut<Terrain>().mTerrainReady = true;
ECS::modified<Terrain>();
} else {
/* Terrain data not ready maybe move to next terrain */
gen_collider m = collider_queue.front();
collider_queue.pop_front();
collider_queue.push_back(m);
break; // allow system to move on
}
}
collider_queue = output;
if (collider_queue.empty() &&
!ECS::get<Terrain>().mTerrainReady) {
ECS::get_mut<Terrain>().mTerrainReady = true;
ECS::modified<Terrain>();
}
}
};
class DummyPageProvider : public Ogre::PageProvider {
@@ -453,6 +478,10 @@ public:
bool unloadProceduralPage(Ogre::Page *page,
Ogre::PagedWorldSection *section)
{
long x, y;
ECS::get<Terrain>().mTerrainGroup->unpackIndex(page->CHUNK_ID,
&x, &y);
StaticGeometryModule::removeGeometryForSlot(x, y);
return true;
}
bool unprepareProceduralPage(Ogre::Page *page,
@@ -461,20 +490,6 @@ public:
return true;
}
};
struct TerrainPrivate {
DummyPageProvider *mDummyPageProvider;
Ogre::Timer mSunUpdate;
};
struct TerrainSlotParent {
std::pair<long, long> slot;
};
struct TerrainItem {
Ogre::Vector3 position;
Ogre::Quaternion orientation;
Ogre::String properties;
};
TerrainModule::TerrainModule(flecs::world &ecs)
{
struct CanSetPlayerPosition {};
@@ -482,12 +497,11 @@ TerrainModule::TerrainModule(flecs::world &ecs)
ecs.component<CanSetPlayerPosition>().add(flecs::Singleton);
ecs.component<Terrain>().add(flecs::Singleton);
ecs.component<TerrainPrivate>().add(flecs::Singleton);
ecs.component<TerrainSlotParent>();
ecs.component<TerrainItem>();
ecs.component<PlacementObjects>();
ecs.component<TerrainReady>().add(flecs::Singleton);
ecs.import <CharacterModule>();
ecs.import <SunModule>();
ecs.import <StaticGeometryModule>();
ecs.set<TerrainPrivate>({ nullptr, {} });
ecs.system<const EngineData, const Camera, const Sun, Terrain,
TerrainPrivate>("SetupUpdateTerrain")
@@ -508,9 +522,6 @@ TerrainModule::TerrainModule(flecs::world &ecs)
OgreAssert(terrain.mTerrainGlobals,
"Failed to allocate global options");
Ogre::LogManager::getSingleton().setMinLogLevel(
Ogre::LML_TRIVIAL);
terrain.mTerrainGroup =
OGRE_NEW Ogre::TerrainGroup(
eng.mScnMgr,
@@ -523,11 +534,11 @@ TerrainModule::TerrainModule(flecs::world &ecs)
terrain.mTerrainGroup->setOrigin(
terrain.mTerrainPos);
// Configure global
terrain.mTerrainGlobals->setMaxPixelError(0);
terrain.mTerrainGlobals->setMaxPixelError(1);
// testing composite map
// mTerrainGlobals->setCompositeMapDistance(30);
terrain.mTerrainGlobals->setCompositeMapDistance(
500);
300);
//mTerrainGlobals->setUseRayBoxDistanceCalculation(true);
terrain.mTerrainGlobals
->getDefaultMaterialGenerator()
@@ -547,7 +558,8 @@ TerrainModule::TerrainModule(flecs::world &ecs)
defaultimp.terrainSize = TERRAIN_SIZE;
defaultimp.worldSize = TERRAIN_WORLD_SIZE;
defaultimp.inputScale = 1.0f;
defaultimp.minBatchSize = 33;
// defaultimp.minBatchSize = 33;
defaultimp.minBatchSize = 5;
defaultimp.maxBatchSize = 65;
Ogre::Image combined;
combined.loadTwoImagesAsRGBA(
@@ -575,24 +587,33 @@ TerrainModule::TerrainModule(flecs::world &ecs)
terrain.mPageManager);
terrain.mPagedWorld =
terrain.mPageManager->createWorld();
#if 0
terrain.mTerrainGroup->setAutoUpdateLod(
Ogre::TerrainAutoUpdateLodFactory::
getAutoUpdateLod(
Ogre::BY_DISTANCE));
#endif
terrain.mTerrainPagedWorldSection =
terrain.mTerrainPaging
->createWorldSection(
terrain.mPagedWorld,
terrain.mTerrainGroup,
300, 800,
300, 500,
ENDLESS_PAGE_MIN_X,
ENDLESS_PAGE_MIN_Y,
ENDLESS_PAGE_MAX_X,
ENDLESS_PAGE_MAX_Y);
terrain.definer = OGRE_NEW FlatTerrainDefiner(
eng.mScnMgr /*, eng.mWorld */);
terrain.mTerrainPagedWorldSection->setDefiner(
OGRE_NEW FlatTerrainDefiner(
eng.mScnMgr /*, eng.mWorld */));
terrain.definer);
terrain.mTerrainGroup->freeTemporaryResources();
std::cout << "Terrain setup done\n";
ECS::get().set<PlacementObjects>({});
terrain.mTerrainGroup->loadAllTerrains(true);
}
if (sun.mSun &&
priv.mSunUpdate.getMilliseconds() > 1000) {
@@ -607,6 +628,7 @@ TerrainModule::TerrainModule(flecs::world &ecs)
.getPitch()
<< "\n";
priv.mSunUpdate.reset();
//terrain.mTerrainGroup->autoUpdateLodAll()
}
});
ecs.system<const ECS::Camera, const Terrain>("UpdateTerrainStatus")
@@ -710,11 +732,12 @@ TerrainModule::TerrainModule(flecs::world &ecs)
ECS::get().add<CanSetPlayerPosition>();
}
});
#if 0
ecs.system<const Terrain>("SetPlayerPosition")
.kind(flecs::OnUpdate)
.with<CanSetPlayerPosition>()
.each([this](const Terrain &terrain) {
flecs::entity player = ECS::player;
flecs::entity player = ECS::get<CharacterM;
if (!player.is_valid())
return;
if (!player.has<CharacterLocation>())
@@ -738,11 +761,14 @@ TerrainModule::TerrainModule(flecs::world &ecs)
player.modified<CharacterLocation>();
ECS::get().remove<CanSetPlayerPosition>();
});
ecs.observer<const Terrain>("LoadTerrainItems")
.event(flecs::OnSet)
#endif
ecs.system<const Terrain>("UpdateTerrainGroup")
.kind(flecs::OnUpdate)
.interval(2.0f)
.each([](const Terrain &terrain) {
if (terrain.mTerrainGroup)
loadItems();
if (!terrain.mTerrainGroup
->isDerivedDataUpdateInProgress())
terrain.mTerrainGroup->update(false);
});
}
float TerrainModule::get_height(Ogre::TerrainGroup *group,
@@ -783,167 +809,9 @@ void TerrainModule::save_heightmap()
{
HeightData::get_singleton()->save_heightmap();
}
flecs::entity TerrainModule::createItem(const Ogre::Vector3 &position,
const Ogre::Quaternion &orientation,
const Ogre::String &type)
void TerrainModule::defineTerrain(long x, long y)
{
long x, y;
ECS::get<Terrain>().mTerrainGroup->convertWorldPositionToTerrainSlot(
position, &x, &y);
std::pair<long, long> pos{ x, y };
flecs::entity slot =
ECS::get().query_builder<const TerrainSlotParent>().build().find(
[&](const TerrainSlotParent &slot) -> bool {
return slot.slot == pos;
});
if (!slot.is_valid())
slot = ECS::get().entity().add<TerrainSlotParent>();
flecs::entity item = ECS::get().entity().child_of(slot);
nlohmann::json jproperties;
jproperties["type"] = type;
item.set<TerrainItem>({ position, orientation, jproperties.dump() });
return item;
ECS::get<Terrain>().definer->define(ECS::get<Terrain>().mTerrainGroup,
x, y);
}
void TerrainModule::setItemProperties(flecs::entity id, Ogre::String properties)
{
OgreAssert(id.is_valid(), "bad id");
id.get_mut<TerrainItem>().properties = properties;
id.modified<TerrainItem>();
}
const Ogre::String &TerrainModule::getItemProperties(flecs::entity id)
{
OgreAssert(id.is_valid(), "bad id");
return id.get<TerrainItem>().properties;
}
static void to_json(nlohmann::json &j, const Ogre::Vector3 &position)
{
j["x"] = position.x;
j["y"] = position.y;
j["z"] = position.z;
}
static void to_json(nlohmann::json &j, const Ogre::Quaternion &orientation)
{
j["w"] = orientation.w;
j["x"] = orientation.x;
j["y"] = orientation.y;
j["z"] = orientation.z;
}
static void from_json(const nlohmann::json &j, Ogre::Vector3 &position)
{
position.x = j["x"].get<float>();
position.y = j["y"].get<float>();
position.z = j["z"].get<float>();
}
static void from_json(const nlohmann::json &j, Ogre::Quaternion &orientation)
{
orientation.w = j["w"].get<float>();
orientation.x = j["x"].get<float>();
orientation.y = j["y"].get<float>();
orientation.z = j["z"].get<float>();
}
void TerrainModule::saveItems()
{
Ogre::String path = "resources/buildings/items.list";
if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(
"items.list")) {
Ogre::String group =
Ogre::ResourceGroupManager::getSingleton()
.findGroupContainingResource("items.list");
Ogre::FileInfoListPtr fileInfoList(
Ogre::ResourceGroupManager::getSingleton()
.findResourceFileInfo(group, "items.list"));
OgreAssert(fileInfoList->size() == 1,
"worpd_map.png should be there and only once");
path = fileInfoList->at(0).archive->getName() + "/" +
"items.list";
Ogre::FileSystemLayer::removeFile(path);
}
std::fstream fout(path.c_str(), std::ios::out);
nlohmann::json jitemlist;
ECS::get().query_builder<const TerrainItem>().build().each(
[&](flecs::entity e, const TerrainItem &item) {
nlohmann::json jitem;
to_json(jitem["position"], item.position);
to_json(jitem["orientation"], item.orientation);
to_json(jitem["properties"], item.properties);
jitemlist.push_back(jitem);
});
fout << jitemlist.dump();
fout.close();
}
void TerrainModule::loadItems()
{
if (!Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(
"items.list"))
return;
Ogre::String group = Ogre::ResourceGroupManager::getSingleton()
.findGroupContainingResource("items.list");
Ogre::DataStreamPtr stream =
Ogre::ResourceGroupManager::getSingleton().openResource(
"items.list", group);
Ogre::String json = stream->getAsString();
nlohmann::json jlist = nlohmann::json::parse(json);
ECS::get().delete_with<TerrainItem>();
ECS::get().delete_with<TerrainSlotParent>();
for (const auto &v : jlist) {
Ogre::Vector3 position;
Ogre::Quaternion orientation;
Ogre::String properties;
from_json(v["position"], position);
from_json(v["orientation"], orientation);
properties = v["properties"].get<Ogre::String>();
long x, y;
ECS::get<Terrain>()
.mTerrainGroup->convertWorldPositionToTerrainSlot(
position, &x, &y);
std::pair<long, long> pos{ x, y };
flecs::entity slot =
ECS::get()
.query_builder<const TerrainSlotParent>()
.build()
.find([&](const TerrainSlotParent &slot)
-> bool {
return slot.slot == pos;
});
if (!slot.is_valid())
slot = ECS::get().entity().add<TerrainSlotParent>();
flecs::entity item = ECS::get().entity().child_of(slot);
item.set<TerrainItem>({ position, orientation, properties });
std::cout << "position: " << item.id() << " " << position
<< std::endl;
}
}
void TerrainModule::getItemPositionPerSlot(long x, long y,
std::list<Ogre::Vector3> *positions)
{
std::pair<long, long> pos{ x, y };
if (!positions)
return;
flecs::entity slot =
ECS::get().query_builder<const TerrainSlotParent>().build().find(
[&](const TerrainSlotParent &slot) -> bool {
return slot.slot == pos;
});
if (!slot.is_valid())
return;
ECS::get()
.query_builder<const TerrainItem>()
.with(flecs::ChildOf, slot)
.build()
.each([&](flecs::entity e, const TerrainItem &item) {
positions->push_back(item.position);
std::cout << e.id() << " " << item.position
<< std::endl;
});
}
void TerrainModule::getItemPositions(std::list<Ogre::Vector3> *positions)
{
ECS::get().query_builder<const TerrainItem>().build().each(
[&](flecs::entity e, const TerrainItem &item) {
positions->push_back(item.position);
std::cout << e.id() << " " << item.position
<< std::endl;
});
}
}

Some files were not shown because too many files have changed in this diff Show More