/*========================================================================= Program: Visualization Toolkit Module: $RCSfile: vtkUnstructuredGridVolumeRayCastMapper.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 "vtkUnstructuredGridVolumeRayCastMapper.h" #include "vtkCamera.h" #include "vtkEncodedGradientEstimator.h" #include "vtkEncodedGradientShader.h" #include "vtkFiniteDifferenceGradientEstimator.h" #include "vtkUnstructuredGrid.h" #include "vtkMath.h" #include "vtkMultiThreader.h" #include "vtkObjectFactory.h" #include "vtkPlaneCollection.h" #include "vtkPointData.h" #include "vtkRenderWindow.h" #include "vtkRenderer.h" #include "vtkTimerLog.h" #include "vtkTransform.h" #include "vtkVolumeProperty.h" #include "vtkUnstructuredGridBunykRayCastFunction.h" #include "vtkUnstructuredGridVolumeRayCastIterator.h" #include "vtkUnstructuredGridPreIntegration.h" #include "vtkUnstructuredGridPartialPreIntegration.h" #include "vtkUnstructuredGridHomogeneousRayIntegrator.h" #include "vtkRayCastImageDisplayHelper.h" #include "vtkDoubleArray.h" #include "vtkIdList.h" #include VTK_THREAD_RETURN_TYPE UnstructuredGridVolumeRayCastMapper_CastRays( void *arg ); vtkCxxRevisionMacro(vtkUnstructuredGridVolumeRayCastMapper, "$Revision: 1.2 $"); vtkStandardNewMacro(vtkUnstructuredGridVolumeRayCastMapper); vtkCxxSetObjectMacro(vtkUnstructuredGridVolumeRayCastMapper, RayCastFunction, vtkUnstructuredGridVolumeRayCastFunction); vtkCxxSetObjectMacro(vtkUnstructuredGridVolumeRayCastMapper, RayIntegrator, vtkUnstructuredGridVolumeRayIntegrator); // Construct a new vtkUnstructuredGridVolumeRayCastMapper with default values vtkUnstructuredGridVolumeRayCastMapper::vtkUnstructuredGridVolumeRayCastMapper() { this->ImageSampleDistance = 1.0; this->MinimumImageSampleDistance = 1.0; this->MaximumImageSampleDistance = 10.0; this->AutoAdjustSampleDistances = 1; this->ImageMemorySize[0] = 0; this->ImageMemorySize[1] = 0; this->Threader = vtkMultiThreader::New(); this->NumberOfThreads = this->Threader->GetNumberOfThreads(); this->Image = NULL; this->RenderTimeTable = NULL; this->RenderVolumeTable = NULL; this->RenderRendererTable = NULL; this->RenderTableSize = 0; this->RenderTableEntries = 0; this->ZBuffer = NULL; this->ZBufferSize[0] = 0; this->ZBufferSize[1] = 0; this->ZBufferOrigin[0] = 0; this->ZBufferOrigin[1] = 0; this->IntermixIntersectingGeometry = 1; this->ImageDisplayHelper = vtkRayCastImageDisplayHelper::New(); this->RayCastFunction = vtkUnstructuredGridBunykRayCastFunction::New(); this->RayIntegrator = NULL; this->RealRayIntegrator = NULL; this->ScalarMode = VTK_SCALAR_MODE_DEFAULT; this->ArrayName = new char[1]; this->ArrayName[0] = '\0'; this->ArrayId = -1; this->ArrayAccessMode = VTK_GET_ARRAY_BY_ID; } // Destruct a vtkUnstructuredGridVolumeRayCastMapper - clean up any memory used vtkUnstructuredGridVolumeRayCastMapper::~vtkUnstructuredGridVolumeRayCastMapper() { this->Threader->Delete(); if ( this->Image ) { delete [] this->Image; } if ( this->RenderTableSize ) { delete [] this->RenderTimeTable; delete [] this->RenderVolumeTable; delete [] this->RenderRendererTable; } this->ImageDisplayHelper->Delete(); this->SetRayCastFunction(NULL); this->SetRayIntegrator(NULL); if (this->RealRayIntegrator) { this->RealRayIntegrator->UnRegister(this); } delete[] this->ArrayName; } float vtkUnstructuredGridVolumeRayCastMapper::RetrieveRenderTime( vtkRenderer *ren, vtkVolume *vol ) { int i; for ( i = 0; i < this->RenderTableEntries; i++ ) { if ( this->RenderVolumeTable[i] == vol && this->RenderRendererTable[i] == ren ) { return this->RenderTimeTable[i]; } } return 0.0; } void vtkUnstructuredGridVolumeRayCastMapper::StoreRenderTime( vtkRenderer *ren, vtkVolume *vol, float time ) { int i; for ( i = 0; i < this->RenderTableEntries; i++ ) { if ( this->RenderVolumeTable[i] == vol && this->RenderRendererTable[i] == ren ) { this->RenderTimeTable[i] = time; return; } } // Need to increase size if ( this->RenderTableEntries >= this->RenderTableSize ) { if ( this->RenderTableSize == 0 ) { this->RenderTableSize = 10; } else { this->RenderTableSize *= 2; } float *oldTimePtr = this->RenderTimeTable; vtkVolume **oldVolumePtr = this->RenderVolumeTable; vtkRenderer **oldRendererPtr = this->RenderRendererTable; this->RenderTimeTable = new float [this->RenderTableSize]; this->RenderVolumeTable = new vtkVolume *[this->RenderTableSize]; this->RenderRendererTable = new vtkRenderer *[this->RenderTableSize]; for (i = 0; i < this->RenderTableEntries; i++ ) { this->RenderTimeTable[i] = oldTimePtr[i]; this->RenderVolumeTable[i] = oldVolumePtr[i]; this->RenderRendererTable[i] = oldRendererPtr[i]; } delete [] oldTimePtr; delete [] oldVolumePtr; delete [] oldRendererPtr; } this->RenderTimeTable[this->RenderTableEntries] = time; this->RenderVolumeTable[this->RenderTableEntries] = vol; this->RenderRendererTable[this->RenderTableEntries] = ren; this->RenderTableEntries++; } void vtkUnstructuredGridVolumeRayCastMapper::SelectScalarArray(int arrayNum) { if ( (this->ArrayId == arrayNum) && (this->ArrayAccessMode == VTK_GET_ARRAY_BY_ID) ) { return; } this->Modified(); this->ArrayId = arrayNum; this->ArrayAccessMode = VTK_GET_ARRAY_BY_ID; } void vtkUnstructuredGridVolumeRayCastMapper::SelectScalarArray(const char *arrayName) { if ( !arrayName || ( (strcmp(this->ArrayName, arrayName) == 0) && (this->ArrayAccessMode == VTK_GET_ARRAY_BY_ID) ) ) { return; } this->Modified(); delete[] this->ArrayName; this->ArrayName = new char[strlen(arrayName) + 1]; strcpy(this->ArrayName, arrayName); this->ArrayAccessMode = VTK_GET_ARRAY_BY_NAME; } // Return the method for obtaining scalar data. const char *vtkUnstructuredGridVolumeRayCastMapper::GetScalarModeAsString(void) { if ( this->ScalarMode == VTK_SCALAR_MODE_USE_CELL_DATA ) { return "UseCellData"; } else if ( this->ScalarMode == VTK_SCALAR_MODE_USE_POINT_DATA ) { return "UsePointData"; } else if ( this->ScalarMode == VTK_SCALAR_MODE_USE_POINT_FIELD_DATA ) { return "UsePointFieldData"; } else if ( this->ScalarMode == VTK_SCALAR_MODE_USE_CELL_FIELD_DATA ) { return "UseCellFieldData"; } else { return "Default"; } } void vtkUnstructuredGridVolumeRayCastMapper::ReleaseGraphicsResources(vtkWindow *) { } void vtkUnstructuredGridVolumeRayCastMapper::Render( vtkRenderer *ren, vtkVolume *vol ) { int i; // Check for input if ( this->GetInput() == NULL ) { vtkErrorMacro(<< "No Input!"); return; } this->Scalars = this->GetScalars(this->GetInput(), this->ScalarMode, this->ArrayAccessMode, this->ArrayId, this->ArrayName, this->CellScalars); if (this->Scalars == NULL) { vtkErrorMacro("Can't use the ray cast mapper without scalars!"); return; } this->GetInput()->UpdateInformation(); this->GetInput()->SetUpdateExtentToWholeExtent(); this->GetInput()->Update(); // Check to make sure we have an appropriate integrator. if (this->RayIntegrator) { if (this->RealRayIntegrator != this->RayIntegrator) { if (this->RealRayIntegrator) { this->RealRayIntegrator->UnRegister(this); } this->RealRayIntegrator = this->RayIntegrator; this->RealRayIntegrator->Register(this); } } else { #define ESTABLISH_INTEGRATOR(classname) \ if ( !this->RealRayIntegrator \ || (!this->RealRayIntegrator->IsA(#classname)) ) \ { \ if (this->RealRayIntegrator) this->RealRayIntegrator->UnRegister(this); \ this->RealRayIntegrator = classname::New(); \ this->RealRayIntegrator->Register(this); \ this->RealRayIntegrator->Delete(); \ } \ if (this->CellScalars) { ESTABLISH_INTEGRATOR(vtkUnstructuredGridHomogeneousRayIntegrator); } else { if (vol->GetProperty()->GetIndependentComponents()) { ESTABLISH_INTEGRATOR(vtkUnstructuredGridPreIntegration); } else { ESTABLISH_INTEGRATOR(vtkUnstructuredGridPartialPreIntegration); } } } #undef ESTABLISH_INTEGRATOR // Start timing now. We didn't want to capture the update of the // input data in the times this->Timer->StartTimer(); int oldImageMemorySize[2]; oldImageMemorySize[0] = this->ImageMemorySize[0]; oldImageMemorySize[1] = this->ImageMemorySize[1]; // If we are automatically adjusting the size to achieve a desired frame // rate, then do that adjustment here. Base the new image sample distance // on the previous one and the previous render time. Don't let // the adjusted image sample distance be less than the minimum image sample // distance or more than the maximum image sample distance. float oldImageSampleDistance = this->ImageSampleDistance; if ( this->AutoAdjustSampleDistances ) { float oldTime = this->RetrieveRenderTime( ren, vol ); float newTime = vol->GetAllocatedRenderTime(); this->ImageSampleDistance *= sqrt(oldTime / newTime); this->ImageSampleDistance = (this->ImageSampleDistance>this->MaximumImageSampleDistance)? (this->MaximumImageSampleDistance):(this->ImageSampleDistance); this->ImageSampleDistance = (this->ImageSampleDistanceMinimumImageSampleDistance)? (this->MinimumImageSampleDistance):(this->ImageSampleDistance); } // The full image fills the viewport. First, compute the actual viewport // size, then divide by the ImageSampleDistance to find the full image // size in pixels int width, height; ren->GetTiledSize(&width, &height); this->ImageViewportSize[0] = static_cast(width/this->ImageSampleDistance); this->ImageViewportSize[1] = static_cast(height/this->ImageSampleDistance); this->ImageInUseSize[0] = this->ImageViewportSize[0]; this->ImageInUseSize[1] = this->ImageViewportSize[1]; this->ImageOrigin[0] = 0; this->ImageOrigin[1] = 0; // What is a power of 2 size big enough to fit this image? this->ImageMemorySize[0] = 32; this->ImageMemorySize[1] = 32; while ( this->ImageMemorySize[0] < this->ImageInUseSize[0] ) { this->ImageMemorySize[0] *= 2; } while ( this->ImageMemorySize[1] < this->ImageInUseSize[1] ) { this->ImageMemorySize[1] *= 2; } // If the old image size is much too big (more than twice in // either direction) then set the old width to 0 which will // cause the image to be recreated if ( oldImageMemorySize[0] > 2*this->ImageMemorySize[0] || oldImageMemorySize[1] > 2*this->ImageMemorySize[1] ) { oldImageMemorySize[0] = 0; } // If the old image is big enough (but not too big - we handled // that above) then we'll bump up our required size to the // previous one. This will keep us from thrashing. if ( oldImageMemorySize[0] >= this->ImageMemorySize[0] && oldImageMemorySize[1] >= this->ImageMemorySize[1] ) { this->ImageMemorySize[0] = oldImageMemorySize[0]; this->ImageMemorySize[1] = oldImageMemorySize[1]; } // Do we already have a texture big enough? If not, create a new one and // clear it. if ( !this->Image || this->ImageMemorySize[0] > oldImageMemorySize[0] || this->ImageMemorySize[1] > oldImageMemorySize[1] ) { // If there is an image there must be row bounds if ( this->Image ) { delete [] this->Image; } this->Image = new unsigned char[(this->ImageMemorySize[0] * this->ImageMemorySize[1] * 4)]; unsigned char *ucptr = this->Image; for ( i = 0; i < this->ImageMemorySize[0]*this->ImageMemorySize[1]; i++ ) { *(ucptr++) = 0; *(ucptr++) = 0; *(ucptr++) = 0; *(ucptr++) = 0; } } // Capture the zbuffer if necessary if ( this->IntermixIntersectingGeometry && ren->GetNumberOfPropsRendered() ) { int x1, x2, y1, y2; double *viewport = ren->GetViewport(); int *renWinSize = ren->GetRenderWindow()->GetSize(); // turn this->ImageOrigin into (x1,y1) in window (not viewport!) // coordinates. x1 = static_cast ( viewport[0] * static_cast(renWinSize[0]) + static_cast(this->ImageOrigin[0]) * this->ImageSampleDistance ); y1 = static_cast ( viewport[1] * static_cast(renWinSize[1]) + static_cast(this->ImageOrigin[1]) * this->ImageSampleDistance); // compute z buffer size this->ZBufferSize[0] = static_cast( static_cast(this->ImageInUseSize[0]) * this->ImageSampleDistance); this->ZBufferSize[1] = static_cast( static_cast(this->ImageInUseSize[1]) * this->ImageSampleDistance); // Use the size to compute (x2,y2) in window coordinates x2 = x1 + this->ZBufferSize[0] - 1; y2 = y1 + this->ZBufferSize[1] - 1; // This is the z buffer origin (in viewport coordinates) this->ZBufferOrigin[0] = static_cast( static_cast(this->ImageOrigin[0]) * this->ImageSampleDistance); this->ZBufferOrigin[1] = static_cast( static_cast(this->ImageOrigin[1]) * this->ImageSampleDistance); // Capture the z buffer this->ZBuffer = ren->GetRenderWindow()->GetZbufferData(x1,y1,x2,y2); } this->RayCastFunction->Initialize( ren, vol ); this->RealRayIntegrator->Initialize(vol, this->Scalars); // Save the volume and mapper temporarily so that they can be accessed later this->CurrentVolume = vol; this->CurrentRenderer = ren; // Create iterators here to prevent race conditions. this->RayCastIterators = new vtkUnstructuredGridVolumeRayCastIterator*[this->NumberOfThreads]; for (i = 0; i < this->NumberOfThreads; i++) { this->RayCastIterators[i] = this->RayCastFunction->NewIterator(); } // Set the number of threads to use for ray casting, // then set the execution method and do it. this->Threader->SetNumberOfThreads( this->NumberOfThreads ); this->Threader->SetSingleMethod( UnstructuredGridVolumeRayCastMapper_CastRays, (void *)this); this->Threader->SingleMethodExecute(); // We don't need these anymore this->CurrentVolume = NULL; this->CurrentRenderer = NULL; for (i = 0; i < this->NumberOfThreads; i++) { this->RayCastIterators[i]->Delete(); } delete[] this->RayCastIterators; if ( !ren->GetRenderWindow()->GetAbortRender() ) { float depth; if ( this->IntermixIntersectingGeometry ) { depth = this->GetMinimumBoundsDepth( ren, vol ); } else { depth = -1; } this->ImageDisplayHelper-> RenderTexture( vol, ren, this->ImageMemorySize, this->ImageViewportSize, this->ImageInUseSize, this->ImageOrigin, depth, this->Image ); this->Timer->StopTimer(); this->TimeToDraw = this->Timer->GetElapsedTime(); this->StoreRenderTime( ren, vol, this->TimeToDraw ); } else { this->ImageSampleDistance = oldImageSampleDistance; } if ( this->ZBuffer ) { delete [] this->ZBuffer; this->ZBuffer = NULL; } this->UpdateProgress(1.0); } VTK_THREAD_RETURN_TYPE UnstructuredGridVolumeRayCastMapper_CastRays( void *arg ) { // Get the info out of the input structure int threadID = ((vtkMultiThreader::ThreadInfo *)(arg))->ThreadID; int threadCount = ((vtkMultiThreader::ThreadInfo *)(arg))->NumberOfThreads; vtkUnstructuredGridVolumeRayCastMapper *me = (vtkUnstructuredGridVolumeRayCastMapper *)((vtkMultiThreader::ThreadInfo *)arg)->UserData; if ( !me ) { vtkGenericWarningMacro("The volume does not have a ray cast mapper!"); return VTK_THREAD_RETURN_VALUE; } me->CastRays( threadID, threadCount ); return VTK_THREAD_RETURN_VALUE; } template inline void vtkUGVRCMLookupCopy(const T *src, T *dest, vtkIdType *lookup, int numcomponents, int numtuples) { for (vtkIdType i = 0; i < numtuples; i++) { const T *srctuple = src + lookup[i] * numcomponents; for (int j = 0; j < numcomponents; j++) { *dest = *srctuple; dest++; srctuple++; } } } void vtkUnstructuredGridVolumeRayCastMapper::CastRays( int threadID, int threadCount ) { int i, j; unsigned char *ucptr; vtkRenderWindow *renWin = this->CurrentRenderer->GetRenderWindow(); vtkUnstructuredGridVolumeRayCastIterator *iterator = this->RayCastIterators[threadID]; vtkDoubleArray *intersectionLengths = vtkDoubleArray::New(); vtkDataArray *nearIntersections = vtkDataArray::CreateDataArray(this->Scalars->GetDataType()); vtkDataArray *farIntersections; vtkIdList *intersectedCells; if (this->CellScalars) { intersectedCells = vtkIdList::New(); farIntersections = nearIntersections; } else { farIntersections = vtkDataArray::CreateDataArray(this->Scalars->GetDataType()); intersectedCells = NULL; } for ( j = 0; j < this->ImageInUseSize[1]; j++ ) { if ( j%threadCount != threadID ) { continue; } if ( !threadID ) { this->UpdateProgress((double)j/this->ImageInUseSize[1]); if ( renWin->CheckAbortStatus() ) { break; } } else if ( renWin->GetAbortRender() ) { break; } ucptr = this->Image + 4*j*this->ImageMemorySize[0]; for ( i = 0; i < this->ImageInUseSize[0]; i++ ) { int x = i + this->ImageOrigin[0]; int y = j + this->ImageOrigin[1]; double bounds[2] = {0.0,1.0}; float color[4] = {0.0f, 0.0f, 0.0f, 0.0f}; if ( this->ZBuffer ) { bounds[1] = this->GetZBufferValue( x, y ); } iterator->SetBounds(bounds); iterator->Initialize(x, y); vtkIdType numIntersections; do { if (this->CellScalars) { numIntersections = iterator->GetNextIntersections(intersectedCells, intersectionLengths, NULL, NULL, NULL); nearIntersections ->SetNumberOfComponents(this->Scalars->GetNumberOfComponents()); nearIntersections->SetNumberOfTuples(numIntersections); switch (this->Scalars->GetDataType()) { vtkTemplateMacro(vtkUGVRCMLookupCopy ((const VTK_TT*)this->Scalars->GetVoidPointer(0), (VTK_TT*)nearIntersections->GetVoidPointer(0), intersectedCells->GetPointer(0), this->Scalars->GetNumberOfComponents(), numIntersections)); } } else { numIntersections = iterator->GetNextIntersections(NULL, intersectionLengths, this->Scalars, nearIntersections, farIntersections); } if (numIntersections < 1) break; this->RealRayIntegrator->Integrate(intersectionLengths, nearIntersections, farIntersections, color); } while (color[3] < 0.99); if ( color[3] > 0.0 ) { int val; val = static_cast((color[0]/color[3])*255.0); val = (val > 255)?(255):(val); val = (val < 0)?( 0):(val); ucptr[0] = static_cast(val); val = static_cast((color[1]/color[3])*255.0); val = (val > 255)?(255):(val); val = (val < 0)?( 0):(val); ucptr[1] = static_cast(val); val = static_cast((color[2]/color[3])*255.0); val = (val > 255)?(255):(val); val = (val < 0)?( 0):(val); ucptr[2] = static_cast(val); val = static_cast(color[3]*255.0); val = (val > 255)?(255):(val); val = (val < 0)?( 0):(val); ucptr[3] = static_cast(val); } else { ucptr[0] = 0; ucptr[1] = 0; ucptr[2] = 0; ucptr[3] = 0; } ucptr+=4; } } intersectionLengths->Delete(); nearIntersections->Delete(); if (!this->CellScalars) { farIntersections->Delete(); } else { intersectedCells->Delete(); } } double vtkUnstructuredGridVolumeRayCastMapper:: GetMinimumBoundsDepth( vtkRenderer *ren, vtkVolume *vol ) { double bounds[6]; vol->GetBounds( bounds ); vtkTransform *perspectiveTransform = vtkTransform::New(); vtkMatrix4x4 *perspectiveMatrix = vtkMatrix4x4::New(); ren->ComputeAspect(); double *aspect = ren->GetAspect(); // 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->GetPerspectiveTransformMatrix(aspect[0]/aspect[1], 0.0, 1.0 )); perspectiveTransform->Concatenate(cam->GetViewTransformMatrix()); perspectiveMatrix->DeepCopy(perspectiveTransform->GetMatrix()); double minZ = 1.0; for ( int k = 0; k < 2; k++ ) { for ( int j = 0; j < 2; j++ ) { for ( int i = 0; i < 2; i++ ) { double inPoint[4]; inPoint[0] = bounds[ i]; inPoint[1] = bounds[2+j]; inPoint[2] = bounds[4+k]; inPoint[3] = 1.0; double outPoint[4]; perspectiveMatrix->MultiplyPoint( inPoint, outPoint ); double testZ = outPoint[2] / outPoint[3]; minZ = ( testZ < minZ ) ? (testZ) : (minZ); } } } perspectiveTransform->Delete(); perspectiveMatrix->Delete(); return minZ; } double vtkUnstructuredGridVolumeRayCastMapper::GetZBufferValue(int x, int y) { int xPos, yPos; xPos = static_cast(static_cast(x) * this->ImageSampleDistance); yPos = static_cast(static_cast(y) * this->ImageSampleDistance); xPos = (xPos >= this->ZBufferSize[0])?(this->ZBufferSize[0]-1):(xPos); yPos = (yPos >= this->ZBufferSize[1])?(this->ZBufferSize[1]-1):(yPos); return *(this->ZBuffer + yPos*this->ZBufferSize[0] + xPos); } // Print method for vtkUnstructuredGridVolumeRayCastMapper void vtkUnstructuredGridVolumeRayCastMapper::PrintSelf(ostream& os, vtkIndent indent) { this->Superclass::PrintSelf(os,indent); os << indent << "ScalarMode: " << this->GetScalarModeAsString() << endl; if (this->ArrayAccessMode == VTK_GET_ARRAY_BY_ID) { os << indent << "ArrayId: " << this->ArrayId << endl; } else { os << indent << "ArrayName: " << this->ArrayName << endl; } os << indent << "Image Sample Distance: " << this->ImageSampleDistance << "\n"; os << indent << "Minimum Image Sample Distance: " << this->MinimumImageSampleDistance << "\n"; os << indent << "Maximum Image Sample Distance: " << this->MaximumImageSampleDistance << "\n"; os << indent << "Auto Adjust Sample Distances: " << this->AutoAdjustSampleDistances << "\n"; os << indent << "Intermix Intersecting Geometry: " << (this->IntermixIntersectingGeometry ? "On\n" : "Off\n"); os << indent << "Number Of Threads: " << this->NumberOfThreads << "\n"; if (this->RayCastFunction) { os << indent << "RayCastFunction: " << this->RayCastFunction->GetClassName() << "\n"; } else { os << indent << "RayCastFunction: (none)\n"; } if (this->RayIntegrator) { os << indent << "RayIntegrator: " << this->RayIntegrator->GetClassName() << endl; } else { os << indent << "RayIntegrator: (automatic)" << endl; } // Do not want to print this->ImageOrigin, this->ImageViewportSize or // this->ImageInUseSize since these are just internal variables with Get // methods for access from the ray cast function (not part of the public // API) }