Don't want the hasle of seting ut MetaXR on oter computers
1
.gitignore
vendored
@ -14,3 +14,4 @@ AndroidBuild
|
||||
*.suo
|
||||
*.xcodeproj
|
||||
*.xcworkspace
|
||||
Plugins
|
@ -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")))
|
@ -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/...
|
||||
|
||||
|
BIN
Plugins/MetaXR/Content/Materials/LeftMetaQuestTouchPlusMaterial.uasset
(Stored with Git LFS)
BIN
Plugins/MetaXR/Content/Materials/LeftMetaQuestTouchProMaterial.uasset
(Stored with Git LFS)
BIN
Plugins/MetaXR/Content/Materials/MF_DistanceMap.uasset
(Stored with Git LFS)
BIN
Plugins/MetaXR/Content/Materials/MI_BlobShadow.uasset
(Stored with Git LFS)
BIN
Plugins/MetaXR/Content/Materials/MI_Highlights_Translucent.uasset
(Stored with Git LFS)
BIN
Plugins/MetaXR/Content/Materials/MPC_Highlights.uasset
(Stored with Git LFS)
BIN
Plugins/MetaXR/Content/Materials/M_BlobShadow.uasset
(Stored with Git LFS)
BIN
Plugins/MetaXR/Content/Materials/M_CreateMask.uasset
(Stored with Git LFS)
BIN
Plugins/MetaXR/Content/Materials/M_DistanceMapAll.uasset
(Stored with Git LFS)
BIN
Plugins/MetaXR/Content/Materials/M_DistanceMapFree.uasset
(Stored with Git LFS)
BIN
Plugins/MetaXR/Content/Materials/M_DistanceMapOccupied.uasset
(Stored with Git LFS)
BIN
Plugins/MetaXR/Content/Materials/M_FloorMask.uasset
(Stored with Git LFS)
BIN
Plugins/MetaXR/Content/Materials/M_Highlights.uasset
(Stored with Git LFS)
BIN
Plugins/MetaXR/Content/Materials/M_JFAPass.uasset
(Stored with Git LFS)
BIN
Plugins/MetaXR/Content/Materials/M_SceneObjectMask.uasset
(Stored with Git LFS)
BIN
Plugins/MetaXR/Content/Materials/MetaQuestTouchProMaterial.uasset
(Stored with Git LFS)
BIN
Plugins/MetaXR/Content/Materials/OculusMR_ChromaKey.uasset
(Stored with Git LFS)
BIN
Plugins/MetaXR/Content/Materials/OculusMR_OpaqueColoredMaterial.uasset
(Stored with Git LFS)
BIN
Plugins/MetaXR/Content/Materials/OculusMR_WhiteMaterial.uasset
(Stored with Git LFS)
BIN
Plugins/MetaXR/Content/Materials/PokeAHoleMaterial.uasset
(Stored with Git LFS)
BIN
Plugins/MetaXR/Content/Materials/RightMetaQuestTouchPlusMaterial.uasset
(Stored with Git LFS)
BIN
Plugins/MetaXR/Content/Materials/RightMetaQuestTouchProMaterial.uasset
(Stored with Git LFS)
BIN
Plugins/MetaXR/Content/Materials/TouchForQuest2Material.uasset
(Stored with Git LFS)
BIN
Plugins/MetaXR/Content/Materials/TouchForQuestRiftSControllerMaterial.uasset
(Stored with Git LFS)
BIN
Plugins/MetaXR/Content/Materials/model_pieces_controllerMATphongRT.uasset
(Stored with Git LFS)
BIN
Plugins/MetaXR/Content/Materials/touchController_mat.uasset
(Stored with Git LFS)
BIN
Plugins/MetaXR/Content/Meshes/LeftMetaQuestTouchPlus.fbx
(Stored with Git LFS)
BIN
Plugins/MetaXR/Content/Meshes/LeftMetaQuestTouchPlus.uasset
(Stored with Git LFS)
BIN
Plugins/MetaXR/Content/Meshes/LeftMetaQuestTouchPro.uasset
(Stored with Git LFS)
BIN
Plugins/MetaXR/Content/Meshes/LeftMetaQuestTouchProNub.uasset
(Stored with Git LFS)
BIN
Plugins/MetaXR/Content/Meshes/LeftTouchForQuest2.uasset
(Stored with Git LFS)
BIN
Plugins/MetaXR/Content/Meshes/LeftTouchForQuestRiftSController.fbx
(Stored with Git LFS)
BIN
Plugins/MetaXR/Content/Meshes/LeftTouchForQuestRiftSController.uasset
(Stored with Git LFS)
BIN
Plugins/MetaXR/Content/Meshes/RightMetaQuestTouchPlus.fbx
(Stored with Git LFS)
BIN
Plugins/MetaXR/Content/Meshes/RightMetaQuestTouchPlus.uasset
(Stored with Git LFS)
BIN
Plugins/MetaXR/Content/Meshes/RightMetaQuestTouchPro.uasset
(Stored with Git LFS)
BIN
Plugins/MetaXR/Content/Meshes/RightMetaQuestTouchProNub.uasset
(Stored with Git LFS)
BIN
Plugins/MetaXR/Content/Meshes/RightTouchForQuest2.uasset
(Stored with Git LFS)
BIN
Plugins/MetaXR/Content/Meshes/RightTouchForQuestRiftSController.fbx
(Stored with Git LFS)
BIN
Plugins/MetaXR/Content/Meshes/RightTouchForQuestRiftSController.uasset
(Stored with Git LFS)
BIN
Plugins/MetaXR/Content/Meshes/SM_Bevelcube.uasset
(Stored with Git LFS)
BIN
Plugins/MetaXR/Content/Meshes/SM_Plane.uasset
(Stored with Git LFS)
BIN
Plugins/MetaXR/Content/Meshes/SM_Volume.uasset
(Stored with Git LFS)
BIN
Plugins/MetaXR/Content/MetaXRControllers.uasset
(Stored with Git LFS)
BIN
Plugins/MetaXR/Content/OculusMR_GreenKey.uasset
(Stored with Git LFS)
@ -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>
|
@ -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.
|
BIN
Plugins/MetaXR/Content/Textures/CRT_DistanceMap.uasset
(Stored with Git LFS)
BIN
Plugins/MetaXR/Content/Textures/CRT_JumpFlood1.uasset
(Stored with Git LFS)
BIN
Plugins/MetaXR/Content/Textures/CRT_JumpFlood2.uasset
(Stored with Git LFS)
BIN
Plugins/MetaXR/Content/Textures/CRT_Mask.uasset
(Stored with Git LFS)
BIN
Plugins/MetaXR/Content/Textures/LeftMetaQuestTouchPlus_Color.uasset
(Stored with Git LFS)
BIN
Plugins/MetaXR/Content/Textures/LeftMetaQuestTouchPro_Color.uasset
(Stored with Git LFS)
BIN
Plugins/MetaXR/Content/Textures/LeftMetaQuestTouchPro_Normal.uasset
(Stored with Git LFS)
BIN
Plugins/MetaXR/Content/Textures/RightMetaQuestTouchPlus_Color.uasset
(Stored with Git LFS)
BIN
Plugins/MetaXR/Content/Textures/RightMetaQuestTouchPro_Color.uasset
(Stored with Git LFS)
BIN
Plugins/MetaXR/Content/Textures/RightMetaQuestTouchPro_Normal.uasset
(Stored with Git LFS)
BIN
Plugins/MetaXR/Content/Textures/T_BlobShadow.uasset
(Stored with Git LFS)
BIN
Plugins/MetaXR/Content/Textures/TouchForQuest2Material_Roughness.uasset
(Stored with Git LFS)
BIN
Plugins/MetaXR/Content/Textures/TouchForQuest2_Color.uasset
(Stored with Git LFS)
BIN
Plugins/MetaXR/Content/Textures/TouchForQuestRiftSController_albedo.uasset
(Stored with Git LFS)
@ -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
|
||||
}
|
||||
]
|
||||
}
|
@ -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 |
BIN
Plugins/MetaXR/Resources/ButtonIcon_80x.png
(Stored with Git LFS)
@ -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 |
@ -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)
@ -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 |
BIN
Plugins/MetaXR/Resources/MetaQuestBackground.png
(Stored with Git LFS)
@ -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 |
@ -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 |
@ -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 |
@ -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 |
@ -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 |
@ -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 |
@ -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;
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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 );
|
||||
}
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
};
|
@ -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)
|
@ -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
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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;
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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));
|
||||
}
|
@ -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;
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|