/*=============================================================================
Project: SharpImage
Module: siVolumeRenderer.cs
Language: C#
Author: Dan Mueller
Date: $Date: 2008-01-09 16:23:10 +1000 (Wed, 09 Jan 2008) $
Revision: $Revision: 29 $
Copyright (c) Queensland University of Technology (QUT) 2007.
All rights reserved.
This software is distributed WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE. See the above copyright notices for more information.
=============================================================================*/
using System;
using System.IO;
using System.Text;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Design;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
using System.ComponentModel;
using Tao.OpenGl;
using Tao.FreeGlut;
using Tao.Platform.Windows;
using SharpImage.Main;
using SharpImage.Forms;
namespace SharpImage.Rendering
{
public class siVolumeRenderer : siRenderer
{
#region Constants
//=====================================================================
protected static readonly Color DEFAULT_BACK_COLOR = Color.White;
//public const int MAXIMUM_IMAGE_TAGS_AND_ATTRIBUTES = 10;
private const long SHADER_WATCHER_TIME = 5; // In seconds
//=====================================================================
#endregion
#region Instance Variables
//=====================================================================
private bool m_IsDisposed = false;
private bool m_HasRenderedFirstTime = false;
private string m_DefaultFragmentProgramPath = null;
FileSystemWatcher m_ShaderWatcher = null;
TimeSpan m_ShaderLastChanged = TimeSpan.Zero;
bool m_IsAskingUserToReloadShader = false;
private siTextureGeometryGenerator m_TexureGeometryGenerator = new siTextureGeometryGenerator();
private siSpatialObjectRenderingHelper m_SpatialObjectHelper = new siSpatialObjectRenderingHelper();
private long m_TickFrequency = 0;
private long m_TickCurrent = 0;
private long m_TickPrevious = 0;
private long m_FrameCount = 0;
private float m_FrameRate = 0;
//=====================================================================
#endregion
#region Construction and Disposal
//=====================================================================
///
/// Default constructor.
///
public siVolumeRenderer(IApplication parent)
: base("Volume Renderer", parent, new siOpenGlRendererForm())
{
// Setup member variables
this.BackColor = DEFAULT_BACK_COLOR;
// Setup Metadata
this.m_Properties = new siVolumeProperties(this);
this.Metadata["IsTranslating"] = false;
this.Metadata["IsRotating"] = false;
this.Metadata["Shader"] = null;
this.Metadata["TransferFunction"] = null;
this.Metadata["VertexProgram"] = null;
this.Metadata["FragmentProgram"] = null;
this.Metadata["ForcePowerOfTwoTextureSize"] = false;
// Watch for a change in the sampling rate
this.Form.ResizeBegin += new EventHandler(Form_ResizeBegin);
this.Form.ResizeEnd += new EventHandler(Form_ResizeEnd);
// Watch for changes in the shader source
this.m_ShaderLastChanged = new TimeSpan(DateTime.Now.Ticks);
this.m_ShaderWatcher = new FileSystemWatcher();
this.m_ShaderWatcher.Path = "C:/";
this.m_ShaderWatcher.Filter = "ThisStringWillNotMatchAnyFiles.xxx";
this.m_ShaderWatcher.IncludeSubdirectories = false;
this.m_ShaderWatcher.NotifyFilter = NotifyFilters.LastWrite;
this.m_ShaderWatcher.EnableRaisingEvents = true;
this.m_ShaderWatcher.Changed += new FileSystemEventHandler(watcher_Changed);
}
///
/// Deconstructor.
///
~siVolumeRenderer()
{
// Call Dispose with false. Since we're in the
// destructor call, the managed resources will be
// disposed of anyways.
this.Dispose(false);
}
///
/// Dispose of any resources.
///
public override void Dispose()
{
// Dispose both managed and unmanaged resources
this.Dispose(true);
// Tell the GC that the Finalize process no longer needs
// to be run for this object.
GC.SuppressFinalize(this);
}
///
/// Dispose of the managed resources.
///
///
protected virtual void Dispose(bool disposeManagedResources)
{
// Check that we are not already disposed
if (this.m_IsDisposed)
return;
// Only get here if not already disposed
if (disposeManagedResources)
{
this.Form.Dispose();
}
// Dispose of the file watcher
if (this.m_ShaderWatcher != null)
{
this.m_ShaderWatcher.Changed -= new FileSystemEventHandler(watcher_Changed);
this.m_ShaderWatcher.Dispose();
this.m_ShaderWatcher = null;
}
// Dispose base
base.Dispose();
// Set that this have been disposed
this.m_IsDisposed = true;
}
//=====================================================================
#endregion
#region Properties
//=====================================================================
#region Properties
//=====================================================================
private siVolumeProperties m_Properties;
///
/// Gets the properties object associated with this renderer.
///
[Browsable(false)]
public siVolumeProperties Properties
{
get { return this.m_Properties; }
set { this.m_Properties = value; }
}
//=====================================================================
#endregion
#region Shader
//=====================================================================
///
/// Gets the vertex and fragment shader programs replacing the OpenGL
/// fixed functionality.
///
[Browsable(false)]
public siOpenGlShader Shader
{
get { return this.Metadata["Shader"] as siOpenGlShader; }
}
//=====================================================================
#endregion
#region TransferFunction
//=====================================================================
///
/// Gets the transfer function associated with this volume renderer.
///
[Browsable(false)]
public siTransferFunction TransferFunction
{
get { return this.Metadata["TransferFunction"] as siTransferFunction; }
}
//=====================================================================
#endregion
#region PowerOfTwoTextureSize
//=====================================================================
///
/// Gets/sets if the texture data should be forced to be a power of two.
/// The default is false (if supported by the hardware).
///
[Browsable(false)]
public bool ForcePowerOfTwoTextureSize
{
get { return (bool)this.Metadata["ForcePowerOfTwoTextureSize"]; }
set { this.Metadata["ForcePowerOfTwoTextureSize"] = value; }
}
//=====================================================================
#endregion
#region Geometry
//=====================================================================
///
/// Gets/sets the blending type.
///
[Browsable(false)]
public siTextureGeometryGenerator GeometryGenerator
{
get { return this.m_TexureGeometryGenerator; }
}
//=====================================================================
#endregion
//=====================================================================
#endregion
#region Public Methods
//=====================================================================
///
/// Initialise the Renderer.
///
public override void Initialise()
{
// Call the base
base.Initialise();
// Init the OpenGL form and control
this.Form.Initialise(this);
//Add filename to form text
this.TitleText = Path.GetFileName(this.Inputs[0].Name);
// Setup trackball
this.Properties.TrackBall.Reset();
// Get the tick frequency for fps calculations
// NOTE: This value will be constant for the machine operation
if (!Kernel.QueryPerformanceFrequency(out this.m_TickFrequency))
throw new ApplicationException("The machine tick frequency could not be queried using Kernel32.dll QueryPerformanceFrequency().");
// Compute the model size
this.Properties.ComputeModelSize();
// Init any input images
this.InitialiseInputImages();
// Init for texture rendering
if (this.ContainsMetadata("ImageValue"))
{
// Do general OpenGL initialisation
Gl.glEnable(Gl.GL_DEPTH_TEST); // Enable depth testing
Gl.glDepthFunc(Gl.GL_LEQUAL);
Gl.glEnable(Gl.GL_NORMALIZE); // Make Normals [0..1]
Gl.glPixelStorei(Gl.GL_UNPACK_ALIGNMENT, 1); // Tightly pack pixels
Gl.glPixelStorei(Gl.GL_PACK_ALIGNMENT, 1);
// Init shader programs
if (!this.ContainsMetadata("VertexProgram", "FragmentProgram"))
throw new ApplicationException("The renderer must be tagged with vertex and fragment shader program file paths using the Metadata fields: VertexProgram and FragmentProgram.");
string pathVertex = (string)this.Metadata["VertexProgram"];
string pathFragment = (string)this.Metadata["FragmentProgram"];
string shaderInfo = "Loading shader program: {0}, {1}";
this.ParentApplication.ScriptConsole.WriteLine(string.Format(shaderInfo, Path.GetFileName(pathVertex), Path.GetFileName(pathFragment)));
siOpenGlShader shader = new siOpenGlShader();
shader.Initialise(pathVertex, pathFragment);
this.Metadata["Shader"] = shader;
// Force the starting sampling rate to be uploaded to the shader
this.SamplingRateChanged(this.Properties.SamplingRate);
// Load OpenGL 3D texture extensions
this.TestOpenGlExtensionsSupported();
// Load the images as textures
this.LoadImagesAsTextures();
// Init the transfer function
if (this.ContainsMetadata("TransferFunction"))
{
this.TransferFunction.Modified += new siTransferFunction.siTransferFunctionModifiedHandler(TransferFunction_Modified);
this.TransferFunction.EditStarted += new siTransferFunction.siTransferFunctionHandler(TransferFunction_EditStarted);
this.TransferFunction.EditFinished += new siTransferFunction.siTransferFunctionHandler(TransferFunction_EditFinished);
this.InitialiseTransferFunction();
this.LoadTransferFunctionAsTexture(false);
}
// Setup the shader texture units
this.SetShaderTextureUnits();
// Setup the lighting environment
this.Properties.LightColorAmbient = Color.FromArgb(255, 128, 128, 128);
this.Properties.LightColorDiffuse = Color.FromArgb(255, 128, 128, 128);
this.Properties.LightColorSpecular = Color.FromArgb(255, 64, 64, 64);
this.Properties.LightColorSpecularExponent = 5.0f;
this.Properties.NormalOffset = 0.004f;
this.Properties.Factor1 = 0.0f;
this.Properties.Factor2 = 0.0f;
this.Properties.Factor3 = 0.0f;
// Init the geometry generator
itk.itkImageBase imageValue = this.GetMetadataAsImage("ImageValue");
this.m_TexureGeometryGenerator.Initialise(imageValue.Size, imageValue.Spacing, this.Properties.ModelSize);
}
// Refresh
this.Repaint();
this.Form.ForceResize();
}
///
/// Forces the Renderer to repaint.
///
public override void Repaint()
{
base.Repaint();
if (this.Form != null)
this.Form.Repaint();
}
///
/// Close the Renderer and dispose of all resources.
///
public override void Close()
{
base.Close();
this.Dispose();
}
///
/// Get the image currently displayed by the screen.
///
///
public override Bitmap GetScreenCapture()
{
int width = this.ViewportSize.Width;
int height = this.ViewportSize.Height;
// Adjust width and height to be a factor of 4
if (width % 4 != 0) width -= width % 4;
if (height % 4 != 0) height -= height % 4;
// Create and lock bitmap
Bitmap bitmap = new Bitmap(width, height, PixelFormat.Format24bppRgb);
Rectangle rect = new Rectangle(0, 0, width, height);
BitmapData bitmapData = bitmap.LockBits(rect, ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb);
// "Tightly" pack pixels
Gl.glPixelStorei(Gl.GL_UNPACK_ALIGNMENT, 1);
Gl.glPixelStorei(Gl.GL_PACK_ALIGNMENT, 1);
// Force a flush / swap buffers
//this..Invalidate();
// Read buffer
Gl.glReadPixels(0, 0, width, height, Gl.GL_BGR, Gl.GL_UNSIGNED_BYTE, bitmapData.Scan0);
// Unlock and flip
bitmap.UnlockBits(bitmapData);
bitmap.RotateFlip(RotateFlipType.RotateNoneFlipY);
//Return
return bitmap;
}
///
/// Update the texture with the given input image.
///
/// Either "Value", "Gradient", "Extra1", or "Extra2".
///
public void UpdateInput(String name, itk.itkImageBase input)
{
// Check dimensions
if (input.Dimension != 3)
throw new ApplicationException("The input image '" + Path.GetFileName(input.Name) + "' is not a 3D image.");
// Ensure size
itk.itkImageBase validInput = null;
if (this.ForcePowerOfTwoTextureSize || !this.IsNonPowerOfTwoTextureSizeSupported())
validInput = this.EnsureImageIsPowerOfTwoSize(input);
else
validInput = input;
// Rescale the image (this will only occur if required)
validInput = this.RescaleImage(validInput);
// Copy metadata keys to validInput
if (validInput != input)
{
foreach (KeyValuePair pair in input.Metadata)
validInput.Metadata[pair.Key] = pair.Value;
}
// Check that current is not input
itk.itkImageBase current = this.GetMetadataAsImage("Image" + name);
if (validInput == current)
return;
// Copy metadata from current input to validInput
foreach (KeyValuePair pair in current.Metadata)
validInput.Metadata[pair.Key] = pair.Value;
// Set the input
this.Metadata["Image" + name] = validInput;
// Upload the input as a texture
this.LoadImageAsTexture(validInput, false);
this.Repaint();
}
//=====================================================================
#endregion
#region Rendering Methods
//=====================================================================
protected override void OnPaint(PaintEventArgs e)
{
// Give the base class a chance to paint
base.OnPaint(e);
// Only render if we are modified
if (!this.IsModified)
return;
// Compute the fps
this.m_FrameCount++;
Kernel.QueryPerformanceCounterFast(out this.m_TickCurrent);
long tickDifference = this.m_TickCurrent - this.m_TickPrevious;
if (tickDifference > this.m_TickFrequency)
{
this.m_FrameRate = (float)(this.m_FrameCount * this.m_TickFrequency) / (float)tickDifference;
this.m_TickPrevious = this.m_TickCurrent;
this.m_FrameCount = 0;
this.TitleText = Path.GetFileName(this.Inputs[0].Name) + " (" + this.m_FrameRate.ToString("00.0") + "fps)";
}
// Do rendering
this.RenderInitialise();
this.RenderBoxOutline();
this.RenderInputs();
this.RenderFinalise();
// Reset modified and first time flags
this.m_HasRenderedFirstTime = true;
this.IsModified = false;
}
///
/// Initialises for the DrawInput method.
///
private void RenderInitialise()
{
// Force background to tbe drawn
Gl.glMatrixMode(Gl.GL_MODELVIEW);
Gl.glDrawBuffer(Gl.GL_BACK);
Gl.glClear(Gl.GL_COLOR_BUFFER_BIT | Gl.GL_DEPTH_BUFFER_BIT);
float[] back = siColorHelper.To4fv(this.BackColor);
Gl.glClearColor(back[0], back[1], back[2], back[3]);
//Gl.glClearDepth(1.0f);
Gl.glShadeModel(Gl.GL_SMOOTH);
Gl.glHint(Gl.GL_PERSPECTIVE_CORRECTION_HINT, Gl.GL_NICEST);
// Change what we are looking at
Gl.glMatrixMode(Gl.GL_MODELVIEW);
Gl.glLoadIdentity();
Glu.gluLookAt(this.Properties.EyeX, this.Properties.EyeY, this.Properties.EyeZ,
this.Properties.CenterX, this.Properties.CenterY, this.Properties.CenterZ,
this.Properties.UpX, this.Properties.UpY, this.Properties.UpZ);
// Set Frustum
Gl.glMatrixMode(Gl.GL_PROJECTION);
Gl.glLoadIdentity();
Gl.glFrustum(this.Properties.FrustrumLeft,
this.Properties.FrustrumRight,
this.Properties.FrustrumBottom,
this.Properties.FrustrumTop,
this.Properties.FrustrumFront,
this.Properties.FrustrumBack);
// Translate
Gl.glMatrixMode(Gl.GL_MODELVIEW);
double[] transdata = this.Properties.TransformTranslate.Data;
Gl.glTranslated(transdata[0], transdata[1], transdata[2]);
// Rotate
Gl.glMatrixMode(Gl.GL_MODELVIEW);
Gl.glMultMatrixd(this.Properties.TrackBall.GetRotationMatrixAsArray());
}
///
/// Finialises the drawing
///
private void RenderFinalise()
{
// Flush
Gl.glFlush();
}
///
/// Render a box outlining the volume.
///
private void RenderBoxOutline()
{
// Exit if we are not rendering the box
if (!this.Properties.BoxOutlineEnabled)
return;
// Begin
Gl.glMatrixMode(Gl.GL_MODELVIEW);
Gl.glPushMatrix();
Gl.glDisable(Gl.GL_LIGHTING);
//Gl.glDisable(Gl.GL_BLEND);
Gl.glEnable(Gl.GL_BLEND);
Gl.glBlendFunc(Gl.GL_SRC_ALPHA, Gl.GL_ONE_MINUS_SRC_ALPHA);
//Gl.glDisable(Gl.GL_DEPTH_TEST);
itk.itkArray size = this.Properties.ModelSize;
Gl.glTranslated(-0.5 * size[0], -0.5 * size[1], -0.5 * size[2]);
Gl.glBegin(Gl.GL_LINES);
{
Gl.glColor4fv(siColorHelper.To4fv(this.Properties.BoxOutlineColor));
// Back square
Gl.glVertex3d(0, 0, 0);
Gl.glVertex3d(0, size[1], 0);
Gl.glVertex3d(0, size[1], 0);
Gl.glVertex3d(size[0], size[1], 0);
Gl.glVertex3d(size[0], size[1], 0);
Gl.glVertex3d(size[0], 0, 0);
Gl.glVertex3d(size[0], 0, 0);
Gl.glVertex3d(0, 0, 0);
// Front Square
Gl.glVertex3d(0, 0, size[2]);
Gl.glVertex3d(0, size[1], size[2]);
Gl.glVertex3d(0, size[1], size[2]);
Gl.glVertex3d(size[0], size[1], size[2]);
Gl.glVertex3d(size[0], size[1], size[2]);
Gl.glVertex3d(size[0], 0, size[2]);
Gl.glVertex3d(size[0], 0, size[2]);
Gl.glVertex3d(0, 0, size[2]);
// Missing bits
Gl.glVertex3d(0, 0, 0);
Gl.glVertex3d(0, 0, size[2]);
Gl.glVertex3d(0, size[1], 0);
Gl.glVertex3d(0, size[1], size[2]);
Gl.glVertex3d(size[0], size[1], 0);
Gl.glVertex3d(size[0], size[1], size[2]);
Gl.glVertex3d(size[0], 0, 0);
Gl.glVertex3d(size[0], 0, size[2]);
}
Gl.glEnd();
Gl.glEnd();
Gl.glDisable(Gl.GL_BLEND);
Gl.glEnable(Gl.GL_DEPTH_TEST);
Gl.glPopMatrix();
}
///
/// Renders the inputs
///
private void RenderInputs()
{
// Dispatch rendering of SpatialObjects
foreach (itk.itkDataObject input in this.Inputs)
{
if (m_SpatialObjectHelper.CanRender(input))
m_SpatialObjectHelper.Render(this, input);
}
// Render the texture interpolation geometry
if (this.ContainsMetadata("ImageValue"))
this.RenderTextureGeometry();
}
//=====================================================================
#endregion
#region Transfer Function Methods
//=====================================================================
private void TransferFunction_Modified(siTransferFunction sender, bool partial)
{
if (!partial)
{
// Reload the transfer function image data to the GPU
this.LoadTransferFunctionAsTexture(true);
this.Repaint();
}
}
private void TransferFunction_EditStarted(siTransferFunction sender)
{
this.Properties.SamplingRate.MakeCurrentLowQuality();
}
private void TransferFunction_EditFinished(siTransferFunction sender)
{
this.Properties.SamplingRate.MakeCurrentHighQuality();
}
private void InitialiseTransferFunction()
{
if (this.ContainsMetadata("TransferFunction"))
{
// NOTE: We assume the transfer function is a power of two size.
// Setup texture metadata
int[] texnames = new int[1];
Gl.glGenTextures(1, texnames);
this.TransferFunction.Image.Metadata["TextureType"] = "TransferFunction";
this.TransferFunction.Image.Metadata["TextureName"] = texnames[0];
this.TransferFunction.Image.Metadata["TextureUnit"] = Gl.GL_TEXTURE0_ARB;
this.TransferFunction.Image.Metadata["TextureInterpolation"] = Gl.GL_NEAREST;
}
}
///
/// Load the transfer function image data to the GPU.
///
private void LoadTransferFunctionAsTexture(bool silent)
{
if (this.TransferFunction != null &&
this.TransferFunction.Image != null &&
!this.TransferFunction.Image.IsDisposed)
{
this.TransferFunction.PaintToImage();
//this.TransferFunction.Image.Write("C:/Temp/TransferFunction.mhd");
this.LoadImageAsTexture(this.TransferFunction.Image, silent);
}
}
//=====================================================================
#endregion
#region Texture Initialisation Methods
//=====================================================================
///
/// Initialises the input images.
/// This method looks at the input image tags, searching for:
/// "Value": for a value image
/// "Gradient": for a gradient image
/// "Extra1": extra data
/// "Extra2": extra data.
/// This method then creates similar tags in the renderer,
/// pointing to the associated input image.
///
private void InitialiseInputImages()
{
foreach (itk.itkDataObject input in this.Inputs)
{
if (input is itk.itkImageBase)
{
// Cast to image
itk.itkImageBase validInput = null;
itk.itkImageBase inputAsImage = input as itk.itkImageBase;
// Check dimensions
if (inputAsImage.Dimension != 3)
throw new ApplicationException("The input image '" + Path.GetFileName(input.Name) + "' is not a 3D image.");
// Ensure size
if (this.ForcePowerOfTwoTextureSize || !this.IsNonPowerOfTwoTextureSizeSupported())
validInput = this.EnsureImageIsPowerOfTwoSize(inputAsImage);
else
validInput = inputAsImage;
// Rescale the image (this will only occur if required)
validInput = this.RescaleImage(validInput);
// Copy metadata keys to validInput
if (validInput != inputAsImage)
{
foreach (KeyValuePair pair in input.Metadata)
validInput.Metadata[pair.Key] = pair.Value;
}
// Set renderer metadata
if (input.Metadata.ContainsKey("Value"))
this.Metadata["ImageValue"] = validInput;
else if (input.Metadata.ContainsKey("Gradient"))
this.Metadata["ImageGradient"] = validInput;
else if (input.Metadata.ContainsKey("Extra1"))
this.Metadata["ImageExtra1"] = validInput;
else if (input.Metadata.ContainsKey("Extra2"))
this.Metadata["ImageExtra2"] = validInput;
}
}
}
///
/// Ensures each dimension of the given image is a power of two.
/// If not,
///
///
private itk.itkImageBase EnsureImageIsPowerOfTwoSize(itk.itkImageBase image)
{
// Determine if the image must be padded
bool imageMustBePadded = false;
itk.itkSize newSize = new itk.itkSize((int[])image.Size.Data.Clone());
for (int i = 0; i < image.Size.Dimension; i++)
{
newSize[i] = siMathHelper.RoundToNextHighestPowerOfTwo(image.Size[i]);
if (image.Size[i] != newSize[i])
imageMustBePadded = true;
}
if (imageMustBePadded)
{
// Pad the image
string info = "Padding {0}: From {1} to {2}";
this.ParentApplication.ScriptConsole.WriteLine(string.Format(info, Path.GetFileName(image.Name), image.Size, newSize));
itk.itkImage imagePadded = itk.itkImage.New(image);
itk.itkConstantPadImageFilter filter = itk.itkConstantPadImageFilter.New(image, image);
filter.SetInput(image);
uint[] upper = new uint[image.Size.Dimension];
uint[] lower = new uint[image.Size.Dimension]; // We want all zeros
for (int i = 0; i < image.Size.Dimension; i++)
upper[i] = (uint)(newSize[i] - image.Size[i]);
filter.PadUpperBound = upper;
filter.PadLowerBound = lower;
filter.Update();
filter.GetOutput(imagePadded);
imagePadded.Name = image.Name;
image.DisconnectPipeline();
imagePadded.DisconnectPipeline();
filter.Dispose();
return imagePadded;
}
else
{
return image;
}
}
///
/// Ensures the given image is spread over the input pixel type.
/// For example: for signed short, the image is rescaled from [-32768, 32767].
/// For example: for float, the image is rescaled from [0.0, 1.0].
///
///
///
private itk.itkImageBase RescaleImage(itk.itkImageBase image)
{
// Check if rescaling is required
if (image.PixelType.IsChar)
return image;
// Get the min/max values
double minValue = 0.0;
double maxValue = 1.0;
if (image.Metadata.ContainsKey("MinimumValueAsD") &&
image.Metadata.ContainsKey("MaximumValueAsD"))
{
// Get min/max values from the image
minValue = (double)image.Metadata["MinimumValueAsD"];
maxValue = (double)image.Metadata["MaximumValueAsD"];
}
else
{
// Compute the min/max values
itk.itkMinimumMaximumImageCalculator calcMinMax = itk.itkMinimumMaximumImageCalculator.New(image);
calcMinMax.SetImage(image);
calcMinMax.Compute();
minValue = calcMinMax.Minimum.ValueAsD;
maxValue = calcMinMax.Maximum.ValueAsD;
image.DisconnectPipeline();
calcMinMax.Dispose();
}
// Check if the rescale needs to be done
switch (image.PixelType.TypeAsEnum)
{
case itk.itkPixelTypeEnum.SignedShort:
if (minValue == Int16.MinValue && maxValue == Int16.MaxValue)
{
image.Metadata["RequiresScaleAndBias"] = true;
return image;
}
break;
case itk.itkPixelTypeEnum.UnsignedShort:
if (minValue == UInt16.MinValue && maxValue == UInt16.MaxValue)
return image;
break;
case itk.itkPixelTypeEnum.Float:
case itk.itkPixelTypeEnum.Double:
if (minValue == 0.0 && maxValue == 1.0)
return image;
break;
default:
throw new NotSupportedException("The given pixel type is not supported: " + image.PixelType.ToString());
}
// NOTE: We only get here if the image needs to be rescaled
// Setup for the rescaling
bool requiresScaleAndBias = false;
itk.itkImage imageRescaled = itk.itkImage.New(image);
itk.itkRescaleIntensityImageFilter filter = itk.itkRescaleIntensityImageFilter.New(image, image);
filter.SetInput(image);
if (image.PixelType.IsReal)
{
filter.OutputMinimum = new itk.itkPixel(image.PixelType, 0.0);
filter.OutputMaximum = new itk.itkPixel(image.PixelType, 1.0);
}
else if (!image.PixelType.IsChar)
{
filter.OutputMinimum = new itk.itkPixel(image.PixelType, image.PixelType.MinValue);
filter.OutputMaximum = new itk.itkPixel(image.PixelType, image.PixelType.MaxValue);
if (image.PixelType.IsSigned)
requiresScaleAndBias = true;
}
// Tell the user
string info = "Rescaling {0}: [{1}, {2}]";
this.ParentApplication.ScriptConsole.WriteLine(string.Format(info, Path.GetFileName(image.Name), filter.OutputMinimum, filter.OutputMaximum));
// Do the rescaling and return the result
filter.UpdateLargestPossibleRegion();
filter.GetOutput(imageRescaled);
imageRescaled.Name = image.Name;
imageRescaled.Metadata.Add("RequiresScaleAndBias", requiresScaleAndBias);
image.DisconnectPipeline();
imageRescaled.DisconnectPipeline();
filter.Dispose();
return imageRescaled;
}
///
/// Test if the required OpenGl extensions are available on the system.
/// If they are not available, an exception is thrown.
///
private void TestOpenGlExtensionsSupported()
{
// List the extensions to load
string[] extensionsToTest = new string[]
{
"GL_EXT_texture3D",
"GL_ARB_multitexture",
"GL_EXT_blend_minmax"
};
// Test the extensions
bool result = true;
foreach (string extensionToTest in extensionsToTest)
result &= Gl.IsExtensionSupported(extensionToTest);
// Format extensions
string formattedExtensionsToTest = string.Empty;
for (int i = 0; i < extensionsToTest.Length; i++)
formattedExtensionsToTest += extensionsToTest[i] + ", ";
// Show status info to user
this.ParentApplication.ScriptConsole.WriteLine("Testing OpenGL extensions: " + formattedExtensionsToTest.TrimEnd(',', ' '));
// Throw exception
if (!result)
{
throw new ApplicationException("The OpenGL extensions required for 3D textures are not available: " + formattedExtensionsToTest.Trim(' ', ','));
}
}
///
/// Loads the given input images as OpenGL 3D textures.
///
private void LoadImagesAsTextures()
{
// TODO: Support bricks
// NOTE:
// TransferFunction (if provided) is always Texture0
// Value (which is required) is always Texture1
// Gradient (if provided) is always Texture2
// Extra1 (if provided) is always Texture3
// Extra2 (if provided) is always Texture4
// Setup textures (names, IDs, unit, interpolation)
if (this.ContainsMetadata("ImageValue"))
{
int[] texnames = new int[1];
Gl.glGenTextures(1, texnames);
this.GetMetadataAsImage("ImageValue").Metadata["TextureType"] = "Value";
this.GetMetadataAsImage("ImageValue").Metadata["TextureName"] = texnames[0];
this.GetMetadataAsImage("ImageValue").Metadata["TextureUnit"] = Gl.GL_TEXTURE1_ARB;
}
if (this.ContainsMetadata("ImageGradient"))
{
int[] texnames = new int[1];
Gl.glGenTextures(1, texnames);
this.GetMetadataAsImage("ImageGradient").Metadata["TextureType"] = "Gradient";
this.GetMetadataAsImage("ImageGradient").Metadata["TextureName"] = texnames[0];
this.GetMetadataAsImage("ImageGradient").Metadata["TextureUnit"] = Gl.GL_TEXTURE2_ARB;
}
if (this.ContainsMetadata("ImageExtra1"))
{
int[] texnames = new int[1];
Gl.glGenTextures(1, texnames);
this.GetMetadataAsImage("ImageExtra1").Metadata["TextureType"] = "Extra1";
this.GetMetadataAsImage("ImageExtra1").Metadata["TextureName"] = texnames[0];
this.GetMetadataAsImage("ImageExtra1").Metadata["TextureUnit"] = Gl.GL_TEXTURE3_ARB;
}
if (this.ContainsMetadata("ImageExtra2"))
{
int[] texnames = new int[1];
Gl.glGenTextures(1, texnames);
this.GetMetadataAsImage("ImageExtra2").Metadata["TextureType"] = "Extra2";
this.GetMetadataAsImage("ImageExtra2").Metadata["TextureName"] = texnames[0];
this.GetMetadataAsImage("ImageExtra2").Metadata["TextureUnit"] = Gl.GL_TEXTURE4_ARB;
}
// Load the textures
if (this.ContainsMetadata("ImageValue"))
this.LoadImageAsTexture(this.GetMetadataAsImage("ImageValue"));
if (this.ContainsMetadata("ImageGradient"))
this.LoadImageAsTexture(this.GetMetadataAsImage("ImageGradient"));
if (this.ContainsMetadata("ImageExtra1"))
this.LoadImageAsTexture(this.GetMetadataAsImage("ImageExtra1"));
if (this.ContainsMetadata("ImageExtra2"))
this.LoadImageAsTexture(this.GetMetadataAsImage("ImageExtra2"));
}
///
/// Load the given image as an OpenGL 3D texture.
/// Information is displayed in the console about the upload.
///
/// The scalar or vector image to load.
private void LoadImageAsTexture(itk.itkImageBase image)
{
this.LoadImageAsTexture(image, false);
}
///
/// Load the given image as an OpenGL 3D texture.
///
/// The scalar or vector image to load.
/// True and no info is displayed in the console, else False and info is printed.
private void LoadImageAsTexture(itk.itkImageBase image, bool silent)
{
const bool IGNORE_CASE = true;
// Get speed info
long tickStart = 0;
long tickEnd = 0;
Kernel.QueryPerformanceCounterFast(out tickStart);
Gl.glEnable(Gl.GL_TEXTURE_3D_EXT); // Enable
{
// Ensure image has interpolation
if (!image.Metadata.ContainsKey("TextureInterpolation"))
image.Metadata["TextureInterpolation"] = Gl.GL_LINEAR;
// Bind
this.BindTexture(image, false);
// Compute required information
// NOTE: We must swap the color components for the TransferFunction
// because the System.Drawing namespace (which we assume was used
// to create the TF) is actually BGR pixel type, not RGB.
bool swapColorComponents = (String.Compare(image.Metadata["TextureType"] as String, "TransferFunction", IGNORE_CASE) == 0);
int iFormat = this.ComputeTextureFormat(image.PixelType, swapColorComponents);
int iInternalFormat = this.ComputeTextureInternalFormat(image.PixelType);
int iDataType = this.ComputeTextureDataType(image.PixelType.TypeAsEnum);
// Set the scale and bias
if (image.Metadata.ContainsKey("RequiresScaleAndBias") &&
(bool)image.Metadata["RequiresScaleAndBias"])
{
float scale = 0.5F;
float bias = 0.5F;
Gl.glPixelTransferf(Gl.GL_ALPHA_SCALE, scale);
Gl.glPixelTransferf(Gl.GL_ALPHA_BIAS, bias);
}
// Upload the data
Gl.glTexImage3DEXT(
Gl.GL_TEXTURE_3D_EXT, // Target
0, // Level
iInternalFormat, // Internal Format
image.Size[0], // Width
image.Size[1], // Height
image.Size[2], // Depth
0, // Border
iFormat, // Format
iDataType, // Data Type
image.Buffer // Data Pointer
);
// Reset the scale and bias
Gl.glPixelTransferf(Gl.GL_ALPHA_SCALE, 1.0F);
Gl.glPixelTransferf(Gl.GL_ALPHA_BIAS, 0.0F);
}
Gl.glDisable(Gl.GL_TEXTURE_3D_EXT); //Disable
// Compute time to load
Kernel.QueryPerformanceCounterFast(out tickEnd);
float timeToLoad = (float)(tickEnd - tickStart) / (float)this.m_TickFrequency;
// Show information to user
if (!silent)
{
string info = "{0}: {2} (loaded in {1} seconds)";
if (image.Name == null || image.Name.Length == 0)
info = "{0}: (loaded in {1} seconds)";
string infoFormatted = string.Format(info, image.Metadata["TextureType"], timeToLoad, Path.GetFileName(image.Name));
this.ParentApplication.ScriptConsole.WriteLine(infoFormatted);
}
}
///
/// Bind to the texture of the given image.
///
///
/// Set to true to ensure the texture is made active.
private void BindTexture(itk.itkImageBase image, bool makeActive)
{
Gl.glEnable(Gl.GL_TEXTURE_3D_EXT); // Enable
{
// Make active
if (makeActive)
Gl.glActiveTextureARB((int)image.Metadata["TextureUnit"]);
// Bind
Gl.glBindTexture(Gl.GL_TEXTURE_3D_EXT, (int)image.Metadata["TextureName"]);
// Set minification and magnification parameters
Gl.glTexParameteri(Gl.GL_TEXTURE_3D_EXT, Gl.GL_TEXTURE_MAG_FILTER, (int)image.Metadata["TextureInterpolation"]);
Gl.glTexParameteri(Gl.GL_TEXTURE_3D_EXT, Gl.GL_TEXTURE_MIN_FILTER, (int)image.Metadata["TextureInterpolation"]);
// Set general parameters
if (String.Compare(image.Metadata["TextureType"] as String, "TransferFunction", true) == 0)
{
Gl.glTexParameteri(Gl.GL_TEXTURE_3D_EXT, Gl.GL_TEXTURE_WRAP_S, Gl.GL_CLAMP_TO_EDGE);
Gl.glTexParameteri(Gl.GL_TEXTURE_3D_EXT, Gl.GL_TEXTURE_WRAP_T, Gl.GL_CLAMP_TO_EDGE);
Gl.glTexParameteri(Gl.GL_TEXTURE_3D_EXT, Gl.GL_TEXTURE_WRAP_R, Gl.GL_CLAMP_TO_EDGE);
}
else
{
Gl.glTexParameteri(Gl.GL_TEXTURE_3D_EXT, Gl.GL_TEXTURE_WRAP_S, Gl.GL_CLAMP);
Gl.glTexParameteri(Gl.GL_TEXTURE_3D_EXT, Gl.GL_TEXTURE_WRAP_T, Gl.GL_CLAMP);
Gl.glTexParameteri(Gl.GL_TEXTURE_3D_EXT, Gl.GL_TEXTURE_WRAP_R, Gl.GL_CLAMP);
}
}
Gl.glDisable(Gl.GL_TEXTURE_3D_EXT); // Disable
}
///
/// Unbind the texture for the given image.
///
///
protected virtual void UnbindTexture(itk.itkImageBase image)
{
Gl.glEnable(Gl.GL_TEXTURE_3D_EXT); // Enable
{
// Make active
Gl.glActiveTextureARB((int)image.Metadata["TextureUnit"]);
// Unbind
Gl.glBindTexture(Gl.GL_TEXTURE_3D_EXT, 0);
}
Gl.glDisable(Gl.GL_TEXTURE_3D_EXT); // Disable
}
///
/// Set the texture units of the input images and transfer
/// function within the shader programs.
///
private void SetShaderTextureUnits()
{
if (this.Shader != null)
{
if (this.ContainsMetadata("TransferFunction"))
this.Shader.SetTextureUnit("sam3Tex0", 0);
if (this.ContainsMetadata("ImageValue"))
this.Shader.SetTextureUnit("sam3Tex1", 1);
if (this.ContainsMetadata("ImageGradient"))
this.Shader.SetTextureUnit("sam3Tex2", 2);
if (this.ContainsMetadata("ImageExtra1"))
this.Shader.SetTextureUnit("sam3Tex3", 3);
if (this.ContainsMetadata("ImageExtra2"))
this.Shader.SetTextureUnit("sam3Tex4", 4);
}
}
///
/// Compute the texture internal format from the given pixel type.
///
///
///
private int ComputeTextureInternalFormat(itk.itkPixelType type)
{
// Get the number of bytes per element
uint numBytesPerComponents = type.PixelSize;
numBytesPerComponents /= type.NumberOfComponentsPerPixel;
// Get the internal format
switch (type.NumberOfComponentsPerPixel)
{
case 1:
if (numBytesPerComponents == 8)
return Gl.GL_ALPHA8;
else if (numBytesPerComponents == 16)
return Gl.GL_ALPHA16;
break;
case 2:
if (numBytesPerComponents == 8)
return Gl.GL_LUMINANCE8_ALPHA8;
else if (numBytesPerComponents == 16)
return Gl.GL_LUMINANCE16_ALPHA16;
break;
case 3:
if (numBytesPerComponents == 8)
return Gl.GL_RGB8;
else if (numBytesPerComponents == 16)
return Gl.GL_RGB16;
break;
case 4:
if (numBytesPerComponents == 8)
return Gl.GL_RGBA8;
else if (numBytesPerComponents == 16)
return Gl.GL_RGBA16;
break;
}
// If we get here the type is not supported
throw new NotSupportedException("Could not compute the internal texture format: the given pixel type '" + type.TypeAsEnum.ToString() + " with " + type.NumberOfComponentsPerPixel.ToString() + " components is not supported.");
}
///
/// Compute the texture format from the given pixel type.
///
/// The pixel type to compute the format.
/// True and the RGB components should be swapped to BGR.
///
private int ComputeTextureFormat(itk.itkPixelType type, bool swapColorComponents)
{
// Get the internal format
switch (type.NumberOfComponentsPerPixel)
{
case 1:
return Gl.GL_ALPHA;
case 2:
return Gl.GL_LUMINANCE_ALPHA;
case 3:
return (swapColorComponents == false) ? Gl.GL_RGB : Gl.GL_BGR;
case 4:
return (swapColorComponents == false) ? Gl.GL_RGBA : Gl.GL_BGRA;
default:
throw new NotSupportedException("Could not compute the internal texture format: " + type.NumberOfComponentsPerPixel.ToString() + " components per pixel is not supported.");
}
}
///
/// Compute the texture data type from the given pixel type.
///
///
///
private int ComputeTextureDataType(itk.itkPixelTypeEnum type)
{
switch (type)
{
case (itk.itkPixelTypeEnum.UnsignedChar):
return Gl.GL_UNSIGNED_BYTE;
case (itk.itkPixelTypeEnum.SignedChar):
return Gl.GL_BYTE;
case (itk.itkPixelTypeEnum.UnsignedShort):
return Gl.GL_UNSIGNED_SHORT;
case (itk.itkPixelTypeEnum.SignedShort):
return Gl.GL_SHORT;
case (itk.itkPixelTypeEnum.UnsignedLong):
return Gl.GL_UNSIGNED_INT;
case (itk.itkPixelTypeEnum.SignedLong):
return Gl.GL_INT;
case (itk.itkPixelTypeEnum.Float):
return Gl.GL_FLOAT;
case (itk.itkPixelTypeEnum.Double):
return Gl.GL_DOUBLE;
default:
throw new NotSupportedException("The given pixel type '" + type.ToString() + " is not currently supported.");
}
}
//=====================================================================
#endregion
#region Texture Rendering Methods
//=====================================================================
///
/// Sets shader. On failure, this method reverts to the first laoded shader
/// which is assumed to be valid.
///
/// A valid path to a fragment shader.
public void SetShader(string pathFragment)
{
try
{
// Check value is not null or empty
if (pathFragment == null || pathFragment.Length == 0)
throw new ArgumentNullException("FragmentProgramPath", "The path can not be null or empty.");
// Check the vertex program exists
string pathVertex = Path.Combine(Path.GetDirectoryName(pathFragment), Path.GetFileNameWithoutExtension(pathFragment) + ".vert");
if (!File.Exists(pathVertex))
throw new FileNotFoundException("Could not find the vertex shader program file.", pathVertex);
// Check if we should make this the default fragment shader
if (this.m_DefaultFragmentProgramPath == null)
this.m_DefaultFragmentProgramPath = pathFragment;
// Force the shader to end
this.Shader.End();
this.IsModified = false;
// Set the fragment and vertex program paths
this.Shader.FragmentProgramPath = pathFragment;
this.Shader.VertexProgramPath = pathVertex;
// Re-initialise the shader and repaint
this.Shader.Initialise();
this.SetShaderTextureUnits();
this.SamplingRateChanged(this.Properties.SamplingRate);
this.Properties.NormalOffset = this.Properties.NormalOffset;
this.Properties.Factor1 = this.Properties.Factor1;
this.Properties.Factor2 = this.Properties.Factor2;
this.Properties.Factor3 = this.Properties.Factor3;
// Watch the file for further changes
this.m_ShaderWatcher.Path = Path.GetDirectoryName(pathFragment);
this.m_ShaderWatcher.Filter = Path.GetFileName(pathFragment);
}
catch (Exception ex)
{
string text = "Error applying shader program (see details below).\r\n";
text += "Using default shader program: " + this.m_DefaultFragmentProgramPath + "\r\n";
text += ex.ToString();
string caption = "Error applying shader program";
MessageBox.Show(this.ParentApplication as IWin32Window, text, caption, MessageBoxButtons.OK, MessageBoxIcon.Error);
// Use the old path
// NOTE: If the old path also fails, the app will probably crash...
this.SetShader(m_DefaultFragmentProgramPath);
}
}
void watcher_Changed(object sender, FileSystemEventArgs e)
{
// Make thread safe
if (this.Form != null && this.Form.InvokeRequired)
{
FileSystemEventHandler method = new FileSystemEventHandler(this.watcher_Changed);
Object[] args = new Object[] { sender, e };
this.Form.Invoke(method, args);
return;
}
// Check that some time has elapsed since the last change
TimeSpan diff = new TimeSpan(DateTime.Now.Ticks).Subtract(this.m_ShaderLastChanged);
if (!this.m_IsAskingUserToReloadShader && diff.Seconds > SHADER_WATCHER_TIME)
{
this.m_IsAskingUserToReloadShader = true;
String text = "The shader '" + e.Name + "' has been changed. Do you want to reload the shader?";
String caption = "Reload shader?";
DialogResult result = MessageBox.Show(this.Form, text, caption, MessageBoxButtons.YesNo, MessageBoxIcon.Question);
if (result == DialogResult.Yes)
{
this.Properties.FragmentProgramPath = e.FullPath;
this.m_ShaderLastChanged = new TimeSpan(DateTime.Now.Ticks);
}
this.m_IsAskingUserToReloadShader = false;
}
}
///
/// Renders the geometry which will interpolate the uploaded textures.
/// This method assumes that the input images are tagged with
/// "Value": for a value image
/// "Gradient": for a gradient image
/// "Extra1": extra data
/// "Extra2": extra data.
///
///
private void RenderTextureGeometry()
{
// Only render geometry if input images are available
if (!this.ContainsMetadata("ImageValue"))
return;
// Bind textures
this.BindTextures();
// Get the image texture units
int texunitValue = this.ContainsMetadata("ImageValue") ? this.GetImageTextureUnit("ImageValue") : 0;
int texunitGradient = this.ContainsMetadata("ImageGradient") ? this.GetImageTextureUnit("ImageGradient") : 0;
int texunitExtra1 = this.ContainsMetadata("ImageExtra1") ? this.GetImageTextureUnit("ImageExtra1") : 0;
int texunitExtra2 = this.ContainsMetadata("ImageExtra2") ? this.GetImageTextureUnit("ImageExtra2") : 0;
// Enable Blending
Gl.glEnable(Gl.GL_BLEND);
switch (this.Properties.Blending)
{
case siVolumeBlendEnum.BackToFront:
Gl.glBlendEquationEXT(Gl.GL_FUNC_ADD_EXT);
Gl.glBlendFunc(Gl.GL_SRC_ALPHA, Gl.GL_ONE_MINUS_SRC_ALPHA);
break;
case siVolumeBlendEnum.MaximumProjection:
Gl.glBlendEquationEXT(Gl.GL_MAX_EXT);
//Gl.glBlendFunc(Gl.GL_ONE, Gl.GL_ONE);
Gl.glBlendFunc(Gl.GL_SRC_ALPHA, Gl.GL_ONE_MINUS_SRC_ALPHA);
break;
case siVolumeBlendEnum.MinimumProjection:
Gl.glBlendEquationEXT(Gl.GL_MIN_EXT);
//Gl.glBlendFunc(Gl.GL_ONE, Gl.GL_ONE);
Gl.glBlendFunc(Gl.GL_ZERO, Gl.GL_ZERO);
break;
}
// Push the matrix
Gl.glMatrixMode(Gl.GL_MODELVIEW);
Gl.glPushMatrix();
// Enable and setup lighting
Gl.glEnable(Gl.GL_LIGHTING);
Gl.glEnable(Gl.GL_LIGHT0);
Gl.glLightfv(Gl.GL_LIGHT0, Gl.GL_AMBIENT, siColorHelper.To4fv(this.Properties.LightColorAmbient));
Gl.glLightfv(Gl.GL_LIGHT0, Gl.GL_DIFFUSE, siColorHelper.To4fv(this.Properties.LightColorDiffuse));
Gl.glLightfv(Gl.GL_LIGHT0, Gl.GL_SPECULAR, siColorHelper.To4fv(this.Properties.LightColorSpecular));
Gl.glLightf(Gl.GL_LIGHT0, Gl.GL_SPOT_EXPONENT, this.Properties.LightColorSpecularExponent);
// Translate into middle of bounding box
itk.itkArray size = this.Properties.ModelSize;
Gl.glTranslated(-0.5 * size[0], -0.5 * size[1], -0.5 * size[2]);
// Begin the geometry generator
this.m_TexureGeometryGenerator.Begin(this.Properties.TrackBall.RotationMatrix, this.Properties.SamplingRate.Current);
Gl.glEnable(Gl.GL_TEXTURE_3D_EXT); // Enable
{
do
{
// Get the line loop points
itk.itkPoint[] points = this.m_TexureGeometryGenerator.GetCurrentLineLoop();
if (points == null || points.Length == 0)
continue; // No geometry
// Render the wire frame
if (this.Properties.WireFrame)
{
this.UnbindTextures();
Gl.glDisable(Gl.GL_LIGHTING);
Gl.glEnable(Gl.GL_BLEND);
Gl.glBlendFunc(Gl.GL_SRC_ALPHA, Gl.GL_ONE_MINUS_SRC_ALPHA);
Gl.glDisable(Gl.GL_DEPTH_TEST);
Gl.glBegin(Gl.GL_LINE_LOOP); // Render line loop
{
Gl.glColor3fv(siColorHelper.To3fv(this.Properties.BoxOutlineColor));
for (int i = 0; i < points.Length; i++)
Gl.glVertex3dv(points[i].Data);
}
Gl.glEnd();
Gl.glEnable(Gl.GL_LIGHTING);
this.BindTextures();
}
// Turn shader on
this.Shader.Begin();
// Render the texture interpolation geometry
Gl.glBegin(Gl.GL_TRIANGLE_FAN); // Render triangle fan
{
for (int i = 0; i < points.Length; i++)
{
double[] pointOut = new double[3] { 0.0, 0.0, 0.0 };
double[] pointTex = new double[3] { 0.0, 0.0, 0.0 };
for (int j = 0; j < 3; j++)
{
pointOut[j] = points[i][j];
pointTex[j] = pointOut[j] / size[j];
}
// Convert to the OpenGL texture coordinate system by
// flipping along the z-axis
pointTex[2] = 1.0 - pointTex[2];
// Output the texture coord
if (this.ContainsMetadata("ImageValue"))
Gl.glMultiTexCoord3dvARB(texunitValue, pointTex);
if (this.ContainsMetadata("ImageGradient"))
Gl.glMultiTexCoord3dvARB(texunitGradient, pointTex);
if (this.ContainsMetadata("ImageExtra1"))
Gl.glMultiTexCoord3dvARB(texunitExtra1, pointTex);
if (this.ContainsMetadata("ImageExtra2"))
Gl.glMultiTexCoord3dvARB(texunitExtra2, pointTex);
// Output the vertex coord
Gl.glVertex3dv(pointOut);
}
}
Gl.glEnd();
// Turn shader off
this.Shader.End();
}
while (!this.m_TexureGeometryGenerator.End());
}
Gl.glDisable(Gl.GL_TEXTURE_3D_EXT); //Disable
// Pop
Gl.glPopMatrix();
// Disable blending
Gl.glBlendEquationEXT(Gl.GL_FUNC_ADD_EXT);
Gl.glDisable(Gl.GL_BLEND);
// Disable lighting
Gl.glDisable(Gl.GL_LIGHT0);
Gl.glDisable(Gl.GL_LIGHTING);
// Unbind textures
this.UnbindTextures();
// End the shader
if (this.Shader != null) this.Shader.End();
}
private void BindTextures()
{
// Bind textures
if (this.ContainsMetadata("ImageValue"))
this.BindTexture(this.GetMetadataAsImage("ImageValue"), true);
if (this.ContainsMetadata("ImageGradient"))
this.BindTexture(this.GetMetadataAsImage("ImageGradient"), true);
if (this.ContainsMetadata("ImageExtra1"))
this.BindTexture(this.GetMetadataAsImage("ImageExtra1"), true);
if (this.ContainsMetadata("ImageExtra2"))
this.BindTexture(this.GetMetadataAsImage("ImageExtra2"), true);
if (this.ContainsMetadata("TransferFunction"))
this.BindTexture(this.TransferFunction.Image, true);
}
private void UnbindTextures()
{
// Bind textures
if (this.ContainsMetadata("ImageValue"))
this.UnbindTexture(this.GetMetadataAsImage("ImageValue"));
if (this.ContainsMetadata("ImageGradient"))
this.UnbindTexture(this.GetMetadataAsImage("ImageGradient"));
if (this.ContainsMetadata("ImageExtra1"))
this.UnbindTexture(this.GetMetadataAsImage("ImageExtra1"));
if (this.ContainsMetadata("ImageExtra2"))
this.UnbindTexture(this.GetMetadataAsImage("ImageExtra2"));
if (this.ContainsMetadata("TransferFunction") && this.TransferFunction.Image != null)
this.UnbindTexture(this.TransferFunction.Image);
}
///
/// Get the texture unit for the image stored in this.Metadata[key].
/// No checking is done to ensure the metadata keys exists or are not null.
///
/// The key of the image to get the texture unit for.
///
private int GetImageTextureUnit(string key)
{
return ((int)this.GetMetadataAsImage(key).Metadata["TextureUnit"]);
}
///
/// Handle the siSamplingRate.Changed event.
///
///
public void SamplingRateChanged(siSamplingRate rate)
{
if (this.ContainsMetadata("Shader"))
{
this.Shader.SetUniformFloat("fSamplingRate", rate.Current);
this.RaiseModified();
this.Repaint();
}
}
private void Form_ResizeBegin(object sender, EventArgs e)
{
this.Properties.SamplingRate.MakeCurrentLowQuality();
}
private void Form_ResizeEnd(object sender, EventArgs e)
{
this.Properties.SamplingRate.MakeCurrentHighQuality();
}
//=====================================================================
#endregion
#region Event Handler Methods
//=====================================================================
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
// Check we have rendered
if (!this.m_HasRenderedFirstTime)
return;
// Change the sampling rate
this.Properties.SamplingRate.MakeCurrentLowQuality();
// Save the last screen mouse down
this.Metadata["LastScreenMouseDown"] = e;
// Check if a change of Metadata is required
if (e.Button == MouseButtons.Right)
{
this.Metadata["IsTranslating"] = true;
this.Cursor = Cursors.Hand;
}
else if (e.Button == MouseButtons.Left)
{
this.Metadata["IsRotating"] = true;
this.Cursor = Cursors.Arrow;
int width = (int)this.Metadata["RenderingControlWidth"];
int height = (int)this.Metadata["RenderingControlHeight"];
this.Properties.TrackBall.StartRotation(e.X, e.Y, width, height);
}
}
protected override void OnMouseUp(MouseEventArgs e)
{
base.OnMouseUp(e);
// Cancel translation
if (e.Button == MouseButtons.Right)
{
this.Metadata["IsTranslating"] = false;
this.Cursor = Cursors.Default;
}
else if (e.Button == MouseButtons.Left)
{
this.Metadata["IsRotating"] = false;
this.Cursor = Cursors.Default;
this.Properties.TrackBall.StopRotation();
}
// Change the sampling rate
this.Properties.SamplingRate.MakeCurrentHighQuality();
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
// Check we have rendered
if (!this.m_HasRenderedFirstTime)
return;
// Save the old cursor value
Cursor cursor = this.Cursor;
// Check the Metadata of the mouse move
if ((bool)this.Metadata["IsTranslating"])
{
// We are translating the volume
MouseEventArgs last = this.Metadata["LastScreenMouseMove"] as MouseEventArgs;
cursor = Cursors.Hand;
// Update transform
this.Properties.TransformTranslate[0] += (double)(e.X - last.X) * 0.005;
this.Properties.TransformTranslate[1] -= (double)(e.Y - last.Y) * 0.005;
// Force a repaint
this.Repaint();
}
else if ((bool)this.Metadata["IsRotating"])
{
// We are rotating the volume
cursor = Cursors.Arrow;
// Update transform
int width = (int)this.Metadata["RenderingControlWidth"];
int height = (int)this.Metadata["RenderingControlHeight"];
this.Properties.TrackBall.TrackRotation(e.X, e.Y, width, height);
// Force a repaint
this.Repaint();
}
// Set the form cursor
if (this.Cursor != Cursors.WaitCursor)
{
if (this.ContainsMetadata("Cursor"))
this.Cursor = (Cursor)this.Metadata["Cursor"];
else
this.Cursor = cursor;
}
// Save the last screen mouse move
this.Metadata["LastScreenMouseMove"] = e;
}
protected override void OnMouseWheel(MouseEventArgs e)
{
// Compute the wheel delta for GL
double glDelta = (double)e.Delta / 2400.0;
// Check if a key is down
KeyEventArgs key = null;
if (this.Metadata.ContainsKey("CurrentKey"))
key = this.Metadata["CurrentKey"] as KeyEventArgs;
// Change what we are doing based on key down
if (key != null && key.Control)
{
// Control, change slice position
this.m_TexureGeometryGenerator.GeometrySliceDepth += glDelta * 0.1;
}
else if (key != null && key.Shift)
{
// Control, change slice thickness
this.m_TexureGeometryGenerator.GeometrySliceThickness += glDelta * 0.1;
}
else
{
// No key, zoom
this.Properties.FrustrumLeft *= 1 + glDelta;
this.Properties.FrustrumRight *= 1 + glDelta;
this.Properties.FrustrumBottom *= 1 + glDelta;
this.Properties.FrustrumTop *= 1 + glDelta;
}
this.Repaint();
}
//=====================================================================
#endregion
#region General Helper Methods
//=====================================================================
///
/// Returns the given Metadata variable as an itkImage.
/// Returns null if the Metadata variable name does not exist.
///
///
///
private itk.itkImageBase GetMetadataAsImage(string key)
{
if (this.ContainsMetadata(key))
return this.Metadata[key] as itk.itkImageBase;
else
return null;
}
///
/// Computes if non power of two texture sizes are supported by the graphics H/W
/// based on the presence of the GL_ARB_texture_non_power_of_two extension.
///
///
private bool IsNonPowerOfTwoTextureSizeSupported()
{
return Gl.IsExtensionSupported("GL_ARB_texture_non_power_of_two");
}
//=====================================================================
#endregion
}
}