From 16b53a728d0e47d9a4da29b31a3111ad92fd5b07 Mon Sep 17 00:00:00 2001 From: Mike Nisbet Date: Tue, 5 Dec 2023 21:47:13 +0000 Subject: [PATCH] Base Multiplayer implementation (#578) * Base Photon files (#516) * add project config * gitignore rest of photon * Use secrets file for photon key * Ignore photon on dotnet-format * Update meta mr to test * Connect photon via secrets key * Test transient player rig * proof of concept sync using OnInput * Streamlined player head sync * Scene Understanding as Guides * Turn off walls temporarily * rename prefab * WIP anchors * Getting Oculus ID * Set and retrieve spatial anchor, reposition * Add Meta platform SDK * Sync position relative to open brush scene * WIP syncing users * Set test room * Sync anchors across network * Try inverse pose * Hacky transform math * maths learnt! * Sync transient pointer for remote players * tidyup * HMDMesh prefab * First pass brush sync! * Use Photon struct to send strokes * Network undo/redo stack Will fall out of sync if clients didn't each join before someone's stack started * WIP Erase * Command queue, working erase * Code tidyup * Update player at correct time * change target tag for meta sdk * Delete conflicting meta file * Remove oculus specific changes * Clone the Photon repo if running from a branch, but not a fork * clone photon in build step * Try patch around quest 1 specifics * missed closing xml * fix filepath for manifest overwrite * supportedDevices is pickier than it should be --------- Co-authored-by: Mike Miller Co-authored-by: Andy Baker --- .github/workflows/build.yml | 18 + .gitignore | 5 + .pre-commit-config.yaml | 2 +- Assets/Editor/BuildTiltBrushPostProcess.cs | 113 +++ .../Editor/BuildTiltBrushPostProcess.cs.meta | 11 + Assets/Oculus/OculusProjectConfig.asset | 15 +- Assets/OculusMR.meta | 8 + Assets/OculusMR/AnchorToGuide.cs | 74 ++ Assets/OculusMR/AnchorToGuide.cs.meta | 11 + Assets/OculusMR/OculusMR.prefab | 82 ++ Assets/OculusMR/OculusMR.prefab.meta | 7 + Assets/OculusMR/OculusMRController.cs | 79 ++ Assets/OculusMR/OculusMRController.cs.meta | 11 + Assets/OculusMR/SceneAnchorBase.prefab | 59 ++ Assets/OculusMR/SceneAnchorBase.prefab.meta | 7 + Assets/OculusMR/SpatialAnchorManager.cs | 189 +++++ Assets/OculusMR/SpatialAnchorManager.cs.meta | 11 + .../Resources/NetworkProjectConfig.fusion | 164 ++++ .../NetworkProjectConfig.fusion.meta | 11 + Assets/Plugins/Android/AndroidManifest.xml | 1 + Assets/Prefabs/HMDMesh.prefab | 85 +++ Assets/Prefabs/HMDMesh.prefab.meta | 7 + Assets/Resources/Multiplayer.meta | 8 + Assets/Resources/Multiplayer/Photon.meta | 8 + .../Multiplayer/Photon/PhotonPlayerRig.prefab | 704 ++++++++++++++++++ .../Photon/PhotonPlayerRig.prefab.meta | 9 + Assets/Resources/OculusRuntimeSettings.asset | 3 + Assets/Scenes/Main.unity | 219 ++++-- Assets/Scripts/Commands/BaseCommand.cs | 29 + Assets/Scripts/Commands/BrushStrokeCommand.cs | 9 +- .../Scripts/Commands/DeleteStrokeCommand.cs | 2 +- Assets/Scripts/Config.cs | 1 + Assets/Scripts/Multiplayer.meta | 8 + .../Multiplayer/MultiplayerDataStructs.cs | 49 ++ .../MultiplayerDataStructs.cs.meta | 11 + .../Multiplayer/MultiplayerInterfaces.cs | 45 ++ .../Multiplayer/MultiplayerInterfaces.cs.meta | 11 + .../Scripts/Multiplayer/MultiplayerManager.cs | 225 ++++++ .../Multiplayer/MultiplayerManager.cs.meta | 11 + Assets/Scripts/Multiplayer/Photon.meta | 8 + .../Multiplayer/Photon/PhotonManager.cs | 281 +++++++ .../Multiplayer/Photon/PhotonManager.cs.meta | 11 + .../Multiplayer/Photon/PhotonPlayerRig.cs | 122 +++ .../Photon/PhotonPlayerRig.cs.meta | 11 + .../Scripts/Multiplayer/Photon/PhotonRPC.cs | 298 ++++++++ .../Multiplayer/Photon/PhotonRPC.cs.meta | 11 + .../Multiplayer/Photon/PhotonStructs.cs | 171 +++++ .../Multiplayer/Photon/PhotonStructs.cs.meta | 11 + Assets/Scripts/PassthroughManager.cs | 23 +- Assets/Scripts/PointerManager.cs | 18 + Assets/Scripts/SecretsConfig.cs | 3 +- Assets/Scripts/SketchMemoryScript.cs | 37 +- Assets/Scripts/Stroke.cs | 2 +- Assets/Scripts/StrokeData.cs | 2 +- Assets/Scripts/VrSdk.cs | 12 + .../XR/Settings/OpenXR Editor Settings.asset | 6 +- Packages/manifest.json | 4 +- Packages/packages-lock.json | 21 +- 58 files changed, 3266 insertions(+), 108 deletions(-) create mode 100644 Assets/Editor/BuildTiltBrushPostProcess.cs create mode 100644 Assets/Editor/BuildTiltBrushPostProcess.cs.meta create mode 100644 Assets/OculusMR.meta create mode 100644 Assets/OculusMR/AnchorToGuide.cs create mode 100644 Assets/OculusMR/AnchorToGuide.cs.meta create mode 100644 Assets/OculusMR/OculusMR.prefab create mode 100644 Assets/OculusMR/OculusMR.prefab.meta create mode 100644 Assets/OculusMR/OculusMRController.cs create mode 100644 Assets/OculusMR/OculusMRController.cs.meta create mode 100644 Assets/OculusMR/SceneAnchorBase.prefab create mode 100644 Assets/OculusMR/SceneAnchorBase.prefab.meta create mode 100644 Assets/OculusMR/SpatialAnchorManager.cs create mode 100644 Assets/OculusMR/SpatialAnchorManager.cs.meta create mode 100644 Assets/Photon/Fusion/Resources/NetworkProjectConfig.fusion create mode 100644 Assets/Photon/Fusion/Resources/NetworkProjectConfig.fusion.meta create mode 100644 Assets/Prefabs/HMDMesh.prefab create mode 100644 Assets/Prefabs/HMDMesh.prefab.meta create mode 100644 Assets/Resources/Multiplayer.meta create mode 100644 Assets/Resources/Multiplayer/Photon.meta create mode 100644 Assets/Resources/Multiplayer/Photon/PhotonPlayerRig.prefab create mode 100644 Assets/Resources/Multiplayer/Photon/PhotonPlayerRig.prefab.meta create mode 100644 Assets/Scripts/Multiplayer.meta create mode 100644 Assets/Scripts/Multiplayer/MultiplayerDataStructs.cs create mode 100644 Assets/Scripts/Multiplayer/MultiplayerDataStructs.cs.meta create mode 100644 Assets/Scripts/Multiplayer/MultiplayerInterfaces.cs create mode 100644 Assets/Scripts/Multiplayer/MultiplayerInterfaces.cs.meta create mode 100644 Assets/Scripts/Multiplayer/MultiplayerManager.cs create mode 100644 Assets/Scripts/Multiplayer/MultiplayerManager.cs.meta create mode 100644 Assets/Scripts/Multiplayer/Photon.meta create mode 100644 Assets/Scripts/Multiplayer/Photon/PhotonManager.cs create mode 100644 Assets/Scripts/Multiplayer/Photon/PhotonManager.cs.meta create mode 100644 Assets/Scripts/Multiplayer/Photon/PhotonPlayerRig.cs create mode 100644 Assets/Scripts/Multiplayer/Photon/PhotonPlayerRig.cs.meta create mode 100644 Assets/Scripts/Multiplayer/Photon/PhotonRPC.cs create mode 100644 Assets/Scripts/Multiplayer/Photon/PhotonRPC.cs.meta create mode 100644 Assets/Scripts/Multiplayer/Photon/PhotonStructs.cs create mode 100644 Assets/Scripts/Multiplayer/Photon/PhotonStructs.cs.meta diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d0d1b1d278..0976141387 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,6 +17,7 @@ env: UNITY_EMAIL: ${{ fromJSON(format('["unitytest@mikeage.net", "{0}"]', secrets.UNITY_EMAIL))[secrets.UNITY_SERIAL != null] }} UNITY_PASSWORD: ${{ fromJSON(format('["DoNotStealThis1", "{0}"]', secrets.UNITY_PASSWORD))[secrets.UNITY_SERIAL != null] }} UNITY_LICENSE: ${{ fromJSON('["\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n 9DZF11miRAzx7TIuCxih78B6CXU=weRQubqMNN61lSZtm/e7S+UDzTPNQjM5aQl/c4aKLH/b2khefpgLfdWneoxdnNopA6+rW6kBqxWt\nhMLdHY+oAOfDsfmMQRTnmQG0Y3G3xh6gjGP1RAIHoLDfFHf+0LQ3FakA2WehcFWPSYeVDrdxm3HW\nqMmdKWooD9i+J4s4rQFTDx9+/G6yjc5KGTyGxIz3c5kxTEkV2qsFPXsauomY9Z8YPKy+cZK7g+Ol\npO+LhtzetgTIlIN/qG8eByjlp6nOuVGdDOIrhNJW+vllNyx0qNWPREadVrhFViI4UXegMFRl5gJc\nrgcrlr/fD+NorDVLfcu7D863QXkkuriILUIq2Q==", null]')[secrets.UNITY_SERIAL != null] }} + PHOTON_PAT: ${{ secrets.PHOTON_PAT }} jobs: configuration: if: | @@ -273,6 +274,8 @@ jobs: mkdir Packages/com.unity.xr.picoxr wget -q https://sdk.picovr.com/developer-platform/sdk/PICO%20Unity%20Integration%20SDK%20v211.zip -O package.zip unzip package.zip -d Packages/com.unity.xr.picoxr + # Pico has a GUID conflict because they copied code from Oculus. Delete the offending .meta file from our mutable Pico SDK. + rm Packages/com.unity.xr.picoxr/Platform/Scripts/Models/Common.cs.meta - name: Install TextMesh Pro package run: | @@ -296,6 +299,21 @@ jobs: cp -R 'tmp.package/Assets/TextMesh Pro' Assets/ rm -rf tmp.plugin tmp.package + - name: Checkout private photon repository if available + if: ${{ env.PHOTON_PAT }} + uses: actions/checkout@v4 + with: + token: ${{ env.PHOTON_PAT }} + repository: icosa-mirror/photon-fusion + path: photon-fusion-mirror/ + + - name: Copy photon files + if: ${{ env.PHOTON_PAT }} + run: | + rsync -a --ignore-existing photon-fusion-mirror/ ./ + echo "For debugging: these are the files in Assets/Photon:" + find Assets/Photon + - name: Restore Library/ id: cache_library uses: actions/cache/restore@v3 diff --git a/.gitignore b/.gitignore index b85fc81855..48368ea94c 100644 --- a/.gitignore +++ b/.gitignore @@ -102,5 +102,10 @@ # Pico Integration Packages/com.unity.xr.picoxr +# Photon Integration +/Assets/Photon/** +/Assets/Photon.meta +!/Assets/Photon/Fusion/Resources/NetworkProjectConfig.fusion* + # Cache files *.cache diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3a10c68900..aa7780d22e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -31,7 +31,7 @@ repos: rev: "a2c1431dabf2229052cf598f86f31bc3eac5bc7b" # Main as of 2022-12-05 hooks: - id: dotnet-format - exclude: ^(Assets/ThirdParty)|(Packages/) + exclude: ^(Assets/ThirdParty)|(Packages/)|(Assets/Photon/) entry: dotnet format whitespace args: - --folder diff --git a/Assets/Editor/BuildTiltBrushPostProcess.cs b/Assets/Editor/BuildTiltBrushPostProcess.cs new file mode 100644 index 0000000000..328a112bfb --- /dev/null +++ b/Assets/Editor/BuildTiltBrushPostProcess.cs @@ -0,0 +1,113 @@ +// Copyright 2023 The Open Brush Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.IO; +using System.Xml; +using UnityEditor; +#if UNITY_ANDROID +using UnityEditor.Android; +#endif + +[InitializeOnLoad] +public class BuildTiltBrushPostProcess +#if UNITY_ANDROID + : IPostGenerateGradleAndroidProject +#endif +{ + // OVRGradleGeneration is 99999, so we'll just go to the extreme. + public int callbackOrder => 1000000; + + public void OnPostGenerateGradleAndroidProject(string path) + { + string manifestFolder = Path.Combine(path, "src/main"); + string file = manifestFolder + "/AndroidManifest.xml"; + + try + { + XmlDocument doc = new XmlDocument(); + doc.Load(file); + + XmlElement element = (XmlElement)doc.SelectSingleNode("/manifest"); + var androidNamespaceURI = element.GetAttribute("xmlns:android"); + + + + UnityEngine.Debug.Log("Add all quest supported devices."); + AddOrRemoveTag(doc, + androidNamespaceURI, + "/manifest/application", + "meta-data", + "com.oculus.supportedDevices", + true, + true, + "value", "quest|quest2" + ); + + doc.Save(file); + } + catch (System.Exception e) + { + UnityEngine.Debug.LogException(e); + } + } + + private static void AddOrRemoveTag(XmlDocument doc, string @namespace, string path, string elementName, string name, + bool required, bool modifyIfFound, params string[] attrs) // name, value pairs + { + var nodes = doc.SelectNodes(path + "/" + elementName); + XmlElement element = null; + foreach (XmlElement e in nodes) + { + if (name == null || name == e.GetAttribute("name", @namespace)) + { + element = e; + break; + } + } + + if (required) + { + if (element == null) + { + var parent = doc.SelectSingleNode(path); + element = doc.CreateElement(elementName); + element.SetAttribute("name", @namespace, name); + parent.AppendChild(element); + } + + for (int i = 0; i < attrs.Length; i += 2) + { + if (modifyIfFound || string.IsNullOrEmpty(element.GetAttribute(attrs[i], @namespace))) + { + if (attrs[i + 1] != null) + { + element.SetAttribute(attrs[i], @namespace, attrs[i + 1]); + } + else + { + element.RemoveAttribute(attrs[i], @namespace); + } + } + } + } + else + { + if (element != null && modifyIfFound) + { + element.ParentNode.RemoveChild(element); + } + } + } + +} diff --git a/Assets/Editor/BuildTiltBrushPostProcess.cs.meta b/Assets/Editor/BuildTiltBrushPostProcess.cs.meta new file mode 100644 index 0000000000..e131169336 --- /dev/null +++ b/Assets/Editor/BuildTiltBrushPostProcess.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 151adde7344d1624cb94167188535adc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Oculus/OculusProjectConfig.asset b/Assets/Oculus/OculusProjectConfig.asset index cca3bd5d40..4280c0f504 100644 --- a/Assets/Oculus/OculusProjectConfig.asset +++ b/Assets/Oculus/OculusProjectConfig.asset @@ -17,13 +17,16 @@ MonoBehaviour: handTrackingSupport: 0 handTrackingFrequency: 0 handTrackingVersion: 0 - anchorSupport: 0 - sharedAnchorSupport: 0 + multimodalHandsControllersSupport: 0 + anchorSupport: 1 + sharedAnchorSupport: 1 renderModelSupport: 0 trackedKeyboardSupport: 0 bodyTrackingSupport: 0 faceTrackingSupport: 0 eyeTrackingSupport: 0 + virtualKeyboardSupport: 0 + sceneSupport: 1 disableBackups: 0 enableNSCConfig: 0 securityXmlPath: @@ -31,7 +34,9 @@ MonoBehaviour: focusAware: 1 requiresSystemKeyboard: 0 experimentalFeaturesEnabled: 0 - insightPassthroughEnabled: 1 + insightPassthroughEnabled: 0 + _insightPassthroughSupport: 1 systemSplashScreen: {fileID: 0} - ovrPluginMd5Win64: cc745177e7550713731157d64af22834 - ovrPluginMd5Android: 997ec6c34867e09e006518cbedd3a649 + systemSplashScreenType: 0 + ovrPluginMd5Win64: 67e58deb5b905ae742512bbee3e06a70 + ovrPluginMd5Android: 017d859ada160e45b27588f910d5d8d8 diff --git a/Assets/OculusMR.meta b/Assets/OculusMR.meta new file mode 100644 index 0000000000..d53462ab23 --- /dev/null +++ b/Assets/OculusMR.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 180a5bc23d784424188dd3d6c5534cd4 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OculusMR/AnchorToGuide.cs b/Assets/OculusMR/AnchorToGuide.cs new file mode 100644 index 0000000000..3d0225f9c1 --- /dev/null +++ b/Assets/OculusMR/AnchorToGuide.cs @@ -0,0 +1,74 @@ +// Copyright 2023 The Open Brush Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +namespace TiltBrush +{ + public class AnchorToGuide : MonoBehaviour + { + // Start is called before the first frame update + + private OVRSceneVolume m_SceneComponentVolume; + private OVRScenePlane m_SceneComponentPlane; + private OVRSemanticClassification m_Classification; + + void Start() + { + m_SceneComponentVolume = GetComponent(); + + if (m_SceneComponentVolume) + { + var dimentions = m_SceneComponentVolume.Dimensions; + + var pos = App.Scene.transform.InverseTransformPoint(this.transform.position); + pos.y /= 2.0f; + var tr = TrTransform.TR(pos, this.transform.rotation); + + CreateWidgetCommand createCommand = new CreateWidgetCommand( + WidgetManager.m_Instance.GetStencilPrefab(StencilType.Cube), tr, null, true); + SketchMemoryScript.m_Instance.PerformAndRecordCommand(createCommand); + + SketchMemoryScript.m_Instance.PerformAndRecordCommand( + new MoveWidgetCommand(createCommand.Widget, tr, dimentions * 10)); + + return; + } + + // Hacky but quick proof of concept + // TODO: Tidyup, seems a bit broken right now. + // You end up stuck grabbin the wall planes. + // m_SceneComponentPlane = GetComponent(); + + // if(m_SceneComponentPlane) + // { + // var dimentions = m_SceneComponentPlane.Dimensions; + + // var tr = TrTransform.TR(this.transform.position, this.transform.rotation); + + // CreateWidgetCommand createCommand = new CreateWidgetCommand( + // WidgetManager.m_Instance.GetStencilPrefab(StencilType.Plane), tr, null, true); + // SketchMemoryScript.m_Instance.PerformAndRecordCommand(createCommand); + + // SketchMemoryScript.m_Instance.PerformAndRecordCommand( + // new MoveWidgetCommand(createCommand.Widget, tr, dimentions * 10)); + + // return; + // } + } + } + +} diff --git a/Assets/OculusMR/AnchorToGuide.cs.meta b/Assets/OculusMR/AnchorToGuide.cs.meta new file mode 100644 index 0000000000..adff9249de --- /dev/null +++ b/Assets/OculusMR/AnchorToGuide.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b2cf8d992af87474ab79d8d4a447a295 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OculusMR/OculusMR.prefab b/Assets/OculusMR/OculusMR.prefab new file mode 100644 index 0000000000..011afb48c2 --- /dev/null +++ b/Assets/OculusMR/OculusMR.prefab @@ -0,0 +1,82 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &7081236046508280627 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 7809753190829035278} + - component: {fileID: 5619048699234477578} + - component: {fileID: -3473970634249864913} + - component: {fileID: 5425681579648101261} + m_Layer: 0 + m_Name: OculusMR + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &7809753190829035278 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7081236046508280627} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &5619048699234477578 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7081236046508280627} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 57acf6fd80bfa99419bc6274bd16c6c8, type: 3} + m_Name: + m_EditorClassIdentifier: + ovrSceneManager: {fileID: 5425681579648101261} + m_SpatialAnchorManager: {fileID: -3473970634249864913} +--- !u!114 &-3473970634249864913 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7081236046508280627} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 91e9b9159d619a2428162e46f16afb16, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!114 &5425681579648101261 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7081236046508280627} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f2253d75e3ddc644b8153409c62dd74e, type: 3} + m_Name: + m_EditorClassIdentifier: + PlanePrefab: {fileID: 5531902977439992835, guid: 96517f8af456f7943b86d2b7ed027964, + type: 3} + VolumePrefab: {fileID: 5531902977439992835, guid: 96517f8af456f7943b86d2b7ed027964, + type: 3} + PrefabOverrides: [] + VerboseLogging: 0 + MaxSceneAnchorUpdatesPerFrame: 3 + _initialAnchorParent: {fileID: 0} diff --git a/Assets/OculusMR/OculusMR.prefab.meta b/Assets/OculusMR/OculusMR.prefab.meta new file mode 100644 index 0000000000..b5dc0c2dc4 --- /dev/null +++ b/Assets/OculusMR/OculusMR.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: d25a8154340103c4883d7fc7ddab0827 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OculusMR/OculusMRController.cs b/Assets/OculusMR/OculusMRController.cs new file mode 100644 index 0000000000..e7fc1f847d --- /dev/null +++ b/Assets/OculusMR/OculusMRController.cs @@ -0,0 +1,79 @@ +// Copyright 2023 The Open Brush Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.Collections; +using System.Collections.Generic; +using OpenBrush.Multiplayer; +using UnityEngine; + +namespace TiltBrush +{ + public class OculusMRController : MonoBehaviour + { + public static OculusMRController m_Instance; + + public OVRSceneManager ovrSceneManager; + public SpatialAnchorManager m_SpatialAnchorManager; + + private bool loadedScene; + + private bool host; + + void Awake() + { + m_Instance = this; + + ovrSceneManager = GetComponent(); + m_SpatialAnchorManager = GetComponent(); + } + + void RequestScenePermission() + { + const string permissionString = "com.oculus.permission.USE_SCENE"; + bool hasUserAuthorizedPermission = UnityEngine.Android.Permission.HasUserAuthorizedPermission(permissionString); + if (!hasUserAuthorizedPermission) + { + UnityEngine.Android.Permission.RequestUserPermission(permissionString); + } + } + + public async void StartMRExperience(bool isHosting) + { + host = isHosting; + + if (host) + { + await m_SpatialAnchorManager.CreateSpatialAnchor(); + m_SpatialAnchorManager.SceneLocalizeToAnchor(); + MultiplayerManager.m_Instance.Connect(); + } + else + { + MultiplayerManager.m_Instance.Connect(); + } + } + + public async void RemoteSyncToAnchor(string uuid) + { + await m_SpatialAnchorManager.SyncToRemoteAnchor(uuid, OVRSpace.StorageLocation.Cloud); + + if (!loadedScene) + { + ovrSceneManager.LoadSceneModel(); + loadedScene = true; + } + } + } +} + diff --git a/Assets/OculusMR/OculusMRController.cs.meta b/Assets/OculusMR/OculusMRController.cs.meta new file mode 100644 index 0000000000..d7f6fd4b9c --- /dev/null +++ b/Assets/OculusMR/OculusMRController.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 57acf6fd80bfa99419bc6274bd16c6c8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OculusMR/SceneAnchorBase.prefab b/Assets/OculusMR/SceneAnchorBase.prefab new file mode 100644 index 0000000000..bc9360ed05 --- /dev/null +++ b/Assets/OculusMR/SceneAnchorBase.prefab @@ -0,0 +1,59 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &1571586382271360071 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 3444398428018961088} + - component: {fileID: 5531902977439992835} + - component: {fileID: 9186225842113814311} + m_Layer: 0 + m_Name: SceneAnchorBase + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &3444398428018961088 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1571586382271360071} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &5531902977439992835 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1571586382271360071} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 8ab3ec25e32ce0c4380714d75e39cb05, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!114 &9186225842113814311 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1571586382271360071} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: b2cf8d992af87474ab79d8d4a447a295, type: 3} + m_Name: + m_EditorClassIdentifier: diff --git a/Assets/OculusMR/SceneAnchorBase.prefab.meta b/Assets/OculusMR/SceneAnchorBase.prefab.meta new file mode 100644 index 0000000000..7915d07a04 --- /dev/null +++ b/Assets/OculusMR/SceneAnchorBase.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 96517f8af456f7943b86d2b7ed027964 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OculusMR/SpatialAnchorManager.cs b/Assets/OculusMR/SpatialAnchorManager.cs new file mode 100644 index 0000000000..84a02677a4 --- /dev/null +++ b/Assets/OculusMR/SpatialAnchorManager.cs @@ -0,0 +1,189 @@ +// Copyright 2023 The Open Brush Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Threading.Tasks; +using UnityEngine; + +namespace TiltBrush +{ + public class SpatialAnchorManager : MonoBehaviour + { + const string kOriginSpatialAnchorPref = "ORIGIN_SPATIAL_ANCHOR"; + + OVRSpatialAnchor m_Anchor; + + public string AnchorUuid + { + get + { + if (m_Anchor != null) + { + return m_Anchor.Uuid.ToString(); + } + return string.Empty; + } + } + + public async Task CreateSpatialAnchor() + { + var anchorGO = new GameObject("Origin Anchor"); + m_Anchor = anchorGO.AddComponent(); + + return await SaveAnchor(); + } + + async Task SaveAnchor() + { + while (!m_Anchor.Created && !m_Anchor.Localized) + { + await Task.Yield(); + } + + //Local save, then cloud save. + var success = await m_Anchor.SaveAsync(); + + if (!success) + { + return false; + } + + Debug.Log("Anchor saved to device!"); + PlayerPrefs.SetString(kOriginSpatialAnchorPref, m_Anchor.Uuid.ToString()); + + success = await m_Anchor.SaveAsync(saveOptions: new OVRSpatialAnchor.SaveOptions { Storage = OVRSpace.StorageLocation.Cloud }); + if (!success) + { + return false; + } + Debug.Log("Anchor saved to cloud!"); + + return true; + } + + public async Task LoadSpatialAnchor() + { + if (PlayerPrefs.HasKey(kOriginSpatialAnchorPref)) + { + var guidString = PlayerPrefs.GetString(kOriginSpatialAnchorPref); + var guid = new Guid(guidString); + + var data = await OVRSpatialAnchor.LoadUnboundAnchorsAsync(new OVRSpatialAnchor.LoadOptions() + { + StorageLocation = OVRSpace.StorageLocation.Local, + Timeout = 0, + Uuids = new List() { guid } + } + ); + + return await BindAnchors(data); + } + else + { + return await CreateSpatialAnchor(); + } + } + + public bool SceneLocalizeToAnchor() + { + if (m_Anchor == null) + { + return false; + } + + var m_anchorTr = TrTransform.FromTransform(m_Anchor.transform); + + var newPose = SketchControlsScript.MakeValidScenePose(m_anchorTr, + SceneSettings.m_Instance.HardBoundsRadiusMeters_SS); + App.Scene.Pose = newPose; + + Debug.Log("Anchor localized!"); + return true; + } + + public async Task SyncToRemoteAnchor(string uuid, OVRSpace.StorageLocation defaultStorageLocation = OVRSpace.StorageLocation.Local) + { + var guid = new Guid(uuid); + + var data = await OVRSpatialAnchor.LoadUnboundAnchorsAsync(new OVRSpatialAnchor.LoadOptions() + { + StorageLocation = defaultStorageLocation, + Timeout = 0, + Uuids = new List() { guid } + } + ); + + Debug.Log("Remote anchor recieved!"); + bool bindSuccess = await BindAnchors(data); + + if (bindSuccess) + { + Debug.Log("Remote anchor bound!"); + return SceneLocalizeToAnchor(); + } + return false; + } + + async Task BindAnchors(OVRSpatialAnchor.UnboundAnchor[] anchors) + { + var unboundAnchor = anchors[0]; + + var anchorGO = new GameObject("Origin Anchor"); + m_Anchor = anchorGO.AddComponent(); + unboundAnchor.BindTo(m_Anchor); + + while (m_Anchor.PendingCreation) + { + await Task.Yield(); + } + + Debug.Log("Cached anchor created!"); + + while (!m_Anchor.Localized) + { + await Task.Yield(); + } + + return true; + } + + public async Task ShareAnchors(List playerIds) + { + if (m_Anchor == null) + { + return false; + } + + var spaceUserList = new List(); + foreach (var id in playerIds) + { + Debug.Log($"new share id: {id}"); + spaceUserList.Add(new OVRSpaceUser(id)); + } + + // TODO: Check anchor exists and is in cloud storage. + var result = await m_Anchor.ShareAsync(spaceUserList); + + if (result == OVRSpatialAnchor.OperationResult.Success) + { + Debug.Log($"Share complete!"); + return true; + } + + return false; + } + } +} diff --git a/Assets/OculusMR/SpatialAnchorManager.cs.meta b/Assets/OculusMR/SpatialAnchorManager.cs.meta new file mode 100644 index 0000000000..86324176de --- /dev/null +++ b/Assets/OculusMR/SpatialAnchorManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 91e9b9159d619a2428162e46f16afb16 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Resources/NetworkProjectConfig.fusion b/Assets/Photon/Fusion/Resources/NetworkProjectConfig.fusion new file mode 100644 index 0000000000..526a559a17 --- /dev/null +++ b/Assets/Photon/Fusion/Resources/NetworkProjectConfig.fusion @@ -0,0 +1,164 @@ +{ + "Version": 1, + "TypeId": "NetworkProjectConfig", + "PeerMode": 0, + "PhysicsEngine": 0, + "ServerPhysicsMode": 0, + "UseLagCompensation": true, + "LagCompensation": { + "HitboxBufferSize": 12, + "HitboxCapacity": 512, + "ExpansionFactor": 0.20000000298023225, + "Optimize": false, + "DebugBroadphase": false, + "DebugHistory": false, + "DebugColor": { + "r": 0.0, + "g": 1.0, + "b": 0.0, + "a": 0.5 + }, + "ClientDebugColor": { + "r": 0.0, + "g": 0.0, + "b": 1.0, + "a": 0.5 + }, + "HistoryDebugColor": { + "r": 0.0, + "g": 0.0, + "b": 1.0, + "a": 0.5 + } + }, + "SceneLoadSpawnMode": 0, + "DeltaCompressor": 0, + "InvokeRenderInBatchMode": true, + "MaxNetworkedObjectCount": 8192, + "NetworkIdIsObjectName": false, + "HideNetworkObjectInactivityGuard": false, + "EnableHostMigration": false, + "HostMigrationSnapshotInterval": 60, + "Simulation": { + "InputDataWordCount": 0, + "TickRate": 60, + "MaxPrediction": 60, + "DefaultPlayers": 10, + "ReplicationMode": 0, + "ObjectInterest": false, + "ServerPacketInterval": 1, + "ClientPacketInterval": 1 + }, + "Interpolation": { + "DeltaAdjustment": 1, + "AllowedJitter": 25, + "SnapLimit": 200, + "MultiplierMin": 1.25, + "MultiplierMax": 3.0 + }, + "Network": { + "SocketSendBufferSize": 256, + "SocketRecvBufferSize": 256, + "ConnectAttempts": 10, + "ConnectInterval": 0.5, + "ConnectionDefaultRtt": 0.1, + "ConnectionTimeout": 10.0, + "ConnectionPingInterval": 1.0, + "ConnectionShutdownTime": 1.0, + "MtuDefault": 1136, + "ReliableDataTransferModes": 3 + }, + "NetworkConditions": { + "Enabled": false, + "DelayShape": 1, + "DelayMin": 0.01, + "DelayMax": 0.1, + "DelayPeriod": 3.0, + "DelayThreshold": 0.5, + "AdditionalJitter": 0.01, + "LossChanceShape": 1, + "LossChanceMin": 0.0, + "LossChanceMax": 0.02, + "LossChanceThreshold": 0.9, + "LossChancePeriod": 3.0, + "AdditionalLoss": 0.005 + }, + "Heap": { + "PageShift": 14, + "PageCount": 128, + "GlobalsSize": 0 + }, + "AccuracyDefaults": { + "coreKeys": [ + "Uncompressed", + "Default", + "Position", + "Rotation", + "NormalizedTime" + ], + "coreDefs": [ + { + "_value": 0.0, + "_inverse": Infinity, + "_hash": -1382104104 + }, + { + "_value": 0.0010000000474974514, + "_inverse": 999.9999389648438, + "_hash": -814817977 + }, + { + "_value": 0.0010000000474974514, + "_inverse": 999.9999389648438, + "_hash": -194233199 + }, + { + "_value": 0.0010000000474974514, + "_inverse": 999.9999389648438, + "_hash": -258202764 + }, + { + "_value": 0.00009999999747378752, + "_inverse": 10000.0, + "_hash": 1061325578 + } + ], + "coreVals": [ + { + "_value": 0.0, + "_inverse": Infinity, + "_hash": -1382104104 + }, + { + "_value": 0.0010000000474974514, + "_inverse": 999.9999389648438, + "_hash": -814817977 + }, + { + "_value": 0.0010000000474974514, + "_inverse": 999.9999389648438, + "_hash": -194233199 + }, + { + "_value": 0.0010000000474974514, + "_inverse": 999.9999389648438, + "_hash": -258202764 + }, + { + "_value": 0.00009999999747378752, + "_inverse": 10000.0, + "_hash": 1061325578 + } + ], + "tags": [], + "values": [] + }, + "AssembliesToWeave": [ + "Assembly-CSharp", + "Assembly-CSharp-firstpass" + ], + "UseSerializableDictionary": true, + "NullChecksForNetworkedProperties": true, + "CheckRpcAttributeUsage": false, + "CheckNetworkedPropertiesBeingEmpty": false +} \ No newline at end of file diff --git a/Assets/Photon/Fusion/Resources/NetworkProjectConfig.fusion.meta b/Assets/Photon/Fusion/Resources/NetworkProjectConfig.fusion.meta new file mode 100644 index 0000000000..4e2a77bdf0 --- /dev/null +++ b/Assets/Photon/Fusion/Resources/NetworkProjectConfig.fusion.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dd4ca0370d231e84e889cd4edcaf0bde +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 11500000, guid: 66a64a17d0b40f34f9224317a5a84bf2, type: 3} + PrefabAssetsContainerPath: diff --git a/Assets/Plugins/Android/AndroidManifest.xml b/Assets/Plugins/Android/AndroidManifest.xml index 8b803d7168..93f8cb04f6 100644 --- a/Assets/Plugins/Android/AndroidManifest.xml +++ b/Assets/Plugins/Android/AndroidManifest.xml @@ -19,4 +19,5 @@ + diff --git a/Assets/Prefabs/HMDMesh.prefab b/Assets/Prefabs/HMDMesh.prefab new file mode 100644 index 0000000000..6656788973 --- /dev/null +++ b/Assets/Prefabs/HMDMesh.prefab @@ -0,0 +1,85 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &1448111865499013753 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1448111865499013754} + - component: {fileID: 1448111865499013748} + - component: {fileID: 1448111865499013755} + m_Layer: 15 + m_Name: HMDMesh + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1448111865499013754 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1448111865499013753} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!33 &1448111865499013748 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1448111865499013753} + m_Mesh: {fileID: 4300000, guid: dd4ebe901b35bd949a7116ab582586b9, type: 3} +--- !u!23 &1448111865499013755 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1448111865499013753} + m_Enabled: 1 + m_CastShadows: 0 + m_ReceiveShadows: 0 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 0 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: 981fe2686b81af74ca426ec05bdfe481, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 1 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 0 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} diff --git a/Assets/Prefabs/HMDMesh.prefab.meta b/Assets/Prefabs/HMDMesh.prefab.meta new file mode 100644 index 0000000000..e7f82c1e63 --- /dev/null +++ b/Assets/Prefabs/HMDMesh.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 834f4ac71ec35d148acacfca28f04405 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Resources/Multiplayer.meta b/Assets/Resources/Multiplayer.meta new file mode 100644 index 0000000000..5797da00ec --- /dev/null +++ b/Assets/Resources/Multiplayer.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 13faaeff6bdbe034ca7a725fd0ce5771 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Resources/Multiplayer/Photon.meta b/Assets/Resources/Multiplayer/Photon.meta new file mode 100644 index 0000000000..49cbffbb03 --- /dev/null +++ b/Assets/Resources/Multiplayer/Photon.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2b0e9afea1d90794ab08a7729ba8d55c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Resources/Multiplayer/Photon/PhotonPlayerRig.prefab b/Assets/Resources/Multiplayer/Photon/PhotonPlayerRig.prefab new file mode 100644 index 0000000000..524ebbe795 --- /dev/null +++ b/Assets/Resources/Multiplayer/Photon/PhotonPlayerRig.prefab @@ -0,0 +1,704 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &303516855785969295 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 6718544642020818535} + - component: {fileID: 7533779467632377196} + - component: {fileID: 6752862290110515822} + m_Layer: 0 + m_Name: PhotonPlayerRig + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &6718544642020818535 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 303516855785969295} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 5041384770245166357} + - {fileID: 1457871284126546349} + m_Father: {fileID: 0} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &7533779467632377196 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 303516855785969295} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f250b0d6a1df335439813ad43ce3dbdb, type: 3} + m_Name: + m_EditorClassIdentifier: + _interpolationDataSource: 0 + m_PlayArea: {fileID: 8895232112724846442} + m_PlayerHead: {fileID: 5083753190464579125} + m_Left: {fileID: 6268094594865570998} + m_Right: {fileID: 4999079164026519325} + m_Tool: {fileID: 5771162771103583132} + headTransform: {fileID: 5041384770245166357} + _oculusPlayerId: 0 +--- !u!114 &6752862290110515822 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 303516855785969295} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: -1552182283, guid: e725a070cec140c4caffb81624c8c787, type: 3} + m_Name: + m_EditorClassIdentifier: + ObjectInterest: 1 + DefaultInterestGroups: [] + DestroyWhenStateAuthorityLeaves: 0 + AllowStateAuthorityOverride: 0 + AoiPositionSource: {fileID: 0} + Flags: 2305 + NetworkGuid: + RawGuidValue: 9ee52735aebb6a445b4905c8aba8cfe3 + NestedObjects: [] + NetworkedBehaviours: + - {fileID: 7533779467632377196} + - {fileID: 8895232112724846442} + - {fileID: 5083753190464579125} + - {fileID: 6268094594865570998} + - {fileID: 4999079164026519325} + - {fileID: 5771162771103583132} + SimulationBehaviours: [] +--- !u!1 &459460118330075700 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 6539275587195953435} + - component: {fileID: 8895232112724846442} + m_Layer: 0 + m_Name: Area + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &6539275587195953435 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 459460118330075700} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 3005319065403690882} + m_Father: {fileID: 1457871284126546349} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &8895232112724846442 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 459460118330075700} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 158639473, guid: e725a070cec140c4caffb81624c8c787, type: 3} + m_Name: + m_EditorClassIdentifier: + _interpolationDataSource: 0 + InterpolationSpace: 0 + InterpolationTarget: {fileID: 3005319065403690882} + InterpolateErrorCorrection: 1 + InterpolatedErrorCorrectionSettings: + MinRate: 3.3 + MaxRate: 10 + PosBlendStart: 0.25 + PosBlendEnd: 1 + PosMinCorrection: 0.025 + PosTeleportDistance: 2 + RotBlendStart: 0.1 + RotBlendEnd: 0.5 + RotTeleportRadians: 1.5 + UseLegacySharedModeInterpolation: 0 + TargetInterpolationDelay: 0.03 +--- !u!1 &1184365666732702344 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 7668165333132264083} + - component: {fileID: 4999079164026519325} + m_Layer: 0 + m_Name: RightHand + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &7668165333132264083 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1184365666732702344} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 4501806461527917157} + m_Father: {fileID: 1457871284126546349} + m_RootOrder: 3 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &4999079164026519325 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1184365666732702344} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 158639473, guid: e725a070cec140c4caffb81624c8c787, type: 3} + m_Name: + m_EditorClassIdentifier: + _interpolationDataSource: 0 + InterpolationSpace: 0 + InterpolationTarget: {fileID: 4501806461527917157} + InterpolateErrorCorrection: 1 + InterpolatedErrorCorrectionSettings: + MinRate: 3.3 + MaxRate: 10 + PosBlendStart: 0.25 + PosBlendEnd: 1 + PosMinCorrection: 0.025 + PosTeleportDistance: 2 + RotBlendStart: 0.1 + RotBlendEnd: 0.5 + RotTeleportRadians: 1.5 + UseLegacySharedModeInterpolation: 0 + TargetInterpolationDelay: 0.03 +--- !u!1 &2181518286534520838 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2439271445520760248} + m_Layer: 0 + m_Name: DummyTool + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &2439271445520760248 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2181518286534520838} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 8617970912018780043} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &2546328019691961597 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1030654070696327489} + - component: {fileID: 5083753190464579125} + m_Layer: 0 + m_Name: Head + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1030654070696327489 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2546328019691961597} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 2514485848220458462} + m_Father: {fileID: 1457871284126546349} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &5083753190464579125 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2546328019691961597} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 158639473, guid: e725a070cec140c4caffb81624c8c787, type: 3} + m_Name: + m_EditorClassIdentifier: + _interpolationDataSource: 0 + InterpolationSpace: 0 + InterpolationTarget: {fileID: 2514485848220458462} + InterpolateErrorCorrection: 1 + InterpolatedErrorCorrectionSettings: + MinRate: 3.3 + MaxRate: 10 + PosBlendStart: 0.25 + PosBlendEnd: 1 + PosMinCorrection: 0.025 + PosTeleportDistance: 2 + RotBlendStart: 0.1 + RotBlendEnd: 0.5 + RotTeleportRadians: 1.5 + UseLegacySharedModeInterpolation: 0 + TargetInterpolationDelay: 0.03 +--- !u!1 &2568790808486584579 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 5887326793658353859} + - component: {fileID: 6268094594865570998} + m_Layer: 0 + m_Name: LeftHand + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &5887326793658353859 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2568790808486584579} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1310333546402417676} + m_Father: {fileID: 1457871284126546349} + m_RootOrder: 2 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &6268094594865570998 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2568790808486584579} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 158639473, guid: e725a070cec140c4caffb81624c8c787, type: 3} + m_Name: + m_EditorClassIdentifier: + _interpolationDataSource: 0 + InterpolationSpace: 0 + InterpolationTarget: {fileID: 1310333546402417676} + InterpolateErrorCorrection: 1 + InterpolatedErrorCorrectionSettings: + MinRate: 3.3 + MaxRate: 10 + PosBlendStart: 0.25 + PosBlendEnd: 1 + PosMinCorrection: 0.025 + PosTeleportDistance: 2 + RotBlendStart: 0.1 + RotBlendEnd: 0.5 + RotTeleportRadians: 1.5 + UseLegacySharedModeInterpolation: 0 + TargetInterpolationDelay: 0.03 +--- !u!1 &3333659737751141766 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 3005319065403690882} + m_Layer: 0 + m_Name: DummyArea + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &3005319065403690882 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3333659737751141766} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 6539275587195953435} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &5681254972916902966 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 5041384770245166357} + m_Layer: 0 + m_Name: HeadTransform + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &5041384770245166357 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5681254972916902966} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 7120707258077581554} + m_Father: {fileID: 6718544642020818535} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &5736389435991655852 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1310333546402417676} + m_Layer: 0 + m_Name: DummyLeft + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1310333546402417676 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5736389435991655852} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 5887326793658353859} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &6003437268994292288 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4501806461527917157} + m_Layer: 0 + m_Name: DummyRight + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &4501806461527917157 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6003437268994292288} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 7668165333132264083} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &7803168780329886017 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 8617970912018780043} + - component: {fileID: 5771162771103583132} + m_Layer: 0 + m_Name: Tool + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &8617970912018780043 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7803168780329886017} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 2439271445520760248} + m_Father: {fileID: 1457871284126546349} + m_RootOrder: 4 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &5771162771103583132 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7803168780329886017} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 158639473, guid: e725a070cec140c4caffb81624c8c787, type: 3} + m_Name: + m_EditorClassIdentifier: + _interpolationDataSource: 0 + InterpolationSpace: 0 + InterpolationTarget: {fileID: 2439271445520760248} + InterpolateErrorCorrection: 1 + InterpolatedErrorCorrectionSettings: + MinRate: 3.3 + MaxRate: 10 + PosBlendStart: 0.25 + PosBlendEnd: 1 + PosMinCorrection: 0.025 + PosTeleportDistance: 2 + RotBlendStart: 0.1 + RotBlendEnd: 0.5 + RotTeleportRadians: 1.5 + UseLegacySharedModeInterpolation: 0 + TargetInterpolationDelay: 0.03 +--- !u!1 &8601996063017238699 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1457871284126546349} + m_Layer: 0 + m_Name: DATA_TRANSFERS + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1457871284126546349 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8601996063017238699} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 6539275587195953435} + - {fileID: 1030654070696327489} + - {fileID: 5887326793658353859} + - {fileID: 7668165333132264083} + - {fileID: 8617970912018780043} + m_Father: {fileID: 6718544642020818535} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &8742815544225261104 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2514485848220458462} + m_Layer: 0 + m_Name: DummyHead + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &2514485848220458462 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8742815544225261104} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1030654070696327489} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1001 &8559495263563694728 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + m_TransformParent: {fileID: 5041384770245166357} + m_Modifications: + - target: {fileID: 1448111865499013753, guid: 834f4ac71ec35d148acacfca28f04405, + type: 3} + propertyPath: m_Name + value: HMDMesh + objectReference: {fileID: 0} + - target: {fileID: 1448111865499013754, guid: 834f4ac71ec35d148acacfca28f04405, + type: 3} + propertyPath: m_RootOrder + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 1448111865499013754, guid: 834f4ac71ec35d148acacfca28f04405, + type: 3} + propertyPath: m_LocalScale.x + value: 12 + objectReference: {fileID: 0} + - target: {fileID: 1448111865499013754, guid: 834f4ac71ec35d148acacfca28f04405, + type: 3} + propertyPath: m_LocalScale.y + value: 12 + objectReference: {fileID: 0} + - target: {fileID: 1448111865499013754, guid: 834f4ac71ec35d148acacfca28f04405, + type: 3} + propertyPath: m_LocalScale.z + value: 12 + objectReference: {fileID: 0} + - target: {fileID: 1448111865499013754, guid: 834f4ac71ec35d148acacfca28f04405, + type: 3} + propertyPath: m_LocalPosition.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 1448111865499013754, guid: 834f4ac71ec35d148acacfca28f04405, + type: 3} + propertyPath: m_LocalPosition.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 1448111865499013754, guid: 834f4ac71ec35d148acacfca28f04405, + type: 3} + propertyPath: m_LocalPosition.z + value: -0.45 + objectReference: {fileID: 0} + - target: {fileID: 1448111865499013754, guid: 834f4ac71ec35d148acacfca28f04405, + type: 3} + propertyPath: m_LocalRotation.w + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 1448111865499013754, guid: 834f4ac71ec35d148acacfca28f04405, + type: 3} + propertyPath: m_LocalRotation.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 1448111865499013754, guid: 834f4ac71ec35d148acacfca28f04405, + type: 3} + propertyPath: m_LocalRotation.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 1448111865499013754, guid: 834f4ac71ec35d148acacfca28f04405, + type: 3} + propertyPath: m_LocalRotation.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 1448111865499013754, guid: 834f4ac71ec35d148acacfca28f04405, + type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 1448111865499013754, guid: 834f4ac71ec35d148acacfca28f04405, + type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 1448111865499013754, guid: 834f4ac71ec35d148acacfca28f04405, + type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + m_RemovedComponents: [] + m_SourcePrefab: {fileID: 100100000, guid: 834f4ac71ec35d148acacfca28f04405, type: 3} +--- !u!4 &7120707258077581554 stripped +Transform: + m_CorrespondingSourceObject: {fileID: 1448111865499013754, guid: 834f4ac71ec35d148acacfca28f04405, + type: 3} + m_PrefabInstance: {fileID: 8559495263563694728} + m_PrefabAsset: {fileID: 0} diff --git a/Assets/Resources/Multiplayer/Photon/PhotonPlayerRig.prefab.meta b/Assets/Resources/Multiplayer/Photon/PhotonPlayerRig.prefab.meta new file mode 100644 index 0000000000..726ed433ed --- /dev/null +++ b/Assets/Resources/Multiplayer/Photon/PhotonPlayerRig.prefab.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 9ee52735aebb6a445b4905c8aba8cfe3 +labels: +- FusionPrefab +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Resources/OculusRuntimeSettings.asset b/Assets/Resources/OculusRuntimeSettings.asset index b5b403bd88..0dde6eca9a 100644 --- a/Assets/Resources/OculusRuntimeSettings.asset +++ b/Assets/Resources/OculusRuntimeSettings.asset @@ -13,3 +13,6 @@ MonoBehaviour: m_Name: OculusRuntimeSettings m_EditorClassIdentifier: colorSpace: 4 + hasSentConsentEvent: 1 + hasSetTelemetryEnabled: 1 + telemetryEnabled: 0 diff --git a/Assets/Scenes/Main.unity b/Assets/Scenes/Main.unity index 25b9bb36d5..43763876e5 100644 --- a/Assets/Scenes/Main.unity +++ b/Assets/Scenes/Main.unity @@ -10123,6 +10123,7 @@ Transform: - {fileID: 1412532063} - {fileID: 383974627} - {fileID: 669339392} + - {fileID: 1052269831} m_Father: {fileID: 0} m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} @@ -13585,89 +13586,24 @@ CanvasRenderer: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 948093179} m_CullTransparentMesh: 0 ---- !u!1 &950303573 +--- !u!1 &950303573 stripped GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} + m_CorrespondingSourceObject: {fileID: 1448111865499013753, guid: 834f4ac71ec35d148acacfca28f04405, + type: 3} + m_PrefabInstance: {fileID: 1448111864570226988} m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 950303574} - - component: {fileID: 950303576} - - component: {fileID: 950303575} - m_Layer: 15 - m_Name: DropCamHeadMesh - m_TagString: Untagged - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!4 &950303574 +--- !u!4 &950303574 stripped Transform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} + m_CorrespondingSourceObject: {fileID: 1448111865499013754, guid: 834f4ac71ec35d148acacfca28f04405, + type: 3} + m_PrefabInstance: {fileID: 1448111864570226988} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 950303573} - m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 0, y: 0, z: -0.15} - m_LocalScale: {x: 4, y: 4, z: 4} - m_ConstrainProportionsScale: 0 - m_Children: [] - m_Father: {fileID: 1087863899} - m_RootOrder: 1 - m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!23 &950303575 +--- !u!23 &950303575 stripped MeshRenderer: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 950303573} - m_Enabled: 1 - m_CastShadows: 0 - m_ReceiveShadows: 0 - m_DynamicOccludee: 1 - m_StaticShadowCaster: 0 - m_MotionVectors: 1 - m_LightProbeUsage: 0 - m_ReflectionProbeUsage: 1 - m_RayTracingMode: 2 - m_RayTraceProcedural: 0 - m_RenderingLayerMask: 1 - m_RendererPriority: 0 - m_Materials: - - {fileID: 2100000, guid: 981fe2686b81af74ca426ec05bdfe481, type: 2} - m_StaticBatchInfo: - firstSubMesh: 0 - subMeshCount: 0 - m_StaticBatchRoot: {fileID: 0} - m_ProbeAnchor: {fileID: 0} - m_LightProbeVolumeOverride: {fileID: 0} - m_ScaleInLightmap: 1 - m_ReceiveGI: 1 - m_PreserveUVs: 1 - m_IgnoreNormalsForChartDetection: 0 - m_ImportantGI: 0 - m_StitchLightmapSeams: 0 - m_SelectedEditorRenderState: 3 - m_MinimumChartSize: 4 - m_AutoUVMaxDistance: 0.5 - m_AutoUVMaxAngle: 89 - m_LightmapParameters: {fileID: 0} - m_SortingLayerID: 0 - m_SortingLayer: 0 - m_SortingOrder: 0 - m_AdditionalVertexStreams: {fileID: 0} ---- !u!33 &950303576 -MeshFilter: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} + m_CorrespondingSourceObject: {fileID: 1448111865499013755, guid: 834f4ac71ec35d148acacfca28f04405, + type: 3} + m_PrefabInstance: {fileID: 1448111864570226988} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 950303573} - m_Mesh: {fileID: 4300000, guid: dd4ebe901b35bd949a7116ab582586b9, type: 3} --- !u!1 &952277096 GameObject: m_ObjectHideFlags: 0 @@ -15023,6 +14959,51 @@ MeshFilter: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1052118967} m_Mesh: {fileID: 10210, guid: 0000000000000000e000000000000000, type: 0} +--- !u!1 &1052269830 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1052269831} + - component: {fileID: 1052269832} + m_Layer: 0 + m_Name: MultiplayerManager + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1052269831 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1052269830} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 652605545} + m_RootOrder: 4 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &1052269832 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1052269830} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: b989d2f19099ac044b955d3a6a62dea9, type: 3} + m_Name: + m_EditorClassIdentifier: + m_MultiplayerType: 2 --- !u!1 &1057179852 GameObject: m_ObjectHideFlags: 0 @@ -35289,6 +35270,90 @@ MeshFilter: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1188089478511272227} m_Mesh: {fileID: 4300000, guid: 5ef960ddf11c1fd4983638f56f6a8be0, type: 3} +--- !u!1001 &1448111864570226988 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + m_TransformParent: {fileID: 1087863899} + m_Modifications: + - target: {fileID: 1448111865499013753, guid: 834f4ac71ec35d148acacfca28f04405, + type: 3} + propertyPath: m_Name + value: DropCamHeadMesh + objectReference: {fileID: 0} + - target: {fileID: 1448111865499013754, guid: 834f4ac71ec35d148acacfca28f04405, + type: 3} + propertyPath: m_RootOrder + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 1448111865499013754, guid: 834f4ac71ec35d148acacfca28f04405, + type: 3} + propertyPath: m_LocalScale.x + value: 4 + objectReference: {fileID: 0} + - target: {fileID: 1448111865499013754, guid: 834f4ac71ec35d148acacfca28f04405, + type: 3} + propertyPath: m_LocalScale.y + value: 4 + objectReference: {fileID: 0} + - target: {fileID: 1448111865499013754, guid: 834f4ac71ec35d148acacfca28f04405, + type: 3} + propertyPath: m_LocalScale.z + value: 4 + objectReference: {fileID: 0} + - target: {fileID: 1448111865499013754, guid: 834f4ac71ec35d148acacfca28f04405, + type: 3} + propertyPath: m_LocalPosition.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 1448111865499013754, guid: 834f4ac71ec35d148acacfca28f04405, + type: 3} + propertyPath: m_LocalPosition.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 1448111865499013754, guid: 834f4ac71ec35d148acacfca28f04405, + type: 3} + propertyPath: m_LocalPosition.z + value: -0.15 + objectReference: {fileID: 0} + - target: {fileID: 1448111865499013754, guid: 834f4ac71ec35d148acacfca28f04405, + type: 3} + propertyPath: m_LocalRotation.w + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 1448111865499013754, guid: 834f4ac71ec35d148acacfca28f04405, + type: 3} + propertyPath: m_LocalRotation.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 1448111865499013754, guid: 834f4ac71ec35d148acacfca28f04405, + type: 3} + propertyPath: m_LocalRotation.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 1448111865499013754, guid: 834f4ac71ec35d148acacfca28f04405, + type: 3} + propertyPath: m_LocalRotation.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 1448111865499013754, guid: 834f4ac71ec35d148acacfca28f04405, + type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 1448111865499013754, guid: 834f4ac71ec35d148acacfca28f04405, + type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 1448111865499013754, guid: 834f4ac71ec35d148acacfca28f04405, + type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + m_RemovedComponents: [] + m_SourcePrefab: {fileID: 100100000, guid: 834f4ac71ec35d148acacfca28f04405, type: 3} --- !u!4 &1518196148397455185 Transform: m_ObjectHideFlags: 0 diff --git a/Assets/Scripts/Commands/BaseCommand.cs b/Assets/Scripts/Commands/BaseCommand.cs index ee38204460..45492bcf57 100644 --- a/Assets/Scripts/Commands/BaseCommand.cs +++ b/Assets/Scripts/Commands/BaseCommand.cs @@ -26,8 +26,30 @@ namespace TiltBrush /// the base class or be sure to recurse into children. public class BaseCommand : IDisposable { + private Guid m_Guid; + private BaseCommand m_Parent; protected List m_Children; + public int ChildrenCount + { + get { return m_Children.Count; } + } + + public Guid Guid + { + get { return m_Guid; } + } + + public List Children + { + get { return m_Children.ToList(); } + } + + public Guid ParentGuid + { + get { return m_Parent != null ? m_Parent.Guid : Guid.Empty; } + } + protected static Vector3 GetPositionForCommand(Stroke stroke) { var points = stroke.m_ControlPoints; @@ -48,10 +70,12 @@ protected static Vector3 GetPositionForCommand(Stroke[] strokes) /// The constructor should not mutate sketch state. public BaseCommand(BaseCommand parent = null) { + m_Guid = Guid.NewGuid(); m_Children = new List(); if (parent != null) { parent.m_Children.Add(this); + m_Parent = parent; } } @@ -111,6 +135,11 @@ public virtual bool Merge(BaseCommand other) return other.IsNoop; } + public virtual string Serialize() + { + return string.Empty; + } + /// Dispose of this entire command tree if there are any /// controlled resources. /// Parent is always destroyed after all children. diff --git a/Assets/Scripts/Commands/BrushStrokeCommand.cs b/Assets/Scripts/Commands/BrushStrokeCommand.cs index bf606b1d5b..3cce0caa6b 100644 --- a/Assets/Scripts/Commands/BrushStrokeCommand.cs +++ b/Assets/Scripts/Commands/BrushStrokeCommand.cs @@ -18,7 +18,7 @@ namespace TiltBrush { public class BrushStrokeCommand : BaseCommand { - private Stroke m_Stroke; + public Stroke m_Stroke; private StencilWidget m_Widget; private float m_LineLength_CS; // Only valid if m_Widget != null @@ -35,6 +35,13 @@ public BrushStrokeCommand(Stroke stroke, StencilWidget widget = null, m_LineLength_CS = lineLength; } + public override string Serialize() + { + //var data = Newtonsoft.Json.JsonConvert.SerializeObject(m_Stroke); + //UnityEngine.Debug.Log($"test: {data}"); + return JsonUtility.ToJson(m_Stroke); + } + public override bool NeedsSave { get { return true; } } protected override void OnDispose() diff --git a/Assets/Scripts/Commands/DeleteStrokeCommand.cs b/Assets/Scripts/Commands/DeleteStrokeCommand.cs index 1250775d89..18f86c1d30 100644 --- a/Assets/Scripts/Commands/DeleteStrokeCommand.cs +++ b/Assets/Scripts/Commands/DeleteStrokeCommand.cs @@ -18,7 +18,7 @@ namespace TiltBrush { public class DeleteStrokeCommand : BaseCommand { - private Stroke m_TargetStroke; + public Stroke m_TargetStroke; private bool m_SilenceFirstAudio; private Vector3 CommandAudioPosition diff --git a/Assets/Scripts/Config.cs b/Assets/Scripts/Config.cs index bed15a24db..fecf321117 100644 --- a/Assets/Scripts/Config.cs +++ b/Assets/Scripts/Config.cs @@ -129,6 +129,7 @@ private class UserConfigChange public SecretsConfig.ServiceAuthData OculusSecrets => Secrets[SecretsConfig.Service.Oculus]; public SecretsConfig.ServiceAuthData OculusMobileSecrets => Secrets[SecretsConfig.Service.OculusMobile]; public SecretsConfig.ServiceAuthData PimaxSecrets => Secrets[SecretsConfig.Service.Pimax]; + public SecretsConfig.ServiceAuthData PhotonFusionSecrets => Secrets[SecretsConfig.Service.PhotonFusion]; public bool DisableAccountLogins; diff --git a/Assets/Scripts/Multiplayer.meta b/Assets/Scripts/Multiplayer.meta new file mode 100644 index 0000000000..8b76c079e2 --- /dev/null +++ b/Assets/Scripts/Multiplayer.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 954339fc67e50a54986118b6ac59ee38 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Multiplayer/MultiplayerDataStructs.cs b/Assets/Scripts/Multiplayer/MultiplayerDataStructs.cs new file mode 100644 index 0000000000..2401d272f0 --- /dev/null +++ b/Assets/Scripts/Multiplayer/MultiplayerDataStructs.cs @@ -0,0 +1,49 @@ +// Copyright 2023 The Open Brush Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +namespace OpenBrush.Multiplayer +{ + [System.Serializable] + public struct PlayerRigData + { + public Vector3 HeadPosition; + public Quaternion HeadRotation; + public Vector3 HeadScale; + + public Vector3 ToolPosition; + public Quaternion ToolRotation; + + public BrushData BrushData; + public ExtraData ExtraData; + + } + + [System.Serializable] + public struct BrushData + { + public Color Color; + public string Guid; + public float Size; + } + + [System.Serializable] + public struct ExtraData + { + public ulong OculusPlayerId; + } +} diff --git a/Assets/Scripts/Multiplayer/MultiplayerDataStructs.cs.meta b/Assets/Scripts/Multiplayer/MultiplayerDataStructs.cs.meta new file mode 100644 index 0000000000..4851bff5c7 --- /dev/null +++ b/Assets/Scripts/Multiplayer/MultiplayerDataStructs.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fd96ac52cd212b3418729495065a5603 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Multiplayer/MultiplayerInterfaces.cs b/Assets/Scripts/Multiplayer/MultiplayerInterfaces.cs new file mode 100644 index 0000000000..a58eeb5210 --- /dev/null +++ b/Assets/Scripts/Multiplayer/MultiplayerInterfaces.cs @@ -0,0 +1,45 @@ +// Copyright 2023 The Open Brush Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Numerics; +using System.Threading.Tasks; +using TiltBrush; +using UnityEngine; + +namespace OpenBrush.Multiplayer +{ + public interface IConnectionHandler + { + Task Connect(); + + bool IsConnected(); + Task Disconnect(bool force = false); + + void Update(); + + Task PerformCommand(BaseCommand command); + Task UndoCommand(BaseCommand command); + Task RedoCommand(BaseCommand command); + Task RpcSyncToSharedAnchor(string uuid); + + //ITransientData SpawnPlayer(); + } + + public interface ITransientData + { + void TransmitData(T data); + T RecieveData(); + } +} diff --git a/Assets/Scripts/Multiplayer/MultiplayerInterfaces.cs.meta b/Assets/Scripts/Multiplayer/MultiplayerInterfaces.cs.meta new file mode 100644 index 0000000000..be153a0983 --- /dev/null +++ b/Assets/Scripts/Multiplayer/MultiplayerInterfaces.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a8a716423ae9ed34087163f00bfe3ca2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Multiplayer/MultiplayerManager.cs b/Assets/Scripts/Multiplayer/MultiplayerManager.cs new file mode 100644 index 0000000000..71c42aff0e --- /dev/null +++ b/Assets/Scripts/Multiplayer/MultiplayerManager.cs @@ -0,0 +1,225 @@ +// Copyright 2023 The Open Brush Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Collections.Generic; +using UnityEngine; +using Unity.XR.CoreUtils; +using OVRPlatform = Oculus.Platform; +using TiltBrush; + +namespace OpenBrush.Multiplayer +{ + public enum MultiplayerType + { + None, + Colyseus = 1, + Photon = 2, + } + + public class MultiplayerManager : MonoBehaviour + { + public static MultiplayerManager m_Instance; + public MultiplayerType m_MultiplayerType; + + private IConnectionHandler m_Manager; + + private ITransientData m_LocalPlayer; + private List> m_RemotePlayers; + + public Action> localPlayerJoined; + public Action> remotePlayerJoined; + + ulong myOculusUserId; + + List oculusPlayerIds; + + private bool IsConnected { get { return m_Manager != null && m_Manager.IsConnected(); } } + + void Awake() + { + m_Instance = this; + oculusPlayerIds = new List(); + m_RemotePlayers = new List>(); + } + + void Start() + { + +#if OCULUS_SUPPORTED + OVRPlatform.Users.GetLoggedInUser().OnComplete((msg) => { + if (!msg.IsError) + { + myOculusUserId = msg.GetUser().ID; + Debug.Log($"OculusID: {myOculusUserId}"); + oculusPlayerIds.Add(myOculusUserId); + } + else + { + Debug.LogError(msg.GetError()); + } + }); +#endif + switch (m_MultiplayerType) + { + case MultiplayerType.Photon: +#if FUSION_WEAVER + m_Manager = new PhotonManager(this); +#endif // FUSION_WEAVER + break; + default: + return; + } + + localPlayerJoined += OnLocalPlayerJoined; + remotePlayerJoined += OnRemotePlayerJoined; + SketchMemoryScript.m_Instance.CommandPerformed += OnCommandPerformed; + SketchMemoryScript.m_Instance.CommandUndo += OnCommandUndo; + SketchMemoryScript.m_Instance.CommandRedo += OnCommandRedo; + } + + void OnDestroy() + { + localPlayerJoined -= OnLocalPlayerJoined; + remotePlayerJoined -= OnRemotePlayerJoined; + SketchMemoryScript.m_Instance.CommandPerformed -= OnCommandPerformed; + SketchMemoryScript.m_Instance.CommandUndo -= OnCommandUndo; + SketchMemoryScript.m_Instance.CommandRedo -= OnCommandRedo; + } + + public async void Connect() + { + var result = await m_Manager.Connect(); + } + + void Update() + { + if (App.CurrentState != App.AppState.Standard || m_Manager == null) + { + return; + } + + m_Manager.Update(); + + // Transmit local player data relative to scene origin + var headRelativeToScene = App.Scene.AsScene[App.VrSdk.GetVrCamera().transform]; + var pointerRelativeToScene = App.Scene.AsScene[PointerManager.m_Instance.MainPointer.transform]; + + var data = new PlayerRigData + { + HeadPosition = headRelativeToScene.translation, + HeadRotation = headRelativeToScene.rotation, + ToolPosition = pointerRelativeToScene.translation, + ToolRotation = pointerRelativeToScene.rotation, + BrushData = new BrushData + { + Color = PointerManager.m_Instance.MainPointer.GetCurrentColor(), + Size = PointerManager.m_Instance.MainPointer.BrushSize01, + Guid = BrushController.m_Instance.ActiveBrush.m_Guid.ToString(), + }, + ExtraData = new ExtraData + { + OculusPlayerId = myOculusUserId, + } + }; + + if (m_LocalPlayer != null) + { + m_LocalPlayer.TransmitData(data); + } + + + // Update remote user refs, and send Anchors if new player joins. + bool newUser = false; + foreach (var player in m_RemotePlayers) + { + data = player.RecieveData(); + // New user, share the anchor with them + if (data.ExtraData.OculusPlayerId != 0 && !oculusPlayerIds.Contains(data.ExtraData.OculusPlayerId)) + { + Debug.Log("detected new user!"); + Debug.Log(data.ExtraData.OculusPlayerId); + oculusPlayerIds.Add(data.ExtraData.OculusPlayerId); + newUser = true; + } + } + + if (newUser) + { + ShareAnchors(); + } + } + + void OnLocalPlayerJoined(ITransientData playerData) + { + m_LocalPlayer = playerData; + } + + void OnRemotePlayerJoined(ITransientData playerData) + { + Debug.Log("Adding new player to track."); + m_RemotePlayers.Add(playerData); + } + + private async void OnCommandPerformed(BaseCommand command) + { + if (!IsConnected) + { + return; + } + + var success = await m_Manager.PerformCommand(command); + + // TODO: Proper rollback if command not possible right now. + // Commented so it doesn't interfere with general use. + // Link actions to connect/disconnect, not Unity lifecycle. + + // if (!success) + // { + // OutputWindowScript.m_Instance.CreateInfoCardAtController(InputManager.ControllerName.Brush, "Don't know how to network this action yet."); + // SketchMemoryScript.m_Instance.StepBack(false); + // } + } + + private void OnCommandUndo(BaseCommand command) + { + if (IsConnected) + { + m_Manager.UndoCommand(command); + } + } + + private void OnCommandRedo(BaseCommand command) + { + if (IsConnected) + { + m_Manager.RedoCommand(command); + } + } + + async void ShareAnchors() + { + Debug.Log($"sharing to {oculusPlayerIds.Count} Ids"); + var success = await OculusMRController.m_Instance.m_SpatialAnchorManager.ShareAnchors(oculusPlayerIds); + + if (success) + { + if (!OculusMRController.m_Instance.m_SpatialAnchorManager.AnchorUuid.Equals(String.Empty)) + { + await m_Manager.RpcSyncToSharedAnchor(OculusMRController.m_Instance.m_SpatialAnchorManager.AnchorUuid); + } + } + } + } +} diff --git a/Assets/Scripts/Multiplayer/MultiplayerManager.cs.meta b/Assets/Scripts/Multiplayer/MultiplayerManager.cs.meta new file mode 100644 index 0000000000..df8e11806b --- /dev/null +++ b/Assets/Scripts/Multiplayer/MultiplayerManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b989d2f19099ac044b955d3a6a62dea9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Multiplayer/Photon.meta b/Assets/Scripts/Multiplayer/Photon.meta new file mode 100644 index 0000000000..394aac8f44 --- /dev/null +++ b/Assets/Scripts/Multiplayer/Photon.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 19da2d520bc5adb4d965967d074e2a48 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Multiplayer/Photon/PhotonManager.cs b/Assets/Scripts/Multiplayer/Photon/PhotonManager.cs new file mode 100644 index 0000000000..579b06bc88 --- /dev/null +++ b/Assets/Scripts/Multiplayer/Photon/PhotonManager.cs @@ -0,0 +1,281 @@ +// Copyright 2023 The Open Brush Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#if FUSION_WEAVER + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using UnityEngine; +using Fusion; +using Fusion.Photon.Realtime; +using Fusion.Sockets; +using TiltBrush; +using System.Linq; + +namespace OpenBrush.Multiplayer +{ + public class PhotonManager : IConnectionHandler, INetworkRunnerCallbacks + { + private NetworkRunner m_Runner; + + MultiplayerManager m_Manager; + + List m_PlayersSpawning; + + PhotonPlayerRig m_LocalPlayer; + + public PhotonManager(MultiplayerManager manager) + { + m_Manager = manager; + m_PlayersSpawning = new List(); + } + + public async Task Connect() + { + if(m_Runner != null) + { + GameObject.Destroy(m_Runner); + } + + var runnerGO = new GameObject("Photon Network Components"); + + m_Runner = runnerGO.AddComponent(); + m_Runner.ProvideInput = true; + m_Runner.AddCallbacks(this); + + var appSettings = new AppSettings + { + AppIdFusion = App.Config.PhotonFusionSecrets.ClientId, + // Need this set for some reason + FixedRegion = "", + }; + + var args = new StartGameArgs() + { + GameMode = GameMode.Shared, + SessionName = "OpenBrushMultiplayerTest", + CustomPhotonAppSettings = appSettings, + SceneManager = m_Runner.gameObject.AddComponent(), + Scene = UnityEngine.SceneManagement.SceneManager.GetActiveScene().buildIndex, + }; + + var result = await m_Runner.StartGame(args); + + return result.Ok; + + } + + public bool IsConnected() + { + if(m_Runner == null) + { + return false; + } + return m_Runner.IsRunning; + } + + public async Task Disconnect(bool force) + { + if(m_Runner != null) + { + await m_Runner.Shutdown(forceShutdownProcedure: force); + return m_Runner.IsShutdown; + } + return true; + } + + public void Update() + { + var copy = m_PlayersSpawning.ToList(); + foreach (var player in copy) + { + var newPlayer = m_Runner.GetPlayerObject(player); + if (newPlayer != null) + { + m_Manager.remotePlayerJoined?.Invoke(newPlayer.GetComponent()); + m_PlayersSpawning.Remove(player); + } + } + } + +#region IConnectionHandler Methods + public async Task PerformCommand(BaseCommand command) + { + await Task.Yield(); + return ProcessCommand(command);; + } + + public async Task UndoCommand(BaseCommand command) + { + PhotonRPC.RPC_Undo(m_Runner, command.GetType().ToString()); + await Task.Yield(); + return true; + } + + public async Task RedoCommand(BaseCommand command) + { + PhotonRPC.RPC_Redo(m_Runner, command.GetType().ToString()); + await Task.Yield(); + return true; + } + + public async Task RpcSyncToSharedAnchor(string uuid) + { + PhotonRPC.RPC_SyncToSharedAnchor(m_Runner, uuid); + await Task.Yield(); + return true; + } +#endregion + +#region Command Methods + private bool ProcessCommand(BaseCommand command) + { + bool success = true; + switch(command) + { + case BrushStrokeCommand: + success = CommandBrushStroke(command as BrushStrokeCommand); + break; + case DeleteStrokeCommand: + success = CommandDeleteStroke(command as DeleteStrokeCommand); + break; + case BaseCommand: + success = CommandBase(command); + break; + default: + // Don't know how to process this command + success = false; + break; + } + + if(command.ChildrenCount > 0) + { + foreach(var child in command.Children) + { + success &= ProcessCommand(child); + } + } + + return success; + } + + private bool CommandBrushStroke(BrushStrokeCommand command) + { + var stroke = command.m_Stroke; + + if (stroke.m_ControlPoints.Length > 128) + { + // Split and Send + int numSplits = stroke.m_ControlPoints.Length / 128; + + var firstStroke = new Stroke(stroke) + { + m_ControlPoints = stroke.m_ControlPoints.Take(128).ToArray(), + m_ControlPointsToDrop = stroke.m_ControlPointsToDrop.Take(128).ToArray() + }; + + var netStroke = new NetworkedStroke().Init(firstStroke); + + var strokeGuid = Guid.NewGuid(); + + // First Stroke + PhotonRPC.RPC_BrushStrokeBegin(m_Runner, strokeGuid, netStroke, stroke.m_ControlPoints.Length); + + // Middle + for (int rounds = 1; rounds < numSplits + 1; ++rounds) + { + var controlPoints = stroke.m_ControlPoints.Skip(rounds*128).Take(128).ToArray(); + var dropPoints = stroke.m_ControlPointsToDrop.Skip(rounds*128).Take(128).ToArray(); + + var netControlPoints = new NetworkedControlPoint[controlPoints.Length]; + + for (int point = 0; point < controlPoints.Length; ++ point) + { + netControlPoints[point] = new NetworkedControlPoint().Init(controlPoints[point]); + } + + PhotonRPC.RPC_BrushStrokeContinue(m_Runner, strokeGuid, rounds * 128, netControlPoints, dropPoints); + } + + // End + PhotonRPC.RPC_BrushStrokeComplete(m_Runner, strokeGuid, command.Guid, command.ParentGuid, command.ChildrenCount); + } + else + { + // Can send in one. + PhotonRPC.RPC_BrushStrokeFull(m_Runner, new NetworkedStroke().Init(command.m_Stroke), command.Guid, command.ParentGuid, command.ChildrenCount); + } + return true; + } + + private bool CommandBase(BaseCommand command) + { + PhotonRPC.RPC_BaseCommand(m_Runner, command.Guid, command.ParentGuid, command.ChildrenCount); + return true; + } + + private bool CommandDeleteStroke(DeleteStrokeCommand command) + { + PhotonRPC.RPC_DeleteStroke(m_Runner, command.m_TargetStroke.m_Seed, command.Guid, command.ParentGuid, command.ChildrenCount); + return true; + } +#endregion + +#region Photon Callbacks + public void OnConnectedToServer(NetworkRunner runner) + { + var rpc = m_Runner.gameObject.AddComponent(); + m_Runner.AddSimulationBehaviour(rpc); + } + + public void OnPlayerJoined(NetworkRunner runner, PlayerRef player) + { + if(player == m_Runner.LocalPlayer) + { + var playerPrefab = Resources.Load("Multiplayer/Photon/PhotonPlayerRig") as GameObject; + var playerObj = m_Runner.Spawn(playerPrefab, inputAuthority: m_Runner.LocalPlayer); + m_LocalPlayer = playerObj.GetComponent(); + m_Runner.SetPlayerObject(m_Runner.LocalPlayer, playerObj); + + + m_Manager.localPlayerJoined?.Invoke(m_LocalPlayer); + } + else + { + m_PlayersSpawning.Add(player); + } + } +#endregion + +#region Unused Photon Callbacks + public void OnPlayerLeft(NetworkRunner runner, PlayerRef player) { } + public void OnShutdown(NetworkRunner runner, ShutdownReason shutdownReason) { } + public void OnDisconnectedFromServer(NetworkRunner runner) { } + public void OnConnectFailed(NetworkRunner runner, NetAddress remoteAddress, NetConnectFailedReason reason) { } + public void OnInput(NetworkRunner runner, NetworkInput input) { } + public void OnInputMissing(NetworkRunner runner, PlayerRef player, NetworkInput input) { } + public void OnConnectRequest(NetworkRunner runner, NetworkRunnerCallbackArgs.ConnectRequest request, byte[] token) { } + public void OnUserSimulationMessage(NetworkRunner runner, SimulationMessagePtr message) { } + public void OnSessionListUpdated(NetworkRunner runner, List sessionList) { } + public void OnCustomAuthenticationResponse(NetworkRunner runner, Dictionary data) { } + public void OnHostMigration(NetworkRunner runner, HostMigrationToken hostMigrationToken) { } + public void OnReliableDataReceived(NetworkRunner runner, PlayerRef player, ArraySegment data) { } + public void OnSceneLoadDone(NetworkRunner runner) { } + public void OnSceneLoadStart(NetworkRunner runner) { } +#endregion + } +} + +#endif // FUSION_WEAVER diff --git a/Assets/Scripts/Multiplayer/Photon/PhotonManager.cs.meta b/Assets/Scripts/Multiplayer/Photon/PhotonManager.cs.meta new file mode 100644 index 0000000000..c84d889790 --- /dev/null +++ b/Assets/Scripts/Multiplayer/Photon/PhotonManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2194b426b1940e944a32c05b1dac9a4a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Multiplayer/Photon/PhotonPlayerRig.cs b/Assets/Scripts/Multiplayer/Photon/PhotonPlayerRig.cs new file mode 100644 index 0000000000..40a9e81912 --- /dev/null +++ b/Assets/Scripts/Multiplayer/Photon/PhotonPlayerRig.cs @@ -0,0 +1,122 @@ +// Copyright 2023 The Open Brush Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#if FUSION_WEAVER + +using UnityEngine; +using Fusion; +using TiltBrush; + +namespace OpenBrush.Multiplayer +{ + public class PhotonPlayerRig : NetworkBehaviour, ITransientData + { + // Only used for transferring data - don't actually use these transforms without offsetting + public NetworkTransform m_PlayArea; + public NetworkTransform m_PlayerHead; + public NetworkTransform m_Left; + public NetworkTransform m_Right; + public NetworkTransform m_Tool; + + [Networked] private Color brushColor { get; set; } + [Networked] private float brushSize { get; set; } + [Networked] private NetworkString<_64> brushGuid { get; set; } + [Networked] public ulong oculusPlayerId { get; set; } + + PointerScript transientPointer; + // The offset transforms. + [SerializeField] private Transform headTransform; + private PlayerRigData transmitData; + + public void TransmitData(PlayerRigData data) + { + transmitData = data; + oculusPlayerId = data.ExtraData.OculusPlayerId; + + brushColor = data.BrushData.Color; + brushSize = data.BrushData.Size; + brushGuid = data.BrushData.Guid; + } + + public PlayerRigData RecieveData() + { + var data = new PlayerRigData + { + HeadPosition = m_PlayerHead.InterpolationTarget.position, + HeadRotation = m_PlayerHead.InterpolationTarget.rotation, + ExtraData = new ExtraData + { + OculusPlayerId = this.oculusPlayerId + } + }; + return data; + } + + public override void Spawned() + { + base.Spawned(); + + brushGuid = BrushCatalog.m_Instance.DefaultBrush.m_Guid.ToString(); + + if(!Object.HasStateAuthority) + { + transientPointer = PointerManager.m_Instance.CreateRemotePointer(); + transientPointer.SetBrush(BrushCatalog.m_Instance.DefaultBrush); + transientPointer.SetColor(App.BrushColor.CurrentColor); + } + } + + public override void FixedUpdateNetwork() + { + base.FixedUpdateNetwork(); + + if(Object.HasStateAuthority) + { + m_PlayerHead.transform.position = transmitData.HeadPosition; + m_PlayerHead.transform.rotation = transmitData.HeadRotation; + + m_Tool.transform.position = transmitData.ToolPosition; + m_Tool.transform.rotation = transmitData.ToolRotation; + } + } + + public override void Render() + { + base.Render(); + + if (Object.HasStateAuthority) + { + + } + + else + { + var toolTR = TrTransform.TR(m_Tool.InterpolationTarget.position, m_Tool.InterpolationTarget.rotation); + App.Scene.AsScene[transientPointer.transform] = toolTR; + + transientPointer.SetColor(brushColor); + if(brushGuid.ToString() != string.Empty) + { + transientPointer.SetBrush(BrushCatalog.m_Instance.GetBrush(new System.Guid(brushGuid.ToString()))); + } + transientPointer.BrushSize01 = brushSize; + } + + var remoteTR = TrTransform.TR(m_PlayerHead.InterpolationTarget.position, m_PlayerHead.InterpolationTarget.rotation); + App.Scene.AsScene[headTransform] = remoteTR; + } + } +} + +#endif // FUSION_WEAVER diff --git a/Assets/Scripts/Multiplayer/Photon/PhotonPlayerRig.cs.meta b/Assets/Scripts/Multiplayer/Photon/PhotonPlayerRig.cs.meta new file mode 100644 index 0000000000..524e97163e --- /dev/null +++ b/Assets/Scripts/Multiplayer/Photon/PhotonPlayerRig.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f250b0d6a1df335439813ad43ce3dbdb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Multiplayer/Photon/PhotonRPC.cs b/Assets/Scripts/Multiplayer/Photon/PhotonRPC.cs new file mode 100644 index 0000000000..477276e880 --- /dev/null +++ b/Assets/Scripts/Multiplayer/Photon/PhotonRPC.cs @@ -0,0 +1,298 @@ +// Copyright 2023 The Open Brush Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +#if FUSION_WEAVER + +using System; +using System.Linq; +using System.Collections.Generic; +using UnityEngine; +using Fusion; +using TiltBrush; + +namespace OpenBrush.Multiplayer +{ + public class PhotonRPC : SimulationBehaviour + { + private static Dictionary m_inProgressStrokes; + private static List m_pendingCommands; + + public void Awake() + { + m_inProgressStrokes = new(); + m_pendingCommands = new(); + } + + public void Update() + { + TryProcessCommands(); + } + + private bool CheckifChildStillPending(PendingCommand pending) + { + if (pending.TotalExpectedChildren == pending.Command.ChildrenCount) + { + bool moreChildrenToAssign = false; + + foreach (var childCommand in pending.Command.Children) + { + // has a child present in the pending queue, check them too + var childPending = m_pendingCommands.FirstOrDefault(x => x.Guid == childCommand.Guid); + + if (!childPending.Guid.Equals(default)) + { + var childIsStillPending = CheckifChildStillPending(childPending); + + if (!childIsStillPending) + { + m_pendingCommands.Remove(childPending); + } + + moreChildrenToAssign |= childIsStillPending; + } + } + + return moreChildrenToAssign; + } + + else + { + return true; + } + } + + private void InvokePreCommands(PendingCommand pendingCommand) + { + pendingCommand.PreCommandAction.Invoke(); + + foreach (var childCommand in pendingCommand.ChildCommands) + { + InvokePreCommands(childCommand); + } + } + + private void TryProcessCommands() + { + if (m_pendingCommands.Count == 0) + { + return; + } + + var command = m_pendingCommands[0]; + + bool stillPending = CheckifChildStillPending(command); + + if (stillPending) + { + return; + } + + // All children present, begin execution + + m_pendingCommands.RemoveAt(0); + + InvokePreCommands(command); + + + SketchMemoryScript.m_Instance.PerformAndRecordCommand(command.Command, invoke: false); + + TryProcessCommands(); + } + + private static void AddPendingCommand(Action preAction, Guid commandGuid, Guid parentGuid, BaseCommand command, int childCount) + { + PendingCommand pendingCommand = new PendingCommand(commandGuid, command, preAction, childCount); + + if (!parentGuid.Equals(default)) + { + var pendingParent = m_pendingCommands.FirstOrDefault(x => x.Guid == parentGuid); + pendingParent.ChildCommands.Add(pendingCommand); + } + + m_pendingCommands.Add(pendingCommand); + } + + private static BaseCommand FindParentCommand(Guid parentGuid) + { + PendingCommand pendingParent = m_pendingCommands.FirstOrDefault(x => x.Guid == parentGuid); + + if (!parentGuid.Equals(default)) + { + return pendingParent.Command; + } + return null; + } + + public static void CreateBrushStroke(Stroke stroke, Guid commandGuid, Guid parentGuid = default, int childCount = 0) + { + Action preAction = () => + { + stroke.m_Type = Stroke.Type.NotCreated; + stroke.m_IntendedCanvas = App.Scene.MainCanvas; + stroke.Recreate(null, App.Scene.MainCanvas); + SketchMemoryScript.m_Instance.MemoryListAdd(stroke); + }; + + var parentCommand = FindParentCommand(parentGuid); + + var command = new BrushStrokeCommand(stroke, parent: parentCommand); + + AddPendingCommand(preAction, commandGuid, parentGuid, command, childCount); + } + +#region RPCS + [Rpc(InvokeLocal = false)] + public static void RPC_SyncToSharedAnchor(NetworkRunner runner, string uuid) + { + OculusMRController.m_Instance.RemoteSyncToAnchor(uuid); + } + + [Rpc(InvokeLocal = false)] + public static void RPC_PerformCommand(NetworkRunner runner, string commandName, string guid, string[] data) + { + Debug.Log($"Command recieved: {commandName}"); + if (commandName.Equals("TiltBrush.BrushStrokeCommand")) + { + var asString = string.Join(string.Empty, data); + Debug.Log(asString); + var decode = JsonUtility.FromJson(asString); + + // Temp + decode.m_BrushGuid = new System.Guid(guid); + + // Can we set up these more sensibly? + decode.m_Type = Stroke.Type.NotCreated; + decode.m_IntendedCanvas = App.Scene.MainCanvas; + + // Setup data that couldn't be transferred + decode.Recreate(null, App.Scene.MainCanvas); + SketchMemoryScript.m_Instance.MemoryListAdd(decode); + + SketchMemoryScript.m_Instance.PerformAndRecordCommand(new BrushStrokeCommand(decode), invoke: false); + } + } + + [Rpc(InvokeLocal = false)] + public static void RPC_Undo(NetworkRunner runner, string commandName) + { + if (SketchMemoryScript.m_Instance.CanUndo()) + { + SketchMemoryScript.m_Instance.StepBack(false); + } + } + + [Rpc(InvokeLocal = false)] + public static void RPC_Redo(NetworkRunner runner, string commandName) + { + if (SketchMemoryScript.m_Instance.CanRedo()) + { + SketchMemoryScript.m_Instance.StepForward(false); + } + } + + [Rpc(InvokeLocal = false)] + public static void RPC_BaseCommand(NetworkRunner runner, Guid commandGuid, Guid parentGuid = default, int childCount = 0) + { + Debug.Log($"Base command child count: {childCount}"); + var parentCommand = FindParentCommand(parentGuid); + var command = new BaseCommand(parent: parentCommand); + + AddPendingCommand(() => {}, commandGuid, parentGuid, command, childCount); + } + + [Rpc(InvokeLocal = false)] + public static void RPC_BrushStrokeFull(NetworkRunner runner, NetworkedStroke strokeData, Guid commandGuid, Guid parentGuid = default, int childCount = 0) + { + var decode = NetworkedStroke.ToStroke(strokeData); + + CreateBrushStroke(decode, commandGuid, parentGuid, childCount); + } + + [Rpc(InvokeLocal = false)] + public static void RPC_BrushStrokeBegin(NetworkRunner runner, Guid id, NetworkedStroke strokeData, int finalLength) + { + var decode = NetworkedStroke.ToStroke(strokeData); + + decode.m_Type = Stroke.Type.NotCreated; + decode.m_IntendedCanvas = App.Scene.MainCanvas; + + Array.Resize(ref decode.m_ControlPoints, finalLength); + Array.Resize(ref decode.m_ControlPointsToDrop, finalLength); + + if(m_inProgressStrokes.ContainsKey(id)) + { + Debug.LogError("Shouldn't be here!"); + return; + } + + m_inProgressStrokes[id] = decode; + } + + [Rpc(InvokeLocal = false)] + public static void RPC_BrushStrokeContinue(NetworkRunner runner, Guid id, int offset, NetworkedControlPoint[] controlPoints, bool[] dropPoints) + { + if(!m_inProgressStrokes.ContainsKey(id)) + { + Debug.LogError("shouldn't be here!"); + return; + } + + var stroke = m_inProgressStrokes[id]; + + for(int i = 0; i < controlPoints.Length; ++i) + { + stroke.m_ControlPoints[offset + i] = NetworkedControlPoint.ToControlPoint(controlPoints[i]); + stroke.m_ControlPointsToDrop[offset + i] = dropPoints[i]; + } + } + + [Rpc(InvokeLocal = false)] + public static void RPC_BrushStrokeComplete(NetworkRunner runner, Guid id, Guid commandGuid, Guid parentGuid = default, int childCount = 0) + { + if(!m_inProgressStrokes.ContainsKey(id)) + { + Debug.LogError("shouldn't be here!"); + return; + } + + var stroke = m_inProgressStrokes[id]; + + CreateBrushStroke(stroke, commandGuid, parentGuid, childCount); + + m_inProgressStrokes.Remove(id); + } + + [Rpc(InvokeLocal = false)] + public static void RPC_DeleteStroke(NetworkRunner runner, int seed, Guid commandGuid, Guid parentGuid = default, int childCount = 0) + { + var foundStroke = SketchMemoryScript.m_Instance.GetMemoryList.Where(x => x.m_Seed == seed).First(); + + if (foundStroke != null) + { + var parentCommand = FindParentCommand(parentGuid); + var command = new DeleteStrokeCommand(foundStroke, parent: parentCommand); + + AddPendingCommand(() => {}, commandGuid, parentGuid, command, childCount); + } + else + { + Debug.LogError($"couldn't find stroke with seed: {seed}"); + } + } +#endregion + } +} + +#endif // FUSION_WEAVER diff --git a/Assets/Scripts/Multiplayer/Photon/PhotonRPC.cs.meta b/Assets/Scripts/Multiplayer/Photon/PhotonRPC.cs.meta new file mode 100644 index 0000000000..6b532f35b6 --- /dev/null +++ b/Assets/Scripts/Multiplayer/Photon/PhotonRPC.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 411fb1979ee6c644cbe60df50efacfd7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Multiplayer/Photon/PhotonStructs.cs b/Assets/Scripts/Multiplayer/Photon/PhotonStructs.cs new file mode 100644 index 0000000000..10d6787740 --- /dev/null +++ b/Assets/Scripts/Multiplayer/Photon/PhotonStructs.cs @@ -0,0 +1,171 @@ +// Copyright 2023 The Open Brush Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#if FUSION_WEAVER + +using System; +using UnityEngine; +using Fusion; +using TiltBrush; +using System.Collections.Generic; + +namespace OpenBrush.Multiplayer +{ + public struct PendingCommand + { + public int TotalExpectedChildren; + + public Guid Guid; + public BaseCommand Command; + public Action PreCommandAction; + public List ChildCommands; + + public PendingCommand(Guid guid, BaseCommand command, Action action, int count) + { + Guid = guid; + Command = command; + PreCommandAction = action; + TotalExpectedChildren = count; + ChildCommands = new List(); + } + } + + public struct NetworkCommandData : INetworkStruct + { + public Guid CommandGuid; + public Guid ParentGuid; + public int ChildCount; + + public NetworkCommandData(Guid commandGuid, Guid parentGuid = default, int childCount = 0) + { + CommandGuid = commandGuid; + ParentGuid = parentGuid; + ChildCount = childCount; + } + } + + public struct NetworkedControlPoint : INetworkStruct + { + public Vector3 m_Pos; + public Quaternion m_Orient; + + public const uint EXTENSIONS = (uint)( + SketchWriter.ControlPointExtension.Pressure | + SketchWriter.ControlPointExtension.Timestamp); + public float m_Pressure; + public uint m_TimestampMs; // CurrentSketchTime of creation, in milliseconds + + public NetworkedControlPoint Init(PointerManager.ControlPoint point) + { + m_Pos = point.m_Pos; + m_Orient = point.m_Orient; + m_Pressure = point.m_Pressure; + m_TimestampMs = point.m_TimestampMs; + + return this; + } + + internal static PointerManager.ControlPoint ToControlPoint(NetworkedControlPoint networkedControlPoint) + { + var point = new PointerManager.ControlPoint + { + m_Pos = networkedControlPoint.m_Pos, + m_Orient = networkedControlPoint.m_Orient, + m_Pressure = networkedControlPoint.m_Pressure, + m_TimestampMs = networkedControlPoint.m_TimestampMs + }; + + return point; + } + } + + [System.Serializable] + public struct NetworkedStroke : INetworkStruct + { + public const int k_MaxCapacity = 128; + public Stroke.Type m_Type; + [Networked][Capacity(k_MaxCapacity)] public NetworkArray m_ControlPointsToDrop => default; + public Color m_Color; + public Guid m_BrushGuid; + // The room-space size of the brush when the stroke was laid down + public float m_BrushSize; + // The size of the pointer, relative to when the stroke was laid down. + // AKA, the "pointer to local" scale factor. + // m_BrushSize * m_BrushScale = size in local/canvas space + public float m_BrushScale; + [Networked][Capacity(k_MaxCapacity)] public NetworkArray m_ControlPoints => default; + + // Use for determining length. + public int m_ControlPointsCapacity; + // Seed for deterministic pseudo-random numbers for geometry generation. + // Not currently serialized. + public int m_Seed; + + public static Stroke ToStroke(NetworkedStroke netStroke) + { + var stroke = new Stroke + { + m_Type = Stroke.Type.NotCreated, + m_IntendedCanvas = App.Scene.MainCanvas, + m_BrushGuid = netStroke.m_BrushGuid, + m_BrushScale = netStroke.m_BrushScale, + m_BrushSize = netStroke.m_BrushSize, + m_Color = netStroke.m_Color, + m_Seed = netStroke.m_Seed, + m_ControlPoints = new PointerManager.ControlPoint[netStroke.m_ControlPointsCapacity], + m_ControlPointsToDrop = new bool[netStroke.m_ControlPointsCapacity] + }; + + for (int i = 0; i < netStroke.m_ControlPointsCapacity; ++i) + { + var point = NetworkedControlPoint.ToControlPoint(netStroke.m_ControlPoints[i]); + stroke.m_ControlPoints[i] = point; + } + + for (int i = 0; i < netStroke.m_ControlPointsCapacity; ++i) + { + stroke.m_ControlPointsToDrop[i] = netStroke.m_ControlPointsToDrop[i]; + } + + return stroke; + } + + public NetworkedStroke Init(Stroke data) + { + m_Type = data.m_Type; + m_BrushGuid = data.m_BrushGuid; + m_BrushScale = data.m_BrushScale; + m_BrushSize = data.m_BrushSize; + m_Color = data.m_Color; + m_Seed = data.m_Seed; + + m_ControlPointsCapacity = data.m_ControlPoints.Length; + + for(int i = 0; i < data.m_ControlPoints.Length; i++) + { + var point = new NetworkedControlPoint().Init(data.m_ControlPoints[i]); + m_ControlPoints.Set(i, point); + } + + for(int i = 0; i < data.m_ControlPointsToDrop.Length; i++) + { + m_ControlPointsToDrop.Set(i, data.m_ControlPointsToDrop[i]); + } + + return this; + } + } +} + +#endif // FUSION_WEAVER diff --git a/Assets/Scripts/Multiplayer/Photon/PhotonStructs.cs.meta b/Assets/Scripts/Multiplayer/Photon/PhotonStructs.cs.meta new file mode 100644 index 0000000000..ab108b277b --- /dev/null +++ b/Assets/Scripts/Multiplayer/Photon/PhotonStructs.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e9097fe8b260d0b47935c11b12db0424 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/PassthroughManager.cs b/Assets/Scripts/PassthroughManager.cs index 84621e649e..3663d5c0a6 100644 --- a/Assets/Scripts/PassthroughManager.cs +++ b/Assets/Scripts/PassthroughManager.cs @@ -1,5 +1,17 @@ -using System.Collections; -using System.Collections.Generic; +// Copyright 2022-2023 The Open Brush Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + using UnityEngine; namespace TiltBrush @@ -9,10 +21,9 @@ public class PassthroughManager : MonoBehaviour void Start() { #if OCULUS_SUPPORTED - var passthrough = gameObject.AddComponent(); - passthrough.overlayType = OVROverlay.OverlayType.Underlay; -#endif // OCULUS_SUPPORTED + var passthrough = gameObject.AddComponent(); + passthrough.overlayType = OVROverlay.OverlayType.Underlay; +#endif // OCULUS_SUPPORTED } } } - diff --git a/Assets/Scripts/PointerManager.cs b/Assets/Scripts/PointerManager.cs index 2a01901a87..95fda8ab87 100644 --- a/Assets/Scripts/PointerManager.cs +++ b/Assets/Scripts/PointerManager.cs @@ -106,6 +106,7 @@ public struct ColorShiftComponentSetting // The layout should match the most commonly-seen layout in the binary file. // See SketchMemoryScript.ReadMemory. [StructLayout(LayoutKind.Sequential, Pack = 1)] + [System.Serializable] public struct ControlPoint { public Vector3 m_Pos; @@ -198,6 +199,8 @@ public Transform SymmetryWidget /// active simultaneously. eg, 4-way symmetry is not allowed during timeline edit mode; /// floating-panel mode doesn't actually _use_ the Wand's pointer, etc. private PointerData[] m_Pointers; + + private List m_RemoteUserPointers; private bool m_InPlaybackMode; private PointerData m_MainPointerData; @@ -388,6 +391,15 @@ public PointerScript GetTransientPointer(int i) return m_Pointers[NumUserPointers + i].m_Script; } + public PointerScript CreateRemotePointer() + { + GameObject obj = (GameObject)Instantiate(m_AuxPointerPrefab, transform, true); + var script = obj.GetComponent(); + script.ChildIndex = m_RemoteUserPointers.Count - 1; + m_RemoteUserPointers.Add(script); + return script; + } + /// The brush size, using "normalized" values in the range [0,1]. /// Guaranteed to be in [0,1]. public float GetPointerBrushSize01(InputManager.ControllerName controller) @@ -469,6 +481,7 @@ void Awake() Debug.Assert(m_MaxPointers > 0); m_Pointers = new PointerData[m_MaxPointers]; + m_RemoteUserPointers = new List(); m_CustomMirrorMatrices = new List(); for (int i = 0; i < m_Pointers.Length; ++i) @@ -609,6 +622,11 @@ void Update() SetPointersRenderingEnabled(false); DisablePointerPreviewLine(); } + + for (int i = 0; i < m_RemoteUserPointers.Count; ++i) + { + m_RemoteUserPointers[i].UpdatePointer(); + } } public void StoreBrushInfo() diff --git a/Assets/Scripts/SecretsConfig.cs b/Assets/Scripts/SecretsConfig.cs index b3f9fa2c32..6b494ceabf 100644 --- a/Assets/Scripts/SecretsConfig.cs +++ b/Assets/Scripts/SecretsConfig.cs @@ -25,7 +25,8 @@ public enum Service Sketchfab = 1, Oculus = 2, OculusMobile = 3, - Pimax = 4 + Pimax = 4, + PhotonFusion = 5, } [Serializable] diff --git a/Assets/Scripts/SketchMemoryScript.cs b/Assets/Scripts/SketchMemoryScript.cs index 1e85ff1a53..992e290168 100644 --- a/Assets/Scripts/SketchMemoryScript.cs +++ b/Assets/Scripts/SketchMemoryScript.cs @@ -36,6 +36,9 @@ public class SketchMemoryScript : MonoBehaviour public static SketchMemoryScript m_Instance; public event Action OperationStackChanged; + public Action CommandPerformed; + public Action CommandUndo; + public Action CommandRedo; public GameObject m_UndoBatchMeshPrefab; public GameObject m_UndoBatchMesh; @@ -340,7 +343,7 @@ void Update() PerformAndRecordCommand(m_RepaintStrokeParent); m_RepaintStrokeParent = null; } - OperationStackChanged(); + OperationStackChanged?.Invoke(); } } @@ -371,7 +374,7 @@ public Stroke DuplicateStroke(Stroke srcStroke, CanvasScript canvas, TrTransform return duplicate; } - public void PerformAndRecordCommand(BaseCommand command, bool discardIfNotMerged = false) + public void PerformAndRecordCommand(BaseCommand command, bool discardIfNotMerged = false, bool invoke = true) { SketchSurfacePanel.m_Instance.m_LastCommand = command; bool discardCommand = discardIfNotMerged; @@ -395,7 +398,12 @@ public void PerformAndRecordCommand(BaseCommand command, bool discardIfNotMerged } delta.Redo(); m_OperationStack.Push(command); - OperationStackChanged(); + OperationStackChanged?.Invoke(); + + if (invoke) + { + CommandPerformed?.Invoke(command); + } } // TODO: deprecate in favor of PerformAndRecordCommand @@ -414,7 +422,8 @@ public void RecordCommand(BaseCommand command) command = top; } m_OperationStack.Push(command); - OperationStackChanged(); + OperationStackChanged?.Invoke(); + CommandPerformed?.Invoke(command); } /// Returns approximate latest timestamp from the stroke list (including deleted strokes). @@ -788,7 +797,7 @@ public void ClearMemory() } } m_OperationStack.Clear(); - if (OperationStackChanged != null) { OperationStackChanged(); } + OperationStackChanged?.Invoke(); m_LastOperationStackCount = 0; m_MemoryList.Clear(); App.GroupManager.ResetGroups(); @@ -904,20 +913,30 @@ public IEnumerator RepaintCoroutine() m_RepaintCoroutine = null; } - public void StepBack() + public void StepBack(bool invoke = true) { var comm = m_OperationStack.Pop(); comm.Undo(); m_RedoStack.Push(comm); - OperationStackChanged(); + OperationStackChanged?.Invoke(); + + if (invoke) + { + CommandUndo?.Invoke(comm); + } } - public void StepForward() + public void StepForward(bool invoke = true) { var comm = m_RedoStack.Pop(); comm.Redo(); m_OperationStack.Push(comm); - OperationStackChanged(); + OperationStackChanged?.Invoke(); + + if (invoke) + { + CommandRedo?.Invoke(comm); + } } public static IEnumerable AllStrokes() diff --git a/Assets/Scripts/Stroke.cs b/Assets/Scripts/Stroke.cs index 796a840b80..209e5c1886 100644 --- a/Assets/Scripts/Stroke.cs +++ b/Assets/Scripts/Stroke.cs @@ -20,7 +20,7 @@ namespace TiltBrush { - + [System.Serializable] public class Stroke : StrokeData { public enum Type diff --git a/Assets/Scripts/StrokeData.cs b/Assets/Scripts/StrokeData.cs index b0921dce36..26ec4f41a3 100644 --- a/Assets/Scripts/StrokeData.cs +++ b/Assets/Scripts/StrokeData.cs @@ -17,7 +17,7 @@ namespace TiltBrush { - + [System.Serializable] public class StrokeData { public Color m_Color; diff --git a/Assets/Scripts/VrSdk.cs b/Assets/Scripts/VrSdk.cs index 537344916e..5018afb2d5 100644 --- a/Assets/Scripts/VrSdk.cs +++ b/Assets/Scripts/VrSdk.cs @@ -184,6 +184,18 @@ void Awake() var cameraRig = m_VrSystem.AddComponent(); //Disable the OVRCameraRig's eye cameras, since Open Brush already has its own. cameraRig.disableEyeAnchorCameras = true; + + //Get Oculus ID + var appId = App.Config.OculusSecrets.ClientId; +#if UNITY_ANDROID + appId = App.Config.OculusMobileSecrets.ClientId; +#endif + + if (Unity.XR.Oculus.Utils.GetSystemHeadsetType() != Unity.XR.Oculus.SystemHeadset.Oculus_Quest) + { + Oculus.Platform.Core.Initialize(appId); + } + #endif // OCULUS_SUPPORTED #if PIMAX_SUPPORTED diff --git a/Assets/XR/Settings/OpenXR Editor Settings.asset b/Assets/XR/Settings/OpenXR Editor Settings.asset index 1cb89fc3b2..0e4babc9a7 100644 --- a/Assets/XR/Settings/OpenXR Editor Settings.asset +++ b/Assets/XR/Settings/OpenXR Editor Settings.asset @@ -12,5 +12,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 975057b4fdcfb8142b3080d19a5cc712, type: 3} m_Name: OpenXR Editor Settings m_EditorClassIdentifier: - Keys: - Values: [] + Keys: 0100000007000000 + Values: + - featureSets: [] + - featureSets: [] diff --git a/Packages/manifest.json b/Packages/manifest.json index 15f6fae119..5b3b01af2e 100644 --- a/Packages/manifest.json +++ b/Packages/manifest.json @@ -1,7 +1,8 @@ { "dependencies": { "com.ixxy.unitysymmetry": "https://github.com/IxxyXR/unity-symmetry.git?nocache=7#upm", - "com.meta.xr.sdk.utilities": "https://github.com/icosa-mirror/com.meta.xr.sdk.utilities.git#open-brush", + "com.meta.xr.sdk.platform": "56.0.0-preview", + "com.meta.xr.sdk.utilities": "https://github.com/icosa-mirror/com.meta.xr.sdk.core.git#56.0.0-openbrush", "com.unity.2d.sprite": "1.0.0", "com.unity.2d.tilemap": "1.0.0", "com.unity.cloud.gltfast": "6.0.1", @@ -12,6 +13,7 @@ "com.unity.inputsystem": "https://github.com/icosa-mirror/com.unity.inputsystem.git#open-brush", "com.unity.localization": "1.4.2", "com.unity.mobile.android-logcat": "1.3.2", + "com.unity.nuget.mono-cecil": "1.10.2", "com.unity.performance.profile-analyzer": "1.2.2", "com.unity.test-framework": "1.1.33", "com.unity.textmeshpro": "3.0.6", diff --git a/Packages/packages-lock.json b/Packages/packages-lock.json index be6f33d4e3..793c3d8c24 100644 --- a/Packages/packages-lock.json +++ b/Packages/packages-lock.json @@ -13,12 +13,22 @@ "dependencies": {}, "hash": "4f87195d54ccefe076678cb83a296b3f0428fc53" }, + "com.meta.xr.sdk.platform": { + "version": "56.0.0-preview", + "depth": 0, + "source": "registry", + "dependencies": { + "com.meta.xr.sdk.utilities": "56.0.0-preview", + "com.unity.ugui": "1.0.0" + }, + "url": "https://npm.developer.oculus.com" + }, "com.meta.xr.sdk.utilities": { - "version": "https://github.com/icosa-mirror/com.meta.xr.sdk.utilities.git#open-brush", + "version": "https://github.com/icosa-mirror/com.meta.xr.sdk.core.git#56.0.0-openbrush", "depth": 0, "source": "git", "dependencies": {}, - "hash": "79e3da292d15308ab01b14e9487b8b1253dcba84" + "hash": "57bad66c67cd72a31898a1d29dae4d4201ef36c1" }, "com.unity.2d.sprite": { "version": "1.0.0", @@ -141,6 +151,13 @@ "dependencies": {}, "url": "https://packages.unity.com" }, + "com.unity.nuget.mono-cecil": { + "version": "1.10.2", + "depth": 0, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, "com.unity.nuget.newtonsoft-json": { "version": "3.2.1", "depth": 1,