Skip to content

Instantly share code, notes, and snippets.

@nsf
Created January 20, 2016 18:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nsf/cf81c7be91a59bd4fc8d to your computer and use it in GitHub Desktop.
Save nsf/cf81c7be91a59bd4fc8d to your computer and use it in GitHub Desktop.
struct Quadric {
float q[10];
Quadric() = default;
Quadric(float q0, float q1, float q2, float q3, float q4,
float q5, float q6, float q7, float q8, float q9)
{
q[0] = q0;
q[1] = q1;
q[2] = q2;
q[3] = q3;
q[4] = q4;
q[5] = q5;
q[6] = q6;
q[7] = q7;
q[8] = q8;
q[9] = q9;
}
Quadric &operator+=(const Quadric &rhs)
{
for (int i = 0; i < 10; i++)
q[i] += rhs[i];
return *this;
}
float &operator[](int i) { return q[i]; }
float operator[](int i) const { return q[i]; }
};
static inline bool operator==(const Quadric &lhs, const Quadric &rhs)
{
for (int i = 0; i < 10; i++) {
if (fabs(lhs[0] - rhs[0]) > MATH_EPSILON)
return false;
}
return true;
}
static inline bool operator!=(const Quadric &lhs, const Quadric &rhs)
{
return !operator==(lhs, rhs);
}
static inline Quadric operator+(const Quadric &lhs, const Quadric &rhs)
{
Quadric out;
for (int i = 0; i < 10; i++)
out[i] = lhs[i] + rhs[i];
return out;
}
static inline Quadric Quadric_Zero()
{
Quadric out;
for (int i = 0; i < 10; i++)
out[i] = 0;
return out;
}
static inline Mat4 ToMat4(const Quadric &q)
{
return Mat4(
q[0], q[1], q[2], q[3],
q[1], q[4], q[5], q[6],
q[2], q[5], q[7], q[8],
q[3], q[6], q[8], q[9]
);
}
static inline Quadric PlaneToQuadric(const Vec4 &p)
{
float a2 = p.x * p.x;
float b2 = p.y * p.y;
float c2 = p.z * p.z;
float d2 = p.w * p.w;
float ab = p.x * p.y;
float ac = p.x * p.z;
float ad = p.x * p.w;
float bc = p.y * p.z;
float bd = p.y * p.w;
float cd = p.z * p.w;
return Quadric(a2, ab, ac, ad, b2, bc, bd, c2, cd, d2);
}
struct HalfEdge {
int vertex;
int face;
int next;
};
enum VertexFlags {
VF_VIRTUAL = 1 << 0,
VF_PINNED = 1 << 1,
VF_BOUNDARY = 1 << 2,
};
struct V3M1_HE {
Vec3 position;
uint16_t material;
uint16_t flags;
int he;
Quadric quadric;
};
struct Face {
int he;
Vec4 plane;
};
struct Edge {
int start;
int end;
};
static inline bool operator==(const Edge &lhs, const Edge &rhs)
{
return lhs.start == rhs.start && lhs.end == rhs.end;
}
static inline bool operator!=(const Edge &lhs, const Edge &rhs)
{
return lhs.start != rhs.start || lhs.end != rhs.end;
}
int Hash(const Edge &e)
{
return Hash(Slice<const char>((const char*)&e, sizeof(e)));
}
struct RankedEdge {
Vec3 new_position;
float rank;
int he;
};
struct Triangle {
Vec3 a;
Vec3 b;
Vec3 c;
};
static inline uint32_t FastRand(uint32_t *state)
{
uint32_t x = *state;
x += x;
if (x & 0x80000000L)
x ^= 0x88888EEFUL;
*state = x;
return x;
}
constexpr float CANT_COLLAPSE_RANK = 100.0f;
struct HalfEdgeMesh {
Vector<HalfEdge> m_edges;
Vector<V3M1_HE> m_vertices;
Vector<Face> m_faces;
HashMap<Edge, int> m_edge_map;
Vector<Triangle> m_triangles;
void Dump()
{
printf("Edges size: %d bytes (%d)\n", m_edges.ByteLength(), m_edges.Length());
printf("Vertices size: %d bytes (%d)\n", m_vertices.ByteLength(), m_vertices.Length());
printf("Faces size: %d bytes (%d)\n", m_faces.ByteLength(), m_faces.Length());
}
HalfEdgeMesh &Init()
{
m_edges.Init();
m_vertices.Init();
m_faces.Init();
m_edge_map.Init();
m_triangles.Init();
return *this;
}
void Free()
{
m_edges.Free();
m_vertices.Free();
m_faces.Free();
m_edge_map.Free();
m_triangles.Free();
}
/*
HalfEdge &HE(int n) { return m_edges[n]; }
Face &F(int n) { return m_faces[n]; }
V3M1_HE &V(int n) { return m_vertices[n]; }
*/
HalfEdge &HE(int n) { return m_edges.Data()[n]; }
Face &F(int n) { return m_faces.Data()[n]; }
V3M1_HE &V(int n) { return m_vertices.Data()[n]; }
void DebugDrawFace(int n, const Vec3 &off, const Vec3 &color = Vec3_X())
{
if (n == -1) {
Warn("drawing non-existent face");
return;
}
const int h0 = m_faces[n].he;
const int h1 = HE(h0).next;
const int h2 = HE(h1).next;
const Vec3 v0 = V(HE(h0).vertex).position;
const Vec3 v1 = V(HE(h1).vertex).position;
const Vec3 v2 = V(HE(h2).vertex).position;
printf("Drawing face at: (%f %f %f), (%f %f %f), (%f %f %f)\n", VEC3(v0), VEC3(v1), VEC3(v2));
debug_draw.Line(v0+off, v1+off, color);
debug_draw.Line(v1+off, v2+off, color);
debug_draw.Line(v2+off, v0+off, color);
}
void DebugDrawEdge(int n, const Vec3 &off, const Vec3 &color = Vec3_X())
{
printf("Drawing edge %d\n", n);
const int h0 = n;
const int h1 = h0^1;
const Vec3 v0 = V(HE(h0).vertex).position;
const Vec3 v1 = V(HE(h1).vertex).position;
debug_draw.Line(v0+off, v1+off, color);
}
void UpdateVertexQuadric(int n)
{
V3M1_HE &v = V(n);
v.quadric = EdgeVertexQuadric(v.he);
}
void UpdateNeighbourVertexQuadrics(int n)
{
const int h0 = V(n).he;
int he = h0;
do {
UpdateVertexQuadric(HE(he^1).vertex);
he = HE(he).next^1;
} while (he != h0);
}
void UpdateFacePlane(int n)
{
Face &f = F(n);
const int a = f.he;
const int b = HE(a).next;
const int c = HE(b).next;
const Vec3 va = V(HE(a).vertex).position;
const Vec3 vb = V(HE(b).vertex).position;
const Vec3 vc = V(HE(c).vertex).position;
const Vec3 ab = va - vb;
const Vec3 cb = vc - vb;
const Vec3 N = Normalize(Cross(cb, ab));
const float d = -Dot(N, va);
f.plane = Vec4(N.x, N.y, N.z, d);
}
void UpdateNeighbourFacePlanes(int n)
{
const int h0 = V(n).he;
int he = h0;
do {
UpdateFacePlane(HE(he).face);
he = HE(he).next^1;
} while (he != h0);
}
void CollapseRandomEdges(float threshold)
{
constexpr int N_EDGES = 5;
uint32_t rnd = 0x49f6428aUL;
int n_failed = 0;
if (threshold >= CANT_COLLAPSE_RANK)
threshold = CANT_COLLAPSE_RANK - 1.0f;
for (int i = 0; i < m_faces.Length(); i++)
UpdateFacePlane(i);
for (int i = 0; i < m_vertices.Length(); i++) {
if (IsBoundaryEdgeVertex(V(i).he))
V(i).flags |= VF_BOUNDARY;
UpdateVertexQuadric(i);
}
while (n_failed < 100) {
RankedEdge the_edge;
the_edge.rank = CANT_COLLAPSE_RANK;
const uint32_t rnd_div = m_edges.Length()/2;
for (int i = 0; i < N_EDGES; i++) {
int j = (FastRand(&rnd) % rnd_div)*2;
const RankedEdge e = RankEdge(j);
if (e.rank < the_edge.rank)
the_edge = e;
}
if (the_edge.rank < threshold) {
CollapseEdge(the_edge.he, the_edge.new_position);
n_failed = 0;
} else {
n_failed++;
}
}
}
RankedEdge RankEdge(int n)
{
RankedEdge re;
if (!CanCollapseEdge(n)) {
re.rank = CANT_COLLAPSE_RANK;
return re;
}
const int h0 = n;
const int h1 = h0^1;
const Mat4 Q = ToMat4(V(HE(h0).vertex).quadric + V(HE(h1).vertex).quadric);
Mat4 Qtmp = Q;
Qtmp[3] = 0;
Qtmp[7] = 0;
Qtmp[11] = 0;
Qtmp[15] = 1;
bool ok = false;
Mat4 Qi = Inverse(Qtmp, &ok);
if (ok) {
Vec4 p = Qi * Vec4(0, 0, 0, 1);
re.new_position = ToVec3(p);
if (IsNaN(re.new_position)) {
re.new_position = EdgeMiddlePoint(n);
p = ToVec4(re.new_position);
}
re.rank = fabs(Dot(p * Q, p));
re.he = n;
} else {
const Vec4 mp = ToVec4(EdgeMiddlePoint(n));
re.new_position = ToVec3(mp);
re.rank = fabs(Dot(mp * Q, mp));
re.he = n;
}
if (!CanCollapseEdgeAt(n, re.new_position))
re.rank += CANT_COLLAPSE_RANK;
return re;
}
Vec3 EdgeMiddlePoint(int n)
{
const int h0 = n;
const int h1 = h0^1;
const Vec3 v0 = V(HE(h0).vertex).position;
const Vec3 v1 = V(HE(h1).vertex).position;
return (v0 + v1) / Vec3(2);
}
Quadric FaceQuadric(int n)
{
return PlaneToQuadric(F(n).plane);
}
Quadric EdgeVertexQuadric(int n)
{
Quadric q = Quadric_Zero();
const int h0 = n;
int he = h0;
bool open = false;
do {
if (HE(he).face == -1) {
open = true;
break;
} else {
q += FaceQuadric(HE(he).face);
}
he = HE(he).next^1;
} while (he != h0);
if (open) {
he = h0^1;
while (HE(he).face != -1) {
q += FaceQuadric(HE(he).face);
he = HE(HE(he).next).next^1;
}
}
return q;
}
void PostCollapse(int v1)
{
UpdateVertexQuadric(v1);
UpdateNeighbourVertexQuadrics(v1);
UpdateNeighbourFacePlanes(v1);
}
void CollapseEdge3Triangles(int n, const Vec3 &pos)
{
const int h0 = n;
const int h1 = n^1;
const int v1 = HE(h1).vertex;
const int a = HE(h0).next;
const int b = HE(h1).next;
const int c = HE(b).next;
const int d = HE(a).next;
const int e = c^1;
const int f = a^1;
const int g = HE(e).next;
// move h1's vertex to new position
V(v1).position = pos;
// redirect all h0's vertex edges to h1's vertex
HE(c).vertex = v1;
HE(f).vertex = v1;
// fix remaining face
F(HE(g).face).he = g;
// reconnect edges
HE(g).next = d;
HE(d).next = b;
HE(b).next = g;
// reconnect face
HE(b).face = HE(g).face;
HE(d).face = HE(g).face;
// fix vertices
V(HE(h1).vertex).he = d;
V(HE(e).vertex).he = b;
V(HE(a).vertex).he = g;
// erase main and side edges
int rm[] = {h0, c, a};
EraseEdges(rm);
PostCollapse(v1);
}
void RedirectEdgesTo(int edge, int newvertex)
{
int he = edge;
do {
HE(he).vertex = newvertex;
he = HE(he).next^1;
} while (he != edge);
}
bool IsBoundaryEdgeVertex(int n)
{
bool open = false;
int he = n;
do {
if (HE(he).face == -1) {
open = true;
break;
}
he = HE(he).next^1;
} while (he != n);
return open;
}
void EraseEdge(int n)
{
int cur = n & ~1;
int last = (m_edges.Length() - 1) & ~1;
if (last != cur)
MoveEdge(last, cur);
m_edges.Resize(m_edges.Length() - 2);
}
void EraseEdges(int *rm)
{
if (rm[0] < rm[1]) std::swap(rm[0], rm[1]);
if (rm[1] < rm[2]) std::swap(rm[1], rm[2]);
if (rm[0] < rm[1]) std::swap(rm[0], rm[1]);
for (int i = 0; i < 3; i++)
EraseEdge(rm[i]);
}
void MoveEdge(int from, int to)
{
const int from2 = from^1;
const int to2 = to^1;
const int f0 = HE(from).face;
const int f1 = HE(from2).face;
if (f0 != -1) {
if (F(f0).he == from) {
F(f0).he = to;
}
int he = HE(HE(from).next).next;
HE(he).next = to;
}
if (f1 != -1) {
if (F(f1).he == from2) {
F(f1).he = to2;
}
int he = HE(HE(from2).next).next;
HE(he).next = to2;
}
const int v0 = HE(from).vertex;
const int v1 = HE(from2).vertex;
if (V(v0).he == from)
V(v0).he = to;
if (V(v1).he == from2)
V(v1).he = to2;
m_edges[to] = m_edges[from];
m_edges[to^1] = m_edges[from^1];
}
void MoveFace(int from, int to)
{
int h0 = F(from).he;
int h1 = HE(h0).next;
int h2 = HE(h1).next;
HE(h0).face = to;
HE(h1).face = to;
HE(h2).face = to;
m_faces[to] = m_faces[from];
}
void EraseFace(int n)
{
const int last = m_faces.Length()-1;
if (last != n)
MoveFace(last, n);
m_faces.Resize(m_faces.Length()-1);
}
void EraseFaces(int *rm)
{
if (rm[0] < rm[1]) std::swap(rm[0], rm[1]);
EraseFace(rm[0]);
EraseFace(rm[1]);
}
void ReassignVertex(int from, int to)
{
const int h0 = V(from).he;
int he = h0;
bool open = false;
do {
HE(he).vertex = to;
if (HE(he).face == -1) {
open = true;
break;
}
he = HE(he).next^1;
} while (he != h0);
if (open) {
he = h0^1;
while (HE(he).face != -1) {
he = HE(HE(he).next).next;
HE(he).vertex = to;
he = he^1;
}
}
}
void MoveVertex(int from, int to)
{
ReassignVertex(from, to);
m_vertices[to] = m_vertices[from];
}
void SwapVertices(int a, int b)
{
ReassignVertex(a, b);
ReassignVertex(b, a);
std::swap(m_vertices[a], m_vertices[b]);
}
// returns the length of vertices without virtual ones, this is also the
// index of the first virtual vertex
int MoveVirtualVerticesToEnd()
{
int last_non_virtual = -1;
for (int i = m_vertices.Length()-1; i >= 0; i--) {
if ((V(i).flags & VF_VIRTUAL) == 0) {
last_non_virtual = i;
break;
}
}
if (last_non_virtual == -1)
return 0;
int first_virtual = -1;
for (int i = 0; i < m_vertices.Length(); i++) {
if (V(i).flags & VF_VIRTUAL) {
first_virtual = i;
break;
}
}
if (first_virtual == -1)
return m_vertices.Length();
while (first_virtual < last_non_virtual) {
SwapVertices(first_virtual, last_non_virtual);
while (first_virtual < last_non_virtual && (V(first_virtual).flags & VF_VIRTUAL) == 0)
first_virtual++;
while (first_virtual < last_non_virtual && V(last_non_virtual).flags & VF_VIRTUAL)
last_non_virtual--;
}
return first_virtual;
}
void EraseVertex(int n)
{
const int last = m_vertices.Length()-1;
if (last != n)
MoveVertex(last, n);
m_vertices.Resize(m_vertices.Length()-1);
}
void CollectTriangle(int n)
{
const int h0 = n;
const int h1 = HE(h0).next;
const int h2 = HE(h1).next;
const Vec3 a = V(HE(h0).vertex).position;
const Vec3 b = V(HE(h1).vertex).position;
const Vec3 c = V(HE(h2).vertex).position;
m_triangles.Append({a, b, c});
}
// collect all triangles around the n's vertex, except two triangles
// next to the 'n' edge, 'a' vertex is always the vertex of 'n'
void CollectVertexTriangles(int n)
{
int he = HE(n).next^1;
int end = HE(HE(n^1).next).next;
do {
CollectTriangle(he);
he = HE(he).next^1;
} while (he != end);
}
bool Validate()
{
for (int i = 0; i < m_edges.Length(); i++) {
if (HE(i).face >= m_faces.Length()) {
Warn("%d edge's face is broken (%d)", i, HE(i).face);
return false;
}
}
return true;
}
bool CanCollapseEdgeAt(int n, const Vec3 &pos)
{
// check if normals are not flipping
const int h0 = n;
const int h1 = n^1;
m_triangles.Clear();
CollectVertexTriangles(h0);
CollectVertexTriangles(h1);
for (const auto &tri : m_triangles) {
const Vec3 alt_ab = pos - tri.b;
const Vec3 ab = tri.a - tri.b;
const Vec3 cb = tri.c - tri.b;
const Vec3 n0 = Cross(cb, ab);
const Vec3 n1 = Cross(cb, alt_ab);
if (Dot(n0, n1) < 0) {
return false;
}
}
return true;
}
bool CanCollapseEdge(int n)
{
const int h0 = n;
const int h1 = n^1;
if (V(HE(h0).vertex).flags & (VF_PINNED | VF_BOUNDARY))
return false;
if (V(HE(h1).vertex).flags & (VF_PINNED | VF_BOUNDARY))
return false;
/*
const bool open0 = IsBoundaryEdgeVertex(h0);
const bool open1 = IsBoundaryEdgeVertex(h1);
// one of the vertices is boundary
if (open0 || open1)
return false;
*/
// avoid closing triangle holes, leads to non-manifold geometry
{
const int v0 = HE(HE(h0).next).vertex;
const int v1 = HE(HE(h1).next).vertex;
int he = h0;
do {
int he2 = h1;
do {
int v = HE(he^1).vertex;
if (v == HE(he2^1).vertex && v != v0 && v != v1)
return false;
he2 = HE(he2).next^1;
} while (he2 != h1);
he = HE(he).next^1;
} while (he != h0);
}
/*
// XXX(nsf): seems like it's not enough, but I haven't seen a case
// where it failed
{
const int a = HE(h1).next;
const int f = HE(a).next;
const int L = HE(a^1).next;
const int g = HE(f^1).next;
const int h = HE(g).next;
if (HE(L).vertex == HE(h^1).vertex)
return false;
const int b = HE(h0).next;
const int e = HE(b).next;
const int c = HE(b^1).next;
const int i = HE(e^1).next;
const int k = HE(i).next;
if (HE(c).vertex == HE(k^1).vertex)
return false;
}
*/
// these checks also handle tetrahedron cases, they are not handled
// by triangle hole test above
{
const int b = HE(h0).next;
const int e = HE(b).next;
const int d = HE(HE(b^1).next).next;
const int i = HE(e^1).next;
if (i == (d^1)) {
return false;
}
}
{
const int a = HE(h1).next;
const int f = HE(a).next;
const int j = HE(HE(a^1).next).next;
const int g = HE(f^1).next;
if (g == (j^1)) {
return false;
}
}
return true;
}
// Assumes CanCollapseEdge is true
bool CollapseEdge(int n, const Vec3 &pos)
{
const int h0 = n;
const int h1 = h0^1;
EraseVertex(HE(h0).vertex);
// erase faces
int rm_faces[] = {HE(h0).face, HE(h1).face};
EraseFaces(rm_faces);
const int v1 = HE(h1).vertex;
// this is an unique case, where tri loop around h0's vertex is closed
// and has only 3 triangles
{
const int left = HE(HE(h1).next).next^1;
const int right = HE(h0).next^1;
if (HE(left).face != -1 && HE(left).face == HE(right).face) {
CollapseEdge3Triangles(n, pos);
return true;
}
}
// move vertex to new position
V(v1).position = pos;
// redirect h0's vertex edges to h1's vertex
RedirectEdgesTo(h0, v1);
const int b0 = HE(h0).next;
const int b1 = b0^1;
const int e = HE(b0).next;
// full case, two triangles of the tri loop on the right side
const int c = HE(b1).next;
const int d = HE(c).next;
// redirect edges
HE(d).next = e;
HE(e).next = c;
// redirect and fix face
F(HE(b1).face).he = e;
HE(e).face = HE(b1).face;
const int a = HE(h1).next;
const int f0 = HE(a).next;
const int f1 = f0^1;
// full case, two triangles of the tri loop on the left side
const int g = HE(f1).next;
const int h = HE(g).next;
// redirect edges
HE(h).next = a;
HE(a).next = g;
// redirect and fix face
F(HE(f1).face).he = a;
HE(a).face = HE(f1).face;
// fix vertex
V(HE(h1).vertex).he = e;
V(HE(f1).vertex).he = a;
V(HE(b0).vertex).he = d;
int rm_edges[] = {b0, f0, h0};
EraseEdges(rm_edges);
PostCollapse(v1);
return true;
}
int InsertEdgeMaybe(Edge e)
{
const bool opposite = e.start > e.end;
if (opposite)
std::swap(e.start, e.end);
const int *i = m_edge_map.Get(e);
if (i) {
return opposite ? *i ^ 1 : *i;
}
const int first = m_edges.Length();
HalfEdge *he0 = m_edges.Append();
he0->face = -1;
he0->next = -1;
he0->vertex = e.end;
V(e.end).he = first;
const int second = m_edges.Length();
HalfEdge *he1 = m_edges.Append();
he1->face = -1;
he1->next = -1;
he1->vertex = e.start;
V(e.start).he = second;
m_edge_map.Insert(e, first);
return opposite ? second : first;
}
void AddTriangle(int a, int b, int c, bool pinned = false)
{
const int hei0 = InsertEdgeMaybe({a, b});
const int hei1 = InsertEdgeMaybe({b, c});
const int hei2 = InsertEdgeMaybe({c, a});
HalfEdge *he0 = &m_edges[hei0];
HalfEdge *he1 = &m_edges[hei1];
HalfEdge *he2 = &m_edges[hei2];
if (pinned) {
V(he0->vertex).flags |= VF_PINNED;
V(he1->vertex).flags |= VF_PINNED;
V(he2->vertex).flags |= VF_PINNED;
}
const int fi = m_faces.Length();
Face *f = m_faces.Append();
f->he = hei2;
he0->face = fi;
he1->face = fi;
he2->face = fi;
he0->next = hei1;
he1->next = hei2;
he2->next = hei0;
}
int AddVertex(const V3M1_HE &v)
{
const int index = m_vertices.Length();
V3M1_HE *nv = m_vertices.Append();
*nv = v;
return index;
}
void Build(Slice<const V3N3M1> vertices, Slice<const uint32_t> indices)
{
m_edges.Clear();
m_vertices.Clear();
m_faces.Clear();
m_edge_map.Clear();
m_vertices.Resize(vertices.length);
for (int i = 0; i < vertices.length; i++) {
m_vertices[i].position = vertices[i].position;
m_vertices[i].material = vertices[i].material;
}
for (int i = 0; i < indices.length; i += 3) {
AddTriangle(indices[i+0], indices[i+1], indices[i+2]);
}
m_edge_map.Free();
m_edge_map.Init();
}
void Output(Vector<V3N3M1> &vertices, Vector<uint32_t> &indices,
Vector<Vec3> &virt_vertices, Vector<uint32_t> &virt_indices)
{
const int non_virt_len = MoveVirtualVerticesToEnd();
const int virt_len = m_vertices.Length() - non_virt_len;
vertices.Resize(non_virt_len);
for (int i = 0; i < non_virt_len; i++) {
vertices[i].position = m_vertices[i].position;
vertices[i].material = m_vertices[i].material;
vertices[i].normal = Vec3(0);
}
virt_vertices.Resize(virt_len+1);
for (int i = 0; i < virt_len; i++)
virt_vertices[1+i] = m_vertices[non_virt_len + i].position;
indices.Reserve(m_faces.Length() * 3);
indices.Clear();
virt_indices.Clear();
for (const auto &face : m_faces) {
int he = face.he;
const int v0 = HE(he).vertex;
he = HE(he).next;
const int v1 = HE(he).vertex;
he = HE(he).next;
const int v2 = HE(he).vertex;
const bool v0_is_virt = v0 >= non_virt_len;
const bool v1_is_virt = v1 >= non_virt_len;
const bool v2_is_virt = v2 >= non_virt_len;
if (v0_is_virt || v1_is_virt || v2_is_virt) {
const int v0i = v0_is_virt ? -(v0 - non_virt_len) - 1 : v0;
const int v1i = v1_is_virt ? -(v1 - non_virt_len) - 1 : v1;
const int v2i = v2_is_virt ? -(v2 - non_virt_len) - 1 : v2;
virt_indices.Append(v0i);
virt_indices.Append(v1i);
virt_indices.Append(v2i);
} else {
indices.Append(v0);
indices.Append(v1);
indices.Append(v2);
}
}
}
};
/*
half_edge_mesh.Build(vertices0, indices0);
printf("HalfEdgeMesh size: %d bytes\n",
half_edge_mesh.m_edges.ByteLength() +
half_edge_mesh.m_faces.ByteLength() +
half_edge_mesh.m_vertices.ByteLength());
auto &m = half_edge_mesh;
if (threshold != 0.0f)
m.CollapseRandomEdges(threshold * threshold);
printf("HalfEdgeMesh size (after simplification): %d bytes\n",
half_edge_mesh.m_edges.ByteLength() +
half_edge_mesh.m_faces.ByteLength() +
half_edge_mesh.m_vertices.ByteLength());
half_edge_mesh.Output(vertices, indices);
printf("Mesh size: %d bytes (threshold: %f)\n", vertices.ByteLength() + indices.ByteLength(), threshold);
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment