% tkz-grapheur-lua.tex
% Copyright 2026 Cédric Pierquet
% Released under the LaTeX Project Public License v1.3c or later, see http://www.latex-project.org/lppl.txt
% macros base [lua] de tkz-grapheur

%====COURBES IMPLICITES
\newlength\pflthickimpl
\tikzset{pflcourbeimplicit/.style={only marks,mark=*}}

\defKV[GraphiqueTikzImplicite]{%
  CoeffPasAuto=\def\pflcoeffpasimpl{#1},step stretch=\def\pflcoeffpasimpl{#1},%
  Pas=\def\pflpasimpl{#1},step=\def\pflpasimpl{#1},%
  Couleur=\def\pflcouleurbimpl{#1},color=\def\pflcouleurbimpl{#1},%
  Epaisseur=\setlength\pflthickimpl{#1},thick=\setlength\pflthickimpl{#1},%
  Precision=\def\pflprecisiimpl{#1},prec=\def\pflprecisiimpl{#1},%
  Tolerance=\def\pfltoleranceimplic{#1},tolerance=\def\pfltoleranceimplic{#1}
}
\setKVdefault[GraphiqueTikzImplicite]{%
  CoeffPasAuto=1,step stretch=1,%
  Pas=auto,step=auto,%
  Couleur=black,color=black,%
  Epaisseur=0.2875pt,thick=0.2875pt,%
  Precision=8,precision=8,%
  Tolerance=1e-10,tolerance=1e-10,%
  lua=true
}

\NewDocumentCommand\TracerCourbeImplicite{ O{} m }{%with jfbu part of code for xint version !
  % #1 = clés (Pas, Couleur, etc)
  % #2 = expression f(x,y) (= 0 implicite)
  \restoreKV[GraphiqueTikzImplicite]%
  \setKV[GraphiqueTikzImplicite]{#1}%
  \ifboolKV[GraphiqueTikzImplicite]{lua}%
  {%
    \ifluatex
    \IfStrEq{\pflpasimpl}{auto}%
    {%
      \pgfmathsetmacro{\pfl@pasx}{\pgfmath@tonumber{\pflthickimpl} * 0.5 * (\pflcoeffpasimpl) / \pgfmath@tonumber{\pgf@xx}}%
      \pgfmathsetmacro{\pfl@pasy}{\pgfmath@tonumber{\pflthickimpl} * 0.5 * (\pflcoeffpasimpl) / \pgfmath@tonumber{\pgf@yy}}%
    }%
    {%
      \def\pfl@pasx{\pflpasimpl}%
      \def\pflpasyimplcalc{\pflpasimpl}%
    }%
    \edef\tmplistepts{\directlua{tex.sprint(pfl.marching_squares("\luaescapestring{#2}",\pflxmin,\pflxmax,\pflymin,\pflymax,\pfl@pasx,\pfl@pasy,\pfltoleranceimplic))}}%
    \begin{scope}
        \clip (\pflxmin,\pflymin) rectangle (\pflxmax,\pflymax);
        \draw[\pflcouleurbimpl] plot[mark size=\pflthickimpl,pflcourbeimplicit] coordinates {\tmplistepts};
    \end{scope}%
    \else
      \PackageWarning{tkz-grapheur}{.: LuaLaTeX not detected, \TracerCourbeImplicite\space with [lua=false] key only available :.}
    \fi
  }%
  {%
  % open a scope limiting group
  \begingroup
  % configure xint to work with less digits
  \xintSetDigits*{\pflprecisiimpl}%
  % définition fonction xint à 2 variables
  \xintdeffloatfunc pflimplicitefct(x,y) = #2 ;%
  %calcul auto du pas
  \IfStrEq{\pflpasimpl}{auto}%
  {%
    \pgfmathsetmacro{\pflpasximplcalc}{\pgfmath@tonumber{\pflthickimpl} * \pflcoeffpasimpl * 1.414 / \pgfmath@tonumber{\pgf@xx}}%
    \pgfmathsetmacro{\pflpasyimplcalc}{\pgfmath@tonumber{\pflthickimpl} * \pflcoeffpasimpl * 1.414 / \pgfmath@tonumber{\pgf@yy}}%
  }%
  {%
    \def\pflpasximplcalc{\pflpasimpl}%
    \def\pflpasyimplcalc{\pflpasimpl}%
  }%
  \def\tmplisteptsh{}%
  \def\tmplisteptsv{}%
  \def\tmplisteptsp{}%
  % double boucle marching squares
  \edef\tmpxcoords{\xintfloateval{\pflxmin..[\pflpasximplcalc]..\pflxmax}}%
  \edef\tmpycoords{\xintfloateval{\pflymin..[\pflpasyimplcalc]..\pflymax}}%
  % attention ensemble de définition ??...
  \def\tmpj{0}%
  % precompute values of function on left vertical border
  \xintFor ##2 in {\tmpycoords}\do{%
    \expandafter\edef\csname tmp_value_\tmpj\endcsname{\xintfloateval{pflimplicitefct(\pflxmin,##2)}}%
    \edef\tmpj{\the\numexpr\tmpj+1}%
    \xintifForLast{% one more
      \expandafter\edef\csname tmp_value_\tmpj\endcsname{\xintfloateval{pflimplicitefct(\pflxmin,##2+\pflpasyimplcalc)}}%
    }{}%
  }%
  \xintFor ##1 in {\tmpxcoords}\do{%
    \edef\tmpC{\csname tmp_value_0\endcsname}%
    \def\tmpj{0}%
    \xintFor ##2 in {\tmpycoords}\do{%
      % évaluer les 3 coins et préparer en même temps la prochaine colonne pré-calculée
      \let\tmpA\tmpC
      \edef\tmpB{\xintfloateval{pflimplicitefct(##1+\pflpasximplcalc,##2)}}%
      % mettre à jour pour la prochaine fois, oui c'est rusé
      \expandafter\let\csname tmp_value_\tmpj\endcsname\tmpB
      \edef\tmpj{\the\numexpr\tmpj+1}%
      % là on est encore avec la colonne en cours, déjà calculée
      \edef\tmpC{\csname tmp_value_\tmpj\endcsname}%
      % changement de signe horizontal
      \xintifboolexpr{sgn(\tmpA)*sgn(\tmpB) < 0}%
        {%
          \xdef\tmplisteptsh{\tmplisteptsh (\xintfloateval{##1-(\tmpA)*\pflpasximplcalc/(\tmpB-\tmpA)},##2)}%
        }{}%
      % changement de signe vertical
      \xintifboolexpr{sgn(\tmpA)*sgn(\tmpC) < 0}%
        {%
          \xdef\tmplisteptsv{\tmplisteptsv (##1,\xintfloateval{##2-(\tmpA)*\pflpasyimplcalc/(\tmpC-\tmpA)})}%
        }{}%
      % si pas de changement de signe détecté → test zéro exact
      \xintifboolexpr{(sgn(\tmpA)*sgn(\tmpB) >= 0) && (sgn(\tmpA)*sgn(\tmpC) >= 0) && (abs(\tmpA) < \pfltoleranceimplic)}%
        {%
          \xdef\tmplisteptsp{\tmplisteptsp (##1,##2)}%
        }{}%
      % le dernier coup il faut en calculer un de plus
      \xintifForLast{%
        \expandafter\edef\csname tmp_value_\tmpj\endcsname{\xintfloateval{pflimplicitefct(##1+\pflpasximplcalc,##2+\pflpasyimplcalc)}}%
      }{}%
    }%
  }%
  \begin{scope}
    \clip (\pflxmin,\pflymin) rectangle (\pflxmax,\pflymax) ;
    \IfEq{\tmplisteptsp}{}{}%
      {%
        \draw[\pflcouleurbimpl] plot[pflcourbeimplicit] coordinates {\tmplisteptsp} ;%
      }%
    \IfEq{\tmplisteptsh}{}{}%
      {%
        \draw[\pflcouleurbimpl] plot[pflcourbeimplicit] coordinates {\tmplisteptsh} ;%
      }%
    \IfEq{\tmplisteptsv}{}{}%
      {%
        \draw[\pflcouleurbimpl] plot[pflcourbeimplicit] coordinates {\tmplisteptsv} ;%
      }%
  \end{scope}%
  \endgroup%
  }%
}
\NewCommandCopy\PlotImplicitCurve\TracerCourbeImplicite

\ifluatex
\RequirePackage{luacode}
\begin{luacode*}
pfl = pfl or {}

function pfl.expr_to_func(expr_str)
    -- Traduction expression LaTeX → Lua
    local e = expr_str
    e = e:gsub("%^",    "^")        -- déjà ok en Lua
    e = e:gsub("sin%(", "math.sin(")
    e = e:gsub("cos%(", "math.cos(")
    e = e:gsub("tan%(", "math.tan(")
    e = e:gsub("exp%(", "math.exp(")
    e = e:gsub("sqrt%(","math.sqrt(")
    e = e:gsub("log%(", "math.log(")
    e = e:gsub("abs%(", "math.abs(")
    e = e:gsub("pi",    tostring(math.pi))
    e = e:gsub("%*%*", "^")   -- ajouter cette ligne dans expr_to_func !
    -- Compilation dynamique
    local fstr = "local x,y = ...; return " .. e
    local f, err = load(fstr)
    if not f then
        texio.write_nl("! pfl.expr_to_func : " .. tostring(err))
        return nil
    end
    return f
end

function pfl.marching_squares(expr_str, xmin, xmax, ymin, ymax, pasx, pasy, eps)
    eps = eps or 1e-12
    local f = pfl.expr_to_func(expr_str)
    if not f then return "" end

    local ni = math.floor((xmax - xmin) / pasx)
    local nj = math.floor((ymax - ymin) / pasy)

    -- Phase 1 : tableau complet F[i][j]
    local F = {}
    local xs = {}
    local ys = {}
    for i = 0, ni do
        xs[i] = xmin + i * pasx
        F[i]  = {}
    end
    for j = 0, nj do
        ys[j] = ymin + j * pasy
    end
    for i = 0, ni do
        for j = 0, nj do
            F[i][j] = f(xs[i], ys[j])
        end
    end

    -- Phase 2 : marching squares
    local pts = {}
    for i = 0, ni - 1 do
        for j = 0, nj - 1 do
            local A = F[i  ][j  ]
            local B = F[i+1][j  ]
            local C = F[i  ][j+1]
            local D = F[i+1][j+1]
            local xi  = xs[i]
            local xip = xs[i+1]
            local yj  = ys[j]
            local yjp = ys[j+1]
            -- Arête A→B
            if A * B < 0 then
                pts[#pts+1] = string.format("(%.3f,%.3g)",
                    xi - A * pasx / (B - A), yj)
            end
            -- Arête A→C
            if A * C < 0 then
                pts[#pts+1] = string.format("(%.3f,%.3g)",
                    xi, yj - A * pasy / (C - A))
            end
            -- Arête C→D
            -- if C * D < 0 then
            --    pts[#pts+1] = string.format("(%.6f,%.6g)",
            --        xi - C * pasx / (D - C), yjp)
            -- end
            -- Arête B→D
            -- if B * D < 0 then
            --     pts[#pts+1] = string.format("(%.6f,%.6g)",
            --         xip, yj - B * pasy / (D - B))
            -- end
            -- Zéro exact
            if A*B >= 0 and A*C >= 0 and math.abs(A) < eps then
                pts[#pts+1] = string.format("(%.6f,%.6g)", xi, yj)
            end
        end
    end
    return table.concat(pts, " ")
end
\end{luacode*}
\fi

\endinput