├── LICENSE ├── README.md ├── Resources ├── UE_128-l.png └── UE_128.png └── Source ├── MobuLiveLinkPlugin2016.Build.cs ├── MobuLiveLinkPlugin2016.Target.cs ├── MobuLiveLinkPlugin2017.Build.cs ├── MobuLiveLinkPlugin2017.Target.cs ├── MobuLiveLinkPlugin2018.Build.cs ├── MobuLiveLinkPlugin2018.Target.cs ├── MobuLiveLinkPlugin2019.Build.cs ├── MobuLiveLinkPlugin2019.Target.cs ├── MobuLiveLinkPlugin2020.Build.cs ├── MobuLiveLinkPlugin2020.Target.cs ├── MobuLiveLinkPlugin2022.Build.cs ├── MobuLiveLinkPlugin2022.Target.cs ├── MobuLiveLinkPlugin2023.Build.cs ├── MobuLiveLinkPlugin2023.Target.cs ├── MobuLiveLinkPlugin2024.Build.cs ├── MobuLiveLinkPlugin2024.Target.cs ├── MobuLiveLinkPlugin2025.Build.cs ├── MobuLiveLinkPlugin2025.Target.cs ├── Private ├── MobuLiveLink.cpp ├── MobuLiveLinkDevice.cpp ├── MobuLiveLinkLayout.cpp ├── MobuLiveLinkUtilities.cpp └── StreamObjectManagement.cpp ├── Public ├── IStreamObject.h ├── MobuLiveLinkCommon.h ├── MobuLiveLinkDevice.h ├── MobuLiveLinkLayout.h ├── MobuLiveLinkStreamObjects.h └── MobuLiveLinkUtilities.h └── StreamObjects ├── Private ├── CameraStreamObject.cpp ├── EditorActiveCameraStreamObject.cpp ├── LightStreamObject.cpp ├── ModelStreamObject.cpp └── SkeletonHierarchyStreamObject.cpp └── Public ├── CameraStreamObject.h ├── EditorActiveCameraStreamObject.h ├── LightStreamObject.h ├── ModelStreamObject.h └── SkeletonHierarchyStreamObject.h /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Epic Games 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Documentation for MotionBuilder Live Link can be found on our site: 2 | https://docs.unrealengine.com/en-US/Engine/Animation/LiveLinkPlugin/ConnectingLiveLinktoMobu/index.html -------------------------------------------------------------------------------- /Resources/UE_128-l.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ue4plugins/MobuLiveLink/621b241ea221e62b727a2bc4c05b600bb531ee4b/Resources/UE_128-l.png -------------------------------------------------------------------------------- /Resources/UE_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ue4plugins/MobuLiveLink/621b241ea221e62b727a2bc4c05b600bb531ee4b/Resources/UE_128.png -------------------------------------------------------------------------------- /Source/MobuLiveLinkPlugin2016.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | using UnrealBuildTool; 4 | using System.IO; 5 | 6 | public class MobuLiveLinkPlugin2016 : MobuLiveLinkPluginBase 7 | { 8 | public MobuLiveLinkPlugin2016(ReadOnlyTargetRules Target) : base(Target, "2016") 9 | { 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Source/MobuLiveLinkPlugin2016.Target.cs: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | using UnrealBuildTool; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | 7 | public class MobuLiveLinkPlugin2016Target : MobuLiveLinkPluginTargetBase 8 | { 9 | public MobuLiveLinkPlugin2016Target(TargetInfo Target) : base(Target, "2016") 10 | { 11 | //Mobu is not strict c++ compliant before Mobu 2019 12 | WindowsPlatform.bStrictConformanceMode = false; 13 | CppStandard = CppStandardVersion.Cpp17; 14 | } 15 | } -------------------------------------------------------------------------------- /Source/MobuLiveLinkPlugin2017.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | using UnrealBuildTool; 4 | using System.IO; 5 | 6 | public abstract class MobuLiveLinkPluginBase : ModuleRules 7 | { 8 | public MobuLiveLinkPluginBase(ReadOnlyTargetRules Target, string MobuVersionString) : base(Target) 9 | { 10 | IWYUSupport = IWYUSupport.None; 11 | bUseRTTI = true; 12 | 13 | PrivateIncludePathModuleNames.Add("Launch"); 14 | 15 | PrivateIncludePaths.AddRange(new string[] 16 | { 17 | Path.Combine(ModuleDirectory, "StreamObjects/Public"), 18 | }); 19 | 20 | // Unreal dependency modules 21 | PrivateDependencyModuleNames.AddRange(new string[] 22 | { 23 | "Core", 24 | "CoreUObject", 25 | "ApplicationCore", 26 | "Messaging", 27 | "Projects", 28 | "UdpMessaging", 29 | "LiveLinkInterface", 30 | "LiveLinkMessageBusFramework", 31 | }); 32 | 33 | // Mobu SDK setup 34 | { 35 | //UE_MOTIONBUILDER2017_INSTALLATIONFOLDER 36 | string MobuInstallFolder = System.Environment.GetEnvironmentVariable("UE_MOTIONBUILDER" + MobuVersionString + "_INSTALLATIONFOLDER"); 37 | if (string.IsNullOrEmpty(MobuInstallFolder)) 38 | { 39 | MobuInstallFolder = @"C:\Program Files\Autodesk\MotionBuilder " + MobuVersionString; 40 | } 41 | MobuInstallFolder = Path.Combine(MobuInstallFolder, "OpenRealitySDK"); 42 | 43 | if (!Directory.Exists(MobuInstallFolder)) 44 | { 45 | // Try with build machine setup 46 | string SDKRootEnvVar = System.Environment.GetEnvironmentVariable("UE_SDKS_ROOT"); 47 | if (!string.IsNullOrEmpty(SDKRootEnvVar)) 48 | { 49 | MobuInstallFolder = Path.Combine(SDKRootEnvVar, "HostWin64", "Win64", "MotionBuilder", MobuVersionString); 50 | } 51 | } 52 | 53 | // Make sure this version of Mobu is actually installed 54 | if (Directory.Exists(MobuInstallFolder)) 55 | { 56 | PrivateIncludePaths.Add(Path.Combine(MobuInstallFolder, "include")); 57 | 58 | if (Target.Platform == UnrealTargetPlatform.Win64) // @todo: Support other platforms? 59 | { 60 | string LibDir = Path.Combine(MobuInstallFolder, "lib/x64"); 61 | 62 | // Mobu library we're depending on 63 | PublicAdditionalLibraries.Add(Path.Combine(LibDir, "fbsdk.lib")); 64 | } 65 | } 66 | 67 | PublicDefinitions.Add("PRODUCT_VERSION=" + MobuVersionString); 68 | } 69 | } 70 | } 71 | 72 | public class MobuLiveLinkPlugin2017 : MobuLiveLinkPluginBase 73 | { 74 | public MobuLiveLinkPlugin2017(ReadOnlyTargetRules Target) : base(Target, "2017") 75 | { 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Source/MobuLiveLinkPlugin2017.Target.cs: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | using UnrealBuildTool; 4 | using System; 5 | using System.IO; 6 | using EpicGames.Core; 7 | using System.Runtime.CompilerServices; 8 | 9 | [SupportedPlatforms(UnrealPlatformClass.Desktop)] 10 | public abstract class MobuLiveLinkPluginTargetBase : TargetRules 11 | { 12 | /// 13 | /// Finds the innermost parent directory with the provided name. Search is case insensitive. 14 | /// 15 | string InnermostParentDirectoryPathWithName(string ParentName, string CurrentPath) 16 | { 17 | DirectoryInfo ParentInfo = Directory.GetParent(CurrentPath); 18 | 19 | if (ParentInfo == null) 20 | { 21 | throw new DirectoryNotFoundException("Could not find parent folder '" + ParentName + "'"); 22 | } 23 | 24 | // Case-insensitive check of the parent folder name. 25 | if (ParentInfo.Name.ToLower() == ParentName.ToLower()) 26 | { 27 | return ParentInfo.ToString(); 28 | } 29 | 30 | return InnermostParentDirectoryPathWithName(ParentName, ParentInfo.ToString()); 31 | } 32 | 33 | /// 34 | /// Returns the path to this .cs file. 35 | /// 36 | string GetCallerFilePath([CallerFilePath] string CallerFilePath = "") 37 | { 38 | if (CallerFilePath.Length == 0) 39 | { 40 | throw new FileNotFoundException("Could not find the path of our .cs file"); 41 | } 42 | 43 | return CallerFilePath; 44 | } 45 | 46 | public MobuLiveLinkPluginTargetBase(TargetInfo Target, string InMobuVersionString) : base(Target) 47 | { 48 | Type = TargetType.Program; 49 | 50 | bShouldCompileAsDLL = true; 51 | LinkType = TargetLinkType.Monolithic; 52 | SolutionDirectory = "Programs/LiveLink"; 53 | LaunchModuleName = "MobuLiveLinkPlugin" + InMobuVersionString; 54 | 55 | // We only need minimal use of the engine for this plugin 56 | bBuildDeveloperTools = false; 57 | bBuildWithEditorOnlyData = true; 58 | bCompileAgainstEngine = false; 59 | bCompileAgainstCoreUObject = true; 60 | bCompileICU = false; 61 | bHasExports = false; 62 | 63 | // This .cs file must be inside the source folder of this Program. We later use this to find other key directories. 64 | string TargetFilePath = GetCallerFilePath(); 65 | 66 | // We need to avoid failing to load DLL due to looking for EngineDir() in non-existent folders. 67 | // By having it build in the same directory as the engine, it will assume the engine is in the same directory 68 | // as the program, and because this folder always exists, it will not fail the check inside EngineDir(). 69 | 70 | // Because this is a Program, we assume that this target file resides under a "Programs" folder. 71 | string ProgramsDir = InnermostParentDirectoryPathWithName("Programs", TargetFilePath); 72 | 73 | // We assume this Program resides under a Source folder. 74 | string SourceDir = InnermostParentDirectoryPathWithName("Source", ProgramsDir); 75 | 76 | // The program is assumed to reside inside the "Engine" folder. 77 | string EngineDir = InnermostParentDirectoryPathWithName("Engine", SourceDir); 78 | 79 | // The default Binaries path is assumed to be a sibling of "Source" folder. 80 | string DefaultBinDir = Path.GetFullPath(Path.Combine(SourceDir, "..", "Binaries", Platform.ToString())); 81 | 82 | // We assume that the engine exe resides in Engine/Binaries/[Platform] 83 | string EngineBinariesDir = Path.Combine(EngineDir, "Binaries", Platform.ToString()); 84 | 85 | // Now we calculate the relative path between the default output directory and the engine binaries, 86 | // in order to force the output of this program to be in the same folder as th engine. 87 | ExeBinariesSubFolder = (new DirectoryReference(EngineBinariesDir)).MakeRelativeTo(new DirectoryReference(DefaultBinDir)); 88 | 89 | // Setting this is necessary since we are creating the binaries outside of Restricted. 90 | bLegalToDistributeBinary = true; 91 | 92 | // 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. 93 | // MobuLiveLinkPlugin.xml will be unaware of how the files got there. 94 | 95 | string ResourcesDir = Path.Combine(ProgramsDir, "MobuLiveLink", "Resources"); 96 | string PostBuildBinDir = Path.Combine(DefaultBinDir, "MotionBuilder", InMobuVersionString); 97 | string TbbDependency = Path.Combine(EngineBinariesDir, "tbb.dll"); 98 | string TbbMallocDependency = Path.Combine(EngineBinariesDir, "tbbmalloc.dll"); 99 | 100 | // Copy resources 101 | PostBuildSteps.Add(string.Format("echo Copying {0} to {1}...", ResourcesDir, PostBuildBinDir)); 102 | PostBuildSteps.Add(string.Format("xcopy /y /i /v \"{0}\\*.*\" \"{1}\" 1>nul", ResourcesDir, PostBuildBinDir)); 103 | 104 | // Copy binaries 105 | PostBuildSteps.Add(string.Format("echo Copying {0} to {1}...", EngineBinariesDir, PostBuildBinDir)); 106 | PostBuildSteps.Add(string.Format("xcopy /y /i /v \"{0}\\{1}.*\" \"{2}\" 1>nul", EngineBinariesDir, LaunchModuleName, PostBuildBinDir)); 107 | 108 | // Copy support dlls 109 | PostBuildSteps.Add(string.Format("echo Copying {0} to {1}...", TbbDependency, PostBuildBinDir)); 110 | PostBuildSteps.Add(string.Format("xcopy /y /i /r /v \"{0}\" \"{1}\" 1>nul", TbbDependency, PostBuildBinDir)); 111 | PostBuildSteps.Add(string.Format("xcopy /y /i /r /v \"{0}\" \"{1}\" 1>nul", TbbMallocDependency, PostBuildBinDir)); 112 | } 113 | } 114 | 115 | public class MobuLiveLinkPlugin2017Target : MobuLiveLinkPluginTargetBase 116 | { 117 | public MobuLiveLinkPlugin2017Target(TargetInfo Target) : base(Target, "2017") 118 | { 119 | //Mobu is not strict c++ compliant before Mobu 2019 120 | WindowsPlatform.bStrictConformanceMode = false; 121 | CppStandard = CppStandardVersion.Cpp17; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /Source/MobuLiveLinkPlugin2018.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | using UnrealBuildTool; 4 | using System.IO; 5 | 6 | public class MobuLiveLinkPlugin2018 : MobuLiveLinkPluginBase 7 | { 8 | public MobuLiveLinkPlugin2018(ReadOnlyTargetRules Target) : base(Target, "2018") 9 | { 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Source/MobuLiveLinkPlugin2018.Target.cs: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | using UnrealBuildTool; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | 7 | public class MobuLiveLinkPlugin2018Target : MobuLiveLinkPluginTargetBase 8 | { 9 | public MobuLiveLinkPlugin2018Target(TargetInfo Target) : base(Target, "2018") 10 | { 11 | //Mobu is not strict c++ compliant before Mobu 2019 12 | WindowsPlatform.bStrictConformanceMode = false; 13 | CppStandard = CppStandardVersion.Cpp17; 14 | } 15 | } -------------------------------------------------------------------------------- /Source/MobuLiveLinkPlugin2019.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | using UnrealBuildTool; 4 | using System.IO; 5 | 6 | public class MobuLiveLinkPlugin2019 : MobuLiveLinkPluginBase 7 | { 8 | public MobuLiveLinkPlugin2019(ReadOnlyTargetRules Target) : base(Target, "2019") 9 | { 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Source/MobuLiveLinkPlugin2019.Target.cs: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | using UnrealBuildTool; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | 7 | public class MobuLiveLinkPlugin2019Target : MobuLiveLinkPluginTargetBase 8 | { 9 | public MobuLiveLinkPlugin2019Target(TargetInfo Target) : base(Target, "2019") 10 | {} 11 | } -------------------------------------------------------------------------------- /Source/MobuLiveLinkPlugin2020.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | using UnrealBuildTool; 4 | using System.IO; 5 | 6 | public class MobuLiveLinkPlugin2020 : MobuLiveLinkPluginBase 7 | { 8 | public MobuLiveLinkPlugin2020(ReadOnlyTargetRules Target) : base(Target, "2020") 9 | { 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Source/MobuLiveLinkPlugin2020.Target.cs: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | using UnrealBuildTool; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | 7 | public class MobuLiveLinkPlugin2020Target : MobuLiveLinkPluginTargetBase 8 | { 9 | public MobuLiveLinkPlugin2020Target(TargetInfo Target) : base(Target, "2020") 10 | {} 11 | } -------------------------------------------------------------------------------- /Source/MobuLiveLinkPlugin2022.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | using UnrealBuildTool; 4 | using System.IO; 5 | 6 | public class MobuLiveLinkPlugin2022 : MobuLiveLinkPluginBase 7 | { 8 | public MobuLiveLinkPlugin2022(ReadOnlyTargetRules Target) : base(Target, "2022") 9 | { 10 | CppStandard = CppStandardVersion.Cpp17; 11 | 12 | // Replace with PCHUsageMode.UseExplicitOrSharedPCHs when this plugin can compile with cpp20 13 | PCHUsage = PCHUsageMode.NoPCHs; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Source/MobuLiveLinkPlugin2022.Target.cs: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | using UnrealBuildTool; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | 7 | public class MobuLiveLinkPlugin2022Target : MobuLiveLinkPluginTargetBase 8 | { 9 | public MobuLiveLinkPlugin2022Target(TargetInfo Target) : base(Target, "2022") 10 | {} 11 | } -------------------------------------------------------------------------------- /Source/MobuLiveLinkPlugin2023.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | using UnrealBuildTool; 4 | using System.IO; 5 | 6 | public class MobuLiveLinkPlugin2023 : MobuLiveLinkPluginBase 7 | { 8 | public MobuLiveLinkPlugin2023(ReadOnlyTargetRules Target) : base(Target, "2023") 9 | { 10 | CppStandard = CppStandardVersion.Cpp17; 11 | 12 | // Replace with PCHUsageMode.UseExplicitOrSharedPCHs when this plugin can compile with cpp20 13 | PCHUsage = PCHUsageMode.NoPCHs; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Source/MobuLiveLinkPlugin2023.Target.cs: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | using UnrealBuildTool; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | 7 | public class MobuLiveLinkPlugin2023Target : MobuLiveLinkPluginTargetBase 8 | { 9 | public MobuLiveLinkPlugin2023Target(TargetInfo Target) : base(Target, "2023") 10 | {} 11 | } -------------------------------------------------------------------------------- /Source/MobuLiveLinkPlugin2024.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | using UnrealBuildTool; 4 | using System.IO; 5 | 6 | public class MobuLiveLinkPlugin2024 : MobuLiveLinkPluginBase 7 | { 8 | public MobuLiveLinkPlugin2024(ReadOnlyTargetRules Target) : base(Target, "2024") 9 | { 10 | CppStandard = CppStandardVersion.Cpp17; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Source/MobuLiveLinkPlugin2024.Target.cs: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | using UnrealBuildTool; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | 7 | public class MobuLiveLinkPlugin2024Target : MobuLiveLinkPluginTargetBase 8 | { 9 | public MobuLiveLinkPlugin2024Target(TargetInfo Target) : base(Target, "2024") 10 | {} 11 | } -------------------------------------------------------------------------------- /Source/MobuLiveLinkPlugin2025.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | using UnrealBuildTool; 4 | using System.IO; 5 | 6 | public class MobuLiveLinkPlugin2025 : MobuLiveLinkPluginBase 7 | { 8 | public MobuLiveLinkPlugin2025(ReadOnlyTargetRules Target) : base(Target, "2025") 9 | { 10 | CppStandard = CppStandardVersion.Cpp17; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Source/MobuLiveLinkPlugin2025.Target.cs: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | using UnrealBuildTool; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | 7 | public class MobuLiveLinkPlugin2025Target : MobuLiveLinkPluginTargetBase 8 | { 9 | public MobuLiveLinkPlugin2025Target(TargetInfo Target) : base(Target, "2025") 10 | {} 11 | } 12 | -------------------------------------------------------------------------------- /Source/Private/MobuLiveLink.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | #include "RequiredProgramMainCPPInclude.h" 4 | #include "MobuLiveLinkCommon.h" 5 | 6 | DEFINE_LOG_CATEGORY_STATIC(LogMoBuPlugin, Log, All); 7 | 8 | IMPLEMENT_APPLICATION(MobuLiveLinkPlugin, "MobuLiveLinkPlugin"); 9 | 10 | //--- Library declaration 11 | FBLibraryDeclare( FMobuLiveLink ) 12 | { 13 | FBLibraryRegister( FMobuLiveLink ); 14 | FBLibraryRegister( FMobuLiveLinkLayout ); 15 | } 16 | FBLibraryDeclareEnd; 17 | 18 | /************************************************ 19 | * Library functions. 20 | ************************************************/ 21 | bool FBLibrary::LibInit() 22 | { 23 | GEngineLoop.PreInit(TEXT("MobuLiveLinkPlugin -Messaging")); 24 | 25 | // ensure target platform manager is referenced early as it must be created on the main thread 26 | GetTargetPlatformManager(); 27 | 28 | ProcessNewlyLoadedUObjects(); 29 | 30 | // Tell the module manager that it may now process newly-loaded UObjects when new C++ modules are loaded 31 | FModuleManager::Get().StartProcessingNewlyLoadedObjects(); 32 | FModuleManager::Get().LoadModule(TEXT("UdpMessaging")); 33 | 34 | IPluginManager::Get().LoadModulesForEnabledPlugins(ELoadingPhase::PreDefault); 35 | IPluginManager::Get().LoadModulesForEnabledPlugins(ELoadingPhase::Default); 36 | IPluginManager::Get().LoadModulesForEnabledPlugins(ELoadingPhase::PostDefault); 37 | 38 | FBTrace("MobuLiveLink Library Initialized\n"); 39 | return true; 40 | } 41 | 42 | bool FBLibrary::LibOpen() { return true; } 43 | bool FBLibrary::LibReady() { return true; } 44 | bool FBLibrary::LibClose() { return true; } 45 | bool FBLibrary::LibRelease(){ return true; } 46 | -------------------------------------------------------------------------------- /Source/Private/MobuLiveLinkDevice.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | //--- Class declaration 4 | #include "MobuLiveLinkDevice.h" 5 | 6 | //--- Stream object for the Editor camera 7 | #include "MobuLiveLinkStreamObjects.h" 8 | 9 | //--- Utility functions 10 | #include "MobuLiveLinkUtilities.h" 11 | 12 | //--- Allow ticking of the engine 13 | #include "Containers/Ticker.h" 14 | 15 | //--- UDP Network configuration 16 | #include "Features/IModularFeatures.h" 17 | #include "INetworkMessagingExtension.h" 18 | #include "Shared/UdpMessagingSettings.h" 19 | 20 | //--- For getting the dll location on disk 21 | #include "Windows/AllowWindowsPlatformTypes.h" 22 | #include 23 | #include "Misc/Paths.h" 24 | EXTERN_C IMAGE_DOS_HEADER __ImageBase; 25 | 26 | FString GetDeviceIconPath() 27 | { 28 | char DllPath[MAX_PATH] = { 0 }; 29 | GetModuleFileNameA((HINSTANCE)&__ImageBase, DllPath, _countof(DllPath)); 30 | 31 | FString BasePath = FPaths::GetPath(FString(DllPath)); 32 | FString FinalPath = FPaths::Combine(BasePath, FString("UE_128.png")); 33 | FBTrace("Device Icon Path - %s\n", FStringToChar(FinalPath)); 34 | 35 | return FinalPath; 36 | }; 37 | 38 | #include "Windows/HideWindowsPlatformTypes.h" 39 | 40 | //--- Device strings 41 | #define MOBULIVELINK__CLASS MOBULIVELINK__CLASSNAME 42 | #define MOBULIVELINK__NAME MOBULIVELINK__CLASSSTR 43 | #define MOBULIVELINK__LABEL "UE - LiveLink" 44 | #define MOBULIVELINK__DESC "UE - LiveLink" 45 | 46 | //--- MobuLiveLink implementation and registration 47 | FBDeviceImplementation ( MOBULIVELINK__CLASS ); 48 | FBRegisterDevice ( MOBULIVELINK__NAME, 49 | MOBULIVELINK__CLASS, 50 | MOBULIVELINK__LABEL, 51 | MOBULIVELINK__DESC, 52 | FStringToChar(GetDeviceIconPath())); 53 | 54 | /************************************************ 55 | * FMobuLiveLink Constructor. 56 | ************************************************/ 57 | bool FMobuLiveLink::FBCreate() 58 | { 59 | // Set sampling rate to Before Render 60 | CurrentSampleRate = SampleOptions.Last().Value; 61 | UpdateSampleRate(); 62 | 63 | StartLiveLink(); 64 | FBSystem().Scene->OnChange.Add(this, (FBCallback)&FMobuLiveLink::EventSceneChange); 65 | 66 | TSharedPtr EditorCamera = MakeShared(); 67 | EditorCameraObject = EditorCamera; 68 | AddStreamObject(-1, EditorCamera); 69 | 70 | LastEvaluationTime = FPlatformTime::Seconds(); 71 | TimecodeMode = ETimecodeMode::TimecodeMode_Local; 72 | 73 | FBTrace("MobuLiveLink FBCreate\n"); 74 | return true; 75 | } 76 | 77 | 78 | /************************************************ 79 | * FMobuLiveLink Destructor. 80 | ************************************************/ 81 | void FMobuLiveLink::FBDestroy() 82 | { 83 | FBSystem().Scene->OnChange.Remove(this, (FBCallback)&FMobuLiveLink::EventSceneChange); 84 | if (bShouldUpdateInRenderCallback) 85 | { 86 | FBEvaluateManager::TheOne().OnRenderingPipelineEvent.Remove(this, (FBCallback)&FMobuLiveLink::EventRenderUpdate); 87 | bShouldUpdateInRenderCallback = false; 88 | } 89 | 90 | TSharedPtr EditorCameraObjectPin = EditorCameraObject.Pin(); 91 | if (EditorCameraObjectPin.IsValid()) 92 | { 93 | LiveLinkProvider->RemoveSubject(EditorCameraObjectPin->GetSubjectName()); 94 | FBTrace("Destroying Editor Camera\n"); 95 | } 96 | 97 | StreamObjects.Empty(); 98 | StopLiveLink(); 99 | FBTrace("MobuLiveLink FBDestroy\n"); 100 | } 101 | 102 | /************************************************ 103 | * Device operation. 104 | ************************************************/ 105 | void FMobuLiveLink::UpdateSampleRate() 106 | { 107 | FBTime lPeriod; 108 | 109 | if (CurrentSampleRate == FFrameRate(-1, 1)) 110 | { 111 | if (!bShouldUpdateInRenderCallback) 112 | { 113 | // After Render 114 | FBEvaluateManager::TheOne().OnRenderingPipelineEvent.Add(this, (FBCallback)&FMobuLiveLink::EventRenderUpdate); 115 | bShouldUpdateInRenderCallback = true; 116 | } 117 | 118 | lPeriod.SetSecondDouble(1.0); 119 | } 120 | else 121 | { 122 | if (bShouldUpdateInRenderCallback) 123 | { 124 | FBEvaluateManager::TheOne().OnRenderingPipelineEvent.Remove(this, (FBCallback)&FMobuLiveLink::EventRenderUpdate); 125 | bShouldUpdateInRenderCallback = false; 126 | } 127 | 128 | lPeriod.SetSecondDouble((double)CurrentSampleRate.Denominator / (double)CurrentSampleRate.Numerator); 129 | } 130 | FBTrace("Setting Sample Rate: %f\n", lPeriod.GetSecondDouble()); 131 | SamplingPeriod = lPeriod; 132 | } 133 | 134 | 135 | /************************************************ 136 | * Device operation. 137 | ************************************************/ 138 | bool FMobuLiveLink::DeviceOperation(kDeviceOperations pOperation) 139 | { 140 | switch (pOperation) 141 | { 142 | case kOpInit: return Init(); 143 | case kOpStart: return Start(); 144 | case kOpStop: return Stop(); 145 | case kOpReset: return Reset(); 146 | case kOpDone: return Done(); 147 | } 148 | return FBDevice::DeviceOperation( pOperation ); 149 | } 150 | 151 | void FMobuLiveLink::SetDeviceInformation(const char* NewDeviceInformation) 152 | { 153 | FString VersionString("v3.0.4 ("); 154 | VersionString += __DATE__; 155 | VersionString += ")"; 156 | HardwareVersionInfo.SetString(FStringToChar(VersionString)); 157 | Information.SetString("Epic Games, Inc."); 158 | Status.SetString(NewDeviceInformation); 159 | } 160 | 161 | 162 | /************************************************ 163 | * Initialization of device. 164 | ************************************************/ 165 | bool FMobuLiveLink::Init() 166 | { 167 | SetDeviceInformation("Status: Offline"); 168 | return true; 169 | } 170 | 171 | 172 | /************************************************ 173 | * Device is put online. 174 | ************************************************/ 175 | 176 | bool FMobuLiveLink::Start() 177 | { 178 | FBProgress lProgress; 179 | lProgress.Caption = "Setting up device"; 180 | lProgress.Text = "Setting sampling rate"; 181 | 182 | SetDeviceInformation("Status: Online"); 183 | return true; 184 | } 185 | 186 | 187 | /************************************************ 188 | * Device is stopped (offline). 189 | ************************************************/ 190 | bool FMobuLiveLink::Stop() 191 | { 192 | FBProgress lProgress; 193 | lProgress.Caption = "Shutting down device"; 194 | 195 | SetDeviceInformation("Status: Offline"); 196 | return false; 197 | } 198 | 199 | 200 | /************************************************ 201 | * Removal of device. 202 | ************************************************/ 203 | bool FMobuLiveLink::Done() 204 | { 205 | return false; 206 | } 207 | 208 | 209 | /************************************************ 210 | * Reset of device. 211 | ************************************************/ 212 | bool FMobuLiveLink::Reset() 213 | { 214 | Stop(); 215 | return Start(); 216 | } 217 | 218 | /************************************************ 219 | * Device Evaluation. 220 | ************************************************/ 221 | bool FMobuLiveLink::DeviceEvaluationNotify(kTransportMode pMode, FBEvaluateInfo* pEvaluateInfo) 222 | { 223 | if (!bShouldUpdateInRenderCallback) 224 | { 225 | UpdateStream(); 226 | } 227 | return true; 228 | } 229 | 230 | void FMobuLiveLink::EventRenderUpdate(HISender Sender, HKEvent Event) 231 | { 232 | FBGlobalEvalCallbackTiming EventTiming = ((FBEventEvalGlobalCallback)Event).GetTiming(); 233 | if (EventTiming == FBSDKNamespace::kFBGlobalEvalCallbackBeforeRender && this->Online) 234 | { 235 | UpdateStream(); 236 | 237 | // Count samples here since we aren't doing it in DeviceIONotify for render callback usage 238 | AckOneSampleReceived(); 239 | } 240 | } 241 | 242 | void FMobuLiveLink::UpdateStream() 243 | { 244 | mCleanUpLock.Lock(); 245 | 246 | TickCoreTicker(); 247 | 248 | FLiveLinkWorldTime WorldTime; 249 | FQualifiedFrameTime QualifiedFrameTime = MobuUtilities::GetSceneTimecode(GetTimecodeMode()); 250 | 251 | 252 | if (IsDirty()) 253 | { 254 | UpdateStreamObjects(); 255 | } 256 | for (TPair>& MapPair : StreamObjects) 257 | { 258 | const TSharedPtr& StreamObject = MapPair.Value; 259 | StreamObject->UpdateSubjectFrame(LiveLinkProvider, WorldTime, QualifiedFrameTime); 260 | } 261 | 262 | mCleanUpLock.Unlock(); 263 | } 264 | 265 | 266 | /************************************************ 267 | * Real-Time Synchronous Device IO. 268 | ************************************************/ 269 | void FMobuLiveLink::DeviceIONotify(kDeviceIOs pAction,FBDeviceNotifyInfo &pDeviceNotifyInfo) 270 | { 271 | // If we are tied to the render callback, then we don't want to count samples here 272 | if (bShouldUpdateInRenderCallback) 273 | { 274 | return; 275 | } 276 | 277 | FBTime lEvalTime; 278 | switch (pAction) 279 | { 280 | // Output devices 281 | case kIOPlayModeWrite: 282 | case kIOStopModeWrite: 283 | { 284 | AckOneSampleSent(); 285 | } 286 | break; 287 | // Input devices 288 | case kIOStopModeRead: 289 | case kIOPlayModeRead: 290 | { 291 | AckOneSampleReceived(); 292 | } 293 | break; 294 | } 295 | } 296 | 297 | int32 FMobuLiveLink::GetCurrentSampleRateIndex() 298 | { 299 | int32 CurrentSampleIdx = 0; 300 | for (int SampleIdx = 0; SampleIdx < SampleOptions.Num(); ++SampleIdx) 301 | { 302 | const FFrameRate& TestSampleRate = SampleOptions[SampleIdx].Value; 303 | if (CurrentSampleRate == TestSampleRate) 304 | { 305 | CurrentSampleIdx = SampleIdx; 306 | break; 307 | } 308 | } 309 | return CurrentSampleIdx; 310 | } 311 | 312 | //--- FBX load/save tags 313 | #define MOBULIVELINK_FBX_DATA_V4 "MobuLiveLinkFBXDataV4" 314 | #define MOBULIVELINK_FBX_DATA "MobuLiveLinkFBXDataV5" 315 | 316 | /************************************************ 317 | * Save Format: 318 | * Str Provider Name 319 | * Int Stream editor camera 320 | * Int use local or system clock to produce timecode 321 | * Int sample rate index 322 | * Str Unicast Endpoint 323 | * Int Number of object 324 | * Str Root Name 325 | * Str Subject Name 326 | * Int Stream mode index 327 | * Int Active status 328 | * Int Animatable status 329 | * Int Number of Static Endpoints 330 | * Str Static Endpoint 331 | ************************************************/ 332 | 333 | /************************************************ 334 | * Store data in FBX. 335 | ************************************************/ 336 | bool FMobuLiveLink::FbxStore(FBFbxObject* pFbxObject, kFbxObjectStore pStoreWhat) 337 | { 338 | if (pStoreWhat & kAttributes) 339 | { 340 | pFbxObject->FieldWriteBegin(MOBULIVELINK_FBX_DATA); 341 | { 342 | FBTrace("FbxStore started\n"); 343 | // Provider Name 344 | pFbxObject->FieldWriteC(FStringToChar(GetProviderName())); 345 | 346 | // Stream editor camera 347 | pFbxObject->FieldWriteI(IsEditorCameraStreamed()); 348 | 349 | // Use Local or System time for timecode 350 | pFbxObject->FieldWriteI(GetTimecodeModeAsInt()); 351 | 352 | // Sample rate index 353 | pFbxObject->FieldWriteI(GetCurrentSampleRateIndex()); 354 | 355 | // NumberOfObjects 356 | int NumberOfObjects = 0; 357 | for (TPair>& MapPair : StreamObjects) 358 | { 359 | const FString StreamObjectRootName = MapPair.Value->GetRootName(); 360 | if (StreamObjectRootName.Len() > 0) 361 | { 362 | ++NumberOfObjects; 363 | } 364 | } 365 | pFbxObject->FieldWriteI(NumberOfObjects); 366 | 367 | for (TPair>& MapPair : StreamObjects) 368 | { 369 | const FString StreamObjectRootName = MapPair.Value->GetRootName(); 370 | if (StreamObjectRootName.Len() > 0) 371 | { 372 | const FName StreamObjectSubjectName = MapPair.Value->GetSubjectName(); 373 | const int32 StreamObjectStreamingMode = MapPair.Value->GetStreamingMode(); 374 | const int32 StreamObjectActive = MapPair.Value->GetActiveStatus(); 375 | const int32 StreamAnimatableActive = MapPair.Value->GetSendAnimatableStatus(); 376 | 377 | pFbxObject->FieldWriteC(TCHAR_TO_UTF8(*StreamObjectRootName)); 378 | pFbxObject->FieldWriteC(TCHAR_TO_UTF8(*StreamObjectSubjectName.ToString())); 379 | pFbxObject->FieldWriteI(StreamObjectStreamingMode); 380 | pFbxObject->FieldWriteI(StreamObjectActive); 381 | pFbxObject->FieldWriteI(StreamAnimatableActive); 382 | } 383 | } 384 | 385 | // Unicast endpoint 386 | pFbxObject->FieldWriteC(FStringToChar(GetUnicastEndpoint())); 387 | 388 | // Static endpoints 389 | pFbxObject->FieldWriteI(StaticEndpoints.Num()); 390 | for (const FString& Endpoint: StaticEndpoints) 391 | { 392 | pFbxObject->FieldWriteC(FStringToChar(Endpoint)); 393 | } 394 | 395 | pFbxObject->FieldWriteEnd(); 396 | FBTrace("FbxStore finished\n"); 397 | } 398 | } 399 | return true; 400 | } 401 | 402 | /************************************************ 403 | * Retrieve data from FBX. 404 | ************************************************/ 405 | bool FMobuLiveLink::FbxRetrieve(FBFbxObject* FbxObject, kFbxObjectStore StoreWhat) 406 | { 407 | if (StoreWhat & kAttributes) 408 | { 409 | if (FbxObject->FieldReadBegin(MOBULIVELINK_FBX_DATA_V4)) 410 | { 411 | FBTrace("FbxRetrieve started\n"); 412 | FbxRetrieveV4(FbxObject, StoreWhat); 413 | 414 | FbxObject->FieldReadEnd(); 415 | 416 | SetRefreshUI(true); 417 | FBTrace("FbxRetrieve finished\n"); 418 | } 419 | else if (FbxObject->FieldReadBegin(MOBULIVELINK_FBX_DATA)) 420 | { 421 | FBTrace("FbxRetrieve started\n"); 422 | FbxRetrieveV4(FbxObject, StoreWhat); 423 | 424 | // Unicast endpoint 425 | SetUnicastEndpoint(CharToFString(FbxObject->FieldReadC())); 426 | 427 | // Static endpoints 428 | const int StaticEndpointNum = FbxObject->FieldReadI(); 429 | for (int i = 0; i < StaticEndpointNum; ++i) 430 | { 431 | AddStaticEndpoint(CharToFString(FbxObject->FieldReadC())); 432 | } 433 | FbxObject->FieldReadEnd(); 434 | 435 | SetRefreshUI(true); 436 | FBTrace("FbxRetrieve finished\n"); 437 | } 438 | } 439 | return true; 440 | } 441 | 442 | void FMobuLiveLink::FbxRetrieveV4(FBFbxObject* pFbxObject, kFbxObjectStore pStoreWhat) 443 | { 444 | // Provider Name 445 | SetProviderName(CharToFString(pFbxObject->FieldReadC())); 446 | 447 | // Stream editor camera 448 | const bool bStreamEditorCamera = pFbxObject->FieldReadI() != 0; 449 | SetEditorCameraStreamed(bStreamEditorCamera); 450 | 451 | // Use Local or System time for timecode 452 | const int32 ReadTimecodeModeInt = pFbxObject->FieldReadI(); 453 | SetTimecodeModeFromInt(ReadTimecodeModeInt); 454 | 455 | // Sample rate index 456 | const int32 CurrentSampleIndex = pFbxObject->FieldReadI(); 457 | if (CurrentSampleIndex > 0 && CurrentSampleIndex < SampleOptions.Num()) 458 | { 459 | CurrentSampleRate = SampleOptions[CurrentSampleIndex].Value; 460 | UpdateSampleRate(); 461 | } 462 | 463 | // NumberOfObjects 464 | const int32 NumberOfObjects = pFbxObject->FieldReadI(); 465 | 466 | for (int32 i = 0; i < NumberOfObjects; ++i) 467 | { 468 | FBComponentList FoundModels; 469 | FString StreamObjectRootName(pFbxObject->FieldReadC()); 470 | FBFindObjectsByName(TCHAR_TO_UTF8(*StreamObjectRootName), FoundModels, true, false); 471 | 472 | if (FoundModels.GetCount() > 0) 473 | { 474 | FBModel* FoundFBModel = (FBModel*)FoundModels[0]; 475 | TSharedPtr FoundStreamObject = StreamObjectManagement::FBModelToStreamObject(FoundFBModel); 476 | 477 | FName SubjectName(pFbxObject->FieldReadC()); 478 | int32 StreamingMode = pFbxObject->FieldReadI(); 479 | 480 | bool bObjectActive = pFbxObject->FieldReadI() != 0; 481 | bool bStreamOAnimatableActive = pFbxObject->FieldReadI() != 0; 482 | 483 | FoundStreamObject->UpdateSubjectName(SubjectName); 484 | FoundStreamObject->UpdateStreamingMode(StreamingMode); 485 | FoundStreamObject->UpdateActiveStatus(bObjectActive); 486 | FoundStreamObject->UpdateSendAnimatableStatus(bStreamOAnimatableActive); 487 | 488 | // Add the object last so the SubjectName is correct 489 | AddStreamObject(GetNextUID(), FoundStreamObject); 490 | } 491 | else 492 | { 493 | pFbxObject->FieldReadC(); 494 | pFbxObject->FieldReadI(); 495 | pFbxObject->FieldReadI(); 496 | pFbxObject->FieldReadI(); 497 | } 498 | } 499 | } 500 | 501 | 502 | void FMobuLiveLink::StartLiveLink() 503 | { 504 | if (LiveLinkProvider != nullptr) 505 | { 506 | FBTrace("Live Link Provider '%s' already started!\n", FStringToChar(GetProviderName())); 507 | return; 508 | } 509 | 510 | LiveLinkProvider = ILiveLinkProvider::CreateLiveLinkProvider(GetProviderName()); 511 | 512 | UpdateStreamObjects(); 513 | 514 | FBTrace("Live Link Provider '%s' started!\n", FStringToChar(GetProviderName())); 515 | } 516 | 517 | 518 | void FMobuLiveLink::StopLiveLink() 519 | { 520 | TickCoreTicker(); 521 | if (LiveLinkProvider.IsValid()) 522 | { 523 | FBTrace("LiveLinkProvider References: %d\n", LiveLinkProvider.GetSharedReferenceCount()); 524 | LiveLinkProvider = nullptr; 525 | FBTrace("Deleting Live Link\n"); 526 | } 527 | FBTrace("Live Link Provider '%s' stopped!\n", FStringToChar(GetProviderName())); 528 | } 529 | 530 | void FMobuLiveLink::EventSceneChange(HISender Sender, HKEvent Event) 531 | { 532 | FBEventSceneChange SceneChangeEvent = Event; 533 | switch (SceneChangeEvent.Type) 534 | { 535 | case kFBSceneChangeSelect: 536 | case kFBSceneChangeUnselect: 537 | case kFBSceneChangeReSelect: 538 | case kFBSceneChangeFocus: 539 | case kFBSceneChangeSoftSelect: 540 | case kFBSceneChangeSoftUnselect: 541 | case kFBSceneChangeHardSelect: 542 | case kFBSceneChangeTransactionBegin: 543 | case kFBSceneChangeTransactionEnd: 544 | return; 545 | case kFBSceneChangeLoadBegin: 546 | // Crashes if you try and stream while loading a new file 547 | DeviceOperation(FBDevice::kOpStop); 548 | return; 549 | default: 550 | SetDirty(true); 551 | break; 552 | } 553 | 554 | } 555 | 556 | void FMobuLiveLink::AddStreamObject(int32 NewUID, StreamObjectPtr NewObject) 557 | { 558 | if (NewObject->IsValid()) 559 | { 560 | FBTrace("Added new Subject '%s' to StreamObjects\n", FStringToChar(NewObject->GetSubjectName().ToString())); 561 | StreamObjects.Emplace(NewUID, NewObject); 562 | 563 | SetDirty(true); 564 | } 565 | } 566 | 567 | void FMobuLiveLink::RemoveStreamObject(int32 DeletionKey, StreamObjectPtr RemoveObject) 568 | { 569 | FBTrace("Removed Subject '%s' from StreamObjects\n", FStringToChar(RemoveObject->GetSubjectName().ToString())); 570 | StreamObjects.Remove(DeletionKey); 571 | LiveLinkProvider->RemoveSubject(RemoveObject->GetSubjectName()); 572 | 573 | SetDirty(true); 574 | } 575 | 576 | void FMobuLiveLink::ChangeSubjectName(StreamObjectPtr ObjectPtr, const char* NewSubjectNameStr) 577 | { 578 | if (ObjectPtr->GetSubjectName() != NewSubjectNameStr) 579 | { 580 | FBTrace("Subject Name changed from '%s' to '%s'\n", FStringToChar(ObjectPtr->GetSubjectName().ToString()), NewSubjectNameStr); 581 | LiveLinkProvider->RemoveSubject(ObjectPtr->GetSubjectName()); 582 | ObjectPtr->UpdateSubjectName(FName(NewSubjectNameStr)); 583 | 584 | SetDirty(true); 585 | } 586 | } 587 | 588 | void FMobuLiveLink::UpdateStreamObjects() 589 | { 590 | decltype(StreamObjects) StreamObjectsToRemove; 591 | 592 | for (TPair>& MapPair : StreamObjects) 593 | { 594 | const TSharedPtr& StreamObject = MapPair.Value; 595 | if (StreamObject->IsValid()) 596 | { 597 | StreamObject->Refresh(LiveLinkProvider); 598 | } 599 | else 600 | { 601 | StreamObjectsToRemove.Add(MapPair); 602 | } 603 | } 604 | 605 | for (const auto& MapPair : StreamObjectsToRemove) 606 | { 607 | RemoveStreamObject(MapPair.Key, MapPair.Value); 608 | } 609 | 610 | SetDirty(false); 611 | SetRefreshUI(true); 612 | } 613 | 614 | void FMobuLiveLink::TickCoreTicker() 615 | { 616 | double CurrentTime = FPlatformTime::Seconds(); 617 | FTSTicker::GetCoreTicker().Tick(CurrentTime - LastEvaluationTime); 618 | LastEvaluationTime = CurrentTime; 619 | } 620 | 621 | int32 FMobuLiveLink::GetNextUID() 622 | { 623 | return NextUID++; 624 | } 625 | 626 | bool FMobuLiveLink::IsEditorCameraStreamed() const 627 | { 628 | TSharedPtr EditorCameraObjectPin = EditorCameraObject.Pin(); 629 | if (EditorCameraObjectPin.IsValid()) 630 | { 631 | return EditorCameraObjectPin->GetActiveStatus(); 632 | } 633 | return false; 634 | } 635 | 636 | void FMobuLiveLink::SetEditorCameraStreamed(bool bStream) 637 | { 638 | TSharedPtr EditorCameraObjectPin = EditorCameraObject.Pin(); 639 | if (EditorCameraObjectPin.IsValid()) 640 | { 641 | EditorCameraObjectPin->UpdateActiveStatus(bStream); 642 | } 643 | } 644 | 645 | ETimecodeMode FMobuLiveLink::GetTimecodeMode() const 646 | { 647 | return TimecodeMode; 648 | } 649 | 650 | int32 FMobuLiveLink::GetTimecodeModeAsInt() const 651 | { 652 | return (int32)TimecodeMode; 653 | } 654 | 655 | void FMobuLiveLink::SetTimecodeMode(ETimecodeMode InTimecodeMode) 656 | { 657 | TimecodeMode = InTimecodeMode; 658 | } 659 | 660 | void FMobuLiveLink::SetTimecodeModeFromInt(int32 InTimecodeModeInt) 661 | { 662 | switch (InTimecodeModeInt) 663 | { 664 | case 2: TimecodeMode = ETimecodeMode::TimecodeMode_Reference; 665 | break; 666 | 667 | case 1: TimecodeMode = ETimecodeMode::TimecodeMode_System; 668 | break; 669 | 670 | // Intentional fallthrough 671 | case 0: 672 | default: TimecodeMode = ETimecodeMode::TimecodeMode_Local; 673 | break; 674 | } 675 | } 676 | 677 | void FMobuLiveLink::SetProviderName(const FString& NewValue) 678 | { 679 | if (NewValue != GetProviderName()) 680 | { 681 | StopLiveLink(); 682 | CurrentProviderName = NewValue; 683 | StartLiveLink(); 684 | 685 | SetRefreshUI(true); 686 | } 687 | } 688 | 689 | FString FMobuLiveLink::GetUnicastEndpoint() const 690 | { 691 | if (IModularFeatures::Get().IsModularFeatureAvailable(INetworkMessagingExtension::ModularFeatureName)) 692 | { 693 | UUdpMessagingSettings* Settings = GetMutableDefault(); 694 | return Settings->UnicastEndpoint; 695 | } 696 | 697 | return TEXT("0.0.0.0:0"); 698 | } 699 | 700 | void FMobuLiveLink::SetUnicastEndpoint(const FString& InEndpoint) 701 | { 702 | if (InEndpoint != GetUnicastEndpoint()) 703 | { 704 | if (IModularFeatures::Get().IsModularFeatureAvailable(INetworkMessagingExtension::ModularFeatureName)) 705 | { 706 | StopLiveLink(); 707 | UUdpMessagingSettings* Settings = GetMutableDefault(); 708 | Settings->UnicastEndpoint = InEndpoint; 709 | INetworkMessagingExtension& NetworkExtension = IModularFeatures::Get().GetModularFeature(INetworkMessagingExtension::ModularFeatureName); 710 | NetworkExtension.RestartServices(); 711 | 712 | StartLiveLink(); 713 | SetRefreshUI(true); 714 | } 715 | } 716 | } 717 | 718 | bool FMobuLiveLink::AddStaticEndpoint(const FString& InEndpoint) 719 | { 720 | if (IModularFeatures::Get().IsModularFeatureAvailable(INetworkMessagingExtension::ModularFeatureName)) 721 | { 722 | INetworkMessagingExtension& NetworkExtension = IModularFeatures::Get().GetModularFeature(INetworkMessagingExtension::ModularFeatureName); 723 | NetworkExtension.AddEndpoint(InEndpoint); 724 | StaticEndpoints.Push(InEndpoint); 725 | SetRefreshUI(true); 726 | return true; 727 | } 728 | return false; 729 | } 730 | 731 | bool FMobuLiveLink::RemoveStaticEndpoint(const FString& InEndpoint) 732 | { 733 | if (IModularFeatures::Get().IsModularFeatureAvailable(INetworkMessagingExtension::ModularFeatureName)) 734 | { 735 | INetworkMessagingExtension& NetworkExtension = IModularFeatures::Get().GetModularFeature(INetworkMessagingExtension::ModularFeatureName); 736 | NetworkExtension.RemoveEndpoint(InEndpoint); 737 | StaticEndpoints.RemoveSingle(InEndpoint); 738 | SetRefreshUI(true); 739 | return true; 740 | } 741 | return false; 742 | } 743 | -------------------------------------------------------------------------------- /Source/Private/MobuLiveLinkLayout.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | #include "MobuLiveLinkLayout.h" 4 | #include "MobuLiveLinkStreamObjects.h" 5 | #include "MobuLiveLinkUtilities.h" 6 | #include 7 | #include 8 | 9 | #define MOBULIVELINK__LAYOUT FMobuLiveLinkLayout 10 | 11 | FBDeviceLayoutImplementation(MOBULIVELINK__LAYOUT); 12 | FBRegisterDeviceLayout(MOBULIVELINK__LAYOUT, 13 | MOBULIVELINK__CLASSSTR, 14 | FB_DEFAULT_SDK_ICON); 15 | 16 | const char MainLayoutName[] = "MainLayout"; 17 | 18 | // Removes all characters (in place) that are neither punctuation nor alphanumeric 19 | void StripWhitespace(char* InStr) 20 | { 21 | const size_t Length = strlen(InStr); 22 | if (Length == 0) 23 | { 24 | return; 25 | } 26 | 27 | size_t pos = 0; 28 | for (size_t i=0; i Allow any amount of leading whitespace 43 | // ([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5]) -> Match single ip block, min 0, max 255 44 | // \\. -> Match dot 45 | // {3} -> Match 3 ip blocks with trailing dots 46 | // ([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5]) -> Match last ip block w/o trailing dot 47 | // \\s*\\:\\s* -> Match colon with any amount of surrounding whitespace 48 | // \\d{1,5} -> Match any number between 1 and 5 digits for the port 49 | // 50 | // to verify/test: https://regex101.com/r/wdePf9/1 51 | 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})$"); 52 | return std::regex_match(InIpAddress, IpRegex); 53 | } 54 | 55 | bool FMobuLiveLinkLayout::FBCreate() 56 | { 57 | FBTrace("Creating UI\n"); 58 | 59 | // Get a handle on the device. 60 | LiveLinkDevice = (FMobuLiveLink*)(FBDevice*)Device; 61 | 62 | FBPropertyPublish(this, ObjectSelection, "ObjectSelection", nullptr, nullptr); 63 | ObjectSelection.SetFilter(FBModel::GetInternalClassId()); 64 | ObjectSelection.SetSingleConnect(false); 65 | 66 | UICreate(); 67 | UIConfigure(); 68 | UIReset(); 69 | 70 | System.OnUIIdle.Add(this, (FBCallback)&FMobuLiveLinkLayout::EventUIIdle); 71 | return true; 72 | } 73 | 74 | 75 | void FMobuLiveLinkLayout::FBDestroy() 76 | { 77 | // Remove device & system callbacks 78 | FBTrace("Destroying UI\n"); 79 | 80 | System.OnUIIdle.Remove(this, (FBCallback)&FMobuLiveLinkLayout::EventUIIdle); 81 | } 82 | 83 | void FMobuLiveLinkLayout::UICreate() 84 | { 85 | // default spacing, width, height in pixels 86 | const int S = 4; 87 | const int W = 110; 88 | const int H = 18; 89 | 90 | const char* TabLayoutName = "TabPanel"; 91 | 92 | AddRegion(TabLayoutName, TabLayoutName, 93 | S, kFBAttachLeft, "", 1.0, 94 | S, kFBAttachTop, "", 1.0, 95 | -S, kFBAttachRight, "", 1.0, 96 | 25, kFBAttachNone, NULL, 1.0); 97 | 98 | // Create regions 99 | AddRegion(MainLayoutName, MainLayoutName, 100 | 0, kFBAttachLeft, TabLayoutName, 1.00, 101 | 0, kFBAttachBottom, TabLayoutName, 1.00, 102 | 0, kFBAttachRight, TabLayoutName, 1.00, 103 | -S, kFBAttachBottom, nullptr, 1.00); 104 | 105 | // Assign regions 106 | SetControl(TabLayoutName, TabPanel); 107 | SetControl(MainLayoutName, Layouts[0]); 108 | 109 | UICreateLayout0(); 110 | UICreateLayout1(); 111 | } 112 | 113 | void FMobuLiveLinkLayout::UICreateLayout0() 114 | { 115 | const int S = 4; 116 | const int W = 110; 117 | const int H = 18; 118 | 119 | const char ObjectSelectorLabelName[] = "ObjectSelectorLabel"; 120 | const char ObjectSelectorName[] = "ObjectSelector"; 121 | const char AddToStreamButtonName[] = "AddToStreamButton"; 122 | const char RemoveFromStreamButtonName[] = "RemoveFromStreamButton"; 123 | const char StreamEditorCameraButtonName[] = "StreamEditorCameraButton"; 124 | const char StreamSpreadName[] = "StreamSpread"; 125 | 126 | { 127 | Layouts[0].AddRegion(ObjectSelectorLabelName, ObjectSelectorLabelName, 128 | S, kFBAttachLeft, nullptr, 1.00, 129 | S, kFBAttachTop, nullptr, 1.00, 130 | W * 0.85f, kFBAttachNone, nullptr, 1.00, 131 | H, kFBAttachNone, nullptr, 1.00); 132 | 133 | Layouts[0].AddRegion(ObjectSelectorName, ObjectSelectorName, 134 | 0, kFBAttachRight, ObjectSelectorLabelName, 1.00, 135 | 0, kFBAttachTop, ObjectSelectorLabelName, 1.00, 136 | W * 2.0f, kFBAttachNone, nullptr, 1.00, 137 | H, kFBAttachNone, nullptr, 1.00); 138 | 139 | Layouts[0].AddRegion(AddToStreamButtonName, AddToStreamButtonName, 140 | S, kFBAttachRight, ObjectSelectorName, 1.00, 141 | 0, kFBAttachTop, ObjectSelectorName, 1.00, 142 | W * 0.75f, kFBAttachNone, nullptr, 1.00, 143 | H, kFBAttachNone, nullptr, 1.00); 144 | 145 | Layouts[0].AddRegion(RemoveFromStreamButtonName, RemoveFromStreamButtonName, 146 | S, kFBAttachRight, AddToStreamButtonName, 1.00, 147 | 0, kFBAttachTop, AddToStreamButtonName, 1.00, 148 | W * 0.75f, kFBAttachNone, nullptr, 1.00, 149 | H, kFBAttachNone, nullptr, 1.00); 150 | 151 | Layouts[0].AddRegion(StreamEditorCameraButtonName, StreamEditorCameraButtonName, 152 | S * 4.0f, kFBAttachRight, RemoveFromStreamButtonName, 1.00, 153 | 0, kFBAttachTop, RemoveFromStreamButtonName, 1.00, 154 | W * 1.35f, kFBAttachNone, nullptr, 1.00, 155 | H, kFBAttachNone, nullptr, 1.00); 156 | } 157 | 158 | { 159 | Layouts[0].AddRegion(StreamSpreadName, StreamSpreadName, 160 | S, kFBAttachLeft, nullptr, 1.00, 161 | S, kFBAttachBottom, ObjectSelectorLabelName, 1.00, 162 | -S, kFBAttachRight, nullptr, 1.00, 163 | -S, kFBAttachBottom, nullptr, 1.00); 164 | } 165 | 166 | Layouts[0].SetControl(ObjectSelectorLabelName, ObjectSelectorLabel); 167 | Layouts[0].SetControl(ObjectSelectorName, ObjectSelector); 168 | Layouts[0].SetControl(AddToStreamButtonName, AddToStreamButton); 169 | Layouts[0].SetControl(RemoveFromStreamButtonName, RemoveFromStreamButton); 170 | Layouts[0].SetControl(StreamEditorCameraButtonName, StreamEditorCameraButton); 171 | 172 | Layouts[0].SetControl(StreamSpreadName, StreamSpread); 173 | } 174 | 175 | void FMobuLiveLinkLayout::UICreateLayout1() 176 | { 177 | const int S = 8; 178 | const int W = 110; 179 | const int H = 24; 180 | 181 | const char SampleRateLabelName[] = "SampleRateLabel"; 182 | const char SampleRateListName[] = "SampleRateList"; 183 | const char ProviderNameLabelName[] = "ProviderNameLabel"; 184 | const char ProviderNameTextName[] = "ProviderNameText"; 185 | const char ProviderNameEditButtonName[] = "ProviderNameEditButton"; 186 | const char TimecodeModeListLabelName[] = "TimecodeModeListLabel"; 187 | const char TimecodeModeListName[] = "TimecodeModeList"; 188 | const char UnicastEndpointLabelName[] = "UnicastEndpointLabel"; 189 | const char UnicastEndpointAddressName[] = "UnicastEndpointAddress"; 190 | const char UnicastEndpointEditButtonName[] = "UnicastEndpointEditButton"; 191 | const char StaticEndpointLabelName[] = "StaticEndpointLabel"; 192 | const char StaticEndpointAddressName[] = "StaticEndpointAddress"; 193 | const char StaticEndpointAddButtonName[] = "StaticEndpointAddButton"; 194 | const char StaticEndpointRemoveButtonName[] = "StaticEndpointRemoveButton"; 195 | 196 | { 197 | Layouts[1].AddRegion(SampleRateLabelName, SampleRateLabelName, 198 | S, kFBAttachLeft, nullptr, 1.00, 199 | S, kFBAttachTop, nullptr, 1.00, 200 | W, kFBAttachNone, nullptr, 1.00, 201 | H, kFBAttachNone, nullptr, 1.00); 202 | 203 | Layouts[1].AddRegion(SampleRateListName, SampleRateListName, 204 | S, kFBAttachRight, SampleRateLabelName, 1.00, 205 | 0, kFBAttachTop, SampleRateLabelName, 1.00, 206 | W, kFBAttachNone, nullptr, 1.00, 207 | H, kFBAttachNone, nullptr, 1.00); 208 | } 209 | { 210 | Layouts[1].AddRegion(TimecodeModeListLabelName, TimecodeModeListLabelName, 211 | S, kFBAttachLeft, nullptr, 1.00, 212 | H, kFBAttachTop, SampleRateLabelName, 1.00, 213 | W, kFBAttachNone, nullptr, 1.00, 214 | H, kFBAttachNone, nullptr, 1.00); 215 | 216 | Layouts[1].AddRegion(TimecodeModeListName, TimecodeModeListName, 217 | S, kFBAttachRight, TimecodeModeListLabelName, 1.00, 218 | 0, kFBAttachTop, TimecodeModeListLabelName, 1.00, 219 | W, kFBAttachNone, nullptr, 1.00, 220 | H, kFBAttachNone, nullptr, 1.00); 221 | } 222 | { 223 | Layouts[1].AddRegion(ProviderNameLabelName, ProviderNameLabelName, 224 | S, kFBAttachLeft, nullptr, 1.00, 225 | H, kFBAttachTop, TimecodeModeListLabelName, 1.00, 226 | W, kFBAttachNone, nullptr, 1.00, 227 | H, kFBAttachNone, nullptr, 1.00); 228 | 229 | Layouts[1].AddRegion(ProviderNameTextName, ProviderNameTextName, 230 | S, kFBAttachRight, ProviderNameLabelName, 1.00, 231 | 0, kFBAttachTop, ProviderNameLabelName, 1.00, 232 | W, kFBAttachNone, nullptr, 1.00, 233 | H, kFBAttachNone, nullptr, 1.00); 234 | 235 | Layouts[1].AddRegion(ProviderNameEditButtonName, ProviderNameEditButtonName, 236 | S, kFBAttachRight, ProviderNameTextName, 1.00, 237 | 0, kFBAttachTop, ProviderNameTextName, 1.00, 238 | W, kFBAttachNone, nullptr, 1.00, 239 | H, kFBAttachNone, nullptr, 1.00); 240 | } 241 | { 242 | Layouts[1].AddRegion(UnicastEndpointLabelName, UnicastEndpointLabelName, 243 | S, kFBAttachLeft, nullptr, 1.00, 244 | H, kFBAttachTop, ProviderNameLabelName, 1.00, 245 | W, kFBAttachNone, nullptr, 1.00, 246 | H, kFBAttachNone, nullptr, 1.00); 247 | 248 | Layouts[1].AddRegion(UnicastEndpointAddressName, UnicastEndpointAddressName, 249 | S, kFBAttachRight, UnicastEndpointLabelName, 1.00, 250 | 0, kFBAttachTop, UnicastEndpointLabelName, 1.00, 251 | W, kFBAttachNone, nullptr, 1.00, 252 | H, kFBAttachNone, nullptr, 1.00); 253 | 254 | Layouts[1].AddRegion(UnicastEndpointEditButtonName, UnicastEndpointEditButtonName, 255 | S, kFBAttachRight, UnicastEndpointAddressName, 1.00, 256 | 0, kFBAttachTop, UnicastEndpointAddressName, 1.00, 257 | W, kFBAttachNone, nullptr, 1.00, 258 | H, kFBAttachNone, nullptr, 1.00); 259 | } 260 | { 261 | Layouts[1].AddRegion(StaticEndpointLabelName, StaticEndpointLabelName, 262 | S, kFBAttachLeft, nullptr, 1.00, 263 | H, kFBAttachTop, UnicastEndpointLabelName, 1.00, 264 | W, kFBAttachNone, nullptr, 1.00, 265 | H, kFBAttachNone, nullptr, 1.00); 266 | 267 | Layouts[1].AddRegion(StaticEndpointAddressName, StaticEndpointAddressName, 268 | S, kFBAttachRight, StaticEndpointLabelName, 1.00, 269 | 0, kFBAttachTop, StaticEndpointLabelName, 1.00, 270 | W, kFBAttachNone, nullptr, 1.00, 271 | H * 3, kFBAttachNone, nullptr, 1.00); 272 | 273 | Layouts[1].AddRegion(StaticEndpointAddButtonName, StaticEndpointAddButtonName, 274 | S, kFBAttachRight, StaticEndpointAddressName, 1.00, 275 | 0, kFBAttachTop, StaticEndpointAddressName, 1.00, 276 | W, kFBAttachNone, nullptr, 1.00, 277 | H, kFBAttachNone, nullptr, 1.00); 278 | 279 | Layouts[1].AddRegion(StaticEndpointRemoveButtonName, StaticEndpointRemoveButtonName, 280 | S, kFBAttachRight, StaticEndpointAddressName, 1.00, 281 | H, kFBAttachTop, StaticEndpointAddButtonName, 1.00, 282 | W, kFBAttachNone, nullptr, 1.00, 283 | H, kFBAttachNone, nullptr, 1.00); 284 | } 285 | 286 | Layouts[1].SetControl(SampleRateLabelName, SampleRateListLabel); 287 | Layouts[1].SetControl(SampleRateListName, SampleRateList); 288 | Layouts[1].SetControl(TimecodeModeListLabelName, TimecodeModeListLabel); 289 | Layouts[1].SetControl(TimecodeModeListName, TimecodeModeList); 290 | Layouts[1].SetControl(ProviderNameLabelName, ProviderNameLabel); 291 | Layouts[1].SetControl(ProviderNameTextName, ProviderNameText); 292 | Layouts[1].SetControl(ProviderNameEditButtonName, ProviderNameEditButton); 293 | Layouts[1].SetControl(UnicastEndpointLabelName, UnicastEndpointLabel); 294 | Layouts[1].SetControl(UnicastEndpointAddressName, UnicastEndpoint); 295 | Layouts[1].SetControl(UnicastEndpointEditButtonName, UnicastEndpointEditButton); 296 | Layouts[1].SetControl(StaticEndpointLabelName, StaticEndpointLabel); 297 | Layouts[1].SetControl(StaticEndpointAddressName, StaticEndpoints); 298 | Layouts[1].SetControl(StaticEndpointAddButtonName, StaticEndpointAddButton); 299 | Layouts[1].SetControl(StaticEndpointRemoveButtonName, StaticEndpointRemoveButton); 300 | } 301 | 302 | void FMobuLiveLinkLayout::CreateSpreadColumns() 303 | { 304 | int W = 100; 305 | 306 | StreamSpread.ColumnAdd("Subject Name", 0); 307 | 308 | StreamSpread.ColumnAdd("Stream Type", 1); 309 | StreamSpread.GetColumn(1).Style = kFBCellStyleMenu; 310 | StreamSpread.GetColumn(1).Width = W * 1.2f; 311 | 312 | StreamSpread.ColumnAdd("Active", 2); 313 | StreamSpread.GetColumn(2).Style = kFBCellStyle2StatesButton; 314 | StreamSpread.GetColumn(2).Width = W * 0.5f; 315 | 316 | StreamSpread.ColumnAdd("Stream Animatable", 3); 317 | StreamSpread.GetColumn(3).Style = kFBCellStyle2StatesButton; 318 | StreamSpread.GetColumn(3).Width = W; 319 | } 320 | 321 | void FMobuLiveLinkLayout::UIConfigure() 322 | { 323 | TabPanel.Items.SetString("Stream~Settings"); 324 | TabPanel.OnChange.Add(this, (FBCallback)&FMobuLiveLinkLayout::EventTabPanelChange); 325 | 326 | SetBorder("MainLayout", kFBStandardBorder, false, true, 1, 0, 90, 0); 327 | 328 | UIConfigureLayout0(); 329 | UIConfigureLayout1(); 330 | } 331 | 332 | void FMobuLiveLinkLayout::UIConfigureLayout0() 333 | { 334 | ObjectSelector.Property = &ObjectSelection; 335 | 336 | ObjectSelectorLabel.Caption = "Subject Selector:"; 337 | 338 | AddToStreamButton.Caption = "Add"; 339 | AddToStreamButton.Justify = kFBTextJustifyCenter; 340 | AddToStreamButton.OnClick.Add(this, (FBCallback)&FMobuLiveLinkLayout::EventAddToStream); 341 | 342 | RemoveFromStreamButton.Caption = "Remove"; 343 | RemoveFromStreamButton.Justify = kFBTextJustifyCenter; 344 | RemoveFromStreamButton.OnClick.Add(this, (FBCallback)&FMobuLiveLinkLayout::EventRemoveFromStream); 345 | 346 | StreamEditorCameraButton.Caption = "Stream Viewport Camera"; 347 | StreamEditorCameraButton.Style = kFBCheckbox; 348 | StreamEditorCameraButton.State = LiveLinkDevice->IsEditorCameraStreamed(); 349 | StreamEditorCameraButton.OnClick.Add(this, (FBCallback)&FMobuLiveLinkLayout::EventStreamEditorCamera); 350 | 351 | StreamSpread.Caption = "Object Root"; 352 | StreamSpread.MultiSelect = true; 353 | 354 | CreateSpreadColumns(); 355 | 356 | StreamSpread.OnCellChange.Add(this, (FBCallback)&FMobuLiveLinkLayout::EventStreamSpreadCellChange); 357 | } 358 | 359 | void FMobuLiveLinkLayout::UIConfigureLayout1() 360 | { 361 | { 362 | TimecodeModeList.Items.Add("Local"); 363 | TimecodeModeList.Items.Add("System"); 364 | TimecodeModeList.Items.Add("Reference"); 365 | TimecodeModeList.ItemIndex = LiveLinkDevice->GetTimecodeModeAsInt(); 366 | TimecodeModeList.OnChange.Add(this, (FBCallback)&FMobuLiveLinkLayout::EventTimecodeModeChanged); 367 | TimecodeModeListLabel.Caption = "Timecode:"; 368 | } 369 | 370 | int CurrentSampleIndex = 0; 371 | for (int SampleOptionIdx = 0; SampleOptionIdx < LiveLinkDevice->SampleOptions.Num(); ++SampleOptionIdx) 372 | { 373 | const TPair& SampleOption = LiveLinkDevice->SampleOptions[SampleOptionIdx]; 374 | SampleRateList.Items.Add(FStringToChar(SampleOption.Key)); 375 | if (SampleOption.Value == LiveLinkDevice->CurrentSampleRate) 376 | { 377 | CurrentSampleIndex = SampleOptionIdx; 378 | } 379 | } 380 | SampleRateList.ItemIndex = CurrentSampleIndex; 381 | 382 | SampleRateList.OnChange.Add(this, (FBCallback)&FMobuLiveLinkLayout::EventSampleRateChange); 383 | 384 | SampleRateListLabel.Caption = "Sample Rate:"; 385 | ProviderNameLabel.Caption = "Provider Name:"; 386 | 387 | ProviderNameText.Text = FStringToChar(LiveLinkDevice->GetProviderName()); 388 | ProviderNameText.ReadOnly = true; 389 | 390 | ProviderNameEditButton.Caption = "Change"; 391 | ProviderNameEditButton.OnClick.Add(this, (FBCallback)&FMobuLiveLinkLayout::EventEditProviderNamePopup); 392 | 393 | UnicastEndpointLabel.Caption = "Unicast Endpoint:"; 394 | UnicastEndpoint.Text = FStringToChar(LiveLinkDevice->GetUnicastEndpoint()); 395 | UnicastEndpoint.ReadOnly = true; 396 | UnicastEndpointEditButton.Caption = "Change"; 397 | UnicastEndpointEditButton.OnClick.Add(this, (FBCallback)&FMobuLiveLinkLayout::EventChangeUnicastEndpoint); 398 | 399 | StaticEndpointLabel.Caption = "Static Endpoints:"; 400 | StaticEndpoints.Style = kFBVerticalList; 401 | 402 | StaticEndpointAddButton.Caption = "Add"; 403 | StaticEndpointAddButton.OnClick.Add(this, (FBCallback)&FMobuLiveLinkLayout::EventAddStaticEndpoint); 404 | 405 | StaticEndpointRemoveButton.Caption = "Remove"; 406 | StaticEndpointRemoveButton.OnClick.Add(this, (FBCallback)&FMobuLiveLinkLayout::EventRemoveStaticEndpoint); 407 | } 408 | 409 | void FMobuLiveLinkLayout::UIReset() 410 | { 411 | FBTrace("UI Reset!\n"); 412 | StreamSpread.Clear(); 413 | CreateSpreadColumns(); 414 | for (const TPair& MapPair : LiveLinkDevice->StreamObjects) 415 | { 416 | AddSpreadRowFromStreamObject(MapPair.Key, MapPair.Value); 417 | } 418 | 419 | UnicastEndpoint.Text = FStringToChar(LiveLinkDevice->GetUnicastEndpoint()); 420 | StaticEndpoints.Items.Clear(); 421 | const TArray& Endpoints = LiveLinkDevice->GetStaticEndpoints(); 422 | for (const FString& Endpoint : Endpoints) 423 | { 424 | StaticEndpoints.Items.Add(FStringToChar(Endpoint)); 425 | } 426 | 427 | LiveLinkDevice->SetRefreshUI(false); 428 | } 429 | 430 | void FMobuLiveLinkLayout::EventUIIdle(HISender Sender, HKEvent Event) 431 | { 432 | if (LiveLinkDevice->IsDirty()) 433 | { 434 | LiveLinkDevice->UpdateStreamObjects(); 435 | } 436 | if (LiveLinkDevice->ShouldRefreshUI()) 437 | { 438 | UIReset(); 439 | } 440 | } 441 | 442 | void FMobuLiveLinkLayout::AddSpreadRowFromStreamObject(int32 NewRowKey, StreamObjectPtr Object) 443 | { 444 | // Check whether the Object should be shown 445 | if (!Object->ShouldShowInUI()) return; 446 | 447 | const FString RootName = Object->GetRootName(); 448 | 449 | StreamSpread.RowAdd(FStringToChar(RootName), NewRowKey); 450 | 451 | StreamSpread.SetCell(NewRowKey, 0, FStringToChar(Object->GetSubjectName().ToString())); 452 | StreamSpread.SetCell(NewRowKey, 1, FStringToChar(Object->GetStreamOptions())); 453 | StreamSpread.SetCell(NewRowKey, 1, Object->GetStreamingMode()); 454 | StreamSpread.SetCell(NewRowKey, 2, Object->GetActiveStatus()); 455 | StreamSpread.SetCell(NewRowKey, 3, Object->GetSendAnimatableStatus()); 456 | } 457 | 458 | 459 | bool IsModelInDeviceStream(const FMobuLiveLink* MobuDevice, const FBModel* Model) 460 | { 461 | for (const TPair>& StreamPair : MobuDevice->StreamObjects) 462 | { 463 | if (StreamPair.Value->GetModelPointer() == Model) 464 | { 465 | return true; 466 | } 467 | } 468 | return false; 469 | } 470 | 471 | void FMobuLiveLinkLayout::EventAddToStream(HISender Sender, HKEvent Event) 472 | { 473 | TArray ParentsToIgnore; 474 | ParentsToIgnore.Reserve(ObjectSelection.GetCount()); 475 | 476 | FBModel* SceneRoot = FBSystem().Scene->RootModel; 477 | for (int CharIndex = 0; CharIndex < ObjectSelection.GetCount(); ++CharIndex) 478 | { 479 | FBModel* Model = (FBModel*)ObjectSelection.GetAt(CharIndex); 480 | 481 | // Ignore the SceneRoot 482 | if (Model == SceneRoot) 483 | { 484 | continue; 485 | } 486 | 487 | // Only grab root items 488 | if (ParentsToIgnore.Contains(Model->Parent)) 489 | { 490 | ParentsToIgnore.Emplace(Model); 491 | } 492 | else if (!IsModelInDeviceStream(LiveLinkDevice, Model)) 493 | { 494 | StreamObjectPtr StoreObject = StreamObjectManagement::FBModelToStreamObject(Model); 495 | int32 NewUID = LiveLinkDevice->GetNextUID(); 496 | 497 | LiveLinkDevice->AddStreamObject(NewUID, StoreObject); 498 | AddSpreadRowFromStreamObject(NewUID, StoreObject); 499 | 500 | ParentsToIgnore.Emplace(Model); 501 | } 502 | } 503 | ObjectSelection.Clear(); 504 | } 505 | 506 | void FMobuLiveLinkLayout::EventRemoveFromStream(HISender Sender, HKEvent Event) 507 | { 508 | int SelectedCount = 0; 509 | 510 | decltype(LiveLinkDevice->StreamObjects) StreamObjectsToRemove; 511 | 512 | for (const TPair& MapPair : LiveLinkDevice->StreamObjects) 513 | { 514 | int32 RowKey = MapPair.Key; 515 | bool bRowSelected = StreamSpread.GetRow(RowKey).RowSelected; 516 | if (bRowSelected) 517 | { 518 | StreamObjectsToRemove.Add(MapPair); 519 | SelectedCount++; 520 | } 521 | } 522 | 523 | for (const auto& MapPair : StreamObjectsToRemove) 524 | { 525 | LiveLinkDevice->RemoveStreamObject(MapPair.Key, MapPair.Value); 526 | } 527 | 528 | if (SelectedCount > 0) 529 | { 530 | UIReset(); 531 | } 532 | 533 | FBTrace("Removed %d items in selection!\n", SelectedCount); 534 | } 535 | 536 | void FMobuLiveLinkLayout::EventStreamEditorCamera(HISender Sender, HKEvent Event) 537 | { 538 | LiveLinkDevice->SetEditorCameraStreamed((bool)StreamEditorCameraButton.State); 539 | } 540 | 541 | void FMobuLiveLinkLayout::EventStreamSpreadCellChange(HISender Sender, HKEvent Event) 542 | { 543 | FBEventSpread SpreadEvent = Event; 544 | 545 | StreamObjectPtr* ObjectPtr = LiveLinkDevice->StreamObjects.Find(SpreadEvent.Row); 546 | if (ObjectPtr == nullptr && ObjectPtr->IsValid()) 547 | { 548 | FBTrace("No object exists for this Row!"); 549 | return; 550 | } 551 | switch (SpreadEvent.Column) 552 | { 553 | case 0: // Subject Name 554 | { 555 | const char* NewSubjectName; 556 | StreamSpread.GetCell(SpreadEvent.Row, SpreadEvent.Column, NewSubjectName); 557 | LiveLinkDevice->ChangeSubjectName(*ObjectPtr, NewSubjectName); 558 | break; 559 | } 560 | case 1: // Stream Type 561 | { 562 | int RowIndex; 563 | StreamSpread.GetCell(SpreadEvent.Row, SpreadEvent.Column, RowIndex); 564 | (*ObjectPtr)->UpdateStreamingMode(RowIndex); 565 | break; 566 | } 567 | case 2: // Stream Status 568 | { 569 | int bIsActive; 570 | StreamSpread.GetCell(SpreadEvent.Row, SpreadEvent.Column, bIsActive); 571 | (*ObjectPtr)->UpdateActiveStatus(bIsActive > 0); 572 | break; 573 | } 574 | case 3: // Stream Animatable 575 | { 576 | int bIsAnimatable; 577 | StreamSpread.GetCell(SpreadEvent.Row, SpreadEvent.Column, bIsAnimatable); 578 | (*ObjectPtr)->UpdateSendAnimatableStatus(bIsAnimatable > 0); 579 | break; 580 | } 581 | default: 582 | break; 583 | } 584 | 585 | LiveLinkDevice->SetDirty(true); 586 | } 587 | 588 | void FMobuLiveLinkLayout::EventTabPanelChange(HISender pSender, HKEvent pEvent) 589 | { 590 | switch (TabPanel.ItemIndex) 591 | { 592 | case 0: 593 | SetControl("MainLayout", Layouts[0]); 594 | break; 595 | case 1: 596 | SetControl("MainLayout", Layouts[1]); 597 | break; 598 | } 599 | } 600 | 601 | void FMobuLiveLinkLayout::EventTimecodeModeChanged(HISender Sender, HKEvent Event) 602 | { 603 | LiveLinkDevice->SetTimecodeModeFromInt(TimecodeModeList.ItemIndex); 604 | } 605 | 606 | void FMobuLiveLinkLayout::EventSampleRateChange(HISender Sender, HKEvent Event) 607 | { 608 | const FFrameRate& NewSampleRate = LiveLinkDevice->SampleOptions[SampleRateList.ItemIndex].Value; 609 | if (NewSampleRate != LiveLinkDevice->CurrentSampleRate) 610 | { 611 | LiveLinkDevice->CurrentSampleRate = NewSampleRate; 612 | LiveLinkDevice->UpdateSampleRate(); 613 | } 614 | } 615 | 616 | void FMobuLiveLinkLayout::EventEditProviderNamePopup(HISender Sender, HKEvent Event) 617 | { 618 | char NewNameString[1024]; 619 | memset(NewNameString, 0, sizeof(NewNameString)); 620 | strncpy_s(NewNameString, sizeof(NewNameString) - 1, ProviderNameText.Text, sizeof(ProviderNameText.Text)); 621 | 622 | // This is scary with no buffer overrun safety on the Mobu SDK side 623 | int ButtonClicked = FBMessageBoxGetUserValue("Change Provider Name", "Enter a new Live Link Provider name", NewNameString, kFBPopupString, "Accept", "Cancel"); 624 | 625 | if ((ButtonClicked == 1) && (strlen(NewNameString) > 0) && (LiveLinkDevice->GetProviderName() != CharToFString(NewNameString))) 626 | { 627 | ProviderNameText.Text = NewNameString; 628 | LiveLinkDevice->SetProviderName(CharToFString(NewNameString)); 629 | } 630 | } 631 | 632 | void FMobuLiveLinkLayout::EventChangeUnicastEndpoint(HISender Sender, HKEvent Event) 633 | { 634 | char NewUnicastString[1024]; 635 | memset(NewUnicastString, 0, sizeof(NewUnicastString)); 636 | strncpy_s(NewUnicastString, sizeof(NewUnicastString) - 1, UnicastEndpoint.Text, sizeof(UnicastEndpoint.Text)); 637 | 638 | const std::string Description("Enter a new Unicast Endpoint address to select which nic to use."); 639 | const std::string FormatHint("\n\nThe entered address was not formatted correctly. The format must match this pattern:\nIpAddress:Port"); 640 | int ButtonClicked = 0; 641 | bool bTryAgain = false; 642 | 643 | do 644 | { 645 | std::string Message(Description); 646 | if (bTryAgain) 647 | { 648 | Message += FormatHint; 649 | } 650 | 651 | // This is scary with no buffer overrun safety on the Mobu SDK side 652 | ButtonClicked = FBMessageBoxGetUserValue("Change Unicast Endpoint", Message.c_str(), NewUnicastString, kFBPopupString, "Accept", "Cancel"); 653 | StripWhitespace(NewUnicastString); 654 | bTryAgain = (ButtonClicked == 1) && !IsValidIpAddressWithPort(NewUnicastString); 655 | } while (bTryAgain); 656 | 657 | if ((ButtonClicked == 1) && (strlen(NewUnicastString) > 0) && (LiveLinkDevice->GetUnicastEndpoint() != CharToFString(NewUnicastString))) 658 | { 659 | UnicastEndpoint.Text = NewUnicastString; 660 | LiveLinkDevice->SetUnicastEndpoint(CharToFString(NewUnicastString)); 661 | } 662 | } 663 | 664 | void FMobuLiveLinkLayout::EventAddStaticEndpoint(HISender Sender, HKEvent Event) 665 | { 666 | char NewAddressString[1024] = "0.0.0.0:6666"; 667 | 668 | const std::string Description("Enter a new Static Endpoint Address of the PC running Unreal Engine."); 669 | const std::string FormatHint("\n\nThe entered address was not formatted correctly. The format must match this pattern:\nIpAddress:Port"); 670 | int ButtonClicked = 0; 671 | bool bTryAgain = false; 672 | 673 | do 674 | { 675 | std::string Message(Description); 676 | if (bTryAgain) 677 | { 678 | Message += FormatHint; 679 | } 680 | // This is scary with no buffer overrun safety on the Mobu SDK side 681 | ButtonClicked = FBMessageBoxGetUserValue("Add StaticEndpoint", Message.c_str(), NewAddressString, kFBPopupString, "Accept", "Cancel"); 682 | StripWhitespace(NewAddressString); 683 | bTryAgain = (ButtonClicked == 1) && !IsValidIpAddressWithPort(NewAddressString); 684 | } while (bTryAgain); 685 | 686 | if ((ButtonClicked == 1) && (strlen(NewAddressString) > 0)) 687 | { 688 | const int ExistingIndex = StaticEndpoints.Items.IndexOf(NewAddressString); 689 | if (ExistingIndex == -1) // avoid duplicates 690 | { 691 | StaticEndpoints.Items.Add(NewAddressString); 692 | if (!LiveLinkDevice->AddStaticEndpoint(CharToFString(NewAddressString))) 693 | { 694 | FBMessageBox("Error", "Could not add Static Endpoint!", "OK"); 695 | } 696 | } 697 | } 698 | } 699 | 700 | void FMobuLiveLinkLayout::EventRemoveStaticEndpoint(HISender Sender, HKEvent Event) 701 | { 702 | if (StaticEndpoints.ItemIndex == -1) 703 | { 704 | return; 705 | } 706 | 707 | if (!LiveLinkDevice->RemoveStaticEndpoint(StaticEndpoints.Items[StaticEndpoints.ItemIndex])) 708 | { 709 | FBMessageBox("Error", "Could not remove Static Endpoint!", "OK"); 710 | } 711 | else 712 | { 713 | StaticEndpoints.Items.RemoveAt(StaticEndpoints.ItemIndex); 714 | } 715 | } 716 | -------------------------------------------------------------------------------- /Source/Private/MobuLiveLinkUtilities.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | #include "MobuLiveLinkUtilities.h" 4 | 5 | const float MobuUtilities::InchesToMillimeters = 25.4f; 6 | 7 | FTransform MobuUtilities::MobuTransformToUnreal(FBMatrix MobuTransfrom) 8 | { 9 | FBMatrix MobuTransformUnrealSpace; 10 | FBTVector TVector; 11 | FBSVector SVector; 12 | FBQuaternion Quat; 13 | for (int j = 0; j < 4; ++j) 14 | { 15 | if (j == 1) 16 | { 17 | MobuTransformUnrealSpace(j, 0) = -MobuTransfrom(j, 0); 18 | MobuTransformUnrealSpace(j, 1) = MobuTransfrom(j, 1); 19 | MobuTransformUnrealSpace(j, 2) = -MobuTransfrom(j, 2); 20 | MobuTransformUnrealSpace(j, 3) = -MobuTransfrom(j, 3); 21 | } 22 | else 23 | { 24 | MobuTransformUnrealSpace(j, 0) = MobuTransfrom(j, 0); 25 | MobuTransformUnrealSpace(j, 1) = -MobuTransfrom(j, 1); 26 | MobuTransformUnrealSpace(j, 2) = MobuTransfrom(j, 2); 27 | MobuTransformUnrealSpace(j, 3) = MobuTransfrom(j, 3); 28 | } 29 | } 30 | 31 | FBMatrixToTranslation(TVector, MobuTransformUnrealSpace); 32 | FBMatrixToQuaternion(Quat, MobuTransformUnrealSpace); 33 | FBMatrixToScaling(SVector, MobuTransformUnrealSpace); 34 | 35 | FTransform UnrealTransform; 36 | UnrealTransform.SetRotation(FQuat(Quat[0], Quat[1], Quat[2], Quat[3])); 37 | UnrealTransform.SetTranslation(FVector(TVector[0], TVector[1], TVector[2])); 38 | UnrealTransform.SetScale3D(FVector(SVector[0], SVector[1], SVector[2])); 39 | 40 | return UnrealTransform; 41 | } 42 | 43 | FColor MobuUtilities::MobuColorToUnreal(FBColor Color) 44 | { 45 | FColor Result; 46 | Result.R = FMath::Clamp(Color[0] * 255.0, 0.0, 255.0); 47 | Result.G = FMath::Clamp(Color[1] * 255.0, 0.0, 255.0); 48 | Result.B = FMath::Clamp(Color[2] * 255.0, 0.0, 255.0); 49 | Result.A = 255; 50 | return Result; 51 | } 52 | 53 | FTransform MobuUtilities::UnrealTransformFromModel(FBModel* MobuModel, bool bIsGlobal) 54 | { 55 | FBMatrix MobuTransform; 56 | FBMatrix MatOffset; 57 | 58 | MobuModel->GetMatrix(MobuTransform, kModelTransformation, bIsGlobal, nullptr); 59 | 60 | // Y-Up Correction 61 | FBRVector RotOffset(90, 0, 0); 62 | FBRotationToMatrix(MatOffset, RotOffset); 63 | FBMatrixMult(MobuTransform, MatOffset, MobuTransform); 64 | 65 | return MobuTransformToUnreal(MobuTransform); 66 | }; 67 | 68 | // Get all properties on a given model that are both Animatable and are of a Type we can stream 69 | TArray MobuUtilities::GetAllAnimatableCurveNames(FBModel* MobuModel, const FString& Prefix) 70 | { 71 | const int PropertyCount = MobuModel->PropertyList.GetCount(); 72 | 73 | TArray LiveLinkCurves; 74 | // Reserve enough memory for worst case 75 | LiveLinkCurves.Reserve(PropertyCount); 76 | 77 | for (int i = 0; i < PropertyCount; ++i) 78 | { 79 | FBProperty* Property = MobuModel->PropertyList[i]; 80 | if (Property->IsAnimatable()) 81 | { 82 | //Only add supported property 83 | switch (Property->GetPropertyType()) 84 | { 85 | case kFBPT_bool: 86 | case kFBPT_double: 87 | case kFBPT_float: 88 | case kFBPT_enum: 89 | case kFBPT_int: 90 | case kFBPT_int64: 91 | case kFBPT_uint64: 92 | break; 93 | default: 94 | continue; 95 | } 96 | 97 | if (!Prefix.IsEmpty()) 98 | { 99 | LiveLinkCurves.Add(*(Prefix + FString(":") + Property->GetName())); 100 | } 101 | else 102 | { 103 | LiveLinkCurves.Add(Property->GetName()); 104 | } 105 | } 106 | } 107 | return LiveLinkCurves; 108 | } 109 | 110 | TArray MobuUtilities::GetAllAnimatableCurveValues(FBModel* MobuModel) 111 | { 112 | int PropertyCount = MobuModel->PropertyList.GetCount(); 113 | 114 | TArray LiveLinkCurves; 115 | // Reserve enough memory for worst case 116 | LiveLinkCurves.Reserve(PropertyCount); 117 | 118 | float PropertyValue; 119 | for (int i = 0; i < PropertyCount; ++i) 120 | { 121 | FBProperty* Property = MobuModel->PropertyList[i]; 122 | if (Property->IsAnimatable()) 123 | { 124 | switch (Property->GetPropertyType()) 125 | { 126 | case kFBPT_bool: 127 | { 128 | bool bValue; 129 | Property->GetData(&bValue, sizeof(bValue), nullptr); 130 | PropertyValue = bValue ? 1.0f : 0.0f; 131 | break; 132 | } 133 | case kFBPT_double: 134 | { 135 | double Value; 136 | Property->GetData(&Value, sizeof(Value), nullptr); 137 | PropertyValue = (float)Value; 138 | break; 139 | } 140 | case kFBPT_float: 141 | { 142 | // PropertyValue is a float so retrieve it directly 143 | Property->GetData(&PropertyValue, sizeof(PropertyValue), nullptr); 144 | break; 145 | } 146 | case kFBPT_enum: // Enums are assumed to be ints 147 | case kFBPT_int: 148 | { 149 | int Value; 150 | Property->GetData(&Value, sizeof(Value), nullptr); 151 | PropertyValue = (float)Value; 152 | break; 153 | } 154 | case kFBPT_int64: 155 | { 156 | int64 Value; 157 | Property->GetData(&Value, sizeof(Value), nullptr); 158 | PropertyValue = (float)Value; 159 | break; 160 | } 161 | case kFBPT_uint64: 162 | { 163 | uint64 Value; 164 | Property->GetData(&Value, sizeof(Value), nullptr); 165 | PropertyValue = (float)Value; 166 | break; 167 | } 168 | default: 169 | continue; 170 | } 171 | LiveLinkCurves.Add(PropertyValue); 172 | } 173 | } 174 | return LiveLinkCurves; 175 | } 176 | 177 | FFrameRate MobuUtilities::TimeModeToFrameRate(FBTimeMode TimeMode) 178 | { 179 | switch (TimeMode) 180 | { 181 | case FBTimeMode::kFBTimeMode1000Frames: 182 | return FFrameRate(1000,1); 183 | case FBTimeMode::kFBTimeMode120Frames: 184 | return FFrameRate(120, 1); 185 | case FBTimeMode::kFBTimeMode100Frames: 186 | return FFrameRate(100, 1); 187 | case FBTimeMode::kFBTimeMode96Frames: 188 | return FFrameRate(96, 1); 189 | case FBTimeMode::kFBTimeMode72Frames: 190 | return FFrameRate(72, 1); 191 | case FBTimeMode::kFBTimeMode60Frames: 192 | return FFrameRate(60, 1); 193 | case FBTimeMode::kFBTimeMode5994Frames: 194 | return FFrameRate(60000, 1001); 195 | case FBTimeMode::kFBTimeMode50Frames: 196 | return FFrameRate(50, 1); 197 | case FBTimeMode::kFBTimeMode48Frames: 198 | return FFrameRate(48, 1); 199 | case FBTimeMode::kFBTimeMode30Frames: 200 | return FFrameRate(30, 1); 201 | case FBTimeMode::kFBTimeMode2997Frames_Drop: 202 | return FFrameRate(30000, 1001); 203 | case FBTimeMode::kFBTimeMode2997Frames: 204 | return FFrameRate(30000, 1001); 205 | case FBTimeMode::kFBTimeMode25Frames: 206 | return FFrameRate(25, 1); 207 | case FBTimeMode::kFBTimeMode24Frames: 208 | return FFrameRate(24, 1); 209 | case FBTimeMode::kFBTimeMode23976Frames: 210 | return FFrameRate(24000, 1001); 211 | case FBTimeMode::kFBTimeModeDefault: 212 | case FBTimeMode::kFBTimeModeCustom: 213 | default: 214 | return FFrameRate(FMath::RoundToInt(FBPlayerControl().GetTransportFpsValue() * 1001), 1001); 215 | } 216 | } 217 | 218 | FQualifiedFrameTime MobuUtilities::GetSceneTimecode(ETimecodeMode TimecodeMode) 219 | { 220 | FBTimeMode TimeMode = FBPlayerControl().GetTransportFps(); 221 | FFrameRate FrameRate = TimeModeToFrameRate(TimeMode); 222 | FFrameTime FrameTime; 223 | 224 | // Make sure we use the decimal frame time instead of the integer frame number to keep subframes 225 | if (TimecodeMode == ETimecodeMode::TimecodeMode_Local) // Local time (Take time) 226 | { 227 | FBTime MobuTime = FBSystem().LocalTime; 228 | FrameTime = FFrameTime(FrameRate.AsFrameTime(MobuTime.GetSecondDouble())); 229 | } 230 | else if (TimecodeMode == ETimecodeMode::TimecodeMode_System) // System time (PC clock) 231 | { 232 | const FDateTime DateTime = FDateTime::Now(); 233 | const FTimespan Timespan = DateTime.GetTimeOfDay(); 234 | FrameTime = FFrameTime(FrameRate.AsFrameTime(Timespan.GetTotalSeconds())); 235 | } 236 | else if (TimecodeMode == ETimecodeMode::TimecodeMode_Reference) // Reference time (Incoming LTC) 237 | { 238 | FBReferenceTime MobuRefTime; 239 | #if PRODUCT_VERSION >= 2019 240 | FBArrayTemplate Identifiers; 241 | MobuRefTime.GetUniqueIDList(&Identifiers); 242 | if (Identifiers.GetCount() > 0) 243 | { 244 | FBTime RefTime = MobuRefTime.GetTime(MobuRefTime.CurrentTimeReferenceID, FBTime(0)); 245 | FrameTime = FFrameTime(FrameRate.AsFrameTime(RefTime.GetSecondDouble())); 246 | } 247 | else 248 | { 249 | FBTrace("GetSceneTimecode - No Reference time sources\n"); 250 | } 251 | #else 252 | if (MobuRefTime.Count > 0) 253 | { 254 | FBTime RefTime = MobuRefTime.GetTime(MobuRefTime.ItemIndex, FBTime(0)); 255 | FrameTime = FFrameTime(FrameRate.AsFrameTime(RefTime.GetSecondDouble())); 256 | } 257 | else 258 | { 259 | FBTrace("GetSceneTimecode - No Reference time sources\n"); 260 | } 261 | #endif 262 | 263 | } 264 | else 265 | { 266 | FBTrace("GetSceneTimecode - Invalid timecode mode\n"); 267 | } 268 | 269 | return FQualifiedFrameTime(FrameTime, FrameRate); 270 | } -------------------------------------------------------------------------------- /Source/Private/StreamObjectManagement.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | #include "MobuLiveLinkStreamObjects.h" 4 | 5 | TSharedPtr StreamObjectManagement::FBModelToStreamObject(FBModel* SourceModel) 6 | { 7 | 8 | const int SourceType = SourceModel->GetTypeId(); 9 | 10 | if (SourceType == FBCamera::TypeInfo) 11 | { 12 | return StoreCamera(SourceModel); 13 | } 14 | else if (SourceType == FBLight::TypeInfo) 15 | { 16 | return StoreLight(SourceModel); 17 | } 18 | else if (SourceType == FBModelSkeleton::TypeInfo || SourceType == FBModelRoot::TypeInfo) 19 | { 20 | return StoreSkeleton(SourceModel); 21 | } 22 | else 23 | { 24 | return StoreGeneric(SourceModel); 25 | } 26 | }; 27 | 28 | TSharedPtr StreamObjectManagement::StoreCamera(const FBModel* Model) 29 | { 30 | FBTrace("%s is a Camera!\n", (const char*)Model->LongName); 31 | 32 | TSharedPtr CameraStore = MakeShared(Model); 33 | return CameraStore; 34 | } 35 | 36 | TSharedPtr StreamObjectManagement::StoreLight(const FBModel* Model) 37 | { 38 | FBTrace("%s is a Light!\n", (const char*)Model->LongName); 39 | 40 | TSharedPtr LightStore = MakeShared(Model); 41 | return LightStore; 42 | } 43 | 44 | TSharedPtr StreamObjectManagement::StoreSkeleton(const FBModel* Model) 45 | { 46 | FBTrace("%s is a Skeleton!\n", (const char*)Model->LongName); 47 | 48 | TSharedPtr SkeletonStore = MakeShared(Model); 49 | return SkeletonStore; 50 | } 51 | 52 | TSharedPtr StreamObjectManagement::StoreGeneric(const FBModel* Model) 53 | { 54 | FBTrace("%s is an Unknown Type! - %s\n", (const char*)Model->LongName, ((FBModel*)Model)->ClassName()); 55 | 56 | TSharedPtr GenericStore = MakeShared(Model); 57 | return GenericStore; 58 | } -------------------------------------------------------------------------------- /Source/Public/IStreamObject.h: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "MobuLiveLinkCommon.h" 6 | 7 | 8 | // Pure Abstract class. Inherit from this to support streaming. 9 | // If you create a new Stream Object then make sure to register it in MobuLiveLinkStreamObject.h 10 | class IStreamObject 11 | { 12 | public: 13 | // Interface for modifying and accessing stream parameters 14 | virtual ~IStreamObject() {} 15 | 16 | virtual const bool ShouldShowInUI() const = 0; 17 | 18 | virtual const FString GetStreamOptions() const = 0; 19 | 20 | virtual FName GetSubjectName() const = 0; 21 | 22 | virtual void UpdateSubjectName(FName NewSubjectName) = 0; 23 | 24 | virtual int GetStreamingMode() const = 0; 25 | 26 | virtual void UpdateStreamingMode(int NewStreamingMode) = 0; 27 | 28 | virtual bool GetActiveStatus() const = 0; 29 | 30 | virtual void UpdateActiveStatus(bool bIsNowActive) = 0; 31 | 32 | virtual bool GetSendAnimatableStatus() const = 0; 33 | 34 | virtual void UpdateSendAnimatableStatus(bool bNewSendAnimatable) = 0; 35 | 36 | virtual const FBModel* GetModelPointer() const = 0; 37 | 38 | virtual const FString GetRootName() const = 0; 39 | 40 | virtual bool IsValid() const = 0; 41 | 42 | // Interface for object streaming 43 | 44 | virtual void Refresh(const TSharedPtr Provider) = 0; 45 | 46 | virtual void UpdateSubjectFrame(const TSharedPtr Provider, FLiveLinkWorldTime WorldTime, FQualifiedFrameTime QualifiedFrameTime) = 0; 47 | }; -------------------------------------------------------------------------------- /Source/Public/MobuLiveLinkCommon.h: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "LiveLinkProvider.h" 7 | #include "LiveLinkRefSkeleton.h" 8 | 9 | #pragma warning(push) 10 | #pragma warning(disable:4263 4264) 11 | #include 12 | #pragma warning(pop) -------------------------------------------------------------------------------- /Source/Public/MobuLiveLinkDevice.h: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "MobuLiveLinkCommon.h" 6 | #include "MobuLiveLinkUtilities.h" 7 | #include "IStreamObject.h" 8 | #include "Misc/CommandLine.h" 9 | #include "Async/TaskGraphInterfaces.h" 10 | #include "Modules/ModuleManager.h" 11 | #include "UObject/Object.h" 12 | #include "Misc/ConfigCacheIni.h" 13 | #include "Misc/OutputDevice.h" 14 | 15 | //--- Registration defines 16 | #define MOBULIVELINK__CLASSNAME FMobuLiveLink 17 | #define MOBULIVELINK__CLASSSTR "MobuLiveLink" 18 | 19 | #define IntToChar(input) std::to_string(input).c_str() 20 | #define FStringToChar(input) ((std::string)TCHAR_TO_UTF8(*input)).c_str() 21 | #define CharToFString(input) UTF8_TO_TCHAR(input) 22 | 23 | //! Simple input device. 24 | class FMobuLiveLink : public FBDevice 25 | { 26 | //--- FiLMBOX declaration 27 | FBDeviceDeclare(FMobuLiveLink, FBDevice); 28 | public: 29 | void StartLiveLink(); 30 | void StopLiveLink(); 31 | 32 | //--- FiLMBOX Construction/Destruction 33 | bool FBCreate() override; //!< FiLMBOX constructor. 34 | void FBDestroy() override; //!< FiLMBOX destructor. 35 | 36 | //--- Initialisation/Shutdown 37 | bool Init(); //!< Initialize/create device. 38 | bool Done(); //!< Remove device. 39 | bool Reset(); //!< Reset device. 40 | bool Stop(); //!< Stop device (offline). 41 | bool Start(); //!< Start device (online). 42 | 43 | //--- The following will be called by the real-time engine. 44 | void DeviceIONotify(kDeviceIOs Action, FBDeviceNotifyInfo &DeviceNotifyInfo) override; //!< Notification of/for Device IO. 45 | bool DeviceEvaluationNotify(kTransportMode Mode, FBEvaluateInfo* EvaluateInfo) override; //!< Evaluation the device (write to hardware). 46 | bool DeviceOperation(kDeviceOperations Operation) override; //!< Operate device. 47 | 48 | //--- FBX Load/Save. 49 | bool FbxStore(FBFbxObject* FbxObject, kFbxObjectStore StoreWhat) override; //!< Store in FBX file. 50 | bool FbxRetrieve(FBFbxObject* FbxObject, kFbxObjectStore StoreWhat) override; //!< Retrieve from FBX file. 51 | 52 | //--- Events 53 | void EventSceneChange(HISender Sender, HKEvent Event); 54 | void EventRenderUpdate(HISender Sender, HKEvent Event); 55 | 56 | private: 57 | typedef TSharedPtr StreamObjectPtr; 58 | 59 | void FbxRetrieveV4(FBFbxObject* FbxObject, kFbxObjectStore StoreWhat); //!< Retrieve from FBX file stored with the previous version. 60 | 61 | public: 62 | void AddStreamObject(int32 NewUID, StreamObjectPtr NewObject); 63 | void RemoveStreamObject(int32 DeletionKey, StreamObjectPtr RemoveObject); 64 | void ChangeSubjectName(StreamObjectPtr ObjectPtr, const char* NewSubjectNameStr); 65 | void UpdateStreamObjects(); 66 | 67 | void SetDirty(bool bNewDirty) { bIsDirty = bNewDirty; }; 68 | bool IsDirty() const { return bIsDirty; }; 69 | 70 | void SetRefreshUI(bool bNewRefreshUI) { bShouldRefreshUI = bNewRefreshUI; }; 71 | bool ShouldRefreshUI() const { return bShouldRefreshUI; }; 72 | 73 | const TArray> SampleOptions = 74 | { 75 | TPair(FString("30hz"), FFrameRate(30, 1)), 76 | TPair(FString("50hz"), FFrameRate(50, 1)), 77 | TPair(FString("60hz"), FFrameRate(60, 1)), 78 | TPair(FString("100hz"), FFrameRate(100, 1)), 79 | TPair(FString("120hz"), FFrameRate(120, 1)), 80 | TPair(FString("Before Render"), FFrameRate(-1, 1)), 81 | }; 82 | 83 | FFrameRate CurrentSampleRate; 84 | void UpdateSampleRate(); 85 | 86 | int32 GetNextUID(); 87 | 88 | bool IsEditorCameraStreamed() const; 89 | void SetEditorCameraStreamed(bool bStream); 90 | 91 | ETimecodeMode GetTimecodeMode() const; 92 | int32 GetTimecodeModeAsInt() const; 93 | void SetTimecodeMode(ETimecodeMode InTimecodeMode); 94 | void SetTimecodeModeFromInt(int32 InTimecodeModeInt); 95 | 96 | const FString& GetProviderName() const { return CurrentProviderName; } 97 | void SetProviderName(const FString& NewValue); 98 | 99 | bool AddStaticEndpoint(const FString& InEndpoint); 100 | const TArray& GetStaticEndpoints() const { return StaticEndpoints; } 101 | bool RemoveStaticEndpoint(const FString& InEndpoint); 102 | 103 | FString GetUnicastEndpoint() const; 104 | void SetUnicastEndpoint(const FString& InEndpoint); 105 | 106 | public: 107 | TMap> StreamObjects; 108 | TSharedPtr LiveLinkProvider; 109 | 110 | private: 111 | TWeakPtr EditorCameraObject; 112 | 113 | FString CurrentProviderName = "Mobu Live Link"; 114 | 115 | TArray StaticEndpoints; 116 | 117 | int32 NextUID = 1; 118 | 119 | void UpdateStream(); //!< Get latest data and send to unreal 120 | 121 | int32 GetCurrentSampleRateIndex(); 122 | 123 | bool bIsDirty = false; 124 | bool bShouldRefreshUI = false; 125 | 126 | bool bShouldUpdateInRenderCallback = false; //!< Whether to update after render or to update in device evaluation 127 | 128 | FBDeviceSamplingMode SamplingType; 129 | FBFastLock mCleanUpLock; 130 | 131 | TMap SceneChangeNameMap; 132 | 133 | double LastEvaluationTime; 134 | ETimecodeMode TimecodeMode; 135 | 136 | void SetDeviceInformation(const char* NewDeviceInformation); 137 | void TickCoreTicker(); 138 | }; 139 | 140 | -------------------------------------------------------------------------------- /Source/Public/MobuLiveLinkLayout.h: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | #pragma once 4 | #include 5 | #include "MobuLiveLinkDevice.h" 6 | 7 | 8 | class FMobuLiveLinkLayout : public FBDeviceLayout 9 | { 10 | FBDeviceLayoutDeclare(FMobuLiveLinkLayout, FBDeviceLayout); 11 | 12 | public: 13 | bool FBCreate() override; 14 | void FBDestroy() override; 15 | 16 | // UI Management 17 | void UICreate(); 18 | void UICreateLayout0(); 19 | void UICreateLayout1(); 20 | void UIConfigure(); 21 | void UIConfigureLayout0(); 22 | void UIConfigureLayout1(); 23 | void UIReset(); 24 | void CreateSpreadColumns(); 25 | 26 | // Main Layout: Events 27 | void EventUIIdle(HISender Sender, HKEvent Event); 28 | void EventAddToStream(HISender Sender, HKEvent Event); 29 | void EventRemoveFromStream(HISender Sender, HKEvent Event); 30 | void EventStreamEditorCamera(HISender Sender, HKEvent Event); 31 | void EventStreamSpreadCellChange(HISender Sender, HKEvent Event); 32 | void EventTabPanelChange(HISender pSender, HKEvent pEvent); 33 | void EventTimecodeModeChanged(HISender Sender, HKEvent Event); 34 | void EventSampleRateChange(HISender Sender, HKEvent Event); 35 | void EventEditProviderNamePopup(HISender Sender, HKEvent Event); 36 | void EventChangeUnicastEndpoint(HISender Sender, HKEvent Event); 37 | void EventAddStaticEndpoint(HISender Sender, HKEvent Event); 38 | void EventRemoveStaticEndpoint(HISender Sender, HKEvent Event); 39 | 40 | public: 41 | 42 | FBTabPanel TabPanel; 43 | FBLayout Layouts[2]; 44 | 45 | FBLabel ObjectSelectorLabel; 46 | FBPropertyConnectionEditor ObjectSelector; 47 | FBButton AddToStreamButton; 48 | FBButton RemoveFromStreamButton; 49 | FBButton StreamEditorCameraButton; 50 | FBList TimecodeModeList; 51 | FBLabel TimecodeModeListLabel; 52 | FBLabel SampleRateListLabel; 53 | FBList SampleRateList; 54 | FBLabel ProviderNameLabel; 55 | FBEdit ProviderNameText; 56 | FBButton ProviderNameEditButton; 57 | FBSpread StreamSpread; 58 | FBLabel UnicastEndpointLabel; 59 | FBEdit UnicastEndpoint; 60 | FBButton UnicastEndpointEditButton; 61 | FBLabel StaticEndpointLabel; 62 | FBList StaticEndpoints; 63 | FBButton StaticEndpointAddButton; 64 | FBButton StaticEndpointRemoveButton; 65 | 66 | private: 67 | typedef TSharedPtr StreamObjectPtr; 68 | 69 | void AddSpreadRowFromStreamObject(int32 NewRowKey, StreamObjectPtr Object); 70 | 71 | FBSystem System; 72 | FMobuLiveLink* LiveLinkDevice; 73 | 74 | FBPropertyListObject ObjectSelection; 75 | }; -------------------------------------------------------------------------------- /Source/Public/MobuLiveLinkStreamObjects.h: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | //--- Include all Stream Objects here 6 | 7 | #include "ModelStreamObject.h" 8 | #include "CameraStreamObject.h" 9 | #include "LightStreamObject.h" 10 | #include "SkeletonHierarchyStreamObject.h" 11 | #include "EditorActiveCameraStreamObject.h" 12 | 13 | // Static Class providing easy creation of Stream Objects 14 | class StreamObjectManagement 15 | { 16 | public: 17 | static TSharedPtr FBModelToStreamObject(FBModel* SourceModel); 18 | 19 | static TSharedPtr StoreCamera(const FBModel* CameraModel); 20 | static TSharedPtr StoreLight(const FBModel* LightModel); 21 | static TSharedPtr StoreGeneric(const FBModel* Model); 22 | static TSharedPtr StoreSkeleton(const FBModel* SkeletonModel); 23 | }; -------------------------------------------------------------------------------- /Source/Public/MobuLiveLinkUtilities.h: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "MobuLiveLinkCommon.h" 6 | 7 | enum class ETimecodeMode : int32 8 | { 9 | TimecodeMode_Local = 0, 10 | TimecodeMode_System = 1, 11 | TimecodeMode_Reference = 2 12 | }; 13 | 14 | class MobuUtilities 15 | { 16 | public: 17 | static const float InchesToMillimeters; 18 | 19 | static FTransform MobuTransformToUnreal(FBMatrix MobuTransfrom); 20 | static FColor MobuColorToUnreal(FBColor Color); 21 | static FTransform UnrealTransformFromModel(FBModel* MobuModel, bool bIsGlobal = true); 22 | static TArray GetAllAnimatableCurveNames(FBModel* MobuModel, const FString& Prefix = FString()); 23 | static TArray GetAllAnimatableCurveValues(FBModel* MobuModel); 24 | 25 | static FFrameRate TimeModeToFrameRate(FBTimeMode TimeMode); 26 | static FQualifiedFrameTime GetSceneTimecode(ETimecodeMode TimecodeMode); 27 | }; -------------------------------------------------------------------------------- /Source/StreamObjects/Private/CameraStreamObject.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | #include "CameraStreamObject.h" 4 | #include "MobuLiveLinkUtilities.h" 5 | 6 | #include "Roles/LiveLinkAnimationRole.h" 7 | #include "Roles/LiveLinkAnimationTypes.h" 8 | #include "Roles/LiveLinkCameraRole.h" 9 | #include "Roles/LiveLinkCameraTypes.h" 10 | #include "Roles/LiveLinkTransformRole.h" 11 | #include "Roles/LiveLinkTransformTypes.h" 12 | 13 | namespace 14 | { 15 | void FixCameraRotation(FTransform& CameraTransform) 16 | { 17 | FMatrix InMatrix = CameraTransform.ToMatrixWithScale(); 18 | 19 | FVector DestAxisX = InMatrix.GetScaledAxis(EAxis::X); 20 | FVector DestAxisY = InMatrix.GetScaledAxis(EAxis::Z); 21 | FVector DestAxisZ = InMatrix.GetScaledAxis(EAxis::Y) * -1.0f; 22 | 23 | FMatrix Result(InMatrix); 24 | Result.SetAxes(&DestAxisX, &DestAxisY, &DestAxisZ); 25 | 26 | CameraTransform.SetFromMatrix(Result); 27 | } 28 | } 29 | 30 | FCameraStreamObject::FCameraStreamObject(const FBModel* ModelPointer) : 31 | FModelStreamObject(ModelPointer) 32 | { 33 | StreamingMode = FCameraStreamMode::Camera; 34 | } 35 | 36 | const FString FCameraStreamObject::GetStreamOptions() const 37 | { 38 | return FString::Join(CameraStreamOptions, _T("~")); 39 | }; 40 | 41 | void FCameraStreamObject::Refresh(const TSharedPtr Provider) 42 | { 43 | if (GetStreamingMode() == FCameraStreamMode::RootOnly) 44 | { 45 | FLiveLinkStaticDataStruct TransformData(FLiveLinkTransformStaticData::StaticStruct()); 46 | UpdateSubjectTransformStaticData(RootModel, bSendAnimatable, *TransformData.Cast()); 47 | Provider->UpdateSubjectStaticData(SubjectName, ULiveLinkTransformRole::StaticClass(), MoveTemp(TransformData)); 48 | } 49 | else if (GetStreamingMode() == FCameraStreamMode::FullHierarchy) 50 | { 51 | FLiveLinkStaticDataStruct SkeletonData(FLiveLinkSkeletonStaticData::StaticStruct()); 52 | UpdateSubjectSkeletalStaticData(*SkeletonData.Cast()); 53 | Provider->UpdateSubjectStaticData(SubjectName, ULiveLinkAnimationRole::StaticClass(), MoveTemp(SkeletonData)); 54 | } 55 | else 56 | { 57 | FLiveLinkStaticDataStruct CameraData(FLiveLinkCameraStaticData::StaticStruct()); 58 | FModelStreamObject::UpdateSubjectTransformStaticData(RootModel, bSendAnimatable, *CameraData.Cast()); 59 | UpdateSubjectCameraStaticData(static_cast(RootModel), *CameraData.Cast()); 60 | Provider->UpdateSubjectStaticData(SubjectName, ULiveLinkCameraRole::StaticClass(), MoveTemp(CameraData)); 61 | } 62 | } 63 | 64 | void FCameraStreamObject::UpdateSubjectFrame(const TSharedPtr Provider, FLiveLinkWorldTime WorldTime, FQualifiedFrameTime QualifiedFrameTime) 65 | { 66 | if (!bIsActive) 67 | { 68 | return; 69 | } 70 | 71 | if (GetStreamingMode() == FCameraStreamMode::RootOnly) 72 | { 73 | FLiveLinkFrameDataStruct TransformData = (FLiveLinkTransformFrameData::StaticStruct()); 74 | FLiveLinkTransformFrameData& CameraTransformData = *TransformData.Cast(); 75 | UpdateSubjectTransformFrameData(RootModel, bSendAnimatable, WorldTime, QualifiedFrameTime, CameraTransformData); 76 | FixCameraRotation(CameraTransformData.Transform); 77 | Provider->UpdateSubjectFrameData(SubjectName, MoveTemp(TransformData)); 78 | } 79 | else if (GetStreamingMode() == FCameraStreamMode::FullHierarchy) 80 | { 81 | FLiveLinkFrameDataStruct TransformData = (FLiveLinkAnimationFrameData::StaticStruct()); 82 | UpdateSubjectSkeletalFrameData(WorldTime, QualifiedFrameTime, *TransformData.Cast()); 83 | Provider->UpdateSubjectFrameData(SubjectName, MoveTemp(TransformData)); 84 | } 85 | else 86 | { 87 | FLiveLinkFrameDataStruct CameraData(FLiveLinkCameraFrameData::StaticStruct()); 88 | FModelStreamObject::UpdateSubjectTransformFrameData(RootModel, bSendAnimatable, WorldTime, QualifiedFrameTime, *CameraData.Cast()); 89 | UpdateSubjectCameraFrameData(static_cast(RootModel), *CameraData.Cast()); 90 | Provider->UpdateSubjectFrameData(SubjectName, MoveTemp(CameraData)); 91 | } 92 | } 93 | 94 | void FCameraStreamObject::UpdateSubjectCameraStaticData(const FBCamera* CameraModel, FLiveLinkCameraStaticData& InOutCameraStatic) 95 | { 96 | InOutCameraStatic.bIsFieldOfViewSupported = true; 97 | InOutCameraStatic.bIsAspectRatioSupported = true; 98 | InOutCameraStatic.bIsProjectionModeSupported = true; 99 | InOutCameraStatic.bIsFocalLengthSupported = true; 100 | 101 | double FilmSizeHeight, FilmSizeWidth; 102 | CameraModel->FilmSizeHeight.GetData(&FilmSizeHeight, sizeof(FilmSizeHeight), nullptr); 103 | CameraModel->FilmSizeWidth.GetData(&FilmSizeWidth, sizeof(FilmSizeWidth), nullptr); 104 | InOutCameraStatic.FilmBackWidth = (float)(FilmSizeWidth * MobuUtilities::InchesToMillimeters); 105 | InOutCameraStatic.FilmBackHeight = (float)(FilmSizeHeight * MobuUtilities::InchesToMillimeters); 106 | 107 | FBCameraFocusDistanceSource FocusMethod; 108 | CameraModel->FocusDistanceSource.GetData(&FocusMethod, sizeof(FocusMethod), nullptr); 109 | InOutCameraStatic.bIsFocusDistanceSupported = (FocusMethod == FBCameraFocusDistanceSource::kFBFocusDistanceSpecificDistance); 110 | } 111 | 112 | void FCameraStreamObject::UpdateSubjectCameraFrameData(const FBCamera* CameraModel, FLiveLinkCameraFrameData& InOutCameraFrame) 113 | { 114 | FixCameraRotation(InOutCameraFrame.Transform); 115 | 116 | double FieldOfView, FilmAspectRatio, FocalLength, FocusSpecificDistance; 117 | CameraModel->FieldOfView.GetData(&FieldOfView, sizeof(FieldOfView), nullptr); 118 | CameraModel->FilmAspectRatio.GetData(&FilmAspectRatio, sizeof(FilmAspectRatio), nullptr); 119 | CameraModel->FocalLength.GetData(&FocalLength, sizeof(FocalLength), nullptr); 120 | CameraModel->FocusSpecificDistance.GetData(&FocusSpecificDistance, sizeof(FocusSpecificDistance), nullptr); 121 | 122 | InOutCameraFrame.FieldOfView = FieldOfView; 123 | InOutCameraFrame.AspectRatio = FilmAspectRatio; 124 | InOutCameraFrame.FocalLength = FocalLength; 125 | InOutCameraFrame.FocusDistance = FocusSpecificDistance; 126 | 127 | FBCameraType CameraType; 128 | CameraModel->Type.GetData(&CameraType, sizeof(CameraType), nullptr); 129 | InOutCameraFrame.ProjectionMode = CameraType == FBCameraType::kFBCameraTypePerspective ? ELiveLinkCameraProjectionMode::Perspective : ELiveLinkCameraProjectionMode::Orthographic; 130 | } 131 | -------------------------------------------------------------------------------- /Source/StreamObjects/Private/EditorActiveCameraStreamObject.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | #include "EditorActiveCameraStreamObject.h" 4 | #include "MobuLiveLinkUtilities.h" 5 | 6 | #include "CameraStreamObject.h" 7 | #include "ModelStreamObject.h" 8 | 9 | #include "Roles/LiveLinkCameraRole.h" 10 | #include "Roles/LiveLinkCameraTypes.h" 11 | 12 | FEditorActiveCameraStreamObject::FEditorActiveCameraStreamObject() 13 | : SubjectName("EditorActiveCamera") 14 | , bIsActive(true) 15 | , bSendAnimatable(false) 16 | { 17 | } 18 | 19 | FEditorActiveCameraStreamObject::~FEditorActiveCameraStreamObject() 20 | { 21 | } 22 | 23 | const bool FEditorActiveCameraStreamObject::ShouldShowInUI() const 24 | { 25 | return false; 26 | } 27 | 28 | const FString FEditorActiveCameraStreamObject::GetStreamOptions() const 29 | { 30 | // Stream options are not valid on Editor camera 31 | return FString(); 32 | }; 33 | 34 | FName FEditorActiveCameraStreamObject::GetSubjectName() const 35 | { 36 | return SubjectName; 37 | }; 38 | 39 | void FEditorActiveCameraStreamObject::UpdateSubjectName(FName NewSubjectName) 40 | { 41 | // Subject name is not changeable on the Editor camera 42 | }; 43 | 44 | 45 | int FEditorActiveCameraStreamObject::GetStreamingMode() const 46 | { 47 | return 0; 48 | }; 49 | 50 | void FEditorActiveCameraStreamObject::UpdateStreamingMode(int NewStreamingMode) 51 | { 52 | // Streaming mode is not changeable on the Editor camera 53 | }; 54 | 55 | 56 | bool FEditorActiveCameraStreamObject::GetActiveStatus() const 57 | { 58 | return bIsActive; 59 | }; 60 | 61 | void FEditorActiveCameraStreamObject::UpdateActiveStatus(bool bIsNowActive) 62 | { 63 | bIsActive = bIsNowActive; 64 | }; 65 | 66 | bool FEditorActiveCameraStreamObject::GetSendAnimatableStatus() const 67 | { 68 | return bSendAnimatable; 69 | }; 70 | 71 | void FEditorActiveCameraStreamObject::UpdateSendAnimatableStatus(bool bNewSendAnimatable) 72 | { 73 | if (bSendAnimatable != bNewSendAnimatable) 74 | { 75 | bSendAnimatable = bNewSendAnimatable; 76 | } 77 | }; 78 | 79 | const FBModel* FEditorActiveCameraStreamObject::GetModelPointer() const 80 | { 81 | return nullptr; 82 | }; 83 | 84 | const FString FEditorActiveCameraStreamObject::GetRootName() const 85 | { 86 | // Root Name is not valid on Editor camera 87 | return FString(); 88 | } 89 | 90 | 91 | bool FEditorActiveCameraStreamObject::IsValid() const 92 | { 93 | // Editor camera is always valid 94 | return true; 95 | } 96 | 97 | void FEditorActiveCameraStreamObject::Refresh(const TSharedPtr Provider) 98 | { 99 | if (!bIsActive) 100 | { 101 | return; 102 | } 103 | 104 | FBSystem System; 105 | const FBCamera* CameraModel = System.Scene->Renderer->GetCameraInPane(0); 106 | 107 | if (CameraModel) 108 | { 109 | FLiveLinkStaticDataStruct CameraData(FLiveLinkCameraStaticData::StaticStruct()); 110 | FModelStreamObject::UpdateSubjectTransformStaticData(CameraModel, bSendAnimatable, *CameraData.Cast()); 111 | FCameraStreamObject::UpdateSubjectCameraStaticData(CameraModel, *CameraData.Cast()); 112 | Provider->UpdateSubjectStaticData(SubjectName, ULiveLinkCameraRole::StaticClass(), MoveTemp(CameraData)); 113 | } 114 | } 115 | 116 | void FEditorActiveCameraStreamObject::UpdateSubjectFrame(const TSharedPtr Provider, FLiveLinkWorldTime WorldTime, FQualifiedFrameTime QualifiedFrameTime) 117 | { 118 | if (!bIsActive) 119 | { 120 | return; 121 | } 122 | 123 | FBSystem System; 124 | const FBCamera* CameraModel = System.Scene->Renderer->GetCameraInPane(0); 125 | 126 | if (CameraModel) 127 | { 128 | FLiveLinkFrameDataStruct CameraData(FLiveLinkCameraFrameData::StaticStruct()); 129 | FModelStreamObject::UpdateSubjectTransformFrameData(CameraModel, bSendAnimatable, WorldTime, QualifiedFrameTime, *CameraData.Cast()); 130 | FCameraStreamObject::UpdateSubjectCameraFrameData(CameraModel, *CameraData.Cast()); 131 | Provider->UpdateSubjectFrameData(SubjectName, MoveTemp(CameraData)); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /Source/StreamObjects/Private/LightStreamObject.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | #include "LightStreamObject.h" 4 | #include "MobuLiveLinkUtilities.h" 5 | 6 | #include "Roles/LiveLinkAnimationRole.h" 7 | #include "Roles/LiveLinkAnimationTypes.h" 8 | #include "Roles/LiveLinkLightRole.h" 9 | #include "Roles/LiveLinkLightTypes.h" 10 | #include "Roles/LiveLinkTransformRole.h" 11 | #include "Roles/LiveLinkTransformTypes.h" 12 | 13 | FLightStreamObject::FLightStreamObject(const FBModel* ModelPointer) : 14 | FModelStreamObject(ModelPointer) 15 | { 16 | StreamingMode = FLightStreamMode::Light; 17 | }; 18 | 19 | const FString FLightStreamObject::GetStreamOptions() const 20 | { 21 | return FString::Join(LightStreamOptions, _T("~")); 22 | }; 23 | 24 | void FLightStreamObject::Refresh(const TSharedPtr Provider) 25 | { 26 | if (GetStreamingMode() == FLightStreamMode::RootOnly) 27 | { 28 | FLiveLinkStaticDataStruct TransformData(FLiveLinkTransformStaticData::StaticStruct()); 29 | UpdateSubjectTransformStaticData(RootModel, bSendAnimatable, *TransformData.Cast()); 30 | Provider->UpdateSubjectStaticData(SubjectName, ULiveLinkTransformRole::StaticClass(), MoveTemp(TransformData)); 31 | } 32 | else if (GetStreamingMode() == FLightStreamMode::FullHierarchy) 33 | { 34 | FLiveLinkStaticDataStruct SkeletonData(FLiveLinkSkeletonStaticData::StaticStruct()); 35 | UpdateSubjectSkeletalStaticData(*SkeletonData.Cast()); 36 | Provider->UpdateSubjectStaticData(SubjectName, ULiveLinkAnimationRole::StaticClass(), MoveTemp(SkeletonData)); 37 | } 38 | else 39 | { 40 | FLiveLinkStaticDataStruct LightData(FLiveLinkLightStaticData::StaticStruct()); 41 | FModelStreamObject::UpdateSubjectTransformStaticData(RootModel, bSendAnimatable, *LightData.Cast()); 42 | UpdateSubjectLightStaticData(static_cast(RootModel), *LightData.Cast()); 43 | Provider->UpdateSubjectStaticData(SubjectName, ULiveLinkLightRole::StaticClass(), MoveTemp(LightData)); 44 | } 45 | }; 46 | 47 | void FLightStreamObject::UpdateSubjectFrame(const TSharedPtr Provider, FLiveLinkWorldTime WorldTime, FQualifiedFrameTime QualifiedFrameTime) 48 | { 49 | if (!bIsActive) 50 | { 51 | return; 52 | } 53 | 54 | if (GetStreamingMode() == FLightStreamMode::RootOnly) 55 | { 56 | FLiveLinkFrameDataStruct TransformData = (FLiveLinkTransformFrameData::StaticStruct()); 57 | UpdateSubjectTransformFrameData(RootModel, bSendAnimatable, WorldTime, QualifiedFrameTime, *TransformData.Cast()); 58 | Provider->UpdateSubjectFrameData(SubjectName, MoveTemp(TransformData)); 59 | } 60 | else if (GetStreamingMode() == FLightStreamMode::FullHierarchy) 61 | { 62 | FLiveLinkFrameDataStruct TransformData = (FLiveLinkAnimationFrameData::StaticStruct()); 63 | UpdateSubjectSkeletalFrameData(WorldTime, QualifiedFrameTime, *TransformData.Cast()); 64 | Provider->UpdateSubjectFrameData(SubjectName, MoveTemp(TransformData)); 65 | } 66 | else 67 | { 68 | FLiveLinkFrameDataStruct LightData(FLiveLinkLightFrameData::StaticStruct()); 69 | FModelStreamObject::UpdateSubjectTransformFrameData(const_cast(RootModel), bSendAnimatable, WorldTime, QualifiedFrameTime, *LightData.Cast()); 70 | UpdateSubjectLightFrameData(static_cast(RootModel), *LightData.Cast()); 71 | Provider->UpdateSubjectFrameData(SubjectName, MoveTemp(LightData)); 72 | } 73 | } 74 | 75 | void FLightStreamObject::UpdateSubjectLightStaticData(const FBLight* LightModel, FLiveLinkLightStaticData& InOutLightFrame) 76 | { 77 | InOutLightFrame.bIsIntensitySupported = true; 78 | InOutLightFrame.bIsLightColorSupported = true; 79 | 80 | FBLightType LightType; 81 | LightModel->LightType.GetData(&LightType, sizeof(LightType), nullptr); 82 | InOutLightFrame.bIsInnerConeAngleSupported = (LightType == FBLightType::kFBLightTypeSpot); 83 | InOutLightFrame.bIsOuterConeAngleSupported = (LightType == FBLightType::kFBLightTypeSpot); 84 | } 85 | 86 | void FLightStreamObject::UpdateSubjectLightFrameData(const FBLight* LightModel, FLiveLinkLightFrameData& InOutLightFrame) 87 | { 88 | double Intensity; 89 | LightModel->Intensity.GetData(&Intensity, sizeof(Intensity), nullptr); 90 | 91 | FBColor DiffuseColor; 92 | LightModel->DiffuseColor.GetData(&DiffuseColor, sizeof(DiffuseColor), nullptr); 93 | 94 | InOutLightFrame.Intensity = Intensity; 95 | InOutLightFrame.LightColor = MobuUtilities::MobuColorToUnreal(DiffuseColor); 96 | 97 | FBLightType LightType; 98 | LightModel->LightType.GetData(&LightType, sizeof(LightType), nullptr); 99 | if (LightType == FBLightType::kFBLightTypeSpot) 100 | { 101 | InOutLightFrame.InnerConeAngle = LightModel->InnerAngle; 102 | InOutLightFrame.OuterConeAngle = LightModel->OuterAngle; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Source/StreamObjects/Private/ModelStreamObject.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | #include "ModelStreamObject.h" 4 | #include "MobuLiveLinkUtilities.h" 5 | #include 6 | 7 | #include "Roles/LiveLinkAnimationRole.h" 8 | #include "Roles/LiveLinkAnimationTypes.h" 9 | #include "Roles/LiveLinkTransformRole.h" 10 | #include "Roles/LiveLinkTransformTypes.h" 11 | 12 | // Creation / Destruction 13 | FModelStreamObject::FModelStreamObject(const FBModel* ModelPointer) 14 | : RootModel(ModelPointer) 15 | , bIsActive(true) 16 | , bSendAnimatable(false) 17 | , StreamingMode(FModelStreamMode::RootOnly) 18 | { 19 | check(ModelPointer); 20 | 21 | FString ModelLongName(ANSI_TO_TCHAR(RootModel->LongName)); 22 | FString RightString; 23 | ModelLongName.Split(TEXT(":"), &ModelLongName, &RightString); 24 | SubjectName = FName(*ModelLongName); 25 | }; 26 | 27 | FModelStreamObject::~FModelStreamObject() 28 | { 29 | }; 30 | 31 | // Stream Object Interface 32 | 33 | const bool FModelStreamObject::ShouldShowInUI() const 34 | { 35 | return true; 36 | }; 37 | 38 | const FString FModelStreamObject::GetStreamOptions() const 39 | { 40 | return FString::Join(ModelStreamOptions, _T("~")); 41 | }; 42 | 43 | FName FModelStreamObject::GetSubjectName() const 44 | { 45 | return SubjectName; 46 | }; 47 | 48 | void FModelStreamObject::UpdateSubjectName(FName NewSubjectName) 49 | { 50 | if (NewSubjectName != SubjectName) 51 | { 52 | SubjectName = NewSubjectName; 53 | } 54 | }; 55 | 56 | 57 | int FModelStreamObject::GetStreamingMode() const 58 | { 59 | return StreamingMode; 60 | }; 61 | 62 | void FModelStreamObject::UpdateStreamingMode(int NewStreamingMode) 63 | { 64 | if (StreamingMode != NewStreamingMode) 65 | { 66 | StreamingMode = NewStreamingMode; 67 | } 68 | }; 69 | 70 | bool FModelStreamObject::GetActiveStatus() const 71 | { 72 | return bIsActive; 73 | }; 74 | 75 | void FModelStreamObject::UpdateActiveStatus(bool bIsNowActive) 76 | { 77 | bIsActive = bIsNowActive; 78 | }; 79 | 80 | bool FModelStreamObject::GetSendAnimatableStatus() const 81 | { 82 | return bSendAnimatable; 83 | }; 84 | 85 | void FModelStreamObject::UpdateSendAnimatableStatus(bool bNewSendAnimatable) 86 | { 87 | if (bSendAnimatable != bNewSendAnimatable) 88 | { 89 | bSendAnimatable = bNewSendAnimatable; 90 | } 91 | }; 92 | 93 | const FBModel* FModelStreamObject::GetModelPointer() const 94 | { 95 | return RootModel; 96 | }; 97 | 98 | const FString FModelStreamObject::GetRootName() const 99 | { 100 | return FString(ANSI_TO_TCHAR(RootModel->LongName)); 101 | }; 102 | 103 | bool FModelStreamObject::IsValid() const 104 | { 105 | // By Default an object is valid if the root model is in the scene 106 | return FBSystem().Scene->Components.Find((FBComponent*)RootModel) >= 0; 107 | }; 108 | 109 | void FModelStreamObject::Refresh(const TSharedPtr Provider) 110 | { 111 | if (GetStreamingMode() == FModelStreamMode::FullHierarchy) 112 | { 113 | FLiveLinkStaticDataStruct SkeletonData(FLiveLinkSkeletonStaticData::StaticStruct()); 114 | UpdateSubjectSkeletalStaticData(*SkeletonData.Cast()); 115 | Provider->UpdateSubjectStaticData(SubjectName, ULiveLinkAnimationRole::StaticClass(), MoveTemp(SkeletonData)); 116 | } 117 | else 118 | { 119 | FLiveLinkStaticDataStruct TransformData(FLiveLinkTransformStaticData::StaticStruct()); 120 | UpdateSubjectTransformStaticData(RootModel, bSendAnimatable, *TransformData.Cast()); 121 | Provider->UpdateSubjectStaticData(SubjectName, ULiveLinkTransformRole::StaticClass(), MoveTemp(TransformData)); 122 | } 123 | } 124 | 125 | void FModelStreamObject::UpdateSubjectFrame(const TSharedPtr Provider, FLiveLinkWorldTime WorldTime, FQualifiedFrameTime QualifiedFrameTime) 126 | { 127 | if (!bIsActive) 128 | { 129 | return; 130 | } 131 | 132 | if (GetStreamingMode() == FModelStreamMode::FullHierarchy) 133 | { 134 | FLiveLinkFrameDataStruct TransformData = (FLiveLinkAnimationFrameData::StaticStruct()); 135 | UpdateSubjectSkeletalFrameData(WorldTime, QualifiedFrameTime, *TransformData.Cast()); 136 | Provider->UpdateSubjectFrameData(SubjectName, MoveTemp(TransformData)); 137 | } 138 | else 139 | { 140 | FLiveLinkFrameDataStruct TransformData = (FLiveLinkTransformFrameData::StaticStruct()); 141 | UpdateSubjectTransformFrameData(RootModel, bSendAnimatable, WorldTime, QualifiedFrameTime, *TransformData.Cast()); 142 | Provider->UpdateSubjectFrameData(SubjectName, MoveTemp(TransformData)); 143 | } 144 | } 145 | 146 | void FModelStreamObject::UpdateBaseStaticData(const FBModel* Model, bool bSendAnimatable, FLiveLinkBaseStaticData& InOutBaseStaticData) 147 | { 148 | if (bSendAnimatable) 149 | { 150 | InOutBaseStaticData.PropertyNames = MobuUtilities::GetAllAnimatableCurveNames(const_cast(Model), FString(ANSI_TO_TCHAR(Model->Name))); 151 | } 152 | } 153 | 154 | void FModelStreamObject::UpdateSubjectTransformStaticData(const FBModel* Model, bool bSendAnimatable, FLiveLinkTransformStaticData& InOutTransformStatic) 155 | { 156 | InOutTransformStatic.bIsScaleSupported = true; 157 | UpdateBaseStaticData(Model, bSendAnimatable, InOutTransformStatic); 158 | } 159 | 160 | void FModelStreamObject::UpdateSubjectSkeletalStaticData(FLiveLinkSkeletonStaticData& InOutAnimationStatic) 161 | { 162 | UpdateBaseStaticData(RootModel, bSendAnimatable, InOutAnimationStatic); 163 | 164 | InOutAnimationStatic.BoneNames.Reset(); 165 | BoneParents.Reset(); 166 | BoneModels.Reset(); 167 | 168 | InOutAnimationStatic.BoneNames.Emplace(RootModel->Name); 169 | BoneParents.Emplace(-1); 170 | BoneModels.Emplace(RootModel); 171 | 172 | { 173 | TArray> SearchList; 174 | TArray> SearchListNext; 175 | 176 | SearchList.Emplace(0, (FBModel*)RootModel); 177 | 178 | while (SearchList.Num() > 0) 179 | { 180 | for (const TPair& SearchPair : SearchList) 181 | { 182 | int ParentIdx = SearchPair.Key; 183 | FBModel* SearchModel = SearchPair.Value; 184 | int ChildCount = SearchModel->Children.GetCount(); 185 | 186 | for (int ChildIdx = 0; ChildIdx < ChildCount; ++ChildIdx) 187 | { 188 | FBModel* ChildModel = SearchModel->Children[ChildIdx]; 189 | 190 | InOutAnimationStatic.BoneNames.Emplace(ChildModel->Name); 191 | BoneParents.Emplace(ParentIdx); 192 | BoneModels.Emplace(ChildModel); 193 | 194 | SearchListNext.Emplace(BoneModels.Num() - 1, ChildModel); 195 | } 196 | } 197 | SearchList = SearchListNext; 198 | SearchListNext.Reset(); 199 | } 200 | } 201 | 202 | InOutAnimationStatic.BoneParents = BoneParents; 203 | 204 | check(BoneModels.Num() == InOutAnimationStatic.BoneNames.Num()); 205 | if (bSendAnimatable) 206 | { 207 | for (int32 BoneIndex = 0; BoneIndex < BoneModels.Num(); ++BoneIndex) 208 | { 209 | InOutAnimationStatic.PropertyNames.Append(MobuUtilities::GetAllAnimatableCurveNames(const_cast(BoneModels[BoneIndex]), InOutAnimationStatic.BoneNames[BoneIndex].ToString())); 210 | } 211 | } 212 | } 213 | 214 | void FModelStreamObject::UpdateBaseFrameData(const FBModel* Model, bool bSendAnimatable, FLiveLinkWorldTime WorldTime, FQualifiedFrameTime QualifiedFrameTime, FLiveLinkBaseFrameData& InOutBaseFrameData) 215 | { 216 | InOutBaseFrameData.WorldTime = WorldTime; 217 | InOutBaseFrameData.MetaData.SceneTime = QualifiedFrameTime; 218 | if (bSendAnimatable) 219 | { 220 | InOutBaseFrameData.PropertyValues = MobuUtilities::GetAllAnimatableCurveValues(const_cast(Model)); 221 | } 222 | } 223 | 224 | void FModelStreamObject::UpdateSubjectTransformFrameData(const FBModel* Model, bool bSendAnimatable, FLiveLinkWorldTime WorldTime, FQualifiedFrameTime QualifiedFrameTime, FLiveLinkTransformFrameData& InOutTransformFrame) 225 | { 226 | UpdateBaseFrameData(Model, bSendAnimatable, WorldTime, QualifiedFrameTime, InOutTransformFrame); 227 | InOutTransformFrame.Transform = MobuUtilities::UnrealTransformFromModel(const_cast(Model)); 228 | } 229 | 230 | void FModelStreamObject::UpdateSubjectSkeletalFrameData(FLiveLinkWorldTime WorldTime, FQualifiedFrameTime QualifiedFrameTime, FLiveLinkAnimationFrameData& InOutAnimationFrame) 231 | { 232 | UpdateBaseFrameData(RootModel, bSendAnimatable, WorldTime, QualifiedFrameTime, InOutAnimationFrame); 233 | 234 | if (BoneParents.Num() != BoneModels.Num()) 235 | { 236 | return; 237 | } 238 | 239 | const int32 BoneCount = BoneParents.Num(); 240 | InOutAnimationFrame.Transforms.SetNum(BoneCount); 241 | 242 | TArray ParentInverseTransforms; 243 | ParentInverseTransforms.SetNum(BoneCount); 244 | 245 | // loop through children here 246 | for (int BoneIndex = 0; BoneIndex < BoneModels.Num(); ++BoneIndex) 247 | { 248 | InOutAnimationFrame.Transforms[BoneIndex] = MobuUtilities::UnrealTransformFromModel(const_cast(BoneModels[BoneIndex])); 249 | 250 | // 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() 251 | if (InOutAnimationFrame.Transforms[BoneIndex].ContainsNaN()) 252 | { 253 | FBTrace("ERROR - Subject %s contains NaNs - %s\n", TCHAR_TO_UTF8(*SubjectName.ToString()), TCHAR_TO_UTF8(*InOutAnimationFrame.Transforms[BoneIndex].ToString())); 254 | ParentInverseTransforms[BoneIndex].SetIdentity(); 255 | InOutAnimationFrame.Transforms[BoneIndex].SetIdentity(); 256 | } 257 | else 258 | { 259 | ParentInverseTransforms[BoneIndex] = InOutAnimationFrame.Transforms[BoneIndex].Inverse(); 260 | if (BoneParents[BoneIndex] != -1) 261 | { 262 | InOutAnimationFrame.Transforms[BoneIndex] = InOutAnimationFrame.Transforms[BoneIndex] * ParentInverseTransforms[BoneParents[BoneIndex]]; 263 | } 264 | } 265 | 266 | if (bSendAnimatable) 267 | { 268 | // Stream all parameters of all bones as ":" 269 | InOutAnimationFrame.PropertyValues.Append(MobuUtilities::GetAllAnimatableCurveValues(const_cast(BoneModels[BoneIndex]))); 270 | } 271 | } 272 | } -------------------------------------------------------------------------------- /Source/StreamObjects/Private/SkeletonHierarchyStreamObject.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | #include "SkeletonHierarchyStreamObject.h" 4 | #include "MobuLiveLinkUtilities.h" 5 | 6 | #include "Roles/LiveLinkAnimationRole.h" 7 | #include "Roles/LiveLinkAnimationTypes.h" 8 | 9 | FSkeletonHierarchyStreamObject::FSkeletonHierarchyStreamObject(const FBModel* ModelPointer) : 10 | FModelStreamObject(ModelPointer) 11 | { 12 | StreamingMode = FSkeletonStreamMode::SkeletonHierarchy; 13 | }; 14 | 15 | const FString FSkeletonHierarchyStreamObject::GetStreamOptions() const 16 | { 17 | return FString::Join(SkeletonStreamOptions, _T("~")); 18 | }; 19 | 20 | void FSkeletonHierarchyStreamObject::Refresh(const TSharedPtr Provider) 21 | { 22 | BoneNames.Empty(); 23 | BoneParents.Empty(); 24 | BoneModels.Empty(); 25 | 26 | if (StreamingMode == FSkeletonStreamMode::RootOnly) 27 | { 28 | FModelStreamObject::Refresh(Provider); 29 | } 30 | else 31 | { 32 | FLiveLinkStaticDataStruct AnimationData = (FLiveLinkSkeletonStaticData::StaticStruct()); 33 | FModelStreamObject::UpdateBaseStaticData(RootModel, bSendAnimatable, *AnimationData.Cast()); 34 | UpdateSubjectStaticData(*AnimationData.Cast()); 35 | Provider->UpdateSubjectStaticData(SubjectName, ULiveLinkAnimationRole::StaticClass(), MoveTemp(AnimationData)); 36 | } 37 | }; 38 | 39 | void FSkeletonHierarchyStreamObject::UpdateSubjectFrame(const TSharedPtr Provider, FLiveLinkWorldTime WorldTime, FQualifiedFrameTime QualifiedFrameTime) 40 | { 41 | if (!bIsActive) 42 | { 43 | return; 44 | } 45 | 46 | if (StreamingMode == FSkeletonStreamMode::RootOnly) 47 | { 48 | FModelStreamObject::UpdateSubjectFrame(Provider, WorldTime, QualifiedFrameTime); 49 | } 50 | else 51 | { 52 | FLiveLinkFrameDataStruct AnimationData(FLiveLinkAnimationFrameData::StaticStruct()); 53 | FModelStreamObject::UpdateBaseFrameData(RootModel, bSendAnimatable, WorldTime, QualifiedFrameTime, *AnimationData.Cast()); 54 | UpdateSubjectFrameData(*AnimationData.Cast()); 55 | Provider->UpdateSubjectFrameData(SubjectName, MoveTemp(AnimationData)); 56 | } 57 | }; 58 | 59 | void FSkeletonHierarchyStreamObject::UpdateSubjectStaticData(FLiveLinkSkeletonStaticData& InOutAnimationFrame) 60 | { 61 | BoneNames.Emplace(RootModel->Name); 62 | BoneParents.Emplace(-1); 63 | BoneModels.Emplace(RootModel); 64 | 65 | TArray> SearchList; 66 | TArray> SearchListNext; 67 | 68 | SearchList.Emplace(0, (FBModel*)RootModel); 69 | 70 | while (SearchList.Num() > 0) 71 | { 72 | for (const TPair& SearchPair : SearchList) 73 | { 74 | int ParentIdx = SearchPair.Key; 75 | FBModel* SearchModel = SearchPair.Value; 76 | int ChildCount = SearchModel->Children.GetCount(); 77 | for (int ChildIdx = 0; ChildIdx < ChildCount; ++ChildIdx) 78 | { 79 | FBModel* ChildModel = SearchModel->Children[ChildIdx]; 80 | 81 | if (StreamingMode == FSkeletonStreamMode::SkeletonHierarchy) 82 | { 83 | // Only want joints when streaming Skeletal Hierarchy 84 | int ChildModelType = ChildModel->GetTypeId(); 85 | if (!(ChildModelType == FBModelSkeleton::TypeInfo || ChildModelType == FBModelRoot::TypeInfo)) 86 | { 87 | continue; 88 | } 89 | } 90 | 91 | BoneNames.Emplace(ChildModel->Name); 92 | BoneParents.Emplace(ParentIdx); 93 | BoneModels.Emplace(ChildModel); 94 | 95 | SearchListNext.Emplace(BoneModels.Num() - 1, ChildModel); 96 | } 97 | } 98 | SearchList = SearchListNext; 99 | SearchListNext.Reset(); 100 | } 101 | 102 | InOutAnimationFrame.BoneNames = BoneNames; 103 | InOutAnimationFrame.BoneParents = BoneParents; 104 | 105 | if (bSendAnimatable) 106 | { 107 | for (int BoneIndex = 0; BoneIndex < BoneModels.Num(); ++BoneIndex) 108 | { 109 | const FBModel* Model = BoneModels[BoneIndex]; 110 | InOutAnimationFrame.PropertyNames.Append(MobuUtilities::GetAllAnimatableCurveNames(const_cast(BoneModels[BoneIndex]), BoneNames[BoneIndex].ToString())); 111 | } 112 | } 113 | } 114 | 115 | void FSkeletonHierarchyStreamObject::UpdateSubjectFrameData(FLiveLinkAnimationFrameData& InOutAnimationFrame) 116 | { 117 | int BoneCount = BoneNames.Num(); 118 | InOutAnimationFrame.Transforms.SetNum(BoneCount); 119 | 120 | TArray ParentInverseTransforms; 121 | ParentInverseTransforms.SetNum(BoneCount); 122 | 123 | // loop through children here 124 | for (int BoneIndex = 0; BoneIndex < BoneModels.Num(); ++BoneIndex) 125 | { 126 | InOutAnimationFrame.Transforms[BoneIndex] = MobuUtilities::UnrealTransformFromModel(const_cast(BoneModels[BoneIndex])); 127 | 128 | // 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() 129 | if (InOutAnimationFrame.Transforms[BoneIndex].ContainsNaN()) 130 | { 131 | FBTrace("ERROR - Bone %s for Subject %s contains NaNs - %s\n", TCHAR_TO_UTF8(*BoneNames[BoneIndex].ToString()), TCHAR_TO_UTF8(*SubjectName.ToString()), TCHAR_TO_UTF8(*InOutAnimationFrame.Transforms[BoneIndex].ToString())); 132 | ParentInverseTransforms[BoneIndex].SetIdentity(); 133 | InOutAnimationFrame.Transforms[BoneIndex].SetIdentity(); 134 | } 135 | else 136 | { 137 | ParentInverseTransforms[BoneIndex] = InOutAnimationFrame.Transforms[BoneIndex].Inverse(); 138 | if (BoneParents[BoneIndex] != -1) 139 | { 140 | InOutAnimationFrame.Transforms[BoneIndex] = InOutAnimationFrame.Transforms[BoneIndex] * ParentInverseTransforms[BoneParents[BoneIndex]]; 141 | } 142 | } 143 | 144 | if (bSendAnimatable) 145 | { 146 | // Stream all parameters of all bones as ":" 147 | InOutAnimationFrame.PropertyValues.Append(MobuUtilities::GetAllAnimatableCurveValues(const_cast(BoneModels[BoneIndex]))); 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /Source/StreamObjects/Public/CameraStreamObject.h: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "ModelStreamObject.h" 6 | 7 | struct FLiveLinkCameraStaticData; 8 | struct FLiveLinkCameraFrameData; 9 | 10 | // FBCamera wrapper 11 | class FCameraStreamObject : public FModelStreamObject 12 | { 13 | private: 14 | const TArray CameraStreamOptions = { TEXT("Root Only"), TEXT("Camera"), TEXT("Full Hierarchy") }; 15 | 16 | enum FCameraStreamMode 17 | { 18 | RootOnly, 19 | Camera, 20 | FullHierarchy 21 | }; 22 | 23 | public: 24 | FCameraStreamObject(const FBModel* ModelPointer); 25 | virtual const FString GetStreamOptions() const override; 26 | virtual void Refresh(const TSharedPtr Provider) override; 27 | virtual void UpdateSubjectFrame(const TSharedPtr Provider, FLiveLinkWorldTime WorldTime, FQualifiedFrameTime QualifiedFrameTime) override; 28 | 29 | public: 30 | static void UpdateSubjectCameraStaticData(const FBCamera* CameraModel, FLiveLinkCameraStaticData& InOutCameraStatic); 31 | static void UpdateSubjectCameraFrameData(const FBCamera* CameraModel, FLiveLinkCameraFrameData& InOutCameraFrame); 32 | }; -------------------------------------------------------------------------------- /Source/StreamObjects/Public/EditorActiveCameraStreamObject.h: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "IStreamObject.h" 6 | 7 | // Wrapper for Streaming the Active Editor Camera 8 | class FEditorActiveCameraStreamObject : public IStreamObject 9 | { 10 | public: 11 | FEditorActiveCameraStreamObject(); 12 | virtual ~FEditorActiveCameraStreamObject(); 13 | 14 | // IStreamObject Interface 15 | 16 | const bool ShouldShowInUI() const final; 17 | 18 | const FString GetStreamOptions() const final; 19 | 20 | FName GetSubjectName() const final; 21 | void UpdateSubjectName(FName NewSubjectName) final; 22 | 23 | int GetStreamingMode() const final; 24 | void UpdateStreamingMode(int NewStreamingMode) final; 25 | 26 | bool GetActiveStatus() const final; 27 | void UpdateActiveStatus(bool bIsNowActive) final; 28 | 29 | virtual bool GetSendAnimatableStatus() const final; 30 | virtual void UpdateSendAnimatableStatus(bool bNewSendAnimatable) final; 31 | 32 | const FBModel* GetModelPointer() const final; 33 | 34 | const FString GetRootName() const final; 35 | 36 | bool IsValid() const final; 37 | 38 | void Refresh(const TSharedPtr Provider) final; 39 | void UpdateSubjectFrame(const TSharedPtr Provider, FLiveLinkWorldTime WorldTime, FQualifiedFrameTime QualifiedFrameTime) final; 40 | 41 | private: 42 | 43 | const FName SubjectName; 44 | bool bIsActive; 45 | bool bSendAnimatable; 46 | }; -------------------------------------------------------------------------------- /Source/StreamObjects/Public/LightStreamObject.h: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "ModelStreamObject.h" 6 | 7 | struct FLiveLinkLightStaticData; 8 | struct FLiveLinkLightFrameData; 9 | 10 | // FBLight wrapper 11 | class FLightStreamObject : public FModelStreamObject 12 | { 13 | private: 14 | const TArray LightStreamOptions = { TEXT("Root Only"), TEXT("Light"), TEXT("Full Hierarchy") }; 15 | 16 | enum FLightStreamMode 17 | { 18 | RootOnly, 19 | Light, 20 | FullHierarchy, 21 | }; 22 | 23 | public: 24 | FLightStreamObject(const FBModel* ModelPointer); 25 | virtual const FString GetStreamOptions() const override; 26 | virtual void Refresh(const TSharedPtr Provider) override; 27 | virtual void UpdateSubjectFrame(const TSharedPtr Provider, FLiveLinkWorldTime WorldTime, FQualifiedFrameTime QualifiedFrameTime) override; 28 | 29 | protected: 30 | void UpdateSubjectLightStaticData(const FBLight* LightModel, FLiveLinkLightStaticData& InOutCameraFrame); 31 | void UpdateSubjectLightFrameData(const FBLight* LightModel, FLiveLinkLightFrameData& InOutCameraFrame); 32 | }; -------------------------------------------------------------------------------- /Source/StreamObjects/Public/ModelStreamObject.h: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "IStreamObject.h" 6 | 7 | struct FLiveLinkSkeletonStaticData; 8 | struct FLiveLinkAnimationFrameData; 9 | struct FLiveLinkTransformStaticData; 10 | struct FLiveLinkTransformFrameData; 11 | 12 | // Generic object that supports FBModels 13 | // Either used for simple objects where no more specific class exists (Nulls, etc.) 14 | // or used as a base class for StreamObjects who's Root object derives from FBModel 15 | class FModelStreamObject : public IStreamObject 16 | { 17 | private: 18 | const TArray ModelStreamOptions = { TEXT("Root Only"), TEXT("Full Hierarchy") }; 19 | 20 | enum FModelStreamMode 21 | { 22 | RootOnly, 23 | FullHierarchy, 24 | }; 25 | 26 | public: 27 | // Construct from a FBModel* 28 | FModelStreamObject(const FBModel* ModelPointer); 29 | 30 | virtual ~FModelStreamObject(); 31 | 32 | // IStreamObject Interface 33 | 34 | virtual const bool ShouldShowInUI() const override; 35 | 36 | virtual const FString GetStreamOptions() const override; 37 | 38 | virtual FName GetSubjectName() const override; 39 | virtual void UpdateSubjectName(FName NewSubjectName) override; 40 | 41 | virtual int GetStreamingMode() const override; 42 | virtual void UpdateStreamingMode(int NewStreamingMode) override; 43 | 44 | virtual bool GetActiveStatus() const override; 45 | virtual void UpdateActiveStatus(bool bIsNowActive) override; 46 | 47 | virtual bool GetSendAnimatableStatus() const override; 48 | virtual void UpdateSendAnimatableStatus(bool bNewSendAnimatable) override; 49 | 50 | virtual const FBModel* GetModelPointer() const override; 51 | 52 | virtual const FString GetRootName() const override; 53 | 54 | virtual bool IsValid() const override; 55 | 56 | virtual void Refresh(const TSharedPtr Provider) override; 57 | virtual void UpdateSubjectFrame(const TSharedPtr Provider, FLiveLinkWorldTime WorldTime, FQualifiedFrameTime QualifiedFrameTime) override; 58 | 59 | public: 60 | static void UpdateBaseStaticData(const FBModel* Model, bool bSendAnimatable, FLiveLinkBaseStaticData& InOutBaseFrameData); 61 | static void UpdateBaseFrameData(const FBModel* Model, bool bSendAnimatable, FLiveLinkWorldTime WorldTime, FQualifiedFrameTime QualifiedFrameTime, FLiveLinkBaseFrameData& InOutBaseFrameData); 62 | void UpdateSubjectSkeletalStaticData(FLiveLinkSkeletonStaticData& InOutTransformFrame); 63 | void UpdateSubjectSkeletalFrameData(FLiveLinkWorldTime WorldTime, FQualifiedFrameTime QualifiedFrameTime, FLiveLinkAnimationFrameData& InOutTransformFrame); 64 | static void UpdateSubjectTransformStaticData(const FBModel* Model, bool bSendAnimatable, FLiveLinkTransformStaticData& InOutTransformFrame); 65 | static void UpdateSubjectTransformFrameData(const FBModel* Model, bool bSendAnimatable, FLiveLinkWorldTime WorldTime, FQualifiedFrameTime QualifiedFrameTime, FLiveLinkTransformFrameData& InOutTransformFrame); 66 | 67 | protected: 68 | // Stream Variables 69 | const FBModel* const RootModel; 70 | 71 | FName SubjectName; 72 | TArray BoneParents; 73 | TArray BoneModels; 74 | bool bIsActive; 75 | bool bSendAnimatable; 76 | int StreamingMode; 77 | }; 78 | -------------------------------------------------------------------------------- /Source/StreamObjects/Public/SkeletonHierarchyStreamObject.h: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "ModelStreamObject.h" 6 | 7 | struct FLiveLinkSkeletonStaticData; 8 | struct FLiveLinkAnimationFrameData; 9 | 10 | // FBModelSkeleton and FBModelRoot wrapper 11 | class FSkeletonHierarchyStreamObject : public FModelStreamObject 12 | { 13 | private: 14 | const TArray SkeletonStreamOptions = { TEXT("Root Only"), TEXT("Full Hierarchy"), TEXT("Skeleton Hierarchy") }; 15 | 16 | enum FSkeletonStreamMode 17 | { 18 | RootOnly, 19 | FullHierarchy, 20 | SkeletonHierarchy 21 | }; 22 | 23 | public: 24 | FSkeletonHierarchyStreamObject(const FBModel* ModelPointer); 25 | 26 | virtual const FString GetStreamOptions() const override; 27 | 28 | // Override Refresh to only add Skeletal Children to the stream Hierarchy 29 | virtual void Refresh(const TSharedPtr Provider) override; 30 | virtual void UpdateSubjectFrame(const TSharedPtr Provider, FLiveLinkWorldTime WorldTime, FQualifiedFrameTime QualifiedFrameTime) override; 31 | 32 | void UpdateSubjectStaticData(FLiveLinkSkeletonStaticData& InOutAnimationFrame); 33 | void UpdateSubjectFrameData(FLiveLinkAnimationFrameData& InOutAnimationFrame); 34 | 35 | private: 36 | TArray BoneNames; 37 | TArray BoneParents; 38 | TArray BoneModels; 39 | }; --------------------------------------------------------------------------------