diff --git a/Handbook/src/appendix/changelog.md b/Handbook/src/appendix/changelog.md index 0b09124c..2107ceb8 100644 --- a/Handbook/src/appendix/changelog.md +++ b/Handbook/src/appendix/changelog.md @@ -21,6 +21,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Now the motion sources for the wrist-tracking hardware or hand-tracking are queried and populated dynamically rather than relying on the hardcoded `EControllerHand` enum. This allows the SenseGlove Unreal Engine Plugin to integrate better into other plugins such as `ViveOpenXR`, which when enabled, provides many more options as the motion source for their various wrist-tracking hardware. +- `FSGWristTrackingSettings::LeftHandMotionSource` and `FSGWristTrackingSettings::RightHandMotionSource` types have changed from `EControllerHand` to `FName`. - Bumped the SenseGlove libraries to `v2.105.3-97ea18cb`. - Bumped the SenseGlove Unreal Engine Marketplace Packager `v0.5.0-7df1183`. - Bumped the copyright years. diff --git a/Source/SenseGlove/Private/SenseGlove/Components/SGWristTrackerComponent.cpp b/Source/SenseGlove/Private/SenseGlove/Components/SGWristTrackerComponent.cpp index 2d5e6c7b..7197e34d 100644 --- a/Source/SenseGlove/Private/SenseGlove/Components/SGWristTrackerComponent.cpp +++ b/Source/SenseGlove/Private/SenseGlove/Components/SGWristTrackerComponent.cpp @@ -36,6 +36,7 @@ #include "SenseGlove/Components/SGWristTrackerComponent.h" #include "IXRTrackingSystem.h" +#include "MotionDelayBuffer.h" #include "SGDebug/SGDebugGizmo.h" #include "SGSettings/SGSettings.h" @@ -344,11 +345,23 @@ void USGWristTrackerComponent::FImpl::OverridePluginSettingsPropertyChanged() void USGWristTrackerComponent::FImpl::UpdateMotionSource() const { - if (GEngine) + if (!GEngine) { - Owner->SetTrackingSource(Owner->IsRight() + return; + } + + UWorld* World{Owner->GetWorld()}; + if (!World) + { + return; + } + + if (World->IsGameWorld() && Owner->HasBeenInitialized()) + { + Owner->MotionSource = Owner->IsRight() ? Owner->GetWristTrackingSettings().RightHandMotionSource - : Owner->GetWristTrackingSettings().LeftHandMotionSource); + : Owner->GetWristTrackingSettings().LeftHandMotionSource; + FMotionDelayService::RegisterDelayTarget(Owner, Owner->PlayerIndex, Owner->MotionSource); } } diff --git a/Source/SenseGloveSettings/Private/SGSettings/SGSettings.cpp b/Source/SenseGloveSettings/Private/SGSettings/SGSettings.cpp index cad3c40d..746b6860 100644 --- a/Source/SenseGloveSettings/Private/SGSettings/SGSettings.cpp +++ b/Source/SenseGloveSettings/Private/SGSettings/SGSettings.cpp @@ -35,15 +35,29 @@ #include "SGSettings/SGSettings.h" +#include "Containers/Array.h" #include "Engine/Engine.h" +#include "PropertyEditorModule.h" +#include "UObject/NameTypes.h" #include "UObject/Object.h" #include "UObject/Package.h" #include "UObject/UObjectGlobals.h" #include "SGLog/SGLog.h" +#include "SGSettings/SGWristTrackingDetailsCustomization.h" struct USGSettings::FImpl { + /************************ + * Static methods + ************************/ + + FORCENOINLINE static const FName& GetWristTrackingSettingsPropertyName() + { + static const FName Name{TEXT("SGWristTrackingSettings")}; + return Name; + } + /************************ * Owner object ************************/ @@ -63,6 +77,13 @@ struct USGSettings::FImpl FImpl(const FImpl& Rhs) = default; FImpl& operator=(const FImpl& Rhs) = default; + + /************************ + * Methods + ************************/ + + void RegisterCustomPropertyTypeLayouts(); + void UnregisterCustomPropertyTypeLayouts(); }; USGSettings* USGSettings::GetInstance() @@ -98,6 +119,8 @@ void USGSettings::Initialize(FSubsystemCollectionBase& Collection) Super::Initialize(Collection); + Pimpl->RegisterCustomPropertyTypeLayouts(); + SGLOG("The SenseGlove settings singleton has been successfully initialized with the engine lifetime!"); } @@ -107,6 +130,8 @@ void USGSettings::Deinitialize() Super::Deinitialize(); + Pimpl->UnregisterCustomPropertyTypeLayouts(); + (void) Pimpl.Release(); SGLOG("The SenseGlove settings singleton has been successfully de-initialized!"); @@ -117,6 +142,32 @@ USGSettings::FImpl::FImpl(USGSettings* InOwner) { } +void USGSettings::FImpl::RegisterCustomPropertyTypeLayouts() +{ + SGLOG("Registering SGSettings custom property type layouts..."); + + FPropertyEditorModule& PropertyEditorModule = + FModuleManager::LoadModuleChecked(FName{TEXT("PropertyEditor")}); + + SGLOG("Registering SGSettings custom property type layout", GetWristTrackingSettingsPropertyName()); + PropertyEditorModule.RegisterCustomPropertyTypeLayout( + GetWristTrackingSettingsPropertyName(), + FOnGetPropertyTypeCustomizationInstance::CreateStatic( + &FSGWristTrackingDetailsCustomization::NewWristTrackingDetailsCustomization) + ); +} + +void USGSettings::FImpl::UnregisterCustomPropertyTypeLayouts() +{ + SGLOG("Unregistering SGSettings custom property type layouts..."); + + FPropertyEditorModule& PropertyEditorModule = + FModuleManager::LoadModuleChecked(FName{TEXT("PropertyEditor")}); + + SGLOG("Unregistering SGSettings custom property type layout", GetWristTrackingSettingsPropertyName()); + PropertyEditorModule.UnregisterCustomPropertyTypeLayout(GetWristTrackingSettingsPropertyName()); +} + USGSettings::FImpl::~FImpl() = default; void USGSettings::FImplDeleter::operator()(const FImpl* P) const diff --git a/Source/SenseGloveSettings/Private/SGSettings/SGWristTrackingDetailsCustomization.cpp b/Source/SenseGloveSettings/Private/SGSettings/SGWristTrackingDetailsCustomization.cpp new file mode 100644 index 00000000..05fbe87a --- /dev/null +++ b/Source/SenseGloveSettings/Private/SGSettings/SGWristTrackingDetailsCustomization.cpp @@ -0,0 +1,218 @@ +/** + * @file + * + * @author Mamadou Babaei + * + * @section LICENSE + * + * (The MIT License) + * + * Copyright (c) 2020 - 2025 SenseGlove + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * @section DESCRIPTION + * + * + */ + + +#include "SGSettings/SGWristTrackingDetailsCustomization.h" + +#include "IMotionController.h" +#include "Widgets/Input/SComboBox.h" +#include "Widgets/Text/STextBlock.h" + +#include "SGLog/SGLog.h" + +struct FSGWristTrackingDetailsCustomization::FImpl +{ + /************************ + * Typedefs + ************************/ + + /************************ + * Static methods + ************************/ + + FORCENOINLINE static const FString& GetLeftHandMotionSourcePropertyName() + { + static const FString Name{TEXT("LeftHandMotionSource")}; + return Name; + } + + FORCENOINLINE static const FString& GetRightHandMotionSourcePropertyName() + { + static const FString Name{TEXT("RightHandMotionSource")}; + return Name; + } + + /************************ + * Member variables + ************************/ + + TArray> MotionSources; + + /************************ + * Owner object + ************************/ + + FSGWristTrackingDetailsCustomization* Owner; + + /************************ + * Constructor / Destructor + ************************/ + + explicit FImpl(FSGWristTrackingDetailsCustomization* InOwner); + ~FImpl(); + + /************************ + * Default copy constructor & copy assignment operator + ************************/ + + FImpl(const FImpl& Rhs) = delete; + FImpl& operator=(const FImpl& Rhs) = delete; + + /************************ + * Methods + ************************/ + + void PopulateComboBoxOptions(); +}; + +TSharedRef FSGWristTrackingDetailsCustomization::NewWristTrackingDetailsCustomization() +{ + return MakeShareable(new FSGWristTrackingDetailsCustomization{}); +} + +FSGWristTrackingDetailsCustomization::FSGWristTrackingDetailsCustomization() + : Pimpl(TUniquePtr(new FImpl{this}, PimplDeleter)) +{ +} + +void FSGWristTrackingDetailsCustomization::CustomizeHeader( + TSharedRef PropertyHandle, + FDetailWidgetRow& HeaderRow, + IPropertyTypeCustomizationUtils& CustomizationUtils) +{ + HeaderRow.NameContent() + [ + PropertyHandle->CreatePropertyNameWidget() + ] + .ValueContent() + [ + PropertyHandle->CreatePropertyValueWidget() + ]; +} + +void FSGWristTrackingDetailsCustomization::CustomizeChildren( + TSharedRef PropertyHandle, + IDetailChildrenBuilder& ChildBuilder, + IPropertyTypeCustomizationUtils& CustomizationUtils) +{ + Pimpl->PopulateComboBoxOptions(); + + uint32 NumChildren; + PropertyHandle->GetNumChildren(NumChildren); + + for (uint32 Index = 0; Index < NumChildren; ++Index) + { + TSharedRef ChildHandle{PropertyHandle->GetChildHandle(Index).ToSharedRef()}; + + if (ChildHandle->GetProperty()->GetName() == Pimpl->GetLeftHandMotionSourcePropertyName() + || ChildHandle->GetProperty()->GetName() == Pimpl->GetRightHandMotionSourcePropertyName()) + { + ChildBuilder.AddCustomRow(ChildHandle->GetPropertyDisplayName()) + .NameContent() + [ + ChildHandle->CreatePropertyNameWidget() + ] + .ValueContent() + [ + SNew(SComboBox>) + .OptionsSource(&Pimpl->MotionSources) + .OnGenerateWidget_Lambda([](TSharedPtr InItem) + { + return SNew(STextBlock).Text(FText::FromName(*InItem)); + }) + .OnSelectionChanged_Lambda([ChildHandle](TSharedPtr NewValue, ESelectInfo::Type) + { + if (NewValue.IsValid()) + { + ChildHandle->SetValue(*NewValue); + } + }) + .Content() + [ + SNew(STextBlock) + .Text_Lambda([ChildHandle]() + { + FName CurrentValue; + ChildHandle->GetValue(CurrentValue); + return FText::FromName(CurrentValue); + }) + ] + ]; + } + else + { + ChildBuilder.AddProperty(ChildHandle); + } + } +} + +void FSGWristTrackingDetailsCustomization::FImpl::PopulateComboBoxOptions() +{ + SGLOG("Populating the FSGWristTrackingSettings MotionSources..."); + + MotionSources.Reset(); + + const TArray MotionControllers{ + IModularFeatures::Get().GetModularFeatureImplementations< + IMotionController>(IMotionController::GetModularFeatureName()) + }; + for (const IMotionController* MotionController: MotionControllers) + { + if (MotionController) + { + TArray MotionControllerSources; + MotionController->EnumerateSources(MotionControllerSources); + + MotionSources.Reserve(MotionSources.Num() + MotionControllerSources.Num()); + for (const FMotionControllerSource& MotionSource: MotionControllerSources) + { + SGLOG("Discovered a new motion source", MotionSource.SourceName); + + MotionSources.AddUnique(MakeShareable(new FName{MotionSource.SourceName})); + } + } + } +} + +FSGWristTrackingDetailsCustomization::FImpl::FImpl(FSGWristTrackingDetailsCustomization* InOwner) + : Owner(InOwner) +{ +} + +FSGWristTrackingDetailsCustomization::FImpl::~FImpl() = default; + +void FSGWristTrackingDetailsCustomization::FImplDeleter::operator()(const FImpl* P) const +{ + delete P; +} \ No newline at end of file diff --git a/Source/SenseGloveSettings/Private/SGSettings/SGWristTrackingDetailsCustomization.h b/Source/SenseGloveSettings/Private/SGSettings/SGWristTrackingDetailsCustomization.h new file mode 100644 index 00000000..08584374 --- /dev/null +++ b/Source/SenseGloveSettings/Private/SGSettings/SGWristTrackingDetailsCustomization.h @@ -0,0 +1,74 @@ +/** + * @file + * + * @author Mamadou Babaei + * + * @section LICENSE + * + * (The MIT License) + * + * Copyright (c) 2020 - 2025 SenseGlove + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * @section DESCRIPTION + * + * + */ + + +#pragma once + +#include "DetailWidgetRow.h" +#include "IDetailChildrenBuilder.h" +#include "IPropertyTypeCustomization.h" +#include "PropertyHandle.h" +#include "Templates/SharedPointer.h" +#include "Templates/UniquePtr.h" + +class SENSEGLOVESETTINGS_API FSGWristTrackingDetailsCustomization : public IPropertyTypeCustomization +{ +public: + static TSharedRef NewWristTrackingDetailsCustomization(); + +private: + FSGWristTrackingDetailsCustomization(); + +public: + virtual void CustomizeHeader( + TSharedRef PropertyHandle, + FDetailWidgetRow& HeaderRow, + IPropertyTypeCustomizationUtils& CustomizationUtils) override; + + virtual void CustomizeChildren( + TSharedRef PropertyHandle, + IDetailChildrenBuilder& ChildBuilder, + IPropertyTypeCustomizationUtils& CustomizationUtils) override; + +private: + struct FImpl; + + struct FImplDeleter + { + void operator()(const FImpl* P) const; + }; + + TUniquePtr Pimpl; + FImplDeleter PimplDeleter; +}; \ No newline at end of file diff --git a/Source/SenseGloveSettings/Private/SGSettings/SGWristTrackingSettings.cpp b/Source/SenseGloveSettings/Private/SGSettings/SGWristTrackingSettings.cpp index 79ace721..848435c5 100644 --- a/Source/SenseGloveSettings/Private/SGSettings/SGWristTrackingSettings.cpp +++ b/Source/SenseGloveSettings/Private/SGSettings/SGWristTrackingSettings.cpp @@ -42,8 +42,8 @@ FSGWristTrackingSettings::FSGWristTrackingSettings() FVector::ZeroVector, FRotator::ZeroRotator, FRotator::ZeroRotator, - EControllerHand::Left, - EControllerHand::Right, + FName{TEXT("Left")}, + FName{TEXT("Right")}, FSGWristTrackingDebuggingSettings{} } { @@ -55,8 +55,8 @@ FSGWristTrackingSettings::FSGWristTrackingSettings( const FVector& InTrackingHardwareLocationOffsetRightHand, const FRotator& InTrackingHardwareRotationOffsetLeftHand, const FRotator& InTrackingHardwareRotationOffsetRightHand, - const EControllerHand InLeftHandMotionSource, - const EControllerHand InRightHandMotionSource, + const FName InLeftHandMotionSource, + const FName InRightHandMotionSource, const FSGWristTrackingDebuggingSettings& InDebuggingSettings) : TrackingHardware(InTrackingHardware), TrackingHardwareLocationOffsetLeftHand{InTrackingHardwareLocationOffsetLeftHand}, diff --git a/Source/SenseGloveSettings/Public/SGSettings/SGWristTrackingSettings.h b/Source/SenseGloveSettings/Public/SGSettings/SGWristTrackingSettings.h index b8126394..81adf5b4 100644 --- a/Source/SenseGloveSettings/Public/SGSettings/SGWristTrackingSettings.h +++ b/Source/SenseGloveSettings/Public/SGSettings/SGWristTrackingSettings.h @@ -39,6 +39,7 @@ #include "InputCoreTypes.h" #include "Math/Rotator.h" #include "Math/Vector.h" +#include "UObject/NameTypes.h" #include "UObject/ObjectMacros.h" #include "SGSettings/SGWristTrackingDebuggingSettings.h" @@ -127,7 +128,7 @@ public: * through the SteamVR app. */ UPROPERTY(Config, EditDefaultsOnly, Category="Wrist Tracking") - EControllerHand LeftHandMotionSource; + FName LeftHandMotionSource; /** * Determines the motion source for the left hand. For Oculus HMDs, this is usually Right, and for VIVE HMDs, it's @@ -138,7 +139,7 @@ public: * through the SteamVR app. */ UPROPERTY(Config, EditDefaultsOnly, Category="Wrist Tracking") - EControllerHand RightHandMotionSource; + FName RightHandMotionSource; /** * Provides debugging options for visually debugging the wrist tracker. @@ -155,7 +156,7 @@ public: const FVector& InTrackingHardwareLocationOffsetRightHand, const FRotator& InTrackingHardwareRotationOffsetLeftHand, const FRotator& InTrackingHardwareRotationOffsetRightHand, - EControllerHand InLeftHandMotionSource, - EControllerHand InRightHandMotionSource, + FName InLeftHandMotionSource, + FName InRightHandMotionSource, const FSGWristTrackingDebuggingSettings& InDebuggingSettings); }; \ No newline at end of file diff --git a/Source/SenseGloveSettings/SenseGloveSettings.Build.cs b/Source/SenseGloveSettings/SenseGloveSettings.Build.cs index bc0742f9..a39d5e4a 100644 --- a/Source/SenseGloveSettings/SenseGloveSettings.Build.cs +++ b/Source/SenseGloveSettings/SenseGloveSettings.Build.cs @@ -48,7 +48,11 @@ public class SenseGloveSettings : ModuleRules "Core", "CoreUObject", "Engine", + "HeadMountedDisplay", "InputCore", + "PropertyEditor", + "Slate", + "SlateCore", } ); @@ -65,6 +69,7 @@ public class SenseGloveSettings : ModuleRules "SenseGloveCore", "SenseGloveLog", "SenseGloveTypes", + "SenseGloveUtils", } ); } diff --git a/Source/SenseGloveTracking/Private/SGTracking/SGXRTracker.cpp b/Source/SenseGloveTracking/Private/SGTracking/SGXRTracker.cpp index 8fc43354..53ab80fb 100644 --- a/Source/SenseGloveTracking/Private/SGTracking/SGXRTracker.cpp +++ b/Source/SenseGloveTracking/Private/SGTracking/SGXRTracker.cpp @@ -291,19 +291,15 @@ struct FSGXRTracker::FImpl return Name; } - FORCEINLINE static FName GetLeftPositionalTrackingHardwareMotionSourceName() + FORCEINLINE static const FName& GetLeftPositionalTrackingHardwareMotionSourceName() { - const FName Name{ - GetControllerHandEnumName(GetWristTrackingSettings().LeftHandMotionSource) - }; + const FName& Name{GetWristTrackingSettings().LeftHandMotionSource}; return Name; } - FORCEINLINE static FName GetRightPositionalTrackingHardwareMotionSourceName() + FORCEINLINE static const FName& GetRightPositionalTrackingHardwareMotionSourceName() { - const FName Name{ - GetControllerHandEnumName(GetWristTrackingSettings().RightHandMotionSource) - }; + const FName& Name{GetWristTrackingSettings().RightHandMotionSource}; return Name; } @@ -2028,12 +2024,9 @@ void FSGXRTracker::FImpl::BuildMotionSourceToKeypointMap() } const FSGWristTrackingSettings& WristTrackingSettings{GetWristTrackingSettings()}; - const FName LeftHandSGMotionSourceName{ - GetControllerHandEnumName(WristTrackingSettings.LeftHandMotionSource) - }; - const FName RightHandSGMotionSourceName{ - GetControllerHandEnumName(WristTrackingSettings.RightHandMotionSource) - }; + const FName& LeftHandSGMotionSourceName{WristTrackingSettings.LeftHandMotionSource}; + const FName& RightHandSGMotionSourceName{WristTrackingSettings.RightHandMotionSource}; + MotionSourceToKeypointMap.Add( LeftHandSGMotionSourceName, FSGMotionSourceInfo(EHandKeypoint::Wrist, true)); MotionSourceToKeypointMap.Add( @@ -2122,12 +2115,10 @@ bool FSGXRTracker::FImpl::GetControllerTransform( static const FName OpenXRViveTrackerName{TEXT("OpenXRViveTracker")}; const FSGWristTrackingSettings& WristTrackingSettings{GetWristTrackingSettings()}; - const EControllerHand PositionalTrackingHardwareHand = + const FName& PositionalTrackingHardwareMotionSource{ bRight ? WristTrackingSettings.RightHandMotionSource - : WristTrackingSettings.LeftHandMotionSource; - const FName& PositionalTrackingHardwareMotionSource{ - GetControllerHandEnumName(PositionalTrackingHardwareHand) + : WristTrackingSettings.LeftHandMotionSource }; bool bTracked = false; @@ -2221,12 +2212,10 @@ bool FSGXRTracker::FImpl::GetControllerTransform( bool FSGXRTracker::FImpl::GetWristTransform(const bool bRight, FTransform& OutTransform) const { const FSGWristTrackingSettings& WristTrackingSettings{GetWristTrackingSettings()}; - const EControllerHand PositionalTrackingHardwareHand = + const FName& MotionSource{ bRight ? WristTrackingSettings.RightHandMotionSource - : WristTrackingSettings.LeftHandMotionSource; - const FName& MotionSource{ - GetControllerHandEnumName(PositionalTrackingHardwareHand) + : WristTrackingSettings.LeftHandMotionSource }; if (!ensureAlwaysMsgf(XRTrackingSystem, TEXT("Invalid XR tracking system!")))