282 lines
13 KiB
C++
282 lines
13 KiB
C++
// 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, it’s 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;
|
||
};
|