godot/thirdparty/thekla_atlas/nvmesh/MeshBuilder.cpp

1001 lines
24 KiB
C++

// This code is in the public domain -- castanyo@yahoo.es
#include "nvmesh.h" // pch
#include "MeshBuilder.h"
#include "TriMesh.h"
#include "QuadTriMesh.h"
#include "halfedge/Mesh.h"
#include "halfedge/Vertex.h"
#include "halfedge/Face.h"
#include "weld/Weld.h"
#include "nvmath/Box.h"
#include "nvmath/Vector.inl"
#include "nvcore/StrLib.h"
#include "nvcore/RadixSort.h"
#include "nvcore/Ptr.h"
#include "nvcore/Array.inl"
#include "nvcore/HashMap.inl"
using namespace nv;
/*
By default the mesh builder creates 3 streams (position, normal, texcoord), I'm planning to add support for extra streams as follows:
enum StreamType { StreamType_Float, StreamType_Vector2, StreamType_Vector3, StreamType_Vector4 };
uint addStream(const char *, uint idx, StreamType);
uint addAttribute(float)
uint addAttribute(Vector2)
uint addAttribute(Vector3)
uint addAttribute(Vector4)
struct Vertex
{
uint pos;
uint nor;
uint tex;
uint * attribs; // NULL or NIL terminated array?
};
All streams must be added before hand, so that you know the size of the attribs array.
The vertex hash function could be kept as is, but the == operator should be extended to test
the extra atributes when available.
That might require a custom hash implementation, or an extension of the current one. How to
handle the variable number of attributes in the attribs array?
bool operator()(const Vertex & a, const Vertex & b) const
{
if (a.pos != b.pos || a.nor != b.nor || a.tex != b.tex) return false;
if (a.attribs == NULL && b.attribs == NULL) return true;
return 0 == memcmp(a.attribs, b.attribs, ???);
}
We could use a NIL terminated array, or provide custom user data to the equals functor.
vertexMap.setUserData((void *)vertexAttribCount);
bool operator()(const Vertex & a, const Vertex & b, void * userData) const { ... }
*/
namespace
{
struct Material
{
Material() : faceCount(0) {}
Material(const String & str) : name(str), faceCount(0) {}
String name;
uint faceCount;
};
struct Vertex
{
//Vertex() {}
//Vertex(uint p, uint n, uint t0, uint t1, uint c) : pos(p), nor(n), tex0(t0), tex1(t1), col(c) {}
friend bool operator==(const Vertex & a, const Vertex & b)
{
return a.pos == b.pos && a.nor == b.nor && a.tex[0] == b.tex[0] && a.tex[1] == b.tex[1] && a.col[0] == b.col[0] && a.col[1] == b.col[1] && a.col[2] == b.col[2];
}
uint pos;
uint nor;
uint tex[2];
uint col[3];
};
struct Face
{
uint id;
uint firstIndex;
uint indexCount;
uint material;
uint group;
};
} // namespace
namespace nv
{
// This is a much better hash than the default and greatly improves performance!
template <> struct Hash<Vertex>
{
uint operator()(const Vertex & v) const { return v.pos + v.nor + v.tex[0]/* + v.col*/; }
};
}
struct MeshBuilder::PrivateData
{
PrivateData() : currentGroup(NIL), currentMaterial(NIL), maxFaceIndexCount(0) {}
uint pushVertex(uint p, uint n, uint t0, uint t1, uint c0, uint c1, uint c2);
uint pushVertex(const Vertex & v);
Array<Vector3> posArray;
Array<Vector3> norArray;
Array<Vector2> texArray[2];
Array<Vector4> colArray[3];
Array<Vertex> vertexArray;
HashMap<Vertex, uint> vertexMap;
HashMap<String, uint> materialMap;
Array<Material> materialArray;
uint currentGroup;
uint currentMaterial;
Array<uint> indexArray;
Array<Face> faceArray;
uint maxFaceIndexCount;
};
uint MeshBuilder::PrivateData::pushVertex(uint p, uint n, uint t0, uint t1, uint c0, uint c1, uint c2)
{
Vertex v;
v.pos = p;
v.nor = n;
v.tex[0] = t0;
v.tex[1] = t1;
v.col[0] = c0;
v.col[1] = c1;
v.col[2] = c2;
return pushVertex(v);
}
uint MeshBuilder::PrivateData::pushVertex(const Vertex & v)
{
// Lookup vertex v in map.
uint idx;
if (vertexMap.get(v, &idx))
{
return idx;
}
idx = vertexArray.count();
vertexArray.pushBack(v);
vertexMap.add(v, idx);
return idx;
}
MeshBuilder::MeshBuilder() : d(new PrivateData())
{
}
MeshBuilder::~MeshBuilder()
{
nvDebugCheck(d != NULL);
delete d;
}
// Builder methods.
uint MeshBuilder::addPosition(const Vector3 & v)
{
d->posArray.pushBack(validate(v));
return d->posArray.count() - 1;
}
uint MeshBuilder::addNormal(const Vector3 & v)
{
d->norArray.pushBack(validate(v));
return d->norArray.count() - 1;
}
uint MeshBuilder::addTexCoord(const Vector2 & v, uint set/*=0*/)
{
d->texArray[set].pushBack(validate(v));
return d->texArray[set].count() - 1;
}
uint MeshBuilder::addColor(const Vector4 & v, uint set/*=0*/)
{
d->colArray[set].pushBack(validate(v));
return d->colArray[set].count() - 1;
}
void MeshBuilder::beginGroup(uint id)
{
d->currentGroup = id;
}
void MeshBuilder::endGroup()
{
d->currentGroup = NIL;
}
// Add named material, check for uniquenes.
uint MeshBuilder::addMaterial(const char * name)
{
uint index;
if (d->materialMap.get(name, &index)) {
nvDebugCheck(d->materialArray[index].name == name);
}
else {
index = d->materialArray.count();
d->materialMap.add(name, index);
Material material(name);
d->materialArray.append(material);
}
return index;
}
void MeshBuilder::beginMaterial(uint id)
{
d->currentMaterial = id;
}
void MeshBuilder::endMaterial()
{
d->currentMaterial = NIL;
}
void MeshBuilder::beginPolygon(uint id/*=0*/)
{
Face face;
face.id = id;
face.firstIndex = d->indexArray.count();
face.indexCount = 0;
face.material = d->currentMaterial;
face.group = d->currentGroup;
d->faceArray.pushBack(face);
}
uint MeshBuilder::addVertex(uint p, uint n/*= NIL*/, uint t0/*= NIL*/, uint t1/*= NIL*/, uint c0/*= NIL*/, uint c1/*= NIL*/, uint c2/*= NIL*/)
{
// @@ In theory there's no need to add vertices before faces, but I'm adding this to debug problems in our maya exporter:
nvDebugCheck(p < d->posArray.count());
nvDebugCheck(n == NIL || n < d->norArray.count());
nvDebugCheck(t0 == NIL || t0 < d->texArray[0].count());
nvDebugCheck(t1 == NIL || t1 < d->texArray[1].count());
//nvDebugCheck(c0 == NIL || c0 < d->colArray[0].count());
if (c0 > d->colArray[0].count()) c0 = NIL; // @@ This seems to be happening in loc_swamp_catwalk.mb! No idea why.
nvDebugCheck(c1 == NIL || c1 < d->colArray[1].count());
nvDebugCheck(c2 == NIL || c2 < d->colArray[2].count());
uint idx = d->pushVertex(p, n, t0, t1, c0, c1, c2);
d->indexArray.pushBack(idx);
d->faceArray.back().indexCount++;
return idx;
}
uint MeshBuilder::addVertex(const Vector3 & pos)
{
uint p = addPosition(pos);
return addVertex(p);
}
#if 0
uint MeshBuilder::addVertex(const Vector3 & pos, const Vector3 & nor, const Vector2 & tex0, const Vector2 & tex1, const Vector4 & col0, const Vector4 & col1)
{
uint p = addPosition(pos);
uint n = addNormal(nor);
uint t0 = addTexCoord(tex0, 0);
uint t1 = addTexCoord(tex1, 1);
uint c0 = addColor(col0);
uint c1 = addColor(col1);
return addVertex(p, n, t0, t1, c0, c1);
}
#endif
// Return true if the face is valid and was added to the mesh.
bool MeshBuilder::endPolygon()
{
const Face & face = d->faceArray.back();
const uint count = face.indexCount;
// Validate polygon here.
bool invalid = count <= 2;
if (!invalid) {
// Skip zero area polygons. Or polygons with degenerate edges (which will result in zero-area triangles).
const uint first = face.firstIndex;
for (uint j = count - 1, i = 0; i < count; j = i, i++) {
uint v0 = d->indexArray[first + i];
uint v1 = d->indexArray[first + j];
uint p0 = d->vertexArray[v0].pos;
uint p1 = d->vertexArray[v1].pos;
if (p0 == p1) {
invalid = true;
break;
}
if (equal(d->posArray[p0], d->posArray[p1], FLT_EPSILON)) {
invalid = true;
break;
}
}
uint v0 = d->indexArray[first];
uint p0 = d->vertexArray[v0].pos;
Vector3 x0 = d->posArray[p0];
float area = 0.0f;
for (uint j = 1, i = 2; i < count; j = i, i++) {
uint v1 = d->indexArray[first + i];
uint v2 = d->indexArray[first + j];
uint p1 = d->vertexArray[v1].pos;
uint p2 = d->vertexArray[v2].pos;
Vector3 x1 = d->posArray[p1];
Vector3 x2 = d->posArray[p2];
area += length(cross(x1-x0, x2-x0));
}
if (0.5 * area < 1e-6) { // Reduce this threshold if artists have legitimate complains.
invalid = true;
}
// @@ This is not complete. We may still get zero area triangles after triangulation.
// However, our plugin triangulates before building the mesh, so hopefully that's not a problem.
}
if (invalid)
{
d->indexArray.resize(d->indexArray.size() - count);
d->faceArray.popBack();
return false;
}
else
{
if (d->currentMaterial != NIL) {
d->materialArray[d->currentMaterial].faceCount++;
}
d->maxFaceIndexCount = max(d->maxFaceIndexCount, count);
return true;
}
}
uint MeshBuilder::weldPositions()
{
Array<uint> xrefs;
Weld<Vector3> weldVector3;
if (d->posArray.count()) {
// Weld vertex attributes.
weldVector3(d->posArray, xrefs);
// Remap vertex indices.
const uint vertexCount = d->vertexArray.count();
for (uint v = 0; v < vertexCount; v++)
{
Vertex & vertex = d->vertexArray[v];
if (vertex.pos != NIL) vertex.pos = xrefs[vertex.pos];
}
}
return d->posArray.count();
}
uint MeshBuilder::weldNormals()
{
Array<uint> xrefs;
Weld<Vector3> weldVector3;
if (d->norArray.count()) {
// Weld vertex attributes.
weldVector3(d->norArray, xrefs);
// Remap vertex indices.
const uint vertexCount = d->vertexArray.count();
for (uint v = 0; v < vertexCount; v++)
{
Vertex & vertex = d->vertexArray[v];
if (vertex.nor != NIL) vertex.nor = xrefs[vertex.nor];
}
}
return d->norArray.count();
}
uint MeshBuilder::weldTexCoords(uint set/*=0*/)
{
Array<uint> xrefs;
Weld<Vector2> weldVector2;
if (d->texArray[set].count()) {
// Weld vertex attributes.
weldVector2(d->texArray[set], xrefs);
// Remap vertex indices.
const uint vertexCount = d->vertexArray.count();
for (uint v = 0; v < vertexCount; v++)
{
Vertex & vertex = d->vertexArray[v];
if (vertex.tex[set] != NIL) vertex.tex[set] = xrefs[vertex.tex[set]];
}
}
return d->texArray[set].count();
}
uint MeshBuilder::weldColors(uint set/*=0*/)
{
Array<uint> xrefs;
Weld<Vector4> weldVector4;
if (d->colArray[set].count()) {
// Weld vertex attributes.
weldVector4(d->colArray[set], xrefs);
// Remap vertex indices.
const uint vertexCount = d->vertexArray.count();
for (uint v = 0; v < vertexCount; v++)
{
Vertex & vertex = d->vertexArray[v];
if (vertex.col[set] != NIL) vertex.col[set] = xrefs[vertex.col[set]];
}
}
return d->colArray[set].count();
}
void MeshBuilder::weldVertices() {
if (d->vertexArray.count() == 0) {
// Nothing to do.
return;
}
Array<uint> xrefs;
Weld<Vertex> weldVertex;
// Weld vertices.
weldVertex(d->vertexArray, xrefs);
// Remap face indices.
const uint indexCount = d->indexArray.count();
for (uint i = 0; i < indexCount; i++)
{
d->indexArray[i] = xrefs[d->indexArray[i]];
}
// Remap vertex map.
foreach(i, d->vertexMap)
{
d->vertexMap[i].value = xrefs[d->vertexMap[i].value];
}
}
void MeshBuilder::optimize()
{
if (d->vertexArray.count() == 0)
{
return;
}
weldPositions();
weldNormals();
weldTexCoords(0);
weldTexCoords(1);
weldColors();
weldVertices();
}
void MeshBuilder::removeUnusedMaterials(Array<uint> & newMaterialId)
{
uint materialCount = d->materialArray.count();
// Reset face counts.
for (uint i = 0; i < materialCount; i++) {
d->materialArray[i].faceCount = 0;
}
// Count faces.
foreach(i, d->faceArray) {
Face & face = d->faceArray[i];
if (face.material != NIL) {
nvDebugCheck(face.material < materialCount);
d->materialArray[face.material].faceCount++;
}
}
// Remove unused materials.
newMaterialId.resize(materialCount);
for (uint i = 0, m = 0; i < materialCount; i++)
{
if (d->materialArray[m].faceCount > 0)
{
newMaterialId[i] = m++;
}
else
{
newMaterialId[i] = NIL;
d->materialArray.removeAt(m);
}
}
materialCount = d->materialArray.count();
// Update face material ids.
foreach(i, d->faceArray) {
Face & face = d->faceArray[i];
if (face.material != NIL) {
uint id = newMaterialId[face.material];
nvDebugCheck(id != NIL && id < materialCount);
face.material = id;
}
}
}
void MeshBuilder::sortFacesByGroup()
{
const uint faceCount = d->faceArray.count();
Array<uint> faceGroupArray;
faceGroupArray.resize(faceCount);
for (uint i = 0; i < faceCount; i++) {
faceGroupArray[i] = d->faceArray[i].group;
}
RadixSort radix;
radix.sort(faceGroupArray);
Array<Face> newFaceArray;
newFaceArray.resize(faceCount);
for (uint i = 0; i < faceCount; i++) {
newFaceArray[i] = d->faceArray[radix.rank(i)];
}
swap(newFaceArray, d->faceArray);
}
void MeshBuilder::sortFacesByMaterial()
{
const uint faceCount = d->faceArray.count();
Array<uint> faceMaterialArray;
faceMaterialArray.resize(faceCount);
for (uint i = 0; i < faceCount; i++) {
faceMaterialArray[i] = d->faceArray[i].material;
}
RadixSort radix;
radix.sort(faceMaterialArray);
Array<Face> newFaceArray;
newFaceArray.resize(faceCount);
for (uint i = 0; i < faceCount; i++) {
newFaceArray[i] = d->faceArray[radix.rank(i)];
}
swap(newFaceArray, d->faceArray);
}
void MeshBuilder::reset()
{
nvDebugCheck(d != NULL);
delete d;
d = new PrivateData();
}
void MeshBuilder::done()
{
if (d->currentGroup != NIL) {
endGroup();
}
if (d->currentMaterial != NIL) {
endMaterial();
}
}
// Hints.
void MeshBuilder::hintTriangleCount(uint count)
{
d->indexArray.reserve(d->indexArray.count() + count * 4);
}
void MeshBuilder::hintVertexCount(uint count)
{
d->vertexArray.reserve(d->vertexArray.count() + count);
d->vertexMap.resize(d->vertexMap.count() + count);
}
void MeshBuilder::hintPositionCount(uint count)
{
d->posArray.reserve(d->posArray.count() + count);
}
void MeshBuilder::hintNormalCount(uint count)
{
d->norArray.reserve(d->norArray.count() + count);
}
void MeshBuilder::hintTexCoordCount(uint count, uint set/*=0*/)
{
d->texArray[set].reserve(d->texArray[set].count() + count);
}
void MeshBuilder::hintColorCount(uint count, uint set/*=0*/)
{
d->colArray[set].reserve(d->colArray[set].count() + count);
}
// Helpers.
void MeshBuilder::addTriangle(uint v0, uint v1, uint v2)
{
beginPolygon();
addVertex(v0);
addVertex(v1);
addVertex(v2);
endPolygon();
}
void MeshBuilder::addQuad(uint v0, uint v1, uint v2, uint v3)
{
beginPolygon();
addVertex(v0);
addVertex(v1);
addVertex(v2);
addVertex(v3);
endPolygon();
}
// Get tri mesh.
TriMesh * MeshBuilder::buildTriMesh() const
{
const uint faceCount = d->faceArray.count();
uint triangleCount = 0;
for (uint f = 0; f < faceCount; f++) {
triangleCount += d->faceArray[f].indexCount - 2;
}
const uint vertexCount = d->vertexArray.count();
TriMesh * mesh = new TriMesh(triangleCount, vertexCount);
// Build faces.
Array<TriMesh::Face> & faces = mesh->faces();
for(uint f = 0; f < faceCount; f++)
{
int firstIndex = d->faceArray[f].firstIndex;
int indexCount = d->faceArray[f].indexCount;
int v0 = d->indexArray[firstIndex + 0];
int v1 = d->indexArray[firstIndex + 1];
for(int t = 0; t < indexCount - 2; t++) {
int v2 = d->indexArray[firstIndex + t + 2];
TriMesh::Face face;
face.id = faces.count();
face.v[0] = v0;
face.v[1] = v1;
face.v[2] = v2;
faces.append(face);
v1 = v2;
}
}
// Build vertices.
Array<BaseMesh::Vertex> & vertices = mesh->vertices();
for(uint i = 0; i < vertexCount; i++)
{
BaseMesh::Vertex vertex;
vertex.id = i;
if (d->vertexArray[i].pos != NIL) vertex.pos = d->posArray[d->vertexArray[i].pos];
if (d->vertexArray[i].nor != NIL) vertex.nor = d->norArray[d->vertexArray[i].nor];
if (d->vertexArray[i].tex[0] != NIL) vertex.tex = d->texArray[0][d->vertexArray[i].tex[0]];
vertices.append(vertex);
}
return mesh;
}
// Get quad/tri mesh.
QuadTriMesh * MeshBuilder::buildQuadTriMesh() const
{
const uint faceCount = d->faceArray.count();
const uint vertexCount = d->vertexArray.count();
QuadTriMesh * mesh = new QuadTriMesh(faceCount, vertexCount);
// Build faces.
Array<QuadTriMesh::Face> & faces = mesh->faces();
for (uint f = 0; f < faceCount; f++)
{
int firstIndex = d->faceArray[f].firstIndex;
int indexCount = d->faceArray[f].indexCount;
QuadTriMesh::Face face;
face.id = f;
face.v[0] = d->indexArray[firstIndex + 0];
face.v[1] = d->indexArray[firstIndex + 1];
face.v[2] = d->indexArray[firstIndex + 2];
// Only adds triangles and quads. Ignores polygons.
if (indexCount == 3) {
face.v[3] = NIL;
faces.append(face);
}
else if (indexCount == 4) {
face.v[3] = d->indexArray[firstIndex + 3];
faces.append(face);
}
}
// Build vertices.
Array<BaseMesh::Vertex> & vertices = mesh->vertices();
for(uint i = 0; i < vertexCount; i++)
{
BaseMesh::Vertex vertex;
vertex.id = i;
if (d->vertexArray[i].pos != NIL) vertex.pos = d->posArray[d->vertexArray[i].pos];
if (d->vertexArray[i].nor != NIL) vertex.nor = d->norArray[d->vertexArray[i].nor];
if (d->vertexArray[i].tex[0] != NIL) vertex.tex = d->texArray[0][d->vertexArray[i].tex[0]];
vertices.append(vertex);
}
return mesh;
}
// Get half edge mesh.
HalfEdge::Mesh * MeshBuilder::buildHalfEdgeMesh(bool weldPositions, Error * error/*=NULL*/, Array<uint> * badFaces/*=NULL*/) const
{
if (error != NULL) *error = Error_None;
const uint vertexCount = d->vertexArray.count();
AutoPtr<HalfEdge::Mesh> mesh(new HalfEdge::Mesh());
for(uint v = 0; v < vertexCount; v++)
{
HalfEdge::Vertex * vertex = mesh->addVertex(d->posArray[d->vertexArray[v].pos]);
if (d->vertexArray[v].nor != NIL) vertex->nor = d->norArray[d->vertexArray[v].nor];
if (d->vertexArray[v].tex[0] != NIL) vertex->tex = Vector2(d->texArray[0][d->vertexArray[v].tex[0]]);
if (d->vertexArray[v].col[0] != NIL) vertex->col = d->colArray[0][d->vertexArray[v].col[0]];
}
if (weldPositions) {
mesh->linkColocals();
}
else {
// Build canonical map from position indices.
Array<uint> canonicalMap(vertexCount);
foreach (i, d->vertexArray) {
canonicalMap.append(d->vertexArray[i].pos);
}
mesh->linkColocalsWithCanonicalMap(canonicalMap);
}
const uint faceCount = d->faceArray.count();
for (uint f = 0; f < faceCount; f++)
{
const uint firstIndex = d->faceArray[f].firstIndex;
const uint indexCount = d->faceArray[f].indexCount;
HalfEdge::Face * face = mesh->addFace(d->indexArray, firstIndex, indexCount);
// @@ This is too late, removing the face here will leave the mesh improperly connected.
/*if (face->area() <= FLT_EPSILON) {
mesh->remove(face);
face = NULL;
}*/
if (face == NULL) {
// Non manifold mesh.
if (error != NULL) *error = Error_NonManifoldEdge;
if (badFaces != NULL) {
badFaces->append(d->faceArray[f].id);
}
//return NULL; // IC: Ignore error and continue building the mesh.
}
if (face != NULL) {
face->group = d->faceArray[f].group;
face->material = d->faceArray[f].material;
}
}
mesh->linkBoundary();
// We cannot fix functions here, because this would introduce new vertices and these vertices won't have the corresponding builder data.
// Maybe the builder should perform the search for T-junctions and update the vertex data directly.
// For now, we don't fix T-junctions at export time, but only during parameterization.
//mesh->fixBoundaryJunctions();
//mesh->sewBoundary();
return mesh.release();
}
bool MeshBuilder::buildPositions(Array<Vector3> & positionArray)
{
const uint vertexCount = d->vertexArray.count();
positionArray.resize(vertexCount);
for (uint v = 0; v < vertexCount; v++)
{
nvDebugCheck(d->vertexArray[v].pos != NIL);
positionArray[v] = d->posArray[d->vertexArray[v].pos];
}
return true;
}
bool MeshBuilder::buildNormals(Array<Vector3> & normalArray)
{
bool anyNormal = false;
const uint vertexCount = d->vertexArray.count();
normalArray.resize(vertexCount);
for (uint v = 0; v < vertexCount; v++)
{
if (d->vertexArray[v].nor == NIL) {
normalArray[v] = Vector3(0, 0, 1);
}
else {
anyNormal = true;
normalArray[v] = d->norArray[d->vertexArray[v].nor];
}
}
return anyNormal;
}
bool MeshBuilder::buildTexCoords(Array<Vector2> & texCoordArray, uint set/*=0*/)
{
bool anyTexCoord = false;
const uint vertexCount = d->vertexArray.count();
texCoordArray.resize(vertexCount);
for (uint v = 0; v < vertexCount; v++)
{
if (d->vertexArray[v].tex[set] == NIL) {
texCoordArray[v] = Vector2(0, 0);
}
else {
anyTexCoord = true;
texCoordArray[v] = d->texArray[set][d->vertexArray[v].tex[set]];
}
}
return anyTexCoord;
}
bool MeshBuilder::buildColors(Array<Vector4> & colorArray, uint set/*=0*/)
{
bool anyColor = false;
const uint vertexCount = d->vertexArray.count();
colorArray.resize(vertexCount);
for (uint v = 0; v < vertexCount; v++)
{
if (d->vertexArray[v].col[set] == NIL) {
colorArray[v] = Vector4(0, 0, 0, 1);
}
else {
anyColor = true;
colorArray[v] = d->colArray[set][d->vertexArray[v].col[set]];
}
}
return anyColor;
}
void MeshBuilder::buildVertexToPositionMap(Array<int> &map)
{
const uint vertexCount = d->vertexArray.count();
map.resize(vertexCount);
foreach (i, d->vertexArray) {
map[i] = d->vertexArray[i].pos;
}
}
uint MeshBuilder::vertexCount() const
{
return d->vertexArray.count();
}
uint MeshBuilder::positionCount() const
{
return d->posArray.count();
}
uint MeshBuilder::normalCount() const
{
return d->norArray.count();
}
uint MeshBuilder::texCoordCount(uint set/*=0*/) const
{
return d->texArray[set].count();
}
uint MeshBuilder::colorCount(uint set/*=0*/) const
{
return d->colArray[set].count();
}
uint MeshBuilder::materialCount() const
{
return d->materialArray.count();
}
const char * MeshBuilder::material(uint i) const
{
return d->materialArray[i].name;
}
uint MeshBuilder::positionIndex(uint vertex) const
{
return d->vertexArray[vertex].pos;
}
uint MeshBuilder::normalIndex(uint vertex) const
{
return d->vertexArray[vertex].nor;
}
uint MeshBuilder::texCoordIndex(uint vertex, uint set/*=0*/) const
{
return d->vertexArray[vertex].tex[set];
}
uint MeshBuilder::colorIndex(uint vertex, uint set/*=0*/) const
{
return d->vertexArray[vertex].col[set];
}