# Pass a 3d coordinate via pgfkeys

I'm using 3dplot to draw in 3D. Now I want to define my own environment, called pose, that is supposed to be used inside a tikzpicture-environment. It should perform a coordinate system tansformation according x,y,z,roll,pitch and yaw, that are passed to my pose-environment via pgfkeys.

Here is the code:

\documentclass{article}

\usepackage{tikz}
\usepackage{tikz-3dplot}
\usetikzlibrary{3d}

% fix wrong implementation of xy canvas
\makeatletter
\tikzoption{canvas is xy plane at z}[]{%
\def\tikz@plane@origin{\pgfpointxyz{0}{0}{#1}}%
\def\tikz@plane@x{\pgfpointxyz{1}{0}{#1}}%
\def\tikz@plane@y{\pgfpointxyz{0}{1}{#1}}%
\tikz@canvas@is@plane
}
\makeatother

\pgfkeys{%
/poseenv/.is family, /poseenv,%
% set the defaut values
default/.style = {%
x = 0,%
y = 0,%
z = 0,%
roll = 0,%
pitch = 0,%
yaw = 0%
},%
x/.estore in = \poseenvx,%
y/.estore in = \poseenvy,%
z/.estore in = \poseenvz,%
roll/.estore in = \poseenvroll,%
pitch/.estore in = \poseenvpitch,%
yaw/.estore in = \poseenvyaw,%
origin/.code n args={1}{%
#1% ### MODIFY CODE HERE ###
}%
}%

\newenvironment{pose}[1][]%
{%
% parse arguments
\pgfkeys{/poseenv, default, #1}%
% modify 3dplot's rotated coords according to \poseenv{x,y,z,roll,pitch,yaw}
\coordinate (origin) at (\poseenvx,\poseenvy,\poseenvz);
\tdplotsetrotatedcoordsorigin{(origin)}
% todo: do rotation

% begin the scope
\begin{scope}[tdplot_rotated_coords,transform shape]
}%
{%
% end the scope
\end{scope}
}%

\begin{document}
\tdplotsetmaincoords{80}{45}
\begin{tikzpicture}[tdplot_main_coords]
% draw a coordinate system in black at the origin
\draw[->] (0,0,0) -- (1,0,0);
\draw[->] (0,0,0) -- (0,1,0);
\draw[->] (0,0,0) -- (0,0,1);
\draw[canvas is xy plane at z=0,lightgray] (0,0) grid (3,3);

% define where the red coordinate system should be
\coordinate (A) at (1,1,0);

% draw the red coordinate system at (A)
\begin{pose}[origin=(A)]
\draw[->,red] (0,0,0) -- (1,0,0);
\draw[->,red] (0,0,0) -- (0,1,0);
\draw[->,red] (0,0,0) -- (0,0,1);
\draw[->,red] (0,0,0) -- (1,1,1);
\end{pose}

% draw the green coordinate system at (1,1,0)
\begin{pose}[x=1,y=1,z=0]
\draw[->,green] (0,0,0) -- (1,0,0);
\draw[->,green] (0,0,0) -- (0,1,0);
\draw[->,green] (0,0,0) -- (0,0,1);
\draw[->,green] (0,0,0) -- (1,1,1);
\end{pose}
\end{tikzpicture}
\end{document}


Except for the rotational part the environment is working in principle, but the rotation is not part of the question here. You see, that the green coordinate system is correctly painted at (1,1,0). But the red coordinate system is painted at (0,0,0) instead of (A). This is because the implementation is not yet finished. Obviously, the implementation at the line, marked with ### MODIFY CODE HERE ### is not correct.

What do I have to replace that code with?

• By the time you have stored (1,1,0) in (A) it has been converted to a point in the plane, so you are loosing some of the 3d information you need for your planned calculations. You would be better off setting up something that passes all three coordinates. – Andrew Swann Apr 26 '13 at 13:49
• @AndrewSwann Can you try adding an answer? – egreg May 3 '14 at 21:07
• @egreg Answer now added. – Andrew Swann May 9 '14 at 12:18

As stated in my comment, the fundamental problem is that the statement

\coordinate (A) at (1,1,0);


only stores the 2d representation of the point in (A).

Using pgfkeys we can define a new command \tdcoord that stores the three-dimensional representation too. Then we define your origin key to be a style that sets x, y and z from the stored information.

My chosen implementation will store \tdcoord (A) at (3,1,0) in a set of x, y and z keys under /tdcoords/(A)/. (td is for three-dimensional). We can then extract values via \pgfkeysvalueof{/tdcoords/(A)/x} etc.

\documentclass{article}

\usepackage{tikz}
\usepackage{tikz-3dplot}
\usetikzlibrary{3d}

% fix wrong implementation of xy canvas
\makeatletter
\tikzoption{canvas is xy plane at z}[]{%
\def\tikz@plane@origin{\pgfpointxyz{0}{0}{#1}}%
\def\tikz@plane@x{\pgfpointxyz{1}{0}{#1}}%
\def\tikz@plane@y{\pgfpointxyz{0}{1}{#1}}%
\tikz@canvas@is@plane
}
\makeatother

\pgfkeys{%
/poseenv/.is family, /poseenv,%
% set the defaut values
default/.style = {%
x = 0,%
y = 0,%
z = 0,%
roll = 0,%
pitch = 0,%
yaw = 0%
},%
x/.estore in = \poseenvx,%
y/.estore in = \poseenvy,%
z/.estore in = \poseenvz,%
roll/.estore in = \poseenvroll,%
pitch/.estore in = \poseenvpitch,%
yaw/.estore in = \poseenvyaw,%
origin/.style={\pgfkeysifdefined{/tdcoords/#1}{%
x=\pgfkeysvalueof{/tdcoords/#1/x},
y=\pgfkeysvalueof{/tdcoords/#1/y},
z=\pgfkeysvalueof{/tdcoords/#1/z}}{/tdcoords/#1}}%
}%

\def\tdcoord #1 at (#2,#3,#4);{\pgfkeys{/tdcoords/#1/.is
family,/tdcoords/#1,x/.initial=#2,y/.initial=#3,z/.initial=#4}
\coordinate #1 at (#2,#3,#4);}

\newenvironment{pose}[1][]%
{%
% parse arguments
\pgfkeys{/poseenv, default, #1}%
% modify 3dplot's rotated coords according to \poseenv{x,y,z,roll,pitch,yaw}
\coordinate (origin) at (\poseenvx,\poseenvy,\poseenvz);
\tdplotsetrotatedcoordsorigin{(origin)}
% todo: do rotation

% begin the scope
\begin{scope}[tdplot_rotated_coords,transform shape]
}%
{%
% end the scope
\end{scope}
}%

\begin{document}
\tdplotsetmaincoords{80}{45}
\begin{tikzpicture}[tdplot_main_coords]
% draw a coordinate system in black at the origin
\draw[->] (0,0,0) -- (1,0,0);
\draw[->] (0,0,0) -- (0,1,0);
\draw[->] (0,0,0) -- (0,0,1);
\draw[canvas is xy plane at z=0,lightgray] (0,0) grid (3,3);

% define where the red coordinate system should be
\tdcoord (A) at (3,1,0);

% draw the red coordinate system at (A)
\begin{pose}[origin=(A)]
\draw[->,red] (0,0,0) -- (1,0,0);
\draw[->,red] (0,0,0) -- (0,1,0);
\draw[->,red] (0,0,0) -- (0,0,1);
\draw[->,red] (0,0,0) -- (1,1,1);
\end{pose}

% draw the green coordinate system at (1,1,0)
\begin{pose}[x=1,y=1,z=0]
\draw[->,green] (0,0,0) -- (1,0,0);
\draw[->,green] (0,0,0) -- (0,1,0);
\draw[->,green] (0,0,0) -- (0,0,1);
\draw[->,green] (0,0,0) -- (1,1,1);
\end{pose}
\end{tikzpicture}
\end{document}


Note that \pgfkeysvalueof does no error checking on whether the given key is defined, so I have wrapped the origin style code in a \pgfkeysifdefined testing the existence of the top key /tdcoords/#1, where #1 is the value passed to origin. In case of non-existence I call /tdcoords/#1 which will then fail with a reasonably understandable error message.

• Oh wow, I wish I had seen this before I set out to "reinvent" it. ;-) – user121799 Feb 10 at 2:54