Pregunta

¿Apoya Mathematica? Extracción de línea oculta para imágenes de marco de alambre? Si este no es el caso, ¿alguien aquí ha encontrado una forma de hacerlo? Comencemos con esto:

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

output

Para crear un marco de alambre, podemos hacer:

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

output

Una cosa que podemos hacer para lograr el efecto es colorear todas las superficies blancas. Sin embargo, esto es indeseable. La razón es porque si exportamos este modelo de marco de cable de línea oculto a PDF, tendremos todos esos polígonos blancos que Mathematica usa para representar la imagen. Quiero poder obtener un marco de cable con eliminación de línea oculta en formato PDF y/o EPS.


ACTUALIZAR:

He publicado una solución a este problema. El problema es que el código se ejecuta muy lento. En su estado actual, no puede generar la estructura de alambre para la imagen en esta pregunta. Siéntase libre de jugar con mi código. Agregué un enlace al final de mi publicación. También puede encontrar el código en este Enlace

¿Fue útil?

Solución

Aquí presento una solución. Primero mostraré cómo usar la función que genera el marco de cable, luego procederé a explicar en detalle el resto de las funciones que componen el algoritmo.


wireFrame

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

La entrada de esta función es un Graphics3D objeto preferiblemente sin ejes.

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

Ahora aplicamos la función wireFrame.

wireFrame[fig]

wireframe

Como puedes ver wireFrame obtuvo la mayoría de las líneas y sus colores. Hay una línea verde que no se incluyó en la estructura alámbrica. Esto probablemente se deba a mi configuración de umbral.

Antes de proceder a explicar los detalles de las funciones G3ToG2Info, getHiddenLines, getFrame y setPoints Te mostraré por qué los marcos de cable con la extracción de línea oculta pueden ser útiles.

RasterWire

La imagen que se muestra arriba es una captura de pantalla de un archivo PDF generado mediante el uso de la técnica descrita en Rásters en gráficos 3D combinado con el marco de cable generado aquí. Esto puede ser ventajoso de varias maneras. No es necesario mantener la información para los triángulos para mostrar una superficie colorida. En cambio, mostramos una imagen ráster de la superficie. Todas las líneas son muy suaves, con la excepción de los límites de la parcela de ráster no cubierto por líneas. También tenemos una reducción del tamaño del archivo. En este caso, el tamaño del archivo PDF se redujo de 1.9MB a 78 kb utilizando la combinación de la gráfica ráster y el marco de cable. Se necesita menos tiempo para mostrar en el visor PDF y la calidad de la imagen es excelente.

Mathematica hace un trabajo bastante bueno al exportar imágenes 3D a archivos PDF. Cuando importamos los archivos PDF, obtenemos un objeto gráfico compuesto por segmentos de línea y triángulos. En algunos casos, estos objetos se superponen y, por lo tanto, tenemos líneas ocultas. Para hacer un modelo de marco de cables sin superficies, primero debemos eliminar esta superposición y luego retirar los polígonos. Comenzaré describiendo cómo obtener la información de una imagen gráfica3D.


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}
  ]

Este código es para Mathematica 8 En la versión 7 reemplazarías JoinedCurve en la función getPoints por Line. La función getPoints asume que estás dando un primitivo Graphics objeto. Verá qué tipo de objeto recibe y luego extraerá la información que necesita de él. Si es un polígono, obtiene una lista de 3 puntos, para una línea obtiene una lista de 2 puntos y si es un color, entonces obtiene una lista de una sola lista que contiene 3 puntos. Esto se ha hecho así para mantener la consistencia con las listas.

La función setPoints ¿El reverso de getPoints. Ingrese una lista de puntos y determinará si debe devolver un polígono, una línea o un color.

Para obtener una lista de triángulos, líneas y colores que usamos G3ToG2Info. Esta función usaráExportString y ImportString Para obtener un Graphics objeto del Graphics3D versión. Esta información es tienda en obj. Hay algo de limpieza que necesitamos realizar, primero obtenemos las opciones del obj. Esta parte es necesaria porque puede contener el PlotRange de la imagen. Luego obtenemos todo el Polygon, JoinedCurve y RGBColor objetos como se describe en Obtener primitivas y directivas gráficas. Finalmente aplicamos la función getPoints En todos estos objetos para obtener una lista de triángulos, líneas y colores. Esta parte cubre la línea {figInfo, opt} = G3ToG2Info[g].


getHiddenLines

Queremos poder saber qué parte de una línea no se mostrará. Para hacer esto, necesitamos conocer el punto de intersección entre dos segmentos de línea. El algoritmo que estoy usando para encontrar la intersección se puede encontrar aquí.

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 asume que la línea L y M No coincidan. Volverá -Infinity Si las líneas son paralelas o si la línea que contiene el segmento L no cruza el segmento de línea M. Si la línea que contiene L se cruza con el segmento de línea M Luego devuelve un escalar. Supongamos que este escalar es u, entonces el punto de intersección es L[[1]] + u (L[[2]]-L[[1]]). Tenga en cuenta que está perfectamente bien para u ser cualquier número real. Puedes jugar con esta función de manipulación para probar cómo lineInt obras.

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

Ahora que sabemos cómo lejos tenemos que viajar desde L[[1]] al segmento de línea M Podemos descubrir qué parte de un segmento de línea se encuentra dentro de un triángulo.

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]];
 ]

Esta función devuelve la parte de la línea L Eso necesita ser eliminado. Por ejemplo, si regresa {.5, 1} Esto significa que eliminará el 50 por ciento de la línea, comenzando desde la mitad del segmento hasta el punto final del segmento. Si L = {A, B} y la función regresa {u, v} Entonces esto significa que el segmento de línea {A+(B-A)u, A+(B-A)v} es la sección de la línea que contiene en el triángulo T.

Al implementar lineInTri Debes tener cuidado de que la línea L no es uno de los bordes de T, si este es el caso, la línea no se encuentra dentro del triángulo. Aquí es donde los erros de redondeo pueden ser malos. Cuando Mathematica Exporta la imagen A veces, una línea se encuentra en el borde del triángulo, pero estas coordenadas difieren en cierta cantidad. Depende de nosotros decidir qué tan cerca se encuentra la línea en el borde, de lo contrario, la función verá que la línea se encuentra casi completamente dentro del triángulo. Esta es la razón de la primera línea en la función. Para ver si una línea se encuentra en un borde de un triángulo, podemos enumerar todos los puntos del triángulo y la línea, y eliminar todos los duplicados. Debe especificar qué es un duplicado en este caso. Al final, si terminamos con una lista de 3 puntos, esto significa que una línea se encuentra en un borde. La siguiente parte es un poco complicada. Lo que hacemos es verificar la intersección de la línea L con cada borde del triángulo T y almacene esto los resultados en una lista. A continuación, ordenamos la lista y descubrimos qué sección, si la hay, de la línea se encuentra en el triángulo. Trate de darle sentido jugando con esto, algunas de las pruebas incluyen verificar si un punto final de la línea es un vértice del triángulo, si la línea está completamente dentro del triángulo, en parte o completamente afuera.

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 se utilizará para ver qué parte de la línea no se dibujará. Es muy probable que esta línea esté cubierta por muchos triángulos. Por esta razón, necesitamos mantener una lista de todas las partes de cada línea que no se dibujarán. Estas listas no tendrán un pedido. Todo lo que sabemos es que estas listas son segmentos unidimensionales. Cada uno que consiste en números en el [0,1] intervalo. No conozco una función sindical para segmentos unidimensionales, así que aquí está mi implementación.

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]]]
 ]

Esta función sería más corta, pero aquí he incluido algunas declaraciones si verifican si un número está cerca de cero o uno. Si un número es EPS Aparte de cero, entonces hacemos este número cero, lo mismo se aplica para uno. Otro aspecto que estoy cubriendo aquí es que si hay una porción relativamente pequeña del segmento que se exhibirá, entonces es más probable que deba eliminarse. Por ejemplo si tenemos {{0,.5}, {.500000000001}} Esto significa que necesitamos dibujar {{.5, .500000000001}}. Pero este segmento es muy pequeño para ser notado especialmente en un segmento de línea grande, por lo que sabemos que esos dos números son los mismos. Todas estas cosas deben tenerse en cuenta al implementar union.

Ahora estamos listos para ver qué debe eliminarse de un segmento de línea. La siguiente requiere la lista de objetos generados desde G3ToG2Info, un objeto de esta lista y un índice.

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 Devuelve una lista que contiene las porciones que deben eliminarse L. Lo sabemos obj es la lista de triángulos, líneas y colores, sabemos que los objetos en la lista con un índice más alto se dibujarán encima de los que se encuentran con un índice más bajo. Por esta razón necesitamos el índice start. Este es el índice, comenzaremos a buscar triángulos en obj. Una vez que encontremos un triángulo, obtendremos la parte del segmento que se encuentra en el triángulo usando la función lineInTri. Al final terminaremos con una lista de secciones que podemos combinar usando union.

Finalmente, llegamos a getHiddenLines. Todo lo que esto requiere es mirar cada objeto en la lista devuelto por G3ToG2Info y aplicar la función getSections. getHiddenLines devolverá una lista de listas. Cada elemento es una lista de secciones que deben eliminarse.

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

Si ha logrado comprender los conceptos hasta aquí, estoy seguro de que sabe lo que se hará a continuación. Si tenemos la lista de triángulos, líneas y colores y las secciones de las líneas que deben eliminarse, necesitamos dibujar solo los colores y las secciones de las líneas que son visibles. Primero hacemos un complement función, esto nos dirá exactamente qué dibujar.

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]
 ]

Ahora el getFrame función

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
]

Ultimas palabras

Estoy algo contento con los resultados del algoritmo. Lo que no me gusta es la velocidad de ejecución. He escrito esto como lo haría en c/c ++/java usando bucles. Hice todo lo posible para usar Reap y Sow para crear listas de crecimiento en lugar de usar la función Append. Independientemente de todo esto, todavía tenía que usar bucles. Cabe señalar que la imagen del marco de cable publicada aquí tardó 63 segundos en generar. Intenté hacer un marco de alambre para la imagen en la pregunta, pero este objeto 3D contiene aproximadamente 32000 objetos. Estaba tardando unos 13 segundos en calcular las porciones que deben mostrarse para una línea. Si suponemos que tenemos 32000 líneas y tarda 13 segundos en hacer todos los cálculos que serán aproximadamente 116 horas de tiempo computacional.

Estoy seguro de que esta vez se puede reducir si usamos la función Compile en todas las rutinas y tal vez encontrar una manera de no usar el Do bucles. ¿Puedo obtener ayuda aquí el desbordamiento de la pila?

Para su convinencia, he subido el código a la web. Puedes encontrarlo aquí. Si puede aplicar una versión modificada de este código a la trama en la pregunta y mostrar el marco de cable, marcaré su solución como respuesta a esta publicación.

Lo mejor, J Manuel López

Otros consejos

Esto no es correcto, pero algo interesante:

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

Sin una forma de cara de ninguno, el polígono no se representa. No estoy seguro de que haya una manera de hacer esto con las líneas de malla.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top