I am trying to make a GIF which will be a rotating Möbius strip, with some text printed along its (one!) side. I am trying to (obviously) do this in Mathematica.

After some diligent searching and a previous question I asked, I realize it is almost impossible to get text to behave well when it comes to opacity, rotations, etc. So instead I decided to make a rectangular image of the text, and then import it into Mathematica. But I'm getting stuck on putting all the pieces together.

Do I want to use this image as a texture on the Möbius strip (which I'm getting from a ParametricPlot3D)? Or is there some other way to "wrap" this image exactly once around the Möbius strip?

Also, would it be better to use an Animate to rotate the image - keeping the Möbius strip fixed - or is it better to simply rotate the whole thing? (I mean "better" as in "easier to do / better-looking").

I would actually prefer to eventually figure all this out on my own, but maybe some hints as to how I might proceed would be awesome.

EDIT: After Heike's helpful comment, I've come up with the following:

text = Style["Hello!", 200];
ParametricPlot3D[{4 Cos[a] + r Cos[a] Cos[a/2], 
4 Sin[a] + r Sin[a] Cos[a/2], r Sin[a/2]}, {a, 0, 2 Pi},
{r, -(3/2), 3/2}, Boxed -> False, Axes -> False, 
Mesh -> False, PlotStyle -> {Directive[Texture[text]], Opacity[.5]}, 
TextureCoordinateFunction -> ({#4, #5} &)]

This of course doesn't rotate. But perhaps something can be done with ViewVector or this esoteric TextureCoordinateFunction? I don't know, because my Mathematica is having a very hard time drawing this correctly.

  • 2
    $\begingroup$ Maybe this answer will help. As for creating an image of text, Rasterize can rasterize text as well. $\endgroup$ – Heike May 19 '12 at 8:23
  • $\begingroup$ @Heike Thanks, this looks really promising. The one thing I don't understand how to use is TextureCoordinateFunction. I feel like if I knew what in the world it did!, I could manipulate it to rotate my image/text around the Mobius strip. $\endgroup$ – Steve D May 19 '12 at 8:31
  • $\begingroup$ Do you want the whole Möbius strip to rotate or do you want to move the text to slide along the strip? In the former case you could play around with ViewVector; in the latter you could adjust TextureCoordinateFunction. You could use something like {#4 - t, #4}&. $\endgroup$ – Heike May 19 '12 at 9:14

Here's my contribution. I know you asked for hints only, but I couldn't resist

text = Style["This is some text on a Möbius strip", 
   FontFamily -> "Helvetica", FontSize -> 35];
img = ImageData@Image[Rasterize[text, Background -> None, ImageSize -> 1000]];

 ParametricPlot3D[{4 Cos[a] + r Cos[a] Cos[a/2], 
   4 Sin[a] + r Sin[a] Cos[a/2], r Sin[a/2]}, {a, 0, 
   4 \[Pi]}, {r, -(3/2), 3/2}, Boxed -> False, Axes -> False, 
  Mesh -> False,
  PlotPoints -> {100, 2},
  PlotStyle -> {EdgeForm[], FaceForm[Directive[Texture[img]], None]}, 
  TextureCoordinateFunction -> ({#4 - t, #5} &),
  PerformanceGoal -> "Quality"
  ], {t, 0, 1}]

Mathematica graphics

The trick to getting a transparent background is to use ImageData[Image[Rasterize[pic, Background -> None]]] for the texture.

Note that I'm using FaceForm[Texture[...], None] to plot the text on one side only. By letting a run from 0 to 4 Pi you traverse around the strip twice, once along the front and once along the back (insofar that you can speak of front and back in the case of a Möbius strip).

  • $\begingroup$ Well, yes, this isn't a hint, but it is an awesome answer! What is the actual difference between Rasterizing and just using the text itself? $\endgroup$ – Steve D May 19 '12 at 10:01
  • $\begingroup$ Very nice idea with the transparent side! But evaluating the code you posted gives me something that looks different: i.stack.imgur.com/EUAOE.png $\endgroup$ – Szabolcs May 19 '12 at 10:03
  • $\begingroup$ Ignore my comment: the problem was the too narrow window width. The text got wrapped during rasterization. $\endgroup$ – Szabolcs May 19 '12 at 10:06
  • $\begingroup$ Wonderful! Btw, the text reads from left to right with {t, 1, 0}. Also, Blue renders better for me, especially at the "folds". I had to shorten the text to "This is some text." to avoid it having two lines of text. $\endgroup$ – DavidC May 19 '12 at 14:09
  • 1
    $\begingroup$ This is a great start, but it doesn't work correctly: on a true Möbius strip, following the meridian around once should result in reversal of orientation. This behavior can be simulated by (a) not doubling the strip: let a range only between $0$ and $2\pi$; and (b) writing the text on a single line and concatenating a reversed version to that, as in img = ImageData@(i = Image[Rasterize[text, ImageSize -> 500]]); img = MapThread[Join[#1, #2] &, {img, Reverse[img]}, 1];. The effect is good (but not quite perfect: it's off by a pixel or two, apparently). $\endgroup$ – whuber May 21 '12 at 19:00

Here's a starting point:

tex = Rasterize[
  Style["Going round and round and round the Möbius strip!   ", Bold, 
   Large, FontFamily -> "Times"]]

{w, h} = ImageDimensions[tex]

tex1 = ImageTake[tex, All, Quotient[w, 2]]

tex2 = ImageTake[tex, All, Quotient[w, 2] - w]

frames = Table[
    ParametricPlot3D[{Cos[u], Sin[u], 0} + 
      r {Cos[u] Cos[u/2], Sin[u] Cos[u/2], Sin[u/2]}, {u, 0 + a, 
      2 Pi + a}, {r, -.2, .2}, 
     PlotStyle -> 
      FaceForm[Texture[tex1], Texture[ImageReflect[tex2, Top -> Bottom]]], 
     Mesh -> False, Boxed -> False, Axes -> False],
   {a, 4Pi, Pi/30, -Pi/30}


Unfortunately rendering the double sided textures is extremely slow on my machine.

enter image description here

  • 1
    $\begingroup$ This is a pretty solution, but please note that it is not a Möbius strip: the text is actually printed on a cylinder that is wrapped twice around with a full twist. As the question mentions, a Möbius strip does not have two sides, so what appears in a location will, when viewed from behind, still be visible. $\endgroup$ – whuber May 21 '12 at 18:37
  • $\begingroup$ @whuber I don't understand your comment. This is a ribbon that is wrapped around only a half twist, and thus has only one side. In particular I don't understand this: "what appears in a location will, when viewed from behind, still be visible". When you flip to the other side of the ribbon at a particular location, of course there will be a different letter of the text there. The text goes around $4\pi$. $\endgroup$ – Szabolcs May 21 '12 at 18:51
  • $\begingroup$ @whuber If you take a 10 cm long ribbon, twist it and glue it into a Möbius strip, you'll get something with a 20 cm long single side. $\endgroup$ – Szabolcs May 21 '12 at 18:56
  • $\begingroup$ That's correct: and that ribbon is not a Mobius strip. You may find this confusing because MMA gives a parameterized surface two sides and can draw separate textures on each side, effectively making it a double cover of the Mobius strip: a cylinder. Try my modification to Heike's solution to see the difference. $\endgroup$ – whuber May 21 '12 at 19:02
  • $\begingroup$ @whuber I am sorry, but it's still not completely clear what you mean. I just made one out of paper to see things better. Do you mean that when the end of the text (the "!" in mine) meets the beginning (the "M") then it should come back upside down? This is not the case on a real strip (just tried). Do you mean that on the back side of a particular location on the strip, the letters should be upside down compared to the front side? This is true, and it is the case for the model in the above animated GIF. $\endgroup$ – Szabolcs May 21 '12 at 19:14

Here is a completely different approach. I explain it in detail, but give only a hack as reference implementation. With the function ImportString and ExportString it is easily possible to convert a text into its outline. Examples for this can be found in the documentation to FilledCurve. Now the bad thing is, that FilledCurve only works in 2d; the good thing is we get lists of points representing the way to draw the letters of the text.

The only thing you have to do is to transform these points in a way that they lie on your Moebius-strip. This of course is easy when you rescale the coordinates of the points so that the x-values range from 0 to 2Pi. The y-values need to be rescaled so that they are in a range $[-r,r]$ where r can be chosen like you want.

What you then have to do is to transform all FilledCurve objects into some 3D graphics primitive and convert all points of the filled curve by the mapping

$$\{x,y\}\to \{\cos (x),\sin (x),0\}+y \left\{\cos (x) \cos \left(\frac{x}{2}\right),\sin (x) \cos \left(\frac{x}{2}\right),\sin \left(\frac{x}{2}\right)\right\}$$

All this can be done by a few lines to extract the min/max values of all points for the rescaling and basically one long line where you just ReplaceAll occurrences of FilledCurve and pack it into a Graphics3D.

With[{text = First[First[ImportString[ExportString[       
       Style["Ah, gravity, thou art a heartless bitch -", Italic, 
        FontSize -> 24, FontFamily -> "Helvetica"], "PDF"], "PDF", 
      "TextMode" -> "Outlines"]]]},

   Block[{allx, ally, meany, minmax}, 
     {allx, ally} = Transpose[Cases[text, {_Real, _Real}, Infinity]]; 
     minmax = {Min[allx], Max[allx]}; 
     meany = ((Max[#1] - Min[#1])/2. & )[Rescale[ally, minmax, {0, 2*Pi}]]; 
     Graphics3D[text /. FilledCurve[_, pts_] :> 
       With[{scaledPts = Rescale[pts, minmax, {0, 2*Pi}]}, 
        {ColorData["FruitPunchColors", scaledPts[[1, 1, 1]]/(2.*Pi)], 
         Tube[scaledPts /. {x_Real, y_Real} :> {Cos[x], Sin[x], 0} +  
           2*(y - meany)*{Cos[x]*Cos[x/2], Sin[x]*Cos[x/2], 
      ], Boxed -> False, Background -> LightGray]

enter image description here

Update and it is of course possible to create an animation from that. For this you create a Table of graphics where you change the rotation angle $\varphi$ in every frame. On the right hand side of the mapping you replace every appearance of $x$ with $x+\varphi$

enter image description here

  • $\begingroup$ Very nice approach! +1 for you $\endgroup$ – Lou May 22 '12 at 8:27

This is really an illustrated comment, because it only builds on Heike's elegant approach to show what a true Möbius strip looks like:

Möbius strip

Look at the left side: when the text finally wraps around to the beginning, it must have its orientation reversed. That's what makes the Möbius strip non-orientable (and so interesting).

To achieve this effect, I needed a trick, because Mathematica "knows" that every surface has two sides, which is not the case here. I therefore printed the text string on the surface and then printed its reversal along the "back" of the surface. Ideally the match between the original string and its reversal is one-for-one: if a pixel has a given color seen from one direction, then when seen from the back it must have exactly the same color.

To play with this, use Heike's solution but replace initialization of img by this:

img = ImageData@(i = Image[Rasterize[text, ImageSize -> 500]]);
img = MapThread[Join[#1, #2] &, {img, Reverse[img]}, 1];

(The image is resized to 500 to make the text fit on a single line.)

EDIT If you would like a semi-transparent view, which can be illuminating, add Opacity[0.5] to the PlotStyle directive.

  • 2
    $\begingroup$ That is exactly what any nonorientable (connected) manifold does, @Szabolcs! A continuous motion of any figure within the manifold can convert the figure into its mirror image. The Möbius strip is the canonical counterexample to your assertion. (It's better for the manifold not to have a boundary, but for that you need to use a Klein bottle or projective plane, neither of which can be drawn in three dimensions without a self-intersection.) $\endgroup$ – whuber May 21 '12 at 19:23
  • 2
    $\begingroup$ Clearly, someone should take a picture of a transparent Möbius strip with stuff written on it for comparison purposes... $\endgroup$ – J. M. is away May 21 '12 at 19:23
  • 2
    $\begingroup$ @J.M. I was hunting one down as you wrote that: segerman.org/autologlyphs/Mobius_strip_real.jpg (I should add that if you run my modification to Heike's solution, you will be able to simulate what you're asking for. :-) $\endgroup$ – whuber May 21 '12 at 19:25
  • 3
    $\begingroup$ @Szabolcs: That's the point of the Möbius strip: Locally the transformation would not be possible (as you say, it's a reflection, and cannot be done continuously). However when following the Möbius strip, exactly that happens. I suggest that you make a (physical) Möbius strip of transparent material and write on it to see how it works. $\endgroup$ – celtschk May 21 '12 at 19:25
  • 1
    $\begingroup$ @whuber What you posted (i.e. reflected images) is only possible if you make the ribbon transparent, and look at it from the other side. This is not a property of the Möbius strip though—just a property of transparent surfaces where you can't choose which side to paint on, you always paint on both. Yes, every point on the Möbius ribbon is accessible by going around it—it has a single surface in this sense, but locally at any point is has two surfaces on which I can paint two different things. $\endgroup$ – Szabolcs May 22 '12 at 13:05

I recently rediscovered the CurvesGraphics6 package, which provides an easy way to write text on a surface.

After loading the CurvesGraphics6 package, you can evaluate the following to create the Möbius strip:

  {{Cos[u], Sin[u], 0} + r {Cos[u] Cos[u/2], Sin[u] Cos[u/2], Sin[u/2]}, 
   {u, 0, 2 Pi}, {r, -.2, .2}}, 
  {Text["one two three four five six seven eight. "]}]

Mathematica graphics

The text is double-sided by default, so you'll see what you would get with a transparent Möbius strip.

  • $\begingroup$ The package is supposed to be "for Mathematica version 6 or later" but I get a black surface with no visible text when I try it in v7 under Windows. Any idea what I might be doing wrong? $\endgroup$ – Mr.Wizard Jul 8 '14 at 20:06
  • $\begingroup$ @Mr.Wizard Sorry, I haven't used this package since this answer ... I don't know. $\endgroup$ – Szabolcs Jul 8 '14 at 20:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.