Frage

Unterstützt Mathematica Versteckte Linienentfernung Für Drahtrahmenbilder? Wenn dies nicht der Fall ist, ist jemand hier jemals auf einen Weg gestoßen, dies zu tun? Beginnen wir damit:

Plot3D[Sin[x+y^2], {x, -3, 3}, {y, -2, 2}, Boxed -> False]

output

Um einen Drahtrahmen zu erstellen, können wir:

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

output

Eine Sache, die wir tun können, um den Effekt zu erzielen, ist, alle weißen Oberflächen zu färben. Dies ist jedoch unerwünscht. Der Grund dafür ist, dass wir, wenn wir dieses versteckte Line -Drahtrahmenmodell in PDF exportieren, all diese weißen Polygone haben, mit denen Mathematica das Bild rendert. Ich möchte in der Lage sein, einen Kabelrahmen mit versteckter Linienentfernung im PDF- und/oder EPS -Format zu erhalten.


AKTUALISIEREN:

Ich habe eine Lösung für dieses Problem gepostet. Das Problem ist, dass der Code sehr langsam läuft. In seinem aktuellen Zustand kann es in dieser Frage den Wireframe für das Bild nicht generieren. Fühlen Sie sich frei, mit meinem Code zu spielen. Ich habe am Ende meines Beitrags einen Link dazu hinzugefügt. Sie können den Code auch darin finden Verknüpfung

War es hilfreich?

Lösung

Hier präsentiere ich eine Lösung. Zunächst werde ich zeigen, wie die Funktion verwendet wird, die den Kabelrahmen erzeugt, und dann werde ich den Rest der Funktionen, aus denen der Algorithmus komponiert wird, ausführlich erklären.


wireFrame

wireFrame[g_] := Module[{figInfo, opt, pts},
   {figInfo, opt} = G3ToG2Info[g];
   pts = getHiddenLines[figInfo];
   Graphics[Map[setPoints[#] &, getFrame[figInfo, pts]], opt]
]

Die Eingabe dieser Funktion ist a Graphics3D Objekt vorzugsweise ohne Achsen.

fig = ListPlot3D[
   {{0, -1, 0}, {0, 1, 0}, {-1, 0, 1}, {1, 0, 1}, {-1, 1, 1}},
   Mesh -> {10, 10},
   Boxed -> False,
   Axes -> False,
   ViewPoint -> {2, -2, 1},
   ViewVertical -> {0, 0, 1},
   MeshStyle -> Directive[RGBColor[0, 0.5, 0, 0.5]],
   BoundaryStyle -> Directive[RGBColor[1, 0.5, 0, 0.5]]
]

surface

Jetzt wenden wir die Funktion an wireFrame.

wireFrame[fig]

wireframe

Wie du sehen kannst wireFrame die meisten Linien und ihre Farben erhalten. Es gibt eine grüne Linie, die nicht im Drahtmodell enthalten war. Dies ist höchstwahrscheinlich auf meine Schwelleneinstellungen zurückzuführen.

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

RasterWire

Das oben gezeigte Bild ist ein Screenshot einer PDF -Datei, die mit der in der beschriebenen Technik generiert wird Raster in 3D -Grafiken kombiniert mit dem hier erzeugten Kabelrahmen. Dies kann auf verschiedene Weise vorteilhaft sein. Es ist nicht erforderlich, die Informationen für die Dreiecke zu behalten, um eine farbenfrohe Oberfläche zu zeigen. Stattdessen zeigen wir ein Rasterbild der Oberfläche. Alle Linien sind sehr glatt, mit Ausnahme der Grenzen des Raster -Diagramms, das nicht von Linien bedeckt ist. Wir haben auch eine Reduzierung der Dateigröße. In diesem Fall reduzierte sich die PDF -Dateigröße von 1,9 MB auf 78 KB unter Verwendung der Kombination des Rasterdiagramms und des Kabelrahmens. Die Anzeige im PDF -Viewer dauert weniger Zeit und die Bildqualität ist großartig.

Mathematica Macht einen ziemlich guten Job beim Exportieren von 3D -Bildern in PDF -Dateien. Wenn wir die PDF -Dateien importieren, erhalten wir ein Grafikobjekt, das aus Liniensegmenten und Dreiecken besteht. In einigen Fällen überlappen sich diese Objekte und somit haben wir versteckte Linien. Um ein Kabelrahmenmodell ohne Oberflächen herzustellen, müssen wir zuerst diese Überlappung entfernen und dann die Polygone entfernen. Ich werde zunächst beschreiben, wie die Informationen aus einem Grafik3d -Bild erhalten werden.


G3ToG2Info

getPoints[obj_] := Switch[Head[obj], 
   Polygon, obj[[1]], 
   JoinedCurve, obj[[2]][[1]], 
   RGBColor, {Table[obj[[i]], {i, 1, 3}]}
  ];
setPoints[obj_] := Switch[Length@obj, 
   3, Polygon[obj], 
   2, Line[obj], 
   1, RGBColor[obj[[1]]]
  ];
G3ToG2Info[g_] := Module[{obj, opt},
   obj = ImportString[ExportString[g, "PDF", Background -> None], "PDF"][[1]];
   opt = Options[obj];
   obj = Flatten[First[obj /. Style[expr_, opts___] :> {opts, expr}], 2];
   obj = Cases[obj, _Polygon | _JoinedCurve | _RGBColor, Infinity];
   obj = Map[getPoints[#] &, obj];
   {obj, opt}
  ]

Dieser Code ist für Mathematica 8 In Version 7 würden Sie ersetzen JoinedCurve in der Funktion getPoints durch Line. Die Funktion getPoints Angenommen, Sie geben einen primitiven Graphics Objekt. Es wird sehen, welche Art von Objekt es erhalten, und dann die von ihnen benötigten Informationen extrahiert. Wenn es sich um ein Polygon handelt, erhält es eine Liste von 3 Punkten, für eine Zeile erhält es eine Liste von 2 Punkten. Wenn es sich um eine Farbe handelt, wird eine Liste einer einzigen Liste mit 3 Punkten enthält. Dies wurde so durchgeführt, um die Konsistenz mit den Listen aufrechtzuerhalten.

Die Funktion setPoints macht das Gegenteil von getPoints. Sie geben eine Liste von Punkten ein und bestimmt, ob es ein Polygon, eine Linie oder eine Farbe zurückgeben sollte.

Um eine Liste von Dreiecken, Linien und Farben zu erhalten, die wir verwenden G3ToG2Info. Diese Funktion wird verwendetExportString und ImportString um a zu erhalten Graphics Objekt aus dem Graphics3D Ausführung. Diese Info ist in Store in gelagert obj. Es gibt einige Aufräumarbeiten, die wir durchführen müssen. Zuerst erhalten wir die Optionen der obj. Dieser Teil ist notwendig, weil er die enthalten kann PlotRange des Bildes. Dann erhalten wir alle Polygon, JoinedCurve und RGBColor Objekte wie in beschrieben in Erhalten von Grafikprimitiven und Richtlinien. Schließlich wenden wir die Funktion an getPoints Bei all diesen Objekten, um eine Liste von Dreiecken, Linien und Farben zu erhalten. Dieser Teil deckt die Linie ab {figInfo, opt} = G3ToG2Info[g].


getHiddenLines

Wir möchten wissen können, welcher Teil einer Linie nicht angezeigt wird. Dazu müssen wir den Schnittpunkt zwischen zwei Liniensegmenten kennen. Der Algorithmus, den ich benutze, um die Kreuzung zu finden hier.

lineInt[L_, M_, EPS_: 10^-6] := Module[
  {x21, y21, x43, y43, x13, y13, numL, numM, den},
  {x21, y21} = L[[2]] - L[[1]];
  {x43, y43} = M[[2]] - M[[1]];
  {x13, y13} = L[[1]] - M[[1]];
  den = y43*x21 - x43*y21;
  If[den*den < EPS, Return[-Infinity]];
  numL = (x43*y13 - y43*x13)/den;
  numM = (x21*y13 - y21*x13)/den;
  If[numM < 0 || numM > 1, Return[-Infinity], Return[numL]];
 ]

lineInt nimmt an, dass die Linie L und M fällt nicht zusammen. Es wird zurückkehren -Infinity Wenn die Linien parallel sind oder wenn die Linie das Segment enthält L Überquert das Liniensegment nicht M. Wenn die Linie enthält L Überschneidet das Liniensegment M Dann gibt es einen Skalar zurück. Angenommen, dieser Skalar ist u, dann ist der Schnittpunkt ist L[[1]] + u (L[[2]]-L[[1]]). Beachten Sie, dass es vollkommen in Ordnung ist für u eine echte Zahl sein. Sie können mit dieser manipulierten Funktion spielen, um zu testen, wie lineInt Arbeiten.

Manipulate[
   Grid[{{
      Graphics[{
        Line[{p1, p2}, VertexColors -> {Red, Red}],
        Line[{p3, p4}]
       },
       PlotRange -> 3, Axes -> True],
      lineInt[{p1, p2}, {p3, p4}]
     }}],
   {{p1, {-1, 1}}, Locator, Appearance -> "L1"},
   {{p2, {2, 1}}, Locator, Appearance -> "L2"},
   {{p3, {1, -1}}, Locator, Appearance -> "M1"},
   {{p4, {1, 2}}, Locator, Appearance -> "M2"}
]

Example

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

lineInTri[L_, T_] := Module[{res},
  If[Length@DeleteDuplicates[Flatten[{T, L}, 1], SquaredEuclideanDistance[#1, #2] < 10^-6 &] == 3, Return[{}]];
  res = Sort[Map[lineInt[L, #] &, {{T[[1]], T[[2]]}, {T[[2]], T[[3]]},  {T[[3]], T[[1]]} }]];
  If[res[[3]] == Infinity || res == {-Infinity, -Infinity, -Infinity}, Return[{}]];
  res = DeleteDuplicates[Cases[res, _Real | _Integer | _Rational], Chop[#1 - #2] == 0 &];
  If[Length@res == 1, Return[{}]];
  If[(Chop[res[[1]]] == 0 && res[[2]] > 1) || (Chop[res[[2]] - 1] == 0 && res[[1]] < 0), Return[{0, 1}]];
  If[(Chop[res[[2]]] == 0 && res[[1]] < 0) || (Chop[res[[1]] - 1] == 0 && res[[2]] > 1), Return[{}]];
  res = {Max[res[[1]], 0], Min[res[[2]], 1]};
  If[res[[1]] > 1 || res[[1]] < 0 || res[[2]] > 1 || res[[2]] < 0, Return[{}], Return[res]];
 ]

Diese Funktion gibt den Teil der Linie zurück L Das muss gelöscht werden. Zum Beispiel, wenn es zurückkehrt {.5, 1} Dies bedeutet, dass Sie 50 Prozent der Linie löschen, beginnend von der Hälfte des Segments bis zum Ende des Segments. Wenn L = {A, B} und die Funktion kehrt zurück {u, v} Dann bedeutet dies, dass das Liniensegment {A+(B-A)u, A+(B-A)v} ist der Abschnitt der Linie, den sie im Dreieck enthalten ist T.

Beim Implementieren lineInTri Sie müssen vorsichtig sein, dass die Linie L ist nicht einer der Kanten von T, Wenn dies der Fall ist, liegt die Linie nicht im Dreieck. Hier kann rundende Fehler schlecht sein. Wann Mathematica Exportiert das Bild Manchmal liegt eine Linie am Rand des Dreiecks, aber diese Koordinaten unterscheiden sich in gewisser Weise. Es liegt an uns zu entscheiden, wie nahe die Linie am Rand liegt, sonst wird die Funktion sehen, dass die Linie fast vollständig im Dreieck liegt. Dies ist der Grund für die erste Zeile in der Funktion. Um zu sehen, ob eine Linie am Rand 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 am Rand liegt. Der nächste Teil ist etwas kompliziert. Wir überprüfen nach dem Schnittpunkt der Linie L mit jeder Kante des Dreiecks T und speichern Sie dies die Ergebnisse in einer Liste. Als nächstes sortieren wir die Liste und finden heraus, welcher Abschnitt der Linie, wenn überhaupt, im Dreieck liegt. Versuchen Sie, dies zu verstehen, indem Sie damit spielen. Einige der Tests umfassen die Überprüfung, ob ein Endpunkt der Linie ein Scheitelpunkt des Dreiecks ist, wenn sich die Linie vollständig innerhalb des Dreiecks befindet, teilweise drinnen oder vollständig draußen.

Manipulate[
  Grid[{{
    Graphics[{
      RGBColor[0, .5, 0, .5], Polygon[{p3, p4, p5}],
      Line[{p1, p2}, VertexColors -> {Red, Red}]
     },
     PlotRange -> 3, Axes -> True],
    lineInTri[{p1, p2}, {p3, p4, p5}]
   }}],
 {{p1, {-1, -2}}, Locator, Appearance -> "L1"},
 {{p2, {0, 0}}, Locator, Appearance -> "L2"},
 {{p3, {-2, -2}}, Locator, Appearance -> "T1"},
 {{p4, {2, -2}}, Locator, Appearance -> "T2"},
 {{p5, {-1, 1}}, Locator, Appearance -> "T3"}
]

triangle test

lineInTri Wird verwendet, um zu sehen, welcher Teil der Linie nicht gezogen wird. Diese Linie wird höchstwahrscheinlich von vielen Dreiecken abgedeckt. Aus diesem Grund müssen wir eine Liste aller Teile jeder Zeile behalten, die nicht gezeichnet werden. Diese Listen haben keine Bestellung. Wir wissen nur, dass diese Listen eine dimensionale Segmente sind. Jeder, der aus Zahlen in der besteht [0,1] Intervall. Mir ist eine Gewerkschaftsfunktion für eine dimensionale Segmente nicht bekannt. Hier ist meine Implementierung.

union[obj_] := Module[{p, tmp, dummy, newp, EPS = 10^-3},
  p = Sort[obj];
  tmp = p[[1]];
  If[tmp[[1]] < EPS, tmp[[1]] = 0];
  {dummy, newp} = Reap[
    Do[
     If[(p[[i, 1]] - tmp[[2]]) > EPS && (tmp[[2]] - tmp[[1]]) > EPS, 
       Sow[tmp]; tmp = p[[i]], 
       tmp[[2]] = Max[p[[i, 2]], tmp[[2]]]
      ];
     , {i, 2, Length@p}
    ];
    If[1 - tmp[[2]] < EPS, tmp[[2]] = 1];
    If[(tmp[[2]] - tmp[[1]]) > EPS, Sow[tmp]];
   ];
  If[Length@newp == 0, {}, newp[[1]]]
 ]

Diese Funktion wäre kürzer, aber hier habe ich einige IF -Anweisungen beigefügt, um zu überprüfen, ob eine Zahl nahe Null oder eins ist. Wenn eine Nummer ist EPS Abgesehen von Null machen wir diese Zahl Null, das gilt auch für eine. Ein weiterer Aspekt, den ich hier abdecke, ist, dass wenn ein relativ kleiner Teil des Segments angezeigt werden muss, ist es höchstwahrscheinlich, dass es gelöscht werden muss. Zum Beispiel, wenn wir haben {{0,.5}, {.500000000001}} Dies bedeutet, dass wir zeichnen müssen {{.5, .500000000001}}. Dieses Segment ist jedoch sehr klein, um speziell in einem großen Liniensegment zu bemerken, denn alles, was diese beiden Zahlen gleich sind. All diese Dinge müssen bei der Implementierung berücksichtigt werden union.

Jetzt sind wir bereit zu sehen, was aus einem Liniensegment gelöscht werden muss. Die nächste erfordert die Liste der Objekte, die aus erstellt wurden G3ToG2Info, ein Objekt aus dieser Liste und ein Index.

getSections[L_, obj_, start_ ] := Module[{dummy, p, seg},
  {dummy, p} = Reap[
    Do[
     If[Length@obj[[i]] == 3,
      seg =  lineInTri[L, obj[[i]]];
      If[Length@seg != 0, Sow[seg]];
     ]
     , {i, start, Length@obj}
    ]
   ];
  If[Length@p == 0, Return[{}], Return[union[First@p]]];
 ]

getSections Gibt eine Liste zurück, die die Teile enthält, aus denen gelöscht werden muss L. Wir wissen das obj Ist die Liste der Dreiecke, Linien und Farben, wir wissen, dass Objekte in der Liste mit einem höheren Index über diejenigen mit niedrigerem Index gezogen werden. Aus diesem Grund brauchen wir den Index start. Dies ist der Index, in dem wir nach Dreiecken suchen werden obj. Sobald wir ein Dreieck gefunden haben, erhalten wir den Teil des Segments, der im Dreieck mit der Funktion liegt lineInTri. Am Ende werden wir eine Liste von Abschnitten haben, die wir mit Verwendung kombinieren können union.

Schließlich kommen wir zu getHiddenLines. All dies erfordert, jedes Objekt in der von der Liste zurückgegebenen Liste zu betrachten, die von zurückgegeben wurden G3ToG2Info und wenden Sie die Funktion an getSections. getHiddenLines Gibt eine Liste von Listen zurück. Jedes Element ist eine Liste von Abschnitten, die gelöscht werden müssen.

getHiddenLines[obj_] := Module[{pts},
  pts = Table[{}, {Length@obj}];
  Do[
   If[Length@obj[[j]] == 2,
      pts[[j]] = getSections[obj[[j]], obj, j + 1]
    ];
    , {j, Length@obj}
   ];
   Return[pts];
  ]

getFrame

Wenn Sie es schaffen, die Konzepte hier zu verstehen, wissen Sie sicher, was als nächstes getan wird. Wenn wir die Liste der Dreiecke, Linien und Farben und die Abschnitte der Linien haben, die gelöscht werden müssen, müssen wir nur die Farben und Abschnitte der sichtbaren Linien zeichnen. Zuerst machen wir a complement Funktion, dies wird uns genau sagen, was wir zeichnen sollen.

complement[obj_] := Module[{dummy, p},
  {dummy, p} = Reap[
    If[obj[[1, 1]] != 0, Sow[{0, obj[[1, 1]]}]];
    Do[
     Sow[{obj[[i - 1, 2]], obj[[i, 1]]}]
     , {i, 2, Length@obj}
    ];
    If[obj[[-1, 2]] != 1, Sow[{obj[[-1, 2]], 1}]];
   ];
  If[Length@p == 0, {}, Flatten@ First@p]
 ]

Jetzt die getFrame Funktion

getFrame[obj_, pts_] := Module[{dummy, lines, L, u, d},
  {dummy, lines} = Reap[
    Do[
     L = obj[[i]];
     If[Length@L == 2,
      If[Length@pts[[i]] == 0, Sow[L]; Continue[]];
      u = complement[pts[[i]]];
      If[Length@u > 0, 
       Do[
        d = L[[2]] - L[[1]];
        Sow[{L[[1]] + u[[j - 1]] d, L[[1]] + u[[j]] d}]
        , {j, 2, Length@u, 2 }]
      ];
    ];
    If[Length@L == 1, Sow[L]];
    , {i, Length@obj}]
  ];
 First@lines
]

Letzte Worte

Ich bin etwas zufrieden mit den Ergebnissen des Algorithmus. Was ich nicht mag, ist die Ausführungsgeschwindigkeit. Ich habe das so geschrieben, wie ich es in C/C ++/Java mit Loops tun würde. Ich versuchte mein Bestes zu verwenden Reap und Sow Erstellen Sie wachsende Listen anstatt die Funktion zu verwenden Append. Unabhängig davon musste ich noch Schleifen verwenden. Es ist zu beachten, dass das hier veröffentlichte Drahtrahmenbild 63 Sekunden dauerte, bis es generiert wurde. Ich habe versucht, in der Frage einen Kabelrahmen für das Bild zu machen, aber dieses 3D -Objekt enthält ungefähr 32000 Objekte. Es dauerte ungefähr 13 Sekunden, um die Teile zu berechnen, die für eine Linie angezeigt werden müssen. Wenn wir davon ausgehen, dass wir 32000 Zeilen haben und es 13 Sekunden dauert, um alle Berechnungen durchzuführen, die etwa 116 Stunden Rechenzeit dauern.

Ich bin sicher, dass diese Zeit reduziert werden kann, wenn wir die Funktion verwenden Compile auf allen Routinen und vielleicht einen Weg finden, das nicht zu verwenden Do Schleifen. Kann ich hier einen Hilfe für den Stapelüberlauf bekommen?

Für Ihre Verurteilung habe ich den Code in das Web hochgeladen. Du kannst es finden hier. Wenn Sie eine modifizierte Version dieses Codes in der Frage in der Frage anwenden und den Kabelrahmen anzeigen können, markiert ich Ihre Lösung als Antwort auf diesen Beitrag.

Am besten, J Manuel Lopez

Andere Tipps

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]

Mit einer Gesichtsform von keiner wird das Polygon nicht gerendert. Ich bin mir nicht sicher, ob es eine Möglichkeit gibt, dies mit den Netzlinien zu tun.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top