Message Boards Message Boards

Transforming 2D images into 3D reflections in a cylindrical mirror

Posted 3 years ago

enter image description here The above photos show 2D anamorphic images reflecting as 3D objects in a cylindrical mirror. In my previous community contribution, Anamorphosis of 3D-Objects & 3D Printing, I demonstrated the reflection of 3D anamorphic objects to get a realistic 3-dimensional reflection. Identical results can be achieved by reflecting 2D images. I will demonstrate this with two examples: a simple chair built from 132 line segments and a more complicated STL file of a human head consisting of 34,500 triangles obtained from the internet.

1. Chair

We start with a simple model of a chair to test our operating procedure. A chair can be constructed with line segments between a limited number of base points. But since the anamorphic image will transform lines into curves, we will need to interpolate a number of points between the base points.The following function will compute the 3D coordinates of m points evenly distributed between points ptA and ptB.

pointsInLine[{ptA : {xa_, ya_, za_}, ptB : {xb_, yb_, zb_}}, m_] := 
 Transpose[{Subdivide[xa, xb, m], Subdivide[ya, yb, m], 
   Subdivide[za, zb, m]}]

Using the above function, we construct the 3 parts of the chair: seat, sides and back. Since we will later rotate the chair around its vertical axis, we introduce the angle phi as a variable.

seat[phi_] := (pointsInLine[#1, 10] &) /@ 
  Partition[
   RotationTransform[
     phi, {0, 0, 1}, {-2, 0, 0}] /@ {{-2.77`, -0.77`, 1.61}, {-2.77`, 
      0.77`, 1.61}, {-1.23`, 0.77`, 1.61}, {-1.23`, -0.77`, 1.61}}, 2,
    1, {1, 1}]
sides[phi_] :=     
 pointsInLine[#, 10] & /@ 
  Map[RotationTransform[
    phi, {0, 0, 1}, {-2, 0, 
     0}], {{{-2.77`, -0.77`, 0.07}, {-2.77`, -0.77`, 1.61}}, {{-2.77`,
       0.77`, 0.07}, {-2.77`, 0.77`, 1.61}}, {{-1.23`, 0.77`, 
      0.07}, {-1.23`, 0.77`, 1.61}}, {{-1.23`, -0.77`, 
      0.07}, {-1.23`, -0.77`, 1.61}}}, {2}]
back[phi_] :=     
 pointsInLine[#, 10] & /@ 
  Map[RotationTransform[
    phi, {0, 0, 1}, {-2, 0, 
     0}], {{{-2.77`, -0.77`, 3.15`}, {-1.23`, -0.77`, 
      3.15`}}, {{-2.77`, -0.77`, 2.38`}, {-1.23`, -0.77`, 
      2.38`}}, {{-2.77`, -0.77`, 3.15`}, {-2.7`, -0.77`, 
      1.61`}}, {{-1.23`, -0.77`, 3.15`}, {-1.23`, -0.77`, 
      1.61`}}}, {2}]
chair[phi_] := Through[{seat, sides, back}[phi]]

This is the complete chair built from 132 line segments between 12 base points :

With[{phi = -1.25}, 
 Graphics3D[{Red, AbsoluteThickness[8], Line /@ chair[phi]}, 
  Boxed -> False, Axes -> True, AxesOrigin -> {0, 0, 0}]]

enter image description here

If we look at the chair, we see it as an "observed" 2D image in a plane perpendicular to our view direction. The following functions will convert the 3D chair point by point into a 2D image in the y-z plane if observed from a view point at (xv, 0, zv):

observedPoint[ptR : {xr_, yr_, zr_}, ptV : {xv_, 0, zv_}] := 
 First[NSolveValues[{x, y, z} \[Element] InfiniteLine[{ptR, ptV}] && 
    x == 0, {x, y, z}]]
observedChair[phi_] := 
 Map[observedPoint[#1, {5, 0, 6}] &, chair[phi], {3}]

This code illustrates the relation between the real 3D chair and the observed 2D image:

With[{phi = -1.35}, 
 Graphics3D[{{Opacity[.10], 
    InfinitePlane[{0, 0, 0}, {{0, 1, 0}, {1, 0, 0}}], 
    Polygon[{{0, -1, 0}, {0, -1, 5}, {0, 1, 5}, {0, 1, 0}}]}, {Red, 
    AbsoluteThickness[6], Line /@ chair[phi]}, {Red, 
    AbsoluteThickness[4], Map[Line, observedChair[phi], {2}]}}, 
  Boxed -> False, Axes -> True, AxesOrigin -> {0, 0, 0}]]

enter image description here

The observed image can also be the reflection of an anamorphic 2D image in the x-y plane. To get the anamorphic image of the observed chair, we take the function explained and used here

anamorphPointCF = 
  Compile[{{ptP, _Real, 1}, {ptV, _Real, 1}}, 
   Module[{yi, zi, xv, zv, t1, t2, t3},
    {yi, zi} = ptP;
    {xv, zv} = ptV; t1 = Sqrt[2 + 1/xv^2 + yi^2 - xv^2 (-1 + yi^2)]; 
    t2 = 2 + yi^2 + 1/xv^2 + xv^2; 
    t3 = (1 + 2 xv^2 + yi^2 xv^2 + xv^4); {(-t1/xv + xv (t1 + yi^2))/
       t2 - (-1 + 
         xv^2 (-1 + xv^2 + 
            xv^4 + (-1 - 2 xv^4 + xv^2 (3 + 2 t1)) yi^2)) (zi - (-1 + 
             1/xv^2 + t1 + yi^2) (-zv + zi)/t2)/(xv t3 (-zv + zi)), 
     yi (xv^2 (-1 + xv^2 - t1) t3 + (-1 - xv^2 (2 + 2 t1 + yi^2) + 
            xv^4 (-1 + 2 t1 + 2 yi^2)) (zv + 
             xv^2 zv (-1 + t1 + yi^2) + xv^2 (3 + xv^2 - t1) zi)/(zv -
              zi))/t3^2, 0}], CompilationTarget -> "C"];

A point R is observed as point P in the y-z plane and is reflected as point A in the x-y plane. The anamorphic point A will be reflected in a cylindrical mirror as P if observed from the view point V at (xv, o, zv)

enter image description here

Applied to all 132 points of the chair, we get the chair's anamorphic image. When reflected in the cylindrical mirror, it will appear as the observed chair in the y-z plane.

anamorphChair[phi_] := 
 Module[{xv = 5, zv = 6}, 
  Map[anamorphPointCF[Rest@#, {xv, zv}] &, observedChair[phi], {3}]]
With[{phi = -1.25}, 
 Graphics3D[{{Opacity[.15], Cylinder[{{0, 0, 0}, {0, 0, 5}}, 1], 
    InfinitePlane[{0, 0, -.001}, {{0, 1, 0}, {1, 0, 0}}]}, {Red, 
    AbsoluteThickness[4], Map[Line, anamorphChair[phi], {2}]}, {Red, 
    AbsoluteThickness[2], Map[Line, observedChair[phi], {2}]}}, 
  Lighting -> "Neutral", Boxed -> False]]

enter image description here

For esthetic purposes, we fill the seat polygon. We can now make an animation of the reflected rotating chair:

Animate[Graphics3D[{{Opacity[.15], 
    Cylinder[{{0, 0, 0}, {0, 0, 5}}, 1], 
    InfinitePlane[{0, 0, -.001}, {{0, 1, 0}, {1, 0, 0}}], 
    Polygon[{{-.01, -1, 0}, {-.01, -1, 5}, {-.01, 1, 5}, {-.01, 1, 
       0}}]}, {Red, AbsoluteThickness[5], 
    Map[Line, 
     anamorphChair[phi], {2}], {FaceForm[Lighter[Red, .5]]}, {Red, 
     AbsoluteThickness[2.5], Map[Line, observedChair[phi], {2}]}}}, 
  PlotRange -> {{-1, 14}, {-11, 11}, {-.01, 5}}, Boxed -> False, 
  ViewPoint -> {1, -2, .5}, ImageSize -> Large], {phi, 0, 
  2 \[Pi], \[Pi]/25}]

enter image description here

In order to demonstrate this reflected in a real mirror, we use what was previously demonstrated in Introducing Anamorphic Movies: we make a GIF of the rotating anamorphic chair.

iPadFrames = 
  Table[Module[{h = 6, x0 = -2, y0 = 0, z0 = 0, xv = 5, zv = 6, 
     sc = .5, chair, observedChair, anaChair, anaSeat, observedSeat}, 
    ptV = {xv, 0, zv};
    chair = Line[#[phi, sc, {x0, y0, z0}]] & /@ {seat, sides, back};
    observedChair = 
     ParallelMap[observedPoint[#, {xv, 0, zv}] &, chair, {4}];
    anaChair = 
     Map[anamorphPointCF[Rest@#, {xv, zv}] &, observedChair, {4}];
    anaSeat = Polygon[Flatten[anaChair[[1, 1]], 1]]; 
    observedSeat = Polygon[Flatten[observedChair[[1, 1]], 1]];
    Graphics3D[{{Opacity[.15], 
       Polygon[{{0, -1, 0}, {0, -1, h}, {0, 1, h}, {0, 1, 0}}], , 
       InfinitePlane[{0, 0, -.001}, {{0, 1, 0}, {1, 0, 0}}], 
       Cylinder[{{0, 0, 0}, {0, 0, h}}, 1]}, {AbsoluteThickness[8], 
       Red, anaChair}, {FaceForm[Lighter[Red, .5]], anaSeat}}, 
     Axes -> {True, True, False}, AxesStyle -> Gray, Ticks -> None, 
     AxesOrigin -> {0, 0, 0}, Boxed -> False, 
     PlotRange -> {{-4, 17}, {-15, 15}, {-.001, 6}}, 
     ImageSize -> Large, ViewPoint -> {0, 0, 10}]], {phi, 0, 
    2 \[Pi], \[Pi]/20}];
Export[NotebookDirectory[] <> "chair iPad.gif", iPadFrames, 
  AnimationRepetitions -> \[Infinity]];

enter image description here

We now upload the (attached)"chair iPad.gif" to an iPad and put a home made cylindrical mirror on top.

enter image description here

This is the result of the GIF's reflection:

enter image description here

2. Human head

We now move to a more complicated example using the same procedure as above. We import an STL file of a human head from: 3D Model Marketplace CGTrader:

enter image description here

headPolys = 
  Import["/Users/er/Downloads/male face 1.stl", "PolygonObjects"];
Length[headPolys[[1]]](*34496*)

Unlike the chair which is "see-through", the head is an opaque sphere and only part of it is visible from any viewpoint. We write a function that will delete the triangles that are invisible if observed from the eye position. Simplified: these are the triangles whose vertices are farther away from vie point V at (xv, 0, zv) than a certain distance d. This way, we extract the 25,600 visible triangles out of the the total amount of 34,496

Quiet[Module[{d = .05, scalingDivider = 20, x0 = -.5, z0 = 1.75, 
   xv = 10, zv = 5, rawScaledVertices, scaledVertices, 
   visibleVertices, observedVertices}, 
  rawScaledVertices = 
   RotationTransform[Pi/2, {0, 0, 1}] /@ 
     headPolys[[1]] /. {x_?NumericQ, y_, z_} -> {x, y, z}/
      scalingDivider;
  visibleVertices = 
   Length[
    DeleteCases[
     scaledVertices, _?(findInvisibles[#1, d] == True &)]];
  observedVertices = 
   ParallelMap[observedPoint[#1, {xv, 0, zv}] &, visibleVertices, {2}];
  Row[(Graphics3D[{FaceForm[LightGray], 
        EdgeForm[AbsoluteThickness[.1]], Triangle[#1]}, 
       Boxed -> False, ViewPoint -> Front, 
       Lighting -> "Accent"] &) /@ {scaledVertices, visibleVertices, 
     observedVertices}]]]

enter image description here

Above left, we see the complete head with all triangles. In the middle is a side view of only the visible triangles. At right are the visible triangles as observed from the view point. We now compute the anamorphic map of the visible triangles:

Quiet@Module[{d = .12, scalingDivider = 22,(*offset*)x0 = -.5, 
   z0 = 1.75, xv = 10, zv = 5, rawScaledVertices, scaledVertices, 
   visibleVertices, observedVertices, anamorphVertices},
  rawScaledVertices = 
   RotationTransform[.4, {0, 0, 1}] /@ 
     headPolys[[1]] /. {x_?NumericQ, y_, z_} -> {x, y, z}/
      scalingDivider;
  scaledVertices = 
   DeleteCases[rawScaledVertices, _?(findInvisibles[#, d] &)];
  visibleVertices = 
   DeleteCases[
    scaledVertices, _?(polyCheck[#, {x0, 0, z0}, {xv, 0, zv}] == 
        3 &(*all 3 vertices under view plane*))];
  observedVertices = 
   ParallelMap[observedPoint[#, {xv, 0, zv}] &, visibleVertices, {2}];
   anamorphVertices = 
   ParallelMap[anamorphPointCF[Rest@#, {xv, zv}] &, 
    observedVertices, {2}];
  Graphics[{{FaceForm[Lighter[Gray, .5]], 
     EdgeForm[AbsoluteThickness[.1]], 
     Polygon[
      anamorphVertices /. {x_?NumberQ, y_, z_} -> {x, y}]}, {Dashed, 
     Circle[]}}, PlotRange -> {{-5, 12}, {-10, 10}}, 
   ImageSize -> 250]]

enter image description here

Reflected in the mirror, the anamorphic image is observed as the complete 3D head:

enter image description here

As we did with the chair, we animate the anamorphic image by varying the rotation angle around a vertical axis.

With[{d = .12, scalingDivider = 24,(*offset*)x0 = -.5, z0 = 1.75, 
   xv = 10, zv = 5}, 
  headFrames = 
   Table[
    Module[{rawScaledVertices, scaledVertices, visibleVertices, 
      observedVertices, anamorphVertices},
     rawScaledVertices = 
      RotationTransform[phi, {0, 0, 1}] /@ 
        headPolys[[1]] /. {x_?NumericQ, y_, z_} -> {x, y, z}/
         scalingDivider;
     scaledVertices = 
      DeleteCases[rawScaledVertices, _?(findInvisibles[#, d] &)];
     visibleVertices = 
      DeleteCases[
       scaledVertices, _?(polyCheck[#, {x0, 0, z0}, {xv, 0, zv}] == 
           3 &(*all 3 vertices under view plane*))];
     observedVertices = 
      ParallelMap[observedPoint[#, {xv, 0, zv}] &, 
       visibleVertices, {2}]; 
     anamorphVertices = 
      ParallelMap[anamorphPointCF[Rest@#, {xv, zv}] &, 
       observedVertices, {2}];
     Graphics[{{FaceForm[Lighter[Gray, .5]], 
        EdgeForm[AbsoluteThickness[.1]], 
        Polygon[
         anamorphVertices /. {x_?NumberQ, y_, z_} -> {x, 
            y}]}, {Dashed, Circle[]}}, 
      PlotRange -> {{-5, 12}, {-10, 10}}, ImageSize -> 600]], {phi, 0,
      2 \[Pi] - \[Pi]/30, \[Pi]/30}]];
Export[NotebookDirectory[] <> "head iPad.gif", headFrames, 
 AnimationRepetitions -> \[Infinity]]

enter image description here

We load the GIF to an iPad and see its reflection in a cylindrical mirror. The reflection appears as a virtual image of a 3D rotating head inside the cylinder. I attached the two GIF files "chair iPad.GIF" and "head iPad.GIF". Load them to your iPad and see the magic! Have fun!

enter image description here

Attachment

Attachment

POSTED BY: Erik Mahieu
2 Replies

enter image description here -- you have earned Featured Contributor Badge enter image description here Your exceptional post has been selected for our editorial column Staff Picks http://wolfr.am/StaffPicks and Your Profile is now distinguished by a Featured Contributor Badge and is displayed on the Featured Contributor Board. Thank you!

POSTED BY: EDITORIAL BOARD

Congratulations! Your post was highlighted on the Wolfram's official social media channels. Thank you for your contribution. We are looking forward to your future posts.

POSTED BY: EDITORIAL BOARD
Reply to this discussion
Community posts can be styled and formatted using the Markdown syntax.
Reply Preview
Attachments
Remove
or Discard

Group Abstract Group Abstract