This commit is contained in:
2025-09-04 11:46:34 +08:00
parent 60e4ef39ed
commit 2bf4e2bd7f
56 changed files with 3635 additions and 0 deletions

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 9c0884dab4c8bbc4d8a979f9838b8cbe
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d9779e2a208874279abf2acd33869873
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d4e1b058addf948b589443857632c8e7
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: f192b64343523994e999fa8a5c964120
folderAsset: yes
timeCreated: 1466076008
licenseType: Free
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e3f6095e1f6cb45b399b9c5a3def17f9
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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<double>("getLatitude");
#endif
}
public static double GetLongitude()
{
#if UNITY_IOS
return getLongitude();
#elif PLATFORM_ANDROID
return obj.CallStatic<double>("getLongitude");
#endif
}
public static double GetAltitude()
{
#if UNITY_IOS
return getAltitude();
#elif PLATFORM_ANDROID
return obj.CallStatic<double>("getAltitude");
#endif
}
public static double GetHorizontalAccuracy()
{
#if UNITY_IOS
return getHorizontalAccuracy();
#elif PLATFORM_ANDROID
return obj.CallStatic<double>("getHorizontalAccuracy");
#endif
}
public static double GetVerticalAccuracy()
{
#if UNITY_IOS
return getVerticalAccuracy();
#elif PLATFORM_ANDROID
return obj.CallStatic<double>("getVerticalAccuracy");
#endif
}
public static bool LocationServicesEnabled()
{
#if UNITY_IOS
return locationServicesEnabled();
#elif PLATFORM_ANDROID
return obj.CallStatic<bool>("locationServicesEnabled");
#endif
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1f141c749ad8c4a4dbdd7b392e91a9c3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 867bc362f511875419b00825c35adb62
folderAsset: yes
timeCreated: 1469455304
licenseType: Free
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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:

View File

@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildMachineOSBuild</key>
<string>22D68</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>PosePlugin</string>
<key>CFBundleIdentifier</key>
<string>com.immersal.PosePlugin</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>PosePlugin</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
</array>
<key>CFBundleVersion</key>
<string>1</string>
<key>DTCompiler</key>
<string>com.apple.compilers.llvm.clang.1_0</string>
<key>DTPlatformBuild</key>
<string></string>
<key>DTPlatformName</key>
<string>macosx</string>
<key>DTPlatformVersion</key>
<string>13.3</string>
<key>DTSDKBuild</key>
<string>22E245</string>
<key>DTSDKName</key>
<string>macosx13.3</string>
<key>DTXcode</key>
<string>1430</string>
<key>DTXcodeBuild</key>
<string>14E222b</string>
<key>LSMinimumSystemVersion</key>
<string>12.0</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2022 Immersal - Part of Hexagon. All rights reserved.</string>
</dict>
</plist>

View File

@@ -0,0 +1,115 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>files</key>
<dict/>
<key>files2</key>
<dict/>
<key>rules</key>
<dict>
<key>^Resources/</key>
<true/>
<key>^Resources/.*\.lproj/</key>
<dict>
<key>optional</key>
<true/>
<key>weight</key>
<real>1000</real>
</dict>
<key>^Resources/.*\.lproj/locversion.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>1100</real>
</dict>
<key>^Resources/Base\.lproj/</key>
<dict>
<key>weight</key>
<real>1010</real>
</dict>
<key>^version.plist$</key>
<true/>
</dict>
<key>rules2</key>
<dict>
<key>.*\.dSYM($|/)</key>
<dict>
<key>weight</key>
<real>11</real>
</dict>
<key>^(.*/)?\.DS_Store$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>2000</real>
</dict>
<key>^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/</key>
<dict>
<key>nested</key>
<true/>
<key>weight</key>
<real>10</real>
</dict>
<key>^.*</key>
<true/>
<key>^Info\.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>20</real>
</dict>
<key>^PkgInfo$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>20</real>
</dict>
<key>^Resources/</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
<key>^Resources/.*\.lproj/</key>
<dict>
<key>optional</key>
<true/>
<key>weight</key>
<real>1000</real>
</dict>
<key>^Resources/.*\.lproj/locversion.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>1100</real>
</dict>
<key>^Resources/Base\.lproj/</key>
<dict>
<key>weight</key>
<real>1010</real>
</dict>
<key>^[^/]+$</key>
<dict>
<key>nested</key>
<true/>
<key>weight</key>
<real>10</real>
</dict>
<key>^embedded\.provisionprofile$</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
<key>^version\.plist$</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
</dict>
</dict>
</plist>

View File

@@ -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:

View File

@@ -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:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: bba6e78353c194c489388f40a4f2bd6c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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: []

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 181e478384bef4862b94db5a060df1b6
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 100100000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 9e40124e0b172426ba7c8c93d75c788b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 30df7aac5e0027a46a0a8d0a3afc1f6a
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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
}
}
}

View File

@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 80bb3c180be837f4daba8ccfbd06b6eb
ShaderImporter:
externalObjects: {}
defaultTextures: []
nonModifiableTextures: []
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: eee6937ee2c707d42a0b601979052c50
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 09263de2dbfc4a743bdd4c5ff73574ef
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f78d5a866c99746cdb1556667e77111c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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<int>
{
}
[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<int, ARMap> mapHandleToMap = new Dictionary<int, ARMap>();
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<int> LoadMap(byte[] mapBytes = null, int mapId = -1)
{
if (mapBytes == null)
{
mapBytes = (mapFile != null) ? mapFile.bytes : null;
}
if (mapBytes != null)
{
Task<int> 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<MeshFilter>();
if (m_MeshFilter == null)
{
m_MeshFilter = gameObject.AddComponent<MeshFilter>();
}
}
if (m_MeshRenderer == null)
{
m_MeshRenderer = gameObject.GetComponent<MeshRenderer>();
if (m_MeshRenderer == null)
{
m_MeshRenderer = gameObject.AddComponent<MeshRenderer>();
}
}
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<ARSpace>();
if (!m_ARSpace)
{
GameObject go = new GameObject("AR Space");
m_ARSpace = go.AddComponent<ARSpace>();
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<MetadataFile>(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<MapLicense>(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;
}
}
}

View File

@@ -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:

View File

@@ -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<Transform, SpaceContainer> transformToSpace = new Dictionary<Transform, SpaceContainer>();
public static Dictionary<SpaceContainer, Transform> spaceToTransform = new Dictionary<SpaceContainer, Transform>();
public static Dictionary<int, MapOffset> mapIdToOffset = new Dictionary<int, MapOffset>();
public static Dictionary<int, ARMap> mapIdToMap = new Dictionary<int, ARMap>();
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();
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 53a086897bd294848aa39db1a6b2e6ed
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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<LocalizerPose> OnPoseFound;
public Action<int> 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<Transform, SpaceContainer> 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<Transform, SpaceContainer> 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];
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 06ec00cf50a16443a92eafce8161c1a1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3081ca6de5f736847b8e96b9683e38d7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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
{
/// <summary>
/// Get a Vector3 point cloud representation of the map data.
/// </summary>
/// <param name="mapHandle">An integer map handle</param>
/// <param name="points">A preallocated Vector3 array for the points</param>
/// <returns>Returns the number of points if succeeded, 0 otherwise.</returns>
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;
}
/// <summary>
/// Get point count of the map's point cloud.
/// </summary>
/// <param name="mapHandle">An integer map handle</param>
/// <returns>Returns the number of points.</returns>
public static int GetPointCloudSize(int mapHandle) => Native.icvPointsGetCount(mapHandle);
/// <summary>
/// Load map data from a .bytes file.
/// </summary>
/// <param name="buffer">Map data as a byte array</param>
/// <returns>An integer map handle.</returns>
public static int LoadMap(byte[] buffer, string license) => Native.icvLoadMap(buffer, license);
/// <summary>
/// Free the map data from memory.
/// </summary>
/// <param name="mapHandle">An integer map handle</param>
/// <returns>Returns 1 if succeeded, 0 otherwise.</returns>
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;
}
/// <summary>
/// Gets the position and orientation of the image within the map.
/// </summary>
/// <param name="width">Image width</param>
/// <param name="height">Image height</param>
/// <param name="intrinsics">Camera intrinsics</param>
/// <param name="pixels">Raw pixel buffer data from the camera</param>
/// <param name="rot">Camera rotation as float array</param>
/// <returns>An integer map ID if succeeded, -1 otherwise</returns>
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);
}
/// <summary>
/// Gets the position and orientation of the image within the map.
/// </summary>
/// <param name="width">Image width</param>
/// <param name="height">Image height</param>
/// <param name="intrinsics">Camera intrinsics</param>
/// <param name="pixels">Raw pixel buffer data from the camera</param>
/// <returns>An integer map ID if succeeded, -1 otherwise</returns>
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);
}
/// <summary>
/// Gets the position and orientation of the image within the map.
/// </summary>
/// <param name="width">Image width</param>
/// <param name="height">Image height</param>
/// <param name="intrinsics">Camera intrinsics</param>
/// <param name="pixels">Raw pixel buffer data from the camera</param>
/// <param name="rot">Camera rotation as float array</param>
/// <returns>An integer map ID if succeeded, -1 otherwise</returns>
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);
}
/// <summary>
///
/// </summary>
/// <param name="ecef"></param>
/// <param name="map"></param>
/// <param name="mapToEcef"></param>
/// <returns></returns>
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;
}
/// <summary>
///
/// </summary>
/// <param name="wgs84"></param>
/// <param name="ecef"></param>
/// <returns></returns>
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;
}
/// <summary>
///
/// </summary>
/// <param name="ecef"></param>
/// <param name="wgs84"></param>
/// <returns></returns>
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;
}
/// <summary>
///
/// </summary>
/// <param name="map"></param>
/// <param name="ecef"></param>
/// <param name="mapToEcef"></param>
/// <returns></returns>
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;
}
/// <summary>
///
/// </summary>
/// <param name="wgs84"></param>
/// <param name="map"></param>
/// <param name="mapToEcef"></param>
/// <returns></returns>
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);
}
/// <summary>
///
/// </summary>
/// <param name="ecef"></param>
/// <param name="map"></param>
/// <param name="mapToEcef"></param>
/// <returns></returns>
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;
}
/// <summary>
///
/// </summary>
/// <param name="map"></param>
/// <param name="ecef"></param>
/// <param name="mapToEcef"></param>
/// <returns></returns>
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;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="parameter">Parameter name</param>
/// <param name="value">An integer parameter value</param>
/// <returns>Returns 1 if succeeded, -1 otherwise.</returns>
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);
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 2a0cde8ffdea2d24bb36590dbe44cb53
timeCreated: 1464961012
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 068ff142a326946a4921c7907dd3368c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6a04c0995666dab4988bf70980c9d391
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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<ARCameraManager>();
}
return m_CameraManager;
}
}
public ARSession arSession
{
get
{
if (m_ARSession == null)
{
m_ARSession = UnityEngine.Object.FindObjectOfType<ARSession>();
}
return m_ARSession;
}
}
public static ImmersalSDK Instance
{
get
{
#if UNITY_EDITOR
if (instance == null && !Application.isPlaying)
{
instance = UnityEngine.Object.FindObjectOfType<ImmersalSDK>();
}
#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;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bc609bf82f8e346d593bc1a41c2b7cb8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,6 @@
using UnityEngine;
public class ReadOnlyAttribute : PropertyAttribute
{
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2058786f596e5a34bae16afef4229107
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: fbaad84308ffb477989e6e185c52e381
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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<RokidLocalizer>();
}
#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();
}
/// <summary>
/// Listener of Camera data
/// </summary>
/// <param name="width">preview size width</param>
/// <param name="height">preview size height</param>
/// <param name="yuvImage">camera image</param>
/// <param name="ts">timestamp</param>
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<LocalizeInfo> 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<RokidCameraData> 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;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: da24111645ab24d549a37ea7da86304b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: ff70fce64a3314305977bdf5610ed86b
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant: