2019-02-17 15:50:58 +03:00
|
|
|
|
using UnityEngine;
|
|
|
|
|
using UnityEditor;
|
2022-09-16 20:21:12 +03:00
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Linq;
|
2019-02-17 15:50:58 +03:00
|
|
|
|
|
2020-02-04 22:59:13 +03:00
|
|
|
|
namespace UnityVolumeRendering
|
2019-02-17 15:50:58 +03:00
|
|
|
|
{
|
2020-02-04 22:59:13 +03:00
|
|
|
|
public class TransferFunction2DEditorWindow : EditorWindow
|
|
|
|
|
{
|
|
|
|
|
private Texture2D hist2DTex = null;
|
2019-02-17 15:50:58 +03:00
|
|
|
|
|
2020-02-04 22:59:13 +03:00
|
|
|
|
private bool needsRegenTexture = true;
|
2020-01-31 23:27:35 +03:00
|
|
|
|
|
2020-02-04 22:59:13 +03:00
|
|
|
|
private Material tfGUIMat = null;
|
|
|
|
|
private int selectedBoxIndex = -1;
|
2022-09-16 20:21:12 +03:00
|
|
|
|
private bool isMovingBox = false;
|
2019-02-17 15:50:58 +03:00
|
|
|
|
|
2020-02-04 22:59:13 +03:00
|
|
|
|
private VolumeRenderedObject volRendObject = null;
|
2020-01-31 23:27:35 +03:00
|
|
|
|
|
2022-09-16 20:21:12 +03:00
|
|
|
|
private List<ResizableArea> tfAreas = new List<ResizableArea>();
|
|
|
|
|
|
2020-05-12 17:19:39 +03:00
|
|
|
|
public static void ShowWindow()
|
2020-02-04 22:59:13 +03:00
|
|
|
|
{
|
2020-05-24 16:27:16 +03:00
|
|
|
|
// Close all (if any) 1D TF editor windows
|
|
|
|
|
TransferFunctionEditorWindow[] tf1dWnds = Resources.FindObjectsOfTypeAll<TransferFunctionEditorWindow>();
|
|
|
|
|
foreach (TransferFunctionEditorWindow tf1dWnd in tf1dWnds)
|
2020-02-04 22:59:13 +03:00
|
|
|
|
tf1dWnd.Close();
|
2019-02-17 15:50:58 +03:00
|
|
|
|
|
2020-02-04 22:59:13 +03:00
|
|
|
|
TransferFunction2DEditorWindow tf2dWnd = (TransferFunction2DEditorWindow)EditorWindow.GetWindow(typeof(TransferFunction2DEditorWindow));
|
|
|
|
|
tf2dWnd.Show();
|
|
|
|
|
tf2dWnd.SetInitialPosition();
|
|
|
|
|
}
|
2020-01-31 23:27:35 +03:00
|
|
|
|
|
2020-02-04 22:59:13 +03:00
|
|
|
|
private void SetInitialPosition()
|
2020-01-31 23:27:35 +03:00
|
|
|
|
{
|
2020-02-04 22:59:13 +03:00
|
|
|
|
Rect rect = this.position;
|
|
|
|
|
rect.width = 800.0f;
|
|
|
|
|
rect.height = 500.0f;
|
|
|
|
|
this.position = rect;
|
2020-01-31 23:27:35 +03:00
|
|
|
|
}
|
2019-02-17 15:50:58 +03:00
|
|
|
|
|
2020-02-04 22:59:13 +03:00
|
|
|
|
private void OnEnable()
|
|
|
|
|
{
|
|
|
|
|
tfGUIMat = Resources.Load<Material>("TransferFunction2DGUIMat");
|
|
|
|
|
|
2020-01-31 23:27:35 +03:00
|
|
|
|
volRendObject = SelectionHelper.GetSelectedVolumeObject();
|
2020-02-04 22:59:13 +03:00
|
|
|
|
if (volRendObject == null)
|
|
|
|
|
{
|
|
|
|
|
volRendObject = GameObject.FindObjectOfType<VolumeRenderedObject>();
|
|
|
|
|
if (volRendObject != null)
|
|
|
|
|
Selection.objects = new Object[] { volRendObject.gameObject };
|
|
|
|
|
}
|
2020-05-12 17:19:39 +03:00
|
|
|
|
|
|
|
|
|
if(volRendObject != null)
|
|
|
|
|
volRendObject.SetTransferFunctionMode(TFRenderMode.TF2D);
|
2020-02-04 22:59:13 +03:00
|
|
|
|
}
|
2020-01-31 23:27:35 +03:00
|
|
|
|
|
2020-02-04 22:59:13 +03:00
|
|
|
|
private void OnGUI()
|
|
|
|
|
{
|
2022-09-16 20:21:12 +03:00
|
|
|
|
Vector2 mousePos = new Vector2(Event.current.mousePosition.x, Event.current.mousePosition.y);
|
|
|
|
|
|
2020-02-04 22:59:13 +03:00
|
|
|
|
// Update selected object
|
|
|
|
|
if (volRendObject == null)
|
|
|
|
|
volRendObject = SelectionHelper.GetSelectedVolumeObject();
|
2019-02-17 15:50:58 +03:00
|
|
|
|
|
2020-02-04 22:59:13 +03:00
|
|
|
|
if (volRendObject == null)
|
|
|
|
|
return;
|
2019-02-17 15:50:58 +03:00
|
|
|
|
|
2020-02-04 22:59:13 +03:00
|
|
|
|
if (hist2DTex == null)
|
|
|
|
|
hist2DTex = HistogramTextureGenerator.Generate2DHistogramTexture(volRendObject.dataset);
|
2019-02-17 15:50:58 +03:00
|
|
|
|
|
2020-02-04 22:59:13 +03:00
|
|
|
|
TransferFunction2D tf2d = volRendObject.transferFunction2D;
|
2019-02-17 15:50:58 +03:00
|
|
|
|
|
2022-09-16 20:21:12 +03:00
|
|
|
|
if (tf2d.boxes.Count != tfAreas.Count)
|
|
|
|
|
{
|
|
|
|
|
tfAreas.Clear();
|
|
|
|
|
foreach (TransferFunction2D.TF2DBox tfBox in tf2d.boxes)
|
|
|
|
|
{
|
|
|
|
|
ResizableArea area = new ResizableArea();
|
|
|
|
|
tfAreas.Add(area);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-04 22:59:13 +03:00
|
|
|
|
// Calculate GUI width (minimum of window width and window height * 2)
|
|
|
|
|
float bgWidth = Mathf.Min(this.position.width - 20.0f, (this.position.height - 250.0f) * 2.0f);
|
|
|
|
|
// Draw the histogram
|
|
|
|
|
Rect histRect = new Rect(0.0f, 0.0f, bgWidth, bgWidth * 0.5f);
|
|
|
|
|
Graphics.DrawTexture(histRect, hist2DTex);
|
|
|
|
|
// Draw the TF texture (showing the rectangles)
|
|
|
|
|
tfGUIMat.SetTexture("_TFTex", tf2d.GetTexture());
|
|
|
|
|
Graphics.DrawTexture(histRect, tf2d.GetTexture(), tfGUIMat);
|
|
|
|
|
|
2022-09-16 20:21:12 +03:00
|
|
|
|
// Handle mouse drag
|
|
|
|
|
for (int i = 0; i < tf2d.boxes.Count; i++)
|
2019-02-17 15:50:58 +03:00
|
|
|
|
{
|
2022-09-16 20:21:12 +03:00
|
|
|
|
int iBox = (i + selectedBoxIndex + 1) % tf2d.boxes.Count;
|
2020-02-04 22:59:13 +03:00
|
|
|
|
TransferFunction2D.TF2DBox box = tf2d.boxes[iBox];
|
2022-09-16 20:21:12 +03:00
|
|
|
|
ResizableArea tfArea = tfAreas[iBox];
|
|
|
|
|
|
|
|
|
|
if (isMovingBox && selectedBoxIndex == iBox)
|
2020-02-04 22:59:13 +03:00
|
|
|
|
{
|
2022-09-16 20:21:12 +03:00
|
|
|
|
if (Event.current.type == EventType.MouseUp)
|
|
|
|
|
{
|
|
|
|
|
tfArea.StopMoving();
|
|
|
|
|
isMovingBox = false;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
tfArea.UpdateMoving(mousePos);
|
|
|
|
|
|
|
|
|
|
if (tfArea.rectChanged)
|
|
|
|
|
{
|
|
|
|
|
Rect rect = tfArea.GetRect();
|
|
|
|
|
box.rect.x = rect.x / histRect.width - histRect.x;
|
|
|
|
|
box.rect.y = 1.0f - (rect.y + rect.height) / histRect.height;
|
|
|
|
|
box.rect.width = rect.width / histRect.width;
|
|
|
|
|
box.rect.height = rect.height / histRect.height;
|
|
|
|
|
tf2d.boxes[iBox] = box;
|
|
|
|
|
needsRegenTexture = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Rect boxRect = new Rect(histRect.x + box.rect.x * histRect.width, histRect.y + (1.0f - box.rect.height - box.rect.y) * histRect.height, box.rect.width * histRect.width, box.rect.height * histRect.height);
|
|
|
|
|
tfArea.SetRect(boxRect);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tfArea.Draw();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (Event.current.type == EventType.MouseDown)
|
|
|
|
|
{
|
|
|
|
|
// First priority: Pick area where mouse intersects the border.
|
|
|
|
|
int candidate = GetIntersectingAreas(selectedBoxIndex + 1, (ResizableArea area) => { return area.IntersectsBorder(mousePos); });
|
|
|
|
|
// Second priority: Pick area where mouse intersects the rect.
|
|
|
|
|
if (candidate == -1)
|
|
|
|
|
candidate = GetIntersectingAreas(selectedBoxIndex, (ResizableArea area) => { return area.Intersects(mousePos); });
|
|
|
|
|
if (candidate != -1)
|
|
|
|
|
{
|
|
|
|
|
selectedBoxIndex = candidate;
|
|
|
|
|
isMovingBox = true;
|
|
|
|
|
tfAreas[candidate].StartMoving(mousePos);
|
2020-02-04 22:59:13 +03:00
|
|
|
|
}
|
2019-02-17 15:50:58 +03:00
|
|
|
|
}
|
|
|
|
|
|
2020-02-04 22:59:13 +03:00
|
|
|
|
float startX = histRect.x;
|
|
|
|
|
float startY = histRect.y + histRect.height + 10;
|
|
|
|
|
// Show GUI for editing selected rectangle
|
|
|
|
|
if (selectedBoxIndex != -1)
|
|
|
|
|
{
|
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
|
|
|
TransferFunction2D.TF2DBox box = tf2d.boxes[selectedBoxIndex];
|
2022-09-16 20:21:12 +03:00
|
|
|
|
box.colour = EditorGUI.ColorField(new Rect(startX + 250.0f, startY + 10, 100.0f, 20.0f), box.colour);
|
|
|
|
|
box.minAlpha = EditorGUI.Slider(new Rect(startX + 250.0f, startY + 30, 200.0f, 20.0f), "min alpha", box.minAlpha, 0.0f, 1.0f);
|
|
|
|
|
box.alpha = EditorGUI.Slider(new Rect(startX + 250.0f, startY + 60, 200.0f, 20.0f), "max alpha", box.alpha, 0.0f, 1.0f);
|
2020-02-04 22:59:13 +03:00
|
|
|
|
|
|
|
|
|
tf2d.boxes[selectedBoxIndex] = box;
|
|
|
|
|
needsRegenTexture |= EditorGUI.EndChangeCheck();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2022-09-16 20:21:12 +03:00
|
|
|
|
EditorGUI.LabelField(new Rect(startX, startY, this.position.width - startX, 40.0f), "Select a rectangle in the above view, or add a new one.");
|
2020-02-04 22:59:13 +03:00
|
|
|
|
}
|
2019-05-12 00:33:51 +03:00
|
|
|
|
|
2020-02-04 22:59:13 +03:00
|
|
|
|
// Add new rectangle
|
2022-09-16 20:21:12 +03:00
|
|
|
|
if (GUI.Button(new Rect(startX, startY + 40, 150.0f, 30.0f), "Add rectangle"))
|
2019-05-12 00:33:51 +03:00
|
|
|
|
{
|
2020-02-04 22:59:13 +03:00
|
|
|
|
tf2d.AddBox(0.1f, 0.1f, 0.8f, 0.8f, Color.white, 0.5f);
|
2019-05-12 00:33:51 +03:00
|
|
|
|
needsRegenTexture = true;
|
|
|
|
|
}
|
2020-02-04 22:59:13 +03:00
|
|
|
|
// Remove selected shape
|
|
|
|
|
if (selectedBoxIndex != -1)
|
|
|
|
|
{
|
2022-09-16 20:21:12 +03:00
|
|
|
|
if (GUI.Button(new Rect(startX, startY + 80, 150.0f, 30.0f), "Remove selected shape"))
|
2020-02-04 22:59:13 +03:00
|
|
|
|
{
|
|
|
|
|
tf2d.boxes.RemoveAt(selectedBoxIndex);
|
2021-11-10 21:00:22 +03:00
|
|
|
|
selectedBoxIndex = -1;
|
2020-02-04 22:59:13 +03:00
|
|
|
|
needsRegenTexture = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-16 20:21:12 +03:00
|
|
|
|
if(GUI.Button(new Rect(startX, startY + 120, 150.0f, 30.0f), "Save"))
|
2020-02-25 23:01:58 +03:00
|
|
|
|
{
|
|
|
|
|
string filepath = EditorUtility.SaveFilePanel("Save transfer function", "", "default.tf2d", "tf2d");
|
|
|
|
|
if(filepath != "")
|
|
|
|
|
TransferFunctionDatabase.SaveTransferFunction2D(tf2d, filepath);
|
|
|
|
|
}
|
2022-09-16 20:21:12 +03:00
|
|
|
|
if(GUI.Button(new Rect(startX, startY + 160, 150.0f, 30.0f), "Load"))
|
2020-02-25 23:01:58 +03:00
|
|
|
|
{
|
|
|
|
|
string filepath = EditorUtility.OpenFilePanel("Save transfer function", "", "tf2d");
|
|
|
|
|
if(filepath != "")
|
|
|
|
|
{
|
|
|
|
|
TransferFunction2D newTF = TransferFunctionDatabase.LoadTransferFunction2D(filepath);
|
|
|
|
|
if(newTF != null)
|
2020-02-26 10:46:36 +03:00
|
|
|
|
{
|
2020-02-25 23:01:58 +03:00
|
|
|
|
volRendObject.transferFunction2D = tf2d = newTF;
|
2020-02-26 10:46:36 +03:00
|
|
|
|
needsRegenTexture = true;
|
|
|
|
|
}
|
2020-02-25 23:01:58 +03:00
|
|
|
|
}
|
|
|
|
|
}
|
2019-05-12 00:33:51 +03:00
|
|
|
|
}
|
2019-02-17 15:50:58 +03:00
|
|
|
|
|
2020-02-04 22:59:13 +03:00
|
|
|
|
private void OnSelectionChange()
|
2019-02-17 15:50:58 +03:00
|
|
|
|
{
|
2020-02-04 22:59:13 +03:00
|
|
|
|
VolumeRenderedObject newVolRendObj = Selection.activeGameObject?.GetComponent<VolumeRenderedObject>();
|
|
|
|
|
// If we selected another volume object than the one previously edited in this GUI
|
|
|
|
|
if (volRendObject != null && newVolRendObj != null && newVolRendObj != volRendObject)
|
|
|
|
|
this.Close();
|
2019-02-17 15:50:58 +03:00
|
|
|
|
}
|
|
|
|
|
|
2020-02-04 22:59:13 +03:00
|
|
|
|
public void OnInspectorUpdate()
|
|
|
|
|
{
|
|
|
|
|
Repaint();
|
2022-09-16 20:21:12 +03:00
|
|
|
|
|
|
|
|
|
// TODO: regenerate on add/remove/modify (and do it async)
|
|
|
|
|
if (needsRegenTexture)
|
|
|
|
|
{
|
|
|
|
|
TransferFunction2D tf2d = volRendObject.transferFunction2D;
|
|
|
|
|
tf2d.GenerateTexture();
|
|
|
|
|
needsRegenTexture = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private int GetIntersectingAreas(int startIndex, System.Func<ResizableArea, bool> comparator)
|
|
|
|
|
{
|
|
|
|
|
for (int i = 0; i < tfAreas.Count; i++)
|
|
|
|
|
{
|
|
|
|
|
int iBox = (i + selectedBoxIndex) % tfAreas.Count;
|
|
|
|
|
if(comparator(tfAreas[iBox]))
|
|
|
|
|
return iBox;
|
|
|
|
|
}
|
|
|
|
|
return -1;
|
2020-02-04 22:59:13 +03:00
|
|
|
|
}
|
2019-02-17 15:50:58 +03:00
|
|
|
|
}
|
|
|
|
|
}
|