9
|
7394 Views
|
2 Replies
|
9 Total Likes
View groups...
Share
GROUPS:

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

Posted 2 years ago
 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}]]  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}]]  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) 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]]  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}]  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]];  We now upload the (attached)"chair iPad.gif" to an iPad and put a home made cylindrical mirror on top. This is the result of the GIF's reflection: 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: 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}]]]  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]]  Reflected in the mirror, the anamorphic image is observed as the complete 3D head: 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]]  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! Attachments:
2 Replies
Sort By:
Posted 2 years ago
 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 2 years ago
 -- you have earned Featured Contributor Badge 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!