Skip to content

Instantly share code, notes, and snippets.

@SimonDarksideJ
Created November 15, 2022 11:55
Show Gist options
  • Save SimonDarksideJ/71d502cc1f1fa9b50fe5cb6012caf7fe to your computer and use it in GitHub Desktop.
Save SimonDarksideJ/71d502cc1f1fa9b50fe5cb6012caf7fe to your computer and use it in GitHub Desktop.
ARFoundation Dynamic Image loading solution using the "Update" method instead of "Job" method
using System;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.ARSubsystems;
/// <summary>
/// Adds images to the reference library at runtime.
/// </summary>
[RequireComponent(typeof(ARTrackedImageManager))]
public class DynamicLibraryManager : MonoBehaviour
{
private ARTrackedImageManager manager;
[Serializable]
public class ImageData
{
[SerializeField, Tooltip("The source texture for the image. Must be marked as readable.")]
private Texture2D m_Texture;
public Texture2D texture
{
get => m_Texture;
set => m_Texture = value;
}
[SerializeField, Tooltip("The name for this image.")]
Guid referenceGuid;
public Guid ReferenceGuid
{
get => referenceGuid;
set => referenceGuid = value;
}
public AddReferenceImageJobState jobState { get; set; }
}
private List<ImageData> processingImages = new List<ImageData>();
[SerializeField, Tooltip("The set of images to add to the image library at runtime")]
private Queue<ImageData> processingImagesQueue = new Queue<ImageData>();
enum State
{
NoImagesAdded,
AddImagesRequested,
AddingImages,
Done,
Error
}
State m_State;
string m_ErrorMessage = "";
private StringBuilder sb = new StringBuilder();
public Action<ImageData> OnImageLoaded;
void SetError(string errorMessage)
{
m_State = State.Error;
sb.AppendLine($"Error: {errorMessage}");
m_ErrorMessage = sb.ToString();
}
private void Awake()
{
manager = GetComponent<ARTrackedImageManager>();
}
public void ProcessImage(Texture2D image, Guid referenceGuid)
{
Debug.Log($"Processing Image {image.name}");
processingImagesQueue.Enqueue(new ImageData() { texture = image, ReferenceGuid = referenceGuid });
}
public void ProcessImage(Texture2D[] images, Guid[] referenceGuids)
{
if (!images.Length.Equals(referenceGuids.Length))
{
SetError("images and guid lists do not match");
}
for (int i = 0; i < images.Length; i++)
{
ProcessImage(images[i], referenceGuids[i]);
}
}
public void ProcessImage(ImageData image)
{
processingImagesQueue.Enqueue(image);
}
public void ProcessImage(ImageData[] images)
{
foreach (var image in images)
{
ProcessImage(image);
}
}
void Update()
{
if (processingImagesQueue.Count > 0 && processingImages.Count.Equals(0))
{
int imageProcessingCount = processingImagesQueue.Count;
for (int i = 0; i < imageProcessingCount; i++)
{
var processingImage = processingImagesQueue.Dequeue();
processingImages.Add(processingImage);
}
Debug.Log($"Imaged added, processing {processingImages.Count} images");
m_State = State.AddImagesRequested;
}
switch (m_State)
{
case State.AddImagesRequested:
{
if (processingImages.Count.Equals(0))
{
SetError("No images to add.");
break;
}
if (manager == null || manager.referenceLibrary == null)
{
SetError($"No {nameof(ARTrackedImageManager)} available.");
break;
}
sb.Clear();
// You can either add raw image bytes or use the extension method (used below) which accepts
// a texture. To use a texture, however, its import settings must have enabled read/write
// access to the texture.
foreach (var image in processingImages)
{
Debug.Log($"Validating image {image.texture.name}");
if (!image.texture.isReadable)
{
SetError($"Image {image.texture.name} must be readable to be added to the image library.");
break;
}
}
if (manager.referenceLibrary is MutableRuntimeReferenceImageLibrary mutableLibrary)
{
ImageData processingImage = null;
try
{
foreach (var image in processingImages)
{
Debug.Log($"Importing image {image.texture.name}");
processingImage = image;
// Note: You do not need to do anything with the returned JobHandle, but it can be
// useful if you want to know when the image has been added to the library since it may
// take several frames.
image.jobState = mutableLibrary.ScheduleAddImageWithValidationJob(image.texture, image.texture.name, 1);
}
m_State = State.AddingImages;
}
catch (InvalidOperationException e)
{
SetError($"ScheduleAddImageJob for image {processingImage.texture.name} threw exception: {e.Message}");
}
}
else
{
SetError($"The reference image library is not mutable.");
}
if (m_State == State.Error)
{
processingImages.Clear();
}
break;
}
case State.AddingImages:
{
// Check for completion
var done = true;
foreach (var image in processingImages)
{
if (!image.jobState.jobHandle.IsCompleted)
{
done = false;
break;
}
if (image.jobState.status != AddReferenceImageJobStatus.Success)
{
SetError($"The image {image.texture.name} was not loaded due to {image.jobState.status}");
}
else
{
OnImageLoaded?.Invoke( image );
}
}
if (done)
{
m_State = State.Done;
}
if (m_State is State.Done or State.Error)
{
processingImages.Clear();
}
break;
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment