Compare commits

...

16 Commits

67 changed files with 6670 additions and 3402 deletions

View File

@@ -29,6 +29,7 @@ find_package(assimp REQUIRED CONFIG)
find_package(OgreProcedural REQUIRED CONFIG)
find_package(pugixml REQUIRED CONFIG)
find_package(flecs REQUIRED CONFIG)
find_package(Tracy REQUIRED CONFIG)
add_library(fix::assimp INTERFACE IMPORTED)
set_target_properties(fix::assimp PROPERTIES
@@ -81,11 +82,12 @@ add_executable(Game Game.cpp ${WATER_SRC})
target_include_directories(Game PRIVATE src/gamedata)
target_link_libraries(Game OgreBites OgrePaging OgreTerrain OgreMeshLodGenerator
OgreProcedural::OgreProcedural
OgreCrowd
GameData
sound
sceneloader physics
OgreCrowd
sceneloader physics lua
flecs::flecs_static
Tracy::TracyClient
-Wl,--as-needed
)
if(OGRE_STATIC)
@@ -244,7 +246,7 @@ 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})
target_compile_definitions(Game PRIVATE FLECS_CPP_NO_AUTO_REGISTRATION JPH_PROFILE_ENABLED)
target_compile_definitions(Game PRIVATE FLECS_CPP_NO_AUTO_REGISTRATION JPH_PROFILE_ENABLED JPH_DEBUG_RENDERER JPH_PROFILE_ENABLED JPH_DOUBLE_PRECISION)
install(TARGETS Game DESTINATION bin)
install(TARGETS Editor DESTINATION bin)

146
Game.cpp
View File

@@ -10,7 +10,6 @@
#include <OgreTimer.h>
#include <OgreMeshLodGenerator.h>
// #include "water/water.h"
#include "GameData.h"
#include "Components.h"
#include "CharacterModule.h"
@@ -21,6 +20,8 @@
#include "PhysicsModule.h"
#include "physics.h"
#include "sound.h"
#include <tracy/Tracy.hpp>
#include <tracy/TracyC.h>
class App;
class SkyRenderer : public Ogre::SceneManager::Listener {
protected:
@@ -171,6 +172,32 @@ public:
mSkyBoxGenParameters.skyBoxDistance = distance;
}
};
class FrameListenerTrace : public Ogre::FrameListener {
public:
TracyCZoneCtx mzone;
FrameListenerTrace()
: initialized(false)
{
}
bool frameStarted(const Ogre::FrameEvent &evt) override
{
TracyCZoneN(ctx, "OgreFrame", true);
mzone = ctx;
return true;
}
bool frameRenderingQueued(const Ogre::FrameEvent &evt) override
{
TracyCZoneEnd(mzone);
return true;
}
bool frameEnded(const Ogre::FrameEvent &evt) override
{
return true;
}
private:
bool initialized;
};
class App;
class KeyboardListener : public OgreBites::InputListener {
App *mApp;
@@ -333,6 +360,8 @@ public:
mScnMgr->addRenderQueueListener(pOverlaySystem);
mScnMgrInterior->addRenderQueueListener(pOverlaySystem);
mScnMgrInventory->addRenderQueueListener(pOverlaySystem);
FrameListenerTrace *trace = OGRE_NEW FrameListenerTrace;
root->addFrameListener(trace);
// mTrayMgr = new OgreBites::TrayManager("AppTrays",
// getRenderWindow());
}
@@ -342,14 +371,22 @@ public:
}
void locateResources() override
{
Ogre::ResourceGroupManager::getSingleton().createResourceGroup(
Ogre::ResourceGroupManager::getSingleton().createResourceGroup(
"Characters", true);
Ogre::ResourceGroupManager::getSingleton().createResourceGroup(
"Water", true);
Ogre::ResourceGroupManager::getSingleton().createResourceGroup(
"LuaScripts", false);
Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
"./lua-scripts", "FileSystem", "LuaScripts", true,
true);
OgreBites::ApplicationContext::locateResources();
Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
"./characters/male", "FileSystem", "Characters", false,
true);
Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
"./characters/female", "FileSystem", "Characters",
false, true);
OgreBites::ApplicationContext::locateResources();
}
void loadResources() override
{
@@ -432,6 +469,7 @@ public:
Ogre::RTShader::ShaderGenerator *shadergen =
Ogre::RTShader::ShaderGenerator::getSingletonPtr();
shadergen->addSceneManager(scnMgr);
scnMgr->setShadowTechnique(Ogre::SHADOWTYPE_NONE);
setWindowGrab(true);
std::cout << "Init camera"
<< "\n";
@@ -477,6 +515,7 @@ public:
}
void updateWorld(float delta)
{
ZoneScoped;
if (!ECS::get().has<ECS::GUI>())
goto end;
{
@@ -486,8 +525,8 @@ public:
setWindowGrab(gui.grab);
gui.grabChanged = false;
ECS::get().modified<ECS::GUI>();
std::cout << "updateWorld " << gui.grabChanged
<< " " << gui.grab << std::endl;
// std::cout << "updateWorld " << gui.grabChanged
// << " " << gui.grab << std::endl;
}
}
end:
@@ -628,7 +667,7 @@ end:
void setupInput()
{
}
JoltPhysicsWrapper *mJolt;
JoltPhysicsWrapper *mJolt;
void createContent()
{
int i;
@@ -686,9 +725,8 @@ end:
.each([this](ECS::GUI &gui) {
if (gui.grabChanged)
setWindowGrab(gui.grab);
std::cout << "grab: " << gui.grab << "\n";
std::cout << "GUI enabled: " << gui.enabled
<< "\n";
// std::cout << "grab: " << gui.grab << "\n";
// std::cout << "GUI enabled: " << gui.enabled << "\n";
});
ECS::get_mut<ECS::GUI>().grab = false;
ECS::get_mut<ECS::GUI>().grabChanged = true;
@@ -738,40 +776,50 @@ end:
};
void KeyboardListener::frameRendered(const Ogre::FrameEvent &evt)
{
if (fps_timer.getMilliseconds() > 1000.0f) {
std::cout << "FPS: "
<< mApp->getRenderWindow()->getStatistics().lastFPS
<< " ";
std::cout << "Draw calls: "
<< mApp->getRenderWindow()->getStatistics().batchCount
<< " ";
fps_timer.reset();
std::cout << "Drops: "
<< mApp->getRenderWindow()
->getStatistics()
.vBlankMissCount
<< "\n";
fps_timer.reset();
}
if (!isGuiEnabled() ||
(isGuiEnabled() && ECS::get<ECS::GUI>().narrationBox)) {
mApp->updateWorld(evt.timeSinceLastFrame);
if (mInitDelay >= 0.0f)
mInitDelay -= evt.timeSinceLastFrame;
}
{
ZoneScopedN("frameRendered");
if (fps_timer.getMilliseconds() > 1000.0f) {
std::cout << "FPS: "
<< mApp->getRenderWindow()
->getStatistics()
.lastFPS
<< " ";
std::cout << "Draw calls: "
<< mApp->getRenderWindow()
->getStatistics()
.batchCount
<< " ";
fps_timer.reset();
std::cout << "Drops: "
<< mApp->getRenderWindow()
->getStatistics()
.vBlankMissCount
<< "\n";
fps_timer.reset();
}
if (!isGuiEnabled() ||
(isGuiEnabled() && ECS::get<ECS::GUI>().narrationBox)) {
mApp->updateWorld(evt.timeSinceLastFrame);
if (mInitDelay >= 0.0f)
mInitDelay -= evt.timeSinceLastFrame;
}
if (!isGuiEnabled() && ECS::get().has<ECS::Input>()) {
ECS::Input &input = ECS::get().get_mut<ECS::Input>();
input.control = control;
input.mouse = mouse;
input.mouse_abs = mouse_abs;
mouse.x = 0;
mouse.y = 0;
input.wheel_y = wheel_y;
wheel_y = 0;
input.mouse_moved = mouse_moved;
input.wheel_moved = wheel_moved;
if (!isGuiEnabled() && ECS::get().has<ECS::Input>()) {
ECS::Input &input = ECS::get().get_mut<ECS::Input>();
input.control = control;
input.mouse = mouse;
input.mouse_abs = mouse_abs;
mouse.x = 0;
mouse.y = 0;
input.wheel_y = wheel_y;
wheel_y = 0;
input.mouse_moved = mouse_moved;
input.wheel_moved = wheel_moved;
}
}
#ifdef USE_RENDER_LOOP
FrameMark;
#endif
}
int main()
@@ -784,7 +832,23 @@ int main()
// KeyHandler keyHandler;
// ctx.addInputListener(&keyHandler);
ctx.enableDbgDraw(false);
#ifdef USE_RENDER_LOOP
ctx.getRoot()->startRendering();
#else
auto renderSystem = Ogre::Root::getSingleton().getRenderSystem();
OgreAssert(renderSystem, "no RenderSystem");
renderSystem->_initRenderTargets();
Ogre::Root::getSingleton().clearEventTimes();
Ogre::Root::getSingleton().queueEndRendering(false);
while (!Ogre::Root::getSingleton().endRenderingQueued()) {
{
ZoneScopedN("render");
if (!Ogre::Root::getSingleton().renderOneFrame())
break;
}
FrameMark;
}
#endif
ctx.setWindowGrab(false);
ctx.closeApp();
return 0;

View File

@@ -18,14 +18,20 @@ endforeach()
foreach(FURNITURE_FILE ${FURNITURE_FILES})
get_filename_component(FILE_NAME ${FURNITURE_FILE} NAME_WE)
set(PARTS_OUTPUT_DIR ${CMAKE_BINARY_DIR}/resources/buildings/parts/${FILE_NAME})
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${FILE_NAME}_baked.blend
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/${FURNITURE_FILE} ${CMAKE_CURRENT_BINARY_DIR}/${FURNITURE_FILE}
COMMAND ${BLENDER} -b ${CMAKE_CURRENT_BINARY_DIR}/${FURNITURE_FILE} -Y -P
${CMAKE_CURRENT_SOURCE_DIR}/bake_furniture.py
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${FURNITURE_FILE} ${CMAKE_CURRENT_SOURCE_DIR}/bake_furniture.py)
add_custom_command(
OUTPUT ${PARTS_OUTPUT_DIR}
COMMAND ${CMAKE_COMMAND} -E make_directory ${PARTS_OUTPUT_DIR}
COMMAND ${BLENDER} ${CMAKE_CURRENT_SOURCE_DIR}/${FURNITURE_FILE}
-b -Y -P
COMMAND ${BLENDER} -b ${CMAKE_CURRENT_BINARY_DIR}/${FILE_NAME}_baked.blend
-Y -P
${CMAKE_CURRENT_SOURCE_DIR}/export_furniture_parts.py
-- ${PARTS_OUTPUT_DIR}
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${PARTS_FILE} ${CMAKE_CURRENT_SOURCE_DIR}/export_furniture_parts.py)
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${FILE_NAME}_baked.blend ${CMAKE_CURRENT_SOURCE_DIR}/export_furniture_parts.py)
list(APPEND PARTS_OUTPUT_DIRS ${PARTS_OUTPUT_DIR})
endforeach()
add_custom_target(import_building_parts ALL DEPENDS ${PARTS_OUTPUT_DIRS})

View File

@@ -0,0 +1,125 @@
import bpy
def get_all_children_recursive(obj, mesh_list):
"""Recursively finds all mesh objects in the hierarchy."""
for child in obj.children:
if child.type == 'MESH' and child.data.uv_layers:
mesh_list.append(child)
get_all_children_recursive(child, mesh_list)
def setup_furniture_atlas():
# 1. Gather valid objects: Children of empties starting with "furniture-"
furniture_meshes = []
roots = [o for o in bpy.data.objects if o.name.startswith("furniture-")]
for root in roots:
# If the root itself is a mesh with UVs, add it
if root.type == 'MESH' and root.data.uv_layers:
furniture_meshes.append(root)
# Find all nested children
get_all_children_recursive(root, furniture_meshes)
# Remove duplicates if any (in case of complex parenting)
furniture_meshes = list(set(furniture_meshes))
if not furniture_meshes:
print("No recursive mesh objects found with UVs.")
return
furniture_objs = furniture_meshes
if not furniture_objs:
print("No valid furniture mesh objects found with UVs.")
return
# 2. Manage the Atlas Image (FurnitureColor)
image_name = "FurnitureColor"
if image_name in bpy.data.images:
atlas_img = bpy.data.images[image_name]
else:
atlas_img = bpy.data.images.new(image_name, width=2048, height=2048)
# 3. Setup Materials & Nodes
processed_mats = set()
for obj in furniture_objs:
for slot in obj.material_slots:
mat = slot.material
if mat and mat not in processed_mats:
mat.use_nodes = True
nodes = mat.node_tree.nodes
# Find or create the target node
bake_node = nodes.get("ATLAS_TARGET")
if not bake_node:
bake_node = nodes.new('ShaderNodeTexImage')
bake_node.name = "ATLAS_TARGET"
bake_node.image = atlas_img
nodes.active = bake_node # Essential for the Bake operator
processed_mats.add(mat)
# 4. Selection & Context Setup
bpy.ops.object.select_all(action='DESELECT')
for obj in furniture_objs:
obj.select_set(True)
bpy.context.view_layer.objects.active = furniture_objs[0]
# 5. Bake Configuration (Cycles, Diffuse, Color only)
scene = bpy.context.scene
scene.render.engine = 'CYCLES'
scene.cycles.device = 'CPU'
scene.cycles.samples = 10
scene.render.bake.use_pass_direct = False
scene.render.bake.use_pass_indirect = False
scene.render.bake.use_pass_color = True
scene.render.bake.target = 'IMAGE_TEXTURES'
print("Baking Furniture Atlas...")
override = {
'active_object': bpy.context.view_layer.objects.active,
'selected_objects': bpy.context.selected_objects,
}
with bpy.context.temp_override(**override):
bpy.ops.object.bake(type='DIFFUSE', margin=2)
atlas_img.pack()
# 6. Create and Assign Final Atlas Material
atlas_mat_name = "M_Furniture_Atlas"
if atlas_mat_name in bpy.data.materials:
atlas_mat = bpy.data.materials[atlas_mat_name]
else:
atlas_mat = bpy.data.materials.new(name=atlas_mat_name)
atlas_mat.use_nodes = True
bsdf = atlas_mat.node_tree.nodes.get("Principled BSDF")
tex_node = atlas_mat.node_tree.nodes.new('ShaderNodeTexImage')
tex_node.image = atlas_img
atlas_mat.node_tree.links.new(tex_node.outputs['Color'], bsdf.inputs['Base Color'])
nodes = atlas_mat.node_tree.nodes
bsdf = nodes.get("Principled BSDF")
if bsdf:
bsdf.inputs['Roughness'].default_value = 1.0
# Reassign all valid objects to the new material
for obj in furniture_objs:
obj.data.materials.clear()
obj.data.materials.append(atlas_mat)
print("Process Complete. Atlas material applied.")
for mat in bpy.data.materials:
if mat.users == 0:
bpy.data.materials.remove(mat)
bpy.ops.outliner.orphans_purge(do_local_ids=True, do_linked_ids=True, do_recursive=True)
scene.render.engine = 'BLENDER_EEVEE'
current_path = bpy.data.filepath
if current_path:
new_path = current_path.replace(".blend", "_baked.blend")
bpy.ops.wm.save_as_mainfile(filepath=new_path)
print(f"File saved to: {new_path}")
else:
print("Warning: File not saved (unsaved blend file).")
setup_furniture_atlas()

View File

@@ -81,7 +81,7 @@ class TEST_PT_ObjectPanel(bpy.types.Panel):
# Link button with preset arguments
row = layout.row(align=True)
op = row.operator("test.link_and_play", text="Link & Sit", icon='PLAY')
op.filepath = "//../../edited-normal-male.blend" # SET YOUR FILEPATH
op.filepath = "//../../characters/edited-normal-male.blend" # SET YOUR FILEPATH
op.rig_name = "male" # SET YOUR RIG NAME
op.action_name = "sitting-chair" # SET YOUR ACTION NAME

View File

@@ -11,18 +11,26 @@ add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/edited-normal-${EDITED_BLEND}.blend
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/edited-normal-${EDITED_BLEND}.blend
${CMAKE_BINARY_DIR}/assets/blender/vrm-vroid-normal-${EDITED_BLEND}.blend
${CMAKE_CURRENT_SOURCE_DIR}/copy_animations.py
${CMAKE_BINARY_DIR}/assets/blender/mixamo
${CMAKE_CURRENT_BINARY_DIR}/blender-addons-installed
COMMAND ${CMAKE_COMMAND}
-E copy ${CMAKE_CURRENT_SOURCE_DIR}/edited-normal-${EDITED_BLEND}.blend
${CMAKE_CURRENT_BINARY_DIR}/edited-normal-${EDITED_BLEND}.blend
COMMAND ${BLENDER} -b -Y
${CMAKE_CURRENT_BINARY_DIR}/edited-normal-${EDITED_BLEND}.blend
-P ${CMAKE_SOURCE_DIR}/assets/blender/scripts/copy_animations.py --
-P ${CMAKE_CURRENT_SOURCE_DIR}/copy_animations.py --
${CMAKE_BINARY_DIR}/assets/blender/vrm-vroid-normal-${EDITED_BLEND}.blend ${EDITED_BLEND}
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)
list(APPEND EDITED_BLEND_TARGETS ${CMAKE_BINARY_DIR}/assets/blender/characters/edited-normal-${EDITED_BLEND}.blend)
list(APPEND CHARACTER_GLBS ${CMAKE_BINARY_DIR}/characters/${EDITED_BLEND}/normal-${EDITED_BLEND}.glb)
endforeach()
list(APPEND CHARACTER_GLBS ${CMAKE_BINARY_DIR}/characters/male/male-clothes-toprobe.glb)
add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/assets/blender/mixamo
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/assets/blender/mixamo ${CMAKE_BINARY_DIR}/assets/blender/mixamo
DEPENDS ${CMAKE_SOURCE_DIR}/assets/blender/mixamo
)
set(VRM_IMPORTED_BLENDS
${CMAKE_BINARY_DIR}/assets/blender/vrm-vroid-normal-female.blend
@@ -30,16 +38,69 @@ set(VRM_IMPORTED_BLENDS
${CMAKE_BINARY_DIR}/assets/blender/shapes/male/vrm-vroid-normal-male-chibi.blend
)
#add_custom_command(
# OUTPUT ${CHARACTER_GLBS}
# COMMAND ${CMAKE_COMMAND} -E make_directory ${CREATE_DIRECTORIES}
# COMMAND ${BLENDER} -b -Y -P ${CMAKE_SOURCE_DIR}/assets/blender/scripts/export_models.py
# COMMAND ${CMAKE_COMMAND} -D FILE=${CMAKE_BINARY_DIR}/characters/male/normal-male.glb -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/check_file_size.cmake
# COMMAND ${CMAKE_COMMAND} -D FILE=${CMAKE_BINARY_DIR}/characters/female/normal-female.glb -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/check_file_size.cmake
# COMMAND ${CMAKE_COMMAND} -E touch_nocreate ${CHARACTER_GLBS}
# DEPENDS ${CMAKE_SOURCE_DIR}/assets/blender/scripts/export_models.py ${VRM_IMPORTED_BLENDS} ${EDITED_BLEND_TARGETS}
# WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
set(FEMALE_OBJECTS "BodyTopRobe;BodyTop;BodyBottom;BodyFeet;Hair;Face;BackHair;Accessoty")
set(MALE_OBJECTS "BodyTopRobe;BodyTop;BodyBottomPants;BodyBottom_Panties001;BodyBottom;BodyFeetPants;BodyFeetPantsShoes;BodyFeet;Hair;Face;BackHair;Accessory")
add_custom_command(
OUTPUT ${CHARACTER_GLBS}
COMMAND ${CMAKE_COMMAND} -E make_directory ${CREATE_DIRECTORIES}
COMMAND ${BLENDER} -b -Y -P ${CMAKE_SOURCE_DIR}/assets/blender/scripts/export_models.py
COMMAND ${CMAKE_COMMAND} -D FILE=${CMAKE_BINARY_DIR}/characters/male/normal-male.glb -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/check_file_size.cmake
COMMAND ${CMAKE_COMMAND} -D FILE=${CMAKE_BINARY_DIR}/characters/female/normal-female.glb -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/check_file_size.cmake
COMMAND ${CMAKE_COMMAND} -E touch_nocreate ${CHARACTER_GLBS}
DEPENDS ${CMAKE_SOURCE_DIR}/assets/blender/scripts/export_models.py ${VRM_IMPORTED_BLENDS} ${EDITED_BLEND_TARGETS}
WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
OUTPUT ${CMAKE_BINARY_DIR}/characters/male/normal-male.glb
COMMAND ${CMAKE_COMMAND} -E make_directory ${CREATE_DIRECTORIES}
COMMAND ${BLENDER} -b -Y -P ${CMAKE_SOURCE_DIR}/assets/blender/scripts/export_models2.py --
${CMAKE_CURRENT_BINARY_DIR}/edited-normal-male-consolidated.blend
${CMAKE_BINARY_DIR}/characters/male/normal-male.glb
"${MALE_OBJECTS}"
"male"
tmp-edited-male.blend
COMMAND ${CMAKE_COMMAND} -D FILE=${CMAKE_BINARY_DIR}/characters/male/normal-male.glb
-P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/check_file_size.cmake
COMMAND ${CMAKE_COMMAND} -D FILE=${CMAKE_BINARY_DIR}/characters/male/normal-male.scene
-P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/check_file_size.cmake
DEPENDS ${CMAKE_SOURCE_DIR}/assets/blender/scripts/export_models2.py
${VRM_IMPORTED_BLENDS}
${CMAKE_CURRENT_BINARY_DIR}/edited-normal-male-consolidated.blend
${CMAKE_CURRENT_BINARY_DIR}/blender-addons-installed
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
VERBATIM
)
add_custom_command(
OUTPUT ${CMAKE_BINARY_DIR}/characters/female/normal-female.glb
COMMAND ${CMAKE_COMMAND} -E make_directory ${CREATE_DIRECTORIES}
COMMAND ${BLENDER} -b -Y -P ${CMAKE_SOURCE_DIR}/assets/blender/scripts/export_models2.py --
${CMAKE_CURRENT_BINARY_DIR}/edited-normal-female.blend
${CMAKE_BINARY_DIR}/characters/female/normal-female.glb
"${FEMALE_OBJECTS}"
"female"
tmp-edited-female.blend
COMMAND ${CMAKE_COMMAND} -D FILE=${CMAKE_BINARY_DIR}/characters/female/normal-female.glb
-P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/check_file_size.cmake
COMMAND ${CMAKE_COMMAND} -D FILE=${CMAKE_BINARY_DIR}/characters/female/normal-female.scene
-P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/check_file_size.cmake
DEPENDS ${CMAKE_SOURCE_DIR}/assets/blender/scripts/export_models2.py
${VRM_IMPORTED_BLENDS}
${CMAKE_CURRENT_BINARY_DIR}/edited-normal-female.blend
${CMAKE_CURRENT_BINARY_DIR}/blender-addons-installed
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
VERBATIM
)
add_custom_command(
OUTPUT ${CMAKE_BINARY_DIR}/characters/male/male-clothes-toprobe.glb
COMMAND ${BLENDER} -b -Y ${CMAKE_CURRENT_SOURCE_DIR}/edited-normal-male.blend
-P ${CMAKE_CURRENT_SOURCE_DIR}/export_clothes.py
-- ${CMAKE_BINARY_DIR}/characters/male/male-clothes-toprobe.glb
BodyTopRobe
COMMAND ${CMAKE_COMMAND} -D FILE=${CMAKE_BINARY_DIR}/characters/male/male-clothes-toprobe.glb
-P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/check_file_size.cmake
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/edited-normal-male.blend
${CMAKE_CURRENT_BINARY_DIR}/blender-addons-installed
)
set(VRM_SOURCE)
@@ -62,13 +123,29 @@ foreach(MIXAMO_FILE ${MIXAMO_FILES})
list(APPEND VRM_SOURCE "${OUTPUT_FILE}")
endforeach()
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/blender-addons-installed
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/assets/blender/scripts/addons ${CMAKE_BINARY_DIR}/assets/blender/scripts/addons
COMMAND ${BLENDER} -b -Y -P ${CMAKE_SOURCE_DIR}/assets/blender/scripts/install_addons.py
COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_BINARY_DIR}/blender-addons-installed
DEPENDS ${CMAKE_SOURCE_DIR}/assets/blender/scripts/addons/3.6/VRM_Addon_for_Blender-release.zip
${CMAKE_SOURCE_DIR}/assets/blender/scripts/addons/3.6/io_ogre.zip
DEPENDS ${CMAKE_BINARY_DIR}/assets/blender/scripts/addons/3.6/VRM_Addon_for_Blender-release.zip
${CMAKE_BINARY_DIR}/assets/blender/scripts/addons/3.6/io_ogre.zip
${CMAKE_SOURCE_DIR}/assets/blender/scripts/install_addons.py
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)
add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/assets/blender/scripts/addons/3.6/VRM_Addon_for_Blender-release.zip
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_SOURCE_DIR}/assets/blender/scripts/addons/3.6/VRM_Addon_for_Blender-release.zip
${CMAKE_BINARY_DIR}/assets/blender/scripts/addons/3.6/VRM_Addon_for_Blender-release.zip
DEPENDS ${CMAKE_SOURCE_DIR}/assets/blender/scripts/addons/3.6/VRM_Addon_for_Blender-release.zip
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)
add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/assets/blender/scripts/addons/3.6/io_ogre.zip
COMMAND zip -r ${CMAKE_BINARY_DIR}/assets/blender/scripts/addons/3.6/io_ogre.zip io_ogre
DEPENDS ${CMAKE_SOURCE_DIR}/assets/blender/scripts/blender2ogre/io_ogre
${CMAKE_SOURCE_DIR}/assets/blender/scripts/blender2ogre/io_ogre/ui/export.py
${CMAKE_SOURCE_DIR}/assets/blender/scripts/blender2ogre/io_ogre/ogre/skeleton.py
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/assets/blender/scripts/blender2ogre
)
add_custom_target(install_addons ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/blender-addons-installed)
#add_custom_command(OUTPUT ${VRM_IMPORTED_BLENDS}
# COMMAND ${CMAKE_COMMAND} -E make_directory ${CREATE_DIRECTORIES}
@@ -84,7 +161,7 @@ function(blender_import_vrm BLEND VRM EDITABLE RIG)
get_filename_component(VRM_NAME ${VRM} NAME_WE)
add_custom_command(OUTPUT ${BLEND}
COMMAND ${CMAKE_COMMAND} -E make_directory ${CREATE_DIRECTORIES}
COMMAND ${BLENDER} -b -Y -P ${CMAKE_SOURCE_DIR}/assets/blender/scripts/import_vrm2.py -- ${VRM_NAME}.vrm ${TARGET_NAME}.blend ${EDITABLE} ${RIG}
COMMAND ${BLENDER} -b -Y -P ${CMAKE_SOURCE_DIR}/assets/blender/scripts/import_vrm2.py -- ${VRM_NAME}.vrm ${BLEND} ${EDITABLE} ${RIG}
COMMAND ${CMAKE_COMMAND} -D FILE=${BLEND} -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/check_file_size.cmake
COMMAND ${CMAKE_COMMAND} -E touch_nocreate ${BLEND}
DEPENDS ${CMAKE_SOURCE_DIR}/assets/blender/scripts/import_vrm2.py
@@ -138,3 +215,218 @@ add_custom_target(edited-blends ALL DEPENDS ${EDITED_BLEND_TARGETS})
add_custom_target(import_vrm DEPENDS ${CHARACTER_GLBS})
function(weight_clothes SRC)
get_filename_component(TARGET_NAME ${SRC} NAME_WE)
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/clothes/${TARGET_NAME}_weighted.stamp
${CMAKE_CURRENT_BINARY_DIR}/clothes/${TARGET_NAME}_weighted.blend
COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/clothes
COMMAND ${BLENDER} -b -Y -P ${CMAKE_CURRENT_SOURCE_DIR}/process_clothes.py -- ${CMAKE_CURRENT_SOURCE_DIR}/${TARGET_NAME}.blend ./ ${CMAKE_CURRENT_BINARY_DIR}/clothes
COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_BINARY_DIR}/clothes/${TARGET_NAME}_weighted.stamp
COMMAND ${CMAKE_COMMAND} -D FILE=${CMAKE_CURRENT_BINARY_DIR}/clothes/${TARGET_NAME}_weighted.blend
-P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/check_file_size.cmake
DEPENDS ${SRC}
${CMAKE_CURRENT_BINARY_DIR}/blender-addons-installed
)
endfunction()
function(transfer_shape_keys SRC CLOTH DST)
add_custom_command(
OUTPUT ${DST}
COMMAND ${BLENDER} -b -Y -P ${CMAKE_CURRENT_SOURCE_DIR}/transfer_shape_keys.py -- ${SRC} ${CLOTH} ${DST}
COMMAND ${CMAKE_COMMAND} -D FILE=${DST}
-P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/check_file_size.cmake
DEPENDS ${SRC} ${CLOTH}
${CMAKE_CURRENT_BINARY_DIR}/blender-addons-installed
${CMAKE_CURRENT_SOURCE_DIR}/transfer_shape_keys.py
)
endfunction()
# Function to combine clothes into a blend file
# Parameters:
# INPUT_BLEND - Input blend file (required)
# WEIGHTED_BLEND - Weighted clothes blend file to combine (required)
# COMBINED_BLEND - Output combined blend file (required)
function(add_clothes_combination INPUT_BLEND WEIGHTED_BLEND COMBINED_BLEND)
# Parse optional arguments
set(options "")
set(oneValueArgs OUTPUT_DIR)
set(multiValueArgs "")
cmake_parse_arguments(COMBINE "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
# Validate required arguments
if(NOT INPUT_BLEND)
message(FATAL_ERROR "INPUT_BLEND is required for add_clothes_combination")
endif()
if(NOT WEIGHTED_BLEND)
message(FATAL_ERROR "WEIGHTED_BLEND is required for add_clothes_combination")
endif()
if(NOT COMBINED_BLEND)
message(FATAL_ERROR "COMBINED_BLEND output path is required for add_clothes_combination")
endif()
# Get the base name from the weighted blend file for stamp file
get_filename_component(WEIGHTED_BLEND_NAME "${WEIGHTED_BLEND}" NAME_WE)
# Remove "_weighted" suffix if present
string(REGEX REPLACE "_weighted$" "" TARGET_BASE "${WEIGHTED_BLEND_NAME}")
# Set default output directory for stamp if not provided
if(NOT COMBINE_OUTPUT_DIR)
set(COMBINE_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/clothes")
endif()
# Define stamp file using the derived base name
set(STAMP_FILE "${COMBINE_OUTPUT_DIR}/${TARGET_BASE}-combined.stamp")
# Derive the weighted stamp dependency
get_filename_component(WEIGHTED_DIR "${WEIGHTED_BLEND}" DIRECTORY)
get_filename_component(WEIGHTED_BASE "${WEIGHTED_BLEND}" NAME_WE)
set(WEIGHTED_STAMP "${WEIGHTED_DIR}/${WEIGHTED_BASE}.stamp")
# Ensure the output directory for COMBINED_BLEND exists
get_filename_component(COMBINED_DIR "${COMBINED_BLEND}" DIRECTORY)
add_custom_command(
OUTPUT ${STAMP_FILE} ${COMBINED_BLEND}
COMMAND ${CMAKE_COMMAND} -E make_directory ${COMBINED_DIR}
COMMAND ${BLENDER} -b -Y -P ${CMAKE_CURRENT_SOURCE_DIR}/combine_clothes.py --
${INPUT_BLEND}
${WEIGHTED_BLEND}
${COMBINED_BLEND}
COMMAND ${CMAKE_COMMAND} -E touch ${STAMP_FILE}
COMMAND ${CMAKE_COMMAND} -D FILE=${COMBINED_BLEND}
-P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/check_file_size.cmake
DEPENDS ${WEIGHTED_STAMP}
${WEIGHTED_BLEND}
${CMAKE_CURRENT_BINARY_DIR}/blender-addons-installed
${CMAKE_CURRENT_SOURCE_DIR}/combine_clothes.py
COMMENT "Combining clothes from ${WEIGHTED_BLEND} into ${COMBINED_BLEND}"
)
endfunction()
# Function to consolidate blend files
# Parameters:
# INPUT_BLEND - Input blend file to consolidate into (required)
# COMBINED_BLEND - Combined blend file to consolidate (required)
# OUTPUT_BLEND - Output consolidated blend file (required)
function(add_blend_consolidation INPUT_BLEND COMBINED_BLEND OUTPUT_BLEND)
# Parse optional arguments
set(options "")
set(oneValueArgs "")
set(multiValueArgs "")
cmake_parse_arguments(CONSOLIDATE "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
# Validate required arguments
if(NOT INPUT_BLEND)
message(FATAL_ERROR "INPUT_BLEND is required for add_blend_consolidation")
endif()
if(NOT COMBINED_BLEND)
message(FATAL_ERROR "COMBINED_BLEND is required for add_blend_consolidation")
endif()
if(NOT OUTPUT_BLEND)
message(FATAL_ERROR "OUTPUT_BLEND output path is required for add_blend_consolidation")
endif()
# Get the base name from the combined blend file for stamp derivation
get_filename_component(COMBINED_NAME "${COMBINED_BLEND}" NAME_WE)
# Remove "_combined" suffix if present
string(REGEX REPLACE "_combined$" "" TARGET_BASE "${COMBINED_NAME}")
# Derive stamp dependency
get_filename_component(COMBINED_DIR "${COMBINED_BLEND}" DIRECTORY)
set(COMBINE_STAMP "${COMBINED_DIR}/${TARGET_BASE}-combined.stamp")
# Ensure output directory exists
get_filename_component(OUTPUT_DIR "${OUTPUT_BLEND}" DIRECTORY)
add_custom_command(
OUTPUT ${OUTPUT_BLEND}
DEPENDS ${COMBINED_BLEND}
${CMAKE_CURRENT_SOURCE_DIR}/consolidate.py
${COMBINE_STAMP}
${INPUT_BLEND}
COMMAND ${CMAKE_COMMAND} -E make_directory ${OUTPUT_DIR}
COMMAND ${BLENDER} -b -Y ${INPUT_BLEND}
-P ${CMAKE_CURRENT_SOURCE_DIR}/consolidate.py --
${COMBINED_BLEND}
${OUTPUT_BLEND}
COMMAND ${CMAKE_COMMAND} -D FILE=${OUTPUT_BLEND}
-P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/check_file_size.cmake
COMMENT "Consolidating ${COMBINED_BLEND} into ${OUTPUT_BLEND}"
)
endfunction()
# Combined pipeline function
# Parameters:
# INPUT_BLEND - Input blend file (required)
# WEIGHTED_BLEND - Weighted clothes blend file (required)
# FINAL_OUTPUT_BLEND - Final consolidated output (required)
function(add_clothes_pipeline INPUT_BLEND WEIGHTED_BLEND FINAL_OUTPUT_BLEND)
# Parse optional arguments
set(options "")
set(oneValueArgs COMBINED_BLEND INTERMEDIATE_DIR)
set(multiValueArgs "")
cmake_parse_arguments(PIPELINE "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
# Validate required arguments
if(NOT INPUT_BLEND)
message(FATAL_ERROR "INPUT_BLEND is required for add_clothes_pipeline")
endif()
if(NOT WEIGHTED_BLEND)
message(FATAL_ERROR "WEIGHTED_BLEND is required for add_clothes_pipeline")
endif()
if(NOT FINAL_OUTPUT_BLEND)
message(FATAL_ERROR "FINAL_OUTPUT_BLEND is required for add_clothes_pipeline")
endif()
# Get the base name for deriving intermediate filenames
get_filename_component(WEIGHTED_NAME "${WEIGHTED_BLEND}" NAME_WE)
string(REGEX REPLACE "_weighted$" "" TARGET_BASE "${WEIGHTED_NAME}")
# Set intermediate directory
if(NOT PIPELINE_INTERMEDIATE_DIR)
set(PIPELINE_INTERMEDIATE_DIR "${CMAKE_CURRENT_BINARY_DIR}/clothes")
endif()
# Define intermediate combined blend file
if(PIPELINE_COMBINED_BLEND)
set(COMBINED_BLEND "${PIPELINE_COMBINED_BLEND}")
else()
set(COMBINED_BLEND "${PIPELINE_INTERMEDIATE_DIR}/${TARGET_BASE}_combined.blend")
endif()
set(SHAPED_BLEND "${PIPELINE_INTERMEDIATE_DIR}/${TARGET_BASE}_shaped.blend")
# Step 1: Combine clothes
add_clothes_combination(
"${INPUT_BLEND}"
"${WEIGHTED_BLEND}"
"${COMBINED_BLEND}"
OUTPUT_DIR "${PIPELINE_INTERMEDIATE_DIR}"
)
transfer_shape_keys(${INPUT_BLEND}
${COMBINED_BLEND}
${SHAPED_BLEND}
)
# Step 2: Consolidate
add_blend_consolidation(
"${INPUT_BLEND}"
"${COMBINED_BLEND}"
"${FINAL_OUTPUT_BLEND}"
)
# Create a custom target to drive the whole pipeline
add_custom_target(${TARGET_BASE}_pipeline ALL
DEPENDS ${FINAL_OUTPUT_BLEND} ${SHAPED_BLEND}
COMMENT "Running complete clothes pipeline for ${TARGET_BASE}"
)
endfunction()
weight_clothes(${CMAKE_CURRENT_SOURCE_DIR}/clothes-male-bottom.blend)
add_clothes_pipeline(
"${CMAKE_CURRENT_SOURCE_DIR}/edited-normal-male.blend" # INPUT_BLEND
"${CMAKE_CURRENT_BINARY_DIR}/clothes/clothes-male-bottom_weighted.blend" # WEIGHTED_BLEND
"${CMAKE_CURRENT_BINARY_DIR}/edited-normal-male-consolidated.blend" # FINAL_OUTPUT_BLEND
)

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,349 @@
import bpy
import bmesh
import sys
import os
import mathutils
from mathutils.bvhtree import BVHTree
def load_blend_files(clothes_blend_path, body_blend_path):
"""Load objects from blend files and return all loaded objects"""
loaded_objects = []
for path in [clothes_blend_path, body_blend_path]:
with bpy.data.libraries.load(path) as (data_from, data_to):
data_to.objects = data_from.objects
for obj in data_to.objects:
if obj:
bpy.context.collection.objects.link(obj)
loaded_objects.append(obj)
return loaded_objects
def setup_bvh_and_matrices(obj):
"""Setup BVH tree and transformation matrices for an object"""
depsgraph = bpy.context.evaluated_depsgraph_get()
obj_eval = obj.evaluated_get(depsgraph)
bvh = BVHTree.FromObject(obj_eval, depsgraph)
return bvh
def get_transformation_matrices(obj):
"""Get transformation matrices for an object"""
m = obj.matrix_world
m_inv = m.inverted()
m_normal = m.to_3x3().inverted().transposed()
return m, m_inv, m_normal
def raycast_and_adjust_vertices(target_body, bvh_cloth):
"""Raycast from body to cloth and adjust vertices that intersect"""
m_body, m_body_inv, m_body_normal = get_transformation_matrices(target_body)
num_verts = len(target_body.data.vertices)
hit_values = [0] * num_verts
has_shape_keys = target_body.data.shape_keys is not None
# Forward raycast (into cloth)
for i, v in enumerate(target_body.data.vertices):
v_world = m_body @ v.co
n_world = (m_body_normal @ v.normal).normalized()
# Raycast forward (into cloth) and backward (from inside cloth)
hit_f, _, _, _ = bvh_cloth.ray_cast(v_world, n_world, 0.015)
hit_b, _, _, _ = bvh_cloth.ray_cast(v_world, -n_world, 0.005)
if hit_f or hit_b:
hit_values[i] = 1
# Adjust vertex position to be slightly outside cloth
offset = -n_world * (0.005 if hit_f else 0.01)
new_co = m_body_inv @ (v_world + offset)
v.co = new_co
# Update shape keys if they exist
if has_shape_keys:
for kb in target_body.data.shape_keys.key_blocks:
kb.data[i].co = new_co
return hit_values
def protect_and_remove_hidden_geometry(target_body, hit_values, threshold=4.0):
"""Protect visible vertices and remove hidden geometry"""
bm = bmesh.new()
bm.from_mesh(target_body.data)
bm.verts.ensure_lookup_table()
# Phase 1: Identify "Layer 1 Border" (Immediate neighbors of visible verts)
border_l1 = set()
for v in bm.verts:
if hit_values[v.index] == 0: # Visible vertex
for edge in v.link_edges:
neighbor = edge.other_vert(v)
if hit_values[neighbor.index] == 1:
border_l1.add(neighbor.index)
# Phase 2: Identify "Layer 2 Buffer" (Neighbors of Layer 1)
border_l2 = set()
for idx in border_l1:
v = bm.verts[idx]
for edge in v.link_edges:
neighbor = edge.other_vert(v)
if hit_values[neighbor.index] == 1:
border_l2.add(neighbor.index)
# Merge all protected vertices
protected_indices = set(border_l1) | set(border_l2)
for i, val in enumerate(hit_values):
if val == 0: # Visible vertices
protected_indices.add(i)
# Deletion logic
to_delete = []
for v in bm.verts:
if v.index in protected_indices:
continue
# Sum hits of neighbors
neighbor_hit_sum = hit_values[v.index]
for edge in v.link_edges:
neighbor = edge.other_vert(v)
neighbor_hit_sum += hit_values[neighbor.index]
if neighbor_hit_sum >= threshold:
to_delete.append(v)
elif len(v.link_edges) == 1: # Loose vertices
to_delete.append(v)
# Perform deletion
bmesh.ops.delete(bm, geom=to_delete, context='VERTS')
bm.to_mesh(target_body.data)
bm.free()
target_body.data.update()
def process_clothing_pair(clothing_obj, target_obj, whitelist, is_clothing_copy=False, original_clothing_name=None):
"""Process a clothing-body pair
Args:
clothing_obj: The clothing object to process
target_obj: The target object to combine with (body or combined object)
whitelist: Set of objects to keep
is_clothing_copy: Whether clothing_obj is a copy (for layer 2 processing)
original_clothing_name: Original name of clothing if it's a copy (for layer 2 naming)
"""
# Create a copy of the target object
new_target = target_obj.copy()
new_target.data = target_obj.data.copy()
bpy.context.collection.objects.link(new_target)
# Copy custom properties
for key in target_obj.keys():
new_target[key] = target_obj[key]
# Ensure the copy has the same transformations
new_target.matrix_world = target_obj.matrix_world.copy()
target_name = target_obj.name
# Determine the name to use for the clothing in the final combined object
if is_clothing_copy and original_clothing_name:
clothing_name_for_final = original_clothing_name
else:
clothing_name_for_final = clothing_obj.name
print(f"Processing: {clothing_name_for_final} -> {target_name} (using copy)")
# Step A: Raycast & adjust vertices
bvh_cloth = setup_bvh_and_matrices(clothing_obj)
hit_values = raycast_and_adjust_vertices(new_target, bvh_cloth)
# Step B: Remove hidden geometry
protect_and_remove_hidden_geometry(new_target, hit_values)
# Step C: Handle armature and join
master_arm = new_target.parent if (new_target.parent and new_target.parent.type == 'ARMATURE') else None
if master_arm:
whitelist.add(master_arm)
# Handle clothing armature (if it's not a copy for layer 2)
if not is_clothing_copy and clothing_obj.parent and clothing_obj.parent.type == 'ARMATURE':
old_arm = clothing_obj.parent
clothing_obj.matrix_world = clothing_obj.matrix_world.copy()
clothing_obj.parent = None
if old_arm not in whitelist:
bpy.data.objects.remove(old_arm, do_unlink=True)
# For layer 2 clothing copies, we don't need to handle armature separately
# as they'll inherit from the target
# Reparent to master armature if exists
if master_arm:
clothing_obj.parent = master_arm
for mod in clothing_obj.modifiers:
if mod.type == 'ARMATURE':
mod.object = master_arm
# Join clothing with target copy
bpy.ops.object.select_all(action='DESELECT')
clothing_obj.select_set(True)
new_target.select_set(True)
bpy.context.view_layer.objects.active = new_target
bpy.ops.object.join()
# Rename the combined object using the appropriate clothing name
new_target.name = f"{target_name}_{clothing_name_for_final}"
whitelist.add(new_target)
return new_target
def cleanup_unused_objects(whitelist):
"""Remove all objects not in whitelist"""
for obj in bpy.data.objects[:]:
if obj.type in {'MESH', 'ARMATURE'} and obj not in whitelist:
bpy.data.objects.remove(obj, do_unlink=True)
bpy.ops.outliner.orphans_purge(do_local_ids=True, do_linked_ids=True, do_recursive=True)
def run_batch_combine():
"""Main function to run the batch combine process"""
try:
args = sys.argv[sys.argv.index("--") + 1:]
body_blend_path, clothes_blend_path, output_path = args[0], args[1], args[2]
except (ValueError, IndexError):
print("Usage: blender -b -P script.py -- <body.blend> <clothes.blend> <output.blend>")
return
# Start fresh
bpy.ops.wm.read_homefile(use_empty=True)
# Load objects from blend files
loaded_objects = load_blend_files(clothes_blend_path, body_blend_path)
# Categorize loaded objects
all_objs = bpy.data.objects
# Separate body objects (no ref_layer property)
body_objects = [o for o in all_objs if o.type == 'MESH' and "ref_layer" not in o]
# Separate clothing by layer
clothing_layer1 = [o for o in all_objs if o.type == 'MESH' and "ref_layer" in o and o["ref_layer"] == 1]
clothing_layer2 = [o for o in all_objs if o.type == 'MESH' and "ref_layer" in o and o["ref_layer"] == 2]
print(f"Found {len(body_objects)} body objects")
print(f"Found {len(clothing_layer1)} layer 1 clothing objects")
print(f"Found {len(clothing_layer2)} layer 2 clothing objects")
# Create dictionary of body objects for quick lookup
body_objects_dict = {obj.name: obj for obj in body_objects}
# Track objects to keep
whitelist = set()
# List to store combined objects from layer 1
combined_objects = []
# PROCESS LAYER 1: Combine with original body parts
print("\n=== PROCESSING LAYER 1 CLOTHING ===")
for clothing_obj in clothing_layer1:
if "ref_part" not in clothing_obj:
print(f"Warning: Layer 1 clothing '{clothing_obj.name}' missing ref_part property, skipping")
continue
target_name = clothing_obj["ref_part"]
original_body = body_objects_dict.get(target_name)
if not original_body or original_body.type != 'MESH':
print(f"Warning: Target body '{target_name}' not found for clothing '{clothing_obj.name}', skipping")
continue
# Process the pair
result = process_clothing_pair(clothing_obj, original_body, whitelist)
if result:
combined_objects.append(result)
print(f"Added '{result.name}' to combined objects list")
print(f"Layer 1 complete. Created {len(combined_objects)} combined objects")
# PROCESS LAYER 2 (First Pass): Combine with layer 1 results
print("\n=== PROCESSING LAYER 2 CLOTHING (First Pass - Over Layer 1) ===")
layer2_results_over_l1 = []
for clothing_obj in clothing_layer2:
print(f"\nProcessing layer 2 clothing: {clothing_obj.name}")
# Store the original clothing name for later use
original_clothing_name = clothing_obj.name
# For each combined object from layer 1
for combined_obj in combined_objects:
# Create a copy of the layer 2 clothing
clothing_copy = clothing_obj.copy()
clothing_copy.data = clothing_obj.data.copy()
bpy.context.collection.objects.link(clothing_copy)
# Copy custom properties
for key in clothing_obj.keys():
clothing_copy[key] = clothing_obj[key]
# Set the ref_part to point to the combined object
clothing_copy["ref_part"] = combined_obj.name
print(f" Combining with layer 1 result: {combined_obj.name}")
# Process the pair
result = process_clothing_pair(clothing_copy, combined_obj, whitelist,
is_clothing_copy=True,
original_clothing_name=original_clothing_name)
if result:
layer2_results_over_l1.append(result)
print(f" Created: {result.name}")
print(f"Layer 2 first pass complete. Created {len(layer2_results_over_l1)} combined objects")
# PROCESS LAYER 2 (Second Pass): Combine directly with body parts (like layer 1)
print("\n=== PROCESSING LAYER 2 CLOTHING (Second Pass - Direct to Body) ===")
layer2_results_direct = []
for clothing_obj in clothing_layer2:
if "ref_part" not in clothing_obj:
print(f"Warning: Layer 2 clothing '{clothing_obj.name}' missing ref_part property, skipping")
continue
target_name = clothing_obj["ref_part"]
original_body = body_objects_dict.get(target_name)
if not original_body or original_body.type != 'MESH':
print(f"Warning: Target body '{target_name}' not found for clothing '{clothing_obj.name}', skipping")
continue
print(f"\nProcessing layer 2 clothing directly with body: {clothing_obj.name} -> {target_name}")
# Process directly with body part (no copy needed for the clothing itself)
result = process_clothing_pair(clothing_obj, original_body, whitelist, is_clothing_copy=False)
if result:
layer2_results_direct.append(result)
print(f" Created: {result.name}")
print(f"Layer 2 second pass complete. Created {len(layer2_results_direct)} combined objects")
# Add all results to combined objects list
all_results = combined_objects + layer2_results_over_l1 + layer2_results_direct
print(f"\n=== SUMMARY ===")
print(f"Layer 1 results: {len(combined_objects)}")
print(f"Layer 2 over layer 1 results: {len(layer2_results_over_l1)}")
print(f"Layer 2 direct to body results: {len(layer2_results_direct)}")
print(f"Total combined objects: {len(all_results)}")
# Final cleanup - keep all combined objects and their armatures
for obj in all_results:
whitelist.add(obj)
cleanup_unused_objects(whitelist)
# Save the result
bpy.ops.wm.save_as_mainfile(filepath=output_path)
print(f"\nSaved to: {output_path}")
if __name__ == "__main__":
run_batch_combine()

View File

@@ -0,0 +1,71 @@
import bpy
import sys
import os
def process_append(source_files, output_path):
required_props = {"age", "sex", "slot"}
for file_path in source_files:
if not os.path.exists(file_path):
continue
with bpy.data.libraries.load(file_path) as (data_from, data_to):
data_to.objects = data_from.objects
for obj in data_to.objects:
if obj is None: continue
# Check criteria
has_props = all(p in obj.keys() for p in required_props)
if obj.type == 'MESH' and has_props:
# 1. Link to the scene root temporarily
bpy.context.collection.objects.link(obj)
# 2. Synchronize Names
obj.data.name = obj.name
# 3. Find Target Armature
arm_name = obj.get("sex")
arm_obj = bpy.data.objects.get(arm_name)
if arm_obj and arm_obj.type == 'ARMATURE':
# A. Handle Collections: Move mesh to armature's collections
# Remove from all current collections first
for col in obj.users_collection:
col.objects.unlink(obj)
# Link to every collection the armature belongs to
for col in arm_obj.users_collection:
col.objects.link(obj)
# B. Parent to Armature
obj.parent = arm_obj
# C. Handle Armature Modifier
arm_mod = next((m for m in obj.modifiers if m.type == 'ARMATURE'), None)
if not arm_mod:
arm_mod = obj.modifiers.new(name="Armature", type='ARMATURE')
arm_mod.object = arm_obj
print(f"Processed {obj.name}: Parented and Modset to {arm_name}")
else:
print(f"Warning: Armature '{arm_name}' not found for {obj.name}")
else:
# Clean up data not meeting criteria
bpy.data.objects.remove(obj, do_unlink=True)
# 4. Recursive Purge of all unlinked data (Materials, Textures, Meshes)
bpy.ops.outliner.orphans_purge(do_local_ids=True, do_linked_ids=True, do_recursive=True)
# Save
bpy.ops.wm.save_as_mainfile(filepath=output_path)
if __name__ == "__main__":
try:
args = sys.argv[sys.argv.index("--") + 1:]
if len(args) >= 2:
*sources, output = args
process_append(sources, output)
except ValueError:
print("Error: Use '--' to separate Blender args from script args.")

View File

@@ -0,0 +1,64 @@
import bpy
import sys
import os
argv = sys.argv
argv = argv[argv.index("--") + 1:]
sys.path.insert(0, os.getcwd() + "/assets/blender/scripts")
source_filepath = argv[0]
armature_name = argv[1]
print("starting...")
def copy_actions_and_create_nla_tracks(source_filepath, target_object_name):
"""
Copies actions from a source Blender file that do not exist in the current
file, creates NLA tracks for them on a target object, and saves the file.
Args:
source_filepath (str): The full path to the source Blender file.
target_object_name (str): The name of the object in the current file
to which the actions and NLA tracks will be applied.
"""
# 1. Link or Append Actions from the Source File
with bpy.data.libraries.load(source_filepath, link=False) as (data_from, data_to):
source_action_names = data_from.actions
current_action_names = {action.name for action in bpy.data.actions}
actions_to_append = [name for name in source_action_names if name not in current_action_names]
data_to.actions = actions_to_append
print(actions_to_append)
# Get the target object
target_object = bpy.data.objects.get(target_object_name)
if not target_object:
print(f"Error: Object '{target_object_name}' not found in the current file.")
return
# Ensure the object has an NLA editor
if not target_object.animation_data:
target_object.animation_data_create()
# 2. Iterate through newly imported actions and create NLA tracks
for action in data_to.actions:
# Check if an action with the same name already exists in the current file
# Add the action to the NLA editor as a strip
nla_track = target_object.animation_data.nla_tracks.new()
nla_track.name = f"NLA_Track_{action.name}"
nla_track.strips.new(name=action.name, start=1, action=action)
print(f"Created NLA track for action: {action.name}")
# 3. Save the current Blender file
bpy.ops.wm.save_as_mainfile(filepath=bpy.context.blend_data.filepath)
print(f"File saved: {bpy.context.blend_data.filepath}")
# --- Usage Example ---
if __name__ == "__main__":
# Replace with your actual source file path and target object name
source_file = source_filepath
target_obj = armature_name
copy_actions_and_create_nla_tracks(source_file, target_obj)

View File

@@ -0,0 +1,78 @@
#!/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:]
incpath = os.path.dirname(__file__)
sys.path.insert(0, incpath)
sys.path.insert(1, incpath + "/blender2ogre")
gltf_file = argv[0]
target_name = argv[1]
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='DESELECT')
print("Target: " + target_name)
for o in bpy.data.objects:
print(o.name)
obj = bpy.data.objects.get(target_name)
obj.select_set(True)
armature = obj.parent if (obj.parent and obj.parent.type == 'ARMATURE') else None
if not armature:
for mod in obj.modifiers:
if mod.type == 'ARMATURE' and mod.object:
armature = mod.object
break
if armature:
armature.select_set(True)
bpy.context.view_layer.objects.active = armature
else:
raise Exception("bad armature")
print("Exporting to " + gltf_file)
basepath = incpath
bpy.ops.export_scene.gltf(filepath=gltf_file,
use_selection=True,
check_existing=False,
export_format='GLB',
export_texture_dir='textures', export_texcoords=True,
export_animation_mode='NLA_TRACKS',
export_normals=True,
export_tangents=True,
export_materials='EXPORT',
export_colors=True,
use_mesh_edges=False,
use_mesh_vertices=False,
export_cameras=False,
use_visible=False,
use_renderable=False,
export_yup=True,
export_apply=True,
export_animations=True,
export_force_sampling=True,
export_def_bones=False,
export_current_frame=False,
export_morph=True,
export_morph_animation=False,
export_morph_normal=True,
export_morph_tangent=True,
export_lights=False,
export_skins=True)
bpy.ops.wm.read_homefile(use_empty=True)
time.sleep(2)
bpy.ops.wm.quit_blender()

View File

@@ -0,0 +1,53 @@
import bpy
def transfer_body_data():
source_name = "Body"
target_names = ["BodyTop", "bodyBottom", "BodyFeet"]
source_obj = bpy.data.objects.get(source_name)
if not source_obj:
print(f"Source object '{source_name}' not found!")
return
for name in target_names:
target_obj = bpy.data.objects.get(name)
if not target_obj:
print(f"Target '{name}' not found, skipping...")
continue
# 1. FIX NORMALS & WEIGHTS (Data Transfer)
# Required for Blender 4.1+: Enable Auto Smooth (Smooth by Angle)
target_obj.data.use_auto_smooth = True
dt_mod = target_obj.modifiers.new(name="TR_DATA", type='DATA_TRANSFER')
dt_mod.object = source_obj
# Transfer Vertex Groups (Weights)
dt_mod.use_vert_data = True
dt_mod.data_types_verts = {'VGROUP_WEIGHTS'}
dt_mod.vert_mapping = 'NEAREST'
# Transfer Normals (Fixes Seams)
dt_mod.use_loop_data = True
dt_mod.data_types_loops = {'CUSTOM_NORMAL'}
dt_mod.loop_mapping = 'NEAREST_POLYNOR'
# Apply to bake the weights and normals
bpy.context.view_layer.objects.active = target_obj
bpy.ops.object.modifier_apply(modifier=dt_mod.name)
# 2. TRANSFER SHAPE KEYS
if source_obj.data.shape_keys:
# Deselect all, then select source then target
bpy.ops.object.select_all(action='DESELECT')
source_obj.select_set(True)
target_obj.select_set(True)
bpy.context.view_layer.objects.active = target_obj
# Joins shape keys from source to target
bpy.ops.object.shape_key_transfer()
print("Transfer complete: Normals, Weights, and Shape Keys synced.")
transfer_body_data()

View File

@@ -0,0 +1,53 @@
import bpy
def fix_seams_and_transfer_data():
source_name = "Body"
target_names = ["BodyTop", "bodyBottom", "BodyFeet"]
source_obj = bpy.data.objects.get(source_name)
if not source_obj:
print(f"Error: '{source_name}' not found.")
return
for t_name in target_names:
target_obj = bpy.data.objects.get(t_name)
if not target_obj:
continue
# 1. PREP TARGET
# In 3.6, Auto Smooth must be True to see custom normals
target_obj.data.use_auto_smooth = True
bpy.context.view_layer.objects.active = target_obj
# 2. TRANSFER WEIGHTS & NORMALS (Data Transfer Mod)
dt_mod = target_obj.modifiers.new(name="SeamFix", type='DATA_TRANSFER')
dt_mod.object = source_obj
# Vertex Data (Weights)
dt_mod.use_vert_data = True
dt_mod.data_types_verts = {'VGROUP_WEIGHTS'}
# Face Corner Data (Normals)
dt_mod.use_loop_data = True
dt_mod.data_types_loops = {'CUSTOM_NORMAL'}
dt_mod.loop_mapping = 'NEAREST_POLYNOR'
# Apply the modifier to bake the data
bpy.ops.object.modifier_apply(modifier=dt_mod.name)
# 3. TRANSFER SHAPE KEYS
if source_obj.data.shape_keys:
# Clear selection and set up: Source must be Active, Target Selected
bpy.ops.object.select_all(action='DESELECT')
target_obj.select_set(True)
source_obj.select_set(True)
bpy.context.view_layer.objects.active = source_obj
# Transfer shape keys based on vertex position
# Note: This creates keys on the Target object
bpy.ops.object.shape_key_transfer()
print("Process complete for Blender 3.6.")
fix_seams_and_transfer_data()

View File

@@ -0,0 +1,54 @@
import bpy
def optimize_mesh_for_ogre(obj):
if obj.type != 'MESH':
return
print(obj.type)
print(f"Starting with {obj.name}")
# Set as active and enter Weight Paint mode to use ops
bpy.context.view_layer.objects.active = obj
bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
# 1. Clean zero weights (below 0.001)
bpy.ops.object.vertex_group_clean(group_select_mode='ALL', limit=0.001)
# 2. Limit influences to 4 per vertex (Ogre/GPU standard)
bpy.ops.object.vertex_group_limit_total(limit=4)
# 3. Normalize all weights (Sum of all bone influences = 1.0)
# This prevents "multiplied" or "weak" movements in engine
bpy.ops.object.vertex_group_normalize_all()
bpy.ops.object.mode_set(mode='OBJECT')
# 4. Remove empty Vertex Groups (groups with no assigned vertices)
# This keeps the bone index list identical across modular parts
vgroup_indices_to_remove = []
for i, group in enumerate(obj.vertex_groups):
has_vertices = False
for v in obj.data.vertices:
for g in v.groups:
if g.group == i and g.weight > 0.001:
has_vertices = True
break
if has_vertices: break
if not has_vertices:
print("removed group:" + group.name)
vgroup_indices_to_remove.append(group.name)
for name in vgroup_indices_to_remove:
print("removing group: " + name)
obj.vertex_groups.remove(obj.vertex_groups.get(name))
print(f"Finished {obj.name}: 4-weight limit applied, Normalized, {len(vgroup_indices_to_remove)} empty groups removed.")
# Execute on all selected mesh objects
selected_meshes = [o for o in bpy.data.objects if o.type == 'MESH' and not o.name.startswith("cs_")]
if not selected_meshes:
print("No mesh objects selected.")
else:
for mesh_obj in selected_meshes:
optimize_mesh_for_ogre(mesh_obj)

View File

@@ -0,0 +1,146 @@
import bpy
import os
import sys
def clean_scene():
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete()
bpy.ops.outliner.orphans_purge(do_local_ids=True, do_linked_ids=True, do_recursive=True)
def remove_empty_vertex_groups(obj, threshold=0.001):
"""Removes vertex groups with no weights or weights below threshold."""
if obj.type != 'MESH':
return
# Ensure we are in object mode
bpy.context.view_layer.objects.active = obj
# Dictionary to track max weight per group
group_max_weights = {g.index: 0.0 for g in obj.vertex_groups}
# Iterate over vertices to find actual max weights
for v in obj.data.vertices:
for g in v.groups:
if g.group in group_max_weights:
group_max_weights[g.group] = max(group_max_weights[g.group], g.weight)
# Remove groups that don't meet the threshold
groups_to_remove = [obj.vertex_groups[idx] for idx, max_w in group_max_weights.items() if max_w < threshold]
for g in groups_to_remove:
obj.vertex_groups.remove(g)
print(f"Cleaned {len(groups_to_remove)} empty/low-weight groups from {obj.name}")
def process_batch():
try:
args = sys.argv[sys.argv.index("--") + 1:]
clothing_path, lib_directory, out_dir = args[0], args[1], args[2]
except (IndexError, ValueError):
print("Usage: blender -b -P script.py -- <source_blend> <lib_dir> <out_dir>")
return
if not os.path.exists(out_dir): os.makedirs(out_dir)
# 1. Identify all clothing in the source file
with bpy.data.libraries.load(clothing_path) as (data_from, data_to):
all_clothing_names = data_from.objects
# Start with a fresh scene
clean_scene()
for name in all_clothing_names:
# 2. Append the clothing item
with bpy.data.libraries.load(clothing_path) as (data_from, data_to):
data_to.objects = [name]
for obj in data_to.objects:
if obj: bpy.context.collection.objects.link(obj)
clothing = bpy.data.objects.get(name)
if not clothing or clothing.type != 'MESH': continue
# Get properties
sex = clothing.get("ref_sex")
age = clothing.get("ref_age")
ref_mesh_name = clothing.get("ref_clothing")
if not all([sex, age, ref_mesh_name]):
print(f"Skipping {name}: Missing properties")
continue
# 3. Locate Reference Library
target_lib_name = f"normal_{age}_{sex}.blend"
target_lib_path = os.path.join(lib_directory, target_lib_name)
rig_name = str(sex)
if not os.path.exists(target_lib_path):
if target_lib_name == "normal_adult_male.blend":
target_lib_name = "edited-normal-male.blend"
elif target_lib_name == "normal_adult_female.blend":
target_lib_name = "edited-normal-female.blend"
target_lib_path = os.path.join(lib_directory, target_lib_name)
if not os.path.exists(target_lib_path):
print(f"Error: Library {target_lib_path} not found")
continue
# 4. Append Weights Source and Rig
with bpy.data.libraries.load(target_lib_path) as (data_from, data_to):
data_to.objects = [ref_mesh_name, rig_name]
for obj in data_to.objects:
if obj: bpy.context.collection.objects.link(obj)
source_mesh = bpy.data.objects.get(ref_mesh_name)
rig = bpy.data.objects.get(rig_name)
# 5. Prep Objects (Apply Scale & Clear Animation)
bpy.context.view_layer.objects.active = clothing
bpy.ops.object.transform_apply(location=False, rotation=True, scale=True)
for item in [clothing, rig]:
if item.animation_data: item.animation_data_clear()
if item.type == 'ARMATURE':
bpy.context.view_layer.objects.active = item
bpy.ops.object.mode_set(mode='POSE')
bpy.ops.pose.transforms_clear()
bpy.ops.object.mode_set(mode='OBJECT')
# 6. Weight Transfer
clothing.vertex_groups.clear()
dt = clothing.modifiers.new(name="WT", type='DATA_TRANSFER')
dt.object = source_mesh
dt.use_vert_data = True
dt.data_types_verts = {'VGROUP_WEIGHTS'}
dt.layers_vgroup_select_src = 'ALL'
dt.vert_mapping = 'POLYINTERP_NEAREST'
bpy.context.view_layer.objects.active = clothing
# "Generate Data Layers" step
bpy.ops.object.datalayout_transfer(modifier=dt.name)
bpy.ops.object.modifier_apply(modifier=dt.name)
remove_empty_vertex_groups(clothing, threshold=0.001)
# 7. Final Parenting
clothing.parent = rig
arm_mod = clothing.modifiers.new(name="Armature", type='ARMATURE')
arm_mod.object = rig
# 8. Cleanup Reference Mesh (keep the Rig!)
bpy.data.objects.remove(source_mesh, do_unlink=True)
print(f"Processed: {name}")
# 9. Save as single file named after source + _weighted
source_filename = os.path.splitext(os.path.basename(clothing_path))[0]
final_save_path = os.path.join(out_dir, f"{source_filename}_weighted.blend")
# Purge any remaining junk before final save
bpy.ops.outliner.orphans_purge(do_local_ids=True, do_linked_ids=True, do_recursive=True)
bpy.ops.wm.save_as_mainfile(filepath=final_save_path)
print(f"\n--- ALL DONE ---")
print(f"Saved to: {final_save_path}")
if __name__ == "__main__":
process_batch()

View File

@@ -0,0 +1,970 @@
"""
Blender 3.6.20 Script: Transfer Shape Keys with Boundary Velocity Limiting
Usage: blender -b --python transfer_shape_keys.py -- <source_file> <target_file> <output_file>
"""
import bpy
import sys
import os
import shutil
import mathutils
from mathutils.bvhtree import BVHTree
import numpy as np
from collections import defaultdict
def parse_args():
"""Parse command line arguments after '--'"""
if '--' in sys.argv:
args_start = sys.argv.index('--') + 1
args = sys.argv[args_start:]
if len(args) >= 3:
return args[0], args[1], args[2]
else:
print("Error: Please provide source, target, and output .blend files")
print("Usage: blender -b --python transfer_shape_keys.py -- <source_file> <target_file> <output_file>")
sys.exit(1)
else:
print("Error: No arguments provided")
print("Usage: blender -b --python transfer_shape_keys.py -- <source_file> <target_file> <output_file>")
sys.exit(1)
def load_source_data(source_path):
"""Load source file and extract shape key information"""
print(f"\nLoading source file: {source_path}")
if not os.path.exists(source_path):
print(f"Error: Source file not found: {source_path}")
sys.exit(1)
current_file = bpy.data.filepath if bpy.data.filepath else None
bpy.ops.wm.open_mainfile(filepath=source_path)
body_object = None
for obj in bpy.data.objects:
if obj.name == "Body_shapes" and obj.type == 'MESH':
body_object = obj
break
if not body_object:
for obj in bpy.data.objects:
if obj.name == "Body" and obj.type == 'MESH':
body_object = obj
break
if not body_object:
print("Error: Could not find mesh object named 'Body' in source file")
sys.exit(1)
if not body_object.data.shape_keys:
print("Error: Body object has no shape keys")
sys.exit(1)
shape_key_data = {
'names': [],
'vertex_positions': {},
'polygons': [],
'is_relative': body_object.data.shape_keys.use_relative
}
# Store polygon data for BVH
mesh = body_object.data
for poly in mesh.polygons:
shape_key_data['polygons'].append([v for v in poly.vertices])
source_shape_keys = body_object.data.shape_keys.key_blocks
print(f"Found Body object with {len(source_shape_keys)} shape keys")
for sk in source_shape_keys:
shape_key_data['names'].append(sk.name)
print(f" - {sk.name}")
vertex_positions = []
for v in sk.data:
vertex_positions.append((v.co.x, v.co.y, v.co.z))
shape_key_data['vertex_positions'][sk.name] = vertex_positions
if current_file and os.path.exists(current_file):
bpy.ops.wm.open_mainfile(filepath=current_file)
elif current_file:
bpy.ops.wm.read_homefile()
return shape_key_data
def load_target_file(target_path):
"""Load the target .blend file and find objects with custom properties"""
print(f"\nLoading target file: {target_path}")
if not os.path.exists(target_path):
print(f"Error: Target file not found: {target_path}")
sys.exit(1)
temp_dir = os.path.join(os.path.dirname(target_path), "temp_blend_files")
os.makedirs(temp_dir, exist_ok=True)
temp_target = os.path.join(temp_dir, os.path.basename(target_path))
shutil.copy2(target_path, temp_target)
bpy.ops.wm.open_mainfile(filepath=temp_target)
target_objects = []
for obj in bpy.data.objects:
if obj.type == 'MESH' and obj.data:
if all(prop in obj for prop in ['age', 'sex', 'slot']):
target_objects.append(obj)
print(f"Found target object: {obj.name}")
print(f" - age: {obj['age']}")
print(f" - sex: {obj['sex']}")
print(f" - slot: {obj['slot']}")
print(f" - vertices: {len(obj.data.vertices)}")
return target_objects, temp_target
def delete_existing_shape_keys(target_obj):
"""Delete all existing shape keys from target object"""
if not target_obj.data.shape_keys:
return False
num_keys = len(target_obj.data.shape_keys.key_blocks)
print(f" Deleting {num_keys} existing shape keys...")
bpy.context.view_layer.objects.active = target_obj
target_obj.select_set(True)
bpy.ops.object.mode_set(mode='OBJECT')
while target_obj.data.shape_keys:
target_obj.active_shape_key_index = 0
bpy.ops.object.shape_key_remove()
target_obj.select_set(False)
return True
def ensure_shape_keys_structure(shape_key_names, target_obj):
"""Create shape key structure on target object"""
delete_existing_shape_keys(target_obj)
bpy.context.view_layer.objects.active = target_obj
target_obj.select_set(True)
# Create Basis with current mesh positions
target_obj.shape_key_add(name='Basis', from_mix=True)
print(f" Created Basis shape key")
# Create other shape keys
for sk_name in shape_key_names:
if sk_name != 'Basis':
target_obj.shape_key_add(name=sk_name, from_mix=False)
print(f" Created shape key: {sk_name}")
target_obj.data.shape_keys.use_relative = True
target_obj.select_set(False)
def create_bvh_for_source(source_data, shape_key_name='Basis'):
"""Create BVH tree for source mesh in specified pose"""
positions = source_data['vertex_positions'][shape_key_name]
verts = [mathutils.Vector(v) for v in positions]
# Create BVH tree
bvh = BVHTree.FromPolygons(verts, source_data['polygons'], all_triangles=False)
return bvh, verts
def compute_signed_distance_and_direction(bvh, point, reference_normal=None):
"""Compute signed distance and direction to surface"""
# Find closest point on surface
location, normal, index, distance = bvh.find_nearest(point)
if location is None:
return None, None, None, None
# Determine sign using reference normal
if reference_normal is not None:
to_surface = location - point
if to_surface.length > 0:
dot = to_surface.normalized().dot(reference_normal)
signed_distance = distance if dot > 0 else -distance
else:
signed_distance = 0
else:
to_surface = point - location
if to_surface.length > 0 and normal.length > 0:
dot = to_surface.normalized().dot(normal)
signed_distance = distance if dot > 0 else -distance
else:
signed_distance = distance
return location, normal, index, signed_distance
def detect_boundary_vertices(target_obj, mapping, threshold=0.3):
"""Detect vertices that lie on the boundary between inner and outer surfaces"""
print(f" Detecting boundary vertices...")
num_verts = len(mapping['target_verts'])
adjacency = mapping['adjacency']
boundary_vertices = set()
# For each vertex, check if its neighbors have different side flags
for i in range(num_verts):
if i >= len(mapping['side_flags']):
continue
side_i = mapping['side_flags'][i]
if side_i == 0: # On surface, definitely boundary
boundary_vertices.add(i)
continue
neighbors = adjacency[i]
opposite_side_count = 0
total_neighbors = 0
for n in neighbors:
if n < len(mapping['side_flags']):
side_n = mapping['side_flags'][n]
if side_n != 0 and side_n != side_i:
opposite_side_count += 1
total_neighbors += 1
# If significant portion of neighbors are on opposite side, this is a boundary
if total_neighbors > 0 and opposite_side_count / total_neighbors > threshold:
boundary_vertices.add(i)
# Also check vertices that are close to the surface (small signed distance)
for i in range(num_verts):
if i < len(mapping['signed_distances']):
if abs(mapping['signed_distances'][i]) < 0.05: # Very close to surface
boundary_vertices.add(i)
print(f" Found {len(boundary_vertices)} boundary vertices")
return boundary_vertices
def compute_robust_surface_mapping(target_obj, source_data):
"""Create robust surface mapping with signed distances"""
print(f" Computing robust surface mapping...")
# Get target vertices and normals
target_verts = [v.co.copy() for v in target_obj.data.vertices]
target_normals = [v.normal.copy() for v in target_obj.data.vertices]
num_verts = len(target_verts)
# Create BVH for source basis
bvh_basis, basis_verts = create_bvh_for_source(source_data, 'Basis')
# Build adjacency for later smoothing
adjacency = defaultdict(set)
mesh = target_obj.data
for edge in mesh.edges:
v1, v2 = edge.vertices
adjacency[v1].add(v2)
adjacency[v2].add(v1)
mapping = {
'target_verts': target_verts,
'source_indices': [],
'surface_points': [],
'signed_distances': [],
'direction_vectors': [],
'face_indices': [],
'barycentric_coords': [],
'side_flags': [],
'confidence': [],
'adjacency': adjacency,
'deformation_magnitude': [0.0] * num_verts,
'boundary_vertices': set()
}
# First pass: find closest points and determine side
for i, (target_pos, target_normal) in enumerate(zip(target_verts, target_normals)):
location, normal, index, signed_dist = compute_signed_distance_and_direction(
bvh_basis, target_pos, target_normal
)
if location is None:
# Fallback to nearest vertex
if i % 100 == 0:
print(f" Warning: Vertex {i} using nearest vertex fallback")
min_dist = float('inf')
nearest_idx = 0
for j, src_pos in enumerate(basis_verts):
dist = (target_pos - src_pos).length
if dist < min_dist:
min_dist = dist
nearest_idx = j
mapping['source_indices'].append([nearest_idx])
mapping['surface_points'].append(basis_verts[nearest_idx])
mapping['signed_distances'].append(min_dist)
mapping['direction_vectors'].append(target_pos - basis_verts[nearest_idx])
mapping['face_indices'].append(-1)
mapping['barycentric_coords'].append([1.0])
mapping['side_flags'].append(1)
mapping['confidence'].append(0.5)
else:
mapping['surface_points'].append(location)
mapping['signed_distances'].append(signed_dist)
abs_dist = abs(signed_dist)
if abs_dist < 0.001:
side_flag = 0
confidence = 1.0
else:
ray_hits = 0
for ray_dir in [target_normal, -target_normal, mathutils.Vector((1,0,0)), mathutils.Vector((-1,0,0))]:
hit, _, _, _ = bvh_basis.ray_cast(target_pos + ray_dir * 0.1, -ray_dir)
if hit is not None:
ray_hits += 1
side_flag = 1 if signed_dist > 0 else -1
confidence = min(1.0, ray_hits / 4.0 + 0.5)
mapping['side_flags'].append(side_flag)
mapping['confidence'].append(confidence)
if index is not None and index < len(source_data['polygons']):
face = source_data['polygons'][index]
mapping['face_indices'].append(index)
if len(face) == 3:
face_verts = [basis_verts[idx] for idx in face]
try:
coords = mathutils.geometry.barycentric_transform(
location, face_verts[0], face_verts[1], face_verts[2]
)
mapping['barycentric_coords'].append(coords)
min_dist = float('inf')
nearest_idx = face[0]
for idx in face:
dist = (location - basis_verts[idx]).length
if dist < min_dist:
min_dist = dist
nearest_idx = idx
mapping['source_indices'].append([nearest_idx])
except:
min_dist = float('inf')
nearest_idx = face[0]
for idx in face:
dist = (location - basis_verts[idx]).length
if dist < min_dist:
min_dist = dist
nearest_idx = idx
mapping['source_indices'].append([nearest_idx])
mapping['barycentric_coords'].append([1.0])
else:
min_dist = float('inf')
nearest_idx = face[0]
for idx in face:
dist = (location - basis_verts[idx]).length
if dist < min_dist:
min_dist = dist
nearest_idx = idx
mapping['source_indices'].append([nearest_idx])
mapping['barycentric_coords'].append([1.0])
else:
mapping['face_indices'].append(-1)
mapping['source_indices'].append([0])
mapping['barycentric_coords'].append([1.0])
direction = target_pos - location
mapping['direction_vectors'].append(direction)
# Detect boundary vertices
mapping['boundary_vertices'] = detect_boundary_vertices(target_obj, mapping)
print(f" Mapping complete. Side distribution: "
f"Outside: {mapping['side_flags'].count(1)}, "
f"Inside: {mapping['side_flags'].count(-1)}, "
f"On surface: {mapping['side_flags'].count(0)}")
return mapping
def interpolate_source_position_safe(source_data, sk_name, surface_point, face_idx, bary_coords, source_indices):
"""Interpolate source position with side preservation"""
if sk_name == 'Basis':
return surface_point
source_positions = source_data['vertex_positions'][sk_name]
if face_idx >= 0 and face_idx < len(source_data['polygons']):
face = source_data['polygons'][face_idx]
if len(face) == 3 and len(bary_coords) == 3:
pos = mathutils.Vector((0, 0, 0))
for j, vert_idx in enumerate(face):
if j < len(bary_coords) and vert_idx < len(source_positions):
pos += bary_coords[j] * mathutils.Vector(source_positions[vert_idx])
return pos
if source_indices and len(source_indices) > 0:
idx = source_indices[0]
if idx < len(source_positions):
return mathutils.Vector(source_positions[idx])
return surface_point
def compute_deformation_magnitudes(source_data, mapping):
"""Compute how much each vertex deforms in the source"""
print(f" Computing deformation magnitudes...")
basis_positions = [mathutils.Vector(v) for v in source_data['vertex_positions']['Basis']]
num_verts = len(mapping['target_verts'])
magnitudes = [0.0] * num_verts
for sk_name in source_data['names']:
if sk_name == 'Basis':
continue
sk_positions = [mathutils.Vector(v) for v in source_data['vertex_positions'][sk_name]]
for i in range(num_verts):
if i < len(mapping['source_indices']):
src_indices = mapping['source_indices'][i]
if src_indices and len(src_indices) > 0:
src_idx = src_indices[0]
if src_idx < len(basis_positions) and src_idx < len(sk_positions):
deformation = (sk_positions[src_idx] - basis_positions[src_idx]).length
magnitudes[i] = max(magnitudes[i], deformation)
max_mag = max(magnitudes) if magnitudes else 1.0
if max_mag > 0:
magnitudes = [m / max_mag for m in magnitudes]
high_count = sum(1 for m in magnitudes if m > 0.7)
print(f" High deformation vertices (>0.7): {high_count}/{num_verts}")
return magnitudes
def enforce_side_constraint(pos, surface_point, reference_normal, target_side, confidence, is_boundary=False):
"""Enforce that vertex stays on correct side of surface"""
to_surface = surface_point - pos
if to_surface.length < 0.001:
return pos
current_side = 1 if to_surface.dot(reference_normal) < 0 else -1
# For boundary vertices, use very gentle enforcement
if is_boundary:
if current_side != target_side and target_side != 0:
# Just pull back slightly toward surface
proj_factor = to_surface.dot(reference_normal) / reference_normal.length_squared
proj_point = pos + reference_normal * proj_factor
# Very small offset to stay near surface
offset = abs((surface_point - pos).length) * 0.2
if target_side > 0:
return proj_point - reference_normal * offset
else:
return proj_point + reference_normal * offset
return pos
# Regular vertices - stronger enforcement
if current_side != target_side and target_side != 0:
proj_factor = to_surface.dot(reference_normal) / reference_normal.length_squared
proj_point = pos + reference_normal * proj_factor
offset = abs((surface_point - pos).length) * 0.95
if target_side > 0:
corrected_pos = proj_point - reference_normal * offset
else:
corrected_pos = proj_point + reference_normal * offset
return corrected_pos
return pos
def limit_boundary_velocity(target_idx, target_pos, deformed_surface, t, mapping):
"""Limit how fast boundary vertices can move"""
if target_idx not in mapping['boundary_vertices']:
return deformed_surface
# Get the full deformation target
full_target = deformed_surface
# Calculate how far this vertex should move at this t value
# Use a slower progression for boundary vertices
safe_t = t * 0.7 # Boundary vertices only move 70% as fast
# Interpolate between start and target with limited speed
limited_pos = (1 - safe_t) * target_pos + safe_t * full_target
return limited_pos
def adaptive_damping(pos, target_pos, deformed_surface, magnitude, t, threshold=0.55, is_boundary=False):
"""Apply adaptive damping based on deformation magnitude and t value"""
if t <= threshold:
return pos
# For boundary vertices, use much less damping to avoid pinching
if is_boundary:
return pos
excess = (t - threshold) / (1.0 - threshold)
damping = min(1.0, magnitude * 0.8 + 0.2)
damped_weight = excess * damping * 0.5
safe_pos = (target_pos + deformed_surface) * 0.5
damped_pos = (1 - damped_weight) * pos + damped_weight * safe_pos
return damped_pos
def compute_side_preserving_position(target_idx, sk_name, mapping, source_data, t):
"""Compute position that preserves side and signed distance"""
if target_idx >= len(mapping['target_verts']):
return mathutils.Vector((0, 0, 0))
target_pos = mapping['target_verts'][target_idx]
if target_idx >= len(mapping['surface_points']):
return target_pos
# Check if this is a boundary vertex
is_boundary = target_idx in mapping.get('boundary_vertices', set())
surface_point = mapping['surface_points'][target_idx]
signed_dist = mapping['signed_distances'][target_idx] if target_idx < len(mapping['signed_distances']) else 0
side_flag = mapping['side_flags'][target_idx] if target_idx < len(mapping['side_flags']) else 1
confidence = mapping['confidence'][target_idx] if target_idx < len(mapping['confidence']) else 0.5
face_idx = mapping['face_indices'][target_idx] if target_idx < len(mapping['face_indices']) else -1
bary_coords = mapping['barycentric_coords'][target_idx] if target_idx < len(mapping['barycentric_coords']) else [1.0]
source_indices = mapping['source_indices'][target_idx] if target_idx < len(mapping['source_indices']) else [0]
deformed_surface = interpolate_source_position_safe(
source_data, sk_name, surface_point, face_idx, bary_coords, source_indices
)
# For boundary vertices, limit their velocity
if is_boundary:
deformed_surface = limit_boundary_velocity(target_idx, target_pos, deformed_surface, t, mapping)
basis_surface = surface_point
current_surface = (1 - t) * basis_surface + t * deformed_surface
if target_idx < len(mapping['direction_vectors']):
reference_normal = mapping['direction_vectors'][target_idx].normalized()
else:
reference_normal = mathutils.Vector((0, 0, 1))
if reference_normal.length < 0.1:
reference_normal = mathutils.Vector((0, 0, 1))
abs_dist = abs(signed_dist)
# For boundary vertices, use a much smaller offset to keep them near the surface
if is_boundary:
# Boundary vertices should stay very close to the surface
# Use a fraction of their original distance
abs_dist = abs_dist * 0.2 # Only 20% of original offset
else:
# Regular vertices use full offset but with confidence weighting
abs_dist = abs_dist * confidence
if side_flag > 0:
base_pos = current_surface + reference_normal * abs_dist
elif side_flag < 0:
base_pos = current_surface - reference_normal * abs_dist
else:
base_pos = current_surface
direct_mapped = (1 - t) * target_pos + t * deformed_surface
# For boundary vertices, blend more heavily with direct mapping
if is_boundary:
blend_weight = 0.1 # Only 10% side-preserving for boundaries
else:
blend_weight = confidence * (1 - t * 0.3)
blended_pos = (1 - blend_weight) * direct_mapped + blend_weight * base_pos
if side_flag != 0:
final_pos = enforce_side_constraint(
blended_pos, current_surface, reference_normal, side_flag, confidence, is_boundary
)
else:
final_pos = blended_pos
if 'deformation_magnitude' in mapping:
if target_idx < len(mapping['deformation_magnitude']):
magnitude = mapping['deformation_magnitude'][target_idx]
final_pos = adaptive_damping(final_pos, target_pos, deformed_surface, magnitude, t, is_boundary=is_boundary)
return final_pos
def smooth_boundary_areas(target_obj, sk_name, mapping, source_data):
"""Specifically smooth boundary vertices to prevent them from popping out"""
print(f" Smoothing boundary areas for {sk_name}...")
sk = target_obj.data.shape_keys.key_blocks[sk_name]
current_positions = [v.co.copy() for v in sk.data]
num_verts = len(current_positions)
boundary_vertices = mapping.get('boundary_vertices', set())
if not boundary_vertices:
return
adjacency = mapping['adjacency']
# Multiple smoothing passes focused on boundaries
for iteration in range(3): # More iterations for boundaries
new_positions = current_positions.copy()
for i in boundary_vertices:
if i >= num_verts:
continue
neighbors = adjacency[i]
if not neighbors:
continue
# Average with neighbors, weighting by side similarity
weighted_sum = mathutils.Vector((0, 0, 0))
total_weight = 0
for n in neighbors:
if n < num_verts:
# Higher weight for neighbors on same side
if n < len(mapping['side_flags']) and i < len(mapping['side_flags']):
if mapping['side_flags'][n] == mapping['side_flags'][i]:
weight = 2.0
else:
weight = 1.0
else:
weight = 1.0
weighted_sum += current_positions[n] * weight
total_weight += weight
if total_weight > 0:
avg_pos = weighted_sum / total_weight
# Progressive smoothing
blend = 0.2 + iteration * 0.1
new_positions[i] = (1 - blend) * current_positions[i] + blend * avg_pos
current_positions = new_positions
# Apply smoothed positions
for i, pos in enumerate(current_positions):
if i < len(sk.data):
sk.data[i].co = pos
sk.data.update()
target_obj.data.update_tag()
bpy.context.view_layer.update()
print(f" Boundary smoothing complete")
def smooth_penetration_areas(target_obj, sk_name, mapping, source_data, threshold_t=0.55):
"""Smooth areas where penetration occurs at high t values"""
print(f" Smoothing penetration areas for {sk_name}...")
sk = target_obj.data.shape_keys.key_blocks[sk_name]
current_positions = [v.co.copy() for v in sk.data]
num_verts = len(current_positions)
boundary_vertices = mapping.get('boundary_vertices', set())
# Detect penetration at t=1.0
problem_vertices = set()
bvh_deformed, _ = create_bvh_for_source(source_data, sk_name)
for i, pos in enumerate(current_positions):
if i < len(mapping['surface_points']) and i < len(mapping['side_flags']):
surface_point = mapping['surface_points'][i]
target_side = mapping['side_flags'][i]
if target_side != 0:
location, _, _, _ = bvh_deformed.find_nearest(pos)
if location and i < len(mapping['direction_vectors']):
to_surface = pos - location
current_side = 1 if to_surface.dot(mapping['direction_vectors'][i]) > 0 else -1
if current_side != target_side:
problem_vertices.add(i)
if not problem_vertices:
print(f" No penetration detected")
return
print(f" Found {len(problem_vertices)} penetrating vertices")
# Separate boundary and interior problem vertices
boundary_problems = problem_vertices.intersection(boundary_vertices)
interior_problems = problem_vertices - boundary_vertices
if boundary_problems:
print(f" {len(boundary_problems)} are boundary vertices - these need special care")
# For boundary vertices, use very gentle smoothing
adjacency = mapping['adjacency']
for iteration in range(2):
new_positions = current_positions.copy()
for i in boundary_problems:
neighbors = adjacency[i]
if not neighbors:
continue
# Only average with non-problem neighbors
weighted_sum = mathutils.Vector((0, 0, 0))
total_weight = 0
for n in neighbors:
if n < num_verts and n not in boundary_problems:
weighted_sum += current_positions[n]
total_weight += 1
if total_weight > 0:
avg_pos = weighted_sum / total_weight
blend = 0.15 # Very gentle
new_positions[i] = (1 - blend) * current_positions[i] + blend * avg_pos
current_positions = new_positions
# Get vertices with large deformation
large_deformation = set()
if 'deformation_magnitude' in mapping:
for i, mag in enumerate(mapping['deformation_magnitude']):
if i < num_verts and mag > 0.7:
large_deformation.add(i)
# Focus on interior problems with large deformation
focus_vertices = interior_problems.intersection(large_deformation)
if focus_vertices:
print(f" {len(focus_vertices)} interior high-deformation vertices")
# Smooth interior problems
adjacency = mapping['adjacency']
for iteration in range(3):
new_positions = current_positions.copy()
for i in focus_vertices:
neighbors = adjacency[i]
if not neighbors:
continue
weighted_sum = mathutils.Vector((0, 0, 0))
total_weight = 0
for n in neighbors:
if n < num_verts:
weight = 1.0
if n in boundary_vertices:
weight = 0.3 # Less influence from boundaries
weighted_sum += current_positions[n] * weight
total_weight += weight
if total_weight > 0:
avg_pos = weighted_sum / total_weight
blend = 0.3 + iteration * 0.1
new_positions[i] = (1 - blend) * current_positions[i] + blend * avg_pos
current_positions = new_positions
# Apply smoothed positions
for i, pos in enumerate(current_positions):
if i < len(sk.data):
sk.data[i].co = pos
sk.data.update()
target_obj.data.update_tag()
bpy.context.view_layer.update()
print(f" Smoothing complete")
def set_shape_key_with_side_preservation(target_obj, sk_name, mapping, source_data):
"""Set shape key with side preservation"""
print(f" Setting {sk_name} with side preservation...")
if 'deformation_magnitude' not in mapping or len(mapping['deformation_magnitude']) != len(mapping['target_verts']):
mapping['deformation_magnitude'] = compute_deformation_magnitudes(source_data, mapping)
target_obj.data.shape_keys.use_relative = False
sk = target_obj.data.shape_keys.key_blocks[sk_name]
for i, v in enumerate(sk.data):
if i < len(mapping['target_verts']):
pos = compute_side_preserving_position(i, sk_name, mapping, source_data, 1.0)
v.co = pos
sk.data.update()
target_obj.data.update_tag()
bpy.context.view_layer.update()
target_obj.data.shape_keys.use_relative = True
target_obj.data.update_tag()
bpy.context.view_layer.update()
# First smooth boundary areas
smooth_boundary_areas(target_obj, sk_name, mapping, source_data)
# Then smooth penetration areas
smooth_penetration_areas(target_obj, sk_name, mapping, source_data)
def test_shape_key_quality(target_obj, sk_name, mapping, source_data):
"""Test shape key quality with side tracking"""
sk = target_obj.data.shape_keys.key_blocks[sk_name]
print(f" Testing quality of '{sk_name}':")
test_values = [0.0, 0.3, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.77, 0.8, 0.9, 1.0]
prev_positions = None
boundary_vertices = mapping.get('boundary_vertices', set())
for val in test_values:
if val == 0.0:
sk.value = 0.0
else:
target_obj.data.shape_keys.use_relative = False
for i, v in enumerate(sk.data):
if i < len(mapping['target_verts']):
pos = compute_side_preserving_position(i, sk_name, mapping, source_data, val)
v.co = pos
target_obj.data.shape_keys.use_relative = True
target_obj.data.update_tag()
bpy.context.view_layer.update()
if prev_positions is not None:
movements = []
side_changes = 0
boundary_issues = 0
for i, v in enumerate(target_obj.data.vertices):
if i < len(prev_positions) and i < len(mapping['side_flags']):
movement = (v.co - prev_positions[i]).length
if movement > 0.001:
movements.append(movement)
if i < len(mapping['surface_points']) and i < len(mapping['direction_vectors']):
current_to_surface = v.co - mapping['surface_points'][i]
current_side = 1 if current_to_surface.dot(mapping['direction_vectors'][i]) > 0 else -1
if current_side != mapping['side_flags'][i] and mapping['side_flags'][i] != 0:
side_changes += 1
if i in boundary_vertices:
boundary_issues += 1
if movements:
avg_movement = sum(movements) / len(movements)
max_movement = max(movements)
print(f" Value {val:.2f}: avg Δ {avg_movement:.4f}, max Δ {max_movement:.4f}")
if side_changes > 0:
print(f"{side_changes} vertices changed side ({boundary_issues} on boundary)")
prev_positions = [v.co.copy() for v in target_obj.data.vertices]
sk.value = 0.0
target_obj.data.update_tag()
bpy.context.view_layer.update()
def transfer_shape_keys(source_data, target_obj):
"""Transfer shape keys with side preservation"""
print(f" Transferring shape keys with side preservation...")
mapping = compute_robust_surface_mapping(target_obj, source_data)
for sk_name in source_data['names']:
if sk_name == 'Basis':
continue
print(f" Processing: {sk_name}")
set_shape_key_with_side_preservation(target_obj, sk_name, mapping, source_data)
print(f" ✓ Transferred {sk_name}")
return mapping
def verify_transfer(source_names, target_obj):
"""Verify shape keys were transferred correctly"""
target_names = [sk.name for sk in target_obj.data.shape_keys.key_blocks]
print(f" Verification:")
print(f" Source keys: {len(source_names)}")
print(f" Target keys: {len(target_names)}")
if set(source_names) == set(target_names):
print(f" ✓ All shape keys present")
def save_output_file(output_path, temp_target):
"""Save the modified file"""
print(f"\nSaving output file: {output_path}")
output_dir = os.path.dirname(output_path)
if output_dir and not os.path.exists(output_dir):
os.makedirs(output_dir)
bpy.ops.wm.save_as_mainfile(filepath=output_path)
try:
if os.path.exists(temp_target):
os.remove(temp_target)
temp_dir = os.path.dirname(temp_target)
if os.path.exists(temp_dir) and not os.listdir(temp_dir):
os.rmdir(temp_dir)
except:
pass
print(f"File saved successfully")
def main():
print("=" * 60)
print("Blender Shape Key Transfer Script - Boundary Velocity Limiting")
print("=" * 60)
source_file, target_file, output_file = parse_args()
print(f"Source: {source_file}")
print(f"Target: {target_file}")
print(f"Output: {output_file}")
try:
source_data = load_source_data(source_file)
target_objects, temp_target = load_target_file(target_file)
if target_objects:
for idx, target_obj in enumerate(target_objects, 1):
print(f"\n[{idx}/{len(target_objects)}] Processing: {target_obj.name}")
print(f" {'=' * 40}")
ensure_shape_keys_structure(source_data['names'], target_obj)
mapping = transfer_shape_keys(source_data, target_obj)
verify_transfer(source_data['names'], target_obj)
for sk_name in source_data['names']:
if sk_name == 'fat':
test_shape_key_quality(target_obj, sk_name, mapping, source_data)
break
print(f" {'=' * 40}")
print(f" ✓ Completed")
save_output_file(output_file, temp_target)
else:
print("\nNo target objects found with required properties")
print("\n" + "=" * 60)
print("Script completed successfully!")
print("=" * 60)
except Exception as e:
print(f"\nError: {str(e)}")
import traceback
traceback.print_exc()
sys.exit(1)
if __name__ == "__main__":
main()

View File

@@ -32,6 +32,8 @@ def dot_skeleton(obj, path, **kwargs):
name = kwargs.get('force_name') or obj.data.name
if config.get('SHARED_ARMATURE') is True:
name = kwargs.get('force_name') or arm.data.name
if name == "Armature":
name = arm.name
name = util.clean_object_name(name)
# Lets export the Armature only once

View File

@@ -18,7 +18,10 @@ sys.path.insert(1, incpath + "/blender2ogre")
import io_ogre
io_ogre.register()
try:
io_ogre.register()
except:
pass
gltf_file = argv[0]
print("Exporting to " + gltf_file)

View File

@@ -0,0 +1,79 @@
#!/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:]
incpath = os.path.dirname(__file__)
sys.path.insert(0, incpath)
sys.path.insert(1, incpath + "/blender2ogre")
print(sys.path)
import io_ogre
io_ogre.register()
gltf_file = argv[0]
print("Exporting to " + gltf_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)
if (obj.rigid_body):
print(obj.rigid_body.collision_shape)
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',
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.wm.read_homefile(use_empty=True)
time.sleep(2)
bpy.ops.wm.quit_blender()

View File

@@ -0,0 +1,333 @@
#!/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
import json
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
#from settings import ExportMappingFemale, ExportMappingMale, ExportMappingMaleBabyShape, ExportMappingMaleEdited, ExportMappingFemaleEdited, ExportMappingMaleTestShapeEdited, ExportMappingMaleBaseShapeEdited
argv = sys.argv
argv = argv[argv.index("--") + 1:]
basepath = os.getcwd()
def check_bone(bname):
ok = True
baddie = ["ctrl_", "mch_", "MCH_", "Ctrl_", "Mch_"]
for bd in baddie:
if bname.startswith(bd):
ok = False
break
return ok
def prepare_armature(mapping):
print("Preparing...")
bpy.ops.object.armature_add(enter_editmode=False)
new_armature = bpy.context.object
orig_armature = bpy.data.objects[mapping.armature_name]
armature_name = orig_armature.name
orig_armature.name = orig_armature.name + "_orig"
new_armature.name = armature_name
queue = []
if new_armature.animation_data is None:
new_armature.animation_data_create()
bpy.context.view_layer.objects.active = new_armature
bpy.ops.object.mode_set(mode='EDIT')
for b in new_armature.data.edit_bones:
new_armature.data.edit_bones.remove(b)
bpy.context.view_layer.objects.active = orig_armature
bpy.ops.object.mode_set(mode='EDIT')
for b in orig_armature.data.edit_bones:
print(b.name)
if b.parent is None:
queue.append(b.name)
print("Copying bones...")
while len(queue) > 0:
item = queue.pop(0)
print(item)
itemb = orig_armature.data.edit_bones[item]
if not itemb.use_deform and not check_bone(item):
continue
for cb in orig_armature.data.edit_bones:
if cb.parent == itemb:
queue.append(cb.name)
nb = new_armature.data.edit_bones.new(item)
nb.name = item
nb.head = itemb.head
nb.tail = itemb.tail
nb.matrix = itemb.matrix
nb.use_deform = itemb.use_deform
if itemb.parent is not None:
ok = True
pname = itemb.parent.name
while not check_bone(pname):
bparent = itemb.parent.parent
if bparent is None:
ok = False
break
pname = bparent.name
if ok:
nb.parent = new_armature.data.edit_bones[itemb.parent.name]
else:
nb.parent = None
else:
nb.parent = None
nb.use_connect = itemb.use_connect
# drivers_data = new_armature.animation_data.drivers
print("Creating constraints...")
bpy.context.view_layer.objects.active = new_armature
bpy.ops.object.mode_set(mode='OBJECT')
bpy.context.view_layer.objects.active = orig_armature
bpy.ops.object.mode_set(mode='OBJECT')
for b in new_armature.pose.bones:
print(b.name)
c = b.constraints.new(type='COPY_TRANSFORMS')
c.target = orig_armature
c.subtarget = b.name
for obj in bpy.data.objects:
if obj.parent == orig_armature:
obj.parent = new_armature
for mod in obj.modifiers:
if mod.type == 'ARMATURE':
mod.object = new_armature
print("Baking actions...")
bpy.context.view_layer.objects.active = new_armature
bpy.ops.object.mode_set(mode='POSE')
for track in orig_armature.animation_data.nla_tracks:
print(track.name)
for s in track.strips:
action = s.action
print(action.name)
orig_armature.animation_data.action = action
new_armature.animation_data.action = None
bpy.context.view_layer.objects.active = new_armature
firstFrame = int(s.action_frame_start)
lastFrame = int(s.action_frame_end)
bpy.ops.nla.bake(frame_start=firstFrame, frame_end=lastFrame, step=5, only_selected=False, visual_keying=True, clear_constraints=False, clean_curves=True, use_current_action=False, bake_types={'POSE'})
aname = orig_armature.animation_data.action.name
orig_armature.animation_data.action.name = "bake_" + aname
new_armature.animation_data.action.name = aname
track = new_armature.animation_data.nla_tracks.new()
track.name = aname
track.strips.new(track.name, int(new_armature.animation_data.action.frame_range[0]), new_armature.animation_data.action)
track.mute = True
track.lock = True
print("Removing constraints...")
for b in new_armature.pose.bones:
for c in b.constraints:
b.constraints.remove(c)
new_armature.animation_data.action = bpy.data.actions[mapping.default_action]
bpy.context.view_layer.objects.active = new_armature
bpy.ops.object.mode_set(mode='OBJECT')
# track = new_armature.animation_data.nla_tracks.new()
# track.name = action.name
def clamp_angle_deg(angle, min_angle_deg, max_angle_deg):
min_angle = radians(min_angle_deg)
max_angle = radians(max_angle_deg)
if angle < min_angle:
angle = min_angle
if angle > max_angle:
angle = max_angle
return angle
def angle_to_linear(angle, divider):
if angle < 0.0:
return angle / divider
else:
return 0.0
def angle_to_linear_x(bone, angle):
skel = bpy.data.objects["skeleton_orig"]
left_base = "ctrl_base_upperleg.L.001"
right_base = "ctrl_base_upperleg.R.001"
base = ""
if base == "":
for e in ["_R", ".R"]:
if bone.name.endswith(e):
base = right_base
break
if base == "":
for e in ["_L", ".L"]:
if bone.name.endswith(e):
base = left_base
break
if base == "":
for e in ["_R.", ".R."]:
if bone.name.find(e) >= 0:
base = right_base
break
if base == "":
for e in ["_L.", ".L."]:
if bone.name.find(e) >= 0:
base = left_base
break
mul = skel.pose.bones[base]["to_linear_x_base"]
offset = skel.pose.bones[base]["angle_offset"]
# print("bone: ", bone.name, "base: ", base, "mul: ", mul)
# print("angle: ", angle, " angle_offset: ", offset, " angle_sum: ", angle + offset)
print("offset: ", mul * (angle + offset), "bone: ", base, "angle: ", angle)
return (angle + offset) * mul
def extra_linear(angle, offset):
ret = 0.0
offt = offset * angle * 2.0 / -radians(-90)
if angle * 2.0 < -radians(65):
if angle * 2.0 > -radians(65):
ret += offset
else:
ret += offt
return ret
mapping_blend_path = argv[0]
mapping_gltf_path = argv[1]
mapping_objects = argv[2]
mapping_armature_name = argv[3]
mapping_outfile = argv[4]
#for mapping in [ExportMappingFemale(), ExportMappingMale(), ExportMappingMaleBabyShape(), ExportMappingMaleEdited(), ExportMappingFemaleEdited(), ExportMappingMaleTestShapeEdited(), ExportMappingMaleBaseShapeEdited()]:
class CommandLineMapping:
blend_path = mapping_blend_path
gltf_path = mapping_gltf_path
# ogre_scene = "characters/female/vroid-normal-female.scene"
inner_path = "Object"
# objs = ["male", "Body", "Hair", "Face", "BackHair", "Tops", "Bottoms", "Shoes", "Accessory"]
# objs = ["female", "Body", "Hair", "Face", "BackHair", "Tops", "Bottoms", "Shoes", "Accessory"]
objs = []
armature_name = mapping_armature_name
outfile = mapping_outfile
default_action = 'default'
def __init__(self):
self.objs = [mapping_armature_name]
if len(mapping_objects) > 0:
self.objs += [o.strip() for o in mapping_objects.split(";")]
self.files = []
for fobj in self.objs:
self.files.append({"name": fobj})
for mapping in[CommandLineMapping()]:
if not os.path.exists(mapping.blend_path):
print("Skipping mapping: " + mapping.blend_path)
continue
print("Processing mapping: from: " + mapping.blend_path + " to: " + mapping.gltf_path)
print("Initializing...")
bpy.ops.wm.read_homefile(use_empty=True)
print("Preparing driver setup...")
bpy.app.driver_namespace["clamp_angle_deg"] = clamp_angle_deg
bpy.app.driver_namespace["angle_to_linear"] = angle_to_linear
bpy.app.driver_namespace["extra_linear"] = extra_linear
bpy.app.driver_namespace["angle_to_linear_x"] = angle_to_linear_x
print("Driver setup done...")
bpy.ops.wm.append(
filepath=os.path.join(mapping.blend_path, mapping.inner_path),
directory=os.path.join(mapping.blend_path, mapping.inner_path),
files=mapping.files)
print("Append done...")
prepare_armature(mapping)
print("Armature done...")
print("Remove junk...")
for ob in bpy.data.objects:
if ob.name.startswith("cs_"):
bpy.data.objects.remove(ob)
elif ob.name.startswith("Face") and ob.name != "Face":
bpy.data.objects.remove(ob)
print("Removing original armature and actions...")
orig_arm = bpy.data.objects[mapping.armature_name + '_orig']
bpy.data.objects.remove(orig_arm)
for act in bpy.data.actions:
if act.name.startswith("bake_"):
act.name = act.name + "-noimp"
for act in bpy.data.actions:
if act.name.startswith("bake_"):
bpy.data.actions.remove(act)
for act in bpy.data.actions:
if act.name.startswith("bake_"):
bpy.data.actions.remove(act)
for obj in bpy.data.objects:
if obj.type == 'MESH':
if not obj.name in mapping.objs and obj.parent is None:
if not obj.name.endswith("-noimp"):
obj.name = obj.name + "-noimp"
bpy.ops.wm.save_as_mainfile(filepath=(basepath + "/assets/blender/scripts/" + mapping.outfile))
os.makedirs(os.path.dirname(mapping.gltf_path), exist_ok=True)
bpy.ops.export_scene.gltf(filepath=mapping.gltf_path,
use_selection=False,
check_existing=False,
# export_format='GLTF_SEPARATE',
export_format='GLB',
export_texture_dir='textures', export_texcoords=True,
export_animation_mode='NLA_TRACKS',
export_normals=True,
export_tangents=True,
export_materials='EXPORT',
# export_all_vertex_colors=True,
# colors_type='SRGB',
# export_vertex_colors=True,
export_colors=True,
use_mesh_edges=False,
use_mesh_vertices=False,
export_cameras=False,
use_visible=False,
use_renderable=False,
export_yup=True,
export_animations=True,
export_force_sampling=True,
export_def_bones=False,
export_current_frame=False,
export_morph=True,
export_morph_animation=False,
export_morph_normal=True,
export_morph_tangent=True,
export_lights=False,
export_skins=True)
print("exported to: " + mapping.gltf_path)
obj_names = mapping.objs
prefix = mapping.armature_name + "_"
for name in obj_names:
obj = bpy.data.objects.get(name)
if obj and obj.type == 'MESH':
# 1. Rename Mesh Data
if not obj.data.name.startswith(prefix):
obj.data.name = prefix + obj.data.name
# 2. Iterate through all Material Slots on the object
for slot in obj.material_slots:
if slot.material:
mat = slot.material
# 3. Check if material already has the prefix
if not mat.name.startswith(prefix):
mat.name = prefix + mat.name
print(f"Renamed material '{mat.name}' on object '{name}'")
# 3. Export custom properties to json
save_data = {}
for key in obj.keys():
if key in ["age", "sex", "slot"]:
save_data[key] = obj[key]
if key.startswith("body_"):
save_data[key.replace("body_", "", 1)] = obj[key]
save_data["mesh"] = obj.data.name + ".mesh"
json_dir = os.path.dirname(mapping.gltf_path)
save_file = json_dir + "/body_part_" + obj.data.name + ".json"
json_filepath = os.path.join(json_dir, save_file)
with open(json_filepath, 'w') as f:
json.dump(save_data, f)
armobj = bpy.data.objects.get(mapping.armature_name)
armobj.data.name = armobj.name
bpy.ops.ogre.export(filepath=mapping.gltf_path.replace(".glb", ".scene"), EX_SELECTED_ONLY=False, EX_SHARED_ARMATURE=True, EX_LOD_GENERATION='0', EX_LOD_DISTANCE=20, EX_LOD_LEVELS=4, EX_GENERATE_TANGENTS='4')
bpy.ops.wm.read_homefile(use_empty=True)
time.sleep(2)
bpy.ops.wm.quit_blender()

View File

@@ -207,5 +207,5 @@ main_armature.animation_data.action = default_action
main_armature.select_set(False)
bpy.context.view_layer.objects.active = None
bpy.ops.wm.save_as_mainfile(filepath=(basepath + "/assets/blender/" + imp.outfile))
bpy.ops.wm.save_as_mainfile(filepath=(imp.outfile))

View File

@@ -73,9 +73,8 @@ FileSystem=resources/fonts
[LuaScripts]
FileSystem=lua-scripts
[Characters]
FileSystem=./characters/male
FileSystem=./characters/female
#[Characters]
#FileSystem=./characters
[Audio]
FileSystem=./audio/gui

View File

@@ -49,6 +49,7 @@ target_link_libraries(Editor PRIVATE
physics
lua
flecs::flecs_static
Tracy::TracyClient
)
if(OGRE_STATIC)
target_link_options(Editor PRIVATE -static-libstdc++ -static-libgcc)

View File

@@ -23,6 +23,7 @@
#include "PhysicsModule.h"
#include "physics.h"
#include "sound.h"
#include <tracy/Tracy.hpp>
class App;
class SkyRenderer : public Ogre::SceneManager::Listener {
@@ -386,14 +387,22 @@ public:
}
void locateResources() override
{
Ogre::ResourceGroupManager::getSingleton().createResourceGroup(
Ogre::ResourceGroupManager::getSingleton().createResourceGroup(
"Characters", true);
Ogre::ResourceGroupManager::getSingleton().createResourceGroup(
"Water", true);
Ogre::ResourceGroupManager::getSingleton().createResourceGroup(
"LuaScripts", false);
Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
"./lua-scripts", "FileSystem", "LuaScripts", true,
true);
OgreBites::ApplicationContext::locateResources();
Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
"./characters/male", "FileSystem", "Characters", false,
true);
Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
"./characters/female", "FileSystem", "Characters",
false, true);
OgreBites::ApplicationContext::locateResources();
}
void loadResources() override
{
@@ -683,52 +692,60 @@ public:
};
void KeyboardListener::frameRendered(const Ogre::FrameEvent &evt)
{
if (fps_timer.getMilliseconds() > 1000.0f) {
std::cout << "FPS: "
<< mApp->getRenderWindow()->getStatistics().lastFPS
<< " ";
std::cout << "Draw calls: "
<< mApp->getRenderWindow()->getStatistics().batchCount
<< " ";
fps_timer.reset();
std::cout << "Drops: "
<< mApp->getRenderWindow()
->getStatistics()
.vBlankMissCount
<< "\n";
fps_timer.reset();
}
/* for editor we always update world */
/* TODO: implement pause */
mApp->updateWorld(evt.timeSinceLastFrame);
if (mInitDelay >= 0.0f)
mInitDelay -= evt.timeSinceLastFrame;
{
ZoneScopedN("frameRendered");
if (fps_timer.getMilliseconds() > 1000.0f) {
std::cout << "FPS: "
<< mApp->getRenderWindow()
->getStatistics()
.lastFPS
<< " ";
std::cout << "Draw calls: "
<< mApp->getRenderWindow()
->getStatistics()
.batchCount
<< " ";
fps_timer.reset();
std::cout << "Drops: "
<< mApp->getRenderWindow()
->getStatistics()
.vBlankMissCount
<< "\n";
fps_timer.reset();
}
/* for editor we always update world */
/* TODO: implement pause */
mApp->updateWorld(evt.timeSinceLastFrame);
if (mInitDelay >= 0.0f)
mInitDelay -= evt.timeSinceLastFrame;
if (!isGuiEnabled() && ECS::get().has<ECS::Input>()) {
ECS::Input &input = ECS::get().get_mut<ECS::Input>();
input.control = control;
input.mouse = mouse;
input.mouse_abs = mouse_abs;
mouse.x = 0;
mouse.y = 0;
input.wheel_y = wheel_y;
wheel_y = 0;
input.mouse_moved = mouse_moved;
input.wheel_moved = wheel_moved;
ECS::modified<ECS::Input>();
} else {
ECS::Input &input = ECS::get().get_mut<ECS::Input>();
input.control = control;
input.mouse = mouse;
input.mouse_abs = mouse_abs;
mouse.x = 0;
mouse.y = 0;
input.wheel_y = wheel_y;
wheel_y = 0;
input.mouse_moved = mouse_moved;
input.wheel_moved = wheel_moved;
ECS::modified<ECS::Input>();
if (!isGuiEnabled() && ECS::get().has<ECS::Input>()) {
ECS::Input &input = ECS::get().get_mut<ECS::Input>();
input.control = control;
input.mouse = mouse;
input.mouse_abs = mouse_abs;
mouse.x = 0;
mouse.y = 0;
input.wheel_y = wheel_y;
wheel_y = 0;
input.mouse_moved = mouse_moved;
input.wheel_moved = wheel_moved;
ECS::modified<ECS::Input>();
} else {
ECS::Input &input = ECS::get().get_mut<ECS::Input>();
input.control = control;
input.mouse = mouse;
input.mouse_abs = mouse_abs;
mouse.x = 0;
mouse.y = 0;
input.wheel_y = wheel_y;
wheel_y = 0;
input.mouse_moved = mouse_moved;
input.wheel_moved = wheel_moved;
ECS::modified<ECS::Input>();
}
}
FrameMark;
}
int main()

View File

@@ -0,0 +1,68 @@
#include <iostream>
#include "AnimationSystem.h"
#include <tracy/Tracy.hpp>
namespace AnimationSystem
{
bool AnimationSystem::addTime(float time)
{
int i;
ZoneScopedN("AnimationSystem::addTime");
preUpdateTriggers();
bool ret = m_builder.animation_nodes[0]->addTime(time);
for (i = 0; i < m_builder.animationNodeList.size(); i++) {
ZoneScoped;
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
if (anim->getWeight() > 0.01f) {
ZoneScoped;
ZoneText("builder", strlen("builder"));
ZoneText(anim->mAnimation->getName().c_str(),
anim->mAnimation->getName().length());
ZoneValue(anim->getWeight() * 100.0f);
}
}
for (i = 0; i < vanimation_list.size(); i++) {
ZoneScoped;
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
if (vanimation_list[i]->mAnimationState->getEnabled()) {
ZoneScoped;
ZoneText("animation", strlen("animation"));
ZoneText(vanimation_list[i]->getName().c_str(),
vanimation_list[i]->getName().length());
ZoneValue(vanimation_list[i]->getWeight() * 100.0f);
}
}
postUpdateTriggers(time);
return ret;
}
Ogre::Vector3 AnimationSystem::getRootMotionDelta()
{
ZoneScopedN("AnimationSystem::getRootMotionDelta");
Ogre::Vector3 motionDelta(0, 0, 0);
int i;
for (i = 0; i < vanimation_list.size(); i++)
motionDelta += vanimation_list[i]->getRootMotionDelta();
return motionDelta;
}
}

View File

@@ -0,0 +1,900 @@
#ifndef ANIMATIONSYSTEM_H
#define ANIMATIONSYSTEM_H
#include <iostream>
#include <Ogre.h>
#include <flecs.h>
#include <tracy/Tracy.hpp>
#include "GameData.h"
#include "EventModule.h"
namespace AnimationSystem
{
struct AnimationTrigger;
struct AnimationTriggerSubscriber {
virtual void operator()(const AnimationTrigger *trigger) = 0;
};
class RootMotionListener : public Ogre::NodeAnimationTrack::Listener {
public:
RootMotionListener()
: Ogre::NodeAnimationTrack::Listener()
{
}
bool getInterpolatedKeyFrame(const Ogre::AnimationTrack *t,
const Ogre::TimeIndex &timeIndex,
Ogre::KeyFrame *kf) override
{
#if 0
Ogre::TransformKeyFrame *vkf =
static_cast<Ogre::TransformKeyFrame *>(kf);
Ogre::KeyFrame *kf1, *kf2;
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;
Ogre::Vector3 deltaMotion;
Ogre::Vector3 prevMotion;
if (tm == 0.0f) {
rotation = k1->getRotation();
translation = k1->getTranslate();
deltaMotion = translation;
} 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;
}
vkf->setTranslate(deltaMotion);
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;
#endif
return false;
}
};
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 {
struct SetupTracks {
Ogre::NodeAnimationTrack *mHipsTrack;
Ogre::NodeAnimationTrack *mRootTrack;
RootMotionListener *mListener;
Ogre::Vector3 mRootTranslation;
SetupTracks(Ogre::Skeleton *skeleton,
Ogre::Animation *animation)
: mListener(OGRE_NEW RootMotionListener())
{
mHipsTrack = nullptr;
mRootTrack = nullptr;
for (const auto &it : animation->_getNodeTrackList()) {
Ogre::NodeAnimationTrack *track = it.second;
Ogre::String trackName =
track->getAssociatedNode()->getName();
if (trackName == "mixamorig:Hips") {
mHipsTrack = track;
} else if (trackName == "Root") {
mRootTrack = track;
}
}
if (!mRootTrack) {
Ogre::Bone *bone = skeleton->getBone("Root");
mRootTrack = animation->createNodeTrack(
bone->getHandle(), bone);
Ogre::TransformKeyFrame *kf =
mRootTrack->createNodeKeyFrame(0.0f);
kf->setTranslate(Ogre::Vector3::ZERO);
kf->setRotation(Ogre::Quaternion::IDENTITY);
}
Ogre::TransformKeyFrame *tkfBeg =
(Ogre::TransformKeyFrame *)
mRootTrack->getKeyFrame(0);
Ogre::TransformKeyFrame *tkfEnd =
(Ogre::TransformKeyFrame *)
mRootTrack->getKeyFrame(
mRootTrack->getNumKeyFrames() -
1);
mRootTranslation =
tkfEnd->getTranslate() - tkfBeg->getTranslate();
}
};
Ogre::AnimationState *mAnimationState;
Ogre::Animation *mSkelAnimation;
SetupTracks *mTracks;
float m_weight;
float m_accWeight;
Ogre::Skeleton *mSkeleton;
Animation(Ogre::Skeleton *skeleton, Ogre::AnimationState *animState,
Ogre::Animation *skelAnimation)
: mTracks(OGRE_NEW SetupTracks(skeleton, skelAnimation))
, mAnimationState(animState)
, mSkelAnimation(skelAnimation)
, m_weight(0)
, m_accWeight(0)
, mSkeleton(skeleton)
{
}
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;
}
Ogre::Vector3 rootMotion;
Ogre::Vector3 getRootMotionDelta()
{
if (mAnimationState->getEnabled())
return rootMotion * mAnimationState->getWeight();
else
return Ogre::Vector3(0, 0, 0);
}
void getKeyframeIndices(Ogre::Real timePos, unsigned short *pindex)
{
Ogre::TimeIndex index = mSkelAnimation->_getTimeIndex(timePos);
Ogre::KeyFrame *kf1, *kf2;
mTracks->mRootTrack->getKeyFramesAtTime(index, &kf1, &kf2,
pindex);
}
bool addTime(float time)
{
bool result = mAnimationState->getEnabled();
if (!result)
return result;
unsigned short prev_index, next_index;
Ogre::TimeIndex index = mSkelAnimation->_getTimeIndex(
mAnimationState->getTimePosition());
getKeyframeIndices(mAnimationState->getTimePosition(),
&prev_index);
unsigned int previous_frame = index.getKeyIndex();
float lastTime = mAnimationState->getTimePosition();
mAnimationState->addTime(time);
float thisTime = mAnimationState->getTimePosition();
float length = mAnimationState->getLength();
bool loop = mAnimationState->getLoop();
int loops =
loop ? (int)std::round((lastTime + time - thisTime) /
length) :
0;
Ogre::TransformKeyFrame tkf(0, 0);
mTracks->mRootTrack->getInterpolatedKeyFrame(lastTime, &tkf);
Ogre::Vector3 lastRootPos = tkf.getTranslate();
mTracks->mRootTrack->getInterpolatedKeyFrame(thisTime, &tkf);
Ogre::Vector3 thisRootPos = tkf.getTranslate();
rootMotion = (thisRootPos - lastRootPos) +
(loops * mTracks->mRootTranslation);
getKeyframeIndices(mAnimationState->getTimePosition(),
&next_index);
// updateRootMotion(mAnimationState->getTimePosition());
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;
}
};
struct AnimationSystem : AnimationNode {
bool debug;
#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();
}
};
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;
}
/* FIXME: need to remove flecs dependency */
AnimationSystemBuilder *
trigger_entity(flecs::entity e, const Ogre::String &name,
float time, const Ogre::String &event)
{
struct EntityEventSubscriber
: AnimationTriggerSubscriber {
Ogre::String event;
flecs::entity ent;
void operator()(const AnimationTrigger *trigger)
{
ent.get_mut<ECS::EventData>().add(
ent, event, ent, ent);
}
EntityEventSubscriber(flecs::entity e,
const Ogre::String &event)
: event(event)
, ent(e)
{
}
};
OgreAssert(parent, "bad parent");
AnimationTrigger *trigger =
new AnimationTrigger(name, time, 0.1f);
EntityEventSubscriber *sub =
new EntityEventSubscriber(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;
Ogre::Vector3 getRootMotionDelta();
bool addTime(float time);
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();
}
};
}
#endif // ANIMATIONSYSTEM_H

View File

@@ -201,16 +201,15 @@ BoatModule::BoatModule(flecs::world &ecs)
->_getDerivedOrientation();
if (ev.e2.has<
CharacterBase>()) {
ev.e2.get_mut<
CharacterBase>()
.mBodyNode
->_setDerivedPosition(
position);
ev.e2.get_mut<
CharacterBase>()
.mBodyNode
->_setDerivedOrientation(
orientation);
Ogre::SceneNode *n =
ECS::get<
CharacterModule>()
.characterNodes
.at(ev.e2);
n->_setDerivedPosition(
position);
n->_setDerivedOrientation(
orientation);
}
}
e.set<BoatCurrentActuator>(
@@ -282,14 +281,14 @@ BoatModule::BoatModule(flecs::world &ecs)
captainSeat
->_getDerivedOrientation();
if (ev.e2.has<CharacterBase>()) {
ev.e2.get_mut<CharacterBase>()
.mBodyNode
->_setDerivedPosition(
position);
ev.e2.get_mut<CharacterBase>()
.mBodyNode
->_setDerivedOrientation(
orientation);
Ogre::SceneNode *n =
ECS::get<CharacterModule>()
.characterNodes
.at(ev.e2);
n->_setDerivedPosition(
position);
n->_setDerivedOrientation(
orientation);
}
} else if (ev.event == "boat_control_exit") {
} else if (ev.event == "actuator_exit") {

View File

@@ -4,19 +4,22 @@ find_package(OGRE REQUIRED COMPONENTS Bites Bullet Paging Terrain Overlay CONFIG
find_package(Bullet REQUIRED)
find_package(nlohmann_json REQUIRED)
find_package(OgreProcedural REQUIRED CONFIG)
find_package(Tracy REQUIRED CONFIG)
add_subdirectory(items)
add_subdirectory(LuaModule)
add_library(GameData STATIC GameData.cpp CharacterModule.cpp WaterModule.cpp SunModule.cpp TerrainModule.cpp
GUIModule.cpp EditorGUIModule.cpp LuaData.cpp WorldMapModule.cpp BoatModule.cpp EventTriggerModule.cpp
GUIModule.cpp EditorGUIModule.cpp WorldMapModule.cpp BoatModule.cpp EventTriggerModule.cpp
CharacterAnimationModule.cpp PhysicsModule.cpp EventModule.cpp CharacterManagerModule.cpp
VehicleManagerModule.cpp AppModule.cpp StaticGeometryModule.cpp SmartObject.cpp SlotsModule.cpp QuestModule.cpp
PlayerActionModule.cpp CharacterAIModule.cpp goap.cpp)
PlayerActionModule.cpp CharacterAIModule.cpp goap.cpp AnimationSystem.cpp)
target_link_libraries(GameData PUBLIC
lua
items luamodule
flecs::flecs_static
nlohmann_json::nlohmann_json
OgreMain
OgreBites
OgrePaging OgreTerrain OgreOverlay OgreProcedural::OgreProcedural items
PRIVATE sceneloader world-build physics editor)
target_include_directories(GameData PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${BULLET_INCLUDE_DIR} ../luaaa ../aitoolkit/include)
target_compile_definitions(GameData PRIVATE FLECS_CPP_NO_AUTO_REGISTRATION)
OgrePaging OgreTerrain OgreOverlay OgreProcedural::OgreProcedural
PRIVATE sceneloader world-build physics editor Tracy::TracyClient
)
target_include_directories(GameData PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${BULLET_INCLUDE_DIR})
target_compile_definitions(GameData PRIVATE FLECS_CPP_NO_AUTO_REGISTRATION PUBLIC TRACY_ENABLE)

View File

@@ -7,6 +7,7 @@
#include "CharacterModule.h"
#include "items.h"
#include "CharacterAIModule.h"
#include <tracy/Tracy.hpp>
namespace ECS
{
struct ActionExec {
@@ -187,17 +188,16 @@ public:
int update(float delta) override
{
if (npc.e.is_valid()) {
Ogre::SceneNode *n =
ECS::get<CharacterModule>()
.characterNodes.at(npc.e);
Ogre::Vector3 position =
npc.e.get<CharacterBase>()
.mBodyNode
->_getDerivedPosition();
n->_getDerivedPosition();
if (position.squaredDistance(targetPosition) >=
radius * radius) {
if (npc.e.is_valid())
npc.e.get<CharacterBase>()
.mBodyNode
->_setDerivedPosition(
targetPosition);
n->_setDerivedPosition(
targetPosition);
npc.position = targetPosition;
return BUSY;
}
@@ -231,7 +231,6 @@ public:
Ogre::Vector3 direction =
(targetPosition - npc.position).normalisedCopy();
targetPosition -= direction * radius;
std::cout << action->get_name();
}
public:
@@ -245,21 +244,20 @@ public:
private:
int update(float delta) override
{
OgreAssert(false, "update");
return OK;
}
void finish(int rc) override
{
if (rc == OK)
bb.apply(action->effects);
OgreAssert(false, "finish");
}
void activate() override
{
std::cout << action->get_name();
const ActionNodeActions::RunActionNode *runaction =
static_cast<const ActionNodeActions::RunActionNode
*>(action);
if (runaction)
std::cout << "Is Run" << std::endl;
OgreAssert(false, "activate");
}
@@ -272,6 +270,7 @@ public:
};
ActionNodeActions(int node, const Blackboard &prereq, int cost)
{
ZoneScoped;
OgreAssert(
node < ECS::get<ActionNodeList>().dynamicNodes.size(),
"bad node " + Ogre::StringConverter::toString(node));
@@ -329,14 +328,33 @@ public:
jactionPrereq["is_seated"] = 0;
jactionEffect["is_seated"] = 1;
} else if (action == "use") {
if (props["tags"].find("toilet") !=
props["tags"].end()) {
std::cout << "toilet" << std::endl;
OgreAssert(false, "props: " + props.dump(4));
OgreAssert(props["tags"].is_array(), "bad formed tags");
const nlohmann::json &tags = props["tags"];
if (tags.size() == 1 &&
tags[0].get<Ogre::String>() == "") {
} else {
std::cout << "use: " << props.dump(4)
<< std::endl;
// OgreAssert(false, "props: " + props.dump(4));
bool have_bits = false;
if (std::find(tags.begin(), tags.end(),
"dance-pole") != tags.end()) {
jactionPrereq["is_pole_dancing"] = 0;
jactionEffect["is_pole_dancing"] = 1;
have_bits = true;
}
if (std::find(tags.begin(), tags.end(),
"toilet") != tags.end()) {
jactionPrereq["toilet"] = 1;
jactionEffect["toilet"] = 0;
have_bits = true;
}
if (!have_bits) {
ZoneScopedN("Use");
std::cout << "use: " << props.dump(4)
<< std::endl;
// OgreAssert(false, "props: " + props.dump(4));
OgreAssert(tags.size() == 0,
"Some tags: " +
props.dump(4));
}
}
} else {
OgreAssert(false, "props: " + props.dump(4));
@@ -363,27 +381,32 @@ public:
};
struct ActionExecCommon : ActionExec {
private:
float delay;
int update(float delta) override
{
std::cout << "running: " << action->get_name() << std::endl;
return OK;
delay -= delta;
if (delay > 0.0f)
return BUSY;
else
return OK;
}
void finish(int rc) override
{
if (rc == OK)
bb.apply(action->effects);
std::cout << "finish: " << action->get_name() << std::endl;
}
void activate() override
{
std::cout << action->get_name();
std::cout << "!";
ZoneScoped;
ZoneTextF("%s", action->get_name().c_str());
delay = 1.0f;
}
public:
ActionExecCommon(ActionExec::PlanExecData &data,
goap::BaseAction<Blackboard> *action)
: ActionExec(data, action)
, delay(0.0f)
{
}
};
@@ -392,6 +415,7 @@ CharacterAIModule::CharacterAIModule(flecs::world &ecs)
static std::mutex ecs_mutex;
ecs.module<CharacterAIModule>();
ecs.import <CharacterManagerModule>();
ecs.import <PlayerActionModule>();
ecs.component<Blackboard>();
ecs.component<TownAI>().on_add([](flecs::entity e, TownAI &ai) {
std::lock_guard<std::mutex> lock(ecs_mutex);
@@ -439,20 +463,30 @@ CharacterAIModule::CharacterAIModule(flecs::world &ecs)
{ "thirsty", 1 } } },
{ { { "thirsty", 0 } } },
10 },
#if 0
{ "DrinkWater",
{ { { "have_water", 1 }, { "thirsty", 1 } } },
{ { { "have_water", 1 },
{ "thirsty", 1 },
{ "is_seated", 0 } } },
{ { { "thirsty", 0 } } },
2000 },
#endif
{ "EatMedicine",
{ { { "have_medicine", 1 }, { "healthy", 0 } } },
{ "EatMedicineSeated",
{ { { "have_medicine", 1 },
{ "healthy", 0 },
{ "is_seated", 1 } } },
{ { { "healthy", 1 } } },
100 },
{ "EatMedicine",
{ { { "have_medicine", 1 },
{ "healthy", 0 },
{ "is_seated", 0 } } },
{ { { "healthy", 1 } } },
10000 },
#if 0
{ "UseToilet",
{ { { "toilet", 1 } } },
{ { { "toilet", 0 } } },
100 },
#endif
{ "GetFood",
{ { { "have_food", 0 } } },
{ { { "have_food", 1 } } },
@@ -478,31 +512,26 @@ CharacterAIModule::CharacterAIModule(flecs::world &ecs)
.kind(flecs::OnUpdate)
.each([this](flecs::entity town, TownAI &ai,
const TownNPCs &npcs) {
Ogre::Root::getSingleton().getWorkQueue()->addTask(
[this, town, npcs, &ai]() {
Ogre::Root::getSingleton()
.getWorkQueue()
->addMainThreadTask([this, town,
npcs,
&ai]() {
std::lock_guard<
std::mutex>
lock(ecs_mutex);
createBlackboards(
town, npcs, ai);
});
});
ZoneScopedN("CreateBlackboards");
std::lock_guard<std::mutex> lock(ecs_mutex);
OgreAssert(npcs.npcs.size() > 0, "npcs not crated");
createBlackboards(town, npcs, ai);
});
ecs.system<ActionNodeList, TownAI, TownNPCs>("UpdateDynamicActions")
.kind(flecs::OnUpdate)
.each([](flecs::entity e, ActionNodeList &alist, TownAI &ai,
TownNPCs &npcs) {
ZoneScopedN("UpdateDynamicActions");
std::lock_guard<std::mutex> lock(ecs_mutex);
OgreAssert(npcs.npcs.size() > 0, "npcs not crated");
if (ai.nodeActions.size() > 0)
return;
if (alist.dynamicNodes.size() == 0)
ECS::get_mut<ActionNodeList>()
.updateDynamicNodes();
OgreAssert(alist.nodes.size() > 0, "bad nodes");
if (alist.dynamicNodes.size() == 0)
return;
OgreAssert(alist.dynamicNodes.size() > 0,
"bad dynamic nodes");
int nodeIndex;
@@ -527,21 +556,44 @@ CharacterAIModule::CharacterAIModule(flecs::world &ecs)
.interval(0.1f)
.each([this](flecs::entity town, ActionNodeList &alist,
TownAI &ai, TownNPCs &npcs) {
ZoneScopedN("UpdateDynamicNodes");
OgreAssert(npcs.npcs.size() > 0, "npcs not crated");
std::lock_guard<std::mutex> lock(ecs_mutex);
ECS::get_mut<ActionNodeList>().updateDynamicNodes();
});
struct MeasureTime {
std::chrono::system_clock::time_point start;
std::string what;
MeasureTime(const std::string &s)
: start(std::chrono::high_resolution_clock::now())
, what(s)
{
}
~MeasureTime()
{
auto end = std::chrono::high_resolution_clock::now();
std::chrono::duration<float, std::milli> elapsed =
end - start;
std::cout << what << " " << elapsed.count()
<< std::endl;
}
};
ecs.system<TownNPCs>("UpdateNPCPositions")
.kind(flecs::OnUpdate)
.each([](flecs::entity e, TownNPCs &npcs) {
ZoneScopedN("UpdateNPCPositions");
OgreAssert(npcs.npcs.size() > 0, "npcs not crated");
for (auto it = npcs.npcs.begin(); it != npcs.npcs.end();
it++) {
auto &npc = npcs.npcs.at(it->first);
if (npc.e.is_valid() &&
npc.e.has<CharacterBase>())
npc.position =
npc.e.get<CharacterBase>()
.mBodyNode
->_getDerivedPosition();
npc.e.has<CharacterBase>()) {
Ogre::SceneNode *n =
ECS::get<CharacterModule>()
.characterNodes.at(
npc.e);
npc.position = n->_getDerivedPosition();
}
}
});
ecs.system<ActionNodeList, TownAI, TownNPCs>("UpdateBlackboards")
@@ -549,49 +601,71 @@ CharacterAIModule::CharacterAIModule(flecs::world &ecs)
.interval(0.1f)
.each([this](flecs::entity town, ActionNodeList &alist,
TownAI &ai, const TownNPCs &npcs) {
Ogre::Root::getSingleton().getWorkQueue()->addTask(
[this, town, &alist, npcs, &ai]() {
{
std::lock_guard<std::mutex> lock(
ecs_mutex);
ZoneScopedN("UpdateBlackboards");
OgreAssert(npcs.npcs.size() > 0, "npcs not crated");
updateBlackboards(town, alist,
npcs, ai);
}
Ogre::Root::getSingleton()
.getWorkQueue()
->addMainThreadTask([this, town,
&alist]() {
town.modified<TownAI>();
town.modified<TownNPCs>();
ECS::modified<
ActionNodeList>();
});
});
Ogre::Root::getSingleton().getWorkQueue()->addTask([this,
town,
&alist,
npcs,
&ai]() {
{
ZoneScopedN(
"UpdateBlackboards::Thread");
std::lock_guard<std::mutex> lock(
ecs_mutex);
updateBlackboards(town, alist, npcs,
ai);
}
Ogre::Root::getSingleton()
.getWorkQueue()
->addMainThreadTask([this, town,
&alist]() {
ZoneScopedN(
"UpdateBlackboards::MainThread");
town.modified<TownAI>();
town.modified<TownNPCs>();
ECS::modified<ActionNodeList>();
});
});
});
ecs.system<TownAI, TownNPCs>("PlanAI")
.kind(flecs::OnUpdate)
.interval(0.5f)
.each([&](flecs::entity town, TownAI &ai,
const TownNPCs &npcs) {
Ogre::Root::getSingleton().getWorkQueue()->addTask(
[this, town, npcs, &ai]() {
std::lock_guard<std::mutex> lock(
ecs_mutex);
buildPlans(town, npcs, ai);
Ogre::Root::getSingleton()
.getWorkQueue()
->addMainThreadTask([this,
town]() {
town.modified<TownAI>();
});
});
ZoneScopedN("PlanAI");
OgreAssert(npcs.npcs.size() > 0, "npcs not crated");
OgreAssert(ai.blackboards.size() > 0,
"blackboards not crated");
OgreAssert(ai.memory.size() > 0, "memory not crated");
Ogre::Root::getSingleton().getWorkQueue()->addTask([this,
town,
npcs,
&ai]() {
ZoneScopedN("PlanAI::Thread");
std::lock_guard<std::mutex> lock(ecs_mutex);
buildPlans(town, npcs, ai);
Ogre::Root::getSingleton()
.getWorkQueue()
->addMainThreadTask([this, town]() {
ZoneScopedN(
"PlanAI::MainThread");
town.modified<TownAI>();
});
});
});
static std::unordered_map<int, struct PlanExec> plan_exec;
ecs.system<const EngineData, TownNPCs, TownAI>("RunPLAN")
.kind(flecs::OnUpdate)
.each([&](flecs::entity town, const EngineData &eng,
TownNPCs &npcs, TownAI &ai) {
ZoneScopedN("RunPLAN");
OgreAssert(npcs.npcs.size() > 0, "npcs not crated");
OgreAssert(ai.blackboards.size() > 0,
"blackboards not crated");
OgreAssert(ai.memory.size() > 0, "memory not crated");
for (const auto &plans : ai.plans) {
if (plan_exec.find(plans.first) !=
plan_exec.end()) {
@@ -614,13 +688,19 @@ CharacterAIModule::CharacterAIModule(flecs::world &ecs)
// std::cout << " Goal: ";
plan.goal->goal.dump_bits();
for (const auto &action : plan.plan) {
ActionExec::PlanExecData data({
TownNPCs::NPCData &npc =
npcs.npcs.at(
plans.first),
plans.first);
Blackboard &bb =
ai.blackboards.at(
plans.first),
plans.first);
nlohmann::json &mem =
ai.memory.at(
plans.first),
plans.first);
ActionExec::PlanExecData data({
npc,
bb,
mem,
});
// TODO: executor factory is needed
@@ -712,25 +792,33 @@ static std::deque<PlanTask> plan_tasks;
void CharacterAIModule::buildPlans(flecs::entity town, const TownNPCs &npcs,
TownAI &ai)
{
ZoneScopedN("buildPlans");
OgreAssert(town.is_valid(), "Bad town entity");
std::lock_guard<std::mutex> lock(*ai.mutex);
auto planner = ai.planner;
if (plan_tasks.size() > 0) {
bool created = (plan_tasks.front())();
if (created) {
std::cout << plan_tasks.front().blackboard.index << " ";
std::cout << "Goal: "
<< plan_tasks.front().goal.get_name();
plan_tasks.front().goal.goal.dump_bits();
std::cout << std::endl;
std::cout << "Path: ";
for (auto &action : plan_tasks.front().plan.plan) {
OgreAssert(action, "No action");
std::cout << action->get_name() + " ";
ZoneTextF("%d: Goal: %s",
plan_tasks.front().blackboard.index,
plan_tasks.front().goal.get_name().c_str());
{
std::cout << plan_tasks.front().blackboard.index
<< " ";
std::cout << "Goal: "
<< plan_tasks.front().goal.get_name();
plan_tasks.front().goal.goal.dump_bits();
std::cout << std::endl;
std::cout << "Path: ";
for (auto &action :
plan_tasks.front().plan.plan) {
OgreAssert(action, "No action");
std::cout << action->get_name() + " ";
}
std::cout << " size: "
<< plan_tasks.front().plan.plan.size()
<< std::endl;
}
std::cout << " size: "
<< plan_tasks.front().plan.plan.size()
<< std::endl;
ai.plans[plan_tasks.front().blackboard.index].push_back(
plan_tasks.front().plan);
}
@@ -758,6 +846,7 @@ void CharacterAIModule::buildPlans(flecs::entity town, const TownNPCs &npcs,
void CharacterAIModule::createBlackboards(flecs::entity town,
const TownNPCs &npcs, TownAI &ai)
{
ZoneScopedN("createBlackboards");
OgreAssert(town.is_valid(), "Bad town entity");
std::lock_guard<std::mutex> lock(*ai.mutex);
for (auto it = npcs.npcs.begin(); it != npcs.npcs.end(); it++) {
@@ -871,6 +960,7 @@ void CharacterAIModule::updateBlackboards(flecs::entity town,
ActionNodeList &alist,
const TownNPCs &npcs, TownAI &ai)
{
ZoneScopedN("updateBlackboards");
std::lock_guard<std::mutex> lock(*ai.mutex);
OgreAssert(town.is_valid(), "Bad town entity");
alist.build();
@@ -1124,11 +1214,13 @@ void Blackboard::query_ai()
const TownNPCs &npcs = town.get<TownNPCs>();
const float distance = 10000.0f;
Ogre::Vector3 position(0, 0, 0);
if (npcs.npcs.at(index).e.is_valid())
position = npcs.npcs.at(index)
.e.get<CharacterBase>()
.mBodyNode->_getDerivedPosition();
else
if (npcs.npcs.at(index).e.is_valid() &&
npcs.npcs.at(index).e.has<CharacterBase>()) {
Ogre::SceneNode *n =
ECS::get<CharacterModule>().characterNodes.at(
npcs.npcs.at(index).e);
position = n->_getDerivedPosition();
} else
from_json(npcs.npcs.at(index).props["position"], position);
this->position = position;
ActionNodeList &alist = ECS::get_mut<ActionNodeList>();

View File

@@ -8,10 +8,13 @@
#include "TerrainModule.h"
#include "WaterModule.h"
#include "world-build.h"
#include "AnimationSystem.h"
#include <tracy/Tracy.hpp>
namespace ECS
{
CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
{
ZoneScoped;
ecs.module<CharacterAnimationModule>();
ecs.component<AnimationControl>();
ecs.import <EventModule>();
@@ -21,30 +24,37 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
ecs.system<const CharacterBase, AnimationControl>("HandleAnimations")
.kind(flecs::OnUpdate)
.each([this](flecs::entity e, const CharacterBase &ch,
AnimationControl &anim) {
AnimationControl &anim) {
ZoneScopedN("HandleAnimations");
if (!anim.configured) {
int i, j;
e.set<EventData>({});
ch.mBodyEnt->getSkeleton()->setBlendMode(
Ogre::ANIMBLEND_CUMULATIVE);
Ogre::SceneNode *n =
ECS::get<CharacterModule>()
.characterNodes.at(e);
Ogre::Entity *ent = static_cast<Ogre::Entity *>(
n->getAttachedObject(0));
ent->getSkeleton()->setBlendMode(
Ogre::ANIMBLEND_CUMULATIVE);
Ogre::AnimationStateSet *animStateSet =
ch.mBodyEnt->getAllAnimationStates();
ent->getAllAnimationStates();
const Ogre::AnimationStateMap &animMap =
animStateSet->getAnimationStates();
anim.mAnimationSystem =
new AnimationSystem(false);
ch.mBodyEnt->getSkeleton()
new AnimationSystem::AnimationSystem(
false);
ent->getSkeleton()
->getBone("Root")
->removeAllChildren();
for (auto it = animMap.begin();
it != animMap.end(); it++) {
Animation *animation = new Animation(
ch.mBodyEnt->getSkeleton(),
it->second,
ch.mBodyEnt->getSkeleton()
->getAnimation(
it->first),
e);
AnimationSystem::Animation *animation =
new AnimationSystem::Animation(
ent->getSkeleton(),
it->second,
ent->getSkeleton()
->getAnimation(
it->first));
#ifdef VDEBUG
std::cout
<< "animation: " << animNames[i]
@@ -93,18 +103,18 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
->end()
->state("swimming-edge-climb")
->animation("swimming-edge-climb")
->trigger(e, "end_of_climb", 0.99f, "animation:swimming-edge-climb:end")
->trigger_entity(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")
->trigger_entity(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")
->trigger_entity(e, "pass-character", 0.99f, "animation:pass-character:end")
->end()
->state("character-talk")
->animation("character-talk")
@@ -127,10 +137,13 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
/* clang-format on */
anim.mAnimationSystem
->get<AnimationNodeStateMachine>("main")
->get<AnimationSystem::
AnimationNodeStateMachine>(
"main")
->setAnimation("locomotion", true);
anim.mAnimationSystem
->get<AnimationNodeStateMachine>(
->get<AnimationSystem::
AnimationNodeStateMachine>(
"locomotion-state")
->setAnimation("idle", true);
anim.configured = true;
@@ -148,73 +161,25 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
.kind(flecs::OnUpdate)
.each([this](flecs::entity e, const EngineData &eng,
CharacterBase &ch, AnimationControl &anim) {
float delta = eng.delta;
ZoneScopedN("HandleAnimations1");
float delta = eng.delta;
// ch.mBoneMotion = Ogre::Vector3::ZERO;
if (!anim.mAnimationSystem)
return;
#if 0
ch.mBodyEnt->getSkeleton()->getBone("Root")->reset();
ch.mBodyEnt->getSkeleton()->getBone("Root")->setPosition(
Ogre::Vector3::ZERO);
#endif
bool result = anim.mAnimationSystem->addTime(delta);
Ogre::Vector3 rootMotion =
anim.mAnimationSystem->getRootMotionDelta();
ch.mBonePrevMotion = ch.mBoneMotion;
ch.mBoneMotion = rootMotion;
ch.mBodyEnt->_updateAnimation();
#if 0
{
ZoneScopedN("Ogre::Entity::_updateAnimation");
ch.mBodyEnt->_updateAnimation();
}
ch.mBodyEnt->getSkeleton()->getBone("Root")->setPosition(
Ogre::Vector3::ZERO);
#if 0
Ogre::Vector3 deltaMotion;
ch.mBodyEnt->_updateAnimation();
std::cout << "motion: " << ch.mBoneMotion << " "
<< rootMotion << " " << ch.mBonePrevMotion
<< std::endl;
rootMotion = ch.mBodyEnt->getSkeleton()
->getBone("Root")
->getPosition();
if (rootMotion.squaredLength() <
ch.mBoneMotion.squaredLength())
deltaMotion = rootMotion;
else
deltaMotion = rootMotion - ch.mBoneMotion;
ch.mBonePrevMotion = ch.mBoneMotion;
ch.mBoneMotion = deltaMotion;
#endif
// The value we get is interpolated value. When result is true it is new step
#if 0
Ogre::Vector3 offset = ch.mRootBone->getPosition();
ch.mBoneMotion = static_cast<RootMotionListener *>(
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
@@ -224,10 +189,6 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
<< " 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")
@@ -236,16 +197,24 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
.with<WaterReady>()
.each([this](flecs::entity e, const EngineData &eng,
CharacterBase &ch, CharacterVelocity &v) {
if (eng.delta < 0.0000001f)
ZoneScopedN("HandleRootMotionVelocity");
if (eng.delta < 0.0000001f)
return;
if (!ch.mBodyNode)
if (ECS::get<CharacterModule>().characterNodes.find(
e) ==
ECS::get<CharacterModule>().characterNodes.end())
return;
Ogre::Quaternion rot = ch.mBodyNode->getOrientation();
Ogre::Vector3 pos = ch.mBodyNode->getPosition();
const Ogre::SceneNode *n =
ECS::get<CharacterModule>().characterNodes.at(
e);
Ogre::Quaternion rot = n->getOrientation();
Ogre::Vector3 pos = n->getPosition();
Ogre::Vector3 boneMotion = ch.mBoneMotion;
v.velocity = Ogre::Vector3::ZERO;
if (eng.delta <= 0.005)
return;
float safeDelta =
Ogre::Math::Clamp(eng.delta, 0.001f, 0.99f);
Ogre::Math::Clamp(eng.delta, 0.005f, 0.99f);
#if 0
if (!e.has<CharacterInActuator>()) {
v.velocity = Ogre::Math::lerp(
@@ -273,41 +242,20 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
#if 0
v.velocity.y = 0.0f;
#endif
OgreAssert(v.velocity.squaredLength() < 1000.0f,
"Bad velocity setting " + Ogre::StringConverter::toString(safeDelta) + " " + Ogre::StringConverter::toString(boneMotion));
});
#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) {
ZoneScopedN("HandleRootMotion");
#if 0
if (!ch.mBodyNode)
return;
#endif
if (eng.delta < 0.0000001f)
return;
OgreAssert(eng.delta > 0.0f, "Zero delta");
@@ -324,23 +272,32 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
.without<Player>()
.each([](flecs::entity e, const Input &input,
const CharacterBase &ch, AnimationControl &anim) {
if (!anim.configured)
ZoneScopedNC("HandleNPCAnimations", 0xFF2020);
if (!anim.configured)
return;
if (!anim.mAnimationSystem)
return;
AnimationNodeStateMachine *state_machine =
anim.mAnimationSystem
->get<AnimationNodeStateMachine>(
"locomotion-state");
AnimationSystem::AnimationNodeStateMachine
*state_machine = anim.mAnimationSystem->get<
AnimationSystem::
AnimationNodeStateMachine>(
"locomotion-state");
Ogre::String current_state =
state_machine->getCurrentState();
Ogre::String next_state = "idle";
if (current_state != "treading_water" &&
ch.is_submerged)
e.has<InWater>())
next_state = "treading_water";
if (current_state != "idle" && !ch.is_submerged)
if (current_state != "idle" && !e.has<InWater>())
next_state = "idle";
state_machine->setAnimation(next_state);
{
ZoneScoped;
ZoneTextF("animation: next_state: %s %d %d",
next_state.c_str(),
(int)ch.is_submerged,
(int)e.has<InWater>());
}
});
ecs.system<const CharacterBase, const CharacterInActuator,
AnimationControl>("HandlePlayerAnimationsActuator")
@@ -349,15 +306,18 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
.each([](flecs::entity e, const CharacterBase &ch,
const CharacterInActuator &inact,
AnimationControl &anim) {
if (!anim.configured)
ZoneScopedN("HandlePlayerAnimationsActuator");
if (!anim.configured)
return;
AnimationNodeStateMachine *main_sm =
AnimationSystem::AnimationNodeStateMachine *main_sm =
anim.mAnimationSystem
->get<AnimationNodeStateMachine>(
->get<AnimationSystem::
AnimationNodeStateMachine>(
"main");
AnimationNodeStateMachine *actuator_sm =
AnimationSystem::AnimationNodeStateMachine *actuator_sm =
anim.mAnimationSystem
->get<AnimationNodeStateMachine>(
->get<AnimationSystem::
AnimationNodeStateMachine>(
"actuator-state");
Ogre::String current_state = main_sm->getCurrentState();
if (current_state != "actuator")
@@ -372,13 +332,15 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
.without<CharacterControlDisable>()
.each([](flecs::entity e, const CharacterBase &ch,
AnimationControl &anim) {
if (!anim.configured)
ZoneScopedN("HandlePlayerAnimationsNoActuator");
if (!anim.configured)
return;
if (!anim.mAnimationSystem)
return;
AnimationNodeStateMachine *main_sm =
AnimationSystem::AnimationNodeStateMachine *main_sm =
anim.mAnimationSystem
->get<AnimationNodeStateMachine>(
->get<AnimationSystem::
AnimationNodeStateMachine>(
"main");
Ogre::String current_state = main_sm->getCurrentState();
if (current_state != "locomotion")
@@ -393,12 +355,14 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
.without<CharacterControlDisable>()
.each([](flecs::entity e, const Input &input,
const CharacterBase &ch, AnimationControl &anim) {
if (!anim.configured)
ZoneScopedN("HandlePlayerAnimations");
if (!anim.configured)
return;
AnimationNodeStateMachine *state_machine =
anim.mAnimationSystem
->get<AnimationNodeStateMachine>(
"locomotion-state");
AnimationSystem::AnimationNodeStateMachine
*state_machine = anim.mAnimationSystem->get<
AnimationSystem::
AnimationNodeStateMachine>(
"locomotion-state");
Ogre::String current_state =
state_machine->getCurrentState();
bool controls_idle = input.motion.zeroLength();
@@ -474,6 +438,7 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
.each([](flecs::entity e, const Input &input,
const CharacterBase &ch, AnimationControl &anim,
CharacterInActuator &act) {
ZoneScopedN("HandlePlayerAnimations2");
bool controls_idle = input.motion.zeroLength();
if (!controls_idle) {
std::cout << "motion.z: "
@@ -547,7 +512,8 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
.kind(flecs::OnUpdate)
.with<Character>()
.each([](flecs::entity e, EventData &evt) {
for (auto ev : evt.events) {
ZoneScopedN("HandleEvents");
for (auto ev : evt.events) {
std::cout << "character event: " << ev.event
<< std::endl;
/* parse character events */
@@ -585,6 +551,7 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
int operator()(const std::vector<GameWorld::Parameter *> &args)
override
{
ZoneScoped;
GameWorld::ValueParameter<flecs::entity> *param_e =
static_cast<GameWorld::ValueParameter<
flecs::entity> *>(args[0]);
@@ -600,10 +567,11 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
true) {
const AnimationControl &control =
param_e->get().get<AnimationControl>();
AnimationNodeStateMachine *sm =
control.mAnimationSystem
->get<AnimationNodeStateMachine>(
param_node->get());
AnimationSystem::AnimationNodeStateMachine *sm =
control.mAnimationSystem->get<
AnimationSystem::
AnimationNodeStateMachine>(
param_node->get());
bool reset = false;
if (args.size() == 4) {
GameWorld::ValueParameter<bool>
@@ -622,6 +590,7 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
}
};
ECS::get_mut<GameWorld>().add_command<AnimationSetCommand>(
"set_animation_state");
"set_animation_state");
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -9,18 +9,21 @@
#include "PlayerActionModule.h"
#include "items.h"
#include "CharacterManagerModule.h"
#include <tracy/Tracy.hpp>
namespace ECS
{
void createNPCActionNodes(flecs::entity town, int index)
{
ZoneScoped;
TownNPCs &npcs = town.get_mut<TownNPCs>();
TownNPCs::NPCData &npc = npcs.npcs.at(index);
flecs::entity e = npc.e;
nlohmann::json npcprops = npc.props;
const CharacterBase &ch = e.get<CharacterBase>();
Ogre::Vector3 characterPos = ch.mBodyNode->_getDerivedPosition();
Ogre::Quaternion characterRot = ch.mBodyNode->_getDerivedOrientation();
Ogre::SceneNode *n = ECS::get<CharacterModule>().characterNodes.at(e);
Ogre::Vector3 characterPos = n->_getDerivedPosition();
Ogre::Quaternion characterRot = n->_getDerivedOrientation();
if (npc.actionNodes.size() > 0) {
int i;
for (i = 0; i < npc.actionNodes.size(); i++) {
@@ -86,12 +89,12 @@ void createNPCActionNodes(flecs::entity town, int index)
CharacterManagerModule::CharacterManagerModule(flecs::world &ecs)
{
ecs.module<CharacterManagerModule>();
ecs.import <CharacterModule>();
ecs.component<TownNPCs>();
ecs.import <CharacterModule>();
ecs.import <CharacterAnimationModule>();
ecs.import <PhysicsModule>();
ecs.import <PlayerActionModule>();
ecs.component<TownCharacterHolder>();
ecs.component<TownNPCs>();
ecs.component<LivesIn>();
ecs.system<TerrainItem, TownNPCs>("UpdateCharacters")
.kind(flecs::OnUpdate)
@@ -102,140 +105,154 @@ CharacterManagerModule::CharacterManagerModule(flecs::world &ecs)
.write<LivesIn>()
.each([this](flecs::entity town, TerrainItem &item,
TownNPCs &npcs) {
ZoneScopedN("UpdateCharacters");
if (!player.is_valid())
return;
if (!player.has<CharacterBase>())
return;
Ogre::Root::getSingleton().getWorkQueue()->addMainThreadTask([this,
town]() {
flecs::entity player =
ECS::get<CharacterManagerModule>()
.getPlayer();
if (!player.is_valid())
return;
if (!player.has<CharacterBase>())
return;
TownNPCs &npcs = town.get_mut<TownNPCs>();
Ogre::Vector3 cameraPos =
player.get<CharacterBase>()
.mBodyNode
->_getDerivedPosition();
for (auto &npc : npcs.npcs) {
int index = npc.first;
TownNPCs::NPCData &data = npc.second;
Ogre::Vector3 npcPosition =
data.position;
Ogre::Quaternion npcOrientation =
data.orientation;
if (cameraPos.squaredDistance(
npcPosition) < 10000.0f) {
if (!data.e.is_valid()) {
data.e = createCharacterData(
data.model,
data.position,
data.orientation);
data.e.add<LivesIn>(
town);
break;
}
}
if (cameraPos.squaredDistance(
npcPosition) > 22500.0f) {
if (data.e.is_valid()) {
data.e.destruct();
data.e =
flecs::entity();
break;
}
flecs::entity player =
ECS::get<CharacterManagerModule>().getPlayer();
if (!player.is_valid())
return;
if (!player.has<CharacterBase>())
return;
Ogre::Vector3 cameraPos =
ECS::get<Camera>()
.mCameraNode->_getDerivedPosition();
for (auto &npc : npcs.npcs) {
int index = npc.first;
TownNPCs::NPCData &data = npc.second;
Ogre::Vector3 npcPosition = data.position;
Ogre::Quaternion npcOrientation =
data.orientation;
if (cameraPos.squaredDistance(npcPosition) <
10000.0f) {
if (!data.e.is_valid()) {
data.e = createCharacterData(
data.modelFace,
data.modelHair,
data.modelTop,
data.modelBottom,
data.modelFeet,
data.position,
data.orientation);
data.e.add<LivesIn>(town);
break;
}
}
for (auto &npc : npcs.npcs) {
int index = npc.first;
TownNPCs::NPCData &data = npc.second;
Ogre::Vector3 npcPosition =
data.position;
Ogre::Quaternion npcOrientation =
data.orientation;
if (cameraPos.squaredDistance(
npcPosition) < 10000.0f) {
if (data.e.is_valid()) {
if (data.e.has<
CharacterBase>() &&
data.e.has<LivesIn>(
town))
createNPCActionNodes(
town,
index);
}
#if 0
if (cameraPos.squaredDistance(npcPosition) >
22500.0f) {
if (data.e.is_valid()) {
data.e.destruct();
data.e = flecs::entity();
break;
}
}
town.modified<TownNPCs>();
});
#endif
}
for (auto &npc : npcs.npcs) {
int index = npc.first;
TownNPCs::NPCData &data = npc.second;
Ogre::Vector3 npcPosition = data.position;
Ogre::Quaternion npcOrientation =
data.orientation;
if (cameraPos.squaredDistance(npcPosition) <
10000.0f) {
if (data.e.is_valid()) {
if (data.e.has<CharacterBase>() &&
data.e.has<LivesIn>(town))
createNPCActionNodes(
town, index);
}
}
}
town.modified<TownNPCs>();
});
}
flecs::entity
CharacterManagerModule::createPlayer(const Ogre::Vector3 &position,
const Ogre::Quaternion &rotation)
{
ZoneScoped;
static int count = 0;
OgreAssert(count == 0, "overspawn");
OgreAssert(!player.is_valid(), "Player already created");
player = ECS::get().entity("player");
OgreAssert(player.is_valid(), "Can't create player");
std::cout << "Begin player create" << std::endl;
player.add<Player>();
ECS::get_mut<CharacterModule>().createCharacter(
player, position, rotation, "normal-male.glb");
ECS::modified<CharacterModule>();
std::cout << "End player create" << std::endl;
count++;
return player;
{
ZoneScopedN("PlayerCreate");
player.add<Player>();
ECS::get_mut<CharacterModule>().createCharacter(
player, position, rotation, "male_Face.mesh",
"male_Hair001.mesh", "male_BodyTop.mesh",
"male_BodyBottom.mesh", "male_BodyFeet.mesh");
ECS::modified<CharacterModule>();
count++;
}
return player;
}
flecs::entity
CharacterManagerModule::createCharacterData(const Ogre::String model,
const Ogre::Vector3 &position,
const Ogre::Quaternion &rotation)
flecs::entity CharacterManagerModule::createCharacterData(
const Ogre::String &modelFace, const Ogre::String &modelHair,
const Ogre::String &modelTop, const Ogre::String &modelBottom,
const Ogre::String &modelFeet, const Ogre::Vector3 &position,
const Ogre::Quaternion &rotation)
{
ZoneScoped;
flecs::entity e = ECS::get().entity();
ECS::get_mut<CharacterModule>().createCharacter(e, position, rotation,
model);
modelFace, modelHair,
modelTop, modelBottom,
modelFeet);
ECS::modified<CharacterModule>();
return e;
}
void CharacterManagerModule::registerTownCharacters(flecs::entity town)
{
Ogre::MeshManager::getSingleton().load("normal-male.glb", "General");
Ogre::MeshManager::getSingleton().load("normal-female.glb", "General");
ZoneScoped;
{
Ogre::MeshPtr maleMesh = Ogre::MeshManager::getSingleton().load(
"normal-male.glb", "Characters");
Ogre::MeshPtr femaleMesh =
Ogre::MeshManager::getSingleton().load(
"normal-female.glb", "Characters");
}
Ogre::String props = StaticGeometryModule::getItemProperties(town);
nlohmann::json j = nlohmann::json::parse(props);
nlohmann::json npcs = nlohmann::json::array();
if (town.has<TownNPCs>())
return;
if (j.find("npcs") != j.end())
npcs = j["npcs"];
if (j.find("npcs") == j.end())
return;
npcs = j["npcs"];
std::cout << npcs.dump(4) << std::endl;
if (npcs.size() == 0)
return;
int index = 0;
std::map<int, TownNPCs::NPCData> npcMap;
for (auto &npc : npcs) {
const char *models[] = { "normal-male.glb",
"normal-female.glb" };
int sex = npc["sex"].get<int>();
Ogre::Vector3 npcPosition;
Ogre::Quaternion npcOrientation;
from_json(npc["position"], npcPosition);
from_json(npc["orientation"], npcOrientation);
Ogre::String model = models[sex];
TownNPCs::NPCData npcData;
npcData.e = flecs::entity();
npcData.model = model;
npcData.modelFace = npc["slot_face"].get<Ogre::String>();
npcData.modelHair = npc["slot_hair"].get<Ogre::String>();
npcData.modelTop = npc["slot_top"].get<Ogre::String>();
npcData.modelBottom = npc["slot_bottom"].get<Ogre::String>();
npcData.modelFeet = npc["slot_feet"].get<Ogre::String>();
npcData.orientation = npcOrientation;
npcData.position = npcPosition;
npcData.props = npc;
npcMap[index] = npcData;
index++;
}
OgreAssert(npcMap.size() > 0, "no npcs registered");
town.set<TownNPCs>({ npcMap });
}

View File

@@ -12,7 +12,11 @@ struct TownNPCs {
nlohmann::json props;
Ogre::Vector3 position;
Ogre::Quaternion orientation;
Ogre::String model;
Ogre::String modelFace;
Ogre::String modelHair;
Ogre::String modelTop;
Ogre::String modelBottom;
Ogre::String modelFeet;
std::vector<ActionNodeList::ActionNode> actionNodes;
};
@@ -25,8 +29,12 @@ struct CharacterManagerModule {
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,
flecs::entity createCharacterData(const Ogre::String &modelFace,
const Ogre::String &modelHair,
const Ogre::String &modelTop,
const Ogre::String &modelBottom,
const Ogre::String &modelFeet,
const Ogre::Vector3 &position,
const Ogre::Quaternion &rotation);
void removeCharacterData(int id);
flecs::entity getPlayer() const

View File

@@ -9,57 +9,174 @@
#include "CharacterAnimationModule.h"
#include "CharacterManagerModule.h"
#include "CharacterModule.h"
#include <tracy/Tracy.hpp>
namespace ECS
{
CharacterModule::CharacterModule(flecs::world &ecs)
{
ZoneScoped;
struct TriggerPhysicsChange {};
ecs.module<CharacterModule>();
static std::vector<Ogre::String> part_names;
const std::vector<Ogre::String> &groups =
Ogre::ResourceGroupManager::getSingleton().getResourceGroups();
if (part_names.size() == 0) {
int i;
for (i = 0; i < groups.size(); i++) {
std::vector<Ogre::String> names =
*Ogre::ResourceGroupManager::getSingleton()
.findResourceNames(groups[i],
"body_part_*.json");
part_names.insert(part_names.end(), names.begin(),
names.end());
}
}
body_parts = nlohmann::json::object();
for (auto &g : part_names) {
Ogre::String group = Ogre::ResourceGroupManager::getSingleton()
.findGroupContainingResource(g);
Ogre::DataStreamPtr stream =
Ogre::ResourceGroupManager::getSingleton().openResource(
g, group);
Ogre::String json = stream->getAsString();
nlohmann::json jdata = nlohmann::json::parse(json);
if (jdata.find("age") == jdata.end())
continue;
if (jdata.find("sex") == jdata.end())
continue;
if (jdata.find("slot") == jdata.end())
continue;
if (jdata.find("mesh") == jdata.end())
continue;
Ogre::String age = jdata["age"].get<Ogre::String>();
Ogre::String sex = jdata["sex"].get<Ogre::String>();
Ogre::String slot = jdata["slot"].get<Ogre::String>();
Ogre::String mesh = jdata["mesh"].get<Ogre::String>();
if (body_parts.find(age) == body_parts.end())
body_parts[age] = nlohmann::json::object();
if (body_parts[age].find(sex) == body_parts[age].end())
body_parts[age][sex] = nlohmann::json::object();
if (body_parts[age][sex].find(slot) ==
body_parts[age][sex].end())
body_parts[age][sex][slot] = nlohmann::json::array();
body_parts[age][sex][slot].push_back(mesh);
mesh_names.insert(mesh);
Ogre::MeshManager::getSingleton().load(mesh, "Characters");
}
std::cout << body_parts.dump(4) << std::endl;
ecs.module<CharacterModule>();
ecs.component<Character>();
ecs.component<Player>();
ecs.component<CharacterBase>()
.on_remove([this](flecs::entity e, CharacterBase &ch) {
// FIXME: clean up data
if (characterEntities.find(e) !=
characterEntities.end() ||
ZoneScoped;
if (characterEntitiesFace.find(e) !=
characterEntitiesFace.end() ||
characterNodes.find(e) != characterNodes.end()) {
characterEntities.erase(e);
// FIXME: clean up data
characterEntitiesFace.erase(e);
characterEntitiesTop.erase(e);
characterEntitiesBottom.erase(e);
characterEntitiesFeet.erase(e);
characterNodes.erase(e);
ECS::modified<CharacterModule>();
}
})
.on_add([this](flecs::entity e, CharacterBase &ch) {
if (characterNodes.find(e) == characterNodes.end()) {
OgreAssert(characterModels.find(e) !=
characterModels.end(),
ZoneScoped;
OgreAssert(characterModelsFace.find(e) !=
characterModelsFace.end(),
"no model set");
const EngineData &eng = ECS::get<EngineData>();
Ogre::SceneNode *bodyNode =
eng.mScnMgr->getRootSceneNode()
->createChildSceneNode();
Ogre::Entity *bodyEnt =
Ogre::Entity *faceEnt =
eng.mScnMgr->createEntity(
characterModels[e]);
characterModelsFace[e]);
bodyNode->attachObject(faceEnt);
characterNodes[e] = bodyNode;
characterEntities[e] = bodyEnt;
characterEntitiesFace[e] = faceEnt;
Ogre::Entity *hairEnt =
eng.mScnMgr->createEntity(
characterModelsHair[e]);
hairEnt->shareSkeletonInstanceWith(faceEnt);
bodyNode->attachObject(hairEnt);
characterEntitiesHair[e] = hairEnt;
Ogre::Entity *topEnt =
eng.mScnMgr->createEntity(
characterModelsTop[e]);
topEnt->shareSkeletonInstanceWith(faceEnt);
bodyNode->attachObject(topEnt);
characterEntitiesTop[e] = topEnt;
Ogre::Entity *bottomEnt =
eng.mScnMgr->createEntity(
characterModelsBottom[e]);
bottomEnt->shareSkeletonInstanceWith(faceEnt);
bodyNode->attachObject(bottomEnt);
characterEntitiesBottom[e] = bottomEnt;
Ogre::Entity *feetEnt =
eng.mScnMgr->createEntity(
characterModelsFeet[e]);
feetEnt->shareSkeletonInstanceWith(faceEnt);
bodyNode->attachObject(feetEnt);
characterEntitiesFeet[e] = feetEnt;
#if 0
if (characterModelsTop.find(e) !=
characterModelsTop.end()) {
Ogre::String skeletonName =
bodyEnt->getMesh()
->getSkeletonName();
Ogre::MeshPtr mesh =
Ogre::MeshManager::getSingleton()
.load(characterModelsTop
[e],
"General");
Ogre::String mname = mesh->getName();
mesh = mesh->clone(mname + "_clone");
OgreAssert(
mesh,
"No mesh " +
characterModelsTop[e]);
Ogre::String clothSkeleton =
mesh->getSkeletonName();
if (clothSkeleton != skeletonName) {
mesh->setSkeletonName(
skeletonName);
mesh->load();
if (Ogre::SkeletonManager::getSingleton()
.resourceExists(
clothSkeleton))
Ogre::SkeletonManager::
getSingleton()
.remove(clothSkeleton);
}
Ogre::Entity *characterTop =
eng.mScnMgr->createEntity(mesh);
characterTop->shareSkeletonInstanceWith(
bodyEnt);
bodyNode->attachObject(characterTop);
}
#endif
ECS::modified<CharacterModule>();
}
OgreAssert(characterOrientations.find(e) !=
characterOrientations.end(),
"Bad orientation/position");
ch.mBodyEnt = characterEntities[e];
ch.mBodyNode = characterNodes[e];
ch.mBodyNode->setOrientation(characterOrientations[e]);
ch.mBodyNode->setPosition(characterPositions[e]);
ch.mBodyNode->attachObject(ch.mBodyEnt);
OgreAssert(ch.mBodyEnt->getSkeleton()->hasBone("Root"),
"No root bone");
Ogre::SceneNode *bodyNode = characterNodes[e];
bodyNode->setOrientation(characterOrientations[e]);
bodyNode->setPosition(characterPositions[e]);
OgreAssert(
characterEntitiesFace[e]->getSkeleton()->hasBone(
"Root"),
"No root bone");
ch.mBoneMotion = Ogre::Vector3::ZERO;
ch.mBonePrevMotion = Ogre::Vector3::ZERO;
});
ecs.component<CharacterGravity>();
ecs.component<CharacterLocation>().on_set(
[this](flecs::entity e, CharacterLocation &loc) {
ZoneScoped;
characterOrientations[e] = loc.orientation;
characterPositions[e] = loc.position;
ECS::modified<CharacterModule>();
@@ -78,11 +195,13 @@ CharacterModule::CharacterModule(flecs::world &ecs)
ecs.system<EngineData, CharacterBase>("UpdateTimer")
.kind(flecs::OnUpdate)
.each([this](EngineData &eng, CharacterBase &ch) {
ch.mTimer += eng.delta;
ZoneScopedN("UpdateTimer");
ch.mTimer += eng.delta;
});
ecs.system<Input, Camera>("HandleInput")
.kind(flecs::OnUpdate)
.each([this](Input &input, Camera &camera) {
ZoneScopedN("HandleInput");
flecs::entity player =
ECS::get<CharacterManagerModule>().getPlayer();
if (!player.is_valid())
@@ -142,39 +261,24 @@ CharacterModule::CharacterModule(flecs::world &ecs)
else
input.fast = false;
input.control_prev = input.control;
float a = 0, b = 0, c = 0;
if (input.mouse_moved) {
updateCameraGoal(camera, -0.18f * input.mouse.x,
-0.12f * input.mouse.y, 0);
a += -0.18f * input.mouse.x;
b += -0.12f * input.mouse.y;
input.mouse_moved = false;
input.mouse.x = 0;
input.mouse.y = 0;
}
updateCameraGoal(camera, a, b, c);
}
if (input.wheel_moved) {
updateCameraGoal(camera, 0, 0,
-0.15f * input.wheel_y);
c += -0.15f * input.wheel_y;
input.wheel_moved = false;
input.wheel_y = 0;
}
updateCameraGoal(camera, a, b, c);
}
ECS::get().modified<ECS::Input>();
});
ecs.system<CharacterBase>()
.kind(flecs::OnUpdate)
.with<TerrainReady>()
.with<WaterReady>()
.with<InWater>()
.each([this](flecs::entity e, CharacterBase &ch) {
float full_subm = 2.0f;
Ogre::Vector3 pos = ch.mBodyNode->getPosition();
float current_subm = -Ogre::Math::Clamp(
pos.y + Ogre::Math::Sin(ch.mTimer * 0.13f +
130.0f) *
0.07f,
-full_subm, 0.0f);
if (current_subm > 0.9f)
ch.is_submerged = true;
else if (current_subm < 0.8f)
ch.is_submerged = false;
});
#define TURN_SPEED 500.0f // character turning in degrees per second
ecs.system<const Input, const Camera, CharacterBase>("UpdateBody")
.kind(flecs::OnUpdate)
@@ -184,7 +288,8 @@ CharacterModule::CharacterModule(flecs::world &ecs)
.without<CharacterControlDisable>()
.each([](flecs::entity e, const Input &input,
const Camera &camera, CharacterBase &ch) {
ch.mGoalDirection = Ogre::Vector3::ZERO;
ZoneScopedN("UpdateBody");
ch.mGoalDirection = Ogre::Vector3::ZERO;
float delta = e.world().delta_time();
if (!input.motion.zeroLength()) {
// calculate actually goal direction in world based on player's key directions
@@ -200,7 +305,9 @@ CharacterModule::CharacterModule(flecs::world &ecs)
ch.mGoalDirection.normalise();
Ogre::Quaternion toGoal =
ch.mBodyNode->getOrientation()
ECS::get<CharacterModule>()
.characterNodes.at(e)
->getOrientation()
.zAxis()
.getRotationTo(
ch.mGoalDirection);
@@ -225,7 +332,9 @@ CharacterModule::CharacterModule(flecs::world &ecs)
std::min<Ogre::Real>(
yawToGoal,
yawAtSpeed)); //yawToGoal = Math::Clamp<Real>(yawToGoal, 0, yawAtSpeed);
ch.mBodyNode->yaw(Ogre::Degree(yawToGoal));
ECS::get<CharacterModule>()
.characterNodes.at(e)
->yaw(Ogre::Degree(yawToGoal));
}
});
#if 0
@@ -253,94 +362,16 @@ CharacterModule::CharacterModule(flecs::world &ecs)
anim.configured = false;
});
#if 0
ecs.system<const EngineData, const CharacterLocation,
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, Body2Entity &b2e) {
CharacterBase &ch = e.ensure<CharacterBase>();
CharacterBody &body = e.ensure<CharacterBody>();
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 } });
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);
{
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 *>(
body.mCollisionShape)
->addChildShape(transform, shape);
btScalar masses[1] = { 0 };
btTransform principal;
static_cast<btCompoundShape *>(
body.mCollisionShape)
->calculatePrincipalAxisTransform(
masses, principal, inertia);
}
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);
eng.mWorld->attachCollisionObject(
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.configured = false;
// OgreAssert(body.mGhostObject->hasContactResponse(),
// "need contact response");
});
#endif
#define CAM_HEIGHT 1.6f // height of camera above character's center of mass
ecs.system<const EngineData, Camera, const CharacterBase>(
"UpdateCamera")
.kind(flecs::OnUpdate)
.with<Player>()
.with<GroundCheckReady>()
.each([](const EngineData &eng, Camera &camera,
.each([](flecs::entity e, const EngineData &eng, Camera &camera,
const CharacterBase &ch) {
float delta = eng.delta;
ZoneScopedN("UpdateCamera");
float delta = eng.delta;
if (!camera.configured) {
// create a pivot at roughly the character's shoulder
camera.mCameraPivot =
@@ -365,7 +396,9 @@ CharacterModule::CharacterModule(flecs::world &ecs)
} else {
// place the camera pivot roughly at the character's shoulder
camera.mCameraPivot->setPosition(
ch.mBodyNode->getPosition() +
ECS::get<CharacterModule>()
.characterNodes.at(e)
->getPosition() +
Ogre::Vector3::UNIT_Y * CAM_HEIGHT);
// move the camera smoothly to the goal
Ogre::Vector3 goalOffset =
@@ -411,6 +444,7 @@ CharacterModule::CharacterModule(flecs::world &ecs)
.with<Player>()
.without<GroundCheckReady>()
.each([](const EngineData &eng, CharacterBase &ch) {
ZoneScopedN("CheckGround");
#if 0
if (body.mGhostObject) {
btVector3 from =
@@ -491,6 +525,7 @@ void CharacterModule::updateCameraGoal(Camera &camera, Ogre::Real deltaYaw,
Ogre::Real deltaPitch,
Ogre::Real deltaZoom)
{
ZoneScoped;
static float canonDist = 0;
camera.mCameraPivot->yaw(Ogre::Degree(deltaYaw), Ogre::Node::TS_PARENT);
if (!(camera.mPivotPitch + deltaPitch > 25 && deltaPitch > 0) &&
@@ -545,10 +580,39 @@ void CharacterModule::updateCameraGoal(Camera &camera, Ogre::Real deltaYaw,
}
}
void CharacterModule::createCharacter(
flecs::entity e, const Ogre::Vector3 &position,
const Ogre::Quaternion &rotation, const Ogre::String &faceModel,
const Ogre::String &hairModel, const Ogre::String &topModel,
const Ogre::String &bottomModel, const Ogre::String &feetModel)
{
ZoneScoped;
if (e.has<CharacterBase>() || e.has<AnimationControl>())
return;
if (characterNodes.find(e) != characterNodes.end())
return;
e.set<CharacterLocation>({ rotation, position });
characterOrientations[e] = rotation;
characterPositions[e] = position;
characterModelsFace[e] = faceModel;
characterModelsHair[e] = hairModel;
characterModelsTop[e] = topModel;
characterModelsBottom[e] = bottomModel;
characterModelsFeet[e] = feetModel;
e.set<CharacterVelocity>(
{ { 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f } });
e.add<CharacterGravity>();
e.add<CharacterBuoyancy>();
e.add<Character>();
e.add<CharacterBase>();
e.add<AnimationControl>();
}
void applyWeightBasedScale(Ogre::Entity *ent,
const Ogre::String &targetBoneName,
const Ogre::Vector3 &scale)
{
ZoneScoped;
Ogre::MeshPtr mesh = ent->getMesh();
Ogre::SkeletonInstance *skel = ent->getSkeleton();
Ogre::Bone *targetBone = skel->getBone(targetBoneName);
@@ -631,25 +695,87 @@ void applyWeightBasedScale(Ogre::Entity *ent,
}
}
void CharacterModule::createCharacter(flecs::entity e,
const Ogre::Vector3 &position,
const Ogre::Quaternion &rotation,
const Ogre::String model)
void CharacterModule::remapMeshToMasterSkeleton(Ogre::MeshPtr clothMesh,
Ogre::MeshPtr masterMesh)
{
if (e.has<CharacterBase>() || e.has<AnimationControl>())
Ogre::SkeletonPtr masterSkel = masterMesh->getSkeleton();
Ogre::SkeletonPtr clothSkel = clothMesh->getSkeleton();
if (!masterSkel || !clothSkel)
return;
if (characterNodes.find(e) != characterNodes.end())
return;
e.set<CharacterLocation>({ rotation, position });
characterOrientations[e] = rotation;
characterPositions[e] = position;
characterModels[e] = model;
e.set<CharacterVelocity>(
{ { 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f } });
e.add<CharacterGravity>();
e.add<CharacterBuoyancy>();
e.add<Character>();
e.add<CharacterBase>();
e.add<AnimationControl>();
// 1. Create a Lookup Table: ClothIndex -> MasterIndex
std::map<unsigned short, unsigned short> indexMap;
for (unsigned short i = 0; i < clothSkel->getNumBones(); ++i) {
Ogre::String boneName = clothSkel->getBone(i)->getName();
if (masterSkel->hasBone(boneName)) {
indexMap[i] =
masterSkel->getBone(boneName)->getHandle();
} else {
indexMap[i] = 0; // Fallback to root
}
}
// 2. Update the Hardware Buffers for each SubMesh
for (unsigned short i = 0; i < clothMesh->getNumSubMeshes(); ++i) {
Ogre::SubMesh *sub = clothMesh->getSubMesh(i);
Ogre::VertexData *vdata = sub->useSharedVertices ?
clothMesh->sharedVertexData :
sub->vertexData;
// Find the element containing bone indices (VES_BLEND_INDICES)
const Ogre::VertexElement *idxElem =
vdata->vertexDeclaration->findElementBySemantic(
Ogre::VES_BLEND_INDICES);
if (!idxElem)
continue;
Ogre::HardwareVertexBufferSharedPtr vbuf =
vdata->vertexBufferBinding->getBuffer(
idxElem->getSource());
unsigned char *vertex = static_cast<unsigned char *>(
vbuf->lock(Ogre::HardwareBuffer::HBL_NORMAL));
for (size_t j = 0; j < vdata->vertexCount; ++j) {
unsigned char *pIndices;
idxElem->baseVertexPointerToElement(vertex, &pIndices);
// Remap the 4 indices (Ogre hardware skinning usually uses 4 bytes)
for (int k = 0; k < 4; ++k) {
pIndices[k] = static_cast<unsigned char>(
indexMap[pIndices[k]]);
}
vertex += vbuf->getVertexSize();
}
vbuf->unlock();
}
// 3. Link to Master Skeleton and rebuild
clothMesh->setSkeletonName(masterSkel->getName());
clothMesh->_compileBoneAssignments();
}
void CharacterModule::getSlotMeshes(const Ogre::String &age,
const Ogre::String &sex,
const Ogre::String &slotName,
std::vector<Ogre::String> &meshes)
{
OgreAssert(body_parts.find(age) != body_parts.end(), "bad age: " + age);
OgreAssert(body_parts[age].find(sex) != body_parts[age].end(),
"bad sex: " + sex);
OgreAssert(body_parts[age][sex].find(slotName) !=
body_parts[age][sex].end(),
"bad slot: " + slotName);
for (auto &slots : body_parts[age][sex][slotName])
meshes.push_back(slots.get<Ogre::String>());
}
void CharacterModule::preloadMeshes()
{
for (const auto &mesh : mesh_names) {
Ogre::Entity *ent =
ECS::get<EngineData>().mScnMgr->createEntity(mesh);
ECS::get<EngineData>().mScnMgr->destroyEntity(ent);
}
}
}

View File

@@ -1,6 +1,7 @@
#ifndef CHARACTER_MODULE_H_
#define CHARACTER_MODULE_H_
#include <flecs.h>
#include <nlohmann/json.hpp>
#include <Ogre.h>
#include "Components.h"
namespace ECS
@@ -18,12 +19,10 @@ struct Female {};
struct CharacterBase {
Ogre::String type;
float mTimer;
float mTimer;
Ogre::Vector3 mBoneMotion;
Ogre::Vector3 mBonePrevMotion;
Ogre::Vector3 mGoalDirection; // actual intended direction in world-space
Ogre::SceneNode *mBodyNode;
Ogre::Entity *mBodyEnt;
bool is_submerged;
};
struct CharacterLocation {
@@ -41,12 +40,37 @@ struct CharacterModule {
Ogre::Real deltaPitch, Ogre::Real deltaZoom);
void createCharacter(flecs::entity e, const Ogre::Vector3 &position,
const Ogre::Quaternion &rotation,
const Ogre::String model);
const Ogre::String &faceModel,
const Ogre::String &hairModel,
const Ogre::String &topModel,
const Ogre::String &bottomModel,
const Ogre::String &feetModel);
std::unordered_map<flecs::entity_t, Ogre::SceneNode *> characterNodes;
std::unordered_map<flecs::entity_t, Ogre::Entity *> characterEntities;
std::unordered_map<flecs::entity_t, Ogre::String> characterModels;
std::unordered_map<flecs::entity_t, Ogre::Entity *>
characterEntitiesFace;
std::unordered_map<flecs::entity_t, Ogre::Entity *>
characterEntitiesHair;
std::unordered_map<flecs::entity_t, Ogre::Entity *> characterEntitiesTop;
std::unordered_map<flecs::entity_t, Ogre::Entity *>
characterEntitiesBottom;
std::unordered_map<flecs::entity_t, Ogre::Entity *>
characterEntitiesFeet;
std::unordered_map<flecs::entity_t, Ogre::String> characterModelsFace;
std::unordered_map<flecs::entity_t, Ogre::String> characterModelsHair;
std::unordered_map<flecs::entity_t, Ogre::String> characterModelsTop;
std::unordered_map<flecs::entity_t, Ogre::String> characterModelsBottom;
std::unordered_map<flecs::entity_t, Ogre::String> characterModelsFeet;
std::unordered_map<flecs::entity_t, Ogre::Vector3> characterPositions;
std::unordered_map<flecs::entity_t, Ogre::Quaternion> characterOrientations;
std::unordered_map<flecs::entity_t, Ogre::Quaternion>
characterOrientations;
nlohmann::json body_parts;
std::set<Ogre::String> mesh_names;
void remapMeshToMasterSkeleton(Ogre::MeshPtr clothMesh,
Ogre::MeshPtr masterMesh);
void getSlotMeshes(const Ogre::String &age, const Ogre::String &sex,
const Ogre::String &slotName,
std::vector<Ogre::String> &meshes);
void preloadMeshes();
};
}
#endif

View File

@@ -5,6 +5,7 @@
#include "LuaData.h"
#include "EventModule.h"
#include "EventTriggerModule.h"
#include <tracy/Tracy.hpp>
struct TriggerBody {
void *data;
@@ -77,6 +78,7 @@ ECS::EventTriggerModule::EventTriggerModule(flecs::world &ecs)
.event(flecs::OnSet)
.each([](flecs::entity e, const EngineData &eng,
const EventTrigger &trigger) {
ZoneScoped;
e.set<EventTriggerData>({});
e.set<EventData>({});
});
@@ -84,13 +86,15 @@ ECS::EventTriggerModule::EventTriggerModule(flecs::world &ecs)
.event(flecs::OnSet)
.each([](flecs::entity e, const EventTrigger &trigger,
EventData &evt) {
evt.add(e, "actuator_created", e, e);
ZoneScoped;
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() &&
ZoneScoped;
if (e.parent().is_valid() &&
e.parent().has<EventData>()) {
bool added = false;
for (auto ev : evt.events) {

View File

@@ -29,6 +29,7 @@
#include "QuestModule.h"
#include "GUIModule.h"
#include "GUIModuleCommon.h"
#include <tracy/Tracy.hpp>
namespace ECS
{
struct GUIListener;
@@ -46,6 +47,7 @@ struct GUIListener : public Ogre::RenderTargetListener {
GUIListener()
: Ogre::RenderTargetListener()
{
ZoneScoped;
_midFont = createFont("midFont", "General",
"Jupiteroid-Regular.ttf", 24.0f);
_smallFont = createFont("smallFont", "General",
@@ -66,7 +68,8 @@ struct GUIListener : public Ogre::RenderTargetListener {
const Ogre::String &group,
const Ogre::String &ttfname, float fontSize)
{
Ogre::FontPtr ret =
ZoneScoped;
Ogre::FontPtr ret =
Ogre::FontManager::getSingleton().create(name, group);
ret->setType(Ogre::FontType::FT_TRUETYPE);
ret->setSource(ttfname);
@@ -79,10 +82,12 @@ struct GUIListener : public Ogre::RenderTargetListener {
void
preViewportUpdate(const Ogre::RenderTargetViewportEvent &evt) override
{
preview(evt);
ZoneScopedN("GUIModule::preViewportUpdate");
preview(evt);
}
void buttons_panel()
{
ZoneScoped;
bool enableDebugRender = ECS::get<EngineData>().enableDbgDraw;
ImVec2 size = ImGui::GetMainViewport()->Size;
float window_width = size.x * 0.2f;
@@ -120,7 +125,8 @@ struct GUIListener : public Ogre::RenderTargetListener {
}
void create_entity_node(const Ogre::String &name, int key)
{
Ogre::Entity *ent =
ZoneScoped;
Ogre::Entity *ent =
ECS::get().get<EngineData>().mScnMgr->createEntity(
name);
Ogre::SceneNode *pnode =
@@ -147,7 +153,8 @@ struct GUIListener : public Ogre::RenderTargetListener {
}
void buildings_editor()
{
int i;
ZoneScoped;
int i;
ImVec2 size = ImGui::GetMainViewport()->Size;
float window_width = size.x * 0.2f;
if (window_width > panel_width)
@@ -174,7 +181,8 @@ struct GUIListener : public Ogre::RenderTargetListener {
}
void position_editor(Ogre::SceneNode *node)
{
Ogre::Vector3 position = node->getPosition();
ZoneScoped;
Ogre::Vector3 position = node->getPosition();
float v[3] = { position.x, position.y, position.z };
ImGui::InputFloat3("position", v);
position.x = v[0];
@@ -184,7 +192,8 @@ struct GUIListener : public Ogre::RenderTargetListener {
}
void orientation_editor(Ogre::SceneNode *node)
{
Ogre::Quaternion q = node->getOrientation();
ZoneScoped;
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();
@@ -203,7 +212,8 @@ struct GUIListener : public Ogre::RenderTargetListener {
}
void attachments_editor(Ogre::SceneNode *node)
{
const Ogre::SceneNode::ObjectMap &pmap =
ZoneScoped;
const Ogre::SceneNode::ObjectMap &pmap =
node->getAttachedObjects();
int i;
for (i = 0; i < pmap.size(); i++) {
@@ -217,6 +227,7 @@ struct GUIListener : public Ogre::RenderTargetListener {
}
Ogre::Vector2 projectToScreen(const Ogre::Vector3 &worldPoint)
{
ZoneScoped;
ImVec2 size = ImGui::GetMainViewport()->Size;
float width = size.x;
float height = size.y;
@@ -241,7 +252,8 @@ struct GUIListener : public Ogre::RenderTargetListener {
}
void preview(const Ogre::RenderTargetViewportEvent &evt)
{
int i;
ZoneScoped;
int i;
Ogre::ImGuiOverlay::NewFrame();
if (ECS::get().get<EngineData>().startupDelay > 0.0f &&
@@ -742,6 +754,7 @@ GUIModule::GUIModule(flecs::world &ecs)
void GUIModule::configure()
{
ZoneScoped;
ECS::get().set<GUIData>({ nullptr, {}, nullptr });
const RenderWindow &window = ECS::get<RenderWindow>();
GUIData &gui = ECS::get_mut<GUIData>();

View File

@@ -28,12 +28,14 @@
#include "QuestModule.h"
#include "world-build.h"
#include "physics.h"
#include <tracy/Tracy.hpp>
namespace ECS
{
static flecs::world ecs;
void setup_minimal()
{
ZoneScoped;
ecs.component<EngineData>().add(flecs::Singleton);
ecs.component<GameData>().add(flecs::Singleton);
ecs.component<Input>().add(flecs::Singleton);
@@ -50,6 +52,7 @@ void setup_minimal()
void setupExteriorScene(Ogre::SceneManager *scnMgr, Ogre::SceneNode *cameraNode,
Ogre::Camera *camera, Ogre::RenderWindow *window)
{
ZoneScoped;
std::cout << "Setup GameData\n";
setup_minimal();
@@ -92,19 +95,19 @@ void setupExteriorScene(Ogre::SceneManager *scnMgr, Ogre::SceneNode *cameraNode,
std::cout << "ground check ready\n";
#endif
});
ecs.system<EngineData>("CheckStatus")
#ifdef VDEBUG
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(),
#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>();
@@ -112,6 +115,7 @@ void setupExteriorScene(Ogre::SceneManager *scnMgr, Ogre::SceneNode *cameraNode,
ecs.observer<GameState>("Game_Start_Scen_Startup")
.event(flecs::OnAdd)
.each([](GameState &game) {
ZoneScopedN("Game_Start_Scen_Startup");
ECS::get().add<WaterSurface>();
ECS::get().set<Sun>(
{ nullptr, nullptr, nullptr, nullptr });
@@ -136,6 +140,7 @@ void setupExteriorScene(Ogre::SceneManager *scnMgr, Ogre::SceneNode *cameraNode,
.kind(flecs::OnUpdate)
.interval(0.5f)
.each([&](Terrain &mterrain, GameState &game) {
ZoneScopedN("SpawnPlayer");
flecs::entity player =
ECS::get<CharacterManagerModule>().getPlayer();
if (!player.is_valid()) {
@@ -182,6 +187,7 @@ void setupExteriorScene(Ogre::SceneManager *scnMgr, Ogre::SceneNode *cameraNode,
.kind(flecs::OnUpdate)
.interval(0.5f)
.each([&](GameState &game) {
ZoneScopedN("NewHame");
flecs::entity player =
ECS::get<CharacterManagerModule>()
.getPlayer();
@@ -194,6 +200,8 @@ void setupExteriorScene(Ogre::SceneManager *scnMgr, Ogre::SceneNode *cameraNode,
new_game_run.destruct();
}
});
ecs.get_mut<CharacterModule>().preloadMeshes();
ecs.modified<CharacterModule>();
std::cout << "scene setup done" << std::endl;
}
void setupInteriorScene(Ogre::SceneManager *scnMgr, Ogre::SceneNode *cameraNode,
@@ -210,6 +218,7 @@ void setupInventoryScene(Ogre::SceneManager *scnMgr,
void setupEditor(Ogre::SceneManager *scnMgr, Ogre::SceneNode *cameraNode,
Ogre::Camera *camera, Ogre::RenderWindow *window)
{
ZoneScoped;
setup_minimal();
ecs.component<RenderWindow>().add(flecs::Singleton);
ecs.component<EditorSceneSwitch>().add(flecs::Singleton);
@@ -245,19 +254,19 @@ void setupEditor(Ogre::SceneManager *scnMgr, Ogre::SceneNode *cameraNode,
std::cout << "ground check ready\n";
#endif
});
ecs.system<EngineData>("CheckStatus")
#ifdef VDEBUG
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(),
#endif
ecs.set<EngineData>({ scnMgr, 0.0f, 5.0f, (int)window->getWidth(),
(int)window->getHeight(), false });
ecs.set<Camera>({ cameraNode, camera, false });
#if 0
@@ -286,7 +295,10 @@ void setupEditor(Ogre::SceneManager *scnMgr, Ogre::SceneNode *cameraNode,
void update(float delta)
{
ecs.progress(delta);
{
ZoneScopedN("ECS");
ecs.progress(delta);
}
}
flecs::world get()
{

View File

@@ -1,945 +0,0 @@
#include <OgreFileSystemLayer.h>
#include "GameData.h"
#include "Components.h"
#include "GUIModuleCommon.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 "PlayerActionModule.h"
#include "QuestModule.h"
#include "LuaData.h"
#include "lua.hpp"
extern "C" {
int luaopen_lpeg(lua_State *L);
}
struct FooPosition {
Ogre::Vector3 value;
Ogre::Vector3 &get()
{
return value;
}
};
#if 0
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);
}
};
}
#endif
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)
{
}
int get_next_id()
{
next_id++;
return next_id;
}
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)
{
OgreAssert(id2entity[id].is_valid(), "Invalid entity");
return id2entity[id];
}
};
struct idmap idmap;
int LuaData::setup_handler()
{
luaL_checktype(L, 1, LUA_TFUNCTION);
lua_pushvalue(L, 1);
int ref = luaL_ref(L, LUA_REGISTRYINDEX);
setup_handlers.push_back(ref);
return 0;
}
int LuaData::call_handler(const Ogre::String &event)
{
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, -1);
lua_pushinteger(L, -1);
if (lua_pcall(L, 3, 0, 0) != LUA_OK) {
Ogre::LogManager::getSingleton().stream()
<< lua_tostring(L, -1);
OgreAssert(false, "Lua error");
}
}
return 0;
}
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) != LUA_OK) {
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)) {
luaL_error(
L,
"luaLibraryLoader: Expected string for first parameter");
}
std::string libraryFile = lua_tostring(L, 1);
std::cout << libraryFile << std::endl;
// In order to be compatible with the normal Lua file loader,
// translate '.' to the file system seperator character.
// In this case (An ogre resource) '/'
while (libraryFile.find('.') != std::string::npos)
libraryFile.replace(libraryFile.find('.'), 1, "/");
libraryFile += ".lua";
std::cout << libraryFile << std::endl;
Ogre::StringVectorPtr scripts =
Ogre::ResourceGroupManager::getSingleton().listResourceNames(
"LuaScripts", false);
std::vector<Ogre::String> &strings = *scripts;
for (i = 0; i < strings.size(); i++)
std::cout << strings[i] << std::endl;
if (0 && !Ogre::ResourceGroupManager::getSingleton()
.resourceExistsInAnyGroup(libraryFile)) {
// Could not find the file.
std::string errMessage = "\n no file '" + libraryFile +
"' found in Ogre resource archives.";
lua_pushstring(L, errMessage.c_str());
} else {
Ogre::DataStreamList streams =
Ogre::ResourceGroupManager::getSingleton().openResources(
"*.lua", "LuaScripts");
Ogre::DataStreamPtr stream =
Ogre::ResourceGroupManager::getSingleton().openResource(
libraryFile, "LuaScripts");
Ogre::String script = stream->getAsString();
if (luaL_loadbuffer(L, script.c_str(), script.length(),
libraryFile.c_str())) {
luaL_error(
L,
"Error loading library '%s' from resource archive.\n%s",
libraryFile.c_str(), lua_tostring(L, -1));
}
}
return 1;
}
struct LuaChildEventTrigger {
flecs::entity parent_e;
Ogre::Vector3 position;
float halfheight;
float radius;
Ogre::String event;
};
static void installLibraryLoader(lua_State *L)
{
// Insert the c++ func 'luaLibraryLoader' into package.loaders.
// Inserted at the start of the table in order to take precedence.
lua_getglobal(L, "table");
lua_getfield(L, -1, "insert");
lua_remove(L, -2); // table
lua_getglobal(L, "package");
lua_getfield(L, -1, "searchers");
lua_remove(L, -2); // package
lua_pushnumber(L, 1); // index where to insert into loaders table
lua_pushcfunction(L, luaLibraryLoader);
if (lua_pcall(L, 3, 0, 0))
Ogre::LogManager::getSingleton().stream() << lua_tostring(L, 1);
}
LuaData::LuaData()
: L(luaL_newstate())
{
luaopen_base(L);
luaopen_table(L);
luaopen_package(L);
luaL_requiref(L, "table", luaopen_table, 1);
lua_pop(L, 1);
luaL_requiref(L, "math", luaopen_math, 1);
lua_pop(L, 1);
luaL_requiref(L, "package", luaopen_package, 1);
lua_pop(L, 1);
luaL_requiref(L, "string", luaopen_string, 1);
lua_pop(L, 1);
luaL_requiref(L, "io", luaopen_io, 1);
lua_pop(L, 1);
luaL_requiref(L, "lpeg", luaopen_lpeg, 1);
lua_pop(L, 1);
#if 0
luaL_getsubtable(L, LUA_REGISTRYINDEX, LUA_PRELOAD_TABLE);
lua_pushcfunction(L, luaopen_lpeg);
lua_setfield(L, -2, "lpeg");
lua_pop(L, 1); // remove PRELOAD table
#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
#if 0
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();
});
#endif
lua_pushcfunction(L, [](lua_State *L) -> int {
OgreAssert(false, "Crash function called");
return 0;
});
lua_setglobal(L, "crash");
lua_pushcfunction(L, [](lua_State *L) -> int {
luaL_checktype(L, 1, LUA_TFUNCTION);
ECS::get<LuaBase>().mLua->setup_handler();
return 0;
});
lua_setglobal(L, "setup_handler");
lua_pushcfunction(L, [](lua_State *L) -> int {
luaL_checktype(L, 1, LUA_TSTRING);
if (lua_type(L, 2) == LUA_TFUNCTION)
luaL_checktype(L, 2, LUA_TFUNCTION);
else
luaL_checktype(L, 2, LUA_TTABLE);
ECS::get_mut<PlayerActionModule>().setupLuaActionHandler(L);
ECS::modified<PlayerActionModule>();
return 0;
});
lua_setglobal(L, "setup_action_handler");
lua_pushcfunction(L, [](lua_State *L) -> int {
luaL_checktype(L, 1, LUA_TSTRING);
luaL_checktype(L, 2, LUA_TTABLE);
ECS::get_mut<QuestModule>().addLuaQuest(L);
ECS::modified<QuestModule>();
return 0;
});
lua_setglobal(L, "add_quest");
lua_pushcfunction(L, [](lua_State *L) -> int {
// FIXME
return 0;
});
lua_setglobal(L, "setup_narration_handler");
lua_pushcfunction(L, [](lua_State *L) -> int {
int args = lua_gettop(L);
if (args < 1)
return 0;
ECS::get_mut<GUI>().choices.clear();
luaL_checktype(L, 1, LUA_TSTRING);
if (args > 1) {
luaL_checktype(L, 2, LUA_TTABLE);
lua_pushnil(L); /* first key */
while (lua_next(L, 2) != 0) {
Ogre::String s = lua_tostring(L, -1);
ECS::get_mut<GUI>().choices.push_back(s);
lua_pop(L, 1); /* remove value but keep key */
}
}
size_t len;
Ogre::String message(luaL_tolstring(L, 1, &len));
std::cout << "narrator message: " << message
<< " length: " << message.length() << std::endl;
if (message.length() == 0 && ECS::get_mut<GUI>().narrationBox) {
ECS::get_mut<GUI>().enabled = false;
ECS::get_mut<GUI>().grab = true;
ECS::get_mut<GUI>().grabChanged = true;
ECS::get_mut<GUI>().narrationText = message;
ECS::get_mut<GUI>().narrationBox = false;
ECS::modified<GUI>();
std::cout << "narration ended\n";
} else if (message.length() > 0) {
ECS::get_mut<GUI>().enabled = true;
ECS::get_mut<GUI>().grab = false;
ECS::get_mut<GUI>().grabChanged = true;
ECS::get_mut<GUI>().narrationText = message;
ECS::get_mut<GUI>().narrationBox = true;
ECS::modified<GUI>();
}
return 0;
});
lua_setglobal(L, "narrate");
lua_pushcfunction(L, [](lua_State *L) -> int {
// ECS::get_mut<GUI>().mainMenu = true;
lua_pushinteger(L, ECS::get<GUI>().narration_answer);
return 1;
});
lua_setglobal(L, "narration_get_answer");
lua_pushcfunction(L, [](lua_State *L) -> int {
// ECS::get_mut<GUI>().mainMenu = true;
ECS::get_mut<GUI>().enabled = true;
ECS::get_mut<GUI>().mainMenu = true;
ECS::get_mut<GUI>().grab = false;
ECS::get_mut<GUI>().grabChanged = true;
ECS::modified<GUI>();
return 0;
});
lua_setglobal(L, "main_menu");
lua_pushcfunction(L, [](lua_State *L) -> int {
luaL_checktype(L, 1, LUA_TSTRING);
luaL_checktype(L, 2, LUA_TNUMBER);
luaL_checktype(L, 3, LUA_TNUMBER);
luaL_checktype(L, 4, LUA_TNUMBER);
luaL_checktype(L, 5, LUA_TNUMBER);
Ogre::String what = lua_tostring(L, 1);
if (what == "boat") {
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(
"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;
});
lua_setglobal(L, "ecs_vehicle_set");
lua_pushcfunction(L, [](lua_State *L) -> int {
OgreAssert(lua_gettop(L) == 6, "bad parameters");
luaL_checktype(L, 1, LUA_TSTRING); // event
luaL_checktype(L, 2, LUA_TNUMBER); // x
luaL_checktype(L, 3, LUA_TNUMBER); // y
luaL_checktype(L, 4, LUA_TNUMBER); // z
luaL_checktype(L, 5, LUA_TNUMBER); // halfh
luaL_checktype(L, 6, LUA_TNUMBER); // radius
flecs::entity e = ECS::get().entity();
Ogre::String event = lua_tostring(L, 1);
float x = lua_tonumber(L, 2);
float y = lua_tonumber(L, 3);
float z = lua_tonumber(L, 4);
float h = lua_tonumber(L, 5);
float r = lua_tonumber(L, 6);
Ogre::Vector3 position(x, y, z);
e.set<EventTrigger>({ nullptr, position, h, r, event });
lua_pushinteger(L, idmap.add_entity(e));
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
luaL_checktype(L, 2, LUA_TSTRING); // event
luaL_checktype(L, 3, LUA_TNUMBER); // x
luaL_checktype(L, 4, LUA_TNUMBER); // y
luaL_checktype(L, 5, LUA_TNUMBER); // z
luaL_checktype(L, 6, LUA_TNUMBER); // halfh
luaL_checktype(L, 7, LUA_TNUMBER); // radius
int parent = lua_tointeger(L, 1);
std::cout << "parent: " << parent << std::endl;
flecs::entity parent_e = idmap.get_entity(parent);
Ogre::String event = lua_tostring(L, 2);
float x = lua_tonumber(L, 3);
float y = lua_tonumber(L, 4);
float z = lua_tonumber(L, 5);
float h = lua_tonumber(L, 6);
float r = lua_tonumber(L, 7);
Ogre::Vector3 position(x, y, z);
flecs::entity e = ECS::get().entity().child_of(parent_e);
e.set<LuaChildEventTrigger>(
{ parent_e, position, h, r, event });
lua_pushinteger(L, idmap.add_entity(e));
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
luaL_checktype(L, 2, LUA_TNUMBER);
luaL_checktype(L, 3, LUA_TNUMBER);
luaL_checktype(L, 4, LUA_TNUMBER);
luaL_checktype(L, 5, LUA_TNUMBER);
Ogre::String type = lua_tostring(L, 1);
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::UNIT_Y);
Ogre::Vector3 npcPos(x, y, z);
flecs::entity e =
ECS::get_mut<CharacterManagerModule>()
.createCharacterData(type, npcPos, orientation);
ECS::modified<CharacterManagerModule>();
lua_pushinteger(L, idmap.add_entity(e));
return 1;
});
lua_setglobal(L, "ecs_npc_set");
lua_pushcfunction(L, [](lua_State *L) -> int {
OgreAssert(lua_gettop(L) == 1, "Bad parameters");
luaL_checktype(L, 1, LUA_TSTRING); // type
const char *fileName = lua_tostring(L, 1);
ECS::get<EngineData>().mScnMgr->getRootSceneNode()->saveChildren(
fileName);
return 0;
});
lua_setglobal(L, "ecs_save_scene_debug");
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_TSTRING); // name
int object = lua_tointeger(L, 1);
flecs::entity object_e = idmap.get_entity(object);
const char *fileName = lua_tostring(L, 2);
Ogre::SceneNode *node = nullptr;
if (object_e.has<CharacterBase>())
node = object_e.get<CharacterBase>().mBodyNode;
else if (object_e.has<BoatBase>())
node = object_e.get<BoatBase>().mNode;
if (node)
node->saveChildren(fileName);
return 0;
});
lua_setglobal(L, "ecs_save_object_debug");
lua_pushcfunction(L, [](lua_State *L) -> int {
luaL_checktype(L, 1, LUA_TBOOLEAN); // object
ECS::get_mut<EngineData>().enableDbgDraw = lua_toboolean(L, 1);
ECS::modified<EngineData>();
return 0;
});
lua_setglobal(L, "ecs_set_debug_drawing");
lua_pushcfunction(L, [](lua_State *L) -> int {
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);
PhysicsModule::controlPhysics(object_e, false);
object_e.set<ParentSlot>({ parent_e, slot });
return 0;
});
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()
{
lua_close(L);
}
void LuaData::lateSetup()
{
#if 0
Ogre::DataStreamList streams =
Ogre::ResourceGroupManager::getSingleton().openResources(
"*.lua", "LuaScripts");
while (!streams.empty()) {
Ogre::DataStreamPtr s = streams.front();
std::cout << "stream: " << s->getAsString() << "\n";
streams.pop_front();
if (luaL_dostring(L, s->getAsString().c_str()) != LUA_OK) {
std::cout << "error: " << lua_tostring(L, -1) << "\n";
OgreAssert(false, "Script failure");
}
}
#endif
Ogre::DataStreamPtr stream =
Ogre::ResourceGroupManager::getSingleton().openResource(
"data.lua", "LuaScripts");
std::cout << "stream: " << stream->getAsString() << "\n";
if (luaL_dostring(L, stream->getAsString().c_str()) != LUA_OK) {
std::cout << "error: " << lua_tostring(L, -1) << "\n";
OgreAssert(false, "Script failure");
}
const char *lua_code = "\n\
function stuff()\n\
return 4\n\
end\n\
x = stuff()\n\
";
luaL_dostring(L, lua_code);
lua_getglobal(L, "x");
int x = lua_tonumber(L, 1);
std::cout << "lua: " << x << "\n";
}
LuaModule::LuaModule(flecs::world &ecs)
{
ecs.module<LuaModule>();
ecs.import <SlotsModule>();
ecs.import <VehicleManagerModule>();
ecs.import <PlayerActionModule>();
ecs.component<LuaChildEventTrigger>();
ecs.component<LuaBase>()
.on_add([](LuaBase &lua) {
lua.mLua = new LuaData;
lua.setup_called = false;
lua.startup_called = false;
})
.add(flecs::Singleton);
ecs.component<LuaEvent>().add(flecs::Singleton);
ecs.system<const EngineData, LuaBase>("LuaUpdate")
.kind(flecs::OnUpdate)
.each([](const EngineData &eng, LuaBase &lua) {
if (!lua.setup_called) {
lua.mLua->lateSetup();
lua.mLua->call_handler("setup");
lua.setup_called = true;
}
if (!lua.startup_called) {
if (eng.startupDelay <= 0.0f) {
lua.mLua->call_handler("startup");
lua.startup_called = true;
}
}
});
ecs.system<const EngineData, const LuaChildEventTrigger>(
"CreateChildTrigger")
.kind(flecs::OnUpdate)
.without<EventTrigger>()
.write<EventTrigger>()
.each([](flecs::entity e, const EngineData &env,
const LuaChildEventTrigger &lct) {
Ogre::SceneNode *parentNode = nullptr;
flecs::entity parent_e = lct.parent_e;
if (parent_e.has<CharacterBase>()) {
parentNode =
parent_e.get<CharacterBase>().mBodyNode;
OgreAssert(
parent_e.get<CharacterBase>().mBodyNode,
"bad node");
} else if (parent_e.has<BoatBase>()) {
parentNode = parent_e.get<BoatBase>().mNode;
OgreAssert(parent_e.get<BoatBase>().mNode,
"bad node");
} else
return;
EventTrigger &trigger = e.ensure<EventTrigger>();
OgreAssert(parentNode, "bad parent");
trigger.position = lct.position;
trigger.halfheight = lct.halfheight;
trigger.radius = lct.radius;
trigger.event = lct.event;
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

@@ -0,0 +1,13 @@
project(LuaModule)
find_package(OGRE REQUIRED COMPONENTS Bites Bullet Paging Terrain Overlay CONFIG)
find_package(flecs REQUIRED CONFIG)
find_package(Tracy REQUIRED CONFIG)
add_library(luamodule STATIC LuaData.cpp)
target_include_directories(luamodule PUBLIC .)
target_link_libraries(luamodule PUBLIC
lua
OgreMain
flecs::flecs_static
PRIVATE GameData Tracy::TracyClient
)

View File

@@ -0,0 +1,914 @@
#include "lua.hpp"
#include <OgreFileSystemLayer.h>
#include "GameData.h"
#include "Components.h"
#include "GUIModuleCommon.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 "PlayerActionModule.h"
#include "QuestModule.h"
#include "LuaData.h"
#include <tracy/Tracy.hpp>
extern "C" {
int luaopen_lpeg(lua_State *L);
}
struct FooPosition {
Ogre::Vector3 value;
Ogre::Vector3 &get()
{
return value;
}
};
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)
{
}
int get_next_id()
{
next_id++;
return next_id;
}
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)
{
OgreAssert(id2entity[id].is_valid(), "Invalid entity");
return id2entity[id];
}
};
struct idmap idmap;
int LuaData::setup_handler()
{
luaL_checktype(L, 1, LUA_TFUNCTION);
lua_pushvalue(L, 1);
int ref = luaL_ref(L, LUA_REGISTRYINDEX);
setup_handlers.push_back(ref);
return 0;
}
int LuaData::call_handler(const Ogre::String &event)
{
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, -1);
lua_pushinteger(L, -1);
if (lua_pcall(L, 3, 0, 0) != LUA_OK) {
Ogre::LogManager::getSingleton().stream()
<< lua_tostring(L, -1);
OgreAssert(false, "Lua error");
}
}
return 0;
}
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) != LUA_OK) {
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)) {
luaL_error(
L,
"luaLibraryLoader: Expected string for first parameter");
}
std::string libraryFile = lua_tostring(L, 1);
std::cout << libraryFile << std::endl;
// In order to be compatible with the normal Lua file loader,
// translate '.' to the file system seperator character.
// In this case (An ogre resource) '/'
while (libraryFile.find('.') != std::string::npos)
libraryFile.replace(libraryFile.find('.'), 1, "/");
libraryFile += ".lua";
std::cout << libraryFile << std::endl;
Ogre::StringVectorPtr scripts =
Ogre::ResourceGroupManager::getSingleton().listResourceNames(
"LuaScripts", false);
std::vector<Ogre::String> &strings = *scripts;
for (i = 0; i < strings.size(); i++)
std::cout << strings[i] << std::endl;
if (0 && !Ogre::ResourceGroupManager::getSingleton()
.resourceExistsInAnyGroup(libraryFile)) {
// Could not find the file.
std::string errMessage = "\n no file '" + libraryFile +
"' found in Ogre resource archives.";
lua_pushstring(L, errMessage.c_str());
} else {
Ogre::DataStreamList streams =
Ogre::ResourceGroupManager::getSingleton().openResources(
"*.lua", "LuaScripts");
Ogre::DataStreamPtr stream =
Ogre::ResourceGroupManager::getSingleton().openResource(
libraryFile, "LuaScripts");
Ogre::String script = stream->getAsString();
if (luaL_loadbuffer(L, script.c_str(), script.length(),
libraryFile.c_str())) {
luaL_error(
L,
"Error loading library '%s' from resource archive.\n%s",
libraryFile.c_str(), lua_tostring(L, -1));
}
}
return 1;
}
struct LuaChildEventTrigger {
flecs::entity parent_e;
Ogre::Vector3 position;
float halfheight;
float radius;
Ogre::String event;
};
static void installLibraryLoader(lua_State *L)
{
// Insert the c++ func 'luaLibraryLoader' into package.loaders.
// Inserted at the start of the table in order to take precedence.
lua_getglobal(L, "table");
lua_getfield(L, -1, "insert");
lua_remove(L, -2); // table
lua_getglobal(L, "package");
lua_getfield(L, -1, "searchers");
lua_remove(L, -2); // package
lua_pushnumber(L, 1); // index where to insert into loaders table
lua_pushcfunction(L, luaLibraryLoader);
if (lua_pcall(L, 3, 0, 0))
Ogre::LogManager::getSingleton().stream() << lua_tostring(L, 1);
}
LuaData::LuaData()
: L(luaL_newstate())
{
luaopen_base(L);
luaopen_table(L);
luaopen_package(L);
luaL_requiref(L, "table", luaopen_table, 1);
lua_pop(L, 1);
luaL_requiref(L, "math", luaopen_math, 1);
lua_pop(L, 1);
luaL_requiref(L, "package", luaopen_package, 1);
lua_pop(L, 1);
luaL_requiref(L, "string", luaopen_string, 1);
lua_pop(L, 1);
luaL_requiref(L, "io", luaopen_io, 1);
lua_pop(L, 1);
luaL_requiref(L, "lpeg", luaopen_lpeg, 1);
lua_pop(L, 1);
#if 0
luaL_getsubtable(L, LUA_REGISTRYINDEX, LUA_PRELOAD_TABLE);
lua_pushcfunction(L, luaopen_lpeg);
lua_setfield(L, -2, "lpeg");
lua_pop(L, 1); // remove PRELOAD table
#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
#if 0
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();
});
#endif
lua_pushcfunction(L, [](lua_State *L) -> int {
OgreAssert(false, "Crash function called");
return 0;
});
lua_setglobal(L, "crash");
lua_pushcfunction(L, [](lua_State *L) -> int {
luaL_checktype(L, 1, LUA_TFUNCTION);
ECS::get<LuaBase>().mLua->setup_handler();
return 0;
});
lua_setglobal(L, "setup_handler");
lua_pushcfunction(L, [](lua_State *L) -> int {
luaL_checktype(L, 1, LUA_TSTRING);
if (lua_type(L, 2) == LUA_TFUNCTION)
luaL_checktype(L, 2, LUA_TFUNCTION);
else
luaL_checktype(L, 2, LUA_TTABLE);
ECS::get_mut<PlayerActionModule>().setupLuaActionHandler(L);
ECS::modified<PlayerActionModule>();
return 0;
});
lua_setglobal(L, "setup_action_handler");
lua_pushcfunction(L, [](lua_State *L) -> int {
luaL_checktype(L, 1, LUA_TSTRING);
luaL_checktype(L, 2, LUA_TTABLE);
ECS::get_mut<QuestModule>().addLuaQuest(L);
ECS::modified<QuestModule>();
return 0;
});
lua_setglobal(L, "add_quest");
lua_pushcfunction(L, [](lua_State *L) -> int {
// FIXME
return 0;
});
lua_setglobal(L, "setup_narration_handler");
lua_pushcfunction(L, [](lua_State *L) -> int {
int args = lua_gettop(L);
if (args < 1)
return 0;
ECS::get_mut<GUI>().choices.clear();
luaL_checktype(L, 1, LUA_TSTRING);
if (args > 1) {
luaL_checktype(L, 2, LUA_TTABLE);
lua_pushnil(L); /* first key */
while (lua_next(L, 2) != 0) {
Ogre::String s = lua_tostring(L, -1);
ECS::get_mut<GUI>().choices.push_back(s);
lua_pop(L, 1); /* remove value but keep key */
}
}
size_t len;
Ogre::String message(luaL_tolstring(L, 1, &len));
std::cout << "narrator message: " << message
<< " length: " << message.length() << std::endl;
if (message.length() == 0 && ECS::get_mut<GUI>().narrationBox) {
ECS::get_mut<GUI>().enabled = false;
ECS::get_mut<GUI>().grab = true;
ECS::get_mut<GUI>().grabChanged = true;
ECS::get_mut<GUI>().narrationText = message;
ECS::get_mut<GUI>().narrationBox = false;
ECS::modified<GUI>();
std::cout << "narration ended\n";
} else if (message.length() > 0) {
ECS::get_mut<GUI>().enabled = true;
ECS::get_mut<GUI>().grab = false;
ECS::get_mut<GUI>().grabChanged = true;
ECS::get_mut<GUI>().narrationText = message;
ECS::get_mut<GUI>().narrationBox = true;
ECS::modified<GUI>();
}
return 0;
});
lua_setglobal(L, "narrate");
lua_pushcfunction(L, [](lua_State *L) -> int {
// ECS::get_mut<GUI>().mainMenu = true;
lua_pushinteger(L, ECS::get<GUI>().narration_answer);
return 1;
});
lua_setglobal(L, "narration_get_answer");
lua_pushcfunction(L, [](lua_State *L) -> int {
// ECS::get_mut<GUI>().mainMenu = true;
ECS::get_mut<GUI>().enabled = true;
ECS::get_mut<GUI>().mainMenu = true;
ECS::get_mut<GUI>().grab = false;
ECS::get_mut<GUI>().grabChanged = true;
ECS::modified<GUI>();
return 0;
});
lua_setglobal(L, "main_menu");
lua_pushcfunction(L, [](lua_State *L) -> int {
luaL_checktype(L, 1, LUA_TSTRING);
luaL_checktype(L, 2, LUA_TNUMBER);
luaL_checktype(L, 3, LUA_TNUMBER);
luaL_checktype(L, 4, LUA_TNUMBER);
luaL_checktype(L, 5, LUA_TNUMBER);
Ogre::String what = lua_tostring(L, 1);
if (what == "boat") {
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(
"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;
});
lua_setglobal(L, "ecs_vehicle_set");
lua_pushcfunction(L, [](lua_State *L) -> int {
OgreAssert(lua_gettop(L) == 6, "bad parameters");
luaL_checktype(L, 1, LUA_TSTRING); // event
luaL_checktype(L, 2, LUA_TNUMBER); // x
luaL_checktype(L, 3, LUA_TNUMBER); // y
luaL_checktype(L, 4, LUA_TNUMBER); // z
luaL_checktype(L, 5, LUA_TNUMBER); // halfh
luaL_checktype(L, 6, LUA_TNUMBER); // radius
flecs::entity e = ECS::get().entity();
Ogre::String event = lua_tostring(L, 1);
float x = lua_tonumber(L, 2);
float y = lua_tonumber(L, 3);
float z = lua_tonumber(L, 4);
float h = lua_tonumber(L, 5);
float r = lua_tonumber(L, 6);
Ogre::Vector3 position(x, y, z);
e.set<EventTrigger>({ nullptr, position, h, r, event });
lua_pushinteger(L, idmap.add_entity(e));
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
luaL_checktype(L, 2, LUA_TSTRING); // event
luaL_checktype(L, 3, LUA_TNUMBER); // x
luaL_checktype(L, 4, LUA_TNUMBER); // y
luaL_checktype(L, 5, LUA_TNUMBER); // z
luaL_checktype(L, 6, LUA_TNUMBER); // halfh
luaL_checktype(L, 7, LUA_TNUMBER); // radius
int parent = lua_tointeger(L, 1);
std::cout << "parent: " << parent << std::endl;
flecs::entity parent_e = idmap.get_entity(parent);
Ogre::String event = lua_tostring(L, 2);
float x = lua_tonumber(L, 3);
float y = lua_tonumber(L, 4);
float z = lua_tonumber(L, 5);
float h = lua_tonumber(L, 6);
float r = lua_tonumber(L, 7);
Ogre::Vector3 position(x, y, z);
flecs::entity e = ECS::get().entity().child_of(parent_e);
e.set<LuaChildEventTrigger>(
{ parent_e, position, h, r, event });
lua_pushinteger(L, idmap.add_entity(e));
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>()) {
Ogre::SceneNode *bodyNode =
ECS::get<CharacterModule>().characterNodes.at(
object_e);
bodyNode->_setDerivedPosition(position);
bodyNode->_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) == 8, "Invalid parameters");
luaL_checktype(L, 1, LUA_TSTRING); // face
luaL_checktype(L, 2, LUA_TSTRING); // hair
luaL_checktype(L, 3, LUA_TSTRING); // top
luaL_checktype(L, 4, LUA_TSTRING); // bottom
luaL_checktype(L, 5, LUA_TSTRING); // feet
luaL_checktype(L, 6, LUA_TNUMBER);
luaL_checktype(L, 7, LUA_TNUMBER);
luaL_checktype(L, 8, LUA_TNUMBER);
luaL_checktype(L, 9, LUA_TNUMBER);
Ogre::String face = lua_tostring(L, 1);
Ogre::String hair = lua_tostring(L, 2);
Ogre::String top = lua_tostring(L, 3);
Ogre::String bottom = lua_tostring(L, 4);
Ogre::String feet = lua_tostring(L, 5);
float yaw = lua_tonumber(L, 8);
float x = lua_tonumber(L, 5);
float y = lua_tonumber(L, 6);
float z = lua_tonumber(L, 7);
Ogre::Quaternion orientation(Ogre::Radian(yaw),
Ogre::Vector3::UNIT_Y);
Ogre::Vector3 npcPos(x, y, z);
flecs::entity e =
ECS::get_mut<CharacterManagerModule>()
.createCharacterData(face, hair, top, bottom,
feet, npcPos, orientation);
ECS::modified<CharacterManagerModule>();
lua_pushinteger(L, idmap.add_entity(e));
return 1;
});
lua_setglobal(L, "ecs_npc_set");
lua_pushcfunction(L, [](lua_State *L) -> int {
OgreAssert(lua_gettop(L) == 1, "Bad parameters");
luaL_checktype(L, 1, LUA_TSTRING); // type
const char *fileName = lua_tostring(L, 1);
ECS::get<EngineData>().mScnMgr->getRootSceneNode()->saveChildren(
fileName);
return 0;
});
lua_setglobal(L, "ecs_save_scene_debug");
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_TSTRING); // name
int object = lua_tointeger(L, 1);
flecs::entity object_e = idmap.get_entity(object);
const char *fileName = lua_tostring(L, 2);
Ogre::SceneNode *node = nullptr;
if (object_e.has<CharacterBase>()) {
node = ECS::get<CharacterModule>().characterNodes.at(
object_e);
} else if (object_e.has<BoatBase>())
node = object_e.get<BoatBase>().mNode;
if (node)
node->saveChildren(fileName);
return 0;
});
lua_setglobal(L, "ecs_save_object_debug");
lua_pushcfunction(L, [](lua_State *L) -> int {
luaL_checktype(L, 1, LUA_TBOOLEAN); // object
ECS::get_mut<EngineData>().enableDbgDraw = lua_toboolean(L, 1);
ECS::modified<EngineData>();
return 0;
});
lua_setglobal(L, "ecs_set_debug_drawing");
lua_pushcfunction(L, [](lua_State *L) -> int {
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);
#if 0
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>();
#endif
OgreAssert(false, "Not implemented");
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);
PhysicsModule::controlPhysics(object_e, false);
object_e.set<ParentSlot>({ parent_e, slot });
return 0;
});
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()
{
lua_close(L);
}
void LuaData::lateSetup()
{
#if 0
Ogre::DataStreamList streams =
Ogre::ResourceGroupManager::getSingleton().openResources(
"*.lua", "LuaScripts");
while (!streams.empty()) {
Ogre::DataStreamPtr s = streams.front();
std::cout << "stream: " << s->getAsString() << "\n";
streams.pop_front();
if (luaL_dostring(L, s->getAsString().c_str()) != LUA_OK) {
std::cout << "error: " << lua_tostring(L, -1) << "\n";
OgreAssert(false, "Script failure");
}
}
#endif
Ogre::DataStreamPtr stream =
Ogre::ResourceGroupManager::getSingleton().openResource(
"data.lua", "LuaScripts");
std::cout << "stream: " << stream->getAsString() << "\n";
if (luaL_dostring(L, stream->getAsString().c_str()) != LUA_OK) {
std::cout << "error: " << lua_tostring(L, -1) << "\n";
OgreAssert(false, "Script failure");
}
const char *lua_code = "\n\
function stuff()\n\
return 4\n\
end\n\
x = stuff()\n\
";
luaL_dostring(L, lua_code);
lua_getglobal(L, "x");
int x = lua_tonumber(L, 1);
std::cout << "lua: " << x << "\n";
}
LuaModule::LuaModule(flecs::world &ecs)
{
ecs.module<LuaModule>();
ecs.import <SlotsModule>();
ecs.import <VehicleManagerModule>();
ecs.import <PlayerActionModule>();
ecs.component<LuaChildEventTrigger>();
ecs.component<LuaBase>()
.on_add([](LuaBase &lua) {
lua.mLua = new LuaData;
lua.setup_called = false;
lua.startup_called = false;
})
.add(flecs::Singleton);
ecs.component<LuaEvent>().add(flecs::Singleton);
ecs.system<const EngineData, LuaBase>("LuaUpdate")
.kind(flecs::OnUpdate)
.each([](const EngineData &eng, LuaBase &lua) {
if (!lua.setup_called) {
lua.mLua->lateSetup();
lua.mLua->call_handler("setup");
lua.setup_called = true;
}
if (!lua.startup_called) {
if (eng.startupDelay <= 0.0f) {
lua.mLua->call_handler("startup");
lua.startup_called = true;
}
}
});
ecs.system<const EngineData, const LuaChildEventTrigger>(
"CreateChildTrigger")
.kind(flecs::OnUpdate)
.without<EventTrigger>()
.write<EventTrigger>()
.each([](flecs::entity e, const EngineData &env,
const LuaChildEventTrigger &lct) {
Ogre::SceneNode *parentNode = nullptr;
flecs::entity parent_e = lct.parent_e;
if (parent_e.has<CharacterBase>()) {
parentNode =
ECS::get<CharacterModule>()
.characterNodes.at(parent_e);
OgreAssert(parentNode, "bad node");
} else if (parent_e.has<BoatBase>()) {
parentNode = parent_e.get<BoatBase>().mNode;
OgreAssert(parent_e.get<BoatBase>().mNode,
"bad node");
} else
return;
EventTrigger &trigger = e.ensure<EventTrigger>();
OgreAssert(parentNode, "bad parent");
trigger.position = lct.position;
trigger.halfheight = lct.halfheight;
trigger.radius = lct.radius;
trigger.event = lct.event;
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

@@ -19,6 +19,7 @@
#include "EventModule.h"
#include "TerrainModule.h"
#include "PhysicsModule.h"
#include <tracy/Tracy.hpp>
namespace ECS
{
struct PhysicsShape {
@@ -34,6 +35,7 @@ struct TriggerBody {
};
PhysicsModule::PhysicsModule(flecs::world &ecs)
{
ZoneScoped;
ecs.module<PhysicsModule>();
ecs.import <EventModule>();
ecs.import <EventTriggerModule>();
@@ -54,8 +56,18 @@ PhysicsModule::PhysicsModule(flecs::world &ecs)
ecs.component<PhysicsMeshPtr>();
ecs.component<PhysicsHeightfieldData>();
ecs.component<CharacterBody>();
ecs.component<TriggerBody>();
ecs.component<CharacterBody>().on_remove([](flecs::entity e,
CharacterBody &body) {
std::shared_ptr<JPH::Character> ch =
std::static_pointer_cast<JPH::Character>(body.ch);
if (ch) {
if (e.has<JPH::BodyID>())
e.remove<JPH::BodyID>();
JoltPhysicsWrapper::getSingleton().destroyCharacter(ch);
body.ch = nullptr;
}
});
ecs.component<TriggerBody>();
ecs.component<CharacterVelocity>();
ecs.component<WaterBody>().add(flecs::Singleton);
ecs.component<CachedMass>();
@@ -64,6 +76,7 @@ PhysicsModule::PhysicsModule(flecs::world &ecs)
ecs.system<EngineData, Physics>("physics_update")
.kind(PhysicsUpdate)
.each([&](EngineData &e, Physics &ph) {
ZoneScopedN("physics_update");
ph.physics->update(e.delta);
});
ecs.observer<const EngineData, PhysicsMeshName>(
@@ -74,6 +87,7 @@ PhysicsModule::PhysicsModule(flecs::world &ecs)
.write<PhysicsShape>()
.each([&](flecs::entity e, const EngineData &eng,
PhysicsMeshName &name) {
ZoneScopedN("create_shape_mesh_name");
Ogre::DefaultHardwareBufferManagerBase dmgr;
Ogre::MeshPtr mesh =
Ogre::MeshManager::getSingleton().getByName(
@@ -94,7 +108,8 @@ PhysicsModule::PhysicsModule(flecs::world &ecs)
.write<PhysicsShape>()
.each([&](flecs::entity e, const EngineData &eng,
PhysicsMeshPtr &meshPtr) {
Ogre::DefaultHardwareBufferManager dmgr;
ZoneScopedN("create_shape_mesh_ptr");
Ogre::DefaultHardwareBufferManager dmgr;
Ogre::MeshPtr mesh = meshPtr.mesh;
if (!mesh->isLoaded()) {
mesh->setHardwareBufferManager(&dmgr);
@@ -115,7 +130,8 @@ PhysicsModule::PhysicsModule(flecs::world &ecs)
.write<PhysicsShape>()
.each([&](flecs::entity e, const EngineData &eng,
PhysicsHeightfieldData &hfd) {
JPH::ShapeRefC shape =
ZoneScopedN("create_shape_heightfield");
JPH::ShapeRefC shape =
JoltPhysicsWrapper::getSingleton()
.createHeightfieldShape(
hfd.samples, hfd.offset,
@@ -136,7 +152,8 @@ PhysicsModule::PhysicsModule(flecs::world &ecs)
.each([&](flecs::entity e, const EngineData &eng,
const PhysicsShape &shape, const PhysicsNode &node,
const PhysicsBody &body) {
JPH::BodyID id =
ZoneScopedN("create_body_from_shape");
JPH::BodyID id =
JoltPhysicsWrapper::getSingleton().createBody(
shape.shape.GetPtr(), 0.0f, node.node,
(JPH::EMotionType)body.motion,
@@ -149,9 +166,13 @@ PhysicsModule::PhysicsModule(flecs::world &ecs)
ecs.observer<const JPH::BodyID>("remove_body")
.event(flecs::OnRemove)
.each([&](flecs::entity e, const JPH::BodyID &id) {
JoltPhysicsWrapper::getSingleton().removeBody(id);
ZoneScopedN("remove_body");
JoltPhysicsWrapper::getSingleton().removeBody(id);
if (e.has<CharacterBase>() || e.has<Character>())
return;
if (JoltPhysicsWrapper::getSingleton().bodyIsCharacter(
id))
return;
JoltPhysicsWrapper::getSingleton().destroyBody(id);
std::cout << "body destroyed" << std::endl;
});
@@ -164,10 +185,13 @@ PhysicsModule::PhysicsModule(flecs::world &ecs)
.write<JPH::BodyID>()
.each([](flecs::entity e, const EngineData &eng,
const CharacterBase &base) {
CharacterBody &b = e.ensure<CharacterBody>();
ZoneScopedN("SetupCharacterPh");
CharacterBody &b = e.ensure<CharacterBody>();
Ogre::SceneNode *n =
ECS::get<CharacterModule>().characterNodes.at(
e);
b.ch.reset(JoltPhysicsWrapper::getSingleton()
.createCharacter(base.mBodyNode,
1.75f, 0.23f));
.createCharacter(n, 1.75f, 0.23f));
if (!e.has<CharacterDisablePhysics>())
static_cast<JPH::Character *>(b.ch.get())
->AddToPhysicsSystem(
@@ -183,7 +207,8 @@ PhysicsModule::PhysicsModule(flecs::world &ecs)
.without<JPH::BodyID>()
.each([](flecs::entity e, const EngineData &eng,
const EventTrigger &trigger) {
JPH::ShapeRefC shape =
ZoneScopedN("CreateTriggerPhysics");
JPH::ShapeRefC shape =
JoltPhysicsWrapper::getSingleton()
.createCylinderShape(trigger.halfheight,
trigger.radius);
@@ -267,20 +292,23 @@ PhysicsModule::PhysicsModule(flecs::world &ecs)
ECS::modified<LuaEvent>();
});
});
// FIXME: convert to normal configure to prevent multiple instances
ecs.system<const EngineData>("init_water")
.kind(PhysicsPreUpdate)
.with<TerrainReady>()
.with<WaterAlmostReady>()
.without<WaterBody>()
.each([this](const EngineData &eng) {
ECS::get().set<WaterBody>({});
ZoneScopedN("init_water");
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>();
ZoneScopedN("update_water");
const WaterSurface &water = ECS::get<WaterSurface>();
body.inWater.clear();
JoltPhysicsWrapper::getSingleton().broadphaseQuery(
eng.delta,
@@ -295,8 +323,11 @@ PhysicsModule::PhysicsModule(flecs::world &ecs)
.with<InWater>()
.each([this](flecs::entity e, const JPH::BodyID &id,
const WaterBody &body) {
if (!body.isInWater(id))
ZoneScopedN("update_water_status1");
if (!body.isInWater(id)) {
e.remove<InWater>();
ZoneTextF("in water");
}
});
ecs.system<const JPH::BodyID, const WaterBody>("update_water_status2")
.kind(PhysicsPostUpdate)
@@ -305,8 +336,11 @@ PhysicsModule::PhysicsModule(flecs::world &ecs)
.without<InWater>()
.each([this](flecs::entity e, const JPH::BodyID &id,
const WaterBody &body) {
if (body.isInWater(id))
ZoneScopedN("update_water_status2");
if (body.isInWater(id)) {
e.add<InWater>();
ZoneTextF("not in water");
}
});
ecs.system<const CharacterBody, const WaterBody>(
"update_water_character1")
@@ -316,10 +350,13 @@ PhysicsModule::PhysicsModule(flecs::world &ecs)
.with<InWater>()
.each([this](flecs::entity e, const CharacterBody &ch,
const WaterBody &body) {
JPH::Character *chptr =
ZoneScopedN("update_water_character1");
JPH::Character *chptr =
static_cast<JPH::Character *>(ch.ch.get());
if (!body.isInWater(chptr->GetBodyID()))
if (!body.isInWater(chptr->GetBodyID())) {
e.remove<InWater>();
ZoneTextF("not in water");
}
});
ecs.system<const CharacterBody, const WaterBody>(
"update_water_character2")
@@ -329,10 +366,13 @@ PhysicsModule::PhysicsModule(flecs::world &ecs)
.without<InWater>()
.each([this](flecs::entity e, const CharacterBody &ch,
const WaterBody &body) {
JPH::Character *chptr =
ZoneScopedN("update_water_character2");
JPH::Character *chptr =
static_cast<JPH::Character *>(ch.ch.get());
if (body.isInWater(chptr->GetBodyID()))
if (body.isInWater(chptr->GetBodyID())) {
e.add<InWater>();
ZoneTextF("in water");
}
});
ecs.system<const EngineData, const BoatBase, const WaterBody,
const JPH::BodyID>("update_water_boat_enable")
@@ -342,7 +382,8 @@ PhysicsModule::PhysicsModule(flecs::world &ecs)
.each([this](flecs::entity e, const EngineData &eng,
const BoatBase &boat, const WaterBody &body,
const JPH::BodyID &id) {
if (!JoltPhysicsWrapper::getSingleton().isAdded(id))
ZoneScopedN("update_water_boat_enable");
if (!JoltPhysicsWrapper::getSingleton().isAdded(id))
JoltPhysicsWrapper::getSingleton().addBody(
id, JPH::EActivation::Activate);
});
@@ -355,7 +396,8 @@ PhysicsModule::PhysicsModule(flecs::world &ecs)
.each([this](flecs::entity e, const EngineData &eng,
const BoatBase &boat, const WaterBody &body,
const JPH::BodyID &id) {
if (!JoltPhysicsWrapper::getSingleton().isActive(id))
ZoneScopedN("update_water_boat_activation");
if (!JoltPhysicsWrapper::getSingleton().isActive(id))
JoltPhysicsWrapper::getSingleton().activate(id);
});
ecs.system<const EngineData, const BoatBase, const WaterBody,
@@ -368,7 +410,8 @@ PhysicsModule::PhysicsModule(flecs::world &ecs)
.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>();
ZoneScopedN("update_water_boat_buoyancy");
const WaterSurface &water = ECS::get<WaterSurface>();
float b = 1.0f, drag = 0.5f, adrag = 0.5f;
float level = 0.25f;
float my = JoltPhysicsWrapper::getSingleton()
@@ -425,7 +468,8 @@ PhysicsModule::PhysicsModule(flecs::world &ecs)
.with<CharacterBuoyancy>()
.each([this](flecs::entity e, const EngineData &eng,
const CharacterBody &ch, const WaterBody &body) {
JPH::Character *chptr =
ZoneScopedN("update_water_character_buoyancy");
JPH::Character *chptr =
static_cast<JPH::Character *>(ch.ch.get());
JPH::BodyID id = chptr->GetBodyID();
if (JoltPhysicsWrapper::getSingleton().isActive(id)) {
@@ -467,13 +511,15 @@ PhysicsModule::PhysicsModule(flecs::world &ecs)
eng.delta);
}
});
ecs.system<const EngineData, const CharacterBody>("UpdatePhysics")
ecs.system<const EngineData, const CharacterBody>(
"UpdateCharacterPhysicsState")
.kind(flecs::OnUpdate)
.with<CharacterUpdatePhysicsState>()
.write<CharacterUpdatePhysicsState>()
.each([](flecs::entity e, const EngineData &eng,
const CharacterBody &body) {
if (e.has<CharacterDisablePhysics>())
ZoneScopedN("UpdateCharacterPhysicsState");
if (e.has<CharacterDisablePhysics>())
PhysicsModule::controlPhysics(e, false);
else
PhysicsModule::controlPhysics(e, true);
@@ -489,8 +535,12 @@ PhysicsModule::PhysicsModule(flecs::world &ecs)
.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)
ZoneScopedN("HandleVelocity");
Ogre::SceneNode *n =
ECS::get<CharacterModule>().characterNodes.at(
e);
if (e.has<InWater>() &&
n->_getDerivedPosition().y > -0.5f)
e.remove<InWater>();
Ogre::Vector3 v = gr.velocity;
v.y = 0.0f;
@@ -519,7 +569,30 @@ PhysicsModule::PhysicsModule(flecs::world &ecs)
JoltPhysics::convert<JPH::Vec3>(v));
gr.velocity = Ogre::Vector3::ZERO;
});
ecs.system<const EngineData, CharacterBase, const CharacterBody,
ecs.system<CharacterBase>("HandleSubmerge")
.kind(flecs::OnUpdate)
.with<TerrainReady>()
.with<WaterReady>()
.with<InWater>()
.each([this](flecs::entity e, CharacterBase &ch) {
ZoneScopedNC("HandleSubmerge", 0xFF3030);
float full_subm = 2.0f;
Ogre::SceneNode *n =
ECS::get<CharacterModule>().characterNodes.at(
e);
Ogre::Vector3 pos = n->getPosition();
float current_subm = -Ogre::Math::Clamp(
pos.y + Ogre::Math::Sin(ch.mTimer * 0.13f +
130.0f) *
0.07f,
-full_subm, 0.0f);
if (current_subm > 0.9f)
ch.is_submerged = true;
else if (current_subm < 0.8f)
ch.is_submerged = false;
ZoneTextF("is submerged: %d", (int)ch.is_submerged);
});
ecs.system<const EngineData, CharacterBase, const CharacterBody,
CharacterVelocity>("HandleVelocityNoPhysics")
.kind(PhysicsPostUpdate)
.with<TerrainReady>()
@@ -529,28 +602,32 @@ PhysicsModule::PhysicsModule(flecs::world &ecs)
.each([this](flecs::entity e, const EngineData &eng,
CharacterBase &ch, const CharacterBody &body,
CharacterVelocity &gr) {
Ogre::Vector3 v = gr.velocity;
ZoneScopedNC("HandleVelocityNoPhysics", 0xFF4040);
Ogre::SceneNode *n =
ECS::get<CharacterModule>().characterNodes.at(
e);
Ogre::Vector3 v = gr.velocity;
// v.y = 0.0f;
ch.mBodyNode->_setDerivedPosition(
ch.mBodyNode->_getDerivedPosition() +
v * eng.delta);
n->_setDerivedPosition(n->_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(),
n->_getDerivedPosition(),
n->_getDerivedOrientation(),
false);
if (e.has<InWater>() &&
ch.mBodyNode->_getDerivedPosition().y > -0.5f) {
n->_getDerivedPosition().y > -0.5f) {
e.remove<InWater>();
ch.is_submerged = false;
}
if (!e.has<InWater>() && ch.is_submerged)
ZoneTextF("remove in water");
}
if (!e.has<InWater>() && ch.is_submerged) {
ch.is_submerged = false;
ZoneTextF("not submerged");
}
});
}
flecs::entity PhysicsModule::createTerrainChunkBody(Ogre::SceneNode *node,
@@ -559,6 +636,7 @@ flecs::entity PhysicsModule::createTerrainChunkBody(Ogre::SceneNode *node,
const Ogre::Vector3 &scale,
int sampleCount)
{
ZoneScoped;
flecs::entity e = ECS::get().entity();
e.set<PhysicsHeightfieldData>({ samples, offset, scale, sampleCount });
e.set<PhysicsBody>({ (uint32_t)JPH::EMotionType::Static,
@@ -569,32 +647,34 @@ flecs::entity PhysicsModule::createTerrainChunkBody(Ogre::SceneNode *node,
}
void PhysicsModule::controlPhysics(flecs::entity e, bool enable)
{
ZoneScoped;
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 {
Ogre::SceneNode *n =
ECS::get<CharacterModule>().characterNodes.at(
e);
if (!JoltPhysicsWrapper::getSingleton().isAdded(
e.get<JPH::BodyID>())) {
Ogre::Vector3 position =
n->_getDerivedPosition();
Ogre::Quaternion orientation =
n->_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>())
@@ -602,20 +682,23 @@ void PhysicsModule::controlPhysics(flecs::entity e, bool enable)
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>();
Ogre::SceneNode *n =
ECS::get<CharacterModule>().characterNodes.at(
e);
if (JoltPhysicsWrapper::getSingleton().isAdded(
e.get<JPH::BodyID>()))
JoltPhysicsWrapper::getSingleton().removeBody(
e.get<JPH::BodyID>());
Ogre::Vector3 position = n->_getDerivedPosition();
e.remove<InWater>();
}
}
}
bool PhysicsModule::raycastQuery(const Ogre::Vector3 &startPos,
const Ogre::Vector3 &endPos,
Ogre::Vector3 &position, JPH::BodyID &id)
{
ZoneScoped;
return JoltPhysicsWrapper::getSingleton().raycastQuery(startPos, endPos,
position, id);
}
@@ -627,6 +710,7 @@ void PhysicsModule::setDebugDraw(bool enable)
bool WaterBody::isInWater(const JPH::BodyID &id) const
{
ZoneScoped;
#if 0
flecs::entity e =
ECS::get().query_builder<const JPH::BodyID>().build().find(

View File

@@ -14,6 +14,7 @@
#include "LuaData.h"
#include "PhysicsModule.h"
#include "PlayerActionModule.h"
#include <tracy/Tracy.hpp>
namespace ECS
{
@@ -70,16 +71,19 @@ struct TestNarrativeHandler : GUI::NarrationHandler {
}
void finish() override
{
ZoneScoped;
_clear_narration();
}
void activate() override
{
ZoneScoped;
_narration("Greetings...", {});
std::cout << getPropsJSON().dump(4) << std::endl;
count = 0;
}
void event(const Ogre::String &evt) override
{
ZoneScoped;
if (evt == "narration_progress" ||
evt == "narration_answered") {
count++;
@@ -111,6 +115,7 @@ struct LuaNarrationHandler : GUI::NarrationHandler {
: ref(ref)
, L(L)
{
ZoneScoped;
lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
lua_pushlightuserdata(L, this);
lua_pushcclosure(
@@ -214,6 +219,7 @@ struct LuaNarrationHandler : GUI::NarrationHandler {
}
void finish() override
{
ZoneScoped;
lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
int type = lua_getfield(L, -1, "finish");
OgreAssert(type == LUA_TFUNCTION, "bad finish()");
@@ -227,6 +233,7 @@ struct LuaNarrationHandler : GUI::NarrationHandler {
}
void activate() override
{
ZoneScoped;
lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
int type = lua_getfield(L, -1, "activate");
OgreAssert(type == LUA_TFUNCTION, "bad activate()");
@@ -239,6 +246,7 @@ struct LuaNarrationHandler : GUI::NarrationHandler {
}
void event(const Ogre::String &evt) override
{
ZoneScoped;
lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
int type = lua_getfield(L, -1, "event");
OgreAssert(type == LUA_TFUNCTION, "bad event()");
@@ -256,6 +264,7 @@ struct SimpleWordHandler : PlayerActionModule::ActionWordHandler {
void operator()(int actor, flecs::entity town, int index,
const Ogre::String &word, int actionNode) override
{
ZoneScoped;
if (index >= 0) {
TestNarrativeHandler *handle =
OGRE_NEW TestNarrativeHandler();
@@ -280,10 +289,12 @@ struct SimpleWordHandler : PlayerActionModule::ActionWordHandler {
PlayerActionModule::PlayerActionModule(flecs::world &ecs)
{
ZoneScoped;
ecs.module<PlayerActionModule>();
ecs.import <CharacterManagerModule>();
ecs.component<ActionNodeList>()
.on_add([](flecs::entity e, ActionNodeList &alist) {
ZoneScoped;
alist.nodeMutex = std::make_shared<std::mutex>();
alist.setDirty();
alist.nodes.reserve(1000);
@@ -295,6 +306,7 @@ PlayerActionModule::PlayerActionModule(flecs::world &ecs)
ecs.system<ActionNodeList>("updateNodeList")
.kind(flecs::OnUpdate)
.each([](ActionNodeList &list) {
ZoneScopedN("updateNodeList");
if (list.isBusy())
return;
if (list.nodes.size() > 0) {
@@ -306,10 +318,12 @@ PlayerActionModule::PlayerActionModule(flecs::world &ecs)
ECS::get<CharacterManagerModule>()
.getPlayer();
if (player.is_valid()) {
Ogre::SceneNode *n =
ECS::get<CharacterModule>()
.characterNodes.at(
player);
Ogre::Vector3 playerPos =
player.get<CharacterBase>()
.mBodyNode
->_getDerivedPosition();
n->_getDerivedPosition();
list.UIquery(playerPos);
} else {
list.UIquery(cameraPos);
@@ -319,6 +333,7 @@ PlayerActionModule::PlayerActionModule(flecs::world &ecs)
ecs.system<ActionNodeList, const Input>("ActivateActionNode")
.kind(flecs::OnUpdate)
.each([this](ActionNodeList &list, const Input &input) {
ZoneScopedN("ActivateActionNode");
std::lock_guard<std::mutex> lock(*list.nodeMutex);
if (input.control & 32)
std::cout << "act pressed" << std::endl;
@@ -365,6 +380,7 @@ PlayerActionModule::PlayerActionModule(flecs::world &ecs)
ecs.system<const EngineData>("UpdateActivatedWords")
.kind(flecs::OnUpdate)
.each([this](const EngineData &eng) {
ZoneScopedN("UpdateActivatedWords");
for (auto it = activatedWords.begin();
it != activatedWords.end();) {
int ret = it->second->_update(eng.delta);
@@ -380,12 +396,14 @@ PlayerActionModule::PlayerActionModule(flecs::world &ecs)
void PlayerActionModule::addWordHandler(const Ogre::String &word,
ActionWordHandler *handler)
{
ZoneScoped;
actionWords.insert({ word, handler });
}
void PlayerActionModule::removeWordHandler(const Ogre::String &word,
ActionWordHandler *handler)
{
ZoneScoped;
for (auto it = actionWords.begin(); it != actionWords.end();) {
if (it->first == word && it->second == handler)
it = actionWords.erase(it);
@@ -418,6 +436,7 @@ struct TestActivatedWordHandler : PlayerActionModule::ActivatedWordHandler {
, state(0)
, delay(0)
{
ZoneScoped;
activeActors.insert(actor);
// dynamic nodes can disappear on us so to avoid that use a copy
anode = ECS::get<ActionNodeList>().dynamicNodes[actionNode];
@@ -450,6 +469,7 @@ out:;
}
void teleport(const Ogre::String &place)
{
ZoneScoped;
if (placeLocalOffset.find(place) == placeLocalOffset.end())
return;
std::cout << "local offset: " << placeLocalOffset[place]
@@ -461,10 +481,11 @@ out:;
anode.position +
anode.rotation * placeLocalOffset[place];
if (ch.is_valid() && ch.has<CharacterBase>()) {
ch.get<CharacterBase>()
.mBodyNode->_setDerivedOrientation(newRotation);
ch.get<CharacterBase>().mBodyNode->_setDerivedPosition(
newPosition);
Ogre::SceneNode *n =
ECS::get<CharacterModule>().characterNodes.at(
ch);
n->_setDerivedOrientation(newRotation);
n->_setDerivedPosition(newPosition);
}
if (actor >= 0) {
town.get_mut<TownNPCs>().npcs[actor].position =
@@ -476,6 +497,7 @@ out:;
}
int update(float delta)
{
ZoneScoped;
switch (state) {
case 0:
if (ECS::get<Input>().act)
@@ -579,6 +601,7 @@ out:;
}
void enter()
{
ZoneScoped;
delay = 0.0f;
state = 0;
ECS::get_mut<GUI>().enableActions = false;
@@ -587,6 +610,7 @@ out:;
}
void exit(int result)
{
ZoneScoped;
ch.remove<CharacterInActuator>();
ch.remove<CharacterControlDisable>();
PhysicsModule::controlPhysics(ch, true);
@@ -599,6 +623,7 @@ out:;
}
virtual ~TestActivatedWordHandler()
{
ZoneScoped;
activeActors.erase(actor);
}
};
@@ -609,6 +634,7 @@ struct LuaWordHandler : PlayerActionModule::ActionWordHandler {
void operator()(int actor, flecs::entity town, int index,
const Ogre::String &word, int actionNode) override
{
ZoneScoped;
lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
if (lua_type(L, -1) == LUA_TFUNCTION) {
luaL_checktype(L, -1, LUA_TFUNCTION);
@@ -662,6 +688,7 @@ struct LuaWordHandler : PlayerActionModule::ActionWordHandler {
void PlayerActionModule::addLuaWordHandler(const Ogre::String &word,
lua_State *L, int ref)
{
ZoneScoped;
struct LuaWordHandler *handler = OGRE_NEW LuaWordHandler;
handler->L = L;
handler->ref = ref;
@@ -671,6 +698,7 @@ void PlayerActionModule::addLuaWordHandler(const Ogre::String &word,
void PlayerActionModule::removeLuaWordHandler(const Ogre::String &word,
lua_State *L, int ref)
{
ZoneScoped;
for (auto it = actionWords.begin(); it != actionWords.end();) {
LuaWordHandler *handler =
static_cast<LuaWordHandler *>(it->second);
@@ -683,6 +711,7 @@ void PlayerActionModule::removeLuaWordHandler(const Ogre::String &word,
int PlayerActionModule::setupLuaActionHandler(lua_State *L)
{
ZoneScoped;
luaL_checktype(L, 1, LUA_TSTRING);
if (lua_type(L, 2) == LUA_TFUNCTION) {
luaL_checktype(L, 2, LUA_TFUNCTION);
@@ -703,6 +732,7 @@ int PlayerActionModule::setupLuaActionHandler(lua_State *L)
void PlayerActionModule::addActivatedWordHandler(const Ogre::String &word,
ActivatedWordHandler *handler)
{
ZoneScoped;
for (auto it = activatedWords.begin(); it != activatedWords.end();
it++) {
}
@@ -712,6 +742,7 @@ void PlayerActionModule::addActivatedWordHandler(const Ogre::String &word,
void PlayerActionModule::removeActivatedWordHandler(
const Ogre::String &word, ActivatedWordHandler *handler)
{
ZoneScoped;
for (auto it = activatedWords.begin(); it != activatedWords.end();) {
if (it->first == word && it->second == handler)
it = activatedWords.erase(it);
@@ -722,29 +753,42 @@ void PlayerActionModule::removeActivatedWordHandler(
void ActionNodeList::updateDynamicNodes()
{
ZoneScoped;
std::lock_guard<std::mutex> lock(*nodeMutex);
if (dynamicNodes.size() > nodes.size())
dynamicNodes.resize(nodes.size());
else {
dynamicNodes.clear();
dynamicNodes.insert(dynamicNodes.end(), nodes.begin(),
nodes.end());
{
ZoneScopedN("static");
ZoneTextF("before: dynamicNodes: %d nodes: %d",
(int)dynamicNodes.size(), (int)nodes.size());
// Do not constantly re-create static nodes
if (dynamicNodes.size() > nodes.size())
dynamicNodes.resize(nodes.size());
else if (dynamicNodes.size() < nodes.size()) {
dynamicNodes.clear();
dynamicNodes.insert(dynamicNodes.end(), nodes.begin(),
nodes.end());
ZoneTextF("updated: dynamicNodes: %d nodes: %d",
(int)dynamicNodes.size(), (int)nodes.size());
}
}
{
ZoneScopedN("dynamic");
ECS::get().query_builder<const TownNPCs>().each(
[this](flecs::entity town, const TownNPCs &npcs) {
for (auto it = npcs.npcs.begin();
it != npcs.npcs.end(); it++) {
dynamicNodes.insert(
dynamicNodes.end(),
it->second.actionNodes.begin(),
it->second.actionNodes.end());
}
});
}
ECS::get().query_builder<const TownNPCs>().each(
[this](flecs::entity town, const TownNPCs &npcs) {
for (auto it = npcs.npcs.begin(); it != npcs.npcs.end();
it++) {
dynamicNodes.insert(
dynamicNodes.end(),
it->second.actionNodes.begin(),
it->second.actionNodes.end());
}
});
dirty = true;
}
void ActionNodeList::build()
{
ZoneScoped;
std::lock_guard<std::mutex> lock(*nodeMutex);
indexObj = std::make_shared<ActionNodeList::indexObject>(dynamicNodes);
indexObj->index.buildIndex();
@@ -755,6 +799,7 @@ bool ActionNodeList::_query(const Ogre::Vector3 &position,
std::vector<size_t> &points,
std::vector<float> &distances)
{
ZoneScoped;
std::vector<size_t> tmppoints;
std::vector<float> tmpdistances;
points.clear();
@@ -784,6 +829,7 @@ bool ActionNodeList::query_ai(const Ogre::Vector3 &position, float distance,
std::vector<size_t> &points,
std::vector<float> &distances)
{
ZoneScoped;
std::lock_guard<std::mutex> lock(*nodeMutex);
std::vector<size_t> tmppoints;
std::vector<float> tmpdistances;
@@ -812,6 +858,7 @@ bool ActionNodeList::query_ai(const Ogre::Vector3 &position, float distance,
int ActionNodeList::addNode(ActionNode &node)
{
ZoneScoped;
std::lock_guard<std::mutex> lock(*nodeMutex);
int index = nodes.size();
nodes.push_back(node);
@@ -821,6 +868,7 @@ int ActionNodeList::addNode(ActionNode &node)
void ActionNodeList::removeNode(int index)
{
ZoneScoped;
std::lock_guard<std::mutex> lock(*nodeMutex);
nodes.erase(nodes.begin() + index);
dirty = true;
@@ -828,12 +876,14 @@ void ActionNodeList::removeNode(int index)
const ActionNodeList::UIData &ActionNodeList::getUIData()
{
ZoneScoped;
std::lock_guard<std::mutex> lock(*uidata.mutex);
return uidata;
}
void ActionNodeList::setUISelected(int selected)
{
ZoneScoped;
std::lock_guard<std::mutex> lock(*uidata.mutex);
uidata.selected = selected;
@@ -842,6 +892,7 @@ void ActionNodeList::setUISelected(int selected)
void ActionNodeList::setUIPoints(const std::vector<size_t> &points,
const std::vector<float> &distances)
{
ZoneScoped;
std::lock_guard<std::mutex> lock(*uidata.mutex);
uidata.points = points;
uidata.distances = distances;
@@ -849,6 +900,7 @@ void ActionNodeList::setUIPoints(const std::vector<size_t> &points,
void ActionNodeList::UIquery(const Ogre::Vector3 &position)
{
ZoneScoped;
bool needBuild = false;
{

View File

@@ -3,16 +3,21 @@
#include "GameData.h"
#include "GUIModuleCommon.h"
#include "QuestModule.h"
#include <tracy/Tracy.hpp>
namespace ECS
{
QuestModule::QuestModule(flecs::world &ecs)
{
ZoneScoped;
ecs.module<QuestModule>();
ecs.observer<GameState>("EnableQuests")
.event(flecs::OnAdd)
.each([this](GameState &game) {
ZoneScoped;
if (quest_update.is_valid())
return;
quest_update =
ECS::get()
.system<const EngineData>(
@@ -28,6 +33,7 @@ QuestModule::QuestModule(flecs::world &ecs)
ecs.observer<GameState>("DisableQuests")
.event(flecs::OnRemove)
.each([this](GameState &game) {
ZoneScoped;
if (quest_update.is_valid())
quest_update.destruct();
});
@@ -35,11 +41,13 @@ QuestModule::QuestModule(flecs::world &ecs)
void QuestModule::addQuest(Quest *quest)
{
ZoneScoped;
quests.insert(quest);
}
void QuestModule::removeQuest(Quest *quest)
{
ZoneScoped;
quests.erase(quests.find(quest));
}
struct LuaNarrationHandler : GUI::NarrationHandler {
@@ -49,6 +57,7 @@ struct LuaNarrationHandler : GUI::NarrationHandler {
: ref(ref)
, L(L)
{
ZoneScoped;
lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
lua_pushlightuserdata(L, this);
lua_pushcclosure(
@@ -125,6 +134,7 @@ struct LuaNarrationHandler : GUI::NarrationHandler {
}
void finish() override
{
ZoneScoped;
lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
int type = lua_getfield(L, -1, "finish");
OgreAssert(type == LUA_TFUNCTION, "bad finish()");
@@ -138,6 +148,7 @@ struct LuaNarrationHandler : GUI::NarrationHandler {
}
void activate() override
{
ZoneScoped;
lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
int type = lua_getfield(L, -1, "activate");
OgreAssert(type == LUA_TFUNCTION, "bad activate()");
@@ -150,6 +161,7 @@ struct LuaNarrationHandler : GUI::NarrationHandler {
}
void event(const Ogre::String &evt) override
{
ZoneScoped;
lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
int type = lua_getfield(L, -1, "event");
OgreAssert(type == LUA_TFUNCTION, "bad event()");
@@ -171,6 +183,7 @@ struct LuaQuest : QuestModule::Quest {
, L(L)
, ref(ref)
{
ZoneScoped;
OgreAssert(L, "bad Lua state");
lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
lua_pushlightuserdata(L, this);
@@ -206,6 +219,7 @@ struct LuaQuest : QuestModule::Quest {
}
void finish(int rc) override
{
ZoneScoped;
lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
int type = lua_getfield(L, -1, "finish");
OgreAssert(type == LUA_TFUNCTION, "bad finish()");
@@ -219,6 +233,7 @@ struct LuaQuest : QuestModule::Quest {
}
void activate() override
{
ZoneScoped;
lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
int type = lua_getfield(L, -1, "activate");
OgreAssert(type == LUA_TFUNCTION, "bad activate()");
@@ -231,6 +246,7 @@ struct LuaQuest : QuestModule::Quest {
}
void event(const Ogre::String &evt) override
{
ZoneScoped;
lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
int type = lua_getfield(L, -1, "event");
OgreAssert(type == LUA_TFUNCTION, "bad event()");
@@ -244,6 +260,7 @@ struct LuaQuest : QuestModule::Quest {
}
int update(float delta) override
{
ZoneScoped;
lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
int type = lua_getfield(L, -1, "finish");
OgreAssert(type == LUA_TFUNCTION, "bad finish()");
@@ -263,6 +280,7 @@ struct LuaQuest : QuestModule::Quest {
void QuestModule::addLuaQuest(lua_State *L)
{
ZoneScoped;
luaL_checktype(L, 1, LUA_TSTRING);
luaL_checktype(L, 2, LUA_TTABLE);
Ogre::String name = lua_tostring(L, 1);
@@ -274,12 +292,14 @@ void QuestModule::addLuaQuest(lua_State *L)
void QuestModule::Quest::_activate()
{
ZoneScoped;
activate();
active = true;
}
void QuestModule::Quest::_finish(int rc)
{
ZoneScoped;
finish(rc);
active = false;
if (rc == OK)
@@ -301,6 +321,7 @@ QuestModule::Quest::~Quest()
int QuestModule::Quest::_update(float delta)
{
ZoneScoped;
if (!active && !_can_activate())
return ERROR;
if (!active)
@@ -320,6 +341,7 @@ int QuestModule::Quest::_update(float delta)
void QuestModule::Quest::_event(const std::string &evt)
{
ZoneScoped;
event(evt);
}

View File

@@ -6,11 +6,13 @@
#include "WaterModule.h"
#include "BoatModule.h"
#include "SlotsModule.h"
#include <tracy/Tracy.hpp>
namespace ECS
{
SlotsModule::SlotsModule(flecs::world &ecs)
{
ecs.module<SlotsModule>();
ZoneScoped;
ecs.module<SlotsModule>();
ecs.import <CharacterModule>();
ecs.component<ParentSlot>();
ecs.component<ParentSlotData>();
@@ -20,7 +22,10 @@ SlotsModule::SlotsModule(flecs::world &ecs)
ecs.observer<const EngineData, const BoatBase>("CreateBoatSlots")
.event(flecs::OnSet)
.each([&](flecs::entity e, const EngineData &eng,
const BoatBase &boat) { createBoatSlots(e); });
const BoatBase &boat) {
ZoneScoped;
createBoatSlots(e);
});
#if 1
ecs.system<const EngineData, const CharacterBase, ParentSlot>(
"UpdateSlotData")
@@ -29,6 +34,7 @@ SlotsModule::SlotsModule(flecs::world &ecs)
.without<ParentSlotData>()
.each([&](flecs::entity e, const EngineData &eng,
const CharacterBase &ch, ParentSlot &slot) {
ZoneScoped;
if (slot.slot_name == "") {
slot.removeSlot(e);
return;
@@ -39,7 +45,10 @@ SlotsModule::SlotsModule(flecs::world &ecs)
slot.removeSlot(e);
return;
}
slot.addChild(ch.mBodyNode);
Ogre::SceneNode *n =
ECS::get<CharacterModule>()
.characterNodes.at(e);
slot.addChild(n);
slot.createSlotData(e);
std::cout << "base: "
<< slot.getSlotBase()->getName();
@@ -77,7 +86,8 @@ SlotsModule::SlotsModule(flecs::world &ecs)
}
void SlotsModule::createBoatSlots(flecs::entity e)
{
const EngineData &eng = e.world().get<EngineData>();
ZoneScoped;
const EngineData &eng = e.world().get<EngineData>();
const BoatBase &boat = e.get<BoatBase>();
int i;
std::vector<Ogre::Node *> slots = boat.mNode->getChildren();
@@ -115,7 +125,8 @@ void SlotsModule::createBoatSlots(flecs::entity e)
}
void ParentSlot::createSlot(flecs::entity e)
{
if (e.has<CharacterBase>()) {
ZoneScoped;
if (e.has<CharacterBase>()) {
createCharacterSlot(e);
}
}
@@ -124,14 +135,16 @@ void ParentSlot::createCharacterSlot(flecs::entity e)
}
void ParentSlot::removeSlot(flecs::entity e)
{
if (e.has<ParentSlot>())
ZoneScoped;
if (e.has<ParentSlot>())
e.remove<ParentSlot>();
if (e.has<ParentSlot>())
e.remove<ParentSlotData>();
}
bool ParentSlot::check() const
{
if (!parent_e.has<ObjectSlots>())
ZoneScoped;
if (!parent_e.has<ObjectSlots>())
return false;
const ObjectSlots &slots = parent_e.get<ObjectSlots>();
if (!slots.exists(slot_name))
@@ -140,13 +153,15 @@ bool ParentSlot::check() const
}
bool ParentSlot::parentIsValid()
{
if (!parent_e.has<ObjectSlots>())
ZoneScoped;
if (!parent_e.has<ObjectSlots>())
return false;
return true;
}
Ogre::SceneNode *ParentSlot::getSlotBase() const
{
if (!check())
ZoneScoped;
if (!check())
return nullptr;
const ObjectSlots &slots = parent_e.get<ObjectSlots>();
Ogre::SceneNode *slot_base = slots.slots.at(slot_name).second;
@@ -154,7 +169,8 @@ Ogre::SceneNode *ParentSlot::getSlotBase() const
}
void ParentSlot::addChild(Ogre::SceneNode *childNode)
{
Ogre::SceneNode *parentNode = getSlotBase();
ZoneScoped;
Ogre::SceneNode *parentNode = getSlotBase();
if (childNode->getParentSceneNode())
childNode->getParentSceneNode()->removeChild(childNode);
parentNode->addChild(childNode);
@@ -163,7 +179,8 @@ void ParentSlot::addChild(Ogre::SceneNode *childNode)
}
void ParentSlot::createSlotData(flecs::entity e)
{
const ObjectSlots &slots = parent_e.get<ObjectSlots>();
ZoneScoped;
const ObjectSlots &slots = parent_e.get<ObjectSlots>();
ParentSlotData &psdata = e.ensure<ParentSlotData>();
Ogre::SceneNode *slot_base = getSlotBase();
// Ogre::Vector3 position = slot_base->_getDerivedPosition();
@@ -178,6 +195,7 @@ void ParentSlot::createSlotData(flecs::entity e)
}
bool ObjectSlots::exists(const Ogre::String &name) const
{
return slots.find(name) != slots.end();
ZoneScoped;
return slots.find(name) != slots.end();
}
}

View File

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

View File

@@ -13,8 +13,11 @@
#include "TerrainModule.h"
#include "physics.h"
#include "PhysicsModule.h"
#include "CharacterManagerModule.h"
#include "items.h"
#include "StaticGeometryModule.h"
#include "CharacterAIModule.h"
#include <tracy/Tracy.hpp>
namespace ECS
{
@@ -25,12 +28,16 @@ static bool templatesLoaded = false;
static std::list<std::pair<long, long> > addQueue;
StaticGeometryModule::StaticGeometryModule(flecs::world &ecs)
{
ZoneScoped;
ecs.module<StaticGeometryModule>();
ecs.import <CharacterManagerModule>();
ecs.import <CharacterAIModule>();
ecs.component<TerrainSlotParent>();
ecs.component<TerrainItem>();
ecs.component<FurnitureItem>();
ecs.component<FurnitureInstance>()
.on_remove([](flecs::entity e, FurnitureInstance &instance) {
ZoneScoped;
if (instance.furniture) {
instance.furniture
->destroyAllChildrenAndObjects();
@@ -40,6 +47,7 @@ StaticGeometryModule::StaticGeometryModule(flecs::world &ecs)
}
})
.on_set([](flecs::entity e, FurnitureInstance &instance) {
ZoneScoped;
if (instance.furniture !=
e.get<FurnitureInstance>().furniture) {
FurnitureInstance &f =
@@ -53,11 +61,13 @@ StaticGeometryModule::StaticGeometryModule(flecs::world &ecs)
}
})
.on_add([](flecs::entity e, FurnitureInstance &instance) {
ZoneScoped;
instance.furniture = nullptr;
});
ecs.component<TerrainItemNode>().on_remove([](flecs::entity e,
TerrainItemNode &item) {
if (item.itemNode) {
ZoneScoped;
if (item.itemNode) {
item.itemNode->destroyAllChildrenAndObjects();
item.itemNode->getCreator()->destroySceneNode(
item.itemNode);
@@ -75,15 +85,30 @@ StaticGeometryModule::StaticGeometryModule(flecs::world &ecs)
ecs.observer<const Terrain>("LoadTerrainItems")
.event(flecs::OnSet)
.each([&](const Terrain &terrain) {
if (terrain.mTerrainGroup && !itemsLoaded) {
ZoneScopedN("LoadTerrainItems");
if (terrain.mTerrainGroup && !itemsLoaded) {
loadItems();
itemsLoaded = true;
}
});
if (!Ogre::MeshLodGenerator::getSingletonPtr())
new Ogre::MeshLodGenerator();
ecs.system<TerrainItem>("SetupTowns")
.kind(flecs::OnUpdate)
.without<TownNPCs>()
.without<TownAI>()
.each([&](flecs::entity e, TerrainItem &item) {
Ogre::String props = item.properties;
nlohmann::json jp = nlohmann::json::parse(props);
if (jp.find("type") == jp.end())
return;
Ogre::String itemType = jp["type"].get<Ogre::String>();
if (itemType == "town")
Geometry::registerTownItem(e);
});
ecs.system("AddGeometryQueue").kind(flecs::OnUpdate).run([&](flecs::iter &it) {
std::list<flecs::entity> items;
ZoneScopedN("AddGeometryQueue");
std::list<flecs::entity> items;
if (!ECS::get().has<Terrain>())
return;
if (!ECS::get<Terrain>().mTerrainGroup)
@@ -126,9 +151,8 @@ StaticGeometryModule::StaticGeometryModule(flecs::world &ecs)
const TerrainItem &item) {
items.push_back(e);
});
for (auto e : items) {
for (auto e : items)
createItemGeometry(e);
}
addQueue.pop_front();
} else {
output.push_back(item);
@@ -142,11 +166,13 @@ StaticGeometryModule::StaticGeometryModule(flecs::world &ecs)
}
void StaticGeometryModule::addGeometryForSlot(long x, long y)
{
addQueue.push_back({ x, y });
ZoneScoped;
addQueue.push_back({ x, y });
}
void StaticGeometryModule::removeGeometryForSlot(long x, long y)
{
std::pair<long, long> slot = { x, y };
ZoneScoped;
std::pair<long, long> slot = { x, y };
flecs::entity parent =
ECS::get().query_builder<const TerrainSlotParent>().build().find(
[&slot](const TerrainSlotParent &parent) {
@@ -169,7 +195,8 @@ StaticGeometryModule::createItem(const Ogre::Vector3 &position,
const Ogre::Quaternion &orientation,
const Ogre::String &type)
{
long x, y;
ZoneScoped;
long x, y;
ECS::get<Terrain>().mTerrainGroup->convertWorldPositionToTerrainSlot(
position, &x, &y);
std::pair<long, long> pos{ x, y };
@@ -191,19 +218,22 @@ StaticGeometryModule::createItem(const Ogre::Vector3 &position,
void StaticGeometryModule::setItemProperties(flecs::entity id,
Ogre::String properties)
{
OgreAssert(id.is_valid(), "bad id");
ZoneScoped;
OgreAssert(id.is_valid(), "bad id");
id.get_mut<TerrainItem>().properties = properties;
id.modified<TerrainItem>();
}
const Ogre::String &StaticGeometryModule::getItemProperties(flecs::entity id)
{
OgreAssert(id.is_valid(), "bad id");
ZoneScoped;
OgreAssert(id.is_valid(), "bad id");
return id.get<TerrainItem>().properties;
}
nlohmann::json templates;
void StaticGeometryModule::loadTemplates()
{
ZoneScoped;
if (!Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(
"templates.list"))
return;
@@ -220,6 +250,7 @@ void StaticGeometryModule::loadTemplates()
void StaticGeometryModule::saveTemplates()
{
ZoneScoped;
Ogre::String path = "resources/buildings/templates.list";
if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(
"templates.list")) {
@@ -242,7 +273,8 @@ void StaticGeometryModule::saveTemplates()
void StaticGeometryModule::saveItems()
{
Ogre::String path = "resources/buildings/items.list";
ZoneScoped;
Ogre::String path = "resources/buildings/items.list";
if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(
"items.list")) {
Ogre::String group =
@@ -288,7 +320,8 @@ void StaticGeometryModule::saveItems()
}
void StaticGeometryModule::loadItems()
{
if (!Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(
ZoneScoped;
if (!Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(
"items.list"))
return;
Ogre::String group = Ogre::ResourceGroupManager::getSingleton()
@@ -362,6 +395,7 @@ void StaticGeometryModule::saveFurniture()
void StaticGeometryModule::loadFurniture()
{
ZoneScoped;
ECS::get().delete_with<FurnitureItem>();
static std::vector<Ogre::String> glb_names;
const std::vector<Ogre::String> &groups =
@@ -405,6 +439,7 @@ void StaticGeometryModule::loadFurniture()
void StaticGeometryModule::getItemPositionPerSlot(
long x, long y, std::list<Ogre::Vector3> *positions)
{
ZoneScoped;
std::pair<long, long> pos{ x, y };
if (!positions)
return;
@@ -425,6 +460,7 @@ void StaticGeometryModule::getItemPositionPerSlot(
}
void StaticGeometryModule::getItemPositions(std::list<Ogre::Vector3> *positions)
{
ZoneScoped;
ECS::get().query_builder<const TerrainItem>().build().each(
[&](flecs::entity e, const TerrainItem &item) {
positions->push_back(item.position);
@@ -433,12 +469,14 @@ void StaticGeometryModule::getItemPositions(std::list<Ogre::Vector3> *positions)
void StaticGeometryModule::getItemPositionAndRotation(
flecs::entity e, Ogre::Vector3 &position, Ogre::Quaternion &orientation)
{
ZoneScoped;
position = e.get<TerrainItem>().position;
orientation = e.get<TerrainItem>().orientation;
}
void StaticGeometryModule::getItemsProperties(
std::list<std::pair<flecs::entity, Ogre::String> > *items)
{
ZoneScoped;
ECS::get().query_builder<const TerrainItem>().build().each(
[&](flecs::entity e, const TerrainItem &item) {
items->push_back({ e, item.properties });
@@ -446,11 +484,13 @@ void StaticGeometryModule::getItemsProperties(
}
void StaticGeometryModule::createItemGeometry(flecs::entity e)
{
ZoneScoped;
Geometry::createItemGeometry(e);
}
void StaticGeometryModule::destroyItemGeometry(flecs::entity e)
{
ZoneScoped;
Geometry::destroyItemGeometry(e);
}
@@ -461,16 +501,15 @@ nlohmann::json &StaticGeometryModule::getTemplates()
void StaticGeometryModule::updateItemGeometry(flecs::entity e)
{
if (e.has<GeometryUpdateItem>())
return;
e.add<GeometryUpdateItem>();
Ogre::Root::getSingleton().getWorkQueue()->addTask([e]() {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
Ogre::Root::getSingleton().getWorkQueue()->addMainThreadTask(
[e]() {
Geometry::updateItemGeometry(e);
e.remove<GeometryUpdateItem>();
});
ZoneScoped;
// We add this as task to reduce UI load
Ogre::Root::getSingleton().getWorkQueue()->addMainThreadTask([e]() {
ZoneScopedN("updateItemGeometry");
if (e.has<GeometryUpdateItem>())
return;
e.add<GeometryUpdateItem>();
Geometry::updateItemGeometry(e);
e.remove<GeometryUpdateItem>();
});
}
@@ -479,6 +518,7 @@ void StaticGeometryModule::addTriangleBufferWork(
const Ogre::Vector3 &position, const Ogre::Quaternion &rotation,
const Procedural::TriangleBuffer &tb)
{
ZoneScoped;
struct WorkData {
Ogre::String meshName;
Ogre::StaticGeometry *geo;
@@ -487,22 +527,14 @@ void StaticGeometryModule::addTriangleBufferWork(
Procedural::TriangleBuffer tb;
};
WorkData data = { meshName, geo, position, rotation, tb };
Ogre::Root::getSingleton().getWorkQueue()->addTask([captData = std::move(
data)]() {
Ogre::Root::getSingleton().getWorkQueue()->addMainThreadTask(
[captData]() {
Ogre::MeshPtr mesh =
captData.tb.transformToMesh(
captData.meshName);
Ogre::Entity *ent =
ECS::get<EngineData>()
.mScnMgr->createEntity(mesh);
captData.geo->addEntity(ent, captData.position,
captData.rotation);
ECS::get<EngineData>().mScnMgr->destroyEntity(
ent);
});
// We add this as task to reduce UI load
Ogre::Root::getSingleton().getWorkQueue()->addMainThreadTask([data]() {
ZoneScopedN("addTriangleBufferWork");
Ogre::MeshPtr mesh = data.tb.transformToMesh(data.meshName);
Ogre::Entity *ent =
ECS::get<EngineData>().mScnMgr->createEntity(mesh);
data.geo->addEntity(ent, data.position, data.rotation);
ECS::get<EngineData>().mScnMgr->destroyEntity(ent);
});
}
struct TiledMeshes {

View File

@@ -2,16 +2,19 @@
#include <Ogre.h>
#include "Components.h"
#include "SunModule.h"
#include <tracy/Tracy.hpp>
namespace ECS
{
SunModule::SunModule(flecs::world &ecs)
{
ecs.component<Sun>().add(flecs::Singleton);
ZoneScoped;
ecs.component<Sun>().add(flecs::Singleton);
ecs.system<const EngineData, Sun>("UpdateSetupSun")
.kind(flecs::OnUpdate)
.each([](const EngineData &eng, Sun &sun) {
if (!sun.mSun) {
ZoneScopedN("UpdateSetupSun");
if (!sun.mSun) {
Ogre::Light *light =
eng.mScnMgr->createLight("Sun");
sun.mSunNode = eng.mScnMgr->getRootSceneNode()

View File

@@ -18,6 +18,7 @@
#include "PhysicsModule.h"
#include "StaticGeometryModule.h"
#include "TerrainModule.h"
#include <tracy/Tracy.hpp>
#define TERRAIN_SIZE 65
#define TERRAIN_WORLD_SIZE 500.0f
@@ -48,6 +49,7 @@ struct HeightData {
static HeightData *singleton;
HeightData()
{
ZoneScoped;
img.load(
"world_map.png",
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
@@ -60,13 +62,15 @@ struct HeightData {
}
static HeightData *get_singleton()
{
if (!singleton)
ZoneScoped;
if (!singleton)
singleton = new HeightData();
return singleton;
}
float get_brush_height(int id, int x, int y)
{
int m = 0;
ZoneScoped;
int m = 0;
switch (id) {
case 0:
m = 0;
@@ -110,7 +114,8 @@ struct HeightData {
}
void save_heightmap()
{
Ogre::String group =
ZoneScoped;
Ogre::String group =
Ogre::ResourceGroupManager::getSingleton()
.findGroupContainingResource("world_map.png");
Ogre::FileInfoListPtr fileInfoList(
@@ -125,7 +130,7 @@ struct HeightData {
}
float get_base_height(long world_x, long world_y)
{
float height = 0.0f;
float height = 0.0f;
int world_img_x =
world_x + (int)img.getWidth() * BRUSH_SIZE / 2;
int world_img_y =
@@ -155,7 +160,7 @@ out:
}
float get_noise_height(long world_x, long world_y)
{
int h;
int h;
Ogre::Vector2 noisePoint;
struct noise_types {
@@ -192,7 +197,7 @@ out:
float get_height(Ogre::TerrainGroup *terrainGroup, long world_x,
long world_y)
{
long grid_center_x = img.getWidth() * BRUSH_SIZE / 2;
long grid_center_x = img.getWidth() * BRUSH_SIZE / 2;
long grid_center_y = img.getHeight() * BRUSH_SIZE / 2;
long world_grid_x = world_x + grid_center_x;
long world_grid_y = world_y + grid_center_y;
@@ -265,6 +270,7 @@ public:
void createTerrainChunk(Ogre::TerrainGroup *terrainGroup, long x,
long y)
{
ZoneScoped;
Ogre::Terrain *terrain = terrainGroup->getTerrain(x, y);
float minH = terrain->getMinHeight();
float maxH = terrain->getMaxHeight();
@@ -290,7 +296,8 @@ public:
}
void define(Ogre::TerrainGroup *terrainGroup, long x, long y) override
{
std::lock_guard<std::mutex> guard(mtx);
ZoneScoped;
std::lock_guard<std::mutex> guard(mtx);
uint16_t terrainSize = terrainGroup->getTerrainSize();
float *heightMap = OGRE_ALLOC_T(float, terrainSize *terrainSize,
MEMCATEGORY_GEOMETRY);
@@ -332,13 +339,15 @@ public:
}
bool frameStarted(const Ogre::FrameEvent &evt) override
{
(void)evt;
ZoneScoped;
(void)evt;
update();
return true;
}
void update()
{
std::lock_guard<std::mutex> guard(mtx);
ZoneScoped;
std::lock_guard<std::mutex> guard(mtx);
static bool created = false;
while (!collider_queue.empty()) {
Ogre::TerrainGroup *group =
@@ -478,7 +487,8 @@ public:
bool unloadProceduralPage(Ogre::Page *page,
Ogre::PagedWorldSection *section)
{
long x, y;
ZoneScoped;
long x, y;
ECS::get<Terrain>().mTerrainGroup->unpackIndex(page->CHUNK_ID,
&x, &y);
StaticGeometryModule::removeGeometryForSlot(x, y);
@@ -492,7 +502,8 @@ public:
};
TerrainModule::TerrainModule(flecs::world &ecs)
{
struct CanSetPlayerPosition {};
ZoneScoped;
struct CanSetPlayerPosition {};
ecs.module<TerrainModule>();
ecs.component<CanSetPlayerPosition>().add(flecs::Singleton);
ecs.component<Terrain>().add(flecs::Singleton);
@@ -509,7 +520,8 @@ TerrainModule::TerrainModule(flecs::world &ecs)
.each([](const EngineData &eng, const Camera &camera,
const Sun &sun, Terrain &terrain,
TerrainPrivate &priv) {
if (!terrain.mTerrainGroup && sun.mSun && eng.mScnMgr) {
ZoneScoped;
if (!terrain.mTerrainGroup && sun.mSun && eng.mScnMgr) {
std::cout << "Terrain setup\n";
if (!priv.mDummyPageProvider)
priv.mDummyPageProvider =
@@ -635,7 +647,8 @@ TerrainModule::TerrainModule(flecs::world &ecs)
.kind(flecs::OnUpdate)
.without<TerrainReady>()
.each([](const ECS::Camera &cam, const Terrain &terrain) {
std::cout << "mTerrainReady: " << terrain.mTerrainReady
ZoneScoped;
std::cout << "mTerrainReady: " << terrain.mTerrainReady
<< "\n";
if (cam.mCameraNode && terrain.mTerrainReady) {
long x, y;
@@ -653,7 +666,8 @@ TerrainModule::TerrainModule(flecs::world &ecs)
ecs.system<const Terrain, PlacementObjects>("UpdatePlacementObjects")
.kind(flecs::OnUpdate)
.each([](const Terrain &terrain, PlacementObjects &placement) {
if (placement.altar_items.size() == 0) {
ZoneScoped;
if (placement.altar_items.size() == 0) {
struct PlacementObjects::item item;
int i, j;
int worldSize = terrain.mTerrainGroup
@@ -766,7 +780,8 @@ TerrainModule::TerrainModule(flecs::world &ecs)
.kind(flecs::OnUpdate)
.interval(2.0f)
.each([](const Terrain &terrain) {
if (!terrain.mTerrainGroup
ZoneScoped;
if (!terrain.mTerrainGroup
->isDerivedDataUpdateInProgress())
terrain.mTerrainGroup->update(false);
});

View File

@@ -10,6 +10,7 @@
#include "GameData.h"
#include "Components.h"
#include "WaterModule.h"
#include <tracy/Tracy.hpp>
namespace ECS
{
#if 0
@@ -112,12 +113,6 @@ WaterModule::WaterModule(flecs::world &ecs)
Ogre::Plane(Ogre::Vector3(0.0, -1.0, 0.0), h);
water.mRefractionClipPlaneBelow =
Ogre::Plane(Ogre::Vector3(0.0, 1.0, 0.0), -h);
#if 0
if (Ogre::TextureManager::getSingleton()
.resourceExists(renderTargetName))
Ogre::TextureManager::getSingleton()
.remove(renderTargetName);
#endif
Ogre::TexturePtr reflectionTexture =
Ogre::TextureManager::getSingleton().createManual(
renderTargetName,
@@ -201,64 +196,6 @@ WaterModule::WaterModule(flecs::world &ecs)
Ogre::MANUAL_CULL_NONE);
pass->setVertexProgram("Water/water_vp");
pass->setFragmentProgram("Water/water_fp");
#if 0
Ogre::GpuProgramPtr water_vp =
Ogre::GpuProgramManager::getSingleton()
.getByName(
"Water/water_vp",
Ogre::RGN_AUTODETECT);
OgreAssert(water_vp != nullptr,
"VP failed");
pass->setGpuProgram(
Ogre::GPT_VERTEX_PROGRAM,
water_vp);
OgreAssert(water_vp->isSupported(),
"VP not supported");
Ogre::GpuProgramPtr water_fp =
Ogre::GpuProgramManager::getSingleton()
.getByName(
"Water/water_fp",
Ogre::RGN_AUTODETECT);
OgreAssert(water_vp != nullptr,
"FP failed");
pass->setGpuProgram(
Ogre::GPT_FRAGMENT_PROGRAM,
water_fp);
OgreAssert(water_fp->isSupported(),
"FP not supported");
Ogre::GpuProgramParametersSharedPtr paramsVP =
water_vp->getDefaultParameters();
paramsVP->setNamedAutoConstant(
"world",
Ogre::GpuProgramParameters::
ACT_WORLD_MATRIX);
paramsVP->setNamedAutoConstant(
"worldViewProj",
Ogre::GpuProgramParameters::
ACT_WORLDVIEWPROJ_MATRIX);
paramsVP->setNamedAutoConstant(
"textureProjMatrix",
Ogre::GpuProgramParameters::
ACT_TEXTURE_WORLDVIEWPROJ_MATRIX);
paramsVP->setNamedAutoConstant(
"eyePosition",
Ogre::GpuProgramParameters::
ACT_CAMERA_POSITION_OBJECT_SPACE);
paramsVP->setNamedAutoConstant(
"normalMatrix",
Ogre::GpuProgramParameters::
ACT_NORMAL_MATRIX);
paramsVP->setNamedAutoConstant(
"worldView",
Ogre::GpuProgramParameters::
ACT_WORLDVIEW_MATRIX);
paramsVP->setNamedAutoConstant(
"viewProj",
Ogre::GpuProgramParameters::
ACT_VIEWPROJ_MATRIX);
Ogre::GpuProgramParametersSharedPtr paramsFP =
water_fp->getDefaultParameters();
#endif
Ogre::TextureUnitState *texture_unit =
pass->createTextureUnitState();
texture_unit->setTextureName(
@@ -284,38 +221,7 @@ WaterModule::WaterModule(flecs::world &ecs)
Ogre::FT_MAG, Ogre::FO_LINEAR);
texture_unit2->setTextureFiltering(
Ogre::FT_MIP, Ogre::FO_LINEAR);
#if 0
bool success =
Ogre::RTShader::ShaderGenerator::getSingletonPtr()
->createShaderBasedTechnique(
*mat,
Ogre::MSN_DEFAULT,
Ogre::MSN_SHADERGEN);
OgreAssert(
success,
"createShaderBasedTechnique");
Ogre::RTShader::RenderState *renderState =
Ogre::RTShader::ShaderGenerator::
getSingletonPtr()
->getRenderState(
Ogre::MSN_SHADERGEN,
*mat,
0);
Ogre::RTShader::SubRenderState *perPixelLightModel =
Ogre::RTShader::ShaderGenerator::getSingletonPtr()
->createSubRenderState(
Ogre::RTShader::
SRS_PER_PIXEL_LIGHTING);
renderState->addTemplateSubRenderState(
perPixelLightModel);
#endif
}
#if 0
mat = Ogre::MaterialManager::getSingleton()
.getByName("Water/Above");
mat->load();
#endif
mat->load();
mat->setReceiveShadows(false);
/*
@@ -494,202 +400,37 @@ WaterModule::WaterModule(flecs::world &ecs)
"Water/Above");
})
.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();
body.mWaterBody = OGRE_NEW btPairCachingGhostObject();
createWaterShape(&body);
body.mWaterBody->setCollisionShape(body.mWaterShape);
btTransform bodyTransform;
bodyTransform.setIdentity();
body.mWaterBody->setWorldTransform(bodyTransform);
body.mWaterBody->setCollisionFlags(
body.mWaterBody->getCollisionFlags() |
btCollisionObject::CF_KINEMATIC_OBJECT |
btCollisionObject::CF_NO_CONTACT_RESPONSE);
body.mWaterBody->setActivationState(
DISABLE_DEACTIVATION);
const EngineData &eng = ECS::get<EngineData>();
const WaterSurface &water = ECS::get<WaterSurface>();
eng.mWorld->attachCollisionObject(body.mWaterBody,
water.mWaterEnt, 16,
0x7fffffff & ~2);
WaterPhysicsAction *action =
OGRE_NEW WaterPhysicsAction(body.mWaterBody);
body.action = action;
ECS::get()
.get<EngineData>()
.mWorld->getBtWorld()
->addAction(body.action);
#endif
ECS::get().add<WaterReady>();
})
.add(flecs::Singleton);
#endif
ecs.system<const EngineData, const Camera, WaterSurface>("UpdateWater")
ecs.system<const EngineData, const Camera, WaterSurface>(
"UpdateWaterPosition")
.kind(flecs::OnUpdate)
.with<WaterReady>()
.interval(0.3f)
.each([](const EngineData &eng, const Camera &camera,
WaterSurface &water) {
float delta = eng.delta;
Ogre::Vector3 mCameraPos =
camera.mCameraNode->_getDerivedPosition();
Ogre::Vector3 waterPos =
water.mWaterNode->_getDerivedPosition();
mCameraPos.y = 0;
waterPos.y = 0;
Ogre::Vector3 d = mCameraPos - waterPos;
// Ogre::Vector3 waterPosition = mCameraPos;
// mWaterNode->setPosition(waterPosition);
if (d.squaredLength() < 100.0f * 100.0f)
water.mWaterNode->translate(d * 3.0f * delta);
else
water.mWaterNode->translate(d);
// water.mWaterEnt->setVisible(false);
water.mViewports[0]->update();
water.mViewports[1]->update();
// water.mRenderTargetListener.mInDepth = true;
// water.mViewports[2]->update();
// water.mViewports[3]->update();
// water.mRenderTargetListener.mInDepth = false;
// water.mWaterEnt->setVisible(true);
ZoneScopedN("UpdateWaterPosition");
float delta = eng.delta;
Ogre::Vector3 mCameraPos =
camera.mCameraNode->_getDerivedPosition();
Ogre::Vector3 waterPos =
water.mWaterNode->_getDerivedPosition();
mCameraPos.y = 0;
waterPos.y = 0;
Ogre::Vector3 d = mCameraPos - waterPos;
if (d.squaredLength() < 100.0f * 100.0f)
water.mWaterNode->translate(d * 3.0f * delta);
else
water.mWaterNode->translate(d);
});
ecs.system<WaterSurface>("UpdateWater")
.kind(flecs::OnUpdate)
.with<WaterReady>()
.each([](WaterSurface &water) {
static int updateViewport = 0;
ZoneScopedN("UpdateWaterRender");
water.mViewports[updateViewport++]->update();
if (updateViewport > 1)
updateViewport = 0;
});
#if 0
ecs.system<const EngineData, const WaterSurface, WaterBody>(
"UpdateWaterBody")
.kind(flecs::OnUpdate)
.with<WaterReady>()
.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 =
water.mWaterNode->_getDerivedPosition();
Ogre::Vector3 waterBodyPos = Ogre::Bullet::convert(
body.mWaterBody->getWorldTransform()
.getOrigin());
waterPos.y = 0;
waterBodyPos.y = 0;
Ogre::Vector3 d = waterPos - waterBodyPos;
d.y = 0;
if (d.squaredLength() > 10.0f * 10.0f)
body.mWaterBody->getWorldTransform().setOrigin(
Ogre::Bullet::convert(waterBodyPos +
d));
#endif
#if 0
btCompoundShape *mshape =
static_cast<btCompoundShape *>(
body.mWaterBody->getCollisionShape());
btDispatcher *dispatch =
eng.mWorld->getBtWorld()->getDispatcher();
btHashedOverlappingPairCache *cache =
body.mWaterBody->getOverlappingPairCache();
btBroadphasePairArray &collisionPairs =
cache->getOverlappingPairArray();
const int numObjects = collisionPairs.size();
std::cout << "numObjects: " << numObjects << "\n";
std::cout
<< "numObjects: "
<< body.mWaterBody->getOverlappingPairs().size()
<< "\n";
for (int i = 0; i < numObjects; i++) {
const btBroadphasePair &collisionPair =
collisionPairs[i];
}
#endif
#if 0
body.mShapeAabbMin =
body.mWaterBody->getWorldTransform()
.getOrigin() +
btVector3(-100, -100, -100);
body.mShapeAabbMax =
body.mWaterBody->getWorldTransform()
.getOrigin() +
btVector3(100, 100, 100);
#if 0
body.mWaterShape->getAabb(
body.mWaterBody->getWorldTransform(),
body.mShapeAabbMin, body.mShapeAabbMax);
#endif
std::cout << "manifolds: "
<< static_cast<WaterPhysicsAction *>(
body.action)
->mManifoldArray.size()
<< "\n";
for (int j = 0;
j < static_cast<WaterPhysicsAction *>(body.action)
->mManifoldArray.size();
j++) {
btPersistentManifold *manifold =
static_cast<WaterPhysicsAction *>(
body.action)
->mManifoldArray[j];
std::cout << "contacts: "
<< manifold->getNumContacts() << "\n";
if (manifold->getNumContacts() == 0)
continue;
const btCollisionObject *obj =
(manifold->getBody0() ==
body.mWaterBody) ?
manifold->getBody1() :
manifold->getBody0();
btCollisionObject *nobj =
const_cast<btCollisionObject *>(obj);
#if 0
if (obj->getCollisionFlags() &
btCollisionObject::CF_STATIC_OBJECT)
continue;
#endif
bool ok = false;
Ogre::Vector3 contactPosA, contactPosB;
float minDist = 0.0f;
for (int p = 0; p < manifold->getNumContacts();
p++) {
const btManifoldPoint &pt =
manifold->getContactPoint(p);
float dist = pt.getDistance();
if (dist < minDist) {
minDist = dist;
ok = true;
}
}
if (ok) {
currentOverlaps.insert(nobj);
if (body.mInWater.find(nobj) ==
body.mInWater.end()) {
/* new body */
body.mInWater.insert(nobj);
/* calculate proj surface */
body.mSurface[nobj] = 100.0f;
body.count++;
}
}
}
for (std::set<btCollisionObject *>::iterator it =
body.mInWater.begin();
it != body.mInWater.end();) {
btCollisionObject *obj = *it;
if (currentOverlaps.find(obj) ==
currentOverlaps.end()) {
/* remove body */
it = body.mInWater.erase(it);
body.mSurface.erase(obj);
body.count--;
} else
it++;
}
#endif
});
#endif
}
struct shapeParams {
float offsetX, offsetY, offsetZ;
@@ -698,38 +439,6 @@ 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;
btCompoundShape *shape = OGRE_NEW btCompoundShape();
shape->setMargin(0.2f);
{
btVector3 inertia(0, 0, 0);
std::vector<btScalar> masses;
btTransform principal;
principal.setIdentity();
for (i = 0; i < sizeof(childShapes) / sizeof(childShapes[0]);
i++) {
btTransform xform;
xform.setIdentity();
xform.setOrigin(btVector3(childShapes[i].offsetX,
childShapes[i].offsetY,
childShapes[i].offsetZ));
btBoxShape *box = OGRE_NEW btBoxShape(btVector3(
childShapes[i].boxX, childShapes[i].boxY,
childShapes[i].boxZ));
water->mChildShapes.push_back(box);
shape->addChildShape(xform, box);
masses.push_back(0);
}
shape->calculatePrincipalAxisTransform(masses.data(), principal,
inertia);
}
shape->recalculateLocalAabb();
water->mWaterShape = shape;
}
#endif
void WaterSurface::RenderTextureListener::preRenderTargetUpdate(
const Ogre::RenderTargetEvent &evt)
{
@@ -758,254 +467,4 @@ bool WaterSurface::RenderTextureListener::renderableQueued(
*ppTech = mSurface->mDepthTech;
return true;
}
#if 0
struct DeepPenetrationContactResultCallback : public btManifoldResult {
DeepPenetrationContactResultCallback(
const btCollisionObjectWrapper *body0Wrap,
const btCollisionObjectWrapper *body1Wrap)
: btManifoldResult(body0Wrap, body1Wrap)
, mPenetrationDistance(0)
, mOtherIndex(0)
{
}
float mPenetrationDistance;
int mOtherIndex;
btVector3 mNormal, mPoint;
void reset()
{
mPenetrationDistance = 0.0f;
}
bool hasHit()
{
return mPenetrationDistance < 0.0f;
}
virtual void addContactPoint(const btVector3 &normalOnBInWorld,
const btVector3 &pointInWorldOnB,
btScalar depth)
{
#ifdef VDEBUG
std::cout
<< "contact: " << Ogre::Bullet::convert(pointInWorldOnB)
<< " " << Ogre::Bullet::convert(normalOnBInWorld)
<< "\n";
#endif
if (mPenetrationDistance > depth) { // Has penetration?
const bool isSwapped =
m_manifoldPtr->getBody0() !=
m_body0Wrap->getCollisionObject();
mPenetrationDistance = depth;
mOtherIndex = isSwapped ? m_index0 : m_index1;
mPoint = isSwapped ? (pointInWorldOnB +
(normalOnBInWorld * depth)) :
pointInWorldOnB;
mNormal = isSwapped ? normalOnBInWorld * -1 :
normalOnBInWorld;
}
}
};
void WaterPhysicsAction::updateAction(btCollisionWorld *collisionWorld,
btScalar deltaTimeStep)
{
collisionWorld->updateSingleAabb(mWaterBody);
mWaterBody->getCollisionShape()->getAabb(
mWaterBody->getWorldTransform(), mShapeAabbMin, mShapeAabbMax);
btDispatcher *dispatch = collisionWorld->getDispatcher();
btHashedOverlappingPairCache *cache =
mWaterBody->getOverlappingPairCache();
btBroadphasePairArray &collisionPairs =
cache->getOverlappingPairArray();
btVector3 a, b;
collisionWorld->getBroadphase()->getAabb(
mWaterBody->getBroadphaseHandle(), a, b);
collisionWorld->getBroadphase()->setAabb(
mWaterBody->getBroadphaseHandle(), mShapeAabbMin, mShapeAabbMax,
dispatch);
btDispatcherInfo &dispatchInfo = collisionWorld->getDispatchInfo();
dispatch->dispatchAllCollisionPairs(cache, dispatchInfo, dispatch);
std::set<btCollisionObject *> currentOverlaps;
std::set<btCollisionObject *>::iterator it;
const int numObjects = collisionPairs.size();
#ifdef VDEBUG
std::cout << "collision pairs: " << numObjects << "\n";
std::cout << "MIN: " << Ogre::Bullet::convert(mShapeAabbMin) << "\n";
std::cout << "MAX: " << Ogre::Bullet::convert(mShapeAabbMax) << "\n";
std::cout << "MIN: " << Ogre::Bullet::convert(a) << "\n";
std::cout << "MAX: " << Ogre::Bullet::convert(b) << "\n";
#endif
std::set<const btCollisionObject *> mCurrentInWater;
/* perform narrow phase */
for (int i = 0; i < numObjects; i++) {
int j;
const btBroadphasePair *collisionPairPtr =
collisionWorld->getBroadphase()
->getOverlappingPairCache()
->findPair(collisionPairs[i].m_pProxy0,
collisionPairs[i].m_pProxy1);
if (!collisionPairPtr)
continue;
#ifndef USE_MANIFOLD
const btBroadphasePair &collisionPair = *collisionPairPtr;
const btCollisionObject *objA =
static_cast<btCollisionObject *>(
collisionPair.m_pProxy0->m_clientObject);
const btCollisionObject *objB =
static_cast<btCollisionObject *>(
collisionPair.m_pProxy1->m_clientObject);
#ifdef VDEBUG
std::cout << "bodies: " << objA << " " << objB << "\n";
std::cout << "bodies: " << objA->getCollisionShape()->getName()
<< " " << objB->getCollisionShape()->getName()
<< "\n";
std::cout << "pair: " << i << " " << collisionPair.m_algorithm
<< "\n";
#endif
const btCollisionObject *me, *other;
if (objA == static_cast<btCollisionObject *>(mWaterBody)) {
me = objA;
other = objB;
} else {
me = objB;
other = objA;
}
const btCollisionShape *my_shape = me->getCollisionShape();
const btCollisionShape *other_shape =
other->getCollisionShape();
btCollisionObjectWrapper obA(NULL, my_shape, mWaterBody,
mWaterBody->getWorldTransform(),
-1, j);
btCollisionObjectWrapper obB(NULL, other_shape, other,
other->getWorldTransform(), -1, 0);
btCollisionAlgorithm *algorithm = dispatch->findAlgorithm(
&obA, &obB, NULL, BT_CONTACT_POINT_ALGORITHMS);
#else
btCollisionAlgorithm *algorithm = collisionPairPtr->m_algorithm;
OgreAssert(algorithm, "No algorithm found");
#endif
#ifdef VDEBUG
std::cout << "algorithm: " << algorithm << "\n";
#endif
#ifndef USE_MANIFOLD
DeepPenetrationContactResultCallback contactPointResult(&obA,
&obB);
#ifdef VDEBUG
std::cout << "process collision\n";
#endif
algorithm->processCollision(&obA, &obB,
collisionWorld->getDispatchInfo(),
&contactPointResult);
algorithm->~btCollisionAlgorithm();
dispatch->freeCollisionAlgorithm(algorithm);
if (contactPointResult.hasHit()) {
mCurrentInWater.insert(other);
mInWater.insert(other);
}
#ifdef VDEBUG
std::cout << "process collision done\n";
#endif
#else
if (collisionPairPtr->m_algorithm)
collisionPairPtr->m_algorithm->getAllContactManifolds(
mManifoldArray);
std::cout << "action: manifold: " << mManifoldArray.size()
<< "\n";
#endif
#if 0
std::cout << " "
<< Ogre::Bullet::convert(
collisionPair.m_pProxy0->m_aabbMin)
<< " -> "
<< Ogre::Bullet::convert(
collisionPair.m_pProxy0->m_aabbMax)
<< " VS "
<< Ogre::Bullet::convert(
collisionPair.m_pProxy1->m_aabbMin)
<< " -> "
<< Ogre::Bullet::convert(
collisionPair.m_pProxy1->m_aabbMax)
<< "\n";
std::cout << "group: 0 " << std::dec
<< collisionPair.m_pProxy0->m_collisionFilterGroup
<< "mask: " << std::hex
<< collisionPair.m_pProxy0->m_collisionFilterMask
<< "\n";
std::cout << "group: 1 " << std::dec
<< collisionPair.m_pProxy1->m_collisionFilterGroup
<< "mask: " << std::hex
<< collisionPair.m_pProxy1->m_collisionFilterMask
<< std::dec << "\n";
if (collisionPair.m_algorithm)
collisionPair.m_algorithm->getAllContactManifolds(
mManifoldArray);
std::cout << "action: manifold: " << mManifoldArray.size()
<< "\n";
#endif
#ifdef USE_MANIFOLD
for (int j = 0; j < mManifoldArray.size(); j++) {
btPersistentManifold *manifold = mManifoldArray[j];
std::cout << "contacts: " << manifold->getNumContacts()
<< "\n";
if (manifold->getNumContacts() == 0)
continue;
const btCollisionObject *obj =
(manifold->getBody0() == mWaterBody) ?
manifold->getBody1() :
manifold->getBody0();
btCollisionObject *nobj =
const_cast<btCollisionObject *>(obj);
#if 1
if (obj->getCollisionFlags() &
btCollisionObject::CF_STATIC_OBJECT)
continue;
#endif
bool ok = false;
Ogre::Vector3 contactPosA, contactPosB;
float minDist = 0.0f;
for (int p = 0; p < manifold->getNumContacts(); p++) {
const btManifoldPoint &pt =
manifold->getContactPoint(p);
float dist = pt.getDistance();
if (dist < minDist) {
minDist = dist;
ok = true;
}
}
if (ok) {
currentOverlaps.insert(nobj);
if (mInWater.find(nobj) == mInWater.end()) {
/* new body */
mInWater.insert(nobj);
}
}
}
#endif
}
for (std::set<const btCollisionObject *>::iterator it =
mInWater.begin();
it != mInWater.end();) {
const btCollisionObject *obj = *it;
if (mCurrentInWater.find(obj) == mCurrentInWater.end()) {
/* remove body */
it = mInWater.erase(it);
} else
it++;
}
#ifdef VDEBUG
std::cout << "water count: " << mInWater.size() << "\n";
#endif
}
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

@@ -3,6 +3,7 @@ find_package(OGRE REQUIRED COMPONENTS Bites Bullet Paging Terrain Overlay CONFIG
find_package(Bullet REQUIRED)
find_package(nlohmann_json REQUIRED)
find_package(OgreProcedural REQUIRED CONFIG)
find_package(flecs REQUIRED CONFIG)
add_library(items STATIC items.cpp harbour.cpp temple.cpp town.cpp)
target_include_directories(items PUBLIC . ${CMAKE_SOURCE_DIR}/src/FastNoiseLite)
target_link_libraries(items PRIVATE
@@ -13,4 +14,5 @@ target_link_libraries(items PRIVATE
OgreBites
editor
physics
Tracy::TracyClient
)

View File

@@ -12,6 +12,7 @@
#include "StaticGeometryModule.h"
#include "items.h"
#include "harbour.h"
#include <tracy/Tracy.hpp>
namespace ECS
{
namespace Items
@@ -19,6 +20,7 @@ namespace Items
/* This is editor function */
static bool findPierOffset(float &offset)
{
ZoneScoped;
Ogre::Vector3 basePos =
ECS::get<EditorGizmo>().sceneNode->_getDerivedPosition();
Ogre::Quaternion baseRot =
@@ -41,7 +43,8 @@ static bool findPierOffset(float &offset)
static bool findPierOffsetAndLengthAndDepth(float &offset, float &length,
float &depth)
{
if (!findPierOffset(offset))
ZoneScoped;
if (!findPierOffset(offset))
return false;
Ogre::Vector3 basePos =
ECS::get<EditorGizmo>().sceneNode->_getDerivedPosition();
@@ -66,7 +69,8 @@ static bool findPierOffsetAndLengthAndDepth(float &offset, float &length,
}
static void findPierHeight(float maxLength, float &height)
{
Ogre::Vector3 basePos =
ZoneScoped;
Ogre::Vector3 basePos =
ECS::get<EditorGizmo>().sceneNode->_getDerivedPosition();
Ogre::Quaternion baseRot =
ECS::get<EditorGizmo>().sceneNode->_getDerivedOrientation();
@@ -86,7 +90,8 @@ static void findPierHeight(float maxLength, float &height)
}
static void findPierPath(float pathLength, std::vector<Ogre::Vector3> &path)
{
float minHeight = 0.2f;
ZoneScoped;
float minHeight = 0.2f;
int i;
Ogre::Vector3 basePos =
ECS::get<EditorGizmo>().sceneNode->_getDerivedPosition();
@@ -146,7 +151,8 @@ static void findPierPath(float pathLength, std::vector<Ogre::Vector3> &path)
}
bool editHarbourDistrict(nlohmann::json &jitem)
{
float plazzaRadius = 5.0f;
ZoneScoped;
float plazzaRadius = 5.0f;
float plazzaHeight = 0.2f;
float plazzaElevation = 0.0f;
float centerOffset = 0.0f;
@@ -280,7 +286,8 @@ bool editHarbourDistrict(nlohmann::json &jitem)
}
void createHarbourPopup(const std::pair<flecs::entity, Ogre::String> item)
{
bool lighthouse = false;
ZoneScoped;
bool lighthouse = false;
float lighthouseDistance = 0.0f;
float lighthouseAngle = 0.0f;
float centerOffset = 0.0f;
@@ -328,7 +335,8 @@ void createHarbourPopup(const std::pair<flecs::entity, Ogre::String> item)
}
void createHarbourItem()
{
Ogre::Vector3 itemPosition =
ZoneScoped;
Ogre::Vector3 itemPosition =
ECS::get<EditorGizmo>().sceneNode->_getDerivedPosition();
Ogre::Quaternion itemOrientation =
ECS::get<EditorGizmo>().sceneNode->_getDerivedOrientation();
@@ -364,7 +372,8 @@ void createHarbourItem()
}
void createHarbourMenu()
{
if (ImGui::MenuItem("Create"))
ZoneScoped;
if (ImGui::MenuItem("Create"))
createHarbourItem();
}
}
@@ -373,7 +382,8 @@ namespace Geometry
void createBridge(flecs::entity e, Ogre::SceneNode *sceneNode,
Ogre::StaticGeometry *geo)
{
int i;
ZoneScoped;
int i;
Procedural::TriangleBuffer tb;
Ogre::MaterialPtr harbourMaterial;
harbourMaterial = Ogre::MaterialManager::getSingleton().getByName(
@@ -545,7 +555,8 @@ void createBridge(flecs::entity e, Ogre::SceneNode *sceneNode,
void createPier(flecs::entity e, Ogre::SceneNode *sceneNode,
Ogre::StaticGeometry *geo)
{
Ogre::MaterialPtr harbourMaterial;
ZoneScoped;
Ogre::MaterialPtr harbourMaterial;
harbourMaterial = Ogre::MaterialManager::getSingleton().getByName(
"proceduralMaterialHarbour" +
Ogre::StringConverter::toString(e.id()));
@@ -705,7 +716,8 @@ void createPier(flecs::entity e, Ogre::SceneNode *sceneNode,
void createPlazza(flecs::entity e, const nlohmann::json &district,
Ogre::SceneNode *sceneNode, Ogre::StaticGeometry *geo)
{
Ogre::MaterialPtr harbourMaterial;
ZoneScoped;
Ogre::MaterialPtr harbourMaterial;
harbourMaterial = Ogre::MaterialManager::getSingleton().getByName(
"proceduralMaterialHarbour" +
Ogre::StringConverter::toString(e.id()));
@@ -784,7 +796,8 @@ void createPlazza(flecs::entity e, const nlohmann::json &district,
void createBuildings(flecs::entity e, const nlohmann::json &district,
Ogre::SceneNode *sceneNode, Ogre::StaticGeometry *geo)
{
Ogre::MaterialPtr harbourMaterial;
ZoneScoped;
Ogre::MaterialPtr harbourMaterial;
harbourMaterial = Ogre::MaterialManager::getSingleton().getByName(
"proceduralMaterialHarbour" +
Ogre::StringConverter::toString(e.id()));
@@ -865,7 +878,8 @@ void createBuildings(flecs::entity e, const nlohmann::json &district,
void createHarbour(flecs::entity e, Ogre::SceneNode *sceneNode,
Ogre::StaticGeometry *geo)
{
std::cout << "createHarbour " << e.id() << std::endl;
ZoneScoped;
std::cout << "createHarbour " << e.id() << std::endl;
Ogre::MaterialPtr harbourMaterial;
harbourMaterial = Ogre::MaterialManager::getSingleton().getByName(
"proceduralMaterialHarbour" +
@@ -980,4 +994,4 @@ void createHarbour(flecs::entity e, Ogre::SceneNode *sceneNode,
geo->build();
}
}
}
}

View File

@@ -18,12 +18,14 @@
#include "temple.h"
#include "town.h"
#include "items.h"
#include <tracy/Tracy.hpp>
namespace ECS
{
namespace Items
{
void runScriptsForAllTowns()
{
ZoneScoped;
std::pair<flecs::entity, Ogre::String> selected_item;
std::list<std::pair<flecs::entity, Ogre::String> > items;
StaticGeometryModule::getItemsProperties(&items);
@@ -40,7 +42,8 @@ void runScriptsForAllTowns()
}
void showItemPopup(const std::pair<flecs::entity, Ogre::String> &item)
{
Ogre::String popupLabel =
ZoneScoped;
Ogre::String popupLabel =
"EditPopup" + Ogre::StringConverter::toString(item.first.id());
if (ImGui::BeginPopup(popupLabel.c_str())) {
Ogre::String prop =
@@ -160,7 +163,8 @@ void showItemPopup(const std::pair<flecs::entity, Ogre::String> &item)
}
void showItemButtons(const std::pair<flecs::entity, Ogre::String> &item)
{
ImGui::SameLine();
ZoneScoped;
ImGui::SameLine();
Ogre::String upd_label =
"Update Height##" +
Ogre::StringConverter::toString(item.first.id());
@@ -190,7 +194,8 @@ void showItemButtons(const std::pair<flecs::entity, Ogre::String> &item)
}
void createItemsMenu()
{
if (ImGui::BeginMenu("Harbour")) {
ZoneScoped;
if (ImGui::BeginMenu("Harbour")) {
Items::createHarbourMenu();
ImGui::EndMenu();
}
@@ -209,6 +214,7 @@ namespace Geometry
{
void setupLods(Ogre::LodConfig &config)
{
ZoneScoped;
int count = 0;
// config.advanced.useCompression = false;
config.advanced.useVertexNormals = true;
@@ -246,7 +252,8 @@ void setupLods(Ogre::LodConfig &config)
Ogre::StaticGeometry *createStaticGeometry(flecs::entity e)
{
Ogre::String props = e.get<TerrainItem>().properties;
ZoneScoped;
Ogre::String props = e.get<TerrainItem>().properties;
nlohmann::json jp = nlohmann::json::parse(props);
if (jp.find("type") != jp.end()) {
Ogre::String itemType = jp["type"].get<Ogre::String>();
@@ -264,7 +271,8 @@ Ogre::StaticGeometry *createStaticGeometry(flecs::entity e)
}
void createItemGeometry(flecs::entity e)
{
OgreAssert(!e.has<TerrainItemNode>(), "Geometry already created");
ZoneScoped;
OgreAssert(!e.has<TerrainItemNode>(), "Geometry already created");
std::cout << "creating geometry for item: " << e.id() << std::endl;
Ogre::String props = e.get<TerrainItem>().properties;
nlohmann::json jp = nlohmann::json::parse(props);
@@ -305,7 +313,7 @@ void createItemGeometry(flecs::entity e)
e.set<TerrainItemNode>({ itemNode, geo });
} else if (itemType == "town") {
OgreAssert(geo, "Can't create static geometry");
createTown(e, itemNode, geo);
createTown(e, itemNode, geo);
e.set<TerrainItemNode>({ itemNode, geo });
std::cout << " town created: " << e.id() << std::endl;
} else {
@@ -322,7 +330,8 @@ void createItemGeometry(flecs::entity e)
void destroyItemGeometry(flecs::entity e)
{
OgreAssert(e.has<TerrainItemNode>(), "No geometry created");
ZoneScoped;
OgreAssert(e.has<TerrainItemNode>(), "No geometry created");
#if 0
ECS::get<EngineData>().mScnMgr->destroyStaticGeometry()
e.get<TerrainItemNode>().geo->destroy();
@@ -333,6 +342,7 @@ void destroyItemGeometry(flecs::entity e)
}
void updateItemGeometry(flecs::entity e)
{
ZoneScoped;
OgreAssert(e.has<TerrainItem>(), "not terrain item");
if (e.has<TerrainItemNode>())
destroyItemGeometry(e);
@@ -344,7 +354,8 @@ flecs::entity createMeshGeometry(const Ogre::String &meshName,
Ogre::SceneNode *sceneNode,
Ogre::StaticGeometry *geo)
{
Ogre::MeshPtr mesh = Ogre::MeshManager::getSingleton().load(
ZoneScoped;
Ogre::MeshPtr mesh = Ogre::MeshManager::getSingleton().load(
meshName,
Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
OgreAssert(mesh, "mesh " + meshName + " not found");
@@ -370,5 +381,10 @@ flecs::entity createMeshGeometry(const Ogre::String &meshName,
return e;
}
void registerTownItem(flecs::entity e)
{
registerTown(e);
}
}
}

View File

@@ -57,6 +57,7 @@ struct harbourMaker {
void createItemGeometry(flecs::entity e);
void destroyItemGeometry(flecs::entity e);
void updateItemGeometry(flecs::entity e);
void registerTownItem(flecs::entity e);
flecs::entity createMeshGeometry(const Ogre::String &meshName,
flecs::entity parente,
Ogre::SceneNode *sceneNode,

View File

@@ -14,12 +14,14 @@
#include "PhysicsModule.h"
#include "items.h"
#include "temple.h"
#include <tracy/Tracy.hpp>
namespace ECS
{
namespace Items
{
void createTempleItem()
{
ZoneScoped;
Ogre::Vector3 itemPosition =
ECS::get<EditorGizmo>().sceneNode->_getDerivedPosition();
Ogre::Quaternion itemOrientation =
@@ -38,12 +40,14 @@ void createTempleItem()
}
void createTempleMenu()
{
if (ImGui::MenuItem("Create"))
ZoneScoped;
if (ImGui::MenuItem("Create"))
createTempleItem();
}
void createTemplePopup(const std::pair<flecs::entity, Ogre::String> item)
{
float size = 40.0f;
ZoneScoped;
float size = 40.0f;
float pillarRadius = 0.5f;
float pillarHeight = 16.0f;
int pillarCount = 20;
@@ -88,7 +92,8 @@ namespace Geometry
void createTemple(flecs::entity e, Ogre::SceneNode *sceneNode,
Ogre::StaticGeometry *geo)
{
Ogre::String props = e.get<TerrainItem>().properties;
ZoneScoped;
Ogre::String props = e.get<TerrainItem>().properties;
nlohmann::json jp = nlohmann::json::parse(props);
float size = 20.0f;
int pillarCount = 4;

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,8 @@
#define __TOWN_H__
#include <OgreMeshLodGenerator.h>
#include <flecs.h>
namespace Procedural {
namespace Procedural
{
class TriangleBuffer;
}
namespace ECS
@@ -21,6 +22,8 @@ void clampUV(flecs::entity e, Procedural::TriangleBuffer &tb,
Ogre::MaterialPtr createTownMaterial(flecs::entity e, bool force = false);
void createTown(flecs::entity e, Ogre::SceneNode *sceneNode,
Ogre::StaticGeometry *geo);
void registerTown(flecs::entity e);
void createTownActionNodes(flecs::entity e);
}
}
#endif

View File

@@ -16,5 +16,5 @@ 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})
target_compile_definitions(physics PRIVATE JPH_PROFILE_ENABLED)
target_compile_definitions(physics PUBLIC JPH_DEBUG_RENDERER JPH_PROFILE_ENABLED JPH_DOUBLE_PRECISION)

View File

@@ -523,7 +523,6 @@ void ContactListener::update()
}
class Physics {
JPH::PhysicsSystem physics_system;
// We need a temp allocator for temporary allocations during the physics update. We're
// pre-allocating 10 MB to avoid having to do allocations during the physics update.
// B.t.w. 10 MB is way too much for this example but it is a typical value you can use.
@@ -550,6 +549,7 @@ class Physics {
// Also have a look at ObjectLayerPairFilterTable or ObjectLayerPairFilterMask for a simpler interface.
ObjectLayerPairFilterImpl object_vs_object_layer_filter;
JPH::PhysicsSystem physics_system;
DebugRenderer *mDebugRenderer;
std::map<JPH::BodyID, Ogre::SceneNode *> id2node;
std::map<Ogre::SceneNode *, JPH::BodyID> node2id;
@@ -580,11 +580,6 @@ public:
OgreAssert(instanceCount == 0, "Bad initialisation");
instanceCount++;
// Register all physics types with the factory and install their collision handlers with the CollisionDispatch class.
// If you have your own custom shape types you probably need to register their handlers with the CollisionDispatch before calling this function.
// If you implement your own default material (PhysicsMaterial::sDefault) make sure to initialize it before this function or else this function will create one for you.
JPH::RegisterTypes();
// This is the max amount of rigid bodies that you can add to the physics system. If you try to add more you'll get an error.
// Note: This value is low because this is a simple test. For a real project use something in the order of 65536.
const uint cMaxBodies = 65536;
@@ -1510,6 +1505,19 @@ public:
}
return hadHit;
}
bool bodyIsCharacter(JPH::BodyID id) const
{
return characterBodies.find(id) != characterBodies.end();
}
void destroyCharacter(std::shared_ptr<JPH::Character> ch)
{
characterBodies.erase(characterBodies.find(ch->GetBodyID()));
characters.erase(ch.get());
Ogre::SceneNode *node = id2node[ch->GetBodyID()];
id2node.erase(ch->GetBodyID());
node2id.erase(node);
ch = nullptr;
}
};
void physics()
@@ -1518,7 +1526,7 @@ void physics()
// physics.update(1.0f / 60.0f);
}
Physics *phys = nullptr;
static std::unique_ptr<Physics> phys = nullptr;
JoltPhysicsWrapper::JoltPhysicsWrapper(Ogre::SceneManager *scnMgr,
Ogre::SceneNode *cameraNode)
: Ogre::Singleton<JoltPhysicsWrapper>()
@@ -1527,21 +1535,24 @@ JoltPhysicsWrapper::JoltPhysicsWrapper(Ogre::SceneManager *scnMgr,
// This needs to be done before any other Jolt function is called.
JPH::RegisterDefaultAllocator();
// Create a factory, this class is responsible for creating instances of classes based on their name or hash and is mainly used for deserialization of saved data.
// It is not directly used in this example but still required.
JPH::Factory::sInstance = new JPH::Factory();
// Install trace and assert callbacks
JPH::Trace = TraceImpl;
JPH_IF_ENABLE_ASSERTS(JPH::AssertFailed = AssertFailedImpl;)
phys = new Physics(scnMgr, cameraNode, nullptr, &contacts);
// Create a factory, this class is responsible for creating instances of classes based on their name or hash and is mainly used for deserialization of saved data.
// It is not directly used in this example but still required.
JPH::Factory::sInstance = new JPH::Factory();
// Register all physics types with the factory and install their collision handlers with the CollisionDispatch class.
// If you have your own custom shape types you probably need to register their handlers with the CollisionDispatch before calling this function.
// If you implement your own default material (PhysicsMaterial::sDefault) make sure to initialize it before this function or else this function will create one for you.
JPH::RegisterTypes();
phys = std::make_unique<Physics>(scnMgr, cameraNode, nullptr,
&contacts);
}
JoltPhysicsWrapper::~JoltPhysicsWrapper()
{
if (phys)
delete phys;
}
void JoltPhysicsWrapper::update(float dt)
@@ -1806,5 +1817,15 @@ bool JoltPhysicsWrapper::raycastQuery(Ogre::Vector3 startPoint,
{
return phys->raycastQuery(startPoint, endPoint, position, id);
}
bool JoltPhysicsWrapper::bodyIsCharacter(JPH::BodyID id) const
{
return phys->bodyIsCharacter(id);
}
void JoltPhysicsWrapper::destroyCharacter(std::shared_ptr<JPH::Character> ch)
{
phys->destroyCharacter(ch);
}
template <>
JoltPhysicsWrapper *Ogre::Singleton<JoltPhysicsWrapper>::msSingleton = 0;

View File

@@ -14,6 +14,7 @@ void physics();
namespace JPH
{
class CharacterBase;
class Character;
class ContactManifold;
class ContactSettings;
class SubShapeIDPair;
@@ -217,5 +218,7 @@ public:
void removeContactListener(const JPH::BodyID &id);
bool raycastQuery(Ogre::Vector3 startPoint, Ogre::Vector3 endPoint,
Ogre::Vector3 &position, JPH::BodyID &id);
bool bodyIsCharacter(JPH::BodyID id) const;
void destroyCharacter(std::shared_ptr<JPH::Character> ch);
};
#endif