Subversion Repositories gelsvn

Rev

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

Rev 220 Rev 222
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) ) > 13)
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
						if (q.determinant()>= 1e-15)
162
// 						if (q.determinant()>= 1e-30)
163
						{
163
						{
164
								opt_pos = q.opt_pos(0.001);
164
								opt_pos = q.opt_pos(0.001);
165
								err = q.error(opt_pos);
165
								err = q.error(opt_pos);
166
						}
166
						}
167
						else
167
// 						else
168
						{
168
// 						{
169
								// To see the effectivenes of the scheme, we can create 
169
// 								// To see the effectivenes of the scheme, we can create 
170
								// Random errors for comparison. Uncomment to use:
170
// 								// Random errors for comparison. Uncomment to use:
171
								// 			float e0 = time_stamp + rand()/float(RAND_MAX);//q.error(v0);
171
// 								// 			float e0 = time_stamp + rand()/float(RAND_MAX);//q.error(v0);
172
								// 			float e1 = time_stamp + rand()/float(RAND_MAX);//q.error(v1);
172
// 								// 			float e1 = time_stamp + rand()/float(RAND_MAX);//q.error(v1);
173
 
173
 
174
								Vec3d v0(he->vert->pos);
174
// 								Vec3d v0(he->vert->pos);
175
								Vec3d v1(he->opp->vert->pos);
175
// 								Vec3d v1(he->opp->vert->pos);
176
								float e0 = q.error(v0);
176
// 								float e0 = q.error(v0);
177
								float e1 = q.error(v1);
177
// 								float e1 = q.error(v1);
178
		
178
		
179
								if(e0 < e1)
179
// 								if(e0 < e1)
180
								{
180
// 								{
181
										err = e0;
181
// 										err = e0;
182
										opt_pos = v0;
182
// 										opt_pos = v0;
183
								}
183
// 								}
184
								else
184
// 								else
185
								{
185
// 								{
186
										err = e1;
186
// 										err = e1;
187
										opt_pos = v1;
187
// 										opt_pos = v1;
188
								}
188
// 								}
189
						}			
189
// 						}			
190
 
190
 
191
						// Create SimplifyRec
191
						// Create SimplifyRec
192
						int he_index = he->touched;
192
						int he_index = he->touched;
193
						SimplifyRec simplify_rec;
193
						SimplifyRec simplify_rec;
194
						simplify_rec.opt_pos = opt_pos;
194
						simplify_rec.opt_pos = opt_pos;
195
						simplify_rec.err = err;
195
						simplify_rec.err = err;
196
						simplify_rec.he_index = he_index;
196
						simplify_rec.he_index = he_index;
197
						simplify_rec.time_stamp = halfedge_vec[he->touched].time_stamp;
197
						simplify_rec.time_stamp = halfedge_vec[he->touched].time_stamp;
198
						simplify_rec.visits = 0;
198
						simplify_rec.visits = 0;
199
						// push it.
199
						// push it.
200
						sim_queue.push(simplify_rec);
200
						sim_queue.push(simplify_rec);
201
						
201
						
202
				}
202
				}
203
		
203
		
204
 
204
 
205
				void QuadricSimplifier::update_onering_timestamp(VertexIter vi)
205
				void QuadricSimplifier::update_onering_timestamp(VertexIter vi)
206
				{
206
				{
207
						// For all emanating edges he
207
						// For all emanating edges he
208
						for(VertexCirculator vc(vi);
208
						for(VertexCirculator vc(vi);
209
								!vc.end(); ++vc)
209
								!vc.end(); ++vc)
210
						{
210
						{
211
								HalfEdgeIter he = vc.get_halfedge();
211
								HalfEdgeIter he = vc.get_halfedge();
212
								update_time_stamp(he);
212
								update_time_stamp(he);
213
						}
213
						}
214
				}
214
				}
215
 
215
 
216
				int QuadricSimplifier::collapse(SimplifyRec& simplify_rec)
216
				int QuadricSimplifier::collapse(SimplifyRec& simplify_rec)
217
				{
217
				{
218
						int he_index = simplify_rec.he_index;
218
						int he_index = simplify_rec.he_index;
219
						HalfEdgeIter he = halfedge_vec[he_index].he;
219
						HalfEdgeIter he = halfedge_vec[he_index].he;
220
 
220
 
221
						// Check the time stamp to verify that the simplification 
221
						// Check the time stamp to verify that the simplification 
222
						// record is the newest. If the halfedge has been removed
222
						// record is the newest. If the halfedge has been removed
223
						// the time stamp is -1 and the comparison will also fail.
223
						// the time stamp is -1 and the comparison will also fail.
224
						if(halfedge_vec[he_index].time_stamp == simplify_rec.time_stamp)
224
						if(halfedge_vec[he_index].time_stamp == simplify_rec.time_stamp)
225
						{
225
						{
226
								VertexIter v = he->opp->vert;
226
								VertexIter v = he->opp->vert;
227
								VertexIter n = he->vert;
227
								VertexIter n = he->vert;
228
 
228
 
229
								// If the edge is, in fact, collapsible
229
								// If the edge is, in fact, collapsible
230
								if(m.collapse_precond(he))
230
								if(m.collapse_precond(he))
231
								{
231
								{
232
 										// If our consistency checks pass, we are relatively
232
 										// If our consistency checks pass, we are relatively
233
 										// sure that the contraction does not lead to a face flip.
233
 										// sure that the contraction does not lead to a face flip.
234
										if(check_consistency(he, simplify_rec.opt_pos) && 
234
										if(check_consistency(he, simplify_rec.opt_pos) && 
235
											 check_consistency(he->opp, simplify_rec.opt_pos))
235
											 check_consistency(he->opp, simplify_rec.opt_pos))
236
										{
236
										{
237
 
237
 
238
												//cout << simplify_rec.err << " " << &(*he->vert) << endl;
238
												//cout << simplify_rec.err << " " << &(*he->vert) << endl;
239
												// Get QEM for both end points
239
												// Get QEM for both end points
240
												const QEM& Q1 = qem_vec[he->vert->touched];
240
												const QEM& Q1 = qem_vec[he->vert->touched];
241
												const QEM& Q2 = qem_vec[he->opp->vert->touched];
241
												const QEM& Q2 = qem_vec[he->opp->vert->touched];
242
												
242
												
243
												// Compute Q_new = Q_1 + Q_2
243
												// Compute Q_new = Q_1 + Q_2
244
												QEM q = Q1;
244
												QEM q = Q1;
245
												q += Q2;
245
												q += Q2;
246
												
246
												
247
												// Mark all halfedges that will be removed as dead
247
												// Mark all halfedges that will be removed as dead
248
												halfedge_vec[he->touched].halfedge_removed();
248
												halfedge_vec[he->touched].halfedge_removed();
249
												halfedge_vec[he->opp->touched].halfedge_removed();
249
												halfedge_vec[he->opp->touched].halfedge_removed();
250
						
250
						
251
												if(he->next->next->next == he)
251
												if(he->next->next->next == he)
252
												{
252
												{
253
														halfedge_vec[he->next->touched].halfedge_removed();
253
														halfedge_vec[he->next->touched].halfedge_removed();
254
														halfedge_vec[he->next->next->touched].halfedge_removed();
254
														halfedge_vec[he->next->next->touched].halfedge_removed();
255
												}
255
												}
256
												if(he->opp->next->next->next == he->opp)
256
												if(he->opp->next->next->next == he->opp)
257
												{
257
												{
258
														halfedge_vec[he->opp->next->touched].halfedge_removed();
258
														halfedge_vec[he->opp->next->touched].halfedge_removed();
259
														halfedge_vec[he->opp->next->next->touched].halfedge_removed();
259
														halfedge_vec[he->opp->next->next->touched].halfedge_removed();
260
												}
260
												}
261
												
261
												
262
												// Do collapse
262
												// Do collapse
263
												m.collapse_halfedge(he,false);
263
												m.collapse_halfedge(he,false);
264
												n->pos = Vec3f(simplify_rec.opt_pos);
264
												n->pos = Vec3f(simplify_rec.opt_pos);
265
												qem_vec[n->touched] = q;
265
												qem_vec[n->touched] = q;
266
												
266
												
267
												update_onering_timestamp(n);
267
												update_onering_timestamp(n);
268
												return 1;
268
												return 1;
269
										}
269
										}
270
								}
270
								}
271
								// 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
272
								// seen this simplify record less than 100 times, we try to
272
								// seen this simplify record less than 100 times, we try to
273
								// increase the error and store the record again. Maybe some
273
								// increase the error and store the record again. Maybe some
274
								// other contractions will make it more digestible later.
274
								// other contractions will make it more digestible later.
275
								if(simplify_rec.visits < 10)
275
								if(simplify_rec.visits < 10)
276
								{
276
								{
277
										simplify_rec.err += 5*simplify_rec.err + 1e-10;
277
										simplify_rec.err += 5*simplify_rec.err + 1e-10;
278
										++simplify_rec.visits;
278
										++simplify_rec.visits;
279
										sim_queue.push(simplify_rec);
279
										sim_queue.push(simplify_rec);
280
								}
280
								}
281
						}
281
						}
282
						// Ok the time stamp did not match. Create a new record.
282
						// Ok the time stamp did not match. Create a new record.
283
						else if(halfedge_vec[he_index].time_stamp != -1)
283
						else if(halfedge_vec[he_index].time_stamp != -1)
284
								push_simplify_rec(he);
284
								push_simplify_rec(he);
285
 
285
 
286
						return 0;
286
						return 0;
287
				}
287
				}
288
 
288
 
289
				void QuadricSimplifier::reduce(int max_work)
289
				void QuadricSimplifier::reduce(int max_work)
290
				{
290
				{
291
						// Set t = 0 for all halfedges
291
						// Set t = 0 for all halfedges
292
						int i=0;
292
						int i=0;
293
						for(HalfEdgeIter he = m.halfedges_begin();
293
						for(HalfEdgeIter he = m.halfedges_begin();
294
								he != m.halfedges_end(); ++he, ++i)
294
								he != m.halfedges_end(); ++he, ++i)
295
								halfedge_vec[i].he = he;
295
								halfedge_vec[i].he = he;
296
 
296
 
297
						cout << "Computing quadrics" << endl;
297
						cout << "Computing quadrics" << endl;
298
		
298
		
299
						// For all vertices, compute quadric and store in qem_vec
299
						// For all vertices, compute quadric and store in qem_vec
300
						for(VertexIter vi=m.vertices_begin(); vi != m.vertices_end(); ++vi)
300
						for(VertexIter vi=m.vertices_begin(); vi != m.vertices_end(); ++vi)
301
						{
301
						{
302
								Vec3d p(vi->pos);
302
								Vec3d p(vi->pos);
303
								QEM q;
303
								QEM q;
304
								for(VertexCirculator vc(vi); !vc.end(); ++vc)
304
								for(VertexCirculator vc(vi); !vc.end(); ++vc)
305
								{
305
								{
306
										FaceIter f = vc.get_face();
306
										FaceIter f = vc.get_face();
307
										if(f != NULL_FACE_ITER)
307
										if(f != NULL_FACE_ITER)
308
										{
308
										{
309
												Vec3d n(normal(f));
309
												Vec3d n(normal(f));
310
												double a = area(f);
310
												double a = area(f);
311
												q += QEM(p, n, a / 3.0);
311
												q += QEM(p, n, a / 3.0);
312
										}
312
										}
313
								}
313
								}
314
								qem_vec[vi->touched] = q;
314
								qem_vec[vi->touched] = q;
315
						}
315
						}
316
						cout << "Pushing initial halfedges" << endl;
316
						cout << "Pushing initial halfedges" << endl;
317
 
317
 
318
						for(HalfEdgeIter he = m.halfedges_begin();
318
						for(HalfEdgeIter he = m.halfedges_begin();
319
								he != m.halfedges_end(); ++he)
319
								he != m.halfedges_end(); ++he)
320
								// For all halfedges, 
320
								// For all halfedges, 
321
						{
321
						{
322
								if(halfedge_vec[he->touched].time_stamp == 0)
322
								if(halfedge_vec[he->touched].time_stamp == 0)
323
								{
323
								{
324
										update_time_stamp(he);
324
										update_time_stamp(he);
325
										push_simplify_rec(he);
325
										push_simplify_rec(he);
326
								}
326
								}
327
						}
327
						}
328
 
328
 
329
						cout << "Simplify" << endl;
329
						cout << "Simplify" << endl;
330
 
330
 
331
						int work = 0;
331
						int work = 0;
332
						while(!sim_queue.empty() && work < max_work)
332
						while(!sim_queue.empty() && work < max_work)
333
						{
333
						{
334
								SimplifyRec simplify_record = sim_queue.top();
334
								SimplifyRec simplify_record = sim_queue.top();
335
								sim_queue.pop();
335
								sim_queue.pop();
336
 
336
 
337
								work += collapse(simplify_record);
337
								work += collapse(simplify_record);
338
								if((work % 100) == 0)
338
								if((work % 100) == 0)
339
								{
339
								{
340
										cout << "work = " << work << endl;
340
										cout << "work = " << work << endl;
341
										cout << "sim Q size = " << sim_queue.size() << endl;
341
										cout << "sim Q size = " << sim_queue.size() << endl;
342
								}
342
								}
343
						}
343
						}
344
				}
344
				}
345
		}
345
		}
346
 
346
 
347
		void quadric_simplify(Manifold& m, int max_work)
347
		void quadric_simplify(Manifold& m, int max_work)
348
		{
348
		{
349
				srand(1210);
349
				srand(1210);
350
				QuadricSimplifier qsim(m);
350
				QuadricSimplifier qsim(m);
351
				qsim.reduce(max_work);
351
				qsim.reduce(max_work);
352
		}
352
		}
353
 
353
 
354
}
354
}
355
 
355