mirror of
https://gitlab.kitware.com/vtk/vtk-m
synced 2024-09-08 13:23:51 +00:00
Merge topic 'add_vector_cell_gradient'
8dadf560 Add in support for vector fields to Gradient worklet. d53f43fb CellDerivaties now support computing the derivatives of vtkm::Vec fields. b4378c85 Allow vtkm::Matrix to support T values which are vtkm::Vec. 9caabf97 vtkm::Vec now supports +=, -=, *=, and /=. Acked-by: Kitware Robot <kwrobot@kitware.com> Acked-by: Kenneth Moreland <kmorel@sandia.gov> Merge-request: !608
This commit is contained in:
commit
8861beda4b
58
vtkm/Math.h
58
vtkm/Math.h
@ -1550,6 +1550,35 @@ struct FloatLimits<vtkm::Float32>
|
||||
}
|
||||
};
|
||||
|
||||
template<int N>
|
||||
struct FloatLimits< vtkm::Vec<vtkm::Float32,N> >
|
||||
{
|
||||
typedef vtkm::detail::IEEE754Bits32 BitsType;
|
||||
|
||||
VTKM_EXEC_CONT_EXPORT
|
||||
static vtkm::Vec<vtkm::Float32,N> Nan() {
|
||||
BitsType nan = {VTKM_NAN_BITS_32};
|
||||
return vtkm::Vec<vtkm::Float32,N>(nan.scalar);
|
||||
}
|
||||
|
||||
VTKM_EXEC_CONT_EXPORT
|
||||
static vtkm::Vec<vtkm::Float32,N> Infinity() {
|
||||
BitsType inf = {VTKM_INF_BITS_32};
|
||||
return vtkm::Vec<vtkm::Float32,N>(inf.scalar);
|
||||
}
|
||||
|
||||
VTKM_EXEC_CONT_EXPORT
|
||||
static vtkm::Vec<vtkm::Float32,N> NegativeInfinity() {
|
||||
BitsType neginf = {VTKM_NEG_INF_BITS_32};
|
||||
return vtkm::Vec<vtkm::Float32,N>(neginf.scalar);
|
||||
}
|
||||
|
||||
VTKM_EXEC_CONT_EXPORT
|
||||
static vtkm::Vec<vtkm::Float32,N> Epsilon() {
|
||||
return vtkm::Vec<vtkm::Float32,N>(VTKM_EPSILON_32);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct FloatLimits<vtkm::Float64>
|
||||
{
|
||||
@ -1579,6 +1608,35 @@ struct FloatLimits<vtkm::Float64>
|
||||
}
|
||||
};
|
||||
|
||||
template<int N>
|
||||
struct FloatLimits< vtkm::Vec<vtkm::Float64,N> >
|
||||
{
|
||||
typedef vtkm::detail::IEEE754Bits64 BitsType;
|
||||
|
||||
VTKM_EXEC_CONT_EXPORT
|
||||
static vtkm::Vec<vtkm::Float64,N> Nan() {
|
||||
BitsType nan = {VTKM_NAN_BITS_64};
|
||||
return vtkm::Vec<vtkm::Float64,N>(nan.scalar);
|
||||
}
|
||||
|
||||
VTKM_EXEC_CONT_EXPORT
|
||||
static vtkm::Vec<vtkm::Float64,N> Infinity() {
|
||||
BitsType inf = {VTKM_INF_BITS_64};
|
||||
return vtkm::Vec<vtkm::Float64,N>(inf.scalar);
|
||||
}
|
||||
|
||||
VTKM_EXEC_CONT_EXPORT
|
||||
static vtkm::Vec<vtkm::Float64,N> NegativeInfinity() {
|
||||
BitsType neginf = {VTKM_NEG_INF_BITS_64};
|
||||
return vtkm::Vec<vtkm::Float64,N>(neginf.scalar);
|
||||
}
|
||||
|
||||
VTKM_EXEC_CONT_EXPORT
|
||||
static vtkm::Vec<vtkm::Float64,N> Epsilon() {
|
||||
return vtkm::Vec<vtkm::Float64,N>(VTKM_EPSILON_64);
|
||||
}
|
||||
};
|
||||
|
||||
#undef VTKM_NAN_BITS_32
|
||||
#undef VTKM_INF_BITS_32
|
||||
#undef VTKM_NEG_INF_BITS_32
|
||||
|
@ -566,6 +566,35 @@ struct FloatLimits<vtkm::Float32>
|
||||
}
|
||||
};
|
||||
|
||||
template<int N>
|
||||
struct FloatLimits< vtkm::Vec<vtkm::Float32,N> >
|
||||
{
|
||||
typedef vtkm::detail::IEEE754Bits32 BitsType;
|
||||
|
||||
VTKM_EXEC_CONT_EXPORT
|
||||
static vtkm::Vec<vtkm::Float32,N> Nan() {
|
||||
BitsType nan = {VTKM_NAN_BITS_32};
|
||||
return vtkm::Vec<vtkm::Float32,N>(nan.scalar);
|
||||
}
|
||||
|
||||
VTKM_EXEC_CONT_EXPORT
|
||||
static vtkm::Vec<vtkm::Float32,N> Infinity() {
|
||||
BitsType inf = {VTKM_INF_BITS_32};
|
||||
return vtkm::Vec<vtkm::Float32,N>(inf.scalar);
|
||||
}
|
||||
|
||||
VTKM_EXEC_CONT_EXPORT
|
||||
static vtkm::Vec<vtkm::Float32,N> NegativeInfinity() {
|
||||
BitsType neginf = {VTKM_NEG_INF_BITS_32};
|
||||
return vtkm::Vec<vtkm::Float32,N>(neginf.scalar);
|
||||
}
|
||||
|
||||
VTKM_EXEC_CONT_EXPORT
|
||||
static vtkm::Vec<vtkm::Float32,N> Epsilon() {
|
||||
return vtkm::Vec<vtkm::Float32,N>(VTKM_EPSILON_32);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct FloatLimits<vtkm::Float64>
|
||||
{
|
||||
@ -595,6 +624,35 @@ struct FloatLimits<vtkm::Float64>
|
||||
}
|
||||
};
|
||||
|
||||
template<int N>
|
||||
struct FloatLimits< vtkm::Vec<vtkm::Float64,N> >
|
||||
{
|
||||
typedef vtkm::detail::IEEE754Bits64 BitsType;
|
||||
|
||||
VTKM_EXEC_CONT_EXPORT
|
||||
static vtkm::Vec<vtkm::Float64,N> Nan() {
|
||||
BitsType nan = {VTKM_NAN_BITS_64};
|
||||
return vtkm::Vec<vtkm::Float64,N>(nan.scalar);
|
||||
}
|
||||
|
||||
VTKM_EXEC_CONT_EXPORT
|
||||
static vtkm::Vec<vtkm::Float64,N> Infinity() {
|
||||
BitsType inf = {VTKM_INF_BITS_64};
|
||||
return vtkm::Vec<vtkm::Float64,N>(inf.scalar);
|
||||
}
|
||||
|
||||
VTKM_EXEC_CONT_EXPORT
|
||||
static vtkm::Vec<vtkm::Float64,N> NegativeInfinity() {
|
||||
BitsType neginf = {VTKM_NEG_INF_BITS_64};
|
||||
return vtkm::Vec<vtkm::Float64,N>(neginf.scalar);
|
||||
}
|
||||
|
||||
VTKM_EXEC_CONT_EXPORT
|
||||
static vtkm::Vec<vtkm::Float64,N> Epsilon() {
|
||||
return vtkm::Vec<vtkm::Float64,N>(VTKM_EPSILON_64);
|
||||
}
|
||||
};
|
||||
|
||||
#undef VTKM_NAN_BITS_32
|
||||
#undef VTKM_INF_BITS_32
|
||||
#undef VTKM_NEG_INF_BITS_32
|
||||
|
@ -387,7 +387,7 @@ void MatrixLUPFactor(vtkm::Matrix<T,Size,Size> &A,
|
||||
{
|
||||
permutation[index] = index;
|
||||
}
|
||||
inversionParity = 1;
|
||||
inversionParity = T(1);
|
||||
valid = true;
|
||||
|
||||
for (vtkm::IdComponent rowIndex = 0; rowIndex < Size; rowIndex++)
|
||||
|
40
vtkm/Types.h
40
vtkm/Types.h
@ -491,6 +491,16 @@ public:
|
||||
return result;
|
||||
}
|
||||
|
||||
VTKM_EXEC_CONT_EXPORT
|
||||
DerivedClass& operator+=(const DerivedClass& other)
|
||||
{
|
||||
for (vtkm::IdComponent i = 0; i < Size; ++i)
|
||||
{
|
||||
this->Components[i] += other[i];
|
||||
}
|
||||
return *static_cast<DerivedClass*>(this);
|
||||
}
|
||||
|
||||
VTKM_EXEC_CONT_EXPORT
|
||||
DerivedClass operator-(const DerivedClass& other) const
|
||||
{
|
||||
@ -502,6 +512,16 @@ public:
|
||||
return result;
|
||||
}
|
||||
|
||||
VTKM_EXEC_CONT_EXPORT
|
||||
DerivedClass& operator-=(const DerivedClass& other)
|
||||
{
|
||||
for (vtkm::IdComponent i = 0; i < Size; ++i)
|
||||
{
|
||||
this->Components[i] -= other[i];
|
||||
}
|
||||
return *static_cast<DerivedClass*>(this);
|
||||
}
|
||||
|
||||
VTKM_EXEC_CONT_EXPORT
|
||||
DerivedClass operator*(const DerivedClass& other) const
|
||||
{
|
||||
@ -513,6 +533,16 @@ public:
|
||||
return result;
|
||||
}
|
||||
|
||||
VTKM_EXEC_CONT_EXPORT
|
||||
DerivedClass& operator*=(const DerivedClass& other)
|
||||
{
|
||||
for (vtkm::IdComponent i = 0; i < Size; ++i)
|
||||
{
|
||||
this->Components[i] *= other[i];
|
||||
}
|
||||
return *static_cast<DerivedClass*>(this);
|
||||
}
|
||||
|
||||
VTKM_EXEC_CONT_EXPORT
|
||||
DerivedClass operator/(const DerivedClass& other) const
|
||||
{
|
||||
@ -524,6 +554,16 @@ public:
|
||||
return result;
|
||||
}
|
||||
|
||||
VTKM_EXEC_CONT_EXPORT
|
||||
DerivedClass& operator/=(const DerivedClass& other)
|
||||
{
|
||||
for (vtkm::IdComponent i = 0; i < Size; ++i)
|
||||
{
|
||||
this->Components[i] /= other[i];
|
||||
}
|
||||
return *static_cast<DerivedClass*>(this);
|
||||
}
|
||||
|
||||
#if (defined(VTKM_GCC) || defined(VTKM_CLANG))
|
||||
#pragma GCC diagnostic pop
|
||||
#endif // gcc || clang
|
||||
|
@ -59,9 +59,9 @@ ParametricDerivative(const FieldVecType &field,
|
||||
typedef vtkm::Vec<FieldType,3> GradientType;
|
||||
|
||||
GradientType pc(pcoords);
|
||||
GradientType rc = GradientType(1) - pc;
|
||||
GradientType rc = GradientType(FieldType(1)) - pc;
|
||||
|
||||
GradientType parametricDerivative(0);
|
||||
GradientType parametricDerivative(FieldType(0));
|
||||
VTKM_ACCUM_PARAMETRIC_DERIVATIVE_3D(0, -rc[1]*rc[2], -rc[0]*rc[2], -rc[0]*rc[1]);
|
||||
VTKM_ACCUM_PARAMETRIC_DERIVATIVE_3D(1, rc[1]*rc[2], -pc[0]*rc[2], -pc[0]*rc[1]);
|
||||
VTKM_ACCUM_PARAMETRIC_DERIVATIVE_3D(2, pc[1]*rc[2], pc[0]*rc[2], -pc[0]*pc[1]);
|
||||
@ -146,9 +146,9 @@ ParametricDerivative(const FieldVecType &field,
|
||||
|
||||
GradientType pc(static_cast<FieldType>(pcoords[0]),
|
||||
static_cast<FieldType>(pcoords[1]));
|
||||
GradientType rc = GradientType(1) - pc;
|
||||
GradientType rc = GradientType(FieldType(1)) - pc;
|
||||
|
||||
GradientType parametricDerivative(0);
|
||||
GradientType parametricDerivative(FieldType(0));
|
||||
VTKM_ACCUM_PARAMETRIC_DERIVATIVE_2D(0, -rc[1], -rc[0]);
|
||||
VTKM_ACCUM_PARAMETRIC_DERIVATIVE_2D(1, rc[1], -pc[0]);
|
||||
VTKM_ACCUM_PARAMETRIC_DERIVATIVE_2D(2, pc[1], pc[0]);
|
||||
@ -375,7 +375,7 @@ CellDerivative(const FieldVecType &field,
|
||||
VTKM_ASSERT(wCoords.GetNumberOfComponents() == 1);
|
||||
|
||||
typedef vtkm::Vec<typename FieldVecType::ComponentType,3> GradientType;
|
||||
return GradientType(0,0,0);
|
||||
return vtkm::TypeTraits<GradientType>::ZeroInitialization();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
@ -467,7 +467,7 @@ TriangleDerivative(const vtkm::Vec<ValueType, 3> &field,
|
||||
vtkm::MatrixSetRow(A, 1, v1);
|
||||
vtkm::MatrixSetRow(A, 2, n);
|
||||
|
||||
GradientType b(field[1] - field[0], field[2] - field[0], 0);
|
||||
GradientType b(field[1] - field[0], field[2] - field[0], ValueType(0));
|
||||
|
||||
// If we want to later change this method to take the gradient of multiple
|
||||
// values (for example, to find the Jacobian of a vector field), then there
|
||||
@ -677,7 +677,7 @@ CellDerivative(const FieldVecType &field,
|
||||
|
||||
VecT pc(static_cast<T>(pcoords[0]),
|
||||
static_cast<T>(pcoords[1]));
|
||||
VecT rc = VecT(1) - pc;
|
||||
VecT rc = VecT(T(1)) - pc;
|
||||
|
||||
VecT sum = field[0]*VecT(-rc[1], -rc[0]);
|
||||
sum = sum + field[1]*VecT( rc[1], -pc[0]);
|
||||
@ -686,7 +686,7 @@ CellDerivative(const FieldVecType &field,
|
||||
|
||||
return vtkm::Vec<T,3>(sum[0]/wCoords.GetSpacing()[0],
|
||||
sum[1]/wCoords.GetSpacing()[1],
|
||||
0);
|
||||
T(0));
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
@ -813,7 +813,7 @@ CellDerivative(const FieldVecType &field,
|
||||
VecT pc(static_cast<T>(pcoords[0]),
|
||||
static_cast<T>(pcoords[1]),
|
||||
static_cast<T>(pcoords[2]));
|
||||
VecT rc = VecT(1) - pc;
|
||||
VecT rc = VecT(T(1)) - pc;
|
||||
|
||||
VecT sum = field[0]*VecT(-rc[1]*rc[2], -rc[0]*rc[2], -rc[0]*rc[1]);
|
||||
sum = sum + field[1]*VecT( rc[1]*rc[2], -pc[0]*rc[2], -pc[0]*rc[1]);
|
||||
|
@ -194,9 +194,9 @@ void JacobianFor3DCell(const WorldCoordType &wCoords,
|
||||
vtkm::CellShapeTagHexahedron)
|
||||
{
|
||||
vtkm::Vec<JacobianType,3> pc(pcoords);
|
||||
vtkm::Vec<JacobianType,3> rc = vtkm::Vec<JacobianType,3>(1) - pc;
|
||||
vtkm::Vec<JacobianType,3> rc = vtkm::Vec<JacobianType,3>(JacobianType(1)) - pc;
|
||||
|
||||
jacobian = vtkm::Matrix<JacobianType,3,3>(0);
|
||||
jacobian = vtkm::Matrix<JacobianType,3,3>(JacobianType(0));
|
||||
VTKM_DERIVATIVE_WEIGHTS_HEXAHEDRON(pc, rc, VTKM_ACCUM_JACOBIAN_3D);
|
||||
}
|
||||
|
||||
@ -272,10 +272,10 @@ void JacobianFor2DCell(const WorldCoordType &wCoords,
|
||||
{
|
||||
vtkm::Vec<JacobianType,2> pc(static_cast<JacobianType>(pcoords[0]),
|
||||
static_cast<JacobianType>(pcoords[1]));
|
||||
vtkm::Vec<JacobianType,2> rc = vtkm::Vec<JacobianType,2>(1) - pc;
|
||||
vtkm::Vec<JacobianType,2> rc = vtkm::Vec<JacobianType,2>(JacobianType(1)) - pc;
|
||||
|
||||
vtkm::Vec<JacobianType,2> wcoords2d;
|
||||
jacobian = vtkm::Matrix<JacobianType,2,2>(0);
|
||||
jacobian = vtkm::Matrix<JacobianType,2,2>(JacobianType(0));
|
||||
VTKM_DERIVATIVE_WEIGHTS_QUAD(pc, rc, VTKM_ACCUM_JACOBIAN_2D);
|
||||
}
|
||||
|
||||
|
@ -35,14 +35,20 @@ namespace worklet {
|
||||
struct GradientOutTypes
|
||||
: vtkm::ListTagBase<
|
||||
vtkm::Vec<vtkm::Float32,3>,
|
||||
vtkm::Vec<vtkm::Float64,3>
|
||||
vtkm::Vec<vtkm::Float64,3>,
|
||||
vtkm::Vec< vtkm::Vec<vtkm::Float32,2>, 3>,
|
||||
vtkm::Vec< vtkm::Vec<vtkm::Float64,2>, 3>,
|
||||
vtkm::Vec< vtkm::Vec<vtkm::Float32,3>, 3>,
|
||||
vtkm::Vec< vtkm::Vec<vtkm::Float64,3>, 3>,
|
||||
vtkm::Vec< vtkm::Vec<vtkm::Float32,4>, 3>,
|
||||
vtkm::Vec< vtkm::Vec<vtkm::Float64,4>, 3>
|
||||
>
|
||||
{ };
|
||||
struct CellGradient : vtkm::worklet::WorkletMapPointToCell
|
||||
{
|
||||
typedef void ControlSignature(CellSetIn,
|
||||
FieldInPoint<Vec3> pointCoordinates,
|
||||
FieldInPoint<Scalar> inputField,
|
||||
FieldInPoint<FieldCommon> inputField,
|
||||
FieldOutCell<GradientOutTypes> outputField);
|
||||
|
||||
typedef void ExecutionSignature(CellShape, PointCount, _2, _3, _4);
|
||||
@ -64,8 +70,8 @@ struct CellGradient : vtkm::worklet::WorkletMapPointToCell
|
||||
using InValueType = typename FieldInVecType::ComponentType;
|
||||
using InDimensionTag = typename TypeTraits<InValueType>::DimensionalityTag;
|
||||
|
||||
//grad the dimension tag for the output component type
|
||||
using OutValueType = typename VecTraits<typename FieldOutType::ComponentType>::ComponentType;
|
||||
//grab the dimension tag for the output component type
|
||||
using OutValueType = typename FieldOutType::ComponentType;
|
||||
using OutDimensionTag = typename TypeTraits<OutValueType>::DimensionalityTag;
|
||||
|
||||
//Verify that input and output dimension tags match
|
||||
@ -99,6 +105,20 @@ struct CellGradient : vtkm::worklet::WorkletMapPointToCell
|
||||
std::false_type) const
|
||||
{
|
||||
//this is invalid
|
||||
// std::cout << "calling invalid compute" << std::endl;
|
||||
// using InValueType = typename FieldInVecType::ComponentType;
|
||||
// using InDimensionTag = typename TypeTraits<InValueType>::DimensionalityTag;
|
||||
|
||||
// using OutValueType = typename VecTraits<typename FieldOutType::ComponentType>::ComponentType;
|
||||
// using OutDimensionTag = typename TypeTraits<OutValueType>::DimensionalityTag;
|
||||
|
||||
// std::cout << typeid(InDimensionTag).name() << '\n';
|
||||
// std::cout << typeid(FieldInVecType).name() << '\n';
|
||||
|
||||
// std::cout << typeid(OutDimensionTag).name() << '\n';
|
||||
// std::cout << typeid(FieldOutType).name() << '\n';
|
||||
|
||||
// std::cout << std::endl;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -78,6 +78,62 @@ void TestCellGradientUniform3D()
|
||||
test_equal(result.GetPortalConstControl().Get(i), expected[i]),
|
||||
"Wrong result for CellGradient worklet on 3D uniform data");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void TestCellGradientUniform3DWithVectorField()
|
||||
{
|
||||
std::cout << "Testing CellGradient Worklet with a vector field on 3D strucutred data" << std::endl;
|
||||
vtkm::cont::testing::MakeTestDataSet testDataSet;
|
||||
vtkm::cont::DataSet dataSet = testDataSet.Make3DUniformDataSet0();
|
||||
|
||||
//Verify that we can compute the gradient of a 3 component vector
|
||||
const int nVerts = 18;
|
||||
vtkm::Float64 vars[nVerts] = {10.1, 20.1, 30.1, 40.1, 50.2,
|
||||
60.2, 70.2, 80.2, 90.3, 100.3,
|
||||
110.3, 120.3, 130.4, 140.4,
|
||||
150.4, 160.4, 170.5, 180.5};
|
||||
std::vector< vtkm::Vec<vtkm::Float64,3> > vec(18);
|
||||
for(std::size_t i=0; i < vec.size(); ++i)
|
||||
{
|
||||
vec[i] = vtkm::make_Vec(vars[i],vars[i],vars[i]);
|
||||
}
|
||||
vtkm::cont::ArrayHandle< vtkm::Vec<vtkm::Float64,3> > input =
|
||||
vtkm::cont::make_ArrayHandle(vec);
|
||||
|
||||
//we need to add Vec3 array to the dataset
|
||||
vtkm::cont::ArrayHandle< vtkm::Vec< vtkm::Vec<vtkm::Float64,3>, 3> > result;
|
||||
vtkm::worklet::DispatcherMapTopology<vtkm::worklet::CellGradient> dispatcher;
|
||||
dispatcher.Invoke(dataSet.GetCellSet(),
|
||||
dataSet.GetCoordinateSystem(),
|
||||
input,
|
||||
result);
|
||||
|
||||
vtkm::Vec< vtkm::Vec<vtkm::Float64,3>, 3> expected[4] = {
|
||||
{ {10.025,10.025,10.025}, {30.075,30.075,30.075}, {60.125,60.125,60.125} },
|
||||
{ {10.025,10.025,10.025}, {30.075,30.075,30.075}, {60.125,60.125,60.125} },
|
||||
{ {10.025,10.025,10.025}, {30.075,30.075,30.075}, {60.175,60.175,60.175} },
|
||||
{ {10.025,10.025,10.025}, {30.075,30.075,30.075}, {60.175,60.175,60.175} }
|
||||
};
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
vtkm::Vec< vtkm::Vec<vtkm::Float64,3>, 3> e = expected[i];
|
||||
vtkm::Vec< vtkm::Vec<vtkm::Float64,3>, 3> r = result.GetPortalConstControl().Get(i);
|
||||
|
||||
std::cout << "0: " << e[0] << " " << r[0] << std::endl;
|
||||
std::cout << "1: " << e[1] << " " << r[1] << std::endl;
|
||||
std::cout << "2: " << e[2] << " " << r[2] << std::endl;
|
||||
VTKM_TEST_ASSERT(
|
||||
test_equal(e[0],r[0]),
|
||||
"Wrong result for vec field CellGradient worklet on 3D uniform data");
|
||||
VTKM_TEST_ASSERT(
|
||||
test_equal(e[1],r[1]),
|
||||
"Wrong result for vec field CellGradient worklet on 3D uniform data");
|
||||
VTKM_TEST_ASSERT(
|
||||
test_equal(e[2],r[2]),
|
||||
"Wrong result for vec field CellGradient worklet on 3D uniform data");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void TestCellGradientExplicit()
|
||||
@ -109,6 +165,7 @@ void TestCellGradient()
|
||||
{
|
||||
TestCellGradientUniform2D();
|
||||
TestCellGradientUniform3D();
|
||||
TestCellGradientUniform3DWithVectorField();
|
||||
TestCellGradientExplicit();
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user