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": [ {