\section{More Advanced Examples} \subsection{The Box of Sugars} The problem\footnote{Problem posed in a forum, the objective being to use it as counting exercises for students.} is to draw sugars in a box. You need to be able to position the desired number of pieces, and wherever you want them in the box\footnote{A piece must rest either on the bottom of the box or on top of another piece} without having to rewrite the entire code. Another constraint: to keep the figure as light as possible, only the facets actually seen should be displayed. In the code below, we keep the default viewing angles, and: \begin{itemize} \item the sugars are cubes of side $1$ (we then modify the 3D matrix of the graph to "elongate" them), \item each piece is identified by the coordinates $(x,y,z)$ of the upper right corner of the front face, with $x$ an integer between $1$ and \emph{Lg}, $y$ an integer between $1$ and \emph{lg}, and $z$ an integer between $1$ and \emph{ht}. \item to store the positions of the pieces, we use a three-dimensional matrix \emph{positions}, one for $x$, one for $y$, and one for $z$, with the convention that \emph{positions[x][y][z]} is $1$ if there is a sugar at position $(x,y,z)$, and $0$ otherwise. \item For each piece, there are at most three visible faces: the top one, the right one, and the front one.\footnote{Provided you don't change the viewing angles!}, but we only draw the top face if there isn't another sugar cube above it, we only draw the right face if there isn't another sugar cube to the right, and we only draw the front face if there isn't another sugar cube in front. This builds the list of facets actually seen. \item In the scene display, you must \textbf{put the box first}, otherwise its facets will be cut off by the planes of the sugar cube facets. The sugar cube facets cannot be cut off by the box because they are all inside. \end{itemize} \begin{demo}{Box of Sugar Cubes} \begin{luadraw}{name=boite_sucres} local ld = luadraw local pt3d = ld.pt3d local Origin, vecI, vecJ, vecK, M, Mc = pt3d.Origin, pt3d.vecI, pt3d.vecJ, pt3d.vecK, pt3d.M, pt3d.Mc local g = ld.graph3d:new{window={-9,8,-10,4},size={10,10}} ld.Hiddenlines = false local Lg, lg, ht = 5, 4, 3 -- length, width, height (box size) local positions = {} -- 3-dimensional matrix initialized with 0s for L = 1, Lg do local X = {} for l = 1, lg do local Y = {} for h = 1, ht do table.insert(Y,0) end table.insert(X,Y) end table.insert(positions,X) end local facetList = function() -- returns the list of facets to draw (pay attention to the orientation) local facet = {} for x = 1, Lg do -- loop over the positions matrix for y = 1, lg do for z = 1, ht do if positions[x][y][z] == 1 then -- there is a sugar in (x,y,z) if (z == ht) or (positions[x][y][z+1] == 0) then -- no sugar on top so top side visible table.insert(facet, {M(x,y,z),M(x-1,y,z),M(x-1,y-1,z),M(x,y-1,z)}) -- insert top face end if (y == lg) or (positions[x][y+1][z] == 0) then -- no sugar on the right so right side visible table.insert(facet, {M(x,y,z),M(x,y,z-1),M(x-1,y,z-1),M(x-1,y,z)}) -- insert right face end if (x == Lg) or (positions[x+1][y][z] == 0) then -- no sugar in front so front side visible table.insert(facet, {M(x,y,z),M(x,y-1,z),M(x,y-1,z-1),M(x,y,z-1)}) -- insert front face end end end end end return facet end -- creation of the box (parallelepiped) local O = Origin -0.1*M(1,1,1) -- so that the box does not stick to the sugars local boite = ld.parallelep(O, (Lg+0.2)*vecI, (lg+0.2)*vecJ, (ht+0.5)*vecK) table.remove(boite.facets,2) -- we remove the top of the box, this is facet number 2 -- sugars are positioned for y = 1, 4 do for z = 1, 3 do positions[1][y][z] = 1 end end for x = 2, 5 do for z = 1, 2 do positions[x][1][z] = 1 end end for z = 1, 3 do positions[5][3][z] = 1 end for z = 1, 2 do positions[4][4][z] = 1 end for z = 1, 2 do positions[3][4][z] = 1 end positions[5][1][3] = 1; positions[3][1][3] = 1; positions[5][4][1] = 1; positions[2][3][1] = 1 g:Setmatrix3d({Origin,3*vecI,2*vecJ,vecK}) -- expansion on Ox and Oy to "lengthen" the cubes... g:Dscene3d( -- drawing g:addPoly(boite,{color="brown",edge=true,opacity=0.9}), g:addFacet(facetList(), {backcull=true,contrast=0.25,edge=true}) ) g:Labelsize("huge"); g:Dlabel3d( "SUGAR", M(Lg/2+0.1,lg+0.1,ht/2+0.1), {dir={-vecI,vecK}}) g:Show() \end{luadraw} \end{demo} \subsection{Stack of Cubes} We can modify the previous example to draw a stack of randomly positioned cubes, with four views. We'll position the cubes by placing a random number per column, starting from the bottom. We'll create four views of the stack, adding axes to help us navigate between these different views. This slightly changes the search for potentially visible facets; there are five cases per cube, not just three (front, back, left, right, and top; we don't create bottom views). To make the stack more readable, we use three colors to paint the cube faces (two opposite faces have the same color). \begin{demo}{Stack of Cubes} \begin{luadraw}{name=cubes_empiles} local ld = luadraw local pt3d = ld.pt3d local Origin, vecI, vecJ, vecK, M = pt3d.Origin, pt3d.vecI, pt3d.vecJ, pt3d.vecK, pt3d.M local g = ld.graph3d:new{window3d={-6,6,-6,6,-6,6},size={10,10}} ld.Hiddenlines = false local Lg, lg, ht, a = 5, 5, 5, 2 -- length, width, height of the space to fill, size of a cube local positions = {} -- 3-dimensional matrix initialized with 0s for L = 1, Lg do local X = {} for l = 1, lg do local Y = {} for h = 1, ht do table.insert(Y,0) end table.insert(X,Y) end table.insert(positions,X) end for x = 1, Lg do -- random positioning of cubes for y = 1, lg do local nb = math.random(0,ht) -- we put number of cubes in the column (x,y,*) starting from the bottom for z = 1, nb do positions[x][y][z] = 1 end end end local dessus,gauche,devant = {},{},{} -- to memorize the facets for x = 1, Lg do -- loop over the positions matrix to determine the facets to draw for y = 1, lg do for z = 1, ht do if positions[x][y][z] == 1 then -- there is a cube in (x,y,z) if (z == ht) or (positions[x][y][z+1] == 0) then -- no cube above so face up table.insert(dessus,{M(x,y,z),M(x-1,y,z),M(x-1,y-1,z),M(x,y-1,z)}) -- insert top face end if (y == lg) or (positions[x][y+1][z] == 0) then -- no cube on the right so face up table.insert(gauche,{M(x,y,z),M(x,y,z-1),M(x-1,y,z-1),M(x-1,y,z)}) -- insert right face end if (y == 1) or (positions[x][y-1][z] == 0) then -- no cube on the left so face up table.insert(gauche,{M(x,y-1,z),M(x-1,y-1,z),M(x-1,y-1,z-1),M(x,y-1,z-1)}) -- insert left face end if (x == Lg) or (positions[x+1][y][z] == 0) then -- no cube in front so face up table.insert(devant,{M(x,y,z),M(x,y-1,z),M(x,y-1,z-1),M(x,y,z-1)}) -- insert front face end if (x == 1) or (positions[x-1][y][z] == 0) then -- no cube behind so back face visible table.insert(devant,{M(x-1,y,z),M(x-1,y,z-1),M(x-1,y-1,z-1),M(x-1,y-1,z)}) -- back face end end end end end g:Setmatrix3d({M(-a*Lg/2,-a*lg/2,-a*ht/2),a*vecI,a*vecJ,a*vecK}) -- to center the figure and have cubes of side a local dessin = function() g:Dscene3d( g:addFacet(dessus, {backcull=true,color="Crimson"}), g:addFacet(gauche, {backcull=true,color="DarkGreen"}), g:addFacet(devant, {backcull=true,color="SteelBlue"}), g:addPolyline(ld.facetedges(ld.concat(dessus,gauche,devant))), -- drawing edges g:addAxes(Origin,{arrows=1})) end g:Saveattr(); g:Viewport(-5,0,0,5); g:Coordsystem(-11,11,-11,11); g:Setviewdir(45,60) -- top left dessin(); g:Restoreattr() g:Saveattr(); g:Viewport(0,5,0,5);g:Coordsystem(-11,11,-11,11); g:Setviewdir(-45,60) -- top right dessin(); g:Restoreattr() g:Saveattr(); g:Viewport(-5,0,-5,0);g:Coordsystem(-11,11,-11,11); g:Setviewdir(-135,60) -- bottom left dessin(); g:Restoreattr() g:Saveattr(); g:Viewport(0,5,-5,0);g:Coordsystem(-11,11,-11,11); g:Setviewdir(135,60) -- bottom right dessin(); g:Restoreattr() g:Show() \end{luadraw} \end{demo} \subsection{Illustration of Dandelin's Theorem} \begin{demo}{Illustration of Dandelin's Theorem} \begin{luadraw}{name=Dandelin} local ld = luadraw local pt3d = ld.pt3d local Origin, vecI, vecJ, vecK, M = pt3d.Origin, pt3d.vecI, pt3d.vecJ, pt3d.vecK, pt3d.M local g = ld.graph3d:new{window3d={-5,5,-5,5,-5,5}, window={-5,5,-5,6}, bg="lightgray",viewdir={-10,85}} g:Linewidth(8) local sqrt = math.sqrt local sqr = function(x) return x*x end local L, a = 4.5, 2 local R = (a+5)*L/sqrt(100+L^2) --large sphere center=M(0,0,a) radius=R local S2 = ld.sphere(M(0,0,a),R,45,45) local k = 0.35 --homothety ratio local b, r = (a+5)*k-5, k*R -- small sphere center = M(0,0,b) radius = r local S1 = ld.sphere(M(0,0,b),r,45,45) local c = (b+k*a)/(1+k) --second center of homothety local z = a+sqr(R)/(c-a) --image of c under inversion with respect to the large sphere local M1 = M(0,sqrt(sqr(R)-sqr(z-a)),z)--point of the large sphere and the tangent plane local N = M1-M(0,0,a) --normal vector to the tangent plane local plane = {M(0,0,c),-N} --tangent plane local z2 = a+sqr(R)/(-5-a) --image of the vertex under inversion with respect to the large sphere local z1 = b+sqr(r)/(-5-b) --image of the vertex by the inversion with respect to the small sphere local P2 = M(sqrt(R^2-(z2-a)^2),0,z2) local P1= M(sqrt(r^2-(z1-b)^2),0,z1) local S = M(0,0,-5) local P = ld.interDP({P1,P2-P1},plane) local C = ld.cone(M(0,0,-5),10*vecK,L,45,true) local ellips = g:Intersection3d(C,plane) local plane1 = {M(0,0,z1),vecK} local plane2 = {M(0,0,z2),vecK} local L1, L2 = g:Intersection3d(S1,plane1), g:Intersection3d(S2,plane2) local F1, F2 = ld.proj3d(M(0,0,b), plane), ld.proj3d(M(0,0,a), plane) --focal points local s1, s2 = g:Proj3d(M(0,0,a)), g:Proj3d(M(0,0,b)) local V, H = g:Classifyfacet(C) -- separate visible and invisible facets local V1, V2 = ld.cutfacet(V,plane) local H1, H2 = ld.cutfacet(H,plane) -- Drawing -- faces not visible under the plane, fill only g:Dpolyline3d( ld.border(H2),"left color=white, right color=DarkSeaGreen, draw=none") g:Dsphere( M(0,0,b), r, {mode=mBorder,color="Orange"}) -- small sphere -- visible faces below the plane g:Dpolyline3d( ld.border(V2),"left color=white, right color=DarkSeaGreen, fill opacity=0.4") g:Dpolyline3d({S,P}) -- segment [S,P] which is partially below the plane g:Dfacet( g:Plane2facet(plane,0.75), {color="Chocolate", opacity=0.8}) -- the plane -- outline of faces not visible above the plane, fill only g:Dpolyline3d( ld.border(H1),"left color=white, right color=DarkSeaGreen,draw=none,fill opacity=0.7") g:Dsphere( M(0,0,a),R, {mode=2,color="SteelBlue"}) -- large sphere -- outline of faces visible above the plane g:Dpolyline3d( ld.border(V1),"left color=white, right color=DarkSeaGreen, fill opacity=0.6") g:Dcircle3d(M(0,0,5),L,vecK) -- opening of the cone g:Dpolyline3d({{P,F1},{F2,P,P2}}) g:Dedges(L1,{hidden=true,color="FireBrick"}) g:Dedges(L2,{hidden=true,color="FireBrick"}) g:Dedges(ellips,{hidden=true, color="blue"}) g:Dballdots3d({F1,F2,S,P1,P,P2},nil,0.75) g:Dlabel3d( "$F_1$",F1,{pos="N"}, "$F_2$",F2,{}, "$N_2$",P2,{},"$S$",S,{pos="S"}, "$N_1$",P1,{pos="SE"}, "$P$",P,{pos="SE"} ) g:Show() \end{luadraw} \end{demo} We want to draw a cone with a section through a plane and two spheres inside this cone (and tangent to the plane), but without drawing any spheres or faceted cones. The starting point, however, is the creation of these faceted solids, the spheres \emph{S1} and \emph{S2} (lines 11 and 8 of the listing) as well as the cone \emph{C} in line 23. The drawing principle is as follows: \begin{enumerate} \item We separate the facets of the cone into two categories: the visible facets (facing the observer) and the others (variables \emph{V} and \emph{H} in line 30), which actually correspond to the front of the cone and the back of the cone. \item We divide the two lists of facets with the plane (lines 31 and 32). Thus, \emph{V1} corresponds to the front facets located above the plane and \emph{V2} corresponds to the front facets located below the plane (same thing with \emph{H1} and \emph{H2} for the back). \item We then draw the outline of \emph{H2} with a gradient fill (only) (line 34). \item We draw the small sphere (in orange, line 35). \item We draw the outline of \emph{V2} with a gradient fill and transparency to see the small sphere (line 36). \item We draw the segment $[S,P]$ (line 37) then the plane as a transparent facet (line 38). \item We draw the outline of \emph{H1} with a gradient fill (line 39). This is the back part above the plane. \item We draw the large sphere (line 40). \item Finally, we draw the outline of \emph{V1} with a gradient fill (line 41) and transparency to see the sphere (this is the front part of the cone above the plane), then the opening of the cone (line 42). \item We draw the intersections between the cone and the spheres (lines 44 and 45) as well as between the cone and the plane (line 46). \end{enumerate} \subsection{Volume defined by a double integral} \begin{demo}{Volume correspondant à $\int_{x_1}^{x_2}\int_{y_1}^{y_2}f(x,y)dxdy$} \begin{luadraw}{name=volume_integrale} local ld = luadraw local M = ld.pt3d.M local pi, sin, cos = math.pi, math.sin, math.cos local g = ld.graph3d:new{window3d={-4,4,-4,4,0,6},adjust2d=true,margin={0,0,0,0},size={10,10}} local x1, x2, y1, y2 = -3,3,-3,3 -- bornes local f = function(x,y) return cos(x)+sin(y)+5 end -- function to integrate local p = function(u,v) return M(u,v,f(u,v)) end -- surface parameterization z=f(x,y) local Fx1 = ld.concat({ld.pxy(p(x1,y2)), ld.pxy(p(x1,y1))}, ld.parametric3d(function(t) return p(x1,t) end,y1,y2,25,false,0)[1]) local Fx2 = ld.concat({ld.pxy(p(x2,y1)), ld.pxy(p(x2,y2))}, ld.parametric3d(function(t) return p(x2,t) end,y2,y1,25,false,0)[1]) local Fy1 = ld.concat({ld.pxy(p(x1,y1)), ld.pxy(p(x2,y1))}, ld.parametric3d(function(t) return p(t,y1) end,x2,x1,25,false,0)[1]) local Fy2 = ld.concat({ld.pxy(p(x2,y2)), ld.pxy(p(x1,y2))}, ld.parametric3d(function(t) return p(t,y2) end,x1,x2,25,false,0)[1]) g:Dboxaxes3d({grid=true, gridcolor="gray",fillcolor="LightGray",labels=false}) g:Filloptions("fdiag","black"); g:Dpolyline3d( {M(x1,y1,0),M(x1,y2,0),M(x2,y2,0),M(x2,y1,0)}) -- below g:Dfacet( {Fx1,Fy1},{mode=ld.mShaded,opacity=0.7,color="Crimson"} ) g:Dfacet(ld.surface(p,x1,x2,y1,y2), {mode=ld.mShadedOnly,color="cyan"}) g:Dfacet( {Fx2,Fy2},{mode=ld.mShaded,opacity=0.7,color="Crimson"} ) g:Dlabel3d("$x_1$", M(x1,4.75,0),{}, "$x_2$", M(x2,4.75,0),{}, "$y_1$", M(4.75,y1,0),{}, "$y_2$", M(4.75,y2,0),{}, "$0$",M(4,-4.75,0),{}) g:Show() \end{luadraw} \end{demo} Here, the solid represented has lateral faces (\emph{Fx1}, \emph{Fx2}, \emph{Fy1} and \emph{Fy2}) with one side being a parametric curve. We therefore take the points of this parametric curve (its first connected component) and add the projections of the two ends onto the $xy$-plane. Care must be taken with the direction of travel so that the faces are correctly oriented (normal outward). This normal is calculated from the first three points of the face; it is best to start the face with the two projections onto the plane to be sure of the orientation. We draw the bottom first, then the lateral faces, and finish with the surface. \subsection{Volume defined on something other than a block} \begin{demo}{Volume : $0\leqslant x\leqslant1;\ 0\leqslant y \leqslant x^2;\ 0\leqslant z\leqslant y^2$} \begin{luadraw}{name=volume2} local ld = luadraw local M = ld.pt3d.M local pi, sin, cos = math.pi, math.sin, math.cos local g = ld.graph3d:new{window3d={0,1,0,1,0,1}, margin={0,0,0,0},adjust2d=true,viewdir={170,40}, size={10,10}} g:Labelsize("scriptsize") local f = function(t) return M(t,t^2,0) end local h = function(t) return M(1,t,t^2) end local C = ld.parametric3d(f,0,1,25,false,0)[1] -- curve y=x^2 in the plane z=0 (first connected component) local D = ld.parametric3d(h,1,0,25,false,0)[1] -- curve z=y^2 in the plane x=1, in the opposite direction local dessous = ld.concat({M(1,0,0)},C) -- forms the underside local arriere = ld.concat({M(1,1,0)},D) -- forms the back face local avant, dessus, A, B = {}, {}, nil, C[1] for k = 2, #C do --We construct the front and top faces facet by facet, starting from the points of C A = B; B = C[k] table.insert(avant, {B,A,M(A.x,A.y,A.y^2),M(B.x,B.y,B.y^2)}) table.insert(dessus, {M(B.x,B.y,B.y^2),M(A.x,A.y,A.y^2),M(1,A.y,A.y^2),M(1,B.y,B.y^2)}) end g:Dboxaxes3d({grid=true, gridcolor="gray",fillcolor="LightGray", drawbox=false, xyzstep=0.25, zlabelstyle="W",zlabelsep=0}) g:Lineoptions(nil,"Navy",8) g:Dpolyline3d(arriere,close,"fill=Crimson, fill opacity=0.6") -- rear face (flat) g:Filloptions("fdiag","black"); g:Dpolyline3d(dessous,close) -- below g:Dmixfacet(avant,{color="Crimson",opacity=0.7,mode=ld.mShadedOnly}, dessus,{color="cyan",opacity=1}) g:Filloptions("none"); g:Dpolyline3d(ld.concat(ld.border(avant),ld.border(dessus))) g:Show() \end{luadraw} \end{demo} In this example, the surface has the equation $z=y^2$ (parabolic cylinder), but we are no longer on a block. The front face is not flat; we construct it like a cylinder (line 14) with vertical facets resting on curve $C$ at the bottom, and on curve $t\mapsto M(t,t^2,t^4)$ at the top. Similarly, the top face (the surface) is constructed like a horizontal cylinder resting on curves $D$ and $t\mapsto M(t,t^2,t^4)$. We could not construct the surface by hand (called \emph{dessus} in the code), and instead draw the following surface (after the front face): \begin{Luacode} g:Dfacet( ld.surface(function(u,v) return M(u,v*u^2,v^2*u^4) end, 0,1,0,1), {mode=ld.mShadedOnly, color="cyan"}) \end{Luacode} but it has many more facets ($25\times 25$) than the cylinder-shaped construction ($21$ facets), which is less interesting.