// 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 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 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 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 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> ChildAnchors; /** * The room this anchor is placed in. */ UPROPERTY(VisibleInstanceOnly, Transient, BlueprintReadOnly, Category = "MR Utility Kit") TObjectPtr 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(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& OutHits, UPARAM(meta = (Bitmask, BitmaskEnum = "EMRUKComponentType")) int32 ComponentTypes = 7 /* EMRUKComponentType::All */); static_assert(static_cast(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 PlaneUVAdjustments, const TArray& 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& PlaneUVAdjustments, const TArray& 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& 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& 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& CutHoleLabels = {}, bool GenerateCollision = true, UMaterialInterface* ProceduralMaterial = nullptr); TSharedRef 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 Vertices; TArray Triangles; TArray Areas; float TotalArea; void Clear(); }; UPROPERTY() AActor* Interior = nullptr; TOptional CachedMesh; };