15
$\begingroup$

I am trying to create a simple, almost transparent 3D sphere with a mesh that is made of solid lines if they are on the front surface (i.e. if they are directly visible) and of dashed lines if those lines happen to be in the back of the sphere (i.e. not directly visible).

I cannot think of an easy way of doing it without involving an explicit calculation of what part of the sphere is directly visible, given the viewpoint, and what part is not... Any ideas?

$\endgroup$
1
  • $\begingroup$ A closely related question is here $\endgroup$
    – Jens
    Mar 9, 2013 at 23:08

4 Answers 4

10
$\begingroup$

Based on this answer and kguler's suggestion, I came up with this. For some reason (1) it takes a long time, (2) the mesh is not smooth (enough, for me), and (3) there's some mesh interpolation craziness on the "horizon", i.e., the boundary between front and back. For (2), I increased PlotPoints. For (3), it looks fine, until you rotate the plot, which you shouldn't do anyway: It's computed for a particular viewpoint.

We compute separate meshes for front and back. sidedMesh returns values of the parameter passed to it if the point is on the desired side; otherwise it returns a value out of range of the parameter, which will prevent the mesh from extending into the other side.

The basis for the sidedMesh function is parametricFaceCosine, which returns the CosineDistance of the surface normal and the vector from the view point to the point on the graph. This is less than 1 when the normal point toward the side the viewpoint is on, that is, when the surface faces forward; and it is greater than 1 on the back. (This depends on the parametrization, so you either have to calculate or experiment.) Note this does not take account of whether there are other parts of the surface between the viewpoint and a point on the surface; thus it should work well from all viewpoints only for convex surfaces. One unsatisfactory issue with sidedMesh is what to return when the surface is on the wrong side. I've wavered between $MaxMachineNumber for generality and 2. π for this particular problem. The documentation says that normally mesh functions should be continuous and monotonic, and this one isn't.

Mathematica's mesh routines are clever. If you say you want 9 lines, it will figure out how to do it, even if the range allowed by the mesh function is restricted; it will just cram more in. So it's best in this case to give explicit values for Mesh. Also, since the boundary cannot be split into dashed and solid parts, I offset the mesh lines, so that they appear evenly spaced.

  (* f      - function
     {u, v} - variables of f
     viewPoint - ViewPoint of the plot *)
parametricFaceCosine[f_, {u_, v_}, viewPoint_] := 
  Function[{xx, yy, zz, uu, vv}, 
   CosineDistance[D[f, u]\[Cross]D[f, v] /. {u -> uu, v -> vv},
    {xx - viewPoint[[1]], yy - viewPoint[[2]], zz - viewPoint[[3]]}]];
sidedMesh[cmp : Greater | Less, cosDist_] := (* violates recommendation that the mesh functions be continuous *)
 If[cmp[cosDist, 1], #, $MaxMachineNumber; 2. Pi] &; 

  (* f, {u, v}, viewPoint - as above
     args   - the arguments {x0, y0, z0, u0, v0} passed to the mesh function
     param  - the value to be return if {x0, y0, z0} is on the desired side *)
frontMesh[f_, {u_, v_}, viewPoint_, {args__}, param_] := (* front <-> Less for our f *)
  sidedMesh[Less, parametricFaceCosine[f, {u, v}, viewPoint][args]][param];
backMesh[f_, {u_, v_}, viewPoint_, {args__}, param_] := (* back <-> Greater for our f *)
  sidedMesh[Greater, parametricFaceCosine[f, {u, v}, viewPoint][args]][param];

meshRange[a_, b_, n_] := Range[a + (b - a)/(2 n), b, (b - a)/n];

f = {Cos[u] Sin[v], Sin[u] Sin[v], Cos[v]};

viewVec = {{1.3, -2.4, 2}, {0, 0, 0}};
viewVert = {0, 0, 1};
plotSphere[] := 
  ParametricPlot3D[f, {u, 0, 2 Pi}, {v, 0, Pi}, PlotPoints -> 50, 
   PlotStyle -> Directive[Opacity[0.3]], BoundaryStyle -> None, 
   Mesh -> {meshRange[0, 2 Pi, 16], meshRange[0, 2 Pi, 16], 
     meshRange[0, Pi, 12], meshRange[0, Pi, 12]}, 
   MeshFunctions -> 
    With[{vp = viewVec[[1]]}, {frontMesh[f, {u, v}, vp, {##}, #4] &, 
      backMesh[f, {u, v}, vp, {##}, #4] &, 
      frontMesh[f, {u, v}, vp, {##}, #5] &, 
      backMesh[f, {u, v}, vp, {##}, #5] &}], 
   MeshStyle -> {Black, Dashed},
   Axes -> None, Boxed -> False,
   ViewVector -> viewVec, ViewVertical -> viewVert, SphericalRegion -> True
   ];

plotSphere[]

Sphere with dashed mesh behind

In the previous answer, there was a control to set the viewVec. Here a version of it, in case you'd like to use it:

controlPlot = 
  ParametricPlot3D[f, {u, 0, 2 Pi}, {v, 0, Pi}, 
   Mesh -> {meshRange[0, 2 Pi, 16], meshRange[0, Pi, 12]}, 
   PlotStyle -> Directive[Opacity[0.5]], BoundaryStyle -> None, Axes -> False
   ];
DynamicModule[{viewVec0, viewVert0},
 viewVec0 = {{1.3, -2.4, 2}, {0, 0, 0}};
 viewVert0 = {0, 0, 1};
 Column[{
   Show[controlPlot,(* rotation control *)
     ViewVector -> Dynamic[viewVec0], ViewVertical -> Dynamic[viewVert0], 
     PlotLabel -> "Drag to desired viewpoint"],
   Button["set viewpoint",
     viewVec = viewVec0; viewVert = viewVert0]
   }], SaveDefinitions -> True]

ViewVector control

Here you can see the problem with the horizon. It is the sphere above rotated around. With fewer PlotPoints, it is much worse. On the right is a plot of the point density of the point generated in the plot. Not surprisingly, many more occur along the boundary between front and back (and of course at the poles).

Rotate sphere shows boundary problem Point density of plot of sphere

That it takes a long time is perhaps connected with the discontinuity of the meshes.

$\endgroup$
4
  • $\begingroup$ Sweet! I wonder what calculation is taking the most time... Is there a way of parallelizing it? $\endgroup$
    – Ziofil
    Mar 13, 2013 at 20:50
  • $\begingroup$ Almost certainly it is an adaptive algorithm that assumes the mesh functions are continuous. I doubt it can be parallelized. (Parallelize doesn't do it.) There may be (undocumented) ways to control it. Alternatively, one can use the math implicit above to construct the solid and dashed lines directly, although it might take some time to get it just right. It would be very fast, though. $\endgroup$
    – Michael E2
    Mar 13, 2013 at 21:22
  • $\begingroup$ I can't believe this... The irony is that exporting to pdf turns dashed lines into solid(ish) lines! : / $\endgroup$
    – Ziofil
    Mar 20, 2013 at 2:55
  • $\begingroup$ @Ziofil I get similar. Changing Dashed to Dashing[{0.005, 0.01}] helped a lot, but not perfect. I get normal dashing with Opacity[0.5] but edges of polygons show up. See the answers to this question and this one - lots of good information. $\endgroup$
    – Michael E2
    Mar 20, 2013 at 10:44
11
$\begingroup$

I know you really wanted dashed lines, but if you just wanted some contrast for the 'hidden' lines:

SphericalPlot3D[{1, 1}, {ϴ, 0, Pi}, {Φ, 0, 2 Pi}, 
   PlotStyle -> {{FaceForm[None], EdgeForm[None]}, 
                 {Opacity[0.9], FaceForm[White], EdgeForm[None]}},
   Mesh -> 20, 
   Boxed -> False, 
   Axes -> None, 
   Lighting -> "Neutral"]

enter image description here

$\endgroup$
1
  • $\begingroup$ +1 SphericalPlot3D a good choice. $\endgroup$
    – DavidC
    Mar 9, 2013 at 22:56
11
$\begingroup$

ClipPlanes is useful here

DynamicModule[{vp={1.3,-2.4,2},latitude,longitude},
latitude=Line@Table[{Sin[ϕ]Cos[θ],Sin[ϕ]Sin[θ],Cos[ϕ]},{ϕ,0.,Pi,Pi/10},{θ,0.,2Pi,2Pi/60}];
longitude=Line@Table[{Cos[ϕ]Sin[θ],Sin[ϕ]Sin[θ],Cos[θ]},{ϕ,0.,Pi,Pi/10},{θ,0.,2Pi,2Pi/60}];
Graphics3D[{
(*{Opacity[0.2],Sphere[]},*)
AbsoluteThickness[1],
{ClipPlanes->Dynamic@Append[vp,0],latitude,longitude},
{Dashed,GrayLevel[0.6],ClipPlanes->Dynamic@Append[-vp,0],latitude,longitude}
},ViewPoint->Dynamic@vp,BoxStyle->Opacity[0],ImageSize->Large,SphericalRegion->True
]
]

enter image description here

$\endgroup$
1
  • 1
    $\begingroup$ Pretty slick! ClipPlanes didn't exist when this question was first asked, but it's always nice to see old questions updated with answers involving new features. $\endgroup$ Oct 14, 2021 at 12:37
6
$\begingroup$

You could join two separate hemispheres, each with its own MeshStyle.

You may have to tweak the ViewPoint or the angles of the plot in order to hide the back mesh from view. In the case below, I simply moved the ViewPoint to {1, -10, 0}.

h1 = ParametricPlot3D[{Cos[u] Sin[v], Sin[u] Sin[v], Cos[v]}, {u, 0, Pi}, {v, 0, Pi}, 
MeshStyle -> Dashed, PlotRange -> {-1, 1},  PlotStyle -> Opacity[.1]]

h2 = ParametricPlot3D[{Cos[u] Sin[v], Sin[u] Sin[v], Cos[v]}, {u, Pi, 2 Pi}, {v, 0, Pi}, 
MeshStyle -> Directive[{Thick, Gray}], PlotStyle -> Opacity[.1]]

Show[h1, h2, ViewPoint -> {1, -10, 0}]

Mathematica graphics

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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