Don't want the hasle of seting ut MetaXR on oter computers

This commit is contained in:
Kauri 2025-05-26 23:10:22 +03:00
parent 91567a4ffd
commit 944596b9f7
591 changed files with 2 additions and 200343 deletions

3
.gitignore vendored
View File

@ -13,4 +13,5 @@ AndroidBuild
*.sln
*.suo
*.xcodeproj
*.xcworkspace
*.xcworkspace
Plugins

View File

@ -1,145 +0,0 @@
[CoreRedirects]
;
+PackageRedirects=(OldName=".../OculusVR/...", NewName="/OculusXR/", MatchWildcard=true)
;
+ClassRedirects=(OldName="/Script/OculusHMD.OculusResourceHolder", NewName="/Script/OculusXRHMD.OculusXRResourceHolder")
+ClassRedirects=(OldName="/Script/OculusHMD.OculusPassthroughLayerComponent", NewName="/Script/OculusXRPassthrough.OculusXRPassthroughLayerComponent")
+ClassRedirects=(OldName="/Script/OculusHMD.StereoLayerShapeUserDefined", NewName="/Script/OculusXRPassthrough.OculusXRStereoLayerShapeUserDefined")
+ClassRedirects=(OldName="/Script/OculusHMD.StereoLayerShapeReconstructed", NewName="/Script/OculusXRPassthrough.OculusXRStereoLayerShapeReconstructed")
+ClassRedirects=(OldName="/Script/OculusHMD.OculusHMDRuntimeSettings", NewName="/Script/OculusXRHMD.OculusXRHMDRuntimeSettings")
+ClassRedirects=(OldName="/Script/OculusHMD.OculusEventComponent", NewName="/Script/OculusXRHMD.OculusXREventComponent")
+ClassRedirects=(OldName="/Script/OculusHMD.OculusSceneCaptureCubemap", NewName="/Script/OculusXRHMD.OculusXRSceneCaptureCubemap")
+ClassRedirects=(OldName="/Script/OculusHMD.PassthroughLayerBase", NewName="/Script/OculusXRPassthrough.OculusXRPassthroughLayerBase")
+ClassRedirects=(OldName="/Script/OculusHMD.OculusFunctionLibrary", NewName="/Script/OculusXRHMD.OculusXRFunctionLibrary")
;
+EnumRedirects=(OldName="EOculusXrApi", NewName="/Script/OculusXRHMD.EOculusXRXrApi")
+EnumRedirects=(OldName="EHandTrackingSupport", NewName="/Script/OculusXRHMD.EOculusXRHandTrackingSupport")
+EnumRedirects=(OldName="ETrackedDeviceType", NewName="/Script/OculusXRHMD.EOculusXRTrackedDeviceType")
+EnumRedirects=(OldName="EHandTrackingFrequency", NewName="/Script/OculusXRHMD.EOculusXRHandTrackingFrequency")
+EnumRedirects=(OldName="EColorMapType", NewName="/Script/OculusXRHMD.EOculusXRColorMapType")
+EnumRedirects=(OldName="EPassthroughLayerOrder", NewName="/Script/OculusXRHMD.EOculusXRPassthroughLayerOrder")
+EnumRedirects=(OldName="EOculusDeviceType", NewName="/Script/OculusXRHMD.EOculusXRDeviceType")
+EnumRedirects=(OldName="EColorSpace", NewName="/Script/OculusXRHMD.EOculusXRColorSpace")
+EnumRedirects=(OldName="EBoundaryType", NewName="/Script/OculusXRHMD.EOculusXRBoundaryType")
+EnumRedirects=(OldName="EProcessorPerformanceLevel", NewName="/Script/OculusXRHMD.EOculusXRProcessorPerformanceLevel")
;
+StructRedirects=(OldName="/Script/OculusHMD.GuardianTestResult", NewName="/Script/OculusXRHMD.OculusXRGuardianTestResult")
+StructRedirects=(OldName="/Script/OculusHMD.OculusSplashDesc", NewName="/Script/OculusXRHMD.OculusXRSplashDesc")
+StructRedirects=(OldName="/Script/OculusHMD.HmdUserProfile", NewName="/Script/OculusXRHMD.OculusXRHmdUserProfile")
+StructRedirects=(OldName="/Script/OculusHMD.HmdUserProfileField", NewName="/Script/OculusXRHMD.OculusXRHmdUserProfileField")
;
+ClassRedirects=(OldName="/Script/OculusInput.OculusHandComponent", NewName="/Script/OculusXRInput.OculusXRHandComponent")
+ClassRedirects=(OldName="/Script/OculusInput.OculusMRFunctionLibrary", NewName="/Script/OculusXRInput.OculusXRMRFunctionLibrary")
+ClassRedirects=(OldName="/Script/OculusInput.OculusInputFunctionLibrary", NewName="/Script/OculusXRInput.OculusXRInputFunctionLibrary")
;
+EnumRedirects=(OldName="ETrackingConfidence", NewName="/Script/OculusXRInput.EOculusXRTrackingConfidence")
+EnumRedirects=(OldName="EConfidenceBehavior", NewName="/Script/OculusXRInput.EOculusXRConfidenceBehavior")
+EnumRedirects=(OldName="EOculusHandType", NewName="/Script/OculusXRInput.EOculusXRHandType")
+EnumRedirects=(OldName="EOculusFinger", NewName="/Script/OculusXRInput.EOculusXRFinger")
+EnumRedirects=(OldName="ESystemGestureBehavior", NewName="/Script/OculusXRInput.EOculusXRSystemGestureBehavior")
+EnumRedirects=(OldName="EBone", NewName="/Script/OculusXRInput.EOculusXRBone")
;
+StructRedirects=(OldName="/Script/OculusInput.OculusCapsuleCollider", NewName="/Script/OculusXRInput.OculusXRCapsuleCollider")
;
;
;
;
+ClassRedirects=(OldName="/Script/OculusEditor.OculusHMDRuntimeSettings", NewName="/Script/OculusXREditor.OculusXRHMDRuntimeSettings")
+ClassRedirects=(OldName="/Script/OculusEditor.OculusEditorSettings", NewName="/Script/OculusXREditor.OculusXREditorSettings")
+ClassRedirects=(OldName="/Script/OculusEditor.OculusPlatformToolSettings", NewName="/Script/OculusXREditor.OculusXRPlatformToolSettings")
;
+EnumRedirects=(OldName="EOculusAssetType", NewName="/Script/OculusXREditor.EOculusXRAssetType")
+EnumRedirects=(OldName="EOculusPlatform", NewName="/Script/OculusXREditor.EOculusXRPlatform")
+EnumRedirects=(OldName="EOculusGamepadEmulation", NewName="/Script/OculusXREditor.EOculusXRGamepadEmulation")
;
+StructRedirects=(OldName="/Script/OculusEditor.RedistPackage", NewName="/Script/OculusXREditor.OculusXRRedistPackage")
+StructRedirects=(OldName="/Script/OculusEditor.AssetConfig", NewName="/Script/OculusXREditor.OculusXRAssetConfig")
;
+ClassRedirects=(OldName="/Script/OculusMR.OculusFunctionLibrary", NewName="/Script/OculusMR.OculusXRFunctionLibrary")
+ClassRedirects=(OldName="/Script/OculusMR.OculusMR_Settings", NewName="/Script/OculusMR.OculusXRMR_Settings")
+ClassRedirects=(OldName="/Script/OculusMR.OculusMRFunctionLibrary", NewName="/Script/OculusMR.OculusXRMRFunctionLibrary")
;
+EnumRedirects=(OldName="EOculusMR_CameraDeviceEnum", NewName="/Script/OculusMR.EOculusXRMR_CameraDeviceEnum")
+EnumRedirects=(OldName="EOculusMR_PostProcessEffects", NewName="/Script/OculusMR.EOculusXRMR_PostProcessEffects")
+EnumRedirects=(OldName="EOculusMR_CompositionMethod", NewName="/Script/OculusMR.EOculusXRMR_CompositionMethod")
+EnumRedirects=(OldName="EOculusMR_ClippingReference", NewName="/Script/OculusMR.EOculusXRMR_ClippingReference")
;
+StructRedirects=(OldName="/Script/OculusMR.OculusMR_PlaneMeshTriangle", NewName="/Script/OculusMR.OculusXRMR_PlaneMeshTriangle")
+StructRedirects=(OldName="/Script/OculusMR.TrackedCamera", NewName="/Script/OculusMR.OculusXRTrackedCamera")
;
+EnumRedirects=(OldName="EOculusXRXrApi",ValueChanges=(("LegacyOVRPlugin","OVRPluginOpenXR")))
;
+EnumRedirects=(OldName="ETiledMultiResLevel",NewName="EOculusXRFoveatedRenderingLevel",ValueChanges=(("ETiledMultiResLevel_Off","Off"),("ETiledMultiResLevel_LMSLow","Low"),("ETiledMultiResLevel_LMSMedium","Medium"),("ETiledMultiResLevel_LMSHigh","High"),("ETiledMultiResLevel_LMSHighTop","HighTop")))
+FunctionRedirects=(OldName="GetTiledMultiresLevel",NewName="GetFoveatedRenderingLevel")
+FunctionRedirects=(OldName="SetTiledMultiresLevel",NewName="SetFoveatedRenderingLevel")
+EnumRedirects=(OldName="EFixedFoveatedRenderingLevel",NewName="EOculusXRFoveatedRenderingLevel",ValueChanges=(("FFR_Off","Off"),("FFR_Low","Low"),("FFR_Medium","Medium"),("FFR_High","High"),("FFR_HighTop","HighTop")))
+FunctionRedirects=(OldName="GetFixedFoveatedRenderingLevel",NewName="GetFoveatedRenderingLevel")
+FunctionRedirects=(OldName="SetFixedFoveatedRenderingLevel",NewName="SetFoveatedRenderingLevel")
;
+EnumRedirects=(OldName="/Script/OculusXRHMD.EOculusDeviceType",ValueChanges=(("OculusQuest","OculusQuest_Deprecated"),("Quest_Link","Quest_Link_Deprecated")))
;
; Anchors and Scene redirects
+StructRedirects=(OldName="/Script/OculusAnchors.OculusSpaceQueryInfo", NewName="/Script/OculusXRAnchors.OculusXRSpaceQueryInfo")
+StructRedirects=(OldName="/Script/OculusAnchors.OculusSpaceQueryResult", NewName="/Script/OculusXRAnchors.OculusXRSpaceQueryResult")
+StructRedirects=(OldName="/Script/OculusAnchors.OculusSpaceQueryFilterValues", NewName="/Script/OculusXRAnchors.OculusXRSpaceQueryFilterValues")
+StructRedirects=(OldName="/Script/OculusAnchors.OculusAnchorManager", NewName="/Script/OculusXRAnchors.OculusXRAnchorManager")
+StructRedirects=(OldName="/Script/OculusAnchors.OculusSpatialAnchorManager", NewName="/Script/OculusXRAnchors.OculusXRSpatialAnchorManager")
+StructRedirects=(OldName="/Script/OculusAnchors.OculusRoomLayoutManager", NewName="/Script/OculusXRAnchors.OculusXRRoomLayoutManager")
+StructRedirects=(OldName="/Script/OculusAnchors.OculusAnchors", NewName="/Script/OculusXRAnchors.OculusXRAnchors")
+StructRedirects=(OldName="/Script/OculusAnchors.OculusRoomLayout", NewName="/Script/OculusXRAnchors.OculusXRRoomLayout")
+StructRedirects=(OldName="/Script/OculusScene.OculusSpawnedSceneAnchorProperties", NewName="/Script/OculusXRScene.OculusXRSpawnedSceneAnchorProperties")
+StructRedirects=(OldName="/Script/OculusAnchors.UUID", NewName="/Script/OculusXRAnchors.OculusXRUUID")
+StructRedirects=(OldName="/Script/OculusAnchors.UInt64", NewName="/Script/OculusXRAnchors.OculusXRUInt64")
;
+EnumRedirects=(OldName="EOculusSpaceQueryFilterType", NewName="/Script/OculusXRAnchors.EOculusXRSpaceQueryFilterType")
+EnumRedirects=(OldName="EOculusSpaceStorageLocation", NewName="/Script/OculusXRAnchors.EOculusXRSpaceStorageLocation")
+EnumRedirects=(OldName="EOculusSpaceStoragePersistenceMode", NewName="/Script/OculusXRAnchors.EOculusXRSpaceStoragePersistenceMode")
+EnumRedirects=(OldName="EOculusSpaceComponentType", NewName="/Script/OculusXRAnchors.EOculusXRSpaceComponentType")
+EnumRedirects=(OldName="EOculusLaunchCaptureFlowWhenMissingScene", NewName="/Script/OculusXRScene.EOculusXRLaunchCaptureFlowWhenMissingScene")
;
+ClassRedirects=(OldName="/Script/OculusAnchors.OculusAnchorComponent", NewName="/Script/OculusXRAnchors.OculusXRAnchorComponent")
+ClassRedirects=(OldName="/Script/OculusAnchors.OculusAnchorBPFuctionLibrary", NewName="/Script/OculusXRAnchors.OculusXRAnchorBPFuctionLibrary")
+ClassRedirects=(OldName="/Script/OculusAnchors.OculusAsyncAction_CreateSpatialAnchor", NewName="/Script/OculusXRAnchors.OculusXRAsyncAction_CreateSpatialAnchor")
+ClassRedirects=(OldName="/Script/OculusAnchors.OculusAsyncAction_EraseAnchor", NewName="/Script/OculusXRAnchors.OculusXRAsyncAction_EraseAnchor")
+ClassRedirects=(OldName="/Script/OculusAnchors.OculusAsyncAction_SaveAnchor", NewName="/Script/OculusXRAnchors.OculusXRAsyncAction_SaveAnchor")
+ClassRedirects=(OldName="/Script/OculusAnchors.OculusAsyncAction_QueryAnchors", NewName="/Script/OculusXRAnchors.OculusXRAsyncAction_QueryAnchors")
+ClassRedirects=(OldName="/Script/OculusAnchors.OculusAsyncAction_SetAnchorComponentStatus", NewName="/Script/OculusXRAnchors.OculusXRAsyncAction_SetAnchorComponentStatus")
+ClassRedirects=(OldName="/Script/OculusAnchors.OculusRoomLayoutManagerComponent", NewName="/Script/OculusXRAnchors.OculusXRRoomLayoutManagerComponent")
+ClassRedirects=(OldName="/Script/OculusAnchors.OculusSpatialAnchorComponent", NewName="/Script/OculusXRAnchors.OculusXRSpatialAnchorComponent")
+ClassRedirects=(OldName="/Script/OculusScene.OculusSceneAnchorComponent", NewName="/Script/OculusXRScene.OculusXRSceneAnchorComponent")
+ClassRedirects=(OldName="/Script/OculusScene.OculusSceneActor", NewName="/Script/OculusXRScene.OculusXRSceneActor")
;
+FunctionRedirects=(OldName="OculusAsyncAction_CreateSpatialAnchor.OculusAsyncCreateSpatialAnchor",NewName="OculusXRAsyncAction_CreateSpatialAnchor.OculusXRAsyncCreateSpatialAnchor")
+FunctionRedirects=(OldName="OculusAsyncAction_EraseAnchor.OculusAsyncEraseAnchor",NewName="OculusXRAsyncAction_EraseAnchor.OculusXRAsyncEraseAnchor")
+FunctionRedirects=(OldName="OculusAsyncAction_SaveAnchor.OculusAsyncSaveAnchor",NewName="OculusXRAsyncAction_SaveAnchor.OculusXRAsyncSaveAnchor")
+FunctionRedirects=(OldName="OculusAsyncAction_QueryAnchors.OculusAsyncQueryAnchors",NewName="OculusXRAsyncAction_QueryAnchors.OculusXRAsyncQueryAnchors")
+FunctionRedirects=(OldName="OculusAsyncAction_QueryAnchors.OculusAsyncQueryAnchorsAdvanced",NewName="OculusXRAsyncAction_QueryAnchors.OculusXRAsyncQueryAnchorsAdvanced")
+FunctionRedirects=(OldName="OculusAsyncAction_SetAnchorComponentStatus.OculusAsyncSetAnchorComponentStatus",NewName="OculusXRAsyncAction_SetAnchorComponentStatus.OculusXRAsyncSetAnchorComponentStatus")
;
+ClassRedirects=(OldName="/Script/OculusXRHMD.OculusXRPassthroughLayerComponent", NewName="/Script/OculusXRPassthrough.OculusXRPassthroughLayerComponent")
+ClassRedirects=(OldName="/Script/OculusXRHMD.OculusXRPassthroughLayerBase", NewName="/Script/OculusXRPassthrough.OculusXRPassthroughLayerBase")
+ClassRedirects=(OldName="/Script/OculusXRHMD.OculusXRStereoLayerShapeUserDefined", NewName="/Script/OculusXRPassthrough.OculusXRStereoLayerShapeUserDefined")
+ClassRedirects=(OldName="/Script/OculusXRHMD.OculusXRStereoLayerShapeReconstructed", NewName="/Script/OculusXRPassthrough.OculusXRStereoLayerShapeReconstructed")
; Movement
+EnumRedirects=(OldName="EOculusBodyTrackingMode", NewName="/Script/OculusXRMovement.EOculusXRBodyTrackingMode")
+EnumRedirects=(OldName="EOculusBoneID", NewName="/Script/OculusXRMovement.EOculusXRBoneID")
+EnumRedirects=(OldName="EOculusFaceExpression", NewName="/Script/OculusXRMovement.EOculusXRFaceExpression")
+EnumRedirects=(OldName="EOculusFaceConfidence", NewName="/Script/OculusXRMovement.EOculusXRFaceConfidence")
+EnumRedirects=(OldName="EOculusEye", NewName="/Script/OculusXRMovement.EOculusXREye")
;
+StructRedirects=(OldName="/Script/OculusMovement.OculusBodyJoint", NewName="/Script/OculusXRMovement.OculusXRBodyJoint")
+StructRedirects=(OldName="/Script/OculusMovement.OculusBodyState", NewName="/Script/OculusXRMovement.OculusXRBodyState")
+StructRedirects=(OldName="/Script/OculusMovement.OculusFaceState", NewName="/Script/OculusXRMovement.OculusXRFaceState")
+StructRedirects=(OldName="/Script/OculusMovement.OculusEyeGazeState", NewName="/Script/OculusXRMovement.OculusXREyeGazeState")
+StructRedirects=(OldName="/Script/OculusMovement.OculusEyeGazesState", NewName="/Script/OculusXRMovement.OculusXREyeGazesState")
;
+ClassRedirects=(OldName="/Script/OculusMovement.OculusBodyTrackingComponent", NewName="/Script/OculusXRMovement.OculusXRBodyTrackingComponent")
+ClassRedirects=(OldName="/Script/OculusMovement.OculusEyeTrackingComponent", NewName="/Script/OculusXRMovement.OculusXREyeTrackingComponent")
+ClassRedirects=(OldName="/Script/OculusMovement.OculusFaceTrackingComponent", NewName="/Script/OculusXRMovement.OculusXRFaceTrackingComponent")
+ClassRedirects=(OldName="/Script/OculusMovement.OculusMovementFunctionLibrary", NewName="/Script/OculusXRMovement.OculusXRMovementFunctionLibrary")
; MRUK
+PackageRedirects=(OldName=".../MRUtilityKit/...", NewName="/OculusXR/", MatchWildcard=true)
+ClassRedirects=(OldName="/Script/OculusXRAnchors.OculusXRRoomLayoutManagerComponent", NewName="/Script/OculusXRScene.OculusXRRoomLayoutManagerComponent")
+EnumRedirects=(OldName="/Script/OculusXRHMD.EOculusXROcclusionsMode",ValueChanges=(("HardOcclusions","HardOcclusions_Deprecated")))

View File

@ -1,14 +0,0 @@
[FilterPlugin]
; This section lists additional files which will be packaged along with your plugin. Paths should be listed relative to the root plugin directory, and
; may include "...", "*", and "?" wildcards to match directories, files, and individual characters respectively.
;
; Examples:
; /README.txt
; /Extras/...
; /Binaries/ThirdParty/*.dll
/Config/...
/build.log
/Shaders/...
/Source/Thirdparty/...

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<TpsData xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Name>Oculus Models</Name>
<Location>/Engine/Plugins/Runtime/Oculus/OculusVR/Content/</Location>
<Function>Provide our licensees with models that match actual physical Oculus devices.</Function>
<Eula>https://developer.oculus.com/licenses/art-1.0/</Eula>
<RedistributeTo>
<EndUserGroup>Licensees</EndUserGroup>
<EndUserGroup>Git</EndUserGroup>
<EndUserGroup>P4</EndUserGroup>
</RedistributeTo>
<LicenseFolder>/Engine/Source/ThirdParty/Licenses/OculusModels_License.txt</LicenseFolder>
</TpsData>

View File

@ -1,8 +0,0 @@
Art Attribution License 1.0
Copyright © Facebook Technologies, LLC and its affiliates. All rights reserved.
You may use these images solely for referring to the corresponding product in your video game or VR experience (including manuals for users). Otherwise, you may not use these images, or any trademarks, logos or other intellectual property owned by Facebook Technologies, LLC formerly known as Oculus VR, LLC (“Oculus”), including but not limited to use on merchandise or other product such as clothing, hats, or mugs. Do not use the Oculus images in a way that implies a partnership, sponsorship or endorsement; or features Oculus on materials associated with pornography, illegal activities, or other materials that violate Oculus Terms.
THE IMAGES ARE PROVIDED TO YOU ON AN “AS IS” BASIS AND YOU ARE SOLELY RESPONSIBLE FOR YOUR USE OF THE IMAGES. OCULUS DISCLAIMS ALL WARRANTIES REGARDING THE IMAGES, INCLUDING WARRANTIES OF NON-INFRINGEMENT. OCULUS SHALL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL OR PUNITIVE DAMAGES ARISING FROM OR RELATED TO YOUR USE OF THE IMAGES.
For the avoidance of doubt, this license shall not apply to the Oculus name, trademark or service mark, logo or design.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,184 +0,0 @@
{
"FileVersion": 3,
"Version": 1,
"VersionName": "1.108.0",
"FriendlyName": "Meta XR",
"Description": "Support for Meta Quest headsets and controllers",
"Category": "Virtual Reality",
"CreatedBy": "Meta Platforms, Inc.",
"CreatedByURL": "https://www.meta.com/",
"DocsURL": "https://developer.oculus.com/documentation/unreal/latest/concepts/unreal-engine/",
"MarketplaceURL": "com.epicgames.launcher://ue/marketplace/product/8313d8d7e7cf4e03a33e79eb757bccba",
"SupportURL": "https://forums.oculusvr.com/developer",
"EngineVersion": "5.5.0",
"CanContainContent": true,
"Installed": true,
"SupportedTargetPlatforms": [
"Win64",
"Android"
],
"Modules": [
{
"Name": "OculusXRHMD",
"Type": "Runtime",
"LoadingPhase": "PostConfigInit",
"PlatformAllowList": [
"Win64",
"Android"
]
},
{
"Name": "OculusXRInput",
"Type": "Runtime",
"LoadingPhase": "PostConfigInit",
"PlatformAllowList": [
"Win64",
"Android"
]
},
{
"Name": "OculusXRMR",
"Type": "Runtime",
"LoadingPhase": "PostEngineInit",
"PlatformAllowList": [
"Win64",
"Android"
]
},
{
"Name": "OculusXRAsyncRequest",
"Type": "Runtime",
"LoadingPhase": "PostEngineInit",
"PlatformAllowList": [
"Win64",
"Android"
]
},
{
"Name": "OculusXRAnchors",
"Type": "Runtime",
"LoadingPhase": "PostConfigInit",
"PlatformAllowList": [
"Win64",
"Android"
]
},
{
"Name": "OculusXRScene",
"Type": "Runtime",
"LoadingPhase": "PostConfigInit",
"PlatformAllowList": [
"Win64",
"Android"
]
},
{
"Name": "OculusXRMovement",
"Type": "Runtime",
"LoadingPhase": "PostConfigInit",
"PlatformAllowList": [
"Win64",
"Android"
]
},
{
"Name": "OculusXREyeTracker",
"Type": "Runtime",
"LoadingPhase": "Default",
"PlatformAllowList": [
"Win64",
"Android"
]
},
{
"Name": "OculusXREditor",
"Type": "Editor",
"LoadingPhase": "Default",
"PlatformAllowList": [
"Win64"
]
},
{
"Name": "OculusXRProjectSetupTool",
"Type": "Editor",
"LoadingPhase": "PostEngineInit",
"PlatformAllowList": [
"Win64"
]
},
{
"Name": "OculusXRUncookedOnly",
"Type": "UncookedOnly",
"LoadingPhase": "Default",
"PlatformAllowList": [
"Win64",
"Android"
]
},
{
"Name": "OculusXRPassthrough",
"Type": "Runtime",
"LoadingPhase": "PostConfigInit",
"PlatformAllowList": [
"Win64",
"Android"
]
},
{
"Name": "MRUtilityKit",
"Type": "Runtime",
"LoadingPhase": "PostEngineInit",
"PlatformAllowList": [
"Win64",
"Android"
]
},
{
"Name": "MRUtilityKitEditor",
"Type": "Editor",
"LoadingPhase": "PostEngineInit",
"PlatformAllowList": [
"Win64"
]
},
{
"Name": "OculusXRColocation",
"Type": "Runtime",
"LoadingPhase": "PostConfigInit",
"PlatformAllowList": [
"Win64",
"Android"
]
}
],
"Plugins": [
{
"Name": "XRBase",
"Enabled": true
},
{
"Name": "EnhancedInput",
"Enabled": true
},
{
"Name": "ProceduralMeshComponent",
"Enabled": true
},
{
"Name": "AndroidPermission",
"Enabled": true
},
{
"Name": "LiveLink",
"Enabled": true
},
{
"Name": "OpenXR",
"Enabled": true
},
{
"Name": "PluginBrowser",
"Enabled": true
}
]
}

View File

@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.86465 2.72488C5.30028 3.21852 4.79372 3.82053 4.50835 4.39838C5.8356 3.47884 7.97132 3.47884 7.97132 3.47884V1.63976C7.5949 1.63976 7.11048 1.83837 6.61682 2.15546C5.94779 1.60106 4.95906 1.33325 3.67126 1.33325C3.43219 1.33325 3.23839 1.5391 3.23839 1.79302C3.23839 2.04695 3.43219 2.25279 3.67126 2.25279C4.63759 2.25279 5.36391 2.41453 5.86465 2.72488ZM11.4344 4.39838C11.1547 3.83207 10.6626 3.24255 10.1118 2.75457C10.6132 2.43465 11.3471 2.26781 12.3285 2.26781C12.5676 2.26781 12.7614 2.06197 12.7614 1.80804C12.7614 1.55412 12.5676 1.34827 12.3285 1.34827C11.0312 1.34827 10.0373 1.62007 9.36813 2.18287C8.85994 1.84985 8.35854 1.63976 7.9714 1.63976V3.47884C7.9714 3.47884 10.1651 3.47884 11.4344 4.39838ZM3.38412 8.9829C2.6113 9.06139 1.94842 9.47124 1.42146 10.2087C1.27693 10.411 1.31414 10.6994 1.50458 10.8529C1.69501 11.0064 1.96655 10.9669 2.11108 10.7646C2.48754 10.2378 2.93442 9.94955 3.47668 9.89641C3.5255 10.2108 3.59074 10.5218 3.66893 10.8176C3.04975 11.4584 2.77744 12.295 2.87356 13.2995C2.89774 13.5521 3.11015 13.7361 3.34799 13.7104C3.58583 13.6847 3.75904 13.4591 3.73487 13.2065C3.68274 12.6618 3.77092 12.2017 4.00845 11.8145C4.04671 11.9024 4.08614 11.9862 4.12656 12.0655C4.84022 13.4654 6.03411 14.2559 7.13425 14.6666V5.47118C6.2758 5.47118 4.8132 5.72395 3.95469 6.66547C3.43913 6.45765 3.05212 6.00752 2.77223 5.29429C2.68026 5.05991 2.42681 4.9491 2.20614 5.0468C1.98548 5.14449 1.88115 5.41369 1.97313 5.64806C2.32338 6.5406 2.84513 7.15382 3.52021 7.47015C3.3984 7.90264 3.35966 8.4296 3.38412 8.9829ZM12.5234 9.89641C12.4746 10.2108 12.4093 10.5218 12.3311 10.8176C12.9503 11.4584 13.2226 12.295 13.1265 13.2995C13.1023 13.5521 12.8899 13.7361 12.6521 13.7104C12.4142 13.6847 12.241 13.4591 12.2652 13.2065C12.3173 12.6618 12.2291 12.2017 11.9916 11.8145C11.9533 11.9024 11.9139 11.9862 11.8735 12.0655C11.1598 13.4654 9.96596 14.2559 8.86581 14.6666V5.47118C9.72427 5.47118 11.1869 5.72395 12.0454 6.66547C12.5609 6.45766 12.9479 6.00752 13.2278 5.29429C13.3198 5.05991 13.5733 4.9491 13.7939 5.0468C14.0146 5.14449 14.1189 5.41369 14.0269 5.64806C13.6767 6.5406 13.1549 7.15382 12.4798 7.47015C12.6017 7.90264 12.6404 8.4296 12.6159 8.9829C13.3888 9.06139 14.0516 9.47124 14.5786 10.2087C14.7231 10.411 14.6859 10.6994 14.4955 10.8529C14.305 11.0064 14.0335 10.9669 13.889 10.7646C13.5125 10.2378 13.0656 9.94955 12.5234 9.89641Z" fill="white" fill-opacity="0.9" style="fill:white;fill-opacity:0.6;"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 8 8" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<circle cx="4" cy="4" r="4" style="fill:rgb(77,230,80);"/>
</svg>

Before

Width:  |  Height:  |  Size: 511 B

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 8 8" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<circle cx="4" cy="4" r="4" style="fill:rgb(154,154,154);"/>
</svg>

Before

Width:  |  Height:  |  Size: 513 B

BIN
Plugins/MetaXR/Resources/Icon128.png (Stored with Git LFS)

Binary file not shown.

View File

@ -1,3 +0,0 @@
<svg width="800" height="800" viewBox="0 0 800 800" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M125 487.5C125 372.5 182.5 252.5 250 252.5C287.5 252.5 317.5 275 365 342.5C320 412.5 292.5 455 292.5 455C232.5 550 212.5 570 180 570C147.5 572.5 125 542.5 125 487.5ZM517.5 445L475 375C465 357.5 452.5 340 442.5 325C480 267.5 510 237.5 547.5 237.5C622.5 237.5 682.5 350 682.5 490C682.5 542.5 665 572.5 630 572.5C595 572.5 582.5 550 517.5 445ZM410 275C355 202.5 307.5 175 252.5 175C137.5 175 50 327.5 50 487.5C50 587.5 97.5 650 177.5 650C235 650 275 622.5 350 492.5C350 492.5 380 437.5 402.5 400C410 412.5 417.5 425 425 440L460 500C527.5 615 565 652.5 632.5 652.5C710 652.5 752.5 587.5 752.5 485C750 315 660 175 552.5 175C495 175 450 220 410 275Z" fill="white" style="fill:white;fill:white;fill-opacity:1;"/>
</svg>

Before

Width:  |  Height:  |  Size: 822 B

Binary file not shown.

View File

@ -1,3 +0,0 @@
<svg width="596" height="596" viewBox="0 0 596 596" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M512.5 0.166671H83.5C61.3986 0.166671 40.2025 8.94641 24.5744 24.5744C8.94642 40.2025 0.166679 61.3986 0.166679 83.5V379.167C0.166679 401.268 8.94642 422.464 24.5744 438.092C40.2025 453.72 61.3986 462.5 83.5 462.5H180.5L168.167 562.5H131.333C126.913 562.5 122.674 564.256 119.548 567.382C116.423 570.507 114.667 574.746 114.667 579.167C114.667 583.587 116.423 587.826 119.548 590.952C122.674 594.077 126.913 595.833 131.333 595.833H465C469.42 595.833 473.66 594.077 476.785 590.952C479.911 587.826 481.667 583.587 481.667 579.167C481.667 574.746 479.911 570.507 476.785 567.382C473.66 564.256 469.42 562.5 465 562.5H428L415.667 462.5H512.667C534.768 462.5 555.964 453.72 571.592 438.092C587.22 422.464 596 401.268 596 379.167V83.5C596 72.5425 593.839 61.6925 589.641 51.5712C585.442 41.4498 579.289 32.2558 571.533 24.5154C563.777 16.775 554.571 10.6402 544.441 6.46214C534.312 2.28407 523.457 0.144756 512.5 0.166671ZM201.5 562.5L214.167 462.5H381.833L394.167 562.5H201.5ZM562.5 379.167C562.5 392.428 557.232 405.145 547.855 414.522C538.479 423.899 525.761 429.167 512.5 429.167H83.5C70.2392 429.167 57.5215 423.899 48.1447 414.522C38.7678 405.145 33.5 392.428 33.5 379.167V362.5H562.5V379.167ZM562.5 329.167H33.5V83.5C33.5 70.2392 38.7678 57.5215 48.1447 48.1447C57.5215 38.7678 70.2392 33.5 83.5 33.5H512.5C525.761 33.5 538.479 38.7678 547.855 48.1447C557.232 57.5215 562.5 70.2392 562.5 83.5V329.167Z" fill="white" style="fill:white;fill:white;fill-opacity:1;"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -1,7 +0,0 @@
<svg width="800" height="800" viewBox="0 0 800 800" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M247.75 490.247C247.75 503.362 237.117 513.996 224 513.996C210.883 513.996 200.25 503.362 200.25 490.247C200.25 477.131 210.883 466.498 224 466.498C237.117 466.498 247.75 477.131 247.75 490.247Z" fill="white" style="fill:white;fill:white;fill-opacity:1;"/>
<path d="M224 371.498C237.117 371.498 247.75 360.865 247.75 347.748C247.75 334.631 237.117 323.998 224 323.998C210.883 323.998 200.25 334.631 200.25 347.748C200.25 360.865 210.883 371.498 224 371.498Z" fill="white" style="fill:white;fill:white;fill-opacity:1;"/>
<path d="M580.246 490.247C580.246 503.362 569.613 513.996 556.497 513.996C543.379 513.996 532.745 503.362 532.745 490.247C532.745 477.131 543.379 466.498 556.497 466.498C569.613 466.498 580.246 477.131 580.246 490.247Z" fill="white" style="fill:white;fill:white;fill-opacity:1;"/>
<path d="M556.497 371.498C569.613 371.498 580.246 360.865 580.246 347.748C580.246 334.631 569.613 323.998 556.497 323.998C543.379 323.998 532.745 334.631 532.745 347.748C532.745 360.865 543.379 371.498 556.497 371.498Z" fill="white" style="fill:white;fill:white;fill-opacity:1;"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M319 276.5C319 250.266 340.267 229 366.5 229H414C440.231 229 461.498 250.266 461.498 276.5H532.748C585.217 276.5 627.75 319.033 627.75 371.5C640.866 371.5 651.499 382.133 651.499 395.25V442.749C651.499 455.868 640.866 466.501 627.75 466.501C627.75 518.968 585.217 561.501 532.748 561.501H466.52C449.793 558.913 434.856 549.337 425.668 535.218L422.932 528.877C419.335 520.541 412.69 513.891 404.356 510.288C386.299 502.482 365.329 510.796 357.522 528.857L354.307 536.293C345.07 549.822 330.47 558.979 314.171 561.501H247.75C195.283 561.501 152.75 518.968 152.75 466.501C139.633 466.501 129 455.868 129 442.749V395.25C129 382.133 139.633 371.5 152.75 371.5C152.75 319.033 195.283 276.5 247.75 276.5H319ZM603.998 466.501V371.5C603.998 332.15 572.101 300.25 532.748 300.25H247.75C208.4 300.25 176.5 332.15 176.5 371.5V466.501C176.5 505.851 208.4 537.752 247.75 537.752H312.113C320.651 536.039 328.299 531.297 333.528 524.508L335.721 519.432C348.732 489.332 383.68 475.479 413.781 488.489C427.669 494.494 438.744 505.572 444.737 519.466L446.57 523.713C451.8 530.92 459.705 535.974 468.58 537.752H532.748C572.101 537.752 603.998 505.851 603.998 466.501ZM437.749 276.5H342.75C342.75 263.383 353.383 252.75 366.5 252.75H414C427.116 252.75 437.749 263.383 437.749 276.5Z" fill="white" style="fill:white;fill:white;fill-opacity:1;"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -1,10 +0,0 @@
<svg width="800" height="800" viewBox="0 0 800 800" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_235_1629)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M442.297 539.461C453.323 556.403 471.247 567.895 491.319 571H570.793C633.755 571 684.795 519.96 684.795 457.001V343C684.795 280.04 633.755 229 570.793 229H228.795C165.834 229 114.794 280.04 114.794 343V457.001C114.794 519.96 165.834 571 228.795 571H308.5C328.058 567.973 345.579 556.985 356.663 540.754L360.521 531.827C369.889 510.155 395.052 500.179 416.722 509.546C426.722 513.869 434.698 521.848 439.014 531.851L442.297 539.461ZM627.794 457.001C627.794 488.479 602.274 513.999 570.793 513.999H497.369C495.31 513.277 493.45 512.118 491.945 510.647L491.347 509.266C481.275 485.927 462.673 467.312 439.339 457.227C388.772 435.366 330.058 458.643 308.199 509.211L307.377 511.112C305.966 512.364 304.286 513.356 302.452 513.999H228.795C197.314 513.999 171.794 488.479 171.794 457.001V343C171.794 311.52 197.314 286 228.795 286H570.793C602.274 286 627.794 311.52 627.794 343V457.001Z" fill="white" style="fill:white;fill:white;fill-opacity:1;"/>
</g>
<defs>
<clipPath id="clip0_235_1629">
<rect width="615.6" height="342" fill="white" style="fill:white;fill:white;fill-opacity:1;" transform="translate(92 229)"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 8 8" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<circle cx="4" cy="4" r="4" style="fill:rgb(230,77,77);"/>
</svg>

Before

Width:  |  Height:  |  Size: 511 B

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 8 8" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<circle cx="4" cy="4" r="4" style="fill:rgb(243,243,243);"/>
</svg>

Before

Width:  |  Height:  |  Size: 513 B

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 8 8" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<circle cx="4" cy="4" r="4" style="fill:rgb(230,187,77);"/>
</svg>

Before

Width:  |  Height:  |  Size: 512 B

View File

@ -1,51 +0,0 @@
#pragma once
float3 MRUKHighlights(float3 TranslatedWorldPosition, MaterialFloat TotalLights, MaterialFloat3 PixelNormal, inout MaterialFloat TotalLightAlpha)
{
float3 TotalLight = 0;
TotalLightAlpha = 0;
UNROLL_N(2)
for (int i = 0; i < TotalLights; i++)
{
int groupID = 2 + i * 3;
half4 PositionParam = MaterialCollection0.Vectors[groupID];
half4 DataParam = MaterialCollection0.Vectors[groupID + 1];
half4 ColorParam = MaterialCollection0.Vectors[groupID + 2];
// LightWorldPosition = float3(PositionParam.xyz);
// float LightColor = float3(ColorParam.xyz);
// float LightInvRadius = DataParam.x;
// float LightIntensity = DataParam.y;
// float LightFalloffExponent = DataParam.z;
// bool LightInverseSquared = DataParam.w;
half3 ToLight = PositionParam.xyz - TranslatedWorldPosition;
half DistanceSqr = dot(ToLight, ToLight);
half3 L = ToLight * rsqrt(DistanceSqr);
float LightMask = 0;
FLATTEN
if (DataParam.w > 0.0)
{
LightMask = Square(saturate(1 - Square(DistanceSqr * Square(DataParam.x))));
DataParam.y *= 0.0001; //fake intensity multiplier, dividing by 0.0001 because Inverse Squared Falloff require really high intensity
}
else
{
half3 WorldLightVector = ToLight * DataParam.x;
half NormalizeDistanceSquared = dot(WorldLightVector, WorldLightVector);
LightMask = pow(1.0f - saturate(NormalizeDistanceSquared), DataParam.z);
}
half angle = saturate(dot(L, PixelNormal));
LightMask *= angle;
TotalLight += LightMask * ColorParam.xyz * DataParam.y;
TotalLightAlpha += LightMask;
}
return TotalLight;
}

View File

@ -1,32 +0,0 @@
float4 MRUKJumpFlood(MaterialFloat2 UV, Texture2D Tex, SamplerState TexSampler , MaterialFloat StepSize)
{
float BestDistance = 99999;
float BestDistance2 = 99999;
float2 BestUV = float2(-1,-1);
float2 BestUV2 = float2(-1.0, -1.0);
for (int y = -1; y <= 1; ++y)
{
for (int x = -1; x <= 1; ++x)
{
float2 UVOff = UV + float2(x,y) * StepSize;
float2 TempVaule = Texture2DSample(Tex, TexSampler, UVOff).xy;
float Dist = length(TempVaule - UV);
if ((TempVaule.x >= 0) && (TempVaule.y >= 0) && (Dist < BestDistance))
{
BestDistance = Dist;
BestUV = TempVaule;
}
float2 TempVaule2 = Texture2DSample(Tex, TexSampler, UVOff).zw;
float Dist2 = length(TempVaule2 - UV);
if ((TempVaule2.x >= 0) && (TempVaule2.y >= 0) && (Dist2 < BestDistance2))
{
BestDistance2 = Dist2;
BestUV2 = TempVaule2;
}
}
}
return float4(BestUV.x, BestUV.y, BestUV2.x, BestUV2.y);
}

View File

@ -1,36 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "/Engine/Public/Platform.ush"
#define NUM_VIEWS 2
// PS Textures Parameters
Texture2DArray EnvironmentDepthTexture;
SamplerState EnvironmentDepthSampler;
float2 DepthFactors;
float4x4 ScreenToDepthMatrices[NUM_VIEWS];
int DepthViewId;
void HardOcclusionsPS(
noperspective float4 UVAndScreenPos : TEXCOORD0,
float4 SvPosition : SV_POSITION,
#if INSTANCED_STEREO
in uint InstanceId : SV_InstanceID,
#elif MOBILE_MULTI_VIEW
in uint ViewId : SV_ViewID,
#endif
out float4 OutColor : SV_Target0,
out float OutDepth : SV_DEPTH)
{
#if INSTANCED_STEREO
uint ViewId = InstanceId & 1;
#elif !MOBILE_MULTI_VIEW
uint ViewId = DepthViewId;
#endif
float4 TexCoordH = mul(ScreenToDepthMatrices[ViewId], float4(UVAndScreenPos.x, 1.0f - UVAndScreenPos.y, 0.0, 1.0));
float3 TexCoord = float3(TexCoordH.x / TexCoordH.w, TexCoordH.y / TexCoordH.w, ViewId);
float InputDepthEye = EnvironmentDepthTexture.Sample(EnvironmentDepthSampler, TexCoord).r;
float DepthEye = InputDepthEye * DepthFactors.x + DepthFactors.y;
OutDepth = DepthEye;
OutColor = float4(0.0, 0.0, 0.0, 1.0);
}

View File

@ -1,103 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "/Engine/Private/Common.ush"
Texture2DArray InTexture;
SamplerState InTextureSampler;
#if !ENABLE_MULTI_VIEW
// Use shader constants on PC Link
int ArraySlice;
#endif
void Main(
FScreenVertexOutput Input,
#if ENABLE_MULTI_VIEW
// Use Multi View on Mobile
uint ArraySlice : SV_ViewID,
#endif
out float4 OutColor : SV_Target0
)
{
float3 Dimensions;
InTexture.GetDimensions(Dimensions.x, Dimensions.y, Dimensions.z);
float2 onePixelOffset = 1.0f / Dimensions.xy;
const uint NUM_SAMPLES = 4U;
const float2 offsets[NUM_SAMPLES] = {
float2(-1.0f, 1.0f),
float2(1.0f, 1.0f),
float2(-1.0f, -1.0f),
float2(1.0f, -1.0f)
};
float4 depths[NUM_SAMPLES];
float minDepth = 1.0f;
float maxDepth = 0.0f;
float depthSum = 0.0f;
// Find the local min and max, and collect all depth samples in the sampling grid
uint i;
UNROLL
for (i = 0U; i < NUM_SAMPLES; ++i) {
float2 uvSample = Input.UV + (offsets[i] + 0.5f) * onePixelOffset;
float4 depth4 = InTexture.Gather(InTextureSampler, float3(uvSample, ArraySlice));
depthSum += dot(depth4, float4(0.25f, 0.25, 0.25, 0.25));
float localMax = max(max(depth4.x, depth4.y), max(depth4.z, depth4.w));
float localMin = min(min(depth4.x, depth4.y), min(depth4.z, depth4.w));
maxDepth = max(maxDepth, localMax);
minDepth = min(minDepth, localMin);
depths[i] = depth4;
}
float maxSumDepth = 0.0f;
float minSumDepth = 0.0f;
float maxSumCount = 0.0f;
float minSumCount = 0.0f;
// Model the entire neighborhood as a bimodal distribution aggregated around the minimum and maximum values.
// Each side of the distribution (min and max) accepts values in a multiplicative range with respect to metric depth
// This will therefore aggregate all depth values until a maximum slope (depending also on the depth resolution)
static const float kMaxMetricDepthThrMultiplier = 0.85f;
static const float kMinMetricDepthThrMultiplier = 1.15f;
// Compute thresholds in window depth space:
// Dmetric = 1 / (1 - Dwin)
// Tmetric = kMultiplier * Dmetric
// Twin = 1 - 1/Tmetric
// Therefore:
// Twin = 1 - 1/(kMultiplier * (1 / (1 - Dwin))) = 1 - (1 - Dwin) / kMultiplier = 1 - 1/kMultiplier + Dwin / kMultiplier
float depthThrMax = (1.0f - 1.0f / kMaxMetricDepthThrMultiplier) + maxDepth * (1.0f / kMaxMetricDepthThrMultiplier);
float depthThrMin = (1.0f - 1.0f / kMinMetricDepthThrMultiplier) + minDepth * (1.0f / kMinMetricDepthThrMultiplier);
float avg = depthSum * (1.0f / float(NUM_SAMPLES));
if (depthThrMax < minDepth && depthThrMin > maxDepth) {
// Degenerate case: the entire neighborhood is within min-max thresholds for averaging
// therefore minAvg == maxAvg == avg.
// Directly output the encoded fragColor as:
// (1 - minAvg, 1 - maxAvg, avg - minAvg, maxAvg - minAvg)
OutColor = float4(1.0f - avg, 1.0f - avg, 0.0f, 0.0f);
} else {
// Compute average depths around the minimum and maximum values
UNROLL
for (i = 0U; i < NUM_SAMPLES; ++i) {
float4 maxMask = (depths[i] >= float4(depthThrMax, depthThrMax, depthThrMax, depthThrMax));
float4 minMask = (depths[i] <= float4(depthThrMin, depthThrMin, depthThrMin, depthThrMin));
minSumDepth += dot(minMask, depths[i]);
minSumCount += dot(minMask, float4(1.0f, 1.0f, 1.0f, 1.0f));
maxSumDepth += dot(maxMask, depths[i]);
maxSumCount += dot(maxMask, float4(1.0f, 1.0f, 1.0f, 1.0f));
}
float minAvg = minSumDepth / minSumCount;
float maxAvg = maxSumDepth / maxSumCount;
// Encoding the depth as a 4-channel RGBA image for improved numerical stability.
// minAvg and maxAvg are encoded in inverse range to use more floating point precision in the far field.
// The interpolation ratio between min and max is computed as: (avg - minAvg) / (maxAvg - minAvg)
// We can perform the differences here at higher precision
// We let the division later to the occlusion shader to preserve the bilinear interpolation properties as with minAvg and maxAvg.
OutColor = float4(1.0f - minAvg, 1.0f - maxAvg, avg - minAvg, maxAvg - minAvg);
}
}

View File

@ -1,25 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "/Engine/Private/Common.ush"
Texture2DArray InTexture;
SamplerState InTextureSampler;
int MipLevel;
int ArraySlice;
void MainMipLevel(
FScreenVertexOutput Input,
out float4 OutColor : SV_Target0
)
{
OutColor = InTexture.SampleLevel(InTextureSampler, float3(Input.UV, ArraySlice), MipLevel);
}
void MainsRGBSourceMipLevel(
FScreenVertexOutput Input,
out float4 OutColor : SV_Target0
)
{
OutColor = InTexture.SampleLevel(InTextureSampler, float3(Input.UV, ArraySlice), MipLevel);
OutColor.rgb = pow( OutColor.rgb, 1.0f / 2.2f );
}

View File

@ -1,53 +0,0 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
using UnrealBuildTool;
public class MRUtilityKit : ModuleRules
{
public MRUtilityKit(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
bUseUnity = true;
PublicDependencyModuleNames.AddRange(
new string[]
{
"Core",
"RenderCore",
"Projects"
});
if (Target.Version.MajorVersion > 5 || (Target.Version.MajorVersion == 5 && Target.Version.MinorVersion >= 3))
{
PublicDependencyModuleNames.AddRange(
new string[]
{
"XRBase",
});
}
PrivateDependencyModuleNames.AddRange(
new string[]
{
"CoreUObject",
"Engine",
"Slate",
"SlateCore",
"OculusXRHMD",
"OculusXRAnchors",
"OculusXRScene",
"Json",
"ProceduralMeshComponent",
"HeadMountedDisplay",
"MRUtilityKitShared",
});
if (Target.bBuildEditor == true)
{
PrivateDependencyModuleNames.Add("UnrealEd");
}
}
}

View File

@ -1,386 +0,0 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
// @generated by `buck2 run //arvr/projects/mixedreality/libraries/mrutilitykit:build_and_deploy unreal`
#pragma once
#include <float.h>
#include <stddef.h>
#include <stdint.h>
#include "CoreTypes.h"
#include "Math/UnrealMath.h"
#include "Math/MathFwd.h"
struct MRUKShared
{
static MRUKShared* GetInstance() { return Instance; }
static void LoadMRUKSharedLibrary();
static void FreeMRUKSharedLibrary();
struct MrukSceneAnchor;
struct MrukRoomAnchor;
struct MrukUuid;
enum MrukSceneModel
{
MRUK_SCENE_MODEL_V2_FALLBACK_V1,
MRUK_SCENE_MODEL_V1,
MRUK_SCENE_MODEL_V2,
};
enum MrukLogLevel
{
MRUK_LOG_LEVEL_DEBUG,
MRUK_LOG_LEVEL_INFO,
MRUK_LOG_LEVEL_WARN,
MRUK_LOG_LEVEL_ERROR,
};
enum MrukResult
{
MRUK_SUCCESS,
MRUK_ERROR_INVALID_ARGS,
MRUK_ERROR_UNKNOWN,
MRUK_ERROR_INTERNAL,
MRUK_ERROR_DISCOVERY_ONGOING,
MRUK_ERROR_INVALID_JSON,
MRUK_ERROR_NO_ROOMS_FOUND,
MRUK_ERROR_INSUFFICIENT_RESOURCES,
MRUK_ERROR_STORAGE_AT_CAPACITY,
MRUK_ERROR_INSUFFICIENT_VIEW,
MRUK_ERROR_PERMISSION_INSUFFICIENT,
MRUK_ERROR_RATE_LIMITED,
MRUK_ERROR_TOO_DARK,
MRUK_ERROR_TOO_BRIGHT,
};
enum MrukSurfaceType
{
MRUK_SURFACE_TYPE_NONE,
MRUK_SURFACE_TYPE_PLANE,
MRUK_SURFACE_TYPE_VOLUME,
MRUK_SURFACE_TYPE_MESH,
MRUK_SURFACE_TYPE_ALL,
};
typedef void (*LogPrinter)(MrukLogLevel logLevel, const char* message);
typedef void (*MrukOnPreRoomAnchorAdded)(const MrukRoomAnchor* roomAnchor, void* userContext);
typedef void (*MrukOnRoomAnchorAdded)(const MrukRoomAnchor* roomAnchor, void* userContext);
typedef void (*MrukOnRoomAnchorUpdated)(const MrukRoomAnchor* roomAnchor, const MrukUuid* oldRoomAnchorUuid, void* userContext);
typedef void (*MrukOnRoomAnchorRemoved)(const MrukRoomAnchor* roomAnchor, void* userContext);
typedef void (*MrukOnSceneAnchorAdded)(const MrukSceneAnchor* sceneAnchor, void* userContext);
typedef void (*MrukOnSceneAnchorUpdated)(const MrukSceneAnchor* sceneAnchor, void* userContext);
typedef void (*MrukOnSceneAnchorRemoved)(const MrukSceneAnchor* sceneAnchor, void* userContext);
typedef void (*MrukOnDiscoveryFinished)(MrukResult result, void* userContext);
struct MrukQuatf
{
float x;
float y;
float z;
float w;
};
struct MrukPosef
{
FVector3f position;
MrukQuatf rotation;
};
struct MrukPolygon2f
{
const FVector2f* points;
uint32_t numPoints;
};
struct MrukMesh2f
{
FVector2f* vertices;
uint32_t numVertices;
uint32_t* indices;
uint32_t numIndices;
};
struct MrukMesh3f
{
FVector3f* vertices;
uint32_t numVertices;
uint32_t* indices;
uint32_t numIndices;
};
struct MrukUuid
{
uint64_t part1;
uint64_t part2;
};
struct MrukVolume
{
FVector3f min;
FVector3f max;
};
struct MrukPlane
{
float x;
float y;
float width;
float height;
};
struct MrukSceneAnchor
{
uint64_t space;
MrukUuid uuid;
MrukUuid roomUuid;
MrukPosef pose;
MrukVolume volume;
MrukPlane plane;
char** semanticLabels;
FVector2f* planeBoundary;
uint32_t* globalMeshIndices;
FVector3f* globalMeshPositions;
uint32_t semanticLabelsCount;
uint32_t planeBoundaryCount;
uint32_t globalMeshIndicesCount;
uint32_t globalMeshPositionsCount;
bool hasVolume;
bool hasPlane;
};
struct MrukRoomAnchor
{
uint64_t space;
MrukUuid uuid;
};
struct MrukEventListener
{
MrukOnPreRoomAnchorAdded onPreRoomAnchorAdded;
MrukOnRoomAnchorAdded onRoomAnchorAdded;
MrukOnRoomAnchorUpdated onRoomAnchorUpdated;
MrukOnRoomAnchorRemoved onRoomAnchorRemoved;
MrukOnSceneAnchorAdded onSceneAnchorAdded;
MrukOnSceneAnchorUpdated onSceneAnchorUpdated;
MrukOnSceneAnchorRemoved onSceneAnchorRemoved;
MrukOnDiscoveryFinished onDiscoveryFinished;
void* userContext;
};
struct MrukHit
{
MrukUuid roomAnchorUuid;
MrukUuid sceneAnchorUuid;
float hitDistance;
FVector3f hitPosition;
FVector3f hitNormal;
};
void (*SetLogPrinter)(LogPrinter printer);
/**
* Create the global anchor store with a external OpenXR instance and session.
* This should only be called once on application startup.
* Make sure to hook up the ContextOnOpenXrEvent() function as well.
* If the context is not needed anymore it should be destroyed with ContextDestroy() to free
* resources.
*/
MrukResult (*AnchorStoreCreate)(uint64_t xrInstance, uint64_t xrSession, void* xrInstanceProcAddrFunc, uint64_t baseSpace);
MrukResult (*AnchorStoreCreateWithoutOpenXr)();
/**
* Destroy the global anchor store
* This should only be called once on application shutdown.
*/
void (*AnchorStoreDestroy)();
/**
* If the base space changes after initialization, this function should be called to update the
* base space.
*/
void (*AnchorStoreSetBaseSpace)(uint64_t baseSpace);
/**
* Start anchor discovery in the anchor store
*/
MrukResult (*AnchorStoreStartDiscovery)(bool shouldRemoveMissingRooms, MrukSceneModel sceneModel);
/**
* Load the scene from a json string
*/
MrukResult (*AnchorStoreLoadSceneFromJson)(const char* jsonString, bool shouldRemoveMissingRooms, MrukSceneModel sceneModel);
/**
* Save the scene to a json string.
* @return The serialized JSON string. This string must be freed with FreeAnchorStoreJson after use!
*/
const char* (*AnchorStoreSaveSceneToJson)();
/**
* Free the json string returned by AnchorStoreSaveSceneToJson.
* @param[in] jsonString The JSON string to free.
*/
void (*AnchorStoreFreeJson)(const char* jsonString);
/**
* Clear and remove all rooms in the anchor store.
*/
void (*AnchorStoreClearRooms)();
/**
* Clear and remove the room that matches the given uuid.
*/
void (*AnchorStoreClearRoom)(MrukUuid roomUuid);
/**
* Allows to forward OpenXR events from the engine into the shared library
*/
void (*AnchorStoreOnOpenXrEvent)(void* baseEventHeader);
/**
* Needs to be called every tick by the engine.
*/
void (*AnchorStoreTick)(uint64_t nextPredictedDisplayTime);
void (*AnchorStoreRegisterEventListener)(MrukEventListener listener);
/**
* Cast a ray against all anchors in the room and return the first hit.
*/
bool (*AnchorStoreRaycastRoom)(MrukUuid roomUuid, FVector3f origin, FVector3f direction, float maxDistance, uint32_t surfaceType, MrukHit* outHit);
/**
* Cast a ray against all anchors in the room and return all hits along the ray.
*/
bool (*AnchorStoreRaycastRoomAll)(MrukUuid roomUuid, FVector3f origin, FVector3f direction, float maxDistance, uint32_t surfaceType, MrukHit* outHits, uint32_t* outHitsCount);
bool (*AnchorStoreIsDiscoveryRunning)();
/**
* Add two vectors together. This is implemented as a test to ensure the native shared
* library is working correctly.
*
* @param[in] a The first vector.
* @param[in] b The second vector.
* @return The sum of the two vectors.
*/
FVector3f (*AddVectors)(FVector3f a, FVector3f b);
/**
* Triangulate a polygon with holes, any winding order works. The first polyline defines the main
* polygon. Following polylines define holes. This function will allocate memory for the vertices
* and indices. You *MUST* call FreeMesh() when you are done with it or you will leak memory.
*
* @param[in] polygons The polygon to triangulate.
* @param[in] numPolygons The number of polygons in the array.
* @return mesh The triangulated mesh.
*/
MrukMesh2f (*TriangulatePolygon)(const MrukPolygon2f* polygons, uint32_t numPolygons);
/**
* Free the memory allocated by TriangulatePolygon.
*
* @param[in] mesh The mesh to free.
*/
void (*FreeMesh)(MrukMesh2f* mesh);
/**
* Compute the mesh segmentation for a given set of vertices, indices and segmentation points.
* You *MUST* call FreeMeshSegmentation() on the meshSegments array when you are done with it or you
* will leak memory.
*
* @param[in] vertices The mesh vertices.
* @param[in] numVertices The number of vertices in the mesh.
* @param[in] indices The mesh indices.
* @param[in] numIndices The number of indices in the mesh.
* @param[in] segmentationPoints The points that should be used to calculate the segments.
* @param[in] numSegmentationPoints The number of segmentation points.
* @param[in] reservedMin The minimum bounding box for the reserved segment.
* @param[in] reservedMax The maximum bounding box for the reserved segment.
* @param[out] meshSegments The resulting segments.
* @param[out] numSegments The number of segments in the resulting array.
* @param[out] reservedSegment The segment that is inside the reserved bounding box.
*/
MrukResult (*ComputeMeshSegmentation)(const FVector3f* vertices, uint32_t numVertices, const uint32_t* indices, uint32_t numIndices, const FVector3f* segmentationPoints, uint32_t numSegmentationPoints, FVector3f reservedMin, FVector3f reservedMax, MrukMesh3f** meshSegments, uint32_t* numSegments, MrukMesh3f* reservedSegment);
/**
* Free the memory allocated by ComputeMeshSegmentation.
*
* @param[in] meshSegments The array of segments to free.
* @param[in] numSegments The number of segments in the array.
* @param[in] reservedSegment The reserved segment to free.
*/
void (*FreeMeshSegmentation)(const MrukMesh3f* meshSegments, uint32_t numSegments, MrukMesh3f* reservedSegment);
private:
void LoadNativeFunctions()
{
SetLogPrinter = reinterpret_cast<decltype(SetLogPrinter)>(LoadFunction(TEXT("SetLogPrinter")));
AnchorStoreCreate = reinterpret_cast<decltype(AnchorStoreCreate)>(LoadFunction(TEXT("AnchorStoreCreate")));
AnchorStoreCreateWithoutOpenXr = reinterpret_cast<decltype(AnchorStoreCreateWithoutOpenXr)>(LoadFunction(TEXT("AnchorStoreCreateWithoutOpenXr")));
AnchorStoreDestroy = reinterpret_cast<decltype(AnchorStoreDestroy)>(LoadFunction(TEXT("AnchorStoreDestroy")));
AnchorStoreSetBaseSpace = reinterpret_cast<decltype(AnchorStoreSetBaseSpace)>(LoadFunction(TEXT("AnchorStoreSetBaseSpace")));
AnchorStoreStartDiscovery = reinterpret_cast<decltype(AnchorStoreStartDiscovery)>(LoadFunction(TEXT("AnchorStoreStartDiscovery")));
AnchorStoreLoadSceneFromJson = reinterpret_cast<decltype(AnchorStoreLoadSceneFromJson)>(LoadFunction(TEXT("AnchorStoreLoadSceneFromJson")));
AnchorStoreSaveSceneToJson = reinterpret_cast<decltype(AnchorStoreSaveSceneToJson)>(LoadFunction(TEXT("AnchorStoreSaveSceneToJson")));
AnchorStoreFreeJson = reinterpret_cast<decltype(AnchorStoreFreeJson)>(LoadFunction(TEXT("AnchorStoreFreeJson")));
AnchorStoreClearRooms = reinterpret_cast<decltype(AnchorStoreClearRooms)>(LoadFunction(TEXT("AnchorStoreClearRooms")));
AnchorStoreClearRoom = reinterpret_cast<decltype(AnchorStoreClearRoom)>(LoadFunction(TEXT("AnchorStoreClearRoom")));
AnchorStoreOnOpenXrEvent = reinterpret_cast<decltype(AnchorStoreOnOpenXrEvent)>(LoadFunction(TEXT("AnchorStoreOnOpenXrEvent")));
AnchorStoreTick = reinterpret_cast<decltype(AnchorStoreTick)>(LoadFunction(TEXT("AnchorStoreTick")));
AnchorStoreRegisterEventListener = reinterpret_cast<decltype(AnchorStoreRegisterEventListener)>(LoadFunction(TEXT("AnchorStoreRegisterEventListener")));
AnchorStoreRaycastRoom = reinterpret_cast<decltype(AnchorStoreRaycastRoom)>(LoadFunction(TEXT("AnchorStoreRaycastRoom")));
AnchorStoreRaycastRoomAll = reinterpret_cast<decltype(AnchorStoreRaycastRoomAll)>(LoadFunction(TEXT("AnchorStoreRaycastRoomAll")));
AnchorStoreIsDiscoveryRunning = reinterpret_cast<decltype(AnchorStoreIsDiscoveryRunning)>(LoadFunction(TEXT("AnchorStoreIsDiscoveryRunning")));
AddVectors = reinterpret_cast<decltype(AddVectors)>(LoadFunction(TEXT("AddVectors")));
TriangulatePolygon = reinterpret_cast<decltype(TriangulatePolygon)>(LoadFunction(TEXT("TriangulatePolygon")));
FreeMesh = reinterpret_cast<decltype(FreeMesh)>(LoadFunction(TEXT("FreeMesh")));
ComputeMeshSegmentation = reinterpret_cast<decltype(ComputeMeshSegmentation)>(LoadFunction(TEXT("ComputeMeshSegmentation")));
FreeMeshSegmentation = reinterpret_cast<decltype(FreeMeshSegmentation)>(LoadFunction(TEXT("FreeMeshSegmentation")));
}
void UnloadNativeFunctions()
{
SetLogPrinter = nullptr;
AnchorStoreCreate = nullptr;
AnchorStoreCreateWithoutOpenXr = nullptr;
AnchorStoreDestroy = nullptr;
AnchorStoreSetBaseSpace = nullptr;
AnchorStoreStartDiscovery = nullptr;
AnchorStoreLoadSceneFromJson = nullptr;
AnchorStoreSaveSceneToJson = nullptr;
AnchorStoreFreeJson = nullptr;
AnchorStoreClearRooms = nullptr;
AnchorStoreClearRoom = nullptr;
AnchorStoreOnOpenXrEvent = nullptr;
AnchorStoreTick = nullptr;
AnchorStoreRegisterEventListener = nullptr;
AnchorStoreRaycastRoom = nullptr;
AnchorStoreRaycastRoomAll = nullptr;
AnchorStoreIsDiscoveryRunning = nullptr;
AddVectors = nullptr;
TriangulatePolygon = nullptr;
FreeMesh = nullptr;
ComputeMeshSegmentation = nullptr;
FreeMeshSegmentation = nullptr;
}
void* LoadFunction(const TCHAR* ProcName);
static MRUKShared* Instance;
void* MRUKSharedHandle;
MRUKShared(void* handle);
~MRUKShared();
};

View File

@ -1,84 +0,0 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#include "MRUtilityKit.h"
#include "Interfaces/IPluginManager.h"
#include "Misc/Paths.h"
#include "ShaderCore.h"
#if WITH_EDITOR
#include "ISettingsModule.h"
#endif // WITH_EDITOR
#define LOCTEXT_NAMESPACE "FMRUKModule"
DEFINE_LOG_CATEGORY(LogMRUK);
const FString FMRUKLabels::Floor("FLOOR");
const FString FMRUKLabels::WallFace("WALL_FACE");
const FString FMRUKLabels::InvisibleWallFace("INVISIBLE_WALL_FACE");
const FString FMRUKLabels::Ceiling("CEILING");
const FString FMRUKLabels::DoorFrame("DOOR_FRAME");
const FString FMRUKLabels::WindowFrame("WINDOW_FRAME");
const FString FMRUKLabels::Couch("COUCH");
const FString FMRUKLabels::Table("TABLE");
const FString FMRUKLabels::Screen("SCREEN");
const FString FMRUKLabels::Bed("BED");
const FString FMRUKLabels::Lamp("LAMP");
const FString FMRUKLabels::Plant("PLANT");
const FString FMRUKLabels::Storage("STORAGE");
const FString FMRUKLabels::WallArt("WALL_ART");
const FString FMRUKLabels::GlobalMesh("GLOBAL_MESH");
const FString FMRUKLabels::Other("OTHER");
bool FMRUKLabelFilter::PassesFilter(const TArray<FString>& Labels) const
{
for (const auto& ExcludedLabel : ExcludedLabels)
{
if (Labels.Contains(ExcludedLabel))
{
return false;
}
}
for (const auto& IncludedLabel : IncludedLabels)
{
if (Labels.Contains(IncludedLabel))
{
return true;
}
}
return IncludedLabels.IsEmpty();
}
UMRUKSettings::UMRUKSettings(const FObjectInitializer& obj)
{
}
void FMRUKModule::StartupModule()
{
// This code will execute after your module is loaded into memory; the exact timing is specified
// in the .uplugin file per-module
#if WITH_EDITOR
if (ISettingsModule* SettingsModule = FModuleManager::GetModulePtr<ISettingsModule>("Settings"))
{
SettingsModule->RegisterSettings("Project", "Plugins", "MRUtilityKit",
LOCTEXT("RuntimeSettingsName", "Mixed Reality Utility Kit"), LOCTEXT("RuntimeSettingsDescription", "Configure the Mixed Reality Utility plugin"),
GetMutableDefault<UMRUKSettings>());
}
#endif // WITH_EDITOR
}
void FMRUKModule::ShutdownModule()
{
// This function may be called during shutdown to clean up your module. For modules that support
// dynamic reloading, we call this function before unloading the module.
#if WITH_EDITOR
if (ISettingsModule* SettingsModule = FModuleManager::GetModulePtr<ISettingsModule>("Settings"))
{
SettingsModule->UnregisterSettings("Project", "Plugins", "MRUtilityKit");
}
#endif // WITH_EDITOR
}
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FMRUKModule, MRUtilityKit)

View File

@ -1,889 +0,0 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#include "MRUtilityKitAnchor.h"
#include "MRUtilityKit.h"
#include "MRUtilityKitBPLibrary.h"
#include "MRUtilityKitGeometry.h"
#include "MRUtilityKitSubsystem.h"
#include "MRUtilityKitSerializationHelpers.h"
#include "MRUtilityKitSeatsComponent.h"
#include "MRUtilityKitRoom.h"
#include "OculusXRAnchorTypes.h"
#include "Engine/World.h"
#define LOCTEXT_NAMESPACE "MRUKAnchor"
// #pragma optimize("", off)
AMRUKAnchor::AMRUKAnchor(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
// Create a scene component as root so we can attach spawned actors to it
RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("SceneComponent"));
}
bool AMRUKAnchor::LoadFromData(UMRUKAnchorData* AnchorData)
{
check(AnchorData);
bool Changed = false;
if (const auto Seat = GetComponentByClass<UMRUKSeatsComponent>(); Seat && !HasLabel(FMRUKLabels::Couch))
{
Seat->UnregisterComponent();
Seat->DestroyComponent();
Changed = true;
}
AnchorUUID = AnchorData->SpaceQuery.UUID;
SpaceHandle = AnchorData->SpaceQuery.Space;
SetActorTransform(AnchorData->Transform, false, nullptr, ETeleportType::ResetPhysics);
const auto NewSemanticClassifications = AnchorData->SemanticClassifications;
if (NewSemanticClassifications != SemanticClassifications)
{
Changed = true;
}
SemanticClassifications = NewSemanticClassifications;
const FString Semantics = FString::Join(SemanticClassifications, TEXT("-"));
UE_LOG(LogMRUK, Log, TEXT("SpatialAnchor label is %s"), *Semantics);
if (PlaneBounds != AnchorData->PlaneBounds)
{
Changed = true;
}
PlaneBounds = AnchorData->PlaneBounds;
PlaneBoundary2D = AnchorData->PlaneBoundary2D;
if (VolumeBounds != AnchorData->VolumeBounds)
{
Changed = true;
}
VolumeBounds = AnchorData->VolumeBounds;
if (Changed)
{
if (ProceduralMeshComponent)
{
ProceduralMeshComponent->UnregisterComponent();
ProceduralMeshComponent->DestroyComponent();
ProceduralMeshComponent = nullptr;
}
if (CachedMesh.IsSet())
{
CachedMesh.GetValue().Clear();
}
}
return Changed;
}
bool AMRUKAnchor::IsPositionInBoundary(const FVector2D& Position)
{
if (PlaneBoundary2D.IsEmpty())
{
return false;
}
int Intersections = 0;
for (int i = 1; i <= PlaneBoundary2D.Num(); i++)
{
const FVector2D P1 = PlaneBoundary2D[i - 1];
const FVector2D P2 = PlaneBoundary2D[i % PlaneBoundary2D.Num()];
if (Position.Y > FMath::Min(P1.Y, P2.Y) && Position.Y <= FMath::Max(P1.Y, P2.Y))
{
if (Position.X <= FMath::Max(P1.X, P2.X))
{
if (P1.Y != P2.Y)
{
const auto Frac = (Position.Y - P1.Y) / (P2.Y - P1.Y);
const auto XIntersection = P1.X + Frac * (P2.X - P1.X);
if (P1.X == P2.X || Position.X <= XIntersection)
{
Intersections++;
}
}
}
}
}
return Intersections % 2 == 1;
}
FVector AMRUKAnchor::GenerateRandomPositionOnPlane()
{
return GenerateRandomPositionOnPlaneFromStream(FRandomStream(NAME_None));
}
FVector AMRUKAnchor::GenerateRandomPositionOnPlaneFromStream(const FRandomStream& RandomStream)
{
if (PlaneBoundary2D.IsEmpty())
{
return FVector::ZeroVector;
}
// Cache the mesh so that if the function is called multiple times it will re-use the previously triangulated mesh.
if (!CachedMesh.IsSet())
{
TriangulatedMeshCache Mesh;
TArray<FVector2f> PlaneBoundary;
PlaneBoundary.Reserve(PlaneBoundary2D.Num());
for (const auto Point : PlaneBoundary2D)
{
PlaneBoundary.Push(FVector2f(Point));
}
MRUKTriangulatePolygon({ PlaneBoundary }, Mesh.Vertices, Mesh.Triangles);
// Compute the area of each triangle and the total surface area of the mesh
Mesh.Areas.Reserve(Mesh.Triangles.Num() / 3);
Mesh.TotalArea = 0.0f;
for (int i = 0; i < Mesh.Triangles.Num(); i += 3)
{
const auto I0 = Mesh.Triangles[i];
const auto I1 = Mesh.Triangles[i + 1];
const auto I2 = Mesh.Triangles[i + 2];
auto V0 = Mesh.Vertices[I0];
auto V1 = Mesh.Vertices[I1];
auto V2 = Mesh.Vertices[I2];
const auto Cross = FVector2D::CrossProduct(V1 - V0, V2 - V0);
float Area = Cross * 0.5f;
Mesh.TotalArea += Area;
Mesh.Areas.Add(Area);
}
CachedMesh.Emplace(MoveTemp(Mesh));
}
const auto& [Vertices, Triangles, Areas, TotalArea] = CachedMesh.GetValue();
// Pick a random triangle weighted by surface area (triangles with larger surface
// area have more chance of being chosen)
auto Rand = RandomStream.FRandRange(0.0f, TotalArea);
int TriangleIndex = 0;
for (; TriangleIndex < Areas.Num() - 1; ++TriangleIndex)
{
Rand -= Areas[TriangleIndex];
if (Rand <= 0.0f)
{
break;
}
}
// Get the vertices of the chosen triangle
const auto I0 = Triangles[TriangleIndex * 3];
const auto I1 = Triangles[TriangleIndex * 3 + 1];
const auto I2 = Triangles[TriangleIndex * 3 + 2];
const auto V0 = FVector(0.0, Vertices[I0].X, Vertices[I0].Y);
const auto V1 = FVector(0.0, Vertices[I1].X, Vertices[I1].Y);
const auto V2 = FVector(0.0, Vertices[I2].X, Vertices[I2].Y);
// Calculate a random point on that triangle
float U = RandomStream.FRandRange(0.0f, 1.0f);
float V = RandomStream.FRandRange(0.0f, 1.0f);
if (U + V > 1.0f)
{
if (U > V)
{
U = 1.0f - U;
}
else
{
V = 1.0f - V;
}
}
return V0 + U * (V1 - V0) + V * (V2 - V0);
}
bool AMRUKAnchor::Raycast(const FVector& Origin, const FVector& Direction, float MaxDist, FMRUKHit& OutHit, int32 ComponentTypes)
{
// If this anchor is the global mesh test against it
if ((ComponentTypes & static_cast<int32>(EMRUKComponentType::Mesh)) != 0 && this == Room->GlobalMeshAnchor)
{
FHitResult GlobalMeshOutHit{};
float Dist = MaxDist;
if (MaxDist <= 0.0)
{
const float WorldToMeters = GetWorld()->GetWorldSettings()->WorldToMeters;
Dist = WorldToMeters * 1024; // 1024 m should cover every scene
}
if (ActorLineTraceSingle(GlobalMeshOutHit, Origin, Origin + Direction * Dist, ECollisionChannel::ECC_WorldDynamic, FCollisionQueryParams::DefaultQueryParam))
{
OutHit.HitPosition = GlobalMeshOutHit.Location;
OutHit.HitNormal = GlobalMeshOutHit.Normal;
OutHit.HitDistance = GlobalMeshOutHit.Distance;
return true;
}
return false;
}
auto Transform = GetTransform();
// Transform the ray into local space
auto InverseTransform = Transform.Inverse();
const auto OriginLocal = InverseTransform.TransformPositionNoScale(Origin);
const auto DirectionLocal = InverseTransform.TransformVectorNoScale(Direction);
FRay LocalRay = FRay(OriginLocal, DirectionLocal);
bool FoundHit = false;
// If this anchor has a plane, hit test against it
if ((ComponentTypes & static_cast<int32>(EMRUKComponentType::Plane)) != 0 && PlaneBounds.bIsValid && RayCastPlane(LocalRay, MaxDist, OutHit))
{
// Update max dist for the volume raycast
MaxDist = OutHit.HitDistance;
FoundHit = true;
}
// If this anchor has a volume, hit test against it
if ((ComponentTypes & static_cast<int32>(EMRUKComponentType::Volume)) != 0 && VolumeBounds.IsValid && RayCastVolume(LocalRay, MaxDist, OutHit))
{
MaxDist = OutHit.HitDistance;
FoundHit = true;
}
return FoundHit;
}
bool AMRUKAnchor::RaycastAll(const FVector& Origin, const FVector& Direction, float MaxDist, TArray<FMRUKHit>& OutHits, int32 ComponentTypes)
{
if ((ComponentTypes & static_cast<int32>(EMRUKComponentType::Mesh)) != 0 && this == Room->GlobalMeshAnchor)
{
FHitResult GlobalMeshOutHit{};
float Dist = MaxDist;
if (MaxDist <= 0.0)
{
const float WorldToMeters = GetWorld()->GetWorldSettings()->WorldToMeters;
Dist = WorldToMeters * 1024; // 1024 m should cover every scene
}
if (ActorLineTraceSingle(GlobalMeshOutHit, Origin, Origin + Direction * Dist, ECollisionChannel::ECC_WorldDynamic, FCollisionQueryParams::DefaultQueryParam))
{
FMRUKHit Hit{};
Hit.HitPosition = GlobalMeshOutHit.Location;
Hit.HitNormal = GlobalMeshOutHit.Normal;
Hit.HitDistance = GlobalMeshOutHit.Distance;
OutHits.Push(Hit);
return true;
}
return false;
}
auto Transform = GetTransform();
// Transform the ray into local space
auto InverseTransform = Transform.Inverse();
const auto OriginLocal = InverseTransform.TransformPositionNoScale(Origin);
const auto DirectionLocal = InverseTransform.TransformVectorNoScale(Direction);
FRay LocalRay = FRay(OriginLocal, DirectionLocal);
bool FoundHit = false;
// If this anchor has a plane, hit test against it
FMRUKHit Hit;
if ((ComponentTypes & static_cast<int32>(EMRUKComponentType::Plane)) != 0 && PlaneBounds.bIsValid && RayCastPlane(LocalRay, MaxDist, Hit))
{
OutHits.Push(Hit);
FoundHit = true;
}
// If this anchor has a volume, hit test against it
if ((ComponentTypes & static_cast<int32>(EMRUKComponentType::Volume)) != 0 && VolumeBounds.IsValid && RayCastVolume(LocalRay, MaxDist, Hit))
{
OutHits.Push(Hit);
FoundHit = true;
}
return FoundHit;
}
void AMRUKAnchor::AttachProceduralMesh(const TArray<FString>& CutHoleLabels, bool GenerateCollision, UMaterialInterface* ProceduralMaterial)
{
AttachProceduralMesh({}, CutHoleLabels, GenerateCollision, ProceduralMaterial);
}
void AMRUKAnchor::AttachProceduralMesh(TArray<FMRUKPlaneUV> PlaneUVAdjustments, const TArray<FString>& CutHoleLabels, bool GenerateCollision, UMaterialInterface* ProceduralMaterial)
{
if (ProceduralMeshComponent)
{
// Procedural mesh already attached
return;
}
ProceduralMeshComponent = NewObject<UProceduralMeshComponent>(this, TEXT("ProceduralMesh"));
ProceduralMeshComponent->SetupAttachment(RootComponent);
ProceduralMeshComponent->RegisterComponent();
GenerateProceduralAnchorMesh(ProceduralMeshComponent, PlaneUVAdjustments, CutHoleLabels, false, GenerateCollision);
for (int32 SectionIndex = 0; SectionIndex < ProceduralMeshComponent->GetNumSections(); ++SectionIndex)
{
ProceduralMeshComponent->SetMaterial(SectionIndex, ProceduralMaterial);
}
}
void AMRUKAnchor::GenerateProceduralAnchorMesh(UProceduralMeshComponent* ProceduralMesh, const TArray<FMRUKPlaneUV>& PlaneUVAdjustments, const TArray<FString>& CutHoleLabels, bool PreferVolume, bool GenerateCollision, double Offset)
{
int SectionIndex = 0;
if (VolumeBounds.IsValid)
{
TArray<FVector> Vertices;
TArray<int32> Triangles;
TArray<FVector> Normals;
TArray<FVector2D> UVs;
TArray<FLinearColor> Colors; // Currently unused
TArray<FProcMeshTangent> Tangents; // Currently unused
constexpr int32 NumVertices = 24;
constexpr int32 NumTriangles = 12;
Vertices.Reserve(NumVertices);
Triangles.Reserve(3 * NumTriangles);
Normals.Reserve(NumVertices);
UVs.Reserve(NumVertices);
FBox VolumeBoundsOffset(VolumeBounds.Min - Offset, VolumeBounds.Max + Offset);
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 2; j++)
{
FVector Normal = FVector::ZeroVector;
if (j == 0)
{
Normal[i] = -1.0f;
}
else
{
Normal[i] = 1.0f;
}
auto BaseIndex = Vertices.Num();
FVector Vertex;
Vertex[i] = VolumeBoundsOffset[j][i];
for (int k = 0; k < 2; k++)
{
for (int l = 0; l < 2; l++)
{
Vertex[(i + 1) % 3] = VolumeBoundsOffset[k][(i + 1) % 3];
Vertex[(i + 2) % 3] = VolumeBoundsOffset[l][(i + 2) % 3];
Vertices.Push(Vertex);
Normals.Push(Normal);
// The 4 side faces of the cube should have their 0, 0 at the top left corner
// when viewed from the outside.
// The top face should have UVs that are consistent with planes to avoid Z fighting
// in case a plane and volume overlap (e.g. in the case of the desk).
FVector2D UV;
switch (i)
{
case 0:
UV = FVector2D(1 - k, 1 - l);
break;
case 1:
UV = FVector2D(k, l);
break;
case 2:
UV = FVector2D(1 - l, k);
break;
default:
UV = FVector2D::Zero();
ensure(0);
}
if (j == 0)
{
UV.X = 1 - UV.X;
}
UVs.Push(UV);
}
}
if (j == 1)
{
Triangles.Push(BaseIndex);
Triangles.Push(BaseIndex + 1);
Triangles.Push(BaseIndex + 2);
Triangles.Push(BaseIndex + 2);
Triangles.Push(BaseIndex + 1);
Triangles.Push(BaseIndex + 3);
}
else
{
Triangles.Push(BaseIndex);
Triangles.Push(BaseIndex + 2);
Triangles.Push(BaseIndex + 1);
Triangles.Push(BaseIndex + 1);
Triangles.Push(BaseIndex + 2);
Triangles.Push(BaseIndex + 3);
}
}
}
ProceduralMesh->CreateMeshSection_LinearColor(SectionIndex++, Vertices, Triangles, Normals, UVs, Colors, Tangents, GenerateCollision);
}
if (PlaneBounds.bIsValid && !(VolumeBounds.IsValid && PreferVolume))
{
TArray<TArray<FVector2f>> Polygons;
TArray<FVector2f> PlaneBoundary;
PlaneBoundary.Reserve(PlaneBoundary2D.Num());
for (const auto Point : PlaneBoundary2D)
{
PlaneBoundary.Push(FVector2f(Point));
}
Polygons.Push(PlaneBoundary);
if (!CutHoleLabels.IsEmpty())
{
for (const auto& ChildAnchor : ChildAnchors)
{
if (!ChildAnchor->HasAnyLabel(CutHoleLabels))
{
continue;
}
if (!ChildAnchor->PlaneBounds.bIsValid)
{
UE_LOG(LogMRUK, Warning, TEXT("Can only cut holes with anchors that have a plane"));
continue;
}
const FVector ChildPositionLS = GetActorTransform().InverseTransformPosition(ChildAnchor->GetActorLocation());
TArray<FVector2f> HoleBoundary;
HoleBoundary.Reserve(ChildAnchor->PlaneBoundary2D.Num());
for (int32 I = ChildAnchor->PlaneBoundary2D.Num() - 1; I >= 0; --I)
{
HoleBoundary.Push(FVector2f(ChildPositionLS.Y, ChildPositionLS.Z) + FVector2f(ChildAnchor->PlaneBoundary2D[I]));
}
Polygons.Push(HoleBoundary);
}
}
TArray<FVector2D> MeshVertices;
TArray<int32> MeshIndices;
MRUKTriangulatePolygon(Polygons, MeshVertices, MeshIndices);
TArray<FVector> Vertices;
TArray<FVector> Normals;
TArray<FVector2D> UV0s;
TArray<FVector2D> UV1s;
TArray<FVector2D> UV2s;
TArray<FVector2D> UV3s;
TArray<FLinearColor> Colors; // Currently unused
TArray<FProcMeshTangent> Tangents;
const int32 NumVertices = MeshVertices.Num();
Normals.Reserve(NumVertices);
UV0s.Reserve(NumVertices);
UV1s.Reserve(NumVertices);
UV2s.Reserve(NumVertices);
UV3s.Reserve(NumVertices);
Tangents.Reserve(NumVertices);
static const FVector Normal = -FVector::XAxisVector;
const FVector NormalOffset = Normal * Offset;
auto BoundsSize = PlaneBounds.GetSize();
for (const auto& PlaneBoundaryVertex : MeshVertices)
{
const FVector Vertex = FVector(0, PlaneBoundaryVertex.X, PlaneBoundaryVertex.Y) + NormalOffset;
Vertices.Push(Vertex);
Normals.Push(Normal);
Tangents.Push(FProcMeshTangent(-FVector::YAxisVector, false));
auto U = (PlaneBoundaryVertex.X - PlaneBounds.Min.X) / BoundsSize.X;
auto V = 1 - (PlaneBoundaryVertex.Y - PlaneBounds.Min.Y) / BoundsSize.Y;
if (PlaneUVAdjustments.Num() == 0)
{
UV0s.Push(FVector2D(U, V));
}
if (PlaneUVAdjustments.Num() >= 1)
{
UV0s.Push(FVector2D(U, V) * PlaneUVAdjustments[0].Scale + PlaneUVAdjustments[0].Offset);
}
if (PlaneUVAdjustments.Num() >= 2)
{
UV1s.Push(FVector2D(U, V) * PlaneUVAdjustments[1].Scale + PlaneUVAdjustments[1].Offset);
}
if (PlaneUVAdjustments.Num() >= 3)
{
UV2s.Push(FVector2D(U, V) * PlaneUVAdjustments[2].Scale + PlaneUVAdjustments[2].Offset);
}
if (PlaneUVAdjustments.Num() >= 4)
{
UV3s.Push(FVector2D(U, V) * PlaneUVAdjustments[3].Scale + PlaneUVAdjustments[3].Offset);
}
}
ProceduralMesh->CreateMeshSection_LinearColor(SectionIndex++, Vertices, MeshIndices, Normals, UV0s, UV1s, UV2s, UV3s, Colors, Tangents, GenerateCollision);
}
}
bool AMRUKAnchor::HasLabel(const FString& Label) const
{
return SemanticClassifications.Contains(Label);
}
bool AMRUKAnchor::HasAnyLabel(const TArray<FString>& Labels) const
{
for (const auto& Label : Labels)
{
if (HasLabel(Label))
{
return true;
}
}
return false;
}
bool AMRUKAnchor::PassesLabelFilter(const FMRUKLabelFilter& LabelFilter) const
{
return LabelFilter.PassesFilter(SemanticClassifications);
}
double AMRUKAnchor::GetClosestSurfacePosition(const FVector& TestPosition, FVector& OutSurfacePosition)
{
const auto& Transform = GetActorTransform();
const auto TestPositionLocal = Transform.InverseTransformPosition(TestPosition);
double ClosestDistance = DBL_MAX;
FVector ClosestPoint = FVector::ZeroVector;
if (PlaneBounds.bIsValid)
{
const auto BestPoint2D = PlaneBounds.GetClosestPointTo(FVector2D(TestPositionLocal.Y, TestPositionLocal.Z));
const FVector BestPoint(0.0, BestPoint2D.X, BestPoint2D.Y);
const auto Distance = FVector::Distance(BestPoint, TestPositionLocal);
if (Distance < ClosestDistance)
{
ClosestPoint = BestPoint;
ClosestDistance = Distance;
}
}
if (VolumeBounds.IsValid)
{
const auto BestPoint = VolumeBounds.GetClosestPointTo(TestPositionLocal);
const auto Distance = FVector::Distance(BestPoint, TestPositionLocal);
if (Distance < ClosestDistance)
{
ClosestPoint = BestPoint;
ClosestDistance = Distance;
}
}
OutSurfacePosition = Transform.TransformPosition(ClosestPoint);
return ClosestDistance;
}
bool AMRUKAnchor::IsPositionInVolumeBounds(const FVector& Position, bool TestVerticalBounds, double Tolerance)
{
if (!VolumeBounds.IsValid)
{
return false;
}
const auto& LocalPosition = GetActorTransform().InverseTransformPosition(Position);
return ((TestVerticalBounds ? ((LocalPosition.X >= VolumeBounds.Min.X - Tolerance) && (LocalPosition.X <= VolumeBounds.Max.X + Tolerance)) : true)
&& (LocalPosition.Y >= VolumeBounds.Min.Y - Tolerance) && (LocalPosition.Y <= VolumeBounds.Max.Y + Tolerance)
&& (LocalPosition.Z >= VolumeBounds.Min.Z - Tolerance) && (LocalPosition.Z <= VolumeBounds.Max.Z + Tolerance));
}
FVector AMRUKAnchor::GetFacingDirection() const
{
if (Room == nullptr)
{
return {};
}
if (!VolumeBounds.IsValid)
{
return GetActorForwardVector();
}
int32 CardinalAxis = 0;
return UMRUKBPLibrary::ComputeDirectionAwayFromClosestWall(this, CardinalAxis, {});
}
AActor* AMRUKAnchor::SpawnInterior(const TSubclassOf<class AActor>& ActorClass, bool MatchAspectRatio, bool CalculateFacingDirection, EMRUKSpawnerScalingMode ScalingMode)
{
Interior = GetWorld()->SpawnActor(ActorClass);
auto InteriorRoot = Interior->GetRootComponent();
if (!InteriorRoot)
{
UE_LOG(LogMRUK, Error, TEXT("SpawnInterior Spawned actor does not have a root component."));
return nullptr;
}
InteriorRoot->SetMobility(EComponentMobility::Movable);
Interior->AttachToComponent(GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform);
Interior->SetActorRelativeScale3D(FVector::OneVector);
const auto ChildLocalBounds = Interior->CalculateComponentsBoundingBoxInLocalSpace(true);
FQuat Rotation = FQuat::Identity;
FVector Offset = FVector::ZeroVector;
FVector Scale = FVector::OneVector;
if (VolumeBounds.IsValid)
{
int CardinalAxisIndex = 0;
if (CalculateFacingDirection && !MatchAspectRatio)
{
// Pick rotation that is pointing away from the closest wall
// If we are also matching the aspect ratio then we only have a choice
// between 2 directions and first need to figure out what those 2 directions
// are before doing the ray casting.
UMRUKBPLibrary::ComputeDirectionAwayFromClosestWall(this, CardinalAxisIndex, {});
}
Rotation = FQuat::MakeFromEuler(FVector(90, -(CardinalAxisIndex + 1) * 90, 90));
FBox ChildBounds = ChildLocalBounds.TransformBy(FTransform(Rotation));
const FVector ChildSize1 = ChildBounds.GetSize();
Scale = VolumeBounds.GetSize() / ChildSize1;
if (MatchAspectRatio)
{
FVector ChildSize2 = ChildSize1;
Swap(ChildSize2.Y, ChildSize2.Z);
FVector Scale2 = VolumeBounds.GetSize() / ChildSize2;
float Distortion1 = FMath::Max(Scale.Y, Scale.Z) / FMath::Min(Scale.Y, Scale.Z);
float Distortion2 = FMath::Max(Scale2.Y, Scale2.Z) / FMath::Min(Scale2.Y, Scale2.Z);
bool FlipToMatchAspectRatio = Distortion1 > Distortion2;
if (FlipToMatchAspectRatio)
{
CardinalAxisIndex = 1;
Scale = Scale2;
}
if (CalculateFacingDirection)
{
UMRUKBPLibrary::ComputeDirectionAwayFromClosestWall(this, CardinalAxisIndex, FlipToMatchAspectRatio ? TArray<int>{ 0, 2 } : TArray<int>{ 1, 3 });
}
if (CardinalAxisIndex != 0)
{
// Update the rotation and child bounds if necessary
Rotation = FQuat::MakeFromEuler(FVector(90, -(CardinalAxisIndex + 1) * 90, 90));
ChildBounds = ChildLocalBounds.TransformBy(FTransform(Rotation));
}
}
switch (ScalingMode)
{
case EMRUKSpawnerScalingMode::UniformScaling:
Scale.X = Scale.Y = Scale.Z = FMath::Min3(Scale.X, Scale.Y, Scale.Z);
break;
case EMRUKSpawnerScalingMode::UniformXYScale:
Scale.Y = Scale.Z = FMath::Min(Scale.Y, Scale.Z);
break;
case EMRUKSpawnerScalingMode::NoScaling:
Scale = FVector::OneVector;
break;
case EMRUKSpawnerScalingMode::Stretch:
// Nothing to do
break;
}
// Calculate the offset between the base of the two bounding boxes. Note that the anchor is on the
// top of the volume and the X axis points downwards. So the base is at Max.X.
FVector VolumeBase = FVector(VolumeBounds.Max.X, 0.5 * (VolumeBounds.Min.Y + VolumeBounds.Max.Y), 0.5 * (VolumeBounds.Min.Z + VolumeBounds.Max.Z));
FVector ChildBase = FVector(ChildBounds.Max.X, 0.5 * (ChildBounds.Min.Y + ChildBounds.Max.Y), 0.5 * (ChildBounds.Min.Z + ChildBounds.Max.Z));
Offset = VolumeBase - ChildBase * Scale;
}
else if (PlaneBounds.bIsValid)
{
const auto XAxis = GetTransform().GetUnitAxis(EAxis::X);
// Adjust the rotation so that Z always points up. This enables assets to be authored in a more natural
// way and show up in the scene as expected.
if (XAxis.Z <= -UE_INV_SQRT_2)
{
// This is a floor or other surface facing upwards
Rotation = FQuat::MakeFromEuler(FVector(0, 90, 0));
}
else if (XAxis.Z >= UE_INV_SQRT_2)
{
// This is ceiling or other surface facing downwards.
Rotation = FQuat::MakeFromEuler(FVector(0, -90, 0));
}
else
{
// This is a wall or other upright surface.
Rotation = FQuat::MakeFromEuler(FVector(0, 0, 180));
}
const auto ChildBounds = ChildLocalBounds.TransformBy(FTransform(Rotation));
const auto ChildBounds2D = FBox2D(FVector2D(ChildBounds.Min.Y, ChildBounds.Min.Z), FVector2D(ChildBounds.Max.Y, ChildBounds.Max.Z));
auto Scale2D = PlaneBounds.GetSize() / ChildBounds2D.GetSize();
switch (ScalingMode)
{
case EMRUKSpawnerScalingMode::UniformScaling:
case EMRUKSpawnerScalingMode::UniformXYScale:
Scale2D.X = Scale2D.Y = FMath::Min(Scale2D.X, Scale2D.Y);
break;
case EMRUKSpawnerScalingMode::NoScaling:
Scale2D = FVector2D::UnitVector;
break;
case EMRUKSpawnerScalingMode::Stretch:
// Nothing to do
break;
}
const auto Offset2D = PlaneBounds.GetCenter() - ChildBounds2D.GetCenter() * Scale2D;
Offset = FVector(0.0, Offset2D.X, Offset2D.Y);
Scale = FVector(0.5 * (Scale2D.X + Scale2D.Y), Scale2D.X, Scale2D.Y);
}
Interior->SetActorRelativeRotation(Rotation);
Interior->SetActorRelativeLocation(Offset);
UMRUKBPLibrary::SetScaleRecursivelyAdjustingForRotation(InteriorRoot, Scale);
return Interior;
}
TSharedRef<FJsonObject> AMRUKAnchor::JsonSerialize()
{
TSharedRef<FJsonObject> JsonObject = MakeShareable(new FJsonObject);
JsonObject->SetField(TEXT("UUID"), MRUKSerialize(AnchorUUID));
JsonObject->SetField(TEXT("SemanticClassifications"), MRUKSerialize(SemanticClassifications));
JsonObject->SetField(TEXT("Transform"), MRUKSerialize(GetTransform()));
if (PlaneBounds.bIsValid)
{
JsonObject->SetField(TEXT("PlaneBounds"), MRUKSerialize(PlaneBounds));
}
if (!PlaneBoundary2D.IsEmpty())
{
JsonObject->SetField(TEXT("PlaneBoundary2D"), MRUKSerialize(PlaneBoundary2D));
}
if (VolumeBounds.IsValid)
{
JsonObject->SetField(TEXT("VolumeBounds"), MRUKSerialize(VolumeBounds));
}
if (this == Room->GlobalMeshAnchor)
{
TArray<UProceduralMeshComponent*> ProcMeshComponents;
GetComponents<UProceduralMeshComponent>(ProcMeshComponents);
for (const auto& Component : ProcMeshComponents)
{
const auto ProcMeshComponent = Cast<UProceduralMeshComponent>(Component);
if (ProcMeshComponent && ProcMeshComponent->ComponentHasTag("GlobalMesh"))
{
ensure(ProcMeshComponent->GetNumSections() == 1);
auto GlobalMeshJson = MakeShared<FJsonObject>();
GlobalMeshJson->SetField(TEXT("UUID"), MRUKSerialize(AnchorUUID));
const auto ProcMeshSection = ProcMeshComponent->GetProcMeshSection(0);
TArray<TSharedPtr<FJsonValue>> PositionsJson;
for (const auto& Vertex : ProcMeshSection->ProcVertexBuffer)
{
PositionsJson.Add(MRUKSerialize(Vertex.Position));
}
GlobalMeshJson->SetArrayField(TEXT("Positions"), PositionsJson);
TArray<TSharedPtr<FJsonValue>> IndicesJson;
for (const auto& Index : ProcMeshSection->ProcIndexBuffer)
{
IndicesJson.Add(MakeShared<FJsonValueNumber>(Index));
}
GlobalMeshJson->SetArrayField(TEXT("Indices"), IndicesJson);
JsonObject->SetObjectField(TEXT("GlobalMesh"), GlobalMeshJson);
}
}
}
return JsonObject;
}
void AMRUKAnchor::EndPlay(EEndPlayReason::Type Reason)
{
if (Interior)
{
Interior->Destroy();
}
Super::EndPlay(Reason);
}
bool AMRUKAnchor::RayCastPlane(const FRay& LocalRay, float MaxDist, FMRUKHit& OutHit)
{
// If the ray is behind or parallel to the anchor's plane then ignore it
if (LocalRay.Direction.X >= UE_KINDA_SMALL_NUMBER)
{
// Distance to the plane from the ray origin along the ray's direction
const float Dist = -LocalRay.Origin.X / LocalRay.Direction.X;
// If the distance is negative or less than the maximum distance then ignore it
if (Dist >= 0.0f && (MaxDist <= 0 || Dist < MaxDist))
{
const FVector HitPos = LocalRay.PointAt(Dist);
// Ensure the hit is within the plane extends and within the boundary
const FVector2D Pos2D(HitPos.Y, HitPos.Z);
if (PlaneBounds.IsInside(Pos2D) && IsPositionInBoundary(Pos2D))
{
// Transform the result back into world space
const auto Transform = GetTransform();
OutHit.HitPosition = Transform.TransformPositionNoScale(HitPos);
OutHit.HitNormal = Transform.TransformVectorNoScale(-FVector::XAxisVector);
OutHit.HitDistance = Dist;
return true;
}
}
}
return false;
}
bool AMRUKAnchor::RayCastVolume(const FRay& LocalRay, float MaxDist, FMRUKHit& OutHit)
{
// Use the slab method to determine if the ray intersects with the bounding box
// https://education.siggraph.org/static/HyperGraph/raytrace/rtinter3.htm
float DistNear = -UE_BIG_NUMBER, DistFar = UE_BIG_NUMBER;
int HitAxis = 0;
for (int i = 0; i < 3; ++i)
{
if (FMath::Abs(LocalRay.Direction.Component(i)) >= UE_KINDA_SMALL_NUMBER)
{
// Distance to the plane from the ray origin along the ray's direction
float Dist1 = (VolumeBounds.Min.Component(i) - LocalRay.Origin.Component(i)) / LocalRay.Direction.Component(i);
float Dist2 = (VolumeBounds.Max.Component(i) - LocalRay.Origin.Component(i)) / LocalRay.Direction.Component(i);
if (Dist1 > Dist2)
{
std::swap(Dist1, Dist2);
}
if (Dist1 > DistNear)
{
DistNear = Dist1;
HitAxis = i;
}
if (Dist2 < DistFar)
{
DistFar = Dist2;
}
}
else
{
// In this case there is no intersection because the ray is parallel to the plane
// Check that it is within bounds
if (LocalRay.Origin.Component(i) < VolumeBounds.Min.Component(i) || LocalRay.Origin.Component(i) > VolumeBounds.Max.Component(i))
{
// No intersection, set DistNear to a large number
DistNear = UE_BIG_NUMBER;
break;
}
}
}
if (DistNear >= 0 && DistNear <= DistFar && (MaxDist <= 0 || DistNear < MaxDist))
{
const FVector HitPos = LocalRay.PointAt(DistNear);
FVector HitNormal = FVector::ZeroVector;
HitNormal.Component(HitAxis) = LocalRay.Direction.Component(HitAxis) > 0 ? -1 : 1;
// Transform the result back into world space
const auto Transform = GetTransform();
OutHit.HitPosition = Transform.TransformPositionNoScale(HitPos);
OutHit.HitNormal = Transform.TransformVectorNoScale(HitNormal);
OutHit.HitDistance = DistNear;
return true;
}
return false;
}
void AMRUKAnchor::TriangulatedMeshCache::Clear()
{
Vertices.Empty();
Triangles.Empty();
Areas.Empty();
TotalArea = 0.0f;
}
// #pragma optimize("", on)
#undef LOCTEXT_NAMESPACE

View File

@ -1,738 +0,0 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#include "MRUtilityKitAnchorActorSpawner.h"
#include "MRUtilityKitAnchor.h"
#include "MRUtilityKitTelemetry.h"
#include "MRUtilityKitSubsystem.h"
#include "MRUtilityKitBPLibrary.h"
#include "GameFramework/WorldSettings.h"
#include "Engine/GameInstance.h"
const FName GMRUK_PROCEDURAL_ANCHOR_MESH_TAG = TEXT("MRUKProceduralAnchorMesh");
namespace
{
AActor* SpawnProceduralMesh(AMRUKAnchor* Anchor, const TArray<FMRUKPlaneUV>& PlaneUVAdjustments, const TArray<FString>& CutHoleLabels, UMaterialInterface* Material)
{
AActor* Actor = Anchor->GetWorld()->SpawnActor<AActor>();
Actor->SetOwner(Anchor);
Actor->Tags.AddUnique(GMRUK_PROCEDURAL_ANCHOR_MESH_TAG);
Actor->SetRootComponent(NewObject<USceneComponent>(Actor, TEXT("Root")));
Actor->GetRootComponent()->SetMobility(EComponentMobility::Movable);
Actor->AttachToComponent(Anchor->GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform);
Actor->SetActorRelativeScale3D(FVector::OneVector);
UProceduralMeshComponent* ProceduralMeshComponent = NewObject<UProceduralMeshComponent>(Actor, TEXT("ProceduralMesh"));
ProceduralMeshComponent->SetupAttachment(Actor->GetRootComponent());
ProceduralMeshComponent->RegisterComponent();
Actor->AddInstanceComponent(ProceduralMeshComponent);
Anchor->GenerateProceduralAnchorMesh(ProceduralMeshComponent, PlaneUVAdjustments, CutHoleLabels, false, true);
for (int32 SectionIndex = 0; SectionIndex < ProceduralMeshComponent->GetNumSections(); ++SectionIndex)
{
ProceduralMeshComponent->SetMaterial(SectionIndex, Material);
}
return Actor;
}
} // namespace
void AMRUKAnchorActorSpawner::BeginPlay()
{
Super::BeginPlay();
OculusXRTelemetry::TScopedMarker<MRUKTelemetry::FLoadAnchorActorSpawnerMarker> Event(static_cast<int>(GetTypeHash(this)));
if (SpawnMode == EMRUKSpawnMode::CurrentRoomOnly)
{
const auto Subsystem = GetGameInstance()->GetSubsystem<UMRUKSubsystem>();
if (Subsystem->SceneLoadStatus == EMRUKInitStatus::Complete)
{
SpawnActors(Subsystem->GetCurrentRoom());
}
else
{
// Only listen for the room created event in case no current room was available yet
Subsystem->OnRoomCreated.AddUniqueDynamic(this, &AMRUKAnchorActorSpawner::OnRoomCreated);
}
}
else if (SpawnMode == EMRUKSpawnMode::AllRooms)
{
const auto Subsystem = GetGameInstance()->GetSubsystem<UMRUKSubsystem>();
for (auto Room : Subsystem->Rooms)
{
SpawnActors(Room);
}
// Listen for new rooms that get created
Subsystem->OnRoomCreated.AddUniqueDynamic(this, &AMRUKAnchorActorSpawner::OnRoomCreated);
}
}
void AMRUKAnchorActorSpawner::OnRoomCreated(AMRUKRoom* Room)
{
if (SpawnMode == EMRUKSpawnMode::CurrentRoomOnly && GetGameInstance()->GetSubsystem<UMRUKSubsystem>()->GetCurrentRoom() != Room)
{
// Skip this room if it is not the current room
return;
}
SpawnActors(Room);
}
void AMRUKAnchorActorSpawner::OnRoomUpdated(AMRUKRoom* Room)
{
if (!SpawnedActors.Find(Room))
{
// A room was updated that we don't care about. If we are in current room only mode
// we only want to update the one room we created
return;
}
SpawnActors(Room);
}
void AMRUKAnchorActorSpawner::OnRoomRemoved(AMRUKRoom* Room)
{
RemoveActors(Room);
}
void AMRUKAnchorActorSpawner::RemoveActors(AMRUKRoom* Room)
{
if (!IsValid(Room))
{
UE_LOG(LogMRUK, Warning, TEXT("Can not remove actors from room that is a nullptr"));
return;
}
if (TArray<AActor*>* Actors = SpawnedActors.Find(Room))
{
for (AActor* Actor : *Actors)
{
if (IsValid(Actor))
{
Actor->Destroy();
}
}
Actors->Empty();
SpawnedActors.Remove(Room);
}
}
bool AMRUKAnchorActorSpawner::ShouldAnchorFallbackToProceduralMesh(const FMRUKSpawnGroup& SpawnGroup) const
{
switch (SpawnGroup.FallbackToProcedural)
{
case EMRUKFallbackToProceduralOverwrite::Default:
return ShouldFallbackToProcedural;
case EMRUKFallbackToProceduralOverwrite::Fallback:
return true;
case EMRUKFallbackToProceduralOverwrite::NoFallback:
return false;
}
return false;
}
TArray<AActor*> AMRUKAnchorActorSpawner::SpawnProceduralMeshesOnWallsIfNoWallActorGiven(AMRUKRoom* Room)
{
TArray<AActor*> Actors;
const auto WallFace = SpawnGroups.Find(FMRUKLabels::WallFace);
if (!WallFace || (WallFace->Actors.IsEmpty() && ShouldAnchorFallbackToProceduralMesh(*WallFace)))
{
// If no wall mesh is given we want to spawn the walls procedural to make seamless UVs
TArray<FMRUKAnchorWithPlaneUVs> AnchorsWithPlaneUVs;
Room->ComputeWallMeshUVAdjustments({}, AnchorsWithPlaneUVs);
for (const auto& AnchorWithPlaneUVs : AnchorsWithPlaneUVs)
{
Actors.Push(SpawnProceduralMesh(AnchorWithPlaneUVs.Anchor, AnchorWithPlaneUVs.PlaneUVs, CutHoleLabels, ProceduralMaterial));
}
}
return Actors;
}
AActor* AMRUKAnchorActorSpawner::SpawnProceduralMeshOnFloorIfNoFloorActorGiven(AMRUKRoom* Room)
{
const auto Floor = SpawnGroups.Find(FMRUKLabels::Floor);
if (Room->FloorAnchor && (!Floor || (Floor->Actors.IsEmpty() && ShouldAnchorFallbackToProceduralMesh(*Floor))))
{
// Use metric scaling to match walls
const float WorldToMeters = GetWorldSettings()->WorldToMeters;
const FVector2D Scale = Room->FloorAnchor->PlaneBounds.GetSize() / WorldToMeters;
const TArray<FMRUKPlaneUV> PlaneUVAdj = { { FVector2D::ZeroVector, Scale } };
return SpawnProceduralMesh(Room->FloorAnchor, PlaneUVAdj, CutHoleLabels, ProceduralMaterial);
}
return nullptr;
}
AActor* AMRUKAnchorActorSpawner::SpawnProceduralMeshOnCeilingIfNoCeilingActorGiven(AMRUKRoom* Room)
{
const auto Ceiling = SpawnGroups.Find(FMRUKLabels::Ceiling);
if (Room->CeilingAnchor && (!Ceiling || (Ceiling->Actors.IsEmpty() && ShouldAnchorFallbackToProceduralMesh(*Ceiling))))
{
// Use metric scaling to match walls
const float WorldToMeters = GetWorldSettings()->WorldToMeters;
const FVector2D Scale = Room->CeilingAnchor->PlaneBounds.GetSize() / WorldToMeters;
const TArray<FMRUKPlaneUV> PlaneUVAdj = { { FVector2D::ZeroVector, Scale } };
return SpawnProceduralMesh(Room->CeilingAnchor, PlaneUVAdj, CutHoleLabels, ProceduralMaterial);
}
return nullptr;
}
AActor* AMRUKAnchorActorSpawner::SpawnProceduralMeshForAnchorIfNeeded(AMRUKAnchor* Anchor)
{
if (!IsValid(Anchor))
{
return nullptr;
}
if (Anchor->SemanticClassifications.IsEmpty())
{
// For unknown scene objects spawn a procedural mesh (should not happen in practice)
return SpawnProceduralMesh(Anchor, {}, CutHoleLabels, ProceduralMaterial);
}
for (const FString& Label : Anchor->SemanticClassifications)
{
if (Label == FMRUKLabels::WallFace && Anchor->SemanticClassifications.Contains(FMRUKLabels::InvisibleWallFace))
{
// Treat anchors with WALL_FACE and INVISIBLE_WALL_FACE as anchors that only have INVISIBLE_WALL_FACE
continue;
}
const FMRUKSpawnGroup* SpawnGroup = SpawnGroups.Find(Label);
if (SpawnGroup && SpawnGroup->Actors.IsEmpty() && ShouldAnchorFallbackToProceduralMesh(*SpawnGroup))
{
return SpawnProceduralMesh(Anchor, {}, CutHoleLabels, ProceduralMaterial);
}
}
return nullptr;
}
TArray<AActor*> AMRUKAnchorActorSpawner::SpawnProceduralMeshesInRoom(AMRUKRoom* Room)
{
TArray<AActor*> Actors;
const TArray<AActor*> WallActors = SpawnProceduralMeshesOnWallsIfNoWallActorGiven(Room);
if (!WallActors.IsEmpty())
{
Actors.Append(WallActors);
}
AActor* Actor = nullptr;
Actor = SpawnProceduralMeshOnFloorIfNoFloorActorGiven(Room);
if (Actor)
{
Actors.Push(Actor);
}
Actor = SpawnProceduralMeshOnCeilingIfNoCeilingActorGiven(Room);
if (Actor)
{
Actors.Push(Actor);
}
for (const auto& Anchor : Room->AllAnchors)
{
if (Anchor->HasLabel(FMRUKLabels::Floor) || Anchor->HasLabel(FMRUKLabels::Ceiling) || Anchor->HasLabel(FMRUKLabels::WallFace))
{
// These have already been spawned above in case it was necessary
continue;
}
Actor = SpawnProceduralMeshForAnchorIfNeeded(Anchor);
if (Actor)
{
Actors.Push(Actor);
}
}
return Actors;
}
bool AMRUKAnchorActorSpawner::SelectSpawnActorClosestSize(AMRUKAnchor* Anchor, const FMRUKSpawnGroup& SpawnGroup, FMRUKSpawnActor& OutSpawnActor)
{
if (SpawnGroup.Actors.IsEmpty())
{
return false;
}
int Index = 0;
if (SpawnGroup.Actors.Num() > 1)
{
if (Anchor->VolumeBounds.IsValid)
{
const double AnchorSize = FMath::Pow(Anchor->VolumeBounds.GetVolume(), 1.0 / 3.0);
double ClosestSizeDifference = UE_BIG_NUMBER;
for (int i = 0; i < SpawnGroup.Actors.Num(); ++i)
{
const auto& SpawnActor = SpawnGroup.Actors[i];
const auto Subsystem = GetGameInstance()->GetSubsystem<UMRUKSubsystem>();
auto Bounds = Subsystem->GetActorClassBounds(SpawnActor.Actor);
if (Bounds.IsValid)
{
const double SpawnActorSize = FMath::Pow(Bounds.GetVolume(), 1.0 / 3.0);
const double SizeDifference = FMath::Abs(AnchorSize - SpawnActorSize);
if (SizeDifference < ClosestSizeDifference)
{
ClosestSizeDifference = SizeDifference;
Index = i;
}
}
}
}
}
OutSpawnActor = SpawnGroup.Actors[Index];
return true;
}
bool AMRUKAnchorActorSpawner::SelectSpawnActorRandom(const FMRUKSpawnGroup& SpawnGroup, const FRandomStream& RandomStream, FMRUKSpawnActor& OutSpawnActor)
{
if (SpawnGroup.Actors.IsEmpty())
{
return false;
}
const int Index = RandomStream.RandRange(0, SpawnGroup.Actors.Num() - 1);
OutSpawnActor = SpawnGroup.Actors[Index];
return true;
}
bool AMRUKAnchorActorSpawner::SelectSpawnActorFromSpawnGroup(AMRUKAnchor* Anchor, const FMRUKSpawnGroup& SpawnGroup, const FRandomStream& RandomStream, FMRUKSpawnActor& OutSpawnActor)
{
if (SpawnGroup.Actors.IsEmpty())
{
return false;
}
if (SpawnGroup.SelectionMode == EMRUKSpawnerSelectionMode::Random)
{
return SelectSpawnActorRandom(SpawnGroup, RandomStream, OutSpawnActor);
}
if (SpawnGroup.SelectionMode == EMRUKSpawnerSelectionMode::ClosestSize)
{
return SelectSpawnActorClosestSize(Anchor, SpawnGroup, OutSpawnActor);
}
if (SpawnGroup.SelectionMode == EMRUKSpawnerSelectionMode::Custom)
{
return SelectSpawnActorCustom(Anchor, SpawnGroup, RandomStream, OutSpawnActor);
}
OutSpawnActor = SpawnGroup.Actors[0];
return true;
}
void AMRUKAnchorActorSpawner::AttachAndFitActorToAnchor(AMRUKAnchor* Anchor, AActor* Actor, EMRUKSpawnerScalingMode ScalingMode, EMRUKAlignMode AlignMode, bool bCalculateFacingDirection, bool bMatchAspectRatio)
{
auto ActorRoot = Actor->GetRootComponent();
if (!ActorRoot)
{
UE_LOG(LogMRUK, Error, TEXT("Spawned actor does not have a root component."));
return;
}
ActorRoot->SetMobility(EComponentMobility::Movable);
Actor->AttachToComponent(Anchor->GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform);
Actor->SetActorRelativeScale3D(FVector::OneVector);
const auto ChildLocalBounds = Actor->CalculateComponentsBoundingBoxInLocalSpace(true);
FQuat Rotation = FQuat::Identity;
FVector Offset = FVector::ZeroVector;
FVector Scale = FVector::OneVector;
if (Anchor->VolumeBounds.IsValid)
{
int CardinalAxisIndex = 0;
if (bCalculateFacingDirection && !bMatchAspectRatio)
{
// Pick rotation that is pointing away from the closest wall
// If we are also matching the aspect ratio then we only have a choice
// between 2 directions and first need to figure out what those 2 directions
// are before doing the ray casting.
UMRUKBPLibrary::ComputeDirectionAwayFromClosestWall(Anchor, CardinalAxisIndex, {});
}
Rotation = FQuat::MakeFromEuler(FVector(90, -(CardinalAxisIndex + 1) * 90, 90));
FBox ChildBounds = ChildLocalBounds.TransformBy(FTransform(Rotation));
const FVector ChildSize1 = ChildBounds.GetSize();
Scale = Anchor->VolumeBounds.GetSize() / ChildSize1;
if (bMatchAspectRatio)
{
FVector ChildSize2 = ChildSize1;
Swap(ChildSize2.Y, ChildSize2.Z);
FVector Scale2 = Anchor->VolumeBounds.GetSize() / ChildSize2;
float Distortion1 = FMath::Max(Scale.Y, Scale.Z) / FMath::Min(Scale.Y, Scale.Z);
float Distortion2 = FMath::Max(Scale2.Y, Scale2.Z) / FMath::Min(Scale2.Y, Scale2.Z);
bool FlipToMatchAspectRatio = Distortion1 > Distortion2;
if (FlipToMatchAspectRatio)
{
CardinalAxisIndex = 1;
Scale = Scale2;
}
if (bCalculateFacingDirection)
{
UMRUKBPLibrary::ComputeDirectionAwayFromClosestWall(Anchor, CardinalAxisIndex, FlipToMatchAspectRatio ? TArray<int>{ 0, 2 } : TArray<int>{ 1, 3 });
}
if (CardinalAxisIndex != 0)
{
// Update the rotation and child bounds if necessary
Rotation = FQuat::MakeFromEuler(FVector(90, -(CardinalAxisIndex + 1) * 90, 90));
ChildBounds = ChildLocalBounds.TransformBy(FTransform(Rotation));
}
}
switch (ScalingMode)
{
case EMRUKSpawnerScalingMode::UniformScaling:
Scale.X = Scale.Y = Scale.Z = FMath::Min3(Scale.X, Scale.Y, Scale.Z);
break;
case EMRUKSpawnerScalingMode::UniformXYScale:
Scale.Y = Scale.Z = FMath::Min(Scale.Y, Scale.Z);
break;
case EMRUKSpawnerScalingMode::NoScaling:
Scale = FVector::OneVector;
break;
case EMRUKSpawnerScalingMode::Stretch:
// Nothing to do
break;
case EMRUKSpawnerScalingMode::Custom:
Scale = ComputeCustomScaling(Anchor, Actor, Scale);
break;
}
if (AlignMode == EMRUKAlignMode::Custom)
{
Offset = ComputeCustomAlign(Anchor, Actor, ChildBounds, Scale);
}
else if (AlignMode != EMRUKAlignMode::None)
{
FVector ChildBase;
FVector VolumeBase;
switch (AlignMode)
{
case EMRUKAlignMode::CenterOnCenter:
ChildBase = FVector(0.5 * (ChildBounds.Min.X + ChildBounds.Max.X), 0.5 * (ChildBounds.Min.Y + ChildBounds.Max.Y), 0.5 * (ChildBounds.Min.Z + ChildBounds.Max.Z));
break;
case EMRUKAlignMode::TopOnTop:
case EMRUKAlignMode::TopOnBottom:
ChildBase = FVector(ChildBounds.Min.X, 0.5 * (ChildBounds.Min.Y + ChildBounds.Max.Y), 0.5 * (ChildBounds.Min.Z + ChildBounds.Max.Z));
break;
case EMRUKAlignMode::Default:
case EMRUKAlignMode::BottomOnBottom:
case EMRUKAlignMode::BottomOnTop:
ChildBase = FVector(ChildBounds.Max.X, 0.5 * (ChildBounds.Min.Y + ChildBounds.Max.Y), 0.5 * (ChildBounds.Min.Z + ChildBounds.Max.Z));
break;
case EMRUKAlignMode::LeftOnLeft:
case EMRUKAlignMode::LeftOnRight:
ChildBase = FVector(0.5 * (ChildBounds.Min.X + ChildBounds.Max.X), 0.5 * (ChildBounds.Min.Y + ChildBounds.Max.Y), ChildBounds.Max.Z);
break;
case EMRUKAlignMode::RightOnRight:
case EMRUKAlignMode::RightOnLeft:
ChildBase = FVector(0.5 * (ChildBounds.Min.X + ChildBounds.Max.X), 0.5 * (ChildBounds.Min.Y + ChildBounds.Max.Y), ChildBounds.Min.Z);
break;
case EMRUKAlignMode::FrontOnFront:
case EMRUKAlignMode::FrontOnBack:
ChildBase = FVector(0.5 * (ChildBounds.Min.X + ChildBounds.Max.X), ChildBounds.Max.Y, 0.5 * (ChildBounds.Min.Z + ChildBounds.Max.Z));
break;
case EMRUKAlignMode::BackOnBack:
case EMRUKAlignMode::BackOnFront:
ChildBase = FVector(0.5 * (ChildBounds.Min.X + ChildBounds.Max.X), ChildBounds.Min.Y, 0.5 * (ChildBounds.Min.Z + ChildBounds.Max.Z));
break;
}
switch (AlignMode)
{
case EMRUKAlignMode::CenterOnCenter:
VolumeBase = FVector(0.5 * (Anchor->VolumeBounds.Min.X + Anchor->VolumeBounds.Max.X), 0.5 * (Anchor->VolumeBounds.Min.Y + Anchor->VolumeBounds.Max.Y), 0.5 * (Anchor->VolumeBounds.Min.Z + Anchor->VolumeBounds.Max.Z));
break;
case EMRUKAlignMode::TopOnTop:
case EMRUKAlignMode::BottomOnTop:
VolumeBase = FVector(Anchor->VolumeBounds.Min.X, 0.5 * (Anchor->VolumeBounds.Min.Y + Anchor->VolumeBounds.Max.Y), 0.5 * (Anchor->VolumeBounds.Min.Z + Anchor->VolumeBounds.Max.Z));
break;
case EMRUKAlignMode::Default:
case EMRUKAlignMode::BottomOnBottom:
case EMRUKAlignMode::TopOnBottom:
VolumeBase = FVector(Anchor->VolumeBounds.Max.X, 0.5 * (Anchor->VolumeBounds.Min.Y + Anchor->VolumeBounds.Max.Y), 0.5 * (Anchor->VolumeBounds.Min.Z + Anchor->VolumeBounds.Max.Z));
break;
case EMRUKAlignMode::LeftOnLeft:
case EMRUKAlignMode::RightOnLeft:
VolumeBase = FVector(0.5 * (Anchor->VolumeBounds.Min.X + Anchor->VolumeBounds.Max.X), 0.5 * (Anchor->VolumeBounds.Min.Y + Anchor->VolumeBounds.Max.Y), Anchor->VolumeBounds.Max.Z);
break;
case EMRUKAlignMode::RightOnRight:
case EMRUKAlignMode::LeftOnRight:
VolumeBase = FVector(0.5 * (Anchor->VolumeBounds.Min.X + Anchor->VolumeBounds.Max.X), 0.5 * (Anchor->VolumeBounds.Min.Y + Anchor->VolumeBounds.Max.Y), Anchor->VolumeBounds.Min.Z);
break;
case EMRUKAlignMode::FrontOnFront:
case EMRUKAlignMode::BackOnFront:
VolumeBase = FVector(0.5 * (Anchor->VolumeBounds.Min.X + Anchor->VolumeBounds.Max.X), Anchor->VolumeBounds.Max.Y, 0.5 * (Anchor->VolumeBounds.Min.Z + Anchor->VolumeBounds.Max.Z));
break;
case EMRUKAlignMode::BackOnBack:
case EMRUKAlignMode::FrontOnBack:
VolumeBase = FVector(0.5 * (Anchor->VolumeBounds.Min.X + Anchor->VolumeBounds.Max.X), Anchor->VolumeBounds.Min.Y, 0.5 * (Anchor->VolumeBounds.Min.Z + Anchor->VolumeBounds.Max.Z));
break;
}
Offset = VolumeBase - ChildBase * Scale;
}
}
else if (Anchor->PlaneBounds.bIsValid)
{
const auto XAxis = Anchor->GetTransform().GetUnitAxis(EAxis::X);
// Adjust the rotation so that Z always points up. This enables assets to be authored in a more natural
// way and show up in the scene as expected.
if (XAxis.Z <= -UE_INV_SQRT_2)
{
// This is a floor or other surface facing upwards
Rotation = FQuat::MakeFromEuler(FVector(0, 90, 0));
}
else if (XAxis.Z >= UE_INV_SQRT_2)
{
// This is ceiling or other surface facing downwards.
Rotation = FQuat::MakeFromEuler(FVector(0, -90, 0));
}
else
{
// This is a wall or other upright surface.
Rotation = FQuat::MakeFromEuler(FVector(0, 0, 180));
}
const auto ChildBounds = ChildLocalBounds.TransformBy(FTransform(Rotation));
const auto ChildBounds2D = FBox2D(FVector2D(ChildBounds.Min.Y, ChildBounds.Min.Z), FVector2D(ChildBounds.Max.Y, ChildBounds.Max.Z));
auto Scale2D = Anchor->PlaneBounds.GetSize() / ChildBounds2D.GetSize();
switch (ScalingMode)
{
case EMRUKSpawnerScalingMode::UniformScaling:
case EMRUKSpawnerScalingMode::UniformXYScale:
Scale2D.X = Scale2D.Y = FMath::Min(Scale2D.X, Scale2D.Y);
break;
case EMRUKSpawnerScalingMode::NoScaling:
Scale2D = FVector2D::UnitVector;
break;
case EMRUKSpawnerScalingMode::Stretch:
// Nothing to do
break;
case EMRUKSpawnerScalingMode::Custom:
const FVector S = ComputeCustomScaling(Anchor, Actor, FVector(Scale2D.X, Scale2D.Y, 0.0));
Scale2D.X = S.X;
Scale2D.Y = S.Y;
break;
}
FVector2D Offset2D = FVector2D::ZeroVector;
switch (AlignMode)
{
case EMRUKAlignMode::None:
case EMRUKAlignMode::BackOnBack:
case EMRUKAlignMode::FrontOnFront:
case EMRUKAlignMode::FrontOnBack:
case EMRUKAlignMode::BackOnFront:
Offset = FVector::ZeroVector;
break;
case EMRUKAlignMode::Default:
case EMRUKAlignMode::CenterOnCenter:
Offset2D = Anchor->PlaneBounds.GetCenter() - ChildBounds2D.GetCenter() * Scale2D;
break;
case EMRUKAlignMode::BottomOnBottom:
Offset2D = FVector2D(Anchor->PlaneBounds.GetCenter().X, Anchor->PlaneBounds.Min.Y) - FVector2D(ChildBounds2D.GetCenter().X, ChildBounds2D.Min.Y) * Scale2D;
break;
case EMRUKAlignMode::TopOnTop:
Offset2D = FVector2D(Anchor->PlaneBounds.GetCenter().X, Anchor->PlaneBounds.Max.Y) - FVector2D(ChildBounds2D.GetCenter().X, ChildBounds2D.Max.Y) * Scale2D;
break;
case EMRUKAlignMode::LeftOnLeft:
Offset2D = FVector2D(Anchor->PlaneBounds.Max.X, Anchor->PlaneBounds.GetCenter().Y) - FVector2D(ChildBounds2D.Max.X, ChildBounds2D.GetCenter().Y) * Scale2D;
break;
case EMRUKAlignMode::RightOnRight:
Offset2D = FVector2D(Anchor->PlaneBounds.Min.X, Anchor->PlaneBounds.GetCenter().Y) - FVector2D(ChildBounds2D.Min.X, ChildBounds2D.GetCenter().Y) * Scale2D;
break;
case EMRUKAlignMode::BottomOnTop:
Offset2D = FVector2D(Anchor->PlaneBounds.GetCenter().X, Anchor->PlaneBounds.Max.Y) - FVector2D(ChildBounds2D.GetCenter().X, ChildBounds2D.Min.Y) * Scale2D;
break;
case EMRUKAlignMode::TopOnBottom:
Offset2D = FVector2D(Anchor->PlaneBounds.GetCenter().X, Anchor->PlaneBounds.Min.Y) - FVector2D(ChildBounds2D.GetCenter().X, ChildBounds2D.Max.Y) * Scale2D;
break;
case EMRUKAlignMode::LeftOnRight:
Offset2D = FVector2D(Anchor->PlaneBounds.Min.X, Anchor->PlaneBounds.GetCenter().Y) - FVector2D(ChildBounds2D.Max.X, ChildBounds2D.GetCenter().Y) * Scale2D;
break;
case EMRUKAlignMode::RightOnLeft:
Offset2D = FVector2D(Anchor->PlaneBounds.Max.X, Anchor->PlaneBounds.GetCenter().Y) - FVector2D(ChildBounds2D.Min.X, ChildBounds2D.GetCenter().Y) * Scale2D;
break;
case EMRUKAlignMode::Custom:
Offset = ComputeCustomAlign(Anchor, Actor, FBox(FVector(ChildBounds2D.Min, 0.0), FVector(ChildBounds2D.Max, 0.0)), FVector(Scale2D.X, Scale2D.Y, 0.0));
Offset2D = FVector2D(Offset.X, Offset.Y);
break;
}
Offset = FVector(0.0, Offset2D.X, Offset2D.Y);
Scale = FVector(0.5 * (Scale2D.X + Scale2D.Y), Scale2D.X, Scale2D.Y);
}
Actor->SetActorRelativeRotation(Rotation);
Actor->SetActorRelativeLocation(Offset);
UMRUKBPLibrary::SetScaleRecursivelyAdjustingForRotation(ActorRoot, Scale);
}
AActor* AMRUKAnchorActorSpawner::SpawnAnchorActor_Implementation(AMRUKAnchor* Anchor, const FMRUKSpawnActor& SpawnActor)
{
AActor* SpawnedActor = GetWorld()->SpawnActor(SpawnActor.Actor);
AttachAndFitActorToAnchor(Anchor, SpawnedActor, SpawnActor.ScalingMode, SpawnActor.AlignMode, SpawnActor.CalculateFacingDirection, SpawnActor.MatchAspectRatio);
return SpawnedActor;
}
FVector AMRUKAnchorActorSpawner::ComputeCustomScaling_Implementation(AMRUKAnchor* Anchor, AActor* SpawnedActor, const FVector& StretchedScale)
{
UE_LOG(LogMRUK, Warning, TEXT("Custom scaling mode selected but default implementation used. Please override ComputeCustomScaling() to define custom scaling"));
return StretchedScale;
}
bool AMRUKAnchorActorSpawner::SelectSpawnActorCustom_Implementation(AMRUKAnchor* Anchor, const FMRUKSpawnGroup& SpawnGroup, const FRandomStream& RandomStream, FMRUKSpawnActor& OutSpawnActor)
{
UE_LOG(LogMRUK, Warning, TEXT("Custom selection mode specified, but custom selection logic was not overwritten. Please overwrite SelectSpawnActorCustom() to define custom selection logic"));
return SelectSpawnActorRandom(SpawnGroup, RandomStream, OutSpawnActor);
}
FVector AMRUKAnchorActorSpawner::ComputeCustomAlign_Implementation(AMRUKAnchor* Anchor, AActor* Actor, const FBox& ChildBounds, const FVector& Scale)
{
UE_LOG(LogMRUK, Warning, TEXT("Custom align mode selected but default implementation used. Please override ComputeCustomAlign() to define custom align"));
return FVector::ZeroVector;
}
bool AMRUKAnchorActorSpawner::ShouldSpawnActorForAnchor(AMRUKAnchor* Anchor, const FString& Label, FMRUKSpawnGroup& OutSpawnGroup) const
{
if (Label == FMRUKLabels::WallFace && Anchor->SemanticClassifications.Contains(FMRUKLabels::InvisibleWallFace))
{
// Treat anchors with WALL_FACE and INVISIBLE_WALL_FACE as anchors that only have INVISIBLE_WALL_FACE
return false;
}
const auto SpawnGroup = SpawnGroups.Find(Label);
if (!SpawnGroup)
{
return false;
}
if (SpawnGroup->Actors.IsEmpty() && ShouldAnchorFallbackToProceduralMesh(*SpawnGroup))
{
return false;
}
OutSpawnGroup = *SpawnGroup;
return true;
}
AActor* AMRUKAnchorActorSpawner::SpawnAnchorActorForLabel_Implementation(AMRUKAnchor* Anchor, const FString& Label, const FMRUKSpawnGroup& SpawnGroup, const FRandomStream& RandomStream)
{
FMRUKSpawnActor SpawnActor{};
if (SelectSpawnActorFromSpawnGroup(Anchor, SpawnGroup, RandomStream, SpawnActor))
{
if (!SpawnActor.Actor)
{
UE_LOG(LogMRUK, Error, TEXT("Actor to spawn is a nullptr for label %s. Skipping it."), *Label);
return nullptr;
}
return SpawnAnchorActor(Anchor, SpawnActor);
}
UE_LOG(LogMRUK, Error, TEXT("Actor is nullptr for label %s."), *Label);
return nullptr;
}
TArray<AActor*> AMRUKAnchorActorSpawner::SpawnAnchorActorsInRoom_Implementation(AMRUKRoom* Room, const FRandomStream& RandomStream)
{
TArray<AActor*> SpawnedActorsInRoom;
SpawnedActorsInRoom.Append(SpawnProceduralMeshesInRoom(Room));
for (const auto& Anchor : Room->AllAnchors)
{
if (!IsValid(Anchor))
{
continue;
}
for (const FString& Label : Anchor->SemanticClassifications)
{
FMRUKSpawnGroup SpawnGroup{};
if (!ShouldSpawnActorForAnchor(Anchor, Label, SpawnGroup))
{
continue;
}
if (AActor* SpawnedActor = SpawnAnchorActorForLabel(Anchor, Label, SpawnGroup, RandomStream))
{
SpawnedActorsInRoom.Push(SpawnedActor);
}
}
}
return SpawnedActorsInRoom;
}
void AMRUKAnchorActorSpawner::SpawnActors(AMRUKRoom* Room)
{
if (!IsValid(Room))
{
UE_LOG(LogMRUK, Warning, TEXT("Can not spawn actors in Room that is a nullptr"));
return;
}
RemoveActors(Room);
// Use last seed if possible to keep spawning deterministic after the first spawn.
// In case the anchor random spawn seed has been changed it will be used instead
// of the last seed.
int32 Seed = -1;
if (LastSeed >= 0)
{
if ((AnchorRandomSpawnSeed >= 0) && (LastSeed != AnchorRandomSpawnSeed))
{
Seed = AnchorRandomSpawnSeed;
}
else
{
Seed = LastSeed;
}
}
else if (AnchorRandomSpawnSeed >= 0)
{
Seed = AnchorRandomSpawnSeed;
}
FRandomStream RandomStream(Seed);
if (Seed < 0)
{
RandomStream.GenerateNewSeed();
}
LastSeed = RandomStream.GetCurrentSeed();
const TArray<AActor*>& Actors = SpawnAnchorActorsInRoom(Room, RandomStream);
SpawnedActors.Add(Room, Actors);
const auto Subsystem = GetGameInstance()->GetSubsystem<UMRUKSubsystem>();
Subsystem->OnRoomUpdated.AddUniqueDynamic(this, &AMRUKAnchorActorSpawner::OnRoomUpdated);
Subsystem->OnRoomRemoved.AddUniqueDynamic(this, &AMRUKAnchorActorSpawner::OnRoomRemoved);
OnActorsSpawned.Broadcast(Room);
}
void AMRUKAnchorActorSpawner::GetSpawnedActorsByRoom(AMRUKRoom* Room, TArray<AActor*>& Actors)
{
if (const TArray<AActor*>* A = SpawnedActors.Find(Room))
{
Actors.Append(*A);
}
}
void AMRUKAnchorActorSpawner::GetSpawnedActors(TArray<AActor*>& Actors)
{
for (const auto& KeyValue : SpawnedActors)
{
Actors.Append(KeyValue.Value);
}
}

View File

@ -1,591 +0,0 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#include "MRUtilityKitBPLibrary.h"
#include "MRUtilityKit.h"
#include "Generated/MRUtilityKitShared.h"
#include "MRUtilityKitAnchor.h"
#include "MRUtilityKitSubsystem.h"
#include "MRUtilityKitSerializationHelpers.h"
#include "ProceduralMeshComponent.h"
#include "VectorUtil.h"
#include "Engine/World.h"
#include "Engine/GameInstance.h"
#include "Engine/Engine.h"
#include "TextureResource.h"
#include "Engine/TextureRenderTarget2D.h"
#include "Engine/Texture2D.h"
#include "Serialization/JsonReader.h"
#include "Serialization/JsonSerializer.h"
namespace
{
TArray<FVector> RecalculateNormals(const TArray<FVector>& Vertices, const TArray<uint32>& Triangles)
{
TArray<FVector> Normals;
// Initialize the normals array with zero vectors
Normals.Init(FVector::ZeroVector, Vertices.Num());
// Iterate through each triangle
for (int32 TriIndex = 0; TriIndex < Triangles.Num(); TriIndex += 3)
{
// Get the vertices of the triangle
FVector VertexA = Vertices[Triangles[TriIndex]];
FVector VertexB = Vertices[Triangles[TriIndex + 1]];
FVector VertexC = Vertices[Triangles[TriIndex + 2]];
// Calculate the triangle's normal
const FVector TriangleNormal = FVector::CrossProduct(VertexC - VertexA, VertexB - VertexA).GetSafeNormal();
// Add the triangle's normal to each of its vertices' normals
Normals[Triangles[TriIndex]] += TriangleNormal;
Normals[Triangles[TriIndex + 1]] += TriangleNormal;
Normals[Triangles[TriIndex + 2]] += TriangleNormal;
}
// Normalize the vertex normals
for (FVector& Normal : Normals)
{
if (!Normal.IsNearlyZero())
{
Normal.Normalize();
}
else
{
Normal = FVector::UpVector;
}
}
return Normals;
}
TArray<FProcMeshTangent> RecalculateTangents(const TArray<FVector>& Normals)
{
TArray<FProcMeshTangent> Tangents;
// Initialize the tangents array with zero tangents
Tangents.Init(FProcMeshTangent(0.f, 0.f, 0.f), Normals.Num());
// Iterate through each normal
for (int32 NormalIndex = 0; NormalIndex < Normals.Num(); NormalIndex++)
{
const FVector& Normal = Normals[NormalIndex];
// Calculate a tangent based on the normal
FVector TangentX = FVector(1.0f, 0.0f, 0.0f);
// Gram-Schmidt orthogonalization
TangentX -= Normal * FVector::DotProduct(TangentX, Normal);
if (!TangentX.IsNearlyZero())
{
TangentX.Normalize();
}
else
{
TangentX = FVector::UpVector;
}
// Store the tangent in the array
Tangents[NormalIndex] = FProcMeshTangent(TangentX, false);
}
return Tangents;
}
void SetScaleRecursivelyAdjustingForRotationInternal(USceneComponent* SceneComponent, const FVector& UnRotatedScale, const FQuat& AccumulatedRotation, const FVector& ParentReciprocalScale)
{
if (SceneComponent)
{
const auto RelativeRotation = SceneComponent->GetRelativeRotationCache().RotatorToQuat(SceneComponent->GetRelativeRotation());
const auto Rotation = AccumulatedRotation * RelativeRotation;
const FVector RotatedXAxis = Rotation.GetAxisX();
const FVector RotatedYAxis = Rotation.GetAxisY();
const FVector RotatedZAxis = Rotation.GetAxisZ();
FVector RotatedScale;
if (FMath::Abs(RotatedXAxis.X) >= UE_INV_SQRT_2)
{
RotatedScale.X = UnRotatedScale.X;
}
else if (FMath::Abs(RotatedXAxis.Y) >= UE_INV_SQRT_2)
{
RotatedScale.X = UnRotatedScale.Y;
}
else
{
RotatedScale.X = UnRotatedScale.Z;
}
if (FMath::Abs(RotatedYAxis.X) >= UE_INV_SQRT_2)
{
RotatedScale.Y = UnRotatedScale.X;
}
else if (FMath::Abs(RotatedYAxis.Y) >= UE_INV_SQRT_2)
{
RotatedScale.Y = UnRotatedScale.Y;
}
else
{
RotatedScale.Y = UnRotatedScale.Z;
}
if (FMath::Abs(RotatedZAxis.X) >= UE_INV_SQRT_2)
{
RotatedScale.Z = UnRotatedScale.X;
}
else if (FMath::Abs(RotatedZAxis.Y) >= UE_INV_SQRT_2)
{
RotatedScale.Z = UnRotatedScale.Y;
}
else
{
RotatedScale.Z = UnRotatedScale.Z;
}
const FVector OldScale = SceneComponent->GetRelativeScale3D();
const FVector NewScale = ParentReciprocalScale * RotatedScale * OldScale;
SceneComponent->SetRelativeScale3D(NewScale);
const FVector NewParentReciprocalScale = ParentReciprocalScale * (OldScale / NewScale);
for (auto Child : SceneComponent->GetAttachChildren())
{
if (Child)
{
SetScaleRecursivelyAdjustingForRotationInternal(Child, UnRotatedScale, Rotation, NewParentReciprocalScale);
}
}
}
}
TArray<FVector> GeneratePoints(const FTransform& Plane, const FBox2D& PlaneBounds, double PointsPerUnitX, double PointsPerUnitY, double WorldToMeters = 100.0)
{
const FVector PlaneRight = Plane.GetRotation().GetRightVector();
const FVector PlaneUp = Plane.GetRotation().GetUpVector();
const FVector PlaneSize = FVector(PlaneBounds.GetSize().X, PlaneBounds.GetSize().Y, 0.0);
const FVector PlaneBottomLeft = Plane.GetLocation() - PlaneRight * PlaneSize.X * 0.5f - PlaneUp * PlaneSize.Y * 0.5f;
const int32 PointsX = FMath::Max(FMathf::Ceil(PointsPerUnitX * PlaneSize.X) / WorldToMeters, 1);
const int32 PointsY = FMath::Max(FMathf::Ceil(PointsPerUnitY * PlaneSize.Y) / WorldToMeters, 1);
const FVector2D Stride{ PlaneSize.X / (PointsX + 1), PlaneSize.Y / (PointsY + 1) };
TArray<FVector> Points;
Points.SetNum(PointsX * PointsY);
for (int Iy = 0; Iy < PointsY; ++Iy)
{
for (int Ix = 0; Ix < PointsX; ++Ix)
{
const float Dx = (Ix + 1) * Stride.X;
const float Dy = (Iy + 1) * Stride.Y;
const FVector Point = PlaneBottomLeft + Dx * PlaneRight + Dy * PlaneUp;
Points[Ix + Iy * PointsX] = Point;
}
}
return Points;
}
} // namespace
UMRUKLoadFromDevice* UMRUKLoadFromDevice::LoadSceneFromDeviceAsync(const UObject* WorldContext
)
{
// We must have a valid contextual world for this action, so we don't even make it
// unless we can resolve the UWorld from WorldContext.
UWorld* World = GEngine->GetWorldFromContextObject(WorldContext, EGetWorldErrorMode::ReturnNull);
if (!ensureAlwaysMsgf(IsValid(WorldContext), TEXT("World Context was not valid.")))
{
return nullptr;
}
// Create a new UMyDelayAsyncAction, and store function arguments in it.
UMRUKLoadFromDevice* NewAction = NewObject<UMRUKLoadFromDevice>();
NewAction->World = World;
NewAction->RegisterWithGameInstance(World->GetGameInstance());
return NewAction;
}
void UMRUKLoadFromDevice::Activate()
{
const auto Subsystem = World->GetGameInstance()->GetSubsystem<UMRUKSubsystem>();
Subsystem->OnSceneLoaded.AddDynamic(this, &UMRUKLoadFromDevice::OnSceneLoaded);
{
Subsystem->LoadSceneFromDevice();
}
}
void UMRUKLoadFromDevice::OnSceneLoaded(bool Succeeded)
{
const auto Subsystem = World->GetGameInstance()->GetSubsystem<UMRUKSubsystem>();
Subsystem->OnSceneLoaded.RemoveDynamic(this, &UMRUKLoadFromDevice::OnSceneLoaded);
if (Succeeded)
{
Success.Broadcast();
}
else
{
Failure.Broadcast();
}
SetReadyToDestroy();
}
bool UMRUKBPLibrary::LoadGlobalMeshFromDevice(FOculusXRUInt64 SpaceHandle, UProceduralMeshComponent* OutProceduralMesh, bool LoadCollision, const UObject* WorldContext)
{
ensure(OutProceduralMesh);
const UWorld* World = GEngine->GetWorldFromContextObject(WorldContext, EGetWorldErrorMode::ReturnNull);
if (!ensureAlwaysMsgf(IsValid(WorldContext), TEXT("World Context was not valid.")))
{
return false;
}
const auto RoomLayoutManager = World->GetGameInstance()->GetSubsystem<UMRUKSubsystem>()->GetRoomLayoutManager();
const bool LoadResult = RoomLayoutManager->LoadTriangleMesh(SpaceHandle.Value, OutProceduralMesh, LoadCollision);
if (!LoadResult)
{
UE_LOG(LogMRUK, Warning, TEXT("Could not load triangle mesh from layout manager"));
return false;
}
return true;
}
bool UMRUKBPLibrary::LoadGlobalMeshFromJsonString(const FString& JsonString, FOculusXRUUID AnchorUUID, UProceduralMeshComponent* OutProceduralMesh, bool LoadCollision)
{
ensure(OutProceduralMesh);
TSharedPtr<FJsonValue> JsonValue;
auto JsonReader = TJsonReaderFactory<>::Create(JsonString);
if (!FJsonSerializer::Deserialize(JsonReader, JsonValue))
{
UE_LOG(LogMRUK, Warning, TEXT("Could not deserialize global mesh JSON data"));
return false;
}
auto JsonObject = JsonValue->AsObject();
// Find room
auto RoomsJson = JsonObject->GetArrayField(TEXT("Rooms"));
for (const auto& RoomJson : RoomsJson)
{
auto RoomObject = RoomJson->AsObject();
FOculusXRUUID RoomUUID;
MRUKDeserialize(*RoomObject->GetField<EJson::None>(TEXT("UUID")), RoomUUID);
if (RoomUUID == AnchorUUID)
{
// Find global mesh anchor
auto AnchorsJson = RoomObject->GetArrayField(TEXT("Anchors"));
for (const auto& AnchorJson : AnchorsJson)
{
auto AnchorObject = AnchorJson->AsObject();
if (AnchorObject->HasField(TEXT("GlobalMesh")))
{
auto GlobalMeshObject = AnchorObject->GetField<EJson::Object>(TEXT("GlobalMesh"))->AsObject();
auto PositionsJson = GlobalMeshObject->GetArrayField(TEXT("Positions"));
TArray<FVector> Positions;
Positions.Reserve(PositionsJson.Num());
for (const auto& PositionJson : PositionsJson)
{
FVector Position;
MRUKDeserialize(*PositionJson, Position);
Positions.Push(Position);
}
auto IndicesJson = GlobalMeshObject->GetArrayField(TEXT("Indices"));
TArray<int32> Indices;
Indices.Reserve(IndicesJson.Num());
for (const auto& IndexJson : IndicesJson)
{
double Index;
MRUKDeserialize(*IndexJson, Index);
Indices.Push((int32)Index);
}
TArray<FVector> EmptyNormals;
TArray<FVector2D> EmptyUV;
TArray<FColor> EmptyVertexColors;
TArray<FProcMeshTangent> EmptyTangents;
OutProceduralMesh->CreateMeshSection(0, Positions, Indices, EmptyNormals, EmptyUV, EmptyVertexColors, EmptyTangents, LoadCollision);
return true;
}
}
break;
}
}
UE_LOG(LogMRUK, Warning, TEXT("Could not find global mesh in room"));
return false;
}
void UMRUKBPLibrary::RecalculateProceduralMeshAndTangents(UProceduralMeshComponent* Mesh)
{
if (!IsValid(Mesh))
return;
for (int s = 0; s < Mesh->GetNumSections(); ++s)
{
FProcMeshSection* Section = Mesh->GetProcMeshSection(s);
// Get vertices of the section
TArray<FVector> Vertices;
for (FProcMeshVertex Vertex : Section->ProcVertexBuffer)
{
Vertices.Add(Vertex.Position);
}
// Calculate normals and tangents
TArray<FVector> Normals = RecalculateNormals(Vertices, Section->ProcIndexBuffer);
TArray<FProcMeshTangent> Tangents = RecalculateTangents(Normals);
TArray<FVector2D> EmptyUV;
TArray<FColor> EmptyVertexColors;
// Update mesh section
Mesh->UpdateMeshSection(s, Vertices, Normals, EmptyUV, EmptyVertexColors, Tangents);
}
}
bool UMRUKBPLibrary::IsUnrealEngineMetaFork()
{
#if defined(WITH_OCULUS_BRANCH)
return true;
#else
return false;
#endif
}
FVector2D UMRUKBPLibrary::ComputeCentroid(const TArray<FVector2D>& PolygonPoints)
{
FVector2D Centroid = FVector2D::ZeroVector;
double SignedArea = 0.0;
for (int32 I = 0; I < PolygonPoints.Num(); ++I)
{
const double X0 = PolygonPoints[I].X;
const double Y0 = PolygonPoints[I].Y;
const double X1 = PolygonPoints[(I + 1) % PolygonPoints.Num()].X;
const double Y1 = PolygonPoints[(I + 1) % PolygonPoints.Num()].Y;
const double A = X0 * Y1 - X1 * Y0;
SignedArea += A;
Centroid.X += (X0 + X1) * A;
Centroid.Y += (Y0 + Y1) * A;
}
return Centroid / (6.0 * (SignedArea * 0.5));
}
void UMRUKBPLibrary::SetScaleRecursivelyAdjustingForRotation(USceneComponent* SceneComponent, const FVector& UnRotatedScale)
{
SetScaleRecursivelyAdjustingForRotationInternal(SceneComponent, UnRotatedScale, FQuat::Identity, FVector::OneVector);
}
FVector UMRUKBPLibrary::ComputeDirectionAwayFromClosestWall(const AMRUKAnchor* Anchor, int& OutCardinalAxisIndex, const TArray<int> ExcludedAxes)
{
double ClosestWallDistance = DBL_MAX;
FVector AwayFromWall{};
for (int i = 0; i < 4; ++i)
{
if (ExcludedAxes.Contains(i))
{
continue;
}
// Shoot a ray along the cardinal directions
// The "Up" (i.e. Z axis) for anchors typically points away from the facing direction, but it depends
// entirely on how the user defined the volume in scene capture.
const auto CardinalAxis = (FQuat::MakeFromEuler({ 0.0, 0.0, 90.0 * i }).RotateVector(Anchor->GetActorUpVector()));
for (const auto& WallAnchor : Anchor->Room->WallAnchors)
{
if (!WallAnchor)
{
continue;
}
FMRUKHit Hit{};
if (!WallAnchor->Raycast(Anchor->GetActorLocation(), CardinalAxis, 0.0, Hit))
{
continue;
}
const auto DistToWall = FVector::Distance(Hit.HitPosition, Anchor->GetActorLocation());
if (DistToWall < ClosestWallDistance)
{
ClosestWallDistance = DistToWall;
AwayFromWall = -CardinalAxis;
OutCardinalAxisIndex = i;
}
}
}
return AwayFromWall;
}
UTexture2D* UMRUKBPLibrary::ConstructTexture2D(UTextureRenderTarget2D* RenderTarget2D, UObject* Outer, const FString& TexName)
{
const auto SizeX = RenderTarget2D->SizeX;
const auto SizeY = RenderTarget2D->SizeY;
const auto Tex = UTexture2D::CreateTransient(SizeX, SizeY, RenderTarget2D->GetFormat());
Tex->AddToRoot();
Tex->Filter = TF_Bilinear;
Tex->CompressionSettings = TC_Default;
Tex->SRGB = 0;
Tex->UpdateResource();
FTextureRenderTargetResource* RenderTargetResource = RenderTarget2D->GameThread_GetRenderTargetResource();
FReadSurfaceDataFlags ReadSurfaceDataFlags;
ReadSurfaceDataFlags.SetLinearToGamma(false);
TArray<FColor> OutBMP;
RenderTargetResource->ReadPixels(OutBMP, ReadSurfaceDataFlags);
FTexture2DMipMap& Mip = Tex->GetPlatformData()->Mips[0];
void* Data = Mip.BulkData.Lock(LOCK_READ_WRITE);
FMemory::Memcpy(Data, OutBMP.GetData(), SizeX * SizeY * 4);
Mip.BulkData.Unlock();
Tex->UpdateResource();
return Tex;
}
FLinearColor UMRUKBPLibrary::GetMatrixColumn(const FMatrix& Matrix, int32 Index)
{
ensure(0 <= Index && Index < 4);
FLinearColor V;
V.R = Matrix.M[0][Index];
V.G = Matrix.M[1][Index];
V.B = Matrix.M[2][Index];
V.A = Matrix.M[3][Index];
return V;
}
TArray<FVector> UMRUKBPLibrary::ComputeRoomBoxGrid(const AMRUKRoom* Room, int32 MaxPointsCount, double PointsPerUnitX, double PointsPerUnitY)
{
TArray<FVector> AllPoints;
const double WorldToMeters = Room->GetWorld()->GetWorldSettings()->WorldToMeters;
for (const AMRUKAnchor* WallAnchor : Room->WallAnchors)
{
const auto Points = GeneratePoints(WallAnchor->GetTransform(), WallAnchor->PlaneBounds, PointsPerUnitX, PointsPerUnitY, WorldToMeters);
AllPoints.Append(Points);
}
// Generate points between floor and ceiling
const float DistFloorCeiling = Room->CeilingAnchor->GetTransform().GetLocation().Z - Room->FloorAnchor->GetTransform().GetLocation().Z;
const int32 PlanesCount = FMath::Max(FMathf::Ceil(PointsPerUnitY * DistFloorCeiling) / WorldToMeters, 1);
const int32 SpaceBetweenPlanes = DistFloorCeiling / PlanesCount;
for (int i = 1; i < PlanesCount; ++i)
{
FTransform Transform = Room->CeilingAnchor->GetTransform();
Transform.SetLocation(FVector(Transform.GetLocation().X, Transform.GetLocation().Y, Transform.GetLocation().Z - (SpaceBetweenPlanes * i)));
const auto Points = GeneratePoints(Transform, Room->CeilingAnchor->PlaneBounds, PointsPerUnitX, PointsPerUnitY, WorldToMeters);
AllPoints.Append(Points);
}
const auto CeilingPoints = GeneratePoints(Room->CeilingAnchor->GetTransform(), Room->CeilingAnchor->PlaneBounds, PointsPerUnitX, PointsPerUnitY, WorldToMeters);
AllPoints.Append(CeilingPoints);
const auto FloorPoints = GeneratePoints(Room->FloorAnchor->GetTransform(), Room->FloorAnchor->PlaneBounds, PointsPerUnitX, PointsPerUnitY, WorldToMeters);
AllPoints.Append(FloorPoints);
if (AllPoints.Num() > MaxPointsCount)
{
// Shuffle the array
AllPoints.Sort([](const FVector& /*Item1*/, const FVector& /*Item2*/) {
return FMath::FRand() < 0.5f;
});
// Randomly remove some points
int32 PointsToRemoveCount = AllPoints.Num() - MaxPointsCount;
while (PointsToRemoveCount > 0)
{
AllPoints.Pop();
--PointsToRemoveCount;
}
}
return AllPoints;
}
void UMRUKBPLibrary::CreateMeshSegmentation(const TArray<FVector>& MeshPositions, const TArray<uint32>& MeshIndices,
const TArray<FVector>& SegmentationPoints, const FVector& ReservedMin, const FVector& ReservedMax,
TArray<FMRUKMeshSegment>& OutSegments, FMRUKMeshSegment& OutReservedSegment)
{
if (!MRUKShared::GetInstance())
{
UE_LOG(LogMRUK, Error, TEXT("MRUK shared library is not available. To use this functionality make sure the library is included"));
return;
}
TArray<FVector3f> MeshPositionsF;
MeshPositionsF.Reserve(MeshPositions.Num());
for (const FVector& V : MeshPositions)
{
MeshPositionsF.Add(FVector3f(V));
}
TArray<FVector3f> SegmentationPointsF;
SegmentationPointsF.Reserve(SegmentationPoints.Num());
for (const FVector& V : SegmentationPoints)
{
SegmentationPointsF.Add(FVector3f(V));
}
MRUKShared::MrukMesh3f* MeshSegmentsF = nullptr;
uint32_t MeshSegmentsCount = 0;
MRUKShared::MrukMesh3f ReservedMeshSegmentF{};
const FVector3f ReservedMinF(ReservedMin);
const FVector3f ReservedMaxF(ReservedMax);
MRUKShared::GetInstance()->ComputeMeshSegmentation(MeshPositionsF.GetData(), MeshPositionsF.Num(), MeshIndices.GetData(),
MeshIndices.Num(), SegmentationPointsF.GetData(), SegmentationPointsF.Num(), ReservedMinF, ReservedMaxF, &MeshSegmentsF,
&MeshSegmentsCount, &ReservedMeshSegmentF);
OutSegments.Reserve(MeshSegmentsCount);
for (uint32_t i = 0; i < MeshSegmentsCount; ++i)
{
const MRUKShared::MrukMesh3f& SegmentF = MeshSegmentsF[i];
if (SegmentF.numIndices == 0)
{
continue;
}
FMRUKMeshSegment MeshSegment{};
MeshSegment.Indices.Reserve(SegmentF.numIndices);
MeshSegment.Positions.Reserve(SegmentF.numVertices);
for (uint32_t j = 0; j < SegmentF.numIndices; ++j)
{
MeshSegment.Indices.Add(SegmentF.indices[j]);
}
for (uint32_t j = 0; j < SegmentF.numVertices; ++j)
{
const FVector3f& V = SegmentF.vertices[j];
MeshSegment.Positions.Add({ V.X, V.Y, V.Z });
}
OutSegments.Emplace(MoveTemp(MeshSegment));
}
if (ReservedMeshSegmentF.numIndices && ReservedMeshSegmentF.numVertices)
{
OutReservedSegment.Indices.Reserve(ReservedMeshSegmentF.numIndices);
OutReservedSegment.Positions.Reserve(ReservedMeshSegmentF.numVertices);
for (uint32_t j = 0; j < ReservedMeshSegmentF.numIndices; ++j)
{
OutReservedSegment.Indices.Add(ReservedMeshSegmentF.indices[j]);
}
for (uint32_t j = 0; j < ReservedMeshSegmentF.numVertices; ++j)
{
const FVector3f& V = ReservedMeshSegmentF.vertices[j];
OutReservedSegment.Positions.Add({ V.X, V.Y, V.Z });
}
}
MRUKShared::GetInstance()->FreeMeshSegmentation(MeshSegmentsF, MeshSegmentsCount, &ReservedMeshSegmentF);
}

View File

@ -1,145 +0,0 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#include "MRUtilityKitBlobShadowComponent.h"
#include "MRUtilityKitTelemetry.h"
#include "MRUtilityKit.h"
#include "Kismet/KismetSystemLibrary.h"
#include "UObject/ConstructorHelpers.h"
#include "Materials/MaterialInstance.h"
#include "Materials/MaterialInstanceDynamic.h"
#include "Engine/StaticMesh.h"
UMRUKBlobShadowComponent::UMRUKBlobShadowComponent()
{
const ConstructorHelpers::FObjectFinder<UStaticMesh> PlaneAsset(TEXT("/Engine/BasicShapes/Plane"));
if (PlaneAsset.Succeeded())
{
SetStaticMesh(PlaneAsset.Object);
}
else
{
UE_LOG(LogMRUK, Log, TEXT("Blob shadow couldn't find plane mesh in /Engine/BasicShapes/Plane"));
}
const ConstructorHelpers::FObjectFinder<UMaterialInstance> BlobShadowMaterialAsset(TEXT("/OculusXR/Materials/MI_BlobShadow"));
if (BlobShadowMaterialAsset.Succeeded())
{
SetMaterial(0, BlobShadowMaterialAsset.Object);
}
else
{
UE_LOG(LogMRUK, Log, TEXT("Blob shadow couldn't find blob shadow material in /OculusXR/Materials/MI_BlobShadow"));
}
// Prevent sorting issue with transparent ground
SetTranslucentSortPriority(1);
// We don't want any collision
SetCollisionProfileName("NoCollision");
// Need tick to be enabled
SetComponentTickEnabled(true);
PrimaryComponentTick.bCanEverTick = true;
bAutoActivate = true;
}
void UMRUKBlobShadowComponent::BeginPlay()
{
Super::BeginPlay();
OculusXRTelemetry::TScopedMarker<MRUKTelemetry::FLoadBlobShadowMarker> Event(static_cast<int>(GetTypeHash(this)));
// Create dynamic material (for roundness and gradient settings)
DynMaterial = CreateAndSetMaterialInstanceDynamic(0);
// Since we're updating the component size and position every frame it's better to not be influenced by parent
SetUsingAbsoluteLocation(true);
SetUsingAbsoluteRotation(true);
SetUsingAbsoluteScale(true);
// Compute size and position once
UpdatePlaneSizeAndPosition();
}
void UMRUKBlobShadowComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
// Update component size and position every frame
UpdatePlaneSizeAndPosition();
}
void UMRUKBlobShadowComponent::UpdatePlaneSizeAndPosition()
{
FVector Origin;
FVector2D Extent;
double Yaw;
ComputeOwner2DBounds(Origin, Extent, Yaw);
Extent += FVector2D::UnitVector * ExtraExtent; // Additional extent
SetWorldScale3D(FVector(Extent * 0.02f, 1.f)); // Plane mesh is 100x100, multiplying by 0.02f to match the correct size when scaling
SetWorldRotation(FRotator(0.f, Yaw, 0.f));
// Sphere trace to the ground
FHitResult Hit;
TArray<AActor*> ActorsToIgnore;
ActorsToIgnore.Add(GetOwner());
const bool bHasHit = UKismetSystemLibrary::SphereTraceSingle(this, Origin, Origin + FVector::DownVector * MaxVerticalDistance, Extent.Length() * 0.5f, TraceTypeQuery1,
true, ActorsToIgnore, EDrawDebugTrace::None, Hit, true);
float Opacity = 0.f;
if (bHasHit)
{
SetHiddenInGame(false); // Make plane visible
SetWorldLocation(Hit.ImpactPoint + FVector::UpVector * 0.02f); // Impact + some offset to avoid Z-fighting
Opacity = FMath::GetMappedRangeValueClamped(
FVector2D(MaxVerticalDistance - FadeDistance, MaxVerticalDistance),
FVector2D(1.f, 0.f),
Hit.Distance); // Set opacity based on distance to ground
}
else
SetHiddenInGame(true); // Hide plane
// Update material's parameters
if (DynMaterial)
{
DynMaterial->SetScalarParameterValue("CornerWorldSize", FMath::Min(Extent.X, Extent.Y) * Roundness);
DynMaterial->SetScalarParameterValue("Gradient", Gradient);
DynMaterial->SetScalarParameterValue("GradientPower", GradientPower);
DynMaterial->SetScalarParameterValue("Opacity", Opacity);
}
else // In case DynMaterial doesn't exist (e.g. in editor), update values directly on the mesh
{
SetScalarParameterValueOnMaterials("CornerWorldSize", FMath::Min(Extent.X, Extent.Y) * Roundness);
SetScalarParameterValueOnMaterials("Gradient", Gradient);
SetScalarParameterValueOnMaterials("GradientPower", GradientPower);
SetScalarParameterValueOnMaterials("Opacity", Opacity);
}
}
void UMRUKBlobShadowComponent::ComputeOwner2DBounds(FVector& Origin, FVector2D& Extent, double& Yaw) const
{
const AActor* Actor = GetOwner();
// Calculate local space BoundingBox from all components, but keep yaw to have a correct 2D bounding box at the end
FBox Box(ForceInit);
const FRotator YawOnly = FRotator(0.f, Actor->GetActorRotation().Yaw, 0.f);
const FTransform ActorToWorld = FTransform(YawOnly.Quaternion());
const FTransform WorldToActor = ActorToWorld.Inverse();
Actor->ForEachComponent<UPrimitiveComponent>(true, [&](const UPrimitiveComponent* InPrimComp) {
// Ignore editor & blob shadow components
if (InPrimComp->IsRegistered() && !InPrimComp->IsEditorOnly() && !InPrimComp->bUseAttachParentBound && !InPrimComp->IsA<UMRUKBlobShadowComponent>())
{
const FTransform ComponentToActor = InPrimComp->GetComponentTransform() * WorldToActor;
Box += InPrimComp->CalcBounds(ComponentToActor).GetBox();
}
});
const FTransform Transform = Actor->GetTransform();
// Project 3D extent to 2D
const FVector ProjectedExtent = FVector::VectorPlaneProject(Box.GetExtent(), FVector::UpVector);
Origin = ActorToWorld.TransformPosition(Box.GetCenter());
Extent = FVector2D(ProjectedExtent);
Yaw = Transform.GetRotation().Rotator().Yaw;
}

View File

@ -1,355 +0,0 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#include "MRUtilityKitData.h"
#include "MRUtilityKitSubsystem.h"
#include "MRUtilityKitSerializationHelpers.h"
#include "MRUtilityKitTelemetry.h"
#include "OculusXRAnchorBPFunctionLibrary.h"
#include "OculusXRScene.h"
#include "Engine/World.h"
#include "Engine/GameInstance.h"
#include "GameFramework/WorldSettings.h"
#include "Serialization/JsonReader.h"
#include "Serialization/JsonSerializer.h"
AMRUKLocalizer::AMRUKLocalizer()
{
PrimaryActorTick.bCanEverTick = true;
PrimaryActorTick.bStartWithTickEnabled = true;
}
void AMRUKLocalizer::Tick(float DeltaTime)
{
for (int i = 0; i < AnchorsData.Num(); ++i)
{
const auto Query = AnchorsData[i];
if (UOculusXRAnchorBPFunctionLibrary::GetAnchorTransformByHandle(Query->SpaceQuery.Space, Query->Transform))
{
Query->NeedAnchorLocalization = false;
if (Query->SemanticClassifications.IsEmpty())
{
UE_LOG(LogMRUK, Log, TEXT("Localized anchor %s"), *Query->SpaceQuery.UUID.ToString());
}
else
{
UE_LOG(LogMRUK, Log, TEXT("Localized anchor %s - %s"), *Query->SpaceQuery.UUID.ToString(), *Query->SemanticClassifications[0]);
}
AnchorsData.RemoveAt(i);
--i;
}
}
if (AnchorsData.IsEmpty())
{
UE_LOG(LogMRUK, Log, TEXT("All anchors localized"));
OnComplete.Broadcast(true);
}
}
void UMRUKAnchorData::LoadFromDevice(const FOculusXRAnchorsDiscoverResult& AnchorsDiscoverResult)
{
SpaceQuery = AnchorsDiscoverResult;
Transform = FTransform::Identity;
NeedAnchorLocalization = false;
if (!UOculusXRAnchorBPFunctionLibrary::GetAnchorTransformByHandle(SpaceQuery.Space, Transform))
{
UE_LOG(LogMRUK, Log, TEXT("Anchor %s is not localized yet. Localize it async."), *SpaceQuery.UUID.ToString());
NeedAnchorLocalization = true;
}
EOculusXRAnchorResult::Type Result = OculusXRScene::FOculusXRScene::GetSemanticClassification(SpaceQuery.Space.Value, SemanticClassifications);
if (!UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(Result))
{
UE_LOG(LogMRUK, Error, TEXT("Failed to get semantic classification space for %s."), *SpaceQuery.UUID.ToString());
}
const UWorld* World = GetWorld();
const float WorldToMeters = World ? World->GetWorldSettings()->WorldToMeters : 100.0;
FVector ScenePlanePos;
FVector ScenePlaneSize;
Result = OculusXRScene::FOculusXRScene::GetScenePlane(SpaceQuery.Space, ScenePlanePos, ScenePlaneSize);
if (UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(Result))
{
const FVector2D PlanePos = FVector2D(ScenePlanePos.Y, ScenePlanePos.Z) * WorldToMeters;
const FVector2D PlaneSize = FVector2D(ScenePlaneSize.Y, ScenePlaneSize.Z) * WorldToMeters;
PlaneBounds = FBox2D(PlanePos, PlanePos + PlaneSize);
TArray<FVector2f> SpaceBoundary2D;
Result = OculusXRScene::FOculusXRScene::GetBoundary2D(SpaceQuery.Space, SpaceBoundary2D);
if (UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(Result))
{
PlaneBoundary2D.Reserve(SpaceBoundary2D.Num());
for (int i = 0; i < SpaceBoundary2D.Num(); ++i)
{
PlaneBoundary2D.Push(FVector2D(SpaceBoundary2D[i].X * WorldToMeters, SpaceBoundary2D[i].Y * WorldToMeters));
}
}
}
FVector SceneVolumePos;
FVector SceneVolumeSize;
Result = OculusXRScene::FOculusXRScene::GetSceneVolume(SpaceQuery.Space, SceneVolumePos, SceneVolumeSize);
if (UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(Result))
{
const FVector VolumePos = SceneVolumePos * WorldToMeters;
const FVector VolumeSize = SceneVolumeSize * WorldToMeters;
VolumeBounds = FBox(VolumePos, VolumePos + VolumeSize);
}
}
void UMRUKAnchorData::LoadFromJson(const FJsonValue& Value)
{
const auto Object = Value.AsObject();
MRUKDeserialize(*Object->GetField<EJson::None>(TEXT("UUID")), SpaceQuery.UUID);
MRUKDeserialize(*Object->GetField<EJson::None>(TEXT("SemanticClassifications")), SemanticClassifications);
MRUKDeserialize(*Object->GetField<EJson::None>(TEXT("Transform")), Transform);
if (const auto JsonValue = Object->TryGetField(TEXT("PlaneBounds")))
{
MRUKDeserialize(*JsonValue, PlaneBounds);
}
if (const auto JsonValue = Object->TryGetField(TEXT("PlaneBoundary2D")))
{
MRUKDeserialize(*JsonValue, PlaneBoundary2D);
}
if (const auto JsonValue = Object->TryGetField(TEXT("VolumeBounds")))
{
MRUKDeserialize(*JsonValue, VolumeBounds);
}
NeedAnchorLocalization = false;
}
void UMRUKRoomData::LoadFromDevice(UMRUKSceneData* Data, const FOculusXRAnchorsDiscoverResult& AnchorsDiscoverResult)
{
SceneData = Data;
SpaceQuery = AnchorsDiscoverResult;
const auto Subsystem = GetWorld()->GetGameInstance()->GetSubsystem<UMRUKSubsystem>();
if (!Subsystem->GetRoomLayoutManager()->GetRoomLayout(SpaceQuery.Space.Value, RoomLayout))
{
UE_LOG(LogMRUK, Error, TEXT("Could not query room layout"));
FinishQuery(false);
return;
}
EOculusXRAnchorResult::Type Result{};
const auto Filter = NewObject<UOculusXRSpaceDiscoveryIdsFilter>(this);
Filter->Uuids = RoomLayout.RoomObjectUUIDs;
FOculusXRSpaceDiscoveryInfo DiscoveryInfo{};
DiscoveryInfo.Filters.Push(Filter);
OculusXRAnchors::FOculusXRAnchors::DiscoverAnchors(DiscoveryInfo, FOculusXRDiscoverAnchorsResultsDelegate::CreateUObject(this, &UMRUKRoomData::RoomDataLoadedIncrementalResults), FOculusXRDiscoverAnchorsCompleteDelegate::CreateUObject(this, &UMRUKRoomData::RoomDataLoadedComplete), Result);
if (Result != EOculusXRAnchorResult::Success)
{
UE_LOG(LogMRUK, Error, TEXT("Failed to discover anchors"));
FinishQuery(false);
}
}
void UMRUKRoomData::LoadFromJson(UMRUKSceneData* Data, const FJsonValue& Value)
{
SceneData = Data;
const auto Object = Value.AsObject();
MRUKDeserialize(*Object->GetField<EJson::None>(TEXT("UUID")), SpaceQuery.UUID);
MRUKDeserialize(*Object->GetField<EJson::None>(TEXT("RoomLayout")), RoomLayout);
auto AnchorsJson = Object->GetArrayField(TEXT("Anchors"));
for (const auto& AnchorJson : AnchorsJson)
{
auto AnchorQuery = NewObject<UMRUKAnchorData>(this);
AnchorsData.Push(AnchorQuery);
RoomLayout.RoomObjectUUIDs.Add(AnchorQuery->SpaceQuery.UUID);
AnchorQuery->LoadFromJson(*AnchorJson);
}
FinishQuery(true);
}
void UMRUKRoomData::FinishQuery(bool Success)
{
OnComplete.Broadcast(Success);
}
void UMRUKRoomData::RoomDataLoadedComplete(EOculusXRAnchorResult::Type Result)
{
if (!UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(Result))
{
UE_LOG(LogMRUK, Error, TEXT("Discovering room data failed"));
FinishQuery(false);
return;
}
if (AnchorsData.Num() == 0)
{
UE_LOG(LogMRUK, Warning, TEXT("Discovered room which doesn't contain any anchors. Skip that room"));
SceneData->RoomsData.Remove(this);
AnchorsInitialized(true);
return;
}
TArray<UMRUKAnchorData*> AnchorQueriesLocalization;
for (auto& AnchorQuery : AnchorsData)
{
if (AnchorQuery->NeedAnchorLocalization)
{
AnchorQueriesLocalization.Push(AnchorQuery);
}
}
if (!AnchorQueriesLocalization.IsEmpty())
{
UE_LOG(LogMRUK, Log, TEXT("Could not localize all anchors. Going to localize them async"));
FActorSpawnParameters ActorSpawnParams;
ActorSpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
LocalizationActor = GetWorld()->SpawnActor<AMRUKLocalizer>(ActorSpawnParams);
LocalizationActor->AnchorsData = AnchorQueriesLocalization;
LocalizationActor->OnComplete.AddDynamic(this, &UMRUKRoomData::AnchorsInitialized);
}
else
{
AnchorsInitialized(true);
}
}
void UMRUKRoomData::RoomDataLoadedIncrementalResults(const TArray<FOculusXRAnchorsDiscoverResult>& DiscoverResults)
{
// NOTE: This function may be called multiple times in batches. E.g. if there are 18 anchors in a room, this may
// be called once with 10 anchors and a second time with 8 anchors in DiscoverResults.
UE_LOG(LogMRUK, Log, TEXT("Received %d anchors from device"), DiscoverResults.Num());
for (auto& DiscoverResult : DiscoverResults)
{
auto AnchorQuery = NewObject<UMRUKAnchorData>(this);
AnchorQuery->LoadFromDevice(DiscoverResult);
AnchorsData.Push(AnchorQuery);
}
}
void UMRUKRoomData::AnchorsInitialized(bool Success)
{
UE_LOG(LogMRUK, Log, TEXT("Anchors data initialized Success==%d"), Success);
if (IsValid(LocalizationActor))
{
LocalizationActor->Destroy();
LocalizationActor = nullptr;
}
FinishQuery(Success);
}
void UMRUKSceneData::LoadFromDevice()
{
NumRoomsLeftToInitialize = 0;
EOculusXRAnchorResult::Type Result{};
const auto Filter = NewObject<UOculusXRSpaceDiscoveryComponentsFilter>(this);
Filter->ComponentType = EOculusXRSpaceComponentType::RoomLayout;
FOculusXRSpaceDiscoveryInfo DiscoveryInfo{};
DiscoveryInfo.Filters.Push(Filter);
OculusXRAnchors::FOculusXRAnchors::DiscoverAnchors(DiscoveryInfo, FOculusXRDiscoverAnchorsResultsDelegate::CreateUObject(this, &UMRUKSceneData::SceneDataLoadedComplete), FOculusXRDiscoverAnchorsCompleteDelegate::CreateUObject(this, &UMRUKSceneData::SceneDataLoadedResult), Result);
if (Result != EOculusXRAnchorResult::Success)
{
UE_LOG(LogMRUK, Error, TEXT("Failed to discover room layouts"));
FinishQuery(false);
}
}
void UMRUKSceneData::LoadFromJson(const FString& Json)
{
TSharedPtr<FJsonValue> Value;
const TSharedRef<TJsonReader<>> JsonReader = TJsonReaderFactory<>::Create(Json);
if (!FJsonSerializer::Deserialize(JsonReader, Value))
{
UE_LOG(LogMRUK, Warning, TEXT("Could not deserialize JSON scene data: %s"), *JsonReader->GetErrorMessage());
FinishQuery(false);
return;
}
const auto Object = Value->AsObject();
auto RoomsJson = Object->GetArrayField(TEXT("Rooms"));
OculusXRTelemetry::TScopedMarker<MRUKTelemetry::FLoadSceneFromJsonMarker> Event(static_cast<int>(GetTypeHash(this)));
Event.AddAnnotation("NumRooms", TCHAR_TO_ANSI(*FString::FromInt(RoomsJson.Num())));
Event.SetResult(RoomsJson.Num() > 0 ? OculusXRTelemetry::EAction::Success : OculusXRTelemetry::EAction::Fail);
if (RoomsJson.IsEmpty())
{
UE_LOG(LogMRUK, Warning, TEXT("Could not find Rooms in JSON"));
FinishQuery(false);
return;
}
NumRoomsLeftToInitialize = RoomsJson.Num();
UE_LOG(LogMRUK, Log, TEXT("Found %d rooms in JSON"), NumRoomsLeftToInitialize);
for (const auto& RoomJson : RoomsJson)
{
auto RoomQuery = NewObject<UMRUKRoomData>(this);
RoomsData.Push(RoomQuery);
RoomQuery->OnComplete.AddDynamic(this, &UMRUKSceneData::RoomQueryComplete);
RoomQuery->LoadFromJson(this, *RoomJson);
}
}
void UMRUKSceneData::FinishQuery(bool Success)
{
if (!Success)
{
AnyRoomFailed = true;
}
--NumRoomsLeftToInitialize;
if (NumRoomsLeftToInitialize <= 0)
{
OnComplete.Broadcast(!AnyRoomFailed);
}
}
void UMRUKSceneData::SceneDataLoadedResult(EOculusXRAnchorResult::Type Result)
{
if (!UOculusXRAnchorBPFunctionLibrary::IsAnchorResultSuccess(Result) || RoomsData.IsEmpty())
{
UE_LOG(LogMRUK, Error, TEXT("Discovering room layouts failed"));
FinishQuery(false);
}
}
void UMRUKSceneData::SceneDataLoadedComplete(const TArray<FOculusXRAnchorsDiscoverResult>& DiscoverResults)
{
NumRoomsLeftToInitialize = DiscoverResults.Num();
UE_LOG(LogMRUK, Log, TEXT("Found on %d rooms on the device"), NumRoomsLeftToInitialize);
OculusXRTelemetry::TScopedMarker<MRUKTelemetry::FLoadSceneFromDeviceMarker> Event(static_cast<int>(GetTypeHash(this)));
Event.AddAnnotation("NumRooms", TCHAR_TO_ANSI(*FString::FromInt(DiscoverResults.Num())));
Event.SetResult(DiscoverResults.Num() > 0 ? OculusXRTelemetry::EAction::Success : OculusXRTelemetry::EAction::Fail);
if (NumRoomsLeftToInitialize == 0)
{
UE_LOG(LogMRUK, Error, TEXT("No room layouts discovered"));
FinishQuery(false);
return;
}
for (auto& DiscoverResult : DiscoverResults)
{
auto RoomQuery = NewObject<UMRUKRoomData>(this);
RoomsData.Push(RoomQuery);
RoomQuery->OnComplete.AddDynamic(this, &UMRUKSceneData::RoomQueryComplete);
RoomQuery->LoadFromDevice(this, DiscoverResult);
}
}
void UMRUKSceneData::RoomQueryComplete(bool Success)
{
if (!Success)
{
AnyRoomFailed = true;
}
--NumRoomsLeftToInitialize;
if (NumRoomsLeftToInitialize == 0)
{
FinishQuery(!AnyRoomFailed);
}
}

View File

@ -1,208 +0,0 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#include "MRUtilityKitDebugComponent.h"
#include "MRUtilityKitTelemetry.h"
#include "MRUtilityKitSubsystem.h"
#include "MRUtilityKitAnchor.h"
#include "Kismet/KismetMathLibrary.h"
#include "IXRTrackingSystem.h"
#include "TextRenderComponent.h"
#include "Engine/World.h"
#include "Engine/GameInstance.h"
#include "Engine/Engine.h"
UMRUKDebugComponent::UMRUKDebugComponent()
{
PrimaryComponentTick.bCanEverTick = true;
}
void UMRUKDebugComponent::ShowAnchorAtRayHit(const FVector& Origin, const FVector& Direction)
{
if (!GizmoActorClass)
{
UE_LOG(LogMRUK, Warning, TEXT("Can not show anchor because no gizmo actor is set"));
return;
}
HideAnchor();
const auto Subsystem = GetOwner()->GetGameInstance()->GetSubsystem<UMRUKSubsystem>();
if (!Subsystem)
{
UE_LOG(LogMRUK, Warning, TEXT("Can not show anchor because there is no MRUtilityKit subsystem"));
return;
}
FMRUKHit Hit{};
FMRUKLabelFilter LabelFilter{};
auto Anchor = Subsystem->Raycast(Origin, Direction, 0.0, LabelFilter, Hit);
if (!Anchor)
{
return;
}
// Spawn Gizmo
if (!ActiveGizmoActor)
{
FActorSpawnParameters ActorSpawnParams;
ActorSpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
ActorSpawnParams.Owner = GetOwner();
ActiveGizmoActor = GetWorld()->SpawnActor(GizmoActorClass, nullptr, ActorSpawnParams);
}
else
{
ActiveGizmoActor->SetActorHiddenInGame(false);
}
ActiveGizmoActor->SetActorLocation(Hit.HitPosition);
ActiveGizmoActor->SetActorScale3D(GizmoScale);
ActiveGizmoActor->SetActorRotation(Anchor->GetActorRotation());
if (!TextActorClass)
{
UE_LOG(LogMRUK, Warning, TEXT("Can not show text at anchor because no text actor is set"));
return;
}
// Spawn Text
if (!ActiveTextActor)
{
FActorSpawnParameters ActorSpawnParams;
ActorSpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
ActorSpawnParams.Owner = GetOwner();
ActiveTextActor = GetWorld()->SpawnActor(TextActorClass, nullptr, ActorSpawnParams);
}
else
{
ActiveTextActor->SetActorHiddenInGame(false);
}
auto TextRenderComponent = ActiveTextActor->GetComponentByClass<UTextRenderComponent>();
FString Text;
for (int i = 0; i < Anchor->SemanticClassifications.Num(); ++i)
{
if (i != 0)
{
Text += ", ";
}
Text += Anchor->SemanticClassifications[i];
}
TextRenderComponent->SetText(FText::FromString(Text));
ActiveTextActor->SetActorLocation(Hit.HitPosition + (Hit.HitNormal * 20.0));
ActiveTextActor->SetActorScale3D(TextScale);
OrientTextActorToPlayer();
SetComponentTickEnabled(true);
}
void UMRUKDebugComponent::HideAnchor()
{
if (ActiveGizmoActor)
{
ActiveGizmoActor->SetActorHiddenInGame(true);
}
if (ActiveTextActor)
{
ActiveTextActor->SetActorHiddenInGame(true);
}
SetComponentTickEnabled(false);
}
void UMRUKDebugComponent::ShowAnchorSpaceAtRayHit(const FVector& Origin, const FVector& Direction)
{
const auto Subsystem = GetOwner()->GetGameInstance()->GetSubsystem<UMRUKSubsystem>();
if (!Subsystem)
{
UE_LOG(LogMRUK, Warning, TEXT("Can not show anchor because there is no MRUtilityKit subsystem"));
return;
}
FMRUKHit Hit{};
const auto Anchor = Subsystem->Raycast(Origin, Direction, 0.0, {}, Hit);
if (!Anchor)
{
return;
}
if (!ActiveAnchorSpaceActor || (ActiveAnchorSpaceActor && ActiveAnchorSpaceActor->GetParentActor() != Anchor))
{
static constexpr double DebugSpaceOffset = 0.5;
HideAnchorSpace();
FActorSpawnParameters ActorSpawnParams;
ActorSpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
ActorSpawnParams.Owner = Anchor;
ActiveAnchorSpaceActor = GetWorld()->SpawnActor<AActor>(ActorSpawnParams);
ActiveAnchorSpaceActor->SetRootComponent(NewObject<USceneComponent>(ActiveAnchorSpaceActor, TEXT("SceneComponent")));
ActiveAnchorSpaceActor->AttachToActor(Anchor, FAttachmentTransformRules::KeepRelativeTransform);
ActiveAnchorSpaceActor->GetRootComponent()->SetMobility(EComponentMobility::Movable);
const auto ProceduralMesh = NewObject<UProceduralMeshComponent>(ActiveAnchorSpaceActor, TEXT("DebugVolumePlane"));
Anchor->GenerateProceduralAnchorMesh(ProceduralMesh, {}, {}, true, false, DebugSpaceOffset);
ActiveAnchorSpaceActor->AddInstanceComponent(ProceduralMesh);
ProceduralMesh->SetupAttachment(ActiveAnchorSpaceActor->GetRootComponent());
ProceduralMesh->RegisterComponent();
}
}
void UMRUKDebugComponent::HideAnchorSpace()
{
if (ActiveAnchorSpaceActor)
{
ActiveAnchorSpaceActor->Destroy();
ActiveAnchorSpaceActor = nullptr;
}
}
void UMRUKDebugComponent::BeginPlay()
{
Super::BeginPlay();
OculusXRTelemetry::TScopedMarker<MRUKTelemetry::FLoadDebugComponentMarker> Event(static_cast<int>(GetTypeHash(this)));
SetComponentTickEnabled(false);
}
void UMRUKDebugComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
UE_LOG(LogTemp, Warning, TEXT("Ticking enabled"));
OrientTextActorToPlayer();
}
void UMRUKDebugComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
Super::EndPlay(EndPlayReason);
if (ActiveGizmoActor)
{
ActiveGizmoActor->Destroy();
ActiveGizmoActor = nullptr;
}
if (ActiveTextActor)
{
ActiveTextActor->Destroy();
ActiveTextActor = nullptr;
}
if (ActiveAnchorSpaceActor)
{
ActiveAnchorSpaceActor->Destroy();
ActiveAnchorSpaceActor = nullptr;
}
}
void UMRUKDebugComponent::OrientTextActorToPlayer() const
{
if (ActiveTextActor)
{
FQuat Orientation;
FVector Position(0.0);
GEngine->XRSystem->GetCurrentPose(IXRTrackingSystem::HMDDeviceId, Orientation, Position);
const auto TextForward = (Position - ActiveTextActor->GetActorLocation()).GetSafeNormal();
const auto TextUp = FVector::UpVector;
const auto TextRot = UKismetMathLibrary::MakeRotFromXZ(TextForward, TextUp);
ActiveTextActor->SetActorRotation(TextRot);
}
}

View File

@ -1,248 +0,0 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#include "MRUtilityKitDestructibleMesh.h"
#include "Engine/GameInstance.h"
#include "MRUtilityKitBPLibrary.h"
#include "MRUtilityKitSubsystem.h"
#include "MRUtilityKitAnchor.h"
#include "MRUtilityKitRoom.h"
#include "MRUtilityKitTelemetry.h"
#include "OculusXRTelemetry.h"
#include "Tasks/Task.h"
constexpr const char* RESERVED_MESH_SEGMENT_TAG = "ReservedMeshSegment";
UMRUKDestructibleMeshComponent::UMRUKDestructibleMeshComponent(const FObjectInitializer& ObjectInitializer)
: UProceduralMeshComponent(ObjectInitializer)
{
PrimaryComponentTick.bCanEverTick = true;
}
void UMRUKDestructibleMeshComponent::SegmentMesh(const TArray<FVector>& MeshPositions, const TArray<uint32>& MeshIndices, const TArray<FVector>& SegmentationPoints)
{
TaskResult = UE::Tasks::Launch(UE_SOURCE_LOCATION, [this, MeshPositions, MeshIndices, SegmentationPoints]() {
TArray<FMRUKMeshSegment> Segments;
FMRUKMeshSegment ReservedMeshSegment;
const FVector ReservedMin(ReservedTop, -1.0, -1.0);
const FVector ReservedMax(ReservedBottom, -1.0, -1.0);
UMRUKBPLibrary::CreateMeshSegmentation(MeshPositions, MeshIndices, SegmentationPoints, ReservedMin, ReservedMax, Segments, ReservedMeshSegment);
return TPair<TArray<FMRUKMeshSegment>, FMRUKMeshSegment>{ MoveTemp(Segments), MoveTemp(ReservedMeshSegment) };
});
SetComponentTickEnabled(true);
}
void UMRUKDestructibleMeshComponent::BeginPlay()
{
Super::BeginPlay();
SetComponentTickEnabled(false);
}
void UMRUKDestructibleMeshComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
if (!TaskResult.IsCompleted())
{
return;
}
const auto& [MeshSegments, ReservedMeshSegment] = TaskResult.GetResult();
for (int32 i = 0; i < MeshSegments.Num(); ++i)
{
const auto& [Positions, Indices] = MeshSegments[i];
const FString ProcMeshName = FString::Printf(TEXT("DestructibleMeshSegment%d"), i);
const auto ProcMesh = NewObject<UProceduralMeshComponent>(GetOwner(), *ProcMeshName);
const FAttachmentTransformRules TransformRules{ EAttachmentRule::KeepRelative, false };
ProcMesh->AttachToComponent(GetOwner()->GetRootComponent(), TransformRules);
ProcMesh->RegisterComponent();
ProcMesh->ComponentTags.AddUnique(TEXT("DestructibleMeshSegment"));
GetOwner()->AddInstanceComponent(ProcMesh);
ProcMesh->CreateMeshSection(0, Positions, Indices, {}, {}, {}, {}, true);
if (GlobalMeshMaterial)
{
ProcMesh->SetMaterial(0, GlobalMeshMaterial);
}
}
if (ReservedMeshSegment.Indices.Num() > 0)
{
const auto ProcMesh = NewObject<UProceduralMeshComponent>(GetOwner(), TEXT("ReservedMeshSegment"));
const FAttachmentTransformRules TransformRules{ EAttachmentRule::KeepRelative, false };
ProcMesh->AttachToComponent(GetOwner()->GetRootComponent(), TransformRules);
ProcMesh->RegisterComponent();
ProcMesh->ComponentTags.AddUnique(RESERVED_MESH_SEGMENT_TAG);
GetOwner()->AddInstanceComponent(ProcMesh);
ProcMesh->CreateMeshSection(0, ReservedMeshSegment.Positions, ReservedMeshSegment.Indices, {}, {}, {}, {}, true);
if (GlobalMeshMaterial)
{
ProcMesh->SetMaterial(0, GlobalMeshMaterial);
}
}
SetComponentTickEnabled(false);
OnMeshesGenerated.Broadcast();
}
AMRUKDestructibleGlobalMesh::AMRUKDestructibleGlobalMesh()
{
DestructibleMeshComponent = CreateDefaultSubobject<UMRUKDestructibleMeshComponent>(TEXT("DestructibleMesh"));
SetRootComponent(DestructibleMeshComponent);
}
void AMRUKDestructibleGlobalMesh::CreateDestructibleMesh(AMRUKRoom* Room)
{
const UMRUKSubsystem* MRUKSubsystem = GetWorld()->GetGameInstance()->GetSubsystem<UMRUKSubsystem>();
if (!Room)
{
Room = MRUKSubsystem->GetCurrentRoom();
}
if (!Room)
{
UE_LOG(LogMRUK, Warning, TEXT("Could not find a room for the destructible mesh"));
return;
}
if (!Room->GlobalMeshAnchor)
{
UE_LOG(LogMRUK, Warning, TEXT("No global mesh available for creating a destructible mesh"));
return;
}
const AMRUKAnchor* GlobalMesh = Room->GlobalMeshAnchor;
UProceduralMeshComponent* GlobalProcMesh = Cast<UProceduralMeshComponent>(GlobalMesh->GetComponentByClass(UProceduralMeshComponent::StaticClass()));
if (!GlobalProcMesh)
{
Room->LoadGlobalMeshFromDevice();
}
if (!GlobalProcMesh)
{
UE_LOG(LogMRUK, Warning, TEXT("Could not load a triangle mesh from the global mesh"));
return;
}
// Attach to the global mesh
const FAttachmentTransformRules AttachmentTransformRules{ EAttachmentRule::KeepRelative, false };
AttachToActor(Room->GlobalMeshAnchor, AttachmentTransformRules);
// Get global mesh data
ensure(GlobalProcMesh);
ensure(GlobalProcMesh->ComponentHasTag("GlobalMesh"));
FProcMeshSection* ProcMeshSection = GlobalProcMesh->GetProcMeshSection(0);
TArray<FVector> MeshPositions;
MeshPositions.SetNum(ProcMeshSection->ProcVertexBuffer.Num());
for (int32 i = 0; i < ProcMeshSection->ProcVertexBuffer.Num(); ++i)
{
MeshPositions[i] = ProcMeshSection->ProcVertexBuffer[i].Position * GetWorldSettings()->WorldToMeters;
}
const TArray<uint32>& MeshIndices = ProcMeshSection->ProcIndexBuffer;
TArray<FVector> SegmentationPointsWS = UMRUKBPLibrary::ComputeRoomBoxGrid(Room, MaxPointsCount, PointsPerUnitX, PointsPerUnitY);
TArray<FVector> SegmentationPointsLS;
SegmentationPointsLS.SetNum(SegmentationPointsWS.Num());
const FTransform T = GlobalMesh->GetActorTransform().Inverse();
for (int32 i = 0; i < SegmentationPointsWS.Num(); ++i)
{
SegmentationPointsLS[i] = T.TransformPosition(SegmentationPointsWS[i]);
}
DestructibleMeshComponent->SegmentMesh(MeshPositions, MeshIndices, SegmentationPointsLS);
}
void AMRUKDestructibleGlobalMesh::RemoveGlobalMeshSegment(UPrimitiveComponent* Mesh)
{
if (!Mesh->ComponentTags.Contains(RESERVED_MESH_SEGMENT_TAG))
{
// Only remove mesh segments that are allowed to be destroyed
Mesh->DestroyComponent();
}
}
void AMRUKDestructibleGlobalMeshSpawner::BeginPlay()
{
Super::BeginPlay();
OculusXRTelemetry::TScopedMarker<MRUKTelemetry::FLoadDestructibleGlobalMeshSpawner> Event(static_cast<int>(GetTypeHash(this)));
if (SpawnMode == EMRUKSpawnMode::CurrentRoomOnly)
{
const auto Subsystem = GetGameInstance()->GetSubsystem<UMRUKSubsystem>();
if (Subsystem->SceneLoadStatus == EMRUKInitStatus::Complete)
{
AddDestructibleGlobalMesh(Subsystem->GetCurrentRoom());
}
else
{
// Only listen for the room created event in case no current room was available yet
Subsystem->OnRoomCreated.AddUniqueDynamic(this, &AMRUKDestructibleGlobalMeshSpawner::OnRoomCreated);
// Remove destructible meshes as soon as the room gets removed
Subsystem->OnRoomRemoved.AddUniqueDynamic(this, &AMRUKDestructibleGlobalMeshSpawner::OnRoomRemoved);
}
}
else if (SpawnMode == EMRUKSpawnMode::AllRooms)
{
const auto Subsystem = GetGameInstance()->GetSubsystem<UMRUKSubsystem>();
for (auto Room : Subsystem->Rooms)
{
AddDestructibleGlobalMesh(Room);
}
// Listen for new rooms that get created
Subsystem->OnRoomCreated.AddUniqueDynamic(this, &AMRUKDestructibleGlobalMeshSpawner::OnRoomCreated);
// Remove destructible meshes as soon as the room gets removed
Subsystem->OnRoomRemoved.AddUniqueDynamic(this, &AMRUKDestructibleGlobalMeshSpawner::OnRoomRemoved);
}
}
void AMRUKDestructibleGlobalMeshSpawner::OnRoomCreated(AMRUKRoom* Room)
{
if (SpawnMode == EMRUKSpawnMode::CurrentRoomOnly && GetGameInstance()->GetSubsystem<UMRUKSubsystem>()->GetCurrentRoom() != Room)
{
// Skip this room if it is not the current room
return;
}
AddDestructibleGlobalMesh(Room);
}
void AMRUKDestructibleGlobalMeshSpawner::OnRoomRemoved(AMRUKRoom* Room)
{
if (!IsValid(Room))
{
return;
}
if (AMRUKDestructibleGlobalMesh** Mesh = SpawnedMeshes.Find(Room))
{
(*Mesh)->Destroy();
SpawnedMeshes.Remove(Room);
}
}
AMRUKDestructibleGlobalMesh* AMRUKDestructibleGlobalMeshSpawner::FindDestructibleMeshForRoom(AMRUKRoom* Room)
{
if (AMRUKDestructibleGlobalMesh** Mesh = SpawnedMeshes.Find(Room))
{
return *Mesh;
}
return nullptr;
}
AMRUKDestructibleGlobalMesh* AMRUKDestructibleGlobalMeshSpawner::AddDestructibleGlobalMesh(AMRUKRoom* Room)
{
if (SpawnedMeshes.Contains(Room))
{
return SpawnedMeshes[Room];
}
AMRUKDestructibleGlobalMesh* Mesh = GetWorld()->SpawnActor<AMRUKDestructibleGlobalMesh>();
Mesh->PointsPerUnitX = PointsPerUnitX;
Mesh->PointsPerUnitY = PointsPerUnitY;
Mesh->MaxPointsCount = MaxPointsCount;
Mesh->DestructibleMeshComponent->GlobalMeshMaterial = GlobalMeshMaterial;
Mesh->DestructibleMeshComponent->ReservedBottom = ReservedBottom;
Mesh->DestructibleMeshComponent->ReservedTop = ReservedTop;
Mesh->CreateDestructibleMesh();
SpawnedMeshes.Add(Room, Mesh);
return Mesh;
}

View File

@ -1,427 +0,0 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#include "MRUtilityKitDistanceMapGenerator.h"
#include "MRUtilityKitSubsystem.h"
#include "MRUtilityKit.h"
#include "MRUtilityKitRoom.h"
#include "MRUtilityKitAnchor.h"
#include "Components/SceneCaptureComponent2D.h"
#include "Engine/CanvasRenderTarget2D.h"
#include "Engine/Canvas.h"
#include "Engine/GameInstance.h"
#include "Engine/World.h"
#include "Kismet/KismetRenderingLibrary.h"
#include "Materials/MaterialInstanceDynamic.h"
#include "Materials/MaterialInterface.h"
#include "UObject/ConstructorHelpers.h"
AMRUKDistanceMapGenerator::AMRUKDistanceMapGenerator()
{
// Create components
Root = CreateDefaultSubobject<USceneComponent>(TEXT("DefaultSceneRoot"));
SceneCapture2D = CreateDefaultSubobject<USceneCaptureComponent2D>(TEXT("SceneCapture2D"));
RootComponent = Root;
// Setup components
SceneCapture2D->SetupAttachment(Root);
SceneCapture2D->ProjectionType = ECameraProjectionMode::Orthographic;
SceneCapture2D->OrthoWidth = 512.0f;
SceneCapture2D->CaptureSource = ESceneCaptureSource::SCS_SceneColorHDR;
SceneCapture2D->PrimitiveRenderMode = ESceneCapturePrimitiveRenderMode::PRM_UseShowOnlyList;
SceneCapture2D->bCaptureEveryFrame = false;
SceneCapture2D->bCaptureOnMovement = false;
const ConstructorHelpers::FObjectFinder<UCanvasRenderTarget2D> RT1Finder(TEXT("/OculusXR/Textures/CRT_JumpFlood1"));
if (RT1Finder.Succeeded())
{
RenderTarget1 = RT1Finder.Object;
}
const ConstructorHelpers::FObjectFinder<UCanvasRenderTarget2D> RT2Finder(TEXT("/OculusXR/Textures/CRT_JumpFlood2"));
if (RT2Finder.Succeeded())
{
RenderTarget2 = RT2Finder.Object;
}
const ConstructorHelpers::FObjectFinder<UCanvasRenderTarget2D> RTMaskFinder(TEXT("/OculusXR/Textures/CRT_Mask"));
if (RTMaskFinder.Succeeded())
{
SceneCapture2D->TextureTarget = RTMaskFinder.Object;
}
const ConstructorHelpers::FObjectFinder<UCanvasRenderTarget2D> RTDistanceMapFinder(TEXT("/OculusXR/Textures/CRT_DistanceMap"));
if (RTDistanceMapFinder.Succeeded())
{
DistanceMapRenderTarget = RTDistanceMapFinder.Object;
}
const ConstructorHelpers::FObjectFinder<UMaterialInterface> MaskMaterialFinder(TEXT("/OculusXR/Materials/M_CreateMask"));
if (MaskMaterialFinder.Succeeded())
{
MaskMaterial = MaskMaterialFinder.Object;
}
const ConstructorHelpers::FObjectFinder<UMaterialInterface> JFPassMaterialFinder(TEXT("/OculusXR/Materials/M_JFAPass"));
if (JFPassMaterialFinder.Succeeded())
{
JFPassMaterial = JFPassMaterialFinder.Object;
}
const ConstructorHelpers::FObjectFinder<UMaterialInterface> SceneObjectMaskMaterialFinder(TEXT("/OculusXR/Materials/M_SceneObjectMask"));
if (SceneObjectMaskMaterialFinder.Succeeded())
{
SceneObjectMaskMaterial = SceneObjectMaskMaterialFinder.Object;
}
const ConstructorHelpers::FObjectFinder<UMaterialInterface> FloorMaskMaterialFinder(TEXT("/OculusXR/Materials/M_FloorMask"));
if (FloorMaskMaterialFinder.Succeeded())
{
FloorMaskMaterial = FloorMaskMaterialFinder.Object;
}
const ConstructorHelpers::FObjectFinder<UMaterialInterface> DistanceMapFreeSpaceMaterialFinder(TEXT("/OculusXR/Materials/M_DistanceMapFree"));
if (DistanceMapFreeSpaceMaterialFinder.Succeeded())
{
DistanceMapFreeSpaceMaterial = DistanceMapFreeSpaceMaterialFinder.Object;
}
const ConstructorHelpers::FObjectFinder<UMaterialInterface> DistanceMapOccupiedSpaceMaterialFinder(TEXT("/OculusXR/Materials/M_DistanceMapOccupied"));
if (DistanceMapOccupiedSpaceMaterialFinder.Succeeded())
{
DistanceMapOccupiedSpaceMaterial = DistanceMapOccupiedSpaceMaterialFinder.Object;
}
const ConstructorHelpers::FObjectFinder<UMaterialInterface> DistanceMapAllSpaceMaterialFinder(TEXT("/OculusXR/Materials/M_DistanceMapAll"));
if (DistanceMapAllSpaceMaterialFinder.Succeeded())
{
DistanceMapAllSpaceMaterial = DistanceMapAllSpaceMaterialFinder.Object;
}
}
void AMRUKDistanceMapGenerator::BeginPlay()
{
Super::BeginPlay();
SceneObjectMaskMaterial->EnsureIsComplete();
FloorMaskMaterial->EnsureIsComplete();
if (SpawnMode == EMRUKSpawnMode::CurrentRoomOnly)
{
const auto Subsystem = GetGameInstance()->GetSubsystem<UMRUKSubsystem>();
if (AMRUKRoom* CurrentRoom = Subsystem->GetCurrentRoom())
{
CreateMaskMeshesForRoom(CurrentRoom);
}
else
{
Subsystem->OnRoomCreated.AddUniqueDynamic(this, &AMRUKDistanceMapGenerator::OnRoomCreated);
}
}
else if (SpawnMode == EMRUKSpawnMode::AllRooms)
{
const auto Subsystem = GetGameInstance()->GetSubsystem<UMRUKSubsystem>();
for (auto& Room : Subsystem->Rooms)
{
CreateMaskMeshesForRoom(Room);
}
Subsystem->OnRoomCreated.AddUniqueDynamic(this, &AMRUKDistanceMapGenerator::OnRoomCreated);
}
}
UTexture* AMRUKDistanceMapGenerator::CaptureDistanceMap()
{
CaptureInitialSceneMask();
RenderDistanceMap();
return GetDistanceMap();
}
void AMRUKDistanceMapGenerator::CaptureInitialSceneMask()
{
if (!JFPassMaterialInstance)
{
JFPassMaterialInstance = UMaterialInstanceDynamic::Create(JFPassMaterial, this);
}
if (DistanceMapFreeSpaceMaterial && !DistanceMapFreeSpaceMaterialInstance)
{
DistanceMapFreeSpaceMaterialInstance = UMaterialInstanceDynamic::Create(DistanceMapFreeSpaceMaterial, this);
}
if (DistanceMapOccupiedSpaceMaterial && !DistanceMapOccupiedSpaceMaterialInstance)
{
DistanceMapOccupiedSpaceMaterialInstance = UMaterialInstanceDynamic::Create(DistanceMapOccupiedSpaceMaterial, this);
}
if (DistanceMapAllSpaceMaterial && !DistanceMapAllSpaceMaterialInstance)
{
DistanceMapAllSpaceMaterialInstance = UMaterialInstanceDynamic::Create(DistanceMapAllSpaceMaterial, this);
}
check(SceneCapture2D->TextureTarget->SizeX == SceneCapture2D->TextureTarget->SizeY);
SceneCapture2D->CaptureScene();
// Renders the texture that was captured by the scene capture component into a mask that can then be used further down
UKismetRenderingLibrary::ClearRenderTarget2D(GetWorld(), RenderTarget1, FLinearColor::Black);
UCanvas* Canvas{};
FVector2D Size{};
FDrawToRenderTargetContext RenderTargetContext{};
UKismetRenderingLibrary::BeginDrawCanvasToRenderTarget(GetWorld(), RenderTarget1, Canvas, Size, RenderTargetContext);
Canvas->K2_DrawMaterial(MaskMaterial, FVector2D::ZeroVector, Size, FVector2D::ZeroVector);
UKismetRenderingLibrary::EndDrawCanvasToRenderTarget(GetWorld(), RenderTargetContext);
}
void AMRUKDistanceMapGenerator::RenderDistanceMap()
{
UCanvasRenderTarget2D* RTs[2] = { RenderTarget1, RenderTarget2 };
int32 RTIndex = 0;
const double TextureSize = SceneCapture2D->TextureTarget->SizeX;
check(TextureSize == RenderTarget1->SizeX);
check(TextureSize == RenderTarget1->SizeY);
check(TextureSize == RenderTarget2->SizeX);
check(TextureSize == RenderTarget2->SizeY);
const int32 LastIndex = static_cast<int32>(FMath::Log2(TextureSize / 2.0));
// Play buffer ping pong and execute the jump flood algorithm on each step
for (int32 I = 1; I <= LastIndex; ++I)
{
// Read from the render target that we have written before
JFPassMaterialInstance->SetTextureParameterValue(FName("RT"), RTs[RTIndex]);
const double Step = 1.0 / FMath::Pow(2.0, static_cast<double>(I));
JFPassMaterialInstance->SetScalarParameterValue(FName("Step"), Step);
// Make sure to render to the other render target
RTIndex = (RTIndex + 1) % 2;
UCanvasRenderTarget2D* RT = RTs[RTIndex];
UKismetRenderingLibrary::ClearRenderTarget2D(GetWorld(), RT, FLinearColor::Black);
UCanvas* Canvas{};
FVector2D Size{};
FDrawToRenderTargetContext RenderTargetContext{};
UKismetRenderingLibrary::BeginDrawCanvasToRenderTarget(GetWorld(), RT, Canvas, Size, RenderTargetContext);
Canvas->K2_DrawMaterial(JFPassMaterialInstance, FVector2D::ZeroVector, Size, FVector2D::ZeroVector);
UKismetRenderingLibrary::EndDrawCanvasToRenderTarget(GetWorld(), RenderTargetContext);
}
DistanceMapRT = RTIndex;
if (DistanceMapGenerationMode != EMRUKDistanceMapGenerationMode::None)
{
UMaterialInstanceDynamic* RenderMaterial = nullptr;
UKismetRenderingLibrary::ClearRenderTarget2D(GetWorld(), DistanceMapRenderTarget);
UCanvas* Canvas{};
FVector2D Size{};
FDrawToRenderTargetContext RenderTargetContext{};
UKismetRenderingLibrary::BeginDrawCanvasToRenderTarget(GetWorld(), DistanceMapRenderTarget, Canvas, Size, RenderTargetContext);
switch (DistanceMapGenerationMode)
{
case EMRUKDistanceMapGenerationMode::FreeSpace:
RenderMaterial = DistanceMapFreeSpaceMaterialInstance;
break;
case EMRUKDistanceMapGenerationMode::OccupiedSpace:
RenderMaterial = DistanceMapOccupiedSpaceMaterialInstance;
break;
case EMRUKDistanceMapGenerationMode::AllSpace:
RenderMaterial = DistanceMapAllSpaceMaterialInstance;
break;
case EMRUKDistanceMapGenerationMode::None:
RenderMaterial = nullptr;
break;
}
if (RenderMaterial)
{
RenderMaterial->SetTextureParameterValue(FName("RT"), GetDistanceMapRenderTarget());
Canvas->K2_DrawMaterial(RenderMaterial, FVector2D::ZeroVector, Size, FVector2D::ZeroVector);
}
UKismetRenderingLibrary::EndDrawCanvasToRenderTarget(GetWorld(), RenderTargetContext);
}
}
void AMRUKDistanceMapGenerator::OnRoomCreated(AMRUKRoom* Room)
{
if (SpawnMode == EMRUKSpawnMode::CurrentRoomOnly && GetGameInstance()->GetSubsystem<UMRUKSubsystem>()->GetCurrentRoom() != Room)
{
// Skip this room if it is not the current room
return;
}
CreateMaskMeshesForRoom(Room);
}
void AMRUKDistanceMapGenerator::OnRoomUpdated(AMRUKRoom* Room)
{
if (!SpawnedMaskMeshes.Find(Room))
{
// A room was updated that we don't care about. If we are in current room only mode
// we only want to update the one room we created
return;
}
CreateMaskMeshesForRoom(Room);
}
void AMRUKDistanceMapGenerator::CreateMaskMeshesForRoom(AMRUKRoom* Room)
{
if (!Room)
{
UE_LOG(LogMRUK, Warning, TEXT("Can not create masked meshes for room that is a nullptr"));
return;
}
if (TArray<AActor*>* Actors = SpawnedMaskMeshes.Find(Room))
{
for (AActor* Actor : *Actors)
{
Actor->Destroy();
}
Actors->Empty();
SpawnedMaskMeshes.Remove(Room);
}
// Create for each anchor a mesh with a material to use as a mask
// to initialize the jump flood algorithm.
TArray<AActor*> SpawnedActors;
for (auto& Anchor : Room->AllAnchors)
{
if (!Anchor->VolumeBounds.IsValid)
{
continue;
}
SpawnedActors.Push(CreateMaskMeshOfAnchor(Anchor));
}
if (Room->FloorAnchor)
{
SpawnedActors.Push(CreateMaskMeshOfAnchor(Room->FloorAnchor));
}
SpawnedMaskMeshes.Add(Room, SpawnedActors);
const auto Subsystem = GetGameInstance()->GetSubsystem<UMRUKSubsystem>();
Subsystem->OnRoomRemoved.AddUniqueDynamic(this, &AMRUKDistanceMapGenerator::RemoveMaskMeshesFromRoom);
Subsystem->OnRoomUpdated.AddUniqueDynamic(this, &AMRUKDistanceMapGenerator::OnRoomUpdated);
OnReady.Broadcast();
}
AActor* AMRUKDistanceMapGenerator::CreateMaskMeshOfAnchor(AMRUKAnchor* Anchor)
{
check(Anchor);
FActorSpawnParameters ActorSpawnParams{};
ActorSpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
ActorSpawnParams.Owner = Anchor;
AActor* Actor = GetWorld()->SpawnActor<AActor>(ActorSpawnParams);
Actor->Tags.Push(GMRUK_DISTANCE_MAP_ACTOR_TAG);
const auto R = NewObject<USceneComponent>(Actor, TEXT("Root"));
Actor->SetRootComponent(R);
R->RegisterComponent();
Actor->AddInstanceComponent(R);
Actor->AttachToActor(Anchor, FAttachmentTransformRules::KeepRelativeTransform);
const auto ProceduralMesh = NewObject<UProceduralMeshComponent>(Actor, TEXT("DistanceMapMesh"));
Anchor->GenerateProceduralAnchorMesh(ProceduralMesh, {}, {}, true, false);
// Set a material depending if the anchor is the floor or a scene object.
// The different materials have different colors. These colors will be used to create different
// initialization masks for the jump flood algorithm.
if (Anchor == Anchor->Room->FloorAnchor)
{
ProceduralMesh->SetMaterial(0, FloorMaskMaterial);
}
else
{
ProceduralMesh->SetMaterial(0, SceneObjectMaskMaterial);
}
ProceduralMesh->SetupAttachment(Actor->GetRootComponent());
ProceduralMesh->RegisterComponent();
Actor->AddInstanceComponent(ProceduralMesh);
// The created meshes will be only used to create a mask for jump flood.
// Therefore we don't want them to show up in the normal camera.
// This unfortunate means that the meshes will show up in other scene captures the user may place as well.
ProceduralMesh->SetVisibleInSceneCaptureOnly(true);
SceneCapture2D->ShowOnlyActors.Push(Actor);
return Actor;
}
AActor* AMRUKDistanceMapGenerator::UpdateMaskMeshOfAnchor(AMRUKAnchor* Anchor)
{
TArray<AActor*> ChildActors;
Anchor->GetAllChildActors(ChildActors);
for (auto Child : ChildActors)
{
if (Child->ActorHasTag(GMRUK_DISTANCE_MAP_ACTOR_TAG))
{
// Remove existing distance map actor
SceneCapture2D->ShowOnlyActors.Remove(Child);
Child->Destroy();
}
}
return CreateMaskMeshOfAnchor(Anchor);
}
UTexture* AMRUKDistanceMapGenerator::GetDistanceMap() const
{
return GetDistanceMapRenderTarget();
}
UCanvasRenderTarget2D* AMRUKDistanceMapGenerator::GetDistanceMapRenderTarget() const
{
if (DistanceMapRT == -1)
{
UE_LOG(LogMRUK, Warning, TEXT("Make sure to first render the distance map by calling CaptureDistanceMap()"));
return nullptr;
}
check(DistanceMapRT >= 0);
UCanvasRenderTarget2D* RTs[2] = { RenderTarget1, RenderTarget2 };
return RTs[DistanceMapRT];
}
FMinimalViewInfo AMRUKDistanceMapGenerator::GetSceneCaptureView() const
{
FMinimalViewInfo Info = {};
SceneCapture2D->GetCameraView(1.0f, Info);
return Info;
}
void AMRUKDistanceMapGenerator::RemoveMaskMeshesFromRoom(AMRUKRoom* Room)
{
if (!Room)
{
UE_LOG(LogMRUK, Warning, TEXT("Can not remove masked meshes for room that is a nullptr"));
return;
}
if (TArray<AActor*>* Actors = SpawnedMaskMeshes.Find(Room))
{
for (AActor* Actor : *Actors)
{
Actor->Destroy();
}
Actors->Empty();
SpawnedMaskMeshes.Remove(Room);
}
}

View File

@ -1,42 +0,0 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#include "MRUtilityKitGeometry.h"
#include "MRUtilityKit.h"
#include "Generated/MRUtilityKitShared.h"
void MRUKTriangulatePolygon(const TArray<TArray<FVector2f>>& Polygons, TArray<FVector2D>& Vertices, TArray<int32>& Indices)
{
Vertices.Empty();
Indices.Empty();
auto MRUKShared = MRUKShared::GetInstance();
if (!MRUKShared)
{
UE_LOG(LogMRUK, Error, TEXT("MRUK shared library is not available. To use this functionality make sure the library is included"));
return;
}
TArray<MRUKShared::MrukPolygon2f> ConvertedPolygons;
ConvertedPolygons.Reserve(Polygons.Num());
for (const auto& Polygon : Polygons)
{
ConvertedPolygons.Push({ Polygon.GetData(), static_cast<uint32_t>(Polygon.Num()) });
}
auto Mesh = MRUKShared->TriangulatePolygon(ConvertedPolygons.GetData(), ConvertedPolygons.Num());
Vertices.Reserve(Mesh.numVertices);
Indices.Reserve(Mesh.numIndices);
for (uint32_t i = 0; i < Mesh.numVertices; ++i)
{
Vertices.Push(FVector2D(Mesh.vertices[i]));
}
for (uint32_t i = 0; i < Mesh.numIndices; ++i)
{
Indices.Push(Mesh.indices[i]);
}
MRUKShared->FreeMesh(&Mesh);
}

View File

@ -1,281 +0,0 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#include "MRUtilityKitGridSliceResizer.h"
#include "MRUtilityKit.h"
#include "MRUtilityKitTelemetry.h"
#include "OculusXRTelemetry.h"
#include "Engine/StaticMesh.h"
#include "ProceduralMeshComponent.h"
#include "StaticMeshResources.h"
UMRUKGridSliceResizerComponent::UMRUKGridSliceResizerComponent()
{
PrimaryComponentTick.bCanEverTick = true;
ProcMesh = CreateDefaultSubobject<UProceduralMeshComponent>(TEXT("ProcMesh"));
ProcMesh->SetupAttachment(this);
}
void UMRUKGridSliceResizerComponent::BeginPlay()
{
Super::BeginPlay();
OculusXRTelemetry::TScopedMarker<MRUKTelemetry::FLoadGridSliceResizerMarker> Event(static_cast<int>(GetTypeHash(this)));
SliceMesh();
}
void UMRUKGridSliceResizerComponent::OnRegister()
{
Super::OnRegister();
SliceMesh();
}
void UMRUKGridSliceResizerComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
const FVector ActorScale = GetOwner() ? GetOwner()->GetActorScale() : FVector::OneVector;
if (Mesh && ActorScale != ResizerScale)
{
ResizerScale = ActorScale;
SliceMesh();
}
}
#if WITH_EDITOR
void UMRUKGridSliceResizerComponent::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
Super::PostEditChangeProperty(PropertyChangedEvent);
if (PropertyChangedEvent.Property->GetOwner<AActor>() == GetOwner())
{
return;
}
const FName PropertyName = (PropertyChangedEvent.Property != nullptr) ? PropertyChangedEvent.Property->GetFName() : NAME_None;
if (PropertyName == GET_MEMBER_NAME_CHECKED(UMRUKGridSliceResizerComponent, BorderXNegative)
|| PropertyName == GET_MEMBER_NAME_CHECKED(UMRUKGridSliceResizerComponent, BorderXPositive)
|| PropertyName == GET_MEMBER_NAME_CHECKED(UMRUKGridSliceResizerComponent, BorderYNegative)
|| PropertyName == GET_MEMBER_NAME_CHECKED(UMRUKGridSliceResizerComponent, BorderYPositive)
|| PropertyName == GET_MEMBER_NAME_CHECKED(UMRUKGridSliceResizerComponent, BorderZNegative)
|| PropertyName == GET_MEMBER_NAME_CHECKED(UMRUKGridSliceResizerComponent, BorderZPositive)
|| PropertyName == GET_MEMBER_NAME_CHECKED(UMRUKGridSliceResizerComponent, SlicerPivotOffset))
{
SliceMesh();
}
}
#endif
void UMRUKGridSliceResizerComponent::SliceMesh()
{
if (!Mesh)
{
return;
}
if (!Mesh->bAllowCPUAccess)
{
UE_LOG(LogMRUK, Error, TEXT("Can not slice a mesh that has no CPU access. Make sure you enable CPU access on the static mesh asset."));
return;
}
TArray<FVector> Positions;
TArray<FVector> Normals;
TArray<FVector2D> UVs;
TArray<FColor> Colors;
TArray<int32> Triangles;
const FStaticMeshLODResources& LODResources = Mesh->GetRenderData()->LODResources[0];
const FStaticMeshVertexBuffers& VertexBuffers = LODResources.VertexBuffers;
const FRawStaticIndexBuffer& IndexBuffer = LODResources.IndexBuffer;
Positions.SetNum(LODResources.GetNumVertices());
Normals.SetNum(LODResources.GetNumVertices());
UVs.SetNum(LODResources.GetNumVertices());
Colors.SetNum(LODResources.GetNumVertices());
const FVector ActorScale = GetOwner() ? GetOwner()->GetActorScale() : FVector::OneVector;
const FVector ActorScaleInv = FVector(1.0 / ActorScale.X, 1.0 / ActorScale.Y, 1.0 / ActorScale.Z);
const FVector Size = ActorScale;
// Slicing
FTransform PivotTransform;
PivotTransform.SetLocation(-SlicerPivotOffset);
FTransform ScaledInvPivotTransform;
ScaledInvPivotTransform.SetLocation(Size * SlicerPivotOffset);
// The bounding box of the mesh to resize
FBox BBox = Mesh->GetBoundingBox();
BBox = FBox(PivotTransform.TransformPosition(BBox.Min), PivotTransform.TransformPosition(BBox.Max));
// The bounding box of the mesh to resize scaled by the size
const FBox BBoxScaled = FBox(BBox.Min * Size, BBox.Max * Size);
// The bounding box of the mesh to resize scaled including the pivot point
// This may be a bigger box as ScaledBBox in case the pivot is outside of the scaled bounding box.
const FBox BBoxScaledPivot = FBox(
FVector(FMath::Min(BBox.Min.X, SlicerPivotOffset.X), FMath::Min(BBox.Min.Y, SlicerPivotOffset.Y), FMath::Min(BBox.Min.Z, SlicerPivotOffset.Z)),
FVector(FMath::Max(BBox.Max.X, SlicerPivotOffset.X), FMath::Max(BBox.Max.Y, SlicerPivotOffset.Y), FMath::Max(BBox.Max.Z, SlicerPivotOffset.Z)));
// Locations of the border slices between 0 - 1
FVector BorderPos = FVector(BorderXPositive, BorderYPositive, BorderZPositive);
FVector BorderNeg = FVector(BorderXNegative, BorderYNegative, BorderZNegative);
// Locations of the border slices for the X,Y,Z axis in local space
FVector BorderPosLS;
FVector BorderNegLS;
// Distance from the Border[Pos|Neg]LS to the outer maximum/minimum of the BBox
FVector StubPos;
FVector StubNeg;
// The inner bounding box that should be stretched in all directions
FVector BBoxInnerMax;
FVector BBoxInnerMin;
// The expected bounding box of the inner bounding box when its scaled up by the size
FVector BBoxInnerScaledMax;
FVector BBoxInnerScaledMin;
// The ratio between the inner bounding box and the scaled bounding box
FVector InnerBoxScaleRatioMax;
FVector InnerBoxScaleRatioMin;
// The ratio to use for downscaling in case it's needed
FVector DownscaleMax;
FVector DownscaleMin;
for (int32 I = 0; I < 3; ++I)
{
// We don't want to have division by zero further down the line
BorderPos[I] = FMath::Clamp(BorderPos[I], DBL_EPSILON, 1.0);
BorderNeg[I] = FMath::Clamp(BorderNeg[I], DBL_EPSILON, 1.0);
BorderPosLS[I] = BBoxScaledPivot.Max[I] - (1.0 - BorderPos[I]) * FMath::Abs(BBoxScaledPivot.Max[I]);
BorderNegLS[I] = BBoxScaledPivot.Min[I] + (1.0 - BorderNeg[I]) * FMath::Abs(BBoxScaledPivot.Min[I]);
StubPos[I] = FMath::Abs(BBox.Max[I] - BorderPosLS[I]);
StubNeg[I] = FMath::Abs(BBox.Min[I] - BorderNegLS[I]);
BBoxInnerMax[I] = BBox.Max[I] - StubPos[I];
BBoxInnerMin[I] = BBox.Min[I] + StubNeg[I];
// Max may be negative and Min may be positive in case the stubs are greater than
// the scaled down bounding box and therefore don't fit the scaled bounding box.
// This case gets treated special down below.
BBoxInnerScaledMax[I] = BBoxScaled.Max[I] - StubPos[I];
BBoxInnerScaledMin[I] = BBoxScaled.Min[I] + StubNeg[I];
InnerBoxScaleRatioMax[I] = FMath::Max(0.0, BBoxInnerScaledMax[I] / BBoxInnerMax[I]);
InnerBoxScaleRatioMin[I] = FMath::Max(0.0, BBoxInnerScaledMin[I] / BBoxInnerMin[I]);
// When Downscale[Min/Max] needs to be applied the temporary bounding box is
// Max == StubPos, Min == StubNeg. Therefore get the ratio between it and the
// expected scaled down bounding box to calculate the scale that needs
// to be applied
DownscaleMax[I] = BBoxScaled.Max[I] / StubPos[I];
DownscaleMin[I] = BBoxScaled.Min[I] / StubNeg[I];
}
// Process vertices
// If the center shouldn't be scaled we need to take care of the case when the original
// center vertices would be outside of the expected downscaled bounding box. Therefore, iterate
// through all vertices and check if the center vertices are outside. If they are outside we need
// to scale down the center part as usually.
// This unfortunately has to be done in a separate first pass.
bool ScaleDownCenter[3] = { false, false, false };
const int32 VertexCount = LODResources.GetNumVertices();
for (int32 I = 0; I < VertexCount; ++I)
{
const FVector3f& Normal = VertexBuffers.StaticMeshVertexBuffer.VertexTangentZ(I);
Normals[I] = FVector(Normal.X, Normal.Y, Normal.Z);
const FVector2f& UV = VertexBuffers.StaticMeshVertexBuffer.GetVertexUV(I, 0);
UVs[I] = FVector2D(UV.X, UV.Y);
const FVector3f& P = VertexBuffers.PositionVertexBuffer.VertexPosition(I);
// Apply pivot offset
Positions[I] = PivotTransform.TransformPosition(FVector(P.X, P.Y, P.Z));
const FVector& Position = Positions[I];
for (int32 A = 0; A < 3; ++A)
{
if ((0.0 <= Position[A] && Position[A] <= BorderPosLS[A]) && (Position[A] > BBoxInnerScaledMax[A]))
{
ScaleDownCenter[A] = true;
}
else if ((BorderNegLS[A] <= Position[A] && Position[A] <= 0.0) && (Position[A] < BBoxInnerScaledMin[A]))
{
ScaleDownCenter[A] = true;
}
}
}
bool bScaleCenter[3] = {};
bScaleCenter[0] = ScaleCenterMode & static_cast<uint8>(EMRUKScaleCenterMode::XAxis) ? true : false;
bScaleCenter[1] = ScaleCenterMode & static_cast<uint8>(EMRUKScaleCenterMode::YAxis) ? true : false;
bScaleCenter[2] = ScaleCenterMode & static_cast<uint8>(EMRUKScaleCenterMode::ZAxis) ? true : false;
for (FVector& Position : Positions)
{
// Apply computations on each axis
for (int32 A = 0; A < 3; ++A)
{
if ((bScaleCenter[A] || ScaleDownCenter[A]) && (0.0 <= Position[A] && Position[A] <= BorderPosLS[A]))
{
// Vertex is inside the inner distance and should be stretched
Position[A] *= InnerBoxScaleRatioMax[A];
}
else if ((bScaleCenter[A] || ScaleDownCenter[A]) && (BorderNegLS[A] <= Position[A] && Position[A] <= 0.0))
{
// Vertex is inside the inner distance and should be stretched
Position[A] *= InnerBoxScaleRatioMin[A];
}
else if (BorderPosLS[A] < Position[A])
{
// Vertex is inside the outer stub and should not be stretched
// Perform linear transform of vertices into their expect position
Position[A] = BorderPosLS[A] * InnerBoxScaleRatioMax[A] + (Position[A] - BorderPosLS[A]);
if (BBoxInnerScaledMax[A] < 0.0)
{
// The mesh that would result from the linear transform above is still not small enough to
// fit into the expected scaled down bounding box. This means the stubs need to be scaled down
// to make them fit.
Position[A] *= DownscaleMax[A];
}
}
else if (Position[A] < BorderNegLS[A])
{
// Vertex is inside the outer stub and should not be stretched
// Perform linear transform of vertices into their expect position
Position[A] = BorderNegLS[A] * InnerBoxScaleRatioMin[A] - (BorderNegLS[A] - Position[A]);
if (BBoxInnerScaledMin[A] > 0.0)
{
// The mesh that would result from the linear transform above is still not small enough to
// fit into the expected scaled down bounding box. This means the stubs need to be scaled down
// to make them fit.
Position[A] *= -DownscaleMin[A];
}
}
}
// Undo pivot offset
Position = ActorScaleInv * ScaledInvPivotTransform.TransformPosition(Position);
}
Triangles.SetNum(IndexBuffer.GetNumIndices());
for (int32 I = 0; I < IndexBuffer.GetNumIndices(); ++I)
{
Triangles[I] = IndexBuffer.GetIndex(I);
}
ProcMesh->ClearMeshSection(0);
ProcMesh->CreateMeshSection(0, Positions, Triangles, Normals, UVs, Colors, {}, bGenerateCollision);
ProcMesh->SetMaterial(0, Mesh->GetMaterial(0));
}

View File

@ -1,17 +0,0 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#include "MRUtilityKitGuardian.h"
AMRUKGuardian::AMRUKGuardian(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
// Create a scene component as root so we can attach spawned actors to it
RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("SceneComponent"));
}
void AMRUKGuardian::CreateGuardian(UProceduralMeshComponent* GuardianMesh)
{
GuardianMesh->SetupAttachment(RootComponent);
GuardianMesh->RegisterComponent();
GuardianMeshComponent = GuardianMesh;
}

View File

@ -1,235 +0,0 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#include "MRUtilityKitGuardianSpawner.h"
#include "Engine/GameInstance.h"
#include "Engine/GameEngine.h"
#include "IXRTrackingSystem.h"
#include "Materials/MaterialInstanceDynamic.h"
#include "MRUtilityKitAnchor.h"
#include "MRUtilityKitGuardian.h"
#include "MRUtilityKitRoom.h"
#include "MRUtilityKitSubsystem.h"
#include "MRUtilityKitTelemetry.h"
AMRUKGuardianSpawner::AMRUKGuardianSpawner()
{
PrimaryActorTick.bCanEverTick = true;
PrimaryActorTick.bTickEvenWhenPaused = true;
PrimaryActorTick.TickGroup = TG_PrePhysics;
}
void AMRUKGuardianSpawner::SetGuardianMaterial(UMaterialInstance* Material)
{
if (!Material)
{
return;
}
GuardianMaterial = Material;
DynamicGuardianMaterial = UMaterialInstanceDynamic::Create(GuardianMaterial, this);
DynamicGuardianMaterial->SetVectorParameterValue(TEXT("WallScale"), FVector(GridDensity));
// Recreate guardian meshes
TArray<AMRUKRoom*> Rooms;
SpawnedGuardians.GetKeys(Rooms);
for (AMRUKRoom* Room : Rooms)
{
SpawnGuardians(Room);
}
}
void AMRUKGuardianSpawner::SpawnGuardians(AMRUKRoom* Room)
{
if (!IsValid(Room))
{
UE_LOG(LogMRUK, Warning, TEXT("Can not spawn Guardians for a room that is a nullptr"));
return;
}
// Remove guardians that are already in this room
DestroyGuardians(Room);
const auto Subsystem = GetGameInstance()->GetSubsystem<UMRUKSubsystem>();
Subsystem->OnRoomUpdated.AddUniqueDynamic(this, &AMRUKGuardianSpawner::OnRoomUpdated);
Subsystem->OnRoomRemoved.AddUniqueDynamic(this, &AMRUKGuardianSpawner::OnRoomRemoved);
const auto SpawnGuardian = [this](AMRUKAnchor* Anchor, const TArray<FMRUKPlaneUV>& PlaneUVAdjustments) {
// Create guardian actor
const auto GuardianActor = GetWorld()->SpawnActor<AMRUKGuardian>();
GuardianActor->AttachToComponent(Anchor->GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform);
GuardianActor->SetActorHiddenInGame(IsHidden());
// Generate procedural mesh
const auto ProceduralMesh = NewObject<UProceduralMeshComponent>(GuardianActor, TEXT("GuardianMesh"));
Anchor->GenerateProceduralAnchorMesh(ProceduralMesh, PlaneUVAdjustments, {}, true, false, 0.01);
ProceduralMesh->SetMaterial(0, DynamicGuardianMaterial);
GuardianActor->CreateGuardian(ProceduralMesh);
return GuardianActor;
};
TArray<AMRUKGuardian*> SpawnedActors;
// Attach procedural meshes to the walls first because they are connected.
TArray<FMRUKAnchorWithPlaneUVs> AnchorsWithPlaneUVs;
const TArray<FMRUKTexCoordModes> WallTextureCoordinateModes = { { EMRUKCoordModeU::Metric, EMRUKCoordModeV::Metric } };
Room->ComputeWallMeshUVAdjustments(WallTextureCoordinateModes, AnchorsWithPlaneUVs);
for (const auto& [Anchor, PlaneUVs] : AnchorsWithPlaneUVs)
{
SpawnedActors.Push(SpawnGuardian(Anchor, PlaneUVs));
}
// Attach procedural meshes to the rest of the anchors. The walls have already meshes applied
// because of the first step and will therefore be ignored by this code automatically.
for (const auto& Anchor : Room->AllAnchors)
{
if (!Anchor || Anchor == Room->FloorAnchor || Anchor == Room->CeilingAnchor || Room->IsWallAnchor(Anchor))
{
continue;
}
SpawnedActors.Push(SpawnGuardian(Anchor, {}));
}
SpawnedGuardians.Add(Room, SpawnedActors);
}
void AMRUKGuardianSpawner::SetGridDensity(double Density)
{
GridDensity = Density;
if (DynamicGuardianMaterial)
{
DynamicGuardianMaterial->SetVectorParameterValue(TEXT("WallScale"), FVector(GridDensity));
}
}
void AMRUKGuardianSpawner::Tick(float DeltaSeconds)
{
if (!DynamicGuardianMaterial)
{
return;
}
if (EnableFade)
{
const auto Subsystem = GetGameInstance()->GetSubsystem<UMRUKSubsystem>();
const auto CurrentRoom = Subsystem->GetCurrentRoom();
if (!CurrentRoom)
{
return;
}
FQuat HeadsetOrientation;
FVector HeadsetPosition(0.f);
GEngine->XRSystem->GetCurrentPose(IXRTrackingSystem::HMDDeviceId, HeadsetOrientation, HeadsetPosition);
FVector SurfacePosition = FVector::ZeroVector;
double SurfaceDistance = 0.0;
FMRUKLabelFilter LabelFilter;
LabelFilter.ExcludedLabels = { FMRUKLabels::Ceiling, FMRUKLabels::Floor };
CurrentRoom->TryGetClosestSurfacePosition(HeadsetPosition, SurfacePosition, SurfaceDistance, LabelFilter);
const auto WorldToMeters = GetWorldSettings()->WorldToMeters;
const auto GuardianFade = FMath::Clamp(1.0 - ((SurfaceDistance / WorldToMeters) / GuardianDistance), 0.0, 1.0);
DynamicGuardianMaterial->SetScalarParameterValue(TEXT("Fade"), GuardianFade);
}
}
void AMRUKGuardianSpawner::BeginPlay()
{
Super::BeginPlay();
SetGuardianMaterial(GuardianMaterial);
OculusXRTelemetry::TScopedMarker<MRUKTelemetry::FLoadGuardianMarker> Event(static_cast<int>(GetTypeHash(this)));
if (SpawnMode == EMRUKSpawnMode::CurrentRoomOnly)
{
const auto Subsystem = GetGameInstance()->GetSubsystem<UMRUKSubsystem>();
if (Subsystem->SceneLoadStatus == EMRUKInitStatus::Complete)
{
if (AMRUKRoom* CurrentRoom = Subsystem->GetCurrentRoom())
{
SpawnGuardians(CurrentRoom);
}
}
else
{
// Only listen for the room created event in case no current room was available yet
Subsystem->OnRoomCreated.AddUniqueDynamic(this, &AMRUKGuardianSpawner::OnRoomCreated);
}
}
else if (SpawnMode == EMRUKSpawnMode::AllRooms)
{
const auto Subsystem = GetGameInstance()->GetSubsystem<UMRUKSubsystem>();
for (auto Room : Subsystem->Rooms)
{
SpawnGuardians(Room);
}
// Listen for new rooms that get created
Subsystem->OnRoomCreated.AddUniqueDynamic(this, &AMRUKGuardianSpawner::OnRoomCreated);
}
}
#if WITH_EDITOR
void AMRUKGuardianSpawner::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
const auto PropertyName = (PropertyChangedEvent.Property != nullptr) ? PropertyChangedEvent.Property->GetFName() : NAME_None;
if (PropertyName == GET_MEMBER_NAME_CHECKED(AMRUKGuardianSpawner, GridDensity))
{
SetGridDensity(GridDensity);
}
else if (PropertyName == GET_MEMBER_NAME_CHECKED(AMRUKGuardianSpawner, GuardianMaterial))
{
SetGuardianMaterial(GuardianMaterial);
}
Super::PostEditChangeProperty(PropertyChangedEvent);
}
#endif
void AMRUKGuardianSpawner::OnRoomCreated(AMRUKRoom* Room)
{
if (SpawnMode == EMRUKSpawnMode::CurrentRoomOnly && GetGameInstance()->GetSubsystem<UMRUKSubsystem>()->GetCurrentRoom() != Room)
{
// Skip this room if it is not the current room
return;
}
SpawnGuardians(Room);
}
void AMRUKGuardianSpawner::OnRoomUpdated(AMRUKRoom* Room)
{
if (!SpawnedGuardians.Find(Room))
{
// A room was updated that we don't care about. If we are in current room only mode
// we only want to update the one room we created
return;
}
SpawnGuardians(Room);
}
void AMRUKGuardianSpawner::OnRoomRemoved(AMRUKRoom* Room)
{
DestroyGuardians(Room);
}
void AMRUKGuardianSpawner::DestroyGuardians(AMRUKRoom* Room)
{
if (TArray<AMRUKGuardian*>* Actors = SpawnedGuardians.Find(Room))
{
for (AActor* Actor : *Actors)
{
if (IsValid(Actor))
{
Actor->Destroy();
}
}
Actors->Empty();
SpawnedGuardians.Remove(Room);
}
}

View File

@ -1,161 +0,0 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#include "MRUtilityKitLightDispatcher.h"
#include "MRUtilityKit.h"
#include "MRUtilityKitTelemetry.h"
#include "Components/PointLightComponent.h"
#include "Engine/PointLight.h"
#include "Engine/World.h"
#include "Kismet/GameplayStatics.h"
#include "Kismet/KismetMaterialLibrary.h"
#include "Materials/MaterialParameterCollection.h"
#include "Materials/MaterialParameterCollectionInstance.h"
#include "UObject/ConstructorHelpers.h"
AMRUKLightDispatcher::AMRUKLightDispatcher()
{
PrimaryActorTick.bCanEverTick = true;
const ConstructorHelpers::FObjectFinder<UMaterialParameterCollection> MpcAsset(TEXT("/OculusXR/Materials/MPC_Highlights"));
if (MpcAsset.Succeeded())
{
Collection = MpcAsset.Object;
}
else
{
UE_LOG(LogMRUK, Log, TEXT("Light dispatcher couldn't find material parameter collection in /OculusXR/Materials/MPC_Highlights"));
}
}
void AMRUKLightDispatcher::Tick(float DeltaSeconds)
{
Super::Tick(DeltaSeconds);
FillParameterCollection();
}
void AMRUKLightDispatcher::FillParameterCollection()
{
if (!Collection || PointLightComponents.IsEmpty())
{
return;
}
UMaterialParameterCollectionInstance* Instance = GetWorld()->GetParameterCollectionInstance(Collection);
for (int i = 0; i < PointLightComponents.Num(); i++)
{
const UPointLightComponent* Light = PointLightComponents[i];
if (!IsValid(Light))
{
continue;
}
const int Step = i * 3;
// It's not possible to expand the amount of parameters in collection at runtime,
// in case we exceed the count of existing parameters break the loop
if (Collection->VectorParameters.Num() < Step + 3)
{
break;
}
// Prepare parameters
FCollectionVectorParameter PositionParam, DataParam, ColorParam;
PositionParam.ParameterName = FName("PointLightPosition" + FString::FromInt(i));
DataParam.ParameterName = FName("PointLightData" + FString::FromInt(i));
ColorParam.ParameterName = FName("PointLightColor" + FString::FromInt(i));
PositionParam.DefaultValue = FLinearColor(Light->GetComponentLocation());
DataParam.DefaultValue = FLinearColor(1.f / Light->AttenuationRadius, Light->ComputeLightBrightness(), Light->LightFalloffExponent, Light->bUseInverseSquaredFalloff);
ColorParam.DefaultValue = Light->GetLightColor();
// Fill collection's vector parameters
Collection->VectorParameters[Step] = PositionParam;
Collection->VectorParameters[Step + 1] = DataParam;
Collection->VectorParameters[Step + 2] = ColorParam;
}
// Send count of lights
Collection->ScalarParameters[0].DefaultValue = PointLightComponents.Num();
UKismetMaterialLibrary::SetScalarParameterValue(GetWorld(), Collection, "TotalLights", PointLightComponents.Num());
// Update instance
Instance->UpdateRenderState(false);
}
void AMRUKLightDispatcher::AddAdditionalPointLightActor(AActor* Actor)
{
AdditionalActorsToLookForPointLightComponents.AddUnique(Actor);
AddPointLightsFromActor(Actor);
}
void AMRUKLightDispatcher::ForceUpdateCollection()
{
FillPointLights();
FillParameterCollection();
PointLightComponents.Empty();
}
void AMRUKLightDispatcher::BeginPlay()
{
Super::BeginPlay();
OculusXRTelemetry::TScopedMarker<MRUKTelemetry::FLoadLightDispatcherMarker> Event(static_cast<int>(GetTypeHash(this)));
FillPointLights();
}
void AMRUKLightDispatcher::FillPointLights()
{
// Make sure we don't have duplicates in the array
PointLightComponents.Empty();
if (ShouldFetchPointLightsAtBeginPlay)
{
// Fetch all point light actors from the level
TArray<AActor*> PointLightActors;
UGameplayStatics::GetAllActorsOfClass(this, APointLight::StaticClass(), PointLightActors);
for (AActor* Actor : PointLightActors)
{
const APointLight* PointLightActor = Cast<APointLight>(Actor);
PointLightComponents.Add(PointLightActor->PointLightComponent);
}
}
else
{
// Only use the point lights that have been specified in ManualPointLights
for (AActor* Actor : ManualPointLights)
{
if (!IsValid(Actor))
{
continue;
}
const APointLight* PointLightActor = Cast<APointLight>(Actor);
PointLightComponents.Add(PointLightActor->PointLightComponent);
}
}
// Check the additional added actors for point lights and add them in case they have
// PointLightComponents attached
for (const AActor* Actor : AdditionalActorsToLookForPointLightComponents)
{
if (!IsValid(Actor))
{
continue;
}
AddPointLightsFromActor(Actor);
}
}
void AMRUKLightDispatcher::AddPointLightsFromActor(const AActor* Actor)
{
TArray<UPointLightComponent*> LightComponents;
Actor->GetComponents(LightComponents, false);
PointLightComponents.Append(LightComponents);
}

View File

@ -1,220 +0,0 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
#include "MRUtilityKitPositionGenerator.h"
#include "MRUtilityKitSubsystem.h"
#include "Engine/OverlapResult.h"
#include "Engine/World.h"
#include "Engine/GameInstance.h"
#include "CollisionShape.h"
bool AMRUtilityKitPositionGenerator::CanSpawnBox(const UWorld* World, const FBox& Box, const FVector& SpawnPosition, const FQuat& SpawnRotation, const FCollisionQueryParams& QueryParams, const ECollisionChannel CollisionChannel)
{
TArray<FOverlapResult> OutOverlaps;
const bool bHasOverlap = World->OverlapMultiByChannel(OutOverlaps, SpawnPosition, SpawnRotation, CollisionChannel, FCollisionShape::MakeBox(Box.GetExtent()), QueryParams);
return !bHasOverlap;
}
void AMRUtilityKitPositionGenerator::BeginPlay()
{
Super::BeginPlay();
if (RunOnStart)
{
const auto Subsystem = GetGameInstance()->GetSubsystem<UMRUKSubsystem>();
if (Subsystem->SceneLoadStatus == EMRUKInitStatus::Complete)
{
SceneLoaded(true);
}
Subsystem->OnSceneLoaded.AddUniqueDynamic(this, &AMRUtilityKitPositionGenerator::SceneLoaded);
}
}
bool AMRUtilityKitPositionGenerator::GenerateRandomPositionsOnSurface(TArray<FTransform>& OutTransforms)
{
const auto Subsystem = GetGameInstance()->GetSubsystem<UMRUKSubsystem>();
bool bSuccess = true;
bool bAnyFailure = false;
switch (RandomSpawnSettings.RoomFilter)
{
case EMRUKRoomFilter::None:
break;
case EMRUKRoomFilter::CurrentRoomOnly:
{
const auto Room = Subsystem->GetCurrentRoom();
bSuccess = GenerateRandomPositionsOnSurfaceInRoom(Room, OutTransforms);
break;
}
case EMRUKRoomFilter::AllRooms:
{
for (auto& Room : Subsystem->Rooms)
{
if (!GenerateRandomPositionsOnSurfaceInRoom(Room, OutTransforms))
{
bAnyFailure = true;
}
}
bSuccess = !bAnyFailure;
break;
}
default:;
}
return bSuccess;
}
bool AMRUtilityKitPositionGenerator::GenerateRandomPositionsOnSurfaceInRoom(AMRUKRoom* Room, TArray<FTransform>& OutTransforms)
{
bool bInitializedAnchor = IsValid(RandomSpawnSettings.ActorInstance);
if (bInitializedAnchor && RandomSpawnSettings.ActorClass != nullptr)
{
UE_LOG(LogMRUK, Error, TEXT("Cannot use an initialized Actor AND a defined ActorClass together. Use one of the options"));
return false;
}
if (!bInitializedAnchor && RandomSpawnSettings.ActorClass == nullptr)
{
UE_LOG(LogMRUK, Error, TEXT("Please define ActorClass."));
return false;
}
const auto Subsystem = GetGameInstance()->GetSubsystem<UMRUKSubsystem>();
auto Bounds = bInitializedAnchor ? RandomSpawnSettings.ActorInstance->CalculateComponentsBoundingBoxInLocalSpace() : Subsystem->GetActorClassBounds(RandomSpawnSettings.ActorClass);
float MinRadius = 0.0f;
float CenterOffset = (Bounds.GetCenter().Z != 0) ? Bounds.GetCenter().Z : 0.0f;
float BaseOffset = (Bounds.Min.Z != 0) ? -Bounds.Min.Z : 0.0f;
FBox AdjustedBounds;
TArray<FBox> SpawnedBounds;
if (Bounds.IsValid)
{
constexpr float ClearanceDistance = 0.01f;
CenterOffset = Bounds.GetCenter().Z;
MinRadius = FMath::Min(FMath::Min(-Bounds.Min.X, -Bounds.Min.Y), FMath::Min(Bounds.Max.X, Bounds.Max.Y));
if (MinRadius < 0.0f)
{
MinRadius = 0.0f;
}
FVector Min = Bounds.Min;
FVector Max = Bounds.Max;
Min.Z += ClearanceDistance;
if (Max.Z < Min.Z)
{
Max.Z = Min.Z;
}
AdjustedBounds = FBox(Min, Max);
if (RandomSpawnSettings.OverrideBounds > 0)
{
FVector Center = FVector(0.0f, 0.0f, ClearanceDistance);
FVector Extents = FVector((RandomSpawnSettings.OverrideBounds), (RandomSpawnSettings.OverrideBounds), ClearanceDistance);
AdjustedBounds = FBox(Center - Extents, Center + Extents);
}
}
int FoundPositions = 0;
for (int i = 0; i < RandomSpawnSettings.SpawnAmount; ++i)
{
for (int j = 0; j < RandomSpawnSettings.MaxIterations; ++j)
{
FVector SpawnPosition = FVector::ZeroVector;
FVector SpawnNormal = FVector::ZeroVector;
bool FoundSpawnPos = false;
if (RandomSpawnSettings.SpawnLocations == EMRUKSpawnLocation::Floating)
{
FVector OutPos;
if (auto bRandomPos = Room->GenerateRandomPositionInRoom(OutPos, MinRadius, true); !bRandomPos)
{
break;
}
SpawnPosition = OutPos;
FoundSpawnPos = true;
}
else
{
if (FVector Normal, Pos; Room->GenerateRandomPositionOnSurface(RandomSpawnSettings.SpawnLocations, MinRadius, RandomSpawnSettings.Labels, Pos, Normal))
{
SpawnPosition = Pos + Normal * BaseOffset;
SpawnNormal = Normal;
auto Center = SpawnPosition + Normal * CenterOffset;
if (auto bInRoom = Room->IsPositionInRoom(Center); !bInRoom)
{
continue;
}
if (Room->IsPositionInSceneVolume(Center))
{
continue;
}
if (FMRUKHit Hit{}; Room->Raycast(SpawnPosition, Normal, RandomSpawnSettings.SurfaceClearanceDistance, RandomSpawnSettings.Labels, Hit))
{
continue;
}
FoundSpawnPos = true;
}
}
FQuat SpawnRotation = FQuat::Identity;
if (!SpawnNormal.IsNearlyZero())
{
SpawnNormal.Normalize();
SpawnRotation = FQuat::FindBetweenNormals(FVector::UpVector, SpawnNormal);
}
if (RandomSpawnSettings.CheckOverlaps && Bounds.IsValid && FoundSpawnPos)
{
FBox WorldBounds(AdjustedBounds.Min + SpawnPosition - AdjustedBounds.GetCenter(), AdjustedBounds.Max + SpawnPosition - AdjustedBounds.GetCenter());
FVector AdjustedSpawnPos = SpawnPosition + SpawnRotation * AdjustedBounds.GetCenter();
// check against world
if (!CanSpawnBox(GetTickableGameObjectWorld(), WorldBounds, AdjustedSpawnPos, SpawnRotation, FCollisionQueryParams::DefaultQueryParam, RandomSpawnSettings.CollisionChannel))
{
continue;
}
}
if (bInitializedAnchor && FoundSpawnPos)
{
RandomSpawnSettings.ActorInstance->SetActorLocationAndRotation(SpawnPosition, SpawnRotation);
// ignore SpawnAmount once we have a successful move of existing object in the scene
return true;
}
if (FoundSpawnPos)
{
OutTransforms.Add(FTransform(SpawnRotation, SpawnPosition, FVector::OneVector));
FoundPositions++;
break;
}
}
}
return FoundPositions == RandomSpawnSettings.SpawnAmount;
}
void AMRUtilityKitPositionGenerator::SceneLoaded(bool Success)
{
if (Success)
{
TArray<FTransform> OutTransforms;
const bool bSuccess = GenerateRandomPositionsOnSurface(OutTransforms);
if (!bSuccess)
{
UE_LOG(LogMRUK, Warning, TEXT("Generate Random Positions on Surface not successful"));
return;
}
if (RandomSpawnSettings.ActorClass != nullptr)
{
for (auto Transform : OutTransforms)
{
FActorSpawnParameters Params{};
Params.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
GetWorld()->SpawnActor(RandomSpawnSettings.ActorClass, &Transform, Params);
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More