Drawing 3D graphics on top of an imported picture with asymptote

I am trying to draw stuff on top of an imported picture with asymptote, following this nice answer. Here is my MWE

     \documentclass{article}
\usepackage[inline]{asymptote}
\begin{document}
\begin{asy}
size(10cm);
import graph3;
import three;
import labelpath3;
import graph;
import math;

// it is not important which precise picture gets imported
defaultpen(fontsize(10pt));
real sc=2;
unitsize(sc*1bp);
real wd=120*sc;
real ht=80*sc;
label(
shift(wd/2,ht/2)*
graphic("earth.pdf"
,"width="+string(wd)+"bp"
+",height="+string(ht)+"bp"
+",scale="+string(sc)
),(0,0)
);
layer();

int ngrid=10;
int n=(int)(wd/ngrid/sc);
int m=(int)(ht/ngrid/sc);

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

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

triple f(real t) {
return (3*cos(0.125*2pi*t)+0.08*cos(2pi*t), 3*sin(0.125*2pi*t),0+ 0.08*sin(2pi*t));
}

path3 helix = graph(f, 0, 8, n=500, operator..);

surface helixtube = tube(helix, width=0.4).s;

draw(helixtube, surfacepen=material(blue+opacity(0.3), emissivepen=0.2*white));

real R=300;
real a=100;

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

draw(s,gray,render(compression=Low,merge=true));

// ---

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

\end{asy}
\end{document}
\endinput


It produces

As one can see, the 2-dimensional stuff, i.e. the grid and the blue line, really appears in front of the picture. However, the 3-dimensional stuff, i.e. the torus, does not. (No, of course I am not expecting asymptote to recognize that the imported stuff is also 3-dimensional.) I'd like to see the 3D stuff also in front of the imported picture. Is that possible?

(Of course, in the best of all worlds, somebody might figure out how to draw a 3D earth with e.g. Mathematicas GeoData in asymptote. Here I just want to put the 3D stuff in front of the background picture. It would be great if opacity would also work...)

• I think that it is not possible in a simple way and with one asymptote commands file. With asy -k (keep intermediate files) you can observe the construction of the picture. You can modify it so that the torus is "at the end". But the white color of the 3D is not transparent. You have to modify the "white" color of the 3D png output with convert => "white" becomes transparent... In my opinion you have to process the final picture with two intermediate pictures (the 2D and the 3D)...
– O.G.
Nov 9, 2017 at 12:55
• is tex.stackexchange.com/questions/244785/… related ?
– user4686
Nov 15, 2017 at 10:48
• @jfbu it might be related, but with what is discussed there or in the link I cannot solve my problem
– user121799
Nov 15, 2017 at 14:50
• Another something that might be related (while it doesn't have anythong to do with asymptote, it has something to do with pictures in front of 2d pictures) tex.stackexchange.com/questions/374998/… Nov 16, 2017 at 13:15
• @ThorbjørnE.K.Christensen Yes, this goes into the right direction, but ultimately I'd like to make use of the nice asymptote features such as opacity.
– user121799
Nov 16, 2017 at 20:12

5th Approach: Wrapping a world map sphere with surfaces

A combination of the 2nd and 4th approaches.

Output:

Code:

\documentclass{standalone}
\usepackage[inline]{asymptote}
\begin{document}
\begin{asy}
import math;
import three;
import graph3;
size(500);

//file fin=input("world.dat"); //1317 limit for the for loop
file fin=input("world_110m.dat"); //5254 limit for the for loop

real zenith  = pi/12.0;
real azimuth = pi/12.0;
currentprojection = perspective(cos(azimuth)*cos(zenith),
sin(azimuth)*cos(zenith),
sin(zenith));
//------------------------------------------------- TUBE
defaultpen(0.5mm);
pen darkgreen=rgb(0,138/255,122/255);

real R=3;
real a=0.75;

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

draw(s,surfacepen=material(gray+opacity(0.9), emissivepen=0.2*white),render(compression=Low,merge=true));

//-------------------------------------------------- RING
triple f(real t) {
return (3*cos(0.125*2pi*t)+0.08*cos(2pi*t), 3*sin(0.125*2pi*t),0+ 0.08*sin(2pi*t));
}
path3 helix = graph(f, 0, 8, n=500, operator..);

surface helixtube = tube(helix, width=0.5).s;

draw(helixtube, red);

//--------------------------------------------------- PLANET EARTH

real r = 1.25;
real ar = 1.2;

path3 myarc = Arc(c=O,normal=X, v1=-Z*r, v2=Z*r, n=24);
surface sphere = surface(myarc, angle1=0, angle2=360, c=O, axis=Z, n=24);
draw(sphere, surfacepen=material(royalblue+opacity(0.9),ambientpen=white));

real[][] a=fin.dimension(0,2);
a=transpose(a);

for(int i=0; i <=5254; ++i){
real u=a[0][i];
real v=a[1][i];
real U=a[0][i+1];
real V=a[1][i+1];
if (u==360 & v==360 | U==360 & V==360){}
else{
real x1=r*Cos(v)*Cos(u);
real y1=r*Cos(v)*Sin(u);
real z1=r*Sin(v);
real x2=r*Cos(V)*Cos(U);
real y2=r*Cos(V)*Sin(U);
real z2=r*Sin(V);
draw((x1,y1,z1)--(x2,y2,z2), darkolive+linewidth(0.2mm));
}
}
\end{asy}
\end{document}


4th Approach: A world map sphere

Of course, in the best of all worlds, somebody might figure out how to draw a 3D earth with e.g. Mathematicas GeoData in asymptote. Here I just want to put the 3D stuff in front of the background picture. It would be great if opacity would also work.

The package pst-map3d (pst-geo) offers the possibility to plot a 3D world map. The data set is based on the CIA World DataBank II. Gnuplot contains also a world.dat file with a low resolution world map data. Natural Earth is a public domain map dataset available at 1:10m, 1:50m, and 1:110 million scales. Based on the approaches Plotting the world and Plotting the world revisited given by Hagen Wierstorf on his website Gnuplotting, its implementation in asymptote or tikz is possible.

Joining pair of points with draw: draw((x1,y1,z1)--(x2,y2,z2), darkolive);

The coastlines are drawn joining a pair of points. guide3 and path3 in combination with draw and surface is used to fill the surface with a color.

     guide3 g;
g=g--(x1,y1,z1);
path3 s=g;
draw(surface(s--cycle),heavygreen);


Unfortunately, it does not work as expected. Any other ideas how to fill with a color the right surfaces?

Output for modified ´world_110m.dat´ with fill color (small scale data):

Output for modified ´world.dat´ with fill color (low resolution data):

Output for modified ´world_110m.dat´ without fill color (small scale data):

Output for modified ´world.dat´ without fill color (low resolution data):

Code:

\documentclass{standalone}
\usepackage[inline]{asymptote}
\begin{document}
\begin{asy}
import solids;
import three;
import graph3;
size(6cm);

//file fin=input("world.dat"); //1317 limit for the for loop
file fin=input("world_110m.dat"); //5254 limit for the for loop

real zenith  = pi/12.0;
real azimuth = pi/12.0;
currentprojection = perspective(cos(azimuth)*cos(zenith),
sin(azimuth)*cos(zenith),
sin(zenith));
real r = 1;
real ar = 1.2;

path3 myarc = Arc(c=O,normal=X, v1=-Z*r, v2=Z*r, n=24);
surface sphere = surface(myarc, angle1=0, angle2=360, c=O, axis=Z, n=24);
draw(sphere, surfacepen=material(royalblue+opacity(0.7),ambientpen=white));

real[][] a=fin.dimension(0,2);
a=transpose(a);
guide3 g;
for(int i=0; i <=5254; ++i){
real u=a[0][i];
real v=a[1][i];
real U=a[0][i+1];
real V=a[1][i+1];
if (u==360 & v==360 | U==360 & V==360){}
else{
real x1=r*Cos(v)*Cos(u);
real y1=r*Cos(v)*Sin(u);
real z1=r*Sin(v);
real x2=r*Cos(V)*Cos(U);
real y2=r*Cos(V)*Sin(U);
real z2=r*Sin(V);
draw((x1,y1,z1)--(x2,y2,z2), darkolive);
g=g--(x1,y1,z1);
}
}
path3 s=g;
//draw(surface(s--cycle),heavygreen);
\end{asy}
\end{document}


Building a guide3 / path3 with single points: draw(p3,black)

The coastlines as well as the colored filling are drawn with guide3, paht3 an subpath in combination with draw and surface. Unfortunately, it compiles only for the first 202 pair of points (world_110m.dat). For a higher number of points brings an error (the next pair of points (203) is a new segment (360 360)). I have not found the error yet. Any ideas?

Output for modified world_110m.dat with fill color (small scale data):

Code:

\documentclass{standalone}
\usepackage[inline]{asymptote}
\begin{document}
\begin{asy}
import solids;
import three;
import graph3;
size(6cm);
//file fin=input("world.dat"); //1317 limit for the for loop
file fin=input("world_110m.dat"); //5254 limit for the for loop

real zenith  = pi/12.0;
real azimuth = pi/12.0;
currentprojection = perspective(cos(azimuth)*cos(zenith),
sin(azimuth)*cos(zenith),
sin(zenith));
real r = 1;
real ar = 1.2;

path3 myarc = Arc(c=O,normal=X, v1=-Z*r, v2=Z*r, n=24);
surface sphere = surface(myarc, angle1=0, angle2=360, c=O, axis=Z, n=24);
draw(sphere, surfacepen=material(royalblue+opacity(0.5),ambientpen=white));

real[][] a=fin.dimension(0,2);
a=transpose(a);
int j=0;
guide3 g;
for(int i=0; i<=202; ++i){
real u=a[0][i];
real v=a[1][i];
if (u!=360){
real x=r*Cos(v)*Cos(u);
real y=r*Cos(v)*Sin(u);
real z=r*Sin(v);
g=g--(x,y,z);
++j;
} else{
int n0=i-1-j;
int nn=i-1;
path3 s=g;
path3 sp3=subpath(s,n0,nn);
draw(sp3--cycle,darkolive);
draw(surface(sp3--cycle),heavygreen);
j=0;
}
}

\end{asy}
\end{document}


Joining pair of points with Arc: Arc(O,r,one.y,one.x,two.y,two.x);

An other solution could be to implement the approach from Fill enclosed region on sphere by Frenzy Li.

Output for modified world_110m.dat without fill color (small scale data):

(only for a few points (70) since Overleaf (free version) returns the error: Timed out. Sorry, your compile took too long to run and timed out. This may be due to a large number of high-res images, or complicated diagrams).

Code:

\documentclass{standalone}
\usepackage[inline]{asymptote}
\begin{document}
\begin{asy}
import solids;
import three;
import graph3;
size(6cm);

//file fin=input("world.dat"); //1317 limit for the for loop
//file fin=input("world_110m.dat"); //5254 limit for the for loop
real zenith  = pi/12.0;
real azimuth = pi/12.0;
currentprojection = perspective(cos(azimuth)*cos(zenith),
sin(azimuth)*cos(zenith),
sin(zenith));
real r = 1;
real ar = 1.2;

path3 myarc = Arc(c=O,normal=X, v1=-Z*r, v2=Z*r, n=24);
surface sphere = surface(myarc, angle1=0, angle2=360, c=O, axis=Z, n=24);
draw(sphere, surfacepen=material(royalblue+opacity(0.7),ambientpen=white));

real[][] a=fin.dimension(0,2);
a=transpose(a);

pair[] region = new pair[];
for(int i=0; i <=60; ++i){
region[i]=(a[0][i],a[1][i]);}

for(int i=1; i<region.length; ++i){
pair one, two;
if (region[i].x==360 & region[i].y==360 | region[i-1].x==360 & region[i-1].y==360){} else{
if(region[i-1].y < region[i].y){
one = (region[i]  .x, 90 - region[i]  .y);
two = (region[i-1].x, 90 - region[i-1].y);
}else if(region[i-1].y > region[i].y){
one = (region[i-1].x, 90 - region[i-1].y);
two = (region[i]  .x, 90 - region[i]  .y);
}else if(region[i-1].x > region[i].x){
one = (region[i]  .x, 90 - region[i]  .y);
two = (region[i-1].x, 90 - region[i-1].y);
}else{
one = (region[i-1].x, 90 - region[i-1].y);
two = (region[i]  .x, 90 - region[i]  .y);
}
path3 temp = Arc(O,r,one.y,one.x,two.y,two.x);
draw(temp,darkolive+linewidth(.3pt));}
}
\end{asy}
\end{document}


The original data contains blank lines to define the segment limits. These lines have been replaced with the pair 360 360. For instance, the first lines of world_110m.dat:

    -163.71289567772871 -78.595667413241543
-163.105800951163786 -78.223338718578589
-161.245113491846439 -78.380176690584435
-160.24620805564453 -78.693645928866943
-159.482404548154477 -79.046337579258974
-159.208183560197654 -79.497007745276406
-161.127601284814716 -79.634208673011329
-162.439846768218416 -79.281465346186991
-163.027407803377002 -78.928773695794959
-163.06660437727038 -78.869965915846805
-163.71289567772871 -78.595667413241543

-6.197884894220991 53.867565009163364
-6.032985398777611 53.153190009160497
-6.788856573910849 52.260117906292336


Modified world_110m.dat:

    -163.71289567772871 -78.595667413241543
-163.105800951163786 -78.223338718578589
-161.245113491846439 -78.380176690584435
-160.24620805564453 -78.693645928866943
-159.482404548154477 -79.046337579258974
-159.208183560197654 -79.497007745276406
-161.127601284814716 -79.634208673011329
-162.439846768218416 -79.281465346186991
-163.027407803377002 -78.928773695794959
-163.06660437727038 -78.869965915846805
-163.71289567772871 -78.595667413241543
360  360
-6.197884894220991 53.867565009163364
-6.032985398777611 53.153190009160497
-6.788856573910849 52.260117906292336


In this way, with a conditional expression it is possible to know when a new segment starts.

3rd Attempt: Wrapping a label with surfaces

I have tried a combination of the 1st and 2nd approaches. That is to say, trying to replace the planet by the imported picture.

There is a possibility to draw a label in 3D by using the base modules three and labelpath3. For example:

    path3 g=(0,-5,0)..(0,5,0);
string world="earth";
draw(labelpath(world,subpath(g,0,reltime(g,0.95)),angle=-90),orange);


gives the following output:

Since (p. 9 of the documentation):

The function string graphic(string name, string options="") returns a string that can be used to include an encapsulated PostScript (EPS) file.

I thought that defining

    real sc=2;
unitsize(sc*1bp);
real wd=100*sc;
real ht=80*sc;
string world2=graphic("earth.pdf","width="+string(wd)+"bp"+",height="+string(ht)+"bp"+",scale="+string(sc));
draw(labelpath(world2,subpath(g,0,reltime(g,0.95)),angle=-90));


could work, but unfortunately it did not.

Code:

\documentclass{standalone}
\usepackage[inline]{asymptote}
\begin{document}
\begin{asy}
import graph3;
import math;
import three;
import labelpath3;

size(500);
currentprojection=perspective(
camera=(25,5,5),
up=Z,
target=(-0.6,0.7,-0.6),
zoom=1,
//------------------------------------------------- TUBE
defaultpen(0.5mm);
pen darkgreen=rgb(0,138/255,122/255);

real R=3;
real a=0.75;

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

draw(s,surfacepen=material(gray+opacity(0.9), emissivepen=0.2*white),render(compression=Low,merge=true));
//-------------------------------------------------- RING
triple f(real t) {
return (3*cos(0.125*2pi*t)+0.08*cos(2pi*t), 3*sin(0.125*2pi*t),0+ 0.08*sin(2pi*t));
}
path3 helix = graph(f, 0, 8, n=500, operator..);

surface helixtube = tube(helix, width=0.5).s;

draw(helixtube, red);

//--------------------------------------------------- PLANET
material m=
//  diffusepen, ambientpen, emissivepen,  specularpen
material( green,      yellow,     blue,      black);
//draw(surface(sphere(1.2)), m);
//--------------------------------------------------- LABEL TEXT
path3 g=(0,-5,0)..(0,5,0);
string world="earth";
draw(labelpath(world,subpath(g,0,reltime(g,0.95)),angle=-90),orange);
//--------------------------------------------------- LABEL PICTURE
real sc=2;
unitsize(sc*1bp);
real wd=100*sc;
real ht=80*sc;
string world2=graphic("earth.pdf","width="+string(wd)+"bp"+",height="+string(ht)+"bp"+",scale="+string(sc));
draw(labelpath(world2,subpath(g,0,reltime(g,0.95)),angle=-90));
\end{asy}
\end{document}


2nd Approach: Wrapping a sphere with surfaces

However, I want to create an output i which the tube wraps around the planet.

Output:

Code:

\documentclass{standalone}
\usepackage[inline]{asymptote}
\begin{document}
\begin{asy}
import graph3;
import solids;
import interpolate;
import math;
import three;
import labelpath3;

size(500);
currentprojection=perspective(
camera=(25,5,5),
up=Z,
target=(-0.6,0.7,-0.6),
zoom=1,
//------------------------------------------------- TUBE
defaultpen(0.5mm);
pen darkgreen=rgb(0,138/255,122/255);

real R=3;
real a=0.75;

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

draw(s,gray,render(compression=Low,merge=true));
//-------------------------------------------------- RING

triple f(real t) {
return (3*cos(0.125*2pi*t)+0.08*cos(2pi*t), 3*sin(0.125*2pi*t),0+ 0.08*sin(2pi*t));
}
path3 helix = graph(f, 0, 8, n=500, operator..);

surface helixtube = tube(helix, width=0.5).s;

draw(helixtube, surfacepen=material(red+opacity(0.9), emissivepen=0.2*white));

//--------------------------------------------------- PLANET
material m=
//  diffusepen, ambientpen, emissivepen,  specularpen
material( green,      yellow,     blue,      black);
draw(surface(sphere(1.2)), m);
\end{asy}
\end{document}


However, the background is not transparent ...

1st Approach: Overlay of saved pictures in Asymptote

I'd like to see the 3D stuff also in front of the imported picture. Is that possible?

Output:

Firstly compile the code pdflatex asy pdflatex to save the pictures and then compile it again by commenting the shipout("");´s:

 //shipout("D2FIG");
//shipout("D3FIG1");
//shipout("D3FIG2");


Code:

\documentclass[12pt]{article}
\usepackage[inline]{asymptote}

\begin{document}
\begin{asy}
size(10cm);
import graph3;
import three;
import labelpath3;
import graph;
import math;
// ----------------------------------------------------2D
// it is not important which precise picture gets imported
defaultpen(fontsize(10pt));
real sc=2;
unitsize(sc*1bp);
real wd=100*sc;
real ht=80*sc;
label(
shift(wd/2,ht/2)*
graphic("earth.pdf"
,"width="+string(wd)+"bp"
+",height="+string(ht)+"bp"
+",scale="+string(sc)
),(0,0)
);
layer();

int ngrid=10;
int n=(int)(wd/ngrid/sc);
int m=(int)(ht/ngrid/sc);

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

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

picture D2FIG = currentpicture;
// ----------------------------------------------------3D1
currentpicture = new picture;
size(10cm);
import graph3;
import three;
import labelpath3;
import graph;
import math;
triple f(real t) {
return (3*cos(0.125*2pi*t)+0.08*cos(2pi*t), 3*sin(0.125*2pi*t),0+ 0.08*sin(2pi*t));
}

path3 helix = graph(f, 0, 8, n=500, operator..);

surface helixtube = tube(helix, width=0.4).s;

draw(helixtube, surfacepen=material(blue+opacity(0.3), emissivepen=0.2*white));
shipout("D3FIG1");

picture D3FIG1 = currentpicture;
// ----------------------------------------------------3D2
currentpicture = new picture;
size(10cm);
import graph3;
import three;
import labelpath3;
import graph;
import math;
real R=300;
real a=100;

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

draw(s,gray,render(compression=Low,merge=true));
shipout("D3FIG2");

picture D3FIG2 = currentpicture;
// ----------------------------------------------------Merge/Overlay
currentpicture = new picture;
size(10cm);
label(graphic("D3FIG1+0_0.pdf"),(0,0));
label(graphic("D3FIG2+0_0.pdf"),(25,-25));
\end{asy}
\end{document}


I have taken the earth picture from MATLAB script for 3D visualizing geodata on a rotating globe

• Thanks for your answer! It may very well be my fault because I may have not explained very well what I want. I do not want the white background be on top of these pictures. That is, if I am not mistaken, the current output can be obtained by just producing the pdf's individually and then putting them on top of each other. However, I want to create an output i which the tube wraps around the planet.
– user121799
Aug 1, 2019 at 22:58
• Your question was I'd like to see the 3D stuff also in front of the imported picture. Is that possible? Here my approach, and of course, my output can be produced individually, but I thought you were looking for a solution with only Asymptote. Transparency, than the same question as Asymptote: 3D graphics with transparent background and the workaround is to use convert for png. May be you can open a bounty there
– Ñako
Aug 1, 2019 at 23:27
• Fine. The intention was really different. If you have a way to make the background transparent and add it to the answer I would greatly appreciate it.
– user121799
Aug 1, 2019 at 23:39
• Unfortunately, not yet, but I keep on working on a solution
– Ñako
Aug 1, 2019 at 23:44
• Your improvement corresponds exactly to my approach, which I found solely through the study of the documentations. Aug 2, 2019 at 23:53

Yes, it is possible to draw 3D graphics in front of an imported picture. That doesn't work with layering (2D) but by moving the objects directly toward (or away from) the camera (3D).

There are several ways to do this in Asymptote:

• label position parameter

• currentprojection.camera

• triple towardcamera