diff --git a/Assets/Plugins/ImmersalSDK/Runtime.meta b/Assets/Plugins/ImmersalSDK/Runtime.meta new file mode 100644 index 0000000..5e6f97d --- /dev/null +++ b/Assets/Plugins/ImmersalSDK/Runtime.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9c0884dab4c8bbc4d8a979f9838b8cbe +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/ImmersalSDK/Runtime/Core.meta b/Assets/Plugins/ImmersalSDK/Runtime/Core.meta new file mode 100644 index 0000000..d320059 --- /dev/null +++ b/Assets/Plugins/ImmersalSDK/Runtime/Core.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d9779e2a208874279abf2acd33869873 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins.meta b/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins.meta new file mode 100644 index 0000000..3f5cf22 --- /dev/null +++ b/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d4e1b058addf948b589443857632c8e7 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/Android.meta b/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/Android.meta new file mode 100644 index 0000000..3d63d3a --- /dev/null +++ b/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/Android.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: f192b64343523994e999fa8a5c964120 +folderAsset: yes +timeCreated: 1466076008 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/Android/arm64-v8a.meta b/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/Android/arm64-v8a.meta new file mode 100644 index 0000000..c9480c3 --- /dev/null +++ b/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/Android/arm64-v8a.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e3f6095e1f6cb45b399b9c5a3def17f9 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/Android/arm64-v8a/.DS_Store b/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/Android/arm64-v8a/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/Android/arm64-v8a/.DS_Store differ diff --git a/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/Android/arm64-v8a/libPosePlugin.so b/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/Android/arm64-v8a/libPosePlugin.so new file mode 100644 index 0000000..cf85c80 Binary files /dev/null and b/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/Android/arm64-v8a/libPosePlugin.so differ diff --git a/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/Android/arm64-v8a/libPosePlugin.so.meta b/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/Android/arm64-v8a/libPosePlugin.so.meta new file mode 100644 index 0000000..cb2c621 --- /dev/null +++ b/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/Android/arm64-v8a/libPosePlugin.so.meta @@ -0,0 +1,80 @@ +fileFormatVersion: 2 +guid: 9571c8196841b422bb87ee1f1f937e39 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 0 + Exclude Editor: 1 + Exclude Linux64: 1 + Exclude OSXUniversal: 1 + Exclude Win: 1 + Exclude Win64: 1 + Exclude iOS: 1 + - first: + Android: Android + second: + enabled: 1 + settings: + CPU: ARM64 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + CPU: AnyCPU + DefaultValueInitialized: true + OS: AnyOS + - first: + Standalone: Linux64 + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: OSXUniversal + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win64 + second: + enabled: 0 + settings: + CPU: None + - first: + iPhone: iOS + second: + enabled: 0 + settings: + AddToEmbeddedBinaries: false + CPU: AnyCPU + CompileFlags: + FrameworkDependencies: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/Android/arm64-v8a/libRokidLocalizer.so b/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/Android/arm64-v8a/libRokidLocalizer.so new file mode 100644 index 0000000..be5a2cd Binary files /dev/null and b/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/Android/arm64-v8a/libRokidLocalizer.so differ diff --git a/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/Android/arm64-v8a/libRokidLocalizer.so.meta b/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/Android/arm64-v8a/libRokidLocalizer.so.meta new file mode 100644 index 0000000..08b3dac --- /dev/null +++ b/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/Android/arm64-v8a/libRokidLocalizer.so.meta @@ -0,0 +1,80 @@ +fileFormatVersion: 2 +guid: ec9137557106b48d8995616414845b71 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 0 + Exclude Editor: 1 + Exclude Linux64: 1 + Exclude OSXUniversal: 1 + Exclude Win: 1 + Exclude Win64: 1 + Exclude iOS: 1 + - first: + Android: Android + second: + enabled: 1 + settings: + CPU: ARM64 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + CPU: AnyCPU + DefaultValueInitialized: true + OS: AnyOS + - first: + Standalone: Linux64 + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: OSXUniversal + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win64 + second: + enabled: 0 + settings: + CPU: None + - first: + iPhone: iOS + second: + enabled: 0 + settings: + AddToEmbeddedBinaries: false + CPU: AnyCPU + CompileFlags: + FrameworkDependencies: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/Android/nativebindings.aar b/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/Android/nativebindings.aar new file mode 100644 index 0000000..0d3ffd5 Binary files /dev/null and b/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/Android/nativebindings.aar differ diff --git a/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/Android/nativebindings.aar.meta b/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/Android/nativebindings.aar.meta new file mode 100644 index 0000000..050dd1c --- /dev/null +++ b/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/Android/nativebindings.aar.meta @@ -0,0 +1,32 @@ +fileFormatVersion: 2 +guid: c0353ebb74a494fc38f3854047d3a2cd +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Android: Android + second: + enabled: 1 + settings: {} + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/NativeBindings.cs b/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/NativeBindings.cs new file mode 100644 index 0000000..a055300 --- /dev/null +++ b/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/NativeBindings.cs @@ -0,0 +1,129 @@ +/*=============================================================================== +Copyright (C) 2023 Immersal - Part of Hexagon. All Rights Reserved. + +This file is part of the Immersal SDK. + +The Immersal SDK cannot be copied, distributed, or made available to +third-parties for commercial purposes without written permission of Immersal Ltd. + +Contact sales@immersal.com for licensing requests. +===============================================================================*/ + +#if (UNITY_IOS || PLATFORM_ANDROID) && !UNITY_EDITOR +using System.Runtime.InteropServices; +using UnityEngine; + +namespace Immersal +{ + public class NativeBindings + { + #if UNITY_IOS + [DllImport("__Internal")] + public static extern void startLocation(); + + [DllImport("__Internal")] + public static extern void stopLocation(); + + [DllImport("__Internal")] + public static extern double getLatitude(); + + [DllImport("__Internal")] + public static extern double getLongitude(); + + [DllImport("__Internal")] + public static extern double getAltitude(); + + [DllImport("__Internal")] + public static extern double getHorizontalAccuracy(); + + [DllImport("__Internal")] + public static extern double getVerticalAccuracy(); + + [DllImport("__Internal")] + public static extern bool locationServicesEnabled(); + + #elif PLATFORM_ANDROID + static AndroidJavaClass obj = new AndroidJavaClass("com.immersal.nativebindings.Main"); + #endif + + public static bool StartLocation() + { + if (!Input.location.isEnabledByUser) + { + return false; + } + + #if UNITY_IOS + startLocation(); + #elif PLATFORM_ANDROID + obj.CallStatic("startLocation"); + #endif + + return true; + } + + public static void StopLocation() + { + #if UNITY_IOS + stopLocation(); + #elif PLATFORM_ANDROID + obj.CallStatic("stopLocation"); + #endif + } + + public static double GetLatitude() + { + #if UNITY_IOS + return getLatitude(); + #elif PLATFORM_ANDROID + return obj.CallStatic("getLatitude"); + #endif + } + + public static double GetLongitude() + { + #if UNITY_IOS + return getLongitude(); + #elif PLATFORM_ANDROID + return obj.CallStatic("getLongitude"); + #endif + } + + public static double GetAltitude() + { + #if UNITY_IOS + return getAltitude(); + #elif PLATFORM_ANDROID + return obj.CallStatic("getAltitude"); + #endif + } + + public static double GetHorizontalAccuracy() + { + #if UNITY_IOS + return getHorizontalAccuracy(); + #elif PLATFORM_ANDROID + return obj.CallStatic("getHorizontalAccuracy"); + #endif + } + + public static double GetVerticalAccuracy() + { + #if UNITY_IOS + return getVerticalAccuracy(); + #elif PLATFORM_ANDROID + return obj.CallStatic("getVerticalAccuracy"); + #endif + } + + public static bool LocationServicesEnabled() + { + #if UNITY_IOS + return locationServicesEnabled(); + #elif PLATFORM_ANDROID + return obj.CallStatic("locationServicesEnabled"); + #endif + } + } +} +#endif \ No newline at end of file diff --git a/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/NativeBindings.cs.meta b/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/NativeBindings.cs.meta new file mode 100644 index 0000000..4d3a25f --- /dev/null +++ b/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/NativeBindings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1f141c749ad8c4a4dbdd7b392e91a9c3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/x86_64.meta b/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/x86_64.meta new file mode 100644 index 0000000..7a55115 --- /dev/null +++ b/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/x86_64.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 867bc362f511875419b00825c35adb62 +folderAsset: yes +timeCreated: 1469455304 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/x86_64/PosePlugin.bundle.meta b/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/x86_64/PosePlugin.bundle.meta new file mode 100644 index 0000000..562feac --- /dev/null +++ b/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/x86_64/PosePlugin.bundle.meta @@ -0,0 +1,128 @@ +fileFormatVersion: 2 +guid: 0a60313de0e704fd682e181e56ca94a7 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 1 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 1 + Exclude Editor: 0 + Exclude Linux: 1 + Exclude Linux64: 0 + Exclude LinuxUniversal: 0 + Exclude Lumin: 1 + Exclude OSXIntel: 1 + Exclude OSXIntel64: 0 + Exclude OSXUniversal: 0 + Exclude Win: 1 + Exclude Win64: 0 + Exclude iOS: 1 + - first: + : Editor + second: + enabled: 0 + settings: + CPU: x86_64 + OS: OSX + - first: + Android: Android + second: + enabled: 0 + settings: + CPU: ARMv7 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 1 + settings: + CPU: AnyCPU + DefaultValueInitialized: true + OS: OSX + - first: + Facebook: Win + second: + enabled: 0 + settings: + CPU: None + - first: + Facebook: Win64 + second: + enabled: 1 + settings: + CPU: AnyCPU + - first: + Standalone: Linux + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Linux64 + second: + enabled: 1 + settings: + CPU: AnyCPU + - first: + Standalone: LinuxUniversal + second: + enabled: 1 + settings: + CPU: x86_64 + - first: + Standalone: OSXIntel + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: OSXIntel64 + second: + enabled: 1 + settings: + CPU: AnyCPU + - first: + Standalone: OSXUniversal + second: + enabled: 1 + settings: + CPU: AnyCPU + - first: + Standalone: Win + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win64 + second: + enabled: 1 + settings: + CPU: AnyCPU + - first: + iPhone: iOS + second: + enabled: 0 + settings: + AddToEmbeddedBinaries: false + CPU: AnyCPU + CompileFlags: + FrameworkDependencies: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/x86_64/PosePlugin.bundle/Contents/Info.plist b/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/x86_64/PosePlugin.bundle/Contents/Info.plist new file mode 100644 index 0000000..404bb35 --- /dev/null +++ b/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/x86_64/PosePlugin.bundle/Contents/Info.plist @@ -0,0 +1,50 @@ + + + + + BuildMachineOSBuild + 22D68 + CFBundleDevelopmentRegion + en + CFBundleExecutable + PosePlugin + CFBundleIdentifier + com.immersal.PosePlugin + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + PosePlugin + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleSupportedPlatforms + + MacOSX + + CFBundleVersion + 1 + DTCompiler + com.apple.compilers.llvm.clang.1_0 + DTPlatformBuild + + DTPlatformName + macosx + DTPlatformVersion + 13.3 + DTSDKBuild + 22E245 + DTSDKName + macosx13.3 + DTXcode + 1430 + DTXcodeBuild + 14E222b + LSMinimumSystemVersion + 12.0 + NSHumanReadableCopyright + Copyright © 2022 Immersal - Part of Hexagon. All rights reserved. + + diff --git a/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/x86_64/PosePlugin.bundle/Contents/MacOS/PosePlugin b/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/x86_64/PosePlugin.bundle/Contents/MacOS/PosePlugin new file mode 100644 index 0000000..b87f899 Binary files /dev/null and b/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/x86_64/PosePlugin.bundle/Contents/MacOS/PosePlugin differ diff --git a/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/x86_64/PosePlugin.bundle/Contents/_CodeSignature/CodeResources b/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/x86_64/PosePlugin.bundle/Contents/_CodeSignature/CodeResources new file mode 100644 index 0000000..d5d0fd7 --- /dev/null +++ b/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/x86_64/PosePlugin.bundle/Contents/_CodeSignature/CodeResources @@ -0,0 +1,115 @@ + + + + + files + + files2 + + rules + + ^Resources/ + + ^Resources/.*\.lproj/ + + optional + + weight + 1000 + + ^Resources/.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Resources/Base\.lproj/ + + weight + 1010 + + ^version.plist$ + + + rules2 + + .*\.dSYM($|/) + + weight + 11 + + ^(.*/)?\.DS_Store$ + + omit + + weight + 2000 + + ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/ + + nested + + weight + 10 + + ^.* + + ^Info\.plist$ + + omit + + weight + 20 + + ^PkgInfo$ + + omit + + weight + 20 + + ^Resources/ + + weight + 20 + + ^Resources/.*\.lproj/ + + optional + + weight + 1000 + + ^Resources/.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Resources/Base\.lproj/ + + weight + 1010 + + ^[^/]+$ + + nested + + weight + 10 + + ^embedded\.provisionprofile$ + + weight + 20 + + ^version\.plist$ + + weight + 20 + + + + diff --git a/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/x86_64/PosePlugin.dll b/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/x86_64/PosePlugin.dll new file mode 100644 index 0000000..b91aea4 Binary files /dev/null and b/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/x86_64/PosePlugin.dll differ diff --git a/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/x86_64/PosePlugin.dll.meta b/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/x86_64/PosePlugin.dll.meta new file mode 100644 index 0000000..aedfc54 --- /dev/null +++ b/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/x86_64/PosePlugin.dll.meta @@ -0,0 +1,116 @@ +fileFormatVersion: 2 +guid: b3ad48f0b80387d47808cea0b43f73fb +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 1 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 1 + Exclude Editor: 0 + Exclude Linux64: 0 + Exclude OSXUniversal: 1 + Exclude Win: 1 + Exclude Win64: 0 + Exclude iOS: 1 + - first: + : Linux + second: + enabled: 0 + settings: + CPU: None + - first: + : LinuxUniversal + second: + enabled: 0 + settings: + CPU: x86_64 + - first: + : OSXIntel + second: + enabled: 0 + settings: + CPU: None + - first: + : OSXIntel64 + second: + enabled: 1 + settings: + CPU: AnyCPU + - first: + Android: Android + second: + enabled: 0 + settings: + CPU: ARMv7 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 1 + settings: + CPU: x86_64 + DefaultValueInitialized: true + OS: Windows + - first: + Facebook: Win + second: + enabled: 0 + settings: + CPU: None + - first: + Facebook: Win64 + second: + enabled: 1 + settings: + CPU: AnyCPU + - first: + Standalone: Linux64 + second: + enabled: 1 + settings: + CPU: AnyCPU + - first: + Standalone: OSXUniversal + second: + enabled: 0 + settings: + CPU: x86_64 + - first: + Standalone: Win + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win64 + second: + enabled: 1 + settings: + CPU: AnyCPU + - first: + iPhone: iOS + second: + enabled: 0 + settings: + AddToEmbeddedBinaries: false + CPU: AnyCPU + CompileFlags: + FrameworkDependencies: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/x86_64/opencv_world453.dll b/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/x86_64/opencv_world453.dll new file mode 100644 index 0000000..a4907f9 Binary files /dev/null and b/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/x86_64/opencv_world453.dll differ diff --git a/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/x86_64/opencv_world453.dll.meta b/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/x86_64/opencv_world453.dll.meta new file mode 100644 index 0000000..be8b17a --- /dev/null +++ b/Assets/Plugins/ImmersalSDK/Runtime/Core/Plugins/x86_64/opencv_world453.dll.meta @@ -0,0 +1,81 @@ +fileFormatVersion: 2 +guid: 9af32ce34e3c09740a2296f38904f864 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 1 + Exclude Editor: 0 + Exclude Linux64: 0 + Exclude Lumin: 1 + Exclude OSXUniversal: 0 + Exclude Win: 0 + Exclude Win64: 0 + Exclude iOS: 1 + - first: + Android: Android + second: + enabled: 0 + settings: + CPU: ARMv7 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 1 + settings: + CPU: x86_64 + DefaultValueInitialized: true + OS: Windows + - first: + Standalone: Linux64 + second: + enabled: 1 + settings: + CPU: AnyCPU + - first: + Standalone: OSXUniversal + second: + enabled: 1 + settings: + CPU: x86_64 + - first: + Standalone: Win + second: + enabled: 1 + settings: + CPU: None + - first: + Standalone: Win64 + second: + enabled: 1 + settings: + CPU: AnyCPU + - first: + iPhone: iOS + second: + enabled: 0 + settings: + AddToEmbeddedBinaries: false + CPU: AnyCPU + CompileFlags: + FrameworkDependencies: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/ImmersalSDK/Runtime/Core/Prefabs.meta b/Assets/Plugins/ImmersalSDK/Runtime/Core/Prefabs.meta new file mode 100644 index 0000000..bab33a3 --- /dev/null +++ b/Assets/Plugins/ImmersalSDK/Runtime/Core/Prefabs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: bba6e78353c194c489388f40a4f2bd6c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/ImmersalSDK/Runtime/Core/Prefabs/ImmersalSDK.prefab b/Assets/Plugins/ImmersalSDK/Runtime/Core/Prefabs/ImmersalSDK.prefab new file mode 100644 index 0000000..12b3508 --- /dev/null +++ b/Assets/Plugins/ImmersalSDK/Runtime/Core/Prefabs/ImmersalSDK.prefab @@ -0,0 +1,78 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &1434895627878410 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4682760393691964} + - component: {fileID: 8869940821398010349} + - component: {fileID: 7715055325084305990} + m_Layer: 0 + m_Name: ImmersalSDK + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &4682760393691964 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1434895627878410} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &8869940821398010349 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1434895627878410} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: bc609bf82f8e346d593bc1a41c2b7cb8, type: 3} + m_Name: + m_EditorClassIdentifier: + m_AndroidResolution: 2 + m_Downsample: 1 + onPoseLost: + m_PersistentCalls: + m_Calls: [] + onPoseFound: + m_PersistentCalls: + m_Calls: [] + secondsToDecayPose: 10 +--- !u!114 &7715055325084305990 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1434895627878410} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: da24111645ab24d549a37ea7da86304b, type: 3} + m_Name: + m_EditorClassIdentifier: + m_AutoStart: 1 + localizationInterval: 2 + m_UseFiltering: 1 + m_ResetOnMapChange: 0 + m_BurstMode: 1 + m_EnableLogging: 0 + MultipleLocalizationsCount: 20 + OnMultipleLocalizations: + m_PersistentCalls: + m_Calls: [] diff --git a/Assets/Plugins/ImmersalSDK/Runtime/Core/Prefabs/ImmersalSDK.prefab.meta b/Assets/Plugins/ImmersalSDK/Runtime/Core/Prefabs/ImmersalSDK.prefab.meta new file mode 100644 index 0000000..7e61dab --- /dev/null +++ b/Assets/Plugins/ImmersalSDK/Runtime/Core/Prefabs/ImmersalSDK.prefab.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 181e478384bef4862b94db5a060df1b6 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 100100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/ImmersalSDK/Runtime/Core/Resources.meta b/Assets/Plugins/ImmersalSDK/Runtime/Core/Resources.meta new file mode 100644 index 0000000..fb0e5f0 --- /dev/null +++ b/Assets/Plugins/ImmersalSDK/Runtime/Core/Resources.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9e40124e0b172426ba7c8c93d75c788b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/ImmersalSDK/Runtime/Core/Resources/Shaders.meta b/Assets/Plugins/ImmersalSDK/Runtime/Core/Resources/Shaders.meta new file mode 100644 index 0000000..49ad600 --- /dev/null +++ b/Assets/Plugins/ImmersalSDK/Runtime/Core/Resources/Shaders.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 30df7aac5e0027a46a0a8d0a3afc1f6a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/ImmersalSDK/Runtime/Core/Resources/Shaders/pointCloud.shader b/Assets/Plugins/ImmersalSDK/Runtime/Core/Resources/Shaders/pointCloud.shader new file mode 100644 index 0000000..bd34342 --- /dev/null +++ b/Assets/Plugins/ImmersalSDK/Runtime/Core/Resources/Shaders/pointCloud.shader @@ -0,0 +1,71 @@ +Shader "Immersal/Point Cloud" +{ + Properties + { + + } + + SubShader + { + Cull Off + Tags{ "RenderType" = "Opaque" } + LOD 100 + + Pass + { + CGPROGRAM + #pragma vertex vert + #pragma fragment frag + #pragma target 3.0 + + #include "UnityCG.cginc" + + float _PointSize; + fixed _PerspectiveEnabled; + fixed4 _PointColor; + + struct Vertex + { + float3 vertex : POSITION; + }; + + struct VertexOut + { + float psize : PSIZE; + float4 center : TEXCOORD0; + half size : TEXCOORD1; + UNITY_FOG_COORDS(0) + }; + + VertexOut vert(Vertex vertex, out float4 outpos : SV_POSITION) + { + VertexOut o; + outpos = UnityObjectToClipPos(vertex.vertex); + + o.psize = lerp(_PointSize, _PointSize / outpos.w * _ScreenParams.y, step(0.5, _PerspectiveEnabled)); + o.size = o.psize; + + o.center = ComputeScreenPos(outpos); + UNITY_TRANSFER_FOG(o, o.position); + return o; + } + + fixed4 frag(VertexOut i, UNITY_VPOS_TYPE vpos : VPOS) : SV_Target + { + fixed4 c = _PointColor; + float4 center = i.center; + center.xy /= center.w; + center.xy *= _ScreenParams.xy; + float d = distance(vpos.xy, center.xy); + + if (d > i.size * 0.5) { + discard; + } + + UNITY_APPLY_FOG(input.fogCoord, c); + return c; + } + ENDCG + } + } +} \ No newline at end of file diff --git a/Assets/Plugins/ImmersalSDK/Runtime/Core/Resources/Shaders/pointCloud.shader.meta b/Assets/Plugins/ImmersalSDK/Runtime/Core/Resources/Shaders/pointCloud.shader.meta new file mode 100644 index 0000000..e0847a3 --- /dev/null +++ b/Assets/Plugins/ImmersalSDK/Runtime/Core/Resources/Shaders/pointCloud.shader.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 80bb3c180be837f4daba8ccfbd06b6eb +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts.meta b/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts.meta new file mode 100644 index 0000000..f95fe11 --- /dev/null +++ b/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: eee6937ee2c707d42a0b601979052c50 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/AR.meta b/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/AR.meta new file mode 100644 index 0000000..ff2afb8 --- /dev/null +++ b/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/AR.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 09263de2dbfc4a743bdd4c5ff73574ef +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/AR/ARHelper.cs b/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/AR/ARHelper.cs new file mode 100644 index 0000000..0018e3a --- /dev/null +++ b/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/AR/ARHelper.cs @@ -0,0 +1,216 @@ +/*=============================================================================== +Copyright (C) 2023 Immersal - Part of Hexagon. All Rights Reserved. + +This file is part of the Immersal SDK. + +The Immersal SDK cannot be copied, distributed, or made available to +third-parties for commercial purposes without written permission of Immersal Ltd. + +Contact sales@immersal.com for licensing requests. +===============================================================================*/ + +using UnityEngine; +using UnityEngine.XR.ARFoundation; +using UnityEngine.XR.ARSubsystems; +using System; +using System.Runtime.InteropServices; +using Unity.Collections.LowLevel.Unsafe; + +namespace Immersal.AR +{ + public class ARHelper { + public static Matrix4x4 SwitchHandedness(Matrix4x4 b) + { + Matrix4x4 D = Matrix4x4.identity; + D.m00 = -1; + return D * b * D; + } + + public static Quaternion SwitchHandedness(Quaternion b) + { + Matrix4x4 m = SwitchHandedness(Matrix4x4.Rotate(b)); + return m.rotation; + } + + public static Vector3 SwitchHandedness(Vector3 b) + { + Matrix4x4 m = SwitchHandedness(Matrix4x4.TRS(b, Quaternion.identity, Vector3.one)); + return m.GetColumn(3); + } + + public static void DoubleQuaternionToDoubleMatrix3x3(out double[] m, double[] q) + { + m = new double [] {1, 0, 0, 0, 1, 0, 0, 0, 1}; //identity matrix + + // input quaternion should be in WXYZ order + double w = q[0]; + double x = q[1]; + double y = q[2]; + double z = q[3]; + + double ww = w * w; + double xx = x * x; + double yy = y * y; + double zz = z * z; + + double xy = x * y; + double zw = z * w; + double xz = x * z; + double yw = y * w; + double yz = y * z; + double xw = x * w; + + double inv = 1.0 / (xx + yy + zz + ww); + + m[0] = ( xx - yy - zz + ww) * inv; + m[1] = 2.0 * (xy - zw) * inv; + m[2] = 2.0 * (xz + yw) * inv; + m[3] = 2.0 * (xy + zw) * inv; + m[4] = (-xx + yy - zz + ww) * inv; + m[5] = 2.0 * (yz - xw) * inv; + m[6] = 2.0 * (xz - yw) * inv; + m[7] = 2.0 * (yz + xw) * inv; + m[8] = (-xx - yy + zz + ww) * inv; + } + + public static void GetIntrinsics(out Vector4 intrinsics) + { + intrinsics = Vector4.zero; + XRCameraIntrinsics intr; + ARCameraManager manager = ImmersalSDK.Instance?.cameraManager; + + if (manager != null && manager.TryGetIntrinsics(out intr)) + { + intrinsics.x = intr.focalLength.x; + intrinsics.y = intr.focalLength.y; + intrinsics.z = intr.principalPoint.x; + intrinsics.w = intr.principalPoint.y; + } + } + + public static void GetRotation(ref Quaternion rot) + { + float angle = 0f; + switch (Screen.orientation) + { + case ScreenOrientation.Portrait: + angle = 90f; + break; + case ScreenOrientation.LandscapeLeft: + angle = 180f; + break; + case ScreenOrientation.LandscapeRight: + angle = 0f; + break; + case ScreenOrientation.PortraitUpsideDown: + angle = -90f; + break; + default: + angle = 0f; + break; + } + + rot *= Quaternion.Euler(0f, 0f, angle); + } + + public static void GetPlaneDataFast(ref IntPtr pixels, XRCpuImage image) + { + XRCpuImage.Plane plane = image.GetPlane(0); // use the Y plane + int width = image.width, height = image.height; + + if (width == plane.rowStride) + { + unsafe + { + pixels = (IntPtr)NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(plane.data); + } + } + else + { + byte[] data = new byte[width * height]; + + unsafe + { + fixed (byte* dstPtr = data) + { + byte* srcPtr = (byte*)NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(plane.data); + if (width > 0 && height > 0) { + UnsafeUtility.MemCpyStride(dstPtr, width, srcPtr, plane.rowStride, width, height); + } + pixels = (IntPtr)dstPtr; + } + } + } + } + + public static void GetPlaneData(out byte[] pixels, XRCpuImage image) + { + XRCpuImage.Plane plane = image.GetPlane(0); // use the Y plane + int width = image.width, height = image.height; + pixels = new byte[width * height]; + + if (width == plane.rowStride) + { + plane.data.CopyTo(pixels); + } + else + { + unsafe + { + fixed (byte* dstPtr = pixels) + { + byte* srcPtr = (byte*)NativeArrayUnsafeUtility.GetUnsafeReadOnlyPtr(plane.data); + if (width > 0 && height > 0) { + UnsafeUtility.MemCpyStride(dstPtr, width, srcPtr, plane.rowStride, width, height); + } + } + } + } + } + + public static void GetPlaneDataRGB(out byte[] pixels, XRCpuImage image) + { + var conversionParams = new XRCpuImage.ConversionParams + { + inputRect = new RectInt(0, 0, image.width, image.height), + outputDimensions = new Vector2Int(image.width, image.height), + outputFormat = TextureFormat.RGB24, + transformation = XRCpuImage.Transformation.None + }; + + int size = image.GetConvertedDataSize(conversionParams); + pixels = new byte[size]; + GCHandle bufferHandle = GCHandle.Alloc(pixels, GCHandleType.Pinned); + image.Convert(conversionParams, bufferHandle.AddrOfPinnedObject(), pixels.Length); + bufferHandle.Free(); + } + + public static bool TryGetTrackingQuality(out int quality) + { + quality = default; + + if (ImmersalSDK.Instance?.arSession == null) + return false; + + var arSubsystem = ImmersalSDK.Instance?.arSession.subsystem; + + if (arSubsystem != null && arSubsystem.running) + { + switch (arSubsystem.trackingState) + { + case TrackingState.Tracking: + quality = 4; + break; + case TrackingState.Limited: + quality = 1; + break; + case TrackingState.None: + quality = 0; + break; + } + } + + return true; + } + } +} diff --git a/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/AR/ARHelper.cs.meta b/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/AR/ARHelper.cs.meta new file mode 100644 index 0000000..8c59f6d --- /dev/null +++ b/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/AR/ARHelper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f78d5a866c99746cdb1556667e77111c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/AR/ARMap.cs b/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/AR/ARMap.cs new file mode 100644 index 0000000..95cccfc --- /dev/null +++ b/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/AR/ARMap.cs @@ -0,0 +1,613 @@ +/*=============================================================================== +Copyright (C) 2023 Immersal - Part of Hexagon. All Rights Reserved. + +This file is part of the Immersal SDK. + +The Immersal SDK cannot be copied, distributed, or made available to +third-parties for commercial purposes without written permission of Immersal Ltd. + +Contact sales@immersal.com for licensing requests. +===============================================================================*/ + +using UnityEngine; +using UnityEngine.Events; +using System; +using System.IO; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using UnityEngine.Rendering; +using Newtonsoft.Json; + +namespace Immersal.AR +{ + [System.Serializable] + public class MapLocalizedEvent : UnityEvent + { + } + + [ExecuteAlways] + public class ARMap : MonoBehaviour + { + public static readonly Color[] pointCloudColors = new Color[] { new Color(0.22f, 1f, 0.46f), + new Color(0.96f, 0.14f, 0.14f), + new Color(0.16f, 0.69f, 0.95f), + new Color(0.93f, 0.84f, 0.12f), + new Color(0.57f, 0.93f, 0.12f), + new Color(1f, 0.38f, 0.78f), + new Color(0.4f, 0f, 0.9f), + new Color(0.89f, 0.4f, 0f) + }; + + public enum RenderMode { DoNotRender, EditorOnly, EditorAndRuntime } + + public static Dictionary mapHandleToMap = new Dictionary(); + public static bool pointCloudVisible = true; + + public RenderMode renderMode = RenderMode.EditorOnly; + public TextAsset mapFile; + + [SerializeField] + private Color m_PointColor = new Color(0.57f, 0.93f, 0.12f); + + [SerializeField] + public string mapLicense; + + +#if UNITY_EDITOR + [Space(10)] + [Header("Map License Detail")] + [SerializeField][ReadOnly] + private string m_LicenceId = "0"; + [SerializeField][ReadOnly] + private string m_ExpireTime = "0"; +#endif + + [Space(10)] + [Header("Map Metadata")] + + [SerializeField][ReadOnly] + private int m_MapId = -1; + [SerializeField][ReadOnly] + private string m_MapName = null; + [ReadOnly] + public int privacy; + [ReadOnly] + public MapAlignment mapAlignment; + [ReadOnly] + public WGS84 wgs84; + + [Space(10)] + [Header("Events")] + + public MapLocalizedEvent OnFirstLocalization = null; + protected ARSpace m_ARSpace = null; + private bool m_LocalizedOnce = false; + + public Color pointColor + { + get { return m_PointColor; } + set { m_PointColor = value; } + } + + public static float pointSize = 0.33f; + // public static bool isRenderable = true; + public static bool renderAs3dPoints = true; + + [System.Serializable] + public struct MapAlignment + { + public double tx; + public double ty; + public double tz; + public double qx; + public double qy; + public double qz; + public double qw; + public double scale; + } + + [System.Serializable] + public struct WGS84 + { + public double latitude; + public double longitude; + public double altitude; + } + + private Shader m_Shader; + private Material m_Material; + private Mesh m_Mesh; + private MeshFilter m_MeshFilter; + private MeshRenderer m_MeshRenderer; + + public Transform root { get; protected set; } + public int mapHandle { get; private set; } = -1; + + public int mapId + { + get => m_MapId; + private set => m_MapId = value; + } + + public string mapName + { + get => m_MapName; + set => m_MapName = value; + } + + public static int MapHandleToId(int handle) + { + if (mapHandleToMap.ContainsKey(handle)) + { + return mapHandleToMap[handle].mapId; + } + return -1; + } + + public static int MapIdToHandle(int id) + { + if (ARSpace.mapIdToMap.ContainsKey(id)) + { + return ARSpace.mapIdToMap[id].mapHandle; + } + return -1; + } + + public double[] MapToEcefGet() + { + double[] q = new double[] {this.mapAlignment.qw, this.mapAlignment.qx, this.mapAlignment.qy, this.mapAlignment.qz}; + double[] m = new double[9]; + ARHelper.DoubleQuaternionToDoubleMatrix3x3(out m, q); + + double[] mapToEcef = new double[] {this.mapAlignment.tx, this.mapAlignment.ty, this.mapAlignment.tz, m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8], this.mapAlignment.scale}; + + return mapToEcef; + } + + public virtual void FreeMap(bool destroy = false) + { + if (mapHandle >= 0) + { + Immersal.Core.FreeMap(mapHandle); + + if (mapHandleToMap.ContainsKey(mapHandle)) + { + mapHandleToMap.Remove(mapHandle); + } + } + + mapHandle = -1; + ClearMesh(); + Reset(); + + if (this.mapId > 0) + { + ARSpace.UnregisterSpace(root, this.mapId); + this.mapId = -1; + } + + if (destroy) + { + GameObject.Destroy(gameObject); + } + } + + public virtual void Reset() + { + m_LocalizedOnce = false; + } + + public virtual async Task LoadMap(byte[] mapBytes = null, int mapId = -1) + { + if (mapBytes == null) + { + mapBytes = (mapFile != null) ? mapFile.bytes : null; + } + + if (mapBytes != null) + { + Task t = Task.Run(() => + { + var bytes = System.Convert.FromBase64String(mapLicense); + var str = System.Text.Encoding.UTF8.GetString(bytes); + return Immersal.Core.LoadMap(mapBytes, str); + }); + + await t; + if (mapHandle != -1) + { + FreeMap(); + } + + mapHandle = t.Result; + + if (this == null) + { + FreeMap(); + return -1; + } + } + + if (mapId > 0) + { + this.mapId = mapId; + } + else + { + ParseMapIdAndName(); + } + + if (mapHandle >= 0) + { + int pointCloudSize = Immersal.Core.GetPointCloudSize(mapHandle); + Vector3[] points = new Vector3[pointCloudSize]; + Immersal.Core.GetPointCloud(mapHandle, points); + for (int i = 0; i < pointCloudSize; i++) + { + points[i] = ARHelper.SwitchHandedness(points[i]); + } + mapHandleToMap[mapHandle] = this; + InitializeMesh(points); + } + + if (this.mapId > 0 && m_ARSpace != null) + { + root = m_ARSpace.transform; + ARSpace.RegisterSpace(root, this, transform.localPosition, transform.localRotation, transform.localScale); + } + + return mapHandle; + } + + private void InitializeMesh(Vector3[] pointPositions) + { + if (this == null) return; + + if (m_Shader == null) + { + m_Shader = Shader.Find("Immersal/Point Cloud"); + } + + if (m_Material == null) + { + m_Material = new Material(m_Shader); + m_Material.hideFlags = HideFlags.DontSave; + } + + if (m_Mesh == null) + { + m_Mesh = new Mesh(); + m_Mesh.indexFormat = IndexFormat.UInt32; + } + + int numPoints = pointPositions.Length; + + int[] indices = new int[numPoints]; + Vector3[] pts = new Vector3[numPoints]; + Color32[] col = new Color32[numPoints]; + + for (int i = 0; i < numPoints; ++i) + { + indices[i] = i; + pts[i] = pointPositions[i]; + } + + m_Mesh.Clear(); + m_Mesh.vertices = pts; + m_Mesh.colors32 = col; + m_Mesh.SetIndices(indices, MeshTopology.Points, 0); + m_Mesh.RecalculateBounds(); + + if (m_MeshFilter == null) + { + m_MeshFilter = gameObject.GetComponent(); + if (m_MeshFilter == null) + { + m_MeshFilter = gameObject.AddComponent(); + } + } + + if (m_MeshRenderer == null) + { + m_MeshRenderer = gameObject.GetComponent(); + if (m_MeshRenderer == null) + { + m_MeshRenderer = gameObject.AddComponent(); + } + } + + m_MeshFilter.mesh = m_Mesh; + m_MeshRenderer.material = m_Material; + + m_MeshRenderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; + m_MeshRenderer.lightProbeUsage = UnityEngine.Rendering.LightProbeUsage.Off; + m_MeshRenderer.reflectionProbeUsage = UnityEngine.Rendering.ReflectionProbeUsage.Off; + } + + private void InitializeMesh() + { + InitializeMesh(new Vector3[0]); + } + + private void ClearMesh() + { + if (m_Mesh != null) + { + m_Mesh.Clear(); + } + } + + public void NotifySuccessfulLocalization(int mapId) + { + if (m_LocalizedOnce) + return; + + OnFirstLocalization?.Invoke(mapId); + m_LocalizedOnce = true; + } + + private void Awake() + { + m_ARSpace = gameObject.GetComponentInParent(); + if (!m_ARSpace) + { + GameObject go = new GameObject("AR Space"); + m_ARSpace = go.AddComponent(); + transform.SetParent(go.transform); + } + + ParseMapIdAndName(); + InitializeMesh(); + } + + private void ParseMapIdAndName() + { + int id; + if (GetMapId(out id)) + { + this.mapId = id; + this.mapName = mapFile.name.Substring(id.ToString().Length + 1); + } + + if (Application.isEditor) + { + if (mapFile != null) + { + try + { + string destinationFolder = Path.Combine("Assets", "Map Data"); + string jsonFilePath = Path.Combine(destinationFolder, string.Format("{0}-metadata.json", mapFile.name)); + + MetadataFile metadataFile = JsonUtility.FromJson(File.ReadAllText(jsonFilePath)); + + this.mapAlignment.tx = metadataFile.tx; + this.mapAlignment.ty = metadataFile.ty; + this.mapAlignment.tz = metadataFile.tz; + + this.mapAlignment.qx = metadataFile.qx; + this.mapAlignment.qy = metadataFile.qy; + this.mapAlignment.qz = metadataFile.qz; + this.mapAlignment.qw = metadataFile.qw; + + this.mapAlignment.scale = metadataFile.scale; + + this.wgs84.latitude = metadataFile.latitude; + this.wgs84.longitude = metadataFile.longitude; + this.wgs84.altitude = metadataFile.altitude; + + this.privacy = metadataFile.privacy; + } + catch (FileNotFoundException e) + { + Debug.LogWarningFormat("{0}\nCould not find {1}-metadata.json", e.Message, mapFile.name); + // set default values in case metadata is not available + + this.mapAlignment.tx = 0.0; + this.mapAlignment.ty = 0.0; + this.mapAlignment.tz = 0.0; + + this.mapAlignment.qx = 0.0; + this.mapAlignment.qy = 0.0; + this.mapAlignment.qz = 0.0; + this.mapAlignment.qw = 1.0; + + this.mapAlignment.scale = 1.0; + + this.wgs84.latitude = 0.0; + this.wgs84.longitude = 0.0; + this.wgs84.altitude = 0.0; + + this.privacy = 0; + } + } + } + } + + [System.Serializable] + public struct MetadataFile + { + public string error; + public int id; + public int type; + public string created; + public string version; + public int user; + public int creator; + public string name; + public int size; + public string status; + public int privacy; + public double latitude; + public double longitude; + public double altitude; + public double tx; + public double ty; + public double tz; + public double qw; + public double qx; + public double qy; + public double qz; + public double scale; + public string sha256_al; + public string sha256_sparse; + public string sha256_dense; + public string sha256_tex; + } + + private bool GetMapId(out int mapId) + { + if (mapFile == null) + { + mapId = -1; + return false; + } + + string mapFileName = mapFile.name; + Regex rx = new Regex(@"^\d+"); + Match match = rx.Match(mapFileName); + if (match.Success) + { + mapId = Int32.Parse(match.Value); + return true; + } + else + { + mapId = -1; + return false; + } + } + + private async void OnEnable() + { + if (mapFile != null) + { + await LoadMap(); + } + } + + private void OnDisable() + { + FreeMap(); + } + + private void OnDestroy() + { + FreeMap(); + + if (m_Material != null) + { + if (Application.isPlaying) + { + Destroy(m_Mesh); + Destroy(m_Material); + } + else + { + DestroyImmediate(m_Mesh); + DestroyImmediate(m_Material); + } + } + } + + private bool IsRenderable() + { + if (pointCloudVisible) + { + switch (renderMode) + { + case RenderMode.DoNotRender: + return false; + case RenderMode.EditorOnly: + if (Application.isEditor) + { + return true; + } + else + { + return false; + } + case RenderMode.EditorAndRuntime: + return true; + default: + return false; + } + } + return false; + } + + + private void OnRenderObject() + { + if (IsRenderable() && m_Material != null) + { + m_MeshRenderer.enabled = true; + + if (renderAs3dPoints) + { + m_Material.SetFloat("_PerspectiveEnabled", 1f); + m_Material.SetFloat("_PointSize", Mathf.Lerp(0.002f, 0.14f, Mathf.Max(0, Mathf.Pow(pointSize, 3f)))); + } + else + { + m_Material.SetFloat("_PerspectiveEnabled", 0f); + m_Material.SetFloat("_PointSize", Mathf.Lerp(1.5f, 40f, Mathf.Max(0, pointSize))); + } + m_Material.SetColor("_PointColor", m_PointColor); + } + else + { + m_MeshRenderer.enabled = false; + } + } + +#if UNITY_EDITOR + + private string _prevMapLicense = null; + private TextAsset _prevMapFile = null; + + void Update() + { + if (mapLicense != _prevMapLicense) + { + try + { + var bytes = System.Convert.FromBase64String(mapLicense); + var str = System.Text.Encoding.UTF8.GetString(bytes); + + MapLicense obj = JsonUtility.FromJson(str); + + m_LicenceId = obj.certificateID; + m_ExpireTime = obj.expireTime; + } + catch (Exception e) { + Debug.LogWarningFormat("Incorrect map license!\n{0}", e.Message); + + m_LicenceId = "0"; + m_ExpireTime = "0"; + } + + _prevMapLicense = mapLicense; + } + + if (mapFile != null) + { + if (mapFile != _prevMapFile) + { + ParseMapIdAndName(); + } + + _prevMapFile = mapFile; + } + } +#endif + + [Serializable] + public class MapLicense + { + public string certificateID; + public string applyTime; + public string expireTime; + } + } +} diff --git a/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/AR/ARMap.cs.meta b/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/AR/ARMap.cs.meta new file mode 100644 index 0000000..c4886f7 --- /dev/null +++ b/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/AR/ARMap.cs.meta @@ -0,0 +1,14 @@ +fileFormatVersion: 2 +guid: 6a7ab799a64cb7941b7b41a8e1211b12 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: + - mapFile: {instanceID: 0} + - m_Shader: {fileID: 4800000, guid: 9cee2ca6ff2bbdc458a11fe108bb2e5b, type: 3} + - m_Sorter: {fileID: 7200000, guid: 333809191cc59694fb5772e17740acfe, type: 3} + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/AR/ARSpace.cs b/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/AR/ARSpace.cs new file mode 100644 index 0000000..39cc880 --- /dev/null +++ b/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/AR/ARSpace.cs @@ -0,0 +1,165 @@ +/*=============================================================================== +Copyright (C) 2023 Immersal - Part of Hexagon. All Rights Reserved. + +This file is part of the Immersal SDK. + +The Immersal SDK cannot be copied, distributed, or made available to +third-parties for commercial purposes without written permission of Immersal Ltd. + +Contact sales@immersal.com for licensing requests. +===============================================================================*/ + +using UnityEngine; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Runtime.InteropServices; + +namespace Immersal.AR +{ + public class SpaceContainer + { + public int mapCount = 0; + public Vector3 targetPosition = Vector3.zero; + public Quaternion targetRotation = Quaternion.identity; + public PoseFilter filter = new PoseFilter(); + } + + public class MapOffset + { + public Vector3 position; + public Quaternion rotation; + public Vector3 scale; + public SpaceContainer space; + } + + public class ARSpace : MonoBehaviour + { + public static bool _inited = false; + + public static Dictionary transformToSpace = new Dictionary(); + public static Dictionary spaceToTransform = new Dictionary(); + public static Dictionary mapIdToOffset = new Dictionary(); + public static Dictionary mapIdToMap = new Dictionary(); + + private Matrix4x4 m_InitialOffset = Matrix4x4.identity; + + public Matrix4x4 initialOffset + { + get { return m_InitialOffset; } + } + + void Awake() + { + if (!_inited) + { +#if !UNITY_EDITOR + init(); +#endif + _inited = true; + } + + Vector3 pos = transform.position; + Quaternion rot = transform.rotation; + Matrix4x4 offset = Matrix4x4.TRS(pos, rot, Vector3.one); + + m_InitialOffset = offset; + } + + public void OnDestroy() + { + transformToSpace.Clear(); + spaceToTransform.Clear(); + mapIdToOffset.Clear(); + mapIdToMap.Clear(); + } + + public Pose ToCloudSpace(Vector3 camPos, Quaternion camRot) + { + Matrix4x4 trackerSpace = Matrix4x4.TRS(camPos, camRot, Vector3.one); + Matrix4x4 trackerToCloudSpace = Matrix4x4.TRS(transform.position, transform.rotation, Vector3.one); + Matrix4x4 cloudSpace = trackerToCloudSpace.inverse * trackerSpace; + + return new Pose(cloudSpace.GetColumn(3), cloudSpace.rotation); + } + + public Pose FromCloudSpace(Vector3 camPos, Quaternion camRot) + { + Matrix4x4 cloudSpace = Matrix4x4.TRS(transform.position, transform.rotation, Vector3.one); + Matrix4x4 trackerSpace = Matrix4x4.TRS(camPos, camRot, Vector3.one); + Matrix4x4 m = trackerSpace * (cloudSpace.inverse); + + return new Pose(m.GetColumn(3), m.rotation); + } + + public static void RegisterSpace(Transform tr, ARMap map, Vector3 offsetPosition, Quaternion offsetRotation, Vector3 offsetScale) + { + if (tr == null) + return; + + SpaceContainer sc; + + if (!transformToSpace.ContainsKey(tr)) + { + sc = new SpaceContainer(); + transformToSpace[tr] = sc; + } + else + { + sc = transformToSpace[tr]; + } + + spaceToTransform[sc] = tr; + + sc.mapCount++; + + MapOffset mo = new MapOffset(); + mo.position = offsetPosition; + mo.rotation = offsetRotation; + mo.scale = offsetScale; + mo.space = sc; + + mapIdToOffset[map.mapId] = mo; + mapIdToMap[map.mapId] = map; + } + + public static void RegisterSpace(Transform tr, ARMap map) + { + RegisterSpace(tr, map, Vector3.zero, Quaternion.identity, Vector3.one); + } + + public static void UnregisterSpace(Transform tr, int mapId) + { + if (tr == null) + return; + + if (transformToSpace.ContainsKey(tr)) + { + SpaceContainer sc = transformToSpace[tr]; + if (--sc.mapCount == 0) + { + transformToSpace.Remove(tr); + spaceToTransform.Remove(sc); + } + if (mapIdToOffset.ContainsKey(mapId)) + mapIdToOffset.Remove(mapId); + if (mapIdToMap.ContainsKey(mapId)) + mapIdToMap.Remove(mapId); + } + } + + public static void UpdateSpace(SpaceContainer space, Vector3 pos, Quaternion rot) + { + if (space == null) + return; + + if (spaceToTransform.ContainsKey(space)) + { + Transform tr = spaceToTransform[space]; + tr.SetPositionAndRotation(pos, rot); + } + } + + [DllImport("RokidLocalizer", CallingConvention = CallingConvention.Cdecl)] + private static extern void init(); + } +} \ No newline at end of file diff --git a/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/AR/ARSpace.cs.meta b/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/AR/ARSpace.cs.meta new file mode 100644 index 0000000..225c643 --- /dev/null +++ b/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/AR/ARSpace.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 53a086897bd294848aa39db1a6b2e6ed +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/AR/LocalizerBase.cs b/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/AR/LocalizerBase.cs new file mode 100644 index 0000000..56ef818 --- /dev/null +++ b/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/AR/LocalizerBase.cs @@ -0,0 +1,290 @@ +/*=============================================================================== +Copyright (C) 2023 Immersal - Part of Hexagon. All Rights Reserved. + +This file is part of the Immersal SDK. + +The Immersal SDK cannot be copied, distributed, or made available to +third-parties for commercial purposes without written permission of Immersal Ltd. + +Contact sales@immersal.com for licensing requests. +===============================================================================*/ + +using UnityEngine; +using System; +using System.Collections.Generic; + +namespace Immersal.AR +{ + public class LocalizerStats + { + public int localizationAttemptCount = 0; + public int localizationSuccessCount = 0; + } + + public struct LocalizerPose + { + public bool valid; + public double[] mapToEcef; + public Matrix4x4 matrix; + public Pose lastUpdatedPose; + public double vLatitude; + public double vLongitude; + public double vAltitude; + } + + + public abstract class LocalizerBase : MonoBehaviour + { + [Tooltip("Start localizing at app startup")] + [SerializeField] + protected bool m_AutoStart = true; + [Tooltip("Time between localization requests in seconds")] + public float localizationInterval = 2.0f; + [Tooltip("Filter localizer poses for smoother results")] + [SerializeField] + protected bool m_UseFiltering = true; + [Tooltip("Reset localizer filtering when relocalized against a different map than the previous time")] + [SerializeField] + protected bool m_ResetOnMapChange = false; + [Tooltip("Try to localize at maximum speed at app startup / resume")] + [SerializeField] + protected bool m_BurstMode = true; + [SerializeField] + protected bool m_EnableLogging; + + public LocalizerStats stats { get; protected set; } = new LocalizerStats(); + public int lastLocalizedMapId { get; protected set; } + public LocalizerPose lastLocalizedPose = default; + public bool isTracking { get; protected set; } + public bool isLocalizing { get; protected set; } + + public Action OnPoseFound; + public Action OnMapChanged; + public Action OnReset; + + protected ImmersalSDK m_Sdk = null; + protected IntPtr m_PixelBuffer = IntPtr.Zero; + protected float m_LastLocalizeTime = 0.0f; + protected float m_BurstStartTime = 0.0f; + protected bool m_BurstModeActive = false; + protected bool m_LocalizeContinuously = false; + protected Camera m_Cam = null; + protected float m_WarpThresholdDistSq = 5.0f * 5.0f; + protected float m_WarpThresholdCosAngle = Mathf.Cos(20.0f * Mathf.PI / 180.0f); + + public bool burstMode + { + get { return m_BurstMode; } + set + { + SetBurstMode(value); + } + } + + public bool useFiltering + { + get { return m_UseFiltering; } + set { m_UseFiltering = value; } + } + + public bool resetOnMapChange + { + get { return m_ResetOnMapChange; } + set { m_ResetOnMapChange = value; } + } + + public bool autoStart + { + get { return m_AutoStart; } + set + { + m_AutoStart = value; + SetContinuousLocalization(value); + } + } + + #region Virtual methods + + public virtual void Start() + { + m_Sdk = ImmersalSDK.Instance; + lastLocalizedMapId = -1; + SetBurstMode(burstMode); + SetContinuousLocalization(autoStart); + } + + public virtual void OnEnable() + { + m_Cam = Camera.main; + } + + public virtual void OnDisable() + { + isTracking = false; + } + + public virtual void OnDestroy() + { + m_PixelBuffer = IntPtr.Zero; + } + + public virtual void OnApplicationPause(bool pauseStatus) + { + Reset(); + + if (!pauseStatus) + SetBurstMode(burstMode); + } + + public virtual void Localize() + { + LocalizerDebugLog(string.Format("Successful localizations: {0}/{1}", stats.localizationSuccessCount, stats.localizationAttemptCount)); + isLocalizing = false; + } + + public virtual void Reset() + { + lastLocalizedMapId = -1; + + stats.localizationAttemptCount = stats.localizationSuccessCount = 0; + SetBurstMode(burstMode); + + foreach (KeyValuePair item in ARSpace.transformToSpace) + item.Value.filter.ResetFiltering(); + + OnReset?.Invoke(); + } + + public virtual void StartLocalizing() + { + Reset(); + SetContinuousLocalization(autoStart); + } + + public virtual void StopLocalizing() + { + SetContinuousLocalization(false); + Reset(); + } + + public virtual void Pause() + { + SetContinuousLocalization(false); + } + + public virtual void Resume() + { + SetContinuousLocalization(true); + } + + protected virtual void LocalizerDebugLog(string message) + { + if (m_EnableLogging) + { + Debug.LogFormat("[{0}]: {1}", this.GetType().Name, message); + } + } + + protected virtual void Update() + { + if (!m_LocalizeContinuously) + return; + + if (ARSpace.transformToSpace.Count == 0) + { + m_BurstStartTime = Time.unscaledTime; + return; + } + + if (useFiltering) + { + foreach (KeyValuePair item in ARSpace.transformToSpace) + { + float distSq = (item.Value.filter.position - item.Value.targetPosition).sqrMagnitude; + float cosAngle = Quaternion.Dot(item.Value.filter.rotation, item.Value.targetRotation); + if (item.Value.filter.SampleCount() == 1 || distSq > m_WarpThresholdDistSq || cosAngle < m_WarpThresholdCosAngle) + { + item.Value.targetPosition = item.Value.filter.position; + item.Value.targetRotation = item.Value.filter.rotation; + } + else + { + float smoothing = 0.025f; + float steps = Time.deltaTime / (1.0f / 60.0f); + if (steps < 1.0f) + steps = 1.0f; + else if (steps > 6.0f) + steps = 6.0f; + float alpha = 1.0f - Mathf.Pow(1.0f - smoothing, steps); + + item.Value.targetRotation = Quaternion.Slerp(item.Value.targetRotation, item.Value.filter.rotation, alpha); + item.Value.targetPosition = Vector3.Lerp(item.Value.targetPosition, item.Value.filter.position, alpha); + } + ARSpace.UpdateSpace(item.Value, item.Value.targetPosition, item.Value.targetRotation); + } + } + + float curTime = Time.unscaledTime; + if (m_BurstModeActive) // try to localize at max speed during app start/resume + { + if (!isLocalizing && isTracking) + { + float elapsedTime = curTime - m_BurstStartTime; + isLocalizing = true; + + Localize(); + + if (stats.localizationSuccessCount == 10 || elapsedTime >= 15f) + { + m_BurstModeActive = false; + } + } + } + + if (!isLocalizing && isTracking && (curTime - m_LastLocalizeTime) >= localizationInterval) + { + m_LastLocalizeTime = curTime; + isLocalizing = true; + + Localize(); + } + } + + #endregion + + private void SetBurstMode(bool on) + { + m_BurstStartTime = Time.unscaledTime; + m_BurstModeActive = on; + } + + private void SetContinuousLocalization(bool on) + { + m_LocalizeContinuously = on; + } + + public static void GetLocalizerPose(out LocalizerPose localizerPose, int mapId, Vector3 pos, Quaternion rot, Matrix4x4 m, double[] mapToEcef = null) + { + localizerPose = default; + + if (mapToEcef == null) + { + mapToEcef = ARSpace.mapIdToMap[mapId].MapToEcefGet(); + } + + double[] wgs84 = new double[3]; + int r = Immersal.Core.PosMapToWgs84(wgs84, ARHelper.SwitchHandedness(pos), mapToEcef); + + if (r == 0) + { + localizerPose.valid = true; + localizerPose.mapToEcef = mapToEcef; + localizerPose.matrix = m; + localizerPose.lastUpdatedPose = new Pose(pos, rot); + localizerPose.vLatitude = wgs84[0]; + localizerPose.vLongitude = wgs84[1]; + localizerPose.vAltitude = wgs84[2]; + } + } + } +} \ No newline at end of file diff --git a/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/AR/LocalizerBase.cs.meta b/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/AR/LocalizerBase.cs.meta new file mode 100644 index 0000000..cfb72bc --- /dev/null +++ b/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/AR/LocalizerBase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 06ec00cf50a16443a92eafce8161c1a1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/AR/PoseFilter.cs b/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/AR/PoseFilter.cs new file mode 100644 index 0000000..fb6934e --- /dev/null +++ b/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/AR/PoseFilter.cs @@ -0,0 +1,92 @@ +/*=============================================================================== +Copyright (C) 2023 Immersal - Part of Hexagon. All Rights Reserved. + +This file is part of the Immersal SDK. + +The Immersal SDK cannot be copied, distributed, or made available to +third-parties for commercial purposes without written permission of Immersal Ltd. + +Contact sales@immersal.com for licensing requests. +===============================================================================*/ + +using UnityEngine; + +namespace Immersal.AR +{ + public class PoseFilter + { + public Vector3 position = Vector3.zero; + public Quaternion rotation = Quaternion.identity; + + private static uint m_HistorySize = 8; + private Vector3[] m_P = new Vector3[m_HistorySize]; + private Vector3[] m_X = new Vector3[m_HistorySize]; + private Vector3[] m_Z = new Vector3[m_HistorySize]; + private uint m_Samples = 0; + + public uint SampleCount() + { + return m_Samples; + } + + public void InvalidateHistory() + { + m_Samples = 0; + } + + public void ResetFiltering() + { + position = Vector3.zero; + rotation = Quaternion.identity; + InvalidateHistory(); + } + + public void RefinePose(Matrix4x4 r) + { + uint idx = m_Samples% m_HistorySize; + m_P[idx] = r.GetColumn(3); + m_X[idx] = r.GetColumn(0); + m_Z[idx] = r.GetColumn(2); + m_Samples++; + uint n = m_Samples > m_HistorySize ? m_HistorySize : m_Samples; + position = FilterAVT(m_P, n); + Vector3 x = Vector3.Normalize(FilterAVT(m_X, n)); + Vector3 z = Vector3.Normalize(FilterAVT(m_Z, n)); + Vector3 up = Vector3.Normalize(Vector3.Cross(z, x)); + rotation = Quaternion.LookRotation(z, up); + } + + private Vector3 FilterAVT(Vector3[] buf, uint n) + { + Vector3 mean = Vector3.zero; + for (uint i = 0; i < n; i++) + mean += buf[i]; + mean /= (float)n; + if (n <= 2) + return mean; + float s = 0; + for (uint i = 0; i < n; i++) + { + s += Vector3.SqrMagnitude(buf[i] - mean); + } + s /= (float)n; + Vector3 avg = Vector3.zero; + int ib = 0; + for (uint i = 0; i < n; i++) + { + float d = Vector3.SqrMagnitude(buf[i] - mean); + if (d <= s) + { + avg += buf[i]; + ib++; + } + } + if (ib > 0) + { + avg /= (float)ib; + return avg; + } + return mean; + } + } +} \ No newline at end of file diff --git a/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/AR/PoseFilter.cs.meta b/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/AR/PoseFilter.cs.meta new file mode 100644 index 0000000..af66e37 --- /dev/null +++ b/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/AR/PoseFilter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3081ca6de5f736847b8e96b9683e38d7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/Core.cs b/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/Core.cs new file mode 100644 index 0000000..350bada --- /dev/null +++ b/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/Core.cs @@ -0,0 +1,323 @@ +/*=============================================================================== +Copyright (C) 2023 Immersal - Part of Hexagon. All Rights Reserved. + +This file is part of the Immersal SDK. + +The Immersal SDK cannot be copied, distributed, or made available to +third-parties for commercial purposes without written permission of Immersal Ltd. + +Contact sales@immersal.com for licensing requests. +===============================================================================*/ + +using UnityEngine; +using System; +using System.Runtime.InteropServices; +#if UNITY_EDITOR || UNITY_STANDALONE +using System.Diagnostics; +#endif + +namespace Immersal +{ + [StructLayout(LayoutKind.Sequential)] + public struct LocalizeInfo + { + public int handle; + public float px, py, pz; + public float r00, r01, r02, r10, r11, r12, r20, r21, r22; + public int confidence; + }; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void LogCallback(IntPtr msg); + + public static class Core + { + /// + /// Get a Vector3 point cloud representation of the map data. + /// + /// An integer map handle + /// A preallocated Vector3 array for the points + /// Returns the number of points if succeeded, 0 otherwise. + public static int GetPointCloud(int mapHandle, Vector3[] points) + { + GCHandle vector3ArrayHandle = GCHandle.Alloc(points, GCHandleType.Pinned); + int n = Native.icvPointsGet(mapHandle, vector3ArrayHandle.AddrOfPinnedObject(), points.Length); + vector3ArrayHandle.Free(); + return n; + } + + /// + /// Get point count of the map's point cloud. + /// + /// An integer map handle + /// Returns the number of points. + public static int GetPointCloudSize(int mapHandle) => Native.icvPointsGetCount(mapHandle); + + /// + /// Load map data from a .bytes file. + /// + /// Map data as a byte array + /// An integer map handle. + public static int LoadMap(byte[] buffer, string license) => Native.icvLoadMap(buffer, license); + + /// + /// Free the map data from memory. + /// + /// An integer map handle + /// Returns 1 if succeeded, 0 otherwise. + public static int FreeMap(int mapHandle) => Native.icvFreeMap(mapHandle); + + public static int UndistortFishEyeImage(byte[] result, int byteSizeMax, byte[] pixels, int width, + int height, int channels, ref Vector4 intrinsics, ref Vector4 distCoeffs, float alpha) + { + GCHandle resultHandle = GCHandle.Alloc(result, GCHandleType.Pinned); + GCHandle pixelsHandle = GCHandle.Alloc(pixels, GCHandleType.Pinned); + int r = Native.icvUndistortFishEye(resultHandle.AddrOfPinnedObject(), byteSizeMax, + pixelsHandle.AddrOfPinnedObject(), width, height, channels, ref intrinsics, ref distCoeffs, alpha); + resultHandle.Free(); + pixelsHandle.Free(); + + return r; + } + + /// + /// Gets the position and orientation of the image within the map. + /// + /// Image width + /// Image height + /// Camera intrinsics + /// Raw pixel buffer data from the camera + /// Camera rotation as float array + /// An integer map ID if succeeded, -1 otherwise + public static LocalizeInfo LocalizeImage(int n, int[] handles, int width, + int height, ref Vector4 intrinsics, IntPtr pixels, int solverType, float[] rot) + { + GCHandle intHandle = GCHandle.Alloc(handles, GCHandleType.Pinned); + LocalizeInfo result = Native.icvLocalize(n, intHandle.AddrOfPinnedObject(), width, height, + ref intrinsics, pixels, solverType, rot); + intHandle.Free(); + return result; + } + + public static LocalizeInfo LocalizeImage(int n, int[] handles, int width, + int height, ref Vector4 intrinsics, IntPtr pixels) + { + int sFlag = 0; + float[] rot = new float[1]; + return LocalizeImage(n, handles, width, height, ref intrinsics, pixels, sFlag, rot); + } + + /// + /// Gets the position and orientation of the image within the map. + /// + /// Image width + /// Image height + /// Camera intrinsics + /// Raw pixel buffer data from the camera + /// An integer map ID if succeeded, -1 otherwise + public static LocalizeInfo LocalizeImage(int width, int height, + ref Vector4 intrinsics, IntPtr pixels) + { + int n = 0; + int[] handles = new int[1]; + return LocalizeImage(n, handles, width, height, ref intrinsics, pixels); + } + + /// + /// Gets the position and orientation of the image within the map. + /// + /// Image width + /// Image height + /// Camera intrinsics + /// Raw pixel buffer data from the camera + /// Camera rotation as float array + /// An integer map ID if succeeded, -1 otherwise + public static LocalizeInfo LocalizeImage(int width, int height, + ref Vector4 intrinsics, IntPtr pixels, float[] rot) + { + int n = 0; + int[] handles = new int[1]; + return LocalizeImage(n, handles, width, height, ref intrinsics, pixels, 1, rot); + } + + /// + /// + /// + /// + /// + /// + /// + public static int PosMapToEcef(double[] ecef, Vector3 map, double[] mapToEcef) + { + GCHandle ecefHandle = GCHandle.Alloc(ecef, GCHandleType.Pinned); + GCHandle mapToEcefHandle = GCHandle.Alloc(mapToEcef, GCHandleType.Pinned); + int r = Native.icvPosMapToEcef(ecefHandle.AddrOfPinnedObject(), ref map, + mapToEcefHandle.AddrOfPinnedObject()); + mapToEcefHandle.Free(); + ecefHandle.Free(); + return r; + } + + /// + /// + /// + /// + /// + /// + public static int PosEcefToWgs84(double[] wgs84, double[] ecef) + { + GCHandle wgs84Handle = GCHandle.Alloc(wgs84, GCHandleType.Pinned); + GCHandle ecefHandle = GCHandle.Alloc(ecef, GCHandleType.Pinned); + int r = Native.icvPosEcefToWgs84(wgs84Handle.AddrOfPinnedObject(), ecefHandle.AddrOfPinnedObject()); + ecefHandle.Free(); + wgs84Handle.Free(); + return r; + } + + /// + /// + /// + /// + /// + /// + public static int PosWgs84ToEcef(double[] ecef, double[] wgs84) + { + GCHandle ecefHandle = GCHandle.Alloc(ecef, GCHandleType.Pinned); + GCHandle wgs84Handle = GCHandle.Alloc(wgs84, GCHandleType.Pinned); + int r = Native.icvPosWgs84ToEcef(ecefHandle.AddrOfPinnedObject(), wgs84Handle.AddrOfPinnedObject()); + wgs84Handle.Free(); + ecefHandle.Free(); + return r; + } + + /// + /// + /// + /// + /// + /// + /// + public static int PosEcefToMap(out Vector3 map, double[] ecef, double[] mapToEcef) + { + GCHandle ecefHandle = GCHandle.Alloc(ecef, GCHandleType.Pinned); + GCHandle mapToEcefHandle = GCHandle.Alloc(mapToEcef, GCHandleType.Pinned); + int r = Native.icvPosEcefToMap(out map, ecefHandle.AddrOfPinnedObject(), + mapToEcefHandle.AddrOfPinnedObject()); + mapToEcefHandle.Free(); + ecefHandle.Free(); + return r; + } + + /// + /// + /// + /// + /// + /// + /// + public static int PosMapToWgs84(double[] wgs84, Vector3 map, double[] mapToEcef) + { + double[] ecef = new double[3]; + int err = PosMapToEcef(ecef, map, mapToEcef); + if (err != 0) + return err; + return PosEcefToWgs84(wgs84, ecef); + } + + /// + /// + /// + /// + /// + /// + /// + public static int RotMapToEcef(out Quaternion ecef, Quaternion map, double[] mapToEcef) + { + GCHandle mapToEcefHandle = GCHandle.Alloc(mapToEcef, GCHandleType.Pinned); + int r = Native.icvRotMapToEcef(out ecef, ref map, mapToEcefHandle.AddrOfPinnedObject()); + mapToEcefHandle.Free(); + return r; + } + + /// + /// + /// + /// + /// + /// + /// + public static int RotEcefToMap(out Quaternion map, Quaternion ecef, double[] mapToEcef) + { + GCHandle mapToEcefHandle = GCHandle.Alloc(mapToEcef, GCHandleType.Pinned); + int r = Native.icvRotEcefToMap(out map, ref ecef, mapToEcefHandle.AddrOfPinnedObject()); + mapToEcefHandle.Free(); + return r; + } + + /// + /// Set internal plugin parameters. + /// + /// Available parameters: + /// "LocalizationMaxPixels" - 0 is no limit (the default), 960*720 or higher. + /// "NumThreads" - how many CPU cores to use; -1 (system default) or a positive integer. + /// "ImageCompressionLevel" - 0 (no compression, fastest) to 9 (slowest). Defaults to 4. + /// + /// Parameter name + /// An integer parameter value + /// Returns 1 if succeeded, -1 otherwise. + public static int SetInteger(string parameter, int value) => Native.icvSetInteger(parameter, value); + } + + public static class Native + { + private const string Assembly = +#if UNITY_IOS && !UNITY_EDITOR + "__Internal"; +#else + "PosePlugin"; +#endif + [DllImport(Assembly, CallingConvention = CallingConvention.Cdecl)] + public static extern void PP_RegisterLogCallback(IntPtr callbackDelegate); + + [DllImport(Assembly, CallingConvention = CallingConvention.Cdecl)] + public static extern int icvPointsGet(int mapHandle, IntPtr array, int maxCount); + + [DllImport(Assembly, CallingConvention = CallingConvention.Cdecl)] + public static extern int icvPointsGetCount(int mapHandle); + + [DllImport(Assembly, CallingConvention = CallingConvention.Cdecl)] + public static extern int icvLoadMap(byte[] buffer, string license); + + [DllImport(Assembly, CallingConvention = CallingConvention.Cdecl)] + public static extern int icvFreeMap(int mapHandle); + + [DllImport(Assembly, CallingConvention = CallingConvention.Cdecl)] + public static extern int icvUndistortFishEye(IntPtr capture, int captureSizeMax, IntPtr pixels, + int width, int height, int channels, ref Vector4 intrinsics, ref Vector4 distCoeffs, float alpha); + + [DllImport(Assembly, CallingConvention = CallingConvention.Cdecl)] + public static extern LocalizeInfo icvLocalize(int n, IntPtr handles, int width, + int height, ref Vector4 intrinsics, IntPtr pixels, int sFlag, float[] rot); + + [DllImport(Assembly, CallingConvention = CallingConvention.Cdecl)] + public static extern int icvPosMapToEcef(IntPtr ecef, ref Vector3 map, IntPtr mapToEcef); + + [DllImport(Assembly, CallingConvention = CallingConvention.Cdecl)] + public static extern int icvPosEcefToWgs84(IntPtr wgs84, IntPtr ecef); + + [DllImport(Assembly, CallingConvention = CallingConvention.Cdecl)] + public static extern int icvPosWgs84ToEcef(IntPtr ecef, IntPtr wgs84); + + [DllImport(Assembly, CallingConvention = CallingConvention.Cdecl)] + public static extern int icvPosEcefToMap(out Vector3 map, IntPtr ecef, IntPtr mapToEcef); + + [DllImport(Assembly, CallingConvention = CallingConvention.Cdecl)] + public static extern int icvRotMapToEcef(out Quaternion ecef, ref Quaternion map, IntPtr mapToEcef); + + [DllImport(Assembly, CallingConvention = CallingConvention.Cdecl)] + public static extern int icvRotEcefToMap(out Quaternion map, ref Quaternion ecef, IntPtr mapToEcef); + + [DllImport(Assembly, CallingConvention = CallingConvention.Cdecl)] + public static extern int icvSetInteger([MarshalAs(UnmanagedType.LPStr)] string parameter, int value); + } +} \ No newline at end of file diff --git a/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/Core.cs.meta b/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/Core.cs.meta new file mode 100644 index 0000000..1689356 --- /dev/null +++ b/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/Core.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 2a0cde8ffdea2d24bb36590dbe44cb53 +timeCreated: 1464961012 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/Editor.meta b/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/Editor.meta new file mode 100644 index 0000000..461f4a2 --- /dev/null +++ b/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 068ff142a326946a4921c7907dd3368c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/Editor/ReadOnlyDrawer.cs b/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/Editor/ReadOnlyDrawer.cs new file mode 100644 index 0000000..1e9509e --- /dev/null +++ b/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/Editor/ReadOnlyDrawer.cs @@ -0,0 +1,34 @@ +/*=============================================================================== +Copyright (C) 2023 Immersal - Part of Hexagon. All Rights Reserved. + +This file is part of the Immersal SDK. + +The Immersal SDK cannot be copied, distributed, or made available to +third-parties for commercial purposes without written permission of Immersal Ltd. + +Contact sales@immersal.com for licensing requests. +===============================================================================*/ + +#if UNITY_EDITOR +using UnityEngine; +using UnityEditor; + +[CustomPropertyDrawer(typeof(ReadOnlyAttribute))] +public class ReadOnlyDrawer : PropertyDrawer +{ + public override float GetPropertyHeight(SerializedProperty property, + GUIContent label) + { + return EditorGUI.GetPropertyHeight(property, label, true); + } + + public override void OnGUI(Rect position, + SerializedProperty property, + GUIContent label) + { + GUI.enabled = false; + EditorGUI.PropertyField(position, property, label, true); + GUI.enabled = true; + } +} +#endif \ No newline at end of file diff --git a/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/Editor/ReadOnlyDrawer.cs.meta b/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/Editor/ReadOnlyDrawer.cs.meta new file mode 100644 index 0000000..f310e68 --- /dev/null +++ b/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/Editor/ReadOnlyDrawer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6a04c0995666dab4988bf70980c9d391 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/ImmersalSDK.cs b/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/ImmersalSDK.cs new file mode 100644 index 0000000..6308ef5 --- /dev/null +++ b/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/ImmersalSDK.cs @@ -0,0 +1,336 @@ +/*=============================================================================== +Copyright (C) 2023 Immersal - Part of Hexagon. All Rights Reserved. + +This file is part of the Immersal SDK. + +The Immersal SDK cannot be copied, distributed, or made available to +third-parties for commercial purposes without written permission of Immersal Ltd. + +Contact sales@immersal.com for licensing requests. +===============================================================================*/ + +using UnityEngine; +using UnityEngine.Events; +using Unity.Collections; +using UnityEngine.XR.ARFoundation; +using System; +using System.Net.Http; +using System.Runtime.InteropServices; +using Immersal.AR; +using UnityEngine.XR.ARSubsystems; +using AOT; + +namespace Immersal +{ + public class ImmersalSDK : MonoBehaviour + { + public static string sdkVersion = "1.20.0"; + public static bool isHWAR = false; + // private static readonly string[] ServerList = new[] {"https://api.immersal.com", "https://immersal.hexagon.com.cn"}; + + public enum CameraResolution { Default, HD, FullHD, Max }; // With Huawei AR Engine SDK, only Default (640x480) and Max (1440x1080) are supported. + + private static ImmersalSDK instance = null; + + [Tooltip("Application target frame rate")] + private int m_TargetFrameRate = 60; + [SerializeField] + [Tooltip("Android resolution")] + private CameraResolution m_AndroidResolution = CameraResolution.FullHD; + [Tooltip("Downsample image to HD resolution")] + [SerializeField] + private bool m_Downsample = true; + + public UnityEvent onPoseLost = null; + public UnityEvent onPoseFound = null; + + public int secondsToDecayPose = 10; + + public LocalizerBase Localizer { get; private set; } + public int TrackingQuality { get; private set; } + + private ARCameraManager m_CameraManager; + private ARSession m_ARSession; + private bool m_bCamConfigDone = false; + private string m_LocalizationServer; + private int m_PreviousResults = 0; + private int m_CurrentResults = 0; + private int q = 0; + private float m_LatestPoseUpdated = 0f; + private bool m_HasPose = false; + private XRCameraConfiguration? m_InitialConfig; + + public static HttpClient client; + + public int targetFrameRate + { + get { return m_TargetFrameRate; } + set + { + m_TargetFrameRate = value; + SetFrameRate(); + } + } + + public CameraResolution androidResolution + { + get { return m_AndroidResolution; } + set + { + m_AndroidResolution = value; + ConfigureCamera(); + } + } + + public bool downsample + { + get { return m_Downsample; } + set + { + m_Downsample = value; + SetDownsample(); + } + } + + public ARCameraManager cameraManager + { + get + { + if (m_CameraManager == null) + { + m_CameraManager = UnityEngine.Object.FindObjectOfType(); + } + return m_CameraManager; + } + } + + public ARSession arSession + { + get + { + if (m_ARSession == null) + { + m_ARSession = UnityEngine.Object.FindObjectOfType(); + } + return m_ARSession; + } + } + + public static ImmersalSDK Instance + { + get + { +#if UNITY_EDITOR + if (instance == null && !Application.isPlaying) + { + instance = UnityEngine.Object.FindObjectOfType(); + } +#endif + if (instance == null) + { + Debug.LogError("No ImmersalSDK instance found. Ensure one exists in the scene."); + } + return instance; + } + } + + void Awake() + { + if (instance == null) + { + instance = this; + } + if (instance != this) + { + Debug.LogError("There must be only one ImmersalSDK object in a scene."); + UnityEngine.Object.DestroyImmediate(this); + return; + } + + LogCallback callback_delegate = new LogCallback(Log); + IntPtr intptr_delegate = Marshal.GetFunctionPointerForDelegate(callback_delegate); + Native.PP_RegisterLogCallback(intptr_delegate); + + HttpClientHandler handler = new HttpClientHandler(); + handler.ClientCertificateOptions = ClientCertificateOption.Automatic; + client = new HttpClient(handler); + client.DefaultRequestHeaders.ExpectContinue = false; + } + + void Start() + { + SetFrameRate(); +#if !UNITY_EDITOR + SetDownsample(); +#endif + + onPoseLost?.Invoke(); + } + + [MonoPInvokeCallback(typeof(LogCallback))] + public static void Log(IntPtr ansiString) + { + string msg = Marshal.PtrToStringAnsi(ansiString); + Debug.LogFormat("Plugin: {0}", msg); + } + + private void SetFrameRate() + { + Application.targetFrameRate = targetFrameRate; + } + + private void SetDownsample() + { + if (downsample) + { + Core.SetInteger("LocalizationMaxPixels", 960*720); + } + else + { + Core.SetInteger("LocalizationMaxPixels", 0); + } + } + + private void Update() + { + if (Localizer != null) + { + LocalizerStats stats = Localizer.stats; + if (stats.localizationAttemptCount > 0) + { + q = CurrentResults(stats.localizationSuccessCount); + + if (!m_HasPose && q > 1) + { + m_HasPose = true; + onPoseFound?.Invoke(); + } + + if (m_HasPose && (q < 1 || !Localizer.isTracking)) + { + m_HasPose = false; + Localizer.Reset(); + m_PreviousResults = 0; + m_CurrentResults = 0; + onPoseLost?.Invoke(); + } + + TrackingQuality = q; + } + } + + if (!isHWAR) + { + if (!m_bCamConfigDone && cameraManager != null) + ConfigureCamera(); + } + } + + private void ConfigureCamera() + { +#if !UNITY_EDITOR && (UNITY_ANDROID || UNITY_IOS) + var cameraSubsystem = cameraManager.subsystem; + if (cameraSubsystem == null || !cameraSubsystem.running) + return; + var configurations = cameraSubsystem.GetConfigurations(Allocator.Temp); + if (!configurations.IsCreated || (configurations.Length <= 0)) + return; + int bestError = int.MaxValue; + var currentConfig = cameraSubsystem.currentConfiguration; + int dw = (int)currentConfig?.width; + int dh = (int)currentConfig?.height; + if (dw == 0 && dh == 0) + return; + + CameraResolution reso = androidResolution; + + if (!m_bCamConfigDone) + { + m_InitialConfig = currentConfig; + } + + switch (reso) + { + case CameraResolution.Default: + dw = (int)currentConfig?.width; + dh = (int)currentConfig?.height; + break; + case CameraResolution.HD: + dw = 1280; + dh = 720; + break; + case CameraResolution.FullHD: + dw = 1920; + dh = 1080; + break; + case CameraResolution.Max: + dw = 80000; + dh = 80000; + break; + } + + foreach (var config in configurations) + { + int perror = config.width * config.height - dw * dh; + if (Math.Abs(perror) < bestError) + { + bestError = Math.Abs(perror); + currentConfig = config; + } + } + + if (reso != CameraResolution.Default) { + Debug.LogFormat("resolution = {0}x{1}", (int)currentConfig?.width, (int)currentConfig?.height); + cameraSubsystem.currentConfiguration = currentConfig; + } + else + { + cameraSubsystem.currentConfiguration = m_InitialConfig; + } +#endif + m_bCamConfigDone = true; + } + + int CurrentResults(int localizationResults) { + int diffResults = localizationResults - m_PreviousResults; + m_PreviousResults = localizationResults; + if (diffResults > 0) + { + m_LatestPoseUpdated = Time.time; + m_CurrentResults += diffResults; + if (m_CurrentResults > 3) + { + m_CurrentResults = 3; + } + } + else if (Time.time - m_LatestPoseUpdated > secondsToDecayPose) + { + m_LatestPoseUpdated = Time.time; + if (m_CurrentResults > 0) + { + m_CurrentResults--; + } + } + + return m_CurrentResults; + } + + public void RegisterLocalizer(LocalizerBase localizer) + { + Localizer = localizer; + Localizer.OnReset += OnLocalizerReset; + } + + public void UnRegisterLocalizer() + { + Localizer.OnReset -= OnLocalizerReset; + Localizer = null; + } + + private void OnLocalizerReset() + { + m_CurrentResults = m_PreviousResults = 0; + m_HasPose = false; + } + } +} diff --git a/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/ImmersalSDK.cs.meta b/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/ImmersalSDK.cs.meta new file mode 100644 index 0000000..09e1582 --- /dev/null +++ b/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/ImmersalSDK.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bc609bf82f8e346d593bc1a41c2b7cb8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/ReadOnlyAttribute.cs b/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/ReadOnlyAttribute.cs new file mode 100644 index 0000000..92afed2 --- /dev/null +++ b/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/ReadOnlyAttribute.cs @@ -0,0 +1,6 @@ +using UnityEngine; + +public class ReadOnlyAttribute : PropertyAttribute +{ + +} diff --git a/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/ReadOnlyAttribute.cs.meta b/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/ReadOnlyAttribute.cs.meta new file mode 100644 index 0000000..194beec --- /dev/null +++ b/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/ReadOnlyAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2058786f596e5a34bae16afef4229107 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/Rokid.meta b/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/Rokid.meta new file mode 100644 index 0000000..e0030a6 --- /dev/null +++ b/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/Rokid.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: fbaad84308ffb477989e6e185c52e381 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/Rokid/RokidLocalizer.cs b/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/Rokid/RokidLocalizer.cs new file mode 100644 index 0000000..0cb8504 --- /dev/null +++ b/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/Rokid/RokidLocalizer.cs @@ -0,0 +1,323 @@ +/*=============================================================================== +Copyright (C) 2023 Immersal - Part of Hexagon. All Rights Reserved. + +This file is part of the Immersal SDK. + +The Immersal SDK cannot be copied, distributed, or made available to +third-parties for commercial purposes without written permission of Immersal Ltd. + +Contact sales@immersal.com for licensing requests. +===============================================================================*/ + +using UnityEngine; +using System; +using System.Threading.Tasks; +using Immersal.AR; +using Rokid.UXR.Module; +using Rokid.UXR.Native; +using UnityEngine.Events; +using UnityEngine.UI; + +namespace Immersal.XR.Rokid +{ + public struct RokidCameraData + { + public long Timestamp; + public int Width; + public int Height; + public byte[] Bytes; + public Pose Pose; + public Vector4 Distortion; + public float Alpha; + public Vector4 Intrinsics; + } + + public enum CameraType + { + NV21 = 2 //ARGB not supported. + } + + public class RokidLocalizer : LocalizerBase + { + private static RokidLocalizer instance = null; + + public static RokidLocalizer Instance + { + get + { +#if UNITY_EDITOR + if (instance == null && !Application.isPlaying) + { + instance = UnityEngine.Object.FindObjectOfType(); + } +#endif + if (instance == null) + { + Debug.LogError("No RokidLocalizer instance found. Ensure one exists in the scene."); + } + + return instance; + } + } + + public int Channels => m_cameraType == CameraType.NV21 ? 1 : 4; + public int MultipleLocalizationsCount = 20; + public UnityEvent OnMultipleLocalizations = null; + public RokidCameraData m_LatestCameraData; + + private bool m_IsInitialized = false; + private bool m_MultipleLocalizationEventInvoked = false; + private CameraType m_cameraType = CameraType.NV21; + + void Awake() + { + if (instance == null) + { + instance = this; + } + + if (instance != this) + { + Debug.LogError("There must be only one RokidLocalizer object in a scene."); + UnityEngine.Object.DestroyImmediate(this); + return; + } + } + + public void Init() + { + NativeInterface.NativeAPI.StartCameraPreview(); + // 1 = ARGB, 2 = NV21 + NativeInterface.NativeAPI.SetCameraPreviewDataType((int) m_cameraType); + NativeInterface.NativeAPI.OnCameraDataUpdate += OnCameraDataUpdate; + m_IsInitialized = true; + isTracking = true; + } + + public override void Start() + { + base.Start(); + m_Sdk.RegisterLocalizer(instance); + + Screen.sleepTimeout = SleepTimeout.NeverSleep; + NativeInterface.NativeAPI.Recenter(); + } + + /// + /// Listener of Camera data + /// + /// preview size width + /// preview size height + /// camera image + /// timestamp + public void OnCameraDataUpdate(int width, int height, byte[] data, long ts) + { + Pose pose = NativeInterface.NativeAPI.GetHistoryCameraPhysicsPose(ts); + + // fisheye:alpha,k1,k2,k3,k4; + var d = new float[5]; + NativeInterface.NativeAPI.GetDistortion(d); + var distortionCoefficients = new Vector4(d[1], d[2], d[3], d[4]); + var alpha = d[0]; + var intrinsics = GetIntrinsics(); + + m_LatestCameraData = new RokidCameraData + { + Timestamp = ts, + Width = width, + Height = height, + Bytes = data, + Pose = pose, + Distortion = distortionCoefficients, + Intrinsics = intrinsics, + Alpha = alpha + }; + } + + public void Release() + { + if (m_IsInitialized) + { + NativeInterface.NativeAPI.OnCameraDataUpdate -= OnCameraDataUpdate; + NativeInterface.NativeAPI.StopCameraPreview(); + NativeInterface.NativeAPI.ClearCameraDataUpdate(); + m_IsInitialized = false; + isTracking = false; + } + } + + override public void OnDestroy() + { + Release(); + base.OnDestroy(); + } + + override public void OnApplicationPause(bool pauseStatus) + { + if (pauseStatus) + { + Release(); + } + + base.OnApplicationPause(pauseStatus); + } + + override protected void Update() + { + if (m_IsInitialized == false && NativeInterface.NativeAPI.IsPreviewing()) + { + Init(); + } + + base.Update(); + } + + public override async void Localize() + { + var data = m_LatestCameraData; + data = await UndistortImage(data); + SetPixelBuffer(data.Bytes); + + if (m_PixelBuffer != IntPtr.Zero) + { + stats.localizationAttemptCount++; + + Vector3 camPos = data.Pose.position; + Quaternion camRot = data.Pose.rotation; + float startTime = Time.realtimeSinceStartup; + + Task t = Task.Run(() => + { + return Immersal.Core.LocalizeImage(data.Width, data.Height, ref data.Intrinsics, m_PixelBuffer); + }); + + await t; + + LocalizeInfo locInfo = t.Result; + + Matrix4x4 resultMatrix = Matrix4x4.identity; + resultMatrix.m00 = locInfo.r00; + resultMatrix.m01 = locInfo.r01; + resultMatrix.m02 = locInfo.r02; + resultMatrix.m03 = locInfo.px; + resultMatrix.m10 = locInfo.r10; + resultMatrix.m11 = locInfo.r11; + resultMatrix.m12 = locInfo.r12; + resultMatrix.m13 = locInfo.py; + resultMatrix.m20 = locInfo.r20; + resultMatrix.m21 = locInfo.r21; + resultMatrix.m22 = locInfo.r22; + resultMatrix.m23 = locInfo.pz; + + Vector3 pos = resultMatrix.GetColumn(3); + Quaternion rot = resultMatrix.rotation; + + int mapHandle = locInfo.handle; + int mapId = ARMap.MapHandleToId(mapHandle); + float elapsedTime = Time.realtimeSinceStartup - startTime; + + if (mapId > 0 && ARSpace.mapIdToMap.ContainsKey(mapId)) + { + LocalizerDebugLog(string.Format("Relocalized in {0} seconds", elapsedTime)); + stats.localizationSuccessCount++; + + if (stats.localizationSuccessCount >= MultipleLocalizationsCount && + !m_MultipleLocalizationEventInvoked) + { + OnMultipleLocalizations.Invoke(); + m_MultipleLocalizationEventInvoked = true; + } + + ARMap map = ARSpace.mapIdToMap[mapId]; + + if (mapId != lastLocalizedMapId) + { + if (resetOnMapChange) + { + Reset(); + } + + lastLocalizedMapId = mapId; + OnMapChanged?.Invoke(mapId); + } + + rot *= Quaternion.Euler(0f, 0f, 180.0f); + pos = ARHelper.SwitchHandedness(pos); + rot = ARHelper.SwitchHandedness(rot); + + MapOffset mo = ARSpace.mapIdToOffset[mapId]; + + Matrix4x4 offsetNoScale = Matrix4x4.TRS(mo.position, mo.rotation, Vector3.one); + Vector3 scaledPos = Vector3.Scale(pos, mo.scale); + Matrix4x4 cloudSpace = offsetNoScale * Matrix4x4.TRS(scaledPos, rot, Vector3.one); + Matrix4x4 trackerSpace = Matrix4x4.TRS(camPos, camRot, Vector3.one); + Matrix4x4 m = trackerSpace * (cloudSpace.inverse); + + if (useFiltering) + mo.space.filter.RefinePose(m); + else + ARSpace.UpdateSpace(mo.space, m.GetColumn(3), m.rotation); + + Vector3 p = m.GetColumn(3); + Vector3 euler = m.rotation.eulerAngles; + + GetLocalizerPose(out lastLocalizedPose, mapId, pos, rot, m.inverse); + map.NotifySuccessfulLocalization(mapId); + OnPoseFound?.Invoke(lastLocalizedPose); + } + else + { + LocalizerDebugLog(string.Format("Localization attempt failed after {0} seconds", elapsedTime)); + } + } + else + { + Debug.LogError("No camera pixel buffer"); + } + + base.Localize(); + } + + + private void SetPixelBuffer(byte[] data) + { + unsafe + { + fixed (byte* pinnedData = data) + { + m_PixelBuffer = (IntPtr) pinnedData; + } + } + } + + private Vector4 GetIntrinsics() + { + Vector4 intrinsics = Vector4.zero; + float[] focalLength = new float[2]; + float[] principalPoint = new float[2]; + NativeInterface.NativeAPI.GetFocalLength(focalLength); + NativeInterface.NativeAPI.GetPrincipalPoint(principalPoint); + intrinsics.x = focalLength[0]; + intrinsics.y = focalLength[1]; + intrinsics.z = principalPoint[0]; + intrinsics.w = principalPoint[1]; + return intrinsics; + } + + private async Task UndistortImage(RokidCameraData data) + { + Task<(byte[], int)> d = Task.Run(() => + { + var result = new byte[Channels * data.Width * data.Height]; + var r = Immersal.Core.UndistortFishEyeImage(result, result.Length, data.Bytes, + data.Width, data.Height, Channels, ref data.Intrinsics, + ref data.Distortion, data.Alpha); + return (result, r); + }); + + await d; + data.Bytes = d.Result.Item1; + return data; + } + } +} \ No newline at end of file diff --git a/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/Rokid/RokidLocalizer.cs.meta b/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/Rokid/RokidLocalizer.cs.meta new file mode 100644 index 0000000..09ddb8e --- /dev/null +++ b/Assets/Plugins/ImmersalSDK/Runtime/Core/Scripts/Rokid/RokidLocalizer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: da24111645ab24d549a37ea7da86304b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/ImmersalSDK/Runtime/ImmersalSDK.asmdef b/Assets/Plugins/ImmersalSDK/Runtime/ImmersalSDK.asmdef new file mode 100644 index 0000000..473864c --- /dev/null +++ b/Assets/Plugins/ImmersalSDK/Runtime/ImmersalSDK.asmdef @@ -0,0 +1,22 @@ +{ + "name": "ImmersalSDK", + "rootNamespace": "", + "references": [ + "Unity.XR.ARFoundation", + "Unity.XR.ARSubsystems", + "Unity.EditorCoroutines.Editor", + "UnityEngine.SpatialTracking", + "Unity.TextMeshPro", + "Unity.XR.CoreUtils", + "Rokid.Unity.XR" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": true, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Assets/Plugins/ImmersalSDK/Runtime/ImmersalSDK.asmdef.meta b/Assets/Plugins/ImmersalSDK/Runtime/ImmersalSDK.asmdef.meta new file mode 100644 index 0000000..c260787 --- /dev/null +++ b/Assets/Plugins/ImmersalSDK/Runtime/ImmersalSDK.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: ff70fce64a3314305977bdf5610ed86b +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: