20
$\begingroup$

What are effective techniques for making the depth of a 3D curve clear when it twists and turns, making its depth ambiguous from a static perspective?

enter image description here

points = Table[RandomReal[{0, 10}, 3], 25];
spline = BSplineFunction[points];
ParametricPlot3D[
  spline[t],
  {t, 0, 1},
  PlotRange -> {{0, 10}, {0, 10}, {0, 10}},
  Boxed -> False,
  AxesLabel -> {"u", "t", "s"},
  AxesOrigin -> {0, 0, 0},
  FaceGrids -> {{{0, 
      0, -1}, {{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, {0, 1, 2, 3, 4, 5, 
       6, 7, 8, 9, 10}}}},
  PlotStyle -> Directive[Black, Thin],
  ViewPoint -> {5/6, -1.5, 0},
  ImageSize -> Large,
  Ticks -> False,
  AxesStyle -> {Arrowheads[{{1/100, 1, arrow[]}}], Automatic}
  ] /. Line -> Arrow

Preferably keeping the presentation static, e.g. not animating it to spin, because then it would make the curve hard to trace and study by eye. Although, I remember seeing a technique where an image is animated to rotate back and forth just a small number of degrees—enough parallax to give the sense of depth—that might be fine, or at least interesting to see. But I'm more interested in general techniques—employing colors, lighting or shadows, opacity, and other properties I'm not imagining—that have been used in publications to address this exact issue.

$\endgroup$
3
  • 1
    $\begingroup$ The first thing I thought was to use a Tube[]. This creates a surface for the curve that reacts with the lighting to better, umm, "reflect" its 3D nature. Odd that nobody mentions this. Especially since so many answer uses, while emphasizing second-order effects. $\endgroup$
    – Michael E2
    Commented Apr 1 at 13:59
  • 1
    $\begingroup$ You might also want to try a 3D stereogram: plot two 3D graphs side by side, but with slightly different points of view. Then stare at the space between the graphs until your brain fuses the two images to create a 3D object. $\endgroup$
    – Peltio
    Commented Apr 1 at 19:33
  • $\begingroup$ draw a shadow on the u-t plane $\endgroup$ Commented Apr 1 at 23:08

8 Answers 8

19
$\begingroup$
  • Or colorize along the curve.
ColorFunction -> Function[{x, y, z, t}, Hue@t], PlotStyle -> 
 Directive[Black, Thin, Tube[.1]], ColorFunctionScaling -> True
points = Table[RandomReal[{0, 10}, 3], 25];
spline = BSplineFunction[points];
ParametricPlot3D[spline[t], {t, 0, 1}, 
 PlotRange -> {{0, 10}, {0, 10}, {0, 10}}, Boxed -> False, 
 AxesLabel -> {"u", "t", "s"}, AxesOrigin -> {0, 0, 0}, 
 FaceGrids -> {{{0, 
     0, -1}, {{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, {0, 1, 2, 3, 4, 5, 
      6, 7, 8, 9, 10}}}}, ViewPoint -> {5/6, -1.5, 0}, 
 ImageSize -> Large, Ticks -> False, 
 AxesStyle -> {Arrowheads[{{1/100, 1}}], Automatic}, 
 ColorFunction -> Function[{x, y, z, t}, Hue@t], 
 PlotStyle -> Directive[Black, Thin, Tube[.1]], 
 ColorFunctionScaling -> True]

enter image description here

  • inspire by @MelaGo, set Method -> {"OneLayer" -> {"Depth", 1}} as in the document of Graphics3D.
SeedRandom[4];
points = Table[RandomReal[{0, 10}, 3], 25];
spline = BSplineFunction[points];
g = ParametricPlot3D[spline[t], {t, 0, 1}, 
   PlotRange -> {{0, 10}, {0, 10}, {0, 10}}, Boxed -> False, 
   AxesLabel -> {"u", "t", "s"}, AxesOrigin -> {0, 0, 0}, 
   PlotStyle -> Black, 
   FaceGrids -> {{{0, 0, -1}, {Range[0, 10], Range[0, 10]}}}, 
   Ticks -> False];
Show[g, Method -> {"OneLayer" -> {"Depth", 1}}, Boxed -> False, 
 Axes -> True]

enter image description here

  • Using the ClipPlanes to clip some part of the curve along one direction in order to show the depth.
  • Due to the bug of 14.0, the ClipPlanesStyle does not work(only 13,12,11 version work)
points = Table[RandomReal[{0, 10}, 3], 25];
spline = BSplineFunction[points];
Manipulate[
 ParametricPlot3D[spline[t], {t, 0, 1}, 
  PlotRange -> {{0, 10}, {0, 10}, {0, 10}}, Boxed -> False, 
  AxesLabel -> {"u", "t", "s"}, AxesOrigin -> {0, 0, 0}, 
  FaceGrids -> {{{0, 
      0, -1}, {{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, {0, 1, 2, 3, 4, 5, 
       6, 7, 8, 9, 10}}}}, ViewPoint -> {5/6, -1.5, 0}, 
  Ticks -> False, AxesStyle -> {Arrowheads[{{1/100, 1}}], Automatic}, 
  ColorFunction -> Function[{x, y, z, t}, Hue@t], 
  PlotStyle -> Directive[Black, Thin, Tube[.1]], 
  ColorFunctionScaling -> True, ClipPlanes -> {0, -1, 0, d}, 
  ClipPlanesStyle ->{Directive[Opacity[.5], Gray]}, PerformanceGoal -> "Quality"], {d, 
  0, 10}]

enter image description here

$\endgroup$
3
  • $\begingroup$ Nice, that's what I was looking for. Good to learn about that Method option. $\endgroup$
    – MelaGo
    Commented Apr 1 at 15:20
  • $\begingroup$ Whoa. The ClipPlanes idea solves my need better than I dreamed of—this is why I love SE and this community. I was trying to draw the path of a particle that travels in a curve in time, i.e. may move forward in time as well as curve and move backwards in time. This would have the effect of the particle appearing to exist at multiple locations at one time. A ClipPlanes animation like you showed illustrates that remarkably well; there would appear to be 2 disjoint curves at first, but the animation would reveal that they meet at the time the particle turns around. Thanks! $\endgroup$ Commented Apr 2 at 12:15
  • $\begingroup$ This gives me the side-idea to try ClipPlanesing in both directions, i.e. showing the points at the cross-section only, although that doesn't exact match as an answer to this question. Anyway, if I get to it first, I'll edit it into my answer. $\endgroup$ Commented Apr 2 at 12:19
17
$\begingroup$

Try this for improved lighting and shading:

points = Table[RandomReal[{0, 10}, 3], 25];
spline = BSplineFunction[points];
ParametricPlot3D[spline[t], {t, 0, 1},
  PlotRange -> {{0, 10}, {0, 10}, {0, 10}},
  Boxed -> False,
  AxesLabel -> {"u", "t", "s"}, AxesOrigin -> {0, 0, 0}, 
  FaceGrids -> {{{0, 
      0, -1}, {{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, {0, 1, 2, 3, 4, 5, 
       6, 7, 8, 9, 10}}}}, 
  PlotStyle -> Directive[Orange, Specularity[White, 30]],
  ViewPoint -> {5/6, -1.5, 0}, ImageSize -> Large, Ticks -> False,
  Lighting -> "Neutral"
  ] /. Line -> (Tube[#, 0.1] &)

enter image description here

$\endgroup$
2
  • 2
    $\begingroup$ Note you can use PlotStyle -> Directive[Orange, Specularity[White, 30], Tube[0.1]] instead of the line replacement. $\endgroup$
    – Michael E2
    Commented Apr 1 at 13:59
  • $\begingroup$ Wow, thank you. I always wondered what the supposed way to do it would be! ^^ $\endgroup$ Commented Apr 1 at 14:39
11
$\begingroup$

Borrowing a page from visualization of protein structures, which often use "fog" as a depth cue (see example at the bottom):

With an x axis depth cue:

SeedRandom[4];
points = Table[RandomReal[{0, 10}, 3], 25];
spline = BSplineFunction[points];
ParametricPlot3D[spline[t], {t, 0, 1}, 
 PlotRange -> {{0, 10}, {0, 10}, {0, 10}}, Boxed -> False, 
 AxesLabel -> {"u", "t", "s"}, AxesOrigin -> {0, 0, 0}, 
 PlotStyle -> Black, 
 FaceGrids -> {{{0, 0, -1}, {Range[0, 10], Range[0, 10]}}}, 
 Ticks -> False, ColorFunction -> Function[{x, y, z, t}, Opacity[x]], 
 ColorFunctionScaling -> True]

enter image description here

Or alternatively a t dimension depth cue, with

ColorFunction -> Function[{x, y, z, t}, Opacity[t]]

enter image description here

Just for fun, here is a ribbon model of the glutamate receptor mGluR4 (PDB #7E9H). The fog depth cue is evident in the top view.

enter image description here

$\endgroup$
2
  • 2
    $\begingroup$ Protein visualization is a great example to draw from! Thank you for demonstrating what a fog depth cue would look like—I was curious myself. Unfortunately, I have to conclude for myself that a fog cue is not quite as strong as the material-light-and-shadow cue—for instance, looking at the left edge of your first example, it took me a few seconds to "bend" the shape in my mind to perceive that the faintest part was in the _back_—I guess the curves themselves can give illusions that oppose reality. $\endgroup$ Commented Apr 1 at 2:28
  • $\begingroup$ The second example, as I'm sure you already know, doesn't actually capture depth since it leaves depth as undecidable as before, though it does show something else—the parametrization direction. (Please do keep it as an educational example though—it was my own instinct to try that as well.) $\endgroup$ Commented Apr 1 at 2:30
10
$\begingroup$

You can explore the various MaterialShading and Lighting options.

SeedRandom[1];
points = Table[RandomReal[{0, 10}, 3], 25];
spline = BSplineFunction[points];

ParametricPlot3D[spline[t], {t, 0, 1},
 AxesLabel -> {"u", "t", "s"},
 AxesOrigin -> {0, 0, 0},
 Boxed -> False,
 FaceGrids -> {{{0, 0, -1}, {Range[0, 10], Range[0, 10]}}},
 Lighting -> "ThreePoint",
 PlotRange -> {0, 10},
 PlotStyle -> {{MaterialShading[{"Glazed", Red}], Tube[0.15]}},
 Ticks -> False,
 ViewPoint -> {5/6, -1.5, 0}]

enter image description here

$\endgroup$
1
  • 1
    $\begingroup$ Huh, thank you for introducing me to Lighting -> "ThreePoint". That looks really awesome! $\endgroup$ Commented Mar 31 at 23:35
9
$\begingroup$
points = Table[RandomReal[{0, 10}, 3], 25];
spline = BSplineFunction[points];
Manipulate[
 ParametricPlot3D[spline[t], {t, 0, 1}, 
  PlotRange -> {{0, 10}, {0, 10}, {-2, 10}}, Boxed -> False, 
  FaceGrids -> {{{0, 0, -1}, {Range[0, 10], Range[0, 10]}}}, 
  PlotStyle -> Directive[Black, Thin], Axes -> False, 
  PlotPoints -> 200, SphericalRegion -> True, 
  RotationAction -> "Clip", 
  ViewPoint -> 
   RotationMatrix[fi, 
     RotationMatrix[0.1, {1, 0, 0}] . {1.3, -2.4, 2.}] . {1.3, -2.4, 
     2.}], {fi, 0, 2 \[Pi]}]

enter image description here

Or if you prefer static image but still perceive 3D you can try stereograms. Click on the image to open it in full screen and then look like if through the screen until you see three copies of the curve and the one in the middle with 3D effect. Head must be kept straight vertical to have eyes in the same height.

enter image description here

enter image description here

$\endgroup$
8
$\begingroup$
SeedRandom[1];
points = RandomReal[{0, 10}, {25, 3}];
spline = BSplineFunction[points];

ptxy = points /. {x_, y_, z_} :> {x, y, 0};
splinexy = BSplineCurve[ptxy];
ptyz = points /. {x_, y_, z_} :> {0, y, z};
splineyz = BSplineCurve[ptyz];
ptxz = points /. {x_, y_, z_} :> {x, 0, z};
splinexz = BSplineCurve[ptxz];

p1 = ParametricPlot3D[{spline[t]}, {t, 0, 1}
   , PlotRange -> {{0, 10}, {0, 10}, {0, 10}}
   , Boxed -> False, AxesLabel -> {"u", "t", "s"}
   , AxesOrigin -> {0, 0, 0}
   , FaceGrids -> {
     {{0, 0, -1}, {Range[1, 10, 2], Range[1, 10, 2]}}
     , {{0, -1, 0}, {Range[1, 10, 2], Range[1, 10, 2]}}
     , {{-1, 0, 0}, {Range[1, 10, 2], Range[1, 10, 2]}}
     }
   , PlotStyle -> Directive[Dashed, Orange, Specularity[White, 30]]
   , ImageSize -> Large, Ticks -> False, Lighting -> "Neutral"];

g1 = Graphics3D[{
    AbsoluteThickness[4], BSplineCurve[points]
    , AbsoluteThickness[1]
    , Red, Point@ptxy, BSplineCurve[ptxy]
    , Blue, Point@ptxz, BSplineCurve[ptxz]
    , Green, Point@ptyz, BSplineCurve[ptyz]
    }
   ];

Show[p1, g1]

enter image description here

$\endgroup$
8
$\begingroup$

Using the option MeshFunction is another possibility :

points = Table[RandomReal[{0, 10}, 3], 25];
spline = BSplineFunction[points];

splineDerivative = D[spline[t], t];

arcLength = 
  NDSolveValue[{f'[t] == Norm[splineDerivative], f[0] == 0}, 
   f, {t, 0, 1}];

ParametricPlot3D[ spline[t], {t, 0, 1}, 
  PlotRange -> {{0, 10}, {0, 10}, {0, 10}}, Boxed -> False, 
  AxesLabel -> {"u", "t", "s"}, AxesOrigin -> {0, 0, 0}, 
  FaceGrids -> {{{0, 
      0, -1}, {{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, {0, 1, 2, 3, 4, 5, 
       6, 7, 8, 9, 10}}}}, ViewPoint -> {5/6, -1.5, 0}, 
  ImageSize -> Large, Ticks -> False, 
  AxesStyle -> {Arrowheads[{{1/100, 1}}], Automatic}
  , MeshFunctions -> {arcLength[#4] &}
  , Mesh -> 300 
  , MeshShading -> {None, Red}] /. Line[data__] :> {Tube[data, .2]}

enter image description here

It is necessary to create the function arcLength because the spline is not arc-length parametrised.

With MeshShading -> {None, Red, None, Blue, None, Yellow} it gives :

enter image description here

$\endgroup$
1
  • $\begingroup$ A nice sausage :-) $\endgroup$ Commented Apr 1 at 10:42
5
$\begingroup$

I appreciate the answers so far; I hope to see more. I've been playing around since I posted my question though—I'll post my findings in this answer.

Dashes

I noticed by accident that dashes happen to give a little bit of depth information. You can see dashes "bunch up" where they travel in exactly the depth direction—in and out of the screen. I know it's not a remarkable difference but, it might be good solution for someone looking for a light touch, without going into tubes and lighting.

enter image description here

More to come.

$\endgroup$

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