diff --git a/Unreal/Config/DefaultGame.ini b/Unreal/Config/DefaultGame.ini index a78ac4f..78c6ab2 100644 --- a/Unreal/Config/DefaultGame.ini +++ b/Unreal/Config/DefaultGame.ini @@ -114,6 +114,7 @@ bSkipMovies=False +DirectoriesToAlwaysCook=(Path="/NNEDenoiser") +DirectoriesToAlwaysCook=(Path="/RuntimeMetaHumanLipSync/RealisticModelData") +DirectoriesToAlwaysCook=(Path="/RuntimeMetaHumanLipSync/ModelData") ++DirectoriesToAlwaysCook=(Path="/MetaHumanCoreTech") +DirectoriesToAlwaysStageAsUFS=(Path="Schema") +DirectoriesToAlwaysStageAsNonUFS=(Path="Schema") +DirectoriesToAlwaysStageAsNonUFS=(Path="Certificates") diff --git a/Unreal/Content/Project/BP/BP_Project_Manager.uasset b/Unreal/Content/Project/BP/BP_Project_Manager.uasset index 3b8aca6..d884b6f 100644 --- a/Unreal/Content/Project/BP/BP_Project_Manager.uasset +++ b/Unreal/Content/Project/BP/BP_Project_Manager.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bdd5350cf10a21d6c9b42fc20c84fa0eb1c2b9baf6fc5f3a424c2a5c4782f57a -size 2845777 +oid sha256:19fd9cc4de4dcb8c267461eced93ff6031faa560ba849402e64a86831e986d69 +size 2843055 diff --git a/Unreal/Content/Project/BP/Modes/States/DT_ProjectStates.uasset b/Unreal/Content/Project/BP/Modes/States/DT_ProjectStates.uasset index 8cf76a0..8d221d1 100644 --- a/Unreal/Content/Project/BP/Modes/States/DT_ProjectStates.uasset +++ b/Unreal/Content/Project/BP/Modes/States/DT_ProjectStates.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:994ded1043d5cba7cbba6de51651592d862bed3de6f8512c61e312bd1e76a9b2 -size 7717 +oid sha256:2975a649e7de687a1a9e7ec40ce2cf26775d0bba8429a77184b15edc1150ef27 +size 7231 diff --git a/Unreal/Content/Project/BP/Modes/States/DT_ProjectStates_Demo.uasset b/Unreal/Content/Project/BP/Modes/States/DT_ProjectStates_Demo.uasset index 741d7c9..3badb51 100644 --- a/Unreal/Content/Project/BP/Modes/States/DT_ProjectStates_Demo.uasset +++ b/Unreal/Content/Project/BP/Modes/States/DT_ProjectStates_Demo.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b674ce995fdb3c941464dc15524e0eef7704100477dfbd81ed0b4250472afe53 -size 4172 +oid sha256:ffa041fdc250d03b1fa5e29cea8af9bfceb2c815d9bbaae82e9cb4db60ee8de1 +size 3871 diff --git a/Unreal/Plugins/AvatarCore_FaceDetector/AvatarCore_FaceDetector.uplugin b/Unreal/Plugins/AvatarCore_FaceDetector/AvatarCore_FaceDetector.uplugin deleted file mode 100644 index bccdcff..0000000 --- a/Unreal/Plugins/AvatarCore_FaceDetector/AvatarCore_FaceDetector.uplugin +++ /dev/null @@ -1,24 +0,0 @@ -{ - "FileVersion": 3, - "Version": 1, - "VersionName": "1.0", - "FriendlyName": "AvatarCore_FaceDetector", - "Description": "Tracking faces in webcame streams", - "Category": "Other", - "CreatedBy": "b.ReX", - "CreatedByURL": "", - "DocsURL": "", - "MarketplaceURL": "", - "SupportURL": "", - "CanContainContent": true, - "IsBetaVersion": false, - "IsExperimentalVersion": false, - "Installed": false, - "Modules": [ - { - "Name": "AvatarCore_FaceDetector", - "Type": "Runtime", - "LoadingPhase": "Default" - } - ] -} \ No newline at end of file diff --git a/Unreal/Plugins/AvatarCore_FaceDetector/Content/BP_FaceDetector.uasset b/Unreal/Plugins/AvatarCore_FaceDetector/Content/BP_FaceDetector.uasset deleted file mode 100644 index 1e0843f..0000000 --- a/Unreal/Plugins/AvatarCore_FaceDetector/Content/BP_FaceDetector.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8e2c75901f0365cc99789da9d57035891f2a36fb4daba590703e78d3dbaae933 -size 439459 diff --git a/Unreal/Plugins/AvatarCore_FaceDetector/Content/Material/M_Face.uasset b/Unreal/Plugins/AvatarCore_FaceDetector/Content/Material/M_Face.uasset deleted file mode 100644 index 8d0e6a5..0000000 --- a/Unreal/Plugins/AvatarCore_FaceDetector/Content/Material/M_Face.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:125c72bb49428c32e298d989f66a7b28416a7311cc2d8282fa943228a6cd2af5 -size 13934 diff --git a/Unreal/Plugins/AvatarCore_FaceDetector/Content/Material/M_FaceArea.uasset b/Unreal/Plugins/AvatarCore_FaceDetector/Content/Material/M_FaceArea.uasset deleted file mode 100644 index 55f9369..0000000 --- a/Unreal/Plugins/AvatarCore_FaceDetector/Content/Material/M_FaceArea.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:618d3bf2b9e96fbf6c5502c9e952189a390b53f600045dad015a7f1058c5daf8 -size 11705 diff --git a/Unreal/Plugins/AvatarCore_FaceDetector/Content/Misc/BP_TargetActor.uasset b/Unreal/Plugins/AvatarCore_FaceDetector/Content/Misc/BP_TargetActor.uasset deleted file mode 100644 index 3735d8c..0000000 --- a/Unreal/Plugins/AvatarCore_FaceDetector/Content/Misc/BP_TargetActor.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3382025c8c64eb4048bf7f8661e34969542da1187472f69c7ddcc35b967ae19f -size 102360 diff --git a/Unreal/Plugins/AvatarCore_FaceDetector/Content/Misc/Target.uasset b/Unreal/Plugins/AvatarCore_FaceDetector/Content/Misc/Target.uasset deleted file mode 100644 index 13dc7c1..0000000 --- a/Unreal/Plugins/AvatarCore_FaceDetector/Content/Misc/Target.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7957059cacf581b1bb22a643c0060c6e9bdd76300e457dc49af58f8cf57d3c81 -size 72077 diff --git a/Unreal/Plugins/AvatarCore_FaceDetector/Resources/Icon128.png b/Unreal/Plugins/AvatarCore_FaceDetector/Resources/Icon128.png deleted file mode 100644 index 9cbf628..0000000 --- a/Unreal/Plugins/AvatarCore_FaceDetector/Resources/Icon128.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f41cab739893bde1040beb34ba2e9f666cc4a502c0f350cf1c6bfa8bee783d1f -size 6957 diff --git a/Unreal/Plugins/AvatarCore_FaceDetector/Source/AvatarCore_FaceDetector/AvatarCore_FaceDetector.Build.cs b/Unreal/Plugins/AvatarCore_FaceDetector/Source/AvatarCore_FaceDetector/AvatarCore_FaceDetector.Build.cs deleted file mode 100644 index 60e0bcc..0000000 --- a/Unreal/Plugins/AvatarCore_FaceDetector/Source/AvatarCore_FaceDetector/AvatarCore_FaceDetector.Build.cs +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -using System.IO; -using UnrealBuildTool; - -public class AvatarCore_FaceDetector : ModuleRules -{ - public AvatarCore_FaceDetector(ReadOnlyTargetRules Target) : base(Target) - { - PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; - - PublicIncludePaths.AddRange( - new string[] { - // ... add public include paths required here ... - } - ); - - - PrivateIncludePaths.AddRange( - new string[] { - // ... add other private include paths required here ... - } - ); - - - PublicDependencyModuleNames.AddRange( - new string[] - { - "Core", - // ... add other public dependencies that you statically link with here ... - } - ); - - - PrivateDependencyModuleNames.AddRange( - new string[] - { - "CoreUObject", - "Engine", - "Projects", - "Json", - "JsonUtilities", - "Slate", - "SlateCore", - // ... add private dependencies that you statically link with here ... - } - ); - - - DynamicallyLoadedModuleNames.AddRange( - new string[] - { - // ... add any modules that your module loads dynamically here ... - } - ); - - RuntimeDependencies.Add(Path.Combine(ModuleDirectory, "..", "ThirdParty", "FaceDetector.exe")); - RuntimeDependencies.Add(Path.Combine(ModuleDirectory, "..", "ThirdParty", "face_landmarker.task")); - } -} diff --git a/Unreal/Plugins/AvatarCore_FaceDetector/Source/AvatarCore_FaceDetector/Private/AvatarCore_FaceDetector.cpp b/Unreal/Plugins/AvatarCore_FaceDetector/Source/AvatarCore_FaceDetector/Private/AvatarCore_FaceDetector.cpp deleted file mode 100644 index 11f5a50..0000000 --- a/Unreal/Plugins/AvatarCore_FaceDetector/Source/AvatarCore_FaceDetector/Private/AvatarCore_FaceDetector.cpp +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#include "AvatarCore_FaceDetector.h" - -#define LOCTEXT_NAMESPACE "FAvatarCore_FaceDetectorModule" - -void FAvatarCore_FaceDetectorModule::StartupModule() -{ - // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module -} - -void FAvatarCore_FaceDetectorModule::ShutdownModule() -{ - // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, - // we call this function before unloading the module. -} - -#undef LOCTEXT_NAMESPACE - -IMPLEMENT_MODULE(FAvatarCore_FaceDetectorModule, AvatarCore_FaceDetector) \ No newline at end of file diff --git a/Unreal/Plugins/AvatarCore_FaceDetector/Source/AvatarCore_FaceDetector/Private/AvatarFaceDetectorSubsystem.cpp b/Unreal/Plugins/AvatarCore_FaceDetector/Source/AvatarCore_FaceDetector/Private/AvatarFaceDetectorSubsystem.cpp deleted file mode 100644 index c321fb8..0000000 --- a/Unreal/Plugins/AvatarCore_FaceDetector/Source/AvatarCore_FaceDetector/Private/AvatarFaceDetectorSubsystem.cpp +++ /dev/null @@ -1,307 +0,0 @@ -#include "AvatarFaceDetectorSubsystem.h" - -#include "Async/Async.h" -#include "Interfaces/IPluginManager.h" -#include "Misc/Paths.h" -#include "Serialization/JsonReader.h" -#include "Serialization/JsonSerializer.h" - -bool UAvatarFaceDetectorSubsystem::InitializeFaceDetector(const FFaceDetectorSettings& Settings) -{ - ShutdownFaceDetector(); - return StartProcess(Settings); -} - -void UAvatarFaceDetectorSubsystem::ShutdownFaceDetector() -{ - StopProcess(); -} - -bool UAvatarFaceDetectorSubsystem::IsFaceDetectorRunning() const -{ - FProcHandle HandleCopy = ProcHandle; - return HandleCopy.IsValid() && FPlatformProcess::IsProcRunning(HandleCopy); -} - -void UAvatarFaceDetectorSubsystem::Deinitialize() -{ - ShutdownFaceDetector(); - Super::Deinitialize(); -} - -FString UAvatarFaceDetectorSubsystem::ResolveThirdPartyFilePath(const FString& FileName) const -{ - const TSharedPtr Plugin = IPluginManager::Get().FindPlugin(TEXT("AvatarCore_FaceDetector")); - if (!Plugin.IsValid()) - { - return FString(); - } - - const FString ThirdPartyDir = FPaths::Combine(Plugin->GetBaseDir(), TEXT("Source"), TEXT("ThirdParty")); - return FPaths::Combine(ThirdPartyDir, FileName); -} - -bool UAvatarFaceDetectorSubsystem::StartProcess(const FFaceDetectorSettings& Settings) -{ - FScopeLock Lock(&StateCriticalSection); - - const FString ExePath = ResolveThirdPartyFilePath(TEXT("FaceDetector.exe")); - if (ExePath.IsEmpty() || !FPaths::FileExists(ExePath)) - { - AsyncTask(ENamedThreads::GameThread, [this]() - { - OnFaceDetectorError.Broadcast(TEXT("FaceDetector.exe not found (expected under Plugin/Source/ThirdParty).")); - }); - return false; - } - - FString Args; - if (!Settings.WebcamName.IsEmpty()) - { - Args += FString::Printf(TEXT(" --device \"%s\""), *Settings.WebcamName); - } - if (!Settings.ModelName.IsEmpty()) - { - const FString ModelPath = ResolveThirdPartyFilePath(Settings.ModelName); - Args += FString::Printf(TEXT(" --model \"%s\""), *ModelPath); - } - Args += FString::Printf(TEXT(" --num_faces %d"), Settings.NumFaces); - if (Settings.bDebugMode) - { - Args += TEXT(" --debugMode"); - } - - Args += FString::Printf(TEXT(" --fps_limit %d"), Settings.FrameRateLimit); - - FPlatformProcess::CreatePipe(ReadPipe, WritePipe); - - bStopRequested = false; - StdoutRemainder.Reset(); - - ProcHandle = FPlatformProcess::CreateProc( - *ExePath, - *Args, - false, - true, - true, - nullptr, - 0, - nullptr, - WritePipe, - ReadPipe); - - if (!ProcHandle.IsValid()) - { - FPlatformProcess::ClosePipe(ReadPipe, WritePipe); - ReadPipe = nullptr; - WritePipe = nullptr; - - AsyncTask(ENamedThreads::GameThread, [this]() - { - OnFaceDetectorError.Broadcast(TEXT("Failed to launch FaceDetector.exe")); - }); - return false; - } - - ReaderFuture = Async(EAsyncExecution::Thread, [this]() - { - ReaderThreadMain(); - }); - - return true; -} - -void UAvatarFaceDetectorSubsystem::StopProcess() -{ - TFuture LocalFuture; - FProcHandle LocalProc; - void* LocalReadPipe = nullptr; - void* LocalWritePipe = nullptr; - - { - FScopeLock Lock(&StateCriticalSection); - - bStopRequested = true; - LocalFuture = MoveTemp(ReaderFuture); - LocalProc = ProcHandle; - LocalReadPipe = ReadPipe; - LocalWritePipe = WritePipe; - - ProcHandle.Reset(); - ReadPipe = nullptr; - WritePipe = nullptr; - StdoutRemainder.Reset(); - } - - if (LocalProc.IsValid()) - { - FPlatformProcess::TerminateProc(LocalProc, true); - FPlatformProcess::CloseProc(LocalProc); - } - - if (LocalFuture.IsValid()) - { - LocalFuture.Wait(); - } - - if (LocalReadPipe != nullptr || LocalWritePipe != nullptr) - { - FPlatformProcess::ClosePipe(LocalReadPipe, LocalWritePipe); - } -} - -void UAvatarFaceDetectorSubsystem::ReaderThreadMain() -{ - while (!bStopRequested) - { - FString NewOutput; - { - FScopeLock Lock(&StateCriticalSection); - if (ReadPipe == nullptr) - { - break; - } - NewOutput = FPlatformProcess::ReadPipe(ReadPipe); - } - - if (NewOutput.IsEmpty()) - { - if (!IsFaceDetectorRunning()) - { - break; - } - FPlatformProcess::Sleep(0.01f); - continue; - } - - FString Combined; - { - FScopeLock Lock(&StateCriticalSection); - Combined = StdoutRemainder + NewOutput; - StdoutRemainder.Reset(); - } - - TArray Lines; - Combined.ParseIntoArrayLines(Lines, false); - - const bool bEndsWithNewline = Combined.EndsWith(TEXT("\n")) || Combined.EndsWith(TEXT("\r\n")); - if (!bEndsWithNewline && Lines.Num() > 0) - { - FScopeLock Lock(&StateCriticalSection); - StdoutRemainder = Lines.Pop(); - } - - for (const FString& Line : Lines) - { - const FString Trimmed = Line.TrimStartAndEnd(); - if (!Trimmed.IsEmpty()) - { - HandleLineOnWorkerThread(Trimmed); - } - } - } - - if (!bStopRequested) - { - AsyncTask(ENamedThreads::GameThread, [this]() - { - OnFaceDetectorError.Broadcast(TEXT("FaceDetector process stopped.")); - }); - } -} - -void UAvatarFaceDetectorSubsystem::HandleLineOnWorkerThread(const FString& Line) -{ - TSharedPtr Root; - TSharedRef> Reader = TJsonReaderFactory<>::Create(Line); - if (!FJsonSerializer::Deserialize(Reader, Root) || !Root.IsValid()) - { - AsyncTask(ENamedThreads::GameThread, [this, Line]() - { - OnFaceDetectorLog.Broadcast(Line); - }); - return; - } - - FString Type; - if (!Root->TryGetStringField(TEXT("type"), Type)) - { - AsyncTask(ENamedThreads::GameThread, [this, Line]() - { - OnFaceDetectorLog.Broadcast(Line); - }); - return; - } - - if (Type.Equals(TEXT("error"), ESearchCase::IgnoreCase)) - { - FString Message; - Root->TryGetStringField(TEXT("message"), Message); - AsyncTask(ENamedThreads::GameThread, [this, Message]() - { - OnFaceDetectorError.Broadcast(Message); - }); - return; - } - - if (Type.Equals(TEXT("log"), ESearchCase::IgnoreCase)) - { - FString Message; - Root->TryGetStringField(TEXT("message"), Message); - AsyncTask(ENamedThreads::GameThread, [this, Message]() - { - OnFaceDetectorLog.Broadcast(Message); - }); - return; - } - - if (Type.Equals(TEXT("faces"), ESearchCase::IgnoreCase)) - { - TArray Faces; - - const TArray>* FacesJson = nullptr; - if (Root->TryGetArrayField(TEXT("faces"), FacesJson) && FacesJson != nullptr) - { - Faces.Reserve(FacesJson->Num()); - for (const TSharedPtr& FaceVal : *FacesJson) - { - if (!FaceVal.IsValid() || FaceVal->Type != EJson::Object) - { - continue; - } - const TSharedPtr FaceObj = FaceVal->AsObject(); - if (!FaceObj.IsValid()) - { - continue; - } - - double X = 0.0; - double Y = 0.0; - double IdNum = (double)INDEX_NONE; - bool bActive = false; - - FaceObj->TryGetNumberField(TEXT("x"), X); - FaceObj->TryGetNumberField(TEXT("y"), Y); - FaceObj->TryGetNumberField(TEXT("id"), IdNum); - FaceObj->TryGetBoolField(TEXT("active"), bActive); - - FDetectedFace Face; - Face.Position = FVector2D((float)X, (float)Y); - Face.Id = (int32)IdNum; - Face.TrackingLost = !bActive; - Faces.Add(Face); - } - } - - AsyncTask(ENamedThreads::GameThread, [this, Faces = MoveTemp(Faces)]() mutable - { - OnFaceDetectorUpdate.Broadcast(Faces); - }); - return; - } - - AsyncTask(ENamedThreads::GameThread, [this, Line]() - { - OnFaceDetectorLog.Broadcast(Line); - }); -} diff --git a/Unreal/Plugins/AvatarCore_FaceDetector/Source/AvatarCore_FaceDetector/Public/AvatarCore_FaceDetector.h b/Unreal/Plugins/AvatarCore_FaceDetector/Source/AvatarCore_FaceDetector/Public/AvatarCore_FaceDetector.h deleted file mode 100644 index 5c81379..0000000 --- a/Unreal/Plugins/AvatarCore_FaceDetector/Source/AvatarCore_FaceDetector/Public/AvatarCore_FaceDetector.h +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#pragma once - -#include "Modules/ModuleManager.h" - -class FAvatarCore_FaceDetectorModule : public IModuleInterface -{ -public: - - /** IModuleInterface implementation */ - virtual void StartupModule() override; - virtual void ShutdownModule() override; -}; diff --git a/Unreal/Plugins/AvatarCore_FaceDetector/Source/AvatarCore_FaceDetector/Public/AvatarFaceDetectorSubsystem.h b/Unreal/Plugins/AvatarCore_FaceDetector/Source/AvatarCore_FaceDetector/Public/AvatarFaceDetectorSubsystem.h deleted file mode 100644 index bd54fd8..0000000 --- a/Unreal/Plugins/AvatarCore_FaceDetector/Source/AvatarCore_FaceDetector/Public/AvatarFaceDetectorSubsystem.h +++ /dev/null @@ -1,56 +0,0 @@ -#pragma once - -#include "CoreMinimal.h" -#include "HAL/PlatformProcess.h" -#include "Async/Future.h" -#include "Subsystems/GameInstanceSubsystem.h" -#include "FaceDetectorTypes.h" -#include "AvatarFaceDetectorSubsystem.generated.h" - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FFaceDetectorErrorSig, const FString&, ErrorMessage); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FFaceDetectorLogSig, const FString&, LogMessage); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FFaceDetectorUpdateSig, const TArray&, Faces); - -UCLASS() -class AVATARCORE_FACEDETECTOR_API UAvatarFaceDetectorSubsystem : public UGameInstanceSubsystem -{ - GENERATED_BODY() - -public: - UFUNCTION(BlueprintCallable, Category="FaceDetector") - bool InitializeFaceDetector(const FFaceDetectorSettings& Settings); - - UFUNCTION(BlueprintCallable, Category="FaceDetector") - void ShutdownFaceDetector(); - - UFUNCTION(BlueprintCallable, Category="FaceDetector") - bool IsFaceDetectorRunning() const; - - UPROPERTY(BlueprintAssignable, Category="FaceDetector") - FFaceDetectorErrorSig OnFaceDetectorError; - - UPROPERTY(BlueprintAssignable, Category="FaceDetector") - FFaceDetectorLogSig OnFaceDetectorLog; - - UPROPERTY(BlueprintAssignable, Category="FaceDetector") - FFaceDetectorUpdateSig OnFaceDetectorUpdate; - - virtual void Deinitialize() override; - -private: - bool StartProcess(const FFaceDetectorSettings& Settings); - void StopProcess(); - void ReaderThreadMain(); - void HandleLineOnWorkerThread(const FString& Line); - - FString ResolveThirdPartyFilePath(const FString& FileName) const; - -private: - FProcHandle ProcHandle; - void* ReadPipe = nullptr; - void* WritePipe = nullptr; - TAtomic bStopRequested = false; - TFuture ReaderFuture; - FCriticalSection StateCriticalSection; - FString StdoutRemainder; -}; diff --git a/Unreal/Plugins/AvatarCore_FaceDetector/Source/AvatarCore_FaceDetector/Public/FaceDetectorTypes.h b/Unreal/Plugins/AvatarCore_FaceDetector/Source/AvatarCore_FaceDetector/Public/FaceDetectorTypes.h deleted file mode 100644 index 265acc8..0000000 --- a/Unreal/Plugins/AvatarCore_FaceDetector/Source/AvatarCore_FaceDetector/Public/FaceDetectorTypes.h +++ /dev/null @@ -1,45 +0,0 @@ -#pragma once - -#include "CoreMinimal.h" -#include "FaceDetectorTypes.generated.h" - -USTRUCT(BlueprintType) -struct FDetectedFace -{ - GENERATED_BODY() - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="FaceDetector") - FVector2D Position = FVector2D::ZeroVector; - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="FaceDetector") - int32 Id = INDEX_NONE; - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="FaceDetector") - bool TrackingLost = false; -}; - -USTRUCT(BlueprintType) -struct FFaceDetectorSettings -{ - GENERATED_BODY() - - //Name of the webcam (pick first if ambigious) - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="FaceDetector") - FString WebcamName; - - //Create a DebugWindow to see detected faces - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="FaceDetector") - bool bDebugMode = false; - - //Override Model (full path to modelfile) - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="FaceDetector") - FString ModelName; - - //Limit Framerate of Videostream (0 to disable) - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="FaceDetector") - int32 FrameRateLimit = 24; - - //Max faces to track - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="FaceDetector") - int32 NumFaces = 5; -}; diff --git a/Unreal/Plugins/AvatarCore_FaceDetector/Source/ThirdParty/FaceDetector.exe b/Unreal/Plugins/AvatarCore_FaceDetector/Source/ThirdParty/FaceDetector.exe deleted file mode 100644 index 64fe9e3..0000000 Binary files a/Unreal/Plugins/AvatarCore_FaceDetector/Source/ThirdParty/FaceDetector.exe and /dev/null differ diff --git a/Unreal/Plugins/AvatarCore_FaceDetector/Source/ThirdParty/blaze_face_short_range.tflite b/Unreal/Plugins/AvatarCore_FaceDetector/Source/ThirdParty/blaze_face_short_range.tflite deleted file mode 100644 index 1b2b522..0000000 Binary files a/Unreal/Plugins/AvatarCore_FaceDetector/Source/ThirdParty/blaze_face_short_range.tflite and /dev/null differ diff --git a/Unreal/Plugins/AvatarCore_FaceDetector/Source/ThirdParty/face_landmarker.task b/Unreal/Plugins/AvatarCore_FaceDetector/Source/ThirdParty/face_landmarker.task deleted file mode 100644 index c50c845..0000000 Binary files a/Unreal/Plugins/AvatarCore_FaceDetector/Source/ThirdParty/face_landmarker.task and /dev/null differ diff --git a/Unreal/Plugins/AvatarCore_FaceDetector/Source/ThirdParty/ffmpeg.exe b/Unreal/Plugins/AvatarCore_FaceDetector/Source/ThirdParty/ffmpeg.exe deleted file mode 100644 index bb2dbeb..0000000 Binary files a/Unreal/Plugins/AvatarCore_FaceDetector/Source/ThirdParty/ffmpeg.exe and /dev/null differ diff --git a/Unreal/Plugins/AvatarCore_FaceDetector/Source/ThirdParty/ffplay.exe b/Unreal/Plugins/AvatarCore_FaceDetector/Source/ThirdParty/ffplay.exe deleted file mode 100644 index a36cb9e..0000000 Binary files a/Unreal/Plugins/AvatarCore_FaceDetector/Source/ThirdParty/ffplay.exe and /dev/null differ diff --git a/Unreal/Plugins/AvatarCore_Manager/Content/StateManagement/BP_StateManager.uasset b/Unreal/Plugins/AvatarCore_Manager/Content/StateManagement/BP_StateManager.uasset index dc6a890..26b3988 100644 --- a/Unreal/Plugins/AvatarCore_Manager/Content/StateManagement/BP_StateManager.uasset +++ b/Unreal/Plugins/AvatarCore_Manager/Content/StateManagement/BP_StateManager.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7c6bc6e2fd7c35f1c08f987e2a850428eb9adc53d9cd23c9d82f1c3766ee5899 -size 713653 +oid sha256:e66a2bfcf852fe93524c18c923903a9761f5dd6c08f0edfa41f9fe59e98ea447 +size 672637 diff --git a/Unreal/Plugins/AvatarCore_Manager/Content/StateManagement/StructsAndEnums/S_StateProcedure.uasset b/Unreal/Plugins/AvatarCore_Manager/Content/StateManagement/StructsAndEnums/S_StateProcedure.uasset index ec56137..c604dd6 100644 --- a/Unreal/Plugins/AvatarCore_Manager/Content/StateManagement/StructsAndEnums/S_StateProcedure.uasset +++ b/Unreal/Plugins/AvatarCore_Manager/Content/StateManagement/StructsAndEnums/S_StateProcedure.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:da1a4fffcc3fd8d3c3d64637d5b26a7f5e55031faa0d9ab4775268dbe68a6a02 -size 18326 +oid sha256:7b8ba154eb4c8498e58503ab3b14bb1c448a12a66f1852b23cc5139d4dfa304d +size 16202 diff --git a/Unreal/Plugins/AvatarCore_Manager/Content/Widgets/Debug/Pages/W_DebugProjectStates.uasset b/Unreal/Plugins/AvatarCore_Manager/Content/Widgets/Debug/Pages/W_DebugProjectStates.uasset index cd33c1c..9e435e6 100644 --- a/Unreal/Plugins/AvatarCore_Manager/Content/Widgets/Debug/Pages/W_DebugProjectStates.uasset +++ b/Unreal/Plugins/AvatarCore_Manager/Content/Widgets/Debug/Pages/W_DebugProjectStates.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:53d905072ceb431e3ff720763badfdfa68220d9ea963e533f72196f73394c03c -size 215221 +oid sha256:1908dcaeaab3e629552a70d3967528691ac8e7081967b2a063ca1c2c066e65d1 +size 210538 diff --git a/Unreal/Plugins/AvatarCore_nDisplay/AvatarCore_nDisplay.uplugin b/Unreal/Plugins/AvatarCore_nDisplay/AvatarCore_nDisplay.uplugin deleted file mode 100644 index ee63252..0000000 --- a/Unreal/Plugins/AvatarCore_nDisplay/AvatarCore_nDisplay.uplugin +++ /dev/null @@ -1,34 +0,0 @@ -{ - "FileVersion": 3, - "Version": 1, - "VersionName": "1.1", - "FriendlyName": "Avatar Core nDisplay", - "Description": "nDisplay integration for the Avatar Core Framework", - "Category": "Other", - "CreatedBy": "b.ReX", - "CreatedByURL": "b.ReX", - "DocsURL": "", - "MarketplaceURL": "", - "SupportURL": "", - "CanContainContent": true, - "IsBetaVersion": false, - "IsExperimentalVersion": false, - "Installed": false, - "Modules": [ - { - "Name": "AvatarCore_nDisplay", - "Type": "Runtime", - "LoadingPhase": "PreLoadingScreen" - } - ], - "Plugins": [ - { - "Name": "nDisplay", - "Enabled": true - }, - { - "Name": "AvatarCore_Manager", - "Enabled": true - } - ] -} \ No newline at end of file diff --git a/Unreal/Plugins/AvatarCore_nDisplay/Content/AvatarBaseReplicated.uasset b/Unreal/Plugins/AvatarCore_nDisplay/Content/AvatarBaseReplicated.uasset deleted file mode 100644 index b440b1e..0000000 --- a/Unreal/Plugins/AvatarCore_nDisplay/Content/AvatarBaseReplicated.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3ffc37cd0272d0cd97829e508ccddcce9a0ff55d0a64559b9a340805a1129993 -size 159662 diff --git a/Unreal/Plugins/AvatarCore_nDisplay/Content/BodyReplicated_AnimBP.uasset b/Unreal/Plugins/AvatarCore_nDisplay/Content/BodyReplicated_AnimBP.uasset deleted file mode 100644 index 4b66d31..0000000 --- a/Unreal/Plugins/AvatarCore_nDisplay/Content/BodyReplicated_AnimBP.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a7b3b542379c45c813e6f04853f9b0862d4a985de11fc009a2aecb728e01503d -size 70187 diff --git a/Unreal/Plugins/AvatarCore_nDisplay/Content/FaceReplicated_AvatarCore_AnimBP.uasset b/Unreal/Plugins/AvatarCore_nDisplay/Content/FaceReplicated_AvatarCore_AnimBP.uasset deleted file mode 100644 index 6559c01..0000000 --- a/Unreal/Plugins/AvatarCore_nDisplay/Content/FaceReplicated_AvatarCore_AnimBP.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:471da184b8567374ec098036ebea5c5ccfda27638968f380cd6d6d17c9f9c65e -size 128602 diff --git a/Unreal/Plugins/AvatarCore_nDisplay/Content/FaceReplicating_AvatarCore_AnimInst_Face_Child.uasset b/Unreal/Plugins/AvatarCore_nDisplay/Content/FaceReplicating_AvatarCore_AnimInst_Face_Child.uasset deleted file mode 100644 index ac069c7..0000000 --- a/Unreal/Plugins/AvatarCore_nDisplay/Content/FaceReplicating_AvatarCore_AnimInst_Face_Child.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:dc591c8c7d4d5ba84ef9f1e75898a1590231e66139fb26337897c2dcaab1cee2 -size 75700 diff --git a/Unreal/Plugins/AvatarCore_nDisplay/Resources/Icon128.png b/Unreal/Plugins/AvatarCore_nDisplay/Resources/Icon128.png deleted file mode 100644 index 25b1721..0000000 --- a/Unreal/Plugins/AvatarCore_nDisplay/Resources/Icon128.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b1831a717c8a8f83fd3819fef3f921966e33c0a546dec6a85592a4b9d81f21bd -size 5946 diff --git a/Unreal/Plugins/AvatarCore_nDisplay/Source/AvatarCore_nDisplay/AvatarCore_nDisplay.Build.cs b/Unreal/Plugins/AvatarCore_nDisplay/Source/AvatarCore_nDisplay/AvatarCore_nDisplay.Build.cs deleted file mode 100644 index f1f2451..0000000 --- a/Unreal/Plugins/AvatarCore_nDisplay/Source/AvatarCore_nDisplay/AvatarCore_nDisplay.Build.cs +++ /dev/null @@ -1,54 +0,0 @@ -// Some copyright should be here... - -using UnrealBuildTool; - -public class AvatarCore_nDisplay : ModuleRules -{ - public AvatarCore_nDisplay(ReadOnlyTargetRules Target) : base(Target) - { - PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; - - PublicIncludePaths.AddRange( - new string[] { - // ... add public include paths required here ... - } - ); - - - PrivateIncludePaths.AddRange( - new string[] { - // ... add other private include paths required here ... - } - ); - - - PublicDependencyModuleNames.AddRange( - new string[] - { - "Core", - "DisplayCluster", - // ... add other public dependencies that you statically link with here ... - } - ); - - - PrivateDependencyModuleNames.AddRange( - new string[] - { - "CoreUObject", - "Engine", - "Slate", - "SlateCore", - // ... add private dependencies that you statically link with here ... - } - ); - - - DynamicallyLoadedModuleNames.AddRange( - new string[] - { - // ... add any modules that your module loads dynamically here ... - } - ); - } -} diff --git a/Unreal/Plugins/AvatarCore_nDisplay/Source/AvatarCore_nDisplay/Private/AvatarCore_nDisplay.cpp b/Unreal/Plugins/AvatarCore_nDisplay/Source/AvatarCore_nDisplay/Private/AvatarCore_nDisplay.cpp deleted file mode 100644 index 8cd439c..0000000 --- a/Unreal/Plugins/AvatarCore_nDisplay/Source/AvatarCore_nDisplay/Private/AvatarCore_nDisplay.cpp +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#include "AvatarCore_nDisplay.h" - -#define LOCTEXT_NAMESPACE "FAvatarCore_nDisplayModule" - -void FAvatarCore_nDisplayModule::StartupModule() -{ - // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module - -} - -void FAvatarCore_nDisplayModule::ShutdownModule() -{ - // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, - // we call this function before unloading the module. - -} - -#undef LOCTEXT_NAMESPACE - -IMPLEMENT_MODULE(FAvatarCore_nDisplayModule, AvatarCore_nDisplay) \ No newline at end of file diff --git a/Unreal/Plugins/AvatarCore_nDisplay/Source/AvatarCore_nDisplay/Private/AvatarCore_nDisplayBPLibrary.cpp b/Unreal/Plugins/AvatarCore_nDisplay/Source/AvatarCore_nDisplay/Private/AvatarCore_nDisplayBPLibrary.cpp deleted file mode 100644 index 048f67a..0000000 --- a/Unreal/Plugins/AvatarCore_nDisplay/Source/AvatarCore_nDisplay/Private/AvatarCore_nDisplayBPLibrary.cpp +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#include "AvatarCore_nDisplayBPLibrary.h" -#include "Engine/Engine.h" -#include "AvatarCore_nDisplay.h" -#include "Animation/AnimInstance.h" -#include "Components/SkeletalMeshComponent.h" -#include "ReplicatedFaceCPPAnimInstance.h" -#include "Serialization/BufferArchive.h" -#include "Serialization/MemoryReader.h" - -UAvatarCore_nDisplayBPLibrary::UAvatarCore_nDisplayBPLibrary(const FObjectInitializer& ObjectInitializer) -: Super(ObjectInitializer) -{ - -} - -FDisplayClusterClusterEventBinary UAvatarCore_nDisplayBPLibrary::MakeClusterPoseEvent( - UAnimInstance* AnimInstance, - int32 EventID, - FName SnapshotName) -{ - FDisplayClusterClusterEventBinary OutEvent; - if (!AnimInstance) - { - return OutEvent; - } - - // 1) Capture skeleton pose into AnimInstance's internal local-space snapshot - AnimInstance->SavePoseSnapshot(SnapshotName); - - // 2) Retrieve the snapshot pointer - const FPoseSnapshot* PosePtr = AnimInstance->GetPoseSnapshot(SnapshotName); - if (!PosePtr || !PosePtr->bIsValid) - { - return OutEvent; - } - - // 3) Serialize the FPoseSnapshot struct - FBufferArchive Buffer(true); - const UScriptStruct* Struct = FPoseSnapshot::StaticStruct(); - Struct->SerializeBin(Buffer, const_cast(PosePtr)); - - // 4) Capture morph target weights directly from the mesh component after graph evaluation - TArray MorphNames; - TArray MorphWeights; - - //Get Morph Curve list and save them to buffer - TMap InOutCurveList; - - AnimInstance->GetAnimationCurveList(EAnimCurveType::MorphTargetCurve, InOutCurveList); - AnimInstance->GetAnimationCurveList(EAnimCurveType::AttributeCurve, InOutCurveList); - - const auto* foundItem = InOutCurveList.Find(FName(TEXT("CTRL_expressions_jawOpen"))); - float JawOpenValue = foundItem ? *foundItem : -1.0f; - - //UAvatarCore_nDisplayBPLibrary::PrintHighestMorphTarget(InOutCurveList); //Debug Printing - - InOutCurveList.GenerateKeyArray(MorphNames); - InOutCurveList.GenerateValueArray(MorphWeights); - Buffer << MorphNames; - Buffer << MorphWeights; - - // 5) Package into cluster event data - OutEvent.EventId = EventID; - OutEvent.EventData = MoveTemp(Buffer); - return OutEvent; -} - - -void UAvatarCore_nDisplayBPLibrary::ApplyClusterPoseEvent( - UAnimInstance* AnimInstance, - const FDisplayClusterClusterEventBinary& EventData) -{ - if (!AnimInstance) - { - return; - } - - // Deserialize skeleton pose and morph target data in one go - FPoseSnapshot Pose; - TArray MorphNames; - TArray MorphWeights; - - - const UScriptStruct* Struct = FPoseSnapshot::StaticStruct(); - { - FMemoryReader Reader(EventData.EventData, /*bIsPersistent=*/ true); - // 1) Skeleton pose - Struct->SerializeBin(Reader, &Pose); - // 2) Morph targets - Reader << MorphNames; - Reader << MorphWeights; - } - - // 3) Apply skeleton pose - if (Pose.bIsValid) - { - AnimInstance->RemovePoseSnapshot(Pose.SnapshotName); - FPoseSnapshot& InternalPose = AnimInstance->AddPoseSnapshot(Pose.SnapshotName); - InternalPose = Pose; - } - - TMap InOutCurveList; - - if (MorphNames.Num() == MorphWeights.Num()) - { - for (int32 i = 0; i < MorphNames.Num(); ++i) - { - InOutCurveList.Add(MorphNames[i], MorphWeights[i]); - } - } - else - { - if (GEngine) - GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Yellow, FString::Printf(TEXT("MorphNames and MorphWeights arrays have different lengths!"))); - } - - //UAvatarCore_nDisplayBPLibrary::PrintHighestMorphTarget(InOutCurveList); //Debug Printing - - // First apply to animation instance for the animation system to pick up - AnimInstance->AppendAnimationCurveList(EAnimCurveType::MorphTargetCurve, InOutCurveList); - AnimInstance->UpdateCurvesPostEvaluation(); - - // Then apply directly to the skeletal mesh component to ensure it works on all nDisplay nodes - if (auto* SkelMesh = AnimInstance->GetSkelMeshComponent()) - { - // Clear old targets - SkelMesh->ClearMorphTargets(); - if (auto* MI = Cast(SkelMesh->GetAnimInstance())) - { - MI->SetMorphTargetCurves(InOutCurveList); - } - - // (Optional) also set them directly on the mesh if you need immediate, nDisplay-safe updates: - for (auto& KV : InOutCurveList) - { - SkelMesh->SetMorphTarget(KV.Key, KV.Value, false); ///*bRemoveZeroWeight=*/ - } - } -} - -void UAvatarCore_nDisplayBPLibrary::PrintHighestMorphTarget(const TMap& MorphTargets) -{ - FName HighestKey; - float HighestValue = -FLT_MAX; - bool bFound = false; - for (const auto& Pair : MorphTargets) - { - if (!bFound || Pair.Value > HighestValue) - { - HighestKey = Pair.Key; - HighestValue = Pair.Value; - bFound = true; - } - } - if (bFound && GEngine) - { - GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Yellow, FString::Printf(TEXT("MorphName %s with %f is the highest"), *HighestKey.ToString(), HighestValue)); - } -} diff --git a/Unreal/Plugins/AvatarCore_nDisplay/Source/AvatarCore_nDisplay/Private/ReplicatedFaceCPPAnimInstance.cpp b/Unreal/Plugins/AvatarCore_nDisplay/Source/AvatarCore_nDisplay/Private/ReplicatedFaceCPPAnimInstance.cpp deleted file mode 100644 index c8f612c..0000000 --- a/Unreal/Plugins/AvatarCore_nDisplay/Source/AvatarCore_nDisplay/Private/ReplicatedFaceCPPAnimInstance.cpp +++ /dev/null @@ -1,14 +0,0 @@ -// Fill out your copyright notice in the Description page of Project Settings. - - -#include "ReplicatedFaceCPPAnimInstance.h" - -void UReplicatedFaceCPPAnimInstance::SetMorphTargetCurves(const TMap& NewCurves) -{ - PendingMorphCurves = NewCurves; -} - -void UReplicatedFaceCPPAnimInstance::NativePostEvaluateAnimation() -{ - Super::NativePostEvaluateAnimation(); -} \ No newline at end of file diff --git a/Unreal/Plugins/AvatarCore_nDisplay/Source/AvatarCore_nDisplay/Public/AvatarCore_nDisplay.h b/Unreal/Plugins/AvatarCore_nDisplay/Source/AvatarCore_nDisplay/Public/AvatarCore_nDisplay.h deleted file mode 100644 index d265bf7..0000000 --- a/Unreal/Plugins/AvatarCore_nDisplay/Source/AvatarCore_nDisplay/Public/AvatarCore_nDisplay.h +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#pragma once - -#include "Modules/ModuleManager.h" - -class FAvatarCore_nDisplayModule : public IModuleInterface -{ -public: - - /** IModuleInterface implementation */ - virtual void StartupModule() override; - virtual void ShutdownModule() override; -}; diff --git a/Unreal/Plugins/AvatarCore_nDisplay/Source/AvatarCore_nDisplay/Public/AvatarCore_nDisplayBPLibrary.h b/Unreal/Plugins/AvatarCore_nDisplay/Source/AvatarCore_nDisplay/Public/AvatarCore_nDisplayBPLibrary.h deleted file mode 100644 index ea114ed..0000000 --- a/Unreal/Plugins/AvatarCore_nDisplay/Source/AvatarCore_nDisplay/Public/AvatarCore_nDisplayBPLibrary.h +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#pragma once -#include "CoreMinimal.h" -#include "Cluster/DisplayClusterClusterEvent.h" -#include "Animation/PoseSnapshot.h" -#include "Animation/AnimInstance.h" -#include "Kismet/BlueprintFunctionLibrary.h" -#include "AvatarCore_nDisplayBPLibrary.generated.h" - -/* -* Function library class. -* Each function in it is expected to be static and represents blueprint node that can be called in any blueprint. -* -* When declaring function you can define metadata for the node. Key function specifiers will be BlueprintPure and BlueprintCallable. -* BlueprintPure - means the function does not affect the owning object in any way and thus creates a node without Exec pins. -* BlueprintCallable - makes a function which can be executed in Blueprints - Thus it has Exec pins. -* DisplayName - full name of the node, shown when you mouse over the node and in the blueprint drop down menu. -* Its lets you name the node using characters not allowed in C++ function names. -* CompactNodeTitle - the word(s) that appear on the node. -* Keywords - the list of keywords that helps you to find node when you search for it using Blueprint drop-down menu. -* Good example is "Print String" node which you can find also by using keyword "log". -* Category - the category your node will be under in the Blueprint drop-down menu. -* -* For more info on custom blueprint nodes visit documentation: -* https://wiki.unrealengine.com/Custom_Blueprint_Node_Creation -*/ -UCLASS() -class UAvatarCore_nDisplayBPLibrary : public UBlueprintFunctionLibrary -{ - GENERATED_UCLASS_BODY() - -public: - /** - * Prints the morph target name with the highest value from the provided map. - */ - UFUNCTION(BlueprintCallable, Category = "Animation|nDisplay") - static void PrintHighestMorphTarget(const TMap& MorphTargets); - /** - * Capture the current pose on Master, serialize to bytes, assign a unique EventId, - * and wrap in a cluster-binary event for broadcasting. - */ - UFUNCTION(BlueprintCallable, Category = "Animation|nDisplay") - static FDisplayClusterClusterEventBinary MakeClusterPoseEvent( - UAnimInstance* AnimInstance, - int32 EventID, - UPARAM(DisplayName = "Snapshot Name") FName SnapshotName = FName("ClusterPose") - ); - - /** - * Unpack a received cluster-binary event, deserialize bytes into a pose snapshot, - * and save it into the target AnimInstance under the same snapshot name. - */ - UFUNCTION(BlueprintCallable, Category = "Animation|nDisplay") - static void ApplyClusterPoseEvent( - UAnimInstance* AnimInstance, - const FDisplayClusterClusterEventBinary& EventData - ); -}; diff --git a/Unreal/Plugins/AvatarCore_nDisplay/Source/AvatarCore_nDisplay/Public/ReplicatedFaceCPPAnimInstance.h b/Unreal/Plugins/AvatarCore_nDisplay/Source/AvatarCore_nDisplay/Public/ReplicatedFaceCPPAnimInstance.h deleted file mode 100644 index ce6c352..0000000 --- a/Unreal/Plugins/AvatarCore_nDisplay/Source/AvatarCore_nDisplay/Public/ReplicatedFaceCPPAnimInstance.h +++ /dev/null @@ -1,30 +0,0 @@ -// Fill out your copyright notice in the Description page of Project Settings. - -#pragma once - -#include "CoreMinimal.h" -#include "Animation/AnimInstance.h" -#include "ReplicatedFaceCPPAnimInstance.generated.h" - -/** - * - */ -UCLASS() -class AVATARCORE_NDISPLAY_API UReplicatedFaceCPPAnimInstance : public UAnimInstance -{ - GENERATED_BODY() - -public: - // Called by your gRPC callback when new morph data arrives - UFUNCTION(BlueprintCallable, Category = "Morph") - void SetMorphTargetCurves(const TMap& NewCurves); - - // Temporary storage of the latest values until injection - UPROPERTY(BlueprintReadOnly, Transient) - TMap PendingMorphCurves; - -protected: - // AnimInstance hook where we inject our curves - virtual void NativePostEvaluateAnimation() override; - -}; diff --git a/Unreal/Plugins/SerialCOM/Content/Arduino_Example/BP_Arduino_Comm.uasset b/Unreal/Plugins/SerialCOM/Content/Arduino_Example/BP_Arduino_Comm.uasset new file mode 100644 index 0000000..76c01a6 --- /dev/null +++ b/Unreal/Plugins/SerialCOM/Content/Arduino_Example/BP_Arduino_Comm.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:469b8c816bc386affa14f8fa389856333fee1ce9bb30ecf6fc83961ec0a96761 +size 226265 diff --git a/Unreal/Plugins/SerialCOM/Content/Arduino_Example/Instructions.txt b/Unreal/Plugins/SerialCOM/Content/Arduino_Example/Instructions.txt new file mode 100644 index 0000000..fd97db9 --- /dev/null +++ b/Unreal/Plugins/SerialCOM/Content/Arduino_Example/Instructions.txt @@ -0,0 +1,15 @@ +Simple Arduino program: + +Arduino: +Analog to Digital: +- Test an analog signal on A0 +- Read the sensor value(0-1023) +- Map the sensor range (0-1023) to a range of 1-100 +- Print the mapped value to the serial port + +Unreal Engine: + +- Copy the BP_Arduino_Comm.uasset inside the "Content" folder of your UE Project. +- Change the "Port" number to the correspondent Port of your Arduino Board. +- Play: The values from 0 to 100 will print on your screen. + \ No newline at end of file diff --git a/Unreal/Plugins/SerialCOM/Content/Arduino_Example/_Arduino_Example.ino b/Unreal/Plugins/SerialCOM/Content/Arduino_Example/_Arduino_Example.ino new file mode 100644 index 0000000..921d9d2 --- /dev/null +++ b/Unreal/Plugins/SerialCOM/Content/Arduino_Example/_Arduino_Example.ino @@ -0,0 +1,18 @@ + + +void setup() { + // initialize serial communication at 9600 bits per second: + Serial.begin(9600); +} + + +void loop() { + // read the sensor value: + int sensorValue = analogRead(A0); + // map the sensor range (0-1023) to a range of 1-100: + int mappedValue = map(sensorValue, 0, 1023, 0, 100); + // print the mapped value to the serial port: + Serial.println(mappedValue); + // wait a bit for the next reading: + delay(100); +} diff --git a/Unreal/Plugins/SerialCOM/Content/UE_BLUEPRINTS/BP_SerialCom_v4_UE510.uasset b/Unreal/Plugins/SerialCOM/Content/UE_BLUEPRINTS/BP_SerialCom_v4_UE510.uasset new file mode 100644 index 0000000..0706808 --- /dev/null +++ b/Unreal/Plugins/SerialCOM/Content/UE_BLUEPRINTS/BP_SerialCom_v4_UE510.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4c06bac00b9b5a4083503d2f88b8b37c91e9492de02a6bdc51c073b0640ff619 +size 209148 diff --git a/Unreal/Plugins/SerialCOM/Content/UE_BLUEPRINTS/INSTRUCTIONS.txt b/Unreal/Plugins/SerialCOM/Content/UE_BLUEPRINTS/INSTRUCTIONS.txt new file mode 100644 index 0000000..5922973 --- /dev/null +++ b/Unreal/Plugins/SerialCOM/Content/UE_BLUEPRINTS/INSTRUCTIONS.txt @@ -0,0 +1,39 @@ + +BLUEPRINT FOR UNREAL ENGINE (5.1.0) +------------------------------------------------------------------------------------------------- + +#1 Inside this folder "BLUEPRINTS", there is the same blueprint example for each different UE version. + +#2 Drag and Drop this file "BP_SerialCom_UExx.uasset" into your project.s + +#3 Drag and Drop ""BP_Serial_UEXxx" blueprint inside your Level. + +#4 Inside the BP_Serial_UExx, change the "Port" number to your correspondent port in your micro-controller or Serial Communication Application. + +#5 Connect your Arduino or any other Serial Communication device you your computer. + +#6 If you see "PORT IS INITIALIZED!" on your screen, that is a confirmation that the port is connected to a Serial Port. + + +#7 Once you confirm your Com port, you are ready to test the blueprint. + - Press the "SPACE BAR" to print a string on the serial port and send another message to the UE screen. + - Press the letter "Q" to close the Serial Port. + - Press the letter "Z" to reconnect to the Serial Port. + +Enjoy it! + + +https://github.com/videofeedback/Unreal-Engine-Plugin-Communication-Serial-Port/ + +For support and comments, + +Discord: https://discord.ramiroslab.com + +Github: https://github.reamiroslab.com + +YouTube: https://youtube.ramiroslab.com + +Thank you! +Ramiro Montes De Oca, +Ramiro's Lab + diff --git a/Unreal/Plugins/SerialCOM/INSTRUCTIONS-PLUGIN.txt b/Unreal/Plugins/SerialCOM/INSTRUCTIONS-PLUGIN.txt new file mode 100644 index 0000000..84e7206 --- /dev/null +++ b/Unreal/Plugins/SerialCOM/INSTRUCTIONS-PLUGIN.txt @@ -0,0 +1,29 @@ + +BLUEPRINT FOR UNREAL ENGINE (5.4.2) +------------------------------------------------------------- + +#1 Select the Zipfile for your correct Engine: + - SERIALCOM_5_UE532/SERIALCOM_5UE532.zip (Unreal Engine 5.4.2) + +#2 Unzip the correspondent Zip file into your project "Plugins" Folder. If doesn't exist, create one. +---------->Your_Project_Folder--> +----------------->Binaries +----------------->Config +----------------->Content +----------------->DerivedDataCache +----------------->Intermidiate +----------------->Plugins_ + |------>SERIALCOM_EUXXX +----------------->Saved +Your_Project.uproject + +#3 Activate "Communication Serial Port (Serial COM)" plugin from your plugin list. + +#4 Re-Start your Project. You are ready to use the plugin!!! + + +Enjoy it! + + +https://github.com/videofeedback/Unreal-Engine-Plugin-Communication-Serial-Port/ + diff --git a/Unreal/Plugins/SerialCOM/LICENSE.txt b/Unreal/Plugins/SerialCOM/LICENSE.txt new file mode 100644 index 0000000..5ccd7a5 --- /dev/null +++ b/Unreal/Plugins/SerialCOM/LICENSE.txt @@ -0,0 +1,23 @@ +MIT License + +Copyright (c) 2015 v1 FusionLabz/Gryzly32 (UE4Duino) +Copyright (c) 2018-2020 v2 Rodrigo Villani Pereira (UE4Duino) +Copyright (c) 2021-2024 v3/v4/v5 Ramiro Montes De Oca (SerialCOM) fork of (UE4Duino 2.2.5) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Unreal/Plugins/SerialCOM/RELEASE_v5.5.4.2.1.txt b/Unreal/Plugins/SerialCOM/RELEASE_v5.5.4.2.1.txt new file mode 100644 index 0000000..35f99d0 --- /dev/null +++ b/Unreal/Plugins/SerialCOM/RELEASE_v5.5.4.2.1.txt @@ -0,0 +1,32 @@ + + +"Serial COM" v5.5.4.2.1 RELEASE NOTES +-------------------------------------------------------------- +- Version compatible with Unreal Engine v5.4.2 + + +Known Issues: +---------------------------- + +- "Serial COM" v5.5.4.2.1 will not be compatible with "UE4Duino" 2.2.5 because the library doesn't share the "UE4DUINO" name anymore, using its own new "SERIALCOM" identifier. Changing to this library will make the old EU4Duino modules in red, indicating that those components are not available anymore. +Solution: Each component can be replaced with the current version by replacing each one by hand. The replacement equivalent chart is available at: +https://github.com/videofeedback/Unreal-Engine-Plugin-Communication-Serial-Port (Modules lists comparison chart) + +- Arduino doesn't connects again if you close the project without closing the port. +Solution: Use the best practice of creating an "Event End Play" with "Close Serial Port" connected to the event. This is the cleanest solution for this problem and best practice. +For more information, visit https://github.com/videofeedback/Unreal-Engine-Plugin-Communication-Serial-Port where we describe the best practices for the use of this plugin. + + + +----------------------------------------------------------------------------------------------------- +Ramiro Montes De Oca (Ramiro's Lab) +Discord: https://discord.ramiroslab.com +YouTube: https://youtube.ramiroslab.com +Github: https://github.ramiroslab.com + +---------------------------------------------------------------------------------------------------- +Copyright (c) 2015 v1 FusionLabz/Gryzly32 (UE4Duino) +Copyright (c) 2018-2020 v2 Rodrigo Villani Pereira (UE4Duino) +Copyright (c) 2021-2024 v3/v4/v5 Ramiro Montes De Oca (SerialCOM) fork of (UE4Duino 2.2.5) + Major modifications (See previous releases notes) + + diff --git a/Unreal/Plugins/SerialCOM/Resources/Icon128.png b/Unreal/Plugins/SerialCOM/Resources/Icon128.png new file mode 100644 index 0000000..25d5c92 --- /dev/null +++ b/Unreal/Plugins/SerialCOM/Resources/Icon128.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ce859213c9fe08114b63bf5cf715a5ee5b2ea98729c616a696390c2ddca637e9 +size 32536 diff --git a/Unreal/Plugins/SerialCOM/SERIALCOM.uplugin b/Unreal/Plugins/SerialCOM/SERIALCOM.uplugin new file mode 100644 index 0000000..35d90a2 --- /dev/null +++ b/Unreal/Plugins/SerialCOM/SERIALCOM.uplugin @@ -0,0 +1,28 @@ +{ + "FileVersion": 3, + "Version": 3, + "VersionName": "5.5.4.2.1", + "FriendlyName": "Communication Serial Port (Serial COM)", + "Description": "This library enables Serial communication with Unreal Engine 4/5. Added DTR/RTS flow control. Compatible with Arduino projects and any other device requiring Serial Communication. This project was forked from EU4Duino (v1 Gryzly32/FuzionLabs, v2 Rodrigo Villani). Serial Com v3/v4/v5 by Ramiro Montes De Oca.", + "Category": "Input", + "CreatedBy": "Ramiro Montes De Oca", + "CreatedByURL": "https://github.com/videofeedback/RamirosLab", + "DocsURL": "https://github.com/videofeedback/Unreal_Engine_SerialCOM_Plugin", + "MarketplaceURL": "", + "SupportURL": "https://discord.ramiroslab.com/", + "EngineVersion": "5.6.0", + "CanContainContent": true, + "IsBetaVersion": true, + "Installed": true, + "Modules": [ + { + "Name": "SERIALCOM", + "Type": "Runtime", + "LoadingPhase": "PreDefault", + "PlatformAllowList": [ + "Win64" + ] + } + ], + "IsExperimentalVersion": false +} \ No newline at end of file diff --git a/Unreal/Plugins/AvatarCore_FaceDetector/Source/ThirdParty/ffprobe.exe b/Unreal/Plugins/SerialCOM/SerialCOM/Binaries/Win64/UnrealEditor-SERIALCOM.pdb similarity index 52% rename from Unreal/Plugins/AvatarCore_FaceDetector/Source/ThirdParty/ffprobe.exe rename to Unreal/Plugins/SerialCOM/SerialCOM/Binaries/Win64/UnrealEditor-SERIALCOM.pdb index f06e311..d1e5a76 100644 Binary files a/Unreal/Plugins/AvatarCore_FaceDetector/Source/ThirdParty/ffprobe.exe and b/Unreal/Plugins/SerialCOM/SerialCOM/Binaries/Win64/UnrealEditor-SERIALCOM.pdb differ diff --git a/Unreal/Plugins/SerialCOM/SerialCOM/Binaries/Win64/UnrealEditor.modules b/Unreal/Plugins/SerialCOM/SerialCOM/Binaries/Win64/UnrealEditor.modules new file mode 100644 index 0000000..3562d46 --- /dev/null +++ b/Unreal/Plugins/SerialCOM/SerialCOM/Binaries/Win64/UnrealEditor.modules @@ -0,0 +1,7 @@ +{ + "BuildId": "33043543", + "Modules": + { + "SERIALCOM": "UnrealEditor-SERIALCOM.dll" + } +} \ No newline at end of file diff --git a/Unreal/Plugins/SerialCOM/SerialCOM/Content/Arduino_Example/BP_Arduino_Comm.uasset b/Unreal/Plugins/SerialCOM/SerialCOM/Content/Arduino_Example/BP_Arduino_Comm.uasset new file mode 100644 index 0000000..76c01a6 --- /dev/null +++ b/Unreal/Plugins/SerialCOM/SerialCOM/Content/Arduino_Example/BP_Arduino_Comm.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:469b8c816bc386affa14f8fa389856333fee1ce9bb30ecf6fc83961ec0a96761 +size 226265 diff --git a/Unreal/Plugins/SerialCOM/SerialCOM/Content/Arduino_Example/Instructions.txt b/Unreal/Plugins/SerialCOM/SerialCOM/Content/Arduino_Example/Instructions.txt new file mode 100644 index 0000000..fd97db9 --- /dev/null +++ b/Unreal/Plugins/SerialCOM/SerialCOM/Content/Arduino_Example/Instructions.txt @@ -0,0 +1,15 @@ +Simple Arduino program: + +Arduino: +Analog to Digital: +- Test an analog signal on A0 +- Read the sensor value(0-1023) +- Map the sensor range (0-1023) to a range of 1-100 +- Print the mapped value to the serial port + +Unreal Engine: + +- Copy the BP_Arduino_Comm.uasset inside the "Content" folder of your UE Project. +- Change the "Port" number to the correspondent Port of your Arduino Board. +- Play: The values from 0 to 100 will print on your screen. + \ No newline at end of file diff --git a/Unreal/Plugins/SerialCOM/SerialCOM/Content/Arduino_Example/_Arduino_Example.ino b/Unreal/Plugins/SerialCOM/SerialCOM/Content/Arduino_Example/_Arduino_Example.ino new file mode 100644 index 0000000..921d9d2 --- /dev/null +++ b/Unreal/Plugins/SerialCOM/SerialCOM/Content/Arduino_Example/_Arduino_Example.ino @@ -0,0 +1,18 @@ + + +void setup() { + // initialize serial communication at 9600 bits per second: + Serial.begin(9600); +} + + +void loop() { + // read the sensor value: + int sensorValue = analogRead(A0); + // map the sensor range (0-1023) to a range of 1-100: + int mappedValue = map(sensorValue, 0, 1023, 0, 100); + // print the mapped value to the serial port: + Serial.println(mappedValue); + // wait a bit for the next reading: + delay(100); +} diff --git a/Unreal/Plugins/SerialCOM/SerialCOM/Content/UE_BLUEPRINTS/BP_SerialCom_v4_UE510.uasset b/Unreal/Plugins/SerialCOM/SerialCOM/Content/UE_BLUEPRINTS/BP_SerialCom_v4_UE510.uasset new file mode 100644 index 0000000..0706808 --- /dev/null +++ b/Unreal/Plugins/SerialCOM/SerialCOM/Content/UE_BLUEPRINTS/BP_SerialCom_v4_UE510.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4c06bac00b9b5a4083503d2f88b8b37c91e9492de02a6bdc51c073b0640ff619 +size 209148 diff --git a/Unreal/Plugins/SerialCOM/SerialCOM/Content/UE_BLUEPRINTS/INSTRUCTIONS.txt b/Unreal/Plugins/SerialCOM/SerialCOM/Content/UE_BLUEPRINTS/INSTRUCTIONS.txt new file mode 100644 index 0000000..5922973 --- /dev/null +++ b/Unreal/Plugins/SerialCOM/SerialCOM/Content/UE_BLUEPRINTS/INSTRUCTIONS.txt @@ -0,0 +1,39 @@ + +BLUEPRINT FOR UNREAL ENGINE (5.1.0) +------------------------------------------------------------------------------------------------- + +#1 Inside this folder "BLUEPRINTS", there is the same blueprint example for each different UE version. + +#2 Drag and Drop this file "BP_SerialCom_UExx.uasset" into your project.s + +#3 Drag and Drop ""BP_Serial_UEXxx" blueprint inside your Level. + +#4 Inside the BP_Serial_UExx, change the "Port" number to your correspondent port in your micro-controller or Serial Communication Application. + +#5 Connect your Arduino or any other Serial Communication device you your computer. + +#6 If you see "PORT IS INITIALIZED!" on your screen, that is a confirmation that the port is connected to a Serial Port. + + +#7 Once you confirm your Com port, you are ready to test the blueprint. + - Press the "SPACE BAR" to print a string on the serial port and send another message to the UE screen. + - Press the letter "Q" to close the Serial Port. + - Press the letter "Z" to reconnect to the Serial Port. + +Enjoy it! + + +https://github.com/videofeedback/Unreal-Engine-Plugin-Communication-Serial-Port/ + +For support and comments, + +Discord: https://discord.ramiroslab.com + +Github: https://github.reamiroslab.com + +YouTube: https://youtube.ramiroslab.com + +Thank you! +Ramiro Montes De Oca, +Ramiro's Lab + diff --git a/Unreal/Plugins/SerialCOM/SerialCOM/INSTRUCTIONS-PLUGIN.txt b/Unreal/Plugins/SerialCOM/SerialCOM/INSTRUCTIONS-PLUGIN.txt new file mode 100644 index 0000000..84e7206 --- /dev/null +++ b/Unreal/Plugins/SerialCOM/SerialCOM/INSTRUCTIONS-PLUGIN.txt @@ -0,0 +1,29 @@ + +BLUEPRINT FOR UNREAL ENGINE (5.4.2) +------------------------------------------------------------- + +#1 Select the Zipfile for your correct Engine: + - SERIALCOM_5_UE532/SERIALCOM_5UE532.zip (Unreal Engine 5.4.2) + +#2 Unzip the correspondent Zip file into your project "Plugins" Folder. If doesn't exist, create one. +---------->Your_Project_Folder--> +----------------->Binaries +----------------->Config +----------------->Content +----------------->DerivedDataCache +----------------->Intermidiate +----------------->Plugins_ + |------>SERIALCOM_EUXXX +----------------->Saved +Your_Project.uproject + +#3 Activate "Communication Serial Port (Serial COM)" plugin from your plugin list. + +#4 Re-Start your Project. You are ready to use the plugin!!! + + +Enjoy it! + + +https://github.com/videofeedback/Unreal-Engine-Plugin-Communication-Serial-Port/ + diff --git a/Unreal/Plugins/SerialCOM/SerialCOM/LICENSE.txt b/Unreal/Plugins/SerialCOM/SerialCOM/LICENSE.txt new file mode 100644 index 0000000..5ccd7a5 --- /dev/null +++ b/Unreal/Plugins/SerialCOM/SerialCOM/LICENSE.txt @@ -0,0 +1,23 @@ +MIT License + +Copyright (c) 2015 v1 FusionLabz/Gryzly32 (UE4Duino) +Copyright (c) 2018-2020 v2 Rodrigo Villani Pereira (UE4Duino) +Copyright (c) 2021-2024 v3/v4/v5 Ramiro Montes De Oca (SerialCOM) fork of (UE4Duino 2.2.5) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Unreal/Plugins/SerialCOM/SerialCOM/RELEASE_v5.5.4.2.1.txt b/Unreal/Plugins/SerialCOM/SerialCOM/RELEASE_v5.5.4.2.1.txt new file mode 100644 index 0000000..35f99d0 --- /dev/null +++ b/Unreal/Plugins/SerialCOM/SerialCOM/RELEASE_v5.5.4.2.1.txt @@ -0,0 +1,32 @@ + + +"Serial COM" v5.5.4.2.1 RELEASE NOTES +-------------------------------------------------------------- +- Version compatible with Unreal Engine v5.4.2 + + +Known Issues: +---------------------------- + +- "Serial COM" v5.5.4.2.1 will not be compatible with "UE4Duino" 2.2.5 because the library doesn't share the "UE4DUINO" name anymore, using its own new "SERIALCOM" identifier. Changing to this library will make the old EU4Duino modules in red, indicating that those components are not available anymore. +Solution: Each component can be replaced with the current version by replacing each one by hand. The replacement equivalent chart is available at: +https://github.com/videofeedback/Unreal-Engine-Plugin-Communication-Serial-Port (Modules lists comparison chart) + +- Arduino doesn't connects again if you close the project without closing the port. +Solution: Use the best practice of creating an "Event End Play" with "Close Serial Port" connected to the event. This is the cleanest solution for this problem and best practice. +For more information, visit https://github.com/videofeedback/Unreal-Engine-Plugin-Communication-Serial-Port where we describe the best practices for the use of this plugin. + + + +----------------------------------------------------------------------------------------------------- +Ramiro Montes De Oca (Ramiro's Lab) +Discord: https://discord.ramiroslab.com +YouTube: https://youtube.ramiroslab.com +Github: https://github.ramiroslab.com + +---------------------------------------------------------------------------------------------------- +Copyright (c) 2015 v1 FusionLabz/Gryzly32 (UE4Duino) +Copyright (c) 2018-2020 v2 Rodrigo Villani Pereira (UE4Duino) +Copyright (c) 2021-2024 v3/v4/v5 Ramiro Montes De Oca (SerialCOM) fork of (UE4Duino 2.2.5) + Major modifications (See previous releases notes) + + diff --git a/Unreal/Plugins/SerialCOM/SerialCOM/Resources/Icon128.png b/Unreal/Plugins/SerialCOM/SerialCOM/Resources/Icon128.png new file mode 100644 index 0000000..25d5c92 --- /dev/null +++ b/Unreal/Plugins/SerialCOM/SerialCOM/Resources/Icon128.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ce859213c9fe08114b63bf5cf715a5ee5b2ea98729c616a696390c2ddca637e9 +size 32536 diff --git a/Unreal/Plugins/SerialCOM/SerialCOM/SERIALCOM.uplugin b/Unreal/Plugins/SerialCOM/SerialCOM/SERIALCOM.uplugin new file mode 100644 index 0000000..3841386 --- /dev/null +++ b/Unreal/Plugins/SerialCOM/SerialCOM/SERIALCOM.uplugin @@ -0,0 +1,28 @@ +{ + "FileVersion": 3, + "Version": 3, + "VersionName": "5.5.4.2.1", + "FriendlyName": "Communication Serial Port (Serial COM)", + "Description": "This library enables Serial communication with Unreal Engine 4/5. Added DTR/RTS flow control. Compatible with Arduino projects and any other device requiring Serial Communication. This project was forked from EU4Duino (v1 Gryzly32/FuzionLabs, v2 Rodrigo Villani). Serial Com v3/v4/v5 by Ramiro Montes De Oca.", + "Category": "Input", + "CreatedBy": "Ramiro Montes De Oca", + "CreatedByURL": "https://github.com/videofeedback/RamirosLab", + "DocsURL": "https://github.com/videofeedback/Unreal_Engine_SerialCOM_Plugin", + "MarketplaceURL": "", + "SupportURL": "https://discord.ramiroslab.com/", + "EngineVersion": "5.4.0", + "CanContainContent": true, + "IsBetaVersion": true, + "Installed": true, + "Modules": [ + { + "Name": "SERIALCOM", + "Type": "Runtime", + "LoadingPhase": "PreDefault", + "PlatformAllowList": [ + "Win64" + ] + } + ], + "IsExperimentalVersion": false +} \ No newline at end of file diff --git a/Unreal/Plugins/SerialCOM/SerialCOM/Source/SERIALCOM/Private/SERIALCOMModule.cpp b/Unreal/Plugins/SerialCOM/SerialCOM/Source/SERIALCOM/Private/SERIALCOMModule.cpp new file mode 100644 index 0000000..42dd0e0 --- /dev/null +++ b/Unreal/Plugins/SerialCOM/SerialCOM/Source/SERIALCOM/Private/SERIALCOMModule.cpp @@ -0,0 +1,21 @@ +#include "SERIALCOMModule.h" + +IMPLEMENT_MODULE(SERIALCOMModule, SERIALCOM); + +#define LOCTEXT_NAMESPACE "SERIALCOM" + +SERIALCOMModule::SERIALCOMModule() +{ +} + +void SERIALCOMModule::StartupModule() +{ + // Startup LOG MSG + UE_LOG(SERIALCOMLog, Warning, TEXT("SERIALCOM: Log Started")); +} + +void SERIALCOMModule::ShutdownModule() +{ + // Shutdown LOG MSG + UE_LOG(SERIALCOMLog, Warning, TEXT("SERIALCOM: Log Ended")); +} \ No newline at end of file diff --git a/Unreal/Plugins/SerialCOM/SerialCOM/Source/SERIALCOM/Private/SerialCom.cpp b/Unreal/Plugins/SerialCOM/SerialCOM/Source/SERIALCOM/Private/SerialCom.cpp new file mode 100644 index 0000000..a988a53 --- /dev/null +++ b/Unreal/Plugins/SerialCOM/SerialCOM/Source/SERIALCOM/Private/SerialCom.cpp @@ -0,0 +1,539 @@ +//Based off the "Arduino and C++ (for Windows)" code found at: http://playground.arduino.cc/Interfacing/CPPWindows + +#include "SerialCom.h" + +#include "Windows/AllowWindowsPlatformTypes.h" +#include "Windows/MinWindows.h" +#include "Windows/HideWindowsPlatformTypes.h" + + +#define BOOL2bool(B) B == 0 ? false : true + +USerialCom* USerialCom::OpenComPortWithFlowControl(bool& bOpened, int32 Port, int32 BaudRate, bool DTR, bool RTS) +{ + USerialCom* Serial = NewObject(); + bOpened = Serial->OpenWFC(Port, BaudRate, DTR, RTS); + return Serial; +} + + +////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////// + + +USerialCom* USerialCom::OpenComPort(bool& bOpened, int32 Port, int32 BaudRate) +{ + USerialCom* Serial = NewObject(); + bOpened = Serial->OpenWFC(Port, BaudRate); + return Serial; +} + +////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////// + + + + +int32 USerialCom::BytesToInt(TArray Bytes) +{ + if (Bytes.Num() != 4) + { + return 0; + } + + return *reinterpret_cast(Bytes.GetData()); +} + +TArray USerialCom::IntToBytes(const int32 &Int) +{ + TArray Bytes; + Bytes.Append(reinterpret_cast(&Int), 4); + return Bytes; +} + + + +float USerialCom::BytesToFloat(TArray Bytes) +{ + if (Bytes.Num() != 4) + { + return 0; + } + + return *reinterpret_cast(Bytes.GetData()); +} + + + +TArray USerialCom::FloatToBytes(const float &Float) +{ + TArray Bytes; + Bytes.Append(reinterpret_cast(&Float), 4); + return Bytes; +} + + + + +USerialCom::USerialCom() + : WriteLineEnd(ELineEnd::n) + , m_hIDComDev(nullptr) + , m_OverlappedRead(nullptr) + , m_OverlappedWrite(nullptr) + , m_Port(-1) + , m_Baud(-1) +{ + // Allocate the OVERLAPPED structs + m_OverlappedRead = new OVERLAPPED(); + m_OverlappedWrite = new OVERLAPPED(); + + FMemory::Memset(m_OverlappedRead, 0, sizeof(OVERLAPPED)); + FMemory::Memset(m_OverlappedWrite, 0, sizeof(OVERLAPPED)); +} + +USerialCom::~USerialCom() +{ + Close(); + + // Delete allocated OVERLAPPED structs + delete m_OverlappedRead; + delete m_OverlappedWrite; +} + + +////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////// + + + + + +////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////// + + + + + +//////////////////////////////////////////////////////////////////// + +bool USerialCom::OpenWFC(int32 nPort, int32 nBaud, bool bDTR, bool bRTS) +{ + if (nPort < 0) + { + UE_LOG(LogTemp, Error, TEXT("Invalid port number: %d"), nPort); + return false; + } + if (m_hIDComDev) + { + UE_LOG(LogTemp, Warning, TEXT("Trying to use opened Serial instance to open a new one. " + "Current open instance port: %d | Port tried: %d"), m_Port, nPort); + return false; + } + + FString szPort; + if (nPort < 10) + szPort = FString::Printf(TEXT("COM%d"), nPort); + else + szPort = FString::Printf(TEXT("\\\\.\\COM%d"), nPort); + DCB dcb; + + m_hIDComDev = CreateFile(*szPort, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL); + if (m_hIDComDev == NULL) + { + unsigned long dwError = GetLastError(); + UE_LOG(LogTemp, Error, TEXT("Failed to open port COM%d (%s). Error: %08X"), nPort, *szPort, dwError); + return false; + } + + FMemory::Memset(m_OverlappedRead, 0, sizeof(OVERLAPPED)); + FMemory::Memset(m_OverlappedWrite, 0, sizeof(OVERLAPPED)); + + COMMTIMEOUTS CommTimeOuts; + //CommTimeOuts.ReadIntervalTimeout = 10; + CommTimeOuts.ReadIntervalTimeout = 0xFFFFFFFF; + CommTimeOuts.ReadTotalTimeoutMultiplier = 0; + CommTimeOuts.ReadTotalTimeoutConstant = 0; + CommTimeOuts.WriteTotalTimeoutMultiplier = 0; + CommTimeOuts.WriteTotalTimeoutConstant = 10; + SetCommTimeouts(m_hIDComDev, &CommTimeOuts); + + m_OverlappedRead->hEvent = CreateEvent(NULL, true, false, NULL); + m_OverlappedWrite->hEvent = CreateEvent(NULL, true, false, NULL); + + dcb.DCBlength = sizeof(DCB); + GetCommState(m_hIDComDev, &dcb); + dcb.BaudRate = nBaud; + dcb.ByteSize = 8; + if (bDTR == true) + { + dcb.fDtrControl = DTR_CONTROL_ENABLE; + } + else + { + dcb.fDtrControl = DTR_CONTROL_DISABLE; + } + + if (bRTS == true) + { + dcb.fRtsControl = RTS_CONTROL_ENABLE; + } + else + { + dcb.fRtsControl = RTS_CONTROL_DISABLE; + } + + if (!SetCommState(m_hIDComDev, &dcb) || + !SetupComm(m_hIDComDev, 10000, 10000) || + m_OverlappedRead->hEvent == NULL || + m_OverlappedWrite->hEvent == NULL) + { + unsigned long dwError = GetLastError(); + if (m_OverlappedRead->hEvent != NULL) CloseHandle(m_OverlappedRead->hEvent); + if (m_OverlappedWrite->hEvent != NULL) CloseHandle(m_OverlappedWrite->hEvent); + CloseHandle(m_hIDComDev); + m_hIDComDev = NULL; + UE_LOG(LogTemp, Error, TEXT("Failed to setup port COM%d. Error: %08X"), nPort, dwError); + return false; + } + + //FPlatformProcess::Sleep(0.05f); + AddToRoot(); + m_Port = nPort; + m_Baud = nBaud; + return true; +} + +//////////////////////////////////////////////////////////////////// + + +void USerialCom::Close() +{ + if (!m_hIDComDev) return; + + if (m_OverlappedRead->hEvent != NULL) CloseHandle(m_OverlappedRead->hEvent); + if (m_OverlappedWrite->hEvent != NULL) CloseHandle(m_OverlappedWrite->hEvent); + CloseHandle(m_hIDComDev); + m_hIDComDev = NULL; + + RemoveFromRoot(); +} + +FString USerialCom::ReadString(bool &bSuccess) +{ + return ReadStringUntil(bSuccess, '\0'); +} + +FString USerialCom::Readln(bool &bSuccess) +{ + return ReadStringUntil(bSuccess, '\n'); +} + +FString USerialCom::ReadStringUntil(bool &bSuccess, uint8 Terminator) +{ + bSuccess = false; + if (!m_hIDComDev) return TEXT(""); + + TArray Chars; + uint8 Byte = 0x0; + bool bReadStatus; + unsigned long dwBytesRead, dwErrorFlags; + COMSTAT ComStat; + + ClearCommError(m_hIDComDev, &dwErrorFlags, &ComStat); + if (!ComStat.cbInQue) return TEXT(""); + + do { + bReadStatus = BOOL2bool(ReadFile( + m_hIDComDev, + &Byte, + 1, + &dwBytesRead, + m_OverlappedRead)); + + if (!bReadStatus) + { + if (GetLastError() == ERROR_IO_PENDING) + { + WaitForSingleObject(m_OverlappedRead->hEvent, 2000); + } + else + { + Chars.Add(0x0); + break; + } + } + + if (Byte == Terminator || dwBytesRead == 0) + { + // when Terminator is \n, we know we're expecting lines from Arduino. But those + // are ended in \r\n. That means that if we found the line Terminator (\n), our previous + // character could be \r. If it is, we remove that from the array. + if (Chars.Num() > 0 && Terminator == '\n' && Chars.Top() == '\r') Chars.Pop(false); + + Chars.Add(0x0); + break; + } + else Chars.Add(Byte); + + } while (Byte != 0x0 && Byte != Terminator); + + bSuccess = true; + auto Convert = FUTF8ToTCHAR((ANSICHAR*)Chars.GetData()); + return FString(Convert.Get()); +} + +float USerialCom::ReadFloat(bool &bSuccess) +{ + bSuccess = false; + + TArray Bytes = ReadBytes(4); + if (Bytes.Num() == 0) return 0; + + bSuccess = true; + return *(reinterpret_cast(Bytes.GetData())); +} + +int32 USerialCom::ReadInt(bool &bSuccess) +{ + bSuccess = false; + + TArray Bytes = ReadBytes(4); + if (Bytes.Num() == 0) return 0; + + bSuccess = true; + return *(reinterpret_cast(Bytes.GetData())); +} + +uint8 USerialCom::ReadByte(bool &bSuccess) +{ + bSuccess = false; + if (!m_hIDComDev) return 0x0; + + uint8 Byte = 0x0; + bool bReadStatus; + unsigned long dwBytesRead, dwErrorFlags; + COMSTAT ComStat; + + ClearCommError(m_hIDComDev, &dwErrorFlags, &ComStat); + if (!ComStat.cbInQue) return 0x0; + + bReadStatus = BOOL2bool(ReadFile( + m_hIDComDev, + &Byte, + 1, + &dwBytesRead, + m_OverlappedRead)); + + if (!bReadStatus) + { + if (GetLastError() == ERROR_IO_PENDING) + { + WaitForSingleObject(m_OverlappedRead->hEvent, 2000); + } + else + { + return 0x0; + } + } + + bSuccess = dwBytesRead > 0; + return Byte; +} + +TArray USerialCom::ReadBytes(int32 Limit) +{ + TArray Data; + + if (!m_hIDComDev) return Data; + + Data.Empty(Limit); + + uint8* Buffer = new uint8[Limit]; + bool bReadStatus; + unsigned long dwBytesRead, dwErrorFlags; + COMSTAT ComStat; + + ClearCommError(m_hIDComDev, &dwErrorFlags, &ComStat); + if (!ComStat.cbInQue) return Data; + + bReadStatus = BOOL2bool(ReadFile( + m_hIDComDev, + Buffer, + Limit, + &dwBytesRead, + m_OverlappedRead)); + + if (!bReadStatus) + { + if (GetLastError() == ERROR_IO_PENDING) + { + WaitForSingleObject(m_OverlappedRead->hEvent, 2000); + } + else + { + return Data; + } + } + + Data.Append(Buffer, dwBytesRead); + return Data; +} + +bool USerialCom::Print(FString String) +{ + auto Convert = FTCHARToUTF8(*String); + TArray Data; + Data.Append((uint8*)Convert.Get(), Convert.Length()); + + return WriteBytes(Data); +} + +bool USerialCom::Println(FString String) +{ + return Print(String + LineEndToStr(WriteLineEnd)); +} + +bool USerialCom::WriteFloat(float Value) +{ + TArray Buffer; + Buffer.Append(reinterpret_cast(&Value), 4); + return WriteBytes(Buffer); +} + +bool USerialCom::WriteInt(int32 Value) +{ + TArray Buffer; + Buffer.Append(reinterpret_cast(&Value), 4); + return WriteBytes(Buffer); +} + +bool USerialCom::WriteByte(uint8 Value) +{ + TArray Buffer({ Value }); + return WriteBytes(Buffer); +} + +bool USerialCom::WriteBytes(TArray Buffer) +{ + if (!m_hIDComDev) false; + + bool bWriteStat; + unsigned long dwBytesWritten; + + bWriteStat = BOOL2bool(WriteFile(m_hIDComDev, Buffer.GetData(), Buffer.Num(), &dwBytesWritten, m_OverlappedWrite)); + if (!bWriteStat && (GetLastError() == ERROR_IO_PENDING)) + { + if (WaitForSingleObject(m_OverlappedWrite->hEvent, 1000)) + { + dwBytesWritten = 0; + return false; + } + else + { + GetOverlappedResult(m_hIDComDev, m_OverlappedWrite, &dwBytesWritten, false); + m_OverlappedWrite->Offset += dwBytesWritten; + return true; + } + } + + return true; +} + +void USerialCom::Flush() +{ + if (!m_hIDComDev) return; + + TArray Data; + + do { + Data = ReadBytes(8192); + } while (Data.Num() > 0); +} + +FString USerialCom::LineEndToStr(ELineEnd LineEnd) +{ + switch (LineEnd) + { + case ELineEnd::rn: + return TEXT("\r\n"); + case ELineEnd::n: + return TEXT("\n"); + case ELineEnd::r: + return TEXT("\r"); + case ELineEnd::nr: + return TEXT("\n\r"); + default: + return TEXT("null"); + } +} + + + + +///////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////// +/* + +FString USerialCom::LineEndToStrBD(ELineEnd LineEnd) +{ + switch (LineEnd) + { + case ELineEnd::A: + return TEXT("150"); + case ELineEnd::B: + return TEXT("200"); + case ELineEnd::C: + return TEXT("300"); + case ELineEnd::D: + return TEXT("600"); + case ELineEnd::E: + return TEXT("1200"); +/* + case ELineEnd::1800: + return TEXT("1800"); + case ELineEnd::2400: + return TEXT("2400"); + case ELineEnd::4800: + return TEXT("4800"); + case ELineEnd::9600: + return TEXT("9600"); + case ELineEnd::19200: + return TEXT("19200"); + case ELineEnd::28800: + return TEXT("28800"); + case ELineEnd::38400: + return TEXT("38400"); + case ELineEnd::57600: + return TEXT("57600"); + case ELineEnd::76800: + return TEXT("76800"); + case ELineEnd::115200: + return TEXT("115200"); + case ELineEnd::230400: + return TEXT("230400"); + case ELineEnd::460800: + return TEXT("460800"); + case ELineEnd::576000: + return TEXT("576000"); + case ELineEnd::921600: + return TEXT("921600"); +*/ + +/* + default: + return TEXT("9600"); + } +} + +*/ + +///////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////// + + + diff --git a/Unreal/Plugins/SerialCOM/SerialCOM/Source/SERIALCOM/Public/SERIALCOMModule.h b/Unreal/Plugins/SerialCOM/SerialCOM/Source/SERIALCOM/Public/SERIALCOMModule.h new file mode 100644 index 0000000..195742b --- /dev/null +++ b/Unreal/Plugins/SerialCOM/SerialCOM/Source/SERIALCOM/Public/SERIALCOMModule.h @@ -0,0 +1,16 @@ +#pragma once + +#include "Modules/ModuleManager.h" + +DECLARE_LOG_CATEGORY_CLASS(SERIALCOMLog, Log, All); + +class SERIALCOMModule : public IModuleInterface +{ +private: + +public: + SERIALCOMModule(); + + virtual void StartupModule() override; + virtual void ShutdownModule() override; +}; \ No newline at end of file diff --git a/Unreal/Plugins/SerialCOM/SerialCOM/Source/SERIALCOM/Public/SerialCom.h b/Unreal/Plugins/SerialCOM/SerialCOM/Source/SERIALCOM/Public/SerialCom.h new file mode 100644 index 0000000..c870cc2 --- /dev/null +++ b/Unreal/Plugins/SerialCOM/SerialCOM/Source/SERIALCOM/Public/SerialCom.h @@ -0,0 +1,321 @@ +#pragma once + +#define FC_DTRDSR 0x01 +#define FC_RTSCTS 0x02 +#define FC_XONXOFF 0x04 +#define ASCII_BEL 0x07 +#define ASCII_BS 0x08 +#define ASCII_LF 0x0A +#define ASCII_CR 0x0D +#define ASCII_XON 0x11 +#define ASCII_XOFF 0x13 + +#include "CoreTypes.h" +#include "SerialCom.generated.h" + + +// Forward declaration +typedef struct _OVERLAPPED OVERLAPPED; + +UENUM(BlueprintType, Category = "Communication Serial") +enum class ELineEnd : uint8 +{ + rn UMETA(DisplayName = "\r\n"), + n UMETA(DisplayName = "\n"), + r UMETA(DisplayName = "\r"), + nr UMETA(DisplayName = "\n\r") +}; + +UCLASS(BlueprintType, Category = "Communication Serial", meta = (Keywords = "com arduino serial arduino duino")) +class SERIALCOM_API USerialCom : public UObject +{ + GENERATED_BODY() + +public: + /** Determines the line ending used when writing lines to serial port with PrintLine. */ + UPROPERTY(BlueprintReadWrite, Category = "Communication Serial | String") + ELineEnd WriteLineEnd; + +public: + USerialCom(); + ~USerialCom(); + + /** + * Open a serial port and return the created Serial instance. + * Don't forget to close the port before exiting the game. + * + * @param bOpened If the serial port was successfully opened. + * @param Port The serial port to open. + * @param BaudRate BaudRate to open the serial port with. + * @param DTR Enable/Disable DTR communication protocol. + * @param RTS Enable/Disable RTS communication protocol. + * @return A Serial instance to work with the opened port. + */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Open Serial Port With Flow Control"), Category = "Communication Serial", meta = (Keywords = "communication com SERIALCOM duino arduino serial port start open serial with flow control")) + static USerialCom* OpenComPortWithFlowControl(bool &bOpened, int32 Port = 1, int32 BaudRate = 9600, bool DTR = true, bool RTS = true); + + /** + * Utility function to convert 4 bytes into an Integer. If the input array's length is not 4, returns 0. + * + * @param Bytes A byte array with 4 values representing the integer in little-endian format. + * @return The final integer value or 0 for an invalid array. + */ + + + + +////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////// + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Open Serial Port"), Category = "Communication Serial", meta = (Keywords = "communication com SERIALCOM duino arduino serial port start open serial")) + static USerialCom* OpenComPort(bool &bOpened, int32 Port = 1, int32 BaudRate = 9600); + + /** + * Utility function to convert 4 bytes into an Integer. If the input array's length is not 4, returns 0. + * + * @param Bytes A byte array with 4 values representing the integer in little-endian format. + * @return The final integer value or 0 for an invalid array. + */ + + + +////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////// + + + + + UFUNCTION(BlueprintPure, meta = (DisplayName = "Serial Bytes to Int"), Category = "Communication Serial", meta = (Keywords = "communication com SERIALCOM duino arduino serial cast concatenate group bit bitwise bytes to int")) + static int32 BytesToInt(TArray Bytes); + + /** + * Utility function to get the 4 bytes that make an integer. + * + * @param Int The integer value to be converted. + * @return A byte array containing the 4 bytes that make the integer, starting from the least significant one (little endian). + */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "Serial Int to Bytes"), Category = "Communication Serial", meta = (Keywords = "communication com SERIALCOM duino arduino serial cast separate bit bitwise int to bytes")) + static TArray IntToBytes(const int32& Int); + + /** + * Utility function to convert 4 bytes into a float. If the input array's length is not 4, returns 0.0. + * + * @param Bytes A byte array with 4 values representing the float in IEEE 754 standard format. + * @return The final float value or 0.0 for an invalid array. + */ + + + UFUNCTION(BlueprintPure, meta = (DisplayName = "Serial Bytes to Float"), Category = "Communication Serial", meta = (Keywords = "communication com SERIALCOM duino arduino serial cast concatenate group bit bitwise bytes to float")) + static float BytesToFloat(TArray Bytes); + + /** + * Utility function to get the 4 bytes that make a float. + * + * @param Float The float value to be converted. + * @return A byte array containing the 4 bytes that make the float, in IEEE 754 standard format. + */ + + + UFUNCTION(BlueprintPure, meta = (DisplayName = "Serial Float to Bytes"), Category = "Communication Serial", meta = (Keywords = "communication com SERIALCOM duino arduino serial cast separate bit bitwise flowat to bytes")) + static TArray FloatToBytes(const float& Float); + + /** + * Open a serial port. Don't forget to close the port before exiting the game. + * If this Serial instance has already an opened port, + * return false and doesn't change the opened port number. + * + * @param Port The serial port to open. + * @param BaudRate BaudRate to open the serial port with. + * @param DTR enable/disable DTR protocol + * @param RTS enable/disable RTS protocol + * @return If the serial port was successfully opened. + */ + +////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////// + + + + +////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////// + + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Open Serial Port With Target and Flow Control"), Category = "Communication Serial", meta = (Keywords = "communication com SERIALCOM duino arduino serial start init open port")) + bool OpenWFC(int32 Port = 2, int32 BaudRate = 9600, bool DTR = true, bool RTS = true); + /** + * Close and end the communication with the serial port. If not open, do nothing. + */ + +//////////////////////////////////////////////////////////////////// + + + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Close Serial Port"), Category = "Communication Serial", meta = (Keywords = "communication com SERIALCOM duino arduino serial end finish release close port")) + void Close(); + + /** + * Will read characters from Serial port until \0 (null char) is found or there are no + * characters left to read. + * + * @param bSuccess If there was anything to read. + * @return The read string + */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Serial Read String", keywords = "read string communication com SERIALCOM duino arduino serial get read receive string words text characters"), Category = "Communication Serial") + FString ReadString(bool& bSuccess); + /** + * Will read characters from Serial port until \r\n (Arduino println line end) is found. + * + * @param bSuccess If there was anything to read. + * @return The read string + */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Serial Read Line", keywords = "communication com SERIALCOM duino arduino serial read line get read receive string words text characters"), Category = "Communication Serial") + FString Readln(bool& bSuccess); + /** + * Reads the string until a specific char is met. + * The Terminator char won't be included in the result string. + */ + //UFUNCTION(BlueprintCallable, meta = (DisplayName = "Read String Until", keywords = "get read receive string words text characters"), Category = "Communication Serial") + FString ReadStringUntil(bool& bSuccess, uint8 Terminator); + /** + * Reads a float from the serial port (sent as 4 bytes). + * @param bSuccess True if there were 4 bytes to read. + * @return The read value + */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Serial Read Float", keywords = "communication com SERIALCOM duino arduino serial read a float get read receive"), Category = "Communication Serial") + float ReadFloat(bool& bSuccess); + /** + * Reads an integer from the serial port (sent as 4 bytes). + * @param bSuccess True if there were 4 bytes to read. + * @return The read value + */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Serial Read Int", keywords = "communication com SERIALCOM duino arduino serial read an int get read receive integer"), Category = "Communication Serial") + int32 ReadInt(bool& bSuccess); + /** + * Reads a byte from the serial port. + * @param bSuccess True if there were 4 bytes to read. + * @return The read value + */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Serial Read Byte", keywords = "communication com SERIALCOM duino arduino serial read a byte get read receive"), Category = "Communication Serial") + uint8 ReadByte(bool& bSuccess); + /** + * Reads up to Limit bytes from the serial port. If there are less than Limit, + * reads all of them and return True. + * @param bSuccess True if there was at least 1 byte to read. + * @return An array containing the read bytes + */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Serial Read Bytes", keywords = "communication com SERIALCOM duino arduino serial read bytes get read receive"), Category = "Communication Serial") + TArray ReadBytes(int32 Limit = 256); + + /** + * Writes a string without newline to the serial port. + * @param String The string to be sent to the serial port. + * @return True if the string was sent. + */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Serial Print", keywords = "communication com SERIALCOM duino arduino serial print send write string words text characters"), Category = "Communication Serial") + bool Print(FString String); + /** + * Writes a string with newline (\n) appended at the end to the serial port. + * @param String The string to be sent to the serial port. + * @return True if the string was sent. + */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Serial Print Line", keywords = "communication com SERIALCOM duino arduino serial print line send write string words text characters"), Category = "Communication Serial") + bool Println(FString String); + /** + * Writes a float value to the serial port as 4 bytes. + * @param Value The value to be sent to the serial port. + * @return True if the bytes were sent. + */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Serial Write Float", keywords = "communication com SERIALCOM duino arduino serial write a float send"), Category = "Communication Serial") + bool WriteFloat(float Value); + /** + * Writes an integer value to the serial port as 4 bytes. + * @param Value The value to be sent to the serial port. + * @return True if the bytes were sent. + */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Serial Write Int", keywords = "communication com SERIALCOM duino arduino serial write an int integer send"), Category = "Communication Serial") + bool WriteInt(int32 Value); + /** + * Writes a byte value to the serial port. + * @param Value The value to be sent to the serial port. + * @return True if the byte was sent. + */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Serial Write Byte", keywords = "communication com SERIALCOM duino arduino serial write a byte send"), Category = "Communication Serial") + bool WriteByte(uint8 Value); + /** + * Writes a byte array as a sequence of bytes to the serial port. + * @param Buffer The byte array to be sent to the serial port. + * @return True if the bytes were sent. + */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Serial Write Bytes", keywords = "communication com SERIALCOM duino arduino serial write bytes send"), Category = "Communication Serial") + bool WriteBytes(TArray Buffer); + + /** Clean the serial port by reading everything left to be read. */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Flush Serial Port"), Category = "Communication Serial") + void Flush(); + + /** + * Check if the serial port is open. + * @return True if the serial port is open. + */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "Is Serial Port Open?"), Category = "Communication Serial") + bool IsOpened() { return m_hIDComDev != NULL; } + + /** + * Read the number of the serial port selected for this Serial instance. + * @return The number of the serial port. + */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Serial Port Number"), Category = "Communication Serial") + int32 GetPort() { return m_Port; } + + /** + * Read the selected BaudRate for this Serial instance. + * @return The baud rate. + */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Serial Port Baud Rate"), Category = "Communication Serial") + int32 GetBaud() { return m_Baud; } + + /** + * Converts a LineEnd enum value to String. + * @param LineEnd LineEnd enum value. + * @return The LineEnd value in string format. + */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Serial Line End to String", keywords = "communication com SERIALCOM duino arduino serial cast convert line end to string"), Category = "Communication Serial") + FString LineEndToStr(ELineEnd LineEnd); + + + + + +///////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////// +/* + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "New Baudrate", keywords = "communication com arduino serial cast convert line end to string"), Category = "Communication Serial") + FString LineEndToStrBD(ELineEnd LineEnd); + +*/ + +///////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////// + + + + + + +protected: + void* m_hIDComDev; + + // These are pointers to be able to use OVERLAPPED with forward declaration + OVERLAPPED* m_OverlappedRead; + OVERLAPPED* m_OverlappedWrite; + + int32 m_Port; + int32 m_Baud; + +}; \ No newline at end of file diff --git a/Unreal/Plugins/SerialCOM/SerialCOM/Source/SERIALCOM/SERIALCOM.Build.cs b/Unreal/Plugins/SerialCOM/SerialCOM/Source/SERIALCOM/SERIALCOM.Build.cs new file mode 100644 index 0000000..116171f --- /dev/null +++ b/Unreal/Plugins/SerialCOM/SerialCOM/Source/SERIALCOM/SERIALCOM.Build.cs @@ -0,0 +1,20 @@ +using UnrealBuildTool; + +public class SERIALCOM : ModuleRules +{ + public SERIALCOM(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + + PrivateIncludePaths.AddRange(new string[] { "SERIALCOM/Private" }); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "Engine", + "Core", + "CoreUObject" + } + ); + } +} \ No newline at end of file diff --git a/Unreal/Plugins/SerialCOM/Source/SERIALCOM/Private/SERIALCOMModule.cpp b/Unreal/Plugins/SerialCOM/Source/SERIALCOM/Private/SERIALCOMModule.cpp new file mode 100644 index 0000000..42dd0e0 --- /dev/null +++ b/Unreal/Plugins/SerialCOM/Source/SERIALCOM/Private/SERIALCOMModule.cpp @@ -0,0 +1,21 @@ +#include "SERIALCOMModule.h" + +IMPLEMENT_MODULE(SERIALCOMModule, SERIALCOM); + +#define LOCTEXT_NAMESPACE "SERIALCOM" + +SERIALCOMModule::SERIALCOMModule() +{ +} + +void SERIALCOMModule::StartupModule() +{ + // Startup LOG MSG + UE_LOG(SERIALCOMLog, Warning, TEXT("SERIALCOM: Log Started")); +} + +void SERIALCOMModule::ShutdownModule() +{ + // Shutdown LOG MSG + UE_LOG(SERIALCOMLog, Warning, TEXT("SERIALCOM: Log Ended")); +} \ No newline at end of file diff --git a/Unreal/Plugins/SerialCOM/Source/SERIALCOM/Private/SerialCom.cpp b/Unreal/Plugins/SerialCOM/Source/SERIALCOM/Private/SerialCom.cpp new file mode 100644 index 0000000..a988a53 --- /dev/null +++ b/Unreal/Plugins/SerialCOM/Source/SERIALCOM/Private/SerialCom.cpp @@ -0,0 +1,539 @@ +//Based off the "Arduino and C++ (for Windows)" code found at: http://playground.arduino.cc/Interfacing/CPPWindows + +#include "SerialCom.h" + +#include "Windows/AllowWindowsPlatformTypes.h" +#include "Windows/MinWindows.h" +#include "Windows/HideWindowsPlatformTypes.h" + + +#define BOOL2bool(B) B == 0 ? false : true + +USerialCom* USerialCom::OpenComPortWithFlowControl(bool& bOpened, int32 Port, int32 BaudRate, bool DTR, bool RTS) +{ + USerialCom* Serial = NewObject(); + bOpened = Serial->OpenWFC(Port, BaudRate, DTR, RTS); + return Serial; +} + + +////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////// + + +USerialCom* USerialCom::OpenComPort(bool& bOpened, int32 Port, int32 BaudRate) +{ + USerialCom* Serial = NewObject(); + bOpened = Serial->OpenWFC(Port, BaudRate); + return Serial; +} + +////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////// + + + + +int32 USerialCom::BytesToInt(TArray Bytes) +{ + if (Bytes.Num() != 4) + { + return 0; + } + + return *reinterpret_cast(Bytes.GetData()); +} + +TArray USerialCom::IntToBytes(const int32 &Int) +{ + TArray Bytes; + Bytes.Append(reinterpret_cast(&Int), 4); + return Bytes; +} + + + +float USerialCom::BytesToFloat(TArray Bytes) +{ + if (Bytes.Num() != 4) + { + return 0; + } + + return *reinterpret_cast(Bytes.GetData()); +} + + + +TArray USerialCom::FloatToBytes(const float &Float) +{ + TArray Bytes; + Bytes.Append(reinterpret_cast(&Float), 4); + return Bytes; +} + + + + +USerialCom::USerialCom() + : WriteLineEnd(ELineEnd::n) + , m_hIDComDev(nullptr) + , m_OverlappedRead(nullptr) + , m_OverlappedWrite(nullptr) + , m_Port(-1) + , m_Baud(-1) +{ + // Allocate the OVERLAPPED structs + m_OverlappedRead = new OVERLAPPED(); + m_OverlappedWrite = new OVERLAPPED(); + + FMemory::Memset(m_OverlappedRead, 0, sizeof(OVERLAPPED)); + FMemory::Memset(m_OverlappedWrite, 0, sizeof(OVERLAPPED)); +} + +USerialCom::~USerialCom() +{ + Close(); + + // Delete allocated OVERLAPPED structs + delete m_OverlappedRead; + delete m_OverlappedWrite; +} + + +////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////// + + + + + +////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////// + + + + + +//////////////////////////////////////////////////////////////////// + +bool USerialCom::OpenWFC(int32 nPort, int32 nBaud, bool bDTR, bool bRTS) +{ + if (nPort < 0) + { + UE_LOG(LogTemp, Error, TEXT("Invalid port number: %d"), nPort); + return false; + } + if (m_hIDComDev) + { + UE_LOG(LogTemp, Warning, TEXT("Trying to use opened Serial instance to open a new one. " + "Current open instance port: %d | Port tried: %d"), m_Port, nPort); + return false; + } + + FString szPort; + if (nPort < 10) + szPort = FString::Printf(TEXT("COM%d"), nPort); + else + szPort = FString::Printf(TEXT("\\\\.\\COM%d"), nPort); + DCB dcb; + + m_hIDComDev = CreateFile(*szPort, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL); + if (m_hIDComDev == NULL) + { + unsigned long dwError = GetLastError(); + UE_LOG(LogTemp, Error, TEXT("Failed to open port COM%d (%s). Error: %08X"), nPort, *szPort, dwError); + return false; + } + + FMemory::Memset(m_OverlappedRead, 0, sizeof(OVERLAPPED)); + FMemory::Memset(m_OverlappedWrite, 0, sizeof(OVERLAPPED)); + + COMMTIMEOUTS CommTimeOuts; + //CommTimeOuts.ReadIntervalTimeout = 10; + CommTimeOuts.ReadIntervalTimeout = 0xFFFFFFFF; + CommTimeOuts.ReadTotalTimeoutMultiplier = 0; + CommTimeOuts.ReadTotalTimeoutConstant = 0; + CommTimeOuts.WriteTotalTimeoutMultiplier = 0; + CommTimeOuts.WriteTotalTimeoutConstant = 10; + SetCommTimeouts(m_hIDComDev, &CommTimeOuts); + + m_OverlappedRead->hEvent = CreateEvent(NULL, true, false, NULL); + m_OverlappedWrite->hEvent = CreateEvent(NULL, true, false, NULL); + + dcb.DCBlength = sizeof(DCB); + GetCommState(m_hIDComDev, &dcb); + dcb.BaudRate = nBaud; + dcb.ByteSize = 8; + if (bDTR == true) + { + dcb.fDtrControl = DTR_CONTROL_ENABLE; + } + else + { + dcb.fDtrControl = DTR_CONTROL_DISABLE; + } + + if (bRTS == true) + { + dcb.fRtsControl = RTS_CONTROL_ENABLE; + } + else + { + dcb.fRtsControl = RTS_CONTROL_DISABLE; + } + + if (!SetCommState(m_hIDComDev, &dcb) || + !SetupComm(m_hIDComDev, 10000, 10000) || + m_OverlappedRead->hEvent == NULL || + m_OverlappedWrite->hEvent == NULL) + { + unsigned long dwError = GetLastError(); + if (m_OverlappedRead->hEvent != NULL) CloseHandle(m_OverlappedRead->hEvent); + if (m_OverlappedWrite->hEvent != NULL) CloseHandle(m_OverlappedWrite->hEvent); + CloseHandle(m_hIDComDev); + m_hIDComDev = NULL; + UE_LOG(LogTemp, Error, TEXT("Failed to setup port COM%d. Error: %08X"), nPort, dwError); + return false; + } + + //FPlatformProcess::Sleep(0.05f); + AddToRoot(); + m_Port = nPort; + m_Baud = nBaud; + return true; +} + +//////////////////////////////////////////////////////////////////// + + +void USerialCom::Close() +{ + if (!m_hIDComDev) return; + + if (m_OverlappedRead->hEvent != NULL) CloseHandle(m_OverlappedRead->hEvent); + if (m_OverlappedWrite->hEvent != NULL) CloseHandle(m_OverlappedWrite->hEvent); + CloseHandle(m_hIDComDev); + m_hIDComDev = NULL; + + RemoveFromRoot(); +} + +FString USerialCom::ReadString(bool &bSuccess) +{ + return ReadStringUntil(bSuccess, '\0'); +} + +FString USerialCom::Readln(bool &bSuccess) +{ + return ReadStringUntil(bSuccess, '\n'); +} + +FString USerialCom::ReadStringUntil(bool &bSuccess, uint8 Terminator) +{ + bSuccess = false; + if (!m_hIDComDev) return TEXT(""); + + TArray Chars; + uint8 Byte = 0x0; + bool bReadStatus; + unsigned long dwBytesRead, dwErrorFlags; + COMSTAT ComStat; + + ClearCommError(m_hIDComDev, &dwErrorFlags, &ComStat); + if (!ComStat.cbInQue) return TEXT(""); + + do { + bReadStatus = BOOL2bool(ReadFile( + m_hIDComDev, + &Byte, + 1, + &dwBytesRead, + m_OverlappedRead)); + + if (!bReadStatus) + { + if (GetLastError() == ERROR_IO_PENDING) + { + WaitForSingleObject(m_OverlappedRead->hEvent, 2000); + } + else + { + Chars.Add(0x0); + break; + } + } + + if (Byte == Terminator || dwBytesRead == 0) + { + // when Terminator is \n, we know we're expecting lines from Arduino. But those + // are ended in \r\n. That means that if we found the line Terminator (\n), our previous + // character could be \r. If it is, we remove that from the array. + if (Chars.Num() > 0 && Terminator == '\n' && Chars.Top() == '\r') Chars.Pop(false); + + Chars.Add(0x0); + break; + } + else Chars.Add(Byte); + + } while (Byte != 0x0 && Byte != Terminator); + + bSuccess = true; + auto Convert = FUTF8ToTCHAR((ANSICHAR*)Chars.GetData()); + return FString(Convert.Get()); +} + +float USerialCom::ReadFloat(bool &bSuccess) +{ + bSuccess = false; + + TArray Bytes = ReadBytes(4); + if (Bytes.Num() == 0) return 0; + + bSuccess = true; + return *(reinterpret_cast(Bytes.GetData())); +} + +int32 USerialCom::ReadInt(bool &bSuccess) +{ + bSuccess = false; + + TArray Bytes = ReadBytes(4); + if (Bytes.Num() == 0) return 0; + + bSuccess = true; + return *(reinterpret_cast(Bytes.GetData())); +} + +uint8 USerialCom::ReadByte(bool &bSuccess) +{ + bSuccess = false; + if (!m_hIDComDev) return 0x0; + + uint8 Byte = 0x0; + bool bReadStatus; + unsigned long dwBytesRead, dwErrorFlags; + COMSTAT ComStat; + + ClearCommError(m_hIDComDev, &dwErrorFlags, &ComStat); + if (!ComStat.cbInQue) return 0x0; + + bReadStatus = BOOL2bool(ReadFile( + m_hIDComDev, + &Byte, + 1, + &dwBytesRead, + m_OverlappedRead)); + + if (!bReadStatus) + { + if (GetLastError() == ERROR_IO_PENDING) + { + WaitForSingleObject(m_OverlappedRead->hEvent, 2000); + } + else + { + return 0x0; + } + } + + bSuccess = dwBytesRead > 0; + return Byte; +} + +TArray USerialCom::ReadBytes(int32 Limit) +{ + TArray Data; + + if (!m_hIDComDev) return Data; + + Data.Empty(Limit); + + uint8* Buffer = new uint8[Limit]; + bool bReadStatus; + unsigned long dwBytesRead, dwErrorFlags; + COMSTAT ComStat; + + ClearCommError(m_hIDComDev, &dwErrorFlags, &ComStat); + if (!ComStat.cbInQue) return Data; + + bReadStatus = BOOL2bool(ReadFile( + m_hIDComDev, + Buffer, + Limit, + &dwBytesRead, + m_OverlappedRead)); + + if (!bReadStatus) + { + if (GetLastError() == ERROR_IO_PENDING) + { + WaitForSingleObject(m_OverlappedRead->hEvent, 2000); + } + else + { + return Data; + } + } + + Data.Append(Buffer, dwBytesRead); + return Data; +} + +bool USerialCom::Print(FString String) +{ + auto Convert = FTCHARToUTF8(*String); + TArray Data; + Data.Append((uint8*)Convert.Get(), Convert.Length()); + + return WriteBytes(Data); +} + +bool USerialCom::Println(FString String) +{ + return Print(String + LineEndToStr(WriteLineEnd)); +} + +bool USerialCom::WriteFloat(float Value) +{ + TArray Buffer; + Buffer.Append(reinterpret_cast(&Value), 4); + return WriteBytes(Buffer); +} + +bool USerialCom::WriteInt(int32 Value) +{ + TArray Buffer; + Buffer.Append(reinterpret_cast(&Value), 4); + return WriteBytes(Buffer); +} + +bool USerialCom::WriteByte(uint8 Value) +{ + TArray Buffer({ Value }); + return WriteBytes(Buffer); +} + +bool USerialCom::WriteBytes(TArray Buffer) +{ + if (!m_hIDComDev) false; + + bool bWriteStat; + unsigned long dwBytesWritten; + + bWriteStat = BOOL2bool(WriteFile(m_hIDComDev, Buffer.GetData(), Buffer.Num(), &dwBytesWritten, m_OverlappedWrite)); + if (!bWriteStat && (GetLastError() == ERROR_IO_PENDING)) + { + if (WaitForSingleObject(m_OverlappedWrite->hEvent, 1000)) + { + dwBytesWritten = 0; + return false; + } + else + { + GetOverlappedResult(m_hIDComDev, m_OverlappedWrite, &dwBytesWritten, false); + m_OverlappedWrite->Offset += dwBytesWritten; + return true; + } + } + + return true; +} + +void USerialCom::Flush() +{ + if (!m_hIDComDev) return; + + TArray Data; + + do { + Data = ReadBytes(8192); + } while (Data.Num() > 0); +} + +FString USerialCom::LineEndToStr(ELineEnd LineEnd) +{ + switch (LineEnd) + { + case ELineEnd::rn: + return TEXT("\r\n"); + case ELineEnd::n: + return TEXT("\n"); + case ELineEnd::r: + return TEXT("\r"); + case ELineEnd::nr: + return TEXT("\n\r"); + default: + return TEXT("null"); + } +} + + + + +///////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////// +/* + +FString USerialCom::LineEndToStrBD(ELineEnd LineEnd) +{ + switch (LineEnd) + { + case ELineEnd::A: + return TEXT("150"); + case ELineEnd::B: + return TEXT("200"); + case ELineEnd::C: + return TEXT("300"); + case ELineEnd::D: + return TEXT("600"); + case ELineEnd::E: + return TEXT("1200"); +/* + case ELineEnd::1800: + return TEXT("1800"); + case ELineEnd::2400: + return TEXT("2400"); + case ELineEnd::4800: + return TEXT("4800"); + case ELineEnd::9600: + return TEXT("9600"); + case ELineEnd::19200: + return TEXT("19200"); + case ELineEnd::28800: + return TEXT("28800"); + case ELineEnd::38400: + return TEXT("38400"); + case ELineEnd::57600: + return TEXT("57600"); + case ELineEnd::76800: + return TEXT("76800"); + case ELineEnd::115200: + return TEXT("115200"); + case ELineEnd::230400: + return TEXT("230400"); + case ELineEnd::460800: + return TEXT("460800"); + case ELineEnd::576000: + return TEXT("576000"); + case ELineEnd::921600: + return TEXT("921600"); +*/ + +/* + default: + return TEXT("9600"); + } +} + +*/ + +///////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////// + + + diff --git a/Unreal/Plugins/SerialCOM/Source/SERIALCOM/Public/SERIALCOMModule.h b/Unreal/Plugins/SerialCOM/Source/SERIALCOM/Public/SERIALCOMModule.h new file mode 100644 index 0000000..195742b --- /dev/null +++ b/Unreal/Plugins/SerialCOM/Source/SERIALCOM/Public/SERIALCOMModule.h @@ -0,0 +1,16 @@ +#pragma once + +#include "Modules/ModuleManager.h" + +DECLARE_LOG_CATEGORY_CLASS(SERIALCOMLog, Log, All); + +class SERIALCOMModule : public IModuleInterface +{ +private: + +public: + SERIALCOMModule(); + + virtual void StartupModule() override; + virtual void ShutdownModule() override; +}; \ No newline at end of file diff --git a/Unreal/Plugins/SerialCOM/Source/SERIALCOM/Public/SerialCom.h b/Unreal/Plugins/SerialCOM/Source/SERIALCOM/Public/SerialCom.h new file mode 100644 index 0000000..c870cc2 --- /dev/null +++ b/Unreal/Plugins/SerialCOM/Source/SERIALCOM/Public/SerialCom.h @@ -0,0 +1,321 @@ +#pragma once + +#define FC_DTRDSR 0x01 +#define FC_RTSCTS 0x02 +#define FC_XONXOFF 0x04 +#define ASCII_BEL 0x07 +#define ASCII_BS 0x08 +#define ASCII_LF 0x0A +#define ASCII_CR 0x0D +#define ASCII_XON 0x11 +#define ASCII_XOFF 0x13 + +#include "CoreTypes.h" +#include "SerialCom.generated.h" + + +// Forward declaration +typedef struct _OVERLAPPED OVERLAPPED; + +UENUM(BlueprintType, Category = "Communication Serial") +enum class ELineEnd : uint8 +{ + rn UMETA(DisplayName = "\r\n"), + n UMETA(DisplayName = "\n"), + r UMETA(DisplayName = "\r"), + nr UMETA(DisplayName = "\n\r") +}; + +UCLASS(BlueprintType, Category = "Communication Serial", meta = (Keywords = "com arduino serial arduino duino")) +class SERIALCOM_API USerialCom : public UObject +{ + GENERATED_BODY() + +public: + /** Determines the line ending used when writing lines to serial port with PrintLine. */ + UPROPERTY(BlueprintReadWrite, Category = "Communication Serial | String") + ELineEnd WriteLineEnd; + +public: + USerialCom(); + ~USerialCom(); + + /** + * Open a serial port and return the created Serial instance. + * Don't forget to close the port before exiting the game. + * + * @param bOpened If the serial port was successfully opened. + * @param Port The serial port to open. + * @param BaudRate BaudRate to open the serial port with. + * @param DTR Enable/Disable DTR communication protocol. + * @param RTS Enable/Disable RTS communication protocol. + * @return A Serial instance to work with the opened port. + */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Open Serial Port With Flow Control"), Category = "Communication Serial", meta = (Keywords = "communication com SERIALCOM duino arduino serial port start open serial with flow control")) + static USerialCom* OpenComPortWithFlowControl(bool &bOpened, int32 Port = 1, int32 BaudRate = 9600, bool DTR = true, bool RTS = true); + + /** + * Utility function to convert 4 bytes into an Integer. If the input array's length is not 4, returns 0. + * + * @param Bytes A byte array with 4 values representing the integer in little-endian format. + * @return The final integer value or 0 for an invalid array. + */ + + + + +////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////// + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Open Serial Port"), Category = "Communication Serial", meta = (Keywords = "communication com SERIALCOM duino arduino serial port start open serial")) + static USerialCom* OpenComPort(bool &bOpened, int32 Port = 1, int32 BaudRate = 9600); + + /** + * Utility function to convert 4 bytes into an Integer. If the input array's length is not 4, returns 0. + * + * @param Bytes A byte array with 4 values representing the integer in little-endian format. + * @return The final integer value or 0 for an invalid array. + */ + + + +////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////// + + + + + UFUNCTION(BlueprintPure, meta = (DisplayName = "Serial Bytes to Int"), Category = "Communication Serial", meta = (Keywords = "communication com SERIALCOM duino arduino serial cast concatenate group bit bitwise bytes to int")) + static int32 BytesToInt(TArray Bytes); + + /** + * Utility function to get the 4 bytes that make an integer. + * + * @param Int The integer value to be converted. + * @return A byte array containing the 4 bytes that make the integer, starting from the least significant one (little endian). + */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "Serial Int to Bytes"), Category = "Communication Serial", meta = (Keywords = "communication com SERIALCOM duino arduino serial cast separate bit bitwise int to bytes")) + static TArray IntToBytes(const int32& Int); + + /** + * Utility function to convert 4 bytes into a float. If the input array's length is not 4, returns 0.0. + * + * @param Bytes A byte array with 4 values representing the float in IEEE 754 standard format. + * @return The final float value or 0.0 for an invalid array. + */ + + + UFUNCTION(BlueprintPure, meta = (DisplayName = "Serial Bytes to Float"), Category = "Communication Serial", meta = (Keywords = "communication com SERIALCOM duino arduino serial cast concatenate group bit bitwise bytes to float")) + static float BytesToFloat(TArray Bytes); + + /** + * Utility function to get the 4 bytes that make a float. + * + * @param Float The float value to be converted. + * @return A byte array containing the 4 bytes that make the float, in IEEE 754 standard format. + */ + + + UFUNCTION(BlueprintPure, meta = (DisplayName = "Serial Float to Bytes"), Category = "Communication Serial", meta = (Keywords = "communication com SERIALCOM duino arduino serial cast separate bit bitwise flowat to bytes")) + static TArray FloatToBytes(const float& Float); + + /** + * Open a serial port. Don't forget to close the port before exiting the game. + * If this Serial instance has already an opened port, + * return false and doesn't change the opened port number. + * + * @param Port The serial port to open. + * @param BaudRate BaudRate to open the serial port with. + * @param DTR enable/disable DTR protocol + * @param RTS enable/disable RTS protocol + * @return If the serial port was successfully opened. + */ + +////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////// + + + + +////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////// + + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Open Serial Port With Target and Flow Control"), Category = "Communication Serial", meta = (Keywords = "communication com SERIALCOM duino arduino serial start init open port")) + bool OpenWFC(int32 Port = 2, int32 BaudRate = 9600, bool DTR = true, bool RTS = true); + /** + * Close and end the communication with the serial port. If not open, do nothing. + */ + +//////////////////////////////////////////////////////////////////// + + + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Close Serial Port"), Category = "Communication Serial", meta = (Keywords = "communication com SERIALCOM duino arduino serial end finish release close port")) + void Close(); + + /** + * Will read characters from Serial port until \0 (null char) is found or there are no + * characters left to read. + * + * @param bSuccess If there was anything to read. + * @return The read string + */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Serial Read String", keywords = "read string communication com SERIALCOM duino arduino serial get read receive string words text characters"), Category = "Communication Serial") + FString ReadString(bool& bSuccess); + /** + * Will read characters from Serial port until \r\n (Arduino println line end) is found. + * + * @param bSuccess If there was anything to read. + * @return The read string + */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Serial Read Line", keywords = "communication com SERIALCOM duino arduino serial read line get read receive string words text characters"), Category = "Communication Serial") + FString Readln(bool& bSuccess); + /** + * Reads the string until a specific char is met. + * The Terminator char won't be included in the result string. + */ + //UFUNCTION(BlueprintCallable, meta = (DisplayName = "Read String Until", keywords = "get read receive string words text characters"), Category = "Communication Serial") + FString ReadStringUntil(bool& bSuccess, uint8 Terminator); + /** + * Reads a float from the serial port (sent as 4 bytes). + * @param bSuccess True if there were 4 bytes to read. + * @return The read value + */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Serial Read Float", keywords = "communication com SERIALCOM duino arduino serial read a float get read receive"), Category = "Communication Serial") + float ReadFloat(bool& bSuccess); + /** + * Reads an integer from the serial port (sent as 4 bytes). + * @param bSuccess True if there were 4 bytes to read. + * @return The read value + */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Serial Read Int", keywords = "communication com SERIALCOM duino arduino serial read an int get read receive integer"), Category = "Communication Serial") + int32 ReadInt(bool& bSuccess); + /** + * Reads a byte from the serial port. + * @param bSuccess True if there were 4 bytes to read. + * @return The read value + */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Serial Read Byte", keywords = "communication com SERIALCOM duino arduino serial read a byte get read receive"), Category = "Communication Serial") + uint8 ReadByte(bool& bSuccess); + /** + * Reads up to Limit bytes from the serial port. If there are less than Limit, + * reads all of them and return True. + * @param bSuccess True if there was at least 1 byte to read. + * @return An array containing the read bytes + */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Serial Read Bytes", keywords = "communication com SERIALCOM duino arduino serial read bytes get read receive"), Category = "Communication Serial") + TArray ReadBytes(int32 Limit = 256); + + /** + * Writes a string without newline to the serial port. + * @param String The string to be sent to the serial port. + * @return True if the string was sent. + */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Serial Print", keywords = "communication com SERIALCOM duino arduino serial print send write string words text characters"), Category = "Communication Serial") + bool Print(FString String); + /** + * Writes a string with newline (\n) appended at the end to the serial port. + * @param String The string to be sent to the serial port. + * @return True if the string was sent. + */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Serial Print Line", keywords = "communication com SERIALCOM duino arduino serial print line send write string words text characters"), Category = "Communication Serial") + bool Println(FString String); + /** + * Writes a float value to the serial port as 4 bytes. + * @param Value The value to be sent to the serial port. + * @return True if the bytes were sent. + */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Serial Write Float", keywords = "communication com SERIALCOM duino arduino serial write a float send"), Category = "Communication Serial") + bool WriteFloat(float Value); + /** + * Writes an integer value to the serial port as 4 bytes. + * @param Value The value to be sent to the serial port. + * @return True if the bytes were sent. + */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Serial Write Int", keywords = "communication com SERIALCOM duino arduino serial write an int integer send"), Category = "Communication Serial") + bool WriteInt(int32 Value); + /** + * Writes a byte value to the serial port. + * @param Value The value to be sent to the serial port. + * @return True if the byte was sent. + */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Serial Write Byte", keywords = "communication com SERIALCOM duino arduino serial write a byte send"), Category = "Communication Serial") + bool WriteByte(uint8 Value); + /** + * Writes a byte array as a sequence of bytes to the serial port. + * @param Buffer The byte array to be sent to the serial port. + * @return True if the bytes were sent. + */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Serial Write Bytes", keywords = "communication com SERIALCOM duino arduino serial write bytes send"), Category = "Communication Serial") + bool WriteBytes(TArray Buffer); + + /** Clean the serial port by reading everything left to be read. */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Flush Serial Port"), Category = "Communication Serial") + void Flush(); + + /** + * Check if the serial port is open. + * @return True if the serial port is open. + */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "Is Serial Port Open?"), Category = "Communication Serial") + bool IsOpened() { return m_hIDComDev != NULL; } + + /** + * Read the number of the serial port selected for this Serial instance. + * @return The number of the serial port. + */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Serial Port Number"), Category = "Communication Serial") + int32 GetPort() { return m_Port; } + + /** + * Read the selected BaudRate for this Serial instance. + * @return The baud rate. + */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Serial Port Baud Rate"), Category = "Communication Serial") + int32 GetBaud() { return m_Baud; } + + /** + * Converts a LineEnd enum value to String. + * @param LineEnd LineEnd enum value. + * @return The LineEnd value in string format. + */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Serial Line End to String", keywords = "communication com SERIALCOM duino arduino serial cast convert line end to string"), Category = "Communication Serial") + FString LineEndToStr(ELineEnd LineEnd); + + + + + +///////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////// +/* + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "New Baudrate", keywords = "communication com arduino serial cast convert line end to string"), Category = "Communication Serial") + FString LineEndToStrBD(ELineEnd LineEnd); + +*/ + +///////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////// + + + + + + +protected: + void* m_hIDComDev; + + // These are pointers to be able to use OVERLAPPED with forward declaration + OVERLAPPED* m_OverlappedRead; + OVERLAPPED* m_OverlappedWrite; + + int32 m_Port; + int32 m_Baud; + +}; \ No newline at end of file diff --git a/Unreal/Plugins/SerialCOM/Source/SERIALCOM/SERIALCOM.Build.cs b/Unreal/Plugins/SerialCOM/Source/SERIALCOM/SERIALCOM.Build.cs new file mode 100644 index 0000000..116171f --- /dev/null +++ b/Unreal/Plugins/SerialCOM/Source/SERIALCOM/SERIALCOM.Build.cs @@ -0,0 +1,20 @@ +using UnrealBuildTool; + +public class SERIALCOM : ModuleRules +{ + public SERIALCOM(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + + PrivateIncludePaths.AddRange(new string[] { "SERIALCOM/Private" }); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "Engine", + "Core", + "CoreUObject" + } + ); + } +} \ No newline at end of file diff --git a/Unreal/Plugins/SerialCOM/arduino_ptt_sketch/spie_arduino_ptt_sketch.ino b/Unreal/Plugins/SerialCOM/arduino_ptt_sketch/spie_arduino_ptt_sketch.ino new file mode 100644 index 0000000..8d4a92e --- /dev/null +++ b/Unreal/Plugins/SerialCOM/arduino_ptt_sketch/spie_arduino_ptt_sketch.ino @@ -0,0 +1,146 @@ +/* + * LED + Button Controller with Serial Commands and Smooth Pulsing + * ================================================================ + * + * HARDWARE WIRING NOTES: + * ---------------------- + * The switch (TRU Components LAS1-AGQ-11E/R/G) has an integrated LED rated + * for +12V with a built-in resistor. Since the Arduino cannot: + * a) Supply 12V (it outputs 5V max) + * b) Drive enough current for the LED directly + * c) Do PWM dimming at 12V + * + * ...you MUST use an NPN Darlington transistor (TIP122) as a low-side switch. + * + * WIRING: + * +12V PSU (+) --> LED+ on switch + * LED- on switch --> COLLECTOR (TIP122 middle pin) + * Arduino Pin 10 --> 1k Ohm resistor --> BASE (TIP122 left pin) + * EMITTER (TIP122 right pin) --> GND + * Arduino GND + 12V PSU GND --> tied together (common ground) + * + * SWITCH (button) WIRING: + * COM --> Arduino Pin 9 + * NO --> GND + * NC --> leave unconnected + * + * Pin 10 must be PWM-capable (Uno/Nano/Mega all support this). + * + * SERIAL COMMANDS: + * LED_ON -> Turn LED fully on (100%, stops pulsing) + * LED_OFF -> Turn LED fully off (stops pulsing) + * LED_DIM:xx -> Turn LED on at xx percent (0-100), e.g. LED_DIM:50 + * LED_PULSE_ON -> Start smooth breathing/pulse animation + * LED_PULSE_OFF -> Stop pulsing (LED goes off) + * CLEAR -> Turn LED off (legacy command) + * + * BUTTON OUTPUT: + * "B/true" when pressed (LOW with pull-up) + * "B/false" when released (HIGH) + */ + +// Constants for pin assignments +const int ledPin = 10; // Must be PWM-capable! +const int buttonPin = 9; + +// Button state variables +int buttonState = 1; +int lastButtonState = 1; + +// Debounce timing +unsigned long lastDebounceTime = 0; +unsigned long debounceDelay = 50; + +// LED state machine +enum LedMode { + LED_MODE_OFF, + LED_MODE_ON, + LED_MODE_DIM, + LED_MODE_PULSE +}; + +LedMode ledMode = LED_MODE_OFF; + +// Pulse animation variables +unsigned long pulseStartTime = 0; +const unsigned long pulseCycleMs = 3000; // Full breath cycle duration in ms + +void setup() { + buttonState = 1; + pinMode(ledPin, OUTPUT); + pinMode(buttonPin, INPUT_PULLUP); + analogWrite(ledPin, 0); + + Serial.begin(9600); +} + +void loop() { + // --- Button debouncing --- + int reading = digitalRead(buttonPin); + + if (reading != lastButtonState) { + lastDebounceTime = millis(); + } + + if ((millis() - lastDebounceTime) > debounceDelay) { + if (reading != buttonState) { + buttonState = reading; + if (buttonState == 0) { + Serial.println("B/true"); + } else { + Serial.println("B/false"); + } + } + } + + lastButtonState = reading; + + // --- Serial command handling --- + if (Serial.available() > 0) { + String command = Serial.readStringUntil('\n'); + command.trim(); + + if (command == "LED_ON") { + ledMode = LED_MODE_ON; + analogWrite(ledPin, 255); + } + else if (command == "LED_OFF") { + ledMode = LED_MODE_OFF; + analogWrite(ledPin, 0); + } + else if (command.startsWith("LED_DIM:")) { + int percent = command.substring(8).toInt(); + percent = constrain(percent, 0, 100); + int pwmValue = map(percent, 0, 100, 0, 255); + ledMode = LED_MODE_DIM; + analogWrite(ledPin, pwmValue); + } + else if (command == "LED_PULSE_ON") { + ledMode = LED_MODE_PULSE; + pulseStartTime = millis(); + } + else if (command == "LED_PULSE_OFF") { + ledMode = LED_MODE_OFF; + analogWrite(ledPin, 0); + } + else if (command.startsWith("CLEAR")) { + ledMode = LED_MODE_OFF; + analogWrite(ledPin, 0); + } + } + + // --- Pulse animation (non-blocking) --- + if (ledMode == LED_MODE_PULSE) { + unsigned long elapsed = millis() - pulseStartTime; + unsigned long pos = elapsed % pulseCycleMs; + + // Sine wave: 0 -> 1 -> 0 over one full "breath" cycle + float phase = (float)pos / (float)pulseCycleMs; + float brightness = sin(phase * PI); + brightness = brightness * brightness; // Squared for organic ease-in-out + + // Min ~8 so LED never fully goes dark (subtle "alive" glow at trough) + int pwmValue = 2 + (int)(brightness * 247.0); + analogWrite(ledPin, pwmValue); + } +} \ No newline at end of file diff --git a/Unreal/SPIE_Avatar.uproject b/Unreal/SPIE_Avatar.uproject index 2564dde..556821e 100644 --- a/Unreal/SPIE_Avatar.uproject +++ b/Unreal/SPIE_Avatar.uproject @@ -41,6 +41,30 @@ { "Name": "HDRIBackdrop", "Enabled": true + }, + { + "Name": "MetaHuman", + "Enabled": true, + "SupportedTargetPlatforms": [ + "Win64", + "Linux" + ] + }, + { + "Name": "MetaHumanCalibrationProcessing", + "Enabled": true, + "SupportedTargetPlatforms": [ + "Win64", + "Linux" + ] + }, + { + "Name": "MetaHumanLiveLink", + "Enabled": true + }, + { + "Name": "RigLogic", + "Enabled": true } ] } \ No newline at end of file