-- luadraw_frustum_and_co.lua -- date 2026/08/04 -- version 3.4 -- Copyright 2026 Patrick Fradin -- This work may be distributed and/or modified under the -- conditions of the LaTeX Project Public License. -- The latest version of this license is in -- https://www.ctan.org/license/lppl -- functions for : cylinder, cone, frustum and sphere local ld = luadraw local pt3d = ld.pt3d --------------------------- outlines ----------------------------------- function ld.graph3d:Cone_outline(B, R, V, H) -- or Cone_outline(B, R, H) -- returns a table: -- {side=, section=, visible=, hidden=, tangency=, angle=} if not pt3d.isPoint3d(H) then -- format B, R, H H = V; V = H-B end if pt3d.dot(V,H-B) < 0 then V = -V end local rep = {} local sin, cos, pi = math.sin, math.cos, math.pi local O, I, J = ld.orthoframe({B, V}) local mat, H1, V1 = self.matrix3d, H, V if not ld.isID3d(mat) then O, I, J = ld.mtransform3d(O,mat), ld.mLtransform3d(I,mat), ld.mLtransform3d(J,mat) H1 = ld.mtransform3d(H,mat); V1 = ld.mLtransform3d(V,mat) end local angle = self:Arg(self:Proj3d(H)-self:Proj3d(B))*ld.rad if angle < 0 then angle = angle+180 elseif angle > 180 then angle = angle-180 end rep.visible = {} rep.hidden = {} rep.side = {} rep.section = {} rep.angle = angle local f = function(t) local A = O + R*(cos(t)*I+sin(t)*J) local N = self.Normal if ld.projection_mode == "central" then N = ld.camera-A end return pt3d.det(N, A-H1, R*(-sin(t)*I+cos(t)*J) ) end local T = ld.solve(f,-pi,pi) if (T ~= nil) and (#T >= 2) then local M1, M2 = O+R*(cos(T[1])*I+sin(T[1])*J), O+R*(cos(T[2])*I+sin(T[2])*J) local t = (T[1]+T[2])/2 local M3 = O+R*(cos(t)*I+sin(t)*J) if not ld.isID3d(mat) then M1, M2 = table.unpack( ld.mtransform3d({M1, M2}, ld.invmatrix3d(mat)) ) end rep.tangency = {M1,M2} local sens = 1 if self:Cosine_incidence(M3-O,O) < 0 then sens = -1 end if self:Cosine_incidence(V1,O) < 0 then -- circular base visible rep.visible = {M1, "m", H, M2, "l", B, -V, "c" } table.insert(rep.section, {M1,B,V,"c"}) else rep.visible = {M1, "m", B, M2, R, sens, V, "ca", H, "l", "cl" } rep.hidden = {M1, "m", B, M2, R, -sens, V, "ca"} end rep.side = {M1, "m", H, M2,"l", B, M1, R, -sens, V, "ca"} else O, I, J = ld.orthoframe({B, V}) rep.visible = {B+R*I,B,V,"c"} if self:Cosine_incidence(V1,O) < 0 then -- circular base visible table.insert(rep.section, {B+R*I,B,V,"c"}) end end return rep end function ld.graph3d:Cylinder_outline(B, R, V, H) -- or Cylinder_outline(B, R, H) -- returns a table: -- {side=, section=, visible=, hidden=, tangency=, angle=} if not pt3d.isPoint3d(H) then -- format B, R, H H = V; V = H-B end if pt3d.dot(V,H-B) < 0 then V = -V end local U = H-B local rep = {} local sin, cos, pi = math.sin, math.cos, math.pi local O, I, J = ld.orthoframe({B, V}) local mat, H1, V1 = self.matrix3d, H, V if not ld.isID3d(mat) then O, I, J = ld.mtransform3d(O,mat), ld.mLtransform3d(I,mat), ld.mLtransform3d(J,mat) H1 = ld.mtransform3d(H,mat); V1 = ld.mLtransform3d(V,mat) end local angle = self:Arg(self:Proj3d(H)-self:Proj3d(B))*ld.rad if angle < 0 then angle = angle+180 elseif angle > 180 then angle = angle-180 end local f = function(t) local A = O + R*(cos(t)*I+sin(t)*J) local N = self.Normal if ld.projection_mode == "central" then N = ld.camera-A end return pt3d.det(N, H1-O, R*(-sin(t)*I+cos(t)*J) ) end rep.visible = {} rep.hidden = {} rep.side = {} rep.section = {} rep.angle = angle local T = ld.solve(f,-pi,pi) if (T ~= nil) and (#T >= 2) then local M1, M2 = O+R*(cos(T[1])*I+sin(T[1])*J), O+R*(cos(T[2])*I+sin(T[2])*J) local t = (T[1]+T[2])/2 local M3 = O+R*(cos(t)*I+sin(t)*J) if not ld.isID3d(mat) then M1, M2 = table.unpack( ld.mtransform3d({M1, M2}, ld.invmatrix3d(mat)) ) end rep.tangency = {M1, M2, M1+U, M2+U} local sens = 1 if self:Cosine_incidence(M3-O,O) < 0 then sens = -1 end rep.visible = {M1, "m", M1+U, "l"} if self:Cosine_incidence(V1,H1) > 0 then -- second circular base visible table.append(rep.visible, {H, V, "c", M2+U, "m"}) table.insert(rep.section, {M1+U,H,V,"c"}) else table.append(rep.visible, {H, M2+U, R, sens, V, "ca"}) table.append(rep.hidden, {M1+U, "m", H, M2+U, R, -sens, V, "ca"}) end table.append(rep.visible, {M2, "l"}) if self:Cosine_incidence(V1,O) < 0 then -- first circular base visible table.append(rep.visible, {B, V, "c"}) table.insert(rep.section, {M1,B,V,"c"}) else -- first circular base not visible table.append(rep.visible, {B, M1, R, -sens, V, "ca"} ) table.append(rep.hidden, {M2, "m", B, M1, R, sens, V, "ca"} ) end rep.side = {M1,"m",M1+U,"l",H,M2+U,R,sens,V,"ca",M2,"l",B,M1,R,-sens,V,"ca"} else O, I, J = ld.orthoframe({B, V}) if self:Cosine_incidence(V1,H1) > 0 then -- second circular base visible table.append(rep.visible, {H+R*I, "m", H, V, "c"}) table.insert(rep.section, {H+R*I, "m", H, V, "c"}) else table.append(rep.hidden, {H+R*I, "m", H, V, "c"}) end if self:Cosine_incidence(V1,O) < 0 then -- first circular base visible table.append(rep.visible, {B+R*I, "m", B, V, "c"}) table.insert(rep.section, {B+R*I, "m", B, V, "c"}) else table.append(rep.hidden, {B+R*I, "m", B, V, "c"}) end end return rep end function ld.graph3d:Frustum_outline(B, R, r, V, H) -- or Frustum_outline(B, R, r, H) -- returns a table: -- {side=, section=, visible=, hidden=, tangency=, angle=} if R == r then return self:Cylinder_outline(B,R,V,H) end if not pt3d.isPoint3d(H) then -- format B, R, r, H H = V; V = H-B end if pt3d.dot(V,H-B) < 0 then V = -V end local U = H-B local rep = {} local sin, cos, pi = math.sin, math.cos, math.pi local O, I, J = ld.orthoframe({B, V}) local mat, H1, V1 = self.matrix3d, H, V if not ld.isID3d(mat) then O, I, J = ld.mtransform3d(O,mat), ld.mLtransform3d(I,mat), ld.mLtransform3d(J,mat) H1 = ld.mtransform3d(H,mat); V1 = ld.mLtransform3d(V,mat) end local angle = self:Arg(self:Proj3d(H)-self:Proj3d(B))*ld.rad if angle < 0 then angle = angle+180 elseif angle > 180 then angle = angle-180 end local k = r/R local S = (H-k*B)/(1-k) local S1 = ld.mtransform3d(S,mat); rep.visible = {} rep.hidden = {} rep.side = {} rep.section = {} rep.angle = angle local f = function(t) local A = O + R*(cos(t)*I+sin(t)*J) local N = self.Normal if ld.projection_mode == "central" then N = ld.camera-A end return pt3d.det(N, A-S1, R*(-sin(t)*I+cos(t)*J) ) end local T = ld.solve(f,-pi,pi) if (T ~= nil) and (#T >= 2) then local M1, M2 = O+R*(cos(T[1])*I+sin(T[1])*J), O+R*(cos(T[2])*I+sin(T[2])*J) local t = (T[1]+T[2])/2 local M3 = O+R*(cos(t)*I+sin(t)*J) if not ld.isID3d(mat) then M1, M2 = table.unpack( ld.mtransform3d({M1, M2}, ld.invmatrix3d(mat)) ) end local M4, M5 = table.unpack( ld.scale3d({M1,M2}, k, S) ) rep.tangency = {M1,M2,M4,M5} local sens = 1 if self:Cosine_incidence(M3-O,O) < 0 then sens = -1 end rep.visible = {M1, "m", M4, "l"} if self:Cosine_incidence(V1,H1) > 0 then -- second circular base visible table.append(rep.visible, {H, V, "c", M5, "m"}) table.insert(rep.section, {M4,H,V,"c"}) else table.append(rep.visible, {H, M5, r, sens, V, "ca"}) table.append(rep.hidden, {M4, "m", H, M5, r, -sens, V, "ca"}) end table.append(rep.visible, {M2, "l"}) if self:Cosine_incidence(V1,O) < 0 then -- first circular base visible table.append(rep.visible, {B, V, "c"}) table.insert(rep.section, {M1,B,V,"c"}) else -- first circular base not visible table.append(rep.visible, {B, M1, R, -sens, V, "ca"} ) table.append(rep.hidden, {M2, "m", B, M1, R, sens, V, "ca"} ) end rep.side = {M1,"m",M4,"l",H,M5,r,sens,V,"ca",M2,"l",B,M1,R,-sens,V,"ca"} else O, I, J = ld.orthoframe({B, V}) if self:Cosine_incidence(V1,H1) > 0 then -- second circular base visible table.append(rep.visible, {H+r*I, "m", H, V, "c"}) table.insert(rep.section, {H+r*I, "m", H, V, "c"}) else table.append(rep.hidden, {H+r*I, "m", H, V, "c"}) end if self:Cosine_incidence(V1,O) < 0 then -- first circular base visible table.append(rep.visible, {B+R*I, "m", B, V, "c"}) table.insert(rep.section, {B+R*I, "m", B, V, "c"}) else table.append(rep.hidden, {B+R*I, "m", B, V, "c"}) end end return rep end function ld.graph3d:Sphere_outline(C, R) -- C=center, R=radius -- returns a table: -- { visible=, data=<{center,radius,normal}> } local O1, R1 = C, R local mat = ld.invmatrix3d( self.matrix3d ) local N1 = ld.mLtransform3d(self.Normal,mat) if ld.projection_mode == "central" then local S = {C,R} local cam = ld.mtransform3d(ld.camera,mat) local S1 = { (cam+C)/2, pt3d.abs(C-cam)/2} O1, R1, N1 = ld.interSS(S,S1) end local O, I, J = ld.orthoframe({O1,N1}) local rep = {} rep.visible = {O1+R1*I,O1,N1,"c"} rep.data = {O1,R1,N1} return rep end ------------------------ tangency -------------------------------------- function ld.graph3d:Cone_tangency(B, R, V, H) -- or Cone_tangency(B, R, H) -- returns a list of points local rep = self:Cone_outline(B, R, V, H) return rep.tangency end function ld.graph3d:Cylinder_tangency(B, R, V, H) -- or Cylinder_tangency(B, R, H) -- returns a list of points local rep = self:Cylinder_outline(B, R, V, H) return rep.tangency end function ld.graph3d:Frustum_tangency(B, R, r, V, H) -- or Frustum_tangency(B, R, r, H) -- returns a list of points local rep = self:Frustum_outline(B, R, r, V, H) return rep.tangency end function ld.graph3d:Sphere_tangency(C, R, P) -- C=center, R=radius, P=plane -- returns a list of points local B, r, n = ld.interPS(P, {C,R}) if B == nil then return end local sin, cos, pi = math.sin, math.cos, math.pi local O, I, J = ld.orthoframe({B,n}) local mat, C1 = self.matrix3d, C local rep = {} local invmat = ld.invmatrix3d(mat) if not ld.isID3d(mat) then O, I, J = ld.mtransform3d(O,mat), ld.mLtransform3d(I,mat), ld.mLtransform3d(J,mat) C1 = ld.mtransform3d(C,mat) end local f = function(t) local A1 = O + r*(cos(t)*I+sin(t)*J) local A = ld.mtransform3d(A1,invmat) local N = self.Normal if ld.projection_mode == "central" then N = ld.camera-A1 end return pt3d.dot( ld.mLtransform3d(N, invmat), C-A) end local T = ld.solve(f,-pi,pi) if (T ~= nil) and (#T >= 2) then local M1, M2 = O+r*(cos(T[1])*I+sin(T[1])*J), O+r*(cos(T[2])*I+sin(T[2])*J) if not ld.isID3d(mat) then rep = ld.mtransform3d({M1, M2}, invmat) else rep = {M1, M2} end end return rep end ------------------------------ drawing methods ------------------------- function ld.graph3d:Dcylinder(A,r,V,B,args) -- ou Dcylinder(A,r,B,args): cylindre droit de A vers B -- ou Dcylinder(A,V,r,args): ancienne syntaxe, -- dessine un cylindre en fil de fer -- A est le centre d'une face circulaire de rayon r orthogonale au vecteur V -- l'autre face a pour centre B -- args est une table à 6 champs : -- {mode =0/1, hiddenstyle="dotted", hiddencolor = linecolor, edgecolor=linecolor, color="", opacity=1} -- mode = 0 fil de fer -- mode = 1 grille -- color = "" : pas de remplissage, color ~= "" remplissage avec ball color if pt3d.isPoint3d(r) then -- ancienne syntaxe A,V,r,args local R = r r = V; V = R; args = B; B = A+V elseif not pt3d.isPoint3d(B) then -- syntaxe A,r,B,args args = B; B = V; V = B-A end args = args or {} args.color = args.color or "" args.color = self:Define_temp_color(args.color) args.edgecolor = args.edgecolor or self.param.linecolor args.edgestyle = args.edgestyle or self.param.linestyle args.edgewidth = args.edgewidth or self.param.linewidth args.hiddencolor = args.hiddencolor or args.edgecolor args.hiddenstyle = args.hiddenstyle or ld.Hiddenlinestyle --if not Hiddenlines then args.hiddenstyle = "noline" end args.mode = args.mode or 0 args.opacity = args.opacity or 1 args.gradsection = args.gradsection or {25,18,50} args.gradside= args.gradside or {50,10,100} local lsection, msection, rsection = table.unpack( args.gradsection) local lside, mside, rside = table.unpack( args.gradside) local gradStyleSide = "left color="..args.color.."!"..tostring(lside)..",right color = "..args.color.."!"..tostring(rside)..",middle color="..args.color.."!"..tostring(mside) local gradStyleSection = "left color="..args.color.."!"..tostring(lsection)..",right color = "..args.color.."!"..tostring(rsection)..",middle color="..args.color.."!"..tostring(msection) local oldfillstyle = self.param.fillstyle local oldfillopacity = self.param.fillopacity local oldfillcolor = self.param.fillcolor local oldlinestyle = self.param.linestyle local oldlineopacity = self.param.lineopacity local oldlinecolor = self.param.linecolor local oldlinewidth = self.param.linewidth local cyl = self:Cylinder_outline(A,r,V,B) local angle = cyl.angle if args.color ~= "" then --fill side and sections gradStyleSide = gradStyleSide..",shading angle="..ld.strReal(angle) gradStyleSection = gradStyleSection..",shading angle="..ld.strReal(angle) self:Filloptions("gradient", gradStyleSide,args.opacity) self:Linestyle("noline") self:Dpath3d(cyl.side) self:Filloptions("gradient", gradStyleSection,args.opacity) for _, p in ipairs(cyl.section) do self:Dpath3d(p) end end if args.mode ~= ld.mGrid then -- edges self:Filloptions("none") self:Lineoptions(args.edgestyle,args.edgecolor,args.edgewidth) self:Dpath3d(cyl.visible) if (args.hiddenstyle ~= "noline") then -- partie cachée self:Lineoptions(args.hiddenstyle,args.hiddencolor,args.edgewidth) self:Dpath3d(cyl.hidden) end else self:Dpoly(ld.cylinder(A,r,V,B,35,false), {mode=0,hiddenstyle=args.hiddenstyle, edgecolor=args.edgecolor,hiddencolor=args.hiddencolor, reverse=(self:Det3d()<0), edgestyle=args.edgestyle, edgewidth=args.edgewidth}) end self:Filloptions(oldfillstyle,oldfillcolor,oldfillopacity) self:Lineoptions(oldlinestyle,oldlinecolor,oldlinewidth) self:Lineopacity(oldlineopacity) end function ld.graph3d:Dcone(C,r,V,A,args) -- ou Dcone(C,r,A,args) -- ou Dcone(A,V,r,args) (ancienne syntaxe) -- dessine un cône en fil de fer -- A est le sommet -- le centre de la face circulaire de rayon r orthogonale au vecteur V est C -- args est une table à 5 champs : -- {mode =0/1, hiddenstyle="dotted", hiddencolor = linecolor, edgecolor= linecolor, color="", opacity=1} -- mode = 0 fil de fer -- mode = 1 grille -- color = "" : pas de remplissage, color ~= "" remplissage avec gradient bi linéaire if pt3d.isPoint3d(r) then -- ancien format : sommet, vecteur, rayon, args (cône droit) args = A; A = C r, V = V, r C = A+V elseif not pt3d.isPoint3d(A) then -- format C,r,A,args (cône droit) args = A; A = V; V = A-C end args = args or {} args.color = args.color or "" args.color = self:Define_temp_color(args.color) args.edgecolor = args.edgecolor or self.param.linecolor args.edgestyle = args.edgestyle or self.param.linestyle args.edgewidth = args.edgewidth or self.param.linewidth args.hiddencolor = args.hiddencolor or args.edgecolor args.hiddenstyle = args.hiddenstyle or ld.Hiddenlinestyle --if not Hiddenlines then args.hiddenstyle = "noline" end args.mode = args.mode or 0 args.opacity = args.opacity or 1 args.gradsection = args.gradsection or {25,18,50} args.gradside= args.gradside or {50,10,100} local lsection, msection, rsection = table.unpack( args.gradsection) local lside, mside, rside = table.unpack( args.gradside) local gradStyleSide = "left color="..args.color.."!"..tostring(lside)..",right color = "..args.color.."!"..tostring(rside)..",middle color="..args.color.."!"..tostring(mside) local gradStyleSection = "left color="..args.color.."!"..tostring(lsection)..",right color = "..args.color.."!"..tostring(rsection)..",middle color="..args.color.."!"..tostring(msection) local oldfillstyle = self.param.fillstyle local oldfillopacity = self.param.fillopacity local oldfillcolor = self.param.fillcolor local oldlinestyle = self.param.linestyle local oldlineopacity = self.param.lineopacity local oldlinecolor = self.param.linecolor local oldlinewidth = self.param.linewidth local cone = self:Cone_outline(C,r,V,A) local angle = cone.angle if args.color ~= "" then --fill side and sections gradStyleSide = gradStyleSide..",shading angle="..ld.strReal(angle) gradStyleSection = gradStyleSection..",shading angle="..ld.strReal(angle) self:Filloptions("gradient", gradStyleSide,args.opacity) self:Linestyle("noline") self:Dpath3d(cone.side) self:Filloptions("gradient", gradStyleSection,args.opacity) for _, p in ipairs(cone.section) do self:Dpath3d(p) end end if args.mode ~= ld.mGrid then -- edges self:Filloptions("none") self:Lineoptions(args.edgestyle,args.edgecolor,args.edgewidth) self:Dpath3d(cone.visible) if (args.hiddenstyle ~= "noline") then -- partie cachée self:Lineoptions(args.hiddenstyle,args.hiddencolor,args.edgewidth) self:Dpath3d(cone.hidden) end else self:Dpoly(ld.cone(C,r,V,A,35,false), {mode=0,hiddenstyle=args.hiddenstyle, edgecolor=args.edgecolor,hiddencolor=args.hiddencolor, reverse=(self:Det3d()<0), edgestyle=args.edgestyle, edgewidth=args.edgewidth}) end self:Filloptions(oldfillstyle,oldfillcolor,oldfillopacity) self:Lineoptions(oldlinestyle,oldlinecolor,oldlinewidth) self:Lineopacity(oldlineopacity) end function ld.graph3d:Dfrustum(A,R,r,V,B,args) -- ou Dfrustum(A,R,r,V,args) pour un cône droit -- frustum drawn without facets (tronc de cône) -- dessine un tronc de cône en fil de fer -- A est le centre de la face de rayon R -- le centre de l'autre face C=A+V et son rayon est r -- args est une table à 5 champs : -- {mode =0/1, hiddenstyle="dotted", hiddencolor = linecolor, edgecolor=linecolor, color="", opacity=1} -- mode = 0 fil de fer -- mode = 1 grille -- color = "" : pas de remplissage, color ~= "" remplissage avec linéaire if R == r then -- cylinder if not isPoint3d(B) then self:Dcylinder(A,V,R,B) -- B is args in this case else self:Dcylinder(A,R,V,B,args) end return end if not pt3d.isPoint3d(B) then -- frustum(A,R,r,B,args) args = B B = V end args = args or {} args.old = args.old or false if args.old then B = A+B end args.color = args.color or "" args.color = self:Define_temp_color(args.color) args.edgecolor = args.edgecolor or self.param.linecolor args.edgestyle = args.edgestyle or self.param.linestyle args.edgewidth = args.edgewidth or self.param.linewidth args.hiddencolor = args.hiddencolor or args.edgecolor args.hiddenstyle = args.hiddenstyle or ld.Hiddenlinestyle --if not Hiddenlines then args.hiddenstyle = "noline" end args.mode = args.mode or 0 args.opacity = args.opacity or 1 args.gradsection = args.gradsection or {25,18,50} args.gradside= args.gradside or {50,10,100} local lsection, msection, rsection = table.unpack( args.gradsection) local lside, mside, rside = table.unpack( args.gradside) local gradStyleSide = "left color="..args.color.."!"..tostring(lside)..",right color = "..args.color.."!"..tostring(rside)..",middle color="..args.color.."!"..tostring(mside) local gradStyleSection = "left color="..args.color.."!"..tostring(lsection)..",right color = "..args.color.."!"..tostring(rsection)..",middle color="..args.color.."!"..tostring(msection) local oldfillstyle = self.param.fillstyle local oldfillopacity = self.param.fillopacity local oldfillcolor = self.param.fillcolor local oldlinestyle = self.param.linestyle local oldlineopacity = self.param.lineopacity local oldlinecolor = self.param.linecolor local oldlinewidth = self.param.linewidth local frustum = self:Frustum_outline(A,R,r,V,B) local angle = frustum.angle if args.color ~= "" then --fill side and sections print("ok") gradStyleSide = gradStyleSide..",shading angle="..ld.strReal(angle) gradStyleSection = gradStyleSection..",shading angle="..ld.strReal(angle) self:Filloptions("gradient", gradStyleSide,args.opacity) self:Linestyle("noline") self:Dpath3d(frustum.side) self:Filloptions("gradient", gradStyleSection,args.opacity) for _, p in ipairs(frustum.section) do self:Dpath3d(p) end end if args.mode ~= ld.mGrid then -- edges self:Filloptions("none") self:Lineoptions(args.edgestyle,args.edgecolor,args.edgewidth) self:Dpath3d(frustum.visible) if (args.hiddenstyle ~= "noline") then -- partie cachée self:Lineoptions(args.hiddenstyle,args.hiddencolor,args.edgewidth) self:Dpath3d(frustum.hidden) end else self:Dpoly(ld.frustum(A,R,r,V,B,35,false), {mode=0,hiddenstyle=args.hiddenstyle, edgecolor=args.edgecolor,hiddencolor=args.hiddencolor, reverse=(self:Det3d()<0), edgestyle=args.edgestyle, edgewidth=args.edgewidth}) end self:Filloptions(oldfillstyle,oldfillcolor,oldfillopacity) self:Lineoptions(oldlinestyle,oldlinecolor,oldlinewidth) self:Lineopacity(oldlineopacity) end function ld.graph3d:Dsphere(A,r,args) -- dessine une sphère en fil de fer -- A est le sommet, r le rayon -- args est une table à 5 champs : -- {mode=0/1/2, hiddenstyle="dotted", hiddencolor = linecolor, edgecolor=linecolor,color="", opacity=1} -- color = "" : pas de remplissage, color ~= "" remplissage avec ball color -- si mode 1 : edgestyle = linestyle, edgecolor = linecolor, edgewidth = linewidth -- mode = 0 contour avec équateur -- mode = 1 contour avec méridiens et fuseaux -- mode = 2 contour seulement (cercle) args = args or {} args.color = args.color or "" args.edgecolor = args.edgecolor or self.param.linecolor args.hiddencolor = args.hiddencolor or args.edgecolor args.hiddenstyle = args.hiddenstyle or ld.Hiddenlinestyle --if not Hiddenlines then args.hiddenstyle = "noline" end args.edgestyle = args.edgestyle or self.param.linestyle args.edgecolor = args.edgecolor or self.param.linecolor args.edgewidth = args.edgewidth or self.param.linewidth args.mode = args.mode or 0 args.opacity = args.opacity or 1 local oldfillstyle = self.param.fillstyle local oldfillopacity = self.param.fillopacity local oldfillcolor = self.param.fillcolor local oldlinestyle = self.param.linestyle local oldlineopacity = self.param.lineopacity local oldlinecolor = self.param.linecolor local oldlinewidth = self.param.linewidth self:Linecolor(args.edgecolor) local V = pt3d.vecK self:Filloptions("none","black") if args.color ~= "" then self:Filloptions("gradient", "ball color="..args.color, args.opacity) end --self:Dcircle(self:Proj3d(A),r) local L = self:Sphere_outline(A,r) self:Lineoptions(args.edgestyle,args.edgecolor,args.edgewidth) self:Dpath3d(L.visible) if args.mode == 0 then -- équateur local M1, M2 = table.unpack( self:Sphere_tangency(A,r,{A,V}) ) local M3 = ld.rotate3d(M1,90,{A,V}) local sens local M4, I = table.unpack( ld.mtransform3d({M3,A}, self.matrix3d) ) if self:Cosine_incidence(M4-I,M4) > 0 then sens = 1 else sens = -1 end self:Filloptions("none") --; self:Lineoptions(args.edgestyle,args.edgecolor,args.edgewidth) self:Darc3d(M1,A,M2,r,sens,V) self:Lineoptions(args.hiddenstyle,args.hiddencolor) self:Darc3d(M1,A,M2,r,-sens,V) elseif args.mode == 1 then -- grille self:Dpoly(ld.sphere(A,r),{mode=0,hiddenstyle=args.hiddenstyle,hiddencolor=args.hiddencolor,edgestyle=args.edgestyle,edgecolor=args.edgecolor,edgewidth=args.edgewidth,reverse=(self:Det3d()<0)}) end self:Filloptions(oldfillstyle,oldfillcolor,oldfillopacity) self:Lineoptions(oldlinestyle,oldlinecolor,oldlinewidth); self:Lineopacity(oldlineopacity) end