Created
November 15, 2022 11:55
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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