310 lines
15 KiB
C++
310 lines
15 KiB
C++
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
|
|
#pragma once
|
|
|
|
#include "Dom/JsonObject.h"
|
|
#include "GameFramework/Actor.h"
|
|
#include "GameFramework/WorldSettings.h"
|
|
#include "MRUtilityKitRoom.h"
|
|
#include "MRUtilityKit.h"
|
|
#include "MRUtilityKitData.h"
|
|
#include "OculusXRAnchorsRequests.h"
|
|
#include "Subsystems/GameInstanceSubsystem.h"
|
|
#include "Tickable.h"
|
|
|
|
#include "MRUtilityKitSubsystem.generated.h"
|
|
|
|
/**
|
|
* The Mixed Reality Utility Kit subsystem.
|
|
*
|
|
* This subsystem acts as a container for scene/anchor data. It has methods to load
|
|
* the scene data from the device or a JSON file. After the scene data has been loaded
|
|
* it will be stored inside the subsystem to make it possible to query the data from
|
|
* everywhere. In addition, it offers methods to fulfill queries on the scene data
|
|
* like ray casts or simple content placement.
|
|
*
|
|
* The subsystem only contains core functionality that is useful for most cases.
|
|
* More specific functionality is part of actors. For example, if your goal is to spawn
|
|
* meshes in the place of scene anchors you can place the AMRUKAnchorActorSpawner in the
|
|
* level to do this. When a level loads you would first load the anchor data from the
|
|
* device with this subsystem by calling LoadSceneFromDevice() and then the AMRUKAnchorActorSpawner
|
|
* will listen for the subsystem to load the scene data and then spawn the actors accordingly.
|
|
*
|
|
* You can expect methods in this subsystem to take all loaded rooms into consideration when computing.
|
|
* If you want to use a method only on a single specific room, there is most of the time a method
|
|
* with the same name on the AMRUKRoom.
|
|
*/
|
|
UCLASS(ClassGroup = MRUtilityKit)
|
|
class MRUTILITYKIT_API UMRUKSubsystem : public UGameInstanceSubsystem, public FTickableGameObject
|
|
{
|
|
GENERATED_BODY()
|
|
|
|
public:
|
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnLoaded, bool, Success);
|
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnCaptureComplete, bool, Success);
|
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnRoomCreated, AMRUKRoom*, Room);
|
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnRoomUpdated, AMRUKRoom*, Room);
|
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnRoomRemoved, AMRUKRoom*, Room);
|
|
|
|
/**
|
|
* The status of the scene loading. When loading from device this is an asynchronous process
|
|
* so will be in the Busy state until it moves to Complete or Failed.
|
|
*/
|
|
UPROPERTY(VisibleInstanceOnly, Transient, BlueprintReadOnly, Category = "MR Utility Kit")
|
|
EMRUKInitStatus SceneLoadStatus = EMRUKInitStatus::None;
|
|
|
|
/**
|
|
* An event that will trigger when a scene is loaded either from Device or from JSON.
|
|
* The Success parameter indicates whether the scene was loaded successfully or not.
|
|
*/
|
|
UPROPERTY(BlueprintAssignable, Category = "MR Utility Kit")
|
|
FOnLoaded OnSceneLoaded;
|
|
|
|
/**
|
|
* An event that gets fired after a room has been created.
|
|
*/
|
|
UPROPERTY(BlueprintAssignable, Category = "MR Utility Kit")
|
|
FOnRoomCreated OnRoomCreated;
|
|
|
|
/**
|
|
* An event that gets fired after a room has been updated.
|
|
*/
|
|
UPROPERTY(BlueprintAssignable, Category = "MR Utility Kit")
|
|
FOnRoomUpdated OnRoomUpdated;
|
|
|
|
/**
|
|
* An event that gets fired when a room gets removed.
|
|
*/
|
|
UPROPERTY(BlueprintAssignable, Category = "MR Utility Kit")
|
|
FOnRoomRemoved OnRoomRemoved;
|
|
|
|
/**
|
|
* An event that will trigger when the capture flow completed.
|
|
* The Success parameter indicates whether the scene was captured successfully or not.
|
|
*/
|
|
UPROPERTY(BlueprintAssignable, Category = "MR Utility Kit")
|
|
FOnCaptureComplete OnCaptureComplete;
|
|
|
|
/**
|
|
* Contains a list of rooms that are tracked by the mixed reality utility kit subsystem.
|
|
*/
|
|
UPROPERTY(VisibleInstanceOnly, Transient, BlueprintReadOnly, Category = "MR Utility Kit")
|
|
TArray<TObjectPtr<AMRUKRoom>> Rooms;
|
|
|
|
/**
|
|
* When world locking is enabled the position of the VR Pawn will be adjusted each frame to ensure
|
|
* the room anchors are where they should be relative to the camera position. This is necessary to
|
|
* ensure the position of the virtual objects in the world do not get out of sync with the real world.
|
|
*/
|
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
|
|
bool EnableWorldLock = true;
|
|
|
|
|
|
/**
|
|
* Cast a ray and return the closest hit anchor in the scene.
|
|
* @param Origin Origin The origin of the ray.
|
|
* @param Direction Direction The direction of the ray.
|
|
* @param MaxDist The maximum distance the ray should travel.
|
|
* @param LabelFilter The label filter can be used to include/exclude certain labels from the search.
|
|
* @param OutHit The closest hit.
|
|
* @return The anchor that the ray hit
|
|
*/
|
|
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit", meta = (AutoCreateRefTerm = "LabelFilter"))
|
|
AMRUKAnchor* Raycast(const FVector& Origin, const FVector& Direction, float MaxDist, const FMRUKLabelFilter& LabelFilter, FMRUKHit& OutHit);
|
|
|
|
/**
|
|
* Cast a ray and collect hits against the volumes and plane bounds in every room in the scene.
|
|
* The order of the hits in the array is not specified.
|
|
* @param Origin Origin The origin of the ray.
|
|
* @param Direction Direction The direction of the ray.
|
|
* @param MaxDist The maximum distance the ray should travel.
|
|
* @param LabelFilter The label filter can be used to include/exclude certain labels from the search.
|
|
* @param OutHits The hits the ray collected.
|
|
* @param OutAnchors The anchors that were hit. Each anchor in this array corresponds to a entry at the same position in OutHits.
|
|
* @return Whether the ray hit anything
|
|
*/
|
|
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit", meta = (AutoCreateRefTerm = "LabelFilter"))
|
|
bool RaycastAll(const FVector& Origin, const FVector& Direction, float MaxDist, const FMRUKLabelFilter& LabelFilter, TArray<FMRUKHit>& OutHits, TArray<AMRUKAnchor*>& OutAnchors);
|
|
|
|
/**
|
|
* Return the room that the headset is currently in. If the headset is not in any given room
|
|
* then it will return the room the headset was last in when this function was called.
|
|
* If the headset hasn't been in a valid room yet then return the first room in the list.
|
|
* If no rooms have been loaded yet then return null.
|
|
*/
|
|
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
|
AMRUKRoom* GetCurrentRoom() const;
|
|
|
|
/**
|
|
* Save all rooms and anchors to JSON. This JSON representation can than later be used by
|
|
* LoadSceneFromJsonString() to load the scene again.
|
|
* @return the JSON string.
|
|
*/
|
|
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
|
FString SaveSceneToJsonString();
|
|
|
|
/**
|
|
* Load rooms and anchors from a JSON representation.
|
|
* If the scene is already loaded the scene will be updated with the changes.
|
|
*/
|
|
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
|
void LoadSceneFromJsonString(const FString& String);
|
|
|
|
/**
|
|
* Load rooms and anchors from the device.
|
|
* If the scene is already loaded the scene will be updated with the changes.
|
|
*/
|
|
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
|
void LoadSceneFromDevice();
|
|
|
|
|
|
/**
|
|
* Removes and clears every room.
|
|
*/
|
|
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
|
void ClearScene();
|
|
|
|
/**
|
|
* Get the position on the surface that is closest to the given position with respect to the distance in all rooms.
|
|
* @param WorldPosition The position in world space from which the closest surface point should be found.
|
|
* @param OutSurfacePosition The closest position on the closest surface if any. Otherwise zero.
|
|
* @param LabelFilter The label filter can be used to include/exclude certain labels from the search.
|
|
* @param MaxDistance The distance to which a closest surface position should be searched. Everything below or equal to zero will be treated as infinity.
|
|
* @return The Anchor on which the closest surface position was found or a null pointer otherwise.
|
|
*/
|
|
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit", meta = (AutoCreateRefTerm = "LabelFilter"))
|
|
AMRUKAnchor* TryGetClosestSurfacePosition(const FVector& WorldPosition, FVector& OutSurfacePosition, const FMRUKLabelFilter& LabelFilter, double MaxDistance = 0.0);
|
|
|
|
/**
|
|
* Finds the closest seat given a ray.
|
|
* @param RayOrigin The origin of the ray.
|
|
* @param RayDirection The direction of the ray.
|
|
* @param OutSeatTransform The seat pose.
|
|
* @return If any seat was found the Anchor that has seats available will be returned. Otherwise a null pointer.
|
|
*/
|
|
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
|
AMRUKAnchor* TryGetClosestSeatPose(const FVector& RayOrigin, const FVector& RayDirection, FTransform& OutSeatTransform);
|
|
|
|
/**
|
|
* Get a suggested pose (position & rotation) from a raycast to place objects on surfaces in the scene.
|
|
* There are different positioning modes available. Default just uses the position where the raycast
|
|
* hit the object. Edge snaps the position to the edge that is nearest to the user and Center simply
|
|
* centers the position on top of the surface.
|
|
* @param RayOrigin The origin of the ray.
|
|
* @param RayDirection The direction of the ray.
|
|
* @param MaxDist The maximum distance the ray should travel.
|
|
* @param LabelFilter The label filter can be used to include/exclude certain labels from the search.
|
|
* @param OutPose The calculated pose.
|
|
* @param PositioningMethod The method that should be used for determining the position on the surface.
|
|
* @return The anchor that was hit by the ray if any. Otherwise a null pointer.
|
|
*/
|
|
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit", meta = (AutoCreateRefTerm = "LabelFilter"))
|
|
AMRUKAnchor* GetBestPoseFromRaycast(const FVector& RayOrigin, const FVector& RayDirection, double MaxDist, const FMRUKLabelFilter& LabelFilter, FTransform& OutPose, EMRUKPositioningMethod PositioningMethod = EMRUKPositioningMethod::Default);
|
|
|
|
/**
|
|
* Return the longest wall in the current room that has no other walls behind it.
|
|
* @param Tolerance The tolerance to use when determining wall that are behind.
|
|
* @return The wall anchor that is the key wall in the room.
|
|
*/
|
|
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
|
AMRUKAnchor* GetKeyWall(double Tolerance = 0.1);
|
|
|
|
/**
|
|
* Return the largest surface for a given label in the current room.
|
|
* @param Label The label of the surfaces to search in.
|
|
* @return The anchor that has the largest surface if any. Otherwise, a null pointer.
|
|
*/
|
|
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
|
AMRUKAnchor* GetLargestSurface(const FString& Label);
|
|
|
|
/**
|
|
* Checks if the given position is on or inside of any scene volume in the rooms.
|
|
* All rooms will be checked and the first anchors scene volume that has the point on or inside it will be returned.
|
|
* @param WorldPosition The position in world space to check
|
|
* @param TestVerticalBounds Whether the vertical bounds should be checked or not
|
|
* @param Tolerance Tolerance
|
|
* @return The anchor the WorldPosition is in. A null pointer otherwise.
|
|
*/
|
|
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
|
AMRUKAnchor* IsPositionInSceneVolume(const FVector& WorldPosition, bool TestVerticalBounds = true, double Tolerance = 0.0);
|
|
|
|
/**
|
|
* Spawn meshes on the position of the anchors of each room.
|
|
* The actors should have Z as up Y as right and X as forward.
|
|
* The pivot point should be in the bottom center.
|
|
* @param SpawnGroups A map which tells to spawn which actor to a given label.
|
|
* @param ProceduralMaterial Material to apply on top of the procedural mesh if any.
|
|
* @param CutHoleLabels Labels for which the generated mesh should have holes. Only works with planes.
|
|
* @param ShouldFallbackToProcedural Whether or not it should by default fallback to generating a procedural mesh if no actor class has been specified for a label.
|
|
* @return The spawned actors.
|
|
*/
|
|
UFUNCTION(BlueprintCallable, meta = (DeprecatedFunction, DeprecationMessage = "Use AMRUKAnchorActorSpawner instead."), Category = "MR Utility Kit")
|
|
TArray<AActor*> SpawnInterior(const TMap<FString, FMRUKSpawnGroup>& SpawnGroups, const TArray<FString>& CutHoleLabels, UMaterialInterface* ProceduralMaterial = nullptr, bool ShouldFallbackToProcedural = true);
|
|
|
|
/**
|
|
* Spawn meshes on the position of the anchors of each room from a random stream.
|
|
* The actors should have Z as up Y as right and X as forward.
|
|
* The pivot point should be in the bottom center.
|
|
* @param SpawnGroups A map which tells to spawn which actor to a given label.
|
|
* @param RandomStream A random generator to choose randomly between actor classes if there a multiple for one label.
|
|
* @param CutHoleLabels Labels for which the generated mesh should have holes. Only works with planes.
|
|
* @param ProceduralMaterial Material to apply on top of the procedural mesh if any.
|
|
* @param ShouldFallbackToProcedural Whether or not it should by default fallback to generating a procedural mesh if no actor class has been specified for a label.
|
|
* @return The spawned actors.
|
|
*/
|
|
UFUNCTION(BlueprintCallable, meta = (DeprecatedFunction, DeprecationMessage = "Use AMRUKAnchorActorSpawner instead."), Category = "MR Utility Kit")
|
|
TArray<AActor*> SpawnInteriorFromStream(const TMap<FString, FMRUKSpawnGroup>& SpawnGroups, const FRandomStream& RandomStream, const TArray<FString>& CutHoleLabels, UMaterialInterface* ProceduralMaterial = nullptr, bool ShouldFallbackToProcedural = true);
|
|
|
|
/**
|
|
* Launch the scene capture. After a successful capture the scene should be updated.
|
|
* @return Whether the capture was successful.
|
|
*/
|
|
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
|
|
bool LaunchSceneCapture();
|
|
|
|
public:
|
|
void Initialize(FSubsystemCollectionBase& Collection) override;
|
|
void Deinitialize() override;
|
|
|
|
TSharedRef<FJsonObject> JsonSerialize();
|
|
void UnregisterRoom(AMRUKRoom* Room);
|
|
// Calculate the bounds of an Actor class and return it, the result is saved in a cache for faster lookup.
|
|
FBox GetActorClassBounds(TSubclassOf<AActor> Actor);
|
|
UOculusXRRoomLayoutManagerComponent* GetRoomLayoutManager();
|
|
|
|
private:
|
|
AMRUKRoom* SpawnRoom();
|
|
|
|
void FinishedLoading(bool Success);
|
|
|
|
// FTickableGameObject interface
|
|
virtual void Tick(float DeltaTime) override;
|
|
virtual bool IsTickable() const override;
|
|
virtual ETickableTickType GetTickableTickType() const override { return (HasAnyFlags(RF_ClassDefaultObject) ? ETickableTickType::Never : ETickableTickType::Conditional); }
|
|
virtual TStatId GetStatId() const override { RETURN_QUICK_DECLARE_CYCLE_STAT(UMRUKSubsystem, STATGROUP_Tickables); }
|
|
virtual UWorld* GetTickableGameObjectWorld() const override { return GetWorld(); }
|
|
// ~FTickableGameObject interface
|
|
|
|
UFUNCTION()
|
|
void SceneDataLoadedComplete(bool Success);
|
|
UFUNCTION()
|
|
void UpdatedSceneDataLoadedComplete(bool Success);
|
|
UFUNCTION()
|
|
void SceneCaptureComplete(FOculusXRUInt64 RequestId, bool bSuccess);
|
|
|
|
UPROPERTY()
|
|
TObjectPtr<UMRUKSceneData> SceneData = nullptr;
|
|
|
|
UPROPERTY()
|
|
AActor* RoomLayoutManagerActor = nullptr;
|
|
UPROPERTY()
|
|
UOculusXRRoomLayoutManagerComponent* RoomLayoutManager = nullptr;
|
|
UPROPERTY()
|
|
mutable AMRUKRoom* CachedCurrentRoom = nullptr;
|
|
mutable int64 CachedCurrentRoomFrame = 0;
|
|
UPROPERTY()
|
|
AActor* PositionGenerator = nullptr;
|
|
|
|
TMap<TSubclassOf<AActor>, FBox> ActorClassBoundsCache;
|
|
};
|