diff --git a/assets/default_input_bindings.json b/assets/default_input_bindings.json index 6342ae08f..cf739cb86 100644 --- a/assets/default_input_bindings.json +++ b/assets/default_input_bindings.json @@ -121,14 +121,14 @@ "menu_secondary_trigger": "input:mouse/mouse_button_right", "menu_cursor_x": "input:mouse/mouse_cursor_x", "menu_cursor_y": "input:mouse/mouse_cursor_y", - "move_world_x": { + "move_relative_x": { "operator": "add", - "values": [ "player:flatview/move_world_x", "vr:left_hand/move_world_x", "vr:right_hand/move_world_x" ] + "values": [ "player:flatview/move_relative_x", "vr:left_hand/move_relative_x", "vr:right_hand/move_relative_x" ] }, - "move_world_y": "player:flatview/move_world_y", - "move_world_z": { + "move_relative_y": "player:flatview/move_relative_y", + "move_relative_z": { "operator": "add", - "values": [ "player:flatview/move_world_z", "vr:left_hand/move_world_z", "vr:right_hand/move_world_z" ] + "values": [ "player:flatview/move_relative_z", "vr:left_hand/move_relative_z", "vr:right_hand/move_relative_z" ] }, "move_sprint": "input:keyboard/key_left_shift" } diff --git a/assets/scenes/blackhole1.json b/assets/scenes/blackhole1.json index 911681956..d4da1b649 100644 --- a/assets/scenes/blackhole1.json +++ b/assets/scenes/blackhole1.json @@ -1,5 +1,20 @@ { + "properties": { + "gravity": [0, 0, 0], + "gravity_transform": { + "translate": [0, 153, 0] + }, + "gravity_func": "station_spin" + }, "entities": [ + { + "name": "global:player_gravity_override", + "scene_connection": "player_gravity_override", + "transform": { + "translate": [0, 153, 0] + }, + "signal_output": { "load_scene_connection": 1 } + }, { "name": "zone", "signal_output": {}, diff --git a/assets/scenes/blackhole2.json b/assets/scenes/blackhole2.json index b9c290462..759ae919a 100644 --- a/assets/scenes/blackhole2.json +++ b/assets/scenes/blackhole2.json @@ -1,5 +1,20 @@ { + "properties": { + "gravity": [0, 0, 0], + "gravity_transform": { + "translate": [0, 153, 0] + }, + "gravity_func": "station_spin" + }, "entities": [ + { + "name": "global:player_gravity_override", + "scene_connection": "player_gravity_override", + "transform": { + "translate": [0, 153, 0] + }, + "signal_output": { "load_scene_connection": 1 } + }, { "name": "zone", "signal_output": {}, diff --git a/assets/scenes/blackhole3.json b/assets/scenes/blackhole3.json index e5b7ec8f3..e41e12a4b 100644 --- a/assets/scenes/blackhole3.json +++ b/assets/scenes/blackhole3.json @@ -1,5 +1,20 @@ { + "properties": { + "gravity": [0, 0, 0], + "gravity_transform": { + "translate": [0, 153, 0] + }, + "gravity_func": "station_spin" + }, "entities": [ + { + "name": "global:player_gravity_override", + "scene_connection": "player_gravity_override", + "transform": { + "translate": [0, 153, 0] + }, + "signal_output": { "load_scene_connection": 1 } + }, { "name": "zone", "signal_output": {}, diff --git a/assets/scenes/blackhole4.json b/assets/scenes/blackhole4.json index 134864f7c..a07604c3c 100644 --- a/assets/scenes/blackhole4.json +++ b/assets/scenes/blackhole4.json @@ -1,5 +1,20 @@ { + "properties": { + "gravity": [0, 0, 0], + "gravity_transform": { + "translate": [0, 153, 0] + }, + "gravity_func": "station_spin" + }, "entities": [ + { + "name": "global:player_gravity_override", + "scene_connection": "player_gravity_override", + "transform": { + "translate": [0, 153, 0] + }, + "signal_output": { "load_scene_connection": 1 } + }, { "name": "zone", "signal_output": {}, diff --git a/assets/scenes/blackhole5.json b/assets/scenes/blackhole5.json index 3d223c5f1..34c74758f 100644 --- a/assets/scenes/blackhole5.json +++ b/assets/scenes/blackhole5.json @@ -1,5 +1,20 @@ { + "properties": { + "gravity": [0, 0, 0], + "gravity_transform": { + "translate": [0, 153, 0] + }, + "gravity_func": "station_spin" + }, "entities": [ + { + "name": "global:player_gravity_override", + "scene_connection": "player_gravity_override", + "transform": { + "translate": [0, 153, 0] + }, + "signal_output": { "load_scene_connection": 1 } + }, { "name": "zone", "signal_output": {}, diff --git a/assets/scenes/menu.json b/assets/scenes/menu.json index 611cfdbc0..789819050 100644 --- a/assets/scenes/menu.json +++ b/assets/scenes/menu.json @@ -417,7 +417,7 @@ { "name": "global:spawn", "transform": { - "translate": [0, 0.05, 0] + "translate": [0, 0, 0] } }, { diff --git a/assets/scenes/moving-platform.json b/assets/scenes/moving-platform.json index cbc5cefdf..c079704d7 100644 --- a/assets/scenes/moving-platform.json +++ b/assets/scenes/moving-platform.json @@ -56,6 +56,7 @@ }, "transform": { "parent": "platform_root", + "translate": [0, -0.25, 0], "scale": [4, 0.5, 4] }, "physics": { diff --git a/assets/scenes/player.json b/assets/scenes/player.json index c4d802bd0..230c19618 100644 --- a/assets/scenes/player.json +++ b/assets/scenes/player.json @@ -4,9 +4,7 @@ "name": "player", "transform": {}, "character_controller": { - "target": "vr:hmd", - "fallback_target": "flatview", - "movement_proxy": "vr:origin" + "head": "player:head" }, "trigger_group": "player", "event_input": [ @@ -27,7 +25,10 @@ "script": [ { "onTick": "relative_movement", - "parameters": { "relative_to": "player:flatview" } + "parameters": { + "relative_to": "player:flatview", + "up_reference": "player:player" + } }, { "onTick": "camera_view" }, { diff --git a/assets/scenes/player_gravity_override.json b/assets/scenes/player_gravity_override.json new file mode 100644 index 000000000..47e1d4201 --- /dev/null +++ b/assets/scenes/player_gravity_override.json @@ -0,0 +1,17 @@ +{ + "priority": "Override", + "properties": { + "gravity": [0, 0, 0], + "gravity_transform": {}, + "gravity_func": "station_spin" + }, + "entities": [ + { + "name": "global:player_gravity_override", + "scene_connection": [], + "transform": {} + }, + { "name": "player:player" }, + { "name": "player:debug-spawner" } + ] +} diff --git a/assets/scenes/station-center.json b/assets/scenes/station-center.json index dd73454b4..ef8692ed4 100644 --- a/assets/scenes/station-center.json +++ b/assets/scenes/station-center.json @@ -1,5 +1,20 @@ { + "properties": { + "gravity": [0, 0, 0], + "gravity_transform": { + "translate": [0, 153, 0] + }, + "gravity_func": "station_spin" + }, "entities": [ + { + "name": "global:player_gravity_override", + "scene_connection": "player_gravity_override", + "transform": { + "translate": [0, 153, 0] + }, + "signal_output": { "load_scene_connection": 1 } + }, { "name": "elevator_zone", "signal_output": {}, diff --git a/assets/scenes/vr.json b/assets/scenes/vr.json index 9c2481cf9..94baded55 100644 --- a/assets/scenes/vr.json +++ b/assets/scenes/vr.json @@ -18,6 +18,17 @@ "/action/snap_rotate" ] }, + { + "name": "hmd_vis", + "transform": { + "parent": "hmd", + "scale": [0.1, 0.1, 0.1] + }, + "renderable": { + "model": "box", + "visibility": "DirectCamera|LightingShadow|LightingVoxel" + } + }, { "name": "left_hand", "transform": { @@ -26,7 +37,10 @@ "script": [ { "onTick": "relative_movement", - "parameters": { "relative_to": "vr:hmd" } + "parameters": { + "relative_to": "vr:hmd", + "up_reference": "player:player" + } } ], "signal_output": {} @@ -39,7 +53,10 @@ "script": [ { "onTick": "relative_movement", - "parameters": { "relative_to": "vr:hmd" } + "parameters": { + "relative_to": "vr:hmd", + "up_reference": "player:player" + } } ], "signal_output": {} diff --git a/assets/scripts/tests/menu.txt b/assets/scripts/tests/menu.txt index dba759701..eabf1a757 100644 --- a/assets/scripts/tests/menu.txt +++ b/assets/scripts/tests/menu.txt @@ -1,5 +1,5 @@ loadscene menu -rotate player:flatview -90 0 1 0 +rotate player:player -90 0 1 0 translate player:player -1 0 0 # Step logic and physics so the voxel_controller moves the grid into place steplogic diff --git a/assets/scripts/tests/mirrortest.txt b/assets/scripts/tests/mirrortest.txt index cca1065bf..ee4666bfa 100644 --- a/assets/scripts/tests/mirrortest.txt +++ b/assets/scripts/tests/mirrortest.txt @@ -18,23 +18,23 @@ sendevent player:flashlight/action/flashlight/grab steplogic setsignal player:player/move_noclip 1 -rotate player:flatview -60 0 1 0 +rotate player:player -60 0 1 0 translate player:player -1 -1 -4 stepphysics screenshot mirrortest-edge-occlusion.png stepgraphics -rotate player:flatview 70 0 1 0 +rotate player:player 70 0 1 0 translate player:player -3 0 -2 stepphysics screenshot mirrortest-bounces.png stepgraphics setsignal player:flashlight/on 1 -rotate player:flatview -45 0 1 0 +rotate player:player -45 0 1 0 rotate player:flatview -55 1 0 0 translate player:player -0.14 6 6.1 stepphysics steplogic -stepgraphics 10 +stepgraphics 11 screenshot mirrortest-overhead.png stepgraphics diff --git a/src/audio/CMakeLists.txt b/src/audio/CMakeLists.txt index e5b072ae4..75a48fa7c 100644 --- a/src/audio/CMakeLists.txt +++ b/src/audio/CMakeLists.txt @@ -6,6 +6,7 @@ target_include_directories(${PROJECT_AUDIO_LIB} PUBLIC target_link_libraries(${PROJECT_AUDIO_LIB} PUBLIC ${PROJECT_CORE_LIB} + ${PROJECT_GAME_LIB} libsoundio_static ${LIBSOUNDIO_LIBS} ResonanceAudioStatic diff --git a/src/audio/audio/AudioManager.cc b/src/audio/audio/AudioManager.cc index 9f229e3d8..b291e44bb 100644 --- a/src/audio/audio/AudioManager.cc +++ b/src/audio/audio/AudioManager.cc @@ -3,6 +3,7 @@ #include "assets/AssetManager.hh" #include "core/Tracing.hh" #include "ecs/EntityReferenceManager.hh" +#include "game/GameEntities.hh" #include #include @@ -125,9 +126,8 @@ namespace sp { ZoneScoped; auto lock = ecs::StartTransaction>(); - auto head = headEntity.Get(lock); - if (!head) head = headEntityFallback.Get(lock); - if (head && head.Has(lock)) { + auto head = entities::Head.Get(lock); + if (head.Has(lock)) { auto transform = head.Get(lock); auto pos = transform.GetPosition(); auto rot = transform.GetRotation(); diff --git a/src/audio/audio/AudioManager.hh b/src/audio/audio/AudioManager.hh index 8b55bb0e6..5bd049a5c 100644 --- a/src/audio/audio/AudioManager.hh +++ b/src/audio/audio/AudioManager.hh @@ -69,9 +69,6 @@ namespace sp { LockFreeAudioSet sounds; LockFreeEventQueue soundEvents; - ecs::EntityRef headEntity = ecs::Name("vr", "hmd"); - ecs::EntityRef headEntityFallback = ecs::Name("player", "flatview"); - ecs::ComponentObserver soundObserver; static void AudioWriteCallback(SoundIoOutStream *outstream, int frameCountMin, int frameCountMax); diff --git a/src/core/console/ConsoleCoreCommands.cc b/src/core/console/ConsoleCoreCommands.cc index 6dca7d7e9..6e845acfc 100644 --- a/src/core/console/ConsoleCoreCommands.cc +++ b/src/core/console/ConsoleCoreCommands.cc @@ -13,8 +13,8 @@ void mutateEntity(const string &entityStr, Callback callback) { Logf("Could not parse entity %s", entityStr); return; } - auto lock = ecs::StartTransaction, LockWrite>(); - auto entity = ecs::EntityWith(lock, entityName); + auto lock = ecs::StartTransaction(); + auto entity = ecs::EntityRef(entityName).Get(lock); if (!entity) { Logf("Entity %s not found", entityName.String()); return; @@ -148,7 +148,7 @@ void sp::ConsoleManager::RegisterCoreCommands() { auto [entityName, signalName] = ecs::ParseSignalString(signalStr); auto lock = ecs::StartTransaction, ecs::Write>(); - auto entity = ecs::EntityWith(lock, entityName); + auto entity = ecs::EntityRef(entityName).Get(lock); if (!entity) { Logf("Signal entity %s not found", entityName.String()); return; @@ -168,7 +168,7 @@ void sp::ConsoleManager::RegisterCoreCommands() { auto [entityName, signalName] = ecs::ParseSignalString(signalStr); auto lock = ecs::StartTransaction, ecs::Write>(); - auto entity = ecs::EntityWith(lock, entityName); + auto entity = ecs::EntityRef(entityName).Get(lock); if (!entity) { Logf("Signal entity %s not found", entityName.String()); return; @@ -194,9 +194,9 @@ void sp::ConsoleManager::RegisterCoreCommands() { funcs.Register("clearsignal", "Clear a signal value (clearsignal /)", [](string signalStr) { auto [entityName, signalName] = ecs::ParseSignalString(signalStr); - auto lock = ecs::StartTransaction, ecs::Write>(); - auto entity = ecs::EntityWith(lock, entityName); - if (entity && entity.Has(lock)) { + auto lock = ecs::StartTransaction>(); + auto entity = ecs::EntityRef(entityName).Get(lock); + if (entity.Has(lock)) { auto &signalComp = entity.Get(lock); signalComp.ClearSignal(signalName); } diff --git a/src/core/ecs/Components.cc b/src/core/ecs/Components.cc index d6f349c81..692a81cb3 100644 --- a/src/core/ecs/Components.cc +++ b/src/core/ecs/Components.cc @@ -8,37 +8,37 @@ namespace ecs { typedef std::map ComponentNameMap; typedef std::map ComponentTypeMap; - ComponentNameMap *GComponentNameMap; - ComponentTypeMap *GComponentTypeMap; + ComponentNameMap *componentNameMap = nullptr; + ComponentTypeMap *componentTypeMap = nullptr; void RegisterComponent(const char *name, const std::type_index &idx, ComponentBase *comp) { - if (GComponentNameMap == nullptr) GComponentNameMap = new ComponentNameMap(); - if (GComponentTypeMap == nullptr) GComponentTypeMap = new ComponentTypeMap(); - Assertf(GComponentNameMap->count(name) == 0, "Duplicate component name registration: %s", name); - Assertf(GComponentTypeMap->count(idx) == 0, "Duplicate component type registration: %s", name); - GComponentNameMap->emplace(name, comp); - GComponentTypeMap->emplace(idx, comp); + if (componentNameMap == nullptr) componentNameMap = new ComponentNameMap(); + if (componentTypeMap == nullptr) componentTypeMap = new ComponentTypeMap(); + Assertf(componentNameMap->count(name) == 0, "Duplicate component name registration: %s", name); + Assertf(componentTypeMap->count(idx) == 0, "Duplicate component type registration: %s", name); + componentNameMap->emplace(name, comp); + componentTypeMap->emplace(idx, comp); } const ComponentBase *LookupComponent(const std::string &name) { - if (GComponentNameMap == nullptr) GComponentNameMap = new ComponentNameMap(); + if (componentNameMap == nullptr) componentNameMap = new ComponentNameMap(); - auto it = GComponentNameMap->find(name); - if (it != GComponentNameMap->end()) return it->second; + auto it = componentNameMap->find(name); + if (it != componentNameMap->end()) return it->second; return nullptr; } const ComponentBase *LookupComponent(const std::type_index &idx) { - if (GComponentTypeMap == nullptr) GComponentTypeMap = new ComponentTypeMap(); + if (componentTypeMap == nullptr) componentTypeMap = new ComponentTypeMap(); - auto it = GComponentTypeMap->find(idx); - if (it != GComponentTypeMap->end()) return it->second; + auto it = componentTypeMap->find(idx); + if (it != componentTypeMap->end()) return it->second; return nullptr; } void ForEachComponent(std::function callback) { - Assertf(GComponentTypeMap != nullptr, "ForEachComponent called before components registered."); - for (auto &[name, comp] : *GComponentNameMap) { + Assertf(componentTypeMap != nullptr, "ForEachComponent called before components registered."); + for (auto &[name, comp] : *componentNameMap) { callback(name, *comp); } } diff --git a/src/core/ecs/Ecs.hh b/src/core/ecs/Ecs.hh index 4e8c4fb79..fe76ce846 100644 --- a/src/core/ecs/Ecs.hh +++ b/src/core/ecs/Ecs.hh @@ -146,9 +146,6 @@ namespace ecs { static inline bool IsStaging(Lock<> lock) { return lock.GetInstance().GetInstanceId() == StagingWorld().GetInstanceId(); } - - template - Entity EntityWith(Lock> lock, const T &value); }; // namespace ecs TECS_NAME_COMPONENT(ecs::Name, "Name"); diff --git a/src/core/ecs/EcsImpl.hh b/src/core/ecs/EcsImpl.hh index 94b57b8ae..c654aa34b 100644 --- a/src/core/ecs/EcsImpl.hh +++ b/src/core/ecs/EcsImpl.hh @@ -19,6 +19,7 @@ #include "ecs/components/Renderable.hh" #include "ecs/components/SceneConnection.hh" #include "ecs/components/SceneInfo.hh" +#include "ecs/components/SceneProperties.hh" #include "ecs/components/Screen.hh" #include "ecs/components/Script.hh" #include "ecs/components/Signals.hh" @@ -28,13 +29,3 @@ #include "ecs/components/View.hh" #include "ecs/components/VoxelArea.hh" #include "ecs/components/XRView.hh" - -namespace ecs { - template - Entity EntityWith(Lock> lock, const T &value) { - for (auto e : lock.template EntitiesWith()) { - if (e.template Has(lock) && e.template Get(lock) == value) return e; - } - return Entity(); - } -} // namespace ecs diff --git a/src/core/ecs/EntityRef.cc b/src/core/ecs/EntityRef.cc index f1ed1caae..048393f4b 100644 --- a/src/core/ecs/EntityRef.cc +++ b/src/core/ecs/EntityRef.cc @@ -11,22 +11,24 @@ namespace ecs { if (IsLive(ent)) { liveEntity = ent; - } else { + } else if (IsStaging(ent)) { stagingEntity = ent; + } else { + Abortf("Invalid EntityRef entity: %s", std::to_string(ent)); } } EntityRef::EntityRef(const Entity &ent) { if (!ent) return; - ptr = GEntityRefs.Get(ent).ptr; + ptr = GetEntityRefs().Get(ent).ptr; } EntityRef::EntityRef(const ecs::Name &name, const Entity &ent) { if (!name) return; if (ent) { - ptr = GEntityRefs.Set(name, ent).ptr; + ptr = GetEntityRefs().Set(name, ent).ptr; } else { - ptr = GEntityRefs.Get(name).ptr; + ptr = GetEntityRefs().Get(name).ptr; } Assertf(ptr, "EntityRef(%s, %s) is invalid", name.String(), std::to_string(ent)); } @@ -38,8 +40,10 @@ namespace ecs { Entity EntityRef::Get(const ecs::Lock<> &lock) const { if (IsLive(lock)) { return GetLive(); - } else { + } else if (IsStaging(lock)) { return GetStaging(); + } else { + Abortf("Invalid EntityRef lock: %u", lock.GetInstance().GetInstanceId()); } } diff --git a/src/core/ecs/EntityReferenceManager.cc b/src/core/ecs/EntityReferenceManager.cc index 45eb9d35b..76a7f7eb8 100644 --- a/src/core/ecs/EntityReferenceManager.cc +++ b/src/core/ecs/EntityReferenceManager.cc @@ -6,7 +6,10 @@ #include namespace ecs { - EntityReferenceManager GEntityRefs; + EntityReferenceManager &GetEntityRefs() { + static EntityReferenceManager entityRefs; + return entityRefs; + } EntityRef EntityReferenceManager::Get(const Name &name) { EntityRef ref = nameRefs.Load(name); @@ -28,9 +31,11 @@ namespace ecs { if (IsLive(entity)) { if (liveRefs.count(entity) == 0) return EntityRef(); return EntityRef(liveRefs[entity].lock()); - } else { + } else if (IsStaging(entity)) { if (stagingRefs.count(entity) == 0) return EntityRef(); return EntityRef(stagingRefs[entity].lock()); + } else { + Abortf("Invalid EntityReferenceManager entity: %s", std::to_string(entity)); } } @@ -42,9 +47,11 @@ namespace ecs { if (IsLive(entity)) { ref.ptr->liveEntity = entity; liveRefs[entity] = ref.ptr; - } else { + } else if (IsStaging(entity)) { ref.ptr->stagingEntity = entity; stagingRefs[entity] = ref.ptr; + } else { + Abortf("Invalid EntityReferenceManager entity: %s", std::to_string(entity)); } return ref; } diff --git a/src/core/ecs/EntityReferenceManager.hh b/src/core/ecs/EntityReferenceManager.hh index da0a944d5..4e7c1e867 100644 --- a/src/core/ecs/EntityReferenceManager.hh +++ b/src/core/ecs/EntityReferenceManager.hh @@ -39,5 +39,5 @@ namespace ecs { Ref(const Entity &ent); }; - extern EntityReferenceManager GEntityRefs; + EntityReferenceManager &GetEntityRefs(); } // namespace ecs diff --git a/src/core/ecs/components/Controller.hh b/src/core/ecs/components/Controller.hh index 4ca7d32a4..cfad25a8b 100644 --- a/src/core/ecs/components/Controller.hh +++ b/src/core/ecs/components/Controller.hh @@ -18,18 +18,12 @@ namespace ecs { const float PLAYER_CAPSULE_HEIGHT = PLAYER_HEIGHT - 2 * PLAYER_RADIUS; const float PLAYER_STEP_HEIGHT = 0.2f; - const float PLAYER_GRAVITY = 9.81f; - const float PLAYER_JUMP_VELOCITY = 4.0f; - const float PLAYER_AIR_STRAFE = 0.8f; // Movement scaler for acceleration in air - struct CharacterController { - EntityRef target, fallbackTarget, movementProxy; + EntityRef head; physx::PxCapsuleController *pxController = nullptr; }; static Component ComponenCharacterController("character_controller", - ComponentField::New("target", &CharacterController::target), - ComponentField::New("fallback_target", &CharacterController::fallbackTarget), - ComponentField::New("movement_proxy", &CharacterController::movementProxy)); + ComponentField::New("head", &CharacterController::head)); } // namespace ecs diff --git a/src/core/ecs/components/SceneConnection.cc b/src/core/ecs/components/SceneConnection.cc index 7a37621ac..fcdc050c3 100644 --- a/src/core/ecs/components/SceneConnection.cc +++ b/src/core/ecs/components/SceneConnection.cc @@ -8,9 +8,6 @@ namespace ecs { template<> bool Component::Load(const EntityScope &scope, SceneConnection &dst, const picojson::value &src) { - auto scene = scope.scene.lock(); - if (scene) dst.scenes.emplace_back(scene->name); - if (src.is()) { dst.scenes.emplace_back(src.get()); } else if (src.is()) { diff --git a/src/core/ecs/components/SceneInfo.cc b/src/core/ecs/components/SceneInfo.cc index 87768d97a..9c641aec7 100644 --- a/src/core/ecs/components/SceneInfo.cc +++ b/src/core/ecs/components/SceneInfo.cc @@ -2,6 +2,7 @@ #include "core/Common.hh" #include "ecs/EcsImpl.hh" +#include "game/Scene.hh" namespace ecs { // Should be called on the live SceneInfo @@ -18,12 +19,15 @@ namespace ecs { this->stagingId = newSceneInfo.stagingId; this->priority = newSceneInfo.priority; this->scene = newSceneInfo.scene; + if (newSceneInfo.properties) this->properties = newSceneInfo.properties; } else { // Search the linked-list for a place to insert auto &stagingInfo = this->stagingId.Get(staging); + bool propertiesSet = false; SceneInfo *prevSceneInfo = &stagingInfo; auto nextId = stagingInfo.nextStagingId; while (nextId.Has(staging)) { + if (prevSceneInfo->properties) propertiesSet = true; auto &nextSceneInfo = nextId.Get(staging); if (newSceneInfo.priority >= nextSceneInfo.priority) break; nextId = nextSceneInfo.nextStagingId; @@ -33,6 +37,7 @@ namespace ecs { // SceneInfo is already inserted return; } + if (!propertiesSet && newSceneInfo.properties) this->properties = newSceneInfo.properties; newStagingInfo.nextStagingId = nextId; prevSceneInfo->nextStagingId = newSceneInfo.stagingId; this->nextStagingId = stagingInfo.nextStagingId; @@ -44,25 +49,28 @@ namespace ecs { bool SceneInfo::Remove(Lock> staging, const Entity &removeId) { Assert(this->liveId, "Remove called on an invalid SceneInfo"); Assert(this->stagingId.Has(staging), "Remove called on an invalid SceneInfo"); + auto &stagingInfo = this->stagingId.Get(staging); + const SceneInfo *removedEntry = nullptr; if (this->stagingId == removeId) { // Remove the linked-list root node this->stagingId = this->nextStagingId; - if (this->stagingId.Has(staging)) { - auto &stagingInfo = this->stagingId.Get(staging); - this->nextStagingId = stagingInfo.nextStagingId; - this->priority = stagingInfo.priority; - this->scene = stagingInfo.scene; + if (this->nextStagingId.Has(staging)) { + auto &nextStagingInfo = this->nextStagingId.Get(staging); + this->nextStagingId = nextStagingInfo.nextStagingId; + this->priority = nextStagingInfo.priority; + this->scene = nextStagingInfo.scene; } + removedEntry = &stagingInfo; } else if (this->nextStagingId.Has(staging)) { // Search the linked-list for the id to remove - auto &stagingInfo = this->stagingId.Get(staging); - SceneInfo *prevSceneInfo = &stagingInfo; + SceneInfo *prevSceneInfo = &this->stagingId.Get(staging); auto nextId = this->nextStagingId; while (nextId.Has(staging)) { auto &sceneInfo = nextId.Get(staging); if (sceneInfo.stagingId == removeId) { prevSceneInfo->nextStagingId = sceneInfo.nextStagingId; + removedEntry = &sceneInfo; break; } nextId = sceneInfo.nextStagingId; @@ -70,6 +78,22 @@ namespace ecs { } this->nextStagingId = stagingInfo.nextStagingId; } - return !this->stagingId; + Assertf(removedEntry, "Expected to find removal id %s in SceneInfo tree", std::to_string(removeId)); + if (!this->stagingId) return true; + + if (this->properties && this->properties == removedEntry->properties) { + this->properties.reset(); + // Find next highest priority scene properties + auto nextId = removedEntry->nextStagingId; + while (nextId.Has(staging)) { + auto &nextSceneInfo = nextId.Get(staging); + if (nextSceneInfo.properties) { + this->properties = nextSceneInfo.properties; + break; + } + nextId = nextSceneInfo.nextStagingId; + } + } + return false; } } // namespace ecs diff --git a/src/core/ecs/components/SceneInfo.hh b/src/core/ecs/components/SceneInfo.hh index 7d844e218..eb7ecae24 100644 --- a/src/core/ecs/components/SceneInfo.hh +++ b/src/core/ecs/components/SceneInfo.hh @@ -9,6 +9,8 @@ namespace sp { } // namespace sp namespace ecs { + struct SceneProperties; + struct SceneInfo { // Lower priority scenes will have their components overwritten with higher priority components. enum class Priority : int { @@ -21,25 +23,30 @@ namespace ecs { SceneInfo() {} - SceneInfo(Entity ent, Priority priority, const std::shared_ptr &scene) - : priority(priority), scene(scene) { + SceneInfo(Entity ent, + Priority priority, + const std::shared_ptr &scene, + const std::shared_ptr &properties) + : priority(priority), scene(scene), properties(properties) { if (IsLive(ent)) { liveId = ent; - } else { + } else if (IsStaging(ent)) { stagingId = ent; + } else { + Abortf("Invalid SceneInfo entity: %s", std::to_string(ent)); } } SceneInfo(Entity liveId, const SceneInfo &sceneInfo) - : liveId(liveId), priority(sceneInfo.priority), scene(sceneInfo.scene) { + : liveId(liveId), priority(sceneInfo.priority), scene(sceneInfo.scene), properties(sceneInfo.properties) { Assertf(IsLive(liveId), "Invalid liveId in SceneInfo: %s", std::to_string(liveId)); } SceneInfo(Entity stagingId, Entity prefabStagingId, const SceneInfo &rootSceneInfo) : stagingId(stagingId), prefabStagingId(prefabStagingId), priority(rootSceneInfo.priority), - scene(rootSceneInfo.scene) { - Assertf(!IsLive(stagingId), "Invalid stagingId in SceneInfo: %s", std::to_string(stagingId)); - Assertf(!IsLive(prefabStagingId), + scene(rootSceneInfo.scene), properties(rootSceneInfo.properties) { + Assertf(IsStaging(stagingId), "Invalid stagingId in SceneInfo: %s", std::to_string(stagingId)); + Assertf(IsStaging(prefabStagingId), "Invalid prefabStagingId in SceneInfo: %s", std::to_string(prefabStagingId)); } @@ -56,5 +63,6 @@ namespace ecs { Entity prefabStagingId; Priority priority = Priority::Scene; std::weak_ptr scene; + std::shared_ptr properties; }; } // namespace ecs diff --git a/src/core/ecs/components/SceneProperties.hh b/src/core/ecs/components/SceneProperties.hh new file mode 100644 index 000000000..af0f281c0 --- /dev/null +++ b/src/core/ecs/components/SceneProperties.hh @@ -0,0 +1,14 @@ +#pragma once + +#include "ecs/components/Transform.h" + +#include +#include + +namespace ecs { + struct SceneProperties { + Transform gravityTransform = Transform(); + glm::vec3 fixedGravity = glm::vec3(0, -9.81, 0); + std::function gravityFunction; + }; +} // namespace ecs diff --git a/src/core/ecs/components/Script.cc b/src/core/ecs/components/Script.cc index 9cae4aef5..a4efe8397 100644 --- a/src/core/ecs/components/Script.cc +++ b/src/core/ecs/components/Script.cc @@ -8,8 +8,8 @@ namespace ecs { ScriptDefinitions &GetScriptDefinitions() { - static ScriptDefinitions GScriptDefinitions; - return GScriptDefinitions; + static ScriptDefinitions scriptDefinitions; + return scriptDefinitions; } static std::atomic_size_t nextInstanceId; diff --git a/src/core/ecs/components/Transform.cc b/src/core/ecs/components/Transform.cc index db1551c46..3370ec27c 100644 --- a/src/core/ecs/components/Transform.cc +++ b/src/core/ecs/components/Transform.cc @@ -150,6 +150,11 @@ namespace ecs { return glm::normalize(scaledRotation * glm::vec3(0, 0, -1)); } + glm::vec3 Transform::GetUp() const { + glm::mat3 scaledRotation = glm::mat3(matrix[0], matrix[1], matrix[2]); + return glm::normalize(scaledRotation * glm::vec3(0, 1, 0)); + } + void Transform::SetScale(const glm::vec3 &xyz) { matrix[0] = glm::normalize(matrix[0]) * xyz.x; matrix[1] = glm::normalize(matrix[1]) * xyz.y; @@ -180,6 +185,28 @@ namespace ecs { return matrix != other.matrix; } + Entity TransformTree::GetRoot(Lock> lock, Entity entity) { + if (!entity.Has(lock)) return {}; + + auto &tree = entity.Get(lock); + auto parent = tree.parent.Get(lock); + if (!parent.Has(lock)) { + return entity; + } + + return GetRoot(lock, parent); + } + + void TransformTree::MoveViaRoot(Lock> lock, Entity entity, Transform target) { + if (!entity.Has(lock)) return; + auto &entityTree = entity.Get(lock); + + auto root = GetRoot(lock, entity); + if (!root.Has(lock)) return; + auto &rootTree = root.Get(lock); + rootTree.pose = target * entityTree.GetRelativeTransform(lock, root).GetInverse(); + } + Transform TransformTree::GetGlobalTransform(Lock> lock) const { if (!parent) return pose; diff --git a/src/core/ecs/components/Transform.h b/src/core/ecs/components/Transform.h index 7dd26add5..aacf8aa9c 100644 --- a/src/core/ecs/components/Transform.h +++ b/src/core/ecs/components/Transform.h @@ -4,6 +4,7 @@ #ifdef __cplusplus #ifndef SP_WASM_BUILD + #include "ecs/Components.hh" #include "ecs/EntityRef.hh" #endif @@ -32,6 +33,7 @@ namespace ecs { const glm::vec3 &GetPosition() const; glm::quat GetRotation() const; glm::vec3 GetForward() const; + glm::vec3 GetUp() const; glm::vec3 GetScale() const; Transform GetInverse() const; @@ -76,6 +78,9 @@ namespace ecs { TransformTree(const Transform &pose) : pose(pose) {} TransformTree(glm::vec3 pos, glm::quat orientation = glm::identity()) : pose(pos, orientation) {} + static void MoveViaRoot(Lock> lock, Entity entity, Transform target); + static Entity GetRoot(Lock> lock, Entity entity); + // Returns a flattened Transform that includes all parent transforms. Transform GetGlobalTransform(Lock> lock) const; glm::quat GetGlobalRotation(Lock> lock) const; diff --git a/src/core/game/Scene.hh b/src/core/game/Scene.hh index b30075f47..cb2fd76ae 100644 --- a/src/core/game/Scene.hh +++ b/src/core/game/Scene.hh @@ -7,6 +7,10 @@ #include "ecs/components/Name.hh" #include "ecs/components/SceneInfo.hh" +namespace ecs { + struct SceneProperties; +} + namespace sp { class Asset; @@ -50,6 +54,8 @@ namespace sp { void ApplyScene(ecs::Lock> staging, ecs::Lock live); void RemoveScene(ecs::Lock staging, ecs::Lock live); + std::shared_ptr properties; + private: ecs::Name GenerateEntityName(const ecs::Name &prefix); bool ValidateEntityName(const ecs::Name &name) const; diff --git a/src/core/input/BindingNames.hh b/src/core/input/BindingNames.hh index 9307b0a49..21c88d66f 100644 --- a/src/core/input/BindingNames.hh +++ b/src/core/input/BindingNames.hh @@ -32,9 +32,9 @@ namespace sp { static const std::string INPUT_SIGNAL_MENU_CURSOR_Y = "menu_cursor_y"; // CharacterController - static const std::string INPUT_SIGNAL_MOVE_WORLD_X = "move_world_x"; - static const std::string INPUT_SIGNAL_MOVE_WORLD_Y = "move_world_y"; - static const std::string INPUT_SIGNAL_MOVE_WORLD_Z = "move_world_z"; + static const std::string INPUT_SIGNAL_MOVE_RELATIVE_X = "move_relative_x"; + static const std::string INPUT_SIGNAL_MOVE_RELATIVE_Y = "move_relative_y"; + static const std::string INPUT_SIGNAL_MOVE_RELATIVE_Z = "move_relative_z"; static const std::string INPUT_SIGNAL_MOVE_SPRINT = "move_sprint"; static const std::string INPUT_SIGNAL_MOVE_NOCLIP = "move_noclip"; diff --git a/src/game/DebugCFuncs.cc b/src/game/DebugCFuncs.cc index 115086826..368feb933 100644 --- a/src/game/DebugCFuncs.cc +++ b/src/game/DebugCFuncs.cc @@ -2,6 +2,7 @@ #include "core/Common.hh" #include "core/Tracing.hh" #include "ecs/EcsImpl.hh" +#include "game/GameEntities.hh" #include "game/Scene.hh" #ifdef SP_PHYSICS_SUPPORT_PHYSX @@ -12,14 +13,29 @@ namespace sp { CFunc CFuncPrintDebug("printdebug", "Print some debug info about the player", []() { - auto lock = ecs::StartTransaction< - ecs::Read>(); - auto player = ecs::EntityWith(lock, ecs::Name("player", "player")); - auto flatview = ecs::EntityWith(lock, ecs::Name("player", "flatview")); - if (flatview.Has(lock)) { - auto &transform = flatview.Get(lock); + auto lock = ecs::StartTransaction>(); + auto player = entities::Player.Get(lock); + auto head = entities::Head.Get(lock); + if (head.Has(lock)) { + auto &transform = head.Get(lock); + auto position = transform.GetGlobalTransform(lock).GetPosition(); + Logf("Head position: [%f, %f, %f]", position.x, position.y, position.z); + } + if (head.Has(lock)) { + auto &transform = head.Get(lock); auto position = transform.GetPosition(); - Logf("Flatview position: [%f, %f, %f]", position.x, position.y, position.z); + Logf("Head position snapshot: [%f, %f, %f]", position.x, position.y, position.z); + } + if (player.Has(lock)) { + auto &transform = player.Get(lock); + auto position = transform.GetGlobalTransform(lock).GetPosition(); + Logf("Player position: [%f, %f, %f]", position.x, position.y, position.z); + } else { + Logf("Scene has no valid player"); } if (player.Has(lock)) { auto &transform = player.Get(lock); @@ -38,18 +54,19 @@ namespace sp { Logf("Player on ground: %s", userData->onGround ? "true" : "false"); if (userData->standingOn) Logf("Standing on: %s", ecs::ToString(lock, userData->standingOn)); } else { - Logf("Player position: [%f, %f, %f]", position.x, position.y, position.z); + Logf("Player position snapshot: [%f, %f, %f]", position.x, position.y, position.z); } } else { - Logf("Player position: [%f, %f, %f]", position.x, position.y, position.z); + Logf("Player position snapshot: [%f, %f, %f]", position.x, position.y, position.z); } #else - Logf("Player position: [%f, %f, %f]", position.x, position.y, position.z); + Logf("Player position snapshot: [%f, %f, %f]", position.x, position.y, position.z); #endif } else { - Logf("Scene has no valid player"); + Logf("Scene has no valid player snapshot"); } + auto flatview = entities::Flatview.Get(lock); if (flatview.Has(lock)) { auto &query = flatview.Get(lock); for (auto &subQuery : query.queries) { @@ -70,7 +87,7 @@ namespace sp { auto lock = ecs::StartTransaction(); ecs::Entity entity; if (entityName.empty()) { - auto flatview = ecs::EntityWith(lock, ecs::Name("player", "flatview")); + auto flatview = entities::Flatview.Get(lock); if (flatview.Has(lock)) { auto &query = flatview.Get(lock); for (auto &subQuery : query.queries) { diff --git a/src/game/editor/EditorSystem.cc b/src/game/editor/EditorSystem.cc index 8872ce399..97157ba60 100644 --- a/src/game/editor/EditorSystem.cc +++ b/src/game/editor/EditorSystem.cc @@ -3,6 +3,7 @@ #include "console/Console.hh" #include "core/Tracing.hh" #include "ecs/EcsImpl.hh" +#include "game/GameEntities.hh" #include "game/SceneManager.hh" namespace sp { @@ -42,10 +43,6 @@ namespace sp { }); } - EditorSystem::~EditorSystem() { - GetSceneManager().QueueActionAndBlock(SceneAction::RemoveScene, "editor"); - } - void EditorSystem::OpenEditor(std::string targetName, bool flatMode) { auto lock = ecs::StartTransaction(lock, ecs::Name("player", "flatview")); + auto flatview = entities::Flatview.Get(lock); if (flatview.Has(lock)) { auto &query = flatview.Get(lock); for (auto &subQuery : query.queries) { @@ -93,7 +90,7 @@ namespace sp { auto &transform = inspector.Get(lock); - auto player = playerEntity.Get(lock); + auto player = entities::Player.Get(lock); if (!player.Has(lock)) return; if (target.Has(lock)) { @@ -107,7 +104,7 @@ namespace sp { transform.parent = {}; } else { transform.pose = ecs::Transform(glm::vec3(0, 1, -1)); - transform.parent = playerEntity; + transform.parent = entities::Player; } } } diff --git a/src/game/editor/EditorSystem.hh b/src/game/editor/EditorSystem.hh index 172a82fe9..25d38e9e4 100644 --- a/src/game/editor/EditorSystem.hh +++ b/src/game/editor/EditorSystem.hh @@ -8,12 +8,10 @@ namespace sp { class EditorSystem { public: EditorSystem(); - ~EditorSystem(); void OpenEditor(std::string targetName, bool flatMode = true); private: - ecs::EntityRef playerEntity = ecs::Name("player", "player"); ecs::EntityRef inspectorEntity = ecs::Name("editor", "inspector"); CFuncCollection funcs; diff --git a/src/game/game/CMakeLists.txt b/src/game/game/CMakeLists.txt index c0822a3cd..72a19bf55 100644 --- a/src/game/game/CMakeLists.txt +++ b/src/game/game/CMakeLists.txt @@ -1,5 +1,6 @@ target_sources(${PROJECT_GAME_LIB} PRIVATE + GameEntities.cc GameLogic.cc Scene.cc SceneManager.cc diff --git a/src/game/game/GameEntities.cc b/src/game/game/GameEntities.cc new file mode 100644 index 000000000..b1dfa96bf --- /dev/null +++ b/src/game/game/GameEntities.cc @@ -0,0 +1,14 @@ +#include "GameEntities.hh" + +#include "ecs/Ecs.hh" +#include "ecs/EntityRef.hh" + +namespace sp::entities { + + const ecs::EntityRef Spawn = ecs::Name("global", "spawn"); + const ecs::EntityRef Player = ecs::Name("player", "player"); + const ecs::EntityRef Head = ecs::Name("player", "head"); + const ecs::EntityRef Flatview = ecs::Name("player", "flatview"); + const ecs::EntityRef VrHmd = ecs::Name("vr", "hmd"); + +} // namespace sp::entities diff --git a/src/game/game/GameEntities.hh b/src/game/game/GameEntities.hh new file mode 100644 index 000000000..d877608f8 --- /dev/null +++ b/src/game/game/GameEntities.hh @@ -0,0 +1,13 @@ +#pragma once + +#include "ecs/EntityRef.hh" + +namespace sp::entities { + + extern const ecs::EntityRef Spawn; + extern const ecs::EntityRef Player; + extern const ecs::EntityRef Head; + extern const ecs::EntityRef Flatview; + extern const ecs::EntityRef VrHmd; + +} // namespace sp::entities diff --git a/src/game/game/GameLogic.hh b/src/game/game/GameLogic.hh index b93bb4a2f..2703abb30 100644 --- a/src/game/game/GameLogic.hh +++ b/src/game/game/GameLogic.hh @@ -1,6 +1,7 @@ #pragma once #include "core/RegisteredThread.hh" +#include "ecs/EntityRef.hh" namespace sp { diff --git a/src/game/game/Scene.cc b/src/game/game/Scene.cc index b0274c045..6f92f995a 100644 --- a/src/game/game/Scene.cc +++ b/src/game/game/Scene.cc @@ -50,7 +50,7 @@ namespace sp { ecs::Entity Scene::NewSystemEntity(ecs::Lock stagingLock, const std::shared_ptr &scene, ecs::Name entityName) { - Assertf(!ecs::IsLive(stagingLock), "Scene::NewSystemEntity must be called with a staging lock"); + Assertf(ecs::IsStaging(stagingLock), "Scene::NewSystemEntity must be called with a staging lock"); if (entityName) { if (!ValidateEntityName(entityName)) { Errorf("Invalid system entity name: %s", entityName.String()); @@ -72,7 +72,7 @@ namespace sp { } auto entity = stagingLock.NewEntity(); - entity.Set(stagingLock, entity, ecs::SceneInfo::Priority::System, scene); + entity.Set(stagingLock, entity, ecs::SceneInfo::Priority::System, scene, scene->properties); entity.Set(stagingLock, entityName); namedEntities.emplace(entityName, entity); references.emplace_back(entityName, entity); @@ -110,7 +110,7 @@ namespace sp { } auto entity = lock.NewEntity(); - entity.Set(lock, entity, priority, scene); + entity.Set(lock, entity, priority, scene, scene->properties); entity.Set(lock, entityName); namedEntities.emplace(entityName, entity); references.emplace_back(entityName, entity); @@ -121,7 +121,7 @@ namespace sp { ecs::Entity prefabRoot, std::string relativeName, ecs::Name scope) { - Assertf(!ecs::IsLive(stagingLock), "Scene::NewPrefabEntity must be called with a staging lock"); + Assertf(ecs::IsStaging(stagingLock), "Scene::NewPrefabEntity must be called with a staging lock"); ecs::Name entityName; if (!relativeName.empty()) { @@ -176,8 +176,8 @@ namespace sp { auto &entityName = e.Get(staging); // Find matching named entity in live scene - sceneInfo.liveId = ecs::EntityWith(live, entityName); - if (sceneInfo.liveId) { + sceneInfo.liveId = ecs::EntityRef(entityName).Get(live); + if (sceneInfo.liveId.Exists(live)) { // Entity overlaps with another scene ZoneScopedN("MergeEntity"); ZoneStr(entityName.String()); @@ -189,8 +189,8 @@ namespace sp { sceneInfo.liveId = live.NewEntity(); sceneInfo.liveId.Set(live, sceneInfo); sceneInfo.liveId.Set(live, entityName); - ecs::GEntityRefs.Set(entityName, e); - ecs::GEntityRefs.Set(entityName, sceneInfo.liveId); + ecs::GetEntityRefs().Set(entityName, e); + ecs::GetEntityRefs().Set(entityName, sceneInfo.liveId); } } for (auto e : staging.EntitiesWith()) { @@ -218,7 +218,7 @@ namespace sp { if (!e.Has(staging)) continue; auto &sceneInfo = e.Get(staging); auto scenePtr = sceneInfo.scene.lock(); - if (scenePtr != nullptr && scenePtr.get() != this) continue; + if (scenePtr.get() != this) continue; Assert(sceneInfo.stagingId == e, "Expected staging entity to match SceneInfo.stagingId"); if (sceneInfo.liveId) { @@ -237,7 +237,7 @@ namespace sp { if (!e.Has(live)) continue; auto &sceneInfo = e.Get(live); auto scenePtr = sceneInfo.scene.lock(); - if (scenePtr != nullptr && scenePtr.get() != this) continue; + if (scenePtr.get() != this) continue; Assert(sceneInfo.liveId == e, "Expected live entity to match SceneInfo.liveId"); if (!sceneInfo.stagingId) e.Destroy(live); diff --git a/src/game/game/SceneImpl.hh b/src/game/game/SceneImpl.hh index f054a3468..e9434fe78 100644 --- a/src/game/game/SceneImpl.hh +++ b/src/game/game/SceneImpl.hh @@ -15,8 +15,10 @@ namespace sp::scene { Tecs::Entity srcEnt, ecs::Lock dst, Tecs::Entity dstEnt) { - if constexpr (std::is_same() || std::is_same()) { + if constexpr (std::is_same()) { if (srcEnt.Has(src) && !dstEnt.Has(dst)) dstEnt.Set(dst, srcEnt.Get(src)); + } else if constexpr (std::is_same()) { + // Ignore, this is handled by the scene } else if constexpr (std::is_same()) { // Ignore, this is handled by TransformTree } else if constexpr (!Tecs::is_global_component()) { @@ -48,6 +50,8 @@ namespace sp::scene { !hasComponents[ecs::ECS::GetComponentIndex()]) { ent.Unset(lock); } + } else if constexpr (std::is_same()) { + // Ignore, this should always be set } else if constexpr (!Tecs::is_global_component()) { if (ent.Has(lock) && !hasComponents[ecs::ECS::template GetComponentIndex()]) { ent.Unset(lock); diff --git a/src/game/game/SceneManager.cc b/src/game/game/SceneManager.cc index d5830e6f7..d1c698b9f 100644 --- a/src/game/game/SceneManager.cc +++ b/src/game/game/SceneManager.cc @@ -2,12 +2,14 @@ #include "assets/Asset.hh" #include "assets/AssetManager.hh" +#include "assets/JsonHelpers.hh" #include "console/Console.hh" #include "console/ConsoleBindingManager.hh" #include "core/Logging.hh" #include "core/Tracing.hh" #include "ecs/EcsImpl.hh" #include "ecs/EntityReferenceManager.hh" +#include "game/GameEntities.hh" #include "game/Scene.hh" #include @@ -286,7 +288,7 @@ namespace sp { playerScene = LoadSceneJson("player", SceneType::World, ecs::SceneInfo::Priority::Player); if (playerScene) { PreloadAndApplyScene(playerScene, [this](auto stagingLock, auto liveLock, auto scene) { - auto stagingPlayer = scene->GetStagingEntity(ecs::Name("player", "player")); + auto stagingPlayer = scene->GetStagingEntity(entities::Player.Name()); if (stagingPlayer.template Has(stagingLock)) { auto &sceneInfo = stagingPlayer.template Get(stagingLock); player = sceneInfo.liveId; @@ -369,7 +371,7 @@ namespace sp { scene->RemoveScene(stagingLock, liveLock); scene.reset(); }); - ecs::GEntityRefs.Tick(this->interval); + ecs::GetEntityRefs().Tick(this->interval); } void SceneManager::QueueAction(SceneAction action, std::string sceneName, PreApplySceneCallback callback) { @@ -475,16 +477,55 @@ namespace sp { Errorf("Failed to parse scene (%s): %s", sceneName, err); return nullptr; } + if (!root.is()) { + Errorf("Failed to parse scene (%s): %s", sceneName, root.to_str()); + return nullptr; + } + auto &sceneObj = root.get(); auto scene = make_shared(sceneName, sceneType, asset); ecs::EntityScope scope; scope.scene = scene; scope.prefix.scene = scene->name; - { + if (sceneObj.count("priority")) { + json::Load(scope, priority, sceneObj["priority"]); + } + + if (sceneObj.count("properties")) { + scene->properties = make_shared(); + auto &properties = *scene->properties; + auto &propertiesValue = sceneObj["properties"]; + if (propertiesValue.is()) { + for (auto &property : propertiesValue.get()) { + if (property.first == "gravity") { + json::Load(scope, properties.fixedGravity, property.second); + } else if (property.first == "gravity_transform") { + json::Load(scope, properties.gravityTransform, property.second); + } else if (property.first == "gravity_func") { + if (property.second.is()) { + auto gravityFunc = property.second.get(); + if (gravityFunc == "station_spin") { + properties.gravityFunction = [](glm::vec3 position) { + position.z = 0; + // Derived from centripetal acceleration formula, rotating around the origin + const float spinRpm = 2.42f; // Calculated for ~1G at 153m radius + float spinTerm = M_PI * spinRpm / 30; + return spinTerm * spinTerm * position; + }; + } + } + } + } + } else { + Errorf("Scene contains invalid properties (%s): %s", sceneName, propertiesValue.to_str()); + } + } + + if (sceneObj.count("entities")) { auto lock = ecs::StartStagingTransaction(); - auto &entityList = root.get()["entities"]; + auto &entityList = sceneObj["entities"]; std::vector entities; for (auto &value : entityList.get()) { auto ent = value.get(); @@ -493,7 +534,6 @@ namespace sp { auto relativeName = hasName ? ent["name"].get() : ""; ecs::Entity entity = scene->NewRootEntity(lock, scene, priority, relativeName); - entity.Set(lock, entity, priority, scene); for (auto comp : ent) { if (comp.first.empty() || comp.first[0] == '_' || comp.first == "name") continue; @@ -545,6 +585,9 @@ namespace sp { picojson::value root; string err = picojson::parse(root, bindingConfig->String()); if (!err.empty()) Abortf("Failed to parse input binding json file: %s", err); + if (!root.is()) { + Abortf("Failed to parse input binding json: %s", root.to_str()); + } { auto lock = ecs::StartStagingTransaction(); @@ -578,7 +621,7 @@ namespace sp { void SceneManager::TranslateSceneByConnection(const std::shared_ptr &scene) { auto stagingLock = ecs::StartStagingTransaction, ecs::Write>(); - auto liveLock = ecs::StartTransaction>(); + auto liveLock = ecs::StartTransaction>(); ecs::Entity liveConnection, stagingConnection; for (auto &e : stagingLock.EntitiesWith()) { @@ -587,10 +630,13 @@ namespace sp { if (sceneInfo.scene.lock() != scene) continue; auto &name = e.Get(stagingLock); - liveConnection = ecs::EntityWith(liveLock, name); + liveConnection = ecs::EntityRef(name).Get(liveLock); if (liveConnection.Has(liveLock)) { - stagingConnection = e; - break; + auto &connection = liveConnection.Get(liveLock); + if (sp::contains(connection.scenes, scene->name)) { + stagingConnection = e; + break; + } } } if (stagingConnection.Has(stagingLock) && @@ -600,6 +646,13 @@ namespace sp { stagingConnection.Get(stagingLock).GetGlobalTransform(stagingLock); glm::quat deltaRotation = liveTransform.GetRotation() * glm::inverse(stagingTransform.GetRotation()); glm::vec3 deltaPos = liveTransform.GetPosition() - deltaRotation * stagingTransform.GetPosition(); + ecs::Transform deltaTransform(deltaPos, deltaRotation); + + if (scene->properties) { + auto &properties = *scene->properties; + properties.fixedGravity = deltaRotation * properties.fixedGravity; + properties.gravityTransform = deltaTransform * properties.gravityTransform; + } for (auto &e : stagingLock.EntitiesWith()) { if (!e.Has(stagingLock)) continue; @@ -608,8 +661,9 @@ namespace sp { auto &transform = e.Get(stagingLock); if (!transform.parent) { - transform.pose.SetPosition(deltaRotation * transform.pose.GetPosition() + deltaPos); - transform.pose.SetRotation(deltaRotation * transform.pose.GetRotation()); + transform.pose = deltaTransform * transform.pose; + // transform.pose.SetPosition(deltaRotation * transform.pose.GetPosition() + deltaPos); + // transform.pose.SetRotation(deltaRotation * transform.pose.GetRotation()); if (e.Has(stagingLock)) { auto &animation = e.Get(stagingLock); @@ -649,25 +703,18 @@ namespace sp { void SceneManager::RespawnPlayer( ecs::Lock, ecs::Write> lock, ecs::Entity player) { - auto spawn = ecs::EntityWith(lock, ecs::Name("global", "spawn")); - if (spawn.Has(lock)) { - auto spawnTransform = spawn.Get(lock); - spawnTransform.SetScale(glm::vec3(1)); - if (player.Has(lock)) { - auto &playerTransform = player.Get(lock); - auto &playerTree = player.Get(lock); - Assert(!playerTree.parent, "Player entity should not have a TransformTree parent"); - playerTransform = spawnTransform; - playerTree.pose = spawnTransform; - } - auto vrOrigin = ecs::EntityWith(lock, ecs::Name("vr", "origin")); - if (vrOrigin.Has(lock)) { - auto &vrTransform = vrOrigin.Get(lock); - auto &vrTree = vrOrigin.Get(lock); - Assert(!vrTree.parent, "VR Origin entity should not have a TransformTree parent"); - vrTransform = spawnTransform; - vrTree.pose = spawnTransform; - } + auto spawn = entities::Spawn.Get(lock); + if (!spawn.Has(lock)) return; + + auto spawnTransform = spawn.Get(lock); + spawnTransform.SetScale(glm::vec3(1)); + + if (player.Has(lock)) { + auto &playerTransform = player.Get(lock); + auto &playerTree = player.Get(lock); + Assert(!playerTree.parent, "Player entity should not have a TransformTree parent"); + playerTransform = spawnTransform; + playerTree.pose = spawnTransform; } } diff --git a/src/game/game/SceneManager.hh b/src/game/game/SceneManager.hh index 744901dd5..a5863dfb3 100644 --- a/src/game/game/SceneManager.hh +++ b/src/game/game/SceneManager.hh @@ -113,6 +113,7 @@ namespace sp { std::shared_ptr playerScene, bindingsScene; CFuncCollection funcs; + friend class SceneInfo; friend class Scene; }; diff --git a/src/graphics/graphics/gui/SystemGuiManager.cc b/src/graphics/graphics/gui/SystemGuiManager.cc index cf5b97f44..9b210061e 100644 --- a/src/graphics/graphics/gui/SystemGuiManager.cc +++ b/src/graphics/graphics/gui/SystemGuiManager.cc @@ -3,6 +3,7 @@ #include "core/Tracing.hh" #include "ecs/EcsImpl.hh" #include "ecs/EntityReferenceManager.hh" +#include "game/GameEntities.hh" #include "game/Scene.hh" #include "game/SceneManager.hh" #include "input/BindingNames.hh" @@ -23,12 +24,14 @@ namespace sp { ent.Set(lock, INPUT_EVENT_MENU_SCROLL, INPUT_EVENT_MENU_TEXT_INPUT); auto &signalBindings = ent.Set(lock); - signalBindings.Bind(INPUT_SIGNAL_MENU_PRIMARY_TRIGGER, playerEntity, INPUT_SIGNAL_MENU_PRIMARY_TRIGGER); + signalBindings.Bind(INPUT_SIGNAL_MENU_PRIMARY_TRIGGER, + entities::Player, + INPUT_SIGNAL_MENU_PRIMARY_TRIGGER); signalBindings.Bind(INPUT_SIGNAL_MENU_SECONDARY_TRIGGER, - playerEntity, + entities::Player, INPUT_SIGNAL_MENU_SECONDARY_TRIGGER); - signalBindings.Bind(INPUT_SIGNAL_MENU_CURSOR_X, playerEntity, INPUT_SIGNAL_MENU_CURSOR_X); - signalBindings.Bind(INPUT_SIGNAL_MENU_CURSOR_Y, playerEntity, INPUT_SIGNAL_MENU_CURSOR_Y); + signalBindings.Bind(INPUT_SIGNAL_MENU_CURSOR_X, entities::Player, INPUT_SIGNAL_MENU_CURSOR_X); + signalBindings.Bind(INPUT_SIGNAL_MENU_CURSOR_Y, entities::Player, INPUT_SIGNAL_MENU_CURSOR_Y); }); } diff --git a/src/graphics/graphics/gui/SystemGuiManager.hh b/src/graphics/graphics/gui/SystemGuiManager.hh index 23143873b..7e9954202 100644 --- a/src/graphics/graphics/gui/SystemGuiManager.hh +++ b/src/graphics/graphics/gui/SystemGuiManager.hh @@ -13,7 +13,6 @@ namespace sp { protected: ecs::EntityRef guiEntity; ecs::EntityRef keyboardEntity = ecs::Name("input", "keyboard"); - ecs::EntityRef playerEntity = ecs::Name("player", "player"); ecs::FocusLayer focusLayer; }; diff --git a/src/physx/physx/CharacterControlSystem.cc b/src/physx/physx/CharacterControlSystem.cc index 13dac630a..6ae943472 100644 --- a/src/physx/physx/CharacterControlSystem.cc +++ b/src/physx/physx/CharacterControlSystem.cc @@ -5,6 +5,9 @@ #include "core/Logging.hh" #include "ecs/Ecs.hh" #include "ecs/EcsImpl.hh" +#include "game/GameEntities.hh" +#include "game/Scene.hh" +#include "game/SceneManager.hh" #include "input/BindingNames.hh" #include "physx/PhysxManager.hh" #include "physx/PhysxUtils.hh" @@ -14,6 +17,7 @@ #include #include #include +#include #include namespace sp { @@ -25,14 +29,83 @@ namespace sp { static CVar CVarCharacterSprintSpeed("p.CharacterSprintSpeed", 5.0, "Character controller sprint speed (m/s)"); + static CVar CVarCharacterMaxHeadSpeed("p.CharacterMaxHeadSpeed", + 5.0, + "Character controller max head movement speed (m/s)"); + static CVar CVarCharacterAirStrafe("p.CharacterAirStrafe", + 0.8, + "Character controller air strafe multiplier"); + static CVar CVarCharacterJumpHeight("p.CharacterJumpHeight", + 0.4, + "Character controller gravity jump multiplier"); + static CVar CVarCharacterFlipSpeed("p.CharacterFlipSpeed", + 10, + "Character controller reorientation speed (degrees/s)"); + static CVar CVarCharacterMinFlipGravity("p.CharacterMinFlipGravity", + 8.0, + "Character controller minimum gravity required to orient (m/s^2)"); CharacterControlSystem::CharacterControlSystem(PhysxManager &manager) : manager(manager) { + GetSceneManager().QueueActionAndBlock(SceneAction::ApplySystemScene, + "character", + [this](ecs::Lock lock, std::shared_ptr scene) { + auto ent = scene->NewSystemEntity(lock, scene, entities::Head.Name()); + auto &tree = ent.Set(lock); + tree.parent = entities::Flatview; + auto &script = ent.Set(lock); + script.AddOnTick(ecs::EntityScope{scene, ecs::Name(scene->name, "")}, + [](ecs::ScriptState &state, + ecs::Lock lock, + ecs::Entity ent, + chrono_clock::duration interval) { + if (!ent.Has(lock)) return; + auto &tree = ent.Get(lock); + + ecs::Entity hmd = entities::VrHmd.Get(lock); + ecs::Entity flatview = entities::Flatview.Get(lock); + if (hmd.Has(lock) && hmd.Get(lock).parent) { + tree.parent = hmd; + } else { + tree.parent = flatview; + } + }); + }); + auto lock = ecs::StartTransaction(); characterControllerObserver = lock.Watch>(); } + glm::vec3 getHeadPosition(const PxCapsuleController *pxController) { + auto headOffset = PxVec3ToGlmVec3(pxController->getUpDirection()) * pxController->getHeight() * 0.5f; + return PxExtendedVec3ToGlmVec3(pxController->getPosition()) + headOffset; + } + + void setHeadPosition(PxCapsuleController *pxController, glm::vec3 position) { + auto upVector = PxVec3ToGlmVec3(pxController->getUpDirection()); + auto capsulePosition = position - upVector * pxController->getHeight() * 0.5f; + pxController->setPosition(GlmVec3ToPxExtendedVec3(capsulePosition)); + + // Updating the controller position does not update the underlying actor, we need to do it ourselves. + PxTransform globalPose(GlmVec3ToPxVec3(capsulePosition)); + globalPose.q = PxShortestRotation(PxVec3(1.0f, 0.0f, 0.0f), pxController->getUpDirection()); + pxController->getActor()->setGlobalPose(globalPose); + } + + void setFootPosition(PxCapsuleController *pxController, glm::vec3 position) { + auto upVector = PxVec3ToGlmVec3(pxController->getUpDirection()); + auto footOffset = pxController->getHeight() * 0.5f + pxController->getRadius() + + pxController->getContactOffset(); + auto capsulePosition = position + upVector * footOffset; + pxController->setPosition(GlmVec3ToPxExtendedVec3(capsulePosition)); + + // Updating the controller position does not update the underlying actor, we need to do it ourselves. + PxTransform globalPose(GlmVec3ToPxVec3(capsulePosition)); + globalPose.q = PxShortestRotation(PxVec3(1.0f, 0.0f, 0.0f), pxController->getUpDirection()); + pxController->getActor()->setGlobalPose(globalPose); + } + void CharacterControlSystem::Frame(ecs::Lock, + ecs::Read, ecs::Write> lock) { // Update PhysX with any added or removed CharacterControllers ecs::ComponentEvent controllerEvent; @@ -42,7 +115,6 @@ namespace sp { auto &controller = controllerEvent.entity.Get(lock); if (!controller.pxController) { auto characterUserData = new CharacterControllerUserData(controllerEvent.entity); - characterUserData->actorData.material = std::shared_ptr( manager.pxPhysics->createMaterial(0.3f, 0.3f, 0.3f), [](auto *ptr) { @@ -59,10 +131,17 @@ namespace sp { // Decreasing the contactOffset value causes the player to be able to stand on // very thin (and likely unintentional) ledges. desc.contactOffset = 0.05f; - desc.material = characterUserData->actorData.material.get(); + desc.climbingMode = PxCapsuleClimbingMode::eCONSTRAINED; + desc.nonWalkableMode = PxControllerNonWalkableMode::ePREVENT_CLIMBING_AND_FORCE_SLIDING; + desc.slopeLimit = cos(glm::radians(30.0f)); + + desc.material = characterUserData->actorData.material.get(); desc.userData = characterUserData; + // Offset capsule position so the feet are the origin + desc.position.y += desc.contactOffset + desc.radius + desc.height * 0.5f; + auto pxController = manager.controllerManager->createController(desc); Assert(pxController->getType() == PxControllerShapeType::eCAPSULE, "Physx did not create a valid PxCapsuleController"); @@ -105,93 +184,166 @@ namespace sp { transformTree.parent.Name().String()); auto &transform = transformTree.pose; + auto head = controller.head.Get(lock); + if (!head.Has(lock)) continue; + + ecs::SceneProperties sceneProperties = {}; + if (entity.Has(lock)) { + auto &properties = entity.Get(lock).properties; + if (properties) sceneProperties = *properties; + } + auto actor = controller.pxController->getActor(); auto userData = (CharacterControllerUserData *)controller.pxController->getUserData(); float contactOffset = controller.pxController->getContactOffset(); + float capsuleRadius = controller.pxController->getRadius(); - float targetHeight = ecs::PLAYER_CAPSULE_HEIGHT; - glm::vec3 targetPosition = transform.GetPosition(); + float targetHeight = controller.pxController->getHeight(); + auto headRoot = ecs::TransformTree::GetRoot(lock, head); + auto &headTree = head.Get(lock); + auto &rootTree = headRoot.Get(lock); + auto headRelativeRoot = headTree.GetRelativeTransform(lock, headRoot); - auto target = controller.target.Get(lock); - if (!target.Has(lock) || !target.Get(lock).parent) { - target = controller.fallbackTarget.Get(lock); - } - if (target.Has(lock)) { - auto &targetTree = target.Get(lock); - targetPosition = targetTree.GetGlobalTransform(lock).GetPosition(); - targetPosition.y = transform.GetPosition().y; - targetHeight = std::max(0.1f, targetTree.pose.GetPosition().y - ecs::PLAYER_RADIUS); - - if (target != userData->target) { - // Move the target to the physics actor when the target changes - auto movementProxy = controller.movementProxy.Get(lock); - if (movementProxy.Has(lock)) { - auto &proxyTransform = movementProxy.Get(lock); - auto deltaPos = userData->actorData.pose.GetPosition() - targetPosition; - proxyTransform.pose.Translate(deltaPos); - targetPosition += deltaPos; - } + auto playerHeight = headRelativeRoot.GetPosition().y; + targetHeight = std::max(0.1f, playerHeight - capsuleRadius - contactOffset); - userData->target = target; - } - } + ecs::Transform headRelativePlayer; - // If the origin moved, teleport the controller - if (transform.GetPosition() != userData->actorData.pose.GetPosition()) { + // If the entity moved or the head was retargeted, teleport the controller + if (transform != userData->actorData.pose || headTree.parent != userData->headTarget) { controller.pxController->setHeight(targetHeight); - controller.pxController->setFootPosition(GlmVec3ToPxExtendedVec3(targetPosition)); + controller.pxController->setUpDirection(GlmVec3ToPxVec3(transform.GetUp())); + setFootPosition(controller.pxController, transform.GetPosition()); + + // Move the head to the new player position and ensure it is facing forward + auto forwardRelativeRoot = headRelativeRoot.GetForward(); + if (std::abs(forwardRelativeRoot.y) > 0.999) { + forwardRelativeRoot = headRelativeRoot.GetRotation() * glm::vec3(0, -forwardRelativeRoot.y, 0); + } + forwardRelativeRoot.y = 0; + auto deltaRotation = glm::rotation(glm::normalize(forwardRelativeRoot), glm::vec3(0, 0, -1)); + headRelativePlayer.SetRotation(deltaRotation * headRelativeRoot.GetRotation()); + headRelativePlayer.SetPosition(glm::vec3(0, headRelativeRoot.GetPosition().y, 0)); + + auto targetTransform = transform * headRelativePlayer; + ecs::TransformTree::MoveViaRoot(lock, head, targetTransform); - // Updating the controller position does not update the underlying actor, we need to do it ourselves. - auto capsulePosition = controller.pxController->getPosition(); - auto actorPose = actor->getGlobalPose(); - actorPose.p = PxVec3(capsulePosition.x, capsulePosition.y, capsulePosition.z); - actor->setGlobalPose(actorPose); + // Logf("Teleport: %s, Up: %s, Height: %f Forward: %s", + // glm::to_string(targetTransform.GetPosition()), + // glm::to_string(targetTransform.GetUp()), + // targetHeight, + // glm::to_string(targetTransform.GetForward())); userData->onGround = false; - userData->actorData.pose = ecs::Transform(targetPosition); + userData->actorData.pose = transform; userData->actorData.velocity = glm::vec3(0); + userData->headTarget = headTree.parent.Get(lock); + } else { + headRelativePlayer = headTree.GetRelativeTransform(lock, entity); + } + // Logf("Start headRelativePlayer pos: %s", glm::to_string(headRelativePlayer.GetPosition())); + + bool noclip = ecs::SignalBindings::GetSignal(lock, entity, INPUT_SIGNAL_MOVE_NOCLIP) >= 0.5; + if (userData->noclipping != noclip) { + manager.SetCollisionGroup(actor, noclip ? ecs::PhysicsGroup::NoClip : ecs::PhysicsGroup::Player); + userData->noclipping = noclip; + controller.pxController->invalidateCache(); } // Update the capsule height auto currentHeight = controller.pxController->getHeight(); if (currentHeight != targetHeight) { - if (targetHeight > currentHeight) { + if (!noclip && targetHeight > currentHeight) { // Check to see if there is room to expand the capsule PxSweepBuffer hit; - PxCapsuleGeometry capsuleGeometry(ecs::PLAYER_RADIUS, currentHeight * 0.5f); + PxCapsuleGeometry capsuleGeometry(capsuleRadius, currentHeight * 0.5f); auto sweepDist = targetHeight - currentHeight + contactOffset; bool status = manager.scene->sweep(capsuleGeometry, actor->getGlobalPose(), - PxVec3(0, 1, 0), + controller.pxController->getUpDirection(), sweepDist, hit, PxHitFlags(), PxQueryFilterData(filterData, PxQueryFlag::eSTATIC | PxQueryFlag::eDYNAMIC)); if (status) { auto headroom = std::max(hit.block.distance - contactOffset, 0.0f); - controller.pxController->resize(currentHeight + headroom); + currentHeight += headroom; } else { - controller.pxController->resize(targetHeight); + currentHeight = targetHeight; } } else { - controller.pxController->resize(targetHeight); + currentHeight = targetHeight; } - // Updating the controller position does not update the underlying actor, we need to do it ourselves. - auto capsulePosition = controller.pxController->getPosition(); - auto actorPose = actor->getGlobalPose(); - actorPose.p = PxVec3(capsulePosition.x, capsulePosition.y, capsulePosition.z); - actor->setGlobalPose(actorPose); - currentHeight = controller.pxController->getHeight(); + controller.pxController->setHeight(currentHeight); + setFootPosition(controller.pxController, transform.GetPosition()); + } + + // Update the capsule orientation + glm::vec3 gravityForce = sceneProperties.fixedGravity; + if (sceneProperties.gravityFunction) { + auto gravityPos = sceneProperties.gravityTransform.GetInverse() * + glm::vec4(getHeadPosition(controller.pxController), 1); + gravityForce = sceneProperties.gravityTransform.GetRotation() * + sceneProperties.gravityFunction(gravityPos); + } + auto gravityStrength = glm::length(gravityForce); + auto gravityDir = glm::normalize(gravityForce); + if (gravityStrength > 0 && gravityStrength > CVarCharacterMinFlipGravity.Get()) { + auto angleDiff = glm::angle(transform.GetUp(), -gravityDir); + if (angleDiff > 0.0001) { + auto scaleFactor = std::min(angleDiff, glm::radians(CVarCharacterFlipSpeed.Get()) * dt) / angleDiff; + auto deltaRotation = glm::rotation(transform.GetUp(), -gravityDir); + deltaRotation = glm::slerp(glm::quat(), deltaRotation, scaleFactor); + auto targetUpVector = deltaRotation * transform.GetUp(); + + bool shouldRotate = true; + if (!noclip) { + auto halfHeight = controller.pxController->getHeight() * 0.5f; + auto currentOffset = transform.GetUp() * halfHeight; + auto newOffset = targetUpVector * halfHeight; + + PxOverlapHit touch; + PxOverlapBuffer overlapHit; + overlapHit.touches = &touch; + overlapHit.maxNbTouches = 1; + PxCapsuleGeometry capsuleGeometry(capsuleRadius, currentHeight * 0.5f); + auto globalPose = actor->getGlobalPose(); + globalPose.p += GlmVec3ToPxVec3(currentOffset - newOffset); + globalPose.q = PxShortestRotation(PxVec3(1.0f, 0.0f, 0.0f), GlmVec3ToPxVec3(targetUpVector)); + shouldRotate = !manager.scene->overlap(capsuleGeometry, + globalPose, + overlapHit, + PxQueryFilterData(filterData, PxQueryFlag::eSTATIC | PxQueryFlag::eDYNAMIC)); + } + + // Only rotate the capsule in there is room to do so + if (shouldRotate) { + auto headPosition = getHeadPosition(controller.pxController); + controller.pxController->setUpDirection(GlmVec3ToPxVec3(targetUpVector)); + setHeadPosition(controller.pxController, headPosition); + + transform.Rotate(deltaRotation); + transform.SetPosition(PxExtendedVec3ToGlmVec3(controller.pxController->getFootPosition())); + + // Rotate the head to match + auto targetTransform = transform * headRelativePlayer; + ecs::TransformTree::MoveViaRoot(lock, head, targetTransform); + + // Logf("Rotating Head: %s, Up: %s, Forward: %s", + // glm::to_string(targetTransform.GetPosition()), + // glm::to_string(targetTransform.GetUp()), + // glm::to_string(targetTransform.GetForward())); + } + } } // Read character movement inputs - glm::vec3 lateralMovement = glm::vec3(0); - lateralMovement.x = ecs::SignalBindings::GetSignal(lock, entity, INPUT_SIGNAL_MOVE_WORLD_X); - lateralMovement.z = ecs::SignalBindings::GetSignal(lock, entity, INPUT_SIGNAL_MOVE_WORLD_Z); - float verticalMovement = ecs::SignalBindings::GetSignal(lock, entity, INPUT_SIGNAL_MOVE_WORLD_Y); + glm::vec3 movementInput = glm::vec3(0); + movementInput.x = ecs::SignalBindings::GetSignal(lock, entity, INPUT_SIGNAL_MOVE_RELATIVE_X); + movementInput.y = ecs::SignalBindings::GetSignal(lock, entity, INPUT_SIGNAL_MOVE_RELATIVE_Y); + movementInput.z = ecs::SignalBindings::GetSignal(lock, entity, INPUT_SIGNAL_MOVE_RELATIVE_Z); bool sprint = ecs::SignalBindings::GetSignal(lock, entity, INPUT_SIGNAL_MOVE_SPRINT) >= 0.5; - bool noclip = ecs::SignalBindings::GetSignal(lock, entity, INPUT_SIGNAL_MOVE_NOCLIP) >= 0.5; bool jump = false; if (entity.Has(lock)) { @@ -202,35 +354,30 @@ namespace sp { } float speed = sprint ? CVarCharacterSprintSpeed.Get() : CVarCharacterMovementSpeed.Get(); - if (lateralMovement != glm::vec3(0)) lateralMovement = glm::normalize(lateralMovement) * speed; - verticalMovement = std::clamp(verticalMovement, -1.0f, 1.0f) * speed; - - auto targetDelta = targetPosition - userData->actorData.pose.GetPosition(); - - if (userData->noclipping != noclip) { - manager.SetCollisionGroup(actor, noclip ? ecs::PhysicsGroup::NoClip : ecs::PhysicsGroup::Player); - userData->noclipping = noclip; - controller.pxController->invalidateCache(); + if (movementInput.x != 0 || movementInput.z != 0) { + auto normalized = glm::normalize(glm::vec3(movementInput.x, 0, movementInput.z)) * speed; + movementInput.x = normalized.x; + movementInput.z = normalized.z; } + movementInput.y = std::clamp(movementInput.y, -1.0f, 1.0f) * speed; + + // Use head as a directional movement input + glm::vec3 headInput = headRelativePlayer.GetPosition(); + headInput.y = 0; + headInput = transform.GetRotation() * headInput; + // Logf("Head input: %s", glm::to_string(headInput)); // Update the capsule position, velocity, and onGround flag if (noclip) { - targetPosition += lateralMovement * dt; - targetPosition.y += verticalMovement * dt; - targetPosition += targetDelta; - controller.pxController->setFootPosition(GlmVec3ToPxExtendedVec3(targetPosition)); - transform.SetPosition(targetPosition); - - auto movementProxy = controller.movementProxy.Get(lock); - if (movementProxy.Has(lock)) { - auto &proxyTransform = movementProxy.Get(lock); - auto deltaPos = (lateralMovement + glm::vec3(0, verticalMovement, 0)) * dt + targetDelta; - proxyTransform.pose.Translate(deltaPos); - } + auto movementVelocity = transform.GetRotation() * movementInput; + transform.Translate(movementVelocity * dt + headInput); + setFootPosition(controller.pxController, transform.GetPosition()); + + // Move the head to the new player position + rootTree.pose.Translate(movementVelocity * dt); userData->onGround = false; - userData->actorData.velocity = lateralMovement; - userData->actorData.velocity.y = verticalMovement; + userData->actorData.velocity = movementVelocity; } else { PxControllerState state; controller.pxController->getState(state); @@ -242,50 +389,63 @@ namespace sp { PxOverlapBuffer overlapHit; overlapHit.touches = &touch; overlapHit.maxNbTouches = 1; - PxCapsuleGeometry capsuleGeometry(ecs::PLAYER_RADIUS, currentHeight * 0.5f); + PxCapsuleGeometry capsuleGeometry(capsuleRadius, currentHeight * 0.5f); bool inGround = manager.scene->overlap(capsuleGeometry, actor->getGlobalPose(), overlapHit, PxQueryFilterData(filterData, PxQueryFlag::eSTATIC | PxQueryFlag::eDYNAMIC)); - glm::vec3 displacement; + glm::vec3 velocityRelativePlayer, displacement; if (userData->onGround || inGround) { - displacement = (PxVec3ToGlmVec3(state.deltaXP) + lateralMovement) * dt; + velocityRelativePlayer = glm::inverse(transform.GetRotation()) * PxVec3ToGlmVec3(state.deltaXP); + velocityRelativePlayer += glm::vec3(movementInput.x, 0, movementInput.z); + auto relative = velocityRelativePlayer * dt; if (jump) { // Move up slightly first to detach the player from the floor - displacement.y = std::max(state.deltaXP.y * dt, 0.0f) + contactOffset; + relative.y = std::max(0.0f, relative.y) + contactOffset; } else { // Always move down slightly for consistent onGround detection - displacement.y = -contactOffset; + relative.y = -contactOffset; } + displacement = transform.GetRotation() * relative; } else { - userData->actorData.velocity += lateralMovement * ecs::PLAYER_AIR_STRAFE * dt; + auto worldMovement = transform.GetRotation() * movementInput; + userData->actorData.velocity += worldMovement * CVarCharacterAirStrafe.Get() * dt; + velocityRelativePlayer = glm::inverse(transform.GetRotation()) * userData->actorData.velocity; displacement = userData->actorData.velocity * dt; } + + auto maxHeadInput = CVarCharacterMaxHeadSpeed.Get() * dt; + headInput = glm::clamp(headInput, -maxHeadInput, maxHeadInput); + // If the displacement is opposite headInput, make movementInput priority + headInput -= glm::clamp(headInput, -glm::abs(displacement), glm::abs(displacement)); + // Logf("Disp: %s + %s, State:%u, On:%u, In:%u, DeltaXp: %s, Vel: %s", // glm::to_string(displacement), - // glm::to_string(targetDelta), + // glm::to_string(headInput), // state.collisionFlags & PxControllerCollisionFlag::eCOLLISION_DOWN, // userData->onGround, // inGround, // glm::to_string(PxVec3ToGlmVec3(state.deltaXP)), // glm::to_string(userData->actorData.velocity)); + auto oldPosition = PxExtendedVec3ToGlmVec3(controller.pxController->getFootPosition()); + auto moveResult = - controller.pxController->move(GlmVec3ToPxVec3(displacement + targetDelta), 0, dt, moveQueryFilter); + controller.pxController->move(GlmVec3ToPxVec3(displacement + headInput), 0, dt, moveQueryFilter); controller.pxController->getState(state); auto newPosition = PxExtendedVec3ToGlmVec3(controller.pxController->getFootPosition()); - auto deltaPos = newPosition - userData->actorData.pose.GetPosition() - targetDelta; PxSweepBuffer sweepHit; auto sweepStart = actor->getGlobalPose(); sweepStart.p = physx::toVec3(controller.pxController->getPosition()); + sweepStart.p += controller.pxController->getUpDirection() * contactOffset; bool onGround = manager.scene->sweep(capsuleGeometry, sweepStart, -controller.pxController->getUpDirection(), - controller.pxController->getContactOffset(), + contactOffset, sweepHit, PxHitFlag::ePOSITION, PxQueryFilterData(filterData, PxQueryFlag::eSTATIC | PxQueryFlag::eDYNAMIC)); @@ -301,10 +461,9 @@ namespace sp { // Logf("OnGround, Vel: %s", glm::to_string(userData->actorData.velocity)); } else { if (userData->onGround || inGround) { - userData->actorData.velocity = PxVec3ToGlmVec3(state.deltaXP); - userData->actorData.velocity.x += displacement.x / dt; - userData->actorData.velocity.z += displacement.z / dt; - if (jump) userData->actorData.velocity.y += ecs::PLAYER_JUMP_VELOCITY; + // When leaving a surface, use the velocity from the input state + userData->actorData.velocity = transform.GetRotation() * velocityRelativePlayer; + if (jump) userData->actorData.velocity -= gravityForce * CVarCharacterJumpHeight.Get(); // Logf("WasOn: %u, In: %u, Jump: %u, DeltaXp: %s, Vel: %s", // userData->onGround, // inGround, @@ -312,39 +471,42 @@ namespace sp { // glm::to_string(PxVec3ToGlmVec3(state.deltaXP)), // glm::to_string(userData->actorData.velocity)); } else { - userData->actorData.velocity = deltaPos / dt; - userData->actorData.velocity.y -= ecs::PLAYER_GRAVITY * dt; - // Logf("OffGround, DeltaPos: %s - %s, Vel: %s", - // glm::to_string(newPosition - userData->actorData.pose.GetPosition()), - // glm::to_string(targetDelta), + userData->actorData.velocity = (newPosition - oldPosition - headInput) / dt; + userData->actorData.velocity += gravityForce * dt; + // Logf("OffGround, DeltaPos: %s, Disp %s, HeadInput: %s, Vel: %s", + // glm::to_string(newPosition - oldPosition), + // glm::to_string(displacement), + // glm::to_string(headInput), // glm::to_string(userData->actorData.velocity)); } userData->onGround = false; } - auto movementProxy = controller.movementProxy.Get(lock); - if (movementProxy.Has(lock)) { - auto &proxyTransform = movementProxy.Get(lock); - // Only move the VR player if the movement is in line with the input displacement - // This allows the headset to detatch from the player capsule so they don't get pushed back by walls - auto absDeltaPos = glm::abs(deltaPos) + 0.00001f; - glm::vec3 clampRatio = glm::min(glm::abs(displacement), absDeltaPos) / absDeltaPos; - if (glm::sign(deltaPos.x) != glm::sign(displacement.x)) clampRatio.x = 0.0f; - clampRatio.y = 1.0f; - if (glm::sign(deltaPos.z) != glm::sign(displacement.z)) clampRatio.z = 0.0f; - proxyTransform.pose.Translate(deltaPos * clampRatio); - - if (movementProxy.Has(lock) && manager.actors.count(movementProxy) != 0) { - auto &proxyActor = manager.actors[movementProxy]; - if (proxyActor && proxyActor->userData) { - auto proxyUserData = (ActorUserData *)proxyActor->userData; - proxyUserData->velocity = userData->actorData.velocity; - } - } - } - + // Move the entities to their new positions transform.SetPosition(newPosition); + + auto deltaPos = newPosition - oldPosition; + // Subtract the head input from the movement without moving backwards. + // This allows the head to detatch from the player when colliding with walls. + deltaPos -= glm::clamp(headInput, -glm::abs(deltaPos), glm::abs(deltaPos)); + + auto targetTransform = headTree.GetGlobalTransform(lock); + targetTransform.Translate(deltaPos); + ecs::TransformTree::MoveViaRoot(lock, head, targetTransform); + + // Logf("Moving Head: %s Up: %s, Forward: %s", + // glm::to_string(targetTransform.GetPosition()), + // glm::to_string(targetTransform.GetUp()), + // glm::to_string(targetTransform.GetForward())); + } + + if (headRoot.Has(lock) && manager.actors.count(headRoot) != 0) { + auto &proxyActor = manager.actors[headRoot]; + if (proxyActor && proxyActor->userData) { + auto proxyUserData = (ActorUserData *)proxyActor->userData; + proxyUserData->velocity = userData->actorData.velocity; + } } userData->actorData.pose = transform; diff --git a/src/physx/physx/CharacterControlSystem.hh b/src/physx/physx/CharacterControlSystem.hh index 857b30f88..7c7b42df0 100644 --- a/src/physx/physx/CharacterControlSystem.hh +++ b/src/physx/physx/CharacterControlSystem.hh @@ -15,7 +15,7 @@ namespace sp { ~CharacterControlSystem() {} void Frame(ecs::Lock, + ecs::Read, ecs::Write> lock); private: diff --git a/src/physx/physx/ConstraintSystem.cc b/src/physx/physx/ConstraintSystem.cc index a5c1df36a..cda8796e1 100644 --- a/src/physx/physx/ConstraintSystem.cc +++ b/src/physx/physx/ConstraintSystem.cc @@ -108,9 +108,9 @@ namespace sp { { // Apply Vertical Force auto maxAcceleration = CVarMaxVerticalConstraintForce.Get() / dynamic->getMass(); if (deltaPos.y > 0) { - maxAcceleration -= CVarGravity.Get(); + maxAcceleration -= -9.81f; // CVarGravity.Get(); } else { - maxAcceleration += CVarGravity.Get(); + maxAcceleration += -9.81f; // CVarGravity.Get(); } auto deltaTick = maxAcceleration * intervalSeconds; auto maxVelocity = std::sqrt(2 * std::max(0.0f, maxAcceleration) * std::abs(deltaPos.y)); @@ -125,7 +125,7 @@ namespace sp { auto deltaVelocity = targetVerticalVelocity - dynamic->getLinearVelocity().y; float force = deltaVelocity * tickFrequency * dynamic->getMass(); - force -= CVarGravity.Get() * dynamic->getMass(); + force -= -9.81f /*CVarGravity.Get()*/ * dynamic->getMass(); float forceAbs = std::abs(force) + 0.00001f; auto forceClampRatio = std::min(CVarMaxVerticalConstraintForce.Get(), forceAbs) / forceAbs; dynamic->addForce(PxVec3(0, force * forceClampRatio, 0)); diff --git a/src/physx/physx/PhysxManager.cc b/src/physx/physx/PhysxManager.cc index fff063d29..5498ddd89 100644 --- a/src/physx/physx/PhysxManager.cc +++ b/src/physx/physx/PhysxManager.cc @@ -25,7 +25,6 @@ namespace sp { using namespace physx; - CVar CVarGravity("x.Gravity", -9.81f, "Acceleration due to gravity (m/sec^2)"); CVar CVarPhysxDebugCollision("x.DebugColliders", false, "Show physx colliders"); CVar CVarPhysxDebugJoints("x.DebugJoints", false, "Show physx joints"); @@ -159,28 +158,6 @@ namespace sp { } void PhysxManager::Frame() { - // Wake up all actors and update the scene if gravity is changed. - if (CVarGravity.Changed()) { - ZoneScopedN("ChangeGravity"); - scene->setGravity(PxVec3(0.f, CVarGravity.Get(true), 0.f)); - - vector buffer(256, nullptr); - size_t startIndex = 0; - - while (true) { - uint32_t n = scene->getActors(PxActorTypeFlag::eRIGID_DYNAMIC, &buffer[0], buffer.size(), startIndex); - - for (uint32_t i = 0; i < n; i++) { - auto dynamic = buffer[i]->is(); - if (dynamic && !dynamic->getRigidBodyFlags().isSet(PxRigidBodyFlag::eKINEMATIC)) dynamic->wakeUp(); - } - - if (n < buffer.size()) break; - - startIndex += n; - } - } - if (CVarPhysxDebugCollision.Changed() || CVarPhysxDebugJoints.Changed()) { bool collision = CVarPhysxDebugCollision.Get(true); bool joints = CVarPhysxDebugJoints.Get(true); @@ -195,7 +172,7 @@ namespace sp { { // Sync ECS state to physx ZoneScopedN("Sync from ECS"); auto lock = ecs::StartTransaction, + ecs::Read, ecs::Write>(); // Delete actors for removed entities @@ -357,7 +334,7 @@ namespace sp { ZoneScoped; PxSceneDesc sceneDesc(pxPhysics->getTolerancesScale()); - sceneDesc.gravity = PxVec3(0.f, CVarGravity.Get(true), 0.f); + sceneDesc.gravity = PxVec3(0); // Gravity handled by scene properties sceneDesc.filterShader = PxDefaultSimulationFilterShader; using Group = ecs::PhysicsGroup; @@ -476,12 +453,18 @@ namespace sp { if (ph.dynamic) { actor = pxPhysics->createRigidDynamic(pxTransform); - if (ph.kinematic) actor->is()->setRigidBodyFlag(PxRigidBodyFlag::eKINEMATIC, true); + if (ph.kinematic) { + actor->is()->setRigidBodyFlag(PxRigidBodyFlag::eKINEMATIC, true); + actor->is()->setRigidBodyFlag(PxRigidBodyFlag::eUSE_KINEMATIC_TARGET_FOR_SCENE_QUERIES, + true); + } } else { actor = pxPhysics->createRigidStatic(pxTransform); } Assert(actor, "Physx did not return valid PxRigidActor"); + actor->setActorFlag(PxActorFlag::eDISABLE_GRAVITY, true); + auto userData = new ActorUserData(e, globalTransform, ph.group); actor->userData = userData; @@ -533,7 +516,8 @@ namespace sp { return actor; } - void PhysxManager::UpdateActor(ecs::Lock> lock, + void PhysxManager::UpdateActor( + ecs::Lock> lock, ecs::Entity &e) { ZoneScoped; // ZoneStr(ecs::ToString(lock, e)); @@ -550,6 +534,12 @@ namespace sp { auto scale = transform.GetScale(); auto userData = (ActorUserData *)actor->userData; + ecs::SceneProperties sceneProperties = {}; + if (e.Has(lock)) { + auto &properties = e.Get(lock).properties; + if (properties) sceneProperties = *properties; + } + const ecs::PhysicsShape::ConvexMesh *mesh = nullptr; for (auto &shape : ph.shapes) { if (mesh) Abortf("Physics actor can't have multiple meshes: %s", ecs::ToString(lock, e)); @@ -675,6 +665,24 @@ namespace sp { dynamic->setLinearDamping(ph.linearDamping); userData->linearDamping = ph.linearDamping; } + + if (!dynamic->getRigidBodyFlags().isSet(PxRigidBodyFlag::eKINEMATIC)) { + glm::vec3 gravityForce = sceneProperties.fixedGravity; + if (sceneProperties.gravityFunction) { + auto gravityPos = sceneProperties.gravityTransform.GetInverse() * + glm::vec4(transform.GetPosition(), 1); + gravityForce = sceneProperties.gravityTransform.GetRotation() * + sceneProperties.gravityFunction(gravityPos); + } + // Force will accumulate on sleeping objects causing jitter + if (gravityForce != glm::vec3(0) && !dynamic->isSleeping()) { + dynamic->addForce(GlmVec3ToPxVec3(gravityForce), PxForceMode::eACCELERATION, false); + } + if (gravityForce != userData->gravity) { + dynamic->wakeUp(); + userData->gravity = gravityForce; + } + } } } diff --git a/src/physx/physx/PhysxManager.hh b/src/physx/physx/PhysxManager.hh index 0ee2f1f8b..940d79f88 100644 --- a/src/physx/physx/PhysxManager.hh +++ b/src/physx/physx/PhysxManager.hh @@ -39,13 +39,12 @@ namespace sp { struct HullSettings; class SceneManager; - extern CVar CVarGravity; - struct ActorUserData { ecs::Entity entity; ecs::Transform pose; glm::vec3 scale = glm::vec3(1); glm::vec3 velocity = glm::vec3(0); + glm::vec3 gravity = glm::vec3(0); float angularDamping = 0.0f; float linearDamping = 0.0f; ecs::PhysicsGroup physicsGroup = ecs::PhysicsGroup::NoClip; @@ -62,7 +61,7 @@ namespace sp { struct CharacterControllerUserData { ActorUserData actorData; - ecs::Entity target; + ecs::Entity headTarget; bool onGround = false; ecs::Entity standingOn; bool noclipping = false; @@ -83,7 +82,8 @@ namespace sp { void Frame() override; physx::PxRigidActor *CreateActor(ecs::Lock> lock, ecs::Entity &e); - void UpdateActor(ecs::Lock> lock, ecs::Entity &e); + void UpdateActor(ecs::Lock> lock, + ecs::Entity &e); void RemoveActor(physx::PxRigidActor *actor); private: diff --git a/src/scripts/scripts/AudioScripts.cc b/src/scripts/scripts/AudioScripts.cc index 22afbd278..dc5ea1670 100644 --- a/src/scripts/scripts/AudioScripts.cc +++ b/src/scripts/scripts/AudioScripts.cc @@ -1,6 +1,7 @@ #include "core/Common.hh" #include "core/Logging.hh" #include "ecs/EcsImpl.hh" +#include "game/GameEntities.hh" #include "game/Scene.hh" namespace ecs { @@ -13,21 +14,14 @@ namespace ecs { if (constSounds.occlusionWeight <= 0.0f) return; struct Data { - EntityRef listener; - EntityRef listenerFallback; PhysicsQuery::Handle raycastQuery; } data; if (state.userData.has_value()) { data = std::any_cast(state.userData); - } else { - data.listener = EntityRef(Name{"vr", "hmd"}); - data.listenerFallback = EntityRef(Name{"player", "flatview"}); } - auto listener = data.listener.Get(lock); - if (!listener) listener = data.listenerFallback.Get(lock); - + auto listener = sp::entities::Head.Get(lock); if (listener.Has(lock)) { auto listenerPos = listener.Get(lock).GetPosition(); auto soundPos = ent.Get(lock).GetPosition(); diff --git a/src/scripts/scripts/InputScripts.cc b/src/scripts/scripts/InputScripts.cc index a8ebab5d2..283e27549 100644 --- a/src/scripts/scripts/InputScripts.cc +++ b/src/scripts/scripts/InputScripts.cc @@ -39,44 +39,69 @@ namespace sp::scripts { [](ScriptState &state, Lock lock, Entity ent, chrono_clock::duration interval) { if (ent.Has(lock)) { auto relativeTargetName = state.GetParam("relative_to"); + auto upReferenceName = state.GetParam("up_reference"); ecs::Name targetName(relativeTargetName, state.scope.prefix); - if (targetName) { - auto targetEntity = state.GetParam("target_entity"); - if (targetEntity.Name() != targetName) targetEntity = targetName; - - auto target = targetEntity.Get(lock); - if (target) { - state.SetParam("target_entity", targetEntity); - - glm::vec3 movement = glm::vec3(0); - movement.z -= SignalBindings::GetSignal(lock, ent, "move_forward"); - movement.z += SignalBindings::GetSignal(lock, ent, "move_back"); - movement.x -= SignalBindings::GetSignal(lock, ent, "move_left"); - movement.x += SignalBindings::GetSignal(lock, ent, "move_right"); - float vertical = SignalBindings::GetSignal(lock, ent, "move_up"); - vertical -= SignalBindings::GetSignal(lock, ent, "move_down"); - - movement.x = std::clamp(movement.x, -1.0f, 1.0f); - movement.z = std::clamp(movement.z, -1.0f, 1.0f); - vertical = std::clamp(vertical, -1.0f, 1.0f); - - if (target.Has(lock)) { - auto parentRotation = target.Get(lock).GetGlobalRotation(lock); - movement = parentRotation * movement; - if (std::abs(movement.y) > 0.999) { - movement = parentRotation * glm::vec3(0, -movement.y, 0); - } - movement.y = 0; - } + ecs::Name referenceName(upReferenceName, state.scope.prefix); + if (!targetName) { + Errorf("Relative movement target name is invalid: %s", relativeTargetName); + return; + } + if (!upReferenceName.empty() && !referenceName) { + Errorf("Relative movement up reference name is invalid: %s", upReferenceName); + return; + } + + auto targetEntity = state.GetParam("target_entity"); + if (targetEntity.Name() != targetName) targetEntity = targetName; + auto referenceEntity = state.GetParam("reference_entity"); + if (referenceEntity.Name() != referenceName) referenceEntity = referenceName; + + glm::vec3 movementInput = glm::vec3(0); + movementInput.x -= SignalBindings::GetSignal(lock, ent, "move_left"); + movementInput.x += SignalBindings::GetSignal(lock, ent, "move_right"); + movementInput.y += SignalBindings::GetSignal(lock, ent, "move_up"); + movementInput.y -= SignalBindings::GetSignal(lock, ent, "move_down"); + movementInput.z -= SignalBindings::GetSignal(lock, ent, "move_forward"); + movementInput.z += SignalBindings::GetSignal(lock, ent, "move_back"); + + movementInput.x = std::clamp(movementInput.x, -1.0f, 1.0f); + movementInput.y = std::clamp(movementInput.y, -1.0f, 1.0f); + movementInput.z = std::clamp(movementInput.z, -1.0f, 1.0f); - auto &outputComp = ent.Get(lock); - outputComp.SetSignal("move_world_x", movement.x); - outputComp.SetSignal("move_world_y", vertical); - outputComp.SetSignal("move_world_z", movement.z); + glm::quat orientation = glm::identity(); + auto reference = referenceEntity.Get(lock); + if (reference.Has(lock)) { + state.SetParam("reference_entity", referenceEntity); + + orientation = reference.Get(lock).GetGlobalRotation(lock); + } + + glm::vec3 output = glm::vec3(0); + + auto target = targetEntity.Get(lock); + if (target.Has(lock)) { + state.SetParam("target_entity", targetEntity); + + auto relativeRotation = target.Get(lock).GetGlobalRotation(lock); + relativeRotation = glm::inverse(orientation) * relativeRotation; + auto flatMovement = glm::vec3(movementInput.x, 0, movementInput.z); + output = relativeRotation * flatMovement; + if (std::abs(output.y) > 0.999) { + output = relativeRotation * glm::vec3(0, -output.y, 0); } + output.y = 0; + if (output != glm::vec3(0)) { + output = glm::normalize(output) * glm::length(flatMovement); + } + output.y = movementInput.y; } else { - Errorf("Relative target name is invalid: %s", relativeTargetName); + output = movementInput; } + + auto &outputComp = ent.Get(lock); + outputComp.SetSignal("move_relative_x", output.x); + outputComp.SetSignal("move_relative_y", output.y); + outputComp.SetSignal("move_relative_z", output.z); } }), InternalScript("player_rotation", diff --git a/src/scripts/scripts/MiscScripts.cc b/src/scripts/scripts/MiscScripts.cc index f7e58acff..4d16fd9d3 100644 --- a/src/scripts/scripts/MiscScripts.cc +++ b/src/scripts/scripts/MiscScripts.cc @@ -68,14 +68,14 @@ namespace sp::scripts { } auto modelName = state.GetParam("model"); + auto scene = state.scope.scene.lock(); + Assert(scene, "Model spawner script must have valid scene"); - std::thread([ent, transform, modelName, scope = state.scope]() { + std::thread([ent, transform, modelName, scene, scope = state.scope]() { auto model = sp::Assets().LoadGltf(modelName); auto lock = ecs::StartTransaction(); if (ent.Has(lock)) { - auto scene = scope.scene.lock(); - Assert(scene, "Model spawner script must have valid scene"); auto newEntity = scene->NewRootEntity(lock, scene, ecs::SceneInfo::Priority::Scene); newEntity.Set(lock, transform); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 66f0ed1e4..a8963d66d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -32,6 +32,7 @@ target_link_libraries(sp-integration-tests ${PROJECT_CORE_LIB} ${PROJECT_GAME_TEST_LIB} ${PROJECT_PHYSICS_PHYSX_LIB} + ${PROJECT_SCRIPTS_LIB} ) target_include_directories(sp-integration-tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/tests/integration/scene-manager.cc b/tests/integration/scene-manager.cc index d6d16be8a..0f5a4b92d 100644 --- a/tests/integration/scene-manager.cc +++ b/tests/integration/scene-manager.cc @@ -10,6 +10,14 @@ namespace SceneManagerTests { sp::SceneManager Scenes(true); + template + ecs::Entity EntityWith(ecs::Lock> lock, const T &value) { + for (auto e : lock.template EntitiesWith()) { + if (e.template Has(lock) && e.template Get(lock) == value) return e; + } + return {}; + } + void AssertEntityScene(ecs::Lock> stagingLock, ecs::Lock> liveLock, std::string sceneName, @@ -17,7 +25,7 @@ namespace SceneManagerTests { std::initializer_list sceneNames) { Assert(sceneNames.size() > 0, "AssertEntityScene expects at least 1 scene name"); - auto liveEnt = ecs::EntityWith(liveLock, ecs::Name(sceneName, entityName)); + auto liveEnt = EntityWith(liveLock, ecs::Name(sceneName, entityName)); Assertf(!!liveEnt, "Expected entity to exist: %s", entityName); Assertf(liveEnt.Has(liveLock), "Expected entity %s to have SceneInfo", entityName); auto &liveSceneInfo = liveEnt.Get(liveLock); @@ -51,7 +59,7 @@ namespace SceneManagerTests { void systemSceneCallback(ecs::Lock lock, std::shared_ptr scene) { auto ent = lock.NewEntity(); ent.Set(lock, "player", "player"); - ent.Set(lock, ent, ecs::SceneInfo::Priority::System, scene); + ent.Set(lock, ent, ecs::SceneInfo::Priority::System, scene, scene->properties); ent.Set(lock, glm::vec3(1, 2, 3)); ent.Set(lock); ent.Set(lock); @@ -61,7 +69,7 @@ namespace SceneManagerTests { ent = lock.NewEntity(); ent.Set(lock, "", "test"); - ent.Set(lock, ent, ecs::SceneInfo::Priority::System, scene); + ent.Set(lock, ent, ecs::SceneInfo::Priority::System, scene, scene->properties); } void TestBasicLoadAddRemove() { @@ -92,7 +100,7 @@ namespace SceneManagerTests { auto stagingLock = ecs::StartStagingTransaction(); auto liveLock = ecs::StartTransaction(); - auto player = ecs::EntityWith(liveLock, ecs::Name("player", "player")); + auto player = EntityWith(liveLock, ecs::Name("player", "player")); Assert(player.Has(liveLock), "Expected player entity to be valid"); AssertEqual(player.Get(liveLock), ecs::Name("player", "player"), diff --git a/tests/unit/ecs-transform.cc b/tests/unit/ecs-transform.cc index 6ca74e577..9f9a2c545 100644 --- a/tests/unit/ecs-transform.cc +++ b/tests/unit/ecs-transform.cc @@ -17,11 +17,15 @@ namespace EcsTransformTests { root = lock.NewEntity(); ecs::EntityRef rootRef(ecs::Name("", "root"), root); - root.Set(lock, glm::vec3(1, 2, 3)); + root.Set(lock, + glm::vec3(1, 2, 3), + glm::angleAxis(glm::radians(90.0f), glm::vec3(1, 0, 0))); a = lock.NewEntity(); ecs::EntityRef aRef(ecs::Name("", "a"), a); - auto &transformA = a.Set(lock, glm::vec3(4, 0, 0)); + auto &transformA = a.Set(lock, + glm::vec3(4, 0, 0), + glm::angleAxis(glm::radians(90.0f), glm::vec3(0, 1, 0))); transformA.parent = root; b = lock.NewEntity(); @@ -48,10 +52,10 @@ namespace EcsTransformTests { glm::vec3(5, 2, 3), "A entity returned wrong position"); AssertEqual(transformB.GetGlobalTransform(lock).GetPosition(), - glm::vec3(5, 7, 3), + glm::vec3(5, 2, 8), "B entity returned wrong position"); AssertEqual(transformC.GetGlobalTransform(lock).GetPosition(), - glm::vec3(5, 2, 9), + glm::vec3(11, 2, 3), "C entity returned wrong position"); { @@ -79,12 +83,39 @@ namespace EcsTransformTests { glm::vec3(3, -2, -3), "A entity returned wrong position"); AssertEqual(transformB.GetGlobalTransform(lock).GetPosition(), - glm::vec3(3, 3, -3), + glm::vec3(3, -2, 2), "B entity returned wrong position"); AssertEqual(transformC.GetGlobalTransform(lock).GetPosition(), - glm::vec3(3, -2, 3), + glm::vec3(9, -2, -3), "C entity returned wrong position"); } + { + Timer t("Try moving entity via transform tree root"); + auto lock = ecs::StartTransaction>(); + + auto &transformRoot = root.Get(lock); + auto &transformA = a.Get(lock); + auto &transformB = b.Get(lock); + auto &transformC = c.Get(lock); + + ecs::Transform target(glm::vec3(1, 1, 1), glm::angleAxis(glm::radians(90.0f), glm::vec3(0, 0, -1))); + std::cout << glm::to_string(target.GetUp()) << std::endl; + ecs::TransformTree::MoveViaRoot(lock, c, target); + + AssertEqual(transformRoot.GetGlobalTransform(lock).GetPosition(), + glm::vec3(1, 1, -9), + "Root entity returned wrong position"); + AssertEqual(transformA.GetGlobalTransform(lock).GetPosition(), + glm::vec3(1, 1, -5), + "A entity returned wrong position"); + AssertEqual(transformB.GetGlobalTransform(lock).GetPosition(), + glm::vec3(6, 1, -5), + "B entity returned wrong position"); + auto globalTransformC = transformC.GetGlobalTransform(lock); + AssertEqual(globalTransformC.GetPosition(), glm::vec3(1, 1, 1), "C entity returned wrong position"); + AssertEqual(globalTransformC.GetUp(), target.GetUp(), "C entity returned wrong up vector"); + AssertEqual(globalTransformC.GetForward(), target.GetForward(), "C entity returned wrong forward vector"); + } { Timer t("Try setting and reading rotation + scale"); auto lock = ecs::StartTransaction>();