365 lines
12 KiB
C++
365 lines
12 KiB
C++
/**
|
|
* @file
|
|
*
|
|
* @author Mamadou Babaei <mamadou@senseglove.com>
|
|
*
|
|
* @section LICENSE
|
|
*
|
|
* (The MIT License)
|
|
*
|
|
* Copyright (c) 2020 - 2024 SenseGlove
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in all
|
|
* copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
* SOFTWARE.
|
|
*
|
|
* @section DESCRIPTION
|
|
*
|
|
*
|
|
*/
|
|
|
|
|
|
#include "SenseGlove/GameFramework/SGPlayerController.h"
|
|
|
|
#include "Runtime/Launch/Resources/Version.h"
|
|
|
|
#if ENGINE_MAJOR_VERSION > 5 || ( ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 2 )
|
|
#include "Engine/TimerHandle.h"
|
|
#else /* ENGINE_MAJOR_VERSION > 5 || ( ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 2 ) */
|
|
#include "Engine/EngineTypes.h"
|
|
#endif /* ENGINE_MAJOR_VERSION > 5 || ( ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 2 ) */
|
|
|
|
#include "TimerManager.h"
|
|
|
|
#include "SenseGlove/Components/SGTouchComponent.h"
|
|
#include "SenseGlove/Components/SGVirtualHandComponent.h"
|
|
#include "SenseGlove/GameFramework/SGPawn.h"
|
|
#include "SenseGlove/Haptics/SGGrabState.h"
|
|
#include "SenseGlove/Haptics/SGTouchState.h"
|
|
#include "SGBuildHacks/SGPlatform.h"
|
|
#include "SGCore/SGHapticGlove.h"
|
|
|
|
struct ASGPlayerController::FImpl
|
|
{
|
|
/************************
|
|
* Static methods
|
|
************************/
|
|
|
|
static float GetForceFeedbackLevel(const AActor* Actor);
|
|
|
|
static float GetVibrotactileDuration(const AActor* ActorThumbTouching, const AActor* ActorIndexTouching,
|
|
const AActor* ActorMiddleTouching, const AActor* ActorRingTouching,
|
|
const AActor* ActorPinkyTouching);
|
|
static float GetVibrotactileLevel(const AActor* Actor);
|
|
|
|
static TArray<float> GetForceFeedbackLevels(
|
|
const AActor* ActorThumbTouching,
|
|
const AActor* ActorIndexTouching,
|
|
const AActor* ActorMiddleTouching,
|
|
const AActor* ActorRingTouching,
|
|
const AActor* ActorPinkyTouching);
|
|
|
|
static TArray<float> GetVibrotactileLevels(
|
|
const AActor* ActorThumbTouching,
|
|
const AActor* ActorIndexTouching,
|
|
const AActor* ActorMiddleTouching,
|
|
const AActor* ActorRingTouching,
|
|
const AActor* ActorPinkyTouching);
|
|
|
|
/************************
|
|
* Member variables
|
|
************************/
|
|
|
|
FTimerHandle TouchVibrotactileStopTimer;
|
|
|
|
/************************
|
|
* Owner object
|
|
************************/
|
|
|
|
ASGPlayerController* Owner;
|
|
|
|
/************************
|
|
* Constructor / Destructor
|
|
************************/
|
|
|
|
explicit FImpl(ASGPlayerController* InOwner);
|
|
~FImpl();
|
|
|
|
/************************
|
|
* Default copy constructor & copy assignment operator
|
|
************************/
|
|
|
|
FImpl(const FImpl& Rhs) = default;
|
|
FImpl& operator=(const FImpl& Rhs) = default;
|
|
|
|
/************************
|
|
* Methods
|
|
************************/
|
|
|
|
void UpdateHapticsFeedback(const FSGTouchState& TouchState);
|
|
};
|
|
|
|
float ASGPlayerController::FImpl::GetForceFeedbackLevel(const AActor* Actor)
|
|
{
|
|
if (IsValid(Actor))
|
|
{
|
|
const USGTouchComponent* TouchComponent = USGTouchComponent::GetTouchComponent(Actor);
|
|
if (IsValid(TouchComponent))
|
|
{
|
|
return TouchComponent->GetForceFeedbackLevel();
|
|
}
|
|
}
|
|
|
|
return 0.0f;
|
|
}
|
|
|
|
float ASGPlayerController::FImpl::GetVibrotactileDuration(const AActor* ActorThumbTouching,
|
|
const AActor* ActorIndexTouching,
|
|
const AActor* ActorMiddleTouching,
|
|
const AActor* ActorRingTouching,
|
|
const AActor* ActorPinkyTouching)
|
|
{
|
|
if (IsValid(ActorThumbTouching))
|
|
{
|
|
const USGTouchComponent* TouchComponent = USGTouchComponent::GetTouchComponent(ActorThumbTouching);
|
|
if (IsValid(TouchComponent))
|
|
{
|
|
return TouchComponent->GetVibrotactileDuration();
|
|
}
|
|
}
|
|
|
|
if (IsValid(ActorIndexTouching))
|
|
{
|
|
const USGTouchComponent* TouchComponent = USGTouchComponent::GetTouchComponent(ActorIndexTouching);
|
|
if (IsValid(TouchComponent))
|
|
{
|
|
return TouchComponent->GetVibrotactileDuration();
|
|
}
|
|
}
|
|
|
|
if (IsValid(ActorMiddleTouching))
|
|
{
|
|
const USGTouchComponent* TouchComponent = USGTouchComponent::GetTouchComponent(ActorMiddleTouching);
|
|
if (IsValid(TouchComponent))
|
|
{
|
|
return TouchComponent->GetVibrotactileDuration();
|
|
}
|
|
}
|
|
|
|
if (IsValid(ActorRingTouching))
|
|
{
|
|
const USGTouchComponent* TouchComponent = USGTouchComponent::GetTouchComponent(ActorRingTouching);
|
|
if (IsValid(TouchComponent))
|
|
{
|
|
return TouchComponent->GetVibrotactileDuration();
|
|
}
|
|
}
|
|
|
|
if (IsValid(ActorPinkyTouching))
|
|
{
|
|
const USGTouchComponent* TouchComponent = USGTouchComponent::GetTouchComponent(ActorPinkyTouching);
|
|
if (IsValid(TouchComponent))
|
|
{
|
|
return TouchComponent->GetVibrotactileDuration();
|
|
}
|
|
}
|
|
|
|
return 0.0f;
|
|
}
|
|
|
|
float ASGPlayerController::FImpl::GetVibrotactileLevel(const AActor* Actor)
|
|
{
|
|
if (IsValid(Actor))
|
|
{
|
|
const USGTouchComponent* TouchComponent = USGTouchComponent::GetTouchComponent(Actor);
|
|
if (IsValid(TouchComponent))
|
|
{
|
|
return TouchComponent->GetVibrotactileLevel();
|
|
}
|
|
}
|
|
|
|
return 0.0f;
|
|
}
|
|
|
|
TArray<float> ASGPlayerController::FImpl::GetForceFeedbackLevels(
|
|
const AActor* ActorThumbTouching,
|
|
const AActor* ActorIndexTouching,
|
|
const AActor* ActorMiddleTouching,
|
|
const AActor* ActorRingTouching,
|
|
const AActor* ActorPinkyTouching)
|
|
{
|
|
const float ThumbForceFeedbackLevel = GetForceFeedbackLevel(ActorThumbTouching);
|
|
const float IndexForceFeedbackLevel = GetForceFeedbackLevel(ActorIndexTouching);
|
|
const float MiddleForceFeedbackLevel = GetForceFeedbackLevel(ActorMiddleTouching);
|
|
const float RingForceFeedbackLevel = GetForceFeedbackLevel(ActorRingTouching);
|
|
const float PinkyForceFeedbackLevel = GetForceFeedbackLevel(ActorPinkyTouching);
|
|
|
|
const TArray<float> ForceFeedbackLevels{
|
|
ThumbForceFeedbackLevel,
|
|
IndexForceFeedbackLevel,
|
|
MiddleForceFeedbackLevel,
|
|
RingForceFeedbackLevel,
|
|
PinkyForceFeedbackLevel,
|
|
};
|
|
|
|
return ForceFeedbackLevels;
|
|
}
|
|
|
|
TArray<float> ASGPlayerController::FImpl::GetVibrotactileLevels(
|
|
const AActor* ActorThumbTouching,
|
|
const AActor* ActorIndexTouching,
|
|
const AActor* ActorMiddleTouching,
|
|
const AActor* ActorRingTouching,
|
|
const AActor* ActorPinkyTouching)
|
|
{
|
|
const float ThumbVibrotactileLevel = GetVibrotactileLevel(ActorThumbTouching);
|
|
const float IndexVibrotactileLevel = GetVibrotactileLevel(ActorIndexTouching);
|
|
const float MiddleVibrotactileLevel = GetVibrotactileLevel(ActorMiddleTouching);
|
|
const float RingVibrotactileLevel = GetVibrotactileLevel(ActorRingTouching);
|
|
const float PinkyVibrotactileLevel = GetVibrotactileLevel(ActorPinkyTouching);
|
|
|
|
const TArray<float> ForceFeedbackLevels{
|
|
ThumbVibrotactileLevel,
|
|
IndexVibrotactileLevel,
|
|
MiddleVibrotactileLevel,
|
|
RingVibrotactileLevel,
|
|
PinkyVibrotactileLevel,
|
|
};
|
|
|
|
return ForceFeedbackLevels;
|
|
}
|
|
|
|
ASGPlayerController::ASGPlayerController(const FObjectInitializer& ObjectInitializer)
|
|
: Super(ObjectInitializer),
|
|
Pimpl(TUniquePtr<FImpl, FImplDeleter>(new FImpl{this}, PimplDeleter))
|
|
{
|
|
}
|
|
|
|
void ASGPlayerController::BeginPlay()
|
|
{
|
|
Super::BeginPlay();
|
|
|
|
ASGPawn* SGPawn = Cast<ASGPawn>(GetPawn());
|
|
if (!ensureAlwaysMsgf(IsValid(SGPawn), TEXT("%s"), TEXT("ERROR: invalid SenseGlove pawn!")))
|
|
{
|
|
return;
|
|
}
|
|
|
|
SGPawn->OnGrabStateUpdated().AddWeakLambda(
|
|
this, [= SG_CAPTURE_THIS](const FSGGrabState& GrabState) -> void
|
|
{
|
|
if (!IsValid(SGPawn))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!IsValid(GrabState.Hand))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (SGPawn->IsGrabbing(GrabState.Hand))
|
|
{
|
|
if (!IsValid(GrabState.ActorThumbCanGrab) ||
|
|
(GrabState.ActorIndexCanGrab != GrabState.ActorThumbCanGrab
|
|
&& GrabState.ActorMiddleCanGrab != GrabState.ActorThumbCanGrab))
|
|
{
|
|
SGPawn->Release(GrabState.Hand);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (SGPawn->CanGrab(GrabState.Hand, GrabState.ActorThumbCanGrab))
|
|
{
|
|
SGPawn->Grab(GrabState.Hand, GrabState.ActorThumbCanGrab);
|
|
}
|
|
}
|
|
});
|
|
|
|
SGPawn->OnTouchStateUpdated().AddWeakLambda(
|
|
this, [= SG_CAPTURE_THIS](const FSGTouchState& TouchState) -> void
|
|
{
|
|
Pimpl->UpdateHapticsFeedback(TouchState);
|
|
});
|
|
}
|
|
|
|
ASGPlayerController::FImpl::FImpl(ASGPlayerController* InOwner)
|
|
: Owner(InOwner)
|
|
{
|
|
}
|
|
|
|
ASGPlayerController::FImpl::~FImpl() = default;
|
|
|
|
void ASGPlayerController::FImpl::UpdateHapticsFeedback(const FSGTouchState& TouchState)
|
|
{
|
|
static const TArray<float> VibrotactileOffCommand{0.0f, 0.0f, 0.0, 0.0, 0.0f};
|
|
|
|
if (!IsValid(TouchState.Hand))
|
|
{
|
|
return;
|
|
}
|
|
|
|
USGHapticGlove* Glove = TouchState.Hand->GetConnectedGlove();
|
|
if (!IsValid(Glove))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Queue the Force-Feedback command...
|
|
TArray<float> ForceFeedbackLevels{
|
|
GetForceFeedbackLevels(
|
|
TouchState.ActorThumbTouching, TouchState.ActorIndexTouching, TouchState.ActorMiddleTouching,
|
|
TouchState.ActorRingTouching, TouchState.ActorPinkyTouching)
|
|
};
|
|
Glove->QueueForceFeedbackLevels(MoveTemp(ForceFeedbackLevels));
|
|
|
|
// Queue the Vibrotactile command...
|
|
TArray<float> VibrotactileLevels{
|
|
GetVibrotactileLevels(
|
|
TouchState.ActorThumbTouching, TouchState.ActorIndexTouching, TouchState.ActorMiddleTouching,
|
|
TouchState.ActorRingTouching, TouchState.ActorPinkyTouching)
|
|
};
|
|
Glove->QueueVibroLevels(MoveTemp(VibrotactileLevels));
|
|
|
|
// Send the haptics commands!
|
|
Glove->SendHaptics();
|
|
|
|
// Stop the Vibrotactile command after the timeout if a.
|
|
const float VibrotactileDuration = FImpl::GetVibrotactileDuration(
|
|
TouchState.ActorThumbTouching, TouchState.ActorIndexTouching, TouchState.ActorMiddleTouching,
|
|
TouchState.ActorRingTouching, TouchState.ActorPinkyTouching);
|
|
|
|
Owner->GetWorldTimerManager().ClearTimer(TouchVibrotactileStopTimer);
|
|
|
|
FTimerDelegate TouchVibrotactileStopHandler;
|
|
TouchVibrotactileStopHandler.BindLambda([= SG_CAPTURE_THIS]()-> void
|
|
{
|
|
if (IsValid(Glove))
|
|
{
|
|
Glove->QueueVibroLevels(VibrotactileOffCommand);
|
|
Glove->SendHaptics();
|
|
}
|
|
});
|
|
|
|
Owner->GetWorldTimerManager().SetTimer(
|
|
TouchVibrotactileStopTimer, TouchVibrotactileStopHandler, VibrotactileDuration, false, -1.0f);
|
|
}
|
|
|
|
void ASGPlayerController::FImplDeleter::operator()(const FImpl* P) const
|
|
{
|
|
delete P;
|
|
} |