Skip to content

Commit 3b69799

Browse files
authored
[FEM] Add support of pyramids elements (#6132)
* [FEM] Add support of pyramids elements * add missing templates * fix symbols * fix example * fix comment
1 parent d303889 commit 3b69799

13 files changed

Lines changed: 287 additions & 2 deletions

File tree

Sofa/Component/Mass/src/sofa/component/mass/FEMMass.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ template class SOFA_COMPONENT_MASS_API FEMMass<sofa::defaulttype::Vec3Types, sof
3838
template class SOFA_COMPONENT_MASS_API FEMMass<sofa::defaulttype::Vec3Types, sofa::geometry::Tetrahedron>;
3939
template class SOFA_COMPONENT_MASS_API FEMMass<sofa::defaulttype::Vec3Types, sofa::geometry::Hexahedron>;
4040
template class SOFA_COMPONENT_MASS_API FEMMass<sofa::defaulttype::Vec3Types, sofa::geometry::Prism>;
41+
template class SOFA_COMPONENT_MASS_API FEMMass<sofa::defaulttype::Vec3Types, sofa::geometry::Pyramid>;
4142

4243
void registerFEMMass(sofa::core::ObjectFactory* factory)
4344
{
@@ -52,6 +53,7 @@ void registerFEMMass(sofa::core::ObjectFactory* factory)
5253
.add< FEMMass<sofa::defaulttype::Vec3Types, sofa::geometry::Tetrahedron> >()
5354
.add< FEMMass<sofa::defaulttype::Vec3Types, sofa::geometry::Hexahedron> >()
5455
.add< FEMMass<sofa::defaulttype::Vec3Types, sofa::geometry::Prism> >()
56+
.add< FEMMass<sofa::defaulttype::Vec3Types, sofa::geometry::Pyramid> >()
5557
);
5658
}
5759

Sofa/Component/Mass/src/sofa/component/mass/FEMMass.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,7 @@ template class SOFA_COMPONENT_MASS_API FEMMass<sofa::defaulttype::Vec3Types, sof
260260
template class SOFA_COMPONENT_MASS_API FEMMass<sofa::defaulttype::Vec3Types, sofa::geometry::Tetrahedron>;
261261
template class SOFA_COMPONENT_MASS_API FEMMass<sofa::defaulttype::Vec3Types, sofa::geometry::Hexahedron>;
262262
template class SOFA_COMPONENT_MASS_API FEMMass<sofa::defaulttype::Vec3Types, sofa::geometry::Prism>;
263+
template class SOFA_COMPONENT_MASS_API FEMMass<sofa::defaulttype::Vec3Types, sofa::geometry::Pyramid>;
263264
#endif
264265

265266
} // namespace sofa::component::mass

Sofa/Component/SolidMechanics/FEM/Elastic/src/sofa/component/solidmechanics/fem/elastic/CorotationalFEMForceField.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ void registerCorotationalFEMForceField(sofa::core::ObjectFactory* factory)
4242
.add< CorotationalFEMForceField<sofa::defaulttype::Vec3Types, sofa::geometry::Tetrahedron> >()
4343
.add< CorotationalFEMForceField<sofa::defaulttype::Vec3Types, sofa::geometry::Hexahedron> >()
4444
.add< CorotationalFEMForceField<sofa::defaulttype::Vec3Types, sofa::geometry::Prism> >()
45+
.add< CorotationalFEMForceField<sofa::defaulttype::Vec3Types, sofa::geometry::Pyramid> >()
4546
);
4647
}
4748

@@ -55,5 +56,6 @@ template class SOFA_COMPONENT_SOLIDMECHANICS_FEM_ELASTIC_API CorotationalFEMForc
5556
template class SOFA_COMPONENT_SOLIDMECHANICS_FEM_ELASTIC_API CorotationalFEMForceField<sofa::defaulttype::Vec3Types, sofa::geometry::Tetrahedron>;
5657
template class SOFA_COMPONENT_SOLIDMECHANICS_FEM_ELASTIC_API CorotationalFEMForceField<sofa::defaulttype::Vec3Types, sofa::geometry::Hexahedron>;
5758
template class SOFA_COMPONENT_SOLIDMECHANICS_FEM_ELASTIC_API CorotationalFEMForceField<sofa::defaulttype::Vec3Types, sofa::geometry::Prism>;
59+
template class SOFA_COMPONENT_SOLIDMECHANICS_FEM_ELASTIC_API CorotationalFEMForceField<sofa::defaulttype::Vec3Types, sofa::geometry::Pyramid>;
5860

5961
}

Sofa/Component/SolidMechanics/FEM/Elastic/src/sofa/component/solidmechanics/fem/elastic/CorotationalFEMForceField.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ extern template class SOFA_COMPONENT_SOLIDMECHANICS_FEM_ELASTIC_API Corotational
185185
extern template class SOFA_COMPONENT_SOLIDMECHANICS_FEM_ELASTIC_API CorotationalFEMForceField<sofa::defaulttype::Vec3Types, sofa::geometry::Tetrahedron>;
186186
extern template class SOFA_COMPONENT_SOLIDMECHANICS_FEM_ELASTIC_API CorotationalFEMForceField<sofa::defaulttype::Vec3Types, sofa::geometry::Hexahedron>;
187187
extern template class SOFA_COMPONENT_SOLIDMECHANICS_FEM_ELASTIC_API CorotationalFEMForceField<sofa::defaulttype::Vec3Types, sofa::geometry::Prism>;
188+
extern template class SOFA_COMPONENT_SOLIDMECHANICS_FEM_ELASTIC_API CorotationalFEMForceField<sofa::defaulttype::Vec3Types, sofa::geometry::Pyramid>;
188189
#endif
189190

190191
} // namespace sofa::component::solidmechanics::fem::elastic

Sofa/Component/SolidMechanics/FEM/Elastic/src/sofa/component/solidmechanics/fem/elastic/LinearSmallStrainFEMForceField.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ void registerLinearSmallStrainFEMForceField(sofa::core::ObjectFactory* factory)
4242
.add< LinearSmallStrainFEMForceField<sofa::defaulttype::Vec3Types, sofa::geometry::Tetrahedron> >()
4343
.add< LinearSmallStrainFEMForceField<sofa::defaulttype::Vec3Types, sofa::geometry::Hexahedron> >()
4444
.add< LinearSmallStrainFEMForceField<sofa::defaulttype::Vec3Types, sofa::geometry::Prism> >()
45+
.add< LinearSmallStrainFEMForceField<sofa::defaulttype::Vec3Types, sofa::geometry::Pyramid> >()
4546
);
4647
}
4748

@@ -55,5 +56,6 @@ template class SOFA_COMPONENT_SOLIDMECHANICS_FEM_ELASTIC_API LinearSmallStrainFE
5556
template class SOFA_COMPONENT_SOLIDMECHANICS_FEM_ELASTIC_API LinearSmallStrainFEMForceField<sofa::defaulttype::Vec3Types, sofa::geometry::Tetrahedron>;
5657
template class SOFA_COMPONENT_SOLIDMECHANICS_FEM_ELASTIC_API LinearSmallStrainFEMForceField<sofa::defaulttype::Vec3Types, sofa::geometry::Hexahedron>;
5758
template class SOFA_COMPONENT_SOLIDMECHANICS_FEM_ELASTIC_API LinearSmallStrainFEMForceField<sofa::defaulttype::Vec3Types, sofa::geometry::Prism>;
59+
template class SOFA_COMPONENT_SOLIDMECHANICS_FEM_ELASTIC_API LinearSmallStrainFEMForceField<sofa::defaulttype::Vec3Types, sofa::geometry::Pyramid>;
5860

5961
}

Sofa/Component/SolidMechanics/FEM/Elastic/src/sofa/component/solidmechanics/fem/elastic/LinearSmallStrainFEMForceField.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ extern template class SOFA_COMPONENT_SOLIDMECHANICS_FEM_ELASTIC_API LinearSmallS
9292
extern template class SOFA_COMPONENT_SOLIDMECHANICS_FEM_ELASTIC_API LinearSmallStrainFEMForceField<sofa::defaulttype::Vec3Types, sofa::geometry::Tetrahedron>;
9393
extern template class SOFA_COMPONENT_SOLIDMECHANICS_FEM_ELASTIC_API LinearSmallStrainFEMForceField<sofa::defaulttype::Vec3Types, sofa::geometry::Hexahedron>;
9494
extern template class SOFA_COMPONENT_SOLIDMECHANICS_FEM_ELASTIC_API LinearSmallStrainFEMForceField<sofa::defaulttype::Vec3Types, sofa::geometry::Prism>;
95+
extern template class SOFA_COMPONENT_SOLIDMECHANICS_FEM_ELASTIC_API LinearSmallStrainFEMForceField<sofa::defaulttype::Vec3Types, sofa::geometry::Pyramid>;
9596
#endif
9697

9798
} // namespace sofa::component::solidmechanics::fem::elastic

Sofa/framework/Core/src/sofa/core/visual/DrawMesh.h

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,85 @@ struct SOFA_CORE_API DrawElementMesh<sofa::geometry::Prism>
453453
}
454454
};
455455

456+
template<>
457+
struct SOFA_CORE_API DrawElementMesh<sofa::geometry::Pyramid>
458+
: public BaseDrawMesh<DrawElementMesh<sofa::geometry::Pyramid>, 5>
459+
{
460+
using ElementType = sofa::geometry::Pyramid;
461+
friend BaseDrawMesh;
462+
463+
static constexpr ColorContainer defaultColors {
464+
sofa::type::RGBAColor::green(),
465+
sofa::type::RGBAColor::teal(),
466+
sofa::type::RGBAColor::navy(),
467+
sofa::type::RGBAColor::gold(),
468+
sofa::type::RGBAColor::purple()
469+
};
470+
471+
private:
472+
template<class PositionContainer, class IndicesContainer>
473+
void doDraw(
474+
sofa::helper::visual::DrawTool* drawTool,
475+
const PositionContainer& position,
476+
sofa::core::topology::BaseMeshTopology* topology,
477+
const IndicesContainer& elementIndices,
478+
const ColorContainer& colors)
479+
{
480+
if (!topology)
481+
return;
482+
483+
const auto& elements = topology->getPyramids();
484+
485+
// Allocate space for rendering points
486+
// 1 Quad + 4 Triangles
487+
renderedPoints[0].resize(elementIndices.size() * sofa::geometry::Quad::NumberOfNodes);
488+
renderedPoints[1].resize(elementIndices.size() * sofa::geometry::Triangle::NumberOfNodes);
489+
renderedPoints[2].resize(elementIndices.size() * sofa::geometry::Triangle::NumberOfNodes);
490+
renderedPoints[3].resize(elementIndices.size() * sofa::geometry::Triangle::NumberOfNodes);
491+
renderedPoints[4].resize(elementIndices.size() * sofa::geometry::Triangle::NumberOfNodes);
492+
493+
std::array<std::size_t, NumberColors> renderedPointId {};
494+
495+
for (auto i : elementIndices)
496+
{
497+
const auto& pyramid = elements[i];
498+
const auto center = this->elementCenter(position, pyramid);
499+
500+
const auto drawQuad = [&](sofa::Index bufferId, sofa::Index v0, sofa::Index v1, sofa::Index v2, sofa::Index v3)
501+
{
502+
const std::array vertexIndices { pyramid[v0], pyramid[v1], pyramid[v2], pyramid[v3] };
503+
for (std::size_t k = 0; k < sofa::geometry::Quad::NumberOfNodes; ++k)
504+
{
505+
const auto p = this->applyElementSpace(position[vertexIndices[k]], center);
506+
renderedPoints[bufferId][renderedPointId[bufferId]++] = sofa::type::toVec3(p);
507+
}
508+
};
509+
510+
const auto drawTriangle = [&](sofa::Index bufferId, sofa::Index v0, sofa::Index v1, sofa::Index v2)
511+
{
512+
const std::array vertexIndices { pyramid[v0], pyramid[v1], pyramid[v2] };
513+
for (std::size_t k = 0; k < sofa::geometry::Triangle::NumberOfNodes; ++k)
514+
{
515+
const auto p = this->applyElementSpace(position[vertexIndices[k]], center);
516+
renderedPoints[bufferId][renderedPointId[bufferId]++] = sofa::type::toVec3(p);
517+
}
518+
};
519+
520+
drawQuad(0, 0, 3, 2, 1);
521+
drawTriangle(1, 0, 1, 4);
522+
drawTriangle(2, 1, 2, 4);
523+
drawTriangle(3, 3, 4, 2);
524+
drawTriangle(4, 0, 4, 3);
525+
}
526+
527+
drawTool->drawQuads(renderedPoints[0], colors[0]);
528+
drawTool->drawTriangles(renderedPoints[1], colors[1]);
529+
drawTool->drawTriangles(renderedPoints[2], colors[2]);
530+
drawTool->drawTriangles(renderedPoints[3], colors[3]);
531+
drawTool->drawTriangles(renderedPoints[4], colors[4]);
532+
}
533+
};
534+
456535
template<>
457536
struct SOFA_CORE_API DrawElementMesh<sofa::geometry::Hexahedron>
458537
: public BaseDrawMesh<DrawElementMesh<sofa::geometry::Hexahedron>, 6>
@@ -564,6 +643,7 @@ class SOFA_CORE_API DrawMesh
564643
drawElements<sofa::geometry::Tetrahedron>(drawTool, position, topology);
565644
drawElements<sofa::geometry::Hexahedron>(drawTool, position, topology);
566645
drawElements<sofa::geometry::Prism>(drawTool, position, topology);
646+
drawElements<sofa::geometry::Pyramid>(drawTool, position, topology);
567647
}
568648

569649
template<class PositionContainer>
@@ -582,8 +662,9 @@ class SOFA_CORE_API DrawMesh
582662
const auto hasTetra = !topology->getTetrahedra().empty();
583663
const auto hasHexa = !topology->getHexahedra().empty();
584664
const auto hasPrism = !topology->getPrisms().empty();
665+
const auto hasPyramid = !topology->getPyramids().empty();
585666

586-
const bool hasVolumeElements = hasTetra || hasHexa || hasPrism;
667+
const bool hasVolumeElements = hasTetra || hasHexa || hasPrism || hasPyramid;
587668

588669
if (!hasSurfaceElements && !hasVolumeElements)
589670
{
@@ -609,7 +690,8 @@ class SOFA_CORE_API DrawMesh
609690
DrawElementMesh<sofa::geometry::Quad>,
610691
DrawElementMesh<sofa::geometry::Tetrahedron>,
611692
DrawElementMesh<sofa::geometry::Hexahedron>,
612-
DrawElementMesh<sofa::geometry::Prism>
693+
DrawElementMesh<sofa::geometry::Prism>,
694+
DrawElementMesh<sofa::geometry::Pyramid>
613695
> m_meshes;
614696
};
615697

Sofa/framework/FEM/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ set(HEADER_FILES
1212
${SOFAFEMSRC_ROOT}/FiniteElement[Edge].h
1313
${SOFAFEMSRC_ROOT}/FiniteElement[Hexahedron].h
1414
${SOFAFEMSRC_ROOT}/FiniteElement[Prism].h
15+
${SOFAFEMSRC_ROOT}/FiniteElement[Pyramid].h
1516
${SOFAFEMSRC_ROOT}/FiniteElement[Quad].h
1617
${SOFAFEMSRC_ROOT}/FiniteElement[Tetrahedron].h
1718
${SOFAFEMSRC_ROOT}/FiniteElement[Triangle].h
@@ -23,6 +24,7 @@ set(SOURCE_FILES
2324
${SOFAFEMSRC_ROOT}/FiniteElement[Edge].cpp
2425
${SOFAFEMSRC_ROOT}/FiniteElement[Hexahedron].cpp
2526
${SOFAFEMSRC_ROOT}/FiniteElement[Prism].cpp
27+
${SOFAFEMSRC_ROOT}/FiniteElement[Pyramid].cpp
2628
${SOFAFEMSRC_ROOT}/FiniteElement[Quad].cpp
2729
${SOFAFEMSRC_ROOT}/FiniteElement[Tetrahedron].cpp
2830
${SOFAFEMSRC_ROOT}/FiniteElement[Triangle].cpp
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/******************************************************************************
2+
* SOFA, Simulation Open-Framework Architecture *
3+
* (c) 2006 INRIA, USTL, UJF, CNRS, MGH *
4+
* *
5+
* This program is free software; you can redistribute it and/or modify it *
6+
* under the terms of the GNU Lesser General Public License as published by *
7+
* the Free Software Foundation; either version 2.1 of the License, or (at *
8+
* your option) any later version. *
9+
* *
10+
* This program is distributed in the hope that it will be useful, but WITHOUT *
11+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
12+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License *
13+
* for more details. *
14+
* *
15+
* You should have received a copy of the GNU Lesser General Public License *
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
17+
*******************************************************************************
18+
* Authors: The SOFA Team and external contributors (see Authors.txt) *
19+
* *
20+
* Contact information: contact@sofa-framework.org *
21+
******************************************************************************/
22+
#define SOFA_FEM_FINITE_ELEMENT_PYRAMID_CPP
23+
#include <sofa/fem/FiniteElement[Pyramid].h>
24+
#include <sofa/defaulttype/VecTypes.h>
25+
26+
namespace sofa::fem
27+
{
28+
29+
template struct SOFA_FEM_API FiniteElement<sofa::geometry::Pyramid, sofa::defaulttype::Vec3Types>;
30+
31+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/******************************************************************************
2+
* SOFA, Simulation Open-Framework Architecture *
3+
* (c) 2006 INRIA, USTL, UJF, CNRS, MGH *
4+
* *
5+
* This program is free software; you can redistribute it and/or modify it *
6+
* under the terms of the GNU Lesser General Public License as published by *
7+
* the Free Software Foundation; either version 2.1 of the License, or (at *
8+
* your option) any later version. *
9+
* *
10+
* This program is distributed in the hope that it will be useful, but WITHOUT *
11+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
12+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License *
13+
* for more details. *
14+
* *
15+
* You should have received a copy of the GNU Lesser General Public License *
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
17+
*******************************************************************************
18+
* Authors: The SOFA Team and external contributors (see Authors.txt) *
19+
* *
20+
* Contact information: contact@sofa-framework.org *
21+
******************************************************************************/
22+
#pragma once
23+
#include <sofa/fem/FiniteElement.h>
24+
#include <sofa/geometry/Pyramid.h>
25+
26+
#if !defined(SOFA_FEM_FINITE_ELEMENT_PYRAMID_CPP)
27+
#include <sofa/defaulttype/VecTypes.h>
28+
#endif
29+
30+
namespace sofa::fem
31+
{
32+
33+
template <class DataTypes>
34+
struct FiniteElement<sofa::geometry::Pyramid, DataTypes>
35+
{
36+
FINITEELEMENT_HEADER(sofa::geometry::Pyramid, DataTypes, 3);
37+
static_assert(spatial_dimensions == 3, "Pyramids are only defined in 3D");
38+
39+
constexpr static std::array<ReferenceCoord, NumberOfNodesInElement> referenceElementNodes {{
40+
{-1, -1, -1},
41+
{ 1, -1, -1},
42+
{ 1, 1, -1},
43+
{-1, 1, -1},
44+
{ 0, 0, 1},
45+
}};
46+
47+
static const sofa::type::vector<TopologyElement>& getElementSequence(sofa::core::topology::BaseMeshTopology& topology)
48+
{
49+
return topology.getPyramids();
50+
}
51+
52+
static constexpr sofa::type::Vec<NumberOfNodesInElement, Real> shapeFunctions(const sofa::type::Vec<TopologicalDimension, Real>& q)
53+
{
54+
return {
55+
static_cast<Real>(0.125) * (1 - q[0]) * (1 - q[1]) * (1 - q[2]),
56+
static_cast<Real>(0.125) * (1 + q[0]) * (1 - q[1]) * (1 - q[2]),
57+
static_cast<Real>(0.125) * (1 + q[0]) * (1 + q[1]) * (1 - q[2]),
58+
static_cast<Real>(0.125) * (1 - q[0]) * (1 + q[1]) * (1 - q[2]),
59+
static_cast<Real>(0.5) * (1 + q[2])
60+
};
61+
}
62+
63+
static constexpr sofa::type::Mat<NumberOfNodesInElement, TopologicalDimension, Real> gradientShapeFunctions(const sofa::type::Vec<TopologicalDimension, Real>& q)
64+
{
65+
return {
66+
{-static_cast<Real>(0.125) * (1 - q[1]) * (1 - q[2]), -static_cast<Real>(0.125) * (1 - q[0]) * (1 - q[2]), -static_cast<Real>(0.125) * (1 - q[0]) * (1 - q[1])},
67+
{ static_cast<Real>(0.125) * (1 - q[1]) * (1 - q[2]), -static_cast<Real>(0.125) * (1 + q[0]) * (1 - q[2]), -static_cast<Real>(0.125) * (1 + q[0]) * (1 - q[1])},
68+
{ static_cast<Real>(0.125) * (1 + q[1]) * (1 - q[2]), static_cast<Real>(0.125) * (1 + q[0]) * (1 - q[2]), -static_cast<Real>(0.125) * (1 + q[0]) * (1 + q[1])},
69+
{-static_cast<Real>(0.125) * (1 + q[1]) * (1 - q[2]), static_cast<Real>(0.125) * (1 - q[0]) * (1 - q[2]), -static_cast<Real>(0.125) * (1 - q[0]) * (1 + q[1])},
70+
{ 0, 0, static_cast<Real>(0.5)}
71+
};
72+
}
73+
74+
static constexpr auto quadraturePoints()
75+
{
76+
constexpr Real sqrt3_1 = static_cast<Real>(1) / static_cast<Real>(1.73205080757);
77+
constexpr Real one = static_cast<Real>(1);
78+
79+
// We use the 8 Gauss points of the hexahedron, which exactly integrate the (1-z)^2 Jacobian of the pyramid.
80+
return std::array {
81+
std::pair{ReferenceCoord{-sqrt3_1, -sqrt3_1, -sqrt3_1}, one},
82+
std::pair{ReferenceCoord{ sqrt3_1, -sqrt3_1, -sqrt3_1}, one},
83+
std::pair{ReferenceCoord{ sqrt3_1, sqrt3_1, -sqrt3_1}, one},
84+
std::pair{ReferenceCoord{-sqrt3_1, sqrt3_1, -sqrt3_1}, one},
85+
std::pair{ReferenceCoord{-sqrt3_1, -sqrt3_1, sqrt3_1}, one},
86+
std::pair{ReferenceCoord{ sqrt3_1, -sqrt3_1, sqrt3_1}, one},
87+
std::pair{ReferenceCoord{ sqrt3_1, sqrt3_1, sqrt3_1}, one},
88+
std::pair{ReferenceCoord{-sqrt3_1, sqrt3_1, sqrt3_1}, one},
89+
};
90+
}
91+
};
92+
93+
#if !defined(SOFA_FEM_FINITE_ELEMENT_PYRAMID_CPP)
94+
extern template struct SOFA_FEM_API FiniteElement<sofa::geometry::Pyramid, sofa::defaulttype::Vec3Types>;
95+
#endif
96+
97+
}

0 commit comments

Comments
 (0)