/*========================================================================= Program: Visualization Toolkit Module: $RCSfile: vtkVoxelContoursToSurfaceFilter.cxx,v $ 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 "vtkVoxelContoursToSurfaceFilter.h" #include "vtkAppendPolyData.h" #include "vtkCellArray.h" #include "vtkContourFilter.h" #include "vtkInformation.h" #include "vtkInformationVector.h" #include "vtkObjectFactory.h" #include "vtkPointData.h" #include "vtkPolyData.h" #include "vtkStructuredPoints.h" vtkCxxRevisionMacro(vtkVoxelContoursToSurfaceFilter, "$Revision: 1.27 $"); vtkStandardNewMacro(vtkVoxelContoursToSurfaceFilter); vtkVoxelContoursToSurfaceFilter::vtkVoxelContoursToSurfaceFilter() { this->MemoryLimitInBytes = 10000000; this->Spacing[0] = 1.0; this->Spacing[1] = 1.0; this->Spacing[2] = 1.0; this->LineList = new double[4*1000]; this->LineListLength = 0; this->LineListSize = 1000; this->SortedXList = NULL; this->SortedYList = NULL; this->WorkingList = NULL; this->IntersectionList = NULL; this->SortedListSize = 0; } vtkVoxelContoursToSurfaceFilter::~vtkVoxelContoursToSurfaceFilter() { if ( this->LineList ) { delete [] this->LineList; } if ( this->SortedXList ) { delete [] this->SortedXList; } if ( this->SortedYList ) { delete [] this->SortedYList; } if ( this->WorkingList ) { delete [] this->WorkingList; } if ( this->IntersectionList ) { delete [] this->IntersectionList; } } void vtkVoxelContoursToSurfaceFilter::AddLineToLineList( double x1, double y1, double x2, double y2 ) { // Do we need to increase the size of our list? if ( this->LineListLength >= this->LineListSize ) { // Double the space we had before double *newList = new double[this->LineListSize*4*2]; memcpy( newList, this->LineList, 4*this->LineListSize*sizeof(double) ); delete [] this->LineList; this->LineList = newList; this->LineListSize *= 2; } // Now we are sure we have space - add the line this->LineList[4*this->LineListLength + 0] = x1; this->LineList[4*this->LineListLength + 1] = y1; this->LineList[4*this->LineListLength + 2] = x2; this->LineList[4*this->LineListLength + 3] = y2; this->LineListLength++; } void vtkVoxelContoursToSurfaceFilter::SortLineList() { int i, j; double tmp[4]; double tmpval; // Make sure we have enough space in our sorted list if ( this->SortedListSize < this->LineListLength ) { if ( this->SortedXList ) { delete [] this->SortedXList; } if ( this->SortedYList ) { delete [] this->SortedYList; } if ( this->WorkingList ) { delete [] this->WorkingList; } if ( this->IntersectionList ) { delete [] this->IntersectionList; } this->SortedXList = new double[4*this->LineListLength]; this->SortedYList = new double[4*this->LineListLength]; this->SortedListSize = this->LineListLength; // Create the space we'll need for our working list of indices // The is the list of lines that we are currently considering // for intersections. Lines move in then out of the list as // we pass the first then the second endpoint. This will be // used during the CastXLines and CastYLines methods, and is // the same size as the number of lines. this->WorkingList = new int[this->LineListLength]; // Create the space we'll need for the intersection list // There can't be more intersections than there are possible // lines. Although it is highly doubtful we'll actually use // all this space, it isn't much and it makes the code simpler // not to have to worry about exceeding the bounds. This will be // used during the CastXLines and CastYLines methods, and is // the same size as the number of lines. this->IntersectionList = new double[this->LineListLength]; } // Copy the lines into the lists memcpy( this->SortedXList, this->LineList, 4*this->LineListLength*sizeof(double) ); memcpy( this->SortedYList, this->LineList, 4*this->LineListLength*sizeof(double) ); // Now sort on x and y // Use a simple bubble sort - will improve if necessary for ( i = 0; i < this->LineListLength; i++ ) { // swap x entry if necessary to keep min x the first endpoint if ( this->SortedXList[4*i + 0] > this->SortedXList[4*i + 2] ) { tmpval = this->SortedXList[4*i]; this->SortedXList[4*i] = this->SortedXList[4*i + 2]; this->SortedXList[4*i + 2] = tmpval; tmpval = this->SortedXList[4*i + 1]; this->SortedXList[4*i + 1] = this->SortedXList[4*i + 3]; this->SortedXList[4*i + 3] = tmpval; } // swap y entry if necessary to keep min y the first endpoint if ( this->SortedYList[4*i + 1] > this->SortedYList[4*i + 3] ) { tmpval = this->SortedYList[4*i]; this->SortedYList[4*i] = this->SortedYList[4*i + 2]; this->SortedYList[4*i + 2] = tmpval; tmpval = this->SortedYList[4*i + 1]; this->SortedYList[4*i + 1] = this->SortedYList[4*i + 3]; this->SortedYList[4*i + 3] = tmpval; } // Sort x list for ( j = i; j > 0; j-- ) { if ( this->SortedXList[j*4] < this->SortedXList[(j-1)*4] ) { memcpy( tmp, this->SortedXList + j*4, 4*sizeof(double) ); memcpy( this->SortedXList + j*4, this->SortedXList + (j-1)*4, 4*sizeof(double) ); memcpy( this->SortedXList + (j-1)*4, tmp, 4*sizeof(double) ); } else { break; } } // Sort y list for ( j = i; j > 0; j-- ) { if ( this->SortedYList[j*4+1] < this->SortedYList[(j-1)*4+1] ) { memcpy( tmp, this->SortedYList + j*4, 4*sizeof(double) ); memcpy( this->SortedYList + j*4, this->SortedYList + (j-1)*4, 4*sizeof(double) ); memcpy( this->SortedYList + (j-1)*4, tmp, 4*sizeof(double) ); } else { break; } } } } void vtkVoxelContoursToSurfaceFilter::CastLines( float *slicePtr, double gridOrigin[3], int gridSize[3], int type ) { double axis1, axis2; double d1, d2; int index; int i, j; double tmp; double *line; float *currSlicePtr; int currSlice; int currentIntersection; double sign; double *sortedList; double low1, low2, high1, high2; int increment1, increment2; int offset1, offset2, offset3, offset4; // this is the x direction if ( type == 0 ) { low1 = gridOrigin[0]; high1 = gridOrigin[0] + (double)gridSize[0]; low2 = gridOrigin[1]; high2 = gridOrigin[1] + (double)gridSize[1]; increment1 = gridSize[0]; increment2 = 1; sortedList = this->SortedXList; offset1 = 0; offset2 = 2; offset3 = 1; offset4 = 3; } // This is the y direction else { low1 = gridOrigin[1]; high1 = gridOrigin[1] + (double)gridSize[1]; low2 = gridOrigin[0]; high2 = gridOrigin[0] + (double)gridSize[0]; increment1 = 1; increment2 = gridSize[0]; sortedList = this->SortedYList; offset1 = 1; offset2 = 3; offset3 = 0; offset4 = 2; } // Initialize the working list to nothing. We will start // looking at index = 0 for the next line to add to the // working list this->WorkingListLength = 0; index = 0; // Loop through the x or y lines for ( axis1 = low1, currSlice = 0; axis1 < high1; axis1 += 1.0, currSlice++ ) { // Initialize the intersection list to nothing this->IntersectionListLength = 0; // Add lines to the working list if necessary for ( ; index < this->LineListLength; index++ ) { if ( sortedList[4*index + offset1] < axis1 ) { this->WorkingList[this->WorkingListLength] = index; this->WorkingListLength++; } else { break; } } // Do the intersections, removing lines from the // working list if necessary for ( i = 0; i < this->WorkingListLength; i++ ) { line = sortedList + 4*this->WorkingList[i]; // Yes, it intersects, add it to the intersection list if ( line[offset1] < axis1 && line[offset2] > axis1 ) { // Compute the intersection distance // For x lines this is y = y1 + (y2 - y1)*((x - x1)/(x2 - x1)) // For y lines this is x = x1 + (x2 - x1)*((y - y1)/(y2 - y1)) this->IntersectionList[this->IntersectionListLength] = line[offset3] + (line[offset4] - line[offset3]) * ((axis1 - line[offset1]) / (line[offset2] - line[offset1] )); // Make sure this distance is sorted for ( j = this->IntersectionListLength; j > 0; j-- ) { if ( this->IntersectionList[j] < this->IntersectionList[j-1] ) { tmp = this->IntersectionList[j]; this->IntersectionList[j] = this->IntersectionList[j-1]; this->IntersectionList[j-1] = tmp; } else { break; } } this->IntersectionListLength++; } // No, it doesn't intersect, remove it from the working list else { for ( j = i; j < (this->WorkingListLength-1); j++ ) { this->WorkingList[j] = this->WorkingList[j+1]; } this->WorkingListLength--; i--; } } // Now we have all the intersections for the x or y line, in sorted // order. Use them to fill in distances (as long as there are // any) if ( this->IntersectionListLength ) { currSlicePtr = slicePtr + currSlice*increment2; currentIntersection = 0; // We are starting outside which has a negative distance sign = -1.0; for ( axis2 = low2; axis2 < high2; axis2 += 1.0 ) { while( currentIntersection < this->IntersectionListLength && this->IntersectionList[currentIntersection] < axis2 ) { currentIntersection++; // Each time we cross a line we are moving across an // inside/outside boundary sign *= -1.0; } // We are now positioned at an x or y value between currentIntersection // and currentIntersection - 1 (except at boundaries where we are // before intersection 0 or after the last intersection) if ( currentIntersection == 0 ) { d1 = axis2 - this->IntersectionList[currentIntersection]; *currSlicePtr = (*currSlicePtr > d1 )?(*currSlicePtr):(d1); } else if ( currentIntersection == this->IntersectionListLength ) { d1 = this->IntersectionList[currentIntersection-1] - axis2; *currSlicePtr = (*currSlicePtr > d1 )?(*currSlicePtr):(d1); } else { d1 = axis2 - this->IntersectionList[currentIntersection-1]; d2 = this->IntersectionList[currentIntersection] - axis2; d1 = ( d1 < d2 )?(d1):(d2); if ( type == 0 ) { *currSlicePtr = sign*d1; } else { *currSlicePtr = (sign*(*currSlicePtr) < d1 )?(*currSlicePtr):(sign*d1); } } currSlicePtr += increment1; } } } } void vtkVoxelContoursToSurfaceFilter::PushDistances( float *volumePtr, int gridSize[3], int chunkSize ) { int i, j, k; float *vptr; // Push distances along x (both ways) and y (both ways) on each slice for ( k = 0; k < chunkSize; k++ ) { // Do the x rows for ( j = 0; j < gridSize[1]; j++ ) { vptr = volumePtr + k*gridSize[0]*gridSize[1] + j*gridSize[0]; vptr++; // first one way for ( i = 1; i < gridSize[0]; i++ ) { if ( *vptr > 0 && *(vptr-1) + 1 < *(vptr) ) { *vptr = *(vptr-1) + 1; } else if ( *vptr < 0 && *(vptr-1) - 1 > *(vptr) ) { *vptr = *(vptr-1) - 1; } vptr++; } vptr -= 2; i -= 2; // then the other for ( ; i >= 0; i-- ) { if ( *vptr > 0 && *(vptr+1) + 1 < *(vptr) ) { *vptr = *(vptr+1) + 1; } else if ( *vptr < 0 && *(vptr+1) - 1 > *(vptr) ) { *vptr = *(vptr+1) - 1; } } } // Do the y columns for ( i = 0; i < gridSize[0]; i++ ) { vptr = volumePtr + k*gridSize[0]*gridSize[1] + i; vptr+=gridSize[0]; // first one way for ( j = 1; j < gridSize[1]; j++ ) { if ( *vptr > 0 && *(vptr-gridSize[0]) + 1 < *(vptr) ) { *vptr = *(vptr-gridSize[0]) + 1; } else if ( *vptr < 0 && *(vptr-gridSize[0]) - 1 > *(vptr) ) { *vptr = *(vptr-gridSize[0]) - 1; } vptr += gridSize[0]; } vptr -= 2*gridSize[0]; j -= 2; // then the other for ( ; j >= 0; j-- ) { if ( *vptr > 0 && *(vptr+gridSize[0]) + 1 < *(vptr) ) { *vptr = *(vptr+gridSize[0]) + 1; } else if ( *vptr < 0 && *(vptr+gridSize[0]) - 1 > *(vptr) ) { *vptr = *(vptr+gridSize[0]) - 1; } } } } } // Append data sets into single unstructured grid int vtkVoxelContoursToSurfaceFilter::RequestData( vtkInformation *vtkNotUsed(request), vtkInformationVector **inputVector, vtkInformationVector *outputVector) { // get the info objects vtkInformation *inInfo = inputVector[0]->GetInformationObject(0); vtkInformation *outInfo = outputVector->GetInformationObject(0); // get the input and ouptut vtkPolyData *input = vtkPolyData::SafeDownCast( inInfo->Get(vtkDataObject::DATA_OBJECT())); vtkPolyData *output = vtkPolyData::SafeDownCast( outInfo->Get(vtkDataObject::DATA_OBJECT())); vtkCellArray *inputPolys = input->GetPolys(); int gridSize[3]; double gridOrigin[3]; double contourBounds[6]; int chunkSize; int currentSlice, lastSlice, currentIndex; int i, j; int numberOfInputCells; int currentInputCellIndex; vtkIdType npts = 0; vtkIdType *pts = 0; double point1[3], point2[3]; double currentZ; vtkStructuredPoints *volume; float *volumePtr, *slicePtr; vtkContourFilter *contourFilter; vtkPolyData *contourOutput; vtkAppendPolyData *appendFilter; vtkDebugMacro(<<"Creating surfaces from contours"); // Get the bounds of the input contours input->GetBounds( contourBounds ); if (contourBounds[0] > contourBounds[1]) { // empty input return 1; } // From the bounds, compute the grid size, and origin // The origin of the grid should be (-0.5, -0.5, 0.0) away from the // lower bounds of the contours. This is because we want the grid // to lie halfway between integer endpoint locations of the line // segments on each plane. Also, we want an extra plane on each end // for capping gridOrigin[0] = contourBounds[0] - 0.5; gridOrigin[1] = contourBounds[2] - 0.5; gridOrigin[2] = contourBounds[4] - 1.0; // The difference between the bounds, plus one to account a // sample on the first and last location, plus one to account // for the larger grid size ( the 0.5 unit border ) On Z, we // want to sample exactly on the contours so we don't need to // add the extra 1, but we have added two extra planes so we // need another 2. gridSize[0] = (int) (contourBounds[1] - contourBounds[0] + 2); gridSize[1] = (int) (contourBounds[3] - contourBounds[2] + 2); gridSize[2] = (int) (contourBounds[5] - contourBounds[4] + 3); // How many slices in a chunk? This will later be decremented // by one to account for the fact that the last slice in the // previous chuck is copied to the first slice in the next chunk. // Stay within memory limit. There are 4 bytes per double. chunkSize = this->MemoryLimitInBytes / ( gridSize[0] * gridSize[1] * 4 ); if ( chunkSize > gridSize[2] ) { chunkSize = gridSize[2]; } currentSlice = 0; currentZ = contourBounds[4] - 1.0; currentIndex = 0; lastSlice = gridSize[2] - 1; numberOfInputCells = inputPolys->GetNumberOfCells(); currentInputCellIndex = 0; volume = vtkStructuredPoints::New(); volume->SetDimensions( gridSize[0], gridSize[1], chunkSize ); volume->SetSpacing( this->Spacing ); volume->SetScalarType( VTK_FLOAT ); volume->AllocateScalars(); volumePtr = (float *)(volume->GetPointData()->GetScalars()->GetVoidPointer(0)); contourFilter = vtkContourFilter::New(); contourFilter->SetInput( volume ); contourFilter->SetNumberOfContours(1); contourFilter->SetValue( 0, 0.0 ); appendFilter = vtkAppendPolyData::New(); inputPolys->InitTraversal(); inputPolys->GetNextCell( npts, pts ); while ( currentSlice <= lastSlice ) { // Make sure the origin of the volume is in the right // place so that the appended polydata all matches up // nicely. volume->SetOrigin( gridOrigin[0], gridOrigin[1], gridOrigin[2] + this->Spacing[2] * (currentSlice - (currentSlice!=0)) ); for ( i = currentIndex; i < chunkSize; i++ ) { slicePtr = volumePtr + i * gridSize[0] * gridSize[1]; // Clear out the slice memory - set it all to a large negative // value indicating no surfaces are nearby, and we assume we // are outside of any surface for ( j = 0; j < gridSize[0] * gridSize[1]; j++ ) { *(slicePtr+j) = -9.99e10; } // If we are past the end, don't do anything if ( currentSlice > lastSlice ) { continue; } this->LineListLength = 0; // Read in the lines for the contours on this slice while ( currentInputCellIndex < numberOfInputCells ) { // Check if we are still on the right z slice input->GetPoint( pts[0], point1 ); if ( point1[2] != currentZ ) { break; } // This contour is on the right z slice - add the lines // to our list for ( j = 0; j < npts; j++ ) { input->GetPoint( pts[j], point1 ); input->GetPoint( pts[(j+1)%npts], point2 ); this->AddLineToLineList( point1[0], point1[1], point2[0], point2[1] ); } inputPolys->GetNextCell( npts, pts ); currentInputCellIndex++; } // Sort the contours in x and y this->SortLineList(); // Cast lines in x and y filling in distance this->CastLines( slicePtr, gridOrigin, gridSize, 0 ); this->CastLines( slicePtr, gridOrigin, gridSize, 1 ); // Move on to the next slice currentSlice++; currentIndex++; currentZ += 1.0; } this->PushDistances( volumePtr, gridSize, chunkSize ); // Update the contour filter and grab the output // Make a new output for it, then grab the output and // add it to the append filter, then delete the output // which is ok since it was registered by the appendFilter contourOutput = vtkPolyData::New(); contourFilter->Update(); contourOutput->ShallowCopy(contourFilter->GetOutput()); appendFilter->AddInput( contourOutput ); contourOutput->Delete(); if ( currentSlice <= lastSlice ) { // Copy last slice to first slice memcpy( volumePtr, volumePtr + (chunkSize-1)*gridSize[0]*gridSize[1], sizeof(float) * gridSize[0] * gridSize[1] ); // reset currentIndex to 1 currentIndex = 1; } } appendFilter->Update(); // Grab the appended data as the output to this filter output->SetPoints( appendFilter->GetOutput()->GetPoints() ); output->SetVerts( appendFilter->GetOutput()->GetVerts() ); output->SetLines( appendFilter->GetOutput()->GetLines() ); output->SetPolys( appendFilter->GetOutput()->GetPolys() ); output->SetStrips( appendFilter->GetOutput()->GetStrips() ); output->GetPointData()->PassData(appendFilter->GetOutput()->GetPointData()); contourFilter->Delete(); appendFilter->Delete(); volume->Delete(); return 1; } void vtkVoxelContoursToSurfaceFilter::PrintSelf(ostream& os, vtkIndent indent) { this->Superclass::PrintSelf(os,indent); os << indent << "Memory Limit (in bytes): " << this->MemoryLimitInBytes << endl; os << indent << "Spacing: " << this->Spacing[0] << " " << this->Spacing[1] << " " << this->Spacing[2] << endl; }