Subversion Repositories gelsvn

Rev

Rev 417 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
149 jab 1
#include <queue>
2
#include <iostream>
3
#include "quadric_simplify.h"
4
#include "smooth.h"
5
#include "HMesh/VertexCirculator.h"
6
#include "Geometry/QEM.h"
7
 
8
 
150 jab 9
namespace HMesh
149 jab 10
{
357 jab 11
	using namespace CGLA;
12
	using namespace std;
13
	using namespace GEO;
14
	using namespace HMesh;
15
 
16
	namespace
17
	{
18
		/* We create a record for each halfedge where we can keep its time
19
		stamp. If the time stamp on the halfedge record is bigger than
20
		the stamp on the simplification record, we cannot use the 
21
		simplification record (see below). */
22
		struct HalfEdgeRec
215 jab 23
				{
357 jab 24
					HalfEdgeIter he;
25
					int time_stamp;
26
					void halfedge_removed() {time_stamp = -1;}
27
					HalfEdgeRec(): time_stamp(0) {}
215 jab 28
				};
357 jab 29
 
30
		/* The simpliciation record contains information about a potential
31
		edge contraction */
32
		struct SimplifyRec
33
		{
34
			Vec3d opt_pos;  // optimal vertex position
35
			int he_index;   // Index (into HalfEdgeRec vector) of edge
36
							// we want to contract
37
			float err;      // Error associated with contraction
38
			int time_stamp; // Time stamp (see comment on HalfEdgeRec)
39
			int visits;     // Visits (number of times we considered this 
40
							// record).
41
		};
42
 
43
		bool operator<(const SimplifyRec& s1, const SimplifyRec& s2)
44
		{
45
			return s1.err > s2.err;
46
		}
47
 
48
		class QuadricSimplifier
49
		{
50
			typedef priority_queue<SimplifyRec> SimplifyQueue;
51
			typedef vector<QEM> QEMVec;
52
			typedef vector<HalfEdgeRec> HalfEdgeVec;
53
 
54
			Manifold& m;
55
			HalfEdgeVec halfedge_vec;
56
			QEMVec qem_vec;
57
			SimplifyQueue sim_queue;
58
			double singular_thresh;
367 jab 59
			bool choose_optimal_positions;
357 jab 60
 
61
			/* Compute the error associated with contraction of he and the
62
				optimal position of resulting vertex. */
63
			void push_simplify_rec(HalfEdgeIter he);
64
 
65
			/* Check whether the contraction is valid. See below for details*/
66
			bool check_consistency(HalfEdgeIter he, const Vec3d& opt_pos);
67
 
68
			/* Update the time stamp of a halfedge. A halfedge and its opp edge
69
				may have different stamps. We choose a stamp that is greater
70
				than either and assign to both.*/
71
			void update_time_stamp(HalfEdgeIter he)
72
			{
73
				int time_stamp = s_max(halfedge_vec[he->touched].time_stamp,
74
									   halfedge_vec[he->opp->touched].time_stamp);
75
				time_stamp++;
76
				halfedge_vec[he->touched].time_stamp = time_stamp;
77
				halfedge_vec[he->opp->touched].time_stamp = time_stamp;
78
			}
79
 
80
			/* Update time stamps for all halfedges in one ring of vi */
81
			void update_onering_timestamp(VertexIter vi);
82
 
83
			/* Perform a collapse - if conditions are met. Returns 1 or 0 
84
				accordingly. */
85
			int collapse(SimplifyRec& simplify_rec);
86
 
87
		public:
88
 
89
				/* Create a simplifier for a manifold */
367 jab 90
				QuadricSimplifier(Manifold& _m, double _singular_thresh, bool _choose_optimal_positions): 
357 jab 91
				m(_m), 
92
				halfedge_vec(m.no_halfedges()), 
93
				qem_vec(m.no_vertices()),
94
				singular_thresh(_singular_thresh),
367 jab 95
				choose_optimal_positions(_choose_optimal_positions)
357 jab 96
			{
97
					// Enumerate vertices
98
					m.enumerate_vertices();
99
 
100
					// Enumerate halfedges
101
					m.enumerate_halfedges();
102
			}
103
 
104
			/* Simplify doing at most max_work contractions */
105
			void reduce(int max_work);
106
		};
107
 
108
		bool QuadricSimplifier::check_consistency(HalfEdgeIter he, 
109
												  const Vec3d& opt_pos)
110
		{
111
			VertexIter v0 = he->vert;
112
			VertexIter v1 = he->opp->vert;
113
			Vec3d p0(v0->pos);
114
 
115
			/* This test is inspired by Garland's Ph.D. thesis. We try
116
				to detect whether flipped triangles will occur by sort of
117
				ensuring that the new vertex is in the hull of the one rings
118
				of the vertices at either end of the edge being contracted 
119
 
120
				I also had an additional check intended to ensure that poor valencies
121
				would not be introduced, but it seemed to be unnecessary.
122
 
123
				*/
124
 
125
			for(VertexCirculator vc(v0); !vc.end(); ++vc)
126
			{
127
				HalfEdgeIter h = vc.get_halfedge();
128
 
129
				if(h->vert != v1 && h->next->vert != v1)
215 jab 130
				{
357 jab 131
					Vec3d pa(h->vert->pos);
132
					Vec3d pb(h->next->vert->pos);
133
 
134
					Vec3d dir = normalize(pb-pa);
135
 
136
					Vec3d n = p0 - pa;
137
					n = n - dir * dot(dir,n);
138
 
139
					if(dot(n,opt_pos - pa) <= 0)
140
						return false;
215 jab 141
				}
357 jab 142
			}
149 jab 143
 
357 jab 144
			return true;
145
		}
146
 
147
 
148
		void QuadricSimplifier::push_simplify_rec(HalfEdgeIter he)
149
		{
150
			// Get QEM for both end points
151
			const QEM& Q1 = qem_vec[he->vert->touched];
152
			const QEM& Q2 = qem_vec[he->opp->vert->touched];
153
 
154
			QEM q = Q1;
155
			q += Q2;
156
 
157
			float err;
158
			Vec3d opt_pos(0);
159
			Vec3d opt_origin = Vec3d(he->vert->pos+he->opp->vert->pos)/2.0;
367 jab 160
			if(choose_optimal_positions)
357 jab 161
				opt_pos = q.opt_pos(singular_thresh,opt_origin);
162
			else
367 jab 163
			{
164
				Vec3d p1(he->vert->pos), p2(he->opp->vert->pos);
165
				float err1 = q.error(p1);
166
				float err2 = q.error(p2);
167
				if(err1<err2) 
168
				{
169
					err = err1;
170
					opt_pos = p1;
171
				}
172
				else
173
				{
174
					err = err2;
175
					opt_pos = p2;				
176
				}
177
			}
357 jab 178
 
179
			err = q.error(opt_pos);
180
 
181
			// Create SimplifyRec
182
			int he_index = he->touched;
183
			SimplifyRec simplify_rec;
184
			simplify_rec.opt_pos = opt_pos;
185
			simplify_rec.err = err;
186
			simplify_rec.he_index = he_index;
187
			simplify_rec.time_stamp = halfedge_vec[he->touched].time_stamp;
188
			simplify_rec.visits = 0;
189
			// push it.
190
			sim_queue.push(simplify_rec);
191
 
192
		}
193
 
194
 
195
		void QuadricSimplifier::update_onering_timestamp(VertexIter vi)
196
		{
197
			// For all emanating edges he
198
			for(VertexCirculator vc(vi);
199
				!vc.end(); ++vc)
200
			{
201
				HalfEdgeIter he = vc.get_halfedge();
202
				update_time_stamp(he);
203
				push_simplify_rec(he);
204
			}
205
		}
206
 
207
		int QuadricSimplifier::collapse(SimplifyRec& simplify_rec)
208
		{
209
			int he_index = simplify_rec.he_index;
210
			HalfEdgeIter he = halfedge_vec[he_index].he;
211
 
212
			// Check the time stamp to verify that the simplification 
213
			// record is the newest. If the halfedge has been removed
214
			// the time stamp is -1 and the comparison will also fail.
215
			if(halfedge_vec[he_index].time_stamp == simplify_rec.time_stamp)
216
			{
217
				VertexIter v = he->opp->vert;
218
				VertexIter n = he->vert;
219
 
220
				// If the edge is, in fact, collapsible
221
				if(m.collapse_precond(he))
215 jab 222
				{
357 jab 223
					// If our consistency checks pass, we are relatively
224
					// sure that the contraction does not lead to a face flip.
225
					if(check_consistency(he, simplify_rec.opt_pos) && 
226
					   check_consistency(he->opp, simplify_rec.opt_pos))
227
					{
215 jab 228
 
521 s042372 229
 
215 jab 230
						// Get QEM for both end points
231
						const QEM& Q1 = qem_vec[he->vert->touched];
232
						const QEM& Q2 = qem_vec[he->opp->vert->touched];
357 jab 233
 
234
						// Compute Q_new = Q_1 + Q_2
215 jab 235
						QEM q = Q1;
236
						q += Q2;
315 jab 237
 
357 jab 238
						// Mark all halfedges that will be removed as dead
239
						halfedge_vec[he->touched].halfedge_removed();
240
						halfedge_vec[he->opp->touched].halfedge_removed();
215 jab 241
 
357 jab 242
						if(he->next->next->next == he)
215 jab 243
						{
357 jab 244
							halfedge_vec[he->next->touched].halfedge_removed();
245
							halfedge_vec[he->next->next->touched].halfedge_removed();
215 jab 246
						}
357 jab 247
						if(he->opp->next->next->next == he->opp)
149 jab 248
						{
357 jab 249
							halfedge_vec[he->opp->next->touched].halfedge_removed();
250
							halfedge_vec[he->opp->next->next->touched].halfedge_removed();
251
						}
149 jab 252
 
357 jab 253
						// Do collapse
254
						m.collapse_halfedge(he,false);
255
						n->pos = Vec3f(simplify_rec.opt_pos);
256
						qem_vec[n->touched] = q;
257
 
258
						update_onering_timestamp(n);
259
						return 1;
260
					}
149 jab 261
				}
357 jab 262
				// If we are here, the collapse was not allowed. If we have
263
				// seen this simplify record less than 100 times, we try to
264
				// increase the error and store the record again. Maybe some
265
				// other contractions will make it more digestible later.
266
				if(simplify_rec.visits < 100)
215 jab 267
				{
373 jrf 268
					simplify_rec.err *= 1.01f;
357 jab 269
					++simplify_rec.visits;
270
					sim_queue.push(simplify_rec);
149 jab 271
				}
357 jab 272
			}
273
			return 0;
149 jab 274
		}
357 jab 275
 
276
		void QuadricSimplifier::reduce(int max_work)
215 jab 277
		{
357 jab 278
			// Set t = 0 for all halfedges
279
			int i=0;
280
			for(HalfEdgeIter he = m.halfedges_begin();
281
				he != m.halfedges_end(); ++he, ++i)
282
				halfedge_vec[i].he = he;
283
 
284
			cout << "Computing quadrics" << endl;
285
 
286
			// For all vertices, compute quadric and store in qem_vec
287
			for(VertexIter vi=m.vertices_begin(); vi != m.vertices_end(); ++vi)
288
			{
289
				Vec3d p(vi->pos);
362 jab 290
				Vec3d vn(normal(vi));
357 jab 291
				QEM q;
292
				for(VertexCirculator vc(vi); !vc.end(); ++vc)
293
				{
294
					FaceIter f = vc.get_face();
295
					if(f != NULL_FACE_ITER)
296
					{
297
						Vec3d n(normal(f));
298
						double a = area(f);
299
						q += QEM(p, n, a / 3.0);
300
					}
363 jab 301
					if ((f == NULL_FACE_ITER || 
302
						vc.get_opp_halfedge()->face ==  NULL_FACE_ITER) && sqr_length(vn) > 0.0)
362 jab 303
					{
304
						Vec3d edge = Vec3d(vc.get_halfedge()->vert->pos)-p;
305
						double edge_len = sqr_length(edge); 
306
						if(edge_len > 0.0)
307
							{
308
								Vec3d n = cross(vn, edge);
309
								q += QEM(p, n, 2*edge_len);
310
							}
311
					}
357 jab 312
				}
313
				qem_vec[vi->touched] = q;
314
			}
315
			cout << "Pushing initial halfedges" << endl;
316
 
317
			for(HalfEdgeIter he = m.halfedges_begin();
318
				he != m.halfedges_end(); ++he)
319
				// For all halfedges, 
320
			{
321
				if(halfedge_vec[he->touched].time_stamp == 0)
322
				{
323
					update_time_stamp(he);
324
					push_simplify_rec(he);
325
				}
326
			}
327
 
328
			cout << "Simplify" << endl;
329
 
330
			int work = 0;
331
			while(!sim_queue.empty() && work < max_work)
332
			{
333
				SimplifyRec simplify_record = sim_queue.top();
334
				sim_queue.pop();
335
 
363 jab 336
				work += 2*collapse(simplify_record);
357 jab 337
				if((work % 100) == 0)
338
				{
339
					cout << "work = " << work << endl;
340
					cout << "sim Q size = " << sim_queue.size() << endl;
341
				}
342
			}
215 jab 343
		}
357 jab 344
	}
345
 
367 jab 346
	void quadric_simplify(Manifold& m, double keep_fraction, double singular_thresh, bool choose_optimal_positions)
357 jab 347
	{
417 jrf 348
		gel_srand(1210);
363 jab 349
		int F = m.no_faces();
350
		int max_work = max(0, F- static_cast<int>(keep_fraction * F));
367 jab 351
		QuadricSimplifier qsim(m, singular_thresh, choose_optimal_positions);
357 jab 352
		qsim.reduce(max_work);
353
	}
354
 
149 jab 355
}