3#include "DrawDebugHelpers.h"
4#include "Engine/Engine.h"
5#include "IXRTrackingSystem.h"
6#include "IHeadMountedDisplay.h"
11#include "Chaos/ParticleHandle.h"
12#include "Chaos/KinematicGeometryParticles.h"
13#include "Chaos/ParticleHandle.h"
14#include "PhysicsProxy/SingleParticlePhysicsProxy.h"
15#include "PBDRigidsSolver.h"
19#include "Editor/UnrealEd/Classes/Editor/EditorEngine.h"
27 if (WorldContextObject)
29 return WorldContextObject->GetWorld()->GetGameViewport();
37 TInlineComponentArray<UPrimitiveComponent*> PrimitiveComponents1;
38 Actor1->GetComponents<UPrimitiveComponent>(PrimitiveComponents1);
40 TInlineComponentArray<UPrimitiveComponent*> PrimitiveComponents2;
41 Actor2->GetComponents<UPrimitiveComponent>(PrimitiveComponents2);
45 if (CollisionIgnoreSubsystem)
48 bool bIgnorefirst =
true;
49 for (
int i = 0; i < PrimitiveComponents1.Num(); ++i)
51 for (
int j = 0; j < PrimitiveComponents2.Num(); ++j)
54 if (!PrimitiveComponents1[i] || !PrimitiveComponents2[j])
60 if (PrimitiveComponents1[i]->GetCollisionEnabled() == ECollisionEnabled::NoCollision || PrimitiveComponents2[j]->GetCollisionEnabled() == ECollisionEnabled::NoCollision)
62 if (!bIgnoreCollision)
64 if (CollisionIgnoreSubsystem->AreComponentsIgnoringCollisions(PrimitiveComponents1[i], PrimitiveComponents2[j]))
66 UE_LOG(VRExpansionFunctionLibraryLog,
Error, TEXT(
"Set Actors Ignore Collision called with at least one object set to no collision that are ignoring collision already!! %s, %s"), *PrimitiveComponents1[i]->GetName(), *PrimitiveComponents2[j]->GetName());
72 CollisionIgnoreSubsystem->SetComponentCollisionIgnoreState(
true,
true, PrimitiveComponents1[i], NAME_None, PrimitiveComponents2[j], NAME_None, bIgnoreCollision, bIgnorefirst);
83 if (CollisionIgnoreSubsystem)
85 CollisionIgnoreSubsystem->
SetComponentCollisionIgnoreState(bAddChildBones1, bAddChildBones2, Prim1, OptionalBoneName1, Prim2, OptionalBoneName2, bIgnoreCollision,
true);
94 if (CollisionIgnoreSubsystem)
118 if (CollisionIgnoreSubsystem)
126 TInlineComponentArray<UPrimitiveComponent*> PrimitiveComponents1;
127 Actor1->GetComponents<UPrimitiveComponent>(PrimitiveComponents1);
131 if (CollisionIgnoreSubsystem)
133 for (
int i = 0; i < PrimitiveComponents1.Num(); ++i)
142 newAverage = lastAverage;
143 newAverage -= newAverage / numSamples;
144 newAverage += newSample / numSamples;
149 newAverage = (newSample * sampleFactor) + ((1 - sampleFactor) * lastAverage);
159 return rootComp->Mobility == EComponentMobility::Movable;
167 bHadSlotInRange =
false;
168 SlotWorldTransform = FTransform::Identity;
169 SlotName = NAME_None;
183 bHadSlotInRange =
false;
184 SlotWorldTransform = FTransform::Identity;
185 SlotName = NAME_None;
191 FVector RelativeWorldLocation = Component->GetComponentTransform().InverseTransformPosition(WorldLocation);
192 MaxRange = FMath::Square(MaxRange);
194 float ClosestSlotDistance = -0.1f;
196 TArray<FName> SocketNames = Component->GetAllSocketNames();
198 FString GripIdentifier = SlotType.ToString();
202 for (
int i = 0; i < SocketNames.Num(); ++i)
204 if (SocketNames[i].ToString().Contains(GripIdentifier, ESearchCase::IgnoreCase, ESearchDir::FromStart))
206 float vecLen = FVector::DistSquared(RelativeWorldLocation, Component->GetSocketTransform(SocketNames[i], ERelativeTransformSpace::RTS_Component).GetLocation());
208 if (MaxRange >= vecLen && (ClosestSlotDistance < 0.0f || vecLen < ClosestSlotDistance))
210 ClosestSlotDistance = vecLen;
211 bHadSlotInRange =
true;
217 TArray<USceneComponent*> AttachChildren = Component->GetAttachChildren();
219 TArray<UHandSocketComponent*> RotationallyMatchingHandSockets;
226 if (SocketComp->bDisabled)
229 FName BoneName = SocketComp->GetAttachSocketName();
230 FString SlotPrefix = BoneName != NAME_None ? BoneName.ToString() + SocketComp->SlotPrefix.ToString() : SocketComp->SlotPrefix.ToString();
232 if (SlotPrefix.Contains(GripIdentifier, ESearchCase::IgnoreCase, ESearchDir::FromStart))
234 FVector SocketRelativeLocation = Component->GetComponentTransform().InverseTransformPosition(SocketComp->GetHandSocketTransform(QueryController,
true).GetLocation());
235 float vecLen = FVector::DistSquared(RelativeWorldLocation, SocketRelativeLocation);
237 if (SocketComp->bAlwaysInRange)
239 if (SocketComp->bMatchRotation)
241 RotationallyMatchingHandSockets.Add(SocketComp);
245 TargetHandSocket = SocketComp;
246 ClosestSlotDistance = vecLen;
247 bHadSlotInRange =
true;
252 float RangeVal = (SocketComp->OverrideDistance > 0.0f ? FMath::Square(SocketComp->OverrideDistance) : MaxRange);
253 if (RangeVal >= vecLen && (ClosestSlotDistance < 0.0f || vecLen < ClosestSlotDistance))
255 if (SocketComp->bMatchRotation)
257 RotationallyMatchingHandSockets.Add(SocketComp);
261 TargetHandSocket = SocketComp;
262 ClosestSlotDistance = vecLen;
263 bHadSlotInRange =
true;
273 if (RotationallyMatchingHandSockets.Num() > 0)
277 FQuat ClosestQuat = RotationallyMatchingHandSockets[0]->GetHandSocketTransform(QueryController,
true).GetRotation();
279 TargetHandSocket = RotationallyMatchingHandSockets[0];
280 bHadSlotInRange =
true;
281 ClosestSlotDistance = ControllerRot.AngularDistance(ClosestQuat);
282 for (
int i = 1; i < RotationallyMatchingHandSockets.Num(); i++)
285 float CheckDistance = ControllerRot.AngularDistance(RotationallyMatchingHandSockets[i]->GetHandSocketTransform(QueryController,
true).GetRotation());
286 if (CheckDistance < ClosestSlotDistance)
288 TargetHandSocket = RotationallyMatchingHandSockets[i];
289 ClosestSlotDistance = CheckDistance;
296 if (TargetHandSocket)
299 SlotName = TargetHandSocket->GetFName();
300 SlotWorldTransform.SetScale3D(FVector(1.0f));
304 SlotWorldTransform = Component->GetSocketTransform(SocketNames[foundIndex]);
305 SlotName = SocketNames[foundIndex];
306 SlotWorldTransform.SetScale3D(FVector(1.0f));
319 if (GEngine->XRSystem.IsValid() && GEngine->XRSystem->GetHMDDevice())
321 return (
EBPHMDWornState)GEngine->XRSystem->GetHMDDevice()->GetHMDWornState();
329 return GEngine->XRSystem.IsValid() && GEngine->XRSystem->GetHMDDevice() && GEngine->XRSystem->GetHMDDevice()->IsHMDConnected();
334 if (GEngine && GEngine->XRSystem.IsValid())
367 static const FName OculusSystemName(TEXT(
"OculusHMD"));
368 static const FName PSVRSystemName(TEXT(
"PSVR"));
369 static const FName OSVRSystemName(TEXT(
"OSVR"));
370 static const FName GoogleARCoreSystemName(TEXT(
"FGoogleARCoreHMD"));
371 static const FName AppleARKitSystemName(TEXT(
"AppleARKit"));
372 static const FName GoogleVRHMDSystemName(TEXT(
"FGoogleVRHMD"));
374 FName DeviceName(NAME_None);
375 DeviceName = GEngine->XRSystem->GetSystemName();
378 if (DeviceName ==
FName(TEXT(
"SimpleHMD")))
382 else if (DeviceName == OculusSystemName)
384 else if (DeviceName == PSVRSystemName)
386 else if (DeviceName == OSVRSystemName)
388 else if (DeviceName == GoogleARCoreSystemName)
390 else if (DeviceName == AppleARKitSystemName)
392 else if (DeviceName == GoogleVRHMDSystemName)
406 if (UEditorEngine* EdEngine = Cast<UEditorEngine>(GEngine))
408 TOptional<FPlayInEditorSessionInfo> PlayInfo = EdEngine->GetPlayInEditorSessionInfo();
409 if (PlayInfo.IsSet())
411 return PlayInfo->OriginalRequestParams.SessionPreviewTypeOverride == EPlaySessionPreviewType::VRPreview;
430 if (UEditorEngine* EdEngine = Cast<UEditorEngine>(GEngine))
432 TOptional<FPlayInEditorSessionInfo> PlayInfo = EdEngine->GetPlayInEditorSessionInfo();
433 if (PlayInfo.IsSet())
435 return PlayInfo->OriginalRequestParams.SessionPreviewTypeOverride == EPlaySessionPreviewType::VRPreview;
451 float MinArea = -1.f;
452 float CurrentArea = -1.f;
453 FVector SupportVectorA, SupportVectorB;
454 FVector RectSideA, RectSideB;
455 float MinDotResultA, MinDotResultB, MaxDotResultA, MaxDotResultB;
457 float TestEdgeDot = 0.f;
458 FVector PolyNormal(0.f, 0.f, 1.f);
459 TArray<int32> PolyVertIndices;
462 if (InVerts.Num() == 0)
468 PolyNormal = (InVerts[InVerts.Num() / 3] - InVerts[0]) ^ (InVerts[InVerts.Num() * 2 / 3] - InVerts[InVerts.Num() / 3]);
469 if ((PolyNormal | SampleSurfaceNormal) < 0.f)
471 PolyNormal = -PolyNormal;
475 FMatrix SurfaceNormalMatrix = FRotationMatrix::MakeFromZX(PolyNormal, FVector(1.f, 0.f, 0.f));
476 TArray<FVector> TransformedVerts;
477 OutRectCenter = FVector(0.f);
478 for (int32 Idx = 0; Idx < InVerts.Num(); ++Idx)
480 OutRectCenter += InVerts[Idx];
481 TransformedVerts.Add(SurfaceNormalMatrix.InverseTransformVector(InVerts[Idx]));
483 OutRectCenter /= InVerts.Num();
486 ConvexHull2D::ComputeConvexHull(TransformedVerts, PolyVertIndices);
489 for (int32 Idx = 1; Idx < PolyVertIndices.Num() - 1; ++Idx)
491 SupportVectorA = (TransformedVerts[PolyVertIndices[Idx]] - TransformedVerts[PolyVertIndices[Idx - 1]]).GetSafeNormal();
492 SupportVectorA.Z = 0.f;
493 SupportVectorB.X = -SupportVectorA.Y;
494 SupportVectorB.Y = SupportVectorA.X;
495 SupportVectorB.Z = 0.f;
496 MinDotResultA = MinDotResultB = MaxDotResultA = MaxDotResultB = 0.f;
498 for (
int TestVertIdx = 1; TestVertIdx < PolyVertIndices.Num(); ++TestVertIdx)
500 TestEdge = TransformedVerts[PolyVertIndices[TestVertIdx]] - TransformedVerts[PolyVertIndices[0]];
501 TestEdgeDot = SupportVectorA | TestEdge;
502 if (TestEdgeDot < MinDotResultA)
504 MinDotResultA = TestEdgeDot;
506 else if (TestEdgeDot > MaxDotResultA)
508 MaxDotResultA = TestEdgeDot;
511 TestEdgeDot = SupportVectorB | TestEdge;
512 if (TestEdgeDot < MinDotResultB)
514 MinDotResultB = TestEdgeDot;
516 else if (TestEdgeDot > MaxDotResultB)
518 MaxDotResultB = TestEdgeDot;
522 CurrentArea = (MaxDotResultA - MinDotResultA) * (MaxDotResultB - MinDotResultB);
523 if (MinArea < 0.f || CurrentArea < MinArea)
525 MinArea = CurrentArea;
526 RectSideA = SupportVectorA * (MaxDotResultA - MinDotResultA);
527 RectSideB = SupportVectorB * (MaxDotResultB - MinDotResultB);
531 RectSideA = SurfaceNormalMatrix.TransformVector(RectSideA);
532 RectSideB = SurfaceNormalMatrix.TransformVector(RectSideB);
533 OutRectRotation = FRotationMatrix::MakeFromZX(PolyNormal, RectSideA).Rotator();
534 OutSideLengthX = RectSideA.Size();
535 OutSideLengthY = RectSideB.Size();
540 UWorld* World = (WorldContextObject) ? GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull) :
nullptr;
541 if (World !=
nullptr)
543 DrawDebugSphere(World, OutRectCenter, 10.f, 12, FColor::Yellow,
true);
544 DrawDebugCoordinateSystem(World, OutRectCenter, SurfaceNormalMatrix.Rotator(), 100.f,
true);
545 DrawDebugLine(World, OutRectCenter - RectSideA * 0.5f + FVector(0, 0, 10.f), OutRectCenter + RectSideA * 0.5f + FVector(0, 0, 10.f), FColor::Green,
true, -1, 0, 5.f);
546 DrawDebugLine(World, OutRectCenter - RectSideB * 0.5f + FVector(0, 0, 10.f), OutRectCenter + RectSideB * 0.5f + FVector(0, 0, 10.f), FColor::Blue,
true, -1, 0, 5.f);
550 FFrame::KismetExecutionMessage(TEXT(
"WorldContext required for MinimumAreaRectangle to draw a debug visualization."), ELogVerbosity::Warning);
574 Translation = InTransform.GetLocation();
575 Rotation = InTransform.Rotator();
576 Scale = InTransform.GetScale3D();
596 return FVector_NetQuantize(InVector);
601 return FVector_NetQuantize(InVector);
606 return FVector_NetQuantize10(InVector);
611 return FVector_NetQuantize10(InVector);
616 return FVector_NetQuantize100(InVector);
621 return FVector_NetQuantize100(InVector);
626 if (Class !=
nullptr && Outer !=
nullptr)
628 USceneComponent* Component = NewObject<USceneComponent>(Outer, *Class);
629 if (Component !=
nullptr)
632 Component->SetupAttachment(ParentComp);
634 Component->RegisterComponent();
635 Component->SetRelativeTransform(ComponentRelativeTransform);
static FName SteamVRSystemName(TEXT("SteamVR"))
EBPHMDDeviceType
UENUM(Blueprintable)
@ DT_ES2GenericStereoMesh
DEFINE_LOG_CATEGORY(VRExpansionFunctionLibraryLog)
EBPHMDWornState
UENUM(BlueprintType)
bool IsComponentIgnoringCollision(UPrimitiveComponent *Prim1)
bool AreComponentsIgnoringCollisions(UPrimitiveComponent *Prim1, UPrimitiveComponent *Prim2)
void SetComponentCollisionIgnoreState(bool bIterateChildren1, bool bIterateChildren2, UPrimitiveComponent *Prim1, FName OptionalBoneName1, UPrimitiveComponent *Prim2, FName OptionalBoneName2, bool bIgnoreCollision, bool bCheckFilters=false)
TMap< FCollisionPrimPair, FCollisionIgnorePairArray > CollisionTrackedPairs
UPROPERTY()
void RemoveComponentCollisionIgnoreState(UPrimitiveComponent *Prim1)
UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent), ClassGroup = MotionController)
FORCEINLINE FTransform GetPivotTransform()
UCLASS(Blueprintable, ClassGroup = (VRExpansionPlugin), hideCategories = ("Component Tick",...
virtual FTransform GetHandSocketTransform(UGripMotionControllerComponent *QueryController, bool bIgnoreOnlySnapMesh=false)
static FVector_NetQuantize10 Conv_FVectorToFVectorNetQuantize10(const FVector &InVector)
UFUNCTION(BlueprintPure, meta = (DisplayName = "ToVector_NetQuantize10 (Vector)", CompactNodeTitle = ...
static bool IsInVREditorPreviewOrGame()
UFUNCTION(BlueprintPure, Category = "VRExpansionFunctions", meta = (bIgnoreSelf = "true",...
static USceneComponent * AddSceneComponentByClass(UObject *Outer, TSubclassOf< USceneComponent > Class, const FTransform &ComponentRelativeTransform)
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Add Scene Component By Class"),...
static FVector_NetQuantize10 MakeVector_NetQuantize10(FVector InVector)
UFUNCTION(BlueprintPure, meta = (Scale = "1,1,1", Keywords = "construct build", NativeMakeFunc),...
static void SetObjectsIgnoreCollision(UObject *WorldContextObject, UPrimitiveComponent *Prim1=nullptr, FName OptionalBoneName1=NAME_None, bool bAddChildBones1=false, UPrimitiveComponent *Prim2=nullptr, FName OptionalBoneName2=NAME_None, bool bAddChildBones2=false, bool bIgnoreCollision=true)
UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|Collision", meta = (bIgnoreSelf = "true...
static bool IsInVREditorPreview()
UFUNCTION(BlueprintPure, Category = "VRExpansionFunctions", meta = (bIgnoreSelf = "true",...
static uint8 Conv_GripPairToGripID(const FBPGripPair &GripPair)
UFUNCTION(BlueprintPure, meta = (DisplayName = "ToGripID (FBPGripPair)", CompactNodeTitle = "->",...
static void RemoveObjectCollisionIgnore(UObject *WorldContextObject, UPrimitiveComponent *Prim1)
UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|Collision", meta = (bIgnoreSelf = "true...
static void GetGripSlotInRangeByTypeName_Component(FName SlotType, USceneComponent *Component, FVector WorldLocation, float MaxRange, bool &bHadSlotInRange, FTransform &SlotWorldTransform, FName &SlotName, UGripMotionControllerComponent *QueryController=nullptr)
UFUNCTION(BlueprintPure, Category = "VRGrip", meta = (bIgnoreSelf = "true", DisplayName = "GetGripSlo...
static FVector_NetQuantize Conv_FVectorToFVectorNetQuantize(const FVector &InVector)
UFUNCTION(BlueprintPure, meta = (DisplayName = "ToVector_NetQuantize (Vector)", CompactNodeTitle = "-...
static EBPHMDWornState GetIsHMDWorn()
UFUNCTION(BlueprintPure, Category = "VRExpansionFunctions", meta = (bIgnoreSelf = "true",...
static FVector_NetQuantize100 MakeVector_NetQuantize100(FVector InVector)
UFUNCTION(BlueprintPure, meta = (Scale = "1,1,1", Keywords = "construct build", NativeMakeFunc),...
static bool EqualEqual_FBPActorGripInformation(const FBPActorGripInformation &A, const FBPActorGripInformation &B)
UFUNCTION(BlueprintPure, meta = (DisplayName = "Equal VR Grip", CompactNodeTitle = "==",...
static FTransform_NetQuantize MakeTransform_NetQuantize(FVector Location, FRotator Rotation, FVector Scale)
UFUNCTION(BlueprintPure, meta = (Scale = "1,1,1", Keywords = "construct build", NativeMakeFunc),...
static void RemoveActorCollisionIgnore(UObject *WorldContextObject, AActor *Actor1)
UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|Collision", meta = (bIgnoreSelf = "true...
static void LowPassFilter_RollingAverage(FVector lastAverage, FVector newSample, FVector &newAverage, int32 numSamples=10)
UFUNCTION(BlueprintPure, Category = "VRExpansionFunctions", meta = (bIgnoreSelf = "true",...
static void LowPassFilter_Exponential(FVector lastAverage, FVector newSample, FVector &newAverage, float sampleFactor=0.25f)
UFUNCTION(BlueprintPure, Category = "VRExpansionFunctions", meta = (bIgnoreSelf = "true",...
static FRotator GetHMDPureYaw(FRotator HMDRotation)
UFUNCTION(BlueprintPure, Category = "VRExpansionFunctions", meta = (bIgnoreSelf = "true",...
static EBPHMDDeviceType GetHMDType()
UFUNCTION(BlueprintPure, Category = "VRExpansionFunctions", meta = (bIgnoreSelf = "true",...
static FVector_NetQuantize100 Conv_FVectorToFVectorNetQuantize100(const FVector &InVector)
UFUNCTION(BlueprintPure, meta = (DisplayName = "ToVector_NetQuantize100 (Vector)",...
static UGameViewportClient * GetGameViewportClient(UObject *WorldContextObject)
UFUNCTION(BlueprintPure, Category = "VRExpansionFunctions", meta = (WorldContext = "WorldContextObjec...
static FVector_NetQuantize MakeVector_NetQuantize(FVector InVector)
UFUNCTION(BlueprintPure, meta = (Scale = "1,1,1", Keywords = "construct build", NativeMakeFunc),...
static void NonAuthorityMinimumAreaRectangle(UObject *WorldContextObject, const TArray< FVector > &InVerts, const FVector &SampleSurfaceNormal, FVector &OutRectCenter, FRotator &OutRectRotation, float &OutSideLengthX, float &OutSideLengthY, bool bDebugDraw=false)
UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions", meta = (WorldContext = "WorldContextO...
static bool IsActiveGrip(const FBPActorGripInformation &Grip)
UFUNCTION(BlueprintPure, Category = "VRExpansionFunctions")
static void BreakTransform_NetQuantize(const FTransform_NetQuantize &InTransform, FVector &Location, FRotator &Rotation, FVector &Scale)
UFUNCTION(BlueprintPure, Category = "VRExpansionLibrary|TransformNetQuantize", meta = (NativeBreakFun...
static bool GetIsActorMovable(AActor *ActorToCheck)
UFUNCTION(BlueprintPure, Category = "VRExpansionFunctions", meta = (bIgnoreSelf = "true",...
static bool AreComponentsIgnoringCollisions(UObject *WorldContextObject, UPrimitiveComponent *Prim1, UPrimitiveComponent *Prim2)
UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|Collision", meta = (bIgnoreSelf = "true...
static void GetGripSlotInRangeByTypeName(FName SlotType, AActor *Actor, FVector WorldLocation, float MaxRange, bool &bHadSlotInRange, FTransform &SlotWorldTransform, FName &SlotName, UGripMotionControllerComponent *QueryController=nullptr)
UFUNCTION(BlueprintPure, Category = "VRGrip", meta = (bIgnoreSelf = "true", DisplayName = "GetGripSlo...
static UGripMotionControllerComponent * Conv_GripPairToMotionController(const FBPGripPair &GripPair)
UFUNCTION(BlueprintPure, meta = (DisplayName = "ToController (FBPGripPair)", CompactNodeTitle = "->",...
static bool GetIsHMDConnected()
UFUNCTION(BlueprintPure, Category = "VRExpansionFunctions", meta = (bIgnoreSelf = "true",...
static FTransform_NetQuantize Conv_TransformToTransformNetQuantize(const FTransform &InTransform)
UFUNCTION(BlueprintPure, meta = (DisplayName = "ToTransform_NetQuantize (Transform)",...
static bool IsComponentIgnoringCollision(UObject *WorldContextObject, UPrimitiveComponent *Prim1)
UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|Collision", meta = (bIgnoreSelf = "true...
static FRotator GetHMDPureYaw_I(FRotator HMDRotation)
static void SetActorsIgnoreAllCollision(UObject *WorldContextObject, AActor *Actor1=nullptr, AActor *Actor2=nullptr, bool bIgnoreCollision=true)
UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|Collision", meta = (bIgnoreSelf = "true...
USTRUCT(BlueprintType, Category = "VRExpansionLibrary")
UGripMotionControllerComponent * HoldingController
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripPair")
uint8 GripID
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripPair")