├── 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 | };
--------------------------------------------------------------------------------