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.
558 lines
20 KiB
558 lines
20 KiB
/*=========================================================================
|
|
|
|
Program: Visualization Toolkit
|
|
Module: $RCSfile: vtkPolyData.h,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.
|
|
|
|
=========================================================================*/
|
|
// .NAME vtkPolyData - concrete dataset represents vertices, lines, polygons, and triangle strips
|
|
// .SECTION Description
|
|
// vtkPolyData is a data object that is a concrete implementation of
|
|
// vtkDataSet. vtkPolyData represents a geometric structure consisting of
|
|
// vertices, lines, polygons, and/or triangle strips. Point and cell
|
|
// attribute values (e.g., scalars, vectors, etc.) also are represented.
|
|
//
|
|
// The actual cell types (vtkCellType.h) supported by vtkPolyData are:
|
|
// vtkVertex, vtkPolyVertex, vtkLine, vtkPolyLine, vtkTriangle, vtkQuad,
|
|
// vtkPolygon, and vtkTriangleStrip.
|
|
//
|
|
// One important feature of vtkPolyData objects is that special traversal and
|
|
// data manipulation methods are available to process data. These methods are
|
|
// generally more efficient than vtkDataSet methods and should be used
|
|
// whenever possible. For example, traversing the cells in a dataset we would
|
|
// use GetCell(). To traverse cells with vtkPolyData we would retrieve the
|
|
// cell array object representing polygons (for example using GetPolys()) and
|
|
// then use vtkCellArray's InitTraversal() and GetNextCell() methods.
|
|
//
|
|
// .SECTION Caveats
|
|
// Because vtkPolyData is implemented with four separate instances of
|
|
// vtkCellArray to represent 0D vertices, 1D lines, 2D polygons, and 2D
|
|
// triangle strips, it is possible to create vtkPolyData instances that
|
|
// consist of a mixture of cell types. Because of the design of the class,
|
|
// there are certain limitations on how mixed cell types are inserted into
|
|
// the vtkPolyData, and in turn the order in which they are processed and
|
|
// rendered. To preserve the consistency of cell ids, and to insure that
|
|
// cells with cell data are rendered properly, users must insert mixed cells
|
|
// in the order of vertices (vtkVertex and vtkPolyVertex), lines (vtkLine and
|
|
// vtkPolyLine), polygons (vtkTriangle, vtkQuad, vtkPolygon), and triangle
|
|
// strips (vtkTriangleStrip).
|
|
//
|
|
// Some filters when processing vtkPolyData with mixed cell types may process
|
|
// the cells in differing ways. Some will convert one type into another
|
|
// (e.g., vtkTriangleStrip into vtkTriangles) or expect a certain type
|
|
// (vtkDecimatePro expects triangles or triangle strips; vtkTubeFilter
|
|
// expects lines). Read the documentation for each filter carefully to
|
|
// understand how each part of vtkPolyData is processed.
|
|
|
|
#ifndef __vtkPolyData_h
|
|
#define __vtkPolyData_h
|
|
|
|
#include "vtkPointSet.h"
|
|
|
|
#include "vtkCellTypes.h" // Needed for inline methods
|
|
#include "vtkCellLinks.h" // Needed for inline methods
|
|
|
|
class vtkVertex;
|
|
class vtkPolyVertex;
|
|
class vtkLine;
|
|
class vtkPolyLine;
|
|
class vtkTriangle;
|
|
class vtkQuad;
|
|
class vtkPolygon;
|
|
class vtkTriangleStrip;
|
|
class vtkEmptyCell;
|
|
|
|
class VTK_FILTERING_EXPORT vtkPolyData : public vtkPointSet
|
|
{
|
|
public:
|
|
static vtkPolyData *New();
|
|
|
|
vtkTypeRevisionMacro(vtkPolyData,vtkPointSet);
|
|
void PrintSelf(ostream& os, vtkIndent indent);
|
|
|
|
// Description:
|
|
// Return what type of dataset this is.
|
|
int GetDataObjectType() {return VTK_POLY_DATA;}
|
|
|
|
// Description:
|
|
// Copy the geometric and topological structure of an input poly data object.
|
|
void CopyStructure(vtkDataSet *ds);
|
|
|
|
// Description:
|
|
// Standard vtkDataSet interface.
|
|
vtkIdType GetNumberOfCells();
|
|
vtkCell *GetCell(vtkIdType cellId);
|
|
void GetCell(vtkIdType cellId, vtkGenericCell *cell);
|
|
int GetCellType(vtkIdType cellId);
|
|
void GetCellBounds(vtkIdType cellId, double bounds[6]);
|
|
void GetCellNeighbors(vtkIdType cellId, vtkIdList *ptIds,
|
|
vtkIdList *cellIds);
|
|
|
|
// Description:
|
|
// Copy cells listed in idList from pd, including points, point data,
|
|
// and cell data. This method assumes that point and cell data have
|
|
// been allocated. If you pass in a point locator, then the points
|
|
// won't be duplicated in the output.
|
|
void CopyCells(vtkPolyData *pd, vtkIdList *idList,
|
|
vtkPointLocator *locator = NULL);
|
|
|
|
// Description:
|
|
// Copy a cells point ids into list provided. (Less efficient.)
|
|
void GetCellPoints(vtkIdType cellId, vtkIdList *ptIds);
|
|
|
|
// Description:
|
|
// Efficient method to obtain cells using a particular point. Make sure that
|
|
// routine BuildLinks() has been called.
|
|
void GetPointCells(vtkIdType ptId, vtkIdList *cellIds);
|
|
|
|
// Description:
|
|
// Compute the (X, Y, Z) bounds of the data.
|
|
void ComputeBounds();
|
|
|
|
// Description:
|
|
// Recover extra allocated memory when creating data whose initial size
|
|
// is unknown. Examples include using the InsertNextCell() method, or
|
|
// when using the CellArray::EstimateSize() method to create vertices,
|
|
// lines, polygons, or triangle strips.
|
|
void Squeeze();
|
|
|
|
// Description:
|
|
// Return the maximum cell size in this poly data.
|
|
int GetMaxCellSize();
|
|
|
|
// Description:
|
|
// Set the cell array defining vertices.
|
|
void SetVerts (vtkCellArray* v);
|
|
|
|
// Description:
|
|
// Get the cell array defining vertices. If there are no vertices, an
|
|
// empty array will be returned (convenience to simplify traversal).
|
|
vtkCellArray *GetVerts();
|
|
|
|
// Description:
|
|
// Set the cell array defining lines.
|
|
void SetLines (vtkCellArray* l);
|
|
|
|
// Description:
|
|
// Get the cell array defining lines. If there are no lines, an
|
|
// empty array will be returned (convenience to simplify traversal).
|
|
vtkCellArray *GetLines();
|
|
|
|
// Description:
|
|
// Set the cell array defining polygons.
|
|
void SetPolys (vtkCellArray* p);
|
|
|
|
// Description:
|
|
// Get the cell array defining polygons. If there are no polygons, an
|
|
// empty array will be returned (convenience to simplify traversal).
|
|
vtkCellArray *GetPolys();
|
|
|
|
// Description:
|
|
// Set the cell array defining triangle strips.
|
|
void SetStrips (vtkCellArray* s);
|
|
|
|
// Description:
|
|
// Get the cell array defining triangle strips. If there are no
|
|
// triangle strips, an empty array will be returned (convenience to
|
|
// simplify traversal).
|
|
vtkCellArray *GetStrips();
|
|
|
|
// Description:
|
|
// Return the number of primitives of a particular type held..
|
|
vtkIdType GetNumberOfVerts();
|
|
vtkIdType GetNumberOfLines();
|
|
vtkIdType GetNumberOfPolys();
|
|
vtkIdType GetNumberOfStrips();
|
|
|
|
// Description:
|
|
// Method allocates initial storage for vertex, line, polygon, and
|
|
// triangle strip arrays. Use this method before the method
|
|
// PolyData::InsertNextCell(). (Or, provide vertex, line, polygon, and
|
|
// triangle strip cell arrays.)
|
|
void Allocate(vtkIdType numCells=1000, int extSize=1000);
|
|
|
|
// Description:
|
|
// Similar to the method above, this method allocates initial storage for
|
|
// vertex, line, polygon, and triangle strip arrays. It does this more
|
|
// intelligently, examining the supplied inPolyData to determine whether to
|
|
// allocate the verts, lines, polys, and strips arrays. (These arrays are
|
|
// allocated only if there is data in the corresponding arrays in the
|
|
// inPolyData.) Caution: if the inPolyData has no verts, and after
|
|
// allocating with this method an PolyData::InsertNextCell() is invoked
|
|
// where a vertex is inserted, bad things will happen.
|
|
void Allocate(vtkPolyData *inPolyData, vtkIdType numCells=1000,
|
|
int extSize=1000);
|
|
|
|
// Description:
|
|
// Insert a cell of type VTK_VERTEX, VTK_POLY_VERTEX, VTK_LINE, VTK_POLY_LINE,
|
|
// VTK_TRIANGLE, VTK_QUAD, VTK_POLYGON, or VTK_TRIANGLE_STRIP. Make sure that
|
|
// the PolyData::Allocate() function has been called first or that vertex,
|
|
// line, polygon, and triangle strip arrays have been supplied.
|
|
// Note: will also insert VTK_PIXEL, but converts it to VTK_QUAD.
|
|
int InsertNextCell(int type, int npts, vtkIdType *pts);
|
|
|
|
// Description:
|
|
// Insert a cell of type VTK_VERTEX, VTK_POLY_VERTEX, VTK_LINE, VTK_POLY_LINE,
|
|
// VTK_TRIANGLE, VTK_QUAD, VTK_POLYGON, or VTK_TRIANGLE_STRIP. Make sure that
|
|
// the PolyData::Allocate() function has been called first or that vertex,
|
|
// line, polygon, and triangle strip arrays have been supplied.
|
|
// Note: will also insert VTK_PIXEL, but converts it to VTK_QUAD.
|
|
int InsertNextCell(int type, vtkIdList *pts);
|
|
|
|
// Description:
|
|
// Begin inserting data all over again. Memory is not freed but otherwise
|
|
// objects are returned to their initial state.
|
|
void Reset();
|
|
|
|
// Description:
|
|
// Create data structure that allows random access of cells.
|
|
void BuildCells();
|
|
|
|
// Description:
|
|
// Create upward links from points to cells that use each point. Enables
|
|
// topologically complex queries. Normally the links array is allocated
|
|
// based on the number of points in the vtkPolyData. The optional
|
|
// initialSize parameter can be used to allocate a larger size initially.
|
|
void BuildLinks(int initialSize=0);
|
|
|
|
// Description:
|
|
// Release data structure that allows random access of the cells. This must
|
|
// be done before a 2nd call to BuildLinks(). DeleteCells implicitly deletes
|
|
// the links as well since they are no longer valid.
|
|
void DeleteCells();
|
|
|
|
// Description:
|
|
// Release the upward links from point to cells that use each point.
|
|
void DeleteLinks();
|
|
|
|
// Description:
|
|
// Special (efficient) operations on poly data. Use carefully.
|
|
void GetPointCells(vtkIdType ptId, unsigned short& ncells,
|
|
vtkIdType* &cells);
|
|
|
|
// Description:
|
|
// Get the neighbors at an edge. More efficient than the general
|
|
// GetCellNeighbors(). Assumes links have been built (with BuildLinks()),
|
|
// and looks specifically for edge neighbors.
|
|
void GetCellEdgeNeighbors(vtkIdType cellId, vtkIdType p1, vtkIdType p2,
|
|
vtkIdList *cellIds);
|
|
|
|
// Description:
|
|
// Return a pointer to a list of point ids defining cell. (More efficient.)
|
|
// Assumes that cells have been built (with BuildCells()).
|
|
void GetCellPoints(vtkIdType cellId, vtkIdType& npts, vtkIdType* &pts);
|
|
|
|
// Description:
|
|
// Given three vertices, determine whether it's a triangle. Make sure
|
|
// BuildLinks() has been called first.
|
|
int IsTriangle(int v1, int v2, int v3);
|
|
|
|
// Description:
|
|
// Determine whether two points form an edge. If they do, return non-zero.
|
|
// By definition PolyVertex and PolyLine have no edges since 1-dimensional
|
|
// edges are only found on cells 2D and higher.
|
|
// Edges are defined as 1-D boundary entities to cells.
|
|
// Make sure BuildLinks() has been called first.
|
|
int IsEdge(vtkIdType p1, vtkIdType p2);
|
|
|
|
// Description:
|
|
// Determine whether a point is used by a particular cell. If it is, return
|
|
// non-zero. Make sure BuildCells() has been called first.
|
|
int IsPointUsedByCell(vtkIdType ptId, vtkIdType cellId);
|
|
|
|
// Description:
|
|
// Replace the points defining cell "cellId" with a new set of points. This
|
|
// operator is (typically) used when links from points to cells have not been
|
|
// built (i.e., BuildLinks() has not been executed). Use the operator
|
|
// ReplaceLinkedCell() to replace a cell when cell structure has been built.
|
|
void ReplaceCell(vtkIdType cellId, int npts, vtkIdType *pts);
|
|
|
|
// Description:
|
|
// Replace a point in the cell connectivity list with a different point.
|
|
void ReplaceCellPoint(vtkIdType cellId, vtkIdType oldPtId,
|
|
vtkIdType newPtId);
|
|
|
|
// Description:
|
|
// Reverse the order of point ids defining the cell.
|
|
void ReverseCell(vtkIdType cellId);
|
|
|
|
// Description:
|
|
// Mark a point/cell as deleted from this vtkPolyData.
|
|
void DeletePoint(vtkIdType ptId);
|
|
void DeleteCell(vtkIdType cellId);
|
|
|
|
// Description:
|
|
// Add a point to the cell data structure (after cell pointers have been
|
|
// built). This method adds the point and then allocates memory for the
|
|
// links to the cells. (To use this method, make sure points are available
|
|
// and BuildLinks() has been invoked.) Of the two methods below, one inserts
|
|
// a point coordinate and the other just makes room for cell links.
|
|
int InsertNextLinkedPoint(int numLinks);
|
|
int InsertNextLinkedPoint(double x[3], int numLinks);
|
|
|
|
// Description:
|
|
// Add a new cell to the cell data structure (after cell pointers have been
|
|
// built). This method adds the cell and then updates the links from the
|
|
// points to the cells. (Memory is allocated as necessary.)
|
|
int InsertNextLinkedCell(int type, int npts, vtkIdType *pts);
|
|
|
|
// Description:
|
|
// Replace one cell with another in cell structure. This operator updates the
|
|
// connectivity list and the point's link list. It does not delete references
|
|
// to the old cell in the point's link list. Use the operator
|
|
// RemoveCellReference() to delete all references from points to (old) cell.
|
|
// You may also want to consider using the operator ResizeCellList() if the
|
|
// link list is changing size.
|
|
void ReplaceLinkedCell(vtkIdType cellId, int npts, vtkIdType *pts);
|
|
|
|
// Description:
|
|
// Remove all references to cell in cell structure. This means the links from
|
|
// the cell's points to the cell are deleted. Memory is not reclaimed. Use the
|
|
// method ResizeCellList() to resize the link list from a point to its using
|
|
// cells. (This operator assumes BuildLinks() has been called.)
|
|
void RemoveCellReference(vtkIdType cellId);
|
|
|
|
// Description:
|
|
// Add references to cell in cell structure. This means the links from
|
|
// the cell's points to the cell are modified. Memory is not extended. Use the
|
|
// method ResizeCellList() to resize the link list from a point to its using
|
|
// cells. (This operator assumes BuildLinks() has been called.)
|
|
void AddCellReference(vtkIdType cellId);
|
|
|
|
// Description:
|
|
// Remove a reference to a cell in a particular point's link list. You may
|
|
// also consider using RemoveCellReference() to remove the references from
|
|
// all the cell's points to the cell. This operator does not reallocate
|
|
// memory; use the operator ResizeCellList() to do this if necessary.
|
|
void RemoveReferenceToCell(vtkIdType ptId, vtkIdType cellId);
|
|
|
|
// Description:
|
|
// Add a reference to a cell in a particular point's link list. (You may also
|
|
// consider using AddCellReference() to add the references from all the
|
|
// cell's points to the cell.) This operator does not realloc memory; use the
|
|
// operator ResizeCellList() to do this if necessary.
|
|
void AddReferenceToCell(vtkIdType ptId, vtkIdType cellId);
|
|
|
|
// Description:
|
|
// Resize the list of cells using a particular point. (This operator assumes
|
|
// that BuildLinks() has been called.)
|
|
void ResizeCellList(vtkIdType ptId, int size);
|
|
|
|
// Description:
|
|
// Restore object to initial state. Release memory back to system.
|
|
virtual void Initialize();
|
|
|
|
// Description:
|
|
// For streaming. User/next filter specifies which piece they want updated.
|
|
// The source of this poly data has to return exactly this piece.
|
|
void GetUpdateExtent(int &piece, int &numPieces, int &ghostLevel);
|
|
|
|
// Description:
|
|
// We need this here to avoid hiding superclass method
|
|
virtual int* GetUpdateExtent();
|
|
virtual void GetUpdateExtent(int& x0, int& x1, int& y0, int& y1,
|
|
int& z0, int& z1);
|
|
virtual void GetUpdateExtent(int extent[6]);
|
|
|
|
// Description:
|
|
// Get the piece and the number of pieces. Similar to extent in 3D.
|
|
virtual int GetPiece();
|
|
virtual int GetNumberOfPieces();
|
|
|
|
// Description:
|
|
// Get the ghost level.
|
|
virtual int GetGhostLevel();
|
|
|
|
// Description:
|
|
// Return the actual size of the data in kilobytes. This number
|
|
// is valid only after the pipeline has updated. The memory size
|
|
// returned is guaranteed to be greater than or equal to the
|
|
// memory required to represent the data (e.g., extra space in
|
|
// arrays, etc. are not included in the return value). THIS METHOD
|
|
// IS THREAD SAFE.
|
|
unsigned long GetActualMemorySize();
|
|
|
|
// Description:
|
|
// Shallow and Deep copy.
|
|
void ShallowCopy(vtkDataObject *src);
|
|
void DeepCopy(vtkDataObject *src);
|
|
|
|
// Description:
|
|
// This method will remove any cell that has a ghost level array value
|
|
// greater or equal to level. It does not remove unused points (yet).
|
|
void RemoveGhostCells(int level);
|
|
|
|
//BTX
|
|
// Description:
|
|
// Retrieve an instance of this class from an information object.
|
|
static vtkPolyData* GetData(vtkInformation* info);
|
|
static vtkPolyData* GetData(vtkInformationVector* v, int i=0);
|
|
//ETX
|
|
|
|
protected:
|
|
vtkPolyData();
|
|
~vtkPolyData();
|
|
|
|
// constant cell objects returned by GetCell called.
|
|
vtkVertex *Vertex;
|
|
vtkPolyVertex *PolyVertex;
|
|
vtkLine *Line;
|
|
vtkPolyLine *PolyLine;
|
|
vtkTriangle *Triangle;
|
|
vtkQuad *Quad;
|
|
vtkPolygon *Polygon;
|
|
vtkTriangleStrip *TriangleStrip;
|
|
vtkEmptyCell *EmptyCell;
|
|
|
|
// points inherited
|
|
// point data (i.e., scalars, vectors, normals, tcoords) inherited
|
|
vtkCellArray *Verts;
|
|
vtkCellArray *Lines;
|
|
vtkCellArray *Polys;
|
|
vtkCellArray *Strips;
|
|
|
|
// dummy static member below used as a trick to simplify traversal
|
|
static vtkCellArray *Dummy;
|
|
|
|
// supporting structures for more complex topological operations
|
|
// built only when necessary
|
|
vtkCellTypes *Cells;
|
|
vtkCellLinks *Links;
|
|
|
|
// This method is called during an update.
|
|
// If the CropFilter is set, the user reqquested a piece which the
|
|
// source cannot generate, then it will break up the
|
|
// data set in order to satisfy the request.
|
|
virtual void Crop();
|
|
|
|
|
|
private:
|
|
// Hide these from the user and the compiler.
|
|
|
|
// Description:
|
|
// For legacy compatibility. Do not use.
|
|
void GetCellNeighbors(vtkIdType cellId, vtkIdList& ptIds, vtkIdList& cellIds)
|
|
{this->GetCellNeighbors(cellId, &ptIds, &cellIds);}
|
|
|
|
private:
|
|
vtkPolyData(const vtkPolyData&); // Not implemented.
|
|
void operator=(const vtkPolyData&); // Not implemented.
|
|
};
|
|
|
|
inline void vtkPolyData::GetPointCells(vtkIdType ptId, unsigned short& ncells,
|
|
vtkIdType* &cells)
|
|
{
|
|
ncells = this->Links->GetNcells(ptId);
|
|
cells = this->Links->GetCells(ptId);
|
|
}
|
|
|
|
inline int vtkPolyData::IsTriangle(int v1, int v2, int v3)
|
|
{
|
|
unsigned short int n1;
|
|
int i, j, tVerts[3];
|
|
vtkIdType *cells, *tVerts2, n2;
|
|
|
|
tVerts[0] = v1;
|
|
tVerts[1] = v2;
|
|
tVerts[2] = v3;
|
|
|
|
for (i=0; i<3; i++)
|
|
{
|
|
this->GetPointCells(tVerts[i], n1, cells);
|
|
for (j=0; j<n1; j++)
|
|
{
|
|
this->GetCellPoints(cells[j], n2, tVerts2);
|
|
if ( (tVerts[0] == tVerts2[0] || tVerts[0] == tVerts2[1] ||
|
|
tVerts[0] == tVerts2[2]) &&
|
|
(tVerts[1] == tVerts2[0] || tVerts[1] == tVerts2[1] ||
|
|
tVerts[1] == tVerts2[2]) &&
|
|
(tVerts[2] == tVerts2[0] || tVerts[2] == tVerts2[1] ||
|
|
tVerts[2] == tVerts2[2]) )
|
|
{
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
inline int vtkPolyData::IsPointUsedByCell(vtkIdType ptId, vtkIdType cellId)
|
|
{
|
|
vtkIdType *pts, npts;
|
|
|
|
this->GetCellPoints(cellId, npts, pts);
|
|
for (vtkIdType i=0; i < npts; i++)
|
|
{
|
|
if ( pts[i] == ptId )
|
|
{
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
inline void vtkPolyData::DeletePoint(vtkIdType ptId)
|
|
{
|
|
this->Links->DeletePoint(ptId);
|
|
}
|
|
|
|
inline void vtkPolyData::DeleteCell(vtkIdType cellId)
|
|
{
|
|
this->Cells->DeleteCell(cellId);
|
|
}
|
|
|
|
inline void vtkPolyData::RemoveCellReference(vtkIdType cellId)
|
|
{
|
|
vtkIdType *pts, npts;
|
|
|
|
this->GetCellPoints(cellId, npts, pts);
|
|
for (vtkIdType i=0; i<npts; i++)
|
|
{
|
|
this->Links->RemoveCellReference(cellId, pts[i]);
|
|
}
|
|
}
|
|
|
|
inline void vtkPolyData::AddCellReference(vtkIdType cellId)
|
|
{
|
|
vtkIdType *pts, npts;
|
|
|
|
this->GetCellPoints(cellId, npts, pts);
|
|
for (vtkIdType i=0; i<npts; i++)
|
|
{
|
|
this->Links->AddCellReference(cellId, pts[i]);
|
|
}
|
|
}
|
|
|
|
inline void vtkPolyData::ResizeCellList(vtkIdType ptId, int size)
|
|
{
|
|
this->Links->ResizeCellList(ptId,size);
|
|
}
|
|
|
|
inline void vtkPolyData::ReplaceCellPoint(vtkIdType cellId, vtkIdType oldPtId,
|
|
vtkIdType newPtId)
|
|
{
|
|
int i;
|
|
vtkIdType *verts, nverts;
|
|
|
|
this->GetCellPoints(cellId,nverts,verts);
|
|
for ( i=0; i < nverts; i++ )
|
|
{
|
|
if ( verts[i] == oldPtId )
|
|
{
|
|
verts[i] = newPtId; // this is very nasty! direct write!
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|