/*========================================================================= Program: Visualization Toolkit Module: vtkUnstructuredGridBunykRayCastFunction.cxx Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen All rights reserved. See Copyright.txt or http://www.kitware.com/Copyright.htm for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notice for more information. =========================================================================*/ #include "vtkUnstructuredGridBunykRayCastFunction.h" #include "vtkArrayDispatch.h" #include "vtkCamera.h" #include "vtkCell.h" #include "vtkCellArray.h" #include "vtkCellIterator.h" #include "vtkCellType.h" #include "vtkColorTransferFunction.h" #include "vtkDoubleArray.h" #include "vtkFloatArray.h" #include "vtkIdList.h" #include "vtkMath.h" #include "vtkMatrix4x4.h" #include "vtkObjectFactory.h" #include "vtkPiecewiseFunction.h" #include "vtkPointData.h" #include "vtkRenderer.h" #include "vtkSmartPointer.h" #include "vtkTransform.h" #include "vtkUnstructuredGrid.h" #include "vtkUnstructuredGridVolumeRayCastIterator.h" #include "vtkUnstructuredGridVolumeRayCastMapper.h" #include "vtkVolume.h" #include "vtkVolumeProperty.h" #include #include vtkStandardNewMacro(vtkUnstructuredGridBunykRayCastFunction); #define VTK_BUNYKRCF_NUMLISTS 100000 namespace { struct TemplateCastRayWorker { vtkUnstructuredGridBunykRayCastFunction* Self; int NumComponents; int X; int Y; double FarClipZ; vtkUnstructuredGridBunykRayCastFunction::Intersection*& IntersectionPtr; vtkUnstructuredGridBunykRayCastFunction::Triangle*& CurrentTriangle; vtkIdType& CurrentTetra; vtkIdType* IntersectedCells; double* IntersectionLengths; int MaxNumIntersections; // Result: vtkIdType NumIntersections; TemplateCastRayWorker(vtkUnstructuredGridBunykRayCastFunction* self, int numComponents, int x, int y, double farClipZ, vtkUnstructuredGridBunykRayCastFunction::Intersection*& intersectionPtr, vtkUnstructuredGridBunykRayCastFunction::Triangle*& currentTriangle, vtkIdType& currentTetra, vtkIdType* intersectedCells, double* intersectedLengths, int maxNumIntersections) : Self(self) , NumComponents(numComponents) , X(x) , Y(y) , FarClipZ(farClipZ) , IntersectionPtr(intersectionPtr) , CurrentTriangle(currentTriangle) , CurrentTetra(currentTetra) , IntersectedCells(intersectedCells) , IntersectionLengths(intersectedLengths) , MaxNumIntersections(maxNumIntersections) , NumIntersections(0) { } TemplateCastRayWorker& operator=(const TemplateCastRayWorker&) = delete; // Execute the algorithm with all arrays set to nullptr. void operator()() { (*this)(static_cast*>(nullptr), static_cast*>(nullptr), static_cast*>(nullptr)); } template void operator()( ScalarArrayT* scalarArray, NearArrayT* nearIntersectionArray, FarArrayT* farIntersectionArray) { typedef typename NearArrayT::ValueType ValueType; int imageViewportSize[2]; this->Self->GetImageViewportSize(imageViewportSize); int origin[2]; this->Self->GetImageOrigin(origin); float fx = this->X - origin[0]; float fy = this->Y - origin[1]; double* points = this->Self->GetPoints(); vtkUnstructuredGridBunykRayCastFunction::Triangle** triangles = this->Self->GetTetraTriangles(); vtkMatrix4x4* viewToWorld = this->Self->GetViewToWorldMatrix(); vtkUnstructuredGridBunykRayCastFunction::Triangle* nextTriangle; vtkIdType nextTetra; this->NumIntersections = 0; double nearZ = VTK_DOUBLE_MIN; double nearPoint[4]; double viewCoords[4]; viewCoords[0] = ((float)this->X / (float)(imageViewportSize[0] - 1)) * 2.0 - 1.0; viewCoords[1] = ((float)this->Y / (float)(imageViewportSize[1] - 1)) * 2.0 - 1.0; // viewCoords[2] set when an intersection is found. viewCoords[3] = 1.0; if (this->CurrentTriangle) { // Find intersection in currentTriangle (the entry point). nearZ = -(fx * this->CurrentTriangle->A + fy * this->CurrentTriangle->B + this->CurrentTriangle->D) / this->CurrentTriangle->C; viewCoords[2] = nearZ; viewToWorld->MultiplyPoint(viewCoords, nearPoint); nearPoint[0] /= nearPoint[3]; nearPoint[1] /= nearPoint[3]; nearPoint[2] /= nearPoint[3]; } while (this->NumIntersections < this->MaxNumIntersections) { // If we have exited the mesh (or are entering it for the first time, // find the next intersection with an external face (which has already // been found with rasterization). if (!this->CurrentTriangle) { if (!this->IntersectionPtr) { break; // No more intersections. } this->CurrentTriangle = this->IntersectionPtr->TriPtr; this->CurrentTetra = this->IntersectionPtr->TriPtr->ReferredByTetra[0]; this->IntersectionPtr = this->IntersectionPtr->Next; // Find intersection in currentTriangle (the entry point). nearZ = -(fx * this->CurrentTriangle->A + fy * this->CurrentTriangle->B + this->CurrentTriangle->D) / this->CurrentTriangle->C; viewCoords[2] = nearZ; viewToWorld->MultiplyPoint(viewCoords, nearPoint); nearPoint[0] /= nearPoint[3]; nearPoint[1] /= nearPoint[3]; nearPoint[2] /= nearPoint[3]; } // Find all triangles that the ray may exit. vtkUnstructuredGridBunykRayCastFunction::Triangle* candidate[3]; int index = 0; int i; for (i = 0; i < 4; i++) { if (triangles[this->CurrentTetra * 4 + i] != this->CurrentTriangle) { if (index == 3) { vtkGenericWarningMacro("Ugh - found too many triangles!"); } else { candidate[index++] = triangles[this->CurrentTetra * 4 + i]; } } } double farZ = VTK_DOUBLE_MAX; int minIdx = -1; // Determine which face the ray exits the cell from. for (i = 0; i < 3; i++) { // Far intersection is the nearest intersectation that is farther // than nearZ. double tmpZ = 1.0; if (candidate[i]->C != 0.0) { tmpZ = -(fx * candidate[i]->A + fy * candidate[i]->B + candidate[i]->D) / candidate[i]->C; } if (tmpZ > nearZ && tmpZ < farZ) { farZ = tmpZ; minIdx = i; } } // Now, the code above should ensure that farZ > nearZ, but I have // seen the case where we reach here with farZ == nearZ. This is very // bad as we need ensure we always move forward so that we do not get // into loops. I think there is something with GCC 3.2.3 that makes // the optimizer be too ambitious and turn the > into >=. if ((minIdx == -1) || (farZ <= nearZ)) { // The ray never exited the cell? Perhaps numerical inaccuracies // got us here. Just bail out as if we exited the mesh. nextTriangle = nullptr; nextTetra = -1; } else { if (farZ > this->FarClipZ) { // Exit happened after point of interest. Bail out now (in case // we wish to restart). return; } if (this->IntersectedCells) { this->IntersectedCells[this->NumIntersections] = this->CurrentTetra; } nextTriangle = candidate[minIdx]; // Compute intersection with exiting face. double farPoint[4]; viewCoords[2] = farZ; viewToWorld->MultiplyPoint(viewCoords, farPoint); farPoint[0] /= farPoint[3]; farPoint[1] /= farPoint[3]; farPoint[2] /= farPoint[3]; double dist = sqrt((nearPoint[0] - farPoint[0]) * (nearPoint[0] - farPoint[0]) + (nearPoint[1] - farPoint[1]) * (nearPoint[1] - farPoint[1]) + (nearPoint[2] - farPoint[2]) * (nearPoint[2] - farPoint[2])); if (this->IntersectionLengths) { this->IntersectionLengths[this->NumIntersections] = dist; } // compute the barycentric weights float ax, ay; double a1, b1, c1; ax = points[3 * this->CurrentTriangle->PointIndex[0]]; ay = points[3 * this->CurrentTriangle->PointIndex[0] + 1]; b1 = ((fx - ax) * this->CurrentTriangle->P2Y - (fy - ay) * this->CurrentTriangle->P2X) / this->CurrentTriangle->Denominator; c1 = ((fy - ay) * this->CurrentTriangle->P1X - (fx - ax) * this->CurrentTriangle->P1Y) / this->CurrentTriangle->Denominator; a1 = 1.0 - b1 - c1; double a2, b2, c2; ax = points[3 * nextTriangle->PointIndex[0]]; ay = points[3 * nextTriangle->PointIndex[0] + 1]; b2 = ((fx - ax) * nextTriangle->P2Y - (fy - ay) * nextTriangle->P2X) / nextTriangle->Denominator; c2 = ((fy - ay) * nextTriangle->P1X - (fx - ax) * nextTriangle->P1Y) / nextTriangle->Denominator; a2 = 1.0 - b2 - c2; if (nearIntersectionArray) { for (int c = 0; c < this->NumComponents; c++) { ValueType A, B, C; A = scalarArray->GetTypedComponent(this->CurrentTriangle->PointIndex[0], c); B = scalarArray->GetTypedComponent(this->CurrentTriangle->PointIndex[1], c); C = scalarArray->GetTypedComponent(this->CurrentTriangle->PointIndex[2], c); nearIntersectionArray->SetTypedComponent( this->NumIntersections, c, static_cast(a1 * A + b1 * B + c1 * C)); } } if (farIntersectionArray) { for (int c = 0; c < this->NumComponents; c++) { ValueType A, B, C; A = scalarArray->GetTypedComponent(nextTriangle->PointIndex[0], c); B = scalarArray->GetTypedComponent(nextTriangle->PointIndex[1], c); C = scalarArray->GetTypedComponent(nextTriangle->PointIndex[2], c); farIntersectionArray->SetTypedComponent( this->NumIntersections, c, static_cast(a2 * A + b2 * B + c2 * C)); } } this->NumIntersections++; // The far triangle has one or two tetras in its referred list. // If one, return -1 for next tetra and nullptr for next triangle // since we are exiting. If two, return the one that isn't the // current one. if ((nextTriangle)->ReferredByTetra[1] == -1) { nextTetra = -1; nextTriangle = nullptr; } else { if (nextTriangle->ReferredByTetra[0] == this->CurrentTetra) { nextTetra = nextTriangle->ReferredByTetra[1]; } else { nextTetra = nextTriangle->ReferredByTetra[0]; } } nearZ = farZ; nearPoint[0] = farPoint[0]; nearPoint[1] = farPoint[1]; nearPoint[2] = farPoint[2]; nearPoint[3] = farPoint[3]; } this->CurrentTriangle = nextTriangle; this->CurrentTetra = nextTetra; } } }; } // end anon namespace //------------------------------------------------------------------------------ // This is an internal hidden class. class vtkUnstructuredGridBunykRayCastIterator : public vtkUnstructuredGridVolumeRayCastIterator { public: vtkTypeMacro(vtkUnstructuredGridBunykRayCastIterator, vtkUnstructuredGridVolumeRayCastIterator); static vtkUnstructuredGridBunykRayCastIterator* New(); void Initialize(int x, int y) override; vtkIdType GetNextIntersections(vtkIdList* intersectedCells, vtkDoubleArray* intersectionLengths, vtkDataArray* scalars, vtkDataArray* nearIntersections, vtkDataArray* farIntersections) override; vtkSetObjectMacro(RayCastFunction, vtkUnstructuredGridBunykRayCastFunction); vtkGetObjectMacro(RayCastFunction, vtkUnstructuredGridBunykRayCastFunction); protected: vtkUnstructuredGridBunykRayCastIterator(); ~vtkUnstructuredGridBunykRayCastIterator() override; int RayPosition[2]; vtkUnstructuredGridBunykRayCastFunction* RayCastFunction; vtkUnstructuredGridBunykRayCastFunction::Intersection* IntersectionPtr; vtkUnstructuredGridBunykRayCastFunction::Triangle* CurrentTriangle; vtkIdType CurrentTetra; private: vtkUnstructuredGridBunykRayCastIterator(const vtkUnstructuredGridBunykRayCastIterator&) = delete; void operator=(const vtkUnstructuredGridBunykRayCastIterator&) = delete; }; vtkStandardNewMacro(vtkUnstructuredGridBunykRayCastIterator); vtkUnstructuredGridBunykRayCastIterator::vtkUnstructuredGridBunykRayCastIterator() { this->RayCastFunction = nullptr; } vtkUnstructuredGridBunykRayCastIterator::~vtkUnstructuredGridBunykRayCastIterator() { this->SetRayCastFunction(nullptr); } void vtkUnstructuredGridBunykRayCastIterator::Initialize(int x, int y) { this->RayPosition[0] = x; this->RayPosition[1] = y; this->IntersectionPtr = this->RayCastFunction->GetIntersectionList(this->RayPosition[0], this->RayPosition[1]); this->CurrentTriangle = nullptr; this->CurrentTetra = -1; // Intersect cells until we get to Bounds[0] (the near clip plane). TemplateCastRayWorker worker(this->RayCastFunction, 0, this->RayPosition[0], this->RayPosition[1], this->Bounds[0], this->IntersectionPtr, this->CurrentTriangle, this->CurrentTetra, nullptr, nullptr, this->MaxNumberOfIntersections); do { worker(); } while (worker.NumIntersections > 0); } vtkIdType vtkUnstructuredGridBunykRayCastIterator::GetNextIntersections(vtkIdList* intersectedCells, vtkDoubleArray* intersectionLengths, vtkDataArray* scalars, vtkDataArray* nearIntersections, vtkDataArray* farIntersections) { if (intersectedCells) { intersectedCells->SetNumberOfIds(this->MaxNumberOfIntersections); } if (intersectionLengths) { intersectionLengths->SetNumberOfComponents(1); intersectionLengths->SetNumberOfTuples(this->MaxNumberOfIntersections); } vtkIdType numIntersections = 0; if (!scalars) { TemplateCastRayWorker worker(this->RayCastFunction, 0, this->RayPosition[0], this->RayPosition[1], this->Bounds[1], this->IntersectionPtr, this->CurrentTriangle, this->CurrentTetra, intersectedCells ? intersectedCells->GetPointer(0) : nullptr, intersectionLengths ? intersectionLengths->GetPointer(0) : nullptr, this->MaxNumberOfIntersections); worker(); numIntersections = worker.NumIntersections; } else { if ((scalars->GetDataType() != nearIntersections->GetDataType()) || (scalars->GetDataType() != farIntersections->GetDataType())) { vtkErrorMacro(<< "Data types for scalars do not match up."); } nearIntersections->SetNumberOfComponents(scalars->GetNumberOfComponents()); nearIntersections->SetNumberOfTuples(this->MaxNumberOfIntersections); farIntersections->SetNumberOfComponents(scalars->GetNumberOfComponents()); farIntersections->SetNumberOfTuples(this->MaxNumberOfIntersections); TemplateCastRayWorker worker(this->RayCastFunction, scalars->GetNumberOfComponents(), this->RayPosition[0], this->RayPosition[1], this->Bounds[1], this->IntersectionPtr, this->CurrentTriangle, this->CurrentTetra, intersectedCells ? intersectedCells->GetPointer(0) : nullptr, intersectionLengths ? intersectionLengths->GetPointer(0) : nullptr, this->MaxNumberOfIntersections); if (!vtkArrayDispatch::Dispatch3SameValueType::Execute( scalars, nearIntersections, farIntersections, worker)) { vtkWarningMacro("Dispatch failed for scalars and intersections."); } else { numIntersections = worker.NumIntersections; } nearIntersections->SetNumberOfTuples(numIntersections); farIntersections->SetNumberOfTuples(numIntersections); } if (intersectedCells) { intersectedCells->SetNumberOfIds(numIntersections); } if (intersectionLengths) { intersectionLengths->SetNumberOfTuples(numIntersections); } return numIntersections; } //------------------------------------------------------------------------------ // Constructor - initially everything to null, and create a matrix for use later vtkUnstructuredGridBunykRayCastFunction::vtkUnstructuredGridBunykRayCastFunction() { this->Renderer = nullptr; this->Volume = nullptr; this->Mapper = nullptr; this->Valid = 0; this->Points = nullptr; this->Image = nullptr; this->TriangleList = nullptr; this->TetraTriangles = nullptr; this->TetraTrianglesSize = 0; this->NumberOfPoints = 0; this->ImageSize[0] = 0; this->ImageSize[1] = 0; this->ViewToWorldMatrix = vtkMatrix4x4::New(); for (int i = 0; i < VTK_BUNYKRCF_MAX_ARRAYS; i++) { this->IntersectionBuffer[i] = nullptr; this->IntersectionBufferCount[i] = 0; } this->SavedTriangleListInput = nullptr; } // Destructor - release all memory vtkUnstructuredGridBunykRayCastFunction::~vtkUnstructuredGridBunykRayCastFunction() { delete[] this->Points; this->ClearImage(); delete[] this->Image; this->Image = nullptr; delete[] this->TetraTriangles; int i; for (i = 0; i < 20; i++) { delete[] this->IntersectionBuffer[i]; } while (this->TriangleList) { Triangle* tmp; tmp = this->TriangleList->Next; delete this->TriangleList; this->TriangleList = tmp; } this->ViewToWorldMatrix->Delete(); } // Clear the intersection image. This does NOT release memory - // it just sets the link pointers to nullptr. The memory is // contained in the IntersectionBuffer arrays. void vtkUnstructuredGridBunykRayCastFunction::ClearImage() { int i; if (this->Image) { for (i = 0; i < this->ImageSize[0] * this->ImageSize[1]; i++) { this->Image[i] = nullptr; } } for (i = 0; i < VTK_BUNYKRCF_MAX_ARRAYS; i++) { this->IntersectionBufferCount[i] = 0; } } // Since we are managing the memory ourself for these intersections, // we need a new method. In this method we return an unused // intersection element from our storage arrays. If we don't // have one, we create a new storage array (unless we have run // out of memory). The memory can never shrink, and will only be // deleted when the class is destructed. void* vtkUnstructuredGridBunykRayCastFunction::NewIntersection() { // Look for the first buffer that has enough space, or the // first one that has not yet been allocated int i; for (i = 0; i < VTK_BUNYKRCF_MAX_ARRAYS; i++) { if (!this->IntersectionBuffer[i] || this->IntersectionBufferCount[i] < VTK_BUNYKRCF_ARRAY_SIZE) { break; } } // We have run out of space - return nullptr if (i == VTK_BUNYKRCF_MAX_ARRAYS) { vtkErrorMacro("Out of space for intersections!"); return nullptr; } // We need another array - allocate it and set its count to 0 indicating // that we have not used any elements yet if (!this->IntersectionBuffer[i]) { this->IntersectionBuffer[i] = new Intersection[VTK_BUNYKRCF_ARRAY_SIZE]; this->IntersectionBufferCount[i] = 0; } // Return the first unused element return (this->IntersectionBuffer[i] + (this->IntersectionBufferCount[i]++)); } // The Initialize method is called from the ray caster at the start of // rendering. In this method we check if the render is valid (there is // a renderer, a volume, a mapper, input, etc). We build the basic // structured if necessary. Then we compute the view dependent information // such as plane equations and barycentric coordinates per triangle, // tranfromed points in view space, and the intersection list per pixel. void vtkUnstructuredGridBunykRayCastFunction::Initialize(vtkRenderer* ren, vtkVolume* vol) { // Check if this is a valid render - we have all the required info // such as the volume, renderer, mapper, input, etc. this->Valid = this->CheckValidity(ren, vol); if (!this->Valid) { return; } // Cache some objects for later use during rendering this->Mapper = vtkUnstructuredGridVolumeRayCastMapper::SafeDownCast(vol->GetMapper()); this->Renderer = ren; this->Volume = vol; vtkUnstructuredGridBase* input = this->Mapper->GetInput(); int numPoints = input->GetNumberOfPoints(); // If the number of points have changed, recreate the structure if (numPoints != this->NumberOfPoints) { delete[] this->Points; this->Points = new double[3 * numPoints]; this->NumberOfPoints = numPoints; } // Get the image size from the ray cast mapper. The ImageViewportSize is // the size of the whole viewport (this does not necessary equal pixel // size since we may be over / undersampling on the image plane). The size // (which will be stored in ImageSize) and the ImageOrigin represent the // subregion of the whole image that we will be considering. int size[2]; this->Mapper->GetImageInUseSize(size); this->Mapper->GetImageOrigin(this->ImageOrigin); this->Mapper->GetImageViewportSize(this->ImageViewportSize); // If our intersection image is not the right size, recreate it. // Clear out any old intersections this->ClearImage(); if (this->ImageSize[0] * this->ImageSize[1] != size[0] * size[1]) { delete[] this->Image; this->Image = new Intersection*[size[0] * size[1]]; this->ImageSize[0] = size[0]; this->ImageSize[1] = size[1]; this->ClearImage(); } // Transform the points. As a by product, compute the ViewToWorldMatrix // that will be used later this->TransformPoints(); // If it has not yet been built, or the data has changed in // some way, we will need to recreate the triangle list. This is // view independent - although we will leave space in the structure // for the view dependent info this->UpdateTriangleList(); // For each triangle store the plane equation and barycentric // coefficients to be used to speed up rendering this->ComputeViewDependentInfo(); // Project each boundary triangle onto the image and store intersections // sorted by depth this->ComputePixelIntersections(); } int vtkUnstructuredGridBunykRayCastFunction::CheckValidity(vtkRenderer* ren, vtkVolume* vol) { // We must have a renderer if (!ren) { vtkErrorMacro("No Renderer"); return 0; } // We must have a volume if (!vol) { vtkErrorMacro("No Volume"); return 0; } // We must have a mapper of the correct type vtkUnstructuredGridVolumeRayCastMapper* mapper = vtkUnstructuredGridVolumeRayCastMapper::SafeDownCast(vol->GetMapper()); if (!mapper) { vtkErrorMacro("No mapper or wrong type"); return 0; } // The mapper must have input vtkUnstructuredGridBase* input = mapper->GetInput(); if (!input) { vtkErrorMacro("No input to mapper"); return 0; } // The input must have some points. This is a silent // error - just render nothing if it occurs. int numPoints = input->GetNumberOfPoints(); if (numPoints == 0) { this->Valid = 0; return 0; } return 1; } // This is done once per render - transform the points into view coordinate. // We also compute the ViewToWorldMatrix here (by inverting the matrix // we use to project to view coordinates) so that later on in the // rendering process we can convert points back to world coordinates. void vtkUnstructuredGridBunykRayCastFunction::TransformPoints() { vtkRenderer* ren = this->Renderer; vtkVolume* vol = this->Volume; ren->ComputeAspect(); double* aspect = ren->GetAspect(); vtkTransform* perspectiveTransform = vtkTransform::New(); vtkMatrix4x4* perspectiveMatrix = vtkMatrix4x4::New(); // Get the view matrix in two steps - there is a one step method in camera // but it turns off stereo so we do not want to use that one vtkCamera* cam = ren->GetActiveCamera(); perspectiveTransform->Identity(); perspectiveTransform->Concatenate( cam->GetProjectionTransformMatrix(aspect[0] / aspect[1], 0.0, 1.0)); perspectiveTransform->Concatenate(cam->GetViewTransformMatrix()); vtkNew modelToWorld; vol->GetModelToWorldMatrix(modelToWorld); perspectiveTransform->Concatenate(modelToWorld); perspectiveMatrix->DeepCopy(perspectiveTransform->GetMatrix()); // Invert this project matrix and store for later use this->ViewToWorldMatrix->DeepCopy(perspectiveTransform->GetMatrix()); this->ViewToWorldMatrix->Invert(); double* origPtr; double* transformedPtr = this->Points; double in[4], out[4]; in[3] = 1.0; vtkUnstructuredGridBase* input = this->Mapper->GetInput(); int numPoints = input->GetNumberOfPoints(); // Loop through all the points and transform them for (int i = 0; i < numPoints; i++) { origPtr = input->GetPoint(i); in[0] = origPtr[0]; in[1] = origPtr[1]; in[2] = origPtr[2]; perspectiveMatrix->MultiplyPoint(in, out); transformedPtr[0] = (out[0] / out[3] + 1.0) / 2.0 * (double)this->ImageViewportSize[0] - this->ImageOrigin[0]; transformedPtr[1] = (out[1] / out[3] + 1.0) / 2.0 * (double)this->ImageViewportSize[1] - this->ImageOrigin[1]; transformedPtr[2] = out[2] / out[3]; transformedPtr += 3; } perspectiveTransform->Delete(); perspectiveMatrix->Delete(); } // This is done once per change in the data - build a list of // enumerated triangles (up to four per tetra). Don't store // duplicates, so we'll have to search for them. void vtkUnstructuredGridBunykRayCastFunction::UpdateTriangleList() { int needsUpdate = 0; // If we have never created the list, we need updating if (!this->TriangleList) { needsUpdate = 1; } // If the data has changed in some way then we need to update vtkUnstructuredGridBase* input = this->Mapper->GetInput(); if (this->SavedTriangleListInput != input || input->GetMTime() > this->SavedTriangleListMTime.GetMTime()) { needsUpdate = 1; } // If we don't need updating, return if (!needsUpdate) { return; } // Clear out the old triangle list while (this->TriangleList) { Triangle* tmp; tmp = this->TriangleList->Next; delete this->TriangleList; this->TriangleList = tmp; } this->TriangleList = nullptr; // A temporary structure to reduce search time - VTK_BUNYKRCF_NUMLISTS small // lists instead of one big one Triangle* tmpList[VTK_BUNYKRCF_NUMLISTS]; vtkIdType i; for (i = 0; i < VTK_BUNYKRCF_NUMLISTS; i++) { tmpList[i] = nullptr; } vtkIdType numCells = input->GetNumberOfCells(); // Provide a warnings for anomalous conditions. int nonTetraWarningNeeded = 0; int faceUsed3TimesWarning = 0; // Create a set of links from each tetra to the four triangles // This is redundant information, but saves time during rendering if (this->TetraTriangles != nullptr && numCells != this->TetraTrianglesSize) { delete[] this->TetraTriangles; this->TetraTriangles = nullptr; } if (this->TetraTriangles == nullptr) { this->TetraTriangles = new Triangle*[4 * numCells]; this->TetraTrianglesSize = numCells; } // Loop through all the cells vtkSmartPointer cellIter = vtkSmartPointer::Take(input->NewCellIterator()); for (cellIter->InitTraversal(); !cellIter->IsDoneWithTraversal(); cellIter->GoToNextCell()) { // We only handle tetra if (cellIter->GetCellType() != VTK_TETRA) { nonTetraWarningNeeded = 1; continue; } // Get the four points i = cellIter->GetCellId(); vtkIdList* ptIds = cellIter->GetPointIds(); vtkIdType pts[4]; pts[0] = ptIds->GetId(0); pts[1] = ptIds->GetId(1); pts[2] = ptIds->GetId(2); pts[3] = ptIds->GetId(3); // Build each of the four triangles int ii, jj; for (jj = 0; jj < 4; jj++) { vtkIdType tri[3]; int idx = 0; for (ii = 0; ii < 4; ii++) { if (ii != jj) { tri[idx++] = pts[ii]; } } if (tri[0] > tri[1]) { vtkIdType tmptri = tri[0]; tri[0] = tri[1]; tri[1] = tmptri; } if (tri[1] > tri[2]) { vtkIdType tmptri = tri[1]; tri[1] = tri[2]; tri[2] = tmptri; } if (tri[0] > tri[1]) { vtkIdType tmptri = tri[0]; tri[0] = tri[1]; tri[1] = tmptri; } // Do we have this triangle already? Triangle* triPtr = tmpList[tri[0] % VTK_BUNYKRCF_NUMLISTS]; while (triPtr) { if (triPtr->PointIndex[0] == tri[0] && triPtr->PointIndex[1] == tri[1] && triPtr->PointIndex[2] == tri[2]) { break; } triPtr = triPtr->Next; } if (triPtr) { if (triPtr->ReferredByTetra[1] != -1) { faceUsed3TimesWarning = 1; } triPtr->ReferredByTetra[1] = i; this->TetraTriangles[i * 4 + jj] = triPtr; } else { Triangle* next = new Triangle; next->PointIndex[0] = tri[0]; next->PointIndex[1] = tri[1]; next->PointIndex[2] = tri[2]; next->ReferredByTetra[0] = i; next->ReferredByTetra[1] = -1; next->Next = tmpList[tri[0] % VTK_BUNYKRCF_NUMLISTS]; tmpList[tri[0] % VTK_BUNYKRCF_NUMLISTS] = next; this->TetraTriangles[i * 4 + jj] = next; } } } if (nonTetraWarningNeeded) { vtkWarningMacro("Input contains more than tetrahedra - only tetrahedra are supported"); } if (faceUsed3TimesWarning) { vtkWarningMacro("Degenerate topology - cell face used more than twice"); } // Put the list together for (i = 0; i < VTK_BUNYKRCF_NUMLISTS; i++) { if (tmpList[i]) { Triangle* last = tmpList[i]; while (last->Next) { last = last->Next; } last->Next = this->TriangleList; this->TriangleList = tmpList[i]; } } this->SavedTriangleListInput = input; this->SavedTriangleListMTime.Modified(); } void vtkUnstructuredGridBunykRayCastFunction::ComputeViewDependentInfo() { Triangle* triPtr = this->TriangleList; while (triPtr) { double P1[3], P2[3]; double A[3], B[3], C[3]; A[0] = this->Points[3 * triPtr->PointIndex[0]]; A[1] = this->Points[3 * triPtr->PointIndex[0] + 1]; A[2] = this->Points[3 * triPtr->PointIndex[0] + 2]; B[0] = this->Points[3 * triPtr->PointIndex[1]]; B[1] = this->Points[3 * triPtr->PointIndex[1] + 1]; B[2] = this->Points[3 * triPtr->PointIndex[1] + 2]; C[0] = this->Points[3 * triPtr->PointIndex[2]]; C[1] = this->Points[3 * triPtr->PointIndex[2] + 1]; C[2] = this->Points[3 * triPtr->PointIndex[2] + 2]; P1[0] = B[0] - A[0]; P1[1] = B[1] - A[1]; P1[2] = B[2] - A[2]; P2[0] = C[0] - A[0]; P2[1] = C[1] - A[1]; P2[2] = C[2] - A[2]; triPtr->Denominator = P1[0] * P2[1] - P2[0] * P1[1]; if (triPtr->Denominator < 0) { double T[3]; triPtr->Denominator = -triPtr->Denominator; T[0] = P1[0]; T[1] = P1[1]; T[2] = P1[2]; P1[0] = P2[0]; P1[1] = P2[1]; P1[2] = P2[2]; P2[0] = T[0]; P2[1] = T[1]; P2[2] = T[2]; vtkIdType tmpIndex = triPtr->PointIndex[1]; triPtr->PointIndex[1] = triPtr->PointIndex[2]; triPtr->PointIndex[2] = tmpIndex; } triPtr->P1X = P1[0]; triPtr->P1Y = P1[1]; triPtr->P2X = P2[0]; triPtr->P2Y = P2[1]; double result[3]; vtkMath::Cross(P1, P2, result); triPtr->A = result[0]; triPtr->B = result[1]; triPtr->C = result[2]; triPtr->D = -(A[0] * result[0] + A[1] * result[1] + A[2] * result[2]); triPtr = triPtr->Next; } } void vtkUnstructuredGridBunykRayCastFunction::ComputePixelIntersections() { Triangle* triPtr = this->TriangleList; while (triPtr) { if (triPtr->ReferredByTetra[1] == -1) { if (this->IsTriangleFrontFacing(triPtr, triPtr->ReferredByTetra[0])) { int minX = static_cast(this->Points[3 * triPtr->PointIndex[0]]); int maxX = minX + 1; int minY = static_cast(this->Points[3 * triPtr->PointIndex[0] + 1]); int maxY = minY + 1; int tmp; tmp = static_cast(this->Points[3 * triPtr->PointIndex[1]]); minX = (tmp < minX) ? (tmp) : (minX); maxX = ((tmp + 1) > maxX) ? (tmp + 1) : (maxX); tmp = static_cast(this->Points[3 * triPtr->PointIndex[1] + 1]); minY = (tmp < minY) ? (tmp) : (minY); maxY = ((tmp + 1) > maxY) ? (tmp + 1) : (maxY); tmp = static_cast(this->Points[3 * triPtr->PointIndex[2]]); minX = (tmp < minX) ? (tmp) : (minX); maxX = ((tmp + 1) > maxX) ? (tmp + 1) : (maxX); tmp = static_cast(this->Points[3 * triPtr->PointIndex[2] + 1]); minY = (tmp < minY) ? (tmp) : (minY); maxY = ((tmp + 1) > maxY) ? (tmp + 1) : (maxY); double minZ = this->Points[3 * triPtr->PointIndex[0] + 2]; double ftmp; ftmp = this->Points[3 * triPtr->PointIndex[1] + 2]; minZ = (ftmp < minZ) ? (ftmp) : (minZ); ftmp = this->Points[3 * triPtr->PointIndex[2] + 2]; minZ = (ftmp < minZ) ? (ftmp) : (minZ); if (minX < this->ImageSize[0] - 1 && minY < this->ImageSize[1] - 1 && maxX >= 0 && maxY >= 0 && minZ > 0.0) { minX = (minX < 0) ? (0) : (minX); maxX = (maxX > (this->ImageSize[0] - 1)) ? (this->ImageSize[0] - 1) : (maxX); minY = (minY < 0) ? (0) : (minY); maxY = (maxY > (this->ImageSize[1] - 1)) ? (this->ImageSize[1] - 1) : (maxY); int x, y; double ax, ay, az; ax = this->Points[3 * triPtr->PointIndex[0]]; ay = this->Points[3 * triPtr->PointIndex[0] + 1]; az = this->Points[3 * triPtr->PointIndex[0] + 2]; for (y = minY; y <= maxY; y++) { double qy = (double)y - ay; for (x = minX; x <= maxX; x++) { double qx = (double)x - ax; if (this->InTriangle(qx, qy, triPtr)) { Intersection* intersect = (Intersection*)this->NewIntersection(); if (intersect) { intersect->TriPtr = triPtr; intersect->Z = az; intersect->Next = nullptr; if (!this->Image[y * this->ImageSize[0] + x] || intersect->Z < this->Image[y * this->ImageSize[0] + x]->Z) { intersect->Next = this->Image[y * this->ImageSize[0] + x]; this->Image[y * this->ImageSize[0] + x] = intersect; } else { Intersection* test = this->Image[y * this->ImageSize[0] + x]; while (test->Next && intersect->Z > test->Next->Z) { test = test->Next; } Intersection* tmpNext = test->Next; test->Next = intersect; intersect->Next = tmpNext; } } } } } } } } triPtr = triPtr->Next; } } // Taken from equation on bottom of left column of page 3 - but note that the // equation in the paper has a mistake: (q1+q2) must be less than 1 (not denom as // stated in the paper). int vtkUnstructuredGridBunykRayCastFunction::InTriangle(double x, double y, Triangle* triPtr) { double q1, q2; q1 = (x * triPtr->P2Y - y * triPtr->P2X) / triPtr->Denominator; q2 = (y * triPtr->P1X - x * triPtr->P1Y) / triPtr->Denominator; if (q1 >= 0 && q2 >= 0 && (q1 + q2) <= 1.0) { return 1; } else { return 0; } } int vtkUnstructuredGridBunykRayCastFunction::IsTriangleFrontFacing( Triangle* triPtr, vtkIdType tetraIndex) { vtkCell* cell = this->Mapper->GetInput()->GetCell(tetraIndex); vtkIdType pts[4]; pts[0] = cell->GetPointId(0); pts[1] = cell->GetPointId(1); pts[2] = cell->GetPointId(2); pts[3] = cell->GetPointId(3); for (int i = 0; i < 4; i++) { if (pts[i] != triPtr->PointIndex[0] && pts[i] != triPtr->PointIndex[1] && pts[i] != triPtr->PointIndex[2]) { double d = triPtr->A * this->Points[3 * pts[i]] + triPtr->B * this->Points[3 * pts[i] + 1] + triPtr->C * this->Points[3 * pts[i] + 2] + triPtr->D; return (d > 0); } } assert(0); return false; } vtkUnstructuredGridVolumeRayCastIterator* vtkUnstructuredGridBunykRayCastFunction::NewIterator() { if (!this->Valid) return nullptr; vtkUnstructuredGridBunykRayCastIterator* iterator = vtkUnstructuredGridBunykRayCastIterator::New(); iterator->SetRayCastFunction(this); return iterator; } void vtkUnstructuredGridBunykRayCastFunction::Finalize() { this->Renderer = nullptr; this->Volume = nullptr; this->Mapper = nullptr; this->Valid = 0; } //------------------------------------------------------------------------------ void vtkUnstructuredGridBunykRayCastFunction::PrintSelf(ostream& os, vtkIndent indent) { this->Superclass::PrintSelf(os, indent); // Do not want to print this->ViewToWorldMatrix , this->ImageViewportSize // this->ScalarOpacityUnitDistance , or this->ImageOrigin - these are // internal ivar and not part of the public API for this class }