From 8c5d173fba23dc2c0058fe90dcef7a4183447ec0 Mon Sep 17 00:00:00 2001 From: Xinkai Jiang Date: Sun, 29 Dec 2024 15:45:35 +0100 Subject: [PATCH 1/3] change to unity 6 and clean the code --- Assets/IRXRClient/Scenes.meta | 8 - .../IRXRClient/Scenes/IRXRClientSample.unity | 374 --------- .../Scenes/IRXRClientSample.unity.meta | 7 - Assets/IRXRClient/Scripts/IRXRNetManager.cs | 743 +++++++++--------- Assets/IRXRClient/Scripts/LogStreamer.cs | 64 +- Assets/IRXRClient/Scripts/MsgUtils.cs | 109 +++ .../{Singleton.cs.meta => MsgUtils.cs.meta} | 2 +- Assets/IRXRClient/Scripts/NetComponent.cs | 360 +++++++-- Assets/IRXRClient/Scripts/NetworkUtils.cs | 118 +++ ...{Streamer.cs.meta => NetworkUtils.cs.meta} | 2 +- Assets/IRXRClient/Scripts/Singleton.cs | 62 -- Assets/IRXRClient/Scripts/Streamer.cs | 19 - Assets/SceneLoader/Prefabs/SimScene.prefab | 1 + Assets/SceneLoader/Scene/SimScene.unity | 30 +- .../SceneLoader/Scripts/PointCloudLoader.cs | 126 +-- .../Scripts/RigidObjectsController.cs | 100 +-- Assets/SceneLoader/Scripts/SimData.cs | 164 ++-- Assets/SceneLoader/Scripts/SimLoader.cs | 473 ++++++----- Packages/manifest.json | 11 +- Packages/packages-lock.json | 64 +- ProjectSettings/MultiplayerManager.asset | 7 + ProjectSettings/ProjectVersion.txt | 4 +- ProjectSettings/SceneTemplateSettings.json | 97 +-- 23 files changed, 1526 insertions(+), 1419 deletions(-) delete mode 100644 Assets/IRXRClient/Scenes.meta delete mode 100644 Assets/IRXRClient/Scenes/IRXRClientSample.unity delete mode 100644 Assets/IRXRClient/Scenes/IRXRClientSample.unity.meta create mode 100644 Assets/IRXRClient/Scripts/MsgUtils.cs rename Assets/IRXRClient/Scripts/{Singleton.cs.meta => MsgUtils.cs.meta} (83%) create mode 100644 Assets/IRXRClient/Scripts/NetworkUtils.cs rename Assets/IRXRClient/Scripts/{Streamer.cs.meta => NetworkUtils.cs.meta} (83%) delete mode 100644 Assets/IRXRClient/Scripts/Singleton.cs delete mode 100644 Assets/IRXRClient/Scripts/Streamer.cs create mode 100644 ProjectSettings/MultiplayerManager.asset diff --git a/Assets/IRXRClient/Scenes.meta b/Assets/IRXRClient/Scenes.meta deleted file mode 100644 index 1e056f9..0000000 --- a/Assets/IRXRClient/Scenes.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 06f865f542585765b96dd50df811dcfd -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/IRXRClient/Scenes/IRXRClientSample.unity b/Assets/IRXRClient/Scenes/IRXRClientSample.unity deleted file mode 100644 index 2399766..0000000 --- a/Assets/IRXRClient/Scenes/IRXRClientSample.unity +++ /dev/null @@ -1,374 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!29 &1 -OcclusionCullingSettings: - m_ObjectHideFlags: 0 - serializedVersion: 2 - m_OcclusionBakeSettings: - smallestOccluder: 5 - smallestHole: 0.25 - backfaceThreshold: 100 - m_SceneGUID: 00000000000000000000000000000000 - m_OcclusionCullingData: {fileID: 0} ---- !u!104 &2 -RenderSettings: - m_ObjectHideFlags: 0 - serializedVersion: 9 - m_Fog: 0 - m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} - m_FogMode: 3 - m_FogDensity: 0.01 - m_LinearFogStart: 0 - m_LinearFogEnd: 300 - m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} - m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} - m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} - m_AmbientIntensity: 1 - m_AmbientMode: 0 - m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} - m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0} - m_HaloStrength: 0.5 - m_FlareStrength: 1 - m_FlareFadeSpeed: 3 - m_HaloTexture: {fileID: 0} - m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} - m_DefaultReflectionMode: 0 - m_DefaultReflectionResolution: 128 - m_ReflectionBounces: 1 - m_ReflectionIntensity: 1 - m_CustomReflection: {fileID: 0} - m_Sun: {fileID: 705507994} - m_UseRadianceAmbientProbe: 0 ---- !u!157 &3 -LightmapSettings: - m_ObjectHideFlags: 0 - serializedVersion: 12 - m_GIWorkflowMode: 1 - m_GISettings: - serializedVersion: 2 - m_BounceScale: 1 - m_IndirectOutputScale: 1 - m_AlbedoBoost: 1 - m_EnvironmentLightingMode: 0 - m_EnableBakedLightmaps: 1 - m_EnableRealtimeLightmaps: 0 - m_LightmapEditorSettings: - serializedVersion: 12 - m_Resolution: 2 - m_BakeResolution: 40 - m_AtlasSize: 1024 - m_AO: 0 - m_AOMaxDistance: 1 - m_CompAOExponent: 1 - m_CompAOExponentDirect: 0 - m_ExtractAmbientOcclusion: 0 - m_Padding: 2 - m_LightmapParameters: {fileID: 0} - m_LightmapsBakeMode: 1 - m_TextureCompression: 1 - m_FinalGather: 0 - m_FinalGatherFiltering: 1 - m_FinalGatherRayCount: 256 - m_ReflectionCompression: 2 - m_MixedBakeMode: 2 - m_BakeBackend: 1 - m_PVRSampling: 1 - m_PVRDirectSampleCount: 32 - m_PVRSampleCount: 500 - m_PVRBounces: 2 - m_PVREnvironmentSampleCount: 500 - m_PVREnvironmentReferencePointCount: 2048 - m_PVRFilteringMode: 2 - m_PVRDenoiserTypeDirect: 0 - m_PVRDenoiserTypeIndirect: 0 - m_PVRDenoiserTypeAO: 0 - m_PVRFilterTypeDirect: 0 - m_PVRFilterTypeIndirect: 0 - m_PVRFilterTypeAO: 0 - m_PVREnvironmentMIS: 0 - m_PVRCulling: 1 - m_PVRFilteringGaussRadiusDirect: 1 - m_PVRFilteringGaussRadiusIndirect: 5 - m_PVRFilteringGaussRadiusAO: 2 - m_PVRFilteringAtrousPositionSigmaDirect: 0.5 - m_PVRFilteringAtrousPositionSigmaIndirect: 2 - m_PVRFilteringAtrousPositionSigmaAO: 1 - m_ExportTrainingData: 0 - m_TrainingDataDestination: TrainingData - m_LightProbeSampleCountMultiplier: 4 - m_LightingDataAsset: {fileID: 0} - m_LightingSettings: {fileID: 0} ---- !u!196 &4 -NavMeshSettings: - serializedVersion: 2 - m_ObjectHideFlags: 0 - m_BuildSettings: - serializedVersion: 3 - agentTypeID: 0 - agentRadius: 0.5 - agentHeight: 2 - agentSlope: 45 - agentClimb: 0.4 - ledgeDropHeight: 0 - maxJumpAcrossDistance: 0 - minRegionArea: 2 - manualCellSize: 0 - cellSize: 0.16666667 - manualTileSize: 0 - tileSize: 256 - buildHeightMesh: 0 - maxJobWorkers: 0 - preserveTilesOutsideBounds: 0 - debug: - m_Flags: 0 - m_NavMeshData: {fileID: 0} ---- !u!1 &705507993 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 705507995} - - component: {fileID: 705507994} - m_Layer: 0 - m_Name: Directional Light - m_TagString: Untagged - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!108 &705507994 -Light: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 705507993} - m_Enabled: 1 - serializedVersion: 10 - m_Type: 1 - m_Shape: 0 - m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1} - m_Intensity: 1 - m_Range: 10 - m_SpotAngle: 30 - m_InnerSpotAngle: 21.80208 - m_CookieSize: 10 - m_Shadows: - m_Type: 2 - m_Resolution: -1 - m_CustomResolution: -1 - m_Strength: 1 - m_Bias: 0.05 - m_NormalBias: 0.4 - m_NearPlane: 0.2 - m_CullingMatrixOverride: - e00: 1 - e01: 0 - e02: 0 - e03: 0 - e10: 0 - e11: 1 - e12: 0 - e13: 0 - e20: 0 - e21: 0 - e22: 1 - e23: 0 - e30: 0 - e31: 0 - e32: 0 - e33: 1 - m_UseCullingMatrixOverride: 0 - m_Cookie: {fileID: 0} - m_DrawHalo: 0 - m_Flare: {fileID: 0} - m_RenderMode: 0 - m_CullingMask: - serializedVersion: 2 - m_Bits: 4294967295 - m_RenderingLayerMask: 1 - m_Lightmapping: 1 - m_LightShadowCasterMode: 0 - m_AreaSize: {x: 1, y: 1} - m_BounceIntensity: 1 - m_ColorTemperature: 6570 - m_UseColorTemperature: 0 - m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0} - m_UseBoundingSphereOverride: 0 - m_UseViewFrustumForShadowCasterCull: 1 - m_ShadowRadius: 0 - m_ShadowAngle: 0 ---- !u!4 &705507995 -Transform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 705507993} - serializedVersion: 2 - m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261} - m_LocalPosition: {x: 0, y: 3, z: 0} - m_LocalScale: {x: 1, y: 1, z: 1} - m_ConstrainProportionsScale: 0 - m_Children: [] - m_Father: {fileID: 0} - m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0} ---- !u!1 &963194225 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 963194228} - - component: {fileID: 963194227} - - component: {fileID: 963194226} - m_Layer: 0 - m_Name: Main Camera - m_TagString: MainCamera - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!81 &963194226 -AudioListener: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 963194225} - m_Enabled: 1 ---- !u!20 &963194227 -Camera: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 963194225} - m_Enabled: 1 - serializedVersion: 2 - m_ClearFlags: 1 - m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} - m_projectionMatrixMode: 1 - m_GateFitMode: 2 - m_FOVAxisMode: 0 - m_Iso: 200 - m_ShutterSpeed: 0.005 - m_Aperture: 16 - m_FocusDistance: 10 - m_FocalLength: 50 - m_BladeCount: 5 - m_Curvature: {x: 2, y: 11} - m_BarrelClipping: 0.25 - m_Anamorphism: 0 - m_SensorSize: {x: 36, y: 24} - m_LensShift: {x: 0, y: 0} - m_NormalizedViewPortRect: - serializedVersion: 2 - x: 0 - y: 0 - width: 1 - height: 1 - near clip plane: 0.3 - far clip plane: 1000 - field of view: 60 - orthographic: 0 - orthographic size: 5 - m_Depth: -1 - m_CullingMask: - serializedVersion: 2 - m_Bits: 4294967295 - m_RenderingPath: -1 - m_TargetTexture: {fileID: 0} - m_TargetDisplay: 0 - m_TargetEye: 3 - m_HDR: 1 - m_AllowMSAA: 1 - m_AllowDynamicResolution: 0 - m_ForceIntoRT: 0 - m_OcclusionCulling: 1 - m_StereoConvergence: 10 - m_StereoSeparation: 0.022 ---- !u!4 &963194228 -Transform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 963194225} - serializedVersion: 2 - m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 0, y: 1, z: -10} - m_LocalScale: {x: 1, y: 1, z: 1} - m_ConstrainProportionsScale: 0 - m_Children: [] - m_Father: {fileID: 0} - m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!1001 &6431986884477952538 -PrefabInstance: - m_ObjectHideFlags: 0 - serializedVersion: 2 - m_Modification: - serializedVersion: 3 - m_TransformParent: {fileID: 0} - m_Modifications: - - target: {fileID: 980812101842878387, guid: 94d5024eac9ef16668f3a4c00717c811, type: 3} - propertyPath: m_LocalPosition.x - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 980812101842878387, guid: 94d5024eac9ef16668f3a4c00717c811, type: 3} - propertyPath: m_LocalPosition.y - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 980812101842878387, guid: 94d5024eac9ef16668f3a4c00717c811, type: 3} - propertyPath: m_LocalPosition.z - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 980812101842878387, guid: 94d5024eac9ef16668f3a4c00717c811, type: 3} - propertyPath: m_LocalRotation.w - value: 1 - objectReference: {fileID: 0} - - target: {fileID: 980812101842878387, guid: 94d5024eac9ef16668f3a4c00717c811, type: 3} - propertyPath: m_LocalRotation.x - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 980812101842878387, guid: 94d5024eac9ef16668f3a4c00717c811, type: 3} - propertyPath: m_LocalRotation.y - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 980812101842878387, guid: 94d5024eac9ef16668f3a4c00717c811, type: 3} - propertyPath: m_LocalRotation.z - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 980812101842878387, guid: 94d5024eac9ef16668f3a4c00717c811, type: 3} - propertyPath: m_LocalEulerAnglesHint.x - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 980812101842878387, guid: 94d5024eac9ef16668f3a4c00717c811, type: 3} - propertyPath: m_LocalEulerAnglesHint.y - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 980812101842878387, guid: 94d5024eac9ef16668f3a4c00717c811, type: 3} - propertyPath: m_LocalEulerAnglesHint.z - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 1956348136357179381, guid: 94d5024eac9ef16668f3a4c00717c811, type: 3} - propertyPath: m_Name - value: IRXRClient - objectReference: {fileID: 0} - m_RemovedComponents: [] - m_RemovedGameObjects: [] - m_AddedGameObjects: [] - m_AddedComponents: [] - m_SourcePrefab: {fileID: 100100000, guid: 94d5024eac9ef16668f3a4c00717c811, type: 3} ---- !u!1660057539 &9223372036854775807 -SceneRoots: - m_ObjectHideFlags: 0 - m_Roots: - - {fileID: 963194228} - - {fileID: 705507995} - - {fileID: 6431986884477952538} diff --git a/Assets/IRXRClient/Scenes/IRXRClientSample.unity.meta b/Assets/IRXRClient/Scenes/IRXRClientSample.unity.meta deleted file mode 100644 index 952bd1e..0000000 --- a/Assets/IRXRClient/Scenes/IRXRClientSample.unity.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 9fc0d4010bbf28b4594072e72b8655ab -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/IRXRClient/Scripts/IRXRNetManager.cs b/Assets/IRXRClient/Scripts/IRXRNetManager.cs index ab5ed19..08a1674 100644 --- a/Assets/IRXRClient/Scripts/IRXRNetManager.cs +++ b/Assets/IRXRClient/Scripts/IRXRNetManager.cs @@ -1,359 +1,400 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; using NetMQ; using NetMQ.Sockets; using UnityEngine; -using System; using System.Net; -using System.Text; -using Newtonsoft.Json; using System.Net.Sockets; -using System.Collections.Generic; -using System.Net.NetworkInformation; -using System.Threading.Tasks; - - -public enum ClientPort { - Discovery = 7720, - Service = 7730, - Topic = 7731, -} - - -public class HostInfo { - public string name; - public string instance; - public string ip; - public string type; - public string servicePort; - public string topicPort; - public List serviceList = new(); - public List topicList = new(); -} - - -public class IRXRNetManager : Singleton { - public Action OnDisconnected; - public Action OnConnectionStart; - public Action OnDeviceSetup; - // public Action OnServerDiscovered; - public Action ConnectionSpin; - private UdpClient _discoveryClient; - private string _conncetionID = null; - public HostInfo _serverInfo = null; - private HostInfo _localInfo = new HostInfo(); - private List _sockets; - // subscriber socket - private SubscriberSocket _subSocket; - private Dictionary> _topicsCallbacks; - // publisher socket - private PublisherSocket _pubSocket; - private RequestSocket _reqSocket; - // response socket - private ResponseSocket _resSocket; - private Dictionary> _serviceCallbacks; - - private float lastTimeStamp = -1.0f; - private bool isConnected = false; - private float timeOffset = 0.0f; - public float TimeOffset - { - get { return timeOffset; } - set { timeOffset = value; } - } - - - void Awake() { - AsyncIO.ForceDotNet.Force(); - _discoveryClient = new UdpClient((int)ClientPort.Discovery); - _sockets = new List(); - _reqSocket = new RequestSocket(); - // response socket - _resSocket = new ResponseSocket(); - _serviceCallbacks = new Dictionary>(); - // subscriber socket - _subSocket = new SubscriberSocket(); - _topicsCallbacks = new Dictionary>(); - _pubSocket = new PublisherSocket(); - // the collection of all sockets - _sockets = new List { _reqSocket, _resSocket, _subSocket, _pubSocket }; - // Default host name - if (PlayerPrefs.HasKey("HostName")) - { - // The key exists, proceed to get the value - string savedHostName = PlayerPrefs.GetString("HostName"); - _localInfo.name = savedHostName; - Debug.Log($"Find Host Name: {_localInfo.name}"); - } - else - { - // The key does not exist, handle it accordingly - _localInfo.name = "UnityClient"; - Debug.Log($"Host Name not found, using default name {_localInfo.name}"); - } - _localInfo.instance = Guid.NewGuid().ToString(); - _localInfo.type = "UnityClient"; - _localInfo.servicePort = ((int)ClientPort.Service).ToString(); - _localInfo.topicPort = ((int)ClientPort.Topic).ToString(); - } - - void Start() { - // OnServerDiscovered += StartConnection; - // OnServerDiscovered += RegisterInfo2Server; - // OnConnectionStart += RegisterInfo2Server; - OnDeviceSetup?.Invoke(); - OnConnectionStart += () => { isConnected = true; }; - ConnectionSpin += () => {}; - OnDisconnected += StopConnection; - lastTimeStamp = -1.0f; - RegisterServiceCallback("ChangeHostName", ChangeHostName); - } - - async void Update() { - ConnectionSpin?.Invoke(); - if (_discoveryClient.Available == 0) return; // there's no message to read - IPEndPoint endPoint = new IPEndPoint(IPAddress.Any, 0); - byte[] result = _discoveryClient.Receive(ref endPoint); - string message = Encoding.UTF8.GetString(result); - if (!message.StartsWith("SimPub")) return; // not the right tag - var split = message.Split(":", 3); - // check if the message is from the same server - if (_conncetionID != split[1]) { - if (isConnected) OnDisconnected.Invoke(); - _conncetionID = split[1]; - string infoStr = split[2]; - _serverInfo = JsonConvert.DeserializeObject(infoStr); - _serverInfo.ip = endPoint.Address.ToString(); - _localInfo.ip = GetLocalIPsInSameSubnet(_serverInfo.ip); - Debug.Log($"Discovered server at {_serverInfo.ip} with local IP {_localInfo.ip}"); - StartConnection(); - RegisterInfo2Server(); - await Task.Run(() => OnConnectionStart.Invoke()); - } - lastTimeStamp = Time.realtimeSinceStartup; - } - - private void CaculateTimestampOffset() { - float startTimer = Time.realtimeSinceStartup; - float serverTime = float.Parse(RequestString("GetServerTimestamp")); - float endTimer = Time.realtimeSinceStartup; - timeOffset = (startTimer + endTimer) / 2 - serverTime; - float requestDelay = (endTimer - startTimer) / 2 * 1000; - Debug.Log($"Request Delay: {requestDelay} ms"); - } - - void OnApplicationQuit() { - if (isConnected) - { - RequestString("ClientQuit", _localInfo.ip); - }; - _discoveryClient.Dispose(); - foreach (var socket in _sockets) { - socket.Dispose(); - } - // This must be called after all the NetMQ sockets are disposed - NetMQConfig.Cleanup(); - } - - public PublisherSocket GetPublisherSocket() { - return _pubSocket; - } - - // Please use these two request functions to send request to the server. - // It may stuck if the server is not responding, - // which will cause the Unity Editor to freeze. - public string RequestString(string service, string request = "") { - - _reqSocket.SendFrame($"{service}:{request}"); - // string result = _reqSocket.TryReceiveFrame(out bool more); - if (!_reqSocket.TryReceiveFrameString(TimeSpan.FromMilliseconds(5000), out string result, out bool more)) { - Debug.LogWarning("Request Timeout"); - return "Request Timeout"; - } - while(more) result += _reqSocket.ReceiveFrameString(out more); - return result; - - } - - public List RequestBytes(string service, string request = "") { - - _reqSocket.SendFrame($"{service}:{request}"); - if (!_reqSocket.TryReceiveFrameBytes(TimeSpan.FromMilliseconds(10000), out byte[] bytes, out bool more)) { - Debug.LogWarning($"Request Timeout"); - return new List(); - } - - List result = new List(bytes); - result.AddRange(bytes); - while (more) result.AddRange(_reqSocket.ReceiveFrameBytes(out more)); - return result; - } - - public void StartConnection() { - if (isConnected) StopConnection(); - _subSocket.Connect($"tcp://{_serverInfo.ip}:{_serverInfo.topicPort}"); - _subSocket.Subscribe(""); - ConnectionSpin += TopicUpdateSpin; - Debug.Log($"Connected topic to {_serverInfo.ip}:{_serverInfo.topicPort}"); - _resSocket.Bind($"tcp://{_localInfo.ip}:{(int)ClientPort.Service}"); - ConnectionSpin += ServiceRespondSpin; - _reqSocket.Connect($"tcp://{_serverInfo.ip}:{_serverInfo.servicePort}"); - Debug.Log($"Starting service connection to {_serverInfo.ip}:{_serverInfo.servicePort}"); - _pubSocket.Bind($"tcp://{_localInfo.ip}:{(int)ClientPort.Topic}"); - isConnected = true; - CaculateTimestampOffset(); - } - - public void StopConnection() { - if (isConnected) { - _subSocket.Disconnect($"tcp://{_serverInfo.ip}:{_serverInfo.topicPort}"); - while (_subSocket.HasIn) _subSocket.SkipFrame(); - } - ConnectionSpin -= TopicUpdateSpin; - // It is not necessary to clear the topics callbacks - // _topicsCallbacks.Clear(); - _resSocket.Unbind($"tcp://{_localInfo.ip}:{(int)ClientPort.Service}"); - ConnectionSpin -= ServiceRespondSpin; - _pubSocket.Unbind($"tcp://{_localInfo.ip}:{(int)ClientPort.Topic}"); - _reqSocket.Disconnect($"tcp://{_serverInfo.ip}:{_serverInfo.servicePort}"); - isConnected = false; - Debug.Log("Disconnected"); - } - - public void TopicUpdateSpin() { - // Only process the latest message of each topic - Dictionary messageProcessed = new(); - while (_subSocket.HasIn) - { - byte[] byteMsgReceived = _subSocket.ReceiveFrameBytes(); - int colonIndex = -1; - for (int i = 0; i < byteMsgReceived.Length; i++) - { - if (byteMsgReceived[i] == 58) - { - colonIndex = i; - break; - } - } - string topic = Encoding.UTF8.GetString(byteMsgReceived, 0, colonIndex); - if (_topicsCallbacks.ContainsKey(topic)) { - messageProcessed[topic] = byteMsgReceived.AsSpan(colonIndex + 1).ToArray(); - } - } - foreach (var (topic, msg) in messageProcessed) { - _topicsCallbacks[topic](msg); - } - } - - public void ServiceRespondSpin() { - if (!_resSocket.HasIn) return; - string messageReceived = _resSocket.ReceiveFrameString(); - string[] messageSplit = messageReceived.Split(":", 2); - Debug.Log($"Received service request {messageSplit[0]}"); - if (_serviceCallbacks.ContainsKey(messageSplit[0])) { - string response = _serviceCallbacks[messageSplit[0]](messageSplit[1]); - _resSocket.SendFrame(response); - } - else { - Debug.LogWarning($"Service {messageSplit[0]} not found"); - _resSocket.SendFrame("Invalid Service"); - } - } - - public void SubscribeTopic(string topic, Action callback) { - _topicsCallbacks[topic] = callback; - Debug.Log($"Subscribe a new topic {topic}"); - } - - public void UnsubscribeTopic(string topic) { - if (_topicsCallbacks.ContainsKey(topic)) _topicsCallbacks.Remove(topic); - } - - public void CreatePublishTopic(string topic) { - if (_localInfo.topicList.Contains(topic)) Debug.LogWarning($"Topic {topic} already exists"); - _localInfo.topicList.Add(topic); - RegisterInfo2Server(); - } - - public void RegisterInfo2Server() { - if (isConnected) { - string data = JsonConvert.SerializeObject(_localInfo); - RequestString("Register", JsonConvert.SerializeObject(_localInfo)); - } - } - - public void RegisterServiceCallback(string service, Func callback) { - Debug.Log($"Register service {service}"); - _serviceCallbacks[service] = callback; - _localInfo.serviceList.Add(service); - } - - public string GetHostName() { - return _localInfo.name; - } - - public static string GetLocalIPsInSameSubnet(string inputIPAddress) - { - IPAddress inputIP; - if (!IPAddress.TryParse(inputIPAddress, out inputIP)) - { - throw new ArgumentException("Invalid IP address format.", nameof(inputIPAddress)); - } - IPAddress subnetMask = IPAddress.Parse("255.255.255.0"); - // Get all network interfaces - NetworkInterface[] networkInterfaces = NetworkInterface.GetAllNetworkInterfaces(); - foreach (NetworkInterface ni in networkInterfaces) - { - // Get IP properties of the network interface - IPInterfaceProperties ipProperties = ni.GetIPProperties(); - UnicastIPAddressInformationCollection unicastIPAddresses = ipProperties.UnicastAddresses; - foreach (UnicastIPAddressInformation ipInfo in unicastIPAddresses) - { - if (ipInfo.Address.AddressFamily == AddressFamily.InterNetwork) +using IRXR.Utilities; + +namespace IRXR.Node +{ + + public class IRXRNetManager : MonoBehaviour + { + // Singleton instance + public static IRXRNetManager Instance { get; private set; } + // Node information + public NodeInfo localInfo { get; set; } + public NodeInfo masterInfo { get; set; } + // public NodeInfoManager nodeInfoManager { get; private set; } + // UDP Task management + private CancellationTokenSource cancellationTokenSource; + private Task nodeTask; + // Lock for updating action in the main thread + private object updateActionLock = new(); + private Action updateAction; + public Action OnConnectionStart; + public Action OnDisconnected; + // ZMQ Sockets for communication, in this stage, we run them in the main thread + // publisher socket for sending messages to other nodes + public PublisherSocket _pubSocket; + // response socket for service running in the local node + public ResponseSocket _resSocket; + public Dictionary> serviceCallbacks { get; private set; } + // subscriber socket for receiving messages from only master node + private SubscriberSocket _subSocket; + public Dictionary> subscribeCallbacks { get; private set; } + // Request socket for sending service request to only master node + private RequestSocket _reqSocket; + private List _sockets; + public Action ConnectionSpin; + // Status flags + private bool isRunning = false; + private bool isConnected = false; + // Constants + private const int HEARTBEAT_INTERVAL = 500; + // Rename Service + private Service renameService; + + private void Awake() + { + // Singleton pattern + if (Instance != null && Instance != this) + { + Destroy(gameObject); + return; + } + Instance = this; + DontDestroyOnLoad(gameObject); + // Force to use .NET implementation of NetMQ + AsyncIO.ForceDotNet.Force(); + // Initialize local node info + localInfo = new NodeInfo + { + name = "UnityNode", + nodeID = Guid.NewGuid().ToString(), + addr = null, + type = NodeTypes.XR, + servicePort = UnityPortSet.SERVICE, + topicPort = UnityPortSet.TOPIC, + serviceList = new List(), + topicList = new List() + }; + // Default host name + if (PlayerPrefs.HasKey("HostName")) + { + // The key exists, proceed to get the value + string savedHostName = PlayerPrefs.GetString("HostName"); + localInfo.name = savedHostName; + Debug.Log($"Find Host Name: {localInfo.name}"); + } + else + { + // The key does not exist, handle it accordingly + localInfo.name = "UnityNode"; + Debug.Log($"Host Name not found, using default name {localInfo.name}"); + } + // NOTE: Since the NetZMQ setting is initialized in "AsyncIO.ForceDotNet.Force();" + // NOTE: we should initialize the sockets after that + _pubSocket = new PublisherSocket(); + _resSocket = new ResponseSocket(); + _subSocket = new SubscriberSocket(); + _reqSocket = new RequestSocket(); + _sockets = new List() { _pubSocket, _resSocket, _subSocket, _reqSocket }; + serviceCallbacks = new (); + subscribeCallbacks = new (); + cancellationTokenSource = new CancellationTokenSource(); + // Action setting + OnConnectionStart += () => RunOnMainThread(() => StartConnection()); + OnDisconnected += () => RunOnMainThread(() => StopConnection()); + // Initialize the service callbacks + renameService = new Service("Rename", Rename, true); + } + + private void Start() + { + // Start tasks + isRunning = true; + nodeTask = Task.Run(async () => await NodeTask(cancellationTokenSource.Token)); + } + + private void Update() { + ConnectionSpin?.Invoke(); + lock (updateActionLock) { + updateAction?.Invoke(); + updateAction = null; + } + } + + void RunOnMainThread(Action action) { - IPAddress localIP = ipInfo.Address; - // Check if the IP is in the same subnet - if (IsInSameSubnet(inputIP, localIP, subnetMask)) - { - return localIP.ToString();; - } + lock (updateActionLock) + { + updateAction += action; + } } - } - } - return "127.0.0.1"; - } - - private static bool IsInSameSubnet(IPAddress ip1, IPAddress ip2, IPAddress subnetMask) - { - byte[] ip1Bytes = ip1.GetAddressBytes(); - byte[] ip2Bytes = ip2.GetAddressBytes(); - byte[] maskBytes = subnetMask.GetAddressBytes(); - for (int i = 0; i < ip1Bytes.Length; i++) - { - if ((ip1Bytes[i] & maskBytes[i]) != (ip2Bytes[i] & maskBytes[i])) - { - return false; - } - } - return true; - } - - public string ChangeHostName(string name) { - _localInfo.name = name; - PlayerPrefs.SetString("HostName", name); - Debug.Log($"Change Host Name to {_localInfo.name}"); - PlayerPrefs.Save(); - return "Host Name Changed"; - } - - public bool CheckServerService(string serviceName) { - if (!isConnected) return false; - if (_serverInfo == null) return false; - if (_serverInfo.serviceList.Contains(serviceName)) return true; - return false; - } - - public HostInfo GetServerInfo() { - return _serverInfo; - } - -} \ No newline at end of file + private void OnDestroy() + { + isRunning = false; + isConnected = false; + if (cancellationTokenSource != null) + { + cancellationTokenSource.Cancel(); + cancellationTokenSource.Dispose(); + } + nodeTask?.Wait(); + Debug.Log("Task has been stopped safely."); + } + + private void OnApplicationQuit() + { + Debug.Log("Stopping task..."); + StopConnection(); + Debug.Log("Net Manager is being destroyed."); + foreach (var sock in _sockets) + { + sock?.Dispose(); + } + NetMQConfig.Cleanup(); + } + + public void StartConnection() { + // if (isConnected) StopConnection(); + // subscription + _subSocket.Connect($"tcp://{masterInfo.addr.ip}:{masterInfo.topicPort}"); + _subSocket.Subscribe(""); + ConnectionSpin += SubscriptionSpin; + Debug.Log($"Start subscribing to {masterInfo.addr.ip}:{masterInfo.topicPort}"); + // local service + _resSocket.Bind($"tcp://{localInfo.addr.ip}:{UnityPortSet.SERVICE}"); + ConnectionSpin += ServiceRespondSpin; + Debug.Log($"Starting service connection at {localInfo.addr.ip}:{UnityPortSet.SERVICE}"); + // request to master node + _reqSocket.Connect($"tcp://{masterInfo.addr.ip}:{masterInfo.servicePort}"); + Debug.Log($"Starting service connection to {masterInfo.addr.ip}:{masterInfo.servicePort}"); + // local publish + _pubSocket.Bind($"tcp://{localInfo.addr.ip}:{UnityPortSet.TOPIC}"); + Debug.Log($"Starting publish topic at {localInfo.addr.ip}:{UnityPortSet.TOPIC}"); + // CalculateTimestampOffset(); + } + + public void StopConnection() { + while (_subSocket.HasIn) _subSocket.SkipFrame(); + ConnectionSpin = () => { }; + // It is not necessary to clear the topics callbacks + // _topicsCallbacks.Clear(); + _resSocket.Unbind($"tcp://{localInfo.addr.ip}:{UnityPortSet.SERVICE}"); + _pubSocket.Unbind($"tcp://{localInfo.addr.ip}:{UnityPortSet.TOPIC}"); + _reqSocket.Disconnect($"tcp://{masterInfo.addr.ip}:{masterInfo.servicePort}"); + _subSocket.Disconnect($"tcp://{masterInfo.addr.ip}:{masterInfo.topicPort}"); + Debug.Log("Disconnected"); + } + + public async Task NodeTask(CancellationToken token) + { + Debug.Log("Node task starts and listening for master node..."); + while (isRunning) + { + try + { + var nodeInfo = await SearchForMasterNode(token, 500); + if (nodeInfo is not null) + { + masterInfo = nodeInfo; + OnConnectionStart?.Invoke(); + isConnected = true; + Debug.Log("Master node found and ready to send heartbeat."); + await HeartbeatLoop(token, 200); + } + await Task.Delay(500, token); + } + catch (TaskCanceledException) + { + Debug.Log("Task was cancelled via exception"); + } + catch (Exception e) + { + Debug.LogWarning($"Error in NodeTask: {e.StackTrace}"); + break; + } + } + Debug.Log("Node task stopped."); + } + + public async Task SearchForMasterNode(CancellationToken token, int timeout) + { + NodeInfo nodeInfo = null; + Debug.Log("Searching for the master node..."); + try + { + while (isRunning) + { + // Now sure why we need to create a new udp client every time + // If not it wouldn't receive the response when the master node started later + UdpClient udpClient = NetworkUtils.CreateUDPClient(UnityPortSet.HEARTBEAT); + udpClient.EnableBroadcast = true; + // Debug.Log("Sending ping message..."); + byte[] pingMessage = EchoHeader.PING; + // await send so we don't need to worry about broadcasting in the loop by mistake + await udpClient.SendAsync(EchoHeader.PING, 1, new IPEndPoint(IPAddress.Broadcast, UnityPortSet.DISCOVERY)); + // waiting for receive of ping + var receiveTask = udpClient.ReceiveAsync(); + if (await Task.WhenAny(receiveTask, Task.Delay(timeout, token)) == receiveTask) + { + // Debug.Log("Received ping response."); + var response = receiveTask.Result; + byte[][] msgSeparated = MsgUtils.SplitByte(response.Buffer); + if (msgSeparated[1] == null) + { + continue; + } + nodeInfo = MsgUtils.BytesDeserialize2Object(msgSeparated[0]); + localInfo.addr = MsgUtils.BytesDeserialize2Object(msgSeparated[1]); + Debug.Log($"Found master node at {nodeInfo.addr.ip}:{nodeInfo.addr.port}"); + udpClient.Close(); + break; + } + udpClient.Close(); + } + } + catch (SocketException) + { + Debug.Log("No response, retrying..."); + } + catch (TaskCanceledException) + { + Debug.Log("Task was cancelled via exception"); + } + catch (Exception e) + { + Debug.LogWarning($"Error during master node discovery: {e.StackTrace}"); + } + return nodeInfo; + } + + public async Task HeartbeatLoop(CancellationToken token, int timeout) + { + UdpClient udpClient = NetworkUtils.CreateUDPClient(new IPEndPoint(IPAddress.Any, UnityPortSet.HEARTBEAT)); + IPEndPoint masterEndPoint = new IPEndPoint(IPAddress.Parse(masterInfo.addr.ip), masterInfo.addr.port); + // Start the update info loop + Debug.Log($"The Net Manager starts heartbeat at {localInfo.addr.ip}:{localInfo.addr.port}"); + while (isConnected) + { + try + { + byte[] heartbeatMessage = MsgUtils.GenerateHeartbeat(localInfo); + await udpClient.SendAsync(heartbeatMessage, heartbeatMessage.Length, masterEndPoint); + var receiveTask = udpClient.ReceiveAsync(); + // If didn't receive the heartbeat response within timeout + if (await Task.WhenAny(receiveTask, Task.Delay(timeout, token)) != receiveTask) + { + Debug.LogWarning("Timeout: The master node is offline"); + throw new SocketException(); + } + var result = await receiveTask; + NodeInfo nodeInfo = MsgUtils.BytesDeserialize2Object(result.Buffer); + if (nodeInfo.nodeID != masterInfo.nodeID) + { + Debug.Log("The master node has been changed, restarting a new connection"); + throw new SocketException(); + } + // Debug.Log($"Sending heartbeat to {masterInfo.addr.ip}:{masterInfo.addr.port}"); + await Task.Delay(HEARTBEAT_INTERVAL); + } + catch (SocketException ex) + { + Debug.Log($"SocketException: {ex.Message}"); + OnDisconnected?.Invoke(); + isConnected = false; + break; + } + catch (TaskCanceledException) + { + Debug.Log("Task was cancelled via exception"); + OnDisconnected?.Invoke(); + isConnected = false; + break; + } + catch (Exception e) + { + Debug.LogWarning($"Failed to send heartbeat: {e.StackTrace}"); + } + } + udpClient.Close(); + Debug.Log("Heartbeat loop has been stopped, waiting for other master node."); + } + + public void SubscriptionSpin() + { + // Only process the latest message of each topic + Dictionary messageProcessed = new(); + while (_subSocket.HasIn) + { + byte[][] msgSeparated = MsgUtils.SplitByte(_subSocket.ReceiveFrameBytes()); + string topic_name = MsgUtils.Bytes2String(msgSeparated[0]); + if (subscribeCallbacks.ContainsKey(topic_name)) + { + // Debug.Log($"Received message from {topic_name}"); + messageProcessed[topic_name] = msgSeparated[1]; + } + } + foreach (var (topic_name, msg) in messageProcessed) + { + subscribeCallbacks[topic_name](msg); + } + } + + public void ServiceRespondSpin() + { + if (!_resSocket.HasIn) return; + // TODO: make it as a byte array + // TODO: make it running in the sub thread + // now we need to carefully handle the service request + // make sure that it would not block the main thread + byte[] messageReceived = _resSocket.ReceiveFrameBytes(); + byte[][] messageSplit = MsgUtils.SplitByte(messageReceived); + string serviceName = MsgUtils.Bytes2String(messageSplit[0]); + if (serviceCallbacks.ContainsKey(serviceName)) + { + byte[] response = serviceCallbacks[serviceName](messageSplit[1]); + _resSocket.SendFrame(response); + } + else + { + Debug.LogWarning($"Service {serviceName} not found"); + _resSocket.SendFrame(IRXRSignal.NOSERVICE); + } + } + + // TODO: make it as a generic request type + public byte[] CallBytesService(string service_name, string request) + { + _reqSocket.SendFrame($"{service_name}{MsgUtils.SEPARATOR}{request}"); + if (!_reqSocket.TryReceiveFrameBytes(TimeSpan.FromMilliseconds(10000), out byte[] bytes, out bool more)) { + Debug.LogWarning($"Request Timeout"); + return new byte[] { }; + } + List result = new List(bytes); + result.AddRange(bytes); + while (more) result.AddRange(_reqSocket.ReceiveFrameBytes(out more)); + return result.ToArray(); + } + + public ResponseType CallService(string serviceName, RequestType request) + { + byte[] requestBytes = MsgUtils.Serialize2Byte(request); + byte[] responseBytes = CallBytesService(serviceName, Encoding.UTF8.GetString(requestBytes)); + if (typeof(ResponseType) == typeof(string)) + { + string result = Encoding.UTF8.GetString(responseBytes); + return (ResponseType)(object)result; + } + return MsgUtils.BytesDeserialize2Object(responseBytes); + } + + // TODO: make it as a generic request type + public string Rename(string newName) + { + localInfo.name = newName; + PlayerPrefs.SetString("HostName", localInfo.name); + Debug.Log($"Change Host Name to {localInfo.name}"); + PlayerPrefs.Save(); + return IRXRSignal.SUCCESS; + } + } +} diff --git a/Assets/IRXRClient/Scripts/LogStreamer.cs b/Assets/IRXRClient/Scripts/LogStreamer.cs index 356be68..08a6054 100644 --- a/Assets/IRXRClient/Scripts/LogStreamer.cs +++ b/Assets/IRXRClient/Scripts/LogStreamer.cs @@ -1,33 +1,41 @@ using UnityEngine; +namespace IRXR.Node +{ + + public class LogStreamer : MonoBehaviour + { + + protected string _topic; + protected Publisher _publisher; + + void Start() + { + _publisher = new Publisher("Log", false); + Application.logMessageReceived += HandleLog; + timer = Time.realtimeSinceStartup; + } + + private int frameCounter = 0; + private float timer = 0; + + void HandleLog(string logString, string stackTrace, LogType type) + { + _publisher.Publish(logString); + } + + void Update() + { + frameCounter += 1; + float totalTime = Time.realtimeSinceStartup - timer; + if (totalTime > 5.0f) + { + float fps = frameCounter / totalTime; + HandleLog("Average FPS in the last 5s: " + fps, null, LogType.Log); + timer = Time.realtimeSinceStartup; + frameCounter = 0; + } + } -public class LogStreamer : Streamer { - - private int frameCounter = 0; - private float timer = 0; - - void HandleLog(string logString, string stackTrace, LogType type) { - _publisher.Publish(logString); - } - - protected override void SetupTopic() { - _topic = "Log"; - } - - protected override void Initialize() { - Application.logMessageReceived += HandleLog; - timer = Time.realtimeSinceStartup; - } - - void Update() { - frameCounter += 1; - float totalTime = Time.realtimeSinceStartup - timer; - if (totalTime > 5.0f) { - float fps = frameCounter / totalTime; - HandleLog("Average FPS in the last 5s: " + fps, null, LogType.Log); - timer = Time.realtimeSinceStartup; - frameCounter = 0; } - } - } \ No newline at end of file diff --git a/Assets/IRXRClient/Scripts/MsgUtils.cs b/Assets/IRXRClient/Scripts/MsgUtils.cs new file mode 100644 index 0000000..68d0f65 --- /dev/null +++ b/Assets/IRXRClient/Scripts/MsgUtils.cs @@ -0,0 +1,109 @@ +using UnityEngine; +using System; +using System.IO; +using System.Text; +using Newtonsoft.Json; +using IRXR.Node; + +namespace IRXR.Utilities +{ + + public static class EchoHeader + { + public static readonly byte[] PING = new byte[] { 0x00 }; + public static readonly byte[] HEARTBEAT = new byte[] { 0x01 }; + public static readonly byte[] NODES = new byte[] { 0x02 }; + } + + public static class IRXRSignal + { + public static readonly string EMPTY = "EMPTY"; + public static readonly string SUCCESS = "SUCCESS"; + public static readonly string ERROR = "ERROR"; + public static readonly string NOSERVICE = "NOSERVICE"; + public static readonly string TIMEOUT = "TIMEOUT"; + } + + public static class MsgUtils + { + + public const string SEPARATOR = "|"; + + public static byte[][] SplitByte(byte[] bytesMsg) + { + int separatorIndex = Array.IndexOf(bytesMsg, Encoding.UTF8.GetBytes(SEPARATOR)[0]); + if (separatorIndex == -1) + { + return new byte[][] { bytesMsg, null }; + } + byte[] part1 = bytesMsg[..separatorIndex]; + byte[] part2 = bytesMsg[(separatorIndex + 1)..]; + return new byte[][] { part1, part2 }; + } + + public static string[] SplitByteToStr(byte[] bytesMsg) + { + byte[][] parts = SplitByte(bytesMsg); + return new string[] { Encoding.UTF8.GetString(parts[0]), Encoding.UTF8.GetString(parts[1]) }; + } + + public static byte[] ConcatenateByteArrays(params byte[][] arrays) + { + using (MemoryStream ms = new MemoryStream()) + { + foreach (byte[] array in arrays) + { + if (array != null) + { + ms.Write(array, 0, array.Length); + } + } + return ms.ToArray(); + } + } + + public static byte[] String2Bytes(string str) + { + return Encoding.UTF8.GetBytes(str); + } + + public static string Bytes2String(byte[] bytes) + { + return Encoding.UTF8.GetString(bytes); + } + + public static byte[] GenerateHeartbeat(NodeInfo nodeInfo) + { + string serializedLocalInfo = JsonConvert.SerializeObject(nodeInfo); + return ConcatenateByteArrays(EchoHeader.HEARTBEAT, String2Bytes(serializedLocalInfo)); + } + + public static byte[] Serialize2Byte(T data) + { + return String2Bytes(JsonConvert.SerializeObject(data)); + } + + public static T BytesDeserialize2Object(byte[] byteMessage) + { + string jsonString = Encoding.UTF8.GetString(byteMessage); + // Debug.Log(jsonString); + return JsonConvert.DeserializeObject(jsonString); + } + + public static byte[] ObjectSerialize2Bytes(T data) + { + return String2Bytes(JsonConvert.SerializeObject(data)); + } + + public static string CombineHeaderWithMessage(string header, string message) + { + return $"{header}{SEPARATOR}{message}"; + } + + public static byte[] CombineHeaderWithMessage(string header, byte[] message) + { + return ConcatenateByteArrays(String2Bytes(header), Encoding.UTF8.GetBytes(SEPARATOR), message); + } + + } +} \ No newline at end of file diff --git a/Assets/IRXRClient/Scripts/Singleton.cs.meta b/Assets/IRXRClient/Scripts/MsgUtils.cs.meta similarity index 83% rename from Assets/IRXRClient/Scripts/Singleton.cs.meta rename to Assets/IRXRClient/Scripts/MsgUtils.cs.meta index 5965bb8..dc6119b 100644 --- a/Assets/IRXRClient/Scripts/Singleton.cs.meta +++ b/Assets/IRXRClient/Scripts/MsgUtils.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 6b0f75fc58118f97ebfdd1c371964e79 +guid: 5eb617c758ddde3b092b558937a5af82 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/IRXRClient/Scripts/NetComponent.cs b/Assets/IRXRClient/Scripts/NetComponent.cs index a90e7c5..f388e44 100644 --- a/Assets/IRXRClient/Scripts/NetComponent.cs +++ b/Assets/IRXRClient/Scripts/NetComponent.cs @@ -3,59 +3,311 @@ using System; using System.Text; using Newtonsoft.Json; +using UnityEngine; +using IRXR.Utilities; -public class Publisher { - protected PublisherSocket _pubSocket; - protected string _topic; - - public Publisher(string topic) { - IRXRNetManager _netManager = IRXRNetManager.Instance; - string hostName = _netManager.GetHostName(); - _topic = $"{hostName}/{topic}"; - _pubSocket = _netManager.GetPublisherSocket(); - _netManager.CreatePublishTopic(_topic); - } - - public void Publish(object data) { - string msg = JsonConvert.SerializeObject(data); - msg = _topic + ":" + msg; - _pubSocket.SendFrame(msg); - } -} - -public class Subscriber { - protected string _topic; - private Action _receiveAction; - - public Subscriber(string topic, Action receiveAction) { - _topic = topic; - _receiveAction = receiveAction; - } - - public void StartSubscription() { - if (typeof(MsgType) == typeof(byte[])) - { - IRXRNetManager.Instance.SubscribeTopic(_topic, OnByteReceive); - } - else - { - IRXRNetManager.Instance.SubscribeTopic(_topic, OnReceive); - } - } - - public void OnByteReceive(byte[] byteMessage) { - _receiveAction((MsgType)(object)byteMessage); - } - - public void OnReceive(byte[] byteMessage) { - string jsonString = Encoding.UTF8.GetString(byteMessage); - MsgType msg = JsonConvert.DeserializeObject(jsonString); - _receiveAction(msg); - } - - public void Unsubscribe() { - IRXRNetManager.Instance.UnsubscribeTopic(_topic); - } - -} +namespace IRXR.Node +{ + public class Publisher + { + protected PublisherSocket _pubSocket; + protected string _topic; + + public Publisher(string topic, bool globalNameSpace = false) + { + IRXRNetManager _netManager = IRXRNetManager.Instance; + if (globalNameSpace) + { + _topic = topic; + } + else + { + _topic = $"{_netManager.localInfo.name}/{topic}"; + } + _pubSocket = _netManager._pubSocket; + if (!_netManager.localInfo.topicList.Contains(_topic)) + { + _netManager.localInfo.topicList.Add(_topic); + } + Debug.Log($"Publisher for topic {_topic} is created"); + } + + public void Publish(string data) + { + // Combine topic and message + string msg = MsgUtils.CombineHeaderWithMessage(_topic, data); + // Send the message + TryPublish(MsgUtils.String2Bytes(msg)); + } + + public void Publish(byte[] data) + { + TryPublish(MsgUtils.CombineHeaderWithMessage(_topic, data)); + } + + public void Publish(MsgType data) + { + string msg = MsgUtils.CombineHeaderWithMessage(_topic, JsonConvert.SerializeObject(data)); + TryPublish(MsgUtils.String2Bytes(msg)); + } + + private void TryPublish(byte[] msg) + { + try + { + _pubSocket.SendFrame(msg); + } + catch (TerminatingException ex) + { + Debug.LogWarning($"Publish failed: NetMQ context terminated. Error: {ex.Message}"); + } + catch (Exception ex) + { + Debug.LogWarning($"Publish failed: Unexpected error occurred. Error: {ex.Message}"); + } + } + + } + + public class Subscriber + { + protected string _topic; + private Action _receiveAction; + private Func _onProcessMsg; + + public Subscriber(string topic, Action receiveAction) + { + _topic = topic; + _receiveAction = receiveAction; + } + + public void StartSubscription() + { + IRXRNetManager _netManager = IRXRNetManager.Instance; + + if (!_netManager.masterInfo.topicList.Contains(_topic)) + { + Debug.LogWarning($"Topic {_topic} is not found in the master node"); + return; + } + + if (typeof(MsgType) == typeof(string)) + { + _onProcessMsg = OnReceiveAsString; + } + else if (typeof(MsgType) == typeof(byte[])) + { + _onProcessMsg = OnReceiveAsBytes; + } + else + { + _onProcessMsg = OnReceiveAsJson; + } + // else if (typeof(MsgType).IsSerializable) + // { + // _onProcessMsg = OnReceiveAsJson; + // } + // else + // { + // throw new NotSupportedException($"Type {typeof(MsgType)} is not supported for subscription."); + // } + _netManager.subscribeCallbacks[_topic] = OnReceive; + Debug.Log($"Subscribed to topic {_topic}"); + } + + public static MsgType OnReceiveAsString(byte[] message) + { + if (typeof(MsgType) != typeof(string)) + { + throw new InvalidOperationException($"Type mismatch: Expected {typeof(MsgType)}, but got string."); + } + + string result = Encoding.UTF8.GetString(message); + return (MsgType)(object)result; + } + + public static MsgType OnReceiveAsBytes(byte[] message) + { + if (typeof(MsgType) != typeof(byte[])) + { + throw new InvalidOperationException($"Type mismatch: Expected {typeof(MsgType)}, but got byte[]."); + } + + return (MsgType)(object)message; + } + + public static MsgType OnReceiveAsJson(byte[] message) + { + string jsonString = Encoding.UTF8.GetString(message); + return JsonConvert.DeserializeObject(jsonString); + } + + public void OnReceive(byte[] byteMessage) + { + try + { + MsgType msg = _onProcessMsg(byteMessage); + _receiveAction(msg); + } + catch (Exception ex) + { + Debug.LogWarning($"Error processing message for topic {_topic}: {ex.Message}"); + } + } + + + public void Unsubscribe() + { + IRXRNetManager _netManager = IRXRNetManager.Instance; + if (_netManager.masterInfo.topicList.Contains(_topic)) + { + _netManager.subscribeCallbacks.Remove(_topic); + Debug.Log($"Unsubscribe from topic {_topic}"); + } + } + + } + + // Service class: Since it is running in the main thread, + // so we don't need to destroy it + public class Service + { + protected string _serviceName; + private readonly Func _onRequest; + private Func ProcessRequestFunc; + private Func ProcessResponseFunc; + + public Service(string serviceName, Func onRequest, bool globalNameSpace = false) + { + IRXRNetManager netManager = IRXRNetManager.Instance; + string hostName = netManager.localInfo.name; + _serviceName = globalNameSpace ? serviceName : $"{hostName}/{serviceName}"; + if (netManager.localInfo.serviceList.Contains(_serviceName)) + { + throw new ArgumentException($"Service {_serviceName} is already registered"); + } + netManager.localInfo.serviceList.Add(_serviceName); + netManager.serviceCallbacks[_serviceName] = BytesCallback; + Debug.Log($"Service {_serviceName} is registered"); + _onRequest = onRequest ?? throw new ArgumentNullException(nameof(onRequest)); + // Initialize Request Processor + if (typeof(RequestType) == typeof(string)) + { + ProcessRequestFunc = bytes => (RequestType)(object)MsgUtils.Bytes2String(bytes); + } + else if (typeof(RequestType) == typeof(byte[])) + { + ProcessRequestFunc = bytes => (RequestType)(object)bytes; + } + else + { + ProcessRequestFunc = bytes => MsgUtils.BytesDeserialize2Object(bytes); + } + // else if (typeof(RequestType).IsSerializable) + // { + // ProcessRequestFunc = bytes => MsgUtils.BytesDeserialize2Object(bytes); + // } + // else + // { + // throw new NotSupportedException($"Type {typeof(RequestType)} is not supported for service request."); + // } + + // Initialize Response Processor + if (typeof(ResponseType) == typeof(string)) + { + ProcessResponseFunc = response => MsgUtils.String2Bytes((string)(object)response); + } + else if (typeof(ResponseType) == typeof(byte[])) + { + ProcessResponseFunc = response => (byte[])(object)response; + } + else + { + ProcessResponseFunc = response => MsgUtils.ObjectSerialize2Bytes(response); + } + // else if (typeof(ResponseType).IsSerializable) + // { + // ProcessResponseFunc = response => MsgUtils.ObjectSerialize2Bytes(response); + // } + // else + // { + // throw new NotSupportedException($"Type {typeof(ResponseType)} is not supported for service response."); + // } + } + + private byte[] BytesCallback(byte[] bytes) + { + try + { + RequestType request = ProcessRequestFunc(bytes); + ResponseType response = _onRequest(request); + return ProcessResponseFunc(response); + } + catch (Exception ex) + { + Debug.LogWarning($"Error processing request for service {_serviceName}: {ex.Message}"); + return HandleErrorResponse(ex); + } + } + + private byte[] HandleErrorResponse(Exception ex) + { + string errorMessage = $"Error: {ex.Message}"; + if (typeof(ResponseType) == typeof(string)) + { + return MsgUtils.ObjectSerialize2Bytes((ResponseType)(object)errorMessage); + } + Debug.LogWarning($"Unsupported error response type for {_serviceName}, returning default."); + return new byte[0]; + } + + public void Unregister() + { + IRXRNetManager netManager = IRXRNetManager.Instance; + + if (netManager.localInfo.serviceList.Contains(_serviceName)) + { + netManager.localInfo.serviceList.Remove(_serviceName); + netManager.serviceCallbacks.Remove(_serviceName); + Debug.Log($"Service {_serviceName} is unregistered"); + } + else + { + Debug.LogWarning($"Service {_serviceName} is not registered"); + } + } + + public static string BytesToString(byte[] bytes) + { + return Encoding.UTF8.GetString(bytes); + } + + public static byte[] StringToBytes(string str) + { + return Encoding.UTF8.GetBytes(str); + } + + public static RequestType BytesToRequest(byte[] bytes) + { + return MsgUtils.BytesDeserialize2Object(bytes); + } + + public static byte[] RequestToBytes(RequestType request) + { + return MsgUtils.ObjectSerialize2Bytes(request); + } + + public static ResponseType BytesToResponse(byte[] bytes) + { + return MsgUtils.BytesDeserialize2Object(bytes); + } + + public static byte[] ResponseToBytes(ResponseType response) + { + return MsgUtils.ObjectSerialize2Bytes(response); + } + } + + +} \ No newline at end of file diff --git a/Assets/IRXRClient/Scripts/NetworkUtils.cs b/Assets/IRXRClient/Scripts/NetworkUtils.cs new file mode 100644 index 0000000..913cf34 --- /dev/null +++ b/Assets/IRXRClient/Scripts/NetworkUtils.cs @@ -0,0 +1,118 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using System.Net.NetworkInformation; + + +namespace IRXR.Utilities +{ + + public static class UnityPortSet + { + public static readonly int DISCOVERY = 7720; + public static readonly int HEARTBEAT = 7721; + public static readonly int SERVICE = 7730; + public static readonly int TOPIC = 7731; + } + + public static class NodeTypes + { + public static readonly string MASTER = "master"; + public static readonly string XR = "xr"; + public static readonly string SLAVE = "slave"; + } + + public class NodeAddress + { + public string ip; + public int port; + public NodeAddress(string ip, int port) + { + this.ip = ip; + this.port = port; + } + } + + public class NodeInfo + { + public string name; + public string nodeID; + public NodeAddress addr; + public string type; + public int servicePort; + public int topicPort; + public List serviceList = new(); + public List topicList = new(); + } + + public static class NetworkUtils + { + + public static UdpClient CreateUDPClient(IPEndPoint endPoint) + { + UdpClient udpClient = new UdpClient(); + udpClient.Client.Bind(endPoint); + return udpClient; + } + + public static UdpClient CreateUDPClient(int port) + { + return CreateUDPClient(new IPEndPoint(IPAddress.Any, port)); + } + + public static UdpClient CreateUDPClient(string localIP, int port) + { + IPAddress ipAddress = IPAddress.Parse(localIP); + return CreateUDPClient(new IPEndPoint(ipAddress, port)); + } + + public static string GetLocalIPsInSameSubnet(string inputIPAddress) + { + IPAddress inputIP; + if (!IPAddress.TryParse(inputIPAddress, out inputIP)) + { + throw new ArgumentException("Invalid IP address format.", nameof(inputIPAddress)); + } + IPAddress subnetMask = IPAddress.Parse("255.255.255.0"); + // Get all network interfaces + NetworkInterface[] networkInterfaces = NetworkInterface.GetAllNetworkInterfaces(); + foreach (NetworkInterface ni in networkInterfaces) + { + // Get IP properties of the network interface + IPInterfaceProperties ipProperties = ni.GetIPProperties(); + UnicastIPAddressInformationCollection unicastIPAddresses = ipProperties.UnicastAddresses; + foreach (UnicastIPAddressInformation ipInfo in unicastIPAddresses) + { + if (ipInfo.Address.AddressFamily == AddressFamily.InterNetwork) + { + IPAddress localIP = ipInfo.Address; + // Check if the IP is in the same subnet + if (IsInSameSubnet(inputIP, localIP, subnetMask)) + { + return localIP.ToString(); ; + } + } + } + } + return "127.0.0.1"; + } + + private static bool IsInSameSubnet(IPAddress ip1, IPAddress ip2, IPAddress subnetMask) + { + byte[] ip1Bytes = ip1.GetAddressBytes(); + byte[] ip2Bytes = ip2.GetAddressBytes(); + byte[] maskBytes = subnetMask.GetAddressBytes(); + + for (int i = 0; i < ip1Bytes.Length; i++) + { + if ((ip1Bytes[i] & maskBytes[i]) != (ip2Bytes[i] & maskBytes[i])) + { + return false; + } + } + return true; + } + + } +} \ No newline at end of file diff --git a/Assets/IRXRClient/Scripts/Streamer.cs.meta b/Assets/IRXRClient/Scripts/NetworkUtils.cs.meta similarity index 83% rename from Assets/IRXRClient/Scripts/Streamer.cs.meta rename to Assets/IRXRClient/Scripts/NetworkUtils.cs.meta index b61bfa5..b33ff72 100644 --- a/Assets/IRXRClient/Scripts/Streamer.cs.meta +++ b/Assets/IRXRClient/Scripts/NetworkUtils.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: b11c3fae2395e7ad5a59433f4d75b7e2 +guid: ac2332fde5ddff203a82d073335ceddc MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/IRXRClient/Scripts/Singleton.cs b/Assets/IRXRClient/Scripts/Singleton.cs deleted file mode 100644 index 2df7c8b..0000000 --- a/Assets/IRXRClient/Scripts/Singleton.cs +++ /dev/null @@ -1,62 +0,0 @@ -using UnityEngine; - -public class Singleton : MonoBehaviour where T : MonoBehaviour -{ - private static T _instance; - private static readonly object _lock = new object(); - private static bool _applicationIsQuitting = false; - - public static T Instance - { - get - { - if (_applicationIsQuitting) - { - Debug.LogWarning("[Singleton] Instance '" + typeof(T) + - "' already destroyed on application quit." + - " Won't create again - returning null."); - return null; - } - - lock (_lock) - { - if (_instance == null) - { - _instance = (T)FindObjectOfType(typeof(T)); - - if (FindObjectsOfType(typeof(T)).Length > 1) - { - Debug.LogError("[Singleton] Something went really wrong " + - " - there should never be more than 1 singleton!" + - " Reopening the scene might fix it."); - return _instance; - } - - if (_instance == null) - { - GameObject singleton = new GameObject(); - _instance = singleton.AddComponent(); - singleton.name = "(singleton) " + typeof(T).ToString(); - - DontDestroyOnLoad(singleton); - - Debug.Log("[Singleton] An instance of " + typeof(T) + - " is needed in the scene, so '" + singleton + - "' was created with DontDestroyOnLoad."); - } - else - { - Debug.Log("[Singleton] Using instance already created"); - } - } - - return _instance; - } - } - } - - private void OnDestroy() - { - _applicationIsQuitting = true; - } -} diff --git a/Assets/IRXRClient/Scripts/Streamer.cs b/Assets/IRXRClient/Scripts/Streamer.cs deleted file mode 100644 index 65293cf..0000000 --- a/Assets/IRXRClient/Scripts/Streamer.cs +++ /dev/null @@ -1,19 +0,0 @@ -using UnityEngine; - - -public class Streamer : MonoBehaviour { - - protected string _topic; - protected Publisher _publisher; - - void Start() { - SetupTopic(); - _publisher = new Publisher(_topic); - Initialize(); - } - - protected virtual void SetupTopic() {} - - protected virtual void Initialize() {} - -} \ No newline at end of file diff --git a/Assets/SceneLoader/Prefabs/SimScene.prefab b/Assets/SceneLoader/Prefabs/SimScene.prefab index 3fe501c..8b6bfe2 100644 --- a/Assets/SceneLoader/Prefabs/SimScene.prefab +++ b/Assets/SceneLoader/Prefabs/SimScene.prefab @@ -45,6 +45,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: f25e002b6045cf2c1b5bcda5d4b0dd28, type: 3} m_Name: m_EditorClassIdentifier: + defaultMaterial: {fileID: 2100000, guid: 77d607bab723c72f4928ee261ed0bda3, type: 2} --- !u!114 &5552327243606421420 MonoBehaviour: m_ObjectHideFlags: 0 diff --git a/Assets/SceneLoader/Scene/SimScene.unity b/Assets/SceneLoader/Scene/SimScene.unity index 1cef00b..f205173 100644 --- a/Assets/SceneLoader/Scene/SimScene.unity +++ b/Assets/SceneLoader/Scene/SimScene.unity @@ -13,7 +13,7 @@ OcclusionCullingSettings: --- !u!104 &2 RenderSettings: m_ObjectHideFlags: 0 - serializedVersion: 9 + serializedVersion: 10 m_Fog: 0 m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} m_FogMode: 3 @@ -37,13 +37,13 @@ RenderSettings: m_ReflectionBounces: 1 m_ReflectionIntensity: 1 m_CustomReflection: {fileID: 0} - m_Sun: {fileID: 705507994} + m_Sun: {fileID: 0} m_UseRadianceAmbientProbe: 0 --- !u!157 &3 LightmapSettings: m_ObjectHideFlags: 0 - serializedVersion: 12 - m_GIWorkflowMode: 1 + serializedVersion: 13 + m_BakeOnSceneLoad: 0 m_GISettings: serializedVersion: 2 m_BounceScale: 1 @@ -66,9 +66,6 @@ LightmapSettings: m_LightmapParameters: {fileID: 0} m_LightmapsBakeMode: 1 m_TextureCompression: 1 - m_FinalGather: 0 - m_FinalGatherFiltering: 1 - m_FinalGatherRayCount: 256 m_ReflectionCompression: 2 m_MixedBakeMode: 2 m_BakeBackend: 1 @@ -97,7 +94,7 @@ LightmapSettings: m_TrainingDataDestination: TrainingData m_LightProbeSampleCountMultiplier: 4 m_LightingDataAsset: {fileID: 0} - m_LightingSettings: {fileID: 0} + m_LightingSettings: {fileID: 4890085278179872738, guid: 2476360b4ece1f65fa01617e82f3168b, type: 2} --- !u!196 &4 NavMeshSettings: serializedVersion: 2 @@ -133,7 +130,7 @@ GameObject: - component: {fileID: 705507995} - component: {fileID: 705507994} m_Layer: 0 - m_Name: Directional Light + m_Name: DirectionalLight m_TagString: Untagged m_Icon: {fileID: 0} m_NavMeshLayer: 0 @@ -147,10 +144,9 @@ Light: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 705507993} m_Enabled: 1 - serializedVersion: 10 + serializedVersion: 11 m_Type: 1 - m_Shape: 0 - m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1} + m_Color: {r: 1, g: 1, b: 1, a: 1} m_Intensity: 1 m_Range: 10 m_SpotAngle: 30 @@ -199,8 +195,12 @@ Light: m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0} m_UseBoundingSphereOverride: 0 m_UseViewFrustumForShadowCasterCull: 1 + m_ForceVisible: 0 m_ShadowRadius: 0 m_ShadowAngle: 0 + m_LightUnit: 1 + m_LuxAtDistance: 1 + m_EnableSpotReflector: 1 --- !u!4 &705507995 Transform: m_ObjectHideFlags: 0 @@ -209,13 +209,13 @@ Transform: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 705507993} serializedVersion: 2 - m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261} - m_LocalPosition: {x: 0, y: 3, z: 0} + m_LocalRotation: {x: 0.92387956, y: 0, z: 0, w: 0.38268343} + m_LocalPosition: {x: 0, y: 3, z: 3} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 0} - m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0} + m_LocalEulerAnglesHint: {x: 135, y: 0, z: 0} --- !u!1 &963194225 GameObject: m_ObjectHideFlags: 0 diff --git a/Assets/SceneLoader/Scripts/PointCloudLoader.cs b/Assets/SceneLoader/Scripts/PointCloudLoader.cs index fe4f11e..5493493 100644 --- a/Assets/SceneLoader/Scripts/PointCloudLoader.cs +++ b/Assets/SceneLoader/Scripts/PointCloudLoader.cs @@ -1,71 +1,71 @@ -using UnityEngine; -using Newtonsoft.Json; +// using UnityEngine; +// using Newtonsoft.Json; -class PointCloudData { - public float[] positions; - public float[] colors; -} +// class PointCloudData { +// public float[] positions; +// public float[] colors; +// } -[RequireComponent(typeof(ParticleSystem))] -public class PointCloudLoader : MonoBehaviour -{ - private ParticleSystem _particleSystem = null; - private ParticleSystem.Particle[] voxels; - bool voxelsUpdated = false; - private IRXRNetManager _netManager; +// [RequireComponent(typeof(ParticleSystem))] +// public class PointCloudLoader : MonoBehaviour +// { +// private ParticleSystem _particleSystem = null; +// private ParticleSystem.Particle[] voxels; +// bool voxelsUpdated = false; +// private IRXRNetManager _netManager; - // Start is called before the first frame update - void Start() - { - _particleSystem = GetComponent(); - _netManager = IRXRNetManager.Instance; - _netManager.RegisterServiceCallback("LoadPointCloud", LoadPointCloud); - } +// // Start is called before the first frame update +// void Start() +// { +// _particleSystem = GetComponent(); +// _netManager = IRXRNetManager.Instance; +// _netManager.RegisterServiceCallback("LoadPointCloud", LoadPointCloud); +// } - // Update is called once per frame - void Update() - { - if (voxelsUpdated) - { - _particleSystem.SetParticles(voxels, voxels.Length); - voxelsUpdated = false; - } - } +// // Update is called once per frame +// void Update() +// { +// if (voxelsUpdated) +// { +// _particleSystem.SetParticles(voxels, voxels.Length); +// voxelsUpdated = false; +// } +// } - private string LoadPointCloud(string pointCloudStrData) - { - PointCloudData pointCloudData = JsonConvert.DeserializeObject(pointCloudStrData); - // Convert the data to the format that Unity's Particle System can use - Vector3[] positions = new Vector3[pointCloudData.positions.Length / 3]; - Color[] colors = new Color[pointCloudData.colors.Length / 4]; - float[] positionArray = pointCloudData.positions; - float[] colorArray = pointCloudData.colors; - for (int i = 0; i < positions.Length; i++) - { - positions[i] = new Vector3(positionArray[i * 3], positionArray[i * 3 + 1], positionArray[i * 3 + 2]); - } - for (int i = 0; i < colors.Length; i++) - { - colors[i] = new Color(colorArray[i * 4], colorArray[i * 4 + 1], colorArray[i * 4 + 2], colorArray[i * 4 + 3]); - } - SetVoxels(positions, colors); - return "Downloaded Point Cloud"; - } +// private string LoadPointCloud(string pointCloudStrData) +// { +// PointCloudData pointCloudData = JsonConvert.DeserializeObject(pointCloudStrData); +// // Convert the data to the format that Unity's Particle System can use +// Vector3[] positions = new Vector3[pointCloudData.positions.Length / 3]; +// Color[] colors = new Color[pointCloudData.colors.Length / 4]; +// float[] positionArray = pointCloudData.positions; +// float[] colorArray = pointCloudData.colors; +// for (int i = 0; i < positions.Length; i++) +// { +// positions[i] = new Vector3(positionArray[i * 3], positionArray[i * 3 + 1], positionArray[i * 3 + 2]); +// } +// for (int i = 0; i < colors.Length; i++) +// { +// colors[i] = new Color(colorArray[i * 4], colorArray[i * 4 + 1], colorArray[i * 4 + 2], colorArray[i * 4 + 3]); +// } +// SetVoxels(positions, colors); +// return "Downloaded Point Cloud"; +// } - public void SetVoxels(Vector3[] positions, Color[] colors) - { - if (voxels == null || voxels.Length != positions.Length) - { - voxels = new ParticleSystem.Particle[positions.Length]; - } - for (int i = 0; i < positions.Length; i++) - { - voxels[i].position = positions[i]; - voxels[i].startColor = colors[i]; - voxels[i].startSize = 0.01f; - } - voxelsUpdated = true; - } +// public void SetVoxels(Vector3[] positions, Color[] colors) +// { +// if (voxels == null || voxels.Length != positions.Length) +// { +// voxels = new ParticleSystem.Particle[positions.Length]; +// } +// for (int i = 0; i < positions.Length; i++) +// { +// voxels[i].position = positions[i]; +// voxels[i].startColor = colors[i]; +// voxels[i].startSize = 0.01f; +// } +// voxelsUpdated = true; +// } -} +// } diff --git a/Assets/SceneLoader/Scripts/RigidObjectsController.cs b/Assets/SceneLoader/Scripts/RigidObjectsController.cs index 7a4fe75..7f9d689 100644 --- a/Assets/SceneLoader/Scripts/RigidObjectsController.cs +++ b/Assets/SceneLoader/Scripts/RigidObjectsController.cs @@ -1,56 +1,68 @@ using System.Collections.Generic; using UnityEngine; +using IRXR.Node; +using System; - -public class StreamMessage { - public Dictionary> updateData; - public float time; -} - -[RequireComponent(typeof(SceneLoader))] -public class RigidObjectsController : MonoBehaviour +namespace IRXR.SceneLoader { - private float lastSimulationTimeStamp = 0.0f; - public Dictionary _objectsTrans; - private Transform _trans; - private float timeOffset = 0.0f; - private float frameCounter = 0; - private float timeDelay = 0; - private Subscriber _subscriber; - - void Start() { - gameObject.GetComponent().OnSceneLoaded += StartSubscription; - gameObject.GetComponent().OnSceneCleared += StopSubscription; - _subscriber = new Subscriber("SceneUpdate", SubscribeCallback); - } - public void StartSubscription() { - _trans = gameObject.transform; - _objectsTrans = gameObject.GetComponent().GetObjectsTrans(); - timeOffset = IRXRNetManager.Instance.TimeOffset; - Debug.Log("Start Update Scene"); - _subscriber.StartSubscription(); + // [Serializable] + public class StreamMessage + { + public Dictionary> updateData; + public float time; } - public void StopSubscription() { - _subscriber.Unsubscribe(); - } + [RequireComponent(typeof(SimLoader))] + public class RigidObjectsController : MonoBehaviour + { + private float lastSimulationTimeStamp = 0.0f; + public Dictionary _objectsTrans; + private Transform _trans; + private float timeOffset = 0.0f; + private float frameCounter = 0; + private float timeDelay = 0; + private Subscriber _subscriber; - public void SubscribeCallback(StreamMessage streamMsg) { - if (streamMsg.time < lastSimulationTimeStamp) return; - lastSimulationTimeStamp = streamMsg.time; - foreach (var (name, value) in streamMsg.updateData) { - _objectsTrans[name].position = transform.TransformPoint(new Vector3(value[0], value[1], value[2])); - _objectsTrans[name].rotation = _trans.rotation * new Quaternion(value[3], value[4], value[5], value[6]); + void Start() + { + gameObject.GetComponent().OnSceneLoaded += StartSubscription; + IRXRNetManager.Instance.OnDisconnected += StopSubscription; + _subscriber = new Subscriber("SceneUpdate", SubscribeCallback); } - timeDelay += Time.realtimeSinceStartup - streamMsg.time - timeOffset; - frameCounter++; - // measure latency every 1000 frames - if (frameCounter == 1000) { - Debug.Log($"Average Latency in the last 1000 frames: {timeDelay} ms"); - timeDelay = 0; - frameCounter = 0; + + public void StartSubscription() + { + _trans = gameObject.transform; + _objectsTrans = gameObject.GetComponent().GetObjectsTrans(); + // timeOffset = IRXRNetManager.Instance.TimeOffset; + _subscriber.StartSubscription(); } - } + public void StopSubscription() + { + _subscriber.Unsubscribe(); + } + + public void SubscribeCallback(StreamMessage streamMsg) + { + if (streamMsg.time < lastSimulationTimeStamp) return; + lastSimulationTimeStamp = streamMsg.time; + foreach (var (name, value) in streamMsg.updateData) + { + _objectsTrans[name].position = transform.TransformPoint(new Vector3(value[0], value[1], value[2])); + _objectsTrans[name].rotation = _trans.rotation * new Quaternion(value[3], value[4], value[5], value[6]); + } + timeDelay += Time.realtimeSinceStartup - streamMsg.time - timeOffset; + frameCounter++; + // measure latency every 1000 frames + // if (frameCounter == 1000) + // { + // Debug.Log($"Average Latency in the last 1000 frames: {timeDelay} ms"); + // timeDelay = 0; + // frameCounter = 0; + // } + } + + } } \ No newline at end of file diff --git a/Assets/SceneLoader/Scripts/SimData.cs b/Assets/SceneLoader/Scripts/SimData.cs index 9b9b073..6771ad6 100644 --- a/Assets/SceneLoader/Scripts/SimData.cs +++ b/Assets/SceneLoader/Scripts/SimData.cs @@ -1,79 +1,91 @@ +using System; using System.Collections.Generic; using UnityEngine; - -public class SimTransform { - public List pos; - public List rot; - public List scale; - - public Vector3 GetPos() { - return new Vector3(pos[0], pos[1], pos[2]); - } - - public Quaternion GetRot() { - return new Quaternion(rot[0], rot[1], rot[2], rot[3]); - } - - public Vector3 GetScale() { - return new Vector3(scale[0], scale[1], scale[2]); - } - -} - - -public class SimVisual { - public string type; - public SimMesh mesh; - public SimMaterial material; - public SimTransform trans; -} - - -public class SimBody { - public string name; - public SimTransform trans; - public List visuals; - public List children; -} - - -public class SimScene { - public string id; - public SimBody root; -} - - -public class SimAsset { - public string name; -} - - -public class SimMesh : SimAsset { - public string hash; - public List indicesLayout; - public List verticesLayout; - public List normalsLayout; - public List uvLayout; - -} - - -public class SimMaterial : SimAsset { - public string hash; - public List color; - public List emissionColor; - public float specular; - public float shininess; - public float reflectance; - public SimTexture texture; -} - - -public class SimTexture : SimAsset { - public string hash; - public int width; - public int height; - public string texureType; - public List textureSize; -} +namespace IRXR.SceneLoader +{ + + public class SimTransform + { + public List pos; + public List rot; + public List scale; + + public Vector3 GetPos() + { + return new Vector3(pos[0], pos[1], pos[2]); + } + + public Quaternion GetRot() + { + return new Quaternion(rot[0], rot[1], rot[2], rot[3]); + } + + public Vector3 GetScale() + { + return new Vector3(scale[0], scale[1], scale[2]); + } + + } + + public class SimVisual + { + public string type; + public SimMesh mesh; + public SimMaterial material; + public SimTransform trans; + } + + public class SimBody + { + public string name; + public SimTransform trans; + public List visuals; + public List children; + } + + public class SimScene + { + public string id; + public SimBody root; + } + + + public class SimAsset + { + public string name; + } + + + public class SimMesh : SimAsset + { + public string hash; + public List indicesLayout; + public List verticesLayout; + public List normalsLayout; + public List uvLayout; + + } + + + public class SimMaterial : SimAsset + { + public string hash; + public List color; + public List emissionColor; + public float specular; + public float shininess; + public float reflectance; + public SimTexture texture; + } + + + public class SimTexture : SimAsset + { + public string hash; + public int width; + public int height; + public string textureType; + public List textureScale; + } +} \ No newline at end of file diff --git a/Assets/SceneLoader/Scripts/SimLoader.cs b/Assets/SceneLoader/Scripts/SimLoader.cs index 80c80f7..8492623 100644 --- a/Assets/SceneLoader/Scripts/SimLoader.cs +++ b/Assets/SceneLoader/Scripts/SimLoader.cs @@ -4,228 +4,281 @@ using System.Runtime.InteropServices; using Newtonsoft.Json; using System.Threading.Tasks; +using IRXR.Node; +using IRXR.Utilities; +namespace IRXR.SceneLoader +{ -public class SceneLoader : MonoBehaviour { - - private object updateActionLock = new(); - private Action updateAction; - public Action OnSceneLoaded; - public Action OnSceneCleared; - private IRXRNetManager _netManager; - private GameObject _simSceneObj; - private SimScene _simScene; - private Dictionary _simObjTrans = new (); - private Dictionary>> _pendingMesh = new (); - private Dictionary>> _pendingTexture = new (); - - - void Start() { - _netManager = IRXRNetManager.Instance; - _netManager.OnDisconnected += ClearScene; - updateAction = () => { }; - OnSceneLoaded += () => Debug.Log("Scene Loaded"); - OnSceneCleared += () => Debug.Log("Scene Cleared"); - _netManager.RegisterServiceCallback("LoadSimScene", LoadSimScene); - } - - private string LoadSimScene(string simSceneJsonStr) { - ClearScene(); - _simScene = JsonConvert.DeserializeObject(simSceneJsonStr); - updateAction += BuildScene; - Debug.Log("Downloaded scene json and starting to build scene"); - return "Received Scene"; - } - - void BuildScene() { - // Don't include System.Diagnostics, Debug becomes disambiguous - // It is more accurate to use System.Diagnostics.Stopwatch, theoretically - var local_watch = new System.Diagnostics.Stopwatch(); - local_watch.Start(); - // Debug.Log("Start Building Scene"); - _simSceneObj = CreateObject(gameObject.transform, _simScene.root); - local_watch.Stop(); - Debug.Log($"Building Scene in {local_watch.ElapsedMilliseconds} ms"); - Task.Run(() => DownloadAssets()); - OnSceneLoaded.Invoke(); - } - - public void DownloadAssets() { - var local_watch = new System.Diagnostics.Stopwatch(); - local_watch.Start(); - int totalMeshSize = 0; - int totalTextureSize = 0; - foreach (string hash in _pendingMesh.Keys) + public class SimLoader : MonoBehaviour { - byte[] meshData = _netManager.RequestBytes("Asset", hash).ToArray(); - var(simMesh, meshFilters) = _pendingMesh[hash]; - RunOnMainThread(() => BuildMesh(meshData, simMesh, meshFilters)); - totalMeshSize += meshData.Length; - } - foreach (string hash in _pendingTexture.Keys) - { - byte[] texData = _netManager.RequestBytes("Asset", hash).ToArray(); - var(simTex, materials) = _pendingTexture[hash]; - RunOnMainThread(() => BuildTexture(texData, simTex, materials)); - totalTextureSize += texData.Length; - } + [SerializeField] private Material defaultMaterial; + private object updateActionLock = new(); + private Action updateAction; + public Action OnSceneLoaded; + public Action OnSceneCleared; + private IRXRNetManager _netManager; + private GameObject _simSceneObj; + private SimScene _simScene; + private Dictionary _simObjTrans = new(); + private Dictionary>> _pendingMesh = new(); + private Dictionary>> _pendingTexture = new(); + // Services + private Service loadSimSceneService; - double meshSizeMB = Math.Round(totalMeshSize / Math.Pow(2, 20), 2); - double textureSizeMB = Math.Round(totalTextureSize / Math.Pow(2, 20), 2); - - local_watch.Stop(); - _pendingMesh.Clear(); - _pendingTexture.Clear(); - - // When debug run in the subthread, it will not send the log to the server - RunOnMainThread(() => Debug.Log($"Downloaded {meshSizeMB}MB meshes, {textureSizeMB}MB textures.")); - RunOnMainThread(() => Debug.Log($"Downloaded Asset in {local_watch.ElapsedMilliseconds} ms")); - } - - void RunOnMainThread(Action action) { - lock(updateActionLock) { - updateAction += action; - } - } + void Start() + { + _netManager = IRXRNetManager.Instance; + updateAction = () => { }; + OnSceneLoaded += () => Debug.Log("Scene Loaded"); + OnSceneCleared += () => Debug.Log("Scene Cleared"); + loadSimSceneService = new Service("LoadSimScene", LoadSimScene, true); + } - void Update() { - lock(updateActionLock) { - updateAction.Invoke(); - updateAction = () => { }; - } - } - - void ApplyTransform(Transform utransform, SimTransform trans) { - utransform.localPosition = trans.GetPos(); - utransform.localRotation = trans.GetRot(); - utransform.localScale = trans.GetScale(); - } - - GameObject CreateObject(Transform root, SimBody body) { - - GameObject bodyRoot = new GameObject(body.name); - bodyRoot.transform.SetParent(root, false); - ApplyTransform(bodyRoot.transform, body.trans); - if (body.visuals.Count != 0) { - GameObject VisualContainer = new GameObject($"{body.name}_Visuals"); - VisualContainer.transform.SetParent(bodyRoot.transform, false); - foreach (SimVisual visual in body.visuals) { - GameObject visualObj; - switch (visual.type) { - case "MESH": { - SimMesh simMesh = visual.mesh; - visualObj = new GameObject(simMesh.hash, typeof(MeshFilter), typeof(MeshRenderer)); - if (!_pendingMesh.ContainsKey(simMesh.hash)) { - _pendingMesh[simMesh.hash] = new(simMesh, new()); + private string LoadSimScene(SimScene simScene) + { + ClearScene(); + _simScene = simScene; + updateAction += BuildScene; + Debug.Log("Downloaded scene json and starting to build scene"); + return IRXRSignal.SUCCESS; + } + + void BuildScene() + { + // Don't include System.Diagnostics, Debug becomes ambiguous + // It is more accurate to use System.Diagnostics.Stopwatch, theoretically + var local_watch = new System.Diagnostics.Stopwatch(); + local_watch.Start(); + // Debug.Log("Start Building Scene"); + _simSceneObj = CreateObject(gameObject.transform, _simScene.root); + local_watch.Stop(); + Debug.Log($"Building Scene in {local_watch.ElapsedMilliseconds} ms"); + Task.Run(() => DownloadAssets()); + OnSceneLoaded.Invoke(); + } + + public void DownloadAssets() + { + var local_watch = new System.Diagnostics.Stopwatch(); + local_watch.Start(); + int totalMeshSize = 0; + int totalTextureSize = 0; + foreach (string hash in _pendingMesh.Keys) + { + byte[] meshData = _netManager.CallBytesService("Asset", hash); + foreach (var item in _pendingMesh[hash]) + { + var (simMesh, meshFilter) = item; + RunOnMainThread(() => BuildMesh(meshData, simMesh, meshFilter)); + } + totalMeshSize += meshData.Length; + } + foreach (string hash in _pendingTexture.Keys) + { + byte[] texData = _netManager.CallBytesService("Asset", hash); + foreach (var item in _pendingTexture[hash]) + { + var (simTex, material) = item; + RunOnMainThread(() => BuildTexture(texData, simTex, material)); + } + totalTextureSize += texData.Length; } - _pendingMesh[simMesh.hash].Item2.Add(visualObj.GetComponent()); - break; - } - case "CUBE": - visualObj = GameObject.CreatePrimitive(PrimitiveType.Cube); - break; - case "PLANE": - visualObj = GameObject.CreatePrimitive(PrimitiveType.Plane); - break; - case "CYLINDER": - visualObj = GameObject.CreatePrimitive(PrimitiveType.Cylinder); - break; - case "CAPSULE": - visualObj = GameObject.CreatePrimitive(PrimitiveType.Capsule); - break; - case "SPHERE": - visualObj = GameObject.CreatePrimitive(PrimitiveType.Sphere); - break; - default: - Debug.LogWarning("Invalid visual, " + visual.type + body.name); - visualObj = GameObject.CreatePrimitive(PrimitiveType.Sphere); - break; + + double meshSizeMB = Math.Round(totalMeshSize / Math.Pow(2, 20), 2); + double textureSizeMB = Math.Round(totalTextureSize / Math.Pow(2, 20), 2); + + local_watch.Stop(); + _pendingMesh.Clear(); + _pendingTexture.Clear(); + + // When debug run in the sub thread, it will not send the log to the server + RunOnMainThread(() => Debug.Log($"Downloaded {meshSizeMB}MB meshes, {textureSizeMB}MB textures.")); + RunOnMainThread(() => Debug.Log($"Downloaded Asset in {local_watch.ElapsedMilliseconds} ms")); } - Renderer renderer = visualObj.GetComponent(); - if (visual.material != null) { - renderer.material = BuildMaterial(visual.material, body.name); + + void RunOnMainThread(Action action) + { + lock (updateActionLock) + { + updateAction += action; + } } - else { - Debug.LogWarning($"Material of {body.name}_Visuals not found"); + + void Update() + { + lock (updateActionLock) + { + updateAction.Invoke(); + updateAction = () => { }; + } } - visualObj.transform.SetParent(VisualContainer.transform, false); - ApplyTransform(visualObj.transform, visual.trans); - } - } - body.children.ForEach(body => CreateObject(bodyRoot.transform, body)); - _simObjTrans.Add(body.name, bodyRoot.transform); - return bodyRoot; - } - - void ClearScene() { - OnSceneCleared.Invoke(); - if (_simSceneObj != null) Destroy(_simSceneObj); - _pendingMesh.Clear(); - _pendingTexture.Clear(); - _simObjTrans.Clear(); - } - - public Material BuildMaterial(SimMaterial simMat, string objName) { - Material mat = new Material(Shader.Find("Standard")); - // Transparency - if (simMat.color[3] < 1) - { - mat.SetFloat("_Mode", 2); - mat.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha); - mat.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha); - mat.SetInt("_ZWrite", 0); - mat.DisableKeyword("_ALPHATEST_ON"); - mat.EnableKeyword("_ALPHABLEND_ON"); - mat.DisableKeyword("_ALPHAPREMULTIPLY_ON"); - mat.renderQueue = 3000; - } - mat.SetColor("_Color", new Color(simMat.color[0], simMat.color[1], simMat.color[2], simMat.color[3])); - if (simMat.emissionColor != null){ - mat.SetColor("_emissionColor", new Color(simMat.emissionColor[0], simMat.emissionColor[1], simMat.emissionColor[2], simMat.emissionColor[3])); - } - mat.SetFloat("_specularHighlights", simMat.specular); - mat.SetFloat("_Smoothness", simMat.shininess); - mat.SetFloat("_GlossyReflections", simMat.reflectance); - if (simMat.texture != null) { - // Debug.Log($"Texture found for {objName}"); - SimTexture simTex = simMat.texture; - if (!_pendingTexture.ContainsKey(simTex.hash)) { - _pendingTexture[simTex.hash] = new(simTex, new()); - } - _pendingTexture[simTex.hash].Item2.Add(mat); - } - return mat; - } - - public void BuildMesh(byte[] meshData, SimMesh simMesh, List meshFilters) { - var mesh = new Mesh{ - vertices = MemoryMarshal.Cast(new ReadOnlySpan(meshData, simMesh.verticesLayout[0], simMesh.verticesLayout[1] * sizeof(float))).ToArray(), - normals = MemoryMarshal.Cast(new ReadOnlySpan(meshData, simMesh.normalsLayout[0], simMesh.normalsLayout[1] * sizeof(float))).ToArray(), - triangles = MemoryMarshal.Cast(new ReadOnlySpan(meshData, simMesh.indicesLayout[0], simMesh.indicesLayout[1] * sizeof(int))).ToArray(), - uv = MemoryMarshal.Cast(new ReadOnlySpan(meshData, simMesh.uvLayout[0], simMesh.uvLayout[1] * sizeof(float))).ToArray() - }; - foreach (MeshFilter meshFilter in meshFilters) { - meshFilter.mesh = mesh; - } - } - public void BuildTexture(byte[] texData, SimTexture simTex, List materials) { - Texture2D tex = new Texture2D(simTex.width, simTex.height, TextureFormat.RGB24, false); - tex.LoadRawTextureData(texData); - tex.Apply(); + void ApplyTransform(Transform uTransform, SimTransform simTrans) + { + uTransform.localPosition = simTrans.GetPos(); + uTransform.localRotation = simTrans.GetRot(); + uTransform.localScale = simTrans.GetScale(); + } - foreach (Material material in materials) { - material.mainTexture = tex; - material.mainTextureScale = new Vector2(simTex.textureSize[0], simTex.textureSize[1]); - } - } + GameObject CreateObject(Transform root, SimBody body) + { + + GameObject bodyRoot = new GameObject(body.name); + bodyRoot.transform.SetParent(root, false); + ApplyTransform(bodyRoot.transform, body.trans); + if (body.visuals.Count != 0) + { + GameObject VisualContainer = new GameObject($"{body.name}_Visuals"); + VisualContainer.transform.SetParent(bodyRoot.transform, false); + foreach (SimVisual visual in body.visuals) + { + GameObject visualObj; + switch (visual.type) + { + case "MESH": + { + SimMesh simMesh = visual.mesh; + visualObj = new GameObject(simMesh.hash, typeof(MeshFilter), typeof(MeshRenderer)); + if (!_pendingMesh.ContainsKey(simMesh.hash)) + { + _pendingMesh[simMesh.hash] = new List>(); + } + _pendingMesh[simMesh.hash].Add(new(simMesh, visualObj.GetComponent())); + break; + } + case "CUBE": + visualObj = GameObject.CreatePrimitive(PrimitiveType.Cube); + break; + case "PLANE": + visualObj = GameObject.CreatePrimitive(PrimitiveType.Plane); + break; + case "CYLINDER": + visualObj = GameObject.CreatePrimitive(PrimitiveType.Cylinder); + break; + case "CAPSULE": + visualObj = GameObject.CreatePrimitive(PrimitiveType.Capsule); + break; + case "SPHERE": + visualObj = GameObject.CreatePrimitive(PrimitiveType.Sphere); + break; + default: + Debug.LogWarning("Invalid visual, " + visual.type + body.name); + visualObj = GameObject.CreatePrimitive(PrimitiveType.Sphere); + break; + } + Renderer renderer = visualObj.GetComponent(); + if (visual.material != null) + { + renderer.material = BuildMaterial(visual.material, body.name); + } + else + { + Debug.LogWarning($"Material of {body.name}_Visuals not found"); + } + visualObj.transform.SetParent(VisualContainer.transform, false); + ApplyTransform(visualObj.transform, visual.trans); + } + } + body.children.ForEach(body => CreateObject(bodyRoot.transform, body)); + if (_simObjTrans.ContainsKey(body.name)) + { + Debug.LogWarning($"Duplicate object name found: {body.name}"); + _simObjTrans.Remove(body.name); + } + _simObjTrans.Add(body.name, bodyRoot.transform); + return bodyRoot; + } + + void ClearScene() + { + OnSceneCleared.Invoke(); + if (_simSceneObj != null) Destroy(_simSceneObj); + _pendingMesh.Clear(); + _pendingTexture.Clear(); + _simObjTrans.Clear(); + Debug.Log("Scene Cleared"); + } + + public Material BuildMaterial(SimMaterial simMat, string objName) + { + Material mat = new Material(defaultMaterial); + if (simMat.color.Count == 3) + { + simMat.color.Add(1.0f); + } + else if (simMat.color.Count != 4) + { + Debug.LogWarning($"Invalid color for {objName}"); + simMat.emissionColor = new List { 1.0f, 1.0f, 1.0f, 1.0f }; + } + // Transparency + if (simMat.color[3] < 1) + { + mat.SetFloat("_Mode", 2); + mat.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha); + mat.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha); + mat.SetInt("_ZWrite", 0); + mat.DisableKeyword("_ALPHATEST_ON"); + mat.EnableKeyword("_ALPHABLEND_ON"); + mat.DisableKeyword("_ALPHAPREMULTIPLY_ON"); + mat.renderQueue = 3000; + } + mat.SetColor("_Color", new Color(simMat.color[0], simMat.color[1], simMat.color[2], simMat.color[3])); + if (simMat.emissionColor != null) + { + mat.SetColor("_emissionColor", new Color(simMat.emissionColor[0], simMat.emissionColor[1], simMat.emissionColor[2], simMat.emissionColor[3])); + } + mat.SetFloat("_specularHighlights", simMat.specular); + mat.SetFloat("_Smoothness", simMat.shininess); + mat.SetFloat("_GlossyReflections", simMat.reflectance); + if (simMat.texture != null) + { + // Debug.Log($"Texture found for {objName}"); + SimTexture simTex = simMat.texture; + if (!_pendingTexture.ContainsKey(simTex.hash)) + { + _pendingTexture[simTex.hash] = new(); + } + _pendingTexture[simTex.hash].Add(new(simTex, mat)); + } + return mat; + } + + public static T[] DecodeArray(byte[] data, int start, int length) where T : struct + { + return MemoryMarshal.Cast(new ReadOnlySpan(data, start, length)).ToArray(); + } + + public void BuildMesh(byte[] meshData, SimMesh simMesh, MeshFilter meshFilter) + { + meshFilter.mesh = new Mesh + { + vertices = DecodeArray(meshData, simMesh.verticesLayout[0], simMesh.verticesLayout[1]), + normals = DecodeArray(meshData, simMesh.normalsLayout[0], simMesh.normalsLayout[1]), + triangles = DecodeArray(meshData, simMesh.indicesLayout[0], simMesh.indicesLayout[1]), + uv = DecodeArray(meshData, simMesh.uvLayout[0], simMesh.uvLayout[1]) + }; + } + + public void BuildTexture(byte[] texData, SimTexture simTex, Material material) + { + Texture2D tex = new Texture2D(simTex.width, simTex.height, TextureFormat.RGB24, false); + tex.LoadRawTextureData(texData); + tex.Apply(); + material.mainTexture = tex; + material.mainTextureScale = new Vector2(simTex.textureScale[0], simTex.textureScale[1]); + } - public Dictionary GetObjectsTrans() { - return _simObjTrans; - } + public Dictionary GetObjectsTrans() + { + return _simObjTrans; + } - public GameObject GetSimObject() { - return _simSceneObj; - } + public GameObject GetSimObject() + { + return _simSceneObj; + } + } } \ No newline at end of file diff --git a/Packages/manifest.json b/Packages/manifest.json index 15b86b9..9e04ae6 100644 --- a/Packages/manifest.json +++ b/Packages/manifest.json @@ -1,14 +1,15 @@ { "dependencies": { "com.github-glitchenzo.nugetforunity": "https://github.com/GlitchEnzo/NuGetForUnity.git?path=/src/NuGetForUnity", - "com.unity.collab-proxy": "2.3.1", - "com.unity.feature.development": "1.0.1", + "com.unity.collab-proxy": "2.5.2", + "com.unity.feature.development": "1.0.2", + "com.unity.multiplayer.center": "1.0.0", "com.unity.nuget.newtonsoft-json": "3.2.1", - "com.unity.textmeshpro": "3.0.6", - "com.unity.timeline": "1.7.6", + "com.unity.timeline": "1.8.7", "com.unity.toolchain.linux-x86_64": "2.0.10", - "com.unity.ugui": "1.0.0", + "com.unity.ugui": "2.0.0", "com.unity.visualscripting": "1.9.4", + "com.unity.modules.accessibility": "1.0.0", "com.unity.modules.ai": "1.0.0", "com.unity.modules.androidjni": "1.0.0", "com.unity.modules.animation": "1.0.0", diff --git a/Packages/packages-lock.json b/Packages/packages-lock.json index 85b0e73..ff98427 100644 --- a/Packages/packages-lock.json +++ b/Packages/packages-lock.json @@ -8,7 +8,7 @@ "hash": "a0bb5566082ff88e5e6ebfb850dafe7fc63b0895" }, "com.unity.collab-proxy": { - "version": "2.3.1", + "version": "2.5.2", "depth": 0, "source": "registry", "dependencies": {}, @@ -22,28 +22,27 @@ "url": "https://packages.unity.com" }, "com.unity.ext.nunit": { - "version": "1.0.6", + "version": "2.0.5", "depth": 2, "source": "registry", "dependencies": {}, "url": "https://packages.unity.com" }, "com.unity.feature.development": { - "version": "1.0.1", + "version": "1.0.2", "depth": 0, "source": "builtin", "dependencies": { "com.unity.ide.visualstudio": "2.0.22", - "com.unity.ide.rider": "3.0.28", - "com.unity.ide.vscode": "1.2.5", + "com.unity.ide.rider": "3.0.31", "com.unity.editorcoroutines": "1.0.0", "com.unity.performance.profile-analyzer": "1.2.2", - "com.unity.test-framework": "1.1.33", - "com.unity.testtools.codecoverage": "1.2.5" + "com.unity.test-framework": "1.4.5", + "com.unity.testtools.codecoverage": "1.2.6" } }, "com.unity.ide.rider": { - "version": "3.0.28", + "version": "3.0.31", "depth": 1, "source": "registry", "dependencies": { @@ -60,12 +59,13 @@ }, "url": "https://packages.unity.com" }, - "com.unity.ide.vscode": { - "version": "1.2.5", - "depth": 1, - "source": "registry", - "dependencies": {}, - "url": "https://packages.unity.com" + "com.unity.multiplayer.center": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.uielements": "1.0.0" + } }, "com.unity.nuget.newtonsoft-json": { "version": "3.2.1", @@ -105,18 +105,18 @@ "url": "https://packages.unity.com" }, "com.unity.test-framework": { - "version": "1.1.33", + "version": "1.4.5", "depth": 1, "source": "registry", "dependencies": { - "com.unity.ext.nunit": "1.0.6", + "com.unity.ext.nunit": "2.0.3", "com.unity.modules.imgui": "1.0.0", "com.unity.modules.jsonserialize": "1.0.0" }, "url": "https://packages.unity.com" }, "com.unity.testtools.codecoverage": { - "version": "1.2.5", + "version": "1.2.6", "depth": 1, "source": "registry", "dependencies": { @@ -125,23 +125,14 @@ }, "url": "https://packages.unity.com" }, - "com.unity.textmeshpro": { - "version": "3.0.6", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.ugui": "1.0.0" - }, - "url": "https://packages.unity.com" - }, "com.unity.timeline": { - "version": "1.7.6", + "version": "1.8.7", "depth": 0, "source": "registry", "dependencies": { + "com.unity.modules.audio": "1.0.0", "com.unity.modules.director": "1.0.0", "com.unity.modules.animation": "1.0.0", - "com.unity.modules.audio": "1.0.0", "com.unity.modules.particlesystem": "1.0.0" }, "url": "https://packages.unity.com" @@ -157,7 +148,7 @@ "url": "https://packages.unity.com" }, "com.unity.ugui": { - "version": "1.0.0", + "version": "2.0.0", "depth": 0, "source": "builtin", "dependencies": { @@ -175,6 +166,12 @@ }, "url": "https://packages.unity.com" }, + "com.unity.modules.accessibility": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, "com.unity.modules.ai": { "version": "1.0.0", "depth": 0, @@ -222,6 +219,12 @@ "com.unity.modules.animation": "1.0.0" } }, + "com.unity.modules.hierarchycore": { + "version": "1.0.0", + "depth": 1, + "source": "builtin", + "dependencies": {} + }, "com.unity.modules.imageconversion": { "version": "1.0.0", "depth": 0, @@ -310,7 +313,8 @@ "dependencies": { "com.unity.modules.ui": "1.0.0", "com.unity.modules.imgui": "1.0.0", - "com.unity.modules.jsonserialize": "1.0.0" + "com.unity.modules.jsonserialize": "1.0.0", + "com.unity.modules.hierarchycore": "1.0.0" } }, "com.unity.modules.umbra": { diff --git a/ProjectSettings/MultiplayerManager.asset b/ProjectSettings/MultiplayerManager.asset new file mode 100644 index 0000000..2a93664 --- /dev/null +++ b/ProjectSettings/MultiplayerManager.asset @@ -0,0 +1,7 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!655991488 &1 +MultiplayerManager: + m_ObjectHideFlags: 0 + m_EnableMultiplayerRoles: 0 + m_StrippingTypes: {} diff --git a/ProjectSettings/ProjectVersion.txt b/ProjectSettings/ProjectVersion.txt index e08682f..2958948 100644 --- a/ProjectSettings/ProjectVersion.txt +++ b/ProjectSettings/ProjectVersion.txt @@ -1,2 +1,2 @@ -m_EditorVersion: 2022.3.32f1 -m_EditorVersionWithRevision: 2022.3.32f1 (c8300dc0a3fa) +m_EditorVersion: 6000.0.24f1 +m_EditorVersionWithRevision: 6000.0.24f1 (11fa355cd605) diff --git a/ProjectSettings/SceneTemplateSettings.json b/ProjectSettings/SceneTemplateSettings.json index 6f3e60f..6ed312a 100644 --- a/ProjectSettings/SceneTemplateSettings.json +++ b/ProjectSettings/SceneTemplateSettings.json @@ -4,164 +4,123 @@ { "userAdded": false, "type": "UnityEngine.AnimationClip", - "ignore": false, - "defaultInstantiationMode": 0, - "supportsModification": true + "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEditor.Animations.AnimatorController", - "ignore": false, - "defaultInstantiationMode": 0, - "supportsModification": true + "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEngine.AnimatorOverrideController", - "ignore": false, - "defaultInstantiationMode": 0, - "supportsModification": true + "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEditor.Audio.AudioMixerController", - "ignore": false, - "defaultInstantiationMode": 0, - "supportsModification": true + "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEngine.ComputeShader", - "ignore": true, - "defaultInstantiationMode": 1, - "supportsModification": true + "defaultInstantiationMode": 1 }, { "userAdded": false, "type": "UnityEngine.Cubemap", - "ignore": false, - "defaultInstantiationMode": 0, - "supportsModification": true + "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEngine.GameObject", - "ignore": false, - "defaultInstantiationMode": 0, - "supportsModification": true + "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEditor.LightingDataAsset", - "ignore": false, - "defaultInstantiationMode": 0, - "supportsModification": false + "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEngine.LightingSettings", - "ignore": false, - "defaultInstantiationMode": 0, - "supportsModification": true + "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEngine.Material", - "ignore": false, - "defaultInstantiationMode": 0, - "supportsModification": true + "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEditor.MonoScript", - "ignore": true, - "defaultInstantiationMode": 1, - "supportsModification": true + "defaultInstantiationMode": 1 }, { "userAdded": false, "type": "UnityEngine.PhysicMaterial", - "ignore": false, - "defaultInstantiationMode": 0, - "supportsModification": true + "defaultInstantiationMode": 0 + }, + { + "userAdded": false, + "type": "UnityEngine.PhysicsMaterial", + "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEngine.PhysicsMaterial2D", - "ignore": false, - "defaultInstantiationMode": 0, - "supportsModification": true + "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEngine.Rendering.PostProcessing.PostProcessProfile", - "ignore": false, - "defaultInstantiationMode": 0, - "supportsModification": true + "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEngine.Rendering.PostProcessing.PostProcessResources", - "ignore": false, - "defaultInstantiationMode": 0, - "supportsModification": true + "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEngine.Rendering.VolumeProfile", - "ignore": false, - "defaultInstantiationMode": 0, - "supportsModification": true + "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEditor.SceneAsset", - "ignore": false, - "defaultInstantiationMode": 0, - "supportsModification": false + "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEngine.Shader", - "ignore": true, - "defaultInstantiationMode": 1, - "supportsModification": true + "defaultInstantiationMode": 1 }, { "userAdded": false, "type": "UnityEngine.ShaderVariantCollection", - "ignore": true, - "defaultInstantiationMode": 1, - "supportsModification": true + "defaultInstantiationMode": 1 }, { "userAdded": false, "type": "UnityEngine.Texture", - "ignore": false, - "defaultInstantiationMode": 0, - "supportsModification": true + "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEngine.Texture2D", - "ignore": false, - "defaultInstantiationMode": 0, - "supportsModification": true + "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEngine.Timeline.TimelineAsset", - "ignore": false, - "defaultInstantiationMode": 0, - "supportsModification": true + "defaultInstantiationMode": 0 } ], "defaultDependencyTypeInfo": { "userAdded": false, "type": "", - "ignore": false, - "defaultInstantiationMode": 1, - "supportsModification": true + "defaultInstantiationMode": 1 }, "newSceneOverride": 0 } \ No newline at end of file From 0e916314d16e7805513fc885c1e3e5fdf3299d28 Mon Sep 17 00:00:00 2001 From: Xinkai Jiang Date: Sun, 29 Dec 2024 16:27:48 +0100 Subject: [PATCH 2/3] light manager --- Assets/SceneLoader/Scene/SimScene.unity | 122 ++++-------------- Assets/SceneLoader/Scripts/LightManager.cs | 51 ++++++++ .../SceneLoader/Scripts/LightManager.cs.meta | 2 + 3 files changed, 76 insertions(+), 99 deletions(-) create mode 100644 Assets/SceneLoader/Scripts/LightManager.cs create mode 100644 Assets/SceneLoader/Scripts/LightManager.cs.meta diff --git a/Assets/SceneLoader/Scene/SimScene.unity b/Assets/SceneLoader/Scene/SimScene.unity index f205173..d191ba7 100644 --- a/Assets/SceneLoader/Scene/SimScene.unity +++ b/Assets/SceneLoader/Scene/SimScene.unity @@ -119,103 +119,6 @@ NavMeshSettings: debug: m_Flags: 0 m_NavMeshData: {fileID: 0} ---- !u!1 &705507993 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 705507995} - - component: {fileID: 705507994} - m_Layer: 0 - m_Name: DirectionalLight - m_TagString: Untagged - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!108 &705507994 -Light: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 705507993} - m_Enabled: 1 - serializedVersion: 11 - m_Type: 1 - m_Color: {r: 1, g: 1, b: 1, a: 1} - m_Intensity: 1 - m_Range: 10 - m_SpotAngle: 30 - m_InnerSpotAngle: 21.80208 - m_CookieSize: 10 - m_Shadows: - m_Type: 2 - m_Resolution: -1 - m_CustomResolution: -1 - m_Strength: 1 - m_Bias: 0.05 - m_NormalBias: 0.4 - m_NearPlane: 0.2 - m_CullingMatrixOverride: - e00: 1 - e01: 0 - e02: 0 - e03: 0 - e10: 0 - e11: 1 - e12: 0 - e13: 0 - e20: 0 - e21: 0 - e22: 1 - e23: 0 - e30: 0 - e31: 0 - e32: 0 - e33: 1 - m_UseCullingMatrixOverride: 0 - m_Cookie: {fileID: 0} - m_DrawHalo: 0 - m_Flare: {fileID: 0} - m_RenderMode: 0 - m_CullingMask: - serializedVersion: 2 - m_Bits: 4294967295 - m_RenderingLayerMask: 1 - m_Lightmapping: 1 - m_LightShadowCasterMode: 0 - m_AreaSize: {x: 1, y: 1} - m_BounceIntensity: 1 - m_ColorTemperature: 6570 - m_UseColorTemperature: 0 - m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0} - m_UseBoundingSphereOverride: 0 - m_UseViewFrustumForShadowCasterCull: 1 - m_ForceVisible: 0 - m_ShadowRadius: 0 - m_ShadowAngle: 0 - m_LightUnit: 1 - m_LuxAtDistance: 1 - m_EnableSpotReflector: 1 ---- !u!4 &705507995 -Transform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 705507993} - serializedVersion: 2 - m_LocalRotation: {x: 0.92387956, y: 0, z: 0, w: 0.38268343} - m_LocalPosition: {x: 0, y: 3, z: 3} - m_LocalScale: {x: 1, y: 1, z: 1} - m_ConstrainProportionsScale: 0 - m_Children: [] - m_Father: {fileID: 0} - m_LocalEulerAnglesHint: {x: 135, y: 0, z: 0} --- !u!1 &963194225 GameObject: m_ObjectHideFlags: 0 @@ -369,6 +272,25 @@ PrefabInstance: m_AddedGameObjects: [] m_AddedComponents: [] m_SourcePrefab: {fileID: 100100000, guid: 94d5024eac9ef16668f3a4c00717c811, type: 3} +--- !u!1 &1561250201 stripped +GameObject: + m_CorrespondingSourceObject: {fileID: 1715612398501171564, guid: 68b4bc8f9089e32fa861e21c5913e321, type: 3} + m_PrefabInstance: {fileID: 1698246963405722006} + m_PrefabAsset: {fileID: 0} +--- !u!114 &1561250205 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1561250201} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: b19136b440547504b88ed42d968a2431, type: 3} + m_Name: + m_EditorClassIdentifier: + lightIntensity: 0.5 + lightColor: {r: 1, g: 1, b: 1, a: 1} --- !u!1001 &1698246963405722006 PrefabInstance: m_ObjectHideFlags: 0 @@ -436,13 +358,15 @@ PrefabInstance: m_RemovedComponents: [] m_RemovedGameObjects: [] m_AddedGameObjects: [] - m_AddedComponents: [] + m_AddedComponents: + - targetCorrespondingSourceObject: {fileID: 1715612398501171564, guid: 68b4bc8f9089e32fa861e21c5913e321, type: 3} + insertIndex: -1 + addedObject: {fileID: 1561250205} m_SourcePrefab: {fileID: 100100000, guid: 68b4bc8f9089e32fa861e21c5913e321, type: 3} --- !u!1660057539 &9223372036854775807 SceneRoots: m_ObjectHideFlags: 0 m_Roots: - {fileID: 963194228} - - {fileID: 705507995} - {fileID: 1058704602} - {fileID: 1698246963405722006} diff --git a/Assets/SceneLoader/Scripts/LightManager.cs b/Assets/SceneLoader/Scripts/LightManager.cs new file mode 100644 index 0000000..a9471bb --- /dev/null +++ b/Assets/SceneLoader/Scripts/LightManager.cs @@ -0,0 +1,51 @@ +using UnityEngine; + +namespace IRXR.SceneLoader +{ + public class LightManager : MonoBehaviour + { + [Header("Light Settings")] + public float lightIntensity = 0.5f; // Intensity of each light + public Color lightColor = Color.white; // Color of the lights + + private Vector3[] lightPositions = new Vector3[] + { + new Vector3(-10, 10, -10), + new Vector3(10, 10, -10), + new Vector3(-10, 10, 10), + new Vector3(10, 10, 10) + }; + + private void Start() + { + CreateLights(); + } + + private void CreateLights() + { + for (int i = 0; i < lightPositions.Length; i++) + { + // Create a new GameObject for the light + GameObject lightObject = new GameObject($"DirectionalLight_{i + 1}"); + + // Add a Light component + Light light = lightObject.AddComponent(); + light.type = LightType.Directional; + light.intensity = lightIntensity; + light.color = lightColor; + light.shadows = LightShadows.None; // Disable shadows + + // Set position of the light + lightObject.transform.position = lightPositions[i]; + + // Make the light aim at the origin + lightObject.transform.LookAt(Vector3.zero); + + // Parent the light object under this GameObject + lightObject.transform.parent = this.transform; + } + + Debug.Log("Directional lights created by LightManager."); + } + } +} diff --git a/Assets/SceneLoader/Scripts/LightManager.cs.meta b/Assets/SceneLoader/Scripts/LightManager.cs.meta new file mode 100644 index 0000000..763c789 --- /dev/null +++ b/Assets/SceneLoader/Scripts/LightManager.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b19136b440547504b88ed42d968a2431 \ No newline at end of file From 4172ee81726019145b8a112e17e4efe9ca4384c4 Mon Sep 17 00:00:00 2001 From: Xinkai Jiang Date: Sun, 29 Dec 2024 21:32:29 +0100 Subject: [PATCH 3/3] update the SimScene example --- Assets/SceneLoader/Prefabs/SimScene.prefab | 15 ++++++++++++++ Assets/SceneLoader/Scene/SimScene.unity | 24 +--------------------- 2 files changed, 16 insertions(+), 23 deletions(-) diff --git a/Assets/SceneLoader/Prefabs/SimScene.prefab b/Assets/SceneLoader/Prefabs/SimScene.prefab index 8b6bfe2..73d5337 100644 --- a/Assets/SceneLoader/Prefabs/SimScene.prefab +++ b/Assets/SceneLoader/Prefabs/SimScene.prefab @@ -11,6 +11,7 @@ GameObject: - component: {fileID: 2085763002845073359} - component: {fileID: 1817450283232457565} - component: {fileID: 5552327243606421420} + - component: {fileID: 7136934837751640320} m_Layer: 0 m_Name: SimScene m_TagString: Untagged @@ -58,3 +59,17 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 43b973fe0a32ff894963acb23af7fd0e, type: 3} m_Name: m_EditorClassIdentifier: +--- !u!114 &7136934837751640320 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1715612398501171564} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: b19136b440547504b88ed42d968a2431, type: 3} + m_Name: + m_EditorClassIdentifier: + lightIntensity: 0.5 + lightColor: {r: 1, g: 1, b: 1, a: 1} diff --git a/Assets/SceneLoader/Scene/SimScene.unity b/Assets/SceneLoader/Scene/SimScene.unity index d191ba7..eaf6c2b 100644 --- a/Assets/SceneLoader/Scene/SimScene.unity +++ b/Assets/SceneLoader/Scene/SimScene.unity @@ -272,25 +272,6 @@ PrefabInstance: m_AddedGameObjects: [] m_AddedComponents: [] m_SourcePrefab: {fileID: 100100000, guid: 94d5024eac9ef16668f3a4c00717c811, type: 3} ---- !u!1 &1561250201 stripped -GameObject: - m_CorrespondingSourceObject: {fileID: 1715612398501171564, guid: 68b4bc8f9089e32fa861e21c5913e321, type: 3} - m_PrefabInstance: {fileID: 1698246963405722006} - m_PrefabAsset: {fileID: 0} ---- !u!114 &1561250205 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1561250201} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: b19136b440547504b88ed42d968a2431, type: 3} - m_Name: - m_EditorClassIdentifier: - lightIntensity: 0.5 - lightColor: {r: 1, g: 1, b: 1, a: 1} --- !u!1001 &1698246963405722006 PrefabInstance: m_ObjectHideFlags: 0 @@ -358,10 +339,7 @@ PrefabInstance: m_RemovedComponents: [] m_RemovedGameObjects: [] m_AddedGameObjects: [] - m_AddedComponents: - - targetCorrespondingSourceObject: {fileID: 1715612398501171564, guid: 68b4bc8f9089e32fa861e21c5913e321, type: 3} - insertIndex: -1 - addedObject: {fileID: 1561250205} + m_AddedComponents: [] m_SourcePrefab: {fileID: 100100000, guid: 68b4bc8f9089e32fa861e21c5913e321, type: 3} --- !u!1660057539 &9223372036854775807 SceneRoots: