diff --git a/Source/DisplayClusterExtensions/Private/Clickable.cpp b/Source/DisplayClusterExtensions/Private/Clickable.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..38d5fd80840f3358866775e20e1e98f97d64c871
--- /dev/null
+++ b/Source/DisplayClusterExtensions/Private/Clickable.cpp
@@ -0,0 +1,8 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+
+#include "Clickable.h"
+
+UClickable::UClickable(const FObjectInitializer& ObjectInitializer)
+ :Super(ObjectInitializer)
+{}
diff --git a/Source/DisplayClusterExtensions/Private/Grabable.cpp b/Source/DisplayClusterExtensions/Private/Grabable.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..99330b38597c05fb5e3ea8d476edd77c3e16b797
--- /dev/null
+++ b/Source/DisplayClusterExtensions/Private/Grabable.cpp
@@ -0,0 +1,10 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+
+#include "Grabable.h"
+
+// to avoid some bugs
+UGrabable::UGrabable(const FObjectInitializer& ObjectInitializer)
+ :Super(ObjectInitializer)
+{}
+
diff --git a/Source/DisplayClusterExtensions/Private/GrabbingBehaviorComponent.cpp b/Source/DisplayClusterExtensions/Private/GrabbingBehaviorComponent.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5942605fe35abe48776669c183c4d0e17264e6c4
--- /dev/null
+++ b/Source/DisplayClusterExtensions/Private/GrabbingBehaviorComponent.cpp
@@ -0,0 +1,34 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+
+#include "GrabbingBehaviorComponent.h"
+
+// Sets default values for this component's properties
+UGrabbingBehaviorComponent::UGrabbingBehaviorComponent()
+{
+ // Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features
+ // off to improve performance if you don't need them.
+ PrimaryComponentTick.bCanEverTick = true;
+
+ // ...
+}
+
+
+// Called when the game starts
+void UGrabbingBehaviorComponent::BeginPlay()
+{
+ Super::BeginPlay();
+
+ // ...
+
+}
+
+
+// Called every frame
+void UGrabbingBehaviorComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
+{
+ Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
+
+ // ...
+}
+
diff --git a/Source/DisplayClusterExtensions/Private/GrabbingBehaviorOnLineComponent.cpp b/Source/DisplayClusterExtensions/Private/GrabbingBehaviorOnLineComponent.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c09f1be1bfa1b2fb92750ebe6d84868e986b6abf
--- /dev/null
+++ b/Source/DisplayClusterExtensions/Private/GrabbingBehaviorOnLineComponent.cpp
@@ -0,0 +1,77 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+
+#include "GrabbingBehaviorOnLineComponent.h"
+
+// Sets default values for this component's properties
+UGrabbingBehaviorOnLineComponent::UGrabbingBehaviorOnLineComponent()
+{
+ // Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features
+ // off to improve performance if you don't need them.
+ PrimaryComponentTick.bCanEverTick = true;
+
+ bAbsoluteLocation = true;
+ bAbsoluteRotation = true;
+ bAbsoluteScale = true;
+
+ this->Distance = 0;
+}
+
+
+void UGrabbingBehaviorOnLineComponent::SetDistance(float Dist)
+{
+
+ check(Dist > 0 && "max distance has to be greater than 0");
+ this->Distance = Dist;
+}
+
+float UGrabbingBehaviorOnLineComponent::GetDistance() const
+{
+ return this->Distance;
+}
+
+void UGrabbingBehaviorOnLineComponent::HandleNewPositionAndDirection(FVector Position, FQuat Orientation)
+{
+ FVector AttachmentPoint = this->RelativeLocation;
+ FVector ConstraintAxis = this->GetComponentQuat().GetUpVector();
+ FVector Direction = Orientation.GetForwardVector();
+ FVector FromHandToMe = -Position + AttachmentPoint;
+
+ // Vector perpendicular to both points
+ FVector Temp = FVector::CrossProduct(FromHandToMe, ConstraintAxis);
+ Temp.Normalize();
+
+ FVector PlaneNormal = FVector::CrossProduct(ConstraintAxis,Temp);
+
+ // get intersection point defined by plane
+ FVector Intersection = FMath::LinePlaneIntersection(Position, Position + Direction, AttachmentPoint, PlaneNormal);
+ FVector FromOriginToIntersection = Intersection - AttachmentPoint;
+
+ // point along the constraint axis with length of the projection from intersection point onto the axis
+ FVector NewPosition = FVector::DotProduct(FromOriginToIntersection, ConstraintAxis)* ConstraintAxis;
+
+ NewPosition = NewPosition.GetClampedToMaxSize(Distance);
+ NewPosition += AttachmentPoint;
+
+ // transform the targeted actor which is owner of this component with calculated quaternion and posiition
+ // here rotation is not changed
+ GetOwner()->SetActorLocation(NewPosition);
+}
+
+// Called when the game starts
+void UGrabbingBehaviorOnLineComponent::BeginPlay()
+{
+ Super::BeginPlay();
+
+ // ...
+
+}
+
+
+// Called every frame
+void UGrabbingBehaviorOnLineComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
+{
+ Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
+
+}
+
diff --git a/Source/DisplayClusterExtensions/Private/GrabbingBehaviorOnPlaneComponent.cpp b/Source/DisplayClusterExtensions/Private/GrabbingBehaviorOnPlaneComponent.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5d5e5e23e648e14a8d6e32a9b5c85c34c37facc8
--- /dev/null
+++ b/Source/DisplayClusterExtensions/Private/GrabbingBehaviorOnPlaneComponent.cpp
@@ -0,0 +1,73 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+//TODO rename distance to maxDistance
+
+#include "GrabbingBehaviorOnPlaneComponent.h"
+
+// Sets default values for this component's properties
+UGrabbingBehaviorOnPlaneComponent::UGrabbingBehaviorOnPlaneComponent()
+{
+ // Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features
+ // off to improve performance if you don't need them.
+ PrimaryComponentTick.bCanEverTick = true;
+
+ bAbsoluteLocation = true;
+ bAbsoluteRotation = true;
+ bAbsoluteScale = true;
+ // ...
+}
+
+
+void UGrabbingBehaviorOnPlaneComponent::SetDistance(float Dist)
+{
+ check(Dist > 0 && "max distance has to be greater than 0");
+ this->Distance = Dist;
+}
+
+float UGrabbingBehaviorOnPlaneComponent::GetDistance() const
+{
+ return this->Distance;
+}
+
+
+void UGrabbingBehaviorOnPlaneComponent::HandleNewPositionAndDirection(FVector Position, FQuat Orientation)
+{
+ FVector AttachmentPoint = this->RelativeLocation;
+ FVector PlaneNormal = this->GetComponentQuat().GetUpVector();
+ FVector Direction = Orientation.GetForwardVector();
+
+ // calculate point on plane which is pointed to by hand ray
+ FVector Intersection = FMath::LinePlaneIntersection(Position, Position + Direction, AttachmentPoint, PlaneNormal);
+ FVector NewPosition = -AttachmentPoint + Intersection;
+
+ // clamp size by maxDistance
+ NewPosition = NewPosition.GetClampedToMaxSize(Distance);
+
+ // after this NewPoint is in world position
+ NewPosition += AttachmentPoint;
+
+
+ // set new position and orientation using calculated quaternion and position
+ // here rotation is not changed
+ GetOwner()->SetActorLocation(NewPosition);
+}
+
+
+// Called when the game starts
+void UGrabbingBehaviorOnPlaneComponent::BeginPlay()
+{
+ Super::BeginPlay();
+
+ // ...
+
+}
+
+
+// Called every frame
+void UGrabbingBehaviorOnPlaneComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
+{
+ Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
+
+ // ...
+
+}
+
diff --git a/Source/DisplayClusterExtensions/Private/VirtualRealityPawn.cpp b/Source/DisplayClusterExtensions/Private/VirtualRealityPawn.cpp
index c9c719b38331a72d7f8f1bcadd57b4f2dc95c924..de17d478c5ac6bc9be9124d845fdc3f56b7aa960 100644
--- a/Source/DisplayClusterExtensions/Private/VirtualRealityPawn.cpp
+++ b/Source/DisplayClusterExtensions/Private/VirtualRealityPawn.cpp
@@ -1,7 +1,6 @@
-#include "VirtualRealityPawn.h"
+#include "VirtualRealityPawn.h"
#include "Camera/CameraComponent.h"
-#include "Cluster/IDisplayClusterClusterManager.h"
#include "Engine/Engine.h"
#include "Engine/World.h"
#include "Game/IDisplayClusterGameManager.h"
@@ -10,8 +9,16 @@
#include "Kismet/GameplayStatics.h"
#include "DisplayClusterSettings.h"
#include "IDisplayCluster.h"
+#include "Components/SphereComponent.h"
+#include "DrawDebugHelpers.h"
+#include "Math/Vector.h"
#include "VirtualRealityUtilities.h"
+#include "GrabbingBehaviorComponent.h"
+#include "Grabable.h"
+#include "Clickable.h"
+
+
AVirtualRealityPawn::AVirtualRealityPawn(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
bUseControllerRotationYaw = true;
@@ -30,8 +37,11 @@ AVirtualRealityPawn::AVirtualRealityPawn(const FObjectInitializer& ObjectInitial
RotatingMovement->RotationRate = FRotator::ZeroRotator;
Head = CreateDefaultSubobject<USceneComponent>(TEXT("Head"));
+ Head->SetupAttachment(RootComponent);
RightHand = CreateDefaultSubobject<USceneComponent>(TEXT("RightHand"));
+ RightHand->SetupAttachment(RootComponent);
LeftHand = CreateDefaultSubobject<USceneComponent>(TEXT("LeftHand"));
+ LeftHand->SetupAttachment(RootComponent);
HmdLeftMotionController = CreateDefaultSubobject<UMotionControllerComponent>(TEXT("HmdLeftMotionController"));
HmdLeftMotionController->SetupAttachment(RootComponent);
@@ -44,40 +54,45 @@ AVirtualRealityPawn::AVirtualRealityPawn(const FObjectInitializer& ObjectInitial
HmdRightMotionController->SetTrackingSource(EControllerHand::Right);
HmdRightMotionController->SetShowDeviceModel(true);
HmdRightMotionController->SetVisibility(false);
+
+ CapsuleColliderComponent = CreateDefaultSubobject<UCapsuleComponent>(TEXT("CapsuleCollider"));
+ CapsuleColliderComponent->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
+ CapsuleColliderComponent->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);
+ CapsuleColliderComponent->SetCollisionResponseToChannel(ECollisionChannel::ECC_WorldStatic, ECollisionResponse::ECR_Block);
+ CapsuleColliderComponent->SetupAttachment(CameraComponent);
+ CapsuleColliderComponent->SetCapsuleSize(40.0f, 96.0f);
+
+ HmdTracker1 = CreateDefaultSubobject<UMotionControllerComponent>(TEXT("HmdTracker1"));
+ HmdTracker1->SetupAttachment(RootComponent);
+ HmdTracker1->SetTrackingSource(EControllerHand::Special_1);
+ HmdTracker1->SetShowDeviceModel(true);
+ HmdTracker1->SetVisibility(false);
+
+ HmdTracker2 = CreateDefaultSubobject<UMotionControllerComponent>(TEXT("HmdTracker2"));
+ HmdTracker2->SetupAttachment(RootComponent);
+ HmdTracker2->SetTrackingSource(EControllerHand::Special_2);
+ HmdTracker2->SetShowDeviceModel(true);
+ HmdTracker2->SetVisibility(false);
}
void AVirtualRealityPawn::OnForward_Implementation(float Value)
{
- // Check if this function triggers correctly on ROLV.
- if (RightHand && (NavigationMode == EVRNavigationModes::nav_mode_fly || UVirtualRealityUtilities::IsDesktopMode() || UVirtualRealityUtilities::IsHeadMountedMode()))
+ if (RightHand)
{
- AddMovementInput(RightHand->GetForwardVector(), Value);
+ HandleMovementInput(Value, RightHand->GetForwardVector());
}
}
void AVirtualRealityPawn::OnRight_Implementation(float Value)
{
- if (RightHand && (NavigationMode == EVRNavigationModes::nav_mode_fly || UVirtualRealityUtilities::IsDesktopMode() || UVirtualRealityUtilities::IsHeadMountedMode()))
+ if (RightHand)
{
- AddMovementInput(RightHand->GetRightVector(), Value);
+ HandleMovementInput(Value, RightHand->GetRightVector());
}
}
void AVirtualRealityPawn::OnTurnRate_Implementation(float Rate)
{
- //if (IsRoomMountedMode())
- //{
- // //const FVector CameraLocation = IDisplayCluster::Get().GetGameMgr()->GetActiveCamera()->GetComponentLocation();
- // //RotatingMovement->PivotTranslation = RotatingMovement
- // // ->UpdatedComponent->GetComponentTransform().
- // // InverseTransformPositionNoScale(CameraLocation);
- // //RotatingMovement->RotationRate = FRotator(RotatingMovement->RotationRate.Pitch, Rate * BaseTurnRate, 0.0f);
-
- //}
- //else
- //{
- // AddControllerYawInput(Rate * BaseTurnRate * GetWorld()->GetDeltaSeconds() * CustomTimeDilation);
- //}
AddControllerYawInput(Rate * BaseTurnRate * GetWorld()->GetDeltaSeconds() * CustomTimeDilation);
}
@@ -93,14 +108,6 @@ void AVirtualRealityPawn::OnLookUpRate_Implementation(float Rate)
}
}
-void AVirtualRealityPawn::OnFire_Implementation(bool Pressed)
-{
-}
-
-void AVirtualRealityPawn::OnAction_Implementation(bool Pressed, int32 Index)
-{
-}
-
float AVirtualRealityPawn::GetBaseTurnRate() const
{
return BaseTurnRate;
@@ -146,6 +153,16 @@ UMotionControllerComponent* AVirtualRealityPawn::GetHmdRightMotionControllerComp
return HmdRightMotionController;
}
+UMotionControllerComponent* AVirtualRealityPawn::GetHmdTracker1MotionControllerComponent()
+{
+ return HmdTracker1;
+}
+
+UMotionControllerComponent* AVirtualRealityPawn::GetHmdTracker2MotionControllerComponent()
+{
+ return HmdTracker2;
+}
+
USceneComponent* AVirtualRealityPawn::GetHeadComponent()
{
return Head;
@@ -225,18 +242,24 @@ void AVirtualRealityPawn::BeginPlay()
HmdLeftMotionController->SetVisibility(ShowHMDControllers);
HmdRightMotionController->SetVisibility(ShowHMDControllers);
+ if (HmdTracker1->IsActive()) {
+ HmdTracker1->SetVisibility(ShowHMDControllers);
+ }
+ if (HmdTracker2->IsActive()) {
+ HmdTracker2->SetVisibility(ShowHMDControllers);
+ }
- LeftHand->AttachToComponent(HmdLeftMotionController, FAttachmentTransformRules::KeepRelativeTransform);
- RightHand->AttachToComponent(HmdRightMotionController, FAttachmentTransformRules::KeepRelativeTransform);
- Head->AttachToComponent(GetCameraComponent(), FAttachmentTransformRules::KeepRelativeTransform);
+ LeftHand->AttachToComponent(HmdLeftMotionController, FAttachmentTransformRules::SnapToTargetIncludingScale);
+ RightHand->AttachToComponent(HmdRightMotionController, FAttachmentTransformRules::SnapToTargetIncludingScale);
+ Head->AttachToComponent(GetCameraComponent(), FAttachmentTransformRules::SnapToTargetIncludingScale);
}
else //Desktop
{
- Head->AttachToComponent(GetCameraComponent(), FAttachmentTransformRules::KeepRelativeTransform);
+ Head->AttachToComponent(GetCameraComponent(), FAttachmentTransformRules::SnapToTargetIncludingScale);
//also attach the hands to the camera component so we can use them for interaction
- LeftHand->AttachToComponent(GetCameraComponent(), FAttachmentTransformRules::KeepRelativeTransform);
- RightHand->AttachToComponent(GetCameraComponent(), FAttachmentTransformRules::KeepRelativeTransform);
+ LeftHand->AttachToComponent(GetCameraComponent(), FAttachmentTransformRules::SnapToTargetIncludingScale);
+ RightHand->AttachToComponent(GetCameraComponent(), FAttachmentTransformRules::SnapToTargetIncludingScale);
//move to eyelevel
@@ -262,6 +285,11 @@ void AVirtualRealityPawn::BeginPlay()
ClusterEventListenerDelegate = FOnClusterEventListener::CreateUObject(this, &AVirtualRealityPawn::HandleClusterEvent);
ClusterManager->AddClusterEventListener(ClusterEventListenerDelegate);
}
+
+ CollisionComponent->SetCollisionProfileName(FName("NoCollision"));
+ CollisionComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision);
+
+ LastCameraPosition = CameraComponent->GetComponentLocation();
}
void AVirtualRealityPawn::EndPlay(const EEndPlayReason::Type EndPlayReason)
@@ -278,9 +306,35 @@ void AVirtualRealityPawn::EndPlay(const EEndPlayReason::Type EndPlayReason)
void AVirtualRealityPawn::Tick(float DeltaSeconds)
{
Super::Tick(DeltaSeconds);
+ //if the walking-mode is activated
+ if (NavigationMode == EVRNavigationModes::nav_mode_walk)
+ {
+ DeltaTime = DeltaSeconds;
+ SetCapsuleColliderCharacterSizeVR();
+ MoveByGravityOrStepUp(DeltaSeconds);
+ CheckForPhysWalkingCollision();
+ }
+
+ // if an actor is grabbed and a behavior is defined move move him accordingly
+ if (GrabbedActor != nullptr)
+ {
+ UGrabbingBehaviorComponent* Behavior = GrabbedActor->FindComponentByClass<UGrabbingBehaviorComponent>();
+
+ // if our Grabable Actor is not constrained
+ if (Behavior != nullptr)
+ {
+ // specifies the hand in space
+ FVector HandPos = this->RightHand->GetComponentLocation();
+ FQuat HandQuat = this->RightHand->GetComponentQuat();
+
+ Behavior->HandleNewPositionAndDirection(HandPos, HandQuat);
+ }
+ }
//Flystick might not be available at start, hence is checked every frame.
InitRoomMountedComponentReferences();
+
+ LastCameraPosition = CameraComponent->GetComponentLocation();
}
void AVirtualRealityPawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
@@ -292,14 +346,207 @@ void AVirtualRealityPawn::SetupPlayerInputComponent(UInputComponent* PlayerInput
PlayerInputComponent->BindAxis("MoveRight", this, &AVirtualRealityPawn::OnRight);
PlayerInputComponent->BindAxis("TurnRate", this, &AVirtualRealityPawn::OnTurnRate);
PlayerInputComponent->BindAxis("LookUpRate", this, &AVirtualRealityPawn::OnLookUpRate);
+
+ // function bindings for grabbing and releasing
+ PlayerInputComponent->BindAction("Fire", IE_Pressed, this, &AVirtualRealityPawn::OnBeginFire);
+ PlayerInputComponent->BindAction("Fire", IE_Released, this, &AVirtualRealityPawn::OnEndFire);
}
}
+void AVirtualRealityPawn::OnBeginFire_Implementation()
+{
+ // start and end point for raytracing
+ FTwoVectors StartEnd = GetHandRay(MaxClickDistance);
+ FVector Start = StartEnd.v1;
+ FVector End = StartEnd.v2;
+
+ // will be filled by the Line Trace Function
+ FHitResult Hit;
+ AActor* HitActor;
+
+ //if hit was not found return
+ FCollisionObjectQueryParams Params;
+ if (!GetWorld()->LineTraceSingleByObjectType(Hit, Start, End, Params))
+ return;
+
+ HitActor = Hit.GetActor();
+
+ // try to cast HitActor int a Grabable if not succeeded will become a nullptr
+ IGrabable* GrabableActor = Cast<IGrabable>(HitActor);
+ IClickable* ClickableActor = Cast<IClickable>(HitActor);
+
+ if (GrabableActor != nullptr && Hit.Distance < MaxGrabDistance)
+ {
+ // call grabable actors function so he reacts to our grab
+ GrabableActor->OnGrabbed_Implementation();
+
+
+ UGrabbingBehaviorComponent* Behavior = HitActor->FindComponentByClass<UGrabbingBehaviorComponent>();
+ if ( Behavior == nullptr)
+ HandlePhysicsAndAttachActor(HitActor);
+
+ // we save the grabbedActor in a general form to access all of AActors functions easily later
+ GrabbedActor = HitActor;
+ }
+ else if (ClickableActor != nullptr && Hit.Distance < MaxClickDistance)
+ {
+ ClickableActor->OnClicked_Implementation(Hit.Location);
+ }
+}
+
+void AVirtualRealityPawn::HandlePhysicsAndAttachActor(AActor* HitActor)
+{
+ UPrimitiveComponent* PhysicsComp = HitActor->FindComponentByClass<UPrimitiveComponent>();
+
+ bDidSimulatePhysics = PhysicsComp->IsSimulatingPhysics(); // remember if we need to tun physics back on or not
+ PhysicsComp->SetSimulatePhysics(false);
+ FAttachmentTransformRules Rules = FAttachmentTransformRules::KeepWorldTransform;
+ Rules.bWeldSimulatedBodies = true;
+ HitActor->AttachToComponent(this->RightHand, Rules);
+}
+
+void AVirtualRealityPawn::OnEndFire_Implementation() {
+
+ // if we didnt grab anyone there is no need to release
+ if (GrabbedActor == nullptr)
+ return;
+
+ // let the grabbed object reacot to release
+ Cast<IGrabable>(GrabbedActor)->OnReleased_Implementation();
+
+ // Detach the Actor
+
+ UPrimitiveComponent* PhysicsComp = GrabbedActor->FindComponentByClass<UPrimitiveComponent>();
+ UGrabbingBehaviorComponent* Behavior = GrabbedActor->FindComponentByClass<UGrabbingBehaviorComponent>();
+ if (Behavior == nullptr)
+ {
+ GrabbedActor->GetRootComponent()->DetachFromComponent(FDetachmentTransformRules::KeepWorldTransform);
+ PhysicsComp->SetSimulatePhysics(bDidSimulatePhysics);
+ }
+
+ // forget about the actor
+ GrabbedActor = nullptr;
+}
+
+FTwoVectors AVirtualRealityPawn::GetHandRay(float Length)
+{
+ FVector Start = this->RightHand->GetComponentLocation();
+ FVector Direction = this->RightHand->GetForwardVector();
+ FVector End = Start + Length * Direction;
+
+ return FTwoVectors(Start, End);
+}
+
+
UPawnMovementComponent* AVirtualRealityPawn::GetMovementComponent() const
{
return Movement;
}
+void AVirtualRealityPawn::SetCapsuleColliderCharacterSizeVR()
+{
+ float CharachterSize = abs(RootComponent->GetComponentLocation().Z - CameraComponent->GetComponentLocation().Z);
+
+ if (CharachterSize > MaxStepHeight)
+ {
+ float ColliderHeight = CharachterSize - MaxStepHeight;
+ float ColliderHalfHeight = ColliderHeight / 2.0f;
+ float ColliderRadius = 40.0f;
+ if (ColliderHalfHeight <= ColliderRadius)
+ {//Make the collider to a Sphere
+ CapsuleColliderComponent->SetCapsuleSize(ColliderHalfHeight, ColliderHalfHeight);
+ }
+ else
+ {//Make the collider to a Capsule
+ CapsuleColliderComponent->SetCapsuleSize(ColliderRadius, ColliderHalfHeight);
+ }
+
+ CapsuleColliderComponent->SetWorldLocation(CameraComponent->GetComponentLocation());
+ CapsuleColliderComponent->AddWorldOffset(FVector(0, 0, -ColliderHalfHeight));
+ CapsuleColliderComponent->SetWorldRotation(FRotator(0, 0, 1));
+ }
+ else
+ {
+ CapsuleColliderComponent->SetWorldLocation(CameraComponent->GetComponentLocation());
+ CapsuleColliderComponent->SetWorldRotation(FRotator(0, 0, 1));
+ }
+}
+
+void AVirtualRealityPawn::CheckForPhysWalkingCollision()
+{
+ FVector CurrentCameraPosition = CameraComponent->GetComponentLocation();
+ FVector Direction = CurrentCameraPosition - LastCameraPosition;
+ FHitResult FHitResultPhys;
+ CapsuleColliderComponent->AddWorldOffset(Direction, true, &FHitResultPhys);
+
+ if (FHitResultPhys.bBlockingHit)
+ {
+ RootComponent->AddLocalOffset(FHitResultPhys.Normal*FHitResultPhys.PenetrationDepth);
+ }
+}
+
+void AVirtualRealityPawn::HandleMovementInput(float Value, FVector Direction)
+{
+ if (NavigationMode == EVRNavigationModes::nav_mode_walk)
+ {
+ VRWalkingMode(Value, Direction);
+ }
+ else if (NavigationMode == EVRNavigationModes::nav_mode_fly)
+ {
+ VRFlyingMode(Value, Direction);
+ }
+}
+
+void AVirtualRealityPawn::VRWalkingMode(float Value, FVector Direction)
+{
+ Direction.Z = 0.0f;//walking
+ FVector End = (Direction * GetFloatingPawnMovement()->GetMaxSpeed());
+ FHitResult FHitResultVR;
+ CapsuleColliderComponent->AddWorldOffset(End* DeltaTime*Value, true, &FHitResultVR);
+
+ if (FVector::Distance(FHitResultVR.Location, CapsuleColliderComponent->GetComponentLocation()) > CapsuleColliderComponent->GetScaledCapsuleRadius())
+ {
+ AddMovementInput(Direction, Value);
+ }
+}
+
+void AVirtualRealityPawn::VRFlyingMode(float Value, FVector Direction)
+{
+ AddMovementInput(Direction, Value);
+}
+
+void AVirtualRealityPawn::MoveByGravityOrStepUp(float DeltaSeconds)
+{
+ FVector StartLineTraceUnderCollider = CapsuleColliderComponent->GetComponentLocation();
+ StartLineTraceUnderCollider.Z -= CapsuleColliderComponent->GetScaledCapsuleHalfHeight();
+ FHitResult HitDetailsMultiLineTrace = CreateMultiLineTrace(FVector(0, 0, -1), StartLineTraceUnderCollider, CapsuleColliderComponent->GetScaledCapsuleRadius() / 4.0f, false);
+ float DiffernceDistance = abs(MaxStepHeight - HitDetailsMultiLineTrace.Distance);
+ //Going up
+ if ((HitDetailsMultiLineTrace.bBlockingHit && HitDetailsMultiLineTrace.Distance < MaxStepHeight))
+ {
+ ShiftVertically(DiffernceDistance, UpSteppingAcceleration, DeltaSeconds, 1);
+ }
+ //Falling, Gravity, Going down
+ else if ((HitDetailsMultiLineTrace.bBlockingHit && HitDetailsMultiLineTrace.Distance > MaxStepHeight) || (HitDetailsMultiLineTrace.Actor == nullptr && HitDetailsMultiLineTrace.Distance != -1.0f))
+ {
+ ShiftVertically(DiffernceDistance, GravityAcceleration, DeltaSeconds, -1);
+ }
+}
+
+void AVirtualRealityPawn::ShiftVertically(float DiffernceDistance, float Acceleration, float DeltaSeconds, int Direction)
+{
+ VerticalSpeed += Acceleration * DeltaSeconds;
+ if (VerticalSpeed*DeltaSeconds < DiffernceDistance)
+ {
+ RootComponent->AddLocalOffset(FVector(0.f, 0.f, Direction * VerticalSpeed * DeltaSeconds));
+ }
+ else
+ {
+ RootComponent->AddLocalOffset(FVector(0.f, 0.f, Direction * DiffernceDistance));
+ VerticalSpeed = 0;
+ }
+}
+
void AVirtualRealityPawn::InitRoomMountedComponentReferences()
{
if (!UVirtualRealityUtilities::IsRoomMountedMode()) return;
@@ -311,26 +558,80 @@ void AVirtualRealityPawn::InitRoomMountedComponentReferences()
if (!ShutterGlasses)
{
ShutterGlasses = UVirtualRealityUtilities::GetClusterComponent("shutter_glasses");
- Head->AttachToComponent(ShutterGlasses, FAttachmentTransformRules::KeepRelativeTransform);
+ Head->AttachToComponent(ShutterGlasses, FAttachmentTransformRules::SnapToTargetIncludingScale);
}
if (!Flystick)
{
Flystick = UVirtualRealityUtilities::GetClusterComponent("flystick");
if (AttachRightHandInCAVE == EAttachementType::AT_FLYSTICK)
- RightHand->AttachToComponent(Flystick, FAttachmentTransformRules::KeepRelativeTransform);
+ RightHand->AttachToComponent(Flystick, FAttachmentTransformRules::SnapToTargetIncludingScale);
if (AttachLeftHandInCAVE == EAttachementType::AT_FLYSTICK)
- LeftHand->AttachToComponent(Flystick, FAttachmentTransformRules::KeepRelativeTransform);
+ LeftHand->AttachToComponent(Flystick, FAttachmentTransformRules::SnapToTargetIncludingScale);
}
if (!LeftHandTarget)
{
LeftHandTarget = UVirtualRealityUtilities::GetClusterComponent("left_hand_target");
if (AttachLeftHandInCAVE == EAttachementType::AT_HANDTARGET)
- LeftHand->AttachToComponent(LeftHandTarget, FAttachmentTransformRules::KeepRelativeTransform);
+ LeftHand->AttachToComponent(LeftHandTarget, FAttachmentTransformRules::SnapToTargetIncludingScale);
}
if (!RightHandTarget)
{
RightHandTarget = UVirtualRealityUtilities::GetClusterComponent("right_hand_target");
if (AttachRightHandInCAVE == EAttachementType::AT_HANDTARGET)
- RightHand->AttachToComponent(RightHandTarget, FAttachmentTransformRules::KeepRelativeTransform);
+ RightHand->AttachToComponent(RightHandTarget, FAttachmentTransformRules::SnapToTargetIncludingScale);
+ }
+}
+
+
+FHitResult AVirtualRealityPawn::CreateLineTrace(FVector Direction, const FVector Start, bool Visibility)
+{
+ //Re-initialize hit info
+ FHitResult HitDetails = FHitResult(ForceInit);
+
+ FVector End = ((Direction * 1000.f) + Start);
+ // additional trace parameters
+ FCollisionQueryParams TraceParams(FName(TEXT("InteractTrace")), true, NULL);
+ TraceParams.bTraceComplex = true; //to use complex collision on whatever we interact with to provide better precision.
+ TraceParams.bReturnPhysicalMaterial = true; //to provide details about the physical material, if one exists on the thing we hit, to come back in our hit result.
+
+ if (Visibility)
+ DrawDebugLine(GetWorld(), Start, End, FColor::Green, false, 1, 0, 1);
+
+ if (GetWorld()->LineTraceSingleByChannel(HitDetails, Start, End, ECC_Visibility, TraceParams))
+ {
+ if (HitDetails.bBlockingHit)
+ {
+ }
}
+ return HitDetails;
+}
+
+FHitResult AVirtualRealityPawn::CreateMultiLineTrace(FVector Direction, const FVector Start, float Radius, bool Visibility)
+{
+ TArray<FVector> StartVectors;
+ TArray<FHitResult> OutHits;
+ FHitResult HitDetailsMultiLineTrace;
+ HitDetailsMultiLineTrace.Distance = -1.0f;//(Distance=-1) not existing, but to know if this Variable not Initialized(when all Traces not compatible)
+
+ StartVectors.Add(Start); //LineTraceCenter
+ StartVectors.Add(Start + FVector(0, -Radius, 0)); //LineTraceLeft
+ StartVectors.Add(Start + FVector(0, +Radius, 0)); //LineTraceRight
+ StartVectors.Add(Start + FVector(+Radius, 0, 0)); //LineTraceFront
+ StartVectors.Add(Start + FVector(-Radius, 0, 0)); //LineTraceBehind
+
+ bool IsBlockingHitAndSameActor = true;
+ bool IsAllNothingHiting = true;
+ // loop through TArray
+ for (FVector& Vector : StartVectors)
+ {
+ FHitResult OutHit = CreateLineTrace(Direction, Vector, Visibility);
+ OutHits.Add(OutHit);
+ IsBlockingHitAndSameActor &= (OutHit.Actor == OutHits[0].Actor); //If all Hiting the same Object, then you are (going up/down) or (walking)
+ IsAllNothingHiting &= (OutHit.Actor == nullptr); //If all Hiting nothing, then you are falling
+ }
+
+ if (IsBlockingHitAndSameActor || IsAllNothingHiting)
+ HitDetailsMultiLineTrace = OutHits[0];
+
+ return HitDetailsMultiLineTrace;
}
diff --git a/Source/DisplayClusterExtensions/Private/VirtualRealityUtilities.cpp b/Source/DisplayClusterExtensions/Private/VirtualRealityUtilities.cpp
index 30a85dd360f560bdc7772418b0e3a653b2a2dfb8..33720fc021c44066651d123029068748d81cc78b 100644
--- a/Source/DisplayClusterExtensions/Private/VirtualRealityUtilities.cpp
+++ b/Source/DisplayClusterExtensions/Private/VirtualRealityUtilities.cpp
@@ -20,6 +20,20 @@ bool UVirtualRealityUtilities::IsHeadMountedMode()
return GEngine->XRSystem.IsValid() && GEngine->XRSystem->IsHeadTrackingAllowed();
}
+bool UVirtualRealityUtilities::IsMaster()
+{
+ IDisplayClusterClusterManager* manager = IDisplayCluster::Get().GetClusterMgr();
+ if (manager == nullptr) // no manager means we are not in clustermode and therefore master
+ return true;
+
+ return manager->IsMaster();
+}
+
+bool UVirtualRealityUtilities::IsSlave()
+{
+ return !IsMaster();
+}
+
FString UVirtualRealityUtilities::GetNodeName()
{
return IsRoomMountedMode() ? IDisplayCluster::Get().GetClusterMgr()->GetNodeId() : FString(TEXT("Localhost"));
diff --git a/Source/DisplayClusterExtensions/Public/Clickable.h b/Source/DisplayClusterExtensions/Public/Clickable.h
new file mode 100644
index 0000000000000000000000000000000000000000..5c96585c9f23feb319d86f4f1063dfec9643ffd0
--- /dev/null
+++ b/Source/DisplayClusterExtensions/Public/Clickable.h
@@ -0,0 +1,29 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "Object.h"
+#include "UObject/Interface.h"
+#include "Clickable.generated.h"
+
+
+UINTERFACE(BlueprintType)
+class DISPLAYCLUSTEREXTENSIONS_API UClickable : public UInterface
+{
+ // has to be empty, this is Unreals syntax to make it visible in blueprints
+ GENERATED_UINTERFACE_BODY()
+};
+
+class IClickable
+{
+ GENERATED_IINTERFACE_BODY()
+
+public:
+ // function that will be called when clickable actor got clicked, and passed the world pos of the click
+ UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = Gameplay)
+ void OnClicked(FVector WorldPositionOfClick);
+
+
+};
+
diff --git a/Source/DisplayClusterExtensions/Public/DisplayClusterEventParameterHelper.h b/Source/DisplayClusterExtensions/Public/DisplayClusterEventParameterHelper.h
new file mode 100644
index 0000000000000000000000000000000000000000..705d893d14456b800995b17c2e20fb830e6f07d0
--- /dev/null
+++ b/Source/DisplayClusterExtensions/Public/DisplayClusterEventParameterHelper.h
@@ -0,0 +1,125 @@
+#pragma once
+
+#include "Serialization/MemoryReader.h"
+#include "Serialization/MemoryWriter.h"
+
+// Helper function to put the arguments into a string map. It uses template substitution to choose the correct specialization.
+template <typename... Values>
+struct FillParameterMapImpl;
+
+// This specialization is chosen when there no argument left to serialize.
+template <>
+struct FillParameterMapImpl<>
+{
+ template <int ArgumentIndex>
+ static inline void Invoke(TMap<FString, FString>*)
+ {
+ // There is nothing left to do here.
+ }
+};
+
+// This specialization is chosen when there is at least one argument left to serialize.
+template <typename CurrentValueType, typename... RemainingValueTypes>
+struct FillParameterMapImpl<CurrentValueType, RemainingValueTypes...>
+{
+ template <int ArgumentIndex>
+ static inline void Invoke(
+ TMap<FString, FString>* ParameterMap, const CurrentValueType& CurrentValue, RemainingValueTypes&&... RemainingValues)
+ {
+ // If this assertion fails: implement the workaround described below!
+ static_assert(sizeof(TCHAR) > sizeof(uint8), "TCHAR needs to have extra room!");
+
+ TArray<uint8> SerializedValue;
+ // TODO: maybe it is a good idea to guess the amount of bytes the serialized value will have. This would probably reduce the
+ // number of reallocations in the serialization process. However, I don't know if this is already taken care of in the
+ // FArchive class. Also it is hard to implement correctly, so we have to see whether it is worth it.
+ FMemoryWriter writer(SerializedValue);
+
+ // Const cast is necessary here because the "<<" operator is used for reading and writing and thus does not take a const
+ // argument. However, it should be fine as, hopefully, the "<<" operator doesn't modify the value in "reading mode".
+ auto NonConstCurrentValue = const_cast<CurrentValueType&>(CurrentValue);
+ writer << NonConstCurrentValue;
+
+ // We have an array of bytes. Now we need to convert them to a string.
+ FString SerializedDataString;
+ SerializedDataString.Empty(SerializedValue.Num()); // Preallocate memory to avoid reallocations.
+ for (const uint8 Byte : SerializedValue)
+ {
+ // This is potentially dangerous:
+ // We treat the individual bytes as characters in a string. The character with the value 0 is normally used to mark the
+ // end of the string. Because of this, FString will not add zero values to the underlying data array. To avoid this I
+ // add 1 to every value. This only works, because the TCHAR is usually more than one byte long. However, this is not
+ // guaranteed. Currently I enforce it with a static_assert above. If this is not the case we would need to split each
+ // byte into two characters. Another option would be to access the underlying TCHAR array directly. However I don't know
+ // if they are transported correctly using the cluster events.
+ SerializedDataString += static_cast<TCHAR>(static_cast<TCHAR>(Byte) + 1);
+ }
+
+ ParameterMap->Add(FString::FromInt(ArgumentIndex), SerializedDataString);
+
+ // Recursive call for the remaining values.
+ FillParameterMapImpl<RemainingValueTypes...>::template Invoke<ArgumentIndex + 1>(
+ ParameterMap, Forward<RemainingValueTypes>(RemainingValues)...);
+ }
+};
+
+// This function creates a string map with the arguments it gets passed. The resulting map will contain an entry for every argument.
+// The first argument will have the key "0", the second "1" and so on.
+template <typename... ArgTypes>
+inline TMap<FString, FString> CreateParameterMap(ArgTypes&&... Arguments)
+{
+ TMap<FString, FString> ParameterMap;
+ ParameterMap.Empty(sizeof...(ArgTypes)); // Preallocate to avoid allocations.
+ FillParameterMapImpl<ArgTypes...>::template Invoke<0>(&ParameterMap, Forward<ArgTypes>(Arguments)...);
+ return ParameterMap;
+}
+
+// This is a wrapper function to recursively fill the argument tuple. This overload is only used if the index indicating the
+// currently handled attribute is less than the number of total attributes. I.e., if the attribute index is valid.
+template <int CurrentIndex, typename... ArgTypes>
+inline typename TEnableIf<(CurrentIndex < sizeof...(ArgTypes))>::Type FillArgumentTuple(
+ TTuple<ArgTypes...>* ArgumentTuple, const TMap<FString, FString>& Parameters)
+{
+ const FString& SerializedDataString = Parameters[FString::FromInt(CurrentIndex)];
+ TArray<uint8> SerializedData;
+ // Preallocate to avoid reallocation
+ SerializedData.Empty(SerializedDataString.Len());
+
+ // Reconstruct the original bytes. I.e., reversing the addition by one.
+ for (const auto Character : SerializedDataString)
+ {
+ SerializedData.Add(static_cast<uint8>(Character - 1));
+ }
+
+ FMemoryReader Reader(SerializedData);
+ // Read the "<<" as ">>" operator here. FArchive uses the same for both and decides based on an internal type on what to do. So
+ // this statement parses the bytes that were passed into reader and puts the parsed object into the tuple at index CurrentIndex.
+ Reader << ArgumentTuple->template Get<CurrentIndex>();
+
+ // Recursive call for the remaining attributes.
+ FillArgumentTuple<CurrentIndex + 1>(
+ Forward<TTuple<ArgTypes...>*>(ArgumentTuple), Forward<const TMap<FString, FString>&>(Parameters));
+}
+
+// The overload that is called if we are "passed the end" of attributes.
+template <int CurrentIndex, typename... ArgTypes>
+inline typename TEnableIf<(CurrentIndex >= sizeof...(ArgTypes))>::Type FillArgumentTuple(
+ TTuple<ArgTypes...>* ArgumentTuple, const TMap<FString, FString>& Parameters)
+{
+}
+
+template <typename RetType, typename... ArgTypes>
+inline RetType CallDelegateWithParameterMap(
+ const TBaseDelegate<RetType, ArgTypes...>& Delegate, const TMap<FString, FString>& Parameters)
+{
+ // Create a tuple that holds all arguments. This assumes that all argument types are default constructible. However, all
+ // types that overload the FArchive "<<" operator probably are.
+ TTuple<typename TRemoveCV<typename TRemoveReference<ArgTypes>::Type>::Type...> ArgumentTuple;
+
+ // This call will parse the string map and fill all values in the tuple appropriately.
+ FillArgumentTuple<0>(&ArgumentTuple, Parameters);
+
+ // The lambda function is only necessary because delegates do not overload the ()-operator but use the Execute() method
+ // instead. So, the lambda acts as a wrapper.
+ return ArgumentTuple.ApplyBefore([Delegate](ArgTypes&&... Arguments) { Delegate.Execute(Forward<ArgTypes>(Arguments)...); });
+}
diff --git a/Source/DisplayClusterExtensions/Public/DisplayClusterEventWrapper.h b/Source/DisplayClusterExtensions/Public/DisplayClusterEventWrapper.h
new file mode 100644
index 0000000000000000000000000000000000000000..3ed716cdd5d9c22a8286058782a5b434a69880ba
--- /dev/null
+++ b/Source/DisplayClusterExtensions/Public/DisplayClusterEventWrapper.h
@@ -0,0 +1,108 @@
+#pragma once
+
+#include "IDisplayCluster.h"
+#include "IDisplayClusterClusterManager.h"
+#include "Cluster/DisplayClusterClusterEvent.h"
+#include "DisplayClusterEventParameterHelper.h"
+#include "Templates/IsInvocable.h"
+
+template <typename MemberFunctionType, MemberFunctionType MemberFunction>
+class ClusterEventWrapperEvent;
+
+template <typename ObjectType, typename ReturnType, typename... ArgTypes, ReturnType (ObjectType::*MemberFunction)(ArgTypes...)>
+class ClusterEventWrapperEvent<ReturnType (ObjectType::*)(ArgTypes...), MemberFunction>
+{
+ static_assert(TIsDerivedFrom<ObjectType, AActor>::IsDerived, "Object needs to derive from AActor");
+
+public:
+ using MemberFunctionType = decltype(MemberFunction);
+
+ ClusterEventWrapperEvent(const TCHAR* EventTypeName) : EventTypeName{EventTypeName}
+ {
+ }
+
+ void Attach(ObjectType* NewObject)
+ {
+ IDisplayClusterClusterManager* ClusterManager = IDisplayCluster::Get().GetClusterMgr();
+ check(ClusterManager != nullptr);
+
+ checkf(Object == nullptr, TEXT("The event is already attached."));
+ Object = NewObject;
+ ObjectName = AActor::GetDebugName(Object);
+
+ if (!ClusterManager->IsStandalone())
+ {
+ check(!ClusterEventListenerDelegate.IsBound());
+ ClusterEventListenerDelegate = FOnClusterEventListener::CreateLambda([this](const FDisplayClusterClusterEvent& Event) {
+ if (Event.Type == EventTypeName && Event.Name == ObjectName)
+ {
+ // Create a tuple that holds all arguments. This assumes that all
+ // argument types are default constructible. However, all
+ // types that overload the FArchive "<<" operator probably are.
+ TTuple<typename TRemoveCV<typename TRemoveReference<ArgTypes>::Type>::Type...> ArgumentTuple;
+
+ // This call will parse the string map and fill all values in the
+ // tuple appropriately.
+ FillArgumentTuple<0>(&ArgumentTuple, Event.Parameters);
+
+ ArgumentTuple.ApplyBefore([this](const ArgTypes&... Arguments) {
+ (Object->*MemberFunction)(Forward<const ArgTypes&>(Arguments)...);
+ });
+ }
+ });
+ ClusterManager->AddClusterEventListener(ClusterEventListenerDelegate);
+ }
+ }
+
+ void Detach()
+ {
+ checkf(Object != nullptr, TEXT("The event was never attached."));
+
+ IDisplayClusterClusterManager* ClusterManager = IDisplayCluster::Get().GetClusterMgr();
+ check(ClusterManager != nullptr);
+
+ if (!ClusterManager->IsStandalone())
+ {
+ // check(ClusterEventListenerDelegate.IsBound());
+ ClusterManager->RemoveClusterEventListener(ClusterEventListenerDelegate);
+ }
+ }
+
+ void Send(ArgTypes&&... Arguments)
+ {
+ IDisplayClusterClusterManager* ClusterManager = IDisplayCluster::Get().GetClusterMgr();
+ check(ClusterManager != nullptr);
+
+ checkf(Object != nullptr, TEXT("The event was not attached."));
+
+ if (ClusterManager->IsStandalone())
+ {
+ (Object->*MemberFunction)(Forward<ArgTypes>(Arguments)...);
+ }
+ else
+ {
+ FDisplayClusterClusterEvent ClusterEvent;
+ ClusterEvent.Category = "DisplayClusterEventWrapper";
+ ClusterEvent.Type = EventTypeName;
+ ClusterEvent.Name = ObjectName;
+ ClusterEvent.Parameters = CreateParameterMap(Forward<ArgTypes>(Arguments)...);
+
+ ClusterManager->EmitClusterEvent(ClusterEvent, true);
+ }
+ }
+
+private:
+ const TCHAR* EventTypeName;
+ ObjectType* Object = nullptr;
+ FString ObjectName;
+ FOnClusterEventListener ClusterEventListenerDelegate;
+};
+
+#define DCEW_STRINGIFY(x) #x
+#define DCEW_TOSTRING(x) DCEW_STRINGIFY(x)
+
+#define DECLARE_DISPLAY_CLUSTER_EVENT(OwningType, MethodIdentifier) \
+ ClusterEventWrapperEvent<decltype(&OwningType::MethodIdentifier), &OwningType::MethodIdentifier> MethodIdentifier##Event \
+ { \
+ TEXT(DCEW_TOSTRING(OwningType) DCEW_TOSTRING(MethodIdentifier)) \
+ }
diff --git a/Source/DisplayClusterExtensions/Public/Grabable.h b/Source/DisplayClusterExtensions/Public/Grabable.h
new file mode 100644
index 0000000000000000000000000000000000000000..873e3346002055ea6b513d9c9b2a18ec4eb5a2c4
--- /dev/null
+++ b/Source/DisplayClusterExtensions/Public/Grabable.h
@@ -0,0 +1,30 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "Object.h"
+#include "UObject/Interface.h"
+#include "Grabable.generated.h"
+
+UINTERFACE(BlueprintType)
+class DISPLAYCLUSTEREXTENSIONS_API UGrabable : public UInterface
+{
+ // has to be empty, this is Unreals syntax to make it visible in blueprints
+ GENERATED_UINTERFACE_BODY()
+};
+
+class IGrabable
+{
+ GENERATED_IINTERFACE_BODY()
+
+public:
+ // function that will be called when grabbed by a pawn
+ UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = Gameplay)
+ void OnGrabbed();
+
+ // called when pawn released the object
+ UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = Gameplay)
+ void OnReleased();
+
+};
diff --git a/Source/DisplayClusterExtensions/Public/GrabbingBehaviorComponent.h b/Source/DisplayClusterExtensions/Public/GrabbingBehaviorComponent.h
new file mode 100644
index 0000000000000000000000000000000000000000..8f28127d23627cd14c2aa1a9586fc0e5652cea04
--- /dev/null
+++ b/Source/DisplayClusterExtensions/Public/GrabbingBehaviorComponent.h
@@ -0,0 +1,30 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "Components/SceneComponent.h"
+#include "GrabbingBehaviorComponent.generated.h"
+
+
+UCLASS(Abstract, ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
+class DISPLAYCLUSTEREXTENSIONS_API UGrabbingBehaviorComponent : public USceneComponent
+{
+ GENERATED_BODY()
+
+public:
+ // Sets default values for this component's properties
+ UGrabbingBehaviorComponent();
+
+
+ // takes the hand ray and moves the parent actor to a new possible position, also might change rotation
+ virtual void HandleNewPositionAndDirection(FVector position, FQuat orientation) PURE_VIRTUAL(UGrabbingBehaviorComponent::GeneratePossiblePosition,);
+
+protected:
+ // Called when the game starts
+ virtual void BeginPlay() override;
+
+public:
+ // Called every frame
+ virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
+};
diff --git a/Source/DisplayClusterExtensions/Public/GrabbingBehaviorOnLineComponent.h b/Source/DisplayClusterExtensions/Public/GrabbingBehaviorOnLineComponent.h
new file mode 100644
index 0000000000000000000000000000000000000000..decc8ead1962e9e8f825b3cf9511c4a827f3e759
--- /dev/null
+++ b/Source/DisplayClusterExtensions/Public/GrabbingBehaviorOnLineComponent.h
@@ -0,0 +1,37 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "GrabbingBehaviorComponent.h"
+#include "GrabbingBehaviorOnLineComponent.generated.h"
+
+
+UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
+class DISPLAYCLUSTEREXTENSIONS_API UGrabbingBehaviorOnLineComponent : public UGrabbingBehaviorComponent
+{
+ GENERATED_BODY()
+
+public:
+ // Sets default values for this component's properties
+ UGrabbingBehaviorOnLineComponent();
+
+ // defining a constraint line with these 3 parameters
+ UFUNCTION(BlueprintCallable) void SetDistance(float Dist);
+ UFUNCTION(BlueprintCallable) float GetDistance() const;
+
+ virtual void HandleNewPositionAndDirection(FVector position, FQuat orientation) override;
+
+protected:
+ // Called when the game starts
+ virtual void BeginPlay() override;
+
+public:
+ // Called every frame
+ virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
+
+
+private:
+ UPROPERTY(EditAnywhere) float Distance; // distance the object can be moved from the center
+
+};
diff --git a/Source/DisplayClusterExtensions/Public/GrabbingBehaviorOnPlaneComponent.h b/Source/DisplayClusterExtensions/Public/GrabbingBehaviorOnPlaneComponent.h
new file mode 100644
index 0000000000000000000000000000000000000000..d37120b788ce12fb8927b363ccf8aeca9c02275b
--- /dev/null
+++ b/Source/DisplayClusterExtensions/Public/GrabbingBehaviorOnPlaneComponent.h
@@ -0,0 +1,37 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "GrabbingBehaviorComponent.h"
+#include "GrabbingBehaviorOnPlaneComponent.generated.h"
+
+
+UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
+class DISPLAYCLUSTEREXTENSIONS_API UGrabbingBehaviorOnPlaneComponent : public UGrabbingBehaviorComponent
+{
+ GENERATED_BODY()
+
+public:
+ // Sets default values for this component's properties
+ UGrabbingBehaviorOnPlaneComponent();
+
+ // defining the constraint plane with these 3 parameters
+ UFUNCTION(BlueprintCallable) void SetDistance(float Dist);
+ UFUNCTION(BlueprintCallable) float GetDistance() const;
+
+ virtual void HandleNewPositionAndDirection(FVector position, FQuat orientation) override;
+
+protected:
+ // Called when the game starts
+ virtual void BeginPlay() override;
+
+public:
+ // Called every frame
+ virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
+
+
+private:
+ UPROPERTY(EditAnywhere) float Distance; // distance the object can be moved from the center
+
+};
diff --git a/Source/DisplayClusterExtensions/Public/VirtualRealityPawn.h b/Source/DisplayClusterExtensions/Public/VirtualRealityPawn.h
index d5b3b4c8db035bbd1e0095f25d570de5940d0620..0570e8aa290c2325b927bca9d036108b0df95741 100644
--- a/Source/DisplayClusterExtensions/Public/VirtualRealityPawn.h
+++ b/Source/DisplayClusterExtensions/Public/VirtualRealityPawn.h
@@ -5,9 +5,11 @@
#include "Cluster/IDisplayClusterClusterManager.h"
#include "DisplayClusterPawn.h"
#include "DisplayClusterSceneComponent.h"
+#include "Components/CapsuleComponent.h"
#include "GameFramework/FloatingPawnMovement.h"
#include "GameFramework/PawnMovementComponent.h"
#include "GameFramework/RotatingMovementComponent.h"
+
#include "MotionControllerComponent.h"
#include "VirtualRealityPawn.generated.h"
@@ -15,6 +17,7 @@ UENUM(BlueprintType)
enum class EVRNavigationModes : uint8
{
nav_mode_none UMETA(DisplayName = "Navigation Mode None"),
+ nav_mode_walk UMETA(DisplayName = "Navigation Mode Walk"),
nav_mode_fly UMETA(DisplayName = "Navigation Mode Fly")
};
@@ -36,8 +39,8 @@ public:
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Pawn") void OnRight(float Value);
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Pawn") void OnTurnRate(float Rate);
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Pawn") void OnLookUpRate(float Rate);
- UFUNCTION(BlueprintNativeEvent, BlueprintCallable, BlueprintCallable, Category = "Pawn") void OnFire(bool Pressed);
- UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Pawn") void OnAction(bool Pressed, int32 Index);
+ UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Pawn") void OnBeginFire();
+ UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Pawn") void OnEndFire();
UFUNCTION(Category = "Pawn") float GetBaseTurnRate() const;
UFUNCTION(Category = "Pawn") void SetBaseTurnRate(float Value);
@@ -50,29 +53,32 @@ public:
UFUNCTION(Category = "Pawn") UDisplayClusterSceneComponent* GetLeftHandtargetComponent();
UFUNCTION(Category = "Pawn") UMotionControllerComponent* GetHmdLeftMotionControllerComponent();
UFUNCTION(Category = "Pawn") UMotionControllerComponent* GetHmdRightMotionControllerComponent();
+ UFUNCTION(Category = "Pawn") UMotionControllerComponent* GetHmdTracker1MotionControllerComponent();
+ UFUNCTION(Category = "Pawn") UMotionControllerComponent* GetHmdTracker2MotionControllerComponent();
UFUNCTION(Category = "Pawn") USceneComponent* GetHeadComponent();
UFUNCTION(Category = "Pawn") USceneComponent* GetLeftHandComponent();
UFUNCTION(Category = "Pawn") USceneComponent* GetRightHandComponent();
+
UFUNCTION(Category = "Pawn") USceneComponent* GetTrackingOriginComponent();
+
private:
UFUNCTION(Category = "Pawn") USceneComponent* GetCaveCenterComponent();
UFUNCTION(Category = "Pawn") USceneComponent* GetShutterGlassesComponent();
+ UFUNCTION(Category = "Pawn") FTwoVectors GetHandRay(float Distance);
+ UFUNCTION(Category = "Pawn") void HandlePhysicsAndAttachActor(AActor* HitActor);
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pawn") EVRNavigationModes NavigationMode = EVRNavigationModes::nav_mode_fly;
- //Execute specified console command on all nDisplayCluster Nodes
- UFUNCTION(Exec, BlueprintCallable, Category = "DisplayCluster") static void ClusterExecute(const FString& Command);
-
+ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pawn") float MaxStepHeight = 40.0f;
+ //Execute specified console command on all nDisplayCluster Nodes
+ UFUNCTION(Exec, BlueprintCallable, Category = "DisplayCluster") static void ClusterExecute(const FString& Command);
private:
FOnClusterEventListener ClusterEventListenerDelegate;
UFUNCTION() void HandleClusterEvent(const FDisplayClusterClusterEvent& Event);
-
protected:
- DECLARE_DELEGATE_OneParam(FFireDelegate, bool);
- DECLARE_DELEGATE_TwoParams(FActionDelegate, bool, int32);
virtual void BeginPlay() override;
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
@@ -92,9 +98,13 @@ protected:
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Pawn", meta = (AllowPrivateAccess = "true")) UDisplayClusterSceneComponent* LeftHandTarget = nullptr;
// Use only when handling cross-device (PC, HMD, CAVE/ROLV) compatibility manually. HMD left motion controller.
- UMotionControllerComponent* HmdLeftMotionController = nullptr;
+ UPROPERTY() UMotionControllerComponent* HmdLeftMotionController = nullptr;
// Use only when handling cross-device (PC, HMD, CAVE/ROLV) compatibility manually. HMD right motion controller.
- UMotionControllerComponent* HmdRightMotionController = nullptr;
+ UPROPERTY() UMotionControllerComponent* HmdRightMotionController = nullptr;
+
+ // used only for HMDs, tested with the additional Vive Trackers
+ UPROPERTY() UMotionControllerComponent* HmdTracker1 = nullptr;
+ UPROPERTY() UMotionControllerComponent* HmdTracker2 = nullptr;
// PC: Camera, HMD: Camera, CAVE/ROLV: Shutter glasses.
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Pawn", meta = (AllowPrivateAccess = "true")) USceneComponent* Head = nullptr;
@@ -110,10 +120,33 @@ protected:
// Holding the Shutter Glasses Component that is attached to this Pawn
UPROPERTY() USceneComponent* ShutterGlasses = nullptr;
+ // Holding a reference to the actor that is currently being grabbed
+ UPROPERTY() AActor* GrabbedActor;
+ // indicates if the grabbed actor was simulating physics before we grabbed it
+ UPROPERTY() bool bDidSimulatePhysics;
+ UPROPERTY(EditAnywhere) float MaxGrabDistance = 50;
+ UPROPERTY(EditAnywhere) float MaxClickDistance = 500;
+
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pawn") bool ShowHMDControllers = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pawn") EAttachementType AttachRightHandInCAVE = EAttachementType::AT_FLYSTICK;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pawn") EAttachementType AttachLeftHandInCAVE = EAttachementType::AT_NONE;
+ UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Pawn", meta = (AllowPrivateAccess = "true")) UCapsuleComponent* CapsuleColliderComponent = nullptr;
private:
+ float DeltaTime = 0.0f;
+ float VerticalSpeed = 0.0f;
+ UPROPERTY() float GravityAcceleration = 981.0f;
+ UPROPERTY() float UpSteppingAcceleration = 500.0f;
+ FVector LastCameraPosition;
+
+ FHitResult CreateLineTrace(FVector Direction, const FVector Start, bool Visibility);
+ FHitResult CreateMultiLineTrace(FVector Direction, const FVector Start, float Radius, bool Visibility);
+ void SetCapsuleColliderCharacterSizeVR();
+ void CheckForPhysWalkingCollision();
+ void HandleMovementInput(float Value, FVector Direction);
+ void VRWalkingMode(float Value, FVector Direction);
+ void VRFlyingMode(float Value, FVector Direction);
+ void MoveByGravityOrStepUp(float DeltaSeconds);
+ void ShiftVertically(float DiffernceDistance, float Acceleration, float DeltaSeconds, int Direction);//(direction = Down = -1), (direction = Up = 1)
void InitRoomMountedComponentReferences();
-};
\ No newline at end of file
+};
diff --git a/Source/DisplayClusterExtensions/Public/VirtualRealityUtilities.h b/Source/DisplayClusterExtensions/Public/VirtualRealityUtilities.h
index 1aef9acc66c039c996c28bb32a1ee830108f2730..f9135f848c48306f3760e1672886627aed39e33e 100644
--- a/Source/DisplayClusterExtensions/Public/VirtualRealityUtilities.h
+++ b/Source/DisplayClusterExtensions/Public/VirtualRealityUtilities.h
@@ -23,6 +23,9 @@ public:
UFUNCTION(BlueprintPure, Category = "DisplayCluster") static bool IsRoomMountedMode();
UFUNCTION(BlueprintPure, Category = "DisplayCluster") static bool IsHeadMountedMode();
+ UFUNCTION(BlueprintPure, Category = "DisplayCluster") static bool IsMaster();
+ UFUNCTION(BlueprintPure, Category = "DisplayCluster") static bool IsSlave();
+
UFUNCTION(BlueprintPure, Category = "DisplayCluster") static FString GetNodeName();
UFUNCTION(BlueprintPure, Category = "DisplayCluster") static float GetEyeDistance();
diff --git a/Source/DisplayClusterExtensionsEditor/DIsplayClusterExtensionsEditor.Build.cs b/Source/DisplayClusterExtensionsEditor/DIsplayClusterExtensionsEditor.Build.cs
new file mode 100644
index 0000000000000000000000000000000000000000..7a96cbafacb2e141d57f368defa510b69fde4449
--- /dev/null
+++ b/Source/DisplayClusterExtensionsEditor/DIsplayClusterExtensionsEditor.Build.cs
@@ -0,0 +1,52 @@
+using UnrealBuildTool;
+
+public class DisplayClusterExtensionsEditor : ModuleRules
+{
+ public DisplayClusterExtensionsEditor(ReadOnlyTargetRules Target) : base(Target)
+ {
+ PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
+
+ PublicDependencyModuleNames.AddRange(
+ new string[]
+ {
+ "Core",
+ "CoreUObject",
+ "DisplayCluster",
+ "Engine",
+ "UnrealEd",
+ "ComponentVisualizers",
+ "HeadMountedDisplay",
+ "InputCore",
+ "DisplayClusterExtensions"
+
+
+ // ... add public dependencies that you statically link with here ...
+ }
+ );
+
+ PrivateDependencyModuleNames.AddRange(
+ new string[]
+ {
+
+
+ // ... add private dependencies that you statically link with here ...
+ }
+ );
+
+ PublicIncludePaths.AddRange(
+ new string[]
+ {
+
+ // ... add private dependencies that you statically link with here ...
+ }
+ );
+
+ PrivateIncludePaths.AddRange(
+ new string[]
+ {
+
+ // ... add private dependencies that you statically link with here ...
+ }
+ );
+ }
+}
\ No newline at end of file
diff --git a/Source/DisplayClusterExtensionsEditor/Private/DisplayClusterExtensionsEditor.cpp b/Source/DisplayClusterExtensionsEditor/Private/DisplayClusterExtensionsEditor.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..22fcd5862b5a97353cc8ca08990999095d7ce25f
--- /dev/null
+++ b/Source/DisplayClusterExtensionsEditor/Private/DisplayClusterExtensionsEditor.cpp
@@ -0,0 +1,48 @@
+#include "DisplayClusterExtensionsEditor.h"
+
+#include "ComponentVisualizers.h"
+#include "GrabbingBehaviorOnLineVisualizer.h"
+#include "GrabbingBehaviorPlaneVisualizer.h"
+
+#include "GrabbingBehaviorOnPlaneComponent.h"
+#include "GrabbingBehaviorOnLineComponent.h"
+
+#include "UnrealEdGlobals.h"
+#include "Editor/UnrealEdEngine.h"
+
+IMPLEMENT_GAME_MODULE(FDisplayClusterExtensionsEditorModule, DisplayClusterExtensionsEditor);
+
+#define LOCTEXT_NAMESPACE "DisplayClusterExtensionsEdito"
+
+void FDisplayClusterExtensionsEditorModule::StartupModule()
+{
+ if (GUnrealEd != NULL)
+ {
+ TSharedPtr<FComponentVisualizer> LineVisualizer = MakeShareable(new FGrabbingBehaviorOnLineVisualizer());
+
+ if (LineVisualizer.IsValid())
+ {
+ GUnrealEd->RegisterComponentVisualizer(UGrabbingBehaviorOnLineComponent::StaticClass()->GetFName(), LineVisualizer);
+ LineVisualizer->OnRegister();
+ }
+
+ TSharedPtr<FComponentVisualizer> PlaneVisualizer = MakeShareable(new FGrabbingBehaviorPlaneVisualizer());
+
+ if (PlaneVisualizer.IsValid())
+ {
+ GUnrealEd->RegisterComponentVisualizer(UGrabbingBehaviorOnPlaneComponent::StaticClass()->GetFName(), PlaneVisualizer);
+ PlaneVisualizer->OnRegister();
+ }
+ }
+}
+
+void FDisplayClusterExtensionsEditorModule::ShutdownModule()
+{
+ if (GUnrealEd != NULL)
+ {
+ GUnrealEd->UnregisterComponentVisualizer(UGrabbingBehaviorOnLineComponent::StaticClass()->GetFName());
+ GUnrealEd->UnregisterComponentVisualizer(UGrabbingBehaviorOnPlaneComponent::StaticClass()->GetFName());
+ }
+}
+
+#undef LOCTEXT_NAMESPACE
\ No newline at end of file
diff --git a/Source/DisplayClusterExtensionsEditor/Private/GrabbingBehaviorOnLineVisualizer.cpp b/Source/DisplayClusterExtensionsEditor/Private/GrabbingBehaviorOnLineVisualizer.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..27fee70ab128be1dbc83ca76a191ac3e531f1efe
--- /dev/null
+++ b/Source/DisplayClusterExtensionsEditor/Private/GrabbingBehaviorOnLineVisualizer.cpp
@@ -0,0 +1,35 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+
+#include "GrabbingBehaviorOnLineVisualizer.h"
+#include "GrabbingBehaviorOnLineComponent.h"
+
+#include "SceneManagement.h"
+
+FGrabbingBehaviorOnLineVisualizer::FGrabbingBehaviorOnLineVisualizer()
+{
+}
+
+FGrabbingBehaviorOnLineVisualizer::~FGrabbingBehaviorOnLineVisualizer()
+{
+}
+
+
+// Fill out your copyright notice in the Description page of Project Settings.
+
+void FGrabbingBehaviorOnLineVisualizer::DrawVisualization(const UActorComponent* Component, const FSceneView* View, FPrimitiveDrawInterface* PDI) {
+
+
+ const UGrabbingBehaviorOnLineComponent* LineBehavior = Cast<const UGrabbingBehaviorOnLineComponent>(Component);
+
+ if (LineBehavior != nullptr)
+ {
+ FVector Attachment = LineBehavior->GetComponentLocation();
+ FVector Forward = LineBehavior->GetComponentQuat().GetUpVector();
+ float Distance = LineBehavior->GetDistance();
+
+ PDI->DrawLine(Attachment + Forward * Distance, Attachment - Forward * Distance, FColor::Blue, SDPG_World);
+ }
+}
+
+
diff --git a/Source/DisplayClusterExtensionsEditor/Private/GrabbingBehaviorPlaneVisualizer.cpp b/Source/DisplayClusterExtensionsEditor/Private/GrabbingBehaviorPlaneVisualizer.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..455e62ce23a46d09f771097bbac41c61a1c54bba
--- /dev/null
+++ b/Source/DisplayClusterExtensionsEditor/Private/GrabbingBehaviorPlaneVisualizer.cpp
@@ -0,0 +1,43 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+
+#include "GrabbingBehaviorPlaneVisualizer.h"
+#include "GrabbingBehaviorOnPlaneComponent.h"
+#include "SceneManagement.h"
+
+FGrabbingBehaviorPlaneVisualizer::FGrabbingBehaviorPlaneVisualizer()
+{
+}
+
+FGrabbingBehaviorPlaneVisualizer::~FGrabbingBehaviorPlaneVisualizer()
+{
+}
+
+void FGrabbingBehaviorPlaneVisualizer::DrawVisualization(const UActorComponent* Component, const FSceneView* View, FPrimitiveDrawInterface* PDI) {
+
+
+ const UGrabbingBehaviorOnPlaneComponent* PlaneBehavior = Cast<const UGrabbingBehaviorOnPlaneComponent>(Component);
+
+ if (PlaneBehavior != nullptr)
+ {
+ FVector Attachment = PlaneBehavior->GetComponentLocation();
+ FVector Forward = PlaneBehavior->GetComponentQuat().GetUpVector();
+ FVector Right = PlaneBehavior->GetComponentQuat().GetRightVector();
+ FVector Next;
+ Right.Normalize();
+
+
+ float Distance = PlaneBehavior->GetDistance();
+ int Segments = 60;
+ check(360% Segments == 0 && "circle cannot be divided equally");
+
+ for (int i = 1; i < Segments + 1; i++) // draw circle using lines
+ {
+ Next = Right.RotateAngleAxis(360/Segments, Forward);
+
+ PDI->DrawLine(Attachment + Right*Distance,Attachment + Next*Distance, FColor::Blue, SDPG_World);
+ Right = Next;
+ }
+ }
+}
+
diff --git a/Source/DisplayClusterExtensionsEditor/Public/DisplayClusterExtensionsEditor.h b/Source/DisplayClusterExtensionsEditor/Public/DisplayClusterExtensionsEditor.h
new file mode 100644
index 0000000000000000000000000000000000000000..0858723e62a2600af6824861fff42f519ee8833f
--- /dev/null
+++ b/Source/DisplayClusterExtensionsEditor/Public/DisplayClusterExtensionsEditor.h
@@ -0,0 +1,15 @@
+#pragma once
+
+#include "CoreMinimal.h"
+#include "Modules/ModuleInterface.h"
+#include "ModuleManager.h"
+
+class FDisplayClusterExtensionsEditorModule : public IModuleInterface
+{
+public:
+ // Begin IModuleInterface implementation
+ virtual void StartupModule() override;
+ virtual void ShutdownModule() override;
+ // End IModuleInterface implementation
+
+};
\ No newline at end of file
diff --git a/Source/DisplayClusterExtensionsEditor/Public/GrabbingBehaviorOnLineVisualizer.h b/Source/DisplayClusterExtensionsEditor/Public/GrabbingBehaviorOnLineVisualizer.h
new file mode 100644
index 0000000000000000000000000000000000000000..60257dbd5fa090d7703c867a9d2c5f1a4b45e2b7
--- /dev/null
+++ b/Source/DisplayClusterExtensionsEditor/Public/GrabbingBehaviorOnLineVisualizer.h
@@ -0,0 +1,24 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "ComponentVisualizer.h"
+/**
+ *
+ */
+class FPrimitiveDrawInterface;
+class FSceneView;
+
+class DISPLAYCLUSTEREXTENSIONSEDITOR_API FGrabbingBehaviorOnLineVisualizer : public FComponentVisualizer
+{
+public:
+ FGrabbingBehaviorOnLineVisualizer();
+ ~FGrabbingBehaviorOnLineVisualizer();
+
+ virtual void DrawVisualization(const UActorComponent* Component, const FSceneView* View, FPrimitiveDrawInterface* PDI) override;
+};
+
+
+
+
diff --git a/Source/DisplayClusterExtensionsEditor/Public/GrabbingBehaviorPlaneVisualizer.h b/Source/DisplayClusterExtensionsEditor/Public/GrabbingBehaviorPlaneVisualizer.h
new file mode 100644
index 0000000000000000000000000000000000000000..517bb3ca97e066043f9de30b3fa103b6a236e305
--- /dev/null
+++ b/Source/DisplayClusterExtensionsEditor/Public/GrabbingBehaviorPlaneVisualizer.h
@@ -0,0 +1,20 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "ComponentVisualizer.h"
+/**
+ *
+ */
+class FPrimitiveDrawInterface;
+class FSceneView;
+
+class DISPLAYCLUSTEREXTENSIONSEDITOR_API FGrabbingBehaviorPlaneVisualizer : public FComponentVisualizer
+{
+public:
+ FGrabbingBehaviorPlaneVisualizer();
+ ~FGrabbingBehaviorPlaneVisualizer();
+
+ virtual void DrawVisualization(const UActorComponent* Component, const FSceneView* View, FPrimitiveDrawInterface* PDI) override;
+};
diff --git a/nDisplayExtensions.uplugin b/nDisplayExtensions.uplugin
index 87da40fd19d5c7397b17ac213df5f60c649378d4..f9391e6242275ad266313d4e45974c9b1cd5985c 100644
--- a/nDisplayExtensions.uplugin
+++ b/nDisplayExtensions.uplugin
@@ -19,7 +19,15 @@
"Name": "DisplayClusterExtensions",
"Type": "Developer",
"LoadingPhase": "Default"
- }
+ },
+
+ {
+ "Name": "DisplayClusterExtensionsEditor",
+ "Type": "Editor",
+ "LoadingPhase": "PostEngineInit"
+ }
+
+
],
"Plugins": [
{