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