-- luadraw_central_perspective.lua 
-- date 2026/06/13
-- version 3.2
-- 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 to be redefined
local graph3d = luadraw.graph3d
local ld = luadraw
local cpx = ld.cpx
local Z = cpx.Z
local pt3d = ld.pt3d
local toPoint3d = pt3d.toPoint3d
local isPoint3d = pt3d.isPoint3d
local Origin, vecI, vecJ, vecK = pt3d.Origin, pt3d.vecI, pt3d.vecJ, pt3d.vecK
local M, Mc, Ms = pt3d.M, pt3d.Mc, pt3d.Ms
local map = ld.map
local notDef = ld.notDef

local old_Dcone = graph3d.Dcone
local old_Dcylinder = graph3d.Dcylinder
local old_Dfrustum = graph3d.Dfrustum
local old_Dsphere = graph3d.Dsphere
local old_Proj3d = graph3d.Proj3d
local old_Proj3dV = graph3d.Proj3dV
local old_Cosine_incidence = graph3d.Cosine_incidence
local old_Isvisible = graph3d.Isvisible
local old_Screenpos = graph3d.Screenpos
local old_Observer_distance = graph3d.Observer_distance

local camera = nil
local target = Origin

--This function is automatically called by the perspective function.
function luadraw.central_perspective(theta,phi,d,look) -- or central_perspective(camera, look)
    local N
    local cos, sin, tet, ph = math.cos, math.sin
    if isPoint3d(theta) then
        camera = theta
        target = phi or Origin
        if type(target) == "number" then target = Origin end
        N = pt3d.normalize(camera-target)
        ph = pt3d.angle(vecK, N) 
        phi = ph*ld.rad -- phi en degrés
        tet = pt3d.angle(vecI, ld.pxy(N))
        theta = tet*ld.rad -- theta en degrés
        d = pt3d.abs(camera-target)
    else
        camera = nil
        theta = theta or 30
        phi = phi or 60
        d = d or 15
        target = look or Origin
        d = math.abs(d)
        tet, ph = theta*ld.deg, phi*ld.deg
    end
    local cosTheta, sinTheta, cosPhi, sinPhi = cos(tet), sin(tet), cos(ph), sin(ph)
    if N == nil then N = M(cosTheta*sinPhi,sinTheta*sinPhi,cosPhi) end
    local mat = {Origin, M(-sinTheta,-cosPhi*cosTheta,cosTheta*sinPhi), M(cosTheta,-cosPhi*sinTheta,sinTheta*sinPhi), M(0,sinPhi,cosPhi) } -- mat.A(x,y,z) gives (a,b,c), and Z(a,b) is the affix of the projection of A on screen
    local invmat = ld.invmatrix3d(mat) -- if Z(a,b) is the affix of the projection on screen, invmat.(a,b,0) gives A(x,y,z) on the screen so that the projection of A has the affix Z(a,b)
    if camera == nil then camera = target+d*N end -- camera
    local Zlookat = Z( cosTheta*target.y-sinTheta*target.x, -cosPhi*cosTheta*target.x-cosPhi*sinTheta*target.y+sinPhi*target.z)
    
    
    function graph3d:Isvisible(facet)
    -- facet est une liste de points 3d coplanaires
    -- la fonction renvoie true si la facette est visible (vecteur normal de même sens que n)
        local N = pt3d.prod(facet[2]-facet[1], facet[3]-facet[1])
        return self:Cosine_incidence(N, pt3d.isobar3d(facet)) > 0 
    end    

    function graph3d:Proj3d(L) -- we redefine Proj3d
        local f = function(A)
            if isPoint3d(A) then
                local den = pt3d.dot(camera-A,N)
                if math.abs(den) > 1e-17 then
                    local k = d/den
                    local A1 = camera-k*(camera-A)
                    return Z( cosTheta*A1.y-sinTheta*A1.x, -cosPhi*cosTheta*A1.x-cosPhi*sinTheta*A1.y+sinPhi*A1.z)-Zlookat
                end
            else return A
            end
        end
        
        L = self:Mtransform3d(L) -- we apply the 3D matrix of the graph
        return ld.ftransform3d(L,f) -- we return the projection on screen
    end 
    
    function graph3d:Proj3dV(L,A) -- we redefine Proj3dV, the origin for the vectors is the point A (target by defaut)
        A = A or target
        local B = self:Proj3d(A)
        local f = function(v)
            if isPoint3d(v) then
                return self:Proj3d(A+v)-B
            else return v
            end
        end
        return ld.ftransform3d(L,f) -- we return the projection on screen
    end 
    
    function graph3d:Screenpos(z,d)
    -- renvoie les coordonnées spatiales d'un point ayant comme projeté sur l'écran le point d'affixe z,
    -- et se trouvant à une distance d (algébrique) du plan de l'écran
        z = cpx.toComplex(z)
        return ld.applymatrix3d(M(z.re+Zlookat.re, z.im+Zlookat.im, pt3d.dot(target,N)), invmat)
    end
    
    function graph3d:Cosine_incidence(n,A)
    -- cosinus de l'angle d'incidence entre le vecteur n au point A et le vecteur dirigé vers l'observateur
        return pt3d.dot(pt3d.normalize(camera-A),n)
    end
    
    function graph3d:Observer_distance(A)
    -- l'abscisse de A sur l'axe issue de Origine, dirigé vers l'observateur
        return -pt3d.abs(camera-A)
    end
    
    function graph3d:arc3db(B,A,C,r,sens,n)
        local n1, n2, V = pt3d.normalize(B-A), pt3d.normalize(C-A)
        if n == nil then V = pt3d.normalize(pt3d.prod(n1,n2)) else V = pt3d.normalize(n) end
        if pt3d.abs(V)<1e-12 then V = pt3d.normalize(n) end
        B = A+r*n1; C = A+r*n2
        local U = pt3d.prod(V,self.Normal)
        if pt3d.abs(U)<1e-16 then --plans parallèles
            local A1 = ld.interDP( {camera,A-camera},{target,self.Normal})
            local alpha = pt3d.abs(A1-camera)/pt3d.abs(A-camera)--*0.99
            --self:Darc(self:Proj3d(B), self:Proj3d(A), self:Proj3d(C),r*alpha,sens,draw_options)
            return ld.arc3db(B,A,C,r*alpha,sens,n)
        elseif math.abs( pt3d.dot(V,self.Normal) ) < 1e-8 then --plans perpendiculaires
            return {B,C,"l"} -- segment [C,B]
        else 
            local N2 = pt3d.normalize(pt3d.prod(U,V))
            local N1 = pt3d.normalize(U)
            local a1,a2,a3,a4,b,c,O,u,v
            a1,a2,a3,a4,b,c = table.unpack( self:Proj3d({A-r*N2, A+r*N2, A-r*N1, A+r*N1, B, C}) )
            O = (a1+a2)/2;  u = cpx.normalize(a4-a3); v = a1-O
            local mat = {O,cpx.abs(v)*u,v}            
            if math.abs(cpx.det(mat[2],mat[3])) < 1e-8 then 
                return {B,C,"l"} -- segment [C,B]
            end
            local invm = ld.invmatrix(mat)
            a4,b,c = table.unpack( ld.mtransform({a4,b,c}, ld.invmatrix(mat)) ) 
            local y = a4.im 
            local x = a4.re
            local alpha = x/math.sqrt(1-y^2)
            local L = ld.mtransform(ld.ellipticarcb(b,0,c,alpha,1,sens), mat)
            local ret = map(function(z) if type(z) == "string" then return z else return self:Screenpos(z) end end, L)
            ret[1] = B; ret[#ret-1] = C
            return ret
        end
    end
    
    function graph3d:circle3db(O,r,n)
        local U = pt3d.prod(n,self.Normal)
        if pt3d.abs(U) < 1e-12 then -- U est nul
            U = pt3d.prod(n,vecJ)
            if pt3d.abs(U) < 1e-12 then -- U est nul
                U = pt3d.prod(n,vecI)
            end
        end
        U = O+r*pt3d.normalize(U)
        return self:arc3db(U,O,U,r,1,n)
    end
    
    function graph3d:Dcone(C,r,V,A,args) 
    -- ou Dcone(C,r,A,args)
    -- 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 mWireframe
    -- mode = 1 mGrid
    -- color = "" : pas de remplissage, color ~= "" remplissage avec gradient bi linéaire
        if isPoint3d(r) then -- ancien format : sommet, vecteur, rayon, args (cône droit)
        args = A; A = C
        r, V = V, r
        C = A+V; V = A-C
        elseif not isPoint3d(A) then -- format C,r,A,args (cône droit)
            args = A; A = V; V = A-C
        end
        args = args or {}
        local color = args.color
        color = color or ""
        color = self:Define_temp_color(color)
        local edgecolor = args.edgecolor or self.param.linecolor
        local hiddenstyle = args.hiddenstyle or ld.Hiddenlinestyle
        local hiddencolor = args.hiddencolor or self.param.linecolor
        --if not Hiddenlines then hiddenstyle = "noline" end        
        local mode = args.mode or ld.mWireframe
        local opacity = args.opacity or 1
        local edgewidth = args.edgewidth or self.param.linewidth
        local edgestyle = args.edgestyle or self.param.linestyle
        
        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="..color.."!"..tostring(lside)..",right color = "..color.."!"..tostring(rside)..",middle color="..color.."!"..tostring(mside)
        local gradStyleSection = "left color="..color.."!"..tostring(lsection)..",right color = "..color.."!"..tostring(rsection)..",middle color="..color.."!"..tostring(msection)
                
        local angle = cpx.arg( self:Proj3d(A)-self:Proj3d(C))*ld.rad
        if angle < 0 then angle = angle+180
        elseif angle > 180 then angle = angle-180 
        end
        local P = ld.cone(C,r,V,A,50,true)
        local Pv, Ph = self:Classifyfacet(P)
        local BPv, BPh = ld.border(Pv), ld.border(Ph)
        
        local oldfillstyle = self.param.fillstyle
        local oldfillopacity = self.param.fillopacity
        local oldfillcolor = self.param.fillcolor
        local oldlinestyle = self.param.linestyle
        local oldlinecolor = self.param.linecolor
        local oldlinewidth = self.param.linewidth
        self:Lineoptions(edgestyle,edgecolor,edgewidth)
        if mode == ld.mGrid then self:Linestyle("noline") end
        -- hidden part
        if color ~= "" then 
            self:Filloptions("gradient", gradStyleSection..",shading angle="..ld.strReal(angle),args.opacity)
        end
        if mode ~= ld.mWireframe  then self:Dpolyline3d(BPh, true) end
        --visible part
        if color ~= "" then 
            self:Filloptions("gradient", gradStyleSide..",shading angle="..ld.strReal(angle),args.opacity)
        end
        self:Dpolyline3d(BPv, true)
        if color ~= "" then 
            self:Filloptions("gradient", gradStyleSection..",shading angle="..ld.strReal(angle),args.opacity)
        end
        if self:Cosine_incidence(-V,C) > 0 then self:Dcircle3d(C,r,V) end
        -- hidden edges
        if (mode ~= ld.mGrid) and (hiddenstyle ~= "noline") then -- partie cachée
            self:Filloptions("none"); self:Linestyle(hiddenstyle)
            self:Dpolyline3d(BPh, true)
        end
        if mode == ld.mGrid then -- edges
            --self:Linestyle(oldlinestyle)
            self:Dpoly(ld.cone(C,r,V,A,35,true), {mode=ld.mWireframe,hiddenstyle=hiddenstyle,hiddencolor=hiddencolor,edgecolor=edgecolor,edgestyle=edgestyle,edgewidth=edgewidth})
        end
        self:Filloptions(oldfillstyle,oldfillcolor,oldfillopacity)
        self:Lineoptions(oldlinestyle,oldlinecolor,oldlinewidth); 
    end
    
    function graph3d:Dcylinder(C,r,V,A,args) 
    -- ou Dcylinder(C,r,A,args)
    -- dessine un cylindre en fil de fer
    -- C est le centre d'une face circulaire de rayon r orthogonale au vecteur V
    -- l'autre face a pour centre A 
    -- args est une table à 5 champs :
    -- {mode =0/1, hiddenstyle="dotted", hiddencolor = linecolor, edgecolor= linecolor, color="", opacity=1}
    -- mode = 0 mWireframe
    -- mode = 1 mGrid
    -- color = "" : pas de remplissage, color ~= "" remplissage avec gradient bi linéaire
        if isPoint3d(r) then -- ancien format : centre, vecteur, rayon, args (cylindre droit)
        args = A; 
        r, V = V, r
        A = C+V
        elseif not isPoint3d(A) then -- format C,r,A,args (cylindre droit)
            args = A; A = V; V = A-C
        end
        args = args or {}
        local color = args.color
        color = color or ""
        color = self:Define_temp_color(color)
        local edgecolor = args.edgecolor or self.param.linecolor
        local hiddenstyle = args.hiddenstyle or ld.Hiddenlinestyle
        local hiddencolor = args.hiddencolor or self.param.linecolor
        --if not Hiddenlines then hiddenstyle = "noline" end
        local mode = args.mode or ld.mWireframe
        local opacity = args.opacity or 1
        local edgewidth = args.edgewidth or self.param.linewidth
        local edgestyle = args.edgestyle or self.param.linestyle
        
        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="..color.."!"..tostring(lside)..",right color = "..color.."!"..tostring(rside)..",middle color="..color.."!"..tostring(mside)
        local gradStyleSection = "left color="..color.."!"..tostring(lsection)..",right color = "..color.."!"..tostring(rsection)..",middle color="..color.."!"..tostring(msection)
        
        local angle = cpx.arg( self:Proj3d(A)-self:Proj3d(C))*ld.rad
        if angle < 0 then angle = angle+180
        elseif angle > 180 then angle = angle-180 
        end
        local P = ld.cylinder(C,r,V,A,50,true)
        local Pv, Ph = self:Classifyfacet(P)
        local BPv, BPh = ld.border(Pv), ld.border(Ph)
        local oldfillstyle = self.param.fillstyle
        local oldfillopacity = self.param.fillopacity
        local oldfillcolor = self.param.fillcolor
        local oldlinestyle = self.param.linestyle
        local oldlinecolor = self.param.linecolor
        local oldlinewidth = self.param.linewidth
        self:Lineoptions(edgestyle,edgecolor,edgewidth)
        if mode == ld.mGrid then self:Linestyle("noline") end
        -- hidden part
        if color ~= "" then 
            self:Filloptions("gradient", gradStyleSection..",shading angle="..ld.strReal(angle),args.opacity)
        end
        --if mode ~= ld.mWireframe  then self:Dpolyline3d(BPh, true) end
        --visible part
        if color ~= "" then 
            self:Filloptions("gradient", gradStyleSide..",shading angle="..ld.strReal(angle),args.opacity)
        end
        self:Dpolyline3d(BPv,true)
        if color ~= "" then 
            self:Filloptions("gradient", gradStyleSection..",shading angle="..ld.strReal(angle),args.opacity)
        end
        if self:Cosine_incidence(V,A) > 0 then self:Dcircle3d(A,r,V) end
        if self:Cosine_incidence(-V,C) > 0 then self:Dcircle3d(C,r,V) end
        -- hidden edges
        if (mode ~= ld.mGrid) and (hiddenstyle ~= "noline") then -- partie cachée
            self:Filloptions("none"); self:Linestyle(hiddenstyle)
            self:Dpolyline3d(BPh, true)
        end
        if mode == ld.mGrid then -- edges
            --self:Linestyle(oldlinestyle)
            self:Dpoly(ld.cylinder(C,r,V,A,35,false), {mode=ld.mWireframe,hiddenstyle=hiddenstyle,hiddencolor=hiddencolor,edgecolor=edgecolor, edgewidth=edgewidth, edgestyle=edgestyle})
        end
        self:Filloptions(oldfillstyle,oldfillcolor,oldfillopacity)
        self:Lineoptions(oldlinestyle,oldlinecolor,oldlinewidth); 
    end
     
    function graph3d:Dfrustum(A,R,r,V,B,args)
    -- 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
        local C
        if isPoint3d(B) then -- slanted frustum
            C = ld.dproj3d(B,{A,V})
            V = C-A
        else C = A+V; args = B; B = C
        end
        
        args = args or {}
        local color = args.color
        color = color or ""
        color = self:Define_temp_color(color)
        local edgecolor = args.edgecolor or self.param.linecolor
        local hiddenstyle = args.hiddenstyle or ld.Hiddenlinestyle
        local hiddencolor = args.hiddencolor or self.param.linecolor
        --if not Hiddenlines then hiddenstyle = "noline" end        
        local mode = args.mode or ld.mWireframe
        local opacity = args.opacity or 1
        local edgewidth = args.edgewidth or self.param.linewidth
        local edgestyle = args.edgestyle or self.param.linestyle
        
        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="..color.."!"..tostring(lside)..",right color = "..color.."!"..tostring(rside)..",middle color="..color.."!"..tostring(mside)
        local gradStyleSection = "left color="..color.."!"..tostring(lsection)..",right color = "..color.."!"..tostring(rsection)..",middle color="..color.."!"..tostring(msection)
                
        local angle = cpx.arg( self:Proj3d(A)-self:Proj3d(B))*ld.rad
        if angle < 0 then angle = angle+180
        elseif angle > 180 then angle = angle-180 
        end
        local P = ld.frustum(A,R,r,V,B,50,true)
        local Pv, Ph = self:Classifyfacet(P)
        local BPv, BPh = ld.border(Pv), ld.border(Ph)
        local oldfillstyle = self.param.fillstyle
        local oldfillopacity = self.param.fillopacity
        local oldfillcolor = self.param.fillcolor
        local oldlinestyle = self.param.linestyle
        local oldlinecolor = self.param.linecolor
        local oldlinewidth = self.param.linewidth
        self:Lineoptions(edgestyle,edgecolor,edgewidth)
        if mode == ld.mGrid then self:Linestyle("noline") end
        -- hidden part
        if color ~= "" then 
            self:Filloptions("gradient", gradStyleSection..",shading angle="..ld.strReal(angle),args.opacity)
        end
        if mode ~= ld.mWireframe  then self:Dpolyline3d(BPh, true) end
        --visible part
        if color ~= "" then 
            self:Filloptions("gradient", gradStyleSide..",shading angle="..ld.strReal(angle),args.opacity)
        end
        self:Dpolyline3d(BPv, true)
       if color ~= "" then 
            self:Filloptions("gradient", gradStyleSection..",shading angle="..ld.strReal(angle),args.opacity)
        end
        if self:Cosine_incidence(V,B) > 0 then self:Dcircle3d(B,r,V) end
        if self:Cosine_incidence(-V,A) > 0 then self:Dcircle3d(A,R,V) end
        -- hidden edges
        if (mode ~= ld.mGrid) and (hiddenstyle ~= "noline") then -- partie cachée
            self:Filloptions("none"); self:Linestyle(hiddenstyle)
            self:Dpolyline3d(BPh, true)
        end
        if mode == ld.mGrid then -- edges
            --self:Linestyle(oldlinestyle)
            self:Dpoly(ld.frustum(A,R,r,V,B,35,false), {mode=ld.mWireframe,hiddenstyle=hiddenstyle,hiddencolor=hiddencolor,edgecolor=edgecolor,edgestyle=edgestyle,edgewidth=edgewidth})
        end
        self:Filloptions(oldfillstyle,oldfillcolor,oldfillopacity)
        self:Lineoptions(oldlinestyle,oldlinecolor,oldlinewidth); 
    end
    
    function 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
        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 = vecK
        if args.color ~= "" then
            self:Filloptions("gradient", "ball color="..args.color, args.opacity)
        else
            self:Filloptions("none")
        end
        local S = {A,r}
        local mat = ld.invmatrix3d( self.matrix3d )
        local cam = ld.mtransform3d(camera,mat)
        local S1 = { (cam+A)/2, pt3d.abs(A-cam)/2}
        local I,R,n = ld.interSS(S,S1)
        self:Lineoptions(args.edgestyle,args.edgecolor,args.edgewidth)
        self:Dcircle3d(I,R,n)
        if  args.mode == 0 then -- équateur
            local M1, M2 = ld.interCS({A,r,V},S1)
            local M3 = ld.rotate3d(M1,90,{A,vecK})
            local sens
            if self:Cosine_incidence(M3-A,M3) > 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})
        end
        self:Filloptions(oldfillstyle,oldfillcolor,oldfillopacity)
        self:Lineoptions(oldlinestyle,oldlinecolor,oldlinewidth); 
        self:Lineopacity(oldlineopacity)
    end
    ld.camera = camera
    ld.target = target

    return {theta,phi,"central"}
end

--This function is automatically called at the next perspective change.
function luadraw.close_central()
    graph3d.Dcone = old_Dcone
    graph3d.Dcylinder = old_Dcylinder
    graph3d.Dfrustum = old_Dfrustum
    graph3d.Dsphere = old_Dsphere
    graph3d.Proj3d = old_Proj3d
    graph3d.Proj3dV = old_Proj3dV
    graph3d.Cosine_incidence = old_Cosine_incidence
    graph3d.Isvisible = old_Isvisible
    graph3d.ScreenPos = old_Screenpos
    graph3d.Observer_distance = old_Observer_distance
end
