12

I am trying to draw a cart on top of a loop. I have a system where a cart rolls down an incline and encounters a loop. I then want to look at cart when it reaches the apex of the loop.

The problem I am having is how to draw it's wheels touching the loop so that they are tangent to the loop.

Here is an image of the bigger picture.

enter image description here

Now here is what I have so far for the just the interaction of the cart and the top of the loop.

\documentclass[tikz]{standalone}
\usetikzlibrary{intersections}

\begin{document}
\begin{tikzpicture}
  \draw[name path = circ] (0, 0) circle[radius = 0.05];

  \path[name path = line] (-0.1, .04) -- +(0.2, 0);
  \path[name intersections = {of = circ and line}];

  \coordinate (P1) at (intersection-1);
  \coordinate (P1) at (intersection-2);
\end{tikzpicture}
\end{document}

I put in a path that intersects the loop and labelled the points. I did this as the line where the cart wheels should be tangent to the loop. Therefore, if everything is based on these points, we can adjust the path for desired distance from the top that will give the image the best look.

dustin
  • 18,617
  • 23
  • 99
  • 204

5 Answers5

16

Here's a version that uses the computational capacities of Asymptote to good advantage. The code is robust in that if you change the distance between the wheels and/or the radius of the wheels, it will still work correctly.

\documentclass[margin=10pt,convert]{standalone}
\usepackage{asypictureB}
\begin{document}
\begin{asypicture}{name=cart_on_loop}
    settings.outformat="pdf";
    unitsize(1cm);
    import graph;
    path loop =  (-3,0) --- (-1,0) .. (0,4){left} .. (1,0) --- (3,0);
    real wheelradius = 0.05, wheeldistance = 0.5, cartheight = 0.3;

    //Where will a wheel center be when it's tangent to the loop at path time t?
    pair wheelcenter(real t) {
        return point(loop, t) + wheelradius*(rotate(90)*dir(loop,t));
    }
    //This path is for computation, not drawing:
    path wheelpath = graph(wheelcenter, 0, length(loop), operator ..);



     picture cart = currentpicture.copy();
     draw(cart, circle(c=(0,0), r=wheelradius));
     draw(cart, circle(c=(wheeldistance, 0), r=wheelradius));
     draw(cart, box((0, wheelradius), (wheeldistance,  wheelradius+cartheight)));
     label(cart, "$M$", align=0.2N, position=(wheeldistance/2, wheelradius));

     void drawcart(pair trailingwheel, pair leadingwheel = trailingwheel + (wheeldistance, 0)) {
        transform T = identity();
        pair vector = leadingwheel - trailingwheel;
        real angle = degrees(atan2(vector.y, vector.x));
        T = rotate(angle)*T;
        T = scale(length(vector) / wheeldistance)*T;
        T = shift(trailingwheel)*T;
        add(T*cart);
     }

     //t is specified in arclength
     void drawcart(real t) {
        pair trailingwheel = arcpoint(wheelpath, t);
        pair estimateleading = arcpoint(wheelpath, t + wheeldistance);
        path samedist = circle(c=trailingwheel, r=wheeldistance);
        pair[] intersections = intersectionpoints(samedist, wheelpath);
        pair leadingwheel = intersections[0];
        for (pair candidate : intersections) {
            if (length(candidate - estimateleading) < length(leadingwheel - estimateleading))
                leadingwheel = candidate;
        }
        drawcart(trailingwheel, leadingwheel);
     }


     //Draw the loop:               
    draw(subpath(loop, 0, length(loop)/2));
    draw(subpath(loop, length(loop)/2, length(loop)), white + linewidth(2pt) + squarecap);
    draw(subpath(loop, length(loop)/2, length(loop)));

    /* Find the times when the center of the wheel has x = wheeldistance/2.
     * There will be three such times (entering loop, at the top, leaving loop), 
     * indexed by 0, 1, 2. We want the middle one.
     */
     real patht = times(wheelpath, wheeldistance/2)[1];
     real t = arclength(subpath(wheelpath,0,patht));
     drawcart(t);

     //Draw a couple more carts at random places
     drawcart(0.2);
     drawcart(3.3);


\end{asypicture}
\end{document}

enter image description here

15

Drawing the wheels is not difficult, since their center is in the radius of the loop. The problem is to draw the rest of the car. In is current form (in your drawing), it looks like a block on top of the wheels. How would it roll? How are the wheels attached to the body?

I think a more appropiate way to draw the block on top of the center of the wheels, instead of on top of the wheels. Using this approach, the following code does the trick. If you want the block on top of the wheels, as in your figure, simply uncomment the marked lines. However the result is uglier, IMHO.

Code:

(Updated to add a translucent opacity to the rectangle, so that the wheels are partially occluded. Thanks to Charles Staats for the suggestion)

\def\loopradius{4cm}
\def\wheelradius{5mm}

\usetikzlibrary{intersections,calc}

\begin{tikzpicture}[thick]
  \coordinate (center) at (0,0);
  \draw[name path = circ] (center) circle[radius = \loopradius];

  \path[name path = line] ($(-10, 0.87*\loopradius)$) -- +(20, 0);
  \path[name intersections = {of = circ and line}];

  \coordinate (P1) at (intersection-1);
  \coordinate (P2) at (intersection-2);

 \coordinate (C1) at ($(P1)!\wheelradius!(center)$);
 \coordinate (C2) at ($(P2)!\wheelradius!(center)$);

 \foreach \p in {1,2} {
 \draw[red] (C\p) circle (\wheelradius);
 }
 %\coordinate (C1) at ($(P1)!2*\wheelradius!(center)$);
 %\coordinate (C2) at ($(P2)!2*\wheelradius!(center)$);

 \draw[red, fill=white, fill opacity=0.85]
    let \p1 = ($(C2)-(C1)$),
        \n1 = {veclen(\p1)}
    in
    (C2) rectangle +($(\n1, -0.4*\n1)$)
    node[midway] {M};

\end{tikzpicture}

Result:

Resulting image

JLDiaz
  • 55,732
  • If I understand correctly, this code cannot accept the width of the cart as a parameter. – Charles Staats May 06 '14 at 15:56
  • @CharlesStaats you are right. This is because I respected the original approach of the OP, in which the position of the wheels are found as the intersection of the "loop" with a horizontal line at a "customizable" height. – JLDiaz May 06 '14 at 15:58
  • How did you determine 2*\wheelradius? From the dimension I am using, I had to change it 1.75. I also changed my circle radius to .7 and wheel radius to .05. I had a typo on circle radius in the OP. – dustin May 06 '14 at 16:05
  • What I mean is if I use a 2, the wheels aren't tangent to the circle. By changing it to a 1.75, they are so I am wandering how you came up with the 2. I just randomly picked 1.5 and it worked out. – dustin May 06 '14 at 16:28
  • Aesthetic suggestion: Fill in the rectangle to hide a quarter of each wheel. (Or else add spokes.) – Charles Staats May 06 '14 at 16:35
  • @dustin The code which is commented-out in my answer, is not meant to replace any other previous code, but to be "added", i.e. just uncomment it. From your question I'm guessing that you replaced the previous definition of C1, C2 with the commented one, but they are separate definitions, and both are needed. The first one finds the centers of the wheels, and calls them C1 and C2. The second one finds the corners of the rectangle, and calls them also C1 and C2, so that if the code is commented out, those corners are at the center of the wheels. – JLDiaz May 06 '14 at 16:44
  • I just tried your code in that manner but it doesn't work when the circle radius is 0.7 and the wheel radius is 0.05. Maybe it only works with your dimensions. – dustin May 06 '14 at 17:05
  • @dustin My code needs absolute units in the radius. I.e. instead of 0.7 you have to write 0.7cm (or whatever). If not, it is possible that there is a bad interaction between "unitless" dimensions (which are scaled according to tikz options), and "absolute" dimensions with units (which are not scaled). – JLDiaz May 06 '14 at 17:07
12

Just for fun with PSTricks.

\documentclass[pstricks,border=12pt]{standalone}
\usepackage{pst-node}

\begin{document}
\foreach \i in {0,15,...,345}{%
\begin{pspicture}(-2,-2)(2,2)
    \pscircle{2}
    \pnodes{A}(!1.9 \i\space 30 add PtoC)(!1.9 \i\space PtoC)
    \pspolygon*[linecolor=gray](A0)(A1)([offset=.5]{A0}A1)([offset=-.5]{A1}A0)
    \qdisk(A0){.1}\qdisk(A1){.1}
\end{pspicture}}
\end{document}

enter image description here

Frozen animation

\documentclass[pstricks,border=12pt]{standalone}
\usepackage{pst-node}

\begin{document}
\begin{pspicture}(-2,-2)(2,2)
    \pscircle{2}
    \pnodes{A}(!1.9 105 PtoC)(!1.9 75 PtoC)
    \pspolygon*[linecolor=gray](A0)(A1)([offset=.5]{A0}A1)([offset=-.5]{A1}A0)
    \qdisk(A0){.1}\qdisk(A1){.1}
\end{pspicture}
\end{document}

enter image description here

TikZ version

\documentclass[tikz,border=12pt]{standalone}
\usetikzlibrary{calc}
\begin{document}
\foreach \i in {0,15,...,345}{%
\begin{tikzpicture}
    \draw (0,0) circle (2);
    \coordinate (A0) at (\i+30:1.9);
    \coordinate (A1) at (\i:1.9);
    \fill [gray] (A0) -- (A1) -- ($(A1)!.5!90:(A0)$) -- ($(A0)!.5!-90:(A1)$) -- cycle;
    \fill (A0) circle (.1) (A1) circle (.1);
\end{tikzpicture}}
\end{document}
11

Here is a version in Metapost. I've defined a draw_car routine that takes an arbitrary path, a starting point along that path, the radius of the wheels, the length of the car, and a label.

Some little cars drawn along a path

The approach is explained in the comments. If you try to draw the car past the end of the path you'll get a paths don't intersect error.

prologues := 3;
outputtemplate := "%j%c.eps";

vardef draw_car(expr p, n, r, len, name) = 
  % p : an arbitrary path
  % n : a point on p (ie "point n of p" should exist)
  % r : the radius of the car's wheels
  % len : the length of the car's body
  save a, b, q, t; path q; pair a, b; transform t;
  % construct a path q, parallel to the given path p starting at point n of p
  q = for i = n step 0.1 until length(p)+eps: 
        point i of p + ((0,r) rotated angle direction i of p) ..  
      endfor
      point infinity of p + ((0,r) rotated angle direction infinity of p);
  % a = center of back wheel, b = center of front wheel
  a = point 0 of q;
  b = q intersectionpoint fullcircle scaled 2 len shifted a;
  % draw the wheels
  fill fullcircle scaled 2r shifted a;
  fill fullcircle scaled 2r shifted b;
  % draw the body of the truck with a label, suitably transformed
  t = identity rotated angle (b-a) shifted a;
  draw unitsquare xscaled len yscaled 13/21 len transformed t;
  picture M; M = image(label(name,(1/2len,1/3len))); draw M transformed t;
  enddef;

beginfig(1);
path p; 
p = origin .. (3cm,-2cm) {dir -45} .. (8cm,1cm) {up};
draw p withcolor .687 red;
draw_car(p, 0, 1mm, 1cm, "A");
draw_car(p, 0.6, 1mm, 1cm, "B");
draw_car(p, 1.2, 1mm, 1cm, "C");
draw_car(p, 1.8, 1mm, 1cm, "D");
endfig;
end.

Given this approach, we can decorate the car in whatever way we like by varying the bit that draws the body. For example, we could add these "go faster" stripes:

enter image description here

by adding

% draw some go faster stripes
draw ((-2,1/4 len) -- (-2,13/21 len)) transformed t;
draw ((-4,1/3 len) -- (-4,13/21 len)) transformed t;
draw ((-6,1/2 len) -- (-6,13/21 len)) transformed t;

after defining the transformation.

We can also have a go at the OP diagram:

Cars on a track with a loop

beginfig(2);
h = 4.4cm;
r = 2cm;
path track; track = (0,h) {dir -50} .. { right } (3r, 0) -- 
    (4.9r, 0) ..  {up}    (6r,r) ..  {left} (5r,2r) .. 
    {down} (4r,r) ..  {right} (5.1r, 0) -- (6.4r,0);

draw track withpen pencircle scaled .7pt withcolor .67 red;
% label the start
draw         (2 left -- 2 right) shifted (-5,0);
draw         (2 left -- 2 right) shifted (-5,h);
drawdblarrow ( origin -- (0,h)) shifted (-5,0);
label(btex $h$ etex, (0,1/2h));
% label the loop
drawarrow ( origin -- (0,r)) shifted  (5r,r);
label(btex $r$ etex, (5r+5,3/2r));
fill fullcircle scaled 2pt shifted (5r,r);
% draw the car at the start and then near the top of the loop
draw_car(track,0,2bp,1cm,"M");
draw_car(track,3.4,2bp,1cm,"M");
endfig;
Thruston
  • 42,268
  • This is essentially the same approach that I took. I see that you start the wheel path at the rear wheel to avoid ambiguous intersections, which is simpler and cleaner than what I did. – Charles Staats May 07 '14 at 20:58
9

The geometry is straightforward back-of-the-envelope stuff. Don't really need the math library but the code looks a bit cleaner.

\documentclass[tikz,border=5]{standalone}
\usetikzlibrary{math}
\begin{document}
\begin{tikzpicture}
\tikzmath{
  \R = 2;   % Loop radius
  \r = 1/8; % Wheel radius
  \w = 1;   % Cart width
  \h = 1/2; % Cart height
  %
  % Calculate the angle from the vertical
  % of the radius of the loop that passes 
  % through the center of the wheels.
  \a = asin((\w/2)/(\R-\r));
  % Calculate the vertical position of the cart wheels;
  \S = (\R-\r)*cos(\a);
}

\draw circle [radius=\R];
\draw (-\w/2,\S-\h-\r) rectangle ++(\w, \h);
\node at (0, \S-\h/2-\r) {$M$};
\draw [fill=white] (-\w/2,\S) circle [radius=\r] (\w/2,\S) circle [radius=\r];

\end{tikzpicture}
\end{document}

enter image description here

Mark Wibrow
  • 70,437