Android build settings + metaxr

This commit is contained in:
2025-05-14 14:00:02 +03:00
parent 6a2bb7475e
commit d5aa21f55c
594 changed files with 200530 additions and 2 deletions

View File

@@ -0,0 +1,412 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#pragma once
#include "Modules/ModuleManager.h"
#include "GameFramework/Actor.h"
#include "MRUtilityKit.generated.h"
DECLARE_LOG_CATEGORY_EXTERN(LogMRUK, Log, All);
UENUM(BlueprintType)
enum class EMRUKInitStatus : uint8
{
/// Not Initialized.
None,
/// Is busy Initializing.
Busy,
/// Has finished Initializing.
Complete,
/// Failed to initialize.
Failed,
};
UENUM(BlueprintType)
enum class EMRUKCoordModeU : uint8
{
/// The texture coordinates start at 0 and increase by 1 unit every meter.
Metric,
/// The texture coordinates start at 0 and increase by 1 unit every meter but are adjusted to end on a whole number to avoid seams.
MetricSeamless,
/// The texture coordinates are adjusted to the other dimensions to ensure the aspect ratio is maintained.
MaintainAspectRatio,
/// The texture coordinates are adjusted to the other dimensions to ensure the aspect ratio is maintained but are adjusted to end on a whole number to avoid seams.
MaintainAspectRatioSeamless,
/// The texture coordinates range from 0 to 1.
Stretch,
};
UENUM(BlueprintType)
enum class EMRUKCoordModeV : uint8
{
/// The texture coordinates start at 0 and increase by 1 unit every meter.
Metric,
/// The texture coordinates are adjusted to the other dimensions to ensure the aspect ratio is maintained.
MaintainAspectRatio,
/// The texture coordinates range from 0 to 1.
Stretch,
};
UENUM(BlueprintType)
enum class EMRUKSpawnerSelectionMode : uint8
{
/// Pick one at random.
Random,
/// Pick the closest size.
ClosestSize,
/// Used in the AMRUKAnchorActorSpawner to use allow for a custom selection mode.
Custom,
};
UENUM(BlueprintType)
enum class EMRUKSpawnerScalingMode : uint8
{
/// Stretch each axis to exactly match the size of the Plane/Volume.
Stretch,
/// Scale each axis by the same amount to maintain the correct aspect ratio.
UniformScaling,
/// Scale the X and Y axes uniformly but the Z scale can be different.
UniformXYScale,
/// Don't perform any scaling.
NoScaling,
/// Used in the AMRUKAnchorActorSpawner to use allow for a custom scaling.
Custom,
};
UENUM(BlueprintType)
enum class EMRUKAlignMode : uint8
{
/// Do not perform any alignment
None,
/// Align the bottom of the bounding boxes and center the rest
Default,
/// Center the bounding box in the anchor bounding box
CenterOnCenter,
/// Align the bottom of the bounding boxes and center the rest
BottomOnBottom,
/// Align the top of the bounding boxes and center the rest
TopOnTop,
/// Align the left of the bounding boxes and center the rest
LeftOnLeft,
/// Align the right of the bounding boxes and center the rest
RightOnRight,
/// Align the front of the bounding boxes and center the rest
FrontOnFront,
/// Align the back of the bounding boxes and center the rest
BackOnBack,
/// Align the top to the bottom of the anchor bounding box and center the rest
BottomOnTop,
/// Align the bottom to the top of the anchor bounding box and center the rest
TopOnBottom,
/// Align the left to the right of the anchor bounding box and center the rest
LeftOnRight,
/// Align the right to the left of the anchor bounding box and center the rest
RightOnLeft,
/// Align the front to the back of the anchor bounding box and center the rest
FrontOnBack,
/// Align the back to the front of the anchor bounding box and center the rest
BackOnFront,
/// Use custom alignment mode
Custom,
};
/**
* This enum is used to specify the component type, scene anchors can either have plane or volume components associated with them or both.
*/
UENUM(meta = (Bitflags, UseEnumValuesAsMaskValuesInEditor = "true"))
enum class EMRUKComponentType
{
/// No component type.
None = 0 UMETA(Hidden),
/// Plane component type.
Plane = 1 << 0,
/// Volume component type.
Volume = 1 << 1,
/// Mesh component type.
Mesh = 1 << 2,
/// All component types.
All = Plane | Volume | Mesh UMETA(Hidden),
};
ENUM_CLASS_FLAGS(EMRUKComponentType);
/**
* Describes a Raycast hit in the MRUK (Mixed Reality Utility Kit). This structure is created by the AMRUKAnchor::Raycast and AMRUKAnchor::RaycastAll methods. You can read the position where the raycast hit, the normal of the surface that was hit, and the distance from the origin to the raycast hit position.
*/
USTRUCT(BlueprintType)
struct MRUTILITYKIT_API FMRUKHit
{
GENERATED_BODY()
/**
* The position where the raycast hit.
*/
UPROPERTY(BlueprintReadOnly, Category = "MR Utility Kit")
FVector HitPosition = FVector::ZeroVector;
/**
* The normal of the surface that was hit.
*/
UPROPERTY(BlueprintReadOnly, Category = "MR Utility Kit")
FVector HitNormal = FVector::ZeroVector;
/**
* The distance between the origin of the ray to the hit position.
*/
UPROPERTY(BlueprintReadOnly, Category = "MR Utility Kit")
float HitDistance = 0.0f;
};
/**
* Label filter to use in MRUK (Mixed Reality Utility Kit). You can use this to filter anchors by their labels.
* use the IncludedLabels and ExcludedLabels list to specify which labels to include and exclude.
*/
USTRUCT(BlueprintType)
struct MRUTILITYKIT_API FMRUKLabelFilter
{
GENERATED_BODY()
/**
* If included labels is not empty then the anchor must have at
* least one of the labels in this list.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
TArray<FString> IncludedLabels;
/**
* Anchors with any of the labels in this exclusion list
* will be ignored.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
TArray<FString> ExcludedLabels;
/**
* Enum flags representing component types to include, by default include all component types.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit", meta = (Bitmask, BitmaskEnum = "EMRUKComponentType"))
int32 ComponentTypes = static_cast<int32>(EMRUKComponentType::All);
/**
* Check if the labels pass the given label filter
* @param Labels The labels to check.
* @return Whether the filter passes or not.
*/
bool PassesFilter(const TArray<FString>& Labels) const;
};
/**
* Represents a configuration for adjusting the UV texture coordinates of a plane.
*
* It contains properties to specify an offset and scale to be applied to the UV texture coordinates.
*/
USTRUCT(BlueprintType)
struct MRUTILITYKIT_API FMRUKPlaneUV
{
GENERATED_BODY()
/**
* Offset applied to the UV texture coordinates.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
FVector2D Offset = FVector2D::ZeroVector;
/**
* Scale applied to the UV texture coordinates.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
FVector2D Scale = FVector2D::UnitVector;
};
/**
* Texture coordinate modes for MRUK (Mixed Reality Utility Kit). You can use this to specify the texture coordinate mode for the U and V directions.
*/
USTRUCT(BlueprintType)
struct MRUTILITYKIT_API FMRUKTexCoordModes
{
GENERATED_BODY()
/**
* Texture Coordinate mode for the U direction.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
EMRUKCoordModeU U = EMRUKCoordModeU::Metric;
/**
* Texture Coordinate mode for the V direction.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
EMRUKCoordModeV V = EMRUKCoordModeV::Metric;
};
/**
* This struct represents a configuration for spawning an actor in the scene.
*
* It contains properties to specify the class of the actor to spawn, whether to match the aspect ratio of the volume,
* whether to calculate the facing direction of the actor, and what scaling and alignment modes to apply to the actor.
*/
USTRUCT(BlueprintType)
struct MRUTILITYKIT_API FMRUKSpawnActor
{
GENERATED_BODY()
/**
* The class of actor to spawn.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
TSubclassOf<AActor> Actor;
/**
* When match aspect ratio is enabled then the actor will be rotated
* to try and match the aspect ratio of the volume as closely as possible.
* This is most useful for long and thin volumes, keep this disabled for
* objects with an aspect ratio close to 1:1. Only applies to volumes.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
bool MatchAspectRatio = false;
/**
* When calculate facing direction is enabled the actor will be rotated to
* face away from the closest wall. If match aspect ratio is also enabled
* then that will take precedence and it will be constrained to a choice
* between 2 directions only. Only applies to volumes.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
bool CalculateFacingDirection = false;
/**
* Set what scaling mode to apply to the actor. By default the actor will
* be stretched to fit the size of the plane/volume. But in some cases
* this may not be desirable and can be customized here.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
EMRUKSpawnerScalingMode ScalingMode = EMRUKSpawnerScalingMode::Stretch;
/**
* Set what alignment mode to apply to the actor. By default the actor will
* be aligned that its bounding box matches the one from the anchor.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
EMRUKAlignMode AlignMode = EMRUKAlignMode::Default;
};
/**
* This enum is used to specify the fallback behaviour when spawning an scene actor.
* Specify whether to fallback to a procedural mesh or not.
*/
UENUM(BlueprintType)
enum class EMRUKFallbackToProceduralOverwrite : uint8
{
/// Don't override the fallback to procedural standard behaviour.
Default,
/// Fallback to a procedural mesh.
Fallback,
/// Don't fallback to a procedural mesh.
NoFallback,
};
/**
* Holds a configuration for spawning a group of actors.
*
* It contains properties to specify a list of actors to choose from, the selection mode when multiple actors are specified,
* and whether to fall back to spawning a procedural mesh if no actor class has been specified for this label.
*/
USTRUCT(BlueprintType)
struct MRUTILITYKIT_API FMRUKSpawnGroup
{
GENERATED_BODY()
/**
* List of actors to choose from, multiple actors can be specified and
* the selection criteria will be determined by the SelectionMode option.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
TArray<FMRUKSpawnActor> Actors;
/**
* Set the selection mode when multiple different actors are specified.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
EMRUKSpawnerSelectionMode SelectionMode = EMRUKSpawnerSelectionMode::Random;
/**
* Control if there should happen a fallback to spawning a procedural mesh
* in case no actor class has been specified for this label. The global
* fallback behaviour can be specified in the AMRUKAnchorActorSpawner.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
EMRUKFallbackToProceduralOverwrite FallbackToProcedural = EMRUKFallbackToProceduralOverwrite::Default;
};
/**
* Implements the settings for the MRUtilityKit plugin. This is Unreal specific and not part of the MR Utility Kit library.
*/
UCLASS(config = Game, defaultconfig)
class MRUTILITYKIT_API UMRUKSettings : public UObject
{
GENERATED_BODY()
public:
UMRUKSettings(const FObjectInitializer& obj);
/**
* 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(config, EditAnywhere, Category = "MR Utility Kit")
bool EnableWorldLock = true;
};
/**
* MRUK (Mixed Reality Utility Kit) labels. These are the labels that are used by the MR Utility Kit library.
* Those labels are used to identify the different types of objects in the scene, such as walls, floors, etc.
*
* Furthermore you also use those labels to filter, for queries and other tools such as the Raycast and RaycastAll methods.
*/
struct MRUTILITYKIT_API FMRUKLabels
{
static const FString Floor;
static const FString WallFace;
static const FString InvisibleWallFace;
static const FString Ceiling;
static const FString DoorFrame;
static const FString WindowFrame;
static const FString Couch;
static const FString Table;
static const FString Screen;
static const FString Bed;
static const FString Lamp;
static const FString Plant;
static const FString Storage;
static const FString WallArt;
static const FString GlobalMesh;
static const FString Other;
};
/**
* This spawnmode controls how the MR Utility Kit handles spawning actors in the scene, either for all rooms, only for the current room or not at all.
*/
UENUM(BlueprintType)
enum class EMRUKSpawnMode : uint8
{
/// Do not spawn anything on loading a scene or rooms.
None = 0,
/// Will only take the current room into account. This enables legacy single room behaviour. Keep in mind that if your
/// experience loads multiple rooms and you use that mode the behaviour might be undefined.
CurrentRoomOnly,
/// Spawn in every room and keep on spawning whenever a new room was discovered.
AllRooms
};
/**
* UE Module interface impelmentation
*/
class FMRUKModule : public IModuleInterface
{
public:
/** IModuleInterface implementation */
virtual void StartupModule() override;
virtual void ShutdownModule() override;
};

View File

@@ -0,0 +1,281 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#pragma once
#include "GameFramework/Actor.h"
#include "Dom/JsonObject.h"
#include "MRUtilityKitAnchorActorSpawner.h"
#include "OculusXRAnchorTypes.h"
#include "ProceduralMeshComponent.h"
#include "MRUtilityKitAnchor.generated.h"
class AMRUKRoom;
class UMRUKAnchorData;
/**
* Represents an anchor in the Mixed Reality Utility Kit. This combines an Unreal actor with the scene anchor.
* The actor is placed at the position of the anchor and the actor's rotation is set to match the rotation of the anchor.
* Provides functions to check if a position is inside the volume or plane of the anchor, raycast against the anchor, etc...
* @see https://developer.oculus.com/documentation/unreal/unreal-spatial-anchors/
* for more information about anchors in the Mixed Reality Utility Kit.
*/
UCLASS(ClassGroup = MRUtilityKit, meta = (DisplayName = "MR Utility Kit Anchor"))
class MRUTILITYKIT_API AMRUKAnchor : public AActor
{
GENERATED_BODY()
public:
/**
* The space handle of this anchor
*/
UPROPERTY(VisibleInstanceOnly, Transient, BlueprintReadOnly, Category = "MR Utility Kit")
FOculusXRUInt64 SpaceHandle;
/**
* The anchors UUID
*/
UPROPERTY(VisibleInstanceOnly, Transient, BlueprintReadOnly, Category = "MR Utility Kit")
FOculusXRUUID AnchorUUID;
/**
* The semantic classification of the anchor, also sometimes refered to as labels for short.
* This can be for example FLOOR, COUCH, TABLE, SCREEN, BED, LAMP, etc...
*/
UPROPERTY(VisibleInstanceOnly, Transient, BlueprintReadOnly, Category = "MR Utility Kit")
TArray<FString> SemanticClassifications;
/**
* If the anchor has a plane attached to it, this represents the bounds of that plane in
* local coordinate space.
*/
UPROPERTY(VisibleInstanceOnly, Transient, BlueprintReadOnly, Category = "MR Utility Kit")
FBox2D PlaneBounds{ ForceInit };
/**
* If the anchor has a plane attached to it, this represents the boundary of it in
* local coordinate space. For rectangular boundaries this will be the same as the
* PlaneBounds.
*/
UPROPERTY(VisibleInstanceOnly, Transient, BlueprintReadOnly, Category = "MR Utility Kit")
TArray<FVector2D> PlaneBoundary2D;
/**
* If the anchor has a volume attached to it, this represents the bounds of that volume in
* local coordinate space.
*/
UPROPERTY(VisibleInstanceOnly, Transient, BlueprintReadOnly, Category = "MR Utility Kit")
FBox VolumeBounds{ ForceInit };
/**
* Procedural mesh that is generated from the anchor geometry.
*/
UPROPERTY(VisibleInstanceOnly, Transient, BlueprintReadWrite, Category = "MR Utility Kit")
TObjectPtr<UProceduralMeshComponent> ProceduralMeshComponent;
/**
* Pointer to the parent anchor, e.g. if this is a door or window frame the parent will
* be a wall. If this is a screen it could have a desk parent.
*/
UPROPERTY(VisibleInstanceOnly, Transient, BlueprintReadOnly, Category = "MR Utility Kit")
TObjectPtr<AMRUKAnchor> ParentAnchor;
/**
* Array of all children attached to it, e.g. if this is a wall, it could have an array
* of door/window frames. If this is a desk it could have an array of screens on it.
*/
UPROPERTY(VisibleInstanceOnly, Transient, BlueprintReadOnly, Category = "MR Utility Kit")
TArray<TObjectPtr<AMRUKAnchor>> ChildAnchors;
/**
* The room this anchor is placed in.
*/
UPROPERTY(VisibleInstanceOnly, Transient, BlueprintReadOnly, Category = "MR Utility Kit")
TObjectPtr<AMRUKRoom> Room;
/**
* Check if a 2D position is within the boundary of the plane. The position should be in
* the local coordinate system NOT world coordinates.
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
bool IsPositionInBoundary(const FVector2D& Position);
/**
* Generate a uniform random position within the boundary of the plane.
* @return The random position in local coordinate space.
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
FVector GenerateRandomPositionOnPlane();
/**
* Generate a uniform random position within the boundary of the plane from a random stream.
* @param RandomStream A random generator used to generate the position on the plane.
* @return The random position in local coordinate space.
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
FVector GenerateRandomPositionOnPlaneFromStream(const FRandomStream& RandomStream);
/**
* Cast a ray and return the closest hit against the volume and plane bounds.
* @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 OutHit The closest hit.
* @param ComponentTypes The component types to include in the raycast.
* @return Whether the ray hit anything
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
bool Raycast(const FVector& Origin, const FVector& Direction, float MaxDist, FMRUKHit& OutHit, UPARAM(meta = (Bitmask, BitmaskEnum = "EMRUKComponentType")) int32 ComponentTypes = 7 /* EMRUKComponentType::All */);
static_assert(static_cast<int32>(EMRUKComponentType::All) == 7, "If this changes, please update the hardcoded default parameter in the Raycast function above");
/**
* Cast a ray and collect hits against the volume and plane bounds. 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 OutHits The hits the ray collected.
* @param ComponentTypes The component types to include in the raycast.
* @return Whether the ray hit anything
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
bool RaycastAll(const FVector& Origin, const FVector& Direction, float MaxDist, TArray<FMRUKHit>& OutHits, UPARAM(meta = (Bitmask, BitmaskEnum = "EMRUKComponentType")) int32 ComponentTypes = 7 /* EMRUKComponentType::All */);
static_assert(static_cast<int32>(EMRUKComponentType::All) == 7, "If this changes, please update the hardcoded default parameter in the RaycastAll function above");
/**
* Attach a procedural mesh to the anchor. The mesh will match the size, position and shape of the volume and/or plane
* if they are set.
* @param PlaneUVAdjustments Scale and offset to apply to the UV texture coordinates. If more than one is specified
* then multiple UV texture coordinates are created (up to 4) and adjustments applied to
* each. This can be left empty in which case a single set of UV texture coordinates are
* created in the range 0 to 1 for the plane.
* @param CutHoleLabels Labels for which the generated mesh should have holes. Only works with planes.
* @param GenerateCollision Whether to generate collision geometry or not
* @param ProceduralMaterial Material to use on the procedural generated mesh.
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit", meta = (AutoCreateRefTerm = "PlaneUVAdjustments", DeprecatedFunction, DeprecationMessage = "Use GenerateProceduralMesh instead."))
void AttachProceduralMesh(TArray<FMRUKPlaneUV> PlaneUVAdjustments, const TArray<FString>& CutHoleLabels, bool GenerateCollision = true, UMaterialInterface* ProceduralMaterial = nullptr);
/**
* Generate a procedural mesh for the anchor. The mesh will match the size, position and shape of the volume and/or plane
* if they are set.
* @param ProceduralMesh The procedural mesh component that should be used to store the generated mesh.
* @param PlaneUVAdjustments Scale and offset to apply to the UV texture coordinates. If more than one is specified
* then multiple UV texture coordinates are created (up to 4) and adjustments applied to
* each. This can be left empty in which case a single set of UV texture coordinates are
* created in the range 0 to 1 for the plane.
* @param CutHoleLabels Labels for which the generated mesh should have holes. Only works with planes.
* @param GenerateCollision Whether to generate collision geometry or not
* @param Offset A offset to make the procedural mesh slightly bigger or smaller than the anchors volume/plane.
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit", meta = (AutoCreateRefTerm = "PlaneUVAdjustments"))
void GenerateProceduralAnchorMesh(UProceduralMeshComponent* ProceduralMesh, const TArray<FMRUKPlaneUV>& PlaneUVAdjustments, const TArray<FString>& CutHoleLabels, bool PreferVolume = false, bool GenerateCollision = true, double Offset = 0.0);
/**
* Check if the anchor has the given label.
* @param Label The label to check.
* @return Whether the anchor has the given label.
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
bool HasLabel(const FString& Label) const;
/**
* Check if the anchor has any of the given labels.
* @param Labels The labels to check.
* @return Whether the anchor has any of the given labels.
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
bool HasAnyLabel(const TArray<FString>& Labels) const;
/**
* Check if the anchor passes the given label filter
* @param LabelFilter The labels to check.
* @return Whether the anchor has any of the given labels.
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
bool PassesLabelFilter(const FMRUKLabelFilter& LabelFilter) const;
/**
* Calculate the closest surface position on this anchor.
* @param TestPosition The position in world space for which the closes surface position should be obtained.
* @param OutSurfacePosition The closest surface position
* @return The distance between TestPosition and OutSurfacePosition
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
double GetClosestSurfacePosition(const FVector& TestPosition, FVector& OutSurfacePosition);
/**
* Checks if the given position is on or inside the volume bounds.
* Floor, ceiling and wall anchors will be excluded from the search.
* @param Position 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")
bool IsPositionInVolumeBounds(const FVector& Position, bool TestVerticalBounds = true, double Tolerance = 0.0);
/**
* Gets a natural “forward” direction for anchors; for planes, this is always Z-forward.
* For volumes, its the X/Y cardinal axis that aligns best with the normal of the closest wall.
* @return The forward facing direction.
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
FVector GetFacingDirection() const;
/**
* Spawn a mesh on the position of this anchor.
* The actor should have Z as up, Y as right and X as forward.
* @param ActorClass The Class to spawn at the anchors position.
* @param MatchAspectRatio If true the actor will be rotated to best match the aspect ratio of the volume (applies to volumes only).
* @param CalculateFacingDirection If true then actor will be rotated to face away from the closest wall (applies to volumes only).
* @param ScalingMode Sets how to scale the actor to fit the size of the volume/plane.
* @return The spawned actor or null if nothing was spawned.
*/
UFUNCTION(BlueprintCallable, meta = (DeprecatedFunction, DeprecationMessage = "Use AMRUKAnchorActorSpawner instead."), Category = "MR Utility Kit")
AActor* SpawnInterior(const TSubclassOf<class AActor>& ActorClass, bool MatchAspectRatio = false, bool CalculateFacingDirection = false, EMRUKSpawnerScalingMode ScalingMode = EMRUKSpawnerScalingMode::Stretch);
public:
AMRUKAnchor(const FObjectInitializer& ObjectInitializer);
/**
* Load the anchor from a MRUKAnchorData. This is used to load or update the anchor from device or from a JSON file.
*
* @param AnchorData The data to load from.
* @return true if the anchor was loaded successfully.
* @return false if the anchor could not be loaded.
*/
bool LoadFromData(UMRUKAnchorData* AnchorData);
/**
* Attach a procedural mesh to the anchor. The mesh will match the size, position and shape of the volume and/or plane.
*
* @param CutHoleLabels Labels for which the generated mesh should have holes. Only works with planes. Example values: "WindowFrame", "DoorFrame".
* @param GenerateCollision Whether to generate collision geometry or not.
* @param ProceduralMaterial Material to use on the procedural generated mesh.
*/
void AttachProceduralMesh(const TArray<FString>& CutHoleLabels = {}, bool GenerateCollision = true, UMaterialInterface* ProceduralMaterial = nullptr);
TSharedRef<FJsonObject> JsonSerialize();
protected:
void EndPlay(EEndPlayReason::Type Reason) override;
private:
bool RayCastPlane(const FRay& LocalRay, float MaxDist, FMRUKHit& OutHit);
bool RayCastVolume(const FRay& LocalRay, float MaxDist, FMRUKHit& OutHit);
struct TriangulatedMeshCache
{
TArray<FVector2D> Vertices;
TArray<int32> Triangles;
TArray<float> Areas;
float TotalArea;
void Clear();
};
UPROPERTY()
AActor* Interior = nullptr;
TOptional<TriangulatedMeshCache> CachedMesh;
};

View File

@@ -0,0 +1,322 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#pragma once
#include "GameFramework/Actor.h"
#include "MRUtilityKit.h"
#include "MRUtilityKitAnchorActorSpawner.generated.h"
extern const FName GMRUK_PROCEDURAL_ANCHOR_MESH_TAG;
class AMRUKAnchor;
/**
* Spawns meshes on anchor positions.
* If the out of the box functionality doesn't match your goals the AnchorActorSpawner provides way to inject
* custom spawning logic into every step of it's spawning process by overwriting certain functions.
* For this please take a look at SpawnAnchorActorsForRoom(), SpawnAnchorActorForLabel(), and SpawnAnchorActor().
*/
UCLASS(ClassGroup = MRUtilityKit, meta = (DisplayName = "MR Utility Kit Anchor Actor Spawner"))
class MRUTILITYKIT_API AMRUKAnchorActorSpawner : public AActor
{
GENERATED_BODY()
public:
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnInteriorSpawned, AMRUKRoom*, Room);
/**
* Event that gets fired when the interior spawner finished spawning actors.
*/
UPROPERTY(BlueprintAssignable, Category = "MR Utility Kit")
FOnInteriorSpawned OnActorsSpawned;
/**
* Seed to use for the random generator that decideds wich actor class to
* spawn if there a given multiple for a label.
* negative values will have the effect to initialize the random generator
* to a random seed.
*/
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
int AnchorRandomSpawnSeed = -1;
/**
* Whether actors should be spawned automatically after the mixed reality
* utility kit has been initialized. This should not be changed after the scene has been loaded.
*/
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
EMRUKSpawnMode SpawnMode = EMRUKSpawnMode::CurrentRoomOnly;
/**
* Material to use when falling back to procedural material.
*/
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
UMaterialInterface* ProceduralMaterial = nullptr;
/**
* Whether or not the spawner should fallback to procedural meshes in case no actor
* class has been defined for a label. This behaviour can be overwritten on the label
* basis in SpawnGroups.
*/
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
bool ShouldFallbackToProcedural = true;
/**
* Labels for which holes should be created in the parents plane mesh.
* E.g. if holes are needed in the walls where the windows and doors are, specify DOOR_FRAME and WINDOW_FRAME.
*/
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
TArray<FString> CutHoleLabels;
/**
* A map of Actor classes to spawn for the given label.
*/
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
TMap<FString, FMRUKSpawnGroup> SpawnGroups{
{ FMRUKLabels::Bed, {} },
{ FMRUKLabels::Ceiling, {} },
{ FMRUKLabels::Couch, {} },
{ FMRUKLabels::DoorFrame, {} },
{ FMRUKLabels::Floor, {} },
{ FMRUKLabels::Lamp, {} },
{ FMRUKLabels::Plant, {} },
{ FMRUKLabels::Screen, {} },
{ FMRUKLabels::Storage, {} },
{ FMRUKLabels::Table, {} },
{ FMRUKLabels::WallArt, {} },
{ FMRUKLabels::WallFace, {} },
{ FMRUKLabels::InvisibleWallFace, { {}, EMRUKSpawnerSelectionMode::Random, EMRUKFallbackToProceduralOverwrite::NoFallback } },
{ FMRUKLabels::WindowFrame, {} },
{ FMRUKLabels::Other, {} },
};
/**
* Spawns the meshes for the given labels above on the anchor positions in each room.
* There might be multiple actor classes for a give label. If thats the case a actor class will be chosen radomly.
* The seed for this random generator can be set by AnchorRandomSpawnSeed.
* This function will be called automatically after the mixed reality utility kit initialized unless
* the option SpawnOnStart is set to false.
* If there is no actor class specified for a label then a procedural mesh matching the anchors volume and plane
* will be generated.
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
void SpawnActors(AMRUKRoom* Room);
/**
* Return all spawned actors from the give room.
* @param Room The room from which the actors should be returned
* @param Actors The spawned actors.
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
void GetSpawnedActorsByRoom(AMRUKRoom* Room, TArray<AActor*>& Actors);
/**
* Return all spawned actors from all rooms.
* @param Actors The spawned actors.
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
void GetSpawnedActors(TArray<AActor*>& Actors);
protected:
/**
* This method gets called by the AnchorActorSpawner when it wants to spawn actors and procedural meshes in the room.
* It's possible to overwrite this function in Blueprint or C++ to implement custom spawning logic.
* The protected methods in the AnchorActorSpawner contain helper functions which can be useful when implementing
* a custom spawning logic. When implementing a custom spawning logic you may want to use SpawnAnchorActor() to spawn
* the actual actor and take care of it's orientation and scaling to match the anchors bounds.
* @param Room The room to spawn actors for.
* @param RandomStream A random stream to be used with the random selection mode.
* @return A list of all spawned actors.
*/
UFUNCTION(BlueprintNativeEvent, Category = "MR Utility Kit")
TArray<AActor*> SpawnAnchorActorsInRoom(AMRUKRoom* Room, const FRandomStream& RandomStream);
virtual TArray<AActor*> SpawnAnchorActorsInRoom_Implementation(AMRUKRoom* Room, const FRandomStream& RandomStream);
/**
* This method gets called by the default implementation of the SpawnAnchorActorsInRoom() for every label that should spawn a actor.
* By overwriting this function it is possible to inject custom spawning logic for actors on a per label basis.
* When implementing a custom spawning logic you may want to use SpawnAnchorActor() to spawn the actual actor and take care of it's
* orientation and scaling to match the anchors bounds.
* @param Anchor The anchor to spawn a actor for.
* @param Label The label to spawn a actor for.
* @param SpawnGroup Information on which actor should be spawned.
* @param RandomStream A random stream for implementing the random selection logic.
* @return The spawned actor.
*/
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "MR Utility Kit")
AActor* SpawnAnchorActorForLabel(AMRUKAnchor* Anchor, const FString& Label, const FMRUKSpawnGroup& SpawnGroup, const FRandomStream& RandomStream);
virtual AActor* SpawnAnchorActorForLabel_Implementation(AMRUKAnchor* Anchor, const FString& Label, const FMRUKSpawnGroup& SpawnGroup, const FRandomStream& RandomStream);
/**
* This method gets called by the default implementation of SpawnAnchorActorForLabel() to spawn the anchor and orient and scale
* it correct to the given anchor. If you are planning to implement a custom spawning logic you likely want to use this function
* in the end to actually spawn the actor as it takes care of orientation and scaling of the actor with regards to the anchor bounds.
* @param Anchor The anchor to spawn the actor for.
* @param SpawnActor Information on which actor should be spawned.
* @return The spawned actor.
*/
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "MR Utility Kit")
AActor* SpawnAnchorActor(AMRUKAnchor* Anchor, const FMRUKSpawnActor& SpawnActor);
virtual AActor* SpawnAnchorActor_Implementation(AMRUKAnchor* Anchor, const FMRUKSpawnActor& SpawnActor);
/**
* Override this method to inject custom scaling logic into the orientation process of an actor. The scale that this method returns
* gets used to scale the actor that will be spawned.
* @param Anchor The anchor for which the actor gets spawned.
* @param SpawnedActor The actor that gets spawned.
* @param StretchedScale The scale that would need to be applied to the actor to make it match with the bounding box of the anchor.
* In case it's a plane anchor only the X and Y component of the scale are relevant.
* @return The scale that should be applied to the actor. In case it's a plane anchor only the X and Y component are relevant.
*/
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "MR Utility Kit")
FVector ComputeCustomScaling(AMRUKAnchor* Anchor, AActor* SpawnedActor, const FVector& StretchedScale);
virtual FVector ComputeCustomScaling_Implementation(AMRUKAnchor* Anchor, AActor* SpawnedActor, const FVector& StretchedScale);
/**
* Override this method to inject custom actor selection logic. This will be called for every actor that gets spawned by the AMRUKAnchorActorSpawner.
* @param Anchor The anchor for which a actor should be spawned
* @param SpawnGroup The group of actors that can be used for decision making.
* @param RandomStream A random stream to randomize outputs if necessary.
* @param OutSpawnActor The actor which should be spawned.
* @return Whether the selection process was successful or not.
*/
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "MR Utility Kit")
bool SelectSpawnActorCustom(AMRUKAnchor* Anchor, const FMRUKSpawnGroup& SpawnGroup, const FRandomStream& RandomStream, FMRUKSpawnActor& OutSpawnActor);
virtual bool SelectSpawnActorCustom_Implementation(AMRUKAnchor* Anchor, const FMRUKSpawnGroup& SpawnGroup, const FRandomStream& RandomStream, FMRUKSpawnActor& OutSpawnActor);
/**
* Override this method to inject custom scaling logic into the orientation process of an actor. The scale that this method returns
* gets used to scale the actor that will be spawned.
* @param Anchor The anchor for which the actor gets spawned.
* @param Actor The actor that gets spawned.
* @param ChildBounds the rotated bounding box of the actor that should be spawned. For planes only X and Y components are relevant.
* @param Scale The scale that will be applied to the actor that will be spawned in place of the anchor. For planes only X and Y components are relevant.
* @return The offset that should be applied to the actor. In case it's a plane anchor only the X and Y component are relevant.
*/
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "MR Utility Kit")
FVector ComputeCustomAlign(AMRUKAnchor* Anchor, AActor* Actor, const FBox& ChildBounds, const FVector& Scale);
virtual FVector ComputeCustomAlign_Implementation(AMRUKAnchor* Anchor, AActor* Actor, const FBox& ChildBounds, const FVector& Scale);
/**
* Check if for the given SpawnGroup a procedural mesh should be spawned.
* @param SpawnGroup The spawn group to check
* @return Whether a procedural mesh should be spawned or not
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
bool ShouldAnchorFallbackToProceduralMesh(const FMRUKSpawnGroup& SpawnGroup) const;
/**
* Check if there should be spawned a actor for the given label. This function may return false in case
* the spawner should fallback to a procedural mesh.
* @param Anchor The anchor where the actor should be spawned
* @param Label The label of the anchor
* @param OutSpawnGroup Will be set in case a actor should be spawned
* @return Whether or not a actor should be spawned for the anchor
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
bool ShouldSpawnActorForAnchor(AMRUKAnchor* Anchor, const FString& Label, FMRUKSpawnGroup& OutSpawnGroup) const;
/**
* Spawn a procedural mesh for all walls if no wall actor is given to the spawner.
* This will take care of generating seamless UVs for the walls.
* @param Room The room to spawn in.
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
TArray<AActor*> SpawnProceduralMeshesOnWallsIfNoWallActorGiven(AMRUKRoom* Room);
/**
* Spawn a procedural mesh for the floor if no floor actor is given to the spawner.
* @param Room The room to spawn in.
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
AActor* SpawnProceduralMeshOnFloorIfNoFloorActorGiven(AMRUKRoom* Room);
/**
* Spawn a procedural mesh for the ceiling if no ceiling actor is given to the spawner.
* @param Room The room to spawn in.
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
AActor* SpawnProceduralMeshOnCeilingIfNoCeilingActorGiven(AMRUKRoom* Room);
/**
* Spawn a procedural mesh for the given anchor if the settings on the AnchorActorSpawner say so.
* @param Anchor The anchor for which the procedural mesh should be spawned
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
AActor* SpawnProceduralMeshForAnchorIfNeeded(AMRUKAnchor* Anchor);
/**
* Spawn procedural meshes for every anchor that needs them. Including walls, ceiling and floor.
* The method determines if procedural mesh should be spawned or not based on the settings of the
* AnchorActorSpawner.
* @param Room The room to spawn in.
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
TArray<AActor*> SpawnProceduralMeshesInRoom(AMRUKRoom* Room);
/**
* Select the SpawnActor based on the size that matches best the anchor bounds.
* @param Anchor The anchor for which a actor should be spawned.
* @param SpawnGroup The spawn group.
* @param OutSpawnActor The found spawn actor.
* @return True if a SpawnActor could be found. Otherwise, false.
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
bool SelectSpawnActorClosestSize(AMRUKAnchor* Anchor, const FMRUKSpawnGroup& SpawnGroup, FMRUKSpawnActor& OutSpawnActor);
/**
* Select the SpawnActor randomly
* @param SpawnGroup The spawn group.
* @param RandomStream The random stream to use for the random selection.
* @param OutSpawnActor The found spawn actor.
* @return True if a SpawnActor could be found. Otherwise, false.
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
bool SelectSpawnActorRandom(const FMRUKSpawnGroup& SpawnGroup, const FRandomStream& RandomStream, FMRUKSpawnActor& OutSpawnActor);
/**
* Select a SpawnActor from the SpawnGroup with respect to the given selection mode in SpawnGroup.
* @param Anchor The anchor for which the actor should be spawned.
* @param SpawnGroup The spawn group.
* @param RandomStream The random stream
* @param OutSpawnActor The found spawn actor
* @return True if a spawn actor has been found. Otherwise, false.
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
bool SelectSpawnActorFromSpawnGroup(AMRUKAnchor* Anchor, const FMRUKSpawnGroup& SpawnGroup, const FRandomStream& RandomStream, FMRUKSpawnActor& OutSpawnActor);
/**
* Orient and scale the given actor to the anchors plane or volume bounds.
* @param Anchor The anchor
* @param Actor The actor which should be oriented and scaled to the given anchor.
* @param ScalingMode The scaling mode that should be used when doing the matching.
* @param bCalculateFacingDirection Whether or not the facing direction of the anchor should be calculated and used for the orientation process.
* @param bMatchAspectRatio Whether or not the aspect ratio of the anchor should be matched.
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
void AttachAndFitActorToAnchor(AMRUKAnchor* Anchor, AActor* Actor, EMRUKSpawnerScalingMode ScalingMode, EMRUKAlignMode AlignMode, bool bCalculateFacingDirection, bool bMatchAspectRatio);
void BeginPlay() override;
UFUNCTION()
void OnRoomCreated(AMRUKRoom* Room);
UFUNCTION()
void OnRoomUpdated(AMRUKRoom* Room);
UFUNCTION()
void OnRoomRemoved(AMRUKRoom* Room);
UFUNCTION()
void RemoveActors(AMRUKRoom* Room);
private:
// Room UUID to spawned actors in this room
TMap<AMRUKRoom*, TArray<AActor*>> SpawnedActors;
int32 LastSeed = -1;
};

View File

@@ -0,0 +1,173 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#pragma once
#include "Kismet/BlueprintFunctionLibrary.h"
#include "Kismet/BlueprintAsyncActionBase.h"
#include "OculusXRAnchorTypes.h"
#include "MRUtilityKitBPLibrary.generated.h"
USTRUCT(BlueprintType)
struct FMRUKMeshSegment
{
GENERATED_BODY()
TArray<FVector> Positions;
TArray<int32> Indices;
};
/**
* Load the scene async from device.
*/
UCLASS()
class MRUTILITYKIT_API UMRUKLoadFromDevice : public UBlueprintAsyncActionBase
{
GENERATED_BODY()
public:
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FMRUKLoaded);
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit", meta = (WorldContext = "WorldContext", BlueprintInternalUseOnly = "true"
))
static UMRUKLoadFromDevice* LoadSceneFromDeviceAsync(const UObject* WorldContext
);
virtual void Activate() override;
UPROPERTY(BlueprintAssignable)
FMRUKLoaded Success;
UPROPERTY(BlueprintAssignable)
FMRUKLoaded Failure;
private:
UFUNCTION(CallInEditor)
void OnSceneLoaded(bool Succeeded);
TWeakObjectPtr<UWorld> World = nullptr;
};
/**
* Mixed Reality Utility Kit Blueprint Function Library.
* See functions for further information.
*/
UCLASS()
class MRUTILITYKIT_API UMRUKBPLibrary : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
/**
* Load the global mesh from the device.
* @param SpaceHandle Space handle of the room.
* @param OutProceduralMesh Procedural mesh to load the triangle data in.
* @param LoadCollision Whether to generate collision or not.
* @param WorldContext Context of the world.
* @return Whether the load was successful or not.
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit", meta = (WorldContext = "WorldContext"))
static bool LoadGlobalMeshFromDevice(FOculusXRUInt64 SpaceHandle, UProceduralMeshComponent* OutProceduralMesh, bool LoadCollision, const UObject* WorldContext);
/**
* Load the global mesh from a JSON string.
* @param JsonString The string containing the JSON.
* @param AnchorUUID Anchor UUID of the room
* @param OutProceduralMesh Procedural mesh to load the triangle data in.
* @param LoadCollision Whether to generate collision or not
* @return Whether the load was successful or not.
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
static bool LoadGlobalMeshFromJsonString(const FString& JsonString, FOculusXRUUID AnchorUUID, UProceduralMeshComponent* OutProceduralMesh, bool LoadCollision);
/**
* (Re)Calculate Normals and Tangents of the given procedural mesh.
* @param Mesh The procedural mesh.
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
static void RecalculateProceduralMeshAndTangents(class UProceduralMeshComponent* Mesh);
/**
* Check if the current Unreal Engine is the fork of Meta.
* @return Whether its the fork or not.
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "MR Utility Kit")
static bool IsUnrealEngineMetaFork();
/**
* Compute the centroid of a polygon that is defined by the points.
* The centroid may be outside of the polygon in case the polygon is non convex.
* @param PolygonPoints Points that define the polygon.
* @return The centroid.
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "MR Utility Kit")
static FVector2D ComputeCentroid(const TArray<FVector2D>& PolygonPoints);
/**
* In Unreal Engine, scale is always applied in the local space to avoid any skew.
* This means that if you have a component which has a 90 degree rotation and is scaled, or any of its
* children are scaled then the scale axes will not be applied as you would expect. This is can make it
* very awkward to work with when trying to scale the actors to fit within the scene volumes. To work around
* this problem, this function will attempt to adjust the scale axes recursively to match the expected behaviour.
* This will only work reliably if the rotations involved are 90 degrees, if they are not then it will pick the closest axis.
* @param SceneComponent The component where the scale should be set
* @param UnRotatedScale The scale you would like to have without considering any rotations
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
static void SetScaleRecursivelyAdjustingForRotation(USceneComponent* SceneComponent, const FVector& UnRotatedScale);
/**
* Compute the direction that faces away from the closest wall of the given anchor.
* @param Anchor The anchor for which the direction should be computed.
* @param OutCardinalAxisIndex The index of the computed cardinal axis. Can be either 0, 1, 2 or 3
* @param ExcludedAxes Axes to exclude in the computation. Can contain 0, 1, 2, 3
* @return The direction
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
static FVector ComputeDirectionAwayFromClosestWall(const AMRUKAnchor* Anchor, int& OutCardinalAxisIndex, const TArray<int> ExcludedAxes);
/**
* Construct a 2D texture from a render target.
* @param RenderTarget2D The render target from which the texture should be created.
* @param Outer The (optional) outer object for the created texture.
* @param TexName Name for the new texture.
* @return The newly created texture.
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
static UTexture2D* ConstructTexture2D(UTextureRenderTarget2D* RenderTarget2D, UObject* Outer, const FString& TexName);
/**
* Extract a column from a matrix.
* @param Matrix The matrix to use.
* @param Index The column index.
* @return The column of the matrix.
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "MR Utility Kit")
static FLinearColor GetMatrixColumn(const FMatrix& Matrix, int32 Index);
/**
* Compute a grid by taking into account the room box geometry. E.g. create evenly spaced points on ceiling, floor and walls.
* @param Room The room to use
* @param MaxPointsCount The maximum number of points
* @param PointsPerUnitX The density of points on the X axis
* @param PointsPerUnitY The density of points on the Y axis
* @param bIncludeFloor Whether or not to include the floor
* @param bIncludeCeiling Whether or not to include the ceiling
* @param bIncludeWalls Whether or not to include the walls
* @return The computed points
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
static TArray<FVector> ComputeRoomBoxGrid(const AMRUKRoom* Room, int32 MaxPointsCount, double PointsPerUnitX = 1.0, double PointsPerUnitY = 1.0);
/**
* Create mesh segments from the given mesh. This can be used for creating a destructible mesh system.
* @param MeshPositions The mesh positions that should be segmented
* @param MeshIndices The mesh indices that should be segmented
* @param SegmentationPoints A set of points that should be used to calculate the segments
* @param ReservedMin Reserved space from the lower part of the bound box
* @param ReservedMax Reserved space from the upper part of the bounding box
* @param OutSegments The segmented meshes that have been created from the given mesh
* @param OutReservedSegment
*/
static void CreateMeshSegmentation(const TArray<FVector>& MeshPositions, const TArray<uint32>& MeshIndices,
const TArray<FVector>& SegmentationPoints, const FVector& ReservedMin, const FVector& ReservedMax,
TArray<FMRUKMeshSegment>& OutSegments, FMRUKMeshSegment& OutReservedSegment);
};

View File

@@ -0,0 +1,72 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#pragma once
#include "CoreMinimal.h"
#include "Components/StaticMeshComponent.h"
#include "MRUtilityKitBlobShadowComponent.generated.h"
/**
* Adds a blob shadow below the actor.
* The blob shadow will position and resize itself automatically during runtime.
*/
UCLASS(ClassGroup = MRUtilityKit, Blueprintable, BlueprintType, meta = (BlueprintSpawnableComponent, DisplayName = "MR Utility Kit Blob Shadow Component"))
class MRUTILITYKIT_API UMRUKBlobShadowComponent : public UStaticMeshComponent
{
GENERATED_BODY()
public:
/**
* Controls the look of the blob shadow corners (0 = squared corners, 1 = rounded corners).
*/
UPROPERTY(Category = "MR Utility Kit|Aspect", EditAnywhere, BlueprintReadWrite, meta = (UIMin = "0", UIMax = "1"))
float Roundness = 1.0f;
/**
* Controls the look of the blob shadow alpha (0 = fully opaque, 1 = gradient from the center).
*/
UPROPERTY(Category = "MR Utility Kit|Aspect", EditAnywhere, BlueprintReadWrite, meta = (UIMin = "0", UIMax = "1"))
float Gradient = 0.544f;
/**
* Controls the curve of the blob shadow alpha gradient (only available if Gradient > 0).
*/
UPROPERTY(Category = "MR Utility Kit|Aspect", EditAnywhere, BlueprintReadWrite, meta = (EditCondition = "Gradient > 0"))
float GradientPower = 3.0f;
/**
* Increase or decrease the calculated blob shadow size by a fixed amount.
*/
UPROPERTY(Category = "MR Utility Kit", EditAnywhere, BlueprintReadWrite)
float ExtraExtent = -10.0f;
/**
* Maximum distance the actor can be away from the ground until the blob shadow is not shown anymore.
*/
UPROPERTY(Category = "MR Utility Kit", EditAnywhere, BlueprintReadWrite)
float MaxVerticalDistance = 100.f;
/**
* Distance from the ground until the blob shadow starts to fade.
*/
UPROPERTY(Category = "MR Utility Kit", EditAnywhere, BlueprintReadWrite)
float FadeDistance = 20.f;
/**
* Only callable in the editor from the scene, will update the blob shadow size, position and material parameters
* to give a preview how the blob shadow would look like.
*/
UFUNCTION(Category = "MR Utility Kit", CallInEditor)
void UpdatePlaneSizeAndPosition();
public:
UMRUKBlobShadowComponent();
void BeginPlay() override;
void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
void ComputeOwner2DBounds(FVector& Origin, FVector2D& Extent, double& Yaw) const;
protected:
UPROPERTY()
UMaterialInstanceDynamic* DynMaterial;
};

View File

@@ -0,0 +1,139 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#pragma once
#include "GameFramework/Actor.h"
#include "OculusXRRoomLayoutManagerComponent.h"
#include "Dom/JsonValue.h"
#include "OculusXRAnchorsRequests.h"
#include "MRUtilityKitData.generated.h"
/**
* Actor to help finding the localization of actors.
* It gets a list of all anchor queries that should be localized
* and checks every tick if the anchor localization is there.
* When the localization is complete, it will emit the event OnComplete.
*
* NOTE: Normally this should be a async task. However, the anchor data
* can only be queried in game thread.
*/
UCLASS(ClassGroup = MRUtilityKit, Hidden)
class MRUTILITYKIT_API AMRUKLocalizer : public AActor
{
GENERATED_BODY()
public:
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnComplete, bool, Success);
/**
* Event that gets fired when all anchors have been localized.
*/
UPROPERTY(BlueprintAssignable, Category = "MR Utility Kit")
FOnComplete OnComplete;
TArray<class UMRUKAnchorData*> AnchorsData;
AMRUKLocalizer();
void Tick(float DeltaTime) override;
};
/**
* A datastrcture to hold the data of a single anchor. It also provides functions to load the data from device or json.
*/
UCLASS(ClassGroup = MRUtilityKit, Hidden)
class MRUTILITYKIT_API UMRUKAnchorData : public UObject
{
GENERATED_BODY()
public:
FOculusXRAnchorsDiscoverResult SpaceQuery;
FTransform Transform;
FBox2D PlaneBounds;
FBox VolumeBounds;
TArray<FString> SemanticClassifications;
TArray<FVector2D> PlaneBoundary2D;
bool NeedAnchorLocalization = false;
void LoadFromDevice(const FOculusXRAnchorsDiscoverResult& AnchorsDiscoverResult);
void LoadFromJson(const FJsonValue& Value);
};
/**
* Load room data from device.
* When all room data has been loaded, the OnComplete event will be fired.
*/
UCLASS(ClassGroup = MRUtilityKit, Hidden)
class MRUTILITYKIT_API UMRUKRoomData : public UObject
{
GENERATED_BODY()
public:
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnComplete, bool, Success);
/**
* Event that gets fired after all room data has been loaded.
*/
UPROPERTY(BlueprintAssignable, Category = "MR Utility Kit")
FOnComplete OnComplete;
FOculusXRAnchorsDiscoverResult SpaceQuery;
FOculusXRRoomLayout RoomLayout;
UPROPERTY()
TArray<TObjectPtr<UMRUKAnchorData>> AnchorsData;
UPROPERTY()
AMRUKLocalizer* LocalizationActor = nullptr;
class UMRUKSceneData* SceneData;
void LoadFromDevice(UMRUKSceneData* Data, const FOculusXRAnchorsDiscoverResult& AnchorsDiscoverResult);
void LoadFromJson(UMRUKSceneData* Data, const FJsonValue& Value);
private:
void FinishQuery(bool Success);
void RoomDataLoadedComplete(EOculusXRAnchorResult::Type Result);
void RoomDataLoadedIncrementalResults(const TArray<FOculusXRAnchorsDiscoverResult>& DiscoverResults);
UFUNCTION()
void AnchorsInitialized(bool Success);
};
/**
* Load scene data from device.
* When all scene data has been loaded, the OnComplete event will be fired.
*/
UCLASS(ClassGroup = MRUtilityKit, Hidden)
class MRUTILITYKIT_API UMRUKSceneData : public UObject
{
GENERATED_BODY()
public:
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnComplete, bool, Success);
/**
* Event that gets fired after all scene data has been loaded.
*/
UPROPERTY(BlueprintAssignable, Category = "MR Utility Kit")
FOnComplete OnComplete;
UPROPERTY()
TArray<TObjectPtr<UMRUKRoomData>> RoomsData;
void LoadFromDevice();
void LoadFromJson(const FString& Json);
private:
int32 NumRoomsLeftToInitialize = 0;
bool AnyRoomFailed = false;
void FinishQuery(bool Success);
void SceneDataLoadedResult(EOculusXRAnchorResult::Type Result);
void SceneDataLoadedComplete(const TArray<FOculusXRAnchorsDiscoverResult>& DiscoverResults);
UFUNCTION()
void RoomQueryComplete(bool Success);
};

View File

@@ -0,0 +1,91 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#pragma once
#include "GameFramework/Actor.h"
#include "MRUtilityKitDebugComponent.generated.h"
/**
* Various debugging utilities for the scene.
* This component can for example attached to the player pawn. The various methods can
* then be called on input from the pawn.
*/
UCLASS(ClassGroup = MRUtilityKit, meta = (BlueprintSpawnableComponent, DisplayName = "MR Utility Kit Debug Component"))
class MRUTILITYKIT_API UMRUKDebugComponent : public UActorComponent
{
GENERATED_BODY()
public:
/**
* The gizmo to show when visualizing an anchor.
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "MR Utility Kit")
TSubclassOf<AActor> GizmoActorClass = nullptr;
/**
* The text to show when visualizing an anchor.
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "MR Utility Kit")
TSubclassOf<AActor> TextActorClass = nullptr;
/**
* The scale that should be applied to the gizmo before displaying it.
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "MR Utility Kit")
FVector GizmoScale = FVector(0.1);
/**
* The scale that should be applied to the text before displaying it.
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "MR Utility Kit")
FVector TextScale = FVector(0.5);
/**
* Shoot a ray and display the anchors coordinate system and labels that was hit by the ray if any.
* Call HideAnchor() to get rid of the displayed anchor.
* @param Origin The ray origin.
* @param Direction The ray direction.
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
void ShowAnchorAtRayHit(const FVector& Origin, const FVector& Direction);
/**
* Hide the current anchor. This method needs only to be called to hide the anchor
* that was displayed by ShowAnchorAtRayHit().
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
void HideAnchor();
/**
* Shoot a ray and display the anchors space that was hit by the ray if any.
* Call HideAnchorSpace() to get rid of the displayed anchor space.
* @param Origin The ray origin.
* @param Direction The ray direction.
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
void ShowAnchorSpaceAtRayHit(const FVector& Origin, const FVector& Direction);
/**
* Hide the current anchor space actor. This method needs only to be called to hide the
* anchor space that was displayed by ShowAnchorAtRayHit().
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
void HideAnchorSpace();
public:
UMRUKDebugComponent();
void BeginPlay() override;
void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
private:
UPROPERTY()
TObjectPtr<AActor> ActiveGizmoActor = nullptr;
UPROPERTY()
TObjectPtr<AActor> ActiveTextActor = nullptr;
UPROPERTY()
TObjectPtr<AActor> ActiveAnchorSpaceActor = nullptr;
void OrientTextActorToPlayer() const;
};

View File

@@ -0,0 +1,212 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#pragma once
#include "MRUtilityKit.h"
#include "MRUtilityKitBPLibrary.h"
#include "GameFramework/Actor.h"
#include "ProceduralMeshComponent.h"
#include "Tasks/Task.h"
#include "MRUtilityKitDestructibleMesh.generated.h"
/**
* Destructible mesh component. Creates mesh segments for the given geometry.
* The segments will be created async.
* In addition, its possible to define areas that are indestructible.
*/
UCLASS(ClassGroup = MRUtilityKit, Blueprintable, BlueprintType, meta = (BlueprintSpawnableComponent, DisplayName = "MR Utility Kit Destructible Mesh Component"))
class MRUTILITYKIT_API UMRUKDestructibleMeshComponent : public UProceduralMeshComponent
{
GENERATED_BODY()
public:
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnMeshesGenerated);
UMRUKDestructibleMeshComponent(const FObjectInitializer& ObjectInitializer);
UPROPERTY(BlueprintAssignable, Category = "MR Utility Kit")
FOnMeshesGenerated OnMeshesGenerated;
/**
* Material to display on the global mesh
*/
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
UMaterialInterface* GlobalMeshMaterial;
/**
* Area on the top of the mesh that should be indestructible.
* The area is given in centimeters 1.0 == 1 cm.
* -1.0 means no reserved area.
*/
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
double ReservedTop = -1.0;
/**
* Area on the bottom of the mesh that should be indestructible.
* The area is given in centimeters 1.0 == 1 cm
* -1.0 means no reserved area.
*/
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
double ReservedBottom = 30.0;
/**
* Segment the given geometry into smaller chunks. For each chunk a procedural mesh component will be spawned and attached to the owning actor.
* @param MeshPositions Positions of the mesh to segment
* @param MeshIndices Indices of the mesh to segment
* @param SegmentationPoints Points to use to determine the segments.
*/
void SegmentMesh(const TArray<FVector>& MeshPositions, const TArray<uint32>& MeshIndices, const TArray<FVector>& SegmentationPoints);
virtual void BeginPlay() override;
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
private:
UE::Tasks::TTask<TPair<TArray<FMRUKMeshSegment>, FMRUKMeshSegment>> TaskResult;
};
/**
* Actor that constructs a destructible mesh for the given room
* The actor will automatically attach to the global mesh anchor of the given room to take it location and orientation.
*/
UCLASS(ClassGroup = MRUtilityKit, Blueprintable, BlueprintType, meta = (BlueprintSpawnableComponent, DisplayName = "MR Utility Kit Destructible Global Mesh"))
class MRUTILITYKIT_API AMRUKDestructibleGlobalMesh : public AActor
{
GENERATED_BODY()
public:
AMRUKDestructibleGlobalMesh();
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
UMRUKDestructibleMeshComponent* DestructibleMeshComponent;
/**
* Density of mesh segments on the X axis.
* Increase this value to get smaller cracks in the global mesh.
* Decrease this value to get bigger cracks in the global mesh.
*/
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
double PointsPerUnitX = 1.0;
/**
* How many segmentation points should be created at a maximum.
*/
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
int MaxPointsCount = 256;
/**
* Density of mesh segments on the Y axis.
* Increase this value to get smaller cracks in the global mesh.
* Decrease this value to get bigger cracks in the global mesh.
*/
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
double PointsPerUnitY = 1.0;
/**
* Create a destructible mesh for the given room. If the global mesh has not yet been loaded
* this function will attempt to load the global mesh from the device.
* @param Room The room
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
void CreateDestructibleMesh(AMRUKRoom* Room = nullptr);
/**
* Remove a segment of the global mesh. Takes care of not removing the reserved global mesh segment.
* @param Mesh The mesh to remove
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
void RemoveGlobalMeshSegment(UPrimitiveComponent* Mesh);
};
/**
* The destructible global mesh spawner allows to spawn (automatically) destructible global meshes
* when new rooms are created.
* A destructible global mesh is a version of the global mesh that can be destructed during runtime.
* The bulk of the work is performed in UDestructibleMeshComponent. It will perform on start a one time
* preprocessing step to segment the given global mesh into smaller chunks. After that the chunks can be used
* during the game and removed (e.g. with ray casts) at any time during the game simulating as if the global
* mesh would crack down. To enhance the visual quality when cracking the (e.g. removing mesh chunks) global mesh
* a particle system could be used. The system allows to define areas that should be non destructible.
*/
UCLASS(ClassGroup = MRUtilityKit, Blueprintable, BlueprintType, meta = (BlueprintSpawnableComponent, DisplayName = "MR Utility Kit Destructible Global Mesh Spawner"))
class MRUTILITYKIT_API AMRUKDestructibleGlobalMeshSpawner : public AActor
{
GENERATED_BODY()
public:
/**
* Whether destructible meshes should be spawned automatically.
*/
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
EMRUKSpawnMode SpawnMode = EMRUKSpawnMode::CurrentRoomOnly;
/**
* Material to display on the global mesh
*/
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
UMaterialInterface* GlobalMeshMaterial;
/**
* Density of mesh segments on the X axis.
* Increase this value to get smaller cracks in the global mesh.
* Decrease this value to get bigger cracks in the global mesh.
*/
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
double PointsPerUnitX = 1.0;
/**
* How many segmentation points should be created at a maximum.
*/
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
int MaxPointsCount = 256;
/**
* Density of mesh segments on the Y axis.
* Increase this value to get smaller cracks in the global mesh.
* Decrease this value to get bigger cracks in the global mesh.
*/
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
double PointsPerUnitY = 1.0;
/**
* Area on the top of the mesh that should be indestructible.
* The area is given in centimeters 1.0 == 1 cm
* -1.0 means no reserved area.
*/
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
double ReservedTop = -1.0;
/**
* Area on the bottom of the mesh that should be indestructible.
* The area is given in centimeters 1.0 == 1 cm
* -1.0 means no reserved area.
*/
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
double ReservedBottom = 30.0;
void BeginPlay() override;
/**
* Find the destructible mesh that has been spawned for the given room.
* @param Room Room to look for the destructible mesh
* @return The destructible mesh
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
AMRUKDestructibleGlobalMesh* FindDestructibleMeshForRoom(AMRUKRoom* Room);
/**
* Add new destructible mesh for the given room. A mesh will only get spawned if no
* destructible mesh has been spawned for the room yet.
* @param Room The room.
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
AMRUKDestructibleGlobalMesh* AddDestructibleGlobalMesh(AMRUKRoom* Room);
private:
TMap<AMRUKRoom*, AMRUKDestructibleGlobalMesh*> SpawnedMeshes;
UFUNCTION()
void OnRoomCreated(AMRUKRoom* Room);
UFUNCTION()
void OnRoomRemoved(AMRUKRoom* Room);
};

View File

@@ -0,0 +1,197 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#pragma once
#include "MRUtilityKit.h"
#include "GameFramework/Actor.h"
#include "MRUtilityKitDistanceMapGenerator.generated.h"
const FName GMRUK_DISTANCE_MAP_ACTOR_TAG = TEXT("DistanceMapActor");
UENUM(BlueprintType)
enum class EMRUKDistanceMapGenerationMode : uint8
{
// Do not generate a distance map
None,
/// Generate distance map only for the free space. E.g. The floor inside the room.
FreeSpace,
/// Generate the distance map only for the occupied space. E.g. outside the room and inside scene objects.
OccupiedSpace,
/// Generate the distance map for free space and occupied space.
AllSpace,
};
/**
* Generates a distance map that can be used in materials to calculate the distance to various objects.
* This can enable interesting effects. With the distance map you can get the distance from scene objects
* or walls in a material shader.
*
* The Jump Flood Algorithm is used to generate the distance map. This is fast enough to regenerate
* every tick.
*
* To capture a distance map after a room has been loaded call CaptureDistanceMap().
* It will return a captured distance map. In case you already called CaptureDistanceMap()
* you can receive the last captured distance map with GetDistanceMap(). No other setup is required.
*
* This class will create procedural meshes for every anchor to create a mask. These meshes have their
* visibility set to scene capture only. That however means that if you place a scene capture component yourself
* that the meshes will show up in your scene capture component. The actors that have the procedural meshes
* attached are tagged with GMRUK_DISTANCE_MAP_ACTOR_TAG. In case you don't want them to show up in your
* scene capture you can hide them by receiving all these actors with the tag GMRUK_DISTANCE_MAP_ACTOR_TAG
* and add these to the scene captures hidden actors.
*/
UCLASS(ClassGroup = MRUtilityKit, meta = (DisplayName = "MR Utility Kit Distance Map Generator"))
class MRUTILITYKIT_API AMRUKDistanceMapGenerator : public AActor
{
GENERATED_BODY()
public:
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnReady);
UPROPERTY(BlueprintAssignable, Category = "MR Utility Kit")
FOnReady OnReady;
/**
* The mode in which the final distance map should be generated.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
EMRUKDistanceMapGenerationMode DistanceMapGenerationMode = EMRUKDistanceMapGenerationMode::FreeSpace;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
class USceneComponent* Root;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
class USceneCaptureComponent2D* SceneCapture2D;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
EMRUKSpawnMode SpawnMode = EMRUKSpawnMode::CurrentRoomOnly;
/**
* First render target for jump flood algorithm.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
class UCanvasRenderTarget2D* RenderTarget1;
/**
* Second render target for jump flood algorithm.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
class UCanvasRenderTarget2D* RenderTarget2;
/**
* Render target for the final distance map
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
class UCanvasRenderTarget2D* DistanceMapRenderTarget;
/**
* Material to render a mask that gets used to calculate the distance map.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
class UMaterialInterface* MaskMaterial;
/**
* Material that executes a pass of the jump flood algorithm.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
class UMaterialInterface* JFPassMaterial;
/**
* Material to render final distance map
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
class UMaterialInterface* DistanceMapFreeSpaceMaterial;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
class UMaterialInterface* DistanceMapOccupiedSpaceMaterial;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
class UMaterialInterface* DistanceMapAllSpaceMaterial;
/**
* Capture the distance map.
* @return The captured distance map.
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
UTexture* CaptureDistanceMap();
/**
* Create mask meshes for the given room.
* These mask meshes are needed for the distance map to be rendered. It should only be called once before
* CaptureDistanceMap in case the SpawnMode has been set to None.
* The operation that this function executes is expensive. It only needs to be called after the room has been
* created or updated.
* @param Room The room for which the masked meshes should be created.
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
void CreateMaskMeshesForRoom(AMRUKRoom* Room);
/**
* Remove mask meshes for the given room.
* This function should only be executed when SpawnMode is set to None.
* It only needs to be called after a room has been removed.
* @param Room The room for which the masked meshes should be removed.
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
void RemoveMaskMeshesFromRoom(AMRUKRoom* Room);
/**
* Return the captured distance map. Be sure to call CaptureDistanceMap() before
* @return The captured distance map.
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
UTexture* GetDistanceMap() const;
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
UCanvasRenderTarget2D* GetDistanceMapRenderTarget() const;
/**
* Retrieve the view info from the scene capture. This is useful for re projection of
* the distance map in a material.
* @return The view info.
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
FMinimalViewInfo GetSceneCaptureView() const;
public:
AMRUKDistanceMapGenerator();
protected:
void BeginPlay() override;
private:
TMap<AMRUKRoom*, TArray<AActor*>> SpawnedMaskMeshes;
int32 DistanceMapRT = -1;
UPROPERTY()
class UMaterialInstanceDynamic* JFPassMaterialInstance = nullptr;
UPROPERTY()
class UMaterialInstanceDynamic* DistanceMapFreeSpaceMaterialInstance = nullptr;
UPROPERTY()
class UMaterialInstanceDynamic* DistanceMapOccupiedSpaceMaterialInstance = nullptr;
UPROPERTY()
class UMaterialInstanceDynamic* DistanceMapAllSpaceMaterialInstance = nullptr;
UPROPERTY()
UMaterialInterface* SceneObjectMaskMaterial;
UPROPERTY()
UMaterialInterface* FloorMaskMaterial;
void CaptureInitialSceneMask();
void RenderDistanceMap();
UFUNCTION()
void OnRoomCreated(AMRUKRoom* Room);
UFUNCTION()
void OnRoomUpdated(AMRUKRoom* Room);
UFUNCTION()
AActor* CreateMaskMeshOfAnchor(AMRUKAnchor* Anchor);
UFUNCTION()
AActor* UpdateMaskMeshOfAnchor(AMRUKAnchor* Anchor);
};

View File

@@ -0,0 +1,8 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#pragma once
#include "Containers/Array.h"
#include "Math/Vector2D.h"
MRUTILITYKIT_API void MRUKTriangulatePolygon(const TArray<TArray<FVector2f>>& Polygons, TArray<FVector2D>& Vertices, TArray<int32>& Indices);

View File

@@ -0,0 +1,191 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#pragma once
#include "CoreMinimal.h"
#include "Components/SceneComponent.h"
#include "MRUtilityKitGridSliceResizer.generated.h"
UENUM(BlueprintType, Meta = (Bitflags, UseEnumValuesAsMaskValuesInEditor = "true"))
enum class EMRUKScaleCenterMode : uint8
{
None = 0 UMETA(Hidden),
XAxis = 1,
YAxis = 2,
ZAxis = 4,
};
/**
* The GridSliceResizerComponent is a versatile tool designed to maintain the proportions of
* specific areas of 3D meshes while allowing others to stretch during scaling. This component
* should replace the static mesh component, rather than being used in conjunction with it.
*
* The concept of the GridSliceResizerComponent is similar to the popular 9-Slice-Scaling technique
* used in 2D graphics, which keeps the borders of sprites unstretched while the inner rectangle is
* stretched. In essence, the GridSliceResizerComponent is a 27-Slice-Scaler for 3D meshes.
*
* The component operates by dividing the bounding box of a 3D mesh into 27 cuboids, as illustrated below.
* Not all cuboids are visible in this picture. Only the once that are front facing:
*
* +-----+-----------+-----+
* /_____/___________/_____/|
* /_____/___________/_____/||
* / / / /|||
* +-----+-----------+-----+ |||
* | A | B | C |/|||
* +-----+-----------+-----+ |||
* | | | | |||
* | D | E | F | |||
* | | | |/||/
* +-----+-----------+-----+ |/
* | G | H | I | /
* +-----+-----+-----+-----+
*
* The scaling behaviour is as follows (assuming all other faces of the bounding box are divided as the
* front facing one):
*
* Center Cuboid (E): Vertices within this cuboid stretch on two axes (Y, Z).
* Corner Cuboids (A, C, G, I): These cuboids do not stretch on any axis.
* Middle Cuboids (B, H): These cuboids stretch horizontally but not vertically.
* Middle Cuboids (D, F): These cuboids stretch vertically but not horizontally.
*
* The slicing areas are defined by the SlicerPivotOffset and BorderXNegative, BorderXPositive, etc.
* These border values range from 0 to 1 and extend from the mesh's pivot (which may be offset by SlicerPivotOffset)
* to the maximum or minimum of the bounding box's axis.
* If all borders are set to 1, the mesh will stretch like a regular mesh during scaling. If set to 0, no stretching
* will occur. Typically, you'll want the pivot in the middle of the mesh and the borders set to around 0.8.
*
* You can visualize the borders and pivot in the Actor editor preview using bDebugDrawPivot, bDebugDrawBorderX, etc.
*
* This component is only compatible with static meshes that have CPU access enabled. Ensure you enable CPU
* access in the static mesh editor.
*/
UCLASS(ClassGroup = MRUtilityKit, Blueprintable, BlueprintType, meta = (BlueprintSpawnableComponent, DisplayName = "MR Utility Kit Grid Slice Resizer Component"))
class MRUTILITYKIT_API UMRUKGridSliceResizerComponent : public USceneComponent
{
GENERATED_BODY()
public:
/**
* The static mesh to slice. Make sure to enable CPU access on it.
*/
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
class UStaticMesh* Mesh;
/**
* Slice border for the negative X axis.
*/
UPROPERTY(EditAnywhere, Category = "MR Utility Kit|Slices", meta = (ClampMin = "0.0", ClampMax = "1.0"))
double BorderXNegative = 1.0;
/**
* Slice border for the positive X axis.
*/
UPROPERTY(EditAnywhere, Category = "MR Utility Kit|Slices", meta = (ClampMin = "0.0", ClampMax = "1.0"))
double BorderXPositive = 1.0;
/**
* Slice border for the negative Y axis.
*/
UPROPERTY(EditAnywhere, Category = "MR Utility Kit|Slices", meta = (ClampMin = "0.0", ClampMax = "1.0"))
double BorderYNegative = 1.0;
/**
* Slice border for the positive Y axis.
*/
UPROPERTY(EditAnywhere, Category = "MR Utility Kit|Slices", meta = (ClampMin = "0.0", ClampMax = "1.0"))
double BorderYPositive = 1.0;
/**
* Slice border for the negative Z axis.
*/
UPROPERTY(EditAnywhere, Category = "MR Utility Kit|Slices", meta = (ClampMin = "0.0", ClampMax = "1.0"))
double BorderZNegative = 1.0;
/**
* Slice border for the positive Z axis.
*/
UPROPERTY(EditAnywhere, Category = "MR Utility Kit|Slices", meta = (ClampMin = "0.0", ClampMax = "1.0"))
double BorderZPositive = 1.0;
/**
* How much the meshes pivot should be offset when applying the slice borders.
*/
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
FVector SlicerPivotOffset;
/**
* This parameter determines whether the center part of the object should be scaled.
* If set to false, the center vertices will remain stationary. This is particularly useful when
* you want to maintain the proportions of certain geometrical features in the center part, such
* as a doorknob. By keeping the center vertices in place, you can avoid unwanted stretching effects,
* resulting in a more visually appealing outcome.
* However, it's important to note that for a convincing visual effect, the texture applied to the object should also not stretch.
* If you encounter issues with texture stretching, consider adding an additional loop cut.
* This can help maintain the texture's proportions and prevent it from distorting.
* In case the mesh gets scaled down and some of the center vertices fall outside of the scaled down center
* all vertices that are inside the center will be scaled down uniformly.
*/
UPROPERTY(EditAnywhere, Category = "MR Utility Kit", meta = (Bitmask, BitmaskEnum = "/Script/MRUtilityKit.EMRUKScaleCenterMode"))
uint8 ScaleCenterMode = 0;
/**
* Whether or not a collision mesh should be created for the static mesh.
*/
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
bool bGenerateCollision = true;
#if WITH_EDITORONLY_DATA
/**
* Show the pivot of the mesh that gets used for the slice borders.
*/
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
bool bDebugDrawPivot = false;
/**
* Show the slice borders on the X axis.
*/
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
bool bDebugDrawBorderX = false;
/**
* Show the slice borders on the Y axis.
*/
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
bool bDebugDrawBorderY = false;
/**
* Show the slice borders on the Z axis.
*/
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
bool bDebugDrawBorderZ = false;
#endif
/**
* Slice the mesh. This gets automatically called whenever
* the scale of the owning Actor changes.
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
void SliceMesh();
public:
UMRUKGridSliceResizerComponent();
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
protected:
virtual void BeginPlay() override;
virtual void OnRegister() override;
#if WITH_EDITOR
virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;
#endif
private:
friend class FMRUKGridSliceResizerSpec;
UPROPERTY(Transient)
class UProceduralMeshComponent* ProcMesh;
FVector ResizerScale = FVector::OneVector;
};

View File

@@ -0,0 +1,37 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#pragma once
#include "MRUtilityKitRoom.h"
#include "GameFramework/Actor.h"
#include "ProceduralMeshComponent.h"
#include "MRUtilityKitGuardian.generated.h"
/**
* The Guardian is a procedural mesh that is generated from the anchor geometry and has the guardian material applied.
* It is used to show the player where the walls and furniture. It prevents the player from walking into walls or furniture.
* It uses TryGetClosestSurfacePosition to determine if the player is close to the walls or furniture.
* This can be beneficial if your application has a full VR mode.
*/
UCLASS(ClassGroup = MRUtilityKit, meta = (DisplayName = "MR Utility Kit Guardian Actor"))
class MRUTILITYKIT_API AMRUKGuardian : public AActor
{
GENERATED_BODY()
public:
/**
* Procedural mesh that is generated from the anchor geometry and has the guardian material applied.
*/
UPROPERTY(VisibleInstanceOnly, Transient, BlueprintReadOnly, Category = "MR Utility Kit")
TObjectPtr<UProceduralMeshComponent> GuardianMeshComponent;
/**
* Attaches the procedural mesh component to this actor.
* @param GuardianMesh The mesh to attach.
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
void CreateGuardian(UProceduralMeshComponent* GuardianMesh);
public:
AMRUKGuardian(const FObjectInitializer& ObjectInitializer);
};

View File

@@ -0,0 +1,107 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#pragma once
#include "MRUtilityKit.h"
#include "GameFramework/Actor.h"
#include "MRUtilityKitGuardian.h"
#include "MRUtilityKitGuardianSpawner.generated.h"
class AMRUKRoom;
/**
* This class helps with spawning a guardian if the player gets close to any furniture or walls. This is useful if your application has a full VR mode.
* It can spawn a guardian for each room in the scene. It can also spawn a guardian for the current room only.
* For details about the guardian see the AMRUKGuardian class.
*/
UCLASS(ClassGroup = MRUtilityKit, meta = (DisplayName = "MR Utility Kit Guardian"))
class MRUTILITYKIT_API AMRUKGuardianSpawner : public AActor
{
GENERATED_BODY()
public:
AMRUKGuardianSpawner();
/**
* Whether SpawnGuardian() should be called automatically after the mixed reality utility kit
* has been initialized.
*/
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
EMRUKSpawnMode SpawnMode = EMRUKSpawnMode::CurrentRoomOnly;
/**
* How close the camera needs to come to a surface before the guardian appears.
*/
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
double GuardianDistance = 0.75;
/**
* Whether the fading value should be calculated for the shader or not.
If fading is not needed this can save performance.
*/
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
bool EnableFade = true;
/**
* Spawn the guardian. This will get called automatically after the mixed reality utility kit has
* been initialized if SpawnMode is set to something other than None.
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
void SpawnGuardians(AMRUKRoom* Room);
/**
* Set the guardian material to a different one.
* @param Material The guardian material.
*/
UFUNCTION(BlueprintSetter, Category = "MR Utility Kit")
void SetGuardianMaterial(UMaterialInstance* Material);
/**
* Set the density of the grid.
* @param Density The grid density.
*/
UFUNCTION(BlueprintSetter, Category = "MR Utility Kit")
void SetGridDensity(double Density);
public:
void Tick(float DeltaSeconds) override;
protected:
/**
* The material to use for the guardian. It needs to have a scalar parameter Fade
* and a vector parameter WallScale. If this material is not set a default one
* will be used.
*/
UPROPERTY(EditAnywhere, BlueprintSetter = SetGuardianMaterial, Category = "MR Utility Kit")
TObjectPtr<UMaterialInstance> GuardianMaterial = nullptr;
/**
* How dense the grid should be.
*/
UPROPERTY(EditAnywhere, BlueprintSetter = SetGridDensity, Category = "MR Utility Kit")
double GridDensity = 2.0;
void BeginPlay() override;
#if WITH_EDITOR
void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;
#endif
private:
// Room UUID to spawned actors in this room
TMap<AMRUKRoom*, TArray<AMRUKGuardian*>> SpawnedGuardians;
UPROPERTY()
TObjectPtr<UMaterialInstanceDynamic> DynamicGuardianMaterial = nullptr;
UFUNCTION()
void DestroyGuardians(AMRUKRoom* Room);
UFUNCTION()
void OnRoomCreated(AMRUKRoom* Room);
UFUNCTION()
void OnRoomUpdated(AMRUKRoom* Room);
UFUNCTION()
void OnRoomRemoved(AMRUKRoom* Room);
};

View File

@@ -0,0 +1,80 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MRUtilityKitLightDispatcher.generated.h"
/**
* If you want to have highlights from lights over passthrough use this actor to collect all point lights in the scene and send them to the M_Highlights material.
* It lights and sends them to a highlight material, which can be used to achieve highlights over Passthrough.
* The highlight effect is achieved by using a material parameter collection.
* See the PTRL Sample Project for an example of how to use this.
*/
UCLASS(ClassGroup = MRUtilityKit, meta = (DisplayName = "MR Utility Kit Light Dispatcher"))
class MRUTILITYKIT_API AMRUKLightDispatcher : public AActor
{
GENERATED_BODY()
public:
/**
* The material parameter collection in which to fill lights data.
* This parameter collection gets then send to the shader.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
class UMaterialParameterCollection* Collection;
/**
* Whether all point lights should be fetched automatically at BeginPlay().
* The automatic fetching only works for PointLightActors. Actors that have PointLightComponents
* attached to them will not be detected. These should be specified in AdditionalActorsToLookForPointLightComponents.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
bool ShouldFetchPointLightsAtBeginPlay = true;
/**
* List of actor(s) that contain a PointLightComponent that should contribute to the highlight effect.
* Use AddAdditionalPointLightActor to add actors during runtime.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
TArray<AActor*> AdditionalActorsToLookForPointLightComponents;
/**
* PointLightActors to use for the highlight effect (not available if "Fetch Point Lights At Begin Play" is true).
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (EditCondition = "!ShouldFetchPointLightsAtBeginPlay"), Category = "MR Utility Kit")
TArray<class APointLight*> ManualPointLights;
/**
* Add a actor to the AdditionalActorsToLookForPointLightComponents list.
* This should be used during runtime instead of adding actors directly to AdditionalActorsToLookForPointLightComponents.
* @param Actor Actor to add to AdditionalActorsToLookForPointLightComponents.
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
void AddAdditionalPointLightActor(AActor* Actor);
/**
* Only callable in the editor from the scene, will update the linked parameter collection with the info
* of the point lights in the scene (based on the parameters), updating the highlight effect in the process.
* This is meant to preview the effect in the editor.
*/
UFUNCTION(CallInEditor, Category = "MR Utility Kit")
void ForceUpdateCollection();
public:
AMRUKLightDispatcher();
void Tick(float DeltaSeconds) override;
void FillParameterCollection();
protected:
UPROPERTY(Transient)
TArray<class UPointLightComponent*> PointLightComponents;
void BeginPlay() override;
void FillPointLights();
void AddPointLightsFromActor(const AActor* Actor);
};

View File

@@ -0,0 +1,144 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#pragma once
#include "CoreMinimal.h"
#include "MRUtilityKitRoom.h"
#include "GameFramework/Actor.h"
#include "MRUtilityKitPositionGenerator.generated.h"
/**
* Holds the settings which are used for generating random positions. It offers several attributes to be configured, such as
* which room to use, what actor to spawn, scene labels to use and much more. This struct is used by the position generator.
* @see AMRUtilityKitPositionGenerator
*/
USTRUCT(BlueprintType)
struct FMRUKRandomSpawnSettings
{
GENERATED_BODY()
/**
* When the scene data is loaded, this controls what room(s) the position generator will be used in.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
EMRUKRoomFilter RoomFilter = EMRUKRoomFilter::CurrentRoomOnly;
/**
* When an actor instance is reference here, this actor will be moved around.
* If you'd need to spawn new actors, use ActorClass.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
AActor* ActorInstance = nullptr;
/**
* Reference the specific actor class for spawning.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
TSubclassOf<AActor> ActorClass;
/**
* How many instances to spawn at the random generated position per room.
* Note: If using an ActorInstance this property is ignored
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
int SpawnAmount = 8;
/**
* Maximum number of times to attempt spawning/moving an object before giving up.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
int MaxIterations = 1000;
/**
* The type of surface by which to limit the generation.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
EMRUKSpawnLocation SpawnLocations = EMRUKSpawnLocation::Floating;
/**
* The labels to include or exclude.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
FMRUKLabelFilter Labels;
/**
* If enabled then the spawn position will be checked to make sure there is no overlap with physics colliders including themselves.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
bool CheckOverlaps = true;
/**
* Required free space for the object.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
float OverrideBounds = -1;
/**
* The CollisionChannel to use.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
TEnumAsByte<ECollisionChannel> CollisionChannel = ECC_WorldStatic;
/**
* The clearance distance required in front of the surface in order for it to be considered a valid spawn position.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
float SurfaceClearanceDistance = 0.1f;
};
/**
* Position generator that can be used to generate random positions on the surface in a specific room or any room.
*
* It contains methods to generate random positions on the surface of a given spawn location,
* while ensuring that the generated positions are at least `MinDistanceToEdge` away from any edges,
* if it should run on start when MRUK initializes and follow the other settings specified in `SpawnSettings`.
*/
UCLASS()
class MRUTILITYKIT_API AMRUtilityKitPositionGenerator : public AActor
{
GENERATED_BODY()
public:
static bool CanSpawnBox(const UWorld* World, const FBox& Box, const FVector& SpawnPosition, const FQuat& SpawnRotation, const FCollisionQueryParams& QueryParams, ECollisionChannel CollisionChannel);
/**
* Generates a set of random positions on the surface of a given spawn location, while ensuring that the generated positions
* are at least `MinDistanceToEdge` away from any edges and follow the other settings specified in `SpawnSettings`.
* @param OutTransforms An array of transforms representing the generated positions.
* @return A boolean value indicating whether valid positions were found. If no valid positions could be found, `OutTransforms` will be empty.
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
bool GenerateRandomPositionsOnSurface(TArray<FTransform>& OutTransforms);
/**
* Generates a set of random positions on the surface of a given spawn location, while ensuring that the generated positions
* are at least `MinDistanceToEdge` away from any edges and follow the other settings specified in `SpawnSettings` in the
* give room.
* @param Room The room where the positions should be generated in.
* @param OutTransforms An array of transforms representing the generated positions.
* @return A boolean value indicating whether valid positions were found. If no valid positions could be found, `OutTransforms` will be empty.
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
bool GenerateRandomPositionsOnSurfaceInRoom(AMRUKRoom* Room, TArray<FTransform>& OutTransforms);
/**
* Whether GenerateRandomPositionsOnSurface() should be called automatically after the mixed reality utility kit has been initialized
*/
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
bool RunOnStart = true;
/**
* Settings that should be used when generating random positions.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MR Utility Kit")
FMRUKRandomSpawnSettings RandomSpawnSettings;
protected:
virtual void BeginPlay() override;
private:
virtual UWorld* GetTickableGameObjectWorld() const { return GetWorld(); }
UFUNCTION()
void SceneLoaded(bool Success);
};

View File

@@ -0,0 +1,481 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#pragma once
#include "GameFramework/Actor.h"
#include "Dom/JsonObject.h"
#include "MRUtilityKit.h"
#include "OculusXRAnchorTypes.h"
#include "MRUtilityKitRoom.generated.h"
class UMRUKRoomData;
UENUM(BlueprintType)
enum class EMRUKSpawnLocation : uint8
{
Floating UMETA(DisplayName = "Floating"), // Spawn somewhere floating in the free space within the room
AnySurface UMETA(DisplayName = "Any surface"), // Spawn on any surface (i.e. a combination of all 3 options below)
VerticalSurfaces UMETA(DisplayName = "Vertical surfaces"), // Spawn only on vertical surfaces such as walls, windows, wall art, doors, etc...
OnTopOfSurface UMETA(DisplayName = "On top of surfaces"), // Spawn on surfaces facing upwards such as ground, top of tables, beds, couches, etc...
HangingDown UMETA(DisplayName = "Hanging down") // Spawn on surfaces facing downwards such as the ceiling
};
enum class EMRUKBoxSide : uint8
{
XPos,
XNeg,
YPos,
YNeg,
ZPos,
ZNeg,
};
UENUM(BlueprintType)
enum class EMRUKRoomFilter : uint8
{
None,
CurrentRoomOnly,
AllRooms
};
/**
* Method to use when determining the position and rotation for the best pose.
*/
UENUM(BlueprintType)
enum class EMRUKPositioningMethod : uint8
{
/**
* Center the object on the surface.
*/
Center = 0,
/**
* Snap the object to edge which is closest to the user.
*/
Edge,
/**
* Use the location where the ray hit the object as the location.
* The rotation is dependent on the objects shape. For example for walls
* the hit normal from the raycast will be used. For floors the rotation
* will be towards the user and for volumes that got hit on the top the
* rotation will be towards the longest edge that is nearest to the player.
*/
Default,
};
/**
* Represents an anchor with its corresponding plane UVs in the Mixed Reality Utility Kit.
*/
USTRUCT(BlueprintType)
struct FMRUKAnchorWithPlaneUVs
{
GENERATED_BODY()
/**
* A readonly reference to the anchor.
*/
UPROPERTY(BlueprintReadOnly, Category = "MR Utility Kit")
TObjectPtr<AMRUKAnchor> Anchor;
/**
* An array of plane UVs that correspond to the anchor.
*/
UPROPERTY(BlueprintReadOnly, Category = "MR Utility Kit")
TArray<FMRUKPlaneUV> PlaneUVs;
};
/**
* Represents a room in the MRUK.
* A room holds (MRUK)Anchors as children for entities such as Desk, Floor, Ceiling, Walls, etc. Those entities are defined with their label.
* It also provides events which will be triggered when an anchor has been added, removed or updated from space setup.
*
* This room class calculates different helper properties such as Outline, Edges, Bounds
* and provides room functions as helpers such as determine if a point in space (XYZ) is inside the room, generating points on surfaces, generate points in room (floating), raycasts and more.
*/
UCLASS(ClassGroup = MRUtilityKit, meta = (DisplayName = "MR Utility Kit Room Actor"))
class MRUTILITYKIT_API AMRUKRoom : public AActor
{
GENERATED_BODY()
public:
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnAnchorUpdated, AMRUKAnchor*, Anchor);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnAnchorCreated, AMRUKAnchor*, Anchor);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnAnchorRemoved, AMRUKAnchor*, Anchor);
/**
* The space handle of this anchor
*/
UPROPERTY(VisibleInstanceOnly, Transient, BlueprintReadOnly, Category = "MR Utility Kit")
FOculusXRUInt64 SpaceHandle;
/**
* The anchors UUID
*/
UPROPERTY(VisibleInstanceOnly, Transient, BlueprintReadOnly, Category = "MR Utility Kit")
FOculusXRUUID AnchorUUID;
/**
* Event that gets fired if a anchor in this room was updated.
* E.g. volume or plane changed.
*/
UPROPERTY(BlueprintAssignable, Category = "MR Utility Kit")
FOnAnchorUpdated OnAnchorUpdated;
/**
* Event that gets fired if a new anchor was created in this room.
*/
UPROPERTY(BlueprintAssignable, Category = "MR Utility Kit")
FOnAnchorCreated OnAnchorCreated;
/**
* Event that gets fired if a anchor gets removed from this room.
*/
UPROPERTY(BlueprintAssignable, Category = "MR Utility Kit")
FOnAnchorRemoved OnAnchorRemoved;
/**
* Bounds of the room.
*/
UPROPERTY(VisibleInstanceOnly, Transient, BlueprintReadOnly, Category = "MR Utility Kit")
FBox RoomBounds;
/**
* Edges of the room.
*/
UPROPERTY(VisibleInstanceOnly, Transient, BlueprintReadOnly, Category = "MR Utility Kit")
TArray<FVector> RoomEdges;
/**
* The floor anchor of this room.
*/
UPROPERTY(VisibleInstanceOnly, Transient, BlueprintReadOnly, Category = "MR Utility Kit")
TObjectPtr<AMRUKAnchor> FloorAnchor;
/**
* The ceiling anchor of this room.
*/
UPROPERTY(VisibleInstanceOnly, Transient, BlueprintReadOnly, Category = "MR Utility Kit")
TObjectPtr<AMRUKAnchor> CeilingAnchor;
/**
* The wall anchors of this room.
*/
UPROPERTY(VisibleInstanceOnly, Transient, BlueprintReadOnly, Category = "MR Utility Kit")
TArray<TObjectPtr<AMRUKAnchor>> WallAnchors;
/**
* The global mesh anchor of this room.
*/
UPROPERTY(VisibleInstanceOnly, Transient, BlueprintReadOnly, Category = "MR Utility Kit")
TObjectPtr<AMRUKAnchor> GlobalMeshAnchor;
/**
* All anchors which are possible to sit on.
*/
UPROPERTY(VisibleInstanceOnly, Transient, BlueprintReadOnly, Category = "MR Utility Kit")
TArray<TObjectPtr<AMRUKAnchor>> SeatAnchors;
/**
* All anchors of this room.
*/
UPROPERTY(VisibleInstanceOnly, Transient, BlueprintReadOnly, Category = "MR Utility Kit")
TArray<TObjectPtr<AMRUKAnchor>> AllAnchors;
/**
* Check whether the position is inside the room or not.
* @param Position The position in world space to check.
* @param TestVerticalBounds Whether the room should be constrained by vertical bounds or not in the check.
* @return Whether the position is inside the room or not.
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
bool IsPositionInRoom(const FVector& Position, bool TestVerticalBounds = true);
/**
* Generate a uniform random position within the room.
* @param OutPosition Contains the randomly generated position.
* @param MinDistanceToSurface The minimum distance between the generated position and the closest surface/volume.
* @param AvoidVolumes If true then the position will not be inside a volume and min distance away from it.
* @return Return true if success otherwise false. If this fails it can be because the min distance to surface is too large.
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
bool GenerateRandomPositionInRoom(FVector& OutPosition, float MinDistanceToSurface = 0.0f, bool AvoidVolumes = false);
/**
* Generate a uniform random position within the room from a random stream.
* @param OutPosition Contains the randomly generated position.
* @param RandomStream A random generator used to generate the position on the plane.
* @param MinDistanceToSurface The minimum distance between the generated position and the closest surface/volume.
* @param AvoidVolumes If true then the position will not be inside a volume and min distance away from it.
* @return Return true if success otherwise false. If this fails it can be because the min distance to surface is too large.
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
bool GenerateRandomPositionInRoomFromStream(FVector& OutPosition, const FRandomStream& RandomStream, float MinDistanceToSurface = 0.0f, bool AvoidVolumes = false);
/**
* Generates a random position on the surface of a given spawn location, while ensuring that the generated position is at least `MinDistanceToEdge` away from any edges. The `LabelFilter` parameter allows you to specify which types of surfaces should be considered for generating the random position.
*
* @param SpawnLocation The location where the random position should be generated.
* @param MinDistanceToEdge The minimum distance from the edge that the generated position must have.
* @param LabelFilter A filter that specifies which types of surfaces should be considered for generating the random position.
* @param OutPosition The generated position.
* @param OutNormal The normal vector of the generated position.
* @return A boolean value indicating whether a valid position was found. If no valid position could be found, both `OutPosition` and `OutNormal` will be set to zero vectors.
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
bool GenerateRandomPositionOnSurface(EMRUKSpawnLocation SpawnLocation, float MinDistanceToEdge, FMRUKLabelFilter LabelFilter, FVector& OutPosition, FVector& OutNormal);
/**
* Cast a ray and return the closest hit anchor
* @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 volume and plane bounds in this room. 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 OutHits The hits the ray collected.
* @param LabelFilter The label filter can be used to include/exclude certain labels from the search.
* @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);
/**
* Clear all anchors from the room.
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
void ClearRoom();
/**
* Check if the room does have any of the labels.
* @param Labels The labels to check.
* @return Whether the label was found in the room.
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
bool DoesRoomHave(const TArray<FString>& Labels);
/**
* Get the position on the surface that is closest to the given position with respect to the distance.
* @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 OutSurfaceDistance The distance between WorldPosition and OutSurfacePosition.
* @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, double& OutSurfaceDistance, const FMRUKLabelFilter& LabelFilter, double MaxDistance = 0.0);
/**
* Checks if the given position is on or inside of any scene volume in the room.
* Floor, ceiling and wall anchors will be excluded from the search.
* @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);
/**
* 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);
/**
* Finds all anchors in this room that have the given label attached.
* @param Label The label to search for.
* @return An array off anchors with the given label.
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
TArray<AMRUKAnchor*> GetAnchorsByLabel(const FString& Label) const;
/**
* Finds the first anchor in this room that has the given label attached.
* @param Label The label to search for.
* @return If found, the Anchor that has the label attached. Otherwise a null pointer.
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
AMRUKAnchor* GetFirstAnchorByLabel(const FString& Label) const;
/**
* 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 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.
* @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);
/**
* Attach a procedural mesh to the walls. This is done at the room level to ensure the UV coordinates
* can be done in a seamless way if desired.
* @param WallTextureCoordinateModes Mode of the wall texture coordinates.
* @param CutHoleLabels Labels for which holes should be cut into the plane meshes
* @param ProceduralMaterial Material to apply on top of the procedural mesh.
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit", meta = (AutoCreateRefTerm = "WallTextureCoordinateModes", DeprecatedFunction, DeprecationMessage = "Use GenerateProceduralMesh instead."))
void AttachProceduralMeshToWalls(const TArray<FMRUKTexCoordModes>& WallTextureCoordinateModes, const TArray<FString>& CutHoleLabels, UMaterialInterface* ProceduralMaterial = nullptr);
/**
* Spawn meshes on the position of the anchors of the 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 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 All spawned interior 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 the 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 wich tells to spawn which actor to a given label.
* @param CutHoleLabels Labels for which the generated mesh should have holes. Only works with planes.
* @param RandomStream A random generator to choose randomly between actor classes if there a multiple for one label.
* @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 All spawned interior 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);
/**
* Check if the given anchor is a wall anchor.
* @param Anchor The anchor to check.
* @return Whether the anchor is a wall anchor or not.
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
bool IsWallAnchor(AMRUKAnchor* Anchor) const;
/**
* Compute the wall mesh texture coordinate adjustments that are needed to generate proper texture coordinates for the walls.
* @param WallTextureCoordinateModes The texture coordinate mode to use for the walls.
* @param OutAnchorsWithPlaneUVs The computed texture coordinate adjustment with the wall anchor.
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
void ComputeWallMeshUVAdjustments(const TArray<FMRUKTexCoordModes>& WallTextureCoordinateModes, TArray<FMRUKAnchorWithPlaneUVs>& OutAnchorsWithPlaneUVs);
/**
* Load the triangle mesh of the global mesh anchor if it's available.
* @param Material The Material to show if the global mesh is visible.
* @return On success true, otherwise false.
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
bool LoadGlobalMeshFromDevice(UMaterialInterface* Material = nullptr);
/**
* Load the triangle mesh of the global mesh anchor. For this function to succeed you need to make
* sure to have a global mesh specified in the JSON file. Not every JSON file has a global mesh in it.
* @param JsonString The string with the JSON data.
* @param Material Material to apply on the global mesh.
* @return On Success true, otherwise false.
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
bool LoadGlobalMeshFromJsonString(const FString& JsonString, UMaterialInterface* Material = nullptr);
/**
* Compute the centroid of the room by taking the points of the floor boundary.
* The centroid may be outside of the room for non convex rooms.
* The Z value determines the height of the resulting vectors and ranges from
* 0 to 1. A Z value of 1 corresponds to the ceiling positions Z, while a Z value
* of 0 corresponds to the floor positions Z. Any value between 0 and 1 will
* interpolate between the two values.
* In case the floor and ceiling anchors haven't been loaded yet a zero vector
* will be returned.
* @param Z Value used for interpolation of Z.
* @return The centroid.
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
FVector ComputeCentroid(double Z = 0.5);
public:
AMRUKRoom(const FObjectInitializer& ObjectInitializer);
void EndPlay(EEndPlayReason::Type Reason) override;
void LoadFromData(UMRUKRoomData* RoomData);
void AttachProceduralMeshToWalls(const TArray<FString>& CutHoleLabels, UMaterialInterface* ProceduralMaterial = nullptr);
void UpdateWorldLock(APawn* Pawn, const FVector& HeadWorldPosition) const;
TSharedRef<FJsonObject> JsonSerialize();
bool Corresponds(UMRUKRoomData* RoomQuery) const;
private:
friend class FMRUKSpec;
AMRUKAnchor* SpawnAnchor();
void InitializeRoom();
void ComputeRoomBounds();
void ComputeAnchorHierarchy();
void ComputeSeats();
void ComputeRoomEdges();
UFUNCTION(CallInEditor)
void AddAnchorToRoom(AMRUKAnchor* Anchor);
class UProceduralMeshComponent* GetOrCreateGlobalMeshProceduralMeshComponent(bool& OutExistedAlready) const;
void SetupGlobalMeshProceduralMeshComponent(UProceduralMeshComponent& ProcMeshComponent, bool ExistedAlready, UMaterialInterface* Material) const;
/**
* Get the list of walls in an order such that each one wall shares an edge with the next
* one in the list.
*/
TArray<TObjectPtr<AMRUKAnchor>> ComputeConnectedWalls() const;
FOculusXRRoomLayout RoomLayout;
UPROPERTY()
AMRUKAnchor* KeyWallAnchor = nullptr;
struct Surface
{
AMRUKAnchor* Anchor;
float UsableArea;
bool IsPlane;
FBox2D Bounds;
EMRUKBoxSide Side;
};
};

View File

@@ -0,0 +1,76 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Engine/DataTable.h"
#include "MRUtilityKitSceneDataProvider.generated.h"
UCLASS(ClassGroup = MRUtilityKit, meta = (DisplayName = "MR Utility Kit Scene Data Provider"))
/*
* This actor is used to provide scene data to the MR Utility Kit when running in editor.
* You can also use it to not load a room from device.
* Use RandomRoom to load a random room from the list of rooms.
*/
class MRUTILITYKIT_API AMRUKSceneDataProvider : public AActor
{
GENERATED_BODY()
public:
/*
* This list holds the rooms that can be loaded, the key is the room type and the value is a data table that contains multiple rooms.
* Roomtypes such as Bedrooms, Livingrooms, etc.
*/
UPROPERTY(EditAnywhere, Category = "MR Utility Kit")
TMap<FString, UDataTable*> Rooms;
/*
* When this is true, a random room will be loaded from the list of rooms.
*/
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "MR Utility Kit")
bool bUseRandomRoom = true;
/*
* When this is true, a random room will be loaded a specific room class, defined in Rooms (Bedrooms, Offices, ..).
*/
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "MR Utility Kit", meta = (EditCondition = "!bUseRandomRoom", EditConditionHides))
bool bUseRandomRoomFromClass = false;
/*
* Use this property to define a specific room class to load, only visible when bUseRandomRoomFromClass is true.
* This can be a room class such as Bedrooms, Offices, ..
*/
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "MR Utility Kit", meta = (EditCondition = "bUseRandomRoomFromClass && !bUseRandomRoom", EditConditionHides))
FString SpecificRoomClass;
/*
* Define a specific room to load, only visible when bUseRandomRoom is false.
*/
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "MR Utility Kit", meta = (EditCondition = "!bUseRandomRoom && !bUseRandomRoomFromClass", EditConditionHides))
FString SpecificRoomName;
/*
* Gets you a room from the list of rooms, if bUseRandomRoom is true, a random room will be returned.
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
void GetRoom(FString& RoomJSON, FString& RoomName);
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
};
USTRUCT(Blueprintable, BlueprintType)
struct FJSONData : public FTableRowBase
{
GENERATED_USTRUCT_BODY()
public:
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "MR Utility Kit")
FString JSON;
};

View File

@@ -0,0 +1,31 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "MRUtilityKitSeatsComponent.generated.h"
/**
* This component gets attached to Anchors which have seats available.
* Seats can be used for example to spawn avatars in the correct locations.
*/
UCLASS(ClassGroup = MRUtilityKit)
class MRUTILITYKIT_API UMRUKSeatsComponent : public UActorComponent
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "MR Utility Kit")
TArray<FTransform> SeatPoses;
/**
* Calculate the seats poses that are available on the actor.
* This gets called automatically after the room has been loaded.
* However, it's okay to call this function again with a different SeatWidth.
* The seat poses will then get recalculated.
* @param SeatWidth The width of each seat.
*/
UFUNCTION(BlueprintCallable, Category = "MR Utility Kit")
void CalculateSeatPoses(double SeatWidth = 60.0);
};

View File

@@ -0,0 +1,204 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#pragma once
#include "Dom/JsonObject.h"
#include "OculusXRAnchorTypes.h"
#include "OculusXRRoomLayoutManagerComponent.h"
TSharedPtr<FJsonValue> MRUKSerialize(const FString& String);
void MRUKDeserialize(const FJsonValue& Value, FString& String);
TSharedPtr<FJsonValue> MRUKSerialize(const FOculusXRUUID& UUID);
void MRUKDeserialize(const FJsonValue& Value, FOculusXRUUID& UUID);
TSharedPtr<FJsonValue> MRUKSerialize(const double& Number);
void MRUKDeserialize(const FJsonValue& Value, double& Number);
TSharedPtr<FJsonValue> MRUKSerialize(const FOculusXRRoomLayout& RoomLayout);
void MRUKDeserialize(const FJsonValue& Value, FOculusXRRoomLayout& RoomLayout);
template <typename T>
TSharedPtr<FJsonValue> MRUKSerialize(const UE::Math::TVector2<T>& Vector)
{
return MakeShareable(new FJsonValueArray({ MakeShareable(new FJsonValueNumber(Vector.X)), MakeShareable(new FJsonValueNumber(Vector.Y)) }));
}
template <typename T>
void MRUKDeserialize(const FJsonValue& Value, UE::Math::TVector2<T>& Vector)
{
if (auto Array = Value.AsArray(); Array.Num() == 2)
{
MRUKDeserialize(*Array[0], Vector.X);
MRUKDeserialize(*Array[1], Vector.Y);
}
else
{
UE_LOG(LogJson, Error, TEXT("Json Array is of length %d (expected 2) when deserializing TVector2"), Array.Num());
Vector = UE::Math::TVector2<T>::ZeroVector;
}
}
template <typename T>
TSharedPtr<FJsonValue> MRUKSerialize(const UE::Math::TVector<T>& Vector)
{
return MakeShareable(new FJsonValueArray({ MakeShareable(new FJsonValueNumber(Vector.X)), MakeShareable(new FJsonValueNumber(Vector.Y)), MakeShareable(new FJsonValueNumber(Vector.Z)) }));
}
template <typename T>
void MRUKDeserialize(const FJsonValue& Value, UE::Math::TVector<T>& Vector)
{
auto Array = Value.AsArray();
if (Array.Num() == 3)
{
MRUKDeserialize(*Array[0], Vector.X);
MRUKDeserialize(*Array[1], Vector.Y);
MRUKDeserialize(*Array[2], Vector.Z);
}
else
{
UE_LOG(LogJson, Error, TEXT("Json Array is of length %d (expected 3) when deserializing TVector"), Array.Num());
Vector = UE::Math::TVector<T>::ZeroVector;
}
}
template <typename T>
TSharedPtr<FJsonValue> MRUKSerialize(const UE::Math::TRotator<T>& Rotation)
{
return MakeShareable(new FJsonValueArray({ MakeShareable(new FJsonValueNumber(Rotation.Pitch)), MakeShareable(new FJsonValueNumber(Rotation.Yaw)), MakeShareable(new FJsonValueNumber(Rotation.Roll)) }));
}
template <typename T>
void MRUKDeserialize(const FJsonValue& Value, UE::Math::TRotator<T>& Rotation)
{
auto Array = Value.AsArray();
if (Array.Num() == 3)
{
MRUKDeserialize(*Array[0], Rotation.Pitch);
MRUKDeserialize(*Array[1], Rotation.Yaw);
MRUKDeserialize(*Array[2], Rotation.Roll);
}
else
{
UE_LOG(LogJson, Error, TEXT("Json Array is of length %d (expected 3) when deserializing TRotator"), Array.Num());
Rotation = UE::Math::TRotator<T>::ZeroRotator;
}
}
template <typename T>
TSharedPtr<FJsonValue> MRUKSerialize(const UE::Math::TBox2<T>& Box)
{
if (Box.bIsValid)
{
const TSharedRef<FJsonObject> JsonObject = MakeShareable(new FJsonObject);
JsonObject->SetField(TEXT("Min"), MRUKSerialize(Box.Min));
JsonObject->SetField(TEXT("Max"), MRUKSerialize(Box.Max));
return MakeShareable(new FJsonValueObject(JsonObject));
}
else
{
return MakeShareable(new FJsonValueNull());
}
}
template <typename T>
void MRUKDeserialize(const FJsonValue& Value, UE::Math::TBox2<T>& Box)
{
if (Value.IsNull())
{
Box.Init();
}
else
{
const auto Object = Value.AsObject();
MRUKDeserialize(*Object->GetField<EJson::None>(TEXT("Min")), Box.Min);
MRUKDeserialize(*Object->GetField<EJson::None>(TEXT("Max")), Box.Max);
Box.bIsValid = true;
}
}
template <typename T>
TSharedPtr<FJsonValue> MRUKSerialize(const UE::Math::TBox<T>& Box)
{
if (Box.IsValid)
{
const TSharedRef<FJsonObject> JsonObject = MakeShareable(new FJsonObject);
JsonObject->SetField(TEXT("Min"), MRUKSerialize(Box.Min));
JsonObject->SetField(TEXT("Max"), MRUKSerialize(Box.Max));
return MakeShareable(new FJsonValueObject(JsonObject));
}
else
{
return MakeShareable(new FJsonValueNull());
}
}
template <typename T>
void MRUKDeserialize(const FJsonValue& Value, UE::Math::TBox<T>& Box)
{
if (Value.IsNull())
{
Box.Init();
}
else
{
const auto Object = Value.AsObject();
MRUKDeserialize(*Object->GetField<EJson::None>(TEXT("Min")), Box.Min);
MRUKDeserialize(*Object->GetField<EJson::None>(TEXT("Max")), Box.Max);
Box.IsValid = 1;
}
}
template <typename T>
TSharedPtr<FJsonValue> MRUKSerialize(const UE::Math::TTransform<T>& Transform)
{
const TSharedRef<FJsonObject> JsonObject = MakeShareable(new FJsonObject);
JsonObject->SetField(TEXT("Translation"), MRUKSerialize(Transform.GetTranslation()));
JsonObject->SetField(TEXT("Rotation"), MRUKSerialize(Transform.Rotator()));
JsonObject->SetField(TEXT("Scale"), MRUKSerialize(Transform.GetScale3D()));
return MakeShareable(new FJsonValueObject(JsonObject));
}
template <typename T>
void MRUKDeserialize(const FJsonValue& Value, UE::Math::TTransform<T>& Transform)
{
const auto Object = Value.AsObject();
UE::Math::TVector<T> Translation;
UE::Math::TRotator<T> Rotation;
UE::Math::TVector<T> Scale;
MRUKDeserialize(*Object->GetField<EJson::None>(TEXT("Translation")), Translation);
MRUKDeserialize(*Object->GetField<EJson::None>(TEXT("Rotation")), Rotation);
MRUKDeserialize(*Object->GetField<EJson::None>(TEXT("Scale")), Scale);
Transform.SetComponents(UE::Math::TQuat<T>(Rotation), Translation, Scale);
}
template <typename T>
TSharedPtr<FJsonValue> MRUKSerialize(const TArray<T>& Array)
{
TArray<TSharedPtr<FJsonValue>> JsonArray;
JsonArray.Reserve(Array.Num());
for (const auto& Item : Array)
{
JsonArray.Add(MRUKSerialize(Item));
}
return MakeShareable(new FJsonValueArray(JsonArray));
}
template <typename T>
void MRUKDeserialize(const FJsonValue& Value, TArray<T>& OutArray)
{
auto Array = Value.AsArray();
OutArray.Empty();
OutArray.Reserve(Array.Num());
for (const auto& Item : Array)
{
T ItemDeserialized;
MRUKDeserialize(*Item, ItemDeserialized);
OutArray.Push(ItemDeserialized);
}
}

View File

@@ -0,0 +1,309 @@
// 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;
};

View File

@@ -0,0 +1,18 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#pragma once
#include "OculusXRTelemetry.h"
namespace MRUKTelemetry
{
using FLoadGuardianMarker = OculusXRTelemetry::TMarker<257237531>;
using FLoadBlobShadowMarker = OculusXRTelemetry::TMarker<257244458>;
using FLoadLightDispatcherMarker = OculusXRTelemetry::TMarker<257234454>;
using FLoadDebugComponentMarker = OculusXRTelemetry::TMarker<257232584>;
using FLoadAnchorActorSpawnerMarker = OculusXRTelemetry::TMarker<257232670>;
using FLoadSceneFromDeviceMarker = OculusXRTelemetry::TMarker<257235234>;
using FLoadSceneFromJsonMarker = OculusXRTelemetry::TMarker<257237876>;
using FLoadGridSliceResizerMarker = OculusXRTelemetry::TMarker<257238248>;
using FLoadDestructibleGlobalMeshSpawner = OculusXRTelemetry::TMarker<257232038>;
} // namespace MRUKTelemetry