implement USGHandTrackerComponent to allow easy retrieval or visualization of FXRHandTrackingState
This commit is contained in:
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
- Added a `USGHandTrackerComponent` to allow retrieval or visualization of `FXRHandTrackingState` without relying on low-level SenseGlove API or UE's generic `GetHandTrackingState` functionality. This is useful when developing a custom hand-interaction system using SenseGlove/UE OpenXR API or interfacing with third-party OpenXR-compatible plugins such as [VR Expansion Plugin (VRE)](https://vreue4.com/).
|
||||||
- A new `FSGDebugVirtualHand::Draw()` overload has been added to allow visualizing `FSGDebugGizmoSettings` directly. This is used internally by the new `USGHandTrackerComponent` to visualize its `FXRHandTrackingState` if `bVisualize` is enabled.
|
- A new `FSGDebugVirtualHand::Draw()` overload has been added to allow visualizing `FSGDebugGizmoSettings` directly. This is used internally by the new `USGHandTrackerComponent` to visualize its `FXRHandTrackingState` if `bVisualize` is enabled.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@@ -0,0 +1,181 @@
|
|||||||
|
/**
|
||||||
|
* @file
|
||||||
|
*
|
||||||
|
* @author Mamadou Babaei <mamadou@senseglove.com>
|
||||||
|
*
|
||||||
|
* @section LICENSE
|
||||||
|
*
|
||||||
|
* (The MIT License)
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020 - 2026 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/Components/SGHandTrackerComponent.h"
|
||||||
|
|
||||||
|
#include "SGDebug/SGDebugVirtualHand.h"
|
||||||
|
#include "SGTracking/SGXRTracker.h"
|
||||||
|
|
||||||
|
struct USGHandTrackerComponent::FImpl
|
||||||
|
{
|
||||||
|
/************************
|
||||||
|
* Static methods
|
||||||
|
************************/
|
||||||
|
|
||||||
|
/************************
|
||||||
|
* Member variables
|
||||||
|
************************/
|
||||||
|
|
||||||
|
FXRHandTrackingState HandTrackingState;
|
||||||
|
|
||||||
|
/************************
|
||||||
|
* Owner object
|
||||||
|
************************/
|
||||||
|
|
||||||
|
USGHandTrackerComponent* Owner;
|
||||||
|
|
||||||
|
/************************
|
||||||
|
* Constructor / Destructor
|
||||||
|
************************/
|
||||||
|
|
||||||
|
explicit FImpl(USGHandTrackerComponent* InOwner);
|
||||||
|
~FImpl();
|
||||||
|
|
||||||
|
/************************
|
||||||
|
* Default copy constructor & copy assignment operator
|
||||||
|
************************/
|
||||||
|
|
||||||
|
FImpl(const FImpl& Rhs) = default;
|
||||||
|
FImpl& operator=(const FImpl& Rhs) = default;
|
||||||
|
|
||||||
|
/************************
|
||||||
|
* Methods
|
||||||
|
************************/
|
||||||
|
|
||||||
|
void UpdateXRData();
|
||||||
|
|
||||||
|
void Visualize() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
USGHandTrackerComponent::USGHandTrackerComponent(const FObjectInitializer& ObjectInitializer)
|
||||||
|
: Super(ObjectInitializer),
|
||||||
|
Pimpl(TUniquePtr<FImpl, FImplDeleter>(new FImpl{this}, PimplDeleter))
|
||||||
|
{
|
||||||
|
PrimaryComponentTick.bTickEvenWhenPaused = false;
|
||||||
|
PrimaryComponentTick.bCanEverTick = true;
|
||||||
|
PrimaryComponentTick.bStartWithTickEnabled = true;
|
||||||
|
bTickInEditor = false;
|
||||||
|
bNeverNeedsRenderUpdate = true;
|
||||||
|
bAllowConcurrentTick = true;
|
||||||
|
|
||||||
|
bRight = true;
|
||||||
|
|
||||||
|
bVisualize = false;
|
||||||
|
VisualizationSettings.Length = 1.0f;
|
||||||
|
VisualizationSettings.XAxisColor = FColor::Red;
|
||||||
|
VisualizationSettings.YAxisColor = FColor::Green;
|
||||||
|
VisualizationSettings.ZAxisColor = FColor::Blue;
|
||||||
|
VisualizationSettings.bPersistentLines = false;
|
||||||
|
VisualizationSettings.LifeTimeModifier = 1.1f;
|
||||||
|
VisualizationSettings.DepthPriority = 0;
|
||||||
|
VisualizationSettings.Thickness = 0.15f;
|
||||||
|
}
|
||||||
|
|
||||||
|
void USGHandTrackerComponent::SetRight(const bool bInRight)
|
||||||
|
{
|
||||||
|
bRight = bInRight;
|
||||||
|
|
||||||
|
Pimpl->UpdateXRData();
|
||||||
|
}
|
||||||
|
|
||||||
|
void USGHandTrackerComponent::InitializeComponent()
|
||||||
|
{
|
||||||
|
Super::InitializeComponent();
|
||||||
|
|
||||||
|
Pimpl->UpdateXRData();
|
||||||
|
}
|
||||||
|
|
||||||
|
void USGHandTrackerComponent::BeginPlay()
|
||||||
|
{
|
||||||
|
Super::BeginPlay();
|
||||||
|
|
||||||
|
Pimpl->UpdateXRData();
|
||||||
|
}
|
||||||
|
|
||||||
|
void USGHandTrackerComponent::TickComponent(
|
||||||
|
const float DeltaTime, const ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
|
||||||
|
{
|
||||||
|
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
|
||||||
|
|
||||||
|
Pimpl->UpdateXRData();
|
||||||
|
|
||||||
|
if (IsVisualized())
|
||||||
|
{
|
||||||
|
Pimpl->Visualize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const FXRHandTrackingState& USGHandTrackerComponent::GetHandTrackingState() const
|
||||||
|
{
|
||||||
|
return Pimpl->HandTrackingState;
|
||||||
|
}
|
||||||
|
|
||||||
|
USGHandTrackerComponent::FImpl::FImpl(USGHandTrackerComponent* InOwner)
|
||||||
|
: Owner(InOwner)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
USGHandTrackerComponent::FImpl::~FImpl() = default;
|
||||||
|
|
||||||
|
void USGHandTrackerComponent::FImpl::UpdateXRData()
|
||||||
|
{
|
||||||
|
const EControllerHand Hand = Owner->IsRight() ? EControllerHand::Right : EControllerHand::Left;
|
||||||
|
|
||||||
|
FXRHandTrackingState State;
|
||||||
|
State.bValid = false;
|
||||||
|
const bool bGotHandTrackingState =
|
||||||
|
FSGXRTracker::GetHandTrackingState(
|
||||||
|
Owner, EXRSpaceType::UnrealWorldSpace, Hand, State);
|
||||||
|
|
||||||
|
(void) bGotHandTrackingState;
|
||||||
|
HandTrackingState = MoveTemp(State);
|
||||||
|
}
|
||||||
|
|
||||||
|
void USGHandTrackerComponent::FImpl::Visualize() const
|
||||||
|
{
|
||||||
|
const UWorld* World{Owner->GetWorld()};
|
||||||
|
|
||||||
|
if (!HandTrackingState.bValid)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FSGDebugVirtualHand::Draw(World, HandTrackingState, Owner->VisualizationSettings);
|
||||||
|
}
|
||||||
|
|
||||||
|
void USGHandTrackerComponent::FImplDeleter::operator()(const FImpl* P) const
|
||||||
|
{
|
||||||
|
delete P;
|
||||||
|
}
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
/**
|
||||||
|
* @file
|
||||||
|
*
|
||||||
|
* @author Mamadou Babaei <mamadou@senseglove.com>
|
||||||
|
*
|
||||||
|
* @section LICENSE
|
||||||
|
*
|
||||||
|
* (The MIT License)
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020 - 2026 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
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Components/SceneComponent.h"
|
||||||
|
#include "HeadMountedDisplayTypes.h"
|
||||||
|
#include "Templates/UniquePtr.h"
|
||||||
|
|
||||||
|
#include "SGSettings/SGVirtualHandSettings.h"
|
||||||
|
|
||||||
|
#include "SGHandTrackerComponent.generated.h"
|
||||||
|
|
||||||
|
class AActor;
|
||||||
|
|
||||||
|
UCLASS(Blueprintable, BlueprintType, ClassGroup=(SenseGlove), meta=(BlueprintSpawnableComponent))
|
||||||
|
class SENSEGLOVE_API USGHandTrackerComponent : public USceneComponent
|
||||||
|
{
|
||||||
|
GENERATED_UCLASS_BODY()
|
||||||
|
|
||||||
|
private:
|
||||||
|
/**
|
||||||
|
* Determines whether this component tracks the right or the left hand.
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, Category="SenseGlove", meta=(AllowPrivateAccess="false"))
|
||||||
|
bool bRight;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If enabled, visualizes the XR hand-tracking data using debug gizmos.
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, Category="SenseGlove", meta=(AllowPrivateAccess="false"))
|
||||||
|
bool bVisualize;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* XR hand-tracking data will be visualized only if bVisualize is enabled.
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, Category="SenseGlove",
|
||||||
|
meta=(AllowPrivateAccess="false",
|
||||||
|
EditCondition="bVisualize", EditConditionHides))
|
||||||
|
FSGDebugGizmoSettings VisualizationSettings;
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct FImpl;
|
||||||
|
|
||||||
|
struct FImplDeleter
|
||||||
|
{
|
||||||
|
void operator()(const FImpl* P) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
TUniquePtr<FImpl, FImplDeleter> Pimpl;
|
||||||
|
FImplDeleter PimplDeleter;
|
||||||
|
|
||||||
|
public:
|
||||||
|
FORCEINLINE bool IsLeft() const
|
||||||
|
{
|
||||||
|
return !IsRight();
|
||||||
|
}
|
||||||
|
|
||||||
|
FORCEINLINE bool IsRight() const
|
||||||
|
{
|
||||||
|
return bRight;
|
||||||
|
}
|
||||||
|
|
||||||
|
FORCEINLINE void SetRight(const bool bInRight);
|
||||||
|
|
||||||
|
FORCEINLINE bool IsVisualized() const
|
||||||
|
{
|
||||||
|
return bVisualize;
|
||||||
|
}
|
||||||
|
|
||||||
|
FORCEINLINE void SetVisualize(const bool bInVisualize)
|
||||||
|
{
|
||||||
|
bVisualize = bInVisualize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual void InitializeComponent() override;
|
||||||
|
|
||||||
|
virtual void BeginPlay() override;
|
||||||
|
|
||||||
|
virtual void TickComponent(float DeltaTime,
|
||||||
|
enum ELevelTick TickType,
|
||||||
|
FActorComponentTickFunction* ThisTickFunction) override;
|
||||||
|
|
||||||
|
public:
|
||||||
|
const FXRHandTrackingState& GetHandTrackingState() const;
|
||||||
|
};
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
/**
|
||||||
|
* @file
|
||||||
|
*
|
||||||
|
* @author Mamadou Babaei <mamadou@senseglove.com>
|
||||||
|
*
|
||||||
|
* @section LICENSE
|
||||||
|
*
|
||||||
|
* (The MIT License)
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020 - 2026 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 "SGKismet/SGHandTrackerComponentKismetLibrary.h"
|
||||||
|
|
||||||
|
#include "SenseGlove/Components/SGHandTrackerComponent.h"
|
||||||
|
|
||||||
|
bool UHandTrackerComponentKismetLibrary::IsLeft(
|
||||||
|
const USGHandTrackerComponent* HandTrackerComponent)
|
||||||
|
{
|
||||||
|
return HandTrackerComponent->IsLeft();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UHandTrackerComponentKismetLibrary::IsRight(
|
||||||
|
const USGHandTrackerComponent* HandTrackerComponent)
|
||||||
|
{
|
||||||
|
return HandTrackerComponent->IsRight();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UHandTrackerComponentKismetLibrary::SetRight(
|
||||||
|
USGHandTrackerComponent* HandTrackerComponent, const bool bInRight)
|
||||||
|
{
|
||||||
|
HandTrackerComponent->SetRight(bInRight);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UHandTrackerComponentKismetLibrary::IsVisualized(
|
||||||
|
const USGHandTrackerComponent* HandTrackerComponent)
|
||||||
|
{
|
||||||
|
return HandTrackerComponent->IsVisualized();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UHandTrackerComponentKismetLibrary::SetVisualize(
|
||||||
|
USGHandTrackerComponent* HandTrackerComponent, const bool bInVisualize)
|
||||||
|
{
|
||||||
|
HandTrackerComponent->SetVisualize(bInVisualize);
|
||||||
|
}
|
||||||
|
|
||||||
|
const FXRHandTrackingState& UHandTrackerComponentKismetLibrary::GetHandTrackingState(
|
||||||
|
USGHandTrackerComponent* HandTrackerComponent)
|
||||||
|
{
|
||||||
|
return HandTrackerComponent->GetHandTrackingState();
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
/**
|
||||||
|
* @file
|
||||||
|
*
|
||||||
|
* @author Mamadou Babaei <mamadou@senseglove.com>
|
||||||
|
*
|
||||||
|
* @section LICENSE
|
||||||
|
*
|
||||||
|
* (The MIT License)
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020 - 2026 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
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "SGKismet/SGBlueprintFunctionLibrary.h"
|
||||||
|
|
||||||
|
#include "SGHandTrackerComponentKismetLibrary.generated.h"
|
||||||
|
|
||||||
|
class AActor;
|
||||||
|
class USGHandTrackerComponent;
|
||||||
|
|
||||||
|
UCLASS(meta=(ScriptName = "SenseGloveHandTrackerComponentKismetLibrary"))
|
||||||
|
class SENSEGLOVEKISMET_API UHandTrackerComponentKismetLibrary final : public USGBlueprintFunctionLibrary
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
UFUNCTION(BlueprintPure, Category="SenseGlove | Components | Hand Tracker Component")
|
||||||
|
static bool IsLeft(const USGHandTrackerComponent* HandTrackerComponent);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintPure, Category="SenseGlove | Components | Hand Tracker Component")
|
||||||
|
static bool IsRight(const USGHandTrackerComponent* HandTrackerComponent);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category="SenseGlove | Components | Hand Tracker Component")
|
||||||
|
static void SetRight(UPARAM(ref) USGHandTrackerComponent* HandTrackerComponent, bool bInRight);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintPure, Category="SenseGlove | Components | Hand Tracker Component")
|
||||||
|
static bool IsVisualized(const USGHandTrackerComponent* HandTrackerComponent);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category="SenseGlove | Components | Hand Tracker Component")
|
||||||
|
static void SetVisualize(UPARAM(ref) USGHandTrackerComponent* HandTrackerComponent, bool bInVisualize);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category="SenseGlove | Components | Hand Tracker Component")
|
||||||
|
static const FXRHandTrackingState& GetHandTrackingState(
|
||||||
|
UPARAM(ref) USGHandTrackerComponent* HandTrackerComponent);
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user