├── .gitignore
├── Algorithms
├── Algorithm.cs
└── PairContract
│ ├── Pair.cs
│ └── PairContract.cs
├── App.config
├── Datastructures
├── Mesh.cs
├── Triangle.cs
├── Vertex.cs
└── VertexSplit.cs
├── LICENSE
├── MeshSimplify.csproj
├── MeshSimplify.sln
├── ObjIO.cs
├── Options.cs
├── Program.cs
├── Properties
└── AssemblyInfo.cs
├── README.md
├── Testdata
├── bunny.obj
└── cow.obj
└── packages.config
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | x64/
19 | x86/
20 | build/
21 | bld/
22 | [Bb]in/
23 | [Oo]bj/
24 |
25 | # Visual Studo 2015 cache/options directory
26 | .vs/
27 |
28 | # MSTest test Results
29 | [Tt]est[Rr]esult*/
30 | [Bb]uild[Ll]og.*
31 |
32 | # NUNIT
33 | *.VisualState.xml
34 | TestResult.xml
35 |
36 | # Build Results of an ATL Project
37 | [Dd]ebugPS/
38 | [Rr]eleasePS/
39 | dlldata.c
40 |
41 | *_i.c
42 | *_p.c
43 | *_i.h
44 | *.ilk
45 | *.meta
46 | # *.obj
47 | *.pch
48 | *.pdb
49 | *.pgc
50 | *.pgd
51 | *.rsp
52 | *.sbr
53 | *.tlb
54 | *.tli
55 | *.tlh
56 | *.tmp
57 | *.tmp_proj
58 | *.log
59 | *.vspscc
60 | *.vssscc
61 | .builds
62 | *.pidb
63 | *.svclog
64 | *.scc
65 |
66 | # Chutzpah Test files
67 | _Chutzpah*
68 |
69 | # Visual C++ cache files
70 | ipch/
71 | *.aps
72 | *.ncb
73 | *.opensdf
74 | *.sdf
75 | *.cachefile
76 |
77 | # Visual Studio profiler
78 | *.psess
79 | *.vsp
80 | *.vspx
81 |
82 | # TFS 2012 Local Workspace
83 | $tf/
84 |
85 | # Guidance Automation Toolkit
86 | *.gpState
87 |
88 | # ReSharper is a .NET coding add-in
89 | _ReSharper*/
90 | *.[Rr]e[Ss]harper
91 | *.DotSettings.user
92 |
93 | # JustCode is a .NET coding addin-in
94 | .JustCode
95 |
96 | # TeamCity is a build add-in
97 | _TeamCity*
98 |
99 | # DotCover is a Code Coverage Tool
100 | *.dotCover
101 |
102 | # NCrunch
103 | _NCrunch_*
104 | .*crunch*.local.xml
105 |
106 | # MightyMoose
107 | *.mm.*
108 | AutoTest.Net/
109 |
110 | # Web workbench (sass)
111 | .sass-cache/
112 |
113 | # Installshield output folder
114 | [Ee]xpress/
115 |
116 | # DocProject is a documentation generator add-in
117 | DocProject/buildhelp/
118 | DocProject/Help/*.HxT
119 | DocProject/Help/*.HxC
120 | DocProject/Help/*.hhc
121 | DocProject/Help/*.hhk
122 | DocProject/Help/*.hhp
123 | DocProject/Help/Html2
124 | DocProject/Help/html
125 |
126 | # Click-Once directory
127 | publish/
128 |
129 | # Publish Web Output
130 | *.[Pp]ublish.xml
131 | *.azurePubxml
132 | # TODO: Comment the next line if you want to checkin your web deploy settings
133 | # but database connection strings (with potential passwords) will be unencrypted
134 | *.pubxml
135 | *.publishproj
136 |
137 | # NuGet Packages
138 | *.nupkg
139 | # The packages folder can be ignored because of Package Restore
140 | **/packages/*
141 | # except build/, which is used as an MSBuild target.
142 | !**/packages/build/
143 | # Uncomment if necessary however generally it will be regenerated when needed
144 | #!**/packages/repositories.config
145 |
146 | # Windows Azure Build Output
147 | csx/
148 | *.build.csdef
149 |
150 | # Windows Store app package directory
151 | AppPackages/
152 |
153 | # Others
154 | *.[Cc]ache
155 | ClientBin/
156 | [Ss]tyle[Cc]op.*
157 | ~$*
158 | *~
159 | *.dbmdl
160 | *.dbproj.schemaview
161 | *.pfx
162 | *.publishsettings
163 | node_modules/
164 | bower_components/
165 |
166 | # RIA/Silverlight projects
167 | Generated_Code/
168 |
169 | # Backup & report files from converting an old project file
170 | # to a newer Visual Studio version. Backup files are not needed,
171 | # because we have git ;-)
172 | _UpgradeReport_Files/
173 | Backup*/
174 | UpgradeLog*.XML
175 | UpgradeLog*.htm
176 |
177 | # SQL Server files
178 | *.mdf
179 | *.ldf
180 |
181 | # Business Intelligence projects
182 | *.rdl.data
183 | *.bim.layout
184 | *.bim_*.settings
185 |
186 | # Microsoft Fakes
187 | FakesAssemblies/
188 |
189 | # Node.js Tools for Visual Studio
190 | .ntvs_analysis.dat
191 |
192 | # Visual Studio 6 build log
193 | *.plg
194 |
195 | # Visual Studio 6 workspace options file
196 | *.opt
197 |
--------------------------------------------------------------------------------
/Algorithms/Algorithm.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace MeshSimplify.Algorithms {
4 | ///
5 | /// Die abstrakte Basisklasse von der jede konkrete Implementierung abgeleitet werden muss.
6 | ///
7 | public abstract class Algorithm : IDisposable {
8 | ///
9 | /// Gewährt der Instanz Zugriff auf etwaige Kommandozeileneinstellungen.
10 | ///
11 | protected Options options;
12 |
13 | ///
14 | /// Initialisiert eine neue Instanz der Algorithm-Klasse.
15 | ///
16 | ///
17 | /// Die Argumente, die dem Algorithmus zur Verfügung gestellt werden.
18 | ///
19 | public Algorithm(Options opts) {
20 | options = opts;
21 | }
22 |
23 | ///
24 | /// Vereinfacht die angegebene Eingabemesh.
25 | ///
26 | ///
27 | /// Die zu vereinfachende Mesh.
28 | ///
29 | ///
30 | /// Die Anzahl der Facetten, die die erzeugte Vereinfachung anstreben soll.
31 | ///
32 | ///
33 | /// true, um VertexSplit Einträge für die Mesh zu erzeugen; andernfalls false.
34 | ///
35 | ///
36 | /// true, um diagnostische Ausgaben während der Vereinfachung zu erzeugen.
37 | ///
38 | ///
39 | /// Die erzeugte vereinfachte Mesh.
40 | ///
41 | public abstract Mesh Simplify(Mesh input, int targetFaceCount, bool createSplitRecords,
42 | bool verbose);
43 |
44 | ///
45 | /// Gibt alle Ressourcen der Instanz frei.
46 | ///
47 | public abstract void Dispose();
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Algorithms/PairContract/Pair.cs:
--------------------------------------------------------------------------------
1 | using OpenTK;
2 | using System;
3 | using System.Collections.Generic;
4 |
5 | namespace MeshSimplify.Algorithms {
6 | ///
7 | /// Repräsentiert ein Vertexpaar.
8 | ///
9 | ///
10 | /// Pair Instanzen sind sortierbar, wobei aufsteigend nach Kosten sortiert wird,
11 | /// d.h. die Pair Instanz mit den kleinsten Kosten steht vorne.
12 | ///
13 | public class Pair : IComparable {
14 | ///
15 | /// Der erste Vertex des Paares.
16 | ///
17 | public int Vertex1;
18 |
19 | ///
20 | /// Der zweite Vertex des Paares.
21 | ///
22 | public int Vertex2;
23 |
24 | ///
25 | /// Die minimalen Kosten für die Kontraktion des Paares.
26 | ///
27 | public double Cost;
28 |
29 | ///
30 | /// Die optimale Position für die Kontraktion des Paares.
31 | ///
32 | public Vector3d Target;
33 |
34 | ///
35 | /// Initialisiert eine neue Instanz der Pair-Klasse.
36 | ///
37 | ///
38 | /// Der erste Vertex des Paares.
39 | ///
40 | ///
41 | /// Der zweite Vertex des Paares.
42 | ///
43 | ///
44 | /// Die optimale Position für die Kontraktion der beiden Vertices.
45 | ///
46 | ///
47 | /// Die minimalen Kosten für die Kontraktion der beiden Vertices.
48 | ///
49 | public Pair(int v1, int v2, Vector3d target, double cost) {
50 | Vertex1 = v1;
51 | Vertex2 = v2;
52 | Target = target;
53 | Cost = cost;
54 | }
55 |
56 | ///
57 | /// Converts this instance to a string representation.
58 | ///
59 | ///
60 | /// A string representation of this instance.
61 | ///
62 | public override string ToString() {
63 | return "(" + Vertex1 + "," + Vertex2 + ")";
64 | }
65 |
66 | ///
67 | /// Compares this instance to the specified Pair instance.
68 | ///
69 | ///
70 | /// The Pair instance to compare this instance to.
71 | ///
72 | ///
73 | /// 1 if this instance is greater than the other instance, or -1 if this instance
74 | /// is less than the other instance, or 0 if both instances are equal.
75 | ///
76 | public int CompareTo(Pair other) {
77 | if (Cost > other.Cost)
78 | return 1;
79 | if (Cost < other.Cost)
80 | return -1;
81 | return 0;
82 | }
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/Algorithms/PairContract/PairContract.cs:
--------------------------------------------------------------------------------
1 | using OpenTK;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.ComponentModel;
5 | using System.Linq;
6 | using System.Text;
7 |
8 | namespace MeshSimplify.Algorithms {
9 | ///
10 | /// Implementiert den 'Pair-Contract' Algorithmus nach Garland's
11 | /// "Surface Simplification Using Quadric Error Metrics"
12 | /// Methode.
13 | ///
14 | [DisplayName("[Gar97]")]
15 | public class PairContract : Algorithm {
16 | ///
17 | /// Die Q-Matrizen der Vertices.
18 | ///
19 | IDictionary Q;
20 |
21 | ///
22 | /// Die Vertices der Mesh.
23 | ///
24 | IDictionary vertices = new SortedDictionary();
25 |
26 | ///
27 | /// Die Facetten der Mesh.
28 | ///
29 | IList faces;
30 |
31 | ///
32 | /// Die Vertexpaare welche nach und nach kontraktiert werden.
33 | ///
34 | ISet pairs = new SortedSet();
35 |
36 | ///
37 | /// Der distanzbasierte Schwellwert zur Bestimmung von gültigen Vertexpaaren.
38 | ///
39 | readonly double distanceThreshold;
40 |
41 | ///
42 | /// true für ausführliche Ausgaben auf der Standardausgabe.
43 | ///
44 | bool verbose;
45 |
46 | ///
47 | /// Speichert für jeden Vertex Referenzen auf seine inzidenten Facetten.
48 | ///
49 | IDictionary> incidentFaces = new Dictionary>();
50 |
51 | ///
52 | /// Speichert für jeden Vertex Referenzen auf die Vertexpaare zu denen er gehört.
53 | ///
54 | IDictionary> pairsPerVertex = new Dictionary>();
55 |
56 | ///
57 | /// Stack der ursprünglichen Indices der Vertices, die kontraktiert wurden.
58 | ///
59 | Stack contractionIndices = new Stack();
60 |
61 | ///
62 | /// Speichert VertexSplit Einträge zur Erstellung einer Progressive Mesh.
63 | ///
64 | Stack splits = new Stack();
65 |
66 | ///
67 | /// Gibt an, ob VertexSplit Einträge erzeugt werden sollen.
68 | ///
69 | bool createSplitRecords;
70 |
71 | ///
72 | /// Initialisiert eine neue Instanz der PairContract-Klasse.
73 | ///
74 | ///
75 | /// Die Argumente, die dem Algorithmus zur Verfügung gestellt werden.
76 | ///
77 | public PairContract(Options opts)
78 | : base(opts) {
79 | distanceThreshold = opts.DistanceThreshold;
80 | Print("PairContract initialized with distanceThreshold = {0}",
81 | distanceThreshold);
82 | }
83 |
84 | ///
85 | /// Vereinfacht die angegebene Eingabemesh.
86 | ///
87 | ///
88 | /// Die zu vereinfachende Mesh.
89 | ///
90 | ///
91 | /// Die Anzahl der Facetten, die die erzeugte Vereinfachung anstreben soll.
92 | ///
93 | ///
94 | /// true, um VertexSplit Einträge für die Mesh zu erzeugen; andernfalls false.
95 | ///
96 | ///
97 | /// true, um diagnostische Ausgaben während der Vereinfachung zu erzeugen.
98 | ///
99 | ///
100 | /// Die erzeugte vereinfachte Mesh.
101 | ///
102 | public override Mesh Simplify(Mesh input, int targetFaceCount, bool createSplitRecords,
103 | bool verbose) {
104 | this.verbose = verbose;
105 | this.createSplitRecords = createSplitRecords;
106 | // Wir starten mit den Vertices und Faces der Ausgangsmesh.
107 | faces = new List(input.Faces);
108 | for (int i = 0; i < input.Vertices.Count; i++)
109 | vertices[i] = input.Vertices[i].Position;
110 | // 1. Die initialen Q Matrizen für jeden Vertex v berechnen.
111 | Q = ComputeInitialQForEachVertex(options.Strict);
112 | // 2. All gültigen Vertexpaare bestimmen.
113 | pairs = ComputeValidPairs();
114 | // 3. Iterativ das Paar mit den geringsten Kosten kontraktieren und entsprechend
115 | // die Kosten aller davon betroffenen Paare aktualisieren.
116 | while (faces.Count > targetFaceCount) {
117 | // var pair = FindLeastCostPair();
118 | var pair = pairs.First();
119 | Print("Contracting pair ({0}, {1}) with contraction cost = {2}",
120 | pair.Vertex1, pair.Vertex2, pair.Cost.ToString("e"));
121 | ContractPair(pair);
122 | Print("New face count: {0}", faces.Count);
123 | }
124 | // 4. Neue Mesh Instanz erzeugen und zurückliefern.
125 | return BuildMesh();
126 | }
127 |
128 | ///
129 | /// Berechnet die Kp-Matrix für die Ebenen aller Facetten.
130 | ///
131 | ///
132 | /// true, um eine InvalidOperationException zu werfen, falls ein
133 | /// degeneriertes Face entdeckt wird.
134 | ///
135 | ///
136 | /// Eine Liste von Kp-Matrizen.
137 | ///
138 | ///
139 | /// Es wurde ein degeneriertes Face gefunden, dessen Vertices kollinear sind.
140 | ///
141 | IList ComputeKpForEachPlane(bool strict) {
142 | var kp = new List();
143 | var degenerate = new List();
144 | foreach (var f in faces) {
145 | var points = new[] {
146 | vertices[f.Indices[0]],
147 | vertices[f.Indices[1]],
148 | vertices[f.Indices[2]]
149 | };
150 | // Ebene aus den 3 Ortsvektoren konstruieren.
151 | var dir1 = points[1] - points[0];
152 | var dir2 = points[2] - points[0];
153 | var n = Vector3d.Cross(dir1, dir2);
154 | // Wenn das Kreuzprodukt der Nullvektor ist, sind die Richtungsvektoren
155 | // kollinear, d.h. die Vertices liegen auf einer Geraden und die Facette
156 | // ist degeneriert.
157 | if (n == Vector3d.Zero) {
158 | degenerate.Add(f);
159 | if (strict) {
160 | var msg = new StringBuilder()
161 | .AppendFormat("Encountered degenerate face ({0} {1} {2})",
162 | f.Indices[0], f.Indices[1], f.Indices[2])
163 | .AppendLine()
164 | .AppendFormat("Vertex 1: {0}\n", points[0])
165 | .AppendFormat("Vertex 2: {0}\n", points[1])
166 | .AppendFormat("Vertex 3: {0}\n", points[2])
167 | .ToString();
168 | throw new InvalidOperationException(msg);
169 | }
170 | } else {
171 | n.Normalize();
172 | var a = n.X;
173 | var b = n.Y;
174 | var c = n.Z;
175 | var d = -Vector3d.Dot(n, points[0]);
176 | // Siehe [Gar97], Abschnitt 5 ("Deriving Error Quadrics").
177 | var m = new Matrix4d() {
178 | M11 = a * a, M12 = a * b, M13 = a * c, M14 = a * d,
179 | M21 = a * b, M22 = b * b, M23 = b * c, M24 = b * d,
180 | M31 = a * c, M32 = b * c, M33 = c * c, M34 = c * d,
181 | M41 = a * d, M42 = b * d, M43 = c * d, M44 = d * d
182 | };
183 | kp.Add(m);
184 | }
185 | }
186 | if (degenerate.Count > 0)
187 | Print("Warning: {0} degenerate faces found.", degenerate.Count);
188 | foreach (var d in degenerate)
189 | faces.Remove(d);
190 | return kp;
191 | }
192 |
193 | ///
194 | /// Berechnet die initialen Q-Matrizen für alle Vertices.
195 | ///
196 | ///
197 | /// true, um eine InvalidOperationException zu werfen, falls ein
198 | /// degeneriertes Face entdeckt wird.
199 | ///
200 | ///
201 | /// Eine Map der initialen Q-Matrizen.
202 | ///
203 | ///
204 | /// Es wurde ein degeneriertes Face gefunden, dessen Vertices kollinear sind.
205 | ///
206 | IDictionary ComputeInitialQForEachVertex(bool strict) {
207 | var q = new Dictionary();
208 | // Kp Matrix für jede Ebene, d.h. jede Facette bestimmen.
209 | var kp = ComputeKpForEachPlane(strict);
210 | // Q Matrix für jeden Vertex mit 0 initialisieren.
211 | for (int i = 0; i < vertices.Count; i++)
212 | q[i] = new Matrix4d();
213 | // Q ist die Summe aller Kp Matrizen der inzidenten Facetten jedes Vertex.
214 | for (int c = 0; c < faces.Count; c++) {
215 | var f = faces[c];
216 | for (int i = 0; i < f.Indices.Length; i++) {
217 | q[f.Indices[i]] = q[f.Indices[i]] + kp[c];
218 | }
219 | }
220 | return q;
221 | }
222 |
223 | ///
224 | /// Berechnet alle gültigen Vertexpaare.
225 | ///
226 | ///
227 | /// Die Menge aller gültigen Vertexpaare.
228 | ///
229 | ISet ComputeValidPairs() {
230 | // 1. Kriterium: 2 Vertices sind ein Kontraktionspaar, wenn sie durch eine Kante
231 | // miteinander verbunden sind.
232 | for (int i = 0; i < faces.Count; i++) {
233 | var f = faces[i];
234 | // Vertices eines Dreiecks sind jeweils durch Kanten miteinander verbunden
235 | // und daher gültige Paare.
236 | var s = f.Indices.OrderBy(val => val).ToArray();
237 | for (int c = 0; c < s.Length; c++) {
238 | if (!pairsPerVertex.ContainsKey(s[c]))
239 | pairsPerVertex[s[c]] = new HashSet();
240 | if (!incidentFaces.ContainsKey(s[c]))
241 | incidentFaces[s[c]] = new HashSet();
242 | incidentFaces[s[c]].Add(f);
243 | }
244 |
245 | var p = ComputeMinimumCostPair(s[0], s[1]);
246 | if (pairs.Add(p))
247 | Print("Added Vertexpair ({0}, {1})", s[0], s[1]);
248 | pairsPerVertex[s[0]].Add(p);
249 | pairsPerVertex[s[1]].Add(p);
250 |
251 | p = ComputeMinimumCostPair(s[0], s[2]);
252 | if (pairs.Add(p))
253 | Print("Added Vertexpair ({0}, {1})", s[0], s[2]);
254 | pairsPerVertex[s[0]].Add(p);
255 | pairsPerVertex[s[2]].Add(p);
256 |
257 | p = ComputeMinimumCostPair(s[1], s[2]);
258 | if (pairs.Add(p))
259 | Print("Added Vertexpair ({0}, {1})", s[1], s[2]);
260 | pairsPerVertex[s[1]].Add(p);
261 | pairsPerVertex[s[2]].Add(p);
262 |
263 | }
264 | // 2. Kriterium: 2 Vertices sind ein Kontraktionspaar, wenn die euklidische
265 | // Distanz < Threshold-Parameter t.
266 | if (distanceThreshold > 0) {
267 | // Nur prüfen, wenn überhaupt ein Threshold angegeben wurde.
268 | for (int i = 0; i < vertices.Count; i++) {
269 | for (int c = i + 1; c < vertices.Count; c++) {
270 | if ((vertices[i] - vertices[c]).Length < distanceThreshold) {
271 | if (pairs.Add(ComputeMinimumCostPair(i, c)))
272 | Print("Added Vertexpair ({0}, {1})", i, c);
273 | }
274 | }
275 | }
276 | }
277 | return pairs;
278 | }
279 |
280 | ///
281 | /// Bestimmt die Kosten für die Kontraktion der angegebenen Vertices.
282 | ///
283 | ///
284 | /// Der erste Vertex des Paares.
285 | ///
286 | ///
287 | /// Der zweite Vertex des Paares.
288 | ///
289 | ///
290 | /// Eine Instanz der Pair-Klasse.
291 | ///
292 | Pair ComputeMinimumCostPair(int s, int t) {
293 | Vector3d target;
294 | double cost;
295 | var q = Q[s] + Q[t];
296 | // Siehe [Gar97], Abschnitt 4 ("Approximating Error With Quadrics").
297 | var m = new Matrix4d() {
298 | M11 = q.M11, M12 = q.M12, M13 = q.M13, M14 = q.M14,
299 | M21 = q.M12, M22 = q.M22, M23 = q.M23, M24 = q.M24,
300 | M31 = q.M13, M32 = q.M23, M33 = q.M33, M34 = q.M34,
301 | M41 = 0, M42 = 0, M43 = 0, M44 = 1
302 | };
303 | // Wenn m invertierbar ist, lässt sich die optimale Position bestimmen.
304 | // if (m.Determinant != 0) {
305 | try {
306 | // Determinante ist ungleich 0 für invertierbare Matrizen.
307 | var inv = Matrix4d.Invert(m);
308 | target = new Vector3d(inv.M14, inv.M24, inv.M34);
309 | cost = ComputeVertexError(target, q);
310 | } catch(InvalidOperationException) {
311 | // } else {
312 | // Ansonsten den besten Wert aus Position von Vertex 1, Vertex 2 und
313 | // Mittelpunkt wählen.
314 | var v1 = vertices[s];
315 | var v2 = vertices[t];
316 | var mp = new Vector3d() {
317 | X = (v1.X + v2.X) / 2,
318 | Y = (v1.Y + v2.Y) / 2,
319 | Z = (v1.Z + v2.Z) / 2
320 | };
321 | var candidates = new[] {
322 | new { cost = ComputeVertexError(v1, q), target = v1 },
323 | new { cost = ComputeVertexError(v2, q), target = v2 },
324 | new { cost = ComputeVertexError(mp, q), target = mp }
325 | };
326 | var best = (from p in candidates
327 | orderby p.cost
328 | select p).First();
329 | target = best.target;
330 | cost = best.cost;
331 | }
332 | return new Pair(s, t, target, cost);
333 | }
334 |
335 | ///
336 | /// Bestimmt den geometrischen Fehler des angegebenen Vertex in Bezug auf die
337 | /// Fehlerquadrik Q.
338 | ///
339 | ///
340 | /// Der Vertex, dessen geometrischer Fehler bestimmt werden soll.
341 | ///
342 | ///
343 | /// Die zugrundeliegende Fehlerquadrik.
344 | ///
345 | ///
346 | /// Der geometrische Fehler an der Stelle des angegebenen Vertex.
347 | ///
348 | double ComputeVertexError(Vector3d v, Matrix4d q) {
349 | var h = new Vector4d(v, 1);
350 | // Geometrischer Fehler Δ(v) = vᵀQv.
351 | return Vector4d.Dot(Vector4d.Transform(h, q), h);
352 | }
353 |
354 | ///
355 | /// Findet aus der angegebenen Menge das Paar mit den kleinsten Kosten.
356 | ///
357 | ///
358 | /// Die Menge der Vertexpaare, aus der das Paar mit den kleinsten Kosten
359 | /// gefunden werden soll.
360 | ///
361 | ///
362 | /// Das Paar mit den kleinsten Kosten.
363 | ///
364 | Pair FindLeastCostPair(ISet pairs) {
365 | double cost = double.MaxValue;
366 | Pair best = null;
367 | foreach (var p in pairs) {
368 | if (p.Cost < cost) {
369 | cost = p.Cost;
370 | best = p;
371 | }
372 | }
373 | return best;
374 | }
375 |
376 | ///
377 | /// Kontraktiert das angegebene Vertexpaar.
378 | ///
379 | ///
380 | /// Das Vertexpaar, das kontraktiert werden soll.
381 | ///
382 | void ContractPair(Pair p) {
383 | if (createSplitRecords)
384 | AddSplitRecord(p);
385 | // 1. Koordinaten von Vertex 1 werden auf neue Koordinaten abgeändert.
386 | vertices[p.Vertex1] = p.Target;
387 | // 2. Matrix Q von Vertex 1 anpassen.
388 | Q[p.Vertex1] = Q[p.Vertex1] + Q[p.Vertex2];
389 | // 3. Alle Referenzen von Facetten auf Vertex 2 auf Vertex 1 umbiegen.
390 | var facesOfVertex2 = incidentFaces[p.Vertex2];
391 | var facesOfVertex1 = incidentFaces[p.Vertex1];
392 | var degeneratedFaces = new HashSet();
393 |
394 | // Jede Facette von Vertex 2 wird entweder Vertex 1 hinzugefügt, oder
395 | // degeneriert.
396 | foreach (var f in facesOfVertex2) {
397 | if (facesOfVertex1.Contains(f)) {
398 | degeneratedFaces.Add(f);
399 | } else {
400 | // Indices umbiegen und zu faces von Vertex 1 hinzufügen.
401 | for (int i = 0; i < f.Indices.Length; i++) {
402 | if (f.Indices[i] == p.Vertex2)
403 | f.Indices[i] = p.Vertex1;
404 | }
405 | facesOfVertex1.Add(f);
406 | }
407 | }
408 | // Nun degenerierte Facetten entfernen.
409 | foreach (var f in degeneratedFaces) {
410 | for (int i = 0; i < f.Indices.Length; i++)
411 | incidentFaces[f.Indices[i]].Remove(f);
412 | faces.Remove(f);
413 | }
414 |
415 | // Vertex 2 aus Map löschen.
416 | vertices.Remove(p.Vertex2);
417 |
418 | // Alle Vertexpaare zu denen Vertex 2 gehört dem Set von Vertex 1 hinzufügen.
419 | pairsPerVertex[p.Vertex1].UnionWith(pairsPerVertex[p.Vertex2]);
420 | // Anschließend alle Paare von Vertex 1 ggf. umbiegen und aktualisieren.
421 | var remove = new List();
422 | foreach (var pair in pairsPerVertex[p.Vertex1]) {
423 | // Aus Collection temporär entfernen, da nach Neuberechnung der Kosten
424 | // neu einsortiert werden muss.
425 | pairs.Remove(pair);
426 | int s = pair.Vertex1, t = pair.Vertex2;
427 | if (s == p.Vertex2)
428 | s = p.Vertex1;
429 | if (t == p.Vertex2)
430 | t = p.Vertex1;
431 | if (s == t) {
432 | remove.Add(pair);
433 | } else {
434 | var np = ComputeMinimumCostPair(Math.Min(s, t), Math.Max(s, t));
435 | pair.Vertex1 = np.Vertex1;
436 | pair.Vertex2 = np.Vertex2;
437 | pair.Target = np.Target;
438 | pair.Cost = np.Cost;
439 |
440 | pairs.Add(pair);
441 | }
442 | }
443 | // "Degenerierte" Paare entfernen.
444 | foreach (var r in remove) {
445 | pairsPerVertex[p.Vertex1].Remove(r);
446 | }
447 | }
448 |
449 | ///
450 | /// Erzeugt und speichert einen VertexSplit Eintrag für das angegebene Paar.
451 | ///
452 | ///
453 | /// Das Paar für welches ein VertexSplit Eintrag erstellt werden soll.
454 | ///
455 | void AddSplitRecord(Pair p) {
456 | contractionIndices.Push(p.Vertex2);
457 | var split = new VertexSplit() {
458 | S = p.Vertex1, SPosition = vertices[p.Vertex1],
459 | TPosition = vertices[p.Vertex2]
460 | };
461 | foreach (var f in incidentFaces[p.Vertex2]) {
462 | // -1 wird später durch eigentlichen Index ersetzt, wenn dieser bekannt
463 | // ist.
464 | split.Faces.Add(new Triangle(
465 | f.Indices[0] == p.Vertex2 ? -1 : f.Indices[0],
466 | f.Indices[1] == p.Vertex2 ? -1 : f.Indices[1],
467 | f.Indices[2] == p.Vertex2 ? -1 : f.Indices[2]));
468 | }
469 | splits.Push(split);
470 | }
471 |
472 | ///
473 | /// Erzeugt eine neue Mesh Instanz den aktuellen Vertex- und Facettendaten.
474 | ///
475 | ///
476 | /// Die erzeugte Mesh Instanz.
477 | ///
478 | Mesh BuildMesh() {
479 | // Mapping von alten auf neue Vertexindices für Facetten erstellen.
480 | var mapping = new Dictionary();
481 | int index = 0;
482 | var verts = new List();
483 | var _faces = new List();
484 | foreach (var p in vertices) {
485 | mapping.Add(p.Key, index++);
486 | verts.Add(new Vertex() { Position = p.Value });
487 | }
488 | foreach (var f in faces) {
489 | _faces.Add(new Triangle(
490 | mapping[f.Indices[0]],
491 | mapping[f.Indices[1]],
492 | mapping[f.Indices[2]]
493 | ));
494 | }
495 | // Progressive Mesh: Indices im Mapping für "zukünftige" Vertices anlegen.
496 | foreach (var c in contractionIndices)
497 | mapping.Add(c, index++);
498 | int n = verts.Count;
499 | foreach (var s in splits) {
500 | var t = n++;
501 | s.S = mapping[s.S];
502 | foreach (var f in s.Faces) {
503 | for (int i = 0; i < f.Indices.Length; i++) {
504 | f.Indices[i] = f.Indices[i] < 0 ? t : mapping[f.Indices[i]];
505 | }
506 | }
507 | }
508 | return new Mesh(verts, _faces, splits);
509 | }
510 |
511 | ///
512 | /// Gibt alle Ressourcen der Instanz frei.
513 | ///
514 | public override void Dispose() {
515 | // Nichts zu tun hier.
516 | }
517 |
518 | ///
519 | /// Schreibt den angegebenen String in die Standardausgabe, wenn ausführliche
520 | /// Ausgaben aktiviert sind.
521 | ///
522 | ///
523 | /// Der formatierte String.
524 | ///
525 | ///
526 | /// Die etwaigen Parameter für den formatierten String.
527 | ///
528 | void Print(string format, params object[] arg) {
529 | if (verbose)
530 | Console.WriteLine(format, arg);
531 | }
532 | }
533 | }
534 |
--------------------------------------------------------------------------------
/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Datastructures/Mesh.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 |
5 | namespace MeshSimplify {
6 | ///
7 | /// Repräsentiert ein (progressives) Polygonnetz.
8 | ///
9 | public class Mesh {
10 | ///
11 | /// Die Vertices des Polgyonnetzes.
12 | ///
13 | public IList Vertices {
14 | get;
15 | private set;
16 | }
17 |
18 | ///
19 | /// Die Facetten des Polygonnetzes.
20 | ///
21 | public IList Faces {
22 | get;
23 | private set;
24 | }
25 |
26 | ///
27 | /// Die mit der Mesh assoziierten Vertex-Splits.
28 | ///
29 | public Queue Splits {
30 | get;
31 | private set;
32 | }
33 |
34 | ///
35 | /// Initialisiert eine neue Instanz der Mesh Klasse.
36 | ///
37 | ///
38 | /// Eine Abzählung von Vertices, die der Mesh hinzugefügt werden sollen.
39 | ///
40 | ///
41 | /// Eine Abzählung von Facetten, die der Mesh hinzugefügt werden sollen.
42 | ///
43 | ///
44 | /// Eine Abzählung von Vertex-Splits, die der Mesh hinzugefügt werden sollen.
45 | ///
46 | public Mesh(IEnumerable vertices = null, IEnumerable faces = null,
47 | IEnumerable splits = null) {
48 | Vertices = new List();
49 | Faces = new List();
50 | Splits = new Queue();
51 | if (vertices != null) {
52 | foreach (var v in vertices)
53 | Vertices.Add(v);
54 | }
55 | if (faces != null) {
56 | foreach (var f in faces)
57 | Faces.Add(f);
58 | }
59 | if (splits != null) {
60 | foreach (var s in splits)
61 | Splits.Enqueue(s);
62 | }
63 | }
64 |
65 | ///
66 | /// Expandiert die (progressive) Mesh auf die gewünschte Anzahl von Facetten.
67 | ///
68 | ///
69 | /// Die Anzahl von Facetten, auf die die Mesh expandiert werden soll.
70 | ///
71 | ///
72 | /// true, um diagnostische Ausgaben während der Vereinfachung zu erzeugen.
73 | ///
74 | ///
75 | /// Eine Referenz auf die Mesh Instanz.
76 | ///
77 | public Mesh Expand(int targetFaceCount, bool verbose) {
78 | var incidentFaces = ComputeIncidentFaces();
79 | while (Faces.Count < targetFaceCount) {
80 | // Keine VertexSplits mehr vorhanden, also müssen wir aufhören.
81 | if (Splits.Count == 0)
82 | break;
83 | var split = Splits.Dequeue();
84 | if (verbose)
85 | Console.WriteLine("Expanding vertex {0}.", split.S);
86 | PerformVertexSplit(split, incidentFaces);
87 | if (verbose)
88 | Console.WriteLine("New face count: {0}", Faces.Count);
89 | }
90 | return this;
91 | }
92 |
93 | ///
94 | /// Berechnet für jeden Vertex die Menge seiner inzidenten Facetten.
95 | ///
96 | ///
97 | /// Eine Map die jedem Vertex der Mesh die Menge seiner inzidenten Facetten zuordnet.
98 | ///
99 | IDictionary> ComputeIncidentFaces() {
100 | var incidentFaces = new Dictionary>();
101 | for (int i = 0; i < Vertices.Count; i++)
102 | incidentFaces.Add(i, new HashSet());
103 | foreach (var f in Faces) {
104 | for (int c = 0; c < f.Indices.Length; c++) {
105 | incidentFaces[f.Indices[c]].Add(f);
106 | }
107 | }
108 | return incidentFaces;
109 | }
110 |
111 | ///
112 | /// Führt eine Vertex-Split Operation auf der Mesh aus.
113 | ///
114 | ///
115 | /// Die Vertex-Split Operation, die ausgeführt werden soll.
116 | ///
117 | ///
118 | /// Eine Map die jedem Vertex der Mesh die Menge seiner inzidenten Facetten zuordnet.
119 | ///
120 | void PerformVertexSplit(VertexSplit split, IDictionary> incidentFaces) {
121 | // 1. Vertex s wird an neue Position verschoben.
122 | Vertices[split.S].Position = split.SPosition;
123 | // 2. Vertex t wird neu zur Mesh hinzugefügt.
124 | Vertices.Add(new Vertex() { Position = split.TPosition });
125 | var t = Vertices.Count - 1;
126 | // 3. Alle Facetten von s, die ursprünglich t "gehört" haben, auf t zurückbiegen.
127 | var facesOfS = incidentFaces[split.S];
128 | var facesOfT = new HashSet();
129 | incidentFaces.Add(t, facesOfT);
130 | var removeFromS = new HashSet();
131 | foreach (var f in facesOfS) {
132 | var _c = IsOriginalFaceOfT(t, f, split);
133 | if (_c < 0)
134 | continue;
135 | f.Indices[_c] = t;
136 | facesOfT.Add(f);
137 | removeFromS.Add(f);
138 | }
139 | foreach (var r in removeFromS)
140 | facesOfS.Remove(r);
141 | // 4. Etwaige gemeinsame Facetten von s und t der Mesh neu hinzufügen.
142 | foreach (var f in split.Faces) {
143 | if (!f.Indices.Contains(split.S))
144 | continue;
145 | var newFace = new Triangle(f.Indices);
146 | Faces.Add(newFace);
147 | for (int c = 0; c < newFace.Indices.Length; c++)
148 | incidentFaces[newFace.Indices[c]].Add(newFace);
149 | }
150 | }
151 |
152 | ///
153 | /// Prüft, ob die angegebene Facette ursprünglich dem angegebenen Vertex "gehörte".
154 | ///
155 | ///
156 | /// Der Vertex.
157 | ///
158 | ///
159 | /// Die Facette.
160 | ///
161 | ///
162 | /// Die zugehörige VertexSplit Instanz.
163 | ///
164 | ///
165 | /// Der Facettenindex, an dessen Stelle der ursprüngliche Vertex eingetragen war,
166 | /// oder -1 falls die Facette nicht ursprünglich dem Vertex "gehörte".
167 | ///
168 | ///
169 | /// Diese Methode könnte schöner sein und sollte eigentlich in 2 Methoden aufgeteilt
170 | /// werden: Eine Methode zum Prüfen mit Rückgabewert Bool und eine Methode zum
171 | /// Abfragen des Index. Aus Geschwindigkeitsgründen ist dies aber ungünstig.
172 | ///
173 | int IsOriginalFaceOfT(int t, Triangle face, VertexSplit split) {
174 | foreach (var f in split.Faces) {
175 | var index = -1;
176 | var isface = true;
177 | for (int i = 0; i < 3; i++) {
178 | if (f.Indices[i] == t && face.Indices[i] == split.S) {
179 | index = i;
180 | } else if (f.Indices[i] != face.Indices[i]) {
181 | isface = false;
182 | }
183 | }
184 | if (isface)
185 | return index;
186 | }
187 | return -1;
188 | }
189 |
190 | }
191 | }
192 |
--------------------------------------------------------------------------------
/Datastructures/Triangle.cs:
--------------------------------------------------------------------------------
1 |
2 | namespace MeshSimplify {
3 | ///
4 | /// Repräsentiert eine Facette.
5 | ///
6 | public class Triangle {
7 | ///
8 | /// Die Indices der Vertices des Triangles.
9 | ///
10 | public readonly int[] Indices = new int[3];
11 |
12 | ///
13 | /// Initialisiert eine neue Instanz der Triangle Klasse.
14 | ///
15 | ///
16 | /// Der erste Index.
17 | ///
18 | ///
19 | /// Der zweite Index.
20 | ///
21 | ///
22 | /// Der dritte Index.
23 | ///
24 | public Triangle(int index1, int index2, int index3) {
25 | Indices[0] = index1;
26 | Indices[1] = index2;
27 | Indices[2] = index3;
28 | }
29 |
30 | ///
31 | /// Initialisiert eine neue Instanz der Triangle Klasse.
32 | ///
33 | ///
34 | /// Die Indices der Vertices, die das Triangle ausmachen.
35 | ///
36 | public Triangle(int[] indices)
37 | : this(indices[0], indices[1], indices[2]) {
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Datastructures/Vertex.cs:
--------------------------------------------------------------------------------
1 | using OpenTK;
2 |
3 | namespace MeshSimplify {
4 | ///
5 | /// Repräsentiert einen Vertex.
6 | ///
7 | public class Vertex {
8 | ///
9 | /// Die Position des Vertex.
10 | ///
11 | public Vector3d Position;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Datastructures/VertexSplit.cs:
--------------------------------------------------------------------------------
1 | using OpenTK;
2 | using System.Collections.Generic;
3 |
4 | namespace MeshSimplify {
5 | ///
6 | /// Repräsentiert eine Vertex-Split Operation, also das Gegenteil einer Edge- bzw.
7 | /// Pair-Contract operation.
8 | ///
9 | public class VertexSplit {
10 | ///
11 | /// Der Index des Vertex, der "gespalten" wird.
12 | ///
13 | public int S;
14 |
15 | ///
16 | /// Die neue Position des Vertex nach der Aufspaltung.
17 | ///
18 | public Vector3d SPosition;
19 |
20 | ///
21 | /// Die Position des zweiten Vertex der bei der Aufspaltung entsteht.
22 | ///
23 | public Vector3d TPosition;
24 |
25 | ///
26 | /// Eine Untermenge der Facetten von Vertex s die Vertex t nach der Aufspaltung
27 | /// zugeteilt werden sollen.
28 | ///
29 | public ISet Faces = new HashSet();
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Torben Könke
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/MeshSimplify.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {60C475D7-F5FB-45A8-97BE-DB69D59DF62F}
8 | Exe
9 | Properties
10 | MeshSimplify
11 | MeshSimplify
12 | v4.5
13 | 512
14 |
15 |
16 | AnyCPU
17 | true
18 | full
19 | false
20 | bin\Debug\
21 | DEBUG;TRACE
22 | prompt
23 | 4
24 | bin\Debug\MeshSimplify.XML
25 |
26 |
27 | AnyCPU
28 | pdbonly
29 | true
30 | bin\Release\
31 | TRACE
32 | prompt
33 | 4
34 |
35 |
36 |
37 | packages\C5.1.0.2.0\lib\net40\C5.dll
38 |
39 |
40 | packages\OpenTK.1.1.1589.5942\lib\NET40\OpenTK.dll
41 |
42 |
43 | packages\Plossum.CommandLine.0.3.0.14\lib\net40\Plossum CommandLine.dll
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | Always
70 |
71 |
72 | Always
73 |
74 |
75 |
76 |
77 |
78 |
85 |
--------------------------------------------------------------------------------
/MeshSimplify.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 2012
4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MeshSimplify", "MeshSimplify.csproj", "{60C475D7-F5FB-45A8-97BE-DB69D59DF62F}"
5 | EndProject
6 | Global
7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
8 | Debug|Any CPU = Debug|Any CPU
9 | Release|Any CPU = Release|Any CPU
10 | EndGlobalSection
11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
12 | {60C475D7-F5FB-45A8-97BE-DB69D59DF62F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
13 | {60C475D7-F5FB-45A8-97BE-DB69D59DF62F}.Debug|Any CPU.Build.0 = Debug|Any CPU
14 | {60C475D7-F5FB-45A8-97BE-DB69D59DF62F}.Release|Any CPU.ActiveCfg = Release|Any CPU
15 | {60C475D7-F5FB-45A8-97BE-DB69D59DF62F}.Release|Any CPU.Build.0 = Release|Any CPU
16 | EndGlobalSection
17 | GlobalSection(SolutionProperties) = preSolution
18 | HideSolutionNode = FALSE
19 | EndGlobalSection
20 | EndGlobal
21 |
--------------------------------------------------------------------------------
/ObjIO.cs:
--------------------------------------------------------------------------------
1 | using OpenTK;
2 | using System;
3 | using System.Globalization;
4 | using System.IO;
5 | using System.Text.RegularExpressions;
6 |
7 | namespace MeshSimplify {
8 | ///
9 | /// Eine Klasse zum Laden und Speichern von Wavefront .obj Dateien.
10 | ///
11 | ///
12 | /// Es werden nur Dreiecksnetze unterstützt.
13 | ///
14 | public static class ObjIO {
15 | ///
16 | /// Parsed die angegebene .obj Datei.
17 | ///
18 | ///
19 | /// Der Name der Datei, welche geparsed werden soll.
20 | ///
21 | ///
22 | /// Eine Mesh Instanz, die aus den Daten der .obj Datei erzeugt wurde.
23 | ///
24 | ///
25 | /// Die angegebene .obj Datei ist ungültig oder enthält ein nicht-unterstütztes
26 | /// Meshformat.
27 | ///
28 | ///
29 | /// Die angegebene Datei konnte nicht gelesen werden.
30 | ///
31 | public static Mesh Load(string path) {
32 | var mesh = new Mesh();
33 | using (var sr = File.OpenText(path)) {
34 | string l = string.Empty;
35 | while ((l = sr.ReadLine()) != null) {
36 | if (l.StartsWith("v "))
37 | mesh.Vertices.Add(ParseVertex(l));
38 | else if (l.StartsWith("f "))
39 | mesh.Faces.Add(ParseFace(l));
40 | else if (l.StartsWith("#vsplit "))
41 | mesh.Splits.Enqueue(ParseVertexSplit(l));
42 | }
43 | }
44 | return mesh;
45 | }
46 |
47 | ///
48 | /// Schreibt die angegebene Mesh in die angegebene Datei.
49 | ///
50 | ///
51 | /// Die zu schreibende Mesh.
52 | ///
53 | ///
54 | /// Der Name der Datei, in die die Mesh geschrieben werden soll.
55 | ///
56 | public static void Save(Mesh mesh, string path) {
57 | using (var fs = File.Open(path, FileMode.Create)) {
58 | using (var sw = new StreamWriter(fs)) {
59 | sw.WriteLine("# {0}", DateTime.Now);
60 | sw.WriteLine("# {0} Vertices", mesh.Vertices.Count);
61 | foreach (var v in mesh.Vertices) {
62 | sw.WriteLine("v {0} {1} {2}",
63 | v.Position.X.ToString(CultureInfo.InvariantCulture),
64 | v.Position.Y.ToString(CultureInfo.InvariantCulture),
65 | v.Position.Z.ToString(CultureInfo.InvariantCulture));
66 | }
67 | sw.WriteLine();
68 | sw.WriteLine("# {0} Faces", mesh.Faces.Count);
69 | foreach (var f in mesh.Faces) {
70 | sw.WriteLine("f {0} {1} {2}", f.Indices[0] + 1, f.Indices[1] + 1,
71 | f.Indices[2] + 1);
72 | }
73 | if (mesh.Splits.Count > 0) {
74 | sw.WriteLine();
75 | sw.WriteLine("# {0} Split Records", mesh.Splits.Count);
76 | foreach (var s in mesh.Splits) {
77 | // vsplit als Kommentar schreiben, so daß die Datei auch weiterhin
78 | // eine gültige .obj Datei bleibt.
79 | sw.Write("#vsplit {0} {{{1} {2} {3}}} {{{4} {5} {6}}} {{ ", s.S + 1,
80 | s.SPosition.X.ToString(CultureInfo.InvariantCulture),
81 | s.SPosition.Y.ToString(CultureInfo.InvariantCulture),
82 | s.SPosition.Z.ToString(CultureInfo.InvariantCulture),
83 | s.TPosition.X.ToString(CultureInfo.InvariantCulture),
84 | s.TPosition.Y.ToString(CultureInfo.InvariantCulture),
85 | s.TPosition.Z.ToString(CultureInfo.InvariantCulture));
86 | foreach (var f in s.Faces) {
87 | sw.Write("({0} {1} {2}) ", f.Indices[0] + 1, f.Indices[1] + 1,
88 | f.Indices[2] + 1);
89 | }
90 | sw.Write("}");
91 | sw.WriteLine();
92 | }
93 | }
94 | }
95 | }
96 | }
97 |
98 | ///
99 | /// Parsed eine Vertex Deklaration.
100 | ///
101 | ///
102 | /// Die Zeile, die die Vertex Deklaration enthält.
103 | ///
104 | ///
105 | /// Der Vertex.
106 | ///
107 | ///
108 | /// Die Vertexdelaration ist ungültig.
109 | ///
110 | static Vertex ParseVertex(string l) {
111 | var p = l.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
112 | if (p.Length != 4)
113 | throw new InvalidOperationException("Invalid vertex format: " + l);
114 | return new Vertex() {
115 | Position = new Vector3d(
116 | double.Parse(p[1], CultureInfo.InvariantCulture),
117 | double.Parse(p[2], CultureInfo.InvariantCulture),
118 | double.Parse(p[3], CultureInfo.InvariantCulture))
119 | };
120 | }
121 |
122 | ///
123 | /// Parsed eine Facetten Deklaration.
124 | ///
125 | ///
126 | /// Die Zeile, die die Facetten Deklaration enthält.
127 | ///
128 | ///
129 | /// Die Facette.
130 | ///
131 | ///
132 | /// Die Facettendeklaration ist ungültig.
133 | ///
134 | static Triangle ParseFace(string l) {
135 | var p = l.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
136 | if (p.Length != 4)
137 | throw new InvalidOperationException("Invalid face: " + l);
138 | var indices = new[] {
139 | int.Parse(p[1]) - 1,
140 | int.Parse(p[2]) - 1,
141 | int.Parse(p[3]) - 1
142 | };
143 | return new Triangle(indices);
144 | }
145 |
146 | ///
147 | /// Parsed einen dreidimensionalen Vektor.
148 | ///
149 | ///
150 | /// Die Zeile, die den Vektor enthält.
151 | ///
152 | ///
153 | /// Der Vektor.
154 | ///
155 | ///
156 | /// Die Vektordeklaration ist ungültig.
157 | ///
158 | static Vector3d ParseVector(string l) {
159 | var p = l.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
160 | if (p.Length != 3)
161 | throw new InvalidOperationException("Invalid vector: " + l);
162 | return new Vector3d(
163 | double.Parse(p[0], CultureInfo.InvariantCulture),
164 | double.Parse(p[1], CultureInfo.InvariantCulture),
165 | double.Parse(p[2], CultureInfo.InvariantCulture));
166 | }
167 |
168 | ///
169 | /// Parsed eine VertexSplit Deklaration.
170 | ///
171 | ///
172 | /// Die Zeile, die die VertexSplit Deklaration enthält.
173 | ///
174 | ///
175 | /// Der Vertex-Split Eintrag.
176 | ///
177 | ///
178 | /// Die VertexSplit Deklaration ist ungültig.
179 | ///
180 | static VertexSplit ParseVertexSplit(string l) {
181 | var m = Regex.Match(l, @"^#vsplit\s+(\d+)\s+{(.*)}\s+{(.*)}\s+{(.*)}$");
182 | if (!m.Success)
183 | throw new InvalidOperationException("Invalid vsplit: " + l);
184 | var s = new VertexSplit() {
185 | S = int.Parse(m.Groups[1].Value) - 1,
186 | SPosition = ParseVector(m.Groups[2].Value),
187 | TPosition = ParseVector(m.Groups[3].Value)
188 | };
189 | var faceIndices = m.Groups[4].Value;
190 | var matches = Regex.Matches(faceIndices, @"\((-?\d+)\s+(-?\d+)\s+(-?\d+)\)");
191 | for (int i = 0; i < matches.Count; i++) {
192 | var _m = matches[i];
193 | if (!_m.Success)
194 | throw new InvalidOperationException("Invalid face index entry in vsplit: " + l);
195 | var indices = new[] {
196 | int.Parse(_m.Groups[1].Value) - 1,
197 | int.Parse(_m.Groups[2].Value) - 1,
198 | int.Parse(_m.Groups[3].Value) - 1
199 | };
200 | s.Faces.Add(new Triangle(indices));
201 | }
202 | return s;
203 | }
204 | }
205 | }
206 |
--------------------------------------------------------------------------------
/Options.cs:
--------------------------------------------------------------------------------
1 | using Plossum.CommandLine;
2 |
3 | namespace MeshSimplify {
4 | ///
5 | /// Repräsentiert die möglichen Kommandozeilenparameter, die an das Programm übergeben
6 | /// werden können.
7 | ///
8 | [CommandLineManager(EnabledOptionStyles = OptionStyles.Group | OptionStyles.LongUnix)]
9 | public class Options {
10 | ///
11 | /// true, um ausführliche Programmausgaben zu erzeugen; andernfalls false.
12 | ///
13 | [CommandLineOption(Name = "v", Aliases = "verbose",
14 | Description = "Produces verbose output.")]
15 | public bool Verbose {
16 | get;
17 | set;
18 | }
19 |
20 | ///
21 | /// Der Name des Algorithmus, der zur Vereinfachung der Eingabemesh benutzt werden soll.
22 | ///
23 | [CommandLineOption(Name = "a", Aliases = "algorithm", MaxOccurs = 1,
24 | Description = "Specifies the simplification algorithm to use. Currently the only valid " +
25 | "value for this option is `PairContract'. More algorithms may be added in the future.")]
26 | public string Algorithm {
27 | get;
28 | set;
29 | }
30 |
31 | ///
32 | /// Die Anzahl der Facetten, die die vereinfachte Mesh anstreben soll.
33 | ///
34 | [CommandLineOption(Name = "n", Aliases = "num-faces", MinOccurs = 1, MinValue = 1,
35 | Description = "Specifies the number of faces to reduce the input mesh to.")]
36 | public int TargetFaceCount {
37 | get;
38 | set;
39 | }
40 |
41 | ///
42 | /// Der maximale Abstand, den zwei Vertices haben dürfen, um als Vertexpaar aufgefasst
43 | /// zu werden. Diese Option ist nur für den Algorithmus `PairContract' relevant.
44 | ///
45 | [CommandLineOption(Name = "d", Aliases = "distance-threshold",
46 | Description = "Specifies the distance-threshold value for pair-contraction. This " +
47 | "option is only applicable for the `PairContract' algorithm and defaults to 0.")]
48 | public float DistanceThreshold {
49 | get;
50 | set;
51 | }
52 |
53 | ///
54 | /// Der Name der Ausgabedatei.
55 | ///
56 | ///
57 | /// Wenn diese Option nicht angegeben wird, wird die Ausgabedatei nach der Eingabedatei mit
58 | /// dem zusätzlichen Suffix 'out' benannt.
59 | ///
60 | [CommandLineOption(Name = "o",
61 | Description = "Specifies the output file.")]
62 | public string OutputFile {
63 | get;
64 | set;
65 | }
66 |
67 | ///
68 | /// true, um die Programmausführung bei nicht-wohlgeformter Eingabedatei abzubrechen.
69 | ///
70 | [CommandLineOption(Name = "s", Aliases = "strict",
71 | Description = "Specifies strict mode. This will cause simplification to fail " +
72 | "if the input mesh is malformed or any other anomaly occurs.")]
73 | public bool Strict {
74 | get;
75 | set;
76 | }
77 |
78 | ///
79 | /// true, um eine Progressive Mesh zu erzeugen.
80 | ///
81 | [CommandLineOption(Name = "p", Aliases = "progressive-mesh",
82 | Description = "Adds vertex-split records to the resulting .obj file, effectively " +
83 | "creating a progressive-mesh representation of the input mesh.")]
84 | public bool ProgressiveMesh {
85 | get;
86 | set;
87 | }
88 |
89 | ///
90 | /// true, um aus der Eingabe (Progressive) Mesh eine detaillierte Version zu
91 | /// erzeugen.
92 | ///
93 | [CommandLineOption(Name = "r", Aliases = "restore-mesh",
94 | Description="Expands the input progressive-mesh to the desired face-count. " +
95 | "If this option is specified, num-faces refers to the number of faces to " +
96 | "restore the mesh to.")]
97 | public bool Restore {
98 | get;
99 | set;
100 | }
101 |
102 | ///
103 | /// true, um Versionsinformationen anzuzeigen; andernfalls false.
104 | ///
105 | [CommandLineOption(Name = "version", Description = "Shows version information.")]
106 | public bool Version {
107 | get;
108 | set;
109 | }
110 |
111 | ///
112 | /// true, um Hilfsinformationen anzuzeigen; andernfalls false.
113 | ///
114 | [CommandLineOption(Name = "h", Aliases = "help",
115 | Description = "Shows this help text.")]
116 | public bool Help {
117 | get;
118 | set;
119 | }
120 |
121 | ///
122 | /// Der Name der Eingabedatei, die verarbeitet werden soll.
123 | ///
124 | public string InputFile {
125 | get;
126 | set;
127 | }
128 |
129 | ///
130 | /// Initialisiert eine neue Instanz der Options-Klasse.
131 | ///
132 | public Options() {
133 | // Momentan ist nur Pair-Contracting implementiert.
134 | Algorithm = "PairContract";
135 | }
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/Program.cs:
--------------------------------------------------------------------------------
1 | using MeshSimplify.Algorithms;
2 | using Plossum.CommandLine;
3 | using System;
4 | using System.ComponentModel;
5 | using System.Diagnostics;
6 | using System.IO;
7 | using System.Reflection;
8 |
9 | namespace MeshSimplify {
10 | ///
11 | /// Implementiert ein Programm zur Vereinfachung von Polygonnetzen.
12 | ///
13 | class Program {
14 | ///
15 | /// Einstiegspunkt des Programms.
16 | ///
17 | static void Main() {
18 | // Kommandozeilenparameter auslesen und verarbeiten.
19 | var args = ProcessArguments();
20 | if(args == null)
21 | return;
22 | try {
23 | var mesh = ObjIO.Load(args.InputFile);
24 | // Das Erzeugen von Progressive Meshes aus bestehenden PMs wird nicht
25 | // unterstützt.
26 | if (args.ProgressiveMesh && mesh.Splits.Count > 0)
27 | Error("progressive-mesh may only be created from plain mesh.");
28 | using (var algo = InstantiateAlgorithm(args.Algorithm, args)) {
29 | Stopwatch sw = Stopwatch.StartNew();
30 | var alteredMesh = args.Restore ? mesh.Expand(args.TargetFaceCount, args.Verbose) :
31 | algo.Simplify(mesh, args.TargetFaceCount, args.ProgressiveMesh, args.Verbose);
32 | sw.Stop();
33 | Console.WriteLine("{0} took {1} ms", args.Restore ? "Expansion" : "Simplification",
34 | sw.Elapsed.TotalMilliseconds);
35 | var output = args.OutputFile ??
36 | Path.GetFileNameWithoutExtension(args.InputFile) + "_out" +
37 | Path.GetExtension(args.InputFile);
38 | Console.WriteLine("Writing {0} mesh to {1}...", args.Restore ? "expanded" : "simplified",
39 | output);
40 | ObjIO.Save(alteredMesh, output);
41 | Console.WriteLine("Done");
42 | }
43 | } catch (FileNotFoundException) {
44 | Console.WriteLine("Couldn't find file `{0}'.", args.InputFile);
45 | }
46 | }
47 |
48 | ///
49 | /// Verarbeitet die Kommandozeilenparameter.
50 | ///
51 | ///
52 | /// Eine Instanz der Options-Klasse, die die an das Programm übergebenen Parameter
53 | /// enthält, oder null, wenn das Programm beendet werden soll.
54 | ///
55 | static Options ProcessArguments() {
56 | int width = 78;
57 | var opts = new Options();
58 | using (var parser = new CommandLineParser(opts)) {
59 | parser.Parse();
60 | if (opts.Help) {
61 | Console.WriteLine(parser.UsageInfo.ToString(width, false));
62 | } else if(opts.Version) {
63 | var a = FileVersionInfo.GetVersionInfo(
64 | Assembly.GetExecutingAssembly().Location);
65 | Console.WriteLine("{0} ({1})\n- {2} -\n{3}\n{4}", a.ProductName, a.ProductVersion,
66 | a.Comments, a.CompanyName, a.LegalCopyright);
67 | } else if (parser.HasErrors) {
68 | Console.WriteLine(parser.UsageInfo.ToString(width, true));
69 | } else if (parser.RemainingArguments.Count == 0) {
70 | Console.WriteLine("error: no input file");
71 | Console.WriteLine("usage: MeshSimplify [options] file");
72 | Console.WriteLine("Try `MeshSimplify --help' for more information.");
73 | } else {
74 | opts.InputFile = parser.RemainingArguments[0];
75 | return opts;
76 | }
77 | return null;
78 | }
79 | }
80 |
81 | ///
82 | /// Erstellt eine Instanz des Algorithmus mit dem angegebenen Namen.
83 | ///
84 | ///
85 | /// Der Name des Algorithmus.
86 | ///
87 | ///
88 | /// Die Argumente, die der Instanz des Algorithmus zur Verfügung gestellt werden sollen.
89 | ///
90 | ///
91 | /// Eine Instanz des Algorithmus mit dem angegebenen Namen.
92 | ///
93 | ///
94 | /// Es konnte keine Klasse mit dem Namen des angegebenen Algorithmus gefunden
95 | /// werden.
96 | ///
97 | static Algorithm InstantiateAlgorithm(string name, Options opts) {
98 | foreach (var t in Assembly.GetCallingAssembly().GetTypes()) {
99 | if(!t.IsSubclassOf(typeof(Algorithm)))
100 | continue;
101 | var a = t.GetCustomAttribute();
102 | if ((a != null && a.DisplayName == name) || (t.Name == name)) {
103 | return (Algorithm) Activator.CreateInstance(t, new object[] { opts });
104 | }
105 | }
106 | throw new InvalidOperationException(string.Format(
107 | "Could find algorithm '{0}'.", name));
108 | }
109 |
110 | ///
111 | /// Gibt die angegebene Nachricht auf der Standardausgabe aus und beendet anschließend
112 | /// das Programm.
113 | ///
114 | ///
115 | /// Ein formatierter String.
116 | ///
117 | ///
118 | /// Etwaige Parameter des formatierten Strings.
119 | ///
120 | static void Error(string format, params object[] arg) {
121 | Console.WriteLine("error: " + format, arg);
122 | Environment.Exit(1);
123 | }
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("MeshSimplify")]
9 | [assembly: AssemblyDescription("A utility for polygon mesh simplification")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("HAW Hamburg")]
12 | [assembly: AssemblyProduct("MeshSimplify")]
13 | [assembly: AssemblyCopyright("Copyright © Torben Könke 2015")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("b50713cf-a0b4-4749-b83a-098168f48d0b")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.5")]
36 | [assembly: AssemblyFileVersion("1.0.0.5")]
37 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Synopsis
2 | ========
3 |
4 | MeshSimplify [*options*] [*input-file*]…
5 |
6 | Description
7 | ===========
8 |
9 | MeshSimplify is a program for polygon mesh simplification. It can be used to create simplified variants of an input mesh with arbitrary numbers of faces. In addition, the program is able to produce progressive-mesh representations as part of the simplification process which can be used for smooth multi-resolutional rendering of 3d objects.
10 |
11 | Using `MeshSimplify`
12 | --------------------
13 |
14 | At its most basic level `MeshSimplify` expects the desired target number of faces as well as an input mesh to create the simplification from. Input files must generally be the .obj wavefront file-format. For specifying the target number of faces, use the `-n` option:
15 |
16 | MeshSimplify -n 1000 bunny.obj
17 |
18 | By default, `MeshSimplify` produces an output file with the same name as the input file suffixed by \_out, so the above example would produce the simplified variant of the input file as bunny\_out.obj. To specify a different output location, you can use the `-o` option:
19 |
20 | MeshSimplify -n 1000 -o output.obj bunny.obj
21 |
22 | In order to have `MeshSimplify` add vertex-split records to the resulting output file and produce a progressive-mesh, you can specify the `-p` option:
23 |
24 | MeshSimplify -n 1000 -p -o pm-bunny.obj bunny.obj
25 |
26 | Finally, `MeshSimplify` is also able to restore the original mesh from a progressive-mesh representation or to expand the progressive-mesh to an arbitrary number of faces by specifying the `--restore-mesh` option. So, to expand the previously created progressive-mesh to a polycount of 20000, you could use:
27 |
28 | MeshSimplify -n 20000 --restore-mesh pm-bunny.obj
29 |
30 | For a detailed description of all available options, please refer to the next section.
31 |
32 | Options
33 | =======
34 |
35 | `-a` *ALGORITHM*, `--algorithm` *ALGORITHM*
36 | Specifies the simplification algorithm to use. Currently the only valid value for this option is `PairContract` which is an implementation of M. Garland’s ‘’Surface Simplification Using Quadric Error Metrics’’ approach. Additional algorithms may be supported in the future.
37 |
38 | `-d` *DISTANCE*, `--distance-threshold` *DISTANCE*
39 | Specifies the distance-threshold value for pair-contraction. This option is only applicable for the `PairContract` algorithm and defaults to 0.
40 |
41 | `-h`, `--help`
42 | Shows usage message.
43 |
44 | `-n`, `--num-faces`
45 | Specifies the number of faces to reduce the input mesh to.
46 |
47 | `-o` *FILE*
48 | Specifies the output file.
49 |
50 | `-p`, `--progressive-mesh`
51 | Adds vertex-split records to the resulting .obj file, effectively creating a progressive-mesh representation of the input mesh.
52 |
53 | `-r`, `--restore-mesh`
54 | Expands the input progressive-mesh to the desired face-count. If this option is specified, the `-n` option refers to the number of faces to restore the output mesh to.
55 |
56 | `-s`, `--strict`
57 | Specifies strict mode. This will cause simplification to fail if the input mesh is malformed or any other anomaly occurs.
58 |
59 | `-v`, `--verbose`
60 | Produces verbose output.
61 |
62 | `--version`
63 | Prints version information.
64 |
65 | Authors
66 | =======
67 |
68 | © 2015 Torben Könke (torben dot koenke at gmail dot com).
69 |
70 | License
71 | =======
72 |
73 | This program is released under the MIT license.
74 |
--------------------------------------------------------------------------------
/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------