All of the code for the project can be found below or on GitHub.
^PersistentARinDomain.cs
:^:
using System.Collections;
using System.Collections.Generic;
using Auki.ConjureKit;
using Auki.ConjureKit.Manna;
using Auki.Integration.ARFoundation;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.ARSubsystems;
using Random = UnityEngine.Random;
public class PersistentARinDomain : MonoBehaviour
{
[SerializeField] private Camera arCamera;
[SerializeField] private GameObject cube;
[SerializeField] private GameObject calibrateUI;
[SerializeField] private ARRaycastManager raycastManager;
[SerializeField] private Button createCubeButton;
private const string AppKey = "YOUR_APP_KEY" ;
private const string AppSecret = "YOUR_APP_SECRET" ;
private IConjureKit _conjureKit;
private Manna _manna;
private bool _calibrated = false ;
private List<ARRaycastHit> _arRaycastHits = new List<ARRaycastHit>();
private SaveData _saveData = new SaveData();
private void Start ( )
{
_conjureKit = new ConjureKit(
arCamera.transform,
AppKey,
AppSecret);
_manna = new Manna(_conjureKit);
var textureProviderComp = CameraFrameProvider.GetOrCreateComponent();
textureProviderComp.OnNewFrameReady += frame => _manna.ProcessVideoFrameTexture(frame.Texture, frame.ARProjectionMatrix, frame.ARWorldToCameraMatrix);
_manna.OnLighthouseTracked += OnLighthouseTracked;
createCubeButton.onClick.AddListener(OnCubeButtonClick);
_conjureKit.Connect();
}
private void OnLighthouseTracked ( Lighthouse lighthouse, Pose qrPose, bool isCalibrationGood )
{
// If the QR detection was good enough and the QR code is static (generated from the posemesh console),
// hide the calibration view and show the cube marker
if (isCalibrationGood && lighthouse.Type == Lighthouse.LighthouseType.Static)
{
if (!_calibrated)
{
_calibrated = true ;
calibrateUI.SetActive( false );
cube.SetActive( true );
LoadLocally();
}
}
}
private void Update ( )
{
// Make a raycast from the center of the screen to an AR plane (floor, wall, or any other surface detected by ARFoundation)
var ray = arCamera.ViewportPointToRay(Vector3.one * 0. 5f);
if (raycastManager.Raycast(ray, _arRaycastHits, TrackableType.PlaneWithinPolygon))
{
// Place the cube where the raycast hits a plane. Move it half the cube size along the hit normal (up if on the ground, forward if on the wall)
cube.transform.position = _arRaycastHits[ 0 ].pose.position + _arRaycastHits[ 0 ].pose.up * cube.transform.localScale.x / 2f;
// Rotate the cube only around y axis to always face the camera
cube.transform.rotation = Quaternion.Euler(Vector3.Scale(arCamera.transform.rotation.eulerAngles, Vector3.up));
}
}
private void PlaceCube ( Vector3 position, Quaternion rotation, Color color )
{
var placedCube = Instantiate(cube, position, rotation);
placedCube.GetComponent<Renderer>().material.color = color;
placedCube.gameObject.SetActive( true );
}
private void OnCubeButtonClick ( )
{
var color = Random.ColorHSV();
// Place the cube where the cube marker is
PlaceCube(cube.transform.position, cube.transform.rotation, color);
// Save the position and rotation information locally
_saveData.cubes.Add( new CubeData(cube.transform.position, cube.transform.rotation, color));
SaveLocally();
}
private void SaveLocally ( )
{
var json = JsonUtility.ToJson(_saveData);
PlayerPrefs.SetString( "_saveData" , json);
PlayerPrefs.Save();
}
private void LoadLocally ( )
{
if (!PlayerPrefs.HasKey( "_saveData" ))
return ;
var json = PlayerPrefs.GetString( "_saveData" );
_saveData = JsonUtility.FromJson<SaveData>(json);
foreach ( var savedCube in _saveData.cubes)
{
PlaceCube(savedCube.position.ToVector3(), savedCube.rotation.ToQuaternion(), savedCube.color.ToColor());
}
}
}
^SaveData.cs^
:
using System;
using System.Collections.Generic;
using UnityEngine;
[Serializable]
public class SaveData
{
public List<CubeData> cubes = new List<CubeData>();
}
// Because Unity's Vector3, Quaternion and Color structs are not marked as [Serializable] they can't be serialized into JSON.
// For that we create serializable versions of each one. There can be other approaches depending on how you serialize/deserialize the data.
[Serializable]
public class CubeData
{
public SerializableVector3 position;
public SerializableQuaternion rotation;
public SerializableColor color;
public CubeData ( ) {}
public CubeData ( Vector3 position, Quaternion rotation, Color color )
{
this .position = new SerializableVector3(position);
this .rotation = new SerializableQuaternion(rotation);
this .color = new SerializableColor(color);
}
}
[Serializable]
public class SerializableVector3
{
public float x, y, z;
public SerializableVector3 ( ) {}
public SerializableVector3 ( Vector3 sourceVector )
{
x = sourceVector.x;
y = sourceVector.y;
z = sourceVector.z;
}
public Vector3 ToVector3() => new Vector3(x, y, z);
}
[Serializable]
public class SerializableQuaternion
{
public float x, y, z, w;
public SerializableQuaternion ( ) {}
public SerializableQuaternion ( Quaternion sourceQuaternion )
{
x = sourceQuaternion.x;
y = sourceQuaternion.y;
z = sourceQuaternion.z;
w = sourceQuaternion.w;
}
public Quaternion ToQuaternion() => new Quaternion(x, y, z, w);
}
[Serializable]
public class SerializableColor
{
public float r, g, b, a;
public SerializableColor ( ) {}
public SerializableColor ( Color sourceColor )
{
r = sourceColor.r;
g = sourceColor.g;
b = sourceColor.b;
a = sourceColor.a;
}
public Color ToColor() => new Color(r, g, b, a);
}
The full project can be found on GitHub .