├── Resources └── Icon128.png ├── Content ├── ue4_livelink.hda └── Example │ ├── ThirdPersonRun.FBX │ ├── ThirdPersonWalk.FBX │ └── ue4_livelink_demo.hip ├── HoudiniLiveLink.uplugin ├── README.md ├── LICENSE.md └── Source └── HoudiniLiveLink ├── Private ├── HoudiniLiveLink.h ├── HoudiniLiveLink.cpp ├── HoudiniLiveLinkSourceFactory.h ├── SHoudiniLiveLinkSourceFactory.h ├── HoudiniLiveLinkSourceFactory.cpp ├── SHoudiniLiveLinkSourceFactory.cpp └── HoudiniLiveLinkSource.cpp ├── HoudiniLiveLink.Build.cs └── Public └── HoudiniLiveLinkSource.h /Resources/Icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sideeffects/HoudiniLiveLink/HEAD/Resources/Icon128.png -------------------------------------------------------------------------------- /Content/ue4_livelink.hda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sideeffects/HoudiniLiveLink/HEAD/Content/ue4_livelink.hda -------------------------------------------------------------------------------- /Content/Example/ThirdPersonRun.FBX: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sideeffects/HoudiniLiveLink/HEAD/Content/Example/ThirdPersonRun.FBX -------------------------------------------------------------------------------- /Content/Example/ThirdPersonWalk.FBX: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sideeffects/HoudiniLiveLink/HEAD/Content/Example/ThirdPersonWalk.FBX -------------------------------------------------------------------------------- /Content/Example/ue4_livelink_demo.hip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sideeffects/HoudiniLiveLink/HEAD/Content/Example/ue4_livelink_demo.hip -------------------------------------------------------------------------------- /HoudiniLiveLink.uplugin: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion": 3, 3 | "Version": 1, 4 | "VersionName": "1.0", 5 | "FriendlyName": "Houdini KineFX LiveLink", 6 | "Description": "LiveLink Source for receiving animation data from Houdini/KineFX via the Houdini Webserver", 7 | "Category": "Animation", 8 | "CreatedBy": "Side FX Software", 9 | "CreatedByURL": "https://www.sidefx.com", 10 | "DocsURL": "https://docs.unrealengine.com/en-US/Engine/Animation/LiveLinkPlugin/index.html", 11 | "MarketplaceURL": "", 12 | "SupportURL": "", 13 | "CanContainContent": true, 14 | "IsBetaVersion": false, 15 | "Installed": false, 16 | "Modules": 17 | [ 18 | { 19 | "Name": "HoudiniLiveLink", 20 | "Type": "Runtime", 21 | "LoadingPhase": "Default" 22 | } 23 | ] 24 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Houdini KineFX Live Link 2 | 3 | LiveLink Plugin for Houdini KineFX and Unreal Engine 4 | 5 | The Houdini KineFX Live Link plugin can be used to let you control rigs in Unreal directly from KineFX in Houdini. 6 | 7 | After installing the plugin, a new "Houdini Live Link" source will be available. 8 | The LiveLink plugin should be used with the "ue4_livelink" HDA, that is available in the plugin's content directory. 9 | 10 | First, start the server on the Houdini side, directly on the live link node. 11 | You can then add a new Live Link source in Unreal (via Windows > Live Link, Add). 12 | The Houdini Live Link source will now be available and can be used as an animation controller. 13 | 14 | # Installation 15 | 16 | After downloading the plugin's release binaries, extract the archive and copy the "HoudiniLiveLink" folder to the Engine/Plugins/Animations folder in Unreal. 17 | You can now start Unreal and enable the Houdini Live Link plugin in the "Plugins" window, under Animation. 18 | The main LiveLink plugin needs to be enabled as well. 19 | 20 | # Compatibility 21 | 22 | Binaries and Source Code are available for Unreal 4.25.3. 23 | The UE4_LiveLink HDA requires Houdini18.5 as it uses KineFX. 24 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | 2 | Copyright (c) 2021 3 | Side Effects Software Inc. All rights reserved. 4 | 5 | Redistribution and use of in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, 9 | this list of conditions and the following disclaimer. 10 | 11 | 2. The names Side Effects Software and SideFX may not be used to endorse or 12 | promote products derived from this software without specific prior 13 | written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE `AS IS' AND ANY EXPRESS 16 | OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 17 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 18 | NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, 19 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 20 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 21 | OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 22 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 23 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 24 | EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /Source/HoudiniLiveLink/Private/HoudiniLiveLink.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) <2020> Side Effects Software Inc. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 11 | * 2. The name of Side Effects Software may not be used to endorse or 12 | * promote products derived from this software without specific prior 13 | * written permission. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS 16 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 17 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 18 | * NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, 19 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 20 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 21 | * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 22 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 23 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 24 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | #pragma once 28 | 29 | #include "CoreMinimal.h" 30 | #include "Modules/ModuleManager.h" 31 | 32 | class FHoudiniLiveLinkModule : public IModuleInterface 33 | { 34 | public: 35 | 36 | /** IModuleInterface implementation */ 37 | virtual void StartupModule() override; 38 | virtual void ShutdownModule() override; 39 | }; 40 | -------------------------------------------------------------------------------- /Source/HoudiniLiveLink/Private/HoudiniLiveLink.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) <2020> Side Effects Software Inc. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 11 | * 2. The name of Side Effects Software may not be used to endorse or 12 | * promote products derived from this software without specific prior 13 | * written permission. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS 16 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 17 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 18 | * NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, 19 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 20 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 21 | * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 22 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 23 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 24 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | #include "HoudiniLiveLink.h" 28 | 29 | #define LOCTEXT_NAMESPACE "FHoudiniLiveLinkModule" 30 | 31 | void 32 | FHoudiniLiveLinkModule::StartupModule() 33 | { 34 | // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module 35 | } 36 | 37 | void 38 | FHoudiniLiveLinkModule::ShutdownModule() 39 | { 40 | // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, 41 | // we call this function before unloading the module. 42 | } 43 | 44 | #undef LOCTEXT_NAMESPACE 45 | 46 | IMPLEMENT_MODULE(FHoudiniLiveLinkModule, HoudiniLiveLink) -------------------------------------------------------------------------------- /Source/HoudiniLiveLink/HoudiniLiveLink.Build.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) <2020> Side Effects Software Inc. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 11 | * 2. The name of Side Effects Software may not be used to endorse or 12 | * promote products derived from this software without specific prior 13 | * written permission. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS 16 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 17 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 18 | * NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, 19 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 20 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 21 | * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 22 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 23 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 24 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | using UnrealBuildTool; 28 | 29 | public class HoudiniLiveLink : ModuleRules 30 | { 31 | public HoudiniLiveLink(ReadOnlyTargetRules Target) : base(Target) 32 | { 33 | PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; 34 | 35 | PublicDependencyModuleNames.AddRange( 36 | new string[] 37 | { 38 | "Core", 39 | "LiveLinkInterface", 40 | "Messaging", 41 | } 42 | ); 43 | 44 | PrivateDependencyModuleNames.AddRange( 45 | new string[] 46 | { 47 | "CoreUObject", 48 | "Engine", 49 | "InputCore", 50 | "Json", 51 | "JsonUtilities", 52 | "Networking", 53 | "Sockets", 54 | "Slate", 55 | "SlateCore", 56 | } 57 | ); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Source/HoudiniLiveLink/Private/HoudiniLiveLinkSourceFactory.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) <2020> Side Effects Software Inc. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 11 | * 2. The name of Side Effects Software may not be used to endorse or 12 | * promote products derived from this software without specific prior 13 | * written permission. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS 16 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 17 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 18 | * NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, 19 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 20 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 21 | * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 22 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 23 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 24 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | #pragma once 28 | 29 | #include "LiveLinkSourceFactory.h" 30 | #include "Interfaces/IPv4/IPv4Endpoint.h" 31 | #include "HoudiniLiveLinkSourceFactory.generated.h" 32 | 33 | class SHoudiniLiveLinkSourceEditor; 34 | 35 | UCLASS() 36 | class UHoudiniLiveLinkSourceFactory : public ULiveLinkSourceFactory 37 | { 38 | public: 39 | 40 | GENERATED_BODY() 41 | 42 | virtual FText GetSourceDisplayName() const override; 43 | virtual FText GetSourceTooltip() const override; 44 | 45 | virtual EMenuType GetMenuType() const override { return EMenuType::SubPanel; } 46 | virtual TSharedPtr BuildCreationPanel(FOnLiveLinkSourceCreated OnLiveLinkSourceCreated) const override; 47 | TSharedPtr CreateSource(const FString& ConnectionString) const override; 48 | 49 | private: 50 | 51 | void OnOkClicked(FIPv4Endpoint Endpoint, float InRefreshRate, FString InSubjectName, FOnLiveLinkSourceCreated OnLiveLinkSourceCreated) const; 52 | }; -------------------------------------------------------------------------------- /Source/HoudiniLiveLink/Private/SHoudiniLiveLinkSourceFactory.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) <2020> Side Effects Software Inc. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 11 | * 2. The name of Side Effects Software may not be used to endorse or 12 | * promote products derived from this software without specific prior 13 | * written permission. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS 16 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 17 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 18 | * NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, 19 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 20 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 21 | * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 22 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 23 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 24 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | #pragma once 28 | 29 | #include "Widgets/SCompoundWidget.h" 30 | #include "Input/Reply.h" 31 | #include "Types/SlateEnums.h" 32 | #include "Widgets/DeclarativeSyntaxSupport.h" 33 | #include "Interfaces/IPv4/IPv4Endpoint.h" 34 | #include "Widgets/Input/SNumericEntryBox.h" 35 | 36 | class SEditableTextBox; 37 | 38 | class SHoudiniLiveLinkSourceFactory : public SCompoundWidget 39 | { 40 | public: 41 | 42 | DECLARE_DELEGATE_ThreeParams(FOnOkClicked, FIPv4Endpoint, float, FString); 43 | 44 | SLATE_BEGIN_ARGS(SHoudiniLiveLinkSourceFactory){} 45 | SLATE_EVENT(FOnOkClicked, OnOkClicked) 46 | SLATE_END_ARGS() 47 | 48 | void Construct(const FArguments& Args); 49 | 50 | private: 51 | 52 | void OnEndpointChanged(const FText& NewValue, ETextCommit::Type); 53 | 54 | void OnNameChanged(const FText& NewValue, ETextCommit::Type); 55 | 56 | void SetRefreshRate(float InRefreshRate); 57 | TOptional GetRefreshRate() const; 58 | 59 | FReply OnOkClicked(); 60 | 61 | FOnOkClicked OkClicked; 62 | 63 | TWeakPtr PortEditabledText; 64 | TWeakPtr NameEditabledText; 65 | TWeakPtr> NumericValue; 66 | 67 | float RefreshValue; 68 | 69 | FString SubjectName; 70 | }; -------------------------------------------------------------------------------- /Source/HoudiniLiveLink/Private/HoudiniLiveLinkSourceFactory.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) <2020> Side Effects Software Inc. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 11 | * 2. The name of Side Effects Software may not be used to endorse or 12 | * promote products derived from this software without specific prior 13 | * written permission. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS 16 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 17 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 18 | * NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, 19 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 20 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 21 | * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 22 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 23 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 24 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | #include "HoudiniLiveLinkSourceFactory.h" 28 | #include "HoudiniLiveLinkSource.h" 29 | #include "SHoudiniLiveLinkSourceFactory.h" 30 | 31 | #define LOCTEXT_NAMESPACE "HoudiniLiveLinkSourceFactory" 32 | 33 | FText 34 | UHoudiniLiveLinkSourceFactory::GetSourceDisplayName() const 35 | { 36 | return LOCTEXT("SourceDisplayName", "Houdini LiveLink"); 37 | } 38 | 39 | FText 40 | UHoudiniLiveLinkSourceFactory::GetSourceTooltip() const 41 | { 42 | return LOCTEXT("SourceTooltip", "Creates a LiveLink connection to Houdini"); 43 | } 44 | 45 | TSharedPtr 46 | UHoudiniLiveLinkSourceFactory::BuildCreationPanel(FOnLiveLinkSourceCreated InOnLiveLinkSourceCreated) const 47 | { 48 | return SNew(SHoudiniLiveLinkSourceFactory) 49 | .OnOkClicked(SHoudiniLiveLinkSourceFactory::FOnOkClicked::CreateUObject(this, &UHoudiniLiveLinkSourceFactory::OnOkClicked, InOnLiveLinkSourceCreated)); 50 | } 51 | 52 | TSharedPtr 53 | UHoudiniLiveLinkSourceFactory::CreateSource(const FString& InConnectionString) const 54 | { 55 | FIPv4Endpoint DeviceEndPoint; 56 | if (!FIPv4Endpoint::Parse(InConnectionString, DeviceEndPoint)) 57 | { 58 | return TSharedPtr(); 59 | } 60 | 61 | return MakeShared(DeviceEndPoint, 60.0f, TEXT("Houdini Subject")); 62 | } 63 | 64 | void 65 | UHoudiniLiveLinkSourceFactory::OnOkClicked(FIPv4Endpoint InEndpoint, float InRefreshRate, FString InSubjectName, FOnLiveLinkSourceCreated InOnLiveLinkSourceCreated) const 66 | { 67 | InOnLiveLinkSourceCreated.ExecuteIfBound(MakeShared(InEndpoint, InRefreshRate, InSubjectName), InEndpoint.ToString()); 68 | } 69 | 70 | #undef LOCTEXT_NAMESPACE -------------------------------------------------------------------------------- /Source/HoudiniLiveLink/Public/HoudiniLiveLinkSource.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) <2020> Side Effects Software Inc. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 11 | * 2. The name of Side Effects Software may not be used to endorse or 12 | * promote products derived from this software without specific prior 13 | * written permission. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS 16 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 17 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 18 | * NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, 19 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 20 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 21 | * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 22 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 23 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 24 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | #pragma once 28 | 29 | #include "ILiveLinkSource.h" 30 | #include "HAL/Runnable.h" 31 | #include "HAL/ThreadSafeBool.h" 32 | #include "IMessageContext.h" 33 | #include "Interfaces/IPv4/IPv4Endpoint.h" 34 | #include "Containers/Set.h" 35 | 36 | class FRunnableThread; 37 | class ILiveLinkClient; 38 | 39 | class HOUDINILIVELINK_API FHoudiniLiveLinkSource : public ILiveLinkSource, public FRunnable 40 | { 41 | public: 42 | 43 | FHoudiniLiveLinkSource(FIPv4Endpoint Endpoint, const float& InRefreshRate, const FString& InSubjectName); 44 | 45 | virtual ~FHoudiniLiveLinkSource(); 46 | 47 | // Begin ILiveLinkSource Interface 48 | 49 | virtual void ReceiveClient(ILiveLinkClient* InClient, FGuid InSourceGuid) override; 50 | 51 | virtual bool IsSourceStillValid() const override; 52 | 53 | virtual bool RequestSourceShutdown() override; 54 | 55 | virtual FText GetSourceType() const override { return SourceType; }; 56 | virtual FText GetSourceMachineName() const override { return SourceMachineName; } 57 | virtual FText GetSourceStatus() const override { return SourceStatus; } 58 | 59 | // End ILiveLinkSource Interface 60 | 61 | // Begin FRunnable Interface 62 | 63 | virtual bool Init() override { return true; } 64 | virtual uint32 Run() override; 65 | void Start(); 66 | virtual void Stop() override; 67 | virtual void Exit() override { } 68 | 69 | // End FRunnable Interface 70 | 71 | bool ProcessResponseData(const FString& ReceivedData); 72 | 73 | private: 74 | 75 | ILiveLinkClient* Client; 76 | 77 | // Our identifier in LiveLink 78 | FGuid SourceGuid; 79 | 80 | // Source Infos 81 | FText SourceType; 82 | FText SourceMachineName; 83 | FText SourceStatus; 84 | 85 | FName SubjectName; 86 | 87 | int NumBones; 88 | int NumCurves; 89 | TSet Roots; 90 | 91 | // Machine/Port we're connected to 92 | FIPv4Endpoint DeviceEndpoint; 93 | 94 | // Threadsafe Bool for terminating the main thread loop 95 | FThreadSafeBool Stopping; 96 | 97 | // Thread to run socket operations on 98 | FRunnableThread* Thread; 99 | 100 | // Name of the sockets thread 101 | FString ThreadName; 102 | 103 | // Indicates that the skeleton needs to be setup from houdini first 104 | bool SkeletonSetupNeeded; 105 | 106 | // Frequency update (sleep time between each update) 107 | float UpdateFrequency; 108 | 109 | // Transform scale 110 | // currently unused 111 | static const double TransformScale; 112 | }; 113 | -------------------------------------------------------------------------------- /Source/HoudiniLiveLink/Private/SHoudiniLiveLinkSourceFactory.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) <2020> Side Effects Software Inc. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 11 | * 2. The name of Side Effects Software may not be used to endorse or 12 | * promote products derived from this software without specific prior 13 | * written permission. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS 16 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 17 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 18 | * NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, 19 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 20 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 21 | * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 22 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 23 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 24 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | #include "SHoudiniLiveLinkSourceFactory.h" 28 | #include "Interfaces/IPv4/IPv4Endpoint.h" 29 | #include "Widgets/SBoxPanel.h" 30 | #include "Widgets/Input/SButton.h" 31 | #include "Widgets/Input/SEditableTextBox.h" 32 | #include "Widgets/Layout/SBox.h" 33 | #include "Widgets/Text/STextBlock.h" 34 | 35 | #define LOCTEXT_NAMESPACE "HoudiniLiveLinkSourceEditor" 36 | 37 | void 38 | SHoudiniLiveLinkSourceFactory::Construct(const FArguments& Args) 39 | { 40 | OkClicked = Args._OnOkClicked; 41 | 42 | // Default to 127.0.0.1:8010 43 | FIPv4Endpoint Endpoint; 44 | Endpoint.Address = FIPv4Address::InternalLoopback; 45 | Endpoint.Port = 8010; 46 | 47 | // Default to 60Fps 48 | RefreshValue = 60.0f; 49 | 50 | // Default to HoudiniSubject 51 | SubjectName = TEXT("Houdini Subject"); 52 | 53 | ChildSlot 54 | [ 55 | SNew(SBox) 56 | .WidthOverride(250) 57 | [ 58 | SNew(SVerticalBox) 59 | + SVerticalBox::Slot() 60 | .Padding(2, 2, 5, 2) 61 | .AutoHeight() 62 | [ 63 | SNew(SHorizontalBox) 64 | + SHorizontalBox::Slot() 65 | .HAlign(HAlign_Left) 66 | .FillWidth(0.5f) 67 | [ 68 | SNew(STextBlock) 69 | .Text(LOCTEXT("HoudiniLLPortNumber", "Port Number")) 70 | ] 71 | + SHorizontalBox::Slot() 72 | .HAlign(HAlign_Fill) 73 | .FillWidth(0.5f) 74 | [ 75 | SAssignNew(PortEditabledText, SEditableTextBox) 76 | .Text(FText::FromString(Endpoint.ToString())) 77 | .OnTextCommitted(this, &SHoudiniLiveLinkSourceFactory::OnEndpointChanged) 78 | ] 79 | ] 80 | + SVerticalBox::Slot() 81 | .Padding(2, 2, 5, 2) 82 | .AutoHeight() 83 | [ 84 | SNew(SHorizontalBox) 85 | + SHorizontalBox::Slot() 86 | .HAlign(HAlign_Left) 87 | .FillWidth(0.5f) 88 | [ 89 | SNew(STextBlock) 90 | .Text(LOCTEXT("HoudiniLLSubjectName", "Subject Name")) 91 | ] 92 | + SHorizontalBox::Slot() 93 | .HAlign(HAlign_Fill) 94 | .FillWidth(0.5f) 95 | [ 96 | SAssignNew(NameEditabledText, SEditableTextBox) 97 | .Text(FText::FromString(SubjectName)) 98 | .OnTextCommitted(this, &SHoudiniLiveLinkSourceFactory::OnNameChanged) 99 | ] 100 | ] 101 | + SVerticalBox::Slot() 102 | .Padding(2, 2, 5, 2) 103 | .AutoHeight() 104 | [ 105 | SNew(SHorizontalBox) 106 | + SHorizontalBox::Slot() 107 | .HAlign(HAlign_Left) 108 | .FillWidth(0.5f) 109 | [ 110 | SNew(STextBlock) 111 | .Text(LOCTEXT("HoudiniLLRefresh", "Refresh Rate (fps)")) 112 | ] 113 | + SHorizontalBox::Slot() 114 | .HAlign(HAlign_Fill) 115 | .FillWidth(0.5f) 116 | [ 117 | SAssignNew(NumericValue, SNumericEntryBox) 118 | .AllowSpin(true) 119 | .MinValue(0.1) 120 | .MinSliderValue(0.1) 121 | .MaxSliderValue(144) 122 | .Value(this, &SHoudiniLiveLinkSourceFactory::GetRefreshRate) 123 | .OnValueChanged(this, &SHoudiniLiveLinkSourceFactory::SetRefreshRate) 124 | .SliderExponent(1.0f) 125 | ] 126 | ] 127 | + SVerticalBox::Slot() 128 | .Padding(2, 2, 5, 2) 129 | .HAlign(HAlign_Right) 130 | .AutoHeight() 131 | [ 132 | SNew(SButton) 133 | .OnClicked(this, &SHoudiniLiveLinkSourceFactory::OnOkClicked) 134 | [ 135 | SNew(STextBlock) 136 | .Text(LOCTEXT("Add", "Add Source")) 137 | ] 138 | ] 139 | ] 140 | ]; 141 | } 142 | 143 | void 144 | SHoudiniLiveLinkSourceFactory::OnEndpointChanged(const FText& NewValue, ETextCommit::Type) 145 | { 146 | TSharedPtr EditabledTextPin = PortEditabledText.Pin(); 147 | if (EditabledTextPin.IsValid()) 148 | { 149 | FIPv4Endpoint Endpoint; 150 | if (!FIPv4Endpoint::Parse(NewValue.ToString(), Endpoint)) 151 | { 152 | Endpoint.Address = FIPv4Address::InternalLoopback; 153 | Endpoint.Port = 8010; 154 | EditabledTextPin->SetText(FText::FromString(Endpoint.ToString())); 155 | } 156 | } 157 | } 158 | 159 | 160 | void 161 | SHoudiniLiveLinkSourceFactory::OnNameChanged(const FText& NewValue, ETextCommit::Type) 162 | { 163 | TSharedPtr EditabledTextPin = NameEditabledText.Pin(); 164 | if (EditabledTextPin.IsValid()) 165 | { 166 | FString NewName = NewValue.ToString(); 167 | if (NewName.IsEmpty()) 168 | { 169 | NewName = TEXT("Houdini Subject"); 170 | EditabledTextPin->SetText(FText::FromString(NewName)); 171 | } 172 | 173 | SubjectName = NewName; 174 | } 175 | } 176 | 177 | void 178 | SHoudiniLiveLinkSourceFactory::SetRefreshRate(float InRefreshRate) 179 | { 180 | RefreshValue = InRefreshRate; 181 | } 182 | 183 | TOptional 184 | SHoudiniLiveLinkSourceFactory::GetRefreshRate() const 185 | { 186 | return RefreshValue; 187 | } 188 | 189 | FReply 190 | SHoudiniLiveLinkSourceFactory::OnOkClicked() 191 | { 192 | TSharedPtr EditabledTextPin = PortEditabledText.Pin(); 193 | if (EditabledTextPin.IsValid()) 194 | { 195 | FIPv4Endpoint Endpoint; 196 | if (FIPv4Endpoint::Parse(EditabledTextPin->GetText().ToString(), Endpoint)) 197 | { 198 | OkClicked.ExecuteIfBound(Endpoint, RefreshValue, SubjectName); 199 | } 200 | } 201 | return FReply::Handled(); 202 | } 203 | 204 | #undef LOCTEXT_NAMESPACE -------------------------------------------------------------------------------- /Source/HoudiniLiveLink/Private/HoudiniLiveLinkSource.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) <2020> Side Effects Software Inc. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * 1. Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 11 | * 2. The name of Side Effects Software may not be used to endorse or 12 | * promote products derived from this software without specific prior 13 | * written permission. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS 16 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 17 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 18 | * NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, 19 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 20 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 21 | * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 22 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 23 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 24 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | #include "HoudiniLiveLinkSource.h" 28 | 29 | #include "ILiveLinkClient.h" 30 | #include "LiveLinkTypes.h" 31 | #include "Roles/LiveLinkAnimationRole.h" 32 | #include "Roles/LiveLinkAnimationTypes.h" 33 | #include "Common/UdpSocketBuilder.h" 34 | #include "Sockets.h" 35 | 36 | #include "Async/Async.h" 37 | #include "HAL/RunnableThread.h" 38 | 39 | #define LOCTEXT_NAMESPACE "HoudiniLiveLinkSource" 40 | 41 | #define RECV_BUFFER_SIZE 1024 * 1024 42 | 43 | const double 44 | FHoudiniLiveLinkSource::TransformScale = 1.0; 45 | 46 | FHoudiniLiveLinkSource::FHoudiniLiveLinkSource(FIPv4Endpoint InEndpoint, const float& InRefreshRate, const FString& InSubjectName) 47 | : Stopping(false) 48 | , Thread(nullptr) 49 | , SkeletonSetupNeeded(true) 50 | { 51 | // defaults 52 | DeviceEndpoint = InEndpoint; 53 | 54 | UpdateFrequency = 0.1f; 55 | if (InRefreshRate > 0.0f) 56 | { 57 | UpdateFrequency = 1 / InRefreshRate; 58 | } 59 | 60 | SourceStatus = LOCTEXT("SourceStatus_DeviceNotFound", "Device Not Found"); 61 | SourceType = LOCTEXT("HoudiniLiveLinkSourceType", "Houdini LiveLink"); 62 | SourceMachineName = LOCTEXT("HoudiniLiveLinkSourceMachineName", "localhost"); 63 | 64 | // Default subject name 65 | SubjectName = TEXT("Houdini Subject"); 66 | if (!InSubjectName.IsEmpty()) 67 | SubjectName = FName(*InSubjectName); 68 | 69 | { 70 | Start(); 71 | SourceStatus = LOCTEXT("SourceStatus_Receiving", "Receiving"); 72 | } 73 | } 74 | 75 | FHoudiniLiveLinkSource::~FHoudiniLiveLinkSource() 76 | { 77 | Stop(); 78 | if (Thread != nullptr) 79 | { 80 | Thread->WaitForCompletion(); 81 | delete Thread; 82 | Thread = nullptr; 83 | } 84 | } 85 | 86 | void 87 | FHoudiniLiveLinkSource::ReceiveClient(ILiveLinkClient* InClient, FGuid InSourceGuid) 88 | { 89 | Client = InClient; 90 | SourceGuid = InSourceGuid; 91 | } 92 | 93 | bool 94 | FHoudiniLiveLinkSource::IsSourceStillValid() const 95 | { 96 | // Source is valid if we have a valid thread and socket 97 | bool bIsSourceValid = !Stopping && Thread != nullptr; 98 | return bIsSourceValid; 99 | } 100 | 101 | bool 102 | FHoudiniLiveLinkSource::RequestSourceShutdown() 103 | { 104 | Stop(); 105 | SourceStatus = LOCTEXT("SourceStatus_Stopped", "Stopped"); 106 | 107 | return true; 108 | } 109 | 110 | // FRunnable interface 111 | void 112 | FHoudiniLiveLinkSource::Start() 113 | { 114 | SkeletonSetupNeeded = true; 115 | NumBones = -1; 116 | NumCurves = -1; 117 | 118 | ThreadName = "Houdini Live Link "; 119 | ThreadName.AppendInt(FAsyncThreadIndex::GetNext()); 120 | 121 | Thread = FRunnableThread::Create(this, *ThreadName, 128 * 1024, TPri_AboveNormal, FPlatformAffinity::GetPoolThreadMask()); 122 | } 123 | 124 | void 125 | FHoudiniLiveLinkSource::Stop() 126 | { 127 | Stopping = true; 128 | SkeletonSetupNeeded = true; 129 | } 130 | 131 | const int BUFFER_SIZE = 65536; 132 | uint32 133 | FHoudiniLiveLinkSource::Run() 134 | { 135 | FUdpSocketBuilder builder("Houdini Live Link Receiver"); 136 | builder.AsBlocking(); 137 | builder.AsReusable(); 138 | builder.BoundToAddress(FIPv4Address::Any); 139 | builder.BoundToPort(DeviceEndpoint.Port); 140 | builder.WithReceiveBufferSize(BUFFER_SIZE); 141 | 142 | char buf[BUFFER_SIZE]; 143 | 144 | FSocket* socket = builder.Build(); 145 | if (socket) 146 | { 147 | if (socket->GetConnectionState() != ESocketConnectionState::SCS_Connected) 148 | return 0; 149 | 150 | while (!Stopping) 151 | { 152 | int32 num_read; 153 | if (socket->Wait(ESocketWaitConditions::WaitForRead, 100)) 154 | { 155 | socket->Recv((uint8*)buf, BUFFER_SIZE, num_read, ESocketReceiveFlags::None); 156 | 157 | SkeletonSetupNeeded = !ProcessResponseData(FString(num_read, buf)); 158 | } 159 | } 160 | socket->Close(); 161 | } 162 | return 0; 163 | } 164 | 165 | bool 166 | FHoudiniLiveLinkSource::ProcessResponseData(const FString& ReceivedData) 167 | { 168 | // No need to process the data if we're stopping 169 | if(Stopping || !Thread) 170 | return false; 171 | 172 | TSharedPtr JsonObject; 173 | TSharedRef> Reader = TJsonReaderFactory<>::Create(ReceivedData); 174 | if (!FJsonSerializer::Deserialize(Reader, JsonObject)) 175 | { 176 | // Whatever we received is not JSON 177 | return false; 178 | } 179 | 180 | // Setup is done via GetSkeleton, and returns the following values: 181 | // parents (Int Array), vertices (FVector Array), Names (String Array) 182 | 183 | // Update is done via GetSkeletonPose, and has: 184 | // position (FVector Array), rotations (FVector Array), Names (String Array) 185 | 186 | // Static Data 187 | bool bStaticDataUpdated = false; 188 | FLiveLinkStaticDataStruct StaticDataStruct = FLiveLinkStaticDataStruct(FLiveLinkSkeletonStaticData::StaticStruct()); 189 | FLiveLinkSkeletonStaticData& StaticData = *StaticDataStruct.Cast(); 190 | 191 | // Frame Data 192 | bool bFrameDataUpdated = false; 193 | FLiveLinkFrameDataStruct FrameDataStruct = FLiveLinkFrameDataStruct(FLiveLinkAnimationFrameData::StaticStruct()); 194 | FLiveLinkAnimationFrameData& FrameData = *FrameDataStruct.Cast(); 195 | 196 | for (TPair>& JsonField : JsonObject->Values) 197 | { 198 | const TArray>& ValueArray = JsonField.Value->AsArray(); 199 | if (JsonField.Key.Equals(TEXT("parents"), ESearchCase::IgnoreCase)) 200 | { 201 | // Parents (STATIC DATA) (GetSkeleton) 202 | Roots.Empty(); 203 | StaticData.BoneParents.SetNumUninitialized(ValueArray.Num()); 204 | for (int BoneIdx = 0; BoneIdx < ValueArray.Num(); BoneIdx++) 205 | { 206 | if (ValueArray[BoneIdx]->IsNull()) 207 | { 208 | // Root Node 209 | StaticData.BoneParents[BoneIdx] = -1; 210 | Roots.Add(BoneIdx); 211 | } 212 | else 213 | { 214 | StaticData.BoneParents[BoneIdx] = (int32)ValueArray[BoneIdx]->AsNumber(); 215 | } 216 | } 217 | 218 | bStaticDataUpdated = true; 219 | } 220 | else if (JsonField.Key.Equals(TEXT("names"), ESearchCase::IgnoreCase)) 221 | { 222 | // Names (STATIC DATA) (both) 223 | StaticData.BoneNames.SetNumUninitialized(ValueArray.Num()); 224 | 225 | for (int BoneIdx = 0; BoneIdx < ValueArray.Num(); BoneIdx++) 226 | { 227 | FString BoneName = ValueArray[BoneIdx]->AsString(); 228 | StaticData.BoneNames[BoneIdx] = FName(*BoneName); 229 | } 230 | 231 | bStaticDataUpdated = true; 232 | } 233 | else if (JsonField.Key.Equals(TEXT("positions"), ESearchCase::IgnoreCase)) 234 | { 235 | // Check the validity of the data we received 236 | if (!SkeletonSetupNeeded && ValueArray.Num() != NumBones) 237 | return false; 238 | 239 | // positions (FRAME DATA) (GetSkeletonPose) 240 | if(FrameData.Transforms.Num() <= 0) 241 | FrameData.Transforms.Init(FTransform::Identity, ValueArray.Num()); 242 | 243 | for (int BoneIdx = 0; BoneIdx < ValueArray.Num(); ++BoneIdx) 244 | { 245 | const TArray>& LocationArray = ValueArray[BoneIdx]->AsArray(); 246 | 247 | FVector BoneLocation = FVector::ZeroVector; 248 | if ( LocationArray.Num() == 3) // X, Y, Z 249 | { 250 | double X = LocationArray[0]->AsNumber(); 251 | double Y = LocationArray[1]->AsNumber(); 252 | double Z = LocationArray[2]->AsNumber(); 253 | 254 | // Houdini to Unreal: Swap Y/Z, meters to cm 255 | BoneLocation = FVector(X, -Y, Z) * TransformScale; 256 | } 257 | FrameData.Transforms[BoneIdx].SetLocation(BoneLocation); 258 | } 259 | 260 | bFrameDataUpdated = true; 261 | } 262 | else if (JsonField.Key.Equals(TEXT("rotations"), ESearchCase::IgnoreCase)) 263 | { 264 | // Check the validity of the data we received 265 | if (!SkeletonSetupNeeded && ValueArray.Num() != NumBones) 266 | return false; 267 | 268 | // rotations (FRAME DATA) (GetSkeletonPose) 269 | if (FrameData.Transforms.Num() <= 0) 270 | FrameData.Transforms.Init(FTransform::Identity, ValueArray.Num()); 271 | 272 | for (int BoneIdx = 0; BoneIdx < ValueArray.Num(); ++BoneIdx) 273 | { 274 | const TArray>& RotationArray = ValueArray[BoneIdx]->AsArray(); 275 | 276 | FQuat HQuat = FQuat::Identity; 277 | if (RotationArray.Num() == 3) 278 | { 279 | double X = RotationArray[0]->AsNumber(); 280 | double Y = RotationArray[1]->AsNumber(); 281 | double Z = RotationArray[2]->AsNumber(); 282 | 283 | HQuat = FQuat::MakeFromEuler(FVector(X, -Y, -Z)); 284 | } 285 | else if (RotationArray.Num() == 4) 286 | { 287 | // TODO: untested, the livelink HDA doesnot send quaternions for now 288 | double X = RotationArray[0]->AsNumber(); 289 | double Y = RotationArray[1]->AsNumber(); 290 | double Z = RotationArray[2]->AsNumber(); 291 | double W = RotationArray[3]->AsNumber(); 292 | HQuat = FQuat(X, Z, Y, -W); 293 | } 294 | 295 | FrameData.Transforms[BoneIdx].SetRotation(HQuat); 296 | if (Roots.Contains(BoneIdx)) 297 | { 298 | FTransform rotate(FQuat::MakeFromEuler(FVector(90.0f, 0, 0))); 299 | FrameData.Transforms[BoneIdx] = FrameData.Transforms[BoneIdx] * rotate; 300 | } 301 | } 302 | 303 | bFrameDataUpdated = true; 304 | } 305 | else if (JsonField.Key.Equals(TEXT("scales"), ESearchCase::IgnoreCase)) 306 | { 307 | // Check the validity of the data we received 308 | if (!SkeletonSetupNeeded && ValueArray.Num() != NumBones) 309 | return false; 310 | 311 | // scale (FRAME DATA) (GetSkeletonPose) 312 | if (FrameData.Transforms.Num() <= 0) 313 | FrameData.Transforms.Init(FTransform::Identity, ValueArray.Num()); 314 | 315 | for (int BoneIdx = 0; BoneIdx < ValueArray.Num(); ++BoneIdx) 316 | { 317 | const TArray>& ScaleArray = ValueArray[BoneIdx]->AsArray(); 318 | 319 | FVector BoneScale = FVector::OneVector; 320 | if (ScaleArray.Num() == 3) // X, Y, Z 321 | { 322 | double X = ScaleArray[0]->AsNumber(); 323 | double Y = ScaleArray[1]->AsNumber(); 324 | double Z = ScaleArray[2]->AsNumber(); 325 | 326 | // Houdini to Unreal: Swap Y/Z 327 | BoneScale = FVector(X, Z, Y); 328 | } 329 | 330 | FrameData.Transforms[BoneIdx].SetScale3D(BoneScale); 331 | } 332 | 333 | bFrameDataUpdated = true; 334 | } 335 | else if (JsonField.Key.Equals(TEXT("blendshape_names"), ESearchCase::IgnoreCase)) 336 | { 337 | StaticData.PropertyNames.Empty(ValueArray.Num()); 338 | 339 | for (int i = 0; i < ValueArray.Num(); ++i) 340 | { 341 | StaticData.PropertyNames.Add(FName(ValueArray[i]->AsString())); 342 | } 343 | 344 | bStaticDataUpdated = true; 345 | } 346 | else if (JsonField.Key.Equals(TEXT("blendshape_values"), ESearchCase::IgnoreCase)) 347 | { 348 | // Check the validity of the data we received 349 | if (!SkeletonSetupNeeded && ValueArray.Num() != NumCurves) 350 | return false; 351 | 352 | FrameData.PropertyValues.Empty(ValueArray.Num()); 353 | 354 | for (int i = 0; i < ValueArray.Num(); ++i) 355 | { 356 | FrameData.PropertyValues.Add(ValueArray[i]->AsNumber()); 357 | } 358 | 359 | bFrameDataUpdated = true; 360 | } 361 | } 362 | 363 | // Make sure the source is still valid before attempting to update the client data 364 | if (!IsSourceStillValid()) 365 | return false; 366 | 367 | if (bStaticDataUpdated && SkeletonSetupNeeded) 368 | { 369 | // Only update the static data if the skeleton setup is required! 370 | NumBones = StaticData.BoneNames.Num(); 371 | NumCurves = StaticData.PropertyNames.Num(); 372 | Client->PushSubjectStaticData_AnyThread({ SourceGuid, SubjectName }, ULiveLinkAnimationRole::StaticClass(), MoveTemp(StaticDataStruct)); 373 | } 374 | 375 | if (bFrameDataUpdated && !SkeletonSetupNeeded) 376 | { 377 | // Only update the frame data if where not setting up the skeleton 378 | Client->PushSubjectFrameData_AnyThread({ SourceGuid, SubjectName }, MoveTemp(FrameDataStruct)); 379 | } 380 | 381 | return true; 382 | } 383 | 384 | #undef LOCTEXT_NAMESPACE 385 | --------------------------------------------------------------------------------