Вопрос

Поддерживает ли Mathematica Скрытая линия удаление Для изображений рамки проволоки? Если это не так, кто -нибудь здесь когда -нибудь наткнулся на это? Давайте начнем с этого:

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

output

Чтобы создать раму проволоки, мы можем сделать:

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

output

Одна вещь, которую мы можем сделать, чтобы достичь эффекта, это раскрасить все поверхности белых. Это, однако, нежелательно. Причина в том, что, если мы экспортируем эту модель скрытой линейной проволочной рамы в 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]
]

Вход этой функции - это 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]]
]

surface

Теперь мы применяем функцию wireFrame.

wireFrame[fig]

wireframe

Как вы видете wireFrame Получили большинство линий и их цветов. Есть зеленая линия, которая не была включена в каркас. Скорее всего, это связано с моими пороговыми настройками.

Прежде чем я приеду, чтобы объяснить детали функций G3ToG2Info, getHiddenLines, getFrame а также setPoints Я покажу вам, почему проволочные рамки со скрытой линией могут быть полезны.

RasterWire

Изображение, показанное выше, представляет собой скриншот файла PDF, сгенерированного с использованием метода, описанной в Растеры в 3D -графике в сочетании с проволочной рамой, сгенерированной здесь. Это может быть выгодным по -разному. Нет необходимости держать информацию для треугольников, чтобы показать красочную поверхность. Вместо этого мы показываем растровое изображение поверхности. Все линии очень гладкие, за исключением границ растрового участка, не покрытого линиями. У нас также есть уменьшение размера файла. В этом случае размер файла PDF уменьшился с 1,9 МБ до 78 КБ, используя комбинацию растрового графика и рамы провода. Требуется меньше времени, чтобы отображаться в PDF Viewer, и качество изображения отличное.

Математика Делает ли довольно хорошая работа по экспорту 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}
  ]

Этот код для Математика 8 В версии 7 вы замените JoinedCurve в функции getPoints по Line. Анкет Функция getPoints предполагает, что вы даете примитив Graphics объект. Он увидит, какой тип объекта он получает, а затем извлечет необходимую из него информацию. Если это многоугольник, он получает список из 3 баллов, для строки она получает список из 2 баллов, и если он цвет, то он получает список одного списка, содержащего 3 балла. Это было сделано так, чтобы поддерживать согласованность с списками.

Функция setPoints Обратный getPoints. Анкет Вы вводите список точек и определяете, должен ли он вернуть многоугольник, линию или цвет.

Чтобы получить список треугольников, линий и цветов, которые мы используем G3ToG2Info. Анкет Эта функция будет использоватьExportString а также ImportString Чтобы получить Graphics объект из Graphics3D версия. Эта информация хранит в obj. Анкет Есть некоторые уборки, которые нам нужно выполнить, сначала мы получаем варианты obj. Анкет Эта часть необходима, потому что она может содержать PlotRange изображения. Тогда мы получаем все Polygon, JoinedCurve а также RGBColor объекты, как описано в Получение графических примитивов и директив. Анкет Наконец мы применяем функцию getPoints На всех этих объектах, чтобы получить список треугольников, линий и цветов. Эта часть покрывает линию {figInfo, opt} = G3ToG2Info[g].


getHiddenLines

Мы хотим узнать, какая часть линии не будет отображаться. Для этого нам нужно знать точку пересечения между двумя сегментами линии. Алгоритм, который я использую, чтобы найти пересечение, можно найти здесь.

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

Example

Теперь, когда мы знаем, как далеко мы должны путешествовать 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 не один из краев T, если это так, то линия не лежит внутри треугольника. Вот где ошибки округления могут быть плохими. Когда Математика Экспортирует изображение Иногда линия лежит на краю треугольника, но эти координаты различаются в некотором количестве. Нам решать, насколько близко находится линия на краю, в противном случае функция увидит, что линия почти полностью лежит внутри треугольника. Это является причиной первой строки в функции. Чтобы увидеть, находится ли линия на краю треугольника, мы можем перечислить все точки треугольника и линии, и удалить все дубликаты. Вам нужно указать, что такое дубликат в этом случае. В конце концов, если мы получим список из 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"}
]

triangle test

lineInTri будет использоваться, чтобы увидеть, какая часть линии не будет нарисована. Эта линия, скорее всего, будет покрыта многими треугольниками. По этой причине нам необходимо составить список всех частей каждой строки, которые не будут нарисованы. Эти списки не будут иметь заказ. Все, что мы знаем, это то, что эти списки представляют собой одномерные сегменты. Каждый состоит из чисел в [0,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]]]
 ]

Эта функция была бы короче, но здесь я включил некоторые операторы, чтобы проверить, близко ли число к нулю или одному. Если один номер EPS Помимо нуля, мы делаем это число ноль, то же самое относится и к одному. Другой аспект, который я здесь освещаю, заключается в том, что если есть относительно небольшая часть сегмента, которая будет отображаться, то, скорее всего, его необходимо удалить. Например, если у нас есть {{0,.5}, {.500000000001}} Это означает, что нам нужно рисовать {{.5, .500000000001}}. Анкет Но этот сегмент очень мал, чтобы быть даже особенно замеченным в крупном сегменте, поскольку все, что мы знаем, эти два числа одинаковы. Все это необходимо учитывать при реализации 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

Если вам удастся понять концепции здесь, я уверен, что вы знаете, что будет сделано дальше. Если у нас есть список треугольников, линий и цветов, а также разделов линий, которые необходимо удалить, нам нужно нарисовать только цвета и разделы виденных линий. Сначала мы делаем 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 часов вычисления.

Я уверен, что это время может быть уменьшено, если мы используем функцию Compile На всех процедурах и, возможно, нахождение способа не использовать Do петли. Могу ли я получить помощь здесь.

Для вашего убеждения я загрузил код в Интернет. Вы можете найти это здесь. Анкет Если вы сможете применить измененную версию этого кода на сюжет в вопросе и показать рамку провода, я отмечу ваше решение в качестве ответа на этот пост.

Лучший, Дж. Мануэль Лопес

Другие советы

Это не так, но несколько интересно:

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

Благодаря форме лица, полигон не отображается. Я не уверен, что есть способ сделать это с сетчатыми линиями.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top