diff --git a/Source/MobuLiveLinkPlugin2016.Target.cs b/Source/MobuLiveLinkPlugin2016.Target.cs index 5b510477..6d5b2342 100644 --- a/Source/MobuLiveLinkPlugin2016.Target.cs +++ b/Source/MobuLiveLinkPlugin2016.Target.cs @@ -1,11 +1,14 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -using UnrealBuildTool; -using System.Collections.Generic; -using System.IO; - -public class MobuLiveLinkPlugin2016Target : MobuLiveLinkPluginTargetBase -{ - public MobuLiveLinkPlugin2016Target(TargetInfo Target) : base(Target, "2016") - {} +// Copyright Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; +using System.Collections.Generic; +using System.IO; + +public class MobuLiveLinkPlugin2016Target : MobuLiveLinkPluginTargetBase +{ + public MobuLiveLinkPlugin2016Target(TargetInfo Target) : base(Target, "2016") + { + //Mobu is not strict c++ compliant before Mobu 2019 + WindowsPlatform.bStrictConformanceMode = false; + } } \ No newline at end of file diff --git a/Source/MobuLiveLinkPlugin2017.Build.cs b/Source/MobuLiveLinkPlugin2017.Build.cs index c246c271..2af6f30a 100644 --- a/Source/MobuLiveLinkPlugin2017.Build.cs +++ b/Source/MobuLiveLinkPlugin2017.Build.cs @@ -1,76 +1,77 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -using UnrealBuildTool; -using System.IO; - -public abstract class MobuLiveLinkPluginBase : ModuleRules -{ - public MobuLiveLinkPluginBase(ReadOnlyTargetRules Target, string MobuVersionString) : base(Target) - { - PrivatePCHHeaderFile = "Private/MobuLiveLinkPluginPrivatePCH.h"; - - bEnforceIWYU = false; - bUseRTTI = true; - - PrivateIncludePaths.AddRange(new string[] - { - Path.Combine(EngineDirectory, "Source/Runtime/Launch/Public"), - Path.Combine(EngineDirectory, "Source/Runtime/Launch/Private"), - Path.Combine(ModuleDirectory, "StreamObjects/Public") - }); - - // Unreal dependency modules - PrivateDependencyModuleNames.AddRange(new string[] - { - "Core", - "CoreUObject", - "ApplicationCore", - "Projects", - "UdpMessaging", - "LiveLinkInterface", - "LiveLinkMessageBusFramework", - }); - - // Mobu SDK setup - { - //UE_MOTIONBUILDER2017_INSTALLATIONFOLDER - string MobuInstallFolder = System.Environment.GetEnvironmentVariable("UE_MOTIONBUILDER" + MobuVersionString + "_INSTALLATIONFOLDER"); - if (string.IsNullOrEmpty(MobuInstallFolder)) - { - MobuInstallFolder = @"C:\Program Files\Autodesk\MotionBuilder " + MobuVersionString; - } - MobuInstallFolder = Path.Combine(MobuInstallFolder, "OpenRealitySDK"); - - if (!Directory.Exists(MobuInstallFolder)) - { - // Try with build machine setup - string SDKRootEnvVar = System.Environment.GetEnvironmentVariable("UE_SDKS_ROOT"); - if (!string.IsNullOrEmpty(SDKRootEnvVar)) - { - MobuInstallFolder = Path.Combine(SDKRootEnvVar, "HostWin64", "Win64", "MotionBuilder", MobuVersionString); - } - } - - // Make sure this version of Mobu is actually installed - if (Directory.Exists(MobuInstallFolder)) - { - PrivateIncludePaths.Add(Path.Combine(MobuInstallFolder, "include")); - - if (Target.Platform == UnrealTargetPlatform.Win64) // @todo: Support other platforms? - { - string LibDir = Path.Combine(MobuInstallFolder, "lib/x64"); - - // Mobu library we're depending on - PublicAdditionalLibraries.Add(Path.Combine(LibDir, "fbsdk.lib")); - } - } - } - } -} - -public class MobuLiveLinkPlugin2017 : MobuLiveLinkPluginBase -{ - public MobuLiveLinkPlugin2017(ReadOnlyTargetRules Target) : base(Target, "2017") - { - } -} +// Copyright Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; +using System.IO; + +public abstract class MobuLiveLinkPluginBase : ModuleRules +{ + public MobuLiveLinkPluginBase(ReadOnlyTargetRules Target, string MobuVersionString) : base(Target) + { + PrivatePCHHeaderFile = "Private/MobuLiveLinkPluginPrivatePCH.h"; + + bEnforceIWYU = false; + bUseRTTI = true; + + PrivateIncludePaths.AddRange(new string[] + { + Path.Combine(EngineDirectory, "Source/Runtime/Launch/Public"), + Path.Combine(EngineDirectory, "Source/Runtime/Launch/Private"), + Path.Combine(ModuleDirectory, "StreamObjects/Public"), + }); + + // Unreal dependency modules + PrivateDependencyModuleNames.AddRange(new string[] + { + "Core", + "CoreUObject", + "ApplicationCore", + "Messaging", + "Projects", + "UdpMessaging", + "LiveLinkInterface", + "LiveLinkMessageBusFramework", + }); + + // Mobu SDK setup + { + //UE_MOTIONBUILDER2017_INSTALLATIONFOLDER + string MobuInstallFolder = System.Environment.GetEnvironmentVariable("UE_MOTIONBUILDER" + MobuVersionString + "_INSTALLATIONFOLDER"); + if (string.IsNullOrEmpty(MobuInstallFolder)) + { + MobuInstallFolder = @"C:\Program Files\Autodesk\MotionBuilder " + MobuVersionString; + } + MobuInstallFolder = Path.Combine(MobuInstallFolder, "OpenRealitySDK"); + + if (!Directory.Exists(MobuInstallFolder)) + { + // Try with build machine setup + string SDKRootEnvVar = System.Environment.GetEnvironmentVariable("UE_SDKS_ROOT"); + if (!string.IsNullOrEmpty(SDKRootEnvVar)) + { + MobuInstallFolder = Path.Combine(SDKRootEnvVar, "HostWin64", "Win64", "MotionBuilder", MobuVersionString); + } + } + + // Make sure this version of Mobu is actually installed + if (Directory.Exists(MobuInstallFolder)) + { + PrivateIncludePaths.Add(Path.Combine(MobuInstallFolder, "include")); + + if (Target.Platform == UnrealTargetPlatform.Win64) // @todo: Support other platforms? + { + string LibDir = Path.Combine(MobuInstallFolder, "lib/x64"); + + // Mobu library we're depending on + PublicAdditionalLibraries.Add(Path.Combine(LibDir, "fbsdk.lib")); + } + } + } + } +} + +public class MobuLiveLinkPlugin2017 : MobuLiveLinkPluginBase +{ + public MobuLiveLinkPlugin2017(ReadOnlyTargetRules Target) : base(Target, "2017") + { + } +} diff --git a/Source/MobuLiveLinkPlugin2017.Target.cs b/Source/MobuLiveLinkPlugin2017.Target.cs index e4d7a7eb..11eb1364 100644 --- a/Source/MobuLiveLinkPlugin2017.Target.cs +++ b/Source/MobuLiveLinkPlugin2017.Target.cs @@ -1,43 +1,116 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -using UnrealBuildTool; -using System.Collections.Generic; -using System.IO; - -public abstract class MobuLiveLinkPluginTargetBase : TargetRules -{ - public MobuLiveLinkPluginTargetBase(TargetInfo Target, string InMobuVersionString) : base(Target) - { - Type = TargetType.Program; - - bShouldCompileAsDLL = true; - LinkType = TargetLinkType.Monolithic; - SolutionDirectory = "Programs/LiveLink"; - - bool bIsNotForLicensees = false; - string NotForLicenseesFolder = bIsNotForLicensees ? "NotForLicensees" : ""; - - ExeBinariesSubFolder = Path.Combine(NotForLicenseesFolder, "MotionBuilder", InMobuVersionString); - LaunchModuleName = "MobuLiveLinkPlugin" + InMobuVersionString; - - // We only need minimal use of the engine for this plugin - bBuildDeveloperTools = false; - bUseMallocProfiler = false; - bBuildWithEditorOnlyData = true; - bCompileAgainstEngine = false; - bCompileAgainstCoreUObject = true; - bCompileICU = false; - bHasExports = false; - - string ResourcesFolder = Path.GetFullPath(Path.Combine("Programs", NotForLicenseesFolder, "MobuLiveLink/Resources")); - string BinariesDirectory = Path.GetFullPath(Path.Combine("../Binaries", "Win64", NotForLicenseesFolder, "MotionBuilder", InMobuVersionString)); - PostBuildSteps.Add(string.Format("echo Copying {0} to {1}...", ResourcesFolder, BinariesDirectory)); - PostBuildSteps.Add(string.Format("copy /a /b /y /v \"{0}\\*.*\" \"{1}\" 1>nul", ResourcesFolder, BinariesDirectory)); - } -} - -public class MobuLiveLinkPlugin2017Target : MobuLiveLinkPluginTargetBase -{ - public MobuLiveLinkPlugin2017Target(TargetInfo Target) : base(Target, "2017") - { } -} \ No newline at end of file +// Copyright Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; +using System; +using System.IO; +using Tools.DotNETCommon; +using System.Runtime.CompilerServices; + +[SupportedPlatforms(UnrealPlatformClass.Desktop)] +public abstract class MobuLiveLinkPluginTargetBase : TargetRules +{ + /// + /// Finds the innermost parent directory with the provided name. Search is case insensitive. + /// + string InnermostParentDirectoryPathWithName(string ParentName, string CurrentPath) + { + DirectoryInfo ParentInfo = Directory.GetParent(CurrentPath); + + if (ParentInfo == null) + { + throw new DirectoryNotFoundException("Could not find parent folder '" + ParentName + "'"); + } + + // Case-insensitive check of the parent folder name. + if (ParentInfo.Name.ToLower() == ParentName.ToLower()) + { + return ParentInfo.ToString(); + } + + return InnermostParentDirectoryPathWithName(ParentName, ParentInfo.ToString()); + } + + /// + /// Returns the path to this .cs file. + /// + string GetCallerFilePath([CallerFilePath] string CallerFilePath = "") + { + if (CallerFilePath.Length == 0) + { + throw new FileNotFoundException("Could not find the path of our .cs file"); + } + + return CallerFilePath; + } + + public MobuLiveLinkPluginTargetBase(TargetInfo Target, string InMobuVersionString) : base(Target) + { + Type = TargetType.Program; + + bShouldCompileAsDLL = true; + LinkType = TargetLinkType.Monolithic; + SolutionDirectory = "Programs/LiveLink"; + LaunchModuleName = "MobuLiveLinkPlugin" + InMobuVersionString; + + // We only need minimal use of the engine for this plugin + bBuildDeveloperTools = false; + bUseMallocProfiler = false; + bBuildWithEditorOnlyData = true; + bCompileAgainstEngine = false; + bCompileAgainstCoreUObject = true; + bCompileICU = false; + bHasExports = false; + + // This .cs file must be inside the source folder of this Program. We later use this to find other key directories. + string TargetFilePath = GetCallerFilePath(); + + // We need to avoid failing to load DLL due to looking for EngineDir() in non-existent folders. + // By having it build in the same directory as the engine, it will assume the engine is in the same directory + // as the program, and because this folder always exists, it will not fail the check inside EngineDir(). + + // Because this is a Program, we assume that this target file resides under a "Programs" folder. + string ProgramsDir = InnermostParentDirectoryPathWithName("Programs", TargetFilePath); + + // We assume this Program resides under a Source folder. + string SourceDir = InnermostParentDirectoryPathWithName("Source", ProgramsDir); + + // The program is assumed to reside inside the "Engine" folder. + string EngineDir = InnermostParentDirectoryPathWithName("Engine", SourceDir); + + // The default Binaries path is assumed to be a sibling of "Source" folder. + string DefaultBinDir = Path.GetFullPath(Path.Combine(SourceDir, "..", "Binaries", Platform.ToString())); + + // We assume that the engine exe resides in Engine/Binaries/[Platform] + string EngineBinariesDir = Path.Combine(EngineDir, "Binaries", Platform.ToString()); + + // Now we calculate the relative path between the default output directory and the engine binaries, + // in order to force the output of this program to be in the same folder as th engine. + ExeBinariesSubFolder = (new DirectoryReference(EngineBinariesDir)).MakeRelativeTo(new DirectoryReference(DefaultBinDir)); + + // Setting this is necessary since we are creating the binaries outside of Restricted. + bLegalToDistributeBinary = true; + + // We still need to copy the resources, so at this point we might as well copy the files where the default Binaries folder was meant to be. + // MobuLiveLinkPlugin.xml will be unaware of how the files got there. + + string ResourcesDir = Path.Combine(ProgramsDir, "MobuLiveLink", "Resources"); + string PostBuildBinDir = Path.Combine(DefaultBinDir, "MotionBuilder", InMobuVersionString); + + // Copy resources + PostBuildSteps.Add(string.Format("echo Copying {0} to {1}...", ResourcesDir, PostBuildBinDir)); + PostBuildSteps.Add(string.Format("xcopy /y /i /v \"{0}\\*.*\" \"{1}\" 1>nul", ResourcesDir, PostBuildBinDir)); + + // Copy binaries + PostBuildSteps.Add(string.Format("echo Copying {0} to {1}...", EngineBinariesDir, PostBuildBinDir)); + PostBuildSteps.Add(string.Format("xcopy /y /i /v \"{0}\\{1}.*\" \"{2}\" 1>nul", EngineBinariesDir, LaunchModuleName, PostBuildBinDir)); + } +} + +public class MobuLiveLinkPlugin2017Target : MobuLiveLinkPluginTargetBase +{ + public MobuLiveLinkPlugin2017Target(TargetInfo Target) : base(Target, "2017") + { + //Mobu is not strict c++ compliant before Mobu 2019 + WindowsPlatform.bStrictConformanceMode = false; + } +} diff --git a/Source/MobuLiveLinkPlugin2018.Target.cs b/Source/MobuLiveLinkPlugin2018.Target.cs index 76e48982..9fa57568 100644 --- a/Source/MobuLiveLinkPlugin2018.Target.cs +++ b/Source/MobuLiveLinkPlugin2018.Target.cs @@ -1,11 +1,14 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -using UnrealBuildTool; -using System.Collections.Generic; -using System.IO; - -public class MobuLiveLinkPlugin2018Target : MobuLiveLinkPluginTargetBase -{ - public MobuLiveLinkPlugin2018Target(TargetInfo Target) : base(Target, "2018") - {} +// Copyright Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; +using System.Collections.Generic; +using System.IO; + +public class MobuLiveLinkPlugin2018Target : MobuLiveLinkPluginTargetBase +{ + public MobuLiveLinkPlugin2018Target(TargetInfo Target) : base(Target, "2018") + { + //Mobu is not strict c++ compliant before Mobu 2019 + WindowsPlatform.bStrictConformanceMode = false; + } } \ No newline at end of file diff --git a/Source/MobuLiveLinkPlugin2020.Build.cs b/Source/MobuLiveLinkPlugin2020.Build.cs new file mode 100644 index 00000000..8985cad0 --- /dev/null +++ b/Source/MobuLiveLinkPlugin2020.Build.cs @@ -0,0 +1,11 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; +using System.IO; + +public class MobuLiveLinkPlugin2020 : MobuLiveLinkPluginBase +{ + public MobuLiveLinkPlugin2020(ReadOnlyTargetRules Target) : base(Target, "2020") + { + } +} diff --git a/Source/MobuLiveLinkPlugin2020.Target.cs b/Source/MobuLiveLinkPlugin2020.Target.cs new file mode 100644 index 00000000..fa6960ad --- /dev/null +++ b/Source/MobuLiveLinkPlugin2020.Target.cs @@ -0,0 +1,11 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; +using System.Collections.Generic; +using System.IO; + +public class MobuLiveLinkPlugin2020Target : MobuLiveLinkPluginTargetBase +{ + public MobuLiveLinkPlugin2020Target(TargetInfo Target) : base(Target, "2020") + {} +} \ No newline at end of file diff --git a/Source/MobuLiveLinkPlugin2022.Build.cs b/Source/MobuLiveLinkPlugin2022.Build.cs new file mode 100644 index 00000000..af16378c --- /dev/null +++ b/Source/MobuLiveLinkPlugin2022.Build.cs @@ -0,0 +1,12 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; +using System.IO; + +public class MobuLiveLinkPlugin2022 : MobuLiveLinkPluginBase +{ + public MobuLiveLinkPlugin2022(ReadOnlyTargetRules Target) : base(Target, "2022") + { + CppStandard = CppStandardVersion.Cpp17; + } +} diff --git a/Source/MobuLiveLinkPlugin2022.Target.cs b/Source/MobuLiveLinkPlugin2022.Target.cs new file mode 100644 index 00000000..7e6249c5 --- /dev/null +++ b/Source/MobuLiveLinkPlugin2022.Target.cs @@ -0,0 +1,11 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; +using System.Collections.Generic; +using System.IO; + +public class MobuLiveLinkPlugin2022Target : MobuLiveLinkPluginTargetBase +{ + public MobuLiveLinkPlugin2022Target(TargetInfo Target) : base(Target, "2022") + {} +} \ No newline at end of file diff --git a/Source/Private/MobuLiveLinkDevice.cpp b/Source/Private/MobuLiveLinkDevice.cpp index a5934e02..18fb1445 100644 --- a/Source/Private/MobuLiveLinkDevice.cpp +++ b/Source/Private/MobuLiveLinkDevice.cpp @@ -1,637 +1,742 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -//--- Class declaration -#include "MobuLiveLinkDevice.h" - -//--- Stream object for the Editor camera -#include "MobuLiveLinkStreamObjects.h" - -//--- Utility functions -#include "MobuLiveLinkUtilities.h" - -//--- Allow ticking of the engine -#include "Containers/Ticker.h" - -//--- For getting the dll location on disk -#include "Windows/AllowWindowsPlatformTypes.h" -#include -#include "Misc/Paths.h" -EXTERN_C IMAGE_DOS_HEADER __ImageBase; - -FString GetDeviceIconPath() -{ - char DllPath[MAX_PATH] = { 0 }; - GetModuleFileNameA((HINSTANCE)&__ImageBase, DllPath, _countof(DllPath)); - - FString BasePath = FPaths::GetPath(FString(DllPath)); - FString FinalPath = FPaths::Combine(BasePath, FString("UE_128.png")); - FBTrace("Device Icon Path - %s\n", FStringToChar(FinalPath)); - - return FinalPath; -}; - -#include "Windows/HideWindowsPlatformTypes.h" - -//--- Device strings -#define MOBULIVELINK__CLASS MOBULIVELINK__CLASSNAME -#define MOBULIVELINK__NAME MOBULIVELINK__CLASSSTR -#define MOBULIVELINK__LABEL "UE - LiveLink" -#define MOBULIVELINK__DESC "UE - LiveLink" - -//--- MobuLiveLink implementation and registration -FBDeviceImplementation ( MOBULIVELINK__CLASS ); -FBRegisterDevice ( MOBULIVELINK__NAME, - MOBULIVELINK__CLASS, - MOBULIVELINK__LABEL, - MOBULIVELINK__DESC, - FStringToChar(GetDeviceIconPath())); - -/************************************************ - * FMobuLiveLink Constructor. - ************************************************/ -bool FMobuLiveLink::FBCreate() -{ - // Set sampling rate to Before Render - CurrentSampleRate = SampleOptions.Last().Value; - UpdateSampleRate(); - - StartLiveLink(); - FBSystem().Scene->OnChange.Add(this, (FBCallback)&FMobuLiveLink::EventSceneChange); - - TSharedPtr EditorCamera = MakeShared(); - EditorCameraObject = EditorCamera; - AddStreamObject(-1, EditorCamera); - - LastEvaluationTime = FPlatformTime::Seconds(); - TimecodeMode = ETimecodeMode::TimecodeMode_Local; - - FBTrace("MobuLiveLink FBCreate\n"); - return true; -} - - -/************************************************ - * FMobuLiveLink Destructor. - ************************************************/ -void FMobuLiveLink::FBDestroy() -{ - FBSystem().Scene->OnChange.Remove(this, (FBCallback)&FMobuLiveLink::EventSceneChange); - if (bShouldUpdateInRenderCallback) - { - FBEvaluateManager::TheOne().OnRenderingPipelineEvent.Remove(this, (FBCallback)&FMobuLiveLink::EventRenderUpdate); - bShouldUpdateInRenderCallback = false; - } - - TSharedPtr EditorCameraObjectPin = EditorCameraObject.Pin(); - if (EditorCameraObjectPin.IsValid()) - { - LiveLinkProvider->RemoveSubject(EditorCameraObjectPin->GetSubjectName()); - FBTrace("Destroying Editor Camera\n"); - } - - StreamObjects.Empty(); - StopLiveLink(); - FBTrace("MobuLiveLink FBDestroy\n"); -} - -/************************************************ -* Device operation. -************************************************/ -void FMobuLiveLink::UpdateSampleRate() -{ - FBTime lPeriod; - - if (CurrentSampleRate == FFrameRate(-1, 1)) - { - if (!bShouldUpdateInRenderCallback) - { - // After Render - FBEvaluateManager::TheOne().OnRenderingPipelineEvent.Add(this, (FBCallback)&FMobuLiveLink::EventRenderUpdate); - bShouldUpdateInRenderCallback = true; - } - - lPeriod.SetSecondDouble(1.0); - } - else - { - if (bShouldUpdateInRenderCallback) - { - FBEvaluateManager::TheOne().OnRenderingPipelineEvent.Remove(this, (FBCallback)&FMobuLiveLink::EventRenderUpdate); - bShouldUpdateInRenderCallback = false; - } - - lPeriod.SetSecondDouble((double)CurrentSampleRate.Denominator / (double)CurrentSampleRate.Numerator); - } - FBTrace("Setting Sample Rate: %f\n", lPeriod.GetSecondDouble()); - SamplingPeriod = lPeriod; -} - - -/************************************************ - * Device operation. - ************************************************/ -bool FMobuLiveLink::DeviceOperation(kDeviceOperations pOperation) -{ - switch (pOperation) - { - case kOpInit: return Init(); - case kOpStart: return Start(); - case kOpStop: return Stop(); - case kOpReset: return Reset(); - case kOpDone: return Done(); - } - return FBDevice::DeviceOperation( pOperation ); -} - -void FMobuLiveLink::SetDeviceInformation(const char* NewDeviceInformation) -{ - FString VersionString("v2.3 ("); - VersionString += __DATE__; - VersionString += ")"; - HardwareVersionInfo.SetString(FStringToChar(VersionString)); - Information.SetString("Epic Games, Inc."); - Status.SetString(NewDeviceInformation); -} - - -/************************************************ - * Initialization of device. - ************************************************/ -bool FMobuLiveLink::Init() -{ - SetDeviceInformation("Status: Offline"); - return true; -} - - -/************************************************ - * Device is put online. - ************************************************/ - -bool FMobuLiveLink::Start() -{ - FBProgress lProgress; - lProgress.Caption = "Setting up device"; - lProgress.Text = "Setting sampling rate"; - - SetDeviceInformation("Status: Online"); - return true; -} - - -/************************************************ - * Device is stopped (offline). - ************************************************/ -bool FMobuLiveLink::Stop() -{ - FBProgress lProgress; - lProgress.Caption = "Shutting down device"; - - SetDeviceInformation("Status: Offline"); - return false; -} - - -/************************************************ - * Removal of device. - ************************************************/ -bool FMobuLiveLink::Done() -{ - return false; -} - - -/************************************************ - * Reset of device. - ************************************************/ -bool FMobuLiveLink::Reset() -{ - Stop(); - return Start(); -} - -/************************************************ - * Device Evaluation. - ************************************************/ -bool FMobuLiveLink::DeviceEvaluationNotify(kTransportMode pMode, FBEvaluateInfo* pEvaluateInfo) -{ - if (!bShouldUpdateInRenderCallback) - { - UpdateStream(); - } - return true; -} - -void FMobuLiveLink::EventRenderUpdate(HISender Sender, HKEvent Event) -{ - FBGlobalEvalCallbackTiming EventTiming = ((FBEventEvalGlobalCallback)Event).GetTiming(); - if (EventTiming == FBSDKNamespace::kFBGlobalEvalCallbackBeforeRender && this->Online) - { - UpdateStream(); - - // Count samples here since we aren't doing it in DeviceIONotify for render callback usage - AckOneSampleReceived(); - } -} - -void FMobuLiveLink::UpdateStream() -{ - mCleanUpLock.Lock(); - - TickCoreTicker(); - - FLiveLinkWorldTime WorldTime; - FQualifiedFrameTime QualifiedFrameTime = MobuUtilities::GetSceneTimecode(GetTimecodeMode()); - - - if (IsDirty()) - { - UpdateStreamObjects(); - } - for (TPair>& MapPair : StreamObjects) - { - const TSharedPtr& StreamObject = MapPair.Value; - StreamObject->UpdateSubjectFrame(LiveLinkProvider, WorldTime, QualifiedFrameTime); - } - - mCleanUpLock.Unlock(); -} - - -/************************************************ - * Real-Time Synchronous Device IO. - ************************************************/ -void FMobuLiveLink::DeviceIONotify(kDeviceIOs pAction,FBDeviceNotifyInfo &pDeviceNotifyInfo) -{ - // If we are tied to the render callback, then we don't want to count samples here - if (bShouldUpdateInRenderCallback) - { - return; - } - - FBTime lEvalTime; - switch (pAction) - { - // Output devices - case kIOPlayModeWrite: - case kIOStopModeWrite: - { - AckOneSampleSent(); - } - break; - // Input devices - case kIOStopModeRead: - case kIOPlayModeRead: - { - AckOneSampleReceived(); - } - break; - } -} - -int32 FMobuLiveLink::GetCurrentSampleRateIndex() -{ - int32 CurrentSampleIdx = 0; - for (int SampleIdx = 0; SampleIdx < SampleOptions.Num(); ++SampleIdx) - { - const FFrameRate& TestSampleRate = SampleOptions[SampleIdx].Value; - if (CurrentSampleRate == TestSampleRate) - { - CurrentSampleIdx = SampleIdx; - break; - } - } - return CurrentSampleIdx; -} - -//--- FBX load/save tags -#define MOBULIVELINK_FBX_DATA "MobuLiveLinkFBXDataV4" - -/************************************************ -* Save Format: -* Str Provider Name -* Int Stream editor camera -* Int use local or system clock to produce timecode -* Int sample rate index -* Int Number of object -* Str Root Name -* Str Subject Name -* Int Stream mode index -* Int Active status -* Int Animatable status -************************************************/ - -/************************************************ -* Store data in FBX. -************************************************/ -bool FMobuLiveLink::FbxStore(FBFbxObject* pFbxObject, kFbxObjectStore pStoreWhat) -{ - if (pStoreWhat & kAttributes) - { - pFbxObject->FieldWriteBegin(MOBULIVELINK_FBX_DATA); - { - FBTrace("FbxStore started\n"); - // Provider Name - pFbxObject->FieldWriteC(FStringToChar(GetProviderName())); - - // Stream editor camera - pFbxObject->FieldWriteI(IsEditorCameraStreamed()); - - // Use Local or System time for timecode - pFbxObject->FieldWriteI(GetTimecodeModeAsInt()); - - // Sample rate index - pFbxObject->FieldWriteI(GetCurrentSampleRateIndex()); - - // NumberOfObjects - int NumberOfObjects = 0; - for (TPair>& MapPair : StreamObjects) - { - const FString StreamObjectRootName = MapPair.Value->GetRootName(); - if (StreamObjectRootName.Len() > 0) - { - ++NumberOfObjects; - } - } - pFbxObject->FieldWriteI(NumberOfObjects); - - for (TPair>& MapPair : StreamObjects) - { - const FString StreamObjectRootName = MapPair.Value->GetRootName(); - if (StreamObjectRootName.Len() > 0) - { - const FName StreamObjectSubjectName = MapPair.Value->GetSubjectName(); - const int32 StreamObjectStreamingMode = MapPair.Value->GetStreamingMode(); - const int32 StreamObjectActive = MapPair.Value->GetActiveStatus(); - const int32 StreamAnimatableActive = MapPair.Value->GetSendAnimatableStatus(); - - pFbxObject->FieldWriteC(TCHAR_TO_UTF8(*StreamObjectRootName)); - pFbxObject->FieldWriteC(TCHAR_TO_UTF8(*StreamObjectSubjectName.ToString())); - pFbxObject->FieldWriteI(StreamObjectStreamingMode); - pFbxObject->FieldWriteI(StreamObjectActive); - pFbxObject->FieldWriteI(StreamAnimatableActive); - } - } - pFbxObject->FieldWriteEnd(); - FBTrace("FbxStore finished\n"); - } - } - return true; -} - -/************************************************ -* Retrieve data from FBX. -************************************************/ -bool FMobuLiveLink::FbxRetrieve(FBFbxObject* pFbxObject, kFbxObjectStore pStoreWhat) -{ - if (pStoreWhat & kAttributes) - { - if (pFbxObject->FieldReadBegin(MOBULIVELINK_FBX_DATA)) - { - FBTrace("FbxRetrieve started\n"); - // Provider Name - SetProviderName(CharToFString(pFbxObject->FieldReadC())); - - // Stream editor camera - const bool bStreamEditorCamera = pFbxObject->FieldReadI() != 0; - SetEditorCameraStreamed(bStreamEditorCamera); - - // Use Local or System time for timecode - const int32 ReadTimecodeModeInt = pFbxObject->FieldReadI(); - SetTimecodeModeFromInt(ReadTimecodeModeInt); - - // Sample rate index - const int32 CurrentSampleIndex = pFbxObject->FieldReadI(); - if (CurrentSampleIndex > 0 && CurrentSampleIndex < SampleOptions.Num()) - { - CurrentSampleRate = SampleOptions[CurrentSampleIndex].Value; - UpdateSampleRate(); - } - - // NumberOfObjects - const int32 NumberOfObjects = pFbxObject->FieldReadI(); - - for (int32 i = 0; i < NumberOfObjects; ++i) - { - FBComponentList FoundModels; - FString StreamObjectRootName(pFbxObject->FieldReadC()); - FBFindObjectsByName(TCHAR_TO_UTF8(*StreamObjectRootName), FoundModels, true, false); - - if (FoundModels.GetCount() > 0) - { - FBModel* FoundFBModel = (FBModel*)FoundModels[0]; - TSharedPtr FoundStreamObject = StreamObjectManagement::FBModelToStreamObject(FoundFBModel); - - FName SubjectName(pFbxObject->FieldReadC()); - int32 StreamingMode = pFbxObject->FieldReadI(); - - bool bObjectActive = pFbxObject->FieldReadI() != 0; - bool bStreamOAnimatableActive = pFbxObject->FieldReadI() != 0; - - FoundStreamObject->UpdateSubjectName(SubjectName); - FoundStreamObject->UpdateStreamingMode(StreamingMode); - FoundStreamObject->UpdateActiveStatus(bObjectActive); - FoundStreamObject->UpdateSendAnimatableStatus(bStreamOAnimatableActive); - - // Add the object last so the SubjectName is correct - AddStreamObject(GetNextUID(), FoundStreamObject); - } - else - { - pFbxObject->FieldReadC(); - pFbxObject->FieldReadI(); - pFbxObject->FieldReadI(); - pFbxObject->FieldReadI(); - } - } - pFbxObject->FieldReadEnd(); - - SetRefreshUI(true); - FBTrace("FbxRetrieve finished\n"); - } - } - return true; -} - - -void FMobuLiveLink::StartLiveLink() -{ - if (LiveLinkProvider != nullptr) - { - FBTrace("Live Link Provider '%s' already started!\n", FStringToChar(GetProviderName())); - return; - } - - LiveLinkProvider = ILiveLinkProvider::CreateLiveLinkProvider(GetProviderName()); - - UpdateStreamObjects(); - - FBTrace("Live Link Provider '%s' started!\n", FStringToChar(GetProviderName())); -} - - -void FMobuLiveLink::StopLiveLink() -{ - TickCoreTicker(); - if (LiveLinkProvider.IsValid()) - { - FBTrace("LiveLinkProvider References: %d\n", LiveLinkProvider.GetSharedReferenceCount()); - LiveLinkProvider = nullptr; - FBTrace("Deleting Live Link\n"); - } - FBTrace("Live Link Provider '%s' stopped!\n", FStringToChar(GetProviderName())); -} - -void FMobuLiveLink::EventSceneChange(HISender Sender, HKEvent Event) -{ - FBEventSceneChange SceneChangeEvent = Event; - switch (SceneChangeEvent.Type) - { - case kFBSceneChangeSelect: - case kFBSceneChangeUnselect: - case kFBSceneChangeReSelect: - case kFBSceneChangeFocus: - case kFBSceneChangeSoftSelect: - case kFBSceneChangeSoftUnselect: - case kFBSceneChangeHardSelect: - case kFBSceneChangeTransactionBegin: - case kFBSceneChangeTransactionEnd: - return; - case kFBSceneChangeLoadBegin: - // Crashes if you try and stream while loading a new file - DeviceOperation(FBDevice::kOpStop); - return; - default: - SetDirty(true); - break; - } - -} - -void FMobuLiveLink::AddStreamObject(int32 NewUID, StreamObjectPtr NewObject) -{ - if (NewObject->IsValid()) - { - FBTrace("Added new Subject '%s' to StreamObjects\n", FStringToChar(NewObject->GetSubjectName().ToString())); - StreamObjects.Emplace(NewUID, NewObject); - - SetDirty(true); - } -} - -void FMobuLiveLink::RemoveStreamObject(int32 DeletionKey, StreamObjectPtr RemoveObject) -{ - if (RemoveObject->IsValid()) - { - FBTrace("Removed Subject '%s' from StreamObjects\n", FStringToChar(RemoveObject->GetSubjectName().ToString())); - StreamObjects.Remove(DeletionKey); - LiveLinkProvider->RemoveSubject(RemoveObject->GetSubjectName()); - - SetDirty(true); - } -} - -void FMobuLiveLink::ChangeSubjectName(StreamObjectPtr ObjectPtr, const char* NewSubjectNameStr) -{ - if (ObjectPtr->GetSubjectName() != NewSubjectNameStr) - { - FBTrace("Subject Name changed from '%s' to '%s'\n", FStringToChar(ObjectPtr->GetSubjectName().ToString()), NewSubjectNameStr); - LiveLinkProvider->RemoveSubject(ObjectPtr->GetSubjectName()); - ObjectPtr->UpdateSubjectName(FName(NewSubjectNameStr)); - - SetDirty(true); - } -} - -void FMobuLiveLink::UpdateStreamObjects() -{ - for (TPair>& MapPair : StreamObjects) - { - const TSharedPtr& StreamObject = MapPair.Value; - if (StreamObject->IsValid()) - { - StreamObject->Refresh(LiveLinkProvider); - } - else - { - FBTrace("UpdateStreamObjects - StreamObject for key %d isn't valid - this should never happen!\nPossible error in LiveLink subject list!", MapPair.Key); - } - } - SetDirty(false); - SetRefreshUI(true); -} - -void FMobuLiveLink::TickCoreTicker() -{ - double CurrentTime = FPlatformTime::Seconds(); - FTicker::GetCoreTicker().Tick(CurrentTime - LastEvaluationTime); - LastEvaluationTime = CurrentTime; -} - -int32 FMobuLiveLink::GetNextUID() -{ - return NextUID++; -} - -bool FMobuLiveLink::IsEditorCameraStreamed() const -{ - TSharedPtr EditorCameraObjectPin = EditorCameraObject.Pin(); - if (EditorCameraObjectPin.IsValid()) - { - return EditorCameraObjectPin->GetActiveStatus(); - } - return false; -} - -void FMobuLiveLink::SetEditorCameraStreamed(bool bStream) -{ - TSharedPtr EditorCameraObjectPin = EditorCameraObject.Pin(); - if (EditorCameraObjectPin.IsValid()) - { - EditorCameraObjectPin->UpdateActiveStatus(bStream); - } -} - -ETimecodeMode FMobuLiveLink::GetTimecodeMode() const -{ - return TimecodeMode; -} - -int32 FMobuLiveLink::GetTimecodeModeAsInt() const -{ - return (int32)TimecodeMode; -} - -void FMobuLiveLink::SetTimecodeMode(ETimecodeMode InTimecodeMode) -{ - TimecodeMode = InTimecodeMode; -} - -void FMobuLiveLink::SetTimecodeModeFromInt(int32 InTimecodeModeInt) -{ - switch (InTimecodeModeInt) - { - case 2: TimecodeMode = ETimecodeMode::TimecodeMode_Reference; - break; - - case 1: TimecodeMode = ETimecodeMode::TimecodeMode_System; - break; - - // Intentional fallthrough - case 0: - default: TimecodeMode = ETimecodeMode::TimecodeMode_Local; - break; - } -} - -void FMobuLiveLink::SetProviderName(const FString& NewValue) -{ - if (NewValue != GetProviderName()) - { - StopLiveLink(); - CurrentProviderName = NewValue; - StartLiveLink(); - - SetRefreshUI(true); - } -} \ No newline at end of file +// Copyright Epic Games, Inc. All Rights Reserved. + +//--- Class declaration +#include "MobuLiveLinkDevice.h" + +//--- Stream object for the Editor camera +#include "MobuLiveLinkStreamObjects.h" + +//--- Utility functions +#include "MobuLiveLinkUtilities.h" + +//--- Allow ticking of the engine +#include "Containers/Ticker.h" + +//--- UDP Network configuration +#include "Features/IModularFeatures.h" +#include "INetworkMessagingExtension.h" +#include "Shared/UdpMessagingSettings.h" + +//--- For getting the dll location on disk +#include "Windows/AllowWindowsPlatformTypes.h" +#include +#include "Misc/Paths.h" +EXTERN_C IMAGE_DOS_HEADER __ImageBase; + +FString GetDeviceIconPath() +{ + char DllPath[MAX_PATH] = { 0 }; + GetModuleFileNameA((HINSTANCE)&__ImageBase, DllPath, _countof(DllPath)); + + FString BasePath = FPaths::GetPath(FString(DllPath)); + FString FinalPath = FPaths::Combine(BasePath, FString("UE_128.png")); + FBTrace("Device Icon Path - %s\n", FStringToChar(FinalPath)); + + return FinalPath; +}; + +#include "Windows/HideWindowsPlatformTypes.h" + +//--- Device strings +#define MOBULIVELINK__CLASS MOBULIVELINK__CLASSNAME +#define MOBULIVELINK__NAME MOBULIVELINK__CLASSSTR +#define MOBULIVELINK__LABEL "UE - LiveLink" +#define MOBULIVELINK__DESC "UE - LiveLink" + +//--- MobuLiveLink implementation and registration +FBDeviceImplementation ( MOBULIVELINK__CLASS ); +FBRegisterDevice ( MOBULIVELINK__NAME, + MOBULIVELINK__CLASS, + MOBULIVELINK__LABEL, + MOBULIVELINK__DESC, + FStringToChar(GetDeviceIconPath())); + +/************************************************ + * FMobuLiveLink Constructor. + ************************************************/ +bool FMobuLiveLink::FBCreate() +{ + // Set sampling rate to Before Render + CurrentSampleRate = SampleOptions.Last().Value; + UpdateSampleRate(); + + StartLiveLink(); + FBSystem().Scene->OnChange.Add(this, (FBCallback)&FMobuLiveLink::EventSceneChange); + + TSharedPtr EditorCamera = MakeShared(); + EditorCameraObject = EditorCamera; + AddStreamObject(-1, EditorCamera); + + LastEvaluationTime = FPlatformTime::Seconds(); + TimecodeMode = ETimecodeMode::TimecodeMode_Local; + + FBTrace("MobuLiveLink FBCreate\n"); + return true; +} + + +/************************************************ + * FMobuLiveLink Destructor. + ************************************************/ +void FMobuLiveLink::FBDestroy() +{ + FBSystem().Scene->OnChange.Remove(this, (FBCallback)&FMobuLiveLink::EventSceneChange); + if (bShouldUpdateInRenderCallback) + { + FBEvaluateManager::TheOne().OnRenderingPipelineEvent.Remove(this, (FBCallback)&FMobuLiveLink::EventRenderUpdate); + bShouldUpdateInRenderCallback = false; + } + + TSharedPtr EditorCameraObjectPin = EditorCameraObject.Pin(); + if (EditorCameraObjectPin.IsValid()) + { + LiveLinkProvider->RemoveSubject(EditorCameraObjectPin->GetSubjectName()); + FBTrace("Destroying Editor Camera\n"); + } + + StreamObjects.Empty(); + StopLiveLink(); + FBTrace("MobuLiveLink FBDestroy\n"); +} + +/************************************************ +* Device operation. +************************************************/ +void FMobuLiveLink::UpdateSampleRate() +{ + FBTime lPeriod; + + if (CurrentSampleRate == FFrameRate(-1, 1)) + { + if (!bShouldUpdateInRenderCallback) + { + // After Render + FBEvaluateManager::TheOne().OnRenderingPipelineEvent.Add(this, (FBCallback)&FMobuLiveLink::EventRenderUpdate); + bShouldUpdateInRenderCallback = true; + } + + lPeriod.SetSecondDouble(1.0); + } + else + { + if (bShouldUpdateInRenderCallback) + { + FBEvaluateManager::TheOne().OnRenderingPipelineEvent.Remove(this, (FBCallback)&FMobuLiveLink::EventRenderUpdate); + bShouldUpdateInRenderCallback = false; + } + + lPeriod.SetSecondDouble((double)CurrentSampleRate.Denominator / (double)CurrentSampleRate.Numerator); + } + FBTrace("Setting Sample Rate: %f\n", lPeriod.GetSecondDouble()); + SamplingPeriod = lPeriod; +} + + +/************************************************ + * Device operation. + ************************************************/ +bool FMobuLiveLink::DeviceOperation(kDeviceOperations pOperation) +{ + switch (pOperation) + { + case kOpInit: return Init(); + case kOpStart: return Start(); + case kOpStop: return Stop(); + case kOpReset: return Reset(); + case kOpDone: return Done(); + } + return FBDevice::DeviceOperation( pOperation ); +} + +void FMobuLiveLink::SetDeviceInformation(const char* NewDeviceInformation) +{ + FString VersionString("v2.5.0 ("); + VersionString += __DATE__; + VersionString += ")"; + HardwareVersionInfo.SetString(FStringToChar(VersionString)); + Information.SetString("Epic Games, Inc."); + Status.SetString(NewDeviceInformation); +} + + +/************************************************ + * Initialization of device. + ************************************************/ +bool FMobuLiveLink::Init() +{ + SetDeviceInformation("Status: Offline"); + return true; +} + + +/************************************************ + * Device is put online. + ************************************************/ + +bool FMobuLiveLink::Start() +{ + FBProgress lProgress; + lProgress.Caption = "Setting up device"; + lProgress.Text = "Setting sampling rate"; + + SetDeviceInformation("Status: Online"); + return true; +} + + +/************************************************ + * Device is stopped (offline). + ************************************************/ +bool FMobuLiveLink::Stop() +{ + FBProgress lProgress; + lProgress.Caption = "Shutting down device"; + + SetDeviceInformation("Status: Offline"); + return false; +} + + +/************************************************ + * Removal of device. + ************************************************/ +bool FMobuLiveLink::Done() +{ + return false; +} + + +/************************************************ + * Reset of device. + ************************************************/ +bool FMobuLiveLink::Reset() +{ + Stop(); + return Start(); +} + +/************************************************ + * Device Evaluation. + ************************************************/ +bool FMobuLiveLink::DeviceEvaluationNotify(kTransportMode pMode, FBEvaluateInfo* pEvaluateInfo) +{ + if (!bShouldUpdateInRenderCallback) + { + UpdateStream(); + } + return true; +} + +void FMobuLiveLink::EventRenderUpdate(HISender Sender, HKEvent Event) +{ + FBGlobalEvalCallbackTiming EventTiming = ((FBEventEvalGlobalCallback)Event).GetTiming(); + if (EventTiming == FBSDKNamespace::kFBGlobalEvalCallbackBeforeRender && this->Online) + { + UpdateStream(); + + // Count samples here since we aren't doing it in DeviceIONotify for render callback usage + AckOneSampleReceived(); + } +} + +void FMobuLiveLink::UpdateStream() +{ + mCleanUpLock.Lock(); + + TickCoreTicker(); + + FLiveLinkWorldTime WorldTime; + FQualifiedFrameTime QualifiedFrameTime = MobuUtilities::GetSceneTimecode(GetTimecodeMode()); + + + if (IsDirty()) + { + UpdateStreamObjects(); + } + for (TPair>& MapPair : StreamObjects) + { + const TSharedPtr& StreamObject = MapPair.Value; + StreamObject->UpdateSubjectFrame(LiveLinkProvider, WorldTime, QualifiedFrameTime); + } + + mCleanUpLock.Unlock(); +} + + +/************************************************ + * Real-Time Synchronous Device IO. + ************************************************/ +void FMobuLiveLink::DeviceIONotify(kDeviceIOs pAction,FBDeviceNotifyInfo &pDeviceNotifyInfo) +{ + // If we are tied to the render callback, then we don't want to count samples here + if (bShouldUpdateInRenderCallback) + { + return; + } + + FBTime lEvalTime; + switch (pAction) + { + // Output devices + case kIOPlayModeWrite: + case kIOStopModeWrite: + { + AckOneSampleSent(); + } + break; + // Input devices + case kIOStopModeRead: + case kIOPlayModeRead: + { + AckOneSampleReceived(); + } + break; + } +} + +int32 FMobuLiveLink::GetCurrentSampleRateIndex() +{ + int32 CurrentSampleIdx = 0; + for (int SampleIdx = 0; SampleIdx < SampleOptions.Num(); ++SampleIdx) + { + const FFrameRate& TestSampleRate = SampleOptions[SampleIdx].Value; + if (CurrentSampleRate == TestSampleRate) + { + CurrentSampleIdx = SampleIdx; + break; + } + } + return CurrentSampleIdx; +} + +//--- FBX load/save tags +#define MOBULIVELINK_FBX_DATA_V4 "MobuLiveLinkFBXDataV4" +#define MOBULIVELINK_FBX_DATA "MobuLiveLinkFBXDataV5" + +/************************************************ +* Save Format: +* Str Provider Name +* Int Stream editor camera +* Int use local or system clock to produce timecode +* Int sample rate index +* Str Unicast Endpoint +* Int Number of object +* Str Root Name +* Str Subject Name +* Int Stream mode index +* Int Active status +* Int Animatable status +* Int Number of Static Endpoints +* Str Static Endpoint +************************************************/ + +/************************************************ +* Store data in FBX. +************************************************/ +bool FMobuLiveLink::FbxStore(FBFbxObject* pFbxObject, kFbxObjectStore pStoreWhat) +{ + if (pStoreWhat & kAttributes) + { + pFbxObject->FieldWriteBegin(MOBULIVELINK_FBX_DATA); + { + FBTrace("FbxStore started\n"); + // Provider Name + pFbxObject->FieldWriteC(FStringToChar(GetProviderName())); + + // Stream editor camera + pFbxObject->FieldWriteI(IsEditorCameraStreamed()); + + // Use Local or System time for timecode + pFbxObject->FieldWriteI(GetTimecodeModeAsInt()); + + // Sample rate index + pFbxObject->FieldWriteI(GetCurrentSampleRateIndex()); + + // NumberOfObjects + int NumberOfObjects = 0; + for (TPair>& MapPair : StreamObjects) + { + const FString StreamObjectRootName = MapPair.Value->GetRootName(); + if (StreamObjectRootName.Len() > 0) + { + ++NumberOfObjects; + } + } + pFbxObject->FieldWriteI(NumberOfObjects); + + for (TPair>& MapPair : StreamObjects) + { + const FString StreamObjectRootName = MapPair.Value->GetRootName(); + if (StreamObjectRootName.Len() > 0) + { + const FName StreamObjectSubjectName = MapPair.Value->GetSubjectName(); + const int32 StreamObjectStreamingMode = MapPair.Value->GetStreamingMode(); + const int32 StreamObjectActive = MapPair.Value->GetActiveStatus(); + const int32 StreamAnimatableActive = MapPair.Value->GetSendAnimatableStatus(); + + pFbxObject->FieldWriteC(TCHAR_TO_UTF8(*StreamObjectRootName)); + pFbxObject->FieldWriteC(TCHAR_TO_UTF8(*StreamObjectSubjectName.ToString())); + pFbxObject->FieldWriteI(StreamObjectStreamingMode); + pFbxObject->FieldWriteI(StreamObjectActive); + pFbxObject->FieldWriteI(StreamAnimatableActive); + } + } + + // Unicast endpoint + pFbxObject->FieldWriteC(FStringToChar(GetUnicastEndpoint())); + + // Static endpoints + pFbxObject->FieldWriteI(StaticEndpoints.Num()); + for (const FString& Endpoint: StaticEndpoints) + { + pFbxObject->FieldWriteC(FStringToChar(Endpoint)); + } + + pFbxObject->FieldWriteEnd(); + FBTrace("FbxStore finished\n"); + } + } + return true; +} + +/************************************************ +* Retrieve data from FBX. +************************************************/ +bool FMobuLiveLink::FbxRetrieve(FBFbxObject* FbxObject, kFbxObjectStore StoreWhat) +{ + if (StoreWhat & kAttributes) + { + if (FbxObject->FieldReadBegin(MOBULIVELINK_FBX_DATA_V4)) + { + FBTrace("FbxRetrieve started\n"); + FbxRetrieveV4(FbxObject, StoreWhat); + + FbxObject->FieldReadEnd(); + + SetRefreshUI(true); + FBTrace("FbxRetrieve finished\n"); + } + else if (FbxObject->FieldReadBegin(MOBULIVELINK_FBX_DATA)) + { + FBTrace("FbxRetrieve started\n"); + FbxRetrieveV4(FbxObject, StoreWhat); + + // Unicast endpoint + SetUnicastEndpoint(CharToFString(FbxObject->FieldReadC())); + + // Static endpoints + const int StaticEndpointNum = FbxObject->FieldReadI(); + for (int i = 0; i < StaticEndpointNum; ++i) + { + AddStaticEndpoint(CharToFString(FbxObject->FieldReadC())); + } + FbxObject->FieldReadEnd(); + + SetRefreshUI(true); + FBTrace("FbxRetrieve finished\n"); + } + } + return true; +} + +void FMobuLiveLink::FbxRetrieveV4(FBFbxObject* pFbxObject, kFbxObjectStore pStoreWhat) +{ + // Provider Name + SetProviderName(CharToFString(pFbxObject->FieldReadC())); + + // Stream editor camera + const bool bStreamEditorCamera = pFbxObject->FieldReadI() != 0; + SetEditorCameraStreamed(bStreamEditorCamera); + + // Use Local or System time for timecode + const int32 ReadTimecodeModeInt = pFbxObject->FieldReadI(); + SetTimecodeModeFromInt(ReadTimecodeModeInt); + + // Sample rate index + const int32 CurrentSampleIndex = pFbxObject->FieldReadI(); + if (CurrentSampleIndex > 0 && CurrentSampleIndex < SampleOptions.Num()) + { + CurrentSampleRate = SampleOptions[CurrentSampleIndex].Value; + UpdateSampleRate(); + } + + // NumberOfObjects + const int32 NumberOfObjects = pFbxObject->FieldReadI(); + + for (int32 i = 0; i < NumberOfObjects; ++i) + { + FBComponentList FoundModels; + FString StreamObjectRootName(pFbxObject->FieldReadC()); + FBFindObjectsByName(TCHAR_TO_UTF8(*StreamObjectRootName), FoundModels, true, false); + + if (FoundModels.GetCount() > 0) + { + FBModel* FoundFBModel = (FBModel*)FoundModels[0]; + TSharedPtr FoundStreamObject = StreamObjectManagement::FBModelToStreamObject(FoundFBModel); + + FName SubjectName(pFbxObject->FieldReadC()); + int32 StreamingMode = pFbxObject->FieldReadI(); + + bool bObjectActive = pFbxObject->FieldReadI() != 0; + bool bStreamOAnimatableActive = pFbxObject->FieldReadI() != 0; + + FoundStreamObject->UpdateSubjectName(SubjectName); + FoundStreamObject->UpdateStreamingMode(StreamingMode); + FoundStreamObject->UpdateActiveStatus(bObjectActive); + FoundStreamObject->UpdateSendAnimatableStatus(bStreamOAnimatableActive); + + // Add the object last so the SubjectName is correct + AddStreamObject(GetNextUID(), FoundStreamObject); + } + else + { + pFbxObject->FieldReadC(); + pFbxObject->FieldReadI(); + pFbxObject->FieldReadI(); + pFbxObject->FieldReadI(); + } + } +} + + +void FMobuLiveLink::StartLiveLink() +{ + if (LiveLinkProvider != nullptr) + { + FBTrace("Live Link Provider '%s' already started!\n", FStringToChar(GetProviderName())); + return; + } + + LiveLinkProvider = ILiveLinkProvider::CreateLiveLinkProvider(GetProviderName()); + + UpdateStreamObjects(); + + FBTrace("Live Link Provider '%s' started!\n", FStringToChar(GetProviderName())); +} + + +void FMobuLiveLink::StopLiveLink() +{ + TickCoreTicker(); + if (LiveLinkProvider.IsValid()) + { + FBTrace("LiveLinkProvider References: %d\n", LiveLinkProvider.GetSharedReferenceCount()); + LiveLinkProvider = nullptr; + FBTrace("Deleting Live Link\n"); + } + FBTrace("Live Link Provider '%s' stopped!\n", FStringToChar(GetProviderName())); +} + +void FMobuLiveLink::EventSceneChange(HISender Sender, HKEvent Event) +{ + FBEventSceneChange SceneChangeEvent = Event; + switch (SceneChangeEvent.Type) + { + case kFBSceneChangeSelect: + case kFBSceneChangeUnselect: + case kFBSceneChangeReSelect: + case kFBSceneChangeFocus: + case kFBSceneChangeSoftSelect: + case kFBSceneChangeSoftUnselect: + case kFBSceneChangeHardSelect: + case kFBSceneChangeTransactionBegin: + case kFBSceneChangeTransactionEnd: + return; + case kFBSceneChangeLoadBegin: + // Crashes if you try and stream while loading a new file + DeviceOperation(FBDevice::kOpStop); + return; + default: + SetDirty(true); + break; + } + +} + +void FMobuLiveLink::AddStreamObject(int32 NewUID, StreamObjectPtr NewObject) +{ + if (NewObject->IsValid()) + { + FBTrace("Added new Subject '%s' to StreamObjects\n", FStringToChar(NewObject->GetSubjectName().ToString())); + StreamObjects.Emplace(NewUID, NewObject); + + SetDirty(true); + } +} + +void FMobuLiveLink::RemoveStreamObject(int32 DeletionKey, StreamObjectPtr RemoveObject) +{ + FBTrace("Removed Subject '%s' from StreamObjects\n", FStringToChar(RemoveObject->GetSubjectName().ToString())); + StreamObjects.Remove(DeletionKey); + LiveLinkProvider->RemoveSubject(RemoveObject->GetSubjectName()); + + SetDirty(true); +} + +void FMobuLiveLink::ChangeSubjectName(StreamObjectPtr ObjectPtr, const char* NewSubjectNameStr) +{ + if (ObjectPtr->GetSubjectName() != NewSubjectNameStr) + { + FBTrace("Subject Name changed from '%s' to '%s'\n", FStringToChar(ObjectPtr->GetSubjectName().ToString()), NewSubjectNameStr); + LiveLinkProvider->RemoveSubject(ObjectPtr->GetSubjectName()); + ObjectPtr->UpdateSubjectName(FName(NewSubjectNameStr)); + + SetDirty(true); + } +} + +void FMobuLiveLink::UpdateStreamObjects() +{ + decltype(StreamObjects) StreamObjectsToRemove; + + for (TPair>& MapPair : StreamObjects) + { + const TSharedPtr& StreamObject = MapPair.Value; + if (StreamObject->IsValid()) + { + StreamObject->Refresh(LiveLinkProvider); + } + else + { + StreamObjectsToRemove.Add(MapPair); + } + } + + for (const auto& MapPair : StreamObjectsToRemove) + { + RemoveStreamObject(MapPair.Key, MapPair.Value); + } + + SetDirty(false); + SetRefreshUI(true); +} + +void FMobuLiveLink::TickCoreTicker() +{ + double CurrentTime = FPlatformTime::Seconds(); + FTicker::GetCoreTicker().Tick(CurrentTime - LastEvaluationTime); + LastEvaluationTime = CurrentTime; +} + +int32 FMobuLiveLink::GetNextUID() +{ + return NextUID++; +} + +bool FMobuLiveLink::IsEditorCameraStreamed() const +{ + TSharedPtr EditorCameraObjectPin = EditorCameraObject.Pin(); + if (EditorCameraObjectPin.IsValid()) + { + return EditorCameraObjectPin->GetActiveStatus(); + } + return false; +} + +void FMobuLiveLink::SetEditorCameraStreamed(bool bStream) +{ + TSharedPtr EditorCameraObjectPin = EditorCameraObject.Pin(); + if (EditorCameraObjectPin.IsValid()) + { + EditorCameraObjectPin->UpdateActiveStatus(bStream); + } +} + +ETimecodeMode FMobuLiveLink::GetTimecodeMode() const +{ + return TimecodeMode; +} + +int32 FMobuLiveLink::GetTimecodeModeAsInt() const +{ + return (int32)TimecodeMode; +} + +void FMobuLiveLink::SetTimecodeMode(ETimecodeMode InTimecodeMode) +{ + TimecodeMode = InTimecodeMode; +} + +void FMobuLiveLink::SetTimecodeModeFromInt(int32 InTimecodeModeInt) +{ + switch (InTimecodeModeInt) + { + case 2: TimecodeMode = ETimecodeMode::TimecodeMode_Reference; + break; + + case 1: TimecodeMode = ETimecodeMode::TimecodeMode_System; + break; + + // Intentional fallthrough + case 0: + default: TimecodeMode = ETimecodeMode::TimecodeMode_Local; + break; + } +} + +void FMobuLiveLink::SetProviderName(const FString& NewValue) +{ + if (NewValue != GetProviderName()) + { + StopLiveLink(); + CurrentProviderName = NewValue; + StartLiveLink(); + + SetRefreshUI(true); + } +} + +FString FMobuLiveLink::GetUnicastEndpoint() const +{ + if (IModularFeatures::Get().IsModularFeatureAvailable(INetworkMessagingExtension::ModularFeatureName)) + { + UUdpMessagingSettings* Settings = GetMutableDefault(); + return Settings->UnicastEndpoint; + } + + return TEXT("0.0.0.0:0"); +} + +void FMobuLiveLink::SetUnicastEndpoint(const FString& InEndpoint) +{ + if (InEndpoint != GetUnicastEndpoint()) + { + if (IModularFeatures::Get().IsModularFeatureAvailable(INetworkMessagingExtension::ModularFeatureName)) + { + StopLiveLink(); + UUdpMessagingSettings* Settings = GetMutableDefault(); + Settings->UnicastEndpoint = InEndpoint; + INetworkMessagingExtension& NetworkExtension = IModularFeatures::Get().GetModularFeature(INetworkMessagingExtension::ModularFeatureName); + NetworkExtension.RestartServices(); + + StartLiveLink(); + SetRefreshUI(true); + } + } +} + +bool FMobuLiveLink::AddStaticEndpoint(const FString& InEndpoint) +{ + if (IModularFeatures::Get().IsModularFeatureAvailable(INetworkMessagingExtension::ModularFeatureName)) + { + INetworkMessagingExtension& NetworkExtension = IModularFeatures::Get().GetModularFeature(INetworkMessagingExtension::ModularFeatureName); + NetworkExtension.AddEndpoint(InEndpoint); + StaticEndpoints.Push(InEndpoint); + SetRefreshUI(true); + return true; + } + return false; +} + +bool FMobuLiveLink::RemoveStaticEndpoint(const FString& InEndpoint) +{ + if (IModularFeatures::Get().IsModularFeatureAvailable(INetworkMessagingExtension::ModularFeatureName)) + { + INetworkMessagingExtension& NetworkExtension = IModularFeatures::Get().GetModularFeature(INetworkMessagingExtension::ModularFeatureName); + NetworkExtension.RemoveEndpoint(InEndpoint); + StaticEndpoints.RemoveSingle(InEndpoint); + SetRefreshUI(true); + return true; + } + return false; +} diff --git a/Source/Private/MobuLiveLinkLayout.cpp b/Source/Private/MobuLiveLinkLayout.cpp index c61568d6..8d40e18f 100644 --- a/Source/Private/MobuLiveLinkLayout.cpp +++ b/Source/Private/MobuLiveLinkLayout.cpp @@ -1,448 +1,715 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#include "MobuLiveLinkLayout.h" -#include "MobuLiveLinkStreamObjects.h" -#include "MobuLiveLinkUtilities.h" -#include - -#define MOBULIVELINK__LAYOUT FMobuLiveLinkLayout - -FBDeviceLayoutImplementation(MOBULIVELINK__LAYOUT); -FBRegisterDeviceLayout(MOBULIVELINK__LAYOUT, - MOBULIVELINK__CLASSSTR, - FB_DEFAULT_SDK_ICON); - - -bool FMobuLiveLinkLayout::FBCreate() -{ - FBTrace("Creating UI\n"); - - // Get a handle on the device. - LiveLinkDevice = (FMobuLiveLink*)(FBDevice*)Device; - - FBPropertyPublish(this, ObjectSelection, "ObjectSelection", nullptr, nullptr); - ObjectSelection.SetFilter(FBModel::GetInternalClassId()); - ObjectSelection.SetSingleConnect(false); - - UICreate(); - UIConfigure(); - UIReset(); - - System.OnUIIdle.Add(this, (FBCallback)&FMobuLiveLinkLayout::EventUIIdle); - return true; -} - - -void FMobuLiveLinkLayout::FBDestroy() -{ - // Remove device & system callbacks - FBTrace("Destroying UI\n"); - - System.OnUIIdle.Remove(this, (FBCallback)&FMobuLiveLinkLayout::EventUIIdle); -} - -void FMobuLiveLinkLayout::UICreate() -{ - int S, W, H; // default spacing, width, height in pixels - - S = 4; - W = 110; - H = 18; - - const char MainLayoutName[] = "MainLayout"; - const char ObjectSelectorLabelName[] = "ObjectSelectorLabel"; - const char ObjectSelectorName[] = "ObjectSelector"; - const char AddToStreamButtonName[] = "AddToStreamButton"; - const char RemoveFromStreamButtonName[] = "RemoveFromStreamButton"; - const char StreamEditorCameraButtonName[] = "StreamEditorCameraButton"; - const char TimecodeModeListLabelName[] = "TimecodeModeListLabel"; - const char TimecodeModeListName[] = "TimecodeModeList"; - const char SampleRateLabelName[] = "SampleRateLabel"; - const char SampleRateListName[] = "SampleRateList"; - const char ProviderNameLabelName[] = "ProviderNameLabel"; - const char ProviderNameTextName[] = "ProviderNameText"; - const char ProviderNameEditButtonName[] = "ProviderNameEditButton"; - const char StreamSpreadName[] = "StreamSpread"; - - // Create regions - AddRegion(MainLayoutName, MainLayoutName, - S, kFBAttachLeft, nullptr, 1.00, - S, kFBAttachTop, nullptr, 1.00, - -S, kFBAttachRight, nullptr, 1.00, - -S, kFBAttachBottom, nullptr, 1.00); - - // Assign regions - SetControl(MainLayoutName, StreamLayout); - - // Add regions - { - StreamLayout.AddRegion(SampleRateLabelName, SampleRateLabelName, - S, kFBAttachLeft, nullptr, 1.00, - S, kFBAttachTop, nullptr, 1.00, - W * 0.65f, kFBAttachNone, nullptr, 1.00, - H, kFBAttachNone, nullptr, 1.00); - - StreamLayout.AddRegion(SampleRateListName, SampleRateListName, - 0, kFBAttachRight, SampleRateLabelName, 1.00, - 0, kFBAttachTop, SampleRateLabelName, 1.00, - W, kFBAttachNone, nullptr, 1.00, - H, kFBAttachNone, nullptr, 1.00); - - StreamLayout.AddRegion(TimecodeModeListLabelName, TimecodeModeListLabelName, - 10, kFBAttachRight, SampleRateListName, 1.00, - 0, kFBAttachTop, SampleRateListName, 1.00, - 50, kFBAttachNone, nullptr, 1.00, - H, kFBAttachNone, nullptr, 1.00); - - StreamLayout.AddRegion(TimecodeModeListName, TimecodeModeListName, - S, kFBAttachRight, TimecodeModeListLabelName, 1.00, - 0, kFBAttachTop, TimecodeModeListLabelName, 1.00, - 80, kFBAttachNone, nullptr, 1.00, - H, kFBAttachNone, nullptr, 1.00); - - StreamLayout.AddRegion(ProviderNameLabelName, ProviderNameLabelName, - S * 4.0f, kFBAttachRight, TimecodeModeListName, 1.00, - 0, kFBAttachTop, TimecodeModeListName, 1.00, - W * 0.75f, kFBAttachNone, nullptr, 1.00, - H, kFBAttachNone, nullptr, 1.00); - - StreamLayout.AddRegion(ProviderNameTextName, ProviderNameTextName, - 0, kFBAttachRight, ProviderNameLabelName, 1.00, - 0, kFBAttachTop, ProviderNameLabelName, 1.00, - W * 2.0f, kFBAttachNone, nullptr, 1.00, - H, kFBAttachNone, nullptr, 1.00); - - StreamLayout.AddRegion(ProviderNameEditButtonName, ProviderNameEditButtonName, - S, kFBAttachRight, ProviderNameTextName, 1.00, - 0, kFBAttachTop, ProviderNameTextName, 1.00, - W * 0.75f, kFBAttachNone, nullptr, 1.00, - H, kFBAttachNone, nullptr, 1.00); - } - - { - StreamLayout.AddRegion(ObjectSelectorLabelName, ObjectSelectorLabelName, - S, kFBAttachLeft, nullptr, 1.00, - S, kFBAttachBottom, SampleRateLabelName, 1.00, - W * 0.85f, kFBAttachNone, nullptr, 1.00, - H, kFBAttachNone, nullptr, 1.00); - - StreamLayout.AddRegion(ObjectSelectorName, ObjectSelectorName, - 0, kFBAttachRight, ObjectSelectorLabelName, 1.00, - 0, kFBAttachTop, ObjectSelectorLabelName, 1.00, - W * 2.0f, kFBAttachNone, nullptr, 1.00, - H, kFBAttachNone, nullptr, 1.00); - - StreamLayout.AddRegion(AddToStreamButtonName, AddToStreamButtonName, - S, kFBAttachRight, ObjectSelectorName, 1.00, - 0, kFBAttachTop, ObjectSelectorName, 1.00, - W * 0.75f, kFBAttachNone, nullptr, 1.00, - H, kFBAttachNone, nullptr, 1.00); - - StreamLayout.AddRegion(RemoveFromStreamButtonName, RemoveFromStreamButtonName, - S, kFBAttachRight, AddToStreamButtonName, 1.00, - 0, kFBAttachTop, AddToStreamButtonName, 1.00, - W * 0.75f, kFBAttachNone, nullptr, 1.00, - H, kFBAttachNone, nullptr, 1.00); - - StreamLayout.AddRegion(StreamEditorCameraButtonName, StreamEditorCameraButtonName, - S * 4.0f, kFBAttachRight, RemoveFromStreamButtonName, 1.00, - 0, kFBAttachTop, RemoveFromStreamButtonName, 1.00, - W * 1.35f, kFBAttachNone, nullptr, 1.00, - H, kFBAttachNone, nullptr, 1.00); - } - - { - StreamLayout.AddRegion(StreamSpreadName, StreamSpreadName, - S, kFBAttachLeft, nullptr, 1.00, - S, kFBAttachBottom, ObjectSelectorLabelName, 1.00, - -S, kFBAttachRight, nullptr, 1.00, - -S, kFBAttachBottom, nullptr, 1.00); - } - - StreamLayout.SetControl(SampleRateLabelName, SampleRateListLabel); - StreamLayout.SetControl(SampleRateListName, SampleRateList); - StreamLayout.SetControl(TimecodeModeListLabelName, TimecodeModeListLabel); - StreamLayout.SetControl(TimecodeModeListName, TimecodeModeList); - StreamLayout.SetControl(ProviderNameLabelName, ProviderNameLabel); - StreamLayout.SetControl(ProviderNameTextName, ProviderNameText); - StreamLayout.SetControl(ProviderNameEditButtonName, ProviderNameEditButton); - - StreamLayout.SetControl(ObjectSelectorLabelName, ObjectSelectorLabel); - StreamLayout.SetControl(ObjectSelectorName, ObjectSelector); - StreamLayout.SetControl(AddToStreamButtonName, AddToStreamButton); - StreamLayout.SetControl(RemoveFromStreamButtonName, RemoveFromStreamButton); - StreamLayout.SetControl(StreamEditorCameraButtonName, StreamEditorCameraButton); - - StreamLayout.SetControl(StreamSpreadName, StreamSpread); -} - -void FMobuLiveLinkLayout::CreateSpreadColumns() -{ - int W = 100; - - StreamSpread.ColumnAdd("Subject Name", 0); - - StreamSpread.ColumnAdd("Stream Type", 1); - StreamSpread.GetColumn(1).Style = kFBCellStyleMenu; - StreamSpread.GetColumn(1).Width = W * 1.2f; - - StreamSpread.ColumnAdd("Active", 2); - StreamSpread.GetColumn(2).Style = kFBCellStyle2StatesButton; - StreamSpread.GetColumn(2).Width = W * 0.5f; - - StreamSpread.ColumnAdd("Stream Animatable", 3); - StreamSpread.GetColumn(3).Style = kFBCellStyle2StatesButton; - StreamSpread.GetColumn(3).Width = W; -} - -void FMobuLiveLinkLayout::UIConfigure() -{ - SetBorder("MainLayout", kFBStandardBorder, false, true, 1, 0, 90, 0); - - ObjectSelector.Property = &ObjectSelection; - - ObjectSelectorLabel.Caption = "Subject Selector:"; - - AddToStreamButton.Caption = "Add"; - AddToStreamButton.Justify = kFBTextJustifyCenter; - AddToStreamButton.OnClick.Add(this, (FBCallback)&FMobuLiveLinkLayout::EventAddToStream); - - RemoveFromStreamButton.Caption = "Remove"; - RemoveFromStreamButton.Justify = kFBTextJustifyCenter; - RemoveFromStreamButton.OnClick.Add(this, (FBCallback)&FMobuLiveLinkLayout::EventRemoveFromStream); - - StreamEditorCameraButton.Caption = "Stream Viewport Camera"; - StreamEditorCameraButton.Style = kFBCheckbox; - StreamEditorCameraButton.State = LiveLinkDevice->IsEditorCameraStreamed(); - StreamEditorCameraButton.OnClick.Add(this, (FBCallback)&FMobuLiveLinkLayout::EventStreamEditorCamera); - - { - TimecodeModeList.Items.Add("Local"); - TimecodeModeList.Items.Add("System"); - TimecodeModeList.Items.Add("Reference"); - TimecodeModeList.ItemIndex = LiveLinkDevice->GetTimecodeModeAsInt(); - TimecodeModeList.OnChange.Add(this, (FBCallback)&FMobuLiveLinkLayout::EventTimecodeModeChanged); - TimecodeModeListLabel.Caption = "Timecode:"; - } - - StreamSpread.Caption = "Object Root"; - StreamSpread.MultiSelect = true; - - CreateSpreadColumns(); - - StreamSpread.OnCellChange.Add(this, (FBCallback)&FMobuLiveLinkLayout::EventStreamSpreadCellChange); - - int CurrentSampleIndex = 0; - for (int SampleOptionIdx=0; SampleOptionIdx < LiveLinkDevice->SampleOptions.Num(); ++SampleOptionIdx) - { - const TPair& SampleOption = LiveLinkDevice->SampleOptions[SampleOptionIdx]; - SampleRateList.Items.Add(FStringToChar(SampleOption.Key)); - if (SampleOption.Value == LiveLinkDevice->CurrentSampleRate) - { - CurrentSampleIndex = SampleOptionIdx; - } - } - SampleRateList.ItemIndex = CurrentSampleIndex; - - SampleRateList.OnChange.Add(this, (FBCallback)&FMobuLiveLinkLayout::EventSampleRateChange); - - SampleRateListLabel.Caption = "Sample Rate:"; - ProviderNameLabel.Caption = "Provider Name:"; - - ProviderNameText.Text = FStringToChar(LiveLinkDevice->GetProviderName()); - ProviderNameText.ReadOnly = true; - - ProviderNameEditButton.Caption = "Change"; - ProviderNameEditButton.OnClick.Add(this, (FBCallback)&FMobuLiveLinkLayout::EventEditProviderNamePopup); -} - -void FMobuLiveLinkLayout::UIReset() -{ - FBTrace("UI Reset!\n"); - StreamSpread.Clear(); - CreateSpreadColumns(); - for (const TPair& MapPair : LiveLinkDevice->StreamObjects) - { - AddSpreadRowFromStreamObject(MapPair.Key, MapPair.Value); - } - LiveLinkDevice->SetRefreshUI(false); -} - -void FMobuLiveLinkLayout::EventUIIdle(HISender Sender, HKEvent Event) -{ - if (LiveLinkDevice->IsDirty()) - { - LiveLinkDevice->UpdateStreamObjects(); - } - if (LiveLinkDevice->ShouldRefreshUI()) - { - UIReset(); - } -} - -void FMobuLiveLinkLayout::AddSpreadRowFromStreamObject(int32 NewRowKey, StreamObjectPtr Object) -{ - // Check whether the Object should be shown - if (!Object->ShouldShowInUI()) return; - - const FString RootName = Object->GetRootName(); - - StreamSpread.RowAdd(FStringToChar(RootName), NewRowKey); - - StreamSpread.SetCell(NewRowKey, 0, FStringToChar(Object->GetSubjectName().ToString())); - StreamSpread.SetCell(NewRowKey, 1, FStringToChar(Object->GetStreamOptions())); - StreamSpread.SetCell(NewRowKey, 1, Object->GetStreamingMode()); - StreamSpread.SetCell(NewRowKey, 2, Object->GetActiveStatus()); - StreamSpread.SetCell(NewRowKey, 3, Object->GetSendAnimatableStatus()); -} - - -bool IsModelInDeviceStream(const FMobuLiveLink* MobuDevice, const FBModel* Model) -{ - for (const TPair>& StreamPair : MobuDevice->StreamObjects) - { - if (StreamPair.Value->GetModelPointer() == Model) - { - return true; - } - } - return false; -} - -void FMobuLiveLinkLayout::EventAddToStream(HISender Sender, HKEvent Event) -{ - TArray ParentsToIgnore; - ParentsToIgnore.Reserve(ObjectSelection.GetCount()); - - FBModel* SceneRoot = FBSystem().Scene->RootModel; - for (int CharIndex = 0; CharIndex < ObjectSelection.GetCount(); ++CharIndex) - { - FBModel* Model = (FBModel*)ObjectSelection.GetAt(CharIndex); - - // Ignore the SceneRoot - if (Model == SceneRoot) - { - continue; - } - - // Only grab root items - if (ParentsToIgnore.Contains(Model->Parent)) - { - ParentsToIgnore.Emplace(Model); - } - else if (!IsModelInDeviceStream(LiveLinkDevice, Model)) - { - StreamObjectPtr StoreObject = StreamObjectManagement::FBModelToStreamObject(Model); - int32 NewUID = LiveLinkDevice->GetNextUID(); - - LiveLinkDevice->AddStreamObject(NewUID, StoreObject); - AddSpreadRowFromStreamObject(NewUID, StoreObject); - - ParentsToIgnore.Emplace(Model); - } - } - ObjectSelection.Clear(); -} - -void FMobuLiveLinkLayout::EventRemoveFromStream(HISender Sender, HKEvent Event) -{ - int SelectedCount = 0; - for (const TPair& MapPair : LiveLinkDevice->StreamObjects) - { - int32 RowKey = MapPair.Key; - bool bRowSelected = StreamSpread.GetRow(RowKey).RowSelected; - if (bRowSelected) - { - LiveLinkDevice->RemoveStreamObject(RowKey, MapPair.Value); - SelectedCount++; - } - } - if (SelectedCount > 0) - { - UIReset(); - } - FBTrace("Removed %d items in selection!\n", SelectedCount); -} - -void FMobuLiveLinkLayout::EventStreamEditorCamera(HISender Sender, HKEvent Event) -{ - LiveLinkDevice->SetEditorCameraStreamed((bool)StreamEditorCameraButton.State); -} - -void FMobuLiveLinkLayout::EventStreamSpreadCellChange(HISender Sender, HKEvent Event) -{ - FBEventSpread SpreadEvent = Event; - - StreamObjectPtr* ObjectPtr = LiveLinkDevice->StreamObjects.Find(SpreadEvent.Row); - if (ObjectPtr == nullptr && ObjectPtr->IsValid()) - { - FBTrace("No object exists for this Row!"); - return; - } - switch (SpreadEvent.Column) - { - case 0: // Subject Name - { - const char* NewSubjectName; - StreamSpread.GetCell(SpreadEvent.Row, SpreadEvent.Column, NewSubjectName); - LiveLinkDevice->ChangeSubjectName(*ObjectPtr, NewSubjectName); - break; - } - case 1: // Stream Type - { - int RowIndex; - StreamSpread.GetCell(SpreadEvent.Row, SpreadEvent.Column, RowIndex); - (*ObjectPtr)->UpdateStreamingMode(RowIndex); - break; - } - case 2: // Stream Status - { - int bIsActive; - StreamSpread.GetCell(SpreadEvent.Row, SpreadEvent.Column, bIsActive); - (*ObjectPtr)->UpdateActiveStatus(bIsActive > 0); - break; - } - case 3: // Stream Animatable - { - int bIsAnimatable; - StreamSpread.GetCell(SpreadEvent.Row, SpreadEvent.Column, bIsAnimatable); - (*ObjectPtr)->UpdateSendAnimatableStatus(bIsAnimatable > 0); - break; - } - default: - break; - } - - LiveLinkDevice->SetDirty(true); -} - -void FMobuLiveLinkLayout::EventTimecodeModeChanged(HISender Sender, HKEvent Event) -{ - LiveLinkDevice->SetTimecodeModeFromInt(TimecodeModeList.ItemIndex); -} - -void FMobuLiveLinkLayout::EventSampleRateChange(HISender Sender, HKEvent Event) -{ - const FFrameRate& NewSampleRate = LiveLinkDevice->SampleOptions[SampleRateList.ItemIndex].Value; - if (NewSampleRate != LiveLinkDevice->CurrentSampleRate) - { - LiveLinkDevice->CurrentSampleRate = NewSampleRate; - LiveLinkDevice->UpdateSampleRate(); - } -} - -void FMobuLiveLinkLayout::EventEditProviderNamePopup(HISender Sender, HKEvent Event) -{ - char NewNameString[1024]; - memset(NewNameString, 0, sizeof(NewNameString)); - strncpy_s(NewNameString, sizeof(NewNameString) - 1, ProviderNameText.Text, sizeof(ProviderNameText.Text)); - - // This is scary with no buffer overrun safety on the Mobu SDK side - int ButtonClicked = FBMessageBoxGetUserValue("Change Provider Name", "Enter a new Live Link Provider name", NewNameString, kFBPopupString, "Accept", "Cancel"); - - if ((ButtonClicked == 1) && (strlen(NewNameString) > 0) && (LiveLinkDevice->GetProviderName() != CharToFString(NewNameString))) - { - ProviderNameText.Text = NewNameString; - LiveLinkDevice->SetProviderName(CharToFString(NewNameString)); - } -} +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "MobuLiveLinkLayout.h" +#include "MobuLiveLinkStreamObjects.h" +#include "MobuLiveLinkUtilities.h" +#include +#include + +#define MOBULIVELINK__LAYOUT FMobuLiveLinkLayout + +FBDeviceLayoutImplementation(MOBULIVELINK__LAYOUT); +FBRegisterDeviceLayout(MOBULIVELINK__LAYOUT, + MOBULIVELINK__CLASSSTR, + FB_DEFAULT_SDK_ICON); + +const char MainLayoutName[] = "MainLayout"; + +// Removes all characters (in place) that are neither punctuation nor alphanumeric +void StripWhitespace(char* InStr) +{ + const size_t Length = strlen(InStr); + if (Length == 0) + { + return; + } + + size_t pos = 0; + for (size_t i=0; i Allow any amount of leading whitespace + // ([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5]) -> Match single ip block, min 0, max 255 + // \\. -> Match dot + // {3} -> Match 3 ip blocks with trailing dots + // ([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5]) -> Match last ip block w/o trailing dot + // \\s*\\:\\s* -> Match colon with any amount of surrounding whitespace + // \\d{1,5} -> Match any number between 1 and 5 digits for the port + // + // to verify/test: https://regex101.com/r/wdePf9/1 + std::regex IpRegex("^\\s*((([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\\.){3}([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5]))\\s*\\:\\s*(\\d{1,5})$"); + return std::regex_match(InIpAddress, IpRegex); +} + +bool FMobuLiveLinkLayout::FBCreate() +{ + FBTrace("Creating UI\n"); + + // Get a handle on the device. + LiveLinkDevice = (FMobuLiveLink*)(FBDevice*)Device; + + FBPropertyPublish(this, ObjectSelection, "ObjectSelection", nullptr, nullptr); + ObjectSelection.SetFilter(FBModel::GetInternalClassId()); + ObjectSelection.SetSingleConnect(false); + + UICreate(); + UIConfigure(); + UIReset(); + + System.OnUIIdle.Add(this, (FBCallback)&FMobuLiveLinkLayout::EventUIIdle); + return true; +} + + +void FMobuLiveLinkLayout::FBDestroy() +{ + // Remove device & system callbacks + FBTrace("Destroying UI\n"); + + System.OnUIIdle.Remove(this, (FBCallback)&FMobuLiveLinkLayout::EventUIIdle); +} + +void FMobuLiveLinkLayout::UICreate() +{ + // default spacing, width, height in pixels + const int S = 4; + const int W = 110; + const int H = 18; + + const char* TabLayoutName = "TabPanel"; + + AddRegion(TabLayoutName, TabLayoutName, + S, kFBAttachLeft, "", 1.0, + S, kFBAttachTop, "", 1.0, + -S, kFBAttachRight, "", 1.0, + 25, kFBAttachNone, NULL, 1.0); + + // Create regions + AddRegion(MainLayoutName, MainLayoutName, + 0, kFBAttachLeft, TabLayoutName, 1.00, + 0, kFBAttachBottom, TabLayoutName, 1.00, + 0, kFBAttachRight, TabLayoutName, 1.00, + -S, kFBAttachBottom, nullptr, 1.00); + + // Assign regions + SetControl(TabLayoutName, TabPanel); + SetControl(MainLayoutName, Layouts[0]); + + UICreateLayout0(); + UICreateLayout1(); +} + +void FMobuLiveLinkLayout::UICreateLayout0() +{ + const int S = 4; + const int W = 110; + const int H = 18; + + const char ObjectSelectorLabelName[] = "ObjectSelectorLabel"; + const char ObjectSelectorName[] = "ObjectSelector"; + const char AddToStreamButtonName[] = "AddToStreamButton"; + const char RemoveFromStreamButtonName[] = "RemoveFromStreamButton"; + const char StreamEditorCameraButtonName[] = "StreamEditorCameraButton"; + const char StreamSpreadName[] = "StreamSpread"; + + { + Layouts[0].AddRegion(ObjectSelectorLabelName, ObjectSelectorLabelName, + S, kFBAttachLeft, nullptr, 1.00, + S, kFBAttachTop, nullptr, 1.00, + W * 0.85f, kFBAttachNone, nullptr, 1.00, + H, kFBAttachNone, nullptr, 1.00); + + Layouts[0].AddRegion(ObjectSelectorName, ObjectSelectorName, + 0, kFBAttachRight, ObjectSelectorLabelName, 1.00, + 0, kFBAttachTop, ObjectSelectorLabelName, 1.00, + W * 2.0f, kFBAttachNone, nullptr, 1.00, + H, kFBAttachNone, nullptr, 1.00); + + Layouts[0].AddRegion(AddToStreamButtonName, AddToStreamButtonName, + S, kFBAttachRight, ObjectSelectorName, 1.00, + 0, kFBAttachTop, ObjectSelectorName, 1.00, + W * 0.75f, kFBAttachNone, nullptr, 1.00, + H, kFBAttachNone, nullptr, 1.00); + + Layouts[0].AddRegion(RemoveFromStreamButtonName, RemoveFromStreamButtonName, + S, kFBAttachRight, AddToStreamButtonName, 1.00, + 0, kFBAttachTop, AddToStreamButtonName, 1.00, + W * 0.75f, kFBAttachNone, nullptr, 1.00, + H, kFBAttachNone, nullptr, 1.00); + + Layouts[0].AddRegion(StreamEditorCameraButtonName, StreamEditorCameraButtonName, + S * 4.0f, kFBAttachRight, RemoveFromStreamButtonName, 1.00, + 0, kFBAttachTop, RemoveFromStreamButtonName, 1.00, + W * 1.35f, kFBAttachNone, nullptr, 1.00, + H, kFBAttachNone, nullptr, 1.00); + } + + { + Layouts[0].AddRegion(StreamSpreadName, StreamSpreadName, + S, kFBAttachLeft, nullptr, 1.00, + S, kFBAttachBottom, ObjectSelectorLabelName, 1.00, + -S, kFBAttachRight, nullptr, 1.00, + -S, kFBAttachBottom, nullptr, 1.00); + } + + Layouts[0].SetControl(ObjectSelectorLabelName, ObjectSelectorLabel); + Layouts[0].SetControl(ObjectSelectorName, ObjectSelector); + Layouts[0].SetControl(AddToStreamButtonName, AddToStreamButton); + Layouts[0].SetControl(RemoveFromStreamButtonName, RemoveFromStreamButton); + Layouts[0].SetControl(StreamEditorCameraButtonName, StreamEditorCameraButton); + + Layouts[0].SetControl(StreamSpreadName, StreamSpread); +} + +void FMobuLiveLinkLayout::UICreateLayout1() +{ + const int S = 8; + const int W = 110; + const int H = 24; + + const char SampleRateLabelName[] = "SampleRateLabel"; + const char SampleRateListName[] = "SampleRateList"; + const char ProviderNameLabelName[] = "ProviderNameLabel"; + const char ProviderNameTextName[] = "ProviderNameText"; + const char ProviderNameEditButtonName[] = "ProviderNameEditButton"; + const char TimecodeModeListLabelName[] = "TimecodeModeListLabel"; + const char TimecodeModeListName[] = "TimecodeModeList"; + const char UnicastEndpointLabelName[] = "UnicastEndpointLabel"; + const char UnicastEndpointAddressName[] = "UnicastEndpointAddress"; + const char UnicastEndpointEditButtonName[] = "UnicastEndpointEditButton"; + const char StaticEndpointLabelName[] = "StaticEndpointLabel"; + const char StaticEndpointAddressName[] = "StaticEndpointAddress"; + const char StaticEndpointAddButtonName[] = "StaticEndpointAddButton"; + const char StaticEndpointRemoveButtonName[] = "StaticEndpointRemoveButton"; + + { + Layouts[1].AddRegion(SampleRateLabelName, SampleRateLabelName, + S, kFBAttachLeft, nullptr, 1.00, + S, kFBAttachTop, nullptr, 1.00, + W, kFBAttachNone, nullptr, 1.00, + H, kFBAttachNone, nullptr, 1.00); + + Layouts[1].AddRegion(SampleRateListName, SampleRateListName, + S, kFBAttachRight, SampleRateLabelName, 1.00, + 0, kFBAttachTop, SampleRateLabelName, 1.00, + W, kFBAttachNone, nullptr, 1.00, + H, kFBAttachNone, nullptr, 1.00); + } + { + Layouts[1].AddRegion(TimecodeModeListLabelName, TimecodeModeListLabelName, + S, kFBAttachLeft, nullptr, 1.00, + H, kFBAttachTop, SampleRateLabelName, 1.00, + W, kFBAttachNone, nullptr, 1.00, + H, kFBAttachNone, nullptr, 1.00); + + Layouts[1].AddRegion(TimecodeModeListName, TimecodeModeListName, + S, kFBAttachRight, TimecodeModeListLabelName, 1.00, + 0, kFBAttachTop, TimecodeModeListLabelName, 1.00, + W, kFBAttachNone, nullptr, 1.00, + H, kFBAttachNone, nullptr, 1.00); + } + { + Layouts[1].AddRegion(ProviderNameLabelName, ProviderNameLabelName, + S, kFBAttachLeft, nullptr, 1.00, + H, kFBAttachTop, TimecodeModeListLabelName, 1.00, + W, kFBAttachNone, nullptr, 1.00, + H, kFBAttachNone, nullptr, 1.00); + + Layouts[1].AddRegion(ProviderNameTextName, ProviderNameTextName, + S, kFBAttachRight, ProviderNameLabelName, 1.00, + 0, kFBAttachTop, ProviderNameLabelName, 1.00, + W, kFBAttachNone, nullptr, 1.00, + H, kFBAttachNone, nullptr, 1.00); + + Layouts[1].AddRegion(ProviderNameEditButtonName, ProviderNameEditButtonName, + S, kFBAttachRight, ProviderNameTextName, 1.00, + 0, kFBAttachTop, ProviderNameTextName, 1.00, + W, kFBAttachNone, nullptr, 1.00, + H, kFBAttachNone, nullptr, 1.00); + } + { + Layouts[1].AddRegion(UnicastEndpointLabelName, UnicastEndpointLabelName, + S, kFBAttachLeft, nullptr, 1.00, + H, kFBAttachTop, ProviderNameLabelName, 1.00, + W, kFBAttachNone, nullptr, 1.00, + H, kFBAttachNone, nullptr, 1.00); + + Layouts[1].AddRegion(UnicastEndpointAddressName, UnicastEndpointAddressName, + S, kFBAttachRight, UnicastEndpointLabelName, 1.00, + 0, kFBAttachTop, UnicastEndpointLabelName, 1.00, + W, kFBAttachNone, nullptr, 1.00, + H, kFBAttachNone, nullptr, 1.00); + + Layouts[1].AddRegion(UnicastEndpointEditButtonName, UnicastEndpointEditButtonName, + S, kFBAttachRight, UnicastEndpointAddressName, 1.00, + 0, kFBAttachTop, UnicastEndpointAddressName, 1.00, + W, kFBAttachNone, nullptr, 1.00, + H, kFBAttachNone, nullptr, 1.00); + } + { + Layouts[1].AddRegion(StaticEndpointLabelName, StaticEndpointLabelName, + S, kFBAttachLeft, nullptr, 1.00, + H, kFBAttachTop, UnicastEndpointLabelName, 1.00, + W, kFBAttachNone, nullptr, 1.00, + H, kFBAttachNone, nullptr, 1.00); + + Layouts[1].AddRegion(StaticEndpointAddressName, StaticEndpointAddressName, + S, kFBAttachRight, StaticEndpointLabelName, 1.00, + 0, kFBAttachTop, StaticEndpointLabelName, 1.00, + W, kFBAttachNone, nullptr, 1.00, + H * 3, kFBAttachNone, nullptr, 1.00); + + Layouts[1].AddRegion(StaticEndpointAddButtonName, StaticEndpointAddButtonName, + S, kFBAttachRight, StaticEndpointAddressName, 1.00, + 0, kFBAttachTop, StaticEndpointAddressName, 1.00, + W, kFBAttachNone, nullptr, 1.00, + H, kFBAttachNone, nullptr, 1.00); + + Layouts[1].AddRegion(StaticEndpointRemoveButtonName, StaticEndpointRemoveButtonName, + S, kFBAttachRight, StaticEndpointAddressName, 1.00, + H, kFBAttachTop, StaticEndpointAddButtonName, 1.00, + W, kFBAttachNone, nullptr, 1.00, + H, kFBAttachNone, nullptr, 1.00); + } + + Layouts[1].SetControl(SampleRateLabelName, SampleRateListLabel); + Layouts[1].SetControl(SampleRateListName, SampleRateList); + Layouts[1].SetControl(TimecodeModeListLabelName, TimecodeModeListLabel); + Layouts[1].SetControl(TimecodeModeListName, TimecodeModeList); + Layouts[1].SetControl(ProviderNameLabelName, ProviderNameLabel); + Layouts[1].SetControl(ProviderNameTextName, ProviderNameText); + Layouts[1].SetControl(ProviderNameEditButtonName, ProviderNameEditButton); + Layouts[1].SetControl(UnicastEndpointLabelName, UnicastEndpointLabel); + Layouts[1].SetControl(UnicastEndpointAddressName, UnicastEndpoint); + Layouts[1].SetControl(UnicastEndpointEditButtonName, UnicastEndpointEditButton); + Layouts[1].SetControl(StaticEndpointLabelName, StaticEndpointLabel); + Layouts[1].SetControl(StaticEndpointAddressName, StaticEndpoints); + Layouts[1].SetControl(StaticEndpointAddButtonName, StaticEndpointAddButton); + Layouts[1].SetControl(StaticEndpointRemoveButtonName, StaticEndpointRemoveButton); +} + +void FMobuLiveLinkLayout::CreateSpreadColumns() +{ + int W = 100; + + StreamSpread.ColumnAdd("Subject Name", 0); + + StreamSpread.ColumnAdd("Stream Type", 1); + StreamSpread.GetColumn(1).Style = kFBCellStyleMenu; + StreamSpread.GetColumn(1).Width = W * 1.2f; + + StreamSpread.ColumnAdd("Active", 2); + StreamSpread.GetColumn(2).Style = kFBCellStyle2StatesButton; + StreamSpread.GetColumn(2).Width = W * 0.5f; + + StreamSpread.ColumnAdd("Stream Animatable", 3); + StreamSpread.GetColumn(3).Style = kFBCellStyle2StatesButton; + StreamSpread.GetColumn(3).Width = W; +} + +void FMobuLiveLinkLayout::UIConfigure() +{ + TabPanel.Items.SetString("Stream~Settings"); + TabPanel.OnChange.Add(this, (FBCallback)&FMobuLiveLinkLayout::EventTabPanelChange); + + SetBorder("MainLayout", kFBStandardBorder, false, true, 1, 0, 90, 0); + + UIConfigureLayout0(); + UIConfigureLayout1(); +} + +void FMobuLiveLinkLayout::UIConfigureLayout0() +{ + ObjectSelector.Property = &ObjectSelection; + + ObjectSelectorLabel.Caption = "Subject Selector:"; + + AddToStreamButton.Caption = "Add"; + AddToStreamButton.Justify = kFBTextJustifyCenter; + AddToStreamButton.OnClick.Add(this, (FBCallback)&FMobuLiveLinkLayout::EventAddToStream); + + RemoveFromStreamButton.Caption = "Remove"; + RemoveFromStreamButton.Justify = kFBTextJustifyCenter; + RemoveFromStreamButton.OnClick.Add(this, (FBCallback)&FMobuLiveLinkLayout::EventRemoveFromStream); + + StreamEditorCameraButton.Caption = "Stream Viewport Camera"; + StreamEditorCameraButton.Style = kFBCheckbox; + StreamEditorCameraButton.State = LiveLinkDevice->IsEditorCameraStreamed(); + StreamEditorCameraButton.OnClick.Add(this, (FBCallback)&FMobuLiveLinkLayout::EventStreamEditorCamera); + + StreamSpread.Caption = "Object Root"; + StreamSpread.MultiSelect = true; + + CreateSpreadColumns(); + + StreamSpread.OnCellChange.Add(this, (FBCallback)&FMobuLiveLinkLayout::EventStreamSpreadCellChange); +} + +void FMobuLiveLinkLayout::UIConfigureLayout1() +{ + { + TimecodeModeList.Items.Add("Local"); + TimecodeModeList.Items.Add("System"); + TimecodeModeList.Items.Add("Reference"); + TimecodeModeList.ItemIndex = LiveLinkDevice->GetTimecodeModeAsInt(); + TimecodeModeList.OnChange.Add(this, (FBCallback)&FMobuLiveLinkLayout::EventTimecodeModeChanged); + TimecodeModeListLabel.Caption = "Timecode:"; + } + + int CurrentSampleIndex = 0; + for (int SampleOptionIdx = 0; SampleOptionIdx < LiveLinkDevice->SampleOptions.Num(); ++SampleOptionIdx) + { + const TPair& SampleOption = LiveLinkDevice->SampleOptions[SampleOptionIdx]; + SampleRateList.Items.Add(FStringToChar(SampleOption.Key)); + if (SampleOption.Value == LiveLinkDevice->CurrentSampleRate) + { + CurrentSampleIndex = SampleOptionIdx; + } + } + SampleRateList.ItemIndex = CurrentSampleIndex; + + SampleRateList.OnChange.Add(this, (FBCallback)&FMobuLiveLinkLayout::EventSampleRateChange); + + SampleRateListLabel.Caption = "Sample Rate:"; + ProviderNameLabel.Caption = "Provider Name:"; + + ProviderNameText.Text = FStringToChar(LiveLinkDevice->GetProviderName()); + ProviderNameText.ReadOnly = true; + + ProviderNameEditButton.Caption = "Change"; + ProviderNameEditButton.OnClick.Add(this, (FBCallback)&FMobuLiveLinkLayout::EventEditProviderNamePopup); + + UnicastEndpointLabel.Caption = "Unicast Endpoint:"; + UnicastEndpoint.Text = FStringToChar(LiveLinkDevice->GetUnicastEndpoint()); + UnicastEndpoint.ReadOnly = true; + UnicastEndpointEditButton.Caption = "Change"; + UnicastEndpointEditButton.OnClick.Add(this, (FBCallback)&FMobuLiveLinkLayout::EventChangeUnicastEndpoint); + + StaticEndpointLabel.Caption = "Static Endpoints:"; + StaticEndpoints.Style = kFBVerticalList; + + StaticEndpointAddButton.Caption = "Add"; + StaticEndpointAddButton.OnClick.Add(this, (FBCallback)&FMobuLiveLinkLayout::EventAddStaticEndpoint); + + StaticEndpointRemoveButton.Caption = "Remove"; + StaticEndpointRemoveButton.OnClick.Add(this, (FBCallback)&FMobuLiveLinkLayout::EventRemoveStaticEndpoint); +} + +void FMobuLiveLinkLayout::UIReset() +{ + FBTrace("UI Reset!\n"); + StreamSpread.Clear(); + CreateSpreadColumns(); + for (const TPair& MapPair : LiveLinkDevice->StreamObjects) + { + AddSpreadRowFromStreamObject(MapPair.Key, MapPair.Value); + } + + UnicastEndpoint.Text = FStringToChar(LiveLinkDevice->GetUnicastEndpoint()); + StaticEndpoints.Items.Clear(); + const TArray& Endpoints = LiveLinkDevice->GetStaticEndpoints(); + for (const FString& Endpoint : Endpoints) + { + StaticEndpoints.Items.Add(FStringToChar(Endpoint)); + } + + LiveLinkDevice->SetRefreshUI(false); +} + +void FMobuLiveLinkLayout::EventUIIdle(HISender Sender, HKEvent Event) +{ + if (LiveLinkDevice->IsDirty()) + { + LiveLinkDevice->UpdateStreamObjects(); + } + if (LiveLinkDevice->ShouldRefreshUI()) + { + UIReset(); + } +} + +void FMobuLiveLinkLayout::AddSpreadRowFromStreamObject(int32 NewRowKey, StreamObjectPtr Object) +{ + // Check whether the Object should be shown + if (!Object->ShouldShowInUI()) return; + + const FString RootName = Object->GetRootName(); + + StreamSpread.RowAdd(FStringToChar(RootName), NewRowKey); + + StreamSpread.SetCell(NewRowKey, 0, FStringToChar(Object->GetSubjectName().ToString())); + StreamSpread.SetCell(NewRowKey, 1, FStringToChar(Object->GetStreamOptions())); + StreamSpread.SetCell(NewRowKey, 1, Object->GetStreamingMode()); + StreamSpread.SetCell(NewRowKey, 2, Object->GetActiveStatus()); + StreamSpread.SetCell(NewRowKey, 3, Object->GetSendAnimatableStatus()); +} + + +bool IsModelInDeviceStream(const FMobuLiveLink* MobuDevice, const FBModel* Model) +{ + for (const TPair>& StreamPair : MobuDevice->StreamObjects) + { + if (StreamPair.Value->GetModelPointer() == Model) + { + return true; + } + } + return false; +} + +void FMobuLiveLinkLayout::EventAddToStream(HISender Sender, HKEvent Event) +{ + TArray ParentsToIgnore; + ParentsToIgnore.Reserve(ObjectSelection.GetCount()); + + FBModel* SceneRoot = FBSystem().Scene->RootModel; + for (int CharIndex = 0; CharIndex < ObjectSelection.GetCount(); ++CharIndex) + { + FBModel* Model = (FBModel*)ObjectSelection.GetAt(CharIndex); + + // Ignore the SceneRoot + if (Model == SceneRoot) + { + continue; + } + + // Only grab root items + if (ParentsToIgnore.Contains(Model->Parent)) + { + ParentsToIgnore.Emplace(Model); + } + else if (!IsModelInDeviceStream(LiveLinkDevice, Model)) + { + StreamObjectPtr StoreObject = StreamObjectManagement::FBModelToStreamObject(Model); + int32 NewUID = LiveLinkDevice->GetNextUID(); + + LiveLinkDevice->AddStreamObject(NewUID, StoreObject); + AddSpreadRowFromStreamObject(NewUID, StoreObject); + + ParentsToIgnore.Emplace(Model); + } + } + ObjectSelection.Clear(); +} + +void FMobuLiveLinkLayout::EventRemoveFromStream(HISender Sender, HKEvent Event) +{ + int SelectedCount = 0; + + decltype(LiveLinkDevice->StreamObjects) StreamObjectsToRemove; + + for (const TPair& MapPair : LiveLinkDevice->StreamObjects) + { + int32 RowKey = MapPair.Key; + bool bRowSelected = StreamSpread.GetRow(RowKey).RowSelected; + if (bRowSelected) + { + StreamObjectsToRemove.Add(MapPair); + SelectedCount++; + } + } + + for (const auto& MapPair : StreamObjectsToRemove) + { + LiveLinkDevice->RemoveStreamObject(MapPair.Key, MapPair.Value); + } + + if (SelectedCount > 0) + { + UIReset(); + } + + FBTrace("Removed %d items in selection!\n", SelectedCount); +} + +void FMobuLiveLinkLayout::EventStreamEditorCamera(HISender Sender, HKEvent Event) +{ + LiveLinkDevice->SetEditorCameraStreamed((bool)StreamEditorCameraButton.State); +} + +void FMobuLiveLinkLayout::EventStreamSpreadCellChange(HISender Sender, HKEvent Event) +{ + FBEventSpread SpreadEvent = Event; + + StreamObjectPtr* ObjectPtr = LiveLinkDevice->StreamObjects.Find(SpreadEvent.Row); + if (ObjectPtr == nullptr && ObjectPtr->IsValid()) + { + FBTrace("No object exists for this Row!"); + return; + } + switch (SpreadEvent.Column) + { + case 0: // Subject Name + { + const char* NewSubjectName; + StreamSpread.GetCell(SpreadEvent.Row, SpreadEvent.Column, NewSubjectName); + LiveLinkDevice->ChangeSubjectName(*ObjectPtr, NewSubjectName); + break; + } + case 1: // Stream Type + { + int RowIndex; + StreamSpread.GetCell(SpreadEvent.Row, SpreadEvent.Column, RowIndex); + (*ObjectPtr)->UpdateStreamingMode(RowIndex); + break; + } + case 2: // Stream Status + { + int bIsActive; + StreamSpread.GetCell(SpreadEvent.Row, SpreadEvent.Column, bIsActive); + (*ObjectPtr)->UpdateActiveStatus(bIsActive > 0); + break; + } + case 3: // Stream Animatable + { + int bIsAnimatable; + StreamSpread.GetCell(SpreadEvent.Row, SpreadEvent.Column, bIsAnimatable); + (*ObjectPtr)->UpdateSendAnimatableStatus(bIsAnimatable > 0); + break; + } + default: + break; + } + + LiveLinkDevice->SetDirty(true); +} + +void FMobuLiveLinkLayout::EventTabPanelChange(HISender pSender, HKEvent pEvent) +{ + switch (TabPanel.ItemIndex) + { + case 0: + SetControl("MainLayout", Layouts[0]); + break; + case 1: + SetControl("MainLayout", Layouts[1]); + break; + } +} + +void FMobuLiveLinkLayout::EventTimecodeModeChanged(HISender Sender, HKEvent Event) +{ + LiveLinkDevice->SetTimecodeModeFromInt(TimecodeModeList.ItemIndex); +} + +void FMobuLiveLinkLayout::EventSampleRateChange(HISender Sender, HKEvent Event) +{ + const FFrameRate& NewSampleRate = LiveLinkDevice->SampleOptions[SampleRateList.ItemIndex].Value; + if (NewSampleRate != LiveLinkDevice->CurrentSampleRate) + { + LiveLinkDevice->CurrentSampleRate = NewSampleRate; + LiveLinkDevice->UpdateSampleRate(); + } +} + +void FMobuLiveLinkLayout::EventEditProviderNamePopup(HISender Sender, HKEvent Event) +{ + char NewNameString[1024]; + memset(NewNameString, 0, sizeof(NewNameString)); + strncpy_s(NewNameString, sizeof(NewNameString) - 1, ProviderNameText.Text, sizeof(ProviderNameText.Text)); + + // This is scary with no buffer overrun safety on the Mobu SDK side + int ButtonClicked = FBMessageBoxGetUserValue("Change Provider Name", "Enter a new Live Link Provider name", NewNameString, kFBPopupString, "Accept", "Cancel"); + + if ((ButtonClicked == 1) && (strlen(NewNameString) > 0) && (LiveLinkDevice->GetProviderName() != CharToFString(NewNameString))) + { + ProviderNameText.Text = NewNameString; + LiveLinkDevice->SetProviderName(CharToFString(NewNameString)); + } +} + +void FMobuLiveLinkLayout::EventChangeUnicastEndpoint(HISender Sender, HKEvent Event) +{ + char NewUnicastString[1024]; + memset(NewUnicastString, 0, sizeof(NewUnicastString)); + strncpy_s(NewUnicastString, sizeof(NewUnicastString) - 1, UnicastEndpoint.Text, sizeof(UnicastEndpoint.Text)); + + const std::string Description("Enter a new Unicast Endpoint address to select which nic to use."); + const std::string FormatHint("\n\nThe entered address was not formatted correctly. The format must match this pattern:\nIpAddress:Port"); + int ButtonClicked = 0; + bool bTryAgain = false; + + do + { + std::string Message(Description); + if (bTryAgain) + { + Message += FormatHint; + } + + // This is scary with no buffer overrun safety on the Mobu SDK side + ButtonClicked = FBMessageBoxGetUserValue("Change Unicast Endpoint", Message.c_str(), NewUnicastString, kFBPopupString, "Accept", "Cancel"); + StripWhitespace(NewUnicastString); + bTryAgain = (ButtonClicked == 1) && !IsValidIpAddressWithPort(NewUnicastString); + } while (bTryAgain); + + if ((ButtonClicked == 1) && (strlen(NewUnicastString) > 0) && (LiveLinkDevice->GetUnicastEndpoint() != CharToFString(NewUnicastString))) + { + UnicastEndpoint.Text = NewUnicastString; + LiveLinkDevice->SetUnicastEndpoint(CharToFString(NewUnicastString)); + } +} + +void FMobuLiveLinkLayout::EventAddStaticEndpoint(HISender Sender, HKEvent Event) +{ + char NewAddressString[1024] = "0.0.0.0:6666"; + + const std::string Description("Enter a new Static Endpoint Address of the PC running UE4."); + const std::string FormatHint("\n\nThe entered address was not formatted correctly. The format must match this pattern:\nIpAddress:Port"); + int ButtonClicked = 0; + bool bTryAgain = false; + + do + { + std::string Message(Description); + if (bTryAgain) + { + Message += FormatHint; + } + // This is scary with no buffer overrun safety on the Mobu SDK side + ButtonClicked = FBMessageBoxGetUserValue("Add StaticEndpoint", Message.c_str(), NewAddressString, kFBPopupString, "Accept", "Cancel"); + StripWhitespace(NewAddressString); + bTryAgain = (ButtonClicked == 1) && !IsValidIpAddressWithPort(NewAddressString); + } while (bTryAgain); + + if ((ButtonClicked == 1) && (strlen(NewAddressString) > 0)) + { + const int ExistingIndex = StaticEndpoints.Items.IndexOf(NewAddressString); + if (ExistingIndex == -1) // avoid duplicates + { + StaticEndpoints.Items.Add(NewAddressString); + if (!LiveLinkDevice->AddStaticEndpoint(CharToFString(NewAddressString))) + { + FBMessageBox("Error", "Could not add Static Endpoint!", "OK"); + } + } + } +} + +void FMobuLiveLinkLayout::EventRemoveStaticEndpoint(HISender Sender, HKEvent Event) +{ + if (StaticEndpoints.ItemIndex == -1) + { + return; + } + + if (!LiveLinkDevice->RemoveStaticEndpoint(StaticEndpoints.Items[StaticEndpoints.ItemIndex])) + { + FBMessageBox("Error", "Could not remove Static Endpoint!", "OK"); + } + else + { + StaticEndpoints.Items.RemoveAt(StaticEndpoints.ItemIndex); + } +} diff --git a/Source/Public/MobuLiveLinkDevice.h b/Source/Public/MobuLiveLinkDevice.h index 73837aad..b5f01305 100644 --- a/Source/Public/MobuLiveLinkDevice.h +++ b/Source/Public/MobuLiveLinkDevice.h @@ -1,128 +1,139 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#pragma once - -#include "MobuLiveLinkCommon.h" -#include "MobuLiveLinkUtilities.h" -#include "IStreamObject.h" -#include "Misc/CommandLine.h" -#include "Async/TaskGraphInterfaces.h" -#include "Modules/ModuleManager.h" -#include "UObject/Object.h" -#include "Misc/ConfigCacheIni.h" -#include "Misc/OutputDevice.h" - -//--- Registration defines -#define MOBULIVELINK__CLASSNAME FMobuLiveLink -#define MOBULIVELINK__CLASSSTR "MobuLiveLink" - -#define IntToChar(input) std::to_string(input).c_str() -#define FStringToChar(input) ((std::string)TCHAR_TO_UTF8(*input)).c_str() -#define CharToFString(input) UTF8_TO_TCHAR(input) - -//! Simple input device. -class FMobuLiveLink : public FBDevice -{ - //--- FiLMBOX declaration - FBDeviceDeclare(FMobuLiveLink, FBDevice); -public: - void StartLiveLink(); - void StopLiveLink(); - - //--- FiLMBOX Construction/Destruction - bool FBCreate() override; //!< FiLMBOX constructor. - void FBDestroy() override; //!< FiLMBOX destructor. - - //--- Initialisation/Shutdown - bool Init(); //!< Initialize/create device. - bool Done(); //!< Remove device. - bool Reset(); //!< Reset device. - bool Stop(); //!< Stop device (offline). - bool Start(); //!< Start device (online). - - //--- The following will be called by the real-time engine. - void DeviceIONotify(kDeviceIOs Action, FBDeviceNotifyInfo &DeviceNotifyInfo) override; //!< Notification of/for Device IO. - bool DeviceEvaluationNotify(kTransportMode Mode, FBEvaluateInfo* EvaluateInfo) override; //!< Evaluation the device (write to hardware). - bool DeviceOperation(kDeviceOperations Operation) override; //!< Operate device. - - //--- FBX Load/Save. - bool FbxStore(FBFbxObject* FbxObject, kFbxObjectStore StoreWhat) override; //!< Store in FBX file. - bool FbxRetrieve(FBFbxObject* FbxObject, kFbxObjectStore StoreWhat) override; //!< Retrieve from FBX file. - - //--- Events - void EventSceneChange(HISender Sender, HKEvent Event); - void EventRenderUpdate(HISender Sender, HKEvent Event); - -private: - typedef TSharedPtr StreamObjectPtr; - -public: - void AddStreamObject(int32 NewUID, StreamObjectPtr NewObject); - void RemoveStreamObject(int32 DeletionKey, StreamObjectPtr RemoveObject); - void ChangeSubjectName(StreamObjectPtr ObjectPtr, const char* NewSubjectNameStr); - void UpdateStreamObjects(); - - void SetDirty(bool bNewDirty) { bIsDirty = bNewDirty; }; - bool IsDirty() const { return bIsDirty; }; - - void SetRefreshUI(bool bNewRefreshUI) { bShouldRefreshUI = bNewRefreshUI; }; - bool ShouldRefreshUI() const { return bShouldRefreshUI; }; - - const TArray> SampleOptions = - { - TPair(FString("30hz"), FFrameRate(30, 1)), - TPair(FString("50hz"), FFrameRate(50, 1)), - TPair(FString("60hz"), FFrameRate(60, 1)), - TPair(FString("100hz"), FFrameRate(100, 1)), - TPair(FString("120hz"), FFrameRate(120, 1)), - TPair(FString("Before Render"), FFrameRate(-1, 1)), - }; - - FFrameRate CurrentSampleRate; - void UpdateSampleRate(); - - int32 GetNextUID(); - - bool IsEditorCameraStreamed() const; - void SetEditorCameraStreamed(bool bStream); - - ETimecodeMode GetTimecodeMode() const; - int32 GetTimecodeModeAsInt() const; - void SetTimecodeMode(ETimecodeMode InTimecodeMode); - void SetTimecodeModeFromInt(int32 InTimecodeModeInt); - - const FString& GetProviderName() const { return CurrentProviderName; } - void SetProviderName(const FString& NewValue); - -public: - TMap> StreamObjects; - TSharedPtr LiveLinkProvider; - -private: - TWeakPtr EditorCameraObject; - - FString CurrentProviderName = "Mobu Live Link"; - - int32 NextUID = 1; - - void UpdateStream(); //!< Get latest data and send to unreal - - int32 GetCurrentSampleRateIndex(); - - bool bIsDirty; - bool bShouldRefreshUI; - - bool bShouldUpdateInRenderCallback; //!< Whether to update after render or to update in device evaluation - - FBDeviceSamplingMode SamplingType; - FBFastLock mCleanUpLock; - - TMap SceneChangeNameMap; - - double LastEvaluationTime; - ETimecodeMode TimecodeMode; - - void SetDeviceInformation(const char* NewDeviceInformation); - void TickCoreTicker(); -}; - +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "MobuLiveLinkCommon.h" +#include "MobuLiveLinkUtilities.h" +#include "IStreamObject.h" +#include "Misc/CommandLine.h" +#include "Async/TaskGraphInterfaces.h" +#include "Modules/ModuleManager.h" +#include "UObject/Object.h" +#include "Misc/ConfigCacheIni.h" +#include "Misc/OutputDevice.h" + +//--- Registration defines +#define MOBULIVELINK__CLASSNAME FMobuLiveLink +#define MOBULIVELINK__CLASSSTR "MobuLiveLink" + +#define IntToChar(input) std::to_string(input).c_str() +#define FStringToChar(input) ((std::string)TCHAR_TO_UTF8(*input)).c_str() +#define CharToFString(input) UTF8_TO_TCHAR(input) + +//! Simple input device. +class FMobuLiveLink : public FBDevice +{ + //--- FiLMBOX declaration + FBDeviceDeclare(FMobuLiveLink, FBDevice); +public: + void StartLiveLink(); + void StopLiveLink(); + + //--- FiLMBOX Construction/Destruction + bool FBCreate() override; //!< FiLMBOX constructor. + void FBDestroy() override; //!< FiLMBOX destructor. + + //--- Initialisation/Shutdown + bool Init(); //!< Initialize/create device. + bool Done(); //!< Remove device. + bool Reset(); //!< Reset device. + bool Stop(); //!< Stop device (offline). + bool Start(); //!< Start device (online). + + //--- The following will be called by the real-time engine. + void DeviceIONotify(kDeviceIOs Action, FBDeviceNotifyInfo &DeviceNotifyInfo) override; //!< Notification of/for Device IO. + bool DeviceEvaluationNotify(kTransportMode Mode, FBEvaluateInfo* EvaluateInfo) override; //!< Evaluation the device (write to hardware). + bool DeviceOperation(kDeviceOperations Operation) override; //!< Operate device. + + //--- FBX Load/Save. + bool FbxStore(FBFbxObject* FbxObject, kFbxObjectStore StoreWhat) override; //!< Store in FBX file. + bool FbxRetrieve(FBFbxObject* FbxObject, kFbxObjectStore StoreWhat) override; //!< Retrieve from FBX file. + + //--- Events + void EventSceneChange(HISender Sender, HKEvent Event); + void EventRenderUpdate(HISender Sender, HKEvent Event); + +private: + typedef TSharedPtr StreamObjectPtr; + + void FbxRetrieveV4(FBFbxObject* FbxObject, kFbxObjectStore StoreWhat); //!< Retrieve from FBX file stored with the previous version. + +public: + void AddStreamObject(int32 NewUID, StreamObjectPtr NewObject); + void RemoveStreamObject(int32 DeletionKey, StreamObjectPtr RemoveObject); + void ChangeSubjectName(StreamObjectPtr ObjectPtr, const char* NewSubjectNameStr); + void UpdateStreamObjects(); + + void SetDirty(bool bNewDirty) { bIsDirty = bNewDirty; }; + bool IsDirty() const { return bIsDirty; }; + + void SetRefreshUI(bool bNewRefreshUI) { bShouldRefreshUI = bNewRefreshUI; }; + bool ShouldRefreshUI() const { return bShouldRefreshUI; }; + + const TArray> SampleOptions = + { + TPair(FString("30hz"), FFrameRate(30, 1)), + TPair(FString("50hz"), FFrameRate(50, 1)), + TPair(FString("60hz"), FFrameRate(60, 1)), + TPair(FString("100hz"), FFrameRate(100, 1)), + TPair(FString("120hz"), FFrameRate(120, 1)), + TPair(FString("Before Render"), FFrameRate(-1, 1)), + }; + + FFrameRate CurrentSampleRate; + void UpdateSampleRate(); + + int32 GetNextUID(); + + bool IsEditorCameraStreamed() const; + void SetEditorCameraStreamed(bool bStream); + + ETimecodeMode GetTimecodeMode() const; + int32 GetTimecodeModeAsInt() const; + void SetTimecodeMode(ETimecodeMode InTimecodeMode); + void SetTimecodeModeFromInt(int32 InTimecodeModeInt); + + const FString& GetProviderName() const { return CurrentProviderName; } + void SetProviderName(const FString& NewValue); + + bool AddStaticEndpoint(const FString& InEndpoint); + const TArray& GetStaticEndpoints() const { return StaticEndpoints; } + bool RemoveStaticEndpoint(const FString& InEndpoint); + + FString GetUnicastEndpoint() const; + void SetUnicastEndpoint(const FString& InEndpoint); + +public: + TMap> StreamObjects; + TSharedPtr LiveLinkProvider; + +private: + TWeakPtr EditorCameraObject; + + FString CurrentProviderName = "Mobu Live Link"; + + TArray StaticEndpoints; + + int32 NextUID = 1; + + void UpdateStream(); //!< Get latest data and send to unreal + + int32 GetCurrentSampleRateIndex(); + + bool bIsDirty = false; + bool bShouldRefreshUI = false; + + bool bShouldUpdateInRenderCallback = false; //!< Whether to update after render or to update in device evaluation + + FBDeviceSamplingMode SamplingType; + FBFastLock mCleanUpLock; + + TMap SceneChangeNameMap; + + double LastEvaluationTime; + ETimecodeMode TimecodeMode; + + void SetDeviceInformation(const char* NewDeviceInformation); + void TickCoreTicker(); +}; + diff --git a/Source/Public/MobuLiveLinkLayout.h b/Source/Public/MobuLiveLinkLayout.h index f1bad30f..3e93f5ab 100644 --- a/Source/Public/MobuLiveLinkLayout.h +++ b/Source/Public/MobuLiveLinkLayout.h @@ -1,59 +1,75 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#pragma once -#include -#include "MobuLiveLinkDevice.h" - - -class FMobuLiveLinkLayout : public FBDeviceLayout -{ - FBDeviceLayoutDeclare(FMobuLiveLinkLayout, FBDeviceLayout); - -public: - bool FBCreate() override; - void FBDestroy() override; - - // UI Management - void UICreate(); - void UIConfigure(); - void UIReset(); - void CreateSpreadColumns(); - - // Main Layout: Events - void EventUIIdle(HISender Sender, HKEvent Event); - void EventAddToStream(HISender Sender, HKEvent Event); - void EventRemoveFromStream(HISender Sender, HKEvent Event); - void EventStreamEditorCamera(HISender Sender, HKEvent Event); - void EventStreamSpreadCellChange(HISender Sender, HKEvent Event); - void EventTimecodeModeChanged(HISender Sender, HKEvent Event); - void EventSampleRateChange(HISender Sender, HKEvent Event); - void EventEditProviderNamePopup(HISender Sender, HKEvent Event); - -public: - - FBLayout StreamLayout; - - FBLabel ObjectSelectorLabel; - FBPropertyConnectionEditor ObjectSelector; - FBButton AddToStreamButton; - FBButton RemoveFromStreamButton; - FBButton StreamEditorCameraButton; - FBList TimecodeModeList; - FBLabel TimecodeModeListLabel; - FBLabel SampleRateListLabel; - FBList SampleRateList; - FBLabel ProviderNameLabel; - FBEdit ProviderNameText; - FBButton ProviderNameEditButton; - FBSpread StreamSpread; - -private: - typedef TSharedPtr StreamObjectPtr; - - void AddSpreadRowFromStreamObject(int32 NewRowKey, StreamObjectPtr Object); - - FBSystem System; - FMobuLiveLink* LiveLinkDevice; - - FBPropertyListObject ObjectSelection; +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once +#include +#include "MobuLiveLinkDevice.h" + + +class FMobuLiveLinkLayout : public FBDeviceLayout +{ + FBDeviceLayoutDeclare(FMobuLiveLinkLayout, FBDeviceLayout); + +public: + bool FBCreate() override; + void FBDestroy() override; + + // UI Management + void UICreate(); + void UICreateLayout0(); + void UICreateLayout1(); + void UIConfigure(); + void UIConfigureLayout0(); + void UIConfigureLayout1(); + void UIReset(); + void CreateSpreadColumns(); + + // Main Layout: Events + void EventUIIdle(HISender Sender, HKEvent Event); + void EventAddToStream(HISender Sender, HKEvent Event); + void EventRemoveFromStream(HISender Sender, HKEvent Event); + void EventStreamEditorCamera(HISender Sender, HKEvent Event); + void EventStreamSpreadCellChange(HISender Sender, HKEvent Event); + void EventTabPanelChange(HISender pSender, HKEvent pEvent); + void EventTimecodeModeChanged(HISender Sender, HKEvent Event); + void EventSampleRateChange(HISender Sender, HKEvent Event); + void EventEditProviderNamePopup(HISender Sender, HKEvent Event); + void EventChangeUnicastEndpoint(HISender Sender, HKEvent Event); + void EventAddStaticEndpoint(HISender Sender, HKEvent Event); + void EventRemoveStaticEndpoint(HISender Sender, HKEvent Event); + +public: + + FBTabPanel TabPanel; + FBLayout Layouts[2]; + + FBLabel ObjectSelectorLabel; + FBPropertyConnectionEditor ObjectSelector; + FBButton AddToStreamButton; + FBButton RemoveFromStreamButton; + FBButton StreamEditorCameraButton; + FBList TimecodeModeList; + FBLabel TimecodeModeListLabel; + FBLabel SampleRateListLabel; + FBList SampleRateList; + FBLabel ProviderNameLabel; + FBEdit ProviderNameText; + FBButton ProviderNameEditButton; + FBSpread StreamSpread; + FBLabel UnicastEndpointLabel; + FBEdit UnicastEndpoint; + FBButton UnicastEndpointEditButton; + FBLabel StaticEndpointLabel; + FBList StaticEndpoints; + FBButton StaticEndpointAddButton; + FBButton StaticEndpointRemoveButton; + +private: + typedef TSharedPtr StreamObjectPtr; + + void AddSpreadRowFromStreamObject(int32 NewRowKey, StreamObjectPtr Object); + + FBSystem System; + FMobuLiveLink* LiveLinkDevice; + + FBPropertyListObject ObjectSelection; }; \ No newline at end of file diff --git a/Source/StreamObjects/Private/ModelStreamObject.cpp b/Source/StreamObjects/Private/ModelStreamObject.cpp index 3683da63..79650da9 100644 --- a/Source/StreamObjects/Private/ModelStreamObject.cpp +++ b/Source/StreamObjects/Private/ModelStreamObject.cpp @@ -1,271 +1,272 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#include "ModelStreamObject.h" -#include "MobuLiveLinkUtilities.h" -#include - -#include "Roles/LiveLinkAnimationRole.h" -#include "Roles/LiveLinkAnimationTypes.h" -#include "Roles/LiveLinkTransformRole.h" -#include "Roles/LiveLinkTransformTypes.h" - -// Creation / Destruction -FModelStreamObject::FModelStreamObject(const FBModel* ModelPointer) - : RootModel(ModelPointer) - , bIsActive(true) - , bSendAnimatable(false) - , StreamingMode(FModelStreamMode::RootOnly) -{ - check(ModelPointer); - - FString ModelLongName(ANSI_TO_TCHAR(RootModel->LongName)); - FString RightString; - ModelLongName.Split(TEXT(":"), &ModelLongName, &RightString); - SubjectName = FName(*ModelLongName); -}; - -FModelStreamObject::~FModelStreamObject() -{ -}; - -// Stream Object Interface - -const bool FModelStreamObject::ShouldShowInUI() const -{ - return true; -}; - -const FString FModelStreamObject::GetStreamOptions() const -{ - return FString::Join(ModelStreamOptions, _T("~")); -}; - -FName FModelStreamObject::GetSubjectName() const -{ - return SubjectName; -}; - -void FModelStreamObject::UpdateSubjectName(FName NewSubjectName) -{ - if (NewSubjectName != SubjectName) - { - SubjectName = NewSubjectName; - } -}; - - -int FModelStreamObject::GetStreamingMode() const -{ - return StreamingMode; -}; - -void FModelStreamObject::UpdateStreamingMode(int NewStreamingMode) -{ - if (StreamingMode != NewStreamingMode) - { - StreamingMode = NewStreamingMode; - } -}; - -bool FModelStreamObject::GetActiveStatus() const -{ - return bIsActive; -}; - -void FModelStreamObject::UpdateActiveStatus(bool bIsNowActive) -{ - bIsActive = bIsNowActive; -}; - -bool FModelStreamObject::GetSendAnimatableStatus() const -{ - return bSendAnimatable; -}; - -void FModelStreamObject::UpdateSendAnimatableStatus(bool bNewSendAnimatable) -{ - if (bSendAnimatable != bNewSendAnimatable) - { - bSendAnimatable = bNewSendAnimatable; - } -}; - -const FBModel* FModelStreamObject::GetModelPointer() const -{ - return RootModel; -}; - -const FString FModelStreamObject::GetRootName() const -{ - return FString(ANSI_TO_TCHAR(RootModel->LongName)); -}; - -bool FModelStreamObject::IsValid() const -{ - // By Default an object is valid if the root model is in the scene - return FBSystem().Scene->Components.Find((FBComponent*)RootModel) >= 0; -}; - -void FModelStreamObject::Refresh(const TSharedPtr Provider) -{ - if (GetStreamingMode() == FModelStreamMode::FullHierarchy) - { - FLiveLinkStaticDataStruct SkeletonData(FLiveLinkSkeletonStaticData::StaticStruct()); - UpdateSubjectSkeletalStaticData(*SkeletonData.Cast()); - Provider->UpdateSubjectStaticData(SubjectName, ULiveLinkAnimationRole::StaticClass(), MoveTemp(SkeletonData)); - } - else - { - FLiveLinkStaticDataStruct TransformData(FLiveLinkTransformStaticData::StaticStruct()); - UpdateSubjectTransformStaticData(RootModel, bSendAnimatable, *TransformData.Cast()); - Provider->UpdateSubjectStaticData(SubjectName, ULiveLinkTransformRole::StaticClass(), MoveTemp(TransformData)); - } -} - -void FModelStreamObject::UpdateSubjectFrame(const TSharedPtr Provider, FLiveLinkWorldTime WorldTime, FQualifiedFrameTime QualifiedFrameTime) -{ - if (!bIsActive) - { - return; - } - - if (GetStreamingMode() == FModelStreamMode::FullHierarchy) - { - FLiveLinkFrameDataStruct TransformData = (FLiveLinkAnimationFrameData::StaticStruct()); - UpdateSubjectSkeletalFrameData(WorldTime, QualifiedFrameTime, *TransformData.Cast()); - Provider->UpdateSubjectFrameData(SubjectName, MoveTemp(TransformData)); - } - else - { - FLiveLinkFrameDataStruct TransformData = (FLiveLinkTransformFrameData::StaticStruct()); - UpdateSubjectTransformFrameData(RootModel, bSendAnimatable, WorldTime, QualifiedFrameTime, *TransformData.Cast()); - Provider->UpdateSubjectFrameData(SubjectName, MoveTemp(TransformData)); - } -} - -void FModelStreamObject::UpdateBaseStaticData(const FBModel* Model, bool bSendAnimatable, FLiveLinkBaseStaticData& InOutBaseStaticData) -{ - if (bSendAnimatable) - { - InOutBaseStaticData.PropertyNames = MobuUtilities::GetAllAnimatableCurveNames(const_cast(Model), FString(ANSI_TO_TCHAR(Model->Name))); - } -} - -void FModelStreamObject::UpdateSubjectTransformStaticData(const FBModel* Model, bool bSendAnimatable, FLiveLinkTransformStaticData& InOutTransformStatic) -{ - UpdateBaseStaticData(Model, bSendAnimatable, InOutTransformStatic); -} - -void FModelStreamObject::UpdateSubjectSkeletalStaticData(FLiveLinkSkeletonStaticData& InOutAnimationStatic) -{ - UpdateBaseStaticData(RootModel, bSendAnimatable, InOutAnimationStatic); - - InOutAnimationStatic.BoneNames.Reset(); - BoneParents.Reset(); - BoneModels.Reset(); - - InOutAnimationStatic.BoneNames.Emplace(RootModel->Name); - BoneParents.Emplace(-1); - BoneModels.Emplace(RootModel); - - { - TArray> SearchList; - TArray> SearchListNext; - - SearchList.Emplace(0, (FBModel*)RootModel); - - while (SearchList.Num() > 0) - { - for (const TPair& SearchPair : SearchList) - { - int ParentIdx = SearchPair.Key; - FBModel* SearchModel = SearchPair.Value; - int ChildCount = SearchModel->Children.GetCount(); - - for (int ChildIdx = 0; ChildIdx < ChildCount; ++ChildIdx) - { - FBModel* ChildModel = SearchModel->Children[ChildIdx]; - - InOutAnimationStatic.BoneNames.Emplace(ChildModel->Name); - BoneParents.Emplace(ParentIdx); - BoneModels.Emplace(ChildModel); - - SearchListNext.Emplace(BoneModels.Num() - 1, ChildModel); - } - } - SearchList = SearchListNext; - SearchListNext.Reset(); - } - } - - InOutAnimationStatic.BoneParents = BoneParents; - - check(BoneModels.Num() == InOutAnimationStatic.BoneNames.Num()); - if (bSendAnimatable) - { - for (int32 BoneIndex = 0; BoneIndex < BoneModels.Num(); ++BoneIndex) - { - InOutAnimationStatic.PropertyNames.Append(MobuUtilities::GetAllAnimatableCurveNames(const_cast(BoneModels[BoneIndex]), InOutAnimationStatic.BoneNames[BoneIndex].ToString())); - } - } -} - -void FModelStreamObject::UpdateBaseFrameData(const FBModel* Model, bool bSendAnimatable, FLiveLinkWorldTime WorldTime, FQualifiedFrameTime QualifiedFrameTime, FLiveLinkBaseFrameData& InOutBaseFrameData) -{ - InOutBaseFrameData.WorldTime = WorldTime; - InOutBaseFrameData.MetaData.SceneTime = QualifiedFrameTime; - if (bSendAnimatable) - { - InOutBaseFrameData.PropertyValues = MobuUtilities::GetAllAnimatableCurveValues(const_cast(Model)); - } -} - -void FModelStreamObject::UpdateSubjectTransformFrameData(const FBModel* Model, bool bSendAnimatable, FLiveLinkWorldTime WorldTime, FQualifiedFrameTime QualifiedFrameTime, FLiveLinkTransformFrameData& InOutTransformFrame) -{ - UpdateBaseFrameData(Model, bSendAnimatable, WorldTime, QualifiedFrameTime, InOutTransformFrame); - InOutTransformFrame.Transform = MobuUtilities::UnrealTransformFromModel(const_cast(Model)); -} - -void FModelStreamObject::UpdateSubjectSkeletalFrameData(FLiveLinkWorldTime WorldTime, FQualifiedFrameTime QualifiedFrameTime, FLiveLinkAnimationFrameData& InOutAnimationFrame) -{ - UpdateBaseFrameData(RootModel, bSendAnimatable, WorldTime, QualifiedFrameTime, InOutAnimationFrame); - - if (BoneParents.Num() != BoneModels.Num()) - { - return; - } - - const int32 BoneCount = BoneParents.Num(); - InOutAnimationFrame.Transforms.SetNum(BoneCount); - - TArray ParentInverseTransforms; - ParentInverseTransforms.SetNum(BoneCount); - - // loop through children here - for (int BoneIndex = 0; BoneIndex < BoneModels.Num(); ++BoneIndex) - { - InOutAnimationFrame.Transforms[BoneIndex] = MobuUtilities::UnrealTransformFromModel(const_cast(BoneModels[BoneIndex])); - - // We seem to be getting NaNs from somewhere for some reason, so let's trap them here to prevent the engine from hitting the Ensure() - if (InOutAnimationFrame.Transforms[BoneIndex].ContainsNaN()) - { - FBTrace("ERROR - Subject %s contains NaNs - %s\n", TCHAR_TO_UTF8(*SubjectName.ToString()), TCHAR_TO_UTF8(*InOutAnimationFrame.Transforms[BoneIndex].ToString())); - ParentInverseTransforms[BoneIndex].SetIdentity(); - InOutAnimationFrame.Transforms[BoneIndex].SetIdentity(); - } - else - { - ParentInverseTransforms[BoneIndex] = InOutAnimationFrame.Transforms[BoneIndex].Inverse(); - if (BoneParents[BoneIndex] != -1) - { - InOutAnimationFrame.Transforms[BoneIndex] = InOutAnimationFrame.Transforms[BoneIndex] * ParentInverseTransforms[BoneParents[BoneIndex]]; - } - } - - if (bSendAnimatable) - { - // Stream all parameters of all bones as ":" - InOutAnimationFrame.PropertyValues.Append(MobuUtilities::GetAllAnimatableCurveValues(const_cast(BoneModels[BoneIndex]))); - } - } +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "ModelStreamObject.h" +#include "MobuLiveLinkUtilities.h" +#include + +#include "Roles/LiveLinkAnimationRole.h" +#include "Roles/LiveLinkAnimationTypes.h" +#include "Roles/LiveLinkTransformRole.h" +#include "Roles/LiveLinkTransformTypes.h" + +// Creation / Destruction +FModelStreamObject::FModelStreamObject(const FBModel* ModelPointer) + : RootModel(ModelPointer) + , bIsActive(true) + , bSendAnimatable(false) + , StreamingMode(FModelStreamMode::RootOnly) +{ + check(ModelPointer); + + FString ModelLongName(ANSI_TO_TCHAR(RootModel->LongName)); + FString RightString; + ModelLongName.Split(TEXT(":"), &ModelLongName, &RightString); + SubjectName = FName(*ModelLongName); +}; + +FModelStreamObject::~FModelStreamObject() +{ +}; + +// Stream Object Interface + +const bool FModelStreamObject::ShouldShowInUI() const +{ + return true; +}; + +const FString FModelStreamObject::GetStreamOptions() const +{ + return FString::Join(ModelStreamOptions, _T("~")); +}; + +FName FModelStreamObject::GetSubjectName() const +{ + return SubjectName; +}; + +void FModelStreamObject::UpdateSubjectName(FName NewSubjectName) +{ + if (NewSubjectName != SubjectName) + { + SubjectName = NewSubjectName; + } +}; + + +int FModelStreamObject::GetStreamingMode() const +{ + return StreamingMode; +}; + +void FModelStreamObject::UpdateStreamingMode(int NewStreamingMode) +{ + if (StreamingMode != NewStreamingMode) + { + StreamingMode = NewStreamingMode; + } +}; + +bool FModelStreamObject::GetActiveStatus() const +{ + return bIsActive; +}; + +void FModelStreamObject::UpdateActiveStatus(bool bIsNowActive) +{ + bIsActive = bIsNowActive; +}; + +bool FModelStreamObject::GetSendAnimatableStatus() const +{ + return bSendAnimatable; +}; + +void FModelStreamObject::UpdateSendAnimatableStatus(bool bNewSendAnimatable) +{ + if (bSendAnimatable != bNewSendAnimatable) + { + bSendAnimatable = bNewSendAnimatable; + } +}; + +const FBModel* FModelStreamObject::GetModelPointer() const +{ + return RootModel; +}; + +const FString FModelStreamObject::GetRootName() const +{ + return FString(ANSI_TO_TCHAR(RootModel->LongName)); +}; + +bool FModelStreamObject::IsValid() const +{ + // By Default an object is valid if the root model is in the scene + return FBSystem().Scene->Components.Find((FBComponent*)RootModel) >= 0; +}; + +void FModelStreamObject::Refresh(const TSharedPtr Provider) +{ + if (GetStreamingMode() == FModelStreamMode::FullHierarchy) + { + FLiveLinkStaticDataStruct SkeletonData(FLiveLinkSkeletonStaticData::StaticStruct()); + UpdateSubjectSkeletalStaticData(*SkeletonData.Cast()); + Provider->UpdateSubjectStaticData(SubjectName, ULiveLinkAnimationRole::StaticClass(), MoveTemp(SkeletonData)); + } + else + { + FLiveLinkStaticDataStruct TransformData(FLiveLinkTransformStaticData::StaticStruct()); + UpdateSubjectTransformStaticData(RootModel, bSendAnimatable, *TransformData.Cast()); + Provider->UpdateSubjectStaticData(SubjectName, ULiveLinkTransformRole::StaticClass(), MoveTemp(TransformData)); + } +} + +void FModelStreamObject::UpdateSubjectFrame(const TSharedPtr Provider, FLiveLinkWorldTime WorldTime, FQualifiedFrameTime QualifiedFrameTime) +{ + if (!bIsActive) + { + return; + } + + if (GetStreamingMode() == FModelStreamMode::FullHierarchy) + { + FLiveLinkFrameDataStruct TransformData = (FLiveLinkAnimationFrameData::StaticStruct()); + UpdateSubjectSkeletalFrameData(WorldTime, QualifiedFrameTime, *TransformData.Cast()); + Provider->UpdateSubjectFrameData(SubjectName, MoveTemp(TransformData)); + } + else + { + FLiveLinkFrameDataStruct TransformData = (FLiveLinkTransformFrameData::StaticStruct()); + UpdateSubjectTransformFrameData(RootModel, bSendAnimatable, WorldTime, QualifiedFrameTime, *TransformData.Cast()); + Provider->UpdateSubjectFrameData(SubjectName, MoveTemp(TransformData)); + } +} + +void FModelStreamObject::UpdateBaseStaticData(const FBModel* Model, bool bSendAnimatable, FLiveLinkBaseStaticData& InOutBaseStaticData) +{ + if (bSendAnimatable) + { + InOutBaseStaticData.PropertyNames = MobuUtilities::GetAllAnimatableCurveNames(const_cast(Model), FString(ANSI_TO_TCHAR(Model->Name))); + } +} + +void FModelStreamObject::UpdateSubjectTransformStaticData(const FBModel* Model, bool bSendAnimatable, FLiveLinkTransformStaticData& InOutTransformStatic) +{ + InOutTransformStatic.bIsScaleSupported = true; + UpdateBaseStaticData(Model, bSendAnimatable, InOutTransformStatic); +} + +void FModelStreamObject::UpdateSubjectSkeletalStaticData(FLiveLinkSkeletonStaticData& InOutAnimationStatic) +{ + UpdateBaseStaticData(RootModel, bSendAnimatable, InOutAnimationStatic); + + InOutAnimationStatic.BoneNames.Reset(); + BoneParents.Reset(); + BoneModels.Reset(); + + InOutAnimationStatic.BoneNames.Emplace(RootModel->Name); + BoneParents.Emplace(-1); + BoneModels.Emplace(RootModel); + + { + TArray> SearchList; + TArray> SearchListNext; + + SearchList.Emplace(0, (FBModel*)RootModel); + + while (SearchList.Num() > 0) + { + for (const TPair& SearchPair : SearchList) + { + int ParentIdx = SearchPair.Key; + FBModel* SearchModel = SearchPair.Value; + int ChildCount = SearchModel->Children.GetCount(); + + for (int ChildIdx = 0; ChildIdx < ChildCount; ++ChildIdx) + { + FBModel* ChildModel = SearchModel->Children[ChildIdx]; + + InOutAnimationStatic.BoneNames.Emplace(ChildModel->Name); + BoneParents.Emplace(ParentIdx); + BoneModels.Emplace(ChildModel); + + SearchListNext.Emplace(BoneModels.Num() - 1, ChildModel); + } + } + SearchList = SearchListNext; + SearchListNext.Reset(); + } + } + + InOutAnimationStatic.BoneParents = BoneParents; + + check(BoneModels.Num() == InOutAnimationStatic.BoneNames.Num()); + if (bSendAnimatable) + { + for (int32 BoneIndex = 0; BoneIndex < BoneModels.Num(); ++BoneIndex) + { + InOutAnimationStatic.PropertyNames.Append(MobuUtilities::GetAllAnimatableCurveNames(const_cast(BoneModels[BoneIndex]), InOutAnimationStatic.BoneNames[BoneIndex].ToString())); + } + } +} + +void FModelStreamObject::UpdateBaseFrameData(const FBModel* Model, bool bSendAnimatable, FLiveLinkWorldTime WorldTime, FQualifiedFrameTime QualifiedFrameTime, FLiveLinkBaseFrameData& InOutBaseFrameData) +{ + InOutBaseFrameData.WorldTime = WorldTime; + InOutBaseFrameData.MetaData.SceneTime = QualifiedFrameTime; + if (bSendAnimatable) + { + InOutBaseFrameData.PropertyValues = MobuUtilities::GetAllAnimatableCurveValues(const_cast(Model)); + } +} + +void FModelStreamObject::UpdateSubjectTransformFrameData(const FBModel* Model, bool bSendAnimatable, FLiveLinkWorldTime WorldTime, FQualifiedFrameTime QualifiedFrameTime, FLiveLinkTransformFrameData& InOutTransformFrame) +{ + UpdateBaseFrameData(Model, bSendAnimatable, WorldTime, QualifiedFrameTime, InOutTransformFrame); + InOutTransformFrame.Transform = MobuUtilities::UnrealTransformFromModel(const_cast(Model)); +} + +void FModelStreamObject::UpdateSubjectSkeletalFrameData(FLiveLinkWorldTime WorldTime, FQualifiedFrameTime QualifiedFrameTime, FLiveLinkAnimationFrameData& InOutAnimationFrame) +{ + UpdateBaseFrameData(RootModel, bSendAnimatable, WorldTime, QualifiedFrameTime, InOutAnimationFrame); + + if (BoneParents.Num() != BoneModels.Num()) + { + return; + } + + const int32 BoneCount = BoneParents.Num(); + InOutAnimationFrame.Transforms.SetNum(BoneCount); + + TArray ParentInverseTransforms; + ParentInverseTransforms.SetNum(BoneCount); + + // loop through children here + for (int BoneIndex = 0; BoneIndex < BoneModels.Num(); ++BoneIndex) + { + InOutAnimationFrame.Transforms[BoneIndex] = MobuUtilities::UnrealTransformFromModel(const_cast(BoneModels[BoneIndex])); + + // We seem to be getting NaNs from somewhere for some reason, so let's trap them here to prevent the engine from hitting the Ensure() + if (InOutAnimationFrame.Transforms[BoneIndex].ContainsNaN()) + { + FBTrace("ERROR - Subject %s contains NaNs - %s\n", TCHAR_TO_UTF8(*SubjectName.ToString()), TCHAR_TO_UTF8(*InOutAnimationFrame.Transforms[BoneIndex].ToString())); + ParentInverseTransforms[BoneIndex].SetIdentity(); + InOutAnimationFrame.Transforms[BoneIndex].SetIdentity(); + } + else + { + ParentInverseTransforms[BoneIndex] = InOutAnimationFrame.Transforms[BoneIndex].Inverse(); + if (BoneParents[BoneIndex] != -1) + { + InOutAnimationFrame.Transforms[BoneIndex] = InOutAnimationFrame.Transforms[BoneIndex] * ParentInverseTransforms[BoneParents[BoneIndex]]; + } + } + + if (bSendAnimatable) + { + // Stream all parameters of all bones as ":" + InOutAnimationFrame.PropertyValues.Append(MobuUtilities::GetAllAnimatableCurveValues(const_cast(BoneModels[BoneIndex]))); + } + } } \ No newline at end of file