Subversion Repositories gelsvn

Rev

Rev 215 | Rev 220 | Go to most recent revision | Only display areas with differences | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed

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