Projection approach
One way to get the lines to show up nicely is to wrap them in Tube
and then project these tubes down onto the plane. To do projections in a more general setting, I had written a function project
as an answer to "How to make a drop-shadow for Graphics3D objects", which however needed some fixes for this answer.
So although the function project
is overkill for this problem, I'll list it here and show how it can be applied:
Options[project] = {"ObjectCenter" -> {0, 0, 0}, "DarkShadow" -> True};
project[x_, direction_, normal_, OptionsPattern[]] := Module[
{d, n, darkShadow, center},
darkShadow = OptionValue["DarkShadow"];
center = OptionValue["ObjectCenter"];
d = Normalize[direction];
n = Normalize[normal];
x /. Graphics3D[gr_, opts___] :> Graphics3D[
{
If[darkShadow, Black],
GeometricTransformation[
If[darkShadow,
gr /. {
Glow[_] -> Glow[],
r_?(MemberQ[{RGBColor, Hue, CMYKColor, GrayLevel},
Head[#]] &) -> Black
},
gr
],
Composition[
TranslationTransform[direction + center],
Quiet[Check[RotationTransform[{d, n}], Identity],
{RotationMatrix::degen, RotationTransform::spln}
],
ScalingTransform[10^-3, d],
Quiet@Check[
ScalingTransform[1./(n.d), n - (n.d) d],
Identity
],
TranslationTransform[-center]
]
]
},
opts
]
]
Here are your definitions of the lines, and my way of translating them to 3D:
line1 = Graphics[{Thickness[0.003], Blue, Line[{{0, 0}, {4, 1}}]}];
line2 = Graphics[{Thickness[0.03], Red, Line[{{0, 0}, {4, 1}}]}];
line1new =
Graphics3D[N[First@line1] /. {x_?AtomQ, y_?AtomQ} -> {x, y, 2.}] /.
Line[x__] -> {CapForm["Butt"], Tube[Line[x], .02]};
line2new =
Graphics3D[N[First@line2] /. {x_?AtomQ, y_?AtomQ} -> {x, y, 1.}] /.
Line[x__] -> {CapForm["Butt"], Tube[Line[x], .1]};
overlapping1 = Show[{line2new, line1new}]

So the lines first of all are offset vertically. This is done so that the next step, the projection, will layer the thin blue line on top of the red one (which is closer to the bottom plane).
Now I create the planar plot as you did - but separately. Then I combine it with the projection result:
cpl3d = Graphics3D[N@First@cpl /. {x_?AtomQ, y_?AtomQ} -> {x, y, 0}];
Show[
project[
overlapping1, {0, 0, -1}, {0, 0, 1},
"DarkShadow" -> False, "ObjectCenter" -> {0, 0, 1}],
cpl3d
]

The result seems to have no visual problems.
To explain how project
works: the first argument is the Graphics3D
(in this case overlapping1
corresponding to the two tubular lines). The second argument is the direction
in which I want the shadow of this object to be projected. The third argument normal
is the surface normal of the surface onto which we imagine projecting the object. The amount by which the object is translated is given by the length of the vector direction
.
The projection involves a "flattening step" in which the center of mass of the object is moved into a plane including the origin, and in that step we lose the information where the object was originally. The translation by direction
is applied starting from the origin.
That's fine if the center of the object was originally near the origin anyway, but in our overlapping1
object the center is somewhere higher up. I actually want the lowest of the lines to end up on the plane at z = 0
, but the red line as at z = 1
. To make this come out right, I have to provide the information that the original position of the object should be taken as z = 1
, so that after the "flattening step" this original position is first restored before applying the translation in the projection direction {0, 0, -1}
.
The original position is provided as an option "ObjectCenter"
. Another option is "DarkShadow"
which by default is True
, making shadows appear black or gray. In our case we want colored shadows, so I set this option to False
.
I'll also have to update this code in the earlier answer linked above, because there I didn't catch an error that appears when projecting straight along the surface normal (since for such cases the function project
is actually overkill, as I mentioned)... However, with this function you can then also project lines (or rather Tube
s) onto arbitrary oblique surfaces, if desired.
Edit 2: Texture approach
The ultimate goal seems to involve further 3D manipulations, which may lead to unacceptably large files and sluggish response due to high polygon count (not just with my method above - it depends mainly on the number of polygons in the ContourPlot
, which is usually rather large).
So I would suggest instead to go with the alternative approach that was already alluded to in the question: make the 2D graphics into a textured polygon. As it happens, I also had previously written a function for that.
It's called label3D
and takes a any arbitrary input as its first argument, converts it into an Image
and then puts it as a texture onto a rectangular Polygon
whose dimensions are given by the arguments pos
, xVec
, tiltAngle
in the code below. Here, pos
is the location of the bottom left corner of the flattened polygon in 3D space. xVec
is a vector pointing in the direction along which the baseline of the image should be oriented. The length of this vector is taken as the width of the polygon in 3D space. tiltAngle
is the angle (in radians) by which the label is rotated around its baseline.
label3D[s_, pos_, xVec_, tiltAngle_, opts : OptionsPattern[]] :=
Module[{ra, width, height, r},
ra = Rasterize[
Style[HoldForm[s], FilterRules[{opts}, Options[Style]],
Magnification -> 10],
Evaluate@
Apply[Sequence, FilterRules[{opts}, Options[Rasterize]]],
"Image"];
{width, height} = ImageDimensions[ra];
r = SetAlphaChannel[ra,
With[{color =
Apply[List,
ColorConvert[
"TransparentColor" /. {opts} /. {"TransparentColor" ->
Apply[RGBColor, ImageData[ra][[2, 2]]]}, "RGB"]]},
Binarize[ra, (Norm[# - color] > .005) &]]];
Translate[(* //to make lefthand corner pos*)
Rotate[(* //around z axis*)
Rotate[(* //around y axis*)
Rotate[(* //tilt around x axis*)
Scale[(*//to make width equal|
xVec|*){EdgeForm[FrameStyle /. {opts} /. FrameStyle -> None],
Texture[ImageData@r],(* //
Texture fills polygon initially in the xz plane*)
Polygon[{{0, 0, 0}, {width, 0, 0}, {width, 0, height}, {0, 0,
height}},
VertexTextureCoordinates -> {{0, 0}, {1, 0}, {1, 1}, {0,
1}}]}, Norm[xVec]/width, {0, 0, 0}],
tiltAngle, {1, 0, 0}],(* //x rotation*)
Arg[Chop@N[Norm[xVec[[1 ;; 2]] + I xVec[[3]]]]], {0, -1,
0}],(* //y rotation*)
Arg[Chop@N[xVec[[1]] + I xVec[[2]]]], {0, 0, 1}],(* //z rotation*)
pos]];
The function recognizes the additional options Magnification
(and also ImageSize
) to determine the resolution of the rasterized image.
But in this application I chose to instead specify a fixed ImageSize
for the original 2D plot, to make sure the line thicknesses are correct relative to the image size.
Here is the application to your problem. The 2D plot is made without tick marks and with no PlotRangePadding
so that it fits snugly into the polygon of dimension $8\times 8$ in the 3D plot.
Although the rasterization step may take a second or two, the result is a smaller 3D graphics object that can be rotated more smoothly:
ClearAll[line1, line2, overlapping1, ov1Flat]
line1 = Graphics[{Thickness[0.003], Blue, Line[{{0, 0}, {4, 1}}]}];
line2 = Graphics[{Thickness[0.03], Red, Line[{{0, 0}, {4, 1}}]}];
overlapping1 = Show[{line2, line1}];
ov1Flat =
Show[{ContourPlot[Sin[y x], {x, -4, 4}, {y, -4, 4}], overlapping1},
ImageSize -> 360, PlotRangePadding -> 0, FrameTicks -> None];
g = Graphics3D[
label3D[ov1Flat, {-4, -4, 0}, {8, 0, 0}, -Pi/2]];
Show[
{
Plot3D[8 + Sin[y x], {x, -4, 4}, {y, -4, 4},
MeshFunctions -> {(0.25 #1 - #2) &}, Mesh -> {{0}},
MeshStyle -> {Blue, Thick}, PlotPoints -> 80],
g
},
PlotRange -> All, BoxRatios -> {Automatic, Automatic, 6}]

Opacity[.99]
or similar does also help, but the rendering of coincident polygons is problematic (see also mathematica.stackexchange.com/questions/15735/…) $\endgroup$Opacity[.99]
doesn't really help but that is a very helpful link. $\endgroup$