本文介绍了Mathematica:3D线框的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

Mathematica是否支持隐藏线去除用于线框图像?如果不是这种情况,这里有没有人遇到过这样做的方法?让我们从这里开始:

Does Mathematica support hidden line removal for wire frame images? If this isn't the case, has anybody here ever come across a way to do it? Lets start with this:

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

要创建线框,我们可以执行以下操作:

To create a wire frame we can do:

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

我们可以做的一件事就是将所有表面涂成白色.但是,这是不希望的.原因是因为如果将这个隐藏线框模型导出为pdf,我们将拥有Mathematica用于渲染图像的所有白色多边形.我希望能够以pdf和/或eps格式获得具有隐藏线去除功能的线框.

One thing we can do to achieve the effect is to color the all the surfaces white. This however, is undesirable. The reason is because if we export this hidden line wire frame model to pdf we will have all of those white polygons that Mathematica uses to render the image. I want to be able to obtain a wire frame with hidden line removal in pdf and/or eps format.

我已经发布了针对此问题的解决方案.问题是代码运行非常缓慢.在当前状态下,无法生成此问题中图像的线框.随意玩我的代码.我在文章末尾添加了指向它的链接.您也可以在此链接

I have posted a solution to this problem. The problem is that the code runs very slow. In its current state it is unable to generate the wireframe for the image in this question. Feel free to play with my code. I added a link to it at the end of my post. You can also find the code in this link

推荐答案

在这里,我提出一个解决方案.首先,我将展示如何使用生成线框的函数,然后,将详细解释构成该算法的其余函数.

Here I present a solution. First I will show how to use the function that generates the wire frame, then I will proceed to explain in detail the rest of the functions that compose the algorithm.

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

此功能的输入是Graphics3D对象,最好没有轴.

The input of this function is a Graphics3D object preferably with no axes.

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

现在我们应用功能wireFrame.

wireFrame[fig]

如您所见,wireFrame获得了大部分线条及其颜色.线框中未包含一条绿线.这很可能是由于我的阈值设置.

As you can see wireFrame obtained most of the lines and its colors. There is a green line that was not included in the wireframe. This is most likely due to my threshold settings.

在继续解释功能G3ToG2InfogetHiddenLinesgetFramesetPoints的详细信息之前,我将向您展示为什么移除隐藏线的线框可能会有用.

Before I proceed to explain the details of the functions G3ToG2Info, getHiddenLines, getFrame and setPoints I will show you why wire frames with hidden line removal can be useful.

上面显示的图像是使用 rasters中描述的技术生成的pdf文件的屏幕截图.在3D图形中结合此处生成的线框.这在各种方面都是有利的.无需保留三角形显示彩色表面的信息.相反,我们显示了表面的栅格图像.除了没有用线覆盖的栅格图的边界外,所有线都非常平滑.我们也减少了文件大小.在这种情况下,结合使用光栅图和线框,pdf文件的大小从1.9mb减小到78kb.在pdf查看器中显示所需的时间更少,并且图像质量很高.

The image shown above is a screenshot of a pdf file generated by using the technique described in rasters in 3D graphics combined with the wire frame generated here. This can be advantageous in various ways. There is no need to keep the information for the triangles to show a colorful surface. Instead we show a raster image of the surface. All of the lines are very smooth, with the exception of the boundaries of the raster plot not covered by lines. We also have a reduction of file size. In this case the pdf file size reduced from 1.9mb to 78kb using the combination of the raster plot and the wire frame. It takes less time to display in the pdf viewer and the image quality is great.

Mathematica 在将3D图像导出为pdf文件方面做得很好.导入pdf文件时,我们将获得一个由线段和三角形组成的Graphics对象.在某些情况下,这些对象重叠,因此我们有隐藏线.要创建没有表面的线框模型,我们首先需要删除此重叠部分,然后删除多边形.我将从描述如何从Graphics3D图像中获取信息开始.

Mathematica does a pretty good job at exporting 3D images to pdf files. When we import the pdf files we obtain a Graphics object composed of line segments and triangles. In some cases this objects overlap and thus we have hidden lines. To make a wire frame model with no surfaces we first need to remove this overlap and then remove the polygons. I will start by describing how to obtain the information from a Graphics3D image.

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

此代码适用于版本7中的 Mathematica 8 ,您可以将getPoints函数中的JoinedCurve替换为Line.函数getPoints假定您要提供基本的Graphics对象.它将看到它接收什么类型的对象,然后从中提取所需的信息.如果是多边形,它将获得3点的列表;对于一条线,它将获得2点的列表;如果是颜色,则它将得到包含3个点的单个列表的列表.这样做是为了保持与列表的一致性.

This code is for Mathematica 8 in version 7 you would replace JoinedCurve in the function getPoints by Line. The function getPoints assumes that you are giving a primitive Graphics object. It will see what type of object it recieves and then extract the information it needs from it. If it is a polygon it gets a list of 3 points, for a line it obtains a list of 2 points and if it is a color then it gets a list of a single list containing 3 points. This has been done like this in order to maintain consistency with the lists.

函数setPoints的作用与getPoints相反.输入一个点列表,它将确定是否应返回多边形,直线或颜色.

The function setPoints does the reverse of getPoints. You input a list of points and it will determine if it should return a polygon, a line or a color.

要获取三角形,直线和颜色的列表,请使用G3ToG2Info.该功能将使用ExportStringImportStringGraphics3D版本获得Graphics对象.此信息存储在obj中.我们需要执行一些清理工作,首先获取obj的选项.这部分是必需的,因为它可能包含图像的PlotRange.然后,如中所述,获取所有PolygonJoinedCurveRGBColor对象.获取图形基元和指令.最后,我们将函数getPoints应用于所有这些对象,以获取三角形,直线和颜色的列表.这部分覆盖了{figInfo, opt} = G3ToG2Info[g]行.

To obtain a list of triangles, lines and colors we use G3ToG2Info. This function will useExportString and ImportString to obtain a Graphics object from the Graphics3D version. This info is store in obj. There is some clean up that we need to perform, first we get the options of the obj. This part is necessary because it may contain the PlotRange of the image. Then we obtain all the Polygon, JoinedCurve and RGBColor objects as described in obtaining graphics primitives and directives. Finally we apply the function getPoints on all of these objects to get a list of triangles, lines and colors. This part covers the line {figInfo, opt} = G3ToG2Info[g].

我们希望能够知道不显示行的哪一部分.为此,我们需要知道两个线段之间的交点.我用来查找路口的算法可以在此处找到.

We want to be able to know what part of a line will not be displayed. To do this we need to know point of intersection between two line segments. The algorithm I'm using to find the intersection can be found here.

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假定行LM不重合.如果线平行或包含段L的线未与线段M交叉,则它将返回-Infinity.如果包含L的线与线段M相交,则它将返回标量.假设该标量为u,则交点为L[[1]] + u (L[[2]]-L[[1]]).请注意,u可以是任何实数都很好.您可以使用此操纵功能来测试lineInt的工作方式.

lineInt assumes that the line L and M do not coincide. It will return -Infinity if the lines are parallel or if the line containing the segment L does not cross the line segment M. If the line containing L intersects the line segment M then it returns a scalar. Suppose this scalar is u, then the point of intersection is L[[1]] + u (L[[2]]-L[[1]]). Notice that it is perfectly fine for u to be any real number. You can play with this manipulate function to test how lineInt works.

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

现在我们知道必须从L[[1]]到线段M行进多远,我们才能找出线段的哪一部分位于三角形内.

Now that we know how to far we have to travel from L[[1]] to the line segment M we can find out what portion of a line segment lies within a triangle.

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

此函数返回L行中需要删除的部分.例如,如果它返回{.5, 1},则意味着您将从线段的一半开始到线段的终点删除该行的50%.如果L = {A, B}并且函数返回{u, v},则这意味着线段{A+(B-A)u, A+(B-A)v}是其包含在三角形T中的线段.

This function returns the the portion of the line L that needs to be deleted. For instance, if it returns {.5, 1} this means that you will delete 50 percent of the line, starting from half the segment to the ending point of the segment. If L = {A, B} and the function returns {u, v} then this means that the line segment {A+(B-A)u, A+(B-A)v} is the section of the line that its contained in the triangle T.

在实现lineInTri时,您需要注意,线L不是T的边缘之一,如果是这种情况,则该线不会位于三角形内.这是舍入错误的坏处.当 Mathematica 导出图像时,有时会在三角形的边缘放置一条线,但是这些坐标相差一定程度.由我们决定线在边缘上的接近程度,否则函数将看到线几乎完全位于三角形内部.这是函数中第一行的原因.要查看一条线是否位于三角形的边缘上,我们可以列出该三角形和该线的所有点,并删除所有重复项.在这种情况下,您需要指定什么是重复项.最后,如果我们得到3个点的列表,则意味着一条线位于一条边上.下一部分有点复杂.我们要做的是检查线L与三角形T的每个边的交点,并将结果存储在列表中.接下来,我们对列表进行排序,找出线的哪一部分(如果有的话)位于三角形中.尝试通过使用它来使它有意义,其中一些测试包括检查线的端点是否为三角形的顶点,线是否完全在三角形内部,部分在内部或完全在外部.

When implementing lineInTri you need to be careful that the line L is not one of the edges of T, if this is the case then the line does not lie inside the triangle. This is where rounding erros can be bad. When Mathematica exports the image sometimes a line lies on the edge of the triangle but these coordinates differ by some amount. It is up to us to decide how close the line lies on the edge, otherwise the function will see that the line lies almost completely inside the triangle. This is the reason of the first line in the function. To see if a line lies on an edge of a triangle we can list all the points of the triangle and the line, and delete all the duplicates. You need to specify what a duplicate is in this case. In the end, if we end up with a list of 3 points this means that a line lies on an edge. The next part is a little complicated. What we do is check for the intersection of the line L with each edge of the triangle T and store this the results in a list. Next we sort the list and find out what section, if any, of the line lies in the triangle. Try to make sense out of it by playing with this, some of the tests include checking if an endpoint of the line is a vertex of the triangle, if the line is completely inside the triangle, partly inside or completely outside.

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

lineInTri将用于查看将不绘制该行的哪一部分.这条线很可能会被许多三角形覆盖.因此,我们需要保留每行将不会绘制的所有部分的列表.这些列表将没有订单.我们所知道的是,此列表是一维片段.每个数字由[0,1]间隔中的数字组成.我不知道一维段的联合函数,所以这是我的实现.

lineInTri will be used to see what portion of the line will not be drawn. This line will most likely be covered by many triangles. For this reason, we need to keep a list of all the portions of each line that will not be drawn. These lists will not have an order. All we know is that this lists are one dimensional segments. Each one consisting of numbers in the [0,1] interval. I'm not aware of a union function for one dimensional segments so here is my implementation.

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

该函数会更短一些,但在这里我包含了一些if语句,用于检查数字是否接近零或一.如果一个数字除零以外是EPS,则我们将该数字设为零,这同样适用于一.我要在这里介绍的另一个方面是,如果要显示的细分中相对较小的部分,则很可能需要删除它.例如,如果我们有{{0,.5}, {.500000000001}},这意味着我们需要绘制{{.5, .500000000001}}.但是这个段很小,甚至在大的线段中也没有特别注意,因为我们都知道这两个数字是相同的.实施union时需要考虑所有这些事情.

This function would be shorter but here I have included some if statements to check if a number is close to zero or one. If one number is EPS apart from zero then we make this number zero, the same applies for one. Another aspect that I'm covering here is that if there is a relatively small portion of the segment to be displayed then it is most likely that it needs to be deleted. For instance if we have {{0,.5}, {.500000000001}} this means that we need to draw {{.5, .500000000001}}. But this segment is very small to be even noticed specially in a large line segment, for all we know those two numbers are the same. All of this things need to be taken into account when implementing union.

现在我们准备好查看需要从线段中删除的内容.下一个需要从G3ToG2Info生成的对象的列表,该列表中的对象和索引.

Now we are ready to see what needs to be deleted from a line segment. The next requires the list of objects generated from G3ToG2Info, an object from this list and an 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返回一个列表,其中包含需要从L中删除的部分.我们知道obj是三角形,直线和颜色的列表,我们知道列表中索引较高的对象将在索引较低的对象的顶部绘制.因此,我们需要索引start.这是我们将开始在obj中寻找三角形的索引.一旦找到三角形,我们将使用函数lineInTri获得位于三角形中的线段部分.最后,我们将获得一个列表,我们可以使用union组合这些部分.

getSections returns a list containing the portions that need to be deleted from L. We know that obj is the list of triangles, lines and colors, we know that objects in the list with a higher index will be drawn on top of ones with lower index. For this reason we need the index start. This is the index we will start looking for triangles in obj. Once we find a triangle we will obtain the portion of the segment that lies in the triangle using the function lineInTri. At the end we will end up with a list of sections which we can combine by using union.

最后,我们进入getHiddenLines.所需要做的就是查看G3ToG2Info返回的列表中的每个对象,并应用函数getSections. getHiddenLines将返回列表列表.每个元素都是需要删除的部分的列表.

Finally, we get to getHiddenLines. All this requires is to look at each object in the list returned by G3ToG2Info and apply the function getSections. getHiddenLines will return a list of lists. Each element is a list of sections that need to be deleted.

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

如果您到目前为止已经能够理解这些概念,那么我相信您知道接下来将要做什么.如果我们有三角形,直线和颜色的列表以及需要删除的直线部分,则只需要绘制可见的直线的颜色和截面即可.首先,我们创建一个complement函数,它将准确告诉我们要绘制的内容.


getFrame

If you have manage to understand the concepts up to here I'm sure you know what will be done next. If we have the list of triangles, lines and colors and the sections of the lines that need to be deleted we need to draw only the colors and the sections of the lines that are visible. First we make a complement function, this will tell us exactly what to draw.

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

现在getFrame功能

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
]


最后一句话

我对算法的结果感到满意.我不喜欢执行速度.我已经像使用循环在C/C ++/java中一样编写了此代码.我尽力使用ReapSow来创建增长列表,而不是使用功能Append.无论如何,我仍然必须使用循环.应该注意的是,这里发布的线框图片需要63秒才能生成.我尝试为问题中的图片制作线框,但是此3D对象包含大约32000个对象.计算一行需要显示的部分大约需要13秒钟.如果我们假设我们有32000行,并且需要13秒来完成所有计算,这将需要大约116个小时的计算时间.


Final words

I'm somewhat happy with the results of the algorithm. What I do not like is the execution speed. I have written this as I would in C/C++/java using loops. I tried my best to use Reap and Sow to create growing lists instead of using the function Append. Regardless of all of this I still had to use loops. It should be noted that the wire frame picture posted here took 63 seconds to generate. I tried doing a wire frame for the picture in the question but this 3D object contains about 32000 objects. It was taking about 13 seconds to compute the portions that need to be displayed for a line. If we assume that we have 32000 lines and it takes 13 seconds to do all the computations that will be about 116 hours of computational time.

我敢肯定,如果我们在所有例程上都使用函数Compile,并且也许找到一种不使用Do循环的方法,那么可以减少此时间.我可以在这里获得一些帮助吗?

I'm sure this time can be reduced if we use the function Compile on all of the routines and maybe finding a way not to use the Do loops. Can I get some help here Stack Overflow?

为方便起见,我已将代码上传到网络上.您可以在此处找到它.如果您可以将此代码的修改后的版本应用于问题中的情节并显示线框,那么我会将您的解决方案标记为该帖子的答案.

For your convinience I have uploaded the code to the web. You can find it here. If you can apply a modified version of this code to the plot in the question and show the wire frame I will mark your solution as the answer to this post.

最好, 曼努埃尔·洛佩兹(J Manuel Lopez)

Best, J Manuel Lopez

这篇关于Mathematica:3D线框的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-14 14:32