Символические 3D-координаты в TikZ

Символические 3D-координаты в TikZ

Предупреждение: ответ на этот вопрос может потребовать некоторых усилий. Цель вопроса — «научить ТикZ 3d координаты". Что это значит? Если мы определим координату в TiкЗ,

 \path (<x>,<y>) coordinate(A);

эта координата Aсвязывается с 2 длинами, которые определяют местоположение. В любой преобразованной (смещенной, повернутой, наклонной) системе координат мы все еще можем ссылаться на эту координату и, скажем, рисовать стрелку к ней. Что еще важнее для этого вопроса, мы всегда можем работать в обратном направлении и выяснить, каково относительное местоположение по отношению к другой координате, например, с помощью библиотеки calc

\path let \p1=($(A)-(B)$),\n1={veclen(\x1,\y1)},\n2={atan2(\y1,\x1)} in <do something with this information>;

Это невозможно в 3D, так как TiкZ усекает координаты.

Один из возможных способов решения этой проблемы был предложен вэтот хороший ответ. Это здорово, но не работает так гладко, как вышеупомянутый синтаксис calc. Возможно, что еще важнее, нужно приложить дополнительные усилия для хранения 3d-координат. В идеале, можно было бы иметь что-то вроде

 \path (x,y,z) coordinate(A);

и ТикzZ также запомнит координаты.

Обратите внимание, что этот запрос может показаться более невинным на первый взгляд, чем он есть на самом деле. В 2D у нас есть предопределенная система отсчета, координаты экрана. Более того, вращения образуют абелеву группу, поэтому отслеживать их и инвертировать их менее обременительно. Вышеупомянутый ответ сохраняет координаты в локальных системах отсчета, поэтому невозможно сравнивать координаты в разных системах отсчета. Однако это было бы полезно для многих приложений, в которых переключаются, скажем, в canvas is xy plane at z=0. В идеале ответ на этот вопрос должен связывать каждую символическую точку с некоторыми тремя длинами, которые являются координатами в умело выбранной системе отсчета, и должны быть средства для определения относительного местоположения двух точек независимым от координат способом, аналогично тому, как это делается veclenв 2D.

В лучшем случае ответ также сопровождался бы соответствующим синтаксическим анализатором, который позволял бы нам выполнять скалярные произведения, векторные произведения, вычислять норму вектора и выполнять умножение матриц, т. е. ортогональные преобразования. (Я думаю, что выход за рамки ортогональных преобразований — это беспорядок, потому что тогда обращение матрицы будет действительно громоздким.) Некоторый прогресс в отношении синтаксического анализа был достигнут вответы на этот вопросно опять же, вероятно, будет справедливо сказать, что это пока не так удобно, как аналоги 2D.

Ответы могут быть основаны на tikz-3dplot. ( tikz-3dplotпоставляется с хорошими ортонормальными проекциями.) Конечно, лучшим из всех вариантов будет что-то, что также работает сТрехточечная перспективабиблиотека.

Обратите внимание, что в пакете реализованы некоторые матричные операции calculator. Это впечатляющий пакет, в котором есть много вещей, и его процедуры могут быть полезны для этой задачи. Существуют ли другие пакеты такого рода, я не знаю.

решение1

Можно что-то в этом роде состряпать. Вот некоторые результаты в этом направлении.

Главный пункт

Можно взломать ТикZ для записи репера. Предполагая, что у пользователя ортографический вид, достаточно двух базисных векторов. Эти два базисных вектора имеют компоненты e_1=(\pgf@xx,\pgf@yx,\pgf@zx)и e_2=(\pgf@xy,\pgf@yy,\pgf@zy), нормаль к экрану просто e_3=e_1 x e_2. (Виртуальное) расстояние координаты от экрана с этого момента называется «глубиной экрана». Это просто p.e_3, где p— точка.

Чтобы записать вилбейн автоматически, нужно «взломать» ТикZ (или определите стиль для этого). Так что, если вам некомфортно делать что-либо из этого, прекратите читать.

Ограничения

На данный момент это работает только для координат/узлов, созданных в декартовых координатах, и масштабные коэффициенты (пока?) не учитываются. Также, может быть, желательно иметь синтаксис

\path let \p1=(A) in <do something with \z1>;

где \z1 — глубина экрана. Это (пока?) не реализовано.

Явный пример

Этот код определяет функцию screendepth, которая возвращает вышеупомянутую глубину экрана. Очевидно, что она не зависит от системы координат. В частности, если нужно добиться 3D-упорядочения, объекты с большей глубиной экрана должны быть отрисованы последними. Это работает независимо от того, как вы устанавливаете 3D-вид. Например, мы могли бы использовать tikz-3dplotвместо perspectiveбиблиотеки.

\documentclass[tikz,border=3mm]{standalone}
\usetikzlibrary{calc,perspective}
\makeatletter
\pgfmathdeclarefunction{tdnormal}{6}{\begingroup
\pgfmathsetmacro\pgfutil@tmpa{(#2/1cm)*(#6)-(#3/1cm)*(#5)}%
\pgfmathsetmacro\pgfutil@tmpb{(#3/1cm)*(#4)-(#1/1cm)*(#6)}%
\pgfmathsetmacro\pgfutil@tmpc{(#1/1cm)*(#5)-(#2/1cm)*(#4)}%
\edef\pgfmathresult{\pgfutil@tmpa,\pgfutil@tmpb,\pgfutil@tmpc}%
\pgfmathsmuggle\pgfmathresult%
\endgroup}%
\pgfmathdeclarefunction{screendepth}{1}{\begingroup
\def\tikz@td@pp(##1){\edef\pgfutil@tmp{\csname tikz@dcl@coord@##1\endcsname}}%
\edef\pgfutil@tmp{\csname tikz@dcl@coord@#1\endcsname}% 
\loop
\pgfutil@tempcnta=0%
\pgfutil@for\pgf@tmp:={\pgfutil@tmp}\do{\advance\pgfutil@tempcnta by1}%
\ifnum\pgfutil@tempcnta=1\relax
\expandafter\tikz@td@pp\pgfutil@tmp%
\repeat
\edef\pgfmathresult{0}%
\ifcase\pgfutil@tempcnta
\message{Something is wrong here.^^J}
\or
\message{Something is wrong here.^^J}
\or
\or
\edef\tikz@td@vielbein{\csname tikz@vielbein@#1\endcsname}%
\pgfmathsetmacro{\tikz@td@normal}{tdnormal(\tikz@td@vielbein)}%
\def\tikz@td@strip@brackets(##1,##2,##3)##4,##5,##6;{%
\edef\pgf@tmp{(##1)*(##4)+(##2)*(##5)+(##3)*(##6)}}%
\edef\temp{\noexpand\tikz@td@strip@brackets\pgfutil@tmp\tikz@td@normal;}%
\temp
\pgfmathparse{\pgf@tmp}%
\fi
\pgfmathsmuggle\pgfmathresult%
\endgroup}
\def\tikz@@fig@main{%
    \pgfutil@ifundefined{pgf@sh@s@\tikz@shape}%
    {\tikzerror{Unknown shape ``\tikz@shape.'' Using ``rectangle'' instead}%
      \def\tikz@shape{rectangle}}%
    {}%
    \expandafter\xdef\csname tikz@dcl@coord@\tikz@fig@name\endcsname{%
      \csname tikz@scan@point@coordinate\endcsname}%
    \expandafter\xdef\csname tikz@vielbein@\tikz@fig@name\endcsname{%
       \the\pgf@xx,\the\pgf@xy,\the\pgf@yx,\the\pgf@yy,\the\pgf@zx,\the\pgf@zy}%
    \expandafter\xdef\csname tikz@trafo@\tikz@fig@name\endcsname{%
       {{\pgf@pt@aa,\pgf@pt@ab},{\pgf@pt@ba,\pgf@pt@bb},%
       {\the\pgf@pt@x,\the\pgf@pt@y}}}%
    \tikzset{every \tikz@shape\space node/.try}%
    \tikz@node@textfont%
    \tikz@node@begin@hook%
    \iftikz@is@matrix%
      \let\tikz@next=\tikz@do@matrix%
    \else%
      \let\tikz@next=\tikz@do@fig%
    \fi%
    \tikz@next%
}%
\makeatother

\begin{document}
\begin{tikzpicture}[dot/.style={circle,fill,inner sep=1.2pt}]
 \begin{scope}[3d view]
  \draw[-stealth] (0,0,0) -- (2,0,0) node[pos=1.2]{$\vec x$};
  \draw[-stealth] (0,0,0) -- (0,2,0) node[pos=1.2]{$\vec y$};
  \draw[-stealth] (0,0,0) -- (0,0,2) node[pos=1.2]{$\vec z$};
  \path[nodes=dot] (1,2,3) node (A){} (4,5) node (B){} (A) node (C){};
  \path let \p1=(A),\p2=(B),\p3=(C) in 
   (A) node[above] {$A=({}$\x1,\y1,\pgfmathparse{screendepth("A")}\pgfmathresult pt)}
   (B) node[above] {$B=({}$\x2,\y2,\pgfmathparse{screendepth("B")}\pgfmathresult pt)}
   (C) node[below] {$C=({}$\x3,\y3,\pgfmathparse{screendepth("C")}\pgfmathresult pt)};
 \end{scope}  
 \begin{scope}[xshift=6cm,3d view={110}{20}]
  \draw[-stealth] (0,0,0) -- (2,0,0) node[pos=1.2]{$\vec x'$};
  \draw[-stealth] (0,0,0) -- (0,2,0) node[pos=1.2]{$\vec y'$};
  \draw[-stealth] (0,0,0) -- (0,0,2) node[pos=1.2]{$\vec z'$};
  \path[nodes=dot] (1,2,3) node (A'){} (4,5) node (B'){} (A') node (C'){};
  \path let \p1=(A'),\p2=(B'),\p3=(C') in 
   (A') node[above] {$A'=({}$\x1,\y1,\pgfmathparse{screendepth("A'")}\pgfmathresult pt)}
   (B') node[above] {$B'=({}$\x2,\y2,\pgfmathparse{screendepth("B'")}\pgfmathresult pt)}
   (C') node[below] {$C'=({}$\x3,\y3,\pgfmathparse{screendepth("C'")}\pgfmathresult pt)};
 \end{scope}  
\end{tikzpicture}
\end{document}

введите описание изображения здесь

Результат не цепляет, ничего особенного, но это попытка сделать 3D-упорядочение в Ti.кZ немного менее громоздкий.

В качестве альтернативы можно использовать "взлом" calcвместо TiкZ. Этот хак не полностью симметричен, нужно ссылаться на координату по ее оригинальному имени, и, конечно, нельзя использовать что-то вроде ($(A)+(B)$). Для этого потребуется более существенная операция. Однако вы можете получить "физические" компоненты с помощью синтаксиса calc.

\documentclass[tikz,border=3mm]{standalone}
\usetikzlibrary{calc,perspective}
\makeatletter
\pgfmathdeclarefunction{tdnormal}{6}{\begingroup
\pgfmathsetmacro\pgfutil@tmpa{(#2/1cm)*(#6)-(#3/1cm)*(#5)}%
\pgfmathsetmacro\pgfutil@tmpb{(#3/1cm)*(#4)-(#1/1cm)*(#6)}%
\pgfmathsetmacro\pgfutil@tmpc{(#1/1cm)*(#5)-(#2/1cm)*(#4)}%
\edef\pgfmathresult{\pgfutil@tmpa,\pgfutil@tmpb,\pgfutil@tmpc}%
\pgfmathsmuggle\pgfmathresult%
\endgroup}%
\pgfmathdeclarefunction{z3d}{1}{\begingroup
\def\tikz@td@pp(##1){\edef\pgfutil@tmp{\csname tikz@dcl@coord@##1\endcsname}}%
\edef\pgfutil@tmp{\csname tikz@dcl@coord@#1\endcsname}% 
\loop
\pgfutil@tempcnta=0%
\pgfutil@for\pgf@tmp:={\pgfutil@tmp}\do{\advance\pgfutil@tempcnta by1}%
\ifnum\pgfutil@tempcnta=1\relax
\expandafter\tikz@td@pp\pgfutil@tmp%
\repeat
\edef\pgfmathresult{0}%
\ifcase\pgfutil@tempcnta
\message{Something is wrong here.^^J}%
\or
\message{Something is wrong here.^^J}%
\or
\or
\pgfmathsetmacro{\tikz@td@normal}{tdnormal(\the\pgf@xx,\the\pgf@xy,\the\pgf@yx,\the\pgf@yy,\the\pgf@zx,\the\pgf@zy)}%
\def\tikz@td@strip@brackets(##1,##2,##3)##4,##5,##6;{%
\edef\pgf@tmp{(##1)*(##4)+(##2)*(##5)+(##3)*(##6)}}%
\edef\temp{\noexpand\tikz@td@strip@brackets\pgfutil@tmp\tikz@td@normal;}%
\temp
\pgfmathparse{\pgf@tmp}%
\fi
\pgfmathsmuggle\pgfmathresult%
\endgroup}
\def\tikz@let@command et{%
  \let\p=\tikz@cc@dop%
  \let\x=\tikz@cc@dox%
  \let\y=\tikz@cc@doy%
  \let\z=\tikz@cc@doz%
  \let\n=\tikz@cc@don%
  \pgfutil@ifnextchar i{\tikz@cc@stop@let}{\tikz@cc@handle@line}%
}%
\def\tikz@cc@doz#1{\csname tikz@cc@z@#1\endcsname}%
\def\tikz@cc@dolet#1{%
  \pgf@process{#1}%
  \expandafter\edef\csname tikz@cc@p@\tikz@cc@coord@name\endcsname{\the\pgf@x,\the\pgf@y}%
  \expandafter\edef\csname tikz@cc@x@\tikz@cc@coord@name\endcsname{\the\pgf@x}%
  \expandafter\edef\csname tikz@cc@y@\tikz@cc@coord@name\endcsname{\the\pgf@y}%
  \pgfutil@ifnextchar,{\tikz@cc@handle@nextline}{\tikz@cc@stop@let}%
}%
\tikzset{record z/.style={execute at end node={%
\pgfmathparse{z3d("\tikz@fig@name")}%
\expandafter\xdef\csname tikz@cc@z@\tikz@fig@name\endcsname{\pgfmathresult pt}}}}
\makeatother

\begin{document}
\begin{tikzpicture}[dot/.style={circle,fill,inner sep=1.2pt,record z}]
 \begin{scope}[3d view]
  \draw[-stealth] (0,0,0) -- (2,0,0) node[pos=1.2]{$\vec x$};
  \draw[-stealth] (0,0,0) -- (0,2,0) node[pos=1.2]{$\vec y$};
  \draw[-stealth] (0,0,0) -- (0,0,2) node[pos=1.2]{$\vec z$};
  \path[nodes=dot] (1,2,3) node (A){} (4,5) node (B){} (A) node (C){};
  \path let \p1=(A),\p2=(B),\p3=(C) in 
   (A) node[above] {$A=({}$\x1,\y1,\z{A})}
   (B) node[above] {$B=({}$\x2,\y2,\z{B}\pgfmathresult pt)}
   (C) node[below] {$C=({}$\x3,\y3,\z{C})};
 \end{scope}  
 \begin{scope}[xshift=6cm,3d view={110}{20}]
  \draw[-stealth] (0,0,0) -- (2,0,0) node[pos=1.2]{$\vec x'$};
  \draw[-stealth] (0,0,0) -- (0,2,0) node[pos=1.2]{$\vec y'$};
  \draw[-stealth] (0,0,0) -- (0,0,2) node[pos=1.2]{$\vec z'$};
  \path[nodes=dot] (1,2,3) node (A'){} (4,5) node (B'){} (A') node (C'){};
  \path let \p1=(A'),\p2=(B'),\p3=(C'),\p4=(A),\p5=(B),\p6=(C) in 
   (A') node[above] {$A'=({}$\x1,\y1,\z{A'})}
   (B') node[above] {$B'=({}$\x2,\y2,\z{B'})}
   (C') node[below] {$C'=({}$\x3,\y3,\z{C'})}
   (A) edge[edge label={\pgfmathparse{sqrt(pow(\x1/1cm-\x4/1cm,2)+pow(\y1/1cm-\y4/1cm,2)+pow(\z{A}/1cm-\z{A'}/1cm,2))}%
   $d=\pgfmathprintnumber\pgfmathresult$cm}] (A');
 \end{scope}  
\end{tikzpicture}
\end{document}

введите описание изображения здесь

Обратите внимание, что z3dфункцию можно использовать независимо от возможных хаков, однако она вычисляет компонент, zпредполагая, что пользователь не переключал свою систему координат.

Связанный контент