Mathematica:3D 线框
Mathematica 是否支持线框图像的隐藏线删除?如果不是这种情况,这里有人遇到过方法吗?让我们从这里开始:
Plot3D[Sin[x+y^2], {x, -3, 3}, {y, -2, 2}, Boxed -> False]
要创建线框,我们可以这样做:
Plot3D[Sin[x+y^2], {x, -3, 3}, {y, -2, 2}, Boxed -> False, PlotStyle -> None]
为了实现这种效果,我们可以做的一件事是将所有表面涂成白色。然而,这是不希望的。原因是因为如果我们将此隐藏线线框模型导出为 pdf,我们将拥有 Mathematica 用于渲染图像的所有白色多边形。我希望能够获得 pdf 和/或 eps 格式的带有隐藏线去除功能的线框。
更新:
我已经发布了这个问题的解决方案。问题是代码运行速度非常慢。在当前状态下,无法生成此问题中图像的线框。请随意使用我的代码。我在帖子末尾添加了一个链接。您还可以在此链接中找到代码
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]
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.
UPDATE:
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
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
这里我提出一个解决方案。首先,我将展示如何使用生成线框的函数,然后我将继续详细解释构成该算法的其余函数。
wireFrame
该函数的输入是一个
Graphics3D
对象,最好没有轴。现在我们应用函数
wireFrame
。如您所见,
wireFrame
获取了大部分线条及其颜色。有一条绿线未包含在线框中。这很可能是由于我的阈值设置造成的。在继续解释函数
G3ToG2Info
、getHiddenLines
、getFrame
和setPoints
的详细信息之前,我将向您展示原因具有隐藏线去除功能的线框可能很有用。上面显示的图像是使用 3D 图形中的栅格与此处生成的线框相结合。这在很多方面都是有利的。无需保留三角形的信息即可显示彩色表面。相反,我们显示表面的光栅图像。所有的线条都非常平滑,除了光栅图的边界没有被线条覆盖之外。我们还减小了文件大小。在本例中,使用光栅图和线框的组合,pdf 文件大小从 1.9mb 减小到 78kb。在 pdf 查看器中显示所需的时间较短,并且图像质量非常好。
Mathematica 在将 3D 图像导出为 pdf 文件方面做得非常好。当我们导入 pdf 文件时,我们获得一个由线段和三角形组成的 Graphics 对象。在某些情况下,这些对象重叠,因此我们有隐藏线。要制作没有曲面的线框模型,我们首先需要删除重叠部分,然后删除多边形。我将首先描述如何从 Graphics3D 图像获取信息。
G3ToG2Info
此代码适用于版本 7 中的 Mathematica 8,您可以将函数
getPoints
中的JoinedCurve
替换为行
。函数getPoints
假定您正在提供一个原始Graphics
对象。它将查看收到的对象类型,然后从中提取所需的信息。如果它是一个多边形,它会得到一个包含 3 个点的列表,对于一条线,它会得到一个包含 2 个点的列表,如果它是一个颜色,它会得到一个包含 3 个点的单个列表的列表。这样做是为了保持与列表的一致性。函数
setPoints
的作用与getPoints
相反。您输入一个点列表,它将确定是否应返回多边形、线条或颜色。为了获取三角形、线条和颜色的列表,我们使用 G3ToG2Info。该函数将使用
ExportString
和ImportString
从Graphics3D
版本获取Graphics
对象。此信息存储在 obj 中。我们需要执行一些清理工作,首先我们获取 obj 的选项。这部分是必需的,因为它可能包含图像的PlotRange
。然后我们获取所有Polygon
、JoinedCurve
和RGBColor
对象,如获取图形基元和指令。最后,我们对所有这些对象应用函数getPoints
来获取三角形、线条和颜色的列表。这部分涵盖了行{figInfo, opt} = G3ToG2Info[g]
。getHiddenLines
我们希望能够知道一行的哪一部分不会被显示。为此,我们需要知道两条线段之间的交点。我用来查找交集的算法可以在此处找到。
lineInt
假定线L
和M
不重合。如果两条线是平行的或者包含线段L
的线不与线段M
相交,它将返回-Infinity
。如果包含L
的线与线段M
相交,则返回一个标量。假设该标量为u
,则交点为L[[1]] + u (L[[2]]-L[[1]])
。请注意,u
可以是任何实数。您可以使用此操作函数来测试lineInt
的工作原理。现在我们知道距离
L[[1]]
有多远对于线段M
,我们可以找出线段的哪一部分位于三角形内。该函数返回
L
行中需要删除的部分。例如,如果它返回{.5, 1}
,这意味着您将删除该行的 50%,从线段的一半开始到线段的终点。如果L = {A, B}
并且函数返回{u, v}
那么这意味着线段{A+(BA)u, A+( BA)v}
是三角形T
中包含的直线部分。实现
lineInTri
时,您需要注意线L
不是T
的边缘之一,如果是这种情况,则该线不在三角形内。这就是舍入错误可能很糟糕的地方。当Mathematica导出图像时,有时一条线位于三角形的边缘,但这些坐标有一定的差异。由我们来决定线与边缘的距离有多近,否则函数将看到线几乎完全位于三角形内部。这就是函数第一行的原因。要查看一条线是否位于三角形的边上,我们可以列出三角形和线的所有点,并删除所有重复项。在这种情况下,您需要指定重复项是什么。最后,如果我们得到一个包含 3 个点的列表,则意味着一条线位于边缘上。下一部分有点复杂。我们所做的是检查线L
与三角形T
每条边的交点,并将结果存储在列表中。接下来,我们对列表进行排序,并找出该线的哪一部分(如果有)位于三角形内。尝试通过玩这个来理解它,一些测试包括检查线的端点是否是三角形的顶点,线是否完全在三角形内部,部分在内部或完全在外部。lineInTri
将用于查看线条的哪一部分不会被绘制。这条线很可能会被许多三角形覆盖。因此,我们需要保留每条线中不会绘制的所有部分的列表。这些列表不会有顺序。我们所知道的是,这个列表是一维段。每一个都由[0,1]
区间内的数字组成。我不知道一维段的联合函数,所以这是我的实现。这个函数会更短,但这里我包含了一些 if 语句来检查数字是否接近零或一。如果一个数字是
EPS
除了零之外,那么我们将这个数字设为零,这同样适用于 1。我在这里讨论的另一个方面是,如果要显示的段的部分相对较小,那么很可能需要将其删除。例如,如果我们有{{0,.5}, {.500000000001}}
,这意味着我们需要绘制{{.5, .500000000001}}
。但这条线段非常小,甚至在一条大线段中都很难被注意到,因为我们知道这两个数字是相同的。实现联合时需要考虑所有这些事情。现在我们准备看看需要从线段中删除什么。接下来需要从
G3ToG2Info
生成的对象列表、该列表中的一个对象和一个索引。getSections
返回一个列表,其中包含需要从L
中删除的部分。我们知道 obj 是三角形、线条和颜色的列表,我们知道列表中索引较高的对象将绘制在索引较低的对象之上。因此我们需要索引start
。这是我们将开始在 obj 中寻找三角形的索引。一旦找到三角形,我们将使用函数 lineInTri 获得位于三角形内的线段部分。最后,我们将得到一个部分列表,我们可以使用union
将其组合起来。最后,我们到达
getHiddenLines
。所需要的只是查看G3ToG2Info
返回的列表中的每个对象并应用函数getSections
。getHiddenLines
将返回列表的列表。每个元素都是需要删除的部分的列表。getFrame
如果您已经成功理解了到目前为止的概念,我相信您知道接下来要做什么。如果我们有三角形、线条和颜色的列表以及需要删除的线条部分,我们只需绘制可见的颜色和线条部分。首先我们创建一个
complement
函数,这将告诉我们到底要画什么。现在是
getFrame
函数最后的话
我对算法的结果有些满意。我不喜欢的是执行速度。我已经像在 C/C++/java 中使用循环一样编写了此代码。我尽力使用
Reap
和Sow
来创建不断增长的列表,而不是使用函数Append
。不管怎样,我仍然必须使用循环。需要注意的是,这里发布的线框图片生成需要 63 秒。我尝试为问题中的图片制作线框,但这个 3D 对象包含大约 32000 个对象。计算一行需要显示的部分大约需要 13 秒。如果我们假设有 32000 行,并且需要 13 秒来完成所有计算,则计算时间约为 116 小时。我确信,如果我们在所有例程上使用
Compile
函数,并且也许找到一种不使用Do
循环的方法,那么这个时间就可以减少。我可以在这里获得 Stack Overflow 的帮助吗?为了您的方便,我已将代码上传到网络。您可以在此处找到它。如果您可以将此代码的修改版本应用于问题中的绘图并显示线框,我会将您的解决方案标记为本文的答案。
最好的,
J·曼努埃尔·洛佩兹
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
The input of this function is a
Graphics3D
object preferably with no axes.Now we apply the function
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.Before I proceed to explain the details of the functions
G3ToG2Info
,getHiddenLines
,getFrame
andsetPoints
I will show you why wire frames with hidden line removal can be useful.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 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.
G3ToG2Info
This code is for Mathematica 8 in version 7 you would replace
JoinedCurve
in the functiongetPoints
byLine
. The functiongetPoints
assumes that you are giving a primitiveGraphics
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.The function
setPoints
does the reverse ofgetPoints
. You input a list of points and it will determine if it should return a polygon, a line or a color.To obtain a list of triangles, lines and colors we use
G3ToG2Info
. This function will useExportString
andImportString
to obtain aGraphics
object from theGraphics3D
version. This info is store inobj
. There is some clean up that we need to perform, first we get the options of theobj
. This part is necessary because it may contain thePlotRange
of the image. Then we obtain all thePolygon
,JoinedCurve
andRGBColor
objects as described in obtaining graphics primitives and directives. Finally we apply the functiongetPoints
on all of these objects to get a list of triangles, lines and colors. This part covers the line{figInfo, opt} = G3ToG2Info[g]
.getHiddenLines
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
assumes that the lineL
andM
do not coincide. It will return-Infinity
if the lines are parallel or if the line containing the segmentL
does not cross the line segmentM
. If the line containingL
intersects the line segmentM
then it returns a scalar. Suppose this scalar isu
, then the point of intersection isL[[1]] + u (L[[2]]-L[[1]])
. Notice that it is perfectly fine foru
to be any real number. You can play with this manipulate function to test howlineInt
works.Now that we know how to far we have to travel from
L[[1]]
to the line segmentM
we can find out what portion of a line segment lies within a triangle.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. IfL = {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 triangleT
.When implementing
lineInTri
you need to be careful that the lineL
is not one of the edges ofT
, 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 lineL
with each edge of the triangleT
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.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.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 implementingunion
.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
returns a list containing the portions that need to be deleted fromL
. We know thatobj
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 indexstart
. This is the index we will start looking for triangles inobj
. Once we find a triangle we will obtain the portion of the segment that lies in the triangle using the functionlineInTri
. At the end we will end up with a list of sections which we can combine by usingunion
.Finally, we get to
getHiddenLines
. All this requires is to look at each object in the list returned byG3ToG2Info
and apply the functiongetSections
.getHiddenLines
will return a list of lists. Each element is a list of sections that need to be deleted.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.Now the
getFrame
functionFinal 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
andSow
to create growing lists instead of using the functionAppend
. 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.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 theDo
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.
Best,
J Manuel Lopez
这是不对的,但有点有趣:
Plot3D[Sin[x + y^2], {x, -3, 3}, {y, -2, 2}, Boxed ->错误的,
绘图样式 -> {EdgeForm[无],FaceForm[红色,无]},网格 -> False]
如果 FaceForm 为 None,则不会渲染多边形。我不确定是否有办法用网格线来做到这一点。
This isn't right, but somewhat interesting:
Plot3D[Sin[x + y^2], {x, -3, 3}, {y, -2, 2}, Boxed -> False,
PlotStyle -> {EdgeForm[None], FaceForm[Red, None]}, Mesh -> False]
With a FaceForm of None, the polygon isn't rendered. I'm not sure there's a way to do this with the Mesh lines.