A Demo Project for the UnrealEngineSDK
Loading...
Searching...
No Matches
PixoVRLaser.cpp
Go to the documentation of this file.
1// Copyright(c) Pixo Group. All Rights Reserved.
2
4#include "NavigationSystem.h"
7#include "Components/SceneComponent.h"
8#include "GameFramework/Character.h"
9#include "Kismet/GameplayStatics.h"
10#include "Materials/MaterialInstanceDynamic.h"
11
12// In UGameplayStatics a lot of BP functions we use here.
13
14DEFINE_LOG_CATEGORY(LogPixoVRLaser);
15
16FString GetRoleName(ENetRole Role)
17{
18 switch (Role)
19 {
20 case ROLE_None:
21 return TEXT("ROLE_None");
22 case ROLE_SimulatedProxy:
23 return TEXT("ROLE_SimulatedProxy");
24 case ROLE_AutonomousProxy:
25 return TEXT("ROLE_AutonomousProxy");
26 case ROLE_Authority:
27 return TEXT("ROLE_Authority");
28 case ROLE_MAX:
29 default:
30 return TEXT("ROLE_MAX");
31 }
32}
33
34FString GetNetModeName(ENetMode Mode)
35{
36 switch (Mode)
37 {
38 case NM_DedicatedServer:
39 return TEXT("NM_DedicatedServer");
40 case NM_ListenServer:
41 return TEXT("NM_ListenServer");
42 case NM_Standalone:
43 return TEXT("NM_Standalone");
44 case NM_Client:
45 return TEXT("NM_Client");
46 case NM_MAX:
47 default:
48 return TEXT("NM_MAX");
49 }
50}
51
56 : LaserBeam(nullptr)
57 , LaserBeamEndPoint(nullptr)
58 , WidgetInteraction(nullptr)
59 , bIsLaserBeamActive(false)
60 , LaserBeamMaxDistance(10000.0f)
61 , LaserBeamRadius(1.0f)
62 , LaserBeamEndpointRadius(0.02f)
63 , LaserBeamColor(FLinearColor::Red)
64 , LaserBeamEndpointColor(FLinearColor::Red)
65 , bActivateHovering(true)
66 , bLinkToInteraction(true)
67 , LaserBeamMaterial(nullptr)
68 , LaserBeamMaterialDynamic(nullptr)
69 , LaserBeamEndpointMaterialDynamic(nullptr)
70 , bIsLocal(true)
71{
72 PrimaryActorTick.bCanEverTick = true;
73 PrimaryActorTick.bStartWithTickEnabled = false;
74
75 bReplicates = true;
76 SetReplicateMovement(true);
77
78 static ConstructorHelpers::FObjectFinder<UStaticMesh> LaserBeamMeshFinder(TEXT("StaticMesh'/PixoCore/Meshes/Controller/BeamMesh.BeamMesh'"));
79 UStaticMesh* LaserBeamMesh = LaserBeamMeshFinder.Object;
80
81 static ConstructorHelpers::FObjectFinder<UStaticMesh> LaserBeamEndPointMeshFinder(TEXT("StaticMesh'/Engine/BasicShapes/Sphere.Sphere'"));
82 UStaticMesh* LaserBeamEndPointMesh = LaserBeamEndPointMeshFinder.Object;
83
84 static ConstructorHelpers::FObjectFinder<UMaterial> LaserBeamMaterialFinder(TEXT("Material'/PixoCore/Materials/Controller/LaserBeamSplineMat.LaserBeamSplineMat'"));
85 LaserBeamMaterial = LaserBeamMaterialFinder.Object;
86
87 SceneComponent = CreateDefaultSubobject<USceneComponent>(TEXT("LaserSceneComponent"));
88 RootComponent = SceneComponent;
89
90 LaserBeam = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("LaserBeam"));
91 LaserBeam->SetupAttachment(SceneComponent);
92 LaserBeam->SetStaticMesh(LaserBeamMesh);
93 LaserBeam->SetMaterial(0, LaserBeamMaterial);
94 LaserBeam->SetVisibility(false, true);
95 LaserBeam->SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName);
96 LaserBeam->SetGenerateOverlapEvents(false);
97 LaserBeam->SetAutoActivate(false);
98
99 LaserBeamEndPoint = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("LaserBeamEndPoint"));
100 LaserBeamEndPoint->SetupAttachment(SceneComponent);
101 LaserBeamEndPoint->SetStaticMesh(LaserBeamEndPointMesh);
102 LaserBeamEndPoint->SetMaterial(0, LaserBeamMaterial);
103 LaserBeamEndPoint->SetVisibility(false, true);
104 LaserBeamEndPoint->SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName);
105 LaserBeamEndPoint->SetAutoActivate(false);
106 LaserBeamEndPoint->SetGenerateOverlapEvents(false);
107
108 LaserPointerMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("LaserPointerMesh"));
109 LaserPointerMesh->SetupAttachment(SceneComponent);
110 LaserPointerMesh->SetVisibility(false, true);
111 LaserPointerMesh->SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName);
112 LaserPointerMesh->SetAutoActivate(false);
113 LaserPointerMesh->SetGenerateOverlapEvents(false);
114
115 WidgetInteraction = CreateDefaultSubobject<UWidgetInteractionComponent>(TEXT("LaserWidgetInteraction"));
116 WidgetInteraction->SetupAttachment(LaserBeam);
118 {
119 WidgetInteraction->InteractionDistance = LaserBeamMaxDistance;
120 }
121 WidgetInteraction->bEnableHitTesting = false;
122 WidgetInteraction->InteractionSource = EWidgetInteractionSource::World;
123 WidgetInteraction->SetAutoActivate(false);
124}
125
131void APixoVRLaser::OnConstruction(const FTransform& Transform)
132{
133 Super::OnConstruction(Transform);
134
136
137 LaserBeamMaterialDynamic = UMaterialInstanceDynamic::Create(LaserBeamMaterial, this);
138 LaserBeamMaterialDynamic->SetVectorParameterValue(TEXT("LaserColor"), LaserBeamColor);
139 LaserBeam->SetMaterial(0, LaserBeamMaterialDynamic);
140
141 LaserBeamEndpointMaterialDynamic = UMaterialInstanceDynamic::Create(LaserBeamMaterial, this);
142 LaserBeamEndpointMaterialDynamic->SetVectorParameterValue("LaserColor", LaserBeamEndpointColor);
144}
145
146void APixoVRLaser::Tick(float DeltaTime)
147{
148 Super::Tick(DeltaTime);
149
150 ClientTickLaserBeam(DeltaTime);
151}
152
153void APixoVRLaser::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
154{
155 Super::GetLifetimeReplicatedProps(OutLifetimeProps);
156
157 DOREPLIFETIME(APixoVRLaser, bIsLaserBeamActive);
158}
159
160void APixoVRLaser::SetHandType(EControllerHand HandType, bool InIsLocal)
161{
162 bIsLocal = InIsLocal;
163}
164
169
171{
172 // Laserbeam got activated.
174 {
175 // TODO Why do we set the custom hit result? It seems that
176 // the InteractionSource isn't set to EWidgetInteractionSource::Custom, so it shouldn't do anything.
177 FHitResult HitResult;
178 HitResult.bBlockingHit = false;
179 HitResult.Time = 0.0f;
180 HitResult.Distance = 0.0f;
181 HitResult.Location = FVector::ZeroVector;
182 HitResult.ImpactPoint = FVector::ZeroVector;
183 HitResult.Normal = FVector(0.0f, 0.0f, 1.0f);
184 HitResult.ImpactNormal = FVector(0.0f, 0.0f, 1.0f);
185 HitResult.BoneName = NAME_None;
186 HitResult.Actor = nullptr;
187 HitResult.Component = nullptr;
188 HitResult.Item = 0;
189 HitResult.FaceIndex = 0;
190 HitResult.TraceStart = FVector::ZeroVector;
191 HitResult.TraceEnd = FVector::ZeroVector;
192
193 WidgetInteraction->SetCustomHitResult(HitResult);
194 }
195
196 // Set active will disable the tick of the components.
197 LaserBeam->SetActive(bIsLaserBeamActive);
199
200 // Here we just hide the components.
201 LaserBeam->SetVisibility(bIsLaserBeamActive, true);
202 LaserBeamEndPoint->SetVisibility(bIsLaserBeamActive, true);
203 LaserPointerMesh->SetVisibility(bIsLaserBeamActive, true);
204
205 if(bIsLocal)
206 {
207 WidgetInteraction->bEnableHitTesting = bIsLaserBeamActive;
209
211 {
212 WidgetInteraction->InteractionDistance = LaserBeamMaxDistance;
213 }
214 }
215
216 if(HoverHitResult.Actor.IsValid() && !bIsLaserBeamActive)
217 {
218 if(HoverHitResult.Actor->GetClass()->ImplementsInterface(UPixoVRInteractionInterface::StaticClass()))
219 {
220 if(!IPixoVRInteractionInterface::Execute_DisableHoverEvents(HoverHitResult.Actor.Get()))
221 {
222 TriggerHover(false, HoverHitResult.Actor.Get(), HoverHitResult);
223
225 }
226 }
227
228 HoverHitResult = FHitResult();
229
230 HitActor = nullptr;
231 }
232
233 SetActorTickEnabled(bIsLaserBeamActive);
235}
236
237void APixoVRLaser::ActivateLaserBeam_Implementation(bool InActivate)
238{
239 // Only change things if the activate state got changed.
240 if(bIsLaserBeamActive == InActivate)
241 {
242 return;
243 }
244
245 bIsLaserBeamActive = InActivate;
247}
248
249bool APixoVRLaser::IsOverWidgetUse(bool InPressed)
250{
252 {
253 return false;
254 }
255
256 if(!WidgetInteraction->IsOverInteractableWidget())
257 {
258 return false;
259 }
260
261 InPressed ? WidgetInteraction->PressPointerKey(EKeys::LeftMouseButton) : WidgetInteraction->ReleasePointerKey(EKeys::LeftMouseButton);
262
263 return true;
264}
265
266void APixoVRLaser::ClientTickLaserBeam_Implementation(float DeltaTime)
267{
268 // If not activated or the Motion controller specified, just skip ticking.
270 {
271 return;
272 }
273
274 // Set start and endpoint for the laser beam.
275 const FVector Start = GetActorLocation();
276 const FVector End = GetActorLocation() + GetActorForwardVector() * LaserBeamMaxDistance;
277
278 // Check for visibility.
279 const ETraceTypeQuery ChannelType = UEngineTypes::ConvertToTraceType(ECC_Visibility);
280 const auto TraceComplex = false;
281
282 // Ignore the motion controller itself.
283 TArray<AActor*> ActorsToIgnore;
284 ActorsToIgnore.Add(GetOwner());
285
286 // Ignore self.
287 const auto IgnoreSelf = true;
288 const float DrawTime = 5.0;
289
290 FVector WorldScale3D;
291 FHitResult HitResult;
292 const auto Result = UKismetSystemLibrary::LineTraceSingle(this,
293 Start,
294 End,
295 ChannelType,
296 TraceComplex,
297 ActorsToIgnore,
298 EDrawDebugTrace::None,
299 HitResult,
300 IgnoreSelf,
301 FLinearColor::Red,
302 FLinearColor::Green,
303 DrawTime);
304 if(Result)
305 {
306 // The laser-beam hit something, scale the beam accordingly and put the endpoint to the correct
307 // location.
308 const auto LaserEndpointDistance = HitResult.Time * LaserBeamMaxDistance;
309
310 WorldScale3D = FVector(LaserEndpointDistance, LaserBeamRadius * 1.0f, LaserBeamRadius * 1.0f);
311 WidgetInteraction->SetCustomHitResult(HitResult);
312
313 LaserBeamEndPoint->SetRelativeLocation(FVector(LaserEndpointDistance, 0.0f, 0.0f), false, nullptr, ETeleportType::TeleportPhysics);
314
315 if( (HitActor != HitResult.Actor) && bActivateHovering)
316 {
317 if(HoverHitResult.Actor.IsValid())
318 {
319 if(HoverHitResult.Actor->GetClass()->ImplementsInterface(UPixoVRInteractionInterface::StaticClass()))
320 {
321 if(!IPixoVRInteractionInterface::Execute_DisableHoverEvents(HoverHitResult.Actor.Get()))
322 {
323 TriggerHover(false, HoverHitResult.Actor.Get(), HoverHitResult);
324
326 }
327 }
328
329 }
330
331 if (HitResult.Actor.IsValid())
332 {
333 if (HitResult.Actor->GetClass()->ImplementsInterface(UPixoVRInteractionInterface::StaticClass()))
334 {
335 if (!IPixoVRInteractionInterface::Execute_DisableHoverEvents(HitResult.Actor.Get()))
336 {
337 TriggerHover(true, HitResult.Actor.Get(), HitResult);
338
339 OnHoverBegin(HitResult);
340 }
341 }
342 HoverHitResult = HitResult;
343 HitActor = HitResult.Actor;
344 }
345 }
346 }
347 else
348 {
350 {
351 if(HoverHitResult.Actor.IsValid())
352 {
353 if(HoverHitResult.Actor->GetClass()->ImplementsInterface(UPixoVRInteractionInterface::StaticClass()))
354 {
355 if(!IPixoVRInteractionInterface::Execute_DisableHoverEvents(HoverHitResult.Actor.Get()))
356 {
357 TriggerHover(false, HoverHitResult.Actor.Get(), HoverHitResult);
358
360 }
361 }
362 HoverHitResult = FHitResult();
363 }
364 HitActor = nullptr;
365 }
366 WorldScale3D = FVector(LaserBeamMaxDistance, LaserBeamRadius * 1.0f, LaserBeamRadius * 1.0f);
367 }
368
369 LaserBeam->SetWorldScale3D(WorldScale3D);
370 LaserBeamEndPoint->SetVisibility(Result, true);
371}
372
373void APixoVRLaser::TriggerHover(bool State, AActor* InteractableObject, const FHitResult& HitResult)
374{
375 APlayerController* Executor = GetWorld()->GetFirstPlayerController();
376 if(Executor && Executor->GetCharacter() == Cast<ACharacter>(GetOwner()))
377 {
378 ServerTriggerHover(State, InteractableObject, HitResult, Executor);
379 }
380}
381
382void APixoVRLaser::ServerTriggerHover_Implementation(bool State, AActor* InteractableObject,
383 const FHitResult& HitResult, APlayerController* Executor)
384{
385 MulticastTriggerHover(State, InteractableObject, HitResult, Executor);
386}
387
388bool APixoVRLaser::ServerTriggerHover_Validate(bool State, AActor* InteractableObject, const FHitResult& HitResult,
389 APlayerController* Executor)
390{
391 return true;
392}
393
394void APixoVRLaser::MulticastTriggerHover_Implementation(bool State, AActor* InteractableObject,
395 const FHitResult& HitResult, APlayerController* Executor)
396{
397 if(!Executor || !InteractableObject)
398 {
399 return;
400 }
401
402 if (State)
403 {
404 IPixoVRInteractionInterface::Execute_OnHoverBegin(InteractableObject, HitResult, Executor);
405 }
406 else
407 {
408 IPixoVRInteractionInterface::Execute_OnHoverEnd(InteractableObject, HitResult, Executor);
409 }
410}
411
412bool APixoVRLaser::MulticastTriggerHover_Validate(bool State, AActor* InteractableObject, const FHitResult& HitResult,
413 APlayerController* Executor)
414{
415 return true;
416}
417
418
419void APixoVRLaser::OnHoverBegin_Implementation(const FHitResult& HitResult)
420{
421
422}
423
424void APixoVRLaser::OnHoverEnd_Implementation(const FHitResult& HitResult)
425{
426
427}
428
429void APixoVRLaser::OnSelected_Implementation(const FHitResult& HitResult)
430{
431
432}
433
434void APixoVRLaser::OnUnSelected_Implementation(const FHitResult& HitResult)
435{
436
437}
438
440{
441 // User triggered select object.
442 // Do we have a valid object that we hovered?
443 if(HoverHitResult.Actor.IsValid() && bActivateHovering)
444 {
445 // In the following lines we just simulate if the user selected or unselected an object.
446
447 if(HoverHitResult.Actor->GetClass()->ImplementsInterface(UPixoVRInteractionInterface::StaticClass()))
448 {
449 if(!IPixoVRInteractionInterface::Execute_DisablePressEvents(HoverHitResult.Actor.Get()))
450 {
451 // it will be always a client code, cause we have inputs only locally
452 APlayerController* Executor = GetWorld()->GetFirstPlayerController();
453 // it will be called on a server if we have it, or on the client in the single-player game
454 ServerTriggerSelect(State, HoverHitResult.Actor.Get(), HoverHitResult, Executor);
455 }
456 }
457
458 if(State)
459 {
461 }
462 else
463 {
465 }
466 }
467}
468
469void APixoVRLaser::ServerTriggerSelect_Implementation(bool State, AActor* InteractableObject, const FHitResult& HitResult, APlayerController* Executor)
470{
471 if (State)
472 {
473 IPixoVRInteractionInterface::Execute_OnPressBegin(InteractableObject, HitResult, Executor);
474 }
475 else
476 {
477 IPixoVRInteractionInterface::Execute_OnPressEnd(InteractableObject, HitResult, Executor);
478 }
479}
480
481bool APixoVRLaser::ServerTriggerSelect_Validate(bool State, AActor* InteractableObject, const FHitResult& HitResult, APlayerController* Executor)
482{
483 return true;
484}
485
487{
488 return bIsLaserBeamActive;
489}
490
491#if WITH_EDITOR
492void APixoVRLaser::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
493{
494 if(PropertyChangedEvent.Property != nullptr)
495 {
496 const FName PropertyName (PropertyChangedEvent.Property->GetFName());
497 if(PropertyName == GET_MEMBER_NAME_CHECKED(APixoVRLaser, LaserBeamMaxDistance))
498 {
500 {
501 WidgetInteraction->InteractionDistance = LaserBeamMaxDistance;
502 }
503 }
504 }
505
506 Super::PostEditChangeProperty(PropertyChangedEvent);
507}
508#endif
DEFINE_LOG_CATEGORY(LogPixoVRLaser)
FString GetRoleName(ENetRole Role)
FString GetNetModeName(ENetMode Mode)
Class for VR laser interaction. It can interact with widget buttons or actors derived from IPixoVRInt...
Definition PixoVRLaser.h:23
UMaterial * LaserBeamMaterial
bool IsOverWidgetUse(bool InPressed)
Check if the laser is over a widget and simulate button press.
void TriggerSelect(bool State)
Call this method when the user wants to select the hovered object.
UStaticMeshComponent * LaserBeamEndPoint
UPROPERTY(VisibleDefaultsOnly, BlueprintReadWrite)
bool IsActivated() const
Check if the laser is activated.
void SetHandType(EControllerHand HandType, bool InIsLocal)
Set the hand type describing which hand is holding it.
UStaticMeshComponent * LaserBeam
UPROPERTY(VisibleDefaultsOnly, BlueprintReadWrite)
FLinearColor LaserBeamColor
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PixoVR | Laser")
UStaticMeshComponent * LaserPointerMesh
UPROPERTY(VisibleDefaultsOnly, BlueprintReadWrite)
void UpdateStateLaserBeam()
FHitResult HoverHitResult
void ServerTriggerSelect(bool State, AActor *InteractableObject, const FHitResult &HitResult, APlayerController *Executor)
UFUNCTION(Server, Reliable, WithValidation)
virtual void Tick(float DeltaTime) override
void OnUnSelected(const FHitResult &HitResult)
Called when the user unselects the hovered object.
TWeakObjectPtr< class AActor > HitActor
void OnRep_LaserBeamActive()
UFUNCTION()
UWidgetInteractionComponent * WidgetInteraction
UPROPERTY(VisibleDefaultsOnly, BlueprintReadWrite)
virtual void OnConstruction(const FTransform &Transform) override
FLaserBeamActivated OnLaserBeamActivated
UPROPERTY(BlueprintAssignable, Category = "PixoVR | Laser")
UMaterialInstanceDynamic * LaserBeamEndpointMaterialDynamic
float LaserBeamEndpointRadius
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PixoVR | Laser")
void OnSelected(const FHitResult &HitResult)
Called when the user selects the hovered object.
UMaterialInstanceDynamic * LaserBeamMaterialDynamic
void MulticastTriggerHover(bool State, AActor *InteractableObject, const FHitResult &HitResult, APlayerController *Executor)
UFUNCTION(NetMulticast, Reliable, WithValidation)
float LaserBeamRadius
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PixoVR | Laser")
float LaserBeamMaxDistance
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PixoVR | Laser")
void ServerTriggerHover(bool State, AActor *InteractableObject, const FHitResult &HitResult, APlayerController *Executor)
UFUNCTION(Server, Reliable, WithValidation)
bool bActivateHovering
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PixoVR | Laser")
void TriggerHover(bool State, AActor *InteractableObject, const FHitResult &HitResult)
Call this method when the user wants to hover over an object.
bool bIsLaserBeamActive
UPROPERTY(BlueprintReadOnly, Transient, ReplicatedUsing=OnRep_LaserBeamActive, Category = "PixoVR | L...
void OnHoverBegin(const FHitResult &HitResult)
Called when an object enters the hover state.
bool bLinkToInteraction
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PixoVR | Laser")
void OnHoverEnd(const FHitResult &HitResult)
Called when an object exits the hover state.
USceneComponent * SceneComponent
UPROPERTY(VisibleDefaultsOnly, BlueprintReadWrite)
FLinearColor LaserBeamEndpointColor
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PixoVR | Laser")
void ClientTickLaserBeam(float DeltaTime)
UFUNCTION(Client, Unreliable)