diff --git a/Documentation/Images/InteractionMode_Toolbar.PNG b/Documentation/Images/InteractionMode_Toolbar.PNG new file mode 100644 index 0000000..1fc1df0 Binary files /dev/null and b/Documentation/Images/InteractionMode_Toolbar.PNG differ diff --git a/Documentation/Images/InteractionMode_Toolbar.PNG.meta b/Documentation/Images/InteractionMode_Toolbar.PNG.meta new file mode 100644 index 0000000..0c1c633 --- /dev/null +++ b/Documentation/Images/InteractionMode_Toolbar.PNG.meta @@ -0,0 +1,114 @@ +fileFormatVersion: 2 +guid: b741c0c457ef8784693661fa5b7e9394 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 0 + wrapV: 0 + wrapW: 0 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Documentation/Images/Interaction_Inspector.PNG b/Documentation/Images/Interaction_Inspector.PNG new file mode 100644 index 0000000..b7b25e6 Binary files /dev/null and b/Documentation/Images/Interaction_Inspector.PNG differ diff --git a/Documentation/Images/Interaction_Inspector.PNG.meta b/Documentation/Images/Interaction_Inspector.PNG.meta new file mode 100644 index 0000000..a733453 --- /dev/null +++ b/Documentation/Images/Interaction_Inspector.PNG.meta @@ -0,0 +1,114 @@ +fileFormatVersion: 2 +guid: 103aae160888de4448661ad550580516 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 0 + wrapV: 0 + wrapW: 0 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Scripts/SceneInteractionTool.cs b/Editor/Scripts/SceneInteractionTool.cs new file mode 100644 index 0000000..7a4bd32 --- /dev/null +++ b/Editor/Scripts/SceneInteractionTool.cs @@ -0,0 +1,174 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using OC.Interactions; +using UnityEditor; +using UnityEditor.EditorTools; +using UnityEditor.ShortcutManagement; +using UnityEngine; +using UnityEngine.EventSystems; + +namespace OC +{ + [EditorTool("Scene Interaction")] + [Icon(ICON)] + public class SceneInteractionTool : EditorTool + { + private readonly LayerMask _layerMask = 1 << (int)DefaultLayers.Interactions; + private readonly RaycastHit[] _hits = new RaycastHit[10]; + private readonly List _hitInteractions = new (); + private int _hitsCount; + private Interaction _activeInteraction; + private int[] _outlineRenderers = Array.Empty(); + private const string ICON = "d_EventTrigger Icon"; + + [Shortcut("Scene Interaction Tool", typeof(SceneView), KeyCode.I)] + private static void SceneViewInteractionShortcut() + { + if (ToolManager.activeToolType == typeof(SceneInteractionTool)) + { + ToolManager.RestorePreviousTool(); + } + else + { + ToolManager.SetActiveTool(typeof(SceneInteractionTool)); + } + } + + public override void OnActivated() + { + SceneView.lastActiveSceneView.ShowNotification(new GUIContent("Scene Interaction Activated"), .1f); + } + + public override void OnWillBeDeactivated() + { + SceneView.lastActiveSceneView.ShowNotification(new GUIContent("Scene Interaction Deactivated"), .1f); + ResetHit(); + } + + public override void OnToolGUI(EditorWindow window) + { + if (!Application.isPlaying) return; + if (window is not SceneView) return; + + var currentEvent = Event.current; + + if (currentEvent.type == EventType.Repaint) + { + Handles.DrawOutline(_outlineRenderers, Color.white, 0.05f); + } + + if (currentEvent.type is not (EventType.MouseMove or EventType.MouseUp or EventType.MouseDown)) return; + if (currentEvent.button != 0) return; + + RayCast(currentEvent); + + if (currentEvent.type == EventType.MouseDown && currentEvent.button == 0) + { + if (_activeInteraction != null) + { + PointerDownEvent(_activeInteraction); + } + else + { + ResetHit(); + } + currentEvent.Use(); + } + + if (currentEvent.type == EventType.MouseUp && currentEvent.button == 0) + { + if (_activeInteraction != null) + { + PointerClickEvent(_activeInteraction); + PointerUpEvent(_activeInteraction); + } + currentEvent.Use(); + } + } + + private void RayCast(Event currentEvent) + { + Array.Clear(_hits, 0, _hitsCount); + _hitInteractions.Clear(); + + var ray = HandleUtility.GUIPointToWorldRay(currentEvent.mousePosition); + _hitsCount = Physics.RaycastNonAlloc(ray, _hits, 500, _layerMask); + + if (_hitsCount == 0) + { + ResetHit(); + return; + } + + var hits = _hits.OrderBy(hit => hit.distance); + foreach (var raycast in hits) + { + if (raycast.distance < Utils.TOLERANCE) continue; + if (raycast.collider.gameObject.TryGetComponent(out var interaction)) + { + _hitInteractions.Add(interaction); + } + } + + if (_hitInteractions.Count < 1) + { + ResetHit(); + return; + } + + if (_activeInteraction == _hitInteractions.First()) + { + return; + } + + if (_activeInteraction != null) PointerExitEvent(_activeInteraction); + _activeInteraction = _hitInteractions.First(); + PointerEnterEvent(_activeInteraction); + + _outlineRenderers = new int[_activeInteraction.Renderers.Count]; + for (var i = 0; i < _activeInteraction.Renderers.Count; i++) + { + _outlineRenderers[i] = _activeInteraction.Renderers[i].GetInstanceID(); + } + } + + private void ResetHit() + { + if (_activeInteraction != null) + { + PointerUpEvent(_activeInteraction); + PointerExitEvent(_activeInteraction); + } + + _hitInteractions.Clear(); + Array.Clear(_outlineRenderers, 0, _outlineRenderers.Length); + _activeInteraction = null; + } + + private void PointerEnterEvent(Interaction interaction) + { + ExecuteEvents.Execute(interaction.gameObject, new PointerEventData(EventSystem.current), ExecuteEvents.pointerEnterHandler); + } + + private void PointerExitEvent(Interaction interaction) + { + ExecuteEvents.Execute(interaction.gameObject, new PointerEventData(EventSystem.current), ExecuteEvents.pointerExitHandler); + } + + private void PointerClickEvent(Interaction interaction) + { + ExecuteEvents.Execute(interaction.gameObject, new PointerEventData(EventSystem.current), ExecuteEvents.pointerClickHandler); + } + + private void PointerDownEvent(Interaction interaction) + { + ExecuteEvents.Execute(interaction.gameObject, new PointerEventData(EventSystem.current), ExecuteEvents.pointerDownHandler); + } + + private void PointerUpEvent(Interaction interaction) + { + ExecuteEvents.Execute(interaction.gameObject, new PointerEventData(EventSystem.current), ExecuteEvents.pointerUpHandler); + } + } +} diff --git a/Editor/Scripts/SceneInteractionTool.cs.meta b/Editor/Scripts/SceneInteractionTool.cs.meta new file mode 100644 index 0000000..f827fb0 --- /dev/null +++ b/Editor/Scripts/SceneInteractionTool.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: aa04ec22271276744adb39480536ee5d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/README.md b/README.md index 53b1d90..4756a9a 100644 --- a/README.md +++ b/README.md @@ -909,6 +909,33 @@ where the tag content is stored in a file and managed through the Tag Reader's _ The [`Payload Data`](#payload-data) and [`Payload Data Reader`](#payload-data-reader) deal with the [`Payload`](#payload)'s internal properties, which are made accessible to _TwinCAT_ through the [`Sensor Analog`](#sensor-analog) component. +## Scene View Interactions + +Interactions with components in the Scene View are managed through the [`Interaction`](#interaction) Script. +To activate interaction mode, simply press the `I` shortcut key. Alternatively, use a toolbar toggle button. +When Interaction mode is enabled, the standard Editor Handles are disabled. + +![InteractionMode_Toolbar.PNG](Documentation%2FImages%2FInteractionMode_Toolbar.PNG) + +### Interaction + +The Interaction Script handles mouse events such as _Hover_, _Click_, _PointerDown_, and _PointerUp_ on this GameObject. +A `Collider` must be defined for these interactions to work. +The script assigns the GameObject's layer to _Interaction_. (See ['Setting Up a New Project'](#setting-up-a-new-project)) + +![Interaction_Inspector.PNG](Documentation%2FImages%2FInteraction_Inspector.PNG) + +**Properties**: + +- Config: + - Mode: Specify which events will be handled + - Target: The target GameObject is used to bind the UI to a specific component + - Debug: Display event debug logs + +- Functions: + - Events: Unity events for handling mouse interactions + - Bound Collider Size: Recalculate the size of the Box Collider based on the combined mesh dimensions of the child objects. + ## Connection to _TwinCAT_ diff --git a/Runtime/Resources/UXML/panel-switch-rotary.uxml b/Runtime/Resources/UXML/panel-switch-rotary.uxml index 5a06536..459db18 100644 --- a/Runtime/Resources/UXML/panel-switch-rotary.uxml +++ b/Runtime/Resources/UXML/panel-switch-rotary.uxml @@ -3,7 +3,7 @@