7

I'd like to create a 3D label in Asymptote that has a "background," effectively giving the label the look as though it were printed on poster board. I don't care if this background conforms to the Billboard effect when the 3D image is rotated; my only need here is for a static picture. I need the label to stand out against the surface behind it.

I know I could fake it by placing a white plane behind my label, but I don't know how to form this plane properly - how to correctly find the normal of the plane so that it looks rectangular from the viewpoint.

Here's a MWE, wherein the text of the label gets a little "lost" in the lines of the surface behind it.

import graph3;
size(200,200,IgnoreAspect);
currentprojection=orthographic(5,5,1);
defaultrender.merge=true;

triple f(pair t) {
    return (t.y*cos(t.x),2*t.y*sin(t.x),t.y);//
}
surface s=surface(f,(0,0),(2*pi,2),32,32,Spline);
draw(s,emissive(white),meshpen=black+.2mm);

label("plot of $x$",(2,2,1.5));

I use TikZ for some of my graphics, and there it is a simple manner to have the node drawn with a solid background color making the text easy to read. I am basically looking for the same effect here.

  • Please provide MWE of the scene with labels for which you'd like to add a background. – g.kov May 13 '15 at 22:22
  • I once spent some hours trying to figure out how to do this. I'm sure it's possible, but in the end I just positioned the white plane by trial and error. In so doing, I discovered that the effect was not nearly as nice as I had hoped--for some reason, this effect just does not look good in 3d. – Charles Staats May 14 '15 at 5:35
  • 1
    That having been said, it would still be good to have a "right" solution, even if the effect is much less useful than one might assume. – Charles Staats May 14 '15 at 5:37
  • @g.kov I have added a MWE for you. – GregH May 14 '15 at 10:36
  • @CharlesStaats If you have tried it, and were not so successful, I probably won't spend much more time on it. Manually adjusting corners of a box in 3D seems like a big pain... if I really need the effect, I might go that route. – GregH May 14 '15 at 10:41
1

The following solution is slightly hacky, but much less draconian than drawing on top of the picture. It creates two labels: one with the text, and one, positioned directly behind the text (relative to the camera), with a solid box the same size as the text. This solution will only work if the projection is orthographic.

settings.outformat="png";
settings.render=4;
import graph3;
usepackage("color");
size(200,200,IgnoreAspect);
currentprojection=orthographic(5,5,1);

// Decreasing the tubegranularity makes the gridlines look nicer.
defaultrender.tubegranularity = 1e-3;

triple forward = -unit(currentprojection.camera);
// Take the component of currentprojection.up orthogonal to forward.
triple upvec = unit(currentprojection.up - dot(currentprojection.up,forward) * forward);
triple rightvec = cross(forward, upvec);

triple f(pair t) {
    return (t.y*cos(t.x),2*t.y*sin(t.x),t.y);
}
surface s=surface(f,(0,0),(2*pi,2),32,32,Spline);
draw(s,emissive(white),meshpen=black+.2mm);

label("plot of $x$", (2,2,1.5));
label("\colorbox{black}{plot of $x$}", (2,2,1.5) + .01 forward, white+opacity(0.9));

cone labeled plot of x

  • Nice. I was able to make something near this, but didn't know that I could add "white" to the end of the label to turn the black box from the \colorbox{}{} white. I'm continually amazed by the number of options/settings that exist; I knew nothing of tube.granularity. Finally, just to be clear, you might remove upvec and rightvec from your answer as you don't actually use them. – GregH May 20 '15 at 10:19
  • I only just found out about tube.granularity myself--see sourceforge.net/p/asymptote/bugs/191/#e4f7 – Charles Staats May 20 '15 at 16:20
  • Concerning upvec and rightvec: They will come in handy if you want to move anything in the left/right or up/down directions. But you are correct that I did not end up using them here. – Charles Staats May 20 '15 at 16:21
  • Nice thinking ahead ... – GregH May 21 '15 at 10:20
4

Following the answer from asymptote discussion,

//
// bglabel3d.asy
//
settings.tex="pdflatex";
settings.prc=false;
settings.render=0;

import graph3;
size(200,200,IgnoreAspect);
currentprojection=orthographic(5,5,1);
defaultrender.merge=true;

triple f(pair t) { return (t.y*cos(t.x),2*t.y*sin(t.x),t.y); }

surface s=surface(f,(0,0),(2*pi,2),32,32,Spline);
draw(s,emissive(white),meshpen=deepblue+.2mm);

label(Label("plot of $x$",Fill(orange+opacity(0.8))),(2,2,1.5));

enter image description here

Edit:

As it is noted in the linked answer, this solution works only with render=0.

For raster images we can draw the labels on the image:

First, make a label-free img.png:

//
// scene3d.asy
//
settings.outformat="png";
settings.prc=false;
settings.render=4;
import graph3;
size(200,200,IgnoreAspect);
currentprojection=orthographic(5,5,1);
defaultrender.merge=true;

triple f(pair t) { return (t.y*cos(t.x),2*t.y*sin(t.x),t.y); }

surface s=surface(f,(0,0),(2*pi,2),32,32,Spline);

pen bpen=rgb(0.75, 0.7, 0.1);
material m=material(diffusepen=0.6bpen
  ,ambientpen=bpen
  ,emissivepen=0.4*bpen
  ,specularpen=0.8white
  ,shininess=1.0);
draw(s,m);
shipout("img");

next, draw the labels on the raster image:

settings.tex="pdflatex";
import graph;
import math; // for grid() function

defaultpen(fontsize(10pt));

real sc=1/4;
unitsize(sc*1bp);

real wd=796*sc;
real ht=808*sc;

label(
  shift(wd/2,ht/2)*
  graphic("img.png"
  ,"width="+string(wd)+"bp"
  +",height="+string(ht)+"bp"
  +",scale="+string(sc)
  ),(0,0)
);
layer();

draw(((0,0)--(wd,ht)/sc),blue+2pt);

int ngrid=100;
int n=(int)(wd/(ngrid-1)/sc);
int m=(int)(ht/(ngrid-1)/sc);
add(scale(ngrid)*grid(n,m,yellow));

xaxis( 0,wd/sc,RightTicks(Step=ngrid));
yaxis(0,ht/sc,LeftTicks(Step=ngrid));


label(Label("plot of $y$"
    ,Fill(1mm,orange+opacity(0.8))),(400,400)); 

enter image description here

  • Nice. I didn't know about nesting label and Label commands. It is interesting that adding extra space to the text of the label doesn't seem to affect the size of the colored box. Adding \phantom{xx}, for instance, does nothing. – GregH May 14 '15 at 17:10
  • @GregH: Yes, the label is trimmed to fit the actual ink, but we can use a trick with microscopic dot: \rule{1sp}{1sp}\phantom{xx}plot of $x$. Also, the first parameter to Fill can be used to enlarge the bounding box, for example, Fill(5mm,orange+opacity(0.8))). – g.kov May 14 '15 at 18:47
  • This is great, but it does not seem to work when settings.render > 0. – Charles Staats May 15 '15 at 2:10
  • I realized that after marking the solution as "the" answer. While I like the vector based graphics of settings.render=0, it doesn't seem to handle multiple objects well. In the cone above, it works fine. For most of my pictures it doesn't. So I still want to see a solution that works for any render setting. – GregH May 15 '15 at 10:38

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.