# On coloring the faces of a surface differently with parameter-dependent colors

Old versions of Mathematica (\$VersionNumber < 6) supported a particular syntax of ParametricPlot3D[] that allowed for graphics specifications to be supplied as a fourth component of a vector-valued function; e.g. ParametricPlot3D[{f[u, v], g[u, v], h[u, v], Hue[w[u, v]]}, {u, umin, umax}, {v, vmin, vmax}].

Nowadays, this functionality has been superseded by the options ColorFunction, PlotStyle, and other related functions. Unfortunately, some flexibility seems to have been lost with these changes.

For instance, the old implementation of ParametricPlot3D[] allowed me to use FaceForm[] to color the two faces of a polygon differently, with the stuff within FaceForm[] depending on the parameters. This allowed me to do things like this:

<<Version5Graphics (* simulates old-school graphics *)

ParametricPlot3D[{(3 + Cos[u]) Cos[v], (3 + Cos[u]) Sin[v], Sin[u],
{EdgeForm[], FaceForm[ColorData["DarkRainbow"][Rescale[v, {0, Pi/2}]],
ColorData["BrightBands"][Rescale[v, {0, Pi/2}]]]}},
{u, -7 Pi/6, 7 Pi/12}, {v, Pi/6, Pi/2},
Axes -> False, Boxed -> False, Lighting -> False, PlotPoints -> 41]


Unfortunately, as can be seen through close inspection, I miss out on the adaptive plotting capability that is provided by the current implementation of ParametricPlot3D[].

I know that in the current version, the output is a GraphicsComplex[] object, with the colors assigned through the option VertexColors, which doesn't interact at all with directives like FaceForm[]. It has been suggested to me that I do two plots with the different colorings, with one a perturbed version of the other, and then combining these two into one Graphics3D[] object. This seems rather wasteful to me, however.

In short: how can I do parameter-dependent coloring of a surface plotted with ParametricPlot3D[], with the faces colored differently?

Both of the answers given so far rely on the fact that color gradients in Mathematica can be turned into images that can subsequently be used as textures. Unfortunately, this doesn't seem to be applicable to general coloring schemes. Consider for instance the following twisted cylinders:

<<Version6Graphics (* restore default graphics *)

With[{a = 1, p = 2/3},
GraphicsGrid[{
{ParametricPlot3D[{a Cos[u] Cos[p Pi v] - a Sin[u] Sin[p Pi v],
a Cos[p Pi v] Sin[u] + a Cos[u] Sin[p Pi v], v},
{u, 0, 3 Pi/2}, {v, -1, 1}, Axes -> None, Boxed -> False,
ColorFunction -> (Hue[(1 - Cos[3 Pi #5] Sin[4 #4])/2] &),
ColorFunctionScaling -> False, Mesh -> False, PlotPoints -> 85],

ParametricPlot3D[{a Cos[u] Cos[p Pi v] - a Sin[u] Sin[p Pi v],
a Cos[p Pi v] Sin[u] + a Cos[u] Sin[p Pi v], v},
{u, 0, 3 Pi/2}, {v, -1, 1}, Axes -> None, Boxed -> False,
ColorFunction -> (Hue[(1 + Cos[3 #4] Cos[4 Pi #5])/2] &),
ColorFunctionScaling -> False, Mesh -> False, PlotPoints -> 85]}
}]]


where the cylinder on the left is colored with Hue[(1 - Cos[3 Pi v] Sin[4 u])/2 and the one on the right is colored with Hue[(1 + Cos[3 u] Cos[4 Pi v])/2]. I want to be able to use something like FaceForm[Hue[(1 + Cos[3 u] Cos[4 Pi v])/2], Hue[(1 - Cos[3 Pi v] Sin[4 u])/2]], as with the following image:

<< Version5Graphics

With[{a = 1, p = 2/3},
ParametricPlot3D[{a Cos[u] Cos[p Pi v] - a Sin[u] Sin[p Pi v],
a Cos[p Pi v] Sin[u] + a Cos[u] Sin[p Pi v], v, {EdgeForm[],
FaceForm[Hue[(1 + Cos[3 u] Cos[4 Pi v])/2],
Hue[(1 - Cos[3 Pi v] Sin[4 u])/2]]}},
{u, 0, 3 Pi/2}, {v, -1, 1},
Axes -> None, Boxed -> False, Lighting -> False, PlotPoints -> 85]]


and it looks to me that the current solutions don't apply to this situation. What can be done here?

-
I love the questions people are coming up with. J. M. I am curious: did you feel this question was inappropriate for SO? –  Mr.Wizard Jan 20 '12 at 12:50
I thought it was too simple, since it was simple to do in old versions of Mathematica... –  Ｊ. Ｍ. Jan 20 '12 at 13:43
Concerning the addition, Vitaliy's second solution would work, i.e. plotting the function twice and overlaying them. Just use a different a ColorFunction for each plot. –  Heike Jan 21 '12 at 22:24
Still seems like a bit of a waste of effort to me, @Heike. The actual functions I'm dealing with can take a long time to evaluate... –  Ｊ. Ｍ. Jan 21 '12 at 23:36

My friend C.H. enlightened me, that in current version of M. ColorFunction defines vertex colors which in turn define polygon colors. Because vertexes cannot have 2 different colors for different sides of a surface, so can’t polygons.

I will show two solutions.

So here is one solution. We can extract images from ColorData:

and just use textures - if you want to call ParametricPlot3D only once. You can easily map different textures on different sides of a surface, use Specularity and Opacity with them.

ParametricPlot3D[{Cos[u] (3 + Cos[v]), Sin[u] (3 + Cos[v]),
Sin[v]}, {u, 0, 1.5}, {v, -3.5, 2},
TextureCoordinateFunction -> ({#4, #5} &),
PlotStyle ->
Directive[Specularity[White, 50],
FaceForm[Texture[ColorData["BrightBands", "Image"]],
Texture[ColorData["DarkRainbow", "Image"]]]], Axes -> False,
Lighting -> "Neutral", Mesh -> None, Boxed -> False]


Another solution, as you mentioned, is to put 2 surfaces together. I really like it, it's light and zippy. I will mention it here for completeness of example. In M. we can make surface (its polygons) to be transparent if you look from one side and colored from the other with help of FaceForm[None , {}]. Clearly demonstrated below with Möbius strip:

ParametricPlot3D[{Cos[t] (3 + r Cos[t/2]), Sin[t] (3 + r Cos[t/2]),
r Sin[t/2]}, {r, -1.5, 1.5}, {t, 0, 2 Pi}, Mesh -> {10, 60},
PlotStyle -> FaceForm[None, Orange], Boxed -> False]


You can also do things like PlotStyle -> FaceForm[None, Directive[Orange, Opacity[.5]]] We will uses this. Each graphics one surface’s side is effectively turned off, so shifting the two surfaces with respect to each other is not needed.

Show[
ParametricPlot3D[{(2 + Cos[v]) Cos[u], (2 + Cos[v]) Sin[u],
Sin[v]}, {v, -Pi/1, Pi/1.5}, {u, 0, Pi/1.5}, Boxed -> False,
Axes -> False, Mesh -> False,
ColorFunction -> (ColorData["DarkRainbow"][#5] &),
PlotPoints -> 30,
PlotStyle -> FaceForm[{}, None] ],

ParametricPlot3D[{(2 + Cos[v]) Cos[u], (2 + Cos[v]) Sin[u],
Sin[v]}, {v, -Pi/1, Pi/1.5}, {u, 0, Pi/1.5}, Boxed -> False,
Axes -> False, Mesh -> False,
ColorFunction -> (ColorData["BrightBands"][#5] &),
PlotPoints -> 30,
PlotStyle -> FaceForm[None, {}] ]
]


======================== Reply to 1st Comment ========================

Trimming anyhow color maps will work:

ParametricPlot3D[{Cos[u] (3 + Cos[v]), Sin[u] (3 + Cos[v]),
Sin[v]}, {u, 0, 1.5}, {v, -3.5, 2},
TextureCoordinateFunction -> ({#4, #5} &),
PlotStyle ->
Directive[Specularity[White, 50],
FaceForm[
Texture[ImageTake[
ColorData["BrightBands", "Image"], {0, 31}, {85, 250}]],
Texture[ImageTake[
ColorData["DarkRainbow", "Image"], {0, 31}, {85, 250}]]]],
Axes -> False, Lighting -> "Neutral", Mesh -> None, Boxed -> False]


-
In your first snippet, I note that you mapped the entire gradient set on different faces. But, what if I only wanted a section of the gradient, like in the image in my question? –  Ｊ. Ｍ. Jan 21 '12 at 1:02
@J.M. I posted a reply at the end of my answer. –  Vitaliy Kaurov Jan 21 '12 at 1:19
I managed to figure out my own solution. :) Thanks for the starting idea! –  Ｊ. Ｍ. Jan 21 '12 at 1:43
It is really not intuitive that a polygon can be transparent from one side and opaque from the other. This was the highlight of the answer for me. You should make this point a lot more visible in the long answer! –  Szabolcs Jan 24 '12 at 18:39
I edited your answer to make it easier to skim through, and still notice the important points. Please review the edit and make sure I didn't change anything you don't want changed! –  Szabolcs Jan 24 '12 at 18:45

I managed to answer my own clarificatory question to Vitaliy. The idea, in addition to giving ColorData[] as a Texture[] (not too straightforward for me, but whatever) is to modify the options TextureCoordinateFunction and TextureCoordinateScaling.

For the example I gave in my question, the old syntax would have looked like

ParametricPlot3D[{(3 + Cos[u])*Cos[v], (3 + Cos[u])*Sin[v], Sin[u],
{EdgeForm[], FaceForm[ColorData["BrightBands"][Rescale[v, {0, Pi/2}]],
ColorData["DarkRainbow"][Rescale[v, {0, Pi/2}]]]}},
{u, -7*(Pi/6), 7*(Pi/12)}, {v, Pi/6, Pi/2}]


Here's how to use Vitaliy's idea along with modifications to TextureCoordinateFunction and TextureCoordinateScaling:

ParametricPlot3D[{(3 + Cos[u])*Cos[v], (3 + Cos[u])*Sin[v], Sin[u]},
{u, -7*(Pi/6), 7*(Pi/12)}, {v, Pi/6, Pi/2}, Mesh -> False,
PlotStyle -> FaceForm[Texture[ColorData["DarkRainbow", "Image"]],
Texture[ColorData["BrightBands", "Image"]]],
TextureCoordinateFunction -> ({Rescale[#5, {0, Pi/2}], #4} & ),
TextureCoordinateScaling -> {True, True, True, True, False}]


Compare:

-

This solution is basically a combination of Vitaliy's solutions. It uses two plots plotted on top of each other using Show but the 3D surface is calculated for only once so it shouldn't take much longer than using FaceForm[] directives as in older versions of Mathematica.

First we plot two 2D images of the pattern we want to use for the fronts and back of the final plot. Suppose that the ColorFunction specifications we want to use are

colf1[u_, v_] := GrayLevel[(1 - Cos[3 Pi v] Sin[4 u])/2];
colf2[u_, v_] := Hue[(1 + Cos[3 u] Cos[4 Pi v])/2];


To create the patterns we use RegionPlot

{tex1, tex2} = Image[RegionPlot[True, {u, 0, 3 Pi/2}, {v, -1, 1},
ColorFunction -> Function[{u, v}, #[u, v]],
ColorFunctionScaling -> False,
Frame -> False, PlotPoints -> 80,
BoundaryStyle -> None]] & /@ {colf1, colf2}


At this point I tried using one of Vitaliy's solutions and plot the 3D surface with TextureCoordinateFunction -> ({#4, #5} &) and PlotStyle -> Directive[FaceForm[Texture[tex1], Texture[tex2]]]. This did produce a plot in the end but it took a lot of time and memory, so instead we're using separate plots for the front and back. First the front:

front = With[{a = 1, p = 2/3},
ParametricPlot3D[{a Cos[u] Cos[p Pi v] - a Sin[u] Sin[p Pi v],
a Cos[p Pi v] Sin[u] + a Cos[u] Sin[p Pi v], v},
{u, 0, 3 Pi/2}, {v, -1, 1},
Axes -> None, Boxed -> False, Mesh -> False,
PlotPoints -> 15,
Lighting -> "Neutral",
TextureCoordinateFunction -> ({#4, #5} &),
PlotStyle -> Directive[Texture[tex1], FaceForm[{}, None]]
]];


Instead of plotting the same function again for the back, we're using replacement rules to replace the Texture and FaceForm of the front

back = front /.
{Texture[a_] :> Texture[tex2], FaceForm[{}, None] :> FaceForm[None, {}]};
Show[front, back]


which gives the following result

The advantage of using Texture over ColorFunction is that we can make do with much fewer PlotPoints for the 3D image when we use Texture (15 vs 80 in this case) to still have a satisfying result, so although we are still plotting every polygon twice, the total number of polygons in the end result is still less than if we had used the pre version 6 method.

The disadvantage of this method is that it only works for patterns that depend on the free parameters of the surface only and not on the spacial coordinates.

-