Mathematica:3Dワイヤーフレーム
-
28-10-2019 - |
質問
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]
効果を達成するためにできることの1つは、すべての表面を白に色付けすることです。ただし、これは望ましくありません。その理由は、この隠しラインワイヤーフレームモデルをPDFにエクスポートすると、Mathematicaが画像をレンダリングするために使用するすべての白いポリゴンがあるためです。 PDFおよび/またはEPS形式で隠されたライン除去を備えたワイヤフレームを取得できるようにしたいと思います。
アップデート:
この問題の解決策を投稿しました。問題は、コードが非常に遅くなることです。現在の状態では、この質問で画像のワイヤーフレームを生成できません。私のコードを自由に再生してください。投稿の最後にリンクを追加しました。これでコードを見つけることもできます リンク
解決
ここに解決策を提示します。最初に、ワイヤフレームを生成する関数の使用方法を示します。次に、アルゴリズムを構成する残りの関数を詳細に説明します。
wireFrame
wireFrame[g_] := Module[{figInfo, opt, pts},
{figInfo, opt} = G3ToG2Info[g];
pts = getHiddenLines[figInfo];
Graphics[Map[setPoints[#] &, getFrame[figInfo, pts]], opt]
]
この関数の入力はaです Graphics3D
できれば軸のないオブジェクト。
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
ほとんどの線とその色を取得しました。ワイヤーフレームには含まれていない緑色のラインがあります。これは、おそらく私のしきい値設定によるものです。
関数の詳細を説明する前に G3ToG2Info
, getHiddenLines
, getFrame
と setPoints
隠されたラインの取り外しを備えたワイヤーフレームが役立つ理由を示します。
上記の画像は、で説明されている手法を使用して生成されたPDFファイルのスクリーンショットです 3Dグラフィックスのラスター ここで生成されたワイヤーフレームと組み合わされています。これはさまざまな方法で有利になる可能性があります。カラフルな表面を示すために、三角形の情報を保持する必要はありません。代わりに、表面のラスター画像を表示します。すべての線は非常に滑らかですが、線で覆われていないラスタープロットの境界を除きます。また、ファイルサイズが削減されています。この場合、PDFファイルサイズは、ラスタープロットとワイヤフレームの組み合わせを使用して、1.9MBから78kBに減少しました。 PDFビューアに表示するのに時間がかかりません。画質は素晴らしいです。
Mathematica 3D画像をPDFファイルにエクスポートするのにかなり良い仕事をします。 PDFファイルをインポートすると、ラインセグメントと三角形で構成されるグラフィックオブジェクトを取得します。場合によっては、このオブジェクトが重複しているため、隠し線があります。表面のないワイヤフレームモデルを作成するには、最初にこのオーバーラップを削除してからポリゴンを削除する必要があります。 Graphics3D画像から情報を取得する方法を説明することから始めます。
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}
]
このコードはのためです Mathematica 8 バージョン7では、交換します JoinedCurve
関数で getPoints
に Line
. 。関数 getPoints
あなたが原始を与えていると仮定します Graphics
物体。それがどのような種類のオブジェクトを受け取るかを確認し、そこから必要な情報を抽出します。ポリゴンの場合、3ポイントのリストを取得します。ラインの場合、2ポイントのリストを取得し、色の場合は3ポイントを含む単一のリストのリストを取得します。これは、リストとの一貫性を維持するためにこのように行われています。
関数 setPoints
の逆はありますか getPoints
. 。ポイントのリストを入力すると、ポリゴン、ライン、または色を返す必要があるかどうかが判断されます。
使用する三角形、線、色のリストを取得するには G3ToG2Info
. 。この関数は使用されますExportString
と ImportString
aを取得するには Graphics
からのオブジェクト Graphics3D
バージョン。この情報はに保存されています obj
. 。実行する必要があるクリーンアップがいくつかあります。まず、 obj
. 。この部分は、それが含まれている可能性があるために必要です PlotRange
画像の。次に、すべてを取得します Polygon
, JoinedCurve
と RGBColor
で説明されているオブジェクト グラフィックスプリミティブとディレクティブの取得. 。最後に、関数を適用します getPoints
これらすべてのオブジェクトで、三角形、線、色のリストを取得します。この部分は行をカバーします {figInfo, opt} = G3ToG2Info[g]
.
getHiddenLines
どのラインの部分が表示されないかを知りたいと思っています。これを行うには、2つのラインセグメント間の交差点を知る必要があります。交差点を見つけるために使用しているアルゴリズムが見つかります ここ.
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
ラインを想定しています L
と M
一致しないでください。それは戻ってきます -Infinity
線が平行である場合、またはセグメントを含む線の場合 L
ラインセグメントを通過しません M
. 。行が含まれている場合 L
ラインセグメントと交差します M
その後、スカラーを返します。このスカラーがそうであると仮定します u
, 、それから交差点はです L[[1]] + u (L[[2]]-L[[1]])
. 。完全に問題ないことに注意してください u
実数になること。この操作関数で遊んで、どのようにテストすることができますか lineInt
作品。
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
ラインセグメントのどの部分が三角形にあるかを確認できます。
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
.
実装時 lineInTri
ラインに注意する必要があります L
のエッジの1つではありません T
, 、これが当てはまる場合、線は三角形の内側にありません。これは、丸めのエロが悪い可能性がある場所です。いつ Mathematica 画像をエクスポートすることがある場合は、線が三角形の端にあることもありますが、これらの座標はある程度は異なります。ラインが端にどれだけ近いかを決定するのは私たち次第です。そうしないと、関数は、線がほぼ完全に三角形の内側にあることがわかります。これが関数の最初の行の理由です。線が三角形の端にあるかどうかを確認するには、三角形と線のすべてのポイントをリストし、すべての複製を削除できます。この場合、重複したものを指定する必要があります。最終的に、3ポイントのリストになった場合、これは線がエッジにあることを意味します。次の部分は少し複雑です。私たちがしていることは、ラインの交差点をチェックすることです L
三角形の各端で T
結果をリストに保存します。次に、リストを並べ替えて、行のセクションがある場合は、三角形にあるかどうかを見つけます。これで遊ぶことでそれを理解してみてください。テストの一部には、線のエンドポイントが三角形の頂点であるかどうかを確認することが含まれます。
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
どのラインの部分が描かれないかを確認するために使用されます。この線は、おそらく多くの三角形で覆われています。このため、描画されない各ラインのすべての部分のリストを保持する必要があります。これらのリストには注文がありません。私たちが知っているのは、このリストが1つの次元セグメントであるということです。それぞれがの数字で構成されています [0,1]
間隔。私は1つの次元セグメントの組合機能を知らないので、ここに私の実装があります。
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]]]
]
この関数は短くなりますが、ここには、数値がゼロまたは1に近いかどうかを確認するためのステートメントをいくつか含めました。 1つの番号がある場合 EPS
ゼロとは別に、この数値をゼロにしますが、同じことが1つに当てはまります。ここで私がカバーしているもう1つの側面は、表示されるセグメントの比較的小さな部分がある場合、削除する必要がある可能性が高いことです。たとえば、持っている場合 {{0,.5}, {.500000000001}}
これは、描く必要があることを意味します {{.5, .500000000001}}
. 。しかし、このセグメントは、特に大きなラインセグメントで気付くのは非常に小さいです。これらの2つの数値は同じであることがわかっているためです。このすべてを実装する際に考慮する必要があります union
.
これで、ラインセグメントから削除する必要があるものを確認する準備が整いました。次に、生成されたオブジェクトのリストが必要です G3ToG2Info
, 、このリストのオブジェクトとインデックス。
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
.
最後に、私たちは到達します getHiddenLines
. 。これに必要なのは、返されたリスト内の各オブジェクトを見ることです G3ToG2Info
関数を適用します getSections
. getHiddenLines
リストのリストを返します。各要素は、削除する必要があるセクションのリストです。
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
ここまでの概念を理解できたら、次に何が行われるか知っていると確信しています。三角形、線、色のリスト、削除する必要がある線のセクションがある場合は、表示される色と線のセクションのみを描く必要があります。最初にaを作ります complement
関数、これは私たちに何を描くべきかを正確に示します。
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でこれを書きました。私は最善を尽くしました Reap
と Sow
関数を使用する代わりに成長するリストを作成します Append
. 。これらすべてに関係なく、私はまだループを使用する必要がありました。ここに投稿されたワイヤーフレームの画像は、生成に63秒かかったことに注意してください。質問の写真のワイヤーフレームを試してみましたが、この3Dオブジェクトには約32000のオブジェクトが含まれています。ライン用に表示する必要がある部分を計算するのに約13秒かかりました。 32000行があり、約116時間の計算時間となるすべての計算を行うには13秒かかると仮定した場合。
関数を使用すれば今回は短縮できると確信しています Compile
すべてのルーチンで、おそらく使用しない方法を見つける Do
ループ。ここでオーバーフローをスタックするヘルプを得ることができますか?
あなたの説得力のために、私はコードをWebにアップロードしました。あなたはそれを見つけることができます ここ. 。このコードの変更されたバージョンを質問のプロットに適用し、ワイヤーフレームを表示できる場合は、この投稿に対する答えとしてソリューションをマークします。
ベスト、Jマヌエル・ロペス
他のヒント
これは正しくありませんが、やや興味深いものです。
Plot3D[Sin[x + y^2], {x, -3, 3}, {y, -2, 2}, Boxed -> False,
PlotStyle -> {EdgeForm[None], FaceForm[Red, None]}, Mesh -> False]
いずれにせずにも、ポリゴンはレンダリングされません。メッシュラインでこれを行う方法があるかどうかはわかりません。