308 lines
12 KiB
C#
308 lines
12 KiB
C#
|
|
using System;
|
|||
|
|
using UnityEngine;
|
|||
|
|
using UnityEditor.XR.ARSubsystems.InternalBridge;
|
|||
|
|
using UnityEngine.XR.ARSubsystems;
|
|||
|
|
|
|||
|
|
namespace UnityEditor.XR.ARSubsystems
|
|||
|
|
{
|
|||
|
|
[CustomEditor(typeof(XRReferenceImageLibrary))]
|
|||
|
|
class XRReferenceImageLibraryEditor : Editor
|
|||
|
|
{
|
|||
|
|
static class Content
|
|||
|
|
{
|
|||
|
|
static readonly int s_AddImageControlId;
|
|||
|
|
static readonly GUIContent s_AddButtonContent;
|
|||
|
|
static readonly GUIContent s_RemoveButtonContent;
|
|||
|
|
|
|||
|
|
static Content()
|
|||
|
|
{
|
|||
|
|
s_AddButtonContent = new GUIContent("Add Image");
|
|||
|
|
s_AddImageControlId = GUIUtility.GetControlID(s_AddButtonContent, FocusType.Keyboard);
|
|||
|
|
|
|||
|
|
s_RemoveButtonContent = new GUIContent(
|
|||
|
|
string.Empty,
|
|||
|
|
EditorGUIUtility.FindTexture("d_winbtn_win_close"),
|
|||
|
|
"Remove this image from the database");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public static readonly GUIContent keepTexture = new GUIContent(
|
|||
|
|
"Keep Texture at Runtime",
|
|||
|
|
"If enabled, the texture will be available in the Player. Otherwise, the texture will be null in the Player.");
|
|||
|
|
|
|||
|
|
public static readonly GUIContent name = new GUIContent(
|
|||
|
|
"Name",
|
|||
|
|
"The name of the reference image. This can useful for matching detected images with their reference image at runtime.");
|
|||
|
|
|
|||
|
|
public static readonly GUIContent specifySize = new GUIContent(
|
|||
|
|
"Specify Size",
|
|||
|
|
"If enabled, you can specify the physical dimensions of the image in meters. Some platforms require this.");
|
|||
|
|
|
|||
|
|
public static readonly GUIContent sizePixels = new GUIContent(
|
|||
|
|
"Texture Size (pixels)",
|
|||
|
|
"The texture dimensions, in pixels.");
|
|||
|
|
|
|||
|
|
public static readonly GUIContent sizeMeters = new GUIContent(
|
|||
|
|
"Physical Size (meters)",
|
|||
|
|
"The dimensions of the physical image, in meters.");
|
|||
|
|
|
|||
|
|
public static int addImageControlId
|
|||
|
|
{
|
|||
|
|
get
|
|||
|
|
{
|
|||
|
|
return s_AddImageControlId;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public static bool addButton
|
|||
|
|
{
|
|||
|
|
get
|
|||
|
|
{
|
|||
|
|
return GUILayout.Button(s_AddButtonContent);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public static bool removeButton
|
|||
|
|
{
|
|||
|
|
get
|
|||
|
|
{
|
|||
|
|
return GUI.Button(
|
|||
|
|
GUILayoutUtility.GetRect(s_RemoveButtonContent, GUI.skin.button, GUILayout.ExpandWidth(false)),
|
|||
|
|
s_RemoveButtonContent,
|
|||
|
|
GUI.skin.button);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
SerializedProperty m_ReferenceImages;
|
|||
|
|
|
|||
|
|
void OnEnable()
|
|||
|
|
{
|
|||
|
|
m_ReferenceImages = serializedObject.FindProperty("m_Images");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public override void OnInspectorGUI()
|
|||
|
|
{
|
|||
|
|
serializedObject.Update();
|
|||
|
|
|
|||
|
|
int indexToRemove = -1;
|
|||
|
|
for (int i = 0; i < m_ReferenceImages.arraySize; ++i)
|
|||
|
|
{
|
|||
|
|
var shouldRemove = ReferenceImageField(i);
|
|||
|
|
if (shouldRemove)
|
|||
|
|
{
|
|||
|
|
indexToRemove = i;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
EditorGUILayout.Separator();
|
|||
|
|
|
|||
|
|
if (i < m_ReferenceImages.arraySize - 1)
|
|||
|
|
EditorGUILayout.LabelField(string.Empty, GUI.skin.horizontalSlider);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (indexToRemove > -1)
|
|||
|
|
m_ReferenceImages.DeleteArrayElementAtIndex(indexToRemove);
|
|||
|
|
|
|||
|
|
serializedObject.ApplyModifiedProperties();
|
|||
|
|
|
|||
|
|
if (Content.addButton)
|
|||
|
|
{
|
|||
|
|
Undo.RecordObject(target, "Add reference image");
|
|||
|
|
(target as XRReferenceImageLibrary).Add();
|
|||
|
|
EditorUtility.SetDirty(target);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Generates the GUI for a reference image at index <paramref name="index"/>.
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="index">The index of the reference image in the <see cref="XRReferenceImageLibrary"/>.</param>
|
|||
|
|
/// <returns>True if the image should be removed.</returns>
|
|||
|
|
bool ReferenceImageField(int index)
|
|||
|
|
{
|
|||
|
|
var library = target as XRReferenceImageLibrary;
|
|||
|
|
var referenceImageProperty = m_ReferenceImages.GetArrayElementAtIndex(index);
|
|||
|
|
var sizeProperty = referenceImageProperty.FindPropertyRelative("m_Size");
|
|||
|
|
var specifySizeProperty = referenceImageProperty.FindPropertyRelative("m_SpecifySize");
|
|||
|
|
var nameProperty = referenceImageProperty.FindPropertyRelative("m_Name");
|
|||
|
|
|
|||
|
|
var referenceImage = library[index];
|
|||
|
|
var texturePath = AssetDatabase.GUIDToAssetPath(referenceImage.textureGuid.ToString("N"));
|
|||
|
|
var texture = AssetDatabase.LoadAssetAtPath<Texture2D>(texturePath);
|
|||
|
|
|
|||
|
|
bool shouldRemove = false;
|
|||
|
|
bool wasTextureUpdated = false;
|
|||
|
|
|
|||
|
|
using (new EditorGUILayout.HorizontalScope())
|
|||
|
|
{
|
|||
|
|
using (var textureCheck = new EditorGUI.ChangeCheckScope())
|
|||
|
|
{
|
|||
|
|
texture = TextureField(texture);
|
|||
|
|
wasTextureUpdated = textureCheck.changed;
|
|||
|
|
}
|
|||
|
|
shouldRemove = Content.removeButton;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
EditorGUILayout.PropertyField(nameProperty, Content.name);
|
|||
|
|
EditorGUILayout.PropertyField(specifySizeProperty, Content.specifySize);
|
|||
|
|
|
|||
|
|
if (specifySizeProperty.boolValue)
|
|||
|
|
{
|
|||
|
|
using (new EditorGUI.IndentLevelScope())
|
|||
|
|
{
|
|||
|
|
using (new EditorGUI.DisabledScope(true))
|
|||
|
|
{
|
|||
|
|
var imageDimensions = (texture == null) ? Vector2Int.zero : GetTextureSize(texture);
|
|||
|
|
EditorGUILayout.Vector2IntField(Content.sizePixels, imageDimensions);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
using (var changeCheck = new EditorGUI.ChangeCheckScope())
|
|||
|
|
{
|
|||
|
|
EditorGUILayout.PropertyField(sizeProperty, Content.sizeMeters);
|
|||
|
|
|
|||
|
|
// Prevent dimensions from going below zero.
|
|||
|
|
var size = new Vector2(
|
|||
|
|
Mathf.Max(0f, sizeProperty.vector2Value.x),
|
|||
|
|
Mathf.Max(0f, sizeProperty.vector2Value.y));
|
|||
|
|
|
|||
|
|
if ((sizeProperty.vector2Value.x < 0f) ||
|
|||
|
|
(sizeProperty.vector2Value.y < 0f))
|
|||
|
|
{
|
|||
|
|
sizeProperty.vector2Value = size;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (changeCheck.changed)
|
|||
|
|
{
|
|||
|
|
if (texture == null)
|
|||
|
|
{
|
|||
|
|
// If the texture is null, then we just set whatever the user specifies
|
|||
|
|
sizeProperty.vector2Value = size;
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
// Otherwise, maintain the aspect ratio
|
|||
|
|
var delta = referenceImage.size - size;
|
|||
|
|
delta = new Vector2(Mathf.Abs(delta.x), Mathf.Abs(delta.y));
|
|||
|
|
|
|||
|
|
// Determine which dimension has changed and compute the unchanged dimension
|
|||
|
|
if (delta.x > delta.y)
|
|||
|
|
{
|
|||
|
|
sizeProperty.vector2Value = SizeFromWidth(texture, size);
|
|||
|
|
}
|
|||
|
|
else if (delta.y > 0f)
|
|||
|
|
{
|
|||
|
|
sizeProperty.vector2Value = SizeFromHeight(texture, size);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else if (wasTextureUpdated && texture != null)
|
|||
|
|
{
|
|||
|
|
// If the texture changed, re-compute width / height
|
|||
|
|
if (size.x == 0f)
|
|||
|
|
{
|
|||
|
|
sizeProperty.vector2Value = SizeFromHeight(texture, size);
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
sizeProperty.vector2Value = SizeFromWidth(texture, size);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ((sizeProperty.vector2Value.x <= 0f) || (sizeProperty.vector2Value.y <= 0f))
|
|||
|
|
{
|
|||
|
|
EditorGUILayout.HelpBox("Dimensions must be greater than zero.", MessageType.Warning);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
using (new EditorGUI.DisabledScope(texture == null))
|
|||
|
|
using (var changeCheck = new EditorGUI.ChangeCheckScope())
|
|||
|
|
{
|
|||
|
|
bool keepTexture = EditorGUILayout.Toggle(Content.keepTexture, referenceImage.texture != null);
|
|||
|
|
if (changeCheck.changed || wasTextureUpdated)
|
|||
|
|
{
|
|||
|
|
// Auto-populate the name from the texture's name if it is not set already.
|
|||
|
|
if (string.IsNullOrEmpty(nameProperty.stringValue) && texture != null)
|
|||
|
|
{
|
|||
|
|
nameProperty.stringValue = texture.name;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Apply properties for anything that may have been modified, otherwise
|
|||
|
|
// the texture change may be overwritten.
|
|||
|
|
serializedObject.ApplyModifiedProperties();
|
|||
|
|
|
|||
|
|
// Create an undo entry, modify, set dirty
|
|||
|
|
Undo.RecordObject(target, "Update reference image texture");
|
|||
|
|
library.SetTexture(index, texture, keepTexture);
|
|||
|
|
EditorUtility.SetDirty(target);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return shouldRemove;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Computes a new size using the width and aspect ratio from the Texture2D.
|
|||
|
|
/// Width remains the same as before; height is recalculated.
|
|||
|
|
/// </summary>
|
|||
|
|
static Vector2 SizeFromWidth(Texture2D texture, Vector2 size)
|
|||
|
|
{
|
|||
|
|
var textureSize = GetTextureSize(texture);
|
|||
|
|
return new Vector2(size.x, size.x * (float)textureSize.y / (float)textureSize.x);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Computes a new size using the height and aspect ratio from the Texture2D.
|
|||
|
|
/// Height remains the same as before; width is recalculated.
|
|||
|
|
/// </summary>
|
|||
|
|
static Vector2 SizeFromHeight(Texture2D texture, Vector2 size)
|
|||
|
|
{
|
|||
|
|
var textureSize = GetTextureSize(texture);
|
|||
|
|
return new Vector2(size.y * (float)textureSize.x / (float)textureSize.y, size.y);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static Vector2Int GetTextureSize(Texture2D texture)
|
|||
|
|
{
|
|||
|
|
if (texture == null)
|
|||
|
|
throw new ArgumentNullException(nameof(texture));
|
|||
|
|
|
|||
|
|
var textureImporter = AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(texture)) as TextureImporter;
|
|||
|
|
if (textureImporter == null)
|
|||
|
|
{
|
|||
|
|
return new Vector2Int(texture.width, texture.height);
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
#if UNITY_2021_2_OR_NEWER
|
|||
|
|
textureImporter.GetSourceTextureWidthAndHeight(out var width, out var height);
|
|||
|
|
return new Vector2Int(width, height);
|
|||
|
|
#else
|
|||
|
|
return TextureImporterInternals.GetSourceTextureDimensions(textureImporter);
|
|||
|
|
#endif
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static Texture2D TextureField(Texture2D texture)
|
|||
|
|
{
|
|||
|
|
const int k_MaxSideLength = 64;
|
|||
|
|
int width = k_MaxSideLength, height = k_MaxSideLength;
|
|||
|
|
if (texture != null)
|
|||
|
|
{
|
|||
|
|
var textureSize = GetTextureSize(texture);
|
|||
|
|
|
|||
|
|
if (textureSize.x > textureSize.y)
|
|||
|
|
height = width * textureSize.y / textureSize.x;
|
|||
|
|
else
|
|||
|
|
width = height * textureSize.x / textureSize.y;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return (Texture2D)EditorGUILayout.ObjectField(
|
|||
|
|
texture,
|
|||
|
|
typeof(Texture2D),
|
|||
|
|
true,
|
|||
|
|
GUILayout.Width(width),
|
|||
|
|
GUILayout.Height(height));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|