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); } } /// /// Generates the GUI for a reference image at index . /// /// The index of the reference image in the . /// True if the image should be removed. 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(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; } /// /// Computes a new size using the width and aspect ratio from the Texture2D. /// Width remains the same as before; height is recalculated. /// static Vector2 SizeFromWidth(Texture2D texture, Vector2 size) { var textureSize = GetTextureSize(texture); return new Vector2(size.x, size.x * (float)textureSize.y / (float)textureSize.x); } /// /// Computes a new size using the height and aspect ratio from the Texture2D. /// Height remains the same as before; width is recalculated. /// 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)); } } }