# How to draw deformed cylinder with tikz and 3dplot

I have a cylinder. And yet I wish to draw its deformed picture. I tried

\pgfsetcurvilinearbeziercurve
{\pgfpointxyz{0}{0}{0}}
{\pgfpointxyz{0.1}{0.1}{1.5}}
{\pgfpointxyz{0.25}{0.25}{1.75}}
{\pgfpointxyz{0.5}{0.5}{2.5}}
\pgftransformnonlinear{\pgfgetlastxy\x\y\pgfpointcurvilinearbezierorthogonal{\y}{-\x}}


but rod’s axis and top circle are not in place, and deformation itself is too unnaturally weird

Here’s full TeX I’ve done for now. It draws a cylinder in true 3D filled with color with black bounding lines and then attempts to deform it

\documentclass[tikz,margin=5]{standalone}

\usepgfmodule{nonlineartransformations}
\usepgflibrary{curvilinear}

\usepackage{tikz}
\usepackage{tikz-3dplot} % needs tikz-3dplot.sty in same folder
\usetikzlibrary{calc}
\usetikzlibrary{arrows, arrows.meta}

\usepackage{bm}

\begin{document}

\begin{center}

\def\cameraangle{105}
\tdplotsetmaincoords{66}{\cameraangle} % orientation of camera

\def\rodheight{8}

\pgfmathsetmacro{\beginangle}{\cameraangle}
\pgfmathsetmacro{\endangle}{\cameraangle - 180}

\tikzset{pics/rod/.style={code={

\coordinate (O) at ( 0, 0, 0 ) ;
\coordinate (rodTopCenter) at ($(O) + ( 0, 0, \rodheight )$) ;

% draw rod

%%\foreach \height in { 0, 0.02, ..., \rodheight }
%%\draw [line width=0.8pt, color=yellow, fill=yellow]
%%($(O) + ( 0, 0, \height )$) circle ( \rodradius ) ;

\pgfmathsetmacro{\stepangle}{\beginangle - 5}
\foreach \angle in { \beginangle, \stepangle, ..., \endangle }
\draw [line width=0.8pt, color=yellow]
( \angle:\rodradius ) -- ($( \angle:\rodradius ) + ( 0, 0, \rodheight )$) ;

\draw [line width=0.8pt, color=black, domain=\beginangle:\endangle]

\draw [line width=0.85pt, color=black, line cap=round]
( \beginangle:\rodradius ) -- ($( \beginangle:\rodradius ) + ( 0, 0, \rodheight )$) ;
\draw [line width=0.85pt, color=black, line cap=round]
( \endangle:\rodradius ) -- ($( \endangle:\rodradius ) + ( 0, 0, \rodheight )$) ;

\draw [line width=0.8pt, color=black, fill=yellow] (rodTopCenter) circle ( \rodradius ) ;

}}}

\tikzset{pics/rodaxis/.style={code={

\coordinate (O) at ( 0, 0, 0 ) ;
\coordinate (rodTopCenter) at ($(O) + ( 0, 0, \rodheight )$) ;

% draw axis
\draw [line width=0.5pt, blue, line cap=round, dash pattern=on 12pt off 2pt on \the\pgflinewidth off 2pt]
($(O) - ( 0, 0, 0.4pt )$) -- ($(rodTopCenter) + ( 0, 0, 0.4pt )$) ;

}}}

\begin{tikzpicture}[scale=1, tdplot_main_coords] % use 3dplot

\coordinate (O) at ( 0, 0, 0 ) ;
\coordinate (rodTopCenter) at ($(O) + ( 0, 0, \rodheight )$) ;

% draw circle
\def\heightofhatch{0.5}

\pgfmathsetmacro{\stepangleforcircle}{\beginangle - 10}
\foreach \angle in { \beginangle, \stepangleforcircle, ..., \endangle }
\draw [line width=0.4pt, color=black]
( \angle:\circleradius ) -- ($( \angle:\circleradius ) - ( 0, 0, \heightofhatch )$) ;

\draw [line width=0.8pt, color=black, fill=white] (O) circle ( \circleradius ) ;

% draw rod
\pic (initial) {rod} ;
\pic (initial) {rodaxis} ;

% draw force
\def\forcelength{1.2}

\draw [line width=1.4pt, blue, line cap=round, -{Triangle[round, length=3.6mm, width=2.4mm]}]
($(rodTopCenter) + ( 0, 0, \forcelength)$) -- (rodTopCenter)
node [pos=0.5, above left, inner sep=0, outer sep=3.2pt]
{\scalebox{1.2}[1.2]{${\bm{F}}$}} ;

\scoped {
\pgfsetcurvilinearbeziercurve
{\pgfpointxyz{0}{0}{0}}
{\pgfpointxyz{0.1}{0.1}{1.5}}
{\pgfpointxyz{0.25}{0.25}{1.75}}
{\pgfpointxyz{0.5}{0.5}{2.5}}
\pgftransformnonlinear{\pgfgetlastxy\x\y\pgfpointcurvilinearbezierorthogonal{\y}{-\x}}
\pic (deformed) {rod} ;
\pic (deformed) {rodaxis} ;
}

\end{tikzpicture}
\end{center}

\end{document}


Why is it messed up? Can such transformation work with 3D? How to deal with these \pgfsetcurvilinearbeziercurve, \pgftransformnonlinear and \pgfpointcurvilinearbezierorthogonal (and not to make hundreds of trials and mistakes)? Or maybe some other transformation will suit me better? Or doing deformation manually is the only way?

update

Thanks @marmot, all parts are together now. His variant is also faster, it doesn’t use loop to paint the rod’s side, but just single \draw

\tikzset{pics/rod/.style={code={

%%\coordinate (O) at ( 0, 0, 0 ) ;

% draw rod

%%
%% previous variant number first
%%

%%\foreach \height in { 0, 0.02, ..., \rodheight }
%%\draw [line width=0.8pt, color=yellow, fill=yellow]
%%($(O) + ( 0, 0, \height )$) circle ( \rodradius ) ;

%%
%% previous variant number second
%%

%%\pgfmathsetmacro{\stepangle}{\beginangle - 4}
%%\foreach \angle in { \beginangle, \stepangle, ..., \endangle }
%%\draw [line width=0.8pt, color=yellow!50!white, opacity=.9]
%%( \angle:\rodradius ) -- ($( \angle:\rodradius ) + ( 0, 0, \rodheight )$) ;

%%\draw [line width=0.8pt, color=black, domain=\beginangle:\endangle]

%%\draw [line width=0.85pt, color=black, line cap=round]
%%( \beginangle:\rodradius ) -- ($( \beginangle:\rodradius ) + ( 0, 0, \rodheight )$) ;
%%\draw [line width=0.85pt, color=black, line cap=round]
%%( \endangle:\rodradius ) -- ($( \endangle:\rodradius ) + ( 0, 0, \rodheight )$) ;

%%
%% current variant by @marmot
%%

\draw [line width=0.8pt, color=black, fill=yellow!50!white, opacity=.9]
plot [domain=\beginangle:\endangle]
-- plot [domain=\endangle:\beginangle]
-- cycle ;

%%\draw [line width=0.8pt, color=black, fill=yellow, opacity=.9] ( 0, 0, \rodheight ) circle ( \rodradius ) ;

\draw [line width=0.8pt, color=black, fill=yellow!50!white, domain=0:360]

}}}

\tikzset{pics/rodaxis/.style={code={

% draw axis
\draw [line width=0.5pt, blue, line cap=round, dash pattern=on 12pt off 2pt on \the\pgflinewidth off 2pt]
( 0, 0, -0.2pt ) -- ( 0, 0, \rodheight + 0.2pt ) ;

}}}


But I am still unsatisfied with the transformation itself. Position the camera at angle 33 instead of 66

\def\cameraangle{100}
\tdplotsetmaincoords{33}{\cameraangle} % orientation of camera


to see the problem

If one wonders what is it expected to be. At first, rod’s cross-sections (circles here) need to remain undeformed. At second, the deformed axis— by Leonhard Euler’s small vibrations/stability theory— is sine (well, I don’t need exact sine, just something looking like smoothly increasing displacements from zero at bottom to maximum at top)

• I assume you copied this code from somewhere? You may be significantly better off just drawing this from scratch and learn Tikz from easy to hard, not the other way around. If you haven't copied it from anywhere but wrote it from scratch, I retract my assumption and nod in appreciation of the code. – Huang_d May 20 at 19:31
• It is ~88% original code. But that transformation part, which I took from tex.stackexchange.com/questions/365967/… and then played with it – Douglas Mencken May 20 at 19:33
• Then I apologize for my assumption, it's a pretty neat approach but hard to understand. Do you want to add a few lines to your question or comments to your code to make it easier to understand? – Huang_d May 20 at 19:42

Nice question, I really like your approach! There are two issues, which get fixed in the code below:

1. you mix 3d and 2d coordinates in your paths.
2. your curve does not have units, so the dimensions get interpreted as points.

This is the answer with your own revised suggestion for the curve.

\documentclass[tikz,margin=5]{standalone}

\usepgfmodule{nonlineartransformations}
\usepgflibrary{curvilinear}

\usepackage{tikz-3dplot} % needs tikz-3dplot.sty in same folder
\usetikzlibrary{calc}
\usetikzlibrary{arrows, arrows.meta}

\usepackage{bm}

\begin{document}

\def\cameraangle{105}
\tdplotsetmaincoords{66}{\cameraangle} % orientation of camera

\def\rodheight{8}

\pgfmathsetmacro{\beginangle}{\cameraangle}
\pgfmathsetmacro{\endangle}{\cameraangle - 180}

\tikzset{pics/rod/.style={code={

\coordinate (O) at ( 0, 0, 0 ) ;
\coordinate (rodTopCenter) at ($(O) + ( 0, 0, \rodheight )$) ;

% draw rod

%%\foreach \height in { 0, 0.02, ..., \rodheight }
%%\draw [line width=0.8pt, color=yellow, fill=yellow]
%%($(O) + ( 0, 0, \height )$) circle ( \rodradius ) ;

\draw [line width=0.8pt, color=black,fill=yellow]
plot[domain=\beginangle:\endangle]
-- plot[domain=\endangle:\beginangle]
\draw [line width=0.8pt, color=black,fill=yellow]
plot[domain=0:360]
}}}

\tikzset{pics/rodaxis/.style={code={
\draw [line width=0.5pt, blue, line cap=round, dash pattern=on 12pt off 2pt on \the\pgflinewidth off 2pt]
(0,0,0.4pt) -- ( 0, 0, \rodheight+0.4pt);
}}}

\begin{tikzpicture}[scale=1, tdplot_main_coords] % use 3dplot

\coordinate (O) at ( 0, 0, 0 ) ;
\coordinate (rodTopCenter) at ($(O) + ( 0, 0, \rodheight )$) ;

% draw circle
\def\heightofhatch{0.5}

\pgfmathsetmacro{\stepangleforcircle}{\beginangle - 10}
\foreach \angle in { \beginangle, \stepangleforcircle, ..., \endangle }
\draw [line width=0.4pt, color=black]
( \angle:\circleradius ) -- ($( \angle:\circleradius ) - ( 0, 0, \heightofhatch )$) ;

\draw [line width=0.8pt, color=black, fill=white] (O) circle ( \circleradius ) ;

% draw rod
\pic (initial) {rod} ;
\pic (initial) {rodaxis} ;

% draw force
\def\forcelength{1.2}

\draw [line width=1.4pt, blue, line cap=round, -{Triangle[round, length=3.6mm, width=2.4mm]}]
($(rodTopCenter) + ( 0, 0, \forcelength)$) -- (rodTopCenter)
node [pos=0.5, above left, inner sep=0, outer sep=3.2pt]
{\scalebox{1.2}[1.2]{${\bm{F}}$}} ;

\scoped {
\pgfsetcurvilinearbeziercurve%
{\pgfpointxyz{0}{0}{0}}%
{\pgfpointxyz{0}{0}{0.5cm}}%
{\pgfpointxyz{0.25cm}{0}{1cm}}%
{\pgfpointxyz{1.25cm}{0}{1.25cm}}
\pgftransformnonlinear{\pgfgetlastxy\x\y\pgfpointcurvilinearbezierorthogonal{\y}{-\x}}
\path (0,0) pic (deformed) {rod} ;
\pic (deformed) {rodaxis} ;
}
\end{tikzpicture}
\end{document}

• I had no idea one can use 3D coordinates in tikz! This is very good to know. Thanks for the example. – Nasser May 21 at 4:22
• Wow, it really replaces loop with one single draw, and boundaries are here, nice; and all parts of picture are in-place yet. Superb. But I am still unsatisfied with the deformation itself, just try to rotate the camera like \tdplotsetmaincoords{33}{\cameraangle}, and you’ll see why – Douglas Mencken May 21 at 8:00
• @DouglasMencken I believe to have fixed the issue. The problem is that you did not specify the units so pgf took them to be points. Therefor the the curve that specifies the deformation was very short, and for most of the stretch that some extrapolation was used, explaining the "strange" deformations. Once one adds cm units, this does no longer happen. – marmot May 21 at 12:39
• @marmot It’s genious! With {\pgfpointxyz{0}{0}{0}}{\pgfpointxyz{0}{0}{0.5cm}}{\pgfpointxyz{0.25cm}{0}{1cm}}{\pgfpointxyz{1.25cm}{0}{1.25cm}} I got what I wanted. Thank you – Douglas Mencken May 21 at 15:31