Subversion Repositories gelsvn

Rev

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

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