282 lines
13 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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;
};