Mathematica: 3D Drahtrahmen

8

Unterstützt Mathematica die Entfernung versteckter Zeilen für Drahtgitterbilder? Wenn das nicht der Fall ist, hat jemand hier jemals einen Weg gefunden, es zu tun? Beginnen wir damit:

%Vor%

Um einen Drahtrahmen zu erstellen, können wir tun:

%Vor%

Eine Sache, die wir tun können, um den Effekt zu erzielen, besteht darin, alle Oberflächen weiß zu färben. Dies ist jedoch unerwünscht. Der Grund dafür ist, dass, wenn wir dieses verdeckte Linien-Drahtrahmenmodell nach pdf exportieren, wir all diese weißen Polygone haben werden, die Mathematica verwendet, um das Bild zu rendern. Ich möchte in der Lage sein, einen Drahtrahmen mit versteckter Linienentfernung im pdf- und / oder eps-Format zu erhalten.

UPDATE:

Ich habe eine Lösung für dieses Problem gepostet. Das Problem ist, dass der Code sehr langsam läuft. In diesem Zustand kann das Drahtgitter für das Bild in dieser Frage nicht generiert werden. Fühlen Sie sich frei, mit meinem Code zu spielen. Ich habe am Ende meines Beitrags einen Link hinzugefügt. Sie finden den Code auch in diesem Link

    
jmlopez 15.06.2011, 05:16
quelle

2 Antworten

5

Hier präsentiere ich eine Lösung. Zuerst werde ich zeigen, wie man die Funktion benutzt, die den Drahtrahmen erzeugt, dann werde ich fortfahren, den Rest der Funktionen, die den Algorithmus bilden, im Detail zu erklären.

wireFrame

%Vor%

Die Eingabe dieser Funktion ist ein Graphics3D -Objekt, vorzugsweise ohne Achsen.

%Vor%

Jetzt wenden wir die Funktion wireFrame an.

%Vor%

Wie Sie sehen können, hat wireFrame die meisten Linien und ihre Farben erhalten. Es gibt eine grüne Linie, die nicht im Drahtgitter enthalten war. Dies liegt wahrscheinlich an meinen Schwelleneinstellungen.

Bevor ich fortfahre, die Details der Funktionen G3ToG2Info , getHiddenLines , getFrame und setPoints zu erklären, werde ich Ihnen zeigen, warum Drahtrahmen mit versteckter Linienentfernung nützlich sein können.

Das oben gezeigte Bild ist ein Screenshot einer PDF-Datei, die mit der in Rastern beschriebenen Technik erstellt wurde in 3D-Grafik kombiniert mit dem hier erzeugten Drahtrahmen. Dies kann auf verschiedene Arten vorteilhaft sein. Es ist nicht notwendig, die Informationen für die Dreiecke so zu speichern, dass sie eine bunte Oberfläche zeigen. Stattdessen zeigen wir ein Rasterbild der Oberfläche. Alle Linien sind sehr glatt, mit Ausnahme der Grenzen des Raster-Plots, die nicht durch Linien abgedeckt sind. Wir haben auch eine Reduzierung der Dateigröße. In diesem Fall wurde die Größe der PDF-Datei von 1,9 MB auf 78 KB reduziert, indem die Kombination aus Rasterplot und Drahtgitter verwendet wurde. Es dauert weniger Zeit im PDF-Viewer anzuzeigen und die Bildqualität ist groß.

Mathematica macht ziemlich gute Arbeit beim Exportieren von 3D-Bildern in PDF-Dateien. Wenn wir die PDF-Dateien importieren, erhalten wir ein Graphics-Objekt, das aus Liniensegmenten und Dreiecken besteht. In einigen Fällen überlappen sich diese Objekte und somit haben wir Linien versteckt. Um ein Drahtrahmenmodell ohne Oberflächen zu erstellen, müssen wir zuerst diese Überlappung entfernen und dann die Polygone entfernen. Ich werde zunächst beschreiben, wie man die Informationen von einem Graphics3D-Bild erhält.

G3ToG2Info

%Vor%

Dieser Code ist für Mathematica 8 . In Version 7 würden Sie JoinedCurve in der Funktion getPoints by Line ersetzen. Die Funktion getPoints setzt voraus, dass Sie ein primitives Graphics -Objekt geben. Es wird sehen, welche Art von Objekt es erhält und extrahiert dann die Informationen, die es benötigt. Wenn es ein Polygon ist, erhält es eine Liste von 3 Punkten, für eine Linie erhält es eine Liste von 2 Punkten und wenn es eine Farbe ist, erhält es eine Liste einer einzelnen Liste, die 3 Punkte enthält. Dies wurde so gemacht, um die Konsistenz mit den Listen zu erhalten.

Die Funktion setPoints bewirkt die Umkehrung von getPoints . Sie geben eine Liste von Punkten ein und bestimmen, ob ein Polygon, eine Linie oder eine Farbe zurückgegeben werden soll.

Um eine Liste von Dreiecken, Linien und Farben zu erhalten, verwenden wir G3ToG2Info . Diese Funktion wird verwendet ExportString und ImportString , um ein Graphics -Objekt von der Graphics3D -Version zu erhalten. Diese Information wird in obj gespeichert. Es gibt einige Aufräumarbeiten, die wir ausführen müssen, zuerst erhalten wir die Optionen von obj . Dieser Teil ist notwendig, weil er PlotRange des Bildes enthalten kann. Dann erhalten wir alle Objekte Polygon , JoinedCurve und RGBColor , wie in beschrieben Grafische Grundelemente und Anweisungen erhalten . Schließlich wenden wir die Funktion getPoints auf alle diese Objekte an, um eine Liste von Dreiecken, Linien und Farben zu erhalten. Dieser Teil umfasst die Zeile {figInfo, opt} = G3ToG2Info[g] .

getHiddenLines

Wir möchten wissen, welcher Teil einer Zeile nicht angezeigt wird. Dazu müssen wir den Schnittpunkt zweier Liniensegmente kennen. Der Algorithmus, den ich verwende, um die Kreuzung zu finden, kann hier gefunden werden.

%Vor%

lineInt geht davon aus, dass die Zeilen L und M nicht übereinstimmen. Es gibt -Infinity zurück, wenn die Zeilen parallel sind oder wenn die Zeile, die das Segment L enthält, nicht das Liniensegment M schneidet. Wenn die Zeile, die L enthält, das Liniensegment M schneidet, wird ein Skalar zurückgegeben. Angenommen dieser Skalar ist u , dann ist der Schnittpunkt L[[1]] + u (L[[2]]-L[[1]]) . Beachten Sie, dass es völlig in Ordnung ist, wenn u eine reelle Zahl ist. Sie können mit dieser Manipulationsfunktion spielen, um zu testen, wie lineInt funktioniert.

%Vor%

Jetzt, da wir wissen, wie weit wir von L[[1]] zum Liniensegment M fahren müssen, können wir herausfinden, welcher Teil eines Liniensegments innerhalb eines Dreiecks liegt.

%Vor%

Diese Funktion gibt den Teil der Zeile L zurück, der gelöscht werden muss. Wenn Sie beispielsweise {.5, 1} zurückgeben, bedeutet dies, dass Sie 50 Prozent der Zeile löschen, beginnend mit der Hälfte des Segments bis zum Endpunkt des Segments.Wenn L = {A, B} und die Funktion {u, v} zurückgibt, bedeutet dies, dass das Liniensegment {A+(B-A)u, A+(B-A)v} der Abschnitt der Linie ist, der im Dreieck T enthalten ist.

Wenn Sie lineInTri implementieren, müssen Sie darauf achten, dass die Zeile L keine der Kanten von T ist. Wenn dies der Fall ist, liegt die Linie nicht innerhalb des Dreiecks. Hier können Rundungsfehler auftreten. Wenn Mathematica das Bild exportiert, liegt manchmal eine Linie am Rand des Dreiecks, aber diese Koordinaten unterscheiden sich um einen bestimmten Betrag. Es liegt an uns zu entscheiden, wie nahe die Linie an der Kante liegt, sonst sieht die Funktion, dass die Linie fast vollständig innerhalb des Dreiecks liegt. Dies ist der Grund für die erste Zeile in der Funktion. Um zu sehen, ob eine Linie an einer Kante eines Dreiecks liegt, können wir alle Punkte des Dreiecks und der Linie auflisten und alle Duplikate löschen. Sie müssen angeben, was ein Duplikat in diesem Fall ist. Wenn wir am Ende eine Liste von 3 Punkten haben, bedeutet dies, dass eine Linie an einer Kante liegt. Der nächste Teil ist ein wenig kompliziert. Wir überprüfen den Schnittpunkt der Linie L mit jeder Kante des Dreiecks T und speichern diese Ergebnisse in einer Liste. Als nächstes sortieren wir die Liste und finden heraus, welcher Abschnitt der Linie im Dreieck liegt. Versuchen Sie, daraus einen Sinn zu ziehen, indem Sie mit diesen Tests prüfen, ob ein Endpunkt der Linie ein Eckpunkt des Dreiecks ist, ob die Linie vollständig innerhalb des Dreiecks liegt, teilweise innerhalb oder ganz außerhalb.

%Vor%

lineInTri wird verwendet, um zu sehen, welcher Teil der Linie nicht gezeichnet wird. Diese Linie wird höchstwahrscheinlich von vielen Dreiecken bedeckt sein. Aus diesem Grund müssen wir eine Liste aller Teile jeder Zeile führen, die nicht gezeichnet werden. Diese Listen haben keine Bestellung. Wir wissen nur, dass diese Listen eindimensionale Segmente sind. Jede besteht aus Zahlen im [0,1] Intervall. Mir ist keine Vereinigungsfunktion für eindimensionale Segmente bekannt, daher hier meine Implementierung.

%Vor%

Diese Funktion wäre kürzer, aber hier habe ich einige if-Anweisungen eingefügt, um zu prüfen, ob eine Zahl nahe Null oder Eins ist. Wenn eine Zahl EPS von Null entfernt ist, dann machen wir diese Zahl Null, das Gleiche gilt für eins. Ein weiterer Aspekt, den ich hier behandle, ist, dass, wenn ein relativ kleiner Teil des Segments angezeigt wird, es höchstwahrscheinlich ist, dass es gelöscht werden muss. Zum Beispiel, wenn wir {{0,.5}, {.500000000001}} haben, bedeutet dies, dass wir {{.5, .500000000001}} zeichnen müssen. Aber dieses Segment ist sehr klein, um besonders in einem großen Liniensegment bemerkt zu werden, denn alles, was wir wissen, sind dieselben Zahlen. All diese Dinge müssen bei der Implementierung von union berücksichtigt werden.

Jetzt können wir sehen, was aus einem Liniensegment gelöscht werden muss. Die nächste benötigt die Liste der Objekte, die von G3ToG2Info generiert wurden, ein Objekt aus dieser Liste und einen Index.

%Vor%

getSections gibt eine Liste mit den Teilen zurück, die aus L gelöscht werden müssen. Wir wissen, dass obj die Liste der Dreiecke, Linien und Farben ist. Wir wissen, dass Objekte in der Liste mit einem höheren Index über diejenigen mit niedrigerem Index gezeichnet werden. Aus diesem Grund benötigen wir den Index start . In diesem Index suchen wir nach Dreiecken in obj . Sobald wir ein Dreieck gefunden haben, erhalten wir den Teil des Segments, der im Dreieck liegt, mit der Funktion lineInTri . Am Ende erhalten wir eine Liste von Abschnitten, die wir kombinieren können, indem wir union verwenden.

Schließlich kommen wir zu getHiddenLines . Dazu müssen Sie jedes Objekt in der von G3ToG2Info zurückgegebenen Liste betrachten und die Funktion getSections anwenden. getHiddenLines gibt eine Liste von Listen zurück. Jedes Element ist eine Liste von Abschnitten, die gelöscht werden müssen.

%Vor%

getFrame

Wenn Sie die Konzepte bis hierher verstanden haben, wissen Sie bestimmt, was als nächstes getan wird. Wenn wir die Liste der Dreiecke, Linien und Farben und die Abschnitte der Linien, die gelöscht werden müssen, haben, müssen wir nur die Farben und die Abschnitte der Linien zeichnen, die sichtbar sind. Zuerst machen wir eine complement -Funktion, das sagt uns genau, was gezeichnet werden soll.

%Vor%

Jetzt die Funktion getFrame

%Vor%

Letzte Wörter

Ich bin etwas glücklich mit den Ergebnissen des Algorithmus. Was ich nicht mag, ist die Ausführungsgeschwindigkeit. Ich habe dies geschrieben, wie ich in C / C ++ / Java mit Schleifen. Ich habe versucht, Reap und Sow zu verwenden, um wachsende Listen zu erstellen, anstatt die Funktion Append zu verwenden. Unabhängig davon musste ich immer noch Loops verwenden. Es sollte beachtet werden, dass das Drahtgitterbild, das hier gepostet wurde, 63 Sekunden benötigte, um erzeugt zu werden. Ich habe versucht, ein Drahtrahmen für das Bild in der Frage, aber dieses 3D-Objekt enthält etwa 32000 Objekte. Es dauerte ungefähr 13 Sekunden, um die Teile zu berechnen, die für eine Zeile angezeigt werden müssen. Wenn wir annehmen, dass wir 32000 Zeilen haben, und es dauert 13 Sekunden, um alle Berechnungen durchzuführen, die etwa 116 Stunden Rechenzeit betragen werden.

Ich bin sicher, dass diese Zeit reduziert werden kann, wenn wir die Funktion Compile für alle Routinen verwenden und vielleicht einen Weg finden, die Do Schleifen nicht zu benutzen. Kann ich hier Hilfe bei Stack Overflow bekommen?

Für Ihre Bequemlichkeit habe ich den Code ins Web hochgeladen. Sie finden es hier . Wenn Sie eine modifizierte Version dieses Codes auf das Diagramm in der Frage anwenden und den Drahtgitterrahmen anzeigen können, werde ich Ihre Lösung als die Antwort auf diesen Beitrag markieren.

Am besten,  J Manuel Lopez

    
jmlopez 22.06.2011, 09:17
quelle
1

Das ist nicht richtig, aber etwas interessant:

Plot3D[Sin[x + y^2], {x, -3, 3}, {y, -2, 2}, Boxed -> False, PlotStyle -> {EdgeForm[None], FaceForm[Red, None]}, Mesh -> False]

Bei einer FaceForm von None wird das Polygon nicht gerendert. Ich bin mir nicht sicher, ob es eine Möglichkeit gibt, dies mit den Mesh-Linien zu tun.

    
Joshua Martell 15.06.2011 12:36
quelle