XRReferenceImageLibraryEditor.cs 12.3 KB
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("texture");

            var textureImporter = AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(texture)) as TextureImporter;
            if (textureImporter == null)
            {
                return new Vector2Int(texture.width, texture.height);
            }
            else
            {
                return TextureImporterInternals.GetSourceTextureDimensions(textureImporter);
            }
        }

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