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
This commit is contained in:
Matias Lavik 2022-09-24 20:19:33 +02:00 коммит произвёл GitHub
Родитель 498e5f52b5
Коммит a7203b765e
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
19 изменённых файлов: 775 добавлений и 242 удалений

Просмотреть файл

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

Просмотреть файл

@ -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<VolumeRenderedObject>();
if (volRendObject != null)
{
Selection.objects = new Object[] { volRendObject.gameObject };
return volRendObject;
}
return null;
}
}

Просмотреть файл

@ -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<TransferFunction2DEditorWindow>();
@ -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<Material>("TransferFunctionGUIMat");
tfPaletteGUIMat = Resources.Load<Material>("TransferFunctionPaletteGUIMat");
volRendObject = SelectionHelper.GetSelectedVolumeObject();
if (volRendObject == null)
{
volRendObject = GameObject.FindObjectOfType<VolumeRenderedObject>();
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;
}
/// <summary>
/// Pick the colour control point, nearest to the specified position.
/// </summary>
/// <param name="maxDistance">Threshold for maximum distance. Points further away than this won't get picked.</param>
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;
}
/// <summary>
/// Pick the alpha control point, nearest to the specified position.
/// </summary>
/// <param name="maxDistance">Threshold for maximum distance. Points further away than this won't get picked.</param>
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<VolumeRenderedObject>();

Просмотреть файл

@ -39,7 +39,7 @@ namespace UnityVolumeRendering
if (GUILayout.Button("Edit transfer function"))
{
if (tfMode == TFRenderMode.TF1D)
TransferFunctionEditorWindow.ShowWindow();
TransferFunctionEditorWindow.ShowWindow(volrendObj);
else
TransferFunction2DEditorWindow.ShowWindow();
}

Просмотреть файл

@ -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")]

Просмотреть файл

@ -0,0 +1,136 @@
using System;
using System.IO;
using UnityEngine;
namespace UnityVolumeRendering
{
public class ColourPickerPopup : MonoBehaviour
{
public Action<Color> 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);
}
}
}

Просмотреть файл

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fb9bffc42ab3c9afcbe13a62b234e554
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

Просмотреть файл

@ -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)))
{

Просмотреть файл

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

Просмотреть файл

@ -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");

Просмотреть файл

@ -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<RuntimeTransferFunctionEditor>();
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();
}
}
/// <summary>
/// Pick the colour control point, nearest to the specified position.
/// </summary>
/// <param name="maxDistance">Threshold for maximum distance. Points further away than this won't get picked.</param>
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;
}
/// <summary>
/// Pick the alpha control point, nearest to the specified position.
/// </summary>
/// <param name="maxDistance">Threshold for maximum distance. Points further away than this won't get picked.</param>
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);
}
}
}

Просмотреть файл

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 05f581cf1454d0be6b36abae59693162
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

Просмотреть файл

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 8c9a71a3f851b78acaf5caabe4350986
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Просмотреть файл

@ -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<ColourPickerPopup>();
}
colourPicker.SetColour(colour);
}
if (colourPicker)
return colourPicker.GetColour();
else
return colour;
}
}
}

Просмотреть файл

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d7eee4fe67949ef989034674911148da
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

Просмотреть файл

@ -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<Material>("TransferFunctionGUIMat");
tfPaletteGUIMat = Resources.Load<Material>("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;
}
}
/// <summary>
/// Pick the colour control point, nearest to the specified position.
/// </summary>
/// <param name="maxDistance">Threshold for maximum distance. Points further away than this won't get picked.</param>
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;
}
/// <summary>
/// Pick the alpha control point, nearest to the specified position.
/// </summary>
/// <param name="maxDistance">Threshold for maximum distance. Points further away than this won't get picked.</param>
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;
}
}
}

Просмотреть файл

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9686cd473128d99a0aa69fa100f03e80
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

Просмотреть файл

@ -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;

Просмотреть файл

@ -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"