Cloned library of VTK-5.0.0 with extra build files for internal package management.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

808 lines
23 KiB

/*=========================================================================
Program: Visualization Toolkit
Module: $RCSfile: vtkTubeFilter.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 "vtkTubeFilter.h"
#include "vtkCellArray.h"
#include "vtkCellData.h"
#include "vtkFloatArray.h"
#include "vtkMath.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkObjectFactory.h"
#include "vtkPointData.h"
#include "vtkPolyData.h"
#include "vtkPolyLine.h"
vtkCxxRevisionMacro(vtkTubeFilter, "$Revision: 1.87.6.1 $");
vtkStandardNewMacro(vtkTubeFilter);
// Construct object with radius 0.5, radius variation turned off, the number
// of sides set to 3, and radius factor of 10.
vtkTubeFilter::vtkTubeFilter()
{
this->Radius = 0.5;
this->VaryRadius = VTK_VARY_RADIUS_OFF;
this->NumberOfSides = 3;
this->RadiusFactor = 10;
this->DefaultNormal[0] = this->DefaultNormal[1] = 0.0;
this->DefaultNormal[2] = 1.0;
this->UseDefaultNormal = 0;
this->SidesShareVertices = 1;
this->Capping = 0;
this->OnRatio = 1;
this->Offset = 0;
this->GenerateTCoords = VTK_TCOORDS_OFF;
this->TextureLength = 1.0;
// by default process active point scalars
this->SetInputArrayToProcess(0,0,0,vtkDataObject::FIELD_ASSOCIATION_POINTS,
vtkDataSetAttributes::SCALARS);
// by default process active point vectors
this->SetInputArrayToProcess(1,0,0,vtkDataObject::FIELD_ASSOCIATION_POINTS,
vtkDataSetAttributes::VECTORS);
}
int vtkTubeFilter::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()));
vtkPointData *pd=input->GetPointData();
vtkPointData *outPD=output->GetPointData();
vtkCellData *cd=input->GetCellData();
vtkCellData *outCD=output->GetCellData();
vtkCellArray *inLines;
vtkDataArray *inNormals;
vtkDataArray *inScalars=this->GetInputArrayToProcess(0,inputVector);
vtkDataArray *inVectors=this->GetInputArrayToProcess(1,inputVector);
vtkPoints *inPts;
vtkIdType numPts;
vtkIdType numLines;
vtkIdType numNewPts, numNewCells;
vtkPoints *newPts;
int deleteNormals=0;
vtkFloatArray *newNormals;
vtkIdType i;
double range[2], maxSpeed=0;
vtkCellArray *newStrips;
vtkIdType npts=0, *pts=NULL;
vtkIdType offset=0;
vtkFloatArray *newTCoords=NULL;
int abort=0;
vtkIdType inCellId;
double oldRadius=1.0;
// Check input and initialize
//
vtkDebugMacro(<<"Creating tube");
if ( !(inPts=input->GetPoints()) ||
(numPts = inPts->GetNumberOfPoints()) < 1 ||
!(inLines = input->GetLines()) ||
(numLines = inLines->GetNumberOfCells()) < 1 )
{
return 1;
}
// Create the geometry and topology
numNewPts = numPts * this->NumberOfSides;
newPts = vtkPoints::New();
newPts->Allocate(numNewPts);
newNormals = vtkFloatArray::New();
newNormals->SetName("TubeNormals");
newNormals->SetNumberOfComponents(3);
newNormals->Allocate(3*numNewPts);
newStrips = vtkCellArray::New();
newStrips->Allocate(newStrips->EstimateSize(1,numNewPts));
vtkCellArray *singlePolyline = vtkCellArray::New();
// Point data: copy scalars, vectors, tcoords. Normals may be computed here.
outPD->CopyNormalsOff();
if ( (this->GenerateTCoords == VTK_TCOORDS_FROM_SCALARS && inScalars) ||
this->GenerateTCoords == VTK_TCOORDS_FROM_LENGTH ||
this->GenerateTCoords == VTK_TCOORDS_FROM_NORMALIZED_LENGTH )
{
newTCoords = vtkFloatArray::New();
newTCoords->SetNumberOfComponents(2);
newTCoords->Allocate(numNewPts);
outPD->CopyTCoordsOff();
}
outPD->CopyAllocate(pd,numNewPts);
int generateNormals = 0;
if ( !(inNormals=pd->GetNormals()) || this->UseDefaultNormal )
{
deleteNormals = 1;
inNormals = vtkFloatArray::New();
inNormals->SetNumberOfComponents(3);
inNormals->SetNumberOfTuples(numPts);
if ( this->UseDefaultNormal )
{
for ( i=0; i < numPts; i++)
{
inNormals->SetTuple(i,this->DefaultNormal);
}
}
else
{
// Normal generation has been moved to lower in the function.
// This allows each different polylines to share vertices, but have
// their normals (and hence their tubes) calculated independently
generateNormals = 1;
}
}
// If varying width, get appropriate info.
//
if ( inScalars )
{
inScalars->GetRange(range,0);
if ((range[1] - range[0]) == 0.0)
{
if (this->VaryRadius == VTK_VARY_RADIUS_BY_SCALAR )
{
vtkWarningMacro(<< "Scalar range is zero!");
}
range[1] = range[0] + 1.0;
}
if (this->VaryRadius == VTK_VARY_RADIUS_BY_ABSOLUTE_SCALAR)
{
// temporarily set the radius to 1.0 so that radius*scalar = scalar
oldRadius = this->Radius;
this->Radius = 1.0;
if (range[0] < 0.0)
{
vtkWarningMacro(<< "Scalar values fall below zero when using absolute radius values!");
}
}
}
if ( inVectors )
{
maxSpeed = inVectors->GetMaxNorm();
}
// Copy selected parts of cell data; certainly don't want normals
//
numNewCells = inLines->GetNumberOfCells() * this->NumberOfSides + 2;
outCD->CopyNormalsOff();
outCD->CopyAllocate(cd,numNewCells);
// Create points along each polyline that are connected into NumberOfSides
// triangle strips. Texture coordinates are optionally generated.
//
this->Theta = 2.0*vtkMath::Pi() / this->NumberOfSides;
vtkPolyLine *lineNormalGenerator = vtkPolyLine::New();
for (inCellId=0, inLines->InitTraversal();
inLines->GetNextCell(npts,pts) && !abort; inCellId++)
{
this->UpdateProgress((double)inCellId/numLines);
abort = this->GetAbortExecute();
if (npts < 2)
{
vtkWarningMacro(<< "Less than two points in line!");
continue; //skip tubing this polyline
}
// If necessary calculate normals, each polyline calculates its
// normals independently, avoiding conflicts at shared vertices.
if (generateNormals)
{
singlePolyline->Reset(); //avoid instantiation
singlePolyline->InsertNextCell(npts,pts);
if ( !lineNormalGenerator->GenerateSlidingNormals(inPts,singlePolyline,
inNormals) )
{
vtkWarningMacro("Could not generate normals for line. "
"Skipping to next.");
continue; //skip tubing this polyline
}
}
// Generate the points around the polyline. The tube is not stripped
// if the polyline is bad.
//
if ( !this->GeneratePoints(offset,npts,pts,inPts,newPts,pd,outPD,
newNormals,inScalars,range,inVectors,
maxSpeed,inNormals) )
{
vtkWarningMacro(<< "Could not generate points!");
continue; //skip tubing this polyline
}
// Generate the strips for this polyline (including caps)
//
this->GenerateStrips(offset,npts,pts,inCellId,cd,outCD,newStrips);
// Generate the texture coordinates for this polyline
//
if ( newTCoords )
{
this->GenerateTextureCoords(offset,npts,pts,inPts,inScalars,newTCoords);
}
// Compute the new offset for the next polyline
offset = this->ComputeOffset(offset,npts);
}//for all polylines
singlePolyline->Delete();
// reset the radius to ite orginal value if necessary
if (this->VaryRadius == VTK_VARY_RADIUS_BY_ABSOLUTE_SCALAR)
{
this->Radius = oldRadius;
}
// Update ourselves
//
if ( deleteNormals )
{
inNormals->Delete();
}
if ( newTCoords )
{
outPD->SetTCoords(newTCoords);
newTCoords->Delete();
}
output->SetPoints(newPts);
newPts->Delete();
output->SetStrips(newStrips);
newStrips->Delete();
outPD->SetNormals(newNormals);
newNormals->Delete();
lineNormalGenerator->Delete();
output->Squeeze();
return 1;
}
int vtkTubeFilter::GeneratePoints(vtkIdType offset,
vtkIdType npts, vtkIdType *pts,
vtkPoints *inPts, vtkPoints *newPts,
vtkPointData *pd, vtkPointData *outPD,
vtkFloatArray *newNormals,
vtkDataArray *inScalars, double range[2],
vtkDataArray *inVectors, double maxSpeed,
vtkDataArray *inNormals)
{
vtkIdType j;
int i, k;
double p[3];
double pNext[3];
double sNext[3];
double sPrev[3];
double startCapNorm[3], endCapNorm[3];
double n[3];
double s[3];
//double bevelAngle;
double w[3];
double nP[3];
double sFactor=1.0;
double normal[3];
vtkIdType ptId=offset;
// Use "averaged" segment to create beveled effect.
// Watch out for first and last points.
//
for (j=0; j < npts; j++)
{
if ( j == 0 ) //first point
{
inPts->GetPoint(pts[0],p);
inPts->GetPoint(pts[1],pNext);
for (i=0; i<3; i++)
{
sNext[i] = pNext[i] - p[i];
sPrev[i] = sNext[i];
startCapNorm[i] = -sPrev[i];
}
vtkMath::Normalize(startCapNorm);
}
else if ( j == (npts-1) ) //last point
{
for (i=0; i<3; i++)
{
sPrev[i] = sNext[i];
p[i] = pNext[i];
endCapNorm[i] = sNext[i];
}
vtkMath::Normalize(endCapNorm);
}
else
{
for (i=0; i<3; i++)
{
p[i] = pNext[i];
}
inPts->GetPoint(pts[j+1],pNext);
for (i=0; i<3; i++)
{
sPrev[i] = sNext[i];
sNext[i] = pNext[i] - p[i];
}
}
inNormals->GetTuple(pts[j], n);
if ( vtkMath::Normalize(sNext) == 0.0 )
{
vtkWarningMacro(<<"Coincident points!");
return 0;
}
for (i=0; i<3; i++)
{
s[i] = (sPrev[i] + sNext[i]) / 2.0; //average vector
}
// if s is zero then just use sPrev cross n
if (vtkMath::Normalize(s) == 0.0)
{
vtkDebugMacro(<< "Using alternate bevel vector");
vtkMath::Cross(sPrev,n,s);
if (vtkMath::Normalize(s) == 0.0)
{
vtkDebugMacro(<< "Using alternate bevel vector");
}
}
/* if ( (bevelAngle = vtkMath::Dot(sNext,sPrev)) > 1.0 )
{
bevelAngle = 1.0;
}
if ( bevelAngle < -1.0 )
{
bevelAngle = -1.0;
}
bevelAngle = acos((double)bevelAngle) / 2.0; //(0->90 degrees)
if ( (bevelAngle = cos(bevelAngle)) == 0.0 )
{
bevelAngle = 1.0;
}
bevelAngle = this->Radius / bevelAngle; //keep tube constant radius
*/
vtkMath::Cross(s,n,w);
if ( vtkMath::Normalize(w) == 0.0)
{
vtkWarningMacro(<<"Bad normal s = " <<s[0]<<" "<<s[1]<<" "<< s[2]
<< " n = " << n[0] << " " << n[1] << " " << n[2]);
return 0;
}
vtkMath::Cross(w,s,nP); //create orthogonal coordinate system
vtkMath::Normalize(nP);
// Compute a scale factor based on scalars or vectors
if ( inScalars && this->VaryRadius == VTK_VARY_RADIUS_BY_SCALAR )
{
sFactor = 1.0 + ((this->RadiusFactor - 1.0) *
(inScalars->GetComponent(pts[j],0) - range[0])
/ (range[1]-range[0]));
}
else if ( inVectors && this->VaryRadius == VTK_VARY_RADIUS_BY_VECTOR )
{
sFactor =
sqrt((double)maxSpeed/vtkMath::Norm(inVectors->GetTuple(pts[j])));
if ( sFactor > this->RadiusFactor )
{
sFactor = this->RadiusFactor;
}
}
else if ( inScalars &&
this->VaryRadius == VTK_VARY_RADIUS_BY_ABSOLUTE_SCALAR )
{
sFactor = inScalars->GetComponent(pts[j],0);
if (sFactor < 0.0)
{
vtkWarningMacro(<<"Scalar value less than zero, skipping line");
return 0;
}
}
//create points around line
if (this->SidesShareVertices)
{
for (k=0; k < this->NumberOfSides; k++)
{
for (i=0; i<3; i++)
{
normal[i] = w[i]*cos((double)k*this->Theta) +
nP[i]*sin((double)k*this->Theta);
s[i] = p[i] + this->Radius * sFactor * normal[i];
}
newPts->InsertPoint(ptId,s);
newNormals->InsertTuple(ptId,normal);
outPD->CopyData(pd,pts[j],ptId);
ptId++;
}//for each side
}
else
{
double n_left[3], n_right[3];
for (k=0; k < this->NumberOfSides; k++)
{
for (i=0; i<3; i++)
{
// Create duplicate vertices at each point
// and adjust the associated normals so that they are
// oriented with the facets. This preserves the tube's
// polygonal appearance, as if by flat-shading around the tube,
// while still allowing smooth (gouraud) shading along the
// tube as it bends.
normal[i] = w[i]*cos((double)(k+0.0)*this->Theta) +
nP[i]*sin((double)(k+0.0)*this->Theta);
n_right[i] = w[i]*cos((double)(k-0.5)*this->Theta) +
nP[i]*sin((double)(k-0.5)*this->Theta);
n_left[i] = w[i]*cos((double)(k+0.5)*this->Theta) +
nP[i]*sin((double)(k+0.5)*this->Theta);
s[i] = p[i] + this->Radius * sFactor * normal[i];
}
newPts->InsertPoint(ptId,s);
newNormals->InsertTuple(ptId,n_right);
outPD->CopyData(pd,pts[j],ptId);
newPts->InsertPoint(ptId+1,s);
newNormals->InsertTuple(ptId+1,n_left);
outPD->CopyData(pd,pts[j],ptId+1);
ptId += 2;
}//for each side
}//else separate vertices
}//for all points in polyline
//Produce end points for cap. They are placed at tail end of points.
if (this->Capping)
{
int numCapSides = this->NumberOfSides;
int capIncr = 1;
if ( ! this->SidesShareVertices )
{
numCapSides = 2 * this->NumberOfSides;
capIncr = 2;
}
//the start cap
for (k=0; k < numCapSides; k+=capIncr)
{
newPts->GetPoint(offset+k,s);
newPts->InsertPoint(ptId,s);
newNormals->InsertTuple(ptId,startCapNorm);
outPD->CopyData(pd,pts[0],ptId);
ptId++;
}
//the end cap
int endOffset = offset + (npts-1)*this->NumberOfSides;
if ( ! this->SidesShareVertices )
{
endOffset = offset + 2*(npts-1)*this->NumberOfSides;
}
for (k=0; k < numCapSides; k+=capIncr)
{
newPts->GetPoint(endOffset+k,s);
newPts->InsertPoint(ptId,s);
newNormals->InsertTuple(ptId,endCapNorm);
outPD->CopyData(pd,pts[npts-1],ptId);
ptId++;
}
}//if capping
return 1;
}
void vtkTubeFilter::GenerateStrips(vtkIdType offset, vtkIdType npts,
vtkIdType* vtkNotUsed(pts),
vtkIdType inCellId,
vtkCellData *cd, vtkCellData *outCD,
vtkCellArray *newStrips)
{
vtkIdType i, outCellId;
int k;
int i1, i2, i3;
if (this->SidesShareVertices)
{
for (k=this->Offset; k<(this->NumberOfSides+this->Offset);
k+=this->OnRatio)
{
i1 = k % this->NumberOfSides;
i2 = (k+1) % this->NumberOfSides;
outCellId = newStrips->InsertNextCell(npts*2);
outCD->CopyData(cd,inCellId,outCellId);
for (i=0; i < npts; i++)
{
i3 = i*this->NumberOfSides;
newStrips->InsertCellPoint(offset+i2+i3);
newStrips->InsertCellPoint(offset+i1+i3);
}
} //for each side of the tube
}
else
{
for (k=this->Offset; k<(this->NumberOfSides+this->Offset);
k+=this->OnRatio)
{
i1 = 2*(k % this->NumberOfSides) + 1;
i2 = 2*((k+1) % this->NumberOfSides);
outCellId = newStrips->InsertNextCell(npts*2);
outCD->CopyData(cd,inCellId,outCellId);
for (i=0; i < npts; i++)
{
i3 = i*2*this->NumberOfSides;
newStrips->InsertCellPoint(offset+i2+i3);
newStrips->InsertCellPoint(offset+i1+i3);
}
} //for each side of the tube
}
// Take care of capping. The caps are n-sided polygons that can be
// easily triangle stripped.
if (this->Capping)
{
vtkIdType startIdx = offset + npts*this->NumberOfSides;
vtkIdType idx;
if ( ! this->SidesShareVertices )
{
startIdx = offset + 2*npts*this->NumberOfSides;
}
//The start cap
outCellId = newStrips->InsertNextCell(this->NumberOfSides);
outCD->CopyData(cd,inCellId,outCellId);
newStrips->InsertCellPoint(startIdx);
newStrips->InsertCellPoint(startIdx+1);
for (i1=this->NumberOfSides-1, i2=2, k=0; k<(this->NumberOfSides-2); k++)
{
if ( (k%2) )
{
idx = startIdx + i2;
newStrips->InsertCellPoint(idx);
i2++;
}
else
{
idx = startIdx + i1;
newStrips->InsertCellPoint(idx);
i1--;
}
}
//The end cap - reversed order to be consistent with normal
startIdx += this->NumberOfSides;
outCellId = newStrips->InsertNextCell(this->NumberOfSides);
outCD->CopyData(cd,inCellId,outCellId);
newStrips->InsertCellPoint(startIdx);
newStrips->InsertCellPoint(startIdx+this->NumberOfSides-1);
for (i1=this->NumberOfSides-2, i2=1, k=0; k<(this->NumberOfSides-2); k++)
{
if ( (k%2) )
{
idx = startIdx + i1;
newStrips->InsertCellPoint(idx);
i1--;
}
else
{
idx = startIdx + i2;
newStrips->InsertCellPoint(idx);
i2++;
}
}
}
}
void vtkTubeFilter::GenerateTextureCoords(vtkIdType offset,
vtkIdType npts, vtkIdType *pts,
vtkPoints *inPts,
vtkDataArray *inScalars,
vtkFloatArray *newTCoords)
{
vtkIdType i;
int k;
double tc=0.0;
int numSides = this->NumberOfSides;
if ( ! this->SidesShareVertices )
{
numSides = 2 * this->NumberOfSides;
}
double s0, s;
//The first texture coordinate is always 0.
for ( k=0; k < numSides; k++)
{
newTCoords->InsertTuple2(offset+k,0.0,0.0);
}
if ( this->GenerateTCoords == VTK_TCOORDS_FROM_SCALARS )
{
s0 = inScalars->GetTuple1(pts[0]);
for (i=1; i < npts; i++)
{
s = inScalars->GetTuple1(pts[i]);
tc = (s - s0) / this->TextureLength;
for ( k=0; k < numSides; k++)
{
newTCoords->InsertTuple2(offset+i*numSides+k,tc,0.0);
}
}
}
else if ( this->GenerateTCoords == VTK_TCOORDS_FROM_LENGTH )
{
double xPrev[3], x[3], len=0.0;
inPts->GetPoint(pts[0],xPrev);
for (i=1; i < npts; i++)
{
inPts->GetPoint(pts[i],x);
len += sqrt(vtkMath::Distance2BetweenPoints(x,xPrev));
tc = len / this->TextureLength;
for ( k=0; k < numSides; k++)
{
newTCoords->InsertTuple2(offset+i*numSides+k,tc,0.0);
}
xPrev[0]=x[0]; xPrev[1]=x[1]; xPrev[2]=x[2];
}
}
else if ( this->GenerateTCoords == VTK_TCOORDS_FROM_NORMALIZED_LENGTH )
{
double xPrev[3], x[3], length=0.0, len=0.0;
inPts->GetPoint(pts[0],xPrev);
for (i=1; i < npts; i++)
{
inPts->GetPoint(pts[i],x);
length += sqrt(vtkMath::Distance2BetweenPoints(x,xPrev));
xPrev[0]=x[0]; xPrev[1]=x[1]; xPrev[2]=x[2];
}
inPts->GetPoint(pts[0],xPrev);
for (i=1; i < npts; i++)
{
inPts->GetPoint(pts[i],x);
len += sqrt(vtkMath::Distance2BetweenPoints(x,xPrev));
tc = len / length;
for ( k=0; k < numSides; k++)
{
newTCoords->InsertTuple2(offset+i*2+k,tc,0.0);
}
xPrev[0]=x[0]; xPrev[1]=x[1]; xPrev[2]=x[2];
}
}
// Capping, set the endpoints as appropriate
if ( this->Capping )
{
int ik;
vtkIdType startIdx = offset + npts*numSides;
//start cap
for (ik=0; ik < this->NumberOfSides; ik++)
{
newTCoords->InsertTuple2(startIdx+ik,0.0,0.0);
}
//end cap
for (ik=0; ik < this->NumberOfSides; ik++)
{
newTCoords->InsertTuple2(startIdx+this->NumberOfSides+ik,tc,0.0);
}
}
}
// Compute the number of points in this tube
vtkIdType vtkTubeFilter::ComputeOffset(vtkIdType offset, vtkIdType npts)
{
if ( this->SidesShareVertices )
{
offset += this->NumberOfSides * npts;
}
else
{
offset += 2 * this->NumberOfSides * npts; //points are duplicated
}
if ( this->Capping )
{
offset += 2*this->NumberOfSides; //cap points are duplicated
}
return offset;
}
// Description:
// Return the method of varying tube radius descriptive character string.
const char *vtkTubeFilter::GetVaryRadiusAsString(void)
{
if ( this->VaryRadius == VTK_VARY_RADIUS_OFF )
{
return "VaryRadiusOff";
}
else if ( this->VaryRadius == VTK_VARY_RADIUS_BY_SCALAR )
{
return "VaryRadiusByScalar";
}
else if ( this->VaryRadius == VTK_VARY_RADIUS_BY_ABSOLUTE_SCALAR )
{
return "VaryRadiusByAbsoluteScalar";
}
else
{
return "VaryRadiusByVector";
}
}
// Description:
// Return the method of generating the texture coordinates.
const char *vtkTubeFilter::GetGenerateTCoordsAsString(void)
{
if ( this->GenerateTCoords == VTK_TCOORDS_OFF )
{
return "GenerateTCoordsOff";
}
else if ( this->GenerateTCoords == VTK_TCOORDS_FROM_SCALARS )
{
return "GenerateTCoordsFromScalar";
}
else if ( this->GenerateTCoords == VTK_TCOORDS_FROM_LENGTH )
{
return "GenerateTCoordsFromLength";
}
else
{
return "GenerateTCoordsFromNormalizedLength";
}
}
void vtkTubeFilter::PrintSelf(ostream& os, vtkIndent indent)
{
this->Superclass::PrintSelf(os,indent);
os << indent << "Radius: " << this->Radius << "\n";
os << indent << "Vary Radius: " << this->GetVaryRadiusAsString() << endl;
os << indent << "Radius Factor: " << this->RadiusFactor << "\n";
os << indent << "Number Of Sides: " << this->NumberOfSides << "\n";
os << indent << "On Ratio: " << this->OnRatio << "\n";
os << indent << "Offset: " << this->Offset << "\n";
os << indent << "Use Default Normal: "
<< (this->UseDefaultNormal ? "On\n" : "Off\n");
os << indent << "Sides Share Vertices: "
<< (this->SidesShareVertices ? "On\n" : "Off\n");
os << indent << "Default Normal: " << "( " << this->DefaultNormal[0] <<
", " << this->DefaultNormal[1] << ", " << this->DefaultNormal[2] <<
" )\n";
os << indent << "Capping: " << (this->Capping ? "On\n" : "Off\n");
os << indent << "Generate TCoords: "
<< this->GetGenerateTCoordsAsString() << endl;
os << indent << "Texture Length: " << this->TextureLength << endl;
}