kicad/plugins/3d/vrml/wrlfacet.cpp

846 lines
20 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2016-2017 Cirilo Bernardo <cirilo.bernardo@gmail.com>
* Copyright (C) 2021 KiCad Developers, see AUTHORS.txt for contributors.
*
* 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 for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you may find one here:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* or you may search the http://www.gnu.org website for the version 2 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#define GLM_FORCE_RADIANS
#include <glm/glm.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <cmath>
#include "wrlfacet.h"
#define LOWER_LIMIT (1e-12)
static bool VDegenerate( glm::vec3* pts )
{
// note: only checks the degenerate case of zero length sides; it
// does not detect the case of 3 distinct collinear points
double dx, dy, dz;
dx = double{ pts[1].x } - pts[0].x;
dy = double{ pts[1].y } - pts[0].y;
dz = double{ pts[1].z } - pts[0].z;
if( ( dx*dx + dy*dy + dz*dz ) < LOWER_LIMIT )
return true;
dx = double{ pts[2].x } - pts[0].x;
dy = double{ pts[2].y } - pts[0].y;
dz = double{ pts[2].z } - pts[0].z;
if( ( dx*dx + dy*dy + dz*dz ) < LOWER_LIMIT )
return true;
dx = double{ pts[2].x } - pts[1].x;
dy = double{ pts[2].y } - pts[1].y;
dz = double{ pts[2].z } - pts[1].z;
if( ( dx*dx + dy*dy + dz*dz ) < LOWER_LIMIT )
return true;
return false;
}
static WRLVEC3F VCalcTriNorm( const WRLVEC3F& p1, const WRLVEC3F& p2, const WRLVEC3F& p3 )
{
// note: p1 = reference vertex
glm::vec3 tri = glm::vec3( 0.0, 0.0, 0.0 );
glm::vec3 pts[3];
pts[0] = p1;
pts[1] = p2;
pts[2] = p3;
// degenerate points are given a default 0, 0, 0 normal
if( VDegenerate( pts ) )
return tri;
// normal
tri = glm::cross( pts[2] - pts[0], pts[1] - pts[0] );
float dn = sqrtf( tri.x * tri.x + tri.y * tri.y + tri.z * tri.z );
if( dn > LOWER_LIMIT )
{
tri.x /= dn;
tri.y /= dn;
tri.z /= dn;
}
return tri;
}
static float VCalcCosAngle( const WRLVEC3F& p1, const WRLVEC3F& p2, const WRLVEC3F& p3 )
{
// note: p1 = reference vertex
float l12, l13;
float dx, dy, dz;
dx = p2.x - p1.x;
dy = p2.y - p1.y;
dz = p2.z - p1.z;
float p12 = dx*dx + dy*dy + dz*dz;
l12 = sqrtf( p12 );
dx = p3.x - p2.x;
dy = p3.y - p2.y;
dz = p3.z - p2.z;
float p23 = dx*dx + dy*dy + dz*dz;
dx = p3.x - p1.x;
dy = p3.y - p1.y;
dz = p3.z - p1.z;
float p13 = dx*dx + dy*dy + dz*dz;
l13 = sqrtf( p13 );
float dn = 2.0f * l12 * l13;
// place a limit to prevent calculations from blowing up
if( dn < LOWER_LIMIT )
{
if( ( p12 + p13 - p23 ) < FLT_EPSILON )
return -1.0f;
if( ( p12 + p13 - p23 ) > FLT_EPSILON )
return 1.0f;
return 0.0f;
}
float cosAngle = ( p12 + p13 - p23 ) / dn;
// check the domain; errors in the cosAngle calculation can result in domain errors
if( cosAngle > 1.0f )
cosAngle = 1.0f;
else if( cosAngle < -1.0f )
cosAngle = -1.0f;
// note: we are guaranteed that acosf() is never negative
return cosAngle;
}
FACET::FACET()
{
face_normal.x = 0.0;
face_normal.y = 0.0;
face_normal.z = 0.0;
maxIdx = 0;
}
void FACET::Init()
{
vertices.clear();
colors.clear();
indices.clear();
norms.clear();
vnweight.clear();
face_normal.x = 0.0;
face_normal.y = 0.0;
face_normal.z = 0.0;
maxIdx = 0;
}
bool FACET::HasMinPoints()
{
if( vertices.size() < 3 )
return false;
return true;
}
bool FACET::HasColors()
{
if( colors.empty() )
return false;
return true;
}
void FACET::AddVertex( WRLVEC3F& aVertex, int aIndex )
{
if( aIndex < 0 )
return;
vertices.push_back( aVertex );
indices.push_back( aIndex );
if( aIndex > maxIdx )
maxIdx = aIndex;
}
void FACET::AddColor( const SGCOLOR& aColor )
{
colors.push_back( aColor );
return;
}
float FACET::CalcFaceNormal()
{
// note: this calculation assumes that the face is a convex polygon;
// concave polygons may be supported in the future via functions which
// split the polygon into triangles
if( vertices.size() < 3 )
return 0.0;
// check if the values were already calculated
if( vertices.size() == vnweight.size() )
return 0.0;
WRLVEC3F lCPts[3];
std::vector< WRLVEC3F >::iterator sV = vertices.begin();
std::vector< WRLVEC3F >::iterator eV = vertices.end();
lCPts[0] = vertices.back();
lCPts[1] = *sV;
++sV;
lCPts[2] = *sV;
++sV;
face_normal = VCalcTriNorm( lCPts[1], lCPts[0], lCPts[2] );
vnweight.clear();
WRLVEC3F wnorm = face_normal;
// calculate area:
size_t nv = vertices.size();
float a1 = 0.0;
glm::vec3 sum( 0.0, 0.0, 0.0 );
size_t j = 0;
for( size_t i = 1; i < nv; ++i, ++j )
sum += glm::cross( vertices[j], vertices[i] );
a1 = fabs( glm::dot( face_normal, sum ) );
float a2 = acosf( VCalcCosAngle( lCPts[1], lCPts[0], lCPts[2] ) );
wnorm.x *= a1 * a2;
wnorm.y *= a1 * a2;
wnorm.z *= a1 * a2;
vnweight.push_back( wnorm );
float maxV = fabs( wnorm.x );
float tV = fabs( wnorm.y );
if( tV > maxV )
maxV = tV;
tV = fabs( wnorm.z );
if( tV > maxV )
maxV = tV;
while( sV != eV )
{
lCPts[0] = lCPts[1];
lCPts[1] = lCPts[2];
lCPts[2] = *sV;
++sV;
wnorm = face_normal;
a2 = acosf( VCalcCosAngle( lCPts[1], lCPts[0], lCPts[2] ) );
wnorm.x *= a1 * a2;
wnorm.y *= a1 * a2;
wnorm.z *= a1 * a2;
vnweight.push_back( wnorm );
tV = fabs( wnorm.x );
if( tV > maxV )
maxV = tV;
tV = fabs( wnorm.y );
if( tV > maxV )
maxV = tV;
tV = fabs( wnorm.z );
if( tV > maxV )
maxV = tV;
}
lCPts[0] = lCPts[1];
lCPts[1] = lCPts[2];
lCPts[2] = vertices.front();
wnorm = face_normal;
a2 = acosf( VCalcCosAngle( lCPts[1], lCPts[0], lCPts[2] ) );
wnorm.x *= a1 * a2;
wnorm.y *= a1 * a2;
wnorm.z *= a1 * a2;
vnweight.push_back( wnorm );
tV = fabs( wnorm.x );
if( tV > maxV )
maxV = tV;
tV = fabs( wnorm.y );
if( tV > maxV )
maxV = tV;
tV = fabs( wnorm.z );
if( tV > maxV )
maxV = tV;
return maxV;
}
void FACET::CalcVertexNormal( int aIndex, std::list< FACET* > &aFacetList, float aCreaseLimit )
{
if( vertices.size() < 3 )
return;
if( vnweight.size() != vertices.size() )
return;
if( norms.size() != vertices.size() )
norms.resize( vertices.size() );
std::vector< int >::iterator sI = indices.begin();
std::vector< int >::iterator eI = indices.end();
int idx = 0;
WRLVEC3F fp[2]; // vectors to calculate facet angle
fp[0].x = 0.0;
fp[0].y = 0.0;
fp[0].z = 0.0;
while( sI != eI )
{
if( *sI == aIndex )
{
// first set the default (weighted) normal value
norms[idx] = vnweight[idx];
// iterate over adjacent facets
std::list< FACET* >::iterator sF = aFacetList.begin();
std::list< FACET* >::iterator eF = aFacetList.end();
while( sF != eF )
{
if( this == *sF )
{
++sF;
continue;
}
// check the crease angle limit
(*sF)->GetFaceNormal( fp[1] );
float thrs = VCalcCosAngle( fp[0], face_normal, fp[1] );
if( aCreaseLimit <= thrs && (*sF)->GetWeightedNormal( aIndex, fp[1] ) )
{
norms[idx].x += fp[1].x;
norms[idx].y += fp[1].y;
norms[idx].z += fp[1].z;
}
++sF;
}
// normalize the vector
float dn = sqrtf( norms[idx].x * norms[idx].x
+ norms[idx].y * norms[idx].y
+ norms[idx].z * norms[idx].z );
if( dn > LOWER_LIMIT )
{
norms[idx].x /= dn;
norms[idx].y /= dn;
norms[idx].z /= dn;
}
// if the normals is an invalid normal this test will pass
if( fabs( norms[idx].x ) < 0.5
&& fabs( norms[idx].y ) < 0.5
&& fabs( norms[idx].z ) < 0.5 )
{
norms[idx] = face_normal;
}
return;
}
++idx;
++sI;
}
}
bool FACET::GetWeightedNormal( int aIndex, WRLVEC3F& aNorm )
{
// the default weighted normal shall have no effect even if accidentally included
aNorm.x = 0.0;
aNorm.y = 0.0;
aNorm.z = 0.0;
if( vertices.size() < 3 )
return false;
if( vnweight.size() != vertices.size() )
return false;
std::vector< int >::iterator sI = indices.begin();
std::vector< int >::iterator eI = indices.end();
int idx = 0;
while( sI != eI )
{
if( *sI == aIndex )
{
aNorm = vnweight[idx];
return true;
}
++idx;
++sI;
}
return false;
}
bool FACET::GetFaceNormal( WRLVEC3F& aNorm )
{
aNorm.x = 0.0;
aNorm.y = 0.0;
aNorm.z = 0.0;
if( vertices.size() < 3 )
return false;
if( vnweight.size() != vertices.size() )
return false;
aNorm = face_normal;
return true;
}
bool FACET::GetData( std::vector< WRLVEC3F >& aVertexList, std::vector< WRLVEC3F >& aNormalsList,
std::vector< SGCOLOR >& aColorsList, WRL1_ORDER aVertexOrder )
{
// if no normals are calculated we simply return
if( norms.empty() )
return false;
// the output must always be triangle sets in order to conform to the
// requirements of the SG* classes
int idx[3];
idx[0] = 0;
idx[1] = 1;
idx[2] = 2;
WRLVEC3F tnorm;
if( aVertexOrder != WRL1_ORDER::ORD_CLOCKWISE )
{
aVertexList.push_back( vertices[idx[0]] );
aVertexList.push_back( vertices[idx[1]] );
aVertexList.push_back( vertices[idx[2]] );
aNormalsList.push_back( norms[idx[0]] );
aNormalsList.push_back( norms[idx[1]] );
aNormalsList.push_back( norms[idx[2]] );
}
if( aVertexOrder != WRL1_ORDER::ORD_CCW )
{
aVertexList.push_back( vertices[idx[0]] );
aVertexList.push_back( vertices[idx[2]] );
aVertexList.push_back( vertices[idx[1]] );
tnorm = norms[idx[0]];
tnorm.x = -tnorm.x;
tnorm.y = -tnorm.y;
tnorm.z = -tnorm.z;
aNormalsList.push_back( tnorm );
tnorm = norms[idx[2]];
tnorm.x = -tnorm.x;
tnorm.y = -tnorm.y;
tnorm.z = -tnorm.z;
aNormalsList.push_back( tnorm );
tnorm = norms[idx[1]];
tnorm.x = -tnorm.x;
tnorm.y = -tnorm.y;
tnorm.z = -tnorm.z;
aNormalsList.push_back( tnorm );
}
bool hasColor = false;
bool perVC = false; // per-vertex colors?
if( !colors.empty() )
{
hasColor = true;
if( colors.size() >= vertices.size() )
perVC = true;
if( perVC )
{
if( aVertexOrder != WRL1_ORDER::ORD_CLOCKWISE )
{
aColorsList.push_back( colors[idx[0]] );
aColorsList.push_back( colors[idx[1]] );
aColorsList.push_back( colors[idx[2]] );
}
if( aVertexOrder != WRL1_ORDER::ORD_CCW )
{
aColorsList.push_back( colors[idx[0]] );
aColorsList.push_back( colors[idx[2]] );
aColorsList.push_back( colors[idx[1]] );
}
}
else
{
if( aVertexOrder != WRL1_ORDER::ORD_CLOCKWISE )
{
aColorsList.push_back( colors[0] );
aColorsList.push_back( colors[0] );
aColorsList.push_back( colors[0] );
}
if( aVertexOrder != WRL1_ORDER::ORD_CCW )
{
aColorsList.push_back( colors[0] );
aColorsList.push_back( colors[0] );
aColorsList.push_back( colors[0] );
}
}
}
int lim = (int) vertices.size() - 1;
while( idx[2] < lim )
{
idx[1] = idx[2];
++idx[2];
if( aVertexOrder != WRL1_ORDER::ORD_CLOCKWISE )
{
aVertexList.push_back( vertices[idx[0]] );
aVertexList.push_back( vertices[idx[1]] );
aVertexList.push_back( vertices[idx[2]] );
aNormalsList.push_back( norms[idx[0]] );
aNormalsList.push_back( norms[idx[1]] );
aNormalsList.push_back( norms[idx[2]] );
}
if( aVertexOrder != WRL1_ORDER::ORD_CCW )
{
aVertexList.push_back( vertices[idx[0]] );
aVertexList.push_back( vertices[idx[2]] );
aVertexList.push_back( vertices[idx[1]] );
tnorm = norms[idx[0]];
tnorm.x = -tnorm.x;
tnorm.y = -tnorm.y;
tnorm.z = -tnorm.z;
aNormalsList.push_back( tnorm );
tnorm = norms[idx[2]];
tnorm.x = -tnorm.x;
tnorm.y = -tnorm.y;
tnorm.z = -tnorm.z;
aNormalsList.push_back( tnorm );
tnorm = norms[idx[1]];
tnorm.x = -tnorm.x;
tnorm.y = -tnorm.y;
tnorm.z = -tnorm.z;
aNormalsList.push_back( tnorm );
}
if( hasColor )
{
if( perVC )
{
if( aVertexOrder != WRL1_ORDER::ORD_CLOCKWISE )
{
aColorsList.push_back( colors[idx[0]] );
aColorsList.push_back( colors[idx[1]] );
aColorsList.push_back( colors[idx[2]] );
}
if( aVertexOrder != WRL1_ORDER::ORD_CCW )
{
aColorsList.push_back( colors[idx[0]] );
aColorsList.push_back( colors[idx[2]] );
aColorsList.push_back( colors[idx[1]] );
}
}
else
{
if( aVertexOrder != WRL1_ORDER::ORD_CLOCKWISE )
{
aColorsList.push_back( colors[0] );
aColorsList.push_back( colors[0] );
aColorsList.push_back( colors[0] );
}
if( aVertexOrder != WRL1_ORDER::ORD_CCW )
{
aColorsList.push_back( colors[0] );
aColorsList.push_back( colors[0] );
aColorsList.push_back( colors[0] );
}
}
}
}
return true;
}
void FACET::CollectVertices( std::vector< std::list< FACET* > >& aFacetList )
{
// check if this facet may contribute anything at all
if( vertices.size() < 3 )
return;
// note: in principle this should never be invoked
if( (maxIdx + 1) >= (int)aFacetList.size() )
aFacetList.resize( static_cast<std::size_t>( maxIdx ) + 1 );
std::vector< int >::iterator sI = indices.begin();
std::vector< int >::iterator eI = indices.end();
while( sI != eI )
{
aFacetList[*sI].push_back( this );
++sI;
}
}
void FACET::Renormalize( float aMaxValue )
{
if( vnweight.empty() || aMaxValue < LOWER_LIMIT )
return;
size_t vs = vnweight.size();
for( size_t i = 0; i < vs; ++i )
{
vnweight[i].x /= aMaxValue;
vnweight[i].y /= aMaxValue;
vnweight[i].z /= aMaxValue;
}
}
SHAPE::~SHAPE()
{
std::list< FACET* >::iterator sF = facets.begin();
std::list< FACET* >::iterator eF = facets.end();
while( sF != eF )
{
delete *sF;
++sF;
}
facets.clear();
return;
}
FACET* SHAPE::NewFacet()
{
FACET* fp = new FACET;
facets.push_back( fp );
return fp;
}
SGNODE* SHAPE::CalcShape( SGNODE* aParent, SGNODE* aColor, WRL1_ORDER aVertexOrder,
float aCreaseLimit, bool isVRML2 )
{
if( facets.empty() || !facets.front()->HasMinPoints() )
return nullptr;
std::vector< std::list< FACET* > > flist;
// determine the max. index and size flist as appropriate
std::list< FACET* >::iterator sF = facets.begin();
std::list< FACET* >::iterator eF = facets.end();
int maxIdx = 0;
int tmi;
float maxV = 0.0;
float tV = 0.0;
while( sF != eF )
{
tV = ( *sF )->CalcFaceNormal();
tmi = ( *sF )->GetMaxIndex();
if( tmi > maxIdx )
maxIdx = tmi;
if( tV > maxV )
maxV = tV;
++sF;
}
++maxIdx;
if( maxIdx < 3 )
return nullptr;
flist.resize( maxIdx );
// create the lists of facets common to indices
sF = facets.begin();
while( sF != eF )
{
( *sF )->Renormalize( tV );
( *sF )->CollectVertices( flist );
++sF;
}
// calculate the normals
size_t vs = flist.size();
for( size_t i = 0; i < vs; ++i )
{
sF = flist[i].begin();
eF = flist[i].end();
while( sF != eF )
{
( *sF )->CalcVertexNormal( static_cast<int>( i ), flist[i], aCreaseLimit );
++sF;
}
}
std::vector< WRLVEC3F > vertices;
std::vector< WRLVEC3F > normals;
std::vector< SGCOLOR > colors;
// push the facet data to the final output list
sF = facets.begin();
eF = facets.end();
while( sF != eF )
{
( *sF )->GetData( vertices, normals, colors, aVertexOrder );
++sF;
}
flist.clear();
if( vertices.size() < 3 )
return nullptr;
IFSG_SHAPE shapeNode( false );
if( !isVRML2 )
{
shapeNode.NewNode( aParent );
if( aColor )
{
if( nullptr == S3D::GetSGNodeParent( aColor ) )
shapeNode.AddChildNode( aColor );
else
shapeNode.AddRefNode( aColor );
}
}
std::vector< SGPOINT > lCPts; // vertex points in SGPOINT (double) format
std::vector< SGVECTOR > lCNorm; // per-vertex normals
vs = vertices.size();
for( size_t i = 0; i < vs; ++i )
{
SGPOINT pt;
pt.x = vertices[i].x;
pt.y = vertices[i].y;
pt.z = vertices[i].z;
lCPts.push_back( pt );
lCNorm.emplace_back( normals[i].x, normals[i].y, normals[i].z );
}
vertices.clear();
normals.clear();
IFSG_FACESET fsNode( false );
if( !isVRML2 )
fsNode.NewNode( shapeNode );
else
fsNode.NewNode( aParent );
IFSG_COORDS cpNode( fsNode );
cpNode.SetCoordsList( lCPts.size(), &lCPts[0] );
IFSG_COORDINDEX ciNode( fsNode );
for( int i = 0; i < (int)lCPts.size(); ++i )
ciNode.AddIndex( i );
IFSG_NORMALS nmNode( fsNode );
nmNode.SetNormalList( lCNorm.size(), &lCNorm[0] );
if( !colors.empty() )
{
IFSG_COLORS nmColor( fsNode );
nmColor.SetColorList( colors.size(), &colors[0] );
colors.clear();
}
if( !isVRML2 )
return shapeNode.GetRawPtr();
return fsNode.GetRawPtr();
}