Compare commits

...

13 Commits

Author SHA1 Message Date
e0db570581 Started implementing deficated editor GUI 2025-12-04 15:51:56 +03:00
6eed5063e6 Fixes in modules 2025-12-02 14:30:05 +03:00
5b014dcb65 Better boat handling 2025-11-30 18:28:26 +03:00
cd82fb0eed converted to Jolt physics 2025-11-23 02:00:31 +03:00
3f0484e87c Updated lots of things 2025-10-22 16:39:19 +03:00
9c4bea5983 Base character for morph targets 2025-10-08 07:11:39 +03:00
3645557520 Working on morph targets 2025-10-07 23:14:15 +03:00
19a1275a8a Now root motion works much better; raft/boat climbing 2025-10-04 04:10:21 +03:00
25280a9cbe Support proper actuator animation 2025-09-27 01:23:16 +03:00
7e06da700a New models; using raft 2025-09-25 04:11:31 +03:00
9e5d08bfc6 Animation tree implemented 2025-09-22 20:34:12 +03:00
a62d781aa0 Updates 2025-09-21 00:05:28 +03:00
fea7c71788 Updates 2025-09-20 23:14:17 +03:00
111 changed files with 14550 additions and 1930 deletions

63
.vscode/settings.json vendored
View File

@@ -10,6 +10,67 @@
"variant": "cpp",
"tuple": "cpp",
"iostream": "cpp",
"string_view": "cpp"
"string_view": "cpp",
"atomic": "cpp",
"memory_resource": "cpp",
"stop_token": "cpp",
"random": "cpp",
"future": "cpp",
"array": "cpp",
"deque": "cpp",
"list": "cpp",
"string": "cpp",
"unordered_map": "cpp",
"unordered_set": "cpp",
"vector": "cpp",
"initializer_list": "cpp",
"span": "cpp",
"chrono": "cpp",
"format": "cpp",
"cctype": "cpp",
"clocale": "cpp",
"cmath": "cpp",
"cstdarg": "cpp",
"cstddef": "cpp",
"cstdio": "cpp",
"cstdlib": "cpp",
"cstring": "cpp",
"ctime": "cpp",
"cwchar": "cpp",
"cwctype": "cpp",
"bit": "cpp",
"bitset": "cpp",
"charconv": "cpp",
"compare": "cpp",
"concepts": "cpp",
"condition_variable": "cpp",
"cstdint": "cpp",
"map": "cpp",
"set": "cpp",
"exception": "cpp",
"algorithm": "cpp",
"functional": "cpp",
"iterator": "cpp",
"memory": "cpp",
"optional": "cpp",
"ratio": "cpp",
"system_error": "cpp",
"type_traits": "cpp",
"utility": "cpp",
"fstream": "cpp",
"iomanip": "cpp",
"iosfwd": "cpp",
"limits": "cpp",
"mutex": "cpp",
"new": "cpp",
"numbers": "cpp",
"numeric": "cpp",
"ostream": "cpp",
"semaphore": "cpp",
"sstream": "cpp",
"stdexcept": "cpp",
"streambuf": "cpp",
"thread": "cpp",
"typeinfo": "cpp"
}
}

View File

@@ -7,9 +7,6 @@
#include "Ogre.h"
#include "OgreApplicationContext.h"
#include "Bullet/OgreBullet.h"
#include "BulletCollision/CollisionDispatch/btGhostObject.h"
#include "LinearMath/btTransform.h"
#include "OgrePageManager.h"
#define CAM_HEIGHT 1.6f // height of camera above character's center of mass
@@ -21,11 +18,15 @@ using Real = Ogre::Real;
using Math = Ogre::Math;
class WorldData {
#if 0
std::unique_ptr<Ogre::Bullet::DynamicsWorld> mDynWorld;
std::unique_ptr<Ogre::Bullet::DebugDrawer> mDbgDraw;
#endif
std::unique_ptr<Ogre::Root> mRoot;
std::unique_ptr<Ogre::SceneManager> mScnMgr;
#if 0
std::unique_ptr<btDynamicsWorld> mbtWorld;
#endif
std::unique_ptr<Ogre::PageManager> mPageManager;
Ogre::PagedWorld *mPagedWorld;
@@ -61,13 +62,14 @@ private:
DummyPageProvider mDummyPageProvider;
WorldData(Ogre::Root *root, Ogre::SceneManager *scnMgr)
: mDynWorld(new Ogre::Bullet::DynamicsWorld(
: /*mDynWorld(new Ogre::Bullet::DynamicsWorld(
Ogre::Vector3(0, -9.8, 0)))
, mDbgDraw(new Ogre::Bullet::DebugDrawer(
scnMgr->getRootSceneNode(), mDynWorld->getBtWorld()))
, mRoot(root)
, */
mRoot(root)
, mScnMgr(scnMgr)
, mbtWorld(mDynWorld->getBtWorld())
/*, mbtWorld(mDynWorld->getBtWorld()) */
, mPageManager(nullptr)
, mPagedWorld(nullptr)
{
@@ -122,6 +124,7 @@ public:
#endif
return ghost;
}
#if 0
btRigidBody *addRigidBody(float mass, Ogre::Entity *ent,
Ogre::Bullet::ColliderType ct, int group = 1,
int mask = 0xFFFF)
@@ -182,11 +185,14 @@ public:
{
return mDynWorld.get();
}
#endif
void update(float delta)
{
#if 0
WorldData::get_singleton()->getBtWorld()->stepSimulation(delta,
10);
mDbgDraw->update();
#endif
}
void initPagedWorld(Ogre::Camera *camera)
{
@@ -200,7 +206,7 @@ public:
WorldData *WorldData::singleton = nullptr;
class MainWorld : public Ogre::FrameListener {
btRigidBody *mFloorBody;
// btRigidBody *mFloorBody;
public:
void setup()
@@ -219,14 +225,18 @@ public:
WorldData::get_singleton()->getSceneManager();
Ogre::Entity *floor = scnMgr->createEntity("Floor", "floor");
scnMgr->getRootSceneNode()->attachObject(floor);
#if 0
mFloorBody = WorldData::get_singleton()->addRigidBody(
0, floor, Ogre::Bullet::CT_TRIMESH);
#endif
}
#if 0
btRigidBody *addCharacter(Ogre::Entity *ent, float mass)
{
return WorldData::get_singleton()->addKinematicRigidBody(
mass, ent, Ogre::Bullet::CT_COMPOUND);
}
#endif
bool frameStarted(const Ogre::FrameEvent &evt) override;
};
class CharacterController : public OgreBites::InputListener,
@@ -265,8 +275,10 @@ class CharacterController : public OgreBites::InputListener,
Ogre::Vector3 rootMotion;
Ogre::Quaternion rootRotation;
// btRigidBody *mRigidBody;
#if 0
btCompoundShape *mCollisionShape;
btPairCachingGhostObject *mGhostObject;
#endif
public:
CharacterController(Ogre::SceneNode *camNode, Ogre::Camera *cam,
@@ -315,6 +327,7 @@ private:
recoverResult *recover_result,
const std::set<btCollisionObject *> &exclude);
#endif
#if 0
inline btQuaternion convert(const Ogre::Quaternion &q)
{
return btQuaternion(q.x, q.y, q.z, q.w);
@@ -344,6 +357,7 @@ private:
q = convert(from.getRotation());
v = convert(from.getOrigin());
}
#endif
};
CharacterController::CharacterController(Ogre::SceneNode *camNode,
Ogre::Camera *cam,

View File

@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.13.0)
project(world2)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD 17)
set(BLENDER "${CMAKE_SOURCE_DIR}/../../blender-bin/bin/blender" CACHE STRING "Blender path")
set(CREATE_DIRECTORIES
${CMAKE_BINARY_DIR}/assets/blender/shapes/male
@@ -27,11 +27,10 @@ file(GLOB WATER_SRC ${CMAKE_SOURCE_DIR}/water/*.cpp)
# The COMPONENTS part checks that OGRE was built the way we need it
# The CONFIG flag makes sure we get OGRE instead of OGRE-next
find_package(OGRE REQUIRED COMPONENTS Bites Bullet Paging Terrain CONFIG)
find_package(OGRE REQUIRED COMPONENTS Bites Paging Terrain CONFIG)
find_package(ZLIB)
find_package(SDL2)
find_package(assimp REQUIRED CONFIG)
find_package(Bullet)
find_package(OgreProcedural REQUIRED CONFIG)
find_package(pugixml REQUIRED CONFIG)
find_package(flecs REQUIRED CONFIG)
@@ -66,41 +65,25 @@ set_target_properties(fix::pugixml PROPERTIES
add_subdirectory(src/lua)
add_subdirectory(src/characters)
add_subdirectory(src/gamedata)
add_subdirectory(src/miniaudio)
add_subdirectory(src/sound)
add_subdirectory(src/sceneloader)
add_subdirectory(audio/gui)
add_subdirectory(tests)
add_subdirectory(lua-scripts)
add_subdirectory(morph)
add_subdirectory(src/world)
add_subdirectory(src/tests)
add_subdirectory(src/physics)
add_subdirectory(src/editor)
# add the source files as usual
add_executable(0_Bootstrap Bootstrap.cpp)
# this also sets the includes and pulls third party dependencies
target_link_libraries(0_Bootstrap OgreBites OgreBullet OgrePaging ${BULLET_DYNAMICS_LIBRARY} ${BULLET_COLLISION_LIBRARY} ${BULLET_MATH_LIBRARY} ${ASSIMP_LIBRARIES}
-Wl,--as-needed
)
if(OGRE_STATIC)
target_link_options(0_Bootstrap PRIVATE -static-libstdc++ -static-libgcc)
endif()
add_dependencies(0_Bootstrap stage_files import_vrm)
add_executable(Editor Editor.cpp ${WATER_SRC})
target_link_libraries(Editor OgreBites OgreBullet OgrePaging OgreTerrain OgreMeshLodGenerator OgreProcedural::OgreProcedural ${BULLET_DYNAMICS_LIBRARY} ${BULLET_COLLISION_LIBRARY} ${BULLET_MATH_LIBRARY}
-Wl,--as-needed
)
if(OGRE_STATIC)
target_link_options(Editor PRIVATE -static-libstdc++ -static-libgcc)
endif()
add_dependencies(Editor stage_files import_buildings import_water_stuff import_vehicles import_vrm)
add_executable(Game Game.cpp ${WATER_SRC})
target_include_directories(Game PRIVATE src/gamedata)
target_link_libraries(Game OgreBites OgreBullet OgrePaging OgreTerrain OgreMeshLodGenerator
OgreProcedural::OgreProcedural ${BULLET_DYNAMICS_LIBRARY}
${BULLET_COLLISION_LIBRARY} ${BULLET_MATH_LIBRARY}
target_link_libraries(Game OgreBites OgrePaging OgreTerrain OgreMeshLodGenerator
OgreProcedural::OgreProcedural
GameData
sound
sceneloader physics
flecs::flecs_static
-Wl,--as-needed
)
@@ -110,9 +93,8 @@ endif()
add_dependencies(Game stage_files import_buildings import_water_stuff import_vehicles import_vrm audio_data_gui)
add_executable(Procedural Procedural.cpp)
target_link_libraries(Procedural OgreBites OgreBullet OgrePaging OgreTerrain
OgreProcedural::OgreProcedural ${BULLET_DYNAMICS_LIBRARY}
${BULLET_COLLISION_LIBRARY} ${BULLET_MATH_LIBRARY}
target_link_libraries(Procedural OgreBites OgrePaging OgreTerrain
OgreProcedural::OgreProcedural
-Wl,--as-needed
)
if(OGRE_STATIC)
@@ -168,6 +150,25 @@ foreach(VEHICLE_FILE ${VEHICLES_SRC})
endforeach()
add_custom_target(import_vehicles ALL DEPENDS ${VEHICLE_OUTPUT_FILES})
set(CHARACTER_SHAPES_SRC edited-normal-male-base.blend edited-shape-test-male.blend)
set(CHARACTER_SHAPES_OUTPUT_FILES)
foreach(CHARACTER_SHAPE_FILE ${CHARACTER_SHAPES_SRC})
get_filename_component(FILE_NAME ${CHARACTER_SHAPE_FILE} NAME_WE)
set(CHARACTER_SHAPE_OUTPUT_FILE ${CMAKE_BINARY_DIR}/characters/shapes/male/${FILE_NAME}/${FILE_NAME}.scene)
add_custom_command(
OUTPUT ${CHARACTER_SHAPE_OUTPUT_FILE}
COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/assets/blender
COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/characters/shapes/male/${FILE_NAME}
COMMAND ${BLENDER} ${CMAKE_SOURCE_DIR}/assets/blender/${CHARACTER_SHAPE_FILE}
-b -Y -P
${CMAKE_SOURCE_DIR}/assets/blender/scripts/export_characters_ogre.py
-- ${CHARACTER_SHAPE_OUTPUT_FILE}
COMMAND touch ${CHARACTER_SHAPE_OUTPUT_FILE}
DEPENDS ${CMAKE_SOURCE_DIR}/assets/blender/${CHARACTER_SHAPE_FILE})
list(APPEND CHARACTER_SHAPES_OUTPUT_FILES ${CHARACTER_SHAPE_OUTPUT_FILE})
endforeach()
add_custom_target(import_character_shapes ALL DEPENDS ${CHARACTER_SHAPES_OUTPUT_FILES})
set(WATER_STUFF)
add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/water/sea.glb
COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/water
@@ -181,7 +182,7 @@ list(APPEND WATER_STUFF ${CMAKE_BINARY_DIR}/water/sea.glb)
add_custom_target(import_water_stuff ALL DEPENDS ${WATER_STUFF})
add_executable(TerrainTest terrain.cpp)
target_link_libraries(TerrainTest OgreBites OgreBullet OgrePaging OgreTerrain lua ${BULLET_DYNAMICS_LIBRARY} ${BULLET_COLLISION_LIBRARY} ${BULLET_MATH_LIBRARY}
target_link_libraries(TerrainTest OgreBites OgrePaging OgreTerrain lua
-Wl,--as-needed
)
target_include_directories(TerrainTest PRIVATE . src/terrain src/lua src/lua/lua-5.4.8/src)
@@ -189,8 +190,6 @@ if(OGRE_STATIC)
target_link_libraries(TerrainTest fix::assimp pugixml)
target_link_options(TerrainTest PRIVATE -static-libstdc++ -static-libgcc)
target_link_libraries(Procedural fix::assimp pugixml)
target_link_libraries(0_Bootstrap fix::assimp pugixml)
target_link_libraries(Editor fix::assimp pugixml)
endif()
add_dependencies(TerrainTest stage_lua_scripts stage_files)
@@ -330,6 +329,23 @@ list(APPEND EDITED_BLEND_TARGETS ${CMAKE_BINARY_DIR}/assets/blender/edited-norma
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(
@@ -366,6 +382,8 @@ add_custom_target(stage_files ALL DEPENDS ${CMAKE_BINARY_DIR}/resources.cfg ${MA
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)
install(TARGETS Game DESTINATION bin)
install(TARGETS Editor DESTINATION bin)

View File

@@ -1,7 +1,6 @@
#include <iostream>
#include <Ogre.h>
#include <OgreBullet.h>
#include <OgreApplicationContext.h>
#include <OgreOverlaySystem.h>
#include <OgreOverlayManager.h>
@@ -245,8 +244,6 @@ public:
void initGui();
};
class App : public OgreBites::ApplicationContext {
std::unique_ptr<Ogre::Bullet::DynamicsWorld> mDynWorld;
std::unique_ptr<Ogre::Bullet::DebugDrawer> mDbgDraw;
Ogre::SceneNode *mCameraNode, *mCameraPivot, *mCameraGoal;
Ogre::Camera *mCamera;
Ogre::Real mPivotPitch;
@@ -376,8 +373,6 @@ public:
: OgreBites::ApplicationContext("ChoroEditor")
, mKbd(this)
, m_edit_ui(this)
, mDynWorld(new Ogre::Bullet::DynamicsWorld(
Ogre::Vector3(0, -9.8, 0)))
{
}
virtual ~App()
@@ -393,8 +388,6 @@ public:
mScnMgr->addRenderQueueListener(pOverlaySystem);
// mTrayMgr = new OgreBites::TrayManager("AppTrays",
// getRenderWindow());
mDbgDraw.reset(new Ogre::Bullet::DebugDrawer(
mScnMgr->getRootSceneNode(), mDynWorld->getBtWorld()));
}
void locateResources() override
{
@@ -455,8 +448,10 @@ public:
std::cout << "Init camera" << "\n";
initCamera();
std::cout << "Set up water" << "\n";
#if 0
m_water.createWater(getRenderWindow(), mCamera,
mDynWorld.get());
#endif
std::cout << "Set up cursor" << "\n";
Ogre::ResourceGroupManager::getSingleton()
.initialiseAllResourceGroups();

View File

@@ -16,6 +16,7 @@
#include "CharacterModule.h"
#include "TerrainModule.h"
#include "GUIModule.h"
#include "AppModule.h"
#include "sound.h"
class App;
class SkyRenderer : public Ogre::SceneManager::Listener {
@@ -228,8 +229,12 @@ public:
control |= 8;
else if (key == OgreBites::SDLK_LSHIFT)
control |= 16;
else if (key == 'e')
control |= 32;
else if (key == 'f')
control |= 64;
if (key == 'w' || key == 'a' || key == 's' || key == 'd' ||
key == OgreBites::SDLK_LSHIFT)
key == 'e' || key == OgreBites::SDLK_LSHIFT)
return true;
return false;
}
@@ -285,8 +290,6 @@ public:
void frameRendered(const Ogre::FrameEvent &evt) override;
};
class App : public OgreBites::ApplicationContext {
std::unique_ptr<Ogre::Bullet::DynamicsWorld> mDynWorld;
std::unique_ptr<Ogre::Bullet::DebugDrawer> mDbgDraw;
Ogre::SceneNode *mCameraNode, *mCameraPivot, *mCameraGoal;
Ogre::Camera *mCamera;
Ogre::Real mPivotPitch;
@@ -300,8 +303,6 @@ public:
App()
: OgreBites::ApplicationContext("ChoroGame")
, mKbd(this)
, mDynWorld(new Ogre::Bullet::DynamicsWorld(
Ogre::Vector3(0, -9.8, 0)))
, mGrab(false)
{
}
@@ -318,8 +319,6 @@ public:
mScnMgr->addRenderQueueListener(pOverlaySystem);
// mTrayMgr = new OgreBites::TrayManager("AppTrays",
// getRenderWindow());
mDbgDraw.reset(new Ogre::Bullet::DebugDrawer(
mScnMgr->getRootSceneNode(), mDynWorld->getBtWorld()));
}
bool isWindowGrab()
{
@@ -428,7 +427,9 @@ public:
}
void updateWorld(float delta)
{
#if 0
mDynWorld->getBtWorld()->stepSimulation(delta, 3);
#endif
if (!ECS::get().has<ECS::GUI>())
return;
/* Update window grab */
@@ -440,8 +441,10 @@ public:
}
ECS::update(delta);
#if 0
if (ECS::get<ECS::EngineData>().enableDbgDraw)
mDbgDraw->update();
#endif
}
class InputListenerChainFlexible : public OgreBites::InputListener {
protected:
@@ -594,7 +597,7 @@ public:
"Skybox/Dynamic", "General");
OgreAssert(m, "Sky box material not found.");
m->load();
ECS::setup(mScnMgr, mDynWorld.get(), mCameraNode, mCamera,
ECS::setup(mScnMgr, /*mDynWorld.get(), */ mCameraNode, mCamera,
getRenderWindow());
ECS::get().set<ECS::RenderWindow>(
{ getRenderWindow(), getDisplayDPI() });

View File

@@ -7,9 +7,6 @@
#include "Ogre.h"
#include "OgreApplicationContext.h"
#include "Bullet/OgreBullet.h"
#include "BulletCollision/CollisionDispatch/btGhostObject.h"
#include "LinearMath/btTransform.h"
#include "OgrePageManager.h"
#include "Procedural.h"
@@ -22,11 +19,8 @@ using Real = Ogre::Real;
using Math = Ogre::Math;
class WorldData {
std::unique_ptr<Ogre::Bullet::DynamicsWorld> mDynWorld;
std::unique_ptr<Ogre::Bullet::DebugDrawer> mDbgDraw;
std::unique_ptr<Ogre::Root> mRoot;
std::unique_ptr<Ogre::SceneManager> mScnMgr;
std::unique_ptr<btDynamicsWorld> mbtWorld;
std::unique_ptr<Ogre::PageManager> mPageManager;
Ogre::PagedWorld *mPagedWorld;
@@ -62,13 +56,8 @@ private:
DummyPageProvider mDummyPageProvider;
WorldData(Ogre::Root *root, Ogre::SceneManager *scnMgr)
: mDynWorld(new Ogre::Bullet::DynamicsWorld(
Ogre::Vector3(0, -9.8, 0)))
, mDbgDraw(new Ogre::Bullet::DebugDrawer(
scnMgr->getRootSceneNode(), mDynWorld->getBtWorld()))
, mRoot(root)
: mRoot(root)
, mScnMgr(scnMgr)
, mbtWorld(mDynWorld->getBtWorld())
, mPageManager(nullptr)
, mPagedWorld(nullptr)
{
@@ -100,94 +89,8 @@ public:
void createTrimesh(Ogre::Entity *entity)
{
}
btPairCachingGhostObject *addGhostObject(Ogre::Entity *ent,
btCollisionShape *shape,
int group = 1,
int mask = 0xFFFF)
{
btDynamicsWorld *world = mDynWorld->getBtWorld();
Ogre::SceneNode *node = ent->getParentSceneNode();
btPairCachingGhostObject *ghost =
new btPairCachingGhostObject();
ghost->setCollisionShape(shape);
ghost->setCollisionFlags(
ghost->getCollisionFlags() |
btCollisionObject::CF_NO_CONTACT_RESPONSE |
btCollisionObject::CF_CHARACTER_OBJECT);
getWorld()->attachCollisionObject(ghost, ent, group, mask);
#if 0
getBtWorld()
->getBroadphase()->getOverlappingPairCache()
->setInternalGhostPairCallback(new btGhostPairCallback());
ghost->setUserPointer(new EntityCollisionListener{ent, nullptr});
#endif
return ghost;
}
btRigidBody *addRigidBody(float mass, Ogre::Entity *ent,
Ogre::Bullet::ColliderType ct, int group = 1,
int mask = 0xFFFF)
{
btDynamicsWorld *world = mDynWorld->getBtWorld();
Ogre::SceneNode *node = ent->getParentSceneNode();
Ogre::Bullet::RigidBodyState *state =
new Ogre::Bullet::RigidBodyState(node);
btCollisionShape *cs;
btCollisionShape *shape;
btVector3 inertia(0, 0, 0);
switch (ct) {
case Ogre::Bullet::CT_TRIMESH: {
cs = Ogre::Bullet::createTrimeshCollider(ent);
if (mass != 0)
cs->calculateLocalInertia(mass, inertia);
} break;
case Ogre::Bullet::CT_CAPSULE: {
cs = new btCompoundShape(false);
btScalar height = 1.0f;
btScalar radius = 0.3f;
shape = new btCapsuleShape(radius,
2 * height - 2 * radius);
btTransform transform;
transform.setIdentity();
transform.setOrigin(btVector3(0, 1, 0));
static_cast<btCompoundShape *>(cs)->addChildShape(
transform, shape);
btScalar masses[1] = { mass };
btTransform principal;
static_cast<btCompoundShape *>(cs)
->calculatePrincipalAxisTransform(
masses, principal, inertia);
} break;
default:
assert(false);
break;
}
btRigidBody *body = new btRigidBody(mass, state, cs, inertia);
getWorld()->attachRigidBody(body, ent, nullptr, group, mask);
#if 0
body->setUserPointer(new EntityCollisionListener{ent, nullptr});
// btRigidBody *body = mDynWorld->addRigidBody(0, ent, Ogre::Bullet::CT_TRIMESH);
#endif
return body;
}
btRigidBody *addKinematicRigidBody(float mass, Ogre::Entity *ent,
Ogre::Bullet::ColliderType ct,
int group = 1, int mask = 0xFFFF)
{
return mDynWorld->addKinematicRigidBody(ent, ct, group, mask);
}
btDynamicsWorld *getBtWorld()
{
return mDynWorld->getBtWorld();
}
Ogre::Bullet::DynamicsWorld *getWorld()
{
return mDynWorld.get();
}
void update(float delta)
{
WorldData::get_singleton()->getBtWorld()->stepSimulation(delta,
10);
mDbgDraw->update();
}
void initPagedWorld(Ogre::Camera *camera)
{
@@ -201,8 +104,6 @@ public:
WorldData *WorldData::singleton = nullptr;
class MainWorld : public Ogre::FrameListener {
btRigidBody *mFloorBody;
public:
void setup()
{
@@ -220,13 +121,6 @@ public:
WorldData::get_singleton()->getSceneManager();
Ogre::Entity *floor = scnMgr->createEntity("Floor", "floor");
scnMgr->getRootSceneNode()->attachObject(floor);
mFloorBody = WorldData::get_singleton()->addRigidBody(
0, floor, Ogre::Bullet::CT_TRIMESH);
}
btRigidBody *addCharacter(Ogre::Entity *ent, float mass)
{
return WorldData::get_singleton()->addKinematicRigidBody(
mass, ent, Ogre::Bullet::CT_COMPOUND);
}
bool frameStarted(const Ogre::FrameEvent &evt) override;
};
@@ -266,8 +160,6 @@ class CharacterController : public OgreBites::InputListener,
Ogre::Vector3 rootMotion;
Ogre::Quaternion rootRotation;
// btRigidBody *mRigidBody;
btCompoundShape *mCollisionShape;
btPairCachingGhostObject *mGhostObject;
public:
CharacterController(Ogre::SceneNode *camNode, Ogre::Camera *cam,
@@ -316,35 +208,6 @@ private:
recoverResult *recover_result,
const std::set<btCollisionObject *> &exclude);
#endif
inline btQuaternion convert(const Ogre::Quaternion &q)
{
return btQuaternion(q.x, q.y, q.z, q.w);
}
inline btVector3 convert(const Ogre::Vector3 &v)
{
return btVector3(v.x, v.y, v.z);
}
inline btTransform convert(const Ogre::Quaternion &q,
const Ogre::Vector3 &v)
{
btQuaternion mq = convert(q);
btVector3 mv = convert(v);
return btTransform(mq, mv);
}
inline Ogre::Quaternion convert(const btQuaternion &q)
{
return Ogre::Quaternion(q.w(), q.x(), q.y(), q.z());
}
inline Ogre::Vector3 convert(const btVector3 &v)
{
return Ogre::Vector3(v.x(), v.y(), v.z());
}
inline void convert(const btTransform &from, Ogre::Quaternion &q,
Ogre::Vector3 &v)
{
q = convert(from.getRotation());
v = convert(from.getOrigin());
}
};
CharacterController::CharacterController(Ogre::SceneNode *camNode,
Ogre::Camera *cam,
@@ -358,8 +221,6 @@ CharacterController::CharacterController(Ogre::SceneNode *camNode,
, mAnimID(ANIM_NONE)
, mRunning(false)
, world(world)
, mCollisionShape(nullptr)
, mGhostObject(nullptr)
{
setupBody();
setupCamera();
@@ -374,72 +235,6 @@ void CharacterController::setupBody()
mBodyNode = mScnMgr->getRootSceneNode()->createChildSceneNode();
mBodyNode->attachObject(mBodyEnt);
mSkeleton = mBodyEnt->getSkeleton();
// mRigidBody = world->addCharacter(mBodyEnt, 0);
// mCollisionShape = static_cast<btCompoundShape *>(mRigidBody->getCollisionShape());
mGhostObject = new btPairCachingGhostObject();
mCollisionShape = new btCompoundShape(false);
mGhostObject->setCollisionShape(mCollisionShape);
{
btVector3 inertia(0, 0, 0);
// mCollisionShape = new btCompoundShape();
btScalar height = 1.0f;
btScalar radius = 0.3f;
btCapsuleShape *shape =
new btCapsuleShape(radius, 2 * height - 2 * radius);
btTransform transform;
transform.setIdentity();
transform.setOrigin(btVector3(0, 1, 0));
static_cast<btCompoundShape *>(mCollisionShape)
->addChildShape(transform, shape);
btScalar masses[1] = { 0 };
btTransform principal;
static_cast<btCompoundShape *>(mCollisionShape)
->calculatePrincipalAxisTransform(masses, principal,
inertia);
}
mGhostObject->setCollisionFlags(
btCollisionObject::CF_KINEMATIC_OBJECT |
btCollisionObject::CF_NO_CONTACT_RESPONSE);
mGhostObject->setActivationState(DISABLE_DEACTIVATION);
Ogre::Bullet::KinematicMotionSimple *controller =
new Ogre::Bullet::KinematicMotionSimple(mGhostObject,
mBodyNode);
WorldData::get_singleton()->getWorld()->attachCollisionObject(
mGhostObject, mBodyEnt, btBroadphaseProxy::AllFilter,
btBroadphaseProxy::AllFilter);
WorldData::get_singleton()->getBtWorld()->addAction(controller);
assert(mCollisionShape);
#if 0
if (mRigidBody->getMass() == 0) {
#if 0
mRigidBody->setCollisionFlags(mRigidBody->getCollisionFlags()
| btCollisionObject::CF_KINEMATIC_OBJECT
| btCollisionObject::CF_NO_CONTACT_RESPONSE
);
#endif
#if 0
mGhostObject->setWorldTransform(mRigidBody->getWorldTransform());
WorldData::get_singleton()->getBtWorld()
->getBroadphase()->getOverlappingPairCache()
->setInternalGhostPairCallback(new btGhostPairCallback());
#endif
}
#endif
#if 0
mRigidBody->setActivationState(DISABLE_DEACTIVATION);
#endif
#if 0
{
Ogre::Entity *e2 = mScnMgr->createEntity("normal-male.glb");
Ogre::SceneNode *e2node = mScnMgr->getRootSceneNode()->createChildSceneNode();
e2node->attachObject(e2);
mGhostObject = WorldData::get_singleton()->addGhostObject(e2, mCollisionShape);
mController = new btKinematicCharacterController(mGhostObject, mCollisionShape, 0.5f);
WorldData::get_singleton()->getBtWorld()->addAction(mController);
}
#endif
assert(mSkeleton->hasBone("Root"));
mRootBone = mSkeleton->getBone("Root");
assert(mRootBone);
@@ -740,8 +535,6 @@ void CharacterController::updateRootMotion(Real delta)
Ogre::Vector3 velocity = rot * boneMotion / delta;
velocity += gravity * delta;
Ogre::Vector3 rotMotion = velocity * delta;
btTransform from(convert(mBodyNode->getOrientation()),
convert(mBodyNode->getPosition()));
mBodyNode->setPosition(mBodyNode->getPosition() + rotMotion);
// WorldData::get_singleton()->getWorld()->testBodyMotion(mRigidBody, from, Ogre::Bullet::convert(rotMotion), true,
// nullptr, false, std::set<btCollisionObject *>());

BIN
assets/blender/edited-normal-female.blend (Stored with Git LFS)

Binary file not shown.

BIN
assets/blender/edited-normal-male-base.blend (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/blender/edited-normal-male.blend (Stored with Git LFS)

Binary file not shown.

BIN
assets/blender/edited-shape-test-male.blend (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/blender/mixamo/female/hanging-climb.fbx (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/blender/mixamo/female/hanging-idle.fbx (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/blender/mixamo/female/sitting.fbx (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/blender/mixamo/male/hanging-climb.fbx (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/blender/mixamo/male/hanging-idle.fbx (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/blender/mixamo/male/sitting.fbx (Stored with Git LFS) Normal file

Binary file not shown.

View File

@@ -92,8 +92,8 @@ class OgreCollisionOp(bpy.types.Operator):
def get_subcollisions( self, ob, create=True ):
r = get_subcollisions( ob )
if not r and create:
method = getattr(self, 'create_%s'%ob.collision_mode)
p = method(ob)
# method = getattr(self, 'create_%s'%ob.collision_mode)
# p = method(ob)
p.name = '%s.%s' %(ob.collision_mode, ob.name)
p.subcollision = True
r.append( p )

View File

@@ -52,7 +52,7 @@ def dot_scene(path, scene_name=None):
linkedgroups = []
invalidnamewarnings = []
for ob in bpy.context.scene.objects:
if ob.subcollision:
if ob.subcollision or "collision_type" in ob:
continue
if ((config.get("EXPORT_HIDDEN") is False) and (ob not in bpy.context.visible_objects)):
continue
@@ -184,6 +184,7 @@ def dot_scene(path, scene_name=None):
mesh_collision_prims = {}
mesh_collision_files = {}
compound_collision_shapes = {}
# Export the objects in the scene
for root in roots:
@@ -193,6 +194,7 @@ def dot_scene(path, scene_name=None):
meshes = meshes,
mesh_collision_prims = mesh_collision_prims,
mesh_collision_files = mesh_collision_files,
compound_collision_shapes = compound_collision_shapes,
exported_armatures = exported_armatures,
prefix = prefix,
objects = objects,
@@ -655,14 +657,16 @@ def dot_scene_skybox_export( path ):
# Recursive Node export
def dot_scene_node_export( ob, path, doc=None, rex=None,
exported_meshes=[], meshes=[], mesh_collision_prims={}, mesh_collision_files={},
exported_meshes=[], meshes=[], mesh_collision_prims={}, mesh_collision_files={}, compound_collision_shapes={},
exported_armatures=[], prefix='', objects=[], xmlparent=None ):
print("Nodes... " + ob.name)
o = _ogre_node_helper( doc, ob )
xmlparent.appendChild(o)
# if config.get('EXPORT_USER') is True:
# Custom user props
user = None
if len(ob.items()) > 0:
user = doc.createElement('userData')
o.appendChild(user)
@@ -672,46 +676,116 @@ def dot_scene_node_export( ob, path, doc=None, rex=None,
if not propname.startswith('_'):
_property_helper(doc, user, propname, propvalue)
if ob.type == 'MESH' or ob.type == 'EMPTY':
if not user:
user = doc.createElement('userData')
o.appendChild(user)
if ob.rigid_body:
_property_helper(doc, user, "blenderCollisionType", ob.rigid_body.type)
_property_helper(doc, user, "blenderCollisionShape", ob.rigid_body.collision_shape)
_property_helper(doc, user, "blenderCollisionEnabled", ob.rigid_body.enabled)
_property_helper(doc, user, "blenderCollisionKinematic", ob.rigid_body.kinematic)
_property_helper(doc, user, "blenderCollisionMass", ob.rigid_body.mass)
if ob.rigid_body.collision_shape == "CONVEX_HULL":
_property_helper(doc, user, "collisionType", "convexHull")
elif ob.rigid_body.collision_shape == "MESH":
_property_helper(doc, user, "collisionType", "mesh")
elif ob.rigid_body.collision_shape == "COMPOUND":
_property_helper(doc, user, "collisionType", "compound")
_property_helper(doc, user, "collisionBodyMass", ob.rigid_body.mass)
if ob.type == 'MESH':
if (not ob.parent) or (ob.parent and not ob.parent.rigid_body) or (ob.parent and ob.parent.rigid_body and ob.parent.rigid_body.collision_shape != "COMPOUND"):
# no compound parent
if ob.rigid_body.collision_shape in ["CONVEX_HULL", "MESH"]:
mesh.dot_mesh(ob, path, force_name='%s_collision_%s' % (prefix, ob.data.name) )
skeleton.dot_skeleton(ob, path)
_property_helper(doc, user, "collisionFile", '%s_collision_%s.mesh' % (prefix, ob.data.name))
elif ob.parent and ob.parent.rigid_body.collision_shape == "COMPOUND":
# has compound parent
mesh.dot_mesh(ob, path, force_name='%s_collision_%s_%s' % (prefix, ob.parent.data.name, ob.data.name) )
skeleton.dot_skeleton(ob, path)
_property_helper(doc, user, "collisionFile", '%s_collision_%s_%s.mesh' % (prefix, ob.parent.data.name, ob.data.name))
if (not ob.parent) or (ob.parent and not ob.parent.rigid_body) or (ob.parent and ob.parent.rigid_body and ob.parent.rigid_body.collision_shape != "COMPOUND"):
if ob.rigid_body.type == 'ACTIVE':
if not ob.rigid_body.kinematic:
_property_helper(doc, user, "collisionBodyType", "dynamic")
else:
_property_helper(doc, user, "collisionBodyType", "ghost")
else:
if not ob.rigid_body.kinematic:
_property_helper(doc, user, "collisionBodyType", "static")
else:
_property_helper(doc, user, "collisionBodyType", "kinematic")
if ob.type == 'MESH':
# ob.data.tessfaces is empty. always until the following call
ob.data.update()
ob.data.calc_loop_triangles()
# if it has no faces at all, the object itself will not be exported, BUT
# it might have children
if ob.type == 'MESH' and len(ob.data.loop_triangles):
print("Loopie..." + ob.data.name + " ", ob.data.loop_triangles)
compoundParent = False
hasPhysics = False
if ob.rigid_body:
hasPhysics = True
if ob.parent:
if ob.parent.rigid_body:
if ob.parent.rigid_body.collision_shape == "COMPOUND":
compoundParent = True
createEntity = not (compoundParent and hasPhysics)
if ob.type == 'MESH' and len(ob.data.loop_triangles) and createEntity:
collisionFile = None
collisionPrim = None
compoundColliders = None
if ob.data.name in mesh_collision_prims:
collisionPrim = mesh_collision_prims[ ob.data.name ]
if ob.data.name in mesh_collision_files:
collisionFile = mesh_collision_files[ ob.data.name ]
e = doc.createElement('entity')
o.appendChild(e); e.setAttribute('name', ob.name)
prefix = ''
e.setAttribute('meshFile', '%s%s.mesh' % (prefix, clean_object_name(ob.data.name)) )
# Set the instancing attribute if the object belongs to the correct group
_mesh_instance_helper(e, ob, "static")
_mesh_instance_helper(e, ob, "instanced")
if ob.data.name in compound_collision_shapes:
compoundColliders = compound_collision_shapes[ ob.data.name ]
print("Meshie..." + ob.data.name)
if True:
e = doc.createElement('entity')
o.appendChild(e); e.setAttribute('name', ob.name)
prefix = ''
e.setAttribute('meshFile', '%s%s.mesh' % (prefix, clean_object_name(ob.data.name)) )
# Set the instancing attribute if the object belongs to the correct group
_mesh_instance_helper(e, ob, "static")
_mesh_instance_helper(e, ob, "instanced")
if not collisionPrim and not collisionFile:
print("Collisions..." + ob.data.name)
for child in ob.children:
if child.type == 'MESH':
print("\tCollisions... " + child.data.name)
#if child.rigid_body:
# print("physics body")
# if child.rigid_body.collision_shape == 'CONVEX_HULL':
# collisionFile = '%s_collision_%s.mesh' % (prefix, ob.data.name)
# elif child.rigid_body.collision_shape == 'MESH':
# collisionFile = '%s_collision_%s.mesh' % (prefix, ob.data.name)
# break
if child.subcollision and child.name.startswith('DECIMATE'):
collisionFile = '%s_collision_%s.mesh' % (prefix, ob.data.name)
break
elif "collision_type" in child and child.name.startswith('DECIMATE'):
collisionFile = '%s_collision_%s.mesh' % (prefix, ob.data.name)
break
elif "collision_type" in child:
collisionFile = '%s_collision_%s.mesh' % (prefix, ob.data.name)
break
if collisionFile:
print("CollisionFile:")
mesh_collision_files[ ob.data.name ] = collisionFile
mesh.dot_mesh(child, path, force_name='%s_collision_%s' % (prefix, ob.data.name) )
skeleton.dot_skeleton(child, path)
if not collisionPrim and not collisionFile:
for child in ob.children:
if child.subcollision and child.name.startswith('DECIMATE'):
collisionFile = '%s_collision_%s.mesh' % (prefix, ob.data.name)
break
if child.name.endswith("-collision")
collisionFile = '%s_collision_%s.mesh' % (prefix, ob.data.name)
if collisionFile:
mesh_collision_files[ ob.data.name ] = collisionFile
mesh.dot_mesh(child, path, force_name='%s_collision_%s' % (prefix, ob.data.name) )
skeleton.dot_skeleton(child, path)
if collisionPrim:
e.setAttribute('collisionPrim', collisionPrim )
elif collisionFile:
e.setAttribute('collisionFile', collisionFile )
if collisionPrim:
e.setAttribute('collisionPrim', collisionPrim )
elif collisionFile:
e.setAttribute('collisionFile', collisionFile )
#if config.get('EXPORT_USER') is True:
_mesh_entity_helper( doc, ob, e )

View File

@@ -769,14 +769,21 @@ def get_subcollision_meshes():
''' returns all collision meshes found in the scene '''
r = []
for ob in bpy.context.scene.objects:
if ob.type=='MESH' and ob.subcollision: r.append( ob )
if ob.type=='MESH' and (ob.subcollision or "collision_type" in ob):
r.append( ob )
return r
def get_objects_with_subcollision():
''' returns objects that have active sub-collisions '''
r = []
for ob in bpy.context.scene.objects:
if ob.type=='MESH' and ob.collision_mode not in ('NONE', 'PRIMITIVE'):
if ob.type != 'MESH':
continue
if not ob.rigid_body:
continue
if ob.rigid_body.collision_shape in ('CONVEX_HULL', 'MESH'):
r.append( ob )
elif ob.type=='MESH' and not ob.collision_mode in ('NONE', 'PRIMITIVE'):
r.append( ob )
return r
@@ -784,7 +791,7 @@ def get_subcollisions(ob):
prefix = '%s.' %ob.collision_mode
r = []
for child in ob.children:
if child.subcollision and child.name.startswith( prefix ):
if (child.subcollision or "collision_type" in ob) and child.name.startswith( prefix ):
r.append( child )
return r

View File

@@ -11,13 +11,18 @@ from math import radians, pi
argv = sys.argv
argv = argv[argv.index("--") + 1:]
sys.path.insert(0, os.getcwd() + "/assets/blender/scripts")
sys.path.insert(1, os.getcwd() + "/assets/blender/scripts/blender2ogre")
incpath = os.path.dirname(__file__)
sys.path.insert(0, incpath)
sys.path.insert(1, incpath + "/blender2ogre")
import io_ogre
io_ogre.register()
gltf_file = argv[0]
print("Exporting to " + gltf_file)
basepath = os.getcwd()
basepath = incpath
# bpy.ops.export_scene.gltf(filepath="", check_existing=True,
# export_import_convert_lighting_mode='SPEC', gltf_export_id="",
# export_format='GLB', ui_tab='GENERAL', export_copyright="", export_image_format='AUTO',
@@ -47,6 +52,7 @@ for obj in bpy.data.objects:
bpy.data.objects.remove(obj)
scene_file = gltf_file.replace(".glb", "").replace(".gltf", "") + ".scene"
bpy.ops.ogre.export(filepath=scene_file,
EX_SWAP_AXIS='xz-y',
EX_V2_MESH_TOOL_VERSION='v2',
@@ -58,7 +64,7 @@ bpy.ops.ogre.export(filepath=scene_file,
EX_FORCE_LIGHTS=False,
EX_NODE_ANIMATION=True,
EX_MATERIALS=True,
EX_SEPARATE_MATERIALS=False,
EX_SEPARATE_MATERIALS=True,
EX_COPY_SHADER_PROGRAMS=True,
EX_MESH=True,
EX_LOD_LEVELS=3,

View File

@@ -0,0 +1,88 @@
#!/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:]
sys.path.insert(0, os.getcwd() + "/assets/blender/scripts")
sys.path.insert(1, os.getcwd() + "/assets/blender/scripts/blender2ogre")
import io_ogre
output_file = argv[0]
print("Exporting to " + output_file)
basepath = os.getcwd()
# bpy.ops.export_scene.gltf(filepath="", check_existing=True,
# export_import_convert_lighting_mode='SPEC', gltf_export_id="",
# export_format='GLB', ui_tab='GENERAL', export_copyright="", export_image_format='AUTO',
# export_texture_dir="", export_jpeg_quality=75, export_keep_originals=False,
# export_texcoords=True, export_normals=True, export_draco_mesh_compression_enable=False,
# export_draco_mesh_compression_level=6, export_draco_position_quantization=14,
# export_draco_normal_quantization=10, export_draco_texcoord_quantization=12,
# export_draco_color_quantization=10, export_draco_generic_quantization=12, export_tangents=False,
# export_materials='EXPORT', export_original_specular=False, export_colors=True,
# export_attributes=False, use_mesh_edges=False, use_mesh_vertices=False, export_cameras=False,
# use_selection=False, use_visible=False, use_renderable=False,
# use_active_collection_with_nested=True, use_active_collection=False, use_active_scene=False,
# export_extras=False, export_yup=True, export_apply=False, export_animations=True,
# export_frame_range=False, export_frame_step=1, export_force_sampling=True, export_animation_mode='ACTIONS',
# export_nla_strips_merged_animation_name="Animation", export_def_bones=False,
# export_hierarchy_flatten_bones=False, export_optimize_animation_size=True,
# export_optimize_animation_keep_anim_armature=True, export_optimize_animation_keep_anim_object=False,
# export_negative_frame='SLIDE', export_anim_slide_to_zero=False, export_bake_animation=False,
# export_anim_single_armature=True, export_reset_pose_bones=True, export_current_frame=False,
# export_rest_position_armature=True, export_anim_scene_split_object=True, export_skins=True,
# export_all_influences=False, export_morph=True, export_morph_normal=True,
# export_morph_tangent=False, export_morph_animation=True, export_morph_reset_sk_data=True,
# export_lights=False, export_nla_strips=True, will_save_settings=False, filter_glob="*.glb")
for obj in bpy.data.objects:
if obj.name.endswith("-col"):
bpy.data.objects.remove(obj)
scene_file = output_file
#bpy.ops.ogre.export(filepath=scene_file,
# EX_SWAP_AXIS='xz-y',
# EX_V2_MESH_TOOL_VERSION='v2',
# EX_EXPORT_XML_DELETE=True,
# EX_SCENE=True,
# EX_SELECTED_ONLY=False,
# EX_EXPORT_HIDDEN=False,
# EX_FORCE_CAMERA=False,
# EX_FORCE_LIGHTS=False,
# EX_NODE_ANIMATION=True,
# EX_MATERIALS=True,
# EX_SEPARATE_MATERIALS=True,
# EX_COPY_SHADER_PROGRAMS=True,
# EX_MESH=True,
# EX_LOD_LEVELS=3,
# EX_LOD_DISTANCE=100,
# EX_LOD_PERCENT=40
#)
#
bpy.ops.ogre.export(filepath=scene_file,
EX_SWAP_AXIS='xz-y',
EX_V2_MESH_TOOL_VERSION='v2',
EX_EXPORT_XML_DELETE=True,
EX_SCENE=True,
EX_SELECTED_ONLY=False,
EX_EXPORT_HIDDEN=False,
EX_FORCE_CAMERA=False,
EX_FORCE_LIGHTS=False,
EX_NODE_ANIMATION=True,
EX_MATERIALS=True,
EX_SEPARATE_MATERIALS=True,
EX_COPY_SHADER_PROGRAMS=True,
EX_MESH=True
)
bpy.ops.wm.read_homefile(use_empty=True)
time.sleep(2)
bpy.ops.wm.quit_blender()

View File

@@ -9,7 +9,7 @@ from mathutils import Vector, Matrix
from math import radians, pi
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from settings import ExportMappingFemale, ExportMappingMale, ExportMappingMaleBabyShape, ExportMappingMaleEdited, ExportMappingFemaleEdited
from settings import ExportMappingFemale, ExportMappingMale, ExportMappingMaleBabyShape, ExportMappingMaleEdited, ExportMappingFemaleEdited, ExportMappingMaleTestShapeEdited, ExportMappingMaleBaseShapeEdited
basepath = os.getcwd()
def check_bone(bname):
@@ -179,7 +179,7 @@ def extra_linear(angle, offset):
ret += offt
return ret
for mapping in [ExportMappingFemale(), ExportMappingMale(), ExportMappingMaleBabyShape(), ExportMappingMaleEdited(), ExportMappingFemaleEdited()]:
for mapping in [ExportMappingFemale(), ExportMappingMale(), ExportMappingMaleBabyShape(), ExportMappingMaleEdited(), ExportMappingFemaleEdited(), ExportMappingMaleTestShapeEdited(), ExportMappingMaleBaseShapeEdited()]:
if not os.path.exists(mapping.blend_path):
print("Skipping mapping: " + mapping.blend_path)
continue

View File

@@ -6,7 +6,7 @@ class VRMDataFemale:
editfile = "modelling-vroid-normal-female.blend"
armature_name = "female"
mixamo_animation_path = basepath + "/assets/blender/mixamo/female/"
mixamo_animations = ["idle", "walking", "running", "jump", "left_turn", "right_turn", "swimming", "treading_water"]
mixamo_animations = ["idle", "walking", "running", "jump", "left_turn", "right_turn", "swimming", "treading_water", "sitting", "hanging-climb", "hanging-idle"]
fbx_scale = 0.89
class VRMDataMale:
path = "buch1.vrm"
@@ -14,7 +14,7 @@ class VRMDataMale:
editfile = "modelling-vroid-normal-male.blend"
armature_name = "male"
mixamo_animation_path = basepath + "/assets/blender/mixamo/male/"
mixamo_animations = ["idle", "walking", "running", "jump", "left_turn", "right_turn", "swimming", "treading_water"]
mixamo_animations = ["idle", "walking", "running", "jump", "left_turn", "right_turn", "swimming", "treading_water", "sitting", "hanging-climb", "hanging-idle"]
fbx_scale = 1.0
class VRMDataMaleBabyShape:
path = "buch1-chibi.vrm"
@@ -101,7 +101,35 @@ class ExportMappingMaleBabyShape:
gltf_path = "assets/blender/" + "characters/shapes/male/chibi/vroid-normal-male-chibi.gltf"
ogre_scene = "characters/shapes/male/chibi/vroid-normal-male-chibi.scene"
inner_path = "Object"
objs = ["male", "Body", "Hair", "Face"]
objs = ["male", "Body"]
armature_name = "male"
outfile = "tmp-male-chibi.blend"
default_action = 'default'
def __init__(self):
self.files = []
for fobj in self.objs:
self.files.append({"name": fobj})
class ExportMappingMaleTestShapeEdited:
blend_path = "assets/blender/" + "edited-shape-test-male.blend"
gltf_path = "characters/shapes/male/edited-shape-test-male.gltf"
ogre_scene = "characters/shapes/male/chibi/edited-shape-test-male.scene"
inner_path = "Object"
objs = ["male", "Body"]
armature_name = "male"
outfile = "tmp-male-chibi.blend"
default_action = 'default'
def __init__(self):
self.files = []
for fobj in self.objs:
self.files.append({"name": fobj})
class ExportMappingMaleBaseShapeEdited:
blend_path = "assets/blender/" + "edited-normal-male-base.blend"
gltf_path = "characters/shapes/male/edited-normal-male-base.gltf"
ogre_scene = "characters/shapes/male/chibi/edited-normal-male-base.scene"
inner_path = "Object"
objs = ["male", "Body"]
armature_name = "male"
outfile = "tmp-male-chibi.blend"
default_action = 'default'

View File

@@ -0,0 +1,127 @@
import bpy
import bmesh
import math
import os, sys, time
argv = sys.argv
argv = argv[argv.index("--") + 1:]
obj_name = argv[0]
basepath = os.getcwd()
print("Object: " + obj_name)
target_obj = None
for obj in bpy.data.objects:
if obj.name == obj_name:
target_obj = obj
break
target_obj.select_set(True)
bpy.context.view_layer.objects.active = target_obj
l = target_obj.data.uv_layers.new(name="UVMapCustom")
target_obj.data.uv_layers.active = l
bpy.ops.object.mode_set(mode='EDIT')
#bpy.ops.mesh.select_mode(type="FACE")
#bpy.ops.mesh.select_all(action='SELECT')
#bpy.ops.uv.unwrap(method='UNWRAP', fill_holes=True, correct_aspect=True, use_subsurf_data=False, margin=0.002)
## bpy.ops.uv.smart_project(angle_limit=66.0, island_margin=0.0, user_area_weight=0.0, use_aspect=True, stretch_to_bounds=True)
# bpy.ops.uv.unwrap()
bpy.ops.mesh.select_all(action='SELECT')
# bpy.ops.uv.unwrap()
bm = bmesh.new()
bm = bmesh.from_edit_mesh(obj.data)
uv_layer = bm.loops.layers.uv["UVMapCustom"]
for f in bm.faces:
f.select = True
bpy.ops.uv.unwrap(method='ANGLE_BASED', fill_holes=True, correct_aspect=True, margin=0.001)
#uv_layer = bm.loops.layers.uv.verify()
if False:
for f in bm.faces:
for l in f.loops:
vert = l.vert
if vert.co.x >= 0.0:
if vert.co.x < 0.225:
if vert.co.y >= 0.0:
l[uv_layer].uv.x = vert.co.x
if vert.co.z < 1.04:
#legs
px = vert.co.x - 0.1
pd = vert.co.y
a = (math.atan2(-pd, px) + math.pi) / (2.0 * math.pi)
l[uv_layer].uv.y = vert.co.z * 0.4
l[uv_layer].uv.x = a * 0.27
elif vert.co.z < 1.3477:
l[uv_layer].uv.y = vert.co.z * 0.5 - 0.1
elif vert.co.z < 1.5497:
#chest
l[uv_layer].uv.y = vert.co.z * 0.9 - 0.65
# elif vert.co.z < 1.5531:
# l[uv_layer].uv.y = vert.co.z * 0.99 - 0.8
else:
# head
pd = vert.co.y
px = vert.co.x
a = (math.atan2(-pd, px) + math.pi) / (2.0 * math.pi)
l[uv_layer].uv.y = vert.co.z * 0.8 - 0.47
l[uv_layer].uv.x = a * 0.8 - 0.2
else:
l[uv_layer].uv.x = 0.5 - vert.co.x
if vert.co.z < 1.04:
#legs
px = vert.co.x - 0.1
pd = vert.co.y
a = (math.atan2(-pd, px) + math.pi) / (2.0 * math.pi)
l[uv_layer].uv.y = vert.co.z * 0.4
l[uv_layer].uv.x = 0.195 + a * 0.3
elif vert.co.z < 1.3477:
l[uv_layer].uv.y = vert.co.z * 0.5 - 0.1
elif vert.co.z < 1.5497:
#chest
l[uv_layer].uv.y = vert.co.z * 0.9 - 0.65
else:
# head
pd = vert.co.y
px = vert.co.x
a = (math.atan2(-pd, px) + math.pi) / (2.0 * math.pi)
l[uv_layer].uv.y = vert.co.z * 0.8 - 0.47
l[uv_layer].uv.x = -0.2 + a * 0.8
else:
# arms
px = (vert.co.z - 1.395)
py = (-vert.co.x + 0.915)
pd = vert.co.y
a = (math.atan2(-pd, px - 0.049) + math.pi) / (2.0 * math.pi)
print(a)
l[uv_layer].uv.x = a * 0.14 + 0.18
l[uv_layer].uv.y = (-vert.co.x + 0.915) * 0.7 + 0.05
else:
if vert.co.x < 0.225:
#not arms
l[uv_layer].uv.x = vert.co.x - 5.0
l[uv_layer].uv.y = vert.co.z
else:
#arms
l[uv_layer].uv.x = vert.co.x - 5.0
l[uv_layer].uv.y = vert.co.z
for f in bm.faces:
for l in f.loops:
vert = l.vert
if vert.co.z < 1.04:
#legs
px = vert.co.x - 0.1
pd = vert.co.y
a = (math.atan2(-pd, px) + math.pi) / (2.0 * math.pi)
l[uv_layer].uv.y = vert.co.z * 0.4
if vert.co.x >= 0.0:
l[uv_layer].uv.x = a * 0.27
else:
l[uv_layer].uv.x = a * 0.27 - 1.0
bmesh.update_edit_mesh(obj.data)
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.wm.save_as_mainfile(filepath="tmp.blend")

BIN
assets/blender/vehicles/boat-big.blend (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/blender/vehicles/boat-gobbot.blend (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/blender/vehicles/boat.blend (Stored with Git LFS)

Binary file not shown.

BIN
assets/blender/vehicles/raft.blend (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/blender/vehicles/tiny-boat.blend (Stored with Git LFS) Normal file

Binary file not shown.

View File

@@ -132,7 +132,7 @@ function Quest(name, book)
this.story:begin()
this:_narration()
end,
event = function(this, event)
event = function(this, event, event_data)
if not this.active then
return
end
@@ -228,13 +228,10 @@ function StartGameQuest()
quest.activate = function(this)
print('activate...')
local mc_is_free = function()
this.boat_id = ecs_vehicle_set("boat", 0, 0, -20, 1.75)
this.trigger_id = ecs_child_character_trigger(this.boat_id, "entered_boat", 0, 0, 0, 3, 3)
this.boat = true
local npc = ecs_npc_set("normal-female.glb", 0, 2, -20, 1.75)
ecs_character_physics_control(npc, false)
ecs_character_params_set("player", "gravity", true)
ecs_character_params_set("player", "buoyancy", true)
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)
@@ -248,31 +245,413 @@ function StartGameQuest()
end
return quest
end
function BoatControlQuest()
-- Parse a book from the Ink file.
local book = narrator.parse_file('stories.boat_control')
local quest = Quest('boat control', book)
quest.base = {}
quest.base.activate = quest.activate
quest.base.complete = quest.complete
quest.boat = false
quest.activate = function(this)
print('activate...')
local ent = ecs_get_player_entity()
ecs_set_slot(this.boat.boat_id, ent, "seat1")
-- ecs_character_set_actuator(ent, "sitting")
ecs_character("set-actuator", ent, "idle")
local boat_activated = function()
-- local ent = ecs_get_player_entity()
-- ecs_character_set_actuator(ent, "idle")
-- ecs_character_physics_control(ent, false)
-- ecs_character_params_set(ent, "gravity", false)
-- ecs_character_params_set(ent, "buoyancy", false)
end
this.story:bind('boat_activated', boat_activated)
this.base.activate(this)
end
quest.complete = function(this)
this.base.complete(this)
this.active = false
local ent = ecs_get_player_entity()
ecs_set_slot(this.boat.boat_id, this.boat.passengers[1], "seat0")
ecs_character("set-actuator", ent, "sitting")
ecs_set_slot(this.boat.boat_id, ent, "seat1")
ecs_character("set-actuator", ent, "sitting")
print("SLOT", "captain_seat", "boo1")
ecs_set_slot(this.boat.boat_id, ent, "captain_seat")
ecs_character("set-actuator", ent, "sitting")
end
return quest
end
function create_actuator()
return {
is_complete = false,
complete = function(this)
return this.is_complete
end,
finish = function(this)
this.is_complete = true
ecs_character("set-actuator", this.entity, "")
ecs_character("physics-control", this.entity, true)
print("COMPLETE")
end,
forward = function(this)
if (this.forward_animation) then
this:animation(this.forward_animation)
end
end,
animation = function(this, animation)
print("ANIMATION: ", animation)
ecs_character("set-actuator", this.entity, animation)
end,
event = function(this, event, trigger_entity, what_entity)
print("actuator events: ", event)
if event == "actuator_forward" then
this:forward()
return true
elseif event == "_in_actuator_forward" then
this:forward()
return true
elseif event == "actuator_exit" then
this:finish()
return true
end
if this.finish_events then
for i, p in ipairs(this.finish_events) do
if p == event then
this:finish()
break
end
end
if this.is_complete then
return true
end
end
return false
end,
}
end
quests = {}
local actuator = nil
function check_actuator_event(event, trigger_entity, what_entity)
print("check_actuator_event: ", event)
--[[
if event == "actuator_enter" then
if not ecs_character("is-player", what_entity) then
return
end
ecs_character("physics-control", what_entity, false)
local animation = ecs_trigger_get_animation(trigger_entity)
ecs_character("set-actuator", what_entity, animation)
ecs_trigger_set_position(trigger_entity, what_entity)
local ent = ecs_get_entity(what_entity)
if (ent.is_character()) then
print("character")
end
if (ent.is_player()) then
print("player")
end
-- crash()
actuator = create_actuator()
actuator.trigger = trigger_entity
actuator.entity = what_entity
actuator.forward = function(this)
this:animation("swimming-edge-climb")
local ent = ecs_get_player_entity()
ecs_character("params-set", ent, "gravity", true)
ecs_character("params-set", ent, "buoyancy", true)
end
actuator.base_event = actuator.event
actuator.finish_events = {"animation:swimming-edge-climb:end"}
actuator.event = function(this, event, te, we)
print("actuator events 1: ", event)
if this.base_event(this, event, te, we) then
return true
end
return false
end
]]--
if event == "character_enter" then
if not ecs_character("is-player", trigger_entity) then
return
end
actuator = create_actuator()
actuator.trigger = trigger_entity
actuator.entity = trigger_entity
actuator.other_entity = what_entity
actuator.forward_animation = "pass-character"
actuator.finish_events = {"animation:pass-character:end"}
actuator.base_event = actuator.event
actuator.event = function(this, event, te, we)
print("actuator events 2: ", event)
if event == "actuator_exit" then
this:animation("idle")
return true
end
if this.base_event(this, event, te, we) then
return true
end
if event == "character_enter" then
-- why?
-- ecs_character_set_actuator(this.entity, "idle")
-- ecs_character_set_actuator(this.entity, "idle")
return true
elseif event == "actuator_enter" then
-- why?
-- ecs_character_set_actuator(this.entity, "idle")
-- ecs_character_set_actuator(this.entity, "idle")
return true
end
return false
end
actuator:animation("idle")
end
end
-- ecs_set_debug_drawing(true)
setup_handler(function(event)
local vehicles = {}
local player_vehicles = {}
local actuators = {}
function create_vehicle(vtype, passengers, position)
local vehicle = {}
vehicle.id = ecs_vehicle_set(vtype, position[1], position[2], position[3], position[4])
vehicle.passengers = {}
vehicle.actuators = {}
vehicle.add_passenger = function(this, data)
local npc_id = ecs_npc_set(data.model, data.position[1], data.position[2], data.position[3], data.position[4])
table.insert(this.passengers, npc_id)
ecs_character("physics-control", npc_id, false)
ecs_character("params-set", npc_id, "gravity", false)
ecs_character("params-set", npc_id, "buoyancy", false)
ecs_set_slot(this.id, npc_id, data.slot)
ecs_character("set-actuator", npc_id, data.animation)
end
for i, v in ipairs(passengers) do
vehicle:add_passenger(v)
end
return vehicle
end
function create_boat()
local boat = create_vehicle("boat", {
model = "normal-female.glb",
position = {0, 2, -10, 1.75},
slot = "captain_seat",
animation = "sitting"}, {0, 0, -10, 1.75})
--[[
boat.id = ecs_vehicle_set("boat", 0, 0, -10, 1.75)
local npc_id = ecs_npc_set("normal-female.glb", 0, 2, -10, 1.75)
boat.passengers = {npc_id}
ecs_character("physics-control", npc_id, false)
ecs_character("params-set", npc_id, "gravity", false)
ecs_character("params-set", npc_id, "buoyancy", false)
print("SLOT", "captain_seat", "boo")
ecs_set_slot(boat.boat_id, npc_id, "captain_seat")
ecs_character("set-actuator", npc_id, "sitting")
-- ecs_set_animation_state(npc_id, "main", "actuator", true)
-- ecs_set_animation_state(npc_id, "actuator-state", "sitting", true)
]]--
boat.event = function(this, event, event_data)
print("boat: ", event)
if event == "boat_control_enter" then
local quest = BoatControlQuest()
quest.boat = this
quests[quest.name] = quest
quest:activate()
return true
end
if event == "boat_control_exit" then
return true
end
return false
end
return boat
end
function create_actuator2(ent)
print("create actuator")
local act = {
id = ent,
activated = false,
contained = {},
enter = function(this, what)
print(this.id, "visited by", what)
if not ecs_character("is-player", what) then
-- actuators are only for players
return
end
ecs_character("physics-control", what, false)
local animation = ecs_trigger_get_animation(this.id)
print(animation)
ecs_character("set-actuator", what, animation)
ecs_trigger_set_position(this.id, what)
local ent = ecs_get_entity(what)
if (ent.is_character()) then
print("character")
end
if (ent.is_player()) then
print("player")
end
table.insert(this.contained, what)
this.activated = true
print("position: ", _ecs.position(what).y)
end,
exit = function(this, what)
print(this.id, "left by", what)
if this.contained[1] ~= what then
crash()
end
ecs_character("set-actuator", this.contained[1], "")
ecs_character("physics-control", this.contained[1], true)
this.activated = false
print("COMPLETE")
end,
event = function(this, event, trigger, what)
if this.activated then
print("!!!", event)
if event == "actuator_forward" then
this:animation("swimming-edge-climb")
local ent = this.contained[1]
ecs_character("params-set", ent, "gravity", true)
ecs_character("params-set", ent, "buoyancy", true)
return true
elseif event == "actuator_backward" then
return true
elseif event == "animation:swimming-edge-climb:end" then
this:exit(this.contained[1])
return true
end
end
return false
end,
animation = function(this, animation)
if this.activated then
print("ANIMATION: ", animation)
local ent = this.contained[1]
ecs_character("set-actuator", ent, animation)
end
end,
}
return act
end
function endswith(s, suffix)
return string.sub(s, -#suffix) == suffix
end
function startswith(s, prefix)
return string.sub(s, 1, #prefix) == prefix
end
setup_handler(function(event, trigger_entity, what_entity)
print(event)
local event_handled = false
if startswith(event, "actuator_") and event ~= "actuator_created" then
for i, act in ipairs(actuators) do
if act.id == trigger_entity then
if event == "actuator_enter" then
if not act.activated then
act:enter(what_entity)
event_handled = true
break
end
elseif event == "actuator_exit" then
if act.activated then
act:exit(what_entity)
event_handled = true
break
end
else
if act.activated then
if act:event(event, trigger_entity, what_entity) then
event_handled = true
break
end
end
end
event_handled = true
end
end
elseif startswith(event, "animation:") and endswith(event, ":end") then
for i, act in ipairs(actuators) do
if act.activated then
if act:event(event, trigger_entity, what_entity) then
event_handled = true
break
end
end
end
end
if event_handled then
return
end
if event == "actuator_enter" or event == "actuator_exit" or event == "actuator_forward" then
print("bad event:", event, trigger_entity, what_entity)
crash()
end
for k, v in pairs(quests) do
if v.active then
v:event(event)
local event_data = {}
event_data.trigger_entity = trigger_entity
event_data.object_entity = what_entity
v:event(event, event_data)
end
end
for i, vehicle in ipairs(vehicles) do
print(i, vehicle)
if vehicle.event then
local event_data = {}
event_data.trigger_entity = trigger_entity
event_data.object_entity = what_entity
if vehicle:event(event, event_data) then
event_handled = true
end
end
end
if event_handled then
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()
story:choose(answer)
print("answered:", answer)
elseif event == "new_game" then
ecs_character_params_set("player", "gravity", true)
ecs_character_params_set("player", "buoyancy", false)
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)
elseif event == "actuator_created" then
print(trigger_entity)
local act = create_actuator2(trigger_entity)
table.insert(actuators, act)
--[[
else
if not actuator then
check_actuator_event(event, trigger_entity, what_entity)
else
if not actuator:event(event, trigger_entity, what_entity) then
crash()
end
if actuator:complete() then
print("ACTUATOR COMPLETE")
print("EXIT ACTUATOR")
actuator = nil
end
end
--]]
end
end)

View File

@@ -0,0 +1,6 @@
- Are we going to set sail?
* Yes
~ boat_activated()
* Not now
- ->END

9
morph/CMakeLists.txt Normal file
View File

@@ -0,0 +1,9 @@
project(morph)
add_executable(MorphTargetsResearch MorphTargetsResearch.cpp)
target_link_libraries(MorphTargetsResearch OgreBites OgrePaging ${BULLET_DYNAMICS_LIBRARY} ${BULLET_COLLISION_LIBRARY} ${BULLET_MATH_LIBRARY} ${ASSIMP_LIBRARIES}
-Wl,--as-needed
)
if(OGRE_STATIC)
target_link_options(MorphTargetsResearch PRIVATE -static-libstdc++ -static-libgcc)
endif()
add_dependencies(MorphTargetsResearch stage_files import_vrm import_character_shapes)

View File

@@ -0,0 +1,901 @@
#include <iostream>
#include <Ogre.h>
#include <OgreApplicationContext.h>
class App;
struct Vertex2D {
float x, y;
Ogre::ColourValue color;
Vertex2D(float x, float y, const Ogre::ColourValue &color)
: x(x)
, y(y)
, color(color)
{
}
};
static bool calculateBarycentricCoordinates(int px, int py, const Vertex2D &v0,
const Vertex2D &v1,
const Vertex2D &v2, float &u,
float &v, float &w)
{
float area = 0.5f * ((v1.x - v0.x) * (v2.y - v0.y) -
(v2.x - v0.x) * (v1.y - v0.y));
if (area == 0)
return false; // Degenerate triangle
u = 0.5f * ((v1.x - px) * (v2.y - py) - (v2.x - px) * (v1.y - py)) /
area;
v = 0.5f * ((v2.x - px) * (v0.y - py) - (v0.x - px) * (v2.y - py)) /
area;
w = 1.0f - u - v;
return u >= 0 && v >= 0 && w >= 0;
}
static void drawColoredTriangle(Ogre::Image &image, const Vertex2D &v0,
const Vertex2D &v1, const Vertex2D &v2)
{
// Determine bounding box of the triangle
int minX = std::min({ v0.x, v1.x, v2.x });
int minY = std::min({ v0.y, v1.y, v2.y });
int maxX = std::max({ v0.x, v1.x, v2.x });
int maxY = std::max({ v0.y, v1.y, v2.y });
// Clamp to image bounds
minX = std::max(0, minX);
minY = std::max(0, minY);
maxX = std::min((int)image.getWidth() - 1, maxX);
maxY = std::min((int)image.getHeight() - 1, maxY);
for (int y = minY; y <= maxY; ++y) {
for (int x = minX; x <= maxX; ++x) {
float u, v, w;
if (calculateBarycentricCoordinates(x, y, v0, v1, v2, u,
v, w)) {
// Interpolate colors
Ogre::ColourValue color = u * v0.color +
v * v1.color +
w * v2.color;
image.setColourAt(color, x, y, 0);
}
}
}
}
static void getSubmeshNormals(const Ogre::Mesh *mesh,
const Ogre::SubMesh *submesh,
std::vector<Ogre::Vector3> &normals)
{
int j;
float *pReal;
int vertex_count = 0;
if (submesh->useSharedVertices)
vertex_count += mesh->sharedVertexData->vertexCount;
else
vertex_count += submesh->vertexData->vertexCount;
Ogre::HardwareVertexBufferSharedPtr vbuf;
Ogre::VertexData *vertex_data = submesh->useSharedVertices ?
mesh->sharedVertexData :
submesh->vertexData;
const Ogre::VertexElement *normalsElem =
vertex_data->vertexDeclaration->findElementBySemantic(
Ogre::VES_NORMAL);
if (!normalsElem)
return;
OgreAssert(normals.size() == 0 || normals.size() == vertex_count,
"bad vertex count");
normals.resize(vertex_count);
vbuf = vertex_data->vertexBufferBinding->getBuffer(
normalsElem->getSource());
unsigned char *vertex = static_cast<unsigned char *>(
vbuf->lock(Ogre::HardwareBuffer::HBL_READ_ONLY));
for (j = 0; j < vertex_data->vertexCount;
++j, vertex += vbuf->getVertexSize()) {
normalsElem->baseVertexPointerToElement(vertex, &pReal);
normals[j] = Ogre::Vector3(pReal[0], pReal[1], pReal[2]);
}
vbuf->unlock();
}
static void getSubmeshUVs(const Ogre::Mesh *mesh, const Ogre::SubMesh *submesh,
std::vector<Ogre::Vector2> &uvs, int index)
{
int j;
float *pReal;
uvs.resize(0);
Ogre::HardwareVertexBufferSharedPtr vbuf;
Ogre::VertexData *vertex_data = submesh->useSharedVertices ?
mesh->sharedVertexData :
submesh->vertexData;
const Ogre::VertexElement *uvElem =
vertex_data->vertexDeclaration->findElementBySemantic(
Ogre::VES_TEXTURE_COORDINATES, index);
int vertex_count = vertex_data->vertexCount;
if (!uvElem)
return;
OgreAssert(uvs.size() == 0 || uvs.size() == vertex_count,
"bad vertex count");
uvs.resize(vertex_count);
vbuf = vertex_data->vertexBufferBinding->getBuffer(uvElem->getSource());
unsigned char *uv = static_cast<unsigned char *>(
vbuf->lock(Ogre::HardwareBuffer::HBL_READ_ONLY));
for (j = 0; j < vertex_data->vertexCount; ++j) {
uvElem->baseVertexPointerToElement(uv, &pReal);
uvs[j] = Ogre::Vector2(pReal[0], pReal[1]);
std::cout << "buffer UV: " << index << ": " << j << " " << uvs[j] << " " << (void *)pReal << " " << (void *)uv << std::endl;
uv += vbuf->getVertexSize();
}
vbuf->unlock();
}
static void getSubmeshVertices(const Ogre::Mesh *mesh,
const Ogre::SubMesh *submesh,
std::vector<Ogre::Vector3> &vertices)
{
int j;
float *pReal;
int vertex_count = 0;
if (submesh->useSharedVertices)
vertex_count += mesh->sharedVertexData->vertexCount;
else
vertex_count += submesh->vertexData->vertexCount;
Ogre::HardwareVertexBufferSharedPtr vbuf;
Ogre::VertexData *vertex_data = submesh->useSharedVertices ?
mesh->sharedVertexData :
submesh->vertexData;
const Ogre::VertexElement *posElem =
vertex_data->vertexDeclaration->findElementBySemantic(
Ogre::VES_POSITION);
if (!posElem)
return;
OgreAssert(vertices.size() == 0 || vertices.size() == vertex_count,
"bad vertex count");
vertices.resize(vertex_count);
vbuf = vertex_data->vertexBufferBinding->getBuffer(
posElem->getSource());
unsigned char *vertex = static_cast<unsigned char *>(
vbuf->lock(Ogre::HardwareBuffer::HBL_READ_ONLY));
for (j = 0; j < vertex_data->vertexCount;
++j, vertex += vbuf->getVertexSize()) {
posElem->baseVertexPointerToElement(vertex, &pReal);
vertices[j] = Ogre::Vector3(pReal[0], pReal[1], pReal[2]);
}
vbuf->unlock();
}
static void getSubmeshIndices(const Ogre::Mesh *mesh,
const Ogre::SubMesh *submesh,
std::vector<unsigned long> &indices)
{
int index_count = 0;
index_count += submesh->indexData->indexCount;
int index_offset = 0;
indices.resize(index_count);
Ogre::IndexData *index_data = submesh->indexData;
size_t numTris = index_data->indexCount / 3;
Ogre::HardwareIndexBufferSharedPtr ibuf = index_data->indexBuffer;
bool use32bitindexes =
(ibuf->getType() == Ogre::HardwareIndexBuffer::IT_32BIT);
unsigned long *pLong = static_cast<unsigned long *>(
ibuf->lock(Ogre::HardwareBuffer::HBL_READ_ONLY));
unsigned short *pShort = reinterpret_cast<unsigned short *>(pLong);
size_t offset = 0;
if (use32bitindexes) {
for (size_t k = 0; k < numTris * 3; ++k) {
indices[index_offset++] =
pLong[k] + static_cast<unsigned long>(offset);
}
} else {
for (size_t k = 0; k < numTris * 3; ++k) {
indices[index_offset++] =
static_cast<unsigned long>(pShort[k]) +
static_cast<unsigned long>(offset);
}
}
ibuf->unlock();
}
struct SubMeshInformation {
Ogre::String materialName;
bool sharedVertices;
std::vector<Ogre::Vector3> vertices;
std::vector<Ogre::Vector3> normals;
std::vector<Ogre::Vector2> uvs;
std::vector<Ogre::Vector2> uv2s;
std::vector<unsigned long> indices;
Ogre::SubMesh::LODFaceList lodFaceList;
Ogre::Mesh::VertexBoneAssignmentList boneList;
void createSubmesh(Ogre::Mesh *mesh)
{
int i;
Ogre::SubMesh *out = mesh->createSubMesh();
out->useSharedVertices = false;
out->vertexData = new Ogre::VertexData();
size_t currOffset = 0;
Ogre::VertexDeclaration *vertexDecl =
out->vertexData->vertexDeclaration;
vertexDecl->addElement(0, currOffset, Ogre::VET_FLOAT3,
Ogre::VES_POSITION);
currOffset +=
Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3);
vertexDecl->addElement(0, currOffset, Ogre::VET_FLOAT3,
Ogre::VES_NORMAL);
currOffset +=
Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3);
vertexDecl->addElement(0, currOffset, Ogre::VET_FLOAT2,
Ogre::VES_TEXTURE_COORDINATES, 0);
currOffset +=
Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT2);
out->vertexData->vertexCount = vertices.size();
Ogre::HardwareVertexBufferSharedPtr vbuf =
Ogre::HardwareBufferManager::getSingleton()
.createVertexBuffer(
vertexDecl->getVertexSize(0),
out->vertexData->vertexCount,
Ogre::HardwareBuffer::
HBU_STATIC_WRITE_ONLY, // only GPU side
false);
Ogre::VertexBufferBinding *binding =
out->vertexData->vertexBufferBinding;
binding->setBinding(0, vbuf);
float *pvertex = static_cast<float *>(
vbuf->lock(Ogre::HardwareBuffer::HBL_DISCARD));
out->indexData->indexCount = indices.size();
out->indexData->indexBuffer =
Ogre::HardwareBufferManager::getSingleton().createIndexBuffer(
vertices.size() < 32768 ?
Ogre::HardwareIndexBuffer::IT_16BIT :
Ogre::HardwareIndexBuffer::IT_32BIT,
out->indexData->indexCount,
Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY,
false);
OgreAssert(normals.size() == vertices.size(), "bad normals");
for (i = 0; i < vertices.size(); i++) {
*pvertex++ = vertices[i].x;
*pvertex++ = vertices[i].y;
*pvertex++ = vertices[i].z;
if (normals.size() > 0) {
*pvertex++ = normals[i].x;
*pvertex++ = normals[i].y;
*pvertex++ = normals[i].z;
} else {
*pvertex++ = 0.0f;
*pvertex++ = 1.0f;
*pvertex++ = 0.0f;
}
if (uvs.size() > 0) {
*pvertex++ = uvs[i].x;
*pvertex++ = uvs[i].y;
} else {
*pvertex++ = 0.0f;
*pvertex++ = 1.0f;
}
}
vbuf->unlock();
Ogre::HardwareIndexBufferSharedPtr ibuf =
out->indexData->indexBuffer;
if (vertices.size() < 32768) {
unsigned short *pindices =
static_cast<unsigned short *>(ibuf->lock(
Ogre::HardwareBuffer::HBL_DISCARD));
for (i = 0; i < indices.size(); i++)
*pindices++ = (unsigned short)indices[i];
ibuf->unlock();
} else {
unsigned long *pindices = static_cast<unsigned long *>(
ibuf->lock(Ogre::HardwareBuffer::HBL_DISCARD));
for (i = 0; i < indices.size(); i++)
*pindices++ = indices[i];
ibuf->unlock();
}
out->clearBoneAssignments();
auto vbass_it = boneList.begin();
while (vbass_it != boneList.end()) {
out->addBoneAssignment(vbass_it->second);
vbass_it++;
}
out->setMaterialName(materialName);
}
};
static void getSubMeshInformation(const Ogre::Mesh *mesh,
const Ogre::SubMesh *submesh,
struct SubMeshInformation *info)
{
int i;
info->materialName = submesh->getMaterialName();
std::cout << "material name: " << info->materialName << std::endl;
info->sharedVertices = submesh->useSharedVertices;
info->lodFaceList = submesh->mLodFaceList;
info->boneList = submesh->getBoneAssignments();
getSubmeshIndices(mesh, submesh, info->indices);
getSubmeshVertices(mesh, submesh, info->vertices);
getSubmeshNormals(mesh, submesh, info->normals);
getSubmeshUVs(mesh, submesh, info->uvs, 0);
std::cout << "UV0: " << info->uvs.size() << std::endl;
getSubmeshUVs(mesh, submesh, info->uv2s, 1);
std::cout << "UV1: " << info->uv2s.size() << std::endl;
for (i = 0; i < info->uvs.size(); i++)
std::cout << "UV0: " << i << " " << info->uvs[i] << std::endl;
for (i = 0; i < info->uv2s.size(); i++)
std::cout << "UV1: " << i << " " << info->uv2s[i] << std::endl;
}
struct MeshMorphTarget {
Ogre::SkeletonPtr skelp;
Ogre::Image image;
struct DataChange {
int index;
int submesh;
Ogre::Vector3 vertex;
Ogre::Vector3 normal;
};
std::vector<DataChange> vertices;
float weight;
};
struct MeshInformation {
std::map<Ogre::String, struct MeshMorphTarget> targets;
std::vector<struct SubMeshInformation> submesh;
Ogre::SkeletonPtr skelp;
Ogre::String skelName;
MeshInformation(Ogre::Mesh *prototype)
{
int i;
skelName = prototype->getSkeletonName();
skelp = Ogre::SkeletonManager::getSingleton().getByName(
prototype->getSkeletonName(), "Characters");
OgreAssert(skelp, "Could not load skeleton " +
prototype->getSkeletonName());
submesh.resize(prototype->getNumSubMeshes());
for (i = 0; i < submesh.size(); i++) {
getSubMeshInformation(prototype,
prototype->getSubMesh(i),
&submesh[i]);
}
}
Ogre::MeshPtr create(const Ogre::String &meshName)
{
int j;
Ogre::MeshPtr baseMesh =
Ogre::MeshManager::getSingleton().createManual(
meshName, Ogre::ResourceGroupManager::
DEFAULT_RESOURCE_GROUP_NAME);
applyMorphTargets();
for (j = 0; j < submesh.size(); j++)
submesh[j].createSubmesh(baseMesh.get());
//define a extreme boundary values
Ogre::Real max_x = -1e+8;
Ogre::Real min_x = 1e+8;
Ogre::Real max_y = -1e+8;
Ogre::Real min_y = 1e+8;
Ogre::Real max_z = -1e+8;
Ogre::Real min_z = +1e+8;
// Setting bounding box
Ogre::Mesh::SubMeshList mlist = baseMesh->getSubMeshes();
for (j = 0; j < mlist.size(); j++) {
Ogre::SubMesh *in = mlist[j];
Ogre::VertexData *vertex_data = in->vertexData;
const Ogre::VertexElement *posElem =
vertex_data->vertexDeclaration
->findElementBySemantic(
Ogre::VES_POSITION);
Ogre::HardwareVertexBufferSharedPtr hwvb =
in->vertexData->vertexBufferBinding->getBuffer(
posElem->getSource());
unsigned char *hbuff =
static_cast<unsigned char *>(hwvb->lock(
Ogre::HardwareBuffer::HBL_READ_ONLY));
Ogre::Real *pValue;
Ogre::Real value;
for (size_t idx = 0; idx < vertex_data->vertexCount;
++idx, hbuff += hwvb->getVertexSize()) {
posElem->baseVertexPointerToElement(hbuff,
&pValue);
value = (*pValue++);
if (value > max_x)
max_x = value;
if (value < min_x)
min_x = value;
value = (*pValue++);
if (value > max_y)
max_y = value;
if (value < min_y)
min_y = value;
value = (*pValue++);
if (value > max_z)
max_z = value;
if (value < min_z)
min_z = value;
}
hwvb->unlock();
}
baseMesh->setSkeletonName(skelName);
baseMesh->_setBounds(Ogre::AxisAlignedBox(min_x, min_y, min_z,
max_x, max_y, max_z));
return baseMesh;
}
std::pair<int, int> findVertex(const Ogre::Vector2 &uv)
{
int i, j;
float d = 1.5f;
int ret_submesh = -1, ret_index = -1;
for (i = 0; i < submesh.size(); i++) {
if (submesh[i].uv2s.size() != submesh[i].normals.size())
continue;
for (j = 0; j < submesh[i].uv2s.size(); j++) {
float l =
submesh[i].uv2s[j].squaredDistance(uv);
if (l < d) {
d = l;
ret_submesh = i;
ret_index = j;
}
}
}
OgreAssert(ret_submesh >= 0 && ret_index >= 0,
"Vertex not found");
return { ret_submesh, ret_index };
}
void createTarget(const Ogre::String &targetName,
const Ogre::Mesh *target)
{
MeshMorphTarget mtarget;
int i, j, k, l;
std::unordered_map<int, SubMeshInformation> targetInfo;
mtarget.skelp = Ogre::SkeletonManager::getSingleton().getByName(
target->getSkeletonName(), "Characters");
int count = 0;
for (i = 0; i < target->getNumSubMeshes(); i++) {
SubMeshInformation info;
getSubMeshInformation(target, target->getSubMesh(i),
&info);
std::cout << "-- processed submesh: " << i << std::endl;
std::cout << "-- processed submesh: "
<< info.materialName << std::endl;
if (info.uv2s.size() == 0)
continue;
targetInfo[i] = info;
count++;
}
OgreAssert(count > 0, "Bad target mesh count");
struct VertexIndex {
int base_submesh;
int base_index;
};
std::vector<VertexIndex> indices;
Ogre::Image image(Ogre::PF_R8G8B8A8, 512, 512);
Ogre::Image image_normals(Ogre::PF_R8G8B8A8, 512, 512);
float vertexMul = 0.0f, normalMul = 0.0f;
struct DeltaData {
std::vector<Ogre::Vector3> vertexDeltas;
std::vector<Ogre::Vector3> normalDeltas;
std::vector<Ogre::Vector2> uvs;
std::vector<unsigned long> indices;
};
std::vector<DeltaData> deltas;
std::cout << "target start " << targetName << "\n";
deltas.reserve(targetInfo.size());
for (i = 0; i < targetInfo.size(); i++) {
if (targetInfo.find(i) == targetInfo.end())
continue;
std::cout << "submesh: " << i << std::endl;
OgreAssert(targetInfo[i].uv2s.size() == targetInfo[i].normals.size(), "bad UV2 count");
OgreAssert(targetInfo[i].vertices.size() > 0, "bad vertex count");
struct DeltaData dd;
dd.vertexDeltas.resize(targetInfo[i].vertices.size());
dd.normalDeltas.resize(targetInfo[i].vertices.size());
dd.uvs.resize(targetInfo[i].vertices.size());
std::cout
<< "\tvertices: " << targetInfo[i].vertices.size()
<< std::endl;
for (j = 0; j < targetInfo[i].vertices.size(); j++) {
const Ogre::Vector2 &target_uv =
targetInfo[i].uv2s[j];
auto ti = findVertex(target_uv);
const Ogre::Vector3 &base_v =
submesh[ti.first].vertices[ti.second];
const Ogre::Vector3 &target_v =
targetInfo[i].vertices[j];
const Ogre::Vector3 &base_n =
submesh[ti.first].normals[ti.second];
const Ogre::Vector3 &target_n =
targetInfo[i].normals[j];
Ogre::Vector3 vertexDelta = target_v - base_v;
Ogre::Vector3 normalDelta = target_n - base_n;
dd.vertexDeltas[j] = vertexDelta;
dd.normalDeltas[j] = normalDelta;
dd.uvs[j] = target_uv;
std::cout << "UV: " << j << " " << target_uv << " " << ti.second << std::endl;
float lv = vertexDelta.length();
if (vertexMul < lv)
vertexMul = lv;
float ln = normalDelta.length();
if (normalMul < ln)
normalMul = ln;
}
dd.indices = targetInfo[i].indices;
std::cout << "indices: " << dd.indices.size() << std::endl;
deltas.push_back(dd);
}
std::cout << "multipliers: " << vertexMul << " " << normalMul
<< "\n";
OgreAssert(vertexMul > 0.0f && normalMul > 0.0f,
"bad multipliers");
// normalMul = 1.0f;
// vertexMul = 1.0f;
for (i = 0; i < deltas.size(); i++) {
OgreAssert(deltas[i].normalDeltas.size() == deltas[i].vertexDeltas.size(), "bad delta count");
for (j = 0; j < deltas[i].vertexDeltas.size(); j++) {
Ogre::Vector3 d =
deltas[i].normalDeltas[j] / normalMul *
0.5f +
Ogre::Vector3(0.5f, 0.5f, 0.5f);
d.x = Ogre::Math::Clamp(d.x, 0.0f, 1.0f);
d.y = Ogre::Math::Clamp(d.y, 0.0f, 1.0f);
d.z = Ogre::Math::Clamp(d.z, 0.0f, 1.0f);
deltas[i].normalDeltas[j] = d;
d =
deltas[i].vertexDeltas[j] / vertexMul *
0.5f +
Ogre::Vector3(0.5f, 0.5f, 0.5f);
d.x = Ogre::Math::Clamp(d.x, 0.0f, 1.0f);
d.y = Ogre::Math::Clamp(d.y, 0.0f, 1.0f);
d.z = Ogre::Math::Clamp(d.z, 0.0f, 1.0f);
deltas[i].vertexDeltas[j] = d;
std::cout << " vertex delta: " << deltas[i].vertexDeltas[j];
std::cout << " normal delta: " << deltas[i].normalDeltas[j];
std::cout << std::endl;
}
}
image.setTo(Ogre::ColourValue(0.5, 0.5, 0.5, 1));
image_normals.setTo(Ogre::ColourValue(0.5, 0.5, 0.5, 1));
for (i = 0; i < deltas.size(); i++) {
for (j = 0; j < deltas[i].indices.size(); j += 3) {
int i1 = (int)deltas[i].indices[j];
int i2 = (int)deltas[i].indices[j + 1];
int i3 = (int)deltas[i].indices[j + 2];
Ogre::Vector2 u0 = deltas[i].uvs[i1];
Ogre::Vector2 u1 = deltas[i].uvs[i2];
Ogre::Vector2 u2 = deltas[i].uvs[i3];
Ogre::ColourValue c0(
deltas[i].vertexDeltas[i1].x,
deltas[i].vertexDeltas[i1].y,
deltas[i].vertexDeltas[i1].z, 1.0f);
Ogre::ColourValue c1(
deltas[i].vertexDeltas[i2].x,
deltas[i].vertexDeltas[i2].y,
deltas[i].vertexDeltas[i2].z, 1.0f);
Ogre::ColourValue c2(
deltas[i].vertexDeltas[i3].x,
deltas[i].vertexDeltas[i3].y,
deltas[i].vertexDeltas[i3].z, 1.0f);
Ogre::ColourValue x0(
deltas[i].normalDeltas[i1].x,
deltas[i].normalDeltas[i1].y,
deltas[i].normalDeltas[i1].z, 1.0f);
Ogre::ColourValue x1(
deltas[i].normalDeltas[i2].x,
deltas[i].normalDeltas[i2].y,
deltas[i].normalDeltas[i2].z, 1.0f);
Ogre::ColourValue x2(
deltas[i].normalDeltas[i3].x,
deltas[i].normalDeltas[i3].y,
deltas[i].normalDeltas[i3].z, 1.0f);
Vertex2D v0(u0.x * 512, u0.y * 512, c0);
Vertex2D v1(u1.x * 512, u1.y * 512, c1);
Vertex2D v2(u2.x * 512, u2.y * 512, c2);
Vertex2D xv0(u0.x * 512, u0.y * 512, x0);
Vertex2D xv1(u1.x * 512, u1.y * 512, x1);
Vertex2D xv2(u2.x * 512, u2.y * 512, x2);
std::cout << "triangle: ";
std::cout << i1 << " " << i2 << " " << i3 << " ";
std::cout << u0 << " " << u1 << " " << u2 << " ";
std::cout << "(" << v0.x << ", " << v0.y << ") ";
std::cout << "(" << v1.x << ", " << v1.y << ") ";
std::cout << "(" << v2.x << ", " << v2.y << ") ";
std::cout << std::endl;
// SoftwareRenderer2D::drawTriangle(image, v0, v1, v2);
drawColoredTriangle(image, v0, v1, v2);
drawColoredTriangle(image_normals, xv0, xv1,
xv2);
}
}
std::cout << "output image\n";
image.save(targetName + "_debug.png");
image_normals.save(targetName + "_normals_debug.png");
std::vector<Ogre::Vector3> vertices;
std::vector<Ogre::Vector3> normals;
std::vector<Ogre::Vector2> uvs;
std::vector<int> uv_submeshes;
std::vector<int> uv_indices;
std::vector<Ogre::Vector2> change_uvs;
std::vector<MeshMorphTarget::DataChange> changes;
for (i = 0; i < targetInfo.size(); i++) {
if (targetInfo[i].uv2s.size() !=
targetInfo[i].normals.size())
continue;
OgreAssert(targetInfo[i].normals.size() ==
targetInfo[i].uv2s.size(),
"UV2 missing");
const std::vector<Ogre::Vector2> &puv =
targetInfo[i].uv2s;
const std::vector<Ogre::Vector3> &pvertices =
targetInfo[i].vertices;
const std::vector<Ogre::Vector3> &pnormals =
targetInfo[i].normals;
for (j = 0; j < pvertices.size(); j++) {
MeshMorphTarget::DataChange dc;
dc.index = -1;
dc.submesh = -1;
change_uvs.push_back(puv[j]);
dc.normal = pnormals[j];
dc.vertex = pvertices[j];
changes.push_back(dc);
}
}
for (i = 0; i < submesh.size(); i++) {
int submesh_id = i;
if (submesh[i].uv2s.size() != submesh[i].normals.size())
continue;
for (j = 0; j < submesh[i].vertices.size(); j++) {
int index_id = j;
const Ogre::Vector2 &xuv = submesh[i].uv2s[j];
uvs.push_back(xuv);
uv_submeshes.push_back(submesh_id);
uv_indices.push_back(index_id);
}
}
for (i = 0; i < changes.size(); i++) {
float l = 1.0f;
int sub = -1, index = -1;
for (j = 0; j < uvs.size(); j++) {
float xl =
change_uvs[i].squaredDistance(uvs[j]);
if (xl < l) {
l = xl;
sub = uv_submeshes[j];
index = uv_indices[j];
}
}
OgreAssert(sub >= 0 && index >= 0, "Indexing error");
if (l < 0.000000005f) {
changes[i].submesh = sub;
changes[i].index = index;
}
}
mtarget.vertices = changes;
mtarget.weight = 0.0f;
targets[targetName] = mtarget;
}
void setTargetWeight(const Ogre::String &targetName, float weight)
{
if (targets.find(targetName) == targets.end())
return;
targets[targetName].weight = weight;
}
void applyMorphTargets()
{
auto it = targets.begin();
float sum = 0.0f;
while (it != targets.end()) {
sum += it->second.weight;
it++;
}
if (sum > 1.0f) {
it = targets.begin();
while (it != targets.end()) {
it->second.weight = it->second.weight / sum;
it++;
}
}
it = targets.begin();
while (it != targets.end()) {
if (it->second.weight > 0.001f) {
int i;
for (i = 0; i < it->second.vertices.size();
i++) {
int sub_id =
it->second.vertices[i].submesh;
int index_id =
it->second.vertices[i].index;
if (sub_id < 0 || index_id < 0)
continue;
Ogre::Vector3 orig_vertex =
submesh[sub_id]
.vertices[index_id];
Ogre::Vector3 new_vertex =
it->second.vertices[i].vertex;
Ogre::Vector3 vertex_update =
Ogre::Math::lerp(
orig_vertex, new_vertex,
it->second.weight);
Ogre::Vector3 orig_normal =
submesh[sub_id]
.normals[index_id];
Ogre::Vector3 new_normal =
it->second.vertices[i].normal;
Ogre::Vector3 normal_update =
Ogre::Math::lerp(
orig_normal, new_normal,
it->second.weight);
submesh[sub_id].vertices[index_id] =
vertex_update;
submesh[sub_id].normals[index_id] =
normal_update;
}
}
it++;
}
}
};
struct SimpleListener : public OgreBites::InputListener {
App *mApp;
SimpleListener(App *app)
: mApp(app)
{
}
void frameRendered(const Ogre::FrameEvent &evt) override;
};
class App : public OgreBites::ApplicationContext {
public:
App()
: OgreBites::ApplicationContext("MorphTargetsResearch")
{
}
void locateResources() override
{
Ogre::ResourceGroupManager::getSingleton().createResourceGroup(
"Characters", true);
Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
"./characters", "FileSystem", "Characters", true, true);
Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
"../characters", "FileSystem", "Characters", true,
true);
OgreBites::ApplicationContext::locateResources();
}
void loadResources() override
{
}
Ogre::MeshPtr loadMesh(Ogre::String meshName)
{
Ogre::MeshPtr ret = Ogre::MeshManager::getSingleton().load(
meshName, "Characters");
return ret;
}
void meshProcessing()
{
int i;
//code to test and create entity based off of sub-meshes
//vector of meshes
std::vector<Ogre::MeshPtr> m_Meshes;
//define meshes
// Ogre::MeshPtr base = Ogre::MeshManager::getSingleton().load(
// "male/normal-male.glb", "Characters");
Ogre::MeshPtr base = loadMesh("shapes/male/edited-normal-male-base.glb");
m_Meshes.push_back(base);
Ogre::MeshPtr shape = loadMesh("shapes/male/edited-shape-test-male.glb");
OgreAssert(shape, "no shape");
MeshInformation meshinfo(base.get());
std::string m_ModelId = "MergedMesh";
meshinfo.createTarget("shape", shape.get());
meshinfo.setTargetWeight("shape", 1.0f);
Ogre::MeshPtr m_BaseMesh = meshinfo.create(m_ModelId);
Ogre::Entity *ent =
mScnMgr->createEntity("entName", m_BaseMesh->getName());
// ent->setMaterialName("TestMat");
//code here is temporary and only ment as a test for the modelers
//Entity* testEntity = mSceneMgr->createEntity("cc", source->getName());
//testEntity->setMaterialName("TestMat");
Ogre::SceneNode *thisSceneNode =
mScnMgr->getRootSceneNode()->createChildSceneNode();
// thisSceneNode->setPosition(100, 100, 100);
thisSceneNode->attachObject(ent);
#if 0
mIdle = ent->getAnimationState("idle");
mIdle->setEnabled(true);
mIdle->setLoop(true);
mIdle->setWeight(1.0f);
#endif
}
void setup() override
{
OgreBites::ApplicationContext::setup();
Ogre::Root *root = getRoot();
Ogre::SceneManager *scnMgr = root->createSceneManager();
Ogre::RTShader::ShaderGenerator *shadergen =
Ogre::RTShader::ShaderGenerator::getSingletonPtr();
shadergen->addSceneManager(scnMgr);
// also need to tell where we are
mScnMgr.reset(scnMgr);
mCameraNode.reset(
mScnMgr->getRootSceneNode()->createChildSceneNode());
mCameraNode->setPosition(0, 2, 3);
mCameraNode->lookAt(Ogre::Vector3(0, 1, -1),
Ogre::Node::TS_PARENT);
Ogre::Light *light = mScnMgr->createLight("MainLight");
Ogre::SceneNode *lightNode =
mScnMgr->getRootSceneNode()->createChildSceneNode();
// lightNode->setPosition(0, 10, 15);
lightNode->setDirection(
Ogre::Vector3(0.55, -0.3, 0.75).normalisedCopy());
lightNode->attachObject(light);
light->setType(Ogre::Light::LT_DIRECTIONAL);
light->setDiffuseColour(Ogre::ColourValue::White);
light->setSpecularColour(Ogre::ColourValue(0.4, 0.4, 0.4));
mScnMgr->setAmbientLight(
Ogre::ColourValue(0.5f, 0.5f, 0.5f, 1.0f));
// create the camera
Ogre::Camera *cam = mScnMgr->createCamera("normal_camera");
cam->setNearClipDistance(0.1f); // specific to this sample
cam->setAutoAspectRatio(true);
mCameraNode->attachObject(cam);
mCamera.reset(cam);
// and tell it to render into the main window
getRenderWindow()->addViewport(cam);
meshProcessing();
#if 0
Ogre::Entity *ent_base =
mScnMgr->createEntity("male/normal-male.glb");
Ogre::Entity *ent_shape = mScnMgr->createEntity(
"shapes/male/chibi/vroid-normal-male-chibi.glb");
Ogre::SceneNode *characterNode =
mScnMgr->getRootSceneNode()->createChildSceneNode("ch");
characterNode->attachObject(ent_base);
#endif
addInputListener(OGRE_NEW SimpleListener(this));
Ogre::Image image(Ogre::PF_R8G8B8A8, 1024, 1024);
image.setTo(Ogre::ColourValue(0, 0, 0, 1));
Vertex2D v0(0.0f, 0.0f, Ogre::ColourValue(1, 0, 0, 1));
Vertex2D v1(500.0f, 100.0f, Ogre::ColourValue(0, 1, 0, 1));
Vertex2D v2(0.0f, 500.0f, Ogre::ColourValue(0, 0, 1, 1));
// SoftwareRenderer2D::drawTriangle(image, v0, v1, v2);
drawColoredTriangle(image, v0, v1, v2);
image.save("checkup.png");
}
void animationTime(float time)
{
mIdle->addTime(time);
}
private:
std::unique_ptr<Ogre::SceneNode> mCameraNode;
std::unique_ptr<Ogre::Camera> mCamera;
std::unique_ptr<Ogre::SceneManager> mScnMgr;
Ogre::AnimationState *mIdle;
};
void SimpleListener::frameRendered(const Ogre::FrameEvent &evt)
{
mApp->animationTime(evt.timeSinceLastFrame);
}
int main(int argc, char *argv[])
{
App ctx;
ctx.initApp();
ctx.setWindowGrab(true);
ctx.getRoot()->startRendering();
ctx.setWindowGrab(false);
ctx.closeApp();
return 0;
}

BIN
sims4-t1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 569 KiB

BIN
sims4-t2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 708 KiB

BIN
sims4-t3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 840 KiB

BIN
sims4-t4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 569 KiB

BIN
sims4-uv1-1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

BIN
sims4-uv1-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

BIN
sims4-uv1-3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

View File

@@ -1,5 +0,0 @@
project(characters)
find_package(OGRE REQUIRED COMPONENTS Bites Bullet Paging Terrain CONFIG)
# add_library(controller STATIC controller.cpp)
# target_link_libraries(controller PUBLIC OgreMain OgreBites PRIVATE GameData)
# target_include_directories(controller PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

View File

@@ -1,369 +0,0 @@
#include <iostream>
#include <Ogre.h>
#include <OgreBullet.h>
#include "BulletCollision/CollisionDispatch/btGhostObject.h"
#include "LinearMath/btTransform.h"
#include "character.h"
#define RUN_SPEED 17 // character running speed in units per second
#define TURN_SPEED 500.0f // character turning in degrees per second
#define ANIM_FADE_SPEED \
7.5f // animation crossfade speed in % of full weight per second
Character::Character(Ogre::SceneManager *scnMgr, const Ogre::String &model,
Ogre::Bullet::DynamicsWorld *world)
: mModelName(model)
, mScnMgr(scnMgr)
, mPivotPitch(0)
, mVerticalVelocity(0)
, mAnimID(ANIM_NONE)
, mRunning(false)
, mCollisionShape(nullptr)
, mGhostObject(nullptr)
, mWorld(world)
, mGoalDirection(0, 0, 0)
, mUpdate(false)
{
setupBody();
setupAnimations();
}
Character::~Character()
{
}
bool Character::frameStarted(const Ogre::FrameEvent &evt)
{
return true;
}
bool Character::frameRenderingQueued(const Ogre::FrameEvent &evt)
{
if (mUpdate) {
updateBody(evt.timeSinceLastFrame);
updateAnimations(evt.timeSinceLastFrame);
if (evt.timeSinceLastFrame > 0)
updateRootMotion(evt.timeSinceLastFrame);
}
return true;
}
bool Character::frameEnded(const Ogre::FrameEvent &evt)
{
return true;
}
void Character::setupBody()
{
mBodyEnt = mScnMgr->createEntity(mModelName);
mBodyNode = mScnMgr->getRootSceneNode()->createChildSceneNode();
mBodyNode->attachObject(mBodyEnt);
mSkeleton = mBodyEnt->getSkeleton();
// mRigidBody = world->addCharacter(mBodyEnt, 0);
// mCollisionShape = static_cast<btCompoundShape *>(mRigidBody->getCollisionShape());
mGhostObject = new btPairCachingGhostObject();
mCollisionShape = new btCompoundShape;
mGhostObject->setCollisionShape(mCollisionShape);
{
btVector3 inertia(0, 0, 0);
// mCollisionShape = new btCompoundShape();
btScalar height = 1.0f;
btScalar radius = 0.3f;
btCapsuleShape *shape =
new btCapsuleShape(radius, 2 * height - 2 * radius);
btTransform transform;
transform.setIdentity();
transform.setOrigin(btVector3(0, 1, 0));
static_cast<btCompoundShape *>(mCollisionShape)
->addChildShape(transform, shape);
btScalar masses[1] = { 0 };
btTransform principal;
static_cast<btCompoundShape *>(mCollisionShape)
->calculatePrincipalAxisTransform(masses, principal,
inertia);
}
mGhostObject->setCollisionFlags(
btCollisionObject::CF_KINEMATIC_OBJECT /* |
btCollisionObject::CF_NO_CONTACT_RESPONSE */);
mGhostObject->setActivationState(DISABLE_DEACTIVATION);
Ogre::Bullet::KinematicMotionSimple *controller =
new Ogre::Bullet::KinematicMotionSimple(mGhostObject,
mBodyNode);
mWorld->attachCollisionObject(mGhostObject, mBodyEnt,
btBroadphaseProxy::AllFilter,
btBroadphaseProxy::AllFilter);
mWorld->getBtWorld()->addAction(controller);
assert(mCollisionShape);
#if 0
if (mRigidBody->getMass() == 0) {
#if 0
mRigidBody->setCollisionFlags(mRigidBody->getCollisionFlags()
| btCollisionObject::CF_KINEMATIC_OBJECT
| btCollisionObject::CF_NO_CONTACT_RESPONSE
);
#endif
#if 0
mGhostObject->setWorldTransform(mRigidBody->getWorldTransform());
WorldData::get_singleton()->getBtWorld()
->getBroadphase()->getOverlappingPairCache()
->setInternalGhostPairCallback(new btGhostPairCallback());
#endif
}
#endif
#if 0
mRigidBody->setActivationState(DISABLE_DEACTIVATION);
#endif
#if 0
{
Ogre::Entity *e2 = mScnMgr->createEntity("normal-male.glb");
Ogre::SceneNode *e2node = mScnMgr->getRootSceneNode()->createChildSceneNode();
e2node->attachObject(e2);
mGhostObject = WorldData::get_singleton()->addGhostObject(e2, mCollisionShape);
mController = new btKinematicCharacterController(mGhostObject, mCollisionShape, 0.5f);
WorldData::get_singleton()->getBtWorld()->addAction(mController);
}
#endif
assert(mSkeleton->hasBone("Root"));
mRootBone = mSkeleton->getBone("Root");
assert(mRootBone);
}
void Character::setupAnimations()
{
int i, j;
mSkeleton->setBlendMode(Ogre::ANIMBLEND_CUMULATIVE);
Ogre::String animNames[NUM_ANIMS] = { "idle", "walking", "running" };
for (i = 0; i < NUM_ANIMS; i++) {
mAnims[i] = mBodyEnt->getAnimationState(animNames[i]);
mAnims[i]->setLoop(true);
mAnims[i]->setEnabled(true);
mAnims[i]->setWeight(0);
mFadingIn[i] = false;
mFadingOut[i] = false;
mSkelAnimations[i] = mSkeleton->getAnimation(animNames[i]);
for (const auto &it : mSkelAnimations[i]->_getNodeTrackList()) {
Ogre::NodeAnimationTrack *track = it.second;
Ogre::String trackName =
track->getAssociatedNode()->getName();
if (trackName == "mixamorig:Hips") {
mHipsTracks[i] = track;
} else if (trackName == "Root") {
mRootTracks[i] = track;
// mRootTracks[i]->removeAllKeyFrames();
}
}
Ogre::Vector3 delta = Ogre::Vector3::ZERO;
Ogre::Vector3 motion = Ogre::Vector3::ZERO;
for (j = 0; j < mRootTracks[i]->getNumKeyFrames(); j++) {
Ogre::Vector3 trans = mRootTracks[i]
->getNodeKeyFrame(j)
->getTranslate();
if (j == 0)
delta = trans;
else
delta = trans - motion;
mRootTracks[i]->getNodeKeyFrame(j)->setTranslate(delta);
motion = trans;
}
}
#if 0
for(i = 0; i < NUM_ANIMS - 1; i++) {
// need to cache
int j;
Ogre::String animName = mAnims[i]->getAnimationName();
Ogre::Animation *anim = mSkeleton->getAnimation(animName);
Ogre::NodeAnimationTrack *hips_track = nullptr, *root_track = nullptr;
Ogre::Node *root_node = nullptr;
for (const auto& it : anim->_getNodeTrackList()) {
Ogre::NodeAnimationTrack* track = it.second;
Ogre::String trackName = track->getAssociatedNode()->getName();
std::cout << animName << " track: " << trackName << "\n";
if (trackName == "mixamorig:Hips")
hips_track = track;
else if (trackName == "Root") {
root_track = track;
root_node = track->getAssociatedNode();
}
}
assert(false);
root_track->removeAllKeyFrames();
std::cout << hips_track << " " << root_track << "\n";
std::cout << hips_track->getNumKeyFrames() << " " << root_track->getNumKeyFrames() << "\n";
assert(hips_track && root_track);
Ogre::Vector3 delta = Ogre::Vector3::ZERO;
for(j = 0; j < hips_track->getNumKeyFrames(); j++) {
float timePos = hips_track->getNodeKeyFrame(j)->getTime();
Ogre::Vector3 trans = hips_track->getNodeKeyFrame(j)->getTranslate();
Ogre::Vector3 hips_trans(0, 0, 0);
Ogre::Vector3 root_trans(0, 0, 0);
hips_track->getNodeKeyFrame(j)->setTranslate(hips_trans);
Ogre::TransformKeyFrame *nk = root_track->createNodeKeyFrame(timePos);
nk->setTranslate(root_trans - delta);
nk->setScale(Ogre::Vector3(1, 1, 1));
nk->setRotation(Ogre::Quaternion());
std::cout << animName << " delta: " << j << " " << timePos << " " << root_trans - delta << "\n";
delta = root_trans;
}
for(j = 0; j < root_track->getNumKeyFrames(); j++) {
float timePos = hips_track->getNodeKeyFrame(j)->getTime();
Ogre::Vector3 root_trans = hips_track->getNodeKeyFrame(j)->getTranslate();
std::cout << animName << " delta: root: " << j << " " << timePos << " " << root_trans << "\n";
}
}
// assert(false);
#endif
setAnimation(ANIM_IDLE);
}
void Character::updateBody(Ogre::Real delta)
{
Ogre::Quaternion toGoal =
mBodyNode->getOrientation().zAxis().getRotationTo(
mGoalDirection);
// calculate how much the character has to turn to face goal direction
Ogre::Real yawToGoal = toGoal.getYaw().valueDegrees();
// this is how much the character CAN turn this frame
Ogre::Real yawAtSpeed =
yawToGoal / Ogre::Math::Abs(yawToGoal) * delta * TURN_SPEED;
// reduce "turnability" if we're in midair
// if (mBaseAnimID == ANIM_JUMP_LOOP) yawAtSpeed *= 0.2f;
if (yawToGoal < 0)
yawToGoal = std::min<Ogre::Real>(
0,
std::max<Ogre::Real>(
yawToGoal,
yawAtSpeed)); //yawToGoal = Math::Clamp<Real>(yawToGoal, yawAtSpeed, 0);
else if (yawToGoal > 0)
yawToGoal = std::max<Ogre::Real>(
0,
std::min<Ogre::Real>(
yawToGoal,
yawAtSpeed)); //yawToGoal = Math::Clamp<Real>(yawToGoal, 0, yawAtSpeed);
mBodyNode->yaw(Ogre::Degree(yawToGoal));
}
void Character::updateAnimations(Ogre::Real delta)
{
int i, j, k;
Ogre::Real animSpeed = 1;
mTimer += delta;
{
Ogre::Quaternion rot = mBodyNode->getOrientation();
OgreAssert(!Ogre::Math::isNaN(rot.x), "NaN");
OgreAssert(!Ogre::Math::isNaN(rot.y), "NaN");
OgreAssert(!Ogre::Math::isNaN(rot.z), "NaN");
}
if (mAnimID != ANIM_NONE) {
if (mAnimID == ANIM_WALK)
mAnims[mAnimID]->addTime(delta * 1.0f);
else
mAnims[mAnimID]->addTime(delta * animSpeed);
}
fadeAnimations(delta);
}
void Character::updateRootMotion(Ogre::Real delta)
{
Ogre::Vector3 boneMotion = mRootBone->getPosition();
OgreAssert(delta > 0.0f, "Zero delta");
Ogre::Vector3 motion = boneMotion - rootMotion;
if (motion.squaredLength() > 0.1f * 0.1f)
motion = Ogre::Vector3();
rootMotion = boneMotion;
#if 0
float mass = mRigidBody->getMass();
std::cout << "Root bone position: " << boneMotion << "\n";
std::cout << "body mass: " << mass << "\n";
#endif
/* Kinematic motion */
Ogre::Quaternion rot = mBodyNode->getOrientation();
// Ogre::Vector3 gravity(0, -9.8, 0);
Ogre::Vector3 gravity(0, 0, 0);
Ogre::Vector3 velocity = rot * boneMotion / delta;
velocity += gravity * delta;
Ogre::Vector3 rotMotion = velocity * delta;
btTransform from(convert(mBodyNode->getOrientation()),
convert(mBodyNode->getPosition()));
mBodyNode->setPosition(mBodyNode->getPosition() + rotMotion);
// WorldData::get_singleton()->getWorld()->testBodyMotion(mRigidBody, from, Ogre::Bullet::convert(rotMotion), true,
// nullptr, false, std::set<btCollisionObject *>());
}
void Character::fadeAnimations(Ogre::Real delta)
{
int i;
for (i = 0; i < NUM_ANIMS; i++) {
if (mFadingIn[i]) {
// slowly fade this animation in until it has full weight
Ogre::Real newWeight = mAnims[i]->getWeight() +
delta * ANIM_FADE_SPEED;
mAnims[i]->setWeight(
Ogre::Math::Clamp<Ogre::Real>(newWeight, 0, 1));
if (newWeight >= 1)
mFadingIn[i] = false;
} else if (mFadingOut[i]) {
// slowly fade this animation out until it has no weight, and then disable it
Ogre::Real newWeight = mAnims[i]->getWeight() -
delta * ANIM_FADE_SPEED;
mAnims[i]->setWeight(
Ogre::Math::Clamp<Ogre::Real>(newWeight, 0, 1));
if (newWeight <= 0) {
mAnims[i]->setEnabled(false);
mFadingOut[i] = false;
}
}
}
}
void Character::setAnimation(AnimID id, bool reset)
{
assert(id >= 0 && id < NUM_ANIMS);
if (mAnimID != ANIM_NONE) {
mFadingIn[mAnimID] = false;
mFadingOut[mAnimID] = true;
}
mAnimID = id;
if (id != ANIM_NONE) {
mAnims[id]->setEnabled(true);
mAnims[id]->setWeight(0);
mFadingOut[id] = false;
mFadingIn[id] = true;
if (reset)
mAnims[id]->setTimePosition(0);
}
}
bool Character::act_run()
{
if (mAnimID == ANIM_IDLE)
setAnimation(ANIM_RUN, true);
else if (mAnimID == ANIM_WALK)
setAnimation(ANIM_RUN);
return true;
}
bool Character::act_walk()
{
if (mAnimID == ANIM_IDLE)
setAnimation(ANIM_WALK, true);
else if (mAnimID == ANIM_RUN)
setAnimation(ANIM_WALK);
return true;
}
bool Character::act_idle()
{
setAnimation(ANIM_IDLE);
return true;
}
bool Character::isRunning()
{
return mAnimID == ANIM_RUN;
}
bool Character::isWalking()
{
return mAnimID == ANIM_WALK;
}
Ogre::Vector3 Character::getPosition()
{
return mBodyNode->_getDerivedPosition();
}

View File

@@ -1,128 +0,0 @@
#include <Ogre.h>
#include <OgreFrameListener.h>
#include <OgreBullet.h>
class btCompoundShape;
class btPairCachingGhostObject;
class Character : public Ogre::FrameListener {
enum AnimID {
ANIM_IDLE = 0,
ANIM_WALK,
ANIM_RUN,
NUM_ANIMS,
ANIM_NONE = NUM_ANIMS
};
Ogre::String mModelName;
Ogre::Node *mRootBone;
Ogre::SceneManager *mScnMgr;
Ogre::SceneNode *mCameraPivot;
Ogre::SceneNode *mCameraGoal, *mBodyNode;
Ogre::Entity *mBodyEnt;
Ogre::Real mPivotPitch;
Ogre::Real mVerticalVelocity;
Ogre::Vector3 mGoalDirection; // actual intended direction in world-space
Ogre::AnimationState *mAnims[NUM_ANIMS]; // master animation list
Ogre::Animation *mSkelAnimations[NUM_ANIMS];
Ogre::NodeAnimationTrack *mHipsTracks[NUM_ANIMS];
Ogre::NodeAnimationTrack *mRootTracks[NUM_ANIMS];
AnimID mAnimID;
bool mFadingIn[NUM_ANIMS]; // which animations are fading in
bool mFadingOut[NUM_ANIMS]; // which animations are fading out
Ogre::Real
mTimer; // general timer to see how long animations have been playing
Ogre::Skeleton *mSkeleton;
bool mRunning;
Ogre::Vector3 rootMotion;
Ogre::Quaternion rootRotation;
// btRigidBody *mRigidBody;
btCompoundShape *mCollisionShape;
btPairCachingGhostObject *mGhostObject;
Ogre::Bullet::DynamicsWorld *mWorld;
bool mUpdate;
public:
Character(Ogre::SceneManager *scnMgr, const Ogre::String &modelName,
Ogre::Bullet::DynamicsWorld *world);
~Character();
private:
void setupBody();
void setupAnimations();
public:
bool frameStarted(const Ogre::FrameEvent &evt) override;
bool frameEnded(const Ogre::FrameEvent &evt) override;
bool frameRenderingQueued(const Ogre::FrameEvent &evt) override;
private:
void updateBody(Ogre::Real deltaTime);
void updateAnimations(Ogre::Real deltaTime);
void updateRootMotion(Ogre::Real deltaTime);
void fadeAnimations(Ogre::Real deltaTime);
void setAnimation(AnimID id, bool reset = false);
inline btQuaternion convert(const Ogre::Quaternion &q)
{
return btQuaternion(q.x, q.y, q.z, q.w);
}
inline btVector3 convert(const Ogre::Vector3 &v)
{
return btVector3(v.x, v.y, v.z);
}
inline btTransform convert(const Ogre::Quaternion &q,
const Ogre::Vector3 &v)
{
btQuaternion mq = convert(q);
btVector3 mv = convert(v);
return btTransform(mq, mv);
}
inline Ogre::Quaternion convert(const btQuaternion &q)
{
return Ogre::Quaternion(q.w(), q.x(), q.y(), q.z());
}
inline Ogre::Vector3 convert(const btVector3 &v)
{
return Ogre::Vector3(v.x(), v.y(), v.z());
}
inline void convert(const btTransform &from, Ogre::Quaternion &q,
Ogre::Vector3 &v)
{
q = convert(from.getRotation());
v = convert(from.getOrigin());
}
public:
bool isIdle()
{
return mAnimID == ANIM_IDLE;
}
bool act_run();
bool act_walk();
bool act_idle();
bool isRunning();
bool isWalking();
bool isMoving()
{
return isRunning() || isWalking();
}
void setGoalDirection(const Ogre::Vector3 &goalDirection)
{
mGoalDirection = goalDirection;
}
Ogre::Vector3 getGoalDirection()
{
return mGoalDirection;
}
Ogre::Vector3 getPosition();
void enableUpdates()
{
mUpdate = true;
}
void disableUpdates()
{
mUpdate = false;
}
bool getUpdates()
{
return mUpdate;
}
};

View File

@@ -0,0 +1,15 @@
#include "AppModule.h"
namespace ECS
{
AppModule::AppModule(flecs::world &ecs)
{
ecs.module<AppModule>();
ecs.component<App>()
.on_add([](App &app) {
app.mInput = nullptr;
app.mGuiOverlay = nullptr;
app.listeners.clear();
})
.add(flecs::Singleton);
}
}

25
src/gamedata/AppModule.h Normal file
View File

@@ -0,0 +1,25 @@
#ifndef _APP_MODULE_H_
#define _APP_MODULE_H_
#include <flecs.h>
#include <vector>
namespace Ogre
{
class ImGuiOverlay;
}
namespace OgreBites
{
class InputListenerChain;
class InputListener;
}
namespace ECS
{
struct App {
Ogre::ImGuiOverlay *mGuiOverlay;
OgreBites::InputListenerChain *mInput;
std::vector<OgreBites::InputListener *> listeners;
};
struct AppModule {
AppModule(flecs::world &ecs);
};
}
#endif

View File

@@ -1,7 +1,15 @@
#include <iostream>
#include <Ogre.h>
#include <OgreMeshManager.h>
#include <pugixml.hpp>
#include "loader.h"
#include "Components.h"
#include "GameData.h"
#include "PhysicsModule.h"
#include "EventModule.h"
#include "EventTriggerModule.h"
#include "CharacterModule.h"
#include "WaterModule.h"
#include "BoatModule.h"
namespace ECS
@@ -11,13 +19,293 @@ BoatModule::BoatModule(flecs::world &ecs)
{
ecs.module<BoatModule>();
ecs.component<BoatBase>();
ecs.component<BoatBody>();
ecs.system<const EngineData, BoatType>("CreateBoat")
ecs.component<BoatType>();
ecs.component<SpawnBoat>();
ecs.component<BoatCurrentActuator>();
ecs.import <EventModule>();
ecs.import <EventTriggerModule>();
ecs.import <CharacterModule>();
ecs.import <WaterModule>();
ecs.observer<const EngineData, const BoatType>("CreateBoat")
.event(flecs::OnSet)
.without<BoatBase>()
.without<SpawnBoat>()
.each([&](flecs::entity e, const EngineData &eng,
const BoatType &type) {
e.add<SpawnBoat>();
e.set<EventData>({});
});
ecs.system<const EngineData, const BoatType>("CreateBoatS")
.kind(flecs::OnUpdate)
.without<BoatBase>()
.with<WaterReady>()
.with<SpawnBoat>()
.each([&](flecs::entity e, const EngineData &eng,
const BoatType &type) {
if (type.resourceName.find(".scene") !=
std::string::npos) {
BoatBase &boat = e.ensure<BoatBase>();
int i;
std::vector<Ogre::SceneNode *> colliderTarget;
boat.mNode =
ECS::get<EngineData>()
.mScnMgr->getRootSceneNode()
->createChildSceneNode(
type.position,
type.orientation);
auto pnodes = boat.mNode->getChildren();
for (i = 0; i < pnodes.size(); i++) {
Ogre::SceneNode *pnode =
static_cast<Ogre::SceneNode *>(
pnodes[i]);
Ogre::Any any =
pnode->getUserObjectBindings()
.getUserAny("type");
if (!any.has_value())
continue;
Ogre::String obj_type =
Ogre::any_cast<Ogre::String>(
any);
if (obj_type == "hull-collider") {
/* FIXME */
}
}
{
Ogre::SceneNode *attachment =
eng.mScnMgr->getRootSceneNode()
->createChildSceneNode();
Ogre::DataStreamPtr scene =
Ogre::ResourceGroupManager::getSingleton()
.openResource(
type.resourceName,
Ogre::ResourceGroupManager::
AUTODETECT_RESOURCE_GROUP_NAME);
SceneLoader loader;
loader.load(scene, "General",
attachment, e);
// attachment->loadChildren(type.resourceName);
std::vector<Ogre::Node *> v =
attachment->getChildren();
OgreAssert(v.size() == 1,
"Bad root nodes count in " +
type.resourceName);
Ogre::Any any =
static_cast<Ogre::SceneNode *>(
v[0])
->getUserObjectBindings()
.getUserAny("type");
OgreAssert(any.has_value(),
"no \"type\" costom prop");
Ogre::String obj_type =
Ogre::any_cast<Ogre::String>(
any);
std::cout << "type: " << obj_type
<< std::endl;
OgreAssert(obj_type == "boat",
"not a boat");
boat.mNode =
static_cast<Ogre::SceneNode *>(
v[0]);
boat.mNode->_setDerivedPosition(
type.position);
boat.mNode->_setDerivedOrientation(
type.orientation);
boat.mEnt = static_cast<Ogre::Entity *>(
boat.mNode->getAttachedObject(
std::to_string(
(int)e.raw_id()) +
"/boat"));
loader.setupPhysicsBody(boat.mNode,
boat.mEnt, e);
e.remove<SpawnBoat>();
e.modified<BoatBase>();
}
// BoatBody &body = e.ensure<BoatBody>();
// e.modified<BoatBase>();
}
// ECS::get_mut<ECS::EngineData>().enableDbgDraw = true;
// ECS::modified<ECS::EngineData>();
// e.modified<BoatBody>();
});
ecs.observer<const BoatBase, EventData>("HandleBoatEvents")
.event(flecs::OnSet)
.each([](flecs::entity e, const BoatBase &boat,
EventData &evt) {
for (auto ev : evt.events) {
std::cout << "event: " << ev.event << " "
<< ev.sender.name() << " "
<< ev.e1.name() << " " << ev.e2.name()
<< std::endl;
if (ev.event == "actuator_created") {
continue;
} else if (ev.event == "actuator_enter") {
if (e.has<BoatCurrentActuator>())
continue;
if (ev.sender.has<EventTrigger>()) {
if (ev.e2.has<Player>())
PhysicsModule::
controlPhysics(
ev.e2,
false);
const EventTrigger &trigger =
ev.sender.get<
EventTrigger>();
Ogre::SceneNode *node =
trigger.node;
Ogre::Any animationAny =
node->getUserObjectBindings()
.getUserAny(
"trigger_animation");
if (animationAny.has_value()) {
Ogre::String animation =
Ogre::any_cast<
Ogre::String>(
animationAny);
ev.e2.set<
CharacterVelocity>(
{ { 0, 0, 0 },
{ 0, 0,
0 } });
if (animation.length() >
0)
ev.e2.set<
CharacterInActuator>(
{ animation,
{ 0,
0,
0 } });
else
ev.e2.remove<
CharacterInActuator>();
}
Ogre::SceneNode *target_node =
nullptr;
Ogre::Any targetAny =
node->getUserObjectBindings()
.getUserAny(
"target");
if (targetAny.has_value()) {
OgreAssert(
targetAny
.has_value(),
"need target");
target_node = Ogre::any_cast<
Ogre::SceneNode
*>(
targetAny);
Ogre::Vector3 position =
target_node
->_getDerivedPosition();
Ogre::Quaternion orientation =
target_node
->_getDerivedOrientation();
if (ev.e2.has<
CharacterBase>()) {
ev.e2.get_mut<
CharacterBase>()
.mBodyNode
->_setDerivedPosition(
position);
ev.e2.get_mut<
CharacterBase>()
.mBodyNode
->_setDerivedOrientation(
orientation);
}
}
e.set<BoatCurrentActuator>(
{ ev.event,
ev.sender });
}
} else if (ev.event == "actuator_forward") {
/* */
if (e.has<BoatCurrentActuator>()) {
if (e.get<BoatCurrentActuator>()
.actuator ==
"actuator_enter")
ev.e2.set<
CharacterInActuator>(
{ "swimming-edge-climb",
{ 0, 0,
0 } });
}
} else if (ev.event == "actuator_backward") {
/* */
} else if (ev.event ==
"animation:swimming-edge-climb:end") {
ev.e2.remove<CharacterInActuator>();
PhysicsModule::controlPhysics(ev.e2,
true);
ev.e2.remove<InTrigger>(ev.e1);
ev.e1.remove<TriggeredBy>(ev.e2);
ev.e2.add<CharacterGravity>();
ev.e2.add<CharacterBuoyancy>();
e.remove<BoatCurrentActuator>();
} else if (ev.event == "boat_control_enter") {
Ogre::SceneNode *captainSeat = nullptr;
std::vector<Ogre::Node *> children =
boat.mNode->getChildren();
for (auto child : children) {
Ogre::Any nameAny =
child->getUserObjectBindings()
.getUserAny(
"name");
if (nameAny.has_value()) {
Ogre::String name =
Ogre::any_cast<
Ogre::String>(
nameAny);
std::cout << "name: "
<< name
<< std::endl;
if (name ==
"captain_seat") {
captainSeat = static_cast<
Ogre::SceneNode
*>(
child);
break;
}
}
}
OgreAssert(captainSeat, "seats");
PhysicsModule::controlPhysics(ev.e2,
false);
ev.e2.set<CharacterVelocity>(
{ { 0, 0, 0 }, { 0, 0, 0 } });
ev.e2.set<CharacterInActuator>(
{ "sitting", { 0, 0, 0 } });
Ogre::Vector3 position =
captainSeat
->_getDerivedPosition();
Ogre::Quaternion orientation =
captainSeat
->_getDerivedOrientation();
if (ev.e2.has<CharacterBase>()) {
ev.e2.get_mut<CharacterBase>()
.mBodyNode
->_setDerivedPosition(
position);
ev.e2.get_mut<CharacterBase>()
.mBodyNode
->_setDerivedOrientation(
orientation);
}
} else if (ev.event == "boat_control_exit") {
} else if (ev.event == "actuator_exit") {
/* */
} else
OgreAssert(false, "event");
}
evt.events.clear();
});
#if 0
ecs.system<const EngineData, BoatType, Body2Entity>("CreateBoat")
.kind(flecs::OnUpdate)
.without<BoatBase>()
.without<BoatBody>()
.each([](flecs::entity e, const EngineData &eng,
BoatType &type) {
.each([](flecs::entity e, const EngineData &eng, BoatType &type,
Body2Entity &b2e) {
BoatBase &boat = e.ensure<BoatBase>();
if (type.resourceName.find(".glb") !=
std::string::npos) {
@@ -30,35 +318,67 @@ BoatModule::BoatModule(flecs::world &ecs)
type.orientation);
boat.mNode->attachObject(boat.mEnt);
#if 0
BoatBody &body = e.ensure<BoatBody>();
body.body =
ECS::get<EngineData>()
.mWorld->addRigidBody(
0, boat.mEnt,
Ogre::Bullet::CT_HULL,
Ogre::Bullet::CT_TRIMESH,
nullptr, 2, 0x7fffffff);
b2e.entities[body.body] = e;
#endif
} else if (type.resourceName.find(".scene") !=
std::string::npos) {
int i;
std::vector<Ogre::SceneNode *> colliderTarget;
boat.mNode =
ECS::get<EngineData>()
.mScnMgr->getRootSceneNode()
->createChildSceneNode(
type.position,
type.orientation);
auto pnodes = boat.mNode->getChildren();
for (i = 0; i < pnodes.size(); i++) {
Ogre::SceneNode *pnode =
static_cast<Ogre::SceneNode *>(
pnodes[i]);
Ogre::Any any =
pnode->getUserObjectBindings()
.getUserAny("type");
if (!any.has_value())
continue;
Ogre::String obj_type =
Ogre::any_cast<Ogre::String>(
any);
if (obj_type == "hull-collider") {
/* FIXME */
}
}
Ogre::SceneNode *attachment =
eng.mScnMgr->getRootSceneNode()
->createChildSceneNode();
attachment->loadChildren(type.resourceName);
Ogre::DataStreamPtr scene =
Ogre::ResourceGroupManager::getSingleton()
.openResource(
type.resourceName,
Ogre::ResourceGroupManager::
AUTODETECT_RESOURCE_GROUP_NAME);
SceneLoader loader;
loader.load(scene, "General", attachment, e);
// attachment->loadChildren(type.resourceName);
std::vector<Ogre::Node *> v =
attachment->getChildren();
int i;
OgreAssert(v.size() == 1, "Bad nodes count");
OgreAssert(v.size() == 1,
"Bad root nodes count in " +
type.resourceName);
Ogre::Any any =
static_cast<Ogre::SceneNode *>(v[0])
->getUserObjectBindings()
.getUserAny("type");
OgreAssert(any.has_value(),
"bas node type costom prop");
"no \"type\" costom prop");
Ogre::String obj_type =
Ogre::any_cast<Ogre::String>(any);
std::cout << "type: " << obj_type << std::endl;
@@ -69,23 +389,88 @@ BoatModule::BoatModule(flecs::world &ecs)
boat.mNode->_setDerivedOrientation(
type.orientation);
boat.mEnt = static_cast<Ogre::Entity *>(
boat.mNode->getAttachedObject("boat"));
boat.mNode->getAttachedObject(
std::to_string((int)e.raw_id()) +
"/boat"));
loader.setupPhysicsBody(boat.mNode, boat.mEnt);
BoatBody &body = e.ensure<BoatBody>();
#if 0
Ogre::Any bodyA =
boat.mNode->getUserObjectBindings()
.getUserAny("bodyPointer");
if (bodyA.has_value())
body.body =
Ogre::any_cast<btRigidBody *>(
bodyA);
#if 0
body.body =
ECS::get<EngineData>()
.mWorld->addRigidBody(
0, boat.mEnt,
Ogre::Bullet::CT_HULL,
Ogre::Bullet::CT_TRIMESH,
nullptr, 2, 0x7fffffff);
#if 0
boat.mEnt = eng.mScnMgr->getEntity("boat");
boat.mNode = boat.mEnt->get
/* no need to attach anything */
BoatBody &body =
e.ensure<BoatBody>();
#endif
OgreAssert(body.body, "No body :()");
b2e.entities[body.body] = e;
#endif
std::vector<Ogre::Node *> slots =
boat.mNode->getChildren();
for (i = 0; i < slots.size(); i++) {
Ogre::Any any =
static_cast<Ogre::SceneNode *>(
slots[i])
->getUserObjectBindings()
.getUserAny("type");
if (!any.has_value())
continue;
Ogre::String obj_type =
Ogre::any_cast<Ogre::String>(
any);
std::cout << "child type: " << obj_type
<< std::endl;
}
if (slots.size() > 0) {
ObjectSlots &vs =
e.ensure<ObjectSlots>();
for (i = 0; i < slots.size(); i++) {
Ogre::Any any =
static_cast<Ogre::SceneNode
*>(
slots[i])
->getUserObjectBindings()
.getUserAny(
"type");
if (!any.has_value())
continue;
Ogre::String obj_type =
Ogre::any_cast<
Ogre::String>(
any);
any = static_cast<
Ogre::SceneNode *>(
slots[i])
->getUserObjectBindings()
.getUserAny(
"name");
if (!any.has_value())
continue;
Ogre::String obj_name =
Ogre::any_cast<
Ogre::String>(
any);
vs.slots[obj_name] = {
obj_type,
static_cast<Ogre::SceneNode
*>(
slots[i])
};
}
}
}
// ECS::get_mut<ECS::EngineData>().enableDbgDraw = true;
// ECS::modified<ECS::EngineData>();
e.modified<BoatBody>();
});
#endif
}
}
}

View File

@@ -18,9 +18,11 @@ struct BoatBase {
Ogre::Entity *mEnt;
Ogre::SceneNode *mNode;
};
struct BoatBody {
btRigidBody *body;
struct BoatCurrentActuator {
Ogre::String actuator;
flecs::entity actuator_e;
};
struct SpawnBoat {};
struct BoatModule {
BoatModule(flecs::world &ecs);
};

View File

@@ -1,6 +1,13 @@
project(gamedata)
set(CMAKE_CXX_STANDARD 17)
find_package(OGRE REQUIRED COMPONENTS Bites Bullet Paging Terrain Overlay CONFIG)
add_library(GameData STATIC GameData.cpp CharacterModule.cpp WaterModule.cpp SunModule.cpp TerrainModule.cpp GUIModule.cpp LuaData.cpp WorldMapModule.cpp
BoatModule.cpp EventTriggerModule.cpp)
target_link_libraries(GameData PUBLIC OgreMain OgreBites OgreBullet OgrePaging OgreTerrain OgreOverlay flecs::flecs_static lua)
target_include_directories(GameData PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
find_package(Bullet REQUIRED)
add_library(GameData STATIC GameData.cpp CharacterModule.cpp WaterModule.cpp SunModule.cpp TerrainModule.cpp
GUIModule.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)
target_link_libraries(GameData PUBLIC lua flecs::flecs_static OgreMain OgreBites
OgrePaging OgreTerrain OgreOverlay
PRIVATE sceneloader world-build physics)
target_include_directories(GameData PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${BULLET_INCLUDE_DIR} ../luaaa)
target_compile_definitions(GameData PRIVATE FLECS_CPP_NO_AUTO_REGISTRATION)

View File

@@ -0,0 +1,744 @@
#include <iostream>
#include "Components.h"
#include "EventTriggerModule.h"
#include "CharacterModule.h"
#include "PhysicsModule.h"
#include "CharacterAnimationModule.h"
#include "EventModule.h"
#include "TerrainModule.h"
#include "WaterModule.h"
#include "world-build.h"
namespace ECS
{
CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
{
ecs.module<CharacterAnimationModule>();
ecs.component<AnimationControl>();
ecs.import <EventModule>();
ecs.import <TerrainModule>();
ecs.import <WaterModule>();
ecs.import <PhysicsModule>();
ecs.system<const CharacterBase, AnimationControl>("HandleAnimations")
.kind(flecs::OnUpdate)
.each([this](flecs::entity e, const CharacterBase &ch,
AnimationControl &anim) {
if (!anim.configured && ch.mSkeleton) {
int i, j;
e.set<EventData>({});
ch.mSkeleton->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++) {
Animation *animation = new Animation(
ch.mSkeleton,
ch.mBodyEnt->getAnimationState(
animNames[i]),
ch.mSkeleton->getAnimation(
animNames[i]),
e);
#ifdef VDEBUG
std::cout
<< "animation: " << animNames[i]
<< std::endl;
#endif
animation->setLoop(true);
anim.mAnimationSystem->add_animation(
animNames[i], animation);
}
anim.mAnimationSystem
->builder()
/* clang-format off */
->output()
->state_machine(ANIM_FADE_SPEED, "main")
->state("locomotion")
->state_machine(ANIM_FADE_SPEED, "locomotion-state")
->state("idle")
->animation("idle")
->end()
->state("walking")
->animation("walking")
->end()
->state("running")
->animation("running")
->end()
->state("treading_water")
->animation("treading_water")
->end()
->state("swimming")
->animation("swimming")
->end()
->state("swimming-fast")
->speed(20.0f)
->animation("swimming")
->end()
->end()
->end()
->end()
->state("actuator")
->state_machine(ANIM_FADE_SPEED * 10.0f, "actuator-state")
->state("hanging-idle")
->animation("hanging-idle")
->end()
->state("swimming-hold-edge")
->animation("swimming-hold-edge")
->end()
->state("swimming-edge-climb")
->animation("swimming-edge-climb")
->trigger(e, "end_of_climb", 0.99f, "animation:swimming-edge-climb:end")
->end()
->state("hanging-climb")
->animation("hanging-climb")
->trigger(e, "end_of_climb2", 0.99f, "animation:hanging-climb:end")
->end()
->state("idle")
->animation("idle-act")
->end()
->state("pass-character")
->animation("pass-character")
->trigger(e, "pass-character", 0.99f, "animation:pass-character:end")
->end()
->state("character-talk")
->animation("character-talk")
->end()
->state("sitting")
->animation("sitting-chair")
->end()
->state("sitting-ground")
->animation("sitting-ground")
->end()
->transition_end("swimming-edge-climb", "idle")
->transition_end("hanging-climb", "idle")
->transition_end("pass-character", "idle")
->end()
->end()
->end();
/* clang-format on */
anim.mAnimationSystem
->get<AnimationNodeStateMachine>("main")
->setAnimation("locomotion", true);
anim.mAnimationSystem
->get<AnimationNodeStateMachine>(
"locomotion-state")
->setAnimation("idle", true);
anim.configured = true;
}
});
#if 0
ecs.system<CharacterBase>("RootMotionStart")
.kind(flecs::OnUpdate)
.each([this](flecs::entity e, CharacterBase &ch) {
ch.mBoneMotion = Ogre::Vector3::ZERO;
});
#endif
ecs.system<const EngineData, CharacterBase, AnimationControl>(
"HandleAnimations1")
.kind(flecs::OnUpdate)
.each([this](flecs::entity e, const EngineData &eng,
CharacterBase &ch, AnimationControl &anim) {
float delta = eng.delta;
// ch.mBoneMotion = Ogre::Vector3::ZERO;
bool result = anim.mAnimationSystem->addTime(delta);
if (!ch.mRootBone)
return;
// 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 *>(
anim.mListener)
->getDeltaMotion();
ch.mRootBone->setPosition(Ogre::Vector3::ZERO);
Ogre::Vector3 d = offset - ch.mBonePrevMotion;
ch.mBonePrevMotion = offset;
std::cout << "length: " << d.length() << std::endl;
if (d.squaredLength() > 0.02f * 0.02f)
d = offset;
if (d.squaredLength() > 0.02f * 0.02f)
d = Ogre::Vector3::ZERO;
std::cout << "length2: " << d.length() << std::endl;
OgreAssert(d.length() < 0.5f, "bad offset");
ch.mBoneMotion = d;
#endif
#if 0
if (result) {
if (d.squaredLength() > 0.0f)
ch.mBoneMotion =
ch.mRootBone->getPosition() -
ch.mBoneMotion;
else
ch.mBoneMotion =
ch.mRootBone->getPosition();
} else {
ch.mBoneMotion = ch.mRootBone->getPosition() -
ch.mBoneMotion;
}
#endif
#undef VDEBUG
#ifdef VDEBUG
std::cout << "root motion: " << delta << ": "
<< ch.mBoneMotion << " - "
<< ch.mRootBone->getPosition()
<< " result: " << result << std::endl;
#endif
#undef VDEBUG
#if 0
// ch.mRootBone->setPosition(Ogre::Vector3::ZERO);
ch.mBonePrevMotion = offset;
#endif
});
ecs.system<const EngineData, CharacterBase, CharacterVelocity>(
"HandleRootMotionVelocity")
.kind(flecs::OnUpdate)
.with<TerrainReady>()
.with<WaterReady>()
.each([this](flecs::entity e, const EngineData &eng,
CharacterBase &ch, CharacterVelocity &v) {
if (eng.delta < 0.0000001f)
return;
if (!ch.mBodyNode)
return;
Ogre::Quaternion rot = ch.mBodyNode->getOrientation();
Ogre::Vector3 pos = ch.mBodyNode->getPosition();
Ogre::Vector3 boneMotion = ch.mBoneMotion;
v.velocity = Ogre::Vector3::ZERO;
float safeDelta =
Ogre::Math::Clamp(eng.delta, 0.001f, 0.99f);
#if 0
if (!e.has<CharacterInActuator>()) {
v.velocity = Ogre::Math::lerp(
v.velocity,
rot * boneMotion / safeDelta, 0.99f);
} else {
// v.velocity = rot * boneMotion / safeDelta;
v.velocity = Ogre::Math::lerp(
v.velocity,
rot * boneMotion / safeDelta, 0.99f);
}
#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;
// safety
// std::cout << "velocity: " << v.velocity << std::endl;
v.velocity.x =
Ogre::Math::Clamp(v.velocity.x, -16.0f, 16.0f);
v.velocity.z =
Ogre::Math::Clamp(v.velocity.z, -16.0f, 16.0f);
v.velocity.y =
Ogre::Math::Clamp(v.velocity.y, -10.5f, 10.0f);
#if 0
v.velocity.y = 0.0f;
#endif
});
#if 0
ecs.system<const EngineData, const AnimationControl,
const CharacterBase, CharacterVelocity>("HandleSwimming")
.kind(flecs::OnUpdate)
.with<TerrainReady>()
.with<WaterReady>()
.with<InWater>()
.with<CharacterBuoyancy>()
.without<CharacterDisablePhysics>()
.without<CharacterInActuator>()
.each([this](flecs::entity e, const EngineData &eng,
const AnimationControl &anim,
const CharacterBase &ch, CharacterVelocity &gr) {
if (anim.mAnimationSystem
->get<AnimationNodeStateMachine>(
"locomotion-state")
->getCurrentState() == "swimming") {
float h = Ogre::Math::Clamp(
0.0f - ch.mBodyNode->getPosition().y,
0.0f, 2000.0f);
if (h > 0.05 && h < 2.0f)
gr.gvelocity.y += 0.1f * (h + 1.0f) *
h * eng.delta;
}
});
#endif
ecs.system<const EngineData, CharacterBase, AnimationControl,
CharacterVelocity>("HandleRootMotion")
.kind(flecs::OnUpdate)
.each([this](flecs::entity e, const EngineData &eng,
CharacterBase &ch, AnimationControl &anim,
CharacterVelocity &v) {
if (!ch.mBodyNode)
return;
if (eng.delta < 0.0000001f)
return;
OgreAssert(eng.delta > 0.0f, "Zero delta");
int maxPen = 0;
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")
.kind(flecs::OnUpdate)
.with<Character>()
.without<Player>()
.each([](flecs::entity e, const Input &input,
const CharacterBase &ch, AnimationControl &anim) {
if (!anim.configured)
return;
AnimationNodeStateMachine *state_machine =
anim.mAnimationSystem
->get<AnimationNodeStateMachine>(
"locomotion-state");
Ogre::String current_state =
state_machine->getCurrentState();
Ogre::String next_state = "idle";
if (current_state != "treading_water" &&
ch.is_submerged)
next_state = "treading_water";
if (current_state != "idle" && !ch.is_submerged)
next_state = "idle";
state_machine->setAnimation(next_state);
});
ecs.system<const CharacterBase, const CharacterInActuator,
AnimationControl>("HandlePlayerAnimationsActuator")
.kind(flecs::OnUpdate)
.with<Character>()
.each([](flecs::entity e, const CharacterBase &ch,
const CharacterInActuator &inact,
AnimationControl &anim) {
if (!anim.configured)
return;
AnimationNodeStateMachine *main_sm =
anim.mAnimationSystem
->get<AnimationNodeStateMachine>(
"main");
AnimationNodeStateMachine *actuator_sm =
anim.mAnimationSystem
->get<AnimationNodeStateMachine>(
"actuator-state");
Ogre::String current_state = main_sm->getCurrentState();
if (current_state != "actuator")
main_sm->setAnimation("actuator", true);
actuator_sm->setAnimation(inact.animationState, true);
});
ecs.system<const CharacterBase, AnimationControl>(
"HandlePlayerAnimationsNoActuator")
.kind(flecs::OnUpdate)
.with<Character>()
.without<CharacterInActuator>()
.each([](flecs::entity e, const CharacterBase &ch,
AnimationControl &anim) {
if (!anim.configured)
return;
AnimationNodeStateMachine *main_sm =
anim.mAnimationSystem
->get<AnimationNodeStateMachine>(
"main");
Ogre::String current_state = main_sm->getCurrentState();
if (current_state != "locomotion")
main_sm->setAnimation("locomotion", true);
});
ecs.system<const Input, const CharacterBase, AnimationControl>(
"HandlePlayerAnimations")
.kind(flecs::OnUpdate)
.with<Character>()
.with<Player>()
.without<CharacterInActuator>()
.each([](flecs::entity e, const Input &input,
const CharacterBase &ch, AnimationControl &anim) {
if (!anim.configured)
return;
AnimationNodeStateMachine *state_machine =
anim.mAnimationSystem
->get<AnimationNodeStateMachine>(
"locomotion-state");
Ogre::String current_state =
state_machine->getCurrentState();
bool controls_idle = input.motion.zeroLength();
bool anim_is_idle = current_state == "idle" ||
current_state == "treading_water";
bool anim_is_walking = current_state == "walking";
bool anim_is_running = current_state == "running";
bool anim_is_swimming_slow = current_state ==
"swimming";
bool anim_is_swimming_fast = current_state ==
"swimming-fast";
bool anim_is_swimming = anim_is_swimming_slow ||
anim_is_swimming_fast;
bool anim_is_motion = anim_is_walking ||
anim_is_running ||
anim_is_swimming;
bool start_motion = !controls_idle && anim_is_idle;
bool end_motion = controls_idle && !anim_is_idle;
Ogre::String next_state = current_state;
if (controls_idle && anim_is_idle) {
if (current_state != "treading_water" &&
ch.is_submerged)
next_state = "treading_water";
else if (current_state != "idle" &&
!ch.is_submerged)
next_state = "idle";
state_machine->setAnimation(next_state);
} else if (start_motion) {
if (ch.is_submerged) {
if (input.fast)
next_state = "swimming-fast";
else
next_state = "swimming";
} else {
if (input.fast)
next_state = "running";
else
next_state = "walking";
}
state_machine->setAnimation(next_state, true);
} else if (end_motion) {
if (ch.is_submerged)
state_machine->setAnimation(
"treading_water");
else
state_machine->setAnimation("idle");
} else {
if (ch.is_submerged) {
if (input.fast &&
!anim_is_swimming_fast) {
next_state = "swimming-fast";
} else if (!input.fast &&
!anim_is_swimming_slow) {
next_state = "swimming";
}
} else {
if (input.fast && !anim_is_running)
next_state = "running";
else if (!input.fast &&
!anim_is_walking)
next_state = "walking";
}
if (current_state != next_state)
state_machine->setAnimation(next_state);
}
});
ecs.system<const Input, const CharacterBase, AnimationControl,
CharacterInActuator>("HandlePlayerAnimations2")
.kind(flecs::OnUpdate)
.with<Character>()
.with<Player>()
.each([](flecs::entity e, const Input &input,
const CharacterBase &ch, AnimationControl &anim,
CharacterInActuator &act) {
bool controls_idle = input.motion.zeroLength();
if (!controls_idle) {
std::cout << "motion.z: "
<< Ogre::Math::Abs(input.motion.z -
act.prevMotion.z)
<< std::endl;
bool trigger_event = false;
e.each<InTrigger>([&](flecs::entity trig) {
if (Ogre::Math::Abs(input.motion.z -
act.prevMotion.z) >
0.001f) {
if (input.motion.z < 0) {
trig.get_mut<EventData>()
.add(e,
"actuator_forward",
trig, e);
trig.modified<
EventData>();
}
if (input.motion.z > 0) {
trig.get_mut<EventData>()
.add(e,
"actuator_backward",
trig, e);
trig.modified<
EventData>();
}
}
if (input.act_pressed) {
trig.get_mut<EventData>().add(
e, "actuator_action",
trig, e);
trig.modified<EventData>();
}
// ECS::get_mut<LuaData>().call_handler(
// "actuator_update", trig, e);
trigger_event = true;
});
if (!trigger_event) {
if (Ogre::Math::Abs(input.motion.z -
act.prevMotion.z) >
0.001f) {
if (input.motion.z < 0) {
e.get_mut<EventData>().add(
e,
"_in_actuator_forward",
e, e);
}
if (input.motion.z > 0) {
e.get_mut<EventData>().add(
e,
"_in_actuator_backward",
e, e);
}
}
if (input.act_pressed) {
e.get_mut<EventData>().add(
e,
"_in_actuator_action",
e, e);
}
}
act.prevMotion.x = input.motion.x;
act.prevMotion.y = input.motion.y;
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")
.kind(flecs::OnUpdate)
.with<Character>()
.each([](flecs::entity e, EventData &evt) {
for (auto ev : evt.events) {
std::cout << "character event: " << ev.event
<< std::endl;
/* parse character events */
e.each<InTrigger>([&](flecs::entity trig) {
/* if triggered, dispatch events to trigger */
trig.get_mut<EventData>().add(
ev.sender, ev.event,
trig, // it is easier this way to identify trigger entity
ev.e2);
});
}
evt.events.clear();
});
#ifdef VDEBUG
ecs.system<const CharacterBase>("CharacterGravityStatus")
.kind(flecs::OnUpdate)
.with<Character>()
.with<Player>()
.each([](flecs::entity e, const CharacterBase &ch) {
if (e.has<CharacterGravity>())
std::cout << "gravity\n";
else
std::cout << "no gravity\n";
if (e.has<InWater>())
std::cout << "in water\n";
else
std::cout << "out of water\n";
std::cout
<< "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)
override
{
GameWorld::ValueParameter<flecs::entity> *param_e =
static_cast<GameWorld::ValueParameter<
flecs::entity> *>(args[0]);
OgreAssert(param_e->get().is_valid(), "bad entity");
GameWorld::ValueParameter<std::string> *param_node =
static_cast<GameWorld::ValueParameter<
std::string> *>(args[1]);
GameWorld::ValueParameter<std::string> *param_state =
static_cast<GameWorld::ValueParameter<
std::string> *>(args[2]);
if (param_e->get().has<AnimationControl>() &&
param_e->get().get<AnimationControl>().configured ==
true) {
const AnimationControl &control =
param_e->get().get<AnimationControl>();
AnimationNodeStateMachine *sm =
control.mAnimationSystem
->get<AnimationNodeStateMachine>(
param_node->get());
bool reset = false;
if (args.size() == 4) {
GameWorld::ValueParameter<bool>
*param_reset = static_cast<
GameWorld::ValueParameter<
bool> *>(
args[3]);
reset = param_reset->get();
}
sm->setAnimation(param_state->get(), reset);
std::cout << "animation switch: "
<< param_node->get() << " "
<< param_state->get() << std::endl;
}
return 0;
}
};
ECS::get_mut<GameWorld>().add_command<AnimationSetCommand>(
"set_animation_state");
}
}

View File

@@ -0,0 +1,903 @@
#ifndef CHARACTER_ANIMATION_MODULE_H_
#define CHARACTER_ANIMATION_MODULE_H_
#include <Ogre.h>
#include <flecs.h>
#include "GameData.h"
#include "CharacterModule.h"
#include "LuaData.h"
#include "EventModule.h"
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
{
Ogre::TransformKeyFrame *vkf =
static_cast<Ogre::TransformKeyFrame *>(kf);
Ogre::KeyFrame *kf1, *kf2;
Ogre::TransformKeyFrame *k1, *k2;
unsigned short firstKeyIndex;
float tm = t->getKeyFramesAtTime(timeIndex, &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();
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);
translation =
k1->getTranslate() +
(k2->getTranslate() - k1->getTranslate()) * tm;
deltaMotion = translation - prevTranslation;
if (deltaMotion.squaredLength() >
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;
e.get_mut<CharacterBase>().mBoneMotion = deltaMotion;
e.get_mut<CharacterBase>().mBonePrevMotion = prevTranslation;
e.modified<CharacterBase>();
return true;
}
};
struct AnimationTrigger;
struct AnimationTriggerSubscriber {
virtual void operator()(const AnimationTrigger *trigger) = 0;
};
struct AnimationTrigger {
Ogre::String name;
float time;
float weight;
std::vector<AnimationTriggerSubscriber *> subscriber_list;
float getTriggerTime() const
{
return time;
}
float getMinWeight() const
{
return weight;
}
const Ogre::String &getName() const
{
return name;
}
void notify(float weight)
{
int i;
if (weight < this->weight)
return;
for (i = 0; i < subscriber_list.size(); i++)
(*subscriber_list[i])(this);
}
void addSubscriber(AnimationTriggerSubscriber *sub)
{
if (std::find(subscriber_list.begin(), subscriber_list.end(),
sub) != subscriber_list.end())
return;
subscriber_list.push_back(sub);
}
void removeSubscriber(AnimationTriggerSubscriber *sub)
{
auto it = std::find(subscriber_list.begin(),
subscriber_list.end(), sub);
if (it != subscriber_list.end())
subscriber_list.erase(it);
}
void clearSubscribers()
{
subscriber_list.clear();
}
AnimationTrigger(const Ogre::String name, float time, float weight)
: name(name)
, time(time)
, weight(weight)
{
}
};
struct Animation {
Ogre::AnimationState *mAnimationState;
Ogre::Animation *mSkelAnimation;
Ogre::NodeAnimationTrack *mHipsTrack;
Ogre::NodeAnimationTrack *mRootTrack;
RootMotionListener *mListener;
float m_weight;
float m_accWeight;
Animation(Ogre::Skeleton *skeleton, Ogre::AnimationState *animState,
Ogre::Animation *skelAnimation, flecs::entity e)
: mAnimationState(animState)
, mSkelAnimation(skelAnimation)
, mListener(OGRE_NEW RootMotionListener(e))
, m_weight(0)
, m_accWeight(0)
{
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()
{
return mAnimationState->getAnimationName();
}
void setLoop(bool loop)
{
mAnimationState->setLoop(loop);
}
bool getLoop() const
{
return mAnimationState->getLoop();
}
void setEnabled(bool enabled)
{
mAnimationState->setEnabled(enabled);
}
bool getEnabled() const
{
return mAnimationState->getEnabled();
}
void setWeight(float weight)
{
bool enabled = weight > 0.001f;
setEnabled(enabled);
mAnimationState->setWeight(weight);
m_weight = weight;
}
float getWeight() const
{
return m_weight;
}
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 int previous_frame = index.getKeyIndex();
mAnimationState->addTime(time);
index = mSkelAnimation->_getTimeIndex(
mAnimationState->getTimePosition());
mRootTrack->getKeyFramesAtTime(index, &kf1, &kf2, &next_index);
return prev_index != next_index;
}
void reset()
{
mAnimationState->setTimePosition(0);
}
void resetAccWeight()
{
m_accWeight = 0;
}
void increaseAccWeight(float weight)
{
m_accWeight += weight;
}
float getAccWeight() const
{
return m_accWeight;
}
float getLength() const
{
return mAnimationState->getLength();
if (getEnabled())
return mAnimationState->getLength();
else
return 0.0f;
}
float getTimePosition() const
{
return mAnimationState->getTimePosition();
if (getEnabled())
return mAnimationState->getTimePosition();
else
return 0.0f;
}
};
struct AnimationNode {
std::vector<AnimationNode *> children;
float m_weight;
Ogre::String m_name;
std::multimap<float, AnimationTrigger *> trigger_list;
AnimationNode()
: m_weight(0)
{
}
virtual bool addTime(float time) = 0;
virtual void setWeight(float weight) = 0;
virtual void reset() = 0;
virtual float getLength() const = 0;
virtual float getTimePosition() const = 0;
float getWeight()
{
return m_weight;
}
const Ogre::String &getName()
{
return m_name;
}
void setName(const Ogre::String &name)
{
m_name = name;
}
virtual float getTime() const
{
float l = getLength();
if (l > 0.0f)
return getTimePosition() / l;
return 0.0f;
}
void addTrigger(AnimationTrigger *trigger)
{
trigger_list.insert(std::pair<float, AnimationTrigger *>(
trigger->getTriggerTime(), trigger));
}
void clearTriggers()
{
auto it = trigger_list.begin();
while (it != trigger_list.end()) {
delete it->second;
it++;
}
trigger_list.clear();
}
float mpreUpdateTime;
void preUpdateTriggers()
{
mpreUpdateTime = getTime();
}
void postUpdateTriggers(float delta)
{
float postUpdateTime = getTime();
bool positive = delta >= 0.0f;
if (positive)
updateTriggers(mpreUpdateTime, postUpdateTime);
else
updateTriggers(postUpdateTime, mpreUpdateTime);
}
void updateTriggers(float currentTime, float nextTime)
{
int i;
float weight = getWeight();
if (currentTime <= nextTime) {
auto it = trigger_list.lower_bound(currentTime);
while (it != trigger_list.end()) {
if (nextTime <=
it->second->getTriggerTime()) // in future, sorrted by time
return;
it->second->notify(weight);
it++;
}
} else {
updateTriggers(currentTime, 1);
updateTriggers(0, nextTime);
}
}
};
struct AnimationNodeAnimation : AnimationNode {
Animation *mAnimation;
bool enabled;
AnimationNodeAnimation(Animation *animation)
: AnimationNode()
, mAnimation(animation)
{
}
bool addTime(float time)
{
bool ret;
preUpdateTriggers();
ret = mAnimation->addTime(time);
postUpdateTriggers(time);
return ret;
}
void setWeight(float weight)
{
m_weight = weight;
enabled = weight > 0.001f;
}
void reset()
{
mAnimation->reset();
}
float getLength() const
{
return mAnimation->getLength();
if (enabled)
return mAnimation->getLength();
else
return 0.0f;
}
float getTimePosition() const
{
return mAnimation->getTimePosition();
if (enabled)
return mAnimation->getTimePosition();
else
return 0.0f;
}
};
struct AnimationNodeStateMachineState : AnimationNode {
AnimationNodeStateMachineState()
: AnimationNode()
{
}
bool addTime(float time)
{
bool ret;
preUpdateTriggers();
ret = children[0]->addTime(time);
postUpdateTriggers(time);
return ret;
}
void setWeight(float weight)
{
m_weight = weight;
bool enabled = weight > 0.001f;
children[0]->setWeight(weight);
}
void reset()
{
children[0]->reset();
}
float getLength() const
{
return children[0]->getLength();
}
float getTimePosition() const
{
return children[0]->getTimePosition();
}
};
struct AnimationNodeSpeed : AnimationNode {
float m_speed;
bool enabled;
AnimationNodeSpeed(float speed)
: AnimationNode()
, m_speed(speed)
, enabled(false)
{
}
bool addTime(float time)
{
bool ret;
preUpdateTriggers();
ret = children[0]->addTime(time * m_speed);
postUpdateTriggers(time);
return ret;
}
void setWeight(float weight)
{
m_weight = weight;
children[0]->setWeight(weight);
}
void reset()
{
children[0]->reset();
}
float getLength() const
{
if (m_speed > 0.0f || m_speed < 0.0f)
return children[0]->getLength() / m_speed;
return 0.0f;
}
float getTimePosition() const
{
if (m_speed > 0.0f || m_speed < 0.0f)
return children[0]->getTimePosition() / m_speed;
return 0.0f;
}
float getTime() const override
{
float l = children[0]->getLength();
if (l > 0.0f)
return children[0]->getTimePosition() / l;
return 0.0f;
}
};
struct AnimationNodeStateMachine : AnimationNode {
std::map<Ogre::String, AnimationNode *> stateMap;
std::set<AnimationNode *> fade_in, fade_out;
AnimationNode *currentAnim, *nextAnim;
float fade_speed;
Ogre::String mCurrentStateName;
bool configured;
bool debug;
AnimationNodeStateMachine(float fade_speed, bool debug = false)
: AnimationNode()
, currentAnim(nullptr)
, nextAnim(nullptr)
, fade_speed(fade_speed)
, mCurrentStateName("")
, configured(false)
, debug(debug)
{
m_weight = 1.0f;
}
bool addTime(float time)
{
int i;
preUpdateTriggers();
if (!configured) {
configure();
configured = true;
}
#ifdef VDEBUG
if (debug) {
std::cout << "state machine addTime" << std::endl;
std::cout
<< "state machine children: " << children.size()
<< std::endl;
}
#endif
for (i = 0; i < children.size(); i++) {
#ifdef VDEBUG
if (debug)
std::cout << "child weight: " << i << " "
<< children[i]->getWeight()
<< std::endl;
#endif
AnimationNode *child = children[i];
if (fade_in.find(child) != fade_in.end()) {
Ogre::Real newWeight =
child->getWeight() + time * fade_speed;
child->setWeight(Ogre::Math::Clamp<Ogre::Real>(
newWeight * m_weight, 0, m_weight));
#ifdef VDEBUG
if (debug) {
std::cout << "fade in: " << newWeight
<< std::endl;
std::cout << "m_weight: " << m_weight
<< std::endl;
}
#endif
if (newWeight >= 1)
fade_in.erase(child);
}
if (fade_out.find(child) != fade_out.end()) {
Ogre::Real newWeight =
child->getWeight() - time * fade_speed;
child->setWeight(Ogre::Math::Clamp<Ogre::Real>(
newWeight * m_weight, 0, 1));
if (newWeight <= 0)
fade_out.erase(child);
}
}
OgreAssert(currentAnim, "bad current anim");
bool ret = false;
if (currentAnim)
ret = currentAnim->addTime(time);
postUpdateTriggers(time);
return ret;
}
void setWeight(float weight)
{
int i;
if (weight > m_weight && currentAnim)
fade_in.insert(currentAnim);
if (weight < m_weight && currentAnim &&
currentAnim->getWeight() > weight)
currentAnim->setWeight(weight);
m_weight = weight;
bool enabled = weight > 0.001f;
/* do not update child state yet */
}
void addState(AnimationNode *state)
{
const Ogre::String &name = state->getName();
stateMap[name] = state;
state->setWeight(0);
fade_in.erase(state);
fade_out.erase(state);
std::cout << "registered state: " << name << std::endl;
}
void configure()
{
int i;
if (debug)
std::cout << "children: " << children.size()
<< std::endl;
for (i = 0; i < children.size(); i++)
addState(children[i]);
if (debug)
std::cout << "configure called" << std::endl;
}
void reset()
{
int i;
for (i = 0; i < children.size(); i++)
children[i]->reset();
}
void setAnimation(const Ogre::String &anim_state, bool reset = false)
{
if (!configured) {
configure();
configured = true;
}
OgreAssert(stateMap.find(anim_state) != stateMap.end(),
"Bad animation state: " + anim_state);
nextAnim = stateMap[anim_state];
if (nextAnim == currentAnim)
return;
if (currentAnim != nullptr) {
fade_out.insert(currentAnim);
fade_in.erase(currentAnim);
}
fade_out.erase(nextAnim);
fade_in.insert(nextAnim);
nextAnim->setWeight(0);
if (reset)
nextAnim->reset();
currentAnim = nextAnim;
mCurrentStateName = anim_state;
}
const Ogre::String &getCurrentState() const
{
return mCurrentStateName;
}
float getLength() const
{
if (currentAnim)
return currentAnim->getLength();
else
return 0.0f;
}
float getTimePosition() const
{
if (currentAnim)
return currentAnim->getTimePosition();
else
return 0.0f;
}
};
#define ANIM_FADE_SPEED \
7.5f // animation crossfade speed in % of full weight per second
struct AnimationNodeOutput : AnimationNode {
float m_weight;
float m_speed;
AnimationNodeOutput()
: AnimationNode()
, m_weight(1.0f)
, m_speed(1.0f)
{
}
bool addTime(float time)
{
bool ret;
preUpdateTriggers();
ret = children[0]->addTime(time * m_speed);
postUpdateTriggers(time);
return ret;
}
void setWeight(float weight)
{
m_weight = weight;
bool enabled = weight > 0.001f;
children[0]->setWeight(weight);
}
void reset()
{
children[0]->reset();
}
float getLength() const
{
return children[0]->getLength();
}
float getTimePosition() const
{
return children[0]->getTimePosition();
}
};
struct AnimationSystem : AnimationNode {
bool debug;
AnimationSystem(bool debug = false)
: debug(debug)
, m_builder(this, debug)
{
}
std::unordered_map<Ogre::String, Animation *> animation_list;
std::vector<Animation *> vanimation_list;
void add_animation(const Ogre::String &name, Animation *animation)
{
OgreAssert(animation, "No animation " + name);
animation_list[name] = animation;
vanimation_list.push_back(animation);
}
void clear_animations()
{
animation_list.clear();
vanimation_list.clear();
}
struct AnimationSystemBuilder {
AnimationSystem *mAnimationSystem;
std::vector<AnimationNode *> animation_nodes;
AnimationNode *parent;
std::list<AnimationNode *> parent_stack;
std::unordered_map<Ogre::String, AnimationNode *> nodeMap;
std::vector<AnimationNodeAnimation *> animationNodeList;
bool debug;
AnimationSystemBuilder(AnimationSystem *animationSystem,
bool debug = false)
: mAnimationSystem(animationSystem)
, debug(debug)
{
}
AnimationSystemBuilder *output()
{
AnimationNodeOutput *onode = new AnimationNodeOutput();
animation_nodes.push_back(onode);
parent = onode;
return this;
}
AnimationSystemBuilder *
animation(const Ogre::String &animation_name)
{
OgreAssert(parent, "bad parent");
Animation *animation =
mAnimationSystem->animation_list[animation_name];
OgreAssert(animation,
"bad animation " + animation_name);
AnimationNodeAnimation *onode =
new AnimationNodeAnimation(animation);
OgreAssert(onode, "bad animation");
OgreAssert(onode->mAnimation, "bad animation");
animation_nodes.push_back(onode);
parent->children.push_back(onode);
animationNodeList.push_back(onode);
return this;
}
AnimationSystemBuilder *trigger(flecs::entity e,
const Ogre::String &name,
float time,
const Ogre::String &event)
{
struct EventSubscriber : AnimationTriggerSubscriber {
flecs::entity ent;
Ogre::String event;
void operator()(const AnimationTrigger *trigger)
{
ent.get_mut<EventData>().add(ent, event,
ent, ent);
}
EventSubscriber(flecs::entity e,
const Ogre::String &event)
: ent(e)
, event(event)
{
}
};
OgreAssert(parent, "bad parent");
AnimationTrigger *trigger =
new AnimationTrigger(name, time, 0.1f);
EventSubscriber *sub = new EventSubscriber(e, event);
trigger->addSubscriber(sub);
parent->addTrigger(trigger);
return this;
} // leaf too...
AnimationSystemBuilder *
transition_end(const Ogre::String &state_from,
const Ogre::String &state_to)
{
struct EndTransitionSubscriber
: AnimationTriggerSubscriber {
AnimationNodeStateMachine *sm;
Ogre::String next_state;
bool reset;
void operator()(const AnimationTrigger *trigger)
{
sm->setAnimation(next_state, reset);
}
EndTransitionSubscriber(
AnimationNodeStateMachine *sm,
const Ogre::String &next_state,
bool reset = true)
: sm(sm)
, next_state(next_state)
, reset(reset)
{
}
};
OgreAssert(parent, "no parent");
AnimationNodeStateMachine *sm =
static_cast<AnimationNodeStateMachine *>(
parent);
OgreAssert(sm, "no state machine");
AnimationTrigger *trigger = new AnimationTrigger(
"transition:" + state_from + "_" + state_to,
0.99f, 0.1f);
EndTransitionSubscriber *sub =
new EndTransitionSubscriber(sm, state_to);
int i;
bool ok = false;
for (i = 0; i < sm->children.size(); i++) {
if (sm->children[i]->getName() == state_from) {
trigger->addSubscriber(sub);
sm->children[i]->addTrigger(trigger);
ok = true;
break;
}
}
OgreAssert(ok, "Failed to set transition");
return this;
}
AnimationSystemBuilder *speed(float speed,
const Ogre::String &anchor = "")
{
OgreAssert(parent, "bad parent");
AnimationNodeSpeed *onode =
new AnimationNodeSpeed(speed);
animation_nodes.push_back(onode);
parent->children.push_back(onode);
parent_stack.push_back(parent);
parent = onode;
if (anchor.length() > 0)
nodeMap[anchor] = onode;
return this;
}
AnimationSystemBuilder *
state_machine(float fade_time = ANIM_FADE_SPEED,
const Ogre::String &anchor = "")
{
OgreAssert(parent, "bad parent");
AnimationNodeStateMachine *onode =
new AnimationNodeStateMachine(fade_time, debug);
animation_nodes.push_back(onode);
parent->children.push_back(onode);
parent_stack.push_back(parent);
parent = onode;
if (anchor.length() > 0)
nodeMap[anchor] = onode;
return this;
}
AnimationSystemBuilder *state(const Ogre::String &state_name)
{
OgreAssert(parent, "bad parent");
AnimationNodeStateMachineState *onode =
new AnimationNodeStateMachineState;
animation_nodes.push_back(onode);
parent->children.push_back(onode);
parent_stack.push_back(parent);
parent = onode;
onode->setName(state_name);
return this;
}
AnimationSystemBuilder *end()
{
parent = parent_stack.back();
parent_stack.pop_back();
return this;
}
};
AnimationSystemBuilder m_builder;
bool addTime(float time)
{
int i;
preUpdateTriggers();
bool ret = m_builder.animation_nodes[0]->addTime(time);
for (i = 0; i < m_builder.animationNodeList.size(); i++) {
AnimationNodeAnimation *anim =
m_builder.animationNodeList[i];
OgreAssert(anim->mAnimation, "No animation");
float weight = anim->getWeight();
anim->mAnimation->increaseAccWeight(weight);
#ifdef VDEBUG
if (debug)
std::cout << i << " node: "
<< anim->mAnimation->getName() << " "
<< weight << std::endl;
#endif
}
for (i = 0; i < vanimation_list.size(); i++) {
float weight = vanimation_list[i]->getAccWeight();
vanimation_list[i]->setWeight(weight);
vanimation_list[i]->resetAccWeight();
#define VDEBUG
#ifdef VDEBUG
if (debug && vanimation_list[i]->getEnabled())
std::cout << i << " animation: "
<< vanimation_list[i]->getName()
<< " " << weight << std::endl;
#endif
#undef VDEBUG
}
postUpdateTriggers(time);
return ret;
}
void setWeight(float weight)
{
m_builder.animation_nodes[0]->setWeight(weight);
}
void reset()
{
m_builder.animation_nodes[0]->reset();
}
AnimationSystemBuilder *builder()
{
m_builder.animation_nodes.reserve(8);
m_builder.parent = nullptr;
return &m_builder;
}
template <class T> T *get(const Ogre::String &name)
{
return static_cast<T *>(m_builder.nodeMap[name]);
}
float getLength() const
{
return m_builder.animation_nodes[0]->getLength();
}
float getTimePosition() const
{
return m_builder.animation_nodes[0]->getTimePosition();
}
};
struct AnimationControl {
bool configured;
AnimationSystem *mAnimationSystem;
};
struct DefaultAnimation {
std::vector<std::pair<std::string, std::string> > animations;
};
struct CharacterAnimationModule {
CharacterAnimationModule(flecs::world &ecs);
};
}
#endif

View File

@@ -0,0 +1,42 @@
#include <Ogre.h>
#include <OgreConfigFile.h>
#include "GameData.h"
#include "Components.h"
#include "CharacterModule.h"
#include "CharacterAnimationModule.h"
#include "CharacterManagerModule.h"
namespace ECS
{
CharacterManagerModule::CharacterManagerModule(flecs::world &ecs)
{
ecs.module<CharacterManagerModule>();
ecs.import <CharacterModule>();
ecs.import <CharacterAnimationModule>();
}
flecs::entity
CharacterManagerModule::createPlayer(const Ogre::Vector3 &position,
const Ogre::Quaternion &rotation)
{
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>();
return player;
}
flecs::entity
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;
}
}

View File

@@ -0,0 +1,17 @@
#ifndef _CHARACTER_MANAGER_MODULE_
#define _CHARACTER_MANAGER_MODULE_
#include <flecs.h>
namespace ECS
{
struct CharacterManagerModule {
std::set<flecs::entity> characters;
CharacterManagerModule(flecs::world &ecs);
flecs::entity createPlayer(const Ogre::Vector3 &position,
const Ogre::Quaternion &rotation);
flecs::entity createCharacterData(const Ogre::String model,
const Ogre::Vector3 &position,
const Ogre::Quaternion &rotation);
void removeCharacterData(int id);
};
}
#endif

View File

@@ -1,11 +1,13 @@
#include <iostream>
#include <Ogre.h>
#include <OgreBullet.h>
#include "GameData.h"
#include "CharacterModule.h"
#include "WaterModule.h"
#include "TerrainModule.h"
#include "Components.h"
#include "PhysicsModule.h"
#include "CharacterAnimationModule.h"
#include "CharacterModule.h"
#include "goap.h"
namespace ECS
{
CharacterModule::CharacterModule(flecs::world &ecs)
@@ -15,10 +17,22 @@ CharacterModule::CharacterModule(flecs::world &ecs)
ecs.component<Character>();
ecs.component<Player>();
ecs.component<CharacterBase>();
ecs.component<CharacterVelocity>();
ecs.component<CharacterBody>();
ecs.component<CharacterGravity>();
ecs.component<CharacterLocation>();
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.import <TerrainModule>();
ecs.import <WaterModule>();
ecs.system<EngineData, CharacterBase>("UpdateTimer")
.kind(flecs::OnUpdate)
.each([this](EngineData &eng, CharacterBase &ch) {
@@ -49,6 +63,22 @@ CharacterModule::CharacterModule(flecs::world &ecs)
zaxis = 0.0f;
else
zaxis = Ogre::Math::Sign(zaxis);
if (active & 32)
input.act = true;
else
input.act = false;
if (pressed & 32)
input.act_pressed = true;
else
input.act_pressed = false;
if (active & 64)
input.act2 = true;
else
input.act2 = false;
if (pressed & 64)
input.act2_pressed = true;
else
input.act2_pressed = false;
input.motion.z = zaxis;
float xaxis = input.motion.x;
xaxis *= 0.9f;
@@ -81,111 +111,6 @@ CharacterModule::CharacterModule(flecs::world &ecs)
}
ECS::get().modified<ECS::Input>();
});
ecs.system<const CharacterBase, AnimationControl>("HandleAnimations")
.kind(flecs::OnUpdate)
.each([this](flecs::entity e, const CharacterBase &ch,
AnimationControl &anim) {
if (!anim.configured && ch.mSkeleton) {
int i, j;
ch.mSkeleton->setBlendMode(
Ogre::ANIMBLEND_CUMULATIVE);
Ogre::String
animNames[AnimationControl::NUM_ANIMS] = {
"idle", "walking", "running",
"treading_water", "swimming"
};
for (i = 0; i < AnimationControl::NUM_ANIMS;
i++) {
anim.mAnims[i] =
ch.mBodyEnt->getAnimationState(
animNames[i]);
anim.mAnims[i]->setLoop(true);
anim.mAnims[i]->setEnabled(true);
anim.mAnims[i]->setWeight(0);
anim.mFadingIn[i] = false;
anim.mFadingOut[i] = false;
anim.mSkelAnimations[i] =
ch.mSkeleton->getAnimation(
animNames[i]);
for (const auto &it :
anim.mSkelAnimations[i]
->_getNodeTrackList()) {
Ogre::NodeAnimationTrack *track =
it.second;
Ogre::String trackName =
track->getAssociatedNode()
->getName();
if (trackName ==
"mixamorig:Hips") {
anim.mHipsTracks[i] =
track;
} else if (trackName ==
"Root") {
anim.mRootTracks[i] =
track;
// mRootTracks[i]->removeAllKeyFrames();
}
}
Ogre::Vector3 delta =
Ogre::Vector3::ZERO;
Ogre::Vector3 motion =
Ogre::Vector3::ZERO;
for (j = 0;
j < anim.mRootTracks[i]
->getNumKeyFrames();
j++) {
Ogre::Vector3 trans =
anim.mRootTracks[i]
->getNodeKeyFrame(
j)
->getTranslate();
if (j == 0)
delta = trans;
else
delta = trans - motion;
anim.mRootTracks[i]
->getNodeKeyFrame(j)
->setTranslate(delta);
motion = trans;
}
}
anim.nextAnim = AnimationControl::ANIM_IDLE;
setAnimation(anim);
anim.configured = true;
}
});
ecs.system<AnimationControl>("HandleAnimations0")
.kind(flecs::OnUpdate)
.each([this](flecs::entity e, AnimationControl &anim) {
if (anim.currentAnim != anim.nextAnim)
setAnimation(anim);
});
ecs.system<const EngineData, const Input, CharacterBase,
AnimationControl>("HandleAnimations1")
.kind(flecs::OnUpdate)
.each([this](const EngineData &eng, const Input &input,
CharacterBase &ch, AnimationControl &anim) {
float delta = eng.delta;
Ogre::Real animSpeed = 1;
if (anim.currentAnim != AnimationControl::ANIM_NONE) {
if (anim.currentAnim ==
AnimationControl::ANIM_WALK)
anim.mAnims[anim.currentAnim]->addTime(
delta * 1.0f);
else if (anim.currentAnim ==
AnimationControl::ANIM_SWIMMING &&
input.fast)
anim.mAnims[anim.currentAnim]->addTime(
delta * 20.0f);
else
anim.mAnims[anim.currentAnim]->addTime(
delta * animSpeed);
}
fadeAnimations(anim, delta);
if (!ch.mRootBone)
return;
ch.mBoneMotion = ch.mRootBone->getPosition();
});
ecs.system<CharacterBase>()
.kind(flecs::OnUpdate)
.with<TerrainReady>()
@@ -204,6 +129,7 @@ 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)
@@ -211,6 +137,7 @@ CharacterModule::CharacterModule(flecs::world &ecs)
.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);
@@ -242,6 +169,8 @@ CharacterModule::CharacterModule(flecs::world &ecs)
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)
@@ -261,191 +190,13 @@ CharacterModule::CharacterModule(flecs::world &ecs)
gr.gvelocity *= (1.0 - eng.delta);
gr.velocity.y *= (1.0 - eng.delta);
});
ecs.system<const EngineData, const AnimationControl,
const CharacterBase, CharacterVelocity>("HandleSwimming")
.kind(flecs::OnUpdate)
.with<TerrainReady>()
.with<WaterReady>()
.with<InWater>()
.with<CharacterBuoyancy>()
.each([this](flecs::entity e, const EngineData &eng,
const AnimationControl &anim,
const CharacterBase &ch, CharacterVelocity &gr) {
if (anim.currentAnim ==
AnimationControl::ANIM_SWIMMING) {
float h = Ogre::Math::Clamp(
0.0f - ch.mBodyNode->getPosition().y,
0.0f, 2000.0f);
if (h > 0.05 && h < 2.0f)
gr.gvelocity.y += 0.1f * (h + 1.0f) *
h * eng.delta;
}
});
ecs.system<const EngineData, const CharacterBase, CharacterVelocity>(
"HandleRootMotionVelocity")
.kind(flecs::OnUpdate)
.with<TerrainReady>()
.with<WaterReady>()
.each([this](flecs::entity e, const EngineData &eng,
const CharacterBase &ch, CharacterVelocity &v) {
if (eng.delta < 0.0000001f)
return;
if (!ch.mBodyNode)
return;
Ogre::Quaternion rot = ch.mBodyNode->getOrientation();
Ogre::Vector3 pos = ch.mBodyNode->getPosition();
Ogre::Vector3 boneMotion = ch.mBoneMotion;
v.velocity = rot * boneMotion / eng.delta;
if (eng.startupDelay <= 0.0f)
v.velocity += v.gvelocity;
v.velocity.y = Ogre::Math::Clamp(v.velocity.y, -10.5f,
1000000.0f);
});
ecs.system<const EngineData, CharacterBase, CharacterBody,
AnimationControl, CharacterVelocity>("HandleRootMotion")
.kind(flecs::OnUpdate)
.each([this](flecs::entity e, const EngineData &eng,
CharacterBase &ch, CharacterBody &body,
AnimationControl &anim, CharacterVelocity &v) {
if (!ch.mBodyNode)
return;
if (eng.delta < 0.0000001f)
return;
OgreAssert(eng.delta > 0.0f, "Zero delta");
int maxPen = 0;
Ogre::Vector3 colNormal;
bool is_on_floor = false;
bool penetration = false;
if (eng.startupDelay < 0.0f) {
if (body.mController) {
Ogre::Vector3 rotMotion =
v.velocity * eng.delta;
btVector3 currentPosition =
body.mGhostObject
->getWorldTransform()
.getOrigin();
is_on_floor =
body.mController->isOnFloor();
penetration = body.mController
->isPenetrating();
if (is_on_floor)
v.gvelocity = Ogre::Vector3(
0.0f, 0.0f, 0.0f);
btTransform from(
Ogre::Bullet::convert(
ch.mBodyNode
->getOrientation()),
Ogre::Bullet::convert(
ch.mBodyNode
->getPosition()));
ch.mBodyNode->setPosition(
ch.mBodyNode->getPosition() +
rotMotion);
ch.mBoneMotion = Ogre::Vector3(0, 0, 0);
}
}
});
ecs.system<const Input, const CharacterBase, AnimationControl>(
"HandlePlayerAnimations")
.kind(flecs::OnUpdate)
.with<Character>()
.with<Player>()
.each([](flecs::entity e, const Input &input,
const CharacterBase &ch, AnimationControl &anim) {
if (!anim.configured)
return;
bool controls_idle = input.motion.zeroLength();
bool anim_is_idle =
anim.currentAnim ==
AnimationControl::ANIM_IDLE ||
anim.currentAnim ==
AnimationControl::ANIM_TREADING_WATER;
bool anim_is_walking = anim.currentAnim ==
AnimationControl::ANIM_WALK;
bool anim_is_running = anim.currentAnim ==
AnimationControl::ANIM_RUN;
bool anim_is_swimming = anim.currentAnim ==
AnimationControl::ANIM_SWIMMING;
bool anim_is_motion = anim_is_walking ||
anim_is_running ||
anim_is_swimming;
if (controls_idle) {
if (anim.currentAnim ==
AnimationControl::ANIM_IDLE &&
ch.is_submerged)
anim.nextAnim = AnimationControl::
ANIM_TREADING_WATER;
else if (anim.currentAnim ==
AnimationControl::
ANIM_TREADING_WATER &&
!ch.is_submerged)
anim.nextAnim =
AnimationControl::ANIM_IDLE;
}
if (!controls_idle && anim_is_idle) {
anim.reset = true;
if (ch.is_submerged) {
if (input.fast)
anim.nextAnim =
AnimationControl::
ANIM_SWIMMING;
else
anim.nextAnim =
AnimationControl::
ANIM_SWIMMING;
} else {
if (input.fast)
anim.nextAnim =
AnimationControl::ANIM_RUN;
else
anim.nextAnim =
AnimationControl::
ANIM_WALK;
}
} else
anim.reset = false;
if (controls_idle && anim_is_motion)
if (ch.is_submerged)
anim.nextAnim = AnimationControl::
ANIM_TREADING_WATER;
else
anim.nextAnim =
AnimationControl::ANIM_IDLE;
else if (!controls_idle && anim_is_motion) {
if (input.fast && anim_is_walking)
anim.nextAnim =
AnimationControl::ANIM_RUN;
else if (!input.fast && anim_is_running)
anim.nextAnim =
AnimationControl::ANIM_WALK;
if ((anim_is_walking || anim_is_running) &&
ch.is_submerged) {
if (input.fast)
anim.nextAnim =
AnimationControl::
ANIM_SWIMMING;
else
anim.nextAnim =
AnimationControl::
ANIM_SWIMMING;
} else if ((anim_is_swimming) &&
!ch.is_submerged) {
if (input.fast)
anim.nextAnim =
AnimationControl::ANIM_RUN;
else
anim.nextAnim =
AnimationControl::
ANIM_WALK;
}
}
});
#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,
const Camera &camera, CharacterBase &ch) {
ch.mGoalDirection = Ogre::Vector3::ZERO;
@@ -492,6 +243,7 @@ CharacterModule::CharacterModule(flecs::world &ecs)
ch.mBodyNode->yaw(Ogre::Degree(yawToGoal));
}
});
#if 0
ecs.system<const EngineData, CharacterLocation, CharacterBase,
CharacterBody>("UpdateCharacterBase")
.kind(flecs::OnUpdate)
@@ -508,15 +260,49 @@ CharacterModule::CharacterModule(flecs::world &ecs)
ch.mBodyNode->_getDerivedPosition();
}
});
#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;
});
#if 0
ecs.system<const EngineData, const CharacterLocation,
const CharacterConf>("SetupCharacter")
const CharacterConf, Body2Entity>("SetupCharacter")
.kind(flecs::OnUpdate)
.with<Character>()
.without<CharacterBase>()
.without<CharacterBody>()
.each([](flecs::entity e, const EngineData &eng,
const CharacterLocation &loc,
const CharacterConf &conf) {
const CharacterConf &conf, Body2Entity &b2e) {
CharacterBase &ch = e.ensure<CharacterBase>();
CharacterBody &body = e.ensure<CharacterBody>();
AnimationControl &anim = e.ensure<AnimationControl>();
@@ -527,19 +313,25 @@ CharacterModule::CharacterModule(flecs::world &ecs)
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;
// 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 } });
body.checkGround = false;
body.checkGroundResult = false;
#if 0
body.mCollisionShape = nullptr;
body.mGhostObject = nullptr;
body.mController = nullptr;
body.mGhostObject = new btPairCachingGhostObject();
b2e.entities[body.mGhostObject] = e;
body.mCollisionShape = new btCompoundShape(false);
body.mGhostObject->setCollisionShape(
body.mCollisionShape);
@@ -563,8 +355,8 @@ CharacterModule::CharacterModule(flecs::world &ecs)
->calculatePrincipalAxisTransform(
masses, principal, inertia);
}
body.mGhostObject->setCollisionFlags(
btCollisionObject::CF_KINEMATIC_OBJECT /*|
body.mGhostObject->setCollisionFlags(body.mGhostObject->getCollisionFlags() | btCollisionObject::CF_CHARACTER_OBJECT | btCollisionObject::CF_KINEMATIC_OBJECT
/*btCollisionObject::CF_KINEMATIC_OBJECT |
btCollisionObject::CF_NO_CONTACT_RESPONSE */);
body.mGhostObject->setActivationState(
DISABLE_DEACTIVATION);
@@ -572,13 +364,15 @@ CharacterModule::CharacterModule(flecs::world &ecs)
body.mGhostObject, ch.mBodyEnt, 1, 0x7FFFFFFF);
OgreAssert(body.mGhostObject, "Need GhostObject");
OgreAssert(body.mCollisionShape, "No collision shape");
#endif
e.add<CharacterGravity>();
e.add<CharacterBuoyancy>();
anim.currentAnim = AnimationControl::ANIM_NONE;
anim.nextAnim = AnimationControl::ANIM_NONE;
anim.reset = false;
anim.configured = false;
// OgreAssert(body.mGhostObject->hasContactResponse(),
// "need contact response");
});
#endif
#if 0
ecs.system<const EngineData, CharacterBase, CharacterBody>(
"UpdateCharacterPhysics")
.kind(flecs::OnUpdate)
@@ -587,17 +381,21 @@ CharacterModule::CharacterModule(flecs::world &ecs)
.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")
@@ -647,6 +445,7 @@ CharacterModule::CharacterModule(flecs::world &ecs)
Ogre::Node::TS_PARENT);
}
});
#if 0
class ClosestNotMeRayResultCallback
: public btCollisionWorld::ClosestRayResultCallback {
btCollisionObject *mMe;
@@ -669,12 +468,14 @@ CharacterModule::CharacterModule(flecs::world &ecs)
rayResult, normalInWorldSpace);
}
};
ecs.system<const EngineData, CharacterBody>("CheckGround")
#endif
ecs.system<const EngineData, CharacterBase>("CheckGround")
.kind(flecs::OnUpdate)
.with<Character>()
.with<Player>()
.without<GroundCheckReady>()
.each([](const EngineData &eng, CharacterBody &body) {
.each([](const EngineData &eng, CharacterBase &ch) {
#if 0
if (body.mGhostObject) {
btVector3 from =
body.mGhostObject->getWorldTransform()
@@ -690,7 +491,10 @@ CharacterModule::CharacterModule(flecs::world &ecs)
if (resultCallback.hasHit())
ECS::get().add<GroundCheckReady>();
}
#endif
ECS::get().add<GroundCheckReady>();
});
#if 0
ecs.system<const WaterBody, const CharacterBase, CharacterBody>(
"CharacterWater1")
.kind(flecs::OnUpdate)
@@ -698,11 +502,13 @@ CharacterModule::CharacterModule(flecs::world &ecs)
.without<InWater>()
.each([](flecs::entity e, const WaterBody &waterb,
const CharacterBase &ch, CharacterBody &body) {
#if 0
if (waterb.isInWater(body.mGhostObject) &&
ch.mBodyNode->_getDerivedPosition().y < -0.05f) {
e.add<InWater>();
std::cout << "Big Splash\n";
}
#endif
#if 0
if (waterb.mInWater.find(body.mGhostObject) ==
waterb.mInWater.end())
@@ -710,6 +516,8 @@ CharacterModule::CharacterModule(flecs::world &ecs)
std::cout << waterb.mInWater.size() << " InWater\n";
#endif
});
#endif
#if 0
ecs.system<const WaterBody, const CharacterBase, CharacterBody>(
"CharacterWater2")
.kind(flecs::OnUpdate)
@@ -717,10 +525,16 @@ CharacterModule::CharacterModule(flecs::world &ecs)
.with<InWater>()
.each([](flecs::entity e, const WaterBody &waterb,
const CharacterBase &ch, CharacterBody &body) {
if (waterb.isInWater(body.mGhostObject) &&
ch.mBodyNode->_getDerivedPosition().y > 0.05f)
float h = ch.mBodyNode->_getDerivedPosition().y;
#if 0
if (waterb.isInWater(body.mGhostObject) && h > 0.05f)
e.remove<InWater>();
else if (!waterb.isInWater(body.mGhostObject) &&
h > 0.05f)
e.remove<InWater>();
#endif
});
#endif
#if 0
ecs.system<const EngineData, CharacterBase, CharacterBody>(
"DisplayPlayerPos")
@@ -733,71 +547,10 @@ CharacterModule::CharacterModule(flecs::world &ecs)
<< "\n";
});
#endif
ecs.system<const EngineData, const CharacterBody>("UpdatePhysics")
.kind(flecs::OnUpdate)
.with<CharacterUpdatePhysicsState>()
.write<CharacterUpdatePhysicsState>()
.each([](flecs::entity e, const EngineData &eng,
const CharacterBody &body) {
if (e.has<CharacterDisablePhysics>()) {
eng.mWorld->getBtWorld()->removeAction(
body.mController);
} else {
eng.mWorld->getBtWorld()->addAction(
body.mController);
}
e.remove<CharacterUpdatePhysicsState>();
});
#if 0
#endif
}
void CharacterModule::setAnimation(AnimationControl &anim)
{
OgreAssert(anim.nextAnim >= 0 &&
anim.nextAnim < AnimationControl::NUM_ANIMS,
"Bad animation");
if (anim.currentAnim != AnimationControl::ANIM_NONE) {
anim.mFadingIn[anim.currentAnim] = false;
anim.mFadingOut[anim.currentAnim] = true;
}
if (anim.nextAnim != AnimationControl::ANIM_NONE) {
anim.mAnims[anim.nextAnim]->setEnabled(true);
anim.mAnims[anim.nextAnim]->setWeight(0);
anim.mFadingOut[anim.nextAnim] = false;
anim.mFadingIn[anim.nextAnim] = true;
if (anim.reset)
anim.mAnims[anim.nextAnim]->setTimePosition(0);
}
anim.currentAnim = anim.nextAnim;
anim.reset = false;
}
#define ANIM_FADE_SPEED \
7.5f // animation crossfade speed in % of full weight per second
void CharacterModule::fadeAnimations(AnimationControl &anim, Ogre::Real delta)
{
int i;
for (i = 0; i < AnimationControl::NUM_ANIMS; i++) {
if (anim.mFadingIn[i]) {
// slowly fade this animation in until it has full weight
Ogre::Real newWeight = anim.mAnims[i]->getWeight() +
delta * ANIM_FADE_SPEED;
anim.mAnims[i]->setWeight(
Ogre::Math::Clamp<Ogre::Real>(newWeight, 0, 1));
if (newWeight >= 1)
anim.mFadingIn[i] = false;
} else if (anim.mFadingOut[i]) {
// slowly fade this animation out until it has no weight, and then disable it
Ogre::Real newWeight = anim.mAnims[i]->getWeight() -
delta * ANIM_FADE_SPEED;
anim.mAnims[i]->setWeight(
Ogre::Math::Clamp<Ogre::Real>(newWeight, 0, 1));
if (newWeight <= 0) {
anim.mAnims[i]->setEnabled(false);
anim.mFadingOut[i] = false;
}
}
}
}
void CharacterModule::updateCameraGoal(Camera &camera, Ogre::Real deltaYaw,
Ogre::Real deltaPitch,
Ogre::Real deltaZoom)
@@ -819,4 +572,182 @@ void CharacterModule::updateCameraGoal(Camera &camera, Ogre::Real deltaYaw,
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;
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>();
}
}
});
}
}

View File

@@ -1,6 +1,8 @@
#ifndef CHARACTER_MODULE_H_
#define CHARACTER_MODULE_H_
#include <flecs.h>
#include <Ogre.h>
#include "goap.h"
namespace ECS
{
struct Camera;
@@ -11,6 +13,9 @@ struct CharacterGravity {};
struct CharacterBuoyancy {};
struct CharacterDisablePhysics {};
struct CharacterUpdatePhysicsState {};
struct Male {};
struct Female {};
struct CharacterBase {
Ogre::String type;
float mTimer;
@@ -19,6 +24,7 @@ struct CharacterBase {
Ogre::Skeleton *mSkeleton;
Ogre::Node *mRootBone;
Ogre::Vector3 mBoneMotion;
Ogre::Vector3 mBonePrevMotion;
Ogre::Vector3 mGoalDirection; // actual intended direction in world-space
bool is_submerged;
};
@@ -29,44 +35,32 @@ struct CharacterLocation {
struct CharacterConf {
Ogre::String type;
};
struct CharacterBody {
btPairCachingGhostObject *mGhostObject;
btCompoundShape *mCollisionShape;
Ogre::Bullet::KinematicMotionSimple *mController;
bool checkGround;
bool checkGroundResult;
struct CharacterInActuator {
Ogre::String animationState;
Vector3 prevMotion;
};
struct CharacterVelocity {
Ogre::Vector3 gvelocity;
Ogre::Vector3 velocity;
struct ActionTarget {
std::vector<BaseAction *> actions;
};
struct AnimationControl {
enum AnimID {
ANIM_IDLE = 0,
ANIM_WALK,
ANIM_RUN,
ANIM_TREADING_WATER,
ANIM_SWIMMING,
NUM_ANIMS,
ANIM_NONE = NUM_ANIMS
};
AnimID currentAnim;
AnimID nextAnim;
bool reset;
bool configured;
Ogre::AnimationState *mAnims[NUM_ANIMS]; // master animation list
Ogre::Animation *mSkelAnimations[NUM_ANIMS];
bool mFadingIn[NUM_ANIMS]; // which animations are fading in
bool mFadingOut[NUM_ANIMS]; // which animations are fading out
Ogre::NodeAnimationTrack *mHipsTracks[NUM_ANIMS];
Ogre::NodeAnimationTrack *mRootTracks[NUM_ANIMS];
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 CharacterModule {
CharacterModule(flecs::world &ecs);
void setAnimation(AnimationControl &anim);
void fadeAnimations(AnimationControl &anim, Ogre::Real deltaTime);
void updateCameraGoal(Camera &camera, Ogre::Real deltaYaw,
Ogre::Real deltaPitch, Ogre::Real deltaZoom);
};
struct CharacterAIModule {
CharacterAIModule(flecs::world &ecs);
};
}
#endif
#endif

View File

@@ -1,7 +1,7 @@
#ifndef COMPONENTS_H_
#define COMPONENTS_H_
#include <flecs.h>
#include <Ogre.h>
#include <OgreBullet.h>
namespace Ogre
{
class ImGuiOverlay;
@@ -19,7 +19,6 @@ struct GameData {
};
struct EngineData {
Ogre::SceneManager *mScnMgr;
Ogre::Bullet::DynamicsWorld *mWorld;
float delta;
float startupDelay;
int width;
@@ -44,6 +43,10 @@ struct Input {
bool mouse_moved;
bool wheel_moved;
bool fast;
bool act;
bool act_pressed;
bool act2;
bool act2_pressed;
Input()
: control(0)
, control_prev(0)
@@ -64,17 +67,12 @@ struct RenderWindow {
Ogre::RenderWindow *window;
float dpi;
};
struct App {
Ogre::ImGuiOverlay *mGuiOverlay;
OgreBites::InputListenerChain *mInput;
std::vector<OgreBites::InputListener *> listeners;
};
struct CollisionShape {
btCollisionShape *shape;
void *shape;
};
struct InWater {};
struct TerrainReady {};
struct WaterReady {};
struct GroundCheckReady {};
struct Body2Entity {
/* std::unordered_map<btCollisionObject *, flecs::entity> entities; */
};
}
#endif

View File

@@ -0,0 +1,13 @@
#include "EventModule.h"
namespace ECS
{
EventModule::EventModule(flecs::world &ecs)
{
ecs.module<EventModule>();
ecs.component<EventData>();
}
void EventModule::send_event(flecs::entity from, const Ogre::String &event,
flecs::entity to)
{
}
}

View File

@@ -0,0 +1,29 @@
#ifndef _EVENT_MODULE_H_
#define _EVENT_MODULE_H_
#include <flecs.h>
#include <Ogre.h>
namespace ECS
{
struct EventData {
struct EventArgs {
int type;
};
struct Event {
flecs::entity sender;
Ogre::String event;
flecs::entity e1, e2;
};
std::list<Event> events;
void add(flecs::entity sender, const Ogre::String &event,
flecs::entity e1, flecs::entity e2)
{
events.push_back({ sender, event, e1, e2 });
}
};
struct EventModule {
EventModule(flecs::world &ecs);
void send_event(flecs::entity from, const Ogre::String &event,
flecs::entity to);
};
}
#endif

View File

@@ -1,11 +1,15 @@
#include <iostream>
#include <OgreBullet.h>
#include <OgreMeshManager.h>
#include "Components.h"
#include "GameData.h"
#include "LuaData.h"
#include "EventModule.h"
#include "EventTriggerModule.h"
struct TriggerBody {
void *data;
};
#if 0
struct TriggerBody {
btPairCachingGhostObject *mBody;
btCylinderShape *shape;
@@ -58,11 +62,50 @@ struct DeepPenetrationContactResultCallback : public btManifoldResult {
}
}
};
#endif
ECS::EventTriggerModule::EventTriggerModule(flecs::world &ecs)
{
ecs.module<EventTriggerModule>();
ecs.component<EventTriggerExit>();
ecs.component<EventTrigger>();
ecs.component<EventTriggerData>();
ecs.component<InTrigger>();
ecs.component<TriggeredBy>();
ecs.import <EventModule>();
ecs.import <LuaModule>();
ecs.observer<const EngineData, const EventTrigger>("CreateTrigger")
.event(flecs::OnSet)
.each([](flecs::entity e, const EngineData &eng,
const EventTrigger &trigger) {
e.set<EventTriggerData>({});
e.set<EventData>({});
});
ecs.observer<const EventTrigger, EventData>("CreateTriggerEvent")
.event(flecs::OnSet)
.each([](flecs::entity e, const EventTrigger &trigger,
EventData &evt) {
evt.add(e, "actuator_created", e, e);
});
ecs.system<const EventTrigger, EventData>("HandleEventSystem")
.kind(flecs::OnUpdate)
.each([](flecs::entity e, const EventTrigger &trigger,
EventData &evt) {
if (e.parent().is_valid() &&
e.parent().has<EventData>()) {
bool added = false;
for (auto ev : evt.events) {
e.parent().get_mut<EventData>().add(
ev.sender, ev.event, ev.e1,
ev.e2);
added = true;
}
evt.events.clear();
if (added)
e.parent().modified<EventData>();
}
});
#if 0
ecs.component<EventTriggerData>();
ecs.component<TriggerBody>().on_add([](flecs::entity e,
TriggerBody &body) {
bool kinematic = false;
@@ -81,14 +124,6 @@ ECS::EventTriggerModule::EventTriggerModule(flecs::world &ecs)
e.get<EventTrigger>().position,
Ogre::Quaternion(0, 0, 0, 1));
}
/*
Ogre::MeshPtr mesh =
Ogre::MeshManager::getSingleton().createManual(
"trigger", "General");
Ogre::Entity *ent =
ECS::get<EngineData>().mScnMgr->createEntity(mesh);
body.mSceneNode->attachObject(ent);
*/
body.mBody = new btPairCachingGhostObject();
body.mBody->getWorldTransform().setOrigin(Ogre::Bullet::convert(
body.mSceneNode->_getDerivedPosition()));
@@ -102,10 +137,6 @@ ECS::EventTriggerModule::EventTriggerModule(flecs::world &ecs)
if (kinematic)
flags |= btCollisionObject::CF_STATIC_OBJECT;
body.mBody->setCollisionFlags(flags);
/*
ECS::get<EngineData>().mWorld->attachCollisionObject(
body.mBody, ent, 16, 0x1);
*/
ECS::get<EngineData>().mWorld->getBtWorld()->addCollisionObject(
body.mBody, 16, 0x1);
struct EntityCollisionListener {
@@ -139,15 +170,19 @@ ECS::EventTriggerModule::EventTriggerModule(flecs::world &ecs)
body.mSceneNode->getUserObjectBindings().setUserAny(
"BtCollisionObject", objWrapper);
});
ecs.component<EventTrigger>().on_set(
[](flecs::entity e, EventTrigger &ev) {
e.add<TriggerBody>();
});
ecs.system<const EngineData, const EventTrigger, TriggerBody>(
"CheckCollisions")
ecs.component<EventTrigger>().on_set([](flecs::entity e,
EventTrigger &ev) {
e.add<TriggerBody>();
e.set<EventTriggerData>({});
ECS::get<LuaBase>().mLua->call_handler("actuator_created", e,
e);
});
ecs.system<const EngineData, const EventTrigger, TriggerBody,
EventTriggerData>("CheckCollisions")
.kind(flecs::OnUpdate)
.each([](flecs::entity e, const EngineData &eng,
const EventTrigger &evt, TriggerBody &body) {
const EventTrigger &evt, TriggerBody &body,
EventTriggerData &data) {
btDispatcher *dispatch =
eng.mWorld->getBtWorld()->getDispatcher();
btHashedOverlappingPairCache *cache =
@@ -255,11 +290,40 @@ ECS::EventTriggerModule::EventTriggerModule(flecs::world &ecs)
.end()) {
body.contactBodies
.insert(other);
OgreAssert(
ECS::get<
Body2Entity>()
.entities
.find(const_cast<
btCollisionObject
*>(
other)) !=
ECS::get<
Body2Entity>()
.entities
.end(),
"No body to entity mapping");
flecs::entity obj_e =
ECS::get<
Body2Entity>()
.entities
.at(const_cast<
btCollisionObject
*>(
other));
ECS::get<
LuaBase>()
.mLua
->call_handler(
evt.event);
evt.event +
"_enter",
e,
obj_e);
obj_e.add<
InTrigger>(
e);
e.add<TriggeredBy>(
obj_e);
}
}
}
@@ -271,21 +335,24 @@ ECS::EventTriggerModule::EventTriggerModule(flecs::world &ecs)
while (it != body.contactBodies.end()) {
if (currentContactBodies.find(*it) ==
currentContactBodies.end()) {
if (e.has<EventTriggerExit>()) {
const Ogre::String &exit_event =
ECS::get<
EventTriggerExit>()
.event;
ECS::get<LuaBase>()
.mLua->call_handler(
exit_event);
} else {
std::cout << "body exited"
<< std::endl;
}
body.contactBodies.erase(*it);
flecs::entity obj_e =
ECS::get<Body2Entity>()
.entities
.at(const_cast<
btCollisionObject
*>(
*it));
ECS::get<LuaBase>().mLua->call_handler(
evt.event + "_exit", e, obj_e);
obj_e.remove<InTrigger>(e);
e.remove<TriggeredBy>(obj_e);
std::cout << "body exited" << std::endl;
it = body.contactBodies.erase(it);
if (it == body.contactBodies.end())
break;
}
it++;
}
});
#endif
}

View File

@@ -10,10 +10,14 @@ struct EventTrigger {
float halfheight;
float radius;
Ogre::String event;
Ogre::SceneNode *node;
bool once;
};
struct EventTriggerExit {
Ogre::String event;
struct EventTriggerData {
std::set<flecs::entity_t> entities;
};
struct InTrigger {};
struct TriggeredBy {};
struct EventTriggerModule {
EventTriggerModule(flecs::world &ecs);
};

View File

@@ -11,15 +11,22 @@
#include "GameData.h"
#include "Components.h"
#include "LuaData.h"
#include "AppModule.h"
#include "GUIModule.h"
namespace ECS
{
struct GUIListener;
struct EditorGUIListener;
struct GUIData {
Ogre::ImGuiOverlay *mGuiOverlay;
std::vector<Ogre::String> glb_names;
GUIListener *mGUIListener;
};
struct EditorGUIData {
Ogre::ImGuiOverlay *mGuiOverlay;
std::vector<Ogre::String> glb_names;
EditorGUIListener *mGUIListener;
};
struct GUIListener : public Ogre::RenderTargetListener {
float panel_width;
bool enableEditor;
@@ -459,8 +466,537 @@ struct GUIListener : public Ogre::RenderTargetListener {
}
};
struct EditorGUIListener : public Ogre::RenderTargetListener {
float panel_width;
bool enableEditor;
bool enableMapEditor;
ImFont *smallFont, *midFont, *bigFont;
Ogre::FontPtr _smallFont, _midFont, _bigFont;
EditorGUIListener(Ogre::ImGuiOverlay *overlay)
: Ogre::RenderTargetListener()
{
std::cout << "WAAAAAAA!!!!!" << std::endl;
_midFont = createFont("midFont", "General",
"Jupiteroid-Regular.ttf", 18.0f);
_smallFont = createFont("smallFont", "General",
"Jupiteroid-Regular.ttf", 13.0f);
_bigFont = createFont("bigFont", "General", "Kenney Bold.ttf",
32.0f);
smallFont = overlay->addFont("smallFont", "General");
OgreAssert(smallFont, "Could not load font");
midFont = overlay->addFont("midFont", "General");
OgreAssert(midFont, "Could not load font");
bigFont = overlay->addFont("bigFont", "General");
OgreAssert(bigFont, "Could not load font");
#if 0
Ogre::FontPtr _midFont = createFont("midFont", "General",
"Kenney Bold.ttf", 28.0f);
#endif
#if 0
ImGui::GetIO().Fonts->Build();
#endif
}
Ogre::FontPtr createFont(const Ogre::String &name,
const Ogre::String &group,
const Ogre::String &ttfname, float fontSize)
{
Ogre::FontPtr ret =
Ogre::FontManager::getSingleton().create(name, group);
ret->setType(Ogre::FontType::FT_TRUETYPE);
ret->setSource(ttfname);
ret->setTrueTypeSize(fontSize);
ret->setTrueTypeResolution(75);
ret->addCodePointRange(Ogre::Font::CodePointRange(30, 128));
ret->load();
return ret;
}
void
preViewportUpdate(const Ogre::RenderTargetViewportEvent &evt) override
{
preview(evt);
}
void buttons_panel()
{
ImVec2 size = ImGui::GetMainViewport()->Size;
float window_width = size.x * 0.2f;
if (window_width > panel_width)
window_width = panel_width;
float window_height = size.y * 0.5f - 20;
ImGui::SetNextWindowPos(ImVec2(0, size.y * 0.5f + 20),
ImGuiCond_Always);
ImGui::SetNextWindowSize(ImVec2(window_width, window_height),
ImGuiCond_Always);
ImGui::Begin("Control");
// if (ECS::get().get<GUI>().enabled)
// ECS::get().get<App>().app->setWindowGrab(true);
if (ImGui::Button("Quit"))
Ogre::Root::getSingleton().queueEndRendering();
if (ImGui::Button("Return"))
ECS::get().get<GUI>().finish();
if (ImGui::Button("Enable/Disable item editor")) {
enableEditor ^= true;
if (enableEditor)
enableMapEditor = false;
}
if (ImGui::Button("Enable/Disable map editor")) {
enableMapEditor ^= true;
if (enableMapEditor)
enableEditor = false;
}
ImGui::Text("Text message...");
ImGui::End();
}
void create_entity_node(const Ogre::String &name, int key)
{
Ogre::Entity *ent =
ECS::get().get<EngineData>().mScnMgr->createEntity(
name);
Ogre::SceneNode *pnode =
ECS::get()
.get<EngineData>()
.mScnMgr->getRootSceneNode()
->createChildSceneNode(
"ent:" + name +
Ogre::StringConverter::toString(
key),
ECS::get()
.get<Camera>()
.mCameraPivot->getPosition(),
ECS::get()
.get<Camera>()
.mCameraPivot->getOrientation());
pnode->attachObject(ent);
Ogre::Quaternion q = pnode->getOrientation();
Ogre::Radian yaw = q.getYaw();
Ogre::Quaternion nq(yaw, Ogre::Vector3(0, 1, 0));
pnode->setOrientation(nq);
ECS::get().get<GUI>().finish();
}
void buildings_editor()
{
int i;
ImVec2 size = ImGui::GetMainViewport()->Size;
float window_width = size.x * 0.2f;
if (window_width > panel_width)
window_width = panel_width;
float window_height = size.y * 0.5 - 20;
ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always);
ImGui::SetNextWindowSize(ImVec2(window_width, window_height),
ImGuiCond_Always);
ImGui::Begin("Droppings...");
for (i = 0;
i < ECS::get().get<EditorGUIData>().glb_names.size();
i++) {
Ogre::String id_button =
"Create entity: " +
ECS::get().get<EditorGUIData>().glb_names[i] +
"##ent:" +
ECS::get().get<EditorGUIData>().glb_names[i];
if (ImGui::Button(id_button.c_str())) {
create_entity_node(ECS::get()
.get<EditorGUIData>()
.glb_names[i],
i);
}
}
ImGui::End();
}
void position_editor(Ogre::SceneNode *node)
{
Ogre::Vector3 position = node->getPosition();
float v[3] = { position.x, position.y, position.z };
ImGui::InputFloat3("position", v);
position.x = v[0];
position.y = v[1];
position.z = v[2];
node->setPosition(position);
}
void orientation_editor(Ogre::SceneNode *node)
{
Ogre::Quaternion q = node->getOrientation();
float yaw = Ogre::Radian(q.getYaw()).valueDegrees();
float pitch = Ogre::Radian(q.getPitch()).valueDegrees();
float roll = Ogre::Radian(q.getRoll()).valueDegrees();
bool m1 = ImGui::InputFloat("yaw", &yaw);
bool m2 = ImGui::InputFloat("pitch", &pitch);
bool m3 = ImGui::InputFloat("roll", &roll);
if (m1 || m2 || m3) {
Ogre::Quaternion q1(Ogre::Radian(Ogre::Degree(yaw)),
Ogre::Vector3::UNIT_Y);
Ogre::Quaternion q2(Ogre::Degree(pitch),
Ogre::Vector3::UNIT_X);
Ogre::Quaternion q3(Ogre::Degree(roll),
Ogre::Vector3::UNIT_Z);
node->setOrientation(q1 * q2 * q3);
}
}
void attachments_editor(Ogre::SceneNode *node)
{
const Ogre::SceneNode::ObjectMap &pmap =
node->getAttachedObjects();
int i;
for (i = 0; i < pmap.size(); i++) {
const Ogre::MovableObject *mobj = pmap[i];
const Ogre::String &pname = mobj->getName();
ImGui::Text("Name: %s", pname.c_str());
}
}
void map_editor()
{
}
void preview(const Ogre::RenderTargetViewportEvent &evt)
{
int i;
Ogre::ImGuiOverlay::NewFrame();
std::cout << "GUI enabled: " << ECS::get().get<GUI>().enabled
<< std::endl;
if (ECS::get().get<GUI>().enabled) {
buttons_panel();
buildings_editor();
map_editor();
ImVec2 size = ImGui::GetMainViewport()->Size;
float window_width = size.x * 0.2f;
if (window_width > panel_width)
window_width = panel_width;
float window_height = size.y * 0.5f - 20;
ImGui::SetNextWindowPos(ImVec2(size.x - window_width,
size.y * 0.5f + 20),
ImGuiCond_Always);
ImGui::SetNextWindowSize(ImVec2(window_width,
window_height),
ImGuiCond_Always);
// ImGui::Begin("Dumb and Stupid", &mKbd.gui_active);
ImGui::Begin("Panel...");
std::deque<Ogre::SceneNode *> tree_input_queue,
tree_output_queue;
std::vector<Ogre::SceneNode *> tree_list;
tree_input_queue.push_back(
ECS::get()
.get<EngineData>()
.mScnMgr->getRootSceneNode());
tree_input_queue.push_back(nullptr);
std::set<Ogre::SceneNode *> visited;
while (true) {
int new_nodes_count = 0;
while (!tree_input_queue.empty()) {
int child;
Ogre::SceneNode *item =
tree_input_queue.front();
tree_input_queue.pop_front();
if (item &&
visited.find(item) ==
visited.end()) { // new node
new_nodes_count++;
tree_output_queue.push_back(
item);
visited.insert(item);
const Ogre::Node::ChildNodeMap
&children =
item->getChildren();
for (child = 0;
child < children.size();
child++) {
tree_output_queue.push_back(
static_cast<
Ogre::SceneNode
*>(
children[child]));
tree_output_queue
.push_back(
nullptr);
}
} else
tree_output_queue.push_back(
item);
}
if (new_nodes_count == 0)
break;
tree_input_queue = tree_output_queue;
tree_output_queue.clear();
}
tree_list.insert(tree_list.begin(),
tree_output_queue.begin(),
tree_output_queue.end());
int count = 0;
int depth = 0;
std::vector<int> check_depth;
int max_depth = 0;
check_depth.push_back(0);
for (count = 0; count < tree_list.size(); count++) {
int t;
Ogre::SceneNode *node = tree_list[count];
if (node && max_depth >= depth) {
Ogre::String name = node->getName();
if (name.length() == 0) {
name = "Node #" +
Ogre::StringConverter::
toString(count);
}
if (ImGui::TreeNode(name.c_str())) {
check_depth.push_back(
max_depth);
max_depth++;
ImGui::Text("%s",
(name + "##caption")
.c_str());
position_editor(node);
ImGui::Separator();
orientation_editor(node);
ImGui::Separator();
ImGui::Text("Attachments");
attachments_editor(node);
}
} else if (!node && max_depth >= depth) {
max_depth = check_depth.back();
check_depth.pop_back();
ImGui::TreePop();
}
if (tree_list[count])
depth++;
else
depth--;
}
ImGui::Spacing();
ImGui::End();
#if 0
if (ECS::get().get<GUI>().narrationBox) {
ImVec2 size = ImGui::GetMainViewport()->Size;
ImGui::SetNextWindowPos(ImVec2(0,
size.y * 0.75f),
ImGuiCond_Always);
ImGui::SetNextWindowSize(ImVec2(size.x,
size.y * 0.25f),
ImGuiCond_Always);
ImGui::Begin("Narration...", NULL,
ImGuiWindowFlags_NoTitleBar);
ImGui::PushFont(midFont);
ImVec2 p = ImGui::GetCursorScreenPos();
ImGui::TextWrapped(
"%s", ECS::get()
.get<GUI>()
.narrationText.c_str());
if (ECS::get().get<GUI>().choices.size() == 0) {
ImGui::SetCursorScreenPos(p);
if (ImGui::InvisibleButton(
"Background",
ImGui::GetWindowSize()))
ECS::get<LuaBase>().mLua->call_handler(
"narration_progress");
} else {
int i;
for (i = 0; i < ECS::get()
.get<GUI>()
.choices.size();
i++) {
if (ImGui::Button(
ECS::get()
.get<GUI>()
.choices[i]
.c_str())) {
ECS::get()
.get_mut<GUI>()
.narration_answer =
i + 1;
std::cout << "answer: "
<< i + 1
<< std::endl;
ECS::modified<GUI>();
ECS::get<LuaBase>()
.mLua
->call_handler(
"narration_answered");
}
}
}
ImGui::Spacing();
ImGui::PopFont();
ImGui::End();
} else if (ECS::get().get<GUI>().mainMenu) {
ImVec2 size = ImGui::GetMainViewport()->Size;
ImGui::SetNextWindowPos(ImVec2(0, 0),
ImGuiCond_Always);
ImGui::SetNextWindowSize(ImVec2(size.x, size.y),
ImGuiCond_Always);
ImGui::Begin(
"MainMenu", nullptr,
ImGuiWindowFlags_NoTitleBar |
ImGuiWindowFlags_NoDecoration |
ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoFocusOnAppearing |
0);
// if (ECS::get().get<GUI>().enabled)
// ECS::get().get<App>().app->setWindowGrab(true);
ImGui::PushFont(bigFont);
ImGui::TextWrapped("%s", "Booo!!!!");
bool pressed = false;
bool new_game = false, cont = false,
load_game = false, opts = false,
quit = false;
ImGui::SetCursorPosY(size.y / 2.0f - 300.0f);
ImGui::SetCursorPosX(size.x / 2.0f - 300.0f);
new_game = ImGui::Button("New Game");
ImGui::SetCursorPosX(size.x / 2.0f - 300.0f);
cont = ImGui::Button("Continue");
ImGui::SetCursorPosX(size.x / 2.0f - 300.0f);
load_game = ImGui::Button("Load Game");
ImGui::SetCursorPosX(size.x / 2.0f - 300.0f);
opts = ImGui::Button("Options");
ImGui::SetCursorPosX(size.x / 2.0f - 300.0f);
quit = ImGui::Button("Quit###quit");
pressed = new_game || cont || load_game ||
opts || quit;
ImGui::PopFont();
ImGui::Spacing();
ImGui::End();
if (quit)
Ogre::Root::getSingleton()
.queueEndRendering();
if (pressed)
ECS::get().get<GUI>().finish();
if (new_game) {
ECS::get<LuaBase>().mLua->call_handler(
"new_game");
}
} else {
buttons_panel();
if (enableEditor)
buildings_editor();
if (enableMapEditor)
map_editor();
ImVec2 size = ImGui::GetMainViewport()->Size;
float window_width = size.x * 0.2f;
if (window_width > panel_width)
window_width = panel_width;
float window_height = size.y * 0.5f - 20;
ImGui::SetNextWindowPos(
ImVec2(size.x - window_width,
size.y * 0.5f + 20),
ImGuiCond_Always);
ImGui::SetNextWindowSize(ImVec2(window_width,
window_height),
ImGuiCond_Always);
// ImGui::Begin("Dumb and Stupid", &mKbd.gui_active);
ImGui::Begin("Panel...");
std::deque<Ogre::SceneNode *> tree_input_queue,
tree_output_queue;
std::vector<Ogre::SceneNode *> tree_list;
tree_input_queue.push_back(
ECS::get()
.get<EngineData>()
.mScnMgr->getRootSceneNode());
tree_input_queue.push_back(nullptr);
std::set<Ogre::SceneNode *> visited;
while (true) {
int new_nodes_count = 0;
while (!tree_input_queue.empty()) {
int child;
Ogre::SceneNode *item =
tree_input_queue.front();
tree_input_queue.pop_front();
if (item &&
visited.find(item) ==
visited.end()) { // new node
new_nodes_count++;
tree_output_queue
.push_back(
item);
visited.insert(item);
const Ogre::Node::ChildNodeMap
&children =
item->getChildren();
for (child = 0;
child <
children.size();
child++) {
tree_output_queue
.push_back(static_cast<
Ogre::SceneNode
*>(
children[child]));
tree_output_queue
.push_back(
nullptr);
}
} else
tree_output_queue
.push_back(
item);
}
if (new_nodes_count == 0)
break;
tree_input_queue = tree_output_queue;
tree_output_queue.clear();
}
tree_list.insert(tree_list.begin(),
tree_output_queue.begin(),
tree_output_queue.end());
int count = 0;
int depth = 0;
std::vector<int> check_depth;
int max_depth = 0;
check_depth.push_back(0);
for (count = 0; count < tree_list.size();
count++) {
int t;
Ogre::SceneNode *node =
tree_list[count];
if (node && max_depth >= depth) {
Ogre::String name =
node->getName();
if (name.length() == 0) {
name = "Node #" +
Ogre::StringConverter::
toString(
count);
}
if (ImGui::TreeNode(
name.c_str())) {
check_depth.push_back(
max_depth);
max_depth++;
ImGui::Text(
"%s",
(name +
"##caption")
.c_str());
position_editor(node);
ImGui::Separator();
orientation_editor(
node);
ImGui::Separator();
ImGui::Text(
"Attachments");
attachments_editor(
node);
}
} else if (!node &&
max_depth >= depth) {
max_depth = check_depth.back();
check_depth.pop_back();
ImGui::TreePop();
}
if (tree_list[count])
depth++;
else
depth--;
}
ImGui::Spacing();
ImGui::End();
}
#endif
}
}
};
GUIModule::GUIModule(flecs::world &ecs)
{
ecs.module<GUIModule>();
ecs.import <AppModule>();
ecs.component<GUI>()
.on_add([](GUI &gui) {
gui.enabled = false;
@@ -522,4 +1058,46 @@ GUIModule::GUIModule(flecs::world &ecs)
}
});
}
EditorGUIModule::EditorGUIModule(flecs::world &ecs)
{
ecs.module<EditorGUIModule>();
ecs.import <AppModule>();
ecs.component<GUI>()
.on_add([](GUI &gui) {
gui.enabled = true;
gui.grab = false;
gui.grabChanged = false;
})
.add(flecs::Singleton);
ecs.component<EditorGUIData>()
.on_add([](EditorGUIData &priv) {
priv.glb_names.clear();
priv.mGUIListener = nullptr;
priv.mGuiOverlay = nullptr;
})
.add(flecs::Singleton);
ecs.observer<const RenderWindow, const App, GUI>("SupportEditorGUI")
.event(flecs::OnSet)
.without<EditorGUIData>()
.each([](const RenderWindow &window, const App &app, GUI &gui) {
float vpScale = window.dpi / 96 *
window.window->getWidth() / 1600.0f;
Ogre::OverlayManager::getSingleton().setPixelRatio(
vpScale);
std::cout << "Editor GUI configure\n";
OgreAssert(app.mGuiOverlay, "No ImGUI overlay");
Ogre::ImGuiOverlay *guiOverlay = app.mGuiOverlay;
EditorGUIListener *guiListener =
new EditorGUIListener(guiOverlay);
guiOverlay->setZOrder(300);
guiOverlay->show();
guiListener->panel_width = 300.0f;
guiListener->enableEditor = false;
window.window->addListener(guiListener);
ECS::get().set<EditorGUIData>(
{ app.mGuiOverlay, {}, guiListener });
std::cout << "Editor GUI configure finished\n";
});
}
}

View File

@@ -17,11 +17,11 @@ struct GUI {
int narration_answer;
static void setWindowGrab(bool g = true)
{
ECS::GUI &gui = ECS::get().get_mut<ECS::GUI>();
ECS::GUI &gui = ECS::get().get_mut<GUI>();
if (gui.grab != g) {
gui.grab = g;
gui.grabChanged = true;
ECS::get().modified<ECS::GUI>();
ECS::get().modified<GUI>();
}
}
static void finish()
@@ -38,5 +38,8 @@ struct GUIModule {
flecs::entity ui_wait;
GUIModule(flecs::world &ecs);
};
struct EditorGUIModule {
EditorGUIModule(flecs::world &ecs);
};
}
#endif

View File

@@ -11,42 +11,50 @@
#include "WorldMapModule.h"
#include "BoatModule.h"
#include "EventTriggerModule.h"
#include "CharacterAnimationModule.h"
#include "PhysicsModule.h"
#include "EventModule.h"
#include "CharacterManagerModule.h"
#include "VehicleManagerModule.h"
#include "AppModule.h"
#include "world-build.h"
namespace ECS
{
static flecs::world ecs;
flecs::entity player;
void setup(Ogre::SceneManager *scnMgr, Ogre::Bullet::DynamicsWorld *world,
Ogre::SceneNode *cameraNode, Ogre::Camera *camera,
Ogre::RenderWindow *window)
void setup_minimal()
{
std::cout << "Setup GameData\n";
ecs.component<EngineData>().add(flecs::Singleton);
ecs.component<GameData>().add(flecs::Singleton);
ecs.component<Input>().add(flecs::Singleton);
ecs.component<Camera>().add(flecs::Singleton);
ecs.component<InWater>();
ecs.component<WaterReady>().add(flecs::Singleton);
ecs.component<GroundCheckReady>().add(flecs::Singleton);
ecs.component<App>()
.on_add([](App &app) {
app.mInput = nullptr;
app.mGuiOverlay = nullptr;
app.listeners.clear();
})
.add(flecs::Singleton);
/* lots of things depend on it */
ecs.component<TerrainReady>().add(flecs::Singleton);
ecs.import <GameWorldModule>();
ecs.import <EventModule>();
ecs.import <CharacterManagerModule>();
ecs.import <VehicleManagerModule>();
ecs.import <WaterModule>();
ecs.import <AppModule>();
/* 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)
{
std::cout << "Setup GameData\n";
setup_minimal();
ecs.component<RenderWindow>().add(flecs::Singleton);
ecs.import <CharacterModule>();
ecs.import <TerrainModule>();
ecs.import <BoatModule>();
ecs.import <PhysicsModule>();
ecs.import <WaterModule>();
ecs.import <SunModule>();
ecs.import <TerrainModule>();
ecs.import <GUIModule>();
ecs.import <EventTriggerModule>();
ecs.import <LuaModule>();
ecs.import <WorldMapModule>();
ecs.import <LuaModule>();
ecs.import <BoatModule>();
ecs.import <EventTriggerModule>();
ecs.import <CharacterAnimationModule>();
ecs.system<EngineData>("UpdateDelta")
.kind(flecs::OnUpdate)
@@ -78,9 +86,8 @@ void setup(Ogre::SceneManager *scnMgr, Ogre::Bullet::DynamicsWorld *world,
std::cout << "ground check ready\n";
#endif
});
ecs.set<EngineData>({ scnMgr, world, 0.0f, 5.0f,
(int)window->getWidth(), (int)window->getHeight(),
false });
ecs.set<EngineData>({ scnMgr, 0.0f, 5.0f, (int)window->getWidth(),
(int)window->getHeight(), false });
ecs.set<Camera>({ cameraNode, camera, false });
ecs.add<GameData>();
ecs.add<Input>();
@@ -94,16 +101,92 @@ void setup(Ogre::SceneManager *scnMgr, Ogre::Bullet::DynamicsWorld *world,
nullptr,
false,
{ 0, 0, 0 } });
if (!ecs.has<LuaBase>())
ecs.add<LuaBase>();
if (!ecs.has<LuaEvent>())
ecs.set<LuaEvent>({});
// ecs.set<Body2Entity>({});
std::cout << "Setup GameData done\n";
/* Create player */
player = ecs.entity("player");
Ogre::Vector3 playerPos(0, 0, 4);
player.set<CharacterLocation>({ { 0, 0, 0, 1 }, playerPos });
player.set<CharacterConf>({ "normal-male.glb" });
player.add<Character>();
player.add<Player>();
player = ecs.get_mut<CharacterManagerModule>().createPlayer(
{ 0, 0, 4 }, Ogre::Quaternion(Ogre::Radian(Ogre::Math::PI),
Ogre::Vector3::UNIT_Y));
}
void setupEditor(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);
ecs.import <CharacterModule>();
ecs.import <BoatModule>();
ecs.import <PhysicsModule>();
ecs.import <WaterModule>();
ecs.import <SunModule>();
ecs.import <TerrainModule>();
ecs.import <EditorGUIModule>();
ecs.import <EventTriggerModule>();
ecs.import <LuaModule>();
// ecs.import <WorldMapModule>();
ecs.import <CharacterAnimationModule>();
ecs.system<EngineData>("UpdateDelta")
.kind(flecs::OnUpdate)
.each([](EngineData &eng) {
eng.delta = ECS::get().delta_time();
});
ecs.system<EngineData>("UpdateDelay")
.kind(flecs::OnUpdate)
.with<TerrainReady>()
.with<WaterReady>()
.with<GroundCheckReady>()
.each([](EngineData &eng) {
if (eng.startupDelay >= 0.0f)
eng.startupDelay -= eng.delta;
#ifdef VDEBUG
if (ECS::get().has<GroundCheckReady>())
std::cout << "ground check ready\n";
#endif
});
ecs.system<EngineData>("CheckStatus")
.kind(flecs::OnUpdate)
.run([](flecs::iter &it) {
#ifdef VDEBUG
if (ECS::get().has<WaterReady>())
std::cout << "water ready\n";
if (ECS::get().has<TerrainReady>())
std::cout << "terrain ready\n";
if (ECS::get().has<GroundCheckReady>())
std::cout << "ground check ready\n";
#endif
});
ecs.set<EngineData>({ scnMgr, 0.0f, 5.0f, (int)window->getWidth(),
(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.set<GUI>({ true, true, true, false, false, "", {}, -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)
{
ecs.progress(delta);
@@ -117,4 +200,4 @@ bool Vector3::zeroLength() const
float l = x * x + y * y + z * z;
return (l < 1e-06 * 1e-06);
}
}
}

View File

@@ -1,13 +1,14 @@
#ifndef GAMEDATA_H
#define GAMEDATA_H
#include <OgreBullet.h>
#include <flecs.h>
namespace ECS
{
extern flecs::entity player;
void setup(Ogre::SceneManager *scnMgr, Ogre::Bullet::DynamicsWorld *world,
Ogre::SceneNode *cameraNode, Ogre::Camera *camera,
Ogre::RenderWindow *window);
void setup_minimal();
void setup(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);
void update(float delta);
flecs::world get();
template <class T> const T &get()
@@ -23,4 +24,4 @@ template <class T> void modified()
ECS::get().modified<T>();
}
}
#endif
#endif

View File

@@ -2,21 +2,83 @@
#include "GameData.h"
#include "Components.h"
#include "GUIModule.h"
#include "PhysicsModule.h"
#include "CharacterModule.h"
#include "CharacterAnimationModule.h"
#include "CharacterManagerModule.h"
#include "VehicleManagerModule.h"
#include "BoatModule.h"
#include "EventTriggerModule.h"
#include "SlotsModule.h"
#include "world-build.h"
#include "LuaData.h"
#include "luaaa.hpp"
extern "C" {
int luaopen_lpeg(lua_State *L);
}
struct FooPosition {
Ogre::Vector3 value;
Ogre::Vector3 &get()
{
return value;
}
};
namespace luaaa
{
template <> struct LuaStack<FooPosition> {
inline static FooPosition get(lua_State *L, int idx)
{
FooPosition result;
if (lua_istable(L, idx)) {
lua_pushnil(L);
while (0 != lua_next(L, idx)) {
const int top = lua_gettop(L);
const char *name =
LuaStack<const char *>::get(L, top - 1);
if (strncmp(name, "x", 1) == 0)
result.value.x =
LuaStack<float>::get(L, top);
else if (strncmp(name, "y", 1) == 0)
result.value.y =
LuaStack<float>::get(L, top);
else if (strncmp(name, "z", 1) == 0)
result.value.z =
LuaStack<float>::get(L, top);
lua_pop(L, 1);
}
lua_pop(L, 0);
}
return result;
}
inline static void put(lua_State *L, const FooPosition &v)
{
lua_newtable(L);
LuaStack<const char *>::put(L, "x");
LuaStack<float>::put(L, v.value.x);
lua_rawset(L, -3);
LuaStack<const char *>::put(L, "y");
LuaStack<float>::put(L, v.value.y);
lua_rawset(L, -3);
LuaStack<const char *>::put(L, "z");
LuaStack<float>::put(L, v.value.z);
lua_rawset(L, -3);
}
};
}
namespace ECS
{
struct LuaEcsEntity {
int id;
flecs::entity e;
};
struct idmap {
std::unordered_map<int, flecs::entity> id2entity;
std::unordered_map<flecs::entity_t, int> entity2id;
int next_id;
idmap()
: id2entity({})
, entity2id({})
, next_id(0)
{
}
@@ -27,8 +89,11 @@ struct idmap {
}
int add_entity(flecs::entity e)
{
if (entity2id.find(e.id()) != entity2id.end())
return entity2id[e.id()];
int id = get_next_id();
id2entity[id] = e;
entity2id[e.id()] = id;
return id;
}
flecs::entity get_entity(int id)
@@ -53,12 +118,36 @@ int LuaData::call_handler(const Ogre::String &event)
for (i = 0; i < setup_handlers.size(); i++) {
lua_rawgeti(L, LUA_REGISTRYINDEX, setup_handlers[i]);
lua_pushstring(L, event.c_str());
lua_pcall(L, 1, 0, 0);
lua_pushinteger(L, -1);
lua_pushinteger(L, -1);
if (lua_pcall(L, 3, 0, 0)) {
Ogre::LogManager::getSingleton().stream()
<< lua_tostring(L, -1);
OgreAssert(false, "Lua error");
}
}
return 0;
}
int luaLibraryLoader(lua_State *L)
int LuaData::call_handler(const Ogre::String &event, flecs::entity e,
flecs::entity o)
{
int i;
for (i = 0; i < setup_handlers.size(); i++) {
lua_rawgeti(L, LUA_REGISTRYINDEX, setup_handlers[i]);
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)) {
Ogre::LogManager::getSingleton().stream()
<< lua_tostring(L, -1);
OgreAssert(false, "Lua error");
}
}
return 0;
}
static int luaLibraryLoader(lua_State *L)
{
int i;
if (!lua_isstring(L, 1)) {
@@ -159,6 +248,46 @@ LuaData::LuaData()
#endif
installLibraryLoader(L);
lua_pop(L, 1);
#if 0
luaaa::LuaClass<Ogre::Vector3> luaVector3(L, "Vector3");
luaVector3.ctor<float, float, float>();
luaVector3.get("x", [](Ogre::Vector3 &v) -> float { return v.x; });
luaVector3.set("x", [](Ogre::Vector3 &v, float value) -> void {
v.x = value;
});
luaVector3.get("y", [](Ogre::Vector3 &v) -> float { return v.y; });
luaVector3.set("y", [](Ogre::Vector3 &v, float value) -> void {
v.y = value;
});
luaVector3.get("z", [](Ogre::Vector3 &v) -> float { return v.z; });
luaVector3.set("z", [](Ogre::Vector3 &v, float value) -> void {
v.z = value;
});
luaVector3.fun("squaredLength", &Ogre::Vector3::squaredLength);
luaVector3.fun("length", &Ogre::Vector3::length);
luaVector3.fun("squaredDistance", &Ogre::Vector3::squaredDistance);
luaVector3.fun("distance", &Ogre::Vector3::distance);
#endif
luaaa::LuaModule luaECS(L, "_ecs");
luaECS.fun("position", [](int id) -> FooPosition {
#if 0
flecs::entity e = idmap.get_entity(id);
if (e.has<CharacterBase>()) {
const CharacterBase &cb = e.get<CharacterBase>();
return cb.mBodyNode->_getDerivedPosition();
} else if (e.has<BoatBase>()) {
const BoatBase &boat = e.get<BoatBase>();
return boat.mNode->_getDerivedPosition();
} else if (e.has<EventTrigger>()) {
const EventTrigger &trigger = e.get<EventTrigger>();
return trigger.position;
} else
return Ogre::Vector3(0, 0, 0);
#endif
return FooPosition();
});
lua_pushcfunction(L, [](lua_State *L) -> int {
OgreAssert(false, "Crash function called");
return 0;
@@ -225,35 +354,6 @@ LuaData::LuaData()
return 0;
});
lua_setglobal(L, "main_menu");
lua_pushcfunction(L, [](lua_State *L) -> int {
OgreAssert(lua_gettop(L) == 3, "Invalid parameters");
luaL_checktype(L, 1, LUA_TSTRING);
luaL_checktype(L, 2, LUA_TSTRING);
luaL_checktype(L, 3, LUA_TBOOLEAN);
bool enable = lua_toboolean(L, 3);
flecs::entity e = ECS::get().lookup(lua_tostring(L, 1));
Ogre::String what = lua_tostring(L, 2);
OgreAssert(e.is_valid(), "Invalid character");
OgreAssert(e.has<Character>(), "Not a character");
if (what == "gravity") {
/* clear momentum */
e.get_mut<CharacterVelocity>().gvelocity.y = 0.0f;
e.get_mut<CharacterVelocity>().velocity.y = 0.0f;
e.modified<CharacterVelocity>();
if (enable)
e.add<CharacterGravity>();
else
e.remove<CharacterGravity>();
} else if (what == "buoyancy") {
if (enable)
e.add<CharacterBuoyancy>();
else
e.remove<CharacterBuoyancy>();
} else
OgreAssert(false, "Bad parameter " + what);
return 0;
});
lua_setglobal(L, "ecs_character_params_set");
lua_pushcfunction(L, [](lua_State *L) -> int {
luaL_checktype(L, 1, LUA_TSTRING);
luaL_checktype(L, 2, LUA_TNUMBER);
@@ -266,16 +366,35 @@ LuaData::LuaData()
float x = lua_tonumber(L, 2);
float y = lua_tonumber(L, 3);
float z = lua_tonumber(L, 4);
flecs::entity e = ECS::get().entity();
Ogre::Quaternion orientation(Ogre::Radian(yaw),
Ogre::Vector3(0, 1, 0));
Ogre::Vector3 position(x, y, z);
e.set<BoatType>(
{ "boat.scene", position, orientation });
flecs::entity e =
ECS::get_mut<VehicleManagerModule>()
.createVehicleAtPosition(
"boat", position, orientation);
ECS::modified<VehicleManagerModule>();
int ret = idmap.add_entity(e);
lua_pushinteger(L, ret);
std::cout << "boat created: " << ret << std::endl;
return 1;
} else if (what == "raft") {
float yaw = lua_tonumber(L, 5);
float x = lua_tonumber(L, 2);
float y = lua_tonumber(L, 3);
float z = lua_tonumber(L, 4);
Ogre::Quaternion orientation(Ogre::Radian(yaw),
Ogre::Vector3(0, 1, 0));
Ogre::Vector3 position(x, y, z);
flecs::entity e =
ECS::get_mut<VehicleManagerModule>()
.createVehicleAtPosition(
"raft", position, orientation);
ECS::modified<VehicleManagerModule>();
int ret = idmap.add_entity(e);
lua_pushinteger(L, ret);
std::cout << "raft created: " << ret << std::endl;
return 1;
}
lua_pushinteger(L, -1);
return 1;
@@ -302,6 +421,15 @@ LuaData::LuaData()
return 1;
});
lua_setglobal(L, "ecs_character_trigger");
lua_pushcfunction(L, [](lua_State *L) -> int {
luaL_checktype(L, 1, LUA_TNUMBER); // object
int object = lua_tointeger(L, 1);
flecs::entity object_e = idmap.get_entity(object);
flecs::entity parent_e = object_e.parent();
lua_pushinteger(L, idmap.add_entity(parent_e));
return 1;
});
lua_setglobal(L, "ecs_parent");
lua_pushcfunction(L, [](lua_State *L) -> int {
OgreAssert(lua_gettop(L) == 7, "bad parameters");
luaL_checktype(L, 1, LUA_TNUMBER); // parent
@@ -328,6 +456,49 @@ LuaData::LuaData()
return 1;
});
lua_setglobal(L, "ecs_child_character_trigger");
lua_pushcfunction(L, [](lua_State *L) -> int {
luaL_checktype(L, 1, LUA_TNUMBER); // trigger
luaL_checktype(L, 2, LUA_TNUMBER); // object
int trigger = lua_tointeger(L, 1);
int object = lua_tointeger(L, 2);
flecs::entity trigger_e = idmap.get_entity(trigger);
flecs::entity object_e = idmap.get_entity(object);
Ogre::SceneNode *target_node = nullptr;
Ogre::Any targetAny = trigger_e.get<EventTrigger>()
.node->getUserObjectBindings()
.getUserAny("target");
OgreAssert(targetAny.has_value(), "need target");
target_node = Ogre::any_cast<Ogre::SceneNode *>(targetAny);
Ogre::Vector3 position = target_node->_getDerivedPosition();
Ogre::Quaternion orientation =
target_node->_getDerivedOrientation();
if (object_e.has<CharacterBase>()) {
object_e.get_mut<CharacterBase>()
.mBodyNode->_setDerivedPosition(position);
object_e.get_mut<CharacterBase>()
.mBodyNode->_setDerivedOrientation(orientation);
}
return 0;
});
lua_setglobal(L, "ecs_trigger_set_position");
lua_pushcfunction(L, [](lua_State *L) -> int {
luaL_checktype(L, 1, LUA_TNUMBER); // trigger
int trigger = lua_tointeger(L, 1);
flecs::entity trigger_e = idmap.get_entity(trigger);
Ogre::SceneNode *node = trigger_e.get<EventTrigger>().node;
Ogre::Any animationAny =
node->getUserObjectBindings().getUserAny(
"trigger_animation");
if (animationAny.has_value()) {
Ogre::String animation =
Ogre::any_cast<Ogre::String>(animationAny);
lua_pushstring(L, animation.c_str());
return 1;
}
lua_pushnil(L);
return 1;
});
lua_setglobal(L, "ecs_trigger_get_animation");
lua_pushcfunction(L, [](lua_State *L) -> int {
OgreAssert(lua_gettop(L) == 5, "Invalid parameters");
luaL_checktype(L, 1, LUA_TSTRING); // type
@@ -340,13 +511,13 @@ LuaData::LuaData()
float x = lua_tonumber(L, 2);
float y = lua_tonumber(L, 3);
float z = lua_tonumber(L, 4);
flecs::entity e = ECS::get().entity();
Ogre::Quaternion orientation(Ogre::Radian(yaw),
Ogre::Vector3(0, 1, 0));
Ogre::Vector3::UNIT_Y);
Ogre::Vector3 npcPos(x, y, z);
e.set<CharacterLocation>({ orientation, npcPos });
e.set<CharacterConf>({ type });
e.add<Character>();
flecs::entity e =
ECS::get_mut<CharacterManagerModule>()
.createCharacterData(type, npcPos, orientation);
ECS::modified<CharacterManagerModule>();
lua_pushinteger(L, idmap.add_entity(e));
return 1;
});
@@ -385,20 +556,247 @@ LuaData::LuaData()
});
lua_setglobal(L, "ecs_set_debug_drawing");
lua_pushcfunction(L, [](lua_State *L) -> int {
OgreAssert(lua_gettop(L) == 2, "Bad parameters");
luaL_checktype(L, 1, LUA_TNUMBER); // object
luaL_checktype(L, 2, LUA_TBOOLEAN);
int object = lua_tointeger(L, 1);
OgreAssert(lua_gettop(L) >= 1, "Bad parameters");
luaL_checktype(L, 1, LUA_TSTRING);
Ogre::String command = lua_tostring(L, 1);
if (command == "physics-control") {
OgreAssert(lua_gettop(L) == 3, "Bad parameters");
luaL_checktype(L, 2, LUA_TNUMBER); // object
luaL_checktype(L, 3, LUA_TBOOLEAN);
int object = lua_tointeger(L, 2);
flecs::entity object_e = idmap.get_entity(object);
bool enable = lua_toboolean(L, 3);
OgreAssert(object_e.has<CharacterBase>(),
"Not a character");
PhysicsModule::controlPhysics(object_e, enable);
object_e.add<CharacterUpdatePhysicsState>();
return 0;
} else if (command == "is-player") {
OgreAssert(lua_gettop(L) == 2, "Bad parameters");
luaL_checktype(L, 2, LUA_TNUMBER); // object
int object = lua_tointeger(L, 2);
flecs::entity object_e = idmap.get_entity(object);
lua_pushboolean(L, object_e.has<Player>());
return 1;
} else if (command == "set-actuator") {
OgreAssert(lua_gettop(L) == 3, "Bad parameters");
luaL_checktype(L, 2, LUA_TNUMBER); // object
luaL_checktype(L, 3, LUA_TSTRING); // animation
Ogre::String animation = lua_tostring(L, 3);
int object = lua_tointeger(L, 2);
flecs::entity object_e = idmap.get_entity(object);
object_e.set<CharacterVelocity>(
{ { 0, 0, 0 }, { 0, 0, 0 } });
if (animation.length() > 0)
object_e.set<CharacterInActuator>(
{ animation, { 0, 0, 0 } });
else
object_e.remove<CharacterInActuator>();
return 0;
} else if (command == "animation-state") {
OgreAssert(lua_gettop(L) >= 4, "Bad parameters");
luaL_checktype(L, 2, LUA_TNUMBER); // object
luaL_checktype(L, 3, LUA_TSTRING); // node
luaL_checktype(L, 4, LUA_TSTRING); // state
if (lua_gettop(L) == 5)
luaL_checktype(L, 5, LUA_TBOOLEAN); // reset
int object = lua_tointeger(L, 2);
flecs::entity object_e = idmap.get_entity(object);
Ogre::String nodeName = lua_tostring(L, 3);
Ogre::String stateName = lua_tostring(L, 4);
bool reset = false;
if (lua_gettop(L) == 5)
reset = lua_toboolean(L, 5);
GameWorld::ValueParameter<flecs::entity> *obj =
object_e.world()
.get_mut<GameWorld>()
.allocate_entity(object_e);
GameWorld::ValueParameter<std::string> *obj_node =
object_e.world()
.get_mut<GameWorld>()
.allocate_string(nodeName);
GameWorld::ValueParameter<std::string> *obj_state =
object_e.world()
.get_mut<GameWorld>()
.allocate_string(stateName);
ECS::get_mut<GameWorld>().command("set_animation_state",
{ obj, obj_node,
obj_state });
ECS::modified<GameWorld>();
return 0;
} else if (command == "params-set") {
OgreAssert(lua_gettop(L) == 4, "Invalid parameters");
luaL_checktype(L, 2, LUA_TNUMBER);
luaL_checktype(L, 3, LUA_TSTRING);
luaL_checktype(L, 4, LUA_TBOOLEAN);
bool enable = lua_toboolean(L, 4);
flecs::entity e = idmap.get_entity(lua_tointeger(L, 2));
Ogre::String what = lua_tostring(L, 3);
OgreAssert(e.is_valid(), "Invalid character");
OgreAssert(e.has<Character>(), "Not a character");
if (what == "gravity") {
/* clear momentum */
if (e.has<CharacterVelocity>()) {
e.get_mut<CharacterVelocity>()
.gvelocity.y = 0.0f;
e.get_mut<CharacterVelocity>()
.velocity.y = 0.0f;
e.modified<CharacterVelocity>();
}
if (enable)
e.add<CharacterGravity>();
else
e.remove<CharacterGravity>();
} else if (what == "buoyancy") {
if (enable)
e.add<CharacterBuoyancy>();
else
e.remove<CharacterBuoyancy>();
} else
OgreAssert(false, "Bad parameter " + what);
return 0;
} else {
OgreAssert(false, "bad argument " + command);
return 0;
}
});
lua_setglobal(L, "ecs_character");
lua_pushcfunction(L, [](lua_State *L) -> int {
OgreAssert(lua_gettop(L) == 3, "Bad parameters");
luaL_checktype(L, 1, LUA_TNUMBER); // parent
luaL_checktype(L, 2, LUA_TNUMBER); // object
luaL_checktype(L, 3, LUA_TSTRING); // slot
int parent = lua_tointeger(L, 1);
int object = lua_tointeger(L, 2);
Ogre::String slot = lua_tostring(L, 3);
flecs::entity parent_e = idmap.get_entity(parent);
flecs::entity object_e = idmap.get_entity(object);
bool enable = lua_toboolean(L, 2);
if (enable)
object_e.remove<CharacterDisablePhysics>();
else
object_e.add<CharacterDisablePhysics>();
object_e.add<CharacterUpdatePhysicsState>();
PhysicsModule::controlPhysics(object_e, false);
object_e.set<ParentSlot>({ parent_e, slot });
return 0;
});
lua_setglobal(L, "ecs_character_physics_control");
lua_setglobal(L, "ecs_set_slot");
lua_pushcfunction(L, [](lua_State *L) -> int {
flecs::entity e = ECS::get().lookup("player");
int result = idmap.add_entity(e);
lua_pushinteger(L, result);
return result;
});
lua_setglobal(L, "ecs_get_player_entity");
lua_pushcfunction(L, [](lua_State *L) -> int {
luaL_checktype(L, 1, LUA_TNUMBER); // entity id
int id = lua_tointeger(L, 1);
flecs::entity e = idmap.get_entity(id);
LuaEcsEntity *edata = static_cast<LuaEcsEntity *>(
lua_newuserdata(L, sizeof(LuaEcsEntity)));
new (edata) LuaEcsEntity();
edata->e = e;
edata->id = e;
luaL_getmetatable(L, "FlecsEntityMetaTable");
lua_setmetatable(L, -2);
return 1;
});
lua_setglobal(L, "ecs_get_entity");
luaL_newmetatable(L, "FlecsEntityMetaTable");
lua_pushstring(L, "__index");
lua_pushcfunction(L, [](lua_State *L) -> int {
luaL_checktype(L, 1, LUA_TUSERDATA); //metatable
luaL_checktype(L, 2, LUA_TSTRING); //function
Ogre::String component = lua_tostring(L, 2);
if (component == "components") {
lua_pushvalue(L, 1);
lua_pushcclosure(
L,
[](lua_State *L) -> int {
luaL_checktype(L, lua_upvalueindex(1),
LUA_TUSERDATA);
LuaEcsEntity *ent = static_cast<
LuaEcsEntity *>(lua_touserdata(
L, lua_upvalueindex(1)));
std::cout << ent->id << " called!!!\n";
ent->e.each([&](flecs::id id) {
flecs::entity cmp =
ECS::get().entity(id);
if (cmp.is_alive()) {
const char *name =
cmp.name();
if (name)
std::cout
<< "component: "
<< name
<< std::endl;
}
});
return 0;
},
1);
} else if (component == "is_trigger") {
lua_pushvalue(L, 1);
lua_pushcclosure(
L,
[](lua_State *L) -> int {
luaL_checktype(L, lua_upvalueindex(1),
LUA_TUSERDATA);
LuaEcsEntity *ent = static_cast<
LuaEcsEntity *>(lua_touserdata(
L, lua_upvalueindex(1)));
lua_pushboolean(
L, ent->e.has<EventTrigger>());
return 1;
},
1);
} else if (component == "is_character") {
lua_pushvalue(L, 1);
lua_pushcclosure(
L,
[](lua_State *L) -> int {
luaL_checktype(L, lua_upvalueindex(1),
LUA_TUSERDATA);
LuaEcsEntity *ent = static_cast<
LuaEcsEntity *>(lua_touserdata(
L, lua_upvalueindex(1)));
lua_pushboolean(
L, ent->e.has<Character>());
return 1;
},
1);
} else if (component == "is_boat") {
lua_pushvalue(L, 1);
lua_pushcclosure(
L,
[](lua_State *L) -> int {
luaL_checktype(L, lua_upvalueindex(1),
LUA_TUSERDATA);
LuaEcsEntity *ent = static_cast<
LuaEcsEntity *>(lua_touserdata(
L, lua_upvalueindex(1)));
lua_pushboolean(L,
ent->e.has<BoatBase>());
return 1;
},
1);
} else if (component == "is_player") {
lua_pushvalue(L, 1);
lua_pushcclosure(
L,
[](lua_State *L) -> int {
luaL_checktype(L, lua_upvalueindex(1),
LUA_TUSERDATA);
LuaEcsEntity *ent = static_cast<
LuaEcsEntity *>(lua_touserdata(
L, lua_upvalueindex(1)));
lua_pushboolean(L,
ent->e.has<Player>());
return 1;
},
1);
} else
lua_pushnil(L);
return 1;
});
lua_settable(L, -3);
}
LuaData::~LuaData()
@@ -445,6 +843,9 @@ void LuaData::lateSetup()
LuaModule::LuaModule(flecs::world &ecs)
{
ecs.module<LuaModule>();
ecs.import <SlotsModule>();
ecs.import <VehicleManagerModule>();
ecs.component<LuaChildEventTrigger>();
ecs.component<LuaBase>()
.on_add([](LuaBase &lua) {
@@ -453,8 +854,7 @@ LuaModule::LuaModule(flecs::world &ecs)
lua.startup_called = false;
})
.add(flecs::Singleton);
ecs.add<LuaBase>();
ecs.component<LuaEvent>().add(flecs::Singleton);
ecs.system<const EngineData, LuaBase>("LuaUpdate")
.kind(flecs::OnUpdate)
.each([](const EngineData &eng, LuaBase &lua) {
@@ -501,5 +901,14 @@ LuaModule::LuaModule(flecs::world &ecs)
trigger.parent = parentNode;
e.modified<EventTrigger>();
});
ecs.system<LuaBase, LuaEvent>("HandleLuaEvents")
.kind(flecs::OnUpdate)
.each([](LuaBase &base, LuaEvent &evt) {
while (!evt.events.empty()) {
LuaEvent::Event &ev = evt.events.front();
base.mLua->call_handler(ev.event, ev.e1, ev.e2);
evt.events.pop_front();
}
});
}
}

View File

@@ -12,6 +12,8 @@ struct LuaData {
std::vector<int> setup_handlers;
int setup_handler();
int call_handler(const Ogre::String &event);
int call_handler(const Ogre::String &event, flecs::entity e,
flecs::entity o);
LuaData();
virtual ~LuaData();
@@ -25,5 +27,16 @@ struct LuaBase {
struct LuaModule {
LuaModule(flecs::world &ecs);
};
struct LuaEvent {
struct Event {
Ogre::String event;
flecs::entity e1, e2;
};
std::list<Event> events;
void add(const Ogre::String &event, flecs::entity e1, flecs::entity e2)
{
events.push_back({ event, e1, e2 });
}
};
}
#endif

View File

@@ -0,0 +1,751 @@
#include <iostream>
#include <Ogre.h>
#include <Jolt/Jolt.h>
#include <Jolt/Physics/Body/BodyID.h>
#include <Jolt/Physics/Character/CharacterBase.h>
#include <Jolt/Physics/Character/Character.h>
#include <Jolt/Physics/Collision/BroadPhase/BroadPhase.h>
#include <Jolt/Physics/Body/BodyLock.h>
#include <Jolt/Physics/Collision/ContactListener.h>
#include "Components.h"
#include "GameData.h"
#include "CharacterModule.h"
#include "WaterModule.h"
#include "BoatModule.h"
#include "EventTriggerModule.h"
#include "LuaData.h"
#include "physics.h"
#include "loader.h"
#include "EventModule.h"
#include "TerrainModule.h"
#include "PhysicsModule.h"
namespace ECS
{
struct PhysicsShape {
JPH::ShapeRefC shape;
};
struct ConvexHull {};
struct WaterBody {
std::set<JPH::BodyID> inWater;
bool isInWater(const JPH::BodyID &id) const;
};
struct TriggerBody {
void *foo;
};
PhysicsModule::PhysicsModule(flecs::world &ecs)
{
ecs.module<PhysicsModule>();
ecs.import <EventModule>();
ecs.import <EventTriggerModule>();
ecs.import <BoatModule>();
flecs::entity PhysicsPreUpdate =
ecs.entity().add(flecs::Phase).depends_on(flecs::OnUpdate);
flecs::entity PhysicsUpdate =
ecs.entity().add(flecs::Phase).depends_on(PhysicsPreUpdate);
flecs::entity PhysicsPostUpdate =
ecs.entity().add(flecs::Phase).depends_on(PhysicsUpdate);
ecs.component<Physics>().add(flecs::Singleton);
ecs.component<JPH::BodyID>().member<uint32_t>("mID");
/* for terrain */
ecs.component<PhysicsBody>();
ecs.component<PhysicsShape>();
ecs.component<PhysicsNode>();
ecs.component<PhysicsMeshName>();
ecs.component<PhysicsMeshPtr>();
ecs.component<PhysicsHeightfieldData>();
ecs.component<CharacterBody>();
ecs.component<TriggerBody>();
ecs.component<CharacterVelocity>();
ecs.component<WaterBody>().add(flecs::Singleton);
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) {
ph.physics->update(e.delta);
});
ecs.observer<const EngineData, PhysicsMeshName>(
"create_shape_mesh_name")
.event(flecs::OnSet)
.without<PhysicsShape>()
.with<Physics>()
.write<PhysicsShape>()
.each([&](flecs::entity e, const EngineData &eng,
PhysicsMeshName &name) {
Ogre::DefaultHardwareBufferManagerBase dmgr;
Ogre::MeshPtr mesh =
Ogre::MeshManager::getSingleton().getByName(
name.meshName);
mesh->setHardwareBufferManager(&dmgr);
mesh->load();
JPH::ShapeRefC shape =
JoltPhysicsWrapper::getSingleton()
.createMeshShape(mesh);
PhysicsShape &s = e.ensure<PhysicsShape>();
s.shape = shape;
e.modified<PhysicsShape>();
});
ecs.observer<const EngineData, PhysicsMeshPtr>("create_shape_mesh_ptr")
.event(flecs::OnSet)
.without<PhysicsShape>()
.with<Physics>()
.write<PhysicsShape>()
.each([&](flecs::entity e, const EngineData &eng,
PhysicsMeshPtr &meshPtr) {
Ogre::DefaultHardwareBufferManager dmgr;
Ogre::MeshPtr mesh = meshPtr.mesh;
if (!mesh->isLoaded()) {
mesh->setHardwareBufferManager(&dmgr);
mesh->load();
}
JPH::ShapeRefC shape =
JoltPhysicsWrapper::getSingleton()
.createMeshShape(mesh);
PhysicsShape &s = e.ensure<PhysicsShape>();
s.shape = shape;
e.modified<PhysicsShape>();
});
ecs.observer<const EngineData, PhysicsHeightfieldData>(
"create_shape_heightfield")
.event(flecs::OnSet)
.without<PhysicsShape>()
.with<Physics>()
.write<PhysicsShape>()
.each([&](flecs::entity e, const EngineData &eng,
PhysicsHeightfieldData &hfd) {
JPH::ShapeRefC shape =
JoltPhysicsWrapper::getSingleton()
.createHeightfieldShape(
hfd.samples, hfd.offset,
hfd.scale, hfd.sampleCount);
PhysicsShape &s = e.ensure<PhysicsShape>();
s.shape = shape;
e.modified<PhysicsShape>();
delete hfd.samples;
e.remove<PhysicsHeightfieldData>();
});
#if 1
ecs.observer<const EngineData, const PhysicsShape, const PhysicsNode,
const PhysicsBody>("create_body_from_shape")
.event(flecs::OnSet)
.without<JPH::BodyID>()
.with<Physics>()
.write<PhysicsShape>()
.write<JPH::BodyID>()
.each([&](flecs::entity e, const EngineData &eng,
const PhysicsShape &shape, const PhysicsNode &node,
const PhysicsBody &body) {
JPH::BodyID id =
JoltPhysicsWrapper::getSingleton().createBody(
shape.shape.GetPtr(), 0.0f, node.node,
(JPH::EMotionType)body.motion,
(JPH::ObjectLayer)body.layer);
e.set<JPH::BodyID>(id);
JoltPhysicsWrapper::getSingleton().addBody(
id, JPH::EActivation::Activate);
});
#endif
ecs.observer<const JPH::BodyID>("remove_body")
.event(flecs::OnRemove)
.each([&](flecs::entity e, const JPH::BodyID &id) {
JoltPhysicsWrapper::getSingleton().removeBody(id);
JoltPhysicsWrapper::getSingleton().destroyBody(id);
std::cout << "body destroyed" << std::endl;
});
ecs.observer<const EngineData, const CharacterBase>("SetupCharacterPh")
.event(flecs::OnSet)
.with<Character>()
.without<CharacterBody>()
.write<CharacterBody>()
.each([](flecs::entity e, const EngineData &eng,
const CharacterBase &base) {
CharacterBody &b = e.ensure<CharacterBody>();
b.ch.reset(JoltPhysicsWrapper::getSingleton()
.createCharacter(base.mBodyNode,
1.75f, 0.23f));
if (!e.has<CharacterDisablePhysics>())
static_cast<JPH::Character *>(b.ch.get())
->AddToPhysicsSystem(
JPH::EActivation::Activate);
e.set<JPH::BodyID>(
static_cast<JPH::Character *>(b.ch.get())
->GetBodyID());
e.modified<CharacterBody>();
});
ecs.observer<const EngineData, const EventTrigger>(
"CreateTriggerPhysics")
.event(flecs::OnSet)
.without<JPH::BodyID>()
.each([](flecs::entity e, const EngineData &eng,
const EventTrigger &trigger) {
JPH::ShapeRefC shape =
JoltPhysicsWrapper::getSingleton()
.createCylinderShape(trigger.halfheight,
trigger.radius);
JPH::BodyID id =
JoltPhysicsWrapper::getSingleton().createSensor(
shape.GetPtr(), trigger.node,
JPH::EMotionType::Kinematic,
Layers::MOVING);
e.set<JPH::BodyID>(id);
// JoltPhysicsWrapper::getSingleton().setDebugDraw(true);
JoltPhysicsWrapper::getSingleton().addBody(
id, JPH::EActivation::Activate);
JoltPhysicsWrapper::getSingleton().addContactListener(
id,
[](const JoltPhysics::ContactListener::
ContactReport &report) -> void {
flecs::entity trigger_e =
ECS::get()
.query_builder<
const JPH::
BodyID>()
.with<EventTrigger>()
.build()
.find([&](const JPH::BodyID
&id) {
return id == report.id1 ||
id == report.id2;
});
flecs::entity parent_e =
trigger_e.parent();
const JPH::BodyID &trigger_body =
trigger_e.get<JPH::BodyID>();
const JPH::BodyID &other_body =
(trigger_body == report.id1) ?
report.id2 :
report.id1;
flecs::entity other_e =
ECS::get()
.query_builder<
const JPH::
BodyID>()
.without<EventTrigger>()
.build()
.find([&](const JPH::BodyID
&id) {
return id ==
other_body;
});
if (!trigger_e.is_valid() ||
!other_e.is_valid())
return;
if (parent_e.is_valid() &&
parent_e == other_e)
return;
OgreAssert(
trigger_e.is_valid(),
"trigger entity is not valid");
OgreAssert(other_e.is_valid(),
"other entity is not valid");
const EventTrigger &trg =
trigger_e.get<EventTrigger>();
if (report.entered) {
trigger_e.get_mut<EventData>()
.add(trigger_e,
trg.event +
"_enter",
trigger_e,
other_e);
other_e.add<InTrigger>(
trigger_e);
trigger_e.add<TriggeredBy>(
other_e);
} else {
trigger_e.get_mut<EventData>()
.add(trigger_e,
trg.event +
"_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>()
.with<WaterAlmostReady>()
.without<WaterBody>()
.each([this](const EngineData &eng) {
ECS::get().set<WaterBody>({});
});
ecs.system<const EngineData, WaterBody>("update_water")
.kind(PhysicsPostUpdate)
.with<TerrainReady>()
.with<WaterAlmostReady>()
.each([this](const EngineData &eng, WaterBody &body) {
const WaterSurface &water = ECS::get<WaterSurface>();
body.inWater.clear();
JoltPhysicsWrapper::getSingleton().broadphaseQuery(
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")
.kind(PhysicsPostUpdate)
.with<TerrainReady>()
.with<WaterReady>()
.with<InWater>()
.each([this](flecs::entity e, const JPH::BodyID &id,
const WaterBody &body) {
if (!body.isInWater(id))
e.remove<InWater>();
});
ecs.system<const JPH::BodyID, const WaterBody>("update_water_status2")
.kind(PhysicsPostUpdate)
.with<TerrainReady>()
.with<WaterReady>()
.without<InWater>()
.each([this](flecs::entity e, const JPH::BodyID &id,
const WaterBody &body) {
if (body.isInWater(id))
e.add<InWater>();
});
ecs.system<const CharacterBody, const WaterBody>(
"update_water_character1")
.kind(PhysicsPostUpdate)
.with<TerrainReady>()
.with<WaterReady>()
.with<InWater>()
.each([this](flecs::entity e, const CharacterBody &ch,
const WaterBody &body) {
JPH::Character *chptr =
static_cast<JPH::Character *>(ch.ch.get());
if (!body.isInWater(chptr->GetBodyID()))
e.remove<InWater>();
});
ecs.system<const CharacterBody, const WaterBody>(
"update_water_character2")
.kind(PhysicsPostUpdate)
.with<TerrainReady>()
.with<WaterReady>()
.without<InWater>()
.each([this](flecs::entity e, const CharacterBody &ch,
const WaterBody &body) {
JPH::Character *chptr =
static_cast<JPH::Character *>(ch.ch.get());
if (body.isInWater(chptr->GetBodyID()))
e.add<InWater>();
});
ecs.system<const EngineData, const BoatBase, const WaterBody,
const JPH::BodyID>("update_water_boat_enable")
.kind(PhysicsPreUpdate)
.with<TerrainReady>()
.with<WaterReady>()
.each([this](flecs::entity e, const EngineData &eng,
const BoatBase &boat, const WaterBody &body,
const JPH::BodyID &id) {
if (!JoltPhysicsWrapper::getSingleton().isAdded(id))
JoltPhysicsWrapper::getSingleton().addBody(
id, JPH::EActivation::Activate);
});
ecs.system<const EngineData, const BoatBase, const WaterBody,
const JPH::BodyID>("update_water_boat_activation")
.kind(PhysicsPreUpdate)
.with<TerrainReady>()
.with<WaterReady>()
.with<InWater>()
.each([this](flecs::entity e, const EngineData &eng,
const BoatBase &boat, const WaterBody &body,
const JPH::BodyID &id) {
if (!JoltPhysicsWrapper::getSingleton().isActive(id))
JoltPhysicsWrapper::getSingleton().activate(id);
});
ecs.system<const EngineData, const BoatBase, const WaterBody,
const JPH::BodyID, const CachedMass>(
"update_water_boat_buoyancy")
.kind(PhysicsPreUpdate)
.with<TerrainReady>()
.with<WaterReady>()
.with<InWater>()
.each([this](flecs::entity e, const EngineData &eng,
const BoatBase &boat, const WaterBody &body,
const JPH::BodyID &id, const CachedMass &mass) {
const WaterSurface &water = ECS::get<WaterSurface>();
float b = 1.0f, drag = 0.5f, adrag = 0.5f;
float level = 0.25f;
float my = JoltPhysicsWrapper::getSingleton()
.getPosition(id)
.y;
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;
else if (my > level)
b = 0.9f;
// std::cout << my << std::endl;
JoltPhysicsWrapper::getSingleton().applyBuoyancyImpulse(
id, water.mWaterNode->_getDerivedPosition(),
Ogre::Vector3::UNIT_Y, b, drag, adrag,
Ogre::Vector3::ZERO, eng.delta);
/* stabilisation */
Ogre::Vector3 currentAngularVelocity =
JoltPhysicsWrapper::getSingleton()
.getAngularVelocity(id);
Ogre::Quaternion rotation =
JoltPhysicsWrapper::getSingleton().getRotation(
id);
Ogre::Vector3 rotComp =
rotation.Inverse() * Ogre::Vector3::UNIT_Y;
rotComp.y = 0;
if (Ogre::Math::Abs(rotComp.x) > 0.1f ||
Ogre::Math::Abs(rotComp.z) > 0.1f) {
Ogre::Vector3 localAngularVelocity =
rotation.Inverse() *
currentAngularVelocity;
Ogre::Vector3 localAngularVelocityChange(
-localAngularVelocity.x, 0,
-localAngularVelocity.z);
localAngularVelocityChange -=
rotComp * 20.0f * eng.delta;
Ogre::Vector3 angularVelocityChange =
rotation * localAngularVelocityChange;
Ogre::Vector3 angularImpulse =
mass.mass * angularVelocityChange;
JoltPhysicsWrapper::getSingleton()
.addAngularImpulse(id, angularImpulse);
}
});
ecs.system<const EngineData, const CharacterBody, const WaterBody>(
"update_water_character_buoyancy")
.kind(PhysicsPreUpdate)
.with<TerrainReady>()
.with<WaterReady>()
.with<InWater>()
.without<CharacterDisablePhysics>()
.with<CharacterBuoyancy>()
.each([this](flecs::entity e, const EngineData &eng,
const CharacterBody &ch, const WaterBody &body) {
JPH::Character *chptr =
static_cast<JPH::Character *>(ch.ch.get());
JPH::BodyID id = chptr->GetBodyID();
if (JoltPhysicsWrapper::getSingleton().isActive(id)) {
const WaterSurface &water =
ECS::get<WaterSurface>();
float my = JoltPhysicsWrapper::getSingleton()
.getPosition(id)
.y;
float myv = JoltPhysicsWrapper::getSingleton()
.getLinearVelocity(id)
.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(
id,
water.mWaterNode
->_getDerivedPosition(),
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")
.kind(flecs::OnUpdate)
.with<CharacterUpdatePhysicsState>()
.write<CharacterUpdatePhysicsState>()
.each([](flecs::entity e, const EngineData &eng,
const CharacterBody &body) {
if (e.has<CharacterDisablePhysics>())
PhysicsModule::controlPhysics(e, false);
else
PhysicsModule::controlPhysics(e, true);
e.remove<CharacterUpdatePhysicsState>();
});
ecs.system<const EngineData, const CharacterBase, const CharacterBody,
CharacterVelocity>("HandleVelocity")
.kind(PhysicsPostUpdate)
.with<TerrainReady>()
.with<WaterReady>()
.without<CharacterDisablePhysics>()
.without<CharacterUpdatePhysicsState>()
.each([this](flecs::entity e, const EngineData &eng,
const CharacterBase &chbase,
const CharacterBody &body, CharacterVelocity &gr) {
if (e.has<InWater>() &&
chbase.mBodyNode->_getDerivedPosition().y > -0.5f)
e.remove<InWater>();
Ogre::Vector3 v = gr.velocity;
v.y = 0.0f;
JPH::Character *ch =
static_cast<JPH::Character *>(body.ch.get());
if (!e.has<InWater>()) {
if (ch->IsSupported()) {
v.y = gr.velocity.y;
gr.gvelocity.y = 0;
} else {
v.y = gr.velocity.y;
v.y += gr.gvelocity.y;
gr.gvelocity.y += -9.8f * eng.delta;
}
} else {
v = JoltPhysics::convert(
ch->GetLinearVelocity());
v.x = gr.velocity.x;
v.z = gr.velocity.z;
}
// gr.velocity.y = 0.0f;
// v.y = 0.0f;
ch->SetLinearVelocity(JoltPhysics::convert(v));
gr.velocity = Ogre::Vector3::ZERO;
});
ecs.system<const EngineData, CharacterBase, const CharacterBody,
CharacterVelocity>("HandleVelocityNoPhysics")
.kind(PhysicsPostUpdate)
.with<TerrainReady>()
.with<WaterReady>()
.with<CharacterDisablePhysics>()
.without<CharacterUpdatePhysicsState>()
.each([this](flecs::entity e, const EngineData &eng,
CharacterBase &ch, const CharacterBody &body,
CharacterVelocity &gr) {
Ogre::Vector3 v = gr.velocity;
// v.y = 0.0f;
ch.mBodyNode->_setDerivedPosition(
ch.mBodyNode->_getDerivedPosition() +
v * eng.delta);
gr.velocity = Ogre::Vector3::ZERO;
if (e.has<JPH::BodyID>())
JoltPhysicsWrapper::getSingleton()
.setPositionAndRotation(
e.get<JPH::BodyID>(),
ch.mBodyNode
->_getDerivedPosition(),
ch.mBodyNode
->_getDerivedOrientation(),
false);
if (e.has<InWater>() &&
ch.mBodyNode->_getDerivedPosition().y > -0.5f) {
e.remove<InWater>();
ch.is_submerged = false;
}
if (!e.has<InWater>() && ch.is_submerged)
ch.is_submerged = false;
});
}
flecs::entity PhysicsModule::createTerrainChunkBody(Ogre::SceneNode *node,
float *samples,
const Ogre::Vector3 &offset,
const Ogre::Vector3 &scale,
int sampleCount)
{
flecs::entity e = ECS::get().entity();
e.set<PhysicsHeightfieldData>({ samples, offset, scale, sampleCount });
e.set<PhysicsBody>({ (uint32_t)JPH::EMotionType::Static,
(uint32_t)Layers::NON_MOVING });
e.set<PhysicsNode>({ node });
return e;
}
void PhysicsModule::controlPhysics(flecs::entity e, bool enable)
{
if (enable) {
if (e.has<CharacterBase>()) {
e.remove<CharacterDisablePhysics>();
OgreAssert(e.has<CharacterBody>(), "No body component");
OgreAssert(e.has<JPH::BodyID>(),
"No body id in entity");
}
if (!JoltPhysicsWrapper::getSingleton().isAdded(
e.get<JPH::BodyID>())) {
Ogre::Vector3 position =
e.get<CharacterBase>()
.mBodyNode->_getDerivedPosition();
Ogre::Quaternion orientation =
e.get<CharacterBase>()
.mBodyNode->_getDerivedOrientation();
if (position.y >= -0.5f)
e.remove<InWater>();
JoltPhysicsWrapper::getSingleton()
.setPositionAndRotation(e.get<JPH::BodyID>(),
position, orientation,
false);
JoltPhysicsWrapper::getSingleton().addBody(
e.get<JPH::BodyID>(),
JPH::EActivation::Activate);
}
} else {
if (e.has<CharacterBase>()) {
e.add<CharacterDisablePhysics>();
if (!e.has<CharacterBody>())
return;
OgreAssert(e.has<CharacterBody>(), "No body component");
OgreAssert(e.has<JPH::BodyID>(),
"No body id in entity");
}
if (JoltPhysicsWrapper::getSingleton().isAdded(
e.get<JPH::BodyID>()))
JoltPhysicsWrapper::getSingleton().removeBody(
e.get<JPH::BodyID>());
Ogre::Vector3 position =
e.get<CharacterBase>().mBodyNode->_getDerivedPosition();
e.remove<InWater>();
}
}
bool WaterBody::isInWater(const JPH::BodyID &id) const
{
flecs::entity e =
ECS::get().query_builder<const JPH::BodyID>().build().find(
[&](const JPH::BodyID &bodyid) {
return bodyid == id;
});
return inWater.find(id) != inWater.end();
}
}

View File

@@ -0,0 +1,62 @@
#ifndef _PHYSICS_MODULE_H_
#define _PHYSICS_MODULE_H_
#include <flecs.h>
class JoltPhysicsWrapper;
namespace JPH
{
class Shape;
class CharacterBase;
class BodyID;
}
namespace Ogre
{
class TerrainGroup;
}
namespace ECS
{
struct Physics {
JoltPhysicsWrapper *physics;
};
struct PhysicsNode {
Ogre::SceneNode *node;
};
struct PhysicsMeshName {
Ogre::String meshName;
};
struct PhysicsMeshPtr {
Ogre::MeshPtr mesh;
};
struct PhysicsBody {
uint32_t motion;
uint32_t layer;
};
struct CharacterBody {
std::shared_ptr<JPH::CharacterBase> ch;
};
struct BoatBody {
void *tmp;
};
struct PhysicsHeightfieldData {
const float *samples;
Ogre::Vector3 offset;
Ogre::Vector3 scale;
int sampleCount;
};
struct CharacterVelocity {
Ogre::Vector3 gvelocity;
Ogre::Vector3 velocity;
};
struct CachedMass {
float mass;
};
struct PhysicsModule {
PhysicsModule(flecs::world &ecs);
static flecs::entity createTerrainChunkBody(Ogre::SceneNode *node,
float *samples,
const Ogre::Vector3 &offset,
const Ogre::Vector3 &scale,
int sampleCount);
static void controlPhysics(flecs::entity e, bool enable);
};
}
#endif

View File

@@ -0,0 +1,183 @@
#include <iostream>
#include "Components.h"
#include "GameData.h"
#include "CharacterModule.h"
#include "BoatModule.h"
#include "WaterModule.h"
#include "BoatModule.h"
#include "SlotsModule.h"
namespace ECS
{
SlotsModule::SlotsModule(flecs::world &ecs)
{
ecs.module<SlotsModule>();
ecs.import <CharacterModule>();
ecs.component<ParentSlot>();
ecs.component<ParentSlotData>();
ecs.component<ObjectSlots>();
ecs.import <WaterModule>();
ecs.import <BoatModule>();
ecs.observer<const EngineData, const BoatBase>("CreateBoatSlots")
.event(flecs::OnSet)
.each([&](flecs::entity e, const EngineData &eng,
const BoatBase &boat) { createBoatSlots(e); });
#if 1
ecs.system<const EngineData, const CharacterBase, ParentSlot>(
"UpdateSlotData")
.kind(flecs::OnUpdate)
.with<CharacterDisablePhysics>()
.without<ParentSlotData>()
.each([&](flecs::entity e, const EngineData &eng,
const CharacterBase &ch, ParentSlot &slot) {
if (slot.slot_name == "") {
slot.removeSlot(e);
return;
}
if (slot.parentIsValid()) {
if (!slot.check()) {
slot.removeSlot(e);
return;
}
slot.addChild(ch.mBodyNode);
slot.createSlotData(e);
std::cout << "base: "
<< slot.getSlotBase()->getName();
e.remove<ParentSlot>();
e.modified<ParentSlotData>();
}
});
#endif
#if 0
ecs.system<const EngineData, const CharacterBase, ParentSlot>(
"UpdateSlotCharacterTransforms")
.kind(flecs::OnUpdate)
.with<CharacterDisablePhysics>()
.without<ParentSlotData>()
.with<WaterReady>()
.each([&](flecs::entity e, const EngineData &eng,
const CharacterBase &ch, ParentSlot &slot) {
std::cout << e.name() << std::endl;
if (!slot.parentIsValid())
return;
OgreAssert(slot.parent_e.has<ObjectSlots>(),
"parent has no slots");
OgreAssert(e.has<Character>(), "not a character");
if (slot.parentIsValid()) {
if (!slot.check()) {
slot.removeSlot(e);
OgreAssert(false, "bad slot");
return;
}
slot.createSlot(e);
slot.createSlotData(e);
}
});
#endif
}
void SlotsModule::createBoatSlots(flecs::entity e)
{
const EngineData &eng = e.world().get<EngineData>();
const BoatBase &boat = e.get<BoatBase>();
int i;
std::vector<Ogre::Node *> slots = boat.mNode->getChildren();
for (i = 0; i < slots.size(); i++) {
Ogre::Any any = static_cast<Ogre::SceneNode *>(slots[i])
->getUserObjectBindings()
.getUserAny("type");
if (!any.has_value())
continue;
Ogre::String obj_type = Ogre::any_cast<Ogre::String>(any);
std::cout << "child type: " << obj_type << std::endl;
}
if (slots.size() > 0) {
ObjectSlots &vs = e.ensure<ObjectSlots>();
for (i = 0; i < slots.size(); i++) {
Ogre::Any any = static_cast<Ogre::SceneNode *>(slots[i])
->getUserObjectBindings()
.getUserAny("type");
if (!any.has_value())
continue;
Ogre::String obj_type =
Ogre::any_cast<Ogre::String>(any);
any = static_cast<Ogre::SceneNode *>(slots[i])
->getUserObjectBindings()
.getUserAny("name");
if (!any.has_value())
continue;
Ogre::String obj_name =
Ogre::any_cast<Ogre::String>(any);
vs.slots[obj_name] = { obj_type,
static_cast<Ogre::SceneNode *>(
slots[i]) };
}
}
}
void ParentSlot::createSlot(flecs::entity e)
{
if (e.has<CharacterBase>()) {
createCharacterSlot(e);
}
}
void ParentSlot::createCharacterSlot(flecs::entity e)
{
}
void ParentSlot::removeSlot(flecs::entity e)
{
if (e.has<ParentSlot>())
e.remove<ParentSlot>();
if (e.has<ParentSlot>())
e.remove<ParentSlotData>();
}
bool ParentSlot::check() const
{
if (!parent_e.has<ObjectSlots>())
return false;
const ObjectSlots &slots = parent_e.get<ObjectSlots>();
if (!slots.exists(slot_name))
return false;
return true;
}
bool ParentSlot::parentIsValid()
{
if (!parent_e.has<ObjectSlots>())
return false;
return true;
}
Ogre::SceneNode *ParentSlot::getSlotBase() const
{
if (!check())
return nullptr;
const ObjectSlots &slots = parent_e.get<ObjectSlots>();
Ogre::SceneNode *slot_base = slots.slots.at(slot_name).second;
return slot_base;
}
void ParentSlot::addChild(Ogre::SceneNode *childNode)
{
Ogre::SceneNode *parentNode = getSlotBase();
if (childNode->getParentSceneNode())
childNode->getParentSceneNode()->removeChild(childNode);
parentNode->addChild(childNode);
childNode->_setDerivedPosition(parentNode->_getDerivedPosition());
childNode->_setDerivedOrientation(parentNode->_getDerivedOrientation());
}
void ParentSlot::createSlotData(flecs::entity e)
{
const ObjectSlots &slots = parent_e.get<ObjectSlots>();
ParentSlotData &psdata = e.ensure<ParentSlotData>();
Ogre::SceneNode *slot_base = getSlotBase();
// Ogre::Vector3 position = slot_base->_getDerivedPosition();
// Ogre::Quaternion orientation = slot_base->_getDerivedOrientation();
// ch.mBodyNode->_setDerivedPosition(position);
// ch.mBodyNode->_setDerivedOrientation(orientation);
psdata.parent_e = parent_e;
psdata.parentNode = slot_base;
psdata.slot_name = slot_name;
e.remove<ParentSlot>();
e.modified<ParentSlotData>();
}
bool ObjectSlots::exists(const Ogre::String &name) const
{
return slots.find(name) != slots.end();
}
}

View File

@@ -0,0 +1,35 @@
#ifndef _SLOTS_MODULE_H_
#define _SLOTS_MODULE_H_
#include <flecs.h>
#include <Ogre.h>
namespace ECS
{
struct ParentSlot {
flecs::entity parent_e;
Ogre::String slot_name;
void createSlot(flecs::entity e);
void createCharacterSlot(flecs::entity e);
void removeSlot(flecs::entity e);
bool check() const;
bool parentIsValid();
Ogre::SceneNode *getSlotBase() const;
void addChild(Ogre::SceneNode *childNode);
void createSlotData(flecs::entity e);
};
struct ParentSlotData {
Ogre::String slot_name;
flecs::entity parent_e;
Ogre::SceneNode *parentNode;
};
struct ObjectSlots {
std::unordered_map<Ogre::String,
std::pair<Ogre::String, Ogre::SceneNode *> >
slots;
bool exists(const Ogre::String &name) const;
};
struct SlotsModule {
SlotsModule(flecs::world &ecs);
void createBoatSlots(flecs::entity e);
};
}
#endif

View File

@@ -0,0 +1,10 @@
#include "SmartObject.h"
namespace ECS
{
SmartObjectModule::SmartObjectModule(flecs::world &ecs)
{
ecs.module<SmartObjectModule>();
ecs.component<SmartObjectManager>().add(flecs::Singleton);
ecs.component<SmartObject>();
}
}

View File

@@ -0,0 +1,19 @@
#ifndef SMART_OBJECT_H_
#define SMART_OBJECT_H_
#include <Ogre.h>
#include <flecs.h>
namespace ECS
{
struct SmartObject {
Ogre::String enterNodeName;
Ogre::String exitNodeName;
Ogre::String scenario;
};
struct SmartObjectManager {
std::vector<flecs::entity> targets;
};
struct SmartObjectModule {
SmartObjectModule(flecs::world &ecs);
};
}
#endif

View File

@@ -1,7 +1,6 @@
#include <unordered_set>
#include <iostream>
#include <Ogre.h>
#include <OgreBullet.h>
#include <BulletCollision/CollisionShapes/btHeightfieldTerrainShape.h>
#include <OgreTerrain.h>
#include <OgreTerrainGroup.h>
@@ -14,6 +13,7 @@
#include "Components.h"
#include "CharacterModule.h"
#include "SunModule.h"
#include "PhysicsModule.h"
#include "TerrainModule.h"
#define TERRAIN_SIZE 129
@@ -173,7 +173,7 @@ class FlatTerrainDefiner
: public Ogre::TerrainPagedWorldSection::TerrainDefiner,
public Ogre::FrameListener {
Ogre::SceneManager *mScnMgr;
Ogre::Bullet::DynamicsWorld *mWorld;
// Ogre::Bullet::DynamicsWorld *mWorld;
struct gen_collider {
Ogre::TerrainGroup *group;
long x;
@@ -182,18 +182,44 @@ class FlatTerrainDefiner
std::deque<struct gen_collider> collider_queue;
public:
FlatTerrainDefiner(Ogre::SceneManager *scm,
Ogre::Bullet::DynamicsWorld *world)
FlatTerrainDefiner(Ogre::SceneManager *
scm /*, Ogre::Bullet::DynamicsWorld *world */)
: Ogre::TerrainPagedWorldSection::TerrainDefiner()
, Ogre::FrameListener()
, mScnMgr(scm)
, mWorld(world)
// , mWorld(world)
{
Ogre::Root::getSingleton().addFrameListener(this);
}
private:
public:
void createTerrainChunk(Ogre::TerrainGroup *terrainGroup, long x,
long y)
{
Ogre::Terrain *terrain = terrainGroup->getTerrain(x, y);
float minH = terrain->getMinHeight();
float maxH = terrain->getMaxHeight();
uint16_t terrainSize = terrainGroup->getTerrainSize();
OgreAssert(terrain && terrain->getHeightData() &&
terrain->isLoaded(),
"invalid terrain supplied");
uint16_t size = terrain->getSize();
float *heightData = terrain->getHeightData();
Ogre::SceneNode *node = terrain->_getRootSceneNode();
float worldSize = terrain->getWorldSize();
float scaled = worldSize / (size - 1);
Ogre::Vector3 bodyPosition = terrain->getPosition();
bodyPosition.y += (maxH + minH) / 2.0f;
bodyPosition.x += worldSize / 2.0f;
bodyPosition.z += worldSize / 2.0f;
Ogre::Vector3 offset =
node->_getDerivedPosition() - bodyPosition;
flecs::entity e = PhysicsModule::createTerrainChunkBody(
node, heightData, offset,
Ogre::Vector3(scaled, 1, scaled), size);
node->getUserObjectBindings().setUserAny("_collider", e);
}
void define(Ogre::TerrainGroup *terrainGroup, long x, long y) override
{
uint16_t terrainSize = terrainGroup->getTerrainSize();
@@ -258,6 +284,10 @@ public:
float maxH = terrain->getMaxHeight();
int size = terrain->getSize();
float worldSize = terrain->getWorldSize();
{
createTerrainChunk(group, x, y);
}
#if 0
if (true) {
btRigidBody *body =
mWorld->addTerrainRigidBody(
@@ -297,6 +327,7 @@ public:
y));
created = true;
}
#endif
collider_queue.pop_front();
// FIXME: create entities and components instead
Ogre::SceneNode *items =
@@ -317,11 +348,6 @@ public:
what->attachObject(ent);
what->setOrientation(item.rotation);
what->setPosition(item.position);
ECS::get<EngineData>()
.mWorld->addRigidBody(
0, ent,
Ogre::Bullet::CT_TRIMESH,
nullptr, 2, 0x7fffffff);
}
} else {
output.push_back(collider_queue.front());
@@ -338,13 +364,10 @@ public:
};
class DummyPageProvider : public Ogre::PageProvider {
public:
DummyPageProvider(btDynamicsWorld *world)
DummyPageProvider(/* btDynamicsWorld *world */)
: Ogre::PageProvider()
, mBtWorld(world)
{
}
std::unordered_map<Ogre::PageID, btRigidBody *> body;
btDynamicsWorld *mBtWorld;
bool prepareProceduralPage(Ogre::Page *page,
Ogre::PagedWorldSection *section)
{
@@ -374,9 +397,14 @@ struct TerrainPrivate {
TerrainModule::TerrainModule(flecs::world &ecs)
{
struct CanSetPlayerPosition {};
ecs.module<TerrainModule>();
ecs.component<CanSetPlayerPosition>().add(flecs::Singleton);
ecs.component<Terrain>().add(flecs::Singleton);
ecs.component<TerrainPrivate>().add(flecs::Singleton);
ecs.component<PlacementObjects>();
ecs.component<TerrainReady>().add(flecs::Singleton);
ecs.import <CharacterModule>();
ecs.import <SunModule>();
ecs.set<TerrainPrivate>({ nullptr, {} });
ecs.system<const EngineData, const Camera, const Sun, Terrain,
TerrainPrivate>("SetupUpdateTerrain")
@@ -389,8 +417,8 @@ TerrainModule::TerrainModule(flecs::world &ecs)
if (!priv.mDummyPageProvider)
priv.mDummyPageProvider =
new DummyPageProvider(
eng.mWorld
->getBtWorld());
/* eng.mWorld
->getBtWorld() */);
terrain.mTerrainGlobals =
OGRE_NEW Ogre::TerrainGlobalOptions();
@@ -477,7 +505,7 @@ TerrainModule::TerrainModule(flecs::world &ecs)
terrain.mTerrainPagedWorldSection->setDefiner(
OGRE_NEW FlatTerrainDefiner(
eng.mScnMgr, eng.mWorld));
eng.mScnMgr /*, eng.mWorld */));
terrain.mTerrainGroup->freeTemporaryResources();
std::cout << "Terrain setup done\n";

View File

@@ -36,5 +36,6 @@ struct TerrainModule {
static float get_height(Ogre::TerrainGroup *group,
const Ogre::Vector3 &position);
};
struct TerrainReady {};
}
#endif

View File

@@ -0,0 +1,24 @@
#include "Components.h"
#include "GameData.h"
#include "BoatModule.h"
#include "VehicleManagerModule.h"
namespace ECS
{
VehicleManagerModule::VehicleManagerModule(flecs::world &ecs)
{
ecs.module<VehicleManagerModule>();
ecs.import <BoatModule>();
}
flecs::entity VehicleManagerModule::createVehicleAtPosition(
const Ogre::String &name, const Ogre::Vector3 &position,
const Ogre::Quaternion &orientation)
{
flecs::entity vehicle = ECS::get().entity();
if (name == "boat")
vehicle.set<BoatType>(
{ name + ".scene", position, orientation });
else
OgreAssert(false, "unsupported type: " + name);
return vehicle;
}
}

View File

@@ -0,0 +1,15 @@
#ifndef _VEHICLE_MANAGER_MODULE_H_
#define _VEHICLE_MANAGE_MODULE_H_
#include <flecs.h>
#include <Ogre.h>
namespace ECS
{
struct VehicleManagerModule {
VehicleManagerModule(flecs::world &ecs);
flecs::entity
createVehicleAtPosition(const Ogre::String &name,
const Ogre::Vector3 &position,
const Ogre::Quaternion &orientation);
};
}
#endif

View File

@@ -12,6 +12,7 @@
#include "WaterModule.h"
namespace ECS
{
#if 0
class WaterPhysicsAction : public btActionInterface {
btPairCachingGhostObject *mWaterBody;
btManifoldArray mManifoldArray;
@@ -53,13 +54,17 @@ public:
return (mInWater.find(body) != mInWater.end());
}
};
#endif
static const uint32_t WATER_MASK = 0xF00;
WaterModule::WaterModule(flecs::world &ecs)
{
ecs.module<WaterModule>();
ecs.component<InWater>();
ecs.component<WaterReady>().add(flecs::Singleton);
ecs.component<WaterAlmostReady>().add(flecs::Singleton);
ecs.component<GroundCheckReady>().add(flecs::Singleton);
ecs.component<WaterSurface>()
.on_add([](flecs::entity e, WaterSurface &water) {
ECS::get().add<WaterBody>();
water.mAbove = false;
water.mDepthMaterial = nullptr;
water.mDepthTech = nullptr;
@@ -457,10 +462,14 @@ WaterModule::WaterModule(flecs::world &ecs)
eng.mScnMgr->getRenderQueue()->setRenderableListener(
&water.mRenderTargetListener);
std::cout << "Water setup done\n";
ECS::get().add<WaterAlmostReady>();
})
.add(flecs::Singleton);
#if 0
ecs.component<WaterBody>().add(flecs::Singleton);
ecs.component<WaterBody>()
.on_add([this](WaterBody &body) {
#if 0
body.mShapeAabbMax = btVector3(0, 0, 0);
body.mShapeAabbMin = btVector3(0, 0, 0);
body.mSurface.clear();
@@ -489,9 +498,11 @@ WaterModule::WaterModule(flecs::world &ecs)
.get<EngineData>()
.mWorld->getBtWorld()
->addAction(body.action);
#endif
ECS::get().add<WaterReady>();
})
.add(flecs::Singleton);
#endif
ecs.system<const EngineData, const Camera, WaterSurface>("UpdateWater")
.kind(flecs::OnUpdate)
.with<WaterReady>()
@@ -520,6 +531,7 @@ WaterModule::WaterModule(flecs::world &ecs)
// water.mRenderTargetListener.mInDepth = false;
// water.mWaterEnt->setVisible(true);
});
#if 0
ecs.system<const EngineData, const WaterSurface, WaterBody>(
"UpdateWaterBody")
.kind(flecs::OnUpdate)
@@ -527,6 +539,7 @@ WaterModule::WaterModule(flecs::world &ecs)
.each([this](const EngineData &eng, const WaterSurface &water,
WaterBody &body) {
int i;
#if 0
OgreAssert(body.mWaterBody, "Water not ready");
std::set<btCollisionObject *> currentOverlaps;
Ogre::Vector3 waterPos =
@@ -542,6 +555,7 @@ WaterModule::WaterModule(flecs::world &ecs)
body.mWaterBody->getWorldTransform().setOrigin(
Ogre::Bullet::convert(waterBodyPos +
d));
#endif
#if 0
btCompoundShape *mshape =
static_cast<btCompoundShape *>(
@@ -646,6 +660,7 @@ WaterModule::WaterModule(flecs::world &ecs)
}
#endif
});
#endif
}
struct shapeParams {
float offsetX, offsetY, offsetZ;
@@ -654,6 +669,7 @@ struct shapeParams {
static struct shapeParams childShapes[] = {
{ 0.0f, 0.0f, 0.0f, 100.0f, 100.0f, 100.0f },
};
#if 0
void WaterModule::createWaterShape(WaterBody *water)
{
int i = 0;
@@ -684,6 +700,7 @@ void WaterModule::createWaterShape(WaterBody *water)
shape->recalculateLocalAabb();
water->mWaterShape = shape;
}
#endif
void WaterSurface::RenderTextureListener::preRenderTargetUpdate(
const Ogre::RenderTargetEvent &evt)
{
@@ -712,6 +729,7 @@ bool WaterSurface::RenderTextureListener::renderableQueued(
*ppTech = mSurface->mDepthTech;
return true;
}
#if 0
struct DeepPenetrationContactResultCallback : public btManifoldResult {
DeepPenetrationContactResultCallback(
const btCollisionObjectWrapper *body0Wrap,
@@ -954,8 +972,11 @@ void WaterPhysicsAction::debugDraw(btIDebugDraw *debugDrawer)
void WaterPhysicsAction::setupBody()
{
}
#endif
#if 0
bool WaterBody::isInWater(const btCollisionObject *body) const
{
return static_cast<WaterPhysicsAction *>(action)->isInWater(body);
}
#endif
}

View File

@@ -37,20 +37,13 @@ struct WaterSurface {
mRefractionClipPlaneBelow;
Ogre::Viewport *mViewports[4];
};
struct WaterBody {
std::vector<btCollisionShape *> mChildShapes;
btCollisionShape *mWaterShape;
btPairCachingGhostObject *mWaterBody;
std::unordered_map<btCollisionObject *, float> mSurface;
btVector3 mShapeAabbMin, mShapeAabbMax;
int count;
btActionInterface *action;
bool isInWater(const btCollisionObject *body) const;
};
struct InWater {};
struct WaterAlmostReady {};
struct WaterReady {};
struct WaterModule {
btPairCachingGhostObject *mGhostObject;
// btPairCachingGhostObject *mGhostObject;
WaterModule(flecs::world &ecs);
void createWaterShape(WaterBody *water);
// void createWaterShape(WaterBody *water);
};
}
#endif

38
src/gamedata/goap.cpp Normal file
View File

@@ -0,0 +1,38 @@
#include <set>
#include "goap.h"
bool BaseAction::can_run(const ECS::Blackboard &state, bool debug)
{
return m_exec->_can_run(state, debug);
}
void BaseAction::_plan_effects(ECS::Blackboard &state)
{
state.clear_flag(m_exec->m_clear_bits);
state.set_flag(m_exec->m_set_bits);
}
bool BaseAction::is_active(const ECS::Blackboard &state)
{
return m_exec->is_active(state);
}
bool BaseAction::stop(ECS::Blackboard &state)
{
if (!is_active(state))
return false;
m_exec->_exit(state);
state._active.erase(m_exec.get());
return true;
}
int BaseAction::execute(ECS::Blackboard &state)
{
if (!is_active(state)) {
state._active.insert(m_exec.get());
m_exec->_enter(state);
}
int ret = m_exec->_execute(state);
if (ret == OK)
stop(state);
return ret;
}
int BaseAction::get_cost(const ECS::Blackboard &state) const
{
return m_exec->_get_cost(state);
}

350
src/gamedata/goap.h Normal file
View File

@@ -0,0 +1,350 @@
#ifndef H_GOAP_H_
#define H_GOAP_H_
#include <string>
#include <memory>
#include <set>
#include <flecs.h>
namespace ECS
{
struct Blackboard;
}
struct BaseAction;
struct BaseActionExec;
namespace ECS
{
struct Blackboard {
enum {
HIGH_LUST = (1 << 0),
LOW_LUST = (1 << 1),
FULL_LUST = (1 << 2),
LOW_HEALTH = (1 << 3),
FULL_HEALTH = (1 << 4),
HAS_TARGET = (1 << 5),
LOW_STAMINA = (1 << 6),
FULL_STAMINA = (1 << 7),
REACHED_TARGET = (1 << 8),
};
flecs::entity me;
int health;
int stamina;
int lust;
uint32_t flags;
std::set<BaseActionExec *> _active;
bool operator==(const Blackboard &other)
{
return flags == other.flags;
}
void set_flag(int flag)
{
flags |= flag;
}
void clear_flag(int flag)
{
flags &= ~flag;
}
bool get_flag(int flag) const
{
return flags & flag;
}
bool check_flag(int flag) const
{
return (flags & flag) == flag;
}
};
}
struct BaseAction {
std::string m_name;
public:
enum { OK = 0, BUSY, ABORT };
private:
std::unique_ptr<BaseActionExec> m_exec;
public:
BaseAction(const std::string &name, BaseActionExec *exec)
: m_name(name)
, m_exec(exec)
{
}
const std::string &get_name() const
{
return m_name;
}
void plan_effects(ECS::Blackboard &state)
{
// std::cout << m_name << " pre: " << &state << " " << state.flags
// << std::endl;
_plan_effects(state);
// std::cout << m_name << " post: " << &state << " " << state.flags
// << std::endl;
}
virtual bool can_run(const ECS::Blackboard &state, bool debug = false);
virtual void _plan_effects(ECS::Blackboard &state);
bool is_active(const ECS::Blackboard &state);
bool stop(ECS::Blackboard &state);
int execute(ECS::Blackboard &state);
virtual int get_cost(const ECS::Blackboard &state) const;
};
struct BaseActionExec {
enum {
OK = BaseAction::OK,
BUSY = BaseAction::BUSY,
ABORT = BaseAction::ABORT
};
BaseActionExec(int set_bits, int clear_bits)
: m_set_bits(set_bits)
, m_clear_bits(clear_bits)
{
}
bool is_active(const ECS::Blackboard &state)
{
return state._active.find(this) != state._active.end();
}
virtual int _execute(ECS::Blackboard &state) = 0;
virtual void _enter(ECS::Blackboard &state) = 0;
virtual void _exit(ECS::Blackboard &state) = 0;
virtual int _get_cost(const ECS::Blackboard &state) const
{
return 1;
}
virtual bool _can_run(const ECS::Blackboard &state, bool debug = false)
{
return true;
}
int m_set_bits, m_clear_bits;
};
template <typename State, typename Act, int N = 100> class BasePlanner {
struct VisitedState {
int priority;
int cost;
State state;
// Used to reconstruct the path
VisitedState *parent;
Act *action;
// Only used for linked list management
VisitedState *next;
};
VisitedState nodes[N];
void visited_states_array_to_list(VisitedState *nodes, int len)
{
for (auto i = 0; i < len - 1; i++) {
nodes[i].next = &nodes[i + 1];
}
nodes[len - 1].next = nullptr;
}
VisitedState *priority_list_pop(VisitedState *&list)
{
VisitedState *min_elem = nullptr;
auto min_prio = std::numeric_limits<int>::max();
// Find the element with the lowest priority
for (auto p = list; p != nullptr; p = p->next) {
if (p->priority < min_prio) {
min_elem = p;
min_prio = p->priority;
}
}
// Remove it from the list
if (min_elem == list) {
list = list->next;
} else {
for (auto p = list; p != nullptr; p = p->next) {
if (p->next == min_elem) {
p->next = p->next->next;
min_elem->next = nullptr;
}
}
}
return min_elem;
}
VisitedState *list_pop_head(VisitedState *&head)
{
auto ret = head;
head = head->next;
ret->next = nullptr;
return ret;
}
void list_push_head(VisitedState *&head, VisitedState *elem)
{
elem->next = head;
head = elem;
}
void update_queued_state(VisitedState *previous,
const VisitedState *current)
{
if (previous->cost > current->cost) {
previous->priority = current->cost;
previous->priority = current->priority;
previous->parent = current->parent;
previous->action = current->action;
}
}
public:
struct BaseGoal {
std::string m_name;
public:
BaseGoal(const std::string &name)
: m_name(name)
{
}
/** Checks if the goal is reached for the given state. */
virtual bool is_reached(const State &state) const
{
return distance_to(state) == 0;
}
/** Computes the distance from state to goal. */
virtual int distance_to(const State &state) const = 0;
virtual ~BaseGoal() = default;
const std::string &get_name() const
{
return m_name;
}
};
/** Finds a plan from state to goal and returns its length.
*
* If path is given, then the found path is stored there.
*/
int plan(const State &state, BaseGoal &goal, Act *actions[],
unsigned action_count, Act **path = nullptr, int path_len = 10)
{
visited_states_array_to_list(nodes, N);
auto free_nodes = &nodes[0];
VisitedState *open = list_pop_head(free_nodes);
VisitedState *close = nullptr;
open->state = state;
open->cost = 0;
open->priority = 0;
open->parent = nullptr;
open->action = nullptr;
while (open) {
VisitedState *current = priority_list_pop(open);
list_push_head(close, current);
if (goal.is_reached(current->state)) {
auto len = 0;
for (auto p = current->parent; p;
p = p->parent) {
len++;
}
if (len > path_len) {
return -1;
}
if (path) {
auto i = len - 1;
for (auto p = current; p->parent;
p = p->parent, i--) {
path[i] = p->action;
}
}
return len;
}
for (auto i = 0u; i < action_count; i++) {
auto action = actions[i];
if (action->can_run(current->state)) {
// Cannot allocate a new node, abort
if (free_nodes == nullptr) {
// Garbage collect the node that is most unlikely to be
// visited (i.e. lowest priority)
VisitedState *gc,
*gc_prev = nullptr;
for (gc = open; gc && gc->next;
gc = gc->next) {
gc_prev = gc;
}
if (!gc) {
return -2 /* OOM */;
}
if (gc_prev) {
gc_prev->next = nullptr;
}
free_nodes = gc;
}
auto neighbor =
list_pop_head(free_nodes);
neighbor->state = current->state;
action->plan_effects(neighbor->state);
neighbor->cost =
current->cost + 1 +
action->get_cost(
neighbor->state);
neighbor->priority =
current->priority + 1 +
goal.distance_to(
neighbor->state);
neighbor->parent = current;
neighbor->action = action;
bool should_insert = true;
// Check if the node is already in the list of nodes
// scheduled to be visited
for (auto p = open; p; p = p->next) {
if (p->state ==
neighbor->state) {
should_insert = false;
update_queued_state(
p, neighbor);
}
}
// Check if the state is in the list of already visited
// state
for (auto p = close; p; p = p->next) {
if (p->state ==
neighbor->state) {
should_insert = false;
update_queued_state(
p, neighbor);
}
}
if (should_insert) {
list_push_head(open, neighbor);
} else {
list_push_head(free_nodes,
neighbor);
}
}
}
}
// Reaching here means we did not find a path
return -1 /* No path */;
}
};
template <class RunnerType> struct DeclareAction : public BaseAction {
RunnerType runner;
template <class... Args>
DeclareAction(const std::string &name, Args... args)
: runner(args...)
, BaseAction(name, &runner)
{
}
};
#endif

View File

@@ -0,0 +1,18 @@
cmake_minimum_required(VERSION 3.13.0)
project(jolt-physics)
set(CMAKE_CXX_STANDARD 17)
# The COMPONENTS part checks that OGRE was built the way we need it
# The CONFIG flag makes sure we get OGRE instead of OGRE-next
find_package(Jolt REQUIRED)
find_package(OGRE REQUIRED COMPONENTS Bites Paging Terrain CONFIG)
find_package(ZLIB)
find_package(SDL2)
find_package(assimp REQUIRED CONFIG)
find_package(OgreProcedural REQUIRED CONFIG)
find_package(pugixml REQUIRED CONFIG)
find_package(flecs REQUIRED CONFIG)
add_library(physics STATIC physics.cpp)
target_link_libraries(physics PUBLIC OgreMain Jolt::Jolt)
target_include_directories(physics PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

1746
src/physics/physics.cpp Normal file

File diff suppressed because it is too large Load Diff

208
src/physics/physics.h Normal file
View File

@@ -0,0 +1,208 @@
#ifndef __PHYSICS_H_
#define __PHYSICS_H_
#include <Ogre.h>
#include <OgreSingleton.h>
#include <Jolt/Jolt.h>
#include <Jolt/Physics/Collision/Shape/Shape.h>
#include <Jolt/Physics/Collision/Shape/StaticCompoundShape.h>
#include <Jolt/Physics/Collision/ObjectLayer.h>
#include <Jolt/Physics/Collision/ContactListener.h>
#include <Jolt/Physics/Collision/BroadPhase/BroadPhaseLayer.h>
#include <Jolt/Physics/Body/BodyCreationSettings.h>
#include <Jolt/Physics/EActivation.h>
void physics();
namespace JPH
{
class CharacterBase;
class ContactManifold;
class ContactSettings;
class SubShapeIDPair;
}
// Layer that objects can be in, determines which other objects it can collide with
// Typically you at least want to have 1 layer for moving bodies and 1 layer for static bodies, but you can have more
// layers if you want. E.g. you could have a layer for high detail collision (which is not used by the physics simulation
// but only if you do collision testing).
namespace Layers
{
static constexpr JPH::ObjectLayer NON_MOVING = 0;
static constexpr JPH::ObjectLayer MOVING = 1;
static constexpr JPH::ObjectLayer NUM_LAYERS = 2;
};
// Each broadphase layer results in a separate bounding volume tree in the broad phase. You at least want to have
// a layer for non-moving and moving objects to avoid having to update a tree full of static objects every frame.
// You can have a 1-on-1 mapping between object layers and broadphase layers (like in this case) but if you have
// many object layers you'll be creating many broad phase trees, which is not efficient. If you want to fine tune
// your broadphase layers define JPH_TRACK_BROADPHASE_STATS and look at the stats reported on the TTY.
namespace BroadPhaseLayers
{
static constexpr JPH::BroadPhaseLayer NON_MOVING(0);
static constexpr JPH::BroadPhaseLayer MOVING(1);
static constexpr uint NUM_LAYERS(2);
};
namespace JoltPhysics
{
Ogre::Vector3 convert(const JPH::Vec3Arg &vec);
JPH::Vec3 convert(const Ogre::Vector3 &vec);
Ogre::Quaternion convert(const JPH::QuatArg &rot);
JPH::Quat convert(const Ogre::Quaternion &rot);
struct ShapeData;
struct CompoundShapeBuilder {
JPH::StaticCompoundShapeSettings shapeSettings;
void addShape(JPH::ShapeRefC shape, const Ogre::Vector3 &position,
const Ogre::Quaternion &rotation);
JPH::ShapeRefC build();
};
class ContactListener : public JPH::ContactListener {
public:
struct ContactReport {
bool entered;
JPH::BodyID id1, id2;
JPH::ContactManifold manifold;
JPH::ContactSettings settings;
};
private:
std::list<ContactReport> reports;
std::function<void(const ContactReport &report)> dispatch;
std::map<JPH::BodyID, std::function<void(const ContactReport &report)> >
listeners;
public:
ContactListener();
JPH::ValidateResult OnContactValidate(
const JPH::Body &inBody1, const JPH::Body &inBody2,
JPH::RVec3Arg inBaseOffset,
const JPH::CollideShapeResult &inCollisionResult) override;
void OnContactAdded(const JPH::Body &inBody1, const JPH::Body &inBody2,
const JPH::ContactManifold &inManifold,
JPH::ContactSettings &ioSettings) override;
void OnContactPersisted(const JPH::Body &inBody1,
const JPH::Body &inBody2,
const JPH::ContactManifold &inManifold,
JPH::ContactSettings &ioSettings) override;
void
OnContactRemoved(const JPH::SubShapeIDPair &inSubShapePair) override;
void setDispatch(const std::function<void(const ContactReport &report)>
dispatcher)
{
dispatch = dispatcher;
}
void addListener(
const JPH::BodyID &id,
const std::function<void(const ContactReport &report)> listener)
{
listeners[id] = listener;
}
void removeListener(const JPH::BodyID &id)
{
listeners.erase(id);
}
void update();
};
}
class JoltPhysicsWrapper : public Ogre::Singleton<JoltPhysicsWrapper> {
public:
JoltPhysics::ContactListener contacts;
JoltPhysicsWrapper(Ogre::SceneManager *scnMgr,
Ogre::SceneNode *cameraNode);
~JoltPhysicsWrapper();
void update(float dt);
void addBody(const JPH::BodyID &body, JPH::EActivation activation);
bool isAdded(const JPH::BodyID &body);
JPH::ShapeRefC createBoxShape(const Ogre::Vector3 &extents);
JPH::ShapeRefC createSphereShape(float radius);
JPH::ShapeRefC createCylinderShape(float halfHeight, float radius);
JPH::ShapeRefC createMeshShape(Ogre::MeshPtr mesh);
JPH::ShapeRefC createMeshShape(Ogre::String meshName);
JPH::ShapeRefC createConvexHullShape(Ogre::MeshPtr mesh);
JPH::ShapeRefC createConvexHullShape(Ogre::String meshName);
JPH::ShapeRefC createHeightfieldShape(const float *samples,
Ogre::Vector3 offset,
Ogre::Vector3 scale,
int sampleCount);
JPH::ShapeRefC createMutableCompoundShape(
const std::vector<JPH::ShapeRefC> &shapes,
const std::vector<Ogre::Vector3> &positions,
const std::vector<Ogre::Quaternion> &rotations);
JPH::ShapeRefC createStaticCompoundShape(
const std::vector<JPH::ShapeRefC> &shapes,
const std::vector<Ogre::Vector3> &positions,
const std::vector<Ogre::Quaternion> &rotations);
JPH::ShapeRefC
createOffsetCenterOfMassShape(const Ogre::Vector3 &offset,
JPH::ShapeRefC shape);
JPH::BodyID createBody(const JPH::BodyCreationSettings &settings);
JPH::BodyID createBody(const JPH::Shape *shape, float mass,
const Ogre::Vector3 &position,
const Ogre::Quaternion &rotation,
JPH::EMotionType motion, JPH::ObjectLayer layer);
JPH::BodyID createBody(const JPH::Shape *shape, float mass,
Ogre::SceneNode *node, JPH::EMotionType motion,
JPH::ObjectLayer layer);
JPH::BodyID createSensor(const JPH::Shape *shape,
const Ogre::Vector3 &position,
const Ogre::Quaternion &rotation,
JPH::EMotionType motion,
JPH::ObjectLayer layer);
JPH::BodyID createSensor(const JPH::Shape *shape, Ogre::SceneNode *node,
JPH::EMotionType motion,
JPH::ObjectLayer layer);
JPH::CharacterBase *createCharacter(Ogre::SceneNode *node,
float characterHeight,
float characterRadius);
void addShapeToCompound(JPH::Ref<JPH::Shape> compoundShape,
JPH::ShapeRefC childShape,
const Ogre::Vector3 &position,
const Ogre::Quaternion &rotation);
void removeBody(const JPH::BodyID &id);
void destroyBody(const JPH::BodyID &id);
void setDebugDraw(bool enable);
void broadphaseQuery(float dt, const Ogre::Vector3 &position,
std::set<JPH::BodyID> &inWater);
void applyBuoyancyImpulse(JPH::BodyID id,
const Ogre::Vector3 &surfacePosition,
const Ogre::Vector3 &surfaceNormal,
float buoyancy, float linearDrag,
float angularDrag,
const Ogre::Vector3 &fluidVelocity,
const Ogre::Vector3 &gravity, float dt);
void applyBuoyancyImpulse(JPH::BodyID id,
const Ogre::Vector3 &surfacePosition,
const Ogre::Vector3 &surfaceNormal,
float buoyancy, float linearDrag,
float angularDrag,
const Ogre::Vector3 &fluidVelocity, float dt);
bool isActive(JPH::BodyID id);
void activate(JPH::BodyID id);
Ogre::Vector3 getPosition(JPH::BodyID id);
void setPosition(JPH::BodyID id, const Ogre::Vector3 &position,
bool activate = true);
Ogre::Quaternion getRotation(JPH::BodyID id);
void setRotation(JPH::BodyID id, const Ogre::Quaternion &rotation,
bool activate = true);
void getPositionAndRotation(JPH::BodyID id, Ogre::Vector3 &position,
Ogre::Quaternion &rotation);
void setPositionAndRotation(JPH::BodyID id,
const Ogre::Vector3 &position,
const Ogre::Quaternion &rotation,
bool activate = true);
Ogre::Vector3 getLinearVelocity(JPH::BodyID id);
Ogre::Vector3 getAngularVelocity(JPH::BodyID id);
float getFriction(JPH::BodyID id);
void setFriction(JPH::BodyID id, float friction);
void addAngularImpulse(const JPH::BodyID &id,
const Ogre::Vector3 &impulse);
void setDispatch(std::function<void(const JoltPhysics::ContactListener::
ContactReport &report)>
dispatcher);
void addContactListener(
const JPH::BodyID &id,
const std::function<void(const JoltPhysics::ContactListener::
ContactReport &report)>
listener);
void removeContactListener(const JPH::BodyID &id);
};
#endif

View File

@@ -0,0 +1,7 @@
project(sceneloader)
set(CMAKE_CXX_STANDARD 17)
add_library(sceneloader STATIC loader.cpp)
target_include_directories(sceneloader PUBLIC .)
target_link_libraries(sceneloader PUBLIC OgreMain PRIVATE pugixml GameData physics)
target_link_libraries(sceneloader PUBLIC OgreTerrain)

2306
src/sceneloader/loader.cpp Normal file

File diff suppressed because it is too large Load Diff

84
src/sceneloader/loader.h Normal file
View File

@@ -0,0 +1,84 @@
#ifndef LOADER_H_
#define LOADER_H_
#include <pugixml.hpp>
#include <flecs.h>
#include <OgreColourValue.h>
#include <OgreQuaternion.h>
#include <OgreResourceGroupManager.h>
#include <OgreString.h>
#include <OgrePlugin.h>
#include <OgreCodec.h>
class SceneLoader {
public:
SceneLoader();
virtual ~SceneLoader();
void load(Ogre::DataStreamPtr &stream, const Ogre::String &groupName,
Ogre::SceneNode *rootNode, flecs::entity e);
void exportScene(Ogre::SceneNode *rootNode,
const Ogre::String &outFileName);
const Ogre::ColourValue &getBackgroundColour()
{
return mBackgroundColour;
}
static void setupPhysicsBody(Ogre::SceneNode *node,
Ogre::MovableObject *entity,
flecs::entity base_e);
protected:
void writeNode(pugi::xml_node &parentXML, const Ogre::SceneNode *node);
void processScene(pugi::xml_node &XMLRoot);
void processNodes(pugi::xml_node &XMLNode);
void processExternals(pugi::xml_node &XMLNode);
void processEnvironment(pugi::xml_node &XMLNode);
void processTerrainGroup(pugi::xml_node &XMLNode);
void processBlendmaps(pugi::xml_node &XMLNode);
void processUserData(pugi::xml_node &XMLNode,
Ogre::UserObjectBindings &userData);
void processLight(pugi::xml_node &XMLNode,
Ogre::SceneNode *pParent = 0);
void processCamera(pugi::xml_node &XMLNode,
Ogre::SceneNode *pParent = 0);
void processNode(pugi::xml_node &XMLNode, Ogre::SceneNode *pParent = 0);
void processLookTarget(pugi::xml_node &XMLNode,
Ogre::SceneNode *pParent);
void processTrackTarget(pugi::xml_node &XMLNode,
Ogre::SceneNode *pParent);
void processEntity(pugi::xml_node &XMLNode, Ogre::SceneNode *pParent);
void processParticleSystem(pugi::xml_node &XMLNode,
Ogre::SceneNode *pParent);
void processBillboardSet(pugi::xml_node &XMLNode,
Ogre::SceneNode *pParent);
void processPlane(pugi::xml_node &XMLNode, Ogre::SceneNode *pParent);
void processNodeAnimations(pugi::xml_node &XMLNode,
Ogre::SceneNode *pParent);
void processNodeAnimation(pugi::xml_node &XMLNode,
Ogre::SceneNode *pParent);
void processKeyframe(pugi::xml_node &XMLNode,
Ogre::NodeAnimationTrack *pTrack);
void processFog(pugi::xml_node &XMLNode);
void processSkyBox(pugi::xml_node &XMLNode);
void processSkyDome(pugi::xml_node &XMLNode);
void processSkyPlane(pugi::xml_node &XMLNode);
void processLightRange(pugi::xml_node &XMLNode, Ogre::Light *pLight);
void processLightAttenuation(pugi::xml_node &XMLNode,
Ogre::Light *pLight);
void processLightSourceSize(pugi::xml_node &XMLNode,
Ogre::Light *pLight);
Ogre::SceneManager *mSceneMgr;
Ogre::SceneNode *mAttachNode;
Ogre::String m_sGroupName;
Ogre::ColourValue mBackgroundColour;
Ogre::String mNamePrefix;
int counter;
flecs::entity base_e;
};
#endif

View File

@@ -13,7 +13,6 @@
#include <OgreMaterialManager.h>
#include <OgreTerrainAutoUpdateLod.h>
#include <OgreTerrainPagedWorldSection.h>
#include <OgreBullet.h>
#include <OgreMath.h>
#include <OgreLogManager.h>
#include "terrain.h"

View File

@@ -4,8 +4,6 @@
#include <unordered_set>
#include <iostream>
#include <Ogre.h>
#include <OgreBullet.h>
#include <BulletCollision/CollisionShapes/btHeightfieldTerrainShape.h>
#include <OgreTerrain.h>
#include <OgreTerrainGroup.h>
#include <OgrePageManager.h>
@@ -404,9 +402,11 @@ public:
return body;
}
#endif
bool isLoadedAt(const Ogre::Vector3 &position) const {
bool isLoadedAt(const Ogre::Vector3 &position) const
{
long x, y;
mTerrainGroup->convertWorldPositionToTerrainSlot(position, &x, &y);
mTerrainGroup->convertWorldPositionToTerrainSlot(position, &x,
&y);
if (mTerrainGroup->getTerrain(x, y))
return mTerrainGroup->getTerrain(x, y)->isLoaded();
return false;

12
src/tests/CMakeLists.txt Normal file
View File

@@ -0,0 +1,12 @@
project(tests)
find_package(pugixml CONFIG)
find_package(assimp REQUIRED CONFIG)
find_package(OGRE REQUIRED COMPONENTS Bites Bullet Paging Terrain CONFIG)
add_executable(check_uv check_uv.cpp)
target_link_libraries(check_uv ${ASSIMP_LIBRARIES})
add_executable(ogre_check_uv ogre_check_uv.cpp)
target_link_libraries(ogre_check_uv OgreBites OgreMain)
add_executable(check_scene_loader check_scene_loader.cpp)
target_link_libraries(check_scene_loader OgreBites OgreMain)

View File

@@ -0,0 +1,64 @@
#include <iostream>
#include <Ogre.h>
#include <OgreCodec.h>
#include <OgreFileSystem.h>
#include <OgreFileSystemLayer.h>
#include <OgreMaterialManager.h>
#include <OgreShaderGenerator.h>
int main()
{
Ogre::LogManager logMgr;
logMgr.createLog("messages.log", true, true, true);
Ogre::DefaultHardwareBufferManager
bufferManager; // needed because we don't have a rendersystem
Ogre::Root *ogre = new Ogre::Root("", "", "");
Ogre::ConfigFile pluginsCfg;
Ogre::FileSystemLayer fsLayer("Ogre3D");
pluginsCfg.load(fsLayer.getConfigFilePath("plugins.cfg"));
auto pluginDir = Ogre::FileSystemLayer::resolveBundlePath(
pluginsCfg.getSetting("PluginFolder") + "/");
ogre->loadPlugin(pluginDir + "/Codec_Assimp");
ogre->loadPlugin(pluginDir + "/Plugin_DotScene");
Ogre::MaterialManager::getSingleton().initialise();
Ogre::RTShader::ShaderGenerator::initialize();
Ogre::DefaultTextureManager texMgr;
auto &shadergen = Ogre::RTShader::ShaderGenerator::getSingleton();
shadergen.setTargetLanguage(
"glsl"); // must be valid, but otherwise arbitrary
shadergen.getRenderState(Ogre::MSN_SHADERGEN)
->setLightCountAutoUpdate(false);
shadergen.validateScheme(Ogre::MSN_SHADERGEN);
Ogre::SceneManager *sceneManager =
ogre->createSceneManager("DefaultSceneManager");
Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
"resources/vehicles", "FileSystem", "Generic", true, true);
Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
"../resources/vehicles", "FileSystem", "Generic", true, true);
Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
"../../resources/vehicles", "FileSystem", "Generic", true,
true);
std::map<Ogre::String, Ogre::Any> options;
options["prefix"] = Ogre::String("0/");
Ogre::SceneNode *p1 =
sceneManager->getRootSceneNode()->createChildSceneNode();
Ogre::Any optionsAny1 = options;
p1->getUserObjectBindings().setUserAny("_DotSceneLoaderOptions",
optionsAny1);
p1->loadChildren("boat.scene");
options["prefix"] = Ogre::String("1/");
Ogre::SceneNode *p2 =
sceneManager->getRootSceneNode()->createChildSceneNode();
Ogre::Any optionsAny2 = options;
p2->getUserObjectBindings().setUserAny("_DotSceneLoaderOptions",
optionsAny2);
p2->loadChildren("boat.scene");
// Ogre::DataStreamPtr sceneData =
// Ogre::ResourceGroupManager::getSingleton().openResource(
// "boat.scene", "General");
// auto codec = Ogre::Codec::getCodec("scene");
// codec->decode(sceneData, sceneManager->getRootSceneNode());
return 0;
}

54
src/tests/check_uv.cpp Normal file
View File

@@ -0,0 +1,54 @@
#include <iostream>
#include <list>
#include <assimp/version.h>
#include <assimp/scene.h>
#include <assimp/postprocess.h>
#include <assimp/Importer.hpp>
#include <assimp/IOStream.hpp>
#include <assimp/IOSystem.hpp>
int main(int argc, char *argv[])
{
int i;
Assimp::Importer importer;
uint32_t flags = aiProcessPreset_TargetRealtime_Fast | aiProcess_TransformUVCoords | aiProcess_FlipUVs;
flags &= ~(aiProcess_JoinIdenticalVertices | aiProcess_CalcTangentSpace); // optimize for fast loading
if((flags & (aiProcess_GenSmoothNormals | aiProcess_GenNormals)) != aiProcess_GenNormals)
flags &= ~aiProcess_GenNormals; // prefer smooth normals
float maxEdgeAngle = 0.75f;
importer.SetPropertyFloat("PP_GSN_MAX_SMOOTHING_ANGLE", maxEdgeAngle);
importer.SetPropertyBool(AI_CONFIG_IMPORT_FBX_EMBEDDED_TEXTURES_LEGACY_NAMING, true);
if (argc < 2) {
std::cerr << "path name needed" << std::endl;
return 1;
}
const char *pname = argv[1];
const aiScene* scene = importer.ReadFile(pname,
flags);
std::list<aiNode *> queue;
if (!scene) {
std::cerr << "could not process file" << std::endl;
return 1;
}
queue.push_back(scene->mRootNode);
while(!queue.empty()) {
aiNode *node = queue.front();
queue.pop_front();
for (i = 0; i < node->mNumMeshes; i++) {
aiMesh* mesh = scene->mMeshes[node->mMeshes[i]];
int uv_count = 0;
for (int uvindex = 0; uvindex < AI_MAX_NUMBER_OF_TEXTURECOORDS; uvindex++) {
aiVector3D* uv = mesh->mTextureCoords[uvindex];
if (!uv)
break;
uv_count++;
}
std::cout << "node: " << node->mName.C_Str() << " mesh: " << i << " mesh uv count: " << uv_count << std::endl;
}
for (i = 0; i < node->mNumChildren; i++)
queue.push_back(node->mChildren[i]);
}
return 0;
}

View File

@@ -0,0 +1,83 @@
#include <iostream>
#include <Ogre.h>
#include <OgreCodec.h>
#include <OgreFileSystem.h>
#include <OgreFileSystemLayer.h>
#include <OgreMaterialManager.h>
#include <OgreShaderGenerator.h>
static void getSubmeshUVs(const Ogre::Mesh *mesh, const Ogre::SubMesh *submesh,
std::vector<Ogre::Vector2> &uvs, int index)
{
int j;
float *pReal;
Ogre::HardwareVertexBufferSharedPtr vbuf;
Ogre::VertexData *vertex_data = submesh->useSharedVertices ?
mesh->sharedVertexData :
submesh->vertexData;
const Ogre::VertexElement *uvElem =
vertex_data->vertexDeclaration->findElementBySemantic(
Ogre::VES_TEXTURE_COORDINATES, index);
int vertex_count = 0;
if (submesh->useSharedVertices)
vertex_count += mesh->sharedVertexData->vertexCount;
else
vertex_count += submesh->vertexData->vertexCount;
if (!uvElem)
return;
OgreAssert(uvs.size() == 0 || uvs.size() == vertex_count,
"bad vertex count");
uvs.resize(vertex_count);
vbuf = vertex_data->vertexBufferBinding->getBuffer(uvElem->getSource());
unsigned char *uv = static_cast<unsigned char *>(
vbuf->lock(Ogre::HardwareBuffer::HBL_READ_ONLY));
for (j = 0; j < vertex_data->vertexCount; ++j) {
uvElem->baseVertexPointerToElement(uv, &pReal);
uvs[j] = Ogre::Vector2(pReal[0], pReal[1]);
uv += vbuf->getVertexSize();
}
vbuf->unlock();
}
int main()
{
Ogre::LogManager logMgr;
logMgr.createLog("messages.log", true, true, true);
Ogre::DefaultHardwareBufferManager bufferManager; // needed because we don't have a rendersystem
Ogre::Root *ogre = new Ogre::Root("", "", "");
Ogre::ConfigFile pluginsCfg;
Ogre::FileSystemLayer fsLayer("Ogre3D");
pluginsCfg.load(fsLayer.getConfigFilePath("plugins.cfg"));
auto pluginDir = Ogre::FileSystemLayer::resolveBundlePath(pluginsCfg.getSetting("PluginFolder")+"/");
ogre->loadPlugin(pluginDir + "/Codec_Assimp");
Ogre::MaterialManager::getSingleton().initialise();
Ogre::RTShader::ShaderGenerator::initialize();
Ogre::DefaultTextureManager texMgr;
auto& shadergen = Ogre::RTShader::ShaderGenerator::getSingleton();
shadergen.setTargetLanguage("glsl"); // must be valid, but otherwise arbitrary
shadergen.getRenderState(Ogre::MSN_SHADERGEN)->setLightCountAutoUpdate(false);
shadergen.validateScheme(Ogre::MSN_SHADERGEN);
auto codec = Ogre::Codec::getCodec("glb");
Ogre::ResourceGroupManager::getSingleton().createResourceGroup(
"Characters", true);
Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
"characters", "FileSystem", "Characters", true, true);
Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
"../characters", "FileSystem", "Characters", true, true);
Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
"../../characters", "FileSystem", "Characters", true, true);
Ogre::DataStreamPtr meshData = Ogre::ResourceGroupManager::getSingleton().openResource("shapes/male/edited-normal-male-base.glb", "Characters");
Ogre::MeshPtr mesh = Ogre::MeshManager::getSingleton().createManual("shapes/male/edited-normal-male-base.glb", "Characters");
codec->decode(meshData, mesh.get());
std::vector<Ogre::Vector2> uvs;
getSubmeshUVs(mesh.get(), mesh->getSubMesh(0), uvs, 0);
std::cout << "UV0: " << uvs.size() << std::endl;
uvs.clear();
getSubmeshUVs(mesh.get(), mesh->getSubMesh(0), uvs, 1);
std::cout << "UV1: " << uvs.size() << std::endl;
return 0;
}

21
src/world/CMakeLists.txt Normal file
View File

@@ -0,0 +1,21 @@
project(world)
set(CMAKE_CXX_STANDARD 17)
find_package(Bullet)
find_package(OGRE REQUIRED COMPONENTS Bites Bullet Paging Terrain Overlay CONFIG)
add_library(action STATIC action.cpp)
target_link_libraries(action PRIVATE GameData)
target_include_directories(action PUBLIC .)
add_library(world-build STATIC world-build.cpp)
target_link_libraries(world-build PRIVATE GameData)
target_include_directories(world-build PUBLIC .)
add_executable(test test.cpp)
target_link_libraries(test PRIVATE action world-build lua GameData OgreMain)
add_executable(test2 test2.cpp)
target_link_libraries(test2 PRIVATE action world-build lua GameData OgreMain)
add_executable(mark_harbors mark_harbors.cpp)
target_link_libraries(mark_harbors PRIVATE lua OgreMain OgreRTShaderSystem)
add_custom_target(world ALL DEPENDS test test2)

2
src/world/action.cpp Normal file
View File

@@ -0,0 +1,2 @@
#include <goap/goap.hpp>
#include "action.h"

2
src/world/action.h Normal file
View File

@@ -0,0 +1,2 @@
class CharacterAction {};
class WorldAction {};

View File

@@ -0,0 +1,320 @@
#pragma once
/**
@defgroup behtree Behavior Tree
## Introduction
A behavior tree is a tree structure where each node represents a behavior. The
tree is evaluated from the root node to the leaf nodes. The leaf nodes are
either tasks or checks. A task is a node that performs an action and returns
either success or failure. A check is a node that returns either success or
failure based on some condition.
<center><pre class="mermaid">
flowchart TD
root{{Selector}} --> seq1
root --> seq2
root --> seq3
seq1{{Sequence}} --> seq1a1
seq1 --> seq1a2
seq2{{Sequence}} --> seq2a1
seq2 --> seq2a2
seq3{{Sequence}} --> seq3a1
seq3 --> seq3a2
seq1a1[Check enemy in attack range]
seq1a2[[Destroy enemy]]
seq2a1[Check enemy in sight]
seq2a2[[Move towards enemy]]
seq3a1[[Move towards waypoint]]
seq3a2[[Select next waypoint]]
style root fill:darkred
style seq1 fill:darkgreen
style seq2 fill:darkgreen
style seq3 fill:darkgreen
</pre></center>
## Usage
First, include the header file:
```cpp
#include <aitoolkit/behtree.hpp>
```
Then, create a blackboard class that will hold the state of the tree:
```cpp
struct blackboard_type {
glm::vec2 agent_position;
glm::vec2 enemy_position;
float attack_range;
float sight_range;
size_t current_waypoint;
std::vector<glm::vec2> waypoints;
};
```
Next, create the tree:
```cpp
using namespace aitoolkit::bt;
auto tree = sel<blackboard_type>(
node_list<blackboard_type>(
seq<blackboard_type>(
node_list<blackboard_type>(
check<blackboard_type>([](const blackboard_type& bb) {
auto distance = glm::distance(bb.agent_position, bb.enemy_position);
return distance <= bb.attack_range;
}),
task<blackboard_type>([](blackboard_type& bb) {
// Destroy enemy
return execution_state::success;
})
)
),
seq<blackboard_type>(
node_list<blackboard_type>(
check<blackboard_type>([](const blackboard_type& bb) {
auto distance = glm::distance(bb.agent_position, bb.enemy_position);
return distance <= bb.sight_range;
}),
task<blackboard_type>([](blackboard_type& bb) {
// Move towards enemy
return execution_state::success;
})
)
),
seq<blackboard_type>::make({
task<blackboard_type>([](blackboard_type& bb) {
// Move towards waypoint
return execution_state::success;
}),
task<blackboard_type>([](blackboard_type& bb) {
// Select next waypoint
return execution_state::success;
})
})
)
);
```
Finally, evaluate the tree:
```cpp
auto bb = blackboard_type{
.agent_position = { 0.0f, 0.0f },
.enemy_position = { 1.0f, 1.0f },
.attack_range = 0.5f,
.sight_range = 1.0f
};
while (true) {
auto state = tree.evaluate(bb);
if (state == execution_state::success) {
break;
}
}
```
*/
#include <functional>
#include <memory>
#include <vector>
#include <type_traits>
#include <concepts>
namespace aitoolkit::bt {
/**
* @ingroup behtree
* @enum execution_state
* @brief Represent the state of a node
*/
enum class execution_state {
success, /**< The node was successful */
failure, /**< The node failed */
running /**< The node is still running */
};
/**
* @ingroup behtree
* @class node
* @brief Base abstract class for all nodes
*/
template <class T>
class node {
public:
node() = default;
node(const node&) = delete;
node(node&& other) {
m_children = std::move(other.m_children);
}
virtual ~node() = default;
virtual execution_state evaluate(T& blackboard) const = 0;
protected:
std::vector<std::unique_ptr<node<T>>> m_children;
};
/**
* @ingroup behtree
* @brief Heap-allocated pointer to node
*/
template <class T>
using node_ptr = std::unique_ptr<node<T>>;
template <typename N, class T>
concept node_trait = std::derived_from<N, node<T>>;
/**
* @ingroup behtree
* @brief Helper function to create a list of nodes
*/
template <typename T, node_trait<T> ...Children>
std::vector<node_ptr<T>> node_list(Children&&... children) {
auto nodes = std::vector<node_ptr<T>>{};
nodes.reserve(sizeof...(children));
(nodes.push_back(std::make_unique<Children>(std::move(children))), ...);
return nodes;
}
/**
* @ingroup behtree
* @class seq
* @brief Sequence node, will execute all children in order until one fails
*/
template <class T>
class seq final : public node<T> {
public:
seq(std::vector<node_ptr<T>> children) {
this->m_children = std::move(children);
}
virtual execution_state evaluate(T& blackboard) const override {
for (auto& child : this->m_children) {
auto state = child->evaluate(blackboard);
if (state != execution_state::success) {
return state;
}
}
return execution_state::success;
}
};
/**
* @ingroup behtree
* @class sel
* @brief Selector node, will execute all children in order until one succeeds
*/
template <class T>
class sel final : public node<T> {
public:
sel(std::vector<node_ptr<T>> children) {
this->m_children = std::move(children);
}
virtual execution_state evaluate(T& blackboard) const override {
for (auto& child : this->m_children) {
auto state = child->evaluate(blackboard);
if (state != execution_state::failure) {
return state;
}
}
return execution_state::failure;
}
};
/**
* @ingroup behtree
* @class neg
* @brief Negate node, will return the opposite of the child node
*/
template <class T>
class neg final : public node<T> {
public:
template <node_trait<T> N>
neg(N&& child) {
this->m_children.reserve(1);
this->m_children.push_back(std::make_unique<N>(std::move(child)));
}
virtual execution_state evaluate(T& blackboard) const override {
if (this->m_children.size() != 1) {
return execution_state::failure;
}
auto& child = this->m_children.front();
auto state = child->evaluate(blackboard);
if (state == execution_state::success) {
return execution_state::failure;
} else if (state == execution_state::failure) {
return execution_state::success;
}
return state;
}
};
/**
* @ingroup behtree
* @class check
* @brief Check node, will return success if the callback returns true
*/
template <class T>
class check final : public node<T> {
public:
using callback_type = std::function<bool(const T&)>;
public:
check(callback_type fn) : m_fn(fn) {}
virtual execution_state evaluate(T& blackboard) const override {
if (m_fn(blackboard)) {
return execution_state::success;
}
return execution_state::failure;
}
private:
callback_type m_fn;
};
/**
* @ingroup behtree
* @class task
* @brief Task node, will execute the callback and return the result
*/
template <class T>
class task final : public node<T> {
public:
using callback_type = std::function<execution_state(T&)>;
public:
task(callback_type fn) : m_fn(fn) {}
virtual execution_state evaluate(T& blackboard) const override {
return m_fn(blackboard);
}
private:
callback_type m_fn;
};
}

318
src/world/aitoolkit/fsm.hpp Normal file
View File

@@ -0,0 +1,318 @@
#pragma once
/**
@defgroup fsm Finite State Machine
## Introduction
A finite state machine (FSM) is a mathematical model of computation. It is an
abstract machine that can be in exactly one of a finite number of states at any
given time. The FSM can change from one state to another in response to some
external inputs; the change from one state to another is called a transition.
This library provides 2 types of FSMs: a simple machiine and a stack machine.
The simple machine is the simplest form of FSM, it can only be in one state at a
time. The stack machine is a more complex form of FSM, it can be in multiple
states at a time.
## Usage
First, include the header:
```cpp
#include <aitoolkit/fsm.hpp>
```
Then, create a blackboard type:
```cpp
struct blackboard_type {
// ...
};
```
Then, create a state type for each of your states:
```cpp
class state_dummy final : public state<blackboard_type> {
public:
virtual void enter(blackboard_type& blackboard) override {
// ...
}
virtual void exit(blackboard_type& blackboard) override {
// ...
}
virtual void pause(blackboard_type& blackboard) override {
// ...
}
virtual void resume(blackboard_type& blackboard) override {
// ...
}
virtual void update(blackboard_type& blackboard) override {
// ...
}
};
```
### Simple state machine
Create an instance of the FSM:
```cpp
using namespace aitoolkit::fsm;
auto machine = simple_machine<blackboard_type>{};
```
To transition to a new state, call `set_state()`:
```cpp
machine.set_state(state_dummy{}, blackboard);
```
> **NB:**
>
> - this will call the `exit` method of the current state (if any) and the
> `enter` method of the new state
> - if the machine is paused while transitioning to a new state, the new state
> will be paused as well
To pause the machine, call `pause()`:
```cpp
machine.pause(blackboard);
```
> **NB:** This will call the `pause` method of the current state (if any).
To resume the machine, call `resume()`:
```cpp
machine.resume(blackboard);
```
> **NB:** This will call the `resume` method of the current state (if any).
To update the machine, call `update()`:
```cpp
machine.update(blackboard);
```
> **NB:**
>
> - this will call the `update` method of the current state (if any)
> - if the machine is paused, calling `update()` will do nothing
To clear any state, call `clear_state()`:
```cpp
machine.clear_state(blackboard);
```
> **NB:** This will call the `exit` method of the current state (if any).
### Stack state machine
Create an instance of the FSM:
```cpp
using namespace aitoolkit::fsm;
auto machine = stack_machine<blackboard_type>{};
```
To push a new state onto the stack, call `push_state()`:
```cpp
machine.push_state(state_dummy{}, blackboard);
```
> **NB:** This will call the `pause` method of the current state (if any) and
> the `enter` method of the new state.
To pop the top state off the stack, call `pop_state()`:
```cpp
machine.pop_state(blackboard);
```
> **NB:** This will call the `exit` method of the current state (if any) and
> the `resume` method of the new top state (if any).
To update the machine, call `update()`:
```cpp
machine.update(blackboard);
```
> **NB:** This will call the `update` method of the top state (if any).
*/
#include <memory>
#include <vector>
#include <type_traits>
#include <concepts>
namespace aitoolkit::fsm {
/**
* @ingroup fsm
* @class state
* @brief A state of the FSM.
*/
template <typename T>
class state {
public:
virtual ~state() = default;
virtual void enter(T& blackboard) {};
virtual void exit(T& blackboard) {};
virtual void pause(T& blackboard) {};
virtual void resume(T& blackboard) {};
virtual void update(T& blackboard) {};
};
/**
* @ingroup fsm
* @brief Heap-allocated pointer to a state.
*/
template <typename T>
using state_ptr = std::unique_ptr<state<T>>;
template <typename S, typename T>
concept state_trait = std::derived_from<S, state<T>>;
/**
* @ingroup fsm
* @class simple_machine
* @brief A simple FSM.
*/
template <typename T>
class simple_machine {
public:
/**
* @brief Enters in a new state, exiting the previous one (if any).
*/
template <state_trait<T> S>
void set_state(S state, T& blackboard) {
if (m_current_state) {
m_current_state->exit(blackboard);
}
m_current_state = std::make_unique<S>(state);
m_current_state->enter(blackboard);
if (m_paused) {
m_current_state->pause(blackboard);
}
}
/**
* @brief Clear the current state.
*/
void clear_state(T& blackboard) {
if (m_current_state) {
m_current_state->exit(blackboard);
m_current_state = nullptr;
}
}
/**
* @brief Pause the machine.
*/
void pause(T& blackboard) {
m_paused = true;
if (m_current_state) {
m_current_state->pause(blackboard);
}
}
/**
* @brief Resume the machine.
*/
void resume(T& blackboard) {
m_paused = false;
if (m_current_state) {
m_current_state->resume(blackboard);
}
}
/**
* @brief Update the machine.
*/
void update(T& blackboard) {
if (m_paused) {
return;
}
if (m_current_state) {
m_current_state->update(blackboard);
}
}
private:
state_ptr<T> m_current_state{nullptr};
bool m_paused{false};
};
/**
* @ingroup fsm
* @class stack_machine
* @brief A stack FSM.
*/
template <typename T>
class stack_machine {
public:
/**
* @brief Enters in a new state, pausing the previous one (if any).
*/
template <state_trait<T> S>
void push_state(S state, T& blackboard) {
if (!m_state_stack.empty()) {
auto& current_state = m_state_stack.back();
current_state->pause(blackboard);
}
state.enter(blackboard);
m_state_stack.push_back(std::make_unique<S>(state));
}
/**
* @brief Exits the current state, resuming the previous one (if any).
*/
void pop_state(T& blackboard) {
if (!m_state_stack.empty()) {
auto& current_state = m_state_stack.back();
current_state->exit(blackboard);
m_state_stack.pop_back();
}
if (!m_state_stack.empty()) {
auto& current_state = m_state_stack.back();
current_state->resume(blackboard);
}
}
/**
* @brief Update the machine.
*/
void update(T& blackboard) {
if (!m_state_stack.empty()) {
auto& current_state = m_state_stack.back();
current_state->update(blackboard);
}
}
private:
std::vector<state_ptr<T>> m_state_stack;
};
}

View File

@@ -0,0 +1,383 @@
#pragma once
/**
@defgroup goap Goal Oriented Action Planning
## Introduction
Goal Oriented Action Planning (GOAP) is a planning algorithm that can be used
to find a sequence of actions that will lead to a goal state. The algorithm
works by searching through a graph of possible actions and their effects. The
algorithm is guaranteed to find a solution if one exists, but it is not
guaranteed to find the optimal solution.
<center><pre class="mermaid">
graph LR
start[Start] --> action1
action1[Get axe] --> action2
action2[Chop tree] --> goal
goal[Goal]
action3[Get pickaxe] --> action4
action3 --> action5
action4[Mine gold]
action5[Mine stone]
style start fill:darkred
style goal fill:darkgreen
</pre></center>
## Usage
First, include the header file:
```cpp
#include <aitoolkit/goap.hpp>
```
Then, create a blackboard class that will hold the state of the planner:
```cpp
struct blackboard_type {
bool has_axe{false};
bool has_pickaxe{false};
int wood{0};
int gold{0};
int stone{0};
};
```
> **NB:** The blackboard needs to be comparable (`a == b`) and hashable.
Next, create a class for each action that you want to be able to perform:
```cpp
using namespace aitoolkit::goap;
class get_axe final : public action<blackboard_type> {
public:
virtual float cost(const blackboard_type& blackboard) const override {
return 1.0f;
}
virtual bool check_preconditions(const blackboard_type& blackboard) const override {
return !blackboard.has_axe;
}
virtual void apply_effects(blackboard_type& blackboard, bool dry_run) const override {
blackboard.has_axe = true;
}
};
class get_pickaxe final : public action<blackboard_type> {
public:
virtual float cost(const blackboard_type& blackboard) const override {
return 1.0f;
}
virtual bool check_preconditions(const blackboard_type& blackboard) const override {
return !blackboard.has_pickaxe;
}
virtual void apply_effects(blackboard_type& blackboard, bool dry_run) const override {
blackboard.has_pickaxe = true;
}
};
class chop_tree final : public action<blackboard_type> {
public:
virtual float cost(const blackboard_type& blackboard) const override {
return 1.0f;
}
virtual bool check_preconditions(const blackboard_type& blackboard) const override {
return blackboard.has_axe;
}
virtual void apply_effects(blackboard_type& blackboard, bool dry_run) const override {
blackboard.wood += 1;
}
};
class mine_gold final : public action<blackboard_type> {
public:
virtual float cost(const blackboard_type& blackboard) const override {
return 1.0f;
}
virtual bool check_preconditions(const blackboard_type& blackboard) const override {
return blackboard.has_pickaxe;
}
virtual void apply_effects(blackboard_type& blackboard, bool dry_run) const override {
blackboard.gold += 1;
}
};
class mine_stone final : public action<blackboard_type> {
public:
virtual float cost(const blackboard_type& blackboard) const override {
return 1.0f;
}
virtual bool check_preconditions(const blackboard_type& blackboard) const override {
return blackboard.has_pickaxe;
}
virtual void apply_effects(blackboard_type& blackboard, bool dry_run) const override {
blackboard.stone += 1;
}
};
```
Finally, create a plan and run it:
```cpp
auto initial = blackboard_type{};
auto goal = blackboard_type{
.has_axe = true,
.has_pickaxe = true,
.wood = 1,
.gold = 1,
.stone = 1
};
auto p = planner<blackboard_type>(
action_list<blackboard_type>(
get_axe{},
get_pickaxe{},
chop_tree{},
mine_gold{},
mine_stone{}
)
initial,
goal
);
auto blackboard = initial;
while (p) {
p.run_next(blackboard); // will mutate the blackboard
}
```
*/
#include <unordered_set>
#include <functional>
#include <coroutine>
#include <optional>
#include <memory>
#include <vector>
#include <queue>
#include <stack>
#include <type_traits>
#include <concepts>
namespace aitoolkit::goap {
template <typename T>
concept blackboard_trait = requires(const T &a, const T &b) {
{ a == b } -> std::convertible_to<bool>;
{ std::hash<T>{}(a) } -> std::convertible_to<size_t>;
};
/**
* @ingroup goap
* @class action
* @brief An action that can be performed on a blackboard.
*
* An action hae a cost, which is used during planning, if 2 actions have a
* similar effect, the one with the lowest cost will be chosen.
*
* An action also has preconditions, which are used to determine if an action
* can be performed on a blackboard. If the preconditions are not met, the
* action will not be considered during planning.
*
* Finally, an action has effects, which are applied to the blackboard when
* the action is performed.
*/
template <blackboard_trait T>
class action {
public:
virtual ~action() = default;
/**
* @brief The cost of performing this action.
*/
virtual float cost(const T& blackboard) const = 0;
/**
* @brief Check if the preconditions for this action are met.
*/
virtual bool check_preconditions(const T& blackboard) const = 0;
/**
* @brief Apply the effects of this action to the blackboard.
*/
virtual void apply_effects(T& blackboard, bool dry_run) const = 0;
};
/**
* @brief Heeap allocated pointer to an action.
*/
template <blackboard_trait T>
using action_ptr = std::unique_ptr<action<T>>;
template <typename A, typename T>
concept action_trait = std::derived_from<A, action<T>>;
template <blackboard_trait T, action_trait<T>... Actions>
std::vector<action_ptr<T>> action_list(Actions... actions) {
auto action_list = std::vector<action_ptr<T>>{};
action_list.reserve(sizeof...(Actions));
(action_list.push_back(std::make_unique<Actions>(std::move(actions))), ...);
return action_list;
}
/**
* @ingroup goap
* @class plan
* @brief A plan is a sequence of actions that will lead to a goal state.
*/
template <blackboard_trait T>
class plan {
public:
plan() = default;
/**
* @brief Get the number of actions in the plan.
*/
size_t size() const {
return m_plan.size();
}
/**
* @brief Check if the plan is empty.
*/
operator bool() const {
return !m_plan.empty();
}
/**
* @brief Execute the next planned action.
*/
void run_next(T& blackboard) {
if (!m_plan.empty()) {
auto action_idx = m_plan.top();
m_plan.pop();
auto& action = m_actions[action_idx];
action->apply_effects(blackboard, false);
}
}
private:
std::stack<size_t> m_plan;
std::vector<action_ptr<T>> m_actions;
friend plan<T> planner<T>(
std::vector<action_ptr<T>> actions,
T initital_blackboard,
T goal_blackboard,
size_t max_iterations
);
};
/**
* @ingroup goap
* @brief Create a plan.
*
* The plan is created by providing a list of actions, an initial blackboard
* state, and a goal blackboard state. The plan will then find a sequence of
* actions that will lead to the goal state.
*
* @param actions The list of actions that can be performed.
* @param initital_blackboard The initial state of the blackboard.
* @param goal_blackboard The goal state of the blackboard.
* @param max_iterations The maximum number of iterations to perform
* before giving up. If 0, the plan will run until it finds a solution.
* @return A plan that can be executed.
*/
template <blackboard_trait T>
plan<T> planner(
std::vector<action_ptr<T>> actions,
T initital_blackboard,
T goal_blackboard,
size_t max_iterations = 0
) {
struct node_type {
T blackboard;
float cost;
std::optional<size_t> action_taken_idx;
std::shared_ptr<node_type> parent;
};
using node_ptr = std::shared_ptr<node_type>;
struct node_compare {
bool operator()(const node_ptr& a, const node_ptr& b) const {
return a->cost > b->cost;
}
};
std::priority_queue<node_ptr, std::vector<node_ptr>, node_compare> open_set;
std::unordered_set<T> closed_set;
open_set.push(std::make_shared<node_type>(node_type{
.blackboard = initital_blackboard,
.cost = 0.0f,
.action_taken_idx = std::nullopt,
.parent = nullptr
}));
for (
size_t iteration = 0;
!open_set.empty() && (max_iterations == 0 || iteration < max_iterations);
++iteration
) {
auto current_node = open_set.top();
open_set.pop();
if (current_node->blackboard == goal_blackboard) {
auto p = plan<T>();
p.m_actions = std::move(actions);
while (current_node->parent != nullptr) {
auto action_idx = current_node->action_taken_idx.value();
p.m_plan.push(action_idx);
current_node = current_node->parent;
}
return p;
}
if (!closed_set.contains(current_node->blackboard)) {
closed_set.insert(current_node->blackboard);
for (size_t action_idx = 0; action_idx < actions.size(); action_idx++) {
auto& action = actions[action_idx];
if (action->check_preconditions(current_node->blackboard)) {
auto next_blackboard = current_node->blackboard;
action->apply_effects(next_blackboard, true);
auto next_cost = current_node->cost + action->cost(current_node->blackboard);
if (!closed_set.contains(next_blackboard)) {
open_set.push(std::make_shared<node_type>(node_type{
.blackboard = next_blackboard,
.cost = next_cost,
.action_taken_idx = action_idx,
.parent = current_node
}));
}
}
}
}
}
return plan<T>();
}
}

View File

@@ -0,0 +1,203 @@
#pragma once
/**
@defgroup utility Utility AI
## Introduction
Utility AI is a planning algorithm that can be used to find the best action to
perform in a given situation. The algorithm works by assigning a score to each
action based on how well it will achieve the goal. The algorithm is guaranteed
to find a solution.
<center><pre class="mermaid">
flowchart TD
a1[Collect food\nscore: +50]
a2[Collect wood\nscore: +150]
a3[Collect stone\nscore: -10]
a4[Collect gold\nscore: +75]
style a1 color:darkred
style a2 color:darkgreen
style a3 color:darkred
style a4 color:darkred
</pre></center>
## Usage
First, include the header file:
```cpp
#include <aitoolkit/utility.hpp>
```
Then, create a blackboard type:
```cpp
struct blackboard_type {
int food{0};
int wood{0};
int stone{0};
int gold{0};
};
```
Next, create a class for each action that you want to be able to perform:
```cpp
using namespace aitoolkit::utility;
class collect_food final : public action<blackboard_type> {
public:
virtual float score(const blackboard_type& blackboard) const override {
return 50.0f;
}
virtual void apply(blackboard_type& blackboard) const override {
blackboard.food += 1;
}
};
class collect_wood final : public action<blackboard_type> {
public:
virtual float score(const blackboard_type& blackboard) const override {
return 150.0f;
}
virtual void apply(blackboard_type& blackboard) const override {
blackboard.wood += 1;
}
};
class collect_stone final : public action<blackboard_type> {
public:
virtual float score(const blackboard_type& blackboard) const override {
return -10.0f;
}
virtual void apply(blackboard_type& blackboard) const override {
blackboard.stone += 1;
}
};
class collect_gold final : public action<blackboard_type> {
public:
virtual float score(const blackboard_type& blackboard) const override {
return 75.0f;
}
virtual void apply(blackboard_type& blackboard) const override {
blackboard.gold += 1;
}
};
```
Finally, create an evaluator and run it:
```cpp
auto evaluator = evaluator<blackboard_type>(
action_list<blackboard_type>(
collect_food{},
collect_wood{},
collect_stone{},
collect_gol{}
)
};
auto blackboard = blackboard_type{};
evaluator.run(blackboard);
```
*/
#include <memory>
#include <vector>
#include <limits>
#include <type_traits>
#include <concepts>
namespace aitoolkit::utility {
/**
* @ingroup utility
* @class action
* @brief Base abstract class for all actions
*/
template <typename T>
class action {
public:
virtual ~action() = default;
/**
* @brief Return the score of the action
*/
virtual float score(const T& blackboard) const = 0;
/**
* @brief Apply the action to the blackboard
*/
virtual void apply(T& blackboard) const = 0;
};
/**
* @ingroup utility
* @brief Heap allocated pointer to an action.
*/
template <typename T>
using action_ptr = std::unique_ptr<action<T>>;
template <typename A, typename T>
concept action_trait = std::derived_from<A, action<T>>;
/**
* @ingroup utility
* @brief Helper function to create a list of actions
*/
template <typename T, action_trait<T> ...Actions>
std::vector<action_ptr<T>> action_list(Actions&&... actions) {
auto actions_list = std::vector<action_ptr<T>>{};
actions_list.reserve(sizeof...(Actions));
(actions_list.push_back(std::make_unique<Actions>(std::move(actions))), ...);
return actions_list;
}
/**
* @ingroup utility
* @class evaluator
* @brief Evaluate a set of actions and apply the best one.
*/
template <typename T>
class evaluator {
public:
/**
* @brief Construct an evaluator from a list of actions
*/
evaluator(std::vector<action_ptr<T>> actions) : m_actions(std::move(actions)) {}
/**
* @brief Find the best action and apply it to the blackboard
*/
void run(T& blackboard) const {
if (m_actions.empty()) {
return;
}
auto best_score = std::numeric_limits<float>::min();
auto best_action = m_actions.front().get();
for (auto& action : m_actions) {
auto score = action->score(blackboard);
if (score > best_score) {
best_score = score;
best_action = action.get();
}
}
best_action->apply(blackboard);
}
private:
std::vector<action_ptr<T>> m_actions;
};
}

21
src/world/goap/LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2016 Club Vaudois de Robotique Autonome
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.

193
src/world/goap/goap.hpp Normal file
View File

@@ -0,0 +1,193 @@
/** Action planner for basic AI purposes
*
* This file implements the technique presented in "Three States and a Plan:
* The A.I. of F.E.A.R." by Jeff Orkin.
*/
#ifndef GOAP_HPP
#define GOAP_HPP
#include <cstdint>
#include <cstdlib>
#include <goap/goap_internals.hpp>
#include <cstring>
namespace goap {
const int kErrorNoPathFound = -1;
const int kErrorNotEnoughMemory = -2;
template <typename State>
class Action {
public:
/** Checks if the given state allows this action to proceed. */
virtual bool can_run(const State& state) = 0;
/** Plans the effects of this action on the state */
virtual void plan_effects(State& state) = 0;
/** Tries to execute the task and returns true if it suceeded. */
virtual bool execute(State& state) = 0;
virtual ~Action() = default;
};
template <typename State>
class Goal {
public:
/** Checks if the goal is reached for the given state. */
virtual bool is_reached(const State& state) const
{
return distance_to(state) == 0;
}
/** Computes the distance from state to goal. */
virtual int distance_to(const State& state) const = 0;
virtual ~Goal() = default;
};
template <typename State, int N = 100>
class Planner {
VisitedState<State> nodes[N];
public:
/** Finds a plan from state to goal and returns its length.
*
* If path is given, then the found path is stored there.
*/
int plan(const State& state, Goal<State>& goal, Action<State>* actions[], unsigned action_count, Action<State>** path = nullptr, int path_len = 10)
{
visited_states_array_to_list(nodes, N);
auto free_nodes = &nodes[0];
auto open = list_pop_head(free_nodes);
VisitedState<State>* close = nullptr;
open->state = state;
open->cost = 0;
open->priority = 0;
open->parent = nullptr;
open->action = nullptr;
while (open) {
auto current = priority_list_pop(open);
list_push_head(close, current);
if (goal.is_reached(current->state)) {
auto len = 0;
for (auto p = current->parent; p; p = p->parent) {
len++;
}
if (len > path_len) {
return -1;
}
if (path) {
auto i = len - 1;
for (auto p = current; p->parent; p = p->parent, i--) {
path[i] = p->action;
}
}
return len;
}
for (auto i = 0u; i < action_count; i++) {
auto action = actions[i];
if (action->can_run(current->state)) {
// Cannot allocate a new node, abort
if (free_nodes == nullptr) {
// Garbage collect the node that is most unlikely to be
// visited (i.e. lowest priority)
VisitedState<State>*gc, *gc_prev = nullptr;
for (gc = open; gc && gc->next; gc = gc->next) {
gc_prev = gc;
}
if (!gc) {
return kErrorNotEnoughMemory;
}
if (gc_prev) {
gc_prev->next = nullptr;
}
free_nodes = gc;
}
auto neighbor = list_pop_head(free_nodes);
neighbor->state = current->state;
action->plan_effects(neighbor->state);
neighbor->cost = current->cost + 1;
neighbor->priority = current->priority + 1 + goal.distance_to(neighbor->state);
neighbor->parent = current;
neighbor->action = action;
bool should_insert = true;
// Check if the node is already in the list of nodes
// scheduled to be visited
for (auto p = open; p; p = p->next) {
if (p->state == neighbor->state) {
should_insert = false;
update_queued_state(p, neighbor);
}
}
// Check if the state is in the list of already visited
// state
for (auto p = close; p; p = p->next) {
if (p->state == neighbor->state) {
should_insert = false;
update_queued_state(p, neighbor);
}
}
if (should_insert) {
list_push_head(open, neighbor);
} else {
list_push_head(free_nodes, neighbor);
}
}
}
}
// Reaching here means we did not find a path
return kErrorNoPathFound;
}
};
// Distance class, used to build distance metrics that read easily
class Distance {
int distance;
public:
Distance shouldBeTrue(bool var)
{
distance += var ? 0 : 1;
return *this;
}
Distance shouldBeFalse(bool var)
{
distance += var ? 1 : 0;
return *this;
}
Distance shouldBeEqual(int target, int var)
{
distance += abs(var - target);
return *this;
}
operator int()
{
return distance;
}
};
}; // namespace goap
#endif

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