From a7203b765eae18a030378756ddfa5b044da5f12c Mon Sep 17 00:00:00 2001 From: Matias Lavik Date: Sat, 24 Sep 2022 20:19:33 +0200 Subject: [PATCH] Runtime Transfer Function editor (#115) Created a runtime 1D transfer function editor Created a runtime colour picker. Bugfix: You can now open ".ini" files directly. Bugfix: Loading/clearing transfer function will now automatically refresh the volume rendered object. Misc. improvements to runtime file dialog. Packaging script: Read path from argv or raw_input --- .../Editor/RAWDatasetImporterEditorWIndow.cs | 18 +- Assets/Editor/SelectionHelper.cs | 10 +- Assets/Editor/TransferFunctionEditorWindow.cs | 235 ++------------- .../VolumeRenderedObjectCustomInspector.cs | 2 +- .../Editor/VolumeRendererEditorFunctions.cs | 11 +- .../GUI/Components/ColourPickerPopup.cs | 136 +++++++++ .../GUI/Components/ColourPickerPopup.cs.meta | 11 + .../Scripts/GUI/Components/EditVolumeGUI.cs | 6 + .../Components/RuntimeFileBrowserComponent.cs | 39 +-- Assets/Scripts/GUI/Components/RuntimeGUI.cs | 2 +- .../RuntimeTransferFunctionEditor.cs | 181 ++++++++++++ .../RuntimeTransferFunctionEditor.cs.meta | 11 + Assets/Scripts/GUI/IMGUI.meta | 8 + Assets/Scripts/GUI/IMGUI/ColourPicker.cs | 43 +++ Assets/Scripts/GUI/IMGUI/ColourPicker.cs.meta | 11 + .../GUI/IMGUI/TransferFunctionEditor.cs | 268 ++++++++++++++++++ .../GUI/IMGUI/TransferFunctionEditor.cs.meta | 11 + .../VolumeObject/VolumeRenderedObject.cs | 6 + scripts/ExportUnityPackage.py | 8 +- 19 files changed, 775 insertions(+), 242 deletions(-) create mode 100644 Assets/Scripts/GUI/Components/ColourPickerPopup.cs create mode 100644 Assets/Scripts/GUI/Components/ColourPickerPopup.cs.meta create mode 100644 Assets/Scripts/GUI/Components/RuntimeTransferFunctionEditor.cs create mode 100644 Assets/Scripts/GUI/Components/RuntimeTransferFunctionEditor.cs.meta create mode 100644 Assets/Scripts/GUI/IMGUI.meta create mode 100644 Assets/Scripts/GUI/IMGUI/ColourPicker.cs create mode 100644 Assets/Scripts/GUI/IMGUI/ColourPicker.cs.meta create mode 100644 Assets/Scripts/GUI/IMGUI/TransferFunctionEditor.cs create mode 100644 Assets/Scripts/GUI/IMGUI/TransferFunctionEditor.cs.meta diff --git a/Assets/Editor/RAWDatasetImporterEditorWIndow.cs b/Assets/Editor/RAWDatasetImporterEditorWIndow.cs index 83b34f2..0d962d5 100644 --- a/Assets/Editor/RAWDatasetImporterEditorWIndow.cs +++ b/Assets/Editor/RAWDatasetImporterEditorWIndow.cs @@ -24,7 +24,7 @@ namespace UnityVolumeRendering fileToImport = filePath; if (Path.GetExtension(fileToImport) == ".ini") - fileToImport = fileToImport.Replace(".ini", ".raw"); + fileToImport = fileToImport.Substring(0, fileToImport.Length - 4); // Try parse ini file (if available) DatasetIniData initData = DatasetIniReader.ParseIniFile(fileToImport + ".ini"); @@ -43,18 +43,18 @@ namespace UnityVolumeRendering private void ImportDataset() { - RawDatasetImporter importer = new RawDatasetImporter(fileToImport, dimX, dimY, dimZ, dataFormat, endianness, bytesToSkip); + RawDatasetImporter importer = new RawDatasetImporter(fileToImport, dimX, dimY, dimZ, dataFormat, endianness, bytesToSkip); VolumeDataset dataset = importer.Import(); if (dataset != null) { - if (EditorPrefs.GetBool("DownscaleDatasetPrompt")) - { - if (EditorUtility.DisplayDialog("Optional DownScaling", - $"Do you want to downscale the dataset? The dataset's dimension is: {dataset.dimX} x {dataset.dimY} x {dataset.dimZ}", "Yes", "No")) - { - dataset.DownScaleData(); - } + if (EditorPrefs.GetBool("DownscaleDatasetPrompt")) + { + if (EditorUtility.DisplayDialog("Optional DownScaling", + $"Do you want to downscale the dataset? The dataset's dimension is: {dataset.dimX} x {dataset.dimY} x {dataset.dimZ}", "Yes", "No")) + { + dataset.DownScaleData(); + } } VolumeRenderedObject obj = VolumeObjectFactory.CreateObject(dataset); } diff --git a/Assets/Editor/SelectionHelper.cs b/Assets/Editor/SelectionHelper.cs index cb27fb6..d9268dc 100644 --- a/Assets/Editor/SelectionHelper.cs +++ b/Assets/Editor/SelectionHelper.cs @@ -5,7 +5,7 @@ namespace UnityVolumeRendering { public class SelectionHelper { - public static VolumeRenderedObject GetSelectedVolumeObject() + public static VolumeRenderedObject GetSelectedVolumeObject(bool autoSelectFirst = false) { foreach (GameObject obj in Selection.gameObjects) { @@ -13,6 +13,14 @@ namespace UnityVolumeRendering if (volrendobj != null) return volrendobj; } + + VolumeRenderedObject volRendObject = GameObject.FindObjectOfType(); + if (volRendObject != null) + { + Selection.objects = new Object[] { volRendObject.gameObject }; + return volRendObject; + } + return null; } } diff --git a/Assets/Editor/TransferFunctionEditorWindow.cs b/Assets/Editor/TransferFunctionEditorWindow.cs index 6502c92..4370729 100644 --- a/Assets/Editor/TransferFunctionEditorWindow.cs +++ b/Assets/Editor/TransferFunctionEditorWindow.cs @@ -7,18 +7,11 @@ namespace UnityVolumeRendering { private TransferFunction tf = null; - private int movingColPointIndex = -1; - private int movingAlphaPointIndex = -1; - - private int selectedColPointIndex = -1; - private VolumeRenderedObject volRendObject = null; - private Texture2D histTex = null; - private Material tfGUIMat = null; - private Material tfPaletteGUIMat = null; + private TransferFunctionEditor tfEditor = new TransferFunctionEditor(); - public static void ShowWindow() + public static void ShowWindow(VolumeRenderedObject volRendObj) { // Close all (if any) 2D TF editor windows TransferFunction2DEditorWindow[] tf2dWnds = Resources.FindObjectsOfTypeAll(); @@ -26,6 +19,8 @@ namespace UnityVolumeRendering tf2dWnd.Close(); TransferFunctionEditorWindow wnd = (TransferFunctionEditorWindow)EditorWindow.GetWindow(typeof(TransferFunctionEditorWindow)); + if (volRendObj) + wnd.volRendObject = volRendObj; wnd.Show(); wnd.SetInitialPosition(); } @@ -40,19 +35,7 @@ namespace UnityVolumeRendering private void OnEnable() { - tfGUIMat = Resources.Load("TransferFunctionGUIMat"); - tfPaletteGUIMat = Resources.Load("TransferFunctionPaletteGUIMat"); - - volRendObject = SelectionHelper.GetSelectedVolumeObject(); - if (volRendObject == null) - { - volRendObject = GameObject.FindObjectOfType(); - if (volRendObject != null) - Selection.objects = new Object[] { volRendObject.gameObject }; - } - - if(volRendObject != null) - volRendObject.SetTransferFunctionMode(TFRenderMode.TF1D); + tfEditor.Initialise(); } private void OnGUI() @@ -63,6 +46,7 @@ namespace UnityVolumeRendering if (volRendObject == null) return; + tf = volRendObject.transferFunction; Event currentEvent = new Event(Event.current); @@ -73,143 +57,14 @@ namespace UnityVolumeRendering float contentHeight = contentWidth * 0.5f; // Interaction area (slightly larger than the histogram rect) - Rect interactRect = new Rect(0.0f, 0.0f, contentWidth, contentHeight); - // Histogram rect (histogram view and alpha control points) - Rect histRect = new Rect(interactRect.x + 20.0f, interactRect.y + 20.0f, interactRect.width - 40.0f, interactRect.height - 40.0f); - // Colour palette rect (colour control points) - Rect paletteRect = new Rect(histRect.x, histRect.y + histRect.height + 20, histRect.width, 20.0f); + Rect outerRect = new Rect(0.0f, 0.0f, contentWidth, contentHeight); + Rect tfEditorRect = new Rect(outerRect.x + 20.0f, outerRect.y + 20.0f, outerRect.width - 40.0f, outerRect.height - 50.0f); - // TODO: Don't do this every frame - tf.GenerateTexture(); - - // Create histogram texture - if(histTex == null) - { - if(SystemInfo.supportsComputeShaders) - histTex = HistogramTextureGenerator.GenerateHistogramTextureOnGPU(volRendObject.dataset); - else - histTex = HistogramTextureGenerator.GenerateHistogramTexture(volRendObject.dataset); - } - - // Draw histogram - tfGUIMat.SetTexture("_TFTex", tf.GetTexture()); - tfGUIMat.SetTexture("_HistTex", histTex); - Graphics.DrawTexture(histRect, tf.GetTexture(), tfGUIMat); - - // Draw colour palette - Texture2D tfTexture = tf.GetTexture(); - tfPaletteGUIMat.SetTexture("_TFTex", tf.GetTexture()); - Graphics.DrawTexture(new Rect(paletteRect.x, paletteRect.y, paletteRect.width, paletteRect.height), tfTexture, tfPaletteGUIMat); - - // Release selected colour/alpha points if mouse leaves window - if (movingAlphaPointIndex != -1 && !interactRect.Contains(currentEvent.mousePosition)) - movingAlphaPointIndex = -1; - if (movingColPointIndex != -1 && !(currentEvent.mousePosition.x >= paletteRect.x && currentEvent.mousePosition.x <= paletteRect.x + paletteRect.width)) - movingColPointIndex = -1; - - // Mouse down => Move or remove selected colour control point - if (currentEvent.type == EventType.MouseDown && paletteRect.Contains(currentEvent.mousePosition)) - { - float mousePos = (currentEvent.mousePosition.x - paletteRect.x) / paletteRect.width; - int pointIndex = PickColourControlPoint(mousePos); - if (pointIndex != -1) - { - // Add control point - if(currentEvent.button == 0 && !currentEvent.control) - { - movingColPointIndex = selectedColPointIndex = pointIndex; - } - // Remove control point - else if(currentEvent.button == 1 && currentEvent.control) - { - tf.colourControlPoints.RemoveAt(pointIndex); - currentEvent.type = EventType.Ignore; - movingColPointIndex = selectedColPointIndex = -1; - } - } - } - else if (currentEvent.type == EventType.MouseUp) - movingColPointIndex = -1; - - // Mouse down => Move or remove selected alpha control point - if (currentEvent.type == EventType.MouseDown) - { - Vector2 mousePos = new Vector2((currentEvent.mousePosition.x - histRect.x) / histRect.width, 1.0f - (currentEvent.mousePosition.y - histRect.y) / histRect.height); - int pointIndex = PickAlphaControlPoint(mousePos); - if (pointIndex != -1) - { - // Add control point - if(currentEvent.button == 0 && !currentEvent.control) - { - movingAlphaPointIndex = pointIndex; - } - // Remove control point - else if(currentEvent.button == 1 && currentEvent.control) - { - tf.alphaControlPoints.RemoveAt(pointIndex); - currentEvent.type = EventType.Ignore; - selectedColPointIndex = -1; - } - } - } - - // Move selected alpha control point - if (movingAlphaPointIndex != -1) - { - TFAlphaControlPoint alphaPoint = tf.alphaControlPoints[movingAlphaPointIndex]; - alphaPoint.dataValue = Mathf.Clamp((currentEvent.mousePosition.x - histRect.x) / histRect.width, 0.0f, 1.0f); - alphaPoint.alphaValue = Mathf.Clamp(1.0f - (currentEvent.mousePosition.y - histRect.y) / histRect.height, 0.0f, 1.0f); - tf.alphaControlPoints[movingAlphaPointIndex] = alphaPoint; - } - - // Move selected colour control point - if (movingColPointIndex != -1) - { - TFColourControlPoint colPoint = tf.colourControlPoints[movingColPointIndex]; - colPoint.dataValue = Mathf.Clamp((currentEvent.mousePosition.x - paletteRect.x) / paletteRect.width, 0.0f, 1.0f); - tf.colourControlPoints[movingColPointIndex] = colPoint; - } - - // Draw colour control points - for (int iCol = 0; iCol < tf.colourControlPoints.Count; iCol++) - { - TFColourControlPoint colPoint = tf.colourControlPoints[iCol]; - Rect ctrlBox = new Rect(histRect.x + histRect.width * colPoint.dataValue, histRect.y + histRect.height + 20, 10, 20); - GUI.color = Color.red; - GUI.skin.box.fontSize = 6; - GUI.Box(ctrlBox, "*"); - } - - // Draw alpha control points - for (int iAlpha = 0; iAlpha < tf.alphaControlPoints.Count; iAlpha++) - { - const int pointSize = 10; - TFAlphaControlPoint alphaPoint = tf.alphaControlPoints[iAlpha]; - Rect ctrlBox = new Rect(histRect.x + histRect.width * alphaPoint.dataValue - pointSize / 2, histRect.y + (1.0f - alphaPoint.alphaValue) * histRect.height - pointSize / 2, pointSize, pointSize); - GUI.color = Color.red; - GUI.skin.box.fontSize = 6; - GUI.Box(ctrlBox, "*"); - GUI.color = oldColour; - } - - if (currentEvent.type == EventType.MouseUp) - { - movingColPointIndex = -1; - movingAlphaPointIndex = -1; - } - - // Add points - if (currentEvent.type == EventType.MouseDown && currentEvent.button == 1) - { - if (histRect.Contains(new Vector2(currentEvent.mousePosition.x, currentEvent.mousePosition.y))) - tf.alphaControlPoints.Add(new TFAlphaControlPoint(Mathf.Clamp((currentEvent.mousePosition.x - histRect.x) / histRect.width, 0.0f, 1.0f), Mathf.Clamp(1.0f - (currentEvent.mousePosition.y - histRect.y) / histRect.height, 0.0f, 1.0f))); - else - tf.colourControlPoints.Add(new TFColourControlPoint(Mathf.Clamp((currentEvent.mousePosition.x - histRect.x) / histRect.width, 0.0f, 1.0f), Random.ColorHSV())); - selectedColPointIndex = -1; - } + tfEditor.SetVolumeObject(volRendObject); + tfEditor.DrawOnGUI(tfEditorRect); // Save TF - if(GUI.Button(new Rect(histRect.x, histRect.y + histRect.height + 50.0f, 70.0f, 30.0f), "Save")) + if(GUI.Button(new Rect(tfEditorRect.x, tfEditorRect.y + tfEditorRect.height + 20.0f, 70.0f, 30.0f), "Save")) { string filepath = EditorUtility.SaveFilePanel("Save transfer function", "", "default.tf", "tf"); if(filepath != "") @@ -217,83 +72,45 @@ namespace UnityVolumeRendering } // Load TF - if(GUI.Button(new Rect(histRect.x + 75.0f, histRect.y + histRect.height + 50.0f, 70.0f, 30.0f), "Load")) + if(GUI.Button(new Rect(tfEditorRect.x + 75.0f, tfEditorRect.y + tfEditorRect.height + 20.0f, 70.0f, 30.0f), "Load")) { string filepath = EditorUtility.OpenFilePanel("Save transfer function", "", "tf"); if(filepath != "") { TransferFunction newTF = TransferFunctionDatabase.LoadTransferFunction(filepath); if(newTF != null) - volRendObject.transferFunction = tf = newTF; + { + tf = newTF; + volRendObject.SetTransferFunction(tf); + tfEditor.ClearSelection(); + } } } // Clear TF - if(GUI.Button(new Rect(histRect.x + 150.0f, histRect.y + histRect.height + 50.0f, 70.0f, 30.0f), "Clear")) + if(GUI.Button(new Rect(tfEditorRect.x + 150.0f, tfEditorRect.y + tfEditorRect.height + 20.0f, 70.0f, 30.0f), "Clear")) { - tf = volRendObject.transferFunction = new TransferFunction(); + tf = new TransferFunction(); tf.alphaControlPoints.Add(new TFAlphaControlPoint(0.2f, 0.0f)); tf.alphaControlPoints.Add(new TFAlphaControlPoint(0.8f, 1.0f)); tf.colourControlPoints.Add(new TFColourControlPoint(0.5f, new Color(0.469f, 0.354f, 0.223f, 1.0f))); - selectedColPointIndex = -1; + volRendObject.SetTransferFunction(tf); + tfEditor.ClearSelection(); } // Colour picker - if (selectedColPointIndex != -1) + Color? selectedColour = tfEditor.GetSelectedColour(); + if (selectedColour != null) { - TFColourControlPoint colPoint = tf.colourControlPoints[selectedColPointIndex]; - colPoint.colourValue = EditorGUI.ColorField(new Rect(histRect.x + 225, histRect.y + histRect.height + 50, 100.0f, 40.0f), colPoint.colourValue); - tf.colourControlPoints[selectedColPointIndex] = colPoint; + Color newColour = EditorGUI.ColorField(new Rect(tfEditorRect.x + 225, tfEditorRect.y + tfEditorRect.height + 30, 100.0f, 40.0f), selectedColour.Value); + tfEditor.SetSelectedColour(newColour); } GUI.skin.label.wordWrap = false; - GUI.Label(new Rect(histRect.x, histRect.y + histRect.height + 85.0f, 720.0f, 30.0f), "Left click to select and move a control point. Right click to add a control point, and ctrl + right click to delete."); + GUI.Label(new Rect(tfEditorRect.x, tfEditorRect.y + tfEditorRect.height + 55.0f, 720.0f, 50.0f), "Left click to select and move a control point.\nRight click to add a control point, and ctrl + right click to delete."); GUI.color = oldColour; } - /// - /// Pick the colour control point, nearest to the specified position. - /// - /// Threshold for maximum distance. Points further away than this won't get picked. - private int PickColourControlPoint(float position, float maxDistance = 0.03f) - { - int nearestPointIndex = -1; - float nearestDist = 1000.0f; - for (int i = 0; i < tf.colourControlPoints.Count; i++) - { - TFColourControlPoint ctrlPoint = tf.colourControlPoints[i]; - float dist = Mathf.Abs(ctrlPoint.dataValue - position); - if (dist < maxDistance && dist < nearestDist) - { - nearestPointIndex = i; - nearestDist = dist; - } - } - return nearestPointIndex; - } - - /// - /// Pick the alpha control point, nearest to the specified position. - /// - /// Threshold for maximum distance. Points further away than this won't get picked. - private int PickAlphaControlPoint(Vector2 position, float maxDistance = 0.05f) - { - int nearestPointIndex = -1; - float nearestDist = 1000.0f; - for (int i = 0; i < tf.alphaControlPoints.Count; i++) - { - TFAlphaControlPoint ctrlPoint = tf.alphaControlPoints[i]; - Vector2 ctrlPos = new Vector2(ctrlPoint.dataValue, ctrlPoint.alphaValue); - float dist = (ctrlPos - position).magnitude; - if (dist < maxDistance && dist < nearestDist) - { - nearestPointIndex = i; - nearestDist = dist; - } - } - return nearestPointIndex; - } - private void OnSelectionChange() { VolumeRenderedObject newVolRendObj = Selection.activeGameObject?.GetComponent(); diff --git a/Assets/Editor/VolumeRenderedObjectCustomInspector.cs b/Assets/Editor/VolumeRenderedObjectCustomInspector.cs index bcd27f7..4ab7629 100644 --- a/Assets/Editor/VolumeRenderedObjectCustomInspector.cs +++ b/Assets/Editor/VolumeRenderedObjectCustomInspector.cs @@ -39,7 +39,7 @@ namespace UnityVolumeRendering if (GUILayout.Button("Edit transfer function")) { if (tfMode == TFRenderMode.TF1D) - TransferFunctionEditorWindow.ShowWindow(); + TransferFunctionEditorWindow.ShowWindow(volrendObj); else TransferFunction2DEditorWindow.ShowWindow(); } diff --git a/Assets/Editor/VolumeRendererEditorFunctions.cs b/Assets/Editor/VolumeRendererEditorFunctions.cs index 5ea824a..f3629f0 100644 --- a/Assets/Editor/VolumeRendererEditorFunctions.cs +++ b/Assets/Editor/VolumeRendererEditorFunctions.cs @@ -243,7 +243,16 @@ namespace UnityVolumeRendering [MenuItem("Volume Rendering/1D Transfer Function")] public static void Show1DTFWindow() { - TransferFunctionEditorWindow.ShowWindow(); + VolumeRenderedObject volRendObj = SelectionHelper.GetSelectedVolumeObject(); + if (volRendObj != null) + { + volRendObj.SetTransferFunctionMode(TFRenderMode.TF1D); + TransferFunctionEditorWindow.ShowWindow(volRendObj); + } + else + { + EditorUtility.DisplayDialog("No imported dataset", "You need to import a dataset first", "Ok"); + } } [MenuItem("Volume Rendering/2D Transfer Function")] diff --git a/Assets/Scripts/GUI/Components/ColourPickerPopup.cs b/Assets/Scripts/GUI/Components/ColourPickerPopup.cs new file mode 100644 index 0000000..f93e95a --- /dev/null +++ b/Assets/Scripts/GUI/Components/ColourPickerPopup.cs @@ -0,0 +1,136 @@ +using System; +using System.IO; +using UnityEngine; + +namespace UnityVolumeRendering +{ + public class ColourPickerPopup : MonoBehaviour + { + public Action callback = null; + private Vector3 selectedHSV = new Vector3(0.0f, 1.0f, 1.0f); + private Rect windowRect = new Rect(150, 15, WINDOW_WIDTH, WINDOW_HEIGHT); + private Rect colourBoxRect = new Rect(10, 30, 250, 250); + private Rect valueSliderRect = new Rect(280, 30, 20, 250); + private int windowID; + private Texture2D texture; + private Color[] gradientColours = { Color.red, Color.green, Color.blue, Color.red }; + + private const int WINDOW_WIDTH = 400; + private const int WINDOW_HEIGHT = 400; + private const int TEXTURE_WIDTH = 128; + private const int TEXTURE_HEIGHT = 128; + + private Vector2 selectedPosition = Vector2.zero; + private bool movingPoint = false; + + public Color GetColour() + { + return Color.HSVToRGB(selectedHSV.x, selectedHSV.y, selectedHSV.z); + } + + public void SetColour(Color col) + { + Color.RGBToHSV(col, out selectedHSV.x, out selectedHSV.y, out selectedHSV.z); + } + + private void Awake() + { + // Fetch a unique ID for our window (see GUI.Window) + windowID = WindowGUID.GetUniqueWindowID(); + } + + private void OnGUI() + { + windowRect = GUI.Window(windowID, windowRect, UpdateWindow, "Colour picker"); + } + + private void UpdateWindow(int windowID) + { + GUI.DragWindow(new Rect(0, 0, 10000, 20)); + + GUI.DrawTexture(colourBoxRect, GetTexture()); + + Rect ctrlBox = new Rect(selectedPosition.x, selectedPosition.y , 10, 10); + GUI.skin.box.fontSize = 6; + GUI.Box(ctrlBox, "*"); + + if (GUI.Button(new Rect(WINDOW_WIDTH - 100, WINDOW_HEIGHT - 40, 90, 30), "Done")) + { + CloseBrowser(); + } + + selectedHSV.z = GUI.VerticalSlider(valueSliderRect, selectedHSV.z, 0.0f, 1.0f); + + Event currentEvent = Event.current; + Vector2 mousePos = currentEvent.mousePosition; + if (currentEvent.type == EventType.MouseDown && colourBoxRect.Contains(mousePos)) + movingPoint = true; + else if (currentEvent.type == EventType.MouseUp) + movingPoint = false; + + if (movingPoint) + { + Vector2 unitPos = new Vector2((mousePos.x - colourBoxRect.x) / colourBoxRect.width, (mousePos.y - colourBoxRect.y) / colourBoxRect.height); + unitPos = (unitPos - new Vector2(0.5f, 0.5f)) * new Vector2(2.0f, -2.0f); + if (unitPos.magnitude <= 1.0f) + { + selectedPosition = mousePos; + selectedHSV = GetHSVAtPoint(unitPos, selectedHSV.z); + } + } + } + + private Vector3 GetHSVAtPoint(Vector2 point, float value) + { + Vector2 a = new Vector2(0.0f, 1.0f); + Vector2 b = point; + float signedAngle = Vector2.SignedAngle(a, b); + float angle = (signedAngle < 0.0f ? signedAngle + 360.0f : signedAngle); + float hue = angle / 360.0f; + float saturation = point.magnitude; + return new Vector3(hue, saturation, value); + } + + private Texture2D GetTexture() + { + if (texture == null) + { + int textureDimension = TEXTURE_WIDTH * TEXTURE_HEIGHT; + Color[] colours = new Color[textureDimension]; + + for (int ix = 0; ix < TEXTURE_WIDTH; ix++) + { + for (int iy = 0; iy < TEXTURE_HEIGHT; iy++) + { + Vector2 unitPos = new Vector2((float)ix / TEXTURE_WIDTH, (float)iy / TEXTURE_HEIGHT); + unitPos = (unitPos - new Vector2(0.5f, 0.5f)) * 2.0f; + Color colour; + if (unitPos.magnitude <= 1.0f) + { + Vector3 hsv = GetHSVAtPoint(unitPos, 1.0f); + colour = Color.HSVToRGB(hsv.x, hsv.y, hsv.z); + } + else + { + colour = Color.clear; + } + colours[ix + iy * TEXTURE_WIDTH] = colour; + } + } + + texture = new Texture2D(TEXTURE_WIDTH, TEXTURE_HEIGHT, TextureFormat.RGBAFloat, false); + texture.wrapMode = TextureWrapMode.Clamp; + texture.SetPixels(colours); + texture.Apply(); + } + return texture; + } + + private void CloseBrowser() + { + callback?.Invoke(GetColour()); + + GameObject.Destroy(this.gameObject); + } + } +} diff --git a/Assets/Scripts/GUI/Components/ColourPickerPopup.cs.meta b/Assets/Scripts/GUI/Components/ColourPickerPopup.cs.meta new file mode 100644 index 0000000..d5584ff --- /dev/null +++ b/Assets/Scripts/GUI/Components/ColourPickerPopup.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fb9bffc42ab3c9afcbe13a62b234e554 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/GUI/Components/EditVolumeGUI.cs b/Assets/Scripts/GUI/Components/EditVolumeGUI.cs index b0bb8a1..e290c94 100644 --- a/Assets/Scripts/GUI/Components/EditVolumeGUI.cs +++ b/Assets/Scripts/GUI/Components/EditVolumeGUI.cs @@ -79,6 +79,12 @@ namespace UnityVolumeRendering rotation.z = GUILayout.HorizontalSlider(rotation.z, 0.0f, 360.0f); targetObject.transform.rotation = Quaternion.Euler(rotation); + // Edit transfer function + if(GUILayout.Button("Edit transfer function", GUILayout.Width(150.0f))) + { + RuntimeTransferFunctionEditor.ShowWindow(targetObject); + } + // Load transfer function if(GUILayout.Button("Load transfer function", GUILayout.Width(150.0f))) { diff --git a/Assets/Scripts/GUI/Components/RuntimeFileBrowserComponent.cs b/Assets/Scripts/GUI/Components/RuntimeFileBrowserComponent.cs index 0e52f74..35a0cf9 100644 --- a/Assets/Scripts/GUI/Components/RuntimeFileBrowserComponent.cs +++ b/Assets/Scripts/GUI/Components/RuntimeFileBrowserComponent.cs @@ -21,16 +21,17 @@ namespace UnityVolumeRendering public DialogMode dialogMode = DialogMode.OpenFile; public DialogCallback callback = null; - public string currentDirectory; - private string selectedFile; + public string currentDirectory = ""; + private string selectedFile = ""; + private string fileName = ""; private Vector2 scrollPos = Vector2.zero; private Vector2 dirScrollPos = Vector2.zero; private Rect windowRect = new Rect(100, 50, WINDOW_WIDTH, WINDOW_HEIGHT); - private const int LEFT_PANEL_WIDTH = 100; - private const int RIGHT_PANEL_WIDTH = 370; - private const int WINDOW_WIDTH = 500; + private const int LEFT_PANEL_WIDTH = 150; + private const int RIGHT_PANEL_WIDTH = 450; + private const int WINDOW_WIDTH = 600; private const int WINDOW_HEIGHT = 300; private int windowID; @@ -99,14 +100,6 @@ namespace UnityVolumeRendering GUILayout.BeginVertical(GUILayout.Width(LEFT_PANEL_WIDTH)); dirScrollPos = GUILayout.BeginScrollView(dirScrollPos); - foreach (DriveInfo driveInfo in DriveInfo.GetDrives()) - { - if (GUILayout.Button(driveInfo.Name)) - { - currentDirectory = driveInfo.Name; - scrollPos = Vector2.zero; - } - } if (GUILayout.Button("Documents")) { @@ -118,6 +111,16 @@ namespace UnityVolumeRendering currentDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Desktop); scrollPos = Vector2.zero; } + + foreach (DriveInfo driveInfo in DriveInfo.GetDrives()) + { + if (GUILayout.Button(driveInfo.Name)) + { + currentDirectory = driveInfo.Name; + scrollPos = Vector2.zero; + } + } + GUILayout.EndScrollView(); GUILayout.EndVertical(); @@ -149,6 +152,7 @@ namespace UnityVolumeRendering if (GUILayout.Button(fileInfo.Name)) { selectedFile = fileInfo.FullName; + fileName = Path.GetFileName(selectedFile); } } } @@ -163,17 +167,16 @@ namespace UnityVolumeRendering if(dialogMode == DialogMode.OpenFile || dialogMode == DialogMode.SaveFile) { - if (!string.IsNullOrEmpty(selectedFile)) + if (!string.IsNullOrEmpty(selectedFile) || dialogMode == DialogMode.SaveFile) { - FileInfo fileInfo = new FileInfo(selectedFile); - string fileName = Path.GetFileName(selectedFile); // Show filename textbox fileName = GUILayout.TextField(fileName, GUILayout.Width(RIGHT_PANEL_WIDTH)); - selectedFile = Path.Combine(fileInfo.Directory.FullName, fileName); + if (fileName.Length > 0) + selectedFile = Path.Combine(currentDirectory, fileName); GUILayout.FlexibleSpace(); // Show button string buttonText = dialogMode == DialogMode.OpenFile ? "Open" : "Save"; - if (File.Exists(selectedFile) && GUILayout.Button(buttonText)) + if (!string.IsNullOrEmpty(fileName) && (File.Exists(selectedFile) || dialogMode == DialogMode.SaveFile) && GUILayout.Button(buttonText)) { CloseBrowser(false, selectedFile); } diff --git a/Assets/Scripts/GUI/Components/RuntimeGUI.cs b/Assets/Scripts/GUI/Components/RuntimeGUI.cs index 12d548c..492a622 100644 --- a/Assets/Scripts/GUI/Components/RuntimeGUI.cs +++ b/Assets/Scripts/GUI/Components/RuntimeGUI.cs @@ -75,7 +75,7 @@ namespace UnityVolumeRendering // Did the user try to import an .ini-file? Open the corresponding .raw file instead string filePath = result.path; if (System.IO.Path.GetExtension(filePath) == ".ini") - filePath = filePath.Replace(".ini", ".raw"); + filePath = filePath.Substring(0, filePath.Length - 4); // Parse .ini file DatasetIniData initData = DatasetIniReader.ParseIniFile(filePath + ".ini"); diff --git a/Assets/Scripts/GUI/Components/RuntimeTransferFunctionEditor.cs b/Assets/Scripts/GUI/Components/RuntimeTransferFunctionEditor.cs new file mode 100644 index 0000000..26c4573 --- /dev/null +++ b/Assets/Scripts/GUI/Components/RuntimeTransferFunctionEditor.cs @@ -0,0 +1,181 @@ +using UnityEngine; + +namespace UnityVolumeRendering +{ + public class RuntimeTransferFunctionEditor : MonoBehaviour + { + private static RuntimeTransferFunctionEditor instance = null; + private TransferFunction tf = null; + private VolumeRenderedObject volRendObject = null; + + private int windowID; + private Rect windowRect = new Rect(150, 15, WINDOW_WIDTH, WINDOW_HEIGHT); + + private TransferFunctionEditor tfEditor = new TransferFunctionEditor(); + + private const int WINDOW_WIDTH = 620; + private const int WINDOW_HEIGHT = 400; + + public static void ShowWindow(VolumeRenderedObject volRendObj) + { + if(instance != null) + GameObject.Destroy(instance); + + GameObject obj = new GameObject("RuntimeTransferFunctionEditor"); + instance = obj.AddComponent(); + instance.volRendObject = volRendObj; + } + + private void Awake() + { + // Fetch a unique ID for our window (see GUI.Window) + windowID = WindowGUID.GetUniqueWindowID(); + } + + private void OnEnable() + { + tfEditor.Initialise(); + } + + private void Update() + { + // Close window if object has been destroyed + if (!volRendObject) + CloseWindow(); + } + + private void OnGUI() + { + windowRect = GUI.Window(windowID, windowRect, UpdateWindow, "Transfer function"); + } + + private void UpdateWindow(int windowID) + { + GUI.DragWindow(new Rect(0, 0, 10000, 20)); + + GUI.skin.button.alignment = TextAnchor.MiddleCenter; + + Color oldColour = GUI.color; // Used for setting GUI.color when drawing UI elements + + if (volRendObject == null) + return; + + tf = volRendObject.transferFunction; + + float contentWidth = Mathf.Min(WINDOW_WIDTH, (WINDOW_HEIGHT - 100.0f) * 2.0f); + float contentHeight = contentWidth * 0.5f; + + Rect outerRect = new Rect(0.0f, 0.0f, contentWidth, contentHeight); + Rect tfEditorRect = new Rect(outerRect.x + 20.0f, outerRect.y + 20.0f, outerRect.width - 40.0f, outerRect.height - 50.0f); + + tfEditor.SetVolumeObject(volRendObject); + tfEditor.DrawOnGUI(tfEditorRect); + + // Save TF + if(GUI.Button(new Rect(tfEditorRect.x, tfEditorRect.y + tfEditorRect.height + 20.0f, 70.0f, 30.0f), "Save")) + { + RuntimeFileBrowser.ShowSaveFileDialog((RuntimeFileBrowser.DialogResult result) => + { + if(!result.cancelled) + { + TransferFunctionDatabase.SaveTransferFunction(tf, result.path); + } + }); + } + + // Load TF + if(GUI.Button(new Rect(tfEditorRect.x + 75.0f, tfEditorRect.y + tfEditorRect.height + 20.0f, 70.0f, 30.0f), "Load")) + { + RuntimeFileBrowser.ShowOpenFileDialog((RuntimeFileBrowser.DialogResult result) => + { + if(!result.cancelled) + { + TransferFunction newTF = TransferFunctionDatabase.LoadTransferFunction(result.path); + if(newTF != null) + { + tf = newTF; + volRendObject.SetTransferFunction(tf); + tfEditor.ClearSelection(); + } + } + }); + } + // Clear TF + if(GUI.Button(new Rect(tfEditorRect.x + 150.0f, tfEditorRect.y + tfEditorRect.height + 20.0f, 70.0f, 30.0f), "Clear")) + { + tf = new TransferFunction(); + tf.alphaControlPoints.Add(new TFAlphaControlPoint(0.2f, 0.0f)); + tf.alphaControlPoints.Add(new TFAlphaControlPoint(0.8f, 1.0f)); + tf.colourControlPoints.Add(new TFColourControlPoint(0.5f, new Color(0.469f, 0.354f, 0.223f, 1.0f))); + volRendObject.SetTransferFunction(tf); + tfEditor.ClearSelection(); + } + + // Colour picker + Color? selectedColour = tfEditor.GetSelectedColour(); + if (selectedColour != null) + { + Color newColour = GUIUtils.ColourField(new Rect(tfEditorRect.x + 250, tfEditorRect.y + tfEditorRect.height + 20, 80.0f, 30.0f), selectedColour.Value); + tfEditor.SetSelectedColour(newColour); + } + + GUI.skin.label.wordWrap = false; + GUI.Label(new Rect(tfEditorRect.x, tfEditorRect.y + tfEditorRect.height + 55.0f, 720.0f, 50.0f), "Left click to select and move a control point.\nRight click to add a control point, and ctrl + right click to delete."); + + GUI.color = oldColour; + + if (GUI.Button(new Rect(WINDOW_WIDTH - 100, WINDOW_HEIGHT - 40, 90, 30), "Close")) + { + CloseWindow(); + } + } + + /// + /// Pick the colour control point, nearest to the specified position. + /// + /// Threshold for maximum distance. Points further away than this won't get picked. + private int PickColourControlPoint(float position, float maxDistance = 0.03f) + { + int nearestPointIndex = -1; + float nearestDist = 1000.0f; + for (int i = 0; i < tf.colourControlPoints.Count; i++) + { + TFColourControlPoint ctrlPoint = tf.colourControlPoints[i]; + float dist = Mathf.Abs(ctrlPoint.dataValue - position); + if (dist < maxDistance && dist < nearestDist) + { + nearestPointIndex = i; + nearestDist = dist; + } + } + return nearestPointIndex; + } + + /// + /// Pick the alpha control point, nearest to the specified position. + /// + /// Threshold for maximum distance. Points further away than this won't get picked. + private int PickAlphaControlPoint(Vector2 position, float maxDistance = 0.05f) + { + int nearestPointIndex = -1; + float nearestDist = 1000.0f; + for (int i = 0; i < tf.alphaControlPoints.Count; i++) + { + TFAlphaControlPoint ctrlPoint = tf.alphaControlPoints[i]; + Vector2 ctrlPos = new Vector2(ctrlPoint.dataValue, ctrlPoint.alphaValue); + float dist = (ctrlPos - position).magnitude; + if (dist < maxDistance && dist < nearestDist) + { + nearestPointIndex = i; + nearestDist = dist; + } + } + return nearestPointIndex; + } + + private void CloseWindow() + { + GameObject.Destroy(this.gameObject); + } + } +} diff --git a/Assets/Scripts/GUI/Components/RuntimeTransferFunctionEditor.cs.meta b/Assets/Scripts/GUI/Components/RuntimeTransferFunctionEditor.cs.meta new file mode 100644 index 0000000..52cfa34 --- /dev/null +++ b/Assets/Scripts/GUI/Components/RuntimeTransferFunctionEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 05f581cf1454d0be6b36abae59693162 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/GUI/IMGUI.meta b/Assets/Scripts/GUI/IMGUI.meta new file mode 100644 index 0000000..79a1b2d --- /dev/null +++ b/Assets/Scripts/GUI/IMGUI.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8c9a71a3f851b78acaf5caabe4350986 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/GUI/IMGUI/ColourPicker.cs b/Assets/Scripts/GUI/IMGUI/ColourPicker.cs new file mode 100644 index 0000000..366ca3d --- /dev/null +++ b/Assets/Scripts/GUI/IMGUI/ColourPicker.cs @@ -0,0 +1,43 @@ +using UnityEngine; + +namespace UnityVolumeRendering +{ + public partial class GUIUtils + { + private static ColourPickerPopup colourPicker = null; // TODO: Not static? + + private static Texture2D previewTexture = null; + private static GUIStyle previewStyle = new GUIStyle(); + + public static Color ColourField(Rect rect, Color colour) + { + if (previewTexture == null) + { + previewTexture = new Texture2D(2, 2, TextureFormat.RGBAFloat, false); + } + + if (colourPicker) + colour = colourPicker.GetColour(); + + Color[] previewCols = { colour, colour, colour, colour }; + previewTexture.SetPixels(previewCols); + previewTexture.Apply(); + previewStyle.normal.background = previewTexture; + previewStyle.alignment = TextAnchor.MiddleCenter; + if (GUI.Button(rect, "Colour", previewStyle)) + { + if (colourPicker == null) + { + GameObject obj = new GameObject(); + colourPicker = obj.AddComponent(); + } + colourPicker.SetColour(colour); + } + + if (colourPicker) + return colourPicker.GetColour(); + else + return colour; + } + } +} diff --git a/Assets/Scripts/GUI/IMGUI/ColourPicker.cs.meta b/Assets/Scripts/GUI/IMGUI/ColourPicker.cs.meta new file mode 100644 index 0000000..abdc103 --- /dev/null +++ b/Assets/Scripts/GUI/IMGUI/ColourPicker.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d7eee4fe67949ef989034674911148da +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/GUI/IMGUI/TransferFunctionEditor.cs b/Assets/Scripts/GUI/IMGUI/TransferFunctionEditor.cs new file mode 100644 index 0000000..07dbcf4 --- /dev/null +++ b/Assets/Scripts/GUI/IMGUI/TransferFunctionEditor.cs @@ -0,0 +1,268 @@ +using UnityEngine; + +namespace UnityVolumeRendering +{ + public class TransferFunctionEditor + { + private int movingColPointIndex = -1; + private int movingAlphaPointIndex = -1; + private int selectedColPointIndex = -1; + + private VolumeRenderedObject volRendObject = null; + private Texture2D histTex = null; + + private Material tfGUIMat = null; + private Material tfPaletteGUIMat = null; + + private bool rightMouseBtnDown = false; + + public void Initialise() + { + tfGUIMat = Resources.Load("TransferFunctionGUIMat"); + tfPaletteGUIMat = Resources.Load("TransferFunctionPaletteGUIMat"); + } + + public void SetVolumeObject(VolumeRenderedObject volRendObject) + { + this.volRendObject = volRendObject; + } + + public void DrawOnGUI(Rect rect) + { + GUI.skin.button.alignment = TextAnchor.MiddleCenter; + + if (volRendObject == null) + return; + + TransferFunction tf = volRendObject.transferFunction; + + Event currentEvent = Event.current; + + Color oldColour = GUI.color; // Used for setting GUI.color when drawing UI elements + + float contentWidth = rect.width; + float contentHeight = rect.height; + + // Histogram rect (histogram view and alpha control points) + Rect histRect = new Rect(rect.x, rect.y, rect.width, rect.height - 40); + // Mouse interaction area + Rect histMouseRect = new Rect(histRect.x - 20.0f, histRect.y - 20.0f, histRect.width + 40.0f, histRect.height + 40.0f); + // Colour palette rect (colour control points) + Rect paletteRect = new Rect(histRect.x, histRect.y + histRect.height + 20, histRect.width, 20.0f); + + // TODO: Don't do this every frame + tf.GenerateTexture(); + + // Create histogram texture + if(histTex == null) + { + if(SystemInfo.supportsComputeShaders) + histTex = HistogramTextureGenerator.GenerateHistogramTextureOnGPU(volRendObject.dataset); + else + histTex = HistogramTextureGenerator.GenerateHistogramTexture(volRendObject.dataset); + } + + // Draw histogram + tfGUIMat.SetTexture("_TFTex", tf.GetTexture()); + tfGUIMat.SetTexture("_HistTex", histTex); + Graphics.DrawTexture(histRect, tf.GetTexture(), tfGUIMat); + + // Draw colour palette + Texture2D tfTexture = tf.GetTexture(); + tfPaletteGUIMat.SetTexture("_TFTex", tf.GetTexture()); + Graphics.DrawTexture(new Rect(paletteRect.x, paletteRect.y, paletteRect.width, paletteRect.height), tfTexture, tfPaletteGUIMat); + + // Release selected colour/alpha points if mouse leaves window + if (movingAlphaPointIndex != -1 && !histMouseRect.Contains(currentEvent.mousePosition)) + movingAlphaPointIndex = -1; + if (movingColPointIndex != -1 && !(currentEvent.mousePosition.x >= paletteRect.x && currentEvent.mousePosition.x <= paletteRect.x + paletteRect.width)) + movingColPointIndex = -1; + + // Mouse down => Move or remove selected colour control point + if (currentEvent.type == EventType.MouseDown && paletteRect.Contains(currentEvent.mousePosition)) + { + float mousePos = (currentEvent.mousePosition.x - paletteRect.x) / paletteRect.width; + int pointIndex = PickColourControlPoint(mousePos); + if (pointIndex != -1) + { + // Add control point + if(currentEvent.button == 0 && !currentEvent.control) + { + movingColPointIndex = selectedColPointIndex = pointIndex; + } + // Remove control point + else if(currentEvent.button == 1 && currentEvent.control) + { + tf.colourControlPoints.RemoveAt(pointIndex); + currentEvent.type = EventType.Ignore; + movingColPointIndex = selectedColPointIndex = -1; + } + } + } + else if (currentEvent.type == EventType.MouseUp) + movingColPointIndex = -1; + + // Mouse down => Move or remove selected alpha control point + if (currentEvent.type == EventType.MouseDown) + { + Vector2 mousePos = new Vector2((currentEvent.mousePosition.x - histRect.x) / histRect.width, 1.0f - (currentEvent.mousePosition.y - histRect.y) / histRect.height); + int pointIndex = PickAlphaControlPoint(mousePos); + if (pointIndex != -1) + { + // Add control point + if(currentEvent.button == 0 && !currentEvent.control) + { + movingAlphaPointIndex = pointIndex; + } + // Remove control point + else if(currentEvent.button == 1 && currentEvent.control) + { + tf.alphaControlPoints.RemoveAt(pointIndex); + currentEvent.type = EventType.Ignore; + selectedColPointIndex = -1; + } + } + } + + // Move selected alpha control point + if (movingAlphaPointIndex != -1) + { + TFAlphaControlPoint alphaPoint = tf.alphaControlPoints[movingAlphaPointIndex]; + alphaPoint.dataValue = Mathf.Clamp((currentEvent.mousePosition.x - histRect.x) / histRect.width, 0.0f, 1.0f); + alphaPoint.alphaValue = Mathf.Clamp(1.0f - (currentEvent.mousePosition.y - histRect.y) / histRect.height, 0.0f, 1.0f); + tf.alphaControlPoints[movingAlphaPointIndex] = alphaPoint; + } + + // Move selected colour control point + if (movingColPointIndex != -1) + { + TFColourControlPoint colPoint = tf.colourControlPoints[movingColPointIndex]; + colPoint.dataValue = Mathf.Clamp((currentEvent.mousePosition.x - paletteRect.x) / paletteRect.width, 0.0f, 1.0f); + tf.colourControlPoints[movingColPointIndex] = colPoint; + } + + // Draw colour control points + for (int iCol = 0; iCol < tf.colourControlPoints.Count; iCol++) + { + TFColourControlPoint colPoint = tf.colourControlPoints[iCol]; + Rect ctrlBox = new Rect(histRect.x + histRect.width * colPoint.dataValue, histRect.y + histRect.height + 20, 10, 20); + GUI.color = Color.red; + GUI.skin.box.fontSize = 6; + GUI.Box(ctrlBox, "*"); + } + + // Draw alpha control points + for (int iAlpha = 0; iAlpha < tf.alphaControlPoints.Count; iAlpha++) + { + const int pointSize = 10; + TFAlphaControlPoint alphaPoint = tf.alphaControlPoints[iAlpha]; + Rect ctrlBox = new Rect(histRect.x + histRect.width * alphaPoint.dataValue - pointSize / 2, histRect.y + (1.0f - alphaPoint.alphaValue) * histRect.height - pointSize / 2, pointSize, pointSize); + GUI.color = Color.red; + GUI.skin.box.fontSize = 6; + GUI.Box(ctrlBox, "*"); + GUI.color = oldColour; + } + + if (currentEvent.type == EventType.MouseUp) + { + movingColPointIndex = -1; + movingAlphaPointIndex = -1; + } + + // Add points + if (currentEvent.type == EventType.MouseDown && currentEvent.button == 1) + { + rightMouseBtnDown = true; + } + else if (currentEvent.type == EventType.MouseUp && currentEvent.button == 1 && rightMouseBtnDown) + { + if (histRect.Contains(new Vector2(currentEvent.mousePosition.x, currentEvent.mousePosition.y))) + { + tf.alphaControlPoints.Add(new TFAlphaControlPoint(Mathf.Clamp((currentEvent.mousePosition.x - histRect.x) / histRect.width, 0.0f, 1.0f), Mathf.Clamp(1.0f - (currentEvent.mousePosition.y - histRect.y) / histRect.height, 0.0f, 1.0f))); + } + else + { + float hue = Random.Range(0.0f, 1.0f); + Color newColour = Color.HSVToRGB(hue, 1.0f, 1.0f); + tf.colourControlPoints.Add(new TFColourControlPoint(Mathf.Clamp((currentEvent.mousePosition.x - histRect.x) / histRect.width, 0.0f, 1.0f), newColour)); + } + selectedColPointIndex = -1; + + rightMouseBtnDown = false; + currentEvent.Use(); + } + + GUI.color = oldColour; + } + + public void ClearSelection() + { + movingColPointIndex = -1; + movingAlphaPointIndex = -1; + selectedColPointIndex = -1; + } + + public Color? GetSelectedColour() + { + if (selectedColPointIndex != -1) + return volRendObject.transferFunction.colourControlPoints[selectedColPointIndex].colourValue; + else + return null; + } + + public void SetSelectedColour(Color colour) + { + if (selectedColPointIndex != -1) + { + TFColourControlPoint colPoint = volRendObject.transferFunction.colourControlPoints[selectedColPointIndex]; + colPoint.colourValue = colour; + volRendObject.transferFunction.colourControlPoints[selectedColPointIndex] = colPoint; + } + } + + /// + /// Pick the colour control point, nearest to the specified position. + /// + /// Threshold for maximum distance. Points further away than this won't get picked. + private int PickColourControlPoint(float position, float maxDistance = 0.03f) + { + TransferFunction tf = volRendObject.transferFunction; + int nearestPointIndex = -1; + float nearestDist = 1000.0f; + for (int i = 0; i < tf.colourControlPoints.Count; i++) + { + TFColourControlPoint ctrlPoint = tf.colourControlPoints[i]; + float dist = Mathf.Abs(ctrlPoint.dataValue - position); + if (dist < maxDistance && dist < nearestDist) + { + nearestPointIndex = i; + nearestDist = dist; + } + } + return nearestPointIndex; + } + + /// + /// Pick the alpha control point, nearest to the specified position. + /// + /// Threshold for maximum distance. Points further away than this won't get picked. + private int PickAlphaControlPoint(Vector2 position, float maxDistance = 0.05f) + { + TransferFunction tf = volRendObject.transferFunction; + int nearestPointIndex = -1; + float nearestDist = 1000.0f; + for (int i = 0; i < tf.alphaControlPoints.Count; i++) + { + TFAlphaControlPoint ctrlPoint = tf.alphaControlPoints[i]; + Vector2 ctrlPos = new Vector2(ctrlPoint.dataValue, ctrlPoint.alphaValue); + float dist = (ctrlPos - position).magnitude; + if (dist < maxDistance && dist < nearestDist) + { + nearestPointIndex = i; + nearestDist = dist; + } + } + return nearestPointIndex; + } + } +} diff --git a/Assets/Scripts/GUI/IMGUI/TransferFunctionEditor.cs.meta b/Assets/Scripts/GUI/IMGUI/TransferFunctionEditor.cs.meta new file mode 100644 index 0000000..fa5eadc --- /dev/null +++ b/Assets/Scripts/GUI/IMGUI/TransferFunctionEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9686cd473128d99a0aa69fa100f03e80 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/VolumeObject/VolumeRenderedObject.cs b/Assets/Scripts/VolumeObject/VolumeRenderedObject.cs index 1866291..8fdc726 100644 --- a/Assets/Scripts/VolumeObject/VolumeRenderedObject.cs +++ b/Assets/Scripts/VolumeObject/VolumeRenderedObject.cs @@ -153,6 +153,12 @@ namespace UnityVolumeRendering } } + public void SetTransferFunction(TransferFunction tf) + { + this.transferFunction = tf; + UpdateMaterialProperties(); + } + private void UpdateMaterialProperties() { bool useGradientTexture = tfRenderMode == TFRenderMode.TF2D || renderMode == RenderMode.IsosurfaceRendering || lightingEnabled; diff --git a/scripts/ExportUnityPackage.py b/scripts/ExportUnityPackage.py index c38fea7..08946a8 100644 --- a/scripts/ExportUnityPackage.py +++ b/scripts/ExportUnityPackage.py @@ -1,4 +1,4 @@ -import os, shutil, errno +import os, shutil, errno, sys def copy_filedir(src, dst): try: @@ -8,7 +8,11 @@ def copy_filedir(src, dst): shutil.copy(src, dst) else: raise -unity_path = "D:/Program Files/UnityEditors/2019.4.35f1/Editor/Unity.exe" # TODO +if len(sys.argv) > 1: + unity_path = str(sys.argv[1]) +else: + unity_path = raw_input("Enter full filepath of Unity executable:") + uvr_project_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir) export_project_path = "tmp-package-export"