Files
kicad-source-mirror/plugins/3d/vrml/wrlfacet.cpp
T
Seth Hillbrand 0b2d4d4879 Revise Copyright statement to align with TLF
Recommendation is to avoid using the year nomenclature as this
information is already encoded in the git repo.  Avoids needing to
repeatly update.

Also updates AUTHORS.txt from current repo with contributor names
2025-01-01 14:12:04 -08:00

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 The 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();
}