First of all, there is no "layering" mechanism in the rendering engine; see this feature request. So if you want to simulate one (e.g., to make visible a path that is behind an opaque object), you have two options:
- Give up hidden object removal by setting
settings.render = 0;
. This doesn't help your cause unless you use something like the surfacepaths
module from this answer or this answer to actually compute the parts of the path in front of and behind the object. (This module is incomplete and may or may not work for your actual use case. It also draws hidden paths as faded, which I have found typically looks better than dashed. Perhaps later I'll see if I can add something to this answer showing a modified version that allows for dashed paths.)
- Simulate layering by moving some objects closer to (or farther away from) the camera. This is generally the preferred solution, but it is harder to make it work for a perspective projection than for an orthographic projection.
Paolo's solution is a clever variant of 2 based on the observation that a dashed curve drawn over a solid curve looks exactly like a solid curve. The mathematical estimates are unnecessary; for orthographic projections, shift(n * unit(currentprojection.camera))
is precisely the transformation required to move an object n
units toward the camera. Making n
too big is unlikely to be a problem, so you can just use something like n=1000
(or start with n=1
and double it until the picture looks right).
Dealing with perspective projections is, unavoidably, an issue--especially since lines closer to the camera will actually appear thicker. Here's the best I can do at the moment:
import graph3;
size(6cm,0);
settings.render = 4;
currentprojection=perspective(5*(4, 1, 1), showtarget=true, autoadjust=false, center=true);
// UTILITY FUNCTIONS
triple cameradirection(triple pt, projection P=currentprojection) {
if (P.infinity) {
return unit(P.camera);
} else {
return unit(P.camera - pt);
}
}
triple towardcamera(triple pt, real distance=1, projection P=currentprojection) {
return pt + distance * cameradirection(pt, P);
}
path3 towardcamera(path3 g, real distance=1, projection P=currentprojection) {
triple f(real t) {
return towardcamera(point(g,t), distance, P);
}
return graph(f, 0, length(g), operator..);
}
pen shortdashed = linetype(new real[] {4, 4});
// SURFACE
triple S(pair uv) {
real x = cos(uv.x)*sin(uv.y);
real y = sin(uv.x)*sin(uv.y);
real z = cos(uv.y);
return (x-(1-z**2-y**2), y-0.3*sin(z*pi), z);
}
// BOUNDARY
triple dS(real u) {
return S((u, pi/2));
}
// GRAPHICAL ELEMENTS
surface s = surface(S, (0, 0), (2pi, pi/2), 64, 64, Spline);
path3 ds = graph(dS, 0, 2pi);
draw(s, surfacepen=material(diffusepen=gray(0.6),
emissivepen=gray(0.3),
specularpen=gray(0.1)));
// Draw boundary.
draw(ds);
// Draw dashed version of boundary out in front. As long as it's exactly the
// same color and width as the solid path, the dashing pattern will only show up
// when the solid path isn't behind it.
//draw(shift(4*unit(currentprojection.camera))*ds, dashed);
draw(towardcamera(ds, 4), linetype(new real[] {4, 4}));
The result:
It's not ideal in a couple ways. First, there is gratuitous whitespace in the edge of the picture. (This can be fixed by removing the parameter autoadjust=false
, but that also messes up the dashed path.) Second, the dashed lines, being closer to the camera, are thicker than the solid lines. This can be mitigated by moving the camera farther away--but if you're going to do that, you're basically just trying to get the perspective projection to behave like an orthographic projection; why not just use orthographic projection in the first place?