/*========================================================================= Program: Visualization Toolkit Module: $RCSfile: vtkVolumeTextureMapper3D.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 "vtkVolumeTextureMapper3D.h" #include "vtkVolumeRenderingFactory.h" #include "vtkRenderer.h" #include "vtkVolume.h" #include "vtkCamera.h" #include "vtkMath.h" #include "vtkPointData.h" #include "vtkImageData.h" #include "vtkColorTransferFunction.h" #include "vtkPiecewiseFunction.h" #include "vtkVolumeProperty.h" #include "vtkMatrix4x4.h" vtkCxxRevisionMacro(vtkVolumeTextureMapper3D, "$Revision: 1.6 $"); //---------------------------------------------------------------------------- // Needed when we don't use the vtkStandardNewMacro. vtkInstantiatorNewMacro(vtkVolumeTextureMapper3D); //---------------------------------------------------------------------------- // This method moves the scalars from the input volume into volume1 (and // possibly volume2) which are the 3D texture maps used for rendering. // // In the case where our volume is a power of two, the copy is done // directly. If we need to resample, then trilinear interpolation is used. // // A shift/scale is applied to the input scalar value to produce an 8 bit // value for the texture volume. // // When the input data is one component, the scalar value is placed in the // second component of the two component volume1. The first component is // filled in later with the gradient magnitude. // // When the input data is two component non-independent, the first component // of the input data is placed in the first component of volume1, and the // second component of the input data is placed in the third component of // volume1. Volume1 has three components - the second is filled in later with // the gradient magnitude. // // When the input data is four component non-independent, the first three // components of the input data are placed in volume1 (which has three // components), and the fourth component is placed in the second component // of volume2. The first component of volume2 is later filled in with the // gradient magnitude. template void vtkVolumeTextureMapper3DComputeScalars( T *dataPtr, vtkVolumeTextureMapper3D *me, float offset, float scale, unsigned char *volume1, unsigned char *volume2) { T *inPtr; unsigned char *outPtr, *outPtr2; int i, j, k; int idx; int inputDimensions[3]; double inputSpacing[3]; me->GetInput()->GetDimensions( inputDimensions ); me->GetInput()->GetSpacing( inputSpacing ); int outputDimensions[3]; float outputSpacing[3]; me->GetVolumeDimensions( outputDimensions ); me->GetVolumeSpacing( outputSpacing ); int components = me->GetInput()->GetNumberOfScalarComponents(); double wx, wy, wz; double fx, fy, fz; int x, y, z; double sampleRate[3]; sampleRate[0] = (double)outputSpacing[0] / (double)inputSpacing[0]; sampleRate[1] = (double)outputSpacing[1] / (double)inputSpacing[1]; sampleRate[2] = (double)outputSpacing[2] / (double)inputSpacing[2]; // This is the case where no interpolation is needed if ( inputDimensions[0] == outputDimensions[0] && inputDimensions[1] == outputDimensions[1] && inputDimensions[2] == outputDimensions[2] ) { int size = outputDimensions[0] * outputDimensions[1] * outputDimensions[2]; inPtr = dataPtr; if ( components == 1 ) { outPtr = volume1; if ( scale == 1.0 ) { for ( i = 0; i < size; i++ ) { idx = static_cast(*(inPtr++) + offset); *(outPtr++) = 0; *(outPtr++) = idx; } } else { for ( i = 0; i < size; i++ ) { idx = static_cast((*(inPtr++) + offset) * scale); *(outPtr++) = 0; *(outPtr++) = idx; } } } else if ( components == 2 ) { outPtr = volume1; if ( scale == 1.0 ) { for ( i = 0; i < size; i++ ) { idx = static_cast(*(inPtr++) + offset); *(outPtr++) = idx; *(outPtr++) = 0; idx = static_cast(*(inPtr++) + offset); *(outPtr++) = idx; } } else { for ( i = 0; i < size; i++ ) { idx = static_cast((*(inPtr++) + offset) * scale); *(outPtr++) = idx; *(outPtr++) = 0; idx = static_cast((*(inPtr++) + offset) * scale); *(outPtr++) = idx; } } } else if ( components == 4 ) { outPtr = volume1; outPtr2 = volume2; if ( scale == 1.0 ) { for ( i = 0; i < size; i++ ) { idx = static_cast(*(inPtr++) + offset); *(outPtr++) = idx; idx = static_cast(*(inPtr++) + offset); *(outPtr++) = idx; idx = static_cast(*(inPtr++) + offset); *(outPtr++) = idx; *(outPtr2++) = 0; idx = static_cast(*(inPtr++) + offset); *(outPtr2++) = idx; } } else { for ( i = 0; i < size; i++ ) { idx = static_cast((*(inPtr++) + offset) * scale); *(outPtr++) = idx; idx = static_cast((*(inPtr++) + offset) * scale); *(outPtr++) = idx; idx = static_cast((*(inPtr++) + offset) * scale); *(outPtr++) = idx; *(outPtr2++) = 0; idx = static_cast((*(inPtr++) + offset) * scale); *(outPtr2++) = idx; } } } } // The sizes are different and interpolation is required else { outPtr = volume1; outPtr2 = volume2; for ( k = 0; k < outputDimensions[2]; k++ ) { fz = k * sampleRate[2]; fz = (fz >= inputDimensions[2]-1)?(inputDimensions[2]-1.001):(fz); z = vtkMath::Floor( fz ); wz = fz - z; for ( j = 0; j < outputDimensions[1]; j++ ) { fy = j * sampleRate[1]; fy = (fy >= inputDimensions[1]-1)?(inputDimensions[1]-1.001):(fy); y = vtkMath::Floor( fy ); wy = fy - y; for ( i = 0; i < outputDimensions[0]; i++ ) { fx = i * sampleRate[0]; fx = (fx >= inputDimensions[0]-1)?(inputDimensions[0]-1.001):(fx); x = vtkMath::Floor( fx ); wx = fx - x; inPtr = dataPtr + components * ( z*inputDimensions[0]*inputDimensions[1] + y*inputDimensions[0] + x ); if ( components == 1 ) { float A, B, C, D, E, F, G, H; A = static_cast(*(inPtr)); B = static_cast(*(inPtr+1)); C = static_cast(*(inPtr+inputDimensions[0])); D = static_cast(*(inPtr+inputDimensions[0]+1)); E = static_cast(*(inPtr+inputDimensions[0]*inputDimensions[1])); F = static_cast(*(inPtr+inputDimensions[0]*inputDimensions[1]+1)); G = static_cast(*(inPtr+inputDimensions[0]*inputDimensions[1]+inputDimensions[0])); H = static_cast(*(inPtr+inputDimensions[0]*inputDimensions[1]+inputDimensions[0]+1)); float val = (1.0-wx)*(1.0-wy)*(1.0-wz)*A + ( wx)*(1.0-wy)*(1.0-wz)*B + (1.0-wx)*( wy)*(1.0-wz)*C + ( wx)*( wy)*(1.0-wz)*D + (1.0-wx)*(1.0-wy)*( wz)*E + ( wx)*(1.0-wy)*( wz)*F + (1.0-wx)*( wy)*( wz)*G + ( wx)*( wy)*( wz)*H; idx = static_cast((val + offset)*scale); *(outPtr++) = 0; *(outPtr++) = idx; } else if ( components == 2 ) { float A1, B1, C1, D1, E1, F1, G1, H1; float A2, B2, C2, D2, E2, F2, G2, H2; A1 = static_cast(*(inPtr)); A2 = static_cast(*(inPtr+1)); B1 = static_cast(*(inPtr+2)); B2 = static_cast(*(inPtr+3)); C1 = static_cast(*(inPtr+2*inputDimensions[0])); C2 = static_cast(*(inPtr+2*inputDimensions[0]+1)); D1 = static_cast(*(inPtr+2*inputDimensions[0]+2)); D2 = static_cast(*(inPtr+2*inputDimensions[0]+3)); E1 = static_cast(*(inPtr+2*inputDimensions[0]*inputDimensions[1])); E2 = static_cast(*(inPtr+2*inputDimensions[0]*inputDimensions[1]+1)); F1 = static_cast(*(inPtr+2*inputDimensions[0]*inputDimensions[1]+2)); F2 = static_cast(*(inPtr+2*inputDimensions[0]*inputDimensions[1]+3)); G1 = static_cast(*(inPtr+2*inputDimensions[0]*inputDimensions[1]+2*inputDimensions[0])); G2 = static_cast(*(inPtr+2*inputDimensions[0]*inputDimensions[1]+2*inputDimensions[0]+1)); H1 = static_cast(*(inPtr+2*inputDimensions[0]*inputDimensions[1]+2*inputDimensions[0]+2)); H2 = static_cast(*(inPtr+2*inputDimensions[0]*inputDimensions[1]+2*inputDimensions[0]+3)); float val1 = (1.0-wx)*(1.0-wy)*(1.0-wz)*A1 + ( wx)*(1.0-wy)*(1.0-wz)*B1 + (1.0-wx)*( wy)*(1.0-wz)*C1 + ( wx)*( wy)*(1.0-wz)*D1 + (1.0-wx)*(1.0-wy)*( wz)*E1 + ( wx)*(1.0-wy)*( wz)*F1 + (1.0-wx)*( wy)*( wz)*G1 + ( wx)*( wy)*( wz)*H1; float val2 = (1.0-wx)*(1.0-wy)*(1.0-wz)*A2 + ( wx)*(1.0-wy)*(1.0-wz)*B2 + (1.0-wx)*( wy)*(1.0-wz)*C2 + ( wx)*( wy)*(1.0-wz)*D2 + (1.0-wx)*(1.0-wy)*( wz)*E2 + ( wx)*(1.0-wy)*( wz)*F2 + (1.0-wx)*( wy)*( wz)*G2 + ( wx)*( wy)*( wz)*H2; idx = static_cast((val1 + offset) * scale); *(outPtr++) = idx; *(outPtr++) = 0; idx = static_cast((val2 + offset) * scale); *(outPtr++) = idx; } else { float Ar, Br, Cr, Dr, Er, Fr, Gr, Hr; float Ag, Bg, Cg, Dg, Eg, Fg, Gg, Hg; float Ab, Bb, Cb, Db, Eb, Fb, Gb, Hb; float Aa, Ba, Ca, Da, Ea, Fa, Ga, Ha; Ar = static_cast(*(inPtr)); Ag = static_cast(*(inPtr+1)); Ab = static_cast(*(inPtr+2)); Aa = static_cast(*(inPtr+3)); Br = static_cast(*(inPtr+4)); Bg = static_cast(*(inPtr+5)); Bb = static_cast(*(inPtr+6)); Ba = static_cast(*(inPtr+7)); Cr = static_cast(*(inPtr+4*inputDimensions[0])); Cg = static_cast(*(inPtr+4*inputDimensions[0]+1)); Cb = static_cast(*(inPtr+4*inputDimensions[0]+2)); Ca = static_cast(*(inPtr+4*inputDimensions[0]+3)); Dr = static_cast(*(inPtr+4*inputDimensions[0]+4)); Dg = static_cast(*(inPtr+4*inputDimensions[0]+5)); Db = static_cast(*(inPtr+4*inputDimensions[0]+6)); Da = static_cast(*(inPtr+4*inputDimensions[0]+7)); Er = static_cast(*(inPtr+4*inputDimensions[0]*inputDimensions[1])); Eg = static_cast(*(inPtr+4*inputDimensions[0]*inputDimensions[1]+1)); Eb = static_cast(*(inPtr+4*inputDimensions[0]*inputDimensions[1]+2)); Ea = static_cast(*(inPtr+4*inputDimensions[0]*inputDimensions[1]+3)); Fr = static_cast(*(inPtr+4*inputDimensions[0]*inputDimensions[1]+4)); Fg = static_cast(*(inPtr+4*inputDimensions[0]*inputDimensions[1]+5)); Fb = static_cast(*(inPtr+4*inputDimensions[0]*inputDimensions[1]+6)); Fa = static_cast(*(inPtr+4*inputDimensions[0]*inputDimensions[1]+7)); Gr = static_cast(*(inPtr+4*inputDimensions[0]*inputDimensions[1]+4*inputDimensions[0])); Gg = static_cast(*(inPtr+4*inputDimensions[0]*inputDimensions[1]+4*inputDimensions[0]+1)); Gb = static_cast(*(inPtr+4*inputDimensions[0]*inputDimensions[1]+4*inputDimensions[0]+2)); Ga = static_cast(*(inPtr+4*inputDimensions[0]*inputDimensions[1]+4*inputDimensions[0]+3)); Hr = static_cast(*(inPtr+4*inputDimensions[0]*inputDimensions[1]+4*inputDimensions[0]+4)); Hg = static_cast(*(inPtr+4*inputDimensions[0]*inputDimensions[1]+4*inputDimensions[0]+5)); Hb = static_cast(*(inPtr+4*inputDimensions[0]*inputDimensions[1]+4*inputDimensions[0]+6)); Ha = static_cast(*(inPtr+4*inputDimensions[0]*inputDimensions[1]+4*inputDimensions[0]+7)); float valr = (1.0-wx)*(1.0-wy)*(1.0-wz)*Ar + ( wx)*(1.0-wy)*(1.0-wz)*Br + (1.0-wx)*( wy)*(1.0-wz)*Cr + ( wx)*( wy)*(1.0-wz)*Dr + (1.0-wx)*(1.0-wy)*( wz)*Er + ( wx)*(1.0-wy)*( wz)*Fr + (1.0-wx)*( wy)*( wz)*Gr + ( wx)*( wy)*( wz)*Hr; float valg = (1.0-wx)*(1.0-wy)*(1.0-wz)*Ag + ( wx)*(1.0-wy)*(1.0-wz)*Bg + (1.0-wx)*( wy)*(1.0-wz)*Cg + ( wx)*( wy)*(1.0-wz)*Dg + (1.0-wx)*(1.0-wy)*( wz)*Eg + ( wx)*(1.0-wy)*( wz)*Fg + (1.0-wx)*( wy)*( wz)*Gg + ( wx)*( wy)*( wz)*Hg; float valb = (1.0-wx)*(1.0-wy)*(1.0-wz)*Ab + ( wx)*(1.0-wy)*(1.0-wz)*Bb + (1.0-wx)*( wy)*(1.0-wz)*Cb + ( wx)*( wy)*(1.0-wz)*Db + (1.0-wx)*(1.0-wy)*( wz)*Eb + ( wx)*(1.0-wy)*( wz)*Fb + (1.0-wx)*( wy)*( wz)*Gb + ( wx)*( wy)*( wz)*Hb; float vala = (1.0-wx)*(1.0-wy)*(1.0-wz)*Aa + ( wx)*(1.0-wy)*(1.0-wz)*Ba + (1.0-wx)*( wy)*(1.0-wz)*Ca + ( wx)*( wy)*(1.0-wz)*Da + (1.0-wx)*(1.0-wy)*( wz)*Ea + ( wx)*(1.0-wy)*( wz)*Fa + (1.0-wx)*( wy)*( wz)*Ga + ( wx)*( wy)*( wz)*Ha; idx = static_cast((valr + offset) * scale); *(outPtr++) = idx; idx = static_cast((valg + offset) * scale); *(outPtr++) = idx; idx = static_cast((valb + offset) * scale); *(outPtr++) = idx; *(outPtr2++) = 0; idx = static_cast((vala + offset) * scale); *(outPtr2++) = idx; } } } } } } template void vtkVolumeTextureMapper3DComputeGradients( T *dataPtr, vtkVolumeTextureMapper3D *me, double scalarRange[2], unsigned char *volume1, unsigned char *volume2, unsigned char *volume3) { int x, y, z; int offset, outputOffset; int x_start, x_limit; int y_start, y_limit; int z_start, z_limit; T *dptr; float n[3], t; float gvalue; float zeroNormalThreshold; int xlow, xhigh; double aspect[3]; unsigned char *outPtr1, *outPtr2; unsigned char *normals, *gradmags; int gradmagIncrement; int gradmagOffset; double floc[3]; int loc[3]; // me->InvokeEvent( vtkEvent::VolumeMapperComputeGradientsStartEvent, NULL ); float outputSpacing[3]; me->GetVolumeSpacing( outputSpacing ); double spacing[3]; me->GetInput()->GetSpacing( spacing ); double sampleRate[3]; sampleRate[0] = (double)outputSpacing[0] / (double)spacing[0]; sampleRate[1] = (double)outputSpacing[1] / (double)spacing[1]; sampleRate[2] = (double)outputSpacing[2] / (double)spacing[2]; int components = me->GetInput()->GetNumberOfScalarComponents(); int dim[3]; me->GetInput()->GetDimensions(dim); int outputDim[3]; me->GetVolumeDimensions( outputDim ); double avgSpacing = ((double)spacing[0] + (double)spacing[1] + (double)spacing[2]) / 3.0; // adjust the aspect aspect[0] = (double)spacing[0] * 2.0 / avgSpacing; aspect[1] = (double)spacing[1] * 2.0 / avgSpacing; aspect[2] = (double)spacing[2] * 2.0 / avgSpacing; float scale; scale = 255.0 / (0.25*(scalarRange[1] - scalarRange[0])); // Get the length at or below which normals are considered to // be "zero" zeroNormalThreshold =.001 * (scalarRange[1] - scalarRange[0]); int thread_id = 0; int thread_count = 1; x_start = 0; x_limit = outputDim[0]; y_start = 0; y_limit = outputDim[1]; z_start = (int)(( (float)thread_id / (float)thread_count ) * outputDim[2] ); z_limit = (int)(( (float)(thread_id + 1) / (float)thread_count ) * outputDim[2] ); // Do final error checking on limits - make sure they are all within bounds // of the scalar input x_start = (x_start<0)?(0):(x_start); y_start = (y_start<0)?(0):(y_start); z_start = (z_start<0)?(0):(z_start); x_limit = (x_limit>dim[0])?(outputDim[0]):(x_limit); y_limit = (y_limit>dim[1])?(outputDim[1]):(y_limit); z_limit = (z_limit>dim[2])?(outputDim[2]):(z_limit); if ( components == 1 || components == 2 ) { normals = volume2; gradmags = volume1; gradmagIncrement = components+1; gradmagOffset = components-1; } else { normals = volume3; gradmags = volume2; gradmagIncrement = 2; gradmagOffset = 0; } double wx, wy, wz; // Loop through all the data and compute the encoded normal and // gradient magnitude for each scalar location for ( z = z_start; z < z_limit; z++ ) { floc[2] = z*sampleRate[2]; floc[2] = (floc[2]>=(dim[2]-1))?(dim[2]-1.001):(floc[2]); loc[2] = vtkMath::Floor(floc[2]); wz = floc[2] - loc[2]; for ( y = y_start; y < y_limit; y++ ) { floc[1] = y*sampleRate[1]; floc[1] = (floc[1]>=(dim[1]-1))?(dim[1]-1.001):(floc[1]); loc[1] = vtkMath::Floor(floc[1]); wy = floc[1] - loc[1]; xlow = x_start; xhigh = x_limit; outputOffset = z * outputDim[0] * outputDim[1] + y * outputDim[0] + xlow; // Set some pointers outPtr1 = gradmags + gradmagIncrement*outputOffset; outPtr2 = normals + 3*outputOffset; for ( x = xlow; x < xhigh; x++ ) { floc[0] = x*sampleRate[0]; floc[0] = (floc[0]>=(dim[0]-1))?(dim[0]-1.001):(floc[0]); loc[0] = vtkMath::Floor(floc[0]); wx = floc[0] - loc[0]; offset = loc[2] * dim[0] * dim[1] + loc[1] * dim[0] + loc[0]; dptr = dataPtr + components*offset + components - 1; // Use a central difference method if possible, // otherwise use a forward or backward difference if // we are on the edge int sampleOffset[6]; sampleOffset[0] = (loc[0]<1) ?(0):(-components); sampleOffset[1] = (loc[0]>=dim[0]-2)?(0):( components); sampleOffset[2] = (loc[1]<1) ?(0):(-components*dim[0]); sampleOffset[3] = (loc[1]>=dim[1]-2)?(0):( components*dim[0]); sampleOffset[4] = (loc[2]<1) ?(0):(-components*dim[0]*dim[1]); sampleOffset[5] = (loc[2]>=dim[2]-2)?(0):( components*dim[0]*dim[1]); float sample[6]; for ( int i = 0; i < 6; i++ ) { float A, B, C, D, E, F, G, H; T *samplePtr = dptr + sampleOffset[i]; A = static_cast(*(samplePtr)); B = static_cast(*(samplePtr+components)); C = static_cast(*(samplePtr+components*dim[0])); D = static_cast(*(samplePtr+components*dim[0]+components)); E = static_cast(*(samplePtr+components*dim[0]*dim[1])); F = static_cast(*(samplePtr+components*dim[0]*dim[1]+components)); G = static_cast(*(samplePtr+components*dim[0]*dim[1]+components*dim[0])); H = static_cast(*(samplePtr+components*dim[0]*dim[1]+components*dim[0]+components)); sample[i] = (1.0-wx)*(1.0-wy)*(1.0-wz)*A + ( wx)*(1.0-wy)*(1.0-wz)*B + (1.0-wx)*( wy)*(1.0-wz)*C + ( wx)*( wy)*(1.0-wz)*D + (1.0-wx)*(1.0-wy)*( wz)*E + ( wx)*(1.0-wy)*( wz)*F + (1.0-wx)*( wy)*( wz)*G + ( wx)*( wy)*( wz)*H; } n[0] = ((sampleOffset[0]==0 || sampleOffset[1]==0)?(2.0):(1.0))*(sample[0] -sample[1]); n[1] = ((sampleOffset[2]==0 || sampleOffset[3]==0)?(2.0):(1.0))*(sample[2] -sample[3]); n[2] = ((sampleOffset[4]==0 || sampleOffset[5]==0)?(2.0):(1.0))*(sample[4] -sample[5]); // Take care of the aspect ratio of the data // Scaling in the vtkVolume is isotropic, so this is the // only place we have to worry about non-isotropic scaling. n[0] /= aspect[0]; n[1] /= aspect[1]; n[2] /= aspect[2]; // Compute the gradient magnitude t = sqrt( (double)( n[0]*n[0] + n[1]*n[1] + n[2]*n[2] ) ); // Encode this into an 4 bit value gvalue = t * scale; gvalue = (gvalue<0.0)?(0.0):(gvalue); gvalue = (gvalue>255.0)?(255.0):(gvalue); *(outPtr1+gradmagOffset) = static_cast(gvalue + 0.5); // Normalize the gradient direction if ( t > zeroNormalThreshold ) { n[0] /= t; n[1] /= t; n[2] /= t; } else { n[0] = n[1] = n[2] = 0.0; } int nx = static_cast((n[0] / 2.0 + 0.5)*255.0 + 0.5); int ny = static_cast((n[1] / 2.0 + 0.5)*255.0 + 0.5); int nz = static_cast((n[2] / 2.0 + 0.5)*255.0 + 0.5); nx = (nx<0)?(0):(nx); ny = (ny<0)?(0):(ny); nz = (nz<0)?(0):(nz); nx = (nx>255)?(255):(nx); ny = (ny>255)?(255):(ny); nz = (nz>255)?(255):(nz); *(outPtr2 ) = nx; *(outPtr2+1) = ny; *(outPtr2+2) = nz; outPtr1 += gradmagIncrement; outPtr2 += 3; } } // if ( z%8 == 7 ) // { // float args[1]; // args[0] = // static_cast(z - z_start) / // static_cast(z_limit - z_start - 1); // me->InvokeEvent( vtkEvent::VolumeMapperComputeGradientsProgressEvent, args ); // } } // me->InvokeEvent( vtkEvent::VolumeMapperComputeGradientsEndEvent, NULL ); } vtkVolumeTextureMapper3D::vtkVolumeTextureMapper3D() { this->PolygonBuffer = NULL; this->IntersectionBuffer = NULL; this->NumberOfPolygons = 0; this->BufferSize = 0; // The input used when creating the textures this->SavedTextureInput = NULL; // The input used when creating the color tables this->SavedParametersInput = NULL; this->SavedRGBFunction = NULL; this->SavedGrayFunction = NULL; this->SavedScalarOpacityFunction = NULL; this->SavedColorChannels = 0; this->SavedSampleDistance = 0; this->SavedScalarOpacityDistance = 0; this->Volume1 = NULL; this->Volume2 = NULL; this->Volume3 = NULL; this->VolumeSize = 0; this->VolumeComponents = 0; this->SampleDistance = 1.0; this->ActualSampleDistance = 1.0; this->RenderMethod = vtkVolumeTextureMapper3D::NO_METHOD; this->PreferredRenderMethod = vtkVolumeTextureMapper3D::FRAGMENT_PROGRAM_METHOD; } vtkVolumeTextureMapper3D::~vtkVolumeTextureMapper3D() { delete [] this->PolygonBuffer; delete [] this->IntersectionBuffer; delete [] this->Volume1; delete [] this->Volume2; delete [] this->Volume3; } vtkVolumeTextureMapper3D *vtkVolumeTextureMapper3D::New() { // First try to create the object from the vtkObjectFactory vtkObject* ret = vtkVolumeRenderingFactory::CreateInstance("vtkVolumeTextureMapper3D"); return (vtkVolumeTextureMapper3D*)ret; } void vtkVolumeTextureMapper3D::ComputePolygons( vtkRenderer *ren, vtkVolume *vol, double inBounds[6] ) { // Get the camera position and focal point double focalPoint[4], position[4]; double plane[4]; vtkCamera *camera = ren->GetActiveCamera(); camera->GetPosition( position ); camera->GetFocalPoint( focalPoint ); position[3] = 1.0; focalPoint[3] = 1.0; // Pass the focal point and position through the inverse of the // volume's matrix to map back into the data coordinates. We // are going to compute these polygons in the coordinate system // of the input data - this is easiest since this data must be // axis aligned. Then we'll use OpenGL to transform these polygons // into the world coordinate system through the use of the // volume's matrix. vtkMatrix4x4 *matrix = vtkMatrix4x4::New(); vol->GetMatrix( matrix ); matrix->Invert(); matrix->MultiplyPoint( position, position ); matrix->MultiplyPoint( focalPoint, focalPoint ); matrix->Delete(); if ( position[3] ) { position[0] /= position[3]; position[1] /= position[3]; position[2] /= position[3]; } if ( focalPoint[3] ) { focalPoint[0] /= focalPoint[3]; focalPoint[1] /= focalPoint[3]; focalPoint[2] /= focalPoint[3]; } // Create a plane equation using the direction and position of the camera plane[0] = (double)focalPoint[0] - (double)position[0]; plane[1] = (double)focalPoint[1] - (double)position[1]; plane[2] = (double)focalPoint[2] - (double)position[2]; vtkMath::Normalize( plane ); plane[3] = -(plane[0] * (double)position[0] + plane[1] * (double)position[1] + plane[2] * (double)position[2]); // Find the min and max distances of the boundary points of the volume double minDistance = VTK_FLOAT_MAX; double maxDistance = -VTK_FLOAT_MAX; // The inBounds parameter is the bounds we are using for clipping the // texture planes against. First we need to clip these against the bounds // of the volume to make sure they don't exceed it. double volBounds[6]; this->GetInput()->GetBounds( volBounds ); double bounds[6]; bounds[0] = (inBounds[0]>volBounds[0])?(inBounds[0]):(volBounds[0]); bounds[1] = (inBounds[1]volBounds[2])?(inBounds[2]):(volBounds[2]); bounds[3] = (inBounds[3]volBounds[4])?(inBounds[4]):(volBounds[4]); bounds[5] = (inBounds[5]maxDistance)?(d):(maxDistance); } } } int dim[6]; this->GetVolumeDimensions(dim); float tCoordOffset[3], tCoordScale[3]; tCoordOffset[0] = 0.5 / static_cast( dim[0] ); tCoordOffset[1] = 0.5 / static_cast( dim[1] ); tCoordOffset[2] = 0.5 / static_cast( dim[2] ); tCoordScale[0] = static_cast( dim[0]-1 ) / static_cast( dim[0] ); tCoordScale[1] = static_cast( dim[1]-1 ) / static_cast( dim[1] ); tCoordScale[2] = static_cast( dim[2]-1 ) / static_cast( dim[2] ); float spacing[3]; this->GetVolumeSpacing( spacing ); double offset = 0.333 * 0.5 * (spacing[0] + spacing[1] + spacing[2]); minDistance += 0.1*offset; maxDistance -= 0.1*offset; minDistance = (minDistance < offset)?(offset):(minDistance); double stepSize = this->ActualSampleDistance; // Determine the number of polygons int numPolys = (int)((maxDistance - minDistance)/(double)stepSize); // Check if we have space, free old space only if it is too small if ( this->BufferSize < numPolys ) { delete [] this->PolygonBuffer; delete [] this->IntersectionBuffer; this->BufferSize = numPolys; this->PolygonBuffer = new float [36*this->BufferSize]; this->IntersectionBuffer = new float [12*this->BufferSize]; } this->NumberOfPolygons = numPolys; // Compute the intersection points for each edge of the volume int lines[12][2] = { {0,1}, {1,3}, {2,3}, {0,2}, {4,5}, {5,7}, {6,7}, {4,6}, {0,4}, {1,5}, {3,7}, {2,6} }; float *iptr, *pptr; for ( i = 0; i < 12; i++ ) { double line[3]; line[0] = vertices[lines[i][1]][0] - vertices[lines[i][0]][0]; line[1] = vertices[lines[i][1]][1] - vertices[lines[i][0]][1]; line[2] = vertices[lines[i][1]][2] - vertices[lines[i][0]][2]; double d = maxDistance; iptr = this->IntersectionBuffer + i; double planeDotLineOrigin = vtkMath::Dot( plane, vertices[lines[i][0]] ); double planeDotLine = vtkMath::Dot( plane, line ); double t, increment; if ( planeDotLine != 0.0 ) { t = (d - planeDotLineOrigin - plane[3] ) / planeDotLine; increment = -stepSize / planeDotLine; } else { t = -1.0; increment = 0.0; } for ( j = 0; j < numPolys; j++ ) { *iptr = (t > 0.0 && t < 1.0)?(t):(-1.0); t += increment; iptr += 12; } } // Compute the polgons by determining which edges were intersected int neighborLines[12][6] = { { 1, 2, 3, 4, 8, 9}, { 0, 2, 3, 5, 9, 10}, { 0, 1, 3, 6, 10, 11}, { 0, 1, 2, 7, 8, 11}, { 0, 5, 6, 7, 8, 9}, { 1, 4, 6, 7, 9, 10}, { 2, 4, 5, 7, 10, 11}, { 3, 4, 5, 6, 8, 11}, { 0, 3, 4, 7, 9, 11}, { 0, 1, 4, 5, 8, 10}, { 1, 2, 5, 6, 9, 11}, { 2, 3, 6, 7, 8, 10} }; float tCoord[12][4] = {{0,0,0,0}, {1,0,0,1}, {0,1,0,0}, {0,0,0,1}, {0,0,1,0}, {1,0,1,1}, {0,1,1,0}, {0,0,1,1}, {0,0,0,2}, {1,0,0,2}, {1,1,0,2}, {0,1,0,2}}; double low[3]; double high[3]; low[0] = (bounds[0] - (double)volBounds[0]) / ((double)volBounds[1] - (double)volBounds[0]); high[0] = (bounds[1] - (double)volBounds[0]) / ((double)volBounds[1] - (double)volBounds[0]); low[1] = (bounds[2] - (double)volBounds[2]) / ((double)volBounds[3] - (double)volBounds[2]); high[1] = (bounds[3] - (double)volBounds[2]) / ((double)volBounds[3] - (double)volBounds[2]); low[2] = (bounds[4] - (double)volBounds[4]) / ((double)volBounds[5] - (double)volBounds[4]); high[2] = (bounds[5] - (double)volBounds[4]) / ((double)volBounds[5] - (double)volBounds[4]); for ( i = 0; i < 12; i++ ) { tCoord[i][0] = (tCoord[i][0])?(high[0]):(low[0]); tCoord[i][1] = (tCoord[i][1])?(high[1]):(low[1]); tCoord[i][2] = (tCoord[i][2])?(high[2]):(low[2]); } iptr = this->IntersectionBuffer; pptr = this->PolygonBuffer; for ( i = 0; i < numPolys; i++ ) { // Look for a starting point int start = 0; while ( start < 12 && iptr[start] == -1.0 ) { start++; } if ( start == 12 ) { pptr[0] = -1.0; } else { int current = start; int previous = -1; int errFlag = 0; idx = 0; while ( idx < 6 && !errFlag && ( idx == 0 || current != start) ) { double t = iptr[current]; *(pptr + idx*6) = tCoord[current][0] * tCoordScale[0] + tCoordOffset[0]; *(pptr + idx*6 + 1) = tCoord[current][1] * tCoordScale[1] + tCoordOffset[1]; *(pptr + idx*6 + 2) = tCoord[current][2] * tCoordScale[2] + tCoordOffset[2]; int coord = static_cast(tCoord[current][3]); *(pptr + idx*6 + coord) = (low[coord] + t*(high[coord]-low[coord]))*tCoordScale[coord] + tCoordOffset[coord]; *(pptr + idx*6 + 3) = (float)( vertices[lines[current][0]][0] + t*(vertices[lines[current][1]][0] - vertices[lines[current][0]][0])); *(pptr + idx*6 + 4) = (float)( vertices[lines[current][0]][1] + t*(vertices[lines[current][1]][1] - vertices[lines[current][0]][1])); *(pptr + idx*6 + 5) = (float)( vertices[lines[current][0]][2] + t*(vertices[lines[current][1]][2] - vertices[lines[current][0]][2])); idx++; j = 0; while ( j < 6 && (*(this->IntersectionBuffer + i*12 + neighborLines[current][j]) < 0 || neighborLines[current][j] == previous) ) { j++; } if ( j >= 6 ) { errFlag = 1; } else { previous = current; current = neighborLines[current][j]; } } if ( idx < 6 ) { *(pptr + idx*6) = -1; } } iptr += 12; pptr += 36; } } int vtkVolumeTextureMapper3D::UpdateVolumes(vtkVolume *vtkNotUsed(vol)) { int needToUpdate = 0; // Get the image data vtkImageData *input = this->GetInput(); input->Update(); // Has the volume changed in some way? if ( this->SavedTextureInput != input || this->SavedTextureMTime.GetMTime() < input->GetMTime() ) { needToUpdate = 1; } if ( !needToUpdate ) { return 0; } this->SavedTextureInput = input; this->SavedTextureMTime.Modified(); // How big does the Volume need to be? int dim[3]; input->GetDimensions(dim); int powerOfTwoDim[3]; for ( int i = 0; i < 3; i++ ) { powerOfTwoDim[i] = 32; while ( powerOfTwoDim[i] < dim[i] ) { powerOfTwoDim[i] *= 2; } } while ( ! this->IsTextureSizeSupported( powerOfTwoDim ) ) { if ( powerOfTwoDim[0] >= powerOfTwoDim[1] && powerOfTwoDim[0] >= powerOfTwoDim[2] ) { powerOfTwoDim[0] /= 2; } else if ( powerOfTwoDim[1] >= powerOfTwoDim[0] && powerOfTwoDim[1] >= powerOfTwoDim[2] ) { powerOfTwoDim[1] /= 2; } else { powerOfTwoDim[2] /= 2; } } int neededSize = powerOfTwoDim[0] * powerOfTwoDim[1] * powerOfTwoDim[2]; int components = input->GetNumberOfScalarComponents(); // What is the spacing? double spacing[3]; input->GetSpacing(spacing); // Is it the right size? If not, allocate it. if ( this->VolumeSize != neededSize || this->VolumeComponents != components ) { delete [] this->Volume1; delete [] this->Volume2; delete [] this->Volume3; switch (components) { case 1: this->Volume1 = new unsigned char [2*neededSize]; this->Volume2 = new unsigned char [3*neededSize]; this->Volume3 = NULL; break; case 2: this->Volume1 = new unsigned char [3*neededSize]; this->Volume2 = new unsigned char [3*neededSize]; this->Volume3 = NULL; break; case 3: case 4: this->Volume1 = new unsigned char [3*neededSize]; this->Volume2 = new unsigned char [2*neededSize]; this->Volume3 = new unsigned char [3*neededSize]; break; } this->VolumeSize = neededSize; this->VolumeComponents = components; } // Find the scalar range double scalarRange[2]; input->GetPointData()->GetScalars()->GetRange(scalarRange, components-1); // Is the difference between max and min less than 4096? If so, and if // the data is not of float or double type, use a simple offset mapping. // If the difference between max and min is 4096 or greater, or the data // is of type float or double, we must use an offset / scaling mapping. // In this case, the array size will be 4096 - we need to figure out the // offset and scale factor. float offset; float scale; int arraySizeNeeded; int scalarType = input->GetScalarType(); if ( scalarType == VTK_FLOAT || scalarType == VTK_DOUBLE || scalarRange[1] - scalarRange[0] > 255 ) { arraySizeNeeded = 256; offset = -scalarRange[0]; scale = 255.0 / (scalarRange[1] - scalarRange[0]); } else { arraySizeNeeded = (int)(scalarRange[1] - scalarRange[0] + 1); offset = -scalarRange[0]; scale = 1.0; } this->ColorTableSize = arraySizeNeeded; this->ColorTableOffset = offset; this->ColorTableScale = scale; // Save the volume size this->VolumeDimensions[0] = powerOfTwoDim[0]; this->VolumeDimensions[1] = powerOfTwoDim[1]; this->VolumeDimensions[2] = powerOfTwoDim[2]; // Compute the new spacing this->VolumeSpacing[0] = (static_cast(dim[0])-1.01)*(double)spacing[0] / static_cast(this->VolumeDimensions[0]-1); this->VolumeSpacing[1] = (static_cast(dim[1])-1.01)*(double)spacing[1] / static_cast(this->VolumeDimensions[1]-1); this->VolumeSpacing[2] = (static_cast(dim[2])-1.01)*(double)spacing[2] / static_cast(this->VolumeDimensions[2]-1); // Transfer the input volume to the RGBA volume void *dataPtr = input->GetScalarPointer(); switch ( scalarType ) { vtkTemplateMacro( vtkVolumeTextureMapper3DComputeScalars( (VTK_TT *)(dataPtr), this, offset, scale, this->Volume1, this->Volume2)); } switch ( scalarType ) { vtkTemplateMacro( vtkVolumeTextureMapper3DComputeGradients( (VTK_TT *)(dataPtr), this, scalarRange, this->Volume1, this->Volume2, this->Volume3)); } return 1; } int vtkVolumeTextureMapper3D::UpdateColorLookup( vtkVolume *vol ) { int needToUpdate = 0; // Get the image data vtkImageData *input = this->GetInput(); input->Update(); // Has the volume changed in some way? if ( this->SavedParametersInput != input || this->SavedParametersMTime.GetMTime() < input->GetMTime() ) { needToUpdate = 1; } // What sample distance are we going to use for rendering? If we // have to render quickly according to our allocated render time, // don't necessary obey the sample distance requested by the user. // Instead set the sample distance to the average spacing. this->ActualSampleDistance = this->SampleDistance; if ( vol->GetAllocatedRenderTime() < 1.0 ) { float spacing[3]; this->GetVolumeSpacing(spacing); this->ActualSampleDistance = 0.333 * ((double)spacing[0] + (double)spacing[1] + (double)spacing[2]); } // How many components? int components = input->GetNumberOfScalarComponents(); // Has the sample distance changed? if ( this->SavedSampleDistance != this->ActualSampleDistance ) { needToUpdate = 1; } vtkColorTransferFunction *rgbFunc = NULL; vtkPiecewiseFunction *grayFunc = NULL; // How many color channels for this component? int colorChannels = vol->GetProperty()->GetColorChannels(0); if ( components < 3 ) { // Has the number of color channels changed? if ( this->SavedColorChannels != colorChannels ) { needToUpdate = 1; } // Has the color transfer function changed in some way, // and we are using it? if ( colorChannels == 3 ) { rgbFunc = vol->GetProperty()->GetRGBTransferFunction(0); if ( this->SavedRGBFunction != rgbFunc || this->SavedParametersMTime.GetMTime() < rgbFunc->GetMTime() ) { needToUpdate = 1; } } // Has the gray transfer function changed in some way, // and we are using it? if ( colorChannels == 1 ) { grayFunc = vol->GetProperty()->GetGrayTransferFunction(0); if ( this->SavedGrayFunction != grayFunc || this->SavedParametersMTime.GetMTime() < grayFunc->GetMTime() ) { needToUpdate = 1; } } } // Has the scalar opacity transfer function changed in some way? vtkPiecewiseFunction *scalarOpacityFunc = vol->GetProperty()->GetScalarOpacity(0); if ( this->SavedScalarOpacityFunction != scalarOpacityFunc || this->SavedParametersMTime.GetMTime() < scalarOpacityFunc->GetMTime() ) { needToUpdate = 1; } // Has the gradient opacity transfer function changed in some way? vtkPiecewiseFunction *gradientOpacityFunc = vol->GetProperty()->GetGradientOpacity(0); if ( this->SavedGradientOpacityFunction != gradientOpacityFunc || this->SavedParametersMTime.GetMTime() < gradientOpacityFunc->GetMTime() ) { needToUpdate = 1; } double scalarOpacityDistance = vol->GetProperty()->GetScalarOpacityUnitDistance(0); if ( this->SavedScalarOpacityDistance != scalarOpacityDistance ) { needToUpdate = 1; } // If we have not found any need to update, return now if ( !needToUpdate ) { return 0; } this->SavedRGBFunction = rgbFunc; this->SavedGrayFunction = grayFunc; this->SavedScalarOpacityFunction = scalarOpacityFunc; this->SavedGradientOpacityFunction = gradientOpacityFunc; this->SavedColorChannels = colorChannels; this->SavedSampleDistance = this->ActualSampleDistance; this->SavedScalarOpacityDistance = scalarOpacityDistance; this->SavedParametersInput = input; this->SavedParametersMTime.Modified(); // Find the scalar range double scalarRange[2]; input->GetPointData()->GetScalars()->GetRange(scalarRange, components-1); int arraySizeNeeded = this->ColorTableSize; if ( components < 3 ) { // Sample the transfer functions between the min and max. if ( colorChannels == 1 ) { grayFunc->GetTable( scalarRange[0], scalarRange[1], arraySizeNeeded, this->TempArray1 ); } else { rgbFunc->GetTable( scalarRange[0], scalarRange[1], arraySizeNeeded, this->TempArray1 ); } } scalarOpacityFunc->GetTable( scalarRange[0], scalarRange[1], arraySizeNeeded, this->TempArray2 ); float goArray[256]; gradientOpacityFunc->GetTable( 0, (scalarRange[1] - scalarRange[0])*0.25, 256, goArray ); // Correct the opacity array for the spacing between the planes. int i; float *fptr2 = this->TempArray2; double factor = (double)this->ActualSampleDistance / scalarOpacityDistance; for ( i = 0; i < arraySizeNeeded; i++ ) { if ( *fptr2 > 0.0001 ) { *fptr2 = 1.0-pow((double)(1.0-(*fptr2)),factor); } fptr2++; } int goLoop; unsigned char *ptr, *rgbptr, *aptr; float *fptr1; switch (components) { case 1: // Move the two temp float arrays into one RGBA unsigned char array ptr = this->ColorLookup; for ( goLoop = 0; goLoop < 256; goLoop++ ) { fptr1 = this->TempArray1; fptr2 = this->TempArray2; if ( colorChannels == 1 ) { for ( i = 0; i < arraySizeNeeded; i++ ) { *(ptr++) = static_cast(*(fptr1)*255.0 + 0.5); *(ptr++) = static_cast(*(fptr1)*255.0 + 0.5); *(ptr++) = static_cast(*(fptr1++)*255.0 + 0.5); *(ptr++) = static_cast(*(fptr2++)*goArray[goLoop]*255.0 + 0.5); } } else { for ( i = 0; i < arraySizeNeeded; i++ ) { *(ptr++) = static_cast(*(fptr1++)*255.0 + 0.5); *(ptr++) = static_cast(*(fptr1++)*255.0 + 0.5); *(ptr++) = static_cast(*(fptr1++)*255.0 + 0.5); *(ptr++) = static_cast(*(fptr2++)*goArray[goLoop]*255.0 + 0.5); } } for ( ; i < 256; i++ ) { *(ptr++) = 0; *(ptr++) = 0; *(ptr++) = 0; *(ptr++) = 0; } } break; case 2: // Move the two temp float arrays into one RGB unsigned char array and // one alpha array. rgbptr = this->ColorLookup; aptr = this->AlphaLookup; if ( colorChannels == 1 ) { for ( i = 0; i < arraySizeNeeded; i++ ) { fptr1 = this->TempArray1; fptr2 = this->TempArray2; for ( goLoop = 0; goLoop < 256; goLoop++ ) { *(rgbptr++) = static_cast(*(fptr1)*255.0 + 0.5); *(rgbptr++) = static_cast(*(fptr1)*255.0 + 0.5); *(rgbptr++) = static_cast(*(fptr1++)*255.0 + 0.5); *(aptr++) = static_cast(*(fptr2++)*goArray[goLoop]*255.0 + 0.5); } } } else { fptr1 = this->TempArray1; fptr2 = this->TempArray2; for ( i = 0; i < arraySizeNeeded; i++ ) { for ( goLoop = 0; goLoop < 256; goLoop++ ) { *(rgbptr++) = static_cast(*(fptr1)*255.0 + 0.5); *(rgbptr++) = static_cast(*(fptr1+1)*255.0 + 0.5); *(rgbptr++) = static_cast(*(fptr1+2)*255.0 + 0.5); *(aptr++) = static_cast(*(fptr2)*goArray[goLoop]*255.0 + 0.5); } fptr1+=3; fptr2++; } } for ( ; i < 256; i++ ) { for ( goLoop = 0; goLoop < 256; goLoop++ ) { *(rgbptr++) = 0; *(rgbptr++) = 0; *(rgbptr++) = 0; *(aptr++) = 0; } } break; case 3: case 4: // Move the two temp float arrays into one alpha array aptr = this->AlphaLookup; for ( goLoop = 0; goLoop < 256; goLoop++ ) { fptr2 = this->TempArray2; for ( i = 0; i < arraySizeNeeded; i++ ) { *(aptr++) = static_cast(*(fptr2++)*goArray[goLoop]*255.0 + 0.5); } for ( ; i < 256; i++ ) { *(aptr++) = 0; } } break; } return 1; } // Print the vtkVolumeTextureMapper3D void vtkVolumeTextureMapper3D::PrintSelf(ostream& os, vtkIndent indent) { this->Superclass::PrintSelf(os,indent); os << indent << "Sample Distance: " << this->SampleDistance << endl; os << indent << "Render Method: " << this->RenderMethod << endl; os << indent << "Preferred Render Method: " << this->PreferredRenderMethod << endl; os << indent << "NumberOfPolygons: " << this->NumberOfPolygons << endl; os << indent << "ActualSampleDistance: " << this->ActualSampleDistance << endl; os << indent << "VolumeDimensions: " << this->VolumeDimensions[0] << " " << this->VolumeDimensions[1] << " " << this->VolumeDimensions[2] << endl; os << indent << "VolumeSpacing: " << this->VolumeSpacing[0] << " " << this->VolumeSpacing[1] << " " << this->VolumeSpacing[2] << endl; }