Commit c08efb86 authored by Hans-Christian Ebke's avatar Hans-Christian Ebke
Browse files

Merge remote-tracking branch 'origin/master' into cpp11

parents 0a32f589 a87a8e5a
.project
.cproject
......@@ -593,7 +593,7 @@ DrawMeshT<Mesh>::rebuild()
meshComp_->setVertices(mesh_.n_vertices(), mesh_.points(), 24, false, GL_DOUBLE, 3);
// normals
if (mesh_.has_halfedge_normals())
if (halfedgeNormalMode_ && mesh_.has_halfedge_normals())
meshComp_->setNormals(mesh_.n_halfedges(), mesh_.property(mesh_.halfedge_normals_pph()).data(), 24, false, GL_DOUBLE, 3);
else if (mesh_.has_vertex_normals())
meshComp_->setNormals(mesh_.n_vertices(), mesh_.vertex_normals(), 24, false, GL_DOUBLE, 3);
......
This diff is collapsed.
......@@ -235,8 +235,12 @@ protected:
class ACGDLLEXPORT MeshCompilerVertexCompare
{
public:
MeshCompilerVertexCompare(double _d_eps = 1e-4, float _f_eps = 1e-4f) : d_eps_(_d_eps), f_eps_(_f_eps) {}
virtual bool equalVertex(const void* v0, const void* v1, const VertexDeclaration* _decl);
const double d_eps_;
const float f_eps_;
};
class ACGDLLEXPORT MeshCompiler
......@@ -544,7 +548,7 @@ public:
/** Get index buffer ready for rendering.
*/
int* getIndexBuffer() const {return indices_;}
const int* getIndexBuffer() const {return indices_.data();}
/** \brief Get index buffer with adjacency information ready for rendering.
*
......@@ -655,7 +659,7 @@ public:
*/
int mapToDrawVertexID(const int _faceID, const int _cornerID) const;
/** Mapping from input Face id -> draw vertex id
/** Mapping from input Face id -> draw triangle id
*
* @param _faceID Face ID in input data
* @param _k triangle no. associated to face, offset 0
......@@ -805,7 +809,6 @@ private:
bool deleteFaceInputeData_; // delete if face input data internally created
std::vector<int> faceBufSplit_; // mapping from (faceID, cornerID) to interleaved vertex id after splitting
std::vector<int> faceRotCount_; // # ccw rotations per face, handled internally by getInputIndexOffset
std::vector<int> faceSortMap_; // face IDs sorted by group; maps sortFaceID -> FaceID
int provokingVertex_; // provoking vertex of each triangle
bool provokingVertexSetByUser_; // was the provoking vertex selected by user or set to default?
......@@ -974,7 +977,7 @@ private:
int numIsolatedVerts_;
/// index buffer
int* indices_;
std::vector<int> indices_;
private:
......@@ -985,7 +988,7 @@ private:
int mapTriToInputFace(const int _tri) const;
int getInputIndexOffset(const int _face, const int _corner, const bool _rotation = true) const;
int getInputIndexOffset(const int _face, const int _corner) const;
inline int getInputFaceOffset(const int _face) const
{
......@@ -1011,6 +1014,9 @@ private:
// convert n-poly -> tris (triangle fans)
void triangulate();
// resolve triangulation
void resolveTriangulation();
// sort input faces by group ids
void sortFacesByGroup();
......@@ -1065,6 +1071,15 @@ public:
std::string checkInputData() const;
};
struct RingTriangle
{
RingTriangle() {}
RingTriangle(int i, RingTriangle* p, RingTriangle* n) : id(i), prev(p), next(n) {}
int id; // local index of the triangle within a polygon [0, ... faceSize-3]
RingTriangle* prev; // prev triangle in the ring
RingTriangle* next; // next triangle in the ring
};
}
......@@ -352,6 +352,46 @@ QString RenderObject::toString() const
resultStrm << "\ninternalFlags: " << internalFlags_;
// textures
for (std::map<size_t, Texture>::const_iterator it = textures_.begin(); it != textures_.end(); ++it)
{
resultStrm << "\ntexture unit " << it->first << ": ";
switch (it->second.type)
{
case GL_TEXTURE_1D: resultStrm << "GL_TEXTURE_1D"; break;
case GL_TEXTURE_2D: resultStrm << "GL_TEXTURE_2D"; break;
case GL_TEXTURE_3D: resultStrm << "GL_TEXTURE_3D"; break;
#ifdef GL_TEXTURE_RECTANGLE
case GL_TEXTURE_RECTANGLE: resultStrm << "GL_TEXTURE_RECTANGLE"; break;
#endif
#ifdef GL_TEXTURE_CUBE_MAP
case GL_TEXTURE_CUBE_MAP: resultStrm << "GL_TEXTURE_CUBE_MAP"; break;
#endif
#ifdef GL_TEXTURE_1D_ARRAY
case GL_TEXTURE_1D_ARRAY: resultStrm << "GL_TEXTURE_1D_ARRAY"; break;
#endif
#ifdef GL_TEXTURE_2D_ARRAY
case GL_TEXTURE_2D_ARRAY: resultStrm << "GL_TEXTURE_2D_ARRAY"; break;
#endif
#ifdef GL_TEXTURE_CUBE_MAP_ARRAY
case GL_TEXTURE_CUBE_MAP_ARRAY: resultStrm << "GL_TEXTURE_CUBE_MAP_ARRAY"; break;
#endif
#ifdef GL_TEXTURE_BUFFER
case GL_TEXTURE_BUFFER: resultStrm << "GL_TEXTURE_BUFFER"; break;
#endif
#ifdef GL_TEXTURE_2D_MULTISAMPLE
case GL_TEXTURE_2D_MULTISAMPLE: resultStrm << "GL_TEXTURE_2D_MULTISAMPLE"; break;
#endif
#ifdef GL_TEXTURE_2D_MULTISAMPLE_ARRAY
case GL_TEXTURE_2D_MULTISAMPLE_ARRAY: resultStrm << "GL_TEXTURE_2D_MULTISAMPLE_ARRAY"; break;
#endif
default: resultStrm << "unknown_type"; break;
}
resultStrm << " - id " << it->second.id;
}
if (vertexDecl)
resultStrm << "\n" << vertexDecl->toString();
......
......@@ -199,7 +199,7 @@ void ShaderGenerator::initVertexShaderIO(const ShaderGenDesc* _desc, const Defau
// assume 2d texcoords as default
int texdim = 2;
if (_desc->texGenMode && _desc->texGenDim > 0 && _desc->texGenDim <= 4)
if (_desc->texGenMode && _desc->texGenDim > 0 && _desc->texGenDim <= 4 && !_desc->texGenPerFragment)
texdim = _desc->texGenDim;
......@@ -1176,18 +1176,6 @@ void ShaderProgGenerator::addVertexBeginCode(QStringList* _code)
_code->push_back("vec3 sg_vNormalVS = vec3(0.0, 1.0, 0.0);");
_code->push_back("vec3 sg_vNormalOS = vec3(0.0, 1.0, 0.0);");
/// note: multi-texturing is not supported, as this requires custom texture compositing
// this can be done via shader modifiers or templates
if (ioDesc_.inputTexCoord_)
{
if (!desc_.textureTypes().empty() && desc_.textureTypes().begin()->second.type == GL_TEXTURE_3D) {
_code->push_back("vec3 sg_vTexCoord = inTexCoord;");
} else {
_code->push_back("vec2 sg_vTexCoord = inTexCoord;");
}
}
if (desc_.vertexColors && (desc_.colorMaterialMode == GL_AMBIENT || desc_.colorMaterialMode == GL_AMBIENT_AND_DIFFUSE))
_code->push_back("vec4 sg_cColor = vec4(g_cEmissive + g_cLightModelAmbient * " SG_INPUT_VERTEXCOLOR ".xyz, SG_ALPHA);");
else
......@@ -1202,83 +1190,8 @@ void ShaderProgGenerator::addVertexBeginCode(QStringList* _code)
if (ioDesc_.inputColor_ && (desc_.shadeMode == SG_SHADE_UNLIT || desc_.colorMaterialMode == GL_EMISSION))
_code->push_back("sg_cColor = " SG_INPUT_VERTEXCOLOR ";");
// texcoord generation
if (desc_.texGenDim && desc_.texGenMode)
{
// https://www.opengl.org/wiki/Mathematics_of_glTexGen
if (!ioDesc_.inputTexCoord_)
{
// declare variable if it has not been allocated yet
QString texGenString;
texGenString.sprintf("vec%i sg_vTexCoord; // glTexGen emulation", desc_.texGenDim);
_code->push_back(texGenString);
}
const char* texGenCoordString[] = { "x", "y", "z", "w" };
switch (desc_.texGenMode)
{
case GL_OBJECT_LINEAR:
{
for (int i = 0; i < desc_.texGenDim; ++i)
{
QString assignmentInstrString;
assignmentInstrString.sprintf("sg_vTexCoord.%s = dot(inPosition, g_vTexGenPlane[%i]);", texGenCoordString[i], i);
_code->push_back(assignmentInstrString);
}
} break;
case GL_EYE_LINEAR:
{
for (int i = 0; i < desc_.texGenDim; ++i)
{
QString assignmentInstrString;
assignmentInstrString.sprintf("sg_vTexCoord.%s = dot(sg_vPosVS, g_vTexGenPlane[%i]);", texGenCoordString[i], i);
_code->push_back(assignmentInstrString);
}
} break;
case GL_SPHERE_MAP:
{
_code->push_back("vec3 sg_vPosVS_unit = normalize(sg_vPosVS.xyz);");
_code->push_back("vec3 sg_TexGenRefl = reflect(sg_vPosVS_unit, sg_vNormalVS);");
_code->push_back("vec3 sg_TexGenRefl2 = sg_TexGenRefl; sg_TexGenRefl2.z += 1.0;");
_code->push_back("float sg_TexGenMRcp = 0.5 * inversesqrt(dot(sg_TexGenRefl2, sg_TexGenRefl2));");
for (int i = 0; i < desc_.texGenDim; ++i)
{
QString assignmentInstrString;
assignmentInstrString.sprintf("sg_vTexCoord.%s = sg_TexGenRefl.%s * sg_TexGenMRcp + 0.5;", texGenCoordString[i], texGenCoordString[i]);
_code->push_back(assignmentInstrString);
}
} break;
case GL_NORMAL_MAP:
{
for (int i = 0; i < desc_.texGenDim; ++i)
{
QString assignmentInstrString;
assignmentInstrString.sprintf("sg_vTexCoord.%s = sg_vNormalVS.%s;", texGenCoordString[i], texGenCoordString[i]);
_code->push_back(assignmentInstrString);
}
} break;
case GL_REFLECTION_MAP:
{
_code->push_back("vec3 sg_vPosVS_unit = normalize(sg_vPosVS.xyz);");
_code->push_back("vec3 sg_TexGenRefl = reflect(sg_vPosVS_unit, sg_vNormalVS);");
for (int i = 0; i < desc_.texGenDim; ++i)
{
QString assignmentInstrString;
assignmentInstrString.sprintf("sg_vTexCoord.%s = sg_TexGenRefl.%s;", texGenCoordString[i], texGenCoordString[i]);
_code->push_back(assignmentInstrString);
}
} break;
default: break;
}
}
addTexGenCode(_code, false);
// apply modifiers
......@@ -1857,14 +1770,6 @@ void ShaderProgGenerator::buildFragmentShader()
void ShaderProgGenerator::addFragmentBeginCode(QStringList* _code)
{
QString inputShader = "Vertex";
if ( tessEval_ )
inputShader = "Te";
if ( geometry_ )
inputShader = "Geometry";
// support for projective texture mapping
_code->push_back("vec4 sg_vPosCS = " SG_INPUT_POSCS ";");
_code->push_back("vec2 sg_vScreenPos = sg_vPosCS.xy / sg_vPosCS.w * 0.5 + vec2(0.5, 0.5);");
......@@ -1893,13 +1798,16 @@ void ShaderProgGenerator::addFragmentBeginCode(QStringList* _code)
if (desc_.shadeMode == SG_SHADE_PHONG)
addLightingCode(_code);
addTexGenCode(_code, true);
if (desc_.textured())
{
std::map<size_t,ShaderGenDesc::TextureType>::const_iterator iter = desc_.textureTypes().begin();
_code->push_back("vec4 sg_cTex = texture(g_Texture"+QString::number(iter->first)+", out" + inputShader + "TexCoord);");
_code->push_back("vec4 sg_cTex = texture(g_Texture"+QString::number(iter->first)+", sg_vTexCoord);");
for (++iter; iter != desc_.textureTypes().end(); ++iter)
_code->push_back("sg_cTex += texture(g_Texture"+QString::number(iter->first)+", out" + inputShader + "TexCoord);");
_code->push_back("sg_cTex += texture(g_Texture"+QString::number(iter->first)+", sg_vTexCoord);");
if (desc_.textureTypes().size() > 1 && desc_.normalizeTexColors)
_code->push_back("sg_cTex = sg_cTex * 1.0/" + QString::number(desc_.textureTypes().size()) +".0 ;");
......@@ -2016,6 +1924,113 @@ void ShaderProgGenerator::addLightingFunctions(QStringList* _code)
_code->push_back(it);
}
void ShaderProgGenerator::addTexGenCode( QStringList* _code, bool _fragmentShader )
{
// declare local texcoord variable name as "sg_vTexCoord"
int texcoordVarDim = 2;
if (ioDesc_.inputTexCoord_ &&
!desc_.textureTypes().empty() &&
desc_.textureTypes().begin()->second.type == GL_TEXTURE_3D)
texcoordVarDim = 3;
bool generateTexCoord = desc_.texGenDim && desc_.texGenMode && (_fragmentShader == desc_.texGenPerFragment);
if (generateTexCoord)
texcoordVarDim = desc_.texGenDim;
QString texcoordVarInit;
texcoordVarInit.sprintf("vec%i sg_vTexCoord", texcoordVarDim);
// init with default value: input or zero
if (ioDesc_.inputTexCoord_ && !generateTexCoord)
texcoordVarInit += "= "SG_INPUT_TEXCOORD";";
else if (0 <= texcoordVarDim && texcoordVarDim <= 4)
{
QString zeroVecDefs[] =
{
";",
"= 0;",
"= vec2(0,0);",
"= vec3(0,0,0);",
"= vec4(0,0,0,0);"
};
texcoordVarInit += zeroVecDefs[texcoordVarDim];
}
_code->push_back(texcoordVarInit);
// texcoord generation
// https://www.opengl.org/wiki/Mathematics_of_glTexGen
if (generateTexCoord)
{
const char* texGenCoordString[] = { "x", "y", "z", "w" };
switch (desc_.texGenMode)
{
case GL_OBJECT_LINEAR:
{
for (int i = 0; i < desc_.texGenDim; ++i)
{
QString assignmentInstrString;
assignmentInstrString.sprintf("sg_vTexCoord.%s = dot("SG_INPUT_POSOS", g_vTexGenPlane[%i]);", texGenCoordString[i], i);
_code->push_back(assignmentInstrString);
}
} break;
case GL_EYE_LINEAR:
{
for (int i = 0; i < desc_.texGenDim; ++i)
{
QString assignmentInstrString;
assignmentInstrString.sprintf("sg_vTexCoord.%s = dot(sg_vPosVS, g_vTexGenPlane[%i]);", texGenCoordString[i], i);
_code->push_back(assignmentInstrString);
}
} break;
case GL_SPHERE_MAP:
{
_code->push_back("vec3 sg_vPosVS_unit = normalize(sg_vPosVS.xyz);");
_code->push_back("vec3 sg_TexGenRefl = reflect(sg_vPosVS_unit, sg_vNormalVS);");
_code->push_back("vec3 sg_TexGenRefl2 = sg_TexGenRefl; sg_TexGenRefl2.z += 1.0;");
_code->push_back("float sg_TexGenMRcp = 0.5 * inversesqrt(dot(sg_TexGenRefl2, sg_TexGenRefl2));");
for (int i = 0; i < desc_.texGenDim; ++i)
{
QString assignmentInstrString;
assignmentInstrString.sprintf("sg_vTexCoord.%s = sg_TexGenRefl.%s * sg_TexGenMRcp + 0.5;", texGenCoordString[i], texGenCoordString[i]);
_code->push_back(assignmentInstrString);
}
} break;
case GL_NORMAL_MAP:
{
for (int i = 0; i < desc_.texGenDim; ++i)
{
QString assignmentInstrString;
assignmentInstrString.sprintf("sg_vTexCoord.%s = sg_vNormalVS.%s;", texGenCoordString[i], texGenCoordString[i]);
_code->push_back(assignmentInstrString);
}
} break;
case GL_REFLECTION_MAP:
{
_code->push_back("vec3 sg_vPosVS_unit = normalize(sg_vPosVS.xyz);");
_code->push_back("vec3 sg_TexGenRefl = reflect(sg_vPosVS_unit, sg_vNormalVS);");
for (int i = 0; i < desc_.texGenDim; ++i)
{
QString assignmentInstrString;
assignmentInstrString.sprintf("sg_vTexCoord.%s = sg_TexGenRefl.%s;", texGenCoordString[i], texGenCoordString[i]);
_code->push_back(assignmentInstrString);
}
} break;
default: break;
}
}
}
void ShaderProgGenerator::generateShaders()
{
// import template source from files
......@@ -2050,11 +2065,31 @@ void ShaderProgGenerator::generateShaders()
desc_.texGenDim = std::max(std::min(desc_.texGenDim, maxTexGenDim), 0);
if (desc_.texGenMode == GL_REFLECTION_MAP || desc_.texGenMode == GL_SPHERE_MAP || desc_.texGenMode == GL_NORMAL_MAP)
ioDesc_.inputNormal_ = true;
if (desc_.texGenDim && desc_.texGenMode)
ioDesc_.passTexCoord_ = true; // no input, but texcoords are generated
{
// pass generated texcoord from vertex to fragment shader
if (!desc_.texGenPerFragment)
ioDesc_.passTexCoord_ = true;
// some modes require normal vectors
if (desc_.texGenMode == GL_REFLECTION_MAP || desc_.texGenMode == GL_SPHERE_MAP || desc_.texGenMode == GL_NORMAL_MAP)
ioDesc_.inputNormal_ = true;
// pass data to the fragment shader as required for the generation
if (desc_.texGenPerFragment)
{
switch (desc_.texGenMode)
{
case GL_OBJECT_LINEAR: ioDesc_.passPosOS_ = true; break;
case GL_EYE_LINEAR: ioDesc_.passPosVS_ = true; break;
case GL_SPHERE_MAP: ioDesc_.passPosVS_ = ioDesc_.passNormalVS_ = true; break;
case GL_NORMAL_MAP: ioDesc_.passNormalVS_ = true; break;
case GL_REFLECTION_MAP: ioDesc_.passPosVS_ = ioDesc_.passNormalVS_ = true; break;
default: break;
}
}
}
if (desc_.vertexColors)
......@@ -2700,6 +2735,7 @@ QString ShaderGenDesc::toString() const
default: resStrm << "\nshaderDesc.texGenMode: unknown"; break;
}
resStrm << "\nshaderDesc.texGenPerFragment: " << texGenPerFragment;
if (!vertexTemplateFile.isEmpty())
resStrm << "\nshaderDesc.vertexTemplateFile: " << vertexTemplateFile;
......
......@@ -104,7 +104,8 @@ public:
colorMaterialMode(GL_AMBIENT_AND_DIFFUSE),
textureTypes_(),
texGenDim(0),
texGenMode(GL_EYE_LINEAR)
texGenMode(GL_EYE_LINEAR),
texGenPerFragment(false)
{
for ( unsigned int i = 0 ; i < SG_MAX_SHADER_LIGHTS ; ++i)
lightTypes[i] = SG_LIGHT_DIRECTIONAL;
......@@ -211,6 +212,10 @@ public:
// texture generation mode: GL_OBJECT_LINEAR, GL_EYE_LINEAR, GL_SPHERE_MAP, GL_NORMAL_MAP, GL_REFLECTION_MAP
GLenum texGenMode;
// generate texture coordinates per vertex or fragment
// default: false -> per vertex
bool texGenPerFragment;
void enableTexGenObjectLinear(int _dim = 2)
{
texGenDim = std::max(std::min(_dim, 4), 0);
......@@ -293,6 +298,9 @@ public:
{
if (texGenMode != _rhs.texGenMode)
return false;
if (texGenPerFragment != _rhs.texGenPerFragment)
return false;
}
if (numLights)
......@@ -1217,6 +1225,10 @@ private:
*/
void modifyLightingCode(QStringList* _code, ShaderModifier* _modifier);
/** \brief Add texture coordinate generation code
*/
void addTexGenCode(QStringList* _code, bool _fragmentShader);
/// returns path to _strFileName without last slash
static QString getPathName(QString _strFileName);
......
......@@ -459,7 +459,6 @@ void Texture2D::buildMipMaps( GLenum _internalfmt,
// compute number of mipmaps
Vec2i curSize = Vec2i(_width, _height);
int curOffset = 0;
std::vector<int> mipMemsize(1, 0);
std::vector<Vec2i> mipSize(1, curSize);
......
This diff is collapsed.
/*===========================================================================*\
* *
* OpenFlipper *
* Copyright (c) 2001-2015, RWTH-Aachen University *
* Department of Computer Graphics and Multimedia *
* All rights reserved. *
* www.openflipper.org *
* *
*---------------------------------------------------------------------------*
* This file is part of OpenFlipper. *
*---------------------------------------------------------------------------*
* *
* Redistribution and use in source and binary forms, with or without *
* modification, are permitted provided that the following conditions *
* are met: *
* *
* 1. Redistributions of source code must retain the above copyright notice, *
* this list of conditions and the following disclaimer. *
* *
* 2. Redistributions in binary form must reproduce the above copyright *
* notice, this list of conditions and the following disclaimer in the *
* documentation and/or other materials provided with the distribution. *
* *
* 3. Neither the name of the copyright holder nor the names of its *
* contributors may be used to endorse or promote products derived from *
* this software without specific prior written permission. *
* *
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS *
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED *
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER *
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, *
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, *
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR *
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF *
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING *
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS *
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
* *
\*===========================================================================*/
/*===========================================================================*\
* *
* $Revision: 21022 $ *
* $Author: moebius $ *
* $Date: 2015-07-17 08:23:03 +0200 (Fri, 17 Jul 2015) $ *
* *
\*===========================================================================*/
#include "Triangulator.hh"
#include <iostream>
namespace ACG {
Triangulator::Triangulator(const std::vector<Vec3f>& _pos)
: polySize_(_pos.size()), numRemaningVertices_(_pos.size()), numTris_(0),
numReflexVertices_(0),
status_(-1), convex_(false)
{
if (polySize_ < 3)
return;
if (polySize_ == 3)
{
numTris_ = 1;
tris_.resize(3);
tris_[0] = 0;
tris_[1] = 1;
tris_[2] = 2;
numRemaningVertices_ = 0;
convex_ = true;
status_ = 0;
}
else
{
// project vertices onto the 2d plane of the polygon.
// the projection plane is chosen orthogonal to the polygon surface normal
// use Newell's Method to compute the surface normal
// http://www.opengl.org/wiki/Calculating_a_Surface_Normal
Vec3f n(0.0f, 0.0f, 0.0f);
for (int i = 0; i < polySize_; ++i)
{
int next = (i + 1) % polySize_;
Vec3f a = _pos[i] - _pos[next];
Vec3f b = _pos[i] + _pos[next];
n[0] += a[1] * b[2];
n[1] += a[2] * b[0];
n[2] += a[0] * b[1];
}
// project to 2d
pos_.resize(polySize_);
Vec3f axis[3] = { Vec3f(1.0f, 0.0f, 0.0f), Vec3f(0.0f, 1.0f, 0.0f), n };
// orthonormalize projection axes
axis[2].normalize();
// make sure first axis is linearly independent from the normal
while (std::abs(axis[0] | axis[2]) > 0.95f || (axis[0].sqrnorm() < 0.001f))