Commit 48be6753 authored by Jan Möbius's avatar Jan Möbius

Splitted Boundary snapping into seperate class


git-svn-id: http://www.openflipper.org/svnrepo/OpenFlipper/branches/Free@15718 383ad7c9-94d9-4d36-a494-682f7c89f535
parent 06a240b2
/*===========================================================================*\
* *
* OpenMesh *
* Copyright (C) 2001-2011 by Computer Graphics Group, RWTH Aachen *
* www.openmesh.org *
* *
*---------------------------------------------------------------------------*
* This file is part of OpenMesh. *
* *
* OpenMesh 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 3 of *
* the License, or (at your option) any later version with the *
* following exceptions: *
* *
* If other files instantiate templates or use macros *
* or inline functions from this file, or you compile this file and *
* link it with other files to produce an executable, this file does *
* not by itself cause the resulting executable to be covered by the *
* GNU Lesser General Public License. This exception does not however *
* invalidate any other reasons why the executable file might be *
* covered by the GNU Lesser General Public License. *
* *
* OpenMesh 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 LesserGeneral Public *
* License along with OpenMesh. If not, *
* see <http://www.gnu.org/licenses/>. *
* *
\*===========================================================================*/
/*===========================================================================*\
* *
* $Revision: 685 $ *
* $Date: 2012-09-19 18:15:39 +0200 (Mi, 19 Sep 2012) $ *
* *
\*===========================================================================*/
/** \file BoundarySnappingT.cc
*/
//=============================================================================
//
// CLASS MeshFixing - IMPLEMENTATION
//
//=============================================================================
#define BOUNDARYSNAPPING_CC
//== INCLUDES =================================================================
#include "BoundarySnappingT.hh"
//== NAMESPACE ===============================================================
//== IMPLEMENTATION ==========================================================
template<class MeshT>
BoundarySnappingT<MeshT>::BoundarySnappingT(MeshT& _mesh ) :
mesh_(_mesh)
{
}
template<typename MeshT>
bool sort_less_pair_second(const std::pair<typename MeshT::VertexHandle,double> &lhs,const std::pair<typename MeshT::VertexHandle,double> &rhs)
{
return lhs.second < rhs.second;
}
template<class MeshT>
void BoundarySnappingT<MeshT>::snap(double _epsilon)
{
std::vector<typename MeshT::VertexHandle> v_boundary;
// collect all boundary vertices
for (typename MeshT::VertexIter v_iter = mesh_.vertices_begin(); v_iter != mesh_.vertices_end(); ++v_iter)
if (mesh_.is_boundary(v_iter) && mesh_.status(v_iter).selected())
v_boundary.push_back(v_iter);
//two maps
// vertexDistMap saves the vertex and his distances as pairs
// vertexVertexMap saves the vertices of a vertex in range and the distances
std::vector< std::pair<typename MeshT::VertexHandle,double> > vertexDistMap;
std::map<typename MeshT::VertexHandle,std::vector<std::pair<typename MeshT::VertexHandle,double> > > vertexVertexMap;
// get all boundary vertices in range and save them into the maps
for (typename std::vector< typename MeshT::VertexHandle >::iterator oIter = v_boundary.begin(); oIter != v_boundary.end(); ++oIter)
{
typename MeshT::Point pos = mesh_.point(*oIter);
if (!mesh_.status(*oIter).deleted())
{
std::vector< std::pair<typename MeshT::VertexHandle,double> > verticesInRange;
//collect all vertices in range
for (typename std::vector<typename MeshT::VertexHandle>::iterator cIter = v_boundary.begin(); cIter != v_boundary.end(); ++cIter)
{
if ( !mesh_.status(*cIter).deleted() && cIter != oIter)
{
double dist = (pos - mesh_.point(*cIter)).length();
if ( dist <= _epsilon )
verticesInRange.push_back(std::make_pair(*cIter,dist));
}
}
// sort them, so nearest vertex is on position 0 (if exist)
if (!verticesInRange.empty())
{
std::sort( verticesInRange.begin(),verticesInRange.end(),&sort_less_pair_second<MeshT> );
vertexDistMap.push_back(std::make_pair(*oIter,verticesInRange[0].second));
vertexVertexMap[*oIter] = verticesInRange;
}
}
}
bool finished = false;
while(!finished)
{
finished = true;
double min = _epsilon;
typename MeshT::VertexHandle v_old;//will be replaced by v_new
typename MeshT::VertexHandle v_new;
typename std::vector<std::pair<typename MeshT::VertexHandle,double> >::iterator v_oldIter = vertexDistMap.end();
typename std::vector<std::pair<typename MeshT::VertexHandle,double> >::iterator v_newIter;
// find next min pair
for (typename std::vector<std::pair<typename MeshT::VertexHandle,double> >::iterator vd_iter = vertexDistMap.begin(); vd_iter != vertexDistMap.end(); ++vd_iter)
{
typename MeshT::VertexHandle v_1 = vd_iter->first;
if (v_1.is_valid() && !mesh_.status(v_1).deleted() && vertexVertexMap.find(v_1) != vertexVertexMap.end())
{
typename MeshT::VertexHandle v_2;
std::vector<std::pair<typename MeshT::VertexHandle,double> >& verticesInRange = vertexVertexMap[v_1];
bool validPair = false;
for (typename std::vector<std::pair<typename MeshT::VertexHandle,double> >::iterator iter = verticesInRange.begin(); iter != verticesInRange.end(); ++iter)
{
//check if v_2 shares a face with v_1
//if so, it is not usable
v_2 = iter->first;
for(typename MeshT::VertexFaceIter vf_iter = mesh_.vf_begin(v_1); vf_iter && v_2.is_valid(); ++vf_iter)
for (typename MeshT::FaceVertexIter fv_iter = mesh_.fv_begin(vf_iter.handle()); fv_iter && v_2.is_valid(); ++fv_iter)
if (fv_iter.handle() == v_2)
v_2 = typename MeshT::VertexHandle();
validPair = v_2.is_valid() && !mesh_.status(v_2).deleted() && mesh_.is_boundary(v_2);
//if v_2 is valid, save it, or erase it if not, because v_2 will not be valid in the future
if (validPair && iter->second <= min)
{
//new min pair found
min = iter->second;
v_old = v_1;
v_new = v_2;
finished = false;
v_oldIter = vd_iter;
v_newIter = iter;
}
}
}
}
// merge, if not finished (pair found)
if (!finished)
{
//remove the vertex since we will proceed with it
vertexVertexMap[v_old].erase(v_newIter);
//save all faces, because faces will be added/deleted
std::vector<typename MeshT::FaceHandle> faces;
for (typename MeshT::VertexFaceIter vf_iter = mesh_.vf_begin(v_old); vf_iter; ++vf_iter)
if (!mesh_.status(vf_iter).deleted())
faces.push_back(vf_iter);
//replace v_old with v_new by creating new faces with v_new instead of v_old if possible
for (typename std::vector<typename MeshT::FaceHandle>::iterator f_iter = faces.begin(); f_iter !=faces.end(); ++f_iter)
{
typename MeshT::FaceHandle fHandle = *f_iter;
if (!fHandle.is_valid() || mesh_.status(fHandle).deleted())
continue;
//get face vertices
std::vector<typename MeshT::VertexHandle> f_vertices;
for(typename MeshT::FaceVertexIter fv_iter = mesh_.fv_begin(fHandle); fv_iter; ++fv_iter)
f_vertices.push_back( fv_iter.handle() );
mesh_.delete_face(fHandle,false);
//try to add new face
std::vector<typename MeshT::VertexHandle> newFace_vertices(f_vertices);
std::replace(newFace_vertices.begin(),newFace_vertices.end(),v_old,v_new);
typename MeshT::FaceHandle faceH = mesh_.add_face(newFace_vertices);
if (!faceH.is_valid())
{
//failed, try reverse direction
std::reverse(newFace_vertices.begin(),newFace_vertices.end());
faceH = mesh_.add_face(newFace_vertices);
if (!faceH.is_valid())
{
//failed, so add the old one
mesh_.add_face(f_vertices);
}
}
}
}
vertexDistMap.erase(v_oldIter);
//todo: delete vertex before proceed. Now, they will be deleted at the end resulting worse snap
}
mesh_.delete_isolated_vertices();
mesh_.garbage_collection();
}
/*===========================================================================*\
* *
* OpenMesh *
* Copyright (C) 2001-2011 by Computer Graphics Group, RWTH Aachen *
* www.openmesh.org *
* *
*---------------------------------------------------------------------------*
* This file is part of OpenMesh. *
* *
* OpenMesh 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 3 of *
* the License, or (at your option) any later version with the *
* following exceptions: *
* *
* If other files instantiate templates or use macros *
* or inline functions from this file, or you compile this file and *
* link it with other files to produce an executable, this file does *
* not by itself cause the resulting executable to be covered by the *
* GNU Lesser General Public License. This exception does not however *
* invalidate any other reasons why the executable file might be *
* covered by the GNU Lesser General Public License. *
* *
* OpenMesh 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 LesserGeneral Public *
* License along with OpenMesh. If not, *
* see <http://www.gnu.org/licenses/>. *
* *
\*===========================================================================*/
/*===========================================================================*\
* *
* $Revision: 653 $ *
* $Date: 2012-08-22 10:49:55 +0200 (Mi, 22 Aug 2012) $ *
* *
\*===========================================================================*/
#ifndef BOUNDARYSNAPPING_HH
#define BOUNDARYSNAPPING_HH
//=============================================================================
//
// CLASS BoundarySnappingT
//
//=============================================================================
//== INCLUDES =================================================================
//== NAMESPACE ================================================================
//== CLASS DEFINITION =========================================================
/** \brief Snaps selected vertices at boundaries
*
* Snaps selected boundary vertices together if they are closer than the given
* distance. No new vertices will be introduced on either edge, so they are just
* snapped to existing ones.
*
*/
template<class MeshT>
class BoundarySnappingT {
public:
BoundarySnappingT(MeshT& _mesh );
/** \brief snaps boundary vertices
*
* snaps selected boundary vertices where the vertices
* distance is not greater than the given distance
*
* @param _epsilon max Distance between 2 boundary vertices
*
*/
void snap(double _epsilon);
private:
MeshT& mesh_;
};
#if defined(INCLUDE_TEMPLATES) && !defined(BOUNDARYSNAPPING_CC)
#define BOUNDARYSNAPPING_TEMPLATES
#include "BoundarySnappingT.cc"
#endif
#endif
......@@ -47,7 +47,34 @@
#include "MeshRepairPlugin.hh"
#include "MeshFixingT.hh"
#include "NonManifoldVertexFixingT.hh"
#include "BoundarySnappingT.hh"
//-----------------------------------------------------------------------------
void MeshRepairPlugin::snapBoundary(int _objectId, double _eps)
{
TriMesh* triMesh = 0;
PolyMesh* polyMesh = 0;
PluginFunctions::getMesh(_objectId, triMesh);
PluginFunctions::getMesh(_objectId, polyMesh);
if (triMesh) {
BoundarySnappingT<TriMesh> snapper(*triMesh);
snapper.snap(_eps);
}
else if (polyMesh) {
BoundarySnappingT<PolyMesh> snapper(*polyMesh);
snapper.snap(_eps);
} else
{
emit log(LOGERR, tr("Unsupported Object Type."));
return;
}
emit updatedObject(_objectId, UPDATE_ALL);
emit createBackup(_objectId, "snapBoundary", UPDATE_ALL);
emit scriptInfo("snapBoundary(" + QString::number(_objectId) + ", " + QString::number(_eps) +")");
}
//-----------------------------------------------------------------------------
......
......@@ -93,46 +93,6 @@ These vertices can usually be removed without destroying the features of the mes
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_13">
<item>
<widget class="QPushButton" name="snapBoundaryButton">
<property name="toolTip">
<string>Snaps selected boundary vertices together.</string>
</property>
<property name="text">
<string>Snap Boundary</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_10">
<property name="text">
<string>max. Distance</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="snapBoundarySpinBox">
<property name="decimals">
<number>6</number>
</property>
<property name="minimum">
<double>0.500000000000000</double>
</property>
<property name="singleStep">
<double>0.100000000000000</double>
</property>
<property name="value">
<double>0.500000000000000</double>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
......@@ -682,6 +642,46 @@ There is no automatic algorithm to fix these foldovers here. So you will have to
<string>General Operations</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_7">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_13">
<item>
<widget class="QPushButton" name="snapBoundaryButton">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Snaps selected boundary vertices together if they are closer than the given distance. No new vertices will be introduced on either edge, so they are just snapped to existing ones.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Snap Boundary</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_10">
<property name="text">
<string>max. Distance</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="snapBoundarySpinBox">
<property name="decimals">
<number>6</number>
</property>
<property name="minimum">
<double>0.500000000000000</double>
</property>
<property name="singleStep">
<double>0.100000000000000</double>
</property>
<property name="value">
<double>0.500000000000000</double>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPushButton" name="fixNonManifoldVerticesButton">
<property name="toolTip">
......
......@@ -44,8 +44,6 @@
#include "MeshRepairPlugin.hh"
#include <iostream>
#include <OpenFlipper/BasePlugin/PluginFunctions.hh>
#include "OpenFlipper/INIFile/INIFile.hh"
......@@ -389,29 +387,6 @@ void MeshRepairPlugin::pluginsInitialized() {
// Scriptable functions
//===========================================================================
void MeshRepairPlugin::snapBoundary(int _objectId, double _eps)
{
TriMesh* triMesh = 0;
PolyMesh* polyMesh = 0;
PluginFunctions::getMesh(_objectId, triMesh);
PluginFunctions::getMesh(_objectId, polyMesh);
if (triMesh)
snapBoundary(triMesh,_eps);
else if (polyMesh)
snapBoundary(polyMesh,_eps);
else
{
emit log(LOGERR, tr("Unsupported Object Type."));
return;
}
emit updatedObject(_objectId, UPDATE_ALL);
emit createBackup(_objectId, "snapBoundary", UPDATE_ALL);
emit scriptInfo("snapBoundary(" + QString::number(_objectId) + ", " + QString::number(_eps) +")");
}
void MeshRepairPlugin::removeSelectedVal3Vertices(int _objectId) {
......
......@@ -194,13 +194,9 @@ public slots:
/// Detect triangle with aspect of _aspect and select candidates
void detectTriangleAspect(int _objectId, float _aspect);
/// Flips the normals of all faces by changing the vertex order
void flipOrientation(int _objectId);
/// Selects all edges of an object which are shorter than the given length
void selectEdgesShorterThan(int _objectId,double _length);
......@@ -210,7 +206,8 @@ public slots:
/// Detect valence 3 vertices with faces that lie in the plane of their adjacent triangles
void detectFlatValence3Vertices(int _objectId, double _angle);
void snapBoundary(int _objectId, double _eps);
......@@ -222,16 +219,28 @@ public slots:
// Normal recomputations
// ==================================================
/// Recomputes the face normals of an object
/** \brief Recomputes the face normals of an object
*
* @param _objectId Id of the object
*/
void updateFaceNormals(int _objectId);
/// Recomputes the halfedge normals of an object
/** \brief Recomputes the halfedge normals of an object
*
* @param _objectId Id of the object
*/
void updateHalfedgeNormals(int _objectId);
/// Recomputes the vertex normals of an object
/** \brief Recomputes the vertex normals of an object
*
* @param _objectId Id of the object
*/
void updateVertexNormals(int _objectId);
/// Recomputes the face and vertex normals of an object
/** \brief Recomputes the face and vertex normals of an object
*
* @param _objectId Id of the object
*/
void updateNormals(int _objectId);
......@@ -239,6 +248,9 @@ public slots:
// General
// ==================================================
void snapBoundary(int _objectId, double _eps);
/** \brief remove non-manifold vertices by duplicating them
*
* @param _objectId Id of the mesh to fix
......@@ -283,20 +295,6 @@ private:
template<typename MeshT>
inline unsigned n_verticesPerFace();
/** \brief snaps boundary vertices
*
* snaps selected boundary vertices where the vertices
* distance is not greater than a given distance
* @param _mesh target mesh
* @param _eps max Distance between 2 boundary vertices
*
*/
template<typename MeshT>
void snapBoundary(MeshT *_mesh, double _eps);
template<typename MeshT>
static bool sort_less_pair_second(const std::pair<typename MeshT::VertexHandle,double> &lhs,const std::pair<typename MeshT::VertexHandle,double> &rhs);
public slots:
QString version() {
return QString("1.2");
......
......@@ -95,161 +95,3 @@ void MeshRepairPlugin::flipOrientationSelected(MeshT *_mesh)
//-----------------------------------------------------------------------------
template<typename MeshT>
bool MeshRepairPlugin::sort_less_pair_second(const std::pair<typename MeshT::VertexHandle,double> &lhs,const std::pair<typename MeshT::VertexHandle,double> &rhs)
{
return lhs.second < rhs.second;
}
//-----------------------------------------------------------------------------
template<typename MeshT>
void MeshRepairPlugin::snapBoundary(MeshT *_mesh, double _eps)
{
std::vector<typename MeshT::VertexHandle> v_boundary;
//collect all boundary vertices
for (typename MeshT::VertexIter v_iter = _mesh->vertices_begin(); v_iter != _mesh->vertices_end(); ++v_iter)
if (_mesh->is_boundary(v_iter) && _mesh->status(v_iter).selected())
v_boundary.push_back(v_iter);
//two maps
//vertexDistMap saves the vertex and his distanceas a pair
//vertexVertexMap saves the vertices of a vertex in range and the distances
std::vector< std::pair<typename MeshT::VertexHandle,double> > vertexDistMap;
std::map<typename MeshT::VertexHandle,std::vector<std::pair<typename MeshT::VertexHandle,double> > > vertexVertexMap;
//get all boundary vertices in range and save them into the maps
for (typename std::vector< typename MeshT::VertexHandle >::iterator oIter = v_boundary.begin(); oIter != v_boundary.end(); ++oIter)
{
typename MeshT::Point pos = _mesh->point(*oIter);
if (!_mesh->status(*oIter).deleted())
{
std::vector< std::pair<typename MeshT::VertexHandle,double> > verticesInRange;
//collect all vertices in range
for (typename std::vector<typename MeshT::VertexHandle>::iterator cIter = v_boundary.begin(); cIter != v_boundary.end(); ++cIter)
{
if ( !_mesh->status(*cIter).deleted() && cIter != oIter)
{
double dist = (pos - _mesh->point(*cIter)).length();
if ( dist <= _eps )
verticesInRange.push_back(std::make_pair(*cIter,dist));
}
}
//sort them, so nearest vertex is on position 0 (if exist)
if (!verticesInRange.empty())
{
std::sort(verticesInRange.begin(),verticesInRange.end(),sort_less_pair_second<MeshT>);
vertexDistMap.push_back(std::make_pair(*oIter,verticesInRange[0].second));
vertexVertexMap[*oIter] = verticesInRange;
}
}
}
bool finished = false;
while(!finished)
{
finished = true;
double min = _eps;
typename MeshT::VertexHandle v_old;//will be replaced by v_new
typename MeshT::VertexHandle v_new;
typename std::vector<std::pair<typename MeshT::VertexHandle,double> >::iterator v_oldIter = vertexDistMap.end();
typename std::vector<std::pair<typename MeshT::VertexHandle,double> >::iterator v_newIter;
//find next min pair
for (typename std::vector<std::pair<typename MeshT::VertexHandle,double> >::iterator vd_iter = vertexDistMap.begin(); vd_iter != vertexDistMap.end(); ++vd_iter)
{
typename MeshT::VertexHandle v_1 = vd_iter->first;
if (v_1.is_valid() && !_mesh->status(v_1).deleted() && vertexVertexMap.find(v_1) != vertexVertexMap.end())
{
typename MeshT::VertexHandle v_2;
std::vector<std::pair<typename MeshT::VertexHandle,double> >& verticesInRange = vertexVertexMap[v_1];
bool validPair = false;
for (typename std::vector<std::pair<typename MeshT::VertexHandle,double> >::iterator iter = verticesInRange.begin(); iter != verticesInRange.end(); ++iter)
{
//check if v_2 shares a face with v_1
//if so, it is not usable
v_2 = iter->first;
for(typename MeshT::VertexFaceIter vf_iter = _mesh->vf_begin(v_1); vf_iter && v_2.is_valid(); ++vf_iter)
for (typename MeshT::FaceVertexIter fv_iter = _mesh->fv_begin(vf_iter.handle()); fv_iter && v_2.is_valid(); ++fv_iter)
if (fv_iter.handle() == v_2)
v_2 = typename MeshT::VertexHandle();
validPair = v_2.is_valid() && !_mesh->status(v_2).deleted() && _mesh->is_boundary(v_2);
//if v_2 is valid, save it, or erase it if not, because v_2 will not be valid in the future
if (validPair && iter->second <= min)
{
//new min pair found
min = iter->second;
v_old = v_1;
v_new = v_2;
finished = false;
v_oldIter = vd_iter;
v_newIter = iter;
}
}
}
}
//merge, if not finished (pair found)
if (!finished)
{
//remove the vertex since we will proceed with it
vertexVertexMap[v_old].erase(v_newIter);
//save all faces, because faces will be added/deleted
std::vector<typename MeshT::FaceHandle> faces;
for (typename MeshT::VertexFaceIter vf_iter = _mesh->vf_begin(v_old); vf_iter; ++vf_iter)
if (!_mesh->status(vf_iter).deleted())
faces.push_back(vf_iter);
//replace v_old with v_new by creating new faces with v_new instead of v_old if possible
for (typename std::vector<typename MeshT::FaceHandle>::iterator f_iter = faces.begin(); f_iter !=faces.end(); ++f_iter)
{
typename MeshT::FaceHandle fHandle = *f_iter;
if (!fHandle.is_valid() || _mesh->status(fHandle).deleted())
continue;
//get face vertices
std::vector<typename MeshT::VertexHandle> f_vertices;
for(typename MeshT::FaceVertexIter fv_iter = _mesh->fv_begin(fHandle); fv_iter; ++fv_iter)
f_vertices.push_back( fv_iter.handle() );
_mesh->delete_face(fHandle,false);
//try to add new face
std::vector<typename MeshT::VertexHandle> newFace_vertices(f_vertices);
std::replace(newFace_vertices.begin(),newFace_vertices.end(),v_old,v_new);
typename MeshT::FaceHandle faceH = _mesh->add_face(newFace_vertices);
if (!faceH.is_valid())
{
//failed, try reverse direction
std::reverse(newFace_vertices.begin(),newFace_vertices.end());
faceH = _mesh->add_face(newFace_vertices);
if (!faceH.is_valid())
{
//failed, so add the old one
_mesh->add_face(f_vertices);
}
}
}
}
vertexDistMap.erase(v_oldIter);
//todo: delete vertex before proceed. Now, they will be deleted at the end resulting worse snap
}
_mesh->delete_isolated_vertices();
_mesh->garbage_collection();
}
//-----------------------------------------------------------------------------