// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause
// .SECTION Thanks
// This file is part of the generalized Youngs material interface reconstruction algorithm
// contributed by CEA/DIF - Commissariat a l'Energie Atomique, Centre DAM Ile-De-France
BP12,
// F-91297 Arpajon, France.
Implementation by Thierry Carrard (CEA)
#include "vtkDataSetGradientPrecompute.h"
#include "vtkCell.h"
#include "vtkCell3D.h"
#include "vtkCellData.h"
#include "vtkDataSet.h"
#include "vtkDoubleArray.h"
#include "vtkFieldData.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkMath.h"
#include "vtkObjectFactory.h"
#include "vtkTetra.h"
#include "vtkTriangle.h"
#include
#define VTK_DATASET_GRADIENT_TETRA_OPTIMIZATION
#define VTK_DATASET_GRADIENT_TRIANGLE_OPTIMIZATION
// #define DEBUG
VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkDataSetGradientPrecompute);
vtkDataSetGradientPrecompute::vtkDataSetGradientPrecompute() = default;
vtkDataSetGradientPrecompute::~vtkDataSetGradientPrecompute() = default;
void vtkDataSetGradientPrecompute::PrintSelf(ostream& os, vtkIndent indent)
{
this->Superclass::PrintSelf(os, indent);
}
#define ADD_VEC(a, b) \
a[0] += b[0]; \
a[1] += b[1]; \
a[2] += b[2]
#define SCALE_VEC(a, b) \
a[0] *= b; \
a[1] *= b; \
a[2] *= b
#define ZERO_VEC(a) \
a[0] = 0; \
a[1] = 0; \
a[2] = 0
#define MAX_CELL_POINTS 128
#define VTK_CQS_EPSILON 1e-12
static inline void TETRA_CQS_VECTOR(
double v0[3], double v1[3], double v2[3], double p[3], double cqs[3])
{
double surface = fabs(vtkTriangle::TriangleArea(v0, v1, v2));
vtkTriangle::ComputeNormal(v0, v1, v2, cqs);
// inverse face normal if not toward opposite vertex
double edge[3];
edge[0] = p[0] - v0[0];
edge[1] = p[1] - v0[1];
edge[2] = p[2] - v0[2];
if (vtkMath::Dot(edge, cqs) < 0)
{
cqs[0] = -cqs[0];
cqs[1] = -cqs[1];
cqs[2] = -cqs[2];
}
SCALE_VEC(cqs, surface / 2.0);
}
static inline void TRIANGLE_CQS_VECTOR(double v0[3], double v1[3], double p[3], double cqs[3])
{
double length = sqrt(vtkMath::Distance2BetweenPoints(v0, v1));
double a[3], b[3], c[3];
for (int i = 0; i < 3; i++)
{
a[i] = v1[i] - v0[i];
b[i] = p[i] - v0[i];
}
vtkMath::Cross(a, b, c);
vtkMath::Cross(c, a, cqs);
vtkMath::Normalize(cqs);
SCALE_VEC(cqs, length / 2.0);
}
static inline void LINE_CQS_VECTOR(double v0[3], double p[3], double cqs[3])
{
cqs[0] = p[0] - v0[0];
cqs[1] = p[1] - v0[1];
cqs[2] = p[2] - v0[2];
vtkMath::Normalize(cqs);
}
int vtkDataSetGradientPrecompute::GradientPrecompute(vtkDataSet* ds, vtkDataSetAlgorithm* self)
{
vtkIdType nCells = ds->GetNumberOfCells();
vtkIdType nCellNodes = 0;
for (vtkIdType i = 0; i < nCells; i++)
{
nCellNodes += ds->GetCell(i)->GetNumberOfPoints();
}
vtkDoubleArray* cqs = vtkDoubleArray::New();
cqs->SetName("GradientPrecomputation");
cqs->SetNumberOfComponents(3);
cqs->SetNumberOfTuples(nCellNodes);
cqs->FillComponent(0, 0.0);
cqs->FillComponent(1, 0.0);
cqs->FillComponent(2, 0.0);
// The cell size determines the amount of space the cell takes up. For 3D
// cells this is the volume. For 2D cells this is the area. For 1D cells
// this is the length. For 0D cells this is undefined, but we set it to 1 so
// as not to get invalid results when normalizing something by the cell size.
vtkDoubleArray* cellSize = vtkDoubleArray::New();
cellSize->SetName("CellSize");
cellSize->SetNumberOfTuples(nCells);
vtkIdType curPoint = 0;
for (vtkIdType c = 0; c < nCells; c++)
{
if (self && self->CheckAbort())
{
break;
}
vtkCell* cell = ds->GetCell(c);
int np = cell->GetNumberOfPoints();
double cellCenter[3] = { 0, 0, 0 };
double cellPoints[MAX_CELL_POINTS][3];
double cellVectors[MAX_CELL_POINTS][3];
double tmp[3];
double size = 0.0;
for (int p = 0; p < np; p++)
{
ds->GetPoint(cell->GetPointId(p), cellPoints[p]);
ADD_VEC(cellCenter, cellPoints[p]);
ZERO_VEC(cellVectors[p]);
}
SCALE_VEC(cellCenter, 1.0 / np);
// -= 3 D =-
if (cell->GetCellDimension() == 3)
{
#ifdef VTK_DATASET_GRADIENT_TETRA_OPTIMIZATION
if (np == 4) // cell is a tetrahedra
{
// vtkWarningMacro(<<"Tetra detected\n");
size = fabs(vtkTetra::ComputeVolume(
cellPoints[0], cellPoints[1], cellPoints[2], cellPoints[3])) *
1.5;
TETRA_CQS_VECTOR(cellPoints[0], cellPoints[1], cellPoints[2], cellPoints[3], tmp);
ADD_VEC(cellVectors[3], tmp);
TETRA_CQS_VECTOR(cellPoints[1], cellPoints[2], cellPoints[3], cellPoints[0], tmp);
ADD_VEC(cellVectors[0], tmp);
TETRA_CQS_VECTOR(cellPoints[2], cellPoints[3], cellPoints[0], cellPoints[1], tmp);
ADD_VEC(cellVectors[1], tmp);
TETRA_CQS_VECTOR(cellPoints[3], cellPoints[0], cellPoints[1], cellPoints[2], tmp);
ADD_VEC(cellVectors[2], tmp);
}
else if (np > 4)
#endif
{
vtkCell3D* cell3d = static_cast(cell);
int nf = cell->GetNumberOfFaces();
for (int f = 0; f < nf; f++)
{
const vtkIdType* faceIds = nullptr;
int nfp = cell->GetFace(f)->GetNumberOfPoints();
cell3d->GetFacePoints(f, faceIds);
#ifdef VTK_DATASET_GRADIENT_TRIANGLE_OPTIMIZATION
if (nfp == 3) // face is a triangle
{
// vtkWarningMacro(<<"triangular face detected\n");
size += fabs(vtkTetra::ComputeVolume(cellCenter, cellPoints[faceIds[0]],
cellPoints[faceIds[1]], cellPoints[faceIds[2]])) *
1.5;
TETRA_CQS_VECTOR(cellCenter, cellPoints[faceIds[0]], cellPoints[faceIds[1]],
cellPoints[faceIds[2]], tmp);
ADD_VEC(cellVectors[faceIds[2]], tmp);
TETRA_CQS_VECTOR(cellCenter, cellPoints[faceIds[1]], cellPoints[faceIds[2]],
cellPoints[faceIds[0]], tmp);
ADD_VEC(cellVectors[faceIds[0]], tmp);
TETRA_CQS_VECTOR(cellCenter, cellPoints[faceIds[2]], cellPoints[faceIds[0]],
cellPoints[faceIds[1]], tmp);
ADD_VEC(cellVectors[faceIds[1]], tmp);
}
else if (nfp > 3) // generic case
#endif
{
double faceCenter[3] = { 0, 0, 0 };
for (int p = 0; p < nfp; p++)
{
ADD_VEC(faceCenter, cellPoints[faceIds[p]]);
}
SCALE_VEC(faceCenter, 1.0 / nfp);
for (int p = 0; p < nfp; p++)
{
int p2 = (p + 1) % nfp;
size += fabs(vtkTetra::ComputeVolume(
cellCenter, faceCenter, cellPoints[faceIds[p]], cellPoints[faceIds[p2]]));
TETRA_CQS_VECTOR(
cellCenter, faceCenter, cellPoints[faceIds[p]], cellPoints[faceIds[p2]], tmp);
ADD_VEC(cellVectors[faceIds[p2]], tmp);
TETRA_CQS_VECTOR(
cellCenter, faceCenter, cellPoints[faceIds[p2]], cellPoints[faceIds[p]], tmp);
ADD_VEC(cellVectors[faceIds[p]], tmp);
}
}
}
}
}
// -= 2 D =-
else if (cell->GetCellDimension() == 2)
{
if (np == 3) // cell is a triangle
{
size = fabs(vtkTriangle::TriangleArea(cellPoints[0], cellPoints[1], cellPoints[2]));
TRIANGLE_CQS_VECTOR(cellPoints[0], cellPoints[1], cellPoints[2], tmp);
ADD_VEC(cellVectors[2], tmp);
TRIANGLE_CQS_VECTOR(cellPoints[1], cellPoints[2], cellPoints[0], tmp);
ADD_VEC(cellVectors[0], tmp);
TRIANGLE_CQS_VECTOR(cellPoints[2], cellPoints[0], cellPoints[1], tmp);
ADD_VEC(cellVectors[1], tmp);
}
else if (np > 3) // generic case
{
for (int f = 0; f < np; f++)
{
const int e0 = f;
const int e1 = (f + 1) % np;
size += fabs(vtkTriangle::TriangleArea(cellCenter, cellPoints[e0], cellPoints[e1]));
TRIANGLE_CQS_VECTOR(cellCenter, cellPoints[e0], cellPoints[e1], tmp);
ADD_VEC(cellVectors[e1], tmp);
TRIANGLE_CQS_VECTOR(cellCenter, cellPoints[e1], cellPoints[e0], tmp);
ADD_VEC(cellVectors[e0], tmp);
}
}
else
{
// vtkWarningMacro(<<"Can't process 2D cells with less than 3 points.");
// return 0;
}
}
// -= 1 D =-
else if (cell->GetCellDimension() == 1)
{
if (np == 2) // cell is a single line segment
{
size = sqrt(vtkMath::Distance2BetweenPoints(cellPoints[0], cellPoints[1]));
LINE_CQS_VECTOR(cellPoints[0], cellPoints[1], tmp);
ADD_VEC(cellVectors[1], tmp);
LINE_CQS_VECTOR(cellPoints[1], cellPoints[0], tmp);
ADD_VEC(cellVectors[0], tmp);
}
else if (np > 2) // generic case, a poly line
{
for (int p = 0; p < np; p++)
{
size += sqrt(vtkMath::Distance2BetweenPoints(cellCenter, cellPoints[p]));
LINE_CQS_VECTOR(cellCenter, cellPoints[p], tmp);
ADD_VEC(cellVectors[p], tmp);
}
}
}
// -= 0 D =-
else
{
// For vertex cells, estimate gradient as weighted sum of vectors from
// centroid.
size = 1.0;
for (int p = 0; p < np; p++)
{
cellVectors[p][0] = cellPoints[p][0] - cellCenter[0];
cellVectors[p][1] = cellPoints[p][1] - cellCenter[1];
cellVectors[p][2] = cellPoints[p][2] - cellCenter[2];
}
}
cellSize->SetTuple1(c, size);
for (int p = 0; p < np; ++p)
{
cqs->SetTuple(curPoint + p, cellVectors[p]);
}
// check cqs consistency
#ifdef DEBUG
double checkZero[3] = { 0, 0, 0 };
double checkVolume = 0;
for (int p = 0; p < np; p++)
{
checkVolume += vtkMath::Dot(cellPoints[p], cellVectors[p]);
ADD_VEC(checkZero, cellVectors[p]);
}
checkVolume /= (double)cell->GetCellDimension();
if (vtkMath::Norm(checkZero) > VTK_CQS_EPSILON || fabs(size - checkVolume) > VTK_CQS_EPSILON)
{
std::cout << "Bad CQS sum at cell #" << c << ", Sum=" << vtkMath::Norm(checkZero)
<< ", volume=" << size << ", ratio Vol=" << size / checkVolume << "\n";
}
#endif
curPoint += np;
}
ds->GetFieldData()->AddArray(cqs);
ds->GetCellData()->AddArray(cellSize);
cqs->Delete();
cellSize->Delete();
return 1;
}
int vtkDataSetGradientPrecompute::RequestData(vtkInformation* vtkNotUsed(request),
vtkInformationVector** inputVector, vtkInformationVector* outputVector)
{
// get the info objects
vtkInformation* inInfo = inputVector[0]->GetInformationObject(0);
vtkInformation* outInfo = outputVector->GetInformationObject(0);
// get connected input & output
vtkDataSet* _output = vtkDataSet::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT()));
vtkDataSet* _input = vtkDataSet::SafeDownCast(inInfo->Get(vtkDataObject::DATA_OBJECT()));
if (_input == nullptr || _output == nullptr)
{
vtkErrorMacro(<< "missing input/output connection\n");
return 0;
}
_output->ShallowCopy(_input);
return vtkDataSetGradientPrecompute::GradientPrecompute(_output, this);
}
VTK_ABI_NAMESPACE_END