]> SALOME platform Git repositories - tools/medcoupling.git/commitdiff
Salome HOME
Add ShapeRecogn sources
authorEl Hadi Moussi <moussi@phimeca.com>
Mon, 5 Aug 2024 09:34:42 +0000 (11:34 +0200)
committerEl Hadi Moussi <moussi@phimeca.com>
Mon, 5 Aug 2024 09:34:42 +0000 (11:34 +0200)
15 files changed:
src/ShapeRecogn/Areas.cxx [new file with mode: 0644]
src/ShapeRecogn/Areas.hxx [new file with mode: 0644]
src/ShapeRecogn/AreasBuilder.cxx [new file with mode: 0644]
src/ShapeRecogn/AreasBuilder.hxx [new file with mode: 0644]
src/ShapeRecogn/CMakeLists.txt
src/ShapeRecogn/MathOps.cxx [new file with mode: 0644]
src/ShapeRecogn/MathOps.hxx [new file with mode: 0644]
src/ShapeRecogn/NodeCurvatureCalculator.cxx [new file with mode: 0644]
src/ShapeRecogn/NodeCurvatureCalculator.hxx [new file with mode: 0644]
src/ShapeRecogn/Nodes.cxx [new file with mode: 0644]
src/ShapeRecogn/Nodes.hxx [new file with mode: 0644]
src/ShapeRecogn/PrimitiveType.hxx [new file with mode: 0644]
src/ShapeRecogn/ShapeRecognMesh.cxx [new file with mode: 0644]
src/ShapeRecogn/ShapeRecognMesh.hxx [new file with mode: 0644]
src/ShapeRecogn/ShapeRecongConstants.hxx [new file with mode: 0644]

diff --git a/src/ShapeRecogn/Areas.cxx b/src/ShapeRecogn/Areas.cxx
new file mode 100644 (file)
index 0000000..5c2559f
--- /dev/null
@@ -0,0 +1,455 @@
+#include "Areas.hxx"
+#include "MathOps.hxx"
+
+#include <random>
+
+using namespace MEDCoupling;
+
+Areas::Areas(const Nodes *nodes) : nodes(nodes), areaIdByNodes(nodes->getNbNodes(), -1)
+{
+}
+
+mcIdType Areas::addArea(PrimitiveType primitive)
+{
+    Area area;
+    area.primitive = primitive;
+    areas.push_back(area);
+    return areas.size() - 1;
+}
+
+void Areas::cleanArea(mcIdType areaId)
+{
+    cleanArea(areaId, -1);
+}
+
+void Areas::cancelArea(mcIdType areaId, PrimitiveType primitive)
+{
+    cleanArea(areaId, -2);
+    Area &area = areas[areaId];
+    area.primitive = primitive;
+}
+
+void Areas::removeArea(mcIdType areaId)
+{
+    for (size_t nodeId = 0; nodeId < areaIdByNodes.size(); ++nodeId)
+    {
+        if (areaIdByNodes[nodeId] == areaId)
+            areaIdByNodes[nodeId] = -1;
+        else if (areaIdByNodes[nodeId] > areaId)
+            areaIdByNodes[nodeId] -= 1;
+    }
+    areas.erase(areas.begin() + areaId);
+}
+
+void Areas::addNode(mcIdType areaId, mcIdType nodeId)
+{
+    removeNode(nodeId);
+    areaIdByNodes[nodeId] = areaId;
+    Area &area = areas[areaId];
+    area.nodeIds.push_back(nodeId);
+    size_t nbNodes = area.nodeIds.size();
+    area.k1 = ((nbNodes - 1) * area.k1 + nodes->getK1(nodeId)) / nbNodes;
+    area.k2 = ((nbNodes - 1) * area.k2 + nodes->getK2(nodeId)) / nbNodes;
+    area.kdiff0 = ((nbNodes - 1) * area.kdiff0 + nodes->getKdiff0(nodeId)) / nbNodes;
+}
+
+void Areas::cleanInvalidNodeAreas()
+{
+    for (mcIdType nodeId = 0; nodeId < (mcIdType)areaIdByNodes.size(); ++nodeId)
+    {
+        if (areaIdByNodes[nodeId] <= -2)
+            areaIdByNodes[nodeId] = -1;
+    }
+}
+
+mcIdType Areas::getAreaId(mcIdType nodeId) const
+{
+    return areaIdByNodes[nodeId];
+}
+
+bool Areas::isEmpty(mcIdType areaId) const
+{
+    return areas[areaId].nodeIds.empty();
+}
+
+size_t Areas::getNumberOfAreas() const
+{
+    return areas.size();
+}
+
+size_t Areas::getNumberOfNodes(mcIdType areaId) const
+{
+    return areas[areaId].nodeIds.size();
+}
+
+bool Areas::isNodeCompatible(mcIdType areaId, mcIdType nodeId) const
+{
+    PrimitiveType areaType = getPrimitiveType(areaId);
+    PrimitiveType nodeType = nodes->getPrimitiveType(nodeId);
+    return (
+        (areaType != PrimitiveType::Cylinder ||
+         nodeType != PrimitiveType::Plane) &&
+        (areaType != PrimitiveType::Sphere ||
+         nodeType != PrimitiveType::Plane));
+}
+
+PrimitiveType Areas::getPrimitiveType(mcIdType areaId) const
+{
+    const Area &area = areas[areaId];
+    if (area.primitive != PrimitiveType::Unknown)
+        return area.primitive;
+    else if (area.nodeIds.empty())
+        return PrimitiveType::Unknown;
+    else
+        return nodes->getPrimitiveType(area.nodeIds[0]);
+}
+
+std::string Areas::getPrimitiveTypeName(mcIdType areaId) const
+{
+    return convertPrimitiveToString(getPrimitiveType(areaId));
+}
+
+int Areas::getPrimitiveTypeInt(mcIdType areaId) const
+{
+    return convertPrimitiveToInt(getPrimitiveType(areaId));
+}
+
+const std::vector<mcIdType> &Areas::getNodeIds(mcIdType areaId) const
+{
+    return areas[areaId].nodeIds;
+}
+
+double Areas::getK1(mcIdType areaId) const
+{
+
+    return areas[areaId].k1;
+}
+
+double Areas::getK2(mcIdType areaId) const
+{
+    return areas[areaId].k2;
+}
+
+double Areas::getKdiff0(mcIdType areaId) const
+{
+    return areas[areaId].kdiff0;
+}
+
+void Areas::computeProperties(mcIdType areaId)
+{
+    switch (getPrimitiveType(areaId))
+    {
+    case PrimitiveType::Plane:
+        computePlaneProperties(areaId);
+        break;
+    case PrimitiveType::Sphere:
+        computeSphereProperties(areaId);
+        break;
+    case PrimitiveType::Cylinder:
+        computeCylinderProperties(areaId);
+        break;
+    case PrimitiveType::Cone:
+        computeConeProperties(areaId);
+        break;
+    case PrimitiveType::Torus:
+        computeTorusProperties(areaId);
+        break;
+    case PrimitiveType::Unknown:
+    default:
+        break;
+    }
+}
+
+double Areas::getRadius(mcIdType areaId) const
+{
+    const Area &area = areas[areaId];
+    return area.radius;
+}
+
+double Areas::getAngle(mcIdType areaId) const
+{
+    return areas[areaId].angle;
+}
+
+const std::array<double, 3> &Areas::getNormal(mcIdType areaId) const
+{
+    return areas[areaId].normal;
+}
+
+const std::array<double, 3> &Areas::getCenter(mcIdType areaId) const
+{
+    const Area &area = areas[areaId];
+    return area.center;
+}
+
+const std::array<double, 3> &Areas::getAxis(mcIdType areaId) const
+{
+    return areas[areaId].axis;
+}
+
+const std::array<double, 3> &Areas::getAxisPoint(mcIdType areaId) const
+{
+    return areas[areaId].axisPoint;
+}
+
+const std::array<double, 3> &Areas::getApex(mcIdType areaId) const
+{
+    return areas[areaId].apex;
+}
+
+std::array<double, 3> Areas::getAffinePoint(mcIdType areaId) const
+{
+    const Area &area = areas[areaId];
+    if (area.nodeIds.empty())
+        return {0.0, 0.0, 0.0};
+    else
+        return nodes->getCoordinates(area.nodeIds[0]);
+}
+
+void Areas::cleanArea(mcIdType areaId, mcIdType newAreaId = -1)
+{
+    Area &area = areas[areaId];
+    for (mcIdType nodeId : area.nodeIds)
+        areaIdByNodes[nodeId] = newAreaId;
+    area.primitive = PrimitiveType::Unknown;
+    area.k1 = 0.0;
+    area.k2 = 0.0;
+    area.kdiff0 = 0.0;
+    area.radius = 0.0;
+    area.angle = 0.0;
+    area.normal = {0.0, 0.0, 0.0};
+    area.center = {0.0, 0.0, 0.0};
+    area.axis = {0.0, 0.0, 0.0};
+    area.axisPoint = {0.0, 0.0, 0.0};
+    area.apex = {0.0, 0.0, 0.0};
+    area.nodeIds.clear();
+}
+
+void Areas::removeNode(mcIdType nodeId)
+{
+    mcIdType areaId = areaIdByNodes[nodeId];
+    if (areaId > -1)
+    {
+        Area &area = areas[areaId];
+        area.nodeIds.erase(
+            std::remove(
+                area.nodeIds.begin(),
+                area.nodeIds.end(),
+                nodeId),
+            area.nodeIds.end());
+        areaIdByNodes[nodeId] = -1;
+        // TODO: Update the parameters of the area ?
+    }
+}
+
+void Areas::computePlaneProperties(mcIdType areaId)
+{
+    Area &area = areas[areaId];
+    const std::vector<double> &normals = nodes->getNormals();
+    mcIdType nbNodes = area.nodeIds.size();
+    area.normal = {0.0, 0.0, 0.0};
+    for (mcIdType nodeId : area.nodeIds)
+    {
+        for (size_t i = 0; i < 3; ++i)
+            area.normal[i] += normals[3 * nodeId + i];
+    }
+    for (size_t i = 0; i < 3; ++i)
+        area.normal[i] /= nbNodes;
+}
+
+void Areas::computeSphereProperties(mcIdType areaId)
+{
+    Area &area = areas[areaId];
+    const std::vector<double> &normals = nodes->getNormals();
+    area.radius = (2 / (area.k1 + area.k2));
+    std::array<double, 3> center{0.0, 0.0, 0.0};
+    if (!area.nodeIds.empty())
+    {
+        size_t nbNodes = area.nodeIds.size();
+        for (mcIdType nodeId : area.nodeIds)
+        {
+            std::array<double, 3> nodeCoords = nodes->getCoordinates(nodeId);
+            for (size_t i = 0; i < 3; ++i)
+                center[i] += nodeCoords[i] - area.radius * normals[3 * nodeId + i];
+        }
+        for (size_t i = 0; i < 3; ++i)
+            center[i] /= nbNodes;
+    }
+    area.center = center;
+    area.radius = fabs(area.radius);
+}
+
+void Areas::computeCylinderProperties(mcIdType areaId)
+{
+    Area &area = areas[areaId];
+    size_t nbNodes = area.nodeIds.size();
+    // Project the nodes to the central axis of the cylinder
+    std::vector<double> projectedNodes(3 * nbNodes, 0.0);
+    area.radius = 0;
+    area.axisPoint.fill(0.0);
+    for (size_t i = 0; i < nbNodes; ++i)
+    {
+        mcIdType nodeId = area.nodeIds[i];
+        std::array<double, 3> nodeCoords = nodes->getCoordinates(nodeId);
+        double approxRadius = 1.0 / nodes->getKdiff0(nodeId);
+        for (size_t j = 0; j < 3; ++j)
+        {
+            projectedNodes[3 * i + j] =
+                nodeCoords[j] - approxRadius * nodes->getNormals()[3 * nodeId + j];
+            area.axisPoint[j] += projectedNodes[3 * i + j];
+        }
+        area.radius += approxRadius;
+    }
+    // Axis point is the mean of the projected nodes
+    for (size_t i = 0; i < 3; ++i)
+        area.axisPoint[i] /= nbNodes;
+    // Radius of the cylinder is the mean of the approximate radius of each node
+    area.radius = fabs(area.radius / nbNodes);
+    // Compute the axis of the cylinder
+    area.axis = MathOps::computePCAFirstAxis(projectedNodes);
+}
+
+void Areas::computeConeProperties(mcIdType areaId)
+{
+    Area &area = areas[areaId];
+    size_t nbNodes = area.nodeIds.size();
+    // Project the nodes to the central axis of the cone
+    std::vector<double> projectedNodes(3 * nbNodes, 0.0);
+    std::vector<double> radiusNodes(nbNodes, 0.0);
+    area.axisPoint.fill(0.0);
+    for (size_t i = 0; i < nbNodes; ++i)
+    {
+        mcIdType nodeId = area.nodeIds[i];
+        std::array<double, 3> nodeCoords = nodes->getCoordinates(nodeId);
+        radiusNodes[i] = 1.0 / nodes->getKdiff0(nodeId);
+        for (size_t j = 0; j < 3; ++j)
+        {
+            projectedNodes[3 * i + j] =
+                nodeCoords[j] - radiusNodes[i] * nodes->getNormals()[3 * nodeId + j];
+            area.axisPoint[j] += projectedNodes[3 * i + j];
+        }
+    }
+    // Axis point is the mean of the projected nodes
+    for (size_t i = 0; i < 3; ++i)
+        area.axisPoint[i] /= nbNodes;
+    // Compute the axis of the cone
+    area.axis = MathOps::computePCAFirstAxis(projectedNodes);
+    double normAxis = MathOps::computeNorm(area.axis);
+    for (size_t i = 0; i < 3; ++i)
+        area.axis[i] /= normAxis;
+    // Compute the angle of the cone
+    const std::vector<double> &weakDirections = nodes->getWeakDirections();
+    std::vector<double> weakDirectionNodes(3 * nbNodes, 0.0);
+    for (size_t i = 0; i < nbNodes; ++i)
+    {
+        mcIdType nodeId = area.nodeIds[i];
+        for (size_t j = 0; j < 3; ++j)
+            weakDirectionNodes[3 * i + j] = weakDirections[3 * nodeId + j];
+    }
+    std::vector<double> angles = MathOps::computeAngles(
+        weakDirectionNodes, area.axis);
+    // Correct the angles > pi/2 with the supplementary angle
+    for (size_t i = 0; i < angles.size(); ++i)
+    {
+        if (angles[i] > M_PI_2)
+            angles[i] = M_PI - angles[i];
+    }
+    area.angle = MathOps::mean(angles);
+    // Compute the radius of the cone which is the mean of the approximate radius of each node
+    area.radius = MathOps::mean(radiusNodes);
+    // Select extrem nodes
+    double q1 = MathOps::computeQuantile(radiusNodes, 0.1);
+    double q2 = MathOps::computeQuantile(radiusNodes, 0.9);
+    std::vector<mcIdType> q1_indices;
+    for (mcIdType idx = 0; idx < (mcIdType)radiusNodes.size(); ++idx)
+    {
+        if (radiusNodes[idx] < q1)
+            q1_indices.push_back(idx);
+    }
+    std::vector<mcIdType> q2_indices;
+    for (mcIdType idx = 0; idx < (mcIdType)radiusNodes.size(); ++idx)
+    {
+        if (radiusNodes[idx] > q2)
+            q2_indices.push_back(idx);
+    }
+    std::random_device rd;
+    std::mt19937 g(rd());
+    std::shuffle(q2_indices.begin(), q2_indices.end(), g);
+    // Compute the height of the cone
+    // std::vector<double> heights(q1_indices.size(), 0.0);
+    // std::vector<double> distancesToApex(q1_indices.size(), 0.0);
+    std::array<double, 3> p{0.0, 0.0, 0.0};
+    for (size_t i = 0; i < q1_indices.size(); ++i)
+    {
+        for (size_t j = 0; j < 3; ++j)
+            p[j] = (projectedNodes[3 * q1_indices[i] + j] - projectedNodes[3 * q2_indices[i] + j]);
+        double height = MathOps::computeNorm(p);
+        double distanceToApex = height * radiusNodes[q1_indices[i]] / (radiusNodes[q2_indices[i]] - radiusNodes[q1_indices[i]]);
+        double orientationAxis = MathOps::dot(p, area.axis);
+        for (size_t j = 0; j < 3; ++j)
+        {
+            area.apex[j] += projectedNodes[3 * q1_indices[i] + j];
+            if (orientationAxis >= 0)
+                area.apex[j] += distanceToApex * area.axis[j];
+            else
+                area.apex[j] -= distanceToApex * area.axis[j];
+        }
+    }
+    for (size_t j = 0; j < 3; ++j)
+        area.apex[j] /= q1_indices.size();
+}
+
+void Areas::computeTorusProperties(mcIdType areaId)
+{
+    Area &area = areas[areaId];
+    size_t n = area.nodeIds.size();
+    std::vector<double> areaNodesK1(n, 0.0);
+    std::vector<double> areaNodesK2(n, 0.0);
+    for (size_t i = 0; i < n; ++i)
+    {
+        areaNodesK1[i] = nodes->getK1(area.nodeIds[i]);
+        areaNodesK2[i] = nodes->getK2(area.nodeIds[i]);
+    }
+    double var1 = MathOps::computeVar(areaNodesK1);
+    double var2 = MathOps::computeVar(areaNodesK2);
+    double minorCurvature;
+    if (var1 > var2)
+        minorCurvature = MathOps::mean(areaNodesK2);
+    else
+        minorCurvature = MathOps::mean(areaNodesK1);
+    double minorRadius = 1.0 / minorCurvature;
+    std::vector<double> majorRadiusNodes(3 * n, 0.0);
+    for (size_t i = 0; i < n; ++i)
+    {
+        std::array<double, 3> coords = nodes->getCoordinates(area.nodeIds[i]);
+        std::array<double, 3> normal = nodes->getNormal(area.nodeIds[i]);
+        for (size_t j = 0; j < 3; ++j)
+            majorRadiusNodes[3 * i + j] = coords[j] - normal[j] * minorRadius;
+    }
+    std::array<double, 3> meanMajorRadiusNodes = MathOps::meanCoordinates(majorRadiusNodes);
+    for (size_t i = 0; i < n; ++i)
+    {
+        for (size_t j = 0; j < 3; ++j)
+            majorRadiusNodes[3 * i + j] -= meanMajorRadiusNodes[j];
+    }
+    std::array<double, 3> normal = MathOps::computePCAThirdAxis(majorRadiusNodes);
+    /*
+    u, v = get_normal_plane_vectors(normal)
+    p_matrix = np.array([u, v]).T
+    p_major_radius_points = major_radius_points @ p_matrix
+    centre_2D, self.major_radius = fitCircle2D(p_major_radius_points)
+    self.centre = centre_2D[0] * u + centre_2D[1] * v + P_mean
+    self.parameters.update(
+        {
+            "minor_radius": self.minor_radius,
+            "major_radius": self.major_radius,
+            "centre": self.centre,
+        }
+    )
+    */
+}
+
+const std::vector<mcIdType> &Areas::getAreaIdByNodes() const
+{
+    return areaIdByNodes;
+}
diff --git a/src/ShapeRecogn/Areas.hxx b/src/ShapeRecogn/Areas.hxx
new file mode 100644 (file)
index 0000000..e0d8ae4
--- /dev/null
@@ -0,0 +1,104 @@
+// Copyright (C) 2007-2024  CEA, EDF
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+
+#ifndef __AREAS_HXX__
+#define __AREAS_HXX__
+
+#include "PrimitiveType.hxx"
+#include "Nodes.hxx"
+
+#include <array>
+
+namespace MEDCoupling
+{
+
+    struct Area
+    {
+        PrimitiveType primitive = PrimitiveType::Unknown;
+        double k1 = 0.0;
+        double k2 = 0.0;
+        double kdiff0 = 0.0;
+        double radius = 0.0;
+        double angle = 0.0;
+        std::array<double, 3> normal{0.0, 0.0, 0.0};
+        std::array<double, 3> center{0.0, 0.0, 0.0};
+        std::array<double, 3> axis{0.0, 0.0, 0.0};
+        std::array<double, 3> axisPoint{0.0, 0.0, 0.0};
+        std::array<double, 3> apex{0.0, 0.0, 0.0};
+        std::vector<mcIdType> nodeIds;
+    };
+
+    class Areas
+    {
+    public:
+        Areas(const Nodes *nodes);
+
+        mcIdType addArea(PrimitiveType primitive = PrimitiveType::Unknown);
+        void cleanArea(mcIdType areaId);
+        void cancelArea(mcIdType areaId, PrimitiveType primitive = PrimitiveType::Unknown);
+        void removeArea(mcIdType areaId);
+        void addNode(mcIdType areaId, mcIdType nodeId);
+        void cleanInvalidNodeAreas();
+
+        mcIdType getAreaId(mcIdType nodeId) const;
+        const std::vector<mcIdType> &getAreaIdByNodes() const;
+
+        bool isEmpty(mcIdType areaId) const;
+        size_t getNumberOfAreas() const;
+        size_t getNumberOfNodes(mcIdType areaId) const;
+
+        bool isNodeCompatible(mcIdType areaId, mcIdType nodeId) const;
+
+        PrimitiveType getPrimitiveType(mcIdType areaId) const;
+        std::string getPrimitiveTypeName(mcIdType areaId) const;
+        int getPrimitiveTypeInt(mcIdType areaId) const;
+
+        const std::vector<mcIdType> &getNodeIds(mcIdType areaId) const;
+
+        double getK1(mcIdType areaId) const;
+        double getK2(mcIdType areaId) const;
+        double getKdiff0(mcIdType areaId) const;
+
+        void computeProperties(mcIdType areaId);
+        double getRadius(mcIdType areaId) const;
+        double getAngle(mcIdType areaId) const;
+        const std::array<double, 3> &getNormal(mcIdType areaId) const;
+        const std::array<double, 3> &getCenter(mcIdType areaId) const;
+        const std::array<double, 3> &getAxis(mcIdType areaId) const;
+        const std::array<double, 3> &getAxisPoint(mcIdType areaId) const;
+        const std::array<double, 3> &getApex(mcIdType areaId) const;
+        std::array<double, 3> getAffinePoint(mcIdType areaId) const;
+
+    private:
+        void cleanArea(mcIdType areaId, mcIdType newAreaId);
+        void removeNode(mcIdType nodeId);
+
+        void computePlaneProperties(mcIdType areaId);
+        void computeSphereProperties(mcIdType areaId);
+        void computeCylinderProperties(mcIdType areaId);
+        void computeConeProperties(mcIdType areaId);
+        void computeTorusProperties(mcIdType areaId);
+
+        std::vector<Area> areas;
+        const Nodes *nodes;
+        std::vector<mcIdType> areaIdByNodes;
+    };
+};
+
+#endif // __AREAS_HXX__
diff --git a/src/ShapeRecogn/AreasBuilder.cxx b/src/ShapeRecogn/AreasBuilder.cxx
new file mode 100644 (file)
index 0000000..125c45b
--- /dev/null
@@ -0,0 +1,414 @@
+#include "AreasBuilder.hxx"
+#include "MathOps.hxx"
+#include "ShapeRecongConstants.hxx"
+
+#include <unordered_set>
+
+using namespace MEDCoupling;
+
+AreasBuilder::AreasBuilder(const Nodes *nodes) : nodes(nodes), areas(new Areas(nodes))
+{
+    size_t nbNodes = nodes->getNbNodes();
+    threshold = std::max<size_t>(THRESHOLD_MIN_NB_NODES, nbNodes / THRESHOLD_MAX_NB_AREAS);
+}
+
+void AreasBuilder::explore()
+{
+    exploreAreas();
+    // std::cout << "exploreAreas ended\n";
+    areas->cleanInvalidNodeAreas();
+    // std::cout << "cleanInvalidNodeAreas ended\n";
+    filterHighPass();
+    // std::cout << "explore ended\n";
+    // std::cout << "-------\n";
+    // for (mcIdType areaId = 0; areaId < areas->getNumberOfAreas(); ++areaId)
+    // {
+    //     std::cout << "area " << areaId << ": "
+    //               << areas->getNumberOfNodes(areaId) << ", "
+    //               << areas->getPrimitiveTypeName(areaId)
+    //               << "\n";
+    // }
+}
+
+void AreasBuilder::expand()
+{
+    expandAreas();
+    // std::cout << "expandAreas ended\n";
+    filterHighPass();
+    // std::cout << "expand ended\n";
+    // std::cout << "-------\n";
+    // for (mcIdType areaId = 0; areaId < areas->getNumberOfAreas(); ++areaId)
+    // {
+    //     std::cout << "area " << areaId << ": "
+    //               << areas->getNumberOfNodes(areaId) << ", "
+    //               << areas->getPrimitiveTypeName(areaId)
+    //               << "\n";
+    // }
+}
+
+void AreasBuilder::rebuild()
+{
+    rebuildInvalidAreas();
+    // std::cout << "rebuildInvalidAreas ended\n";
+    filterHighPass();
+    // std::cout << "filterHighPass ended\n";
+    expandAreasByType(PrimitiveType::Cone);
+    // std::cout << "expandAreas Cone ended\n";
+    filterHighPass();
+    // std::cout << "rebuild ended\n";
+}
+
+Areas *AreasBuilder::getAreas() const
+{
+    return areas;
+}
+
+void AreasBuilder::exploreAreas()
+{
+    // int nbNodesExplored = 0;
+    std::vector<bool> exploredNodeIds(nodes->getNbNodes(), false);
+    std::unordered_set<mcIdType> nodesToExplore;
+    // Reserve a set with the size of nodes to avoid reallocation for each insert/erase
+    // TODO: Improve the size ? Nb of Nodes is too much ?
+    nodesToExplore.reserve(nodes->getNbNodes());
+    mcIdType areaId = -1;
+    for (mcIdType nodeId = 0; nodeId < nodes->getNbNodes(); ++nodeId)
+    {
+        if (!exploredNodeIds[nodeId] &&
+            nodes->getPrimitiveType(nodeId) != PrimitiveType::Unknown)
+        {
+            exploredNodeIds[nodeId] = true;
+            if (areaId != -1 && areas->getNumberOfNodes(areaId) < threshold)
+                areas->cancelArea(areaId, nodes->getPrimitiveType(nodeId));
+            else
+                areaId = areas->addArea(nodes->getPrimitiveType(nodeId));
+            areas->addNode(areaId, nodeId);
+            // if (nbNodesExplored % 1 == 0)
+            // {
+            //     std::cout << "nodeId: " << nodeId << " " << convertPrimitiveToString(nodes->getPrimitiveType(nodeId)) << "\n";
+            //     std::cout << "nbNodesExplored (current area: "
+            //               << areaId
+            //               << ", "
+            //               << areas->getPrimitiveTypeName(areaId)
+            //               << "): "
+            //               << nbNodesExplored
+            //               << "\n";
+            // }
+            const std::vector<mcIdType> neighbors = nodes->getNeighbors(nodeId);
+            for (mcIdType neighborId : neighbors)
+            {
+                if (nodes->getPrimitiveType(neighborId) == areas->getPrimitiveType(areaId) &&
+                    areas->getAreaId(neighborId) <= -1)
+                    nodesToExplore.insert(neighborId);
+            }
+            // Explore all the neighbors matching the area
+            while (!nodesToExplore.empty())
+            {
+                mcIdType neighborId = *nodesToExplore.begin();
+                nodesToExplore.erase(neighborId);
+                if (doesItMatch(areaId, neighborId))
+                {
+                    // if (!exploredNodeIds[neighborId])
+                    //     nbNodesExplored += 1;
+                    // if (areas->getNumberOfNodes(areaId) % 500 == 0)
+                    // {
+                    //     std::cout << "nbNodesExplored (current area: "
+                    //               << areaId
+                    //               << ", " << areas->getPrimitiveTypeName(areaId)
+                    //               << ", " << areas->getNumberOfNodes(areaId)
+                    //               << "): "
+                    //               << nbNodesExplored
+                    //               << "\n";
+                    //     std::cout << "neighborId: " << neighborId << " " << convertPrimitiveToString(nodes->getPrimitiveType(neighborId)) << "\n";
+                    // }
+                    exploredNodeIds[neighborId] = true;
+                    areas->addNode(areaId, neighborId);
+                    const std::vector<mcIdType> neighborsOfNeighbor = nodes->getNeighbors(neighborId);
+                    for (mcIdType neighborIdOfNeighbor : neighborsOfNeighbor)
+                    {
+                        if (!exploredNodeIds[neighborIdOfNeighbor] &&
+                            areas->getAreaId(neighborIdOfNeighbor) <= -1 &&
+                            // Already in doesItMatch but avoid useless insertion
+                            nodes->getPrimitiveType(neighborIdOfNeighbor) == areas->getPrimitiveType(areaId))
+                            nodesToExplore.insert(neighborIdOfNeighbor);
+                    }
+                }
+            }
+        }
+        // if (!exploredNodeIds[nodeId])
+        //     nbNodesExplored += 1;
+        exploredNodeIds[nodeId] = true;
+    }
+}
+
+void AreasBuilder::expandAreas()
+{
+    // Expand by topological order
+    expandAreasByType(PrimitiveType::Plane);
+    expandAreasByType(PrimitiveType::Sphere);
+    expandAreasByType(PrimitiveType::Cylinder);
+    expandAreasByType(PrimitiveType::Cone);
+    expandAreasByType(PrimitiveType::Torus);
+}
+
+void AreasBuilder::expandAreasByType(PrimitiveType primitive)
+{
+    std::unordered_set<mcIdType> nodesToExplore;
+    // Reserve a set with the size of nodes to avoid reallocation for each insert/erase
+    // TODO: Improve the size ? Nb of Nodes is too much ?
+    nodesToExplore.reserve(nodes->getNbNodes());
+    for (mcIdType areaId = 0; areaId < (mcIdType)areas->getNumberOfAreas(); ++areaId)
+    {
+        if (areas->getPrimitiveType(areaId) == primitive)
+        {
+            std::vector<bool> exploredNodeIds(nodes->getNbNodes(), false);
+            areas->computeProperties(areaId);
+            const std::vector<mcIdType> &nodeIds = areas->getNodeIds(areaId);
+            for (mcIdType nodeId : nodeIds)
+            {
+                exploredNodeIds[nodeId] = true;
+                nodesToExplore.insert(nodeId);
+            }
+            while (!nodesToExplore.empty())
+            {
+                mcIdType nodeId = *nodesToExplore.begin();
+                nodesToExplore.erase(nodeId);
+                if (doesItBelong(areaId, nodeId))
+                {
+                    // TODO: Is the properties need to be updated after adding a node ?
+                    // It gives bad results for the cone and the cylinder
+                    // mcIdType oldAreaId = areas->getAreaId(nodeId);
+                    areas->addNode(areaId, nodeId);
+                    // areas->computeProperties(areaId);
+                    // areas->computeProperties(oldAreaId);
+                    const std::vector<mcIdType> neighborIds = nodes->getNeighbors(nodeId);
+                    for (mcIdType neighborId : neighborIds)
+                    {
+                        if (!exploredNodeIds[neighborId])
+                            nodesToExplore.insert(neighborId);
+                    }
+                }
+                exploredNodeIds[nodeId] = true;
+            }
+        }
+    }
+}
+
+void AreasBuilder::rebuildInvalidAreas()
+{
+    std::vector<mcIdType> exploredNodeIds(nodes->getNbNodes(), false);
+    std::vector<bool> isIinvalidNodes(nodes->getNbNodes(), false);
+    std::unordered_set<mcIdType> nodesToExplore;
+    // Reserve a set with the size of nodes to avoid reallocation for each insert/erase
+    // TODO: Improve the size ? Nb of Nodes is too much ?
+    nodesToExplore.reserve(nodes->getNbNodes());
+    for (mcIdType nodeId = 0; nodeId < nodes->getNbNodes(); ++nodeId)
+        isIinvalidNodes[nodeId] = isInvalidCylinderNode(nodeId);
+    for (mcIdType nodeId = 0; nodeId < nodes->getNbNodes(); ++nodeId)
+    {
+        if (isIinvalidNodes[nodeId] && !exploredNodeIds[nodeId])
+        {
+            mcIdType areaId = areas->addArea(PrimitiveType::Cone);
+            areas->addNode(areaId, nodeId);
+            const std::vector<mcIdType> neighbors = nodes->getNeighbors(nodeId);
+            for (mcIdType neighborId : neighbors)
+                nodesToExplore.insert(neighborId);
+            while (!nodesToExplore.empty())
+            {
+                mcIdType neighborId = *nodesToExplore.begin();
+                nodesToExplore.erase(neighborId);
+                if (
+                    (areas->getAreaId(neighborId) == -1 ||
+                     isIinvalidNodes[neighborId]))
+                {
+                    exploredNodeIds[neighborId] = true;
+                    areas->addNode(areaId, neighborId);
+                    const std::vector<mcIdType> neighborsOfNeighbor = nodes->getNeighbors(neighborId);
+                    for (mcIdType neighborIdOfNeighbor : neighborsOfNeighbor)
+                    {
+                        if (!exploredNodeIds[neighborIdOfNeighbor])
+                            nodesToExplore.insert(neighborIdOfNeighbor);
+                    }
+                }
+            }
+        }
+    }
+}
+
+void AreasBuilder::filterHighPass()
+{
+    mcIdType nbAreas = areas->getNumberOfAreas();
+    for (mcIdType areaId = (nbAreas - 1); areaId >= 0; --areaId)
+    {
+        if (areas->getNumberOfNodes(areaId) < threshold)
+            areas->removeArea(areaId);
+    }
+}
+
+bool AreasBuilder::doesItMatch(mcIdType areaId, mcIdType nodeId) const
+{
+    PrimitiveType areaType = areas->getPrimitiveType(areaId);
+    PrimitiveType neighborType = nodes->getPrimitiveType(nodeId);
+    bool isMatching = false;
+    if (areaType == neighborType)
+    {
+        switch (areaType)
+        {
+        case PrimitiveType::Plane:
+        case PrimitiveType::Torus:
+            isMatching = true;
+            break;
+        case PrimitiveType::Sphere:
+        {
+            double kmoy = fabs(
+                (areas->getK1(areaId) + areas->getK2(areaId)) / 2.0);
+            double nodeKmoy = fabs(
+                (nodes->getK1(nodeId) + nodes->getK2(nodeId)) / 2.0);
+            // TODO: Replace by relative tolerance
+            isMatching = (kmoy + TOL_MATCH_SPHERE > nodeKmoy) &&
+                         (nodeKmoy > kmoy - TOL_MATCH_SPHERE);
+        }
+        break;
+        case PrimitiveType::Cylinder:
+            isMatching =
+                fabs((areas->getKdiff0(areaId) - nodes->getKdiff0(nodeId)) /
+                     areas->getKdiff0(areaId)) < EPSILON_PROP;
+            break;
+        case PrimitiveType::Cone:
+        case PrimitiveType::Unknown:
+        default:
+            break;
+        }
+    }
+    return isMatching;
+}
+
+bool AreasBuilder::doesItBelong(mcIdType areaId, mcIdType nodeId) const
+{
+    bool isClose = false;
+    if (areas->isNodeCompatible(areaId, nodeId))
+    {
+        PrimitiveType areaType = areas->getPrimitiveType(areaId);
+        switch (areaType)
+        {
+        case PrimitiveType::Plane:
+        {
+            isClose = distanceToPlane(
+                          nodes->getCoordinates(nodeId),
+                          areas->getAffinePoint(areaId),
+                          areas->getNormal(areaId)) < DELTA_PLANE;
+        }
+        break;
+        case PrimitiveType::Sphere:
+        {
+            double distanceToCenter = distanceToSphere(
+                nodes->getCoordinates(nodeId),
+                areas->getCenter(areaId));
+            isClose = fabs(distanceToCenter - areas->getRadius(areaId)) < DELTA_SPHERE;
+        }
+        break;
+        case PrimitiveType::Cylinder:
+        {
+            double distance = distanceToCylinder(
+                nodes->getCoordinates(nodeId),
+                areas->getAxis(areaId),
+                areas->getAxisPoint(areaId));
+            double radius = areas->getRadius(areaId);
+            isClose = fabs((distance - radius) / radius) < DELTA_CYLINDER;
+        }
+        break;
+        case PrimitiveType::Cone:
+        {
+            isClose = distanceToCone(
+                          nodes->getCoordinates(nodeId),
+                          areas->getAxis(areaId),
+                          areas->getApex(areaId),
+                          areas->getAngle(areaId),
+                          areas->getRadius(areaId)) < DELTA_CONE;
+        }
+        break;
+        case PrimitiveType::Torus:
+        case PrimitiveType::Unknown:
+        default:
+            break;
+        }
+    }
+    return isClose;
+}
+
+bool AreasBuilder::isInvalidCylinderNode(mcIdType nodeId) const
+{
+    mcIdType areaId = areas->getAreaId(nodeId);
+    if (areaId != -1 &&
+        nodes->getPrimitiveType(nodeId) == PrimitiveType::Cylinder &&
+        areas->getPrimitiveType(areaId) == PrimitiveType::Cylinder)
+    {
+        double angle = MathOps::computeAngle(
+            nodes->getWeakDirection(nodeId),
+            areas->getAxis(areaId));
+        return angle >= THETA_MAX_CYLINDER && angle <= (M_PI - THETA_MAX_CYLINDER);
+    }
+    return false;
+}
+
+double AreasBuilder::distanceToPlane(
+    const std::array<double, 3> &nodeCoords,
+    const std::array<double, 3> &point,
+    const std::array<double, 3> &normal)
+{
+    std::array<double, 3> vec{0.0, 0.0, 0.0};
+    for (size_t i = 0; i < nodeCoords.size(); ++i)
+        vec[i] = nodeCoords[i] - point[i];
+    return fabs(MathOps::dot(vec, normal)) / MathOps::computeNorm(normal);
+}
+
+double AreasBuilder::distanceToSphere(
+    const std::array<double, 3> &nodeCoords,
+    const std::array<double, 3> &center)
+{
+    return MathOps::computeNorm(
+        std::array<double, 3>{
+            nodeCoords[0] - center[0],
+            nodeCoords[1] - center[1],
+            nodeCoords[2] - center[2]});
+}
+
+double AreasBuilder::distanceToCylinder(
+    const std::array<double, 3> &nodeCoords,
+    const std::array<double, 3> &axis,
+    const std::array<double, 3> &axisPoint)
+{
+
+    std::array<double, 3> pa = {
+        axisPoint[0] - nodeCoords[0],
+        axisPoint[1] - nodeCoords[1],
+        axisPoint[2] - nodeCoords[2]};
+    double innerProduct = MathOps::dot(pa, axis);
+    return MathOps::computeNorm(
+        std::array<double, 3>({pa[0] - innerProduct * axis[0],
+                               pa[1] - innerProduct * axis[1],
+                               pa[2] - innerProduct * axis[2]}));
+}
+
+double AreasBuilder::distanceToCone(
+    const std::array<double, 3> &nodeCoords,
+    const std::array<double, 3> &axis,
+    const std::array<double, 3> &apex,
+    double angle,
+    double radius)
+{
+    std::array<double, 3> ps{
+        apex[0] - nodeCoords[0],
+        apex[1] - nodeCoords[1],
+        apex[2] - nodeCoords[2]};
+    std::array<double, 3> v(axis);
+    if (MathOps::dot(axis, ps) <= 0)
+    {
+        for (size_t i = 0; i < 3; ++i)
+            v[i] *= -1;
+    }
+    double a = MathOps::computeNorm(
+        MathOps::cross(ps, v));
+    double b = MathOps::dot(ps, v);
+    return fabs(a * cos(angle) - b * sin(angle)) / fabs(radius);
+}
diff --git a/src/ShapeRecogn/AreasBuilder.hxx b/src/ShapeRecogn/AreasBuilder.hxx
new file mode 100644 (file)
index 0000000..eb97c57
--- /dev/null
@@ -0,0 +1,74 @@
+// Copyright (C) 2007-2024  CEA, EDF
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+
+#ifndef __AREASBUILDER_HXX__
+#define __AREASBUILDER_HXX__
+
+#include "Nodes.hxx"
+#include "Areas.hxx"
+
+namespace MEDCoupling
+{
+    class AreasBuilder
+    {
+    public:
+        AreasBuilder(const Nodes *nodes);
+
+        void explore();
+        void expand();
+        void rebuild();
+
+        Areas *getAreas() const;
+
+    private:
+        void exploreAreas();
+        void expandAreas();
+        void expandAreasByType(PrimitiveType primitive);
+        void rebuildInvalidAreas();
+        void filterHighPass();
+        bool doesItMatch(mcIdType areaId, mcIdType nodeId) const;
+        bool doesItBelong(mcIdType areaId, mcIdType nodeId) const;
+        bool isInvalidCylinderNode(mcIdType nodeId) const;
+
+        static double distanceToPlane(
+            const std::array<double, 3> &nodeCoords,
+            const std::array<double, 3> &point,
+            const std::array<double, 3> &normal);
+        static double distanceToSphere(
+            const std::array<double, 3> &nodeCoords,
+            const std::array<double, 3> &center);
+        static double distanceToCylinder(
+            const std::array<double, 3> &nodeCoords,
+            const std::array<double, 3> &axis,
+            const std::array<double, 3> &axisPoint);
+        static double distanceToCone(
+            const std::array<double, 3> &nodeCoords,
+            const std::array<double, 3> &axis,
+            const std::array<double, 3> &apex,
+            double angle,
+            double radius);
+
+        const Nodes *nodes;
+        Areas *areas;
+
+        size_t threshold = 5;
+    };
+};
+
+#endif // __AREASBUILDER_HXX__
\ No newline at end of file
index c5717d293916532b2ac8f103d3837301761e8dbd..5dac83f40ba027db2b49fc1df80f4f8f04672209 100644 (file)
@@ -37,10 +37,18 @@ INCLUDE_DIRECTORIES(
   ${LAPACKE_INCLUDE_DIRS}
   ${CMAKE_CURRENT_SOURCE_DIR}/../MEDCoupling
   ${CMAKE_CURRENT_SOURCE_DIR}/../MEDLoader
+  ${CMAKE_CURRENT_SOURCE_DIR}/../INTERP_KERNEL
+  ${CMAKE_CURRENT_SOURCE_DIR}/../INTERP_KERNEL/Bases
 )
 
 SET(shaperecogn_SOURCES
-  )
+  MathOps.cxx
+  Nodes.cxx
+  NodeCurvatureCalculator.cxx
+  Areas.cxx
+  AreasBuilder.cxx
+  ShapeRecognMesh.cxx
+)
 
 ADD_LIBRARY(shaperecogn ${shaperecogn_SOURCES})
 SET_TARGET_PROPERTIES(shaperecogn PROPERTIES COMPILE_FLAGS "")
diff --git a/src/ShapeRecogn/MathOps.cxx b/src/ShapeRecogn/MathOps.cxx
new file mode 100644 (file)
index 0000000..910b8cb
--- /dev/null
@@ -0,0 +1,272 @@
+// Copyright (C) 2007-2024  CEA, EDF
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+
+#include "MathOps.hxx"
+
+#include <algorithm>
+#include <lapacke.h>
+#include <cblas.h>
+#include <iostream>
+#include <cfloat>
+
+using namespace MEDCoupling;
+
+std::vector<double> MathOps::lstsq(
+    std::vector<double> &a, const std::vector<double> &b)
+{
+    int m = (int)b.size();
+    int n = 3;
+    int nrhs = 1;
+    return lstsq(a, b, m, n, nrhs);
+}
+
+std::vector<double> MathOps::lstsq(
+    std::vector<double> &a, const std::vector<double> &b, int m, int n, int nrhs)
+{
+    int ldb = std::max<int>(m, n);
+    int lds = std::min<int>(m, n);
+    std::vector<double> x(b.begin(), b.end());
+    std::vector<double> s(lds, 0.0);
+    double rcond = DBL_EPSILON * (double)ldb; // same value as numpy.linalg.lstsq
+    int rank = 0;
+    int info = LAPACKE_dgelsd(
+        LAPACK_COL_MAJOR,
+        m, n, nrhs,
+        a.data(), m,
+        x.data(), ldb,
+        s.data(),
+        rcond,
+        &rank);
+    return x;
+}
+
+std::array<double, 3> MathOps::cross(const std::array<double, 3> &a, const std::array<double, 3> &b)
+{
+    std::array<double, 3> c{0.0, 0.0, 0.0};
+    c[0] = a[1] * b[2] - a[2] * b[1];
+    c[1] = a[2] * b[0] - a[0] * b[2];
+    c[2] = a[0] * b[1] - a[1] * b[0];
+    return c;
+}
+
+std::array<double, 3> MathOps::normalize(const std::array<double, 3> &a)
+{
+    std::array<double, 3> an{0.0, 0.0, 0.0};
+    double n = computeNorm(a);
+    for (size_t i = 0; i < 3; i++)
+        an[i] = a[i] / n;
+    return an;
+}
+
+double MathOps::computeNorm(const std::array<double, 3> &a)
+{
+    double n = 0;
+    for (size_t i = 0; i < 3; i++)
+        n += a[i] * a[i];
+    return sqrt(n);
+}
+
+std::vector<double> MathOps::computeNorm(
+    const std::vector<double> &a)
+{
+    size_t s = a.size() / 3;
+    std::vector<double> n(s, 0.0);
+    for (size_t i = 0; i < s; i++)
+    {
+        for (size_t j = 0; j < 3; j++)
+            n[i] += a[3 * i + j] * a[3 * i + j];
+        n[i] = sqrt(n[i]);
+    }
+    return n;
+}
+
+double MathOps::dot(const std::array<double, 3> &a, const std::array<double, 3> &b)
+{
+    double d = 0.0;
+    for (size_t i = 0; i < 3; i++)
+        d += a[i] * b[i];
+    return d;
+}
+
+std::vector<double> MathOps::dot(const std::vector<double> &a, const std::array<double, 3> &b)
+{
+    size_t nbNodes = a.size() / 3;
+    std::vector<double> d(nbNodes, 0.0);
+    cblas_dgemv(
+        CBLAS_LAYOUT::CblasRowMajor,
+        CBLAS_TRANSPOSE::CblasNoTrans,
+        (int)nbNodes, 3, 1.0,
+        a.data(), 3,
+        b.data(), 1,
+        0.0, d.data(), 1);
+    return d;
+}
+
+double MathOps::mean(const std::vector<double> &values)
+{
+    double mean = 0.0;
+    for (double value : values)
+        mean += value;
+    return mean / values.size();
+}
+
+std::array<double, 3> MathOps::meanCoordinates(const std::vector<double> &coordinates)
+{
+    std::array<double, 3> coordsMean{0.0, 0.0, 0.0};
+    size_t nbNodes = coordinates.size() / 3;
+    for (size_t nodeId = 0; nodeId < nbNodes; ++nodeId)
+    {
+        coordsMean[0] += coordinates[3 * nodeId];
+        coordsMean[1] += coordinates[3 * nodeId + 1];
+        coordsMean[2] += coordinates[3 * nodeId + 2];
+    }
+    coordsMean[0] /= nbNodes;
+    coordsMean[1] /= nbNodes;
+    coordsMean[2] /= nbNodes;
+    return coordsMean;
+}
+
+std::array<double, 9> MathOps::computeCov(
+    const std::vector<double> &coordinates)
+{
+    std::array<double, 9> covMatrix;
+    covMatrix.fill(0);
+    size_t nbNodes = coordinates.size() / 3;
+    // Center the coordinates
+    std::vector<double> coordsCentered(coordinates);
+    std::array<double, 3> coordsMean = meanCoordinates(coordinates);
+    for (size_t nodeId = 0; nodeId < nbNodes; ++nodeId)
+    {
+        coordsCentered[3 * nodeId] -= coordsMean[0];
+        coordsCentered[3 * nodeId + 1] -= coordsMean[1];
+        coordsCentered[3 * nodeId + 2] -= coordsMean[2];
+    }
+    cblas_dgemm(
+        CBLAS_LAYOUT::CblasColMajor,
+        CBLAS_TRANSPOSE::CblasNoTrans,
+        CBLAS_TRANSPOSE::CblasTrans,
+        3, 3, nbNodes, 1.0,
+        coordsCentered.data(), 3,
+        coordsCentered.data(), 3,
+        0.0, covMatrix.data(), 3);
+    for (size_t i = 0; i < 9; ++i)
+    {
+        covMatrix[i] /= nbNodes - 1;
+    }
+    return covMatrix;
+}
+
+std::array<double, 9> MathOps::computePCA(const std::vector<double> &coordinates)
+{
+    std::array<double, 9> covMatrix = computeCov(coordinates);
+    std::array<double, 3> eigenValues{0.0, 0.0, 0.0};
+    LAPACKE_dsyevd(
+        LAPACK_COL_MAJOR,
+        'V',
+        'U',
+        3,
+        covMatrix.data(),
+        3,
+        eigenValues.data());
+    return covMatrix;
+}
+
+std::array<double, 3> MathOps::computePCAFirstAxis(const std::vector<double> &coordinates)
+{
+    std::array<double, 9> pca = computePCA(coordinates);
+    // The eignvalues are in ascending order so the first axis correspond to the last vector
+    return {pca[6], pca[7], pca[8]};
+}
+
+std::array<double, 3> MathOps::computePCAThirdAxis(const std::vector<double> &coordinates)
+{
+    std::array<double, 9> pca = computePCA(coordinates);
+    // The eignvalues are in ascending order so the third axis correspond to the first vector
+    return {pca[0], pca[1], pca[2]};
+}
+
+double MathOps::computeQuantile(const std::vector<double> &values, double q)
+{
+    std::vector<double> sortedValues(values);
+    std::sort(sortedValues.begin(), sortedValues.end());
+    double pos = q * (sortedValues.size() - 1);
+    size_t index = static_cast<size_t>(pos);
+    if (pos == index)
+        return sortedValues[index];
+    else
+    {
+        double frac = pos - index;
+        return sortedValues[index] * (1 - frac) + sortedValues[index + 1] * frac;
+    }
+}
+
+double MathOps::computeAngle(const std::array<double, 3> &direction, std::array<double, 3> axis)
+{
+    double angle = dot(direction, axis);
+    double normAxis = computeNorm(axis);
+    double normDirection = computeNorm(direction);
+    angle /= normDirection * normAxis;
+    if (fabs(angle) >= 1)
+        return 0;
+    else
+        return acos(angle);
+}
+
+std::vector<double> MathOps::computeAngles(
+    const std::vector<double> &directions, std::array<double, 3> axis)
+{
+    size_t nbDirections = directions.size() / 3;
+    std::vector<double> angles(nbDirections, 0.0);
+    cblas_dgemv(
+        CBLAS_LAYOUT::CblasRowMajor,
+        CBLAS_TRANSPOSE::CblasNoTrans,
+        nbDirections, 3, 1.0,
+        directions.data(), 3,
+        axis.data(), 1,
+        0.0, angles.data(), 1);
+    double normAxis = computeNorm(axis);
+    std::vector<double> normDirections = computeNorm(directions);
+    for (size_t i = 0; i < nbDirections; ++i)
+    {
+        angles[i] /= normAxis * normDirections[i];
+        if (fabs(angles[i]) >= 1.0)
+            angles[i] = 0.0;
+        angles[i] = acos(angles[i]);
+    }
+    return angles;
+}
+
+double MathOps::computeOrientedAngle(const std::array<double, 3> &normal, const std::array<double, 3> &vector1, const std::array<double, 3> &vector2)
+{
+    double angle = computeAngle(vector1, vector2);
+    if (dot(cross(vector1, vector2), normal) >= 0.0)
+        return angle;
+    else
+        return -angle;
+}
+
+double MathOps::computeVar(std::vector<double> values)
+{
+    int n = values.size();
+    double m = mean(values);
+    double d2 = 0;
+    for (size_t i = 0; i < n; ++i)
+        d2 += pow(values[i] - m, 2);
+    return d2 / n;
+}
diff --git a/src/ShapeRecogn/MathOps.hxx b/src/ShapeRecogn/MathOps.hxx
new file mode 100644 (file)
index 0000000..267f917
--- /dev/null
@@ -0,0 +1,67 @@
+// Copyright (C) 2007-2024  CEA, EDF
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+
+#ifndef __MATHOPS_HXX__
+#define __MATHOPS_HXX__
+
+#include <vector>
+#include <array>
+
+namespace MEDCoupling
+{
+    class MathOps
+    {
+    public:
+        static std::vector<double> lstsq(std::vector<double> &a, const std::vector<double> &b);
+        static std::vector<double> lstsq(
+            std::vector<double> &a,
+            const std::vector<double> &b,
+            int m,
+            int n = 3,
+            int nrhs = 1);
+        static std::array<double, 3> cross(const std::array<double, 3> &a, const std::array<double, 3> &b);
+        static std::array<double, 3> normalize(const std::array<double, 3> &a);
+        static double computeNorm(const std::array<double, 3> &a);
+        static std::vector<double> computeNorm(const std::vector<double> &a);
+        static double dot(const std::array<double, 3> &a, const std::array<double, 3> &b);
+        static std::vector<double> dot(const std::vector<double> &a, const std::array<double, 3> &b);
+        static double mean(const std::vector<double> &values);
+        static std::array<double, 3> meanCoordinates(const std::vector<double> &coordinates);
+        static std::array<double, 9> computeCov(const std::vector<double> &coordinates);
+        static std::array<double, 9> computePCA(const std::vector<double> &coordinates);
+        static std::array<double, 3> computePCAFirstAxis(const std::vector<double> &coordinates);
+        static std::array<double, 3> computePCAThirdAxis(const std::vector<double> &coordinates);
+        static double computeQuantile(
+            const std::vector<double> &values,
+            double q);
+        static double computeAngle(
+            const std::array<double, 3> &direction,
+            std::array<double, 3> axis);
+        static std::vector<double> computeAngles(
+            const std::vector<double> &directions,
+            std::array<double, 3> axis);
+        static double computeOrientedAngle(
+            const std::array<double, 3> &normal,
+            const std::array<double, 3> &vector1,
+            const std::array<double, 3> &vector2);
+        static double computeVar(std::vector<double> values);
+    };
+};
+
+#endif //__MATHOPS_HXX__
diff --git a/src/ShapeRecogn/NodeCurvatureCalculator.cxx b/src/ShapeRecogn/NodeCurvatureCalculator.cxx
new file mode 100644 (file)
index 0000000..46b5454
--- /dev/null
@@ -0,0 +1,264 @@
+
+#include "NodeCurvatureCalculator.hxx"
+#include "Nodes.hxx"
+#include "MathOps.hxx"
+#include "ShapeRecongConstants.hxx"
+
+using namespace MEDCoupling;
+
+NodeCurvatureCalculator::NodeCurvatureCalculator(
+    const MEDCouplingUMesh *mesh) : mesh(mesh)
+{
+}
+
+Nodes *NodeCurvatureCalculator::compute()
+{
+    DataArrayInt64 *neighbors;
+    DataArrayInt64 *neighborsIdx;
+    mesh->computeNeighborsOfNodes(neighbors, neighborsIdx);
+    nodes = new Nodes(mesh, neighbors, neighborsIdx);
+    computeNormals();
+    // std::cout << "computeNormals ended\n";
+    computeCurvatures();
+    // std::cout << "computeCurvatures ended\n";
+    return nodes;
+}
+
+void NodeCurvatureCalculator::computeNormals()
+{
+    mcIdType nbNodes = mesh->getNumberOfNodes();
+    mcIdType nbCells = mesh->getNumberOfCells();
+    std::array<double, 3> cellNormal;
+    cellNormal.fill(0.0);
+    // product of normal AreaByCell
+    std::vector<double> prodNormalAreaByCell(3 * nbCells, 0.0);
+    for (int cellId = 0; cellId < nbCells; cellId++)
+    {
+        std::vector<mcIdType> nodeIds;
+        mesh->getNodeIdsOfCell(cellId, nodeIds);
+        computeCellNormal(nodeIds, cellNormal);
+        prodNormalAreaByCell[3 * cellId + 0] = cellNormal[0] / 2.0;
+        prodNormalAreaByCell[3 * cellId + 1] = cellNormal[1] / 2.0;
+        prodNormalAreaByCell[3 * cellId + 2] = cellNormal[2] / 2.0;
+    }
+    //
+    nodes->normals.resize(3 * nbNodes, 1.0);
+    DataArrayInt64 *revNodal = DataArrayInt64::New();
+    DataArrayInt64 *revNodalIdx = DataArrayInt64::New();
+    mesh->getReverseNodalConnectivity(revNodal, revNodalIdx);
+    for (mcIdType nodeId = 0; nodeId < nbNodes; nodeId++)
+    {
+        // nodeIds[0] = nodeId;
+        // auto cellIds = mesh->getCellIdsLyingOnNodes(nodeIds.data(), nodeIds.data() + 1, false);
+        int nbCells = revNodalIdx->getIJ(nodeId + 1, 0) - revNodalIdx->getIJ(nodeId, 0);
+        std::vector<mcIdType> cellIds(nbCells, 0);
+        int start = revNodalIdx->getIJ(nodeId, 0);
+        for (size_t i = 0; i < cellIds.size(); ++i)
+            cellIds[i] = revNodal->getIJ(start + i, 0);
+        double normal = 0.0;
+        for (size_t i = 0; i < 3; i++)
+        {
+            nodes->normals[3 * nodeId + i] = 0.0;
+            for (mcIdType j = 0; j < nbCells; j++)
+            {
+                nodes->normals[3 * nodeId + i] +=
+                    prodNormalAreaByCell[3 * cellIds[j] + i];
+            }
+            nodes->normals[3 * nodeId + i] /= (double)nbCells;
+            normal += pow(nodes->normals[3 * nodeId + i], 2);
+        }
+        for (size_t i = 0; i < 3; i++)
+            nodes->normals[3 * nodeId + i] /= sqrt(normal);
+    }
+    revNodal->decrRef();
+    revNodalIdx->decrRef();
+}
+
+void NodeCurvatureCalculator::computeCurvatures(double tol)
+{
+    mcIdType nbNodes = mesh->getNumberOfNodes();
+    nodes->k1.resize(nbNodes);
+    nodes->k2.resize(nbNodes);
+    nodes->theta0.resize(nbNodes);
+    nodes->isConvex.resize(nbNodes);
+    nodes->kdiff0.resize(nbNodes);
+    nodes->primitives.resize(nbNodes);
+    nodes->weakDirections.resize(3 * nbNodes);
+    for (mcIdType nodeId = 0; nodeId < nbNodes; nodeId++)
+        computeCurvatures(nodeId, tol);
+}
+
+void NodeCurvatureCalculator::computeCurvatures(mcIdType nodeId, double tol)
+{
+    std::array<double, 3> normal = nodes->getNormal(nodeId);
+    std::vector<mcIdType> neighborIds = nodes->getNeighbors(nodeId);
+    std::vector<double> discreteCurvatures = computeDiscreteCurvatures(nodeId, neighborIds);
+    std::vector<double> tangents = computeTangentDirections(nodeId, neighborIds);
+    mcIdType maxCurvatureId = std::distance(
+        discreteCurvatures.begin(),
+        std::max_element(discreteCurvatures.begin(), discreteCurvatures.end()));
+    std::array<double, 3> e1{
+        tangents[3 * maxCurvatureId],
+        tangents[3 * maxCurvatureId + 1],
+        tangents[3 * maxCurvatureId + 2]};
+    std::array<double, 3> e2 = MathOps::normalize(MathOps::cross(e1, normal));
+    std::vector<double> coeffs = computeNormalCurvatureCoefficients(
+        discreteCurvatures, tangents, normal, e1);
+    double a = coeffs[0], b = coeffs[1], c = coeffs[2];
+    double h = (a + c) / 2.0;
+    double kg = a * c - pow(b, 2) / 4.0;
+    double k1 = h + sqrt(fabs(pow(h, 2) - kg));
+    double k2 = h - sqrt(fabs(pow(h, 2) - kg));
+    double theta0 = 0.0;
+    if (fabs(k1 - k2) >= tol)
+        theta0 = 0.5 * asin(b / (k1 - k2));
+    std::array<double, 3> direction1{0.0, 0.0, 0.0};
+    std::array<double, 3> direction2{0.0, 0.0, 0.0};
+    for (size_t i = 0; i < 3; ++i)
+    {
+        direction1[i] = cos(theta0) * e1[i] + sin(theta0) * e2[i];
+        direction2[i] = -sin(theta0) * e1[i] + cos(theta0) * e2[i];
+    }
+    bool isConvex;
+    double kdiff0;
+    double kis0;
+    std::array<double, 3> mainDirection{0.0, 0.0, 0.0};
+    std::array<double, 3> weakDirection{0.0, 0.0, 0.0};
+    if (fabs(k1) > fabs(k2))
+    {
+        isConvex = true;
+        kdiff0 = k1;
+        kis0 = k2;
+        mainDirection = direction1;
+        weakDirection = direction2;
+    }
+    else
+    {
+        isConvex = false;
+        kdiff0 = k2;
+        kis0 = k1;
+        mainDirection = direction2;
+        weakDirection = direction1;
+    }
+    PrimitiveType primitive = findPrimitiveType(k1, k2, kdiff0, kis0);
+    // Populate nodes
+    nodes->isConvex[nodeId] = isConvex;
+    nodes->theta0[nodeId] = theta0;
+    nodes->k1[nodeId] = k1;
+    nodes->k2[nodeId] = k2;
+    nodes->kdiff0[nodeId] = kdiff0;
+    for (size_t i = 0; i < 3; ++i)
+        nodes->weakDirections[3 * nodeId + i] = weakDirection[i];
+    nodes->primitives[nodeId] = primitive;
+}
+
+PrimitiveType NodeCurvatureCalculator::findPrimitiveType(double k1, double k2, double kdiff0, double kis0) const
+{
+    if ((fabs(k1 - k2) < EPSILON_SC) && (fabs((k1 + k2) / 2) < EPSILON_PL))
+        return PrimitiveType::Plane;
+    else if ((fabs(k1 - k2) < EPSILON_SC) && (fabs((k1 + k2) / 2) > EPSILON_PL))
+        return PrimitiveType::Sphere;
+    else if ((fabs(kdiff0) > EPSILON_CY) && (fabs(kis0) < EPSILON_CY))
+        return PrimitiveType::Cylinder;
+    else if ((fabs(k1) > EPSILON_CY) && (fabs(k2) > EPSILON_CY))
+        return PrimitiveType::Torus;
+    else
+        return PrimitiveType::Unknown;
+}
+
+std::vector<double> NodeCurvatureCalculator::computeNormalCurvatureCoefficients(
+    const std::vector<double> &discreteCurvatures,
+    const std::vector<double> &tangents,
+    const std::array<double, 3> &normal,
+    const std::array<double, 3> &e1) const
+{
+    size_t nbNeighbors = discreteCurvatures.size();
+    std::vector<double> a(3 * nbNeighbors, 0.0);
+    for (size_t i = 0; i < nbNeighbors; ++i)
+    {
+        std::array<double, 3> tangent{tangents[3 * i], tangents[3 * i + 1], tangents[3 * i + 2]};
+        double theta = MathOps::computeOrientedAngle(normal, tangent, e1);
+        double cos_theta = cos(theta);
+        double sin_theta = sin(theta);
+        a[i] = pow(cos_theta, 2);
+        a[nbNeighbors + i] = cos_theta * sin_theta;
+        a[2 * nbNeighbors + i] = pow(sin_theta, 2);
+    }
+    return MathOps::lstsq(a, discreteCurvatures);
+}
+
+void NodeCurvatureCalculator::computeCellNormal(
+    const std::vector<mcIdType> &nodeIds,
+    std::array<double, 3> &cellNormal) const
+{
+    std::vector<double> point1;
+    std::vector<double> point2;
+    std::vector<double> point3;
+    mesh->getCoordinatesOfNode(nodeIds[0], point1);
+    mesh->getCoordinatesOfNode(nodeIds[1], point2);
+    mesh->getCoordinatesOfNode(nodeIds[2], point3);
+    std::array<double, 3> a;
+    a.fill(3);
+    std::array<double, 3> b;
+    b.fill(3);
+    for (int i = 0; i < 3; i++)
+    {
+        a[i] = point2[i] - point1[i];
+        b[i] = point3[i] - point1[i];
+    }
+    cellNormal[0] = a[1] * b[2] - a[2] * b[1];
+    cellNormal[1] = a[2] * b[0] - a[0] * b[2];
+    cellNormal[2] = a[0] * b[1] - a[1] * b[0];
+}
+
+std::vector<double> NodeCurvatureCalculator::computeDiscreteCurvatures(
+    mcIdType nodeId,
+    const std::vector<mcIdType> &neighborIds) const
+{
+    std::vector<double> discreteCurvatures(neighborIds.size(), 0.0);
+    for (size_t i = 0; i < neighborIds.size(); ++i)
+        discreteCurvatures[i] = computeDiscreteCurvature(nodeId, neighborIds[i]);
+    return discreteCurvatures;
+}
+
+double NodeCurvatureCalculator::computeDiscreteCurvature(
+    mcIdType nodeId,
+    mcIdType neighborId) const
+{
+    double curvature = 0.0;
+    double n = 0.0;
+    for (size_t i = 0; i < 3; i++)
+    {
+        double ni = nodes->coords->getIJ(neighborId, i) - nodes->coords->getIJ(nodeId, i);
+        curvature += ni * (nodes->normals[3 * neighborId + i] - nodes->normals[3 * nodeId + i]);
+        n += ni * ni;
+    }
+    return curvature / n;
+}
+
+std::vector<double> NodeCurvatureCalculator::computeTangentDirections(
+    mcIdType nodeId,
+    const std::vector<mcIdType> &neighborIds) const
+{
+    size_t nbNeighbors = neighborIds.size();
+    std::vector<double> tangent(3 * nbNeighbors, 0.0);
+    for (size_t i = 0; i < nbNeighbors; ++i)
+    {
+        mcIdType neighborId = neighborIds[i];
+        double s = 0.0;
+        for (size_t j = 0; j < 3; j++)
+        {
+            tangent[3 * i + j] = nodes->coords->getIJ(neighborId, j) - nodes->coords->getIJ(nodeId, j);
+            s += tangent[3 * i + j] * nodes->normals[3 * nodeId + j];
+        }
+        double n = 0.0;
+        for (size_t j = 0; j < 3; j++)
+        {
+            tangent[3 * i + j] -= s * nodes->normals[3 * nodeId + j];
+            n += tangent[3 * i + j] * tangent[3 * i + j];
+        }
+        for (size_t j = 0; j < 3; j++)
+            tangent[3 * i + j] /= sqrt(n);
+    }
+    return tangent;
+}
diff --git a/src/ShapeRecogn/NodeCurvatureCalculator.hxx b/src/ShapeRecogn/NodeCurvatureCalculator.hxx
new file mode 100644 (file)
index 0000000..8b3a585
--- /dev/null
@@ -0,0 +1,60 @@
+// Copyright (C) 2007-2024  CEA, EDF
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+
+#ifndef __NODECURVATURECALCULATOR_HXX__
+#define __NODECURVATURECALCULATOR_HXX__
+
+#include <string>
+#include <vector>
+#include <array>
+#include "MEDCouplingUMesh.hxx"
+#include "PrimitiveType.hxx"
+
+namespace MEDCoupling
+{
+    class Nodes;
+
+    class NodeCurvatureCalculator
+    {
+    public:
+        NodeCurvatureCalculator(const MEDCouplingUMesh *);
+
+        Nodes *compute();
+
+    private:
+        void computeNormals();
+        void computeCurvatures(double tol = 0.000001);
+        void computeCurvatures(mcIdType nodeId, double tol);
+        PrimitiveType findPrimitiveType(double k1, double k2, double kdiff0, double kis0) const;
+        std::vector<double> computeNormalCurvatureCoefficients(
+            const std::vector<double> &discreteCurvatures,
+            const std::vector<double> &tangents,
+            const std::array<double, 3> &normal,
+            const std::array<double, 3> &e1) const;
+        void computeCellNormal(const std::vector<mcIdType> &nodeIds, std::array<double, 3> &cellNormal) const;
+        std::vector<double> computeDiscreteCurvatures(mcIdType nodeId, const std::vector<mcIdType> &neighborIds) const;
+        double computeDiscreteCurvature(mcIdType nodeId, mcIdType neighborId) const;
+        std::vector<double> computeTangentDirections(mcIdType nodeId, const std::vector<mcIdType> &neighborIds) const;
+
+        const MEDCouplingUMesh *mesh;
+        Nodes *nodes;
+    };
+};
+
+#endif //__NODECURVATURECALCULATOR_HXX__
\ No newline at end of file
diff --git a/src/ShapeRecogn/Nodes.cxx b/src/ShapeRecogn/Nodes.cxx
new file mode 100644 (file)
index 0000000..845e48b
--- /dev/null
@@ -0,0 +1,88 @@
+#include "Nodes.hxx"
+
+using namespace MEDCoupling;
+
+Nodes::Nodes(
+    const MEDCouplingUMesh *mesh,
+    const DataArrayInt64 *neighbors,
+    const DataArrayInt64 *neighborsIdx)
+    : mesh(mesh), coords(mesh->getCoords()), neighbors(neighbors), neighborsIdx(neighborsIdx)
+{
+}
+
+Nodes::~Nodes()
+{
+    mesh->decrRef();
+    neighbors->decrRef();
+    neighborsIdx->decrRef();
+}
+
+mcIdType Nodes::getNbNodes() const
+{
+    return coords->getNumberOfTuples();
+}
+
+const std::vector<double> &Nodes::getNormals() const
+{
+    return normals;
+}
+
+const std::vector<double> &Nodes::getWeakDirections() const
+{
+    return weakDirections;
+}
+
+const std::vector<double> &Nodes::getK1() const
+{
+    return k1;
+}
+
+std::array<double, 3> Nodes::getNormal(mcIdType nodeId) const
+{
+    return {normals[3 * nodeId], normals[3 * nodeId + 1], normals[3 * nodeId + 2]};
+}
+
+double Nodes::getK1(mcIdType nodeId) const
+{
+    return k1[nodeId];
+}
+
+double Nodes::getK2(mcIdType nodeId) const
+{
+    return k2[nodeId];
+}
+
+double Nodes::getKdiff0(mcIdType nodeId) const
+{
+    return kdiff0[nodeId];
+}
+
+const std::vector<mcIdType> Nodes::getNeighbors(mcIdType nodeId) const
+{
+    mcIdType start = neighborsIdx->getIJ(nodeId, 0);
+    mcIdType nbNeighbors = neighborsIdx->getIJ(nodeId + 1, 0) - start;
+
+    std::vector<mcIdType> neighborNodes(nbNeighbors, 0);
+    for (mcIdType i = 0; i < nbNeighbors; i++)
+        neighborNodes[i] = neighbors->getIJ(start + i, 0);
+    return neighborNodes;
+}
+
+PrimitiveType Nodes::getPrimitiveType(mcIdType nodeId) const
+{
+    return primitives[nodeId];
+}
+
+std::array<double, 3> Nodes::getWeakDirection(mcIdType nodeId) const
+{
+    return {weakDirections[3 * nodeId],
+            weakDirections[3 * nodeId + 1],
+            weakDirections[3 * nodeId + 2]};
+}
+
+std::array<double, 3> Nodes::getCoordinates(mcIdType nodeId) const
+{
+    std::array<double, 3> nodeCoords;
+    coords->getTuple(nodeId, nodeCoords.data());
+    return nodeCoords;
+}
diff --git a/src/ShapeRecogn/Nodes.hxx b/src/ShapeRecogn/Nodes.hxx
new file mode 100644 (file)
index 0000000..ed8d267
--- /dev/null
@@ -0,0 +1,71 @@
+// Copyright (C) 2007-2024  CEA, EDF
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+
+#ifndef __NODES_HXX__
+#define __NODES_HXX__
+
+#include <vector>
+#include "MEDCouplingUMesh.hxx"
+#include "PrimitiveType.hxx"
+
+namespace MEDCoupling
+{
+    class Nodes
+    {
+    public:
+        friend class NodeCurvatureCalculator;
+        Nodes(const MEDCouplingUMesh *mesh,
+              const DataArrayInt64 *neighbors,
+              const DataArrayInt64 *neighborsIdx);
+        ~Nodes();
+
+        mcIdType getNbNodes() const;
+        const std::vector<double> &getNormals() const;
+        const std::vector<double> &getWeakDirections() const;
+        const std::vector<double> &getK1() const;
+
+        std::array<double, 3> getNormal(mcIdType nodeId) const;
+        double getK1(mcIdType nodeId) const;
+        double getK2(mcIdType nodeId) const;
+        double getKdiff0(mcIdType nodeId) const;
+        const std::vector<mcIdType> getNeighbors(mcIdType nodeId) const;
+        PrimitiveType getPrimitiveType(mcIdType nodeId) const;
+        std::array<double, 3> getWeakDirection(mcIdType nodeId) const;
+        std::array<double, 3> getCoordinates(mcIdType nodeId) const;
+
+    private:
+        const MEDCouplingUMesh *mesh;
+        const DataArrayDouble *coords;
+        // normals 3 * nbNodes
+        std::vector<double> normals;
+        // neighbors
+        const DataArrayInt64 *neighbors;
+        const DataArrayInt64 *neighborsIdx;
+        // curvature
+        std::vector<bool> isConvex;
+        std::vector<double> theta0;
+        std::vector<double> k1;
+        std::vector<double> k2;
+        std::vector<double> kdiff0;
+        std::vector<double> weakDirections;
+        std::vector<PrimitiveType> primitives;
+    };
+};
+
+#endif //__NODES_HXX__
diff --git a/src/ShapeRecogn/PrimitiveType.hxx b/src/ShapeRecogn/PrimitiveType.hxx
new file mode 100644 (file)
index 0000000..b131da8
--- /dev/null
@@ -0,0 +1,93 @@
+// Copyright (C) 2007-2024  CEA, EDF
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+
+#ifndef __PRIMITIVETYPE_HXX__
+#define __PRIMITIVETYPE_HXX__
+
+#include <string>
+namespace MEDCoupling
+{
+    enum PrimitiveType
+    {
+        Plane = 0,
+        Sphere = 1,
+        Cylinder = 2,
+        Cone = 3,
+        Torus = 4,
+        Unknown = 5
+    };
+
+    inline std::string convertPrimitiveToString(PrimitiveType type)
+    {
+        std::string typeName = "";
+        switch (type)
+        {
+        case PrimitiveType::Plane:
+            typeName = "Plane";
+            break;
+        case PrimitiveType::Sphere:
+            typeName = "Sphere";
+            break;
+        case PrimitiveType::Cylinder:
+            typeName = "Cylinder";
+            break;
+        case PrimitiveType::Cone:
+            typeName = "Cone";
+            break;
+        case PrimitiveType::Torus:
+            typeName = "Torus";
+            break;
+        case PrimitiveType::Unknown:
+            typeName = "Unknown";
+            break;
+        default:
+            break;
+        }
+        return typeName;
+    };
+
+    inline int convertPrimitiveToInt(PrimitiveType type)
+    {
+        int typeInt = 5;
+        switch (type)
+        {
+        case PrimitiveType::Plane:
+            typeInt = 0;
+            break;
+        case PrimitiveType::Sphere:
+            typeInt = 1;
+            break;
+        case PrimitiveType::Cylinder:
+            typeInt = 2;
+            break;
+        case PrimitiveType::Cone:
+            typeInt = 3;
+            break;
+        case PrimitiveType::Torus:
+            typeInt = 4;
+            break;
+        case PrimitiveType::Unknown:
+        default:
+            break;
+        }
+        return typeInt;
+    };
+};
+
+#endif // __PRIMITIVETYPE_HXX__
\ No newline at end of file
diff --git a/src/ShapeRecogn/ShapeRecognMesh.cxx b/src/ShapeRecogn/ShapeRecognMesh.cxx
new file mode 100644 (file)
index 0000000..8dc73c4
--- /dev/null
@@ -0,0 +1,137 @@
+#include "ShapeRecognMesh.hxx"
+
+#include "NodeCurvatureCalculator.hxx"
+#include "AreasBuilder.hxx"
+#include "MEDLoader.hxx"
+#include "MEDCouplingFieldDouble.hxx"
+
+using namespace MEDCoupling;
+
+ShapeRecognMesh::ShapeRecognMesh(const std::string &fileName)
+{
+    mesh = ReadUMeshFromFile(fileName);
+    // TODO: check if the elements of the mesh are triangle ?
+}
+
+ShapeRecognMesh::~ShapeRecognMesh()
+{
+    if (areas != 0)
+        delete areas;
+    if (nodes != 0)
+        delete nodes;
+    mesh->decrRef();
+}
+
+const Nodes *ShapeRecognMesh::getNodes() const
+{
+    return nodes;
+}
+
+const Areas *ShapeRecognMesh::getAreas() const
+{
+    return areas;
+}
+
+void ShapeRecognMesh::recognize()
+{
+    mesh->incrRef();
+    NodeCurvatureCalculator nodeCurvatureCalc(mesh);
+    nodes = nodeCurvatureCalc.compute();
+    AreasBuilder areasBuilder(nodes);
+    areasBuilder.explore();
+    areasBuilder.expand();
+    areasBuilder.rebuild();
+    areas = areasBuilder.getAreas();
+    // for (mcIdType areaId = 0; areaId < areas->getNumberOfAreas(); ++areaId)
+    // {
+    //     std::cout << "area ("
+    //               << areaId
+    //               << ") : "
+    //               << areas->getPrimitiveTypeName(areaId)
+    //               << ", " << areas->getNumberOfNodes(areaId) << " "
+    //               << "[";
+    //     // for (mcIdType nodeId : areas->getNodeIds(areaId))
+    //     // {
+    //     //     std::cout << nodeId << ",";
+    //     // }
+    //     std::cout << "]\n";
+    // }
+}
+
+void ShapeRecognMesh::recognize(const std::string &outputFile)
+{
+    recognize();
+    // Nodes
+    // - k1
+    MEDCouplingFieldDouble *fieldNodeK1 = MEDCouplingFieldDouble::New(
+        ON_NODES);
+    fieldNodeK1->setName("node_k1");
+    fieldNodeK1->setMesh(mesh);
+    DataArrayDouble *nodeK1 = DataArrayDouble::New();
+    nodeK1->setName("node_k1");
+    nodeK1->alloc(nodes->getNbNodes(), 1);
+    for (mcIdType nodeId = 0; nodeId < nodes->getNbNodes(); ++nodeId)
+        nodeK1->setIJ(nodeId, 0, nodes->getK1(nodeId));
+    fieldNodeK1->setArray(nodeK1);
+    nodeK1->decrRef();
+    WriteField(outputFile, fieldNodeK1, true);
+    fieldNodeK1->decrRef();
+    // - k2
+    MEDCouplingFieldDouble *fieldNodeK2 = MEDCouplingFieldDouble::New(
+        ON_NODES);
+    fieldNodeK2->setName("node_k2");
+    fieldNodeK2->setMesh(mesh);
+    DataArrayDouble *nodeK2 = DataArrayDouble::New();
+    nodeK2->setName("node_k2");
+    nodeK2->alloc(nodes->getNbNodes(), 1);
+    for (mcIdType nodeId = 0; nodeId < nodes->getNbNodes(); ++nodeId)
+        nodeK2->setIJ(nodeId, 0, nodes->getK2(nodeId));
+    fieldNodeK2->setArray(nodeK2);
+    nodeK2->decrRef();
+    WriteField(outputFile, fieldNodeK2, false);
+    fieldNodeK2->decrRef();
+    // - primitive types
+    MEDCouplingFieldDouble *fieldNodePrimitiveTypes = MEDCouplingFieldDouble::New(
+        ON_NODES);
+    fieldNodePrimitiveTypes->setName("node_primitive_types");
+    fieldNodePrimitiveTypes->setMesh(mesh);
+    DataArrayDouble *nodePrimitiveTypes = DataArrayDouble::New();
+    nodePrimitiveTypes->setName("node_primitive_types");
+    nodePrimitiveTypes->alloc(nodes->getNbNodes(), 1);
+    for (mcIdType nodeId = 0; nodeId < nodes->getNbNodes(); ++nodeId)
+        nodePrimitiveTypes->setIJ(nodeId, 0, (double)convertPrimitiveToInt(nodes->getPrimitiveType(nodeId)));
+    fieldNodePrimitiveTypes->setArray(nodePrimitiveTypes);
+    nodePrimitiveTypes->decrRef();
+    WriteField(outputFile, fieldNodePrimitiveTypes, false);
+    fieldNodePrimitiveTypes->decrRef();
+    // Areas
+    const std::vector<mcIdType> &areaIdByNodes = areas->getAreaIdByNodes();
+    // - area id
+    MEDCouplingFieldDouble *fieldAreaIds = MEDCouplingFieldDouble::New(
+        ON_NODES);
+    fieldAreaIds->setName("area_ids");
+    fieldAreaIds->setMesh(mesh);
+    DataArrayDouble *areaIds = DataArrayDouble::New();
+    areaIds->setName("area_ids");
+    areaIds->alloc(nodes->getNbNodes(), 1);
+    for (mcIdType nodeId = 0; nodeId < areaIdByNodes.size(); ++nodeId)
+        areaIds->setIJ(nodeId, 0, (double)areaIdByNodes[nodeId]);
+    fieldAreaIds->setArray(areaIds);
+    areaIds->decrRef();
+    WriteField(outputFile, fieldAreaIds, false);
+    fieldAreaIds->decrRef();
+    // - primitive types
+    MEDCouplingFieldDouble *fieldAreaPrimitiveTypes = MEDCouplingFieldDouble::New(
+        ON_NODES);
+    fieldAreaPrimitiveTypes->setName("area_primitive_types");
+    fieldAreaPrimitiveTypes->setMesh(mesh);
+    DataArrayDouble *areaPrimitiveTypes = DataArrayDouble::New();
+    areaPrimitiveTypes->setName("area_primitive_types");
+    areaPrimitiveTypes->alloc(nodes->getNbNodes(), 1);
+    for (mcIdType nodeId = 0; nodeId < areaIdByNodes.size(); ++nodeId)
+        areaPrimitiveTypes->setIJ(nodeId, 0, (double)areas->getPrimitiveTypeInt(areaIdByNodes[nodeId]));
+    fieldAreaPrimitiveTypes->setArray(areaPrimitiveTypes);
+    areaPrimitiveTypes->decrRef();
+    WriteField(outputFile, fieldAreaPrimitiveTypes, false);
+    fieldAreaPrimitiveTypes->decrRef();
+}
diff --git a/src/ShapeRecogn/ShapeRecognMesh.hxx b/src/ShapeRecogn/ShapeRecognMesh.hxx
new file mode 100644 (file)
index 0000000..d4532e7
--- /dev/null
@@ -0,0 +1,51 @@
+// Copyright (C) 2007-2024  CEA, EDF
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+
+#ifndef __SHAPERECOGNMESH_HXX__
+#define __SHAPERECOGNMESH_HXX__
+
+#include <string>
+
+#include "MEDCouplingUMesh.hxx"
+
+namespace MEDCoupling
+{
+    class Nodes;
+    class Areas;
+
+    class ShapeRecognMesh
+    {
+    public:
+        ShapeRecognMesh(const std::string &fileName);
+        ~ShapeRecognMesh();
+
+        const Areas *getAreas() const;
+        const Nodes *getNodes() const;
+
+        void recognize();
+        void recognize(const std::string &outputFile);
+
+    private:
+        const MEDCouplingUMesh *mesh;
+        Nodes *nodes = 0;
+        Areas *areas = 0;
+    };
+};
+
+#endif // __SHAPERECOGNMESH_HXX__
diff --git a/src/ShapeRecogn/ShapeRecongConstants.hxx b/src/ShapeRecogn/ShapeRecongConstants.hxx
new file mode 100644 (file)
index 0000000..2c36f19
--- /dev/null
@@ -0,0 +1,41 @@
+// Copyright (C) 2007-2024  CEA, EDF
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+
+#ifndef __SHAPERECOGNCONSTANTS_HXX__
+#define __SHAPERECOGNCONSTANTS_HXX__
+
+namespace MEDCoupling
+{
+    // Nodes
+    constexpr double EPSILON_SC = 0.005; // For |k1 - k2|
+    constexpr double EPSILON_PL = 0.001; // For |k1 + k2| / 2 = 1 / R
+    constexpr double EPSILON_CY = 0.005; // For k1 or k2
+    // Areas
+    constexpr double EPSILON_PROP = 0.05;
+    constexpr double DELTA_PLANE = 0.01;
+    constexpr double DELTA_SPHERE = 0.1;
+    constexpr double DELTA_CYLINDER = 0.02;
+    constexpr double DELTA_CONE = 0.05;
+    constexpr double THETA_MAX_CYLINDER = 0.05;
+    constexpr double TOL_MATCH_SPHERE = 0.1;
+    constexpr int THRESHOLD_MIN_NB_NODES = 5;
+    constexpr int THRESHOLD_MAX_NB_AREAS = 500;
+};
+
+#endif //__SHAPERECOGNCONSTANTS_HXX__
\ No newline at end of file