/* THIS FILE IS BASED ON THE WORK OF OPENQVIS * (http://sourceforge.net/projects/openqvis/) * ************************************************************************* * (C) Copyright 2000-2002 Christof Rezk-Salama * * * * Contact: Dr.-Ing. Christof Rezk-Salama * * Computer Graphics Group, * * Computer Science Department * * University of Erlangen-Nuremberg * * Am Weichselgarten 9, * * D-91058 Erlangen, Germany * * http://www9.informatik.uni-erlangen.de/~rezk * * mailto;rezk@cs.fau.fr * * * * OpenQVis is free software; you can redistribute it and/or modify it * * under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * OpenQVis is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with OpenQVis; if not, write to the Free Software Foundation, * * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * * *************************************************************************/ using System; using System.Diagnostics; using System.Collections.Generic; using System.Text; using Tao.OpenGl; using System.Drawing; using itk; using TransformType = itk.itkAffineTransform_D3; namespace SharpImage.Rendering { public class siTextureGeometryGenerator { #region Enumerations and Structs //===================================================================== [Flags] public enum siTextureBoundingBoxFace { Front = 01, Back = 02, Right = 04, Left = 08, Bottom = 16, Top = 32 } //===================================================================== #endregion #region Private Members //===================================================================== private siVolumeGeometryEnum m_GeometryType = siVolumeGeometryEnum.ViewAlignedFill; private TransformType m_Transform = null; private TransformType m_TransformInv = null; private itkSize DataSize = null; private itkSpacing DataSpacing = null; private itkArray ModelSize = null; private itkPoint[] TextureBoundingBoxModel = null; private itkPoint[] TextureBoundingBoxWorld = null; private itkPoint[][] TextureBoundingBoxModelLines = null; private siTextureBoundingBoxFace[] TextureBoundingBoxModelEdges = null; private siPlane Plane = null; private itkPoint[] TexturePlaneModel = null; private itkPoint[] TexturePlaneWorld = null; private double OffsetBetweenSlices = 0.1; private double CurrentSlice = 0; private double NumberOfSlices = 0; private itkPoint PointFar = null; private itkPoint PointNear = null; private double DistanceBetweenNearAndFar = 0.0; private int OrthogonalCount = 0; private double m_GeometrySliceDepth = 0.5; private double m_GeometrySliceThickness = 0.01; //===================================================================== #endregion #region Construction and Disposal //===================================================================== /// /// Construct the generator. /// public siTextureGeometryGenerator() { this.m_Transform = TransformType.New(); this.m_TransformInv = TransformType.New(); } //===================================================================== #endregion #region Properties //===================================================================== /// /// Gets/sets the geometry mode. /// public siVolumeGeometryEnum GeometryType { get { return this.m_GeometryType; } set { this.m_GeometryType = value; } } /// /// Get/set the geometry slice depth value used by /// Geometry mode ViewAlignedSingle and ViewAlignedSlab. /// public double GeometrySliceDepth { get { return this.m_GeometrySliceDepth; } set { this.m_GeometrySliceDepth = value; } } /// /// Get/set the geometry slice thickness value used by /// Geometry mode ViewAlignedSlab. /// public double GeometrySliceThickness { get { return this.m_GeometrySliceThickness; } set { this.m_GeometrySliceThickness = value; } } //===================================================================== #endregion #region Public Methods //===================================================================== /// /// Initialise the generator. This method creates /// TextureBoundingBoxModel, TextureBoundingBoxLines, and /// TextureBoundingBoxEdges arrays. /// public void Initialise(itkSize dataSize, itkSpacing dataSpacing, itkArray modelSize) { this.DataSize = dataSize; this.DataSpacing = dataSpacing; this.ModelSize = modelSize; this.SetupTextureBoundingBox(); this.SetupTexturePlane(); this.SetupTextureBoundingBoxLines(); this.SetupTextureBoundingBoxEdges(); } /// /// Setup the generator for the current rotation. /// /// The current rotation matrix which will be applied to the model bounding box. /// Controls how many polygons intersect with the texture bounding box. 1.0 means 1 polygon for every pixel. 2.0 means 2 polygons for every pixel. 0.5 means 0.5 polygons for every pixel. public void Begin(itk.itkMatrix rotation, double samplingRate) { // Check the geometry type switch (GeometryType) { case siVolumeGeometryEnum.Orthogonal: // Reset counter this.OrthogonalCount = 0; return; case siVolumeGeometryEnum.ViewAlignedFill: // Fall through case siVolumeGeometryEnum.ViewAlignedSingle: // Fall through case siVolumeGeometryEnum.ViewAlignedSlab: // Execute code below break; } // Construct transforms from the rotation matrix m_Transform.Matrix = rotation; m_Transform.GetInverse(m_TransformInv); // Init the near-point and far-point with 'large' values // NOTE: These values will be replaced in the loop following this.PointNear = new itkPoint(0.0, 0.0, 10000.0); this.PointFar = new itkPoint(0.0, 0.0, -10000.0); // Compute the texture bounding box model in world coordinates for (int i = 0; i < 8; i++) { // Transform the model coords to world coords TextureBoundingBoxWorld[i] = m_TransformInv.TransformPoint(TextureBoundingBoxModel[i]); if (TextureBoundingBoxWorld[i][2] < this.PointNear[2]) this.PointNear = new itkPoint(TextureBoundingBoxWorld[i]); if (TextureBoundingBoxWorld[i][2] > this.PointFar[2]) this.PointFar = new itkPoint(TextureBoundingBoxWorld[i]); } // Transform the plane from model to world space for (int i = 0; i < 3; i++) { itkVector temp = this.TexturePlaneModel[i] + this.PointFar; this.TexturePlaneWorld[i] = m_Transform.TransformPoint(new itkPoint(temp.Data)); } // Create the plane this.Plane = new siPlane(this.TexturePlaneWorld); // Position the plane if (this.GeometryType == siVolumeGeometryEnum.ViewAlignedSingle || this.GeometryType == siVolumeGeometryEnum.ViewAlignedSlab) { this.Plane.Offset(this.GeometrySliceDepth); } // Compute distance between near and far point this.DistanceBetweenNearAndFar = this.PointNear.EuclideanDistanceTo(this.PointFar); // Compute the resolution double resolution = this.ComputeResolution(); // Compute the distance between slices and the number of slices itkVector vecData = new itkVector(this.DataSize.Length); for (int i = 0; i < this.DataSize.Length; i++) vecData[i] = (double)this.DataSize[i]; double vecDataNorm = vecData.GetNorm() / 2.0; this.OffsetBetweenSlices = resolution / (vecDataNorm * samplingRate); this.NumberOfSlices = 1; if (this.GeometryType == siVolumeGeometryEnum.ViewAlignedFill) this.NumberOfSlices = Math.Ceiling(this.DistanceBetweenNearAndFar / this.OffsetBetweenSlices); else if (this.GeometryType == siVolumeGeometryEnum.ViewAlignedSingle) this.NumberOfSlices = 1; else if (this.GeometryType == siVolumeGeometryEnum.ViewAlignedSlab) this.NumberOfSlices = Math.Ceiling(this.GeometrySliceThickness / this.OffsetBetweenSlices); this.CurrentSlice = 0; } /// /// Compute the next set of triangle fan points using the Rezk-Salama method. /// /// An array of points constituting the view-aligned polygon intersecting the texture bounding box. Returns null if no intersections. public itkPoint[] GetCurrentLineLoop() { // Check the geometry type switch (GeometryType) { case siVolumeGeometryEnum.Orthogonal: return this.GetCurrentLineLoopOrthogonal(); case siVolumeGeometryEnum.ViewAlignedFill: return this.GetCurrentLineLoopViewAligned(); case siVolumeGeometryEnum.ViewAlignedSingle: return this.GetCurrentLineLoopViewAligned(); case siVolumeGeometryEnum.ViewAlignedSlab: return this.GetCurrentLineLoopViewAligned(); } return null; } /// /// Offset to the next point. /// /// True and no more geometry to render, false if more geometry to render. public bool End() { // Check the geometry type switch (GeometryType) { case siVolumeGeometryEnum.Orthogonal: // Render three slices return this.OrthogonalCount == 4; case siVolumeGeometryEnum.ViewAlignedSingle: // Only render 1 slice return true; case siVolumeGeometryEnum.ViewAlignedFill: // Fall through case siVolumeGeometryEnum.ViewAlignedSlab: // Render the correct number of slices if (this.CurrentSlice > this.NumberOfSlices) { return true; } else { this.CurrentSlice++; this.Plane.Offset(this.OffsetBetweenSlices); return false; } } return true; } //===================================================================== #endregion #region Private Helper Methods //===================================================================== private itkPoint[] GetCurrentLineLoopOrthogonal() { itkPoint[] p = new itkPoint[4]; switch (this.OrthogonalCount) { case 0: p[0] = new itkPoint(0.0, 0.0, this.ModelSize[2] / 2.0); p[1] = new itkPoint(this.ModelSize[0], 0.0, this.ModelSize[2] / 2.0); p[2] = new itkPoint(this.ModelSize[0], this.ModelSize[1], this.ModelSize[2] / 2.0); p[3] = new itkPoint(0.0, this.ModelSize[1], this.ModelSize[2] / 2.0); break; case 1: p[0] = new itkPoint(0.0, this.ModelSize[1] / 2.0, 0.0); p[1] = new itkPoint(this.ModelSize[0], this.ModelSize[1] / 2.0, 0.0); p[2] = new itkPoint(this.ModelSize[0], this.ModelSize[1] / 2.0, this.ModelSize[2]); p[3] = new itkPoint(0.0, this.ModelSize[1] / 2.0, this.ModelSize[2]); break; case 2: p[0] = new itkPoint(this.ModelSize[0] / 2.0, 0.0, 0.0); p[1] = new itkPoint(this.ModelSize[0] / 2.0, this.ModelSize[1], 0.0); p[2] = new itkPoint(this.ModelSize[0] / 2.0, this.ModelSize[1], this.ModelSize[2]); p[3] = new itkPoint(this.ModelSize[0] / 2.0, 0.0, this.ModelSize[2]); break; default: p = null; break; } this.OrthogonalCount++; return p; } private itkPoint[] GetCurrentLineLoopViewAligned() { // STEP 0: Setup int countFound = 0; int countMerged = 0; int count = 0; itkPoint[] pointIntersections = new itkPoint[12]; itkPoint[] pointSortedIntersections = new itkPoint[12]; bool[] bUsedIntersections = new bool[12]; siTextureBoundingBoxFace[] edges = new siTextureBoundingBoxFace[12]; //STEP 1: Find all edge intersections for (int indexEdge = 0; indexEdge < 12; indexEdge++) { // Init itkPoint pointIntersect = new itkPoint(3U); itkPoint p0 = this.TextureBoundingBoxModelLines[indexEdge][0]; itkPoint p1 = this.TextureBoundingBoxModelLines[indexEdge][1]; // Test if intersection occured if (this.Plane.Intersect(p1, p0, out pointIntersect)) { pointIntersections[count] = pointIntersect; edges[count] = this.TextureBoundingBoxModelEdges[indexEdge]; bUsedIntersections[count] = false; count++; } } // end for i countFound = count; //STEP 2: Eliminate duplicate points double tolerance = this.OffsetBetweenSlices / 100.0; if (count > 2) { for (int i = 0; i < (count - 1); i++) { for (int j = (i + 1); j < count; j++) { itkVector diffIntersection = pointIntersections[i] - pointIntersections[j]; if (diffIntersection.GetNorm() < tolerance) { // Merge the two intersections edges[i] |= edges[j]; // Remove j because it was merged if (j < count - 1) { pointIntersections[j] = pointIntersections[count - 1]; edges[j] = edges[count - 1]; j--; } count--; } // end if |diff| < tolerance } // end for j } // end for i }// end if (index > 2) countMerged = count; //STEP 3: Sort intersection points int countSorted = 0; if (count > 2) { pointSortedIntersections[0] = pointIntersections[0]; bUsedIntersections[0] = true; countSorted = 1; siTextureBoundingBoxFace edge = edges[0]; while (countSorted != count) { for (int i = 1; i < count; i++) { // The sequence is valid if the edge codes of both points have one bit in common int test = (int)(edges[i] & edge); bool hasEdgeInCommon = (test != 0); if ((bUsedIntersections[i] == false) && (hasEdgeInCommon)) { pointSortedIntersections[countSorted] = pointIntersections[i]; edge = edges[i]; bUsedIntersections[i] = true; countSorted++; } } // end for i } //end while } //end (index > 2) // Trim the sorted intersections and return if (count > 2) { itk.itkPoint[] result = new itkPoint[count]; for (int i = 0; i < count; i++) result[i] = pointSortedIntersections[i]; return result; } // No geometry return null; } /// /// Creates the model and empty world coords of the texture bounding box. /// private void SetupTextureBoundingBox() { TextureBoundingBoxModel = new itkPoint[8]{ new itkPoint(0.0, 0.0, 0.0), new itkPoint(this.ModelSize[0], 0.0, 0.0), new itkPoint(this.ModelSize[0], this.ModelSize[1], 0.0), new itkPoint(0.0, this.ModelSize[1], 0.0), new itkPoint(0.0, 0.0, this.ModelSize[2]), new itkPoint(this.ModelSize[0], 0.0, this.ModelSize[2]), new itkPoint(this.ModelSize[0], this.ModelSize[1], this.ModelSize[2]), new itkPoint(0.0, this.ModelSize[1], this.ModelSize[2]) }; TextureBoundingBoxWorld = new itkPoint[8]{ new itkPoint(0.0, 0.0, 0.0), new itkPoint(0.0, 0.0, 0.0), new itkPoint(0.0, 0.0, 0.0), new itkPoint(0.0, 0.0, 0.0), new itkPoint(0.0, 0.0, 0.0), new itkPoint(0.0, 0.0, 0.0), new itkPoint(0.0, 0.0, 0.0), new itkPoint(0.0, 0.0, 0.0) }; } /// /// Creates the model and empty world coords of the texture bounding box. /// private void SetupTexturePlane() { TexturePlaneModel = new itkPoint[4]{ new itkPoint(0.0, 0.0, 0.0), new itkPoint(1.0, 0.0, 0.0), new itkPoint(1.0, 1.0, 0.0), new itkPoint(0.0, 1.0, 0.0) }; TexturePlaneWorld = new itkPoint[4]{ new itkPoint(0.0, 0.0, 0.0), new itkPoint(0.0, 0.0, 0.0), new itkPoint(0.0, 0.0, 0.0), new itkPoint(0.0, 0.0, 0.0) }; } /// /// Compute the texture bounding box model lines. /// private void SetupTextureBoundingBoxLines() { TextureBoundingBoxModelLines = new itkPoint[][]{ new itkPoint[]{ TextureBoundingBoxModel[4], TextureBoundingBoxModel[5] }, //FrontBottom new itkPoint[]{ TextureBoundingBoxModel[6], TextureBoundingBoxModel[7] }, //FrontTop new itkPoint[]{ TextureBoundingBoxModel[0], TextureBoundingBoxModel[1] }, //BackBottom new itkPoint[]{ TextureBoundingBoxModel[2], TextureBoundingBoxModel[3] }, //BackTop new itkPoint[]{ TextureBoundingBoxModel[4], TextureBoundingBoxModel[7] }, //FrontRight new itkPoint[]{ TextureBoundingBoxModel[5], TextureBoundingBoxModel[6] }, //FrontLeft new itkPoint[]{ TextureBoundingBoxModel[1], TextureBoundingBoxModel[2] }, //BackLeft new itkPoint[]{ TextureBoundingBoxModel[0], TextureBoundingBoxModel[3] }, //BackRight new itkPoint[]{ TextureBoundingBoxModel[0], TextureBoundingBoxModel[4] }, //RightBottom new itkPoint[]{ TextureBoundingBoxModel[3], TextureBoundingBoxModel[7] }, //RightTop new itkPoint[]{ TextureBoundingBoxModel[2], TextureBoundingBoxModel[6] }, //TopLeft new itkPoint[]{ TextureBoundingBoxModel[1], TextureBoundingBoxModel[5] } //BottomLeft }; } /// /// Compute the texture bounding box model edges. /// private void SetupTextureBoundingBoxEdges() { TextureBoundingBoxModelEdges = new siTextureBoundingBoxFace[]{ ( siTextureBoundingBoxFace.Front | siTextureBoundingBoxFace.Bottom ), ( siTextureBoundingBoxFace.Front | siTextureBoundingBoxFace.Top ), ( siTextureBoundingBoxFace.Back | siTextureBoundingBoxFace.Bottom ), ( siTextureBoundingBoxFace.Back | siTextureBoundingBoxFace.Top ), ( siTextureBoundingBoxFace.Front | siTextureBoundingBoxFace.Right ), ( siTextureBoundingBoxFace.Front | siTextureBoundingBoxFace.Left ), ( siTextureBoundingBoxFace.Back | siTextureBoundingBoxFace.Left ), ( siTextureBoundingBoxFace.Back | siTextureBoundingBoxFace.Right ), ( siTextureBoundingBoxFace.Right | siTextureBoundingBoxFace.Bottom ), ( siTextureBoundingBoxFace.Right | siTextureBoundingBoxFace.Top ), ( siTextureBoundingBoxFace.Top | siTextureBoundingBoxFace.Left ), ( siTextureBoundingBoxFace.Bottom | siTextureBoundingBoxFace.Left ) }; } private double ComputeResolution() { if (this.DataSpacing[0] == this.DataSpacing[1]) return this.DataSpacing[2] / this.DataSpacing[0]; else if (this.DataSpacing[1] == this.DataSpacing[2]) return this.DataSpacing[0] / this.DataSpacing[1]; else if (this.DataSpacing[0] == this.DataSpacing[2]) return this.DataSpacing[1] / this.DataSpacing[0]; else return this.DataSpacing[2] / this.DataSpacing[0]; } //===================================================================== #endregion } }