735 lines
26 KiB
C++
Executable File
735 lines
26 KiB
C++
Executable File
/****************************************************************************
|
|
* VCGLib o o *
|
|
* Visual and Computer Graphics Library o o *
|
|
* _ O _ *
|
|
* Copyright(C) 2004-2016 \/)\/ *
|
|
* Visual Computing Lab /\/| *
|
|
* ISTI - Italian National Research Council | *
|
|
* \ *
|
|
* All rights reserved. *
|
|
* *
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
* it under the terms of the GNU General Public License as published by *
|
|
* the Free Software Foundation; either version 2 of the License, or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
* This program 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 General Public License (http://www.gnu.org/licenses/gpl.txt) *
|
|
* for more details. *
|
|
* *
|
|
****************************************************************************/
|
|
#ifndef HALFEDGEQUADCLEAN_H
|
|
#define HALFEDGEQUADCLEAN_H
|
|
|
|
#include <vcg/complex/algorithms/update/halfedge_topology.h>
|
|
#include <queue>
|
|
#include <set>
|
|
#include<vcg/math/base.h>
|
|
#include<valarray>
|
|
#include<cmath>
|
|
|
|
|
|
namespace vcg
|
|
{
|
|
namespace tri
|
|
{
|
|
/*!
|
|
* \brief The class provides methods for detecting doublets and singlets and removing them
|
|
*
|
|
*/
|
|
|
|
template<class MeshType> class HalfedgeQuadClean
|
|
{
|
|
|
|
protected:
|
|
|
|
typedef typename MeshType::VertexPointer VertexPointer;
|
|
typedef typename MeshType::EdgePointer EdgePointer;
|
|
typedef typename MeshType::HEdgePointer HEdgePointer;
|
|
typedef typename MeshType::FacePointer FacePointer;
|
|
|
|
typedef typename MeshType::VertexIterator VertexIterator;
|
|
typedef typename MeshType::EdgeIterator EdgeIterator;
|
|
typedef typename MeshType::HEdgeIterator HEdgeIterator;
|
|
typedef typename MeshType::FaceIterator FaceIterator;
|
|
|
|
/*! Adds to a queue all faces on the 1-ring of a given vertex
|
|
*
|
|
* \param q Queue of faces
|
|
* \param vp Pointer to the vertex
|
|
*
|
|
*/
|
|
static void add_faces(queue<FacePointer> &q, VertexPointer vp)
|
|
{
|
|
vector<FacePointer> faces = HalfEdgeTopology<MeshType>::get_incident_faces(vp);
|
|
|
|
for(typename vector<FacePointer>::iterator fi = faces.begin(); fi != faces.end(); ++fi)
|
|
{
|
|
if(*fi)
|
|
q.push(*fi);
|
|
}
|
|
}
|
|
|
|
/*! Removes doublets from all the faces into a queue until the queue empties
|
|
*
|
|
* \param m Mesh
|
|
* \param faces Set of all faces modified by the removals
|
|
* \param q Queue of faces to clean
|
|
*
|
|
*/
|
|
static void remove_doublets(MeshType &m, set<FacePointer> &faces, queue<FacePointer> &q)
|
|
{
|
|
|
|
while(!q.empty())
|
|
{
|
|
FacePointer fp = q.front();
|
|
q.pop();
|
|
|
|
if( !fp->IsD() )
|
|
{
|
|
faces.insert(remove_doublet(m,fp, &q));
|
|
}
|
|
}
|
|
}
|
|
|
|
/*! Removes a doublet and all the other ones that the removal may have generated
|
|
*
|
|
* \param m Mesh
|
|
* \param fp Pointer to the face to clean from doublets
|
|
* \param Queue of faces to check for new doublets
|
|
*
|
|
* \return The new face generated after doublet removal
|
|
*/
|
|
static FacePointer remove_doublet(MeshType &m, FacePointer fp, queue<FacePointer> *q = NULL)
|
|
{
|
|
vector<HEdgePointer> hedges = HalfEdgeTopology<MeshType>::find_doublet_hedges_quad(fp);
|
|
|
|
assert(hedges.size() <= 4);
|
|
|
|
switch(hedges.size())
|
|
{
|
|
// No doublets
|
|
case 0:
|
|
return NULL;
|
|
|
|
// A single doublet
|
|
case 1:
|
|
if(q)
|
|
{
|
|
add_faces(*q, hedges[0]->HNp()->HVp());
|
|
|
|
add_faces(*q, hedges[0]->HPp()->HVp());
|
|
}
|
|
|
|
return HalfEdgeTopology<MeshType>::doublet_remove_quad(m, hedges[0]->HVp());
|
|
|
|
// Two doublets on the same face
|
|
case 2:
|
|
{
|
|
|
|
if(q)
|
|
{
|
|
add_faces(*q, hedges[0]->HNp()->HVp());
|
|
|
|
add_faces(*q, hedges[0]->HPp()->HVp());
|
|
}
|
|
|
|
FacePointer fp1 = HalfEdgeTopology<MeshType>::doublet_remove_quad(m, hedges[0]->HVp());
|
|
|
|
// Removal of the doublet may generate a singlet
|
|
if(HalfEdgeTopology<MeshType>::is_singlet_quad(fp1))
|
|
{
|
|
|
|
HEdgePointer hp = HalfEdgeTopology<MeshType>::singlet_remove_quad(m, fp1);
|
|
|
|
if(hp)
|
|
{
|
|
if(q)
|
|
{
|
|
if(hp->HFp())
|
|
q->push(hp->HFp());
|
|
if(hp->HOp()->HFp())
|
|
q->push(hp->HOp()->HFp());
|
|
}
|
|
|
|
int valence1, valence2;
|
|
|
|
valence1 = HalfEdgeTopology<MeshType>::vertex_valence(hp->HVp());
|
|
valence2 = HalfEdgeTopology<MeshType>::vertex_valence(hp->HOp()->HVp());
|
|
|
|
// Check if the removal of the singlet generated other singlets, then iteratively remove them
|
|
while(valence1 == 1 || valence2 == 1)
|
|
{
|
|
|
|
assert(! (valence1 == 1 && valence2 == 1));
|
|
|
|
FacePointer singlet_pointer;
|
|
|
|
if(valence1 == 1 )
|
|
singlet_pointer = hp->HFp();
|
|
else
|
|
singlet_pointer = hp->HOp()->HFp();
|
|
|
|
hp = HalfEdgeTopology<MeshType>::singlet_remove_quad(m, singlet_pointer);
|
|
|
|
if(!hp)
|
|
break;
|
|
|
|
if(q)
|
|
{
|
|
if(hp->HFp())
|
|
q->push(hp->HFp());
|
|
if(hp->HOp()->HFp())
|
|
q->push(hp->HOp()->HFp());
|
|
}
|
|
|
|
valence1 = HalfEdgeTopology<MeshType>::vertex_valence(hp->HVp());
|
|
valence2 = HalfEdgeTopology<MeshType>::vertex_valence(hp->HOp()->HVp());
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
return fp1;
|
|
}
|
|
|
|
// Four doublets: simply remove one of the two faces
|
|
case 4:
|
|
HalfEdgeTopology<MeshType>::remove_face(m,fp->FHp()->HOp()->HFp());
|
|
return fp;
|
|
|
|
default:
|
|
assert(0);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
/*! Removes doublets from all the faces on the 1-ring of the given vertices
|
|
*
|
|
* \param m Mesh
|
|
* \param Set of all faces modified by the removals
|
|
* \param vertices Vector of vertices that will be
|
|
*
|
|
*/
|
|
static void remove_doublets(MeshType &m, set<FacePointer> &faces, vector<VertexPointer> vertices)
|
|
{
|
|
queue<FacePointer> q;
|
|
|
|
for(typename vector<VertexPointer>::iterator vi = vertices.begin(); vi != vertices.end(); ++vi)
|
|
{
|
|
vector<FacePointer> inc_faces = HalfEdgeTopology<MeshType>::get_incident_faces(*vi);
|
|
|
|
for(typename vector<FacePointer>::iterator fi = inc_faces.begin(); fi != inc_faces.end(); ++fi)
|
|
{
|
|
if(*fi)
|
|
if( !((*fi)->IsD()) )
|
|
q.push(*fi);
|
|
}
|
|
}
|
|
|
|
remove_doublets(m, faces, q);
|
|
}
|
|
|
|
/*! Removes doublets from all the faces on the 1-ring of the given vertices
|
|
*
|
|
* \param m Mesh
|
|
* \param vertices Vector of vertices that will be
|
|
*
|
|
*/
|
|
static void remove_doublets(MeshType &m, vector<VertexPointer> vertices)
|
|
{
|
|
set<FacePointer> faces;
|
|
|
|
remove_doublets(m,faces,vertices);
|
|
}
|
|
|
|
/*! Removes doublets from a set of faces
|
|
*
|
|
* \param m Mesh
|
|
* \param set of faces to clean
|
|
*
|
|
*/
|
|
static void remove_doublets(MeshType &m, set<FacePointer> &faces)
|
|
{
|
|
|
|
queue<FacePointer> q;
|
|
|
|
for(typename set<FacePointer>::iterator fi = faces.begin(); fi != faces.end(); ++fi)
|
|
{
|
|
if(*fi)
|
|
if( !((*fi)->IsD()) )
|
|
q.push(*fi);
|
|
}
|
|
|
|
remove_doublets(m, faces, q);
|
|
|
|
}
|
|
|
|
|
|
/*! Removes all doublets from a mesh
|
|
*
|
|
* \param m Mesh
|
|
*
|
|
* \return Number of doublets removed
|
|
*/
|
|
static int remove_doublets(MeshType &m)
|
|
{
|
|
int count;
|
|
int removed = 0;
|
|
do
|
|
{
|
|
count = 0;
|
|
for(FaceIterator fi = m.face.begin(); fi != m.face.end(); ++fi)
|
|
{
|
|
if( !((*fi).IsD()) )
|
|
{
|
|
if(remove_doublet(m, &(*fi)))
|
|
count++;
|
|
}
|
|
}
|
|
|
|
removed += count;
|
|
|
|
}while(count != 0);
|
|
|
|
return removed;
|
|
}
|
|
|
|
/*! Removes all singlets from a mesh
|
|
*
|
|
* \param m Mesh
|
|
*
|
|
* \return Number of singlets removed
|
|
*/
|
|
static int remove_singlets(MeshType &m)
|
|
{
|
|
int removed = 0;
|
|
int count;
|
|
do
|
|
{
|
|
count = 0;
|
|
for(FaceIterator fi = m.face.begin(); fi != m.face.end(); ++fi)
|
|
{
|
|
if( !((*fi).IsD()) )
|
|
{
|
|
if( HalfEdgeTopology<MeshType>::is_singlet_quad(&(*fi)) )
|
|
{
|
|
HalfEdgeTopology<MeshType>::singlet_remove_quad(m, &(*fi));
|
|
count++;
|
|
}
|
|
}
|
|
}
|
|
|
|
removed += count;
|
|
|
|
}while(count != 0);
|
|
|
|
return removed;
|
|
}
|
|
|
|
/*! Checks if a mesh has singlets
|
|
*
|
|
* \param m Mesh
|
|
*
|
|
* \return Value indicating whether mesh has singlets
|
|
*/
|
|
static bool has_singlets(MeshType &m)
|
|
{
|
|
|
|
for(FaceIterator fi = m.face.begin(); fi != m.face.end(); ++fi)
|
|
{
|
|
if( !((*fi).IsD()) )
|
|
{
|
|
if( HalfEdgeTopology<MeshType>::is_singlet_quad(&(*fi)) )
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*! Checks if a mesh has doublets
|
|
*
|
|
* \param m Mesh
|
|
*
|
|
* \return Value indicating whether mesh has doublets
|
|
*/
|
|
static bool has_doublets(MeshType &m)
|
|
{
|
|
|
|
for(FaceIterator fi = m.face.begin(); fi != m.face.end(); ++fi)
|
|
{
|
|
if( !((*fi).IsD()) )
|
|
{
|
|
if( HalfEdgeTopology<MeshType>::has_doublet_quad(&(*fi)) )
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*! Performs rotation of selected edges computing the best rotation
|
|
*
|
|
*
|
|
* \param m Mesh
|
|
* \param hedges Vector of halfedges representing the edges to rotate
|
|
* \param faces Set of modified faces (every modified face will be inserted into this set)
|
|
*
|
|
*/
|
|
template<class PriorityType>
|
|
static void flip_edges(MeshType &m, vector<HEdgePointer> &hedges, set<FacePointer> &faces)
|
|
{
|
|
|
|
for(typename vector<HEdgePointer>::iterator hi = hedges.begin(); hi != hedges.end(); ++hi)
|
|
{
|
|
// edge must be shared by two faces
|
|
if((*hi)->HFp() && (*hi)->HOp()->HFp())
|
|
{
|
|
if(!(*hi)->IsD() && !(*hi)->HFp()->IsD() && !(*hi)->HOp()->HFp()->IsD())
|
|
{
|
|
typename PriorityType::FlipType fliptype = PriorityType::best_flip( *hi );
|
|
|
|
if(fliptype != PriorityType::NO_FLIP)
|
|
{
|
|
|
|
vector<VertexPointer> vertices;
|
|
|
|
// Record old vertices for future removal of doublets
|
|
vertices.push_back((*hi)->HVp());
|
|
vertices.push_back((*hi)->HOp()->HVp());
|
|
|
|
// Add modified faces into the set of faces
|
|
faces.insert((*hi)->HFp());
|
|
faces.insert((*hi)->HOp()->HFp());
|
|
|
|
bool cw = (fliptype == PriorityType::CW_FLIP);
|
|
HalfEdgeTopology<MeshType>::edge_rotate_quad((*hi),cw);
|
|
|
|
// After a rotation doublets may have generated
|
|
remove_doublets(m, faces, vertices);
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*! Performs edge rotations on the entire mesh computing the best rotation for each edge
|
|
*
|
|
*
|
|
* \param m Mesh
|
|
*
|
|
*/
|
|
template <class PriorityType>
|
|
static int flip_edges(MeshType &m)
|
|
{
|
|
int count;
|
|
int ret=0;
|
|
do
|
|
{
|
|
count = 0;
|
|
for(typename MeshType::EdgeIterator ei = m.edge.begin(); ei != m.edge.end(); ++ei)
|
|
{
|
|
|
|
if( !(ei->IsD()) )
|
|
{
|
|
HEdgePointer hp = ei->EHp();
|
|
|
|
if(hp->HFp() && hp->HOp()->HFp())
|
|
{
|
|
typename PriorityType::FlipType fliptype = PriorityType::best_flip( hp );
|
|
|
|
if(fliptype != PriorityType::NO_FLIP)
|
|
{
|
|
vector<VertexPointer> vertices;
|
|
|
|
// Record old vertices for future removal of doublets
|
|
vertices.push_back(hp->HVp());
|
|
vertices.push_back(hp->HOp()->HVp());
|
|
|
|
bool cw = (fliptype == PriorityType::CW_FLIP);
|
|
HalfEdgeTopology<MeshType>::edge_rotate_quad(hp,cw);
|
|
|
|
// After a rotation doublets may have generated
|
|
remove_doublets(m, vertices);
|
|
|
|
count++;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
ret+=count;
|
|
|
|
}while(count != 0);
|
|
|
|
return ret;
|
|
}
|
|
|
|
};
|
|
|
|
|
|
/*!
|
|
* \brief Generic priority for edge rotations
|
|
*
|
|
*/
|
|
template <class MeshType> class EdgeFlipPriority
|
|
{
|
|
public:
|
|
typedef typename MeshType::HEdgePointer HEdgePointer;
|
|
|
|
/// Possible types of rotation
|
|
enum FlipType { NO_FLIP, CW_FLIP, CCW_FLIP};
|
|
|
|
/*!
|
|
* Computes the best rotation to perform
|
|
*
|
|
* \param hp Pointer to an halfedge representing the edge to rotate
|
|
*
|
|
* \return The best type of rotation
|
|
*/
|
|
static FlipType best_flip( HEdgePointer hp);
|
|
};
|
|
|
|
/*!
|
|
* \brief Priority based on maximizing vertices regularity
|
|
*
|
|
*/
|
|
template <class MeshType> class VertReg: public EdgeFlipPriority<MeshType>
|
|
{
|
|
|
|
public:
|
|
|
|
typedef typename MeshType::VertexPointer VertexPointer;
|
|
typedef typename MeshType::EdgePointer EdgePointer;
|
|
typedef typename MeshType::HEdgePointer HEdgePointer;
|
|
typedef typename MeshType::FacePointer FacePointer;
|
|
|
|
typedef EdgeFlipPriority<MeshType> Base;
|
|
typedef typename Base::FlipType FlipType;
|
|
|
|
/// Default Constructor
|
|
VertReg(){}
|
|
|
|
~VertReg(){}
|
|
|
|
/*!
|
|
* Computes the best rotation to perform for maximizing vertices regularity
|
|
*
|
|
* \param hp Pointer to an halfedge representing the edge to rotate
|
|
*
|
|
* \return The best type of rotation
|
|
*/
|
|
static FlipType best_flip( HEdgePointer hp)
|
|
{
|
|
assert(hp);
|
|
assert(!hp->IsD());
|
|
|
|
vector<VertexPointer> vps1 = HalfEdgeTopology<MeshType>::getVertices(hp->HFp(), hp);
|
|
|
|
vector<VertexPointer> vps2 = HalfEdgeTopology<MeshType>::getVertices(hp->HOp()->HFp(), hp->HOp());
|
|
|
|
valarray<double> valences(6);
|
|
|
|
/*
|
|
Indices of vertices into vector valences:
|
|
|
|
3-------2
|
|
| |
|
|
| f1 |
|
|
| |
|
|
0-------1
|
|
| |
|
|
| f2 |
|
|
| |
|
|
4-------5
|
|
|
|
*/
|
|
|
|
// Compute valencies of vertices
|
|
for(int i=0; i<4; i++)
|
|
valences[i] = HalfEdgeTopology<MeshType>::vertex_valence(vps1[i]) - 4 ;
|
|
|
|
valences[4] = HalfEdgeTopology<MeshType>::vertex_valence(vps2[2]) - 4;
|
|
valences[5] = HalfEdgeTopology<MeshType>::vertex_valence(vps2[3]) - 4;
|
|
|
|
|
|
// Vector containing sums of the valencies in all the possible cases: no rotation, ccw rotation, cw rotation
|
|
vector<int> sums;
|
|
|
|
// positions:
|
|
// sums[0]: now (No rotation)
|
|
// sums[1]: flipping ccw
|
|
// sums[2]: flipping cw
|
|
|
|
// No rotation
|
|
sums.push_back( pow(valences, 2.0).sum() );
|
|
|
|
// CCW
|
|
valences[0]--;
|
|
valences[1]--;
|
|
valences[2]++;
|
|
valences[4]++;
|
|
|
|
sums.push_back( pow(valences, 2.0).sum() );
|
|
|
|
// CW
|
|
valences[2]--;
|
|
valences[4]--;
|
|
valences[3]++;
|
|
valences[5]++;
|
|
|
|
sums.push_back( pow(valences, 2.0).sum() );
|
|
|
|
|
|
if( sums[2]<= sums[1] && sums[2]< sums[0] )
|
|
return Base::CW_FLIP;
|
|
|
|
else if( sums[1]< sums[2] && sums[1]< sums[0] )
|
|
return Base::CCW_FLIP;
|
|
|
|
return Base::NO_FLIP;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
/*!
|
|
* \brief Priority based on minimizing homeometry
|
|
*
|
|
*/
|
|
template <class MeshType> class Homeometry: public EdgeFlipPriority<MeshType>
|
|
{
|
|
|
|
public:
|
|
|
|
typedef typename MeshType::VertexPointer VertexPointer;
|
|
typedef typename MeshType::EdgePointer EdgePointer;
|
|
typedef typename MeshType::HEdgePointer HEdgePointer;
|
|
typedef typename MeshType::FacePointer FacePointer;
|
|
|
|
|
|
typedef EdgeFlipPriority<MeshType> Base;
|
|
typedef typename Base::FlipType FlipType;
|
|
|
|
/// Default Constructor
|
|
Homeometry(){}
|
|
|
|
~Homeometry(){}
|
|
|
|
/*!
|
|
* Computes the best rotation to perform for minimizing the distance from homeometry
|
|
*
|
|
* \param hp Pointer to an halfedge representing the edge to rotate
|
|
*
|
|
* \return The best type of rotation
|
|
*/
|
|
static FlipType best_flip( HEdgePointer hp)
|
|
{
|
|
assert(hp);
|
|
assert(!hp->IsD());
|
|
|
|
vector<VertexPointer> face1 = HalfEdgeTopology<MeshType>::getVertices(hp->HFp(), hp);
|
|
vector<VertexPointer> face2 = HalfEdgeTopology<MeshType>::getVertices(hp->HOp()->HFp(), hp->HOp());
|
|
|
|
// Vector containing sums of the valencies in all the possible cases: no rotation, ccw rotation, cw rotation
|
|
vector<int> sums;
|
|
|
|
// positions:
|
|
// sums[0]: now (No rotation)
|
|
// sums[1]: flipping ccw
|
|
// sums[2]: flipping cw
|
|
|
|
// No rotation
|
|
sums.push_back( distance_from_homeometry(face1, face2, 0) );
|
|
|
|
// CCW
|
|
face1[1] = face2[2];
|
|
face2[1] = face1[2];
|
|
|
|
sums.push_back( distance_from_homeometry(face1, face2, 1) );
|
|
|
|
// CW
|
|
face1[2] = face2[3];
|
|
face2[2] = face1[3];
|
|
|
|
sums.push_back( distance_from_homeometry(face1, face2, 2) );
|
|
|
|
|
|
if( sums[2]<= sums[1] && sums[2]< sums[0] )
|
|
return Base::CW_FLIP;
|
|
|
|
else if( sums[1]< sums[2] && sums[1]< sums[0] )
|
|
return Base::CCW_FLIP;
|
|
|
|
return Base::NO_FLIP;
|
|
|
|
}
|
|
|
|
protected:
|
|
|
|
/*!
|
|
* Computes the area of a quad
|
|
*
|
|
* \param vertices Vector of the four vertices of the quad
|
|
*
|
|
* \return Area of the quad
|
|
*/
|
|
static float area(vector<VertexPointer> &vertices)
|
|
{
|
|
|
|
assert(vertices.size() == 4);
|
|
|
|
float tri1 = Norm( (vertices[1]->cP() - vertices[0]->cP()) ^ (vertices[2]->cP() - vertices[0]->cP()) );
|
|
float tri2 = Norm( (vertices[2]->cP() - vertices[0]->cP()) ^ (vertices[3]->cP() - vertices[0]->cP()) );
|
|
|
|
return (tri1+tri2) / 2;
|
|
}
|
|
|
|
/*!
|
|
* Computes the distance of two faces from being homeometirc
|
|
*
|
|
* \param face1 Vector of vertices belonging to the first face
|
|
* \param face2 Vector of vertices belonging to the second face
|
|
* \param i Index of the edge to compute
|
|
*
|
|
* \return The computed homeometry
|
|
*/
|
|
static float distance_from_homeometry(vector<VertexPointer> &face1, vector<VertexPointer> &face2, int i)
|
|
{
|
|
// Ideal edge length
|
|
float mu = sqrt( (area(face1) + area(face2)) / 2 );
|
|
|
|
// Length of the i-th edge (the edge changed with a rotation)
|
|
float edge_length = Distance( face1[i]->cP(), face1[i+1]->cP() );
|
|
|
|
// Length of the diagonals
|
|
valarray<float> diagonals(4);
|
|
|
|
diagonals[0] = Distance( face1[0]->cP(), face1[2]->cP() );
|
|
diagonals[1] = Distance( face1[1]->cP(), face1[3]->cP() );
|
|
diagonals[2] = Distance( face2[0]->cP(), face2[2]->cP() );
|
|
diagonals[3] = Distance( face2[1]->cP(), face2[3]->cP() );
|
|
|
|
// Ideal diagonal length
|
|
float ideal_diag_length = SQRT_TWO*mu;
|
|
|
|
float sum_diagonals = pow(diagonals - ideal_diag_length, 2.0).sum();
|
|
|
|
return (pow (edge_length - mu , static_cast<float>(2.0)) + sum_diagonals);
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
}
|
|
#endif // HALFEDGEQUADCLEAN_H
|